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  }