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/types"
|
||||
"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"
|
||||
"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/dockerversion"
|
||||
dopts "github.com/docker/docker/opts"
|
||||
|
@ -150,7 +150,7 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error {
|
|||
cli.defaultVersion = cli.client.ClientVersion()
|
||||
|
||||
if opts.Common.TrustKey == "" {
|
||||
cli.keyFile = filepath.Join(cliconfig.ConfigDir(), cliflags.DefaultTrustKeyFile)
|
||||
cli.keyFile = filepath.Join(cliconfig.Dir(), cliflags.DefaultTrustKeyFile)
|
||||
} else {
|
||||
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
|
||||
// an initialized ConfigFile struct if none is found.
|
||||
func LoadDefaultConfigFile(err io.Writer) *configfile.ConfigFile {
|
||||
configFile, e := cliconfig.Load(cliconfig.ConfigDir())
|
||||
configFile, e := cliconfig.Load(cliconfig.Dir())
|
||||
if e != nil {
|
||||
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"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/cliconfig"
|
||||
cliconfig "github.com/docker/docker/cli/config"
|
||||
"github.com/docker/docker/opts"
|
||||
"github.com/docker/go-connections/tlsconfig"
|
||||
"github.com/spf13/pflag"
|
||||
|
@ -49,7 +49,7 @@ func NewCommonOptions() *CommonOptions {
|
|||
// InstallFlags adds flags for the common options on the FlagSet
|
||||
func (commonOpts *CommonOptions) InstallFlags(flags *pflag.FlagSet) {
|
||||
if dockerCertPath == "" {
|
||||
dockerCertPath = cliconfig.ConfigDir()
|
||||
dockerCertPath = cliconfig.Dir()
|
||||
}
|
||||
|
||||
flags.BoolVarP(&commonOpts.Debug, "debug", "D", false, "Enable debug mode")
|
||||
|
|
|
@ -18,7 +18,7 @@ import (
|
|||
"github.com/docker/docker/api/types"
|
||||
registrytypes "github.com/docker/docker/api/types/registry"
|
||||
"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/go-connections/tlsconfig"
|
||||
"github.com/docker/notary"
|
||||
|
@ -37,7 +37,7 @@ var (
|
|||
)
|
||||
|
||||
func trustDirectory() string {
|
||||
return filepath.Join(cliconfig.ConfigDir(), "trust")
|
||||
return filepath.Join(cliconfig.Dir(), "trust")
|
||||
}
|
||||
|
||||
// certificateDirectory returns the directory containing
|
||||
|
@ -49,7 +49,7 @@ func certificateDirectory(server string) (string, error) {
|
|||
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.
|
||||
|
|
Loading…
Reference in New Issue