Skip to content

Commit 1ef2a69

Browse files
authored
feat: add organization resource (#131)
1 parent 03a98cd commit 1ef2a69

File tree

7 files changed

+427
-5
lines changed

7 files changed

+427
-5
lines changed

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
default: testacc
22

33
fmt:
4+
go fmt ./...
45
terraform fmt -recursive
56

7+
vet:
8+
go vet ./...
9+
610
gen:
711
go generate ./...
812

docs/resources/organization.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
---
2+
# generated by https://github.com/hashicorp/terraform-plugin-docs
3+
page_title: "coderd_organization Resource - terraform-provider-coderd"
4+
subcategory: ""
5+
description: |-
6+
An organization on the Coder deployment
7+
---
8+
9+
# coderd_organization (Resource)
10+
11+
An organization on the Coder deployment
12+
13+
14+
15+
<!-- schema generated by tfplugindocs -->
16+
## Schema
17+
18+
### Required
19+
20+
- `name` (String) Name of the organization.
21+
22+
### Optional
23+
24+
- `description` (String)
25+
- `display_name` (String) Display name of the organization. Defaults to name.
26+
- `icon` (String)
27+
28+
### Read-Only
29+
30+
- `id` (String) Organization ID
31+
32+
## Import
33+
34+
Import is supported using the following syntax:
35+
36+
```shell
37+
# Organizations can be imported by their name
38+
terraform import coderd_organization.our_org our_org
39+
```
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Organizations can be imported by their name
2+
terraform import coderd_organization.our_org our_org
Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
package provider
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/coder/coder/v2/codersdk"
8+
"github.com/coder/terraform-provider-coderd/internal/codersdkvalidator"
9+
"github.com/hashicorp/terraform-plugin-framework/path"
10+
"github.com/hashicorp/terraform-plugin-framework/resource"
11+
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
12+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
13+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
14+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
15+
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
16+
"github.com/hashicorp/terraform-plugin-framework/types"
17+
"github.com/hashicorp/terraform-plugin-log/tflog"
18+
)
19+
20+
// Ensure provider defined types fully satisfy framework interfaces.
21+
var _ resource.Resource = &OrganizationResource{}
22+
var _ resource.ResourceWithImportState = &OrganizationResource{}
23+
24+
type OrganizationResource struct {
25+
*CoderdProviderData
26+
}
27+
28+
// OrganizationResourceModel describes the resource data model.
29+
type OrganizationResourceModel struct {
30+
ID UUID `tfsdk:"id"`
31+
32+
Name types.String `tfsdk:"name"`
33+
DisplayName types.String `tfsdk:"display_name"`
34+
Description types.String `tfsdk:"description"`
35+
Icon types.String `tfsdk:"icon"`
36+
}
37+
38+
func NewOrganizationResource() resource.Resource {
39+
return &OrganizationResource{}
40+
}
41+
42+
func (r *OrganizationResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
43+
resp.TypeName = req.ProviderTypeName + "_organization"
44+
}
45+
46+
func (r *OrganizationResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
47+
resp.Schema = schema.Schema{
48+
MarkdownDescription: "An organization on the Coder deployment",
49+
50+
Attributes: map[string]schema.Attribute{
51+
"id": schema.StringAttribute{
52+
CustomType: UUIDType,
53+
Computed: true,
54+
MarkdownDescription: "Organization ID",
55+
PlanModifiers: []planmodifier.String{
56+
stringplanmodifier.UseStateForUnknown(),
57+
},
58+
},
59+
"name": schema.StringAttribute{
60+
MarkdownDescription: "Name of the organization.",
61+
Required: true,
62+
Validators: []validator.String{
63+
codersdkvalidator.Name(),
64+
},
65+
},
66+
"display_name": schema.StringAttribute{
67+
MarkdownDescription: "Display name of the organization. Defaults to name.",
68+
Computed: true,
69+
Optional: true,
70+
Default: stringdefault.StaticString(""),
71+
Validators: []validator.String{
72+
codersdkvalidator.DisplayName(),
73+
},
74+
},
75+
"description": schema.StringAttribute{
76+
Optional: true,
77+
Computed: true,
78+
Default: stringdefault.StaticString(""),
79+
},
80+
"icon": schema.StringAttribute{
81+
Optional: true,
82+
Computed: true,
83+
Default: stringdefault.StaticString(""),
84+
},
85+
},
86+
}
87+
}
88+
89+
func (r *OrganizationResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
90+
// Prevent panic if the provider has not been configured.
91+
if req.ProviderData == nil {
92+
return
93+
}
94+
95+
data, ok := req.ProviderData.(*CoderdProviderData)
96+
97+
if !ok {
98+
resp.Diagnostics.AddError(
99+
"Unable to configure provider data",
100+
fmt.Sprintf("Expected *CoderdProviderData, got: %T. Please report this issue to the provider developers.", req.ProviderData),
101+
)
102+
103+
return
104+
}
105+
106+
r.CoderdProviderData = data
107+
}
108+
109+
func (r *OrganizationResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
110+
// Read Terraform prior state data into the model
111+
var data OrganizationResourceModel
112+
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
113+
if resp.Diagnostics.HasError() {
114+
return
115+
}
116+
117+
var org codersdk.Organization
118+
var err error
119+
if data.ID.IsNull() {
120+
orgName := data.Name.ValueString()
121+
org, err = r.Client.OrganizationByName(ctx, orgName)
122+
if err != nil {
123+
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to get organization by name, got error: %s", err))
124+
return
125+
}
126+
data.ID = UUIDValue(org.ID)
127+
} else {
128+
orgID := data.ID.ValueUUID()
129+
org, err = r.Client.Organization(ctx, orgID)
130+
if err != nil {
131+
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to get organization by ID, got error: %s", err))
132+
return
133+
}
134+
}
135+
136+
// We've fetched the organization ID from state, and the latest values for
137+
// everything else from the backend. Ensure that any mutable data is synced
138+
// with the backend.
139+
data.Name = types.StringValue(org.Name)
140+
data.DisplayName = types.StringValue(org.DisplayName)
141+
data.Description = types.StringValue(org.Description)
142+
data.Icon = types.StringValue(org.Icon)
143+
144+
// Save updated data into Terraform state
145+
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
146+
}
147+
148+
func (r *OrganizationResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
149+
// Read Terraform plan data into the model
150+
var data OrganizationResourceModel
151+
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
152+
if resp.Diagnostics.HasError() {
153+
return
154+
}
155+
156+
tflog.Trace(ctx, "creating organization", map[string]any{
157+
"id": data.ID.ValueUUID(),
158+
"name": data.Name.ValueString(),
159+
"display_name": data.DisplayName.ValueString(),
160+
"description": data.Description.ValueString(),
161+
"icon": data.Icon.ValueString(),
162+
})
163+
org, err := r.Client.CreateOrganization(ctx, codersdk.CreateOrganizationRequest{
164+
Name: data.Name.ValueString(),
165+
DisplayName: data.DisplayName.ValueString(),
166+
Description: data.Description.ValueString(),
167+
Icon: data.Icon.ValueString(),
168+
})
169+
if err != nil {
170+
resp.Diagnostics.AddError("Failed to create organization", err.Error())
171+
return
172+
}
173+
tflog.Trace(ctx, "successfully created organization", map[string]any{
174+
"id": org.ID,
175+
"name": org.Name,
176+
"display_name": org.DisplayName,
177+
"description": org.Description,
178+
"icon": org.Icon,
179+
})
180+
// Fill in `ID` since it must be "computed".
181+
data.ID = UUIDValue(org.ID)
182+
// We also fill in `DisplayName`, since it's optional but the backend will
183+
// default it.
184+
data.DisplayName = types.StringValue(org.DisplayName)
185+
186+
// Save data into Terraform state
187+
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
188+
}
189+
190+
func (r *OrganizationResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
191+
// Read Terraform plan data into the model
192+
var data OrganizationResourceModel
193+
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
194+
if resp.Diagnostics.HasError() {
195+
return
196+
}
197+
198+
orgID := data.ID.ValueUUID()
199+
200+
// Update the organization metadata
201+
tflog.Trace(ctx, "updating organization", map[string]any{
202+
"id": orgID,
203+
"new_name": data.Name.ValueString(),
204+
"new_display_name": data.DisplayName.ValueString(),
205+
"new_description": data.Description.ValueString(),
206+
"new_icon": data.Icon.ValueString(),
207+
})
208+
org, err := r.Client.UpdateOrganization(ctx, orgID.String(), codersdk.UpdateOrganizationRequest{
209+
Name: data.Name.ValueString(),
210+
DisplayName: data.DisplayName.ValueString(),
211+
Description: data.Description.ValueStringPointer(),
212+
Icon: data.Icon.ValueStringPointer(),
213+
})
214+
if err != nil {
215+
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update organization %s, got error: %s", orgID, err))
216+
return
217+
}
218+
tflog.Trace(ctx, "successfully updated organization", map[string]any{
219+
"id": orgID,
220+
"name": org.Name,
221+
"display_name": org.DisplayName,
222+
"description": org.Description,
223+
"icon": org.Icon,
224+
})
225+
226+
// Save updated data into Terraform state
227+
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
228+
}
229+
230+
func (r *OrganizationResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
231+
// Read Terraform prior state data into the model
232+
var data OrganizationResourceModel
233+
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
234+
if resp.Diagnostics.HasError() {
235+
return
236+
}
237+
238+
orgID := data.ID.ValueUUID()
239+
240+
tflog.Trace(ctx, "deleting organization", map[string]any{
241+
"id": orgID,
242+
"name": data.Name.ValueString(),
243+
})
244+
err := r.Client.DeleteOrganization(ctx, orgID.String())
245+
if err != nil {
246+
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete organization %s, got error: %s", orgID, err))
247+
return
248+
}
249+
tflog.Trace(ctx, "successfully deleted organization", map[string]any{
250+
"id": orgID,
251+
"name": data.Name.ValueString(),
252+
})
253+
254+
// Read Terraform prior state data into the model
255+
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
256+
}
257+
258+
func (r *OrganizationResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
259+
// Terraform will eventually `Read` in the rest of the fields after we have
260+
// set the `name` attribute.
261+
resource.ImportStatePassthroughID(ctx, path.Root("name"), req, resp)
262+
}

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