Ansible systemd mount dependency

Viewed 343

I use Ansible to deploy a systemd unit file to all the servers in an estate and enable it to start on boot. On some of those servers, the binary defined in the unit file is on a different filesystem. This requires the unit file to have a dependency on that filesystem being mounted. E.g.:

[Unit]
Description = Start the widget
After = network.target usr.mount

[Service]
Type = simple
ExecStart = /usr/bin/widget

[Install]
WantedBy = multi-user.target

Currently the unit file is deployed by Ansible as a static file, using the ansible.builtin.copy module. Is there a way I can convert this to a template and append the mountpoint to After = if /usr is a mountpoint?

2 Answers

Ansible fact ansible_mounts keeps the list of the mount points. Let's use the following vars for testing

    widgets:
      - /usr/local/apps/widget
      - /usr/share/apps/widget
      - /scratch/apps/widget
    my_ansible_mounts:
      - mount: /
      - mount: /boot/efi
      - mount: /usr
      - mount: /usr/local
    root:
      - /

Create a dictionary of widgets and related mount points, e.g.

    - set_fact:
        _mlist: []
    - set_fact:
        _mlist: "{{ _mlist + [{'dict': item.0, 'mount': item.1}] }}"
      with_nested:
        - "{{ widgets }}"
        - "{{ my_ansible_mounts|map(attribute='mount')|difference(root) }}"
      when: item.0|regex_search('^' ~ item.1) != None
    - set_fact:
        _mdict: {}
    - set_fact:
        _mdict: "{{ _mdict|combine({item.0: item.1|
                                            map(attribute='mount')|
                                            map('regex_replace', '/', '-')|
                                            list}) }}"
      loop: "{{ _mlist|groupby('dict') }}"

gives

  _mdict:
    /usr/local/apps/widget:
    - -usr
    - -usr-local
    /usr/share/apps/widget:
    - -usr

Then the flow should be trivial, e.g.

    - debug:
        msg: |-
          {% if _mdict[item]|default([])|length > 0 %}
          [Unit]
          Description = Start the widget
          After = network.target{% for i in _mdict[item] %} {{ i[1:] }}.mount{% endfor %}


          {% endif %}
          [Service]
          Type = simple
          ExecStart = {{ item }}
      loop: "{{ widgets }}"

gives

ok: [localhost] => (item=/usr/local/apps/widget) => 
  msg: |-
    [Unit]
    Description = Start the widget
    After = network.target usr.mount usr-local.mount
  
    [Service]
    Type = simple
    ExecStart = /usr/local/apps/widget
ok: [localhost] => (item=/usr/share/apps/widget) => 
  msg: |-
    [Unit]
    Description = Start the widget
    After = network.target usr.mount
  
    [Service]
    Type = simple
    ExecStart = /usr/share/apps/widget
ok: [localhost] => (item=/scratch/apps/widget) => 
  msg: |-
    [Service]
    Type = simple
    ExecStart = /scratch/apps/widget

To test the real stuff, in the code, replace my_ansible_mounts with ansible_mounts and fit the widgets and hosts to your needs.

Just use template task and in src template task have a condition to print required block based on a condition. You can pass a varible to template task and make a decision based on it, ie. you first check if that filesystem is needed in a task, then generate file based on the template task.

Related