@@ -53,6 +53,8 @@ import (
53
53
"gopkg.in/yaml.v3"
54
54
"tailscale.com/tailcfg"
55
55
56
+ "github.com/coder/coder/v2/coderd/database/dbauthz"
57
+
56
58
"cdr.dev/slog"
57
59
"cdr.dev/slog/sloggers/sloghuman"
58
60
"github.com/coder/coder/v2/buildinfo"
@@ -73,6 +75,7 @@ import (
73
75
"github.com/coder/coder/v2/coderd/externalauth"
74
76
"github.com/coder/coder/v2/coderd/gitsshkey"
75
77
"github.com/coder/coder/v2/coderd/httpmw"
78
+ "github.com/coder/coder/v2/coderd/notifications"
76
79
"github.com/coder/coder/v2/coderd/oauthpki"
77
80
"github.com/coder/coder/v2/coderd/prometheusmetrics"
78
81
"github.com/coder/coder/v2/coderd/prometheusmetrics/insights"
@@ -660,6 +663,10 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
660
663
options .OIDCConfig = oc
661
664
}
662
665
666
+ experiments := coderd .ReadExperiments (
667
+ options .Logger , options .DeploymentValues .Experiments .Value (),
668
+ )
669
+
663
670
// We'll read from this channel in the select below that tracks shutdown. If it remains
664
671
// nil, that case of the select will just never fire, but it's important not to have a
665
672
// "bare" read on this channel.
@@ -982,6 +989,23 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
982
989
options .WorkspaceUsageTracker = tracker
983
990
defer tracker .Close ()
984
991
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
+
985
1009
// Wrap the server in middleware that redirects to the access URL if
986
1010
// the request is not to a local IP.
987
1011
var handler http.Handler = coderAPI .RootHandler
@@ -1062,10 +1086,10 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
1062
1086
case <- stopCtx .Done ():
1063
1087
exitErr = stopCtx .Err ()
1064
1088
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 " ))
1066
1090
case <- interruptCtx .Done ():
1067
1091
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 " ))
1069
1093
case <- tunnelDone :
1070
1094
exitErr = xerrors .New ("dev tunnel closed unexpectedly" )
1071
1095
case <- pubsubWatchdogTimeout :
@@ -1101,6 +1125,21 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
1101
1125
// Cancel any remaining in-flight requests.
1102
1126
shutdownConns ()
1103
1127
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
+
1104
1143
// Shut down provisioners before waiting for WebSockets
1105
1144
// connections to close.
1106
1145
var wg sync.WaitGroup
@@ -1240,6 +1279,15 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
1240
1279
return serverCmd
1241
1280
}
1242
1281
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
+
1243
1291
// printDeprecatedOptions loops through all command options, and prints
1244
1292
// a warning for usage of deprecated options.
1245
1293
func PrintDeprecatedOptions () serpent.MiddlewareFunc {
0 commit comments