Skip to content

Commit 428f3f5

Browse files
committed
merge main
2 parents 839a52e + d52bc91 commit 428f3f5

31 files changed

+1047
-680
lines changed

cli/completion.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package cli
33
import (
44
"fmt"
55

6+
"golang.org/x/xerrors"
7+
68
"github.com/coder/coder/v2/cli/cliui"
79
"github.com/coder/serpent"
810
"github.com/coder/serpent/completion"
@@ -45,7 +47,10 @@ func (*RootCmd) completion() *serpent.Command {
4547
if err == nil {
4648
return installCompletion(inv, shell)
4749
}
48-
// Silently continue to the shell selection if detecting failed.
50+
if !isTTYOut(inv) {
51+
return xerrors.New("could not detect the current shell, please specify one with --shell or run interactively")
52+
}
53+
// Silently continue to the shell selection if detecting failed in interactive mode
4954
choice, err := cliui.Select(inv, cliui.SelectOptions{
5055
Message: "Select a shell to install completion for:",
5156
Options: shellOptions.Choices,
@@ -71,6 +76,9 @@ func installCompletion(inv *serpent.Invocation, shell completion.Shell) error {
7176
cliui.Error(inv.Stderr, fmt.Sprintf("Failed to determine completion path %v", err))
7277
return shell.WriteCompletion(inv.Stdout)
7378
}
79+
if !isTTYOut(inv) {
80+
return shell.WriteCompletion(inv.Stdout)
81+
}
7482
choice, err := cliui.Select(inv, cliui.SelectOptions{
7583
Options: []string{
7684
"Confirm",

cli/support.go

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -184,16 +184,8 @@ func (r *RootCmd) supportBundle() *serpent.Command {
184184
_ = os.Remove(outputPath) // best effort
185185
return xerrors.Errorf("create support bundle: %w", err)
186186
}
187-
docsURL := bun.Deployment.Config.Values.DocsURL.String()
188-
deployHealthSummary := bun.Deployment.HealthReport.Summarize(docsURL)
189-
if len(deployHealthSummary) > 0 {
190-
cliui.Warn(inv.Stdout, "Deployment health issues detected:", deployHealthSummary...)
191-
}
192-
clientNetcheckSummary := bun.Network.Netcheck.Summarize("Client netcheck:", docsURL)
193-
if len(clientNetcheckSummary) > 0 {
194-
cliui.Warn(inv.Stdout, "Networking issues detected:", deployHealthSummary...)
195-
}
196187

188+
summarizeBundle(inv, bun)
197189
bun.CLILogs = cliLogBuf.Bytes()
198190

199191
if err := writeBundle(bun, zwr); err != nil {
@@ -225,6 +217,40 @@ func (r *RootCmd) supportBundle() *serpent.Command {
225217
return cmd
226218
}
227219

220+
// summarizeBundle makes a best-effort attempt to write a short summary
221+
// of the support bundle to the user's terminal.
222+
func summarizeBundle(inv *serpent.Invocation, bun *support.Bundle) {
223+
if bun == nil {
224+
cliui.Error(inv.Stdout, "No support bundle generated!")
225+
return
226+
}
227+
228+
if bun.Deployment.Config == nil {
229+
cliui.Error(inv.Stdout, "No deployment configuration available!")
230+
return
231+
}
232+
233+
docsURL := bun.Deployment.Config.Values.DocsURL.String()
234+
if bun.Deployment.HealthReport == nil {
235+
cliui.Error(inv.Stdout, "No deployment health report available!")
236+
return
237+
}
238+
deployHealthSummary := bun.Deployment.HealthReport.Summarize(docsURL)
239+
if len(deployHealthSummary) > 0 {
240+
cliui.Warn(inv.Stdout, "Deployment health issues detected:", deployHealthSummary...)
241+
}
242+
243+
if bun.Network.Netcheck == nil {
244+
cliui.Error(inv.Stdout, "No network troubleshooting information available!")
245+
return
246+
}
247+
248+
clientNetcheckSummary := bun.Network.Netcheck.Summarize("Client netcheck:", docsURL)
249+
if len(clientNetcheckSummary) > 0 {
250+
cliui.Warn(inv.Stdout, "Networking issues detected:", deployHealthSummary...)
251+
}
252+
}
253+
228254
func findAgent(agentName string, haystack []codersdk.WorkspaceResource) (*codersdk.WorkspaceAgent, bool) {
229255
for _, res := range haystack {
230256
for _, agt := range res.Agents {

cli/support_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import (
55
"bytes"
66
"encoding/json"
77
"io"
8+
"net/http"
9+
"net/http/httptest"
10+
"net/url"
811
"os"
912
"path/filepath"
1013
"runtime"
@@ -14,6 +17,7 @@ import (
1417
"tailscale.com/ipn/ipnstate"
1518

1619
"github.com/google/uuid"
20+
"github.com/stretchr/testify/assert"
1721
"github.com/stretchr/testify/require"
1822

1923
"github.com/coder/coder/v2/agent"
@@ -156,6 +160,53 @@ func TestSupportBundle(t *testing.T) {
156160
err := inv.Run()
157161
require.ErrorContains(t, err, "failed authorization check")
158162
})
163+
164+
// This ensures that the CLI does not panic when trying to generate a support bundle
165+
// against a fake server that returns an empty response for all requests. This essentially
166+
// ensures that (almost) all of the support bundle generating code paths get a zero value.
167+
t.Run("DontPanic", func(t *testing.T) {
168+
t.Parallel()
169+
170+
for _, code := range []int{
171+
http.StatusOK,
172+
http.StatusUnauthorized,
173+
http.StatusForbidden,
174+
http.StatusNotFound,
175+
http.StatusInternalServerError,
176+
} {
177+
t.Run(http.StatusText(code), func(t *testing.T) {
178+
t.Parallel()
179+
// Start up a fake server
180+
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
181+
t.Logf("received request: %s %s", r.Method, r.URL)
182+
switch r.URL.Path {
183+
case "/api/v2/authcheck":
184+
// Fake auth check
185+
resp := codersdk.AuthorizationResponse{
186+
"Read DeploymentValues": true,
187+
}
188+
w.WriteHeader(http.StatusOK)
189+
assert.NoError(t, json.NewEncoder(w).Encode(resp))
190+
default:
191+
// Simply return a blank response for everything else.
192+
w.WriteHeader(code)
193+
}
194+
}))
195+
defer srv.Close()
196+
u, err := url.Parse(srv.URL)
197+
require.NoError(t, err)
198+
client := codersdk.New(u)
199+
200+
d := t.TempDir()
201+
path := filepath.Join(d, "bundle.zip")
202+
203+
inv, root := clitest.New(t, "support", "bundle", "--url-override", srv.URL, "--output-file", path, "--yes")
204+
clitest.SetupConfig(t, client, root)
205+
err = inv.Run()
206+
require.NoError(t, err)
207+
})
208+
}
209+
})
159210
}
160211

161212
// nolint:revive // It's a control flag, but this is just a test.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
DELETE FROM notification_templates WHERE id = 'b02ddd82-4733-4d02-a2d7-c36f3598997d';
2+
DELETE FROM notification_templates WHERE id = '6a2f0609-9b69-4d36-a989-9f5925b6cbff';
3+
DELETE FROM notification_templates WHERE id = '9f5af851-8408-4e73-a7a1-c6502ba46689';
4+
DELETE FROM notification_templates WHERE id = '1a6a6bea-ee0a-43e2-9e7c-eabdb53730e4';
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
INSERT INTO notification_templates (id, name, title_template, body_template, "group", actions)
2+
VALUES ('b02ddd82-4733-4d02-a2d7-c36f3598997d', 'User account suspended', E'User account "{{.Labels.suspended_account_name}}" suspended',
3+
E'Hi {{.UserName}},\nUser account **{{.Labels.suspended_account_name}}** has been suspended.',
4+
'User Events', '[
5+
{
6+
"label": "View suspended accounts",
7+
"url": "{{ base_url }}/deployment/users?filter=status%3Asuspended"
8+
}
9+
]'::jsonb);
10+
INSERT INTO notification_templates (id, name, title_template, body_template, "group", actions)
11+
VALUES ('6a2f0609-9b69-4d36-a989-9f5925b6cbff', 'Your account has been suspended', E'Your account "{{.Labels.suspended_account_name}}" has been suspended',
12+
E'Hi {{.UserName}},\nYour account **{{.Labels.suspended_account_name}}** has been suspended.',
13+
'User Events', '[]'::jsonb);
14+
INSERT INTO notification_templates (id, name, title_template, body_template, "group", actions)
15+
VALUES ('9f5af851-8408-4e73-a7a1-c6502ba46689', 'User account activated', E'User account "{{.Labels.activated_account_name}}" activated',
16+
E'Hi {{.UserName}},\nUser account **{{.Labels.activated_account_name}}** has been activated.',
17+
'User Events', '[
18+
{
19+
"label": "View accounts",
20+
"url": "{{ base_url }}/deployment/users?filter=status%3Aactive"
21+
}
22+
]'::jsonb);
23+
INSERT INTO notification_templates (id, name, title_template, body_template, "group", actions)
24+
VALUES ('1a6a6bea-ee0a-43e2-9e7c-eabdb53730e4', 'Your account has been activated', E'Your account "{{.Labels.activated_account_name}}" has been activated',
25+
E'Hi {{.UserName}},\nYour account **{{.Labels.activated_account_name}}** has been activated.',
26+
'User Events', '[
27+
{
28+
"label": "Open Coder",
29+
"url": "{{ base_url }}"
30+
}
31+
]'::jsonb);

coderd/notifications/events.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ var (
1818
var (
1919
TemplateUserAccountCreated = uuid.MustParse("4e19c0ac-94e1-4532-9515-d1801aa283b2")
2020
TemplateUserAccountDeleted = uuid.MustParse("f44d9314-ad03-4bc8-95d0-5cad491da6b6")
21+
22+
TemplateUserAccountSuspended = uuid.MustParse("b02ddd82-4733-4d02-a2d7-c36f3598997d")
23+
TemplateUserAccountActivated = uuid.MustParse("9f5af851-8408-4e73-a7a1-c6502ba46689")
24+
TemplateYourAccountSuspended = uuid.MustParse("6a2f0609-9b69-4d36-a989-9f5925b6cbff")
25+
TemplateYourAccountActivated = uuid.MustParse("1a6a6bea-ee0a-43e2-9e7c-eabdb53730e4")
2126
)
2227

2328
// Template-related events.

coderd/notifications/notifications_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -756,6 +756,46 @@ func TestNotificationTemplatesCanRender(t *testing.T) {
756756
},
757757
},
758758
},
759+
{
760+
name: "TemplateUserAccountSuspended",
761+
id: notifications.TemplateUserAccountSuspended,
762+
payload: types.MessagePayload{
763+
UserName: "bobby",
764+
Labels: map[string]string{
765+
"suspended_account_name": "bobby",
766+
},
767+
},
768+
},
769+
{
770+
name: "TemplateUserAccountActivated",
771+
id: notifications.TemplateUserAccountActivated,
772+
payload: types.MessagePayload{
773+
UserName: "bobby",
774+
Labels: map[string]string{
775+
"activated_account_name": "bobby",
776+
},
777+
},
778+
},
779+
{
780+
name: "TemplateYourAccountSuspended",
781+
id: notifications.TemplateYourAccountSuspended,
782+
payload: types.MessagePayload{
783+
UserName: "bobby",
784+
Labels: map[string]string{
785+
"suspended_account_name": "bobby",
786+
},
787+
},
788+
},
789+
{
790+
name: "TemplateYourAccountActivated",
791+
id: notifications.TemplateYourAccountActivated,
792+
payload: types.MessagePayload{
793+
UserName: "bobby",
794+
Labels: map[string]string{
795+
"activated_account_name": "bobby",
796+
},
797+
},
798+
},
759799
{
760800
name: "TemplateTemplateDeleted",
761801
id: notifications.TemplateTemplateDeleted,

0 commit comments

Comments
 (0)