Skip to content

Commit 5fcdcf5

Browse files
committed
Merge branch 'main' into bq/refactor-rename-feature
2 parents 2bbc919 + a32169c commit 5fcdcf5

File tree

101 files changed

+2813
-2360
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

101 files changed

+2813
-2360
lines changed

.vscode/settings.json

+1
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@
113113
"stretchr",
114114
"STTY",
115115
"stuntest",
116+
"tanstack",
116117
"tailbroker",
117118
"tailcfg",
118119
"tailexchange",

Makefile

+3-2
Original file line numberDiff line numberDiff line change
@@ -501,9 +501,10 @@ docs/admin/prometheus.md: scripts/metricsdocgen/main.go scripts/metricsdocgen/me
501501
yarn run format:write:only ../docs/admin/prometheus.md
502502

503503
docs/cli/coder.md: scripts/clidocgen/main.go $(GO_SRC_FILES) docs/manifest.json
504-
BASE_PATH="." go run scripts/clidocgen/main.go
504+
rm -rf ./docs/cli/*.md
505+
BASE_PATH="." go run ./scripts/clidocgen
505506
cd site
506-
yarn run format:write:only ../docs/cli/*.md ../docs/manifest.json
507+
yarn run format:write:only ../docs/cli.md ../docs/cli/*.md ../docs/manifest.json
507508

508509
docs/admin/audit-logs.md: scripts/auditdocgen/main.go enterprise/audit/table.go
509510
go run scripts/auditdocgen/main.go

cli/state.go

+6-4
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@ func state() *cobra.Command {
2727
func statePull() *cobra.Command {
2828
var buildNumber int
2929
cmd := &cobra.Command{
30-
Use: "pull <workspace> [file]",
31-
Args: cobra.MinimumNArgs(1),
30+
Use: "pull <workspace> [file]",
31+
Short: "Pull a Terraform state file from a workspace.",
32+
Args: cobra.MinimumNArgs(1),
3233
RunE: func(cmd *cobra.Command, args []string) error {
3334
client, err := CreateClient(cmd)
3435
if err != nil {
@@ -68,8 +69,9 @@ func statePull() *cobra.Command {
6869
func statePush() *cobra.Command {
6970
var buildNumber int
7071
cmd := &cobra.Command{
71-
Use: "push <workspace> <file>",
72-
Args: cobra.ExactArgs(2),
72+
Use: "push <workspace> <file>",
73+
Args: cobra.ExactArgs(2),
74+
Short: "Push a Terraform state file to a workspace.",
7375
RunE: func(cmd *cobra.Command, args []string) error {
7476
client, err := CreateClient(cmd)
7577
if err != nil {

cli/templatepull_test.go

+26-14
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ package cli_test
33
import (
44
"bytes"
55
"context"
6-
"io"
6+
"crypto/sha256"
7+
"encoding/hex"
78
"os"
89
"path/filepath"
910
"testing"
1011

1112
"github.com/codeclysm/extract"
1213
"github.com/google/uuid"
13-
"github.com/ory/dockertest/v3/docker/pkg/archive"
1414
"github.com/stretchr/testify/require"
1515

1616
"github.com/coder/coder/cli/clitest"
@@ -20,6 +20,26 @@ import (
2020
"github.com/coder/coder/pty/ptytest"
2121
)
2222

23+
// dirSum calculates a checksum of the files in a directory.
24+
func dirSum(t *testing.T, dir string) string {
25+
ents, err := os.ReadDir(dir)
26+
require.NoError(t, err)
27+
sum := sha256.New()
28+
for _, e := range ents {
29+
path := filepath.Join(dir, e.Name())
30+
31+
stat, err := os.Stat(path)
32+
require.NoError(t, err)
33+
34+
byt, err := os.ReadFile(
35+
path,
36+
)
37+
require.NoError(t, err, "mode: %+v", stat.Mode())
38+
_, _ = sum.Write(byt)
39+
}
40+
return hex.EncodeToString(sum.Sum(nil))
41+
}
42+
2343
func TestTemplatePull(t *testing.T) {
2444
t.Parallel()
2545

@@ -119,18 +139,10 @@ func TestTemplatePull(t *testing.T) {
119139

120140
require.NoError(t, <-errChan)
121141

122-
expectedTarRd, err := archive.Tar(expectedDest, archive.Uncompressed)
123-
require.NoError(t, err)
124-
expectedTar, err := io.ReadAll(expectedTarRd)
125-
require.NoError(t, err)
126-
127-
actualTarRd, err := archive.Tar(actualDest, archive.Uncompressed)
128-
require.NoError(t, err)
129-
130-
actualTar, err := io.ReadAll(actualTarRd)
131-
require.NoError(t, err)
132-
133-
require.True(t, bytes.Equal(expectedTar, actualTar), "tar files differ")
142+
require.Equal(t,
143+
dirSum(t, expectedDest),
144+
dirSum(t, actualDest),
145+
)
134146
})
135147

136148
// FolderConflict tests that 'templates pull' fails when a folder with has

cli/testdata/coder_state_--help.golden

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ Usage:
66
coder state [command]
77

88
Commands:
9-
pull
10-
push
9+
pull Pull a Terraform state file from a workspace.
10+
push Push a Terraform state file to a workspace.
1111

1212
Flags:
1313
-h, --help help for state

cli/testdata/coder_state_pull_--help.golden

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
Pull a Terraform state file from a workspace.
2+
13
Usage:
24
coder state pull <workspace> [file] [flags]
35

cli/testdata/coder_state_push_--help.golden

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
Push a Terraform state file to a workspace.
2+
13
Usage:
24
coder state push <workspace> <file> [flags]
35

cli/testdata/coder_tokens_list_--help.golden

+4-1
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@ Aliases:
77
list, ls
88

99
Flags:
10+
-a, --all Specifies whether all users' tokens will be listed or not (must have
11+
Owner role to see all tokens).
1012
-c, --column strings Columns to display in table output. Available columns: id, last used,
11-
expires at, created at (default [id,last used,expires at,created at])
13+
expires at, created at, owner (default [id,last used,expires
14+
at,created at])
1215
-h, --help help for list
1316
-o, --output string Output format. Available formats: table, json (default "table")
1417

cli/tokens.go

+67-8
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ package cli
22

33
import (
44
"fmt"
5+
"os"
56
"strings"
67
"time"
78

9+
"github.com/google/uuid"
810
"github.com/spf13/cobra"
11+
"golang.org/x/exp/slices"
912
"golang.org/x/xerrors"
1013

1114
"github.com/coder/coder/cli/cliflag"
@@ -83,12 +86,47 @@ func createToken() *cobra.Command {
8386
return cmd
8487
}
8588

89+
// tokenListRow is the type provided to the OutputFormatter.
90+
type tokenListRow struct {
91+
// For JSON format:
92+
codersdk.APIKey `table:"-"`
93+
94+
// For table format:
95+
ID string `json:"-" table:"id,default_sort"`
96+
LastUsed time.Time `json:"-" table:"last used"`
97+
ExpiresAt time.Time `json:"-" table:"expires at"`
98+
CreatedAt time.Time `json:"-" table:"created at"`
99+
Owner string `json:"-" table:"owner"`
100+
}
101+
102+
func tokenListRowFromToken(token codersdk.APIKey, usersByID map[uuid.UUID]codersdk.User) tokenListRow {
103+
user := usersByID[token.UserID]
104+
105+
return tokenListRow{
106+
APIKey: token,
107+
ID: token.ID,
108+
LastUsed: token.LastUsed,
109+
ExpiresAt: token.ExpiresAt,
110+
CreatedAt: token.CreatedAt,
111+
Owner: user.Username,
112+
}
113+
}
114+
86115
func listTokens() *cobra.Command {
87-
formatter := cliui.NewOutputFormatter(
88-
cliui.TableFormat([]codersdk.APIKey{}, nil),
89-
cliui.JSONFormat(),
90-
)
116+
// we only display the 'owner' column if the --all argument is passed in
117+
defaultCols := []string{"id", "last used", "expires at", "created at"}
118+
if slices.Contains(os.Args, "-a") || slices.Contains(os.Args, "--all") {
119+
defaultCols = append(defaultCols, "owner")
120+
}
91121

122+
var (
123+
all bool
124+
displayTokens []tokenListRow
125+
formatter = cliui.NewOutputFormatter(
126+
cliui.TableFormat([]tokenListRow{}, defaultCols),
127+
cliui.JSONFormat(),
128+
)
129+
)
92130
cmd := &cobra.Command{
93131
Use: "list",
94132
Aliases: []string{"ls"},
@@ -99,18 +137,36 @@ func listTokens() *cobra.Command {
99137
return xerrors.Errorf("create codersdk client: %w", err)
100138
}
101139

102-
keys, err := client.Tokens(cmd.Context(), codersdk.Me)
140+
tokens, err := client.Tokens(cmd.Context(), codersdk.Me, codersdk.TokensFilter{
141+
IncludeAll: all,
142+
})
103143
if err != nil {
104-
return xerrors.Errorf("create tokens: %w", err)
144+
return xerrors.Errorf("list tokens: %w", err)
105145
}
106146

107-
if len(keys) == 0 {
147+
if len(tokens) == 0 {
108148
cmd.Println(cliui.Styles.Wrap.Render(
109149
"No tokens found.",
110150
))
111151
}
112152

113-
out, err := formatter.Format(cmd.Context(), keys)
153+
userRes, err := client.Users(cmd.Context(), codersdk.UsersRequest{})
154+
if err != nil {
155+
return err
156+
}
157+
158+
usersByID := map[uuid.UUID]codersdk.User{}
159+
for _, user := range userRes.Users {
160+
usersByID[user.ID] = user
161+
}
162+
163+
displayTokens = make([]tokenListRow, len(tokens))
164+
165+
for i, token := range tokens {
166+
displayTokens[i] = tokenListRowFromToken(token, usersByID)
167+
}
168+
169+
out, err := formatter.Format(cmd.Context(), displayTokens)
114170
if err != nil {
115171
return err
116172
}
@@ -120,6 +176,9 @@ func listTokens() *cobra.Command {
120176
},
121177
}
122178

179+
cmd.Flags().BoolVarP(&all, "all", "a", false,
180+
"Specifies whether all users' tokens will be listed or not (must have Owner role to see all tokens).")
181+
123182
formatter.AttachFlags(cmd)
124183
return cmd
125184
}

coderd/apikey.go

+29-8
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"fmt"
99
"net"
1010
"net/http"
11+
"strconv"
1112
"time"
1213

1314
"github.com/go-chi/chi/v5"
@@ -175,15 +176,35 @@ func (api *API) apiKey(rw http.ResponseWriter, r *http.Request) {
175176
// @Success 200 {array} codersdk.APIKey
176177
// @Router /users/{user}/keys/tokens [get]
177178
func (api *API) tokens(rw http.ResponseWriter, r *http.Request) {
178-
ctx := r.Context()
179+
var (
180+
ctx = r.Context()
181+
user = httpmw.UserParam(r)
182+
keys []database.APIKey
183+
err error
184+
queryStr = r.URL.Query().Get("include_all")
185+
includeAll, _ = strconv.ParseBool(queryStr)
186+
)
179187

180-
keys, err := api.Database.GetAPIKeysByLoginType(ctx, database.LoginTypeToken)
181-
if err != nil {
182-
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
183-
Message: "Internal error fetching API keys.",
184-
Detail: err.Error(),
185-
})
186-
return
188+
if includeAll {
189+
// get tokens for all users
190+
keys, err = api.Database.GetAPIKeysByLoginType(ctx, database.LoginTypeToken)
191+
if err != nil {
192+
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
193+
Message: "Internal error fetching API keys.",
194+
Detail: err.Error(),
195+
})
196+
return
197+
}
198+
} else {
199+
// get user's tokens only
200+
keys, err = api.Database.GetAPIKeysByUserID(ctx, database.GetAPIKeysByUserIDParams{LoginType: database.LoginTypeToken, UserID: user.ID})
201+
if err != nil {
202+
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
203+
Message: "Internal error fetching API keys.",
204+
Detail: err.Error(),
205+
})
206+
return
207+
}
187208
}
188209

189210
keys, err = AuthorizeFilter(api.HTTPAuth, r, rbac.ActionRead, keys)

coderd/apikey_test.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,15 @@ func TestTokenCRUD(t *testing.T) {
2424
defer cancel()
2525
client := coderdtest.New(t, nil)
2626
_ = coderdtest.CreateFirstUser(t, client)
27-
keys, err := client.Tokens(ctx, codersdk.Me)
27+
keys, err := client.Tokens(ctx, codersdk.Me, codersdk.TokensFilter{})
2828
require.NoError(t, err)
2929
require.Empty(t, keys)
3030

3131
res, err := client.CreateToken(ctx, codersdk.Me, codersdk.CreateTokenRequest{})
3232
require.NoError(t, err)
3333
require.Greater(t, len(res.Key), 2)
3434

35-
keys, err = client.Tokens(ctx, codersdk.Me)
35+
keys, err = client.Tokens(ctx, codersdk.Me, codersdk.TokensFilter{})
3636
require.NoError(t, err)
3737
require.EqualValues(t, len(keys), 1)
3838
require.Contains(t, res.Key, keys[0].ID)
@@ -45,7 +45,7 @@ func TestTokenCRUD(t *testing.T) {
4545

4646
err = client.DeleteAPIKey(ctx, codersdk.Me, keys[0].ID)
4747
require.NoError(t, err)
48-
keys, err = client.Tokens(ctx, codersdk.Me)
48+
keys, err = client.Tokens(ctx, codersdk.Me, codersdk.TokensFilter{})
4949
require.NoError(t, err)
5050
require.Empty(t, keys)
5151
}
@@ -64,7 +64,7 @@ func TestTokenScoped(t *testing.T) {
6464
require.NoError(t, err)
6565
require.Greater(t, len(res.Key), 2)
6666

67-
keys, err := client.Tokens(ctx, codersdk.Me)
67+
keys, err := client.Tokens(ctx, codersdk.Me, codersdk.TokensFilter{})
6868
require.NoError(t, err)
6969
require.EqualValues(t, len(keys), 1)
7070
require.Contains(t, res.Key, keys[0].ID)
@@ -83,7 +83,7 @@ func TestTokenDuration(t *testing.T) {
8383
Lifetime: time.Hour * 24 * 7,
8484
})
8585
require.NoError(t, err)
86-
keys, err := client.Tokens(ctx, codersdk.Me)
86+
keys, err := client.Tokens(ctx, codersdk.Me, codersdk.TokensFilter{})
8787
require.NoError(t, err)
8888
require.Greater(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*6*24))
8989
require.Less(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*8*24))

coderd/coderdtest/authorize.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,9 @@ func NewAuthTester(ctx context.Context, t *testing.T, client *codersdk.Client, a
347347
})
348348
require.NoError(t, err, "create token")
349349

350-
apiKeys, err := client.Tokens(ctx, admin.UserID.String())
350+
apiKeys, err := client.Tokens(ctx, admin.UserID.String(), codersdk.TokensFilter{
351+
IncludeAll: true,
352+
})
351353
require.NoError(t, err, "get tokens")
352354
apiKey := apiKeys[0]
353355

coderd/database/dbauthz/querier.go

+4
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ func (q *querier) GetAPIKeysByLoginType(ctx context.Context, loginType database.
4040
return fetchWithPostFilter(q.auth, q.db.GetAPIKeysByLoginType)(ctx, loginType)
4141
}
4242

43+
func (q *querier) GetAPIKeysByUserID(ctx context.Context, params database.GetAPIKeysByUserIDParams) ([]database.APIKey, error) {
44+
return fetchWithPostFilter(q.auth, q.db.GetAPIKeysByUserID)(ctx, database.GetAPIKeysByUserIDParams{LoginType: params.LoginType, UserID: params.UserID})
45+
}
46+
4347
func (q *querier) GetAPIKeysLastUsedAfter(ctx context.Context, lastUsed time.Time) ([]database.APIKey, error) {
4448
return fetchWithPostFilter(q.auth, q.db.GetAPIKeysLastUsedAfter)(ctx, lastUsed)
4549
}

0 commit comments

Comments
 (0)