feat: add prebuild invalidation via last_invalidated_at timestamp (#20582)

Updates #17917
This commit is contained in:
Marcin Tojek
2025-11-20 17:12:25 +01:00
committed by GitHub
parent c2319e5b4e
commit d004710a74
27 changed files with 637 additions and 23 deletions
+12
View File
@@ -1021,6 +1021,18 @@ func AIBridgeToolUsage(usage database.AIBridgeToolUsage) codersdk.AIBridgeToolUs
}
}
func InvalidatedPresets(invalidatedPresets []database.UpdatePresetsLastInvalidatedAtRow) []codersdk.InvalidatedPreset {
var presets []codersdk.InvalidatedPreset
for _, p := range invalidatedPresets {
presets = append(presets, codersdk.InvalidatedPreset{
TemplateName: p.TemplateName,
TemplateVersionName: p.TemplateVersionName,
PresetName: p.TemplateVersionPresetName,
})
}
return presets
}
func jsonOrEmptyMap(rawMessage pqtype.NullRawMessage) map[string]any {
var m map[string]any
if !rawMessage.Valid {
+14
View File
@@ -4972,6 +4972,20 @@ func (q *querier) UpdatePresetPrebuildStatus(ctx context.Context, arg database.U
return q.db.UpdatePresetPrebuildStatus(ctx, arg)
}
func (q *querier) UpdatePresetsLastInvalidatedAt(ctx context.Context, arg database.UpdatePresetsLastInvalidatedAtParams) ([]database.UpdatePresetsLastInvalidatedAtRow, error) {
// Fetch template to check authorization
template, err := q.db.GetTemplateByID(ctx, arg.TemplateID)
if err != nil {
return nil, err
}
if err := q.authorizeContext(ctx, policy.ActionUpdate, template); err != nil {
return nil, err
}
return q.db.UpdatePresetsLastInvalidatedAt(ctx, arg)
}
func (q *querier) UpdateProvisionerDaemonLastSeenAt(ctx context.Context, arg database.UpdateProvisionerDaemonLastSeenAtParams) error {
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceProvisionerDaemon); err != nil {
return err
+7
View File
@@ -1315,6 +1315,13 @@ func (s *MethodTestSuite) TestTemplate() {
dbm.EXPECT().UpsertTemplateUsageStats(gomock.Any()).Return(nil).AnyTimes()
check.Asserts(rbac.ResourceSystem, policy.ActionUpdate)
}))
s.Run("UpdatePresetsLastInvalidatedAt", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
t1 := testutil.Fake(s.T(), faker, database.Template{})
arg := database.UpdatePresetsLastInvalidatedAtParams{LastInvalidatedAt: sql.NullTime{Valid: true, Time: dbtime.Now()}, TemplateID: t1.ID}
dbm.EXPECT().GetTemplateByID(gomock.Any(), t1.ID).Return(t1, nil).AnyTimes()
dbm.EXPECT().UpdatePresetsLastInvalidatedAt(gomock.Any(), arg).Return([]database.UpdatePresetsLastInvalidatedAtRow{}, nil).AnyTimes()
check.Args(arg).Asserts(t1, policy.ActionUpdate)
}))
}
func (s *MethodTestSuite) TestUser() {
+1
View File
@@ -613,6 +613,7 @@ func (t TemplateVersionBuilder) Do() TemplateVersionResponse {
IsDefault: false,
Description: preset.Description,
Icon: preset.Icon,
LastInvalidatedAt: preset.LastInvalidatedAt,
})
t.logger.Debug(context.Background(), "added preset",
slog.F("preset_id", prst.ID),
+1
View File
@@ -1428,6 +1428,7 @@ func Preset(t testing.TB, db database.Store, seed database.InsertPresetParams) d
IsDefault: seed.IsDefault,
Description: seed.Description,
Icon: seed.Icon,
LastInvalidatedAt: seed.LastInvalidatedAt,
})
require.NoError(t, err, "insert preset")
return preset
@@ -3070,6 +3070,13 @@ func (m queryMetricsStore) UpdatePresetPrebuildStatus(ctx context.Context, arg d
return r0
}
func (m queryMetricsStore) UpdatePresetsLastInvalidatedAt(ctx context.Context, arg database.UpdatePresetsLastInvalidatedAtParams) ([]database.UpdatePresetsLastInvalidatedAtRow, error) {
start := time.Now()
r0, r1 := m.s.UpdatePresetsLastInvalidatedAt(ctx, arg)
m.queryLatencies.WithLabelValues("UpdatePresetsLastInvalidatedAt").Observe(time.Since(start).Seconds())
return r0, r1
}
func (m queryMetricsStore) UpdateProvisionerDaemonLastSeenAt(ctx context.Context, arg database.UpdateProvisionerDaemonLastSeenAtParams) error {
start := time.Now()
r0 := m.s.UpdateProvisionerDaemonLastSeenAt(ctx, arg)
+15
View File
@@ -6598,6 +6598,21 @@ func (mr *MockStoreMockRecorder) UpdatePresetPrebuildStatus(ctx, arg any) *gomoc
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdatePresetPrebuildStatus", reflect.TypeOf((*MockStore)(nil).UpdatePresetPrebuildStatus), ctx, arg)
}
// UpdatePresetsLastInvalidatedAt mocks base method.
func (m *MockStore) UpdatePresetsLastInvalidatedAt(ctx context.Context, arg database.UpdatePresetsLastInvalidatedAtParams) ([]database.UpdatePresetsLastInvalidatedAtRow, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdatePresetsLastInvalidatedAt", ctx, arg)
ret0, _ := ret[0].([]database.UpdatePresetsLastInvalidatedAtRow)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UpdatePresetsLastInvalidatedAt indicates an expected call of UpdatePresetsLastInvalidatedAt.
func (mr *MockStoreMockRecorder) UpdatePresetsLastInvalidatedAt(ctx, arg any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdatePresetsLastInvalidatedAt", reflect.TypeOf((*MockStore)(nil).UpdatePresetsLastInvalidatedAt), ctx, arg)
}
// UpdateProvisionerDaemonLastSeenAt mocks base method.
func (m *MockStore) UpdateProvisionerDaemonLastSeenAt(ctx context.Context, arg database.UpdateProvisionerDaemonLastSeenAtParams) error {
m.ctrl.T.Helper()
+2 -1
View File
@@ -2170,7 +2170,8 @@ CREATE TABLE template_version_presets (
scheduling_timezone text DEFAULT ''::text NOT NULL,
is_default boolean DEFAULT false NOT NULL,
description character varying(128) DEFAULT ''::character varying NOT NULL,
icon character varying(256) DEFAULT ''::character varying NOT NULL
icon character varying(256) DEFAULT ''::character varying NOT NULL,
last_invalidated_at timestamp with time zone
);
COMMENT ON COLUMN template_version_presets.description IS 'Short text describing the preset (max 128 characters).';
@@ -0,0 +1 @@
ALTER TABLE template_version_presets DROP COLUMN last_invalidated_at;
@@ -0,0 +1 @@
ALTER TABLE template_version_presets ADD COLUMN last_invalidated_at TIMESTAMPTZ;
+2 -1
View File
@@ -4452,7 +4452,8 @@ type TemplateVersionPreset struct {
// Short text describing the preset (max 128 characters).
Description string `db:"description" json:"description"`
// URL or path to an icon representing the preset (max 256 characters).
Icon string `db:"icon" json:"icon"`
Icon string `db:"icon" json:"icon"`
LastInvalidatedAt sql.NullTime `db:"last_invalidated_at" json:"last_invalidated_at"`
}
type TemplateVersionPresetParameter struct {
+1
View File
@@ -673,6 +673,7 @@ type sqlcQuerier interface {
// This is an optimization to clean up stale pending jobs.
UpdatePrebuildProvisionerJobWithCancel(ctx context.Context, arg UpdatePrebuildProvisionerJobWithCancelParams) ([]UpdatePrebuildProvisionerJobWithCancelRow, error)
UpdatePresetPrebuildStatus(ctx context.Context, arg UpdatePresetPrebuildStatusParams) error
UpdatePresetsLastInvalidatedAt(ctx context.Context, arg UpdatePresetsLastInvalidatedAtParams) ([]UpdatePresetsLastInvalidatedAtRow, error)
UpdateProvisionerDaemonLastSeenAt(ctx context.Context, arg UpdateProvisionerDaemonLastSeenAtParams) error
UpdateProvisionerJobByID(ctx context.Context, arg UpdateProvisionerJobByIDParams) error
UpdateProvisionerJobLogsLength(ctx context.Context, arg UpdateProvisionerJobLogsLengthParams) error
+69 -6
View File
@@ -8709,6 +8709,7 @@ SELECT
tvp.scheduling_timezone,
tvp.invalidate_after_secs AS ttl,
tvp.prebuild_status,
tvp.last_invalidated_at,
t.deleted,
t.deprecated != '' AS deprecated
FROM templates t
@@ -8734,6 +8735,7 @@ type GetTemplatePresetsWithPrebuildsRow struct {
SchedulingTimezone string `db:"scheduling_timezone" json:"scheduling_timezone"`
Ttl sql.NullInt32 `db:"ttl" json:"ttl"`
PrebuildStatus PrebuildStatus `db:"prebuild_status" json:"prebuild_status"`
LastInvalidatedAt sql.NullTime `db:"last_invalidated_at" json:"last_invalidated_at"`
Deleted bool `db:"deleted" json:"deleted"`
Deprecated bool `db:"deprecated" json:"deprecated"`
}
@@ -8764,6 +8766,7 @@ func (q *sqlQuerier) GetTemplatePresetsWithPrebuilds(ctx context.Context, templa
&i.SchedulingTimezone,
&i.Ttl,
&i.PrebuildStatus,
&i.LastInvalidatedAt,
&i.Deleted,
&i.Deprecated,
); err != nil {
@@ -8897,7 +8900,7 @@ func (q *sqlQuerier) GetActivePresetPrebuildSchedules(ctx context.Context) ([]Te
}
const getPresetByID = `-- name: GetPresetByID :one
SELECT tvp.id, tvp.template_version_id, tvp.name, tvp.created_at, tvp.desired_instances, tvp.invalidate_after_secs, tvp.prebuild_status, tvp.scheduling_timezone, tvp.is_default, tvp.description, tvp.icon, tv.template_id, tv.organization_id FROM
SELECT tvp.id, tvp.template_version_id, tvp.name, tvp.created_at, tvp.desired_instances, tvp.invalidate_after_secs, tvp.prebuild_status, tvp.scheduling_timezone, tvp.is_default, tvp.description, tvp.icon, tvp.last_invalidated_at, tv.template_id, tv.organization_id FROM
template_version_presets tvp
INNER JOIN template_versions tv ON tvp.template_version_id = tv.id
WHERE tvp.id = $1
@@ -8915,6 +8918,7 @@ type GetPresetByIDRow struct {
IsDefault bool `db:"is_default" json:"is_default"`
Description string `db:"description" json:"description"`
Icon string `db:"icon" json:"icon"`
LastInvalidatedAt sql.NullTime `db:"last_invalidated_at" json:"last_invalidated_at"`
TemplateID uuid.NullUUID `db:"template_id" json:"template_id"`
OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
}
@@ -8934,6 +8938,7 @@ func (q *sqlQuerier) GetPresetByID(ctx context.Context, presetID uuid.UUID) (Get
&i.IsDefault,
&i.Description,
&i.Icon,
&i.LastInvalidatedAt,
&i.TemplateID,
&i.OrganizationID,
)
@@ -8942,7 +8947,7 @@ func (q *sqlQuerier) GetPresetByID(ctx context.Context, presetID uuid.UUID) (Get
const getPresetByWorkspaceBuildID = `-- name: GetPresetByWorkspaceBuildID :one
SELECT
template_version_presets.id, template_version_presets.template_version_id, template_version_presets.name, template_version_presets.created_at, template_version_presets.desired_instances, template_version_presets.invalidate_after_secs, template_version_presets.prebuild_status, template_version_presets.scheduling_timezone, template_version_presets.is_default, template_version_presets.description, template_version_presets.icon
template_version_presets.id, template_version_presets.template_version_id, template_version_presets.name, template_version_presets.created_at, template_version_presets.desired_instances, template_version_presets.invalidate_after_secs, template_version_presets.prebuild_status, template_version_presets.scheduling_timezone, template_version_presets.is_default, template_version_presets.description, template_version_presets.icon, template_version_presets.last_invalidated_at
FROM
template_version_presets
INNER JOIN workspace_builds ON workspace_builds.template_version_preset_id = template_version_presets.id
@@ -8965,6 +8970,7 @@ func (q *sqlQuerier) GetPresetByWorkspaceBuildID(ctx context.Context, workspaceB
&i.IsDefault,
&i.Description,
&i.Icon,
&i.LastInvalidatedAt,
)
return i, err
}
@@ -9046,7 +9052,7 @@ func (q *sqlQuerier) GetPresetParametersByTemplateVersionID(ctx context.Context,
const getPresetsByTemplateVersionID = `-- name: GetPresetsByTemplateVersionID :many
SELECT
id, template_version_id, name, created_at, desired_instances, invalidate_after_secs, prebuild_status, scheduling_timezone, is_default, description, icon
id, template_version_id, name, created_at, desired_instances, invalidate_after_secs, prebuild_status, scheduling_timezone, is_default, description, icon, last_invalidated_at
FROM
template_version_presets
WHERE
@@ -9074,6 +9080,7 @@ func (q *sqlQuerier) GetPresetsByTemplateVersionID(ctx context.Context, template
&i.IsDefault,
&i.Description,
&i.Icon,
&i.LastInvalidatedAt,
); err != nil {
return nil, err
}
@@ -9099,7 +9106,8 @@ INSERT INTO template_version_presets (
scheduling_timezone,
is_default,
description,
icon
icon,
last_invalidated_at
)
VALUES (
$1,
@@ -9111,8 +9119,9 @@ VALUES (
$7,
$8,
$9,
$10
) RETURNING id, template_version_id, name, created_at, desired_instances, invalidate_after_secs, prebuild_status, scheduling_timezone, is_default, description, icon
$10,
$11
) RETURNING id, template_version_id, name, created_at, desired_instances, invalidate_after_secs, prebuild_status, scheduling_timezone, is_default, description, icon, last_invalidated_at
`
type InsertPresetParams struct {
@@ -9126,6 +9135,7 @@ type InsertPresetParams struct {
IsDefault bool `db:"is_default" json:"is_default"`
Description string `db:"description" json:"description"`
Icon string `db:"icon" json:"icon"`
LastInvalidatedAt sql.NullTime `db:"last_invalidated_at" json:"last_invalidated_at"`
}
func (q *sqlQuerier) InsertPreset(ctx context.Context, arg InsertPresetParams) (TemplateVersionPreset, error) {
@@ -9140,6 +9150,7 @@ func (q *sqlQuerier) InsertPreset(ctx context.Context, arg InsertPresetParams) (
arg.IsDefault,
arg.Description,
arg.Icon,
arg.LastInvalidatedAt,
)
var i TemplateVersionPreset
err := row.Scan(
@@ -9154,6 +9165,7 @@ func (q *sqlQuerier) InsertPreset(ctx context.Context, arg InsertPresetParams) (
&i.IsDefault,
&i.Description,
&i.Icon,
&i.LastInvalidatedAt,
)
return i, err
}
@@ -9249,6 +9261,57 @@ func (q *sqlQuerier) UpdatePresetPrebuildStatus(ctx context.Context, arg UpdateP
return err
}
const updatePresetsLastInvalidatedAt = `-- name: UpdatePresetsLastInvalidatedAt :many
UPDATE
template_version_presets tvp
SET
last_invalidated_at = $1
FROM
templates t
JOIN template_versions tv ON tv.id = t.active_version_id
WHERE
t.id = $2
AND tvp.template_version_id = tv.id
RETURNING
t.name AS template_name,
tv.name AS template_version_name,
tvp.name AS template_version_preset_name
`
type UpdatePresetsLastInvalidatedAtParams struct {
LastInvalidatedAt sql.NullTime `db:"last_invalidated_at" json:"last_invalidated_at"`
TemplateID uuid.UUID `db:"template_id" json:"template_id"`
}
type UpdatePresetsLastInvalidatedAtRow struct {
TemplateName string `db:"template_name" json:"template_name"`
TemplateVersionName string `db:"template_version_name" json:"template_version_name"`
TemplateVersionPresetName string `db:"template_version_preset_name" json:"template_version_preset_name"`
}
func (q *sqlQuerier) UpdatePresetsLastInvalidatedAt(ctx context.Context, arg UpdatePresetsLastInvalidatedAtParams) ([]UpdatePresetsLastInvalidatedAtRow, error) {
rows, err := q.db.QueryContext(ctx, updatePresetsLastInvalidatedAt, arg.LastInvalidatedAt, arg.TemplateID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []UpdatePresetsLastInvalidatedAtRow
for rows.Next() {
var i UpdatePresetsLastInvalidatedAtRow
if err := rows.Scan(&i.TemplateName, &i.TemplateVersionName, &i.TemplateVersionPresetName); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const deleteOldProvisionerDaemons = `-- name: DeleteOldProvisionerDaemons :exec
DELETE FROM provisioner_daemons WHERE (
(created_at < (NOW() - INTERVAL '7 days') AND last_seen_at IS NULL) OR
+1
View File
@@ -51,6 +51,7 @@ SELECT
tvp.scheduling_timezone,
tvp.invalidate_after_secs AS ttl,
tvp.prebuild_status,
tvp.last_invalidated_at,
t.deleted,
t.deprecated != '' AS deprecated
FROM templates t
+20 -2
View File
@@ -9,7 +9,8 @@ INSERT INTO template_version_presets (
scheduling_timezone,
is_default,
description,
icon
icon,
last_invalidated_at
)
VALUES (
@id,
@@ -21,7 +22,8 @@ VALUES (
@scheduling_timezone,
@is_default,
@description,
@icon
@icon,
@last_invalidated_at
) RETURNING *;
-- name: InsertPresetParameters :many
@@ -103,3 +105,19 @@ WHERE
tv.id = t.active_version_id
AND NOT t.deleted
AND t.deprecated = '';
-- name: UpdatePresetsLastInvalidatedAt :many
UPDATE
template_version_presets tvp
SET
last_invalidated_at = @last_invalidated_at
FROM
templates t
JOIN template_versions tv ON tv.id = t.active_version_id
WHERE
t.id = @template_id
AND tvp.template_version_id = tv.id
RETURNING
t.name AS template_name,
tv.name AS template_version_name,
tvp.name AS template_version_preset_name;