@@ -256,6 +256,9 @@ func (c *StoreReconciler) ReconcileAll(ctx context.Context) error {
256
256
if err != nil {
257
257
return xerrors .Errorf ("determine current snapshot: %w" , err )
258
258
}
259
+
260
+ c .reportHardLimitedPresets (snapshot )
261
+
259
262
if len (snapshot .Presets ) == 0 {
260
263
logger .Debug (ctx , "no templates found with prebuilds configured" )
261
264
return nil
@@ -296,6 +299,49 @@ func (c *StoreReconciler) ReconcileAll(ctx context.Context) error {
296
299
return err
297
300
}
298
301
302
+ func (c * StoreReconciler ) reportHardLimitedPresets (snapshot * prebuilds.GlobalSnapshot ) {
303
+ // presetsMap is a map from key (orgName:templateName:presetName) to list of corresponding presets.
304
+ // Multiple versions of a preset can exist with the same orgName, templateName, and presetName,
305
+ // because templates can have multiple versions — or deleted templates can share the same name.
306
+ presetsMap := make (map [hardLimitedPresetKey ][]database.GetTemplatePresetsWithPrebuildsRow )
307
+ for _ , preset := range snapshot .Presets {
308
+ key := hardLimitedPresetKey {
309
+ orgName : preset .OrganizationName ,
310
+ templateName : preset .TemplateName ,
311
+ presetName : preset .Name ,
312
+ }
313
+
314
+ presetsMap [key ] = append (presetsMap [key ], preset )
315
+ }
316
+
317
+ // Report a preset as hard-limited only if all the following conditions are met:
318
+ // - The preset is marked as hard-limited
319
+ // - The preset is using the active version of its template, and the template has not been deleted
320
+ //
321
+ // The second condition is important because a hard-limited preset that has become outdated is no longer relevant.
322
+ // Its associated prebuilt workspaces were likely deleted, and it's not meaningful to continue reporting it
323
+ // as hard-limited to the admin.
324
+ //
325
+ // This approach accounts for all relevant scenarios:
326
+ // Scenario #1: The admin created a new template version with the same preset names.
327
+ // Scenario #2: The admin created a new template version and renamed the presets.
328
+ // Scenario #3: The admin deleted a template version that contained hard-limited presets.
329
+ //
330
+ // In all of these cases, only the latest and non-deleted presets will be reported.
331
+ // All other presets will be ignored and eventually removed from Prometheus.
332
+ isPresetHardLimited := make (map [hardLimitedPresetKey ]bool )
333
+ for key , presets := range presetsMap {
334
+ for _ , preset := range presets {
335
+ if preset .UsingActiveVersion && ! preset .Deleted && snapshot .IsHardLimited (preset .ID ) {
336
+ isPresetHardLimited [key ] = true
337
+ break
338
+ }
339
+ }
340
+ }
341
+
342
+ c .metrics .registerHardLimitedPresets (isPresetHardLimited )
343
+ }
344
+
299
345
// SnapshotState captures the current state of all prebuilds across templates.
300
346
func (c * StoreReconciler ) SnapshotState (ctx context.Context , store database.Store ) (* prebuilds.GlobalSnapshot , error ) {
301
347
if err := ctx .Err (); err != nil {
@@ -361,24 +407,6 @@ func (c *StoreReconciler) ReconcilePreset(ctx context.Context, ps prebuilds.Pres
361
407
slog .F ("preset_name" , ps .Preset .Name ),
362
408
)
363
409
364
- // Report a metric only if the preset uses the latest version of the template and the template is not deleted.
365
- // This avoids conflicts between metrics from old and new template versions.
366
- //
367
- // NOTE: Multiple versions of a preset can exist with the same orgName, templateName, and presetName,
368
- // because templates can have multiple versions — or deleted templates can share the same name.
369
- //
370
- // The safest approach is to report the metric only for the latest version of the preset.
371
- // When a new template version is released, the metric for the new preset should overwrite
372
- // the old value in Prometheus.
373
- //
374
- // However, there’s one edge case: if an admin creates a template, it becomes hard-limited,
375
- // then deletes the template and never creates another with the same name,
376
- // the old preset will continue to be reported as hard-limited —
377
- // even though it’s deleted. This will persist until `coderd` is restarted.
378
- if ps .Preset .UsingActiveVersion && ! ps .Preset .Deleted {
379
- c .metrics .trackHardLimitedStatus (ps .Preset .OrganizationName , ps .Preset .TemplateName , ps .Preset .Name , ps .IsHardLimited )
380
- }
381
-
382
410
// If the preset reached the hard failure limit for the first time during this iteration:
383
411
// - Mark it as hard-limited in the database
384
412
// - Send notifications to template admins
0 commit comments