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

feat: add scoped token support to CLI#19985

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
ThomasK33 wants to merge1 commit intothomask33/09-26-add_api_key_patch_endpoint
base:thomask33/09-26-add_api_key_patch_endpoint
Choose a base branch
Loading
fromthomask33/09-26-add_token_scope_support_in_cli
Open
Show file tree
Hide file tree
Changes fromall 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
5 changes: 5 additions & 0 deletionscli/testdata/coder_tokens_--help.golden
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,6 +16,10 @@ USAGE:

$ coder tokens ls

- Create a scoped token:

$ coder tokens create --scope workspace:read --allow workspace:<uuid>

- Remove a token by ID:

$ coder tokens rm WuoWs4ZsMX
Expand All@@ -24,6 +28,7 @@ SUBCOMMANDS:
create Create a token
list List tokens
remove Delete a token
view Display detailed information about a token

———
Run `coder --help` for a list of global options.
6 changes: 6 additions & 0 deletionscli/testdata/coder_tokens_create_--help.golden
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,12 +6,18 @@ USAGE:
Create a token

OPTIONS:
--allow allowList
Repeatable allow-list entry (<type>:<uuid>, e.g. workspace:1234-...).

--lifetime string, $CODER_TOKEN_LIFETIME
Specify a duration for the lifetime of the token.

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

--scope scope
Repeatable scope to attach to the token (e.g. workspace:read).

-u, --user string, $CODER_TOKEN_USER
Specify the user to create the token for (Only works if logged in user
is admin).
Expand Down
2 changes: 1 addition & 1 deletioncli/testdata/coder_tokens_list_--help.golden
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,7 +12,7 @@ OPTIONS:
Specifies whether all users' tokens will be listed or not (must have
Owner role to see all tokens).

-c, --column [id|name|last used|expires at|created at|owner] (default: id,name,last used,expires at,created at)
-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)
Columns to display in table output.

-o, --output table|json (default: table)
Expand Down
16 changes: 16 additions & 0 deletionscli/testdata/coder_tokens_view_--help.golden
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
coder v0.0.0-devel

USAGE:
coder tokens view [flags] <name|id>

Display detailed information about a token

OPTIONS:
-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)
Columns to display in table output.

-o, --output table|json (default: table)
Output format.

———
Run `coder --help` for a list of global options.
110 changes: 105 additions & 5 deletionscli/tokens.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,6 +4,7 @@ import (
"fmt"
"os"
"slices"
"sort"
"strings"
"time"

Expand All@@ -27,6 +28,10 @@ func (r *RootCmd) tokens() *serpent.Command {
Description: "List your tokens",
Command: "coder tokens ls",
},
Example{
Description: "Create a scoped token",
Command: "coder tokens create --scope workspace:read --allow workspace:<uuid>",
},
Example{
Description: "Remove a token by ID",
Command: "coder tokens rm WuoWs4ZsMX",
Expand All@@ -39,6 +44,7 @@ func (r *RootCmd) tokens() *serpent.Command {
Children: []*serpent.Command{
r.createToken(),
r.listTokens(),
r.viewToken(),
r.removeToken(),
},
}
Expand All@@ -50,6 +56,8 @@ func (r *RootCmd) createToken() *serpent.Command {
tokenLifetime string
name string
user string
scopes []codersdk.APIKeyScope
allowList []codersdk.APIAllowListTarget
)
cmd := &serpent.Command{
Use: "create",
Expand DownExpand Up@@ -88,10 +96,18 @@ func (r *RootCmd) createToken() *serpent.Command {
}
}

res, err := client.CreateToken(inv.Context(), userID, codersdk.CreateTokenRequest{
req := codersdk.CreateTokenRequest{
Lifetime: parsedLifetime,
TokenName: name,
})
}
if len(scopes) > 0 {
req.Scopes = append([]codersdk.APIKeyScope(nil), scopes...)
}
if len(allowList) > 0 {
req.AllowList = append([]codersdk.APIAllowListTarget(nil), allowList...)
}

res, err := client.CreateToken(inv.Context(), userID, req)
if err != nil {
return xerrors.Errorf("create tokens: %w", err)
}
Expand DownExpand Up@@ -123,6 +139,16 @@ func (r *RootCmd) createToken() *serpent.Command {
Description: "Specify the user to create the token for (Only works if logged in user is admin).",
Value: serpent.StringOf(&user),
},
{
Flag: "scope",
Description: "Repeatable scope to attach to the token (e.g. workspace:read).",
Value: newScopeFlag(&scopes),
},
{
Flag: "allow",
Description: "Repeatable allow-list entry (<type>:<uuid>, e.g. workspace:1234-...).",
Value: newAllowListFlag(&allowList),
},
}

return cmd
Expand All@@ -136,27 +162,59 @@ type tokenListRow struct {
// For table format:
ID string `json:"-" table:"id,default_sort"`
TokenName string `json:"token_name" table:"name"`
Scopes string `json:"-" table:"scopes"`
Allow string `json:"-" table:"allow list"`
LastUsed time.Time `json:"-" table:"last used"`
ExpiresAt time.Time `json:"-" table:"expires at"`
CreatedAt time.Time `json:"-" table:"created at"`
Owner string `json:"-" table:"owner"`
}

func tokenListRowFromToken(token codersdk.APIKeyWithOwner) tokenListRow {
return tokenListRowFromKey(token.APIKey, token.Username)
}

func tokenListRowFromKey(token codersdk.APIKey, owner string) tokenListRow {
return tokenListRow{
APIKey: token.APIKey,
APIKey: token,
ID: token.ID,
TokenName: token.TokenName,
Scopes: joinScopes(token.Scopes),
Allow: joinAllowList(token.AllowList),
LastUsed: token.LastUsed,
ExpiresAt: token.ExpiresAt,
CreatedAt: token.CreatedAt,
Owner:token.Username,
Owner:owner,
}
}

func joinScopes(scopes []codersdk.APIKeyScope) string {
if len(scopes) == 0 {
return ""
}
vals := make([]string, len(scopes))
for i, scope := range scopes {
vals[i] = string(scope)
}
sort.Strings(vals)
return strings.Join(vals, ", ")
}

func joinAllowList(entries []codersdk.APIAllowListTarget) string {
if len(entries) == 0 {
return ""
}
vals := make([]string, len(entries))
for i, entry := range entries {
vals[i] = entry.String()
}
sort.Strings(vals)
return strings.Join(vals, ", ")
}

func (r *RootCmd) listTokens() *serpent.Command {
// we only display the 'owner' column if the --all argument is passed in
defaultCols := []string{"id", "name", "last used", "expires at", "created at"}
defaultCols := []string{"id", "name", "scopes", "allow list", "last used", "expires at", "created at"}
if slices.Contains(os.Args, "-a") || slices.Contains(os.Args, "--all") {
defaultCols = append(defaultCols, "owner")
}
Expand DownExpand Up@@ -226,6 +284,48 @@ func (r *RootCmd) listTokens() *serpent.Command {
return cmd
}

func (r *RootCmd) viewToken() *serpent.Command {
formatter := cliui.NewOutputFormatter(
cliui.TableFormat([]tokenListRow{}, []string{"id", "name", "scopes", "allow list", "last used", "expires at", "created at", "owner"}),
cliui.JSONFormat(),
)

cmd := &serpent.Command{
Use: "view <name|id>",
Short: "Display detailed information about a token",
Middleware: serpent.Chain(
serpent.RequireNArgs(1),
),
Handler: func(inv *serpent.Invocation) error {
client, err := r.InitClient(inv)
if err != nil {
return err
}

tokenName := inv.Args[0]
token, err := client.APIKeyByName(inv.Context(), codersdk.Me, tokenName)
if err != nil {
maybeID := strings.Split(tokenName, "-")[0]
token, err = client.APIKeyByID(inv.Context(), codersdk.Me, maybeID)
if err != nil {
return xerrors.Errorf("fetch api key by name or id: %w", err)
}
}

row := tokenListRowFromKey(*token, "")
out, err := formatter.Format(inv.Context(), []tokenListRow{row})
if err != nil {
return err
}
_, err = fmt.Fprintln(inv.Stdout, out)
return err
},
}

formatter.AttachOptions(&cmd.Options)
return cmd
}

func (r *RootCmd) removeToken() *serpent.Command {
cmd := &serpent.Command{
Use: "remove <name|id|token>",
Expand Down
85 changes: 85 additions & 0 deletionscli/tokens_allowlist_flag.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
package cli

import (
"strings"

"golang.org/x/xerrors"

"github.com/coder/coder/v2/codersdk"
)

// allowListFlag implements pflag.SliceValue for codersdk.APIAllowListTarget entries.
type allowListFlag struct {
targets *[]codersdk.APIAllowListTarget
}

func newAllowListFlag(dst *[]codersdk.APIAllowListTarget) *allowListFlag {
return &allowListFlag{targets: dst}
}

func (a *allowListFlag) ensureSlice() error {
if a.targets == nil {
return xerrors.New("allow list destination is nil")
}
if *a.targets == nil {
*a.targets = make([]codersdk.APIAllowListTarget, 0)
}
return nil
}

func (a *allowListFlag) String() string {
if a.targets == nil || len(*a.targets) == 0 {
return ""
}
parts := make([]string, len(*a.targets))
for i, t := range *a.targets {
parts[i] = t.String()
}
return strings.Join(parts, ",")
}

func (a *allowListFlag) Set(raw string) error {
if err := a.ensureSlice(); err != nil {
return err
}
raw = strings.TrimSpace(raw)
if raw == "" {
return xerrors.New("allow list entry cannot be empty")
}
var target codersdk.APIAllowListTarget
if err := target.UnmarshalText([]byte(raw)); err != nil {
return err
}
*a.targets = append(*a.targets, target)
return nil
}

func (*allowListFlag) Type() string { return "allowList" }

func (a *allowListFlag) Append(value string) error {
return a.Set(value)
}

func (a *allowListFlag) Replace(items []string) error {
if err := a.ensureSlice(); err != nil {
return err
}
(*a.targets) = (*a.targets)[:0]
for _, item := range items {
if err := a.Set(item); err != nil {
return err
}
}
return nil
}

func (a *allowListFlag) GetSlice() []string {
if a.targets == nil {
return nil
}
out := make([]string, len(*a.targets))
for i, t := range *a.targets {
out[i] = t.String()
}
return out
}
Loading
Loading

[8]ページ先頭

©2009-2025 Movatter.jp