github.com/demonoid81/containerd@v1.3.4/container.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package containerd
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"os"
    23  	"path/filepath"
    24  	"strings"
    25  
    26  	"github.com/containerd/containerd/api/services/tasks/v1"
    27  	"github.com/containerd/containerd/api/types"
    28  	tasktypes "github.com/containerd/containerd/api/types/task"
    29  	"github.com/containerd/containerd/cio"
    30  	"github.com/containerd/containerd/containers"
    31  	"github.com/containerd/containerd/errdefs"
    32  	"github.com/containerd/containerd/images"
    33  	"github.com/containerd/containerd/oci"
    34  	"github.com/containerd/containerd/runtime/v2/runc/options"
    35  	"github.com/containerd/containerd/sys"
    36  	"github.com/containerd/typeurl"
    37  	prototypes "github.com/gogo/protobuf/types"
    38  	ver "github.com/opencontainers/image-spec/specs-go"
    39  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    40  	"github.com/pkg/errors"
    41  )
    42  
    43  const (
    44  	checkpointImageNameLabel       = "org.opencontainers.image.ref.name"
    45  	checkpointRuntimeNameLabel     = "io.containerd.checkpoint.runtime"
    46  	checkpointSnapshotterNameLabel = "io.containerd.checkpoint.snapshotter"
    47  )
    48  
    49  // Container is a metadata object for container resources and task creation
    50  type Container interface {
    51  	// ID identifies the container
    52  	ID() string
    53  	// Info returns the underlying container record type
    54  	Info(context.Context, ...InfoOpts) (containers.Container, error)
    55  	// Delete removes the container
    56  	Delete(context.Context, ...DeleteOpts) error
    57  	// NewTask creates a new task based on the container metadata
    58  	NewTask(context.Context, cio.Creator, ...NewTaskOpts) (Task, error)
    59  	// Spec returns the OCI runtime specification
    60  	Spec(context.Context) (*oci.Spec, error)
    61  	// Task returns the current task for the container
    62  	//
    63  	// If cio.Attach options are passed the client will reattach to the IO for the running
    64  	// task. If no task exists for the container a NotFound error is returned
    65  	//
    66  	// Clients must make sure that only one reader is attached to the task and consuming
    67  	// the output from the task's fifos
    68  	Task(context.Context, cio.Attach) (Task, error)
    69  	// Image returns the image that the container is based on
    70  	Image(context.Context) (Image, error)
    71  	// Labels returns the labels set on the container
    72  	Labels(context.Context) (map[string]string, error)
    73  	// SetLabels sets the provided labels for the container and returns the final label set
    74  	SetLabels(context.Context, map[string]string) (map[string]string, error)
    75  	// Extensions returns the extensions set on the container
    76  	Extensions(context.Context) (map[string]prototypes.Any, error)
    77  	// Update a container
    78  	Update(context.Context, ...UpdateContainerOpts) error
    79  	// Checkpoint creates a checkpoint image of the current container
    80  	Checkpoint(context.Context, string, ...CheckpointOpts) (Image, error)
    81  }
    82  
    83  func containerFromRecord(client *Client, c containers.Container) *container {
    84  	return &container{
    85  		client:   client,
    86  		id:       c.ID,
    87  		metadata: c,
    88  	}
    89  }
    90  
    91  var _ = (Container)(&container{})
    92  
    93  type container struct {
    94  	client   *Client
    95  	id       string
    96  	metadata containers.Container
    97  }
    98  
    99  // ID returns the container's unique id
   100  func (c *container) ID() string {
   101  	return c.id
   102  }
   103  
   104  func (c *container) Info(ctx context.Context, opts ...InfoOpts) (containers.Container, error) {
   105  	i := &InfoConfig{
   106  		// default to refreshing the container's local metadata
   107  		Refresh: true,
   108  	}
   109  	for _, o := range opts {
   110  		o(i)
   111  	}
   112  	if i.Refresh {
   113  		metadata, err := c.get(ctx)
   114  		if err != nil {
   115  			return c.metadata, err
   116  		}
   117  		c.metadata = metadata
   118  	}
   119  	return c.metadata, nil
   120  }
   121  
   122  func (c *container) Extensions(ctx context.Context) (map[string]prototypes.Any, error) {
   123  	r, err := c.get(ctx)
   124  	if err != nil {
   125  		return nil, err
   126  	}
   127  	return r.Extensions, nil
   128  }
   129  
   130  func (c *container) Labels(ctx context.Context) (map[string]string, error) {
   131  	r, err := c.get(ctx)
   132  	if err != nil {
   133  		return nil, err
   134  	}
   135  	return r.Labels, nil
   136  }
   137  
   138  func (c *container) SetLabels(ctx context.Context, labels map[string]string) (map[string]string, error) {
   139  	container := containers.Container{
   140  		ID:     c.id,
   141  		Labels: labels,
   142  	}
   143  
   144  	var paths []string
   145  	// mask off paths so we only muck with the labels encountered in labels.
   146  	// Labels not in the passed in argument will be left alone.
   147  	for k := range labels {
   148  		paths = append(paths, strings.Join([]string{"labels", k}, "."))
   149  	}
   150  
   151  	r, err := c.client.ContainerService().Update(ctx, container, paths...)
   152  	if err != nil {
   153  		return nil, err
   154  	}
   155  	return r.Labels, nil
   156  }
   157  
   158  // Spec returns the current OCI specification for the container
   159  func (c *container) Spec(ctx context.Context) (*oci.Spec, error) {
   160  	r, err := c.get(ctx)
   161  	if err != nil {
   162  		return nil, err
   163  	}
   164  	var s oci.Spec
   165  	if err := json.Unmarshal(r.Spec.Value, &s); err != nil {
   166  		return nil, err
   167  	}
   168  	return &s, nil
   169  }
   170  
   171  // Delete deletes an existing container
   172  // an error is returned if the container has running tasks
   173  func (c *container) Delete(ctx context.Context, opts ...DeleteOpts) error {
   174  	if _, err := c.loadTask(ctx, nil); err == nil {
   175  		return errors.Wrapf(errdefs.ErrFailedPrecondition, "cannot delete running task %v", c.id)
   176  	}
   177  	r, err := c.get(ctx)
   178  	if err != nil {
   179  		return err
   180  	}
   181  	for _, o := range opts {
   182  		if err := o(ctx, c.client, r); err != nil {
   183  			return err
   184  		}
   185  	}
   186  	return c.client.ContainerService().Delete(ctx, c.id)
   187  }
   188  
   189  func (c *container) Task(ctx context.Context, attach cio.Attach) (Task, error) {
   190  	return c.loadTask(ctx, attach)
   191  }
   192  
   193  // Image returns the image that the container is based on
   194  func (c *container) Image(ctx context.Context) (Image, error) {
   195  	r, err := c.get(ctx)
   196  	if err != nil {
   197  		return nil, err
   198  	}
   199  	if r.Image == "" {
   200  		return nil, errors.Wrap(errdefs.ErrNotFound, "container not created from an image")
   201  	}
   202  	i, err := c.client.ImageService().Get(ctx, r.Image)
   203  	if err != nil {
   204  		return nil, errors.Wrapf(err, "failed to get image %s for container", r.Image)
   205  	}
   206  	return NewImage(c.client, i), nil
   207  }
   208  
   209  func (c *container) NewTask(ctx context.Context, ioCreate cio.Creator, opts ...NewTaskOpts) (_ Task, err error) {
   210  	i, err := ioCreate(c.id)
   211  	if err != nil {
   212  		return nil, err
   213  	}
   214  	defer func() {
   215  		if err != nil && i != nil {
   216  			i.Cancel()
   217  			i.Close()
   218  		}
   219  	}()
   220  	cfg := i.Config()
   221  	request := &tasks.CreateTaskRequest{
   222  		ContainerID: c.id,
   223  		Terminal:    cfg.Terminal,
   224  		Stdin:       cfg.Stdin,
   225  		Stdout:      cfg.Stdout,
   226  		Stderr:      cfg.Stderr,
   227  	}
   228  	r, err := c.get(ctx)
   229  	if err != nil {
   230  		return nil, err
   231  	}
   232  	if r.SnapshotKey != "" {
   233  		if r.Snapshotter == "" {
   234  			return nil, errors.Wrapf(errdefs.ErrInvalidArgument, "unable to resolve rootfs mounts without snapshotter on container")
   235  		}
   236  
   237  		// get the rootfs from the snapshotter and add it to the request
   238  		s, err := c.client.getSnapshotter(ctx, r.Snapshotter)
   239  		if err != nil {
   240  			return nil, err
   241  		}
   242  		mounts, err := s.Mounts(ctx, r.SnapshotKey)
   243  		if err != nil {
   244  			return nil, err
   245  		}
   246  		for _, m := range mounts {
   247  			request.Rootfs = append(request.Rootfs, &types.Mount{
   248  				Type:    m.Type,
   249  				Source:  m.Source,
   250  				Options: m.Options,
   251  			})
   252  		}
   253  	}
   254  	info := TaskInfo{
   255  		runtime: r.Runtime.Name,
   256  	}
   257  	for _, o := range opts {
   258  		if err := o(ctx, c.client, &info); err != nil {
   259  			return nil, err
   260  		}
   261  	}
   262  	if info.RootFS != nil {
   263  		for _, m := range info.RootFS {
   264  			request.Rootfs = append(request.Rootfs, &types.Mount{
   265  				Type:    m.Type,
   266  				Source:  m.Source,
   267  				Options: m.Options,
   268  			})
   269  		}
   270  	}
   271  	if info.Options != nil {
   272  		any, err := typeurl.MarshalAny(info.Options)
   273  		if err != nil {
   274  			return nil, err
   275  		}
   276  		request.Options = any
   277  	}
   278  	t := &task{
   279  		client: c.client,
   280  		io:     i,
   281  		id:     c.id,
   282  	}
   283  	if info.Checkpoint != nil {
   284  		request.Checkpoint = info.Checkpoint
   285  	}
   286  	response, err := c.client.TaskService().Create(ctx, request)
   287  	if err != nil {
   288  		return nil, errdefs.FromGRPC(err)
   289  	}
   290  	t.pid = response.Pid
   291  	return t, nil
   292  }
   293  
   294  func (c *container) Update(ctx context.Context, opts ...UpdateContainerOpts) error {
   295  	// fetch the current container config before updating it
   296  	r, err := c.get(ctx)
   297  	if err != nil {
   298  		return err
   299  	}
   300  	for _, o := range opts {
   301  		if err := o(ctx, c.client, &r); err != nil {
   302  			return err
   303  		}
   304  	}
   305  	if _, err := c.client.ContainerService().Update(ctx, r); err != nil {
   306  		return errdefs.FromGRPC(err)
   307  	}
   308  	return nil
   309  }
   310  
   311  func (c *container) Checkpoint(ctx context.Context, ref string, opts ...CheckpointOpts) (Image, error) {
   312  	index := &ocispec.Index{
   313  		Versioned: ver.Versioned{
   314  			SchemaVersion: 2,
   315  		},
   316  		Annotations: make(map[string]string),
   317  	}
   318  	copts := &options.CheckpointOptions{
   319  		Exit:                false,
   320  		OpenTcp:             false,
   321  		ExternalUnixSockets: false,
   322  		Terminal:            false,
   323  		FileLocks:           true,
   324  		EmptyNamespaces:     nil,
   325  	}
   326  	info, err := c.Info(ctx)
   327  	if err != nil {
   328  		return nil, err
   329  	}
   330  
   331  	img, err := c.Image(ctx)
   332  	if err != nil {
   333  		return nil, err
   334  	}
   335  
   336  	ctx, done, err := c.client.WithLease(ctx)
   337  	if err != nil {
   338  		return nil, err
   339  	}
   340  	defer done(ctx)
   341  
   342  	// add image name to manifest
   343  	index.Annotations[checkpointImageNameLabel] = img.Name()
   344  	// add runtime info to index
   345  	index.Annotations[checkpointRuntimeNameLabel] = info.Runtime.Name
   346  	// add snapshotter info to index
   347  	index.Annotations[checkpointSnapshotterNameLabel] = info.Snapshotter
   348  
   349  	// process remaining opts
   350  	for _, o := range opts {
   351  		if err := o(ctx, c.client, &info, index, copts); err != nil {
   352  			err = errdefs.FromGRPC(err)
   353  			if !errdefs.IsAlreadyExists(err) {
   354  				return nil, err
   355  			}
   356  		}
   357  	}
   358  
   359  	desc, err := writeIndex(ctx, index, c.client, c.ID()+"index")
   360  	if err != nil {
   361  		return nil, err
   362  	}
   363  	i := images.Image{
   364  		Name:   ref,
   365  		Target: desc,
   366  	}
   367  	checkpoint, err := c.client.ImageService().Create(ctx, i)
   368  	if err != nil {
   369  		return nil, err
   370  	}
   371  
   372  	return NewImage(c.client, checkpoint), nil
   373  }
   374  
   375  func (c *container) loadTask(ctx context.Context, ioAttach cio.Attach) (Task, error) {
   376  	response, err := c.client.TaskService().Get(ctx, &tasks.GetRequest{
   377  		ContainerID: c.id,
   378  	})
   379  	if err != nil {
   380  		err = errdefs.FromGRPC(err)
   381  		if errdefs.IsNotFound(err) {
   382  			return nil, errors.Wrapf(err, "no running task found")
   383  		}
   384  		return nil, err
   385  	}
   386  	var i cio.IO
   387  	if ioAttach != nil && response.Process.Status != tasktypes.StatusUnknown {
   388  		// Do not attach IO for task in unknown state, because there
   389  		// are no fifo paths anyway.
   390  		if i, err = attachExistingIO(response, ioAttach); err != nil {
   391  			return nil, err
   392  		}
   393  	}
   394  	t := &task{
   395  		client: c.client,
   396  		io:     i,
   397  		id:     response.Process.ID,
   398  		pid:    response.Process.Pid,
   399  	}
   400  	return t, nil
   401  }
   402  
   403  func (c *container) get(ctx context.Context) (containers.Container, error) {
   404  	return c.client.ContainerService().Get(ctx, c.id)
   405  }
   406  
   407  // get the existing fifo paths from the task information stored by the daemon
   408  func attachExistingIO(response *tasks.GetResponse, ioAttach cio.Attach) (cio.IO, error) {
   409  	fifoSet := loadFifos(response)
   410  	return ioAttach(fifoSet)
   411  }
   412  
   413  // loadFifos loads the containers fifos
   414  func loadFifos(response *tasks.GetResponse) *cio.FIFOSet {
   415  	fifos := []string{
   416  		response.Process.Stdin,
   417  		response.Process.Stdout,
   418  		response.Process.Stderr,
   419  	}
   420  	closer := func() error {
   421  		var (
   422  			err  error
   423  			dirs = map[string]struct{}{}
   424  		)
   425  		for _, fifo := range fifos {
   426  			if isFifo, _ := sys.IsFifo(fifo); isFifo {
   427  				if rerr := os.Remove(fifo); err == nil {
   428  					err = rerr
   429  				}
   430  				dirs[filepath.Dir(fifo)] = struct{}{}
   431  			}
   432  		}
   433  		for dir := range dirs {
   434  			// we ignore errors here because we don't
   435  			// want to remove the directory if it isn't
   436  			// empty
   437  			os.Remove(dir)
   438  		}
   439  		return err
   440  	}
   441  
   442  	return cio.NewFIFOSet(cio.Config{
   443  		Stdin:    response.Process.Stdin,
   444  		Stdout:   response.Process.Stdout,
   445  		Stderr:   response.Process.Stderr,
   446  		Terminal: response.Process.Terminal,
   447  	}, closer)
   448  }