Skip to content

Commit bdc08d6

Browse files
committed
Merge branch 'main' of https://github.com/coder/coder into bq/implement-notifications
2 parents a445c4c + 1691768 commit bdc08d6

File tree

178 files changed

+2835
-865
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

178 files changed

+2835
-865
lines changed

Makefile

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -448,8 +448,7 @@ lint/ts:
448448
lint/go:
449449
./scripts/check_enterprise_imports.sh
450450
linter_ver=$(shell egrep -o 'GOLANGCI_LINT_VERSION=\S+' dogfood/Dockerfile | cut -d '=' -f 2)
451-
go install github.com/golangci/golangci-lint/cmd/golangci-lint@v$$linter_ver
452-
golangci-lint run
451+
go run github.com/golangci/golangci-lint/cmd/golangci-lint@v$$linter_ver run
453452
.PHONY: lint/go
454453

455454
lint/examples:

agent/agentscripts/agentscripts.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ func (r *Runner) run(ctx context.Context, script codersdk.WorkspaceAgentScript)
349349
"This usually means a child process was started with references to stdout or stderr. As a result, this " +
350350
"process may now have been terminated. Consider redirecting the output or using a separate " +
351351
"\"coder_script\" for the process, see " +
352-
"https://coder.com/docs/v2/latest/templates/troubleshooting#startup-script-issues for more information.",
352+
"https://coder.com/docs/templates/troubleshooting#startup-script-issues for more information.",
353353
)
354354
// Inform the user by propagating the message via log writers.
355355
_, _ = fmt.Fprintf(cmd.Stderr, "WARNING: %s. %s\n", message, details)

apiversion/apiversion.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ type APIVersion struct {
2626
}
2727

2828
func (v *APIVersion) WithBackwardCompat(majs ...int) *APIVersion {
29-
v.additionalMajors = append(v.additionalMajors, majs[:]...)
29+
v.additionalMajors = append(v.additionalMajors, majs...)
3030
return v
3131
}
3232

cli/cliui/agent.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ func Agent(ctx context.Context, writer io.Writer, agentID uuid.UUID, opts AgentO
116116
if agent.Status == codersdk.WorkspaceAgentTimeout {
117117
now := time.Now()
118118
sw.Log(now, codersdk.LogLevelInfo, "The workspace agent is having trouble connecting, wait for it to connect or restart your workspace.")
119-
sw.Log(now, codersdk.LogLevelInfo, troubleshootingMessage(agent, "https://coder.com/docs/v2/latest/templates#agent-connection-issues"))
119+
sw.Log(now, codersdk.LogLevelInfo, troubleshootingMessage(agent, "https://coder.com/docs/templates#agent-connection-issues"))
120120
for agent.Status == codersdk.WorkspaceAgentTimeout {
121121
if agent, err = fetch(); err != nil {
122122
return xerrors.Errorf("fetch: %w", err)
@@ -221,13 +221,13 @@ func Agent(ctx context.Context, writer io.Writer, agentID uuid.UUID, opts AgentO
221221
sw.Fail(stage, safeDuration(sw, agent.ReadyAt, agent.StartedAt))
222222
// Use zero time (omitted) to separate these from the startup logs.
223223
sw.Log(time.Time{}, codersdk.LogLevelWarn, "Warning: A startup script exited with an error and your workspace may be incomplete.")
224-
sw.Log(time.Time{}, codersdk.LogLevelWarn, troubleshootingMessage(agent, "https://coder.com/docs/v2/latest/templates/troubleshooting#startup-script-exited-with-an-error"))
224+
sw.Log(time.Time{}, codersdk.LogLevelWarn, troubleshootingMessage(agent, "https://coder.com/docs/templates/troubleshooting#startup-script-exited-with-an-error"))
225225
default:
226226
switch {
227227
case agent.LifecycleState.Starting():
228228
// Use zero time (omitted) to separate these from the startup logs.
229229
sw.Log(time.Time{}, codersdk.LogLevelWarn, "Notice: The startup scripts are still running and your workspace may be incomplete.")
230-
sw.Log(time.Time{}, codersdk.LogLevelWarn, troubleshootingMessage(agent, "https://coder.com/docs/v2/latest/templates/troubleshooting#your-workspace-may-be-incomplete"))
230+
sw.Log(time.Time{}, codersdk.LogLevelWarn, troubleshootingMessage(agent, "https://coder.com/docs/templates/troubleshooting#your-workspace-may-be-incomplete"))
231231
// Note: We don't complete or fail the stage here, it's
232232
// intentionally left open to indicate this stage didn't
233233
// complete.
@@ -249,7 +249,7 @@ func Agent(ctx context.Context, writer io.Writer, agentID uuid.UUID, opts AgentO
249249
stage := "The workspace agent lost connection"
250250
sw.Start(stage)
251251
sw.Log(time.Now(), codersdk.LogLevelWarn, "Wait for it to reconnect or restart your workspace.")
252-
sw.Log(time.Now(), codersdk.LogLevelWarn, troubleshootingMessage(agent, "https://coder.com/docs/v2/latest/templates/troubleshooting#agent-connection-issues"))
252+
sw.Log(time.Now(), codersdk.LogLevelWarn, troubleshootingMessage(agent, "https://coder.com/docs/templates/troubleshooting#agent-connection-issues"))
253253

254254
disconnectedAt := agent.DisconnectedAt
255255
for agent.Status == codersdk.WorkspaceAgentDisconnected {

cli/dotfiles.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ func (r *RootCmd) dotfiles() *serpent.Command {
204204
}
205205

206206
if fi.Mode()&0o111 == 0 {
207-
return xerrors.Errorf("script %q is not executable. See https://coder.com/docs/v2/latest/dotfiles for information on how to resolve the issue.", script)
207+
return xerrors.Errorf("script %q is not executable. See https://coder.com/docs/dotfiles for information on how to resolve the issue.", script)
208208
}
209209

210210
// it is safe to use a variable command here because it's from

cli/notifications.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package cli
2+
3+
import (
4+
"fmt"
5+
6+
"golang.org/x/xerrors"
7+
8+
"github.com/coder/serpent"
9+
10+
"github.com/coder/coder/v2/codersdk"
11+
)
12+
13+
func (r *RootCmd) notifications() *serpent.Command {
14+
cmd := &serpent.Command{
15+
Use: "notifications",
16+
Short: "Manage Coder notifications",
17+
Long: "Administrators can use these commands to change notification settings.\n" + FormatExamples(
18+
Example{
19+
Description: "Pause Coder notifications. Administrators can temporarily stop notifiers from dispatching messages in case of the target outage (for example: unavailable SMTP server or Webhook not responding).",
20+
Command: "coder notifications pause",
21+
},
22+
Example{
23+
Description: "Resume Coder notifications",
24+
Command: "coder notifications resume",
25+
},
26+
),
27+
Aliases: []string{"notification"},
28+
Handler: func(inv *serpent.Invocation) error {
29+
return inv.Command.HelpHandler(inv)
30+
},
31+
Children: []*serpent.Command{
32+
r.pauseNotifications(),
33+
r.resumeNotifications(),
34+
},
35+
}
36+
return cmd
37+
}
38+
39+
func (r *RootCmd) pauseNotifications() *serpent.Command {
40+
client := new(codersdk.Client)
41+
cmd := &serpent.Command{
42+
Use: "pause",
43+
Short: "Pause notifications",
44+
Middleware: serpent.Chain(
45+
serpent.RequireNArgs(0),
46+
r.InitClient(client),
47+
),
48+
Handler: func(inv *serpent.Invocation) error {
49+
err := client.PutNotificationsSettings(inv.Context(), codersdk.NotificationsSettings{
50+
NotifierPaused: true,
51+
})
52+
if err != nil {
53+
return xerrors.Errorf("unable to pause notifications: %w", err)
54+
}
55+
56+
_, _ = fmt.Fprintln(inv.Stderr, "Notifications are now paused.")
57+
return nil
58+
},
59+
}
60+
return cmd
61+
}
62+
63+
func (r *RootCmd) resumeNotifications() *serpent.Command {
64+
client := new(codersdk.Client)
65+
cmd := &serpent.Command{
66+
Use: "resume",
67+
Short: "Resume notifications",
68+
Middleware: serpent.Chain(
69+
serpent.RequireNArgs(0),
70+
r.InitClient(client),
71+
),
72+
Handler: func(inv *serpent.Invocation) error {
73+
err := client.PutNotificationsSettings(inv.Context(), codersdk.NotificationsSettings{
74+
NotifierPaused: false,
75+
})
76+
if err != nil {
77+
return xerrors.Errorf("unable to resume notifications: %w", err)
78+
}
79+
80+
_, _ = fmt.Fprintln(inv.Stderr, "Notifications are now resumed.")
81+
return nil
82+
},
83+
}
84+
return cmd
85+
}

cli/notifications_test.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package cli_test
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"encoding/json"
7+
"net/http"
8+
"testing"
9+
10+
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
12+
13+
"github.com/coder/coder/v2/cli/clitest"
14+
"github.com/coder/coder/v2/coderd/coderdtest"
15+
"github.com/coder/coder/v2/codersdk"
16+
"github.com/coder/coder/v2/testutil"
17+
)
18+
19+
func TestNotifications(t *testing.T) {
20+
t.Parallel()
21+
22+
tests := []struct {
23+
name string
24+
command string
25+
expectPaused bool
26+
}{
27+
{
28+
name: "PauseNotifications",
29+
command: "pause",
30+
expectPaused: true,
31+
},
32+
{
33+
name: "ResumeNotifications",
34+
command: "resume",
35+
expectPaused: false,
36+
},
37+
}
38+
39+
for _, tt := range tests {
40+
tt := tt
41+
t.Run(tt.name, func(t *testing.T) {
42+
t.Parallel()
43+
44+
// given
45+
ownerClient, db := coderdtest.NewWithDatabase(t, nil)
46+
_ = coderdtest.CreateFirstUser(t, ownerClient)
47+
48+
// when
49+
inv, root := clitest.New(t, "notifications", tt.command)
50+
clitest.SetupConfig(t, ownerClient, root)
51+
52+
var buf bytes.Buffer
53+
inv.Stdout = &buf
54+
err := inv.Run()
55+
require.NoError(t, err)
56+
57+
// then
58+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
59+
t.Cleanup(cancel)
60+
settingsJSON, err := db.GetNotificationsSettings(ctx)
61+
require.NoError(t, err)
62+
63+
var settings codersdk.NotificationsSettings
64+
err = json.Unmarshal([]byte(settingsJSON), &settings)
65+
require.NoError(t, err)
66+
require.Equal(t, tt.expectPaused, settings.NotifierPaused)
67+
})
68+
}
69+
}
70+
71+
func TestPauseNotifications_RegularUser(t *testing.T) {
72+
t.Parallel()
73+
74+
// given
75+
ownerClient, db := coderdtest.NewWithDatabase(t, nil)
76+
owner := coderdtest.CreateFirstUser(t, ownerClient)
77+
anotherClient, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID)
78+
79+
// when
80+
inv, root := clitest.New(t, "notifications", "pause")
81+
clitest.SetupConfig(t, anotherClient, root)
82+
83+
var buf bytes.Buffer
84+
inv.Stdout = &buf
85+
err := inv.Run()
86+
var sdkError *codersdk.Error
87+
require.Error(t, err)
88+
require.ErrorAsf(t, err, &sdkError, "error should be of type *codersdk.Error")
89+
assert.Equal(t, http.StatusForbidden, sdkError.StatusCode())
90+
assert.Contains(t, sdkError.Message, "Insufficient permissions to update notifications settings.")
91+
92+
// then
93+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
94+
t.Cleanup(cancel)
95+
settingsJSON, err := db.GetNotificationsSettings(ctx)
96+
require.NoError(t, err)
97+
98+
var settings codersdk.NotificationsSettings
99+
err = json.Unmarshal([]byte(settingsJSON), &settings)
100+
require.NoError(t, err)
101+
require.False(t, settings.NotifierPaused) // still running
102+
}

cli/root.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ func (r *RootCmd) CoreSubcommands() []*serpent.Command {
8787
r.login(),
8888
r.logout(),
8989
r.netcheck(),
90+
r.notifications(),
91+
r.organizations(),
9092
r.portForward(),
9193
r.publickey(),
9294
r.resetPassword(),
@@ -95,7 +97,6 @@ func (r *RootCmd) CoreSubcommands() []*serpent.Command {
9597
r.tokens(),
9698
r.users(),
9799
r.version(defaultVersionInfo),
98-
r.organizations(),
99100

100101
// Workspace Commands
101102
r.autoupdate(),
@@ -120,11 +121,11 @@ func (r *RootCmd) CoreSubcommands() []*serpent.Command {
120121
r.whoami(),
121122

122123
// Hidden
124+
r.expCmd(),
123125
r.gitssh(),
126+
r.support(),
124127
r.vscodeSSH(),
125128
r.workspaceAgent(),
126-
r.expCmd(),
127-
r.support(),
128129
}
129130
}
130131

cli/server.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -838,7 +838,7 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
838838
}
839839
defer options.Telemetry.Close()
840840
} else {
841-
logger.Warn(ctx, `telemetry disabled, unable to notify of security issues. Read more: https://coder.com/docs/v2/latest/admin/telemetry`)
841+
logger.Warn(ctx, `telemetry disabled, unable to notify of security issues. Read more: https://coder.com/docs/admin/telemetry`)
842842
}
843843

844844
// This prevents the pprof import from being accidentally deleted.
@@ -983,6 +983,7 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
983983
)
984984
if experiments.Enabled(codersdk.ExperimentNotifications) {
985985
cfg := options.DeploymentValues.Notifications
986+
metrics := notifications.NewMetrics(options.PrometheusRegistry)
986987

987988
// The enqueuer is responsible for enqueueing notifications to the given store.
988989
enqueuer, err := notifications.NewStoreEnqueuer(cfg, options.Database, templateHelpers(options), logger.Named("notifications.enqueuer"))
@@ -994,7 +995,7 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
994995
// The notification manager is responsible for:
995996
// - creating notifiers and managing their lifecycles (notifiers are responsible for dequeueing/sending notifications)
996997
// - keeping the store updated with status updates
997-
notificationsManager, err = notifications.NewManager(cfg, options.Database, logger.Named("notifications.manager"))
998+
notificationsManager, err = notifications.NewManager(cfg, options.Database, metrics, logger.Named("notifications.manager"))
998999
if err != nil {
9991000
return xerrors.Errorf("failed to instantiate notification manager: %w", err)
10001001
}

cli/templatelist_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,9 @@ func TestTemplateList(t *testing.T) {
122122
_ = coderdtest.CreateTemplate(t, client, owner.OrganizationID, firstVersion.ID)
123123

124124
secondOrg := coderdtest.CreateOrganization(t, client, coderdtest.CreateOrganizationOptions{
125-
IncludeProvisionerDaemon: true,
125+
// Listing templates does not require the template actually completes.
126+
// We cannot provision an external provisioner in AGPL tests.
127+
IncludeProvisionerDaemon: false,
126128
})
127129
secondVersion := coderdtest.CreateTemplateVersion(t, client, secondOrg.ID, nil)
128130
_ = coderdtest.CreateTemplate(t, client, secondOrg.ID, secondVersion.ID)

0 commit comments

Comments
 (0)