diff --git a/cmd/porter/bundle.go b/cmd/porter/bundle.go index faa12275d..237e81b78 100644 --- a/cmd/porter/bundle.go +++ b/cmd/porter/bundle.go @@ -32,10 +32,16 @@ func buildBundleCommands(p *porter.Porter) *cobra.Command { func buildBundleCreateCommand(p *porter.Porter) *cobra.Command { return &cobra.Command{ - Use: "create", + Use: "create [bundle-name]", Short: "Create a bundle", - Long: "Create a bundle. This generates a porter bundle in the current directory.", + Long: "Create a bundle. This command creates a new porter bundle with the specified bundle-name, in the directory with the specified bundle-name." + + " The directory will be created if it doesn't already exist. If no bundle-name is provided, the bundle will be created in current directory and the bundle name will be 'porter-hello'.", + Args: cobra.MaximumNArgs(1), // Expect at most one argument for the bundle name RunE: func(cmd *cobra.Command, args []string) error { + if len(args) > 0 { + bundleName := args[0] + return p.CreateInDir(bundleName) + } return p.Create() }, } diff --git a/docs/content/references/cli/bundles_create.md b/docs/content/references/cli/bundles_create.md index 706d0ef77..3abfb8200 100644 --- a/docs/content/references/cli/bundles_create.md +++ b/docs/content/references/cli/bundles_create.md @@ -9,10 +9,10 @@ Create a bundle ### Synopsis -Create a bundle. This generates a porter bundle in the current directory. +Create a bundle. This generates a porter bundle in the directory with the specified name or in the current directory if no name is provided. ``` -porter bundles create [flags] +porter bundles create [bundle-name] [flags] ``` ### Options diff --git a/docs/content/references/cli/create.md b/docs/content/references/cli/create.md index f505582d7..2ac4414cd 100644 --- a/docs/content/references/cli/create.md +++ b/docs/content/references/cli/create.md @@ -9,10 +9,10 @@ Create a bundle ### Synopsis -Create a bundle. This generates a porter bundle in the current directory. +Create a bundle. This generates a porter bundle in the directory with the specified name or in the current directory if no name is provided. ``` -porter create [flags] +porter create [bundle-name] [flags] ``` ### Options diff --git a/pkg/porter/create.go b/pkg/porter/create.go index ef0a88d0f..7a8c61eb5 100644 --- a/pkg/porter/create.go +++ b/pkg/porter/create.go @@ -1,43 +1,80 @@ package porter import ( + "errors" "fmt" "os" "path/filepath" + "strings" "get.porter.sh/porter/pkg" "get.porter.sh/porter/pkg/config" ) +// Create creates a new bundle configuration in the current directory func (p *Porter) Create() error { - fmt.Fprintln(p.Out, "creating porter configuration in the current directory") + destinationDir := "." // current directory - err := p.CopyTemplate(p.Templates.GetManifest, config.Name) + if err := p.CopyTemplate(p.Templates.GetManifest, filepath.Join(destinationDir, config.Name)); err != nil { + return err + } + return p.copyAllTemplatesExceptPorterYaml(destinationDir) +} + +// CreateInDir creates a new bundle configuration in the specified directory. The directory will be created if it +// doesn't already exist. For example, if dir is "foo/bar/baz", the directory structure "foo/bar/baz" will be created. +// The bundle name will be set to the "base" of the given directory, which is "baz" in the example above. +func (p *Porter) CreateInDir(dir string) error { + bundleName := filepath.Base(dir) + + // Create dirs if they don't exist + _, err := p.FileSystem.Stat(dir) + if errors.Is(err, os.ErrNotExist) { + err = p.FileSystem.MkdirAll(dir, 0755) + } + if err != nil { + // the Stat failed with an error different from os.ErrNotExist OR the MkdirAll failed to create the dir(s) + return fmt.Errorf("failed to create directory for bundle: %w", err) + } + + // create porter.yaml, using base of given dir as the bundle name + err = p.CopyTemplate(func() ([]byte, error) { + content, err := p.Templates.GetManifest() + if err != nil { + return nil, err + } + content = []byte(strings.ReplaceAll(string(content), "porter-hello", bundleName)) + return content, nil + }, filepath.Join(dir, config.Name)) if err != nil { return err } - err = p.CopyTemplate(p.Templates.GetManifestHelpers, "helpers.sh") + return p.copyAllTemplatesExceptPorterYaml(dir) +} + +func (p *Porter) copyAllTemplatesExceptPorterYaml(destinationDir string) error { + err := p.CopyTemplate(p.Templates.GetManifestHelpers, filepath.Join(destinationDir, "helpers.sh")) if err != nil { return err } - err = p.CopyTemplate(p.Templates.GetReadme, "README.md") + err = p.CopyTemplate(p.Templates.GetReadme, filepath.Join(destinationDir, "README.md")) if err != nil { return err } - err = p.CopyTemplate(p.Templates.GetDockerfileTemplate, "template.Dockerfile") + err = p.CopyTemplate(p.Templates.GetDockerfileTemplate, filepath.Join(destinationDir, "template.Dockerfile")) if err != nil { return err } - err = p.CopyTemplate(p.Templates.GetDockerignore, ".dockerignore") + err = p.CopyTemplate(p.Templates.GetDockerignore, filepath.Join(destinationDir, ".dockerignore")) if err != nil { return err } - return p.CopyTemplate(p.Templates.GetGitignore, ".gitignore") + return p.CopyTemplate(p.Templates.GetGitignore, filepath.Join(destinationDir, ".gitignore")) } func (p *Porter) CopyTemplate(getTemplate func() ([]byte, error), dest string) error { @@ -51,6 +88,9 @@ func (p *Porter) CopyTemplate(getTemplate func() ([]byte, error), dest string) e mode = pkg.FileModeExecutable } + if _, err := p.FileSystem.Stat(dest); err == nil { + fmt.Fprintf(p.Err, "WARNING: File %q already exists. Overwriting.\n", dest) + } err = p.FileSystem.WriteFile(dest, tmpl, mode) if err != nil { return fmt.Errorf("failed to write template to %s: %w", dest, err) diff --git a/pkg/porter/create_test.go b/pkg/porter/create_test.go index 5773c2a52..8eb75390c 100644 --- a/pkg/porter/create_test.go +++ b/pkg/porter/create_test.go @@ -1,25 +1,28 @@ package porter import ( + "path/filepath" "testing" "get.porter.sh/porter/pkg" + "get.porter.sh/porter/pkg/manifest" + "get.porter.sh/porter/pkg/yaml" "get.porter.sh/porter/tests" "github.com/stretchr/testify/require" ) -func TestCreate(t *testing.T) { +func TestCreateInWorkingDirectory(t *testing.T) { p := NewTestPorter(t) defer p.Close() err := p.Create() require.NoError(t, err) + // Verify that files are present in the root directory configFileStats, err := p.FileSystem.Stat("porter.yaml") require.NoError(t, err) tests.AssertFilePermissionsEqual(t, "porter.yaml", pkg.FileModeWritable, configFileStats.Mode()) - // Verify that helpers is present and executable helperFileStats, err := p.FileSystem.Stat("helpers.sh") require.NoError(t, err) tests.AssertFilePermissionsEqual(t, "helpers.sh", pkg.FileModeExecutable, helperFileStats.Mode()) @@ -39,5 +42,87 @@ func TestCreate(t *testing.T) { dockerignoreStats, err := p.FileSystem.Stat(".dockerignore") require.NoError(t, err) tests.AssertFilePermissionsEqual(t, ".dockerignore", pkg.FileModeWritable, dockerignoreStats.Mode()) +} + +// tests to ensure behavior similarity with helm create +func TestCreateWithBundleName(t *testing.T) { + bundleName := "mybundle" + + p := NewTestPorter(t) + err := p.CreateInDir(bundleName) + require.NoError(t, err) + // Verify that files are present in the "mybundle" directory + configFileStats, err := p.FileSystem.Stat(filepath.Join(bundleName, "porter.yaml")) + require.NoError(t, err) + tests.AssertFilePermissionsEqual(t, filepath.Join(bundleName, "porter.yaml"), pkg.FileModeWritable, configFileStats.Mode()) + + helperFileStats, err := p.FileSystem.Stat(filepath.Join(bundleName, "helpers.sh")) + require.NoError(t, err) + tests.AssertFilePermissionsEqual(t, filepath.Join(bundleName, "helpers.sh"), pkg.FileModeExecutable, helperFileStats.Mode()) + + dockerfileStats, err := p.FileSystem.Stat(filepath.Join(bundleName, "template.Dockerfile")) + require.NoError(t, err) + tests.AssertFilePermissionsEqual(t, filepath.Join(bundleName, "template.Dockerfile"), pkg.FileModeWritable, dockerfileStats.Mode()) + + readmeStats, err := p.FileSystem.Stat(filepath.Join(bundleName, "README.md")) + require.NoError(t, err) + tests.AssertFilePermissionsEqual(t, filepath.Join(bundleName, "README.md"), pkg.FileModeWritable, readmeStats.Mode()) + + gitignoreStats, err := p.FileSystem.Stat(filepath.Join(bundleName, ".gitignore")) + require.NoError(t, err) + tests.AssertFilePermissionsEqual(t, filepath.Join(bundleName, ".gitignore"), pkg.FileModeWritable, gitignoreStats.Mode()) + + dockerignoreStats, err := p.FileSystem.Stat(filepath.Join(bundleName, ".dockerignore")) + require.NoError(t, err) + tests.AssertFilePermissionsEqual(t, filepath.Join(bundleName, ".dockerignore"), pkg.FileModeWritable, dockerignoreStats.Mode()) + + // verify "name" inside porter.yaml is set to "mybundle" + porterYaml := &manifest.Manifest{} + data, err := p.FileSystem.ReadFile(filepath.Join(bundleName, "porter.yaml")) + require.NoError(t, err) + require.NoError(t, yaml.Unmarshal(data, &porterYaml)) + require.True(t, porterYaml.Name == bundleName) +} + +// make sure bundlename is not the entire file structure, just the "base" +func TestCreateNestedBundleName(t *testing.T) { + dir := "foo/bar/bar" + bundleName := "mybundle" + + p := NewTestPorter(t) + err := p.CreateInDir(filepath.Join(dir, bundleName)) + require.NoError(t, err) + + // Verify that files are present in the "mybundle" directory + configFileStats, err := p.FileSystem.Stat(filepath.Join(dir, bundleName, "porter.yaml")) + require.NoError(t, err) + tests.AssertFilePermissionsEqual(t, filepath.Join(dir, bundleName, "porter.yaml"), pkg.FileModeWritable, configFileStats.Mode()) + + helperFileStats, err := p.FileSystem.Stat(filepath.Join(dir, bundleName, "helpers.sh")) + require.NoError(t, err) + tests.AssertFilePermissionsEqual(t, filepath.Join(dir, bundleName, "helpers.sh"), pkg.FileModeExecutable, helperFileStats.Mode()) + + dockerfileStats, err := p.FileSystem.Stat(filepath.Join(dir, bundleName, "template.Dockerfile")) + require.NoError(t, err) + tests.AssertFilePermissionsEqual(t, filepath.Join(dir, bundleName, "template.Dockerfile"), pkg.FileModeWritable, dockerfileStats.Mode()) + + readmeStats, err := p.FileSystem.Stat(filepath.Join(dir, bundleName, "README.md")) + require.NoError(t, err) + tests.AssertFilePermissionsEqual(t, filepath.Join(dir, bundleName, "README.md"), pkg.FileModeWritable, readmeStats.Mode()) + + gitignoreStats, err := p.FileSystem.Stat(filepath.Join(dir, bundleName, ".gitignore")) + require.NoError(t, err) + tests.AssertFilePermissionsEqual(t, filepath.Join(dir, bundleName, ".gitignore"), pkg.FileModeWritable, gitignoreStats.Mode()) + + dockerignoreStats, err := p.FileSystem.Stat(filepath.Join(dir, bundleName, ".dockerignore")) + require.NoError(t, err) + tests.AssertFilePermissionsEqual(t, filepath.Join(dir, bundleName, ".dockerignore"), pkg.FileModeWritable, dockerignoreStats.Mode()) + + // verify "name" inside porter.yaml is set to "mybundle" + porterYaml := &manifest.Manifest{} + data, err := p.FileSystem.ReadFile(filepath.Join(dir, bundleName, "porter.yaml")) + require.NoError(t, err) + require.NoError(t, yaml.Unmarshal(data, &porterYaml)) + require.True(t, porterYaml.Name == bundleName) }