container.go 12 KB

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