diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 09c16fab5..85ae68157 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -71,4 +71,4 @@ and we will add you. **All** contributors belong here. 💯 * [Hrittik Roy](https://github.com/hrittikhere) * [Tanmay Chaudhry](https://github.com/tchaudhry91) * [Kevin Barbour](https://github.com/kevinbarbour) -* [Epsxy](https://github.com/epsxy) \ No newline at end of file +* [Epsxy](https://github.com/epsxy) diff --git a/pkg/porter/archive_test.go b/pkg/porter/archive_test.go index 833807a59..8b43c339a 100644 --- a/pkg/porter/archive_test.go +++ b/pkg/porter/archive_test.go @@ -70,6 +70,7 @@ func TestArchive_ArchiveDirectory(t *testing.T) { tests.AssertDirectoryPermissionsEqual(t, dir, pkg.FileModeDirectory) } + func TestArchive_AddImage(t *testing.T) { p := NewTestPorter(t) defer p.Close() diff --git a/pkg/porter/list.go b/pkg/porter/list.go index cd9ebe4b4..b32ef2174 100644 --- a/pkg/porter/list.go +++ b/pkg/porter/list.go @@ -16,6 +16,16 @@ import ( "github.com/cnabio/cnab-go/schema" ) +const ( + StateInstalled = "installed" + StateUninstalled = "uninstalled" + StateDefined = "defined" + + StatusInstalling = "installing" + StatusUninstalling = "uninstalling" + StatusUpgrading = "upgrading" +) + // ListOptions represent generic options for use by Porter's list commands type ListOptions struct { printer.PrintOptions @@ -105,6 +115,13 @@ type DisplayInstallation struct { type DisplayInstallationMetadata struct { ResolvedParameters DisplayValues `json:"resolvedParameters" yaml:"resolvedParameters"` + + // DisplayInstallationState is the latest state of the installation. + // It is either "installed", "uninstalled", or "defined". + DisplayInstallationState string `json:"displayInstallationState,omitempty" yaml:"displayInstallationState,omitempty" toml:"displayInstallationState,omitempty"` + // DisplayInstallationStatus is the latest status of the installation. + // It is either "succeeded, "failed", "installing", "uninstalling", "upgrading", or "running " + DisplayInstallationStatus string `json:"displayInstallationStatus,omitempty" yaml:"displayInstallationStatus,omitempty" toml:"displayInstallationStatus,omitempty"` } func NewDisplayInstallation(installation storage.Installation) DisplayInstallation { @@ -122,6 +139,10 @@ func NewDisplayInstallation(installation storage.Installation) DisplayInstallati CredentialSets: installation.CredentialSets, ParameterSets: installation.ParameterSets, Status: installation.Status, + DisplayInstallationMetadata: DisplayInstallationMetadata{ + DisplayInstallationState: getDisplayInstallationState(installation), + DisplayInstallationStatus: getDisplayInstallationStatus(installation), + }, } return di @@ -265,11 +286,45 @@ func (p *Porter) PrintInstallations(ctx context.Context, opts ListOptions) error if !ok { return nil } - return []string{cl.Namespace, cl.Name, tp.Format(cl.Status.Created), tp.Format(cl.Status.Modified), cl.Status.Action, cl.Status.ResultStatus} + return []string{cl.Namespace, cl.Name, cl.Status.BundleVersion, cl.DisplayInstallationState, cl.DisplayInstallationStatus, tp.Format(cl.Status.Modified)} } return printer.PrintTable(p.Out, displayInstallations, row, - "NAMESPACE", "NAME", "CREATED", "MODIFIED", "LAST ACTION", "LAST STATUS") + "NAMESPACE", "NAME", "VERSION", "STATE", "STATUS", "MODIFIED") default: return fmt.Errorf("invalid format: %s", opts.Format) } } + +func getDisplayInstallationState(installation storage.Installation) string { + if installation.IsInstalled() { + return StateInstalled + } else if installation.IsUninstalled() { + return StateUninstalled + } + + return StateDefined +} + +func getDisplayInstallationStatus(installation storage.Installation) string { + var status string + + switch installation.Status.ResultStatus { + case cnab.StatusSucceeded: + status = cnab.StatusSucceeded + case cnab.StatusFailed: + status = cnab.StatusFailed + case cnab.StatusRunning: + switch installation.Status.Action { + case cnab.ActionInstall: + status = StatusInstalling + case cnab.ActionUninstall: + status = StatusUninstalling + case cnab.ActionUpgrade: + status = StatusUpgrading + default: + status = fmt.Sprintf("running %s", installation.Status.Action) + } + } + + return status +} diff --git a/pkg/porter/list_test.go b/pkg/porter/list_test.go index 63403d187..a729e2750 100644 --- a/pkg/porter/list_test.go +++ b/pkg/porter/list_test.go @@ -5,6 +5,7 @@ import ( "testing" "get.porter.sh/porter/pkg/cnab" + "get.porter.sh/porter/pkg/printer" "get.porter.sh/porter/pkg/secrets" "get.porter.sh/porter/pkg/storage" "github.com/stretchr/testify/assert" @@ -159,3 +160,109 @@ func TestDisplayInstallation_ConvertToInstallation(t *testing.T) { require.Equal(t, cnab.StatusRunning, convertedInstallation.Status.ResultStatus, "invalid last status") } + +func TestPorter_PrintInstallations(t *testing.T) { + t.Parallel() + + testcases := []struct { + name string + format printer.Format + outputFile string + }{ + {name: "plain", format: printer.FormatPlaintext, outputFile: "testdata/list/expected-output.txt"}, + {name: "no reference, plain", format: printer.FormatPlaintext, outputFile: "testdata/list/no-reference-expected-output.txt"}, + {name: "json", format: printer.FormatJson, outputFile: "testdata/list/expected-output.json"}, + {name: "yaml", format: printer.FormatYaml, outputFile: "testdata/list/expected-output.yaml"}, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + p := NewTestPorter(t) + defer p.Close() + + opts := ListOptions{ + Namespace: "dev", + Name: "mywordpress", + PrintOptions: printer.PrintOptions{ + Format: tc.format, + }, + } + + p.TestInstallations.CreateInstallation(storage.NewInstallation("dev", "mywordpress"), p.TestInstallations.SetMutableInstallationValues, func(i *storage.Installation) { + i.Status.BundleVersion = "v1.2.3" + i.Status.ResultStatus = cnab.StatusSucceeded + }) + + ctx := context.Background() + + err := p.PrintInstallations(ctx, opts) + require.NoError(t, err, "PrintInstallation failed") + p.CompareGoldenFile(tc.outputFile, p.TestConfig.TestContext.GetOutput()) + }) + } +} + +func TestPorter_getDisplayInstallationState(t *testing.T) { + p := NewTestPorter(t) + defer p.Close() + + installation := p.TestInstallations.CreateInstallation(storage.NewInstallation("dev", "mywordpress"), p.TestInstallations.SetMutableInstallationValues) + displayInstallationState := getDisplayInstallationState(installation) + require.Equal(t, StateDefined, displayInstallationState) + + run := p.TestInstallations.CreateRun(installation.NewRun(cnab.ActionInstall), p.TestInstallations.SetMutableRunValues) + result := p.TestInstallations.CreateResult(run.NewResult(cnab.StatusSucceeded), p.TestInstallations.SetMutableResultValues) + installation.ApplyResult(run, result) + installation.Status.Installed = &now + displayInstallationState = getDisplayInstallationState(installation) + require.Equal(t, StateInstalled, displayInstallationState) + + run = p.TestInstallations.CreateRun(installation.NewRun(cnab.ActionUninstall), p.TestInstallations.SetMutableRunValues) + result = p.TestInstallations.CreateResult(run.NewResult(cnab.StatusSucceeded), p.TestInstallations.SetMutableResultValues) + installation.ApplyResult(run, result) + installation.Status.Uninstalled = &now + displayInstallationState = getDisplayInstallationState(installation) + require.Equal(t, StateUninstalled, displayInstallationState) +} + +func TestPorter_getDisplayInstallationStatus(t *testing.T) { + p := NewTestPorter(t) + defer p.Close() + + installation := p.TestInstallations.CreateInstallation(storage.NewInstallation("dev", "mywordpress"), p.TestInstallations.SetMutableInstallationValues) + run := p.TestInstallations.CreateRun(installation.NewRun(cnab.ActionInstall), p.TestInstallations.SetMutableRunValues) + result := p.TestInstallations.CreateResult(run.NewResult(cnab.StatusSucceeded), p.TestInstallations.SetMutableResultValues) + installation.ApplyResult(run, result) + displayInstallationStatus := getDisplayInstallationStatus(installation) + require.Equal(t, cnab.StatusSucceeded, displayInstallationStatus) + + result = p.TestInstallations.CreateResult(run.NewResult(cnab.StatusFailed), p.TestInstallations.SetMutableResultValues) + installation.ApplyResult(run, result) + displayInstallationStatus = getDisplayInstallationStatus(installation) + require.Equal(t, cnab.StatusFailed, displayInstallationStatus) + + run = p.TestInstallations.CreateRun(installation.NewRun(cnab.ActionInstall), p.TestInstallations.SetMutableRunValues) + result = p.TestInstallations.CreateResult(run.NewResult(cnab.StatusRunning), p.TestInstallations.SetMutableResultValues) + installation.ApplyResult(run, result) + displayInstallationStatus = getDisplayInstallationStatus(installation) + require.Equal(t, StatusInstalling, displayInstallationStatus) + + run = p.TestInstallations.CreateRun(installation.NewRun(cnab.ActionUninstall), p.TestInstallations.SetMutableRunValues) + result = p.TestInstallations.CreateResult(run.NewResult(cnab.StatusRunning), p.TestInstallations.SetMutableResultValues) + installation.ApplyResult(run, result) + displayInstallationStatus = getDisplayInstallationStatus(installation) + require.Equal(t, StatusUninstalling, displayInstallationStatus) + + run = p.TestInstallations.CreateRun(installation.NewRun(cnab.ActionUpgrade), p.TestInstallations.SetMutableRunValues) + result = p.TestInstallations.CreateResult(run.NewResult(cnab.StatusRunning), p.TestInstallations.SetMutableResultValues) + installation.ApplyResult(run, result) + displayInstallationStatus = getDisplayInstallationStatus(installation) + require.Equal(t, StatusUpgrading, displayInstallationStatus) + + run = p.TestInstallations.CreateRun(installation.NewRun("customaction"), p.TestInstallations.SetMutableRunValues) + result = p.TestInstallations.CreateResult(run.NewResult(cnab.StatusRunning), p.TestInstallations.SetMutableResultValues) + installation.ApplyResult(run, result) + installation.Status.Action = "customaction" + displayInstallationStatus = getDisplayInstallationStatus(installation) + require.Equal(t, "running customaction", displayInstallationStatus) +} diff --git a/pkg/porter/testdata/list/expected-output.json b/pkg/porter/testdata/list/expected-output.json new file mode 100644 index 000000000..683117465 --- /dev/null +++ b/pkg/porter/testdata/list/expected-output.json @@ -0,0 +1,28 @@ +[ + { + "schemaType": "Installation", + "schemaVersion": "1.0.1", + "id": "01FZVC5AVP8Z7A78CSCP1EJ604", + "name": "mywordpress", + "namespace": "dev", + "bundle": {}, + "status": { + "runId": "", + "action": "", + "resultId": "", + "resultStatus": "succeeded", + "created": "2020-04-18T01:02:03.000000004Z", + "modified": "2020-04-18T01:02:03.000000004Z", + "installed": null, + "uninstalled": null, + "bundleReference": "", + "bundleVersion": "v1.2.3", + "bundleDigest": "" + }, + "_calculated": { + "resolvedParameters": null, + "displayInstallationState": "defined", + "displayInstallationStatus": "succeeded" + } + } +] diff --git a/pkg/porter/testdata/list/expected-output.txt b/pkg/porter/testdata/list/expected-output.txt new file mode 100644 index 000000000..2fd40a2bc --- /dev/null +++ b/pkg/porter/testdata/list/expected-output.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------- + NAMESPACE NAME VERSION STATE STATUS MODIFIED +------------------------------------------------------------------- + dev mywordpress v1.2.3 defined succeeded 2020-04-18 diff --git a/pkg/porter/testdata/list/expected-output.yaml b/pkg/porter/testdata/list/expected-output.yaml new file mode 100644 index 000000000..09e56e376 --- /dev/null +++ b/pkg/porter/testdata/list/expected-output.yaml @@ -0,0 +1,22 @@ +- schemaType: Installation + schemaVersion: 1.0.1 + id: 01FZVC5AVP8Z7A78CSCP1EJ604 + name: mywordpress + namespace: dev + bundle: {} + status: + runId: "" + action: "" + resultId: "" + resultStatus: succeeded + created: 2020-04-18T01:02:03.000000004Z + modified: 2020-04-18T01:02:03.000000004Z + installed: null + uninstalled: null + bundleReference: "" + bundleVersion: v1.2.3 + bundleDigest: "" + _calculated: + resolvedParameters: [] + displayInstallationState: defined + displayInstallationStatus: succeeded diff --git a/pkg/porter/testdata/list/no-reference-expected-output.txt b/pkg/porter/testdata/list/no-reference-expected-output.txt new file mode 100644 index 000000000..2fd40a2bc --- /dev/null +++ b/pkg/porter/testdata/list/no-reference-expected-output.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------- + NAMESPACE NAME VERSION STATE STATUS MODIFIED +------------------------------------------------------------------- + dev mywordpress v1.2.3 defined succeeded 2020-04-18 diff --git a/pkg/porter/testdata/show/expected-output.json b/pkg/porter/testdata/show/expected-output.json index 74e15467a..d201ecb75 100644 --- a/pkg/porter/testdata/show/expected-output.json +++ b/pkg/porter/testdata/show/expected-output.json @@ -52,6 +52,8 @@ "sensitive": false, "value": "top-secret" } - ] + ], + "displayInstallationState": "installed", + "displayInstallationStatus": "succeeded" } } diff --git a/pkg/porter/testdata/show/expected-output.yaml b/pkg/porter/testdata/show/expected-output.yaml index bffef6425..e1f9983cc 100644 --- a/pkg/porter/testdata/show/expected-output.yaml +++ b/pkg/porter/testdata/show/expected-output.yaml @@ -40,3 +40,5 @@ _calculated: type: unknown sensitive: false value: top-secret + displayInstallationState: installed + displayInstallationStatus: succeeded diff --git a/pkg/storage/installation.go b/pkg/storage/installation.go index c60470fd3..141f822ce 100644 --- a/pkg/storage/installation.go +++ b/pkg/storage/installation.go @@ -229,6 +229,11 @@ func (i Installation) IsUninstalled() bool { return i.Status.Uninstalled != nil } +// IsDefined checks if the installation is has already been defined but not installed yet. +func (i Installation) IsDefined() bool { + return i.Status.Installed == nil +} + // OCIReferenceParts is our storage representation of cnab.OCIReference // with the parts explicitly stored separately so that they are queryable. type OCIReferenceParts struct { diff --git a/pkg/storage/installation_provider_helpers.go b/pkg/storage/installation_provider_helpers.go index f9ef0423e..e893bce9a 100644 --- a/pkg/storage/installation_provider_helpers.go +++ b/pkg/storage/installation_provider_helpers.go @@ -61,7 +61,7 @@ func (p *TestInstallationProvider) SetMutableInstallationValues(i *Installation) i.Status.Modified = now } -// CreateRun creates a new claim and saves it. +// CreateRun creates a new test run and saves it. func (p *TestInstallationProvider) CreateRun(r Run, transformations ...func(r *Run)) Run { for _, transform := range transformations { transform(&r) @@ -78,7 +78,7 @@ func (p *TestInstallationProvider) SetMutableRunValues(r *Run) { r.Created = now } -// CreateResult creates a new result from the specified claim and saves it. +// CreateResult creates a new test result and saves it. func (p *TestInstallationProvider) CreateResult(r Result, transformations ...func(r *Result)) Result { for _, transform := range transformations { transform(&r) @@ -95,7 +95,7 @@ func (p *TestInstallationProvider) SetMutableResultValues(r *Result) { r.Created = now } -// CreateOutput creates a new output from the specified claim and result and saves it. +// CreateOutput creates a new test output and saves it. func (p *TestInstallationProvider) CreateOutput(o Output, transformations ...func(o *Output)) Output { for _, transform := range transformations { transform(&o)