-
Notifications
You must be signed in to change notification settings - Fork 917
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all 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
Jump to
Diff view
Diff view
There are no files selected for viewing
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 ( | ||
regenVapidKeypairDBURL string | ||
regenVapidKeypairPgAuth string | ||
) | ||
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)) | ||
) | ||
if r.verbose { | ||
logger = logger.Leveled(slog.LevelDebug) | ||
} | ||
|
||
defer cancel() | ||
|
||
if regenVapidKeypairDBURL == "" { | ||
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 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, "") | ||
if err != nil { | ||
return err | ||
} | ||
defer func() { | ||
_ = closePg() | ||
}() | ||
regenVapidKeypairDBURL = url | ||
} | ||
|
||
sqlDriver := "postgres" | ||
var err error | ||
if codersdk.PostgresAuth(regenVapidKeypairPgAuth) == codersdk.PostgresAuthAWSIAMRDS { | ||
sqlDriver, err = awsiamrds.Register(inv.Context(), sqlDriver) | ||
if err != nil { | ||
return xerrors.Errorf("register aws rds iam auth: %w", err) | ||
} | ||
} | ||
|
||
sqlDB, err := ConnectToPostgres(ctx, logger, sqlDriver, regenVapidKeypairDBURL, nil) | ||
if err != nil { | ||
return xerrors.Errorf("connect to postgres: %w", err) | ||
} | ||
defer func() { | ||
_ = 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)") | ||
|
||
if resp, err := cliui.Prompt(inv, cliui.PromptOptions{ | ||
IsConfirm: true, | ||
Default: cliui.ConfirmNo, | ||
}); err != nil || resp != cliui.ConfirmYes { | ||
return xerrors.Errorf("VAPID keypair regeneration failed: %w", err) | ||
} | ||
|
||
if _, _, err := webpush.RegenerateVAPIDKeys(ctx, db); err != nil { | ||
return xerrors.Errorf("regenerate vapid keypair: %w", err) | ||
} | ||
|
||
_, _ = fmt.Fprintln(inv.Stdout, "VAPID keypair regenerated successfully.") | ||
return nil | ||
}, | ||
} | ||
|
||
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...), | ||
}, | ||
) | ||
|
||
return regenerateVapidKeypairCommand | ||
} |
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" | ||
) | ||
|
||
func TestRegenerateVapidKeypair(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) | ||
defer sqlDB.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) | ||
defer sqlDB.Close() | ||
|
||
db := database.New(sqlDB) | ||
for i := 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. | ||
for j := 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. | ||
var count int64 | ||
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-user Create a new admin user with the given username, | ||
email and password and adds it to every | ||
organization. | ||
postgres-builtin-serve Run the built-in PostgreSQL deployment. | ||
postgres-builtin-url Output the connection URL for the built-in | ||
PostgreSQL deployment. | ||
create-admin-user Create 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-serve Run the built-in PostgreSQL deployment. | ||
postgres-builtin-url Output 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.