diff --git a/pkg/extensions/sync/remote.go b/pkg/extensions/sync/remote.go index f3f2a2b3..4dc760c0 100644 --- a/pkg/extensions/sync/remote.go +++ b/pkg/extensions/sync/remote.go @@ -93,6 +93,33 @@ func (registry *RemoteRegistry) GetImageReference(repo, reference string) (ref.R } func (registry *RemoteRegistry) headManifest(ctx context.Context, imageReference ref.Ref, +) (manifest.Manifest, error) { + /// check what error it gives when not found + man, err := registry.client.ManifestHead(ctx, imageReference) + if err != nil { + /* public registries may return 401 for image not found + they will try to check private registries as a fallback => 401 */ + if errors.Is(err, errs.ErrHTTPUnauthorized) { + registry.log.Info().Str("errorType", common.TypeOf(err)). + Str("repository", imageReference.Repository).Str("reference", imageReference.Reference). + Err(err).Msg("failed to get manifest: unauthorized") + + return nil, zerr.ErrUnauthorizedAccess + } else if errors.Is(err, errs.ErrNotFound) { + registry.log.Info().Str("errorType", common.TypeOf(err)). + Str("repository", imageReference.Repository).Str("reference", imageReference.Reference). + Err(err).Msg("failed to find manifest") + + return nil, zerr.ErrManifestNotFound + } + + return nil, err + } + + return man, nil +} + +func (registry *RemoteRegistry) getManifest(ctx context.Context, imageReference ref.Ref, ) (manifest.Manifest, error) { /// check what error it gives when not found man, err := registry.client.ManifestGet(ctx, imageReference) @@ -134,7 +161,7 @@ func (registry *RemoteRegistry) GetDigest(ctx context.Context, repo, tag string, return man.GetDescriptor().Digest, err } -// returns OCI remote digest, original remote digaest (unconverted), if it was converted. +// returns OCI remote digest, original remote digest (unconverted), if it was converted. func (registry *RemoteRegistry) GetOCIDigest(ctx context.Context, repo, tag string, ) (godigest.Digest, godigest.Digest, bool, error) { var isConverted bool @@ -146,7 +173,7 @@ func (registry *RemoteRegistry) GetOCIDigest(ctx context.Context, repo, tag stri return "", "", false, err } - man, err := registry.headManifest(ctx, imageReference) + man, err := registry.getManifest(ctx, imageReference) if err != nil { return "", "", false, err } diff --git a/pkg/extensions/sync/service.go b/pkg/extensions/sync/service.go index 83ac7333..ea1cc5b4 100644 --- a/pkg/extensions/sync/service.go +++ b/pkg/extensions/sync/service.go @@ -496,16 +496,16 @@ func (service *BaseService) syncRef(ctx context.Context, localRepo string, remot } // get "would be" digest of image after synced. -func (service *BaseService) getLocalStoredImageDigest(ctx context.Context, repo, tag string, +func (service *BaseService) computeLocalStoredImageDigest(ctx context.Context, repo, tag string, ) (godigest.Digest, godigest.Digest, bool, error) { var err error - var convertedDigest, remoteDigest godigest.Digest + var localDigest, remoteDigest godigest.Digest var isConverted bool if !service.config.PreserveDigest { - convertedDigest, remoteDigest, isConverted, err = service.remote.GetOCIDigest(ctx, repo, tag) + localDigest, remoteDigest, isConverted, err = service.remote.GetOCIDigest(ctx, repo, tag) if err != nil { service.log.Error().Err(err).Str("repository", repo).Str("reference", tag). Msg("failed to get upstream image manifest details") @@ -520,9 +520,12 @@ func (service *BaseService) getLocalStoredImageDigest(ctx context.Context, repo, return "", "", false, err } + + // preserve digest is true, so the local digest is same as remote + localDigest = remoteDigest } - return convertedDigest, remoteDigest, isConverted, nil + return localDigest, remoteDigest, isConverted, nil } func (service *BaseService) syncImage(ctx context.Context, localRepo, remoteRepo, tag string, @@ -543,7 +546,7 @@ func (service *BaseService) syncImage(ctx context.Context, localRepo, remoteRepo return err } - localDigest, remoteDigest, isConverted, err = service.getLocalStoredImageDigest(ctx, remoteRepo, tag) + localDigest, remoteDigest, isConverted, err = service.computeLocalStoredImageDigest(ctx, remoteRepo, tag) if err != nil { return err } diff --git a/pkg/extensions/sync/sync_test.go b/pkg/extensions/sync/sync_test.go index b6631a38..c0aac087 100644 --- a/pkg/extensions/sync/sync_test.go +++ b/pkg/extensions/sync/sync_test.go @@ -1308,203 +1308,83 @@ func TestSyncWithNonDistributableBlob(t *testing.T) { } func TestDockerImagesAreSkipped(t *testing.T) { - Convey("Verify docker images are skipped when they are already synced", t, func() { - updateDuration, _ := time.ParseDuration("30m") + testCases := []struct { + name string + preserveDigest bool + }{ + { + name: "preserveDigest and compat docker2s2 enabled", + preserveDigest: true, + }, + { + name: "preserve digest and compat docker2s2 disabled", + preserveDigest: false, + }, + } - sctlr, srcBaseURL, srcDir, _, _ := makeUpstreamServer(t, false, false) + for _, testCase := range testCases { + Convey("Verify docker images are skipped when they are already synced, preserveDigest: "+testCase.name, t, func() { + updateDuration, _ := time.ParseDuration("30m") - scm := test.NewControllerManager(sctlr) - scm.StartAndWait(sctlr.Config.HTTP.Port) + sctlr, srcBaseURL, srcDir, _, _ := makeUpstreamServer(t, false, false) - defer scm.StopServer() + scm := test.NewControllerManager(sctlr) + scm.StartAndWait(sctlr.Config.HTTP.Port) - var tlsVerify bool + defer scm.StopServer() - maxRetries := 1 - delay := 1 * time.Second + var tlsVerify bool - indexRepoName := "index" + maxRetries := 1 + delay := 1 * time.Second - syncRegistryConfig := syncconf.RegistryConfig{ - Content: []syncconf.Content{ - { - Prefix: testImage, + indexRepoName := "index" + + syncRegistryConfig := syncconf.RegistryConfig{ + Content: []syncconf.Content{ + { + Prefix: testImage, + }, + { + Prefix: indexRepoName, + }, }, - { - Prefix: indexRepoName, - }, - }, - URLs: []string{srcBaseURL}, - PollInterval: updateDuration, - TLSVerify: &tlsVerify, - CertDir: "", - MaxRetries: &maxRetries, - OnDemand: true, - RetryDelay: &delay, - } - - defaultVal := true - syncConfig := &syncconf.Config{ - Enable: &defaultVal, - Registries: []syncconf.RegistryConfig{syncRegistryConfig}, - } - - dctlr, destBaseURL, destDir, _ := makeDownstreamServer(t, false, syncConfig) - - Convey("skipping already synced docker image", func() { - // because we can not store images in docker format, modify the test image so that it has docker mediatype - indexContent, err := os.ReadFile(path.Join(srcDir, testImage, "index.json")) - So(err, ShouldBeNil) - So(indexContent, ShouldNotBeNil) - - var index ispec.Index - err = json.Unmarshal(indexContent, &index) - So(err, ShouldBeNil) - - var configBlobDigest godigest.Digest - - for idx, manifestDesc := range index.Manifests { - manifestContent, err := os.ReadFile(path.Join(srcDir, testImage, "blobs/sha256", manifestDesc.Digest.Encoded())) - So(err, ShouldBeNil) - - var manifest ispec.Manifest - - err = json.Unmarshal(manifestContent, &manifest) - So(err, ShouldBeNil) - - configBlobDigest = manifest.Config.Digest - - manifest.MediaType = dockerManifestMediaType - manifest.Config.MediaType = dockerManifestConfigMediaType - index.Manifests[idx].MediaType = dockerManifestMediaType - - for idx := range manifest.Layers { - manifest.Layers[idx].MediaType = dockerLayerMediaType - } - - manifestBuf, err := json.Marshal(manifest) - So(err, ShouldBeNil) - - manifestDigest := godigest.FromBytes(manifestBuf) - index.Manifests[idx].Digest = manifestDigest - - // write modified manifest, remove old one - err = os.WriteFile(path.Join(srcDir, testImage, "blobs/sha256", manifestDigest.Encoded()), - manifestBuf, storageConstants.DefaultFilePerms) - So(err, ShouldBeNil) - - err = os.Remove(path.Join(srcDir, testImage, "blobs/sha256", manifestDesc.Digest.Encoded())) - So(err, ShouldBeNil) + URLs: []string{srcBaseURL}, + PollInterval: updateDuration, + TLSVerify: &tlsVerify, + CertDir: "", + MaxRetries: &maxRetries, + OnDemand: true, + RetryDelay: &delay, + PreserveDigest: testCase.preserveDigest, } - indexBuf, err := json.Marshal(index) - So(err, ShouldBeNil) - - err = os.WriteFile(path.Join(srcDir, testImage, "index.json"), indexBuf, storageConstants.DefaultFilePerms) - So(err, ShouldBeNil) - - dcm := test.NewControllerManager(dctlr) - dcm.StartAndWait(dctlr.Config.HTTP.Port) - defer dcm.StopServer() - - resp, err := resty.R().Get(destBaseURL + "/v2/" + testImage + "/manifests/" + testImageTag) - So(err, ShouldBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusOK) - - // now it should be skipped - resp, err = resty.R().Get(destBaseURL + "/v2/" + testImage + "/manifests/" + testImageTag) - So(err, ShouldBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusOK) - - found, err := test.ReadLogFileAndSearchString(dctlr.Config.Log.Output, - "skipping image because it's already synced", 20*time.Second) - if err != nil { - panic(err) + defaultVal := true + syncConfig := &syncconf.Config{ + Enable: &defaultVal, + Registries: []syncconf.RegistryConfig{syncRegistryConfig}, } - if !found { - data, err := os.ReadFile(dctlr.Config.Log.Output) - So(err, ShouldBeNil) + dctlr, destBaseURL, destDir, _ := makeDownstreamServer(t, false, syncConfig) - t.Logf("downstream log: %s", string(data)) + if testCase.preserveDigest { + dctlr.Config.HTTP.Compat = append(dctlr.Config.HTTP.Compat, "docker2s2") } - So(found, ShouldBeTrue) + Convey("skipping already synced docker image", func() { + // because we can not store images in docker format, modify the test image so that it has docker mediatype + indexContent, err := os.ReadFile(path.Join(srcDir, testImage, "index.json")) + So(err, ShouldBeNil) + So(indexContent, ShouldNotBeNil) - Convey("trigger config blob upstream error", func() { - // remove synced image - err := os.RemoveAll(path.Join(destDir, testImage)) + var index ispec.Index + err = json.Unmarshal(indexContent, &index) So(err, ShouldBeNil) - err = os.Chmod(path.Join(srcDir, testImage, "blobs/sha256", configBlobDigest.Encoded()), 0o000) - So(err, ShouldBeNil) + var configBlobDigest godigest.Digest - resp, err = resty.R().Get(destBaseURL + "/v2/" + testImage + "/manifests/" + testImageTag) - So(err, ShouldBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusNotFound) - }) - }) - - Convey("skipping already synced multiarch docker image", func() { - // create an image index on upstream - multiarchImage := CreateMultiarchWith().Images( - []Image{ - CreateRandomImage(), - CreateRandomImage(), - CreateRandomImage(), - CreateRandomImage(), - }, - ).Build() - - // upload the previously defined images - err := UploadMultiarchImage(multiarchImage, srcBaseURL, indexRepoName, "latest") - So(err, ShouldBeNil) - - resp, err := resty.R().SetHeader("Content-Type", ispec.MediaTypeImageIndex). - Get(srcBaseURL + "/v2/index/manifests/latest") - So(err, ShouldBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusOK) - So(resp.Body(), ShouldNotBeEmpty) - So(resp.Header().Get("Content-Type"), ShouldNotBeEmpty) - - // 'convert' oci multi arch image to docker multi arch - - indexContent, err := os.ReadFile(path.Join(srcDir, indexRepoName, "index.json")) - So(err, ShouldBeNil) - So(indexContent, ShouldNotBeNil) - - var newIndex ispec.Index - err = json.Unmarshal(indexContent, &newIndex) - So(err, ShouldBeNil) - - /* first find multiarch manifest in index.json - so that we can update both multiarch manifest and index.json at the same time*/ - var indexManifest ispec.Index - indexManifest.Manifests = make([]ispec.Descriptor, 4) - - var indexManifestIdx int - - for idx, manifestDesc := range newIndex.Manifests { - if manifestDesc.MediaType == ispec.MediaTypeImageIndex { - indexManifestContent, err := os.ReadFile(path.Join(srcDir, indexRepoName, "blobs/sha256", - manifestDesc.Digest.Encoded())) - So(err, ShouldBeNil) - - err = json.Unmarshal(indexManifestContent, &indexManifest) - So(err, ShouldBeNil) - indexManifestIdx = idx - } - } - - var ( - configBlobDigest godigest.Digest - indexManifestContent []byte - ) - - for idx, manifestDesc := range newIndex.Manifests { - if manifestDesc.MediaType == ispec.MediaTypeImageManifest { - manifestContent, err := os.ReadFile(path.Join(srcDir, indexRepoName, "blobs/sha256", - manifestDesc.Digest.Encoded())) + for idx, manifestDesc := range index.Manifests { + manifestContent, err := os.ReadFile(path.Join(srcDir, testImage, "blobs/sha256", manifestDesc.Digest.Encoded())) So(err, ShouldBeNil) var manifest ispec.Manifest @@ -1516,8 +1396,7 @@ func TestDockerImagesAreSkipped(t *testing.T) { manifest.MediaType = dockerManifestMediaType manifest.Config.MediaType = dockerManifestConfigMediaType - newIndex.Manifests[idx].MediaType = dockerManifestMediaType - indexManifest.Manifests[idx].MediaType = dockerManifestMediaType + index.Manifests[idx].MediaType = dockerManifestMediaType for idx := range manifest.Layers { manifest.Layers[idx].MediaType = dockerLayerMediaType @@ -1527,86 +1406,237 @@ func TestDockerImagesAreSkipped(t *testing.T) { So(err, ShouldBeNil) manifestDigest := godigest.FromBytes(manifestBuf) - newIndex.Manifests[idx].Digest = manifestDigest - indexManifest.Manifests[idx].Digest = manifestDigest + index.Manifests[idx].Digest = manifestDigest // write modified manifest, remove old one - err = os.WriteFile(path.Join(srcDir, indexRepoName, "blobs/sha256", manifestDigest.Encoded()), + err = os.WriteFile(path.Join(srcDir, testImage, "blobs/sha256", manifestDigest.Encoded()), manifestBuf, storageConstants.DefaultFilePerms) So(err, ShouldBeNil) - err = os.Remove(path.Join(srcDir, indexRepoName, "blobs/sha256", manifestDesc.Digest.Encoded())) + err = os.Remove(path.Join(srcDir, testImage, "blobs/sha256", manifestDesc.Digest.Encoded())) So(err, ShouldBeNil) } - indexManifest.MediaType = dockerIndexManifestMediaType - // write converted multi arch manifest - indexManifestContent, err = json.Marshal(indexManifest) + indexBuf, err := json.Marshal(index) So(err, ShouldBeNil) - err = os.WriteFile(path.Join(srcDir, indexRepoName, "blobs/sha256", - godigest.FromBytes(indexManifestContent).Encoded()), indexManifestContent, storageConstants.DefaultFilePerms) - So(err, ShouldBeNil) - } - - newIndex.Manifests[indexManifestIdx].MediaType = dockerIndexManifestMediaType - newIndex.Manifests[indexManifestIdx].Digest = godigest.FromBytes(indexManifestContent) - - indexBuf, err := json.Marshal(newIndex) - So(err, ShouldBeNil) - - err = os.WriteFile(path.Join(srcDir, indexRepoName, "index.json"), indexBuf, storageConstants.DefaultFilePerms) - So(err, ShouldBeNil) - - dcm := test.NewControllerManager(dctlr) - dcm.StartAndWait(dctlr.Config.HTTP.Port) - defer dcm.StopServer() - - // sync - resp, err = resty.R().SetHeader("Content-Type", ispec.MediaTypeImageIndex). - Get(destBaseURL + "/v2/" + indexRepoName + "/manifests/" + "latest") - So(err, ShouldBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusOK) - So(resp.Body(), ShouldNotBeEmpty) - So(resp.Header().Get("Content-Type"), ShouldNotBeEmpty) - - // sync again, should skip - resp, err = resty.R().SetHeader("Content-Type", ispec.MediaTypeImageIndex). - Get(destBaseURL + "/v2/" + indexRepoName + "/manifests/" + "latest") - So(err, ShouldBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusOK) - So(resp.Body(), ShouldNotBeEmpty) - So(resp.Header().Get("Content-Type"), ShouldNotBeEmpty) - - found, err := test.ReadLogFileAndSearchString(dctlr.Config.Log.Output, - "skipping image because it's already synced", 20*time.Second) - if err != nil { - panic(err) - } - - if !found { - data, err := os.ReadFile(dctlr.Config.Log.Output) + err = os.WriteFile(path.Join(srcDir, testImage, "index.json"), indexBuf, storageConstants.DefaultFilePerms) So(err, ShouldBeNil) - t.Logf("downstream log: %s", string(data)) - } + dcm := test.NewControllerManager(dctlr) + dcm.StartAndWait(dctlr.Config.HTTP.Port) + defer dcm.StopServer() - So(found, ShouldBeTrue) - - Convey("trigger config blob upstream error", func() { - // remove synced image - err := os.RemoveAll(path.Join(destDir, indexRepoName)) + resp, err := resty.R().Get(destBaseURL + "/v2/" + testImage + "/manifests/" + testImageTag) So(err, ShouldBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusOK) - err = os.Chmod(path.Join(srcDir, indexRepoName, "blobs/sha256", configBlobDigest.Encoded()), 0o000) + // now it should be skipped + resp, err = resty.R().Get(destBaseURL + "/v2/" + testImage + "/manifests/" + testImageTag) So(err, ShouldBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusOK) - resp, err = resty.R().Get(destBaseURL + "/v2/" + indexRepoName + "/manifests/" + "latest") + found, err := test.ReadLogFileAndSearchString(dctlr.Config.Log.Output, + "skipping image because it's already synced", 20*time.Second) + if err != nil { + panic(err) + } + + if !found { + data, err := os.ReadFile(dctlr.Config.Log.Output) + So(err, ShouldBeNil) + + t.Logf("downstream log: %s", string(data)) + } + + So(found, ShouldBeTrue) + + // trigger not found + resp, err = resty.R().Get(destBaseURL + "/v2/" + testImage + "/manifests/" + "1.9") So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, http.StatusNotFound) + + Convey("trigger config blob upstream error", func() { + // remove synced image + err := os.RemoveAll(path.Join(destDir, testImage)) + So(err, ShouldBeNil) + + err = os.Chmod(path.Join(srcDir, testImage, "blobs/sha256", configBlobDigest.Encoded()), 0o000) + So(err, ShouldBeNil) + + resp, err = resty.R().Get(destBaseURL + "/v2/" + testImage + "/manifests/" + testImageTag) + So(err, ShouldBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusNotFound) + }) + }) + + Convey("skipping already synced multiarch docker image", func() { + // create an image index on upstream + multiarchImage := CreateMultiarchWith().Images( + []Image{ + CreateRandomImage(), + CreateRandomImage(), + CreateRandomImage(), + CreateRandomImage(), + }, + ).Build() + + // upload the previously defined images + err := UploadMultiarchImage(multiarchImage, srcBaseURL, indexRepoName, "latest") + So(err, ShouldBeNil) + + resp, err := resty.R().SetHeader("Content-Type", ispec.MediaTypeImageIndex). + Get(srcBaseURL + "/v2/index/manifests/latest") + So(err, ShouldBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusOK) + So(resp.Body(), ShouldNotBeEmpty) + So(resp.Header().Get("Content-Type"), ShouldNotBeEmpty) + + // 'convert' oci multi arch image to docker multi arch + indexContent, err := os.ReadFile(path.Join(srcDir, indexRepoName, "index.json")) + So(err, ShouldBeNil) + So(indexContent, ShouldNotBeNil) + + var newIndex ispec.Index + err = json.Unmarshal(indexContent, &newIndex) + So(err, ShouldBeNil) + + /* first find multiarch manifest in index.json + so that we can update both multiarch manifest and index.json at the same time*/ + var indexManifest ispec.Index + indexManifest.Manifests = make([]ispec.Descriptor, 4) + + var indexManifestIdx int + + for idx, manifestDesc := range newIndex.Manifests { + if manifestDesc.MediaType == ispec.MediaTypeImageIndex { + indexManifestContent, err := os.ReadFile(path.Join(srcDir, indexRepoName, "blobs/sha256", + manifestDesc.Digest.Encoded())) + So(err, ShouldBeNil) + + err = json.Unmarshal(indexManifestContent, &indexManifest) + So(err, ShouldBeNil) + indexManifestIdx = idx + } + } + + var ( + configBlobDigest godigest.Digest + indexManifestContent []byte + ) + + for idx, manifestDesc := range newIndex.Manifests { + if manifestDesc.MediaType == ispec.MediaTypeImageManifest { + manifestContent, err := os.ReadFile(path.Join(srcDir, indexRepoName, "blobs/sha256", + manifestDesc.Digest.Encoded())) + So(err, ShouldBeNil) + + var manifest ispec.Manifest + + err = json.Unmarshal(manifestContent, &manifest) + So(err, ShouldBeNil) + + configBlobDigest = manifest.Config.Digest + + manifest.MediaType = dockerManifestMediaType + manifest.Config.MediaType = dockerManifestConfigMediaType + newIndex.Manifests[idx].MediaType = dockerManifestMediaType + indexManifest.Manifests[idx].MediaType = dockerManifestMediaType + + for idx := range manifest.Layers { + manifest.Layers[idx].MediaType = dockerLayerMediaType + } + + manifestBuf, err := json.Marshal(manifest) + So(err, ShouldBeNil) + + manifestDigest := godigest.FromBytes(manifestBuf) + newIndex.Manifests[idx].Digest = manifestDigest + indexManifest.Manifests[idx].Digest = manifestDigest + + // write modified manifest, remove old one + err = os.WriteFile(path.Join(srcDir, indexRepoName, "blobs/sha256", manifestDigest.Encoded()), + manifestBuf, storageConstants.DefaultFilePerms) + So(err, ShouldBeNil) + + err = os.Remove(path.Join(srcDir, indexRepoName, "blobs/sha256", manifestDesc.Digest.Encoded())) + So(err, ShouldBeNil) + } + + indexManifest.MediaType = dockerIndexManifestMediaType + // write converted multi arch manifest + indexManifestContent, err = json.Marshal(indexManifest) + So(err, ShouldBeNil) + + err = os.WriteFile(path.Join(srcDir, indexRepoName, "blobs/sha256", + godigest.FromBytes(indexManifestContent).Encoded()), indexManifestContent, storageConstants.DefaultFilePerms) + So(err, ShouldBeNil) + } + + newIndex.Manifests[indexManifestIdx].MediaType = dockerIndexManifestMediaType + newIndex.Manifests[indexManifestIdx].Digest = godigest.FromBytes(indexManifestContent) + + indexBuf, err := json.Marshal(newIndex) + So(err, ShouldBeNil) + + err = os.WriteFile(path.Join(srcDir, indexRepoName, "index.json"), indexBuf, storageConstants.DefaultFilePerms) + So(err, ShouldBeNil) + + dcm := test.NewControllerManager(dctlr) + dcm.StartAndWait(dctlr.Config.HTTP.Port) + defer dcm.StopServer() + + // sync + resp, err = resty.R().SetHeader("Content-Type", ispec.MediaTypeImageIndex). + Get(destBaseURL + "/v2/" + indexRepoName + "/manifests/" + "latest") + So(err, ShouldBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusOK) + So(resp.Body(), ShouldNotBeEmpty) + So(resp.Header().Get("Content-Type"), ShouldNotBeEmpty) + + // sync again, should skip + resp, err = resty.R().SetHeader("Content-Type", ispec.MediaTypeImageIndex). + Get(destBaseURL + "/v2/" + indexRepoName + "/manifests/" + "latest") + So(err, ShouldBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusOK) + So(resp.Body(), ShouldNotBeEmpty) + So(resp.Header().Get("Content-Type"), ShouldNotBeEmpty) + + found, err := test.ReadLogFileAndSearchString(dctlr.Config.Log.Output, + "skipping image because it's already synced", 20*time.Second) + if err != nil { + panic(err) + } + + if !found { + data, err := os.ReadFile(dctlr.Config.Log.Output) + So(err, ShouldBeNil) + + t.Logf("downstream log: %s", string(data)) + } + + So(found, ShouldBeTrue) + + // trigger not found + resp, err = resty.R().Get(destBaseURL + "/v2/" + indexRepoName + "/manifests/" + "1.9") + So(err, ShouldBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusNotFound) + + Convey("trigger config blob upstream error", func() { + // remove synced image + err := os.RemoveAll(path.Join(destDir, indexRepoName)) + So(err, ShouldBeNil) + + err = os.Chmod(path.Join(srcDir, indexRepoName, "blobs/sha256", configBlobDigest.Encoded()), 0o000) + So(err, ShouldBeNil) + + resp, err = resty.R().Get(destBaseURL + "/v2/" + indexRepoName + "/manifests/" + "latest") + So(err, ShouldBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusNotFound) + }) }) }) - }) + } } func TestPeriodically(t *testing.T) {