diff --git a/api.go b/api.go index 5ac39e216a..61df3e7fe6 100644 --- a/api.go +++ b/api.go @@ -925,10 +925,17 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ rawRm = r.FormValue("rm") authEncoded = r.Header.Get("X-Registry-Auth") authConfig = &auth.AuthConfig{} + configFileEncoded = r.Header.Get("X-Registry-Config") + configFile = &auth.ConfigFile{} tag string ) repoName, tag = utils.ParseRepositoryTag(repoName) - if authEncoded != "" { + + // This block can be removed when API versions prior to 1.9 are deprecated. + // Both headers will be parsed and sent along to the daemon, but if a non-empty + // ConfigFile is present, any value provided as an AuthConfig directly will + // be overridden. See BuildFile::CmdFrom for details. + if version < 1.9 && authEncoded != "" { authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) if err := json.NewDecoder(authJson).Decode(authConfig); err != nil { // for a pull it is not an error if no auth was given @@ -937,6 +944,15 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ } } + if configFileEncoded != "" { + configFileJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(configFileEncoded)) + if err := json.NewDecoder(configFileJson).Decode(configFile); err != nil { + // 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 + configFile = &auth.ConfigFile{} + } + } + var context io.Reader if remoteURL == "" { @@ -1003,7 +1019,7 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ Writer: utils.NewWriteFlusher(w), StreamFormatter: sf, }, - !suppressOutput, !noCache, rm, utils.NewWriteFlusher(w), sf, authConfig) + !suppressOutput, !noCache, rm, utils.NewWriteFlusher(w), sf, authConfig, configFile) id, err := b.Build(context) if err != nil { if sf.Used() { diff --git a/buildfile.go b/buildfile.go index 19249b04c4..97cf35c406 100644 --- a/buildfile.go +++ b/buildfile.go @@ -8,6 +8,7 @@ import ( "fmt" "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/auth" + "github.com/dotcloud/docker/registry" "github.com/dotcloud/docker/utils" "io" "io/ioutil" @@ -47,6 +48,7 @@ type buildFile struct { rm bool authConfig *auth.AuthConfig + configFile *auth.ConfigFile tmpContainers map[string]struct{} tmpImages map[string]struct{} @@ -72,7 +74,17 @@ func (b *buildFile) CmdFrom(name string) error { if err != nil { if b.runtime.graph.IsNotExist(err) { remote, tag := utils.ParseRepositoryTag(name) - if err := b.srv.ImagePull(remote, tag, b.outOld, b.sf, b.authConfig, nil, true); err != nil { + pullRegistryAuth := b.authConfig + if len(b.configFile.Configs) > 0 { + // The request came with a full auth config file, we prefer to use that + endpoint, _, err := registry.ResolveRepositoryName(remote) + if err != nil { + return err + } + resolvedAuth := b.configFile.ResolveAuthConfig(endpoint) + pullRegistryAuth = &resolvedAuth + } + if err := b.srv.ImagePull(remote, tag, b.outOld, b.sf, pullRegistryAuth, nil, true); err != nil { return err } image, err = b.runtime.repositories.LookupImage(name) @@ -696,7 +708,7 @@ func (b *buildFile) Build(context io.Reader) (string, error) { return "", fmt.Errorf("No image was generated. This may be because the Dockerfile does not, like, do anything.\n") } -func NewBuildFile(srv *Server, outStream, errStream io.Writer, verbose, utilizeCache, rm bool, outOld io.Writer, sf *utils.StreamFormatter, auth *auth.AuthConfig) BuildFile { +func NewBuildFile(srv *Server, outStream, errStream io.Writer, verbose, utilizeCache, rm bool, outOld io.Writer, sf *utils.StreamFormatter, auth *auth.AuthConfig, authConfigFile *auth.ConfigFile) BuildFile { return &buildFile{ runtime: srv.runtime, srv: srv, @@ -710,6 +722,7 @@ func NewBuildFile(srv *Server, outStream, errStream io.Writer, verbose, utilizeC rm: rm, sf: sf, authConfig: auth, + configFile: authConfigFile, outOld: outOld, } } diff --git a/commands.go b/commands.go index a864180fef..a5d7c9100a 100644 --- a/commands.go +++ b/commands.go @@ -227,12 +227,14 @@ func (cli *DockerCli) CmdBuild(args ...string) error { v.Set("rm", "1") } + cli.LoadConfigFile() + headers := http.Header(make(map[string][]string)) buf, err := json.Marshal(cli.configFile) if err != nil { return err } - headers.Add("X-Registry-Auth", base64.URLEncoding.EncodeToString(buf)) + headers.Add("X-Registry-Config", base64.URLEncoding.EncodeToString(buf)) if context != nil { headers.Set("Content-Type", "application/tar") diff --git a/docs/sources/reference/api/docker_remote_api.rst b/docs/sources/reference/api/docker_remote_api.rst index bcac603717..b616e10a8f 100644 --- a/docs/sources/reference/api/docker_remote_api.rst +++ b/docs/sources/reference/api/docker_remote_api.rst @@ -54,6 +54,13 @@ What's new **New!** This endpoint now returns a list of json message, like the events endpoint +.. http:post:: /build + + **New!** This endpoint now takes a serialized ConfigFile which it uses to + resolve the proper registry auth credentials for pulling the base image. + Clients which previously implemented the version accepting an AuthConfig + object must be updated. + v1.8 **** diff --git a/docs/sources/reference/api/docker_remote_api_v1.9.rst b/docs/sources/reference/api/docker_remote_api_v1.9.rst index 6546035a57..65e501234f 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.9.rst +++ b/docs/sources/reference/api/docker_remote_api_v1.9.rst @@ -1025,7 +1025,7 @@ Build an image from Dockerfile via stdin :query q: suppress verbose build output :query nocache: do not use the cache when building the image :reqheader Content-type: should be set to ``"application/tar"``. - :reqheader X-Registry-Auth: base64-encoded AuthConfig object + :reqheader X-Registry-Config: base64-encoded ConfigFile object :statuscode 200: no error :statuscode 500: server error diff --git a/integration/buildfile_test.go b/integration/buildfile_test.go index 5dd403274e..6c1acc0088 100644 --- a/integration/buildfile_test.go +++ b/integration/buildfile_test.go @@ -296,7 +296,7 @@ func buildImage(context testContextTemplate, t *testing.T, eng *engine.Engine, u } dockerfile := constructDockerfile(context.dockerfile, ip, port) - buildfile := docker.NewBuildFile(srv, ioutil.Discard, ioutil.Discard, false, useCache, false, ioutil.Discard, utils.NewStreamFormatter(false), nil) + buildfile := docker.NewBuildFile(srv, ioutil.Discard, ioutil.Discard, false, useCache, false, ioutil.Discard, utils.NewStreamFormatter(false), nil, nil) id, err := buildfile.Build(mkTestContext(dockerfile, context.files, t)) if err != nil { return nil, err @@ -700,7 +700,7 @@ func TestForbiddenContextPath(t *testing.T) { } dockerfile := constructDockerfile(context.dockerfile, ip, port) - buildfile := docker.NewBuildFile(srv, ioutil.Discard, ioutil.Discard, false, true, false, ioutil.Discard, utils.NewStreamFormatter(false), nil) + buildfile := docker.NewBuildFile(srv, ioutil.Discard, ioutil.Discard, false, true, false, ioutil.Discard, utils.NewStreamFormatter(false), nil, nil) _, err = buildfile.Build(mkTestContext(dockerfile, context.files, t)) if err == nil { @@ -746,7 +746,7 @@ func TestBuildADDFileNotFound(t *testing.T) { } dockerfile := constructDockerfile(context.dockerfile, ip, port) - buildfile := docker.NewBuildFile(mkServerFromEngine(eng, t), ioutil.Discard, ioutil.Discard, false, true, false, ioutil.Discard, utils.NewStreamFormatter(false), nil) + buildfile := docker.NewBuildFile(mkServerFromEngine(eng, t), ioutil.Discard, ioutil.Discard, false, true, false, ioutil.Discard, utils.NewStreamFormatter(false), nil, nil) _, err = buildfile.Build(mkTestContext(dockerfile, context.files, t)) if err == nil {