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

     1  package client_test
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"os"
     8  	"path/filepath"
     9  	"testing"
    10  
    11  	"github.com/buildpacks/imgutil/fakes"
    12  	"github.com/buildpacks/lifecycle/api"
    13  	"github.com/docker/docker/api/types/system"
    14  	"github.com/golang/mock/gomock"
    15  	"github.com/heroku/color"
    16  	"github.com/sclevine/spec"
    17  	"github.com/sclevine/spec/report"
    18  
    19  	pubbldpkg "github.com/buildpacks/pack/buildpackage"
    20  	ifakes "github.com/buildpacks/pack/internal/fakes"
    21  	"github.com/buildpacks/pack/pkg/blob"
    22  	"github.com/buildpacks/pack/pkg/client"
    23  	"github.com/buildpacks/pack/pkg/dist"
    24  	"github.com/buildpacks/pack/pkg/image"
    25  	"github.com/buildpacks/pack/pkg/logging"
    26  	"github.com/buildpacks/pack/pkg/testmocks"
    27  	h "github.com/buildpacks/pack/testhelpers"
    28  )
    29  
    30  func TestPackageExtension(t *testing.T) {
    31  	color.Disable(true)
    32  	defer color.Disable(false)
    33  	spec.Run(t, "PackageExtension", testPackageExtension, spec.Parallel(), spec.Report(report.Terminal{}))
    34  }
    35  
    36  func testPackageExtension(t *testing.T, when spec.G, it spec.S) {
    37  	var (
    38  		subject          *client.Client
    39  		mockController   *gomock.Controller
    40  		mockDownloader   *testmocks.MockBlobDownloader
    41  		mockImageFactory *testmocks.MockImageFactory
    42  		mockImageFetcher *testmocks.MockImageFetcher
    43  		mockDockerClient *testmocks.MockCommonAPIClient
    44  		out              bytes.Buffer
    45  	)
    46  
    47  	it.Before(func() {
    48  		mockController = gomock.NewController(t)
    49  		mockDownloader = testmocks.NewMockBlobDownloader(mockController)
    50  		mockImageFactory = testmocks.NewMockImageFactory(mockController)
    51  		mockImageFetcher = testmocks.NewMockImageFetcher(mockController)
    52  		mockDockerClient = testmocks.NewMockCommonAPIClient(mockController)
    53  
    54  		var err error
    55  		subject, err = client.NewClient(
    56  			client.WithLogger(logging.NewLogWithWriters(&out, &out)),
    57  			client.WithDownloader(mockDownloader),
    58  			client.WithImageFactory(mockImageFactory),
    59  			client.WithFetcher(mockImageFetcher),
    60  			client.WithDockerClient(mockDockerClient),
    61  		)
    62  		h.AssertNil(t, err)
    63  	})
    64  
    65  	it.After(func() {
    66  		mockController.Finish()
    67  	})
    68  
    69  	createExtension := func(descriptor dist.ExtensionDescriptor) string {
    70  		ex, err := ifakes.NewFakeExtensionBlob(&descriptor, 0644)
    71  		h.AssertNil(t, err)
    72  		url := fmt.Sprintf("https://example.com/ex.%s.tgz", h.RandString(12))
    73  		mockDownloader.EXPECT().Download(gomock.Any(), url).Return(ex, nil).AnyTimes()
    74  		return url
    75  	}
    76  
    77  	when("extension has issues", func() {
    78  		when("extension has no URI", func() {
    79  			it("should fail", func() {
    80  				err := subject.PackageExtension(context.TODO(), client.PackageBuildpackOptions{
    81  					Name: "Fake-Name",
    82  					Config: pubbldpkg.Config{
    83  						Platform:  dist.Platform{OS: "linux"},
    84  						Extension: dist.BuildpackURI{URI: ""},
    85  					},
    86  					Publish: true,
    87  				})
    88  				h.AssertError(t, err, "extension URI must be provided")
    89  			})
    90  		})
    91  
    92  		when("can't download extension", func() {
    93  			it("should fail", func() {
    94  				exURL := fmt.Sprintf("https://example.com/ex.%s.tgz", h.RandString(12))
    95  				mockDownloader.EXPECT().Download(gomock.Any(), exURL).Return(nil, image.ErrNotFound).AnyTimes()
    96  
    97  				err := subject.PackageExtension(context.TODO(), client.PackageBuildpackOptions{
    98  					Name: "Fake-Name",
    99  					Config: pubbldpkg.Config{
   100  						Platform:  dist.Platform{OS: "linux"},
   101  						Extension: dist.BuildpackURI{URI: exURL},
   102  					},
   103  					Publish: true,
   104  				})
   105  				h.AssertError(t, err, "downloading buildpack")
   106  			})
   107  		})
   108  
   109  		when("extension isn't a valid extension", func() {
   110  			it("should fail", func() {
   111  				fakeBlob := blob.NewBlob(filepath.Join("testdata", "empty-file"))
   112  				exURL := fmt.Sprintf("https://example.com/ex.%s.tgz", h.RandString(12))
   113  				mockDownloader.EXPECT().Download(gomock.Any(), exURL).Return(fakeBlob, nil).AnyTimes()
   114  
   115  				err := subject.PackageExtension(context.TODO(), client.PackageBuildpackOptions{
   116  					Name: "Fake-Name",
   117  					Config: pubbldpkg.Config{
   118  						Platform:  dist.Platform{OS: "linux"},
   119  						Extension: dist.BuildpackURI{URI: exURL},
   120  					},
   121  					Publish: true,
   122  				})
   123  				h.AssertError(t, err, "creating extension")
   124  			})
   125  		})
   126  	})
   127  
   128  	when("FormatImage", func() {
   129  		when("simple package for both OS formats (experimental only)", func() {
   130  			it("creates package image based on daemon OS", func() {
   131  				for _, daemonOS := range []string{"linux", "windows"} {
   132  					localMockDockerClient := testmocks.NewMockCommonAPIClient(mockController)
   133  					localMockDockerClient.EXPECT().Info(context.TODO()).Return(system.Info{OSType: daemonOS}, nil).AnyTimes()
   134  
   135  					packClientWithExperimental, err := client.NewClient(
   136  						client.WithDockerClient(localMockDockerClient),
   137  						client.WithDownloader(mockDownloader),
   138  						client.WithImageFactory(mockImageFactory),
   139  						client.WithExperimental(true),
   140  					)
   141  					h.AssertNil(t, err)
   142  
   143  					fakeImage := fakes.NewImage("basic/package-"+h.RandString(12), "", nil)
   144  					mockImageFactory.EXPECT().NewImage(fakeImage.Name(), true, daemonOS).Return(fakeImage, nil)
   145  
   146  					fakeBlob := blob.NewBlob(filepath.Join("testdata", "empty-file"))
   147  					exURL := fmt.Sprintf("https://example.com/ex.%s.tgz", h.RandString(12))
   148  					mockDownloader.EXPECT().Download(gomock.Any(), exURL).Return(fakeBlob, nil).AnyTimes()
   149  
   150  					h.AssertNil(t, packClientWithExperimental.PackageExtension(context.TODO(), client.PackageBuildpackOptions{
   151  						Format: client.FormatImage,
   152  						Name:   fakeImage.Name(),
   153  						Config: pubbldpkg.Config{
   154  							Platform: dist.Platform{OS: daemonOS},
   155  							Extension: dist.BuildpackURI{URI: createExtension(dist.ExtensionDescriptor{
   156  								WithAPI:  api.MustParse("0.2"),
   157  								WithInfo: dist.ModuleInfo{ID: "ex.basic", Version: "2.3.4"},
   158  							})},
   159  						},
   160  						PullPolicy: image.PullNever,
   161  					}))
   162  				}
   163  			})
   164  
   165  			it("fails without experimental on Windows daemons", func() {
   166  				windowsMockDockerClient := testmocks.NewMockCommonAPIClient(mockController)
   167  
   168  				packClientWithoutExperimental, err := client.NewClient(
   169  					client.WithDockerClient(windowsMockDockerClient),
   170  					client.WithExperimental(false),
   171  				)
   172  				h.AssertNil(t, err)
   173  
   174  				err = packClientWithoutExperimental.PackageExtension(context.TODO(), client.PackageBuildpackOptions{
   175  					Config: pubbldpkg.Config{
   176  						Platform: dist.Platform{
   177  							OS: "windows",
   178  						},
   179  					},
   180  				})
   181  				h.AssertError(t, err, "Windows extensionpackage support is currently experimental.")
   182  			})
   183  
   184  			it("fails for mismatched platform and daemon os", func() {
   185  				windowsMockDockerClient := testmocks.NewMockCommonAPIClient(mockController)
   186  				windowsMockDockerClient.EXPECT().Info(context.TODO()).Return(system.Info{OSType: "windows"}, nil).AnyTimes()
   187  
   188  				packClientWithoutExperimental, err := client.NewClient(
   189  					client.WithDockerClient(windowsMockDockerClient),
   190  					client.WithExperimental(false),
   191  				)
   192  				h.AssertNil(t, err)
   193  
   194  				err = packClientWithoutExperimental.PackageExtension(context.TODO(), client.PackageBuildpackOptions{
   195  					Config: pubbldpkg.Config{
   196  						Platform: dist.Platform{
   197  							OS: "linux",
   198  						},
   199  					},
   200  				})
   201  
   202  				h.AssertError(t, err, "invalid 'platform.os' specified: DOCKER_OS is 'windows'")
   203  			})
   204  		})
   205  	})
   206  
   207  	when("FormatFile", func() {
   208  		when("simple package for both OS formats (experimental only)", func() {
   209  			it("creates package image in either OS format", func() {
   210  				tmpDir, err := os.MkdirTemp("", "package-extension")
   211  				h.AssertNil(t, err)
   212  				defer os.Remove(tmpDir)
   213  
   214  				for _, imageOS := range []string{"linux", "windows"} {
   215  					localMockDockerClient := testmocks.NewMockCommonAPIClient(mockController)
   216  					localMockDockerClient.EXPECT().Info(context.TODO()).Return(system.Info{OSType: imageOS}, nil).AnyTimes()
   217  
   218  					packClientWithExperimental, err := client.NewClient(
   219  						client.WithDockerClient(localMockDockerClient),
   220  						client.WithLogger(logging.NewLogWithWriters(&out, &out)),
   221  						client.WithDownloader(mockDownloader),
   222  						client.WithExperimental(true),
   223  					)
   224  					h.AssertNil(t, err)
   225  
   226  					fakeBlob := blob.NewBlob(filepath.Join("testdata", "empty-file"))
   227  					exURL := fmt.Sprintf("https://example.com/ex.%s.tgz", h.RandString(12))
   228  					mockDownloader.EXPECT().Download(gomock.Any(), exURL).Return(fakeBlob, nil).AnyTimes()
   229  
   230  					packagePath := filepath.Join(tmpDir, h.RandString(12)+"-test.cnb")
   231  					h.AssertNil(t, packClientWithExperimental.PackageExtension(context.TODO(), client.PackageBuildpackOptions{
   232  						Format: client.FormatFile,
   233  						Name:   packagePath,
   234  						Config: pubbldpkg.Config{
   235  							Platform: dist.Platform{OS: imageOS},
   236  							Extension: dist.BuildpackURI{URI: createExtension(dist.ExtensionDescriptor{
   237  								WithAPI:  api.MustParse("0.2"),
   238  								WithInfo: dist.ModuleInfo{ID: "ex.basic", Version: "2.3.4"},
   239  							})},
   240  						},
   241  						PullPolicy: image.PullNever,
   242  					}))
   243  				}
   244  			})
   245  		})
   246  	})
   247  
   248  	when("unknown format is provided", func() {
   249  		it("should error", func() {
   250  			mockDockerClient.EXPECT().Info(context.TODO()).Return(system.Info{OSType: "linux"}, nil).AnyTimes()
   251  
   252  			err := subject.PackageExtension(context.TODO(), client.PackageBuildpackOptions{
   253  				Name:   "some-extension",
   254  				Format: "invalid-format",
   255  				Config: pubbldpkg.Config{
   256  					Platform: dist.Platform{OS: "linux"},
   257  					Extension: dist.BuildpackURI{URI: createExtension(dist.ExtensionDescriptor{
   258  						WithAPI:  api.MustParse("0.2"),
   259  						WithInfo: dist.ModuleInfo{ID: "ex.1", Version: "1.2.3"},
   260  					})},
   261  				},
   262  				Publish:    false,
   263  				PullPolicy: image.PullAlways,
   264  			})
   265  			h.AssertError(t, err, "unknown format: 'invalid-format'")
   266  		})
   267  	})
   268  }