Merge pull request #12636 from duglin/MoveConfig
Move CLI config processing out from under registry dir
This commit is contained in:
commit
9ed5bfb083
|
@ -15,10 +15,10 @@ import (
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/docker/cliconfig"
|
||||||
"github.com/docker/docker/pkg/homedir"
|
"github.com/docker/docker/pkg/homedir"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/term"
|
"github.com/docker/docker/pkg/term"
|
||||||
"github.com/docker/docker/registry"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// DockerCli represents the docker command line client.
|
// DockerCli represents the docker command line client.
|
||||||
|
@ -28,8 +28,9 @@ type DockerCli struct {
|
||||||
proto string
|
proto string
|
||||||
// addr holds the client address.
|
// addr holds the client address.
|
||||||
addr string
|
addr string
|
||||||
// configFile holds the configuration file (instance of registry.ConfigFile).
|
|
||||||
configFile *registry.ConfigFile
|
// configFile has the client configuration file
|
||||||
|
configFile *cliconfig.ConfigFile
|
||||||
// in holds the input stream and closer (io.ReadCloser) for the client.
|
// in holds the input stream and closer (io.ReadCloser) for the client.
|
||||||
in io.ReadCloser
|
in io.ReadCloser
|
||||||
// out holds the output stream (io.Writer) for the client.
|
// out holds the output stream (io.Writer) for the client.
|
||||||
|
@ -184,7 +185,7 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, keyFile string, proto, a
|
||||||
tr.Dial = (&net.Dialer{Timeout: timeout}).Dial
|
tr.Dial = (&net.Dialer{Timeout: timeout}).Dial
|
||||||
}
|
}
|
||||||
|
|
||||||
configFile, e := registry.LoadConfig(filepath.Join(homedir.Get(), ".docker"))
|
configFile, e := cliconfig.Load(filepath.Join(homedir.Get(), ".docker"))
|
||||||
if e != nil {
|
if e != nil {
|
||||||
fmt.Fprintf(err, "WARNING: Error loading config file:%v\n", e)
|
fmt.Fprintf(err, "WARNING: Error loading config file:%v\n", e)
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ func (cli *DockerCli) pullImageCustomOut(image string, out io.Writer) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve the Auth config relevant for this server
|
// Resolve the Auth config relevant for this server
|
||||||
authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index)
|
authConfig := registry.ResolveAuthConfig(cli.configFile, repoInfo.Index)
|
||||||
buf, err := json.Marshal(authConfig)
|
buf, err := json.Marshal(authConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/cliconfig"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/term"
|
"github.com/docker/docker/pkg/term"
|
||||||
"github.com/docker/docker/registry"
|
"github.com/docker/docker/registry"
|
||||||
|
@ -56,7 +57,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
|
||||||
|
|
||||||
authconfig, ok := cli.configFile.AuthConfigs[serverAddress]
|
authconfig, ok := cli.configFile.AuthConfigs[serverAddress]
|
||||||
if !ok {
|
if !ok {
|
||||||
authconfig = registry.AuthConfig{}
|
authconfig = cliconfig.AuthConfig{}
|
||||||
}
|
}
|
||||||
|
|
||||||
if username == "" {
|
if username == "" {
|
||||||
|
|
|
@ -28,7 +28,7 @@ func (cli *DockerCli) CmdPush(args ...string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Resolve the Auth config relevant for this server
|
// Resolve the Auth config relevant for this server
|
||||||
authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index)
|
authConfig := registry.ResolveAuthConfig(cli.configFile, repoInfo.Index)
|
||||||
// If we're not using a custom registry, we know the restrictions
|
// If we're not using a custom registry, we know the restrictions
|
||||||
// applied to repository names and can warn the user in advance.
|
// applied to repository names and can warn the user in advance.
|
||||||
// Custom repositories can have different rules, and we must also
|
// Custom repositories can have different rules, and we must also
|
||||||
|
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"github.com/docker/docker/api"
|
"github.com/docker/docker/api"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/autogen/dockerversion"
|
"github.com/docker/docker/autogen/dockerversion"
|
||||||
|
"github.com/docker/docker/cliconfig"
|
||||||
"github.com/docker/docker/engine"
|
"github.com/docker/docker/engine"
|
||||||
"github.com/docker/docker/pkg/jsonmessage"
|
"github.com/docker/docker/pkg/jsonmessage"
|
||||||
"github.com/docker/docker/pkg/signal"
|
"github.com/docker/docker/pkg/signal"
|
||||||
|
@ -119,7 +120,7 @@ func (cli *DockerCli) clientRequest(method, path string, in io.Reader, headers m
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *DockerCli) clientRequestAttemptLogin(method, path string, in io.Reader, out io.Writer, index *registry.IndexInfo, cmdName string) (io.ReadCloser, int, error) {
|
func (cli *DockerCli) clientRequestAttemptLogin(method, path string, in io.Reader, out io.Writer, index *registry.IndexInfo, cmdName string) (io.ReadCloser, int, error) {
|
||||||
cmdAttempt := func(authConfig registry.AuthConfig) (io.ReadCloser, int, error) {
|
cmdAttempt := func(authConfig cliconfig.AuthConfig) (io.ReadCloser, int, error) {
|
||||||
buf, err := json.Marshal(authConfig)
|
buf, err := json.Marshal(authConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, -1, err
|
return nil, -1, err
|
||||||
|
@ -150,14 +151,14 @@ func (cli *DockerCli) clientRequestAttemptLogin(method, path string, in io.Reade
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve the Auth config relevant for this server
|
// Resolve the Auth config relevant for this server
|
||||||
authConfig := cli.configFile.ResolveAuthConfig(index)
|
authConfig := registry.ResolveAuthConfig(cli.configFile, index)
|
||||||
body, statusCode, err := cmdAttempt(authConfig)
|
body, statusCode, err := cmdAttempt(authConfig)
|
||||||
if statusCode == http.StatusUnauthorized {
|
if statusCode == http.StatusUnauthorized {
|
||||||
fmt.Fprintf(cli.out, "\nPlease login prior to %s:\n", cmdName)
|
fmt.Fprintf(cli.out, "\nPlease login prior to %s:\n", cmdName)
|
||||||
if err = cli.CmdLogin(index.GetAuthConfigKey()); err != nil {
|
if err = cli.CmdLogin(index.GetAuthConfigKey()); err != nil {
|
||||||
return nil, -1, err
|
return nil, -1, err
|
||||||
}
|
}
|
||||||
authConfig = cli.configFile.ResolveAuthConfig(index)
|
authConfig = registry.ResolveAuthConfig(cli.configFile, index)
|
||||||
return cmdAttempt(authConfig)
|
return cmdAttempt(authConfig)
|
||||||
}
|
}
|
||||||
return body, statusCode, err
|
return body, statusCode, err
|
||||||
|
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/autogen/dockerversion"
|
"github.com/docker/docker/autogen/dockerversion"
|
||||||
"github.com/docker/docker/builder"
|
"github.com/docker/docker/builder"
|
||||||
|
"github.com/docker/docker/cliconfig"
|
||||||
"github.com/docker/docker/daemon"
|
"github.com/docker/docker/daemon"
|
||||||
"github.com/docker/docker/daemon/networkdriver/bridge"
|
"github.com/docker/docker/daemon/networkdriver/bridge"
|
||||||
"github.com/docker/docker/engine"
|
"github.com/docker/docker/engine"
|
||||||
|
@ -34,7 +35,6 @@ import (
|
||||||
"github.com/docker/docker/pkg/stdcopy"
|
"github.com/docker/docker/pkg/stdcopy"
|
||||||
"github.com/docker/docker/pkg/streamformatter"
|
"github.com/docker/docker/pkg/streamformatter"
|
||||||
"github.com/docker/docker/pkg/version"
|
"github.com/docker/docker/pkg/version"
|
||||||
"github.com/docker/docker/registry"
|
|
||||||
"github.com/docker/docker/runconfig"
|
"github.com/docker/docker/runconfig"
|
||||||
"github.com/docker/docker/utils"
|
"github.com/docker/docker/utils"
|
||||||
)
|
)
|
||||||
|
@ -240,7 +240,7 @@ func streamJSON(out *engine.Output, w http.ResponseWriter, flush bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) postAuth(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
func (s *Server) postAuth(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
var config *registry.AuthConfig
|
var config *cliconfig.AuthConfig
|
||||||
err := json.NewDecoder(r.Body).Decode(&config)
|
err := json.NewDecoder(r.Body).Decode(&config)
|
||||||
r.Body.Close()
|
r.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -729,13 +729,13 @@ func (s *Server) postImagesCreate(eng *engine.Engine, version version.Version, w
|
||||||
tag = r.Form.Get("tag")
|
tag = r.Form.Get("tag")
|
||||||
)
|
)
|
||||||
authEncoded := r.Header.Get("X-Registry-Auth")
|
authEncoded := r.Header.Get("X-Registry-Auth")
|
||||||
authConfig := ®istry.AuthConfig{}
|
authConfig := &cliconfig.AuthConfig{}
|
||||||
if authEncoded != "" {
|
if authEncoded != "" {
|
||||||
authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
|
authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
|
||||||
if err := json.NewDecoder(authJson).Decode(authConfig); err != nil {
|
if err := json.NewDecoder(authJson).Decode(authConfig); err != nil {
|
||||||
// for a pull it is not an error if no auth was given
|
// for a pull it is not an error if no auth was given
|
||||||
// to increase compatibility with the existing api it is defaulting to be empty
|
// to increase compatibility with the existing api it is defaulting to be empty
|
||||||
authConfig = ®istry.AuthConfig{}
|
authConfig = &cliconfig.AuthConfig{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -803,7 +803,7 @@ func (s *Server) getImagesSearch(eng *engine.Engine, version version.Version, w
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
config *registry.AuthConfig
|
config *cliconfig.AuthConfig
|
||||||
authEncoded = r.Header.Get("X-Registry-Auth")
|
authEncoded = r.Header.Get("X-Registry-Auth")
|
||||||
headers = map[string][]string{}
|
headers = map[string][]string{}
|
||||||
)
|
)
|
||||||
|
@ -813,7 +813,7 @@ func (s *Server) getImagesSearch(eng *engine.Engine, version version.Version, w
|
||||||
if err := json.NewDecoder(authJson).Decode(&config); err != nil {
|
if err := json.NewDecoder(authJson).Decode(&config); err != nil {
|
||||||
// for a search it is not an error if no auth was given
|
// for a search it is not an error if no auth was given
|
||||||
// to increase compatibility with the existing api it is defaulting to be empty
|
// to increase compatibility with the existing api it is defaulting to be empty
|
||||||
config = ®istry.AuthConfig{}
|
config = &cliconfig.AuthConfig{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for k, v := range r.Header {
|
for k, v := range r.Header {
|
||||||
|
@ -842,7 +842,7 @@ func (s *Server) postImagesPush(eng *engine.Engine, version version.Version, w h
|
||||||
if err := parseForm(r); err != nil {
|
if err := parseForm(r); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
authConfig := ®istry.AuthConfig{}
|
authConfig := &cliconfig.AuthConfig{}
|
||||||
|
|
||||||
authEncoded := r.Header.Get("X-Registry-Auth")
|
authEncoded := r.Header.Get("X-Registry-Auth")
|
||||||
if authEncoded != "" {
|
if authEncoded != "" {
|
||||||
|
@ -850,7 +850,7 @@ func (s *Server) postImagesPush(eng *engine.Engine, version version.Version, w h
|
||||||
authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
|
authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
|
||||||
if err := json.NewDecoder(authJson).Decode(authConfig); err != nil {
|
if err := json.NewDecoder(authJson).Decode(authConfig); err != nil {
|
||||||
// to increase compatibility to existing api it is defaulting to be empty
|
// to increase compatibility to existing api it is defaulting to be empty
|
||||||
authConfig = ®istry.AuthConfig{}
|
authConfig = &cliconfig.AuthConfig{}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// the old format is supported for compatibility if there was no authConfig header
|
// the old format is supported for compatibility if there was no authConfig header
|
||||||
|
@ -1279,9 +1279,9 @@ func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.R
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
authEncoded = r.Header.Get("X-Registry-Auth")
|
authEncoded = r.Header.Get("X-Registry-Auth")
|
||||||
authConfig = ®istry.AuthConfig{}
|
authConfig = &cliconfig.AuthConfig{}
|
||||||
configFileEncoded = r.Header.Get("X-Registry-Config")
|
configFileEncoded = r.Header.Get("X-Registry-Config")
|
||||||
configFile = ®istry.ConfigFile{}
|
configFile = &cliconfig.ConfigFile{}
|
||||||
buildConfig = builder.NewBuildConfig()
|
buildConfig = builder.NewBuildConfig()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1294,7 +1294,7 @@ func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.R
|
||||||
if err := json.NewDecoder(authJson).Decode(authConfig); err != nil {
|
if err := json.NewDecoder(authJson).Decode(authConfig); err != nil {
|
||||||
// for a pull it is not an error if no auth was given
|
// for a pull it is not an error if no auth was given
|
||||||
// to increase compatibility with the existing api it is defaulting to be empty
|
// to increase compatibility with the existing api it is defaulting to be empty
|
||||||
authConfig = ®istry.AuthConfig{}
|
authConfig = &cliconfig.AuthConfig{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1303,7 +1303,7 @@ func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.R
|
||||||
if err := json.NewDecoder(configFileJson).Decode(configFile); err != nil {
|
if err := json.NewDecoder(configFileJson).Decode(configFile); err != nil {
|
||||||
// for a pull it is not an error if no auth was given
|
// for a pull it is not an error if no auth was given
|
||||||
// to increase compatibility with the existing api it is defaulting to be empty
|
// to increase compatibility with the existing api it is defaulting to be empty
|
||||||
configFile = ®istry.ConfigFile{}
|
configFile = &cliconfig.ConfigFile{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,13 +30,13 @@ import (
|
||||||
"github.com/docker/docker/api"
|
"github.com/docker/docker/api"
|
||||||
"github.com/docker/docker/builder/command"
|
"github.com/docker/docker/builder/command"
|
||||||
"github.com/docker/docker/builder/parser"
|
"github.com/docker/docker/builder/parser"
|
||||||
|
"github.com/docker/docker/cliconfig"
|
||||||
"github.com/docker/docker/daemon"
|
"github.com/docker/docker/daemon"
|
||||||
"github.com/docker/docker/pkg/fileutils"
|
"github.com/docker/docker/pkg/fileutils"
|
||||||
"github.com/docker/docker/pkg/streamformatter"
|
"github.com/docker/docker/pkg/streamformatter"
|
||||||
"github.com/docker/docker/pkg/stringid"
|
"github.com/docker/docker/pkg/stringid"
|
||||||
"github.com/docker/docker/pkg/symlink"
|
"github.com/docker/docker/pkg/symlink"
|
||||||
"github.com/docker/docker/pkg/tarsum"
|
"github.com/docker/docker/pkg/tarsum"
|
||||||
"github.com/docker/docker/registry"
|
|
||||||
"github.com/docker/docker/runconfig"
|
"github.com/docker/docker/runconfig"
|
||||||
"github.com/docker/docker/utils"
|
"github.com/docker/docker/utils"
|
||||||
)
|
)
|
||||||
|
@ -99,8 +99,8 @@ type Builder struct {
|
||||||
// the final configs of the Dockerfile but dont want the layers
|
// the final configs of the Dockerfile but dont want the layers
|
||||||
disableCommit bool
|
disableCommit bool
|
||||||
|
|
||||||
AuthConfig *registry.AuthConfig
|
AuthConfig *cliconfig.AuthConfig
|
||||||
ConfigFile *registry.ConfigFile
|
ConfigFile *cliconfig.ConfigFile
|
||||||
|
|
||||||
// Deprecated, original writer used for ImagePull. To be removed.
|
// Deprecated, original writer used for ImagePull. To be removed.
|
||||||
OutOld io.Writer
|
OutOld io.Writer
|
||||||
|
|
|
@ -36,6 +36,7 @@ import (
|
||||||
"github.com/docker/docker/pkg/system"
|
"github.com/docker/docker/pkg/system"
|
||||||
"github.com/docker/docker/pkg/tarsum"
|
"github.com/docker/docker/pkg/tarsum"
|
||||||
"github.com/docker/docker/pkg/urlutil"
|
"github.com/docker/docker/pkg/urlutil"
|
||||||
|
"github.com/docker/docker/registry"
|
||||||
"github.com/docker/docker/runconfig"
|
"github.com/docker/docker/runconfig"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -443,7 +444,7 @@ func (b *Builder) pullImage(name string) (*imagepkg.Image, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
resolvedAuth := b.ConfigFile.ResolveAuthConfig(repoInfo.Index)
|
resolvedAuth := registry.ResolveAuthConfig(b.ConfigFile, repoInfo.Index)
|
||||||
pullRegistryAuth = &resolvedAuth
|
pullRegistryAuth = &resolvedAuth
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
|
|
||||||
"github.com/docker/docker/api"
|
"github.com/docker/docker/api"
|
||||||
"github.com/docker/docker/builder/parser"
|
"github.com/docker/docker/builder/parser"
|
||||||
|
"github.com/docker/docker/cliconfig"
|
||||||
"github.com/docker/docker/daemon"
|
"github.com/docker/docker/daemon"
|
||||||
"github.com/docker/docker/graph"
|
"github.com/docker/docker/graph"
|
||||||
"github.com/docker/docker/pkg/archive"
|
"github.com/docker/docker/pkg/archive"
|
||||||
|
@ -50,8 +51,8 @@ type Config struct {
|
||||||
CpuShares int64
|
CpuShares int64
|
||||||
CpuSetCpus string
|
CpuSetCpus string
|
||||||
CpuSetMems string
|
CpuSetMems string
|
||||||
AuthConfig *registry.AuthConfig
|
AuthConfig *cliconfig.AuthConfig
|
||||||
ConfigFile *registry.ConfigFile
|
ConfigFile *cliconfig.ConfigFile
|
||||||
|
|
||||||
Stdout io.Writer
|
Stdout io.Writer
|
||||||
Context io.ReadCloser
|
Context io.ReadCloser
|
||||||
|
@ -76,8 +77,8 @@ func (b *Config) WaitCancelled() <-chan struct{} {
|
||||||
|
|
||||||
func NewBuildConfig() *Config {
|
func NewBuildConfig() *Config {
|
||||||
return &Config{
|
return &Config{
|
||||||
AuthConfig: ®istry.AuthConfig{},
|
AuthConfig: &cliconfig.AuthConfig{},
|
||||||
ConfigFile: ®istry.ConfigFile{},
|
ConfigFile: &cliconfig.ConfigFile{},
|
||||||
cancelled: make(chan struct{}),
|
cancelled: make(chan struct{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,208 @@
|
||||||
|
package cliconfig
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/docker/pkg/homedir"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Where we store the config file
|
||||||
|
CONFIGFILE = "config.json"
|
||||||
|
OLD_CONFIGFILE = ".dockercfg"
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
DEFAULT_INDEXSERVER = "https://index.docker.io/v1/"
|
||||||
|
)
|
||||||
|
|
||||||
|
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"`
|
||||||
|
Auth string `json:"auth"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
ServerAddress string `json:"serveraddress,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ~/.docker/config.json file info
|
||||||
|
type ConfigFile struct {
|
||||||
|
AuthConfigs map[string]AuthConfig `json:"auths"`
|
||||||
|
HttpHeaders map[string]string `json:"HttpHeaders,omitempty"`
|
||||||
|
filename string // Note: not serialized - for internal use only
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConfigFile(fn string) *ConfigFile {
|
||||||
|
return &ConfigFile{
|
||||||
|
AuthConfigs: make(map[string]AuthConfig),
|
||||||
|
HttpHeaders: make(map[string]string),
|
||||||
|
filename: fn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// load up the auth config information and return values
|
||||||
|
// FIXME: use the internal golang config parser
|
||||||
|
func Load(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.AuthConfigs); err != nil {
|
||||||
|
arr := strings.Split(string(b), "\n")
|
||||||
|
if len(arr) < 2 {
|
||||||
|
return &configFile, fmt.Errorf("The Auth config file is empty")
|
||||||
|
}
|
||||||
|
authConfig := AuthConfig{}
|
||||||
|
origAuth := strings.Split(arr[0], " = ")
|
||||||
|
if len(origAuth) != 2 {
|
||||||
|
return &configFile, fmt.Errorf("Invalid Auth config file")
|
||||||
|
}
|
||||||
|
authConfig.Username, authConfig.Password, err = DecodeAuth(origAuth[1])
|
||||||
|
if err != nil {
|
||||||
|
return &configFile, err
|
||||||
|
}
|
||||||
|
origEmail := strings.Split(arr[1], " = ")
|
||||||
|
if len(origEmail) != 2 {
|
||||||
|
return &configFile, fmt.Errorf("Invalid Auth config file")
|
||||||
|
}
|
||||||
|
authConfig.Email = origEmail[1]
|
||||||
|
authConfig.ServerAddress = DEFAULT_INDEXSERVER
|
||||||
|
configFile.AuthConfigs[DEFAULT_INDEXSERVER] = authConfig
|
||||||
|
} else {
|
||||||
|
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.AuthConfigs[k] = authConfig
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &configFile, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = ""
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll(filepath.Dir(configFile.filename), 0700); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ioutil.WriteFile(configFile.filename, data, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (config *ConfigFile) Filename() string {
|
||||||
|
return config.filename
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a base64 encoded auth string to store in config
|
||||||
|
func EncodeAuth(authConfig *AuthConfig) string {
|
||||||
|
authStr := authConfig.Username + ":" + authConfig.Password
|
||||||
|
msg := []byte(authStr)
|
||||||
|
encoded := make([]byte, base64.StdEncoding.EncodedLen(len(msg)))
|
||||||
|
base64.StdEncoding.Encode(encoded, msg)
|
||||||
|
return string(encoded)
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode the auth string
|
||||||
|
func DecodeAuth(authStr string) (string, string, error) {
|
||||||
|
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
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package registry
|
package cliconfig
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -14,7 +14,7 @@ import (
|
||||||
func TestMissingFile(t *testing.T) {
|
func TestMissingFile(t *testing.T) {
|
||||||
tmpHome, _ := ioutil.TempDir("", "config-test")
|
tmpHome, _ := ioutil.TempDir("", "config-test")
|
||||||
|
|
||||||
config, err := LoadConfig(tmpHome)
|
config, err := Load(tmpHome)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed loading on missing file: %q", err)
|
t.Fatalf("Failed loading on missing file: %q", err)
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@ func TestSaveFileToDirs(t *testing.T) {
|
||||||
|
|
||||||
tmpHome += "/.docker"
|
tmpHome += "/.docker"
|
||||||
|
|
||||||
config, err := LoadConfig(tmpHome)
|
config, err := Load(tmpHome)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed loading on missing file: %q", err)
|
t.Fatalf("Failed loading on missing file: %q", err)
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,7 @@ func TestEmptyFile(t *testing.T) {
|
||||||
fn := filepath.Join(tmpHome, CONFIGFILE)
|
fn := filepath.Join(tmpHome, CONFIGFILE)
|
||||||
ioutil.WriteFile(fn, []byte(""), 0600)
|
ioutil.WriteFile(fn, []byte(""), 0600)
|
||||||
|
|
||||||
_, err := LoadConfig(tmpHome)
|
_, err := Load(tmpHome)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("Was supposed to fail")
|
t.Fatalf("Was supposed to fail")
|
||||||
}
|
}
|
||||||
|
@ -69,7 +69,7 @@ func TestEmptyJson(t *testing.T) {
|
||||||
fn := filepath.Join(tmpHome, CONFIGFILE)
|
fn := filepath.Join(tmpHome, CONFIGFILE)
|
||||||
ioutil.WriteFile(fn, []byte("{}"), 0600)
|
ioutil.WriteFile(fn, []byte("{}"), 0600)
|
||||||
|
|
||||||
config, err := LoadConfig(tmpHome)
|
config, err := Load(tmpHome)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed loading on empty json file: %q", err)
|
t.Fatalf("Failed loading on empty json file: %q", err)
|
||||||
}
|
}
|
||||||
|
@ -104,7 +104,7 @@ func TestOldJson(t *testing.T) {
|
||||||
js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}`
|
js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}`
|
||||||
ioutil.WriteFile(fn, []byte(js), 0600)
|
ioutil.WriteFile(fn, []byte(js), 0600)
|
||||||
|
|
||||||
config, err := LoadConfig(tmpHome)
|
config, err := Load(tmpHome)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed loading on empty json file: %q", err)
|
t.Fatalf("Failed loading on empty json file: %q", err)
|
||||||
}
|
}
|
||||||
|
@ -133,7 +133,7 @@ func TestNewJson(t *testing.T) {
|
||||||
js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } } }`
|
js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } } }`
|
||||||
ioutil.WriteFile(fn, []byte(js), 0600)
|
ioutil.WriteFile(fn, []byte(js), 0600)
|
||||||
|
|
||||||
config, err := LoadConfig(tmpHome)
|
config, err := Load(tmpHome)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed loading on empty json file: %q", err)
|
t.Fatalf("Failed loading on empty json file: %q", err)
|
||||||
}
|
}
|
|
@ -12,6 +12,7 @@ import (
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/distribution/digest"
|
"github.com/docker/distribution/digest"
|
||||||
|
"github.com/docker/docker/cliconfig"
|
||||||
"github.com/docker/docker/image"
|
"github.com/docker/docker/image"
|
||||||
"github.com/docker/docker/pkg/progressreader"
|
"github.com/docker/docker/pkg/progressreader"
|
||||||
"github.com/docker/docker/pkg/streamformatter"
|
"github.com/docker/docker/pkg/streamformatter"
|
||||||
|
@ -23,7 +24,7 @@ import (
|
||||||
type ImagePullConfig struct {
|
type ImagePullConfig struct {
|
||||||
Parallel bool
|
Parallel bool
|
||||||
MetaHeaders map[string][]string
|
MetaHeaders map[string][]string
|
||||||
AuthConfig *registry.AuthConfig
|
AuthConfig *cliconfig.AuthConfig
|
||||||
Json bool
|
Json bool
|
||||||
OutStream io.Writer
|
OutStream io.Writer
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/distribution/digest"
|
"github.com/docker/distribution/digest"
|
||||||
|
"github.com/docker/docker/cliconfig"
|
||||||
"github.com/docker/docker/image"
|
"github.com/docker/docker/image"
|
||||||
"github.com/docker/docker/pkg/progressreader"
|
"github.com/docker/docker/pkg/progressreader"
|
||||||
"github.com/docker/docker/pkg/streamformatter"
|
"github.com/docker/docker/pkg/streamformatter"
|
||||||
|
@ -26,7 +27,7 @@ var ErrV2RegistryUnavailable = errors.New("error v2 registry unavailable")
|
||||||
|
|
||||||
type ImagePushConfig struct {
|
type ImagePushConfig struct {
|
||||||
MetaHeaders map[string][]string
|
MetaHeaders map[string][]string
|
||||||
AuthConfig *registry.AuthConfig
|
AuthConfig *cliconfig.AuthConfig
|
||||||
Tag string
|
Tag string
|
||||||
Json bool
|
Json bool
|
||||||
OutStream io.Writer
|
OutStream io.Writer
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
apiserver "github.com/docker/docker/api/server"
|
apiserver "github.com/docker/docker/api/server"
|
||||||
|
"github.com/docker/docker/cliconfig"
|
||||||
"github.com/docker/docker/daemon"
|
"github.com/docker/docker/daemon"
|
||||||
"github.com/docker/docker/daemon/execdriver"
|
"github.com/docker/docker/daemon/execdriver"
|
||||||
"github.com/docker/docker/engine"
|
"github.com/docker/docker/engine"
|
||||||
|
@ -28,7 +29,6 @@ import (
|
||||||
"github.com/docker/docker/pkg/ioutils"
|
"github.com/docker/docker/pkg/ioutils"
|
||||||
"github.com/docker/docker/pkg/reexec"
|
"github.com/docker/docker/pkg/reexec"
|
||||||
"github.com/docker/docker/pkg/stringid"
|
"github.com/docker/docker/pkg/stringid"
|
||||||
"github.com/docker/docker/registry"
|
|
||||||
"github.com/docker/docker/runconfig"
|
"github.com/docker/docker/runconfig"
|
||||||
"github.com/docker/docker/utils"
|
"github.com/docker/docker/utils"
|
||||||
)
|
)
|
||||||
|
@ -135,7 +135,7 @@ func setupBaseImage() {
|
||||||
imagePullConfig := &graph.ImagePullConfig{
|
imagePullConfig := &graph.ImagePullConfig{
|
||||||
Parallel: true,
|
Parallel: true,
|
||||||
OutStream: ioutils.NopWriteCloser(os.Stdout),
|
OutStream: ioutils.NopWriteCloser(os.Stdout),
|
||||||
AuthConfig: ®istry.AuthConfig{},
|
AuthConfig: &cliconfig.AuthConfig{},
|
||||||
}
|
}
|
||||||
d := getDaemon(eng)
|
d := getDaemon(eng)
|
||||||
if err := d.Repositories().Pull(unitTestImageName, "", imagePullConfig); err != nil {
|
if err := d.Repositories().Pull(unitTestImageName, "", imagePullConfig); err != nil {
|
||||||
|
|
210
registry/auth.go
210
registry/auth.go
|
@ -1,51 +1,21 @@
|
||||||
package registry
|
package registry
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/pkg/homedir"
|
"github.com/docker/docker/cliconfig"
|
||||||
"github.com/docker/docker/pkg/requestdecorator"
|
"github.com/docker/docker/pkg/requestdecorator"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
// Where we store the config file
|
|
||||||
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"`
|
|
||||||
Auth string `json:"auth"`
|
|
||||||
Email string `json:"email"`
|
|
||||||
ServerAddress string `json:"serveraddress,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ~/.docker/config.json file info
|
|
||||||
type ConfigFile struct {
|
|
||||||
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 {
|
type RequestAuthorization struct {
|
||||||
authConfig *AuthConfig
|
authConfig *cliconfig.AuthConfig
|
||||||
registryEndpoint *Endpoint
|
registryEndpoint *Endpoint
|
||||||
resource string
|
resource string
|
||||||
scope string
|
scope string
|
||||||
|
@ -56,7 +26,7 @@ type RequestAuthorization struct {
|
||||||
tokenExpiration time.Time
|
tokenExpiration time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRequestAuthorization(authConfig *AuthConfig, registryEndpoint *Endpoint, resource, scope string, actions []string) *RequestAuthorization {
|
func NewRequestAuthorization(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint, resource, scope string, actions []string) *RequestAuthorization {
|
||||||
return &RequestAuthorization{
|
return &RequestAuthorization{
|
||||||
authConfig: authConfig,
|
authConfig: authConfig,
|
||||||
registryEndpoint: registryEndpoint,
|
registryEndpoint: registryEndpoint,
|
||||||
|
@ -121,160 +91,8 @@ func (auth *RequestAuthorization) Authorize(req *http.Request) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// create a base64 encoded auth string to store in config
|
|
||||||
func encodeAuth(authConfig *AuthConfig) string {
|
|
||||||
authStr := authConfig.Username + ":" + authConfig.Password
|
|
||||||
msg := []byte(authStr)
|
|
||||||
encoded := make([]byte, base64.StdEncoding.EncodedLen(len(msg)))
|
|
||||||
base64.StdEncoding.Encode(encoded, msg)
|
|
||||||
return string(encoded)
|
|
||||||
}
|
|
||||||
|
|
||||||
// decode the auth string
|
|
||||||
func decodeAuth(authStr string) (string, string, error) {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// load up the auth config information and return values
|
|
||||||
// FIXME: use the internal golang config parser
|
|
||||||
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.AuthConfigs); err != nil {
|
|
||||||
arr := strings.Split(string(b), "\n")
|
|
||||||
if len(arr) < 2 {
|
|
||||||
return &configFile, fmt.Errorf("The Auth config file is empty")
|
|
||||||
}
|
|
||||||
authConfig := AuthConfig{}
|
|
||||||
origAuth := strings.Split(arr[0], " = ")
|
|
||||||
if len(origAuth) != 2 {
|
|
||||||
return &configFile, fmt.Errorf("Invalid Auth config file")
|
|
||||||
}
|
|
||||||
authConfig.Username, authConfig.Password, err = decodeAuth(origAuth[1])
|
|
||||||
if err != nil {
|
|
||||||
return &configFile, err
|
|
||||||
}
|
|
||||||
origEmail := strings.Split(arr[1], " = ")
|
|
||||||
if len(origEmail) != 2 {
|
|
||||||
return &configFile, fmt.Errorf("Invalid Auth config file")
|
|
||||||
}
|
|
||||||
authConfig.Email = origEmail[1]
|
|
||||||
authConfig.ServerAddress = IndexServerAddress()
|
|
||||||
// *TODO: Switch to using IndexServerName() instead?
|
|
||||||
configFile.AuthConfigs[IndexServerAddress()] = authConfig
|
|
||||||
} else {
|
|
||||||
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.AuthConfigs[k] = authConfig
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &configFile, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = ""
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.MkdirAll(filepath.Dir(configFile.filename), 0700); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = ioutil.WriteFile(configFile.filename, data, 0600)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Login tries to register/login to the registry server.
|
// Login tries to register/login to the registry server.
|
||||||
func Login(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *requestdecorator.RequestFactory) (string, error) {
|
func Login(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint, factory *requestdecorator.RequestFactory) (string, error) {
|
||||||
// Separates the v2 registry login logic from the v1 logic.
|
// Separates the v2 registry login logic from the v1 logic.
|
||||||
if registryEndpoint.Version == APIVersion2 {
|
if registryEndpoint.Version == APIVersion2 {
|
||||||
return loginV2(authConfig, registryEndpoint, factory)
|
return loginV2(authConfig, registryEndpoint, factory)
|
||||||
|
@ -283,7 +101,7 @@ func Login(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *requestd
|
||||||
}
|
}
|
||||||
|
|
||||||
// loginV1 tries to register/login to the v1 registry server.
|
// loginV1 tries to register/login to the v1 registry server.
|
||||||
func loginV1(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *requestdecorator.RequestFactory) (string, error) {
|
func loginV1(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint, factory *requestdecorator.RequestFactory) (string, error) {
|
||||||
var (
|
var (
|
||||||
status string
|
status string
|
||||||
reqBody []byte
|
reqBody []byte
|
||||||
|
@ -396,7 +214,7 @@ func loginV1(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *reques
|
||||||
// now, users should create their account through other means like directly from a web page
|
// now, users should create their account through other means like directly from a web page
|
||||||
// served by the v2 registry service provider. Whether this will be supported in the future
|
// served by the v2 registry service provider. Whether this will be supported in the future
|
||||||
// is to be determined.
|
// is to be determined.
|
||||||
func loginV2(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *requestdecorator.RequestFactory) (string, error) {
|
func loginV2(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint, factory *requestdecorator.RequestFactory) (string, error) {
|
||||||
logrus.Debugf("attempting v2 login to registry endpoint %s", registryEndpoint)
|
logrus.Debugf("attempting v2 login to registry endpoint %s", registryEndpoint)
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
|
@ -429,7 +247,7 @@ func loginV2(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *reques
|
||||||
return "", fmt.Errorf("no successful auth challenge for %s - errors: %s", registryEndpoint, allErrors)
|
return "", fmt.Errorf("no successful auth challenge for %s - errors: %s", registryEndpoint, allErrors)
|
||||||
}
|
}
|
||||||
|
|
||||||
func tryV2BasicAuthLogin(authConfig *AuthConfig, params map[string]string, registryEndpoint *Endpoint, client *http.Client, factory *requestdecorator.RequestFactory) error {
|
func tryV2BasicAuthLogin(authConfig *cliconfig.AuthConfig, params map[string]string, registryEndpoint *Endpoint, client *http.Client, factory *requestdecorator.RequestFactory) error {
|
||||||
req, err := factory.NewRequest("GET", registryEndpoint.Path(""), nil)
|
req, err := factory.NewRequest("GET", registryEndpoint.Path(""), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -450,7 +268,7 @@ func tryV2BasicAuthLogin(authConfig *AuthConfig, params map[string]string, regis
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func tryV2TokenAuthLogin(authConfig *AuthConfig, params map[string]string, registryEndpoint *Endpoint, client *http.Client, factory *requestdecorator.RequestFactory) error {
|
func tryV2TokenAuthLogin(authConfig *cliconfig.AuthConfig, params map[string]string, registryEndpoint *Endpoint, client *http.Client, factory *requestdecorator.RequestFactory) error {
|
||||||
token, err := getToken(authConfig.Username, authConfig.Password, params, registryEndpoint, client, factory)
|
token, err := getToken(authConfig.Username, authConfig.Password, params, registryEndpoint, client, factory)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -477,7 +295,7 @@ func tryV2TokenAuthLogin(authConfig *AuthConfig, params map[string]string, regis
|
||||||
}
|
}
|
||||||
|
|
||||||
// this method matches a auth configuration to a server address or a url
|
// this method matches a auth configuration to a server address or a url
|
||||||
func (config *ConfigFile) ResolveAuthConfig(index *IndexInfo) AuthConfig {
|
func ResolveAuthConfig(config *cliconfig.ConfigFile, index *IndexInfo) cliconfig.AuthConfig {
|
||||||
configKey := index.GetAuthConfigKey()
|
configKey := index.GetAuthConfigKey()
|
||||||
// First try the happy case
|
// First try the happy case
|
||||||
if c, found := config.AuthConfigs[configKey]; found || index.Official {
|
if c, found := config.AuthConfigs[configKey]; found || index.Official {
|
||||||
|
@ -499,16 +317,12 @@ func (config *ConfigFile) ResolveAuthConfig(index *IndexInfo) AuthConfig {
|
||||||
|
|
||||||
// Maybe they have a legacy config file, we will iterate the keys converting
|
// Maybe they have a legacy config file, we will iterate the keys converting
|
||||||
// them to the new format and testing
|
// them to the new format and testing
|
||||||
for registry, config := range config.AuthConfigs {
|
for registry, ac := range config.AuthConfigs {
|
||||||
if configKey == convertToHostname(registry) {
|
if configKey == convertToHostname(registry) {
|
||||||
return config
|
return ac
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// When all else fails, return an empty auth config
|
// When all else fails, return an empty auth config
|
||||||
return AuthConfig{}
|
return cliconfig.AuthConfig{}
|
||||||
}
|
|
||||||
|
|
||||||
func (config *ConfigFile) Filename() string {
|
|
||||||
return config.filename
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,14 +5,16 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/docker/cliconfig"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestEncodeAuth(t *testing.T) {
|
func TestEncodeAuth(t *testing.T) {
|
||||||
newAuthConfig := &AuthConfig{Username: "ken", Password: "test", Email: "test@example.com"}
|
newAuthConfig := &cliconfig.AuthConfig{Username: "ken", Password: "test", Email: "test@example.com"}
|
||||||
authStr := encodeAuth(newAuthConfig)
|
authStr := cliconfig.EncodeAuth(newAuthConfig)
|
||||||
decAuthConfig := &AuthConfig{}
|
decAuthConfig := &cliconfig.AuthConfig{}
|
||||||
var err error
|
var err error
|
||||||
decAuthConfig.Username, decAuthConfig.Password, err = decodeAuth(authStr)
|
decAuthConfig.Username, decAuthConfig.Password, err = cliconfig.DecodeAuth(authStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -27,19 +29,16 @@ func TestEncodeAuth(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupTempConfigFile() (*ConfigFile, error) {
|
func setupTempConfigFile() (*cliconfig.ConfigFile, error) {
|
||||||
root, err := ioutil.TempDir("", "docker-test-auth")
|
root, err := ioutil.TempDir("", "docker-test-auth")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
root = filepath.Join(root, CONFIGFILE)
|
root = filepath.Join(root, cliconfig.CONFIGFILE)
|
||||||
configFile := &ConfigFile{
|
configFile := cliconfig.NewConfigFile(root)
|
||||||
AuthConfigs: make(map[string]AuthConfig),
|
|
||||||
filename: root,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, registry := range []string{"testIndex", IndexServerAddress()} {
|
for _, registry := range []string{"testIndex", IndexServerAddress()} {
|
||||||
configFile.AuthConfigs[registry] = AuthConfig{
|
configFile.AuthConfigs[registry] = cliconfig.AuthConfig{
|
||||||
Username: "docker-user",
|
Username: "docker-user",
|
||||||
Password: "docker-pass",
|
Password: "docker-pass",
|
||||||
Email: "docker@docker.io",
|
Email: "docker@docker.io",
|
||||||
|
@ -54,7 +53,7 @@ func TestSameAuthDataPostSave(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(configFile.filename)
|
defer os.RemoveAll(configFile.Filename())
|
||||||
|
|
||||||
err = configFile.Save()
|
err = configFile.Save()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -81,7 +80,7 @@ func TestResolveAuthConfigIndexServer(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(configFile.filename)
|
defer os.RemoveAll(configFile.Filename())
|
||||||
|
|
||||||
indexConfig := configFile.AuthConfigs[IndexServerAddress()]
|
indexConfig := configFile.AuthConfigs[IndexServerAddress()]
|
||||||
|
|
||||||
|
@ -92,10 +91,10 @@ func TestResolveAuthConfigIndexServer(t *testing.T) {
|
||||||
Official: false,
|
Official: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
resolved := configFile.ResolveAuthConfig(officialIndex)
|
resolved := ResolveAuthConfig(configFile, officialIndex)
|
||||||
assertEqual(t, resolved, indexConfig, "Expected ResolveAuthConfig to return IndexServerAddress()")
|
assertEqual(t, resolved, indexConfig, "Expected ResolveAuthConfig to return IndexServerAddress()")
|
||||||
|
|
||||||
resolved = configFile.ResolveAuthConfig(privateIndex)
|
resolved = ResolveAuthConfig(configFile, privateIndex)
|
||||||
assertNotEqual(t, resolved, indexConfig, "Expected ResolveAuthConfig to not return IndexServerAddress()")
|
assertNotEqual(t, resolved, indexConfig, "Expected ResolveAuthConfig to not return IndexServerAddress()")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,26 +103,26 @@ func TestResolveAuthConfigFullURL(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(configFile.filename)
|
defer os.RemoveAll(configFile.Filename())
|
||||||
|
|
||||||
registryAuth := AuthConfig{
|
registryAuth := cliconfig.AuthConfig{
|
||||||
Username: "foo-user",
|
Username: "foo-user",
|
||||||
Password: "foo-pass",
|
Password: "foo-pass",
|
||||||
Email: "foo@example.com",
|
Email: "foo@example.com",
|
||||||
}
|
}
|
||||||
localAuth := AuthConfig{
|
localAuth := cliconfig.AuthConfig{
|
||||||
Username: "bar-user",
|
Username: "bar-user",
|
||||||
Password: "bar-pass",
|
Password: "bar-pass",
|
||||||
Email: "bar@example.com",
|
Email: "bar@example.com",
|
||||||
}
|
}
|
||||||
officialAuth := AuthConfig{
|
officialAuth := cliconfig.AuthConfig{
|
||||||
Username: "baz-user",
|
Username: "baz-user",
|
||||||
Password: "baz-pass",
|
Password: "baz-pass",
|
||||||
Email: "baz@example.com",
|
Email: "baz@example.com",
|
||||||
}
|
}
|
||||||
configFile.AuthConfigs[IndexServerAddress()] = officialAuth
|
configFile.AuthConfigs[IndexServerAddress()] = officialAuth
|
||||||
|
|
||||||
expectedAuths := map[string]AuthConfig{
|
expectedAuths := map[string]cliconfig.AuthConfig{
|
||||||
"registry.example.com": registryAuth,
|
"registry.example.com": registryAuth,
|
||||||
"localhost:8000": localAuth,
|
"localhost:8000": localAuth,
|
||||||
"registry.com": localAuth,
|
"registry.com": localAuth,
|
||||||
|
@ -160,12 +159,12 @@ func TestResolveAuthConfigFullURL(t *testing.T) {
|
||||||
}
|
}
|
||||||
for _, registry := range registries {
|
for _, registry := range registries {
|
||||||
configFile.AuthConfigs[registry] = configured
|
configFile.AuthConfigs[registry] = configured
|
||||||
resolved := configFile.ResolveAuthConfig(index)
|
resolved := ResolveAuthConfig(configFile, index)
|
||||||
if resolved.Email != configured.Email {
|
if resolved.Email != configured.Email {
|
||||||
t.Errorf("%s -> %q != %q\n", registry, resolved.Email, configured.Email)
|
t.Errorf("%s -> %q != %q\n", registry, resolved.Email, configured.Email)
|
||||||
}
|
}
|
||||||
delete(configFile.AuthConfigs, registry)
|
delete(configFile.AuthConfigs, registry)
|
||||||
resolved = configFile.ResolveAuthConfig(index)
|
resolved = ResolveAuthConfig(configFile, index)
|
||||||
if resolved.Email == configured.Email {
|
if resolved.Email == configured.Email {
|
||||||
t.Errorf("%s -> %q == %q\n", registry, resolved.Email, configured.Email)
|
t.Errorf("%s -> %q == %q\n", registry, resolved.Email, configured.Email)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/docker/cliconfig"
|
||||||
"github.com/docker/docker/pkg/requestdecorator"
|
"github.com/docker/docker/pkg/requestdecorator"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -20,7 +21,7 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
func spawnTestRegistrySession(t *testing.T) *Session {
|
func spawnTestRegistrySession(t *testing.T) *Session {
|
||||||
authConfig := &AuthConfig{}
|
authConfig := &cliconfig.AuthConfig{}
|
||||||
endpoint, err := NewEndpoint(makeIndex("/v1/"))
|
endpoint, err := NewEndpoint(makeIndex("/v1/"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -33,7 +34,7 @@ func spawnTestRegistrySession(t *testing.T) *Session {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPublicSession(t *testing.T) {
|
func TestPublicSession(t *testing.T) {
|
||||||
authConfig := &AuthConfig{}
|
authConfig := &cliconfig.AuthConfig{}
|
||||||
|
|
||||||
getSessionDecorators := func(index *IndexInfo) int {
|
getSessionDecorators := func(index *IndexInfo) int {
|
||||||
endpoint, err := NewEndpoint(index)
|
endpoint, err := NewEndpoint(index)
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package registry
|
package registry
|
||||||
|
|
||||||
|
import "github.com/docker/docker/cliconfig"
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
Config *ServiceConfig
|
Config *ServiceConfig
|
||||||
}
|
}
|
||||||
|
@ -15,7 +17,7 @@ func NewService(options *Options) *Service {
|
||||||
// Auth contacts the public registry with the provided credentials,
|
// Auth contacts the public registry with the provided credentials,
|
||||||
// and returns OK if authentication was sucessful.
|
// and returns OK if authentication was sucessful.
|
||||||
// It can be used to verify the validity of a client's credentials.
|
// It can be used to verify the validity of a client's credentials.
|
||||||
func (s *Service) Auth(authConfig *AuthConfig) (string, error) {
|
func (s *Service) Auth(authConfig *cliconfig.AuthConfig) (string, error) {
|
||||||
addr := authConfig.ServerAddress
|
addr := authConfig.ServerAddress
|
||||||
if addr == "" {
|
if addr == "" {
|
||||||
// Use the official registry address if not specified.
|
// Use the official registry address if not specified.
|
||||||
|
@ -35,7 +37,7 @@ func (s *Service) Auth(authConfig *AuthConfig) (string, error) {
|
||||||
|
|
||||||
// Search queries the public registry for images matching the specified
|
// Search queries the public registry for images matching the specified
|
||||||
// search terms, and returns the results.
|
// search terms, and returns the results.
|
||||||
func (s *Service) Search(term string, authConfig *AuthConfig, headers map[string][]string) (*SearchResults, error) {
|
func (s *Service) Search(term string, authConfig *cliconfig.AuthConfig, headers map[string][]string) (*SearchResults, error) {
|
||||||
repoInfo, err := s.ResolveRepository(term)
|
repoInfo, err := s.ResolveRepository(term)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -18,20 +18,21 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/docker/cliconfig"
|
||||||
"github.com/docker/docker/pkg/httputils"
|
"github.com/docker/docker/pkg/httputils"
|
||||||
"github.com/docker/docker/pkg/requestdecorator"
|
"github.com/docker/docker/pkg/requestdecorator"
|
||||||
"github.com/docker/docker/pkg/tarsum"
|
"github.com/docker/docker/pkg/tarsum"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Session struct {
|
type Session struct {
|
||||||
authConfig *AuthConfig
|
authConfig *cliconfig.AuthConfig
|
||||||
reqFactory *requestdecorator.RequestFactory
|
reqFactory *requestdecorator.RequestFactory
|
||||||
indexEndpoint *Endpoint
|
indexEndpoint *Endpoint
|
||||||
jar *cookiejar.Jar
|
jar *cookiejar.Jar
|
||||||
timeout TimeoutType
|
timeout TimeoutType
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSession(authConfig *AuthConfig, factory *requestdecorator.RequestFactory, endpoint *Endpoint, timeout bool) (r *Session, err error) {
|
func NewSession(authConfig *cliconfig.AuthConfig, factory *requestdecorator.RequestFactory, endpoint *Endpoint, timeout bool) (r *Session, err error) {
|
||||||
r = &Session{
|
r = &Session{
|
||||||
authConfig: authConfig,
|
authConfig: authConfig,
|
||||||
indexEndpoint: endpoint,
|
indexEndpoint: endpoint,
|
||||||
|
@ -600,12 +601,12 @@ func (r *Session) SearchRepositories(term string) (*SearchResults, error) {
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Session) GetAuthConfig(withPasswd bool) *AuthConfig {
|
func (r *Session) GetAuthConfig(withPasswd bool) *cliconfig.AuthConfig {
|
||||||
password := ""
|
password := ""
|
||||||
if withPasswd {
|
if withPasswd {
|
||||||
password = r.authConfig.Password
|
password = r.authConfig.Password
|
||||||
}
|
}
|
||||||
return &AuthConfig{
|
return &cliconfig.AuthConfig{
|
||||||
Username: r.authConfig.Username,
|
Username: r.authConfig.Username,
|
||||||
Password: password,
|
Password: password,
|
||||||
Email: r.authConfig.Email,
|
Email: r.authConfig.Email,
|
||||||
|
|
Loading…
Reference in New Issue