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  }