@@ -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)
5961func (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
7071type 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
147120type 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+
176150func (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