github.com/buildpacks/pack@v0.33.3-0.20240516162812-884dd1837311/internal/commands/extension_package_test.go (about)

     1  package commands_test
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"testing"
     7  
     8  	"github.com/heroku/color"
     9  	"github.com/pkg/errors"
    10  	"github.com/sclevine/spec"
    11  	"github.com/sclevine/spec/report"
    12  	"github.com/spf13/cobra"
    13  
    14  	pubbldpkg "github.com/buildpacks/pack/buildpackage"
    15  	"github.com/buildpacks/pack/internal/commands"
    16  	"github.com/buildpacks/pack/internal/commands/fakes"
    17  	"github.com/buildpacks/pack/internal/config"
    18  	"github.com/buildpacks/pack/pkg/dist"
    19  	"github.com/buildpacks/pack/pkg/image"
    20  	"github.com/buildpacks/pack/pkg/logging"
    21  	h "github.com/buildpacks/pack/testhelpers"
    22  )
    23  
    24  func TestExtensionPackageCommand(t *testing.T) {
    25  	color.Disable(true)
    26  	defer color.Disable(false)
    27  	spec.Run(t, "ExtensionPackageCommand", testExtensionPackageCommand, spec.Parallel(), spec.Report(report.Terminal{}))
    28  }
    29  
    30  func testExtensionPackageCommand(t *testing.T, when spec.G, it spec.S) {
    31  	var (
    32  		logger *logging.LogWithWriters
    33  		outBuf bytes.Buffer
    34  	)
    35  
    36  	it.Before(func() {
    37  		logger = logging.NewLogWithWriters(&outBuf, &outBuf)
    38  	})
    39  
    40  	when("Package#Execute", func() {
    41  		var fakeExtensionPackager *fakes.FakeBuildpackPackager
    42  
    43  		it.Before(func() {
    44  			fakeExtensionPackager = &fakes.FakeBuildpackPackager{}
    45  		})
    46  
    47  		when("valid package config", func() {
    48  			it("reads package config from the configured path", func() {
    49  				fakePackageConfigReader := fakes.NewFakePackageConfigReader()
    50  				expectedPackageConfigPath := "/path/to/some/file"
    51  
    52  				cmd := packageExtensionCommand(
    53  					withExtensionPackageConfigReader(fakePackageConfigReader),
    54  					withExtensionPackageConfigPath(expectedPackageConfigPath),
    55  				)
    56  				err := cmd.Execute()
    57  				h.AssertNil(t, err)
    58  
    59  				h.AssertEq(t, fakePackageConfigReader.ReadCalledWithArg, expectedPackageConfigPath)
    60  			})
    61  
    62  			it("creates package with correct image name", func() {
    63  				cmd := packageExtensionCommand(
    64  					withExtensionImageName("my-specific-image"),
    65  					withExtensionPackager(fakeExtensionPackager),
    66  				)
    67  				err := cmd.Execute()
    68  				h.AssertNil(t, err)
    69  
    70  				receivedOptions := fakeExtensionPackager.CreateCalledWithOptions
    71  				h.AssertEq(t, receivedOptions.Name, "my-specific-image")
    72  			})
    73  
    74  			it("creates package with config returned by the reader", func() {
    75  				myConfig := pubbldpkg.Config{
    76  					Extension: dist.BuildpackURI{URI: "test"},
    77  				}
    78  
    79  				cmd := packageExtensionCommand(
    80  					withExtensionPackager(fakeExtensionPackager),
    81  					withExtensionPackageConfigReader(fakes.NewFakePackageConfigReader(whereReadReturns(myConfig, nil))),
    82  				)
    83  				err := cmd.Execute()
    84  				h.AssertNil(t, err)
    85  
    86  				receivedOptions := fakeExtensionPackager.CreateCalledWithOptions
    87  				h.AssertEq(t, receivedOptions.Config, myConfig)
    88  			})
    89  
    90  			when("file format", func() {
    91  				when("extension is .cnb", func() {
    92  					it("does not modify the name", func() {
    93  						cmd := packageExtensionCommand(withExtensionPackager(fakeExtensionPackager))
    94  						cmd.SetArgs([]string{"test.cnb", "-f", "file"})
    95  						h.AssertNil(t, cmd.Execute())
    96  
    97  						receivedOptions := fakeExtensionPackager.CreateCalledWithOptions
    98  						h.AssertEq(t, receivedOptions.Name, "test.cnb")
    99  					})
   100  				})
   101  				when("extension is empty", func() {
   102  					it("appends .cnb to the name", func() {
   103  						cmd := packageExtensionCommand(withExtensionPackager(fakeExtensionPackager))
   104  						cmd.SetArgs([]string{"test", "-f", "file"})
   105  						h.AssertNil(t, cmd.Execute())
   106  
   107  						receivedOptions := fakeExtensionPackager.CreateCalledWithOptions
   108  						h.AssertEq(t, receivedOptions.Name, "test.cnb")
   109  					})
   110  				})
   111  				when("extension is something other than .cnb", func() {
   112  					it("does not modify the name but shows a warning", func() {
   113  						cmd := packageExtensionCommand(withExtensionPackager(fakeExtensionPackager), withExtensionLogger(logger))
   114  						cmd.SetArgs([]string{"test.tar.gz", "-f", "file"})
   115  						h.AssertNil(t, cmd.Execute())
   116  
   117  						receivedOptions := fakeExtensionPackager.CreateCalledWithOptions
   118  						h.AssertEq(t, receivedOptions.Name, "test.tar.gz")
   119  						h.AssertContains(t, outBuf.String(), "'.gz' is not a valid extension for a packaged extension. Packaged extensions must have a '.cnb' extension")
   120  					})
   121  				})
   122  			})
   123  
   124  			when("pull-policy", func() {
   125  				var pullPolicyArgs = []string{
   126  					"some-image-name",
   127  					"--config", "/path/to/some/file",
   128  					"--pull-policy",
   129  				}
   130  
   131  				it("pull-policy=never sets policy", func() {
   132  					cmd := packageExtensionCommand(withExtensionPackager(fakeExtensionPackager))
   133  					cmd.SetArgs(append(pullPolicyArgs, "never"))
   134  					h.AssertNil(t, cmd.Execute())
   135  
   136  					receivedOptions := fakeExtensionPackager.CreateCalledWithOptions
   137  					h.AssertEq(t, receivedOptions.PullPolicy, image.PullNever)
   138  				})
   139  
   140  				it("pull-policy=always sets policy", func() {
   141  					cmd := packageExtensionCommand(withExtensionPackager(fakeExtensionPackager))
   142  					cmd.SetArgs(append(pullPolicyArgs, "always"))
   143  					h.AssertNil(t, cmd.Execute())
   144  
   145  					receivedOptions := fakeExtensionPackager.CreateCalledWithOptions
   146  					h.AssertEq(t, receivedOptions.PullPolicy, image.PullAlways)
   147  				})
   148  			})
   149  			when("no --pull-policy", func() {
   150  				var pullPolicyArgs = []string{
   151  					"some-image-name",
   152  					"--config", "/path/to/some/file",
   153  				}
   154  
   155  				it("uses the default policy when no policy configured", func() {
   156  					cmd := packageExtensionCommand(withExtensionPackager(fakeExtensionPackager))
   157  					cmd.SetArgs(pullPolicyArgs)
   158  					h.AssertNil(t, cmd.Execute())
   159  
   160  					receivedOptions := fakeExtensionPackager.CreateCalledWithOptions
   161  					h.AssertEq(t, receivedOptions.PullPolicy, image.PullAlways)
   162  				})
   163  				it("uses the configured pull policy when policy configured", func() {
   164  					cmd := packageExtensionCommand(
   165  						withExtensionPackager(fakeExtensionPackager),
   166  						withExtensionClientConfig(config.Config{PullPolicy: "never"}),
   167  					)
   168  
   169  					cmd.SetArgs([]string{
   170  						"some-image-name",
   171  						"--config", "/path/to/some/file",
   172  					})
   173  
   174  					err := cmd.Execute()
   175  					h.AssertNil(t, err)
   176  
   177  					receivedOptions := fakeExtensionPackager.CreateCalledWithOptions
   178  					h.AssertEq(t, receivedOptions.PullPolicy, image.PullNever)
   179  				})
   180  			})
   181  		})
   182  
   183  		when("no config path is specified", func() {
   184  			when("no path is specified", func() {
   185  				it("creates a default config with the uri set to the current working directory", func() {
   186  					cmd := packageExtensionCommand(withExtensionPackager(fakeExtensionPackager))
   187  					cmd.SetArgs([]string{"some-name"})
   188  					h.AssertNil(t, cmd.Execute())
   189  
   190  					receivedOptions := fakeExtensionPackager.CreateCalledWithOptions
   191  					h.AssertEq(t, receivedOptions.Config.Extension.URI, ".")
   192  				})
   193  			})
   194  		})
   195  	})
   196  
   197  	when("invalid flags", func() {
   198  		when("both --publish and --pull-policy never flags are specified", func() {
   199  			it("errors with a descriptive message", func() {
   200  				cmd := packageExtensionCommand()
   201  				cmd.SetArgs([]string{
   202  					"some-image-name", "--config", "/path/to/some/file",
   203  					"--publish",
   204  					"--pull-policy", "never",
   205  				})
   206  
   207  				err := cmd.Execute()
   208  				h.AssertNotNil(t, err)
   209  				h.AssertError(t, err, "--publish and --pull-policy=never cannot be used together. The --publish flag requires the use of remote images.")
   210  			})
   211  		})
   212  
   213  		it("logs an error and exits when package toml is invalid", func() {
   214  			expectedErr := errors.New("it went wrong")
   215  
   216  			cmd := packageExtensionCommand(
   217  				withExtensionLogger(logger),
   218  				withExtensionPackageConfigReader(
   219  					fakes.NewFakePackageConfigReader(whereReadReturns(pubbldpkg.Config{}, expectedErr)),
   220  				),
   221  			)
   222  
   223  			err := cmd.Execute()
   224  			h.AssertNotNil(t, err)
   225  
   226  			h.AssertContains(t, outBuf.String(), fmt.Sprintf("ERROR: reading config: %s", expectedErr))
   227  		})
   228  
   229  		when("package-config is specified", func() {
   230  			it("errors with a descriptive message", func() {
   231  				cmd := packageExtensionCommand()
   232  				cmd.SetArgs([]string{"some-name", "--package-config", "some-path"})
   233  
   234  				err := cmd.Execute()
   235  				h.AssertError(t, err, "unknown flag: --package-config")
   236  			})
   237  		})
   238  
   239  		when("--pull-policy unknown-policy", func() {
   240  			it("fails to run", func() {
   241  				cmd := packageExtensionCommand()
   242  				cmd.SetArgs([]string{
   243  					"some-image-name",
   244  					"--config", "/path/to/some/file",
   245  					"--pull-policy",
   246  					"unknown-policy",
   247  				})
   248  
   249  				h.AssertError(t, cmd.Execute(), "parsing pull policy")
   250  			})
   251  		})
   252  	})
   253  }
   254  
   255  type packageExtensionCommandConfig struct {
   256  	logger              *logging.LogWithWriters
   257  	packageConfigReader *fakes.FakePackageConfigReader
   258  	extensionPackager   *fakes.FakeBuildpackPackager
   259  	clientConfig        config.Config
   260  	imageName           string
   261  	configPath          string
   262  }
   263  
   264  type packageExtensionCommandOption func(config *packageExtensionCommandConfig)
   265  
   266  func packageExtensionCommand(ops ...packageExtensionCommandOption) *cobra.Command {
   267  	config := &packageExtensionCommandConfig{
   268  		logger:              logging.NewLogWithWriters(&bytes.Buffer{}, &bytes.Buffer{}),
   269  		packageConfigReader: fakes.NewFakePackageConfigReader(),
   270  		extensionPackager:   &fakes.FakeBuildpackPackager{},
   271  		clientConfig:        config.Config{},
   272  		imageName:           "some-image-name",
   273  		configPath:          "/path/to/some/file",
   274  	}
   275  
   276  	for _, op := range ops {
   277  		op(config)
   278  	}
   279  
   280  	cmd := commands.ExtensionPackage(config.logger, config.clientConfig, config.extensionPackager, config.packageConfigReader)
   281  	cmd.SetArgs([]string{config.imageName, "--config", config.configPath})
   282  
   283  	return cmd
   284  }
   285  
   286  func withExtensionLogger(logger *logging.LogWithWriters) packageExtensionCommandOption {
   287  	return func(config *packageExtensionCommandConfig) {
   288  		config.logger = logger
   289  	}
   290  }
   291  
   292  func withExtensionPackageConfigReader(reader *fakes.FakePackageConfigReader) packageExtensionCommandOption {
   293  	return func(config *packageExtensionCommandConfig) {
   294  		config.packageConfigReader = reader
   295  	}
   296  }
   297  
   298  func withExtensionPackager(creator *fakes.FakeBuildpackPackager) packageExtensionCommandOption {
   299  	return func(config *packageExtensionCommandConfig) {
   300  		config.extensionPackager = creator
   301  	}
   302  }
   303  
   304  func withExtensionImageName(name string) packageExtensionCommandOption {
   305  	return func(config *packageExtensionCommandConfig) {
   306  		config.imageName = name
   307  	}
   308  }
   309  
   310  func withExtensionPackageConfigPath(path string) packageExtensionCommandOption {
   311  	return func(config *packageExtensionCommandConfig) {
   312  		config.configPath = path
   313  	}
   314  }
   315  
   316  func withExtensionClientConfig(clientCfg config.Config) packageExtensionCommandOption {
   317  	return func(config *packageExtensionCommandConfig) {
   318  		config.clientConfig = clientCfg
   319  	}
   320  }