【问题标题】:Use ansible package module to work with apt and homebrew使用 ansible 包模块来处理 apt 和 homebrew
【发布时间】:2020-08-04 07:40:29
【问题描述】:

我在创建适用于 linux 和 macOS 的 playbook 时遇到两个问题。

我的剧本中有很多这样的步骤:

- 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 但这是很多样板。

有什么帮助吗?

【问题讨论】:

标签: macos ansible homebrew


【解决方案1】:

如果您想要一组足够灵活的任务来支持多个 Linux 包管理器和 macOS brew,选择更多逻辑或更多 重复。

这三种模式应该会有所帮助。他们仍然有重复和 样板代码,但这是我们使用 Ansible 的领域 跨平台。

  1. 仅针对 Linux 全局声明 become: yes (root)
  2. 使用when 解决需要根据需要进行平台特定处理的包
    • 这可能是--head 用于brew,或者为apt 设置PPA,等等
  3. 映射包名称与变量的差异
    • 例如:brew install ncursesapt install libncurses5-devdnf 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 的变量将是 undefined 并在第一个任务运行前抛出错误:

# 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) 使用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

上面的游戏会针对 Linux 机器产生 ffmpeg 的输出:

TASK [youtube-dl : ffmpeg is present] ******************************************
ok: [localhost]

TASK [youtube-dl : ffmpeg is present (brew)] ***********************************
skipping: [localhost]

3) 映射包名与变量的差异

这不是您问题的具体部分,但很可能会出现在下一个问题中。

Package Module 文档还提到:

包名称也因包管理器而异;这个模块不会“翻译” 他们每个发行版。例如 libyaml-dev、libyaml-devel。

因此,我们自行处理相同软件使用不同的情况 包管理器平台之间的名称。这很常见。

对此有多种模式,例如:

他们都不是很愉快。这是使用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 值。它也是 安装时包含cmatrixffmpeg 的特殊情况 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

【讨论】:

  • 很高兴为您提供帮助!干杯
  • 你的意思不是反过来吗? “所以,我们需要在使用 brew 时变为:yes(真),否则(对于 Linux)需要变为:no(假)。” -- 顺便说一句,这是我见过的最好的 Stack Overflow 答案之一。非常感谢。
  • 这对我有用:``` - 名称:检查包管理器是否需要 root 用户 set_fact: package_needs_root: "{{ ansible_facts['distribution'] != 'MacOSX' }}" ```
  • 谢谢费尔南多!编辑了答案中的错字。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-03-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多