Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitd7fcc25

Browse files
committed
feat: add scope and allowlist support to tokens CLI
This commit adds comprehensive support for token scoping and allow-listing in the CLI token management commands:- Add --scope flag to create scoped tokens with specific permissions- Add --allow flag to create tokens restricted to specific resources- Display scopes and allow-list in token list/view commands- Add tokens view subcommand for detailed token inspection- Update help text and documentation with scoping examples- Add comprehensive test coverage for new functionality
1 parent2a701ed commitd7fcc25

13 files changed

+420
-18
lines changed

‎cli/testdata/coder_tokens_--help.golden‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ USAGE:
1616

1717
$ coder tokens ls
1818

19+
- Create a scoped token:
20+
21+
$ coder tokens create --scope workspace:read --allow workspace:<uuid>
22+
1923
- Remove a token by ID:
2024

2125
$ coder tokens rm WuoWs4ZsMX
@@ -24,6 +28,7 @@ SUBCOMMANDS:
2428
create Create a token
2529
list List tokens
2630
remove Delete a token
31+
view Display detailed information about a token
2732

2833
———
2934
Run `coder --help` for a list of global options.

‎cli/testdata/coder_tokens_create_--help.golden‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,18 @@ USAGE:
66
Create a token
77

88
OPTIONS:
9+
--allow allowList
10+
Repeatable allow-list entry (<type>:<uuid>, e.g. workspace:1234-...).
11+
912
--lifetime string, $CODER_TOKEN_LIFETIME
1013
Specify a duration for the lifetime of the token.
1114

1215
-n, --name string, $CODER_TOKEN_NAME
1316
Specify a human-readable name.
1417

18+
--scope scope
19+
Repeatable scope to attach to the token (e.g. workspace:read).
20+
1521
-u, --user string, $CODER_TOKEN_USER
1622
Specify the user to create the token for (Only works if logged in user
1723
is admin).

‎cli/testdata/coder_tokens_list_--help.golden‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ OPTIONS:
1212
Specifies whether all users' tokens will be listed or not (must have
1313
Owner role to see all tokens).
1414

15-
-c, --column [id|name|last used|expires at|created at|owner] (default: id,name,last used,expires at,created at)
15+
-c, --column [id|name|scopes|allow list|last used|expires at|created at|owner] (default: id,name,scopes,allow list,last used,expires at,created at)
1616
Columns to display in table output.
1717

1818
-o, --output table|json (default: table)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
coder v0.0.0-devel
2+
3+
USAGE:
4+
coder tokens view [flags] <name|id>
5+
6+
Display detailed information about a token
7+
8+
OPTIONS:
9+
-c, --column [id|name|scopes|allow list|last used|expires at|created at|owner] (default: id,name,scopes,allow list,last used,expires at,created at,owner)
10+
Columns to display in table output.
11+
12+
-o, --output table|json (default: table)
13+
Output format.
14+
15+
———
16+
Run `coder --help` for a list of global options.

‎cli/tokens.go‎

Lines changed: 105 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"os"
66
"slices"
7+
"sort"
78
"strings"
89
"time"
910

@@ -27,6 +28,10 @@ func (r *RootCmd) tokens() *serpent.Command {
2728
Description:"List your tokens",
2829
Command:"coder tokens ls",
2930
},
31+
Example{
32+
Description:"Create a scoped token",
33+
Command:"coder tokens create --scope workspace:read --allow workspace:<uuid>",
34+
},
3035
Example{
3136
Description:"Remove a token by ID",
3237
Command:"coder tokens rm WuoWs4ZsMX",
@@ -39,6 +44,7 @@ func (r *RootCmd) tokens() *serpent.Command {
3944
Children: []*serpent.Command{
4045
r.createToken(),
4146
r.listTokens(),
47+
r.viewToken(),
4248
r.removeToken(),
4349
},
4450
}
@@ -50,6 +56,8 @@ func (r *RootCmd) createToken() *serpent.Command {
5056
tokenLifetimestring
5157
namestring
5258
userstring
59+
scopes []codersdk.APIKeyScope
60+
allowList []codersdk.APIAllowListTarget
5361
)
5462
cmd:=&serpent.Command{
5563
Use:"create",
@@ -88,10 +96,18 @@ func (r *RootCmd) createToken() *serpent.Command {
8896
}
8997
}
9098

91-
res,err:=client.CreateToken(inv.Context(),userID, codersdk.CreateTokenRequest{
99+
req:= codersdk.CreateTokenRequest{
92100
Lifetime:parsedLifetime,
93101
TokenName:name,
94-
})
102+
}
103+
iflen(scopes)>0 {
104+
req.Scopes=append([]codersdk.APIKeyScope(nil),scopes...)
105+
}
106+
iflen(allowList)>0 {
107+
req.AllowList=append([]codersdk.APIAllowListTarget(nil),allowList...)
108+
}
109+
110+
res,err:=client.CreateToken(inv.Context(),userID,req)
95111
iferr!=nil {
96112
returnxerrors.Errorf("create tokens: %w",err)
97113
}
@@ -123,6 +139,16 @@ func (r *RootCmd) createToken() *serpent.Command {
123139
Description:"Specify the user to create the token for (Only works if logged in user is admin).",
124140
Value:serpent.StringOf(&user),
125141
},
142+
{
143+
Flag:"scope",
144+
Description:"Repeatable scope to attach to the token (e.g. workspace:read).",
145+
Value:newScopeFlag(&scopes),
146+
},
147+
{
148+
Flag:"allow",
149+
Description:"Repeatable allow-list entry (<type>:<uuid>, e.g. workspace:1234-...).",
150+
Value:newAllowListFlag(&allowList),
151+
},
126152
}
127153

128154
returncmd
@@ -136,27 +162,59 @@ type tokenListRow struct {
136162
// For table format:
137163
IDstring`json:"-" table:"id,default_sort"`
138164
TokenNamestring`json:"token_name" table:"name"`
165+
Scopesstring`json:"-" table:"scopes"`
166+
Allowstring`json:"-" table:"allow list"`
139167
LastUsed time.Time`json:"-" table:"last used"`
140168
ExpiresAt time.Time`json:"-" table:"expires at"`
141169
CreatedAt time.Time`json:"-" table:"created at"`
142170
Ownerstring`json:"-" table:"owner"`
143171
}
144172

145173
functokenListRowFromToken(token codersdk.APIKeyWithOwner)tokenListRow {
174+
returntokenListRowFromKey(token.APIKey,token.Username)
175+
}
176+
177+
functokenListRowFromKey(token codersdk.APIKey,ownerstring)tokenListRow {
146178
returntokenListRow{
147-
APIKey:token.APIKey,
179+
APIKey:token,
148180
ID:token.ID,
149181
TokenName:token.TokenName,
182+
Scopes:joinScopes(token.Scopes),
183+
Allow:joinAllowList(token.AllowList),
150184
LastUsed:token.LastUsed,
151185
ExpiresAt:token.ExpiresAt,
152186
CreatedAt:token.CreatedAt,
153-
Owner:token.Username,
187+
Owner:owner,
154188
}
155189
}
156190

191+
funcjoinScopes(scopes []codersdk.APIKeyScope)string {
192+
iflen(scopes)==0 {
193+
return""
194+
}
195+
vals:=make([]string,len(scopes))
196+
fori,scope:=rangescopes {
197+
vals[i]=string(scope)
198+
}
199+
sort.Strings(vals)
200+
returnstrings.Join(vals,", ")
201+
}
202+
203+
funcjoinAllowList(entries []codersdk.APIAllowListTarget)string {
204+
iflen(entries)==0 {
205+
return""
206+
}
207+
vals:=make([]string,len(entries))
208+
fori,entry:=rangeentries {
209+
vals[i]=entry.String()
210+
}
211+
sort.Strings(vals)
212+
returnstrings.Join(vals,", ")
213+
}
214+
157215
func (r*RootCmd)listTokens()*serpent.Command {
158216
// we only display the 'owner' column if the --all argument is passed in
159-
defaultCols:= []string{"id","name","last used","expires at","created at"}
217+
defaultCols:= []string{"id","name","scopes","allow list","last used","expires at","created at"}
160218
ifslices.Contains(os.Args,"-a")||slices.Contains(os.Args,"--all") {
161219
defaultCols=append(defaultCols,"owner")
162220
}
@@ -226,6 +284,48 @@ func (r *RootCmd) listTokens() *serpent.Command {
226284
returncmd
227285
}
228286

287+
func (r*RootCmd)viewToken()*serpent.Command {
288+
formatter:=cliui.NewOutputFormatter(
289+
cliui.TableFormat([]tokenListRow{}, []string{"id","name","scopes","allow list","last used","expires at","created at","owner"}),
290+
cliui.JSONFormat(),
291+
)
292+
293+
cmd:=&serpent.Command{
294+
Use:"view <name|id>",
295+
Short:"Display detailed information about a token",
296+
Middleware:serpent.Chain(
297+
serpent.RequireNArgs(1),
298+
),
299+
Handler:func(inv*serpent.Invocation)error {
300+
client,err:=r.InitClient(inv)
301+
iferr!=nil {
302+
returnerr
303+
}
304+
305+
tokenName:=inv.Args[0]
306+
token,err:=client.APIKeyByName(inv.Context(),codersdk.Me,tokenName)
307+
iferr!=nil {
308+
maybeID:=strings.Split(tokenName,"-")[0]
309+
token,err=client.APIKeyByID(inv.Context(),codersdk.Me,maybeID)
310+
iferr!=nil {
311+
returnxerrors.Errorf("fetch api key by name or id: %w",err)
312+
}
313+
}
314+
315+
row:=tokenListRowFromKey(*token,"")
316+
out,err:=formatter.Format(inv.Context(), []tokenListRow{row})
317+
iferr!=nil {
318+
returnerr
319+
}
320+
_,err=fmt.Fprintln(inv.Stdout,out)
321+
returnerr
322+
},
323+
}
324+
325+
formatter.AttachOptions(&cmd.Options)
326+
returncmd
327+
}
328+
229329
func (r*RootCmd)removeToken()*serpent.Command {
230330
cmd:=&serpent.Command{
231331
Use:"remove <name|id|token>",

‎cli/tokens_allowlist_flag.go‎

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package cli
2+
3+
import (
4+
"strings"
5+
6+
"golang.org/x/xerrors"
7+
8+
"github.com/coder/coder/v2/codersdk"
9+
)
10+
11+
// allowListFlag implements pflag.SliceValue for codersdk.APIAllowListTarget entries.
12+
typeallowListFlagstruct {
13+
targets*[]codersdk.APIAllowListTarget
14+
}
15+
16+
funcnewAllowListFlag(dst*[]codersdk.APIAllowListTarget)*allowListFlag {
17+
return&allowListFlag{targets:dst}
18+
}
19+
20+
func (a*allowListFlag)ensureSlice()error {
21+
ifa.targets==nil {
22+
returnxerrors.New("allow list destination is nil")
23+
}
24+
if*a.targets==nil {
25+
*a.targets=make([]codersdk.APIAllowListTarget,0)
26+
}
27+
returnnil
28+
}
29+
30+
func (a*allowListFlag)String()string {
31+
ifa.targets==nil||len(*a.targets)==0 {
32+
return""
33+
}
34+
parts:=make([]string,len(*a.targets))
35+
fori,t:=range*a.targets {
36+
parts[i]=t.String()
37+
}
38+
returnstrings.Join(parts,",")
39+
}
40+
41+
func (a*allowListFlag)Set(rawstring)error {
42+
iferr:=a.ensureSlice();err!=nil {
43+
returnerr
44+
}
45+
raw=strings.TrimSpace(raw)
46+
ifraw=="" {
47+
returnxerrors.New("allow list entry cannot be empty")
48+
}
49+
vartarget codersdk.APIAllowListTarget
50+
iferr:=target.UnmarshalText([]byte(raw));err!=nil {
51+
returnerr
52+
}
53+
*a.targets=append(*a.targets,target)
54+
returnnil
55+
}
56+
57+
func (*allowListFlag)Type()string {return"allowList" }
58+
59+
func (a*allowListFlag)Append(valuestring)error {
60+
returna.Set(value)
61+
}
62+
63+
func (a*allowListFlag)Replace(items []string)error {
64+
iferr:=a.ensureSlice();err!=nil {
65+
returnerr
66+
}
67+
(*a.targets)= (*a.targets)[:0]
68+
for_,item:=rangeitems {
69+
iferr:=a.Set(item);err!=nil {
70+
returnerr
71+
}
72+
}
73+
returnnil
74+
}
75+
76+
func (a*allowListFlag)GetSlice() []string {
77+
ifa.targets==nil {
78+
returnnil
79+
}
80+
out:=make([]string,len(*a.targets))
81+
fori,t:=range*a.targets {
82+
out[i]=t.String()
83+
}
84+
returnout
85+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp