Skip to content

Commit 841c66c

Browse files
committed
Implement resource_tfe_hyok_configuration.go
1 parent 624c9ce commit 841c66c

File tree

2 files changed

+370
-0
lines changed

2 files changed

+370
-0
lines changed

internal/provider/provider_next.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ func (p *frameworkProvider) Resources(ctx context.Context) []func() resource.Res
172172
NewGCPOIDCConfigurationResource,
173173
NewAzureOIDCConfigurationResource,
174174
NewVaultOIDCConfigurationResource,
175+
NewHYOKConfigurationResource,
175176
}
176177
}
177178

Lines changed: 369 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,369 @@
1+
// // Copyright (c) HashiCorp, Inc.
2+
// // SPDX-License-Identifier: MPL-2.0
3+
4+
package provider
5+
6+
import (
7+
"context"
8+
"errors"
9+
"fmt"
10+
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
11+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
12+
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
13+
14+
tfe "github.com/hashicorp/go-tfe"
15+
"github.com/hashicorp/terraform-plugin-framework/path"
16+
"github.com/hashicorp/terraform-plugin-framework/resource"
17+
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
18+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
19+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
20+
"github.com/hashicorp/terraform-plugin-framework/types"
21+
"github.com/hashicorp/terraform-plugin-log/tflog"
22+
)
23+
24+
var (
25+
_ resource.ResourceWithConfigure = &resourceTFEHYOKConfiguration{}
26+
_ resource.ResourceWithImportState = &resourceTFEHYOKConfiguration{}
27+
)
28+
29+
func NewHYOKConfigurationResource() resource.Resource {
30+
return &resourceTFEHYOKConfiguration{}
31+
}
32+
33+
type resourceTFEHYOKConfiguration struct {
34+
config ConfiguredClient
35+
}
36+
37+
type modelTFEHYOKConfiguration struct {
38+
ID types.String `tfsdk:"id"`
39+
Name types.String `tfsdk:"name"`
40+
KEKID types.String `tfsdk:"kek_id"`
41+
KMSOptions *modelTFEKMSOptions `tfsdk:"kms_options"`
42+
43+
AWSOIDCConfigurationID types.String `tfsdk:"aws_oidc_configuration_id"`
44+
GCPOIDCConfigurationID types.String `tfsdk:"gcp_oidc_configuration_id"`
45+
VaultOIDCConfigurationID types.String `tfsdk:"vault_oidc_configuration_id"`
46+
AzureOIDCConfigurationID types.String `tfsdk:"azure_oidc_configuration_id"`
47+
48+
AgentPoolID types.String `tfsdk:"agent_pool_id"`
49+
Organization types.String `tfsdk:"organization"`
50+
}
51+
52+
type modelTFEKMSOptions struct {
53+
KeyRegion types.String `tfsdk:"key_region"`
54+
KeyLocation types.String `tfsdk:"key_location"`
55+
KeyRingID types.String `tfsdk:"key_ring_id"`
56+
}
57+
58+
func (r *resourceTFEHYOKConfiguration) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
59+
// Prevent panic if the provider has not been configured.
60+
if req.ProviderData == nil {
61+
return
62+
}
63+
64+
client, ok := req.ProviderData.(ConfiguredClient)
65+
if !ok {
66+
resp.Diagnostics.AddError(
67+
"Unexpected resource Configure type",
68+
fmt.Sprintf("Expected tfe.ConfiguredClient, got %T. This is a bug in the tfe provider, so please report it on GitHub.", req.ProviderData),
69+
)
70+
}
71+
r.config = client
72+
}
73+
74+
func (r *resourceTFEHYOKConfiguration) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
75+
resp.TypeName = req.ProviderTypeName + "_hyok_configuration"
76+
}
77+
78+
func (r *resourceTFEHYOKConfiguration) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
79+
resp.Schema = schema.Schema{
80+
Attributes: map[string]schema.Attribute{
81+
"id": schema.StringAttribute{
82+
Description: "The ID of the HYOK configuration.",
83+
Computed: true,
84+
PlanModifiers: []planmodifier.String{
85+
stringplanmodifier.UseStateForUnknown(),
86+
},
87+
},
88+
"name": schema.StringAttribute{
89+
Description: "Label for the HYOK configuration to be used within HCP Terraform.",
90+
Required: true,
91+
},
92+
"kek_id": schema.StringAttribute{
93+
Description: "Refers to the name of your key encryption key stored in your key management service.",
94+
Required: true,
95+
},
96+
"aws_oidc_configuration_id": schema.StringAttribute{
97+
Description: "The ID of the TFE AWS OIDC configuration.",
98+
Optional: true,
99+
PlanModifiers: []planmodifier.String{
100+
stringplanmodifier.RequiresReplace(),
101+
},
102+
Validators: []validator.String{
103+
validateSingleOIDCConfigurationChoice(),
104+
},
105+
},
106+
"gcp_oidc_configuration_id": schema.StringAttribute{
107+
Description: "The ID of the TFE HYOK configuration.",
108+
Optional: true,
109+
PlanModifiers: []planmodifier.String{
110+
stringplanmodifier.RequiresReplace(),
111+
},
112+
Validators: []validator.String{
113+
validateSingleOIDCConfigurationChoice(),
114+
},
115+
},
116+
"vault_oidc_configuration_id": schema.StringAttribute{
117+
Description: "The ID of the TFE Vault OIDC configuration.",
118+
Optional: true,
119+
PlanModifiers: []planmodifier.String{
120+
stringplanmodifier.RequiresReplace(),
121+
},
122+
Validators: []validator.String{
123+
validateSingleOIDCConfigurationChoice(),
124+
},
125+
},
126+
"azure_oidc_configuration_id": schema.StringAttribute{
127+
Description: "The ID of the TFE Azure OIDC configuration.",
128+
Optional: true,
129+
PlanModifiers: []planmodifier.String{
130+
stringplanmodifier.RequiresReplace(),
131+
},
132+
Validators: []validator.String{
133+
validateSingleOIDCConfigurationChoice(),
134+
},
135+
},
136+
"agent_pool_id": schema.StringAttribute{
137+
Description: "The ID of the agent-pool to associate with the HYOK configuration.",
138+
Required: true,
139+
},
140+
"organization": schema.StringAttribute{
141+
Description: "Name of the organization to which the TFE HYOK configuration belongs.",
142+
Optional: true,
143+
Computed: true,
144+
PlanModifiers: []planmodifier.String{
145+
stringplanmodifier.RequiresReplace(),
146+
},
147+
},
148+
},
149+
Blocks: map[string]schema.Block{
150+
"kms_options": schema.SingleNestedBlock{
151+
Description: "Optional object used to specify additional fields for some key management services.",
152+
Attributes: map[string]schema.Attribute{
153+
"key_region": schema.StringAttribute{
154+
Description: "The AWS region where your key is located.",
155+
Optional: true,
156+
Computed: true,
157+
Default: stringdefault.StaticString(""),
158+
},
159+
"key_location": schema.StringAttribute{
160+
Description: "The location in which the GCP key ring exists.",
161+
Optional: true,
162+
Computed: true,
163+
Default: stringdefault.StaticString(""),
164+
},
165+
"key_ring_id": schema.StringAttribute{
166+
Description: "The root resource for Google Cloud KMS keys and key versions.",
167+
Optional: true,
168+
Computed: true,
169+
Default: stringdefault.StaticString(""),
170+
},
171+
},
172+
},
173+
},
174+
Description: "Generates a new TFE HYOK Configuration.",
175+
}
176+
}
177+
178+
func validateSingleOIDCConfigurationChoice() validator.String {
179+
return stringvalidator.ExactlyOneOf(
180+
path.MatchRoot("aws_oidc_configuration_id"),
181+
path.MatchRoot("gcp_oidc_configuration_id"),
182+
path.MatchRoot("azure_oidc_configuration_id"),
183+
path.MatchRoot("vault_oidc_configuration_id"),
184+
)
185+
}
186+
187+
func (r *resourceTFEHYOKConfiguration) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
188+
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
189+
}
190+
191+
func (r *resourceTFEHYOKConfiguration) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
192+
// Read Terraform plan into the model
193+
var plan modelTFEHYOKConfiguration
194+
diags := req.Plan.Get(ctx, &plan)
195+
resp.Diagnostics.Append(diags...)
196+
if resp.Diagnostics.HasError() {
197+
return
198+
}
199+
200+
// Get the organization name from resource or provider config
201+
var orgName string
202+
resp.Diagnostics.Append(r.config.dataOrDefaultOrganization(ctx, req.Config, &orgName)...)
203+
if resp.Diagnostics.HasError() {
204+
return
205+
}
206+
207+
var awsOIDCConfig tfe.AWSOIDCConfiguration
208+
if plan.AWSOIDCConfigurationID.ValueString() != "" {
209+
awsOIDCConfig = tfe.AWSOIDCConfiguration{ID: plan.AWSOIDCConfigurationID.ValueString()}
210+
}
211+
212+
var gcpOIDCConfig tfe.GCPOIDCConfiguration
213+
if plan.AWSOIDCConfigurationID.ValueString() != "" {
214+
gcpOIDCConfig = tfe.GCPOIDCConfiguration{ID: plan.GCPOIDCConfigurationID.ValueString()}
215+
}
216+
217+
var vaultOIDCConfig tfe.VaultOIDCConfiguration
218+
if plan.VaultOIDCConfigurationID.ValueString() != "" {
219+
vaultOIDCConfig = tfe.VaultOIDCConfiguration{ID: plan.VaultOIDCConfigurationID.ValueString()}
220+
}
221+
222+
var azureOIDCConfig tfe.AzureOIDCConfiguration
223+
if plan.VaultOIDCConfigurationID.ValueString() != "" {
224+
azureOIDCConfig = tfe.AzureOIDCConfiguration{ID: plan.AzureOIDCConfigurationID.ValueString()}
225+
}
226+
227+
options := tfe.HYOKConfigurationsCreateOptions{
228+
KEKID: plan.KEKID.ValueString(),
229+
Name: plan.Name.ValueString(),
230+
KMSOptions: &tfe.KMSOptions{
231+
KeyRegion: plan.KMSOptions.KeyRegion.ValueString(),
232+
KeyLocation: plan.KMSOptions.KeyLocation.ValueString(),
233+
KeyRingID: plan.KMSOptions.KeyRingID.ValueString(),
234+
},
235+
OIDCConfiguration: &tfe.OIDCConfigurationTypeChoice{
236+
AWSOIDCConfiguration: &awsOIDCConfig,
237+
GCPOIDCConfiguration: &gcpOIDCConfig,
238+
VaultOIDCConfiguration: &vaultOIDCConfig,
239+
AzureOIDCConfiguration: &azureOIDCConfig,
240+
},
241+
AgentPool: &tfe.AgentPool{ID: plan.AgentPoolID.ValueString()},
242+
}
243+
244+
tflog.Debug(ctx, fmt.Sprintf("Create TFE HYOK Configuration for organization %s", orgName))
245+
hyok, err := r.config.Client.HYOKConfigurations.Create(ctx, orgName, options)
246+
if err != nil {
247+
resp.Diagnostics.AddError("Error creating TFE HYOK Configuration", err.Error())
248+
return
249+
}
250+
result := modelFromTFEHYOKConfiguration(hyok)
251+
resp.Diagnostics.Append(resp.State.Set(ctx, result)...)
252+
}
253+
254+
func (r *resourceTFEHYOKConfiguration) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
255+
// Read Terraform state into the model
256+
var state modelTFEHYOKConfiguration
257+
diags := req.State.Get(ctx, &state)
258+
resp.Diagnostics.Append(diags...)
259+
if resp.Diagnostics.HasError() {
260+
return
261+
}
262+
263+
hyokID := state.ID.ValueString()
264+
opts := tfe.HYOKConfigurationsReadOptions{
265+
Include: []tfe.HYOKConfigurationsIncludeOpt{
266+
tfe.HYOKConfigurationsIncludeOIDCConfiguration,
267+
},
268+
}
269+
tflog.Debug(ctx, fmt.Sprintf("Read HYOK configuration: %s", hyokID))
270+
hyok, err := r.config.Client.HYOKConfigurations.Read(ctx, state.ID.ValueString(), &opts)
271+
if err != nil {
272+
if errors.Is(err, tfe.ErrResourceNotFound) {
273+
tflog.Debug(ctx, fmt.Sprintf("HYOK configuration %s no longer exists", hyokID))
274+
resp.State.RemoveResource(ctx)
275+
} else {
276+
resp.Diagnostics.AddError(
277+
fmt.Sprintf("Error reading HYOK configuration %s", hyokID),
278+
err.Error(),
279+
)
280+
}
281+
return
282+
}
283+
result := modelFromTFEHYOKConfiguration(hyok)
284+
resp.Diagnostics.Append(resp.State.Set(ctx, result)...)
285+
}
286+
287+
func (r *resourceTFEHYOKConfiguration) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
288+
var plan modelTFEHYOKConfiguration
289+
diags := req.Plan.Get(ctx, &plan)
290+
resp.Diagnostics.Append(diags...)
291+
292+
var state modelTFEHYOKConfiguration
293+
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
294+
if resp.Diagnostics.HasError() {
295+
return
296+
}
297+
298+
options := tfe.HYOKConfigurationsUpdateOptions{
299+
Name: plan.Name.ValueStringPointer(),
300+
KEKID: plan.KEKID.ValueStringPointer(),
301+
KMSOptions: &tfe.KMSOptions{
302+
KeyRegion: plan.KMSOptions.KeyRegion.ValueString(),
303+
KeyLocation: plan.KMSOptions.KeyLocation.ValueString(),
304+
KeyRingID: plan.KMSOptions.KeyRingID.ValueString(),
305+
},
306+
AgentPool: &tfe.AgentPool{ID: plan.AgentPoolID.ValueString()},
307+
}
308+
309+
hyokID := state.ID.ValueString()
310+
tflog.Debug(ctx, fmt.Sprintf("Update TFE HYOK Configuration %s", hyokID))
311+
hyok, err := r.config.Client.HYOKConfigurations.Update(ctx, hyokID, options)
312+
if err != nil {
313+
resp.Diagnostics.AddError("Error updating TFE HYOK Configuration", err.Error())
314+
return
315+
}
316+
317+
result := modelFromTFEHYOKConfiguration(hyok)
318+
resp.Diagnostics.Append(resp.State.Set(ctx, result)...)
319+
}
320+
321+
func (r *resourceTFEHYOKConfiguration) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
322+
var state modelTFEHYOKConfiguration
323+
diags := req.State.Get(ctx, &state)
324+
resp.Diagnostics.Append(diags...)
325+
if resp.Diagnostics.HasError() {
326+
return
327+
}
328+
329+
hyokID := state.ID.ValueString()
330+
tflog.Debug(ctx, fmt.Sprintf("Delete TFE HYOK configuration: %s", hyokID))
331+
err := r.config.Client.HYOKConfigurations.Delete(ctx, hyokID)
332+
if err != nil {
333+
if errors.Is(err, tfe.ErrResourceNotFound) {
334+
tflog.Debug(ctx, fmt.Sprintf("TFE HYOK configuration %s no longer exists", hyokID))
335+
}
336+
337+
resp.Diagnostics.AddError("Error deleting TFE HYOK Configuration", err.Error())
338+
return
339+
}
340+
}
341+
342+
func modelFromTFEHYOKConfiguration(p *tfe.HYOKConfiguration) modelTFEHYOKConfiguration {
343+
kmsOptions := modelTFEKMSOptions{
344+
KeyRegion: types.StringValue(p.KMSOptions.KeyRegion),
345+
KeyLocation: types.StringValue(p.KMSOptions.KeyLocation),
346+
KeyRingID: types.StringValue(p.KMSOptions.KeyRingID),
347+
}
348+
349+
model := modelTFEHYOKConfiguration{
350+
ID: types.StringValue(p.ID),
351+
Name: types.StringValue(p.Name),
352+
KEKID: types.StringValue(p.KEKID),
353+
Organization: types.StringValue(p.Organization.Name),
354+
AgentPoolID: types.StringValue(p.AgentPool.ID),
355+
KMSOptions: &kmsOptions,
356+
}
357+
358+
if p.OIDCConfiguration.AWSOIDCConfiguration != nil {
359+
model.AWSOIDCConfigurationID = types.StringValue(p.OIDCConfiguration.AWSOIDCConfiguration.ID)
360+
} else if p.OIDCConfiguration.GCPOIDCConfiguration != nil {
361+
model.GCPOIDCConfigurationID = types.StringValue(p.OIDCConfiguration.GCPOIDCConfiguration.ID)
362+
} else if p.OIDCConfiguration.AzureOIDCConfiguration != nil {
363+
model.AzureOIDCConfigurationID = types.StringValue(p.OIDCConfiguration.AzureOIDCConfiguration.ID)
364+
} else if p.OIDCConfiguration.VaultOIDCConfiguration != nil {
365+
model.VaultOIDCConfigurationID = types.StringValue(p.OIDCConfiguration.VaultOIDCConfiguration.ID)
366+
}
367+
368+
return model
369+
}

0 commit comments

Comments
 (0)