Wildcard or Glob option in Ansible variables

I’m trying to install a set of services on my Linux boxes and have to start the services using Ansible Playbook as well.

The issue is that when I install a package the service name of that particular package comes with the version number, so I want to create a play that can start the service regardless of the version that is getting installed on the machine.

I have defines the packages and service names in a group_vars file and used the below option

start-services:
- service1
- service2
- service*-3
But this is not working, so can someone please help me with this?

Thanks,

I think you probably need to write a task to find the name of the service, including the version number and then start them.

1 Like

Challenge accepted! :crazy_face: This turns out to be tricky to do right, but I think this works pretty well.

Let’s assume the system uses systemd service units. The systemctl command provides a show command which takes globs and lists matching systemd units – not just service units. Thus:

$ systemctl show '*' --property=Names --all --value --no-pager

The “--all” flag is necessary to include those units not currently loaded. A couple of extra flags – “--property=Names --value” – make it produce output suitable for consumption by scripts rather than humans. But even then there are complications:

  • Some units have multiple names. For example, on my system “ModemManager.service” is also known as “dbus-org.freedesktop.ModemManager1.service.” Likewise, “multi-user.target” is also called “runlevel3.target,” “runlevel4.target,” and “runlevel2.target.” These names get listed on the same line with a space between them, so that must be dealt with.

  • Some unit names have backslash-escaped hex codes in their names. systemctl conveniently double-quotes such names that would need quoting to be re-used in scripts.For example,

    “systemd-cryptsetup@luks\x2d2d01b229\x2de0cc\x2d4dfc\x2dbdd3\x2de846ac4f88a5.service”
    including the double-quotes! Since I’m attempting to produce JSON from a bash script, I have to handle optional quotes and backslash-escapes.

  • For reasons I don’t understand, systemctl likes to include extra (well, I feel they’re extra) blank lines in its output. Those need to be discarded without data loss.

With all that said, here’s my attempt at a task to find systemd service names which match a set of glob patterns. Beware: this is set up to take a list of globs, even if that list has only one glob. Interpreting the registered output would require some changes were you to run the task once without “loop:.” I’ve tried to make it handle the general case.

---
# anoopv89_01.yml
- name: Systemd units via globs
  hosts: localhost
  gather_facts: false
  tasks:
    - name: Determine systemd units matching a list of globs
      register: unit_globs
      args:
        executable: /bin/bash
      loop:
        - '*'                # Matches all units, not just services
        - '[aeiou]*.service' # Only services starting with a vowel
        - 'autosi*'          # Matches my custom 'autosig.service'.
      loop_control:
        loop_var: glob
      vars:
        action: none # Only used in a comment below, but still must be defined.
                     # You would also need to 'become' if this were, say, 'start'.
      ansible.builtin.shell: |
        unquote() {
          # This is nuts: we need to remove the quotes (which the 'eval' does).
          # But we also need to preserve each backslash in the
          # json going to stdout, so we have to double them here in the yaml.
          eval "printf \"%s\" ${1//\\/\\\\}"
        }
        declare -a primaries=()
        while read -r primary alternates; do
          if [ -n "$primary" ] ; then # Some lines will be blank!
            # If you wanted to take action on each service, here is where
            # to do it, sending output to stderr since we are printing JSON
            # to stdout, like this:
            #   eval "systemctl {{ action }} $primary" >&2
            # Note: systemctl quotes $primary if quotes are needed,
            # so do not quote $primary in the above eval expression.
            primaries+=( "\"$(unquote "$primary")\"" )
          fi
        done < <( systemctl show '{{ glob }}' --all --property=Names --value )
        printf "{\"%s\": [" "{{ glob }}"
        IFS=$', \t\n'; printf "%s" "${primaries[*]}"
        printf "]}"

    - name: Reinterpret registered results stdout as json
      # This could be a set_fact instead.
      ansible.builtin.debug:
        msg: '{{ unit_globs.results | map(attribute="stdout") | map("from_json") }}'

On my system, that set of globs produces the following output, slightly edited and annotated for clarity:

ok: [localhost] => 
  msg:
# It's a list of dicts with the globs as keys and the list of matching
# primary unit names as values. (Alternate names are excluded.)
  - '*':   # This glob will match all units
    - sys-fs-fuse-connections.mount
    - rpc_pipefs.target
    - network-pre.target
    - systemd-ask-password-plymouth.path
    - sys-devices-virtual-misc-rfkill.device
    - systemd-user-sessions.service
      [… skipping ahead a bit …]
    - sshd.service
    - sys-subsystem-net-devices-virbr0.device
    - veritysetup.target
    - systemd-udevd-kernel.socket
    - abrt-journal-core.service
    - system-dbus\x2d:1.3\x2dorg.freedesktop.problems.slice
    - systemd-sysctl.service
    - systemd-logind.service
      [… still skipping …]
    - bluetooth.service
    - uresourced.service
    - sys-devices-pci0000:00-0000:00:1d.4-0000:07:00.0-nvme-nvme0-nvme0n1-nvme0n1p1.device
    - system.slice
      [… more skipping …]
    - httpd.service
    - system-systemd\x2dzram\x2dsetup.slice
    - logwatch.timer
    - gssproxy.service
      [… there are a lot of systemd units! …]
    - systemd-network-generator.service
    - avahi-daemon.socket
    - systemd-tmpfiles-setup-dev.service
    - lightdm.service

  - '[aeiou]*.service':  # the glob that matches service names beginning with a vowel.
    - abrt-xorg.service
    - abrt-journal-core.service
    - uresourced.service
    - initial-setup.service
    - accounts-daemon.service
    - earlyoom.service
    - atd.service
    - upower.service
    - alsa-state.service
    - abrt-oops.service
    - irqbalance.service
    - abrtd.service
    - autosig.service
    - user-runtime-dir@12428.service
    - avahi-daemon.service
    - udisks2.service
    - auditd.service
    - user@12428.service

  - autosi*:  # This service rotates my email signature quip.
    - autosig.service

Maybe someone will find this useful. I sure learned a lot.

1 Like

I’d use the ansible.builtin.service_facts module, to save using shell, if possible…

Good to know about, that module.

I’ve never bought into the “avoid the shell” mantra, but I see where it comes from.

I’ve been looking around for something in Ansible that exposes fnmatch() (i.e. glob pattern matching) outside of a files-in-a-directory context, and I’m kind of surprised it’s not there. I would use a regex instead, but that’s not the original question.

1 Like