迴圈¶
通常你想在一個任務中幹很多事,比如建立一群使用者,安裝很多包,或者重複一個輪詢步驟直到收到某個特定結果.
本章將對在playbook中如何使用迴圈做全面的介紹.
Topics
標準迴圈¶
為了保持簡潔,重複的任務可以用以下簡寫的方式:
- name: add several users
user: name={{ item }} state=present groups=wheel
with_items:
- testuser1
- testuser2
如果你在變數檔案中或者 ‘vars’ 區域定義了一組YAML列表,你也可以這樣做:
with_items: "{{somelist}}"
以上寫法與下面是完全等同的:
- name: add user testuser1
user: name=testuser1 state=present groups=wheel
- name: add user testuser2
user: name=testuser2 state=present groups=wheel
yum和apt模組中使用with_items執行時會有較少的包管理事務.
請note使用 ‘with_items’ 用於迭代的條目類型不僅僅支援簡單的字元串列表.如果你有一個雜湊列表,那麼你可以用以下方式來引用子項:
- name: add several users
user: name={{ item.name }} state=present groups={{ item.groups }}
with_items:
- { name: 'testuser1', groups: 'wheel' }
- { name: 'testuser2', groups: 'root' }
請note如果同時使用 when 和 with_items (或其它迴圈聲明),`when`聲明會為每個條目單獨執行.請參見 the_when_statement 示例.
巢狀迴圈¶
迴圈也可以巢狀:
- name: give users access to multiple databases
mysql_user: name={{ item[0] }} priv={{ item[1] }}.*:ALL append_privs=yes password=foo
with_nested:
- [ 'alice', 'bob' ]
- [ 'clientdb', 'employeedb', 'providerdb' ]
和以上介紹的’with_items’一樣,你也可以使用預定義變數.:
- name: here, 'users' contains the above list of employees
mysql_user: name={{ item[0] }} priv={{ item[1] }}.*:ALL append_privs=yes password=foo
with_nested:
- "{{users}}"
- [ 'clientdb', 'employeedb', 'providerdb' ]
對雜湊表使用迴圈¶
New in version 1.5.
假如你有以下變數:
---
users:
alice:
name: Alice Appleworth
telephone: 123-456-7890
bob:
name: Bob Bananarama
telephone: 987-654-3210
你想打印出每個使用者的名稱和電話號碼.你可以使用 with_dict
來迴圈雜湊表中的元素:
tasks:
- name: Print phone records
debug: msg="User {{ item.key }} is {{ item.value.name }} ({{ item.value.telephone }})"
with_dict: "{{users}}"
對檔案列表使用迴圈¶
with_fileglob
可以以非遞迴的方式來模式匹配單個目錄中的檔案.如下面所示:
---
- hosts: all
tasks:
# first ensure our target directory exists
- file: dest=/etc/fooapp state=directory
# copy each file over that matches the given pattern
- copy: src={{ item }} dest=/etc/fooapp/ owner=root mode=600
with_fileglob:
- /playbooks/files/fooapp/*
Note
當在role中對 with_fileglob
使用相對路徑時, Ansible會把路徑對映到`roles/<rolename>/files`目錄.
對並行資料集使用迴圈¶
Note
這是一個不常見的使用方式,但為了文件完整性我們還是把它寫出來.你可能不會經常使用這種方式.
假設你通過某種方式載入了以下變數資料:
---
alpha: [ 'a', 'b', 'c', 'd' ]
numbers: [ 1, 2, 3, 4 ]
如果你想得到’(a, 1)’和’(b, 2)’之類的集合.可以使用’with_together’:
tasks:
- debug: msg="{{ item.0 }} and {{ item.1 }}"
with_together:
- "{{alpha}}"
- "{{numbers}}"
對子元素使用迴圈¶
假設你想對一組使用者做一些動作,比如建立這些使用者,並且允許它們使用一組SSH key來登入.
如何實現那? 先假設你有按以下方式定義的資料,可以通過”vars_files”或”group_vars/all”檔案載入:
---
users:
- name: alice
authorized:
- /tmp/alice/onekey.pub
- /tmp/alice/twokey.pub
mysql:
password: mysql-password
hosts:
- "%"
- "127.0.0.1"
- "::1"
- "localhost"
privs:
- "*.*:SELECT"
- "DB1.*:ALL"
- name: bob
authorized:
- /tmp/bob/id_rsa.pub
mysql:
password: other-mysql-password
hosts:
- "db1"
privs:
- "*.*:SELECT"
- "DB2.*:ALL"
那麼可以這樣實現:
- user: name={{ item.name }} state=present generate_ssh_key=yes
with_items: "{{users}}"
- authorized_key: "user={{ item.0.name }} key='{{ lookup('file', item.1) }}'"
with_subelements:
- users
- authorized
根據mysql hosts以及預先給定的privs subkey列表,我們也可以在巢狀的subkey中迭代列表:
- name: Setup MySQL users
mysql_user: name={{ item.0.user }} password={{ item.0.mysql.password }} host={{ item.1 }} priv={{ item.0.mysql.privs | join('/') }}
with_subelements:
- users
- mysql.hosts
Subelements walks a list of hashes (aka dictionaries) and then traverses a list with a given key inside of those records.
你也可以為字元素列表新增第三個元素,該元素可以放置標誌位字典.現在你可以加入’skip_missing’標誌位.如果設定為True,那麼查詢外掛會跳過不包含指定子鍵的列表條目.如果沒有該標誌位,或者標誌位值為False,外掛會產生錯誤並指出缺少該子鍵.
這就是authorized_key模式中key的獲取方式.
對整數序列使用迴圈¶
with_sequence
可以以升序數字順序生成一組序列.你可以指定起始值、終止值,以及一個可選的步長值.
指定參數時也可以使用key=value這種鍵值對的方式.如果採用這種方式,’format’是一個可列印的字元串.
數字值可以被指定為10進位制,16進位制(0x3f8)或者八進位制(0600).負數則不受支援.請看以下示例:
---
- hosts: all
tasks:
# create groups
- group: name=evens state=present
- group: name=odds state=present
# create some test users
- user: name={{ item }} state=present groups=evens
with_sequence: start=0 end=32 format=testuser%02x
# create a series of directories with even numbers for some reason
- file: dest=/var/stuff/{{ item }} state=directory
with_sequence: start=4 end=16 stride=2
# a simpler way to use the sequence plugin
# create 4 groups
- group: name=group{{ item }} state=present
with_sequence: count=4
隨機選擇¶
‘random_choice’功能可以用來隨機獲取一些值.它並不是負載均衡器(已經有相關的模組了).它有時可以用作一個簡化版的負載均衡器,比如作為條件判斷:
- debug: msg={{ item }}
with_random_choice:
- "go through the door"
- "drink from the goblet"
- "press the red button"
- "do nothing"
提供的字元串中的其中一個會被隨機選中.
還有一個基本的場景,該功能可用於在一個可預測的自動化環境中新增混亂和興奮點.
Do-Until迴圈¶
有時你想重試一個任務直到達到某個條件.比如下面這個例子:
- action: shell /usr/bin/foo
register: result
until: result.stdout.find("all systems go") != -1
retries: 5
delay: 10
上面的例子遞迴執行shell模組,直到模組結果中的stdout輸出中包含”all systems go”字元串,或者該任務按照10秒的延遲重試超過5次.”retries”和”delay”的預設值分別是3和5.
該任務返回最後一個任務返回的結果.單次重試的結果可以使用-vv選項來檢視. 被註冊的變數會有一個新的屬性’attempts’,值為該任務重試的次數.
查詢第一個匹配的檔案¶
Note
這是一個不常見的使用方式,但為了文件完整性我們還是把它寫出來.你可能不會經常使用這種方式.
這其實不是一個迴圈,但和迴圈很相似.如果你想引用一個檔案,而該檔案是從一組檔案中根據給定條件匹配出來的.這組檔案中部分檔名由變數拼接而成.針對該場景你可以這樣做:
- name: INTERFACES | Create Ansible header for /etc/network/interfaces
template: src={{ item }} dest=/etc/foo.conf
with_first_found:
- "{{ansible_virtualization_type}}_foo.conf"
- "default_foo.conf"
該功能還有一個更完整的版本,可以配置搜尋路徑.請看以下示例:
- name: some configuration template
template: src={{ item }} dest=/etc/file.cfg mode=0444 owner=root group=root
with_first_found:
- files:
- "{{inventory_hostname}}/etc/file.cfg"
paths:
- ../../../templates.overwrites
- ../../../templates
- files:
- etc/file.cfg
paths:
- templates
迭代程式的執行結果¶
Note
這是一個不常見的使用方式,但為了文件完整性我們還是把它寫出來.你可能不會經常使用這種方式.
有時你想執行一個程式,而且按行迴圈該程式的輸出.Ansible提供了一個優雅的方式來實現這一點.但請記住,該功能始終在控制機上執行,而不是本地機器:
- name: Example of looping over a command result
shell: /usr/bin/frobnicate {{ item }}
with_lines: /usr/bin/frobnications_per_host --param {{ inventory_hostname }}
好吧,這好像有點隨意.事實上,如果你在做一些與inventory有關的事情,比如你想編寫一個動態的inventory源(參見 動態 Inventory),那麼藉助該功能能夠快速實現.
如果你想遠端執行命令,那麼以上方法則不行.但你可以這樣寫:
- name: Example of looping over a REMOTE command result
shell: /usr/bin/something
register: command_result
- name: Do something with each result
shell: /usr/bin/something_else --param {{ item }}
with_items: "{{command_result.stdout_lines}}"
使用索引迴圈列表¶
Note
這是一個不常見的使用方式,但為了文件完整性我們還是把它寫出來.你可能不會經常使用這種方式.
如果你想迴圈一個列表,同時得到一個數字索引來標明你當前處於列表什麼位置,那麼你可以這樣做.雖然該方法不太常用:
- name: indexed loop demo
debug: msg="at array position {{ item.0 }} there is a value {{ item.1 }}"
with_indexed_items: "{{some_list}}"
迴圈配置檔案¶
ini外掛可以使用正則表示式來獲取一組鍵值對.因此,我們可以遍歷該集合.以下是我們使用的ini檔案:
[section1]
value1=section1/value1
value2=section1/value2
[section2]
value1=section2/value1
value2=section2/value2
以下是使用 with_ini
的例子:
- debug: msg="{{item}}"
with_ini: value[1-2] section=section1 file=lookup.ini re=true
以下是返回的值:
{
"changed": false,
"msg": "All items completed",
"results": [
{
"invocation": {
"module_args": "msg=\"section1/value1\"",
"module_name": "debug"
},
"item": "section1/value1",
"msg": "section1/value1",
"verbose_always": true
},
{
"invocation": {
"module_args": "msg=\"section1/value2\"",
"module_name": "debug"
},
"item": "section1/value2",
"msg": "section1/value2",
"verbose_always": true
}
]
}
扁平化列表¶
Note
這是一個不常見的使用方式,但為了文件完整性我們還是把它寫出來.你可能不會經常使用這種方式.
在罕見的情況下,你可能有幾組列表,列表中會巢狀列表.而你只是想迭代所有列表中的每個元素.比如有一個非常瘋狂的假定的資料結構:
----
# file: roles/foo/vars/main.yml
packages_base:
- [ 'foo-package', 'bar-package' ]
packages_apps:
- [ ['one-package', 'two-package' ]]
- [ ['red-package'], ['blue-package']]
你可以看到列表中的包到處都是.那麼如果想安裝兩個列表中的所有包那?:
- name: flattened loop demo
yum: name={{ item }} state=installed
with_flattened:
- packages_base
- packages_apps
這就行了!
迴圈中使用註冊器¶
當對處於迴圈中的某個資料結構使用 register
來註冊變數時,結果包含一個 results
屬性,這是從模組中得到的所有響應的一個列表.
以下是在 with_items
中使用 register
的示例:
- shell: echo "{{ item }}"
with_items:
- one
- two
register: echo
返回的資料結構如下,與非迴圈結構中使用 register
的返回結果是不同的:
{
"changed": true,
"msg": "All items completed",
"results": [
{
"changed": true,
"cmd": "echo \"one\" ",
"delta": "0:00:00.003110",
"end": "2013-12-19 12:00:05.187153",
"invocation": {
"module_args": "echo \"one\"",
"module_name": "shell"
},
"item": "one",
"rc": 0,
"start": "2013-12-19 12:00:05.184043",
"stderr": "",
"stdout": "one"
},
{
"changed": true,
"cmd": "echo \"two\" ",
"delta": "0:00:00.002920",
"end": "2013-12-19 12:00:05.245502",
"invocation": {
"module_args": "echo \"two\"",
"module_name": "shell"
},
"item": "two",
"rc": 0,
"start": "2013-12-19 12:00:05.242582",
"stderr": "",
"stdout": "two"
}
]
}
隨後的任務可以用以下方式來迴圈註冊變數,用來檢查結果值:
- name: Fail if return code is not 0
fail:
msg: "The command ({{ item.cmd }}) did not have a 0 return code"
when: item.rc != 0
with_items: "{{echo.results}}"
自定義迭代¶
雖然你通常無需自定義實現自己的迭代,但如果你想按你自己的方式來迴圈任意資料結構,你可以閱讀:doc:developing_plugins 來作為開始.以上的每個功能都以外掛的方式來實現,所以有很多的實現可供引用.
See also
- Playbooks
- An introduction to playbooks
- Playbook Roles and Include Statements
- Playbook organization by roles
- 最佳實踐
- Best practices in playbooks
- 條件選擇
- Conditional statements in playbooks
- Variables
- All about variables
- User Mailing List
- Have a question? Stop by the google group!
- irc.freenode.net
- #ansible IRC chat channel