Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 010010e

Browse files
committed
fix: support non-enterprise template resources
1 parent 3b82199 commit 010010e

File tree

7 files changed

+167
-124
lines changed

7 files changed

+167
-124
lines changed

docs/resources/group.md

+1-1
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

+28-28
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

+4-2
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

+1-1
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

+4-2
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

+68-69
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)