diff --git a/Documentation/cloud-config.md b/Documentation/cloud-config.md index 3d8b370..f5a9f74 100644 --- a/Documentation/cloud-config.md +++ b/Documentation/cloud-config.md @@ -87,6 +87,7 @@ Arbitrary systemd units may be provided in the `coreos.units` attribute. - **name**: String representing unit's name. Required. - **runtime**: Boolean indicating whether or not to persist the unit across reboots. This is analagous to the `--runtime` argument to `systemd enable`. Default value is false. +- **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. diff --git a/initialize/config.go b/initialize/config.go index 6213207..71efefe 100644 --- a/initialize/config.go +++ b/initialize/config.go @@ -130,16 +130,17 @@ func Apply(cfg CloudConfig, env *Environment) error { if len(cfg.Coreos.Units) > 0 { commands := make(map[string]string, 0) - for _, unit := range cfg.Coreos.Units { + dst := system.UnitDestination(&unit, env.Root()) if unit.Content != "" { - log.Printf("Writing unit %s to filesystem", unit.Name) - dst, err := system.PlaceUnit(&unit, env.Root()) - if err != nil { + log.Printf("Writing unit %s to filesystem at path %s", unit.Name, dst) + if err := system.PlaceUnit(&unit, dst); err != nil { return err } log.Printf("Placed unit %s at %s", unit.Name, dst) + } + if unit.Enable { if unit.Group() != "network" { log.Printf("Enabling unit file %s", dst) if err := system.EnableUnitFile(dst, unit.Runtime); err != nil { diff --git a/system/systemd.go b/system/systemd.go index dda28b2..9315638 100644 --- a/system/systemd.go +++ b/system/systemd.go @@ -19,6 +19,7 @@ const fakeMachineID = "42000000000000000000000000000042" type Unit struct { Name string + Enable bool Runtime bool Content string Command string @@ -41,21 +42,28 @@ func (u *Unit) Group() (group string) { type Script []byte -func PlaceUnit(u *Unit, root string) (string, error) { +// UnitDestination builds the appropriate absolte file path for +// the given unit. The root argument indicates the effective base +// directory of the system (similar to a chroot). +func UnitDestination(u *Unit, root string) string { dir := "etc" if u.Runtime { dir = "run" } - dst := path.Join(root, dir, "systemd", u.Group()) - if _, err := os.Stat(dst); os.IsNotExist(err) { - if err := os.MkdirAll(dst, os.FileMode(0755)); err != nil { - return "", err + return path.Join(root, dir, "systemd", u.Group(), u.Name) +} + +// PlaceUnit writes a unit file at the provided destination, creating +// parent directories as necessary. +func PlaceUnit(u *Unit, dst string) error { + dir := filepath.Dir(dst) + if _, err := os.Stat(dir); os.IsNotExist(err) { + if err := os.MkdirAll(dir, os.FileMode(0755)); err != nil { + return err } } - dst = path.Join(dst, u.Name) - file := File{ Path: dst, Content: u.Content, @@ -64,10 +72,10 @@ func PlaceUnit(u *Unit, root string) (string, error) { err := WriteFile(&file) if err != nil { - return "", err + return err } - return dst, nil + return nil } func EnableUnitFile(file string, runtime bool) error { diff --git a/system/systemd_test.go b/system/systemd_test.go index 6635d0e..4498456 100644 --- a/system/systemd_test.go +++ b/system/systemd_test.go @@ -26,12 +26,17 @@ Address=10.209.171.177/19 } defer syscall.Rmdir(dir) - if _, err := PlaceUnit(&u, dir); err != nil { + dst := UnitDestination(&u, dir) + expectDst := path.Join(dir, "run", "systemd", "network", "50-eth0.network") + if dst != expectDst { + t.Fatalf("UnitDestination returned %s, expected %s", dst, expectDst) + } + + if err := PlaceUnit(&u, dst); err != nil { t.Fatalf("PlaceUnit failed: %v", err) } - fullPath := path.Join(dir, "run", "systemd", "network", "50-eth0.network") - fi, err := os.Stat(fullPath) + fi, err := os.Stat(dst) if err != nil { t.Fatalf("Unable to stat file: %v", err) } @@ -40,19 +45,19 @@ Address=10.209.171.177/19 t.Errorf("File has incorrect mode: %v", fi.Mode()) } - contents, err := ioutil.ReadFile(fullPath) + contents, err := ioutil.ReadFile(dst) if err != nil { t.Fatalf("Unable to read expected file: %v", err) } - expect := `[Match] + expectContents := `[Match] Name=eth47 [Network] Address=10.209.171.177/19 ` - if string(contents) != expect { - t.Fatalf("File has incorrect contents '%s'.\nExpected '%s'", string(contents), expect) + if string(contents) != expectContents { + t.Fatalf("File has incorrect contents '%s'.\nExpected '%s'", string(contents), expectContents) } } @@ -72,12 +77,17 @@ Where=/media/state } defer syscall.Rmdir(dir) - if _, err := PlaceUnit(&u, dir); err != nil { + dst := UnitDestination(&u, dir) + expectDst := path.Join(dir, "etc", "systemd", "system", "media-state.mount") + if dst != expectDst { + t.Fatalf("UnitDestination returned %s, expected %s", dst, expectDst) + } + + if err := PlaceUnit(&u, dst); err != nil { t.Fatalf("PlaceUnit failed: %v", err) } - fullPath := path.Join(dir, "etc", "systemd", "system", "media-state.mount") - fi, err := os.Stat(fullPath) + fi, err := os.Stat(dst) if err != nil { t.Fatalf("Unable to stat file: %v", err) } @@ -86,17 +96,17 @@ Where=/media/state t.Errorf("File has incorrect mode: %v", fi.Mode()) } - contents, err := ioutil.ReadFile(fullPath) + contents, err := ioutil.ReadFile(dst) if err != nil { t.Fatalf("Unable to read expected file: %v", err) } - expect := `[Mount] + expectContents := `[Mount] What=/dev/sdb1 Where=/media/state ` - if string(contents) != expect { - t.Fatalf("File has incorrect contents '%s'.\nExpected '%s'", string(contents), expect) + if string(contents) != expectContents { + t.Fatalf("File has incorrect contents '%s'.\nExpected '%s'", string(contents), expectContents) } }