@@ -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 ).
0 commit comments