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