container_opts.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  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. "errors"
  18. "fmt"
  19. "github.com/containerd/containerd/containers"
  20. "github.com/containerd/containerd/content"
  21. "github.com/containerd/containerd/errdefs"
  22. "github.com/containerd/containerd/images"
  23. "github.com/containerd/containerd/namespaces"
  24. "github.com/containerd/containerd/oci"
  25. "github.com/containerd/containerd/protobuf"
  26. "github.com/containerd/containerd/snapshots"
  27. "github.com/containerd/typeurl/v2"
  28. "github.com/opencontainers/image-spec/identity"
  29. v1 "github.com/opencontainers/image-spec/specs-go/v1"
  30. )
  31. // DeleteOpts allows the caller to set options for the deletion of a container
  32. type DeleteOpts func(ctx context.Context, client *Client, c containers.Container) error
  33. // NewContainerOpts allows the caller to set additional options when creating a container
  34. type NewContainerOpts func(ctx context.Context, client *Client, c *containers.Container) error
  35. // UpdateContainerOpts allows the caller to set additional options when updating a container
  36. type UpdateContainerOpts func(ctx context.Context, client *Client, c *containers.Container) error
  37. // InfoOpts controls how container metadata is fetched and returned
  38. type InfoOpts func(*InfoConfig)
  39. // InfoConfig specifies how container metadata is fetched
  40. type InfoConfig struct {
  41. // Refresh will to a fetch of the latest container metadata
  42. Refresh bool
  43. }
  44. // WithRuntime allows a user to specify the runtime name and additional options that should
  45. // be used to create tasks for the container
  46. func WithRuntime(name string, options interface{}) NewContainerOpts {
  47. return func(ctx context.Context, client *Client, c *containers.Container) error {
  48. var (
  49. any typeurl.Any
  50. err error
  51. )
  52. if options != nil {
  53. any, err = typeurl.MarshalAny(options)
  54. if err != nil {
  55. return err
  56. }
  57. }
  58. c.Runtime = containers.RuntimeInfo{
  59. Name: name,
  60. Options: any,
  61. }
  62. return nil
  63. }
  64. }
  65. // WithSandbox joins the container to a container group (aka sandbox) from the given ID
  66. // Note: shim runtime must support sandboxes environments.
  67. func WithSandbox(sandboxID string) NewContainerOpts {
  68. return func(ctx context.Context, client *Client, c *containers.Container) error {
  69. c.SandboxID = sandboxID
  70. return nil
  71. }
  72. }
  73. // WithImage sets the provided image as the base for the container
  74. func WithImage(i Image) NewContainerOpts {
  75. return func(ctx context.Context, client *Client, c *containers.Container) error {
  76. c.Image = i.Name()
  77. return nil
  78. }
  79. }
  80. // WithImageName allows setting the image name as the base for the container
  81. func WithImageName(n string) NewContainerOpts {
  82. return func(ctx context.Context, _ *Client, c *containers.Container) error {
  83. c.Image = n
  84. return nil
  85. }
  86. }
  87. // WithContainerLabels sets the provided labels to the container.
  88. // The existing labels are cleared.
  89. // Use WithAdditionalContainerLabels to preserve the existing labels.
  90. func WithContainerLabels(labels map[string]string) NewContainerOpts {
  91. return func(_ context.Context, _ *Client, c *containers.Container) error {
  92. c.Labels = labels
  93. return nil
  94. }
  95. }
  96. // WithImageConfigLabels sets the image config labels on the container.
  97. // The existing labels are cleared as this is expected to be the first
  98. // operation in setting up a container's labels. Use WithAdditionalContainerLabels
  99. // to add/overwrite the existing image config labels.
  100. func WithImageConfigLabels(image Image) NewContainerOpts {
  101. return func(ctx context.Context, _ *Client, c *containers.Container) error {
  102. ic, err := image.Config(ctx)
  103. if err != nil {
  104. return err
  105. }
  106. var (
  107. ociimage v1.Image
  108. config v1.ImageConfig
  109. )
  110. switch ic.MediaType {
  111. case v1.MediaTypeImageConfig, images.MediaTypeDockerSchema2Config:
  112. p, err := content.ReadBlob(ctx, image.ContentStore(), ic)
  113. if err != nil {
  114. return err
  115. }
  116. if err := json.Unmarshal(p, &ociimage); err != nil {
  117. return err
  118. }
  119. config = ociimage.Config
  120. default:
  121. return fmt.Errorf("unknown image config media type %s", ic.MediaType)
  122. }
  123. c.Labels = config.Labels
  124. return nil
  125. }
  126. }
  127. // WithAdditionalContainerLabels adds the provided labels to the container
  128. // The existing labels are preserved as long as they do not conflict with the added labels.
  129. func WithAdditionalContainerLabels(labels map[string]string) NewContainerOpts {
  130. return func(_ context.Context, _ *Client, c *containers.Container) error {
  131. if c.Labels == nil {
  132. c.Labels = labels
  133. return nil
  134. }
  135. for k, v := range labels {
  136. c.Labels[k] = v
  137. }
  138. return nil
  139. }
  140. }
  141. // WithImageStopSignal sets a well-known containerd label (StopSignalLabel)
  142. // on the container for storing the stop signal specified in the OCI image
  143. // config
  144. func WithImageStopSignal(image Image, defaultSignal string) NewContainerOpts {
  145. return func(ctx context.Context, _ *Client, c *containers.Container) error {
  146. if c.Labels == nil {
  147. c.Labels = make(map[string]string)
  148. }
  149. stopSignal, err := GetOCIStopSignal(ctx, image, defaultSignal)
  150. if err != nil {
  151. return err
  152. }
  153. c.Labels[StopSignalLabel] = stopSignal
  154. return nil
  155. }
  156. }
  157. // WithSnapshotter sets the provided snapshotter for use by the container
  158. //
  159. // This option must appear before other snapshotter options to have an effect.
  160. func WithSnapshotter(name string) NewContainerOpts {
  161. return func(ctx context.Context, client *Client, c *containers.Container) error {
  162. c.Snapshotter = name
  163. return nil
  164. }
  165. }
  166. // WithSnapshot uses an existing root filesystem for the container
  167. func WithSnapshot(id string) NewContainerOpts {
  168. return func(ctx context.Context, client *Client, c *containers.Container) error {
  169. // check that the snapshot exists, if not, fail on creation
  170. var err error
  171. c.Snapshotter, err = client.resolveSnapshotterName(ctx, c.Snapshotter)
  172. if err != nil {
  173. return err
  174. }
  175. s, err := client.getSnapshotter(ctx, c.Snapshotter)
  176. if err != nil {
  177. return err
  178. }
  179. if _, err := s.Mounts(ctx, id); err != nil {
  180. return err
  181. }
  182. c.SnapshotKey = id
  183. return nil
  184. }
  185. }
  186. // WithNewSnapshot allocates a new snapshot to be used by the container as the
  187. // root filesystem in read-write mode
  188. func WithNewSnapshot(id string, i Image, opts ...snapshots.Opt) NewContainerOpts {
  189. return func(ctx context.Context, client *Client, c *containers.Container) error {
  190. diffIDs, err := i.RootFS(ctx)
  191. if err != nil {
  192. return err
  193. }
  194. parent := identity.ChainID(diffIDs).String()
  195. c.Snapshotter, err = client.resolveSnapshotterName(ctx, c.Snapshotter)
  196. if err != nil {
  197. return err
  198. }
  199. s, err := client.getSnapshotter(ctx, c.Snapshotter)
  200. if err != nil {
  201. return err
  202. }
  203. parent, err = resolveSnapshotOptions(ctx, client, c.Snapshotter, s, parent, opts...)
  204. if err != nil {
  205. return err
  206. }
  207. if _, err := s.Prepare(ctx, id, parent, opts...); err != nil {
  208. return err
  209. }
  210. c.SnapshotKey = id
  211. c.Image = i.Name()
  212. return nil
  213. }
  214. }
  215. // WithSnapshotCleanup deletes the rootfs snapshot allocated for the container
  216. func WithSnapshotCleanup(ctx context.Context, client *Client, c containers.Container) error {
  217. if c.SnapshotKey != "" {
  218. if c.Snapshotter == "" {
  219. return fmt.Errorf("container.Snapshotter must be set to cleanup rootfs snapshot: %w", errdefs.ErrInvalidArgument)
  220. }
  221. s, err := client.getSnapshotter(ctx, c.Snapshotter)
  222. if err != nil {
  223. return err
  224. }
  225. if err := s.Remove(ctx, c.SnapshotKey); err != nil && !errdefs.IsNotFound(err) {
  226. return err
  227. }
  228. }
  229. return nil
  230. }
  231. // WithNewSnapshotView allocates a new snapshot to be used by the container as the
  232. // root filesystem in read-only mode
  233. func WithNewSnapshotView(id string, i Image, opts ...snapshots.Opt) NewContainerOpts {
  234. return func(ctx context.Context, client *Client, c *containers.Container) error {
  235. diffIDs, err := i.(*image).i.RootFS(ctx, client.ContentStore(), client.platform)
  236. if err != nil {
  237. return err
  238. }
  239. parent := identity.ChainID(diffIDs).String()
  240. c.Snapshotter, err = client.resolveSnapshotterName(ctx, c.Snapshotter)
  241. if err != nil {
  242. return err
  243. }
  244. s, err := client.getSnapshotter(ctx, c.Snapshotter)
  245. if err != nil {
  246. return err
  247. }
  248. parent, err = resolveSnapshotOptions(ctx, client, c.Snapshotter, s, parent, opts...)
  249. if err != nil {
  250. return err
  251. }
  252. if _, err := s.View(ctx, id, parent, opts...); err != nil {
  253. return err
  254. }
  255. c.SnapshotKey = id
  256. c.Image = i.Name()
  257. return nil
  258. }
  259. }
  260. // WithContainerExtension appends extension data to the container object.
  261. // Use this to decorate the container object with additional data for the client
  262. // integration.
  263. //
  264. // Make sure to register the type of `extension` in the typeurl package via
  265. // `typeurl.Register` or container creation may fail.
  266. func WithContainerExtension(name string, extension interface{}) NewContainerOpts {
  267. return func(ctx context.Context, client *Client, c *containers.Container) error {
  268. if name == "" {
  269. return fmt.Errorf("extension key must not be zero-length: %w", errdefs.ErrInvalidArgument)
  270. }
  271. any, err := typeurl.MarshalAny(extension)
  272. if err != nil {
  273. if errors.Is(err, typeurl.ErrNotFound) {
  274. return fmt.Errorf("extension %q is not registered with the typeurl package, see `typeurl.Register`: %w", name, err)
  275. }
  276. return fmt.Errorf("error marshalling extension: %w", err)
  277. }
  278. if c.Extensions == nil {
  279. c.Extensions = make(map[string]typeurl.Any)
  280. }
  281. c.Extensions[name] = any
  282. return nil
  283. }
  284. }
  285. // WithNewSpec generates a new spec for a new container
  286. func WithNewSpec(opts ...oci.SpecOpts) NewContainerOpts {
  287. return func(ctx context.Context, client *Client, c *containers.Container) error {
  288. if _, ok := namespaces.Namespace(ctx); !ok {
  289. ctx = namespaces.WithNamespace(ctx, client.DefaultNamespace())
  290. }
  291. s, err := oci.GenerateSpec(ctx, client, c, opts...)
  292. if err != nil {
  293. return err
  294. }
  295. c.Spec, err = typeurl.MarshalAny(s)
  296. return err
  297. }
  298. }
  299. // WithSpec sets the provided spec on the container
  300. func WithSpec(s *oci.Spec, opts ...oci.SpecOpts) NewContainerOpts {
  301. return func(ctx context.Context, client *Client, c *containers.Container) error {
  302. if err := oci.ApplyOpts(ctx, client, c, s, opts...); err != nil {
  303. return err
  304. }
  305. var err error
  306. c.Spec, err = protobuf.MarshalAnyToProto(s)
  307. return err
  308. }
  309. }
  310. // WithoutRefreshedMetadata will use the current metadata attached to the container object
  311. func WithoutRefreshedMetadata(i *InfoConfig) {
  312. i.Refresh = false
  313. }