Browse code

Merge pull request #23584 from nishanttotla/private-images-swarm-services

Passing registry auth token for service create, update

Tõnis Tiigi authored on 2016/07/01 09:18:59
Showing 14 changed files
... ...
@@ -13,6 +13,7 @@ import (
13 13
 	"golang.org/x/net/context"
14 14
 
15 15
 	"github.com/docker/docker/pkg/term"
16
+	"github.com/docker/docker/reference"
16 17
 	"github.com/docker/docker/registry"
17 18
 	"github.com/docker/engine-api/types"
18 19
 	registrytypes "github.com/docker/engine-api/types/registry"
... ...
@@ -148,6 +149,34 @@ func (cli *DockerCli) ConfigureAuth(flUser, flPassword, serverAddress string, is
148 148
 	return authconfig, nil
149 149
 }
150 150
 
151
+// resolveAuthConfigFromImage retrieves that AuthConfig using the image string
152
+func (cli *DockerCli) resolveAuthConfigFromImage(ctx context.Context, image string) (types.AuthConfig, error) {
153
+	registryRef, err := reference.ParseNamed(image)
154
+	if err != nil {
155
+		return types.AuthConfig{}, err
156
+	}
157
+	repoInfo, err := registry.ParseRepositoryInfo(registryRef)
158
+	if err != nil {
159
+		return types.AuthConfig{}, err
160
+	}
161
+	authConfig := cli.ResolveAuthConfig(ctx, repoInfo.Index)
162
+	return authConfig, nil
163
+}
164
+
165
+// RetrieveAuthTokenFromImage retrieves an encoded auth token given a complete image
166
+func (cli *DockerCli) RetrieveAuthTokenFromImage(ctx context.Context, image string) (string, error) {
167
+	// Retrieve encoded auth token from the image reference
168
+	authConfig, err := cli.resolveAuthConfigFromImage(ctx, image)
169
+	if err != nil {
170
+		return "", err
171
+	}
172
+	encodedAuth, err := EncodeAuthToBase64(authConfig)
173
+	if err != nil {
174
+		return "", err
175
+	}
176
+	return encodedAuth, nil
177
+}
178
+
151 179
 func readInput(in io.Reader, out io.Writer) string {
152 180
 	reader := bufio.NewReader(in)
153 181
 	line, _, err := reader.ReadLine()
... ...
@@ -32,14 +32,27 @@ func newCreateCommand(dockerCli *client.DockerCli) *cobra.Command {
32 32
 }
33 33
 
34 34
 func runCreate(dockerCli *client.DockerCli, opts *serviceOptions) error {
35
-	client := dockerCli.Client()
35
+	apiClient := dockerCli.Client()
36
+	headers := map[string][]string{}
36 37
 
37 38
 	service, err := opts.ToService()
38 39
 	if err != nil {
39 40
 		return err
40 41
 	}
41 42
 
42
-	response, err := client.ServiceCreate(context.Background(), service)
43
+	ctx := context.Background()
44
+
45
+	// only send auth if flag was set
46
+	if opts.registryAuth {
47
+		// Retrieve encoded auth token from the image reference
48
+		encodedAuth, err := dockerCli.RetrieveAuthTokenFromImage(ctx, opts.image)
49
+		if err != nil {
50
+			return err
51
+		}
52
+		headers["X-Registry-Auth"] = []string{encodedAuth}
53
+	}
54
+
55
+	response, err := apiClient.ServiceCreate(ctx, service, headers)
43 56
 	if err != nil {
44 57
 		return err
45 58
 	}
... ...
@@ -373,6 +373,8 @@ type serviceOptions struct {
373 373
 	update        updateOptions
374 374
 	networks      []string
375 375
 	endpoint      endpointOptions
376
+
377
+	registryAuth bool
376 378
 }
377 379
 
378 380
 func newServiceOptions() *serviceOptions {
... ...
@@ -436,7 +438,7 @@ func (opts *serviceOptions) ToService() (swarm.ServiceSpec, error) {
436 436
 	return service, nil
437 437
 }
438 438
 
439
-// addServiceFlags adds all flags that are common to both `create` and `update.
439
+// addServiceFlags adds all flags that are common to both `create` and `update`.
440 440
 // Any flags that are not common are added separately in the individual command
441 441
 func addServiceFlags(cmd *cobra.Command, opts *serviceOptions) {
442 442
 	flags := cmd.Flags()
... ...
@@ -469,6 +471,8 @@ func addServiceFlags(cmd *cobra.Command, opts *serviceOptions) {
469 469
 	flags.StringSliceVar(&opts.networks, flagNetwork, []string{}, "Network attachments")
470 470
 	flags.StringVar(&opts.endpoint.mode, flagEndpointMode, "", "Endpoint mode(Valid values: vip, dnsrr)")
471 471
 	flags.VarP(&opts.endpoint.ports, flagPublish, "p", "Publish a port as a node port")
472
+
473
+	flags.BoolVar(&opts.registryAuth, flagRegistryAuth, false, "Send registry authentication details to Swarm agents")
472 474
 }
473 475
 
474 476
 const (
... ...
@@ -493,4 +497,5 @@ const (
493 493
 	flagUpdateDelay        = "update-delay"
494 494
 	flagUpdateParallelism  = "update-parallelism"
495 495
 	flagUser               = "user"
496
+	flagRegistryAuth       = "registry-auth"
496 497
 )
... ...
@@ -77,7 +77,7 @@ func runServiceScale(dockerCli *client.DockerCli, serviceID string, scale string
77 77
 	}
78 78
 	serviceMode.Replicated.Replicas = &uintScale
79 79
 
80
-	err = client.ServiceUpdate(ctx, service.ID, service.Version, service.Spec)
80
+	err = client.ServiceUpdate(ctx, service.ID, service.Version, service.Spec, nil)
81 81
 	if err != nil {
82 82
 		return err
83 83
 	}
... ...
@@ -37,10 +37,11 @@ func newUpdateCommand(dockerCli *client.DockerCli) *cobra.Command {
37 37
 }
38 38
 
39 39
 func runUpdate(dockerCli *client.DockerCli, flags *pflag.FlagSet, serviceID string) error {
40
-	client := dockerCli.Client()
40
+	apiClient := dockerCli.Client()
41 41
 	ctx := context.Background()
42
+	headers := map[string][]string{}
42 43
 
43
-	service, _, err := client.ServiceInspectWithRaw(ctx, serviceID)
44
+	service, _, err := apiClient.ServiceInspectWithRaw(ctx, serviceID)
44 45
 	if err != nil {
45 46
 		return err
46 47
 	}
... ...
@@ -49,7 +50,24 @@ func runUpdate(dockerCli *client.DockerCli, flags *pflag.FlagSet, serviceID stri
49 49
 	if err != nil {
50 50
 		return err
51 51
 	}
52
-	err = client.ServiceUpdate(ctx, service.ID, service.Version, service.Spec)
52
+
53
+	// only send auth if flag was set
54
+	sendAuth, err := flags.GetBool(flagRegistryAuth)
55
+	if err != nil {
56
+		return err
57
+	}
58
+	if sendAuth {
59
+		// Retrieve encoded auth token from the image reference
60
+		// This would be the old image if it didn't change in this update
61
+		image := service.Spec.TaskTemplate.ContainerSpec.Image
62
+		encodedAuth, err := dockerCli.RetrieveAuthTokenFromImage(ctx, image)
63
+		if err != nil {
64
+			return err
65
+		}
66
+		headers["X-Registry-Auth"] = []string{encodedAuth}
67
+	}
68
+
69
+	err = apiClient.ServiceUpdate(ctx, service.ID, service.Version, service.Spec, headers)
53 70
 	if err != nil {
54 71
 		return err
55 72
 	}
... ...
@@ -184,18 +184,21 @@ func deployServices(
184 184
 		if service, exists := existingServiceMap[name]; exists {
185 185
 			fmt.Fprintf(out, "Updating service %s (id: %s)\n", name, service.ID)
186 186
 
187
+			// TODO(nishanttotla): Pass headers with X-Registry-Auth
187 188
 			if err := apiClient.ServiceUpdate(
188 189
 				ctx,
189 190
 				service.ID,
190 191
 				service.Version,
191 192
 				serviceSpec,
193
+				nil,
192 194
 			); err != nil {
193 195
 				return err
194 196
 			}
195 197
 		} else {
196 198
 			fmt.Fprintf(out, "Creating service %s\n", name)
197 199
 
198
-			if _, err := apiClient.ServiceCreate(ctx, serviceSpec); err != nil {
200
+			// TODO(nishanttotla): Pass headers with X-Registry-Auth
201
+			if _, err := apiClient.ServiceCreate(ctx, serviceSpec, nil); err != nil {
199 202
 				return err
200 203
 			}
201 204
 		}
... ...
@@ -14,8 +14,8 @@ type Backend interface {
14 14
 	Update(uint64, types.Spec) error
15 15
 	GetServices(basictypes.ServiceListOptions) ([]types.Service, error)
16 16
 	GetService(string) (types.Service, error)
17
-	CreateService(types.ServiceSpec) (string, error)
18
-	UpdateService(string, uint64, types.ServiceSpec) error
17
+	CreateService(types.ServiceSpec, string) (string, error)
18
+	UpdateService(string, uint64, types.ServiceSpec, string) error
19 19
 	RemoveService(string) error
20 20
 	GetNodes(basictypes.NodeListOptions) ([]types.Node, error)
21 21
 	GetNode(string) (types.Node, error)
... ...
@@ -107,7 +107,10 @@ func (sr *swarmRouter) createService(ctx context.Context, w http.ResponseWriter,
107 107
 		return err
108 108
 	}
109 109
 
110
-	id, err := sr.backend.CreateService(service)
110
+	// Get returns "" if the header does not exist
111
+	encodedAuth := r.Header.Get("X-Registry-Auth")
112
+
113
+	id, err := sr.backend.CreateService(service, encodedAuth)
111 114
 	if err != nil {
112 115
 		logrus.Errorf("Error creating service %s: %v", id, err)
113 116
 		return err
... ...
@@ -130,7 +133,10 @@ func (sr *swarmRouter) updateService(ctx context.Context, w http.ResponseWriter,
130 130
 		return fmt.Errorf("Invalid service version '%s': %s", rawVersion, err.Error())
131 131
 	}
132 132
 
133
-	if err := sr.backend.UpdateService(vars["id"], version, service); err != nil {
133
+	// Get returns "" if the header does not exist
134
+	encodedAuth := r.Header.Get("X-Registry-Auth")
135
+
136
+	if err := sr.backend.UpdateService(vars["id"], version, service, encodedAuth); err != nil {
134 137
 		logrus.Errorf("Error updating service %s: %v", vars["id"], err)
135 138
 		return err
136 139
 	}
... ...
@@ -663,7 +663,7 @@ func (c *Cluster) GetServices(options apitypes.ServiceListOptions) ([]types.Serv
663 663
 }
664 664
 
665 665
 // CreateService creates a new service in a managed swarm cluster.
666
-func (c *Cluster) CreateService(s types.ServiceSpec) (string, error) {
666
+func (c *Cluster) CreateService(s types.ServiceSpec, encodedAuth string) (string, error) {
667 667
 	c.RLock()
668 668
 	defer c.RUnlock()
669 669
 
... ...
@@ -682,6 +682,15 @@ func (c *Cluster) CreateService(s types.ServiceSpec) (string, error) {
682 682
 	if err != nil {
683 683
 		return "", err
684 684
 	}
685
+
686
+	if encodedAuth != "" {
687
+		ctnr := serviceSpec.Task.GetContainer()
688
+		if ctnr == nil {
689
+			return "", fmt.Errorf("service does not use container tasks")
690
+		}
691
+		ctnr.PullOptions = &swarmapi.ContainerSpec_PullOptions{RegistryAuth: encodedAuth}
692
+	}
693
+
685 694
 	r, err := c.client.CreateService(ctx, &swarmapi.CreateServiceRequest{Spec: &serviceSpec})
686 695
 	if err != nil {
687 696
 		return "", err
... ...
@@ -707,7 +716,7 @@ func (c *Cluster) GetService(input string) (types.Service, error) {
707 707
 }
708 708
 
709 709
 // UpdateService updates existing service to match new properties.
710
-func (c *Cluster) UpdateService(serviceID string, version uint64, spec types.ServiceSpec) error {
710
+func (c *Cluster) UpdateService(serviceID string, version uint64, spec types.ServiceSpec, encodedAuth string) error {
711 711
 	c.RLock()
712 712
 	defer c.RUnlock()
713 713
 
... ...
@@ -720,6 +729,26 @@ func (c *Cluster) UpdateService(serviceID string, version uint64, spec types.Ser
720 720
 		return err
721 721
 	}
722 722
 
723
+	if encodedAuth != "" {
724
+		ctnr := serviceSpec.Task.GetContainer()
725
+		if ctnr == nil {
726
+			return fmt.Errorf("service does not use container tasks")
727
+		}
728
+		ctnr.PullOptions = &swarmapi.ContainerSpec_PullOptions{RegistryAuth: encodedAuth}
729
+	} else {
730
+		// this is needed because if the encodedAuth isn't being updated then we
731
+		// shouldn't lose it, and continue to use the one that was already present
732
+		currentService, err := getService(c.getRequestContext(), c.client, serviceID)
733
+		if err != nil {
734
+			return err
735
+		}
736
+		ctnr := currentService.Spec.Task.GetContainer()
737
+		if ctnr == nil {
738
+			return fmt.Errorf("service does not use container tasks")
739
+		}
740
+		serviceSpec.Task.GetContainer().PullOptions = ctnr.PullOptions
741
+	}
742
+
723 743
 	_, err = c.client.UpdateService(
724 744
 		c.getRequestContext(),
725 745
 		&swarmapi.UpdateServiceRequest{
... ...
@@ -44,7 +44,6 @@ func (c *containerAdapter) pullImage(ctx context.Context) error {
44 44
 	var encodedAuthConfig string
45 45
 	if spec.PullOptions != nil {
46 46
 		encodedAuthConfig = spec.PullOptions.RegistryAuth
47
-
48 47
 	}
49 48
 
50 49
 	authConfig := &types.AuthConfig{}
... ...
@@ -60,7 +60,7 @@ clone git golang.org/x/net 2beffdc2e92c8a3027590f898fe88f69af48a3f8 https://gith
60 60
 clone git golang.org/x/sys eb2c74142fd19a79b3f237334c7384d5167b1b46 https://github.com/golang/sys.git
61 61
 clone git github.com/docker/go-units 651fc226e7441360384da338d0fd37f2440ffbe3
62 62
 clone git github.com/docker/go-connections fa2850ff103453a9ad190da0df0af134f0314b3d
63
-clone git github.com/docker/engine-api c57d0447ea1ae71f6dad83c8d8a1215a89869a0c
63
+clone git github.com/docker/engine-api 19b4fb48a86c3318e610e156ec06b684f79ac31d
64 64
 clone git github.com/RackSec/srslog 259aed10dfa74ea2961eddd1d9847619f6e98837
65 65
 clone git github.com/imdario/mergo 0.2.1
66 66
 
... ...
@@ -100,11 +100,11 @@ type NodeAPIClient interface {
100 100
 
101 101
 // ServiceAPIClient defines API client methods for the services
102 102
 type ServiceAPIClient interface {
103
-	ServiceCreate(ctx context.Context, service swarm.ServiceSpec) (types.ServiceCreateResponse, error)
103
+	ServiceCreate(ctx context.Context, service swarm.ServiceSpec, headers map[string][]string) (types.ServiceCreateResponse, error)
104 104
 	ServiceInspectWithRaw(ctx context.Context, serviceID string) (swarm.Service, []byte, error)
105 105
 	ServiceList(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error)
106 106
 	ServiceRemove(ctx context.Context, serviceID string) error
107
-	ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec) error
107
+	ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, headers map[string][]string) error
108 108
 	TaskInspectWithRaw(ctx context.Context, taskID string) (swarm.Task, []byte, error)
109 109
 	TaskList(ctx context.Context, options types.TaskListOptions) ([]swarm.Task, error)
110 110
 }
... ...
@@ -9,9 +9,9 @@ import (
9 9
 )
10 10
 
11 11
 // ServiceCreate creates a new Service.
12
-func (cli *Client) ServiceCreate(ctx context.Context, service swarm.ServiceSpec) (types.ServiceCreateResponse, error) {
12
+func (cli *Client) ServiceCreate(ctx context.Context, service swarm.ServiceSpec, headers map[string][]string) (types.ServiceCreateResponse, error) {
13 13
 	var response types.ServiceCreateResponse
14
-	resp, err := cli.post(ctx, "/services/create", nil, service, nil)
14
+	resp, err := cli.post(ctx, "/services/create", nil, service, headers)
15 15
 	if err != nil {
16 16
 		return response, err
17 17
 	}
... ...
@@ -9,10 +9,10 @@ import (
9 9
 )
10 10
 
11 11
 // ServiceUpdate updates a Service.
12
-func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec) error {
12
+func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, headers map[string][]string) error {
13 13
 	query := url.Values{}
14 14
 	query.Set("version", strconv.FormatUint(version.Index, 10))
15
-	resp, err := cli.post(ctx, "/services/"+serviceID+"/update", query, service, nil)
15
+	resp, err := cli.post(ctx, "/services/"+serviceID+"/update", query, service, headers)
16 16
 	ensureReaderClosed(resp)
17 17
 	return err
18 18
 }