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?
Challenge accepted! 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,
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.
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.