Skip to content

Commit c35464d

Browse files
authored
Organization resource support (#28)
* Organization resource support
1 parent a0450bc commit c35464d

File tree

6 files changed

+380
-3
lines changed

6 files changed

+380
-3
lines changed

README.md

+6
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ provider "terrakube" {
3838
token = "(PERSONAL ACCESS TOKEN OR TEAM TOKEN)"
3939
}
4040
41+
resource "terrakube_organization" "org_sample" {
42+
name = "unique-org-name"
43+
description = "my super description"
44+
execution_mode = "local"
45+
}
46+
4147
data "terrakube_organization" "org" {
4248
name = "simple"
4349
}

docs/resources/organization.md

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
# generated by https://github.com/hashicorp/terraform-plugin-docs
3+
page_title: "terrakube_organization Resource - terraform-provider-terrakube"
4+
subcategory: ""
5+
description: |-
6+
7+
---
8+
9+
# terrakube_organization (Resource)
10+
11+
12+
13+
14+
15+
<!-- schema generated by tfplugindocs -->
16+
## Schema
17+
18+
### Required
19+
20+
- `description` (String) Organization description
21+
- `execution_mode` (String) Select default execution mode for the organization (remote or local)
22+
- `name` (String) Organization name
23+
24+
### Read-Only
25+
26+
- `id` (String) Organization Id
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
resource "terrakube_organization" "organization" {
2+
name = "sample-organization"
3+
description = "sample organization description"
4+
executionModule = "remote"
5+
}

internal/client/client.go

+5-3
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ import (
55
)
66

77
type OrganizationEntity struct {
8-
ID string `jsonapi:"primary,organization"`
9-
Name string `jsonapi:"attr,name"`
10-
Description string `jsonapi:"attr,description"`
8+
ID string `jsonapi:"primary,organization"`
9+
Name string `jsonapi:"attr,name"`
10+
Description string `jsonapi:"attr,description"`
11+
ExecutionMode string `jsonapi:"attr,executionMode"`
12+
Disabled bool `jsonapi:"attr,disabled"`
1113
}
1214

1315
type TeamEntity struct {
+337
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,337 @@
1+
package provider
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"crypto/tls"
7+
"fmt"
8+
"github.com/google/jsonapi"
9+
"io"
10+
"net/http"
11+
"strings"
12+
"terraform-provider-terrakube/internal/client"
13+
14+
"github.com/hashicorp/terraform-plugin-framework/path"
15+
"github.com/hashicorp/terraform-plugin-framework/resource"
16+
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
17+
"github.com/hashicorp/terraform-plugin-framework/types"
18+
"github.com/hashicorp/terraform-plugin-log/tflog"
19+
)
20+
21+
// Ensure provider defined types fully satisfy framework interfaces.
22+
var _ resource.Resource = &OrganizationResource{}
23+
var _ resource.ResourceWithImportState = &OrganizationResource{}
24+
25+
type OrganizationResource struct {
26+
client *http.Client
27+
endpoint string
28+
token string
29+
}
30+
31+
type OrganizationResourceModel struct {
32+
ID types.String `tfsdk:"id"`
33+
Name types.String `tfsdk:"name"`
34+
Description types.String `tfsdk:"description"`
35+
ExecutionMode types.String `tfsdk:"execution_mode"`
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+
Attributes: map[string]schema.Attribute{
49+
"id": schema.StringAttribute{
50+
Computed: true,
51+
Description: "Organization Id",
52+
},
53+
"name": schema.StringAttribute{
54+
Required: true,
55+
Description: "Organization name",
56+
},
57+
"description": schema.StringAttribute{
58+
Required: true,
59+
Description: "Organization description",
60+
},
61+
"execution_mode": schema.StringAttribute{
62+
Required: true,
63+
Description: "Select default execution mode for the organization (remote or local)",
64+
},
65+
},
66+
}
67+
}
68+
69+
func (r *OrganizationResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
70+
if req.ProviderData == nil {
71+
return
72+
}
73+
74+
providerData, ok := req.ProviderData.(*TerrakubeConnectionData)
75+
if !ok {
76+
resp.Diagnostics.AddError(
77+
"Unexpected Organization Resource Configure Type",
78+
fmt.Sprintf("Expected *TerrakubeConnectionData, got: %T. Please report this issue to the provider developers.", req.ProviderData),
79+
)
80+
81+
return
82+
}
83+
84+
if providerData.InsecureHttpClient {
85+
if custom, ok := http.DefaultTransport.(*http.Transport); ok {
86+
customTransport := custom.Clone()
87+
customTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
88+
r.client = &http.Client{Transport: customTransport}
89+
} else {
90+
r.client = &http.Client{}
91+
}
92+
} else {
93+
r.client = &http.Client{}
94+
}
95+
96+
r.endpoint = providerData.Endpoint
97+
r.token = providerData.Token
98+
99+
tflog.Debug(ctx, "Configuring Organization resource", map[string]any{"success": true})
100+
}
101+
102+
func (r *OrganizationResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
103+
var plan OrganizationResourceModel
104+
105+
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
106+
107+
if resp.Diagnostics.HasError() {
108+
return
109+
}
110+
111+
bodyRequest := &client.OrganizationEntity{
112+
Name: plan.Name.ValueString(),
113+
Description: plan.Description.ValueString(),
114+
ExecutionMode: plan.ExecutionMode.ValueString(),
115+
}
116+
117+
var out = new(bytes.Buffer)
118+
err := jsonapi.MarshalPayload(out, bodyRequest)
119+
120+
if err != nil {
121+
resp.Diagnostics.AddError("Unable to marshal payload", fmt.Sprintf("Unable to marshal payload: %s", err))
122+
return
123+
}
124+
125+
organizationRequest, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/api/v1/organization", r.endpoint), strings.NewReader(out.String()))
126+
organizationRequest.Header.Add("Authorization", fmt.Sprintf("Bearer %s", r.token))
127+
organizationRequest.Header.Add("Content-Type", "application/vnd.api+json")
128+
if err != nil {
129+
resp.Diagnostics.AddError("Error creating organization resource request", fmt.Sprintf("Error creating organization resource request: %s", err))
130+
return
131+
}
132+
133+
organizationResponse, err := r.client.Do(organizationRequest)
134+
if err != nil {
135+
resp.Diagnostics.AddError("Error executing organization resource request", fmt.Sprintf("Error executing organization resource request: %s", err))
136+
return
137+
}
138+
139+
bodyResponse, err := io.ReadAll(organizationResponse.Body)
140+
if err != nil {
141+
tflog.Error(ctx, "Error reading organization resource response")
142+
}
143+
newOrganization := &client.OrganizationEntity{}
144+
145+
err = jsonapi.UnmarshalPayload(strings.NewReader(string(bodyResponse)), newOrganization)
146+
147+
if err != nil {
148+
resp.Diagnostics.AddError("Error unmarshal payload response", fmt.Sprintf("Error unmarshal payload response: %s", err))
149+
return
150+
}
151+
152+
tflog.Info(ctx, "Body Response", map[string]any{"bodyResponse": string(bodyResponse)})
153+
154+
plan.ID = types.StringValue(newOrganization.ID)
155+
plan.Name = types.StringValue(newOrganization.Name)
156+
plan.Description = types.StringValue(newOrganization.Description)
157+
plan.ExecutionMode = types.StringValue(newOrganization.ExecutionMode)
158+
159+
tflog.Info(ctx, "Organization Resource Created", map[string]any{"success": true})
160+
161+
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
162+
}
163+
164+
func (r *OrganizationResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
165+
var state OrganizationResourceModel
166+
diags := req.State.Get(ctx, &state)
167+
resp.Diagnostics.Append(diags...)
168+
if resp.Diagnostics.HasError() {
169+
return
170+
}
171+
172+
organizationRequest, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/v1/organization/%s", r.endpoint, state.ID.ValueString()), nil)
173+
organizationRequest.Header.Add("Authorization", fmt.Sprintf("Bearer %s", r.token))
174+
organizationRequest.Header.Add("Content-Type", "application/vnd.api+json")
175+
if err != nil {
176+
resp.Diagnostics.AddError("Error creating organization resource request", fmt.Sprintf("Error creating organization resource request: %s", err))
177+
return
178+
}
179+
180+
organizationResponse, err := r.client.Do(organizationRequest)
181+
if err != nil {
182+
resp.Diagnostics.AddError("Error executing organization resource request", fmt.Sprintf("Error executing organization resource request: %s", err))
183+
return
184+
}
185+
186+
bodyResponse, err := io.ReadAll(organizationResponse.Body)
187+
if err != nil {
188+
tflog.Error(ctx, "Error reading organization resource response")
189+
}
190+
organization := &client.OrganizationEntity{}
191+
192+
tflog.Info(ctx, "Body Response", map[string]any{"bodyResponse": string(bodyResponse)})
193+
err = jsonapi.UnmarshalPayload(strings.NewReader(string(bodyResponse)), organization)
194+
195+
if err != nil {
196+
resp.Diagnostics.AddError("Error unmarshal payload response", fmt.Sprintf("Error unmarshal payload response: %s", err))
197+
return
198+
}
199+
200+
tflog.Info(ctx, "Body Response", map[string]any{"bodyResponse": string(bodyResponse)})
201+
202+
state.Description = types.StringValue(organization.Description)
203+
state.ExecutionMode = types.StringValue(organization.ExecutionMode)
204+
205+
// Set refreshed state
206+
diags = resp.State.Set(ctx, &state)
207+
resp.Diagnostics.Append(diags...)
208+
if resp.Diagnostics.HasError() {
209+
return
210+
}
211+
212+
tflog.Info(ctx, "Organization Resource reading", map[string]any{"success": true})
213+
}
214+
215+
func (r *OrganizationResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
216+
// Retrieve values from plan
217+
var plan OrganizationResourceModel
218+
var state OrganizationResourceModel
219+
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
220+
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
221+
if resp.Diagnostics.HasError() {
222+
return
223+
}
224+
225+
bodyRequest := &client.OrganizationEntity{
226+
Description: plan.Description.ValueString(),
227+
ExecutionMode: plan.ExecutionMode.ValueString(),
228+
Name: plan.Name.ValueString(),
229+
ID: state.ID.ValueString(),
230+
}
231+
232+
var out = new(bytes.Buffer)
233+
err := jsonapi.MarshalPayload(out, bodyRequest)
234+
235+
if err != nil {
236+
resp.Diagnostics.AddError("Unable to marshal payload", fmt.Sprintf("Unable to marshal payload: %s", err))
237+
return
238+
}
239+
240+
organizationRequest, err := http.NewRequest(http.MethodPatch, fmt.Sprintf("%s/api/v1/organization/%s", r.endpoint, state.ID.ValueString()), strings.NewReader(out.String()))
241+
organizationRequest.Header.Add("Authorization", fmt.Sprintf("Bearer %s", r.token))
242+
organizationRequest.Header.Add("Content-Type", "application/vnd.api+json")
243+
if err != nil {
244+
resp.Diagnostics.AddError("Error creating organization resource request", fmt.Sprintf("Error creating organization resource request: %s", err))
245+
return
246+
}
247+
248+
organizationResponse, err := r.client.Do(organizationRequest)
249+
if err != nil {
250+
resp.Diagnostics.AddError("Error organization resource request", fmt.Sprintf("Error executing organization resource request: %s", err))
251+
return
252+
}
253+
254+
bodyResponse, err := io.ReadAll(organizationResponse.Body)
255+
if err != nil {
256+
tflog.Error(ctx, "Error reading organization resource response")
257+
}
258+
259+
tflog.Info(ctx, "Body Response", map[string]any{"success": string(bodyResponse)})
260+
261+
organizationRequest, err = http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/v1/organization/%s", r.endpoint, state.ID.ValueString()), nil)
262+
organizationRequest.Header.Add("Authorization", fmt.Sprintf("Bearer %s", r.token))
263+
organizationRequest.Header.Add("Content-Type", "application/vnd.api+json")
264+
if err != nil {
265+
resp.Diagnostics.AddError("Error creating organization resource request", fmt.Sprintf("Error creating organization resource request: %s", err))
266+
return
267+
}
268+
269+
organizationResponse, err = r.client.Do(organizationRequest)
270+
if err != nil {
271+
resp.Diagnostics.AddError("Error executing organization resource request", fmt.Sprintf("Error executing organizationñ resource request: %s", err))
272+
return
273+
}
274+
275+
bodyResponse, err = io.ReadAll(organizationResponse.Body)
276+
if err != nil {
277+
resp.Diagnostics.AddError("Error reading organization resource response body", fmt.Sprintf("Error reading organization resource response body: %s", err))
278+
}
279+
280+
tflog.Info(ctx, "Body Response", map[string]any{"bodyResponse": string(bodyResponse)})
281+
282+
organization := &client.OrganizationEntity{}
283+
err = jsonapi.UnmarshalPayload(strings.NewReader(string(bodyResponse)), organization)
284+
285+
if err != nil {
286+
resp.Diagnostics.AddError("Error unmarshal payload response", fmt.Sprintf("Error unmarshal payload response: %s", err))
287+
return
288+
}
289+
290+
plan.ID = types.StringValue(state.ID.ValueString())
291+
plan.Name = types.StringValue(organization.Name)
292+
plan.Description = types.StringValue(organization.Description)
293+
plan.ExecutionMode = types.StringValue(organization.ExecutionMode)
294+
295+
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
296+
}
297+
298+
func (r *OrganizationResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
299+
var data OrganizationResourceModel
300+
301+
// Read Terraform prior state data into the model
302+
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
303+
304+
if resp.Diagnostics.HasError() {
305+
return
306+
}
307+
308+
bodyRequest := &client.OrganizationEntity{
309+
Disabled: true,
310+
ID: data.ID.ValueString(),
311+
}
312+
313+
var out = new(bytes.Buffer)
314+
err := jsonapi.MarshalPayload(out, bodyRequest)
315+
316+
if err != nil {
317+
resp.Diagnostics.AddError("Unable to marshal payload", fmt.Sprintf("Unable to marshal payload: %s", err))
318+
return
319+
}
320+
321+
reqOrg, err := http.NewRequest(http.MethodPatch, fmt.Sprintf("%s/api/v1/organization/%s", r.endpoint, data.ID.ValueString()), strings.NewReader(out.String()))
322+
reqOrg.Header.Add("Authorization", fmt.Sprintf("Bearer %s", r.token))
323+
if err != nil {
324+
resp.Diagnostics.AddError("Error creating organization resource request", fmt.Sprintf("Error creating organization resource request: %s", err))
325+
return
326+
}
327+
328+
_, err = r.client.Do(reqOrg)
329+
if err != nil {
330+
resp.Diagnostics.AddError("Error executing organization resource request", fmt.Sprintf("Error executing organization resource request: %s", err))
331+
return
332+
}
333+
}
334+
335+
func (r *OrganizationResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
336+
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
337+
}

0 commit comments

Comments
 (0)