-
Notifications
You must be signed in to change notification settings - Fork 209
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add porter bundle list command #329
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ import ( | |
"strings" | ||
|
||
"github.com/deislabs/porter/pkg/porter" | ||
"github.com/deislabs/porter/pkg/printer" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
|
@@ -20,6 +21,7 @@ func buildBundlesCommand(p *porter.Porter) *cobra.Command { | |
|
||
cmd.AddCommand(buildBundleCreateCommand(p)) | ||
cmd.AddCommand(buildBundleBuildCommand(p)) | ||
cmd.AddCommand(buildBundleListCommand(p)) | ||
cmd.AddCommand(buildBundleInstallCommand(p)) | ||
cmd.AddCommand(buildBundleUpgradeCommand(p)) | ||
cmd.AddCommand(buildBundleUninstallCommand(p)) | ||
|
@@ -77,6 +79,36 @@ func buildBuildCommand(p *porter.Porter) *cobra.Command { | |
return cmd | ||
} | ||
|
||
func buildBundleListCommand(p *porter.Porter) *cobra.Command { | ||
opts := porter.ListOptions{} | ||
|
||
cmd := &cobra.Command{ | ||
Use: "list", | ||
Short: "list installed bundles", | ||
Long: `List all bundles installed by Porter. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💯 |
||
|
||
A listing of bundles currently installed by Porter will be provided, along with metadata such as creation time, last action, last status, etc. | ||
|
||
Optional output formats include json and yaml.`, | ||
Example: ` porter bundle list | ||
porter bundle list -o json`, | ||
PreRunE: func(cmd *cobra.Command, args []string) error { | ||
var err error | ||
opts.Format, err = printer.ParseFormat(opts.RawFormat) | ||
return err | ||
}, | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
return p.ListBundles(printer.PrintOptions{Format: opts.Format}) | ||
}, | ||
} | ||
|
||
f := cmd.Flags() | ||
f.StringVarP(&opts.RawFormat, "output", "o", "table", | ||
"Specify an output format. Allowed values: table, json, yaml") | ||
|
||
return cmd | ||
} | ||
|
||
func buildBundleInstallCommand(p *porter.Porter) *cobra.Command { | ||
opts := porter.InstallOptions{} | ||
cmd := &cobra.Command{ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -66,3 +66,34 @@ func TestValidateUninstallCommand(t *testing.T) { | |
}) | ||
} | ||
} | ||
|
||
func TestValidateBundleListCommand(t *testing.T) { | ||
testcases := []struct { | ||
name string | ||
args string | ||
wantError string | ||
}{ | ||
{"no args", "bundle list", ""}, | ||
{"output json", "bundle list -o json", ""}, | ||
{"invalid format", "bundle list -o wingdings", "invalid format: wingdings"}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. haha wingdings |
||
} | ||
|
||
for _, tc := range testcases { | ||
t.Run(tc.name, func(t *testing.T) { | ||
p := buildRootCommand() | ||
osargs := strings.Split(tc.args, " ") | ||
cmd, args, err := p.Find(osargs) | ||
require.NoError(t, err) | ||
|
||
err = cmd.ParseFlags(args) | ||
require.NoError(t, err) | ||
|
||
err = cmd.PreRunE(cmd, cmd.Flags().Args()) | ||
if tc.wantError == "" { | ||
require.NoError(t, err) | ||
} else { | ||
require.EqualError(t, err, tc.wantError) | ||
} | ||
}) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
package porter | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/pkg/errors" | ||
|
||
cnab "github.com/deislabs/porter/pkg/cnab/provider" | ||
printer "github.com/deislabs/porter/pkg/printer" | ||
) | ||
|
||
// ListOptions represent options for a bundle list command | ||
type ListOptions struct { | ||
RawFormat string | ||
Format printer.Format | ||
} | ||
|
||
const ( | ||
// TimeFormat is used to generate a human-readable representation of a raw time.Time value | ||
TimeFormat = "Mon Jan _2 15:04:05" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 Seriously, I'm so going to make a prettytime pkg now. |
||
) | ||
|
||
// CondensedClaim holds a subset of pertinent values to be listed from a claim.Claim | ||
type CondensedClaim struct { | ||
Name string | ||
Created string | ||
Modified string | ||
Action string | ||
Status string | ||
} | ||
|
||
// ListBundles lists installed bundles using the printer.Format provided | ||
func (p *Porter) ListBundles(opts printer.PrintOptions) error { | ||
cp := cnab.NewDuffle(p.Config) | ||
claimStore := cp.NewClaimStore() | ||
claims, err := claimStore.ReadAll() | ||
if err != nil { | ||
return errors.Wrap(err, "could not list claims") | ||
} | ||
|
||
var condensedClaims []CondensedClaim | ||
for _, claim := range claims { | ||
condensedClaim := CondensedClaim{ | ||
Name: claim.Name, | ||
Created: fmt.Sprint(claim.Created.Format(TimeFormat)), | ||
Modified: fmt.Sprint(claim.Modified.Format(TimeFormat)), | ||
Action: claim.Result.Action, | ||
Status: claim.Result.Status, | ||
} | ||
condensedClaims = append(condensedClaims, condensedClaim) | ||
} | ||
|
||
switch opts.Format { | ||
case printer.FormatJson: | ||
return printer.PrintJson(p.Out, condensedClaims) | ||
case printer.FormatYaml: | ||
return printer.PrintYaml(p.Out, condensedClaims) | ||
case printer.FormatTable: | ||
printClaimRow := | ||
func(v interface{}) []interface{} { | ||
cl, ok := v.(CondensedClaim) | ||
if !ok { | ||
return nil | ||
} | ||
return []interface{}{cl.Name, cl.Created, cl.Modified, cl.Action, cl.Status} | ||
} | ||
return printer.PrintTable(p.Out, condensedClaims, printClaimRow, | ||
"NAME", "CREATED", "MODIFIED", "LAST ACTION", "LAST STATUS") | ||
default: | ||
return fmt.Errorf("invalid format: %s", opts.Format) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,10 +21,24 @@ func PrintTable(out io.Writer, v interface{}, getRow func(row interface{}) []int | |
|
||
table := NewTableWriter(out) | ||
if len(headers) > 0 { | ||
fmt.Fprintln(table, headers...) | ||
fmt.Fprintln(table, tabify(headers)...) | ||
} | ||
for i := 0; i < rows.Len(); i++ { | ||
fmt.Fprintln(table, getRow(rows.Index(i).Interface())...) | ||
fmt.Fprintln(table, tabify(getRow(rows.Index(i).Interface()))...) | ||
} | ||
return table.Flush() | ||
} | ||
|
||
// tabify is a helper function which takes a slice and injects tab characters | ||
// between each element such that tabwriter can work its magic | ||
func tabify(untabified []interface{}) []interface{} { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. D'oh! I always use a better package than the stdlib I think I'll do a follow-up later to switch to a table writer package that I like better but thank you, this fixes the stdlib for now. 👍 |
||
var tabified []interface{} | ||
for i := 0; i < len(untabified); i++ { | ||
tabified = append(tabified, untabified[i]) | ||
// only append tab character if prior to last element | ||
if i+1 < len(untabified) { | ||
tabified = append(tabified, "\t") | ||
} | ||
} | ||
return tabified | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yay! Thanks for fixing my past mistakes for me. 🙇♀