diff options
author | Qi Wang <qiwan@redhat.com> | 2020-04-29 10:51:02 -0400 |
---|---|---|
committer | Qi Wang <qiwan@redhat.com> | 2020-05-05 15:01:32 -0400 |
commit | 1090d4d5d99410b5433330516a4ea3f5e5a27375 (patch) | |
tree | 1dde472b1b5b5cf8b6b48dca84bd289a3e581618 | |
parent | fb6eca50ba9e2dc652da0c33c72db70ab9da85e9 (diff) | |
download | podman-1090d4d5d99410b5433330516a4ea3f5e5a27375.tar.gz podman-1090d4d5d99410b5433330516a4ea3f5e5a27375.tar.bz2 podman-1090d4d5d99410b5433330516a4ea3f5e5a27375.zip |
manifest annotate
Signed-off-by: Qi Wang <qiwan@redhat.com>
-rw-r--r-- | cmd/podman/manifest/annotate.go | 56 | ||||
-rw-r--r-- | cmd/podman/manifest/manifest.go | 6 | ||||
-rw-r--r-- | completions/bash/podman | 27 | ||||
-rw-r--r-- | docs/source/markdown/podman-manifest-annotate.1.md | 61 | ||||
-rw-r--r-- | docs/source/markdown/podman-manifest.1.md | 13 | ||||
-rw-r--r-- | libpod/image/manifests.go | 56 | ||||
-rw-r--r-- | pkg/bindings/manifests/manifests.go | 21 | ||||
-rw-r--r-- | pkg/bindings/test/manifests_test.go | 20 | ||||
-rw-r--r-- | pkg/domain/entities/engine_image.go | 1 | ||||
-rw-r--r-- | pkg/domain/entities/manifest.go | 10 | ||||
-rw-r--r-- | pkg/domain/infra/abi/manifest.go | 39 | ||||
-rw-r--r-- | pkg/domain/infra/tunnel/manifest.go | 29 | ||||
-rw-r--r-- | test/e2e/manifest_test.go | 16 |
13 files changed, 346 insertions, 9 deletions
diff --git a/cmd/podman/manifest/annotate.go b/cmd/podman/manifest/annotate.go new file mode 100644 index 000000000..21d4fb747 --- /dev/null +++ b/cmd/podman/manifest/annotate.go @@ -0,0 +1,56 @@ +package manifest + +import ( + "context" + "fmt" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + manifestAnnotateOpts = entities.ManifestAnnotateOptions{} + annotateCmd = &cobra.Command{ + Use: "annotate [flags] LIST IMAGE", + Short: "Add or update information about an entry in a manifest list or image index", + Long: "Adds or updates information about an entry in a manifest list or image index.", + RunE: annotate, + Example: `podman manifest annotate --annotation left=right mylist:v1.11 image:v1.11-amd64`, + Args: cobra.ExactArgs(2), + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: annotateCmd, + Parent: manifestCmd, + }) + flags := annotateCmd.Flags() + flags.StringSliceVar(&manifestAnnotateOpts.Annotation, "annotation", nil, "set an `annotation` for the specified image") + flags.StringVar(&manifestAnnotateOpts.Arch, "arch", "", "override the `architecture` of the specified image") + flags.StringSliceVar(&manifestAnnotateOpts.Features, "features", nil, "override the `features` of the specified image") + flags.StringVar(&manifestAnnotateOpts.OS, "os", "", "override the `OS` of the specified image") + flags.StringSliceVar(&manifestAnnotateOpts.OSFeatures, "os-features", nil, "override the OS `features` of the specified image") + flags.StringVar(&manifestAnnotateOpts.OSVersion, "os-version", "", "override the OS `version` of the specified image") + flags.StringVar(&manifestAnnotateOpts.Variant, "variant", "", "override the `variant` of the specified image") +} + +func annotate(cmd *cobra.Command, args []string) error { + listImageSpec := args[0] + instanceSpec := args[1] + if listImageSpec == "" { + return errors.Errorf(`invalid image name "%s"`, listImageSpec) + } + if instanceSpec == "" { + return errors.Errorf(`invalid image digest "%s"`, instanceSpec) + } + updatedListID, err := registry.ImageEngine().ManifestAnnotate(context.Background(), args, manifestAnnotateOpts) + if err != nil { + return errors.Wrapf(err, "error removing from manifest list %s", listImageSpec) + } + fmt.Printf("%s\n", updatedListID) + return nil +} diff --git a/cmd/podman/manifest/manifest.go b/cmd/podman/manifest/manifest.go index b78879b34..88d264c1f 100644 --- a/cmd/podman/manifest/manifest.go +++ b/cmd/podman/manifest/manifest.go @@ -15,8 +15,10 @@ var ( Long: manifestDescription, TraverseChildren: true, RunE: validate.SubCommandExists, - Example: `podman manifest create localhost/list - podman manifest inspect localhost/list`, + Example: `podman manifest add mylist:v1.11 image:v1.11-amd64 + podman manifest create localhost/list + podman manifest inspect localhost/list + podman manifest annotate --annotation left=right mylist:v1.11 image:v1.11-amd64`, } ) diff --git a/completions/bash/podman b/completions/bash/podman index d6e9408c6..61af7ac59 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -1782,6 +1782,33 @@ _podman_manifest_add() { esac } +_podman_manifest_annotate() { + local options_with_args=" + --annotation + --arch + --features + --os + --os-features + --os-version + --variant + " + + local boolean_options=" + --help + -h + " + + _complete_ "$options_with_args" "$boolean_options" + case "$cur" in + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_complete_images --id + ;; + esac +} + _podman_manifest_create() { local boolean_options=" --all diff --git a/docs/source/markdown/podman-manifest-annotate.1.md b/docs/source/markdown/podman-manifest-annotate.1.md new file mode 100644 index 000000000..4450de7fd --- /dev/null +++ b/docs/source/markdown/podman-manifest-annotate.1.md @@ -0,0 +1,61 @@ +% podman-manifest-annotate(1) + +## NAME +podman\-manifest\-annotate - Add or update information about an entry in a manifest list or image index + +## SYNOPSIS +**podman manifest annotate** [options...] *listnameorindexname* *imagemanifestdigest* + +## DESCRIPTION + +Adds or updates information about an image included in a manifest list or image index. + +## OPTIONS + +**--annotation** *annotation=value* + +Set an annotation on the entry for the specified image. + +**--arch** + +Override the architecture which the list or index records as a requirement for +the image. This is usually automatically retrieved from the image's +configuration information, so it is rarely necessary to use this option. + + +**--features** + +Specify the features list which the list or index records as requirements for +the image. This option is rarely used. + +**--os** + +Override the OS which the list or index records as a requirement for the image. +This is usually automatically retrieved from the image's configuration +information, so it is rarely necessary to use this option. + +**--os-features** + +Specify the OS features list which the list or index records as requirements +for the image. This option is rarely used. + +**--os-version** + +Specify the OS version which the list or index records as a requirement for the +image. This option is rarely used. + +**--variant** + +Specify the variant which the list or index records for the image. This option +is typically used to distinguish between multiple entries which share the same +architecture value, but which expect different versions of its instruction set. + +## EXAMPLE + +``` +podman manifest annotate --arch arm64 --variant v8 mylist:v1.11 sha256:59eec8837a4d942cc19a52b8c09ea75121acc38114a2c68b98983ce9356b8610 +07ec8dc22b5dba3a33c60b68bce28bbd2b905e383fdb32a90708fa5eeac13a07: sha256:59eec8837a4d942cc19a52b8c09ea75121acc38114a2c68b98983ce9356b8610 +``` + +## SEE ALSO +podman(1), podman-manifest(1), podman-manifest-add(1), podman-manifest-create(1), podman-manifest-inspect(1), podman-rmi(1) diff --git a/docs/source/markdown/podman-manifest.1.md b/docs/source/markdown/podman-manifest.1.md index 70d695883..c86035ce3 100644 --- a/docs/source/markdown/podman-manifest.1.md +++ b/docs/source/markdown/podman-manifest.1.md @@ -13,11 +13,12 @@ The `podman manifest` command provides subcommands which can be used to: ## SUBCOMMANDS -| Command | Man Page | Description | -| ------- | ---------------------------------------------------------- | --------------------------------------------------------------------------- | -| add | [podman-manifest-add(1)](podman-manifest-add.1.md) | Add an image to a manifest list or image index. | -| create | [podman-manifest-create(1)](podman-manifest-create.1.md) | Create a manifest list or image index. | -| inspect | [podman-manifest-inspect(1)](podman-manifest-inspect.1.md) | Display a manifest list or image index. | +| Command | Man Page | Description | +| -------- | ------------------------------------------------------------ | --------------------------------------------------------------------------- | +| add | [podman-manifest-add(1)](podman-manifest-add.1.md) | Add an image to a manifest list or image index. | +| annotate | [podman-manifest-annotate(1)](podman-manifest-annotate.1.md) | Add or update information about an entry in a manifest list or image index. | +| create | [podman-manifest-create(1)](podman-manifest-create.1.md) | Create a manifest list or image index. | +| inspect | [podman-manifest-inspect(1)](podman-manifest-inspect.1.md) | Display a manifest list or image index. | ## SEE ALSO -podman(1), podman-manifest-add(1), podman-manifest-create(1), podman-manifest-inspect(1) +podman(1), podman-manifest-add(1), podman-manifest-annotate(1), podman-manifest-create(1), podman-manifest-inspect(1) diff --git a/libpod/image/manifests.go b/libpod/image/manifests.go index 7ca17f86c..59678fdb2 100644 --- a/libpod/image/manifests.go +++ b/libpod/image/manifests.go @@ -24,6 +24,18 @@ type ManifestAddOpts struct { Variant string `json:"variant"` } +// ManifestAnnotateOptions defines the options for +// manifest annotate +type ManifestAnnotateOpts struct { + Annotation map[string]string `json:"annotation"` + Arch string `json:"arch"` + Features []string `json:"features"` + OS string `json:"os"` + OSFeatures []string `json:"os_feature"` + OSVersion string `json:"os_version"` + Variant string `json:"variant"` +} + // InspectManifest returns a dockerized version of the manifest list func (i *Image) InspectManifest() (*manifest.Schema2List, error) { list, err := i.getManifestList() @@ -158,3 +170,47 @@ func (i *Image) PushManifest(dest types.ImageReference, opts manifests.PushOptio _, d, err := list.Push(context.Background(), dest, opts) return d, err } + +// AnnotateManifest updates an image configuration of a manifest list. +func (i *Image) AnnotateManifest(systemContext types.SystemContext, d digest.Digest, opts ManifestAnnotateOpts) (string, error) { + list, err := i.getManifestList() + if err != nil { + return "", err + } + if len(opts.OS) > 0 { + if err := list.SetOS(d, opts.OS); err != nil { + return "", err + } + } + if len(opts.OSVersion) > 0 { + if err := list.SetOSVersion(d, opts.OSVersion); err != nil { + return "", err + } + } + if len(opts.Features) > 0 { + if err := list.SetFeatures(d, opts.Features); err != nil { + return "", err + } + } + if len(opts.OSFeatures) > 0 { + if err := list.SetOSFeatures(d, opts.OSFeatures); err != nil { + return "", err + } + } + if len(opts.Arch) > 0 { + if err := list.SetArchitecture(d, opts.Arch); err != nil { + return "", err + } + } + if len(opts.Variant) > 0 { + if err := list.SetVariant(d, opts.Variant); err != nil { + return "", err + } + } + if len(opts.Annotation) > 0 { + if err := list.SetAnnotations(&d, opts.Annotation); err != nil { + return "", err + } + } + return list.SaveToImage(i.imageruntime.store, i.ID(), nil, "") +} diff --git a/pkg/bindings/manifests/manifests.go b/pkg/bindings/manifests/manifests.go index a8d1e6ca3..b85169410 100644 --- a/pkg/bindings/manifests/manifests.go +++ b/pkg/bindings/manifests/manifests.go @@ -124,3 +124,24 @@ func Push(ctx context.Context, name string, destination *string, all *bool) (str } return idr.ID, response.Process(&idr) } + +// Annotate updates the image configuration of a given manifest list +func Annotate(ctx context.Context, name, digest string, options image.ManifestAnnotateOpts) (string, error) { + var idr handlers.IDResponse + conn, err := bindings.GetClient(ctx) + if err != nil { + return "", err + } + params := url.Values{} + params.Set("digest", digest) + optionsString, err := jsoniter.MarshalToString(options) + if err != nil { + return "", err + } + stringReader := strings.NewReader(optionsString) + response, err := conn.DoRequest(stringReader, http.MethodPost, "/manifests/%s/annotate", params, name) + if err != nil { + return "", err + } + return idr.ID, response.Process(&idr) +} diff --git a/pkg/bindings/test/manifests_test.go b/pkg/bindings/test/manifests_test.go index 4987dfe5b..ddb75865c 100644 --- a/pkg/bindings/test/manifests_test.go +++ b/pkg/bindings/test/manifests_test.go @@ -118,6 +118,26 @@ var _ = Describe("Podman containers ", func() { Expect(len(data.Manifests)).To(BeZero()) }) + It("annotate manifest", func() { + id, err := manifests.Create(bt.conn, []string{"quay.io/libpod/foobar:latest"}, []string{}, nil) + Expect(err).To(BeNil()) + opts := image.ManifestAddOpts{Images: []string{"docker.io/library/alpine:latest"}} + + _, err = manifests.Add(bt.conn, id, opts) + Expect(err).To(BeNil()) + data, err := manifests.Inspect(bt.conn, id) + Expect(err).To(BeNil()) + Expect(len(data.Manifests)).To(BeNumerically("==", 1)) + digest := data.Manifests[0].Digest.String() + annoOpts := image.ManifestAnnotateOpts{OS: "foo"} + _, err = manifests.Annotate(bt.conn, id, digest, annoOpts) + Expect(err).To(BeNil()) + list, err := manifests.Inspect(bt.conn, id) + Expect(err).To(BeNil()) + Expect(len(list.Manifests)).To(BeNumerically("==", 1)) + Expect(list.Manifests[0].Platform.OS).To(Equal("foo")) + }) + It("push manifest", func() { Skip("TODO") }) diff --git a/pkg/domain/entities/engine_image.go b/pkg/domain/entities/engine_image.go index 45686ec79..c46ba815a 100644 --- a/pkg/domain/entities/engine_image.go +++ b/pkg/domain/entities/engine_image.go @@ -29,4 +29,5 @@ type ImageEngine interface { ManifestCreate(ctx context.Context, names, images []string, opts ManifestCreateOptions) (string, error) ManifestInspect(ctx context.Context, name string) ([]byte, error) ManifestAdd(ctx context.Context, opts ManifestAddOptions) (string, error) + ManifestAnnotate(ctx context.Context, names []string, opts ManifestAnnotateOptions) (string, error) } diff --git a/pkg/domain/entities/manifest.go b/pkg/domain/entities/manifest.go index 7316735b0..d92b1dc9b 100644 --- a/pkg/domain/entities/manifest.go +++ b/pkg/domain/entities/manifest.go @@ -14,3 +14,13 @@ type ManifestAddOptions struct { OSVersion string `json:"os_version" schema:"os_version"` Variant string `json:"variant" schema:"variant"` } + +type ManifestAnnotateOptions struct { + Annotation []string `json:"annotation"` + Arch string `json:"arch" schema:"arch"` + Features []string `json:"features" schema:"features"` + OS string `json:"os" schema:"os"` + OSFeatures []string `json:"os_features" schema:"os_features"` + OSVersion string `json:"os_version" schema:"os_version"` + Variant string `json:"variant" schema:"variant"` +} diff --git a/pkg/domain/infra/abi/manifest.go b/pkg/domain/infra/abi/manifest.go index 88331f96c..812507f0a 100644 --- a/pkg/domain/infra/abi/manifest.go +++ b/pkg/domain/infra/abi/manifest.go @@ -14,6 +14,7 @@ import ( libpodImage "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/util" + "github.com/opencontainers/go-digest" "github.com/pkg/errors" ) @@ -71,7 +72,7 @@ func (ir *ImageEngine) ManifestAdd(ctx context.Context, opts entities.ManifestAd } listImage, err := ir.Libpod.ImageRuntime().NewFromLocal(listImageSpec) if err != nil { - return "", errors.Wrapf(err, "error retriving local image from image name %s", listImageSpec) + return "", errors.Wrapf(err, "error retrieving local image from image name %s", listImageSpec) } manifestAddOpts := libpodImage.ManifestAddOpts{ @@ -100,3 +101,39 @@ func (ir *ImageEngine) ManifestAdd(ctx context.Context, opts entities.ManifestAd } return listID, nil } + +// ManifestAnnotate updates an entry of the manifest list +func (ir *ImageEngine) ManifestAnnotate(ctx context.Context, names []string, opts entities.ManifestAnnotateOptions) (string, error) { + listImage, err := ir.Libpod.ImageRuntime().NewFromLocal(names[0]) + if err != nil { + return "", errors.Wrapf(err, "error retreiving local image from image name %s", names[0]) + } + digest, err := digest.Parse(names[1]) + if err != nil { + return "", errors.Errorf(`invalid image digest "%s": %v`, names[1], err) + } + manifestAnnotateOpts := libpodImage.ManifestAnnotateOpts{ + Arch: opts.Arch, + Features: opts.Features, + OS: opts.OS, + OSFeatures: opts.OSFeatures, + OSVersion: opts.OSVersion, + Variant: opts.Variant, + } + if len(opts.Annotation) > 0 { + annotations := make(map[string]string) + for _, annotationSpec := range opts.Annotation { + spec := strings.SplitN(annotationSpec, "=", 2) + if len(spec) != 2 { + return "", errors.Errorf("no value given for annotation %q", spec[0]) + } + annotations[spec[0]] = spec[1] + } + manifestAnnotateOpts.Annotation = annotations + } + updatedListID, err := listImage.AnnotateManifest(*ir.Libpod.SystemContext(), digest, manifestAnnotateOpts) + if err == nil { + return fmt.Sprintf("%s: %s", updatedListID, digest.String()), nil + } + return "", err +} diff --git a/pkg/domain/infra/tunnel/manifest.go b/pkg/domain/infra/tunnel/manifest.go index 18b400533..3d3196019 100644 --- a/pkg/domain/infra/tunnel/manifest.go +++ b/pkg/domain/infra/tunnel/manifest.go @@ -3,6 +3,7 @@ package tunnel import ( "context" "encoding/json" + "fmt" "strings" "github.com/containers/libpod/libpod/image" @@ -62,3 +63,31 @@ func (ir *ImageEngine) ManifestAdd(ctx context.Context, opts entities.ManifestAd } return listID, nil } + +// ManifestAnnotate updates an entry of the manifest list +func (ir *ImageEngine) ManifestAnnotate(ctx context.Context, names []string, opts entities.ManifestAnnotateOptions) (string, error) { + manifestAnnotateOpts := image.ManifestAnnotateOpts{ + Arch: opts.Arch, + Features: opts.Features, + OS: opts.OS, + OSFeatures: opts.OSFeatures, + OSVersion: opts.OSVersion, + Variant: opts.Variant, + } + if len(opts.Annotation) > 0 { + annotations := make(map[string]string) + for _, annotationSpec := range opts.Annotation { + spec := strings.SplitN(annotationSpec, "=", 2) + if len(spec) != 2 { + return "", errors.Errorf("no value given for annotation %q", spec[0]) + } + annotations[spec[0]] = spec[1] + } + manifestAnnotateOpts.Annotation = annotations + } + updatedListID, err := manifests.Annotate(ctx, names[0], names[1], manifestAnnotateOpts) + if err != nil { + return updatedListID, errors.Wrapf(err, "error annotating %s of manifest list %s", names[1], names[0]) + } + return fmt.Sprintf("%s :%s", updatedListID, names[1]), nil +} diff --git a/test/e2e/manifest_test.go b/test/e2e/manifest_test.go index 9b5a24771..be6919bdc 100644 --- a/test/e2e/manifest_test.go +++ b/test/e2e/manifest_test.go @@ -98,4 +98,20 @@ var _ = Describe("Podman manifest", func() { Expect(session.ExitCode()).To(Equal(0)) Expect(session.OutputToString()).To(ContainSubstring(`"os": "bar"`)) }) + + It("podman manifest annotate", func() { + session := podmanTest.Podman([]string{"manifest", "create", "foo"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + session = podmanTest.Podman([]string{"manifest", "add", "foo", imageListInstance}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + session = podmanTest.Podman([]string{"manifest", "annotate", "--arch", "bar", "foo", imageListARM64InstanceDigest}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + session = podmanTest.Podman([]string{"manifest", "inspect", "foo"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(ContainSubstring(`"architecture": "bar"`)) + }) }) |