Merge pull request #15359 from rhatdan/manifest

Add --insecure flag to podman manifest inspect for Docker compatibility
This commit is contained in:
OpenShift Merge Robot 2022-11-04 05:14:46 -04:00 committed by GitHub
commit 0702b4cf4c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 157 additions and 29 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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