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 }