18

At my organization we have a number of simple-to-use base AMIs for different services such as ECS and Docker. Since many of our projects involve CloudFormation, we're using cfn-bootstrap, which consists of a couple of scripts and a service which run on boot to install certain packages and do certain configuration management tasks for us.

On startup of a system, an equivalent of the following script must be executed:

#!/bin/bash

# capture stderr only
output="$(cfn-init -s $STACK_NAME -r $RESOURCE_NAME --region $REGION >/dev/null)"

# if it failed, signal to CloudFormation that it failed and include a reason
returncode=$?
if [[ $returncode == 0]]; then
    cfn-signal -e $returncode -r "$output"
    exit $returncode
fi

# otherwise, signal success
cfn-signal -s

I was thinking of running this as a systemd oneshot service which runs After=network.target and WantedBy=multi-user.target.

The only problem is that I'd like my AMI to be flexible and only execute this if a certain file exists. Rather than embedding the above script into the EC2 user data, I can have the user data just define an environment file which defines the variables I need and only run my one-shot service if that environment file exists:

#cloud-init
write_files:
    - path: /etc/sysconfig/cloudformation
      # ...
      content: |
          CFN_STACK_NAME="stack-name"
          CFN_RESOURCE="resource-name"
          CFN_REGION="region"

Is there a way to make systemd only run a service if a given condition is met?

Tollef Fog Heen
  • 692
  • 3
  • 10
Naftuli Kay
  • 1,648
  • 6
  • 22
  • 43

2 Answers2

24

systemd provides a wide variety of conditions you can test. For instance, you can use ConditionPathExists= to test for the existence of a file.

[Unit]
ConditionPathExists=/etc/sysconfig/cloudformation
ki9
  • 1,169
  • 1
  • 10
  • 18
Michael Hampton
  • 237,123
  • 42
  • 477
  • 940
  • 5
    It's worth noting that this isn't a `while` condition, but an `if`, meaning that if the path specified in `ConditionPathExists` doesn't exist by the time the service starts the rest of the service will just not run. I.e., it does not wait for the path to exist. – Mahn Jul 12 '16 at 20:59
  • @Mahn using a systemd timer, it should be possible to repeatedly trigger the service on an interval to overcome that limitation. – Naftuli Kay Feb 15 '17 at 23:23
  • 2
    @Mahn Take a look at https://www.freedesktop.org/software/systemd/man/systemd.path.html#. It can monitor a path and provide activation based on, for example, when a path comes into existence. – benf Sep 13 '19 at 22:28
2

I stumbled upon this question looking for ways to start a systemd service using a condition. There are many ways:

ConditionArchitecture=, ConditionVirtualization=, ConditionHost=, ConditionKernelCommandLine=, ConditionSecurity=, ConditionCapability=, ConditionACPower=, ConditionNeedsUpdate=, ConditionFirstBoot=, ConditionPathExists=, ConditionPathExistsGlob=, ConditionPathIsDirectory=, ConditionPathIsSymbolicLink=, ConditionPathIsMountPoint=, ConditionPathIsReadWrite=, ConditionDirectoryNotEmpty=, ConditionFileNotEmpty=, ConditionFileIsExecutable=

I wanted to start the service based on a specific host name.

ConditionHost= may be used to match against the hostname or machine ID of the host. This either takes a hostname string (optionally with shell style globs) which is tested against the locally set hostname as returned by gethostname(2), or a machine ID formatted as string (see machine-id(5)). The test may be negated by prepending an exclamation mark.

More on that here.

radtek
  • 405
  • 4
  • 6