Skip to content
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

feat(credentials): add credentials list command #375

Merged
merged 3 commits into from Jun 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 6 additions & 2 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 14 additions & 11 deletions cmd/porter/credentials.go
Expand Up @@ -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"},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙇‍♀

Annotations: map[string]string{"group": "resource"},
Short: "Credentials commands",
}
Expand Down Expand Up @@ -102,25 +102,28 @@ 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{}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯


cmd := &cobra.Command{
Use: "list",
Short: "List credentials",
Hidden: true,
Use: "list",
Aliases: []string{"ls"},
Short: "List credentials",
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
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
}

Expand Down
@@ -1,13 +1,13 @@
package credentials
package credentialsgenerator

import (
"errors"
"fmt"
"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"
)
Expand Down
@@ -1,4 +1,4 @@
package credentials
package credentialsgenerator

import (
"fmt"
Expand Down
100 changes: 95 additions & 5 deletions pkg/porter/credentials.go
Expand Up @@ -2,16 +2,106 @@ 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)
}

// fetchCredentials fetches all credentials from the designated credentials dir
func (p *Porter) fetchCredentials() (*CredentialsFileList, error) {
credsDir, err := p.Config.GetCredentialsDir()
if err != nil {
return &CredentialsFileList{}, errors.Wrap(err, "unable to determine credentials directory")
}

credentialsFiles := CredentialsFileList{}
if ok, _ := p.Context.FileSystem.DirExists(credsDir); ok {
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 {
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 {
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
})
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, "unable to fetch credentials")
}

switch opts.Format {
case printer.FormatJson:
return printer.PrintJson(p.Out, *credentialsFiles)
case printer.FormatYaml:
return printer.PrintYaml(p.Out, *credentialsFiles)
case printer.FormatTable:
// 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 {
Expand Down Expand Up @@ -56,15 +146,15 @@ 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,
}
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")
}
Expand Down
152 changes: 151 additions & 1 deletion pkg/porter/credentials_test.go
Expand Up @@ -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"
)

Expand Down Expand Up @@ -81,3 +83,151 @@ 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: []string{},
errorMsg: "invalid format: wingdings",
},
{
name: "json",
format: printer.FormatJson,
wantContains: []string{"[]\n"},
errorMsg: "",
},
{
name: "yaml",
format: printer.FormatYaml,
wantContains: []string{"[]\n\n"},
errorMsg: "",
},
{
name: "table",
format: printer.FormatTable,
wantContains: []string{"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()
for _, contains := range tc.wantContains {
require.Contains(t, gotOutput, contains)
}
})
}
}

func TestCredentialsList(t *testing.T) {
testcases := []CredentialsListTest{
{
name: "json",
format: printer.FormatJson,
wantContains: []string{`"Name": "kool-kreds"`},
errorMsg: "",
},
{
name: "yaml",
format: printer.FormatYaml,
wantContains: []string{`- name: kool-kreds`},
errorMsg: "",
},
{
name: "table",
format: printer.FormatTable,
wantContains: []string{`NAME MODIFIED
kool-kreds now`},
errorMsg: "",
},
{
name: "error",
format: printer.FormatTable,
wantContains: []string{},
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()
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)
}
})
}
}
1 change: 1 addition & 0 deletions pkg/porter/testdata/good-and-bad-test-creds/bad-creds.yaml
@@ -0,0 +1 @@
these are some bad creds
5 changes: 5 additions & 0 deletions 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
5 changes: 5 additions & 0 deletions 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