Browse Source

daemon: add annotations to container HostConfig

Allow clients to set annotations on a container which will applied to
the container's OCI spec.

Signed-off-by: Cory Snider <csnider@mirantis.com>
Cory Snider 2 years ago
parent
commit
0ffaa6c785

+ 5 - 0
api/server/router/container/container_routes.go

@@ -563,6 +563,11 @@ func (s *containerRouter) postContainersCreate(ctx context.Context, w http.Respo
 		hostConfig.ConsoleSize = [2]uint{0, 0}
 	}
 
+	if hostConfig != nil && versions.LessThan(version, "1.43") {
+		// Ignore Annotations because it was added in API v1.43.
+		hostConfig.Annotations = nil
+	}
+
 	var platform *specs.Platform
 	if versions.GreaterThanOrEqualTo(version, "1.41") {
 		if v := r.Form.Get("platform"); v != "" {

+ 11 - 10
api/types/container/hostconfig.go

@@ -377,16 +377,17 @@ type UpdateConfig struct {
 // Portable information *should* appear in Config.
 type HostConfig struct {
 	// Applicable to all platforms
-	Binds           []string      // List of volume bindings for this container
-	ContainerIDFile string        // File (path) where the containerId is written
-	LogConfig       LogConfig     // Configuration of the logs for this container
-	NetworkMode     NetworkMode   // Network mode to use for the container
-	PortBindings    nat.PortMap   // Port mapping between the exposed port (container) and the host
-	RestartPolicy   RestartPolicy // Restart policy to be used for the container
-	AutoRemove      bool          // Automatically remove container when it exits
-	VolumeDriver    string        // Name of the volume driver used to mount volumes
-	VolumesFrom     []string      // List of volumes to take from other container
-	ConsoleSize     [2]uint       // Initial console size (height,width)
+	Binds           []string          // List of volume bindings for this container
+	ContainerIDFile string            // File (path) where the containerId is written
+	LogConfig       LogConfig         // Configuration of the logs for this container
+	NetworkMode     NetworkMode       // Network mode to use for the container
+	PortBindings    nat.PortMap       // Port mapping between the exposed port (container) and the host
+	RestartPolicy   RestartPolicy     // Restart policy to be used for the container
+	AutoRemove      bool              // Automatically remove container when it exits
+	VolumeDriver    string            // Name of the volume driver used to mount volumes
+	VolumesFrom     []string          // List of volumes to take from other container
+	ConsoleSize     [2]uint           // Initial console size (height,width)
+	Annotations     map[string]string `json:",omitempty"` // Arbitrary non-identifying metadata attached to container and provided to the runtime
 
 	// Applicable to UNIX platforms
 	CapAdd          strslice.StrSlice // List of kernel capabilities to add to the container

+ 5 - 0
daemon/container.go

@@ -305,6 +305,11 @@ func validateHostConfig(hostConfig *containertypes.HostConfig) error {
 	if !hostConfig.Isolation.IsValid() {
 		return errors.Errorf("invalid isolation '%s' on %s", hostConfig.Isolation, runtime.GOOS)
 	}
+	for k := range hostConfig.Annotations {
+		if k == "" {
+			return errors.Errorf("invalid Annotations: the empty string is not permitted as an annotation key")
+		}
+	}
 	return nil
 }
 

+ 1 - 0
daemon/oci_linux.go

@@ -1026,6 +1026,7 @@ func (daemon *Daemon) createSpec(ctx context.Context, c *container.Container) (r
 		WithApparmor(c),
 		WithSelinux(c),
 		WithOOMScore(&c.HostConfig.OomScoreAdj),
+		coci.WithAnnotations(c.HostConfig.Annotations),
 	)
 	if daemon.UsesSnapshotter() {
 		s.Root = &specs.Root{

+ 5 - 0
daemon/oci_windows.go

@@ -8,6 +8,7 @@ import (
 	"path/filepath"
 	"strings"
 
+	coci "github.com/containerd/containerd/oci"
 	containertypes "github.com/docker/docker/api/types/container"
 	imagetypes "github.com/docker/docker/api/types/image"
 	"github.com/docker/docker/container"
@@ -37,6 +38,10 @@ func (daemon *Daemon) createSpec(ctx context.Context, c *container.Container) (*
 
 	s := oci.DefaultSpec()
 
+	if err := coci.WithAnnotations(c.HostConfig.Annotations)(ctx, nil, nil, &s); err != nil {
+		return nil, err
+	}
+
 	linkedEnv, err := daemon.setupLinkedContainers(c)
 	if err != nil {
 		return nil, err

+ 7 - 0
docs/api/v1.43.yaml

@@ -976,6 +976,13 @@ definitions:
             items:
               type: "integer"
               minimum: 0
+          Annotations:
+            type: "object"
+            description: |
+              Arbitrary non-identifying metadata attached to container and
+              provided to the runtime when the container is started.
+            additionalProperties:
+              type: "string"
 
           # Applicable to UNIX platforms
           CapAdd:

+ 3 - 1
docs/api/version-history.md

@@ -17,7 +17,9 @@ keywords: "API, Docker, rcli, REST, documentation"
 
 [Docker Engine API v1.43](https://docs.docker.com/engine/api/v1.43/) documentation
 
-* TODO add API changes for v1.43 here when they arrive.
+* `POST /containers/create` now accepts `Annotations` as part of `HostConfig`.
+  Can be used to attach arbitrary metadata to the container, which will also be
+  passed to the runtime when the container is started.
 
 ## v1.42 API changes
 

+ 5 - 0
integration/container/create_test.go

@@ -561,6 +561,11 @@ func TestCreateInvalidHostConfig(t *testing.T) {
 			hc:          containertypes.HostConfig{UTSMode: "invalid"},
 			expectedErr: "Error response from daemon: invalid UTS mode: invalid",
 		},
+		{
+			doc:         "invalid Annotations",
+			hc:          containertypes.HostConfig{Annotations: map[string]string{"": "a"}},
+			expectedErr: "Error response from daemon: invalid Annotations: the empty string is not permitted as an annotation key",
+		},
 	}
 
 	for _, tc := range testCases {

+ 24 - 0
integration/container/inspect_test.go

@@ -47,3 +47,27 @@ func TestInspectCpusetInConfigPre120(t *testing.T) {
 	_, ok = cfg["Cpuset"]
 	assert.Check(t, is.Equal(true, ok), "API version 1.19 expected to include Cpuset in 'Config'")
 }
+
+func TestInspectAnnotations(t *testing.T) {
+	defer setupTest(t)()
+	client := request.NewAPIClient(t)
+	ctx := context.Background()
+
+	annotations := map[string]string{
+		"hello": "world",
+		"foo":   "bar",
+	}
+
+	name := strings.ToLower(t.Name())
+	id := container.Create(ctx, t, client,
+		container.WithName(name),
+		container.WithCmd("true"),
+		func(c *container.TestContainerConfig) {
+			c.HostConfig.Annotations = annotations
+		},
+	)
+
+	inspect, err := client.ContainerInspect(ctx, id)
+	assert.NilError(t, err)
+	assert.Check(t, is.DeepEqual(inspect.HostConfig.Annotations, annotations))
+}