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  }