迴圈

通常你想在一個任務中幹很多事,比如建立一群使用者,安裝很多包,或者重複一個輪詢步驟直到收到某個特定結果.

本章將對在playbook中如何使用迴圈做全面的介紹.

標準迴圈

為了保持簡潔,重複的任務可以用以下簡寫的方式:

- 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如果同時使用 whenwith_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