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

Add secrets ls, add, view, rm commands #81

Merged
merged 2 commits into from
Jul 30, 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
124 changes: 74 additions & 50 deletions ci/integration/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@ import (
"context"
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"math/rand"
"regexp"
"testing"
"time"

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

func build(path string) error {
cmd := exec.Command(
"sh", "-c",
fmt.Sprintf("cd ../../ && go build -o %s ./cmd/coder", path),
)
cmd.Env = append(os.Environ(), "GOOS=linux", "CGO_ENABLED=0")

_, err := cmd.CombinedOutput()
if err != nil {
return err
}
return nil
}

var binpath string

func init() {
cwd, err := os.Getwd()
if err != nil {
panic(err)
}

binpath = filepath.Join(cwd, "bin", "coder")
err = build(binpath)
if err != nil {
panic(err)
}
}

// write session tokens to the given container runner
func headlessLogin(ctx context.Context, t *testing.T, runner *tcli.ContainerRunner) {
creds := login(ctx, t)
cmd := exec.CommandContext(ctx, "sh", "-c", "mkdir -p ~/.config/coder && cat > ~/.config/coder/session")

// !IMPORTANT: be careful that this does not appear in logs
cmd.Stdin = strings.NewReader(creds.token)
runner.RunCmd(cmd).Assert(t,
tcli.Success(),
)
runner.Run(ctx, fmt.Sprintf("echo -ne %s > ~/.config/coder/url", creds.url)).Assert(t,
tcli.Success(),
)
}

func TestCoderCLI(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)
Expand Down Expand Up @@ -116,7 +70,7 @@ func TestCoderCLI(t *testing.T) {
var user entclient.User
c.Run(ctx, `coder users ls -o json | jq -c '.[] | select( .username == "charlie")'`).Assert(t,
tcli.Success(),
jsonUnmarshals(&user),
stdoutUnmarshalsJSON(&user),
)
assert.Equal(t, "user email is as expected", "charlie@coder.com", user.Email)
assert.Equal(t, "username is as expected", "Charlie", user.Name)
Expand All @@ -135,10 +89,80 @@ func TestCoderCLI(t *testing.T) {
)
}

func jsonUnmarshals(target interface{}) tcli.Assertion {
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()
err := json.Unmarshal(r.Stdout, target)
assert.Success(t, "json unmarshals", err)
}
}

var seededRand = rand.New(rand.NewSource(time.Now().UnixNano()))

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

import (
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"

"cdr.dev/coder-cli/ci/tcli"
"golang.org/x/xerrors"
)

var binpath string

// initialize integration tests by building the coder-cli binary
func init() {
cwd, err := os.Getwd()
if err != nil {
panic(err)
}

binpath = filepath.Join(cwd, "bin", "coder")
err = build(binpath)
if err != nil {
panic(err)
}
}

// build the coder-cli binary and move to the integration testing bin directory
func build(path string) error {
cmd := exec.Command(
"sh", "-c",
fmt.Sprintf("cd ../../ && go build -o %s ./cmd/coder", path),
)
cmd.Env = append(os.Environ(), "GOOS=linux", "CGO_ENABLED=0")

out, err := cmd.CombinedOutput()
if err != nil {
return xerrors.Errorf("failed to build coder-cli (%v): %w", string(out), err)
}
return nil
}

// write session tokens to the given container runner
func headlessLogin(ctx context.Context, t *testing.T, runner *tcli.ContainerRunner) {
creds := login(ctx, t)
cmd := exec.CommandContext(ctx, "sh", "-c", "mkdir -p ~/.config/coder && cat > ~/.config/coder/session")

// !IMPORTANT: be careful that this does not appear in logs
cmd.Stdin = strings.NewReader(creds.token)
runner.RunCmd(cmd).Assert(t,
tcli.Success(),
)
runner.Run(ctx, fmt.Sprintf("echo -ne %s > ~/.config/coder/url", creds.url)).Assert(t,
tcli.Success(),
)
}
5 changes: 4 additions & 1 deletion ci/tcli/tcli.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,13 +163,16 @@ type Assertable struct {
}

// Assert runs the Assertable and
func (a Assertable) Assert(t *testing.T, option ...Assertion) {
func (a *Assertable) Assert(t *testing.T, option ...Assertion) {
slog.Helper()
var (
stdout bytes.Buffer
stderr bytes.Buffer
result CommandResult
)
if a.cmd == nil {
slogtest.Fatal(t, "test failed to initialize: no command specified")
}

a.cmd.Stdout = &stdout
a.cmd.Stderr = &stderr
Expand Down
14 changes: 3 additions & 11 deletions cmd/coder/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,19 @@ package main
import (
"net/url"

"go.coder.com/flog"

"cdr.dev/coder-cli/internal/config"
"cdr.dev/coder-cli/internal/entclient"
)

func requireAuth() *entclient.Client {
sessionToken, err := config.Session.Read()
if err != nil {
flog.Fatal("read session: %v (did you run coder login?)", err)
}
requireSuccess(err, "read session: %v (did you run coder login?)", err)

rawURL, err := config.URL.Read()
if err != nil {
flog.Fatal("read url: %v (did you run coder login?)", err)
}
requireSuccess(err, "read url: %v (did you run coder login?)", err)

u, err := url.Parse(rawURL)
if err != nil {
flog.Fatal("url misformatted: %v (try runing coder login)", err)
}
requireSuccess(err, "url misformatted: %v (try runing coder login)", err)

return &entclient.Client{
BaseURL: u,
Expand Down
13 changes: 4 additions & 9 deletions cmd/coder/ceapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,24 +27,19 @@ outer:
// getEnvs returns all environments for the user.
func getEnvs(client *entclient.Client) []entclient.Environment {
me, err := client.Me()
if err != nil {
flog.Fatal("get self: %+v", err)
}
requireSuccess(err, "get self: %+v", err)

orgs, err := client.Orgs()
if err != nil {
flog.Fatal("get orgs: %+v", err)
}
requireSuccess(err, "get orgs: %+v", err)

orgs = userOrgs(me, orgs)

var allEnvs []entclient.Environment

for _, org := range orgs {
envs, err := client.Envs(me, org)
if err != nil {
flog.Fatal("get envs for %v: %+v", org.Name, err)
}
requireSuccess(err, "get envs for %v: %+v", org.Name, err)

for _, env := range envs {
allEnvs = append(allEnvs, env)
}
Expand Down
10 changes: 9 additions & 1 deletion cmd/coder/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
_ "net/http/pprof"
"os"

"cdr.dev/coder-cli/internal/xterminal"
"cdr.dev/coder-cli/internal/x/xterminal"
"github.com/spf13/pflag"

"go.coder.com/flog"
Expand Down Expand Up @@ -43,6 +43,7 @@ func (r *rootCmd) Subcommands() []cli.Command {
&versionCmd{},
&configSSHCmd{},
&usersCmd{},
&secretsCmd{},
}
}

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

cli.RunRoot(&rootCmd{})
}

// requireSuccess prints the given message and format args as a fatal error if err != nil
func requireSuccess(err error, msg string, args ...interface{}) {
if err != nil {
flog.Fatal(msg, args...)
}
}
Loading