diff --git a/.gitignore b/.gitignore
index 725d809..952b275 100644
--- a/.gitignore
+++ b/.gitignore
@@ -39,3 +39,10 @@ terraform-provider-coderd
integration/integration.tfrc
*.tfstate
+
+# Local .terraform directories
+**/.terraform/*
+
+
+# Ignore transient lock info files created by terraform apply
+.terraform.tfstate.lock.info
diff --git a/docs/data-sources/template.md b/docs/data-sources/template.md
index c07fd38..28cfcad 100644
--- a/docs/data-sources/template.md
+++ b/docs/data-sources/template.md
@@ -47,12 +47,15 @@ resource "coderd_template" "debian-main" {
### Read-Only
+- `acl` (Attributes) (Enterprise) Access control list for the template. (see [below for nested schema](#nestedatt--acl))
- `active_user_count` (Number) Number of active users using the template.
- `active_version_id` (String) ID of the active version of the template.
- `activity_bump_ms` (Number) Duration to bump the deadline of a workspace when it receives activity.
- `allow_user_autostart` (Boolean) Whether users can autostart workspaces created from the template.
- `allow_user_autostop` (Boolean) Whether users can customize autostop behavior for workspaces created from the template.
- `allow_user_cancel_workspace_jobs` (Boolean) Whether users can cancel jobs in workspaces created from the template.
+- `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.
+- `auto_stop_requirement` (Attributes) The auto-stop requirement for all workspaces created from this template. (see [below for nested schema](#nestedatt--auto_stop_requirement))
- `created_at` (Number) Unix timestamp of when the template was created.
- `created_by_user_id` (String) ID of the user who created the template.
- `default_ttl_ms` (Number) Default time-to-live for workspaces created from the template.
@@ -62,7 +65,46 @@ resource "coderd_template" "debian-main" {
- `display_name` (String) Display name of the template.
- `failure_ttl_ms` (Number) Automatic cleanup TTL for failed workspace builds.
- `icon` (String) URL of the template's icon.
+- `max_port_share_level` (String) The maximum port share level for workspaces created from the template.
- `require_active_version` (Boolean) Whether workspaces created from the template must be up-to-date on the latest active version.
- `time_til_dormant_autodelete_ms` (Number) Duration of inactivity after the workspace becomes dormant before a workspace is automatically deleted.
- `time_til_dormant_ms` (Number) Duration of inactivity before a workspace is considered dormant.
- `updated_at` (Number) Unix timestamp of when the template was last updated.
+
+
+### Nested Schema for `acl`
+
+Read-Only:
+
+- `groups` (Attributes Set) (see [below for nested schema](#nestedatt--acl--groups))
+- `users` (Attributes Set) (see [below for nested schema](#nestedatt--acl--users))
+
+
+### Nested Schema for `acl.groups`
+
+Read-Only:
+
+- `id` (String)
+- `role` (String)
+
+
+
+### Nested Schema for `acl.users`
+
+Read-Only:
+
+- `id` (String)
+- `role` (String)
+
+
+
+
+### Nested Schema for `auto_stop_requirement`
+
+Optional:
+
+- `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.
+
+Read-Only:
+
+- `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.
diff --git a/docs/resources/template.md b/docs/resources/template.md
index 2e8dc9a..4300c55 100644
--- a/docs/resources/template.md
+++ b/docs/resources/template.md
@@ -69,7 +69,7 @@ resource "coderd_template" "ubuntu-main" {
- `auto_start_permitted_days_of_week` (Set of String) (Enterprise) 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.
- `auto_stop_requirement` (Attributes) (Enterprise) The auto-stop requirement for all workspaces created from this template. (see [below for nested schema](#nestedatt--auto_stop_requirement))
- `default_ttl_ms` (Number) The default time-to-live for all workspaces created from this template, in milliseconds.
-- `deprecation_message` (String) If set, the template will be marked as deprecated with the provided message and users will be blocked from creating new workspaces from it.
+- `deprecation_message` (String) If set, the template will be marked as deprecated with the provided message and users will be blocked from creating new workspaces from it. Does nothing if set when the resource is created.
- `description` (String) A description of the template.
- `display_name` (String) The display name of the template. Defaults to the template name.
- `failure_ttl_ms` (Number) (Enterprise) The max lifetime before Coder stops all resources for failed workspaces created from this template, in milliseconds.
diff --git a/internal/provider/template_data_source.go b/internal/provider/template_data_source.go
index 9b5d4a2..406ef33 100644
--- a/internal/provider/template_data_source.go
+++ b/internal/provider/template_data_source.go
@@ -7,6 +7,7 @@ import (
"github.com/coder/coder/v2/codersdk"
"github.com/google/uuid"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
+ "github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/path"
@@ -42,10 +43,10 @@ type TemplateDataSourceModel struct {
DeprecationMessage types.String `tfsdk:"deprecation_message"`
Icon types.String `tfsdk:"icon"`
- DefaultTTLMillis types.Int64 `tfsdk:"default_ttl_ms"`
- ActivityBumpMillis types.Int64 `tfsdk:"activity_bump_ms"`
- // TODO: AutostopRequirement
- // TODO: AutostartRequirement
+ DefaultTTLMillis types.Int64 `tfsdk:"default_ttl_ms"`
+ ActivityBumpMillis types.Int64 `tfsdk:"activity_bump_ms"`
+ AutostopRequirement types.Object `tfsdk:"auto_stop_requirement"`
+ AutostartPermittedDaysOfWeek types.Set `tfsdk:"auto_start_permitted_days_of_week"`
AllowUserAutostart types.Bool `tfsdk:"allow_user_autostart"`
AllowUserAutostop types.Bool `tfsdk:"allow_user_autostop"`
@@ -55,14 +56,14 @@ type TemplateDataSourceModel struct {
TimeTilDormantMillis types.Int64 `tfsdk:"time_til_dormant_ms"`
TimeTilDormantAutoDeleteMillis types.Int64 `tfsdk:"time_til_dormant_autodelete_ms"`
- RequireActiveVersion types.Bool `tfsdk:"require_active_version"`
- // TODO: MaxPortShareLevel
+ RequireActiveVersion types.Bool `tfsdk:"require_active_version"`
+ MaxPortShareLevel types.String `tfsdk:"max_port_share_level"`
CreatedByUserID UUID `tfsdk:"created_by_user_id"`
CreatedAt types.Int64 `tfsdk:"created_at"` // Unix timestamp
UpdatedAt types.Int64 `tfsdk:"updated_at"` // Unix timestamp
- // TODO: ACL-related stuff
+ ACL types.Object `tfsdk:"acl"`
}
func (d *TemplateDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
@@ -134,6 +135,27 @@ func (d *TemplateDataSource) Schema(ctx context.Context, req datasource.SchemaRe
MarkdownDescription: "Duration to bump the deadline of a workspace when it receives activity.",
Computed: true,
},
+ "auto_stop_requirement": schema.SingleNestedAttribute{
+ MarkdownDescription: "The auto-stop requirement for all workspaces created from this template.",
+ Computed: true,
+ Attributes: map[string]schema.Attribute{
+ "days_of_week": schema.SetAttribute{
+ 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.",
+ Computed: true,
+ ElementType: types.StringType,
+ },
+ "weeks": schema.Int64Attribute{
+ 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.",
+ Optional: true,
+ Computed: true,
+ },
+ },
+ },
+ "auto_start_permitted_days_of_week": schema.SetAttribute{
+ 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.",
+ Computed: true,
+ ElementType: types.StringType,
+ },
"allow_user_autostart": schema.BoolAttribute{
MarkdownDescription: "Whether users can autostart workspaces created from the template.",
Computed: true,
@@ -162,6 +184,10 @@ func (d *TemplateDataSource) Schema(ctx context.Context, req datasource.SchemaRe
MarkdownDescription: "Whether workspaces created from the template must be up-to-date on the latest active version.",
Computed: true,
},
+ "max_port_share_level": schema.StringAttribute{
+ MarkdownDescription: "The maximum port share level for workspaces created from the template.",
+ Computed: true,
+ },
"created_by_user_id": schema.StringAttribute{
MarkdownDescription: "ID of the user who created the template.",
CustomType: UUIDType,
@@ -175,6 +201,14 @@ func (d *TemplateDataSource) Schema(ctx context.Context, req datasource.SchemaRe
MarkdownDescription: "Unix timestamp of when the template was last updated.",
Computed: true,
},
+ "acl": schema.SingleNestedAttribute{
+ MarkdownDescription: "(Enterprise) Access control list for the template.",
+ Computed: true,
+ Attributes: map[string]schema.Attribute{
+ "users": computedPermissionAttribute,
+ "groups": computedPermissionAttribute,
+ },
+ },
},
}
}
@@ -244,6 +278,33 @@ func (d *TemplateDataSource) Read(ctx context.Context, req datasource.ReadReques
return
}
+ acl, err := client.TemplateACL(ctx, template.ID)
+ if err != nil {
+ resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to get template ACL: %s", err))
+ return
+ }
+ tfACL := convertResponseToACL(acl)
+ aclObj, diag := types.ObjectValueFrom(ctx, aclTypeAttr, tfACL)
+ if diag.HasError() {
+ resp.Diagnostics.Append(diag...)
+ return
+ }
+
+ asrObj, diag := types.ObjectValueFrom(ctx, autostopRequirementTypeAttr, AutostopRequirement{
+ DaysOfWeek: template.AutostopRequirement.DaysOfWeek,
+ Weeks: template.AutostopRequirement.Weeks,
+ })
+ resp.Diagnostics.Append(diag...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ autoStartDays := make([]attr.Value, 0, len(template.AutostartRequirement.DaysOfWeek))
+ for _, day := range template.AutostartRequirement.DaysOfWeek {
+ autoStartDays = append(autoStartDays, types.StringValue(day))
+ }
+ data.ACL = aclObj
+ data.AutostartPermittedDaysOfWeek = types.SetValueMust(types.StringType, autoStartDays)
+ data.AutostopRequirement = asrObj
data.OrganizationID = UUIDValue(template.OrganizationID)
data.ID = UUIDValue(template.ID)
data.Name = types.StringValue(template.Name)
@@ -263,6 +324,7 @@ func (d *TemplateDataSource) Read(ctx context.Context, req datasource.ReadReques
data.TimeTilDormantMillis = types.Int64Value(template.TimeTilDormantMillis)
data.TimeTilDormantAutoDeleteMillis = types.Int64Value(template.TimeTilDormantAutoDeleteMillis)
data.RequireActiveVersion = types.BoolValue(template.RequireActiveVersion)
+ data.MaxPortShareLevel = types.StringValue(string(template.MaxPortShareLevel))
data.CreatedByUserID = UUIDValue(template.CreatedByID)
data.CreatedAt = types.Int64Value(template.CreatedAt.Unix())
data.UpdatedAt = types.Int64Value(template.UpdatedAt.Unix())
@@ -270,3 +332,18 @@ func (d *TemplateDataSource) Read(ctx context.Context, req datasource.ReadReques
// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
+
+// computedPermissionAttribute is the attribute schema for a computed instance of `[]Permission`.
+var computedPermissionAttribute = schema.SetNestedAttribute{
+ Computed: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ Computed: true,
+ },
+ "role": schema.StringAttribute{
+ Computed: true,
+ },
+ },
+ },
+}
diff --git a/internal/provider/template_data_source_test.go b/internal/provider/template_data_source_test.go
index 0c1e40d..79c1f80 100644
--- a/internal/provider/template_data_source_test.go
+++ b/internal/provider/template_data_source_test.go
@@ -99,6 +99,16 @@ func TestAccTemplateDataSource(t *testing.T) {
})
require.NoError(t, err)
+ err = client.UpdateTemplateACL(ctx, tpl.ID, codersdk.UpdateTemplateACL{
+ UserPerms: map[string]codersdk.TemplateRole{
+ firstUser.ID.String(): codersdk.TemplateRoleAdmin,
+ },
+ GroupPerms: map[string]codersdk.TemplateRole{
+ firstUser.OrganizationIDs[0].String(): codersdk.TemplateRoleUse,
+ },
+ })
+ require.NoError(t, err)
+
checkFn := resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("data.coderd_template.test", "organization_id", tpl.OrganizationID.String()),
resource.TestCheckResourceAttr("data.coderd_template.test", "id", tpl.ID.String()),
@@ -112,6 +122,10 @@ func TestAccTemplateDataSource(t *testing.T) {
resource.TestCheckResourceAttr("data.coderd_template.test", "icon", tpl.Icon),
resource.TestCheckResourceAttr("data.coderd_template.test", "default_ttl_ms", strconv.FormatInt(tpl.DefaultTTLMillis, 10)),
resource.TestCheckResourceAttr("data.coderd_template.test", "activity_bump_ms", strconv.FormatInt(tpl.ActivityBumpMillis, 10)),
+ resource.TestCheckResourceAttr("data.coderd_template.test", "auto_stop_requirement.days_of_week.#", strconv.FormatInt(int64(len(tpl.AutostopRequirement.DaysOfWeek)), 10)),
+ resource.TestCheckResourceAttr("data.coderd_template.test", "auto_stop_requirement.weeks", strconv.FormatInt(tpl.AutostopRequirement.Weeks, 10)),
+ resource.TestCheckResourceAttr("data.coderd_template.test", "auto_start_permitted_days_of_week.#", strconv.FormatInt(int64(len(tpl.AutostartRequirement.DaysOfWeek)), 10)),
+ resource.TestCheckResourceAttr("data.coderd_template.test", "allow_user_cancel_workspace_jobs", "true"),
resource.TestCheckResourceAttr("data.coderd_template.test", "allow_user_autostart", strconv.FormatBool(tpl.AllowUserAutostart)),
resource.TestCheckResourceAttr("data.coderd_template.test", "allow_user_autostop", strconv.FormatBool(tpl.AllowUserAutostop)),
resource.TestCheckResourceAttr("data.coderd_template.test", "allow_user_cancel_workspace_jobs", strconv.FormatBool(tpl.AllowUserCancelWorkspaceJobs)),
@@ -119,9 +133,20 @@ func TestAccTemplateDataSource(t *testing.T) {
resource.TestCheckResourceAttr("data.coderd_template.test", "time_til_dormant_ms", strconv.FormatInt(tpl.TimeTilDormantMillis, 10)),
resource.TestCheckResourceAttr("data.coderd_template.test", "time_til_dormant_autodelete_ms", strconv.FormatInt(tpl.TimeTilDormantAutoDeleteMillis, 10)),
resource.TestCheckResourceAttr("data.coderd_template.test", "require_active_version", strconv.FormatBool(tpl.RequireActiveVersion)),
+ resource.TestCheckResourceAttr("data.coderd_template.test", "max_port_share_level", string(tpl.MaxPortShareLevel)),
resource.TestCheckResourceAttr("data.coderd_template.test", "created_by_user_id", firstUser.ID.String()),
resource.TestCheckResourceAttr("data.coderd_template.test", "created_at", strconv.Itoa(int(tpl.CreatedAt.Unix()))),
resource.TestCheckResourceAttr("data.coderd_template.test", "updated_at", strconv.Itoa(int(tpl.UpdatedAt.Unix()))),
+ resource.TestCheckResourceAttr("data.coderd_template.test", "acl.groups.#", "1"),
+ resource.TestCheckResourceAttr("data.coderd_template.test", "acl.users.#", "1"),
+ resource.TestMatchTypeSetElemNestedAttrs("data.coderd_template.test", "acl.groups.*", map[string]*regexp.Regexp{
+ "id": regexp.MustCompile(firstUser.OrganizationIDs[0].String()),
+ "role": regexp.MustCompile("^use$"),
+ }),
+ resource.TestMatchTypeSetElemNestedAttrs("data.coderd_template.test", "acl.users.*", map[string]*regexp.Regexp{
+ "id": regexp.MustCompile(firstUser.ID.String()),
+ "role": regexp.MustCompile("^admin$"),
+ }),
)
t.Run("TemplateByOrgAndNameOK", func(t *testing.T) {
diff --git a/internal/provider/template_resource.go b/internal/provider/template_resource.go
index b643cb3..3bdde6d 100644
--- a/internal/provider/template_resource.go
+++ b/internal/provider/template_resource.go
@@ -361,7 +361,7 @@ func (r *TemplateResource) Schema(ctx context.Context, req resource.SchemaReques
Default: booldefault.StaticBool(false),
},
"deprecation_message": schema.StringAttribute{
- MarkdownDescription: "If set, the template will be marked as deprecated with the provided message and users will be blocked from creating new workspaces from it.",
+ MarkdownDescription: "If set, the template will be marked as deprecated with the provided message and users will be blocked from creating new workspaces from it. Does nothing if set when the resource is created.",
Optional: true,
Computed: true,
Default: stringdefault.StaticString(""),
@@ -586,8 +586,8 @@ func (r *TemplateResource) Read(ctx context.Context, req resource.ReadRequest, r
}
tfACL := convertResponseToACL(acl)
aclObj, diag := types.ObjectValueFrom(ctx, aclTypeAttr, tfACL)
- diag.Append(diag...)
if diag.HasError() {
+ resp.Diagnostics.Append(diag...)
return
}
data.ACL = aclObj