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 }