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

Commitacd0cd6

Browse files
authored
coder features list CLI command (#3533)
* AGPL Entitlements APISigned-off-by: Spike Curtis <spike@coder.com>* Generate typesGenerated.tsSigned-off-by: Spike Curtis <spike@coder.com>* AllFeatures -> FeatureNamesSigned-off-by: Spike Curtis <spike@coder.com>* Features CLI commandSigned-off-by: Spike Curtis <spike@coder.com>* Validate columnsSigned-off-by: Spike Curtis <spike@coder.com>* Tests for features list CLI commandSigned-off-by: Spike Curtis <spike@coder.com>* Drop empty EntitlementsRequestSigned-off-by: Spike Curtis <spike@coder.com>* Fix dump.sql generationSigned-off-by: Spike Curtis <spike@coder.com>Signed-off-by: Spike Curtis <spike@coder.com>
1 parent5c898d0 commitacd0cd6

File tree

6 files changed

+215
-1
lines changed

6 files changed

+215
-1
lines changed

‎cli/cliui/table.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,3 +301,19 @@ func valueToTableMap(val reflect.Value) (map[string]any, error) {
301301

302302
returnrow,nil
303303
}
304+
305+
funcValidateColumns(all,given []string)error {
306+
for_,col:=rangegiven {
307+
found:=false
308+
for_,c:=rangeall {
309+
ifstrings.EqualFold(strings.ReplaceAll(col,"_"," "),c) {
310+
found=true
311+
break
312+
}
313+
}
314+
if!found {
315+
returnfmt.Errorf("unknown column: %s",col)
316+
}
317+
}
318+
returnnil
319+
}

‎cli/features.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package cli
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"strings"
7+
8+
"github.com/coder/coder/cli/cliui"
9+
"github.com/jedib0t/go-pretty/v6/table"
10+
11+
"github.com/spf13/cobra"
12+
"golang.org/x/xerrors"
13+
14+
"github.com/coder/coder/codersdk"
15+
)
16+
17+
varfeatureColumns= []string{"Name","Entitlement","Enabled","Limit","Actual"}
18+
19+
funcfeatures()*cobra.Command {
20+
cmd:=&cobra.Command{
21+
Short:"List features",
22+
Use:"features",
23+
Aliases: []string{"feature"},
24+
}
25+
cmd.AddCommand(
26+
featuresList(),
27+
)
28+
returncmd
29+
}
30+
31+
funcfeaturesList()*cobra.Command {
32+
var (
33+
columns []string
34+
outputFormatstring
35+
)
36+
37+
cmd:=&cobra.Command{
38+
Use:"list",
39+
Aliases: []string{"ls"},
40+
RunE:func(cmd*cobra.Command,args []string)error {
41+
err:=cliui.ValidateColumns(featureColumns,columns)
42+
iferr!=nil {
43+
returnerr
44+
}
45+
client,err:=createClient(cmd)
46+
iferr!=nil {
47+
returnerr
48+
}
49+
entitlements,err:=client.Entitlements(cmd.Context())
50+
iferr!=nil {
51+
returnerr
52+
}
53+
54+
out:=""
55+
switchoutputFormat {
56+
case"table","":
57+
out=displayFeatures(columns,entitlements.Features)
58+
case"json":
59+
outBytes,err:=json.Marshal(entitlements)
60+
iferr!=nil {
61+
returnxerrors.Errorf("marshal users to JSON: %w",err)
62+
}
63+
64+
out=string(outBytes)
65+
default:
66+
returnxerrors.Errorf(`unknown output format %q, only "table" and "json" are supported`,outputFormat)
67+
}
68+
69+
_,err=fmt.Fprintln(cmd.OutOrStdout(),out)
70+
returnerr
71+
},
72+
}
73+
74+
cmd.Flags().StringArrayVarP(&columns,"column","c",featureColumns,
75+
fmt.Sprintf("Specify a column to filter in the table. Available columns are: %s",
76+
strings.Join(featureColumns,", ")))
77+
cmd.Flags().StringVarP(&outputFormat,"output","o","table","Output format. Available formats are: table, json.")
78+
returncmd
79+
}
80+
81+
// displayFeatures will return a table displaying all features passed in.
82+
// filterColumns must be a subset of the feature fields and will determine which
83+
// columns to display
84+
funcdisplayFeatures(filterColumns []string,featuresmap[string]codersdk.Feature)string {
85+
tableWriter:=cliui.Table()
86+
header:= table.Row{}
87+
for_,h:=rangefeatureColumns {
88+
header=append(header,h)
89+
}
90+
tableWriter.AppendHeader(header)
91+
tableWriter.SetColumnConfigs(cliui.FilterTableColumns(header,filterColumns))
92+
tableWriter.SortBy([]table.SortBy{{
93+
Name:"username",
94+
}})
95+
forname,feat:=rangefeatures {
96+
tableWriter.AppendRow(table.Row{
97+
name,
98+
feat.Entitlement,
99+
feat.Enabled,
100+
intOrNil(feat.Limit),
101+
intOrNil(feat.Actual),
102+
})
103+
}
104+
returntableWriter.Render()
105+
}
106+
107+
funcintOrNil(i*int64)string {
108+
ifi==nil {
109+
return""
110+
}
111+
returnfmt.Sprintf("%d",*i)
112+
}

‎cli/features_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package cli_test
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"testing"
7+
8+
"github.com/stretchr/testify/assert"
9+
"github.com/stretchr/testify/require"
10+
11+
"github.com/coder/coder/cli/clitest"
12+
"github.com/coder/coder/coderd/coderdtest"
13+
"github.com/coder/coder/codersdk"
14+
"github.com/coder/coder/pty/ptytest"
15+
)
16+
17+
funcTestFeaturesList(t*testing.T) {
18+
t.Parallel()
19+
t.Run("Table",func(t*testing.T) {
20+
t.Parallel()
21+
client:=coderdtest.New(t,nil)
22+
coderdtest.CreateFirstUser(t,client)
23+
cmd,root:=clitest.New(t,"features","list")
24+
clitest.SetupConfig(t,client,root)
25+
pty:=ptytest.New(t)
26+
cmd.SetIn(pty.Input())
27+
cmd.SetOut(pty.Output())
28+
errC:=make(chanerror)
29+
gofunc() {
30+
errC<-cmd.Execute()
31+
}()
32+
require.NoError(t,<-errC)
33+
pty.ExpectMatch("user_limit")
34+
pty.ExpectMatch("not_entitled")
35+
})
36+
t.Run("JSON",func(t*testing.T) {
37+
t.Parallel()
38+
39+
client:=coderdtest.New(t,nil)
40+
coderdtest.CreateFirstUser(t,client)
41+
cmd,root:=clitest.New(t,"features","list","-o","json")
42+
clitest.SetupConfig(t,client,root)
43+
doneChan:=make(chanstruct{})
44+
45+
buf:=bytes.NewBuffer(nil)
46+
cmd.SetOut(buf)
47+
gofunc() {
48+
deferclose(doneChan)
49+
err:=cmd.Execute()
50+
assert.NoError(t,err)
51+
}()
52+
53+
<-doneChan
54+
55+
varentitlements codersdk.Entitlements
56+
err:=json.Unmarshal(buf.Bytes(),&entitlements)
57+
require.NoError(t,err,"unmarshal JSON output")
58+
assert.Len(t,entitlements.Features,2)
59+
assert.Empty(t,entitlements.Warnings)
60+
assert.Equal(t,codersdk.EntitlementNotEntitled,
61+
entitlements.Features[codersdk.FeatureUserLimit].Entitlement)
62+
assert.Equal(t,codersdk.EntitlementNotEntitled,
63+
entitlements.Features[codersdk.FeatureAuditLog].Entitlement)
64+
assert.False(t,entitlements.HasLicense)
65+
})
66+
}

‎cli/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ func Root() *cobra.Command {
135135
versionCmd(),
136136
wireguardPortForward(),
137137
workspaceAgent(),
138+
features(),
138139
)
139140

140141
cmd.SetUsageTemplate(usageTemplate())

‎coderd/features.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ func entitlements(rw http.ResponseWriter, _ *http.Request) {
1717
}
1818
httpapi.Write(rw,http.StatusOK, codersdk.Entitlements{
1919
Features:features,
20-
Warnings:nil,
20+
Warnings:[]string{},
2121
HasLicense:false,
2222
})
2323
}

‎codersdk/features.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
package codersdk
22

3+
import (
4+
"context"
5+
"encoding/json"
6+
"net/http"
7+
)
8+
39
typeEntitlementstring
410

511
const (
@@ -27,3 +33,16 @@ type Entitlements struct {
2733
Warnings []string`json:"warnings"`
2834
HasLicensebool`json:"has_license"`
2935
}
36+
37+
func (c*Client)Entitlements(ctx context.Context) (Entitlements,error) {
38+
res,err:=c.Request(ctx,http.MethodGet,"/api/v2/entitlements",nil)
39+
iferr!=nil {
40+
returnEntitlements{},err
41+
}
42+
deferres.Body.Close()
43+
ifres.StatusCode!=http.StatusOK {
44+
returnEntitlements{},readBodyAsError(res)
45+
}
46+
varentEntitlements
47+
returnent,json.NewDecoder(res.Body).Decode(&ent)
48+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp