Skip to content

Commit

Permalink
Update installation show output
Browse files Browse the repository at this point in the history
Signed-off-by: Carolyn Van Slyck <[email protected]>
  • Loading branch information
carolynvs committed Aug 25, 2021
1 parent 80a2365 commit 82994a9
Show file tree
Hide file tree
Showing 14 changed files with 205 additions and 69 deletions.
85 changes: 80 additions & 5 deletions pkg/claims/installation.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"get.porter.sh/porter/pkg/storage"
"github.com/Masterminds/semver/v3"
"github.com/cnabio/cnab-go/schema"
"github.com/opencontainers/go-digest"
"github.com/pkg/errors"
)

Expand Down Expand Up @@ -44,6 +45,9 @@ type Installation struct {
// BundleDigest is the current digest of the bundle.
BundleDigest string `json:"bundleDigest,omitempty" yaml:"bundleDigest,omitempty" toml:"bundleDigest,omitempty"`

// BundleTag is the OCI tag of the current bundle definition.
BundleTag string `json:"bundleTag,omitempty" yaml:"bundleTag,omitempty" toml:"bundleTag,omitempty"`

// Custom extension data applicable to a given runtime.
// TODO(carolynvs): remove and populate in ToCNAB when we firm up the spec
Custom interface{} `json:"custom,omitempty" yaml:"custom,omitempty" toml:"custom,omitempty"`
Expand All @@ -53,22 +57,63 @@ type Installation struct {

// Parameters specified by the user through overrides or parameter sets.
// Does not include defaults, or values resolved from parameter sources.
Parameters map[string]interface{} `json:"parameters" yaml:"parameters" toml:"parameters"`
Parameters map[string]interface{} `json:"parameters,omitempty" yaml:"parameters,omitempty" toml:"parameters,omitempty"`

// CredentialSets that should be included when the bundle is reconciled.
CredentialSets []string `json:"credentialSets" yaml:"credentialSets" toml:"credentialSets"`
CredentialSets []string `json:"credentialSets,omitempty" yaml:"credentialSets,omitempty" toml:"credentialSets,omitempty"`

// ParameterSets that should be included when the bundle is reconciled.
ParameterSets []string `json:"parameterSets" yaml:"parameterSets" toml:"parameterSets"`
ParameterSets []string `json:"parameterSets,omitempty" yaml:"parameterSets,omitempty" toml:"parameterSets,omitempty"`

// Status of the installation.
Status InstallationStatus `json:"status" yaml:"status" toml:"status"`
Status InstallationStatus `json:"status,omitempty" yaml:"status,omitempty" toml:"status,omitempty"`
}

func (i Installation) String() string {
return fmt.Sprintf("%s/%s", i.Namespace, i.Name)
}

func (i Installation) GetBundleReference() (cnab.OCIReference, bool, error) {
if i.BundleRepository == "" {
return cnab.OCIReference{}, false, nil
}

ref, err := cnab.ParseOCIReference(i.BundleRepository)
if err != nil {
return cnab.OCIReference{}, false, errors.Wrapf(err, "invalid BundleRepository %s", i.BundleRepository)
}

if i.BundleDigest != "" {
d, err := digest.Parse(i.BundleDigest)
if err != nil {
return cnab.OCIReference{}, false, errors.Wrapf(err, "invalid BundleDigest %s", i.BundleDigest)
}

ref, err = ref.WithDigest(d)
if err != nil {
return cnab.OCIReference{}, false, errors.Wrapf(err, "error joining the BundleRepository %s and BundleDigest %s", i.BundleRepository, i.BundleDigest)
}
return ref, true, nil
}

if i.BundleVersion != "" {
v, err := semver.NewVersion(i.BundleVersion)
if err != nil {
return cnab.OCIReference{}, false, errors.New("invalid BundleVersion")
}

// The bundle version feature can only be used with standard naming conventions
// TODO(carolynvs): do we want to support desired state with non-standard snowflakes?
ref, err = ref.WithTag("v" + v.String())
if err != nil {
return cnab.OCIReference{}, false, errors.Wrapf(err, "error joining the BundleRepository %s and BundleVersion %s", i.BundleRepository, i.BundleVersion)
}
return ref, true, nil
}

return cnab.OCIReference{}, false, errors.New("Invalid installation, either BundleDigest or BundleVersion must be specified")
}

func (i Installation) DefaultDocumentFilter() interface{} {
return map[string]interface{}{"namespace": i.Namespace, "name": i.Name}
}
Expand Down Expand Up @@ -113,6 +158,9 @@ func (i Installation) NewRun(action string) Run {
func (i *Installation) ApplyResult(run Run, result Result) {
// Update the installation with the last modifying action
if action, err := run.Bundle.GetAction(run.Action); err == nil && action.Modifies {
i.Status.BundleReference = run.BundleReference
i.Status.BundleVersion = run.Bundle.Version
i.Status.BundleDigest = run.BundleDigest
i.Status.RunID = run.ID
i.Status.Action = run.Action
i.Status.ResultID = result.ID
Expand All @@ -131,7 +179,10 @@ func (i *Installation) Apply(input Installation) {
i.BundleRepository = input.BundleRepository
i.BundleDigest = input.BundleDigest
i.BundleVersion = input.BundleVersion
i.BundleTag = input.BundleTag
i.Parameters = input.Parameters
i.CredentialSets = input.CredentialSets
i.ParameterSets = input.ParameterSets
i.Labels = input.Labels
}

Expand All @@ -154,9 +205,11 @@ func (i *Installation) Validate() error {
}

if i.BundleVersion != "" {
if _, err := semver.NewVersion(i.BundleVersion); err != nil {
v, err := semver.NewVersion(i.BundleVersion)
if err != nil {
return errors.Wrapf(err, "Invalid bundleVersion. Must be a valid v2 semver value.")
}
i.BundleVersion = v.String()
}

if i.BundleRepository != "" && i.BundleDigest == "" && i.BundleVersion == "" {
Expand All @@ -166,6 +219,19 @@ func (i *Installation) Validate() error {
return nil
}

// TrackBundle updates the bundle that the installation is tracking.
func (i *Installation) TrackBundle(ref cnab.OCIReference) {
// Determine if the bundle is managed by version, digest or tag
i.BundleRepository = ref.Repository()
if ref.HasVersion() {
i.BundleVersion = ref.Version()
} else if ref.HasDigest() {
i.BundleDigest = ref.Digest().String()
} else {
i.BundleTag = ref.Tag()
}
}

// InstallationStatus's purpose is to assist with making porter list be able to display everything
// with a single database query. Do not replicate data available on Run and Result here.
type InstallationStatus struct {
Expand All @@ -185,4 +251,13 @@ type InstallationStatus struct {
// Once that state is reached, Porter should not allow it to be reinstalled as a protection from installations
// being overwritten.
InstallationCompleted bool `json:"installationCompleted" yaml:"installationCompleted" toml:"installationCompleted"`

// BundleReference of the bundle that last altered the installation state.
BundleReference string `json:"bundleReference" yaml:"bundleReference" toml:"bundleReference"`

// BundleVersion is the version of the bundle that last altered the installation state.
BundleVersion string `json:"bundleVersion" yaml:"bundleVersion" toml:"bundleVersion"`

// BundleDigest is the digest of the bundle that last altered the installation state.
BundleDigest string `json:"bundleDigest" yaml:"bundleDigest" toml:"bundleDigest"`
}
39 changes: 39 additions & 0 deletions pkg/claims/installation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestInstallation_String(t *testing.T) {
Expand All @@ -13,3 +14,41 @@ func TestInstallation_String(t *testing.T) {
i.Namespace = "dev"
assert.Equal(t, "dev/mybun", i.String())
}

func TestInstallation_GetBundleReference(t *testing.T) {
testcases := []struct {
name string
repo string
digest string
version string
wantRef string
wantErr string
}{
{name: "repo missing", wantRef: ""},
{name: "incomplete reference", repo: "getporter/porter-hello", wantErr: "either BundleDigest or BundleVersion must be specified"},
{name: "version specified", repo: "getporter/porter-hello", version: "v0.1.1", wantRef: "getporter/porter-hello:v0.1.1"},
{name: "digest specified", repo: "getporter/porter-hello", digest: "sha256:a881bbc015bade9f11d95a4244888d8e7fa8800f843b43c74cc07c7b7276b062", wantRef: "getporter/porter-hello@sha256:a881bbc015bade9f11d95a4244888d8e7fa8800f843b43c74cc07c7b7276b062"},
}

for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
i := Installation{
BundleRepository: tc.repo,
BundleDigest: tc.digest,
BundleVersion: tc.version,
}

ref, ok, err := i.GetBundleReference()
if tc.wantErr != "" {
require.Error(t, err)
assert.Contains(t, err.Error(), tc.wantErr)
} else if tc.wantRef != "" {
require.NoError(t, err)
assert.Equal(t, tc.wantRef, ref.String())
} else {
require.NoError(t, err)
require.False(t, ok)
}
})
}
}
6 changes: 3 additions & 3 deletions pkg/porter/credentials_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func TestGenerateNoName(t *testing.T) {
Silent: true,
}
opts.CNABFile = "/bundle.json"
err := opts.Validate(nil, p.Context)
err := opts.Validate(nil, p.Porter)
require.NoError(t, err, "Validate failed")

err = p.GenerateCredentials(opts)
Expand All @@ -51,7 +51,7 @@ func TestGenerateNameProvided(t *testing.T) {
opts.Name = "kool-kred"
opts.Labels = []string{"env=dev"}
opts.CNABFile = "/bundle.json"
err := opts.Validate(nil, p.Context)
err := opts.Validate(nil, p.Porter)
require.NoError(t, err, "Validate failed")

err = p.GenerateCredentials(opts)
Expand All @@ -72,7 +72,7 @@ func TestGenerateBadNameProvided(t *testing.T) {
}
opts.Name = "this.isabadname"
opts.CNABFile = "/bundle.json"
err := opts.Validate(nil, p.Context)
err := opts.Validate(nil, p.Porter)
require.NoError(t, err, "Validate failed")

err = p.GenerateCredentials(opts)
Expand Down
48 changes: 27 additions & 21 deletions pkg/porter/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,18 @@ func parseLabels(raw []string) map[string]string {
// DisplayInstallation holds a subset of pertinent values to be listed from installation data
// originating from its claims, results and outputs records
type DisplayInstallation struct {
Name string
Namespace string
Created time.Time
Modified time.Time
Bundle string
Version string
Digest string
Action string
Status string
Name string
Namespace string
Created time.Time
Modified time.Time
TrackedRepository string
TrackedVersion string
TrackedDigest string
StatusLastAction string
StatusText string
StatusReference string
StatusVersion string
StatusDigest string

Parameters DisplayValues
Labels []string
Expand All @@ -75,17 +78,20 @@ type DisplayInstallation struct {

func NewDisplayInstallation(installation claims.Installation, run *claims.Run) DisplayInstallation {
di := DisplayInstallation{
Name: installation.Name,
Namespace: installation.Namespace,
Bundle: installation.BundleRepository,
Version: installation.BundleVersion,
Digest: installation.BundleDigest,
ParameterSets: installation.ParameterSets,
CredentialSets: installation.CredentialSets,
Created: installation.Created,
Modified: installation.Modified,
Action: installation.Status.Action,
Status: installation.Status.ResultStatus,
Name: installation.Name,
Namespace: installation.Namespace,
TrackedRepository: installation.BundleRepository,
TrackedVersion: installation.BundleVersion,
TrackedDigest: installation.BundleDigest,
ParameterSets: installation.ParameterSets,
CredentialSets: installation.CredentialSets,
Created: installation.Created,
Modified: installation.Modified,
StatusReference: installation.Status.BundleReference,
StatusVersion: installation.Status.BundleVersion,
StatusDigest: installation.Status.BundleDigest,
StatusLastAction: installation.Status.Action,
StatusText: installation.Status.ResultStatus,
}

labels := make([]string, 0, len(installation.Labels))
Expand Down Expand Up @@ -179,7 +185,7 @@ func (p *Porter) PrintInstallations(opts ListOptions) error {
if !ok {
return nil
}
return []interface{}{cl.Namespace, cl.Name, tp.Format(cl.Created), tp.Format(cl.Modified), cl.Action, cl.Status}
return []interface{}{cl.Namespace, cl.Name, tp.Format(cl.Created), tp.Format(cl.Modified), cl.StatusLastAction, cl.StatusText}
}
return printer.PrintTable(p.Out, displayInstallations, row,
"NAMESPACE", "NAME", "CREATED", "MODIFIED", "LAST ACTION", "LAST STATUS")
Expand Down
8 changes: 4 additions & 4 deletions pkg/porter/list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ func TestNewDisplayInstallation(t *testing.T) {
require.Equal(t, di.Name, i.Name, "invalid installation name")
require.Equal(t, di.Created, i.Created, "invalid created time")
require.Equal(t, di.Modified, i.Modified, "invalid modified time")
require.Equal(t, cnab.ActionUpgrade, di.Action, "invalid last action")
require.Equal(t, cnab.StatusRunning, di.Status, "invalid last status")
require.Equal(t, cnab.ActionUpgrade, di.StatusLastAction, "invalid last action")
require.Equal(t, cnab.StatusRunning, di.StatusText, "invalid last status")
})

t.Run("installation has not been installed", func(t *testing.T) {
Expand All @@ -45,8 +45,8 @@ func TestNewDisplayInstallation(t *testing.T) {
require.Equal(t, di.Name, i.Name, "invalid installation name")
require.Equal(t, i.Created, di.Created, "invalid created time")
require.Equal(t, i.Modified, di.Modified, "invalid modified time")
require.Empty(t, di.Action, "invalid last action")
require.Empty(t, di.Status, "invalid last status")
require.Empty(t, di.StatusLastAction, "invalid last action")
require.Empty(t, di.StatusText, "invalid last status")
})
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/porter/parameters_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func TestGenerateParameterSet(t *testing.T) {
opts.Name = "kool-params"
opts.Labels = []string{"env=dev"}
opts.CNABFile = "/bundle.json"
err := opts.Validate(nil, p.Context)
err := opts.Validate(nil, p.Porter)
require.NoError(t, err, "Validate failed")

err = p.GenerateParameters(opts)
Expand Down
22 changes: 16 additions & 6 deletions pkg/porter/show.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,18 @@ func (p *Porter) ShowInstallation(opts ShowOptions) error {
fmt.Fprintf(p.Out, "Namespace: %s\n", displayInstallation.Namespace)
fmt.Fprintf(p.Out, "Created: %s\n", tp.Format(displayInstallation.Created))
fmt.Fprintf(p.Out, "Modified: %s\n", tp.Format(displayInstallation.Modified))
if displayInstallation.Bundle != "" {
fmt.Fprintf(p.Out, "Bundle: %s\n", displayInstallation.Bundle)

if displayInstallation.TrackedRepository != "" {
fmt.Fprintln(p.Out)
fmt.Fprintln(p.Out, "Bundle:")
fmt.Fprintf(p.Out, " Repository: %s\n", displayInstallation.TrackedRepository)
if displayInstallation.TrackedVersion != "" {
fmt.Fprintf(p.Out, " Version: %s\n", displayInstallation.TrackedVersion)
}
if displayInstallation.TrackedDigest != "" {
fmt.Fprintf(p.Out, " Digest: %s\n", displayInstallation.TrackedDigest)
}
}
fmt.Fprintf(p.Out, "Version: %s\n", displayInstallation.Version)
fmt.Fprintf(p.Out, "Digest: %s\n", displayInstallation.Digest)

// Print labels, if any
if len(displayInstallation.Labels) > 0 {
Expand Down Expand Up @@ -136,8 +143,11 @@ func (p *Porter) ShowInstallation(opts ShowOptions) error {
if installation.Status != (claims.InstallationStatus{}) {
fmt.Fprintln(p.Out)
fmt.Fprintln(p.Out, "Status:")
fmt.Fprintf(p.Out, " Last Action: %s\n", displayInstallation.Action)
fmt.Fprintf(p.Out, " Status: %s\n", displayInstallation.Status)
fmt.Fprintf(p.Out, " Reference: %s\n", displayInstallation.StatusReference)
fmt.Fprintf(p.Out, " Version: %s\n", displayInstallation.StatusVersion)
fmt.Fprintf(p.Out, " Last Action: %s\n", displayInstallation.StatusLastAction)
fmt.Fprintf(p.Out, " Status: %s\n", displayInstallation.StatusText)
fmt.Fprintf(p.Out, " Digest: %s\n", displayInstallation.StatusDigest)
}

return nil
Expand Down
Loading

0 comments on commit 82994a9

Please sign in to comment.