Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit6570400

Browse files
feat: add scheduling configuration for prebuilds (#408)
* feat: add autoscaling configuration for prebuilds* fix: improve schedule validation* fix: allow DOM and Month fields* docs: improve documentation for timezone field* docs: make gen* Update provider/workspace_preset.goCo-authored-by: Danny Kopping <dannykopping@gmail.com>* docs: improve doc comments* fix: tests* refactor: rename autoscaling to scheduling* docs: make gen* refactor: minor refactor after renaming* Update provider/helpers/schedule_validation.goCo-authored-by: Danny Kopping <dannykopping@gmail.com>* Update provider/helpers/schedule_validation.goCo-authored-by: Danny Kopping <dannykopping@gmail.com>* refactor: improve docs* refactor: improve docs* test: improve test coverage* test: improve test coverage* refactor: check for a specific error in tests* refactor: check for a specific error in tests---------Co-authored-by: Danny Kopping <dannykopping@gmail.com>
1 parenteee4ed5 commit6570400

File tree

7 files changed

+1246
-6
lines changed

7 files changed

+1246
-6
lines changed

‎docs/data-sources/workspace_preset.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,30 @@ Required:
5555
Optional:
5656

5757
-`expiration_policy` (Block Set, Max: 1) Configuration block that defines TTL (time-to-live) behavior for prebuilds. Use this to automatically invalidate and delete prebuilds after a certain period, ensuring they stay up-to-date. (see[below for nested schema](#nestedblock--prebuilds--expiration_policy))
58+
-`scheduling` (Block List, Max: 1) Configuration block that defines scheduling behavior for prebuilds. Use this to automatically adjust the number of prebuild instances based on a schedule. (see[below for nested schema](#nestedblock--prebuilds--scheduling))
5859

5960
<aid="nestedblock--prebuilds--expiration_policy"></a>
6061
###Nested Schema for`prebuilds.expiration_policy`
6162

6263
Required:
6364

6465
-`ttl` (Number) Time in seconds after which an unclaimed prebuild is considered expired and eligible for cleanup.
66+
67+
68+
<aid="nestedblock--prebuilds--scheduling"></a>
69+
###Nested Schema for`prebuilds.scheduling`
70+
71+
Required:
72+
73+
-`schedule` (Block List, Min: 1) One or more schedule blocks that define when to scale the number of prebuild instances. (see[below for nested schema](#nestedblock--prebuilds--scheduling--schedule))
74+
-`timezone` (String) The timezone to use for the prebuild schedules (e.g., "UTC", "America/New_York").
75+
Timezone must be a valid timezone in the IANA timezone database.
76+
Seehttps://en.wikipedia.org/wiki/List_of_tz_database_time_zones for a complete list of valid timezone identifiers andhttps://www.iana.org/time-zones for the official IANA timezone database.
77+
78+
<aid="nestedblock--prebuilds--scheduling--schedule"></a>
79+
###Nested Schema for`prebuilds.scheduling.schedule`
80+
81+
Required:
82+
83+
-`cron` (String) A cron expression that defines when this schedule should be active. The cron expression must be in the format "* HOUR DOM MONTH DAY-OF-WEEK" where HOUR is 0-23, DOM (day-of-month) is 1-31, MONTH is 1-12, and DAY-OF-WEEK is 0-6 (Sunday-Saturday). The minute field must be "*" to ensure the schedule covers entire hours rather than specific minute intervals.
84+
-`instances` (Number) The number of prebuild instances to maintain during this schedule period.

‎integration/integration_test.go

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,17 @@ func TestIntegration(t *testing.T) {
9090
// TODO (sasswart): the cli doesn't support presets yet.
9191
// once it does, the value for workspace_parameter.value
9292
// will be the preset value.
93-
"workspace_parameter.value":`param value`,
94-
"workspace_parameter.icon":`param icon`,
95-
"workspace_preset.name":`preset`,
96-
"workspace_preset.parameters.param":`preset param value`,
97-
"workspace_preset.prebuilds.instances":`1`,
98-
"workspace_preset.prebuilds.expiration_policy.ttl":`86400`,
93+
"workspace_parameter.value":`param value`,
94+
"workspace_parameter.icon":`param icon`,
95+
"workspace_preset.name":`preset`,
96+
"workspace_preset.parameters.param":`preset param value`,
97+
"workspace_preset.prebuilds.instances":`1`,
98+
"workspace_preset.prebuilds.expiration_policy.ttl":`86400`,
99+
"workspace_preset.prebuilds.scheduling.timezone":`UTC`,
100+
"workspace_preset.prebuilds.scheduling.schedule0.cron":`\* 8-18 \* \* 1-5`,
101+
"workspace_preset.prebuilds.scheduling.schedule0.instances":`3`,
102+
"workspace_preset.prebuilds.scheduling.schedule1.cron":`\* 8-14 \* \* 6`,
103+
"workspace_preset.prebuilds.scheduling.schedule1.instances":`1`,
99104
},
100105
},
101106
{

‎integration/test-data-source/main.tf

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,17 @@ data "coder_workspace_preset" "preset" {
3030
expiration_policy {
3131
ttl=86400
3232
}
33+
scheduling {
34+
timezone="UTC"
35+
schedule {
36+
cron="* 8-18 * * 1-5"
37+
instances=3
38+
}
39+
schedule {
40+
cron="* 8-14 * * 6"
41+
instances=1
42+
}
43+
}
3344
}
3445
}
3546

@@ -56,6 +67,11 @@ locals {
5667
"workspace_preset.parameters.param": data.coder_workspace_preset.preset.parameters.param,
5768
"workspace_preset.prebuilds.instances":tostring(one(data.coder_workspace_preset.preset.prebuilds).instances),
5869
"workspace_preset.prebuilds.expiration_policy.ttl":tostring(one(one(data.coder_workspace_preset.preset.prebuilds).expiration_policy).ttl),
70+
"workspace_preset.prebuilds.scheduling.timezone":tostring(one(one(data.coder_workspace_preset.preset.prebuilds).scheduling).timezone),
71+
"workspace_preset.prebuilds.scheduling.schedule0.cron":tostring(one(one(data.coder_workspace_preset.preset.prebuilds).scheduling).schedule[0].cron),
72+
"workspace_preset.prebuilds.scheduling.schedule0.instances":tostring(one(one(data.coder_workspace_preset.preset.prebuilds).scheduling).schedule[0].instances),
73+
"workspace_preset.prebuilds.scheduling.schedule1.cron":tostring(one(one(data.coder_workspace_preset.preset.prebuilds).scheduling).schedule[1].cron),
74+
"workspace_preset.prebuilds.scheduling.schedule1.instances":tostring(one(one(data.coder_workspace_preset.preset.prebuilds).scheduling).schedule[1].instances),
5975
}
6076
}
6177

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
package helpers
2+
3+
import (
4+
"strconv"
5+
"strings"
6+
7+
"golang.org/x/xerrors"
8+
)
9+
10+
// ValidateSchedules checks if any schedules overlap
11+
funcValidateSchedules(schedules []string)error {
12+
fori:=0;i<len(schedules);i++ {
13+
forj:=i+1;j<len(schedules);j++ {
14+
overlap,err:=SchedulesOverlap(schedules[i],schedules[j])
15+
iferr!=nil {
16+
returnxerrors.Errorf("invalid schedule: %w",err)
17+
}
18+
ifoverlap {
19+
returnxerrors.Errorf("schedules overlap: %s and %s",
20+
schedules[i],schedules[j])
21+
}
22+
}
23+
}
24+
returnnil
25+
}
26+
27+
// SchedulesOverlap checks if two schedules overlap by checking
28+
// all cron fields separately
29+
funcSchedulesOverlap(schedule1,schedule2string) (bool,error) {
30+
// Get cron fields
31+
fields1:=strings.Fields(schedule1)
32+
fields2:=strings.Fields(schedule2)
33+
34+
iflen(fields1)!=5 {
35+
returnfalse,xerrors.Errorf("schedule %q has %d fields, expected 5 fields (minute hour day-of-month month day-of-week)",schedule1,len(fields1))
36+
}
37+
iflen(fields2)!=5 {
38+
returnfalse,xerrors.Errorf("schedule %q has %d fields, expected 5 fields (minute hour day-of-month month day-of-week)",schedule2,len(fields2))
39+
}
40+
41+
// Check if months overlap
42+
monthsOverlap,err:=MonthsOverlap(fields1[3],fields2[3])
43+
iferr!=nil {
44+
returnfalse,xerrors.Errorf("invalid month range: %w",err)
45+
}
46+
if!monthsOverlap {
47+
returnfalse,nil
48+
}
49+
50+
// Check if days overlap (DOM OR DOW)
51+
daysOverlap,err:=DaysOverlap(fields1[2],fields1[4],fields2[2],fields2[4])
52+
iferr!=nil {
53+
returnfalse,xerrors.Errorf("invalid day range: %w",err)
54+
}
55+
if!daysOverlap {
56+
returnfalse,nil
57+
}
58+
59+
// Check if hours overlap
60+
hoursOverlap,err:=HoursOverlap(fields1[1],fields2[1])
61+
iferr!=nil {
62+
returnfalse,xerrors.Errorf("invalid hour range: %w",err)
63+
}
64+
65+
returnhoursOverlap,nil
66+
}
67+
68+
// MonthsOverlap checks if two month ranges overlap
69+
funcMonthsOverlap(months1,months2string) (bool,error) {
70+
returnCheckOverlap(months1,months2,12)
71+
}
72+
73+
// HoursOverlap checks if two hour ranges overlap
74+
funcHoursOverlap(hours1,hours2string) (bool,error) {
75+
returnCheckOverlap(hours1,hours2,23)
76+
}
77+
78+
// DomOverlap checks if two day-of-month ranges overlap
79+
funcDomOverlap(dom1,dom2string) (bool,error) {
80+
returnCheckOverlap(dom1,dom2,31)
81+
}
82+
83+
// DowOverlap checks if two day-of-week ranges overlap
84+
funcDowOverlap(dow1,dow2string) (bool,error) {
85+
returnCheckOverlap(dow1,dow2,6)
86+
}
87+
88+
// DaysOverlap checks if two day ranges overlap, considering both DOM and DOW.
89+
// Returns true if both DOM and DOW overlap, or if one is * and the other overlaps.
90+
funcDaysOverlap(dom1,dow1,dom2,dow2string) (bool,error) {
91+
// If either DOM is *, we only need to check DOW overlap
92+
ifdom1=="*"||dom2=="*" {
93+
returnDowOverlap(dow1,dow2)
94+
}
95+
96+
// If either DOW is *, we only need to check DOM overlap
97+
ifdow1=="*"||dow2=="*" {
98+
returnDomOverlap(dom1,dom2)
99+
}
100+
101+
// If both DOM and DOW are specified, we need to check both
102+
// because the schedule runs when either matches
103+
domOverlap,err:=DomOverlap(dom1,dom2)
104+
iferr!=nil {
105+
returnfalse,err
106+
}
107+
dowOverlap,err:=DowOverlap(dow1,dow2)
108+
iferr!=nil {
109+
returnfalse,err
110+
}
111+
112+
// If either DOM or DOW overlaps, the schedules overlap
113+
returndomOverlap||dowOverlap,nil
114+
}
115+
116+
// CheckOverlap is a function to check if two ranges overlap
117+
funcCheckOverlap(range1,range2string,maxValueint) (bool,error) {
118+
set1,err:=ParseRange(range1,maxValue)
119+
iferr!=nil {
120+
returnfalse,err
121+
}
122+
set2,err:=ParseRange(range2,maxValue)
123+
iferr!=nil {
124+
returnfalse,err
125+
}
126+
127+
forvalue:=rangeset1 {
128+
ifset2[value] {
129+
returntrue,nil
130+
}
131+
}
132+
returnfalse,nil
133+
}
134+
135+
// ParseRange converts a cron range to a set of integers
136+
// maxValue is the maximum allowed value (e.g., 23 for hours, 6 for DOW, 12 for months, 31 for DOM)
137+
funcParseRange(inputstring,maxValueint) (map[int]bool,error) {
138+
result:=make(map[int]bool)
139+
140+
// Handle "*" case
141+
ifinput=="*" {
142+
fori:=0;i<=maxValue;i++ {
143+
result[i]=true
144+
}
145+
returnresult,nil
146+
}
147+
148+
// Parse ranges like "1-3,5,7-9"
149+
parts:=strings.Split(input,",")
150+
for_,part:=rangeparts {
151+
ifstrings.Contains(part,"-") {
152+
// Handle range like "1-3"
153+
rangeParts:=strings.Split(part,"-")
154+
start,err:=strconv.Atoi(rangeParts[0])
155+
iferr!=nil {
156+
returnnil,xerrors.Errorf("invalid start value in range: %w",err)
157+
}
158+
end,err:=strconv.Atoi(rangeParts[1])
159+
iferr!=nil {
160+
returnnil,xerrors.Errorf("invalid end value in range: %w",err)
161+
}
162+
163+
// Validate range
164+
ifstart<0||end>maxValue||start>end {
165+
returnnil,xerrors.Errorf("invalid range %d-%d: values must be between 0 and %d",start,end,maxValue)
166+
}
167+
168+
fori:=start;i<=end;i++ {
169+
result[i]=true
170+
}
171+
}else {
172+
// Handle single value
173+
value,err:=strconv.Atoi(part)
174+
iferr!=nil {
175+
returnnil,xerrors.Errorf("invalid value: %w",err)
176+
}
177+
178+
// Validate value
179+
ifvalue<0||value>maxValue {
180+
returnnil,xerrors.Errorf("invalid value %d: must be between 0 and %d",value,maxValue)
181+
}
182+
183+
result[value]=true
184+
}
185+
}
186+
returnresult,nil
187+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp