Manifest remove, push

Implements podman manifest remove and podman manifest push.

Signed-off-by: Qi Wang <qiwan@redhat.com>
This commit is contained in:
Qi Wang 2020-04-23 15:36:47 -04:00
parent 0eb905ff2c
commit 5621f5199d
15 changed files with 452 additions and 6 deletions

View File

@ -18,7 +18,9 @@ var (
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`,
podman manifest annotate --annotation left=right mylist:v1.11 image:v1.11-amd64
podman manifest push mylist:v1.11 quay.io/myimagelist
podman manifest remove mylist:v1.11 sha256:15352d97781ffdf357bf3459c037be3efac4133dc9070c2dce7eca7c05c3e736`,
}
)

View File

@ -0,0 +1,66 @@
package manifest
import (
"context"
"github.com/containers/common/pkg/auth"
"github.com/containers/libpod/cmd/podman/registry"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
var (
manifestPushOpts = entities.ManifestPushOptions{}
pushCmd = &cobra.Command{
Use: "push [flags] SOURCE DESTINATION",
Short: "Push a manifest list or image index to a registry",
Long: "Pushes manifest lists and image indexes to registries.",
RunE: push,
Example: `podman manifest push mylist:v1.11 quay.io/myimagelist`,
Args: cobra.ExactArgs(2),
}
)
func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
Command: pushCmd,
Parent: manifestCmd,
})
flags := pushCmd.Flags()
flags.BoolVar(&manifestPushOpts.Purge, "purge", false, "remove the manifest list if push succeeds")
flags.BoolVar(&manifestPushOpts.All, "all", false, "also push the images in the list")
flags.StringVar(&manifestPushOpts.Authfile, "authfile", auth.GetDefaultAuthFile(), "path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override")
flags.StringVar(&manifestPushOpts.CertDir, "cert-dir", "", "use certificates at the specified path to access the registry")
flags.StringVar(&manifestPushOpts.Creds, "creds", "", "use `[username[:password]]` for accessing the registry")
flags.StringVar(&manifestPushOpts.DigestFile, "digestfile", "", "after copying the image, write the digest of the resulting digest to the file")
flags.StringVarP(&manifestPushOpts.Format, "format", "f", "", "manifest type (oci or v2s2) to attempt to use when pushing the manifest list (default is manifest type of source)")
flags.BoolVarP(&manifestPushOpts.RemoveSignatures, "remove-signatures", "", false, "don't copy signatures when pushing images")
flags.StringVar(&manifestPushOpts.SignBy, "sign-by", "", "sign the image using a GPG key with the specified `FINGERPRINT`")
flags.BoolVar(&manifestPushOpts.TlsVerify, "tls-verify", true, "require HTTPS and verify certificates when accessing the registry")
flags.BoolVarP(&manifestPushOpts.Quiet, "quiet", "q", false, "don't output progress information when pushing lists")
if registry.IsRemote() {
_ = flags.MarkHidden("authfile")
_ = flags.MarkHidden("cert-dir")
_ = flags.MarkHidden("tls-verify")
}
}
func push(cmd *cobra.Command, args []string) error {
if err := auth.CheckAuthFile(manifestPushOpts.Authfile); err != nil {
return err
}
listImageSpec := args[0]
destSpec := args[1]
if listImageSpec == "" {
return errors.Errorf(`invalid image name "%s"`, listImageSpec)
}
if destSpec == "" {
return errors.Errorf(`invalid destination "%s"`, destSpec)
}
if err := registry.ImageEngine().ManifestPush(context.Background(), args, manifestPushOpts); err != nil {
return errors.Wrapf(err, "error pushing manifest %s to %s", listImageSpec, destSpec)
}
return nil
}

View File

@ -0,0 +1,47 @@
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 (
removeCmd = &cobra.Command{
Use: "remove [flags] LIST IMAGE",
Short: "Remove an entry from a manifest list or image index",
Long: "Removes an image from a manifest list or image index.",
RunE: remove,
Example: `podman manifest remove mylist:v1.11 sha256:15352d97781ffdf357bf3459c037be3efac4133dc9070c2dce7eca7c05c3e736`,
Args: cobra.ExactArgs(2),
}
)
func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
Command: removeCmd,
Parent: manifestCmd,
})
}
func remove(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().ManifestRemove(context.Background(), args)
if err != nil {
return errors.Wrapf(err, "error removing from manifest list %s", listImageSpec)
}
fmt.Printf("%s\n", updatedListID)
return nil
}

View File

@ -1742,7 +1742,9 @@ _podman_manifest() {
add
create
inspect
"
push
remove
"
__podman_subcommands "$subcommands" && return
case "$cur" in
@ -1838,6 +1840,57 @@ _podman_manifest_inspect() {
esac
}
_podman_manifest_push() {
local options_with_args="
--authfile
--cert-dir
--creds
--digestfile
--format
-f
--sign-by
--signature-policy,
"
local boolean_options="
--all
--purge
--help
-h
--remove-signatures
--tls-verify
--quiet
"
_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_remove() {
local options_with_args="
"
local boolean_options="
"
_complete_ "$options_with_args" "$boolean_options"
case "$cur" in
-*)
COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
;;
*)
__podman_complete_images --id
;;
esac
}
_podman_pull() {
local options_with_args="
--authfile

View File

@ -73,4 +73,4 @@ podman manifest add --arch arm64 --variant v8 mylist:v1.11 docker://71c201d10fff
```
## SEE ALSO
podman(1), podman-manifest(1), podman-manifest-create(1), podman-manifest-inspect(1), podman-rmi(1)
podman(1), podman-manifest(1), podman-manifest-create(1), podman-manifest-inspect(1), podman-manifest-push(1), podman-manifest-remove(1), podman-rmi(1)

View File

@ -40,4 +40,4 @@ podman manifest create --all mylist:v1.11 docker://fedora
```
## SEE ALSO
podman(1), podman-manifest(1), podman-manifest-add(1), podman-manifest-inspect(1), podman-rmi(1)
podman(1), podman-manifest(1), podman-manifest-add(1), podman-manifest-inspect(1), podman-manifest-push(1), podman-manifest-remove(1), podman-rmi(1)

View File

@ -21,4 +21,4 @@ podman manifest inspect mylist:v1.11
```
## SEE ALSO
podman(1), podman-manifest(1), podman-manifest-create(1), podman-manifest-add(1), podman-rmi(1)
podman(1), podman-manifest(1), podman-manifest-create(1), podman-manifest-add(1), podman-manifest-push(1), podman-manifest-remove(1), podman-rmi(1)

View File

@ -0,0 +1,72 @@
% podman-manifest-push(1)
## NAME
podman\-manifest\-push - Push a manifest list or image index to a registry
## SYNOPSIS
**podman manifest push** [options...] *listnameorindexname* *transport:details*
## DESCRIPTION
Pushes a manifest list or image index to a registry.
## RETURN VALUE
The list image's ID and the digest of the image's manifest.
## OPTIONS
**--all**
Push the images mentioned in the manifest list or image index, in addition to
the list or index itself.
**--authfile** *path*
Path of the authentication file. Default is ${XDG\_RUNTIME\_DIR}/containers/auth.json, which is set using `podman login`.
If the authorization state is not found there, $HOME/.docker/config.json is checked, which is set using `docker login`. (Not available for remote commands)
Note: You can also override the default path of the authentication file by setting the REGISTRY\_AUTH\_FILE
environment variable. `export REGISTRY_AUTH_FILE=path`
**--cert-dir** *path*
Use certificates at *path* (\*.crt, \*.cert, \*.key) to connect to the registry.
Default certificates directory is _/etc/containers/certs.d_. (Not available for remote commands)
**--creds** *creds*
The [username[:password]] to use to authenticate with the registry if required.
If one or both values are not supplied, a command line prompt will appear and the
value can be entered. The password is entered without echo.
**--digestfile** *Digestfile*
After copying the image, write the digest of the resulting image to the file.
**--format, -f**
Manifest list type (oci or v2s2) to use when pushing the list (default is oci).
**--purge**
Delete the manifest list or image index from local storage if pushing succeeds.
**--remove-signatures**
Don't copy signatures when pushing images.
**--sign-by** *fingerprint*
Sign the pushed images using the GPG key that matches the specified fingerprint.
**--tls-verify** *bool-value*
Require HTTPS and verify certificates when talking to container registries (defaults to true) (Not available for remote commands)
## EXAMPLE
```
podman manifest push mylist:v1.11 docker://registry.example.org/mylist:v1.11
```
## SEE ALSO
podman(1), podman-manifest(1), podman-manifest-add(1), podman-manifest-create(1), podman-manifest-inspect(1), podman-manifest-remove(1), podman-rmi(1)

View File

@ -0,0 +1,23 @@
% podman-manifest-remove(1)
## NAME
podman\-manifest\-remove - Remove an image from a manifest list or image index
## SYNOPSIS
**podman manifest remove** *listnameorindexname* *transport:details*
## DESCRIPTION
Removes the image with the specified digest from the specified manifest list or image index.
## RETURN VALUE
The list image's ID and the digest of the removed image's manifest.
## EXAMPLE
```
podman manifest remove mylist:v1.11 sha256:cb8a924afdf0229ef7515d9e5b3024e23b3eb03ddbba287f4a19c6ac90b8d221
e604eabaaee4858232761b4fef84e2316ed8f93e15eceafce845966ee3400036 :sha256:cb8a924afdf0229ef7515d9e5b3024e23b3eb03ddbba287f4a19c6ac90b8d221
```
## SEE ALSO
podman(1), podman-manifest(1), podman-manifest-add(1), podman-manifest-create(1), podman-manifest-inspect(1), podman-manifest-push(1), podman-rmi(1)

View File

@ -19,6 +19,8 @@ The `podman manifest` command provides subcommands which can be used to:
| 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. |
| push | [podman-manifest-push(1)](podman-manifest-push.1.md) | Push a manifest list or image index to a registry. |
| remove | [podman-manifest-remove(1)](podman-manifest-remove.1.md) | Remove an image from a manifest list or image index. |
## SEE ALSO
podman(1), podman-manifest-add(1), podman-manifest-annotate(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), podman-manifest-push(1), podman-manifest-remove(1)

View File

@ -30,4 +30,6 @@ type ImageEngine interface {
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)
ManifestRemove(ctx context.Context, names []string) (string, error)
ManifestPush(ctx context.Context, names []string, manifestPushOpts ManifestPushOptions) error
}

View File

@ -24,3 +24,8 @@ type ManifestAnnotateOptions struct {
OSVersion string `json:"os_version" schema:"os_version"`
Variant string `json:"variant" schema:"variant"`
}
type ManifestPushOptions struct {
Purge, Quiet, All, TlsVerify, RemoveSignatures bool
Authfile, CertDir, Creds, DigestFile, Format, SignBy string
}

View File

@ -6,15 +6,21 @@ import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"strings"
"github.com/containers/buildah/manifests"
buildahUtil "github.com/containers/buildah/util"
cp "github.com/containers/image/v5/copy"
"github.com/containers/image/v5/docker"
"github.com/containers/image/v5/manifest"
"github.com/containers/image/v5/transports/alltransports"
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"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
)
@ -137,3 +143,68 @@ func (ir *ImageEngine) ManifestAnnotate(ctx context.Context, names []string, opt
}
return "", err
}
// ManifestRemove removes specified digest from the specified manifest list
func (ir *ImageEngine) ManifestRemove(ctx context.Context, names []string) (string, error) {
instanceDigest, err := digest.Parse(names[1])
if err != nil {
return "", errors.Errorf(`invalid image digest "%s": %v`, names[1], err)
}
listImage, err := ir.Libpod.ImageRuntime().NewFromLocal(names[0])
if err != nil {
return "", errors.Wrapf(err, "error retriving local image from image name %s", names[0])
}
updatedListID, err := listImage.RemoveManifest(instanceDigest)
if err == nil {
return fmt.Sprintf("%s :%s\n", updatedListID, instanceDigest.String()), nil
}
return "", err
}
// ManifestPush pushes a manifest list or image index to the destination
func (ir *ImageEngine) ManifestPush(ctx context.Context, names []string, opts entities.ManifestPushOptions) error {
listImage, err := ir.Libpod.ImageRuntime().NewFromLocal(names[0])
if err != nil {
return errors.Wrapf(err, "error retriving local image from image name %s", names[0])
}
dest, err := alltransports.ParseImageName(names[1])
if err != nil {
return err
}
var manifestType string
if opts.Format != "" {
switch opts.Format {
case "oci":
manifestType = imgspecv1.MediaTypeImageManifest
case "v2s2", "docker":
manifestType = manifest.DockerV2Schema2MediaType
default:
return errors.Errorf("unknown format %q. Choose on of the supported formats: 'oci' or 'v2s2'", opts.Format)
}
}
options := manifests.PushOptions{
Store: ir.Libpod.GetStore(),
SystemContext: ir.Libpod.SystemContext(),
ImageListSelection: cp.CopySpecificImages,
Instances: nil,
RemoveSignatures: opts.RemoveSignatures,
SignBy: opts.SignBy,
ManifestType: manifestType,
}
if opts.All {
options.ImageListSelection = cp.CopyAllImages
}
if !opts.Quiet {
options.ReportWriter = os.Stderr
}
digest, err := listImage.PushManifest(dest, options)
if err == nil && opts.Purge {
_, err = ir.Libpod.GetStore().DeleteImage(listImage.ID(), true)
}
if opts.DigestFile != "" {
if err = ioutil.WriteFile(opts.DigestFile, []byte(digest.String()), 0644); err != nil {
return buildahUtil.GetFailureCause(err, errors.Wrapf(err, "failed to write digest to file %q", opts.DigestFile))
}
}
return err
}

View File

@ -91,3 +91,18 @@ func (ir *ImageEngine) ManifestAnnotate(ctx context.Context, names []string, opt
}
return fmt.Sprintf("%s :%s", updatedListID, names[1]), nil
}
// ManifestRemove removes the digest from manifest list
func (ir *ImageEngine) ManifestRemove(ctx context.Context, names []string) (string, error) {
updatedListID, err := manifests.Remove(ctx, names[0], names[1])
if err != nil {
return updatedListID, errors.Wrapf(err, "error removing from manifest %s", names[0])
}
return fmt.Sprintf("%s :%s\n", updatedListID, names[1]), nil
}
// ManifestPush pushes a manifest list or image index to the destination
func (ir *ImageEngine) ManifestPush(ctx context.Context, names []string, opts entities.ManifestPushOptions) error {
_, err := manifests.Push(ctx, names[0], &names[1], &opts.All)
return err
}

View File

@ -2,6 +2,8 @@ package integration
import (
"os"
"path/filepath"
"strings"
. "github.com/containers/libpod/test/utils"
. "github.com/onsi/ginkgo"
@ -114,4 +116,90 @@ var _ = Describe("Podman manifest", func() {
Expect(session.ExitCode()).To(Equal(0))
Expect(session.OutputToString()).To(ContainSubstring(`"architecture": "bar"`))
})
It("podman manifest remove", func() {
session := podmanTest.Podman([]string{"manifest", "create", "foo"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
session = podmanTest.Podman([]string{"manifest", "add", "--all", "foo", imageList})
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(imageListARM64InstanceDigest))
session = podmanTest.Podman([]string{"manifest", "remove", "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(imageListAMD64InstanceDigest))
Expect(session.OutputToString()).To(ContainSubstring(imageListARMInstanceDigest))
Expect(session.OutputToString()).To(ContainSubstring(imageListPPC64LEInstanceDigest))
Expect(session.OutputToString()).To(ContainSubstring(imageListS390XInstanceDigest))
Expect(session.OutputToString()).To(Not(ContainSubstring(imageListARM64InstanceDigest)))
})
It("podman manifest remove not-found", func() {
session := podmanTest.Podman([]string{"manifest", "create", "foo"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
session = podmanTest.Podman([]string{"manifest", "add", "foo", imageList})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
session = podmanTest.Podman([]string{"manifest", "remove", "foo", "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Not(Equal(0)))
})
It("podman manifest push", func() {
session := podmanTest.Podman([]string{"manifest", "create", "foo"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
session = podmanTest.Podman([]string{"manifest", "add", "--all", "foo", imageList})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
dest := filepath.Join(podmanTest.TempDir, "pushed")
err := os.MkdirAll(dest, os.ModePerm)
Expect(err).To(BeNil())
defer func() {
os.RemoveAll(dest)
}()
session = podmanTest.Podman([]string{"manifest", "push", "--all", "foo", "dir:" + dest})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
files, err := filepath.Glob(dest + string(os.PathSeparator) + "*")
Expect(err).To(BeNil())
check := SystemExec("sha256sum", files)
check.WaitWithDefaultTimeout()
Expect(check.ExitCode()).To(Equal(0))
prefix := "sha256:"
Expect(check.OutputToString()).To(ContainSubstring(strings.TrimPrefix(imageListAMD64InstanceDigest, prefix)))
Expect(check.OutputToString()).To(ContainSubstring(strings.TrimPrefix(imageListARMInstanceDigest, prefix)))
Expect(check.OutputToString()).To(ContainSubstring(strings.TrimPrefix(imageListPPC64LEInstanceDigest, prefix)))
Expect(check.OutputToString()).To(ContainSubstring(strings.TrimPrefix(imageListS390XInstanceDigest, prefix)))
Expect(check.OutputToString()).To(ContainSubstring(strings.TrimPrefix(imageListARM64InstanceDigest, prefix)))
})
It("podman manifest push purge", func() {
session := podmanTest.Podman([]string{"manifest", "create", "foo"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
session = podmanTest.Podman([]string{"manifest", "add", "foo", imageList})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
dest := filepath.Join(podmanTest.TempDir, "pushed")
err := os.MkdirAll(dest, os.ModePerm)
Expect(err).To(BeNil())
defer func() {
os.RemoveAll(dest)
}()
session = podmanTest.Podman([]string{"manifest", "push", "--purge", "foo", "dir:" + dest})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
session = podmanTest.Podman([]string{"manifest", "inspect", "foo"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Not(Equal(0)))
})
})