Skip to content

Commit 23fd05e

Browse files
authored
feat: Add annotations option (#238)
Signed-off-by: Arjun Raja Yogidas <[email protected]>
1 parent 82b0ff4 commit 23fd05e

File tree

4 files changed

+121
-13
lines changed

4 files changed

+121
-13
lines changed

api/handlers/container/create.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -108,16 +108,6 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) {
108108
}
109109
}
110110

111-
// Annotations: TODO - available in nerdctl 2.0
112-
// Annotations are passed in as a map of strings,
113-
// but nerdctl expects an array of strings with format [annotations1=VALUE1, annotations2=VALUE2, ...].
114-
// annotations := []string{}
115-
// if req.HostConfig.Annotations != nil {
116-
// for key, val := range req.HostConfig.Annotations {
117-
// annotations = append(annotations, fmt.Sprintf("%s=%s", key, val))
118-
// }
119-
// }
120-
121111
ulimits := []string{}
122112
if req.HostConfig.Ulimits != nil {
123113
for _, ulimit := range req.HostConfig.Ulimits {
@@ -279,8 +269,9 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) {
279269
// #endregion
280270

281271
// #region for metadata flags
282-
Name: name, // container name
283-
Label: labels, // container labels
272+
Name: name, // container name
273+
Label: labels, // container labels
274+
Annotations: translateAnnotations(req.HostConfig.Annotations),
284275
// #endregion
285276

286277
// #region for logging flags
@@ -430,3 +421,12 @@ func translateSysctls(sysctls map[string]string) []string {
430421
}
431422
return result
432423
}
424+
425+
// translateAnnotations converts a map of annotations to a slice of strings in the format "KEY=VALUE".
426+
func translateAnnotations(annotations map[string]string) []string {
427+
var result []string
428+
for key, val := range annotations {
429+
result = append(result, fmt.Sprintf("%s=%s", key, val))
430+
}
431+
return result
432+
}

api/handlers/container/create_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -928,6 +928,52 @@ var _ = Describe("Container Create API ", func() {
928928
Expect(rr.Body).Should(MatchJSON(jsonResponse))
929929
})
930930

931+
It("should set specified annotation", func() {
932+
body := []byte(`{
933+
"Image": "test-image",
934+
"HostConfig": {
935+
"Annotations": {
936+
"com.example.key": "value1"
937+
}
938+
}
939+
}`)
940+
req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body))
941+
942+
// Create a copy of default options and add annotation
943+
expectedCreateOpt := createOpt
944+
expectedCreateOpt.Annotations = []string{"com.example.key=value1"}
945+
946+
service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(expectedCreateOpt), equalTo(netOpt)).Return(
947+
cid, nil)
948+
949+
h.create(rr, req)
950+
Expect(rr).Should(HaveHTTPStatus(http.StatusCreated))
951+
Expect(rr.Body).Should(MatchJSON(jsonResponse))
952+
})
953+
954+
It("should set specified annotation", func() {
955+
body := []byte(`{
956+
"Image": "test-image",
957+
"HostConfig": {
958+
"Annotations": {
959+
"com.example.key": "value1"
960+
}
961+
}
962+
}`)
963+
req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body))
964+
965+
// Create a copy of default options and add annotation
966+
expectedCreateOpt := createOpt
967+
expectedCreateOpt.Annotations = []string{"com.example.key=value1"}
968+
969+
service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(expectedCreateOpt), equalTo(netOpt)).Return(
970+
cid, nil)
971+
972+
h.create(rr, req)
973+
Expect(rr).Should(HaveHTTPStatus(http.StatusCreated))
974+
Expect(rr.Body).Should(MatchJSON(jsonResponse))
975+
})
976+
931977
Context("translate port mappings", func() {
932978
It("should return empty if port mappings is nil", func() {
933979
Expect(translatePortMappings(nil)).Should(BeEmpty())
@@ -1036,6 +1082,38 @@ var _ = Describe("Container Create API ", func() {
10361082
Expect(translateTmpfs(input)).Should(Equal(expected))
10371083
})
10381084
})
1085+
1086+
Context("translate annotations", func() {
1087+
It("should return nil for nil input", func() {
1088+
Expect(translateAnnotations(nil)).Should(BeNil())
1089+
})
1090+
1091+
It("should return empty slice for empty map", func() {
1092+
Expect(translateAnnotations(map[string]string{})).Should(BeEmpty())
1093+
})
1094+
1095+
It("should handle single annotation", func() {
1096+
input := map[string]string{
1097+
"com.example.key": "value1",
1098+
}
1099+
expected := []string{"com.example.key=value1"}
1100+
Expect(translateAnnotations(input)).Should(Equal(expected))
1101+
})
1102+
1103+
It("should handle multiple annotations", func() {
1104+
input := map[string]string{
1105+
"com.example.key1": "value1",
1106+
"com.example.key2": "value2",
1107+
"io.containerd.key": "value3",
1108+
}
1109+
result := translateAnnotations(input)
1110+
Expect(result).Should(ConsistOf(
1111+
"com.example.key1=value1",
1112+
"com.example.key2=value2",
1113+
"io.containerd.key=value3",
1114+
))
1115+
})
1116+
})
10391117
})
10401118
})
10411119

api/types/container_types.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ type ContainerHostConfig struct {
6969
VolumesFrom []string // List of volumes to take from other container
7070
// TODO: VolumeDriver string // Name of the volume driver used to mount volumes
7171
// TODO: ConsoleSize [2]uint // Initial console size (height,width)
72-
// TODO: Annotations map[string]string `json:",omitempty"` // Arbitrary non-identifying metadata attached to container and provided to the runtime
72+
Annotations map[string]string `json:",omitempty"` // Arbitrary non-identifying metadata attached to container and provided to the runtime
7373

7474
// Applicable to UNIX platforms
7575
CapAdd []string // List of kernel capabilities to add to the container

e2e/tests/container_create.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1382,6 +1382,36 @@ func ContainerCreate(opt *option.Option) {
13821382
Expect(ok).Should(BeTrue())
13831383
Expect(readonly).Should(BeTrue())
13841384
})
1385+
1386+
It("should create a container with specified annotation", func() {
1387+
// Define options
1388+
options.Cmd = []string{"sleep", "Infinity"}
1389+
options.HostConfig.Annotations = map[string]string{
1390+
"com.example.key": "test-value",
1391+
}
1392+
1393+
// Create container
1394+
statusCode, ctr := createContainer(uClient, url, testContainerName, options)
1395+
Expect(statusCode).Should(Equal(http.StatusCreated))
1396+
Expect(ctr.ID).ShouldNot(BeEmpty())
1397+
1398+
// Start container
1399+
command.Run(opt, "start", testContainerName)
1400+
1401+
// Inspect using native format to verify annotation
1402+
nativeResp := command.Stdout(opt, "inspect", "--mode=native", testContainerName)
1403+
var nativeInspect []map[string]interface{}
1404+
err := json.Unmarshal(nativeResp, &nativeInspect)
1405+
Expect(err).Should(BeNil())
1406+
Expect(nativeInspect).Should(HaveLen(1))
1407+
1408+
// Verify annotation in container spec
1409+
spec, ok := nativeInspect[0]["Spec"].(map[string]interface{})
1410+
Expect(ok).Should(BeTrue())
1411+
annotations, ok := spec["annotations"].(map[string]interface{})
1412+
Expect(ok).Should(BeTrue())
1413+
Expect(annotations["com.example.key"]).Should(Equal("test-value"))
1414+
})
13851415
})
13861416
}
13871417

0 commit comments

Comments
 (0)