inspect: --manifest-digest flag

If this flag is specified, it'll display digest of that type, otherwise
it'll display the original digest.

Doesn't break any existing sha256 workflow.

Example:

1. Default
```
$ ./bin/skopeo inspect docker://docker.io/library/alpine:latest --format "Digest: {{.Digest}}"
Digest: sha256:4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2ad8dd412
```

2. with --manifest-digest
```
$ ./bin/skopeo inspect --manifest-digest=sha512 docker://docker.io/library/alpine:latest --format "Digest: {{.Digest}}"
Digest: sha512:5acb33fb56a7791bf0c69d5b19a1c70272148e4107be5261d57305d14e9509792bbca53e5277c456181ecfa1c20ad8427f9b8ba46868020584a819de1128dbd2
```

Signed-off-by: Lokesh Mandvekar <lsm5@redhat.com>
This commit is contained in:
Lokesh Mandvekar
2025-11-15 14:34:07 -05:00
parent a25bf91823
commit f85b6db46e

View File

@@ -9,6 +9,7 @@ import (
"github.com/containers/skopeo/cmd/skopeo/inspect"
"github.com/docker/distribution/registry/api/errcode"
"github.com/opencontainers/go-digest"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
@@ -22,13 +23,14 @@ import (
)
type inspectOptions struct {
global *globalOptions
image *imageOptions
retryOpts *retry.Options
format string
raw bool // Output the raw manifest instead of parsing information about the image
config bool // Output the raw config blob instead of parsing information about the image
doNotListTags bool // Do not list all tags available in the same repository
global *globalOptions
image *imageOptions
retryOpts *retry.Options
format string
raw bool // Output the raw manifest instead of parsing information about the image
config bool // Output the raw config blob instead of parsing information about the image
doNotListTags bool // Do not list all tags available in the same repository
manifestDigest digest.Algorithm // Algorithm to use for computing manifest digest
}
func inspectCmd(global *globalOptions) *cobra.Command {
@@ -64,6 +66,7 @@ skopeo inspect --format "Name: {{.Name}} Digest: {{.Digest}}" docker://registry.
flags.BoolVar(&opts.config, "config", false, "output configuration")
flags.StringVarP(&opts.format, "format", "f", "", "Format the output to a Go template")
flags.BoolVarP(&opts.doNotListTags, "no-tags", "n", false, "Do not list the available tags from the repository in the output")
flags.Var(newAlgorithmValue(&opts.manifestDigest), "manifest-digest", "Algorithm to use for computing manifest digest (sha256, sha512); defaults to algorithm used in config digest")
return cmd
}
@@ -176,7 +179,7 @@ func (opts *inspectOptions) run(args []string, stdout io.Writer) (retErr error)
LayersData: imgInspect.LayersData,
Env: imgInspect.Env,
}
outputData.Digest, err = manifest.Digest(rawManifest)
outputData.Digest, err = manifestDigestFromManifest(rawManifest, img, opts.manifestDigest)
if err != nil {
return fmt.Errorf("Error computing manifest digest: %w", err)
}
@@ -235,3 +238,48 @@ func (opts *inspectOptions) writeOutput(stdout io.Writer, data any) error {
defer rpt.Flush()
return rpt.Execute([]any{data})
}
func manifestDigestFromManifest(manifestBlob []byte, img types.Image, userAlgorithm digest.Algorithm) (digest.Digest, error) {
if userAlgorithm != "" {
if !userAlgorithm.Available() {
return "", fmt.Errorf("digest algorithm %q is not available", userAlgorithm)
}
return manifest.DigestWithAlgorithm(manifestBlob, userAlgorithm)
}
configInfo := img.ConfigInfo()
if configInfo.Digest != "" {
alg := configInfo.Digest.Algorithm()
if !alg.Available() {
return "", fmt.Errorf("config digest algorithm %q is not available", alg)
}
return manifest.DigestWithAlgorithm(manifestBlob, alg)
}
return manifest.Digest(manifestBlob)
}
type algorithmValue digest.Algorithm
func newAlgorithmValue(alg *digest.Algorithm) *algorithmValue {
return (*algorithmValue)(alg)
}
func (a *algorithmValue) Set(value string) error {
algorithm := digest.Algorithm(value)
*a = algorithmValue(algorithm)
if algorithm == "" {
*a = algorithmValue(digest.Canonical)
}
return nil
}
func (a *algorithmValue) String() string {
return digest.Algorithm(*a).String()
}
func (a *algorithmValue) Type() string {
return "algorithm"
}