From 16b6f0ade5729a9561f48e981d2d0483fbfc6ec9 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Mon, 26 Feb 2024 12:25:32 +0100 Subject: [PATCH] main: return exit code `2` when an input is not found This commit makes skopeo return a different exit code when an input is not found. The use case is `osbuild` which uses skopeo to inspect images and it would be nice to differenciate between an image that is not found and general skopeo errors (or errors like network issues etc). I picked exit code `2` for `not found` because it is also the value of `ENOENT`. Man page and a test are added. Signed-off-by: Michael Vogt --- cmd/skopeo/main.go | 4 ++++ cmd/skopeo/proxy.go | 22 ---------------------- cmd/skopeo/utils.go | 24 ++++++++++++++++++++++++ docs/skopeo.1.md | 9 +++++++++ systemtest/010-inspect.bats | 7 +++++++ systemtest/040-local-registry-auth.bats | 2 +- systemtest/060-delete.bats | 2 +- 7 files changed, 46 insertions(+), 24 deletions(-) diff --git a/cmd/skopeo/main.go b/cmd/skopeo/main.go index 3617eeb2df..8b8de3c7bc 100644 --- a/cmd/skopeo/main.go +++ b/cmd/skopeo/main.go @@ -129,6 +129,10 @@ func main() { } rootCmd, _ := createApp() if err := rootCmd.Execute(); err != nil { + if isNotFoundImageError(err) { + logrus.StandardLogger().Log(logrus.FatalLevel, err) + logrus.Exit(2) + } logrus.Fatal(err) } } diff --git a/cmd/skopeo/proxy.go b/cmd/skopeo/proxy.go index aab85365b1..9396f42273 100644 --- a/cmd/skopeo/proxy.go +++ b/cmd/skopeo/proxy.go @@ -73,13 +73,10 @@ import ( "github.com/containers/image/v5/image" "github.com/containers/image/v5/manifest" - ocilayout "github.com/containers/image/v5/oci/layout" "github.com/containers/image/v5/pkg/blobinfocache" "github.com/containers/image/v5/transports" "github.com/containers/image/v5/transports/alltransports" "github.com/containers/image/v5/types" - dockerdistributionerrcode "github.com/docker/distribution/registry/api/errcode" - dockerdistributionapi "github.com/docker/distribution/registry/api/v2" "github.com/opencontainers/go-digest" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/sirupsen/logrus" @@ -219,25 +216,6 @@ func (h *proxyHandler) OpenImage(args []any) (replyBuf, error) { return h.openImageImpl(args, false) } -// isDockerManifestUnknownError is a copy of code from containers/image, -// please update there first. -func isDockerManifestUnknownError(err error) bool { - var ec dockerdistributionerrcode.ErrorCoder - if !errors.As(err, &ec) { - return false - } - return ec.ErrorCode() == dockerdistributionapi.ErrorCodeManifestUnknown -} - -// isNotFoundImageError heuristically attempts to determine whether an error -// is saying the remote source couldn't find the image (as opposed to an -// authentication error, an I/O error etc.) -// TODO drive this into containers/image properly -func isNotFoundImageError(err error) bool { - return isDockerManifestUnknownError(err) || - errors.Is(err, ocilayout.ImageNotFoundError{}) -} - func (h *proxyHandler) openImageImpl(args []any, allowNotFound bool) (retReplyBuf replyBuf, retErr error) { h.lock.Lock() defer h.lock.Unlock() diff --git a/cmd/skopeo/utils.go b/cmd/skopeo/utils.go index 3fb0fab9e9..75a2fa823b 100644 --- a/cmd/skopeo/utils.go +++ b/cmd/skopeo/utils.go @@ -12,9 +12,13 @@ import ( "github.com/containers/common/pkg/retry" "github.com/containers/image/v5/directory" "github.com/containers/image/v5/manifest" + ocilayout "github.com/containers/image/v5/oci/layout" "github.com/containers/image/v5/pkg/compression" + "github.com/containers/image/v5/storage" "github.com/containers/image/v5/transports/alltransports" "github.com/containers/image/v5/types" + dockerdistributionerrcode "github.com/docker/distribution/registry/api/errcode" + dockerdistributionapi "github.com/docker/distribution/registry/api/v2" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -406,3 +410,23 @@ func promptForPassphrase(privateKeyFile string, stdin, stdout *os.File) (string, fmt.Fprintf(stdout, "\n") return string(passphrase), nil } + +// isNotFoundImageError heuristically attempts to determine whether an error +// is saying the remote source couldn't find the image (as opposed to an +// authentication error, an I/O error etc.) +// TODO drive this into containers/image properly +func isNotFoundImageError(err error) bool { + return isDockerManifestUnknownError(err) || + errors.Is(err, storage.ErrNoSuchImage) || + errors.Is(err, ocilayout.ImageNotFoundError{}) +} + +// isDockerManifestUnknownError is a copy of code from containers/image, +// please update there first. +func isDockerManifestUnknownError(err error) bool { + var ec dockerdistributionerrcode.ErrorCoder + if !errors.As(err, &ec) { + return false + } + return ec.ErrorCode() == dockerdistributionapi.ErrorCodeManifestUnknown +} diff --git a/docs/skopeo.1.md b/docs/skopeo.1.md index 26583f1160..53feab57db 100644 --- a/docs/skopeo.1.md +++ b/docs/skopeo.1.md @@ -114,6 +114,15 @@ Print the version number | [skopeo-standalone-verify(1)](skopeo-standalone-verify.1.md)| Verify an image signature. | | [skopeo-sync(1)](skopeo-sync.1.md)| Synchronize images between registry repositories and local directories. | +## EXIT STATUS +`skopeo` exits with status 0 on success, non-zero on error. + +Details about the exit statuses: + +**1** Generic error, details can be found in the error message. + +**2** The input image cannot be found. Note that this is best effort and for remote registries the status often cannot be reliably reported. + ## FILES **/etc/containers/policy.json** Default trust policy file, if **--policy** is not specified. diff --git a/systemtest/010-inspect.bats b/systemtest/010-inspect.bats index 548174da48..6a8ad80989 100644 --- a/systemtest/010-inspect.bats +++ b/systemtest/010-inspect.bats @@ -129,4 +129,11 @@ END_EXPECT expect_output --from="$repo_tags" "" "inspect --no-tags was expected to return empty RepoTags[]" } +@test "inspect: image unknown" { + # non existing image + run_skopeo 2 inspect containers-storage:non-existing-tag + expect_output --substring "identifier is not an image" \ + "skopeo inspect containers-storage:010101010101" +} + # vim: filetype=sh diff --git a/systemtest/040-local-registry-auth.bats b/systemtest/040-local-registry-auth.bats index 34e5c68560..e2880bf74b 100644 --- a/systemtest/040-local-registry-auth.bats +++ b/systemtest/040-local-registry-auth.bats @@ -40,7 +40,7 @@ function setup() { expect_output --substring "authentication required" # Correct creds, but no such image - run_skopeo 1 inspect --tls-verify=false --creds=$testuser:$testpassword \ + run_skopeo 2 inspect --tls-verify=false --creds=$testuser:$testpassword \ docker://localhost:5000/nonesuch expect_output --substring "manifest unknown" diff --git a/systemtest/060-delete.bats b/systemtest/060-delete.bats index 09e6688a66..70b3a191b1 100644 --- a/systemtest/060-delete.bats +++ b/systemtest/060-delete.bats @@ -24,7 +24,7 @@ function setup() { run_skopeo delete --tls-verify=false $localimg # make sure image is removed from registry - expected_rc=1 + expected_rc=2 run_skopeo $expected_rc inspect --tls-verify=false $localimg }