chore(*): split out unit processing from config.Apply

This commit is contained in:
Jonathan Boulle
2014-06-05 17:40:53 -07:00
committed by Michael Marineau
parent a55e2cd49b
commit be51f4eba0
7 changed files with 278 additions and 123 deletions

View File

@@ -60,7 +60,7 @@ func probe8012q() error {
}
func restartNetworkd() error {
_, err := RunUnitCommand("restart", "systemd-networkd.service")
_, err := NewUnitManager("").RunUnitCommand("restart", "systemd-networkd.service")
return err
}

View File

@@ -13,63 +13,21 @@ import (
"github.com/coreos/coreos-cloudinit/third_party/github.com/coreos/go-systemd/dbus"
)
func NewUnitManager(root string) UnitManager {
return &systemd{root}
}
type systemd struct {
root string
}
// fakeMachineID is placed on non-usr CoreOS images and should
// never be used as a true MachineID
const fakeMachineID = "42000000000000000000000000000042"
// Name for drop-in service configuration files created by cloudconfig
const cloudConfigDropIn = "20-cloudinit.conf"
type Unit struct {
Name string
Mask bool
Enable bool
Runtime bool
Content string
Command string
// For drop-in units, a cloudinit.conf is generated.
// This is currently unbound in YAML (and hence unsettable in cloud-config files)
// until the correct behaviour for multiple drop-in units is determined.
DropIn bool `yaml:"-"`
}
func (u *Unit) Type() string {
ext := filepath.Ext(u.Name)
return strings.TrimLeft(ext, ".")
}
func (u *Unit) Group() (group string) {
t := u.Type()
if t == "network" || t == "netdev" || t == "link" {
group = "network"
} else {
group = "system"
}
return
}
type Script []byte
// Destination builds the appropriate absolute file path for
// the Unit. The root argument indicates the effective base
// directory of the system (similar to a chroot).
func (u *Unit) Destination(root string) string {
dir := "etc"
if u.Runtime {
dir = "run"
}
if u.DropIn {
return path.Join(root, dir, "systemd", u.Group(), fmt.Sprintf("%s.d", u.Name), cloudConfigDropIn)
} else {
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 {
func (s *systemd) 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 {
@@ -91,7 +49,7 @@ func PlaceUnit(u *Unit, dst string) error {
return nil
}
func EnableUnitFile(unit string, runtime bool) error {
func (s *systemd) EnableUnitFile(unit string, runtime bool) error {
conn, err := dbus.New()
if err != nil {
return err
@@ -102,7 +60,7 @@ func EnableUnitFile(unit string, runtime bool) error {
return err
}
func RunUnitCommand(command, unit string) (string, error) {
func (s *systemd) RunUnitCommand(command, unit string) (string, error) {
conn, err := dbus.New()
if err != nil {
return "", err
@@ -131,7 +89,7 @@ func RunUnitCommand(command, unit string) (string, error) {
return fn(unit, "replace")
}
func DaemonReload() error {
func (s *systemd) DaemonReload() error {
conn, err := dbus.New()
if err != nil {
return err
@@ -140,6 +98,57 @@ func DaemonReload() error {
return conn.Reload()
}
// MaskUnit masks the given Unit by symlinking its unit file to
// /dev/null, analogous to `systemctl mask`.
// N.B.: Unlike `systemctl mask`, this function will *remove any existing unit
// file at the location*, to ensure that the mask will succeed.
func (s *systemd) MaskUnit(unit *Unit) error {
masked := unit.Destination(s.root)
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)
}
// UnmaskUnit is analogous to systemd's unit_file_unmask. If the file
// associated with the given Unit is empty or appears to be a symlink to
// /dev/null, it is removed.
func (s *systemd) UnmaskUnit(unit *Unit) error {
masked := unit.Destination(s.root)
ne, err := nullOrEmpty(masked)
if os.IsNotExist(err) {
return nil
} else if err != nil {
return err
}
if !ne {
log.Printf("%s is not null or empty, refusing to unmask", masked)
return nil
}
return os.Remove(masked)
}
// nullOrEmpty checks whether a given path appears to be an empty regular file
// or a symlink to /dev/null
func nullOrEmpty(path string) (bool, error) {
fi, err := os.Stat(path)
if err != nil {
return false, err
}
m := fi.Mode()
if m.IsRegular() && fi.Size() <= 0 {
return true, nil
}
if m&os.ModeCharDevice > 0 {
return true, nil
}
return false, nil
}
func ExecuteScript(scriptPath string) (string, error) {
props := []dbus.Property{
dbus.PropDescription("Unit generated and executed by coreos-cloudinit on behalf of user"),
@@ -178,54 +187,3 @@ func MachineID(root string) string {
return id
}
// MaskUnit masks the given Unit by symlinking its unit file to
// /dev/null, analogous to `systemctl mask`.
// N.B.: Unlike `systemctl mask`, this function will *remove any existing unit
// file at the location*, to ensure that the mask will succeed.
func MaskUnit(unit *Unit, root string) error {
masked := unit.Destination(root)
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)
}
// UnmaskUnit is analogous to systemd's unit_file_unmask. If the file
// associated with the given Unit is empty or appears to be a symlink to
// /dev/null, it is removed.
func UnmaskUnit(unit *Unit, root string) error {
masked := unit.Destination(root)
ne, err := nullOrEmpty(masked)
if os.IsNotExist(err) {
return nil
} else if err != nil {
return err
}
if !ne {
log.Printf("%s is not null or empty, refusing to unmask", masked)
return nil
}
return os.Remove(masked)
}
// nullOrEmpty checks whether a given path appears to be an empty regular file
// or a symlink to /dev/null
func nullOrEmpty(path string) (bool, error) {
fi, err := os.Stat(path)
if err != nil {
return false, err
}
m := fi.Mode()
if m.IsRegular() && fi.Size() <= 0 {
return true, nil
}
if m&os.ModeCharDevice > 0 {
return true, nil
}
return false, nil
}

View File

@@ -25,13 +25,15 @@ Address=10.209.171.177/19
}
defer os.RemoveAll(dir)
sd := &systemd{dir}
dst := u.Destination(dir)
expectDst := path.Join(dir, "run", "systemd", "network", "50-eth0.network")
if dst != expectDst {
t.Fatalf("unit.Destination returned %s, expected %s", dst, expectDst)
}
if err := PlaceUnit(&u, dst); err != nil {
if err := sd.PlaceUnit(&u, dst); err != nil {
t.Fatalf("PlaceUnit failed: %v", err)
}
@@ -100,13 +102,15 @@ Where=/media/state
}
defer os.RemoveAll(dir)
sd := &systemd{dir}
dst := u.Destination(dir)
expectDst := path.Join(dir, "etc", "systemd", "system", "media-state.mount")
if dst != expectDst {
t.Fatalf("unit.Destination returned %s, expected %s", dst, expectDst)
}
if err := PlaceUnit(&u, dst); err != nil {
if err := sd.PlaceUnit(&u, dst); err != nil {
t.Fatalf("PlaceUnit failed: %v", err)
}
@@ -155,9 +159,11 @@ func TestMaskUnit(t *testing.T) {
}
defer os.RemoveAll(dir)
sd := &systemd{dir}
// Ensure mask works with units that do not currently exist
uf := &Unit{Name: "foo.service"}
if err := MaskUnit(uf, dir); err != nil {
if err := sd.MaskUnit(uf); err != nil {
t.Fatalf("Unable to mask new unit: %v", err)
}
fooPath := path.Join(dir, "etc", "systemd", "system", "foo.service")
@@ -175,7 +181,7 @@ func TestMaskUnit(t *testing.T) {
if _, err := os.Create(barPath); err != nil {
t.Fatalf("Error creating new unit file: %v", err)
}
if err := MaskUnit(ub, dir); err != nil {
if err := sd.MaskUnit(ub); err != nil {
t.Fatalf("Unable to mask existing unit: %v", err)
}
barTgt, err := os.Readlink(barPath)
@@ -194,8 +200,10 @@ func TestUnmaskUnit(t *testing.T) {
}
defer os.RemoveAll(dir)
sd := &systemd{dir}
nilUnit := &Unit{Name: "null.service"}
if err := UnmaskUnit(nilUnit, dir); err != nil {
if err := sd.UnmaskUnit(nilUnit); err != nil {
t.Errorf("unexpected error from unmasking nonexistent unit: %v", err)
}
@@ -211,7 +219,7 @@ func TestUnmaskUnit(t *testing.T) {
if err := ioutil.WriteFile(dst, []byte(uf.Content), 700); err != nil {
t.Fatalf("Unable to write unit file: %v", err)
}
if err := UnmaskUnit(uf, dir); err != nil {
if err := sd.UnmaskUnit(uf); err != nil {
t.Errorf("unmask of non-empty unit returned unexpected error: %v", err)
}
got, _ := ioutil.ReadFile(dst)
@@ -224,7 +232,7 @@ func TestUnmaskUnit(t *testing.T) {
if err := os.Symlink("/dev/null", dst); err != nil {
t.Fatalf("Unable to create masked unit: %v", err)
}
if err := UnmaskUnit(ub, dir); err != nil {
if err := sd.UnmaskUnit(ub); err != nil {
t.Errorf("unmask of unit returned unexpected error: %v", err)
}
if _, err := os.Stat(dst); !os.IsNotExist(err) {

67
system/unit.go Normal file
View File

@@ -0,0 +1,67 @@
package system
import (
"fmt"
"path"
"path/filepath"
"strings"
)
// Name for drop-in service configuration files created by cloudconfig
const cloudConfigDropIn = "20-cloudinit.conf"
type UnitManager interface {
PlaceUnit(unit *Unit, dst string) error
EnableUnitFile(unit string, runtime bool) error
RunUnitCommand(command, unit string) (string, error)
DaemonReload() error
MaskUnit(unit *Unit) error
UnmaskUnit(unit *Unit) error
}
type Unit struct {
Name string
Mask bool
Enable bool
Runtime bool
Content string
Command string
// For drop-in units, a cloudinit.conf is generated.
// This is currently unbound in YAML (and hence unsettable in cloud-config files)
// until the correct behaviour for multiple drop-in units is determined.
DropIn bool `yaml:"-"`
}
func (u *Unit) Type() string {
ext := filepath.Ext(u.Name)
return strings.TrimLeft(ext, ".")
}
func (u *Unit) Group() (group string) {
t := u.Type()
if t == "network" || t == "netdev" || t == "link" {
group = "network"
} else {
group = "system"
}
return
}
type Script []byte
// Destination builds the appropriate absolute file path for
// the Unit. The root argument indicates the effective base
// directory of the system (similar to a chroot).
func (u *Unit) Destination(root string) string {
dir := "etc"
if u.Runtime {
dir = "run"
}
if u.DropIn {
return path.Join(root, dir, "systemd", u.Group(), fmt.Sprintf("%s.d", u.Name), cloudConfigDropIn)
} else {
return path.Join(root, dir, "systemd", u.Group(), u.Name)
}
}