Add .docker/config.json and support for HTTP Headers
This PR does the following: - migrated ~/.dockerfg to ~/.docker/config.json. The data is migrated but the old file remains in case its needed - moves the auth json in that fie into an "auth" property so we can add new top-level properties w/o messing with the auth stuff - adds support for an HttpHeaders property in ~/.docker/config.json which adds these http headers to all msgs from the cli In a follow-on PR I'll move the config file process out from under "registry" since it not specific to that any more. I didn't do it here because I wanted the diff to be smaller so people can make sure I didn't break/miss any auth code during my edits. Signed-off-by: Doug Davis <dug@us.ibm.com>
This commit is contained in:
parent
dacc0507f0
commit
18c9b6c645
19 changed files with 360 additions and 81 deletions
|
@ -286,10 +286,8 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
|||
|
||||
v.Set("dockerfile", *dockerfileName)
|
||||
|
||||
cli.LoadConfigFile()
|
||||
|
||||
headers := http.Header(make(map[string][]string))
|
||||
buf, err := json.Marshal(cli.configFile)
|
||||
buf, err := json.Marshal(cli.configFile.AuthConfigs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
@ -120,14 +121,6 @@ func (cli *DockerCli) Subcmd(name, signature, description string, exitOnError bo
|
|||
return flags
|
||||
}
|
||||
|
||||
func (cli *DockerCli) LoadConfigFile() (err error) {
|
||||
cli.configFile, err = registry.LoadConfig(homedir.Get())
|
||||
if err != nil {
|
||||
fmt.Fprintf(cli.err, "WARNING: %s\n", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CheckTtyInput(attachStdin, ttyMode bool) error {
|
||||
// In order to attach to a container tty, input stream for the client must
|
||||
// be a tty itself: redirecting or piping the client standard input is
|
||||
|
@ -184,9 +177,15 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, keyFile string, proto, a
|
|||
tr.Dial = (&net.Dialer{Timeout: timeout}).Dial
|
||||
}
|
||||
|
||||
configFile, e := registry.LoadConfig(filepath.Join(homedir.Get(), ".docker"))
|
||||
if e != nil {
|
||||
fmt.Fprintf(err, "WARNING: Error loading config file:%v\n", e)
|
||||
}
|
||||
|
||||
return &DockerCli{
|
||||
proto: proto,
|
||||
addr: addr,
|
||||
configFile: configFile,
|
||||
in: in,
|
||||
out: out,
|
||||
err: err,
|
||||
|
|
|
@ -37,9 +37,6 @@ func (cli *DockerCli) pullImageCustomOut(image string, out io.Writer) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// Load the auth config file, to be able to pull the image
|
||||
cli.LoadConfigFile()
|
||||
|
||||
// Resolve the Auth config relevant for this server
|
||||
authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index)
|
||||
buf, err := json.Marshal(authConfig)
|
||||
|
|
|
@ -142,6 +142,13 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add CLI Config's HTTP Headers BEFORE we set the Docker headers
|
||||
// then the user can't change OUR headers
|
||||
for k, v := range cli.configFile.HttpHeaders {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
|
||||
req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION)
|
||||
req.Header.Set("Content-Type", "text/plain")
|
||||
req.Header.Set("Connection", "Upgrade")
|
||||
|
|
|
@ -68,8 +68,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
|
|||
}
|
||||
|
||||
if info.IndexServerAddress != "" {
|
||||
cli.LoadConfigFile()
|
||||
u := cli.configFile.Configs[info.IndexServerAddress].Username
|
||||
u := cli.configFile.AuthConfigs[info.IndexServerAddress].Username
|
||||
if len(u) > 0 {
|
||||
fmt.Fprintf(cli.out, "Username: %v\n", u)
|
||||
fmt.Fprintf(cli.out, "Registry: %v\n", info.IndexServerAddress)
|
||||
|
|
|
@ -6,11 +6,9 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/pkg/homedir"
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
"github.com/docker/docker/pkg/term"
|
||||
"github.com/docker/docker/registry"
|
||||
|
@ -56,8 +54,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
|
|||
return string(line)
|
||||
}
|
||||
|
||||
cli.LoadConfigFile()
|
||||
authconfig, ok := cli.configFile.Configs[serverAddress]
|
||||
authconfig, ok := cli.configFile.AuthConfigs[serverAddress]
|
||||
if !ok {
|
||||
authconfig = registry.AuthConfig{}
|
||||
}
|
||||
|
@ -113,12 +110,14 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
|
|||
authconfig.Password = password
|
||||
authconfig.Email = email
|
||||
authconfig.ServerAddress = serverAddress
|
||||
cli.configFile.Configs[serverAddress] = authconfig
|
||||
cli.configFile.AuthConfigs[serverAddress] = authconfig
|
||||
|
||||
stream, statusCode, err := cli.call("POST", "/auth", cli.configFile.Configs[serverAddress], nil)
|
||||
stream, statusCode, err := cli.call("POST", "/auth", cli.configFile.AuthConfigs[serverAddress], nil)
|
||||
if statusCode == 401 {
|
||||
delete(cli.configFile.Configs, serverAddress)
|
||||
registry.SaveConfig(cli.configFile)
|
||||
delete(cli.configFile.AuthConfigs, serverAddress)
|
||||
if err2 := cli.configFile.Save(); err2 != nil {
|
||||
fmt.Fprintf(cli.out, "WARNING: could not save config file: %v\n", err2)
|
||||
}
|
||||
return err
|
||||
}
|
||||
if err != nil {
|
||||
|
@ -127,12 +126,15 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
|
|||
|
||||
var response types.AuthResponse
|
||||
if err := json.NewDecoder(stream).Decode(&response); err != nil {
|
||||
cli.configFile, _ = registry.LoadConfig(homedir.Get())
|
||||
// Upon error, remove entry
|
||||
delete(cli.configFile.AuthConfigs, serverAddress)
|
||||
return err
|
||||
}
|
||||
|
||||
registry.SaveConfig(cli.configFile)
|
||||
fmt.Fprintf(cli.out, "WARNING: login credentials saved in %s.\n", path.Join(homedir.Get(), registry.CONFIGFILE))
|
||||
if err := cli.configFile.Save(); err != nil {
|
||||
return fmt.Errorf("Error saving config file: %v", err)
|
||||
}
|
||||
fmt.Fprintf(cli.out, "WARNING: login credentials saved in %s\n", cli.configFile.Filename())
|
||||
|
||||
if response.Status != "" {
|
||||
fmt.Fprintf(cli.out, "%s\n", response.Status)
|
||||
|
|
|
@ -22,14 +22,13 @@ func (cli *DockerCli) CmdLogout(args ...string) error {
|
|||
serverAddress = cmd.Arg(0)
|
||||
}
|
||||
|
||||
cli.LoadConfigFile()
|
||||
if _, ok := cli.configFile.Configs[serverAddress]; !ok {
|
||||
if _, ok := cli.configFile.AuthConfigs[serverAddress]; !ok {
|
||||
fmt.Fprintf(cli.out, "Not logged in to %s\n", serverAddress)
|
||||
} else {
|
||||
fmt.Fprintf(cli.out, "Remove login credentials for %s\n", serverAddress)
|
||||
delete(cli.configFile.Configs, serverAddress)
|
||||
delete(cli.configFile.AuthConfigs, serverAddress)
|
||||
|
||||
if err := registry.SaveConfig(cli.configFile); err != nil {
|
||||
if err := cli.configFile.Save(); err != nil {
|
||||
return fmt.Errorf("Failed to save docker config: %v", err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,8 +42,6 @@ func (cli *DockerCli) CmdPull(args ...string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
cli.LoadConfigFile()
|
||||
|
||||
_, _, err = cli.clientRequestAttemptLogin("POST", "/images/create?"+v.Encode(), nil, cli.out, repoInfo.Index, "pull")
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -20,8 +20,6 @@ func (cli *DockerCli) CmdPush(args ...string) error {
|
|||
|
||||
name := cmd.Arg(0)
|
||||
|
||||
cli.LoadConfigFile()
|
||||
|
||||
remote, tag := parsers.ParseRepositoryTag(name)
|
||||
|
||||
// Resolve the Repository name from fqn to RepositoryInfo
|
||||
|
|
|
@ -44,8 +44,6 @@ func (cli *DockerCli) CmdSearch(args ...string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
cli.LoadConfigFile()
|
||||
|
||||
rdr, _, err := cli.clientRequestAttemptLogin("GET", "/images/search?"+v.Encode(), nil, nil, repoInfo.Index, "search")
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -65,6 +65,13 @@ func (cli *DockerCli) clientRequest(method, path string, in io.Reader, headers m
|
|||
if err != nil {
|
||||
return nil, "", -1, err
|
||||
}
|
||||
|
||||
// Add CLI Config's HTTP Headers BEFORE we set the Docker headers
|
||||
// then the user can't change OUR headers
|
||||
for k, v := range cli.configFile.HttpHeaders {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
|
||||
req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION)
|
||||
req.URL.Host = cli.addr
|
||||
req.URL.Scheme = cli.scheme
|
||||
|
@ -299,7 +306,7 @@ func (cli *DockerCli) monitorTtySize(id string, isExec bool) error {
|
|||
sigchan := make(chan os.Signal, 1)
|
||||
gosignal.Notify(sigchan, signal.SIGWINCH)
|
||||
go func() {
|
||||
for _ = range sigchan {
|
||||
for range sigchan {
|
||||
cli.resizeTty(id, isExec)
|
||||
}
|
||||
}()
|
||||
|
|
|
@ -101,8 +101,8 @@ type Builder struct {
|
|||
// the final configs of the Dockerfile but dont want the layers
|
||||
disableCommit bool
|
||||
|
||||
AuthConfig *registry.AuthConfig
|
||||
AuthConfigFile *registry.ConfigFile
|
||||
AuthConfig *registry.AuthConfig
|
||||
ConfigFile *registry.ConfigFile
|
||||
|
||||
// Deprecated, original writer used for ImagePull. To be removed.
|
||||
OutOld io.Writer
|
||||
|
|
|
@ -437,13 +437,13 @@ func (b *Builder) pullImage(name string) (*imagepkg.Image, error) {
|
|||
}
|
||||
|
||||
pullRegistryAuth := b.AuthConfig
|
||||
if len(b.AuthConfigFile.Configs) > 0 {
|
||||
if len(b.ConfigFile.AuthConfigs) > 0 {
|
||||
// The request came with a full auth config file, we prefer to use that
|
||||
repoInfo, err := b.Daemon.RegistryService.ResolveRepository(remote)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resolvedAuth := b.AuthConfigFile.ResolveAuthConfig(repoInfo.Index)
|
||||
resolvedAuth := b.ConfigFile.ResolveAuthConfig(repoInfo.Index)
|
||||
pullRegistryAuth = &resolvedAuth
|
||||
}
|
||||
|
||||
|
|
|
@ -150,7 +150,7 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) error {
|
|||
OutOld: job.Stdout,
|
||||
StreamFormatter: sf,
|
||||
AuthConfig: authConfig,
|
||||
AuthConfigFile: configFile,
|
||||
ConfigFile: configFile,
|
||||
dockerfileName: dockerfileName,
|
||||
cpuShares: cpuShares,
|
||||
cpuSetCpus: cpuSetCpus,
|
||||
|
|
|
@ -48,6 +48,35 @@ These Go environment variables are case-insensitive. See the
|
|||
[Go specification](http://golang.org/pkg/net/http/) for details on these
|
||||
variables.
|
||||
|
||||
## Configuration Files
|
||||
|
||||
The Docker command line stores its configuration files in a directory called
|
||||
`.docker` within your `HOME` directory. Docker manages most of the files in
|
||||
`.docker` and you should not modify them. However, you *can modify* the
|
||||
`.docker/config.json` file to control certain aspects of how the `docker`
|
||||
command behaves.
|
||||
|
||||
Currently, you can modify the `docker` command behavior using environment
|
||||
variables or command-line options. You can also use options within
|
||||
`config.json` to modify some of the same behavior. When using these
|
||||
mechanisms, you must keep in mind the order of precedence among them. Command
|
||||
line options override environment variables and environment variables override
|
||||
properties you specify in a `config.json` file.
|
||||
|
||||
The `config.json` file stores a JSON encoding of a single `HttpHeaders`
|
||||
property. The property specifies a set of headers to include in all
|
||||
messages sent from the Docker client to the daemon. Docker does not try to
|
||||
interpret or understand these header; it simply puts them into the messages.
|
||||
Docker does not allow these headers to change any headers it sets for itself.
|
||||
|
||||
Following is a sample `config.json` file:
|
||||
|
||||
{
|
||||
"HttpHeaders: {
|
||||
"MyHeader": "MyValue"
|
||||
}
|
||||
}
|
||||
|
||||
## Help
|
||||
To list the help on any command just execute the command, followed by the `--help` option.
|
||||
|
||||
|
|
58
integration-cli/docker_cli_config_test.go
Normal file
58
integration-cli/docker_cli_config_test.go
Normal file
|
@ -0,0 +1,58 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/pkg/homedir"
|
||||
)
|
||||
|
||||
func TestConfigHttpHeader(t *testing.T) {
|
||||
testRequires(t, UnixCli) // Can't set/unset HOME on windows right now
|
||||
// We either need a level of Go that supports Unsetenv (for cases
|
||||
// when HOME/USERPROFILE isn't set), or we need to be able to use
|
||||
// os/user but user.Current() only works if we aren't statically compiling
|
||||
|
||||
var headers map[string][]string
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
headers = r.Header
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
homeKey := homedir.Key()
|
||||
homeVal := homedir.Get()
|
||||
tmpDir, _ := ioutil.TempDir("", "fake-home")
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
dotDocker := filepath.Join(tmpDir, ".docker")
|
||||
os.Mkdir(dotDocker, 0600)
|
||||
tmpCfg := filepath.Join(dotDocker, "config.json")
|
||||
|
||||
defer func() { os.Setenv(homeKey, homeVal) }()
|
||||
os.Setenv(homeKey, tmpDir)
|
||||
|
||||
data := `{
|
||||
"HttpHeaders": { "MyHeader": "MyValue" }
|
||||
}`
|
||||
|
||||
err := ioutil.WriteFile(tmpCfg, []byte(data), 0600)
|
||||
if err != nil {
|
||||
t.Fatalf("Err creating file(%s): %v", tmpCfg, err)
|
||||
}
|
||||
|
||||
cmd := exec.Command(dockerBinary, "-H="+server.URL[7:], "ps")
|
||||
out, _, _ := runCommandWithOutput(cmd)
|
||||
|
||||
if headers["Myheader"] == nil || headers["Myheader"][0] != "MyValue" {
|
||||
t.Fatalf("Missing/bad header: %q\nout:%v", headers, out)
|
||||
}
|
||||
|
||||
logDone("config - add new http headers")
|
||||
}
|
105
registry/auth.go
105
registry/auth.go
|
@ -8,24 +8,27 @@ import (
|
|||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/pkg/homedir"
|
||||
"github.com/docker/docker/pkg/requestdecorator"
|
||||
)
|
||||
|
||||
const (
|
||||
// Where we store the config file
|
||||
CONFIGFILE = ".dockercfg"
|
||||
CONFIGFILE = "config.json"
|
||||
OLD_CONFIGFILE = ".dockercfg"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrConfigFileMissing = errors.New("The Auth config file is missing")
|
||||
)
|
||||
|
||||
// Registry Auth Info
|
||||
type AuthConfig struct {
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
|
@ -34,9 +37,11 @@ type AuthConfig struct {
|
|||
ServerAddress string `json:"serveraddress,omitempty"`
|
||||
}
|
||||
|
||||
// ~/.docker/config.json file info
|
||||
type ConfigFile struct {
|
||||
Configs map[string]AuthConfig `json:"configs,omitempty"`
|
||||
rootPath string
|
||||
AuthConfigs map[string]AuthConfig `json:"auths"`
|
||||
HttpHeaders map[string]string `json:"HttpHeaders,omitempty"`
|
||||
filename string // Note: not serialized - for internal use only
|
||||
}
|
||||
|
||||
type RequestAuthorization struct {
|
||||
|
@ -147,18 +152,58 @@ func decodeAuth(authStr string) (string, string, error) {
|
|||
|
||||
// load up the auth config information and return values
|
||||
// FIXME: use the internal golang config parser
|
||||
func LoadConfig(rootPath string) (*ConfigFile, error) {
|
||||
configFile := ConfigFile{Configs: make(map[string]AuthConfig), rootPath: rootPath}
|
||||
confFile := path.Join(rootPath, CONFIGFILE)
|
||||
func LoadConfig(configDir string) (*ConfigFile, error) {
|
||||
if configDir == "" {
|
||||
configDir = filepath.Join(homedir.Get(), ".docker")
|
||||
}
|
||||
|
||||
configFile := ConfigFile{
|
||||
AuthConfigs: make(map[string]AuthConfig),
|
||||
filename: filepath.Join(configDir, CONFIGFILE),
|
||||
}
|
||||
|
||||
// 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, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if err := json.NewDecoder(file).Decode(&configFile); err != nil {
|
||||
return &configFile, err
|
||||
}
|
||||
|
||||
for addr, ac := range configFile.AuthConfigs {
|
||||
ac.Username, ac.Password, err = decodeAuth(ac.Auth)
|
||||
if err != nil {
|
||||
return &configFile, err
|
||||
}
|
||||
ac.Auth = ""
|
||||
ac.ServerAddress = addr
|
||||
configFile.AuthConfigs[addr] = ac
|
||||
}
|
||||
|
||||
return &configFile, nil
|
||||
} 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, err
|
||||
}
|
||||
|
||||
// Can't find latest config file so check for the old one
|
||||
confFile := filepath.Join(homedir.Get(), OLD_CONFIGFILE)
|
||||
|
||||
if _, err := os.Stat(confFile); err != nil {
|
||||
return &configFile, nil //missing file is not an error
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadFile(confFile)
|
||||
if err != nil {
|
||||
return &configFile, err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(b, &configFile.Configs); err != nil {
|
||||
if err := json.Unmarshal(b, &configFile.AuthConfigs); err != nil {
|
||||
arr := strings.Split(string(b), "\n")
|
||||
if len(arr) < 2 {
|
||||
return &configFile, fmt.Errorf("The Auth config file is empty")
|
||||
|
@ -179,48 +224,52 @@ func LoadConfig(rootPath string) (*ConfigFile, error) {
|
|||
authConfig.Email = origEmail[1]
|
||||
authConfig.ServerAddress = IndexServerAddress()
|
||||
// *TODO: Switch to using IndexServerName() instead?
|
||||
configFile.Configs[IndexServerAddress()] = authConfig
|
||||
configFile.AuthConfigs[IndexServerAddress()] = authConfig
|
||||
} else {
|
||||
for k, authConfig := range configFile.Configs {
|
||||
for k, authConfig := range configFile.AuthConfigs {
|
||||
authConfig.Username, authConfig.Password, err = decodeAuth(authConfig.Auth)
|
||||
if err != nil {
|
||||
return &configFile, err
|
||||
}
|
||||
authConfig.Auth = ""
|
||||
authConfig.ServerAddress = k
|
||||
configFile.Configs[k] = authConfig
|
||||
configFile.AuthConfigs[k] = authConfig
|
||||
}
|
||||
}
|
||||
return &configFile, nil
|
||||
}
|
||||
|
||||
// save the auth config
|
||||
func SaveConfig(configFile *ConfigFile) error {
|
||||
confFile := path.Join(configFile.rootPath, CONFIGFILE)
|
||||
if len(configFile.Configs) == 0 {
|
||||
os.Remove(confFile)
|
||||
return nil
|
||||
}
|
||||
|
||||
configs := make(map[string]AuthConfig, len(configFile.Configs))
|
||||
for k, authConfig := range configFile.Configs {
|
||||
func (configFile *ConfigFile) Save() error {
|
||||
// Encode sensitive data into a new/temp struct
|
||||
tmpAuthConfigs := make(map[string]AuthConfig, len(configFile.AuthConfigs))
|
||||
for k, authConfig := range configFile.AuthConfigs {
|
||||
authCopy := authConfig
|
||||
|
||||
authCopy.Auth = encodeAuth(&authCopy)
|
||||
authCopy.Username = ""
|
||||
authCopy.Password = ""
|
||||
authCopy.ServerAddress = ""
|
||||
configs[k] = authCopy
|
||||
tmpAuthConfigs[k] = authCopy
|
||||
}
|
||||
|
||||
b, err := json.MarshalIndent(configs, "", "\t")
|
||||
saveAuthConfigs := configFile.AuthConfigs
|
||||
configFile.AuthConfigs = tmpAuthConfigs
|
||||
defer func() { configFile.AuthConfigs = saveAuthConfigs }()
|
||||
|
||||
data, err := json.MarshalIndent(configFile, "", "\t")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ioutil.WriteFile(confFile, b, 0600)
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(configFile.filename), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(configFile.filename, data, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -431,7 +480,7 @@ func tryV2TokenAuthLogin(authConfig *AuthConfig, params map[string]string, regis
|
|||
func (config *ConfigFile) ResolveAuthConfig(index *IndexInfo) AuthConfig {
|
||||
configKey := index.GetAuthConfigKey()
|
||||
// First try the happy case
|
||||
if c, found := config.Configs[configKey]; found || index.Official {
|
||||
if c, found := config.AuthConfigs[configKey]; found || index.Official {
|
||||
return c
|
||||
}
|
||||
|
||||
|
@ -450,7 +499,7 @@ func (config *ConfigFile) ResolveAuthConfig(index *IndexInfo) AuthConfig {
|
|||
|
||||
// Maybe they have a legacy config file, we will iterate the keys converting
|
||||
// them to the new format and testing
|
||||
for registry, config := range config.Configs {
|
||||
for registry, config := range config.AuthConfigs {
|
||||
if configKey == convertToHostname(registry) {
|
||||
return config
|
||||
}
|
||||
|
@ -459,3 +508,7 @@ func (config *ConfigFile) ResolveAuthConfig(index *IndexInfo) AuthConfig {
|
|||
// When all else fails, return an empty auth config
|
||||
return AuthConfig{}
|
||||
}
|
||||
|
||||
func (config *ConfigFile) Filename() string {
|
||||
return config.filename
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package registry
|
|||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
@ -31,13 +32,14 @@ func setupTempConfigFile() (*ConfigFile, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
root = filepath.Join(root, CONFIGFILE)
|
||||
configFile := &ConfigFile{
|
||||
rootPath: root,
|
||||
Configs: make(map[string]AuthConfig),
|
||||
AuthConfigs: make(map[string]AuthConfig),
|
||||
filename: root,
|
||||
}
|
||||
|
||||
for _, registry := range []string{"testIndex", IndexServerAddress()} {
|
||||
configFile.Configs[registry] = AuthConfig{
|
||||
configFile.AuthConfigs[registry] = AuthConfig{
|
||||
Username: "docker-user",
|
||||
Password: "docker-pass",
|
||||
Email: "docker@docker.io",
|
||||
|
@ -52,14 +54,14 @@ func TestSameAuthDataPostSave(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(configFile.rootPath)
|
||||
defer os.RemoveAll(configFile.filename)
|
||||
|
||||
err = SaveConfig(configFile)
|
||||
err = configFile.Save()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
authConfig := configFile.Configs["testIndex"]
|
||||
authConfig := configFile.AuthConfigs["testIndex"]
|
||||
if authConfig.Username != "docker-user" {
|
||||
t.Fail()
|
||||
}
|
||||
|
@ -79,9 +81,9 @@ func TestResolveAuthConfigIndexServer(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(configFile.rootPath)
|
||||
defer os.RemoveAll(configFile.filename)
|
||||
|
||||
indexConfig := configFile.Configs[IndexServerAddress()]
|
||||
indexConfig := configFile.AuthConfigs[IndexServerAddress()]
|
||||
|
||||
officialIndex := &IndexInfo{
|
||||
Official: true,
|
||||
|
@ -102,7 +104,7 @@ func TestResolveAuthConfigFullURL(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(configFile.rootPath)
|
||||
defer os.RemoveAll(configFile.filename)
|
||||
|
||||
registryAuth := AuthConfig{
|
||||
Username: "foo-user",
|
||||
|
@ -119,7 +121,7 @@ func TestResolveAuthConfigFullURL(t *testing.T) {
|
|||
Password: "baz-pass",
|
||||
Email: "baz@example.com",
|
||||
}
|
||||
configFile.Configs[IndexServerAddress()] = officialAuth
|
||||
configFile.AuthConfigs[IndexServerAddress()] = officialAuth
|
||||
|
||||
expectedAuths := map[string]AuthConfig{
|
||||
"registry.example.com": registryAuth,
|
||||
|
@ -157,12 +159,12 @@ func TestResolveAuthConfigFullURL(t *testing.T) {
|
|||
Name: configKey,
|
||||
}
|
||||
for _, registry := range registries {
|
||||
configFile.Configs[registry] = configured
|
||||
configFile.AuthConfigs[registry] = configured
|
||||
resolved := configFile.ResolveAuthConfig(index)
|
||||
if resolved.Email != configured.Email {
|
||||
t.Errorf("%s -> %q != %q\n", registry, resolved.Email, configured.Email)
|
||||
}
|
||||
delete(configFile.Configs, registry)
|
||||
delete(configFile.AuthConfigs, registry)
|
||||
resolved = configFile.ResolveAuthConfig(index)
|
||||
if resolved.Email == configured.Email {
|
||||
t.Errorf("%s -> %q == %q\n", registry, resolved.Email, configured.Email)
|
||||
|
|
135
registry/config_file_test.go
Normal file
135
registry/config_file_test.go
Normal file
|
@ -0,0 +1,135 @@
|
|||
package registry
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/pkg/homedir"
|
||||
)
|
||||
|
||||
func TestMissingFile(t *testing.T) {
|
||||
tmpHome, _ := ioutil.TempDir("", "config-test")
|
||||
|
||||
config, err := LoadConfig(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
|
||||
err = config.Save()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to save: %q", err)
|
||||
}
|
||||
|
||||
buf, err := ioutil.ReadFile(filepath.Join(tmpHome, CONFIGFILE))
|
||||
if !strings.Contains(string(buf), `"auths":`) {
|
||||
t.Fatalf("Should have save in new form: %s", string(buf))
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmptyFile(t *testing.T) {
|
||||
tmpHome, _ := ioutil.TempDir("", "config-test")
|
||||
fn := filepath.Join(tmpHome, CONFIGFILE)
|
||||
ioutil.WriteFile(fn, []byte(""), 0600)
|
||||
|
||||
_, err := LoadConfig(tmpHome)
|
||||
if err == nil {
|
||||
t.Fatalf("Was supposed to fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmptyJson(t *testing.T) {
|
||||
tmpHome, _ := ioutil.TempDir("", "config-test")
|
||||
fn := filepath.Join(tmpHome, CONFIGFILE)
|
||||
ioutil.WriteFile(fn, []byte("{}"), 0600)
|
||||
|
||||
config, err := LoadConfig(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
|
||||
err = config.Save()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to save: %q", err)
|
||||
}
|
||||
|
||||
buf, err := ioutil.ReadFile(filepath.Join(tmpHome, CONFIGFILE))
|
||||
if !strings.Contains(string(buf), `"auths":`) {
|
||||
t.Fatalf("Should have save in new form: %s", string(buf))
|
||||
}
|
||||
}
|
||||
|
||||
func TestOldJson(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
return
|
||||
}
|
||||
|
||||
tmpHome, _ := ioutil.TempDir("", "config-test")
|
||||
defer os.RemoveAll(tmpHome)
|
||||
|
||||
homeKey := homedir.Key()
|
||||
homeVal := homedir.Get()
|
||||
|
||||
defer func() { os.Setenv(homeKey, homeVal) }()
|
||||
os.Setenv(homeKey, tmpHome)
|
||||
|
||||
fn := filepath.Join(tmpHome, OLD_CONFIGFILE)
|
||||
js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}`
|
||||
ioutil.WriteFile(fn, []byte(js), 0600)
|
||||
|
||||
config, err := LoadConfig(tmpHome)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed loading on empty json file: %q", err)
|
||||
}
|
||||
|
||||
ac := config.AuthConfigs["https://index.docker.io/v1/"]
|
||||
if ac.Email != "user@example.com" || 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
|
||||
err = config.Save()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to save: %q", err)
|
||||
}
|
||||
|
||||
buf, err := ioutil.ReadFile(filepath.Join(tmpHome, CONFIGFILE))
|
||||
if !strings.Contains(string(buf), `"auths":`) ||
|
||||
!strings.Contains(string(buf), "user@example.com") {
|
||||
t.Fatalf("Should have save in new form: %s", string(buf))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewJson(t *testing.T) {
|
||||
tmpHome, _ := ioutil.TempDir("", "config-test")
|
||||
fn := filepath.Join(tmpHome, CONFIGFILE)
|
||||
js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } } }`
|
||||
ioutil.WriteFile(fn, []byte(js), 0600)
|
||||
|
||||
config, err := LoadConfig(tmpHome)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed loading on empty json file: %q", err)
|
||||
}
|
||||
|
||||
ac := config.AuthConfigs["https://index.docker.io/v1/"]
|
||||
if ac.Email != "user@example.com" || 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
|
||||
err = config.Save()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to save: %q", err)
|
||||
}
|
||||
|
||||
buf, err := ioutil.ReadFile(filepath.Join(tmpHome, CONFIGFILE))
|
||||
if !strings.Contains(string(buf), `"auths":`) ||
|
||||
!strings.Contains(string(buf), "user@example.com") {
|
||||
t.Fatalf("Should have save in new form: %s", string(buf))
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue