mirror of https://github.com/containers/podman.git
Merge pull request #15359 from rhatdan/manifest
Add --insecure flag to podman manifest inspect for Docker compatibility
This commit is contained in:
commit
0702b4cf4c
|
@ -3,14 +3,17 @@ package manifest
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/containers/image/v5/types"
|
||||||
"github.com/containers/podman/v4/cmd/podman/common"
|
"github.com/containers/podman/v4/cmd/podman/common"
|
||||||
"github.com/containers/podman/v4/cmd/podman/registry"
|
"github.com/containers/podman/v4/cmd/podman/registry"
|
||||||
|
"github.com/containers/podman/v4/pkg/domain/entities"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
tlsVerifyCLI bool
|
||||||
inspectCmd = &cobra.Command{
|
inspectCmd = &cobra.Command{
|
||||||
Use: "inspect IMAGE",
|
Use: "inspect [options] IMAGE",
|
||||||
Short: "Display the contents of a manifest list or image index",
|
Short: "Display the contents of a manifest list or image index",
|
||||||
Long: "Display the contents of a manifest list or image index.",
|
Long: "Display the contents of a manifest list or image index.",
|
||||||
RunE: inspect,
|
RunE: inspect,
|
||||||
|
@ -25,10 +28,24 @@ func init() {
|
||||||
Command: inspectCmd,
|
Command: inspectCmd,
|
||||||
Parent: manifestCmd,
|
Parent: manifestCmd,
|
||||||
})
|
})
|
||||||
|
flags := inspectCmd.Flags()
|
||||||
|
|
||||||
|
flags.Bool("verbose", false, "Added for Docker compatibility")
|
||||||
|
_ = flags.MarkHidden("verbose")
|
||||||
|
flags.BoolVar(&tlsVerifyCLI, "tls-verify", true, "require HTTPS and verify certificates when accessing the registry")
|
||||||
|
flags.Bool("insecure", false, "Purely for Docker compatibility")
|
||||||
|
_ = flags.MarkHidden("insecure")
|
||||||
}
|
}
|
||||||
|
|
||||||
func inspect(cmd *cobra.Command, args []string) error {
|
func inspect(cmd *cobra.Command, args []string) error {
|
||||||
buf, err := registry.ImageEngine().ManifestInspect(registry.Context(), args[0])
|
opts := entities.ManifestInspectOptions{}
|
||||||
|
if cmd.Flags().Changed("tls-verify") {
|
||||||
|
opts.SkipTLSVerify = types.NewOptionalBool(!tlsVerifyCLI)
|
||||||
|
} else if cmd.Flags().Changed("insecure") {
|
||||||
|
insecure, _ := cmd.Flags().GetBool("insecure")
|
||||||
|
opts.SkipTLSVerify = types.NewOptionalBool(insecure)
|
||||||
|
}
|
||||||
|
buf, err := registry.ImageEngine().ManifestInspect(registry.Context(), args[0], opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ podman-manifest-add.1.md
|
||||||
podman-manifest-annotate.1.md
|
podman-manifest-annotate.1.md
|
||||||
podman-manifest-create.1.md
|
podman-manifest-create.1.md
|
||||||
podman-manifest-push.1.md
|
podman-manifest-push.1.md
|
||||||
|
podman-manifest-inspect.1.md
|
||||||
podman-pause.1.md
|
podman-pause.1.md
|
||||||
podman-pod-clone.1.md
|
podman-pod-clone.1.md
|
||||||
podman-pod-create.1.md
|
podman-pod-create.1.md
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
####> This option file is used in:
|
####> This option file is used in:
|
||||||
####> podman build, container runlabel, create, kube play, login, manifest add, manifest create, manifest push, pull, push, run, search
|
####> podman build, container runlabel, create, kube play, login, manifest add, manifest create, manifest inspect, manifest push, pull, push, run, search
|
||||||
####> If you edit this file, make sure your changes
|
####> If you edit this file, make sure your changes
|
||||||
####> are applicable to all of those.
|
####> are applicable to all of those.
|
||||||
#### **--tls-verify**
|
#### **--tls-verify**
|
||||||
|
|
|
@ -4,16 +4,19 @@
|
||||||
podman\-manifest\-inspect - Display a manifest list or image index
|
podman\-manifest\-inspect - Display a manifest list or image index
|
||||||
|
|
||||||
## SYNOPSIS
|
## SYNOPSIS
|
||||||
**podman manifest inspect** *listnameorindexname*
|
**podman manifest inspect** [*options*] *listnameorindexname*
|
||||||
|
|
||||||
## DESCRIPTION
|
## DESCRIPTION
|
||||||
|
|
||||||
Displays the manifest list or image index stored using the specified image name.
|
Displays the manifest list or image index stored using the specified image name.
|
||||||
|
|
||||||
## RETURN VALUE
|
## RETURN VALUE
|
||||||
|
|
||||||
A formatted JSON representation of the manifest list or image index.
|
A formatted JSON representation of the manifest list or image index.
|
||||||
|
|
||||||
|
## OPTIONS
|
||||||
|
|
||||||
|
@@option tls-verify
|
||||||
|
|
||||||
## EXAMPLES
|
## EXAMPLES
|
||||||
|
|
||||||
```
|
```
|
|
@ -139,10 +139,26 @@ func ManifestExists(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
func ManifestInspect(w http.ResponseWriter, r *http.Request) {
|
func ManifestInspect(w http.ResponseWriter, r *http.Request) {
|
||||||
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
|
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
|
||||||
|
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
|
||||||
name := utils.GetName(r)
|
name := utils.GetName(r)
|
||||||
|
// Wrapper to support 3.x with 4.x libpod
|
||||||
|
query := struct {
|
||||||
|
TLSVerify bool `schema:"tlsVerify"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
|
||||||
|
utils.Error(w, http.StatusBadRequest,
|
||||||
|
fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
imageEngine := abi.ImageEngine{Libpod: runtime}
|
imageEngine := abi.ImageEngine{Libpod: runtime}
|
||||||
rawManifest, err := imageEngine.ManifestInspect(r.Context(), name)
|
opts := entities.ManifestInspectOptions{}
|
||||||
|
if _, found := r.URL.Query()["tlsVerify"]; found {
|
||||||
|
opts.SkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
|
||||||
|
}
|
||||||
|
|
||||||
|
rawManifest, err := imageEngine.ManifestInspect(r.Context(), name, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Error(w, http.StatusNotFound, err)
|
utils.Error(w, http.StatusNotFound, err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -175,6 +175,11 @@ func (s *APIServer) registerManifestHandlers(r *mux.Router) error {
|
||||||
// type: string
|
// type: string
|
||||||
// required: true
|
// required: true
|
||||||
// description: the name or ID of the manifest list
|
// description: the name or ID of the manifest list
|
||||||
|
// - in: query
|
||||||
|
// name: tlsVerify
|
||||||
|
// type: boolean
|
||||||
|
// default: true
|
||||||
|
// description: Require HTTPS and verify signatures when contacting registries.
|
||||||
// responses:
|
// responses:
|
||||||
// 200:
|
// 200:
|
||||||
// $ref: "#/responses/manifestInspect"
|
// $ref: "#/responses/manifestInspect"
|
||||||
|
|
|
@ -71,13 +71,27 @@ func Exists(ctx context.Context, name string, options *ExistsOptions) (bool, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inspect returns a manifest list for a given name.
|
// Inspect returns a manifest list for a given name.
|
||||||
func Inspect(ctx context.Context, name string, _ *InspectOptions) (*manifest.Schema2List, error) {
|
func Inspect(ctx context.Context, name string, options *InspectOptions) (*manifest.Schema2List, error) {
|
||||||
conn, err := bindings.GetClient(ctx)
|
conn, err := bindings.GetClient(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if options == nil {
|
||||||
|
options = new(InspectOptions)
|
||||||
|
}
|
||||||
|
|
||||||
response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/manifests/%s/json", nil, nil, name)
|
params, err := options.ToParams()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// SkipTLSVerify is special. We need to delete the param added by
|
||||||
|
// ToParams() and change the key and flip the bool
|
||||||
|
if options.SkipTLSVerify != nil {
|
||||||
|
params.Del("SkipTLSVerify")
|
||||||
|
params.Set("tlsVerify", strconv.FormatBool(!options.GetSkipTLSVerify()))
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/manifests/%s/json", params, nil, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ package manifests
|
||||||
//
|
//
|
||||||
//go:generate go run ../generator/generator.go InspectOptions
|
//go:generate go run ../generator/generator.go InspectOptions
|
||||||
type InspectOptions struct {
|
type InspectOptions struct {
|
||||||
|
SkipTLSVerify *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateOptions are optional options for creating manifests
|
// CreateOptions are optional options for creating manifests
|
||||||
|
|
|
@ -16,3 +16,18 @@ func (o *InspectOptions) Changed(fieldName string) bool {
|
||||||
func (o *InspectOptions) ToParams() (url.Values, error) {
|
func (o *InspectOptions) ToParams() (url.Values, error) {
|
||||||
return util.ToParams(o)
|
return util.ToParams(o)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithSkipTLSVerify set field SkipTLSVerify to given value
|
||||||
|
func (o *InspectOptions) WithSkipTLSVerify(value bool) *InspectOptions {
|
||||||
|
o.SkipTLSVerify = &value
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSkipTLSVerify returns value of field SkipTLSVerify
|
||||||
|
func (o *InspectOptions) GetSkipTLSVerify() bool {
|
||||||
|
if o.SkipTLSVerify == nil {
|
||||||
|
var z bool
|
||||||
|
return z
|
||||||
|
}
|
||||||
|
return *o.SkipTLSVerify
|
||||||
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ type ImageEngine interface { //nolint:interfacebloat
|
||||||
Untag(ctx context.Context, nameOrID string, tags []string, options ImageUntagOptions) error
|
Untag(ctx context.Context, nameOrID string, tags []string, options ImageUntagOptions) error
|
||||||
ManifestCreate(ctx context.Context, name string, images []string, opts ManifestCreateOptions) (string, error)
|
ManifestCreate(ctx context.Context, name string, images []string, opts ManifestCreateOptions) (string, error)
|
||||||
ManifestExists(ctx context.Context, name string) (*BoolReport, error)
|
ManifestExists(ctx context.Context, name string) (*BoolReport, error)
|
||||||
ManifestInspect(ctx context.Context, name string) ([]byte, error)
|
ManifestInspect(ctx context.Context, name string, opts ManifestInspectOptions) ([]byte, error)
|
||||||
ManifestAdd(ctx context.Context, listName string, imageNames []string, opts ManifestAddOptions) (string, error)
|
ManifestAdd(ctx context.Context, listName string, imageNames []string, opts ManifestAddOptions) (string, error)
|
||||||
ManifestAnnotate(ctx context.Context, names, image string, opts ManifestAnnotateOptions) (string, error)
|
ManifestAnnotate(ctx context.Context, names, image string, opts ManifestAnnotateOptions) (string, error)
|
||||||
ManifestRemoveDigest(ctx context.Context, names, image string) (string, error)
|
ManifestRemoveDigest(ctx context.Context, names, image string) (string, error)
|
||||||
|
|
|
@ -12,6 +12,12 @@ type ManifestCreateOptions struct {
|
||||||
SkipTLSVerify types.OptionalBool `json:"-" schema:"-"`
|
SkipTLSVerify types.OptionalBool `json:"-" schema:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ManifestInspectOptions provides model for inspecting manifest
|
||||||
|
type ManifestInspectOptions struct {
|
||||||
|
// Should TLS registry certificate be verified?
|
||||||
|
SkipTLSVerify types.OptionalBool `json:"-" schema:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
// ManifestAddOptions provides model for adding digests to manifest list
|
// ManifestAddOptions provides model for adding digests to manifest list
|
||||||
//
|
//
|
||||||
// swagger:model
|
// swagger:model
|
||||||
|
|
|
@ -17,6 +17,7 @@ import (
|
||||||
"github.com/containers/image/v5/pkg/shortnames"
|
"github.com/containers/image/v5/pkg/shortnames"
|
||||||
"github.com/containers/image/v5/transports"
|
"github.com/containers/image/v5/transports"
|
||||||
"github.com/containers/image/v5/transports/alltransports"
|
"github.com/containers/image/v5/transports/alltransports"
|
||||||
|
"github.com/containers/image/v5/types"
|
||||||
"github.com/containers/podman/v4/pkg/domain/entities"
|
"github.com/containers/podman/v4/pkg/domain/entities"
|
||||||
"github.com/containers/storage"
|
"github.com/containers/storage"
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
|
@ -67,7 +68,7 @@ func (ir *ImageEngine) ManifestExists(ctx context.Context, name string) (*entiti
|
||||||
}
|
}
|
||||||
|
|
||||||
// ManifestInspect returns the content of a manifest list or image
|
// ManifestInspect returns the content of a manifest list or image
|
||||||
func (ir *ImageEngine) ManifestInspect(ctx context.Context, name string) ([]byte, error) {
|
func (ir *ImageEngine) ManifestInspect(ctx context.Context, name string, opts entities.ManifestInspectOptions) ([]byte, error) {
|
||||||
// NOTE: we have to do a bit of a limbo here as `podman manifest
|
// NOTE: we have to do a bit of a limbo here as `podman manifest
|
||||||
// inspect foo` wants to do a remote-inspect of foo iff "foo" in the
|
// inspect foo` wants to do a remote-inspect of foo iff "foo" in the
|
||||||
// containers storage is an ordinary image but not a manifest list.
|
// containers storage is an ordinary image but not a manifest list.
|
||||||
|
@ -77,7 +78,7 @@ func (ir *ImageEngine) ManifestInspect(ctx context.Context, name string) ([]byte
|
||||||
if errors.Is(err, storage.ErrImageUnknown) || errors.Is(err, libimage.ErrNotAManifestList) {
|
if errors.Is(err, storage.ErrImageUnknown) || errors.Is(err, libimage.ErrNotAManifestList) {
|
||||||
// Do a remote inspect if there's no local image or if the
|
// Do a remote inspect if there's no local image or if the
|
||||||
// local image is not a manifest list.
|
// local image is not a manifest list.
|
||||||
return ir.remoteManifestInspect(ctx, name)
|
return ir.remoteManifestInspect(ctx, name, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -101,9 +102,14 @@ func (ir *ImageEngine) ManifestInspect(ctx context.Context, name string) ([]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// inspect a remote manifest list.
|
// inspect a remote manifest list.
|
||||||
func (ir *ImageEngine) remoteManifestInspect(ctx context.Context, name string) ([]byte, error) {
|
func (ir *ImageEngine) remoteManifestInspect(ctx context.Context, name string, opts entities.ManifestInspectOptions) ([]byte, error) {
|
||||||
sys := ir.Libpod.SystemContext()
|
sys := ir.Libpod.SystemContext()
|
||||||
|
|
||||||
|
sys.DockerInsecureSkipTLSVerify = opts.SkipTLSVerify
|
||||||
|
if opts.SkipTLSVerify == types.OptionalBoolTrue {
|
||||||
|
sys.OCIInsecureSkipTLSVerify = true
|
||||||
|
}
|
||||||
|
|
||||||
resolved, err := shortnames.Resolve(sys, name)
|
resolved, err := shortnames.Resolve(sys, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -33,8 +33,17 @@ func (ir *ImageEngine) ManifestExists(ctx context.Context, name string) (*entiti
|
||||||
}
|
}
|
||||||
|
|
||||||
// ManifestInspect returns contents of manifest list with given name
|
// ManifestInspect returns contents of manifest list with given name
|
||||||
func (ir *ImageEngine) ManifestInspect(_ context.Context, name string) ([]byte, error) {
|
func (ir *ImageEngine) ManifestInspect(ctx context.Context, name string, opts entities.ManifestInspectOptions) ([]byte, error) {
|
||||||
list, err := manifests.Inspect(ir.ClientCtx, name, nil)
|
options := new(manifests.InspectOptions)
|
||||||
|
if s := opts.SkipTLSVerify; s != types.OptionalBoolUndefined {
|
||||||
|
if s == types.OptionalBoolTrue {
|
||||||
|
options.WithSkipTLSVerify(true)
|
||||||
|
} else {
|
||||||
|
options.WithSkipTLSVerify(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
list, err := manifests.Inspect(ir.ClientCtx, name, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("getting content of manifest list or image %s: %w", name, err)
|
return nil, fmt.Errorf("getting content of manifest list or image %s: %w", name, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -232,20 +232,6 @@ Labels.created_at | 20[0-9-]\\\+T[0-9:]\\\+Z
|
||||||
run_podman rmi ${aaa_name}:${aaa_tag} ${zzz_name}:${zzz_tag}
|
run_podman rmi ${aaa_name}:${aaa_tag} ${zzz_name}:${zzz_tag}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Regression test for #8931
|
|
||||||
@test "podman images - bare manifest list" {
|
|
||||||
# Create an empty manifest list and list images.
|
|
||||||
|
|
||||||
run_podman inspect --format '{{.ID}}' $IMAGE
|
|
||||||
iid=$output
|
|
||||||
|
|
||||||
run_podman manifest create test:1.0
|
|
||||||
run_podman images --format '{{.ID}}' --no-trunc
|
|
||||||
[[ "$output" == *"sha256:$iid"* ]]
|
|
||||||
|
|
||||||
run_podman rmi test:1.0
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "podman images - rmi -af removes all containers and pods" {
|
@test "podman images - rmi -af removes all containers and pods" {
|
||||||
pname=$(random_string)
|
pname=$(random_string)
|
||||||
run_podman create --pod new:$pname $IMAGE
|
run_podman create --pod new:$pname $IMAGE
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
#!/usr/bin/env bats
|
||||||
|
|
||||||
|
load helpers
|
||||||
|
|
||||||
|
# Regression test for #8931
|
||||||
|
@test "podman images - bare manifest list" {
|
||||||
|
# Create an empty manifest list and list images.
|
||||||
|
|
||||||
|
run_podman inspect --format '{{.ID}}' $IMAGE
|
||||||
|
iid=$output
|
||||||
|
|
||||||
|
run_podman manifest create test:1.0
|
||||||
|
run_podman manifest inspect --verbose $output
|
||||||
|
is "$output" ".*\"mediaType\": \"application/vnd.docker.distribution.manifest.list.v2+json\"" "--insecure is a noop want to make sure manifest inspect is successful"
|
||||||
|
run_podman images --format '{{.ID}}' --no-trunc
|
||||||
|
is "$output" ".*sha256:$iid" "Original image ID still shown in podman-images output"
|
||||||
|
run_podman rmi test:1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
# vim: filetype=sh
|
|
@ -290,6 +290,35 @@ function _test_skopeo_credential_sharing() {
|
||||||
rm -f $authfile
|
rm -f $authfile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@test "podman manifest --tls-verify - basic test" {
|
||||||
|
run_podman login --tls-verify=false \
|
||||||
|
--username ${PODMAN_LOGIN_USER} \
|
||||||
|
--password-stdin \
|
||||||
|
localhost:${PODMAN_LOGIN_REGISTRY_PORT} <<<"${PODMAN_LOGIN_PASS}"
|
||||||
|
is "$output" "Login Succeeded!" "output from podman login"
|
||||||
|
|
||||||
|
manifest1="localhost:${PODMAN_LOGIN_REGISTRY_PORT}/test:1.0"
|
||||||
|
run_podman manifest create $manifest1
|
||||||
|
mid=$output
|
||||||
|
run_podman manifest push --authfile=$authfile \
|
||||||
|
--tls-verify=false $mid \
|
||||||
|
$manifest1
|
||||||
|
run_podman manifest rm $manifest1
|
||||||
|
run_podman manifest inspect --insecure $manifest1
|
||||||
|
is "$output" ".*\"mediaType\": \"application/vnd.docker.distribution.manifest.list.v2+json\"" "Verify --insecure works against an insecure registry"
|
||||||
|
run_podman 125 manifest inspect --insecure=false $manifest1
|
||||||
|
is "$output" ".*Error: reading image \"docker://$manifest1\": pinging container registry localhost:${PODMAN_LOGIN_REGISTRY_PORT}:" "Verify --insecure=false fails"
|
||||||
|
run_podman manifest inspect --tls-verify=false $manifest1
|
||||||
|
is "$output" ".*\"mediaType\": \"application/vnd.docker.distribution.manifest.list.v2+json\"" "Verify --tls-verify=false works against an insecure registry"
|
||||||
|
run_podman 125 manifest inspect --tls-verify=true $manifest1
|
||||||
|
is "$output" ".*Error: reading image \"docker://$manifest1\": pinging container registry localhost:${PODMAN_LOGIN_REGISTRY_PORT}:" "Verify --tls-verify=true fails"
|
||||||
|
|
||||||
|
# Now log out
|
||||||
|
run_podman logout localhost:${PODMAN_LOGIN_REGISTRY_PORT}
|
||||||
|
is "$output" "Removed login credentials for localhost:${PODMAN_LOGIN_REGISTRY_PORT}" \
|
||||||
|
"output from podman logout"
|
||||||
|
}
|
||||||
|
|
||||||
# END cooperation with skopeo
|
# END cooperation with skopeo
|
||||||
# END actual tests
|
# END actual tests
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
Loading…
Reference in New Issue