Reputation: 83
I'm thinking about deploying rails apps on hosts in three different environments(prod/stag/dev) using Ansible. But struggling to set different RAILS_ENV
for each host.
I've tried with the following setup.
Directory structure:
.
├── group_vars
│ └── app1.yml
├── inventories
│ ├── develop
│ │ └── group_vars
│ │ └── app1.yml
│ ├── production
│ └── staging
└── roles
group_vars/app1.yml
(for common vars across environments)
services:
- name: app
environment:
{env_vars other than RAILS_ENV}: foo
- name: db
...
inventories/develop/group_vars/app1.yml
(for inventory specific vars)
services:
- name: app
environment:
RAILS_ENV: development
It seems Ansible only looks at keys at top level when judging if the variable is already defined. So the RAILS_ENV
in the second file gets unintentionally ignored.
(Not sure why inventory vars are lower in precedence order, I think it should be higher because it is more specific?)
Is there any clean way to do this?
Upvotes: 2
Views: 1527
Reputation: 39264
As suggested in the hash behaviour article of Ansible documentation:
DEFAULT_HASH_BEHAVIOUR
Description: This setting controls how variables merge in Ansible. By default Ansible will override variables in specific precedence orders, as described in Variables. When a variable of higher precedence wins, it will replace the other value. Some users prefer that variables that are hashes (aka ‘dictionaries’ in Python terms) are merged. This setting is called ‘merge’. This is not the default behavior and it does not affect variables whose values are scalars (integers, strings) or arrays. We generally recommend not using this setting unless you think you have an absolute need for it, and playbooks in the official examples repos do not use this setting In version 2.0 a
combine
filter was added to allow doing this for a particular variable (described in Filters).
Source: https://docs.ansible.com/ansible/latest/reference_appendices/config.html#default-hash-behaviour
The recommended way to achieve this would be to use the combine
filter.
This said, you do have a list inside your dictionary, and this will make it quite a complex task to achieve.
Switching it to a dictionary would ease the pain:
services:
app:
environment:
RAILS_ENV: development
overridable_var: foo
db:
foo: bar
If I understand it properly, you are trying to put in place the alternative directory layout of Ansible best practices.
If this is the case, here could be a solution matching your use case, given that you can transpose your list inside services
in a dictionary, as proposed above:
services
dictionary key by the name of the environment, e.g. develop_services
develop_services:
app:
environment:
RAILS_ENV: development
overridable_var: develop from inventories group_vars
pre_task
, combine
the services
dictionary with the one matching your environment, which you can get as part of your inventory_file
path:
pre_tasks:
- set_fact:
services: "{{ services | combine(vars[inventory_file.split('/')[-2] ~ '_services'], recursive=True) }}"
Then just use it.
Given the directory layout:
.
├── group_vars
│ └── app1.yml
├── inventories
│ ├── develop
│ │ ├── group_vars
│ │ │ └── app1.yml
│ │ └── hosts
│ └── staging
│ ├── group_vars
│ │ └── app1.yml
│ └── hosts
└── play.yml
group_vars/app1.yml
services:
app:
environment:
bar: foo
overridable_var: from root group_vars
do_not_override_me: from root group_vars
db:
engine: postgres
inventories/develop/hosts
all:
children:
app1:
hosts:
app:
inventories/develop/group_vars/app1.yml
develop_services:
app:
environment:
RAILS_ENV: development
overridable_var: develop from inventories group_vars
inventories/staging/hosts
all:
children:
app1:
hosts:
app:
inventories/staging/group_vars/app1.yml
staging_services:
app:
environment:
RAILS_ENV: staging
overridable_var: staging from inventories group_vars
play.yml
- hosts: all
gather_facts: no
pre_tasks:
- set_fact:
services: "{{ services | combine(vars[inventory_file.split('/')[-2] ~ '_services'], recursive=True) }}"
tasks:
- debug:
msg: "{{ services }}"
Running it for develop
would give the recap:
$ ansible-playbook play.yml -i inventories/develop
PLAY [all] *********************************************************************************************************
TASK [set_fact] ****************************************************************************************************
ok: [app]
TASK [debug] *******************************************************************************************************
ok: [app] => {
"msg": {
"app": {
"environment": {
"RAILS_ENV": "development",
"bar": "foo",
"do_not_override_me": "from root group_vars",
"overridable_var": "develop from inventories group_vars"
}
},
"db": {
"engine": "postgres"
}
}
}
PLAY RECAP *********************************************************************************************************
app : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Running it for staging
would give the recap:
$ ansible-playbook play.yml -i inventories/staging
PLAY [all] *********************************************************************************************************
TASK [set_fact] ****************************************************************************************************
ok: [app]
TASK [debug] *******************************************************************************************************
ok: [app] => {
"msg": {
"app": {
"environment": {
"RAILS_ENV": "staging",
"bar": "foo",
"do_not_override_me": "from root group_vars",
"overridable_var": "staging from inventories group_vars"
}
},
"db": {
"engine": "postgres"
}
}
}
PLAY RECAP *********************************************************************************************************
app : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
An extra note: mind that this would not allow the playbook to be run on two environment at the same time, as pointed by the note under the section using multiple inventory sources of the documentation:
Keep in mind that if there are variable conflicts in the inventories, they are resolved according to the rules described in How variables are merged and Variable precedence: Where should I put a variable?. The merging order is controlled by the order of the inventory source parameters. If
[all:vars]
in staging inventory definesmyvar = 1
, but production inventory definesmyvar = 2
, the playbook will be run withmyvar = 2
. The result would be reversed if the playbook was run with-i production -i staging
.
Upvotes: 2