Skip to content

Commit

Permalink
Add porter credentials create command (getporter#1838)
Browse files Browse the repository at this point in the history
* Add porter credentials create command

Signed-off-by: joshuabezaleel <joshua.bezaleel@gmail.com>

* Set default value of output format to yaml

Signed-off-by: joshuabezaleel <joshua.bezaleel@gmail.com>

* Remove required validation of FILE to enable writing the output to standard output

Signed-off-by: joshuabezaleel <joshua.bezaleel@gmail.com>

* Add examples for YAML and fix JSON content of CredentialSet

Signed-off-by: joshuabezaleel <joshua.bezaleel@gmail.com>

* Use error formating from errors package

Signed-off-by: joshuabezaleel <joshua.bezaleel@gmail.com>

* Write log to stderr instead

Signed-off-by: joshuabezaleel <joshua.bezaleel@gmail.com>

* Remove using yaml for the --output flag default value

Signed-off-by: joshuabezaleel <joshua.bezaleel@gmail.com>

* Log error when file format can't be deduced either from file extension of positional argument or outputtype flag

Signed-off-by: joshuabezaleel <joshua.bezaleel@gmail.com>

* Use switch statement for checking output type and enable print to stdout whene no filename is supplied

Signed-off-by: joshuabezaleel <joshua.bezaleel@gmail.com>

* Fix and add testcases

Signed-off-by: joshuabezaleel <joshua.bezaleel@gmail.com>

* Add checking for file extension

Signed-off-by: joshuabezaleel <joshua.bezaleel@gmail.com>

* Add comments for the yaml example

Signed-off-by: joshuabezaleel <joshua.bezaleel@gmail.com>

* Use secret key for credential set of secret type

Signed-off-by: joshuabezaleel <joshua.bezaleel@gmail.com>

* Remove schema from filename of the credential set with JSON format type

Signed-off-by: joshuabezaleel <joshua.bezaleel@gmail.com>

* Retrieve appropraite file for JSON format type of credential set

Signed-off-by: joshuabezaleel <joshua.bezaleel@gmail.com>

* Remove schema from test filename

Signed-off-by: joshuabezaleel <joshua.bezaleel@gmail.com>
  • Loading branch information
joshuabezaleel authored Feb 28, 2022
1 parent ecedc49 commit 65f914c
Show file tree
Hide file tree
Showing 9 changed files with 386 additions and 0 deletions.
26 changes: 26 additions & 0 deletions cmd/porter/credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ func buildCredentialsCommands(p *porter.Porter) *cobra.Command {
cmd.AddCommand(buildCredentialsListCommand(p))
cmd.AddCommand(buildCredentialsDeleteCommand(p))
cmd.AddCommand(buildCredentialsShowCommand(p))
cmd.AddCommand(buildCredentialsCreateCommand(p))

return cmd
}
Expand Down Expand Up @@ -214,3 +215,28 @@ func buildCredentialsShowCommand(p *porter.Porter) *cobra.Command {

return cmd
}

func buildCredentialsCreateCommand(p *porter.Porter) *cobra.Command {
opts := porter.CredentialCreateOptions{}

cmd := &cobra.Command{
Use: "create",
Short: "Create a Credential",
Long: "Create a new blank resource for the definition of a Credential Set.",
Example: `
porter credentials create FILE [--output yaml|json]
porter credentials create credential-set.json
porter credentials create credential-set --output yaml`,
PreRunE: func(cmd *cobra.Command, args []string) error {
return opts.Validate(args)
},
RunE: func(cmd *cobra.Command, argrs []string) error {
return p.CreateCredential(opts)
},
}

f := cmd.Flags()
f.StringVar(&opts.OutputType, "output", "", "Credential set resource file format")

return cmd
}
1 change: 1 addition & 0 deletions docs/content/cli/credentials.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Most commands require a Docker daemon, either local or remote.
Try our QuickStart https://porter.sh/quickstart to learn how to use Porter.

* [porter credentials apply](/cli/porter_credentials_apply/) - Apply changes to a credential set
* [porter credentials create](/cli/porter_credentials_create/) - Create a Credential
* [porter credentials delete](/cli/porter_credentials_delete/) - Delete a Credential
* [porter credentials edit](/cli/porter_credentials_edit/) - Edit Credential
* [porter credentials generate](/cli/porter_credentials_generate/) - Generate Credential Set
Expand Down
45 changes: 45 additions & 0 deletions docs/content/cli/credentials_create.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
title: "porter credentials create"
slug: porter_credentials_create
url: /cli/porter_credentials_create/
---
## porter credentials create

Create a Credential

### Synopsis

Create a new blank resource for the definition of a Credential Set.

```
porter credentials create [flags]
```

### Examples

```
porter credentials create FILE [--output yaml|json]
porter credentials create credential-set.json
porter credentials create credential-set --output yaml
```

### Options

```
-h, --help help for create
--output string Credential set resource file format
```

### Options inherited from parent commands

```
--debug Enable debug logging
--debug-plugins Enable plugin debug logging
--experimental strings Comma separated list of experimental features to enable. See https://porter.sh/configuration/#experimental-feature-flags for available feature flags.
```

### SEE ALSO

* [porter credentials](/cli/porter_credentials/) - Credentials commands

73 changes: 73 additions & 0 deletions pkg/porter/credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package porter
import (
"context"
"fmt"
"path/filepath"
"strings"
"time"

"get.porter.sh/porter/pkg/credentials"
Expand Down Expand Up @@ -373,3 +375,74 @@ func (p *Porter) getNamespaceFromFile(o ApplyOptions) (string, error) {

return o.Namespace, nil
}

// CredentialCreateOptions represent options for Porter's credential create command
type CredentialCreateOptions struct {
FileName string
OutputType string
}

func (o *CredentialCreateOptions) Validate(args []string) error {
if len(args) > 1 {
return errors.Errorf("only one positional argument may be specified, fileName, but multiple were received: %s", args)
}

if len(args) > 0 {
o.FileName = args[0]
}

if o.OutputType == "" && o.FileName != "" && strings.Trim(filepath.Ext(o.FileName), ".") == "" {
return errors.New("could not detect the file format from the file extension (.txt). Specify the format with --output.")
}

return nil
}

func (p *Porter) CreateCredential(opts CredentialCreateOptions) error {
if opts.OutputType == "" {
opts.OutputType = strings.Trim(filepath.Ext(opts.FileName), ".")
}

if opts.FileName == "" {
if opts.OutputType == "" {
opts.OutputType = "yaml"
}

switch opts.OutputType {
case "json":
credentialSet, err := p.Templates.GetCredentialSetJSON()
if err != nil {
return err
}
fmt.Fprintln(p.Out, string(credentialSet))

return nil
case "yaml", "yml":
credentialSet, err := p.Templates.GetCredentialSetYAML()
if err != nil {
return err
}
fmt.Fprintln(p.Out, string(credentialSet))

return nil
default:
return newUnsupportedFormatError(opts.OutputType)
}

}

fmt.Fprintln(p.Err, "creating porter credential set in the current directory")

switch opts.OutputType {
case "json":
return p.CopyTemplate(p.Templates.GetCredentialSetJSON, opts.FileName)
case "yaml", "yml":
return p.CopyTemplate(p.Templates.GetCredentialSetYAML, opts.FileName)
default:
return newUnsupportedFormatError(opts.OutputType)
}
}

func newUnsupportedFormatError(format string) error {
return errors.Errorf("unsupported format %s. Supported formats are: yaml and json.", format)
}
132 changes: 132 additions & 0 deletions pkg/porter/credentials_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package porter

import (
"context"
"fmt"
"os"
"path/filepath"
"testing"
Expand Down Expand Up @@ -378,3 +379,134 @@ func TestApplyOptions_Validate(t *testing.T) {
})

}

func TestCredentialsCreateOptions_Validate(t *testing.T) {
testcases := []struct {
name string
args []string
outputType string
wantErr string
}{
{
name: "no fileName defined",
args: []string{},
outputType: "",
wantErr: "",
},
{
name: "two positional arguments",
args: []string{"credential-set1", "credential-set2"},
outputType: "",
wantErr: "only one positional argument may be specified",
},
{
name: "no file format defined from file extension or output flag",
args: []string{"credential-set"},
outputType: "",
wantErr: "could not detect the file format from the file extension (.txt). Specify the format with --output.",
},
{
name: "different file format",
args: []string{"credential-set.json"},
outputType: "yaml",
wantErr: "",
},
{
name: "format from output flag",
args: []string{"creds"},
outputType: "json",
wantErr: "",
},
{
name: "format from file extension",
args: []string{"credential-set.yml"},
outputType: "",
wantErr: "",
},
}

for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
opts := CredentialCreateOptions{OutputType: tc.outputType}
err := opts.Validate(tc.args)
if tc.wantErr == "" {
require.NoError(t, err, "no error should have existed")
return
}
assert.Contains(t, err.Error(), tc.wantErr)
})
}
}

func TestCredentialsCreate(t *testing.T) {
testcases := []struct {
name string
fileName string
outputType string
wantErr string
}{
{
name: "valid input: no input defined, will output yaml format to stdout",
fileName: "",
outputType: "",
wantErr: "",
},
{
name: "valid input: output to stdout with format json",
fileName: "",
outputType: "json",
wantErr: "",
},
{
name: "valid input: file format from fileName",
fileName: "fileName.json",
outputType: "",
wantErr: "",
},
{
name: "valid input: file format from outputType",
fileName: "fileName",
outputType: "json",
wantErr: "",
},
{
name: "valid input: different file format from fileName and outputType",
fileName: "fileName.yaml",
outputType: "json",
wantErr: "",
},
{
name: "valid input: same file format in fileName and outputType",
fileName: "fileName.json",
outputType: "json",
wantErr: "",
},
{
name: "invalid input: invalid file format from fileName",
fileName: "fileName.txt",
outputType: "",
wantErr: fmt.Sprintf("unsupported format %s. Supported formats are: yaml and json.", "txt"),
},
{
name: "invalid input: invalid file format from outputType",
fileName: "fileName",
outputType: "txt",
wantErr: fmt.Sprintf("unsupported format %s. Supported formats are: yaml and json.", "txt"),
},
}

for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
p := NewTestPorter(t)
defer p.Teardown()

opts := CredentialCreateOptions{FileName: tc.fileName, OutputType: tc.outputType}
err := p.CreateCredential(opts)
if tc.wantErr == "" {
require.NoError(t, err, "no error should have existed")
return
}
assert.Contains(t, err.Error(), tc.wantErr)
})
}
}
10 changes: 10 additions & 0 deletions pkg/templates/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,13 @@ func (t *Templates) GetDockerfile() ([]byte, error) {
tmpl := fmt.Sprintf("templates/build/%s.Dockerfile", t.GetBuildDriver())
return t.fs.ReadFile(tmpl)
}

// GetCredentialSetJSON returns a credential-set.schema.json template file to define new credential set.
func (t *Templates) GetCredentialSetJSON() ([]byte, error) {
return t.fs.ReadFile("templates/credentials/create/credential-set.json")
}

// GetCredentialSetYAML returns a credential-set.yaml template file to define new credential set.
func (t *Templates) GetCredentialSetYAML() ([]byte, error) {
return t.fs.ReadFile("templates/credentials/create/credential-set.yaml")
}
41 changes: 41 additions & 0 deletions pkg/templates/templates/credentials/create/credential-set.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"schemaType": "CredentialSet",
"schemaVersion": "1.0.0",
"name": "NAME",
"namespace": "NAMESPACE",
"labels": {
"LABEL": "LABEL_VALUE"
},
"credentials": [
{
"name": "credential-path",
"source": {
"path": "/path/to/credential-path-file.txt"
}
},
{
"name": "credential-command",
"source": {
"command": "echo 'credential command'"
}
},
{
"name": "credential-env",
"source": {
"ENV": "CREDENTIAL_ENV"
}
},
{
"name": "credential-value",
"source": {
"value": "credentialvalue"
}
},
{
"name": "credential-secret",
"source": {
"secret": "credential-secret"
}
}
]
}
Loading

0 comments on commit 65f914c

Please sign in to comment.