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

Commit47f2c7d

Browse files
authored
feat: notify about manual failed builds (#14419)
1 parent0afff43 commit47f2c7d

File tree

6 files changed

+172
-4
lines changed

6 files changed

+172
-4
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
DELETEFROM notification_templatesWHERE id='2faeee0f-26cb-4e96-821c-85ccb9f71513';
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 ('2faeee0f-26cb-4e96-821c-85ccb9f71513','Workspace Manual Build Failed', E'Workspace "{{.Labels.name}}" manual build failed',
3+
E'Hi {{.UserName}},\n\nA manual build of the workspace **{{.Labels.name}}** using the template **{{.Labels.template_name}}** failed (version: **{{.Labels.template_version_name}}**).\nThe workspace build was initiated by **{{.Labels.initiator}}**.',
4+
'Workspace Events','[
5+
{
6+
"label": "View build",
7+
"url": "{{ base_url }}/@{{.Labels.workspace_owner_username}}/{{.Labels.name}}/builds/{{.Labels.workspace_build_number}}"
8+
}
9+
]'::jsonb);

‎coderd/notifications/events.go‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ var (
1212
TemplateWorkspaceDormant=uuid.MustParse("0ea69165-ec14-4314-91f1-69566ac3c5a0")
1313
TemplateWorkspaceAutoUpdated=uuid.MustParse("c34a0c09-0704-4cac-bd1c-0c0146811c2b")
1414
TemplateWorkspaceMarkedForDeletion=uuid.MustParse("51ce2fdf-c9ca-4be1-8d70-628674f9bc42")
15+
TemplateWorkspaceManualBuildFailed=uuid.MustParse("2faeee0f-26cb-4e96-821c-85ccb9f71513")
1516
)
1617

1718
// Account-related events.

‎coderd/notifications/notifications_test.go‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -814,6 +814,21 @@ func TestNotificationTemplatesCanRender(t *testing.T) {
814814
},
815815
},
816816
},
817+
{
818+
name:"TemplateWorkspaceManualBuildFailed",
819+
id:notifications.TemplateWorkspaceManualBuildFailed,
820+
payload: types.MessagePayload{
821+
UserName:"bobby",
822+
Labels:map[string]string{
823+
"name":"bobby-workspace",
824+
"template_name":"bobby-template",
825+
"template_version_name":"bobby-template-version",
826+
"initiator":"joe",
827+
"workspace_owner_username":"mrbobby",
828+
"workspace_build_number":"3",
829+
},
830+
},
831+
},
817832
}
818833

819834
allTemplates,err:=enumerateAllTemplates(t)

‎coderd/provisionerdserver/provisionerdserver.go‎

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"net/http"
1010
"net/url"
1111
"reflect"
12+
"sort"
1213
"strconv"
1314
"strings"
1415
"sync/atomic"
@@ -1098,7 +1099,8 @@ func (s *server) FailJob(ctx context.Context, failJob *proto.FailedJob) (*proto.
10981099
func (s*server)notifyWorkspaceBuildFailed(ctx context.Context,workspace database.Workspace,build database.WorkspaceBuild) {
10991100
varreasonstring
11001101
ifbuild.Reason.Valid()&&build.Reason==database.BuildReasonInitiator {
1101-
return// failed workspace build initiated by a user should not notify
1102+
s.notifyWorkspaceManualBuildFailed(ctx,workspace,build)
1103+
return
11021104
}
11031105
reason=string(build.Reason)
11041106

@@ -1114,6 +1116,85 @@ func (s *server) notifyWorkspaceBuildFailed(ctx context.Context, workspace datab
11141116
}
11151117
}
11161118

1119+
func (s*server)notifyWorkspaceManualBuildFailed(ctx context.Context,workspace database.Workspace,build database.WorkspaceBuild) {
1120+
templateAdmins,template,templateVersion,workspaceOwner,err:=s.prepareForNotifyWorkspaceManualBuildFailed(ctx,workspace,build)
1121+
iferr!=nil {
1122+
s.Logger.Error(ctx,"unable to collect data for manual build failed notification",slog.Error(err))
1123+
return
1124+
}
1125+
1126+
for_,templateAdmin:=rangetemplateAdmins {
1127+
if_,err:=s.NotificationsEnqueuer.Enqueue(ctx,templateAdmin.ID,notifications.TemplateWorkspaceManualBuildFailed,
1128+
map[string]string{
1129+
"name":workspace.Name,
1130+
"template_name":template.Name,
1131+
"template_version_name":templateVersion.Name,
1132+
"initiator":build.InitiatorByUsername,
1133+
"workspace_owner_username":workspaceOwner.Username,
1134+
"workspace_build_number":strconv.Itoa(int(build.BuildNumber)),
1135+
},"provisionerdserver",
1136+
// Associate this notification with all the related entities.
1137+
workspace.ID,workspace.OwnerID,workspace.TemplateID,workspace.OrganizationID,
1138+
);err!=nil {
1139+
s.Logger.Warn(ctx,"failed to notify of failed workspace manual build",slog.Error(err))
1140+
}
1141+
}
1142+
}
1143+
1144+
// prepareForNotifyWorkspaceManualBuildFailed collects data required to build notifications for template admins.
1145+
// The template `notifications.TemplateWorkspaceManualBuildFailed` is quite detailed as it requires information about the template,
1146+
// template version, workspace, workspace build, etc.
1147+
func (s*server)prepareForNotifyWorkspaceManualBuildFailed(ctx context.Context,workspace database.Workspace,build database.WorkspaceBuild) ([]database.GetUsersRow,
1148+
database.Template, database.TemplateVersion, database.User,error,
1149+
) {
1150+
users,err:=s.Database.GetUsers(ctx, database.GetUsersParams{
1151+
RbacRole: []string{codersdk.RoleTemplateAdmin},
1152+
})
1153+
iferr!=nil {
1154+
returnnil, database.Template{}, database.TemplateVersion{}, database.User{},xerrors.Errorf("unable to fetch template admins: %w",err)
1155+
}
1156+
1157+
usersByIDs:=map[uuid.UUID]database.GetUsersRow{}
1158+
varuserIDs []uuid.UUID
1159+
for_,user:=rangeusers {
1160+
usersByIDs[user.ID]=user
1161+
userIDs=append(userIDs,user.ID)
1162+
}
1163+
1164+
vartemplateAdmins []database.GetUsersRow
1165+
iflen(userIDs)>0 {
1166+
orgIDsByMemberIDs,err:=s.Database.GetOrganizationIDsByMemberIDs(ctx,userIDs)
1167+
iferr!=nil {
1168+
returnnil, database.Template{}, database.TemplateVersion{}, database.User{},xerrors.Errorf("unable to fetch organization IDs by member IDs: %w",err)
1169+
}
1170+
1171+
for_,entry:=rangeorgIDsByMemberIDs {
1172+
ifslices.Contains(entry.OrganizationIDs,workspace.OrganizationID) {
1173+
templateAdmins=append(templateAdmins,usersByIDs[entry.UserID])
1174+
}
1175+
}
1176+
}
1177+
sort.Slice(templateAdmins,func(i,jint)bool {
1178+
returntemplateAdmins[i].Username<templateAdmins[j].Username
1179+
})
1180+
1181+
template,err:=s.Database.GetTemplateByID(ctx,workspace.TemplateID)
1182+
iferr!=nil {
1183+
returnnil, database.Template{}, database.TemplateVersion{}, database.User{},xerrors.Errorf("unable to fetch template: %w",err)
1184+
}
1185+
1186+
templateVersion,err:=s.Database.GetTemplateVersionByID(ctx,build.TemplateVersionID)
1187+
iferr!=nil {
1188+
returnnil, database.Template{}, database.TemplateVersion{}, database.User{},xerrors.Errorf("unable to fetch template version: %w",err)
1189+
}
1190+
1191+
workspaceOwner,err:=s.Database.GetUserByID(ctx,workspace.OwnerID)
1192+
iferr!=nil {
1193+
returnnil, database.Template{}, database.TemplateVersion{}, database.User{},xerrors.Errorf("unable to fetch workspace owner: %w",err)
1194+
}
1195+
returntemplateAdmins,template,templateVersion,workspaceOwner,nil
1196+
}
1197+
11171198
// CompleteJob is triggered by a provision daemon to mark a provisioner job as completed.
11181199
func (s*server)CompleteJob(ctx context.Context,completed*proto.CompletedJob) (*proto.Empty,error) {
11191200
ctx,span:=s.startTrace(ctx,tracing.FuncName())

‎coderd/provisionerdserver/provisionerdserver_test.go‎

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"encoding/json"
77
"io"
88
"net/url"
9+
"strconv"
910
"strings"
1011
"sync"
1112
"sync/atomic"
@@ -1738,8 +1739,6 @@ func TestNotifications(t *testing.T) {
17381739
Provisioner:database.ProvisionerTypeEcho,
17391740
OrganizationID:pd.OrganizationID,
17401741
})
1741-
template,err:=db.GetTemplateByID(ctx,template.ID)
1742-
require.NoError(t,err)
17431742
file:=dbgen.File(t,db, database.File{CreatedBy:user.ID})
17441743
workspace:=dbgen.Workspace(t,db, database.Workspace{
17451744
TemplateID:template.ID,
@@ -1769,7 +1768,7 @@ func TestNotifications(t *testing.T) {
17691768
})),
17701769
OrganizationID:pd.OrganizationID,
17711770
})
1772-
_,err=db.AcquireProvisionerJob(ctx, database.AcquireProvisionerJobParams{
1771+
_,err:=db.AcquireProvisionerJob(ctx, database.AcquireProvisionerJobParams{
17731772
OrganizationID:pd.OrganizationID,
17741773
WorkerID: uuid.NullUUID{
17751774
UUID:pd.ID,
@@ -1804,6 +1803,68 @@ func TestNotifications(t *testing.T) {
18041803
})
18051804
}
18061805
})
1806+
1807+
t.Run("Manual build failed, template admins notified",func(t*testing.T) {
1808+
t.Parallel()
1809+
1810+
ctx:=context.Background()
1811+
1812+
// given
1813+
notifEnq:=&testutil.FakeNotificationsEnqueuer{}
1814+
srv,db,ps,pd:=setup(t,true/* ignoreLogErrors */,&overrides{notificationEnqueuer:notifEnq})
1815+
1816+
templateAdmin:=dbgen.User(t,db, database.User{RBACRoles: []string{codersdk.RoleTemplateAdmin}})
1817+
_/* other template admin, should not receive notification */=dbgen.User(t,db, database.User{RBACRoles: []string{codersdk.RoleTemplateAdmin}})
1818+
_=dbgen.OrganizationMember(t,db, database.OrganizationMember{UserID:templateAdmin.ID,OrganizationID:pd.OrganizationID})
1819+
user:=dbgen.User(t,db, database.User{})
1820+
_=dbgen.OrganizationMember(t,db, database.OrganizationMember{UserID:user.ID,OrganizationID:pd.OrganizationID})
1821+
1822+
template:=dbgen.Template(t,db, database.Template{
1823+
Name:"template",Provisioner:database.ProvisionerTypeEcho,OrganizationID:pd.OrganizationID,
1824+
})
1825+
workspace:=dbgen.Workspace(t,db, database.Workspace{
1826+
TemplateID:template.ID,OwnerID:user.ID,OrganizationID:pd.OrganizationID,
1827+
})
1828+
version:=dbgen.TemplateVersion(t,db, database.TemplateVersion{
1829+
OrganizationID:pd.OrganizationID,TemplateID: uuid.NullUUID{UUID:template.ID,Valid:true},JobID:uuid.New(),
1830+
})
1831+
build:=dbgen.WorkspaceBuild(t,db, database.WorkspaceBuild{
1832+
WorkspaceID:workspace.ID,TemplateVersionID:version.ID,InitiatorID:user.ID,Transition:database.WorkspaceTransitionDelete,Reason:database.BuildReasonInitiator,
1833+
})
1834+
job:=dbgen.ProvisionerJob(t,db,ps, database.ProvisionerJob{
1835+
FileID:dbgen.File(t,db, database.File{CreatedBy:user.ID}).ID,
1836+
Type:database.ProvisionerJobTypeWorkspaceBuild,
1837+
Input:must(json.Marshal(provisionerdserver.WorkspaceProvisionJob{WorkspaceBuildID:build.ID})),
1838+
OrganizationID:pd.OrganizationID,
1839+
})
1840+
_,err:=db.AcquireProvisionerJob(ctx, database.AcquireProvisionerJobParams{
1841+
OrganizationID:pd.OrganizationID,
1842+
WorkerID: uuid.NullUUID{UUID:pd.ID,Valid:true},
1843+
Types: []database.ProvisionerType{database.ProvisionerTypeEcho},
1844+
})
1845+
require.NoError(t,err)
1846+
1847+
// when
1848+
_,err=srv.FailJob(ctx,&proto.FailedJob{
1849+
JobId:job.ID.String(),Type:&proto.FailedJob_WorkspaceBuild_{WorkspaceBuild:&proto.FailedJob_WorkspaceBuild{State: []byte{}}},
1850+
})
1851+
require.NoError(t,err)
1852+
1853+
// then
1854+
require.Len(t,notifEnq.Sent,1)
1855+
assert.Equal(t,notifEnq.Sent[0].UserID,templateAdmin.ID)
1856+
assert.Equal(t,notifEnq.Sent[0].TemplateID,notifications.TemplateWorkspaceManualBuildFailed)
1857+
assert.Contains(t,notifEnq.Sent[0].Targets,template.ID)
1858+
assert.Contains(t,notifEnq.Sent[0].Targets,workspace.ID)
1859+
assert.Contains(t,notifEnq.Sent[0].Targets,workspace.OrganizationID)
1860+
assert.Contains(t,notifEnq.Sent[0].Targets,user.ID)
1861+
assert.Equal(t,workspace.Name,notifEnq.Sent[0].Labels["name"])
1862+
assert.Equal(t,template.Name,notifEnq.Sent[0].Labels["template_name"])
1863+
assert.Equal(t,version.Name,notifEnq.Sent[0].Labels["template_version_name"])
1864+
assert.Equal(t,user.Username,notifEnq.Sent[0].Labels["initiator"])
1865+
assert.Equal(t,user.Username,notifEnq.Sent[0].Labels["workspace_owner_username"])
1866+
assert.Equal(t,strconv.Itoa(int(build.BuildNumber)),notifEnq.Sent[0].Labels["workspace_build_number"])
1867+
})
18071868
}
18081869

18091870
typeoverridesstruct {

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp