github.com/buildpacks/pack@v0.33.3-0.20240516162812-884dd1837311/pkg/client/manifest_create_test.go (about) 1 package client 2 3 import ( 4 "bytes" 5 "context" 6 "os" 7 "path/filepath" 8 "testing" 9 10 "github.com/buildpacks/imgutil" 11 "github.com/golang/mock/gomock" 12 "github.com/google/go-containerregistry/pkg/authn" 13 "github.com/google/go-containerregistry/pkg/v1/types" 14 "github.com/heroku/color" 15 "github.com/sclevine/spec" 16 "github.com/sclevine/spec/report" 17 18 ifakes "github.com/buildpacks/pack/internal/fakes" 19 "github.com/buildpacks/pack/pkg/logging" 20 "github.com/buildpacks/pack/pkg/testmocks" 21 h "github.com/buildpacks/pack/testhelpers" 22 ) 23 24 func TestCreateManifest(t *testing.T) { 25 color.Disable(true) 26 defer color.Disable(false) 27 spec.Run(t, "build", testCreateManifest, spec.Report(report.Terminal{})) 28 } 29 30 func testCreateManifest(t *testing.T, when spec.G, it spec.S) { 31 var ( 32 mockController *gomock.Controller 33 mockIndexFactory *testmocks.MockIndexFactory 34 fakeImageFetcher *ifakes.FakeImageFetcher 35 out bytes.Buffer 36 logger logging.Logger 37 subject *Client 38 err error 39 tmpDir string 40 ) 41 42 it.Before(func() { 43 fakeImageFetcher = ifakes.NewFakeImageFetcher() 44 logger = logging.NewLogWithWriters(&out, &out, logging.WithVerbose()) 45 mockController = gomock.NewController(t) 46 mockIndexFactory = testmocks.NewMockIndexFactory(mockController) 47 48 tmpDir, err = os.MkdirTemp("", "add-manifest-test") 49 h.AssertNil(t, err) 50 os.Setenv("XDG_RUNTIME_DIR", tmpDir) 51 52 subject, err = NewClient( 53 WithLogger(logger), 54 WithFetcher(fakeImageFetcher), 55 WithIndexFactory(mockIndexFactory), 56 WithExperimental(true), 57 WithKeychain(authn.DefaultKeychain), 58 ) 59 h.AssertSameInstance(t, mockIndexFactory, subject.indexFactory) 60 h.AssertNil(t, err) 61 }) 62 it.After(func() { 63 mockController.Finish() 64 h.AssertNil(t, os.RemoveAll(tmpDir)) 65 }) 66 67 when("#CreateManifest", func() { 68 var indexRepoName string 69 when("index doesn't exist", func() { 70 var indexLocalPath string 71 72 when("remote manifest is provided", func() { 73 it.Before(func() { 74 fakeImage := h.NewFakeWithRandomUnderlyingV1Image(t, "pack/image", nil) 75 fakeImageFetcher.RemoteImages["index.docker.io/library/busybox:1.36-musl"] = fakeImage 76 }) 77 78 when("publish is false", func() { 79 it.Before(func() { 80 // We want to actually create an index, so no need to mock the index factory 81 subject, err = NewClient( 82 WithLogger(logger), 83 WithFetcher(fakeImageFetcher), 84 WithExperimental(true), 85 WithKeychain(authn.DefaultKeychain), 86 ) 87 }) 88 89 when("no errors on save", func() { 90 it.Before(func() { 91 indexRepoName = h.NewRandomIndexRepoName() 92 indexLocalPath = filepath.Join(tmpDir, imgutil.MakeFileSafeName(indexRepoName)) 93 }) 94 95 when("no media type is provided", func() { 96 it("creates the index adding the manifest", func() { 97 err = subject.CreateManifest( 98 context.TODO(), 99 CreateManifestOptions{ 100 IndexRepoName: indexRepoName, 101 RepoNames: []string{"busybox:1.36-musl"}, 102 }, 103 ) 104 h.AssertNil(t, err) 105 index := h.ReadIndexManifest(t, indexLocalPath) 106 h.AssertEq(t, len(index.Manifests), 1) 107 // By default uses OCI media-types 108 h.AssertEq(t, index.MediaType, types.OCIImageIndex) 109 }) 110 }) 111 112 when("media type is provided", func() { 113 it("creates the index adding the manifest", func() { 114 err = subject.CreateManifest( 115 context.TODO(), 116 CreateManifestOptions{ 117 IndexRepoName: indexRepoName, 118 RepoNames: []string{"busybox:1.36-musl"}, 119 Format: types.DockerManifestList, 120 }, 121 ) 122 h.AssertNil(t, err) 123 index := h.ReadIndexManifest(t, indexLocalPath) 124 h.AssertEq(t, len(index.Manifests), 1) 125 h.AssertEq(t, index.MediaType, types.DockerManifestList) 126 }) 127 }) 128 }) 129 }) 130 131 when("publish is true", func() { 132 var index *h.MockImageIndex 133 134 when("no errors on save", func() { 135 it.Before(func() { 136 indexRepoName = h.NewRandomIndexRepoName() 137 indexLocalPath = filepath.Join(tmpDir, imgutil.MakeFileSafeName(indexRepoName)) 138 139 // index stub return to check if push operation was called 140 index = h.NewMockImageIndex(t, indexRepoName, 0, 0) 141 142 // We need to mock the index factory to inject a stub index to be pushed. 143 mockIndexFactory.EXPECT().Exists(gomock.Eq(indexRepoName)).Return(false) 144 mockIndexFactory.EXPECT().CreateIndex(gomock.Eq(indexRepoName), gomock.Any()).Return(index, nil) 145 }) 146 147 it("creates the index adding the manifest and pushes it to the registry", func() { 148 err = subject.CreateManifest( 149 context.TODO(), 150 CreateManifestOptions{ 151 IndexRepoName: indexRepoName, 152 RepoNames: []string{"busybox:1.36-musl"}, 153 Publish: true, 154 }, 155 ) 156 h.AssertNil(t, err) 157 158 // index is not saved locally and push it to the registry 159 h.AssertPathDoesNotExists(t, indexLocalPath) 160 h.AssertTrue(t, index.PushCalled) 161 h.AssertTrue(t, index.PurgeOption) 162 }) 163 }) 164 }) 165 }) 166 167 when("no manifest is provided", func() { 168 when("no errors on save", func() { 169 it.Before(func() { 170 // We want to actually create an index, so no need to mock the index factory 171 subject, err = NewClient( 172 WithLogger(logger), 173 WithFetcher(fakeImageFetcher), 174 WithExperimental(true), 175 WithKeychain(authn.DefaultKeychain), 176 ) 177 178 indexRepoName = h.NewRandomIndexRepoName() 179 indexLocalPath = filepath.Join(tmpDir, imgutil.MakeFileSafeName(indexRepoName)) 180 }) 181 182 it("creates an empty index with OCI media-type", func() { 183 err = subject.CreateManifest( 184 context.TODO(), 185 CreateManifestOptions{ 186 IndexRepoName: indexRepoName, 187 Format: types.OCIImageIndex, 188 }, 189 ) 190 h.AssertNil(t, err) 191 index := h.ReadIndexManifest(t, indexLocalPath) 192 h.AssertEq(t, len(index.Manifests), 0) 193 h.AssertEq(t, index.MediaType, types.OCIImageIndex) 194 }) 195 196 it("creates an empty index with Docker media-type", func() { 197 err = subject.CreateManifest( 198 context.TODO(), 199 CreateManifestOptions{ 200 IndexRepoName: indexRepoName, 201 Format: types.DockerManifestList, 202 }, 203 ) 204 h.AssertNil(t, err) 205 index := h.ReadIndexManifest(t, indexLocalPath) 206 h.AssertEq(t, len(index.Manifests), 0) 207 h.AssertEq(t, index.MediaType, types.DockerManifestList) 208 }) 209 }) 210 }) 211 }) 212 213 when("index exists", func() { 214 it.Before(func() { 215 indexRepoName = h.NewRandomIndexRepoName() 216 217 // mock the index factory to simulate the index exists 218 mockIndexFactory.EXPECT().Exists(gomock.Eq(indexRepoName)).AnyTimes().Return(true) 219 }) 220 221 it("returns an error when index already exists", func() { 222 err = subject.CreateManifest( 223 context.TODO(), 224 CreateManifestOptions{ 225 IndexRepoName: indexRepoName, 226 }, 227 ) 228 h.AssertError(t, err, "already exists in local storage; use 'pack manifest remove' to remove it before creating a new manifest list with the same name") 229 }) 230 }) 231 }) 232 }