mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: notify on successful autoupdate (#13903)
This commit is contained in:
@@ -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"
|
||||
)
|
||||
@@ -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.
|
||||
@@ -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),
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user