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(coderd): notify when workspace is marked as dormant#13868

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
BrunoQuaresma merged 44 commits intomainfrombq/implement-notifications
Jul 24, 2024
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
Show all changes
44 commits
Select commitHold shift + click to select a range
9a398e7
feat(coderd): notify when workspace is marked as dormant
BrunoQuaresmaJul 10, 2024
fbf99be
Fix role
BrunoQuaresmaJul 10, 2024
40e5801
Apply Danny suggestions
BrunoQuaresmaJul 11, 2024
9a35cbb
Notify dormant workspace on lifecycle executor
BrunoQuaresmaJul 11, 2024
0701572
Notify dormancy on template schedule
BrunoQuaresmaJul 11, 2024
64cf76b
Apply Danny review suggestions
BrunoQuaresmaJul 12, 2024
b2f9180
Fix mismatch usage
BrunoQuaresmaJul 12, 2024
a445c4c
Resolve migration conflict
BrunoQuaresmaJul 12, 2024
bdc08d6
Merge branch 'main' of https://github.com/coder/coder into bq/impleme…
BrunoQuaresmaJul 12, 2024
ee7d542
Return error instead of receiving the logger
BrunoQuaresmaJul 12, 2024
f6db3c7
Improve verbiage
BrunoQuaresmaJul 12, 2024
95c784a
Merge branch 'main' of github.com:/coder/coder into bq/implement-noti…
dannykoppingJul 15, 2024
68bcfb0
Merge branch 'main' of github.com:/coder/coder into bq/implement-noti…
dannykoppingJul 16, 2024
467a797
Possible implementation simplification
dannykoppingJul 16, 2024
853e59e
Merge branch 'main' of https://github.com/coder/coder into bq/impleme…
BrunoQuaresmaJul 17, 2024
bbcb28e
Apply fmt
BrunoQuaresmaJul 17, 2024
e3c6f49
Notify after executor is done
BrunoQuaresmaJul 17, 2024
20a8766
Revert refactoring mistake
dannykoppingJul 18, 2024
e087172
Add workspace name to the log
BrunoQuaresmaJul 18, 2024
4b99061
Set a fake enqueuer on coderdtest options
BrunoQuaresmaJul 18, 2024
1932168
Merge branch 'bq/implement-notifications' of https://github.com/coder…
BrunoQuaresmaJul 18, 2024
36c043c
Fix migration
BrunoQuaresmaJul 18, 2024
d07f9d8
Fix migration sql
BrunoQuaresmaJul 18, 2024
21f7c35
Merge branch 'main' of https://github.com/coder/coder into bq/impleme…
BrunoQuaresmaJul 18, 2024
72da82d
Fix typo
BrunoQuaresmaJul 18, 2024
b49cd08
Fix migration number
BrunoQuaresmaJul 18, 2024
6f61f9f
Merge branch 'main' of https://github.com/coder/coder into bq/impleme…
BrunoQuaresmaJul 18, 2024
281b545
Apply Marcin comments
BrunoQuaresmaJul 18, 2024
7f73d90
Fix lint
BrunoQuaresmaJul 18, 2024
7c5de97
Update coderd/autobuild/lifecycle_executor.go
BrunoQuaresmaJul 18, 2024
299cd7f
Apply dannys comment
BrunoQuaresmaJul 18, 2024
16a3c19
Simplify dormancy template
BrunoQuaresmaJul 22, 2024
cf9eaec
Merge branch 'main' of https://github.com/coder/coder into bq/impleme…
BrunoQuaresmaJul 22, 2024
aea55aa
Add test to verify dormancy in lifecycle executor
BrunoQuaresmaJul 22, 2024
6d47c04
Add placeholder
BrunoQuaresmaJul 22, 2024
7b4ae29
make test pass
sreyaJul 23, 2024
308bd69
fix: lint
mtojekJul 23, 2024
10ff6cf
Merge branch 'main' of https://github.com/coder/coder into bq/impleme…
BrunoQuaresmaJul 23, 2024
48ad269
Add test to verify notification in lifecycle executor
BrunoQuaresmaJul 23, 2024
0226fdf
Add notification for marked as deletion
BrunoQuaresmaJul 23, 2024
4d71c94
Apply Dannys review comments
BrunoQuaresmaJul 24, 2024
e1d5fec
Fix SQL
BrunoQuaresmaJul 24, 2024
a2023a1
Rollback dormant at test
BrunoQuaresmaJul 24, 2024
bae985c
Fix template
BrunoQuaresmaJul 24, 2024
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
37 changes: 28 additions & 9 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/dormancy"
"github.com/coder/coder/v2/coderd/notifications"
"github.com/coder/coder/v2/coderd/schedule"
"github.com/coder/coder/v2/coderd/wsbuilder"
Expand All@@ -35,7 +36,6 @@ 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
}
Expand DownExpand Up@@ -142,13 +142,15 @@ 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
var (
job *database.ProvisionerJob
auditLog *auditParams
dormantNotification *dormancy.WorkspaceDormantNotification
nextBuild *database.WorkspaceBuild
activeTemplateVersion database.TemplateVersion
ws database.Workspace
didAutoUpdate bool
)
err := e.db.InTx(func(tx database.Store) error {
var err error

Expand DownExpand Up@@ -246,6 +248,13 @@ func (e *Executor) runOnce(t time.Time) Stats {
return xerrors.Errorf("update workspace dormant deleting at: %w", err)
}

dormantNotification = &dormancy.WorkspaceDormantNotification{
Workspace: ws,
Initiator: "autobuild",
Reason: "breached the template's threshold for inactivity",
CreatedBy: "lifecycleexecutor",
}

log.Info(e.ctx, "dormant workspace",
slog.F("last_used_at", ws.LastUsedAt),
slog.F("time_til_dormant", templateSchedule.TimeTilDormant),
Expand DownExpand Up@@ -290,7 +299,7 @@ func (e *Executor) runOnce(t time.Time) Stats {
nextBuildReason = string(nextBuild.Reason)
}

if _, err := e.notificationsEnqueuer.Enqueue(e.ctx, ws.OwnerID, notifications.WorkspaceAutoUpdated,
if _, err := e.notificationsEnqueuer.Enqueue(e.ctx, ws.OwnerID, notifications.TemplateWorkspaceAutoUpdated,
map[string]string{
"name": ws.Name,
"initiator": "autobuild",
Expand All@@ -316,6 +325,16 @@ func (e *Executor) runOnce(t time.Time) Stats {
return xerrors.Errorf("post provisioner job to pubsub: %w", err)
}
}
if dormantNotification != nil {
_, err = dormancy.NotifyWorkspaceDormant(
e.ctx,
e.notificationsEnqueuer,
*dormantNotification,
)
if err != nil {
log.Warn(e.ctx, "failed to notify of workspace marked as dormant", slog.Error(err), slog.F("workspace_id", dormantNotification.Workspace.ID))
}
}
return nil
}()
if err != nil {
Expand Down
66 changes: 65 additions & 1 deletioncoderd/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"
"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@@ -115,7 +116,7 @@ func TestExecutorAutostartTemplateUpdated(t *testing.T) {
tickCh = make(chan time.Time)
statsCh = make(chan autobuild.Stats)
logger = slogtest.Make(t, &slogtest.Options{IgnoreErrors: !tc.expectStart}).Leveled(slog.LevelDebug)
enqueuer = testutil.FakeNotificationEnqueuer{}
enqueuer = testutil.FakeNotificationsEnqueuer{}
client = coderdtest.New(t, &coderdtest.Options{
AutobuildTicker: tickCh,
IncludeProvisionerDaemon: true,
Expand DownExpand Up@@ -1062,6 +1063,69 @@ func TestExecutorInactiveWorkspace(t *testing.T) {
})
}

func TestNotifications(t *testing.T) {
t.Parallel()

t.Run("Dormancy", func(t *testing.T) {
t.Parallel()

// Setup template with dormancy and create a workspace with it
var (
ticker = make(chan time.Time)
statCh = make(chan autobuild.Stats)
notifyEnq = testutil.FakeNotificationsEnqueuer{}
timeTilDormant = time.Minute
client = coderdtest.New(t, &coderdtest.Options{
AutobuildTicker: ticker,
AutobuildStats: statCh,
IncludeProvisionerDaemon: true,
NotificationsEnqueuer: &notifyEnq,
TemplateScheduleStore: schedule.MockTemplateScheduleStore{
GetFn: func(_ context.Context, _ database.Store, _ uuid.UUID) (schedule.TemplateScheduleOptions, error) {
return schedule.TemplateScheduleOptions{
UserAutostartEnabled: false,
UserAutostopEnabled: true,
DefaultTTL: 0,
AutostopRequirement: schedule.TemplateAutostopRequirement{},
TimeTilDormant: timeTilDormant,
}, nil
},
},
})
admin = coderdtest.CreateFirstUser(t, client)
version = coderdtest.CreateTemplateVersion(t, client, admin.OrganizationID, nil)
)

coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, admin.OrganizationID, version.ID)
userClient, _ := coderdtest.CreateAnotherUser(t, client, admin.OrganizationID)
workspace := coderdtest.CreateWorkspace(t, userClient, admin.OrganizationID, template.ID)
coderdtest.AwaitWorkspaceBuildJobCompleted(t, userClient, workspace.LatestBuild.ID)

// Stop workspace
workspace = coderdtest.MustTransitionWorkspace(t, client, workspace.ID, database.WorkspaceTransitionStart, database.WorkspaceTransitionStop)
_ = coderdtest.AwaitWorkspaceBuildJobCompleted(t, userClient, workspace.LatestBuild.ID)

// Wait for workspace to become dormant
ticker <- workspace.LastUsedAt.Add(timeTilDormant * 3)
_ = testutil.RequireRecvCtx(testutil.Context(t, testutil.WaitShort), t, statCh)

// Check that the workspace is dormant
workspace = coderdtest.MustWorkspace(t, client, workspace.ID)
require.NotNil(t, workspace.DormantAt)

// Check that a notification was enqueued
require.Len(t, notifyEnq.Sent, 1)
require.Equal(t, notifyEnq.Sent[0].UserID, workspace.OwnerID)
require.Equal(t, notifyEnq.Sent[0].TemplateID, notifications.TemplateWorkspaceDormant)
require.Contains(t, notifyEnq.Sent[0].Targets, template.ID)
require.Contains(t, notifyEnq.Sent[0].Targets, workspace.ID)
require.Contains(t, notifyEnq.Sent[0].Targets, workspace.OrganizationID)
require.Contains(t, notifyEnq.Sent[0].Targets, workspace.OwnerID)
require.Equal(t, notifyEnq.Sent[0].Labels["initiator"], "autobuild")
})
}

func mustProvisionWorkspace(t *testing.T, client *codersdk.Client, mut ...func(*codersdk.CreateWorkspaceRequest)) codersdk.Workspace {
t.Helper()
user := coderdtest.CreateFirstUser(t, client)
Expand Down
5 changes: 4 additions & 1 deletioncoderd/coderdtest/coderdtest.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -242,7 +242,7 @@ func NewOptions(t testing.TB, options *Options) (func(http.Handler), context.Can
}

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

accessControlStore := &atomic.Pointer[dbauthz.AccessControlStore]{}
Expand DownExpand Up@@ -289,6 +289,9 @@ func NewOptions(t testing.TB, options *Options) (func(http.Handler), context.Can
options.StatsBatcher = batcher
t.Cleanup(closeBatcher)
}
if options.NotificationsEnqueuer == nil {
options.NotificationsEnqueuer = &testutil.FakeNotificationsEnqueuer{}
}

var templateScheduleStore atomic.Pointer[schedule.TemplateScheduleStore]
if options.TemplateScheduleStore == nil {
Expand Down
13 changes: 8 additions & 5 deletionscoderd/database/dbauthz/dbauthz.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -3555,12 +3555,15 @@ func (q *querier) UpdateWorkspaceTTL(ctx context.Context, arg database.UpdateWor
return update(q.log, q.auth, fetch, q.db.UpdateWorkspaceTTL)(ctx, arg)
}

func (q *querier) UpdateWorkspacesDormantDeletingAtByTemplateID(ctx context.Context, arg database.UpdateWorkspacesDormantDeletingAtByTemplateIDParams) error {
fetch := func(ctx context.Context, arg database.UpdateWorkspacesDormantDeletingAtByTemplateIDParams) (database.Template, error) {
return q.db.GetTemplateByID(ctx, arg.TemplateID)
func (q *querier) UpdateWorkspacesDormantDeletingAtByTemplateID(ctx context.Context, arg database.UpdateWorkspacesDormantDeletingAtByTemplateIDParams) ([]database.Workspace, error) {
template, err := q.db.GetTemplateByID(ctx, arg.TemplateID)
if err != nil {
return nil, xerrors.Errorf("get template by id: %w", err)
}

return fetchAndExec(q.log, q.auth, policy.ActionUpdate, fetch, q.db.UpdateWorkspacesDormantDeletingAtByTemplateID)(ctx, arg)
if err := q.authorizeContext(ctx, policy.ActionUpdate, template); err != nil {
return nil, err
}
return q.db.UpdateWorkspacesDormantDeletingAtByTemplateID(ctx, arg)
}

func (q *querier) UpsertAnnouncementBanners(ctx context.Context, value string) error {
Expand Down
8 changes: 5 additions & 3 deletionscoderd/database/dbmem/dbmem.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -8700,15 +8700,16 @@ func (q *FakeQuerier) UpdateWorkspaceTTL(_ context.Context, arg database.UpdateW
return sql.ErrNoRows
}

func (q *FakeQuerier) UpdateWorkspacesDormantDeletingAtByTemplateID(_ context.Context, arg database.UpdateWorkspacesDormantDeletingAtByTemplateIDParams) error {
func (q *FakeQuerier) UpdateWorkspacesDormantDeletingAtByTemplateID(_ context.Context, arg database.UpdateWorkspacesDormantDeletingAtByTemplateIDParams)([]database.Workspace,error) {
q.mutex.Lock()
defer q.mutex.Unlock()

err := validateDatabaseType(arg)
if err != nil {
return err
returnnil,err
}

affectedRows := []database.Workspace{}
for i, ws := range q.workspaces {
if ws.TemplateID != arg.TemplateID {
continue
Expand All@@ -8733,9 +8734,10 @@ func (q *FakeQuerier) UpdateWorkspacesDormantDeletingAtByTemplateID(_ context.Co
}
ws.DeletingAt = deletingAt
q.workspaces[i] = ws
affectedRows = append(affectedRows, ws)
}

return nil
returnaffectedRows,nil
}

func (q *FakeQuerier) UpsertAnnouncementBanners(_ context.Context, data string) error {
Expand Down
6 changes: 3 additions & 3 deletionscoderd/database/dbmetrics/dbmetrics.go
View file
Open in desktop

Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.

7 changes: 4 additions & 3 deletionscoderd/database/dbmock/dbmock.go
View file
Open in desktop

Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.

View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
DELETE FROM notification_templates
WHERE
id = '0ea69165-ec14-4314-91f1-69566ac3c5a0';

DELETE FROM notification_templates
WHERE
id = '51ce2fdf-c9ca-4be1-8d70-628674f9bc42';
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
INSERT INTO
notification_templates (
id,
name,
title_template,
body_template,
"group",
actions
)
VALUES (
'0ea69165-ec14-4314-91f1-69566ac3c5a0',
'Workspace Marked as Dormant',
E'Workspace "{{.Labels.name}}" marked as dormant',
E'Hi {{.UserName}}\n\n'|| E'Your workspace **{{.Labels.name}}** has been marked as **dormant**.\n'|| E'The specified reason was "**{{.Labels.reason}} (initiated by: {{ .Labels.initiator }}){{end}}**\n\n'|| E'Dormancy refers to a workspace being unused for a defined length of time, and after it exceeds {{.Labels.dormancyHours}} hours of dormancy might be deleted.\n'|| E'To activate your workspace again, simply use it as normal.',
Copy link
Collaborator

Choose a reason for hiding this comment

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

The wording is a little weird here. Can we change it and add a link to the dormancy docs:

I don't think we need to include the initiator, just improve the reasons (see end of message).

Hi{user-name}, your workspace{ws-name} has been markeddormant because of{reason}.

Append if dormancy cleanup is enabled anddormancy_hours < 24:

Dormant workspaces areautomatically deleted after{dormancy_hours} hours of inactivity.

Append if dormancy cleanup is enabled, anddormancy_hours > 24:

Dormant workspaces areautomatically deleted after{dormancy_hours // 24} days of inactivity.

To prevent deletion, use your workspace with the link below.


The reasons also fit into differing grammatic context:

  • Lifecycle executor: "breached the template's threshold for inactivity"
  • API: "requested by user"
  • Template: "template updated to new dormancy policy"

If we use these, they'd fit the above messages better.

  • Lifcycle: "prolonged inactivity, exceeding the dormancy threshold"
  • API: "a user request"
  • Template: "an update to the template's dormancy"

BrunoQuaresma reacted with thumbs up emoji
Copy link
CollaboratorAuthor

Choose a reason for hiding this comment

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

Going to refactor the templates in the next PR to avoid further postponement.

'Workspace Events',
'[
{
"label": "View workspace",
"url": "{{ base_url }}/@{{.UserName}}/{{.Labels.name}}"
}
]'::jsonb
),
(
'51ce2fdf-c9ca-4be1-8d70-628674f9bc42',
'Workspace Marked for Deletion',
E'Workspace "{{.Labels.name}}" marked for deletion',
E'Hi {{.UserName}}\n\n'|| E'Your workspace **{{.Labels.name}}** has been marked for **deletion** after {{.Labels.dormancyHours}} hours of dormancy.\n'|| E'The specified reason was "**{{.Labels.reason}}{{end}}**\n\n'|| E'Dormancy refers to a workspace being unused for a defined length of time, and after it exceeds {{.Labels.dormancyHours}} hours of dormancy it will be deleted.\n'|| E'To prevent your workspace from being deleted, simply use it as normal.',
'Workspace Events',
'[
{
"label": "View workspace",
"url": "{{ base_url }}/@{{.UserName}}/{{.Labels.name}}"
}
]'::jsonb
);
2 changes: 1 addition & 1 deletioncoderd/database/querier.go
View file
Open in desktop

Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.

43 changes: 39 additions & 4 deletionscoderd/database/queries.sql.go
View file
Open in desktop

Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.

Loading

[8]ページ先頭

©2009-2025 Movatter.jp