Labels are consistently validated across services

* The combined size of a key/value pair cannot exceed 4096 bytes

Signed-off-by: Jess Valarezo <valarezo.jessica@gmail.com>
This commit is contained in:
Jess Valarezo 2017-09-21 15:11:46 -07:00
parent d700a9c35b
commit 18c4322bb3
19 changed files with 189 additions and 8 deletions

View File

@ -65,6 +65,8 @@ type Container struct {
ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
// Labels provides an area to include arbitrary data on containers. // Labels provides an area to include arbitrary data on containers.
// //
// The combined size of a key/value pair cannot exceed 4096 bytes.
//
// Note that to add a new value to this field, read the existing set and // Note that to add a new value to this field, read the existing set and
// include the entire result in the update call. // include the entire result in the update call.
Labels map[string]string `protobuf:"bytes,2,rep,name=labels" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` Labels map[string]string `protobuf:"bytes,2,rep,name=labels" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`

View File

@ -42,6 +42,8 @@ message Container {
// Labels provides an area to include arbitrary data on containers. // Labels provides an area to include arbitrary data on containers.
// //
// The combined size of a key/value pair cannot exceed 4096 bytes.
//
// Note that to add a new value to this field, read the existing set and // Note that to add a new value to this field, read the existing set and
// include the entire result in the update call. // include the entire result in the update call.
map<string, string> labels = 2; map<string, string> labels = 2;

View File

@ -115,7 +115,9 @@ type Info struct {
CreatedAt time.Time `protobuf:"bytes,3,opt,name=created_at,json=createdAt,stdtime" json:"created_at"` CreatedAt time.Time `protobuf:"bytes,3,opt,name=created_at,json=createdAt,stdtime" json:"created_at"`
// UpdatedAt provides the time the info was last updated. // UpdatedAt provides the time the info was last updated.
UpdatedAt time.Time `protobuf:"bytes,4,opt,name=updated_at,json=updatedAt,stdtime" json:"updated_at"` UpdatedAt time.Time `protobuf:"bytes,4,opt,name=updated_at,json=updatedAt,stdtime" json:"updated_at"`
// Labels are arbitrary data on content. // Labels are arbitrary data on snapshots.
//
// The combined size of a key/value pair cannot exceed 4096 bytes.
Labels map[string]string `protobuf:"bytes,5,rep,name=labels" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` Labels map[string]string `protobuf:"bytes,5,rep,name=labels" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
} }
@ -321,7 +323,9 @@ type WriteContentRequest struct {
// If this is empty and the message is not a commit, a response will be // If this is empty and the message is not a commit, a response will be
// returned with the current write state. // returned with the current write state.
Data []byte `protobuf:"bytes,6,opt,name=data,proto3" json:"data,omitempty"` Data []byte `protobuf:"bytes,6,opt,name=data,proto3" json:"data,omitempty"`
// Labels are arbitrary data to set on commit. // Labels are arbitrary data on snapshots.
//
// The combined size of a key/value pair cannot exceed 4096 bytes.
Labels map[string]string `protobuf:"bytes,7,rep,name=labels" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` Labels map[string]string `protobuf:"bytes,7,rep,name=labels" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
} }

View File

@ -87,7 +87,9 @@ message Info {
// UpdatedAt provides the time the info was last updated. // UpdatedAt provides the time the info was last updated.
google.protobuf.Timestamp updated_at = 4 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false]; google.protobuf.Timestamp updated_at = 4 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false];
// Labels are arbitrary data on content. // Labels are arbitrary data on snapshots.
//
// The combined size of a key/value pair cannot exceed 4096 bytes.
map<string, string> labels = 5; map<string, string> labels = 5;
} }
@ -270,7 +272,9 @@ message WriteContentRequest {
// returned with the current write state. // returned with the current write state.
bytes data = 6; bytes data = 6;
// Labels are arbitrary data to set on commit. // Labels are arbitrary data on snapshots.
//
// The combined size of a key/value pair cannot exceed 4096 bytes.
map<string, string> labels = 7; map<string, string> labels = 7;
} }

View File

@ -67,6 +67,7 @@ type Image struct {
// and do not get inherited into the package image in any way. // and do not get inherited into the package image in any way.
// //
// Labels may be updated using the field mask. // Labels may be updated using the field mask.
// The combined size of a key/value pair cannot exceed 4096 bytes.
Labels map[string]string `protobuf:"bytes,2,rep,name=labels" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` Labels map[string]string `protobuf:"bytes,2,rep,name=labels" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
// Target describes the content entry point of the image. // Target describes the content entry point of the image.
Target containerd_types.Descriptor `protobuf:"bytes,3,opt,name=target" json:"target"` Target containerd_types.Descriptor `protobuf:"bytes,3,opt,name=target" json:"target"`

View File

@ -51,6 +51,7 @@ message Image {
// and do not get inherited into the package image in any way. // and do not get inherited into the package image in any way.
// //
// Labels may be updated using the field mask. // Labels may be updated using the field mask.
// The combined size of a key/value pair cannot exceed 4096 bytes.
map<string, string> labels = 2; map<string, string> labels = 2;
// Target describes the content entry point of the image. // Target describes the content entry point of the image.

View File

@ -55,6 +55,8 @@ type Namespace struct {
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
// Labels provides an area to include arbitrary data on namespaces. // Labels provides an area to include arbitrary data on namespaces.
// //
// The combined size of a key/value pair cannot exceed 4096 bytes.
//
// Note that to add a new value to this field, read the existing set and // Note that to add a new value to this field, read the existing set and
// include the entire result in the update call. // include the entire result in the update call.
Labels map[string]string `protobuf:"bytes,2,rep,name=labels" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` Labels map[string]string `protobuf:"bytes,2,rep,name=labels" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`

View File

@ -32,6 +32,8 @@ message Namespace {
// Labels provides an area to include arbitrary data on namespaces. // Labels provides an area to include arbitrary data on namespaces.
// //
// The combined size of a key/value pair cannot exceed 4096 bytes.
//
// Note that to add a new value to this field, read the existing set and // Note that to add a new value to this field, read the existing set and
// include the entire result in the update call. // include the entire result in the update call.
map<string, string> labels = 2; map<string, string> labels = 2;

View File

@ -97,6 +97,8 @@ type PrepareSnapshotRequest struct {
Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"`
Parent string `protobuf:"bytes,3,opt,name=parent,proto3" json:"parent,omitempty"` Parent string `protobuf:"bytes,3,opt,name=parent,proto3" json:"parent,omitempty"`
// Labels are arbitrary data on snapshots. // Labels are arbitrary data on snapshots.
//
// The combined size of a key/value pair cannot exceed 4096 bytes.
Labels map[string]string `protobuf:"bytes,4,rep,name=labels" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` Labels map[string]string `protobuf:"bytes,4,rep,name=labels" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
} }
@ -117,6 +119,8 @@ type ViewSnapshotRequest struct {
Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"`
Parent string `protobuf:"bytes,3,opt,name=parent,proto3" json:"parent,omitempty"` Parent string `protobuf:"bytes,3,opt,name=parent,proto3" json:"parent,omitempty"`
// Labels are arbitrary data on snapshots. // Labels are arbitrary data on snapshots.
//
// The combined size of a key/value pair cannot exceed 4096 bytes.
Labels map[string]string `protobuf:"bytes,4,rep,name=labels" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` Labels map[string]string `protobuf:"bytes,4,rep,name=labels" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
} }
@ -163,6 +167,8 @@ type CommitSnapshotRequest struct {
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
Key string `protobuf:"bytes,3,opt,name=key,proto3" json:"key,omitempty"` Key string `protobuf:"bytes,3,opt,name=key,proto3" json:"key,omitempty"`
// Labels are arbitrary data on snapshots. // Labels are arbitrary data on snapshots.
//
// The combined size of a key/value pair cannot exceed 4096 bytes.
Labels map[string]string `protobuf:"bytes,4,rep,name=labels" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` Labels map[string]string `protobuf:"bytes,4,rep,name=labels" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
} }
@ -188,6 +194,8 @@ type Info struct {
// UpdatedAt provides the time the info was last updated. // UpdatedAt provides the time the info was last updated.
UpdatedAt time.Time `protobuf:"bytes,5,opt,name=updated_at,json=updatedAt,stdtime" json:"updated_at"` UpdatedAt time.Time `protobuf:"bytes,5,opt,name=updated_at,json=updatedAt,stdtime" json:"updated_at"`
// Labels are arbitrary data on snapshots. // Labels are arbitrary data on snapshots.
//
// The combined size of a key/value pair cannot exceed 4096 bytes.
Labels map[string]string `protobuf:"bytes,6,rep,name=labels" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` Labels map[string]string `protobuf:"bytes,6,rep,name=labels" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
} }

View File

@ -29,6 +29,8 @@ message PrepareSnapshotRequest {
string parent = 3; string parent = 3;
// Labels are arbitrary data on snapshots. // Labels are arbitrary data on snapshots.
//
// The combined size of a key/value pair cannot exceed 4096 bytes.
map<string, string> labels = 4; map<string, string> labels = 4;
} }
@ -42,6 +44,8 @@ message ViewSnapshotRequest {
string parent = 3; string parent = 3;
// Labels are arbitrary data on snapshots. // Labels are arbitrary data on snapshots.
//
// The combined size of a key/value pair cannot exceed 4096 bytes.
map<string, string> labels = 4; map<string, string> labels = 4;
} }
@ -69,6 +73,8 @@ message CommitSnapshotRequest {
string key = 3; string key = 3;
// Labels are arbitrary data on snapshots. // Labels are arbitrary data on snapshots.
//
// The combined size of a key/value pair cannot exceed 4096 bytes.
map<string, string> labels = 4; map<string, string> labels = 4;
} }
@ -99,6 +105,8 @@ message Info {
google.protobuf.Timestamp updated_at = 5 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false]; google.protobuf.Timestamp updated_at = 5 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false];
// Labels are arbitrary data on snapshots. // Labels are arbitrary data on snapshots.
//
// The combined size of a key/value pair cannot exceed 4096 bytes.
map<string, string> labels = 6; map<string, string> labels = 6;
} }

View File

@ -108,10 +108,16 @@ var imagesSetLabelsCommand = cli.Command{
Usage: "Set and clear labels for an image.", Usage: "Set and clear labels for an image.",
ArgsUsage: "[flags] <name> [<key>=<value>, ...]", ArgsUsage: "[flags] <name> [<key>=<value>, ...]",
Description: "Set and clear labels for an image.", Description: "Set and clear labels for an image.",
Flags: []cli.Flag{}, Flags: []cli.Flag{
cli.BoolFlag{
Name: "replace-all, r",
Usage: "replace all labels",
},
},
Action: func(clicontext *cli.Context) error { Action: func(clicontext *cli.Context) error {
var ( var (
ctx, cancel = appContext(clicontext) ctx, cancel = appContext(clicontext)
replaceAll = clicontext.Bool("replace-all")
name, labels = objectWithLabelArgs(clicontext) name, labels = objectWithLabelArgs(clicontext)
) )
defer cancel() defer cancel()
@ -131,7 +137,11 @@ var imagesSetLabelsCommand = cli.Command{
) )
for k := range labels { for k := range labels {
fieldpaths = append(fieldpaths, strings.Join([]string{"labels", k}, ".")) if replaceAll {
fieldpaths = append(fieldpaths, "labels")
} else {
fieldpaths = append(fieldpaths, strings.Join([]string{"labels", k}, "."))
}
} }
image := images.Image{ image := images.Image{

23
labels/validate.go Normal file
View File

@ -0,0 +1,23 @@
package labels
import (
"github.com/containerd/containerd/errdefs"
"github.com/pkg/errors"
)
const (
maxSize = 4096
)
func Validate(k, v string) error {
// A label key and value should be under 4096 bytes
if (len(k) + len(v)) > maxSize {
if len(k) > 10 {
k = k[:10]
}
return errors.Wrapf(errdefs.ErrInvalidArgument, "label key and value greater than maximum size (%d bytes), key: %s", maxSize, k)
}
return nil
}

37
labels/validate_test.go Normal file
View File

@ -0,0 +1,37 @@
package labels
import (
"strings"
"testing"
"github.com/containerd/containerd/errdefs"
)
func TestValidLabels(t *testing.T) {
shortStr := "s"
longStr := strings.Repeat("s", maxSize-1)
for key, value := range map[string]string{
"some": "value",
shortStr: longStr,
} {
if err := Validate(key, value); err != nil {
t.Fatalf("unexpected error: %v != nil", err)
}
}
}
func TestInvalidLabels(t *testing.T) {
addOneStr := "s"
maxSizeStr := strings.Repeat("s", maxSize)
for key, value := range map[string]string{
maxSizeStr: addOneStr,
} {
if err := Validate(key, value); err == nil {
t.Fatal("expected invalid error")
} else if !errdefs.IsInvalidArgument(err) {
t.Fatal("error should be an invalid label error")
}
}
}

View File

@ -10,6 +10,7 @@ import (
"github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/filters" "github.com/containerd/containerd/filters"
"github.com/containerd/containerd/identifiers" "github.com/containerd/containerd/identifiers"
"github.com/containerd/containerd/labels"
"github.com/containerd/containerd/metadata/boltutil" "github.com/containerd/containerd/metadata/boltutil"
"github.com/containerd/containerd/namespaces" "github.com/containerd/containerd/namespaces"
"github.com/gogo/protobuf/proto" "github.com/gogo/protobuf/proto"
@ -243,7 +244,13 @@ func validateContainer(container *containers.Container) error {
} }
} }
// labels and image have no validation // image has no validation
for k, v := range container.Labels {
if err := labels.Validate(k, v); err == nil {
return errors.Wrapf(err, "containers.Labels")
}
}
if container.Runtime.Name == "" { if container.Runtime.Name == "" {
return errors.Wrapf(errdefs.ErrInvalidArgument, "container.Runtime.Name must be set") return errors.Wrapf(errdefs.ErrInvalidArgument, "container.Runtime.Name must be set")
} }

View File

@ -10,6 +10,7 @@ import (
"github.com/containerd/containerd/content" "github.com/containerd/containerd/content"
"github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/filters" "github.com/containerd/containerd/filters"
"github.com/containerd/containerd/labels"
"github.com/containerd/containerd/metadata/boltutil" "github.com/containerd/containerd/metadata/boltutil"
"github.com/containerd/containerd/namespaces" "github.com/containerd/containerd/namespaces"
digest "github.com/opencontainers/go-digest" digest "github.com/opencontainers/go-digest"
@ -94,6 +95,9 @@ func (cs *contentStore) Update(ctx context.Context, info content.Info, fieldpath
// Set mutable fields // Set mutable fields
updated.Labels = info.Labels updated.Labels = info.Labels
} }
if err := validateInfo(&updated); err != nil {
return err
}
updated.UpdatedAt = time.Now().UTC() updated.UpdatedAt = time.Now().UTC()
return writeInfo(&updated, bkt) return writeInfo(&updated, bkt)
@ -371,6 +375,10 @@ func (nw *namespacedWriter) commit(ctx context.Context, tx *bolt.Tx, size int64,
return err return err
} }
} }
if err := validateInfo(&base); err != nil {
return err
}
status, err := nw.Writer.Status() status, err := nw.Writer.Status()
if err != nil { if err != nil {
return err return err
@ -446,6 +454,16 @@ func (cs *contentStore) checkAccess(ctx context.Context, dgst digest.Digest) err
}) })
} }
func validateInfo(info *content.Info) error {
for k, v := range info.Labels {
if err := labels.Validate(k, v); err == nil {
return errors.Wrapf(err, "info.Labels")
}
}
return nil
}
func readInfo(info *content.Info, bkt *bolt.Bucket) error { func readInfo(info *content.Info, bkt *bolt.Bucket) error {
if err := boltutil.ReadTimestamps(bkt, &info.CreatedAt, &info.UpdatedAt); err != nil { if err := boltutil.ReadTimestamps(bkt, &info.CreatedAt, &info.UpdatedAt); err != nil {
return err return err

View File

@ -11,6 +11,7 @@ import (
"github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/filters" "github.com/containerd/containerd/filters"
"github.com/containerd/containerd/images" "github.com/containerd/containerd/images"
"github.com/containerd/containerd/labels"
"github.com/containerd/containerd/metadata/boltutil" "github.com/containerd/containerd/metadata/boltutil"
"github.com/containerd/containerd/namespaces" "github.com/containerd/containerd/namespaces"
digest "github.com/opencontainers/go-digest" digest "github.com/opencontainers/go-digest"
@ -101,6 +102,10 @@ func (s *imageStore) Create(ctx context.Context, image images.Image) (images.Ima
return images.Image{}, errors.Wrapf(errdefs.ErrInvalidArgument, "image name is required for create") return images.Image{}, errors.Wrapf(errdefs.ErrInvalidArgument, "image name is required for create")
} }
if err := validateImage(&image); err != nil {
return images.Image{}, err
}
return image, withImagesBucket(s.tx, namespace, func(bkt *bolt.Bucket) error { return image, withImagesBucket(s.tx, namespace, func(bkt *bolt.Bucket) error {
ibkt, err := bkt.CreateBucket([]byte(image.Name)) ibkt, err := bkt.CreateBucket([]byte(image.Name))
if err != nil { if err != nil {
@ -170,6 +175,10 @@ func (s *imageStore) Update(ctx context.Context, image images.Image, fieldpaths
updated = image updated = image
} }
if err := validateImage(&image); err != nil {
return err
}
updated.CreatedAt = createdat updated.CreatedAt = createdat
updated.UpdatedAt = time.Now().UTC() updated.UpdatedAt = time.Now().UTC()
return writeImage(ibkt, &updated) return writeImage(ibkt, &updated)
@ -191,6 +200,16 @@ func (s *imageStore) Delete(ctx context.Context, name string) error {
}) })
} }
func validateImage(image *images.Image) error {
for k, v := range image.Labels {
if err := labels.Validate(k, v); err != nil {
return errors.Wrapf(err, "image.Labels")
}
}
return nil
}
func readImage(image *images.Image, bkt *bolt.Bucket) error { func readImage(image *images.Image, bkt *bolt.Bucket) error {
if err := boltutil.ReadTimestamps(bkt, &image.CreatedAt, &image.UpdatedAt); err != nil { if err := boltutil.ReadTimestamps(bkt, &image.CreatedAt, &image.UpdatedAt); err != nil {
return err return err

View File

@ -5,6 +5,7 @@ import (
"github.com/boltdb/bolt" "github.com/boltdb/bolt"
"github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/errdefs"
l "github.com/containerd/containerd/labels"
"github.com/containerd/containerd/namespaces" "github.com/containerd/containerd/namespaces"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -27,6 +28,12 @@ func (s *namespaceStore) Create(ctx context.Context, namespace string, labels ma
return err return err
} }
for k, v := range labels {
if err := l.Validate(k, v); err != nil {
return errors.Wrapf(err, "namespace.Labels")
}
}
// provides the already exists error. // provides the already exists error.
bkt, err := topbkt.CreateBucket([]byte(namespace)) bkt, err := topbkt.CreateBucket([]byte(namespace))
if err != nil { if err != nil {
@ -70,6 +77,10 @@ func (s *namespaceStore) Labels(ctx context.Context, namespace string) (map[stri
} }
func (s *namespaceStore) SetLabel(ctx context.Context, namespace, key, value string) error { func (s *namespaceStore) SetLabel(ctx context.Context, namespace, key, value string) error {
if err := l.Validate(key, value); err != nil {
return errors.Wrapf(err, "namespace.Labels")
}
return withNamespacesLabelsBucket(s.tx, namespace, func(bkt *bolt.Bucket) error { return withNamespacesLabelsBucket(s.tx, namespace, func(bkt *bolt.Bucket) error {
if value == "" { if value == "" {
return bkt.Delete([]byte(key)) return bkt.Delete([]byte(key))

View File

@ -8,6 +8,7 @@ import (
"github.com/boltdb/bolt" "github.com/boltdb/bolt"
"github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/labels"
"github.com/containerd/containerd/metadata/boltutil" "github.com/containerd/containerd/metadata/boltutil"
"github.com/containerd/containerd/mount" "github.com/containerd/containerd/mount"
"github.com/containerd/containerd/namespaces" "github.com/containerd/containerd/namespaces"
@ -180,6 +181,9 @@ func (s *snapshotter) Update(ctx context.Context, info snapshot.Info, fieldpaths
} else { } else {
local.Labels = info.Labels local.Labels = info.Labels
} }
if err := validateSnapshot(&local); err != nil {
return err
}
local.Updated = time.Now().UTC() local.Updated = time.Now().UTC()
if err := boltutil.WriteTimestamps(sbkt, local.Created, local.Updated); err != nil { if err := boltutil.WriteTimestamps(sbkt, local.Created, local.Updated); err != nil {
@ -257,6 +261,10 @@ func (s *snapshotter) createSnapshot(ctx context.Context, key, parent string, re
} }
} }
if err := validateSnapshot(&base); err != nil {
return nil, err
}
var m []mount.Mount var m []mount.Mount
if err := update(ctx, s.db, func(tx *bolt.Tx) error { if err := update(ctx, s.db, func(tx *bolt.Tx) error {
bkt, err := createSnapshotterBucket(tx, ns, s.name) bkt, err := createSnapshotterBucket(tx, ns, s.name)
@ -328,6 +336,10 @@ func (s *snapshotter) Commit(ctx context.Context, name, key string, opts ...snap
} }
} }
if err := validateSnapshot(&base); err != nil {
return err
}
return update(ctx, s.db, func(tx *bolt.Tx) error { return update(ctx, s.db, func(tx *bolt.Tx) error {
bkt := getSnapshotterBucket(tx, ns, s.name) bkt := getSnapshotterBucket(tx, ns, s.name)
if bkt == nil { if bkt == nil {
@ -502,3 +514,13 @@ func (s *snapshotter) Walk(ctx context.Context, fn func(context.Context, snapsho
return nil return nil
} }
func validateSnapshot(info *snapshot.Info) error {
for k, v := range info.Labels {
if err := labels.Validate(k, v); err != nil {
return errors.Wrapf(err, "info.Labels")
}
}
return nil
}

View File

@ -354,7 +354,7 @@ func (s *Service) Write(session api.Content_WriteServer) (err error) {
// 2. Compress inline. // 2. Compress inline.
// 3. Validate digest and size (maybe). // 3. Validate digest and size (maybe).
// //
// Supporting these two paths is quite awkward but it let's both API // Supporting these two paths is quite awkward but it lets both API
// users use the same writer style for each with a minimum of overhead. // users use the same writer style for each with a minimum of overhead.
if req.Expected != "" { if req.Expected != "" {
if expected != "" && expected != req.Expected { if expected != "" && expected != req.Expected {