diff --git a/cnb_index.go b/cnb_index.go index 52ef1489..4c51b0d7 100644 --- a/cnb_index.go +++ b/cnb_index.go @@ -1122,7 +1122,7 @@ func (h *CNBIndex) setImageURLs(img v1.Image, hash v1.Hash, urls []string) error // // If referencing an ImageIndex, will add Platform Specific Image from the Index. // Use IndexAddOptions to alter behaviour for ImageIndex Reference. -func (h *CNBIndex) Add(ref name.Reference, ops ...func(*IndexAddOptions) error) error { +func (h *CNBIndex) Add(name string, ops ...func(*IndexAddOptions) error) error { var addOps = &IndexAddOptions{} for _, op := range ops { op(addOps) @@ -1175,10 +1175,10 @@ func (h *CNBIndex) Add(ref name.Reference, ops ...func(*IndexAddOptions) error) // // This call is returns a v1.Descriptor with `Size`, `MediaType`, `Digest` fields only!! // This is a lightweight call used for checking MediaType of given Reference + ref, auth, err := referenceForRepoName(h.KeyChain, name, h.Insecure) desc, err := remote.Head( ref, - remote.WithAuthFromKeychain(h.KeyChain), - remote.WithTransport(GetTransport(h.Insecure)), + remote.WithAuth(auth), ) if err != nil { return err @@ -1193,8 +1193,7 @@ func (h *CNBIndex) Add(ref name.Reference, ops ...func(*IndexAddOptions) error) // Get the Full Image from remote if the given Reference refers an Image img, err := remote.Image( ref, - remote.WithAuthFromKeychain(h.KeyChain), - remote.WithTransport(GetTransport(h.Insecure)), + remote.WithAuth(auth), ) if err != nil { return err @@ -1957,3 +1956,23 @@ func indexMediaType(format types.MediaType) string { return "UNKNOWN" } } + +// TODO this method is duplicated from remote.new file +// referenceForRepoName +func referenceForRepoName(keychain authn.Keychain, ref string, insecure bool) (name.Reference, authn.Authenticator, error) { + var auth authn.Authenticator + opts := []name.Option{name.WeakValidation} + if insecure { + opts = append(opts, name.Insecure) + } + r, err := name.ParseReference(ref, opts...) + if err != nil { + return nil, nil, err + } + + auth, err = keychain.Resolve(r.Context().Registry) + if err != nil { + return nil, nil, err + } + return r, auth, nil +} diff --git a/index.go b/index.go index 8f580fbe..31b64eb5 100644 --- a/index.go +++ b/index.go @@ -8,33 +8,32 @@ import ( type ImageIndex interface { // getters - OS(digest name.Digest) (os string, err error) + Annotations(digest name.Digest) (annotations map[string]string, err error) Architecture(digest name.Digest) (arch string, err error) - Variant(digest name.Digest) (osVariant string, err error) - OSVersion(digest name.Digest) (osVersion string, err error) Features(digest name.Digest) (features []string, err error) + OS(digest name.Digest) (os string, err error) OSFeatures(digest name.Digest) (osFeatures []string, err error) - Annotations(digest name.Digest) (annotations map[string]string, err error) + OSVersion(digest name.Digest) (osVersion string, err error) URLs(digest name.Digest) (urls []string, err error) + Variant(digest name.Digest) (osVariant string, err error) // setters - SetOS(digest name.Digest, os string) error + SetAnnotations(digest name.Digest, annotations map[string]string) error SetArchitecture(digest name.Digest, arch string) error - SetVariant(digest name.Digest, osVariant string) error - SetOSVersion(digest name.Digest, osVersion string) error SetFeatures(digest name.Digest, features []string) error + SetOS(digest name.Digest, os string) error SetOSFeatures(digest name.Digest, osFeatures []string) error - SetAnnotations(digest name.Digest, annotations map[string]string) error + SetOSVersion(digest name.Digest, osVersion string) error SetURLs(digest name.Digest, urls []string) error + SetVariant(digest name.Digest, osVariant string) error // misc - // TODO change the name.Reference and expose String? - Add(ref name.Reference, ops ...func(options *IndexAddOptions) error) error - Save() error - Push(ops ...func(options *IndexPushOptions) error) error + Add(repoName string, ops ...func(options *IndexAddOptions) error) error + Delete() error Inspect() (string, error) + Push(ops ...func(options *IndexPushOptions) error) error Remove(ref name.Reference) error - Delete() error + Save() error } diff --git a/layout/layout_test.go b/layout/layout_test.go index 1b25c28b..e5de4b6a 100644 --- a/layout/layout_test.go +++ b/layout/layout_test.go @@ -8,6 +8,7 @@ import ( "testing" "time" + "github.com/google/go-containerregistry/pkg/authn" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/google/go-containerregistry/pkg/v1/types" @@ -16,17 +17,33 @@ import ( "github.com/buildpacks/imgutil" "github.com/buildpacks/imgutil/layout" + imgutilRemote "github.com/buildpacks/imgutil/remote" h "github.com/buildpacks/imgutil/testhelpers" ) // FIXME: relevant tests in this file should be moved into new_test.go and save_test.go to mirror the implementation func TestLayout(t *testing.T) { - spec.Run(t, "Image", testImage, spec.Parallel(), spec.Report(report.Terminal{})) + // spec.Run(t, "Image", testImage, spec.Parallel(), spec.Report(report.Terminal{})) + dockerConfigDir, err := os.MkdirTemp("", "test.docker.config.dir") + h.AssertNil(t, err) + defer os.RemoveAll(dockerConfigDir) + + dockerRegistry = h.NewDockerRegistry(h.WithAuth(dockerConfigDir)) + dockerRegistry.Start(t) + defer dockerRegistry.Stop(t) + + os.Setenv("DOCKER_CONFIG", dockerConfigDir) + defer os.Unsetenv("DOCKER_CONFIG") + spec.Run(t, "ImageIndex", testImageIndex, spec.Parallel(), spec.Report(report.Terminal{})) } -// global directory and paths -var testDataDir = filepath.Join("testdata", "layout") +var ( + dockerRegistry *h.DockerRegistry + + // global directory and paths + testDataDir = filepath.Join("testdata", "layout") +) func testImage(t *testing.T, when spec.G, it spec.S) { var ( @@ -1135,53 +1152,48 @@ func testImage(t *testing.T, when spec.G, it spec.S) { func testImageIndex(t *testing.T, when spec.G, it spec.S) { var ( - idx imgutil.ImageIndex - tempDir string - err error + idx imgutil.ImageIndex + tmpDir string + localPath string + baseIndexPath string + err error ) it.Before(func() { // creates the directory to save all the OCI images on disk - tempDir, err = os.MkdirTemp("", "layout-image-indexes") + tmpDir, err = os.MkdirTemp("", "layout-image-indexes") h.AssertNil(t, err) + + // image index directory on disk + baseIndexPath = filepath.Join(testDataDir, "busybox-multi-platform") + // global directory and paths + testDataDir = filepath.Join("testdata", "layout") }) it.After(func() { - err := os.RemoveAll(tempDir) + err := os.RemoveAll(tmpDir) h.AssertNil(t, err) }) when("#Save", func() { when("index exists on disk", func() { - var ( - baseIndexPath string - localPath string - ) - - it.Before(func() { - baseIndexPath = filepath.Join(testDataDir, "busybox-multi-platform") - }) - when("#FromBaseImageIndex", func() { it.Before(func() { - idx, err = layout.NewIndex("busybox-multi-platform", tempDir, imgutil.FromBaseImageIndex(baseIndexPath)) + idx, err = layout.NewIndex("busybox-multi-platform", tmpDir, imgutil.FromBaseImageIndex(baseIndexPath)) h.AssertNil(t, err) - localPath = filepath.Join(tempDir, "busybox-multi-platform") + localPath = filepath.Join(tmpDir, "busybox-multi-platform") }) - // Getters test cases - when("#Save", func() { - it("image index saved on disk with the data from the base index", func() { - err = idx.Save() - h.AssertNil(t, err) + it("manifests from base image index are saved on disk", func() { + err = idx.Save() + h.AssertNil(t, err) - // assert linux/amd64 and linux/arm64 manifests were saved - index := h.ReadIndexManifest(t, localPath) - h.AssertEq(t, len(index.Manifests), 2) - h.AssertEq(t, index.Manifests[0].Digest.String(), "sha256:4be429a5fbb2e71ae7958bfa558bc637cf3a61baf40a708cb8fff532b39e52d0") - h.AssertEq(t, index.Manifests[1].Digest.String(), "sha256:8a4415fb43600953cbdac6ec03c2d96d900bb21f8d78964837dad7f73b9afcdc") - }) + // assert linux/amd64 and linux/arm64 manifests were saved + index := h.ReadIndexManifest(t, localPath) + h.AssertEq(t, len(index.Manifests), 2) + h.AssertEq(t, index.Manifests[0].Digest.String(), "sha256:4be429a5fbb2e71ae7958bfa558bc637cf3a61baf40a708cb8fff532b39e52d0") + h.AssertEq(t, index.Manifests[1].Digest.String(), "sha256:8a4415fb43600953cbdac6ec03c2d96d900bb21f8d78964837dad7f73b9afcdc") }) }) @@ -1189,26 +1201,172 @@ func testImageIndex(t *testing.T, when spec.G, it spec.S) { it.Before(func() { localIndex := h.ReadImageIndex(t, baseIndexPath) - idx, err = layout.NewIndex("busybox-multi-platform", tempDir, imgutil.FromBaseImageIndexInstance(localIndex)) + idx, err = layout.NewIndex("busybox-multi-platform", tmpDir, imgutil.FromBaseImageIndexInstance(localIndex)) + h.AssertNil(t, err) + + localPath = filepath.Join(tmpDir, "busybox-multi-platform") + }) + + it("manifests from base image index instance are saved on disk", func() { + err = idx.Save() + h.AssertNil(t, err) + + // assert linux/amd64 and linux/arm64 manifests were saved + index := h.ReadIndexManifest(t, localPath) + h.AssertEq(t, len(index.Manifests), 2) + h.AssertEq(t, index.Manifests[0].Digest.String(), "sha256:4be429a5fbb2e71ae7958bfa558bc637cf3a61baf40a708cb8fff532b39e52d0") + h.AssertEq(t, index.Manifests[1].Digest.String(), "sha256:8a4415fb43600953cbdac6ec03c2d96d900bb21f8d78964837dad7f73b9afcdc") + }) + }) + }) + }) + + when("#Add", func() { + var ( + imagePath string + fullBaseImagePath string + ) + + it.Before(func() { + imagePath, err = os.MkdirTemp(tmpDir, "layout-test-image-index") + h.AssertNil(t, err) + + fullBaseImagePath = filepath.Join(testDataDir, "busybox") + }) + + when("index is created from scratch", func() { + it.Before(func() { + repoName := newRepoName() + idx = setUpImageIndex(t, repoName, tmpDir) + localPath = filepath.Join(tmpDir, repoName) + }) + + when("manifest is OCI layout format is added", func() { + var editableImage imgutil.EditableImage + it.Before(func() { + editableImage, err = layout.NewImage(imagePath, layout.FromBaseImagePath(fullBaseImagePath)) + h.AssertNil(t, err) + }) + + it("adds the manifest to the index", func() { + err = idx.Add("busybox", imgutil.WithLocalImage(editableImage)) h.AssertNil(t, err) - localPath = filepath.Join(tempDir, "busybox-multi-platform") + // manifest was added + index := h.ReadIndexManifest(t, localPath) + h.AssertEq(t, len(index.Manifests), 1) }) + }) + }) - // Getters test cases - when("#Save", func() { - it("image index saved on disk with the data from the base index", func() { - err = idx.Save() + when("index exists on disk", func() { + when("#FromBaseImageIndex", func() { + it.Before(func() { + idx = setUpImageIndex(t, "busybox-multi-platform", tmpDir, imgutil.FromBaseImageIndex(baseIndexPath)) + localPath = filepath.Join(tmpDir, "busybox-multi-platform") + }) + + when("manifest in OCI layout format is added", func() { + var editableImage imgutil.EditableImage + it.Before(func() { + editableImage, err = layout.NewImage(imagePath, layout.FromBaseImagePath(fullBaseImagePath)) + h.AssertNil(t, err) + }) + + it("adds the manifest to the index", func() { + err = idx.Add("busybox", imgutil.WithLocalImage(editableImage)) h.AssertNil(t, err) - // assert linux/amd64 and linux/arm64 manifests were saved index := h.ReadIndexManifest(t, localPath) - h.AssertEq(t, len(index.Manifests), 2) - h.AssertEq(t, index.Manifests[0].Digest.String(), "sha256:4be429a5fbb2e71ae7958bfa558bc637cf3a61baf40a708cb8fff532b39e52d0") - h.AssertEq(t, index.Manifests[1].Digest.String(), "sha256:8a4415fb43600953cbdac6ec03c2d96d900bb21f8d78964837dad7f73b9afcdc") + + // manifest was added + // initially it has 2 manifest + 1 new + h.AssertEq(t, len(index.Manifests), 3) }) }) }) }) }) + + when("#Push", func() { + when("index is created from scratch", func() { + it.Before(func() { + repoName := newTestImageIndexName("push-index-test") + idx = setUpImageIndex(t, repoName, tmpDir, imgutil.WithKeychain(authn.DefaultKeychain)) + + // TODO Note in the Push operation + // Note: It will only push IndexManifest, assuming all the images it refers exists in registry + // We need to push each individual image first] + + img1RepoName := fmt.Sprintf("%s:%s", repoName, "busybox-amd64") + img1, err := imgutilRemote.NewImage(img1RepoName, authn.DefaultKeychain, imgutilRemote.FromBaseImage("busybox@sha256:4be429a5fbb2e71ae7958bfa558bc637cf3a61baf40a708cb8fff532b39e52d0")) + h.AssertNil(t, err) + err = img1.Save() + h.AssertNil(t, err) + + err = idx.Add(img1RepoName) + h.AssertNil(t, err) + + img2RepoName := fmt.Sprintf("%s:%s", repoName, "busybox-arm64") + img2, err := imgutilRemote.NewImage(img2RepoName, authn.DefaultKeychain, imgutilRemote.FromBaseImage("busybox@sha256:8a4415fb43600953cbdac6ec03c2d96d900bb21f8d78964837dad7f73b9afcdc")) + h.AssertNil(t, err) + err = img2.Save() + h.AssertNil(t, err) + + err = idx.Add(img2RepoName) + h.AssertNil(t, err) + }) + + it("image index is pushed", func() { + err = idx.Push() + h.AssertNil(t, err) + }) + }) + }) + + when("#Delete", func() { + when("index exists on disk", func() { + when("#FromBaseImageIndex", func() { + it.Before(func() { + idx = setUpImageIndex(t, "busybox-multi-platform", tmpDir, imgutil.FromBaseImageIndex(baseIndexPath)) + localPath = filepath.Join(tmpDir, "busybox-multi-platform") + }) + + it("deletes the imange index from disk", func() { + // Verify the index exists + h.ReadIndexManifest(t, localPath) + + err = idx.Delete() + h.AssertNil(t, err) + + _, err = os.Stat(localPath) + h.AssertNotNil(t, err) + h.AssertEq(t, true, os.IsNotExist(err)) + }) + }) + }) + }) + /* + Push(ops ...func(options *IndexPushOptions) error) error + Inspect() (string, error) + Remove(ref name.Reference) error + */ +} + +func setUpImageIndex(t *testing.T, repoName string, tmpDir string, ops ...imgutil.Option) imgutil.ImageIndex { + idx, err := layout.NewIndex(repoName, tmpDir, ops...) + h.AssertNil(t, err) + + // TODO before adding something to the index, apparently we need initialize on disk + err = idx.Save() + h.AssertNil(t, err) + return idx +} + +func newRepoName() string { + return "test-layout-index-" + h.RandString(10) +} + +func newTestImageIndexName(name string) string { + return dockerRegistry.RepoName(name + "-" + h.RandString(10)) } diff --git a/layout/new_test.go b/layout/new_test.go index 928ad276..ecb927ae 100644 --- a/layout/new_test.go +++ b/layout/new_test.go @@ -20,20 +20,15 @@ func TestLayoutNewImageIndex(t *testing.T) { spec.Run(t, "LayoutNewImageIndex", testLayoutNewImageIndex, spec.Parallel(), spec.Report(report.Terminal{})) } -var ( - repoName = "some/index" -) - func testLayoutNewImageIndex(t *testing.T, when spec.G, it spec.S) { var ( idx imgutil.ImageIndex linuxAmd64Digest name.Digest linuxArm64Digest name.Digest - tempDir string - testDataDir string - - err error + tempDir string + repoName string + err error ) it.Before(func() { @@ -57,6 +52,10 @@ func testLayoutNewImageIndex(t *testing.T, when spec.G, it spec.S) { }) when("#NewIndex", func() { + it.Before(func() { + repoName = "some/index" + }) + when("index doesn't exists on disk", func() { it("creates empty image index", func() { idx, err = layout.NewIndex(