- Notifications
You must be signed in to change notification settings - Fork928
feat: implement thin vertical slice of system-generated notifications#13537
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
Uh oh!
There was an error while loading.Please reload this page.
Changes fromall commits
53c9cbb
4856aed
cda6efb
86f937a
e8f1af2
a056f54
8c64d30
ac149ec
884fadf
4e362e7
8097290
1b841ad
61f5bd6
3c8e33b
757327c
6f909ae
36698c5
c5701a6
9d4c312
9380d8e
4b7214d
6679ef1
337997d
ba5f7c6
0f29293
7c6c486
c6e75c2
aff9e6c
613e074
faea7fc
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -195,6 +195,11 @@ linters-settings: | ||
- name: var-naming | ||
- name: waitgroup-by-value | ||
# irrelevant as of Go v1.22: https://go.dev/blog/loopvar-preview | ||
govet: | ||
dannykopping marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
disable: | ||
- loopclosure | ||
issues: | ||
# Rules listed here: https://github.com/securego/gosec#available-rules | ||
exclude-rules: | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -55,6 +55,11 @@ import ( | ||
"cdr.dev/slog" | ||
"cdr.dev/slog/sloggers/sloghuman" | ||
"github.com/coder/pretty" | ||
"github.com/coder/retry" | ||
"github.com/coder/serpent" | ||
"github.com/coder/wgtunnel/tunnelsdk" | ||
"github.com/coder/coder/v2/buildinfo" | ||
"github.com/coder/coder/v2/cli/clilog" | ||
"github.com/coder/coder/v2/cli/cliui" | ||
@@ -64,6 +69,7 @@ import ( | ||
"github.com/coder/coder/v2/coderd/autobuild" | ||
"github.com/coder/coder/v2/coderd/database" | ||
"github.com/coder/coder/v2/coderd/database/awsiamrds" | ||
"github.com/coder/coder/v2/coderd/database/dbauthz" | ||
"github.com/coder/coder/v2/coderd/database/dbmem" | ||
"github.com/coder/coder/v2/coderd/database/dbmetrics" | ||
"github.com/coder/coder/v2/coderd/database/dbpurge" | ||
@@ -73,6 +79,7 @@ import ( | ||
"github.com/coder/coder/v2/coderd/externalauth" | ||
"github.com/coder/coder/v2/coderd/gitsshkey" | ||
"github.com/coder/coder/v2/coderd/httpmw" | ||
"github.com/coder/coder/v2/coderd/notifications" | ||
"github.com/coder/coder/v2/coderd/oauthpki" | ||
"github.com/coder/coder/v2/coderd/prometheusmetrics" | ||
"github.com/coder/coder/v2/coderd/prometheusmetrics/insights" | ||
@@ -97,10 +104,6 @@ import ( | ||
"github.com/coder/coder/v2/provisionersdk" | ||
sdkproto "github.com/coder/coder/v2/provisionersdk/proto" | ||
"github.com/coder/coder/v2/tailnet" | ||
) | ||
func createOIDCConfig(ctx context.Context, vals *codersdk.DeploymentValues) (*coderd.OIDCConfig, error) { | ||
@@ -592,6 +595,7 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd. | ||
SSHConfigOptions: configSSHOptions, | ||
}, | ||
AllowWorkspaceRenames: vals.AllowWorkspaceRenames.Value(), | ||
NotificationsEnqueuer: notifications.NewNoopEnqueuer(), // Changed further down if notifications enabled. | ||
} | ||
if httpServers.TLSConfig != nil { | ||
options.TLSCertificates = httpServers.TLSConfig.Certificates | ||
@@ -660,6 +664,10 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd. | ||
options.OIDCConfig = oc | ||
} | ||
experiments := coderd.ReadExperiments( | ||
options.Logger, options.DeploymentValues.Experiments.Value(), | ||
) | ||
// We'll read from this channel in the select below that tracks shutdown. If it remains | ||
// nil, that case of the select will just never fire, but it's important not to have a | ||
// "bare" read on this channel. | ||
@@ -969,6 +977,32 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd. | ||
options.WorkspaceUsageTracker = tracker | ||
defer tracker.Close() | ||
// Manage notifications. | ||
var ( | ||
notificationsManager *notifications.Manager | ||
) | ||
if experiments.Enabled(codersdk.ExperimentNotifications) { | ||
cfg := options.DeploymentValues.Notifications | ||
// The enqueuer is responsible for enqueueing notifications to the given store. | ||
enqueuer, err := notifications.NewStoreEnqueuer(cfg, options.Database, templateHelpers(options), logger.Named("notifications.enqueuer")) | ||
if err != nil { | ||
return xerrors.Errorf("failed to instantiate notification store enqueuer: %w", err) | ||
} | ||
options.NotificationsEnqueuer = enqueuer | ||
// The notification manager is responsible for: | ||
// - creating notifiers and managing their lifecycles (notifiers are responsible for dequeueing/sending notifications) | ||
// - keeping the store updated with status updates | ||
notificationsManager, err = notifications.NewManager(cfg, options.Database, logger.Named("notifications.manager")) | ||
if err != nil { | ||
return xerrors.Errorf("failed to instantiate notification manager: %w", err) | ||
} | ||
// nolint:gocritic // TODO: create own role. | ||
notificationsManager.Run(dbauthz.AsSystemRestricted(ctx)) | ||
} | ||
// Wrap the server in middleware that redirects to the access URL if | ||
// the request is not to a local IP. | ||
var handler http.Handler = coderAPI.RootHandler | ||
@@ -1049,10 +1083,10 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd. | ||
case <-stopCtx.Done(): | ||
exitErr = stopCtx.Err() | ||
waitForProvisionerJobs = true | ||
_, _ = io.WriteString(inv.Stdout, cliui.Bold("Stop caught, waiting for provisioner jobs to complete and gracefully exiting. Use ctrl+\\ to force quit\n")) | ||
case <-interruptCtx.Done(): | ||
exitErr = interruptCtx.Err() | ||
_, _ = io.WriteString(inv.Stdout, cliui.Bold("Interrupt caught, gracefully exiting. Use ctrl+\\ to force quit\n")) | ||
case <-tunnelDone: | ||
exitErr = xerrors.New("dev tunnel closed unexpectedly") | ||
case <-pubsubWatchdogTimeout: | ||
@@ -1088,6 +1122,21 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd. | ||
// Cancel any remaining in-flight requests. | ||
shutdownConns() | ||
if notificationsManager != nil { | ||
// Stop the notification manager, which will cause any buffered updates to the store to be flushed. | ||
// If the Stop() call times out, messages that were sent but not reflected as such in the store will have | ||
// their leases expire after a period of time and will be re-queued for sending. | ||
// See CODER_NOTIFICATIONS_LEASE_PERIOD. | ||
cliui.Info(inv.Stdout, "Shutting down notifications manager..."+"\n") | ||
err = shutdownWithTimeout(notificationsManager.Stop, 5*time.Second) | ||
if err != nil { | ||
cliui.Warnf(inv.Stderr, "Notifications manager shutdown took longer than 5s, "+ | ||
"this may result in duplicate notifications being sent: %s\n", err) | ||
} else { | ||
cliui.Info(inv.Stdout, "Gracefully shut down notifications manager\n") | ||
} | ||
} | ||
// Shut down provisioners before waiting for WebSockets | ||
// connections to close. | ||
var wg sync.WaitGroup | ||
@@ -1227,6 +1276,15 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd. | ||
return serverCmd | ||
} | ||
// templateHelpers builds a set of functions which can be called in templates. | ||
// We build them here to avoid an import cycle by using coderd.Options in notifications.Manager. | ||
// We can later use this to inject whitelabel fields when app name / logo URL are overridden. | ||
func templateHelpers(options *coderd.Options) map[string]any { | ||
return map[string]any{ | ||
"base_url": func() string { return options.AccessURL.String() }, | ||
} | ||
dannykopping marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
} | ||
// printDeprecatedOptions loops through all command options, and prints | ||
// a warning for usage of deprecated options. | ||
func PrintDeprecatedOptions() serpent.MiddlewareFunc { | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -326,6 +326,30 @@ can safely ignore these settings. | ||
Minimum supported version of TLS. Accepted values are "tls10", | ||
"tls11", "tls12" or "tls13". | ||
NOTIFICATIONS OPTIONS: | ||
--notifications-dispatch-timeout duration, $CODER_NOTIFICATIONS_DISPATCH_TIMEOUT (default: 1m0s) | ||
How long to wait while a notification is being sent before giving up. | ||
--notifications-max-send-attempts int, $CODER_NOTIFICATIONS_MAX_SEND_ATTEMPTS (default: 5) | ||
The upper limit of attempts to send a notification. | ||
--notifications-method string, $CODER_NOTIFICATIONS_METHOD (default: smtp) | ||
Which delivery method to use (available options: 'smtp', 'webhook'). | ||
NOTIFICATIONS / EMAIL OPTIONS: | ||
--notifications-email-from string, $CODER_NOTIFICATIONS_EMAIL_FROM | ||
The sender's address to use. | ||
--notifications-email-hello string, $CODER_NOTIFICATIONS_EMAIL_HELLO (default: localhost) | ||
The hostname identifying the SMTP server. | ||
--notifications-email-smarthost host:port, $CODER_NOTIFICATIONS_EMAIL_SMARTHOST (default: localhost:587) | ||
The intermediary SMTP host through which emails are sent. | ||
NOTIFICATIONS / WEBHOOK OPTIONS: | ||
--notifications-webhook-endpoint url, $CODER_NOTIFICATIONS_WEBHOOK_ENDPOINT | ||
The endpoint to which to send webhooks. | ||
dannykopping marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
OAUTH2 / GITHUB OPTIONS: | ||
--oauth2-github-allow-everyone bool, $CODER_OAUTH2_GITHUB_ALLOW_EVERYONE | ||
Allow all logins, setting this option means allowed orgs and teams | ||
Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.