Skip to content

Commit b6b302f

Browse files
committed
feat: system-generated notifications
1 parent 25636ec commit b6b302f

35 files changed

+3369
-13
lines changed

.golangci.yaml

+5
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,11 @@ linters-settings:
195195
- name: var-naming
196196
- name: waitgroup-by-value
197197

198+
# irrelevant as of Go v1.22: https://go.dev/blog/loopvar-preview
199+
govet:
200+
disable:
201+
- loopclosure
202+
198203
issues:
199204
# Rules listed here: https://github.com/securego/gosec#available-rules
200205
exclude-rules:

cli/server.go

+50-2
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ import (
5353
"gopkg.in/yaml.v3"
5454
"tailscale.com/tailcfg"
5555

56+
"github.com/coder/coder/v2/coderd/database/dbauthz"
57+
5658
"cdr.dev/slog"
5759
"cdr.dev/slog/sloggers/sloghuman"
5860
"github.com/coder/coder/v2/buildinfo"
@@ -73,6 +75,7 @@ import (
7375
"github.com/coder/coder/v2/coderd/externalauth"
7476
"github.com/coder/coder/v2/coderd/gitsshkey"
7577
"github.com/coder/coder/v2/coderd/httpmw"
78+
"github.com/coder/coder/v2/coderd/notifications"
7679
"github.com/coder/coder/v2/coderd/oauthpki"
7780
"github.com/coder/coder/v2/coderd/prometheusmetrics"
7881
"github.com/coder/coder/v2/coderd/prometheusmetrics/insights"
@@ -660,6 +663,10 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
660663
options.OIDCConfig = oc
661664
}
662665

666+
experiments := coderd.ReadExperiments(
667+
options.Logger, options.DeploymentValues.Experiments.Value(),
668+
)
669+
663670
// We'll read from this channel in the select below that tracks shutdown. If it remains
664671
// nil, that case of the select will just never fire, but it's important not to have a
665672
// "bare" read on this channel.
@@ -982,6 +989,23 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
982989
options.WorkspaceUsageTracker = tracker
983990
defer tracker.Close()
984991

992+
// Manage notifications.
993+
var notificationsManager *notifications.Manager
994+
if experiments.Enabled(codersdk.ExperimentNotifications) {
995+
cfg := options.DeploymentValues.Notifications
996+
nlog := logger.Named("notifications-manager")
997+
notificationsManager, err = notifications.NewManager(cfg, options.Database, nlog, templateHelpers(options))
998+
if err != nil {
999+
return xerrors.Errorf("failed to instantiate notification manager: %w", err)
1000+
}
1001+
1002+
// nolint:gocritic // TODO: create own role.
1003+
notificationsManager.Run(dbauthz.AsSystemRestricted(ctx), int(cfg.WorkerCount.Value()))
1004+
notifications.RegisterInstance(notificationsManager)
1005+
} else {
1006+
notifications.RegisterInstance(notifications.NewNoopManager())
1007+
}
1008+
9851009
// Wrap the server in middleware that redirects to the access URL if
9861010
// the request is not to a local IP.
9871011
var handler http.Handler = coderAPI.RootHandler
@@ -1062,10 +1086,10 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
10621086
case <-stopCtx.Done():
10631087
exitErr = stopCtx.Err()
10641088
waitForProvisionerJobs = true
1065-
_, _ = io.WriteString(inv.Stdout, cliui.Bold("Stop caught, waiting for provisioner jobs to complete and gracefully exiting. Use ctrl+\\ to force quit"))
1089+
_, _ = io.WriteString(inv.Stdout, cliui.Bold("Stop caught, waiting for provisioner jobs to complete and gracefully exiting. Use ctrl+\\ to force quit\n"))
10661090
case <-interruptCtx.Done():
10671091
exitErr = interruptCtx.Err()
1068-
_, _ = io.WriteString(inv.Stdout, cliui.Bold("Interrupt caught, gracefully exiting. Use ctrl+\\ to force quit"))
1092+
_, _ = io.WriteString(inv.Stdout, cliui.Bold("Interrupt caught, gracefully exiting. Use ctrl+\\ to force quit\n"))
10691093
case <-tunnelDone:
10701094
exitErr = xerrors.New("dev tunnel closed unexpectedly")
10711095
case <-pubsubWatchdogTimeout:
@@ -1101,6 +1125,21 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
11011125
// Cancel any remaining in-flight requests.
11021126
shutdownConns()
11031127

1128+
if notificationsManager != nil {
1129+
// Stop the notification manager, which will cause any buffered updates to the store to be flushed.
1130+
// If the Stop() call times out, messages that were sent but not reflected as such in the store will have
1131+
// their leases expire after a period of time and will be re-queued for sending.
1132+
// See CODER_NOTIFICATIONS_LEASE_PERIOD.
1133+
cliui.Info(inv.Stdout, "Shutting down notifications manager..."+"\n")
1134+
err = shutdownWithTimeout(notificationsManager.Stop, 5*time.Second)
1135+
if err != nil {
1136+
cliui.Warnf(inv.Stderr, "Notifications manager shutdown took longer than 5s, "+
1137+
"this may result in duplicate notifications being sent: %s\n", err)
1138+
} else {
1139+
cliui.Info(inv.Stdout, "Gracefully shut down notifications manager\n")
1140+
}
1141+
}
1142+
11041143
// Shut down provisioners before waiting for WebSockets
11051144
// connections to close.
11061145
var wg sync.WaitGroup
@@ -1240,6 +1279,15 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
12401279
return serverCmd
12411280
}
12421281

1282+
// templateHelpers builds a set of functions which can be called in templates.
1283+
// We build them here to avoid an import cycle by using coderd.Options in notifications.Manager.
1284+
// We can later use this to inject whitelabel fields when app name / logo URL are overridden.
1285+
func templateHelpers(options *coderd.Options) map[string]any {
1286+
return map[string]any{
1287+
"base_url": func() string { return options.AccessURL.String() },
1288+
}
1289+
}
1290+
12431291
// printDeprecatedOptions loops through all command options, and prints
12441292
// a warning for usage of deprecated options.
12451293
func PrintDeprecatedOptions() serpent.MiddlewareFunc {

coderd/apidoc/docs.go

+81-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

+81-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)