Ansible 2.0 移植指南

本节讨论 Ansible 1.x 和 Ansible 2.0 之间的行为变化。

旨在帮助您更新您的剧本、插件以及 Ansible 基础架构的其他部分,以便它们能够与这个版本的 Ansible 兼容。

我们建议您阅读此页面以及 Ansible 2.0 变更日志,以了解您可能需要进行哪些更新。

本文档是关于移植的集合的一部分。完整的移植指南列表可以在 移植指南 找到。

剧本

本节讨论您可能需要对剧本进行的任何更改。

  • 1.9.x 中的语法

- debug:
    msg: "{{ 'test1_junk 1\\\\3' | regex_replace('(.*)_junk (.*)', '\\\\1 \\\\2') }}"
  • 2.0.x 中的语法

- debug:
    msg: "{{ 'test1_junk 1\\3' | regex_replace('(.*)_junk (.*)', '\\1 \\2') }}"
  • 输出

"msg": "test1 1\\3"

要创建一个在所有版本中都能工作的转义字符串,您有两个选择

- debug: msg="{{ 'test1_junk 1\\3' | regex_replace('(.*)_junk (.*)', '\\1 \\2') }}"

使用 key=value 转义,这没有改变。另一个选择是检查 ansible 版本

"{{ (ansible_version|version_compare('2.0', 'ge'))|ternary( 'test1_junk 1\\3' | regex_replace('(.*)_junk (.*)', '\\1 \\2') , 'test1_junk 1\\\\3' | regex_replace('(.*)_junk (.*)', '\\\\1 \\\\2') ) }}"
  • 尾随换行符 当通过 yaml 字典格式在剧本中指定带有尾随换行符的字符串时,尾随换行符会被去除。当以 key=value 格式指定时,尾随换行符会被保留。在 v2 中,这两种指定字符串的方法都会保留尾随换行符。如果您依赖于尾随换行符被去除,您可以使用以下示例更改您的剧本

    * Syntax in 1.9.x
    
    vars:
      message: >
        Testing
        some things
    tasks:
    - debug:
        msg: "{{ message }}"
    
    • 2.0.x 中的语法

    vars:
      old_message: >
        Testing
        some things
      message: "{{ old_message[:-1] }}"
    - debug:
        msg: "{{ message }}"
    
    • 输出

    "msg": "Testing some things"
    
  • Ansible v2 更改了模板化 DOS 类型文本文件的方式。

    Ansible v1 中的一个错误会导致 DOS 类型文本文件(使用回车符和换行符)被模板化为 Unix 类型文本文件(只使用换行符)。在 Ansible v2 中,这个长期存在的错误终于被修复了,DOS 类型文本文件被正确地保留了。当您期望您的剧本在迁移到 Ansible v2 时不会显示任何差异时,这可能会令人困惑,而实际上您会看到每个 DOS 类型文件都被完全替换了(看起来内容完全相同)。

  • 当将复杂的参数指定为变量时,变量必须使用完整的 jinja2 变量语法(`{{var_name}}`)— 不再接受裸变量名。事实上,即使使用变量指定参数也被弃用,并且在未来的版本中将不被允许。

---
- hosts: localhost
  connection: local
  gather_facts: false
  vars:
    my_dirs:
      - { path: /tmp/3a, state: directory, mode: 0755 }
      - { path: /tmp/3b, state: directory, mode: 0700 }
  tasks:
    - file:
      args: "{{item}}"
      with_items: "{{my_dirs}}"
  • 移植任务 include

  • 更动态。原本不应该工作的极端情况格式现在按预期不起作用。

  • 在 yaml 字典格式中定义的变量,请参见 issue 13324

  • 在保留原始值而不是将所有内容转换为字符串方面,模板化(剧本中的变量和模板查找)得到了改进。如果您需要旧的行为,请引用该值以将其作为字符串传递。

  • 空变量和在 yaml 中设置为 null 的变量不再转换为空字符串。它们将保留 None 的值。您可以通过设置 ANSIBLE_NULL_REPRESENTATION 环境变量,在您的配置文件中将 null_representation 设置为空字符串来覆盖它。

  • 必须在 ansible.cfg 中启用额外回调。不再需要复制,但您必须在 ansible.cfg 中启用它们。

  • dnf 模块已重写。可能会观察到一些细微的行为变化。

  • win_updates 已重写,现在按预期工作。

  • 从 2.0.1 开始,gather_facts 的隐式 setup 任务现在正确地继承了来自 play 的所有内容,但这可能会导致那些在 play 级别设置 environment 并依赖于 ansible_env 存在的人遇到问题。以前这是被忽略的,但现在可能会发出“未定义”错误。

已弃用

虽然此处列出的所有项目都会显示弃用警告消息,但它们仍然像在 1.9.x 中一样工作。请注意,它们将在 2.2 中删除(Ansible 总是等待两个主要版本才能删除弃用的功能)。

  • with_ 循环中的裸变量应该改为使用 "{{ var }}" 语法,这有助于消除歧义。

  • ansible-galaxy 文本格式需求文件。用户应该改用 YAML 格式的需求文件。

  • with_ 循环列表中的未定义变量目前不会中断循环,但会发出警告;将来,它们会发出错误。

  • 使用字典变量设置所有任务参数是不安全的,并且将在未来的版本中删除。已弃用变体的示例

- hosts: localhost
  gather_facts: no
  vars:
    debug_params:
      msg: "hello there"
  tasks:
    - debug: "{{debug_params}}"
    - debug:
      args: "{{debug_params}}"

推荐变体的示例

- hosts: localhost
  gather_facts: no
  vars:
    debug_params:
      msg: "hello there"
  tasks:
    - debug:
      msg: "{{debug_params['msg']}}"
  • 主机模式应该使用逗号 (,) 或冒号 (:) 来代替分号 (;) 分隔主机/组。

  • 主机模式中指定的范围应该使用 [x:y] 语法,而不是 [x-y]。

  • 使用权限提升的剧本应该始终使用“become*”选项,而不是旧的 su*/sudo* 选项。

  • vars_prompt 的“简写形式”不再受支持。例如

    vars_prompt:
        variable_name: "Prompt string"
    
  • 不再支持在任务 include 语句的顶层指定变量。例如

    - include_tasks: foo.yml
        a: 1
    

现在应该是

- include_tasks: foo.yml
  vars:
    a: 1
  • 不再支持在任务上设置 any_errors_fatal。这应该只在 play 级别设置。

  • 不再支持 environment 字典(对于 play/task 等)中的裸变量。在那里指定的变量应该使用完整的变量语法:'{{foo}}'。

  • 标签(或任何指令)不再应该与任务 include 中的其他参数一起指定。相反,它们应该作为任务上的选项指定。例如

    - include_tasks: foo.yml tags=a,b,c
    

    应该是

    - include_tasks: foo.yml
      tags: [a, b, c]
    
  • 任务上的 first_available_file 选项已被弃用。用户应该使用 with_first_found 选项或查找('first_found',…)插件。

其他注意事项

以下是一些更新时遇到的极端情况。这些主要由更严格的解析器验证和对以前被忽略的错误的捕获引起。

  • 错误的变量组合

    with_items: myvar_{{rest_of_name}}
    

    这“偶然”有效,因为错误被重新模板化并最终解析了变量,它从未被设计为有效的语法,现在会正确返回错误,请改为使用以下方法。

    hostvars[inventory_hostname]['myvar_' + rest_of_name]
    
  • 拼写错误的指令

    - task: dostuf
      becom: yes
    

    该任务始终在不使用权限提升的情况下运行(为此您需要 become),但也静默忽略,因此即使不应该运行,play 也“运行”了,现在这是一个解析错误。

  • 重复的指令

    - task: dostuf
      when: True
      when: False
    

    第一个 when 被忽略,只有第二个被使用,play 在没有警告它忽略了一个指令的情况下运行,现在这会产生一个解析错误。

  • 混淆变量和指令

    - role: {name=rosy, port=435 }
    
    # in tasks/main.yml
    - wait_for: port={{port}}
    

    port 变量被保留为 play/task 指令,用于覆盖连接端口,在以前的版本中,这与名为 port 的变量混淆了,并且可以在 play 中稍后使用,如果主机尝试重新连接或使用非缓存连接,这会产生问题。现在它将被正确识别为指令,并且 port 变量将显示为未定义,这现在强制使用不冲突的名称,并在向角色调用添加设置和变量时消除歧义。

  • with_ 的裸操作

    with_items: var1 + var2
    

    “裸变量”功能存在一个问题,该功能原本旨在仅模板化单个变量而无需使用大括号({{ }}),但在某些版本的 Ansible 中会模板化完整的表达式。现在,除了条件语句(when)外,所有表达式都需要使用正确的模板和括号。

    with_items: "{{var1 + var2}}"
    

    “裸变量”功能本身已被弃用,因为未定义的变量与字符串无法区分,这使得难以显示正确的错误。

插件移植

在 Ansible-1.9.x 中,通常会复制现有插件来创建新插件。只需实现插件调用者期望的方法和属性,即可使其成为该类型的插件。在 Ansible-2.0 中,大多数插件都是通过为每种插件类型子类化基类来实现的。这样,自定义插件就不需要包含未自定义的方法。

查找插件

  • 查找插件;导入版本

连接插件

  • 连接插件

动作插件

  • 动作插件

回调插件

虽然 Ansible 2.0 提供了新的回调 API,但旧的 API 仍然适用于大多数回调插件。但是,如果您的回调插件使用了 self.playbookself.playself.task,则您必须自己存储这些值,因为 Ansible 不再自动用它们填充回调。下面是一个简短的代码片段,向您展示如何操作

import os
from ansible.plugins.callback import CallbackBase

class CallbackModule(CallbackBase):
    def __init__(self):
        self.playbook = None
        self.playbook_name = None
        self.play = None
        self.task = None

    def v2_playbook_on_start(self, playbook):
        self.playbook = playbook
        self.playbook_name = os.path.basename(self.playbook._file_name)

    def v2_playbook_on_play_start(self, play):
        self.play = play

    def v2_playbook_on_task_start(self, task, is_conditional):
        self.task = task

    def v2_on_any(self, *args, **kwargs):
        self._display.display('%s: %s: %s' % (self.playbook_name,
        self.play.name, self.task))

连接插件

  • 连接插件

混合插件

在特定情况下,您可能需要一个同时支持 Ansible-1.9.x 和 Ansible-2.0 的插件。与将插件从 v1 移植到 v2 类似,您需要了解插件在每个版本中的工作方式,并同时满足这两个要求。

由于 Ansible-2.0 插件系统更先进,因此更容易调整您的插件以针对 Ansible-1.9.x 提供类似的组件(子类、方法),因为 Ansible-2.0 期望这样做。这样,您的代码看起来会更简洁。

您可能会发现以下提示很有用:

  • 检查 Ansible-2.0 类是否存在,如果它们不存在(Ansible-1.9.x),则使用所需的方法(例如,__init__)模拟它们。

  • 当导入 Ansible-2.0 Python 模块失败时(Ansible-1.9.x),捕获 ImportError 异常并执行 Ansible-1.9.x 的等效导入。可能需要进行转换(例如,导入特定方法)。

  • 使用这些方法的存在作为限定符来判断您正在运行哪个版本的 Ansible。因此,与其使用版本检查,不如使用功能检查。(请参见下面的示例)

  • 为每个 if-then-else 案例记录哪个特定版本需要哪个代码块。这将帮助其他人了解他们如何调整他们的插件,但它也会帮助您在弃用旧的 Ansible-1.9.x 支持时将其删除。

  • 在进行插件开发时,在开发过程中使用 warning() 方法非常有用,但是为死锁(您预计永远不会触发的案例)或极端情况(例如,您预计会发生错误配置的案例)发出警告也很重要。

  • 查看 Ansible-1.9.x 和 Ansible-2.0 中的其他插件,以了解 API 的工作方式以及可用的模块、类和方法,这将很有帮助。

查找插件

作为一个简单的示例,我们将创建一个混合 fileglob 查找插件。

from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

import os
import glob

try:
    # ansible-2.0
    from ansible.plugins.lookup import LookupBase
except ImportError:
    # ansible-1.9.x

    class LookupBase(object):
        def __init__(self, basedir=None, runner=None, **kwargs):
            self.runner = runner
            self.basedir = self.runner.basedir

        def get_basedir(self, variables):
            return self.basedir

try:
    # ansible-1.9.x
    from ansible.utils import (listify_lookup_plugin_terms, path_dwim, warning)
except ImportError:
    # ansible-2.0
    from ansible.utils.display import Display
    warning = Display().warning

class LookupModule(LookupBase):

    # For ansible-1.9.x, we added inject=None as valid argument
    def run(self, terms, inject=None, variables=None, **kwargs):

        # ansible-2.0, but we made this work for ansible-1.9.x too !
        basedir = self.get_basedir(variables)

        # ansible-1.9.x
        if 'listify_lookup_plugin_terms' in globals():
            terms = listify_lookup_plugin_terms(terms, basedir, inject)

        ret = []
        for term in terms:
            term_file = os.path.basename(term)

            # For ansible-1.9.x, we imported path_dwim() from ansible.utils
            if 'path_dwim' in globals():
                # ansible-1.9.x
                dwimmed_path = path_dwim(basedir, os.path.dirname(term))
            else:
                # ansible-2.0
                dwimmed_path = self._loader.path_dwim_relative(basedir, 'files', os.path.dirname(term))

            globbed = glob.glob(os.path.join(dwimmed_path, term_file))
            ret.extend(g for g in globbed if os.path.isfile(g))

        return ret

注意

在上面的示例中,我们没有使用 warning() 方法,因为我们在最终版本中没有直接使用它。但是,我们保留了这段代码,以便人们可以在开发/移植/使用过程中使用这部分代码。

连接插件

  • 连接插件

动作插件

  • 动作插件

回调插件

  • 回调插件

连接插件

  • 连接插件

自定义脚本移植

在 1.x 版本中使用 ansible.runner.Runner API 的自定义脚本必须在 2.x 版本中进行移植。请参考:Python API