diff --git a/.cirrus.yml b/.cirrus.yml index 7d44c28be..7d5eaba3e 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -6,9 +6,9 @@ env: #### Global variables used for all tasks #### # Name of the ultimate destination branch for this CI run - DEST_BRANCH: "main" + DEST_BRANCH: "release-5.22" # CI container image tag (c/skopeo branch name) - SKOPEO_CI_TAG: "main" + SKOPEO_CI_TAG: "release-1.10" # Use GO module mirror (reason unknown, travis did it this way) GOPROXY: https://proxy.golang.org # Overrides default location (/tmp/cirrus) for repo clone diff --git a/docker/docker_client.go b/docker/docker_client.go index c7ef38b9d..73e26a1ad 100644 --- a/docker/docker_client.go +++ b/docker/docker_client.go @@ -1,6 +1,7 @@ package docker import ( + "bytes" "context" "crypto/tls" "encoding/json" @@ -983,15 +984,25 @@ func (c *dockerClient) getOCIDescriptorContents(ctx context.Context, ref dockerR // isManifestUnknownError returns true iff err from fetchManifest is a “manifest unknown” error. func isManifestUnknownError(err error) bool { var errs errcode.Errors - if !errors.As(err, &errs) || len(errs) == 0 { - return false + if errors.As(err, &errs) && len(errs) != 0 { + firstErr := errs[0] + // docker/distribution, and as defined in the spec + var ec errcode.ErrorCoder + if errors.As(firstErr, &ec) && ec.ErrorCode() == v2.ErrorCodeManifestUnknown { + return true + } + // registry.redhat.io as of October 2022 + var e errcode.Error + if errors.As(firstErr, &e) && e.ErrorCode() == errcode.ErrorCodeUnknown && e.Message == "Not Found" { + return true + } } - err = errs[0] - ec, ok := err.(errcode.ErrorCoder) - if !ok { - return false + // ALSO registry.redhat.io as of October 2022 + var unexpected *unexpectedHTTPResponseError + if errors.As(err, &unexpected) && unexpected.StatusCode == http.StatusNotFound && bytes.Contains(unexpected.Response, []byte("Not found")) { + return true } - return ec.ErrorCode() == v2.ErrorCodeManifestUnknown + return false } // getSigstoreAttachmentManifest loads and parses the manifest for sigstore attachments for diff --git a/docker/docker_client_test.go b/docker/docker_client_test.go index a7c085215..da89cd427 100644 --- a/docker/docker_client_test.go +++ b/docker/docker_client_test.go @@ -1,6 +1,8 @@ package docker import ( + "bufio" + "bytes" "context" "errors" "fmt" @@ -328,3 +330,59 @@ func TestNeedsNoRetry(t *testing.T) { t.Fatal("Got the need to retry, but none should be required") } } + +func TestIsManifestUnknownError(t *testing.T) { + // Mostly a smoke test; we can add more registries here if they need special handling. + + for _, c := range []struct{ name, response string }{ + { + name: "docker.io when a tag in an _existing repo_ is not found", + response: "HTTP/1.1 404 Not Found\r\n" + + "Connection: close\r\n" + + "Content-Length: 109\r\n" + + "Content-Type: application/json\r\n" + + "Date: Thu, 12 Aug 2021 20:51:32 GMT\r\n" + + "Docker-Distribution-Api-Version: registry/2.0\r\n" + + "Ratelimit-Limit: 100;w=21600\r\n" + + "Ratelimit-Remaining: 100;w=21600\r\n" + + "Strict-Transport-Security: max-age=31536000\r\n" + + "\r\n" + + "{\"errors\":[{\"code\":\"MANIFEST_UNKNOWN\",\"message\":\"manifest unknown\",\"detail\":{\"Tag\":\"this-does-not-exist\"}}]}\n", + }, + { + name: "registry.redhat.io/v2/this-does-not-exist/manifests/latest", + response: "HTTP/1.1 404 Not Found\r\n" + + "Connection: close\r\n" + + "Content-Length: 53\r\n" + + "Cache-Control: max-age=0, no-cache, no-store\r\n" + + "Content-Type: application/json\r\n" + + "Date: Thu, 13 Oct 2022 18:15:15 GMT\r\n" + + "Expires: Thu, 13 Oct 2022 18:15:15 GMT\r\n" + + "Pragma: no-cache\r\n" + + "Server: Apache\r\n" + + "Strict-Transport-Security: max-age=63072000; includeSubdomains; preload\r\n" + + "X-Hostname: crane-tbr06.cran-001.prod.iad2.dc.redhat.com\r\n" + + "\r\n" + + "{\"errors\": [{\"code\": \"404\", \"message\": \"Not Found\"}]}\r\n", + }, + { + name: "registry.redhat.io/v2/rhosp15-rhel8/openstack-cron/manifests/sha256-8df5e60c42668706ac108b59c559b9187fa2de7e4e262e2967e3e9da35d5a8d7.sig", + response: "HTTP/1.1 404 Not Found\r\n" + + "Connection: close\r\n" + + "Content-Length: 10\r\n" + + "Accept-Ranges: bytes\r\n" + + "Date: Thu, 13 Oct 2022 18:13:53 GMT\r\n" + + "Server: AkamaiNetStorage\r\n" + + "X-Docker-Size: -1\r\n" + + "\r\n" + + "Not found\r\n", + }, + } { + resp, err := http.ReadResponse(bufio.NewReader(bytes.NewReader([]byte(c.response))), nil) + require.NoError(t, err, c.name) + err = fmt.Errorf("wrapped: %w", registryHTTPResponseToError(resp)) + + res := isManifestUnknownError(err) + assert.True(t, res, "%#v", err, c.name) + } +} diff --git a/docker/docker_image_dest.go b/docker/docker_image_dest.go index 44b45c472..bcbae7db1 100644 --- a/docker/docker_image_dest.go +++ b/docker/docker_image_dest.go @@ -653,6 +653,7 @@ func (d *dockerImageDestination) putSignaturesToSigstoreAttachments(ctx context. Digest: "", // We will fill this in later. Size: 0, }, nil) + ociConfig.RootFS.Type = "layers" } else { logrus.Debugf("Fetching sigstore attachment config %s", ociManifest.Config.Digest.String()) // We don’t benefit from a real BlobInfoCache here because we never try to reuse/mount configs. diff --git a/docker/errors.go b/docker/errors.go index 9c02e27e3..e1986554c 100644 --- a/docker/errors.go +++ b/docker/errors.go @@ -52,7 +52,8 @@ func registryHTTPResponseToError(res *http.Response) error { if len(response) > 50 { response = response[:50] + "..." } - err = fmt.Errorf("StatusCode: %d, %s", e.StatusCode, response) + // %.0w makes e visible to error.Unwrap() without including any text + err = fmt.Errorf("StatusCode: %d, %s%.0w", e.StatusCode, response, e) } return err }