Skip to content

Commit 341aea0

Browse files
vdicejeremyrickard
authored andcommitted
Implement porter credential show command (#401)
* feat(credentials): add porter credential show command * feat(credentials.go): change tablewriter for credentials show * ref(credentials.go): split credential fetch/read from listing
1 parent a497af3 commit 341aea0

18 files changed

+2719
-20
lines changed

Gopkg.lock

+17
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/porter/credentials.go

+13-6
Original file line numberDiff line numberDiff line change
@@ -146,17 +146,24 @@ func buildCredentialsRemoveCommand(p *porter.Porter) *cobra.Command {
146146
}
147147

148148
func buildCredentialsShowCommand(p *porter.Porter) *cobra.Command {
149+
opts := porter.CredentialShowOptions{}
150+
149151
cmd := &cobra.Command{
150-
Use: "show",
151-
Short: "Show a Credential",
152-
Hidden: true,
152+
Use: "show",
153+
Short: "Show a Credential",
154+
Long: `Show a particular credential set, including all named credentials and their corresponding mappings.`,
155+
Example: ` porter credential show NAME [-o table|json|yaml]`,
153156
PreRunE: func(cmd *cobra.Command, args []string) error {
154-
return nil
157+
return opts.Validate(args)
155158
},
156159
RunE: func(cmd *cobra.Command, args []string) error {
157-
p.PrintVersion()
158-
return nil
160+
return p.ShowCredential(opts)
159161
},
160162
}
163+
164+
f := cmd.Flags()
165+
f.StringVarP(&opts.RawFormat, "output", "o", "table",
166+
"Specify an output format. Allowed values: table, json, yaml")
167+
161168
return cmd
162169
}

pkg/porter/credentials.go

+147-10
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ package porter
33
import (
44
"fmt"
55
"os"
6+
"path/filepath"
7+
"reflect"
68
"sort"
9+
"strings"
710
"time"
811

912
"github.com/deislabs/porter/pkg/context"
@@ -12,10 +15,18 @@ import (
1215

1316
dtprinter "github.com/carolynvs/datetime-printer"
1417
credentials "github.com/deislabs/cnab-go/credentials"
18+
tablewriter "github.com/olekukonko/tablewriter"
1519
"github.com/pkg/errors"
1620
yaml "gopkg.in/yaml.v2"
1721
)
1822

23+
// CredentialShowOptions represent options for Porter's credential show command
24+
type CredentialShowOptions struct {
25+
RawFormat string
26+
Format printer.Format
27+
Name string
28+
}
29+
1930
// CredentialsFile represents a CNAB credentials file and corresponding metadata
2031
type CredentialsFile struct {
2132
Name string
@@ -35,7 +46,20 @@ func (l CredentialsFileList) Less(i, j int) bool {
3546
return l[i].Modified.Before(l[j].Modified)
3647
}
3748

38-
// fetchCredentials fetches all credentials from the designated credentials dir
49+
// fetchCredential returns a *credentials.CredentialsSet according to the supplied
50+
// credential name, or an error if encountered
51+
func (p *Porter) fetchCredential(name string) (*credentials.CredentialSet, error) {
52+
credsDir, err := p.Config.GetCredentialsDir()
53+
if err != nil {
54+
return nil, errors.Wrap(err, "unable to determine credentials directory")
55+
}
56+
57+
path := filepath.Join(credsDir, fmt.Sprintf("%s.yaml", name))
58+
return p.readCredential(name, path)
59+
}
60+
61+
// fetchCredentials fetches all credentials in the form of a CredentialsFileList
62+
// from the designated credentials dir, or an error if encountered
3963
func (p *Porter) fetchCredentials() (*CredentialsFileList, error) {
4064
credsDir, err := p.Config.GetCredentialsDir()
4165
if err != nil {
@@ -46,17 +70,12 @@ func (p *Porter) fetchCredentials() (*CredentialsFileList, error) {
4670
if ok, _ := p.Context.FileSystem.DirExists(credsDir); ok {
4771
p.Context.FileSystem.Walk(credsDir, func(path string, info os.FileInfo, err error) error {
4872
if !info.IsDir() {
49-
credSet := &credentials.CredentialSet{}
50-
data, err := p.Context.FileSystem.ReadFile(path)
73+
credName := strings.Split(info.Name(), ".")[0]
74+
credSet, err := p.readCredential(credName, path)
5175
if err != nil {
76+
// If an error is encountered while reading, log and move on to the next
5277
if p.Debug {
53-
fmt.Fprintf(p.Err, "unable to load credential set from %s: %s\n", path, err)
54-
}
55-
return nil
56-
}
57-
if err = yaml.Unmarshal(data, credSet); err != nil {
58-
if p.Debug {
59-
fmt.Fprintf(p.Err, "unable to unmarshal credential set from file %s: %s\n", info.Name(), err)
78+
fmt.Fprint(p.Err, err.Error())
6079
}
6180
return nil
6281
}
@@ -70,6 +89,23 @@ func (p *Porter) fetchCredentials() (*CredentialsFileList, error) {
7089
return &credentialsFiles, nil
7190
}
7291

92+
// readCredential reads a credential with the given name via the provided path
93+
// and returns a CredentialSet or an error, if encountered
94+
func (p *Porter) readCredential(name, path string) (*credentials.CredentialSet, error) {
95+
credSet := &credentials.CredentialSet{}
96+
97+
data, err := p.Context.FileSystem.ReadFile(path)
98+
if err != nil {
99+
return credSet, errors.Wrapf(err, "unable to load credential %s", name)
100+
}
101+
102+
if err = yaml.Unmarshal(data, credSet); err != nil {
103+
return credSet, errors.Wrapf(err, "unable to unmarshal credential %s", name)
104+
}
105+
106+
return credSet, nil
107+
}
108+
73109
// ListCredentials lists credentials using the provided printer.PrintOptions
74110
func (p *Porter) ListCredentials(opts printer.PrintOptions) error {
75111
credentialsFiles, err := p.fetchCredentials()
@@ -203,3 +239,104 @@ func (p *Porter) GenerateCredentials(opts CredentialOptions) error {
203239
}
204240
return nil
205241
}
242+
243+
// Validate validates the args provided Porter's credential show command
244+
func (o *CredentialShowOptions) Validate(args []string) error {
245+
switch len(args) {
246+
case 0:
247+
return errors.Errorf("no credential name was specified")
248+
case 1:
249+
o.Name = strings.ToLower(args[0])
250+
default:
251+
return errors.Errorf("only one positional argument may be specified, the credential name, but multiple were received: %s", args)
252+
}
253+
254+
format, err := printer.ParseFormat(o.RawFormat)
255+
if err != nil {
256+
return err
257+
}
258+
o.Format = format
259+
260+
return nil
261+
}
262+
263+
// ShowCredential shows the credential set corresponding to the provided name, using
264+
// the provided printer.PrintOptions for display.
265+
func (p *Porter) ShowCredential(opts CredentialShowOptions) error {
266+
credSet, err := p.fetchCredential(opts.Name)
267+
if err != nil {
268+
return err
269+
}
270+
271+
switch opts.Format {
272+
case printer.FormatJson:
273+
return printer.PrintJson(p.Out, credSet)
274+
case printer.FormatYaml:
275+
return printer.PrintYaml(p.Out, credSet)
276+
case printer.FormatTable:
277+
// Here we use an instance of olekukonko/tablewriter as our table,
278+
// rather than using the printer pkg variant, as we wish to decorate
279+
// the table a bit differently from the default
280+
var rows [][]string
281+
282+
// Iterate through all CredentialStrategies and add to rows
283+
for _, cs := range credSet.Credentials {
284+
sourceVal, sourceType := GetCredentialSourceValueAndType(cs.Source)
285+
rows = append(rows, []string{cs.Name, sourceVal, sourceType})
286+
}
287+
288+
// Build and configure our tablewriter
289+
table := tablewriter.NewWriter(p.Out)
290+
table.SetCenterSeparator("")
291+
table.SetColumnSeparator("")
292+
table.SetAlignment(tablewriter.ALIGN_LEFT)
293+
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
294+
table.SetBorders(tablewriter.Border{Left: false, Right: false, Bottom: false, Top: true})
295+
table.SetAutoFormatHeaders(false)
296+
297+
// First, print the CredentialSet name
298+
fmt.Fprintf(p.Out, "Name: %s\n\n", credSet.Name)
299+
300+
// Now print the table
301+
table.SetHeader([]string{"Name", "Local Source", "Source Type"})
302+
for _, row := range rows {
303+
table.Append(row)
304+
}
305+
table.Render()
306+
return nil
307+
default:
308+
return fmt.Errorf("invalid format: %s", opts.Format)
309+
}
310+
}
311+
312+
type reflectedStruct struct {
313+
Value reflect.Value
314+
Type reflect.Type
315+
}
316+
317+
// GetCredentialSourceValueAndType takes a given credentials.Source struct and
318+
// returns the source value itself as well as source type, e.g., 'Path', 'EnvVar', etc.,
319+
// both in their string forms
320+
func GetCredentialSourceValueAndType(cs credentials.Source) (string, string) {
321+
var sourceVal, sourceType string
322+
323+
// Build a reflected credentials.Source struct
324+
reflectedSource := reflectedStruct{
325+
Value: reflect.ValueOf(cs),
326+
Type: reflect.TypeOf(cs),
327+
}
328+
329+
// Iterate through all of the fields of a credentials.Source struct
330+
for i := 0; i < reflectedSource.Type.NumField(); i++ {
331+
// A Field name would be 'Path', 'EnvVar', etc.
332+
fieldName := reflectedSource.Type.Field(i).Name
333+
// Get the value for said Field
334+
fieldValue := reflect.Indirect(reflectedSource.Value).FieldByName(fieldName).String()
335+
// If value non-empty, this field value and name represent our source value
336+
// and source type, respectively
337+
if fieldValue != "" {
338+
sourceVal, sourceType = fieldValue, fieldName
339+
}
340+
}
341+
return sourceVal, sourceType
342+
}

0 commit comments

Comments
 (0)