Skip to content

Commit 7937339

Browse files
committed
Add tlspc_plugin resource
1 parent 5d8941d commit 7937339

File tree

6 files changed

+352
-0
lines changed

6 files changed

+352
-0
lines changed

docs/resources/plugin.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
# generated by https://github.com/hashicorp/terraform-plugin-docs
3+
page_title: "tlspc_plugin Resource - tlspc"
4+
subcategory: ""
5+
description: |-
6+
7+
---
8+
9+
# tlspc_plugin (Resource)
10+
11+
12+
13+
14+
15+
<!-- schema generated by tfplugindocs -->
16+
## Schema
17+
18+
### Required
19+
20+
- `manifest` (String)
21+
- `type` (String)
22+
23+
### Read-Only
24+
25+
- `id` (String) The ID of this resource.

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go 1.22.7
44

55
require (
66
github.com/hashicorp/terraform-plugin-framework v1.12.0
7+
github.com/hashicorp/terraform-plugin-framework-jsontypes v0.2.0
78
github.com/hashicorp/terraform-plugin-go v0.24.0
89
github.com/hashicorp/terraform-plugin-log v0.9.0
910
github.com/hashicorp/terraform-plugin-testing v1.10.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ github.com/hashicorp/terraform-json v0.22.1 h1:xft84GZR0QzjPVWs4lRUwvTcPnegqlyS7
7878
github.com/hashicorp/terraform-json v0.22.1/go.mod h1:JbWSQCLFSXFFhg42T7l9iJwdGXBYV8fmmD6o/ML4p3A=
7979
github.com/hashicorp/terraform-plugin-framework v1.12.0 h1:7HKaueHPaikX5/7cbC1r9d1m12iYHY+FlNZEGxQ42CQ=
8080
github.com/hashicorp/terraform-plugin-framework v1.12.0/go.mod h1:N/IOQ2uYjW60Jp39Cp3mw7I/OpC/GfZ0385R0YibmkE=
81+
github.com/hashicorp/terraform-plugin-framework-jsontypes v0.2.0 h1:SJXL5FfJJm17554Kpt9jFXngdM6fXbnUnZ6iT2IeiYA=
82+
github.com/hashicorp/terraform-plugin-framework-jsontypes v0.2.0/go.mod h1:p0phD0IYhsu9bR4+6OetVvvH59I6LwjXGnTVEr8ox6E=
8183
github.com/hashicorp/terraform-plugin-go v0.24.0 h1:2WpHhginCdVhFIrWHxDEg6RBn3YaWzR2o6qUeIEat2U=
8284
github.com/hashicorp/terraform-plugin-go v0.24.0/go.mod h1:tUQ53lAsOyYSckFGEefGC5C8BAaO0ENqzFd3bQeuYQg=
8385
github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0=

internal/provider/plugin_resource.go

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
// Copyright (c) Venafi, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package provider
5+
6+
import (
7+
"context"
8+
"encoding/json"
9+
"fmt"
10+
11+
"terraform-provider-tlspc/internal/tlspc"
12+
13+
"github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes"
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+
)
19+
20+
var (
21+
_ resource.Resource = &pluginResource{}
22+
_ resource.ResourceWithConfigure = &pluginResource{}
23+
_ resource.ResourceWithImportState = &pluginResource{}
24+
)
25+
26+
type pluginResource struct {
27+
client *tlspc.Client
28+
}
29+
30+
func NewPluginResource() resource.Resource {
31+
return &pluginResource{}
32+
}
33+
34+
func (r *pluginResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
35+
resp.TypeName = req.ProviderTypeName + "_plugin"
36+
}
37+
38+
func (r *pluginResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
39+
resp.Schema = schema.Schema{
40+
Attributes: map[string]schema.Attribute{
41+
"id": schema.StringAttribute{
42+
Computed: true,
43+
},
44+
"type": schema.StringAttribute{
45+
Required: true,
46+
},
47+
"manifest": schema.StringAttribute{
48+
Required: true,
49+
CustomType: jsontypes.NormalizedType{},
50+
},
51+
},
52+
}
53+
}
54+
55+
func (r *pluginResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
56+
if req.ProviderData == nil {
57+
return
58+
}
59+
60+
client, ok := req.ProviderData.(*tlspc.Client)
61+
62+
if !ok {
63+
resp.Diagnostics.AddError(
64+
"Unexpected Data Source Configure Type",
65+
fmt.Sprintf("Expected *tlspc.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
66+
)
67+
68+
return
69+
}
70+
71+
r.client = client
72+
}
73+
74+
type pluginResourceModel struct {
75+
ID types.String `tfsdk:"id"`
76+
Type types.String `tfsdk:"type"`
77+
Manifest jsontypes.Normalized `tfsdk:"manifest"`
78+
}
79+
80+
func (r *pluginResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
81+
var plan pluginResourceModel
82+
diags := req.Plan.Get(ctx, &plan)
83+
resp.Diagnostics.Append(diags...)
84+
if resp.Diagnostics.HasError() {
85+
return
86+
}
87+
88+
var manifest any
89+
err := json.Unmarshal([]byte(plan.Manifest.ValueString()), &manifest)
90+
if err != nil {
91+
resp.Diagnostics.AddError(
92+
"Error creating plugin",
93+
"Could not create team, invalid manifest: "+err.Error(),
94+
)
95+
return
96+
}
97+
98+
plugin := tlspc.Plugin{
99+
ID: plan.ID.ValueString(),
100+
Type: plan.Type.ValueString(),
101+
Manifest: manifest,
102+
}
103+
104+
created, err := r.client.CreatePlugin(plugin)
105+
if err != nil {
106+
resp.Diagnostics.AddError(
107+
"Error creating plugin",
108+
"Could not create plugin, unexpected error: "+err.Error(),
109+
)
110+
return
111+
}
112+
plan.ID = types.StringValue(created.ID)
113+
diags = resp.State.Set(ctx, plan)
114+
resp.Diagnostics.Append(diags...)
115+
}
116+
117+
func (r *pluginResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
118+
var state pluginResourceModel
119+
120+
diags := req.State.Get(ctx, &state)
121+
resp.Diagnostics.Append(diags...)
122+
if resp.Diagnostics.HasError() {
123+
return
124+
}
125+
126+
plugin, err := r.client.GetPlugin(state.ID.ValueString())
127+
if err != nil {
128+
resp.Diagnostics.AddError(
129+
"Error Reading Plugin",
130+
"Could not read plugin ID "+state.ID.ValueString()+": "+err.Error(),
131+
)
132+
return
133+
}
134+
135+
state.ID = types.StringValue(plugin.ID)
136+
state.Type = types.StringValue(plugin.Type)
137+
stateManifest, err := json.Marshal(plugin.Manifest)
138+
if err != nil {
139+
resp.Diagnostics.AddError(
140+
"Error Reading Plugin",
141+
"Could not read plugin manifest: "+err.Error(),
142+
)
143+
return
144+
}
145+
state.Manifest = jsontypes.NewNormalizedValue(string(stateManifest))
146+
147+
diags = resp.State.Set(ctx, state)
148+
resp.Diagnostics.Append(diags...)
149+
}
150+
151+
func (r *pluginResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
152+
var state, plan pluginResourceModel
153+
154+
diags := req.State.Get(ctx, &state)
155+
resp.Diagnostics.Append(diags...)
156+
if resp.Diagnostics.HasError() {
157+
return
158+
}
159+
diags = req.Plan.Get(ctx, &plan)
160+
resp.Diagnostics.Append(diags...)
161+
if resp.Diagnostics.HasError() {
162+
return
163+
}
164+
165+
var manifest any
166+
err := json.Unmarshal([]byte(plan.Manifest.ValueString()), &manifest)
167+
if err != nil {
168+
resp.Diagnostics.AddError(
169+
"Error Reading Plugin",
170+
"Could not read plugin manifest: "+err.Error(),
171+
)
172+
return
173+
}
174+
plugin := tlspc.Plugin{
175+
ID: state.ID.ValueString(),
176+
Type: plan.Type.ValueString(),
177+
Manifest: manifest,
178+
}
179+
err = r.client.UpdatePlugin(plugin)
180+
if err != nil {
181+
resp.Diagnostics.AddError(
182+
"Error updating Plugin",
183+
"Could not update plugin: "+err.Error(),
184+
)
185+
return
186+
}
187+
188+
plan.ID = state.ID
189+
diags = resp.State.Set(ctx, plan)
190+
resp.Diagnostics.Append(diags...)
191+
}
192+
193+
func (r *pluginResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
194+
var state pluginResourceModel
195+
196+
diags := req.State.Get(ctx, &state)
197+
resp.Diagnostics.Append(diags...)
198+
if resp.Diagnostics.HasError() {
199+
return
200+
}
201+
202+
err := r.client.DeletePlugin(state.ID.ValueString())
203+
if err != nil {
204+
resp.Diagnostics.AddError(
205+
"Error Deleting Plugin",
206+
"Could not delete plugin ID "+state.ID.ValueString()+": "+err.Error(),
207+
)
208+
return
209+
}
210+
}
211+
212+
func (r *pluginResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
213+
// Retrieve import ID and save to id attribute
214+
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
215+
}

internal/provider/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ func (p *tlspcProvider) Resources(ctx context.Context) []func() resource.Resourc
8585
NewTeamResource,
8686
NewServiceAccountResource,
8787
NewRegistryAccountResource,
88+
NewPluginResource,
8889
}
8990
}
9091

internal/tlspc/tlspc.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,3 +397,111 @@ func (c *Client) DeleteServiceAccount(id string) error {
397397

398398
return nil
399399
}
400+
401+
type Plugin struct {
402+
ID string `json:"id,omitempty"`
403+
Type string `json:"pluginType"`
404+
Manifest any `json:"manifest"`
405+
}
406+
407+
type plugins struct {
408+
Plugins []Plugin `json:"plugins"`
409+
}
410+
411+
func (c *Client) CreatePlugin(p Plugin) (*Plugin, error) {
412+
path := c.Path(`%s/v1/plugins`)
413+
414+
body, err := json.Marshal(p)
415+
if err != nil {
416+
return nil, fmt.Errorf("Error encoding request: %s", err)
417+
}
418+
419+
resp, err := c.Post(path, body)
420+
if err != nil {
421+
return nil, fmt.Errorf("Error posting request: %s", err)
422+
}
423+
424+
respBody, err := io.ReadAll(resp.Body)
425+
if err != nil {
426+
return nil, fmt.Errorf("Error reading response body: %s", err)
427+
}
428+
var created plugins
429+
err = json.Unmarshal(respBody, &created)
430+
if err != nil {
431+
return nil, fmt.Errorf("Error decoding response: %s", string(respBody))
432+
}
433+
if len(created.Plugins) != 1 {
434+
return nil, fmt.Errorf("Unexpected number of plugins returned (%d): %s", len(created.Plugins), string(respBody))
435+
}
436+
if created.Plugins[0].ID == "" {
437+
return nil, fmt.Errorf("Didn't create a plugin; response was: %s", string(respBody))
438+
}
439+
440+
return &created.Plugins[0], nil
441+
}
442+
443+
func (c *Client) GetPlugin(id string) (*Plugin, error) {
444+
path := c.Path(`%s/v1/plugins/` + id)
445+
446+
resp, err := c.Get(path)
447+
if err != nil {
448+
return nil, fmt.Errorf("Error getting plugin: %s", err)
449+
}
450+
451+
respBody, err := io.ReadAll(resp.Body)
452+
if err != nil {
453+
return nil, fmt.Errorf("Error reading response body: %s", err)
454+
}
455+
var plugin Plugin
456+
err = json.Unmarshal(respBody, &plugin)
457+
if err != nil {
458+
return nil, fmt.Errorf("Error decoding response: %s", string(respBody))
459+
}
460+
if plugin.ID == "" {
461+
return nil, fmt.Errorf("Didn't find a Plugin; response was: %s", string(respBody))
462+
}
463+
464+
return &plugin, nil
465+
}
466+
467+
func (c *Client) UpdatePlugin(p Plugin) error {
468+
id := p.ID
469+
if id == "" {
470+
return errors.New("Empty ID")
471+
}
472+
p.ID = ""
473+
path := c.Path(`%s/v1/plugins/` + id)
474+
475+
body, err := json.Marshal(p)
476+
if err != nil {
477+
return fmt.Errorf("Error encoding request: %s", err)
478+
}
479+
480+
resp, err := c.Patch(path, body)
481+
if err != nil {
482+
return fmt.Errorf("Error patching request: %s", err)
483+
}
484+
if resp.StatusCode != http.StatusOK {
485+
// returning an error here anyway, no more information if we couldn't read the body
486+
respBody, _ := io.ReadAll(resp.Body)
487+
return fmt.Errorf("Failed to update Plugin; response was: %s", string(respBody))
488+
}
489+
490+
return nil
491+
}
492+
493+
func (c *Client) DeletePlugin(id string) error {
494+
path := c.Path(`%s/v1/plugins/` + id)
495+
496+
resp, err := c.Delete(path, nil)
497+
if err != nil {
498+
return fmt.Errorf("Error with delete request: %s", err)
499+
}
500+
if resp.StatusCode != http.StatusNoContent {
501+
// returning an error here anyway, no more information if we couldn't read the body
502+
respBody, _ := io.ReadAll(resp.Body)
503+
return fmt.Errorf("Failed to delete Plugin; response was: %s", string(respBody))
504+
}
505+
506+
return nil
507+
}

0 commit comments

Comments
 (0)