@@ -3,7 +3,10 @@ package porter
3
3
import (
4
4
"fmt"
5
5
"os"
6
+ "path/filepath"
7
+ "reflect"
6
8
"sort"
9
+ "strings"
7
10
"time"
8
11
9
12
"github.com/deislabs/porter/pkg/context"
@@ -12,10 +15,18 @@ import (
12
15
13
16
dtprinter "github.com/carolynvs/datetime-printer"
14
17
credentials "github.com/deislabs/cnab-go/credentials"
18
+ tablewriter "github.com/olekukonko/tablewriter"
15
19
"github.com/pkg/errors"
16
20
yaml "gopkg.in/yaml.v2"
17
21
)
18
22
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
+
19
30
// CredentialsFile represents a CNAB credentials file and corresponding metadata
20
31
type CredentialsFile struct {
21
32
Name string
@@ -35,7 +46,20 @@ func (l CredentialsFileList) Less(i, j int) bool {
35
46
return l [i ].Modified .Before (l [j ].Modified )
36
47
}
37
48
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
39
63
func (p * Porter ) fetchCredentials () (* CredentialsFileList , error ) {
40
64
credsDir , err := p .Config .GetCredentialsDir ()
41
65
if err != nil {
@@ -46,17 +70,12 @@ func (p *Porter) fetchCredentials() (*CredentialsFileList, error) {
46
70
if ok , _ := p .Context .FileSystem .DirExists (credsDir ); ok {
47
71
p .Context .FileSystem .Walk (credsDir , func (path string , info os.FileInfo , err error ) error {
48
72
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 )
51
75
if err != nil {
76
+ // If an error is encountered while reading, log and move on to the next
52
77
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 ())
60
79
}
61
80
return nil
62
81
}
@@ -70,6 +89,23 @@ func (p *Porter) fetchCredentials() (*CredentialsFileList, error) {
70
89
return & credentialsFiles , nil
71
90
}
72
91
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
+
73
109
// ListCredentials lists credentials using the provided printer.PrintOptions
74
110
func (p * Porter ) ListCredentials (opts printer.PrintOptions ) error {
75
111
credentialsFiles , err := p .fetchCredentials ()
@@ -203,3 +239,104 @@ func (p *Porter) GenerateCredentials(opts CredentialOptions) error {
203
239
}
204
240
return nil
205
241
}
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