github.com/olljanat/moby@v1.13.1/daemon/cluster/executor/container/adapter.go (about) 1 package container 2 3 import ( 4 "encoding/base64" 5 "encoding/json" 6 "fmt" 7 "io" 8 "os" 9 "strings" 10 "syscall" 11 "time" 12 13 "github.com/Sirupsen/logrus" 14 "github.com/docker/distribution/digest" 15 "github.com/docker/docker/api/types" 16 "github.com/docker/docker/api/types/backend" 17 containertypes "github.com/docker/docker/api/types/container" 18 "github.com/docker/docker/api/types/events" 19 "github.com/docker/docker/daemon/cluster/convert" 20 executorpkg "github.com/docker/docker/daemon/cluster/executor" 21 "github.com/docker/docker/reference" 22 "github.com/docker/libnetwork" 23 "github.com/docker/swarmkit/agent/exec" 24 "github.com/docker/swarmkit/api" 25 "github.com/docker/swarmkit/log" 26 "github.com/docker/swarmkit/protobuf/ptypes" 27 "golang.org/x/net/context" 28 "golang.org/x/time/rate" 29 ) 30 31 // containerAdapter conducts remote operations for a container. All calls 32 // are mostly naked calls to the client API, seeded with information from 33 // containerConfig. 34 type containerAdapter struct { 35 backend executorpkg.Backend 36 container *containerConfig 37 secrets exec.SecretGetter 38 } 39 40 func newContainerAdapter(b executorpkg.Backend, task *api.Task, secrets exec.SecretGetter) (*containerAdapter, error) { 41 ctnr, err := newContainerConfig(task) 42 if err != nil { 43 return nil, err 44 } 45 46 return &containerAdapter{ 47 container: ctnr, 48 backend: b, 49 secrets: secrets, 50 }, nil 51 } 52 53 func (c *containerAdapter) pullImage(ctx context.Context) error { 54 spec := c.container.spec() 55 56 // Skip pulling if the image is referenced by image ID. 57 if _, err := digest.ParseDigest(spec.Image); err == nil { 58 return nil 59 } 60 61 // Skip pulling if the image is referenced by digest and already 62 // exists locally. 63 named, err := reference.ParseNamed(spec.Image) 64 if err == nil { 65 if _, ok := named.(reference.Canonical); ok { 66 _, err := c.backend.LookupImage(spec.Image) 67 if err == nil { 68 return nil 69 } 70 } 71 } 72 73 // if the image needs to be pulled, the auth config will be retrieved and updated 74 var encodedAuthConfig string 75 if spec.PullOptions != nil { 76 encodedAuthConfig = spec.PullOptions.RegistryAuth 77 } 78 79 authConfig := &types.AuthConfig{} 80 if encodedAuthConfig != "" { 81 if err := json.NewDecoder(base64.NewDecoder(base64.URLEncoding, strings.NewReader(encodedAuthConfig))).Decode(authConfig); err != nil { 82 logrus.Warnf("invalid authconfig: %v", err) 83 } 84 } 85 86 pr, pw := io.Pipe() 87 metaHeaders := map[string][]string{} 88 go func() { 89 err := c.backend.PullImage(ctx, c.container.image(), "", metaHeaders, authConfig, pw) 90 pw.CloseWithError(err) 91 }() 92 93 dec := json.NewDecoder(pr) 94 dec.UseNumber() 95 m := map[string]interface{}{} 96 spamLimiter := rate.NewLimiter(rate.Every(time.Second), 1) 97 98 lastStatus := "" 99 for { 100 if err := dec.Decode(&m); err != nil { 101 if err == io.EOF { 102 break 103 } 104 return err 105 } 106 l := log.G(ctx) 107 // limit pull progress logs unless the status changes 108 if spamLimiter.Allow() || lastStatus != m["status"] { 109 // if we have progress details, we have everything we need 110 if progress, ok := m["progressDetail"].(map[string]interface{}); ok { 111 // first, log the image and status 112 l = l.WithFields(logrus.Fields{ 113 "image": c.container.image(), 114 "status": m["status"], 115 }) 116 // then, if we have progress, log the progress 117 if progress["current"] != nil && progress["total"] != nil { 118 l = l.WithFields(logrus.Fields{ 119 "current": progress["current"], 120 "total": progress["total"], 121 }) 122 } 123 } 124 l.Debug("pull in progress") 125 } 126 // sometimes, we get no useful information at all, and add no fields 127 if status, ok := m["status"].(string); ok { 128 lastStatus = status 129 } 130 } 131 132 // if the final stream object contained an error, return it 133 if errMsg, ok := m["error"]; ok { 134 return fmt.Errorf("%v", errMsg) 135 } 136 return nil 137 } 138 139 func (c *containerAdapter) createNetworks(ctx context.Context) error { 140 for _, network := range c.container.networks() { 141 ncr, err := c.container.networkCreateRequest(network) 142 if err != nil { 143 return err 144 } 145 146 if err := c.backend.CreateManagedNetwork(ncr); err != nil { // todo name missing 147 if _, ok := err.(libnetwork.NetworkNameError); ok { 148 continue 149 } 150 151 return err 152 } 153 } 154 155 return nil 156 } 157 158 func (c *containerAdapter) removeNetworks(ctx context.Context) error { 159 for _, nid := range c.container.networks() { 160 if err := c.backend.DeleteManagedNetwork(nid); err != nil { 161 switch err.(type) { 162 case *libnetwork.ActiveEndpointsError: 163 continue 164 case libnetwork.ErrNoSuchNetwork: 165 continue 166 default: 167 log.G(ctx).Errorf("network %s remove failed: %v", nid, err) 168 return err 169 } 170 } 171 } 172 173 return nil 174 } 175 176 func (c *containerAdapter) networkAttach(ctx context.Context) error { 177 config := c.container.createNetworkingConfig() 178 179 var ( 180 networkName string 181 networkID string 182 ) 183 184 if config != nil { 185 for n, epConfig := range config.EndpointsConfig { 186 networkName = n 187 networkID = epConfig.NetworkID 188 break 189 } 190 } 191 192 return c.backend.UpdateAttachment(networkName, networkID, c.container.id(), config) 193 } 194 195 func (c *containerAdapter) waitForDetach(ctx context.Context) error { 196 config := c.container.createNetworkingConfig() 197 198 var ( 199 networkName string 200 networkID string 201 ) 202 203 if config != nil { 204 for n, epConfig := range config.EndpointsConfig { 205 networkName = n 206 networkID = epConfig.NetworkID 207 break 208 } 209 } 210 211 return c.backend.WaitForDetachment(ctx, networkName, networkID, c.container.taskID(), c.container.id()) 212 } 213 214 func (c *containerAdapter) create(ctx context.Context) error { 215 var cr containertypes.ContainerCreateCreatedBody 216 var err error 217 218 if cr, err = c.backend.CreateManagedContainer(types.ContainerCreateConfig{ 219 Name: c.container.name(), 220 Config: c.container.config(), 221 HostConfig: c.container.hostConfig(), 222 // Use the first network in container create 223 NetworkingConfig: c.container.createNetworkingConfig(), 224 }); err != nil { 225 return err 226 } 227 228 // Docker daemon currently doesn't support multiple networks in container create 229 // Connect to all other networks 230 nc := c.container.connectNetworkingConfig() 231 232 if nc != nil { 233 for n, ep := range nc.EndpointsConfig { 234 if err := c.backend.ConnectContainerToNetwork(cr.ID, n, ep); err != nil { 235 return err 236 } 237 } 238 } 239 240 container := c.container.task.Spec.GetContainer() 241 if container == nil { 242 return fmt.Errorf("unable to get container from task spec") 243 } 244 245 // configure secrets 246 if err := c.backend.SetContainerSecretStore(cr.ID, c.secrets); err != nil { 247 return err 248 } 249 250 refs := convert.SecretReferencesFromGRPC(container.Secrets) 251 if err := c.backend.SetContainerSecretReferences(cr.ID, refs); err != nil { 252 return err 253 } 254 255 if err := c.backend.UpdateContainerServiceConfig(cr.ID, c.container.serviceConfig()); err != nil { 256 return err 257 } 258 259 return nil 260 } 261 262 // checkMounts ensures that the provided mounts won't have any host-specific 263 // problems at start up. For example, we disallow bind mounts without an 264 // existing path, which slightly different from the container API. 265 func (c *containerAdapter) checkMounts() error { 266 spec := c.container.spec() 267 for _, mount := range spec.Mounts { 268 switch mount.Type { 269 case api.MountTypeBind: 270 if _, err := os.Stat(mount.Source); os.IsNotExist(err) { 271 return fmt.Errorf("invalid bind mount source, source path not found: %s", mount.Source) 272 } 273 } 274 } 275 276 return nil 277 } 278 279 func (c *containerAdapter) start(ctx context.Context) error { 280 if err := c.checkMounts(); err != nil { 281 return err 282 } 283 284 return c.backend.ContainerStart(c.container.name(), nil, "", "") 285 } 286 287 func (c *containerAdapter) inspect(ctx context.Context) (types.ContainerJSON, error) { 288 cs, err := c.backend.ContainerInspectCurrent(c.container.name(), false) 289 if ctx.Err() != nil { 290 return types.ContainerJSON{}, ctx.Err() 291 } 292 if err != nil { 293 return types.ContainerJSON{}, err 294 } 295 return *cs, nil 296 } 297 298 // events issues a call to the events API and returns a channel with all 299 // events. The stream of events can be shutdown by cancelling the context. 300 func (c *containerAdapter) events(ctx context.Context) <-chan events.Message { 301 log.G(ctx).Debugf("waiting on events") 302 buffer, l := c.backend.SubscribeToEvents(time.Time{}, time.Time{}, c.container.eventFilter()) 303 eventsq := make(chan events.Message, len(buffer)) 304 305 for _, event := range buffer { 306 eventsq <- event 307 } 308 309 go func() { 310 defer c.backend.UnsubscribeFromEvents(l) 311 312 for { 313 select { 314 case ev := <-l: 315 jev, ok := ev.(events.Message) 316 if !ok { 317 log.G(ctx).Warnf("unexpected event message: %q", ev) 318 continue 319 } 320 select { 321 case eventsq <- jev: 322 case <-ctx.Done(): 323 return 324 } 325 case <-ctx.Done(): 326 return 327 } 328 } 329 }() 330 331 return eventsq 332 } 333 334 func (c *containerAdapter) wait(ctx context.Context) error { 335 return c.backend.ContainerWaitWithContext(ctx, c.container.nameOrID()) 336 } 337 338 func (c *containerAdapter) shutdown(ctx context.Context) error { 339 // Default stop grace period to nil (daemon will use the stopTimeout of the container) 340 var stopgrace *int 341 spec := c.container.spec() 342 if spec.StopGracePeriod != nil { 343 stopgraceValue := int(spec.StopGracePeriod.Seconds) 344 stopgrace = &stopgraceValue 345 } 346 return c.backend.ContainerStop(c.container.name(), stopgrace) 347 } 348 349 func (c *containerAdapter) terminate(ctx context.Context) error { 350 return c.backend.ContainerKill(c.container.name(), uint64(syscall.SIGKILL)) 351 } 352 353 func (c *containerAdapter) remove(ctx context.Context) error { 354 return c.backend.ContainerRm(c.container.name(), &types.ContainerRmConfig{ 355 RemoveVolume: true, 356 ForceRemove: true, 357 }) 358 } 359 360 func (c *containerAdapter) createVolumes(ctx context.Context) error { 361 // Create plugin volumes that are embedded inside a Mount 362 for _, mount := range c.container.task.Spec.GetContainer().Mounts { 363 if mount.Type != api.MountTypeVolume { 364 continue 365 } 366 367 if mount.VolumeOptions == nil { 368 continue 369 } 370 371 if mount.VolumeOptions.DriverConfig == nil { 372 continue 373 } 374 375 req := c.container.volumeCreateRequest(&mount) 376 377 // Check if this volume exists on the engine 378 if _, err := c.backend.VolumeCreate(req.Name, req.Driver, req.DriverOpts, req.Labels); err != nil { 379 // TODO(amitshukla): Today, volume create through the engine api does not return an error 380 // when the named volume with the same parameters already exists. 381 // It returns an error if the driver name is different - that is a valid error 382 return err 383 } 384 385 } 386 387 return nil 388 } 389 390 func (c *containerAdapter) activateServiceBinding() error { 391 return c.backend.ActivateContainerServiceBinding(c.container.name()) 392 } 393 394 func (c *containerAdapter) deactivateServiceBinding() error { 395 return c.backend.DeactivateContainerServiceBinding(c.container.name()) 396 } 397 398 func (c *containerAdapter) logs(ctx context.Context, options api.LogSubscriptionOptions) (io.ReadCloser, error) { 399 reader, writer := io.Pipe() 400 401 apiOptions := &backend.ContainerLogsConfig{ 402 ContainerLogsOptions: types.ContainerLogsOptions{ 403 Follow: options.Follow, 404 405 // TODO(stevvooe): Parse timestamp out of message. This 406 // absolutely needs to be done before going to production with 407 // this, at it is completely redundant. 408 Timestamps: true, 409 Details: false, // no clue what to do with this, let's just deprecate it. 410 }, 411 OutStream: writer, 412 } 413 414 if options.Since != nil { 415 since, err := ptypes.Timestamp(options.Since) 416 if err != nil { 417 return nil, err 418 } 419 apiOptions.Since = since.Format(time.RFC3339Nano) 420 } 421 422 if options.Tail < 0 { 423 // See protobuf documentation for details of how this works. 424 apiOptions.Tail = fmt.Sprint(-options.Tail - 1) 425 } else if options.Tail > 0 { 426 return nil, fmt.Errorf("tail relative to start of logs not supported via docker API") 427 } 428 429 if len(options.Streams) == 0 { 430 // empty == all 431 apiOptions.ShowStdout, apiOptions.ShowStderr = true, true 432 } else { 433 for _, stream := range options.Streams { 434 switch stream { 435 case api.LogStreamStdout: 436 apiOptions.ShowStdout = true 437 case api.LogStreamStderr: 438 apiOptions.ShowStderr = true 439 } 440 } 441 } 442 443 chStarted := make(chan struct{}) 444 go func() { 445 defer writer.Close() 446 c.backend.ContainerLogs(ctx, c.container.name(), apiOptions, chStarted) 447 }() 448 449 return reader, nil 450 } 451 452 // todo: typed/wrapped errors 453 func isContainerCreateNameConflict(err error) bool { 454 return strings.Contains(err.Error(), "Conflict. The name") 455 } 456 457 func isUnknownContainer(err error) bool { 458 return strings.Contains(err.Error(), "No such container:") 459 } 460 461 func isStoppedContainer(err error) bool { 462 return strings.Contains(err.Error(), "is already stopped") 463 }