Merge pull request #6063 from QiWang19/manifest-annotate

manifest annotate
This commit is contained in:
OpenShift Merge Robot 2020-05-06 03:41:09 +02:00 committed by GitHub
commit 7885b5cd52
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 346 additions and 9 deletions

View File

@ -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
}

View File

@ -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`,
}
)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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, "")
}

View File

@ -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)
}

View File

@ -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")
})

View File

@ -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)
}

View File

@ -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"`
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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"`))
})
})