diff --git a/README.md b/README.md index 0fa111a..f70f698 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ In fact, if `systemctl` isn't found in the `PATH`, this library will panic. - [x] `systemctl daemon-reload` - [x] `systemctl disable` - [x] `systemctl enable` +- [x] `systemctl reenable` - [x] `systemctl is-active` - [x] `systemctl is-enabled` - [x] `systemctl is-failed` diff --git a/systemctl.go b/systemctl.go index ab6b969..cce2688 100644 --- a/systemctl.go +++ b/systemctl.go @@ -15,7 +15,21 @@ import ( // reloaded, all sockets systemd listens on behalf of user configuration will // stay accessible. func DaemonReload(ctx context.Context, opts Options) error { - var args = []string{"daemon-reload", "--system"} + args := []string{"daemon-reload", "--system"} + if opts.UserMode { + args[1] = "--user" + } + _, _, _, err := execute(ctx, args) + return err +} + +// Reenables one or more units. +// +// This removes all symlinks to the unit files backing the specified units from +// the unit configuration directory, then recreates the symlink to the unit again, +// atomically. Can be used to change the symlink target. +func Reenable(ctx context.Context, unit string, opts Options) error { + args := []string{"reenable", "--system", unit} if opts.UserMode { args[1] = "--user" } @@ -29,7 +43,7 @@ func DaemonReload(ctx context.Context, opts Options) error { // the unit configuration directory, and hence undoes any changes made by // enable or link. func Disable(ctx context.Context, unit string, opts Options) error { - var args = []string{"disable", "--system", unit} + args := []string{"disable", "--system", unit} if opts.UserMode { args[1] = "--user" } @@ -44,7 +58,7 @@ func Disable(ctx context.Context, unit string, opts Options) error { // manager configuration is reloaded (in a way equivalent to daemon-reload), // in order to ensure the changes are taken into account immediately. func Enable(ctx context.Context, unit string, opts Options) error { - var args = []string{"enable", "--system", unit} + args := []string{"enable", "--system", unit} if opts.UserMode { args[1] = "--user" } @@ -57,7 +71,7 @@ func Enable(ctx context.Context, unit string, opts Options) error { // Returns true if the unit is active, false if inactive or failed. // Also returns false in an error case. func IsActive(ctx context.Context, unit string, opts Options) (bool, error) { - var args = []string{"is-active", "--system", unit} + args := []string{"is-active", "--system", unit} if opts.UserMode { args[1] = "--user" } @@ -87,7 +101,7 @@ func IsActive(ctx context.Context, unit string, opts Options) (bool, error) { // See https://www.freedesktop.org/software/systemd/man/systemctl.html#is-enabled%20UNIT%E2%80%A6 // for more information func IsEnabled(ctx context.Context, unit string, opts Options) (bool, error) { - var args = []string{"is-enabled", "--system", unit} + args := []string{"is-enabled", "--system", unit} if opts.UserMode { args[1] = "--user" } @@ -127,7 +141,7 @@ func IsEnabled(ctx context.Context, unit string, opts Options) (bool, error) { // Check whether any of the specified units are in a "failed" state. func IsFailed(ctx context.Context, unit string, opts Options) (bool, error) { - var args = []string{"is-failed", "--system", unit} + args := []string{"is-failed", "--system", unit} if opts.UserMode { args[1] = "--user" } @@ -149,7 +163,7 @@ func IsFailed(ctx context.Context, unit string, opts Options) (bool, error) { // continue masking anyway. Calling Mask on a non-existing masked unit does not // return an error. Similarly, see Unmask. func Mask(ctx context.Context, unit string, opts Options) error { - var args = []string{"mask", "--system", unit} + args := []string{"mask", "--system", unit} if opts.UserMode { args[1] = "--user" } @@ -160,7 +174,7 @@ func Mask(ctx context.Context, unit string, opts Options) error { // Stop and then start one or more units specified on the command line. // If the units are not running yet, they will be started. func Restart(ctx context.Context, unit string, opts Options) error { - var args = []string{"restart", "--system", unit} + args := []string{"restart", "--system", unit} if opts.UserMode { args[1] = "--user" } @@ -171,7 +185,7 @@ func Restart(ctx context.Context, unit string, opts Options) error { // Show a selected property of a unit. Accepted properties are predefined in the // properties subpackage to guarantee properties are valid and assist code-completion. func Show(ctx context.Context, unit string, property properties.Property, opts Options) (string, error) { - var args = []string{"show", "--system", unit, "--property", string(property)} + args := []string{"show", "--system", unit, "--property", string(property)} if opts.UserMode { args[1] = "--user" } @@ -183,7 +197,7 @@ func Show(ctx context.Context, unit string, property properties.Property, opts O // Start (activate) a given unit func Start(ctx context.Context, unit string, opts Options) error { - var args = []string{"start", "--system", unit} + args := []string{"start", "--system", unit} if opts.UserMode { args[1] = "--user" } @@ -197,7 +211,7 @@ func Start(ctx context.Context, unit string, opts Options) error { // Generally, it makes more sense to programatically retrieve the properties // using Show, but this command is provided for the sake of completeness func Status(ctx context.Context, unit string, opts Options) (string, error) { - var args = []string{"status", "--system", unit} + args := []string{"status", "--system", unit} if opts.UserMode { args[1] = "--user" } @@ -207,7 +221,7 @@ func Status(ctx context.Context, unit string, opts Options) (string, error) { // Stop (deactivate) a given unit func Stop(ctx context.Context, unit string, opts Options) error { - var args = []string{"stop", "--system", unit} + args := []string{"stop", "--system", unit} if opts.UserMode { args[1] = "--user" } @@ -223,7 +237,7 @@ func Stop(ctx context.Context, unit string, opts Options) error { // If the unit doesn't exist but it's masked anyway, no error will be // returned. Gross, I know. Take it up with Poettering. func Unmask(ctx context.Context, unit string, opts Options) error { - var args = []string{"unmask", "--system", unit} + args := []string{"unmask", "--system", unit} if opts.UserMode { args[1] = "--user" } diff --git a/systemctl_test.go b/systemctl_test.go index 7e155e4..94a4f3c 100644 --- a/systemctl_test.go +++ b/systemctl_test.go @@ -38,6 +38,7 @@ func TestMain(m *testing.M) { } os.Exit(retCode) } + func TestDaemonReload(t *testing.T) { testCases := []struct { unit string @@ -78,6 +79,7 @@ func TestDaemonReload(t *testing.T) { }) } } + func TestDisable(t *testing.T) { t.Run(fmt.Sprintf(""), func(t *testing.T) { if userString != "root" && userString != "system" { @@ -101,10 +103,34 @@ func TestDisable(t *testing.T) { t.Errorf("Unable to unmask %s", unit) } }) - } -func TestEnable(t *testing.T) { +func TestReenable(t *testing.T) { + t.Run(fmt.Sprintf(""), func(t *testing.T) { + if userString != "root" && userString != "system" { + t.Skip("skipping superuser test while running as user") + } + unit := "nginx" + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + err := Mask(ctx, unit, Options{UserMode: false}) + if err != nil { + Unmask(ctx, unit, Options{UserMode: false}) + t.Errorf("Unable to mask %s", unit) + } + err = Reenable(ctx, unit, Options{UserMode: false}) + if err != ErrMasked { + Unmask(ctx, unit, Options{UserMode: false}) + t.Errorf("error is %v, but should have been %v", err, ErrMasked) + } + err = Unmask(ctx, unit, Options{UserMode: false}) + if err != nil { + t.Errorf("Unable to unmask %s", unit) + } + }) +} + +func TestEnable(t *testing.T) { t.Run(fmt.Sprintf(""), func(t *testing.T) { if userString != "root" && userString != "system" { t.Skip("skipping superuser test while running as user") @@ -127,8 +153,8 @@ func TestEnable(t *testing.T) { t.Errorf("Unable to unmask %s", unit) } }) - } + func ExampleEnable() { unit := "syncthing" ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) @@ -196,7 +222,6 @@ func TestIsActive(t *testing.T) { t.Errorf("error is %v, but should have been %v", err, ErrDoesNotExist) } }) - } func TestIsEnabled(t *testing.T) { @@ -251,7 +276,6 @@ func TestIsEnabled(t *testing.T) { Unmask(ctx, unit, Options{UserMode: userMode}) Enable(ctx, unit, Options{UserMode: userMode}) }) - } func TestMask(t *testing.T) { @@ -263,7 +287,7 @@ func TestMask(t *testing.T) { }{ /* Run these tests only as an unpriviledged user */ - //try nonexistant unit in user mode as user + // try nonexistant unit in user mode as user {"nonexistant", ErrDoesNotExist, Options{UserMode: true}, true}, // try existing unit in user mode as user {"syncthing", nil, Options{UserMode: true}, true}, @@ -321,7 +345,6 @@ func TestMask(t *testing.T) { t.Errorf("error on second masking is %v, but should have been %v", err, nil) } Unmask(ctx, unit, opts) - }) t.Run(fmt.Sprintf("test double masking nonexisting"), func(t *testing.T) { unit := "nonexistant" @@ -342,7 +365,6 @@ func TestMask(t *testing.T) { } Unmask(ctx, unit, opts) }) - } func TestRestart(t *testing.T) { @@ -381,7 +403,6 @@ func TestRestart(t *testing.T) { if restarts+1 != secondRestarts { t.Errorf("Expected restart count to differ by one, but difference was: %d", secondRestarts-restarts) } - } // Runs through all defined Properties in parallel and checks for error cases @@ -439,7 +460,6 @@ func TestStart(t *testing.T) { break } } - } func TestStatus(t *testing.T) { @@ -452,7 +472,6 @@ func TestStatus(t *testing.T) { if err != nil { t.Errorf("error: %v", err) } - } func TestStop(t *testing.T) { @@ -488,7 +507,6 @@ func TestStop(t *testing.T) { break } } - } func TestUnmask(t *testing.T) { @@ -500,7 +518,7 @@ func TestUnmask(t *testing.T) { }{ /* Run these tests only as an unpriviledged user */ - //try nonexistant unit in user mode as user + // try nonexistant unit in user mode as user {"nonexistant", ErrDoesNotExist, Options{UserMode: true}, true}, // try existing unit in user mode as user {"syncthing", nil, Options{UserMode: true}, true}, @@ -558,7 +576,6 @@ func TestUnmask(t *testing.T) { t.Errorf("error on second unmasking is %v, but should have been %v", err, nil) } Unmask(ctx, unit, opts) - }) t.Run(fmt.Sprintf("test double unmasking nonexisting"), func(t *testing.T) { unit := "nonexistant" @@ -579,5 +596,4 @@ func TestUnmask(t *testing.T) { t.Errorf("error on second unmasking is %v, but should have been %v", err, ErrDoesNotExist) } }) - }