github.com/buildpacks/pack@v0.33.3-0.20240516162812-884dd1837311/internal/commands/builder_create_test.go (about) 1 package commands_test 2 3 import ( 4 "bytes" 5 "fmt" 6 "os" 7 "path/filepath" 8 "testing" 9 10 "github.com/golang/mock/gomock" 11 "github.com/heroku/color" 12 "github.com/sclevine/spec" 13 "github.com/sclevine/spec/report" 14 "github.com/spf13/cobra" 15 16 "github.com/buildpacks/pack/builder" 17 "github.com/buildpacks/pack/internal/commands" 18 "github.com/buildpacks/pack/internal/commands/testmocks" 19 "github.com/buildpacks/pack/internal/config" 20 "github.com/buildpacks/pack/pkg/logging" 21 h "github.com/buildpacks/pack/testhelpers" 22 ) 23 24 const validConfig = ` 25 [[buildpacks]] 26 id = "some.buildpack" 27 28 [[order]] 29 [[order.group]] 30 id = "some.buildpack" 31 32 ` 33 34 const validConfigWithExtensions = ` 35 [[buildpacks]] 36 id = "some.buildpack" 37 38 [[extensions]] 39 id = "some.extension" 40 41 [[order]] 42 [[order.group]] 43 id = "some.buildpack" 44 45 [[order-extensions]] 46 [[order-extensions.group]] 47 id = "some.extension" 48 49 ` 50 51 var BuildConfigEnvSuffixNone = builder.BuildConfigEnv{ 52 Name: "suffixNone", 53 Value: "suffixNoneValue", 54 } 55 56 var BuildConfigEnvSuffixNoneWithEmptySuffix = builder.BuildConfigEnv{ 57 Name: "suffixNoneWithEmptySuffix", 58 Value: "suffixNoneWithEmptySuffixValue", 59 Suffix: "", 60 } 61 62 var BuildConfigEnvSuffixDefault = builder.BuildConfigEnv{ 63 Name: "suffixDefault", 64 Value: "suffixDefaultValue", 65 Suffix: "default", 66 } 67 68 var BuildConfigEnvSuffixOverride = builder.BuildConfigEnv{ 69 Name: "suffixOverride", 70 Value: "suffixOverrideValue", 71 Suffix: "override", 72 } 73 74 var BuildConfigEnvSuffixAppend = builder.BuildConfigEnv{ 75 Name: "suffixAppend", 76 Value: "suffixAppendValue", 77 Suffix: "append", 78 Delim: ":", 79 } 80 81 var BuildConfigEnvSuffixPrepend = builder.BuildConfigEnv{ 82 Name: "suffixPrepend", 83 Value: "suffixPrependValue", 84 Suffix: "prepend", 85 Delim: ":", 86 } 87 88 var BuildConfigEnvDelimWithoutSuffix = builder.BuildConfigEnv{ 89 Name: "delimWithoutSuffix", 90 Delim: ":", 91 } 92 93 var BuildConfigEnvSuffixUnknown = builder.BuildConfigEnv{ 94 Name: "suffixUnknown", 95 Value: "suffixUnknownValue", 96 Suffix: "unknown", 97 } 98 99 var BuildConfigEnvSuffixMultiple = []builder.BuildConfigEnv{ 100 { 101 Name: "MY_VAR", 102 Value: "suffixAppendValueValue", 103 Suffix: "append", 104 Delim: ";", 105 }, 106 { 107 Name: "MY_VAR", 108 Value: "suffixDefaultValue", 109 Suffix: "default", 110 Delim: "%", 111 }, 112 { 113 Name: "MY_VAR", 114 Value: "suffixPrependValue", 115 Suffix: "prepend", 116 Delim: ":", 117 }, 118 } 119 120 var BuildConfigEnvEmptyValue = builder.BuildConfigEnv{ 121 Name: "warning", 122 Value: "", 123 } 124 125 var BuildConfigEnvEmptyName = builder.BuildConfigEnv{ 126 Name: "", 127 Value: "suffixUnknownValue", 128 Suffix: "default", 129 } 130 131 var BuildConfigEnvSuffixPrependWithoutDelim = builder.BuildConfigEnv{ 132 Name: "suffixPrepend", 133 Value: "suffixPrependValue", 134 Suffix: "prepend", 135 } 136 137 var BuildConfigEnvDelimWithoutSuffixAppendOrPrepend = builder.BuildConfigEnv{ 138 Name: "delimWithoutActionAppendOrPrepend", 139 Value: "some-value", 140 Delim: ":", 141 } 142 143 var BuildConfigEnvDelimWithSameSuffixAndName = []builder.BuildConfigEnv{ 144 { 145 Name: "MY_VAR", 146 Value: "some-value", 147 Suffix: "", 148 }, 149 { 150 Name: "MY_VAR", 151 Value: "some-value", 152 }, 153 } 154 155 func TestCreateCommand(t *testing.T) { 156 color.Disable(true) 157 defer color.Disable(false) 158 spec.Run(t, "CreateCommand", testCreateCommand, spec.Parallel(), spec.Report(report.Terminal{})) 159 } 160 161 func testCreateCommand(t *testing.T, when spec.G, it spec.S) { 162 var ( 163 command *cobra.Command 164 logger logging.Logger 165 outBuf bytes.Buffer 166 mockController *gomock.Controller 167 mockClient *testmocks.MockPackClient 168 tmpDir string 169 builderConfigPath string 170 cfg config.Config 171 ) 172 173 it.Before(func() { 174 var err error 175 tmpDir, err = os.MkdirTemp("", "create-builder-test") 176 h.AssertNil(t, err) 177 builderConfigPath = filepath.Join(tmpDir, "builder.toml") 178 cfg = config.Config{} 179 180 mockController = gomock.NewController(t) 181 mockClient = testmocks.NewMockPackClient(mockController) 182 logger = logging.NewLogWithWriters(&outBuf, &outBuf) 183 command = commands.BuilderCreate(logger, cfg, mockClient) 184 }) 185 186 it.After(func() { 187 mockController.Finish() 188 }) 189 190 when("#Create", func() { 191 when("both --publish and pull-policy=never flags are specified", func() { 192 it("errors with a descriptive message", func() { 193 command.SetArgs([]string{ 194 "some/builder", 195 "--config", "some-config-path", 196 "--publish", 197 "--pull-policy", 198 "never", 199 }) 200 err := command.Execute() 201 h.AssertNotNil(t, err) 202 h.AssertError(t, err, "--publish and --pull-policy never cannot be used together. The --publish flag requires the use of remote images.") 203 }) 204 }) 205 206 when("--pull-policy", func() { 207 it("returns error for unknown policy", func() { 208 command.SetArgs([]string{ 209 "some/builder", 210 "--config", builderConfigPath, 211 "--pull-policy", "unknown-policy", 212 }) 213 h.AssertError(t, command.Execute(), "parsing pull policy") 214 }) 215 }) 216 217 when("--pull-policy is not specified", func() { 218 when("configured pull policy is invalid", func() { 219 it("errors when config set with unknown policy", func() { 220 cfg = config.Config{PullPolicy: "unknown-policy"} 221 command = commands.BuilderCreate(logger, cfg, mockClient) 222 command.SetArgs([]string{ 223 "some/builder", 224 "--config", builderConfigPath, 225 }) 226 h.AssertError(t, command.Execute(), "parsing pull policy") 227 }) 228 }) 229 }) 230 231 when("--buildpack-registry flag is specified but experimental isn't set in the config", func() { 232 it("errors with a descriptive message", func() { 233 command.SetArgs([]string{ 234 "some/builder", 235 "--config", "some-config-path", 236 "--buildpack-registry", "some-registry", 237 }) 238 err := command.Execute() 239 h.AssertNotNil(t, err) 240 h.AssertError(t, err, "Support for buildpack registries is currently experimental.") 241 }) 242 }) 243 244 when("warnings encountered in builder.toml", func() { 245 it.Before(func() { 246 h.AssertNil(t, os.WriteFile(builderConfigPath, []byte(` 247 [[buildpacks]] 248 id = "some.buildpack" 249 `), 0666)) 250 }) 251 252 it("logs the warnings", func() { 253 mockClient.EXPECT().CreateBuilder(gomock.Any(), gomock.Any()).Return(nil) 254 255 command.SetArgs([]string{ 256 "some/builder", 257 "--config", builderConfigPath, 258 }) 259 h.AssertNil(t, command.Execute()) 260 261 h.AssertContains(t, outBuf.String(), "Warning: builder configuration: empty 'order' definition") 262 }) 263 }) 264 265 when("uses --builder-config", func() { 266 it.Before(func() { 267 h.AssertNil(t, os.WriteFile(builderConfigPath, []byte(validConfig), 0666)) 268 }) 269 270 it("errors with a descriptive message", func() { 271 command.SetArgs([]string{ 272 "some/builder", 273 "--builder-config", builderConfigPath, 274 }) 275 h.AssertError(t, command.Execute(), "unknown flag: --builder-config") 276 }) 277 }) 278 279 when("#ParseBuildpackConfigEnv", func() { 280 it("should create envMap as expected when suffix is omitted", func() { 281 envMap, warnings, err := builder.ParseBuildConfigEnv([]builder.BuildConfigEnv{BuildConfigEnvSuffixNone}, "") 282 h.AssertEq(t, envMap, map[string]string{ 283 BuildConfigEnvSuffixNone.Name: BuildConfigEnvSuffixNone.Value, 284 }) 285 h.AssertEq(t, len(warnings), 0) 286 h.AssertNil(t, err) 287 }) 288 it("should create envMap as expected when suffix is empty string", func() { 289 envMap, warnings, err := builder.ParseBuildConfigEnv([]builder.BuildConfigEnv{BuildConfigEnvSuffixNoneWithEmptySuffix}, "") 290 h.AssertEq(t, envMap, map[string]string{ 291 BuildConfigEnvSuffixNoneWithEmptySuffix.Name: BuildConfigEnvSuffixNoneWithEmptySuffix.Value, 292 }) 293 h.AssertEq(t, len(warnings), 0) 294 h.AssertNil(t, err) 295 }) 296 it("should create envMap as expected when suffix is `default`", func() { 297 envMap, warnings, err := builder.ParseBuildConfigEnv([]builder.BuildConfigEnv{BuildConfigEnvSuffixDefault}, "") 298 h.AssertEq(t, envMap, map[string]string{ 299 BuildConfigEnvSuffixDefault.Name + "." + string(BuildConfigEnvSuffixDefault.Suffix): BuildConfigEnvSuffixDefault.Value, 300 }) 301 h.AssertEq(t, len(warnings), 0) 302 h.AssertNil(t, err) 303 }) 304 it("should create envMap as expected when suffix is `override`", func() { 305 envMap, warnings, err := builder.ParseBuildConfigEnv([]builder.BuildConfigEnv{BuildConfigEnvSuffixOverride}, "") 306 h.AssertEq(t, envMap, map[string]string{ 307 BuildConfigEnvSuffixOverride.Name + "." + string(BuildConfigEnvSuffixOverride.Suffix): BuildConfigEnvSuffixOverride.Value, 308 }) 309 h.AssertEq(t, len(warnings), 0) 310 h.AssertNil(t, err) 311 }) 312 it("should create envMap as expected when suffix is `append`", func() { 313 envMap, warnings, err := builder.ParseBuildConfigEnv([]builder.BuildConfigEnv{BuildConfigEnvSuffixAppend}, "") 314 h.AssertEq(t, envMap, map[string]string{ 315 BuildConfigEnvSuffixAppend.Name + "." + string(BuildConfigEnvSuffixAppend.Suffix): BuildConfigEnvSuffixAppend.Value, 316 BuildConfigEnvSuffixAppend.Name + ".delim": BuildConfigEnvSuffixAppend.Delim, 317 }) 318 h.AssertEq(t, len(warnings), 0) 319 h.AssertNil(t, err) 320 }) 321 it("should create envMap as expected when suffix is `prepend`", func() { 322 envMap, warnings, err := builder.ParseBuildConfigEnv([]builder.BuildConfigEnv{BuildConfigEnvSuffixPrepend}, "") 323 h.AssertEq(t, envMap, map[string]string{ 324 BuildConfigEnvSuffixPrepend.Name + "." + string(BuildConfigEnvSuffixPrepend.Suffix): BuildConfigEnvSuffixPrepend.Value, 325 BuildConfigEnvSuffixPrepend.Name + ".delim": BuildConfigEnvSuffixPrepend.Delim, 326 }) 327 h.AssertEq(t, len(warnings), 0) 328 h.AssertNil(t, err) 329 }) 330 it("should create envMap as expected when delim is specified", func() { 331 envMap, warnings, err := builder.ParseBuildConfigEnv([]builder.BuildConfigEnv{BuildConfigEnvDelimWithoutSuffix}, "") 332 h.AssertEq(t, envMap, map[string]string{ 333 BuildConfigEnvDelimWithoutSuffix.Name: BuildConfigEnvDelimWithoutSuffix.Value, 334 BuildConfigEnvDelimWithoutSuffix.Name + ".delim": BuildConfigEnvDelimWithoutSuffix.Delim, 335 }) 336 h.AssertNotEq(t, len(warnings), 0) 337 h.AssertNil(t, err) 338 }) 339 it("should create envMap with a warning when `value` is empty", func() { 340 envMap, warnings, err := builder.ParseBuildConfigEnv([]builder.BuildConfigEnv{BuildConfigEnvEmptyValue}, "") 341 h.AssertEq(t, envMap, map[string]string{ 342 BuildConfigEnvEmptyValue.Name: BuildConfigEnvEmptyValue.Value, 343 }) 344 h.AssertNotEq(t, len(warnings), 0) 345 h.AssertNil(t, err) 346 }) 347 it("should return an error when `name` is empty", func() { 348 envMap, warnings, err := builder.ParseBuildConfigEnv([]builder.BuildConfigEnv{BuildConfigEnvEmptyName}, "") 349 h.AssertEq(t, envMap, map[string]string(nil)) 350 h.AssertEq(t, len(warnings), 0) 351 h.AssertNotNil(t, err) 352 }) 353 it("should return warnings when `apprend` or `prepend` is used without `delim`", func() { 354 envMap, warnings, err := builder.ParseBuildConfigEnv([]builder.BuildConfigEnv{BuildConfigEnvSuffixPrependWithoutDelim}, "") 355 h.AssertEq(t, envMap, map[string]string{ 356 BuildConfigEnvSuffixPrependWithoutDelim.Name + "." + string(BuildConfigEnvSuffixPrependWithoutDelim.Suffix): BuildConfigEnvSuffixPrependWithoutDelim.Value, 357 }) 358 h.AssertNotEq(t, len(warnings), 0) 359 h.AssertNotNil(t, err) 360 }) 361 it("should return an error when unknown `suffix` is used", func() { 362 envMap, warnings, err := builder.ParseBuildConfigEnv([]builder.BuildConfigEnv{BuildConfigEnvSuffixUnknown}, "") 363 h.AssertEq(t, envMap, map[string]string{}) 364 h.AssertEq(t, len(warnings), 0) 365 h.AssertNotNil(t, err) 366 }) 367 it("should override with the last specified delim when `[[build.env]]` has multiple delims with same `name` with a `append` or `prepend` suffix", func() { 368 envMap, warnings, err := builder.ParseBuildConfigEnv(BuildConfigEnvSuffixMultiple, "") 369 h.AssertEq(t, envMap, map[string]string{ 370 BuildConfigEnvSuffixMultiple[0].Name + "." + string(BuildConfigEnvSuffixMultiple[0].Suffix): BuildConfigEnvSuffixMultiple[0].Value, 371 BuildConfigEnvSuffixMultiple[1].Name + "." + string(BuildConfigEnvSuffixMultiple[1].Suffix): BuildConfigEnvSuffixMultiple[1].Value, 372 BuildConfigEnvSuffixMultiple[2].Name + "." + string(BuildConfigEnvSuffixMultiple[2].Suffix): BuildConfigEnvSuffixMultiple[2].Value, 373 BuildConfigEnvSuffixMultiple[2].Name + ".delim": BuildConfigEnvSuffixMultiple[2].Delim, 374 }) 375 h.AssertNotEq(t, len(warnings), 0) 376 h.AssertNil(t, err) 377 }) 378 it("should override `value` with the last read value when a `[[build.env]]` has same `name` with same `suffix`", func() { 379 envMap, warnings, err := builder.ParseBuildConfigEnv(BuildConfigEnvDelimWithSameSuffixAndName, "") 380 h.AssertEq(t, envMap, map[string]string{ 381 BuildConfigEnvDelimWithSameSuffixAndName[1].Name: BuildConfigEnvDelimWithSameSuffixAndName[1].Value, 382 }) 383 h.AssertNotEq(t, len(warnings), 0) 384 h.AssertNil(t, err) 385 }) 386 }) 387 388 when("no config provided", func() { 389 it("errors with a descriptive message", func() { 390 command.SetArgs([]string{ 391 "some/builder", 392 }) 393 h.AssertError(t, command.Execute(), "Please provide a builder config path") 394 }) 395 }) 396 397 when("builder config has extensions but experimental isn't set in the config", func() { 398 it.Before(func() { 399 h.AssertNil(t, os.WriteFile(builderConfigPath, []byte(validConfigWithExtensions), 0666)) 400 }) 401 402 it("errors", func() { 403 command.SetArgs([]string{ 404 "some/builder", 405 "--config", builderConfigPath, 406 }) 407 h.AssertError(t, command.Execute(), "builder config contains image extensions; support for image extensions is currently experimental") 408 }) 409 }) 410 411 when("--flatten", func() { 412 it.Before(func() { 413 h.AssertNil(t, os.WriteFile(builderConfigPath, []byte(validConfig), 0666)) 414 }) 415 416 when("requested buildpack doesn't have format <buildpack>@<version>", func() { 417 it("errors with a descriptive message", func() { 418 command.SetArgs([]string{ 419 "some/builder", 420 "--config", builderConfigPath, 421 "--flatten", "some-buildpack", 422 }) 423 h.AssertError(t, command.Execute(), fmt.Sprintf("invalid format %s; please use '<buildpack-id>@<buildpack-version>' to add buildpacks to be flattened", "some-buildpack")) 424 }) 425 }) 426 }) 427 428 when("--label", func() { 429 when("can not be parsed", func() { 430 it("errors with a descriptive message", func() { 431 cmd := packageCommand() 432 cmd.SetArgs([]string{ 433 "some/builder", 434 "--config", builderConfigPath, 435 "--label", "name+value", 436 }) 437 438 err := cmd.Execute() 439 h.AssertNotNil(t, err) 440 h.AssertError(t, err, "invalid argument \"name+value\" for \"-l, --label\" flag: name+value must be formatted as key=value") 441 }) 442 }) 443 }) 444 }) 445 }