使用 ansible 包模块与 apt 和 homebrew 一起工作
Use ansible package module to work with apt and homebrew
我在创建适用于 linux 和 macOS 的剧本时遇到了两个问题。
我的剧本中有很多这样的步骤:
- name: install something
package:
name: [something_1, something_2, ...]
state: present
become: yes
它适用于 apt 和 yum,但是当我尝试在 macOS 上 运行 时,自制程序抱怨:
Running Homebrew as root is extremely dangerous and no longer supported.
我在很多地方都找不到解决这个问题的优雅方法。复制所有任务并使用 when 子句对我来说似乎势不可挡。可能我可以使用 become_user 变量设置为 root/local_user 取决于分布,但这也有很多变化。
第二个问题是 head-only 公式(只能使用 --head 标志安装的自制程序包)。如果 something_2 需要安装这个标志怎么办?我可以再次复制任务并将 package 模块更改为 homebrew 但这有很多样板。
有什么帮助吗?
如果您想要一组足够灵活的任务来处理多个 Linux
包管理器和 macOS brew,选择是更符合逻辑还是更符合逻辑
重复。
这三种模式应该有所帮助。他们仍然有重复和
样板代码,但这是我们在 Ansible 游戏中所处的领域
cross-platform.
- 仅为Linux全局声明
become: yes
(root)
- 需要 platform-specific 处理 as-needed 和
when
的地址包
- 这可能是
--head
用于 brew
,或者为 apt
设置 PPA,等等
- 地图包名称与变量不一致
- 例如:
brew install ncurses
、apt install libncurses5-dev
、dnf install ncurses-devel
都是同一个库。
1) 仅针对 Linux
全局声明 become: yes
(root)
对于 Linux 主机,切换到 root 进行安装是预期的行为。
对于 macOS a la Homebrew,以 root 身份安装并不好。所以,我们在使用 brew
时需要 become: no
(false
),否则需要 become: yes
(true
)(因为
Linux).
在您的示例中,become
指令嵌套在每个任务(“步骤”)中。到
防止重复,在任务之前在更高的词法范围内调用 become
开始。之后的任务会继承become
的状态,即
根据条件表达式设置。
不幸的是,become
在根剧本范围内的变量将是
未定义并在第一个任务 运行:
之前抛出错误
# playbook.yml
- name: Demo
hosts: localhost
connection: local
# This works
become: True
# This doesn't - the variable is undefined
become: "{{ False if ansible_pkg_mgr == 'brew' else True }}"
# Nor does this - also undefined
become: "{{ False if ansible_os_family == 'Darwin' else True }}"
tasks:
# ...
要解决此问题,我们可以将任务存储在另一个文件中并 import 它们,或者
将任务包装在 block 中。这些模式中的任何一个都将提供
有机会及时用我们的自定义变量值声明 become
捡起它的任务:
# playbook.yml
---
- name: Demo
hosts: localhost
connection: local
vars:
# This variable gives us a boolean for deciding whether or not to become
# root. It cascades down to any subsequent tasks unless overwritten.
should_be_root: "{{ true if ansible_pkg_mgr != 'brew' else false }}"
# It could also be based on the OS type, but since brew is the main cause
# it's probably better this way.
# should_be_root: "{{ False if ansible_os_family == 'Darwin' else True }}"
tasks:
# Import the tasks from another file, which gives us a chance to pass along
# a `become` context with our variable:
- import_tasks: test_tasks.yml
become: "{{ should_be_root }}"
# Wrapping the tasks in a block will also work:
- block:
- name: ncurses is present
package:
name: [libncurses5-dev, libncursesw5-dev]
state: present
- name: cmatrix is present
package:
name: cmatrix
state: present
become: "{{ should_be_root }}"
现在有一个针对 brew
的逻辑检查和一个 before
指令
(取决于使用上面的任务模式)。所有任务将被执行为
root 用户,除非使用的包管理器是 brew
.
2) 需要 platform-specific 处理 as-needed 和 when
的地址包
Package Module 非常方便,但它非常有限。经过
它本身只适用于理想场景;意思是,一个没有的包
需要来自底层包管理器的任何特殊处理或标志。全部
它可以做的是传递要安装的包的文字字符串,state
,以及
强制使用特定包管理器可执行文件的可选参数。
这是一个安装 wget
的示例
当安装 brew
:
时,详细处理 ffmpeg
的特殊情况
# playbook.yml
# ...
tasks:
# wget is the same among package managers, nothing to see here
- name: wget is present
when: ansible_pkg_mgr != 'brew'
package:
name: wget
state: present
# This will only run on hosts that do not use `brew`, like linux
- name: ffmpeg is present
when: ansible_pkg_mgr != 'brew'
package:
name: ffmpeg
state: present
# This will only run on hosts that use `brew`, i.e. macOS
- name: ffmpeg is present (brew)
when: ansible_pkg_mgr == 'brew'
homebrew:
name: ffmpeg
# head flag
state: head
# --with-chromaprint --with-fdk-aac --with-etc-etc
install_options: with-chromaprint, with-fdk-aac, with-etc-etc
上面的游戏将针对 ffmpeg
对 Linux 框产生此输出:
TASK [youtube-dl : ffmpeg is present] ******************************************
ok: [localhost]
TASK [youtube-dl : ffmpeg is present (brew)] ***********************************
skipping: [localhost]
3) 地图包名称与变量不一致
这不是您问题的具体部分,但下一个可能会出现。
Package Module 文档还提到:
Package names also vary with package manager; this module will not "translate"
them per distro. For example libyaml-dev, libyaml-devel.
所以,我们自己来处理相同软件使用不同的情况
包管理器平台之间的名称。这很常见。
这个有多种模式,例如:
- 对每个 OS/distro 使用 separate variable files 并导入它们
有条件地
- 跨平台使用相同的包管理器,例如Homebrew或
Conda
- 通过git
从源代码编译所有内容
None 其中非常愉快。这是一种使用 role 的方法。角色做
涉及更多样板和目录杂耍,但作为交换,它们提供
模块化和局部变量环境。当角色中的一组任务
需要更多的努力才能正确,它最终不会污染其他任务
集。
# playbook.yml
---
- name: Demo
hosts: localhost
connection: local
roles:
- cmatrix
# roles/cmatrix/defaults/main.yml
---
ncurses:
default:
- ncurses
# Important: these keys need to exactly match the name of package managers for
# our logic to hold up
apt:
- libncurses5-dev
- libncursesw5-dev
brew:
- pkg-config
- ncurses
# roles/cmatrix/tasks/main.yml
---
- name: cmatix and its dependencies are present
become: "{{ should_be_root }}"
block:
- name: ncurses is present
package:
name: '{{ item }}'
state: latest
loop: "{{ ncurses[ansible_pkg_mgr] | default(ncurses['default']) }}"
- name: cmatrix is present
when: ansible_pkg_mgr != 'brew'
package:
name: cmatrix
state: present
ncurses
的任务查找一组项目以循环通过由
相应的包管理器。如果未定义正在使用的包管理器
在变量对象中,使用 Jinja 默认过滤器来引用
default
我们设置的值。
使用此模式,添加对另一个包管理器或其他包的支持
依赖关系只涉及更新变量对象:
# roles/cmatrix/defaults/main.yml
---
ncurses:
default:
- ncurses
apt:
- libncurses5-dev
- libncursesw5-dev
# add a new dependency for Debian
- imaginarycurses-dep
brew:
- pkg-config
- ncurses
# add support for Fedora
dnf:
- ncurses-devel
集结成真
这是涵盖所有三个方面的完整示例。剧本有两个角色
每个人都根据单个变量使用正确的 become
值。它也是
安装时包含 cmatrix
和 ffmpeg
的特例
brew
,并处理包管理器之间 ncurses 的备用名称。
# playbook.yml
---
- name: Demo
hosts: localhost
connection: local
vars:
should_be_root: "{{ true if ansible_pkg_mgr != 'brew' else false }}"
roles:
- cmatrix
- youtube-dl
# roles/cmatrix/defaults/main.yml
ncurses:
default:
- ncurses
apt:
- libncurses5-dev
- libncursesw5-dev
brew:
- pkg-config
- ncurses
dnf:
- ncurses-devel
# roles/cmatrix/tasks/main.yml
---
- name: cmatrix and dependencies are present
# A var from above, in the playbook
become: "{{ should_be_root }}"
block:
- name: ncurses is present
package:
name: '{{ item }}'
state: latest
# Get an array of the correct package names to install from the map in our
# default variables file
loop: "{{ ncurses[ansible_pkg_mgr] | default(ncurses['default']) }}"
# Install as usual if this is not a brew system
- name: cmatrix is present
when: ansible_pkg_mgr != 'brew'
package:
name: cmatrix
state: present
# If it is a brew system, use this instead
- name: cmatrix is present (brew)
when: ansible_pkg_mgr == 'brew'
homebrew:
name: cmatrix
state: head
install_options: with-some-option
# roles/youtube-dl/tasks/main.yml
---
- name: youtube-dl and dependencies are present
become: "{{ should_be_root }}"
block:
- name: ffmpeg is present
when: ansible_pkg_mgr != 'brew'
package:
name: ffmpeg
state: latest
- name: ffmpeg is present (brew)
when: ansible_pkg_mgr == 'brew'
homebrew:
name: ffmpeg
state: head
install_options: with-chromaprint, with-fdk-aac, with-etc-etc
- name: atomicparsley is present
package:
name: atomicparsley
state: latest
- name: youtube-dl is present
package:
name: youtube-dl
state: latest
Ubuntu的结果:
$ ansible-playbook demo.yml
[WARNING]: provided hosts list is empty, only localhost is available. Note that
the implicit localhost does not match 'all'
PLAY [Demo] ********************************************************************
TASK [Gathering Facts] *********************************************************
ok: [localhost]
TASK [cmatrix : ncurses is present] ********************************************
ok: [localhost] => (item=libncurses5-dev)
ok: [localhost] => (item=libncursesw5-dev)
TASK [cmatrix : cmatrix is present] ********************************************
ok: [localhost]
TASK [cmatrix : cmatrix is present (brew)] *************************************
skipping: [localhost]
TASK [youtube-dl : ffmpeg is present] ******************************************
ok: [localhost]
TASK [youtube-dl : ffmpeg is present (brew)] ***********************************
skipping: [localhost]
TASK [youtube-dl : atomicparsley is present] ***********************************
ok: [localhost]
TASK [youtube-dl : youtube-dl is present] **************************************
ok: [localhost]
PLAY RECAP *********************************************************************
localhost : ok=6 changed=0 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0
我在创建适用于 linux 和 macOS 的剧本时遇到了两个问题。
我的剧本中有很多这样的步骤:
- name: install something
package:
name: [something_1, something_2, ...]
state: present
become: yes
它适用于 apt 和 yum,但是当我尝试在 macOS 上 运行 时,自制程序抱怨:
Running Homebrew as root is extremely dangerous and no longer supported.
我在很多地方都找不到解决这个问题的优雅方法。复制所有任务并使用 when 子句对我来说似乎势不可挡。可能我可以使用 become_user 变量设置为 root/local_user 取决于分布,但这也有很多变化。
第二个问题是 head-only 公式(只能使用 --head 标志安装的自制程序包)。如果 something_2 需要安装这个标志怎么办?我可以再次复制任务并将 package 模块更改为 homebrew 但这有很多样板。
有什么帮助吗?
如果您想要一组足够灵活的任务来处理多个 Linux 包管理器和 macOS brew,选择是更符合逻辑还是更符合逻辑 重复。
这三种模式应该有所帮助。他们仍然有重复和 样板代码,但这是我们在 Ansible 游戏中所处的领域 cross-platform.
- 仅为Linux全局声明
become: yes
(root) - 需要 platform-specific 处理 as-needed 和
when
的地址包- 这可能是
--head
用于brew
,或者为apt
设置 PPA,等等
- 这可能是
- 地图包名称与变量不一致
- 例如:
brew install ncurses
、apt install libncurses5-dev
、dnf install ncurses-devel
都是同一个库。
- 例如:
1) 仅针对 Linux
全局声明become: yes
(root)
对于 Linux 主机,切换到 root 进行安装是预期的行为。
对于 macOS a la Homebrew,以 root 身份安装并不好。所以,我们在使用 brew
时需要 become: no
(false
),否则需要 become: yes
(true
)(因为
Linux).
在您的示例中,become
指令嵌套在每个任务(“步骤”)中。到
防止重复,在任务之前在更高的词法范围内调用 become
开始。之后的任务会继承become
的状态,即
根据条件表达式设置。
不幸的是,become
在根剧本范围内的变量将是
未定义并在第一个任务 运行:
# playbook.yml
- name: Demo
hosts: localhost
connection: local
# This works
become: True
# This doesn't - the variable is undefined
become: "{{ False if ansible_pkg_mgr == 'brew' else True }}"
# Nor does this - also undefined
become: "{{ False if ansible_os_family == 'Darwin' else True }}"
tasks:
# ...
要解决此问题,我们可以将任务存储在另一个文件中并 import 它们,或者
将任务包装在 block 中。这些模式中的任何一个都将提供
有机会及时用我们的自定义变量值声明 become
捡起它的任务:
# playbook.yml
---
- name: Demo
hosts: localhost
connection: local
vars:
# This variable gives us a boolean for deciding whether or not to become
# root. It cascades down to any subsequent tasks unless overwritten.
should_be_root: "{{ true if ansible_pkg_mgr != 'brew' else false }}"
# It could also be based on the OS type, but since brew is the main cause
# it's probably better this way.
# should_be_root: "{{ False if ansible_os_family == 'Darwin' else True }}"
tasks:
# Import the tasks from another file, which gives us a chance to pass along
# a `become` context with our variable:
- import_tasks: test_tasks.yml
become: "{{ should_be_root }}"
# Wrapping the tasks in a block will also work:
- block:
- name: ncurses is present
package:
name: [libncurses5-dev, libncursesw5-dev]
state: present
- name: cmatrix is present
package:
name: cmatrix
state: present
become: "{{ should_be_root }}"
现在有一个针对 brew
的逻辑检查和一个 before
指令
(取决于使用上面的任务模式)。所有任务将被执行为
root 用户,除非使用的包管理器是 brew
.
2) 需要 platform-specific 处理 as-needed 和 when
的地址包
Package Module 非常方便,但它非常有限。经过
它本身只适用于理想场景;意思是,一个没有的包
需要来自底层包管理器的任何特殊处理或标志。全部
它可以做的是传递要安装的包的文字字符串,state
,以及
强制使用特定包管理器可执行文件的可选参数。
这是一个安装 wget
的示例
当安装 brew
:
ffmpeg
的特殊情况
# playbook.yml
# ...
tasks:
# wget is the same among package managers, nothing to see here
- name: wget is present
when: ansible_pkg_mgr != 'brew'
package:
name: wget
state: present
# This will only run on hosts that do not use `brew`, like linux
- name: ffmpeg is present
when: ansible_pkg_mgr != 'brew'
package:
name: ffmpeg
state: present
# This will only run on hosts that use `brew`, i.e. macOS
- name: ffmpeg is present (brew)
when: ansible_pkg_mgr == 'brew'
homebrew:
name: ffmpeg
# head flag
state: head
# --with-chromaprint --with-fdk-aac --with-etc-etc
install_options: with-chromaprint, with-fdk-aac, with-etc-etc
上面的游戏将针对 ffmpeg
对 Linux 框产生此输出:
TASK [youtube-dl : ffmpeg is present] ******************************************
ok: [localhost]
TASK [youtube-dl : ffmpeg is present (brew)] ***********************************
skipping: [localhost]
3) 地图包名称与变量不一致
这不是您问题的具体部分,但下一个可能会出现。
Package Module 文档还提到:
Package names also vary with package manager; this module will not "translate" them per distro. For example libyaml-dev, libyaml-devel.
所以,我们自己来处理相同软件使用不同的情况 包管理器平台之间的名称。这很常见。
这个有多种模式,例如:
- 对每个 OS/distro 使用 separate variable files 并导入它们 有条件地
- 跨平台使用相同的包管理器,例如Homebrew或 Conda
- 通过git 从源代码编译所有内容
None 其中非常愉快。这是一种使用 role 的方法。角色做 涉及更多样板和目录杂耍,但作为交换,它们提供 模块化和局部变量环境。当角色中的一组任务 需要更多的努力才能正确,它最终不会污染其他任务 集。
# playbook.yml
---
- name: Demo
hosts: localhost
connection: local
roles:
- cmatrix
# roles/cmatrix/defaults/main.yml
---
ncurses:
default:
- ncurses
# Important: these keys need to exactly match the name of package managers for
# our logic to hold up
apt:
- libncurses5-dev
- libncursesw5-dev
brew:
- pkg-config
- ncurses
# roles/cmatrix/tasks/main.yml
---
- name: cmatix and its dependencies are present
become: "{{ should_be_root }}"
block:
- name: ncurses is present
package:
name: '{{ item }}'
state: latest
loop: "{{ ncurses[ansible_pkg_mgr] | default(ncurses['default']) }}"
- name: cmatrix is present
when: ansible_pkg_mgr != 'brew'
package:
name: cmatrix
state: present
ncurses
的任务查找一组项目以循环通过由
相应的包管理器。如果未定义正在使用的包管理器
在变量对象中,使用 Jinja 默认过滤器来引用
default
我们设置的值。
使用此模式,添加对另一个包管理器或其他包的支持 依赖关系只涉及更新变量对象:
# roles/cmatrix/defaults/main.yml
---
ncurses:
default:
- ncurses
apt:
- libncurses5-dev
- libncursesw5-dev
# add a new dependency for Debian
- imaginarycurses-dep
brew:
- pkg-config
- ncurses
# add support for Fedora
dnf:
- ncurses-devel
集结成真
这是涵盖所有三个方面的完整示例。剧本有两个角色
每个人都根据单个变量使用正确的 become
值。它也是
安装时包含 cmatrix
和 ffmpeg
的特例
brew
,并处理包管理器之间 ncurses 的备用名称。
# playbook.yml
---
- name: Demo
hosts: localhost
connection: local
vars:
should_be_root: "{{ true if ansible_pkg_mgr != 'brew' else false }}"
roles:
- cmatrix
- youtube-dl
# roles/cmatrix/defaults/main.yml
ncurses:
default:
- ncurses
apt:
- libncurses5-dev
- libncursesw5-dev
brew:
- pkg-config
- ncurses
dnf:
- ncurses-devel
# roles/cmatrix/tasks/main.yml
---
- name: cmatrix and dependencies are present
# A var from above, in the playbook
become: "{{ should_be_root }}"
block:
- name: ncurses is present
package:
name: '{{ item }}'
state: latest
# Get an array of the correct package names to install from the map in our
# default variables file
loop: "{{ ncurses[ansible_pkg_mgr] | default(ncurses['default']) }}"
# Install as usual if this is not a brew system
- name: cmatrix is present
when: ansible_pkg_mgr != 'brew'
package:
name: cmatrix
state: present
# If it is a brew system, use this instead
- name: cmatrix is present (brew)
when: ansible_pkg_mgr == 'brew'
homebrew:
name: cmatrix
state: head
install_options: with-some-option
# roles/youtube-dl/tasks/main.yml
---
- name: youtube-dl and dependencies are present
become: "{{ should_be_root }}"
block:
- name: ffmpeg is present
when: ansible_pkg_mgr != 'brew'
package:
name: ffmpeg
state: latest
- name: ffmpeg is present (brew)
when: ansible_pkg_mgr == 'brew'
homebrew:
name: ffmpeg
state: head
install_options: with-chromaprint, with-fdk-aac, with-etc-etc
- name: atomicparsley is present
package:
name: atomicparsley
state: latest
- name: youtube-dl is present
package:
name: youtube-dl
state: latest
Ubuntu的结果:
$ ansible-playbook demo.yml
[WARNING]: provided hosts list is empty, only localhost is available. Note that
the implicit localhost does not match 'all'
PLAY [Demo] ********************************************************************
TASK [Gathering Facts] *********************************************************
ok: [localhost]
TASK [cmatrix : ncurses is present] ********************************************
ok: [localhost] => (item=libncurses5-dev)
ok: [localhost] => (item=libncursesw5-dev)
TASK [cmatrix : cmatrix is present] ********************************************
ok: [localhost]
TASK [cmatrix : cmatrix is present (brew)] *************************************
skipping: [localhost]
TASK [youtube-dl : ffmpeg is present] ******************************************
ok: [localhost]
TASK [youtube-dl : ffmpeg is present (brew)] ***********************************
skipping: [localhost]
TASK [youtube-dl : atomicparsley is present] ***********************************
ok: [localhost]
TASK [youtube-dl : youtube-dl is present] **************************************
ok: [localhost]
PLAY RECAP *********************************************************************
localhost : ok=6 changed=0 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0