Skip to content

Commit 68bcfb0

Browse files
committed
Merge branch 'main' of github.com:/coder/coder into bq/implement-notifications
2 parents 95c784a + a5e4bf3 commit 68bcfb0

File tree

60 files changed

+478
-301
lines changed

Some content is hidden

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

60 files changed

+478
-301
lines changed

.github/actions/setup-tf/action.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@ runs:
77
- name: Install Terraform
88
uses: hashicorp/setup-terraform@v3
99
with:
10-
terraform_version: 1.8.4
10+
terraform_version: 1.9.2
1111
terraform_wrapper: false

.github/workflows/ci.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ jobs:
170170
171171
# Check for any typos
172172
- name: Check for typos
173-
uses: crate-ci/typos@v1.23.1
173+
uses: crate-ci/typos@v1.23.2
174174
with:
175175
config: .github/workflows/typos.toml
176176

.github/workflows/security.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ jobs:
114114
echo "image=$(cat "$image_job")" >> $GITHUB_OUTPUT
115115
116116
- name: Run Trivy vulnerability scanner
117-
uses: aquasecurity/trivy-action@7c2007bcb556501da015201bcba5aa14069b74e2
117+
uses: aquasecurity/trivy-action@6e7b7d1fd3e4fef0c5fa8cce1229c54b2c9bd0d8
118118
with:
119119
image-ref: ${{ steps.build.outputs.image }}
120120
format: sarif
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
DELETE FROM notification_templates WHERE id = '381df2a9-c0c0-4749-420f-80a9280c66f9';
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 ('381df2a9-c0c0-4749-420f-80a9280c66f9', 'Workspace Autobuild Failed', E'Workspace "{{.Labels.name}}" autobuild failed',
3+
E'Hi {{.UserName}}\n\Automatic build of your workspace **{{.Labels.name}}** failed.\nThe specified reason was "**{{.Labels.reason}}**".',
4+
'Workspace Events', '[
5+
{
6+
"label": "View workspace",
7+
"url": "{{ base_url }}/@{{.UserName}}/{{.Labels.name}}"
8+
}
9+
]'::jsonb);

coderd/notifications/enqueuer.go

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ func (s *StoreEnqueuer) Enqueue(ctx context.Context, userID, templateID uuid.UUI
8080
// buildPayload creates the payload that the notification will for variable substitution and/or routing.
8181
// The payload contains information about the recipient, the event that triggered the notification, and any subsequent
8282
// actions which can be taken by the recipient.
83-
func (s *StoreEnqueuer) buildPayload(ctx context.Context, userID uuid.UUID, templateID uuid.UUID, labels map[string]string) (*types.MessagePayload, error) {
83+
func (s *StoreEnqueuer) buildPayload(ctx context.Context, userID, templateID uuid.UUID, labels map[string]string) (*types.MessagePayload, error) {
8484
metadata, err := s.store.FetchNewMessageMetadata(ctx, database.FetchNewMessageMetadataParams{
8585
UserID: userID,
8686
NotificationTemplateID: templateID,
@@ -89,8 +89,21 @@ func (s *StoreEnqueuer) buildPayload(ctx context.Context, userID uuid.UUID, temp
8989
return nil, xerrors.Errorf("new message metadata: %w", err)
9090
}
9191

92+
payload := types.MessagePayload{
93+
Version: "1.0",
94+
95+
NotificationName: metadata.NotificationName,
96+
97+
UserID: metadata.UserID.String(),
98+
UserEmail: metadata.UserEmail,
99+
UserName: metadata.UserName,
100+
101+
Labels: labels,
102+
// No actions yet
103+
}
104+
92105
// Execute any templates in actions.
93-
out, err := render.GoTemplate(string(metadata.Actions), types.MessagePayload{}, s.helpers)
106+
out, err := render.GoTemplate(string(metadata.Actions), payload, s.helpers)
94107
if err != nil {
95108
return nil, xerrors.Errorf("render actions: %w", err)
96109
}
@@ -100,19 +113,8 @@ func (s *StoreEnqueuer) buildPayload(ctx context.Context, userID uuid.UUID, temp
100113
if err = json.Unmarshal(metadata.Actions, &actions); err != nil {
101114
return nil, xerrors.Errorf("new message metadata: parse template actions: %w", err)
102115
}
103-
104-
return &types.MessagePayload{
105-
Version: "1.0",
106-
107-
NotificationName: metadata.NotificationName,
108-
109-
UserID: metadata.UserID.String(),
110-
UserEmail: metadata.UserEmail,
111-
UserName: metadata.UserName,
112-
113-
Actions: actions,
114-
Labels: labels,
115-
}, nil
116+
payload.Actions = actions
117+
return &payload, nil
116118
}
117119

118120
// NoopEnqueuer implements the Enqueuer interface but performs a noop.

coderd/notifications/events.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,8 @@ import "github.com/google/uuid"
66
// TODO: autogenerate these.
77

88
// Workspace-related events.
9-
var TemplateWorkspaceDeleted = uuid.MustParse("f517da0b-cdc9-410f-ab89-a86107c420ed")
10-
var TemplateWorkspaceDormant = uuid.MustParse("123e4567-e89b-12d3-a456-426614174000")
9+
var (
10+
TemplateWorkspaceDeleted = uuid.MustParse("f517da0b-cdc9-410f-ab89-a86107c420ed")
11+
WorkspaceAutobuildFailed = uuid.MustParse("381df2a9-c0c0-4749-420f-80a9280c66f9")
12+
TemplateWorkspaceDormant = uuid.MustParse("123e4567-e89b-12d3-a456-426614174000")
13+
)

coderd/notifications/manager_test.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,11 @@ func TestBuildPayload(t *testing.T) {
9898

9999
// GIVEN: a set of helpers to be injected into the templates
100100
const label = "Click here!"
101-
const url = "http://xyz.com/"
101+
const baseURL = "http://xyz.com"
102+
const url = baseURL + "/@bobby/my-workspace"
102103
helpers := map[string]any{
103104
"my_label": func() string { return label },
104-
"my_url": func() string { return url },
105+
"my_url": func() string { return baseURL },
105106
}
106107

107108
// GIVEN: an enqueue interceptor which returns mock metadata
@@ -112,7 +113,7 @@ func TestBuildPayload(t *testing.T) {
112113
actions := []types.TemplateAction{
113114
{
114115
Label: "{{ my_label }}",
115-
URL: "{{ my_url }}",
116+
URL: "{{ my_url }}/@{{.UserName}}/{{.Labels.name}}",
116117
},
117118
}
118119
out, err := json.Marshal(actions)
@@ -131,7 +132,9 @@ func TestBuildPayload(t *testing.T) {
131132
require.NoError(t, err)
132133

133134
// WHEN: a notification is enqueued
134-
_, err = enq.Enqueue(ctx, uuid.New(), notifications.TemplateWorkspaceDeleted, nil, "test")
135+
_, err = enq.Enqueue(ctx, uuid.New(), notifications.TemplateWorkspaceDeleted, map[string]string{
136+
"name": "my-workspace",
137+
}, "test")
135138
require.NoError(t, err)
136139

137140
// THEN: expect that a payload will be constructed and have the expected values

coderd/notifications/render/gotmpl_test.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,23 @@ func TestGoTemplate(t *testing.T) {
3838
expectedOutput: userEmail,
3939
expectedErr: nil,
4040
},
41+
{
42+
name: "render workspace URL",
43+
in: `[{
44+
"label": "View workspace",
45+
"url": "{{ base_url }}/@{{.UserName}}/{{.Labels.name}}"
46+
}]`,
47+
payload: types.MessagePayload{
48+
UserName: "johndoe",
49+
Labels: map[string]string{
50+
"name": "my-workspace",
51+
},
52+
},
53+
expectedOutput: `[{
54+
"label": "View workspace",
55+
"url": "https://mocked-server-address/@johndoe/my-workspace"
56+
}]`,
57+
},
4158
}
4259

4360
for _, tc := range tests {
@@ -46,7 +63,9 @@ func TestGoTemplate(t *testing.T) {
4663
t.Run(tc.name, func(t *testing.T) {
4764
t.Parallel()
4865

49-
out, err := render.GoTemplate(tc.in, tc.payload, nil)
66+
out, err := render.GoTemplate(tc.in, tc.payload, map[string]any{
67+
"base_url": func() string { return "https://mocked-server-address" },
68+
})
5069
if tc.expectedErr == nil {
5170
require.NoError(t, err)
5271
} else {

coderd/provisionerdserver/provisionerdserver.go

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -982,12 +982,18 @@ func (s *server) FailJob(ctx context.Context, failJob *proto.FailedJob) (*proto.
982982
}
983983

984984
var build database.WorkspaceBuild
985+
var workspace database.Workspace
985986
err = s.Database.InTx(func(db database.Store) error {
986987
build, err = db.GetWorkspaceBuildByID(ctx, input.WorkspaceBuildID)
987988
if err != nil {
988989
return xerrors.Errorf("get workspace build: %w", err)
989990
}
990991

992+
workspace, err = db.GetWorkspaceByID(ctx, build.WorkspaceID)
993+
if err != nil {
994+
return xerrors.Errorf("get workspace: %w", err)
995+
}
996+
991997
if jobType.WorkspaceBuild.State != nil {
992998
err = db.UpdateWorkspaceBuildProvisionerStateByID(ctx, database.UpdateWorkspaceBuildProvisionerStateByIDParams{
993999
ID: input.WorkspaceBuildID,
@@ -1014,6 +1020,8 @@ func (s *server) FailJob(ctx context.Context, failJob *proto.FailedJob) (*proto.
10141020
return nil, err
10151021
}
10161022

1023+
s.notifyWorkspaceBuildFailed(ctx, workspace, build)
1024+
10171025
err = s.Pubsub.Publish(codersdk.WorkspaceNotifyChannel(build.WorkspaceID), []byte{})
10181026
if err != nil {
10191027
return nil, xerrors.Errorf("update workspace: %w", err)
@@ -1087,6 +1095,27 @@ func (s *server) FailJob(ctx context.Context, failJob *proto.FailedJob) (*proto.
10871095
return &proto.Empty{}, nil
10881096
}
10891097

1098+
func (s *server) notifyWorkspaceBuildFailed(ctx context.Context, workspace database.Workspace, build database.WorkspaceBuild) {
1099+
var reason string
1100+
if build.Reason.Valid() && build.Reason == database.BuildReasonInitiator {
1101+
return // failed workspace build initiated by a user should not notify
1102+
}
1103+
reason = string(build.Reason)
1104+
initiator := "autobuild"
1105+
1106+
if _, err := s.NotificationEnqueuer.Enqueue(ctx, workspace.OwnerID, notifications.WorkspaceAutobuildFailed,
1107+
map[string]string{
1108+
"name": workspace.Name,
1109+
"initiator": initiator,
1110+
"reason": reason,
1111+
}, "provisionerdserver",
1112+
// Associate this notification with all the related entities.
1113+
workspace.ID, workspace.OwnerID, workspace.TemplateID, workspace.OrganizationID,
1114+
); err != nil {
1115+
s.Logger.Warn(ctx, "failed to notify of failed workspace autobuild", slog.Error(err))
1116+
}
1117+
}
1118+
10901119
// CompleteJob is triggered by a provision daemon to mark a provisioner job as completed.
10911120
func (s *server) CompleteJob(ctx context.Context, completed *proto.CompletedJob) (*proto.Empty, error) {
10921121
ctx, span := s.startTrace(ctx, tracing.FuncName())
@@ -1523,6 +1552,7 @@ func (s *server) CompleteJob(ctx context.Context, completed *proto.CompletedJob)
15231552

15241553
func (s *server) notifyWorkspaceDeleted(ctx context.Context, workspace database.Workspace, build database.WorkspaceBuild) {
15251554
var reason string
1555+
initiator := build.InitiatorByUsername
15261556
if build.Reason.Valid() {
15271557
switch build.Reason {
15281558
case database.BuildReasonInitiator:
@@ -1534,6 +1564,7 @@ func (s *server) notifyWorkspaceDeleted(ctx context.Context, workspace database.
15341564
reason = "initiated by user"
15351565
case database.BuildReasonAutodelete:
15361566
reason = "autodeleted due to dormancy"
1567+
initiator = "autobuild"
15371568
default:
15381569
reason = string(build.Reason)
15391570
}
@@ -1545,9 +1576,9 @@ func (s *server) notifyWorkspaceDeleted(ctx context.Context, workspace database.
15451576

15461577
if _, err := s.NotificationEnqueuer.Enqueue(ctx, workspace.OwnerID, notifications.TemplateWorkspaceDeleted,
15471578
map[string]string{
1548-
"name": workspace.Name,
1549-
"initiatedBy": build.InitiatorByUsername,
1550-
"reason": reason,
1579+
"name": workspace.Name,
1580+
"reason": reason,
1581+
"initiator": initiator,
15511582
}, "provisionerdserver",
15521583
// Associate this notification with all the related entities.
15531584
workspace.ID, workspace.OwnerID, workspace.TemplateID, workspace.OrganizationID,

0 commit comments

Comments
 (0)