Skip to content

Commit 778616b

Browse files
committed
feat(cli): add support command and accompanying unit tests
1 parent 99ad0db commit 778616b

File tree

8 files changed

+193
-0
lines changed

8 files changed

+193
-0
lines changed

cli/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ func (r *RootCmd) Core() []*clibase.Cmd {
123123
r.vscodeSSH(),
124124
r.workspaceAgent(),
125125
r.expCmd(),
126+
r.support(),
126127
}
127128
}
128129

cli/support.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package cli
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
7+
"golang.org/x/xerrors"
8+
9+
"cdr.dev/slog"
10+
"cdr.dev/slog/sloggers/sloghuman"
11+
"github.com/coder/coder/v2/cli/clibase"
12+
"github.com/coder/coder/v2/codersdk"
13+
"github.com/coder/coder/v2/support"
14+
)
15+
16+
func (r *RootCmd) support() *clibase.Cmd {
17+
client := new(codersdk.Client)
18+
return &clibase.Cmd{
19+
Use: "support <workspace> [<agent>]",
20+
Short: "Generate a support bundle to troubleshoot issues.",
21+
Long: `This command generates a file containing detailed troubleshooting information about the Coder deployment and workspace connections. You must specify a single workspace (and optionally an agent name).`,
22+
Middleware: clibase.Chain(
23+
clibase.RequireRangeArgs(0, 2),
24+
r.InitClient(client),
25+
),
26+
Handler: func(inv *clibase.Invocation) error {
27+
log := slog.Make(sloghuman.Sink(inv.Stdout))
28+
deps := support.Deps{
29+
Client: client,
30+
Log: log,
31+
}
32+
33+
if len(inv.Args) == 0 {
34+
return xerrors.Errorf("must specify workspace name")
35+
}
36+
ws, err := namedWorkspace(inv.Context(), client, inv.Args[0])
37+
if err != nil {
38+
return err
39+
}
40+
41+
deps.WorkspaceID = ws.ID
42+
43+
agentName := ""
44+
if len(inv.Args) > 1 {
45+
agentName = inv.Args[1]
46+
}
47+
48+
agt, found := findAgent(agentName, ws.LatestBuild.Resources)
49+
if !found {
50+
return xerrors.Errorf("could not find any agent for workspace")
51+
}
52+
53+
deps.AgentID = agt.ID
54+
55+
bun, err := support.Run(inv.Context(), &deps)
56+
if err != nil {
57+
return err
58+
}
59+
60+
bs, err := json.MarshalIndent(bun, "", " ")
61+
if err != nil {
62+
return err
63+
}
64+
_, _ = fmt.Fprintln(inv.Stdout, string(bs))
65+
return nil
66+
},
67+
}
68+
}
69+
70+
func findAgent(agentName string, haystack []codersdk.WorkspaceResource) (*codersdk.WorkspaceAgent, bool) {
71+
for _, res := range haystack {
72+
for _, agt := range res.Agents {
73+
if agentName == "" {
74+
// just return the first
75+
return &agt, true
76+
}
77+
if agt.Name == agentName {
78+
return &agt, true
79+
}
80+
}
81+
}
82+
return nil, false
83+
}

cli/support_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package cli_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
8+
"github.com/coder/coder/v2/cli/clitest"
9+
"github.com/coder/coder/v2/coderd/coderdtest"
10+
"github.com/coder/coder/v2/coderd/database"
11+
"github.com/coder/coder/v2/coderd/database/dbfake"
12+
)
13+
14+
func TestSupport(t *testing.T) {
15+
t.Parallel()
16+
17+
t.Run("Workspace", func(t *testing.T) {
18+
t.Parallel()
19+
client, db := coderdtest.NewWithDatabase(t, nil)
20+
owner := coderdtest.CreateFirstUser(t, client)
21+
r := dbfake.WorkspaceBuild(t, db, database.Workspace{
22+
OrganizationID: owner.OrganizationID,
23+
OwnerID: owner.UserID,
24+
}).WithAgent().Do()
25+
inv, root := clitest.New(t, "support", r.Workspace.Name)
26+
//nolint: gocritic // requires owner privilege
27+
clitest.SetupConfig(t, client, root)
28+
err := inv.Run()
29+
require.NoError(t, err)
30+
})
31+
32+
t.Run("NoWorkspace", func(t *testing.T) {
33+
t.Parallel()
34+
client := coderdtest.New(t, nil)
35+
_ = coderdtest.CreateFirstUser(t, client)
36+
inv, root := clitest.New(t, "support")
37+
//nolint: gocritic // requires owner privilege
38+
clitest.SetupConfig(t, client, root)
39+
err := inv.Run()
40+
require.ErrorContains(t, err, "must specify workspace name")
41+
})
42+
43+
t.Run("NoAgent", func(t *testing.T) {
44+
t.Parallel()
45+
client, db := coderdtest.NewWithDatabase(t, nil)
46+
admin := coderdtest.CreateFirstUser(t, client)
47+
r := dbfake.WorkspaceBuild(t, db, database.Workspace{
48+
OrganizationID: admin.OrganizationID,
49+
OwnerID: admin.UserID,
50+
}).Do() // without agent!
51+
inv, root := clitest.New(t, "support", r.Workspace.Name)
52+
//nolint: gocritic // requires owner privilege
53+
clitest.SetupConfig(t, client, root)
54+
err := inv.Run()
55+
require.ErrorContains(t, err, "could not find any agent for workspace")
56+
})
57+
58+
t.Run("NoPrivilege", func(t *testing.T) {
59+
t.Parallel()
60+
client, db := coderdtest.NewWithDatabase(t, nil)
61+
user := coderdtest.CreateFirstUser(t, client)
62+
memberClient, member := coderdtest.CreateAnotherUser(t, client, user.OrganizationID)
63+
r := dbfake.WorkspaceBuild(t, db, database.Workspace{
64+
OrganizationID: user.OrganizationID,
65+
OwnerID: member.ID,
66+
}).WithAgent().Do()
67+
inv, root := clitest.New(t, "support", r.Workspace.Name)
68+
clitest.SetupConfig(t, memberClient, root)
69+
err := inv.Run()
70+
require.ErrorContains(t, err, "failed authorization check")
71+
})
72+
}

cli/testdata/coder_--help.golden

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ SUBCOMMANDS:
4646
stat Show resource usage for the current workspace.
4747
state Manually manage Terraform state to fix broken workspaces
4848
stop Stop a workspace
49+
support Generate a support bundle to troubleshoot issues.
4950
templates Manage templates
5051
tokens Manage personal access tokens
5152
unfavorite Remove a workspace from your favorites
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
coder v0.0.0-devel
2+
3+
USAGE:
4+
coder support <workspace> [<agent>]
5+
6+
Generate a support bundle to troubleshoot issues.
7+
8+
This command generates a file containing detailed troubleshooting information
9+
about the Coder deployment and workspace connections. You must specify a
10+
single workspace (and optionally an agent name).
11+
12+
———
13+
Run `coder --help` for a list of global options.

docs/cli.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ Coder — A tool for provisioning self-hosted development environments with Terr
5656
| [<code>stat</code>](./cli/stat.md) | Show resource usage for the current workspace. |
5757
| [<code>state</code>](./cli/state.md) | Manually manage Terraform state to fix broken workspaces |
5858
| [<code>stop</code>](./cli/stop.md) | Stop a workspace |
59+
| [<code>support</code>](./cli/support.md) | Generate a support bundle to troubleshoot issues. |
5960
| [<code>templates</code>](./cli/templates.md) | Manage templates |
6061
| [<code>tokens</code>](./cli/tokens.md) | Manage personal access tokens |
6162
| [<code>unfavorite</code>](./cli/unfavorite.md) | Remove a workspace from your favorites |

docs/cli/support.md

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/manifest.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -880,6 +880,11 @@
880880
"description": "Stop a workspace",
881881
"path": "cli/stop.md"
882882
},
883+
{
884+
"title": "support",
885+
"description": "Generate a support bundle to troubleshoot issues.",
886+
"path": "cli/support.md"
887+
},
883888
{
884889
"title": "templates",
885890
"description": "Manage templates",

0 commit comments

Comments
 (0)