github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/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 }