Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 5c29d3c

Browse files
committedAug 1, 2024·
feat: add all settings for template resources
1 parent 3209c04 commit 5c29d3c

File tree

4 files changed

+391
-59
lines changed

4 files changed

+391
-59
lines changed
 

‎docs/resources/template.md

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,21 @@ A Coder template
2323
### Optional
2424

2525
- `acl` (Attributes) Access control list for the template. Requires an enterprise Coder deployment. If null, ACL policies will not be added or removed by Terraform. (see [below for nested schema](#nestedatt--acl))
26-
- `allow_user_auto_start` (Boolean)
27-
- `allow_user_auto_stop` (Boolean)
26+
- `activity_bump` (Number) The activity bump duration for all workspaces created from this template, in milliseconds. Defaults to one hour.
27+
- `allow_user_auto_start` (Boolean) Whether users can auto-start workspaces created from this template. Defaults to true.
28+
- `allow_user_auto_stop` (Boolean) Whether users can auto-start workspaces created from this template. Defaults to true.
29+
- `allow_user_cancel_workspace_jobs` (Boolean) Whether users can cancel in-progress workspace jobs using this template. Defaults to true.
30+
- `auto_start_requirement` (Set of String) List of days of the week in which autostart is allowed to happen, for all workspaces created from this template. Defaults to all days. If no days are specified, autostart is not allowed. Requires an enterprise Coder deployment.
31+
- `auto_stop_requirement` (Attributes) The auto-stop requirement for all workspaces created from this template. Requires an enterprise Coder deployment. (see [below for nested schema](#nestedatt--auto_stop_requirement))
32+
- `default_ttl` (Number) The default time-to-live for all workspaces created from this template, in milliseconds.
2833
- `description` (String) A description of the template.
2934
- `display_name` (String) The display name of the template. Defaults to the template name.
35+
- `failure_ttl` (Number) The max lifetime before Coder stops all resources for failed workspaces created from this template, in milliseconds.
3036
- `icon` (String) Relative path or external URL that specifes an icon to be displayed in the dashboard.
3137
- `organization_id` (String) The ID of the organization. Defaults to the provider's default organization
38+
- `require_active_version` (Boolean) Whether workspaces must be created from the active version of this template. Defaults to false.
39+
- `time_til_dormant` (Number) The max lifetime before Coder locks inactive workspaces created from this template, in milliseconds.
40+
- `time_til_dormant_autodelete` (Number) The max lifetime before Coder permanently deletes dormant workspaces created from this template.
3241

3342
### Read-Only
3443

@@ -97,3 +106,13 @@ Required:
97106

98107
- `id` (String)
99108
- `role` (String)
109+
110+
111+
112+
<a id="nestedatt--auto_stop_requirement"></a>
113+
### Nested Schema for `auto_stop_requirement`
114+
115+
Optional:
116+
117+
- `days_of_week` (Set of String) List of days of the week on which restarts are required. Restarts happen within the user's quiet hours (in their configured timezone). If no days are specified, restarts are not required.
118+
- `weeks` (Number) Weeks is the number of weeks between required restarts. Weeks are synced across all workspaces (and Coder deployments) using modulo math on a hardcoded epoch week of January 2nd, 2023 (the first Monday of 2023). Values of 0 or 1 indicate weekly restarts. Values of 2 indicate fortnightly restarts, etc.

‎internal/provider/template_resource.go

Lines changed: 278 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,19 @@ import (
1111
"github.com/coder/coder/v2/provisionersdk"
1212
"github.com/google/uuid"
1313
"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
14+
"github.com/hashicorp/terraform-plugin-framework-validators/setvalidator"
1415
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
1516
"github.com/hashicorp/terraform-plugin-framework/attr"
17+
"github.com/hashicorp/terraform-plugin-framework/diag"
1618
"github.com/hashicorp/terraform-plugin-framework/path"
1719
"github.com/hashicorp/terraform-plugin-framework/resource"
1820
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
1921
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
22+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
23+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/objectdefault"
2024
"github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier"
2125
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
26+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/setdefault"
2227
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
2328
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
2429
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
@@ -45,14 +50,24 @@ type TemplateResource struct {
4550
type TemplateResourceModel struct {
4651
ID UUID `tfsdk:"id"`
4752

48-
Name types.String `tfsdk:"name"`
49-
DisplayName types.String `tfsdk:"display_name"`
50-
Description types.String `tfsdk:"description"`
51-
OrganizationID UUID `tfsdk:"organization_id"`
52-
Icon types.String `tfsdk:"icon"`
53-
AllowUserAutoStart types.Bool `tfsdk:"allow_user_auto_start"`
54-
AllowUserAutoStop types.Bool `tfsdk:"allow_user_auto_stop"`
55-
53+
Name types.String `tfsdk:"name"`
54+
DisplayName types.String `tfsdk:"display_name"`
55+
Description types.String `tfsdk:"description"`
56+
OrganizationID UUID `tfsdk:"organization_id"`
57+
Icon types.String `tfsdk:"icon"`
58+
DefaultTTL types.Int64 `tfsdk:"default_ttl"`
59+
ActivityBump types.Int64 `tfsdk:"activity_bump"`
60+
AutostopRequirement types.Object `tfsdk:"auto_stop_requirement"`
61+
AutostartRequirement types.Set `tfsdk:"auto_start_requirement"`
62+
AllowUserCancelWorkspaceJobs types.Bool `tfsdk:"allow_user_cancel_workspace_jobs"`
63+
AllowUserAutostart types.Bool `tfsdk:"allow_user_auto_start"`
64+
AllowUserAutostop types.Bool `tfsdk:"allow_user_auto_stop"`
65+
FailureTTL types.Int64 `tfsdk:"failure_ttl"`
66+
TimeTilDormant types.Int64 `tfsdk:"time_til_dormant"`
67+
TimeTilDormantAutodelete types.Int64 `tfsdk:"time_til_dormant_autodelete"`
68+
RequireActiveVersion types.Bool `tfsdk:"require_active_version"`
69+
70+
// If null, we are not managing ACL via Terraform (such as for AGPL).
5671
ACL types.Object `tfsdk:"acl"`
5772
Versions Versions `tfsdk:"versions"`
5873
}
@@ -64,8 +79,17 @@ func (m TemplateResourceModel) EqualTemplateMetadata(other TemplateResourceModel
6479
m.Description.Equal(other.Description) &&
6580
m.OrganizationID.Equal(other.OrganizationID) &&
6681
m.Icon.Equal(other.Icon) &&
67-
m.AllowUserAutoStart.Equal(other.AllowUserAutoStart) &&
68-
m.AllowUserAutoStop.Equal(other.AllowUserAutoStop)
82+
m.DefaultTTL.Equal(other.DefaultTTL) &&
83+
m.ActivityBump.Equal(other.ActivityBump) &&
84+
m.AutostopRequirement.Equal(other.AutostopRequirement) &&
85+
m.AutostartRequirement.Equal(other.AutostartRequirement) &&
86+
m.AllowUserCancelWorkspaceJobs.Equal(other.AllowUserCancelWorkspaceJobs) &&
87+
m.AllowUserAutostart.Equal(other.AllowUserAutostart) &&
88+
m.AllowUserAutostop.Equal(other.AllowUserAutostop) &&
89+
m.FailureTTL.Equal(other.FailureTTL) &&
90+
m.TimeTilDormant.Equal(other.TimeTilDormant) &&
91+
m.TimeTilDormantAutodelete.Equal(other.TimeTilDormantAutodelete) &&
92+
m.RequireActiveVersion.Equal(other.RequireActiveVersion)
6993
}
7094

7195
type TemplateVersion struct {
@@ -147,6 +171,16 @@ var permissionTypeAttr = basetypes.SetType{ElemType: types.ObjectType{
147171
},
148172
}}
149173

174+
type AutostopRequirement struct {
175+
DaysOfWeek []string `tfsdk:"days_of_week"`
176+
Weeks int64 `tfsdk:"weeks"`
177+
}
178+
179+
var autostopRequirementTypeAttr = map[string]attr.Type{
180+
"days_of_week": basetypes.SetType{ElemType: basetypes.StringType{}},
181+
"weeks": basetypes.Int64Type{},
182+
}
183+
150184
func (r *TemplateResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
151185
resp.TypeName = req.ProviderTypeName + "_template"
152186
}
@@ -197,15 +231,92 @@ func (r *TemplateResource) Schema(ctx context.Context, req resource.SchemaReques
197231
Computed: true,
198232
Default: stringdefault.StaticString(""),
199233
},
234+
"default_ttl": schema.Int64Attribute{
235+
MarkdownDescription: "The default time-to-live for all workspaces created from this template, in milliseconds.",
236+
Optional: true,
237+
Computed: true,
238+
Default: int64default.StaticInt64(0),
239+
},
240+
"activity_bump": schema.Int64Attribute{
241+
MarkdownDescription: "The activity bump duration for all workspaces created from this template, in milliseconds. Defaults to one hour.",
242+
Optional: true,
243+
Computed: true,
244+
Default: int64default.StaticInt64(3600000),
245+
},
246+
"auto_stop_requirement": schema.SingleNestedAttribute{
247+
MarkdownDescription: "The auto-stop requirement for all workspaces created from this template. Requires an enterprise Coder deployment.",
248+
Optional: true,
249+
Computed: true,
250+
Attributes: map[string]schema.Attribute{
251+
"days_of_week": schema.SetAttribute{
252+
MarkdownDescription: "List of days of the week on which restarts are required. Restarts happen within the user's quiet hours (in their configured timezone). If no days are specified, restarts are not required.",
253+
Optional: true,
254+
Computed: true,
255+
ElementType: types.StringType,
256+
Validators: []validator.Set{weekValidator},
257+
Default: setdefault.StaticValue(types.SetValueMust(types.StringType, []attr.Value{})),
258+
},
259+
"weeks": schema.Int64Attribute{
260+
MarkdownDescription: "Weeks is the number of weeks between required restarts. Weeks are synced across all workspaces (and Coder deployments) using modulo math on a hardcoded epoch week of January 2nd, 2023 (the first Monday of 2023). Values of 0 or 1 indicate weekly restarts. Values of 2 indicate fortnightly restarts, etc.",
261+
Optional: true,
262+
Computed: true,
263+
Default: int64default.StaticInt64(1),
264+
},
265+
},
266+
Default: objectdefault.StaticValue(types.ObjectValueMust(autostopRequirementTypeAttr, map[string]attr.Value{
267+
"days_of_week": types.SetValueMust(types.StringType, []attr.Value{}),
268+
"weeks": types.Int64Value(1),
269+
})),
270+
},
271+
"auto_start_requirement": schema.SetAttribute{
272+
MarkdownDescription: "List of days of the week in which autostart is allowed to happen, for all workspaces created from this template. Defaults to all days. If no days are specified, autostart is not allowed. Requires an enterprise Coder deployment.",
273+
Optional: true,
274+
Computed: true,
275+
ElementType: types.StringType,
276+
Validators: []validator.Set{weekValidator},
277+
Default: setdefault.StaticValue(types.SetValueMust(types.StringType, []attr.Value{types.StringValue("monday"), types.StringValue("tuesday"), types.StringValue("wednesday"), types.StringValue("thursday"), types.StringValue("friday"), types.StringValue("saturday"), types.StringValue("sunday")})),
278+
},
279+
"allow_user_cancel_workspace_jobs": schema.BoolAttribute{
280+
MarkdownDescription: "Whether users can cancel in-progress workspace jobs using this template. Defaults to true.",
281+
Optional: true,
282+
Computed: true,
283+
Default: booldefault.StaticBool(true),
284+
},
200285
"allow_user_auto_start": schema.BoolAttribute{
201-
Optional: true,
202-
Computed: true,
203-
Default: booldefault.StaticBool(true),
286+
MarkdownDescription: "Whether users can auto-start workspaces created from this template. Defaults to true.",
287+
Optional: true,
288+
Computed: true,
289+
Default: booldefault.StaticBool(true),
204290
},
205291
"allow_user_auto_stop": schema.BoolAttribute{
206-
Optional: true,
207-
Computed: true,
208-
Default: booldefault.StaticBool(true),
292+
MarkdownDescription: "Whether users can auto-start workspaces created from this template. Defaults to true.",
293+
Optional: true,
294+
Computed: true,
295+
Default: booldefault.StaticBool(true),
296+
},
297+
"failure_ttl": schema.Int64Attribute{
298+
MarkdownDescription: "The max lifetime before Coder stops all resources for failed workspaces created from this template, in milliseconds.",
299+
Optional: true,
300+
Computed: true,
301+
Default: int64default.StaticInt64(0),
302+
},
303+
"time_til_dormant": schema.Int64Attribute{
304+
MarkdownDescription: "The max lifetime before Coder locks inactive workspaces created from this template, in milliseconds.",
305+
Optional: true,
306+
Computed: true,
307+
Default: int64default.StaticInt64(0),
308+
},
309+
"time_til_dormant_autodelete": schema.Int64Attribute{
310+
MarkdownDescription: "The max lifetime before Coder permanently deletes dormant workspaces created from this template.",
311+
Optional: true,
312+
Computed: true,
313+
Default: int64default.StaticInt64(0),
314+
},
315+
"require_active_version": schema.BoolAttribute{
316+
MarkdownDescription: "Whether workspaces must be created from the active version of this template. Defaults to false.",
317+
Optional: true,
318+
Computed: true,
319+
Default: booldefault.StaticBool(false),
209320
},
210321
"acl": schema.SingleNestedAttribute{
211322
MarkdownDescription: "Access control list for the template. Requires an enterprise Coder deployment. If null, ACL policies will not be added or removed by Terraform.",
@@ -327,16 +438,11 @@ func (r *TemplateResource) Create(ctx context.Context, req resource.CreateReques
327438
}
328439
if idx == 0 {
329440
tflog.Trace(ctx, "creating template")
330-
templateResp, err = client.CreateTemplate(ctx, orgID, codersdk.CreateTemplateRequest{
331-
Name: data.Name.ValueString(),
332-
DisplayName: data.DisplayName.ValueString(),
333-
Description: data.Description.ValueString(),
334-
VersionID: versionResp.ID,
335-
AllowUserAutostart: data.AllowUserAutoStart.ValueBoolPointer(),
336-
AllowUserAutostop: data.AllowUserAutoStop.ValueBoolPointer(),
337-
Icon: data.Icon.ValueString(),
338-
DisableEveryoneGroupAccess: true,
339-
})
441+
createReq := data.toCreateRequest(ctx, resp, versionResp.ID)
442+
if resp.Diagnostics.HasError() {
443+
return
444+
}
445+
templateResp, err = client.CreateTemplate(ctx, orgID, *createReq)
340446
if err != nil {
341447
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to create template: %s", err))
342448
return
@@ -345,6 +451,13 @@ func (r *TemplateResource) Create(ctx context.Context, req resource.CreateReques
345451
"id": templateResp.ID,
346452
})
347453

454+
// Read the response into the state to set computed fields
455+
diag := data.ReadResponse(ctx, &templateResp)
456+
if diag.HasError() {
457+
resp.Diagnostics.Append(diag...)
458+
return
459+
}
460+
348461
if !data.ACL.IsNull() {
349462
tflog.Trace(ctx, "updating template ACL")
350463
var acl ACL
@@ -382,7 +495,7 @@ func (r *TemplateResource) Create(ctx context.Context, req resource.CreateReques
382495
data.ID = UUIDValue(templateResp.ID)
383496
data.DisplayName = types.StringValue(templateResp.DisplayName)
384497

385-
// Save data into Terraform state
498+
// Save data into Terraform sutate
386499
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
387500
}
388501

@@ -405,13 +518,11 @@ func (r *TemplateResource) Read(ctx context.Context, req resource.ReadRequest, r
405518
return
406519
}
407520

408-
data.Name = types.StringValue(template.Name)
409-
data.DisplayName = types.StringValue(template.DisplayName)
410-
data.Description = types.StringValue(template.Description)
411-
data.OrganizationID = UUIDValue(template.OrganizationID)
412-
data.Icon = types.StringValue(template.Icon)
413-
data.AllowUserAutoStart = types.BoolValue(template.AllowUserAutostart)
414-
data.AllowUserAutoStop = types.BoolValue(template.AllowUserAutostop)
521+
diag := data.ReadResponse(ctx, &template)
522+
if diag.HasError() {
523+
resp.Diagnostics.Append(diag...)
524+
return
525+
}
415526

416527
if !data.ACL.IsNull() {
417528
tflog.Trace(ctx, "reading template ACL")
@@ -481,26 +592,26 @@ func (r *TemplateResource) Update(ctx context.Context, req resource.UpdateReques
481592

482593
client := r.data.Client
483594

484-
if !planState.EqualTemplateMetadata(curState) {
595+
templateMetadataChanged := !planState.EqualTemplateMetadata(curState)
596+
// This is required, as the API will reject no-diff updates.
597+
if templateMetadataChanged {
485598
tflog.Trace(ctx, "change in template metadata detected, updating.")
486-
_, err := client.UpdateTemplateMeta(ctx, templateID, codersdk.UpdateTemplateMeta{
487-
Name: planState.Name.ValueString(),
488-
DisplayName: planState.DisplayName.ValueString(),
489-
Description: planState.Description.ValueString(),
490-
AllowUserAutostart: planState.AllowUserAutoStart.ValueBool(),
491-
AllowUserAutostop: planState.AllowUserAutoStop.ValueBool(),
492-
Icon: planState.Icon.ValueString(),
493-
DisableEveryoneGroupAccess: true,
494-
})
599+
updateReq := planState.toUpdateRequest(ctx, resp)
600+
if resp.Diagnostics.HasError() {
601+
return
602+
}
603+
_, err := client.UpdateTemplateMeta(ctx, templateID, *updateReq)
495604
if err != nil {
496605
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to update template metadata: %s", err))
497606
return
498607
}
608+
499609
tflog.Trace(ctx, "successfully updated template metadata")
500610
}
501611

502-
// If there's a change, and we're still managing ACL
503-
if !planState.ACL.Equal(curState.ACL) && !planState.ACL.IsNull() {
612+
// Since the everyone group always gets deleted by `DisableEveryoneGroupAccess`, we need to run this even if there
613+
// were no ACL changes but the template metadata was updated.
614+
if !planState.ACL.IsNull() && (!curState.ACL.Equal(planState.ACL) || templateMetadataChanged) {
504615
var acl ACL
505616
resp.Diagnostics.Append(planState.ACL.As(ctx, &acl, basetypes.ObjectAsOptions{})...)
506617
if resp.Diagnostics.HasError() {
@@ -675,6 +786,10 @@ func NewDirectoryHashPlanModifier() planmodifier.Object {
675786

676787
var _ planmodifier.Object = &directoryHashPlanModifier{}
677788

789+
var weekValidator = setvalidator.ValueStringsAre(
790+
stringvalidator.OneOf("monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"),
791+
)
792+
678793
func uploadDirectory(ctx context.Context, client *codersdk.Client, logger slog.Logger, directory string) (*codersdk.UploadResponse, error) {
679794
pipeReader, pipeWriter := io.Pipe()
680795
go func() {
@@ -821,3 +936,120 @@ func convertResponseToACL(acl codersdk.TemplateACL) ACL {
821936
GroupPermissions: groupPerms,
822937
}
823938
}
939+
940+
func (r *TemplateResourceModel) ReadResponse(ctx context.Context, template *codersdk.Template) diag.Diagnostics {
941+
r.Name = types.StringValue(template.Name)
942+
r.DisplayName = types.StringValue(template.DisplayName)
943+
r.Description = types.StringValue(template.Description)
944+
r.OrganizationID = UUIDValue(template.OrganizationID)
945+
r.Icon = types.StringValue(template.Icon)
946+
r.DefaultTTL = types.Int64Value(template.DefaultTTLMillis)
947+
r.ActivityBump = types.Int64Value(template.ActivityBumpMillis)
948+
asrObj, diag := types.ObjectValueFrom(ctx, autostopRequirementTypeAttr, AutostopRequirement{
949+
DaysOfWeek: template.AutostopRequirement.DaysOfWeek,
950+
Weeks: template.AutostopRequirement.Weeks,
951+
})
952+
if diag.HasError() {
953+
return diag
954+
}
955+
r.AutostopRequirement = asrObj
956+
autoStartDays := make([]attr.Value, 0, len(template.AutostartRequirement.DaysOfWeek))
957+
for _, day := range template.AutostartRequirement.DaysOfWeek {
958+
autoStartDays = append(autoStartDays, types.StringValue(day))
959+
}
960+
r.AutostartRequirement = types.SetValueMust(types.StringType, autoStartDays)
961+
r.AllowUserCancelWorkspaceJobs = types.BoolValue(template.AllowUserCancelWorkspaceJobs)
962+
r.AllowUserAutostart = types.BoolValue(template.AllowUserAutostart)
963+
r.AllowUserAutostop = types.BoolValue(template.AllowUserAutostop)
964+
r.FailureTTL = types.Int64Value(template.FailureTTLMillis)
965+
r.TimeTilDormant = types.Int64Value(template.TimeTilDormantMillis)
966+
r.TimeTilDormantAutodelete = types.Int64Value(template.TimeTilDormantAutoDeleteMillis)
967+
r.RequireActiveVersion = types.BoolValue(template.RequireActiveVersion)
968+
return nil
969+
}
970+
971+
func (r *TemplateResourceModel) toUpdateRequest(ctx context.Context, resp *resource.UpdateResponse) *codersdk.UpdateTemplateMeta {
972+
var days []string
973+
resp.Diagnostics.Append(
974+
r.AutostartRequirement.ElementsAs(ctx, &days, false)...,
975+
)
976+
if resp.Diagnostics.HasError() {
977+
return nil
978+
}
979+
autoStart := &codersdk.TemplateAutostartRequirement{
980+
DaysOfWeek: days,
981+
}
982+
var reqs AutostopRequirement
983+
resp.Diagnostics.Append(
984+
r.AutostopRequirement.As(ctx, &reqs, basetypes.ObjectAsOptions{})...,
985+
)
986+
if resp.Diagnostics.HasError() {
987+
return nil
988+
}
989+
autoStop := &codersdk.TemplateAutostopRequirement{
990+
DaysOfWeek: reqs.DaysOfWeek,
991+
Weeks: reqs.Weeks,
992+
}
993+
return &codersdk.UpdateTemplateMeta{
994+
Name: r.Name.ValueString(),
995+
DisplayName: r.DisplayName.ValueString(),
996+
Description: r.Description.ValueString(),
997+
Icon: r.Icon.ValueString(),
998+
DefaultTTLMillis: r.DefaultTTL.ValueInt64(),
999+
ActivityBumpMillis: r.ActivityBump.ValueInt64(),
1000+
AutostopRequirement: autoStop,
1001+
AutostartRequirement: autoStart,
1002+
AllowUserCancelWorkspaceJobs: r.AllowUserCancelWorkspaceJobs.ValueBool(),
1003+
AllowUserAutostart: r.AllowUserAutostart.ValueBool(),
1004+
AllowUserAutostop: r.AllowUserAutostop.ValueBool(),
1005+
FailureTTLMillis: r.FailureTTL.ValueInt64(),
1006+
TimeTilDormantMillis: r.TimeTilDormant.ValueInt64(),
1007+
TimeTilDormantAutoDeleteMillis: r.TimeTilDormantAutodelete.ValueInt64(),
1008+
RequireActiveVersion: r.RequireActiveVersion.ValueBool(),
1009+
// If we're managing ACL, we want to delete the everyone group
1010+
DisableEveryoneGroupAccess: !r.ACL.IsNull(),
1011+
}
1012+
}
1013+
1014+
func (r *TemplateResourceModel) toCreateRequest(ctx context.Context, resp *resource.CreateResponse, versionID uuid.UUID) *codersdk.CreateTemplateRequest {
1015+
var days []string
1016+
resp.Diagnostics.Append(
1017+
r.AutostartRequirement.ElementsAs(ctx, &days, false)...,
1018+
)
1019+
if resp.Diagnostics.HasError() {
1020+
return nil
1021+
}
1022+
autoStart := &codersdk.TemplateAutostartRequirement{
1023+
DaysOfWeek: days,
1024+
}
1025+
var reqs AutostopRequirement
1026+
resp.Diagnostics.Append(
1027+
r.AutostopRequirement.As(ctx, &reqs, basetypes.ObjectAsOptions{})...,
1028+
)
1029+
if resp.Diagnostics.HasError() {
1030+
return nil
1031+
}
1032+
autoStop := &codersdk.TemplateAutostopRequirement{
1033+
DaysOfWeek: reqs.DaysOfWeek,
1034+
Weeks: reqs.Weeks,
1035+
}
1036+
return &codersdk.CreateTemplateRequest{
1037+
Name: r.Name.ValueString(),
1038+
DisplayName: r.DisplayName.ValueString(),
1039+
Description: r.Description.ValueString(),
1040+
Icon: r.Icon.ValueString(),
1041+
VersionID: versionID,
1042+
DefaultTTLMillis: r.DefaultTTL.ValueInt64Pointer(),
1043+
ActivityBumpMillis: r.ActivityBump.ValueInt64Pointer(),
1044+
AutostopRequirement: autoStop,
1045+
AutostartRequirement: autoStart,
1046+
AllowUserCancelWorkspaceJobs: r.AllowUserCancelWorkspaceJobs.ValueBoolPointer(),
1047+
AllowUserAutostart: r.AllowUserAutostart.ValueBoolPointer(),
1048+
AllowUserAutostop: r.AllowUserAutostop.ValueBoolPointer(),
1049+
FailureTTLMillis: r.FailureTTL.ValueInt64Pointer(),
1050+
TimeTilDormantMillis: r.TimeTilDormant.ValueInt64Pointer(),
1051+
TimeTilDormantAutoDeleteMillis: r.TimeTilDormantAutodelete.ValueInt64Pointer(),
1052+
RequireActiveVersion: r.RequireActiveVersion.ValueBool(),
1053+
DisableEveryoneGroupAccess: !r.ACL.IsNull(),
1054+
}
1055+
}

‎internal/provider/template_resource_test.go

Lines changed: 87 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ func TestAccTemplateResource(t *testing.T) {
5454
cfg2 := cfg1
5555
cfg2.Versions = slices.Clone(cfg2.Versions)
5656
cfg2.Name = PtrTo("example-template-new")
57+
cfg2.AllowUserAutostart = PtrTo(false)
5758
cfg2.Versions[0].Directory = PtrTo("../../integration/template-test/example-template-2/")
5859
cfg2.Versions[0].Name = PtrTo("new")
5960
cfg2.ACL.UserACL = []testAccTemplateKeyValueConfig{
@@ -62,6 +63,10 @@ func TestAccTemplateResource(t *testing.T) {
6263
Value: PtrTo("admin"),
6364
},
6465
}
66+
cfg2.AutostopRequirement = testAccAutostopRequirementConfig{
67+
DaysOfWeek: PtrTo([]string{"monday", "tuesday"}),
68+
Weeks: PtrTo(int64(2)),
69+
}
6570

6671
cfg3 := cfg2
6772
cfg3.Versions = slices.Clone(cfg3.Versions)
@@ -104,6 +109,19 @@ func TestAccTemplateResource(t *testing.T) {
104109
resource.TestCheckResourceAttr("coderd_template.test", "display_name", "example-template"),
105110
resource.TestCheckResourceAttr("coderd_template.test", "description", ""),
106111
resource.TestCheckResourceAttr("coderd_template.test", "organization_id", firstUser.OrganizationIDs[0].String()),
112+
resource.TestCheckResourceAttr("coderd_template.test", "icon", ""),
113+
resource.TestCheckResourceAttr("coderd_template.test", "default_ttl", "0"),
114+
resource.TestCheckResourceAttr("coderd_template.test", "activity_bump", "3600000"),
115+
resource.TestCheckResourceAttr("coderd_template.test", "auto_stop_requirement.days_of_week.#", "0"),
116+
resource.TestCheckResourceAttr("coderd_template.test", "auto_stop_requirement.weeks", "1"),
117+
resource.TestCheckResourceAttr("coderd_template.test", "auto_start_requirement.#", "7"),
118+
resource.TestCheckResourceAttr("coderd_template.test", "allow_user_cancel_workspace_jobs", "true"),
119+
resource.TestCheckResourceAttr("coderd_template.test", "allow_user_auto_start", "true"),
120+
resource.TestCheckResourceAttr("coderd_template.test", "allow_user_auto_stop", "true"),
121+
resource.TestCheckResourceAttr("coderd_template.test", "failure_ttl", "0"),
122+
resource.TestCheckResourceAttr("coderd_template.test", "time_til_dormant", "0"),
123+
resource.TestCheckResourceAttr("coderd_template.test", "time_til_dormant_autodelete", "0"),
124+
resource.TestCheckResourceAttr("coderd_template.test", "require_active_version", "false"),
107125
resource.TestMatchTypeSetElemNestedAttrs("coderd_template.test", "versions.*", map[string]*regexp.Regexp{
108126
"name": regexp.MustCompile("main"),
109127
"id": regexp.MustCompile(".*"),
@@ -127,6 +145,9 @@ func TestAccTemplateResource(t *testing.T) {
127145
Check: resource.ComposeAggregateTestCheckFunc(
128146
resource.TestCheckResourceAttrSet("coderd_template.test", "id"),
129147
resource.TestCheckResourceAttr("coderd_template.test", "name", "example-template-new"),
148+
resource.TestCheckResourceAttr("coderd_template.test", "allow_user_auto_start", "false"),
149+
resource.TestCheckResourceAttr("coderd_template.test", "auto_stop_requirement.days_of_week.#", "2"),
150+
resource.TestCheckResourceAttr("coderd_template.test", "auto_stop_requirement.weeks", "2"),
130151
resource.TestMatchTypeSetElemNestedAttrs("coderd_template.test", "versions.*", map[string]*regexp.Regexp{
131152
"name": regexp.MustCompile("new"),
132153
}),
@@ -201,12 +222,25 @@ type testAccTemplateResourceConfig struct {
201222
URL string
202223
Token string
203224

204-
Name *string
205-
DisplayName *string
206-
Description *string
207-
OrganizationID *string
208-
Versions []testAccTemplateVersionConfig
209-
ACL testAccTemplateACLConfig
225+
Name *string
226+
DisplayName *string
227+
Description *string
228+
OrganizationID *string
229+
Icon *string
230+
DefaultTTL *int64
231+
ActivityBump *int64
232+
AutostopRequirement testAccAutostopRequirementConfig
233+
AutostartRequirement *[]string
234+
AllowUserCancelWorkspaceJobs *bool
235+
AllowUserAutostart *bool
236+
AllowUserAutostop *bool
237+
FailureTTL *int64
238+
TimeTilDormant *int64
239+
TimeTilDormantAutodelete *int64
240+
RequireActiveVersion *bool
241+
242+
Versions []testAccTemplateVersionConfig
243+
ACL testAccTemplateACLConfig
210244
}
211245

212246
type testAccTemplateACLConfig struct {
@@ -216,10 +250,10 @@ type testAccTemplateACLConfig struct {
216250
}
217251

218252
func (c testAccTemplateACLConfig) String(t *testing.T) string {
253+
t.Helper()
219254
if c.null == true {
220255
return "null"
221256
}
222-
t.Helper()
223257
tpl := `{
224258
groups = [
225259
{{- range .GroupACL}}
@@ -254,6 +288,36 @@ func (c testAccTemplateACLConfig) String(t *testing.T) string {
254288
return buf.String()
255289
}
256290

291+
type testAccAutostopRequirementConfig struct {
292+
null bool
293+
DaysOfWeek *[]string
294+
Weeks *int64
295+
}
296+
297+
func (c testAccAutostopRequirementConfig) String(t *testing.T) string {
298+
t.Helper()
299+
if c.null == true {
300+
return "null"
301+
}
302+
tpl := `{
303+
days_of_week = {{orNull .DaysOfWeek}}
304+
weeks = {{orNull .Weeks}}
305+
}
306+
`
307+
funcMap := template.FuncMap{
308+
"orNull": PrintOrNull,
309+
}
310+
311+
buf := strings.Builder{}
312+
tmpl, err := template.New("test").Funcs(funcMap).Parse(tpl)
313+
require.NoError(t, err)
314+
315+
err = tmpl.Execute(&buf, c)
316+
require.NoError(t, err)
317+
318+
return buf.String()
319+
}
320+
257321
func (c testAccTemplateResourceConfig) String(t *testing.T) string {
258322
t.Helper()
259323
tpl := `
@@ -263,10 +327,22 @@ provider coderd {
263327
}
264328
265329
resource "coderd_template" "test" {
266-
name = {{orNull .Name}}
267-
display_name = {{orNull .DisplayName}}
268-
description = {{orNull .Description}}
269-
organization_id = {{orNull .OrganizationID}}
330+
name = {{orNull .Name}}
331+
display_name = {{orNull .DisplayName}}
332+
description = {{orNull .Description}}
333+
organization_id = {{orNull .OrganizationID}}
334+
icon = {{orNull .Icon}}
335+
default_ttl = {{orNull .DefaultTTL}}
336+
activity_bump = {{orNull .ActivityBump}}
337+
auto_stop_requirement = ` + c.AutostopRequirement.String(t) + `
338+
auto_start_requirement = {{orNull .AutostartRequirement}}
339+
allow_user_cancel_workspace_jobs = {{orNull .AllowUserCancelWorkspaceJobs}}
340+
allow_user_auto_start = {{orNull .AllowUserAutostart}}
341+
allow_user_auto_stop = {{orNull .AllowUserAutostop}}
342+
failure_ttl = {{orNull .FailureTTL}}
343+
time_til_dormant = {{orNull .TimeTilDormant}}
344+
time_til_dormant_autodelete = {{orNull .TimeTilDormantAutodelete}}
345+
require_active_version = {{orNull .RequireActiveVersion}}
270346
271347
acl = ` + c.ACL.String(t) + `
272348

‎internal/provider/util.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ func PrintOrNull(v any) string {
2424
return "null"
2525
}
2626
return fmt.Sprintf("%d", *value)
27+
case *int64:
28+
if value == nil {
29+
return "null"
30+
}
31+
return fmt.Sprintf("%d", *value)
2732
case *string:
2833
if value == nil {
2934
return "null"

0 commit comments

Comments
 (0)
Please sign in to comment.