Skip to content

Commit 5c864b4

Browse files
committed
First draft of resource_tfe_aws_oidc_configuration.go
resource_tfe_aws_oidc_configuration.go and basic test resource_tfe_gcp_oidc_configuration.go and test resource_tfe_azure_oidc_configuration.go_oidc_configuration.go and test resource_tfe_vault_oidc_configuration.go and tests Add HYOK_ORGANIATION_NAME environment variable to testing.md Add documentation for resources Add to CHANGELOG.md update basic usage in vault_oidc_configuration.html.markdown Do not require replace for everything Update documentation and default value of auth_path skipIfEnterprise update pricing info Implement resource_tfe_hyok_configuration.go Add hyok_configuration.html.markdown fix bug where aws configs recognized as gcp configs Add in acceptance tests Wait for revoked status during acceptance test Add CHANGELOG.md
1 parent 91036d7 commit 5c864b4

File tree

5 files changed

+785
-0
lines changed

5 files changed

+785
-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: 382 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,382 @@
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.GCPOIDCConfigurationID.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.AzureOIDCConfigurationID.ValueString() != "" {
224+
azureOIDCConfig = &tfe.AzureOIDCConfiguration{ID: plan.AzureOIDCConfigurationID.ValueString()}
225+
}
226+
227+
var kmsOptions *tfe.KMSOptions
228+
if plan.KMSOptions != nil {
229+
kmsOptions = &tfe.KMSOptions{
230+
KeyRegion: plan.KMSOptions.KeyRegion.ValueString(),
231+
KeyLocation: plan.KMSOptions.KeyLocation.ValueString(),
232+
KeyRingID: plan.KMSOptions.KeyRingID.ValueString(),
233+
}
234+
}
235+
236+
options := tfe.HYOKConfigurationsCreateOptions{
237+
KEKID: plan.KEKID.ValueString(),
238+
Name: plan.Name.ValueString(),
239+
KMSOptions: kmsOptions,
240+
OIDCConfiguration: &tfe.OIDCConfigurationTypeChoice{
241+
AWSOIDCConfiguration: awsOIDCConfig,
242+
GCPOIDCConfiguration: gcpOIDCConfig,
243+
VaultOIDCConfiguration: vaultOIDCConfig,
244+
AzureOIDCConfiguration: azureOIDCConfig,
245+
},
246+
AgentPool: &tfe.AgentPool{ID: plan.AgentPoolID.ValueString()},
247+
}
248+
249+
tflog.Debug(ctx, fmt.Sprintf("Create TFE HYOK Configuration for organization %s", orgName))
250+
hyok, err := r.config.Client.HYOKConfigurations.Create(ctx, orgName, options)
251+
if err != nil {
252+
resp.Diagnostics.AddError("Error creating TFE HYOK Configuration", err.Error())
253+
return
254+
}
255+
result := modelFromTFEHYOKConfiguration(hyok)
256+
resp.Diagnostics.Append(resp.State.Set(ctx, result)...)
257+
}
258+
259+
func (r *resourceTFEHYOKConfiguration) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
260+
// Read Terraform state into the model
261+
var state modelTFEHYOKConfiguration
262+
diags := req.State.Get(ctx, &state)
263+
resp.Diagnostics.Append(diags...)
264+
if resp.Diagnostics.HasError() {
265+
return
266+
}
267+
268+
hyokID := state.ID.ValueString()
269+
opts := tfe.HYOKConfigurationsReadOptions{
270+
Include: []tfe.HYOKConfigurationsIncludeOpt{
271+
tfe.HYOKConfigurationsIncludeOIDCConfiguration,
272+
},
273+
}
274+
tflog.Debug(ctx, fmt.Sprintf("Read HYOK configuration: %s", hyokID))
275+
hyok, err := r.config.Client.HYOKConfigurations.Read(ctx, state.ID.ValueString(), &opts)
276+
if err != nil {
277+
if errors.Is(err, tfe.ErrResourceNotFound) {
278+
tflog.Debug(ctx, fmt.Sprintf("HYOK configuration %s no longer exists", hyokID))
279+
resp.State.RemoveResource(ctx)
280+
} else {
281+
resp.Diagnostics.AddError(
282+
fmt.Sprintf("Error reading HYOK configuration %s", hyokID),
283+
err.Error(),
284+
)
285+
}
286+
return
287+
}
288+
result := modelFromTFEHYOKConfiguration(hyok)
289+
resp.Diagnostics.Append(resp.State.Set(ctx, result)...)
290+
}
291+
292+
func (r *resourceTFEHYOKConfiguration) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
293+
var plan modelTFEHYOKConfiguration
294+
diags := req.Plan.Get(ctx, &plan)
295+
resp.Diagnostics.Append(diags...)
296+
297+
var state modelTFEHYOKConfiguration
298+
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
299+
if resp.Diagnostics.HasError() {
300+
return
301+
}
302+
303+
var kmsOptions *tfe.KMSOptions
304+
if plan.KMSOptions != nil {
305+
kmsOptions = &tfe.KMSOptions{
306+
KeyRegion: plan.KMSOptions.KeyRegion.ValueString(),
307+
KeyLocation: plan.KMSOptions.KeyLocation.ValueString(),
308+
KeyRingID: plan.KMSOptions.KeyRingID.ValueString(),
309+
}
310+
}
311+
312+
options := tfe.HYOKConfigurationsUpdateOptions{
313+
Name: plan.Name.ValueStringPointer(),
314+
KEKID: plan.KEKID.ValueStringPointer(),
315+
KMSOptions: kmsOptions,
316+
AgentPool: &tfe.AgentPool{ID: plan.AgentPoolID.ValueString()},
317+
}
318+
319+
hyokID := state.ID.ValueString()
320+
tflog.Debug(ctx, fmt.Sprintf("Update TFE HYOK Configuration %s", hyokID))
321+
hyok, err := r.config.Client.HYOKConfigurations.Update(ctx, hyokID, options)
322+
if err != nil {
323+
resp.Diagnostics.AddError("Error updating TFE HYOK Configuration", err.Error())
324+
return
325+
}
326+
327+
result := modelFromTFEHYOKConfiguration(hyok)
328+
resp.Diagnostics.Append(resp.State.Set(ctx, result)...)
329+
}
330+
331+
func (r *resourceTFEHYOKConfiguration) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
332+
var state modelTFEHYOKConfiguration
333+
diags := req.State.Get(ctx, &state)
334+
resp.Diagnostics.Append(diags...)
335+
if resp.Diagnostics.HasError() {
336+
return
337+
}
338+
339+
hyokID := state.ID.ValueString()
340+
tflog.Debug(ctx, fmt.Sprintf("Delete TFE HYOK configuration: %s", hyokID))
341+
err := r.config.Client.HYOKConfigurations.Delete(ctx, hyokID)
342+
if err != nil {
343+
if errors.Is(err, tfe.ErrResourceNotFound) {
344+
tflog.Debug(ctx, fmt.Sprintf("TFE HYOK configuration %s no longer exists", hyokID))
345+
}
346+
347+
resp.Diagnostics.AddError("Error deleting TFE HYOK Configuration", err.Error())
348+
return
349+
}
350+
}
351+
352+
func modelFromTFEHYOKConfiguration(p *tfe.HYOKConfiguration) modelTFEHYOKConfiguration {
353+
var kmsOptions *modelTFEKMSOptions
354+
if p.KMSOptions != nil {
355+
kmsOptions = &modelTFEKMSOptions{
356+
KeyRegion: types.StringValue(p.KMSOptions.KeyRegion),
357+
KeyLocation: types.StringValue(p.KMSOptions.KeyLocation),
358+
KeyRingID: types.StringValue(p.KMSOptions.KeyRingID),
359+
}
360+
}
361+
362+
model := modelTFEHYOKConfiguration{
363+
ID: types.StringValue(p.ID),
364+
Name: types.StringValue(p.Name),
365+
KEKID: types.StringValue(p.KEKID),
366+
Organization: types.StringValue(p.Organization.Name),
367+
AgentPoolID: types.StringValue(p.AgentPool.ID),
368+
KMSOptions: kmsOptions,
369+
}
370+
371+
if p.OIDCConfiguration.AWSOIDCConfiguration != nil {
372+
model.AWSOIDCConfigurationID = types.StringValue(p.OIDCConfiguration.AWSOIDCConfiguration.ID)
373+
} else if p.OIDCConfiguration.GCPOIDCConfiguration != nil {
374+
model.GCPOIDCConfigurationID = types.StringValue(p.OIDCConfiguration.GCPOIDCConfiguration.ID)
375+
} else if p.OIDCConfiguration.AzureOIDCConfiguration != nil {
376+
model.AzureOIDCConfigurationID = types.StringValue(p.OIDCConfiguration.AzureOIDCConfiguration.ID)
377+
} else if p.OIDCConfiguration.VaultOIDCConfiguration != nil {
378+
model.VaultOIDCConfigurationID = types.StringValue(p.OIDCConfiguration.VaultOIDCConfiguration.ID)
379+
}
380+
381+
return model
382+
}

0 commit comments

Comments
 (0)