Skip to content

Commit c83b4ba

Browse files
authored
[TF-28671] Add HYOK configuration resources (#1841)
1 parent 91036d7 commit c83b4ba

File tree

5 files changed

+768
-0
lines changed

5 files changed

+768
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ FEATURES:
44
* **New resource**: `r/tfe_aws_oidc_configuration` for managing AWS OIDC configurations. [#1835](https://github.com/hashicorp/terraform-provider-tfe/pull/1835)
55
* **New resource**: `r/tfe_gcp_oidc_configuration` for managing GCP OIDC configurations. [#1835](https://github.com/hashicorp/terraform-provider-tfe/pull/1835)
66
* **New resource**: `r/tfe_azure_oidc_configuration` for managing Azure OIDC configurations. [#1835](https://github.com/hashicorp/terraform-provider-tfe/pull/1835)
7+
* **New resource**: `r/tfe_hyok_configuration` for managing HYOK configurations. [#1835](https://github.com/hashicorp/terraform-provider-tfe/pull/1841)
78

89
## v0.70.0
910

internal/provider/provider_next.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ func (p *frameworkProvider) Resources(ctx context.Context) []func() resource.Res
176176
NewGCPOIDCConfigurationResource,
177177
NewAzureOIDCConfigurationResource,
178178
NewVaultOIDCConfigurationResource,
179+
NewHYOKConfigurationResource,
179180
}
180181
}
181182

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

0 commit comments

Comments
 (0)