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

ci/integration/integration_test.go

Lines changed: 1 addition & 62 deletions
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))]

ci/integration/secrets_test.go

Lines changed: 99 additions & 0 deletions
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+
}

cmd/coder/secrets.go

Lines changed: 56 additions & 20 deletions
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{}

cmd/coder/users.go

Lines changed: 2 additions & 2 deletions
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
}

go.mod

Lines changed: 1 addition & 0 deletions
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

go.sum

Lines changed: 13 additions & 0 deletions
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=

internal/x/xvalidate/errors.go

Lines changed: 8 additions & 3 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy