diff --git a/api/client/registry.go b/api/client/registry.go index f8e40725f2..b0b0ebd50e 100644 --- a/api/client/registry.go +++ b/api/client/registry.go @@ -13,6 +13,7 @@ import ( "golang.org/x/net/context" "github.com/docker/docker/pkg/term" + "github.com/docker/docker/reference" "github.com/docker/docker/registry" "github.com/docker/engine-api/types" registrytypes "github.com/docker/engine-api/types/registry" @@ -148,6 +149,34 @@ func (cli *DockerCli) ConfigureAuth(flUser, flPassword, serverAddress string, is return authconfig, nil } +// ResolveAuthConfigFromImage retrieves that AuthConfig using the image string +func (cli *DockerCli) ResolveAuthConfigFromImage(ctx context.Context, image string) (types.AuthConfig, error) { + registryRef, err := reference.ParseNamed(image) + if err != nil { + return types.AuthConfig{}, err + } + repoInfo, err := registry.ParseRepositoryInfo(registryRef) + if err != nil { + return types.AuthConfig{}, err + } + authConfig := cli.ResolveAuthConfig(ctx, repoInfo.Index) + return authConfig, nil +} + +// RetrieveAuthTokenFromImage retrieves an encoded auth token given a complete image +func (cli *DockerCli) RetrieveAuthTokenFromImage(ctx context.Context, image string) (string, error) { + // Retrieve encoded auth token from the image reference + authConfig, err := cli.ResolveAuthConfigFromImage(ctx, image) + if err != nil { + return "", err + } + encodedAuth, err := EncodeAuthToBase64(authConfig) + if err != nil { + return "", err + } + return encodedAuth, nil +} + func readInput(in io.Reader, out io.Writer) string { reader := bufio.NewReader(in) line, _, err := reader.ReadLine() diff --git a/api/client/service/create.go b/api/client/service/create.go index 1f2e2a053f..9333ec411f 100644 --- a/api/client/service/create.go +++ b/api/client/service/create.go @@ -32,14 +32,25 @@ func newCreateCommand(dockerCli *client.DockerCli) *cobra.Command { } func runCreate(dockerCli *client.DockerCli, opts *serviceOptions) error { - client := dockerCli.Client() + apiClient := dockerCli.Client() service, err := opts.ToService() if err != nil { return err } - response, err := client.ServiceCreate(context.Background(), service) + ctx := context.Background() + // Retrieve encoded auth token from the image reference + encodedAuth, err := dockerCli.RetrieveAuthTokenFromImage(ctx, opts.image) + if err != nil { + return err + } + + headers := map[string][]string{ + "x-registry-auth": {encodedAuth}, + } + + response, err := apiClient.ServiceCreate(ctx, service, headers) if err != nil { return err } diff --git a/api/client/service/scale.go b/api/client/service/scale.go index e313948aca..d272b557cf 100644 --- a/api/client/service/scale.go +++ b/api/client/service/scale.go @@ -60,6 +60,7 @@ func runScale(dockerCli *client.DockerCli, args []string) error { func runServiceScale(dockerCli *client.DockerCli, serviceID string, scale string) error { client := dockerCli.Client() ctx := context.Background() + headers := map[string][]string{} service, _, err := client.ServiceInspectWithRaw(ctx, serviceID) @@ -67,6 +68,19 @@ func runServiceScale(dockerCli *client.DockerCli, serviceID string, scale string return err } + // TODO(nishanttotla): Is this the best way to get the image? + image := service.Spec.TaskTemplate.ContainerSpec.Image + if image != "" { + // Retrieve encoded auth token from the image reference + encodedAuth, err := dockerCli.RetrieveAuthTokenFromImage(ctx, image) + if err != nil { + return err + } + headers = map[string][]string{ + "x-registry-auth": {encodedAuth}, + } + } + serviceMode := &service.Spec.Mode if serviceMode.Replicated == nil { return fmt.Errorf("scale can only be used with replicated mode") @@ -77,7 +91,7 @@ func runServiceScale(dockerCli *client.DockerCli, serviceID string, scale string } serviceMode.Replicated.Replicas = &uintScale - err = client.ServiceUpdate(ctx, service.ID, service.Version, service.Spec) + err = client.ServiceUpdate(ctx, service.ID, service.Version, service.Spec, headers) if err != nil { return err } diff --git a/api/client/service/update.go b/api/client/service/update.go index 48337e82aa..1fa514b2a3 100644 --- a/api/client/service/update.go +++ b/api/client/service/update.go @@ -37,10 +37,28 @@ func newUpdateCommand(dockerCli *client.DockerCli) *cobra.Command { } func runUpdate(dockerCli *client.DockerCli, flags *pflag.FlagSet, serviceID string) error { - client := dockerCli.Client() + apiClient := dockerCli.Client() ctx := context.Background() + headers := map[string][]string{} - service, _, err := client.ServiceInspectWithRaw(ctx, serviceID) + // TODO(nishanttotla): Is this the best way to get the new image? + image, err := flags.GetString("image") + if err != nil { + return err + } + if image != "" { + // Retrieve encoded auth token from the image reference + // only do this if a new image has been provided as part of the udpate + encodedAuth, err := dockerCli.RetrieveAuthTokenFromImage(ctx, image) + if err != nil { + return err + } + headers = map[string][]string{ + "x-registry-auth": {encodedAuth}, + } + } + + service, _, err := apiClient.ServiceInspectWithRaw(ctx, serviceID) if err != nil { return err } @@ -49,7 +67,8 @@ func runUpdate(dockerCli *client.DockerCli, flags *pflag.FlagSet, serviceID stri if err != nil { return err } - err = client.ServiceUpdate(ctx, service.ID, service.Version, service.Spec) + + err = apiClient.ServiceUpdate(ctx, service.ID, service.Version, service.Spec, headers) if err != nil { return err } diff --git a/api/server/router/swarm/backend.go b/api/server/router/swarm/backend.go index 05fe00a0c2..f9ee0a1bbe 100644 --- a/api/server/router/swarm/backend.go +++ b/api/server/router/swarm/backend.go @@ -14,8 +14,8 @@ type Backend interface { Update(uint64, types.Spec) error GetServices(basictypes.ServiceListOptions) ([]types.Service, error) GetService(string) (types.Service, error) - CreateService(types.ServiceSpec) (string, error) - UpdateService(string, uint64, types.ServiceSpec) error + CreateService(types.ServiceSpec, string) (string, error) + UpdateService(string, uint64, types.ServiceSpec, string) error RemoveService(string) error GetNodes(basictypes.NodeListOptions) ([]types.Node, error) GetNode(string) (types.Node, error) diff --git a/api/server/router/swarm/cluster_routes.go b/api/server/router/swarm/cluster_routes.go index 2f678d706c..7744de2d2b 100644 --- a/api/server/router/swarm/cluster_routes.go +++ b/api/server/router/swarm/cluster_routes.go @@ -107,7 +107,12 @@ func (sr *swarmRouter) createService(ctx context.Context, w http.ResponseWriter, return err } - id, err := sr.backend.CreateService(service) + encodedAuth := "" + if auth, ok := r.Header["x-registry-auth"]; ok { + encodedAuth = auth[0] + } + + id, err := sr.backend.CreateService(service, encodedAuth) if err != nil { logrus.Errorf("Error creating service %s: %v", id, err) return err @@ -130,7 +135,12 @@ func (sr *swarmRouter) updateService(ctx context.Context, w http.ResponseWriter, return fmt.Errorf("Invalid service version '%s': %s", rawVersion, err.Error()) } - if err := sr.backend.UpdateService(vars["id"], version, service); err != nil { + encodedAuth := "" + if auth, ok := r.Header["x-registry-auth"]; ok { + encodedAuth = auth[0] + } + + if err := sr.backend.UpdateService(vars["id"], version, service, encodedAuth); err != nil { logrus.Errorf("Error updating service %s: %v", vars["id"], err) return err } diff --git a/daemon/cluster/cluster.go b/daemon/cluster/cluster.go index aeee0643f0..46b5384223 100644 --- a/daemon/cluster/cluster.go +++ b/daemon/cluster/cluster.go @@ -663,7 +663,7 @@ func (c *Cluster) GetServices(options apitypes.ServiceListOptions) ([]types.Serv } // CreateService creates a new service in a managed swarm cluster. -func (c *Cluster) CreateService(s types.ServiceSpec) (string, error) { +func (c *Cluster) CreateService(s types.ServiceSpec, encodedAuth string) (string, error) { c.RLock() defer c.RUnlock() @@ -682,6 +682,11 @@ func (c *Cluster) CreateService(s types.ServiceSpec) (string, error) { if err != nil { return "", err } + + if encodedAuth != "" { + serviceSpec.Task.Runtime.(*swarmapi.TaskSpec_Container).Container.PullOptions = &swarmapi.ContainerSpec_PullOptions{RegistryAuth: encodedAuth} + } + r, err := c.client.CreateService(ctx, &swarmapi.CreateServiceRequest{Spec: &serviceSpec}) if err != nil { return "", err @@ -707,7 +712,7 @@ func (c *Cluster) GetService(input string) (types.Service, error) { } // UpdateService updates existing service to match new properties. -func (c *Cluster) UpdateService(serviceID string, version uint64, spec types.ServiceSpec) error { +func (c *Cluster) UpdateService(serviceID string, version uint64, spec types.ServiceSpec, encodedAuth string) error { c.RLock() defer c.RUnlock() @@ -720,6 +725,10 @@ func (c *Cluster) UpdateService(serviceID string, version uint64, spec types.Ser return err } + if encodedAuth != "" { + serviceSpec.Task.Runtime.(*swarmapi.TaskSpec_Container).Container.PullOptions = &swarmapi.ContainerSpec_PullOptions{RegistryAuth: encodedAuth} + } + _, err = c.client.UpdateService( c.getRequestContext(), &swarmapi.UpdateServiceRequest{