mirror of https://github.com/docker/cli.git
Move package cliconfig to cli/config
I felt it made more sence 👼
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
This commit is contained in:
parent
2825296deb
commit
bfe47a124a
|
@ -12,10 +12,10 @@ import (
|
||||||
"github.com/docker/docker/api"
|
"github.com/docker/docker/api"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/versions"
|
"github.com/docker/docker/api/types/versions"
|
||||||
|
cliconfig "github.com/docker/docker/cli/config"
|
||||||
|
"github.com/docker/docker/cli/config/configfile"
|
||||||
|
"github.com/docker/docker/cli/config/credentials"
|
||||||
cliflags "github.com/docker/docker/cli/flags"
|
cliflags "github.com/docker/docker/cli/flags"
|
||||||
"github.com/docker/docker/cliconfig"
|
|
||||||
"github.com/docker/docker/cliconfig/configfile"
|
|
||||||
"github.com/docker/docker/cliconfig/credentials"
|
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
"github.com/docker/docker/dockerversion"
|
"github.com/docker/docker/dockerversion"
|
||||||
dopts "github.com/docker/docker/opts"
|
dopts "github.com/docker/docker/opts"
|
||||||
|
@ -150,7 +150,7 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error {
|
||||||
cli.defaultVersion = cli.client.ClientVersion()
|
cli.defaultVersion = cli.client.ClientVersion()
|
||||||
|
|
||||||
if opts.Common.TrustKey == "" {
|
if opts.Common.TrustKey == "" {
|
||||||
cli.keyFile = filepath.Join(cliconfig.ConfigDir(), cliflags.DefaultTrustKeyFile)
|
cli.keyFile = filepath.Join(cliconfig.Dir(), cliflags.DefaultTrustKeyFile)
|
||||||
} else {
|
} else {
|
||||||
cli.keyFile = opts.Common.TrustKey
|
cli.keyFile = opts.Common.TrustKey
|
||||||
}
|
}
|
||||||
|
@ -179,7 +179,7 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer) *DockerCli {
|
||||||
// LoadDefaultConfigFile attempts to load the default config file and returns
|
// LoadDefaultConfigFile attempts to load the default config file and returns
|
||||||
// an initialized ConfigFile struct if none is found.
|
// an initialized ConfigFile struct if none is found.
|
||||||
func LoadDefaultConfigFile(err io.Writer) *configfile.ConfigFile {
|
func LoadDefaultConfigFile(err io.Writer) *configfile.ConfigFile {
|
||||||
configFile, e := cliconfig.Load(cliconfig.ConfigDir())
|
configFile, e := cliconfig.Load(cliconfig.Dir())
|
||||||
if e != nil {
|
if e != nil {
|
||||||
fmt.Fprintf(err, "WARNING: Error loading config file:%v\n", e)
|
fmt.Fprintf(err, "WARNING: Error loading config file:%v\n", e)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/cli/config/configfile"
|
||||||
|
"github.com/docker/docker/pkg/homedir"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ConfigFileName is the name of config file
|
||||||
|
ConfigFileName = "config.json"
|
||||||
|
configFileDir = ".docker"
|
||||||
|
oldConfigfile = ".dockercfg"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
configDir = os.Getenv("DOCKER_CONFIG")
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if configDir == "" {
|
||||||
|
configDir = filepath.Join(homedir.Get(), configFileDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dir returns the directory the configuration file is stored in
|
||||||
|
func Dir() string {
|
||||||
|
return configDir
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDir sets the directory the configuration file is stored in
|
||||||
|
func SetDir(dir string) {
|
||||||
|
configDir = dir
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConfigFile initializes an empty configuration file for the given filename 'fn'
|
||||||
|
func NewConfigFile(fn string) *configfile.ConfigFile {
|
||||||
|
return &configfile.ConfigFile{
|
||||||
|
AuthConfigs: make(map[string]types.AuthConfig),
|
||||||
|
HTTPHeaders: make(map[string]string),
|
||||||
|
Filename: fn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LegacyLoadFromReader is a convenience function that creates a ConfigFile object from
|
||||||
|
// a non-nested reader
|
||||||
|
func LegacyLoadFromReader(configData io.Reader) (*configfile.ConfigFile, error) {
|
||||||
|
configFile := configfile.ConfigFile{
|
||||||
|
AuthConfigs: make(map[string]types.AuthConfig),
|
||||||
|
}
|
||||||
|
err := configFile.LegacyLoadFromReader(configData)
|
||||||
|
return &configFile, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadFromReader is a convenience function that creates a ConfigFile object from
|
||||||
|
// a reader
|
||||||
|
func LoadFromReader(configData io.Reader) (*configfile.ConfigFile, error) {
|
||||||
|
configFile := configfile.ConfigFile{
|
||||||
|
AuthConfigs: make(map[string]types.AuthConfig),
|
||||||
|
}
|
||||||
|
err := configFile.LoadFromReader(configData)
|
||||||
|
return &configFile, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load reads the configuration files in the given directory, and sets up
|
||||||
|
// the auth config information and returns values.
|
||||||
|
// FIXME: use the internal golang config parser
|
||||||
|
func Load(configDir string) (*configfile.ConfigFile, error) {
|
||||||
|
if configDir == "" {
|
||||||
|
configDir = Dir()
|
||||||
|
}
|
||||||
|
|
||||||
|
configFile := configfile.ConfigFile{
|
||||||
|
AuthConfigs: make(map[string]types.AuthConfig),
|
||||||
|
Filename: filepath.Join(configDir, ConfigFileName),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try happy path first - latest config file
|
||||||
|
if _, err := os.Stat(configFile.Filename); err == nil {
|
||||||
|
file, err := os.Open(configFile.Filename)
|
||||||
|
if err != nil {
|
||||||
|
return &configFile, fmt.Errorf("%s - %v", configFile.Filename, err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
err = configFile.LoadFromReader(file)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("%s - %v", configFile.Filename, err)
|
||||||
|
}
|
||||||
|
return &configFile, err
|
||||||
|
} else if !os.IsNotExist(err) {
|
||||||
|
// if file is there but we can't stat it for any reason other
|
||||||
|
// than it doesn't exist then stop
|
||||||
|
return &configFile, fmt.Errorf("%s - %v", configFile.Filename, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Can't find latest config file so check for the old one
|
||||||
|
confFile := filepath.Join(homedir.Get(), oldConfigfile)
|
||||||
|
if _, err := os.Stat(confFile); err != nil {
|
||||||
|
return &configFile, nil //missing file is not an error
|
||||||
|
}
|
||||||
|
file, err := os.Open(confFile)
|
||||||
|
if err != nil {
|
||||||
|
return &configFile, fmt.Errorf("%s - %v", confFile, err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
err = configFile.LegacyLoadFromReader(file)
|
||||||
|
if err != nil {
|
||||||
|
return &configFile, fmt.Errorf("%s - %v", confFile, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if configFile.HTTPHeaders == nil {
|
||||||
|
configFile.HTTPHeaders = map[string]string{}
|
||||||
|
}
|
||||||
|
return &configFile, nil
|
||||||
|
}
|
|
@ -0,0 +1,621 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/docker/cli/config/configfile"
|
||||||
|
"github.com/docker/docker/pkg/homedir"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEmptyConfigDir(t *testing.T) {
|
||||||
|
tmpHome, err := ioutil.TempDir("", "config-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpHome)
|
||||||
|
|
||||||
|
SetDir(tmpHome)
|
||||||
|
|
||||||
|
config, err := Load("")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed loading on empty config dir: %q", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedConfigFilename := filepath.Join(tmpHome, ConfigFileName)
|
||||||
|
if config.Filename != expectedConfigFilename {
|
||||||
|
t.Fatalf("Expected config filename %s, got %s", expectedConfigFilename, config.Filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now save it and make sure it shows up in new form
|
||||||
|
saveConfigAndValidateNewFormat(t, config, tmpHome)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMissingFile(t *testing.T) {
|
||||||
|
tmpHome, err := ioutil.TempDir("", "config-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpHome)
|
||||||
|
|
||||||
|
config, err := Load(tmpHome)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed loading on missing file: %q", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now save it and make sure it shows up in new form
|
||||||
|
saveConfigAndValidateNewFormat(t, config, tmpHome)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSaveFileToDirs(t *testing.T) {
|
||||||
|
tmpHome, err := ioutil.TempDir("", "config-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpHome)
|
||||||
|
|
||||||
|
tmpHome += "/.docker"
|
||||||
|
|
||||||
|
config, err := Load(tmpHome)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed loading on missing file: %q", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now save it and make sure it shows up in new form
|
||||||
|
saveConfigAndValidateNewFormat(t, config, tmpHome)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEmptyFile(t *testing.T) {
|
||||||
|
tmpHome, err := ioutil.TempDir("", "config-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpHome)
|
||||||
|
|
||||||
|
fn := filepath.Join(tmpHome, ConfigFileName)
|
||||||
|
if err := ioutil.WriteFile(fn, []byte(""), 0600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = Load(tmpHome)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Was supposed to fail")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEmptyJSON(t *testing.T) {
|
||||||
|
tmpHome, err := ioutil.TempDir("", "config-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpHome)
|
||||||
|
|
||||||
|
fn := filepath.Join(tmpHome, ConfigFileName)
|
||||||
|
if err := ioutil.WriteFile(fn, []byte("{}"), 0600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
config, err := Load(tmpHome)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed loading on empty json file: %q", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now save it and make sure it shows up in new form
|
||||||
|
saveConfigAndValidateNewFormat(t, config, tmpHome)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOldInvalidsAuth(t *testing.T) {
|
||||||
|
invalids := map[string]string{
|
||||||
|
`username = test`: "The Auth config file is empty",
|
||||||
|
`username
|
||||||
|
password`: "Invalid Auth config file",
|
||||||
|
`username = test
|
||||||
|
email`: "Invalid auth configuration file",
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpHome, err := ioutil.TempDir("", "config-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpHome)
|
||||||
|
|
||||||
|
homeKey := homedir.Key()
|
||||||
|
homeVal := homedir.Get()
|
||||||
|
|
||||||
|
defer func() { os.Setenv(homeKey, homeVal) }()
|
||||||
|
os.Setenv(homeKey, tmpHome)
|
||||||
|
|
||||||
|
for content, expectedError := range invalids {
|
||||||
|
fn := filepath.Join(tmpHome, oldConfigfile)
|
||||||
|
if err := ioutil.WriteFile(fn, []byte(content), 0600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
config, err := Load(tmpHome)
|
||||||
|
// Use Contains instead of == since the file name will change each time
|
||||||
|
if err == nil || !strings.Contains(err.Error(), expectedError) {
|
||||||
|
t.Fatalf("Should have failed\nConfig: %v\nGot: %v\nExpected: %v", config, err, expectedError)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOldValidAuth(t *testing.T) {
|
||||||
|
tmpHome, err := ioutil.TempDir("", "config-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpHome)
|
||||||
|
|
||||||
|
homeKey := homedir.Key()
|
||||||
|
homeVal := homedir.Get()
|
||||||
|
|
||||||
|
defer func() { os.Setenv(homeKey, homeVal) }()
|
||||||
|
os.Setenv(homeKey, tmpHome)
|
||||||
|
|
||||||
|
fn := filepath.Join(tmpHome, oldConfigfile)
|
||||||
|
js := `username = am9lam9lOmhlbGxv
|
||||||
|
email = user@example.com`
|
||||||
|
if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
config, err := Load(tmpHome)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// defaultIndexserver is https://index.docker.io/v1/
|
||||||
|
ac := config.AuthConfigs["https://index.docker.io/v1/"]
|
||||||
|
if ac.Username != "joejoe" || ac.Password != "hello" {
|
||||||
|
t.Fatalf("Missing data from parsing:\n%q", config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now save it and make sure it shows up in new form
|
||||||
|
configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
|
||||||
|
|
||||||
|
expConfStr := `{
|
||||||
|
"auths": {
|
||||||
|
"https://index.docker.io/v1/": {
|
||||||
|
"auth": "am9lam9lOmhlbGxv"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
if configStr != expConfStr {
|
||||||
|
t.Fatalf("Should have save in new form: \n%s\n not \n%s", configStr, expConfStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOldJSONInvalid(t *testing.T) {
|
||||||
|
tmpHome, err := ioutil.TempDir("", "config-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpHome)
|
||||||
|
|
||||||
|
homeKey := homedir.Key()
|
||||||
|
homeVal := homedir.Get()
|
||||||
|
|
||||||
|
defer func() { os.Setenv(homeKey, homeVal) }()
|
||||||
|
os.Setenv(homeKey, tmpHome)
|
||||||
|
|
||||||
|
fn := filepath.Join(tmpHome, oldConfigfile)
|
||||||
|
js := `{"https://index.docker.io/v1/":{"auth":"test","email":"user@example.com"}}`
|
||||||
|
if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
config, err := Load(tmpHome)
|
||||||
|
// Use Contains instead of == since the file name will change each time
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "Invalid auth configuration file") {
|
||||||
|
t.Fatalf("Expected an error got : %v, %v", config, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOldJSON(t *testing.T) {
|
||||||
|
tmpHome, err := ioutil.TempDir("", "config-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpHome)
|
||||||
|
|
||||||
|
homeKey := homedir.Key()
|
||||||
|
homeVal := homedir.Get()
|
||||||
|
|
||||||
|
defer func() { os.Setenv(homeKey, homeVal) }()
|
||||||
|
os.Setenv(homeKey, tmpHome)
|
||||||
|
|
||||||
|
fn := filepath.Join(tmpHome, oldConfigfile)
|
||||||
|
js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}`
|
||||||
|
if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
config, err := Load(tmpHome)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed loading on empty json file: %q", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ac := config.AuthConfigs["https://index.docker.io/v1/"]
|
||||||
|
if ac.Username != "joejoe" || ac.Password != "hello" {
|
||||||
|
t.Fatalf("Missing data from parsing:\n%q", config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now save it and make sure it shows up in new form
|
||||||
|
configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
|
||||||
|
|
||||||
|
expConfStr := `{
|
||||||
|
"auths": {
|
||||||
|
"https://index.docker.io/v1/": {
|
||||||
|
"auth": "am9lam9lOmhlbGxv",
|
||||||
|
"email": "user@example.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
if configStr != expConfStr {
|
||||||
|
t.Fatalf("Should have save in new form: \n'%s'\n not \n'%s'\n", configStr, expConfStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewJSON(t *testing.T) {
|
||||||
|
tmpHome, err := ioutil.TempDir("", "config-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpHome)
|
||||||
|
|
||||||
|
fn := filepath.Join(tmpHome, ConfigFileName)
|
||||||
|
js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv" } } }`
|
||||||
|
if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
config, err := Load(tmpHome)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed loading on empty json file: %q", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ac := config.AuthConfigs["https://index.docker.io/v1/"]
|
||||||
|
if ac.Username != "joejoe" || ac.Password != "hello" {
|
||||||
|
t.Fatalf("Missing data from parsing:\n%q", config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now save it and make sure it shows up in new form
|
||||||
|
configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
|
||||||
|
|
||||||
|
expConfStr := `{
|
||||||
|
"auths": {
|
||||||
|
"https://index.docker.io/v1/": {
|
||||||
|
"auth": "am9lam9lOmhlbGxv"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
if configStr != expConfStr {
|
||||||
|
t.Fatalf("Should have save in new form: \n%s\n not \n%s", configStr, expConfStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewJSONNoEmail(t *testing.T) {
|
||||||
|
tmpHome, err := ioutil.TempDir("", "config-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpHome)
|
||||||
|
|
||||||
|
fn := filepath.Join(tmpHome, ConfigFileName)
|
||||||
|
js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv" } } }`
|
||||||
|
if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
config, err := Load(tmpHome)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed loading on empty json file: %q", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ac := config.AuthConfigs["https://index.docker.io/v1/"]
|
||||||
|
if ac.Username != "joejoe" || ac.Password != "hello" {
|
||||||
|
t.Fatalf("Missing data from parsing:\n%q", config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now save it and make sure it shows up in new form
|
||||||
|
configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
|
||||||
|
|
||||||
|
expConfStr := `{
|
||||||
|
"auths": {
|
||||||
|
"https://index.docker.io/v1/": {
|
||||||
|
"auth": "am9lam9lOmhlbGxv"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
if configStr != expConfStr {
|
||||||
|
t.Fatalf("Should have save in new form: \n%s\n not \n%s", configStr, expConfStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJSONWithPsFormat(t *testing.T) {
|
||||||
|
tmpHome, err := ioutil.TempDir("", "config-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpHome)
|
||||||
|
|
||||||
|
fn := filepath.Join(tmpHome, ConfigFileName)
|
||||||
|
js := `{
|
||||||
|
"auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } },
|
||||||
|
"psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}"
|
||||||
|
}`
|
||||||
|
if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
config, err := Load(tmpHome)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed loading on empty json file: %q", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.PsFormat != `table {{.ID}}\t{{.Label "com.docker.label.cpu"}}` {
|
||||||
|
t.Fatalf("Unknown ps format: %s\n", config.PsFormat)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now save it and make sure it shows up in new form
|
||||||
|
configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
|
||||||
|
if !strings.Contains(configStr, `"psFormat":`) ||
|
||||||
|
!strings.Contains(configStr, "{{.ID}}") {
|
||||||
|
t.Fatalf("Should have save in new form: %s", configStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJSONWithCredentialStore(t *testing.T) {
|
||||||
|
tmpHome, err := ioutil.TempDir("", "config-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpHome)
|
||||||
|
|
||||||
|
fn := filepath.Join(tmpHome, ConfigFileName)
|
||||||
|
js := `{
|
||||||
|
"auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } },
|
||||||
|
"credsStore": "crazy-secure-storage"
|
||||||
|
}`
|
||||||
|
if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
config, err := Load(tmpHome)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed loading on empty json file: %q", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.CredentialsStore != "crazy-secure-storage" {
|
||||||
|
t.Fatalf("Unknown credential store: %s\n", config.CredentialsStore)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now save it and make sure it shows up in new form
|
||||||
|
configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
|
||||||
|
if !strings.Contains(configStr, `"credsStore":`) ||
|
||||||
|
!strings.Contains(configStr, "crazy-secure-storage") {
|
||||||
|
t.Fatalf("Should have save in new form: %s", configStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJSONWithCredentialHelpers(t *testing.T) {
|
||||||
|
tmpHome, err := ioutil.TempDir("", "config-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpHome)
|
||||||
|
|
||||||
|
fn := filepath.Join(tmpHome, ConfigFileName)
|
||||||
|
js := `{
|
||||||
|
"auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } },
|
||||||
|
"credHelpers": { "images.io": "images-io", "containers.com": "crazy-secure-storage" }
|
||||||
|
}`
|
||||||
|
if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
config, err := Load(tmpHome)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed loading on empty json file: %q", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.CredentialHelpers == nil {
|
||||||
|
t.Fatal("config.CredentialHelpers was nil")
|
||||||
|
} else if config.CredentialHelpers["images.io"] != "images-io" ||
|
||||||
|
config.CredentialHelpers["containers.com"] != "crazy-secure-storage" {
|
||||||
|
t.Fatalf("Credential helpers not deserialized properly: %v\n", config.CredentialHelpers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now save it and make sure it shows up in new form
|
||||||
|
configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
|
||||||
|
if !strings.Contains(configStr, `"credHelpers":`) ||
|
||||||
|
!strings.Contains(configStr, "images.io") ||
|
||||||
|
!strings.Contains(configStr, "images-io") ||
|
||||||
|
!strings.Contains(configStr, "containers.com") ||
|
||||||
|
!strings.Contains(configStr, "crazy-secure-storage") {
|
||||||
|
t.Fatalf("Should have save in new form: %s", configStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save it and make sure it shows up in new form
|
||||||
|
func saveConfigAndValidateNewFormat(t *testing.T, config *configfile.ConfigFile, homeFolder string) string {
|
||||||
|
if err := config.Save(); err != nil {
|
||||||
|
t.Fatalf("Failed to save: %q", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadFile(filepath.Join(homeFolder, ConfigFileName))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(buf), `"auths":`) {
|
||||||
|
t.Fatalf("Should have save in new form: %s", string(buf))
|
||||||
|
}
|
||||||
|
return string(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigDir(t *testing.T) {
|
||||||
|
tmpHome, err := ioutil.TempDir("", "config-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpHome)
|
||||||
|
|
||||||
|
if Dir() == tmpHome {
|
||||||
|
t.Fatalf("Expected ConfigDir to be different than %s by default, but was the same", tmpHome)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update configDir
|
||||||
|
SetDir(tmpHome)
|
||||||
|
|
||||||
|
if Dir() != tmpHome {
|
||||||
|
t.Fatalf("Expected ConfigDir to %s, but was %s", tmpHome, Dir())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigFile(t *testing.T) {
|
||||||
|
configFilename := "configFilename"
|
||||||
|
configFile := NewConfigFile(configFilename)
|
||||||
|
|
||||||
|
if configFile.Filename != configFilename {
|
||||||
|
t.Fatalf("Expected %s, got %s", configFilename, configFile.Filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJSONReaderNoFile(t *testing.T) {
|
||||||
|
js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } } }`
|
||||||
|
|
||||||
|
config, err := LoadFromReader(strings.NewReader(js))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed loading on empty json file: %q", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ac := config.AuthConfigs["https://index.docker.io/v1/"]
|
||||||
|
if ac.Username != "joejoe" || ac.Password != "hello" {
|
||||||
|
t.Fatalf("Missing data from parsing:\n%q", config)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOldJSONReaderNoFile(t *testing.T) {
|
||||||
|
js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}`
|
||||||
|
|
||||||
|
config, err := LegacyLoadFromReader(strings.NewReader(js))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed loading on empty json file: %q", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ac := config.AuthConfigs["https://index.docker.io/v1/"]
|
||||||
|
if ac.Username != "joejoe" || ac.Password != "hello" {
|
||||||
|
t.Fatalf("Missing data from parsing:\n%q", config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJSONWithPsFormatNoFile(t *testing.T) {
|
||||||
|
js := `{
|
||||||
|
"auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } },
|
||||||
|
"psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}"
|
||||||
|
}`
|
||||||
|
config, err := LoadFromReader(strings.NewReader(js))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed loading on empty json file: %q", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.PsFormat != `table {{.ID}}\t{{.Label "com.docker.label.cpu"}}` {
|
||||||
|
t.Fatalf("Unknown ps format: %s\n", config.PsFormat)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJSONSaveWithNoFile(t *testing.T) {
|
||||||
|
js := `{
|
||||||
|
"auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv" } },
|
||||||
|
"psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}"
|
||||||
|
}`
|
||||||
|
config, err := LoadFromReader(strings.NewReader(js))
|
||||||
|
err = config.Save()
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Expected error. File should not have been able to save with no file name.")
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpHome, err := ioutil.TempDir("", "config-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create a temp dir: %q", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpHome)
|
||||||
|
|
||||||
|
fn := filepath.Join(tmpHome, ConfigFileName)
|
||||||
|
f, _ := os.OpenFile(fn, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
err = config.SaveToWriter(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed saving to file: %q", err)
|
||||||
|
}
|
||||||
|
buf, err := ioutil.ReadFile(filepath.Join(tmpHome, ConfigFileName))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
expConfStr := `{
|
||||||
|
"auths": {
|
||||||
|
"https://index.docker.io/v1/": {
|
||||||
|
"auth": "am9lam9lOmhlbGxv"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}"
|
||||||
|
}`
|
||||||
|
if string(buf) != expConfStr {
|
||||||
|
t.Fatalf("Should have save in new form: \n%s\nnot \n%s", string(buf), expConfStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLegacyJSONSaveWithNoFile(t *testing.T) {
|
||||||
|
|
||||||
|
js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}`
|
||||||
|
config, err := LegacyLoadFromReader(strings.NewReader(js))
|
||||||
|
err = config.Save()
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Expected error. File should not have been able to save with no file name.")
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpHome, err := ioutil.TempDir("", "config-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create a temp dir: %q", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpHome)
|
||||||
|
|
||||||
|
fn := filepath.Join(tmpHome, ConfigFileName)
|
||||||
|
f, _ := os.OpenFile(fn, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
if err = config.SaveToWriter(f); err != nil {
|
||||||
|
t.Fatalf("Failed saving to file: %q", err)
|
||||||
|
}
|
||||||
|
buf, err := ioutil.ReadFile(filepath.Join(tmpHome, ConfigFileName))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expConfStr := `{
|
||||||
|
"auths": {
|
||||||
|
"https://index.docker.io/v1/": {
|
||||||
|
"auth": "am9lam9lOmhlbGxv",
|
||||||
|
"email": "user@example.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
if string(buf) != expConfStr {
|
||||||
|
t.Fatalf("Should have save in new form: \n%s\n not \n%s", string(buf), expConfStr)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,183 @@
|
||||||
|
package configfile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// This constant is only used for really old config files when the
|
||||||
|
// URL wasn't saved as part of the config file and it was just
|
||||||
|
// assumed to be this value.
|
||||||
|
defaultIndexserver = "https://index.docker.io/v1/"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConfigFile ~/.docker/config.json file info
|
||||||
|
type ConfigFile struct {
|
||||||
|
AuthConfigs map[string]types.AuthConfig `json:"auths"`
|
||||||
|
HTTPHeaders map[string]string `json:"HttpHeaders,omitempty"`
|
||||||
|
PsFormat string `json:"psFormat,omitempty"`
|
||||||
|
ImagesFormat string `json:"imagesFormat,omitempty"`
|
||||||
|
NetworksFormat string `json:"networksFormat,omitempty"`
|
||||||
|
VolumesFormat string `json:"volumesFormat,omitempty"`
|
||||||
|
StatsFormat string `json:"statsFormat,omitempty"`
|
||||||
|
DetachKeys string `json:"detachKeys,omitempty"`
|
||||||
|
CredentialsStore string `json:"credsStore,omitempty"`
|
||||||
|
CredentialHelpers map[string]string `json:"credHelpers,omitempty"`
|
||||||
|
Filename string `json:"-"` // Note: for internal use only
|
||||||
|
ServiceInspectFormat string `json:"serviceInspectFormat,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LegacyLoadFromReader reads the non-nested configuration data given and sets up the
|
||||||
|
// auth config information with given directory and populates the receiver object
|
||||||
|
func (configFile *ConfigFile) LegacyLoadFromReader(configData io.Reader) error {
|
||||||
|
b, err := ioutil.ReadAll(configData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(b, &configFile.AuthConfigs); err != nil {
|
||||||
|
arr := strings.Split(string(b), "\n")
|
||||||
|
if len(arr) < 2 {
|
||||||
|
return fmt.Errorf("The Auth config file is empty")
|
||||||
|
}
|
||||||
|
authConfig := types.AuthConfig{}
|
||||||
|
origAuth := strings.Split(arr[0], " = ")
|
||||||
|
if len(origAuth) != 2 {
|
||||||
|
return fmt.Errorf("Invalid Auth config file")
|
||||||
|
}
|
||||||
|
authConfig.Username, authConfig.Password, err = decodeAuth(origAuth[1])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
authConfig.ServerAddress = defaultIndexserver
|
||||||
|
configFile.AuthConfigs[defaultIndexserver] = authConfig
|
||||||
|
} else {
|
||||||
|
for k, authConfig := range configFile.AuthConfigs {
|
||||||
|
authConfig.Username, authConfig.Password, err = decodeAuth(authConfig.Auth)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
authConfig.Auth = ""
|
||||||
|
authConfig.ServerAddress = k
|
||||||
|
configFile.AuthConfigs[k] = authConfig
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadFromReader reads the configuration data given and sets up the auth config
|
||||||
|
// information with given directory and populates the receiver object
|
||||||
|
func (configFile *ConfigFile) LoadFromReader(configData io.Reader) error {
|
||||||
|
if err := json.NewDecoder(configData).Decode(&configFile); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
for addr, ac := range configFile.AuthConfigs {
|
||||||
|
ac.Username, ac.Password, err = decodeAuth(ac.Auth)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ac.Auth = ""
|
||||||
|
ac.ServerAddress = addr
|
||||||
|
configFile.AuthConfigs[addr] = ac
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainsAuth returns whether there is authentication configured
|
||||||
|
// in this file or not.
|
||||||
|
func (configFile *ConfigFile) ContainsAuth() bool {
|
||||||
|
return configFile.CredentialsStore != "" ||
|
||||||
|
len(configFile.CredentialHelpers) > 0 ||
|
||||||
|
len(configFile.AuthConfigs) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveToWriter encodes and writes out all the authorization information to
|
||||||
|
// the given writer
|
||||||
|
func (configFile *ConfigFile) SaveToWriter(writer io.Writer) error {
|
||||||
|
// Encode sensitive data into a new/temp struct
|
||||||
|
tmpAuthConfigs := make(map[string]types.AuthConfig, len(configFile.AuthConfigs))
|
||||||
|
for k, authConfig := range configFile.AuthConfigs {
|
||||||
|
authCopy := authConfig
|
||||||
|
// encode and save the authstring, while blanking out the original fields
|
||||||
|
authCopy.Auth = encodeAuth(&authCopy)
|
||||||
|
authCopy.Username = ""
|
||||||
|
authCopy.Password = ""
|
||||||
|
authCopy.ServerAddress = ""
|
||||||
|
tmpAuthConfigs[k] = authCopy
|
||||||
|
}
|
||||||
|
|
||||||
|
saveAuthConfigs := configFile.AuthConfigs
|
||||||
|
configFile.AuthConfigs = tmpAuthConfigs
|
||||||
|
defer func() { configFile.AuthConfigs = saveAuthConfigs }()
|
||||||
|
|
||||||
|
data, err := json.MarshalIndent(configFile, "", "\t")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = writer.Write(data)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save encodes and writes out all the authorization information
|
||||||
|
func (configFile *ConfigFile) Save() error {
|
||||||
|
if configFile.Filename == "" {
|
||||||
|
return fmt.Errorf("Can't save config with empty filename")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll(filepath.Dir(configFile.Filename), 0700); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f, err := os.OpenFile(configFile.Filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
return configFile.SaveToWriter(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeAuth creates a base64 encoded string to containing authorization information
|
||||||
|
func encodeAuth(authConfig *types.AuthConfig) string {
|
||||||
|
if authConfig.Username == "" && authConfig.Password == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
authStr := authConfig.Username + ":" + authConfig.Password
|
||||||
|
msg := []byte(authStr)
|
||||||
|
encoded := make([]byte, base64.StdEncoding.EncodedLen(len(msg)))
|
||||||
|
base64.StdEncoding.Encode(encoded, msg)
|
||||||
|
return string(encoded)
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeAuth decodes a base64 encoded string and returns username and password
|
||||||
|
func decodeAuth(authStr string) (string, string, error) {
|
||||||
|
if authStr == "" {
|
||||||
|
return "", "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
decLen := base64.StdEncoding.DecodedLen(len(authStr))
|
||||||
|
decoded := make([]byte, decLen)
|
||||||
|
authByte := []byte(authStr)
|
||||||
|
n, err := base64.StdEncoding.Decode(decoded, authByte)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
if n > decLen {
|
||||||
|
return "", "", fmt.Errorf("Something went wrong decoding auth config")
|
||||||
|
}
|
||||||
|
arr := strings.SplitN(string(decoded), ":", 2)
|
||||||
|
if len(arr) != 2 {
|
||||||
|
return "", "", fmt.Errorf("Invalid auth configuration file")
|
||||||
|
}
|
||||||
|
password := strings.Trim(arr[1], "\x00")
|
||||||
|
return arr[0], password, nil
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package configfile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEncodeAuth(t *testing.T) {
|
||||||
|
newAuthConfig := &types.AuthConfig{Username: "ken", Password: "test"}
|
||||||
|
authStr := encodeAuth(newAuthConfig)
|
||||||
|
decAuthConfig := &types.AuthConfig{}
|
||||||
|
var err error
|
||||||
|
decAuthConfig.Username, decAuthConfig.Password, err = decodeAuth(authStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if newAuthConfig.Username != decAuthConfig.Username {
|
||||||
|
t.Fatal("Encode Username doesn't match decoded Username")
|
||||||
|
}
|
||||||
|
if newAuthConfig.Password != decAuthConfig.Password {
|
||||||
|
t.Fatal("Encode Password doesn't match decoded Password")
|
||||||
|
}
|
||||||
|
if authStr != "a2VuOnRlc3Q=" {
|
||||||
|
t.Fatal("AuthString encoding isn't correct.")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package credentials
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Store is the interface that any credentials store must implement.
|
||||||
|
type Store interface {
|
||||||
|
// Erase removes credentials from the store for a given server.
|
||||||
|
Erase(serverAddress string) error
|
||||||
|
// Get retrieves credentials from the store for a given server.
|
||||||
|
Get(serverAddress string) (types.AuthConfig, error)
|
||||||
|
// GetAll retrieves all the credentials from the store.
|
||||||
|
GetAll() (map[string]types.AuthConfig, error)
|
||||||
|
// Store saves credentials in the store.
|
||||||
|
Store(authConfig types.AuthConfig) error
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package credentials
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/exec"
|
||||||
|
|
||||||
|
"github.com/docker/docker/cli/config/configfile"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DetectDefaultStore sets the default credentials store
|
||||||
|
// if the host includes the default store helper program.
|
||||||
|
func DetectDefaultStore(c *configfile.ConfigFile) {
|
||||||
|
if c.CredentialsStore != "" {
|
||||||
|
// user defined
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if defaultCredentialsStore != "" {
|
||||||
|
if _, err := exec.LookPath(remoteCredentialsPrefix + defaultCredentialsStore); err == nil {
|
||||||
|
c.CredentialsStore = defaultCredentialsStore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
package credentials
|
||||||
|
|
||||||
|
const defaultCredentialsStore = "osxkeychain"
|
|
@ -0,0 +1,3 @@
|
||||||
|
package credentials
|
||||||
|
|
||||||
|
const defaultCredentialsStore = "secretservice"
|
|
@ -0,0 +1,5 @@
|
||||||
|
// +build !windows,!darwin,!linux
|
||||||
|
|
||||||
|
package credentials
|
||||||
|
|
||||||
|
const defaultCredentialsStore = ""
|
|
@ -0,0 +1,3 @@
|
||||||
|
package credentials
|
||||||
|
|
||||||
|
const defaultCredentialsStore = "wincred"
|
|
@ -0,0 +1,53 @@
|
||||||
|
package credentials
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/cli/config/configfile"
|
||||||
|
"github.com/docker/docker/registry"
|
||||||
|
)
|
||||||
|
|
||||||
|
// fileStore implements a credentials store using
|
||||||
|
// the docker configuration file to keep the credentials in plain text.
|
||||||
|
type fileStore struct {
|
||||||
|
file *configfile.ConfigFile
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFileStore creates a new file credentials store.
|
||||||
|
func NewFileStore(file *configfile.ConfigFile) Store {
|
||||||
|
return &fileStore{
|
||||||
|
file: file,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Erase removes the given credentials from the file store.
|
||||||
|
func (c *fileStore) Erase(serverAddress string) error {
|
||||||
|
delete(c.file.AuthConfigs, serverAddress)
|
||||||
|
return c.file.Save()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get retrieves credentials for a specific server from the file store.
|
||||||
|
func (c *fileStore) Get(serverAddress string) (types.AuthConfig, error) {
|
||||||
|
authConfig, ok := c.file.AuthConfigs[serverAddress]
|
||||||
|
if !ok {
|
||||||
|
// Maybe they have a legacy config file, we will iterate the keys converting
|
||||||
|
// them to the new format and testing
|
||||||
|
for r, ac := range c.file.AuthConfigs {
|
||||||
|
if serverAddress == registry.ConvertToHostname(r) {
|
||||||
|
return ac, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
authConfig = types.AuthConfig{}
|
||||||
|
}
|
||||||
|
return authConfig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fileStore) GetAll() (map[string]types.AuthConfig, error) {
|
||||||
|
return c.file.AuthConfigs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store saves the given credentials in the file store.
|
||||||
|
func (c *fileStore) Store(authConfig types.AuthConfig) error {
|
||||||
|
c.file.AuthConfigs[authConfig.ServerAddress] = authConfig
|
||||||
|
return c.file.Save()
|
||||||
|
}
|
|
@ -0,0 +1,139 @@
|
||||||
|
package credentials
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
cliconfig "github.com/docker/docker/cli/config"
|
||||||
|
"github.com/docker/docker/cli/config/configfile"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newConfigFile(auths map[string]types.AuthConfig) *configfile.ConfigFile {
|
||||||
|
tmp, _ := ioutil.TempFile("", "docker-test")
|
||||||
|
name := tmp.Name()
|
||||||
|
tmp.Close()
|
||||||
|
|
||||||
|
c := cliconfig.NewConfigFile(name)
|
||||||
|
c.AuthConfigs = auths
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileStoreAddCredentials(t *testing.T) {
|
||||||
|
f := newConfigFile(make(map[string]types.AuthConfig))
|
||||||
|
|
||||||
|
s := NewFileStore(f)
|
||||||
|
err := s.Store(types.AuthConfig{
|
||||||
|
Auth: "super_secret_token",
|
||||||
|
Email: "foo@example.com",
|
||||||
|
ServerAddress: "https://example.com",
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(f.AuthConfigs) != 1 {
|
||||||
|
t.Fatalf("expected 1 auth config, got %d", len(f.AuthConfigs))
|
||||||
|
}
|
||||||
|
|
||||||
|
a, ok := f.AuthConfigs["https://example.com"]
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected auth for https://example.com, got %v", f.AuthConfigs)
|
||||||
|
}
|
||||||
|
if a.Auth != "super_secret_token" {
|
||||||
|
t.Fatalf("expected auth `super_secret_token`, got %s", a.Auth)
|
||||||
|
}
|
||||||
|
if a.Email != "foo@example.com" {
|
||||||
|
t.Fatalf("expected email `foo@example.com`, got %s", a.Email)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileStoreGet(t *testing.T) {
|
||||||
|
f := newConfigFile(map[string]types.AuthConfig{
|
||||||
|
"https://example.com": {
|
||||||
|
Auth: "super_secret_token",
|
||||||
|
Email: "foo@example.com",
|
||||||
|
ServerAddress: "https://example.com",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
s := NewFileStore(f)
|
||||||
|
a, err := s.Get("https://example.com")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if a.Auth != "super_secret_token" {
|
||||||
|
t.Fatalf("expected auth `super_secret_token`, got %s", a.Auth)
|
||||||
|
}
|
||||||
|
if a.Email != "foo@example.com" {
|
||||||
|
t.Fatalf("expected email `foo@example.com`, got %s", a.Email)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileStoreGetAll(t *testing.T) {
|
||||||
|
s1 := "https://example.com"
|
||||||
|
s2 := "https://example2.com"
|
||||||
|
f := newConfigFile(map[string]types.AuthConfig{
|
||||||
|
s1: {
|
||||||
|
Auth: "super_secret_token",
|
||||||
|
Email: "foo@example.com",
|
||||||
|
ServerAddress: "https://example.com",
|
||||||
|
},
|
||||||
|
s2: {
|
||||||
|
Auth: "super_secret_token2",
|
||||||
|
Email: "foo@example2.com",
|
||||||
|
ServerAddress: "https://example2.com",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
s := NewFileStore(f)
|
||||||
|
as, err := s.GetAll()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(as) != 2 {
|
||||||
|
t.Fatalf("wanted 2, got %d", len(as))
|
||||||
|
}
|
||||||
|
if as[s1].Auth != "super_secret_token" {
|
||||||
|
t.Fatalf("expected auth `super_secret_token`, got %s", as[s1].Auth)
|
||||||
|
}
|
||||||
|
if as[s1].Email != "foo@example.com" {
|
||||||
|
t.Fatalf("expected email `foo@example.com`, got %s", as[s1].Email)
|
||||||
|
}
|
||||||
|
if as[s2].Auth != "super_secret_token2" {
|
||||||
|
t.Fatalf("expected auth `super_secret_token2`, got %s", as[s2].Auth)
|
||||||
|
}
|
||||||
|
if as[s2].Email != "foo@example2.com" {
|
||||||
|
t.Fatalf("expected email `foo@example2.com`, got %s", as[s2].Email)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileStoreErase(t *testing.T) {
|
||||||
|
f := newConfigFile(map[string]types.AuthConfig{
|
||||||
|
"https://example.com": {
|
||||||
|
Auth: "super_secret_token",
|
||||||
|
Email: "foo@example.com",
|
||||||
|
ServerAddress: "https://example.com",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
s := NewFileStore(f)
|
||||||
|
err := s.Erase("https://example.com")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// file store never returns errors, check that the auth config is empty
|
||||||
|
a, err := s.Get("https://example.com")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.Auth != "" {
|
||||||
|
t.Fatalf("expected empty auth token, got %s", a.Auth)
|
||||||
|
}
|
||||||
|
if a.Email != "" {
|
||||||
|
t.Fatalf("expected empty email, got %s", a.Email)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,144 @@
|
||||||
|
package credentials
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/docker/docker-credential-helpers/client"
|
||||||
|
"github.com/docker/docker-credential-helpers/credentials"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/cli/config/configfile"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
remoteCredentialsPrefix = "docker-credential-"
|
||||||
|
tokenUsername = "<token>"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
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, helperSuffix string) Store {
|
||||||
|
name := remoteCredentialsPrefix + helperSuffix
|
||||||
|
return &nativeStore{
|
||||||
|
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 := client.Erase(c.programFunc, serverAddress); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to plain text store to remove email
|
||||||
|
return c.fileStore.Erase(serverAddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get retrieves credentials for a specific server from the native store.
|
||||||
|
func (c *nativeStore) Get(serverAddress string) (types.AuthConfig, error) {
|
||||||
|
// load user email if it exist or an empty auth config.
|
||||||
|
auth, _ := c.fileStore.Get(serverAddress)
|
||||||
|
|
||||||
|
creds, err := c.getCredentialsFromStore(serverAddress)
|
||||||
|
if err != nil {
|
||||||
|
return auth, err
|
||||||
|
}
|
||||||
|
auth.Username = creds.Username
|
||||||
|
auth.IdentityToken = creds.IdentityToken
|
||||||
|
auth.Password = creds.Password
|
||||||
|
|
||||||
|
return auth, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAll retrieves all the credentials from the native store.
|
||||||
|
func (c *nativeStore) GetAll() (map[string]types.AuthConfig, error) {
|
||||||
|
auths, err := c.listCredentialsInStore()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emails are only stored in the file store.
|
||||||
|
// This call can be safely eliminated when emails are removed.
|
||||||
|
fileConfigs, _ := c.fileStore.GetAll()
|
||||||
|
|
||||||
|
authConfigs := make(map[string]types.AuthConfig)
|
||||||
|
for registry := range auths {
|
||||||
|
creds, err := c.getCredentialsFromStore(registry)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ac, _ := fileConfigs[registry] // might contain Email
|
||||||
|
ac.Username = creds.Username
|
||||||
|
ac.Password = creds.Password
|
||||||
|
ac.IdentityToken = creds.IdentityToken
|
||||||
|
authConfigs[registry] = ac
|
||||||
|
}
|
||||||
|
|
||||||
|
return authConfigs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store saves the given credentials in the file store.
|
||||||
|
func (c *nativeStore) Store(authConfig types.AuthConfig) error {
|
||||||
|
if err := c.storeCredentialsInStore(authConfig); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
authConfig.Username = ""
|
||||||
|
authConfig.Password = ""
|
||||||
|
authConfig.IdentityToken = ""
|
||||||
|
|
||||||
|
// Fallback to old credential in plain text to save only the email
|
||||||
|
return c.fileStore.Store(authConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// storeCredentialsInStore executes the command to store the credentials in the native store.
|
||||||
|
func (c *nativeStore) storeCredentialsInStore(config types.AuthConfig) error {
|
||||||
|
creds := &credentials.Credentials{
|
||||||
|
ServerURL: config.ServerAddress,
|
||||||
|
Username: config.Username,
|
||||||
|
Secret: config.Password,
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.IdentityToken != "" {
|
||||||
|
creds.Username = tokenUsername
|
||||||
|
creds.Secret = config.IdentityToken
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
creds, err := client.Get(c.programFunc, serverAddress)
|
||||||
|
if err != nil {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
return ret, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if creds.Username == tokenUsername {
|
||||||
|
ret.IdentityToken = creds.Secret
|
||||||
|
} else {
|
||||||
|
ret.Password = creds.Secret
|
||||||
|
ret.Username = creds.Username
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.ServerAddress = serverAddress
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// listCredentialsInStore returns a listing of stored credentials as a map of
|
||||||
|
// URL -> username.
|
||||||
|
func (c *nativeStore) listCredentialsInStore() (map[string]string, error) {
|
||||||
|
return client.List(c.programFunc)
|
||||||
|
}
|
|
@ -0,0 +1,355 @@
|
||||||
|
package credentials
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/docker-credential-helpers/client"
|
||||||
|
"github.com/docker/docker-credential-helpers/credentials"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
validServerAddress = "https://index.docker.io/v1"
|
||||||
|
validServerAddress2 = "https://example.com:5002"
|
||||||
|
invalidServerAddress = "https://foobar.example.com"
|
||||||
|
missingCredsAddress = "https://missing.docker.io/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
var errCommandExited = fmt.Errorf("exited 1")
|
||||||
|
|
||||||
|
// mockCommand simulates interactions between the docker client and a remote
|
||||||
|
// credentials helper.
|
||||||
|
// Unit tests inject this mocked command into the remote to control execution.
|
||||||
|
type mockCommand struct {
|
||||||
|
arg string
|
||||||
|
input io.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output returns responses from the remote credentials helper.
|
||||||
|
// It mocks those responses based in the input in the mock.
|
||||||
|
func (m *mockCommand) Output() ([]byte, error) {
|
||||||
|
in, err := ioutil.ReadAll(m.input)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
inS := string(in)
|
||||||
|
|
||||||
|
switch m.arg {
|
||||||
|
case "erase":
|
||||||
|
switch inS {
|
||||||
|
case validServerAddress:
|
||||||
|
return nil, nil
|
||||||
|
default:
|
||||||
|
return []byte("program failed"), errCommandExited
|
||||||
|
}
|
||||||
|
case "get":
|
||||||
|
switch inS {
|
||||||
|
case validServerAddress:
|
||||||
|
return []byte(`{"Username": "foo", "Secret": "bar"}`), nil
|
||||||
|
case validServerAddress2:
|
||||||
|
return []byte(`{"Username": "<token>", "Secret": "abcd1234"}`), nil
|
||||||
|
case missingCredsAddress:
|
||||||
|
return []byte(credentials.NewErrCredentialsNotFound().Error()), errCommandExited
|
||||||
|
case invalidServerAddress:
|
||||||
|
return []byte("program failed"), errCommandExited
|
||||||
|
}
|
||||||
|
case "store":
|
||||||
|
var c credentials.Credentials
|
||||||
|
err := json.NewDecoder(strings.NewReader(inS)).Decode(&c)
|
||||||
|
if err != nil {
|
||||||
|
return []byte("program failed"), errCommandExited
|
||||||
|
}
|
||||||
|
switch c.ServerURL {
|
||||||
|
case validServerAddress:
|
||||||
|
return nil, nil
|
||||||
|
default:
|
||||||
|
return []byte("program failed"), errCommandExited
|
||||||
|
}
|
||||||
|
case "list":
|
||||||
|
return []byte(fmt.Sprintf(`{"%s": "%s", "%s": "%s"}`, validServerAddress, "foo", validServerAddress2, "<token>")), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return []byte(fmt.Sprintf("unknown argument %q with %q", m.arg, inS)), errCommandExited
|
||||||
|
}
|
||||||
|
|
||||||
|
// Input sets the input to send to a remote credentials helper.
|
||||||
|
func (m *mockCommand) Input(in io.Reader) {
|
||||||
|
m.input = in
|
||||||
|
}
|
||||||
|
|
||||||
|
func mockCommandFn(args ...string) client.Program {
|
||||||
|
return &mockCommand{
|
||||||
|
arg: args[0],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNativeStoreAddCredentials(t *testing.T) {
|
||||||
|
f := newConfigFile(make(map[string]types.AuthConfig))
|
||||||
|
f.CredentialsStore = "mock"
|
||||||
|
|
||||||
|
s := &nativeStore{
|
||||||
|
programFunc: mockCommandFn,
|
||||||
|
fileStore: NewFileStore(f),
|
||||||
|
}
|
||||||
|
err := s.Store(types.AuthConfig{
|
||||||
|
Username: "foo",
|
||||||
|
Password: "bar",
|
||||||
|
Email: "foo@example.com",
|
||||||
|
ServerAddress: validServerAddress,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(f.AuthConfigs) != 1 {
|
||||||
|
t.Fatalf("expected 1 auth config, got %d", len(f.AuthConfigs))
|
||||||
|
}
|
||||||
|
|
||||||
|
a, ok := f.AuthConfigs[validServerAddress]
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected auth for %s, got %v", validServerAddress, f.AuthConfigs)
|
||||||
|
}
|
||||||
|
if a.Auth != "" {
|
||||||
|
t.Fatalf("expected auth to be empty, got %s", a.Auth)
|
||||||
|
}
|
||||||
|
if a.Username != "" {
|
||||||
|
t.Fatalf("expected username to be empty, got %s", a.Username)
|
||||||
|
}
|
||||||
|
if a.Password != "" {
|
||||||
|
t.Fatalf("expected password to be empty, got %s", a.Password)
|
||||||
|
}
|
||||||
|
if a.IdentityToken != "" {
|
||||||
|
t.Fatalf("expected identity token to be empty, got %s", a.IdentityToken)
|
||||||
|
}
|
||||||
|
if a.Email != "foo@example.com" {
|
||||||
|
t.Fatalf("expected email `foo@example.com`, got %s", a.Email)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNativeStoreAddInvalidCredentials(t *testing.T) {
|
||||||
|
f := newConfigFile(make(map[string]types.AuthConfig))
|
||||||
|
f.CredentialsStore = "mock"
|
||||||
|
|
||||||
|
s := &nativeStore{
|
||||||
|
programFunc: mockCommandFn,
|
||||||
|
fileStore: NewFileStore(f),
|
||||||
|
}
|
||||||
|
err := s.Store(types.AuthConfig{
|
||||||
|
Username: "foo",
|
||||||
|
Password: "bar",
|
||||||
|
Email: "foo@example.com",
|
||||||
|
ServerAddress: invalidServerAddress,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error, got nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(err.Error(), "program failed") {
|
||||||
|
t.Fatalf("expected `program failed`, got %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(f.AuthConfigs) != 0 {
|
||||||
|
t.Fatalf("expected 0 auth config, got %d", len(f.AuthConfigs))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNativeStoreGet(t *testing.T) {
|
||||||
|
f := newConfigFile(map[string]types.AuthConfig{
|
||||||
|
validServerAddress: {
|
||||||
|
Email: "foo@example.com",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
f.CredentialsStore = "mock"
|
||||||
|
|
||||||
|
s := &nativeStore{
|
||||||
|
programFunc: mockCommandFn,
|
||||||
|
fileStore: NewFileStore(f),
|
||||||
|
}
|
||||||
|
a, err := s.Get(validServerAddress)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.Username != "foo" {
|
||||||
|
t.Fatalf("expected username `foo`, got %s", a.Username)
|
||||||
|
}
|
||||||
|
if a.Password != "bar" {
|
||||||
|
t.Fatalf("expected password `bar`, got %s", a.Password)
|
||||||
|
}
|
||||||
|
if a.IdentityToken != "" {
|
||||||
|
t.Fatalf("expected identity token to be empty, got %s", a.IdentityToken)
|
||||||
|
}
|
||||||
|
if a.Email != "foo@example.com" {
|
||||||
|
t.Fatalf("expected email `foo@example.com`, got %s", a.Email)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNativeStoreGetIdentityToken(t *testing.T) {
|
||||||
|
f := newConfigFile(map[string]types.AuthConfig{
|
||||||
|
validServerAddress2: {
|
||||||
|
Email: "foo@example2.com",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
f.CredentialsStore = "mock"
|
||||||
|
|
||||||
|
s := &nativeStore{
|
||||||
|
programFunc: mockCommandFn,
|
||||||
|
fileStore: NewFileStore(f),
|
||||||
|
}
|
||||||
|
a, err := s.Get(validServerAddress2)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.Username != "" {
|
||||||
|
t.Fatalf("expected username to be empty, got %s", a.Username)
|
||||||
|
}
|
||||||
|
if a.Password != "" {
|
||||||
|
t.Fatalf("expected password to be empty, got %s", a.Password)
|
||||||
|
}
|
||||||
|
if a.IdentityToken != "abcd1234" {
|
||||||
|
t.Fatalf("expected identity token `abcd1234`, got %s", a.IdentityToken)
|
||||||
|
}
|
||||||
|
if a.Email != "foo@example2.com" {
|
||||||
|
t.Fatalf("expected email `foo@example2.com`, got %s", a.Email)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNativeStoreGetAll(t *testing.T) {
|
||||||
|
f := newConfigFile(map[string]types.AuthConfig{
|
||||||
|
validServerAddress: {
|
||||||
|
Email: "foo@example.com",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
f.CredentialsStore = "mock"
|
||||||
|
|
||||||
|
s := &nativeStore{
|
||||||
|
programFunc: mockCommandFn,
|
||||||
|
fileStore: NewFileStore(f),
|
||||||
|
}
|
||||||
|
as, err := s.GetAll()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(as) != 2 {
|
||||||
|
t.Fatalf("wanted 2, got %d", len(as))
|
||||||
|
}
|
||||||
|
|
||||||
|
if as[validServerAddress].Username != "foo" {
|
||||||
|
t.Fatalf("expected username `foo` for %s, got %s", validServerAddress, as[validServerAddress].Username)
|
||||||
|
}
|
||||||
|
if as[validServerAddress].Password != "bar" {
|
||||||
|
t.Fatalf("expected password `bar` for %s, got %s", validServerAddress, as[validServerAddress].Password)
|
||||||
|
}
|
||||||
|
if as[validServerAddress].IdentityToken != "" {
|
||||||
|
t.Fatalf("expected identity to be empty for %s, got %s", validServerAddress, as[validServerAddress].IdentityToken)
|
||||||
|
}
|
||||||
|
if as[validServerAddress].Email != "foo@example.com" {
|
||||||
|
t.Fatalf("expected email `foo@example.com` for %s, got %s", validServerAddress, as[validServerAddress].Email)
|
||||||
|
}
|
||||||
|
if as[validServerAddress2].Username != "" {
|
||||||
|
t.Fatalf("expected username to be empty for %s, got %s", validServerAddress2, as[validServerAddress2].Username)
|
||||||
|
}
|
||||||
|
if as[validServerAddress2].Password != "" {
|
||||||
|
t.Fatalf("expected password to be empty for %s, got %s", validServerAddress2, as[validServerAddress2].Password)
|
||||||
|
}
|
||||||
|
if as[validServerAddress2].IdentityToken != "abcd1234" {
|
||||||
|
t.Fatalf("expected identity token `abcd1324` for %s, got %s", validServerAddress2, as[validServerAddress2].IdentityToken)
|
||||||
|
}
|
||||||
|
if as[validServerAddress2].Email != "" {
|
||||||
|
t.Fatalf("expected no email for %s, got %s", validServerAddress2, as[validServerAddress2].Email)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNativeStoreGetMissingCredentials(t *testing.T) {
|
||||||
|
f := newConfigFile(map[string]types.AuthConfig{
|
||||||
|
validServerAddress: {
|
||||||
|
Email: "foo@example.com",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
f.CredentialsStore = "mock"
|
||||||
|
|
||||||
|
s := &nativeStore{
|
||||||
|
programFunc: mockCommandFn,
|
||||||
|
fileStore: NewFileStore(f),
|
||||||
|
}
|
||||||
|
_, err := s.Get(missingCredsAddress)
|
||||||
|
if err != nil {
|
||||||
|
// missing credentials do not produce an error
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNativeStoreGetInvalidAddress(t *testing.T) {
|
||||||
|
f := newConfigFile(map[string]types.AuthConfig{
|
||||||
|
validServerAddress: {
|
||||||
|
Email: "foo@example.com",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
f.CredentialsStore = "mock"
|
||||||
|
|
||||||
|
s := &nativeStore{
|
||||||
|
programFunc: mockCommandFn,
|
||||||
|
fileStore: NewFileStore(f),
|
||||||
|
}
|
||||||
|
_, err := s.Get(invalidServerAddress)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error, got nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(err.Error(), "program failed") {
|
||||||
|
t.Fatalf("expected `program failed`, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNativeStoreErase(t *testing.T) {
|
||||||
|
f := newConfigFile(map[string]types.AuthConfig{
|
||||||
|
validServerAddress: {
|
||||||
|
Email: "foo@example.com",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
f.CredentialsStore = "mock"
|
||||||
|
|
||||||
|
s := &nativeStore{
|
||||||
|
programFunc: mockCommandFn,
|
||||||
|
fileStore: NewFileStore(f),
|
||||||
|
}
|
||||||
|
err := s.Erase(validServerAddress)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(f.AuthConfigs) != 0 {
|
||||||
|
t.Fatalf("expected 0 auth configs, got %d", len(f.AuthConfigs))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNativeStoreEraseInvalidAddress(t *testing.T) {
|
||||||
|
f := newConfigFile(map[string]types.AuthConfig{
|
||||||
|
validServerAddress: {
|
||||||
|
Email: "foo@example.com",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
f.CredentialsStore = "mock"
|
||||||
|
|
||||||
|
s := &nativeStore{
|
||||||
|
programFunc: mockCommandFn,
|
||||||
|
fileStore: NewFileStore(f),
|
||||||
|
}
|
||||||
|
err := s.Erase(invalidServerAddress)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error, got nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(err.Error(), "program failed") {
|
||||||
|
t.Fatalf("expected `program failed`, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/cliconfig"
|
cliconfig "github.com/docker/docker/cli/config"
|
||||||
"github.com/docker/docker/opts"
|
"github.com/docker/docker/opts"
|
||||||
"github.com/docker/go-connections/tlsconfig"
|
"github.com/docker/go-connections/tlsconfig"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
|
@ -49,7 +49,7 @@ func NewCommonOptions() *CommonOptions {
|
||||||
// InstallFlags adds flags for the common options on the FlagSet
|
// InstallFlags adds flags for the common options on the FlagSet
|
||||||
func (commonOpts *CommonOptions) InstallFlags(flags *pflag.FlagSet) {
|
func (commonOpts *CommonOptions) InstallFlags(flags *pflag.FlagSet) {
|
||||||
if dockerCertPath == "" {
|
if dockerCertPath == "" {
|
||||||
dockerCertPath = cliconfig.ConfigDir()
|
dockerCertPath = cliconfig.Dir()
|
||||||
}
|
}
|
||||||
|
|
||||||
flags.BoolVarP(&commonOpts.Debug, "debug", "D", false, "Enable debug mode")
|
flags.BoolVarP(&commonOpts.Debug, "debug", "D", false, "Enable debug mode")
|
||||||
|
|
|
@ -18,7 +18,7 @@ import (
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
registrytypes "github.com/docker/docker/api/types/registry"
|
registrytypes "github.com/docker/docker/api/types/registry"
|
||||||
"github.com/docker/docker/cli/command"
|
"github.com/docker/docker/cli/command"
|
||||||
"github.com/docker/docker/cliconfig"
|
cliconfig "github.com/docker/docker/cli/config"
|
||||||
"github.com/docker/docker/registry"
|
"github.com/docker/docker/registry"
|
||||||
"github.com/docker/go-connections/tlsconfig"
|
"github.com/docker/go-connections/tlsconfig"
|
||||||
"github.com/docker/notary"
|
"github.com/docker/notary"
|
||||||
|
@ -37,7 +37,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func trustDirectory() string {
|
func trustDirectory() string {
|
||||||
return filepath.Join(cliconfig.ConfigDir(), "trust")
|
return filepath.Join(cliconfig.Dir(), "trust")
|
||||||
}
|
}
|
||||||
|
|
||||||
// certificateDirectory returns the directory containing
|
// certificateDirectory returns the directory containing
|
||||||
|
@ -49,7 +49,7 @@ func certificateDirectory(server string) (string, error) {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return filepath.Join(cliconfig.ConfigDir(), "tls", u.Host), nil
|
return filepath.Join(cliconfig.Dir(), "tls", u.Host), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Server returns the base URL for the trust server.
|
// Server returns the base URL for the trust server.
|
||||||
|
|
Loading…
Reference in New Issue