github.com/buildpacks/pack@v0.33.3-0.20240516162812-884dd1837311/pkg/client/manifest_annotate_test.go (about)

     1  package client
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"os"
     7  	"testing"
     8  
     9  	"github.com/buildpacks/imgutil"
    10  	"github.com/golang/mock/gomock"
    11  	"github.com/google/go-containerregistry/pkg/authn"
    12  	"github.com/google/go-containerregistry/pkg/name"
    13  	"github.com/heroku/color"
    14  	"github.com/pkg/errors"
    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  const invalidDigest = "sha256:d4707523ce6e12afdbe9a3be5ad69027150a834870ca0933baf7516dd1fe0f56"
    25  
    26  func TestAnnotateManifest(t *testing.T) {
    27  	color.Disable(true)
    28  	defer color.Disable(false)
    29  	spec.Run(t, "build", testAnnotateManifest, spec.Sequential(), spec.Report(report.Terminal{}))
    30  }
    31  
    32  func testAnnotateManifest(t *testing.T, when spec.G, it spec.S) {
    33  	var (
    34  		mockController   *gomock.Controller
    35  		mockIndexFactory *testmocks.MockIndexFactory
    36  		fakeImageFetcher *ifakes.FakeImageFetcher
    37  		out              bytes.Buffer
    38  		logger           logging.Logger
    39  		subject          *Client
    40  		err              error
    41  		tmpDir           string
    42  	)
    43  
    44  	it.Before(func() {
    45  		fakeImageFetcher = ifakes.NewFakeImageFetcher()
    46  		logger = logging.NewLogWithWriters(&out, &out, logging.WithVerbose())
    47  		mockController = gomock.NewController(t)
    48  		mockIndexFactory = testmocks.NewMockIndexFactory(mockController)
    49  
    50  		tmpDir, err = os.MkdirTemp("", "annotate-manifest-test")
    51  		h.AssertNil(t, err)
    52  		os.Setenv("XDG_RUNTIME_DIR", tmpDir)
    53  
    54  		subject, err = NewClient(
    55  			WithLogger(logger),
    56  			WithFetcher(fakeImageFetcher),
    57  			WithIndexFactory(mockIndexFactory),
    58  			WithExperimental(true),
    59  			WithKeychain(authn.DefaultKeychain),
    60  		)
    61  		h.AssertSameInstance(t, mockIndexFactory, subject.indexFactory)
    62  		h.AssertNil(t, err)
    63  	})
    64  	it.After(func() {
    65  		mockController.Finish()
    66  		os.RemoveAll(tmpDir)
    67  	})
    68  
    69  	when("#AnnotateManifest", func() {
    70  		var (
    71  			digest        name.Digest
    72  			idx           imgutil.ImageIndex
    73  			indexRepoName string
    74  		)
    75  		when("index doesn't exist", func() {
    76  			it.Before(func() {
    77  				indexRepoName = h.NewRandomIndexRepoName()
    78  				mockIndexFactory.EXPECT().LoadIndex(gomock.Any(), gomock.Any()).Return(nil, errors.New("index not found locally"))
    79  			})
    80  
    81  			it("should return an error", func() {
    82  				err = subject.AnnotateManifest(
    83  					context.TODO(),
    84  					ManifestAnnotateOptions{
    85  						IndexRepoName: indexRepoName,
    86  						RepoName:      "pack/image",
    87  					},
    88  				)
    89  				h.AssertEq(t, err.Error(), "index not found locally")
    90  			})
    91  		})
    92  
    93  		when("index exists", func() {
    94  			when("no errors on save", func() {
    95  				when("OS is given", func() {
    96  					it.Before(func() {
    97  						indexRepoName = h.NewRandomIndexRepoName()
    98  						idx, digest = h.RandomCNBIndexAndDigest(t, indexRepoName, 1, 2)
    99  						mockIndexFactory.EXPECT().LoadIndex(gomock.Eq(indexRepoName), gomock.Any()).Return(idx, nil)
   100  						fakeImage := h.NewFakeWithRandomUnderlyingV1Image(t, "pack/image", digest)
   101  						fakeImageFetcher.RemoteImages[digest.Name()] = fakeImage
   102  					})
   103  
   104  					it("should set OS for given image", func() {
   105  						err = subject.AnnotateManifest(
   106  							context.TODO(),
   107  							ManifestAnnotateOptions{
   108  								IndexRepoName: indexRepoName,
   109  								RepoName:      digest.Name(),
   110  								OS:            "some-os",
   111  							},
   112  						)
   113  						h.AssertNil(t, err)
   114  
   115  						os, err := idx.OS(digest)
   116  						h.AssertNil(t, err)
   117  						h.AssertEq(t, os, "some-os")
   118  					})
   119  				})
   120  				when("Arch is given", func() {
   121  					it.Before(func() {
   122  						indexRepoName = h.NewRandomIndexRepoName()
   123  						idx, digest = h.RandomCNBIndexAndDigest(t, indexRepoName, 1, 2)
   124  						mockIndexFactory.EXPECT().LoadIndex(gomock.Eq(indexRepoName), gomock.Any()).Return(idx, nil)
   125  						fakeImage := h.NewFakeWithRandomUnderlyingV1Image(t, "pack/image", digest)
   126  						fakeImageFetcher.RemoteImages[digest.Name()] = fakeImage
   127  					})
   128  
   129  					it("should set Arch for given image", func() {
   130  						err = subject.AnnotateManifest(
   131  							context.TODO(),
   132  							ManifestAnnotateOptions{
   133  								IndexRepoName: indexRepoName,
   134  								RepoName:      digest.Name(),
   135  								OSArch:        "some-arch",
   136  							},
   137  						)
   138  						h.AssertNil(t, err)
   139  
   140  						arch, err := idx.Architecture(digest)
   141  						h.AssertNil(t, err)
   142  						h.AssertEq(t, arch, "some-arch")
   143  					})
   144  				})
   145  				when("OS Variant is given", func() {
   146  					it.Before(func() {
   147  						indexRepoName = h.NewRandomIndexRepoName()
   148  						idx, digest = h.RandomCNBIndexAndDigest(t, indexRepoName, 1, 2)
   149  						mockIndexFactory.EXPECT().LoadIndex(gomock.Eq(indexRepoName), gomock.Any()).Return(idx, nil)
   150  						fakeImage := h.NewFakeWithRandomUnderlyingV1Image(t, "pack/image", digest)
   151  						fakeImageFetcher.RemoteImages[digest.Name()] = fakeImage
   152  					})
   153  
   154  					it("should set Variant for given image", func() {
   155  						err = subject.AnnotateManifest(
   156  							context.TODO(),
   157  							ManifestAnnotateOptions{
   158  								IndexRepoName: indexRepoName,
   159  								RepoName:      digest.Name(),
   160  								OSVariant:     "some-variant",
   161  							},
   162  						)
   163  						h.AssertNil(t, err)
   164  
   165  						variant, err := idx.Variant(digest)
   166  						h.AssertNil(t, err)
   167  						h.AssertEq(t, variant, "some-variant")
   168  					})
   169  				})
   170  				when("Annotations are given", func() {
   171  					it.Before(func() {
   172  						indexRepoName = h.NewRandomIndexRepoName()
   173  						idx, digest = h.RandomCNBIndexAndDigest(t, indexRepoName, 1, 2)
   174  						mockIndexFactory.EXPECT().LoadIndex(gomock.Eq(indexRepoName), gomock.Any()).Return(idx, nil)
   175  						fakeImage := h.NewFakeWithRandomUnderlyingV1Image(t, "pack/image", digest)
   176  						fakeImageFetcher.RemoteImages[digest.Name()] = fakeImage
   177  					})
   178  
   179  					it("should set Annotations for given image", func() {
   180  						err = subject.AnnotateManifest(
   181  							context.TODO(),
   182  							ManifestAnnotateOptions{
   183  								IndexRepoName: indexRepoName,
   184  								RepoName:      digest.Name(),
   185  								Annotations:   map[string]string{"some-key": "some-value"},
   186  							},
   187  						)
   188  						h.AssertNil(t, err)
   189  
   190  						annos, err := idx.Annotations(digest)
   191  						h.AssertNil(t, err)
   192  						h.AssertEq(t, annos, map[string]string{"some-key": "some-value"})
   193  					})
   194  
   195  					it("should save the annotated index", func() {
   196  						var (
   197  							fakeOS          = "some-os"
   198  							fakeArch        = "some-arch"
   199  							fakeVariant     = "some-variant"
   200  							fakeAnnotations = map[string]string{"some-key": "some-value"}
   201  						)
   202  
   203  						err = subject.AnnotateManifest(
   204  							context.TODO(),
   205  							ManifestAnnotateOptions{
   206  								IndexRepoName: indexRepoName,
   207  								RepoName:      digest.Name(),
   208  								OS:            fakeOS,
   209  								OSArch:        fakeArch,
   210  								OSVariant:     fakeVariant,
   211  								Annotations:   fakeAnnotations,
   212  							},
   213  						)
   214  						h.AssertNil(t, err)
   215  
   216  						err = idx.SaveDir()
   217  						h.AssertNil(t, err)
   218  
   219  						os, err := idx.OS(digest)
   220  						h.AssertNil(t, err)
   221  						h.AssertEq(t, os, fakeOS)
   222  
   223  						arch, err := idx.Architecture(digest)
   224  						h.AssertNil(t, err)
   225  						h.AssertEq(t, arch, fakeArch)
   226  
   227  						variant, err := idx.Variant(digest)
   228  						h.AssertNil(t, err)
   229  						h.AssertEq(t, variant, fakeVariant)
   230  
   231  						annos, err := idx.Annotations(digest)
   232  						h.AssertNil(t, err)
   233  						h.AssertEq(t, annos, fakeAnnotations)
   234  					})
   235  				})
   236  			})
   237  		})
   238  
   239  		when("image does not exist with given digest", func() {
   240  			var nonExistentDigest string
   241  
   242  			it.Before(func() {
   243  				indexRepoName = h.NewRandomIndexRepoName()
   244  				idx = h.RandomCNBIndex(t, indexRepoName, 1, 2)
   245  				nonExistentDigest = "busybox@" + invalidDigest
   246  				mockIndexFactory.EXPECT().LoadIndex(gomock.Eq(indexRepoName), gomock.Any()).Return(idx, nil)
   247  			})
   248  
   249  			it("errors for Arch", func() {
   250  				err = subject.AnnotateManifest(
   251  					context.TODO(),
   252  					ManifestAnnotateOptions{
   253  						IndexRepoName: indexRepoName,
   254  						RepoName:      nonExistentDigest,
   255  						OSArch:        "some-arch",
   256  					},
   257  				)
   258  				h.AssertNotNil(t, err)
   259  			})
   260  			it("errors for Variant", func() {
   261  				err = subject.AnnotateManifest(
   262  					context.TODO(),
   263  					ManifestAnnotateOptions{
   264  						IndexRepoName: indexRepoName,
   265  						RepoName:      nonExistentDigest,
   266  						OSVariant:     "some-variant",
   267  					},
   268  				)
   269  				h.AssertNotNil(t, err)
   270  			})
   271  			it("errors for Annotations", func() {
   272  				err = subject.AnnotateManifest(
   273  					context.TODO(),
   274  					ManifestAnnotateOptions{
   275  						IndexRepoName: indexRepoName,
   276  						RepoName:      nonExistentDigest,
   277  						Annotations:   map[string]string{"some-key": "some-value"},
   278  					},
   279  				)
   280  				h.AssertNotNil(t, err)
   281  			})
   282  		})
   283  	})
   284  }