Merge pull request #22988 from calavera/use_client_credentials_library
Move native credentials lookup to the client library.
This commit is contained in:
commit
0f13b69fe2
11 changed files with 360 additions and 148 deletions
|
@ -1,14 +1,8 @@
|
|||
package credentials
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker-credential-helpers/client"
|
||||
"github.com/docker/docker-credential-helpers/credentials"
|
||||
"github.com/docker/docker/cliconfig/configfile"
|
||||
"github.com/docker/engine-api/types"
|
||||
)
|
||||
|
@ -18,50 +12,27 @@ const (
|
|||
tokenUsername = "<token>"
|
||||
)
|
||||
|
||||
// Standarize the not found error, so every helper returns
|
||||
// the same message and docker can handle it properly.
|
||||
var errCredentialsNotFound = errors.New("credentials not found in native keychain")
|
||||
|
||||
// command is an interface that remote executed commands implement.
|
||||
type command interface {
|
||||
Output() ([]byte, error)
|
||||
Input(in io.Reader)
|
||||
}
|
||||
|
||||
// credentialsRequest holds information shared between docker and a remote credential store.
|
||||
type credentialsRequest struct {
|
||||
ServerURL string
|
||||
Username string
|
||||
Secret string
|
||||
}
|
||||
|
||||
// credentialsGetResponse is the information serialized from a remote store
|
||||
// when the plugin sends requests to get the user credentials.
|
||||
type credentialsGetResponse struct {
|
||||
Username string
|
||||
Secret string
|
||||
}
|
||||
|
||||
// nativeStore implements a credentials store
|
||||
// using native keychain to keep credentials secure.
|
||||
// It piggybacks into a file store to keep users' emails.
|
||||
type nativeStore struct {
|
||||
commandFn func(args ...string) command
|
||||
fileStore Store
|
||||
programFunc client.ProgramFunc
|
||||
fileStore Store
|
||||
}
|
||||
|
||||
// NewNativeStore creates a new native store that
|
||||
// uses a remote helper program to manage credentials.
|
||||
func NewNativeStore(file *configfile.ConfigFile) Store {
|
||||
name := remoteCredentialsPrefix + file.CredentialsStore
|
||||
return &nativeStore{
|
||||
commandFn: shellCommandFn(file.CredentialsStore),
|
||||
fileStore: NewFileStore(file),
|
||||
programFunc: client.NewShellProgramFunc(name),
|
||||
fileStore: NewFileStore(file),
|
||||
}
|
||||
}
|
||||
|
||||
// Erase removes the given credentials from the native store.
|
||||
func (c *nativeStore) Erase(serverAddress string) error {
|
||||
if err := c.eraseCredentialsFromStore(serverAddress); err != nil {
|
||||
if err := client.Erase(c.programFunc, serverAddress); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -115,8 +86,7 @@ func (c *nativeStore) Store(authConfig types.AuthConfig) error {
|
|||
|
||||
// storeCredentialsInStore executes the command to store the credentials in the native store.
|
||||
func (c *nativeStore) storeCredentialsInStore(config types.AuthConfig) error {
|
||||
cmd := c.commandFn("store")
|
||||
creds := &credentialsRequest{
|
||||
creds := &credentials.Credentials{
|
||||
ServerURL: config.ServerAddress,
|
||||
Username: config.Username,
|
||||
Secret: config.Password,
|
||||
|
@ -127,70 +97,30 @@ func (c *nativeStore) storeCredentialsInStore(config types.AuthConfig) error {
|
|||
creds.Secret = config.IdentityToken
|
||||
}
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
if err := json.NewEncoder(buffer).Encode(creds); err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.Input(buffer)
|
||||
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
t := strings.TrimSpace(string(out))
|
||||
logrus.Debugf("error adding credentials - err: %v, out: `%s`", err, t)
|
||||
return fmt.Errorf(t)
|
||||
}
|
||||
|
||||
return nil
|
||||
return client.Store(c.programFunc, creds)
|
||||
}
|
||||
|
||||
// getCredentialsFromStore executes the command to get the credentials from the native store.
|
||||
func (c *nativeStore) getCredentialsFromStore(serverAddress string) (types.AuthConfig, error) {
|
||||
var ret types.AuthConfig
|
||||
|
||||
cmd := c.commandFn("get")
|
||||
cmd.Input(strings.NewReader(serverAddress))
|
||||
|
||||
out, err := cmd.Output()
|
||||
creds, err := client.Get(c.programFunc, serverAddress)
|
||||
if err != nil {
|
||||
t := strings.TrimSpace(string(out))
|
||||
|
||||
// do not return an error if the credentials are not
|
||||
// in the keyckain. Let docker ask for new credentials.
|
||||
if t == errCredentialsNotFound.Error() {
|
||||
if credentials.IsErrCredentialsNotFound(err) {
|
||||
// do not return an error if the credentials are not
|
||||
// in the keyckain. Let docker ask for new credentials.
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
logrus.Debugf("error getting credentials - err: %v, out: `%s`", err, t)
|
||||
return ret, fmt.Errorf(t)
|
||||
}
|
||||
|
||||
var resp credentialsGetResponse
|
||||
if err := json.NewDecoder(bytes.NewReader(out)).Decode(&resp); err != nil {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
if resp.Username == tokenUsername {
|
||||
ret.IdentityToken = resp.Secret
|
||||
if creds.Username == tokenUsername {
|
||||
ret.IdentityToken = creds.Secret
|
||||
} else {
|
||||
ret.Password = resp.Secret
|
||||
ret.Username = resp.Username
|
||||
ret.Password = creds.Secret
|
||||
ret.Username = creds.Username
|
||||
}
|
||||
|
||||
ret.ServerAddress = serverAddress
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// eraseCredentialsFromStore executes the command to remove the server credentails from the native store.
|
||||
func (c *nativeStore) eraseCredentialsFromStore(serverURL string) error {
|
||||
cmd := c.commandFn("erase")
|
||||
cmd.Input(strings.NewReader(serverURL))
|
||||
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
t := strings.TrimSpace(string(out))
|
||||
logrus.Debugf("error erasing credentials - err: %v, out: `%s`", err, t)
|
||||
return fmt.Errorf(t)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker-credential-helpers/client"
|
||||
"github.com/docker/docker-credential-helpers/credentials"
|
||||
"github.com/docker/engine-api/types"
|
||||
)
|
||||
|
||||
|
@ -43,7 +45,7 @@ func (m *mockCommand) Output() ([]byte, error) {
|
|||
case validServerAddress:
|
||||
return nil, nil
|
||||
default:
|
||||
return []byte("error erasing credentials"), errCommandExited
|
||||
return []byte("program failed"), errCommandExited
|
||||
}
|
||||
case "get":
|
||||
switch inS {
|
||||
|
@ -52,21 +54,21 @@ func (m *mockCommand) Output() ([]byte, error) {
|
|||
case validServerAddress2:
|
||||
return []byte(`{"Username": "<token>", "Secret": "abcd1234"}`), nil
|
||||
case missingCredsAddress:
|
||||
return []byte(errCredentialsNotFound.Error()), errCommandExited
|
||||
return []byte(credentials.NewErrCredentialsNotFound().Error()), errCommandExited
|
||||
case invalidServerAddress:
|
||||
return []byte("error getting credentials"), errCommandExited
|
||||
return []byte("program failed"), errCommandExited
|
||||
}
|
||||
case "store":
|
||||
var c credentialsRequest
|
||||
var c credentials.Credentials
|
||||
err := json.NewDecoder(strings.NewReader(inS)).Decode(&c)
|
||||
if err != nil {
|
||||
return []byte("error storing credentials"), errCommandExited
|
||||
return []byte("program failed"), errCommandExited
|
||||
}
|
||||
switch c.ServerURL {
|
||||
case validServerAddress:
|
||||
return nil, nil
|
||||
default:
|
||||
return []byte("error storing credentials"), errCommandExited
|
||||
return []byte("program failed"), errCommandExited
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -78,7 +80,7 @@ func (m *mockCommand) Input(in io.Reader) {
|
|||
m.input = in
|
||||
}
|
||||
|
||||
func mockCommandFn(args ...string) command {
|
||||
func mockCommandFn(args ...string) client.Program {
|
||||
return &mockCommand{
|
||||
arg: args[0],
|
||||
}
|
||||
|
@ -89,8 +91,8 @@ func TestNativeStoreAddCredentials(t *testing.T) {
|
|||
f.CredentialsStore = "mock"
|
||||
|
||||
s := &nativeStore{
|
||||
commandFn: mockCommandFn,
|
||||
fileStore: NewFileStore(f),
|
||||
programFunc: mockCommandFn,
|
||||
fileStore: NewFileStore(f),
|
||||
}
|
||||
err := s.Store(types.AuthConfig{
|
||||
Username: "foo",
|
||||
|
@ -133,8 +135,8 @@ func TestNativeStoreAddInvalidCredentials(t *testing.T) {
|
|||
f.CredentialsStore = "mock"
|
||||
|
||||
s := &nativeStore{
|
||||
commandFn: mockCommandFn,
|
||||
fileStore: NewFileStore(f),
|
||||
programFunc: mockCommandFn,
|
||||
fileStore: NewFileStore(f),
|
||||
}
|
||||
err := s.Store(types.AuthConfig{
|
||||
Username: "foo",
|
||||
|
@ -147,8 +149,8 @@ func TestNativeStoreAddInvalidCredentials(t *testing.T) {
|
|||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
|
||||
if err.Error() != "error storing credentials" {
|
||||
t.Fatalf("expected `error storing credentials`, got %v", err)
|
||||
if !strings.Contains(err.Error(), "program failed") {
|
||||
t.Fatalf("expected `program failed`, got %v", err)
|
||||
}
|
||||
|
||||
if len(f.AuthConfigs) != 0 {
|
||||
|
@ -165,8 +167,8 @@ func TestNativeStoreGet(t *testing.T) {
|
|||
f.CredentialsStore = "mock"
|
||||
|
||||
s := &nativeStore{
|
||||
commandFn: mockCommandFn,
|
||||
fileStore: NewFileStore(f),
|
||||
programFunc: mockCommandFn,
|
||||
fileStore: NewFileStore(f),
|
||||
}
|
||||
a, err := s.Get(validServerAddress)
|
||||
if err != nil {
|
||||
|
@ -196,8 +198,8 @@ func TestNativeStoreGetIdentityToken(t *testing.T) {
|
|||
f.CredentialsStore = "mock"
|
||||
|
||||
s := &nativeStore{
|
||||
commandFn: mockCommandFn,
|
||||
fileStore: NewFileStore(f),
|
||||
programFunc: mockCommandFn,
|
||||
fileStore: NewFileStore(f),
|
||||
}
|
||||
a, err := s.Get(validServerAddress2)
|
||||
if err != nil {
|
||||
|
@ -230,8 +232,8 @@ func TestNativeStoreGetAll(t *testing.T) {
|
|||
f.CredentialsStore = "mock"
|
||||
|
||||
s := &nativeStore{
|
||||
commandFn: mockCommandFn,
|
||||
fileStore: NewFileStore(f),
|
||||
programFunc: mockCommandFn,
|
||||
fileStore: NewFileStore(f),
|
||||
}
|
||||
as, err := s.GetAll()
|
||||
if err != nil {
|
||||
|
@ -277,8 +279,8 @@ func TestNativeStoreGetMissingCredentials(t *testing.T) {
|
|||
f.CredentialsStore = "mock"
|
||||
|
||||
s := &nativeStore{
|
||||
commandFn: mockCommandFn,
|
||||
fileStore: NewFileStore(f),
|
||||
programFunc: mockCommandFn,
|
||||
fileStore: NewFileStore(f),
|
||||
}
|
||||
_, err := s.Get(missingCredsAddress)
|
||||
if err != nil {
|
||||
|
@ -296,16 +298,16 @@ func TestNativeStoreGetInvalidAddress(t *testing.T) {
|
|||
f.CredentialsStore = "mock"
|
||||
|
||||
s := &nativeStore{
|
||||
commandFn: mockCommandFn,
|
||||
fileStore: NewFileStore(f),
|
||||
programFunc: mockCommandFn,
|
||||
fileStore: NewFileStore(f),
|
||||
}
|
||||
_, err := s.Get(invalidServerAddress)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
|
||||
if err.Error() != "error getting credentials" {
|
||||
t.Fatalf("expected `error getting credentials`, got %v", err)
|
||||
if !strings.Contains(err.Error(), "program failed") {
|
||||
t.Fatalf("expected `program failed`, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -318,8 +320,8 @@ func TestNativeStoreErase(t *testing.T) {
|
|||
f.CredentialsStore = "mock"
|
||||
|
||||
s := &nativeStore{
|
||||
commandFn: mockCommandFn,
|
||||
fileStore: NewFileStore(f),
|
||||
programFunc: mockCommandFn,
|
||||
fileStore: NewFileStore(f),
|
||||
}
|
||||
err := s.Erase(validServerAddress)
|
||||
if err != nil {
|
||||
|
@ -340,15 +342,15 @@ func TestNativeStoreEraseInvalidAddress(t *testing.T) {
|
|||
f.CredentialsStore = "mock"
|
||||
|
||||
s := &nativeStore{
|
||||
commandFn: mockCommandFn,
|
||||
fileStore: NewFileStore(f),
|
||||
programFunc: mockCommandFn,
|
||||
fileStore: NewFileStore(f),
|
||||
}
|
||||
err := s.Erase(invalidServerAddress)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
|
||||
if err.Error() != "error erasing credentials" {
|
||||
t.Fatalf("expected `error erasing credentials`, got %v", err)
|
||||
if !strings.Contains(err.Error(), "program failed") {
|
||||
t.Fatalf("expected `program failed`, got %v", err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
package credentials
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
func shellCommandFn(storeName string) func(args ...string) command {
|
||||
name := remoteCredentialsPrefix + storeName
|
||||
return func(args ...string) command {
|
||||
return &shell{cmd: exec.Command(name, args...)}
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (s *shell) Output() ([]byte, error) {
|
||||
return s.cmd.Output()
|
||||
}
|
||||
|
||||
// Input sets the input to send to a remote credentials helper.
|
||||
func (s *shell) Input(in io.Reader) {
|
||||
s.cmd.Stdin = in
|
||||
}
|
|
@ -108,7 +108,7 @@ clean() {
|
|||
go list -e -tags "$buildTags" -f '{{join .Deps "\n"}}' "${packages[@]}"
|
||||
go list -e -tags "$buildTags" -f '{{join .TestImports "\n"}}' "${packages[@]}"
|
||||
done
|
||||
done | grep -vE "^${PROJECT}" | sort -u
|
||||
done | grep -vE "^${PROJECT}/" | sort -u
|
||||
) )
|
||||
imports=( $(go list -e -f '{{if not .Standard}}{{.ImportPath}}{{end}}' "${imports[@]}") )
|
||||
unset IFS
|
||||
|
|
|
@ -131,6 +131,9 @@ clone git golang.org/x/oauth2 2baa8a1b9338cf13d9eeb27696d761155fa480be https://g
|
|||
clone git google.golang.org/api dc6d2353af16e2a2b0ff6986af051d473a4ed468 https://code.googlesource.com/google-api-go-client
|
||||
clone git google.golang.org/cloud dae7e3d993bc3812a2185af60552bb6b847e52a0 https://code.googlesource.com/gocloud
|
||||
|
||||
# native credentials
|
||||
clone git github.com/docker/docker-credential-helpers v0.3.0
|
||||
|
||||
# containerd
|
||||
clone git github.com/docker/containerd 57b7c3da915ebe943bd304c00890959b191e5264
|
||||
|
||||
|
|
20
vendor/src/github.com/docker/docker-credential-helpers/LICENSE
vendored
Normal file
20
vendor/src/github.com/docker/docker-credential-helpers/LICENSE
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
Copyright (c) 2016 David Calavera
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
70
vendor/src/github.com/docker/docker-credential-helpers/client/client.go
vendored
Normal file
70
vendor/src/github.com/docker/docker-credential-helpers/client/client.go
vendored
Normal file
|
@ -0,0 +1,70 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker-credential-helpers/credentials"
|
||||
)
|
||||
|
||||
// Store uses an external program to save credentials.
|
||||
func Store(program ProgramFunc, credentials *credentials.Credentials) error {
|
||||
cmd := program("store")
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
if err := json.NewEncoder(buffer).Encode(credentials); err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.Input(buffer)
|
||||
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
t := strings.TrimSpace(string(out))
|
||||
return fmt.Errorf("error storing credentials - err: %v, out: `%s`", err, t)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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.Input(strings.NewReader(serverURL))
|
||||
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
t := strings.TrimSpace(string(out))
|
||||
|
||||
if credentials.IsErrCredentialsNotFoundMessage(t) {
|
||||
return nil, credentials.NewErrCredentialsNotFound()
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("error getting credentials - err: %v, out: `%s`", err, t)
|
||||
}
|
||||
|
||||
resp := &credentials.Credentials{
|
||||
ServerURL: serverURL,
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(bytes.NewReader(out)).Decode(resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Erase executes a program to remove the server credentails from the native store.
|
||||
func Erase(program ProgramFunc, serverURL string) error {
|
||||
cmd := program("erase")
|
||||
cmd.Input(strings.NewReader(serverURL))
|
||||
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
t := strings.TrimSpace(string(out))
|
||||
return fmt.Errorf("error erasing credentials - err: %v, out: `%s`", err, t)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
37
vendor/src/github.com/docker/docker-credential-helpers/client/command.go
vendored
Normal file
37
vendor/src/github.com/docker/docker-credential-helpers/client/command.go
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// Program is an interface to execute external programs.
|
||||
type Program interface {
|
||||
Output() ([]byte, error)
|
||||
Input(in io.Reader)
|
||||
}
|
||||
|
||||
// ProgramFunc is a type of function that initializes programs based on arguments.
|
||||
type ProgramFunc func(args ...string) Program
|
||||
|
||||
// NewShellProgramFunc creates programs that are executed in a Shell.
|
||||
func NewShellProgramFunc(name string) ProgramFunc {
|
||||
return func(args ...string) Program {
|
||||
return &Shell{cmd: exec.Command(name, args...)}
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (s *Shell) Output() ([]byte, error) {
|
||||
return s.cmd.Output()
|
||||
}
|
||||
|
||||
// Input sets the input to send to a remote credentials helper.
|
||||
func (s *Shell) Input(in io.Reader) {
|
||||
s.cmd.Stdin = in
|
||||
}
|
129
vendor/src/github.com/docker/docker-credential-helpers/credentials/credentials.go
vendored
Normal file
129
vendor/src/github.com/docker/docker-credential-helpers/credentials/credentials.go
vendored
Normal file
|
@ -0,0 +1,129 @@
|
|||
package credentials
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Credentials holds the information shared between docker and the credentials store.
|
||||
type Credentials struct {
|
||||
ServerURL string
|
||||
Username string
|
||||
Secret string
|
||||
}
|
||||
|
||||
// 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 <store|get|erase>", os.Args[0])
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
err = HandleCommand(helper, os.Args[1], os.Stdin, os.Stdout)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stdout, "%v\n", 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":
|
||||
return Store(helper, in)
|
||||
case "get":
|
||||
return Get(helper, in, out)
|
||||
case "erase":
|
||||
return Erase(helper, in)
|
||||
}
|
||||
return fmt.Errorf("Unknown credential action `%s`", key)
|
||||
}
|
||||
|
||||
// Store uses a helper and an input reader to save credentials.
|
||||
// The reader must contain the JSON serialization of a Credentials struct.
|
||||
func Store(helper Helper, reader io.Reader) error {
|
||||
scanner := bufio.NewScanner(reader)
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
for scanner.Scan() {
|
||||
buffer.Write(scanner.Bytes())
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil && err != io.EOF {
|
||||
return err
|
||||
}
|
||||
|
||||
var creds Credentials
|
||||
if err := json.NewDecoder(buffer).Decode(&creds); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return helper.Add(&creds)
|
||||
}
|
||||
|
||||
// Get retrieves the credentials for a given server url.
|
||||
// The reader must contain the server URL to search.
|
||||
// The writer is used to write the JSON serialization of the credentials.
|
||||
func Get(helper Helper, reader io.Reader, writer io.Writer) error {
|
||||
scanner := bufio.NewScanner(reader)
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
for scanner.Scan() {
|
||||
buffer.Write(scanner.Bytes())
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil && err != io.EOF {
|
||||
return err
|
||||
}
|
||||
|
||||
serverURL := strings.TrimSpace(buffer.String())
|
||||
|
||||
username, secret, err := helper.Get(serverURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp := Credentials{
|
||||
Username: username,
|
||||
Secret: secret,
|
||||
}
|
||||
|
||||
buffer.Reset()
|
||||
if err := json.NewEncoder(buffer).Encode(resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprint(writer, buffer.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
// Erase removes credentials from the store.
|
||||
// The reader must contain the server URL to remove.
|
||||
func Erase(helper Helper, reader io.Reader) error {
|
||||
scanner := bufio.NewScanner(reader)
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
for scanner.Scan() {
|
||||
buffer.Write(scanner.Bytes())
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil && err != io.EOF {
|
||||
return err
|
||||
}
|
||||
|
||||
serverURL := strings.TrimSpace(buffer.String())
|
||||
|
||||
return helper.Delete(serverURL)
|
||||
}
|
37
vendor/src/github.com/docker/docker-credential-helpers/credentials/error.go
vendored
Normal file
37
vendor/src/github.com/docker/docker-credential-helpers/credentials/error.go
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
package credentials
|
||||
|
||||
// ErrCredentialsNotFound standarizes the not found error, so every helper returns
|
||||
// the same message and docker can handle it properly.
|
||||
const errCredentialsNotFoundMessage = "credentials not found in native keychain"
|
||||
|
||||
// errCredentialsNotFound represents an error
|
||||
// raised when credentials are not in the store.
|
||||
type errCredentialsNotFound struct{}
|
||||
|
||||
// Error returns the standard error message
|
||||
// for when the credentials are not in the store.
|
||||
func (errCredentialsNotFound) Error() string {
|
||||
return errCredentialsNotFoundMessage
|
||||
}
|
||||
|
||||
// NewErrCredentialsNotFound creates a new error
|
||||
// for when the credentials are not in the store.
|
||||
func NewErrCredentialsNotFound() error {
|
||||
return errCredentialsNotFound{}
|
||||
}
|
||||
|
||||
// IsErrCredentialsNotFound returns true if the error
|
||||
// was caused by not having a set of credentials in a store.
|
||||
func IsErrCredentialsNotFound(err error) bool {
|
||||
_, ok := err.(errCredentialsNotFound)
|
||||
return ok
|
||||
}
|
||||
|
||||
// IsErrCredentialsNotFoundMessage returns true if the error
|
||||
// was caused by not having a set of credentials in a store.
|
||||
//
|
||||
// This function helps to check messages returned by an
|
||||
// external program via its standard output.
|
||||
func IsErrCredentialsNotFoundMessage(err string) bool {
|
||||
return err == errCredentialsNotFoundMessage
|
||||
}
|
12
vendor/src/github.com/docker/docker-credential-helpers/credentials/helper.go
vendored
Normal file
12
vendor/src/github.com/docker/docker-credential-helpers/credentials/helper.go
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
package credentials
|
||||
|
||||
// Helper is the interface a credentials store helper must implement.
|
||||
type Helper interface {
|
||||
// Add appends credentials to the store.
|
||||
Add(*Credentials) error
|
||||
// Delete removes credentials from the store.
|
||||
Delete(serverURL string) error
|
||||
// Get retrieves credentials from the store.
|
||||
// It returns username and secret as strings.
|
||||
Get(serverURL string) (string, string, error)
|
||||
}
|
Loading…
Reference in a new issue