diff --git a/Documentation/cloud-config.md b/Documentation/cloud-config.md index aaed0ea..d1577c5 100644 --- a/Documentation/cloud-config.md +++ b/Documentation/cloud-config.md @@ -123,6 +123,7 @@ The `coreos.units.*` parameters define a list of arbitrary systemd units to star - **enable**: Boolean indicating whether or not to handle the [Install] section of the unit file. This is similar to running `systemctl enable `. Default value is false. - **content**: Plaintext string representing entire unit file. If no value is provided, the unit is assumed to exist already. - **command**: Command to execute on unit: start, stop, reload, restart, try-restart, reload-or-restart, reload-or-try-restart. Default value is restart. +- **mask**: Whether to mask the unit file by symlinking it to `/dev/null` (analogous to `systemctl mask `). Note that unlike `systemctl mask`, **this will destructively remove any existing unit file** located at `/etc/systemd/system/`, to ensure that the mask succeeds. Default value is false. **NOTE:** The command field is ignored for all network, netdev, and link units. The systemd-networkd.service unit will be restarted in their place. diff --git a/system/systemd.go b/system/systemd.go index d87f57a..a867219 100644 --- a/system/systemd.go +++ b/system/systemd.go @@ -179,9 +179,17 @@ func MachineID(root string) string { return id } +// MaskUnit masks a Unit by the given name by symlinking its unit file (in +// /etc/systemd/system) to /dev/null, analogous to `systemctl mask` +// N.B.: Unlike `systemctl mask`, this function will *remove any existing unit +// file* in /etc/systemd/system, to ensure that the mask will succeed. func MaskUnit(unit string, root string) error { masked := path.Join(root, "etc", "systemd", "system", unit) - if err := os.MkdirAll(path.Dir(masked), os.FileMode(0755)); err != nil { + if _, err := os.Stat(masked); os.IsNotExist(err) { + if err := os.MkdirAll(path.Dir(masked), os.FileMode(0755)); err != nil { + return err + } + } else if err := os.Remove(masked); err != nil { return err } return os.Symlink("/dev/null", masked) diff --git a/system/systemd_test.go b/system/systemd_test.go index fb29035..7bf3696 100644 --- a/system/systemd_test.go +++ b/system/systemd_test.go @@ -154,16 +154,33 @@ func TestMaskUnit(t *testing.T) { t.Fatalf("Unable to create tempdir: %v", err) } defer os.RemoveAll(dir) - if err := MaskUnit("foo.service", dir); err != nil { - t.Fatalf("Unable to mask unit: %v", err) - } - fullPath := path.Join(dir, "etc", "systemd", "system", "foo.service") - target, err := os.Readlink(fullPath) + // Ensure mask works with units that do not currently exist + if err := MaskUnit("foo.service", dir); err != nil { + t.Fatalf("Unable to mask new unit: %v", err) + } + fooPath := path.Join(dir, "etc", "systemd", "system", "foo.service") + fooTgt, err := os.Readlink(fooPath) if err != nil { t.Fatalf("Unable to read link", err) } - if target != "/dev/null" { - t.Fatalf("unit not masked, got unit target", target) + if fooTgt != "/dev/null" { + t.Fatalf("unit not masked, got unit target", fooTgt) + } + + // Ensure mask works with unit files that already exist + barPath := path.Join(dir, "etc", "systemd", "system", "bar.service") + if _, err := os.Create(barPath); err != nil { + t.Fatalf("Error creating new unit file: %v", err) + } + if err := MaskUnit("bar.service", dir); err != nil { + t.Fatalf("Unable to mask existing unit: %v", err) + } + barTgt, err := os.Readlink(barPath) + if err != nil { + t.Fatalf("Unable to read link", err) + } + if barTgt != "/dev/null" { + t.Fatalf("unit not masked, got unit target", barTgt) } }