diff --git a/client/client.go b/client/client.go index d1d0434..678153c 100644 --- a/client/client.go +++ b/client/client.go @@ -26,7 +26,7 @@ func isValidCredsMessage(msg string) error { // Store uses an external program to save credentials. func Store(program ProgramFunc, creds *credentials.Credentials) error { - cmd := program("store") + cmd := program(credentials.ActionStore) buffer := new(bytes.Buffer) if err := json.NewEncoder(buffer).Encode(creds); err != nil { @@ -50,7 +50,7 @@ func Store(program ProgramFunc, creds *credentials.Credentials) error { // Get executes an external program to get the credentials from a native store. func Get(program ProgramFunc, serverURL string) (*credentials.Credentials, error) { - cmd := program("get") + cmd := program(credentials.ActionGet) cmd.Input(strings.NewReader(serverURL)) out, err := cmd.Output() @@ -81,7 +81,7 @@ func Get(program ProgramFunc, serverURL string) (*credentials.Credentials, error // Erase executes a program to remove the server credentials from the native store. func Erase(program ProgramFunc, serverURL string) error { - cmd := program("erase") + cmd := program(credentials.ActionErase) cmd.Input(strings.NewReader(serverURL)) out, err := cmd.Output() if err != nil { @@ -99,7 +99,7 @@ func Erase(program ProgramFunc, serverURL string) error { // List executes a program to list server credentials in the native store. func List(program ProgramFunc) (map[string]string, error) { - cmd := program("list") + cmd := program(credentials.ActionList) cmd.Input(strings.NewReader("unused")) out, err := cmd.Output() if err != nil { diff --git a/client/client_test.go b/client/client_test.go index 322d7f0..ebb2a6b 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -21,14 +21,14 @@ const ( var errProgramExited = fmt.Errorf("exited 1") // mockProgram simulates interactions between the docker client and a remote -// credentials helper. +// credentials-helper. // Unit tests inject this mocked command into the remote to control execution. type mockProgram struct { arg string input io.Reader } -// Output returns responses from the remote credentials helper. +// Output returns responses from the remote credentials-helper. // It mocks those responses based in the input in the mock. func (m *mockProgram) Output() ([]byte, error) { in, err := io.ReadAll(m.input) @@ -80,7 +80,7 @@ func (m *mockProgram) Output() ([]byte, error) { return []byte(fmt.Sprintf("unknown argument %q with %q", m.arg, inS)), errProgramExited } -// Input sets the input to send to a remote credentials helper. +// Input sets the input to send to a remote credentials-helper. func (m *mockProgram) Input(in io.Reader) { m.input = in } @@ -92,16 +92,16 @@ func mockProgramFn(args ...string) Program { } func ExampleStore() { - p := NewShellProgramFunc("docker-credential-secretservice") + p := NewShellProgramFunc("docker-credential-pass") c := &credentials.Credentials{ - ServerURL: "https://example.com", - Username: "calavera", + ServerURL: "https://registry.example.com", + Username: "exampleuser", Secret: "my super secret token", } if err := Store(p, c); err != nil { - fmt.Println(err) + _, _ = fmt.Println(err) } } @@ -129,14 +129,14 @@ func TestStore(t *testing.T) { } func ExampleGet() { - p := NewShellProgramFunc("docker-credential-secretservice") + p := NewShellProgramFunc("docker-credential-pass") - creds, err := Get(p, "https://example.com") + creds, err := Get(p, "https://registry.example.com") if err != nil { - fmt.Println(err) + _, _ = fmt.Println(err) } - fmt.Printf("Got credentials for user `%s` in `%s`\n", creds.Username, creds.ServerURL) + _, _ = fmt.Printf("Got credentials for user `%s` in `%s`\n", creds.Username, creds.ServerURL) } func TestGet(t *testing.T) { @@ -190,10 +190,10 @@ func TestGet(t *testing.T) { } func ExampleErase() { - p := NewShellProgramFunc("docker-credential-secretservice") + p := NewShellProgramFunc("docker-credential-pass") - if err := Erase(p, "https://example.com"); err != nil { - fmt.Println(err) + if err := Erase(p, "https://registry.example.com"); err != nil { + _, _ = fmt.Println(err) } } diff --git a/client/command.go b/client/command.go index 8da3343..1936234 100644 --- a/client/command.go +++ b/client/command.go @@ -1,7 +1,6 @@ package client import ( - "fmt" "io" "os" "os/exec" @@ -30,27 +29,26 @@ func NewShellProgramFuncWithEnv(name string, env *map[string]string) ProgramFunc func createProgramCmdRedirectErr(commandName string, args []string, env *map[string]string) *exec.Cmd { programCmd := exec.Command(commandName, args...) - programCmd.Env = os.Environ() if env != nil { for k, v := range *env { - programCmd.Env = append(programCmd.Env, fmt.Sprintf("%s=%s", k, v)) + programCmd.Env = append(programCmd.Environ(), k+"="+v) } } programCmd.Stderr = os.Stderr return programCmd } -// Shell invokes shell commands to talk with a remote credentials helper. +// Shell invokes shell commands to talk with a remote credentials-helper. type Shell struct { cmd *exec.Cmd } -// Output returns responses from the remote credentials helper. +// Output returns responses from the remote credentials-helper. func (s *Shell) Output() ([]byte, error) { return s.cmd.Output() } -// Input sets the input to send to a remote credentials helper. +// Input sets the input to send to a remote credentials-helper. func (s *Shell) Input(in io.Reader) { s.cmd.Stdin = in } diff --git a/credentials/credentials.go b/credentials/credentials.go index 91d9d4b..eac5518 100644 --- a/credentials/credentials.go +++ b/credentials/credentials.go @@ -10,6 +10,20 @@ import ( "strings" ) +// Action defines the name of an action (sub-command) supported by a +// credential-helper binary. It is an alias for "string", and mostly +// for convenience. +type Action = string + +// List of actions (sub-commands) supported by credential-helper binaries. +const ( + ActionStore Action = "store" + ActionGet Action = "get" + ActionErase Action = "erase" + ActionList Action = "list" + ActionVersion Action = "version" +) + // Credentials holds the information shared between docker and the credentials store. type Credentials struct { ServerURL string @@ -43,42 +57,52 @@ func SetCredsLabel(label string) { CredsLabel = label } -// Serve initializes the credentials helper and parses the action argument. +// Serve initializes the credentials-helper and parses the action argument. // This function is designed to be called from a command line interface. // It uses os.Args[1] as the key for the action. // It uses os.Stdin as input and os.Stdout as output. // This function terminates the program with os.Exit(1) if there is an error. func Serve(helper Helper) { - var err error if len(os.Args) != 2 { - err = fmt.Errorf("Usage: %s ", os.Args[0]) + _, _ = fmt.Fprintln(os.Stdout, usage()) + os.Exit(1) } - if err == nil { - err = HandleCommand(helper, os.Args[1], os.Stdin, os.Stdout) + switch os.Args[1] { + case "--version", "-v": + _ = PrintVersion(os.Stdout) + os.Exit(0) + case "--help", "-h": + _, _ = fmt.Fprintln(os.Stdout, usage()) + os.Exit(0) } - if err != nil { - fmt.Fprintf(os.Stdout, "%v\n", err) + if err := HandleCommand(helper, os.Args[1], os.Stdin, os.Stdout); err != nil { + _, _ = fmt.Fprintln(os.Stdout, err) os.Exit(1) } } -// HandleCommand uses a helper and a key to run a credential action. -func HandleCommand(helper Helper, key string, in io.Reader, out io.Writer) error { - switch key { - case "store": +func usage() string { + return fmt.Sprintf("Usage: %s ", Name) +} + +// HandleCommand runs a helper to execute a credential action. +func HandleCommand(helper Helper, action Action, in io.Reader, out io.Writer) error { + switch action { + case ActionStore: return Store(helper, in) - case "get": + case ActionGet: return Get(helper, in, out) - case "erase": + case ActionErase: return Erase(helper, in) - case "list": + case ActionList: return List(helper, out) - case "version": + case ActionVersion: return PrintVersion(out) + default: + return fmt.Errorf("%s: unknown action: %s", Name, action) } - return fmt.Errorf("Unknown credential action `%s`", key) } // Store uses a helper and an input reader to save credentials. @@ -132,18 +156,17 @@ func Get(helper Helper, reader io.Reader, writer io.Writer) error { return err } - resp := Credentials{ + buffer.Reset() + err = json.NewEncoder(buffer).Encode(Credentials{ ServerURL: serverURL, Username: username, Secret: secret, - } - - buffer.Reset() - if err := json.NewEncoder(buffer).Encode(resp); err != nil { + }) + if err != nil { return err } - fmt.Fprint(writer, buffer.String()) + _, _ = fmt.Fprint(writer, buffer.String()) return nil } @@ -181,6 +204,6 @@ func List(helper Helper, writer io.Writer) error { // PrintVersion outputs the current version. func PrintVersion(writer io.Writer) error { - fmt.Fprintf(writer, "%s (%s) %s\n", Name, Package, Version) + _, _ = fmt.Fprintf(writer, "%s (%s) %s\n", Name, Package, Version) return nil }