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

Commit7c40f86

Browse files
authored
feat(cli): include license status in support bundle (#18472)
Closes#18207This PR adds license status to support bundle to help withtroubleshooting license-related issues.- `license-status.txt`, is added to the support bundle. - it contains the same output as the `coder license list` command.- license output formatter logic has been extracted into a separatefunction.- this allows it to be reused both in the `coder license list` cmd andin the support bundle generation.
1 parent2afd1a2 commit7c40f86

File tree

6 files changed

+129
-72
lines changed

6 files changed

+129
-72
lines changed

‎cli/cliutil/license.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package cliutil
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
"time"
7+
8+
"github.com/google/uuid"
9+
"golang.org/x/xerrors"
10+
11+
"github.com/coder/coder/v2/cli/cliui"
12+
"github.com/coder/coder/v2/codersdk"
13+
)
14+
15+
// NewLicenseFormatter returns a new license formatter.
16+
// The formatter will return a table and JSON output.
17+
funcNewLicenseFormatter()*cliui.OutputFormatter {
18+
typetableLicensestruct {
19+
IDint32`table:"id,default_sort"`
20+
UUID uuid.UUID`table:"uuid" format:"uuid"`
21+
UploadedAt time.Time`table:"uploaded at" format:"date-time"`
22+
// Features is the formatted string for the license claims.
23+
// Used for the table view.
24+
Featuresstring`table:"features"`
25+
ExpiresAt time.Time`table:"expires at" format:"date-time"`
26+
Trialbool`table:"trial"`
27+
}
28+
29+
returncliui.NewOutputFormatter(
30+
cliui.ChangeFormatterData(
31+
cliui.TableFormat([]tableLicense{}, []string{"ID","UUID","Expires At","Uploaded At","Features"}),
32+
func(dataany) (any,error) {
33+
list,ok:=data.([]codersdk.License)
34+
if!ok {
35+
returnnil,xerrors.Errorf("invalid data type %T",data)
36+
}
37+
out:=make([]tableLicense,0,len(list))
38+
for_,lic:=rangelist {
39+
varformattedFeaturesstring
40+
features,err:=lic.FeaturesClaims()
41+
iferr!=nil {
42+
formattedFeatures=xerrors.Errorf("invalid license: %w",err).Error()
43+
}else {
44+
varstrs []string
45+
iflic.AllFeaturesClaim() {
46+
// If all features are enabled, just include that
47+
strs=append(strs,"all features")
48+
}else {
49+
fork,v:=rangefeatures {
50+
ifv>0 {
51+
// Only include claims > 0
52+
strs=append(strs,fmt.Sprintf("%s=%v",k,v))
53+
}
54+
}
55+
}
56+
formattedFeatures=strings.Join(strs,", ")
57+
}
58+
// If this returns an error, a zero time is returned.
59+
exp,_:=lic.ExpiresAt()
60+
61+
out=append(out,tableLicense{
62+
ID:lic.ID,
63+
UUID:lic.UUID,
64+
UploadedAt:lic.UploadedAt,
65+
Features:formattedFeatures,
66+
ExpiresAt:exp,
67+
Trial:lic.Trial(),
68+
})
69+
}
70+
returnout,nil
71+
}),
72+
cliui.ChangeFormatterData(cliui.JSONFormat(),func(dataany) (any,error) {
73+
list,ok:=data.([]codersdk.License)
74+
if!ok {
75+
returnnil,xerrors.Errorf("invalid data type %T",data)
76+
}
77+
fori:=rangelist {
78+
humanExp,err:=list[i].ExpiresAt()
79+
iferr==nil {
80+
list[i].Claims[codersdk.LicenseExpiryClaim+"_human"]=humanExp.Format(time.RFC3339)
81+
}
82+
}
83+
84+
returnlist,nil
85+
}),
86+
)
87+
}

‎cli/support.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package cli
33
import (
44
"archive/zip"
55
"bytes"
6+
"context"
67
"encoding/base64"
78
"encoding/json"
89
"fmt"
@@ -13,6 +14,8 @@ import (
1314
"text/tabwriter"
1415
"time"
1516

17+
"github.com/coder/coder/v2/cli/cliutil"
18+
1619
"github.com/google/uuid"
1720
"golang.org/x/xerrors"
1821

@@ -48,6 +51,7 @@ var supportBundleBlurb = cliui.Bold("This will collect the following information
4851
- Agent details (with environment variable sanitized)
4952
- Agent network diagnostics
5053
- Agent logs
54+
- License status
5155
`+cliui.Bold("Note: ")+
5256
cliui.Wrap("While we try to sanitize sensitive data from support bundles, we cannot guarantee that they do not contain information that you or your organization may consider sensitive.\n")+
5357
cliui.Bold("Please confirm that you will:\n")+
@@ -302,6 +306,11 @@ func writeBundle(src *support.Bundle, dest *zip.Writer) error {
302306
returnxerrors.Errorf("decode template zip from base64")
303307
}
304308

309+
licenseStatus,err:=humanizeLicenses(src.Deployment.Licenses)
310+
iferr!=nil {
311+
returnxerrors.Errorf("format license status: %w",err)
312+
}
313+
305314
// The below we just write as we have them:
306315
fork,v:=rangemap[string]string{
307316
"agent/logs.txt":string(src.Agent.Logs),
@@ -315,6 +324,7 @@ func writeBundle(src *support.Bundle, dest *zip.Writer) error {
315324
"network/tailnet_debug.html":src.Network.TailnetDebug,
316325
"workspace/build_logs.txt":humanizeBuildLogs(src.Workspace.BuildLogs),
317326
"workspace/template_file.zip":string(templateVersionBytes),
327+
"license-status.txt":licenseStatus,
318328
} {
319329
f,err:=dest.Create(k)
320330
iferr!=nil {
@@ -359,3 +369,13 @@ func humanizeBuildLogs(ls []codersdk.ProvisionerJobLog) string {
359369
_=tw.Flush()
360370
returnbuf.String()
361371
}
372+
373+
funchumanizeLicenses(licenses []codersdk.License) (string,error) {
374+
formatter:=cliutil.NewLicenseFormatter()
375+
376+
iflen(licenses)==0 {
377+
return"No licenses found",nil
378+
}
379+
380+
returnformatter.Format(context.Background(),licenses)
381+
}

‎cli/support_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,9 @@ func assertBundleContents(t *testing.T, path string, wantWorkspace bool, wantAge
386386
case"cli_logs.txt":
387387
bs:=readBytesFromZip(t,f)
388388
require.NotEmpty(t,bs,"CLI logs should not be empty")
389+
case"license-status.txt":
390+
bs:=readBytesFromZip(t,f)
391+
require.NotEmpty(t,bs,"license status should not be empty")
389392
default:
390393
require.Failf(t,"unexpected file in bundle",f.Name)
391394
}

‎enterprise/cli/licenses.go

Lines changed: 2 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,11 @@ import (
88
"regexp"
99
"strconv"
1010
"strings"
11-
"time"
1211

13-
"github.com/google/uuid"
1412
"golang.org/x/xerrors"
1513

1614
"github.com/coder/coder/v2/cli/cliui"
15+
"github.com/coder/coder/v2/cli/cliutil"
1716
"github.com/coder/coder/v2/codersdk"
1817
"github.com/coder/serpent"
1918
)
@@ -137,76 +136,7 @@ func validJWT(s string) error {
137136
}
138137

139138
func (r*RootCmd)licensesList()*serpent.Command {
140-
typetableLicensestruct {
141-
IDint32`table:"id,default_sort"`
142-
UUID uuid.UUID`table:"uuid" format:"uuid"`
143-
UploadedAt time.Time`table:"uploaded at" format:"date-time"`
144-
// Features is the formatted string for the license claims.
145-
// Used for the table view.
146-
Featuresstring`table:"features"`
147-
ExpiresAt time.Time`table:"expires at" format:"date-time"`
148-
Trialbool`table:"trial"`
149-
}
150-
151-
formatter:=cliui.NewOutputFormatter(
152-
cliui.ChangeFormatterData(
153-
cliui.TableFormat([]tableLicense{}, []string{"ID","UUID","Expires At","Uploaded At","Features"}),
154-
func(dataany) (any,error) {
155-
list,ok:=data.([]codersdk.License)
156-
if!ok {
157-
returnnil,xerrors.Errorf("invalid data type %T",data)
158-
}
159-
out:=make([]tableLicense,0,len(list))
160-
for_,lic:=rangelist {
161-
varformattedFeaturesstring
162-
features,err:=lic.FeaturesClaims()
163-
iferr!=nil {
164-
formattedFeatures=xerrors.Errorf("invalid license: %w",err).Error()
165-
}else {
166-
varstrs []string
167-
iflic.AllFeaturesClaim() {
168-
// If all features are enabled, just include that
169-
strs=append(strs,"all features")
170-
}else {
171-
fork,v:=rangefeatures {
172-
ifv>0 {
173-
// Only include claims > 0
174-
strs=append(strs,fmt.Sprintf("%s=%v",k,v))
175-
}
176-
}
177-
}
178-
formattedFeatures=strings.Join(strs,", ")
179-
}
180-
// If this returns an error, a zero time is returned.
181-
exp,_:=lic.ExpiresAt()
182-
183-
out=append(out,tableLicense{
184-
ID:lic.ID,
185-
UUID:lic.UUID,
186-
UploadedAt:lic.UploadedAt,
187-
Features:formattedFeatures,
188-
ExpiresAt:exp,
189-
Trial:lic.Trial(),
190-
})
191-
}
192-
returnout,nil
193-
}),
194-
cliui.ChangeFormatterData(cliui.JSONFormat(),func(dataany) (any,error) {
195-
list,ok:=data.([]codersdk.License)
196-
if!ok {
197-
returnnil,xerrors.Errorf("invalid data type %T",data)
198-
}
199-
fori:=rangelist {
200-
humanExp,err:=list[i].ExpiresAt()
201-
iferr==nil {
202-
list[i].Claims[codersdk.LicenseExpiryClaim+"_human"]=humanExp.Format(time.RFC3339)
203-
}
204-
}
205-
206-
returnlist,nil
207-
}),
208-
)
209-
139+
formatter:=cliutil.NewLicenseFormatter()
210140
client:=new(codersdk.Client)
211141
cmd:=&serpent.Command{
212142
Use:"list",

‎support/support.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ type Deployment struct {
4343
Config*codersdk.DeploymentConfig`json:"config"`
4444
Experiments codersdk.Experiments`json:"experiments"`
4545
HealthReport*healthsdk.HealthcheckReport`json:"health_report"`
46+
Licenses []codersdk.License`json:"licenses"`
4647
}
4748

4849
typeNetworkstruct {
@@ -138,6 +139,21 @@ func DeploymentInfo(ctx context.Context, client *codersdk.Client, log slog.Logge
138139
returnnil
139140
})
140141

142+
eg.Go(func()error {
143+
licenses,err:=client.Licenses(ctx)
144+
iferr!=nil {
145+
// Ignore 404 because AGPL doesn't have this endpoint
146+
ifcerr,ok:=codersdk.AsError(err);ok&&cerr.StatusCode()!=http.StatusNotFound {
147+
returnxerrors.Errorf("fetch license status: %w",err)
148+
}
149+
}
150+
iflicenses==nil {
151+
licenses=make([]codersdk.License,0)
152+
}
153+
d.Licenses=licenses
154+
returnnil
155+
})
156+
141157
iferr:=eg.Wait();err!=nil {
142158
log.Error(ctx,"fetch deployment information",slog.Error(err))
143159
}

‎support/support_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ func TestRun(t *testing.T) {
6262
assertSanitizedDeploymentConfig(t,bun.Deployment.Config)
6363
assertNotNilNotEmpty(t,bun.Deployment.HealthReport,"deployment health report should be present")
6464
assertNotNilNotEmpty(t,bun.Deployment.Experiments,"deployment experiments should be present")
65+
require.NotNil(t,bun.Deployment.Licenses,"license status should be present")
6566
assertNotNilNotEmpty(t,bun.Network.ConnectionInfo,"agent connection info should be present")
6667
assertNotNilNotEmpty(t,bun.Network.CoordinatorDebug,"network coordinator debug should be present")
6768
assertNotNilNotEmpty(t,bun.Network.Netcheck,"network netcheck should be present")

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp