github.com/fabiokung/docker@v0.11.2-0.20170222101415-4534dcd49497/daemon/cluster/services.go (about) 1 package cluster 2 3 import ( 4 "encoding/base64" 5 "encoding/json" 6 "fmt" 7 "io" 8 "os" 9 "strings" 10 11 "github.com/Sirupsen/logrus" 12 "github.com/docker/distribution/reference" 13 apierrors "github.com/docker/docker/api/errors" 14 apitypes "github.com/docker/docker/api/types" 15 "github.com/docker/docker/api/types/backend" 16 types "github.com/docker/docker/api/types/swarm" 17 "github.com/docker/docker/daemon/cluster/convert" 18 "github.com/docker/docker/daemon/logger" 19 "github.com/docker/docker/pkg/ioutils" 20 "github.com/docker/docker/pkg/stdcopy" 21 swarmapi "github.com/docker/swarmkit/api" 22 gogotypes "github.com/gogo/protobuf/types" 23 "github.com/pkg/errors" 24 "golang.org/x/net/context" 25 ) 26 27 // GetServices returns all services of a managed swarm cluster. 28 func (c *Cluster) GetServices(options apitypes.ServiceListOptions) ([]types.Service, error) { 29 c.mu.RLock() 30 defer c.mu.RUnlock() 31 32 state := c.currentNodeState() 33 if !state.IsActiveManager() { 34 return nil, c.errNoManager(state) 35 } 36 37 filters, err := newListServicesFilters(options.Filters) 38 if err != nil { 39 return nil, err 40 } 41 ctx, cancel := c.getRequestContext() 42 defer cancel() 43 44 r, err := state.controlClient.ListServices( 45 ctx, 46 &swarmapi.ListServicesRequest{Filters: filters}) 47 if err != nil { 48 return nil, err 49 } 50 51 services := []types.Service{} 52 53 for _, service := range r.Services { 54 services = append(services, convert.ServiceFromGRPC(*service)) 55 } 56 57 return services, nil 58 } 59 60 // GetService returns a service based on an ID or name. 61 func (c *Cluster) GetService(input string) (types.Service, error) { 62 c.mu.RLock() 63 defer c.mu.RUnlock() 64 65 state := c.currentNodeState() 66 if !state.IsActiveManager() { 67 return types.Service{}, c.errNoManager(state) 68 } 69 70 ctx, cancel := c.getRequestContext() 71 defer cancel() 72 73 service, err := getService(ctx, state.controlClient, input) 74 if err != nil { 75 return types.Service{}, err 76 } 77 return convert.ServiceFromGRPC(*service), nil 78 } 79 80 // CreateService creates a new service in a managed swarm cluster. 81 func (c *Cluster) CreateService(s types.ServiceSpec, encodedAuth string) (*apitypes.ServiceCreateResponse, error) { 82 c.mu.RLock() 83 defer c.mu.RUnlock() 84 85 state := c.currentNodeState() 86 if !state.IsActiveManager() { 87 return nil, c.errNoManager(state) 88 } 89 90 ctx, cancel := c.getRequestContext() 91 defer cancel() 92 93 err := c.populateNetworkID(ctx, state.controlClient, &s) 94 if err != nil { 95 return nil, err 96 } 97 98 serviceSpec, err := convert.ServiceSpecToGRPC(s) 99 if err != nil { 100 return nil, apierrors.NewBadRequestError(err) 101 } 102 103 ctnr := serviceSpec.Task.GetContainer() 104 if ctnr == nil { 105 return nil, errors.New("service does not use container tasks") 106 } 107 108 if encodedAuth != "" { 109 ctnr.PullOptions = &swarmapi.ContainerSpec_PullOptions{RegistryAuth: encodedAuth} 110 } 111 112 // retrieve auth config from encoded auth 113 authConfig := &apitypes.AuthConfig{} 114 if encodedAuth != "" { 115 if err := json.NewDecoder(base64.NewDecoder(base64.URLEncoding, strings.NewReader(encodedAuth))).Decode(authConfig); err != nil { 116 logrus.Warnf("invalid authconfig: %v", err) 117 } 118 } 119 120 resp := &apitypes.ServiceCreateResponse{} 121 122 // pin image by digest 123 if os.Getenv("DOCKER_SERVICE_PREFER_OFFLINE_IMAGE") != "1" { 124 digestImage, err := c.imageWithDigestString(ctx, ctnr.Image, authConfig) 125 if err != nil { 126 logrus.Warnf("unable to pin image %s to digest: %s", ctnr.Image, err.Error()) 127 resp.Warnings = append(resp.Warnings, fmt.Sprintf("unable to pin image %s to digest: %s", ctnr.Image, err.Error())) 128 } else if ctnr.Image != digestImage { 129 logrus.Debugf("pinning image %s by digest: %s", ctnr.Image, digestImage) 130 ctnr.Image = digestImage 131 } else { 132 logrus.Debugf("creating service using supplied digest reference %s", ctnr.Image) 133 } 134 } 135 136 r, err := state.controlClient.CreateService(ctx, &swarmapi.CreateServiceRequest{Spec: &serviceSpec}) 137 if err != nil { 138 return nil, err 139 } 140 141 resp.ID = r.Service.ID 142 return resp, nil 143 } 144 145 // UpdateService updates existing service to match new properties. 146 func (c *Cluster) UpdateService(serviceIDOrName string, version uint64, spec types.ServiceSpec, encodedAuth string, registryAuthFrom string) (*apitypes.ServiceUpdateResponse, error) { 147 c.mu.RLock() 148 defer c.mu.RUnlock() 149 150 state := c.currentNodeState() 151 if !state.IsActiveManager() { 152 return nil, c.errNoManager(state) 153 } 154 155 ctx, cancel := c.getRequestContext() 156 defer cancel() 157 158 err := c.populateNetworkID(ctx, state.controlClient, &spec) 159 if err != nil { 160 return nil, err 161 } 162 163 serviceSpec, err := convert.ServiceSpecToGRPC(spec) 164 if err != nil { 165 return nil, apierrors.NewBadRequestError(err) 166 } 167 168 currentService, err := getService(ctx, state.controlClient, serviceIDOrName) 169 if err != nil { 170 return nil, err 171 } 172 173 newCtnr := serviceSpec.Task.GetContainer() 174 if newCtnr == nil { 175 return nil, errors.New("service does not use container tasks") 176 } 177 178 if encodedAuth != "" { 179 newCtnr.PullOptions = &swarmapi.ContainerSpec_PullOptions{RegistryAuth: encodedAuth} 180 } else { 181 // this is needed because if the encodedAuth isn't being updated then we 182 // shouldn't lose it, and continue to use the one that was already present 183 var ctnr *swarmapi.ContainerSpec 184 switch registryAuthFrom { 185 case apitypes.RegistryAuthFromSpec, "": 186 ctnr = currentService.Spec.Task.GetContainer() 187 case apitypes.RegistryAuthFromPreviousSpec: 188 if currentService.PreviousSpec == nil { 189 return nil, errors.New("service does not have a previous spec") 190 } 191 ctnr = currentService.PreviousSpec.Task.GetContainer() 192 default: 193 return nil, errors.New("unsupported registryAuthFrom value") 194 } 195 if ctnr == nil { 196 return nil, errors.New("service does not use container tasks") 197 } 198 newCtnr.PullOptions = ctnr.PullOptions 199 // update encodedAuth so it can be used to pin image by digest 200 if ctnr.PullOptions != nil { 201 encodedAuth = ctnr.PullOptions.RegistryAuth 202 } 203 } 204 205 // retrieve auth config from encoded auth 206 authConfig := &apitypes.AuthConfig{} 207 if encodedAuth != "" { 208 if err := json.NewDecoder(base64.NewDecoder(base64.URLEncoding, strings.NewReader(encodedAuth))).Decode(authConfig); err != nil { 209 logrus.Warnf("invalid authconfig: %v", err) 210 } 211 } 212 213 resp := &apitypes.ServiceUpdateResponse{} 214 215 // pin image by digest 216 if os.Getenv("DOCKER_SERVICE_PREFER_OFFLINE_IMAGE") != "1" { 217 digestImage, err := c.imageWithDigestString(ctx, newCtnr.Image, authConfig) 218 if err != nil { 219 logrus.Warnf("unable to pin image %s to digest: %s", newCtnr.Image, err.Error()) 220 resp.Warnings = append(resp.Warnings, fmt.Sprintf("unable to pin image %s to digest: %s", newCtnr.Image, err.Error())) 221 } else if newCtnr.Image != digestImage { 222 logrus.Debugf("pinning image %s by digest: %s", newCtnr.Image, digestImage) 223 newCtnr.Image = digestImage 224 } else { 225 logrus.Debugf("updating service using supplied digest reference %s", newCtnr.Image) 226 } 227 } 228 229 _, err = state.controlClient.UpdateService( 230 ctx, 231 &swarmapi.UpdateServiceRequest{ 232 ServiceID: currentService.ID, 233 Spec: &serviceSpec, 234 ServiceVersion: &swarmapi.Version{ 235 Index: version, 236 }, 237 }, 238 ) 239 240 return resp, err 241 } 242 243 // RemoveService removes a service from a managed swarm cluster. 244 func (c *Cluster) RemoveService(input string) error { 245 c.mu.RLock() 246 defer c.mu.RUnlock() 247 248 state := c.currentNodeState() 249 if !state.IsActiveManager() { 250 return c.errNoManager(state) 251 } 252 253 ctx, cancel := c.getRequestContext() 254 defer cancel() 255 256 service, err := getService(ctx, state.controlClient, input) 257 if err != nil { 258 return err 259 } 260 261 _, err = state.controlClient.RemoveService(ctx, &swarmapi.RemoveServiceRequest{ServiceID: service.ID}) 262 return err 263 } 264 265 // ServiceLogs collects service logs and writes them back to `config.OutStream` 266 func (c *Cluster) ServiceLogs(ctx context.Context, input string, config *backend.ContainerLogsConfig, started chan struct{}) error { 267 c.mu.RLock() 268 state := c.currentNodeState() 269 if !state.IsActiveManager() { 270 c.mu.RUnlock() 271 return c.errNoManager(state) 272 } 273 274 service, err := getService(ctx, state.controlClient, input) 275 if err != nil { 276 c.mu.RUnlock() 277 return err 278 } 279 280 stream, err := state.logsClient.SubscribeLogs(ctx, &swarmapi.SubscribeLogsRequest{ 281 Selector: &swarmapi.LogSelector{ 282 ServiceIDs: []string{service.ID}, 283 }, 284 Options: &swarmapi.LogSubscriptionOptions{ 285 Follow: config.Follow, 286 }, 287 }) 288 if err != nil { 289 c.mu.RUnlock() 290 return err 291 } 292 293 wf := ioutils.NewWriteFlusher(config.OutStream) 294 defer wf.Close() 295 close(started) 296 wf.Flush() 297 298 outStream := stdcopy.NewStdWriter(wf, stdcopy.Stdout) 299 errStream := stdcopy.NewStdWriter(wf, stdcopy.Stderr) 300 301 // Release the lock before starting the stream. 302 c.mu.RUnlock() 303 for { 304 // Check the context before doing anything. 305 select { 306 case <-ctx.Done(): 307 return ctx.Err() 308 default: 309 } 310 311 subscribeMsg, err := stream.Recv() 312 if err == io.EOF { 313 return nil 314 } 315 if err != nil { 316 return err 317 } 318 319 for _, msg := range subscribeMsg.Messages { 320 data := []byte{} 321 322 if config.Timestamps { 323 ts, err := gogotypes.TimestampFromProto(msg.Timestamp) 324 if err != nil { 325 return err 326 } 327 data = append(data, []byte(ts.Format(logger.TimeFormat)+" ")...) 328 } 329 330 data = append(data, []byte(fmt.Sprintf("%s.node.id=%s,%s.service.id=%s,%s.task.id=%s ", 331 contextPrefix, msg.Context.NodeID, 332 contextPrefix, msg.Context.ServiceID, 333 contextPrefix, msg.Context.TaskID, 334 ))...) 335 336 data = append(data, msg.Data...) 337 338 switch msg.Stream { 339 case swarmapi.LogStreamStdout: 340 outStream.Write(data) 341 case swarmapi.LogStreamStderr: 342 errStream.Write(data) 343 } 344 } 345 } 346 } 347 348 // imageWithDigestString takes an image such as name or name:tag 349 // and returns the image pinned to a digest, such as name@sha256:34234 350 func (c *Cluster) imageWithDigestString(ctx context.Context, image string, authConfig *apitypes.AuthConfig) (string, error) { 351 ref, err := reference.ParseAnyReference(image) 352 if err != nil { 353 return "", err 354 } 355 namedRef, ok := ref.(reference.Named) 356 if !ok { 357 if _, ok := ref.(reference.Digested); ok { 358 return "", errors.New("image reference is an image ID") 359 } 360 return "", errors.Errorf("unknown image reference format: %s", image) 361 } 362 // only query registry if not a canonical reference (i.e. with digest) 363 if _, ok := namedRef.(reference.Canonical); !ok { 364 namedRef = reference.TagNameOnly(namedRef) 365 366 taggedRef, ok := namedRef.(reference.NamedTagged) 367 if !ok { 368 return "", errors.Errorf("image reference not tagged: %s", image) 369 } 370 371 repo, _, err := c.config.Backend.GetRepository(ctx, taggedRef, authConfig) 372 if err != nil { 373 return "", err 374 } 375 dscrptr, err := repo.Tags(ctx).Get(ctx, taggedRef.Tag()) 376 if err != nil { 377 return "", err 378 } 379 380 namedDigestedRef, err := reference.WithDigest(taggedRef, dscrptr.Digest) 381 if err != nil { 382 return "", err 383 } 384 // return familiar form until interface updated to return type 385 return reference.FamiliarString(namedDigestedRef), nil 386 } 387 // reference already contains a digest, so just return it 388 return reference.FamiliarString(ref), nil 389 }