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

feat: notify on successful autoupdate#13903

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

Merged
mtojek merged 10 commits intomainfrom7-auto-update
Jul 18, 2024
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletioncli/server.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -1066,7 +1066,7 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
autobuildTicker := time.NewTicker(vals.AutobuildPollInterval.Value())
defer autobuildTicker.Stop()
autobuildExecutor := autobuild.NewExecutor(
ctx, options.Database, options.Pubsub, coderAPI.TemplateScheduleStore, &coderAPI.Auditor, coderAPI.AccessControlStore, logger, autobuildTicker.C)
ctx, options.Database, options.Pubsub, coderAPI.TemplateScheduleStore, &coderAPI.Auditor, coderAPI.AccessControlStore, logger, autobuildTicker.C, options.NotificationsEnqueuer)
autobuildExecutor.Run()

hangDetectorTicker := time.NewTicker(vals.JobHangDetectorInterval.Value())
Expand Down
48 changes: 45 additions & 3 deletionscoderd/autobuild/lifecycle_executor.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,7 @@ import (
"github.com/coder/coder/v2/coderd/database/dbtime"
"github.com/coder/coder/v2/coderd/database/provisionerjobs"
"github.com/coder/coder/v2/coderd/database/pubsub"
"github.com/coder/coder/v2/coderd/notifications"
"github.com/coder/coder/v2/coderd/schedule"
"github.com/coder/coder/v2/coderd/wsbuilder"
)
Expand All@@ -34,6 +35,9 @@ type Executor struct {
log slog.Logger
tick <-chan time.Time
statsCh chan<- Stats

// NotificationsEnqueuer handles enqueueing notifications for delivery by SMTP, webhook, etc.
notificationsEnqueuer notifications.Enqueuer
}

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

// New returns a new wsactions executor.
func NewExecutor(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 {
func NewExecutor(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 {
le := &Executor{
//nolint:gocritic // Autostart has a limited set of permissions.
ctx: dbauthz.AsAutostart(ctx),
Expand All@@ -55,6 +59,7 @@ func NewExecutor(ctx context.Context, db database.Store, ps pubsub.Pubsub, tss *
log: log.Named("autobuild"),
auditor: auditor,
accessControlStore: acs,
notificationsEnqueuer: enqueuer,
}
return le
}
Expand DownExpand Up@@ -138,11 +143,18 @@ func (e *Executor) runOnce(t time.Time) Stats {
eg.Go(func() error {
err := func() error {
var job *database.ProvisionerJob
var nextBuild *database.WorkspaceBuild
var activeTemplateVersion database.TemplateVersion
var ws database.Workspace

var auditLog *auditParams
var didAutoUpdate bool
err := e.db.InTx(func(tx database.Store) error {
var err error

// Re-check eligibility since the first check was outside the
// transaction and the workspace settings may have changed.
ws, err:= tx.GetWorkspaceByID(e.ctx, wsID)
ws, err = tx.GetWorkspaceByID(e.ctx, wsID)
if err != nil {
return xerrors.Errorf("get workspace by id: %w", err)
}
Expand DownExpand Up@@ -173,6 +185,11 @@ func (e *Executor) runOnce(t time.Time) Stats {
return xerrors.Errorf("get template by ID: %w", err)
}

activeTemplateVersion, err = tx.GetTemplateVersionByID(e.ctx, template.ActiveVersionID)
if err != nil {
return xerrors.Errorf("get active template version by ID: %w", err)
}

accessControl := (*(e.accessControlStore.Load())).GetTemplateAccessControl(template)

nextTransition, reason, err := getNextTransition(user, ws, latestBuild, latestJob, templateSchedule, currentTick)
Expand All@@ -195,9 +212,15 @@ func (e *Executor) runOnce(t time.Time) Stats {
useActiveVersion(accessControl, ws) {
log.Debug(e.ctx, "autostarting with active version")
builder = builder.ActiveVersion()

if latestBuild.TemplateVersionID != template.ActiveVersionID {
// control flag to know if the workspace was auto-updated,
// so the lifecycle executor can notify the user
didAutoUpdate = true
}
}

_, job, err = builder.Build(e.ctx, tx, nil, audit.WorkspaceBuildBaggage{IP: "127.0.0.1"})
nextBuild, job, err = builder.Build(e.ctx, tx, nil, audit.WorkspaceBuildBaggage{IP: "127.0.0.1"})
if err != nil {
return xerrors.Errorf("build workspace with transition %q: %w", nextTransition, err)
}
Expand DownExpand Up@@ -261,6 +284,25 @@ func (e *Executor) runOnce(t time.Time) Stats {
auditLog.Success = err == nil
auditBuild(e.ctx, log, *e.auditor.Load(), *auditLog)
}
if didAutoUpdate && err == nil {
nextBuildReason := ""
if nextBuild != nil {
nextBuildReason = string(nextBuild.Reason)
}

if _, err := e.notificationsEnqueuer.Enqueue(e.ctx, ws.OwnerID, notifications.WorkspaceAutoUpdated,
map[string]string{
"name": ws.Name,
"initiator": "autobuild",
"reason": nextBuildReason,
"template_version_name": activeTemplateVersion.Name,
}, "autobuild",
// Associate this notification with all the related entities.
ws.ID, ws.OwnerID, ws.TemplateID, ws.OrganizationID,
); err != nil {
log.Warn(e.ctx, "failed to notify of autoupdated workspace", slog.Error(err))
}
}
if err != nil {
return xerrors.Errorf("transition workspace: %w", err)
}
Expand Down
33 changes: 26 additions & 7 deletionscoderd/autobuild/lifecycle_executor_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,6 +18,7 @@ import (
"github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbauthz"
"github.com/coder/coder/v2/coderd/notifications/notiffake"
"github.com/coder/coder/v2/coderd/schedule"
"github.com/coder/coder/v2/coderd/schedule/cron"
"github.com/coder/coder/v2/coderd/util/ptr"
Expand DownExpand Up@@ -79,6 +80,7 @@ func TestExecutorAutostartTemplateUpdated(t *testing.T) {
compatibleParameters bool
expectStart bool
expectUpdate bool
expectNotification bool
}{
{
name: "Never",
Expand All@@ -93,6 +95,7 @@ func TestExecutorAutostartTemplateUpdated(t *testing.T) {
compatibleParameters: true,
expectStart: true,
expectUpdate: true,
expectNotification: true,
},
{
name: "Always_Incompatible",
Expand All@@ -107,17 +110,19 @@ func TestExecutorAutostartTemplateUpdated(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
var (
sched = mustSchedule(t, "CRON_TZ=UTC 0 * * * *")
ctx = context.Background()
err error
tickCh = make(chan time.Time)
statsCh = make(chan autobuild.Stats)
logger = slogtest.Make(t, &slogtest.Options{IgnoreErrors: !tc.expectStart}).Leveled(slog.LevelDebug)
client = coderdtest.New(t, &coderdtest.Options{
sched = mustSchedule(t, "CRON_TZ=UTC 0 * * * *")
ctx = context.Background()
err error
tickCh = make(chan time.Time)
statsCh = make(chan autobuild.Stats)
logger = slogtest.Make(t, &slogtest.Options{IgnoreErrors: !tc.expectStart}).Leveled(slog.LevelDebug)
enqueuer = notiffake.FakeNotificationEnqueuer{}
client = coderdtest.New(t, &coderdtest.Options{
AutobuildTicker: tickCh,
IncludeProvisionerDaemon: true,
AutobuildStats: statsCh,
Logger: &logger,
NotificationsEnqueuer: &enqueuer,
})
// Given: we have a user with a workspace that has autostart enabled
workspace = mustProvisionWorkspace(t, client, func(cwr *codersdk.CreateWorkspaceRequest) {
Expand DownExpand Up@@ -195,6 +200,20 @@ func TestExecutorAutostartTemplateUpdated(t *testing.T) {
assert.Equal(t, workspace.LatestBuild.TemplateVersionID, ws.LatestBuild.TemplateVersionID,
"expected workspace build to be using the old template version")
}

if tc.expectNotification {
require.Len(t, enqueuer.Sent, 1)
require.Equal(t, enqueuer.Sent[0].UserID, workspace.OwnerID)
require.Contains(t, enqueuer.Sent[0].Targets, workspace.TemplateID)
require.Contains(t, enqueuer.Sent[0].Targets, workspace.ID)
require.Contains(t, enqueuer.Sent[0].Targets, workspace.OrganizationID)
require.Contains(t, enqueuer.Sent[0].Targets, workspace.OwnerID)
require.Equal(t, newVersion.Name, enqueuer.Sent[0].Labels["template_version_name"])
require.Equal(t, "autobuild", enqueuer.Sent[0].Labels["initiator"])
require.Equal(t, "autostart", enqueuer.Sent[0].Labels["reason"])
} else {
require.Len(t, enqueuer.Sent, 0)
}
})
}
}
Expand Down
10 changes: 10 additions & 0 deletionscoderd/coderdtest/coderdtest.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -64,6 +64,8 @@ import (
"github.com/coder/coder/v2/coderd/externalauth"
"github.com/coder/coder/v2/coderd/gitsshkey"
"github.com/coder/coder/v2/coderd/httpmw"
"github.com/coder/coder/v2/coderd/notifications"
"github.com/coder/coder/v2/coderd/notifications/notiffake"
"github.com/coder/coder/v2/coderd/rbac"
"github.com/coder/coder/v2/coderd/schedule"
"github.com/coder/coder/v2/coderd/telemetry"
Expand DownExpand Up@@ -154,6 +156,8 @@ type Options struct {
DatabaseRolluper *dbrollup.Rolluper
WorkspaceUsageTrackerFlush chan int
WorkspaceUsageTrackerTick chan time.Time

NotificationsEnqueuer notifications.Enqueuer
}

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

if options.NotificationsEnqueuer == nil {
options.NotificationsEnqueuer = new(notiffake.FakeNotificationEnqueuer)
}

accessControlStore := &atomic.Pointer[dbauthz.AccessControlStore]{}
var acs dbauthz.AccessControlStore = dbauthz.AGPLTemplateAccessControlStore{}
accessControlStore.Store(&acs)
Expand DownExpand Up@@ -305,6 +313,7 @@ func NewOptions(t testing.TB, options *Options) (func(http.Handler), context.Can
accessControlStore,
*options.Logger,
options.AutobuildTicker,
options.NotificationsEnqueuer,
).WithStatsChannel(options.AutobuildStats)
lifecycleExecutor.Run()

Expand DownExpand Up@@ -498,6 +507,7 @@ func NewOptions(t testing.TB, options *Options) (func(http.Handler), context.Can
NewTicker: options.NewTicker,
DatabaseRolluper: options.DatabaseRolluper,
WorkspaceUsageTracker: wuTracker,
NotificationsEnqueuer: options.NotificationsEnqueuer,
}
}

Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
DELETE FROM notification_templates WHERE id = 'c34a0c09-0704-4cac-bd1c-0c0146811c2b';
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
INSERT INTO notification_templates (id, name, title_template, body_template, "group", actions)
VALUES ('c34a0c09-0704-4cac-bd1c-0c0146811c2b', 'Workspace updated automatically', E'Workspace "{{.Labels.name}}" updated automatically',
E'Hi {{.UserName}}\n\Your workspace **{{.Labels.name}}** has been updated automatically to the latest template version ({{.Labels.template_version_name}}).',
'Workspace Events', '[
{
"label": "View workspace",
"url": "{{ base_url }}/@{{.UserName}}/{{.Labels.name}}"
}
]'::jsonb);
1 change: 1 addition & 0 deletionscoderd/notifications/events.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,4 +9,5 @@ import "github.com/google/uuid"
var (
TemplateWorkspaceDeleted=uuid.MustParse("f517da0b-cdc9-410f-ab89-a86107c420ed")
WorkspaceAutobuildFailed=uuid.MustParse("381df2a9-c0c0-4749-420f-80a9280c66f9")
WorkspaceAutoUpdated=uuid.MustParse("c34a0c09-0704-4cac-bd1c-0c0146811c2b")
)
37 changes: 37 additions & 0 deletionscoderd/notifications/notiffake/notiffake.go
View file
Open in desktop
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

In the other PR, we did this a bit differently. We added this fake helper into thetestutils package.Link to PR

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Yes, I'm aware of it. This is an alternative approach, and I'm happy to leave it or switch to the other form.@dannykopping any preference?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I prefer names which are explicit and clear, so I prefertestutils TBH.

mtojek reacted with thumbs up emoji
Copy link
MemberAuthor

@mtojekmtojekJul 18, 2024
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I will wait with merging this PR until Bruno merges#13868, then adjust it.

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
package notiffake
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Nit: I like splitting this out but I think the name is too "cute".
I think something liketestutil or something would be more idiomatic.

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Alright, I decided to merge it as is, and refactor/unify once#13868 is merged.


import (
"context"
"sync"

"github.com/google/uuid"
)

type FakeNotificationEnqueuer struct {
mu sync.Mutex

Sent []*Notification
}

type Notification struct {
UserID, TemplateID uuid.UUID
Labels map[string]string
CreatedBy string
Targets []uuid.UUID
}

func (f *FakeNotificationEnqueuer) Enqueue(_ context.Context, userID, templateID uuid.UUID, labels map[string]string, createdBy string, targets ...uuid.UUID) (*uuid.UUID, error) {
f.mu.Lock()
defer f.mu.Unlock()

f.Sent = append(f.Sent, &Notification{
UserID: userID,
TemplateID: templateID,
Labels: labels,
CreatedBy: createdBy,
Targets: targets,
})

id := uuid.New()
return &id, nil
}
Loading

[8]ページ先頭

©2009-2025 Movatter.jp