- Notifications
You must be signed in to change notification settings - Fork1k
feat(coderd): add webpush package#17091
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
a980379
bc02200
2ecae9d
84e3ace
377eaab
982acef
d82073e
fc59c70
cbff3ca
9e7e1dc
1315a46
85db78c
892388a
e600b7d
eef2038
46f7519
4408090
204ab4a
0535ed6
9f1f4f9
29bba04
46c2cd8
960c5db
aa22161
57d84a9
ef22b35
9a1a605
e8b6083
5331f9c
449f882
e5fd00e
bcf108a
eb7b102
9b5ed09
c06558e
10ac9fb
a114ddb
ca72676
3b1dc70
12808f1
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 |
---|---|---|
@@ -64,6 +64,7 @@ import ( | ||
"github.com/coder/coder/v2/coderd/entitlements" | ||
"github.com/coder/coder/v2/coderd/notifications/reports" | ||
"github.com/coder/coder/v2/coderd/runtimeconfig" | ||
"github.com/coder/coder/v2/coderd/webpush" | ||
"github.com/coder/coder/v2/buildinfo" | ||
"github.com/coder/coder/v2/cli/clilog" | ||
@@ -775,6 +776,26 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd. | ||
return xerrors.Errorf("set deployment id: %w", err) | ||
} | ||
// Manage push notifications. | ||
experiments := coderd.ReadExperiments(options.Logger, options.DeploymentValues.Experiments.Value()) | ||
if experiments.Enabled(codersdk.ExperimentWebPush) { | ||
webpusher, err := webpush.New(ctx, &options.Logger, options.Database) | ||
if err != nil { | ||
options.Logger.Error(ctx, "failed to create web push dispatcher", slog.Error(err)) | ||
options.Logger.Warn(ctx, "web push notifications will not work until the VAPID keys are regenerated") | ||
webpusher = &webpush.NoopWebpusher{ | ||
Msg: "Web Push notifications are disabled due to a system error. Please contact your Coder administrator.", | ||
} | ||
} | ||
options.WebPushDispatcher = webpusher | ||
} else { | ||
options.WebPushDispatcher = &webpush.NoopWebpusher{ | ||
// Users will likely not see this message as the endpoints return 404 | ||
// if not enabled. Just in case... | ||
Msg: "Web Push notifications are an experimental feature and are disabled by default. Enable the 'web-push' experiment to use this feature.", | ||
} | ||
} | ||
githubOAuth2ConfigParams, err := getGithubOAuth2ConfigParams(ctx, options.Database, vals) | ||
if err != nil { | ||
return xerrors.Errorf("get github oauth2 config params: %w", err) | ||
@@ -1255,6 +1276,7 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd. | ||
} | ||
createAdminUserCmd := r.newCreateAdminUserCommand() | ||
regenerateVapidKeypairCmd := r.newRegenerateVapidKeypairCommand() | ||
johnstcn marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
rawURLOpt := serpent.Option{ | ||
Flag: "raw-url", | ||
@@ -1268,7 +1290,7 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd. | ||
serverCmd.Children = append( | ||
serverCmd.Children, | ||
createAdminUserCmd, postgresBuiltinURLCmd, postgresBuiltinServeCmd, regenerateVapidKeypairCmd, | ||
) | ||
return serverCmd | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
//go:build !slim | ||
package cli | ||
import ( | ||
"fmt" | ||
"golang.org/x/xerrors" | ||
"cdr.dev/slog" | ||
"cdr.dev/slog/sloggers/sloghuman" | ||
"github.com/coder/coder/v2/cli/cliui" | ||
"github.com/coder/coder/v2/coderd/database" | ||
"github.com/coder/coder/v2/coderd/database/awsiamrds" | ||
"github.com/coder/coder/v2/coderd/webpush" | ||
"github.com/coder/coder/v2/codersdk" | ||
"github.com/coder/serpent" | ||
) | ||
func (r*RootCmd)newRegenerateVapidKeypairCommand()*serpent.Command { | ||
var ( | ||
regenVapidKeypairDBURLstring | ||
regenVapidKeypairPgAuthstring | ||
) | ||
regenerateVapidKeypairCommand:=&serpent.Command{ | ||
Use:"regenerate-vapid-keypair", | ||
Short:"Regenerate the VAPID keypair used for web push notifications.", | ||
Hidden:true,// Hide this command as it's an experimental feature | ||
Handler:func(inv*serpent.Invocation)error { | ||
var ( | ||
ctx,cancel=inv.SignalNotifyContext(inv.Context(),StopSignals...) | ||
cfg=r.createConfig() | ||
logger=inv.Logger.AppendSinks(sloghuman.Sink(inv.Stderr)) | ||
) | ||
ifr.verbose { | ||
logger=logger.Leveled(slog.LevelDebug) | ||
} | ||
defercancel() | ||
ifregenVapidKeypairDBURL=="" { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. I'm quite sure I have seen this pattern in other CLI commands: we may consider some base pattern for all commands using the database MemberAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Yep there are a few commands that do this. I'll create a follow-up. (EDIT:coder/internal#541) | ||
cliui.Infof(inv.Stdout,"Using built-in PostgreSQL (%s)",cfg.PostgresPath()) | ||
url,closePg,err:=startBuiltinPostgres(ctx,cfg,logger,"") | ||
iferr!=nil { | ||
returnerr | ||
} | ||
deferfunc() { | ||
_=closePg() | ||
}() | ||
regenVapidKeypairDBURL=url | ||
} | ||
sqlDriver:="postgres" | ||
varerrerror | ||
ifcodersdk.PostgresAuth(regenVapidKeypairPgAuth)==codersdk.PostgresAuthAWSIAMRDS { | ||
sqlDriver,err=awsiamrds.Register(inv.Context(),sqlDriver) | ||
iferr!=nil { | ||
returnxerrors.Errorf("register aws rds iam auth: %w",err) | ||
} | ||
} | ||
sqlDB,err:=ConnectToPostgres(ctx,logger,sqlDriver,regenVapidKeypairDBURL,nil) | ||
iferr!=nil { | ||
returnxerrors.Errorf("connect to postgres: %w",err) | ||
} | ||
deferfunc() { | ||
_=sqlDB.Close() | ||
}() | ||
db:=database.New(sqlDB) | ||
// Confirm that the user really wants to regenerate the VAPID keypair. | ||
cliui.Infof(inv.Stdout,"Regenerating VAPID keypair...") | ||
cliui.Infof(inv.Stdout,"This will delete all existing webpush subscriptions.") | ||
cliui.Infof(inv.Stdout,"Are you sure you want to continue? (y/N)") | ||
ifresp,err:=cliui.Prompt(inv, cliui.PromptOptions{ | ||
IsConfirm:true, | ||
Default:cliui.ConfirmNo, | ||
});err!=nil||resp!=cliui.ConfirmYes { | ||
returnxerrors.Errorf("VAPID keypair regeneration failed: %w",err) | ||
} | ||
if_,_,err:=webpush.RegenerateVAPIDKeys(ctx,db);err!=nil { | ||
returnxerrors.Errorf("regenerate vapid keypair: %w",err) | ||
} | ||
_,_=fmt.Fprintln(inv.Stdout,"VAPID keypair regenerated successfully.") | ||
returnnil | ||
}, | ||
} | ||
regenerateVapidKeypairCommand.Options.Add( | ||
cliui.SkipPromptOption(), | ||
serpent.Option{ | ||
Env:"CODER_PG_CONNECTION_URL", | ||
Flag:"postgres-url", | ||
Description:"URL of a PostgreSQL database. If empty, the built-in PostgreSQL deployment will be used (Coder must not be already running in this case).", | ||
Value:serpent.StringOf(®enVapidKeypairDBURL), | ||
}, | ||
serpent.Option{ | ||
Name:"Postgres Connection Auth", | ||
Description:"Type of auth to use when connecting to postgres.", | ||
Flag:"postgres-connection-auth", | ||
Env:"CODER_PG_CONNECTION_AUTH", | ||
Default:"password", | ||
Value:serpent.EnumOf(®enVapidKeypairPgAuth,codersdk.PostgresAuthDrivers...), | ||
}, | ||
) | ||
returnregenerateVapidKeypairCommand | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
package cli_test | ||
import ( | ||
"context" | ||
"database/sql" | ||
"testing" | ||
"github.com/stretchr/testify/require" | ||
"github.com/coder/coder/v2/cli/clitest" | ||
"github.com/coder/coder/v2/coderd/database" | ||
"github.com/coder/coder/v2/coderd/database/dbgen" | ||
"github.com/coder/coder/v2/coderd/database/dbtestutil" | ||
"github.com/coder/coder/v2/pty/ptytest" | ||
"github.com/coder/coder/v2/testutil" | ||
) | ||
funcTestRegenerateVapidKeypair(t*testing.T) { | ||
t.Parallel() | ||
if!dbtestutil.WillUsePostgres() { | ||
t.Skip("this test is only supported on postgres") | ||
} | ||
t.Run("NoExistingVAPIDKeys",func(t*testing.T) { | ||
t.Parallel() | ||
ctx,cancel:=context.WithTimeout(context.Background(),testutil.WaitShort) | ||
t.Cleanup(cancel) | ||
connectionURL,err:=dbtestutil.Open(t) | ||
require.NoError(t,err) | ||
sqlDB,err:=sql.Open("postgres",connectionURL) | ||
require.NoError(t,err) | ||
defersqlDB.Close() | ||
db:=database.New(sqlDB) | ||
// Ensure there is no existing VAPID keypair. | ||
rows,err:=db.GetWebpushVAPIDKeys(ctx) | ||
require.NoError(t,err) | ||
require.Empty(t,rows) | ||
inv,_:=clitest.New(t,"server","regenerate-vapid-keypair","--postgres-url",connectionURL,"--yes") | ||
pty:=ptytest.New(t) | ||
inv.Stdout=pty.Output() | ||
inv.Stderr=pty.Output() | ||
clitest.Start(t,inv) | ||
pty.ExpectMatchContext(ctx,"Regenerating VAPID keypair...") | ||
pty.ExpectMatchContext(ctx,"This will delete all existing webpush subscriptions.") | ||
pty.ExpectMatchContext(ctx,"Are you sure you want to continue? (y/N)") | ||
pty.WriteLine("y") | ||
pty.ExpectMatchContext(ctx,"VAPID keypair regenerated successfully.") | ||
// Ensure the VAPID keypair was created. | ||
keys,err:=db.GetWebpushVAPIDKeys(ctx) | ||
require.NoError(t,err) | ||
require.NotEmpty(t,keys.VapidPublicKey) | ||
require.NotEmpty(t,keys.VapidPrivateKey) | ||
}) | ||
t.Run("ExistingVAPIDKeys",func(t*testing.T) { | ||
t.Parallel() | ||
ctx,cancel:=context.WithTimeout(context.Background(),testutil.WaitShort) | ||
t.Cleanup(cancel) | ||
connectionURL,err:=dbtestutil.Open(t) | ||
require.NoError(t,err) | ||
sqlDB,err:=sql.Open("postgres",connectionURL) | ||
require.NoError(t,err) | ||
defersqlDB.Close() | ||
db:=database.New(sqlDB) | ||
fori:=0;i<10;i++ { | ||
// Insert a few fake users. | ||
u:=dbgen.User(t,db, database.User{}) | ||
// Insert a few fake push subscriptions for each user. | ||
forj:=0;j<10;j++ { | ||
_=dbgen.WebpushSubscription(t,db, database.InsertWebpushSubscriptionParams{ | ||
UserID:u.ID, | ||
}) | ||
} | ||
} | ||
inv,_:=clitest.New(t,"server","regenerate-vapid-keypair","--postgres-url",connectionURL,"--yes") | ||
pty:=ptytest.New(t) | ||
inv.Stdout=pty.Output() | ||
inv.Stderr=pty.Output() | ||
clitest.Start(t,inv) | ||
pty.ExpectMatchContext(ctx,"Regenerating VAPID keypair...") | ||
pty.ExpectMatchContext(ctx,"This will delete all existing webpush subscriptions.") | ||
pty.ExpectMatchContext(ctx,"Are you sure you want to continue? (y/N)") | ||
pty.WriteLine("y") | ||
pty.ExpectMatchContext(ctx,"VAPID keypair regenerated successfully.") | ||
// Ensure the VAPID keypair was created. | ||
keys,err:=db.GetWebpushVAPIDKeys(ctx) | ||
require.NoError(t,err) | ||
require.NotEmpty(t,keys.VapidPublicKey) | ||
require.NotEmpty(t,keys.VapidPrivateKey) | ||
// Ensure the push subscriptions were deleted. | ||
varcountint64 | ||
rows,err:=sqlDB.QueryContext(ctx,"SELECT COUNT(*) FROM webpush_subscriptions") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. just confirming - is there a chance that there will be another testrun operating on this table (flakiness risk?) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. There shouldn't be. It operates entirely in its own transaction, and each test should be operating on its own database. | ||
require.NoError(t,err) | ||
t.Cleanup(func() { | ||
_=rows.Close() | ||
}) | ||
require.True(t,rows.Next()) | ||
require.NoError(t,rows.Scan(&count)) | ||
require.Equal(t,int64(0),count) | ||
}) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -6,12 +6,12 @@ USAGE: | ||
Start a Coder server | ||
SUBCOMMANDS: | ||
create-admin-userCreate a new admin user with the given username, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. nit: unrelated change? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. I'm actually not sure what's causing these changes, but the command to update these golden files is convinced that this is how it should be now. | ||
email and password and adds it to every | ||
organization. | ||
postgres-builtin-serveRun the built-in PostgreSQL deployment. | ||
postgres-builtin-urlOutput the connection URL for the built-in | ||
PostgreSQL deployment. | ||
OPTIONS: | ||
--allow-workspace-renames bool, $CODER_ALLOW_WORKSPACE_RENAMES (default: false) | ||
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.