Skip to content

Commit

Permalink
Merge branch 'main' into manisbindra/porter-installations-list-add-fi…
Browse files Browse the repository at this point in the history
…eld-selector-argument
  • Loading branch information
schristoff authored Sep 14, 2023
2 parents 053149a + 8dc0095 commit 79518d1
Show file tree
Hide file tree
Showing 8 changed files with 335 additions and 15 deletions.
10 changes: 8 additions & 2 deletions cmd/porter/bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
},
}
Expand Down
11 changes: 10 additions & 1 deletion cmd/porter/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
)

var includeDocsCommand = false
Expand Down Expand Up @@ -188,7 +189,15 @@ Try our QuickStart https://getporter.org/quickstart to learn how to use Porter.
// Reload configuration with the now parsed cli flags
p.DataLoader = cli.LoadHierarchicalConfig(cmd)
ctx, err := p.Connect(cmd.Context())
cmd.SetContext(ctx)

// Extract the parent span from the main command
parentSpan := trace.SpanFromContext(cmd.Context())

// Create a context with the main command's span
ctxWithRootCmdSpan := trace.ContextWithSpan(ctx, parentSpan)

// Set the new context to the command
cmd.SetContext(ctxWithRootCmdSpan)
return err
},
RunE: func(cmd *cobra.Command, args []string) error {
Expand Down
4 changes: 2 additions & 2 deletions docs/content/references/cli/bundles_create.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions docs/content/references/cli/create.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
53 changes: 47 additions & 6 deletions pkg/porter/create.go
Original file line number Diff line number Diff line change
@@ -1,43 +1,81 @@
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 {
Expand All @@ -51,6 +89,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)
Expand Down
89 changes: 87 additions & 2 deletions pkg/porter/create_test.go
Original file line number Diff line number Diff line change
@@ -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())
Expand All @@ -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)
}
41 changes: 41 additions & 0 deletions pkg/porter/lifecycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"strings"

"get.porter.sh/porter/pkg/cache"
"get.porter.sh/porter/pkg/cnab"
Expand Down Expand Up @@ -196,6 +197,12 @@ func (p *Porter) resolveBundleReference(ctx context.Context, opts *BundleReferen
useReference := func(ref cnab.OCIReference) error {
pullOpts := *opts // make a copy just to do the pull
pullOpts.Reference = ref.String()

err := ensureVPrefix(&pullOpts)
if err != nil {
return err
}

cachedBundle, err := p.prepullBundleByReference(ctx, &pullOpts)
if err != nil {
return err
Expand Down Expand Up @@ -298,6 +305,40 @@ func (p *Porter) BuildActionArgs(ctx context.Context, installation storage.Insta
return args, nil
}

// ensureVPrefix adds a "v" prefix to the version tag if it's not already there.
// Version tag should always be prefixed with a "v", see https://github.com/getporter/porter/issues/2886.
// This is safe because "porter publish" adds a "v", see
// https://github.com/getporter/porter/blob/17bd7816ef6bde856793f6122e32274aa9d01d1b/pkg/storage/installation.go#L350
func ensureVPrefix(opts *BundleReferenceOptions) error {
var ociRef *cnab.OCIReference
if opts._ref != nil {
ociRef = opts._ref
} else {
ref, err := cnab.ParseOCIReference(opts.Reference)
if err != nil {
return fmt.Errorf("unable to parse OCI reference from '%s': %w", opts.Reference, err)
}
ociRef = &ref
}

if strings.HasPrefix(ociRef.Tag(), "v") {
// don't do anything if "v" is already there
return nil
}

vRef, err := ociRef.WithTag("v" + ociRef.Tag())
if err != nil {
return fmt.Errorf("unable to prefix reference tag '%s' with 'v': %w", ociRef.Tag(), err)
}

// always update the .Reference string, but don't add the _ref field unless it was already there (non-nil)
opts.Reference = vRef.String()
if opts._ref != nil {
opts._ref = &vRef
}
return nil
}

// prepullBundleByReference handles calling the bundle pull operation and updating
// the shared options like name and bundle file path. This is used by install, upgrade
// and uninstall
Expand Down
Loading

0 comments on commit 79518d1

Please sign in to comment.