feat: implement scheduling mechanism for prebuilds (#18126)

Closes https://github.com/coder/internal/issues/312
Depends on https://github.com/coder/terraform-provider-coder/pull/408

This PR adds support for defining an **autoscaling block** for
prebuilds, allowing number of desired instances to scale dynamically
based on a schedule.

Example usage:
```
data "coder_workspace_preset" "us-nix" {
  ...
  
  prebuilds = {
    instances = 0                  # default to 0 instances
    
    scheduling = {
      timezone = "UTC"             # a single timezone is used for simplicity
      
      # Scale to 3 instances during the work week
      schedule {
        cron = "* 8-18 * * 1-5"    # from 8AM–6:59PM, Mon–Fri, UTC
        instances = 3              # scale to 3 instances
      }
      
      # Scale to 1 instance on Saturdays for urgent support queries
      schedule {
        cron = "* 8-14 * * 6"      # from 8AM–2:59PM, Sat, UTC
        instances = 1              # scale to 1 instance
      }
    }
  }
}
```

### Behavior
- Multiple `schedule` blocks per `prebuilds` block are supported.
- If the current time matches any defined autoscaling schedule, the
corresponding number of instances is used.
- If no schedule matches, the **default instance count**
(`prebuilds.instances`) is used as a fallback.

### Why
This feature allows prebuild instance capacity to adapt to predictable
usage patterns, such as:
- Scaling up during business hours or high-demand periods
- Reducing capacity during off-hours to save resources

### Cron specification
The cron specification is interpreted as a **continuous time range.**

For example, the expression:

```
* 9-18 * * 1-5
```

is intended to represent a continuous range from **09:00 to 18:59**,
Monday through Friday.

However, due to minor implementation imprecision, it is currently
interpreted as a range from **08:59:00 to 18:58:59**, Monday through
Friday.

This slight discrepancy arises because the evaluation is based on
whether a specific **point in time** falls within the range, using the
`github.com/coder/coder/v2/coderd/schedule/cron` library, which performs
per-minute matching rather than strict range evaluation.

---------

Co-authored-by: Danny Kopping <danny@coder.com>
This commit is contained in:
Yevhenii Shcherbina
2025-06-19 11:08:48 -04:00
committed by GitHub
parent 511fd09582
commit 0f6ca55238
38 changed files with 2528 additions and 871 deletions
+16
View File
@@ -1686,6 +1686,13 @@ func (q *querier) GetAPIKeysLastUsedAfter(ctx context.Context, lastUsed time.Tim
return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.GetAPIKeysLastUsedAfter)(ctx, lastUsed)
}
func (q *querier) GetActivePresetPrebuildSchedules(ctx context.Context) ([]database.TemplateVersionPresetPrebuildSchedule, error) {
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceTemplate.All()); err != nil {
return nil, err
}
return q.db.GetActivePresetPrebuildSchedules(ctx)
}
func (q *querier) GetActiveUserCount(ctx context.Context, includeSystem bool) (int64, error) {
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceSystem); err != nil {
return 0, err
@@ -3661,6 +3668,15 @@ func (q *querier) InsertPresetParameters(ctx context.Context, arg database.Inser
return q.db.InsertPresetParameters(ctx, arg)
}
func (q *querier) InsertPresetPrebuildSchedule(ctx context.Context, arg database.InsertPresetPrebuildScheduleParams) (database.TemplateVersionPresetPrebuildSchedule, error) {
err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceTemplate)
if err != nil {
return database.TemplateVersionPresetPrebuildSchedule{}, err
}
return q.db.InsertPresetPrebuildSchedule(ctx, arg)
}
func (q *querier) InsertProvisionerJob(ctx context.Context, arg database.InsertProvisionerJobParams) (database.ProvisionerJob, error) {
// TODO: Remove this once we have a proper rbac check for provisioner jobs.
// Details in https://github.com/coder/coder/issues/16160
+29
View File
@@ -979,6 +979,29 @@ func (s *MethodTestSuite) TestOrganization() {
}
check.Args(insertPresetParametersParams).Asserts(rbac.ResourceTemplate, policy.ActionUpdate)
}))
s.Run("InsertPresetPrebuildSchedule", s.Subtest(func(db database.Store, check *expects) {
org := dbgen.Organization(s.T(), db, database.Organization{})
user := dbgen.User(s.T(), db, database.User{})
template := dbgen.Template(s.T(), db, database.Template{
CreatedBy: user.ID,
OrganizationID: org.ID,
})
templateVersion := dbgen.TemplateVersion(s.T(), db, database.TemplateVersion{
TemplateID: uuid.NullUUID{UUID: template.ID, Valid: true},
OrganizationID: org.ID,
CreatedBy: user.ID,
})
preset := dbgen.Preset(s.T(), db, database.InsertPresetParams{
TemplateVersionID: templateVersion.ID,
Name: "test",
})
arg := database.InsertPresetPrebuildScheduleParams{
PresetID: preset.ID,
}
check.Args(arg).
Asserts(rbac.ResourceTemplate, policy.ActionUpdate).
ErrorsWithInMemDB(dbmem.ErrUnimplemented)
}))
s.Run("DeleteOrganizationMember", s.Subtest(func(db database.Store, check *expects) {
o := dbgen.Organization(s.T(), db, database.Organization{})
u := dbgen.User(s.T(), db, database.User{})
@@ -4916,6 +4939,12 @@ func (s *MethodTestSuite) TestPrebuilds() {
Asserts(template.RBACObject(), policy.ActionRead).
Returns(insertedParameters)
}))
s.Run("GetActivePresetPrebuildSchedules", s.Subtest(func(db database.Store, check *expects) {
check.Args().
Asserts(rbac.ResourceTemplate.All(), policy.ActionRead).
Returns([]database.TemplateVersionPresetPrebuildSchedule{}).
ErrorsWithInMemDB(dbmem.ErrUnimplemented)
}))
s.Run("GetPresetsByTemplateVersionID", s.Subtest(func(db database.Store, check *expects) {
ctx := context.Background()
org := dbgen.Organization(s.T(), db, database.Organization{})
+1
View File
@@ -415,6 +415,7 @@ func (t TemplateVersionBuilder) Do() TemplateVersionResponse {
CreatedAt: version.CreatedAt,
DesiredInstances: preset.DesiredInstances,
InvalidateAfterSecs: preset.InvalidateAfterSecs,
SchedulingTimezone: preset.SchedulingTimezone,
})
}
+11
View File
@@ -1302,11 +1302,22 @@ func Preset(t testing.TB, db database.Store, seed database.InsertPresetParams) d
CreatedAt: takeFirst(seed.CreatedAt, dbtime.Now()),
DesiredInstances: seed.DesiredInstances,
InvalidateAfterSecs: seed.InvalidateAfterSecs,
SchedulingTimezone: seed.SchedulingTimezone,
})
require.NoError(t, err, "insert preset")
return preset
}
func PresetPrebuildSchedule(t testing.TB, db database.Store, seed database.InsertPresetPrebuildScheduleParams) database.TemplateVersionPresetPrebuildSchedule {
schedule, err := db.InsertPresetPrebuildSchedule(genCtx, database.InsertPresetPrebuildScheduleParams{
PresetID: takeFirst(seed.PresetID, uuid.New()),
CronExpression: takeFirst(seed.CronExpression, "* 9-18 * * 1-5"),
DesiredInstances: takeFirst(seed.DesiredInstances, 1),
})
require.NoError(t, err, "insert preset prebuild schedule")
return schedule
}
func PresetParameter(t testing.TB, db database.Store, seed database.InsertPresetParametersParams) []database.TemplateVersionPresetParameter {
parameters, err := db.InsertPresetParameters(genCtx, database.InsertPresetParametersParams{
TemplateVersionPresetID: takeFirst(seed.TemplateVersionPresetID, uuid.New()),
+13
View File
@@ -2778,6 +2778,10 @@ func (q *FakeQuerier) GetAPIKeysLastUsedAfter(_ context.Context, after time.Time
return apiKeys, nil
}
func (q *FakeQuerier) GetActivePresetPrebuildSchedules(ctx context.Context) ([]database.TemplateVersionPresetPrebuildSchedule, error) {
return nil, ErrUnimplemented
}
// nolint:revive // It's not a control flag, it's a filter.
func (q *FakeQuerier) GetActiveUserCount(_ context.Context, includeSystem bool) (int64, error) {
q.mutex.RLock()
@@ -9191,6 +9195,15 @@ func (q *FakeQuerier) InsertPresetParameters(_ context.Context, arg database.Ins
return presetParameters, nil
}
func (q *FakeQuerier) InsertPresetPrebuildSchedule(ctx context.Context, arg database.InsertPresetPrebuildScheduleParams) (database.TemplateVersionPresetPrebuildSchedule, error) {
err := validateDatabaseType(arg)
if err != nil {
return database.TemplateVersionPresetPrebuildSchedule{}, err
}
return database.TemplateVersionPresetPrebuildSchedule{}, ErrUnimplemented
}
func (q *FakeQuerier) InsertProvisionerJob(_ context.Context, arg database.InsertProvisionerJobParams) (database.ProvisionerJob, error) {
if err := validateDatabaseType(arg); err != nil {
return database.ProvisionerJob{}, err
+14
View File
@@ -564,6 +564,13 @@ func (m queryMetricsStore) GetAPIKeysLastUsedAfter(ctx context.Context, lastUsed
return apiKeys, err
}
func (m queryMetricsStore) GetActivePresetPrebuildSchedules(ctx context.Context) ([]database.TemplateVersionPresetPrebuildSchedule, error) {
start := time.Now()
r0, r1 := m.s.GetActivePresetPrebuildSchedules(ctx)
m.queryLatencies.WithLabelValues("GetActivePresetPrebuildSchedules").Observe(time.Since(start).Seconds())
return r0, r1
}
func (m queryMetricsStore) GetActiveUserCount(ctx context.Context, includeSystem bool) (int64, error) {
start := time.Now()
count, err := m.s.GetActiveUserCount(ctx, includeSystem)
@@ -2237,6 +2244,13 @@ func (m queryMetricsStore) InsertPresetParameters(ctx context.Context, arg datab
return r0, r1
}
func (m queryMetricsStore) InsertPresetPrebuildSchedule(ctx context.Context, arg database.InsertPresetPrebuildScheduleParams) (database.TemplateVersionPresetPrebuildSchedule, error) {
start := time.Now()
r0, r1 := m.s.InsertPresetPrebuildSchedule(ctx, arg)
m.queryLatencies.WithLabelValues("InsertPresetPrebuildSchedule").Observe(time.Since(start).Seconds())
return r0, r1
}
func (m queryMetricsStore) InsertProvisionerJob(ctx context.Context, arg database.InsertProvisionerJobParams) (database.ProvisionerJob, error) {
start := time.Now()
job, err := m.s.InsertProvisionerJob(ctx, arg)
+30
View File
@@ -1022,6 +1022,21 @@ func (mr *MockStoreMockRecorder) GetAPIKeysLastUsedAfter(ctx, lastUsed any) *gom
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAPIKeysLastUsedAfter", reflect.TypeOf((*MockStore)(nil).GetAPIKeysLastUsedAfter), ctx, lastUsed)
}
// GetActivePresetPrebuildSchedules mocks base method.
func (m *MockStore) GetActivePresetPrebuildSchedules(ctx context.Context) ([]database.TemplateVersionPresetPrebuildSchedule, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetActivePresetPrebuildSchedules", ctx)
ret0, _ := ret[0].([]database.TemplateVersionPresetPrebuildSchedule)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetActivePresetPrebuildSchedules indicates an expected call of GetActivePresetPrebuildSchedules.
func (mr *MockStoreMockRecorder) GetActivePresetPrebuildSchedules(ctx any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetActivePresetPrebuildSchedules", reflect.TypeOf((*MockStore)(nil).GetActivePresetPrebuildSchedules), ctx)
}
// GetActiveUserCount mocks base method.
func (m *MockStore) GetActiveUserCount(ctx context.Context, includeSystem bool) (int64, error) {
m.ctrl.T.Helper()
@@ -4722,6 +4737,21 @@ func (mr *MockStoreMockRecorder) InsertPresetParameters(ctx, arg any) *gomock.Ca
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertPresetParameters", reflect.TypeOf((*MockStore)(nil).InsertPresetParameters), ctx, arg)
}
// InsertPresetPrebuildSchedule mocks base method.
func (m *MockStore) InsertPresetPrebuildSchedule(ctx context.Context, arg database.InsertPresetPrebuildScheduleParams) (database.TemplateVersionPresetPrebuildSchedule, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "InsertPresetPrebuildSchedule", ctx, arg)
ret0, _ := ret[0].(database.TemplateVersionPresetPrebuildSchedule)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// InsertPresetPrebuildSchedule indicates an expected call of InsertPresetPrebuildSchedule.
func (mr *MockStoreMockRecorder) InsertPresetPrebuildSchedule(ctx, arg any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertPresetPrebuildSchedule", reflect.TypeOf((*MockStore)(nil).InsertPresetPrebuildSchedule), ctx, arg)
}
// InsertProvisionerJob mocks base method.
func (m *MockStore) InsertProvisionerJob(ctx context.Context, arg database.InsertProvisionerJobParams) (database.ProvisionerJob, error) {
m.ctrl.T.Helper()
+15 -1
View File
@@ -1497,6 +1497,13 @@ CREATE TABLE template_version_preset_parameters (
value text NOT NULL
);
CREATE TABLE template_version_preset_prebuild_schedules (
id uuid DEFAULT gen_random_uuid() NOT NULL,
preset_id uuid NOT NULL,
cron_expression text NOT NULL,
desired_instances integer NOT NULL
);
CREATE TABLE template_version_presets (
id uuid DEFAULT gen_random_uuid() NOT NULL,
template_version_id uuid NOT NULL,
@@ -1504,7 +1511,8 @@ CREATE TABLE template_version_presets (
created_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
desired_instances integer,
invalidate_after_secs integer DEFAULT 0,
prebuild_status prebuild_status DEFAULT 'healthy'::prebuild_status NOT NULL
prebuild_status prebuild_status DEFAULT 'healthy'::prebuild_status NOT NULL,
scheduling_timezone text DEFAULT ''::text NOT NULL
);
CREATE TABLE template_version_terraform_values (
@@ -2510,6 +2518,9 @@ ALTER TABLE ONLY template_version_parameters
ALTER TABLE ONLY template_version_preset_parameters
ADD CONSTRAINT template_version_preset_parameters_pkey PRIMARY KEY (id);
ALTER TABLE ONLY template_version_preset_prebuild_schedules
ADD CONSTRAINT template_version_preset_prebuild_schedules_pkey PRIMARY KEY (id);
ALTER TABLE ONLY template_version_presets
ADD CONSTRAINT template_version_presets_pkey PRIMARY KEY (id);
@@ -2965,6 +2976,9 @@ ALTER TABLE ONLY template_version_parameters
ALTER TABLE ONLY template_version_preset_parameters
ADD CONSTRAINT template_version_preset_paramet_template_version_preset_id_fkey FOREIGN KEY (template_version_preset_id) REFERENCES template_version_presets(id) ON DELETE CASCADE;
ALTER TABLE ONLY template_version_preset_prebuild_schedules
ADD CONSTRAINT template_version_preset_prebuild_schedules_preset_id_fkey FOREIGN KEY (preset_id) REFERENCES template_version_presets(id) ON DELETE CASCADE;
ALTER TABLE ONLY template_version_presets
ADD CONSTRAINT template_version_presets_template_version_id_fkey FOREIGN KEY (template_version_id) REFERENCES template_versions(id) ON DELETE CASCADE;
@@ -45,6 +45,7 @@ const (
ForeignKeyTailnetTunnelsCoordinatorID ForeignKeyConstraint = "tailnet_tunnels_coordinator_id_fkey" // ALTER TABLE ONLY tailnet_tunnels ADD CONSTRAINT tailnet_tunnels_coordinator_id_fkey FOREIGN KEY (coordinator_id) REFERENCES tailnet_coordinators(id) ON DELETE CASCADE;
ForeignKeyTemplateVersionParametersTemplateVersionID ForeignKeyConstraint = "template_version_parameters_template_version_id_fkey" // ALTER TABLE ONLY template_version_parameters ADD CONSTRAINT template_version_parameters_template_version_id_fkey FOREIGN KEY (template_version_id) REFERENCES template_versions(id) ON DELETE CASCADE;
ForeignKeyTemplateVersionPresetParametTemplateVersionPresetID ForeignKeyConstraint = "template_version_preset_paramet_template_version_preset_id_fkey" // ALTER TABLE ONLY template_version_preset_parameters ADD CONSTRAINT template_version_preset_paramet_template_version_preset_id_fkey FOREIGN KEY (template_version_preset_id) REFERENCES template_version_presets(id) ON DELETE CASCADE;
ForeignKeyTemplateVersionPresetPrebuildSchedulesPresetID ForeignKeyConstraint = "template_version_preset_prebuild_schedules_preset_id_fkey" // ALTER TABLE ONLY template_version_preset_prebuild_schedules ADD CONSTRAINT template_version_preset_prebuild_schedules_preset_id_fkey FOREIGN KEY (preset_id) REFERENCES template_version_presets(id) ON DELETE CASCADE;
ForeignKeyTemplateVersionPresetsTemplateVersionID ForeignKeyConstraint = "template_version_presets_template_version_id_fkey" // ALTER TABLE ONLY template_version_presets ADD CONSTRAINT template_version_presets_template_version_id_fkey FOREIGN KEY (template_version_id) REFERENCES template_versions(id) ON DELETE CASCADE;
ForeignKeyTemplateVersionTerraformValuesCachedModuleFiles ForeignKeyConstraint = "template_version_terraform_values_cached_module_files_fkey" // ALTER TABLE ONLY template_version_terraform_values ADD CONSTRAINT template_version_terraform_values_cached_module_files_fkey FOREIGN KEY (cached_module_files) REFERENCES files(id);
ForeignKeyTemplateVersionTerraformValuesTemplateVersionID ForeignKeyConstraint = "template_version_terraform_values_template_version_id_fkey" // ALTER TABLE ONLY template_version_terraform_values ADD CONSTRAINT template_version_terraform_values_template_version_id_fkey FOREIGN KEY (template_version_id) REFERENCES template_versions(id) ON DELETE CASCADE;
@@ -0,0 +1,6 @@
-- Drop the prebuild schedules table
DROP TABLE template_version_preset_prebuild_schedules;
-- Remove scheduling_timezone column from template_version_presets table
ALTER TABLE template_version_presets
DROP COLUMN scheduling_timezone;
@@ -0,0 +1,12 @@
-- Add scheduling_timezone column to template_version_presets table
ALTER TABLE template_version_presets
ADD COLUMN scheduling_timezone TEXT DEFAULT '' NOT NULL;
-- Add table for prebuild schedules
CREATE TABLE template_version_preset_prebuild_schedules (
id UUID PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
preset_id UUID NOT NULL,
cron_expression TEXT NOT NULL,
desired_instances INTEGER NOT NULL,
FOREIGN KEY (preset_id) REFERENCES template_version_presets (id) ON DELETE CASCADE
);
@@ -0,0 +1,13 @@
INSERT INTO
template_version_preset_prebuild_schedules (
id,
preset_id,
cron_expression,
desired_instances
)
VALUES (
'e387cac1-9bf1-4fb6-8a34-db8cfb750dd0',
'28b42cc0-c4fe-4907-a0fe-e4d20f1e9bfe',
'* 8-18 * * 1-5',
1
);
+8
View File
@@ -3410,6 +3410,7 @@ type TemplateVersionPreset struct {
DesiredInstances sql.NullInt32 `db:"desired_instances" json:"desired_instances"`
InvalidateAfterSecs sql.NullInt32 `db:"invalidate_after_secs" json:"invalidate_after_secs"`
PrebuildStatus PrebuildStatus `db:"prebuild_status" json:"prebuild_status"`
SchedulingTimezone string `db:"scheduling_timezone" json:"scheduling_timezone"`
}
type TemplateVersionPresetParameter struct {
@@ -3419,6 +3420,13 @@ type TemplateVersionPresetParameter struct {
Value string `db:"value" json:"value"`
}
type TemplateVersionPresetPrebuildSchedule struct {
ID uuid.UUID `db:"id" json:"id"`
PresetID uuid.UUID `db:"preset_id" json:"preset_id"`
CronExpression string `db:"cron_expression" json:"cron_expression"`
DesiredInstances int32 `db:"desired_instances" json:"desired_instances"`
}
type TemplateVersionTable struct {
ID uuid.UUID `db:"id" json:"id"`
TemplateID uuid.NullUUID `db:"template_id" json:"template_id"`
+2
View File
@@ -137,6 +137,7 @@ type sqlcQuerier interface {
GetAPIKeysByLoginType(ctx context.Context, loginType LoginType) ([]APIKey, error)
GetAPIKeysByUserID(ctx context.Context, arg GetAPIKeysByUserIDParams) ([]APIKey, error)
GetAPIKeysLastUsedAfter(ctx context.Context, lastUsed time.Time) ([]APIKey, error)
GetActivePresetPrebuildSchedules(ctx context.Context) ([]TemplateVersionPresetPrebuildSchedule, error)
GetActiveUserCount(ctx context.Context, includeSystem bool) (int64, error)
GetActiveWorkspaceBuildsByTemplateID(ctx context.Context, templateID uuid.UUID) ([]WorkspaceBuild, error)
GetAllTailnetAgents(ctx context.Context) ([]TailnetAgent, error)
@@ -498,6 +499,7 @@ type sqlcQuerier interface {
InsertOrganizationMember(ctx context.Context, arg InsertOrganizationMemberParams) (OrganizationMember, error)
InsertPreset(ctx context.Context, arg InsertPresetParams) (TemplateVersionPreset, error)
InsertPresetParameters(ctx context.Context, arg InsertPresetParametersParams) ([]TemplateVersionPresetParameter, error)
InsertPresetPrebuildSchedule(ctx context.Context, arg InsertPresetPrebuildScheduleParams) (TemplateVersionPresetPrebuildSchedule, error)
InsertProvisionerJob(ctx context.Context, arg InsertProvisionerJobParams) (ProvisionerJob, error)
InsertProvisionerJobLogs(ctx context.Context, arg InsertProvisionerJobLogsParams) ([]ProvisionerJobLog, error)
InsertProvisionerJobTimings(ctx context.Context, arg InsertProvisionerJobTimingsParams) ([]ProvisionerJobTiming, error)
+93 -7
View File
@@ -6511,7 +6511,8 @@ SELECT
tvp.id,
tvp.name,
tvp.desired_instances AS desired_instances,
tvp.invalidate_after_secs AS ttl,
tvp.scheduling_timezone,
tvp.invalidate_after_secs AS ttl,
tvp.prebuild_status,
t.deleted,
t.deprecated != '' AS deprecated
@@ -6535,6 +6536,7 @@ type GetTemplatePresetsWithPrebuildsRow struct {
ID uuid.UUID `db:"id" json:"id"`
Name string `db:"name" json:"name"`
DesiredInstances sql.NullInt32 `db:"desired_instances" json:"desired_instances"`
SchedulingTimezone string `db:"scheduling_timezone" json:"scheduling_timezone"`
Ttl sql.NullInt32 `db:"ttl" json:"ttl"`
PrebuildStatus PrebuildStatus `db:"prebuild_status" json:"prebuild_status"`
Deleted bool `db:"deleted" json:"deleted"`
@@ -6564,6 +6566,7 @@ func (q *sqlQuerier) GetTemplatePresetsWithPrebuilds(ctx context.Context, templa
&i.ID,
&i.Name,
&i.DesiredInstances,
&i.SchedulingTimezone,
&i.Ttl,
&i.PrebuildStatus,
&i.Deleted,
@@ -6582,8 +6585,51 @@ func (q *sqlQuerier) GetTemplatePresetsWithPrebuilds(ctx context.Context, templa
return items, nil
}
const getActivePresetPrebuildSchedules = `-- name: GetActivePresetPrebuildSchedules :many
SELECT
tvpps.id, tvpps.preset_id, tvpps.cron_expression, tvpps.desired_instances
FROM
template_version_preset_prebuild_schedules tvpps
INNER JOIN template_version_presets tvp ON tvp.id = tvpps.preset_id
INNER JOIN template_versions tv ON tv.id = tvp.template_version_id
INNER JOIN templates t ON t.id = tv.template_id
WHERE
-- Template version is active, and template is not deleted or deprecated
tv.id = t.active_version_id
AND NOT t.deleted
AND t.deprecated = ''
`
func (q *sqlQuerier) GetActivePresetPrebuildSchedules(ctx context.Context) ([]TemplateVersionPresetPrebuildSchedule, error) {
rows, err := q.db.QueryContext(ctx, getActivePresetPrebuildSchedules)
if err != nil {
return nil, err
}
defer rows.Close()
var items []TemplateVersionPresetPrebuildSchedule
for rows.Next() {
var i TemplateVersionPresetPrebuildSchedule
if err := rows.Scan(
&i.ID,
&i.PresetID,
&i.CronExpression,
&i.DesiredInstances,
); 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 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, 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, 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
@@ -6597,6 +6643,7 @@ type GetPresetByIDRow struct {
DesiredInstances sql.NullInt32 `db:"desired_instances" json:"desired_instances"`
InvalidateAfterSecs sql.NullInt32 `db:"invalidate_after_secs" json:"invalidate_after_secs"`
PrebuildStatus PrebuildStatus `db:"prebuild_status" json:"prebuild_status"`
SchedulingTimezone string `db:"scheduling_timezone" json:"scheduling_timezone"`
TemplateID uuid.NullUUID `db:"template_id" json:"template_id"`
OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
}
@@ -6612,6 +6659,7 @@ func (q *sqlQuerier) GetPresetByID(ctx context.Context, presetID uuid.UUID) (Get
&i.DesiredInstances,
&i.InvalidateAfterSecs,
&i.PrebuildStatus,
&i.SchedulingTimezone,
&i.TemplateID,
&i.OrganizationID,
)
@@ -6620,7 +6668,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.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
FROM
template_version_presets
INNER JOIN workspace_builds ON workspace_builds.template_version_preset_id = template_version_presets.id
@@ -6639,6 +6687,7 @@ func (q *sqlQuerier) GetPresetByWorkspaceBuildID(ctx context.Context, workspaceB
&i.DesiredInstances,
&i.InvalidateAfterSecs,
&i.PrebuildStatus,
&i.SchedulingTimezone,
)
return i, err
}
@@ -6720,7 +6769,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
id, template_version_id, name, created_at, desired_instances, invalidate_after_secs, prebuild_status, scheduling_timezone
FROM
template_version_presets
WHERE
@@ -6744,6 +6793,7 @@ func (q *sqlQuerier) GetPresetsByTemplateVersionID(ctx context.Context, template
&i.DesiredInstances,
&i.InvalidateAfterSecs,
&i.PrebuildStatus,
&i.SchedulingTimezone,
); err != nil {
return nil, err
}
@@ -6765,7 +6815,8 @@ INSERT INTO template_version_presets (
name,
created_at,
desired_instances,
invalidate_after_secs
invalidate_after_secs,
scheduling_timezone
)
VALUES (
$1,
@@ -6773,8 +6824,9 @@ VALUES (
$3,
$4,
$5,
$6
) RETURNING id, template_version_id, name, created_at, desired_instances, invalidate_after_secs, prebuild_status
$6,
$7
) RETURNING id, template_version_id, name, created_at, desired_instances, invalidate_after_secs, prebuild_status, scheduling_timezone
`
type InsertPresetParams struct {
@@ -6784,6 +6836,7 @@ type InsertPresetParams struct {
CreatedAt time.Time `db:"created_at" json:"created_at"`
DesiredInstances sql.NullInt32 `db:"desired_instances" json:"desired_instances"`
InvalidateAfterSecs sql.NullInt32 `db:"invalidate_after_secs" json:"invalidate_after_secs"`
SchedulingTimezone string `db:"scheduling_timezone" json:"scheduling_timezone"`
}
func (q *sqlQuerier) InsertPreset(ctx context.Context, arg InsertPresetParams) (TemplateVersionPreset, error) {
@@ -6794,6 +6847,7 @@ func (q *sqlQuerier) InsertPreset(ctx context.Context, arg InsertPresetParams) (
arg.CreatedAt,
arg.DesiredInstances,
arg.InvalidateAfterSecs,
arg.SchedulingTimezone,
)
var i TemplateVersionPreset
err := row.Scan(
@@ -6804,6 +6858,7 @@ func (q *sqlQuerier) InsertPreset(ctx context.Context, arg InsertPresetParams) (
&i.DesiredInstances,
&i.InvalidateAfterSecs,
&i.PrebuildStatus,
&i.SchedulingTimezone,
)
return i, err
}
@@ -6852,6 +6907,37 @@ func (q *sqlQuerier) InsertPresetParameters(ctx context.Context, arg InsertPrese
return items, nil
}
const insertPresetPrebuildSchedule = `-- name: InsertPresetPrebuildSchedule :one
INSERT INTO template_version_preset_prebuild_schedules (
preset_id,
cron_expression,
desired_instances
)
VALUES (
$1,
$2,
$3
) RETURNING id, preset_id, cron_expression, desired_instances
`
type InsertPresetPrebuildScheduleParams struct {
PresetID uuid.UUID `db:"preset_id" json:"preset_id"`
CronExpression string `db:"cron_expression" json:"cron_expression"`
DesiredInstances int32 `db:"desired_instances" json:"desired_instances"`
}
func (q *sqlQuerier) InsertPresetPrebuildSchedule(ctx context.Context, arg InsertPresetPrebuildScheduleParams) (TemplateVersionPresetPrebuildSchedule, error) {
row := q.db.QueryRowContext(ctx, insertPresetPrebuildSchedule, arg.PresetID, arg.CronExpression, arg.DesiredInstances)
var i TemplateVersionPresetPrebuildSchedule
err := row.Scan(
&i.ID,
&i.PresetID,
&i.CronExpression,
&i.DesiredInstances,
)
return i, err
}
const updatePresetPrebuildStatus = `-- name: UpdatePresetPrebuildStatus :exec
UPDATE template_version_presets
SET prebuild_status = $1
+2 -1
View File
@@ -35,7 +35,8 @@ SELECT
tvp.id,
tvp.name,
tvp.desired_instances AS desired_instances,
tvp.invalidate_after_secs AS ttl,
tvp.scheduling_timezone,
tvp.invalidate_after_secs AS ttl,
tvp.prebuild_status,
t.deleted,
t.deprecated != '' AS deprecated
+30 -2
View File
@@ -5,7 +5,8 @@ INSERT INTO template_version_presets (
name,
created_at,
desired_instances,
invalidate_after_secs
invalidate_after_secs,
scheduling_timezone
)
VALUES (
@id,
@@ -13,7 +14,8 @@ VALUES (
@name,
@created_at,
@desired_instances,
@invalidate_after_secs
@invalidate_after_secs,
@scheduling_timezone
) RETURNING *;
-- name: InsertPresetParameters :many
@@ -25,6 +27,18 @@ SELECT
unnest(@values :: TEXT[])
RETURNING *;
-- name: InsertPresetPrebuildSchedule :one
INSERT INTO template_version_preset_prebuild_schedules (
preset_id,
cron_expression,
desired_instances
)
VALUES (
@preset_id,
@cron_expression,
@desired_instances
) RETURNING *;
-- name: UpdatePresetPrebuildStatus :exec
UPDATE template_version_presets
SET prebuild_status = @status
@@ -69,3 +83,17 @@ SELECT tvp.*, 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 = @preset_id;
-- name: GetActivePresetPrebuildSchedules :many
SELECT
tvpps.*
FROM
template_version_preset_prebuild_schedules tvpps
INNER JOIN template_version_presets tvp ON tvp.id = tvpps.preset_id
INNER JOIN template_versions tv ON tv.id = tvp.template_version_id
INNER JOIN templates t ON t.id = tv.template_id
WHERE
-- Template version is active, and template is not deleted or deprecated
tv.id = t.active_version_id
AND NOT t.deleted
AND t.deprecated = '';
+1
View File
@@ -61,6 +61,7 @@ const (
UniqueTemplateUsageStatsPkey UniqueConstraint = "template_usage_stats_pkey" // ALTER TABLE ONLY template_usage_stats ADD CONSTRAINT template_usage_stats_pkey PRIMARY KEY (start_time, template_id, user_id);
UniqueTemplateVersionParametersTemplateVersionIDNameKey UniqueConstraint = "template_version_parameters_template_version_id_name_key" // ALTER TABLE ONLY template_version_parameters ADD CONSTRAINT template_version_parameters_template_version_id_name_key UNIQUE (template_version_id, name);
UniqueTemplateVersionPresetParametersPkey UniqueConstraint = "template_version_preset_parameters_pkey" // ALTER TABLE ONLY template_version_preset_parameters ADD CONSTRAINT template_version_preset_parameters_pkey PRIMARY KEY (id);
UniqueTemplateVersionPresetPrebuildSchedulesPkey UniqueConstraint = "template_version_preset_prebuild_schedules_pkey" // ALTER TABLE ONLY template_version_preset_prebuild_schedules ADD CONSTRAINT template_version_preset_prebuild_schedules_pkey PRIMARY KEY (id);
UniqueTemplateVersionPresetsPkey UniqueConstraint = "template_version_presets_pkey" // ALTER TABLE ONLY template_version_presets ADD CONSTRAINT template_version_presets_pkey PRIMARY KEY (id);
UniqueTemplateVersionTerraformValuesTemplateVersionIDKey UniqueConstraint = "template_version_terraform_values_template_version_id_key" // ALTER TABLE ONLY template_version_terraform_values ADD CONSTRAINT template_version_terraform_values_template_version_id_key UNIQUE (template_version_id);
UniqueTemplateVersionVariablesTemplateVersionIDNameKey UniqueConstraint = "template_version_variables_template_version_id_name_key" // ALTER TABLE ONLY template_version_variables ADD CONSTRAINT template_version_variables_template_version_id_name_key UNIQUE (template_version_id, name);