diff --git a/README.md b/README.md index bc0e166..702b071 100644 --- a/README.md +++ b/README.md @@ -46,9 +46,11 @@ import ( func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - userMode := false // Equivalent to `systemctl enable nginx` with a 10 second timeout - err := systemctl.Enable(ctx, "nginx", userMode) + opts := Options{ + usermode: false, + } + err := Enable(ctx, unit, opts) if err != nil { log.Fatalf("unable to enable unit %s: %v", "nginx", err) } diff --git a/structs.go b/structs.go new file mode 100644 index 0000000..def6794 --- /dev/null +++ b/structs.go @@ -0,0 +1,5 @@ +package systemctl + +type Options struct { + usermode bool +} diff --git a/systemctl.go b/systemctl.go index 3c4d0cd..b4786ef 100644 --- a/systemctl.go +++ b/systemctl.go @@ -2,98 +2,143 @@ package systemctl import ( "context" - "fmt" + "regexp" ) -// TODO -func IsFailed(ctx context.Context, unit string, usermode bool) (bool, error) { - - return false, nil +func IsFailed(ctx context.Context, unit string, opts Options) (bool, error) { + var args = []string{"is-failed", "--system", unit} + if opts.usermode { + args[1] = "--user" + } + stdout, _, _, err := execute(ctx, args) + if matched, _ := regexp.MatchString(`inactive`, stdout); matched { + return false, nil + } else if matched, _ := regexp.MatchString(`active`, stdout); matched { + return false, nil + } else if matched, _ := regexp.MatchString(`failed`, stdout); matched { + return true, nil + } + return false, err } -// TODO -func IsActive(ctx context.Context, unit string, usermode bool) (bool, error) { - return false, nil +func IsActive(ctx context.Context, unit string, opts Options) (bool, error) { + var args = []string{"is-active", "--system", unit} + if opts.usermode { + args[1] = "--user" + } + stdout, _, _, err := execute(ctx, args) + if matched, _ := regexp.MatchString(`inactive`, stdout); matched { + return false, nil + } else if matched, _ := regexp.MatchString(`active`, stdout); matched { + return true, nil + } else if matched, _ := regexp.MatchString(`failed`, stdout); matched { + return false, nil + } + + return false, err } -// TODO -func Status(ctx context.Context, unit string, usermode bool) (bool, error) { - return false, nil +func IsEnabled(ctx context.Context, unit string, opts Options) (bool, error) { + var args = []string{"is-enabled", "--system", unit} + if opts.usermode { + args[1] = "--user" + } + stdout, _, _, err := execute(ctx, args) + if matched, _ := regexp.MatchString(`enabled`, stdout); matched { + return true, nil + } else if matched, _ := regexp.MatchString(`disabled`, stdout); matched { + return false, nil + } + return false, err } -// TODO -func Restart(ctx context.Context, unit string, usermode bool) error { - return nil +func Status(ctx context.Context, unit string, opts Options) (string, error) { + var args = []string{"status", "--system", unit} + if opts.usermode { + args[1] = "--user" + } + stdout, _, _, err := execute(ctx, args) + return stdout, err } -// TODO -func Start(ctx context.Context, unit string, usermode bool) error { - return nil +func Restart(ctx context.Context, unit string, opts Options) error { + var args = []string{"restart", "--system", unit} + if opts.usermode { + args[1] = "--user" + } + _, _, _, err := execute(ctx, args) + return err } -// TODO -func Stop(ctx context.Context, unit string, usermode bool) error { - return nil +func Start(ctx context.Context, unit string, opts Options) error { + var args = []string{"start", "--system", unit} + if opts.usermode { + args[1] = "--user" + } + _, _, _, err := execute(ctx, args) + return err } -func Enable(ctx context.Context, unit string, usermode bool) error { +func Stop(ctx context.Context, unit string, opts Options) error { + var args = []string{"stop", "--system", unit} + if opts.usermode { + args[1] = "--user" + } + _, _, _, err := execute(ctx, args) + return err +} + +func Enable(ctx context.Context, unit string, opts Options) error { var args = []string{"enable", "--system", unit} - if usermode { + if opts.usermode { args[1] = "--user" } - _, stderr, code, err := execute(ctx, args) - customErr := filterErr(stderr) - if customErr != nil { - return customErr - } - if err != nil { - return err - } - if code != 0 { - return fmt.Errorf("received error code %d for stderr `%s`: %w", code, stderr, ErrUnspecified) - } - return nil + _, _, _, err := execute(ctx, args) + return err } -func Disable(ctx context.Context, unit string, usermode bool) error { +func Disable(ctx context.Context, unit string, opts Options) error { var args = []string{"disable", "--system", unit} - if usermode { + if opts.usermode { args[1] = "--user" } - _, stderr, code, err := execute(ctx, args) - customErr := filterErr(stderr) - if customErr != nil { - return customErr - } - if err != nil { - return err - } - if code != 0 { - return fmt.Errorf("received error code %d for stderr `%s`: %w", code, stderr, ErrUnspecified) - } - return nil + _, _, _, err := execute(ctx, args) + return err } -// TODO -func IsEnabled(ctx context.Context, unit string, usermode bool) (bool, error) { - return false, nil -} - -// TODO -func DaemonReload(ctx context.Context, unit string, usermode bool) error { - return nil +func DaemonReload(ctx context.Context, opts Options) error { + var args = []string{"daemon-reload", "--system"} + if opts.usermode { + args[1] = "--user" + } + _, _, _, err := execute(ctx, args) + return err } //TODO -func Show(ctx context.Context, unit string, property string, usermode bool) (string, error) { - return "", nil +func Show(ctx context.Context, unit string, property string, opts Options) (string, error) { + var args = []string{"show", "--system", unit} + if opts.usermode { + args[1] = "--user" + } + _, _, _, err := execute(ctx, args) + return "", err } -//TODO -func Mask(ctx context.Context, unit string, usermode bool) error { - return nil +func Mask(ctx context.Context, unit string, opts Options) error { + var args = []string{"mask", "--system", unit} + if opts.usermode { + args[1] = "--user" + } + _, _, _, err := execute(ctx, args) + return err } -func Unmask(ctx context.Context, unit string, usermode bool) error { - return nil +func Unmask(ctx context.Context, unit string, opts Options) error { + var args = []string{"unmask", "--system", unit} + if opts.usermode { + args[1] = "--user" + } + _, _, _, err := execute(ctx, args) + return err } diff --git a/systemctl_test.go b/systemctl_test.go index 2642e87..0b299ed 100644 --- a/systemctl_test.go +++ b/systemctl_test.go @@ -10,7 +10,10 @@ func TestEnableNonexistant(t *testing.T) { unit := "nonexistant" ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) defer cancel() - err := Enable(ctx, unit, true) + opts := Options{ + usermode: true, + } + err := Enable(ctx, unit, opts) if err != ErrDoesNotExist { t.Errorf("error is %v, but should have been %v", err, ErrDoesNotExist) } @@ -23,7 +26,10 @@ func TestEnableNoPermissions(t *testing.T) { unit := "nginx" ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) defer cancel() - err := Enable(ctx, unit, false) + opts := Options{ + usermode: false, + } + err := Enable(ctx, unit, opts) if err != ErrInsufficientPermissions { t.Errorf("error is %v, but should have been %v", err, ErrInsufficientPermissions) } @@ -37,7 +43,10 @@ func TestEnableSuccess(t *testing.T) { unit := "syncthing" ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) defer cancel() - err := Enable(ctx, unit, true) + opts := Options{ + usermode: true, + } + err := Enable(ctx, unit, opts) if err != nil { t.Errorf("error is %v, but should have been %v", err, nil) } diff --git a/util.go b/util.go index 499490b..7a78964 100644 --- a/util.go +++ b/util.go @@ -3,6 +3,7 @@ package systemctl import ( "bytes" "context" + "fmt" "os/exec" "regexp" ) @@ -37,27 +38,32 @@ func execute(ctx context.Context, args []string) (string, string, int, error) { warnings = stderr.String() code = cmd.ProcessState.ExitCode() + customErr := filterErr(warnings) + if customErr != nil { + err = customErr + } + if code != 0 && err == nil { + err = fmt.Errorf("received error code %d for stderr `%s`: %w", code, warnings, ErrUnspecified) + } + return output, warnings, code, err } func filterErr(stderr string) error { - matched, _ := regexp.MatchString(`does not exist`, stderr) - if matched { + if matched, _ := regexp.MatchString(`does not exist`, stderr); matched { return ErrDoesNotExist } - matched, _ = regexp.MatchString(`Interactive authentication required`, stderr) - if matched { + if matched, _ := regexp.MatchString(`No such file or directory`, stderr); matched { + return ErrDoesNotExist + } + if matched, _ := regexp.MatchString(`Interactive authentication required`, stderr); matched { return ErrInsufficientPermissions } - matched, _ = regexp.MatchString(`Access denied`, stderr) - if matched { + if matched, _ := regexp.MatchString(`Access denied`, stderr); matched { return ErrInsufficientPermissions } - - matched, _ = regexp.MatchString(`Failed`, stderr) - if matched { + if matched, _ := regexp.MatchString(`Failed`, stderr); matched { return ErrUnspecified } - return nil }