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

Commit 6316e07

Browse files
authored
Merge pull request #83 from cdr/secret-prompt
Add more secret creation input paths
2 parents 3a54328 + b5487cb commit 6316e07

File tree

7 files changed

+180
-87
lines changed

7 files changed

+180
-87
lines changed

Diff for: ci/integration/integration_test.go

+1-62
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@ package integration
33
import (
44
"context"
55
"encoding/json"
6-
"fmt"
76
"math/rand"
8-
"regexp"
97
"testing"
108
"time"
119

@@ -89,65 +87,6 @@ func TestCoderCLI(t *testing.T) {
8987
)
9088
}
9189

92-
func TestSecrets(t *testing.T) {
93-
t.Parallel()
94-
ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)
95-
defer cancel()
96-
97-
c, err := tcli.NewContainerRunner(ctx, &tcli.ContainerConfig{
98-
Image: "codercom/enterprise-dev",
99-
Name: "secrets-cli-tests",
100-
BindMounts: map[string]string{
101-
binpath: "/bin/coder",
102-
},
103-
})
104-
assert.Success(t, "new run container", err)
105-
defer c.Close()
106-
107-
headlessLogin(ctx, t, c)
108-
109-
c.Run(ctx, "coder secrets ls").Assert(t,
110-
tcli.Success(),
111-
)
112-
113-
name, value := randString(8), randString(8)
114-
115-
c.Run(ctx, "coder secrets create").Assert(t,
116-
tcli.Error(),
117-
tcli.StdoutEmpty(),
118-
tcli.StderrMatches("required flag"),
119-
)
120-
121-
c.Run(ctx, fmt.Sprintf("coder secrets create --name %s --value %s", name, value)).Assert(t,
122-
tcli.Success(),
123-
tcli.StderrEmpty(),
124-
)
125-
126-
c.Run(ctx, "coder secrets ls").Assert(t,
127-
tcli.Success(),
128-
tcli.StderrEmpty(),
129-
tcli.StdoutMatches("Value"),
130-
tcli.StdoutMatches(regexp.QuoteMeta(name)),
131-
)
132-
133-
c.Run(ctx, "coder secrets view "+name).Assert(t,
134-
tcli.Success(),
135-
tcli.StderrEmpty(),
136-
tcli.StdoutMatches(regexp.QuoteMeta(value)),
137-
)
138-
139-
c.Run(ctx, "coder secrets rm").Assert(t,
140-
tcli.Error(),
141-
)
142-
c.Run(ctx, "coder secrets rm "+name).Assert(t,
143-
tcli.Success(),
144-
)
145-
c.Run(ctx, "coder secrets view "+name).Assert(t,
146-
tcli.Error(),
147-
tcli.StdoutEmpty(),
148-
)
149-
}
150-
15190
func stdoutUnmarshalsJSON(target interface{}) tcli.Assertion {
15291
return func(t *testing.T, r *tcli.CommandResult) {
15392
slog.Helper()
@@ -159,7 +98,7 @@ func stdoutUnmarshalsJSON(target interface{}) tcli.Assertion {
15998
var seededRand = rand.New(rand.NewSource(time.Now().UnixNano()))
16099

161100
func randString(length int) string {
162-
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
101+
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
163102
b := make([]byte, length)
164103
for i := range b {
165104
b[i] = charset[seededRand.Intn(len(charset))]

Diff for: ci/integration/secrets_test.go

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package integration
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"regexp"
7+
"testing"
8+
"time"
9+
10+
"cdr.dev/coder-cli/ci/tcli"
11+
"cdr.dev/slog/sloggers/slogtest/assert"
12+
)
13+
14+
func TestSecrets(t *testing.T) {
15+
t.Parallel()
16+
ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)
17+
defer cancel()
18+
19+
c, err := tcli.NewContainerRunner(ctx, &tcli.ContainerConfig{
20+
Image: "codercom/enterprise-dev",
21+
Name: "secrets-cli-tests",
22+
BindMounts: map[string]string{
23+
binpath: "/bin/coder",
24+
},
25+
})
26+
assert.Success(t, "new run container", err)
27+
defer c.Close()
28+
29+
headlessLogin(ctx, t, c)
30+
31+
c.Run(ctx, "coder secrets ls").Assert(t,
32+
tcli.Success(),
33+
)
34+
35+
name, value := randString(8), randString(8)
36+
37+
c.Run(ctx, "coder secrets create").Assert(t,
38+
tcli.Error(),
39+
tcli.StdoutEmpty(),
40+
)
41+
42+
// this tests the "Value:" prompt fallback
43+
c.Run(ctx, fmt.Sprintf("echo %s | coder secrets create %s --from-prompt", value, name)).Assert(t,
44+
tcli.Success(),
45+
tcli.StderrEmpty(),
46+
)
47+
48+
c.Run(ctx, "coder secrets ls").Assert(t,
49+
tcli.Success(),
50+
tcli.StderrEmpty(),
51+
tcli.StdoutMatches("Value"),
52+
tcli.StdoutMatches(regexp.QuoteMeta(name)),
53+
)
54+
55+
c.Run(ctx, "coder secrets view "+name).Assert(t,
56+
tcli.Success(),
57+
tcli.StderrEmpty(),
58+
tcli.StdoutMatches(regexp.QuoteMeta(value)),
59+
)
60+
61+
c.Run(ctx, "coder secrets rm").Assert(t,
62+
tcli.Error(),
63+
)
64+
c.Run(ctx, "coder secrets rm "+name).Assert(t,
65+
tcli.Success(),
66+
)
67+
c.Run(ctx, "coder secrets view "+name).Assert(t,
68+
tcli.Error(),
69+
tcli.StdoutEmpty(),
70+
)
71+
72+
name, value = randString(8), randString(8)
73+
74+
c.Run(ctx, fmt.Sprintf("coder secrets create %s --from-literal %s", name, value)).Assert(t,
75+
tcli.Success(),
76+
tcli.StderrEmpty(),
77+
)
78+
79+
c.Run(ctx, "coder secrets view "+name).Assert(t,
80+
tcli.Success(),
81+
tcli.StdoutMatches(regexp.QuoteMeta(value)),
82+
)
83+
84+
name, value = randString(8), randString(8)
85+
c.Run(ctx, fmt.Sprintf("echo %s > ~/secret.json", value)).Assert(t,
86+
tcli.Success(),
87+
)
88+
c.Run(ctx, fmt.Sprintf("coder secrets create %s --from-literal %s --from-file ~/secret.json", name, value)).Assert(t,
89+
tcli.Error(),
90+
)
91+
c.Run(ctx, fmt.Sprintf("coder secrets create %s --from-file ~/secret.json", name)).Assert(t,
92+
tcli.Success(),
93+
)
94+
//
95+
c.Run(ctx, "coder secrets view "+name).Assert(t,
96+
tcli.Success(),
97+
tcli.StdoutMatches(regexp.QuoteMeta(value)),
98+
)
99+
}

Diff for: cmd/coder/secrets.go

+56-20
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ package main
22

33
import (
44
"fmt"
5+
"io/ioutil"
56
"os"
67

78
"cdr.dev/coder-cli/internal/entclient"
89
"cdr.dev/coder-cli/internal/x/xtabwriter"
910
"cdr.dev/coder-cli/internal/x/xvalidate"
11+
"github.com/manifoldco/promptui"
1012
"github.com/spf13/pflag"
1113
"golang.org/x/xerrors"
1214

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

113115
type createSecretCmd struct {
114-
name, value, description string
115-
}
116-
117-
func (cmd *createSecretCmd) Validate() (e []error) {
118-
if cmd.name == "" {
119-
e = append(e, xerrors.New("--name is a required flag"))
120-
}
121-
if cmd.value == "" {
122-
e = append(e, xerrors.New("--value is a required flag"))
123-
}
124-
return e
116+
description string
117+
fromFile string
118+
fromLiteral string
119+
fromPrompt bool
125120
}
126121

127122
func (cmd *createSecretCmd) Spec() cli.CommandSpec {
128123
return cli.CommandSpec{
129124
Name: "create",
130-
Usage: `--name MYSQL_KEY --value 123456 --description "MySQL credential for database access"`,
131-
Desc: "insert a new secret",
125+
Usage: `[secret_name] [...flags]`,
126+
Desc: "create a new secret",
127+
}
128+
}
129+
130+
func (cmd *createSecretCmd) Validate(fl *pflag.FlagSet) (e []error) {
131+
if cmd.fromPrompt && (cmd.fromLiteral != "" || cmd.fromFile != "") {
132+
e = append(e, xerrors.Errorf("--from-prompt cannot be set along with --from-file or --from-literal"))
132133
}
134+
if cmd.fromLiteral != "" && cmd.fromFile != "" {
135+
e = append(e, xerrors.Errorf("--from-literal and --from-file cannot both be set"))
136+
}
137+
if !cmd.fromPrompt && cmd.fromFile == "" && cmd.fromLiteral == "" {
138+
e = append(e, xerrors.Errorf("one of [--from-literal, --from-file, --from-prompt] is required"))
139+
}
140+
return e
133141
}
134142

135143
func (cmd *createSecretCmd) Run(fl *pflag.FlagSet) {
136144
var (
137145
client = requireAuth()
146+
name = fl.Arg(0)
147+
value string
148+
err error
138149
)
139-
xvalidate.Validate(cmd)
150+
if name == "" {
151+
exitUsage(fl)
152+
}
153+
xvalidate.Validate(fl, cmd)
154+
155+
if cmd.fromLiteral != "" {
156+
value = cmd.fromLiteral
157+
} else if cmd.fromFile != "" {
158+
contents, err := ioutil.ReadFile(cmd.fromFile)
159+
requireSuccess(err, "failed to read file: %v", err)
160+
value = string(contents)
161+
} else {
162+
prompt := promptui.Prompt{
163+
Label: "value",
164+
Mask: '*',
165+
Validate: func(s string) error {
166+
if len(s) < 1 {
167+
return xerrors.Errorf("a length > 0 is required")
168+
}
169+
return nil
170+
},
171+
}
172+
value, err = prompt.Run()
173+
requireSuccess(err, "failed to prompt for value: %v", err)
174+
}
140175

141-
err := client.InsertSecret(entclient.InsertSecretReq{
142-
Name: cmd.name,
143-
Value: cmd.value,
176+
err = client.InsertSecret(entclient.InsertSecretReq{
177+
Name: name,
178+
Value: value,
144179
Description: cmd.description,
145180
})
146181
requireSuccess(err, "failed to insert secret: %v", err)
147182
}
148183

149184
func (cmd *createSecretCmd) RegisterFlags(fl *pflag.FlagSet) {
150-
fl.StringVar(&cmd.name, "name", "", "the name of the secret")
151-
fl.StringVar(&cmd.value, "value", "", "the value of the secret")
152-
fl.StringVar(&cmd.description, "description", "", "a description of the secret")
185+
fl.StringVar(&cmd.fromFile, "from-file", "", "specify a file from which to read the value of the secret")
186+
fl.StringVar(&cmd.fromLiteral, "from-literal", "", "specify the value of the secret")
187+
fl.BoolVar(&cmd.fromPrompt, "from-prompt", false, "specify the value of the secret through a prompt")
188+
fl.StringVar(&cmd.description, "description", "", "specify a description of the secret")
153189
}
154190

155191
type deleteSecretsCmd struct{}

Diff for: cmd/coder/users.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ type listCmd struct {
3838
}
3939

4040
func (cmd *listCmd) Run(fl *pflag.FlagSet) {
41-
xvalidate.Validate(cmd)
41+
xvalidate.Validate(fl, cmd)
4242
entClient := requireAuth()
4343

4444
users, err := entClient.Users()
@@ -77,7 +77,7 @@ func (cmd *listCmd) Spec() cli.CommandSpec {
7777
}
7878
}
7979

80-
func (cmd *listCmd) Validate() (e []error) {
80+
func (cmd *listCmd) Validate(fl *pflag.FlagSet) (e []error) {
8181
if !(cmd.outputFmt == "json" || cmd.outputFmt == "human") {
8282
e = append(e, fmt.Errorf(`--output must be "json" or "human"`))
8383
}

Diff for: go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ require (
99
github.com/gorilla/websocket v1.4.1
1010
github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f
1111
github.com/klauspost/compress v1.10.8 // indirect
12+
github.com/manifoldco/promptui v0.7.0
1213
github.com/mattn/go-colorable v0.1.6 // indirect
1314
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4
1415
github.com/rjeczalik/notify v0.9.2

Diff for: go.sum

+13
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ github.com/alecthomas/kong-hcl v0.1.8-0.20190615233001-b21fea9723c8/go.mod h1:MR
3333
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY=
3434
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
3535
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
36+
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
37+
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
38+
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
39+
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
40+
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
41+
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
3642
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
3743
github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
3844
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@@ -111,6 +117,8 @@ github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJS
111117
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
112118
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
113119
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
120+
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU=
121+
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU=
114122
github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f h1:dKccXx7xA56UNqOcFIbuqFjAWPVtP688j5QMgmo6OHU=
115123
github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f/go.mod h1:4rEELDSfUAlBSyUjPG0JnaNGjf13JySHFeRdD/3dLP0=
116124
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
@@ -125,6 +133,10 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
125133
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
126134
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
127135
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
136+
github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a h1:weJVJJRzAJBFRlAiJQROKQs8oC9vOxvm4rZmBBk0ONw=
137+
github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
138+
github.com/manifoldco/promptui v0.7.0 h1:3l11YT8tm9MnwGFQ4kETwkzpAwY2Jt9lCrumCUW4+z4=
139+
github.com/manifoldco/promptui v0.7.0/go.mod h1:n4zTdgP0vr0S3w7/O/g98U+e0gwLScEXGwov2nIKuGQ=
128140
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
129141
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
130142
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
@@ -225,6 +237,7 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03i
225237
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
226238
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
227239
golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
240+
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
228241
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
229242
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
230243
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=

Diff for: internal/x/xvalidate/errors.go

+8-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ package xvalidate
22

33
import (
44
"bytes"
5+
"fmt"
6+
7+
"github.com/spf13/pflag"
58

69
"go.coder.com/flog"
710
)
@@ -81,16 +84,18 @@ func combineErrors(errs ...error) error {
8184

8285
// Validator is a command capable of validating its flags
8386
type Validator interface {
84-
Validate() []error
87+
Validate(fl *pflag.FlagSet) []error
8588
}
8689

8790
// Validate performs validation and exits with a nonzero status code if validation fails.
8891
// The proper errors are printed to stderr.
89-
func Validate(v Validator) {
90-
errs := v.Validate()
92+
func Validate(fl *pflag.FlagSet, v Validator) {
93+
errs := v.Validate(fl)
9194

9295
err := combineErrors(errs...)
9396
if err != nil {
97+
fl.Usage()
98+
fmt.Println("")
9499
flog.Fatal("failed to validate this command\n%v", err)
95100
}
96101
}

0 commit comments

Comments
 (0)