Skip to content
This repository was archived by the owner on Aug 30, 2024. It is now read-only.

Add more secret creation input paths #83

Merged
merged 1 commit into from
Jul 31, 2020
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 1 addition & 62 deletions ci/integration/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ package integration
import (
"context"
"encoding/json"
"fmt"
"math/rand"
"regexp"
"testing"
"time"

Expand Down Expand Up @@ -89,65 +87,6 @@ func TestCoderCLI(t *testing.T) {
)
}

func TestSecrets(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)
defer cancel()

c, err := tcli.NewContainerRunner(ctx, &tcli.ContainerConfig{
Image: "codercom/enterprise-dev",
Name: "secrets-cli-tests",
BindMounts: map[string]string{
binpath: "/bin/coder",
},
})
assert.Success(t, "new run container", err)
defer c.Close()

headlessLogin(ctx, t, c)

c.Run(ctx, "coder secrets ls").Assert(t,
tcli.Success(),
)

name, value := randString(8), randString(8)

c.Run(ctx, "coder secrets create").Assert(t,
tcli.Error(),
tcli.StdoutEmpty(),
tcli.StderrMatches("required flag"),
)

c.Run(ctx, fmt.Sprintf("coder secrets create --name %s --value %s", name, value)).Assert(t,
tcli.Success(),
tcli.StderrEmpty(),
)

c.Run(ctx, "coder secrets ls").Assert(t,
tcli.Success(),
tcli.StderrEmpty(),
tcli.StdoutMatches("Value"),
tcli.StdoutMatches(regexp.QuoteMeta(name)),
)

c.Run(ctx, "coder secrets view "+name).Assert(t,
tcli.Success(),
tcli.StderrEmpty(),
tcli.StdoutMatches(regexp.QuoteMeta(value)),
)

c.Run(ctx, "coder secrets rm").Assert(t,
tcli.Error(),
)
c.Run(ctx, "coder secrets rm "+name).Assert(t,
tcli.Success(),
)
c.Run(ctx, "coder secrets view "+name).Assert(t,
tcli.Error(),
tcli.StdoutEmpty(),
)
}

func stdoutUnmarshalsJSON(target interface{}) tcli.Assertion {
return func(t *testing.T, r *tcli.CommandResult) {
slog.Helper()
Expand All @@ -159,7 +98,7 @@ func stdoutUnmarshalsJSON(target interface{}) tcli.Assertion {
var seededRand = rand.New(rand.NewSource(time.Now().UnixNano()))

func randString(length int) string {
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
b := make([]byte, length)
for i := range b {
b[i] = charset[seededRand.Intn(len(charset))]
Expand Down
99 changes: 99 additions & 0 deletions ci/integration/secrets_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package integration

import (
"context"
"fmt"
"regexp"
"testing"
"time"

"cdr.dev/coder-cli/ci/tcli"
"cdr.dev/slog/sloggers/slogtest/assert"
)

func TestSecrets(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)
defer cancel()

c, err := tcli.NewContainerRunner(ctx, &tcli.ContainerConfig{
Image: "codercom/enterprise-dev",
Name: "secrets-cli-tests",
BindMounts: map[string]string{
binpath: "/bin/coder",
},
})
assert.Success(t, "new run container", err)
defer c.Close()

headlessLogin(ctx, t, c)

c.Run(ctx, "coder secrets ls").Assert(t,
tcli.Success(),
)

name, value := randString(8), randString(8)

c.Run(ctx, "coder secrets create").Assert(t,
tcli.Error(),
tcli.StdoutEmpty(),
)

// this tests the "Value:" prompt fallback
c.Run(ctx, fmt.Sprintf("echo %s | coder secrets create %s --from-prompt", value, name)).Assert(t,
tcli.Success(),
tcli.StderrEmpty(),
)

c.Run(ctx, "coder secrets ls").Assert(t,
tcli.Success(),
tcli.StderrEmpty(),
tcli.StdoutMatches("Value"),
tcli.StdoutMatches(regexp.QuoteMeta(name)),
)

c.Run(ctx, "coder secrets view "+name).Assert(t,
tcli.Success(),
tcli.StderrEmpty(),
tcli.StdoutMatches(regexp.QuoteMeta(value)),
)

c.Run(ctx, "coder secrets rm").Assert(t,
tcli.Error(),
)
c.Run(ctx, "coder secrets rm "+name).Assert(t,
tcli.Success(),
)
c.Run(ctx, "coder secrets view "+name).Assert(t,
tcli.Error(),
tcli.StdoutEmpty(),
)

name, value = randString(8), randString(8)

c.Run(ctx, fmt.Sprintf("coder secrets create %s --from-literal %s", name, value)).Assert(t,
tcli.Success(),
tcli.StderrEmpty(),
)

c.Run(ctx, "coder secrets view "+name).Assert(t,
tcli.Success(),
tcli.StdoutMatches(regexp.QuoteMeta(value)),
)

name, value = randString(8), randString(8)
c.Run(ctx, fmt.Sprintf("echo %s > ~/secret.json", value)).Assert(t,
tcli.Success(),
)
c.Run(ctx, fmt.Sprintf("coder secrets create %s --from-literal %s --from-file ~/secret.json", name, value)).Assert(t,
tcli.Error(),
)
c.Run(ctx, fmt.Sprintf("coder secrets create %s --from-file ~/secret.json", name)).Assert(t,
tcli.Success(),
)
//
c.Run(ctx, "coder secrets view "+name).Assert(t,
tcli.Success(),
tcli.StdoutMatches(regexp.QuoteMeta(value)),
)
}
76 changes: 56 additions & 20 deletions cmd/coder/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ package main

import (
"fmt"
"io/ioutil"
"os"

"cdr.dev/coder-cli/internal/entclient"
"cdr.dev/coder-cli/internal/x/xtabwriter"
"cdr.dev/coder-cli/internal/x/xvalidate"
"github.com/manifoldco/promptui"
"github.com/spf13/pflag"
"golang.org/x/xerrors"

Expand Down Expand Up @@ -111,45 +113,79 @@ func (cmd viewSecretsCmd) Run(fl *pflag.FlagSet) {
}

type createSecretCmd struct {
name, value, description string
}

func (cmd *createSecretCmd) Validate() (e []error) {
if cmd.name == "" {
e = append(e, xerrors.New("--name is a required flag"))
}
if cmd.value == "" {
e = append(e, xerrors.New("--value is a required flag"))
}
return e
description string
fromFile string
fromLiteral string
fromPrompt bool
}

func (cmd *createSecretCmd) Spec() cli.CommandSpec {
return cli.CommandSpec{
Name: "create",
Usage: `--name MYSQL_KEY --value 123456 --description "MySQL credential for database access"`,
Desc: "insert a new secret",
Usage: `[secret_name] [...flags]`,
Desc: "create a new secret",
}
}

func (cmd *createSecretCmd) Validate(fl *pflag.FlagSet) (e []error) {
if cmd.fromPrompt && (cmd.fromLiteral != "" || cmd.fromFile != "") {
e = append(e, xerrors.Errorf("--from-prompt cannot be set along with --from-file or --from-literal"))
}
if cmd.fromLiteral != "" && cmd.fromFile != "" {
e = append(e, xerrors.Errorf("--from-literal and --from-file cannot both be set"))
}
if !cmd.fromPrompt && cmd.fromFile == "" && cmd.fromLiteral == "" {
e = append(e, xerrors.Errorf("one of [--from-literal, --from-file, --from-prompt] is required"))
}
return e
}

func (cmd *createSecretCmd) Run(fl *pflag.FlagSet) {
var (
client = requireAuth()
name = fl.Arg(0)
value string
err error
)
xvalidate.Validate(cmd)
if name == "" {
exitUsage(fl)
}
xvalidate.Validate(fl, cmd)

if cmd.fromLiteral != "" {
value = cmd.fromLiteral
} else if cmd.fromFile != "" {
contents, err := ioutil.ReadFile(cmd.fromFile)
requireSuccess(err, "failed to read file: %v", err)
value = string(contents)
} else {
prompt := promptui.Prompt{
Label: "value",
Mask: '*',
Validate: func(s string) error {
if len(s) < 1 {
return xerrors.Errorf("a length > 0 is required")
}
return nil
},
}
value, err = prompt.Run()
requireSuccess(err, "failed to prompt for value: %v", err)
}

err := client.InsertSecret(entclient.InsertSecretReq{
Name: cmd.name,
Value: cmd.value,
err = client.InsertSecret(entclient.InsertSecretReq{
Name: name,
Value: value,
Description: cmd.description,
})
requireSuccess(err, "failed to insert secret: %v", err)
}

func (cmd *createSecretCmd) RegisterFlags(fl *pflag.FlagSet) {
fl.StringVar(&cmd.name, "name", "", "the name of the secret")
fl.StringVar(&cmd.value, "value", "", "the value of the secret")
fl.StringVar(&cmd.description, "description", "", "a description of the secret")
fl.StringVar(&cmd.fromFile, "from-file", "", "specify a file from which to read the value of the secret")
fl.StringVar(&cmd.fromLiteral, "from-literal", "", "specify the value of the secret")
fl.BoolVar(&cmd.fromPrompt, "from-prompt", false, "specify the value of the secret through a prompt")
fl.StringVar(&cmd.description, "description", "", "specify a description of the secret")
}

type deleteSecretsCmd struct{}
Expand Down
4 changes: 2 additions & 2 deletions cmd/coder/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ type listCmd struct {
}

func (cmd *listCmd) Run(fl *pflag.FlagSet) {
xvalidate.Validate(cmd)
xvalidate.Validate(fl, cmd)
entClient := requireAuth()

users, err := entClient.Users()
Expand Down Expand Up @@ -77,7 +77,7 @@ func (cmd *listCmd) Spec() cli.CommandSpec {
}
}

func (cmd *listCmd) Validate() (e []error) {
func (cmd *listCmd) Validate(fl *pflag.FlagSet) (e []error) {
if !(cmd.outputFmt == "json" || cmd.outputFmt == "human") {
e = append(e, fmt.Errorf(`--output must be "json" or "human"`))
}
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
github.com/gorilla/websocket v1.4.1
github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f
github.com/klauspost/compress v1.10.8 // indirect
github.com/manifoldco/promptui v0.7.0
github.com/mattn/go-colorable v0.1.6 // indirect
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4
github.com/rjeczalik/notify v0.9.2
Expand Down
13 changes: 13 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ github.com/alecthomas/kong-hcl v0.1.8-0.20190615233001-b21fea9723c8/go.mod h1:MR
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY=
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
Expand Down Expand Up @@ -111,6 +117,8 @@ github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJS
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU=
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU=
github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f h1:dKccXx7xA56UNqOcFIbuqFjAWPVtP688j5QMgmo6OHU=
github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f/go.mod h1:4rEELDSfUAlBSyUjPG0JnaNGjf13JySHFeRdD/3dLP0=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
Expand All @@ -125,6 +133,10 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a h1:weJVJJRzAJBFRlAiJQROKQs8oC9vOxvm4rZmBBk0ONw=
github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/manifoldco/promptui v0.7.0 h1:3l11YT8tm9MnwGFQ4kETwkzpAwY2Jt9lCrumCUW4+z4=
github.com/manifoldco/promptui v0.7.0/go.mod h1:n4zTdgP0vr0S3w7/O/g98U+e0gwLScEXGwov2nIKuGQ=
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
Expand Down Expand Up @@ -225,6 +237,7 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03i
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
Expand Down
11 changes: 8 additions & 3 deletions internal/x/xvalidate/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package xvalidate

import (
"bytes"
"fmt"

"github.com/spf13/pflag"

"go.coder.com/flog"
)
Expand Down Expand Up @@ -81,16 +84,18 @@ func combineErrors(errs ...error) error {

// Validator is a command capable of validating its flags
type Validator interface {
Validate() []error
Validate(fl *pflag.FlagSet) []error
}

// Validate performs validation and exits with a nonzero status code if validation fails.
// The proper errors are printed to stderr.
func Validate(v Validator) {
errs := v.Validate()
func Validate(fl *pflag.FlagSet, v Validator) {
errs := v.Validate(fl)

err := combineErrors(errs...)
if err != nil {
fl.Usage()
fmt.Println("")
flog.Fatal("failed to validate this command\n%v", err)
}
}