@@ -29,6 +29,7 @@ import (
29
29
"github.com/coder/coder/v2/coderd/database/provisionerjobs"
30
30
"github.com/coder/coder/v2/coderd/database/pubsub"
31
31
"github.com/coder/coder/v2/coderd/notifications"
32
+ "github.com/coder/coder/v2/coderd/provisionerdserver"
32
33
"github.com/coder/coder/v2/coderd/schedule"
33
34
"github.com/coder/coder/v2/coderd/wsbuilder"
34
35
"github.com/coder/coder/v2/codersdk"
@@ -132,6 +133,39 @@ func (e *Executor) Run() {
132
133
})
133
134
}
134
135
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
+
135
169
func (e * Executor )runOnce (t time.Time )Stats {
136
170
stats := Stats {
137
171
Transitions :make (map [uuid.UUID ]database.WorkspaceTransition ),
@@ -281,6 +315,22 @@ func (e *Executor) runOnce(t time.Time) Stats {
281
315
return nil
282
316
}
283
317
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
+
284
334
if nextTransition != "" {
285
335
builder := wsbuilder .New (ws ,nextTransition ,* e .buildUsageChecker .Load ()).
286
336
SetLastWorkspaceBuildInTx (& latestBuild ).