From 618f2e656f12fb6e57ef1e008429f159a13c797d Mon Sep 17 00:00:00 2001 From: Vaughn Dice Date: Fri, 31 May 2019 17:41:27 -0600 Subject: [PATCH 1/3] feat(credentials): add credentials list command --- Gopkg.lock | 15 +- cmd/porter/credentials.go | 29 +-- .../generator.go | 4 +- .../generator_test.go | 2 +- pkg/porter/credentials.go | 94 ++++++++- pkg/porter/credentials_test.go | 106 +++++++++- .../testdata/test-creds/kool-kreds.yaml | 5 + .../cnab-go/credentials/credentialset.go | 183 ++++++++++++++++++ 8 files changed, 417 insertions(+), 21 deletions(-) rename pkg/{credentials => credentialsgenerator}/generator.go (97%) rename pkg/{credentials => credentialsgenerator}/generator_test.go (98%) create mode 100644 pkg/porter/testdata/test-creds/kool-kreds.yaml create mode 100644 vendor/github.com/deislabs/cnab-go/credentials/credentialset.go diff --git a/Gopkg.lock b/Gopkg.lock index ee8d24a4e..da3665ec7 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -213,12 +213,24 @@ version = "v1.1.1" [[projects]] +<<<<<<< HEAD branch = "custom-extensions" digest = "1:dfe6ce9e92d2315c432afc126c2527b99b1b2212f4af7df1629c818d0d8dafbe" +======= + branch = "master" + digest = "1:c40d52bd9a6ce87eeb00a2d93e375c7bc92acb96426627da6555b21a264c1bdf" +>>>>>>> feat(credentials): add credentials list command name = "github.com/deislabs/cnab-go" - packages = ["bundle"] + packages = [ + "bundle", + "credentials", + ] pruneopts = "NUT" +<<<<<<< HEAD revision = "1aa435456c4b74dba1d2276cb45a8482e33477a9" +======= + revision = "ef2eb2a629bbca48ca1ab277b3b3bfb60eb53316" +>>>>>>> feat(credentials): add credentials list command [[projects]] digest = "1:a222203ae2f42f5a3d06fde5f9746e51291567a3df8a5ebedce334cd93b7b97a" @@ -1401,6 +1413,7 @@ "github.com/cbroglie/mustache", "github.com/containerd/containerd", "github.com/deislabs/cnab-go/bundle", + "github.com/deislabs/cnab-go/credentials", "github.com/deislabs/duffle/pkg/action", "github.com/deislabs/duffle/pkg/bundle", "github.com/deislabs/duffle/pkg/claim", diff --git a/cmd/porter/credentials.go b/cmd/porter/credentials.go index 247f23730..429428af6 100644 --- a/cmd/porter/credentials.go +++ b/cmd/porter/credentials.go @@ -9,7 +9,7 @@ import ( func buildCredentialsCommand(p *porter.Porter) *cobra.Command { cmd := &cobra.Command{ Use: "credentials", - Aliases: []string{"cred"}, + Aliases: []string{"credential", "cred", "creds"}, Annotations: map[string]string{"group": "resource"}, Short: "Credentials commands", } @@ -102,25 +102,32 @@ will then provide it to the bundle in the correct location. `, } func buildCredentialsListCommand(p *porter.Porter) *cobra.Command { - opts := struct { - rawFormat string - format printer.Format - }{} + opts := porter.ListOptions{} + cmd := &cobra.Command{ - Use: "list", - Short: "List credentials", - Hidden: true, + Use: "list", + Aliases: []string{"ls"}, + Short: "List credentials", + Long: `List credentials available to Porter. + +A listing of credentials currently available to Porter will be provided, along with metadata such as modification time, etc. + +Optional output formats include json and yaml.`, + Example: ` porter credentials list [-o table|json|yaml]`, PreRunE: func(cmd *cobra.Command, args []string) error { var err error - opts.format, err = printer.ParseFormat(opts.rawFormat) + opts.Format, err = printer.ParseFormat(opts.RawFormat) return err }, RunE: func(cmd *cobra.Command, args []string) error { - p.PrintVersion() - return nil + return p.ListCredentials(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 } diff --git a/pkg/credentials/generator.go b/pkg/credentialsgenerator/generator.go similarity index 97% rename from pkg/credentials/generator.go rename to pkg/credentialsgenerator/generator.go index 1747dafc4..c762e5598 100644 --- a/pkg/credentials/generator.go +++ b/pkg/credentialsgenerator/generator.go @@ -1,4 +1,4 @@ -package credentials +package credentialsgenerator import ( "errors" @@ -6,8 +6,8 @@ import ( "sort" "strings" + "github.com/deislabs/cnab-go/credentials" "github.com/deislabs/duffle/pkg/bundle" - "github.com/deislabs/duffle/pkg/credentials" survey "gopkg.in/AlecAivazis/survey.v1" ) diff --git a/pkg/credentials/generator_test.go b/pkg/credentialsgenerator/generator_test.go similarity index 98% rename from pkg/credentials/generator_test.go rename to pkg/credentialsgenerator/generator_test.go index e62a3799b..10bdecac8 100644 --- a/pkg/credentials/generator_test.go +++ b/pkg/credentialsgenerator/generator_test.go @@ -1,4 +1,4 @@ -package credentials +package credentialsgenerator import ( "fmt" diff --git a/pkg/porter/credentials.go b/pkg/porter/credentials.go index de81e99e0..7cdc3ff45 100644 --- a/pkg/porter/credentials.go +++ b/pkg/porter/credentials.go @@ -2,16 +2,100 @@ package porter import ( "fmt" + "os" + "sort" + "time" "github.com/deislabs/porter/pkg/context" - "github.com/deislabs/porter/pkg/credentials" + "github.com/deislabs/porter/pkg/credentialsgenerator" "github.com/deislabs/porter/pkg/printer" + + dtprinter "github.com/carolynvs/datetime-printer" + credentials "github.com/deislabs/cnab-go/credentials" "github.com/pkg/errors" yaml "gopkg.in/yaml.v2" ) -func (p *Porter) PrintCredentials(opts printer.PrintOptions) error { - return nil +// CredentialsFile represents a CNAB credentials file and corresponding metadata +type CredentialsFile struct { + Name string + Modified time.Time +} + +// CredentialsFileList is a slice of CredentialsFiles +type CredentialsFileList []CredentialsFile + +func (l CredentialsFileList) Len() int { + return len(l) +} +func (l CredentialsFileList) Swap(i, j int) { + l[i], l[j] = l[j], l[i] +} +func (l CredentialsFileList) Less(i, j int) bool { + return l[i].Modified.Before(l[j].Modified) +} + +// ListCredentials lists credentials using the provided printer.PrintOptions +func (p *Porter) ListCredentials(opts printer.PrintOptions) error { + dir, err := p.Config.GetCredentialsDir() + if err != nil { + return errors.Wrap(err, "unable to determine credentials directory") + } + + // TODO: should this go somewhere else? + // i.e., where should logic for init-ing dirs needed by porter go? + // e.g., on 'porter creds list' with a fresh porter install/home + if ok, _ := p.Context.FileSystem.DirExists(dir); !ok { + p.Context.FileSystem.Mkdir(dir, os.ModePerm) + } + + credentialsFiles := CredentialsFileList{} + err = p.Context.FileSystem.Walk(dir, func(path string, info os.FileInfo, err error) error { + if !info.IsDir() { + credSet := &credentials.CredentialSet{} + data, err := p.Context.FileSystem.ReadFile(path) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("unable to load credential set from %s:\n%s", path, err)) + } + if err = yaml.Unmarshal(data, credSet); err != nil { + return errors.Wrap(err, "unable to unmarshal credential set") + } + credentialsFiles = append(credentialsFiles, + CredentialsFile{Name: credSet.Name, Modified: info.ModTime()}) + } + return nil + }) + if err != nil { + return errors.Wrap(err, "encountered error while listing credentials") + } + sort.Sort(sort.Reverse(credentialsFiles)) + + switch opts.Format { + case printer.FormatJson: + return printer.PrintJson(p.Out, credentialsFiles) + case printer.FormatYaml: + return printer.PrintYaml(p.Out, credentialsFiles) + case printer.FormatTable: + // TODO: update table printing + // have every row use the same "now" starting ... NOW! + now := time.Now() + tp := dtprinter.DateTimePrinter{ + Now: func() time.Time { return now }, + } + + printCredRow := + func(v interface{}) []interface{} { + cr, ok := v.(CredentialsFile) + if !ok { + return nil + } + return []interface{}{cr.Name, tp.Format(cr.Modified)} + } + return printer.PrintTable(p.Out, credentialsFiles, printCredRow, + "NAME", "MODIFIED") + default: + return fmt.Errorf("invalid format: %s", opts.Format) + } } type CredentialOptions struct { @@ -56,7 +140,7 @@ func (p *Porter) GenerateCredentials(opts CredentialOptions) error { if name == "" { name = bundle.Name } - genOpts := credentials.GenerateOptions{ + genOpts := credentialsgenerator.GenerateOptions{ Name: name, Credentials: bundle.Credentials, Silent: opts.Silent, @@ -64,7 +148,7 @@ func (p *Porter) GenerateCredentials(opts CredentialOptions) error { fmt.Fprintf(p.Out, "Generating new credential %s from bundle %s\n", genOpts.Name, bundle.Name) fmt.Fprintf(p.Out, "==> %d credentials required for bundle %s\n", len(genOpts.Credentials), bundle.Name) - cs, err := credentials.GenerateCredentials(genOpts) + cs, err := credentialsgenerator.GenerateCredentials(genOpts) if err != nil { return errors.Wrap(err, "unable to generate credentials") } diff --git a/pkg/porter/credentials_test.go b/pkg/porter/credentials_test.go index 8f97b3ef9..7248a6280 100644 --- a/pkg/porter/credentials_test.go +++ b/pkg/porter/credentials_test.go @@ -3,8 +3,10 @@ package porter import ( "testing" - "github.com/deislabs/duffle/pkg/bundle" cnabprovider "github.com/deislabs/porter/pkg/cnab/provider" + printer "github.com/deislabs/porter/pkg/printer" + + "github.com/deislabs/duffle/pkg/bundle" "github.com/stretchr/testify/require" ) @@ -81,3 +83,105 @@ func TestGenerateBadNameProvided(t *testing.T) { _, err = p.Porter.Context.FileSystem.Stat(path) require.Error(t, err, "expected the file %s to not exist", path) } + +type CredentialsListTest struct { + name string + format printer.Format + wantContains string + errorMsg string +} + +func TestCredentialsList_None(t *testing.T) { + testcases := []CredentialsListTest{ + { + name: "invalid format", + format: "wingdings", + wantContains: "", + errorMsg: "invalid format: wingdings", + }, + { + name: "json", + format: printer.FormatJson, + wantContains: "[]\n", + errorMsg: "", + }, + { + name: "yaml", + format: printer.FormatYaml, + wantContains: "[]\n\n", + errorMsg: "", + }, + { + name: "table", + format: printer.FormatTable, + wantContains: "NAME MODIFIED\n", + errorMsg: "", + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + p := NewTestPorter(t) + p.CNAB = &TestCNABProvider{} + + listOpts := printer.PrintOptions{ + Format: tc.format, + } + err := p.ListCredentials(listOpts) + if tc.errorMsg != "" { + require.Equal(t, err.Error(), tc.errorMsg) + } else { + require.NoError(t, err, "no error should have existed") + } + + gotOutput := p.TestConfig.TestContext.GetOutput() + require.Equal(t, tc.wantContains, gotOutput) + }) + } +} + +func TestCredentialsList(t *testing.T) { + testcases := []CredentialsListTest{ + { + name: "json", + format: printer.FormatJson, + wantContains: `"Name": "kool-kreds"`, + errorMsg: "", + }, + { + name: "yaml", + format: printer.FormatYaml, + wantContains: `- name: kool-kreds`, + errorMsg: "", + }, + { + name: "table", + format: printer.FormatTable, + wantContains: `NAME MODIFIED +kool-kreds now`, + errorMsg: "", + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + p := NewTestPorter(t) + p.CNAB = &TestCNABProvider{} + + credsDir, err := p.TestConfig.GetCredentialsDir() + require.NoError(t, err, "no error should have existed") + + p.TestConfig.TestContext.AddTestDirectory("testdata/test-creds", credsDir) + + listOpts := printer.PrintOptions{ + Format: tc.format, + } + err = p.ListCredentials(listOpts) + require.NoError(t, err, "no error should have existed") + + gotOutput := p.TestConfig.TestContext.GetOutput() + // TODO: change to require.Equal, verify modified, perhaps w/ regex? + require.Contains(t, gotOutput, tc.wantContains) + }) + } +} diff --git a/pkg/porter/testdata/test-creds/kool-kreds.yaml b/pkg/porter/testdata/test-creds/kool-kreds.yaml new file mode 100644 index 000000000..43775b6ea --- /dev/null +++ b/pkg/porter/testdata/test-creds/kool-kreds.yaml @@ -0,0 +1,5 @@ +name: kool-kreds +credentials: +- name: kool-config + source: + path: /path/to/kool-config diff --git a/vendor/github.com/deislabs/cnab-go/credentials/credentialset.go b/vendor/github.com/deislabs/cnab-go/credentials/credentialset.go new file mode 100644 index 000000000..aa42467a9 --- /dev/null +++ b/vendor/github.com/deislabs/cnab-go/credentials/credentialset.go @@ -0,0 +1,183 @@ +package credentials + +import ( + "fmt" + "io/ioutil" + "os" + "os/exec" + "strings" + + "github.com/deislabs/cnab-go/bundle" + + yaml "gopkg.in/yaml.v2" +) + +// Set is an actual set of resolved credentials. +// This is the output of resolving a credentialset file. +type Set map[string]string + +// Expand expands the set into env vars and paths per the spec in the bundle. +// +// This matches the credentials required by the bundle to the credentials present +// in the credentialset, and then expands them per the definition in the Bundle. +func (s Set) Expand(b *bundle.Bundle, stateless bool) (env, files map[string]string, err error) { + env, files = map[string]string{}, map[string]string{} + for name, val := range b.Credentials { + src, ok := s[name] + if !ok { + if stateless { + continue + } + err = fmt.Errorf("credential %q is missing from the user-supplied credentials", name) + return + } + if val.EnvironmentVariable != "" { + env[val.EnvironmentVariable] = src + } + if val.Path != "" { + files[val.Path] = src + } + } + return +} + +// Merge merges a second Set into the base. +// +// Duplicate credential names are not allow and will result in an +// error, this is the case even if the values are identical. +func (s Set) Merge(s2 Set) error { + for k, v := range s2 { + if _, ok := s[k]; ok { + return fmt.Errorf("ambiguous credential resolution: %q is already present in base credential sets, cannot merge", k) + } + s[k] = v + } + return nil +} + +// CredentialSet represents a collection of credentials +type CredentialSet struct { + // Name is the name of the credentialset. + Name string `json:"name" yaml:"name"` + // Creadentials is a list of credential specs. + Credentials []CredentialStrategy `json:"credentials" yaml:"credentials"` +} + +// Load a CredentialSet from a file at a given path. +// +// It does not load the individual credentials. +func Load(path string) (*CredentialSet, error) { + cset := &CredentialSet{} + data, err := ioutil.ReadFile(path) + if err != nil { + return cset, err + } + return cset, yaml.Unmarshal(data, cset) +} + +// Validate compares the given credentials with the spec. +// +// This will result in an error only if: +// - a parameter in the spec is not present in the given set +// - a parameter in the given set does not match the format required by the spec +// +// It is allowed for spec to specify both an env var and a file. In such case, if +// the givn set provides either, it will be considered valid. +func Validate(given Set, spec map[string]bundle.Location) error { + for name := range spec { + if !isValidCred(given, name) { + return fmt.Errorf("bundle requires credential for %s", name) + } + } + return nil +} + +func isValidCred(haystack Set, needle string) bool { + for name := range haystack { + if name == needle { + return true + } + } + return false +} + +// Resolve looks up the credentials as described in Source, then copies +// the resulting value into the Value field of each credential strategy. +// +// The typical workflow for working with a credential set is: +// +// - Load the set +// - Validate the credentials against a spec +// - Resolve the credentials +// - Expand them into bundle values +func (c *CredentialSet) Resolve() (Set, error) { + l := len(c.Credentials) + res := make(map[string]string, l) + for i := 0; i < l; i++ { + cred := c.Credentials[i] + src := cred.Source + // Precedence is Command, Path, EnvVar, Value + switch { + case src.Command != "": + data, err := execCmd(src.Command) + if err != nil { + return res, err + } + cred.Value = string(data) + case src.Path != "": + data, err := ioutil.ReadFile(os.ExpandEnv(src.Path)) + if err != nil { + return res, fmt.Errorf("credential %q: %s", c.Credentials[i].Name, err) + } + cred.Value = string(data) + case src.EnvVar != "": + var ok bool + cred.Value, ok = os.LookupEnv(src.EnvVar) + if ok { + break + } + fallthrough + default: + cred.Value = src.Value + } + res[c.Credentials[i].Name] = cred.Value + } + return res, nil +} + +func execCmd(cmd string) ([]byte, error) { + parts := strings.Split(cmd, " ") + c := parts[0] + args := parts[1:] + run := exec.Command(c, args...) + + return run.CombinedOutput() +} + +// CredentialStrategy represents a source credential and the destination to which it should be sent. +type CredentialStrategy struct { + // Name is the name of the credential. + // Name is used to match a credential strategy to a bundle's credential. + Name string `json:"name" yaml:"name"` + // Source is the location of the credential. + // During resolution, the source will be loaded, and the result temporarily placed + // into Value. + Source Source `json:"source,omitempty" yaml:"source,omitempty"` + // Value holds the credential value. + // When a credential is loaded, it is loaded into this field. In all + // other cases, it is empty. This field is omitted during serialization. + Value string `json:"-" yaml:"-"` +} + +// Source represents a strategy for loading a credential from local host. +type Source struct { + Path string `json:"path,omitempty" yaml:"path,omitempty"` + Command string `json:"command,omitempty" yaml:"command,omitempty"` + Value string `json:"value,omitempty" yaml:"value,omitempty"` + EnvVar string `json:"env,omitempty" yaml:"env,omitempty"` +} + +// Destination reprents a strategy for injecting a credential into an image. +type Destination struct { + Value string `json:"value,omitempty" yaml:"value,omitempty"` +} From f2e6ce36166601535083064dfd3c772c1228d3a7 Mon Sep 17 00:00:00 2001 From: Vaughn Dice Date: Tue, 4 Jun 2019 13:34:46 -0600 Subject: [PATCH 2/3] ref(credentials.go): split logic of fetching and listing creds --- cmd/porter/credentials.go | 6 +--- pkg/porter/credentials.go | 63 ++++++++++++++++++++------------------- 2 files changed, 34 insertions(+), 35 deletions(-) diff --git a/cmd/porter/credentials.go b/cmd/porter/credentials.go index 429428af6..2f3e60232 100644 --- a/cmd/porter/credentials.go +++ b/cmd/porter/credentials.go @@ -108,11 +108,7 @@ func buildCredentialsListCommand(p *porter.Porter) *cobra.Command { Use: "list", Aliases: []string{"ls"}, Short: "List credentials", - Long: `List credentials available to Porter. - -A listing of credentials currently available to Porter will be provided, along with metadata such as modification time, etc. - -Optional output formats include json and yaml.`, + Long: `List named sets of credentials defined by the user.`, Example: ` porter credentials list [-o table|json|yaml]`, PreRunE: func(cmd *cobra.Command, args []string) error { var err error diff --git a/pkg/porter/credentials.go b/pkg/porter/credentials.go index 7cdc3ff45..41f6c72de 100644 --- a/pkg/porter/credentials.go +++ b/pkg/porter/credentials.go @@ -35,48 +35,51 @@ func (l CredentialsFileList) Less(i, j int) bool { return l[i].Modified.Before(l[j].Modified) } -// ListCredentials lists credentials using the provided printer.PrintOptions -func (p *Porter) ListCredentials(opts printer.PrintOptions) error { - dir, err := p.Config.GetCredentialsDir() +// fetchCredentials fetches all credentials from the designated credentials dir +func (p *Porter) fetchCredentials() (*CredentialsFileList, error) { + credsDir, err := p.Config.GetCredentialsDir() if err != nil { - return errors.Wrap(err, "unable to determine credentials directory") - } - - // TODO: should this go somewhere else? - // i.e., where should logic for init-ing dirs needed by porter go? - // e.g., on 'porter creds list' with a fresh porter install/home - if ok, _ := p.Context.FileSystem.DirExists(dir); !ok { - p.Context.FileSystem.Mkdir(dir, os.ModePerm) + return &CredentialsFileList{}, errors.Wrap(err, "unable to determine credentials directory") } credentialsFiles := CredentialsFileList{} - err = p.Context.FileSystem.Walk(dir, func(path string, info os.FileInfo, err error) error { - if !info.IsDir() { - credSet := &credentials.CredentialSet{} - data, err := p.Context.FileSystem.ReadFile(path) - if err != nil { - return errors.Wrap(err, fmt.Sprintf("unable to load credential set from %s:\n%s", path, err)) - } - if err = yaml.Unmarshal(data, credSet); err != nil { - return errors.Wrap(err, "unable to unmarshal credential set") + if ok, _ := p.Context.FileSystem.DirExists(credsDir); ok { + err = p.Context.FileSystem.Walk(credsDir, func(path string, info os.FileInfo, err error) error { + if !info.IsDir() { + credSet := &credentials.CredentialSet{} + data, err := p.Context.FileSystem.ReadFile(path) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("unable to load credential set from %s:\n%s", path, err)) + } + if err = yaml.Unmarshal(data, credSet); err != nil { + return errors.Wrap(err, "unable to unmarshal credential set") + } + credentialsFiles = append(credentialsFiles, + CredentialsFile{Name: credSet.Name, Modified: info.ModTime()}) } - credentialsFiles = append(credentialsFiles, - CredentialsFile{Name: credSet.Name, Modified: info.ModTime()}) + return nil + }) + if err != nil { + return &CredentialsFileList{}, errors.Wrap(err, "encountered error while listing credentials") } - return nil - }) + sort.Sort(sort.Reverse(credentialsFiles)) + } + return &credentialsFiles, nil +} + +// ListCredentials lists credentials using the provided printer.PrintOptions +func (p *Porter) ListCredentials(opts printer.PrintOptions) error { + credentialsFiles, err := p.fetchCredentials() if err != nil { - return errors.Wrap(err, "encountered error while listing credentials") + return errors.Wrap(err, "unable to fetch credentials") } - sort.Sort(sort.Reverse(credentialsFiles)) switch opts.Format { case printer.FormatJson: - return printer.PrintJson(p.Out, credentialsFiles) + return printer.PrintJson(p.Out, *credentialsFiles) case printer.FormatYaml: - return printer.PrintYaml(p.Out, credentialsFiles) + return printer.PrintYaml(p.Out, *credentialsFiles) case printer.FormatTable: - // TODO: update table printing // have every row use the same "now" starting ... NOW! now := time.Now() tp := dtprinter.DateTimePrinter{ @@ -91,7 +94,7 @@ func (p *Porter) ListCredentials(opts printer.PrintOptions) error { } return []interface{}{cr.Name, tp.Format(cr.Modified)} } - return printer.PrintTable(p.Out, credentialsFiles, printCredRow, + return printer.PrintTable(p.Out, *credentialsFiles, printCredRow, "NAME", "MODIFIED") default: return fmt.Errorf("invalid format: %s", opts.Format) From 413a841544c56bf007d38f65b820dfe2bc0ac94a Mon Sep 17 00:00:00 2001 From: Vaughn Dice Date: Tue, 4 Jun 2019 17:02:48 -0600 Subject: [PATCH 3/3] feat(credentials.go): add good/bad creds test case; update logic --- Gopkg.lock | 11 +-- pkg/porter/credentials.go | 15 ++-- pkg/porter/credentials_test.go | 70 +++++++++++++++---- .../good-and-bad-test-creds/bad-creds.yaml | 1 + .../good-and-bad-test-creds/good-creds.yaml | 5 ++ 5 files changed, 74 insertions(+), 28 deletions(-) create mode 100644 pkg/porter/testdata/good-and-bad-test-creds/bad-creds.yaml create mode 100644 pkg/porter/testdata/good-and-bad-test-creds/good-creds.yaml diff --git a/Gopkg.lock b/Gopkg.lock index da3665ec7..9bd4a2492 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -213,24 +213,15 @@ version = "v1.1.1" [[projects]] -<<<<<<< HEAD branch = "custom-extensions" - digest = "1:dfe6ce9e92d2315c432afc126c2527b99b1b2212f4af7df1629c818d0d8dafbe" -======= - branch = "master" - digest = "1:c40d52bd9a6ce87eeb00a2d93e375c7bc92acb96426627da6555b21a264c1bdf" ->>>>>>> feat(credentials): add credentials list command + digest = "1:9bc1a8079ba8252a4bd75a1e25e9bacb6267d6272d91985f0301d77ad8b1375c" name = "github.com/deislabs/cnab-go" packages = [ "bundle", "credentials", ] pruneopts = "NUT" -<<<<<<< HEAD revision = "1aa435456c4b74dba1d2276cb45a8482e33477a9" -======= - revision = "ef2eb2a629bbca48ca1ab277b3b3bfb60eb53316" ->>>>>>> feat(credentials): add credentials list command [[projects]] digest = "1:a222203ae2f42f5a3d06fde5f9746e51291567a3df8a5ebedce334cd93b7b97a" diff --git a/pkg/porter/credentials.go b/pkg/porter/credentials.go index 41f6c72de..d3e62715b 100644 --- a/pkg/porter/credentials.go +++ b/pkg/porter/credentials.go @@ -44,24 +44,27 @@ func (p *Porter) fetchCredentials() (*CredentialsFileList, error) { credentialsFiles := CredentialsFileList{} if ok, _ := p.Context.FileSystem.DirExists(credsDir); ok { - err = p.Context.FileSystem.Walk(credsDir, func(path string, info os.FileInfo, err error) error { + p.Context.FileSystem.Walk(credsDir, func(path string, info os.FileInfo, err error) error { if !info.IsDir() { credSet := &credentials.CredentialSet{} data, err := p.Context.FileSystem.ReadFile(path) if err != nil { - return errors.Wrap(err, fmt.Sprintf("unable to load credential set from %s:\n%s", path, err)) + if p.Debug { + fmt.Fprintf(p.Err, "unable to load credential set from %s: %s\n", path, err) + } + return nil } if err = yaml.Unmarshal(data, credSet); err != nil { - return errors.Wrap(err, "unable to unmarshal credential set") + if p.Debug { + fmt.Fprintf(p.Err, "unable to unmarshal credential set from file %s: %s\n", info.Name(), err) + } + return nil } credentialsFiles = append(credentialsFiles, CredentialsFile{Name: credSet.Name, Modified: info.ModTime()}) } return nil }) - if err != nil { - return &CredentialsFileList{}, errors.Wrap(err, "encountered error while listing credentials") - } sort.Sort(sort.Reverse(credentialsFiles)) } return &credentialsFiles, nil diff --git a/pkg/porter/credentials_test.go b/pkg/porter/credentials_test.go index 7248a6280..e8d9ca350 100644 --- a/pkg/porter/credentials_test.go +++ b/pkg/porter/credentials_test.go @@ -87,7 +87,7 @@ func TestGenerateBadNameProvided(t *testing.T) { type CredentialsListTest struct { name string format printer.Format - wantContains string + wantContains []string errorMsg string } @@ -96,25 +96,25 @@ func TestCredentialsList_None(t *testing.T) { { name: "invalid format", format: "wingdings", - wantContains: "", + wantContains: []string{}, errorMsg: "invalid format: wingdings", }, { name: "json", format: printer.FormatJson, - wantContains: "[]\n", + wantContains: []string{"[]\n"}, errorMsg: "", }, { name: "yaml", format: printer.FormatYaml, - wantContains: "[]\n\n", + wantContains: []string{"[]\n\n"}, errorMsg: "", }, { name: "table", format: printer.FormatTable, - wantContains: "NAME MODIFIED\n", + wantContains: []string{"NAME MODIFIED\n"}, errorMsg: "", }, } @@ -135,7 +135,9 @@ func TestCredentialsList_None(t *testing.T) { } gotOutput := p.TestConfig.TestContext.GetOutput() - require.Equal(t, tc.wantContains, gotOutput) + for _, contains := range tc.wantContains { + require.Contains(t, gotOutput, contains) + } }) } } @@ -145,22 +147,28 @@ func TestCredentialsList(t *testing.T) { { name: "json", format: printer.FormatJson, - wantContains: `"Name": "kool-kreds"`, + wantContains: []string{`"Name": "kool-kreds"`}, errorMsg: "", }, { name: "yaml", format: printer.FormatYaml, - wantContains: `- name: kool-kreds`, + wantContains: []string{`- name: kool-kreds`}, errorMsg: "", }, { name: "table", format: printer.FormatTable, - wantContains: `NAME MODIFIED -kool-kreds now`, + wantContains: []string{`NAME MODIFIED +kool-kreds now`}, errorMsg: "", }, + { + name: "error", + format: printer.FormatTable, + wantContains: []string{}, + errorMsg: "", + }, } for _, tc := range testcases { @@ -180,8 +188,46 @@ kool-kreds now`, require.NoError(t, err, "no error should have existed") gotOutput := p.TestConfig.TestContext.GetOutput() - // TODO: change to require.Equal, verify modified, perhaps w/ regex? - require.Contains(t, gotOutput, tc.wantContains) + for _, contains := range tc.wantContains { + require.Contains(t, gotOutput, contains) + } + }) + } +} + +func TestCredentialsList_BadCred(t *testing.T) { + testcases := []CredentialsListTest{ + { + name: "unmarshal error", + format: printer.FormatTable, + wantContains: []string{ + "unable to unmarshal credential set from file bad-creds.yaml: yaml: unmarshal errors", + `NAME MODIFIED +good-creds now`}, + errorMsg: "", + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + p := NewTestPorter(t) + p.CNAB = &TestCNABProvider{} + + credsDir, err := p.TestConfig.GetCredentialsDir() + require.NoError(t, err, "no error should have existed") + + p.TestConfig.TestContext.AddTestDirectory("testdata/good-and-bad-test-creds", credsDir) + + listOpts := printer.PrintOptions{ + Format: tc.format, + } + err = p.ListCredentials(listOpts) + require.NoError(t, err, "no error should have existed") + + gotOutput := p.TestConfig.TestContext.GetOutput() + for _, contains := range tc.wantContains { + require.Contains(t, gotOutput, contains) + } }) } } diff --git a/pkg/porter/testdata/good-and-bad-test-creds/bad-creds.yaml b/pkg/porter/testdata/good-and-bad-test-creds/bad-creds.yaml new file mode 100644 index 000000000..30ec8e59a --- /dev/null +++ b/pkg/porter/testdata/good-and-bad-test-creds/bad-creds.yaml @@ -0,0 +1 @@ +these are some bad creds \ No newline at end of file diff --git a/pkg/porter/testdata/good-and-bad-test-creds/good-creds.yaml b/pkg/porter/testdata/good-and-bad-test-creds/good-creds.yaml new file mode 100644 index 000000000..7eedcb0dc --- /dev/null +++ b/pkg/porter/testdata/good-and-bad-test-creds/good-creds.yaml @@ -0,0 +1,5 @@ +name: good-creds +credentials: +- name: good-cred + source: + env: GOOD_CRED