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

Commitfbd1d7f

Browse files
authored
feat: notify on successful autoupdate (#13903)
1 parent44924cd commitfbd1d7f

File tree

8 files changed

+130
-11
lines changed

8 files changed

+130
-11
lines changed

‎cli/server.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1066,7 +1066,7 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
10661066
autobuildTicker:=time.NewTicker(vals.AutobuildPollInterval.Value())
10671067
deferautobuildTicker.Stop()
10681068
autobuildExecutor:=autobuild.NewExecutor(
1069-
ctx,options.Database,options.Pubsub,coderAPI.TemplateScheduleStore,&coderAPI.Auditor,coderAPI.AccessControlStore,logger,autobuildTicker.C)
1069+
ctx,options.Database,options.Pubsub,coderAPI.TemplateScheduleStore,&coderAPI.Auditor,coderAPI.AccessControlStore,logger,autobuildTicker.C,options.NotificationsEnqueuer)
10701070
autobuildExecutor.Run()
10711071

10721072
hangDetectorTicker:=time.NewTicker(vals.JobHangDetectorInterval.Value())

‎coderd/autobuild/lifecycle_executor.go

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"github.com/coder/coder/v2/coderd/database/dbtime"
2020
"github.com/coder/coder/v2/coderd/database/provisionerjobs"
2121
"github.com/coder/coder/v2/coderd/database/pubsub"
22+
"github.com/coder/coder/v2/coderd/notifications"
2223
"github.com/coder/coder/v2/coderd/schedule"
2324
"github.com/coder/coder/v2/coderd/wsbuilder"
2425
)
@@ -34,6 +35,9 @@ type Executor struct {
3435
log slog.Logger
3536
tick<-chan time.Time
3637
statsChchan<-Stats
38+
39+
// NotificationsEnqueuer handles enqueueing notifications for delivery by SMTP, webhook, etc.
40+
notificationsEnqueuer notifications.Enqueuer
3741
}
3842

3943
// Stats contains information about one run of Executor.
@@ -44,7 +48,7 @@ type Stats struct {
4448
}
4549

4650
// New returns a new wsactions executor.
47-
funcNewExecutor(ctx context.Context,db database.Store,ps pubsub.Pubsub,tss*atomic.Pointer[schedule.TemplateScheduleStore],auditor*atomic.Pointer[audit.Auditor],acs*atomic.Pointer[dbauthz.AccessControlStore],log slog.Logger,tick<-chan time.Time)*Executor {
51+
funcNewExecutor(ctx context.Context,db database.Store,ps pubsub.Pubsub,tss*atomic.Pointer[schedule.TemplateScheduleStore],auditor*atomic.Pointer[audit.Auditor],acs*atomic.Pointer[dbauthz.AccessControlStore],log slog.Logger,tick<-chan time.Time,enqueuer notifications.Enqueuer)*Executor {
4852
le:=&Executor{
4953
//nolint:gocritic // Autostart has a limited set of permissions.
5054
ctx:dbauthz.AsAutostart(ctx),
@@ -55,6 +59,7 @@ func NewExecutor(ctx context.Context, db database.Store, ps pubsub.Pubsub, tss *
5559
log:log.Named("autobuild"),
5660
auditor:auditor,
5761
accessControlStore:acs,
62+
notificationsEnqueuer:enqueuer,
5863
}
5964
returnle
6065
}
@@ -138,11 +143,18 @@ func (e *Executor) runOnce(t time.Time) Stats {
138143
eg.Go(func()error {
139144
err:=func()error {
140145
varjob*database.ProvisionerJob
146+
varnextBuild*database.WorkspaceBuild
147+
varactiveTemplateVersion database.TemplateVersion
148+
varws database.Workspace
149+
141150
varauditLog*auditParams
151+
vardidAutoUpdatebool
142152
err:=e.db.InTx(func(tx database.Store)error {
153+
varerrerror
154+
143155
// Re-check eligibility since the first check was outside the
144156
// transaction and the workspace settings may have changed.
145-
ws,err:=tx.GetWorkspaceByID(e.ctx,wsID)
157+
ws,err=tx.GetWorkspaceByID(e.ctx,wsID)
146158
iferr!=nil {
147159
returnxerrors.Errorf("get workspace by id: %w",err)
148160
}
@@ -173,6 +185,11 @@ func (e *Executor) runOnce(t time.Time) Stats {
173185
returnxerrors.Errorf("get template by ID: %w",err)
174186
}
175187

188+
activeTemplateVersion,err=tx.GetTemplateVersionByID(e.ctx,template.ActiveVersionID)
189+
iferr!=nil {
190+
returnxerrors.Errorf("get active template version by ID: %w",err)
191+
}
192+
176193
accessControl:= (*(e.accessControlStore.Load())).GetTemplateAccessControl(template)
177194

178195
nextTransition,reason,err:=getNextTransition(user,ws,latestBuild,latestJob,templateSchedule,currentTick)
@@ -195,9 +212,15 @@ func (e *Executor) runOnce(t time.Time) Stats {
195212
useActiveVersion(accessControl,ws) {
196213
log.Debug(e.ctx,"autostarting with active version")
197214
builder=builder.ActiveVersion()
215+
216+
iflatestBuild.TemplateVersionID!=template.ActiveVersionID {
217+
// control flag to know if the workspace was auto-updated,
218+
// so the lifecycle executor can notify the user
219+
didAutoUpdate=true
220+
}
198221
}
199222

200-
_,job,err=builder.Build(e.ctx,tx,nil, audit.WorkspaceBuildBaggage{IP:"127.0.0.1"})
223+
nextBuild,job,err=builder.Build(e.ctx,tx,nil, audit.WorkspaceBuildBaggage{IP:"127.0.0.1"})
201224
iferr!=nil {
202225
returnxerrors.Errorf("build workspace with transition %q: %w",nextTransition,err)
203226
}
@@ -261,6 +284,25 @@ func (e *Executor) runOnce(t time.Time) Stats {
261284
auditLog.Success=err==nil
262285
auditBuild(e.ctx,log,*e.auditor.Load(),*auditLog)
263286
}
287+
ifdidAutoUpdate&&err==nil {
288+
nextBuildReason:=""
289+
ifnextBuild!=nil {
290+
nextBuildReason=string(nextBuild.Reason)
291+
}
292+
293+
if_,err:=e.notificationsEnqueuer.Enqueue(e.ctx,ws.OwnerID,notifications.WorkspaceAutoUpdated,
294+
map[string]string{
295+
"name":ws.Name,
296+
"initiator":"autobuild",
297+
"reason":nextBuildReason,
298+
"template_version_name":activeTemplateVersion.Name,
299+
},"autobuild",
300+
// Associate this notification with all the related entities.
301+
ws.ID,ws.OwnerID,ws.TemplateID,ws.OrganizationID,
302+
);err!=nil {
303+
log.Warn(e.ctx,"failed to notify of autoupdated workspace",slog.Error(err))
304+
}
305+
}
264306
iferr!=nil {
265307
returnxerrors.Errorf("transition workspace: %w",err)
266308
}

‎coderd/autobuild/lifecycle_executor_test.go

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/coder/coder/v2/coderd/coderdtest"
1919
"github.com/coder/coder/v2/coderd/database"
2020
"github.com/coder/coder/v2/coderd/database/dbauthz"
21+
"github.com/coder/coder/v2/coderd/notifications/notiffake"
2122
"github.com/coder/coder/v2/coderd/schedule"
2223
"github.com/coder/coder/v2/coderd/schedule/cron"
2324
"github.com/coder/coder/v2/coderd/util/ptr"
@@ -79,6 +80,7 @@ func TestExecutorAutostartTemplateUpdated(t *testing.T) {
7980
compatibleParametersbool
8081
expectStartbool
8182
expectUpdatebool
83+
expectNotificationbool
8284
}{
8385
{
8486
name:"Never",
@@ -93,6 +95,7 @@ func TestExecutorAutostartTemplateUpdated(t *testing.T) {
9395
compatibleParameters:true,
9496
expectStart:true,
9597
expectUpdate:true,
98+
expectNotification:true,
9699
},
97100
{
98101
name:"Always_Incompatible",
@@ -107,17 +110,19 @@ func TestExecutorAutostartTemplateUpdated(t *testing.T) {
107110
t.Run(tc.name,func(t*testing.T) {
108111
t.Parallel()
109112
var (
110-
sched=mustSchedule(t,"CRON_TZ=UTC 0 * * * *")
111-
ctx=context.Background()
112-
errerror
113-
tickCh=make(chan time.Time)
114-
statsCh=make(chan autobuild.Stats)
115-
logger=slogtest.Make(t,&slogtest.Options{IgnoreErrors:!tc.expectStart}).Leveled(slog.LevelDebug)
116-
client=coderdtest.New(t,&coderdtest.Options{
113+
sched=mustSchedule(t,"CRON_TZ=UTC 0 * * * *")
114+
ctx=context.Background()
115+
errerror
116+
tickCh=make(chan time.Time)
117+
statsCh=make(chan autobuild.Stats)
118+
logger=slogtest.Make(t,&slogtest.Options{IgnoreErrors:!tc.expectStart}).Leveled(slog.LevelDebug)
119+
enqueuer= notiffake.FakeNotificationEnqueuer{}
120+
client=coderdtest.New(t,&coderdtest.Options{
117121
AutobuildTicker:tickCh,
118122
IncludeProvisionerDaemon:true,
119123
AutobuildStats:statsCh,
120124
Logger:&logger,
125+
NotificationsEnqueuer:&enqueuer,
121126
})
122127
// Given: we have a user with a workspace that has autostart enabled
123128
workspace=mustProvisionWorkspace(t,client,func(cwr*codersdk.CreateWorkspaceRequest) {
@@ -195,6 +200,20 @@ func TestExecutorAutostartTemplateUpdated(t *testing.T) {
195200
assert.Equal(t,workspace.LatestBuild.TemplateVersionID,ws.LatestBuild.TemplateVersionID,
196201
"expected workspace build to be using the old template version")
197202
}
203+
204+
iftc.expectNotification {
205+
require.Len(t,enqueuer.Sent,1)
206+
require.Equal(t,enqueuer.Sent[0].UserID,workspace.OwnerID)
207+
require.Contains(t,enqueuer.Sent[0].Targets,workspace.TemplateID)
208+
require.Contains(t,enqueuer.Sent[0].Targets,workspace.ID)
209+
require.Contains(t,enqueuer.Sent[0].Targets,workspace.OrganizationID)
210+
require.Contains(t,enqueuer.Sent[0].Targets,workspace.OwnerID)
211+
require.Equal(t,newVersion.Name,enqueuer.Sent[0].Labels["template_version_name"])
212+
require.Equal(t,"autobuild",enqueuer.Sent[0].Labels["initiator"])
213+
require.Equal(t,"autostart",enqueuer.Sent[0].Labels["reason"])
214+
}else {
215+
require.Len(t,enqueuer.Sent,0)
216+
}
198217
})
199218
}
200219
}

‎coderd/coderdtest/coderdtest.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ import (
6464
"github.com/coder/coder/v2/coderd/externalauth"
6565
"github.com/coder/coder/v2/coderd/gitsshkey"
6666
"github.com/coder/coder/v2/coderd/httpmw"
67+
"github.com/coder/coder/v2/coderd/notifications"
68+
"github.com/coder/coder/v2/coderd/notifications/notiffake"
6769
"github.com/coder/coder/v2/coderd/rbac"
6870
"github.com/coder/coder/v2/coderd/schedule"
6971
"github.com/coder/coder/v2/coderd/telemetry"
@@ -154,6 +156,8 @@ type Options struct {
154156
DatabaseRolluper*dbrollup.Rolluper
155157
WorkspaceUsageTrackerFlushchanint
156158
WorkspaceUsageTrackerTickchan time.Time
159+
160+
NotificationsEnqueuer notifications.Enqueuer
157161
}
158162

159163
// New constructs a codersdk client connected to an in-memory API instance.
@@ -238,6 +242,10 @@ func NewOptions(t testing.TB, options *Options) (func(http.Handler), context.Can
238242
options.Database,options.Pubsub=dbtestutil.NewDB(t)
239243
}
240244

245+
ifoptions.NotificationsEnqueuer==nil {
246+
options.NotificationsEnqueuer=new(notiffake.FakeNotificationEnqueuer)
247+
}
248+
241249
accessControlStore:=&atomic.Pointer[dbauthz.AccessControlStore]{}
242250
varacs dbauthz.AccessControlStore= dbauthz.AGPLTemplateAccessControlStore{}
243251
accessControlStore.Store(&acs)
@@ -305,6 +313,7 @@ func NewOptions(t testing.TB, options *Options) (func(http.Handler), context.Can
305313
accessControlStore,
306314
*options.Logger,
307315
options.AutobuildTicker,
316+
options.NotificationsEnqueuer,
308317
).WithStatsChannel(options.AutobuildStats)
309318
lifecycleExecutor.Run()
310319

@@ -498,6 +507,7 @@ func NewOptions(t testing.TB, options *Options) (func(http.Handler), context.Can
498507
NewTicker:options.NewTicker,
499508
DatabaseRolluper:options.DatabaseRolluper,
500509
WorkspaceUsageTracker:wuTracker,
510+
NotificationsEnqueuer:options.NotificationsEnqueuer,
501511
}
502512
}
503513

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
DELETEFROM notification_templatesWHERE id='c34a0c09-0704-4cac-bd1c-0c0146811c2b';
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
INSERT INTO notification_templates (id, name, title_template, body_template,"group", actions)
2+
VALUES ('c34a0c09-0704-4cac-bd1c-0c0146811c2b','Workspace updated automatically', E'Workspace "{{.Labels.name}}" updated automatically',
3+
E'Hi {{.UserName}}\n\Your workspace **{{.Labels.name}}** has been updated automatically to the latest template version ({{.Labels.template_version_name}}).',
4+
'Workspace Events','[
5+
{
6+
"label": "View workspace",
7+
"url": "{{ base_url }}/@{{.UserName}}/{{.Labels.name}}"
8+
}
9+
]'::jsonb);

‎coderd/notifications/events.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ import "github.com/google/uuid"
99
var (
1010
TemplateWorkspaceDeleted=uuid.MustParse("f517da0b-cdc9-410f-ab89-a86107c420ed")
1111
WorkspaceAutobuildFailed=uuid.MustParse("381df2a9-c0c0-4749-420f-80a9280c66f9")
12+
WorkspaceAutoUpdated=uuid.MustParse("c34a0c09-0704-4cac-bd1c-0c0146811c2b")
1213
)
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package notiffake
2+
3+
import (
4+
"context"
5+
"sync"
6+
7+
"github.com/google/uuid"
8+
)
9+
10+
typeFakeNotificationEnqueuerstruct {
11+
mu sync.Mutex
12+
13+
Sent []*Notification
14+
}
15+
16+
typeNotificationstruct {
17+
UserID,TemplateID uuid.UUID
18+
Labelsmap[string]string
19+
CreatedBystring
20+
Targets []uuid.UUID
21+
}
22+
23+
func (f*FakeNotificationEnqueuer)Enqueue(_ context.Context,userID,templateID uuid.UUID,labelsmap[string]string,createdBystring,targets...uuid.UUID) (*uuid.UUID,error) {
24+
f.mu.Lock()
25+
deferf.mu.Unlock()
26+
27+
f.Sent=append(f.Sent,&Notification{
28+
UserID:userID,
29+
TemplateID:templateID,
30+
Labels:labels,
31+
CreatedBy:createdBy,
32+
Targets:targets,
33+
})
34+
35+
id:=uuid.New()
36+
return&id,nil
37+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp