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 c0950ec

Browse files
committedAug 2, 2024·
feat: add all settings for template resources
1 parent 557da95 commit c0950ec

File tree

4 files changed

+403
-59
lines changed

4 files changed

+403
-59
lines changed
 

‎docs/resources/template.md

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,22 @@ 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_ms` (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_permitted_days_of_week` (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_ms` (Number) The default time-to-live for all workspaces created from this template, in milliseconds.
33+
- `deprecation_message` (String) If set, the template will be marked as deprecated and users will be blocked from creating new workspaces from it.
2834
- `description` (String) A description of the template.
2935
- `display_name` (String) The display name of the template. Defaults to the template name.
36+
- `failure_ttl_ms` (Number) The max lifetime before Coder stops all resources for failed workspaces created from this template, in milliseconds.
3037
- `icon` (String) Relative path or external URL that specifes an icon to be displayed in the dashboard.
3138
- `organization_id` (String) The ID of the organization. Defaults to the provider's default organization
39+
- `require_active_version` (Boolean) Whether workspaces must be created from the active version of this template. Defaults to false.
40+
- `time_til_dormant_autodelete_ms` (Number) The max lifetime before Coder permanently deletes dormant workspaces created from this template.
41+
- `time_til_dormant_ms` (Number) The max lifetime before Coder locks inactive workspaces created from this template, in milliseconds.
3242

3343
### Read-Only
3444

@@ -97,3 +107,13 @@ Required:
97107

98108
- `id` (String)
99109
- `role` (String)
110+
111+
112+
113+
<a id="nestedatt--auto_stop_requirement"></a>
114+
### Nested Schema for `auto_stop_requirement`
115+
116+
Optional:
117+
118+
- `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.
119+
- `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: 287 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,25 @@ 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+
DefaultTTLMillis types.Int64 `tfsdk:"default_ttl_ms"`
59+
ActivityBumpMillis types.Int64 `tfsdk:"activity_bump_ms"`
60+
AutostopRequirement types.Object `tfsdk:"auto_stop_requirement"`
61+
AutostartPermittedDaysOfWeek types.Set `tfsdk:"auto_start_permitted_days_of_week"`
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+
FailureTTLMillis types.Int64 `tfsdk:"failure_ttl_ms"`
66+
TimeTilDormantMillis types.Int64 `tfsdk:"time_til_dormant_ms"`
67+
TimeTilDormantAutoDeleteMillis types.Int64 `tfsdk:"time_til_dormant_autodelete_ms"`
68+
RequireActiveVersion types.Bool `tfsdk:"require_active_version"`
69+
DeprecationMessage types.String `tfsdk:"deprecation_message"`
70+
71+
// If null, we are not managing ACL via Terraform (such as for AGPL).
5672
ACL types.Object `tfsdk:"acl"`
5773
Versions Versions `tfsdk:"versions"`
5874
}
@@ -64,8 +80,17 @@ func (m TemplateResourceModel) EqualTemplateMetadata(other TemplateResourceModel
6480
m.Description.Equal(other.Description) &&
6581
m.OrganizationID.Equal(other.OrganizationID) &&
6682
m.Icon.Equal(other.Icon) &&
67-
m.AllowUserAutoStart.Equal(other.AllowUserAutoStart) &&
68-
m.AllowUserAutoStop.Equal(other.AllowUserAutoStop)
83+
m.DefaultTTLMillis.Equal(other.DefaultTTLMillis) &&
84+
m.ActivityBumpMillis.Equal(other.ActivityBumpMillis) &&
85+
m.AutostopRequirement.Equal(other.AutostopRequirement) &&
86+
m.AutostartPermittedDaysOfWeek.Equal(other.AutostartPermittedDaysOfWeek) &&
87+
m.AllowUserCancelWorkspaceJobs.Equal(other.AllowUserCancelWorkspaceJobs) &&
88+
m.AllowUserAutostart.Equal(other.AllowUserAutostart) &&
89+
m.AllowUserAutostop.Equal(other.AllowUserAutostop) &&
90+
m.FailureTTLMillis.Equal(other.FailureTTLMillis) &&
91+
m.TimeTilDormantMillis.Equal(other.TimeTilDormantMillis) &&
92+
m.TimeTilDormantAutoDeleteMillis.Equal(other.TimeTilDormantAutoDeleteMillis) &&
93+
m.RequireActiveVersion.Equal(other.RequireActiveVersion)
6994
}
7095

7196
type TemplateVersion struct {
@@ -147,6 +172,16 @@ var permissionTypeAttr = basetypes.SetType{ElemType: types.ObjectType{
147172
},
148173
}}
149174

175+
type AutostopRequirement struct {
176+
DaysOfWeek []string `tfsdk:"days_of_week"`
177+
Weeks int64 `tfsdk:"weeks"`
178+
}
179+
180+
var autostopRequirementTypeAttr = map[string]attr.Type{
181+
"days_of_week": basetypes.SetType{ElemType: basetypes.StringType{}},
182+
"weeks": basetypes.Int64Type{},
183+
}
184+
150185
func (r *TemplateResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
151186
resp.TypeName = req.ProviderTypeName + "_template"
152187
}
@@ -197,15 +232,98 @@ func (r *TemplateResource) Schema(ctx context.Context, req resource.SchemaReques
197232
Computed: true,
198233
Default: stringdefault.StaticString(""),
199234
},
235+
"default_ttl_ms": schema.Int64Attribute{
236+
MarkdownDescription: "The default time-to-live for all workspaces created from this template, in milliseconds.",
237+
Optional: true,
238+
Computed: true,
239+
Default: int64default.StaticInt64(0),
240+
},
241+
"activity_bump_ms": schema.Int64Attribute{
242+
MarkdownDescription: "The activity bump duration for all workspaces created from this template, in milliseconds. Defaults to one hour.",
243+
Optional: true,
244+
Computed: true,
245+
Default: int64default.StaticInt64(3600000),
246+
},
247+
"auto_stop_requirement": schema.SingleNestedAttribute{
248+
MarkdownDescription: "The auto-stop requirement for all workspaces created from this template. Requires an enterprise Coder deployment.",
249+
Optional: true,
250+
Computed: true,
251+
Attributes: map[string]schema.Attribute{
252+
"days_of_week": schema.SetAttribute{
253+
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.",
254+
Optional: true,
255+
Computed: true,
256+
ElementType: types.StringType,
257+
Validators: []validator.Set{weekValidator},
258+
Default: setdefault.StaticValue(types.SetValueMust(types.StringType, []attr.Value{})),
259+
},
260+
"weeks": schema.Int64Attribute{
261+
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.",
262+
Optional: true,
263+
Computed: true,
264+
Default: int64default.StaticInt64(1),
265+
},
266+
},
267+
Default: objectdefault.StaticValue(types.ObjectValueMust(autostopRequirementTypeAttr, map[string]attr.Value{
268+
"days_of_week": types.SetValueMust(types.StringType, []attr.Value{}),
269+
"weeks": types.Int64Value(1),
270+
})),
271+
},
272+
"auto_start_permitted_days_of_week": schema.SetAttribute{
273+
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.",
274+
Optional: true,
275+
Computed: true,
276+
ElementType: types.StringType,
277+
Validators: []validator.Set{weekValidator},
278+
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")})),
279+
},
280+
"allow_user_cancel_workspace_jobs": schema.BoolAttribute{
281+
MarkdownDescription: "Whether users can cancel in-progress workspace jobs using this template. Defaults to true.",
282+
Optional: true,
283+
Computed: true,
284+
Default: booldefault.StaticBool(true),
285+
},
200286
"allow_user_auto_start": schema.BoolAttribute{
201-
Optional: true,
202-
Computed: true,
203-
Default: booldefault.StaticBool(true),
287+
MarkdownDescription: "Whether users can auto-start workspaces created from this template. Defaults to true.",
288+
Optional: true,
289+
Computed: true,
290+
Default: booldefault.StaticBool(true),
204291
},
205292
"allow_user_auto_stop": schema.BoolAttribute{
206-
Optional: true,
207-
Computed: true,
208-
Default: booldefault.StaticBool(true),
293+
MarkdownDescription: "Whether users can auto-start workspaces created from this template. Defaults to true.",
294+
Optional: true,
295+
Computed: true,
296+
Default: booldefault.StaticBool(true),
297+
},
298+
"failure_ttl_ms": schema.Int64Attribute{
299+
MarkdownDescription: "The max lifetime before Coder stops all resources for failed workspaces created from this template, in milliseconds.",
300+
Optional: true,
301+
Computed: true,
302+
Default: int64default.StaticInt64(0),
303+
},
304+
"time_til_dormant_ms": schema.Int64Attribute{
305+
MarkdownDescription: "The max lifetime before Coder locks inactive workspaces created from this template, in milliseconds.",
306+
Optional: true,
307+
Computed: true,
308+
Default: int64default.StaticInt64(0),
309+
},
310+
"time_til_dormant_autodelete_ms": schema.Int64Attribute{
311+
MarkdownDescription: "The max lifetime before Coder permanently deletes dormant workspaces created from this template.",
312+
Optional: true,
313+
Computed: true,
314+
Default: int64default.StaticInt64(0),
315+
},
316+
"require_active_version": schema.BoolAttribute{
317+
MarkdownDescription: "Whether workspaces must be created from the active version of this template. Defaults to false.",
318+
Optional: true,
319+
Computed: true,
320+
Default: booldefault.StaticBool(false),
321+
},
322+
"deprecation_message": schema.StringAttribute{
323+
MarkdownDescription: "If set, the template will be marked as deprecated and users will be blocked from creating new workspaces from it.",
324+
Optional: true,
325+
Computed: true,
326+
Default: stringdefault.StaticString(""),
209327
},
210328
"acl": schema.SingleNestedAttribute{
211329
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 +445,11 @@ func (r *TemplateResource) Create(ctx context.Context, req resource.CreateReques
327445
}
328446
if idx == 0 {
329447
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-
})
448+
createReq := data.toCreateRequest(ctx, resp, versionResp.ID)
449+
if resp.Diagnostics.HasError() {
450+
return
451+
}
452+
templateResp, err = client.CreateTemplate(ctx, orgID, *createReq)
340453
if err != nil {
341454
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to create template: %s", err))
342455
return
@@ -345,6 +458,13 @@ func (r *TemplateResource) Create(ctx context.Context, req resource.CreateReques
345458
"id": templateResp.ID,
346459
})
347460

461+
// Read the response into the state to set computed fields
462+
diag := data.readResponse(ctx, &templateResp)
463+
if diag.HasError() {
464+
resp.Diagnostics.Append(diag...)
465+
return
466+
}
467+
348468
if !data.ACL.IsNull() {
349469
tflog.Trace(ctx, "updating template ACL")
350470
var acl ACL
@@ -382,7 +502,7 @@ func (r *TemplateResource) Create(ctx context.Context, req resource.CreateReques
382502
data.ID = UUIDValue(templateResp.ID)
383503
data.DisplayName = types.StringValue(templateResp.DisplayName)
384504

385-
// Save data into Terraform state
505+
// Save data into Terraform sutate
386506
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
387507
}
388508

@@ -405,13 +525,11 @@ func (r *TemplateResource) Read(ctx context.Context, req resource.ReadRequest, r
405525
return
406526
}
407527

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)
528+
diag := data.readResponse(ctx, &template)
529+
if diag.HasError() {
530+
resp.Diagnostics.Append(diag...)
531+
return
532+
}
415533

416534
if !data.ACL.IsNull() {
417535
tflog.Trace(ctx, "reading template ACL")
@@ -481,26 +599,26 @@ func (r *TemplateResource) Update(ctx context.Context, req resource.UpdateReques
481599

482600
client := r.data.Client
483601

484-
if !planState.EqualTemplateMetadata(curState) {
602+
templateMetadataChanged := !planState.EqualTemplateMetadata(curState)
603+
// This is required, as the API will reject no-diff updates.
604+
if templateMetadataChanged {
485605
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-
})
606+
updateReq := planState.toUpdateRequest(ctx, resp)
607+
if resp.Diagnostics.HasError() {
608+
return
609+
}
610+
_, err := client.UpdateTemplateMeta(ctx, templateID, *updateReq)
495611
if err != nil {
496612
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to update template metadata: %s", err))
497613
return
498614
}
615+
499616
tflog.Trace(ctx, "successfully updated template metadata")
500617
}
501618

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

676794
var _ planmodifier.Object = &directoryHashPlanModifier{}
677795

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

‎internal/provider/template_resource_test.go

Lines changed: 89 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_ms", "0"),
114+
resource.TestCheckResourceAttr("coderd_template.test", "activity_bump_ms", "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_permitted_days_of_week.#", "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_ms", "0"),
122+
resource.TestCheckResourceAttr("coderd_template.test", "time_til_dormant_ms", "0"),
123+
resource.TestCheckResourceAttr("coderd_template.test", "time_til_dormant_autodelete_ms", "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,26 @@ 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+
DeprecationMessage *string
242+
243+
Versions []testAccTemplateVersionConfig
244+
ACL testAccTemplateACLConfig
210245
}
211246

212247
type testAccTemplateACLConfig struct {
@@ -216,10 +251,10 @@ type testAccTemplateACLConfig struct {
216251
}
217252

218253
func (c testAccTemplateACLConfig) String(t *testing.T) string {
254+
t.Helper()
219255
if c.null == true {
220256
return "null"
221257
}
222-
t.Helper()
223258
tpl := `{
224259
groups = [
225260
{{- range .GroupACL}}
@@ -254,6 +289,36 @@ func (c testAccTemplateACLConfig) String(t *testing.T) string {
254289
return buf.String()
255290
}
256291

292+
type testAccAutostopRequirementConfig struct {
293+
null bool
294+
DaysOfWeek *[]string
295+
Weeks *int64
296+
}
297+
298+
func (c testAccAutostopRequirementConfig) String(t *testing.T) string {
299+
t.Helper()
300+
if c.null == true {
301+
return "null"
302+
}
303+
tpl := `{
304+
days_of_week = {{orNull .DaysOfWeek}}
305+
weeks = {{orNull .Weeks}}
306+
}
307+
`
308+
funcMap := template.FuncMap{
309+
"orNull": PrintOrNull,
310+
}
311+
312+
buf := strings.Builder{}
313+
tmpl, err := template.New("test").Funcs(funcMap).Parse(tpl)
314+
require.NoError(t, err)
315+
316+
err = tmpl.Execute(&buf, c)
317+
require.NoError(t, err)
318+
319+
return buf.String()
320+
}
321+
257322
func (c testAccTemplateResourceConfig) String(t *testing.T) string {
258323
t.Helper()
259324
tpl := `
@@ -263,10 +328,23 @@ provider coderd {
263328
}
264329
265330
resource "coderd_template" "test" {
266-
name = {{orNull .Name}}
267-
display_name = {{orNull .DisplayName}}
268-
description = {{orNull .Description}}
269-
organization_id = {{orNull .OrganizationID}}
331+
name = {{orNull .Name}}
332+
display_name = {{orNull .DisplayName}}
333+
description = {{orNull .Description}}
334+
organization_id = {{orNull .OrganizationID}}
335+
icon = {{orNull .Icon}}
336+
default_ttl_ms = {{orNull .DefaultTTL}}
337+
activity_bump_ms = {{orNull .ActivityBump}}
338+
auto_stop_requirement = ` + c.AutostopRequirement.String(t) + `
339+
auto_start_permitted_days_of_week = {{orNull .AutostartRequirement}}
340+
allow_user_cancel_workspace_jobs = {{orNull .AllowUserCancelWorkspaceJobs}}
341+
allow_user_auto_start = {{orNull .AllowUserAutostart}}
342+
allow_user_auto_stop = {{orNull .AllowUserAutostop}}
343+
failure_ttl_ms = {{orNull .FailureTTL}}
344+
time_til_dormant_ms = {{orNull .TimeTilDormant}}
345+
time_til_dormant_autodelete_ms = {{orNull .TimeTilDormantAutodelete}}
346+
require_active_version = {{orNull .RequireActiveVersion}}
347+
deprecation_message = {{orNull .DeprecationMessage}}
270348
271349
acl = ` + c.ACL.String(t) + `
272350

‎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.