summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/podman/main.go1
-rw-r--r--cmd/podman/manifest/add.go49
-rw-r--r--cmd/podman/manifest/create.go44
-rw-r--r--cmd/podman/manifest/inspect.go39
-rw-r--r--cmd/podman/manifest/manifest.go27
-rw-r--r--completions/bash/podman78
-rw-r--r--docs/source/markdown/podman-manifest-add.1.md69
-rw-r--r--docs/source/markdown/podman-manifest-create.1.md43
-rw-r--r--docs/source/markdown/podman-manifest-inspect.1.md24
-rw-r--r--docs/source/markdown/podman-manifest.1.md23
-rw-r--r--docs/source/markdown/podman.1.md1
-rw-r--r--go.sum1
-rw-r--r--libpod/runtime.go5
-rw-r--r--pkg/domain/entities/engine_image.go3
-rw-r--r--pkg/domain/entities/manifest.go15
-rw-r--r--pkg/domain/infra/abi/manifest.go101
-rw-r--r--pkg/domain/infra/tunnel/manifest.go63
-rw-r--r--test/e2e/manifest_test.go88
18 files changed, 674 insertions, 0 deletions
diff --git a/cmd/podman/main.go b/cmd/podman/main.go
index 2d9e45177..8109eca2f 100644
--- a/cmd/podman/main.go
+++ b/cmd/podman/main.go
@@ -6,6 +6,7 @@ import (
_ "github.com/containers/libpod/cmd/podman/containers"
_ "github.com/containers/libpod/cmd/podman/healthcheck"
_ "github.com/containers/libpod/cmd/podman/images"
+ _ "github.com/containers/libpod/cmd/podman/manifest"
_ "github.com/containers/libpod/cmd/podman/networks"
_ "github.com/containers/libpod/cmd/podman/pods"
"github.com/containers/libpod/cmd/podman/registry"
diff --git a/cmd/podman/manifest/add.go b/cmd/podman/manifest/add.go
new file mode 100644
index 000000000..20251ca87
--- /dev/null
+++ b/cmd/podman/manifest/add.go
@@ -0,0 +1,49 @@
+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 (
+ manifestAddOpts = entities.ManifestAddOptions{}
+ addCmd = &cobra.Command{
+ Use: "add",
+ Short: "Add images to a manifest list or image index",
+ Long: "Adds an image to a manifest list or image index.",
+ RunE: add,
+ Example: `podman manifest add mylist:v1.11 image:v1.11-amd64
+ podman manifest add mylist:v1.11 transport:imageName`,
+ Args: cobra.ExactArgs(2),
+ }
+)
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: addCmd,
+ Parent: manifestCmd,
+ })
+ flags := addCmd.Flags()
+ flags.BoolVar(&manifestAddOpts.All, "all", false, "add all of the list's images if the image is a list")
+ flags.StringSliceVar(&manifestAddOpts.Annotation, "annotation", nil, "set an `annotation` for the specified image")
+ flags.StringVar(&manifestAddOpts.Arch, "arch", "", "override the `architecture` of the specified image")
+ flags.StringSliceVar(&manifestAddOpts.Features, "features", nil, "override the `features` of the specified image")
+ flags.StringVar(&manifestAddOpts.OSVersion, "os-version", "", "override the OS `version` of the specified image")
+ flags.StringVar(&manifestAddOpts.Variant, "variant", "", "override the `Variant` of the specified image")
+}
+
+func add(cmd *cobra.Command, args []string) error {
+ manifestAddOpts.Images = []string{args[1], args[0]}
+ listID, err := registry.ImageEngine().ManifestAdd(context.Background(), manifestAddOpts)
+ if err != nil {
+ return errors.Wrapf(err, "error adding to manifest list %s", args[0])
+ }
+ fmt.Printf("%s\n", listID)
+ return nil
+}
diff --git a/cmd/podman/manifest/create.go b/cmd/podman/manifest/create.go
new file mode 100644
index 000000000..4f3e27774
--- /dev/null
+++ b/cmd/podman/manifest/create.go
@@ -0,0 +1,44 @@
+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 (
+ manifestCreateOpts = entities.ManifestCreateOptions{}
+ createCmd = &cobra.Command{
+ Use: "create",
+ Short: "Create manifest list or image index",
+ Long: "Creates manifest lists or image indexes.",
+ RunE: create,
+ Example: `podman manifest create mylist:v1.11
+ podman manifest create mylist:v1.11 arch-specific-image-to-add
+ podman manifest create --all mylist:v1.11 transport:tagged-image-to-add`,
+ Args: cobra.MinimumNArgs(1),
+ }
+)
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: createCmd,
+ Parent: manifestCmd,
+ })
+ flags := createCmd.Flags()
+ flags.BoolVar(&manifestCreateOpts.All, "all", false, "add all of the lists' images if the images to add are lists")
+}
+
+func create(cmd *cobra.Command, args []string) error {
+ imageID, err := registry.ImageEngine().ManifestCreate(context.Background(), args[:1], args[1:], manifestCreateOpts)
+ if err != nil {
+ return errors.Wrapf(err, "error creating manifest %s", args[0])
+ }
+ fmt.Printf("%s\n", imageID)
+ return nil
+}
diff --git a/cmd/podman/manifest/inspect.go b/cmd/podman/manifest/inspect.go
new file mode 100644
index 000000000..36ecdc87b
--- /dev/null
+++ b/cmd/podman/manifest/inspect.go
@@ -0,0 +1,39 @@
+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 (
+ inspectCmd = &cobra.Command{
+ Use: "inspect IMAGE",
+ Short: "Display the contents of a manifest list or image index",
+ Long: "Display the contents of a manifest list or image index.",
+ RunE: inspect,
+ Example: "podman manifest inspect localhost/list",
+ Args: cobra.ExactArgs(1),
+ }
+)
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: inspectCmd,
+ Parent: manifestCmd,
+ })
+}
+
+func inspect(cmd *cobra.Command, args []string) error {
+ buf, err := registry.ImageEngine().ManifestInspect(context.Background(), args[0])
+ if err != nil {
+ return errors.Wrapf(err, "error inspect manifest %s", args[0])
+ }
+ fmt.Printf("%s\n", buf)
+ return nil
+}
diff --git a/cmd/podman/manifest/manifest.go b/cmd/podman/manifest/manifest.go
new file mode 100644
index 000000000..b9ac7ea68
--- /dev/null
+++ b/cmd/podman/manifest/manifest.go
@@ -0,0 +1,27 @@
+package manifest
+
+import (
+ "github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/spf13/cobra"
+)
+
+var (
+ manifestDescription = "Creates, modifies, and pushes manifest lists and image indexes."
+ manifestCmd = &cobra.Command{
+ Use: "manifest",
+ Short: "Manipulate manifest lists and image indexes",
+ Long: manifestDescription,
+ TraverseChildren: true,
+ RunE: registry.SubCommandExists,
+ Example: `podman manifest create localhost/list
+ podman manifest inspect localhost/list`,
+ }
+)
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: manifestCmd,
+ })
+}
diff --git a/completions/bash/podman b/completions/bash/podman
index 6997db3b5..41a76a967 100644
--- a/completions/bash/podman
+++ b/completions/bash/podman
@@ -1733,6 +1733,83 @@ _podman_logs() {
esac
}
+_podman_manifest() {
+ local boolean_options="
+ --help
+ -h
+ "
+ subcommands="
+ add
+ create
+ inspect
+ "
+ __podman_subcommands "$subcommands" && return
+
+ case "$cur" in
+ -*)
+ COMPREPLY=( $( compgen -W "--help" -- "$cur" ) )
+ ;;
+ *)
+ COMPREPLY=( $( compgen -W "$subcommands" -- "$cur" ) )
+ ;;
+ esac
+}
+
+_podman_manifest_add() {
+ local options_with_args="
+ --annotation
+ --arch
+ --features
+ --os-version
+ --variant
+ "
+
+ local boolean_options="
+ --all
+ --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
+ --help
+ -h
+ "
+
+ _complete_ "$boolean_options"
+}
+
+_podman_manifest_inspect() {
+ 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
@@ -3356,6 +3433,7 @@ _podman_podman() {
login
logout
logs
+ manifest
mount
pause
pod
diff --git a/docs/source/markdown/podman-manifest-add.1.md b/docs/source/markdown/podman-manifest-add.1.md
new file mode 100644
index 000000000..4ecf03900
--- /dev/null
+++ b/docs/source/markdown/podman-manifest-add.1.md
@@ -0,0 +1,69 @@
+% podman-manifest-add(1)
+
+## NAME
+podman\-manifest\-add - Add an image to a manifest list or image index
+
+## SYNOPSIS
+**podman manifest add** *listnameorindexname* *imagename*
+
+## DESCRIPTION
+
+Adds the specified image to the specified manifest list or image index.
+
+## RETURN VALUE
+The list image's ID.
+
+## OPTIONS
+
+**--all**
+
+If the image which should be added to the list or index is itself a list or
+index, add all of the contents to the local list. By default, only one image
+from such a list or index will be added to the list or index. Combining
+*--all* with any of the other options described below is NOT recommended.
+
+**--annotation** *annotation=value*
+
+Set an annotation on the entry for the newly-added image.
+
+**--arch**
+
+Override the architecture which the list or index records as a requirement for
+the image. If *imageName* refers to a manifest list or image index, the
+architecture information will be retrieved from it. Otherwise, it will be
+retrieved from the image's configuration information.
+
+**--features**
+
+Specify the 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 add mylist:v1.11 docker://fedora
+71c201d10fffdcac52968a000d85a0a016ca1c7d5473948000d3131c1773d965
+```
+
+```
+podman manifest add --all mylist:v1.11 docker://fedora
+71c201d10fffdcac52968a000d85a0a016ca1c7d5473948000d3131c1773d965
+```
+
+```
+podman manifest add --arch arm64 --variant v8 mylist:v1.11 docker://71c201d10fffdcac52968a000d85a0a016ca1c7d5473948000d3131c1773d965
+```
+
+## SEE ALSO
+podman(1), podman-manifest(1), podman-manifest-create(1), podman-manifest-inspect(1), podman-rmi(1)
diff --git a/docs/source/markdown/podman-manifest-create.1.md b/docs/source/markdown/podman-manifest-create.1.md
new file mode 100644
index 000000000..941e70c32
--- /dev/null
+++ b/docs/source/markdown/podman-manifest-create.1.md
@@ -0,0 +1,43 @@
+% podman-manifest-create(1)
+
+## NAME
+podman\-manifest\-create - Create a manifest list or image index
+
+## SYNOPSIS
+**podman manifest create** [*options*] *listnameorindexname* [*imagename* ...]
+
+## DESCRIPTION
+
+Creates a new manifest list and stores it as an image in local storage using
+the specified name.
+
+If additional images are specified, they are added to the newly-created list or
+index.
+
+## OPTIONS
+
+**--all**
+
+If any of the images which should be added to the new list or index are
+themselves lists or indexes, add all of their contents. By default, only one
+image from such a list will be added to the newly-created list or index.
+
+## EXAMPLES
+
+```
+podman manifest create mylist:v1.11
+9cfd24048d5fc80903f088f1531a21bff01172abe66effa8941a4c2308dc745f
+```
+
+```
+podman manifest create mylist:v1.11 docker://fedora
+5c2bc76bfb4ba6665a7973f7e1c05ee0536b4580637f27adc9fa5a4b2bc03cf1
+```
+
+```
+podman manifest create --all mylist:v1.11 docker://fedora
+30330571e79c65288a4fca421d9aed29b0210d57294d9c2056743fdcf6e3967b
+```
+
+## SEE ALSO
+podman(1), podman-manifest(1), podman-manifest-add(1), podman-manifest-inspect(1), podman-rmi(1)
diff --git a/docs/source/markdown/podman-manifest-inspect.1.md b/docs/source/markdown/podman-manifest-inspect.1.md
new file mode 100644
index 000000000..efde02643
--- /dev/null
+++ b/docs/source/markdown/podman-manifest-inspect.1.md
@@ -0,0 +1,24 @@
+% podman-manifest-inspect(1)
+
+## NAME
+podman\-manifest\-inspect - Display a manifest list or image index
+
+## SYNOPSIS
+**podman manifest inspect** *listnameorindexname*
+
+## DESCRIPTION
+
+Displays the manifest list or image index stored using the specified image name.
+
+## RETURN VALUE
+
+A formatted JSON representation of the manifest list or image index.
+
+## EXAMPLES
+
+```
+podman manifest inspect mylist:v1.11
+```
+
+## SEE ALSO
+podman(1), podman-manifest(1), podman-manifest-create(1), podman-manifest-add(1), podman-rmi(1)
diff --git a/docs/source/markdown/podman-manifest.1.md b/docs/source/markdown/podman-manifest.1.md
new file mode 100644
index 000000000..70d695883
--- /dev/null
+++ b/docs/source/markdown/podman-manifest.1.md
@@ -0,0 +1,23 @@
+% podman-manifest(1)
+
+## NAME
+podman\-manifest - Create and manipulate manifest lists and image indexes
+
+## SYNOPSIS
+**podman manifest** *subcommand*
+
+## DESCRIPTION
+The `podman manifest` command provides subcommands which can be used to:
+
+ * Create a working Docker manifest list or OCI image index.
+
+## 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. |
+
+## SEE ALSO
+podman(1), podman-manifest-add(1), podman-manifest-create(1), podman-manifest-inspect(1)
diff --git a/docs/source/markdown/podman.1.md b/docs/source/markdown/podman.1.md
index cd4148c95..6bac0cc9d 100644
--- a/docs/source/markdown/podman.1.md
+++ b/docs/source/markdown/podman.1.md
@@ -169,6 +169,7 @@ the exit codes follow the `chroot` standard, see below:
| [podman-login(1)](podman-login.1.md) | Login to a container registry. |
| [podman-logout(1)](podman-logout.1.md) | Logout of a container registry. |
| [podman-logs(1)](podman-logs.1.md) | Display the logs of one or more containers. |
+| [podman-manifest(1)](podman-manifest.1.md) | Create and manipulate manifest lists and image indexes. |
| [podman-mount(1)](podman-mount.1.md) | Mount a working container's root filesystem. |
| [podman-network(1)](podman-network.1.md) | Manage Podman CNI networks. |
| [podman-pause(1)](podman-pause.1.md) | Pause one or more containers. |
diff --git a/go.sum b/go.sum
index 92f23392d..cae07d467 100644
--- a/go.sum
+++ b/go.sum
@@ -131,6 +131,7 @@ github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e h1:p1yVGRW3nmb85p1
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
+github.com/etcd-io/bbolt v1.3.3 h1:gSJmxrs37LgTqR/oyJBWok6k6SvXEUerFTbltIhXkBM=
github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
diff --git a/libpod/runtime.go b/libpod/runtime.go
index 3b8f9e057..e71483ef9 100644
--- a/libpod/runtime.go
+++ b/libpod/runtime.go
@@ -733,6 +733,11 @@ func (r *Runtime) StorageConfig() storage.StoreOptions {
return r.storageConfig
}
+// GetStore returns the runtime stores
+func (r *Runtime) GetStore() storage.Store {
+ return r.store
+}
+
// DBConfig is a set of Libpod runtime configuration settings that are saved in
// a State when it is first created, and can subsequently be retrieved.
type DBConfig struct {
diff --git a/pkg/domain/entities/engine_image.go b/pkg/domain/entities/engine_image.go
index 84680ab1b..fefcd751d 100644
--- a/pkg/domain/entities/engine_image.go
+++ b/pkg/domain/entities/engine_image.go
@@ -25,4 +25,7 @@ type ImageEngine interface {
Tag(ctx context.Context, nameOrId string, tags []string, options ImageTagOptions) error
Tree(ctx context.Context, nameOrId string, options ImageTreeOptions) (*ImageTreeReport, error)
Untag(ctx context.Context, nameOrId string, tags []string, options ImageUntagOptions) error
+ 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)
}
diff --git a/pkg/domain/entities/manifest.go b/pkg/domain/entities/manifest.go
new file mode 100644
index 000000000..a9c961f9d
--- /dev/null
+++ b/pkg/domain/entities/manifest.go
@@ -0,0 +1,15 @@
+package entities
+
+type ManifestCreateOptions struct {
+ All bool `schema:"all"`
+}
+
+type ManifestAddOptions struct {
+ All bool `json:"all" schema:"all"`
+ Annotation []string `json:"annotation" schema:"annotation"`
+ Arch string `json:"arch" schema:"arch"`
+ Features []string `json:"features" schema:"features"`
+ Images []string `json:"images" schema:"images"`
+ 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
new file mode 100644
index 000000000..27d4bf9a5
--- /dev/null
+++ b/pkg/domain/infra/abi/manifest.go
@@ -0,0 +1,101 @@
+// +build ABISupport
+
+package abi
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "strings"
+
+ buildahUtil "github.com/containers/buildah/util"
+ "github.com/containers/image/v5/docker"
+ "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/pkg/errors"
+)
+
+// ManifestCreate implements logic for creating manifest lists via ImageEngine
+func (ir *ImageEngine) ManifestCreate(ctx context.Context, names, images []string, opts entities.ManifestCreateOptions) (string, error) {
+ fullNames, err := buildahUtil.ExpandNames(names, "", ir.Libpod.SystemContext(), ir.Libpod.GetStore())
+ if err != nil {
+ return "", errors.Wrapf(err, "error encountered while expanding image name %q", names)
+ }
+ imageID, err := libpodImage.CreateManifestList(ir.Libpod.ImageRuntime(), *ir.Libpod.SystemContext(), fullNames, images, opts.All)
+ if err != nil {
+ return imageID, err
+ }
+ return imageID, err
+}
+
+// ManifestInspect returns the content of a manifest list or image
+func (ir *ImageEngine) ManifestInspect(ctx context.Context, name string) ([]byte, error) {
+ dockerPrefix := fmt.Sprintf("%s://", docker.Transport.Name())
+ _, err := alltransports.ParseImageName(name)
+ if err != nil {
+ _, err = alltransports.ParseImageName(dockerPrefix + name)
+ if err != nil {
+ return nil, errors.Errorf("invalid image reference %q", name)
+ }
+ }
+ image, err := ir.Libpod.ImageRuntime().New(ctx, name, "", "", nil, nil, libpodImage.SigningOptions{}, nil, util.PullImageMissing)
+ if err != nil {
+ return nil, errors.Wrapf(err, "reading image %q", name)
+ }
+
+ list, err := image.InspectManifest()
+ if err != nil {
+ return nil, errors.Wrapf(err, "loading manifest %q", name)
+ }
+ buf, err := json.MarshalIndent(list, "", " ")
+ if err != nil {
+ return buf, errors.Wrapf(err, "error rendering manifest for display")
+ }
+ return buf, nil
+}
+
+// ManifestAdd adds images to the manifest list
+func (ir *ImageEngine) ManifestAdd(ctx context.Context, opts entities.ManifestAddOptions) (string, error) {
+ imageSpec := opts.Images[0]
+ listImageSpec := opts.Images[1]
+ dockerPrefix := fmt.Sprintf("%s://", docker.Transport.Name())
+ _, err := alltransports.ParseImageName(imageSpec)
+ if err != nil {
+ _, err = alltransports.ParseImageName(fmt.Sprintf("%s%s", dockerPrefix, imageSpec))
+ if err != nil {
+ return "", errors.Errorf("invalid image reference %q", imageSpec)
+ }
+ }
+ listImage, err := ir.Libpod.ImageRuntime().NewFromLocal(listImageSpec)
+ if err != nil {
+ return "", errors.Wrapf(err, "error retriving local image from image name %s", listImageSpec)
+ }
+
+ manifestAddOpts := libpodImage.ManifestAddOpts{
+ All: opts.All,
+ Arch: opts.Arch,
+ Features: opts.Features,
+ Images: opts.Images,
+ 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]
+ }
+ manifestAddOpts.Annotation = annotations
+ }
+ listID, err := listImage.AddManifest(*ir.Libpod.SystemContext(), manifestAddOpts)
+ if err != nil {
+ return listID, err
+ }
+ return listID, nil
+}
diff --git a/pkg/domain/infra/tunnel/manifest.go b/pkg/domain/infra/tunnel/manifest.go
new file mode 100644
index 000000000..338256530
--- /dev/null
+++ b/pkg/domain/infra/tunnel/manifest.go
@@ -0,0 +1,63 @@
+package tunnel
+
+import (
+ "context"
+ "encoding/json"
+ "strings"
+
+ "github.com/containers/libpod/libpod/image"
+ "github.com/containers/libpod/pkg/bindings/manifests"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/pkg/errors"
+)
+
+// ManifestCreate implements manifest create via ImageEngine
+func (ir *ImageEngine) ManifestCreate(ctx context.Context, names, images []string, opts entities.ManifestCreateOptions) (string, error) {
+ imageID, err := manifests.Create(ir.ClientCxt, names, images, &opts.All)
+ if err != nil {
+ return imageID, errors.Wrapf(err, "error creating manifest")
+ }
+ return imageID, err
+}
+
+// ManifestInspect returns contents of manifest list with given name
+func (ir *ImageEngine) ManifestInspect(ctx context.Context, name string) ([]byte, error) {
+ list, err := manifests.Inspect(ir.ClientCxt, name)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error getting content of manifest list or image %s", name)
+ }
+
+ buf, err := json.MarshalIndent(list, "", " ")
+ if err != nil {
+ return buf, errors.Wrapf(err, "error rendering manifest for display")
+ }
+ return buf, err
+}
+
+// ManifestAdd adds images to the manifest list
+func (ir *ImageEngine) ManifestAdd(ctx context.Context, opts entities.ManifestAddOptions) (string, error) {
+ manifestAddOpts := image.ManifestAddOpts{
+ All: opts.All,
+ Arch: opts.Arch,
+ Features: opts.Features,
+ Images: opts.Images,
+ 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]
+ }
+ manifestAddOpts.Annotation = annotations
+ }
+ listID, err := manifests.Add(ctx, opts.Images[1], manifestAddOpts)
+ if err != nil {
+ return listID, errors.Wrapf(err, "error adding to manifest list %s", opts.Images[1])
+ }
+ return listID, nil
+}
diff --git a/test/e2e/manifest_test.go b/test/e2e/manifest_test.go
new file mode 100644
index 000000000..a52916e87
--- /dev/null
+++ b/test/e2e/manifest_test.go
@@ -0,0 +1,88 @@
+package integration
+
+import (
+ "os"
+
+ . "github.com/containers/libpod/test/utils"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+)
+
+var _ = Describe("Podman manifest", func() {
+ var (
+ tempdir string
+ err error
+ podmanTest *PodmanTestIntegration
+ )
+
+ const (
+ imageList = "docker://k8s.gcr.io/pause:3.1"
+ imageListInstance = "docker://k8s.gcr.io/pause@sha256:f365626a556e58189fc21d099fc64603db0f440bff07f77c740989515c544a39"
+ imageListARM64InstanceDigest = "sha256:f365626a556e58189fc21d099fc64603db0f440bff07f77c740989515c544a39"
+ imageListAMD64InstanceDigest = "sha256:59eec8837a4d942cc19a52b8c09ea75121acc38114a2c68b98983ce9356b8610"
+ imageListARMInstanceDigest = "sha256:c84b0a3a07b628bc4d62e5047d0f8dff80f7c00979e1e28a821a033ecda8fe53"
+ imageListPPC64LEInstanceDigest = "sha256:bcf9771c0b505e68c65440474179592ffdfa98790eb54ffbf129969c5e429990"
+ imageListS390XInstanceDigest = "sha256:882a20ee0df7399a445285361d38b711c299ca093af978217112c73803546d5e"
+ )
+
+ BeforeEach(func() {
+ tempdir, err = CreateTempDirInTempDir()
+ if err != nil {
+ os.Exit(1)
+ }
+ podmanTest = PodmanTestCreate(tempdir)
+ podmanTest.Setup()
+ podmanTest.SeedImages()
+ })
+
+ AfterEach(func() {
+ podmanTest.Cleanup()
+ f := CurrentGinkgoTestDescription()
+ processTestResult(f)
+
+ })
+ It("podman manifest create", func() {
+ session := podmanTest.Podman([]string{"manifest", "create", "foo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ })
+
+ It("podman manifest add", func() {
+ session := podmanTest.Podman([]string{"manifest", "create", "foo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ session = podmanTest.Podman([]string{"manifest", "add", "--arch=arm64", "foo", imageListInstance})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ })
+
+ It("podman manifest add one", func() {
+ session := podmanTest.Podman([]string{"manifest", "create", "foo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ session = podmanTest.Podman([]string{"manifest", "add", "--arch=arm64", "foo", imageListInstance})
+ 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))
+ })
+
+ It("podman manifest add --all", 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(imageListAMD64InstanceDigest))
+ Expect(session.OutputToString()).To(ContainSubstring(imageListARMInstanceDigest))
+ Expect(session.OutputToString()).To(ContainSubstring(imageListARM64InstanceDigest))
+ Expect(session.OutputToString()).To(ContainSubstring(imageListPPC64LEInstanceDigest))
+ Expect(session.OutputToString()).To(ContainSubstring(imageListS390XInstanceDigest))
+ })
+})