This post covers the following Ansible loop modules: with_items, with_nested, with_subelements, with_dict.
All of these with * are already deprecated and it is recommended to use loop.
One of my roles at Chromatic is as a member of the DevOps team. Among other things, this includes working with our servers and our clients' servers. This, in turn, means I spend a lot of time working with Ansible , a popular tool for provisioning, configuring, and deploying servers and applications.
Simply put, the machine running Ansible runs commands on another machine via SSH. These commands are specified declaratively (optional) using small sections of YAML called tasks. These TASKS call Ansible modules that specialize in executing options on certain components such as files, databases, etc.
For example, the following task uses the File module ( documentation , code ) to create a specific directory if it doesn't already exist, and modifies its attributes if they aren't already set correctly:
- file:
path: /home/jenkins/.ssh
state: directory
owner: jenkins
group: jenkins
mode: 700
Multiple tasks related to one task are grouped into roles, and multiple roles can be grouped into playbooks. You can then use the playbook to perform exactly the same configuration steps on any number of servers at the same time.
Is Ansible declarative?
TASKS Ansible , , TASKS. , , , . , Ansible Copy, . Ansible , :
- name: Copy SSH config file into Aliceβs .ssh directory.
copy:
src: files/config
dest: /home/alice/.ssh/config
owner: alice
group: alice
mode: 0600
, , bash, scp
, chown
chmod. Ansible , .
, , β , , .
, Ansible, β TASKS . , Ansible , , β PHP, .
, Ansible. Loops , Β«loops _ + lookup(), Β». (Lookups) β Ansible, Β« Ansible Β», Loops Ansible Github, .
Ansible Β« Β», , . Ansible, , , .
Ansible
TASKS, , , , , ( : , , , , !)
:
, :
alice
,bob
,carol
dan
.
, :
.ssh/
loops
.
, . , alice :
/home/alice/
βββ .ssh/
βββ bob/
βββ carol/
βββ dan/
βββ loops/
1. WITH_ITEMS
Ansible , chuck
, :
- name: Remove user βChuckβ from the system.
user:
name: chuck
state: absent
remove: yes
β , Chuck
Craig
β with_items
. with_items ( ), ( ):
- name: Remove users βChuckβ and βCraigβ from the system.
user:
name: "{{ item }}"
state: absent
remove: yes
with_items:
- chuck
- craig
, with_items , alice
bob
:
users_with_items:
- name: "alice"
personal_directories:
- "bob"
- "carol"
- "dan"
- name: "bob"
personal_directories:
- "alice"
- "carol"
- "dan"
TASKS
- name: "Loop 1: create users using 'with_items'."
user:
name: "{{ item.name }}"
with_items: "{{ users_with_items }}"
Ansible User users_with_items. , , , , personal_directories ( , personal_directories β ).
Ansible ( Ansible ): TASKS , . , , personal_directories
TASKS (. . User, File).
with_items
, PHP:
<?php
foreach ($users_with_items as $user) {
// Do something with $user...
}
, , :
β’ item.name
.
β’ with_items
, .
, Ansible item
, item.property
.
/home/
βββ alice/
βββ bob/
2: , WITH_NESTED
: 2 , 1. chown failed: failed to look up user
, users_with_items
1, , common_directories
, , . , ( PHP), -, :
<?php
foreach ($users_with_items as $user) {
foreach ($common_directories as $directory) {
// Create $directory for $user...
}
}
Ansible with_nested
. with_nested
, :
users_with_items:
- name: "alice"
personal_directories:
- "bob"
- "carol"
- "dan"
- name: "bob"
personal_directories:
- "alice"
- "carol"
- "dan"
common_directories:
- ".ssh"
- "loops
TASKS
# Note that this does not set correct permissions on /home/{{ item.x.name }}/.ssh!
- name: "Loop 2: create common users' directories using 'with_nested'."
file:
dest: "/home/{{ item.0.name }}/{{ item.1 }}"
owner: "{{ item.0.name }}"
group: "{{ item.0.name }}"
state: directory
with_nested:
- "{{ users_with_items }}"
- "{{ common_directories }}"
, with_nested , item.0
( users_with_items
) item.1
( common_directories
) . , , /home/alice/.ssh
.
/home/
βββ alice/
β βββ .ssh/
β βββ loops/
βββ bob/
βββ .ssh/
βββ loops/
3: , WITH_SUBELEMENTS
: 3 , 1. chown failed: failed to look up user
with_subelements
, users_with_items
1. PHP :
<?php
foreach ($users_with_items as $user) {
foreach ($user['personal_directories'] as $directory) {
// Create $directory for $user...
}
}
, $users_with_items
$user['personal_directories']
.
users_with_items:
- name: "alice"
personal_directories:
- "bob"
- "carol"
- "dan"
- name: "bob"
personal_directories:
- "alice"
- "carol"
- "dan"
TASKS
- name: "Loop 3: create personal users' directories using 'with_subelements'."
file:
dest: "/home/{{ item.0.name }}/{{ item.1 }}"
owner: "{{ item.0.name }}"
group: "{{ item.0.name }}"
state: directory
with_subelements:
- "{{ users_with_items }}"
- personal_directories
with_subelements
, with_nested
, , , β personal_directories. 2, ( ) /home/alice/bob
.
/home/
βββ alice/
β βββ .ssh/
β βββ bob/
β βββ carol/
β βββ dan/
β βββ loops/
βββ bob/
βββ .ssh/
βββ alice/
βββ carol/
βββ dan/
βββ loops/
4: WITH_DICT
3 , alice
bob
, , , carol
dan
. users_with_dict
Ansible with_dict
.
, (dict
dictionary
β Python ); with_dict
, . , Ansible, PHP :
<?php
foreach ($users_with_dict as $user => $properties) {
// Create a user named $user...
}
users_with_dict:
carol:
common_directories: "{{ common_directories }}"
dan:
common_directories: "{{ common_directories }}"
TASKS
- name: "Loop 4: create users using 'with_dict'."
user:
name: "{{ item.key }}"
with_dict: "{{ users_with_dict }}"
with_dict
. , , , dict
with_dict
(, , with_dict
).
/home/
βββ alice/
β βββ .ssh/
β βββ bob/
β βββ carol/
β βββ dan/
β βββ loops/
βββ bob/
β βββ .ssh/
β βββ alice/
β βββ carol/
β βββ dan/
β βββ loops/
βββ carol/
βββ dan/
5: ,
users_with_dict
, Ansible, -. alice
, bob
, carol
dan
, with_nested
/home/
. , , , TASKS:
- Ansible
- Ansible
- Jinja2 ()
- Jinja2 ()
common_directories:
- ".ssh"
- "loops"
TASKS
- name: "Get list of extant users."
shell: "find * -type d -prune | sort"
args:
chdir: "/home"
register: "home_directories"
changed_when: false
- name: "Loop 5: create personal user directories if they don't exist."
file:
dest: "/home/{{ item.0 }}/{{ item.1 }}"
owner: "{{ item.0 }}"
group: "{{ item.0 }}"
state: directory
with_nested:
- "{{ home_directories.stdout_lines }}"
- "{{ home_directories.stdout_lines | union(common_directories) }}"
when: "'{{ item.0 }}' != '{{ item.1 }}'"
TASKS: shell
find
, file .
/home
find \ -type d -prune | sort
( shell
) , /home
, , , .
home_directories
register: "home_directories"
. , , :
"stdout_lines": [
"alice",
"bob",
"carol",
"dan",
],
( ) with_nested
, :
-
with_nested
:
- "{{ home_directories.stdout_lines | union(common_directories) }}"
,
when
TASKS:
when: "'{{ item.0 }}' != '{{ item.1 }}'"
. with_nested
Jinja2 TASKS ( home_directories.stdout_lines
). Jinja:
- (
home_directories.stdout_lines
) - (
|
) - , (
union (common_directories)
)
, home_directories.stdout_lines
common_directories
:
item:
- .ssh
- alice
- bob
- carol
- dan
- loops
, with_nested
home_directories.stdout_lines
( with_nested
) , .
, β , , , ! (, /home/alice/alice
, /home/bob/bob
. .) Ansible β when
β :
when: "'{{ item.0 }}' != '{{ item.1 }}'"
, home_directories.stdout_lines
home_directories.stdout_lines
( Ansible Loops, Β«β¦ when
with_items
( ), when Β»). PHP , , :
<?php
$users = ['alice', 'bob', 'carol', 'dan'];
$common_directories = ['.ssh', 'loops'];
$directories = $user + $common_directories;
foreach ($users as $user) {
foreach ($directories as $directory) {
if ($directory != $user) {
// Create the directoryβ¦
}
}
}
, , .
/home/
βββ alice/
β βββ .ssh/
β βββ bob/
β βββ carol/
β βββ dan/
β βββ loops/
βββ bob/
β βββ .ssh/
β βββ alice/
β βββ carol/
β βββ dan/
β βββ loops/
βββ carol/
β βββ .ssh/
β βββ alice/
β βββ bob/
β βββ dan/
β βββ loops/
βββ dan/
βββ .ssh/
βββ alice/
βββ bob/
βββ carol/
βββ loops/
Ansible . ( Ansible), , (with_nested
? with_subitems
?) .
, , TASKS, ( , array_filter
, array_reduce
, array_map
, ). , , β β .
Hopefully this post will help you out of my initial difficulty. To this end, I built a Vagrant virtual machine (Vagrant natively supports using Ansible for provisioning) and an Ansible playbook that I used to create and test these examples). Just follow the instructions in the README to run the examples from this post or try your own. If you have any questions or comments, tweet at @chromaticHQ!