allow annotations for OCI image index

Signed-off-by: Qasim Sarfraz <qasimsarfraz@microsoft.com>
pull/1965/head
Qasim Sarfraz 1 year ago
parent c010d3de8d
commit 18894a8e3a

@ -1101,7 +1101,7 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
} }
} }
dt, desc, err := itpull.Combine(ctx, srcs) dt, desc, err := itpull.Combine(ctx, srcs, nil)
if err != nil { if err != nil {
return err return err
} }

@ -25,6 +25,7 @@ type createOptions struct {
builder string builder string
files []string files []string
tags []string tags []string
annotations []string
dryrun bool dryrun bool
actionAppend bool actionAppend bool
progress string progress string
@ -82,6 +83,11 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
return errors.Errorf("no repositories specified, please set a reference in tag or source") return errors.Errorf("no repositories specified, please set a reference in tag or source")
} }
ann, err := parseAnnotations(in.annotations)
if err != nil {
return err
}
var defaultRepo *string var defaultRepo *string
if len(repos) == 1 { if len(repos) == 1 {
for repo := range repos { for repo := range repos {
@ -154,7 +160,7 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
} }
} }
dt, desc, err := r.Combine(ctx, srcs) dt, desc, err := r.Combine(ctx, srcs, ann)
if err != nil { if err != nil {
return err return err
} }
@ -264,6 +270,18 @@ func parseSource(in string) (*imagetools.Source, error) {
return &s, nil return &s, nil
} }
func parseAnnotations(in []string) (map[string]string, error) {
out := make(map[string]string)
for _, i := range in {
kv := strings.SplitN(i, "=", 2)
if len(kv) != 2 {
return nil, errors.Errorf("invalid annotation %q, expected key=value", in)
}
out[kv[0]] = kv[1]
}
return out, nil
}
func createCmd(dockerCli command.Cli, opts RootOptions) *cobra.Command { func createCmd(dockerCli command.Cli, opts RootOptions) *cobra.Command {
var options createOptions var options createOptions
@ -283,6 +301,7 @@ func createCmd(dockerCli command.Cli, opts RootOptions) *cobra.Command {
flags.BoolVar(&options.dryrun, "dry-run", false, "Show final image instead of pushing") flags.BoolVar(&options.dryrun, "dry-run", false, "Show final image instead of pushing")
flags.BoolVar(&options.actionAppend, "append", false, "Append to existing manifest") flags.BoolVar(&options.actionAppend, "append", false, "Append to existing manifest")
flags.StringVar(&options.progress, "progress", "auto", `Set type of progress output ("auto", "plain", "tty"). Use plain to show container output`) flags.StringVar(&options.progress, "progress", "auto", `Set type of progress output ("auto", "plain", "tty"). Use plain to show container output`)
flags.StringArrayVarP(&options.annotations, "annotation", "", []string{}, "Add annotation to the image")
return cmd return cmd
} }

@ -11,6 +11,7 @@ Create a new image based on source images
| Name | Type | Default | Description | | Name | Type | Default | Description |
|:---------------------------------|:--------------|:--------|:-----------------------------------------------------------------------------------------| |:---------------------------------|:--------------|:--------|:-----------------------------------------------------------------------------------------|
| `--annotation` | `stringArray` | | Add annotation to the image |
| [`--append`](#append) | | | Append to existing manifest | | [`--append`](#append) | | | Append to existing manifest |
| [`--builder`](#builder) | `string` | | Override the configured builder instance | | [`--builder`](#builder) | `string` | | Override the configured builder instance |
| [`--dry-run`](#dry-run) | | | Show final image instead of pushing | | [`--dry-run`](#dry-run) | | | Show final image instead of pushing |

@ -5,6 +5,7 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"net/url" "net/url"
"regexp"
"strings" "strings"
"github.com/containerd/containerd/content" "github.com/containerd/containerd/content"
@ -13,6 +14,7 @@ import (
"github.com/containerd/containerd/platforms" "github.com/containerd/containerd/platforms"
"github.com/containerd/containerd/remotes" "github.com/containerd/containerd/remotes"
"github.com/docker/distribution/reference" "github.com/docker/distribution/reference"
"github.com/moby/buildkit/exporter/containerimage/exptypes"
"github.com/moby/buildkit/util/contentutil" "github.com/moby/buildkit/util/contentutil"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/specs-go" "github.com/opencontainers/image-spec/specs-go"
@ -26,7 +28,7 @@ type Source struct {
Ref reference.Named Ref reference.Named
} }
func (r *Resolver) Combine(ctx context.Context, srcs []*Source) ([]byte, ocispec.Descriptor, error) { func (r *Resolver) Combine(ctx context.Context, srcs []*Source, ann map[string]string) ([]byte, ocispec.Descriptor, error) {
eg, ctx := errgroup.WithContext(ctx) eg, ctx := errgroup.WithContext(ctx)
dts := make([][]byte, len(srcs)) dts := make([][]byte, len(srcs))
@ -75,7 +77,7 @@ func (r *Resolver) Combine(ctx context.Context, srcs []*Source) ([]byte, ocispec
} }
// on single source, return original bytes // on single source, return original bytes
if len(srcs) == 1 { if len(srcs) == 1 && len(ann) == 0 {
if mt := srcs[0].Desc.MediaType; mt == images.MediaTypeDockerSchema2ManifestList || mt == ocispec.MediaTypeImageIndex { if mt := srcs[0].Desc.MediaType; mt == images.MediaTypeDockerSchema2ManifestList || mt == ocispec.MediaTypeImageIndex {
return dts[0], srcs[0].Desc, nil return dts[0], srcs[0].Desc, nil
} }
@ -138,12 +140,39 @@ func (r *Resolver) Combine(ctx context.Context, srcs []*Source) ([]byte, ocispec
mt = ocispec.MediaTypeImageIndex mt = ocispec.MediaTypeImageIndex
} }
// annotations are only allowed on OCI indexes
indexAnnotation := make(map[string]string)
if mt == ocispec.MediaTypeImageIndex {
annotations, err := parseAnnotations(ann)
if err != nil {
return nil, ocispec.Descriptor{}, err
}
if len(annotations[exptypes.AnnotationIndex]) > 0 {
for k, v := range annotations[exptypes.AnnotationIndex] {
indexAnnotation[k.Key] = v
}
}
if len(annotations[exptypes.AnnotationManifestDescriptor]) > 0 {
for i := 0; i < len(newDescs); i++ {
if newDescs[i].Annotations == nil {
newDescs[i].Annotations = map[string]string{}
}
for k, v := range annotations[exptypes.AnnotationManifestDescriptor] {
if k.Platform == nil || k.PlatformString() == platforms.Format(*newDescs[i].Platform) {
newDescs[i].Annotations[k.Key] = v
}
}
}
}
}
idxBytes, err := json.MarshalIndent(ocispec.Index{ idxBytes, err := json.MarshalIndent(ocispec.Index{
MediaType: mt, MediaType: mt,
Versioned: specs.Versioned{ Versioned: specs.Versioned{
SchemaVersion: 2, SchemaVersion: 2,
}, },
Manifests: newDescs, Manifests: newDescs,
Annotations: indexAnnotation,
}, "", " ") }, "", " ")
if err != nil { if err != nil {
return nil, ocispec.Descriptor{}, errors.Wrap(err, "failed to marshal index") return nil, ocispec.Descriptor{}, errors.Wrap(err, "failed to marshal index")
@ -266,3 +295,52 @@ func detectMediaType(dt []byte) (string, error) {
return images.MediaTypeDockerSchema2ManifestList, nil return images.MediaTypeDockerSchema2ManifestList, nil
} }
func parseAnnotations(ann map[string]string) (map[string]map[exptypes.AnnotationKey]string, error) {
// TODO: use buildkit's annotation parser once it supports setting custom prefix and ":" separator
annotationRegexp := regexp.MustCompile(`^([a-z-]+)(?:\[([A-Za-z0-9_/-]+)\])?:(\S+)$`)
indexAnnotations := make(map[exptypes.AnnotationKey]string)
manifestDescriptorAnnotations := make(map[exptypes.AnnotationKey]string)
for k, v := range ann {
groups := annotationRegexp.FindStringSubmatch(k)
if groups == nil {
return nil, errors.Errorf("invalid annotation format, expected <type>:<key>=<value>, got %q", k)
}
typ, platform, key := groups[1], groups[2], groups[3]
var ociPlatform *ocispec.Platform
if platform != "" {
p, err := platforms.Parse(platform)
if err != nil {
return nil, errors.Wrapf(err, "invalid platform %q", platform)
}
ociPlatform = &p
}
switch typ {
case exptypes.AnnotationIndex:
ak := exptypes.AnnotationKey{
Type: typ,
Platform: ociPlatform,
Key: key,
}
indexAnnotations[ak] = v
case exptypes.AnnotationManifestDescriptor:
ak := exptypes.AnnotationKey{
Type: typ,
Platform: ociPlatform,
Key: key,
}
manifestDescriptorAnnotations[ak] = v
case exptypes.AnnotationManifest:
return nil, errors.Errorf("%q annotations are not supported yet", typ)
case exptypes.AnnotationIndexDescriptor:
return nil, errors.Errorf("%q annotations are invalid while creating an image", typ)
default:
return nil, errors.Errorf("unknown annotation type %q", typ)
}
}
return map[string]map[exptypes.AnnotationKey]string{
exptypes.AnnotationIndex: indexAnnotations,
exptypes.AnnotationManifestDescriptor: manifestDescriptorAnnotations,
}, nil
}

Loading…
Cancel
Save