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
forked fromcoder/coder

Commit6de5937

Browse files
authored
feat: notifications: report failed workspace builds (coder#14571)
1 parent1e5438e commit6de5937

29 files changed

+1545
-55
lines changed

‎cli/server.go‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import (
5656
"cdr.dev/slog"
5757
"cdr.dev/slog/sloggers/sloghuman"
5858
"github.com/coder/coder/v2/coderd/entitlements"
59+
"github.com/coder/coder/v2/coderd/notifications/reports"
5960
"github.com/coder/coder/v2/coderd/runtimeconfig"
6061
"github.com/coder/pretty"
6162
"github.com/coder/quartz"
@@ -1018,6 +1019,10 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
10181019

10191020
// nolint:gocritic // TODO: create own role.
10201021
notificationsManager.Run(dbauthz.AsSystemRestricted(ctx))
1022+
1023+
// Run report generator to distribute periodic reports.
1024+
notificationReportGenerator:=reports.NewReportGenerator(ctx,logger,options.Database,options.NotificationsEnqueuer,quartz.NewReal())
1025+
defernotificationReportGenerator.Close()
10211026
}
10221027

10231028
// Wrap the server in middleware that redirects to the access URL if

‎coderd/database/dbauthz/dbauthz.go‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1459,6 +1459,13 @@ func (q *querier) GetExternalAuthLinksByUserID(ctx context.Context, userID uuid.
14591459
returnfetchWithPostFilter(q.auth,policy.ActionReadPersonal,q.db.GetExternalAuthLinksByUserID)(ctx,userID)
14601460
}
14611461

1462+
func (q*querier)GetFailedWorkspaceBuildsByTemplateID(ctx context.Context,arg database.GetFailedWorkspaceBuildsByTemplateIDParams) ([]database.GetFailedWorkspaceBuildsByTemplateIDRow,error) {
1463+
iferr:=q.authorizeContext(ctx,policy.ActionRead,rbac.ResourceSystem);err!=nil {
1464+
returnnil,err
1465+
}
1466+
returnq.db.GetFailedWorkspaceBuildsByTemplateID(ctx,arg)
1467+
}
1468+
14621469
func (q*querier)GetFileByHashAndCreator(ctx context.Context,arg database.GetFileByHashAndCreatorParams) (database.File,error) {
14631470
file,err:=q.db.GetFileByHashAndCreator(ctx,arg)
14641471
iferr!=nil {
@@ -1628,6 +1635,13 @@ func (q *querier) GetNotificationMessagesByStatus(ctx context.Context, arg datab
16281635
returnq.db.GetNotificationMessagesByStatus(ctx,arg)
16291636
}
16301637

1638+
func (q*querier)GetNotificationReportGeneratorLogByTemplate(ctx context.Context,arg uuid.UUID) (database.NotificationReportGeneratorLog,error) {
1639+
iferr:=q.authorizeContext(ctx,policy.ActionRead,rbac.ResourceSystem);err!=nil {
1640+
return database.NotificationReportGeneratorLog{},err
1641+
}
1642+
returnq.db.GetNotificationReportGeneratorLogByTemplate(ctx,arg)
1643+
}
1644+
16311645
func (q*querier)GetNotificationTemplateByID(ctx context.Context,id uuid.UUID) (database.NotificationTemplate,error) {
16321646
iferr:=q.authorizeContext(ctx,policy.ActionRead,rbac.ResourceNotificationTemplate);err!=nil {
16331647
return database.NotificationTemplate{},err
@@ -2510,6 +2524,13 @@ func (q *querier) GetWorkspaceBuildParameters(ctx context.Context, workspaceBuil
25102524
returnq.db.GetWorkspaceBuildParameters(ctx,workspaceBuildID)
25112525
}
25122526

2527+
func (q*querier)GetWorkspaceBuildStatsByTemplates(ctx context.Context,since time.Time) ([]database.GetWorkspaceBuildStatsByTemplatesRow,error) {
2528+
iferr:=q.authorizeContext(ctx,policy.ActionRead,rbac.ResourceSystem);err!=nil {
2529+
returnnil,err
2530+
}
2531+
returnq.db.GetWorkspaceBuildStatsByTemplates(ctx,since)
2532+
}
2533+
25132534
func (q*querier)GetWorkspaceBuildsByWorkspaceID(ctx context.Context,arg database.GetWorkspaceBuildsByWorkspaceIDParams) ([]database.WorkspaceBuild,error) {
25142535
if_,err:=q.GetWorkspaceByID(ctx,arg.WorkspaceID);err!=nil {
25152536
returnnil,err
@@ -3966,6 +3987,13 @@ func (q *querier) UpsertLogoURL(ctx context.Context, value string) error {
39663987
returnq.db.UpsertLogoURL(ctx,value)
39673988
}
39683989

3990+
func (q*querier)UpsertNotificationReportGeneratorLog(ctx context.Context,arg database.UpsertNotificationReportGeneratorLogParams)error {
3991+
iferr:=q.authorizeContext(ctx,policy.ActionCreate,rbac.ResourceSystem);err!=nil {
3992+
returnerr
3993+
}
3994+
returnq.db.UpsertNotificationReportGeneratorLog(ctx,arg)
3995+
}
3996+
39693997
func (q*querier)UpsertNotificationsSettings(ctx context.Context,valuestring)error {
39703998
iferr:=q.authorizeContext(ctx,policy.ActionUpdate,rbac.ResourceDeploymentConfig);err!=nil {
39713999
returnerr

‎coderd/database/dbauthz/dbauthz_test.go‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2819,6 +2819,28 @@ func (s *MethodTestSuite) TestSystemFunctions() {
28192819
Value:"value",
28202820
}).Asserts(rbac.ResourceSystem,policy.ActionCreate)
28212821
}))
2822+
s.Run("GetFailedWorkspaceBuildsByTemplateID",s.Subtest(func(db database.Store,check*expects) {
2823+
check.Args(database.GetFailedWorkspaceBuildsByTemplateIDParams{
2824+
TemplateID:uuid.New(),
2825+
Since:dbtime.Now(),
2826+
}).Asserts(rbac.ResourceSystem,policy.ActionRead)
2827+
}))
2828+
s.Run("GetNotificationReportGeneratorLogByTemplate",s.Subtest(func(db database.Store,check*expects) {
2829+
_=db.UpsertNotificationReportGeneratorLog(context.Background(), database.UpsertNotificationReportGeneratorLogParams{
2830+
NotificationTemplateID:notifications.TemplateWorkspaceBuildsFailedReport,
2831+
LastGeneratedAt:dbtime.Now(),
2832+
})
2833+
check.Args(notifications.TemplateWorkspaceBuildsFailedReport).Asserts(rbac.ResourceSystem,policy.ActionRead)
2834+
}))
2835+
s.Run("GetWorkspaceBuildStatsByTemplates",s.Subtest(func(db database.Store,check*expects) {
2836+
check.Args(dbtime.Now()).Asserts(rbac.ResourceSystem,policy.ActionRead)
2837+
}))
2838+
s.Run("UpsertNotificationReportGeneratorLog",s.Subtest(func(db database.Store,check*expects) {
2839+
check.Args(database.UpsertNotificationReportGeneratorLogParams{
2840+
NotificationTemplateID:uuid.New(),
2841+
LastGeneratedAt:dbtime.Now(),
2842+
}).Asserts(rbac.ResourceSystem,policy.ActionCreate)
2843+
}))
28222844
}
28232845

28242846
func (s*MethodTestSuite)TestNotifications() {

‎coderd/database/dbmem/dbmem.go‎

Lines changed: 211 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -187,53 +187,54 @@ type data struct {
187187
userLinks []database.UserLink
188188

189189
// New tables
190-
workspaceAgentStats []database.WorkspaceAgentStat
191-
auditLogs []database.AuditLog
192-
cryptoKeys []database.CryptoKey
193-
dbcryptKeys []database.DBCryptKey
194-
files []database.File
195-
externalAuthLinks []database.ExternalAuthLink
196-
gitSSHKey []database.GitSSHKey
197-
groupMembers []database.GroupMemberTable
198-
groups []database.Group
199-
jfrogXRayScans []database.JfrogXrayScan
200-
licenses []database.License
201-
notificationMessages []database.NotificationMessage
202-
notificationPreferences []database.NotificationPreference
203-
oauth2ProviderApps []database.OAuth2ProviderApp
204-
oauth2ProviderAppSecrets []database.OAuth2ProviderAppSecret
205-
oauth2ProviderAppCodes []database.OAuth2ProviderAppCode
206-
oauth2ProviderAppTokens []database.OAuth2ProviderAppToken
207-
parameterSchemas []database.ParameterSchema
208-
provisionerDaemons []database.ProvisionerDaemon
209-
provisionerJobLogs []database.ProvisionerJobLog
210-
provisionerJobs []database.ProvisionerJob
211-
provisionerKeys []database.ProvisionerKey
212-
replicas []database.Replica
213-
templateVersions []database.TemplateVersionTable
214-
templateVersionParameters []database.TemplateVersionParameter
215-
templateVersionVariables []database.TemplateVersionVariable
216-
templateVersionWorkspaceTags []database.TemplateVersionWorkspaceTag
217-
templates []database.TemplateTable
218-
templateUsageStats []database.TemplateUsageStat
219-
workspaceAgents []database.WorkspaceAgent
220-
workspaceAgentMetadata []database.WorkspaceAgentMetadatum
221-
workspaceAgentLogs []database.WorkspaceAgentLog
222-
workspaceAgentLogSources []database.WorkspaceAgentLogSource
223-
workspaceAgentScripts []database.WorkspaceAgentScript
224-
workspaceAgentPortShares []database.WorkspaceAgentPortShare
225-
workspaceApps []database.WorkspaceApp
226-
workspaceAppStatsLastInsertIDint64
227-
workspaceAppStats []database.WorkspaceAppStat
228-
workspaceBuilds []database.WorkspaceBuild
229-
workspaceBuildParameters []database.WorkspaceBuildParameter
230-
workspaceResourceMetadata []database.WorkspaceResourceMetadatum
231-
workspaceResources []database.WorkspaceResource
232-
workspaces []database.Workspace
233-
workspaceProxies []database.WorkspaceProxy
234-
customRoles []database.CustomRole
235-
provisionerJobTimings []database.ProvisionerJobTiming
236-
runtimeConfigmap[string]string
190+
auditLogs []database.AuditLog
191+
cryptoKeys []database.CryptoKey
192+
dbcryptKeys []database.DBCryptKey
193+
files []database.File
194+
externalAuthLinks []database.ExternalAuthLink
195+
gitSSHKey []database.GitSSHKey
196+
groupMembers []database.GroupMemberTable
197+
groups []database.Group
198+
jfrogXRayScans []database.JfrogXrayScan
199+
licenses []database.License
200+
notificationMessages []database.NotificationMessage
201+
notificationPreferences []database.NotificationPreference
202+
notificationReportGeneratorLogs []database.NotificationReportGeneratorLog
203+
oauth2ProviderApps []database.OAuth2ProviderApp
204+
oauth2ProviderAppSecrets []database.OAuth2ProviderAppSecret
205+
oauth2ProviderAppCodes []database.OAuth2ProviderAppCode
206+
oauth2ProviderAppTokens []database.OAuth2ProviderAppToken
207+
parameterSchemas []database.ParameterSchema
208+
provisionerDaemons []database.ProvisionerDaemon
209+
provisionerJobLogs []database.ProvisionerJobLog
210+
provisionerJobs []database.ProvisionerJob
211+
provisionerKeys []database.ProvisionerKey
212+
replicas []database.Replica
213+
templateVersions []database.TemplateVersionTable
214+
templateVersionParameters []database.TemplateVersionParameter
215+
templateVersionVariables []database.TemplateVersionVariable
216+
templateVersionWorkspaceTags []database.TemplateVersionWorkspaceTag
217+
templates []database.TemplateTable
218+
templateUsageStats []database.TemplateUsageStat
219+
workspaceAgents []database.WorkspaceAgent
220+
workspaceAgentMetadata []database.WorkspaceAgentMetadatum
221+
workspaceAgentLogs []database.WorkspaceAgentLog
222+
workspaceAgentLogSources []database.WorkspaceAgentLogSource
223+
workspaceAgentPortShares []database.WorkspaceAgentPortShare
224+
workspaceAgentScripts []database.WorkspaceAgentScript
225+
workspaceAgentStats []database.WorkspaceAgentStat
226+
workspaceApps []database.WorkspaceApp
227+
workspaceAppStatsLastInsertIDint64
228+
workspaceAppStats []database.WorkspaceAppStat
229+
workspaceBuilds []database.WorkspaceBuild
230+
workspaceBuildParameters []database.WorkspaceBuildParameter
231+
workspaceResourceMetadata []database.WorkspaceResourceMetadatum
232+
workspaceResources []database.WorkspaceResource
233+
workspaces []database.Workspace
234+
workspaceProxies []database.WorkspaceProxy
235+
customRoles []database.CustomRole
236+
provisionerJobTimings []database.ProvisionerJobTiming
237+
runtimeConfigmap[string]string
237238
// Locks is a map of lock names. Any keys within the map are currently
238239
// locked.
239240
locksmap[int64]struct{}
@@ -2621,6 +2622,75 @@ func (q *FakeQuerier) GetExternalAuthLinksByUserID(_ context.Context, userID uui
26212622
returngals,nil
26222623
}
26232624

2625+
func (q*FakeQuerier)GetFailedWorkspaceBuildsByTemplateID(ctx context.Context,arg database.GetFailedWorkspaceBuildsByTemplateIDParams) ([]database.GetFailedWorkspaceBuildsByTemplateIDRow,error) {
2626+
err:=validateDatabaseType(arg)
2627+
iferr!=nil {
2628+
returnnil,err
2629+
}
2630+
2631+
q.mutex.RLock()
2632+
deferq.mutex.RUnlock()
2633+
2634+
workspaceBuildStats:= []database.GetFailedWorkspaceBuildsByTemplateIDRow{}
2635+
for_,wb:=rangeq.workspaceBuilds {
2636+
job,err:=q.getProvisionerJobByIDNoLock(ctx,wb.JobID)
2637+
iferr!=nil {
2638+
returnnil,xerrors.Errorf("get provisioner job by ID: %w",err)
2639+
}
2640+
2641+
ifjob.JobStatus!=database.ProvisionerJobStatusFailed {
2642+
continue
2643+
}
2644+
2645+
if!job.CompletedAt.Valid {
2646+
continue
2647+
}
2648+
2649+
ifwb.CreatedAt.Before(arg.Since) {
2650+
continue
2651+
}
2652+
2653+
w,err:=q.getWorkspaceByIDNoLock(ctx,wb.WorkspaceID)
2654+
iferr!=nil {
2655+
returnnil,xerrors.Errorf("get workspace by ID: %w",err)
2656+
}
2657+
2658+
t,err:=q.getTemplateByIDNoLock(ctx,w.TemplateID)
2659+
iferr!=nil {
2660+
returnnil,xerrors.Errorf("get template by ID: %w",err)
2661+
}
2662+
2663+
ift.ID!=arg.TemplateID {
2664+
continue
2665+
}
2666+
2667+
workspaceOwner,err:=q.getUserByIDNoLock(w.OwnerID)
2668+
iferr!=nil {
2669+
returnnil,xerrors.Errorf("get user by ID: %w",err)
2670+
}
2671+
2672+
templateVersion,err:=q.getTemplateVersionByIDNoLock(ctx,wb.TemplateVersionID)
2673+
iferr!=nil {
2674+
returnnil,xerrors.Errorf("get template version by ID: %w",err)
2675+
}
2676+
2677+
workspaceBuildStats=append(workspaceBuildStats, database.GetFailedWorkspaceBuildsByTemplateIDRow{
2678+
WorkspaceName:w.Name,
2679+
WorkspaceOwnerUsername:workspaceOwner.Username,
2680+
TemplateVersionName:templateVersion.Name,
2681+
WorkspaceBuildNumber:wb.BuildNumber,
2682+
})
2683+
}
2684+
2685+
sort.Slice(workspaceBuildStats,func(i,jint)bool {
2686+
ifworkspaceBuildStats[i].TemplateVersionName!=workspaceBuildStats[j].TemplateVersionName {
2687+
returnworkspaceBuildStats[i].TemplateVersionName<workspaceBuildStats[j].TemplateVersionName
2688+
}
2689+
returnworkspaceBuildStats[i].WorkspaceBuildNumber>workspaceBuildStats[j].WorkspaceBuildNumber
2690+
})
2691+
returnworkspaceBuildStats,nil
2692+
}
2693+
26242694
func (q*FakeQuerier)GetFileByHashAndCreator(_ context.Context,arg database.GetFileByHashAndCreatorParams) (database.File,error) {
26252695
iferr:=validateDatabaseType(arg);err!=nil {
26262696
return database.File{},err
@@ -3044,6 +3114,23 @@ func (q *FakeQuerier) GetNotificationMessagesByStatus(_ context.Context, arg dat
30443114
returnout,nil
30453115
}
30463116

3117+
func (q*FakeQuerier)GetNotificationReportGeneratorLogByTemplate(_ context.Context,templateID uuid.UUID) (database.NotificationReportGeneratorLog,error) {
3118+
err:=validateDatabaseType(templateID)
3119+
iferr!=nil {
3120+
return database.NotificationReportGeneratorLog{},err
3121+
}
3122+
3123+
q.mutex.RLock()
3124+
deferq.mutex.RUnlock()
3125+
3126+
for_,record:=rangeq.notificationReportGeneratorLogs {
3127+
ifrecord.NotificationTemplateID==templateID {
3128+
returnrecord,nil
3129+
}
3130+
}
3131+
return database.NotificationReportGeneratorLog{},sql.ErrNoRows
3132+
}
3133+
30473134
func (*FakeQuerier)GetNotificationTemplateByID(_ context.Context,_ uuid.UUID) (database.NotificationTemplate,error) {
30483135
// Not implementing this function because it relies on state in the database which is created with migrations.
30493136
// We could consider using code-generation to align the database state and dbmem, but it's not worth it right now.
@@ -5964,6 +6051,63 @@ func (q *FakeQuerier) GetWorkspaceBuildParameters(_ context.Context, workspaceBu
59646051
returnparams,nil
59656052
}
59666053

6054+
func (q*FakeQuerier)GetWorkspaceBuildStatsByTemplates(ctx context.Context,since time.Time) ([]database.GetWorkspaceBuildStatsByTemplatesRow,error) {
6055+
q.mutex.RLock()
6056+
deferq.mutex.RUnlock()
6057+
6058+
templateStats:=map[uuid.UUID]database.GetWorkspaceBuildStatsByTemplatesRow{}
6059+
for_,wb:=rangeq.workspaceBuilds {
6060+
job,err:=q.getProvisionerJobByIDNoLock(ctx,wb.JobID)
6061+
iferr!=nil {
6062+
returnnil,xerrors.Errorf("get provisioner job by ID: %w",err)
6063+
}
6064+
6065+
if!job.CompletedAt.Valid {
6066+
continue
6067+
}
6068+
6069+
ifwb.CreatedAt.Before(since) {
6070+
continue
6071+
}
6072+
6073+
w,err:=q.getWorkspaceByIDNoLock(ctx,wb.WorkspaceID)
6074+
iferr!=nil {
6075+
returnnil,xerrors.Errorf("get workspace by ID: %w",err)
6076+
}
6077+
6078+
if_,ok:=templateStats[w.TemplateID];!ok {
6079+
t,err:=q.getTemplateByIDNoLock(ctx,w.TemplateID)
6080+
iferr!=nil {
6081+
returnnil,xerrors.Errorf("get template by ID: %w",err)
6082+
}
6083+
6084+
templateStats[w.TemplateID]= database.GetWorkspaceBuildStatsByTemplatesRow{
6085+
TemplateID:w.TemplateID,
6086+
TemplateName:t.Name,
6087+
TemplateDisplayName:t.DisplayName,
6088+
TemplateOrganizationID:w.OrganizationID,
6089+
}
6090+
}
6091+
6092+
s:=templateStats[w.TemplateID]
6093+
s.TotalBuilds++
6094+
ifjob.JobStatus==database.ProvisionerJobStatusFailed {
6095+
s.FailedBuilds++
6096+
}
6097+
templateStats[w.TemplateID]=s
6098+
}
6099+
6100+
rows:=make([]database.GetWorkspaceBuildStatsByTemplatesRow,0,len(templateStats))
6101+
for_,ts:=rangetemplateStats {
6102+
rows=append(rows,ts)
6103+
}
6104+
6105+
sort.Slice(rows,func(i,jint)bool {
6106+
returnrows[i].TemplateName<rows[j].TemplateName
6107+
})
6108+
returnrows,nil
6109+
}
6110+
59676111
func (q*FakeQuerier)GetWorkspaceBuildsByWorkspaceID(_ context.Context,
59686112
params database.GetWorkspaceBuildsByWorkspaceIDParams,
59696113
) ([]database.WorkspaceBuild,error) {
@@ -9440,6 +9584,26 @@ func (q *FakeQuerier) UpsertLogoURL(_ context.Context, data string) error {
94409584
returnnil
94419585
}
94429586

9587+
func (q*FakeQuerier)UpsertNotificationReportGeneratorLog(_ context.Context,arg database.UpsertNotificationReportGeneratorLogParams)error {
9588+
err:=validateDatabaseType(arg)
9589+
iferr!=nil {
9590+
returnerr
9591+
}
9592+
9593+
q.mutex.Lock()
9594+
deferq.mutex.Unlock()
9595+
9596+
fori,record:=rangeq.notificationReportGeneratorLogs {
9597+
ifarg.NotificationTemplateID==record.NotificationTemplateID {
9598+
q.notificationReportGeneratorLogs[i].LastGeneratedAt=arg.LastGeneratedAt
9599+
returnnil
9600+
}
9601+
}
9602+
9603+
q.notificationReportGeneratorLogs=append(q.notificationReportGeneratorLogs,database.NotificationReportGeneratorLog(arg))
9604+
returnnil
9605+
}
9606+
94439607
func (q*FakeQuerier)UpsertNotificationsSettings(_ context.Context,datastring)error {
94449608
q.mutex.Lock()
94459609
deferq.mutex.Unlock()

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp