From 33c9edaf6c5401fc1891713d1ad8d861e6cea51f Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Thu, 21 Apr 2016 17:51:28 -0400 Subject: [PATCH 1/3] Cleanup the structure of the cli package. Move all flags into cli/flags Move usage help into cli/usage.go Signed-off-by: Daniel Nephin --- api/client/cli.go | 4 ++-- cli/{ => flags}/client.go | 2 +- cli/flags/common.go | 21 +++++++++++++++++---- cli/{common.go => usage.go} | 19 ------------------- cmd/docker/client.go | 3 +-- cmd/dockerd/daemon.go | 5 ++--- cmd/dockerd/daemon_test.go | 20 ++++++++++---------- cmd/dockerd/daemon_unix_test.go | 12 ++++++------ 8 files changed, 39 insertions(+), 47 deletions(-) rename cli/{ => flags}/client.go (94%) rename cli/{common.go => usage.go} (86%) diff --git a/api/client/cli.go b/api/client/cli.go index 273174a250..ed0a75dc81 100644 --- a/api/client/cli.go +++ b/api/client/cli.go @@ -9,7 +9,7 @@ import ( "runtime" "github.com/docker/docker/api" - "github.com/docker/docker/cli" + cliflags "github.com/docker/docker/cli/flags" "github.com/docker/docker/cliconfig" "github.com/docker/docker/cliconfig/credentials" "github.com/docker/docker/dockerversion" @@ -112,7 +112,7 @@ func (cli *DockerCli) restoreTerminal(in io.Closer) error { // The key file, protocol (i.e. unix) and address are passed in as strings, along with the tls.Config. If the tls.Config // is set the client scheme will be set to https. // The client will be given a 32-second timeout (see https://github.com/docker/docker/pull/8035). -func NewDockerCli(in io.ReadCloser, out, err io.Writer, clientFlags *cli.ClientFlags) *DockerCli { +func NewDockerCli(in io.ReadCloser, out, err io.Writer, clientFlags *cliflags.ClientFlags) *DockerCli { cli := &DockerCli{ in: in, out: out, diff --git a/cli/client.go b/cli/flags/client.go similarity index 94% rename from cli/client.go rename to cli/flags/client.go index 6a82eb52a5..cc7309db4b 100644 --- a/cli/client.go +++ b/cli/flags/client.go @@ -1,4 +1,4 @@ -package cli +package flags import flag "github.com/docker/docker/pkg/mflag" diff --git a/cli/flags/common.go b/cli/flags/common.go index d23696979b..4726b04f2a 100644 --- a/cli/flags/common.go +++ b/cli/flags/common.go @@ -6,7 +6,6 @@ import ( "path/filepath" "github.com/Sirupsen/logrus" - "github.com/docker/docker/cli" "github.com/docker/docker/cliconfig" "github.com/docker/docker/opts" flag "github.com/docker/docker/pkg/mflag" @@ -31,9 +30,23 @@ var ( dockerTLSVerify = os.Getenv("DOCKER_TLS_VERIFY") != "" ) +// CommonFlags are flags common to both the client and the daemon. +type CommonFlags struct { + FlagSet *flag.FlagSet + PostParse func() + + Debug bool + Hosts []string + LogLevel string + TLS bool + TLSVerify bool + TLSOptions *tlsconfig.Options + TrustKey string +} + // InitCommonFlags initializes flags common to both client and daemon -func InitCommonFlags() *cli.CommonFlags { - var commonFlags = &cli.CommonFlags{FlagSet: new(flag.FlagSet)} +func InitCommonFlags() *CommonFlags { + var commonFlags = &CommonFlags{FlagSet: new(flag.FlagSet)} if dockerCertPath == "" { dockerCertPath = cliconfig.ConfigDir() @@ -60,7 +73,7 @@ func InitCommonFlags() *cli.CommonFlags { return commonFlags } -func postParseCommon(commonFlags *cli.CommonFlags) { +func postParseCommon(commonFlags *CommonFlags) { cmd := commonFlags.FlagSet SetDaemonLogLevel(commonFlags.LogLevel) diff --git a/cli/common.go b/cli/usage.go similarity index 86% rename from cli/common.go rename to cli/usage.go index 7f6a24ba1f..4b0eb0e0c3 100644 --- a/cli/common.go +++ b/cli/usage.go @@ -1,24 +1,5 @@ package cli -import ( - flag "github.com/docker/docker/pkg/mflag" - "github.com/docker/go-connections/tlsconfig" -) - -// CommonFlags represents flags that are common to both the client and the daemon. -type CommonFlags struct { - FlagSet *flag.FlagSet - PostParse func() - - Debug bool - Hosts []string - LogLevel string - TLS bool - TLSVerify bool - TLSOptions *tlsconfig.Options - TrustKey string -} - // Command is the struct containing the command name and description type Command struct { Name string diff --git a/cmd/docker/client.go b/cmd/docker/client.go index e8c7f889f8..4d98a33cc4 100644 --- a/cmd/docker/client.go +++ b/cmd/docker/client.go @@ -3,7 +3,6 @@ package main import ( "path/filepath" - "github.com/docker/docker/cli" cliflags "github.com/docker/docker/cli/flags" "github.com/docker/docker/cliconfig" flag "github.com/docker/docker/pkg/mflag" @@ -12,7 +11,7 @@ import ( var ( commonFlags = cliflags.InitCommonFlags() - clientFlags = &cli.ClientFlags{FlagSet: new(flag.FlagSet), Common: commonFlags} + clientFlags = &cliflags.ClientFlags{FlagSet: new(flag.FlagSet), Common: commonFlags} ) func init() { diff --git a/cmd/dockerd/daemon.go b/cmd/dockerd/daemon.go index e6e5ccbd70..a020d9b844 100644 --- a/cmd/dockerd/daemon.go +++ b/cmd/dockerd/daemon.go @@ -23,7 +23,6 @@ import ( systemrouter "github.com/docker/docker/api/server/router/system" "github.com/docker/docker/api/server/router/volume" "github.com/docker/docker/builder/dockerfile" - "github.com/docker/docker/cli" cliflags "github.com/docker/docker/cli/flags" "github.com/docker/docker/cliconfig" "github.com/docker/docker/daemon" @@ -51,7 +50,7 @@ const ( // DaemonCli represents the daemon CLI. type DaemonCli struct { *daemon.Config - commonFlags *cli.CommonFlags + commonFlags *cliflags.CommonFlags configFile *string } @@ -345,7 +344,7 @@ func shutdownDaemon(d *daemon.Daemon, timeout time.Duration) { } } -func loadDaemonCliConfig(config *daemon.Config, flags *flag.FlagSet, commonConfig *cli.CommonFlags, configFile string) (*daemon.Config, error) { +func loadDaemonCliConfig(config *daemon.Config, flags *flag.FlagSet, commonConfig *cliflags.CommonFlags, configFile string) (*daemon.Config, error) { config.Debug = commonConfig.Debug config.Hosts = commonConfig.Hosts config.LogLevel = commonConfig.LogLevel diff --git a/cmd/dockerd/daemon_test.go b/cmd/dockerd/daemon_test.go index feaa9aae26..c16e11aec9 100644 --- a/cmd/dockerd/daemon_test.go +++ b/cmd/dockerd/daemon_test.go @@ -7,7 +7,7 @@ import ( "testing" "github.com/Sirupsen/logrus" - "github.com/docker/docker/cli" + cliflags "github.com/docker/docker/cli/flags" "github.com/docker/docker/daemon" "github.com/docker/docker/opts" "github.com/docker/docker/pkg/mflag" @@ -16,7 +16,7 @@ import ( func TestLoadDaemonCliConfigWithoutOverriding(t *testing.T) { c := &daemon.Config{} - common := &cli.CommonFlags{ + common := &cliflags.CommonFlags{ Debug: true, } @@ -35,7 +35,7 @@ func TestLoadDaemonCliConfigWithoutOverriding(t *testing.T) { func TestLoadDaemonCliConfigWithTLS(t *testing.T) { c := &daemon.Config{} - common := &cli.CommonFlags{ + common := &cliflags.CommonFlags{ TLS: true, TLSOptions: &tlsconfig.Options{ CAFile: "/tmp/ca.pem", @@ -57,7 +57,7 @@ func TestLoadDaemonCliConfigWithTLS(t *testing.T) { func TestLoadDaemonCliConfigWithConflicts(t *testing.T) { c := &daemon.Config{} - common := &cli.CommonFlags{} + common := &cliflags.CommonFlags{} f, err := ioutil.TempFile("", "docker-config-") if err != nil { t.Fatal(err) @@ -93,7 +93,7 @@ func TestLoadDaemonCliConfigWithConflicts(t *testing.T) { func TestLoadDaemonCliConfigWithTLSVerify(t *testing.T) { c := &daemon.Config{} - common := &cli.CommonFlags{ + common := &cliflags.CommonFlags{ TLSOptions: &tlsconfig.Options{ CAFile: "/tmp/ca.pem", }, @@ -126,7 +126,7 @@ func TestLoadDaemonCliConfigWithTLSVerify(t *testing.T) { func TestLoadDaemonCliConfigWithExplicitTLSVerifyFalse(t *testing.T) { c := &daemon.Config{} - common := &cli.CommonFlags{ + common := &cliflags.CommonFlags{ TLSOptions: &tlsconfig.Options{ CAFile: "/tmp/ca.pem", }, @@ -159,7 +159,7 @@ func TestLoadDaemonCliConfigWithExplicitTLSVerifyFalse(t *testing.T) { func TestLoadDaemonCliConfigWithoutTLSVerify(t *testing.T) { c := &daemon.Config{} - common := &cli.CommonFlags{ + common := &cliflags.CommonFlags{ TLSOptions: &tlsconfig.Options{ CAFile: "/tmp/ca.pem", }, @@ -191,7 +191,7 @@ func TestLoadDaemonCliConfigWithoutTLSVerify(t *testing.T) { func TestLoadDaemonCliConfigWithLogLevel(t *testing.T) { c := &daemon.Config{} - common := &cli.CommonFlags{} + common := &cliflags.CommonFlags{} f, err := ioutil.TempFile("", "docker-config-") if err != nil { @@ -223,7 +223,7 @@ func TestLoadDaemonCliConfigWithLogLevel(t *testing.T) { func TestLoadDaemonConfigWithEmbeddedOptions(t *testing.T) { c := &daemon.Config{} - common := &cli.CommonFlags{} + common := &cliflags.CommonFlags{} flags := mflag.NewFlagSet("test", mflag.ContinueOnError) flags.String([]string{"-tlscacert"}, "", "") @@ -256,7 +256,7 @@ func TestLoadDaemonConfigWithEmbeddedOptions(t *testing.T) { func TestLoadDaemonConfigWithRegistryOptions(t *testing.T) { c := &daemon.Config{} - common := &cli.CommonFlags{} + common := &cliflags.CommonFlags{} flags := mflag.NewFlagSet("test", mflag.ContinueOnError) c.ServiceOptions.InstallCliFlags(flags, absentFromHelp) diff --git a/cmd/dockerd/daemon_unix_test.go b/cmd/dockerd/daemon_unix_test.go index f10f6a9227..a72468eddb 100644 --- a/cmd/dockerd/daemon_unix_test.go +++ b/cmd/dockerd/daemon_unix_test.go @@ -6,7 +6,7 @@ import ( "io/ioutil" "testing" - "github.com/docker/docker/cli" + cliflags "github.com/docker/docker/cli/flags" "github.com/docker/docker/daemon" "github.com/docker/docker/opts" "github.com/docker/docker/pkg/mflag" @@ -14,7 +14,7 @@ import ( func TestLoadDaemonCliConfigWithDaemonFlags(t *testing.T) { c := &daemon.Config{} - common := &cli.CommonFlags{ + common := &cliflags.CommonFlags{ Debug: true, LogLevel: "info", } @@ -61,7 +61,7 @@ func TestLoadDaemonCliConfigWithDaemonFlags(t *testing.T) { func TestLoadDaemonConfigWithNetwork(t *testing.T) { c := &daemon.Config{} - common := &cli.CommonFlags{} + common := &cliflags.CommonFlags{} flags := mflag.NewFlagSet("test", mflag.ContinueOnError) flags.String([]string{"-bip"}, "", "") flags.String([]string{"-ip"}, "", "") @@ -92,7 +92,7 @@ func TestLoadDaemonConfigWithNetwork(t *testing.T) { func TestLoadDaemonConfigWithMapOptions(t *testing.T) { c := &daemon.Config{} - common := &cli.CommonFlags{} + common := &cliflags.CommonFlags{} flags := mflag.NewFlagSet("test", mflag.ContinueOnError) flags.Var(opts.NewNamedMapOpts("cluster-store-opts", c.ClusterOpts, nil), []string{"-cluster-store-opt"}, "") @@ -136,7 +136,7 @@ func TestLoadDaemonConfigWithMapOptions(t *testing.T) { func TestLoadDaemonConfigWithTrueDefaultValues(t *testing.T) { c := &daemon.Config{} - common := &cli.CommonFlags{} + common := &cliflags.CommonFlags{} flags := mflag.NewFlagSet("test", mflag.ContinueOnError) flags.BoolVar(&c.EnableUserlandProxy, []string{"-userland-proxy"}, true, "") @@ -181,7 +181,7 @@ func TestLoadDaemonConfigWithTrueDefaultValues(t *testing.T) { func TestLoadDaemonConfigWithTrueDefaultValuesLeaveDefaults(t *testing.T) { c := &daemon.Config{} - common := &cli.CommonFlags{} + common := &cliflags.CommonFlags{} flags := mflag.NewFlagSet("test", mflag.ContinueOnError) flags.BoolVar(&c.EnableUserlandProxy, []string{"-userland-proxy"}, true, "") From 30e3620eaea88cf33fd7deb8c88359469c27b3f7 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Thu, 21 Apr 2016 18:37:08 -0400 Subject: [PATCH 2/3] Refactor cliconfig so that the default constructor can exist in the package. Signed-off-by: Daniel Nephin --- api/client/cli.go | 3 +- api/client/login.go | 12 +- cliconfig/config.go | 196 ++--------------------- cliconfig/config_test.go | 32 +--- cliconfig/configfile/file.go | 177 ++++++++++++++++++++ cliconfig/configfile/file_test.go | 27 ++++ cliconfig/credentials/default_store.go | 4 +- cliconfig/credentials/file_store.go | 6 +- cliconfig/credentials/file_store_test.go | 3 +- cliconfig/credentials/native_store.go | 4 +- 10 files changed, 243 insertions(+), 221 deletions(-) create mode 100644 cliconfig/configfile/file.go create mode 100644 cliconfig/configfile/file_test.go diff --git a/api/client/cli.go b/api/client/cli.go index ed0a75dc81..653d6b43ce 100644 --- a/api/client/cli.go +++ b/api/client/cli.go @@ -11,6 +11,7 @@ import ( "github.com/docker/docker/api" 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/dockerversion" "github.com/docker/docker/opts" @@ -27,7 +28,7 @@ type DockerCli struct { init func() error // configFile has the client configuration file - configFile *cliconfig.ConfigFile + configFile *configfile.ConfigFile // in holds the input stream and closer (io.ReadCloser) for the client. in io.ReadCloser // out holds the output stream (io.Writer) for the client. diff --git a/api/client/login.go b/api/client/login.go index a772348c8d..0bba8d2909 100644 --- a/api/client/login.go +++ b/api/client/login.go @@ -11,7 +11,7 @@ import ( "golang.org/x/net/context" Cli "github.com/docker/docker/cli" - "github.com/docker/docker/cliconfig" + "github.com/docker/docker/cliconfig/configfile" "github.com/docker/docker/cliconfig/credentials" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/term" @@ -143,33 +143,33 @@ func readInput(in io.Reader, out io.Writer) string { // getCredentials loads the user credentials from a credentials store. // The store is determined by the config file settings. -func getCredentials(c *cliconfig.ConfigFile, serverAddress string) (types.AuthConfig, error) { +func getCredentials(c *configfile.ConfigFile, serverAddress string) (types.AuthConfig, error) { s := loadCredentialsStore(c) return s.Get(serverAddress) } -func getAllCredentials(c *cliconfig.ConfigFile) (map[string]types.AuthConfig, error) { +func getAllCredentials(c *configfile.ConfigFile) (map[string]types.AuthConfig, error) { s := loadCredentialsStore(c) return s.GetAll() } // storeCredentials saves the user credentials in a credentials store. // The store is determined by the config file settings. -func storeCredentials(c *cliconfig.ConfigFile, auth types.AuthConfig) error { +func storeCredentials(c *configfile.ConfigFile, auth types.AuthConfig) error { s := loadCredentialsStore(c) return s.Store(auth) } // eraseCredentials removes the user credentials from a credentials store. // The store is determined by the config file settings. -func eraseCredentials(c *cliconfig.ConfigFile, serverAddress string) error { +func eraseCredentials(c *configfile.ConfigFile, serverAddress string) error { s := loadCredentialsStore(c) return s.Erase(serverAddress) } // loadCredentialsStore initializes a new credentials store based // in the settings provided in the configuration file. -func loadCredentialsStore(c *cliconfig.ConfigFile) credentials.Store { +func loadCredentialsStore(c *configfile.ConfigFile) credentials.Store { if c.CredentialsStore != "" { return credentials.NewNativeStore(c) } diff --git a/cliconfig/config.go b/cliconfig/config.go index d42366c23f..a5a1303943 100644 --- a/cliconfig/config.go +++ b/cliconfig/config.go @@ -1,15 +1,12 @@ package cliconfig import ( - "encoding/base64" - "encoding/json" "fmt" "io" - "io/ioutil" "os" "path/filepath" - "strings" + "github.com/docker/docker/cliconfig/configfile" "github.com/docker/docker/pkg/homedir" "github.com/docker/engine-api/types" ) @@ -46,94 +43,19 @@ func SetConfigDir(dir string) { configDir = dir } -// 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"` - DetachKeys string `json:"detachKeys,omitempty"` - CredentialsStore string `json:"credsStore,omitempty"` - filename string // Note: not serialized - for internal use only -} - // NewConfigFile initializes an empty configuration file for the given filename 'fn' -func NewConfigFile(fn string) *ConfigFile { - return &ConfigFile{ +func NewConfigFile(fn string) *configfile.ConfigFile { + return &configfile.ConfigFile{ AuthConfigs: make(map[string]types.AuthConfig), HTTPHeaders: make(map[string]string), - filename: fn, + Filename: fn, } } -// 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 != "" || - (configFile.AuthConfigs != nil && len(configFile.AuthConfigs) > 0) -} - // LegacyLoadFromReader is a convenience function that creates a ConfigFile object from // a non-nested reader -func LegacyLoadFromReader(configData io.Reader) (*ConfigFile, error) { - configFile := ConfigFile{ +func LegacyLoadFromReader(configData io.Reader) (*configfile.ConfigFile, error) { + configFile := configfile.ConfigFile{ AuthConfigs: make(map[string]types.AuthConfig), } err := configFile.LegacyLoadFromReader(configData) @@ -142,8 +64,8 @@ func LegacyLoadFromReader(configData io.Reader) (*ConfigFile, error) { // LoadFromReader is a convenience function that creates a ConfigFile object from // a reader -func LoadFromReader(configData io.Reader) (*ConfigFile, error) { - configFile := ConfigFile{ +func LoadFromReader(configData io.Reader) (*configfile.ConfigFile, error) { + configFile := configfile.ConfigFile{ AuthConfigs: make(map[string]types.AuthConfig), } err := configFile.LoadFromReader(configData) @@ -153,32 +75,32 @@ func LoadFromReader(configData io.Reader) (*ConfigFile, error) { // 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, error) { +func Load(configDir string) (*configfile.ConfigFile, error) { if configDir == "" { configDir = ConfigDir() } - configFile := ConfigFile{ + configFile := configfile.ConfigFile{ AuthConfigs: make(map[string]types.AuthConfig), - filename: filepath.Join(configDir, ConfigFileName), + 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 := os.Stat(configFile.Filename); err == nil { + file, err := os.Open(configFile.Filename) if err != nil { - return &configFile, fmt.Errorf("%s - %v", configFile.filename, err) + 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) + 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) + return &configFile, fmt.Errorf("%s - %v", configFile.Filename, err) } // Can't find latest config file so check for the old one @@ -201,89 +123,3 @@ func Load(configDir string) (*ConfigFile, error) { } return &configFile, nil } - -// 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) -} - -// Filename returns the name of the configuration file -func (configFile *ConfigFile) Filename() string { - return configFile.filename -} - -// 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 -} diff --git a/cliconfig/config_test.go b/cliconfig/config_test.go index 30c1777007..78b360cc2c 100644 --- a/cliconfig/config_test.go +++ b/cliconfig/config_test.go @@ -7,8 +7,8 @@ import ( "strings" "testing" + "github.com/docker/docker/cliconfig/configfile" "github.com/docker/docker/pkg/homedir" - "github.com/docker/engine-api/types" ) func TestEmptyConfigDir(t *testing.T) { @@ -26,8 +26,8 @@ func TestEmptyConfigDir(t *testing.T) { } expectedConfigFilename := filepath.Join(tmpHome, ConfigFileName) - if config.Filename() != expectedConfigFilename { - t.Fatalf("Expected config filename %s, got %s", expectedConfigFilename, config.Filename()) + 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 @@ -377,7 +377,7 @@ func TestJsonWithPsFormat(t *testing.T) { } // Save it and make sure it shows up in new form -func saveConfigAndValidateNewFormat(t *testing.T, config *ConfigFile, homeFolder string) string { +func saveConfigAndValidateNewFormat(t *testing.T, config *configfile.ConfigFile, homeFolder string) string { if err := config.Save(); err != nil { t.Fatalf("Failed to save: %q", err) } @@ -415,8 +415,8 @@ func TestConfigFile(t *testing.T) { configFilename := "configFilename" configFile := NewConfigFile(configFilename) - if configFile.Filename() != configFilename { - t.Fatalf("Expected %s, got %s", configFilename, configFile.Filename()) + if configFile.Filename != configFilename { + t.Fatalf("Expected %s, got %s", configFilename, configFile.Filename) } } @@ -543,23 +543,3 @@ func TestLegacyJsonSaveWithNoFile(t *testing.T) { t.Fatalf("Should have save in new form: \n%s\n not \n%s", string(buf), expConfStr) } } - -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.") - } -} diff --git a/cliconfig/configfile/file.go b/cliconfig/configfile/file.go new file mode 100644 index 0000000000..7c94e27dce --- /dev/null +++ b/cliconfig/configfile/file.go @@ -0,0 +1,177 @@ +package configfile + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + + "github.com/docker/engine-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"` + DetachKeys string `json:"detachKeys,omitempty"` + CredentialsStore string `json:"credsStore,omitempty"` + Filename string `json:"-"` // Note: for internal use only +} + +// 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 != "" || + (configFile.AuthConfigs != nil && 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 +} diff --git a/cliconfig/configfile/file_test.go b/cliconfig/configfile/file_test.go new file mode 100644 index 0000000000..15eecb73e2 --- /dev/null +++ b/cliconfig/configfile/file_test.go @@ -0,0 +1,27 @@ +package configfile + +import ( + "testing" + + "github.com/docker/engine-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.") + } +} diff --git a/cliconfig/credentials/default_store.go b/cliconfig/credentials/default_store.go index b5fc47ccb3..b4733709b1 100644 --- a/cliconfig/credentials/default_store.go +++ b/cliconfig/credentials/default_store.go @@ -3,12 +3,12 @@ package credentials import ( "os/exec" - "github.com/docker/docker/cliconfig" + "github.com/docker/docker/cliconfig/configfile" ) // DetectDefaultStore sets the default credentials store // if the host includes the default store helper program. -func DetectDefaultStore(c *cliconfig.ConfigFile) { +func DetectDefaultStore(c *configfile.ConfigFile) { if c.CredentialsStore != "" { // user defined return diff --git a/cliconfig/credentials/file_store.go b/cliconfig/credentials/file_store.go index 8e7edd624a..cf1c89fcc5 100644 --- a/cliconfig/credentials/file_store.go +++ b/cliconfig/credentials/file_store.go @@ -3,18 +3,18 @@ package credentials import ( "strings" - "github.com/docker/docker/cliconfig" + "github.com/docker/docker/cliconfig/configfile" "github.com/docker/engine-api/types" ) // fileStore implements a credentials store using // the docker configuration file to keep the credentials in plain text. type fileStore struct { - file *cliconfig.ConfigFile + file *configfile.ConfigFile } // NewFileStore creates a new file credentials store. -func NewFileStore(file *cliconfig.ConfigFile) Store { +func NewFileStore(file *configfile.ConfigFile) Store { return &fileStore{ file: file, } diff --git a/cliconfig/credentials/file_store_test.go b/cliconfig/credentials/file_store_test.go index 668b6f097d..f087f04e75 100644 --- a/cliconfig/credentials/file_store_test.go +++ b/cliconfig/credentials/file_store_test.go @@ -5,10 +5,11 @@ import ( "testing" "github.com/docker/docker/cliconfig" + "github.com/docker/docker/cliconfig/configfile" "github.com/docker/engine-api/types" ) -func newConfigFile(auths map[string]types.AuthConfig) *cliconfig.ConfigFile { +func newConfigFile(auths map[string]types.AuthConfig) *configfile.ConfigFile { tmp, _ := ioutil.TempFile("", "docker-test") name := tmp.Name() tmp.Close() diff --git a/cliconfig/credentials/native_store.go b/cliconfig/credentials/native_store.go index 9b8997dd64..e81de437e2 100644 --- a/cliconfig/credentials/native_store.go +++ b/cliconfig/credentials/native_store.go @@ -9,7 +9,7 @@ import ( "strings" "github.com/Sirupsen/logrus" - "github.com/docker/docker/cliconfig" + "github.com/docker/docker/cliconfig/configfile" "github.com/docker/engine-api/types" ) @@ -52,7 +52,7 @@ type nativeStore struct { // NewNativeStore creates a new native store that // uses a remote helper program to manage credentials. -func NewNativeStore(file *cliconfig.ConfigFile) Store { +func NewNativeStore(file *configfile.ConfigFile) Store { return &nativeStore{ commandFn: shellCommandFn(file.CredentialsStore), fileStore: NewFileStore(file), From 01a34e43b3f7a701ee1112b960740a5aaa6bdb93 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Mon, 25 Apr 2016 12:05:42 -0400 Subject: [PATCH 3/3] Consolidate the files in client/ Signed-off-by: Daniel Nephin --- cli/usage.go | 5 ++- cmd/docker/client.go | 37 ------------------- cmd/docker/docker.go | 34 +++++++++++++++++ cmd/docker/{client_test.go => docker_test.go} | 0 cmd/docker/{flags.go => usage.go} | 16 ++------ cmd/docker/{flags_test.go => usage_test.go} | 4 +- 6 files changed, 44 insertions(+), 52 deletions(-) delete mode 100644 cmd/docker/client.go rename cmd/docker/{client_test.go => docker_test.go} (100%) rename cmd/docker/{flags.go => usage.go} (51%) rename cmd/docker/{flags_test.go => usage_test.go} (70%) diff --git a/cli/usage.go b/cli/usage.go index 4b0eb0e0c3..1ef6a35dec 100644 --- a/cli/usage.go +++ b/cli/usage.go @@ -6,7 +6,8 @@ type Command struct { Description string } -var dockerCommands = []Command{ +// DockerCommandUsage lists the top level docker commands and their short usage +var DockerCommandUsage = []Command{ {"attach", "Attach to a running container"}, {"build", "Build an image from a Dockerfile"}, {"commit", "Create a new image from a container's changes"}, @@ -55,7 +56,7 @@ var dockerCommands = []Command{ var DockerCommands = make(map[string]Command) func init() { - for _, cmd := range dockerCommands { + for _, cmd := range DockerCommandUsage { DockerCommands[cmd.Name] = cmd } } diff --git a/cmd/docker/client.go b/cmd/docker/client.go deleted file mode 100644 index 4d98a33cc4..0000000000 --- a/cmd/docker/client.go +++ /dev/null @@ -1,37 +0,0 @@ -package main - -import ( - "path/filepath" - - cliflags "github.com/docker/docker/cli/flags" - "github.com/docker/docker/cliconfig" - flag "github.com/docker/docker/pkg/mflag" - "github.com/docker/docker/utils" -) - -var ( - commonFlags = cliflags.InitCommonFlags() - clientFlags = &cliflags.ClientFlags{FlagSet: new(flag.FlagSet), Common: commonFlags} -) - -func init() { - - client := clientFlags.FlagSet - client.StringVar(&clientFlags.ConfigDir, []string{"-config"}, cliconfig.ConfigDir(), "Location of client config files") - - clientFlags.PostParse = func() { - clientFlags.Common.PostParse() - - if clientFlags.ConfigDir != "" { - cliconfig.SetConfigDir(clientFlags.ConfigDir) - } - - if clientFlags.Common.TrustKey == "" { - clientFlags.Common.TrustKey = filepath.Join(cliconfig.ConfigDir(), cliflags.DefaultTrustKeyFile) - } - - if clientFlags.Common.Debug { - utils.EnableDebug() - } - } -} diff --git a/cmd/docker/docker.go b/cmd/docker/docker.go index 838602164d..8397124932 100644 --- a/cmd/docker/docker.go +++ b/cmd/docker/docker.go @@ -3,16 +3,26 @@ package main import ( "fmt" "os" + "path/filepath" "github.com/Sirupsen/logrus" "github.com/docker/docker/api/client" "github.com/docker/docker/cli" + cliflags "github.com/docker/docker/cli/flags" + "github.com/docker/docker/cliconfig" "github.com/docker/docker/dockerversion" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/term" "github.com/docker/docker/utils" ) +var ( + commonFlags = cliflags.InitCommonFlags() + clientFlags = initClientFlags(commonFlags) + flHelp = flag.Bool([]string{"h", "-help"}, false, "Print usage") + flVersion = flag.Bool([]string{"v", "-version"}, false, "Print version information and quit") +) + func main() { // Set terminal emulation based on platform as required. stdin, stdout, stderr := term.StdStreams() @@ -30,6 +40,7 @@ func main() { help := "\nCommands:\n" + dockerCommands := sortCommands(cli.DockerCommandUsage) for _, cmd := range dockerCommands { help += fmt.Sprintf(" %-10.10s%s\n", cmd.Name, cmd.Description) } @@ -75,3 +86,26 @@ func showVersion() { fmt.Printf("Docker version %s, build %s\n", dockerversion.Version, dockerversion.GitCommit) } } + +func initClientFlags(commonFlags *cliflags.CommonFlags) *cliflags.ClientFlags { + clientFlags := &cliflags.ClientFlags{FlagSet: new(flag.FlagSet), Common: commonFlags} + client := clientFlags.FlagSet + client.StringVar(&clientFlags.ConfigDir, []string{"-config"}, cliconfig.ConfigDir(), "Location of client config files") + + clientFlags.PostParse = func() { + clientFlags.Common.PostParse() + + if clientFlags.ConfigDir != "" { + cliconfig.SetConfigDir(clientFlags.ConfigDir) + } + + if clientFlags.Common.TrustKey == "" { + clientFlags.Common.TrustKey = filepath.Join(cliconfig.ConfigDir(), cliflags.DefaultTrustKeyFile) + } + + if clientFlags.Common.Debug { + utils.EnableDebug() + } + } + return clientFlags +} diff --git a/cmd/docker/client_test.go b/cmd/docker/docker_test.go similarity index 100% rename from cmd/docker/client_test.go rename to cmd/docker/docker_test.go diff --git a/cmd/docker/flags.go b/cmd/docker/usage.go similarity index 51% rename from cmd/docker/flags.go rename to cmd/docker/usage.go index 35a8108880..792d178073 100644 --- a/cmd/docker/flags.go +++ b/cmd/docker/usage.go @@ -4,12 +4,6 @@ import ( "sort" "github.com/docker/docker/cli" - flag "github.com/docker/docker/pkg/mflag" -) - -var ( - flHelp = flag.Bool([]string{"h", "-help"}, false, "Print usage") - flVersion = flag.Bool([]string{"v", "-version"}, false, "Print version information and quit") ) type byName []cli.Command @@ -18,13 +12,11 @@ func (a byName) Len() int { return len(a) } func (a byName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a byName) Less(i, j int) bool { return a[i].Name < a[j].Name } -var dockerCommands []cli.Command - // TODO(tiborvass): do not show 'daemon' on client-only binaries -func init() { - for _, cmd := range cli.DockerCommands { - dockerCommands = append(dockerCommands, cmd) - } +func sortCommands(commands []cli.Command) []cli.Command { + dockerCommands := make([]cli.Command, len(commands)) + copy(dockerCommands, commands) sort.Sort(byName(dockerCommands)) + return dockerCommands } diff --git a/cmd/docker/flags_test.go b/cmd/docker/usage_test.go similarity index 70% rename from cmd/docker/flags_test.go rename to cmd/docker/usage_test.go index 28021ba4c9..0453265db8 100644 --- a/cmd/docker/flags_test.go +++ b/cmd/docker/usage_test.go @@ -3,11 +3,13 @@ package main import ( "sort" "testing" + + "github.com/docker/docker/cli" ) // Tests if the subcommands of docker are sorted func TestDockerSubcommandsAreSorted(t *testing.T) { - if !sort.IsSorted(byName(dockerCommands)) { + if !sort.IsSorted(byName(cli.DockerCommandUsage)) { t.Fatal("Docker subcommands are not in sorted order") } }