Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit06e5d9e

Browse files
feat(coderd): add webpush package (#17091)
* Adds `codersdk.ExperimentWebPush` (`web-push`)* Adds a `coderd/webpush` package that allows sending native pushnotifications via `github.com/SherClockHolmes/webpush-go`* Adds database tables to store push notification subscriptions.* Adds an API endpoint that allows users to subscribe/unsubscribe, andsend a test notification (404 without experiment, excluded from API docs)* Adds server CLI command to regenerate VAPID keys (note: regeneratingthe VAPID keypair requires deleting all existing subscriptions)---------Co-authored-by: Kyle Carberry <kyle@carberry.com>
1 parent006600e commit06e5d9e

File tree

43 files changed

+2136
-20
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+2136
-20
lines changed

‎cli/server.go‎

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ import (
6464
"github.com/coder/coder/v2/coderd/entitlements"
6565
"github.com/coder/coder/v2/coderd/notifications/reports"
6666
"github.com/coder/coder/v2/coderd/runtimeconfig"
67+
"github.com/coder/coder/v2/coderd/webpush"
6768

6869
"github.com/coder/coder/v2/buildinfo"
6970
"github.com/coder/coder/v2/cli/clilog"
@@ -775,6 +776,26 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
775776
returnxerrors.Errorf("set deployment id: %w",err)
776777
}
777778

779+
// Manage push notifications.
780+
experiments:=coderd.ReadExperiments(options.Logger,options.DeploymentValues.Experiments.Value())
781+
ifexperiments.Enabled(codersdk.ExperimentWebPush) {
782+
webpusher,err:=webpush.New(ctx,&options.Logger,options.Database)
783+
iferr!=nil {
784+
options.Logger.Error(ctx,"failed to create web push dispatcher",slog.Error(err))
785+
options.Logger.Warn(ctx,"web push notifications will not work until the VAPID keys are regenerated")
786+
webpusher=&webpush.NoopWebpusher{
787+
Msg:"Web Push notifications are disabled due to a system error. Please contact your Coder administrator.",
788+
}
789+
}
790+
options.WebPushDispatcher=webpusher
791+
}else {
792+
options.WebPushDispatcher=&webpush.NoopWebpusher{
793+
// Users will likely not see this message as the endpoints return 404
794+
// if not enabled. Just in case...
795+
Msg:"Web Push notifications are an experimental feature and are disabled by default. Enable the 'web-push' experiment to use this feature.",
796+
}
797+
}
798+
778799
githubOAuth2ConfigParams,err:=getGithubOAuth2ConfigParams(ctx,options.Database,vals)
779800
iferr!=nil {
780801
returnxerrors.Errorf("get github oauth2 config params: %w",err)
@@ -1255,6 +1276,7 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
12551276
}
12561277

12571278
createAdminUserCmd:=r.newCreateAdminUserCommand()
1279+
regenerateVapidKeypairCmd:=r.newRegenerateVapidKeypairCommand()
12581280

12591281
rawURLOpt:= serpent.Option{
12601282
Flag:"raw-url",
@@ -1268,7 +1290,7 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
12681290

12691291
serverCmd.Children=append(
12701292
serverCmd.Children,
1271-
createAdminUserCmd,postgresBuiltinURLCmd,postgresBuiltinServeCmd,
1293+
createAdminUserCmd,postgresBuiltinURLCmd,postgresBuiltinServeCmd,regenerateVapidKeypairCmd,
12721294
)
12731295

12741296
returnserverCmd
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
//go:build !slim
2+
3+
package cli
4+
5+
import (
6+
"fmt"
7+
8+
"golang.org/x/xerrors"
9+
10+
"cdr.dev/slog"
11+
"cdr.dev/slog/sloggers/sloghuman"
12+
13+
"github.com/coder/coder/v2/cli/cliui"
14+
"github.com/coder/coder/v2/coderd/database"
15+
"github.com/coder/coder/v2/coderd/database/awsiamrds"
16+
"github.com/coder/coder/v2/coderd/webpush"
17+
"github.com/coder/coder/v2/codersdk"
18+
"github.com/coder/serpent"
19+
)
20+
21+
func (r*RootCmd)newRegenerateVapidKeypairCommand()*serpent.Command {
22+
var (
23+
regenVapidKeypairDBURLstring
24+
regenVapidKeypairPgAuthstring
25+
)
26+
regenerateVapidKeypairCommand:=&serpent.Command{
27+
Use:"regenerate-vapid-keypair",
28+
Short:"Regenerate the VAPID keypair used for web push notifications.",
29+
Hidden:true,// Hide this command as it's an experimental feature
30+
Handler:func(inv*serpent.Invocation)error {
31+
var (
32+
ctx,cancel=inv.SignalNotifyContext(inv.Context(),StopSignals...)
33+
cfg=r.createConfig()
34+
logger=inv.Logger.AppendSinks(sloghuman.Sink(inv.Stderr))
35+
)
36+
ifr.verbose {
37+
logger=logger.Leveled(slog.LevelDebug)
38+
}
39+
40+
defercancel()
41+
42+
ifregenVapidKeypairDBURL=="" {
43+
cliui.Infof(inv.Stdout,"Using built-in PostgreSQL (%s)",cfg.PostgresPath())
44+
url,closePg,err:=startBuiltinPostgres(ctx,cfg,logger,"")
45+
iferr!=nil {
46+
returnerr
47+
}
48+
deferfunc() {
49+
_=closePg()
50+
}()
51+
regenVapidKeypairDBURL=url
52+
}
53+
54+
sqlDriver:="postgres"
55+
varerrerror
56+
ifcodersdk.PostgresAuth(regenVapidKeypairPgAuth)==codersdk.PostgresAuthAWSIAMRDS {
57+
sqlDriver,err=awsiamrds.Register(inv.Context(),sqlDriver)
58+
iferr!=nil {
59+
returnxerrors.Errorf("register aws rds iam auth: %w",err)
60+
}
61+
}
62+
63+
sqlDB,err:=ConnectToPostgres(ctx,logger,sqlDriver,regenVapidKeypairDBURL,nil)
64+
iferr!=nil {
65+
returnxerrors.Errorf("connect to postgres: %w",err)
66+
}
67+
deferfunc() {
68+
_=sqlDB.Close()
69+
}()
70+
db:=database.New(sqlDB)
71+
72+
// Confirm that the user really wants to regenerate the VAPID keypair.
73+
cliui.Infof(inv.Stdout,"Regenerating VAPID keypair...")
74+
cliui.Infof(inv.Stdout,"This will delete all existing webpush subscriptions.")
75+
cliui.Infof(inv.Stdout,"Are you sure you want to continue? (y/N)")
76+
77+
ifresp,err:=cliui.Prompt(inv, cliui.PromptOptions{
78+
IsConfirm:true,
79+
Default:cliui.ConfirmNo,
80+
});err!=nil||resp!=cliui.ConfirmYes {
81+
returnxerrors.Errorf("VAPID keypair regeneration failed: %w",err)
82+
}
83+
84+
if_,_,err:=webpush.RegenerateVAPIDKeys(ctx,db);err!=nil {
85+
returnxerrors.Errorf("regenerate vapid keypair: %w",err)
86+
}
87+
88+
_,_=fmt.Fprintln(inv.Stdout,"VAPID keypair regenerated successfully.")
89+
returnnil
90+
},
91+
}
92+
93+
regenerateVapidKeypairCommand.Options.Add(
94+
cliui.SkipPromptOption(),
95+
serpent.Option{
96+
Env:"CODER_PG_CONNECTION_URL",
97+
Flag:"postgres-url",
98+
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).",
99+
Value:serpent.StringOf(&regenVapidKeypairDBURL),
100+
},
101+
serpent.Option{
102+
Name:"Postgres Connection Auth",
103+
Description:"Type of auth to use when connecting to postgres.",
104+
Flag:"postgres-connection-auth",
105+
Env:"CODER_PG_CONNECTION_AUTH",
106+
Default:"password",
107+
Value:serpent.EnumOf(&regenVapidKeypairPgAuth,codersdk.PostgresAuthDrivers...),
108+
},
109+
)
110+
111+
returnregenerateVapidKeypairCommand
112+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package cli_test
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"testing"
7+
8+
"github.com/stretchr/testify/require"
9+
10+
"github.com/coder/coder/v2/cli/clitest"
11+
"github.com/coder/coder/v2/coderd/database"
12+
"github.com/coder/coder/v2/coderd/database/dbgen"
13+
"github.com/coder/coder/v2/coderd/database/dbtestutil"
14+
"github.com/coder/coder/v2/pty/ptytest"
15+
"github.com/coder/coder/v2/testutil"
16+
)
17+
18+
funcTestRegenerateVapidKeypair(t*testing.T) {
19+
t.Parallel()
20+
if!dbtestutil.WillUsePostgres() {
21+
t.Skip("this test is only supported on postgres")
22+
}
23+
24+
t.Run("NoExistingVAPIDKeys",func(t*testing.T) {
25+
t.Parallel()
26+
27+
ctx,cancel:=context.WithTimeout(context.Background(),testutil.WaitShort)
28+
t.Cleanup(cancel)
29+
30+
connectionURL,err:=dbtestutil.Open(t)
31+
require.NoError(t,err)
32+
33+
sqlDB,err:=sql.Open("postgres",connectionURL)
34+
require.NoError(t,err)
35+
defersqlDB.Close()
36+
37+
db:=database.New(sqlDB)
38+
// Ensure there is no existing VAPID keypair.
39+
rows,err:=db.GetWebpushVAPIDKeys(ctx)
40+
require.NoError(t,err)
41+
require.Empty(t,rows)
42+
43+
inv,_:=clitest.New(t,"server","regenerate-vapid-keypair","--postgres-url",connectionURL,"--yes")
44+
45+
pty:=ptytest.New(t)
46+
inv.Stdout=pty.Output()
47+
inv.Stderr=pty.Output()
48+
clitest.Start(t,inv)
49+
50+
pty.ExpectMatchContext(ctx,"Regenerating VAPID keypair...")
51+
pty.ExpectMatchContext(ctx,"This will delete all existing webpush subscriptions.")
52+
pty.ExpectMatchContext(ctx,"Are you sure you want to continue? (y/N)")
53+
pty.WriteLine("y")
54+
pty.ExpectMatchContext(ctx,"VAPID keypair regenerated successfully.")
55+
56+
// Ensure the VAPID keypair was created.
57+
keys,err:=db.GetWebpushVAPIDKeys(ctx)
58+
require.NoError(t,err)
59+
require.NotEmpty(t,keys.VapidPublicKey)
60+
require.NotEmpty(t,keys.VapidPrivateKey)
61+
})
62+
63+
t.Run("ExistingVAPIDKeys",func(t*testing.T) {
64+
t.Parallel()
65+
66+
ctx,cancel:=context.WithTimeout(context.Background(),testutil.WaitShort)
67+
t.Cleanup(cancel)
68+
69+
connectionURL,err:=dbtestutil.Open(t)
70+
require.NoError(t,err)
71+
72+
sqlDB,err:=sql.Open("postgres",connectionURL)
73+
require.NoError(t,err)
74+
defersqlDB.Close()
75+
76+
db:=database.New(sqlDB)
77+
fori:=0;i<10;i++ {
78+
// Insert a few fake users.
79+
u:=dbgen.User(t,db, database.User{})
80+
// Insert a few fake push subscriptions for each user.
81+
forj:=0;j<10;j++ {
82+
_=dbgen.WebpushSubscription(t,db, database.InsertWebpushSubscriptionParams{
83+
UserID:u.ID,
84+
})
85+
}
86+
}
87+
88+
inv,_:=clitest.New(t,"server","regenerate-vapid-keypair","--postgres-url",connectionURL,"--yes")
89+
90+
pty:=ptytest.New(t)
91+
inv.Stdout=pty.Output()
92+
inv.Stderr=pty.Output()
93+
clitest.Start(t,inv)
94+
95+
pty.ExpectMatchContext(ctx,"Regenerating VAPID keypair...")
96+
pty.ExpectMatchContext(ctx,"This will delete all existing webpush subscriptions.")
97+
pty.ExpectMatchContext(ctx,"Are you sure you want to continue? (y/N)")
98+
pty.WriteLine("y")
99+
pty.ExpectMatchContext(ctx,"VAPID keypair regenerated successfully.")
100+
101+
// Ensure the VAPID keypair was created.
102+
keys,err:=db.GetWebpushVAPIDKeys(ctx)
103+
require.NoError(t,err)
104+
require.NotEmpty(t,keys.VapidPublicKey)
105+
require.NotEmpty(t,keys.VapidPrivateKey)
106+
107+
// Ensure the push subscriptions were deleted.
108+
varcountint64
109+
rows,err:=sqlDB.QueryContext(ctx,"SELECT COUNT(*) FROM webpush_subscriptions")
110+
require.NoError(t,err)
111+
t.Cleanup(func() {
112+
_=rows.Close()
113+
})
114+
require.True(t,rows.Next())
115+
require.NoError(t,rows.Scan(&count))
116+
require.Equal(t,int64(0),count)
117+
})
118+
}

‎cli/testdata/coder_server_--help.golden‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ USAGE:
66
Start a Coder server
77

88
SUBCOMMANDS:
9-
create-admin-user Create a new admin user with the given username,
10-
email and password and adds it to every
11-
organization.
12-
postgres-builtin-serve Run the built-in PostgreSQL deployment.
13-
postgres-builtin-url Output the connection URL for the built-in
14-
PostgreSQL deployment.
9+
create-admin-userCreate a new admin user with the given username,
10+
email and password and adds it to every
11+
organization.
12+
postgres-builtin-serveRun the built-in PostgreSQL deployment.
13+
postgres-builtin-urlOutput the connection URL for the built-in
14+
PostgreSQL deployment.
1515

1616
OPTIONS:
1717
--allow-workspace-renames bool, $CODER_ALLOW_WORKSPACE_RENAMES (default: false)

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp