diff --git a/docs/content/archive-bundles.md b/docs/content/archive-bundles.md index adf2b1f13..fe13fec91 100644 --- a/docs/content/archive-bundles.md +++ b/docs/content/archive-bundles.md @@ -96,6 +96,4 @@ space_name Name for DO Space Outputs: Name Description Type Applies To service_ip IP Address assigned to the Load Balancer string All Actions - -No custom actions defined ``` diff --git a/docs/content/examine-bundles.md b/docs/content/examine-bundles.md index cb1b14334..32213430f 100644 --- a/docs/content/examine-bundles.md +++ b/docs/content/examine-bundles.md @@ -32,8 +32,6 @@ space_name Name for DO Space Outputs: Name Description Type Applies To service_ip IP Address assigned to the Load Balancer string install,upgrade - -No custom actions defined ``` The `porter explain` command will show what credentials and parameters are required for the bundle, what outputs are generated, and what custom actions have been defined. For `parameters`, this command will also show you the default value, if one has been provided. Additionally, the user can quickly see what actions a `parameter` or `output` apply to. diff --git a/docs/content/plugin-tutorial.md b/docs/content/plugin-tutorial.md index 14e97eabf..9bd03427a 100644 --- a/docs/content/plugin-tutorial.md +++ b/docs/content/plugin-tutorial.md @@ -228,12 +228,6 @@ Version: 0.1.0 Credentials: Name Description Required password Password for installing the world. We recommend getting this from a secret store. true - -No parameters defined - -No outputs defined - -No custom actions defined ``` Since the bundle needs a credential we will generate some for it using `porter diff --git a/docs/content/quickstart/_index.md b/docs/content/quickstart/_index.md index 954155cd4..4a33e53bb 100644 --- a/docs/content/quickstart/_index.md +++ b/docs/content/quickstart/_index.md @@ -52,8 +52,6 @@ namespace string false All wordpress-name string porter-ci-wordpress false All Actions wordpress-password string true install,upgrade -No outputs defined - Actions: Name Description Modifies Installation Stateless ping ping true false @@ -70,16 +68,6 @@ $ porter explain --reference getporter/porter-hello:v0.1.0 Name: HELLO Description: An example Porter configuration Version: 0.1.0 - -No credentials defined - -No parameters defined - -No outputs defined - -No custom actions defined - -No dependencies defined ``` ## Install a Bundle diff --git a/docs/content/quickstart/credentials.md b/docs/content/quickstart/credentials.md index a9c640e6a..b0ee738ea 100644 --- a/docs/content/quickstart/credentials.md +++ b/docs/content/quickstart/credentials.md @@ -33,14 +33,6 @@ Porter Version: v0.38.1 Credentials: Name Description Required Applies To github-token A GitHub Personal Access Token. Generate one at https://github.com/settings/tokens. No scopes are required. true install,upgrade - -No parameters defined - -No outputs defined - -No custom actions defined - -No dependencies defined ``` In the Credentials section of the output returned by explain, there is a single required credential, github-token, that applies to the install and upgrade actions. diff --git a/docs/content/quickstart/parameters.md b/docs/content/quickstart/parameters.md index 64f32dbe8..bcc7ede8c 100644 --- a/docs/content/quickstart/parameters.md +++ b/docs/content/quickstart/parameters.md @@ -24,17 +24,10 @@ Description: An example Porter bundle with parameters Version: 0.1.0 Porter Version: v0.38.1-32-gb76f5c1c -No credentials defined - Parameters: Name Description Type Default Required Applies To name Name of to whom we should say hello string llama false All Actions -No outputs defined - -No custom actions defined - -No dependencies defined ``` In the Parameters section of the output returned by explain, there is a single optional string parameter, name, with a default of "llama" that applies to "All Actions". diff --git a/pkg/porter/explain.go b/pkg/porter/explain.go index 52307a81c..395b28776 100644 --- a/pkg/porter/explain.go +++ b/pkg/porter/explain.go @@ -31,6 +31,7 @@ type PrintableBundle struct { Outputs []PrintableOutput `json:"outputs,omitempty" yaml:"outputs,omitempty"` Actions []PrintableAction `json:"customActions,omitempty" yaml:"customActions,omitempty"` Dependencies []PrintableDependency `json:"dependencies,omitempty" yaml:"dependencies,omitempty"` + Mixins []string `json:"mixins" yaml:"mixins"` } type PrintableCredential struct { @@ -184,25 +185,35 @@ func generatePrintable(bun cnab.ExtendedBundle, action string) (*PrintableBundle stamp = configadapter.Stamp{} } + solver := &cnab.DependencySolver{} + deps, err := solver.ResolveDependencies(bun) + if err != nil { + return nil, errors.Wrapf(err, "error resolving bundle dependencies") + } + pb := PrintableBundle{ Name: bun.Name, Description: bun.Description, Version: bun.Version, PorterVersion: stamp.Version, + Actions: make([]PrintableAction, 0, len(bun.Actions)), + Credentials: make([]PrintableCredential, 0, len(bun.Credentials)), + Parameters: make([]PrintableParameter, 0, len(bun.Parameters)), + Outputs: make([]PrintableOutput, 0, len(bun.Outputs)), + Dependencies: make([]PrintableDependency, 0, len(deps)), + Mixins: make([]string, 0, len(stamp.Mixins)), } - actions := make([]PrintableAction, 0, len(bun.Actions)) for a, v := range bun.Actions { pa := PrintableAction{} pa.Name = a pa.Description = v.Description pa.Modifies = v.Modifies pa.Stateless = v.Stateless - actions = append(actions, pa) + pb.Actions = append(pb.Actions, pa) } - sort.Sort(SortPrintableAction(actions)) + sort.Sort(SortPrintableAction(pb.Actions)) - creds := make([]PrintableCredential, 0, len(bun.Credentials)) for c, v := range bun.Credentials { pc := PrintableCredential{} pc.Name = c @@ -211,12 +222,11 @@ func generatePrintable(bun cnab.ExtendedBundle, action string) (*PrintableBundle pc.ApplyTo = generateApplyToString(v.ApplyTo) if shouldIncludeInExplainOutput(&v, action) { - creds = append(creds, pc) + pb.Credentials = append(pb.Credentials, pc) } } - sort.Sort(SortPrintableCredential(creds)) + sort.Sort(SortPrintableCredential(pb.Credentials)) - params := make([]PrintableParameter, 0, len(bun.Parameters)) for p, v := range bun.Parameters { if bun.IsInternalParameter(p) { continue @@ -237,12 +247,11 @@ func generatePrintable(bun cnab.ExtendedBundle, action string) (*PrintableBundle pp.Description = v.Description if shouldIncludeInExplainOutput(&v, action) { - params = append(params, pp) + pb.Parameters = append(pb.Parameters, pp) } } - sort.Sort(SortPrintableParameter(params)) + sort.Sort(SortPrintableParameter(pb.Parameters)) - outputs := make([]PrintableOutput, 0, len(bun.Outputs)) for o, v := range bun.Outputs { def, ok := bun.Definitions[v.Definition] if !ok { @@ -258,31 +267,25 @@ func generatePrintable(bun cnab.ExtendedBundle, action string) (*PrintableBundle po.Description = v.Description if shouldIncludeInExplainOutput(&v, action) { - outputs = append(outputs, po) + pb.Outputs = append(pb.Outputs, po) } } - sort.Sort(SortPrintableOutput(outputs)) - - solver := &cnab.DependencySolver{} - deps, err := solver.ResolveDependencies(bun) - if err != nil { - return nil, errors.Wrapf(err, "error executing dependencies") - } + sort.Sort(SortPrintableOutput(pb.Outputs)) - dependencies := make([]PrintableDependency, 0, len(deps)) for _, dep := range deps { pd := PrintableDependency{} pd.Alias = dep.Alias pd.Reference = dep.Reference - dependencies = append(dependencies, pd) + pb.Dependencies = append(pb.Dependencies, pd) } + // dependencies are sorted by their dependency sequence already + + for mixin := range stamp.Mixins { + pb.Mixins = append(pb.Mixins, mixin) + } + sort.Strings(pb.Mixins) - pb.Actions = actions - pb.Credentials = creds - pb.Outputs = outputs - pb.Parameters = params - pb.Dependencies = dependencies return &pb, nil } @@ -318,19 +321,22 @@ func (p *Porter) printBundleExplainTable(bun *PrintableBundle) error { p.printOutputsExplainBlock(bun) p.printActionsExplainBlock(bun) p.printDependenciesExplainBlock(bun) + + fmt.Fprintf(p.Out, "This bundle uses the following tools: %s.\n", strings.Join(bun.Mixins, ", ")) return nil } func (p *Porter) printCredentialsExplainBlock(bun *PrintableBundle) error { - if len(bun.Credentials) > 0 { - fmt.Fprintln(p.Out, "Credentials:") - err := p.printCredentialsExplainTable(bun) - if err != nil { - return errors.Wrap(err, "unable to print credentials table") - } - } else { - fmt.Fprintln(p.Out, "No credentials defined") + if len(bun.Credentials) == 0 { + return nil } + + fmt.Fprintln(p.Out, "Credentials:") + err := p.printCredentialsExplainTable(bun) + if err != nil { + return errors.Wrap(err, "unable to print credentials table") + } + fmt.Fprintln(p.Out, "") // force a blank line after this block return nil } @@ -347,15 +353,16 @@ func (p *Porter) printCredentialsExplainTable(bun *PrintableBundle) error { } func (p *Porter) printParametersExplainBlock(bun *PrintableBundle) error { - if len(bun.Parameters) > 0 { - fmt.Fprintln(p.Out, "Parameters:") - err := p.printParametersExplainTable(bun) - if err != nil { - return errors.Wrap(err, "unable to print parameters table") - } - } else { - fmt.Fprintln(p.Out, "No parameters defined") + if len(bun.Parameters) == 0 { + return nil } + + fmt.Fprintln(p.Out, "Parameters:") + err := p.printParametersExplainTable(bun) + if err != nil { + return errors.Wrap(err, "unable to print parameters table") + } + fmt.Fprintln(p.Out, "") // force a blank line after this block return nil } @@ -372,15 +379,16 @@ func (p *Porter) printParametersExplainTable(bun *PrintableBundle) error { } func (p *Porter) printOutputsExplainBlock(bun *PrintableBundle) error { - if len(bun.Outputs) > 0 { - fmt.Fprintln(p.Out, "Outputs:") - err := p.printOutputsExplainTable(bun) - if err != nil { - return errors.Wrap(err, "unable to print outputs table") - } - } else { - fmt.Fprintln(p.Out, "No outputs defined") + if len(bun.Outputs) == 0 { + return nil } + + fmt.Fprintln(p.Out, "Outputs:") + err := p.printOutputsExplainTable(bun) + if err != nil { + return errors.Wrap(err, "unable to print outputs table") + } + fmt.Fprintln(p.Out, "") // force a blank line after this block return nil } @@ -398,15 +406,16 @@ func (p *Porter) printOutputsExplainTable(bun *PrintableBundle) error { } func (p *Porter) printActionsExplainBlock(bun *PrintableBundle) error { - if len(bun.Actions) > 0 { - fmt.Fprintln(p.Out, "Actions:") - err := p.printActionsExplainTable(bun) - if err != nil { - return errors.Wrap(err, "unable to print actions block") - } - } else { - fmt.Fprintln(p.Out, "No custom actions defined") + if len(bun.Actions) == 0 { + return nil } + + fmt.Fprintln(p.Out, "Actions:") + err := p.printActionsExplainTable(bun) + if err != nil { + return errors.Wrap(err, "unable to print actions block") + } + fmt.Fprintln(p.Out, "") // force a blank line after this block return nil } @@ -425,15 +434,16 @@ func (p *Porter) printActionsExplainTable(bun *PrintableBundle) error { // Dependencies func (p *Porter) printDependenciesExplainBlock(bun *PrintableBundle) error { - if len(bun.Dependencies) > 0 { - fmt.Fprintln(p.Out, "Dependencies:") - err := p.printDependenciesExplainTable(bun) - if err != nil { - return errors.Wrap(err, "unable to print dependencies table") - } - } else { - fmt.Fprintln(p.Out, "No dependencies defined") + if len(bun.Dependencies) == 0 { + return nil } + + fmt.Fprintln(p.Out, "Dependencies:") + err := p.printDependenciesExplainTable(bun) + if err != nil { + return errors.Wrap(err, "unable to print dependencies table") + } + fmt.Fprintln(p.Out, "") // force a blank line after this block return nil } diff --git a/pkg/porter/explain_test.go b/pkg/porter/explain_test.go index ff2a10db7..81c5bd677 100644 --- a/pkg/porter/explain_test.go +++ b/pkg/porter/explain_test.go @@ -12,111 +12,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestExplain_generateActionsTableNoActions(t *testing.T) { - bun := PrintableBundle{} - - p := NewTestPorter(t) - defer p.Teardown() - - p.printActionsExplainTable(&bun) - expected := "Name Description Modifies Installation Stateless\n" - - gotOutput := p.TestConfig.TestContext.GetOutput() - assert.Equal(t, expected, gotOutput) - t.Log(gotOutput) -} - -func TestExplain_generateActionsBlockNoActions(t *testing.T) { - bun := PrintableBundle{} - - p := NewTestPorter(t) - defer p.Teardown() - - p.printActionsExplainBlock(&bun) - expected := "No custom actions defined\n\n" - gotOutput := p.TestConfig.TestContext.GetOutput() - assert.Equal(t, expected, gotOutput) - t.Log(gotOutput) -} - -func TestExplain_generateCredentialsTableNoCreds(t *testing.T) { - bun := PrintableBundle{} - - p := NewTestPorter(t) - defer p.Teardown() - - p.printCredentialsExplainTable(&bun) - expected := "Name Description Required Applies To\n" - gotOutput := p.TestConfig.TestContext.GetOutput() - assert.Equal(t, expected, gotOutput) - t.Log(gotOutput) -} - -func TestExplain_generateCredentialsBlockNoCreds(t *testing.T) { - bun := PrintableBundle{} - - p := NewTestPorter(t) - defer p.Teardown() - - p.printCredentialsExplainBlock(&bun) - expected := "No credentials defined\n\n" - gotOutput := p.TestConfig.TestContext.GetOutput() - assert.Equal(t, expected, gotOutput) - t.Log(gotOutput) -} - -func TestExplain_generateOutputsTableNoOutputs(t *testing.T) { - bun := PrintableBundle{} - - p := NewTestPorter(t) - defer p.Teardown() - - p.printOutputsExplainTable(&bun) - expected := "Name Description Type Applies To\n" - gotOutput := p.TestConfig.TestContext.GetOutput() - assert.Equal(t, expected, gotOutput) - t.Log(gotOutput) -} - -func TestExplain_generateOutputsBlockNoOutputs(t *testing.T) { - bun := PrintableBundle{} - - p := NewTestPorter(t) - defer p.Teardown() - - p.printOutputsExplainBlock(&bun) - expected := "No outputs defined\n\n" - gotOutput := p.TestConfig.TestContext.GetOutput() - assert.Equal(t, expected, gotOutput) - t.Log(gotOutput) -} - -func TestExplain_generateParametersTableNoParams(t *testing.T) { - bun := PrintableBundle{} - - p := NewTestPorter(t) - defer p.Teardown() - - p.printParametersExplainTable(&bun) - expected := "Name Description Type Default Required Applies To\n" - gotOutput := p.TestConfig.TestContext.GetOutput() - assert.Equal(t, expected, gotOutput) - t.Log(gotOutput) -} - -func TestExplain_generateParametersBlockNoParams(t *testing.T) { - bun := PrintableBundle{} - - p := NewTestPorter(t) - defer p.Teardown() - - p.printParametersExplainBlock(&bun) - expected := "No parameters defined\n\n" - gotOutput := p.TestConfig.TestContext.GetOutput() - assert.Equal(t, expected, gotOutput) - t.Log(gotOutput) -} - func TestExplain_validateBadFormat(t *testing.T) { p := NewTestPorter(t) defer p.Teardown() @@ -169,9 +64,7 @@ func TestExplain_generateJSON(t *testing.T) { err = p.printBundleExplain(opts, pb) assert.NoError(t, err) gotOutput := p.TestConfig.TestContext.GetOutput() - expected, err := ioutil.ReadFile("testdata/explain/expected-json-output.json") - require.NoError(t, err) - assert.Equal(t, string(expected), gotOutput) + p.CompareGoldenFile("testdata/explain/expected-json-output.json", gotOutput) } func TestExplain_generateYAML(t *testing.T) { @@ -193,9 +86,7 @@ func TestExplain_generateYAML(t *testing.T) { err = p.printBundleExplain(opts, pb) assert.NoError(t, err) gotOutput := p.TestConfig.TestContext.GetOutput() - expected, err := ioutil.ReadFile("testdata/explain/expected-yaml-output.yaml") - require.NoError(t, err) - assert.Equal(t, string(expected), gotOutput) + p.CompareGoldenFile("testdata/explain/expected-yaml-output.yaml", gotOutput) } func TestExplain_generatePrintableBundleParams(t *testing.T) { @@ -533,7 +424,6 @@ func TestExplain_generateJSONForDependencies(t *testing.T) { err = p.printBundleExplain(opts, pb) assert.NoError(t, err) gotOutput := p.TestConfig.TestContext.GetOutput() - expected, err := ioutil.ReadFile("testdata/explain/expected-json-dependencies-output.json") - require.NoError(t, err) - assert.Equal(t, string(expected), gotOutput) + + p.CompareGoldenFile("testdata/explain/expected-json-dependencies-output.json", gotOutput) } diff --git a/pkg/porter/inspect.go b/pkg/porter/inspect.go index 491f480d4..a7f54f7b0 100644 --- a/pkg/porter/inspect.go +++ b/pkg/porter/inspect.go @@ -125,16 +125,17 @@ func (p *Porter) printInvocationImageInspectTable(bun *InspectableBundle) error } func (p *Porter) printImagesInspectBlock(bun *InspectableBundle) error { + if len(bun.Images) == 0 { + return nil + } + fmt.Fprintln(p.Out, "Images:") - if len(bun.Images) > 0 { - err := p.printImagesInspectTable(bun) - if err != nil { - return errors.Wrap(err, "unable to print images table") - } - fmt.Fprintln(p.Out, "") // force a blank line after this block - } else { - fmt.Fprintln(p.Out, "No images defined") + err := p.printImagesInspectTable(bun) + if err != nil { + return errors.Wrap(err, "unable to print images table") } + fmt.Fprintln(p.Out, "") // force a blank line after this block + return nil } diff --git a/pkg/porter/testdata/explain/expected-json-dependencies-output.json b/pkg/porter/testdata/explain/expected-json-dependencies-output.json index c95f977ee..b95906148 100644 --- a/pkg/porter/testdata/explain/expected-json-dependencies-output.json +++ b/pkg/porter/testdata/explain/expected-json-dependencies-output.json @@ -12,5 +12,6 @@ "alias": "mysql", "reference": "getporter/mysql:v0.1.3" } - ] + ], + "mixins": [] } diff --git a/pkg/porter/testdata/explain/expected-json-output.json b/pkg/porter/testdata/explain/expected-json-output.json index 510dc27f1..61d9c99de 100644 --- a/pkg/porter/testdata/explain/expected-json-output.json +++ b/pkg/porter/testdata/explain/expected-json-output.json @@ -12,5 +12,9 @@ "description": "", "required": false } + ], + "mixins": [ + "helm", + "terraform" ] } diff --git a/pkg/porter/testdata/explain/expected-table-output.txt b/pkg/porter/testdata/explain/expected-table-output.txt index dd1680a54..e139e0502 100644 --- a/pkg/porter/testdata/explain/expected-table-output.txt +++ b/pkg/porter/testdata/explain/expected-table-output.txt @@ -3,15 +3,8 @@ Description: An example Porter configuration Version: 0.1.0 Porter Version: v0.30.0 -No credentials defined - Parameters: Name Description Type Default Required Applies To region string mars false All Actions -No outputs defined - -No custom actions defined - -No dependencies defined - +This bundle uses the following tools: helm, terraform. diff --git a/pkg/porter/testdata/explain/expected-yaml-output.yaml b/pkg/porter/testdata/explain/expected-yaml-output.yaml index 6052413d0..0303a722d 100644 --- a/pkg/porter/testdata/explain/expected-yaml-output.yaml +++ b/pkg/porter/testdata/explain/expected-yaml-output.yaml @@ -9,3 +9,6 @@ parameters: applyTo: All Actions description: "" required: false +mixins: + - helm + - terraform diff --git a/pkg/porter/testdata/explain/params-bundle.json b/pkg/porter/testdata/explain/params-bundle.json index 6aff4c6e7..39eb18996 100644 --- a/pkg/porter/testdata/explain/params-bundle.json +++ b/pkg/porter/testdata/explain/params-bundle.json @@ -1 +1,51 @@ -{"custom":{"io.cnab.dependencies":null,"sh.porter":{"manifestDigest":"5040d45d0c44e7632563966c33f5e8980e83cfa7c0485f725b623b7604f072f0","version": "v0.30.0","commit": "3b7c85ba"}},"definitions":{"porter-debug":{"$comment":"porter-internal","default":false,"description":"Print debug information from Porter when executing the bundle","type":"boolean"},"region":{"default":"mars","type":"string"}},"description":"An example Porter configuration","invocationImages":[{"image":"porter-hello:latest","imageType":"docker"}],"name":"porter-hello","parameters":{"porter-debug":{"definition":"porter-debug","description":"Print debug information from Porter when executing the bundle","destination":{"env":"PORTER_DEBUG"}},"region":{"definition":"region","destination":{"env":"REGION"}}},"schemaVersion":"v1.0.0-WD","version":"0.1.0"} \ No newline at end of file +{ + "custom": { + "io.cnab.dependencies": null, + "sh.porter": { + "manifestDigest": "5040d45d0c44e7632563966c33f5e8980e83cfa7c0485f725b623b7604f072f0", + "version": "v0.30.0", + "commit": "3b7c85ba", + "mixins": { + "terraform": {}, + "helm": {} + } + } + }, + "definitions": { + "porter-debug": { + "$comment": "porter-internal", + "default": false, + "description": "Print debug information from Porter when executing the bundle", + "type": "boolean" + }, + "region": { + "default": "mars", + "type": "string" + } + }, + "description": "An example Porter configuration", + "invocationImages": [ + { + "image": "porter-hello:latest", + "imageType": "docker" + } + ], + "name": "porter-hello", + "parameters": { + "porter-debug": { + "definition": "porter-debug", + "description": "Print debug information from Porter when executing the bundle", + "destination": { + "env": "PORTER_DEBUG" + } + }, + "region": { + "definition": "region", + "destination": { + "env": "REGION" + } + } + }, + "schemaVersion": "v1.0.0-WD", + "version": "0.1.0" +} \ No newline at end of file