Ansible 2.0 移植指南
本节讨论 Ansible 1.x 和 Ansible 2.0 之间的行为变化。
旨在帮助您更新 playbook、插件和 Ansible 基础架构的其他部分,以便它们能够与 Ansible 的此版本一起使用。
我们建议您阅读此页面以及 Ansible 2.0 的变更日志,以了解您可能需要进行哪些更新。
本文档是移植集合的一部分。完整的移植指南列表可在 移植指南 中找到。
剧本
本节讨论您可能需要对 playbook 进行的任何更改。
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 字典格式在 playbook 中指定带有尾随换行符的字符串时,尾随换行符会被去除。当以 key=value 格式指定时,尾随换行符会被保留。在 v2 中,这两种指定字符串的方法都会保留尾随换行符。如果您依赖于尾随换行符被去除,您可以使用以下示例更改您的 playbook
* 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 类型文本文件被正确地保留了下来。当您期望您的 playbook 在迁移到 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
模板化(playbook 中的变量和模板查找)在保留原始值而不是将所有内容转换为字符串方面得到了改进。如果您需要旧的行为,请引用该值以将其作为字符串传递。
空变量和在 yaml 中设置为 null 的变量不再转换为空字符串。它们将保留 None 的值。您可以通过设置
ANSIBLE_NULL_REPRESENTATION
环境变量,在您的配置文件中将 null_representation 设置为空字符串来覆盖它。必须在 ansible.cfg 中启用额外回调。复制不再必要,但您必须在 ansible.cfg 中启用它们。
dnf 模块已被重写。可能会观察到一些细微的行为变化。
win_updates 已被重写,现在可以按预期工作了。
从 2.0.1 开始,gather_facts 中的隐式 setup 任务现在正确地继承了来自 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]。
使用权限提升的 playbook 应该始终使用“become*”选项,而不是旧的 su*/sudo* 选项。
vars_prompt 的“简写形式”不再受支持。例如
vars_prompt: variable_name: "Prompt string"
在任务包含语句的顶层指定变量不再受支持。例如
- include_tasks: foo.yml a: 1
现在应该是
- include_tasks: foo.yml
vars:
a: 1
在任务上设置 any_errors_fatal 不再受支持。这应该仅在 playbook 级别设置。
在 environment 字典(用于 playbook/任务等)中的裸变量不再受支持。在那里指定的变量应该使用完整的变量语法:'{{foo}}'。
标签(或任何指令)不应再与任务包含中的其他参数一起指定。相反,它们应该作为任务上的选项指定。例如
- 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),但也静默地被忽略,因此 playbook“运行”,即使它不应该运行,现在这是一个解析错误。
重复的指令
- task: dostuf when: True when: False
第一个 when 被忽略,并且只有第二个被使用,因为 playbook 在没有警告的情况下运行,它忽略了其中一个指令,现在这会产生解析错误。
混淆变量和指令
- 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.playbook
、self.play
或self.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