aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/podman/manifest/annotate.go56
-rw-r--r--cmd/podman/manifest/manifest.go6
-rw-r--r--completions/bash/podman27
-rw-r--r--docs/source/markdown/podman-manifest-annotate.1.md61
-rw-r--r--docs/source/markdown/podman-manifest.1.md13
-rw-r--r--libpod/image/manifests.go56
-rw-r--r--pkg/bindings/manifests/manifests.go21
-rw-r--r--pkg/bindings/test/manifests_test.go20
-rw-r--r--pkg/domain/entities/engine_image.go1
-rw-r--r--pkg/domain/entities/manifest.go10
-rw-r--r--pkg/domain/infra/abi/manifest.go39
-rw-r--r--pkg/domain/infra/tunnel/manifest.go29
-rw-r--r--test/e2e/manifest_test.go16
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"`))
+ })
})