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}}"
  • 移植任务包含

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

  • 在 yaml 字典格式中定义的变量,请参阅 问题 13324

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

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

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

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

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

  • 从 2.0.1 开始,gather_facts 中的隐式设置任务现在可以正确地继承来自 playbook 的所有内容,但这可能会导致那些在 playbook 级别设置 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。这应该仅在 playbook 级别设置。

  • 不再支持 environment 字典(对于 playbook/任务等)中的裸变量。在那里指定的变量应使用完整的变量语法:'{{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 变量被保留为覆盖连接端口的 playbook/任务指令,在以前的版本中,这与名为 port 的变量混淆,并且可以在稍后的 playbook 中使用,如果主机尝试重新连接或使用非缓存连接,这会导致问题。现在它将被正确地识别为指令,并且 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