Skip to content

Commit

Permalink
fix: support non-enterprise template resources
Browse files Browse the repository at this point in the history
  • Loading branch information
ethanndickson committed Jul 30, 2024
1 parent c773873 commit 9d183b4
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 124 deletions.
2 changes: 1 addition & 1 deletion docs/resources/group.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ A group on the Coder deployment. If you want to have a group resource with unman

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

Expand Down
56 changes: 28 additions & 28 deletions docs/resources/template.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ A Coder template

### Required

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

### Optional

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

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

<a id="nestedatt--acl"></a>
### Nested Schema for `acl`

Required:

- `groups` (Attributes Set) (see [below for nested schema](#nestedatt--acl--groups))
- `users` (Attributes Set) (see [below for nested schema](#nestedatt--acl--users))

<a id="nestedatt--acl--groups"></a>
### Nested Schema for `acl.groups`

Required:

- `id` (String)
- `role` (String)


<a id="nestedatt--acl--users"></a>
### Nested Schema for `acl.users`

Required:

- `id` (String)
- `role` (String)



<a id="nestedatt--versions"></a>
### Nested Schema for `versions`

Expand Down Expand Up @@ -97,3 +70,30 @@ Required:

- `name` (String)
- `value` (String)



<a id="nestedatt--acl"></a>
### Nested Schema for `acl`

Required:

- `groups` (Attributes Set) (see [below for nested schema](#nestedatt--acl--groups))
- `users` (Attributes Set) (see [below for nested schema](#nestedatt--acl--users))

<a id="nestedatt--acl--groups"></a>
### Nested Schema for `acl.groups`

Required:

- `id` (String)
- `role` (String)


<a id="nestedatt--acl--users"></a>
### Nested Schema for `acl.users`

Required:

- `id` (String)
- `role` (String)
6 changes: 4 additions & 2 deletions internal/provider/group_data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,10 @@ func (d *GroupDataSource) Read(ctx context.Context, req datasource.ReadRequest,
data.OrganizationID = UUIDValue(d.data.DefaultOrganizationID)
}

var group codersdk.Group
var err error
var (
group codersdk.Group
err error
)
if !data.ID.IsNull() {
groupID := data.ID.ValueUUID()
group, err = client.Group(ctx, groupID)
Expand Down
2 changes: 1 addition & 1 deletion internal/provider/group_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func (r *GroupResource) Schema(ctx context.Context, req resource.SchemaRequest,
},
},
"members": schema.SetAttribute{
MarkdownDescription: "Members of the group, by ID. If null, members will not be added or removed.",
MarkdownDescription: "Members of the group, by ID. If null, members will not be added or removed by Terraform.",
ElementType: UUIDType,
Optional: true,
},
Expand Down
6 changes: 4 additions & 2 deletions internal/provider/organization_data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,10 @@ func (d *OrganizationDataSource) Read(ctx context.Context, req datasource.ReadRe

client := d.data.Client

var org codersdk.Organization
var err error
var (
org codersdk.Organization
err error
)
if !data.ID.IsNull() { // By ID
orgID := data.ID.ValueUUID()
org, err = client.Organization(ctx, orgID)
Expand Down
137 changes: 68 additions & 69 deletions internal/provider/template_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/google/uuid"
"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
Expand All @@ -22,6 +23,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/hashicorp/terraform-plugin-log/tflog"
)

Expand Down Expand Up @@ -51,20 +53,19 @@ type TemplateResourceModel struct {
AllowUserAutoStart types.Bool `tfsdk:"allow_user_auto_start"`
AllowUserAutoStop types.Bool `tfsdk:"allow_user_auto_stop"`

ACL *ACL `tfsdk:"acl"`
Versions Versions `tfsdk:"versions"`
ACL types.Object `tfsdk:"acl"`
Versions Versions `tfsdk:"versions"`
}

// EqualTemplateMetadata returns true if two templates have identical metadata & ACL.
// EqualTemplateMetadata returns true if two templates have identical metadata (excluding ACL).
func (m TemplateResourceModel) EqualTemplateMetadata(other TemplateResourceModel) bool {
return m.Name.Equal(other.Name) &&
m.DisplayName.Equal(other.DisplayName) &&
m.Description.Equal(other.Description) &&
m.OrganizationID.Equal(other.OrganizationID) &&
m.Icon.Equal(other.Icon) &&
m.AllowUserAutoStart.Equal(other.AllowUserAutoStart) &&
m.AllowUserAutoStop.Equal(other.AllowUserAutoStop) &&
m.ACL.Equal(other.ACL)
m.AllowUserAutoStop.Equal(other.AllowUserAutoStop)
}

type TemplateVersion struct {
Expand Down Expand Up @@ -110,38 +111,10 @@ type ACL struct {
GroupPermissions []Permission `tfsdk:"groups"`
}

func (a *ACL) Equal(other *ACL) bool {
if len(a.UserPermissions) != len(other.UserPermissions) {
return false
}
if len(a.GroupPermissions) != len(other.GroupPermissions) {
return false
}
for _, e1 := range a.UserPermissions {
found := false
for _, e2 := range other.UserPermissions {
if e1.Equal(&e2) {
found = true
break
}
}
if !found {
return false
}
}
for _, e1 := range a.GroupPermissions {
found := false
for _, e2 := range other.GroupPermissions {
if e1.Equal(&e2) {
found = true
break
}
}
if !found {
return false
}
}
return true
// aclTypeAttr is the type schema for an instance of `ACL`.
var aclTypeAttr = map[string]attr.Type{
"users": permissionTypeAttr,
"groups": permissionTypeAttr,
}

type Permission struct {
Expand All @@ -151,12 +124,8 @@ type Permission struct {
Role types.String `tfsdk:"role"`
}

func (p *Permission) Equal(other *Permission) bool {
return p.ID.Equal(other.ID) && p.Role.Equal(other.Role)
}

// permissionsAttribute is the attribute schema for an instance of `[]Permission`.
var permissionsAttribute = schema.SetNestedAttribute{
// permissionAttribute is the attribute schema for an instance of `[]Permission`.
var permissionAttribute = schema.SetNestedAttribute{
Required: true,
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
Expand All @@ -165,14 +134,19 @@ var permissionsAttribute = schema.SetNestedAttribute{
},
"role": schema.StringAttribute{
Required: true,
Validators: []validator.String{
stringvalidator.OneOf("admin", "use", ""),
},
},
},
},
}

// permissionTypeAttr is the type schema for an instance of `[]Permission`.
var permissionTypeAttr = basetypes.SetType{ElemType: types.ObjectType{
AttrTypes: map[string]attr.Type{
"id": basetypes.StringType{},
"role": basetypes.StringType{},
},
}}

func (r *TemplateResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_template"
}
Expand Down Expand Up @@ -234,11 +208,11 @@ func (r *TemplateResource) Schema(ctx context.Context, req resource.SchemaReques
Default: booldefault.StaticBool(true),
},
"acl": schema.SingleNestedAttribute{
MarkdownDescription: "Access control list for the template.",
Required: true,
MarkdownDescription: "Access control list for the template. Requires an enterprise Coder deployment. If null, ACL policies will not be added or removed by Terraform.",
Optional: true,
Attributes: map[string]schema.Attribute{
"users": permissionsAttribute,
"groups": permissionsAttribute,
"users": permissionAttribute,
"groups": permissionAttribute,
},
},
"versions": schema.ListNestedAttribute{
Expand Down Expand Up @@ -371,13 +345,22 @@ func (r *TemplateResource) Create(ctx context.Context, req resource.CreateReques
"id": templateResp.ID,
})

tflog.Trace(ctx, "updating template ACL")
err = client.UpdateTemplateACL(ctx, templateResp.ID, convertACLToRequest(data.ACL))
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to update template ACL: %s", err))
return
if !data.ACL.IsNull() {
tflog.Trace(ctx, "updating template ACL")
var acl ACL
resp.Diagnostics.Append(
data.ACL.As(ctx, &acl, basetypes.ObjectAsOptions{})...,
)
if resp.Diagnostics.HasError() {
return
}
err = client.UpdateTemplateACL(ctx, templateResp.ID, convertACLToRequest(acl))
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to create template ACL: %s", err))
return
}
tflog.Trace(ctx, "successfully updated template ACL")
}
tflog.Trace(ctx, "successfully updated template ACL")
}
if version.Active.ValueBool() {
tflog.Trace(ctx, "marking template version as active", map[string]any{
Expand Down Expand Up @@ -430,12 +413,22 @@ func (r *TemplateResource) Read(ctx context.Context, req resource.ReadRequest, r
data.AllowUserAutoStart = types.BoolValue(template.AllowUserAutostart)
data.AllowUserAutoStop = types.BoolValue(template.AllowUserAutostop)

acl, err := client.TemplateACL(ctx, templateID)
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to get template ACL: %s", err))
return
if !data.ACL.IsNull() {
tflog.Trace(ctx, "reading template ACL")
acl, err := client.TemplateACL(ctx, templateID)
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)
diag.Append(diag...)
if diag.HasError() {
return
}
data.ACL = aclObj
tflog.Trace(ctx, "read template ACL")
}
data.ACL = convertResponseToACL(acl)

for idx, version := range data.Versions {
versionID := version.ID.ValueUUID()
Expand Down Expand Up @@ -500,11 +493,20 @@ func (r *TemplateResource) Update(ctx context.Context, req resource.UpdateReques
DisableEveryoneGroupAccess: true,
})
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to update template: %s", err))
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to update template metadata: %s", err))
return
}
tflog.Trace(ctx, "successfully updated template metadata")
err = client.UpdateTemplateACL(ctx, templateID, convertACLToRequest(planState.ACL))
}

// If there's a change, and we're still managing ACL
if !planState.ACL.Equal(curState.ACL) && !planState.ACL.IsNull() {
var acl ACL
resp.Diagnostics.Append(planState.ACL.As(ctx, &acl, basetypes.ObjectAsOptions{})...)
if resp.Diagnostics.HasError() {
return
}
err := client.UpdateTemplateACL(ctx, templateID, convertACLToRequest(acl))
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to update template ACL: %s", err))
return
Expand Down Expand Up @@ -784,10 +786,7 @@ func newVersion(ctx context.Context, client *codersdk.Client, req newVersionRequ
return &versionResp, nil
}

func convertACLToRequest(permissions *ACL) codersdk.UpdateTemplateACL {
if permissions == nil {
return codersdk.UpdateTemplateACL{}
}
func convertACLToRequest(permissions ACL) codersdk.UpdateTemplateACL {
var userPerms = make(map[string]codersdk.TemplateRole)
for _, perm := range permissions.UserPermissions {
userPerms[perm.ID.ValueString()] = codersdk.TemplateRole(perm.Role.ValueString())
Expand All @@ -802,7 +801,7 @@ func convertACLToRequest(permissions *ACL) codersdk.UpdateTemplateACL {
}
}

func convertResponseToACL(acl codersdk.TemplateACL) *ACL {
func convertResponseToACL(acl codersdk.TemplateACL) ACL {
userPerms := make([]Permission, 0, len(acl.Users))
for _, user := range acl.Users {
userPerms = append(userPerms, Permission{
Expand All @@ -817,7 +816,7 @@ func convertResponseToACL(acl codersdk.TemplateACL) *ACL {
Role: types.StringValue(string(group.Role)),
})
}
return &ACL{
return ACL{
UserPermissions: userPerms,
GroupPermissions: groupPerms,
}
Expand Down
Loading

0 comments on commit 9d183b4

Please sign in to comment.