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

Commit 3d90829

Browse files
authored
Merge pull request #81 from cdr/secrets
Add secrets ls, add, view, rm commands
2 parents 87c708a + cbdd4b6 commit 3d90829

File tree

17 files changed

+576
-103
lines changed

17 files changed

+576
-103
lines changed

ci/integration/integration_test.go

Lines changed: 74 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,8 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7-
"os"
8-
"os/exec"
9-
"path/filepath"
10-
"strings"
7+
"math/rand"
8+
"regexp"
119
"testing"
1210
"time"
1311

@@ -17,50 +15,6 @@ import (
1715
"cdr.dev/slog/sloggers/slogtest/assert"
1816
)
1917

20-
func build(path string) error {
21-
cmd := exec.Command(
22-
"sh", "-c",
23-
fmt.Sprintf("cd ../../ && go build -o %s ./cmd/coder", path),
24-
)
25-
cmd.Env = append(os.Environ(), "GOOS=linux", "CGO_ENABLED=0")
26-
27-
_, err := cmd.CombinedOutput()
28-
if err != nil {
29-
return err
30-
}
31-
return nil
32-
}
33-
34-
var binpath string
35-
36-
func init() {
37-
cwd, err := os.Getwd()
38-
if err != nil {
39-
panic(err)
40-
}
41-
42-
binpath = filepath.Join(cwd, "bin", "coder")
43-
err = build(binpath)
44-
if err != nil {
45-
panic(err)
46-
}
47-
}
48-
49-
// write session tokens to the given container runner
50-
func headlessLogin(ctx context.Context, t *testing.T, runner *tcli.ContainerRunner) {
51-
creds := login(ctx, t)
52-
cmd := exec.CommandContext(ctx, "sh", "-c", "mkdir -p ~/.config/coder && cat > ~/.config/coder/session")
53-
54-
// !IMPORTANT: be careful that this does not appear in logs
55-
cmd.Stdin = strings.NewReader(creds.token)
56-
runner.RunCmd(cmd).Assert(t,
57-
tcli.Success(),
58-
)
59-
runner.Run(ctx, fmt.Sprintf("echo -ne %s > ~/.config/coder/url", creds.url)).Assert(t,
60-
tcli.Success(),
61-
)
62-
}
63-
6418
func TestCoderCLI(t *testing.T) {
6519
t.Parallel()
6620
ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)
@@ -116,7 +70,7 @@ func TestCoderCLI(t *testing.T) {
11670
var user entclient.User
11771
c.Run(ctx, `coder users ls -o json | jq -c '.[] | select( .username == "charlie")'`).Assert(t,
11872
tcli.Success(),
119-
jsonUnmarshals(&user),
73+
stdoutUnmarshalsJSON(&user),
12074
)
12175
assert.Equal(t, "user email is as expected", "charlie@coder.com", user.Email)
12276
assert.Equal(t, "username is as expected", "Charlie", user.Name)
@@ -135,10 +89,80 @@ func TestCoderCLI(t *testing.T) {
13589
)
13690
}
13791

138-
func jsonUnmarshals(target interface{}) tcli.Assertion {
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+
151+
func stdoutUnmarshalsJSON(target interface{}) tcli.Assertion {
139152
return func(t *testing.T, r *tcli.CommandResult) {
140153
slog.Helper()
141154
err := json.Unmarshal(r.Stdout, target)
142155
assert.Success(t, "json unmarshals", err)
143156
}
144157
}
158+
159+
var seededRand = rand.New(rand.NewSource(time.Now().UnixNano()))
160+
161+
func randString(length int) string {
162+
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
163+
b := make([]byte, length)
164+
for i := range b {
165+
b[i] = charset[seededRand.Intn(len(charset))]
166+
}
167+
return string(b)
168+
}

ci/integration/setup_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package integration
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
"os/exec"
8+
"path/filepath"
9+
"strings"
10+
"testing"
11+
12+
"cdr.dev/coder-cli/ci/tcli"
13+
"golang.org/x/xerrors"
14+
)
15+
16+
var binpath string
17+
18+
// initialize integration tests by building the coder-cli binary
19+
func init() {
20+
cwd, err := os.Getwd()
21+
if err != nil {
22+
panic(err)
23+
}
24+
25+
binpath = filepath.Join(cwd, "bin", "coder")
26+
err = build(binpath)
27+
if err != nil {
28+
panic(err)
29+
}
30+
}
31+
32+
// build the coder-cli binary and move to the integration testing bin directory
33+
func build(path string) error {
34+
cmd := exec.Command(
35+
"sh", "-c",
36+
fmt.Sprintf("cd ../../ && go build -o %s ./cmd/coder", path),
37+
)
38+
cmd.Env = append(os.Environ(), "GOOS=linux", "CGO_ENABLED=0")
39+
40+
out, err := cmd.CombinedOutput()
41+
if err != nil {
42+
return xerrors.Errorf("failed to build coder-cli (%v): %w", string(out), err)
43+
}
44+
return nil
45+
}
46+
47+
// write session tokens to the given container runner
48+
func headlessLogin(ctx context.Context, t *testing.T, runner *tcli.ContainerRunner) {
49+
creds := login(ctx, t)
50+
cmd := exec.CommandContext(ctx, "sh", "-c", "mkdir -p ~/.config/coder && cat > ~/.config/coder/session")
51+
52+
// !IMPORTANT: be careful that this does not appear in logs
53+
cmd.Stdin = strings.NewReader(creds.token)
54+
runner.RunCmd(cmd).Assert(t,
55+
tcli.Success(),
56+
)
57+
runner.Run(ctx, fmt.Sprintf("echo -ne %s > ~/.config/coder/url", creds.url)).Assert(t,
58+
tcli.Success(),
59+
)
60+
}

ci/tcli/tcli.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,13 +163,16 @@ type Assertable struct {
163163
}
164164

165165
// Assert runs the Assertable and
166-
func (a Assertable) Assert(t *testing.T, option ...Assertion) {
166+
func (a *Assertable) Assert(t *testing.T, option ...Assertion) {
167167
slog.Helper()
168168
var (
169169
stdout bytes.Buffer
170170
stderr bytes.Buffer
171171
result CommandResult
172172
)
173+
if a.cmd == nil {
174+
slogtest.Fatal(t, "test failed to initialize: no command specified")
175+
}
173176

174177
a.cmd.Stdout = &stdout
175178
a.cmd.Stderr = &stderr

cmd/coder/auth.go

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,19 @@ package main
33
import (
44
"net/url"
55

6-
"go.coder.com/flog"
7-
86
"cdr.dev/coder-cli/internal/config"
97
"cdr.dev/coder-cli/internal/entclient"
108
)
119

1210
func requireAuth() *entclient.Client {
1311
sessionToken, err := config.Session.Read()
14-
if err != nil {
15-
flog.Fatal("read session: %v (did you run coder login?)", err)
16-
}
12+
requireSuccess(err, "read session: %v (did you run coder login?)", err)
1713

1814
rawURL, err := config.URL.Read()
19-
if err != nil {
20-
flog.Fatal("read url: %v (did you run coder login?)", err)
21-
}
15+
requireSuccess(err, "read url: %v (did you run coder login?)", err)
2216

2317
u, err := url.Parse(rawURL)
24-
if err != nil {
25-
flog.Fatal("url misformatted: %v (try runing coder login)", err)
26-
}
18+
requireSuccess(err, "url misformatted: %v (try runing coder login)", err)
2719

2820
return &entclient.Client{
2921
BaseURL: u,

cmd/coder/ceapi.go

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,24 +27,19 @@ outer:
2727
// getEnvs returns all environments for the user.
2828
func getEnvs(client *entclient.Client) []entclient.Environment {
2929
me, err := client.Me()
30-
if err != nil {
31-
flog.Fatal("get self: %+v", err)
32-
}
30+
requireSuccess(err, "get self: %+v", err)
3331

3432
orgs, err := client.Orgs()
35-
if err != nil {
36-
flog.Fatal("get orgs: %+v", err)
37-
}
33+
requireSuccess(err, "get orgs: %+v", err)
3834

3935
orgs = userOrgs(me, orgs)
4036

4137
var allEnvs []entclient.Environment
4238

4339
for _, org := range orgs {
4440
envs, err := client.Envs(me, org)
45-
if err != nil {
46-
flog.Fatal("get envs for %v: %+v", org.Name, err)
47-
}
41+
requireSuccess(err, "get envs for %v: %+v", org.Name, err)
42+
4843
for _, env := range envs {
4944
allEnvs = append(allEnvs, env)
5045
}

cmd/coder/main.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import (
66
_ "net/http/pprof"
77
"os"
88

9-
"cdr.dev/coder-cli/internal/xterminal"
9+
"cdr.dev/coder-cli/internal/x/xterminal"
1010
"github.com/spf13/pflag"
1111

1212
"go.coder.com/flog"
@@ -43,6 +43,7 @@ func (r *rootCmd) Subcommands() []cli.Command {
4343
&versionCmd{},
4444
&configSSHCmd{},
4545
&usersCmd{},
46+
&secretsCmd{},
4647
}
4748
}
4849

@@ -61,3 +62,10 @@ func main() {
6162

6263
cli.RunRoot(&rootCmd{})
6364
}
65+
66+
// requireSuccess prints the given message and format args as a fatal error if err != nil
67+
func requireSuccess(err error, msg string, args ...interface{}) {
68+
if err != nil {
69+
flog.Fatal(msg, args...)
70+
}
71+
}

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