diff --git a/go.mod b/go.mod index 0d7699cdc..3b3570868 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,6 @@ require ( github.com/go-logr/logr v1.4.2 github.com/google/go-cmp v0.6.0 github.com/google/go-containerregistry v0.20.2 - github.com/olareg/olareg v0.1.1 github.com/onsi/ginkgo/v2 v2.20.2 github.com/onsi/gomega v1.34.2 github.com/opencontainers/go-digest v1.0.0 diff --git a/go.sum b/go.sum index 5c9316e6f..124f295ce 100644 --- a/go.sum +++ b/go.sum @@ -513,8 +513,6 @@ github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/olareg/olareg v0.1.1 h1:Ui7q93zjcoF+U9U71sgqgZWByDoZOpqHitUXEu2xV+g= -github.com/olareg/olareg v0.1.1/go.mod h1:w8NP4SWrHHtxsFaUiv1lnCnYPm4sN1seCd2h7FK/dc0= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= diff --git a/internal/controllers/clusterextension_controller.go b/internal/controllers/clusterextension_controller.go index faa3a7d5d..4272e05e5 100644 --- a/internal/controllers/clusterextension_controller.go +++ b/internal/controllers/clusterextension_controller.go @@ -110,8 +110,7 @@ func (r *ClusterExtensionReconciler) Reconcile(ctx context.Context, req ctrl.Req } reconciledExt := existingExt.DeepCopy() - res, err := r.reconcile(ctx, reconciledExt) - updateError := err + res, reconcileErr := r.reconcile(ctx, reconciledExt) // Do checks before any Update()s, as Update() may modify the resource structure! updateStatus := !equality.Semantic.DeepEqual(existingExt.Status, reconciledExt.Status) @@ -130,18 +129,18 @@ func (r *ClusterExtensionReconciler) Reconcile(ctx context.Context, req ctrl.Req finalizers := reconciledExt.Finalizers if updateStatus { if err := r.Client.Status().Update(ctx, reconciledExt); err != nil { - updateError = errors.Join(updateError, fmt.Errorf("error updating status: %v", err)) + reconcileErr = errors.Join(reconcileErr, fmt.Errorf("error updating status: %v", err)) } } reconciledExt.Finalizers = finalizers if updateFinalizers { if err := r.Client.Update(ctx, reconciledExt); err != nil { - updateError = errors.Join(updateError, fmt.Errorf("error updating finalizers: %v", err)) + reconcileErr = errors.Join(reconcileErr, fmt.Errorf("error updating finalizers: %v", err)) } } - return res, updateError + return res, reconcileErr } // ensureAllConditionsWithReason checks that all defined condition types exist in the given ClusterExtension, diff --git a/internal/controllers/clusterextension_controller_test.go b/internal/controllers/clusterextension_controller_test.go index d38404b9c..b80ca59b0 100644 --- a/internal/controllers/clusterextension_controller_test.go +++ b/internal/controllers/clusterextension_controller_test.go @@ -18,6 +18,7 @@ import ( "k8s.io/apimachinery/pkg/util/rand" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/operator-framework/operator-registry/alpha/declcfg" @@ -94,74 +95,103 @@ func TestClusterExtensionResolutionFails(t *testing.T) { } func TestClusterExtensionResolutionSuccessfulUnpackFails(t *testing.T) { - cl, reconciler := newClientAndReconciler(t) - reconciler.Unpacker = &MockUnpacker{ - err: errors.New("unpack failure"), + type testCase struct { + name string + unpackErr error + expectTerminal bool } - - ctx := context.Background() - extKey := types.NamespacedName{Name: fmt.Sprintf("cluster-extension-test-%s", rand.String(8))} - - t.Log("When the cluster extension specifies a channel with version that exist") - t.Log("By initializing cluster state") - pkgName := "prometheus" - pkgVer := "1.0.0" - pkgChan := "beta" - namespace := fmt.Sprintf("test-ns-%s", rand.String(8)) - serviceAccount := fmt.Sprintf("test-sa-%s", rand.String(8)) - - clusterExtension := &ocv1alpha1.ClusterExtension{ - ObjectMeta: metav1.ObjectMeta{Name: extKey.Name}, - Spec: ocv1alpha1.ClusterExtensionSpec{ - Source: ocv1alpha1.SourceConfig{ - SourceType: "Catalog", - Catalog: &ocv1alpha1.CatalogSource{ - PackageName: pkgName, - Version: pkgVer, - Channels: []string{pkgChan}, - }, - }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: namespace, - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: serviceAccount, - }, - }, + for _, tc := range []testCase{ + { + name: "non-terminal unpack failure", + unpackErr: errors.New("unpack failure"), }, + { + name: "terminal unpack failure", + unpackErr: reconcile.TerminalError(errors.New("terminal unpack failure")), + expectTerminal: true, + }, + } { + t.Run(tc.name, func(t *testing.T) { + cl, reconciler := newClientAndReconciler(t) + reconciler.Unpacker = &MockUnpacker{ + err: tc.unpackErr, + } + + ctx := context.Background() + extKey := types.NamespacedName{Name: fmt.Sprintf("cluster-extension-test-%s", rand.String(8))} + + t.Log("When the cluster extension specifies a channel with version that exist") + t.Log("By initializing cluster state") + pkgName := "prometheus" + pkgVer := "1.0.0" + pkgChan := "beta" + namespace := fmt.Sprintf("test-ns-%s", rand.String(8)) + serviceAccount := fmt.Sprintf("test-sa-%s", rand.String(8)) + + clusterExtension := &ocv1alpha1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{Name: extKey.Name}, + Spec: ocv1alpha1.ClusterExtensionSpec{ + Source: ocv1alpha1.SourceConfig{ + SourceType: "Catalog", + Catalog: &ocv1alpha1.CatalogSource{ + PackageName: pkgName, + Version: pkgVer, + Channels: []string{pkgChan}, + }, + }, + Install: ocv1alpha1.ClusterExtensionInstallConfig{ + Namespace: namespace, + ServiceAccount: ocv1alpha1.ServiceAccountReference{ + Name: serviceAccount, + }, + }, + }, + } + err := cl.Create(ctx, clusterExtension) + require.NoError(t, err) + + t.Log("It sets resolution success status") + t.Log("By running reconcile") + reconciler.Resolver = resolve.Func(func(_ context.Context, _ *ocv1alpha1.ClusterExtension, _ *ocv1alpha1.BundleMetadata) (*declcfg.Bundle, *bsemver.Version, *declcfg.Deprecation, error) { + v := bsemver.MustParse("1.0.0") + return &declcfg.Bundle{ + Name: "prometheus.v1.0.0", + Package: "prometheus", + Image: "quay.io/operatorhubio/prometheus@fake1.0.0", + }, &v, nil, nil + }) + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: extKey}) + require.Equal(t, ctrl.Result{}, res) + require.Error(t, err) + + isTerminal := errors.Is(err, reconcile.TerminalError(nil)) + assert.Equal(t, tc.expectTerminal, isTerminal, "expected terminal error: %v, got: %v", tc.expectTerminal, isTerminal) + assert.ErrorContains(t, err, tc.unpackErr.Error()) + + t.Log("By fetching updated cluster extension after reconcile") + require.NoError(t, cl.Get(ctx, extKey, clusterExtension)) + + t.Log("By checking the status fields") + expectedBundleMetadata := ocv1alpha1.BundleMetadata{Name: "prometheus.v1.0.0", Version: "1.0.0"} + require.Equal(t, expectedBundleMetadata, clusterExtension.Status.Resolution.Bundle) + require.Empty(t, clusterExtension.Status.Install) + + t.Log("By checking the expected conditions") + expectStatus := metav1.ConditionTrue + expectReason := ocv1alpha1.ReasonRetrying + if tc.expectTerminal { + expectStatus = metav1.ConditionFalse + expectReason = ocv1alpha1.ReasonBlocked + } + progressingCond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeProgressing) + require.NotNil(t, progressingCond) + require.Equal(t, expectStatus, progressingCond.Status) + require.Equal(t, expectReason, progressingCond.Reason) + require.Contains(t, progressingCond.Message, fmt.Sprintf("for resolved bundle %q with version %q", expectedBundleMetadata.Name, expectedBundleMetadata.Version)) + + require.NoError(t, cl.DeleteAllOf(ctx, &ocv1alpha1.ClusterExtension{})) + }) } - err := cl.Create(ctx, clusterExtension) - require.NoError(t, err) - - t.Log("It sets resolution success status") - t.Log("By running reconcile") - reconciler.Resolver = resolve.Func(func(_ context.Context, _ *ocv1alpha1.ClusterExtension, _ *ocv1alpha1.BundleMetadata) (*declcfg.Bundle, *bsemver.Version, *declcfg.Deprecation, error) { - v := bsemver.MustParse("1.0.0") - return &declcfg.Bundle{ - Name: "prometheus.v1.0.0", - Package: "prometheus", - Image: "quay.io/operatorhubio/prometheus@fake1.0.0", - }, &v, nil, nil - }) - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: extKey}) - require.Equal(t, ctrl.Result{}, res) - require.Error(t, err) - - t.Log("By fetching updated cluster extension after reconcile") - require.NoError(t, cl.Get(ctx, extKey, clusterExtension)) - - t.Log("By checking the status fields") - expectedBundleMetadata := ocv1alpha1.BundleMetadata{Name: "prometheus.v1.0.0", Version: "1.0.0"} - require.Equal(t, expectedBundleMetadata, clusterExtension.Status.Resolution.Bundle) - require.Empty(t, clusterExtension.Status.Install) - - t.Log("By checking the expected conditions") - progressingCond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeProgressing) - require.NotNil(t, progressingCond) - require.Equal(t, metav1.ConditionTrue, progressingCond.Status) - require.Equal(t, ocv1alpha1.ReasonRetrying, progressingCond.Reason) - require.Contains(t, progressingCond.Message, fmt.Sprintf("for resolved bundle %q with version %q", expectedBundleMetadata.Name, expectedBundleMetadata.Version)) - - require.NoError(t, cl.DeleteAllOf(ctx, &ocv1alpha1.ClusterExtension{})) } func TestClusterExtensionUnpackUnexpectedState(t *testing.T) { diff --git a/internal/rukpak/source/containers_image.go b/internal/rukpak/source/containers_image.go index 2394e5f30..2248e6477 100644 --- a/internal/rukpak/source/containers_image.go +++ b/internal/rukpak/source/containers_image.go @@ -19,6 +19,7 @@ import ( "github.com/containers/image/v5/pkg/compression" "github.com/containers/image/v5/signature" "github.com/containers/image/v5/types" + "github.com/go-logr/logr" "github.com/opencontainers/go-digest" "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/reconcile" @@ -45,14 +46,9 @@ func (i *ContainersImageRegistry) Unpack(ctx context.Context, bundle *BundleSour // Resolve a canonical reference for the image. // ////////////////////////////////////////////////////// - imgRef, err := reference.ParseNamed(bundle.Image.Ref) + imgRef, canonicalRef, _, err := resolveReferences(ctx, bundle.Image.Ref, i.SourceContext) if err != nil { - return nil, reconcile.TerminalError(fmt.Errorf("error parsing image reference %q: %w", bundle.Image.Ref, err)) - } - - canonicalRef, err := resolveCanonicalRef(ctx, imgRef, i.SourceContext) - if err != nil { - return nil, fmt.Errorf("error resolving canonical reference: %w", err) + return nil, err } ////////////////////////////////////////////////////// @@ -64,7 +60,7 @@ func (i *ContainersImageRegistry) Unpack(ctx context.Context, bundle *BundleSour unpackPath := i.unpackPath(bundle.Name, canonicalRef.Digest()) if unpackStat, err := os.Stat(unpackPath); err == nil { if !unpackStat.IsDir() { - return nil, fmt.Errorf("unexpected file at unpack path %q: expected a directory", unpackPath) + panic(fmt.Sprintf("unexpected file at unpack path %q: expected a directory", unpackPath)) } l.Info("image already unpacked", "ref", imgRef.String(), "digest", canonicalRef.Digest().String()) return successResult(bundle.Name, unpackPath, canonicalRef), nil @@ -89,7 +85,11 @@ func (i *ContainersImageRegistry) Unpack(ctx context.Context, bundle *BundleSour if err != nil { return nil, fmt.Errorf("error creating temporary directory: %w", err) } - defer os.RemoveAll(layoutDir) + defer func() { + if err := os.RemoveAll(layoutDir); err != nil { + l.Error(err, "error removing temporary OCI layout directory") + } + }() layoutRef, err := layout.NewReference(layoutDir, canonicalRef.String()) if err != nil { @@ -102,17 +102,9 @@ func (i *ContainersImageRegistry) Unpack(ctx context.Context, bundle *BundleSour // a policy context for the image pull. // ////////////////////////////////////////////////////// - policy, err := signature.DefaultPolicy(i.SourceContext) - if os.IsNotExist(err) { - l.Info("no default policy found, using insecure policy") - policy, err = signature.NewPolicyFromBytes([]byte(`{"default":[{"type":"insecureAcceptAnything"}]}`)) - } - if err != nil { - return nil, fmt.Errorf("error getting policy: %w", err) - } - policyContext, err := signature.NewPolicyContext(policy) + policyContext, err := loadPolicyContext(i.SourceContext, l) if err != nil { - return nil, fmt.Errorf("error getting policy context: %w", err) + return nil, fmt.Errorf("error loading policy context: %w", err) } defer func() { if err := policyContext.Destroy(); err != nil { @@ -138,6 +130,9 @@ func (i *ContainersImageRegistry) Unpack(ctx context.Context, bundle *BundleSour // ////////////////////////////////////////////////////// if err := i.unpackImage(ctx, unpackPath, layoutRef); err != nil { + if cleanupErr := deleteRecursive(unpackPath); cleanupErr != nil { + err = errors.Join(err, cleanupErr) + } return nil, fmt.Errorf("error unpacking image: %w", err) } @@ -163,7 +158,7 @@ func successResult(bundleName, unpackPath string, canonicalRef reference.Canonic } func (i *ContainersImageRegistry) Cleanup(_ context.Context, bundle *BundleSource) error { - return os.RemoveAll(i.bundlePath(bundle.Name)) + return deleteRecursive(i.bundlePath(bundle.Name)) } func (i *ContainersImageRegistry) bundlePath(bundleName string) string { @@ -174,31 +169,60 @@ func (i *ContainersImageRegistry) unpackPath(bundleName string, digest digest.Di return filepath.Join(i.bundlePath(bundleName), digest.String()) } -func resolveCanonicalRef(ctx context.Context, imgRef reference.Named, imageCtx *types.SystemContext) (reference.Canonical, error) { +func resolveReferences(ctx context.Context, ref string, sourceContext *types.SystemContext) (reference.Named, reference.Canonical, bool, error) { + imgRef, err := reference.ParseNamed(ref) + if err != nil { + return nil, nil, false, reconcile.TerminalError(fmt.Errorf("error parsing image reference %q: %w", ref, err)) + } + + canonicalRef, isCanonical, err := resolveCanonicalRef(ctx, imgRef, sourceContext) + if err != nil { + return nil, nil, false, fmt.Errorf("error resolving canonical reference: %w", err) + } + return imgRef, canonicalRef, isCanonical, nil +} + +func resolveCanonicalRef(ctx context.Context, imgRef reference.Named, imageCtx *types.SystemContext) (reference.Canonical, bool, error) { if canonicalRef, ok := imgRef.(reference.Canonical); ok { - return canonicalRef, nil + return canonicalRef, true, nil } srcRef, err := docker.NewReference(imgRef) if err != nil { - return nil, reconcile.TerminalError(fmt.Errorf("error creating reference: %w", err)) + return nil, false, reconcile.TerminalError(fmt.Errorf("error creating reference: %w", err)) } imgSrc, err := srcRef.NewImageSource(ctx, imageCtx) if err != nil { - return nil, fmt.Errorf("error creating image source: %w", err) + return nil, false, fmt.Errorf("error creating image source: %w", err) } defer imgSrc.Close() imgManifestData, _, err := imgSrc.GetManifest(ctx, nil) if err != nil { - return nil, fmt.Errorf("error getting manifest: %w", err) + return nil, false, fmt.Errorf("error getting manifest: %w", err) } imgDigest, err := manifest.Digest(imgManifestData) if err != nil { - return nil, fmt.Errorf("error getting digest of manifest: %w", err) + return nil, false, fmt.Errorf("error getting digest of manifest: %w", err) + } + canonicalRef, err := reference.WithDigest(reference.TrimNamed(imgRef), imgDigest) + if err != nil { + return nil, false, fmt.Errorf("error creating canonical reference: %w", err) + } + return canonicalRef, false, nil +} + +func loadPolicyContext(sourceContext *types.SystemContext, l logr.Logger) (*signature.PolicyContext, error) { + policy, err := signature.DefaultPolicy(sourceContext) + if os.IsNotExist(err) { + l.Info("no default policy found, using insecure policy") + policy, err = signature.NewPolicyFromBytes([]byte(`{"default":[{"type":"insecureAcceptAnything"}]}`)) + } + if err != nil { + return nil, fmt.Errorf("error loading default policy: %w", err) } - return reference.WithDigest(reference.TrimNamed(imgRef), imgDigest) + return signature.NewPolicyContext(policy) } func (i *ContainersImageRegistry) unpackImage(ctx context.Context, unpackPath string, imageReference types.ImageReference) error { @@ -217,7 +241,7 @@ func (i *ContainersImageRegistry) unpackImage(ctx context.Context, unpackPath st return fmt.Errorf("error creating image source: %w", err) } - if err := os.MkdirAll(unpackPath, 0755); err != nil { + if err := os.MkdirAll(unpackPath, 0700); err != nil { return fmt.Errorf("error creating unpack directory: %w", err) } l := log.FromContext(ctx) @@ -236,9 +260,12 @@ func (i *ContainersImageRegistry) unpackImage(ctx context.Context, unpackPath st l.Info("applied layer", "layer", i) return nil }(); err != nil { - return errors.Join(err, os.RemoveAll(unpackPath)) + return errors.Join(err, deleteRecursive(unpackPath)) } } + if err := setReadOnlyRecursive(unpackPath); err != nil { + return fmt.Errorf("error making unpack directory read-only: %w", err) + } return nil } @@ -249,13 +276,17 @@ func applyLayer(ctx context.Context, unpackPath string, layer io.ReadCloser) err } defer decompressed.Close() - _, err = archive.Apply(ctx, unpackPath, decompressed, archive.WithFilter(func(h *tar.Header) (bool, error) { + _, err = archive.Apply(ctx, unpackPath, decompressed, archive.WithFilter(applyLayerFilter())) + return err +} + +func applyLayerFilter() archive.Filter { + return func(h *tar.Header) (bool, error) { h.Uid = os.Getuid() h.Gid = os.Getgid() - h.Mode |= 0770 + h.Mode |= 0700 return true, nil - })) - return err + } } func (i *ContainersImageRegistry) deleteOtherImages(bundleName string, digestToKeep digest.Digest) error { @@ -269,9 +300,65 @@ func (i *ContainersImageRegistry) deleteOtherImages(bundleName string, digestToK continue } imgDirPath := filepath.Join(bundlePath, imgDir.Name()) - if err := os.RemoveAll(imgDirPath); err != nil { + if err := deleteRecursive(imgDirPath); err != nil { return fmt.Errorf("error removing image directory: %w", err) } } return nil } + +func setReadOnlyRecursive(root string) error { + if err := filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error { + if err != nil { + return err + } + + fi, err := d.Info() + if err != nil { + return err + } + + if err := func() error { + switch typ := fi.Mode().Type(); typ { + case os.ModeSymlink: + // do not follow symlinks + // 1. if they resolve to other locations in the root, we'll find them anyway + // 2. if they resolve to other locations outside the root, we don't want to change their permissions + return nil + case os.ModeDir: + return os.Chmod(path, 0500) + case 0: // regular file + return os.Chmod(path, 0400) + default: + return fmt.Errorf("refusing to change ownership of file %q with type %v", path, typ.String()) + } + }(); err != nil { + return err + } + return nil + }); err != nil { + return fmt.Errorf("error making bundle cache read-only: %w", err) + } + return nil +} + +func deleteRecursive(root string) error { + if err := filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error { + if os.IsNotExist(err) { + return nil + } + if err != nil { + return err + } + if !d.IsDir() { + return nil + } + if err := os.Chmod(path, 0700); err != nil { + return err + } + return nil + }); err != nil { + return fmt.Errorf("error making bundle cache writable for deletion: %w", err) + } + return os.RemoveAll(root) +} diff --git a/internal/rukpak/source/containers_image_test.go b/internal/rukpak/source/containers_image_test.go index 32b43d2fc..104a85721 100644 --- a/internal/rukpak/source/containers_image_test.go +++ b/internal/rukpak/source/containers_image_test.go @@ -15,8 +15,7 @@ import ( "github.com/containers/image/v5/pkg/sysregistriesv2" "github.com/containers/image/v5/types" "github.com/google/go-containerregistry/pkg/crane" - "github.com/olareg/olareg" - "github.com/olareg/olareg/config" + "github.com/google/go-containerregistry/pkg/registry" "github.com/opencontainers/go-digest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -62,6 +61,7 @@ func TestUnpackValidInsecure(t *testing.T) { assert.NoError(t, err) // Ensure the unpacked file matches the source content assert.Equal(t, []byte(testFileContents), unpackedFile) + assert.NoError(t, unpacker.Cleanup(context.Background(), bundleSource)) } func TestUnpackValidUsesCache(t *testing.T) { @@ -94,6 +94,7 @@ func TestUnpackValidUsesCache(t *testing.T) { // Make sure the original contents of the cache are still present. If the cached contents // were not used, we would expect the original contents to be removed. assert.DirExists(t, testCacheFilePath) + assert.NoError(t, unpacker.Cleanup(context.Background(), bundleSource)) } func TestUnpackCacheCheckError(t *testing.T) { @@ -271,8 +272,7 @@ func TestUnpackUnexpectedFile(t *testing.T) { require.NoError(t, os.WriteFile(unpackPath, []byte{}, 0600)) // Attempt to pull and unpack the image - _, err := unpacker.Unpack(context.Background(), bundleSource) - assert.ErrorContains(t, err, "expected a directory") + assert.Panics(t, func() { _, _ = unpacker.Unpack(context.Background(), bundleSource) }) } func TestUnpackCopySucceedsMountFails(t *testing.T) { @@ -327,12 +327,7 @@ func TestCleanup(t *testing.T) { } func setupRegistry(t *testing.T) (reference.NamedTagged, reference.Canonical, func()) { - regHandler := olareg.New(config.Config{ - Storage: config.ConfigStorage{ - StoreType: config.StoreMem, - }, - }) - server := httptest.NewServer(regHandler) + server := httptest.NewServer(registry.New()) serverURL, err := url.Parse(server.URL) require.NoError(t, err) @@ -353,7 +348,6 @@ func setupRegistry(t *testing.T) (reference.NamedTagged, reference.Canonical, fu cleanup := func() { server.Close() - require.NoError(t, regHandler.Close()) } return imageTagRef, imageDigestRef, cleanup }