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(cli): include license status in support bundle#18472

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

Draft
kacpersaw wants to merge1 commit intomain
base:main
Choose a base branch
Loading
fromkacpersaw/add-license-status-to-support-bundle
Draft
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
97 changes: 97 additions & 0 deletionscli/cliutil/license.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
package cliutil

import (
"fmt"
"strings"
"time"

"github.com/google/uuid"
"golang.org/x/xerrors"

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

// LicenseFormatterOpts are options for the license formatter.
type LicenseFormatterOpts struct {
Sanitize bool // If true, the UUID of the license will be redacted.
}

// NewLicenseFormatter returns a new license formatter.
// The formatter will return a table and JSON output.
func NewLicenseFormatter(opts LicenseFormatterOpts) *cliui.OutputFormatter {
type tableLicense struct {
ID int32 `table:"id,default_sort"`
UUID uuid.UUID `table:"uuid" format:"uuid"`
UploadedAt time.Time `table:"uploaded at" format:"date-time"`
// Features is the formatted string for the license claims.
// Used for the table view.
Features string `table:"features"`
ExpiresAt time.Time `table:"expires at" format:"date-time"`
Trial bool `table:"trial"`
}

return cliui.NewOutputFormatter(
cliui.ChangeFormatterData(
cliui.TableFormat([]tableLicense{}, []string{"ID", "UUID", "Expires At", "Uploaded At", "Features"}),
func(data any) (any, error) {
list, ok := data.([]codersdk.License)
if !ok {
return nil, xerrors.Errorf("invalid data type %T", data)
}
out := make([]tableLicense, 0, len(list))
for _, lic := range list {
var formattedFeatures string
features, err := lic.FeaturesClaims()
if err != nil {
formattedFeatures = xerrors.Errorf("invalid license: %w", err).Error()
} else {
var strs []string
if lic.AllFeaturesClaim() {
// If all features are enabled, just include that
strs = append(strs, "all features")
} else {
for k, v := range features {
if v > 0 {
// Only include claims > 0
strs = append(strs, fmt.Sprintf("%s=%v", k, v))
}
}
}
formattedFeatures = strings.Join(strs, ", ")
}
// If this returns an error, a zero time is returned.
exp, _ := lic.ExpiresAt()

// If sanitize is true, we redact the UUID.
if opts.Sanitize {
lic.UUID = uuid.Nil
}

out = append(out, tableLicense{
ID: lic.ID,
UUID: lic.UUID,
UploadedAt: lic.UploadedAt,
Features: formattedFeatures,
ExpiresAt: exp,
Trial: lic.Trial(),
})
}
return out, nil
}),
cliui.ChangeFormatterData(cliui.JSONFormat(), func(data any) (any, error) {
list, ok := data.([]codersdk.License)
if !ok {
return nil, xerrors.Errorf("invalid data type %T", data)
}
for i := range list {
humanExp, err := list[i].ExpiresAt()
if err == nil {
list[i].Claims[codersdk.LicenseExpiryClaim+"_human"] = humanExp.Format(time.RFC3339)
}
}

return list, nil
}),
)
}
2 changes: 2 additions & 0 deletionscli/support.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -48,6 +48,7 @@ var supportBundleBlurb = cliui.Bold("This will collect the following information
- Agent details (with environment variable sanitized)
- Agent network diagnostics
- Agent logs
- License status (sanitized)
` + cliui.Bold("Note: ") +
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") +
cliui.Bold("Please confirm that you will:\n") +
Expand DownExpand Up@@ -315,6 +316,7 @@ func writeBundle(src *support.Bundle, dest *zip.Writer) error {
"network/tailnet_debug.html": src.Network.TailnetDebug,
"workspace/build_logs.txt": humanizeBuildLogs(src.Workspace.BuildLogs),
"workspace/template_file.zip": string(templateVersionBytes),
"license-status.txt": src.LicenseStatus,
} {
f, err := dest.Create(k)
if err != nil {
Expand Down
3 changes: 3 additions & 0 deletionscli/support_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -386,6 +386,9 @@ func assertBundleContents(t *testing.T, path string, wantWorkspace bool, wantAge
case "cli_logs.txt":
bs := readBytesFromZip(t, f)
require.NotEmpty(t, bs, "CLI logs should not be empty")
case "license-status.txt":
bs := readBytesFromZip(t, f)
require.NotEmpty(t, bs, "license status should not be empty")
default:
require.Failf(t, "unexpected file in bundle", f.Name)
}
Expand Down
74 changes: 2 additions & 72 deletionsenterprise/cli/licenses.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,12 +8,11 @@ import (
"regexp"
"strconv"
"strings"
"time"

"github.com/google/uuid"
"golang.org/x/xerrors"

"github.com/coder/coder/v2/cli/cliui"
"github.com/coder/coder/v2/cli/cliutil"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/serpent"
)
Expand DownExpand Up@@ -137,76 +136,7 @@ func validJWT(s string) error {
}

func (r *RootCmd) licensesList() *serpent.Command {
type tableLicense struct {
ID int32 `table:"id,default_sort"`
UUID uuid.UUID `table:"uuid" format:"uuid"`
UploadedAt time.Time `table:"uploaded at" format:"date-time"`
// Features is the formatted string for the license claims.
// Used for the table view.
Features string `table:"features"`
ExpiresAt time.Time `table:"expires at" format:"date-time"`
Trial bool `table:"trial"`
}

formatter := cliui.NewOutputFormatter(
cliui.ChangeFormatterData(
cliui.TableFormat([]tableLicense{}, []string{"ID", "UUID", "Expires At", "Uploaded At", "Features"}),
func(data any) (any, error) {
list, ok := data.([]codersdk.License)
if !ok {
return nil, xerrors.Errorf("invalid data type %T", data)
}
out := make([]tableLicense, 0, len(list))
for _, lic := range list {
var formattedFeatures string
features, err := lic.FeaturesClaims()
if err != nil {
formattedFeatures = xerrors.Errorf("invalid license: %w", err).Error()
} else {
var strs []string
if lic.AllFeaturesClaim() {
// If all features are enabled, just include that
strs = append(strs, "all features")
} else {
for k, v := range features {
if v > 0 {
// Only include claims > 0
strs = append(strs, fmt.Sprintf("%s=%v", k, v))
}
}
}
formattedFeatures = strings.Join(strs, ", ")
}
// If this returns an error, a zero time is returned.
exp, _ := lic.ExpiresAt()

out = append(out, tableLicense{
ID: lic.ID,
UUID: lic.UUID,
UploadedAt: lic.UploadedAt,
Features: formattedFeatures,
ExpiresAt: exp,
Trial: lic.Trial(),
})
}
return out, nil
}),
cliui.ChangeFormatterData(cliui.JSONFormat(), func(data any) (any, error) {
list, ok := data.([]codersdk.License)
if !ok {
return nil, xerrors.Errorf("invalid data type %T", data)
}
for i := range list {
humanExp, err := list[i].ExpiresAt()
if err == nil {
list[i].Claims[codersdk.LicenseExpiryClaim+"_human"] = humanExp.Format(time.RFC3339)
}
}

return list, nil
}),
)

formatter := cliutil.NewLicenseFormatter(cliutil.LicenseFormatterOpts{})
client := new(codersdk.Client)
cmd := &serpent.Command{
Use: "list",
Expand Down
41 changes: 35 additions & 6 deletionssupport/support.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,8 @@ import (
"net/http/httptest"
"strings"

"github.com/coder/coder/v2/cli/cliutil"

"github.com/google/uuid"
"golang.org/x/sync/errgroup"
"golang.org/x/xerrors"
Expand All@@ -30,12 +32,13 @@ import (
// Even though we do attempt to sanitize data, it may still contain
// sensitive information and should thus be treated as secret.
type Bundle struct {
Deployment Deployment `json:"deployment"`
Network Network `json:"network"`
Workspace Workspace `json:"workspace"`
Agent Agent `json:"agent"`
Logs []string `json:"logs"`
CLILogs []byte `json:"cli_logs"`
Deployment Deployment `json:"deployment"`
Network Network `json:"network"`
Workspace Workspace `json:"workspace"`
Agent Agent `json:"agent"`
LicenseStatus string
Logs []string `json:"logs"`
CLILogs []byte `json:"cli_logs"`
}

type Deployment struct {
Expand DownExpand Up@@ -351,6 +354,27 @@ func AgentInfo(ctx context.Context, client *codersdk.Client, log slog.Logger, ag
return a
}

func LicenseStatus(ctx context.Context, client *codersdk.Client, log slog.Logger) string {
licenses, err := client.Licenses(ctx)
if err != nil {
log.Warn(ctx, "fetch licenses", slog.Error(err))
return "No licenses found"
}
// Ensure that we print "[]" instead of "null" when there are no licenses.
if licenses == nil {
licenses = make([]codersdk.License, 0)
}

formatter := cliutil.NewLicenseFormatter(cliutil.LicenseFormatterOpts{
Sanitize: true,
})
out, err := formatter.Format(ctx, licenses)
if err != nil {
log.Error(ctx, "format licenses", slog.Error(err))
}
return out
}

func connectedAgentInfo(ctx context.Context, client *codersdk.Client, log slog.Logger, agentID uuid.UUID, eg *errgroup.Group, a *Agent) (closer func()) {
conn, err := workspacesdk.New(client).
DialAgent(ctx, agentID, &workspacesdk.DialAgentOptions{
Expand DownExpand Up@@ -510,6 +534,11 @@ func Run(ctx context.Context, d *Deps) (*Bundle, error) {
b.Agent = ai
return nil
})
eg.Go(func() error {
ls := LicenseStatus(ctx, d.Client, d.Log)
b.LicenseStatus = ls
return nil
})

_ = eg.Wait()

Expand Down
1 change: 1 addition & 0 deletionssupport/support_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -87,6 +87,7 @@ func TestRun(t *testing.T) {
assertNotNilNotEmpty(t, bun.Agent.Prometheus, "agent prometheus metrics should be present")
assertNotNilNotEmpty(t, bun.Agent.StartupLogs, "agent startup logs should be present")
assertNotNilNotEmpty(t, bun.Logs, "bundle logs should be present")
assertNotNilNotEmpty(t, bun.LicenseStatus, "license status should be present")
})

t.Run("OK_NoWorkspace", func(t *testing.T) {
Expand Down
Loading

[8]ページ先頭

©2009-2025 Movatter.jp