Skip to content

Commit 3552ce5

Browse files
committed
Add field to Container for client-defined data
This field allows a client to store specialized information in the container metadata rather than having to store this itself and keep the data in sync with containerd. Signed-off-by: Brian Goff <cpuguy83@gmail.com>
1 parent d0457b2 commit 3552ce5

File tree

12 files changed

+350
-48
lines changed

12 files changed

+350
-48
lines changed

api/next.pb.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,17 @@ file {
185185
}
186186
json_name: "updatedAt"
187187
}
188+
field {
189+
name: "extensions"
190+
number: 10
191+
label: LABEL_REPEATED
192+
type: TYPE_MESSAGE
193+
type_name: ".google.protobuf.Any"
194+
options {
195+
65001: 0
196+
}
197+
json_name: "extensions"
198+
}
188199
nested_type {
189200
name: "LabelsEntry"
190201
field {

api/services/containers/v1/containers.pb.go

Lines changed: 103 additions & 47 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/services/containers/v1/containers.proto

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,12 @@ message Container {
8484

8585
// UpdatedAt is the last time the container was mutated.
8686
google.protobuf.Timestamp updated_at = 9 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false];
87+
88+
// Extensions allow clients to provide zero or more blobs that are directly
89+
// associated with the container. One may provide protobuf, json, or other
90+
// encoding formats. The primary use of this is to further decorate the
91+
// container object with fields that may be specific to a client integration.
92+
repeated google.protobuf.Any extensions = 10 [(gogoproto.nullable) = false];
8793
}
8894

8995
message GetContainerRequest {

container.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/containerd/containerd/containers"
1313
"github.com/containerd/containerd/errdefs"
1414
"github.com/containerd/containerd/typeurl"
15+
prototypes "github.com/gogo/protobuf/types"
1516
specs "github.com/opencontainers/runtime-spec/specs-go"
1617
"github.com/pkg/errors"
1718
)
@@ -42,6 +43,8 @@ type Container interface {
4243
Labels(context.Context) (map[string]string, error)
4344
// SetLabels sets the provided labels for the container and returns the final label set
4445
SetLabels(context.Context, map[string]string) (map[string]string, error)
46+
// Extensions returns the extensions set on the container
47+
Extensions() []prototypes.Any
4548
}
4649

4750
func containerFromRecord(client *Client, c containers.Container) *container {
@@ -158,6 +161,12 @@ func (c *container) Image(ctx context.Context) (Image, error) {
158161
}, nil
159162
}
160163

164+
func (c *container) Extensions() []prototypes.Any {
165+
c.mu.Lock()
166+
defer c.mu.Unlock()
167+
return c.c.Extensions
168+
}
169+
161170
func (c *container) NewTask(ctx context.Context, ioCreate IOCreation, opts ...NewTaskOpts) (Task, error) {
162171
c.mu.Lock()
163172
defer c.mu.Unlock()

container_opts.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,21 @@ func setSnapshotterIfEmpty(c *containers.Container) {
127127
c.Snapshotter = DefaultSnapshotter
128128
}
129129
}
130+
131+
// WithContainerExtension appends extension data to the container object.
132+
// Use this to decorate the container object with additional data for the client
133+
// integration.
134+
//
135+
// Make sure to register the type of `extension` in the typeurl package via
136+
// `typeurl.Register` otherwise the type data will be inferred, including how
137+
// to encode and decode the object.
138+
func WithContainerExtension(extension interface{}) NewContainerOpts {
139+
return func(ctx context.Context, client *Client, c *containers.Container) error {
140+
any, err := typeurl.MarshalAny(extension)
141+
if err != nil {
142+
return err
143+
}
144+
c.Extensions = append(c.Extensions, *any)
145+
return nil
146+
}
147+
}

container_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
_ "github.com/containerd/containerd/runtime"
1515

1616
"github.com/containerd/containerd/errdefs"
17+
gogotypes "github.com/gogo/protobuf/types"
1718
)
1819

1920
func empty() IOCreation {
@@ -1368,3 +1369,45 @@ func TestContainerMetrics(t *testing.T) {
13681369

13691370
<-statusC
13701371
}
1372+
1373+
func TestContainerExtensions(t *testing.T) {
1374+
t.Parallel()
1375+
1376+
ctx, cancel := testContext()
1377+
defer cancel()
1378+
id := t.Name()
1379+
1380+
client, err := newClient(t, address)
1381+
if err != nil {
1382+
t.Fatal(err)
1383+
}
1384+
defer client.Close()
1385+
1386+
ext := gogotypes.Any{TypeUrl: "test.ext.url", Value: []byte("hello")}
1387+
container, err := client.NewContainer(ctx, id, WithNewSpec(), WithContainerExtension(&ext))
1388+
if err != nil {
1389+
t.Fatal(err)
1390+
}
1391+
defer container.Delete(ctx)
1392+
1393+
checkExt := func(container Container) {
1394+
cExts := container.Extensions()
1395+
if len(cExts) != 1 {
1396+
t.Fatal("expected 1 container extension")
1397+
}
1398+
if cExts[0].TypeUrl != ext.TypeUrl {
1399+
t.Fatalf("got unexpected type url for extension: %s", cExts[0].TypeUrl)
1400+
}
1401+
if !bytes.Equal(cExts[0].Value, ext.Value) {
1402+
t.Fatalf("expected extension value %q, got: %q", ext.Value, cExts[0].Value)
1403+
}
1404+
}
1405+
1406+
checkExt(container)
1407+
1408+
container, err = client.LoadContainer(ctx, container.ID())
1409+
if err != nil {
1410+
t.Fatal(err)
1411+
}
1412+
checkExt(container)
1413+
}

containers/containers.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ type Container struct {
5757

5858
// UpdatedAt is the time at which the container was updated.
5959
UpdatedAt time.Time
60+
61+
// Extensions stores client-specified metadata
62+
Extensions []types.Any
6063
}
6164

6265
type RuntimeInfo struct {

containerstore.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ func containerToProto(container *containers.Container) containersapi.Container {
9797
Spec: container.Spec,
9898
Snapshotter: container.Snapshotter,
9999
SnapshotKey: container.SnapshotKey,
100+
Extensions: container.Extensions,
100101
}
101102
}
102103

@@ -116,6 +117,7 @@ func containerFromProto(containerpb *containersapi.Container) containers.Contain
116117
Spec: containerpb.Spec,
117118
Snapshotter: containerpb.Snapshotter,
118119
SnapshotKey: containerpb.SnapshotKey,
120+
Extensions: containerpb.Extensions,
119121
}
120122
}
121123

metadata/buckets.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ var (
5050
bucketKeySnapshotKey = []byte("snapshotKey")
5151
bucketKeySnapshotter = []byte("snapshotter")
5252
bucketKeyTarget = []byte("target")
53+
bucketKeyExtensions = []byte("extensions")
5354
)
5455

5556
func getBucket(tx *bolt.Tx, keys ...[]byte) *bolt.Bucket {

0 commit comments

Comments
 (0)