I encountered this problem in a setup where I needed to create multiple deployment environments (live, demo, sandbox) to the same physical server (not allowed virtual machines here), and then a script to deploy arbitrary svn repos
This required a directory tree of (optional) variable.yml files, that would merge ontop of each other and not throw an exception if any where missing
Start by enabling variable merging in ansible
- note that this does shallow hash merging (1 level deep) and not fully recursive deep merge
ansible.cfg
[defaults]
hash_behaviour=merge ;; merge rather than replace dictionaries http://docs.ansible.com/ansible/intro_configuration.html###hash-behaviour
Ansible directory layout
/group_vars
└── all.yml
/playbooks
├── boostrap.yml
├── demo.yml
├── live.yml
└── sandbox.yml
/roles/deploy/
├── files
├── tasks
│ ├── includes.yml
│ ├── main.yml
└── vars
├── main.yml
├── project_1.yml
├── project_2.yml
├── demo
│ ├── project_1.yml
│ ├── project_2.yml
│ └── main.yml
├── live
│ ├── project_1.yml
│ ├── project_2.yml
│ └── main.yml
└── sandbox
├── project_1.yml
├── project_2.yml
└── main.yml
roles/deploy/tasks/includes.yml
This is the main logic for a directory tree of optional variable files.
;; imports in this order:
;; - /roles/deploy/vars/main.yml
;; - /roles/deploy/vars/{{ project_name }}.yml
;; - /roles/deploy/vars/{{ project_name }}/main.yml
;; - /roles/deploy/vars/{{ project_name }}/{{ project_env }}.yml
- include_vars:
dir: 'vars'
files_matching: "{{ item }}"
depth: 1
with_items:
- "main.yml"
- "{{ project_name }}.yml"
- include_vars:
dir: 'vars/{{ env_name }}'
files_matching: "{{ item }}"
depth: 1
with_items:
- "main.yml"
- "{{ project_name }}.yml"
group_vars/all.yml
Configure default variables for the project and various users and environments
project_users:
bootstrap:
env: bootstrap
user: ansible
group: ansible
mode: 755
root: /cs/ansible/
home: /cs/ansible/home/ansible/
directories:
- /cs/ansible/
- /cs/ansible/home/
live:
env: live
user: ansible-live
group: ansible
mode: 755
root: /cs/ansible/live/
home: /cs/ansible/home/ansible-live/
demo:
env: demo
user: ansible-demo
group: ansible
mode: 755
root: /cs/ansible/demo/
home: /cs/ansible/home/ansible-demo/
sandbox:
env: sandbox
user: ansible-sandbox
group: ansible
mode: 755
root: /cs/ansible/sandbox/
home: /cs/ansible/home/ansible-sandbox/
project_env: bootstrap
project_user: "{{ ansible_users[project_env] }}" ;; this will be retroactively updated if project_env is redefined later
roles/deploy/vars/main.yml
project defaults
ansible_project:
node_env: development
node_port: 4200
nginx_port: 4400
roles/deploy/vars/project_1.yml
defaults for project_1
ansible_project:
node_port: 4201
nginx_port: 4401
roles/deploy/vars/live/main.yml
defaults for live environment, overrides project defaults
ansible_project:
node_env: production
roles/deploy/vars/live/project_1.yml
final overrides for project_1 in the live environment
ansible_project:
nginx_port: 80
playbooks/demo.yml
Configure separate playbooks for each environment
- hosts: shared_server
remote_user: ansible-demo
vars:
project_env: demo
pre_tasks:
- debug: "msg='{{ facter_gid }}@{{ facter_fqdn }} ({{ server_pseudonym }})'"
- debug: var=project_ssh_user
roles:
- { role: deploy, project_name: project_1 }
WARNING: Because all environments live on a single host, all playbooks must be run individually, otherwise Ansible will brokenly attempt to run all scripts as the first ssh login user and only use the variables for the first user. If you need to run all scripts sequentially, then use xargs to run them each as separate commands.
find ./playbooks/*.yml | xargs -L1 time ansible-playbook