Skip to content

Commit 9aa27ba

Browse files
fix: support non-enterprise template resources (#47)
1 parent 42bee92 commit 9aa27ba

File tree

7 files changed

+167
-124
lines changed

7 files changed

+167
-124
lines changed

docs/resources/group.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ A group on the Coder deployment. If you want to have a group resource with unman
2323

2424
- `avatar_url` (String) The URL of the group's avatar.
2525
- `display_name` (String) The display name of the group. Defaults to the group name.
26-
- `members` (Set of String) Members of the group, by ID. If null, members will not be added or removed.
26+
- `members` (Set of String) Members of the group, by ID. If null, members will not be added or removed by Terraform.
2727
- `organization_id` (String) The organization ID that the group belongs to. Defaults to the provider default organization ID.
2828
- `quota_allowance` (Number) The number of quota credits to allocate to each user in the group.
2929

docs/resources/template.md

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ A Coder template
1717

1818
### Required
1919

20-
- `acl` (Attributes) Access control list for the template. (see [below for nested schema](#nestedatt--acl))
2120
- `name` (String) The name of the template.
2221
- `versions` (Attributes List) (see [below for nested schema](#nestedatt--versions))
2322

2423
### Optional
2524

25+
- `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))
2626
- `allow_user_auto_start` (Boolean)
2727
- `allow_user_auto_stop` (Boolean)
2828
- `description` (String) A description of the template.
@@ -34,33 +34,6 @@ A Coder template
3434

3535
- `id` (String) The ID of the template.
3636

37-
<a id="nestedatt--acl"></a>
38-
### Nested Schema for `acl`
39-
40-
Required:
41-
42-
- `groups` (Attributes Set) (see [below for nested schema](#nestedatt--acl--groups))
43-
- `users` (Attributes Set) (see [below for nested schema](#nestedatt--acl--users))
44-
45-
<a id="nestedatt--acl--groups"></a>
46-
### Nested Schema for `acl.groups`
47-
48-
Required:
49-
50-
- `id` (String)
51-
- `role` (String)
52-
53-
54-
<a id="nestedatt--acl--users"></a>
55-
### Nested Schema for `acl.users`
56-
57-
Required:
58-
59-
- `id` (String)
60-
- `role` (String)
61-
62-
63-
6437
<a id="nestedatt--versions"></a>
6538
### Nested Schema for `versions`
6639

@@ -97,3 +70,30 @@ Required:
9770

9871
- `name` (String)
9972
- `value` (String)
73+
74+
75+
76+
<a id="nestedatt--acl"></a>
77+
### Nested Schema for `acl`
78+
79+
Required:
80+
81+
- `groups` (Attributes Set) (see [below for nested schema](#nestedatt--acl--groups))
82+
- `users` (Attributes Set) (see [below for nested schema](#nestedatt--acl--users))
83+
84+
<a id="nestedatt--acl--groups"></a>
85+
### Nested Schema for `acl.groups`
86+
87+
Required:
88+
89+
- `id` (String)
90+
- `role` (String)
91+
92+
93+
<a id="nestedatt--acl--users"></a>
94+
### Nested Schema for `acl.users`
95+
96+
Required:
97+
98+
- `id` (String)
99+
- `role` (String)

internal/provider/group_data_source.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,10 @@ func (d *GroupDataSource) Read(ctx context.Context, req datasource.ReadRequest,
174174
data.OrganizationID = UUIDValue(d.data.DefaultOrganizationID)
175175
}
176176

177-
var group codersdk.Group
178-
var err error
177+
var (
178+
group codersdk.Group
179+
err error
180+
)
179181
if !data.ID.IsNull() {
180182
groupID := data.ID.ValueUUID()
181183
group, err = client.Group(ctx, groupID)

internal/provider/group_resource.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ func (r *GroupResource) Schema(ctx context.Context, req resource.SchemaRequest,
9393
},
9494
},
9595
"members": schema.SetAttribute{
96-
MarkdownDescription: "Members of the group, by ID. If null, members will not be added or removed.",
96+
MarkdownDescription: "Members of the group, by ID. If null, members will not be added or removed by Terraform.",
9797
ElementType: UUIDType,
9898
Optional: true,
9999
},

internal/provider/organization_data_source.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,10 @@ func (d *OrganizationDataSource) Read(ctx context.Context, req datasource.ReadRe
115115

116116
client := d.data.Client
117117

118-
var org codersdk.Organization
119-
var err error
118+
var (
119+
org codersdk.Organization
120+
err error
121+
)
120122
if !data.ID.IsNull() { // By ID
121123
orgID := data.ID.ValueUUID()
122124
org, err = client.Organization(ctx, orgID)

internal/provider/template_resource.go

Lines changed: 68 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/google/uuid"
1313
"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
1414
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
15+
"github.com/hashicorp/terraform-plugin-framework/attr"
1516
"github.com/hashicorp/terraform-plugin-framework/path"
1617
"github.com/hashicorp/terraform-plugin-framework/resource"
1718
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
@@ -22,6 +23,7 @@ import (
2223
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
2324
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
2425
"github.com/hashicorp/terraform-plugin-framework/types"
26+
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
2527
"github.com/hashicorp/terraform-plugin-log/tflog"
2628
)
2729

@@ -51,20 +53,19 @@ type TemplateResourceModel struct {
5153
AllowUserAutoStart types.Bool `tfsdk:"allow_user_auto_start"`
5254
AllowUserAutoStop types.Bool `tfsdk:"allow_user_auto_stop"`
5355

54-
ACL *ACL `tfsdk:"acl"`
55-
Versions Versions `tfsdk:"versions"`
56+
ACL types.Object `tfsdk:"acl"`
57+
Versions Versions `tfsdk:"versions"`
5658
}
5759

58-
// EqualTemplateMetadata returns true if two templates have identical metadata & ACL.
60+
// EqualTemplateMetadata returns true if two templates have identical metadata (excluding ACL).
5961
func (m TemplateResourceModel) EqualTemplateMetadata(other TemplateResourceModel) bool {
6062
return m.Name.Equal(other.Name) &&
6163
m.DisplayName.Equal(other.DisplayName) &&
6264
m.Description.Equal(other.Description) &&
6365
m.OrganizationID.Equal(other.OrganizationID) &&
6466
m.Icon.Equal(other.Icon) &&
6567
m.AllowUserAutoStart.Equal(other.AllowUserAutoStart) &&
66-
m.AllowUserAutoStop.Equal(other.AllowUserAutoStop) &&
67-
m.ACL.Equal(other.ACL)
68+
m.AllowUserAutoStop.Equal(other.AllowUserAutoStop)
6869
}
6970

7071
type TemplateVersion struct {
@@ -110,38 +111,10 @@ type ACL struct {
110111
GroupPermissions []Permission `tfsdk:"groups"`
111112
}
112113

113-
func (a *ACL) Equal(other *ACL) bool {
114-
if len(a.UserPermissions) != len(other.UserPermissions) {
115-
return false
116-
}
117-
if len(a.GroupPermissions) != len(other.GroupPermissions) {
118-
return false
119-
}
120-
for _, e1 := range a.UserPermissions {
121-
found := false
122-
for _, e2 := range other.UserPermissions {
123-
if e1.Equal(&e2) {
124-
found = true
125-
break
126-
}
127-
}
128-
if !found {
129-
return false
130-
}
131-
}
132-
for _, e1 := range a.GroupPermissions {
133-
found := false
134-
for _, e2 := range other.GroupPermissions {
135-
if e1.Equal(&e2) {
136-
found = true
137-
break
138-
}
139-
}
140-
if !found {
141-
return false
142-
}
143-
}
144-
return true
114+
// aclTypeAttr is the type schema for an instance of `ACL`.
115+
var aclTypeAttr = map[string]attr.Type{
116+
"users": permissionTypeAttr,
117+
"groups": permissionTypeAttr,
145118
}
146119

147120
type Permission struct {
@@ -151,12 +124,8 @@ type Permission struct {
151124
Role types.String `tfsdk:"role"`
152125
}
153126

154-
func (p *Permission) Equal(other *Permission) bool {
155-
return p.ID.Equal(other.ID) && p.Role.Equal(other.Role)
156-
}
157-
158-
// permissionsAttribute is the attribute schema for an instance of `[]Permission`.
159-
var permissionsAttribute = schema.SetNestedAttribute{
127+
// permissionAttribute is the attribute schema for an instance of `[]Permission`.
128+
var permissionAttribute = schema.SetNestedAttribute{
160129
Required: true,
161130
NestedObject: schema.NestedAttributeObject{
162131
Attributes: map[string]schema.Attribute{
@@ -165,14 +134,19 @@ var permissionsAttribute = schema.SetNestedAttribute{
165134
},
166135
"role": schema.StringAttribute{
167136
Required: true,
168-
Validators: []validator.String{
169-
stringvalidator.OneOf("admin", "use", ""),
170-
},
171137
},
172138
},
173139
},
174140
}
175141

142+
// permissionTypeAttr is the type schema for an instance of `[]Permission`.
143+
var permissionTypeAttr = basetypes.SetType{ElemType: types.ObjectType{
144+
AttrTypes: map[string]attr.Type{
145+
"id": basetypes.StringType{},
146+
"role": basetypes.StringType{},
147+
},
148+
}}
149+
176150
func (r *TemplateResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
177151
resp.TypeName = req.ProviderTypeName + "_template"
178152
}
@@ -234,11 +208,11 @@ func (r *TemplateResource) Schema(ctx context.Context, req resource.SchemaReques
234208
Default: booldefault.StaticBool(true),
235209
},
236210
"acl": schema.SingleNestedAttribute{
237-
MarkdownDescription: "Access control list for the template.",
238-
Required: true,
211+
MarkdownDescription: "Access control list for the template. Requires an enterprise Coder deployment. If null, ACL policies will not be added or removed by Terraform.",
212+
Optional: true,
239213
Attributes: map[string]schema.Attribute{
240-
"users": permissionsAttribute,
241-
"groups": permissionsAttribute,
214+
"users": permissionAttribute,
215+
"groups": permissionAttribute,
242216
},
243217
},
244218
"versions": schema.ListNestedAttribute{
@@ -371,13 +345,22 @@ func (r *TemplateResource) Create(ctx context.Context, req resource.CreateReques
371345
"id": templateResp.ID,
372346
})
373347

374-
tflog.Trace(ctx, "updating template ACL")
375-
err = client.UpdateTemplateACL(ctx, templateResp.ID, convertACLToRequest(data.ACL))
376-
if err != nil {
377-
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to update template ACL: %s", err))
378-
return
348+
if !data.ACL.IsNull() {
349+
tflog.Trace(ctx, "updating template ACL")
350+
var acl ACL
351+
resp.Diagnostics.Append(
352+
data.ACL.As(ctx, &acl, basetypes.ObjectAsOptions{})...,
353+
)
354+
if resp.Diagnostics.HasError() {
355+
return
356+
}
357+
err = client.UpdateTemplateACL(ctx, templateResp.ID, convertACLToRequest(acl))
358+
if err != nil {
359+
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to create template ACL: %s", err))
360+
return
361+
}
362+
tflog.Trace(ctx, "successfully updated template ACL")
379363
}
380-
tflog.Trace(ctx, "successfully updated template ACL")
381364
}
382365
if version.Active.ValueBool() {
383366
tflog.Trace(ctx, "marking template version as active", map[string]any{
@@ -430,12 +413,22 @@ func (r *TemplateResource) Read(ctx context.Context, req resource.ReadRequest, r
430413
data.AllowUserAutoStart = types.BoolValue(template.AllowUserAutostart)
431414
data.AllowUserAutoStop = types.BoolValue(template.AllowUserAutostop)
432415

433-
acl, err := client.TemplateACL(ctx, templateID)
434-
if err != nil {
435-
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to get template ACL: %s", err))
436-
return
416+
if !data.ACL.IsNull() {
417+
tflog.Trace(ctx, "reading template ACL")
418+
acl, err := client.TemplateACL(ctx, templateID)
419+
if err != nil {
420+
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to get template ACL: %s", err))
421+
return
422+
}
423+
tfACL := convertResponseToACL(acl)
424+
aclObj, diag := types.ObjectValueFrom(ctx, aclTypeAttr, tfACL)
425+
diag.Append(diag...)
426+
if diag.HasError() {
427+
return
428+
}
429+
data.ACL = aclObj
430+
tflog.Trace(ctx, "read template ACL")
437431
}
438-
data.ACL = convertResponseToACL(acl)
439432

440433
for idx, version := range data.Versions {
441434
versionID := version.ID.ValueUUID()
@@ -500,11 +493,20 @@ func (r *TemplateResource) Update(ctx context.Context, req resource.UpdateReques
500493
DisableEveryoneGroupAccess: true,
501494
})
502495
if err != nil {
503-
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to update template: %s", err))
496+
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to update template metadata: %s", err))
504497
return
505498
}
506499
tflog.Trace(ctx, "successfully updated template metadata")
507-
err = client.UpdateTemplateACL(ctx, templateID, convertACLToRequest(planState.ACL))
500+
}
501+
502+
// If there's a change, and we're still managing ACL
503+
if !planState.ACL.Equal(curState.ACL) && !planState.ACL.IsNull() {
504+
var acl ACL
505+
resp.Diagnostics.Append(planState.ACL.As(ctx, &acl, basetypes.ObjectAsOptions{})...)
506+
if resp.Diagnostics.HasError() {
507+
return
508+
}
509+
err := client.UpdateTemplateACL(ctx, templateID, convertACLToRequest(acl))
508510
if err != nil {
509511
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to update template ACL: %s", err))
510512
return
@@ -784,10 +786,7 @@ func newVersion(ctx context.Context, client *codersdk.Client, req newVersionRequ
784786
return &versionResp, nil
785787
}
786788

787-
func convertACLToRequest(permissions *ACL) codersdk.UpdateTemplateACL {
788-
if permissions == nil {
789-
return codersdk.UpdateTemplateACL{}
790-
}
789+
func convertACLToRequest(permissions ACL) codersdk.UpdateTemplateACL {
791790
var userPerms = make(map[string]codersdk.TemplateRole)
792791
for _, perm := range permissions.UserPermissions {
793792
userPerms[perm.ID.ValueString()] = codersdk.TemplateRole(perm.Role.ValueString())
@@ -802,7 +801,7 @@ func convertACLToRequest(permissions *ACL) codersdk.UpdateTemplateACL {
802801
}
803802
}
804803

805-
func convertResponseToACL(acl codersdk.TemplateACL) *ACL {
804+
func convertResponseToACL(acl codersdk.TemplateACL) ACL {
806805
userPerms := make([]Permission, 0, len(acl.Users))
807806
for _, user := range acl.Users {
808807
userPerms = append(userPerms, Permission{
@@ -817,7 +816,7 @@ func convertResponseToACL(acl codersdk.TemplateACL) *ACL {
817816
Role: types.StringValue(string(group.Role)),
818817
})
819818
}
820-
return &ACL{
819+
return ACL{
821820
UserPermissions: userPerms,
822821
GroupPermissions: groupPerms,
823822
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy