Skip to content

Commit 6c902a7

Browse files
authored
fix: don't create autostart workspace builds with no available provisioners (#19067)
This should fix #17941 by introducing a check for whether there are any valid (non-stale provisioners for a job in the autobuild executor code path. --------- Signed-off-by: Callum Styan <callumstyan@gmail.com>
1 parent a25d856 commit 6c902a7

File tree

4 files changed

+476
-84
lines changed

4 files changed

+476
-84
lines changed

coderd/autobuild/lifecycle_executor.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"github.com/coder/coder/v2/coderd/database/provisionerjobs"
3030
"github.com/coder/coder/v2/coderd/database/pubsub"
3131
"github.com/coder/coder/v2/coderd/notifications"
32+
"github.com/coder/coder/v2/coderd/provisionerdserver"
3233
"github.com/coder/coder/v2/coderd/schedule"
3334
"github.com/coder/coder/v2/coderd/wsbuilder"
3435
"github.com/coder/coder/v2/codersdk"
@@ -132,6 +133,39 @@ func (e *Executor) Run() {
132133
})
133134
}
134135

136+
// hasValidProvisioner checks whether there is at least one valid (non-stale, correct tags) provisioner
137+
// based on time t and the tags maps (such as from a templateVersionJob).
138+
func (e *Executor) hasValidProvisioner(ctx context.Context, tx database.Store, t time.Time, ws database.Workspace, tags map[string]string) (bool, error) {
139+
queryParams := database.GetProvisionerDaemonsByOrganizationParams{
140+
OrganizationID: ws.OrganizationID,
141+
WantTags: tags,
142+
}
143+
144+
// nolint: gocritic // The user (in this case, the user/context for autostart builds) may not have the full
145+
// permissions to read provisioner daemons, but we need to check if there's any for the job prior to the
146+
// execution of the job via autostart to fix: https://github.com/coder/coder/issues/17941
147+
provisionerDaemons, err := tx.GetProvisionerDaemonsByOrganization(dbauthz.AsSystemReadProvisionerDaemons(ctx), queryParams)
148+
if err != nil {
149+
return false, xerrors.Errorf("get provisioner daemons: %w", err)
150+
}
151+
152+
logger := e.log.With(slog.F("tags", tags))
153+
// Check if any provisioners are active (not stale)
154+
for _, pd := range provisionerDaemons {
155+
if pd.LastSeenAt.Valid {
156+
age := t.Sub(pd.LastSeenAt.Time)
157+
if age <= provisionerdserver.StaleInterval {
158+
logger.Debug(ctx, "hasValidProvisioner: found active provisioner",
159+
slog.F("daemon_id", pd.ID),
160+
)
161+
return true, nil
162+
}
163+
}
164+
}
165+
logger.Debug(ctx, "hasValidProvisioner: no active provisioners found")
166+
return false, nil
167+
}
168+
135169
func (e *Executor) runOnce(t time.Time) Stats {
136170
stats := Stats{
137171
Transitions: make(map[uuid.UUID]database.WorkspaceTransition),
@@ -281,6 +315,22 @@ func (e *Executor) runOnce(t time.Time) Stats {
281315
return nil
282316
}
283317

318+
// Get the template version job to access tags
319+
templateVersionJob, err := tx.GetProvisionerJobByID(e.ctx, activeTemplateVersion.JobID)
320+
if err != nil {
321+
return xerrors.Errorf("get template version job: %w", err)
322+
}
323+
324+
// Before creating the workspace build, check for available provisioners
325+
hasProvisioners, err := e.hasValidProvisioner(e.ctx, tx, t, ws, templateVersionJob.Tags)
326+
if err != nil {
327+
return xerrors.Errorf("check provisioner availability: %w", err)
328+
}
329+
if !hasProvisioners {
330+
log.Warn(e.ctx, "skipping autostart - no available provisioners")
331+
return nil // Skip this workspace
332+
}
333+
284334
if nextTransition != "" {
285335
builder := wsbuilder.New(ws, nextTransition, *e.buildUsageChecker.Load()).
286336
SetLastWorkspaceBuildInTx(&latestBuild).

0 commit comments

Comments
 (0)