mirror of https://github.com/docker/docs.git
				
				
				
			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
					
				| 
						 | 
				
			
			@ -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
 | 
			
		||||
	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),
 | 
			
		||||
		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))
 | 
			
		||||
 | 
			
		||||
		if credentials.IsErrCredentialsNotFound(err) {
 | 
			
		||||
			// do not return an error if the credentials are not
 | 
			
		||||
			// in the keyckain. Let docker ask for new credentials.
 | 
			
		||||
		if t == errCredentialsNotFound.Error() {
 | 
			
		||||
			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,7 +91,7 @@ func TestNativeStoreAddCredentials(t *testing.T) {
 | 
			
		|||
	f.CredentialsStore = "mock"
 | 
			
		||||
 | 
			
		||||
	s := &nativeStore{
 | 
			
		||||
		commandFn: mockCommandFn,
 | 
			
		||||
		programFunc: mockCommandFn,
 | 
			
		||||
		fileStore:   NewFileStore(f),
 | 
			
		||||
	}
 | 
			
		||||
	err := s.Store(types.AuthConfig{
 | 
			
		||||
| 
						 | 
				
			
			@ -133,7 +135,7 @@ func TestNativeStoreAddInvalidCredentials(t *testing.T) {
 | 
			
		|||
	f.CredentialsStore = "mock"
 | 
			
		||||
 | 
			
		||||
	s := &nativeStore{
 | 
			
		||||
		commandFn: mockCommandFn,
 | 
			
		||||
		programFunc: mockCommandFn,
 | 
			
		||||
		fileStore:   NewFileStore(f),
 | 
			
		||||
	}
 | 
			
		||||
	err := s.Store(types.AuthConfig{
 | 
			
		||||
| 
						 | 
				
			
			@ -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,7 +167,7 @@ func TestNativeStoreGet(t *testing.T) {
 | 
			
		|||
	f.CredentialsStore = "mock"
 | 
			
		||||
 | 
			
		||||
	s := &nativeStore{
 | 
			
		||||
		commandFn: mockCommandFn,
 | 
			
		||||
		programFunc: mockCommandFn,
 | 
			
		||||
		fileStore:   NewFileStore(f),
 | 
			
		||||
	}
 | 
			
		||||
	a, err := s.Get(validServerAddress)
 | 
			
		||||
| 
						 | 
				
			
			@ -196,7 +198,7 @@ func TestNativeStoreGetIdentityToken(t *testing.T) {
 | 
			
		|||
	f.CredentialsStore = "mock"
 | 
			
		||||
 | 
			
		||||
	s := &nativeStore{
 | 
			
		||||
		commandFn: mockCommandFn,
 | 
			
		||||
		programFunc: mockCommandFn,
 | 
			
		||||
		fileStore:   NewFileStore(f),
 | 
			
		||||
	}
 | 
			
		||||
	a, err := s.Get(validServerAddress2)
 | 
			
		||||
| 
						 | 
				
			
			@ -230,7 +232,7 @@ func TestNativeStoreGetAll(t *testing.T) {
 | 
			
		|||
	f.CredentialsStore = "mock"
 | 
			
		||||
 | 
			
		||||
	s := &nativeStore{
 | 
			
		||||
		commandFn: mockCommandFn,
 | 
			
		||||
		programFunc: mockCommandFn,
 | 
			
		||||
		fileStore:   NewFileStore(f),
 | 
			
		||||
	}
 | 
			
		||||
	as, err := s.GetAll()
 | 
			
		||||
| 
						 | 
				
			
			@ -277,7 +279,7 @@ func TestNativeStoreGetMissingCredentials(t *testing.T) {
 | 
			
		|||
	f.CredentialsStore = "mock"
 | 
			
		||||
 | 
			
		||||
	s := &nativeStore{
 | 
			
		||||
		commandFn: mockCommandFn,
 | 
			
		||||
		programFunc: mockCommandFn,
 | 
			
		||||
		fileStore:   NewFileStore(f),
 | 
			
		||||
	}
 | 
			
		||||
	_, err := s.Get(missingCredsAddress)
 | 
			
		||||
| 
						 | 
				
			
			@ -296,7 +298,7 @@ func TestNativeStoreGetInvalidAddress(t *testing.T) {
 | 
			
		|||
	f.CredentialsStore = "mock"
 | 
			
		||||
 | 
			
		||||
	s := &nativeStore{
 | 
			
		||||
		commandFn: mockCommandFn,
 | 
			
		||||
		programFunc: mockCommandFn,
 | 
			
		||||
		fileStore:   NewFileStore(f),
 | 
			
		||||
	}
 | 
			
		||||
	_, err := s.Get(invalidServerAddress)
 | 
			
		||||
| 
						 | 
				
			
			@ -304,8 +306,8 @@ func TestNativeStoreGetInvalidAddress(t *testing.T) {
 | 
			
		|||
		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,7 +320,7 @@ func TestNativeStoreErase(t *testing.T) {
 | 
			
		|||
	f.CredentialsStore = "mock"
 | 
			
		||||
 | 
			
		||||
	s := &nativeStore{
 | 
			
		||||
		commandFn: mockCommandFn,
 | 
			
		||||
		programFunc: mockCommandFn,
 | 
			
		||||
		fileStore:   NewFileStore(f),
 | 
			
		||||
	}
 | 
			
		||||
	err := s.Erase(validServerAddress)
 | 
			
		||||
| 
						 | 
				
			
			@ -340,7 +342,7 @@ func TestNativeStoreEraseInvalidAddress(t *testing.T) {
 | 
			
		|||
	f.CredentialsStore = "mock"
 | 
			
		||||
 | 
			
		||||
	s := &nativeStore{
 | 
			
		||||
		commandFn: mockCommandFn,
 | 
			
		||||
		programFunc: mockCommandFn,
 | 
			
		||||
		fileStore:   NewFileStore(f),
 | 
			
		||||
	}
 | 
			
		||||
	err := s.Erase(invalidServerAddress)
 | 
			
		||||
| 
						 | 
				
			
			@ -348,7 +350,7 @@ func TestNativeStoreEraseInvalidAddress(t *testing.T) {
 | 
			
		|||
		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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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.
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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 New Issue