Search code examples
ansiblesystemd

Ansible systemd mount dependency


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?


Solution

  • 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.