Skip to content

Commit fd071b4

Browse files
committed
resource_tfe_vault_oidc_configuration.go and tests
1 parent a33a54a commit fd071b4

File tree

3 files changed

+352
-0
lines changed

3 files changed

+352
-0
lines changed

internal/provider/provider_next.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,10 @@ func (p *frameworkProvider) Resources(ctx context.Context) []func() resource.Res
171171
NewTerraformVersionResource,
172172
NewOPAVersionResource,
173173
NewsentinelVersionResource,
174+
NewAWSOIDCConfigurationResource,
175+
NewGCPOIDCConfigurationResource,
176+
NewAzureOIDCConfigurationResource,
177+
NewVaultOIDCConfigurationResource,
174178
}
175179
}
176180

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
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/path"
11+
12+
tfe "github.com/hashicorp/go-tfe"
13+
"github.com/hashicorp/terraform-plugin-framework/resource"
14+
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
15+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
16+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
17+
"github.com/hashicorp/terraform-plugin-framework/types"
18+
"github.com/hashicorp/terraform-plugin-log/tflog"
19+
)
20+
21+
var (
22+
_ resource.ResourceWithConfigure = &resourceTFEVaultOIDCConfiguration{}
23+
_ resource.ResourceWithImportState = &resourceTFEVaultOIDCConfiguration{}
24+
)
25+
26+
func NewVaultOIDCConfigurationResource() resource.Resource {
27+
return &resourceTFEVaultOIDCConfiguration{}
28+
}
29+
30+
type resourceTFEVaultOIDCConfiguration struct {
31+
config ConfiguredClient
32+
}
33+
34+
type modelTFEVaultOIDCConfiguration struct {
35+
ID types.String `tfsdk:"id"`
36+
Address types.String `tfsdk:"address"`
37+
RoleName types.String `tfsdk:"role_name"`
38+
Namespace types.String `tfsdk:"namespace"`
39+
JWTAuthPath types.String `tfsdk:"auth_path"`
40+
TLSCACertificate types.String `tfsdk:"encoded_cacert"`
41+
Organization types.String `tfsdk:"organization"`
42+
}
43+
44+
func (r *resourceTFEVaultOIDCConfiguration) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
45+
// Prevent panic if the provider has not been configured.
46+
if req.ProviderData == nil {
47+
return
48+
}
49+
50+
client, ok := req.ProviderData.(ConfiguredClient)
51+
if !ok {
52+
resp.Diagnostics.AddError(
53+
"Unexpected resource Configure type",
54+
fmt.Sprintf("Expected tfe.ConfiguredClient, got %T. This is a bug in the tfe provider, so please report it on GitHub.", req.ProviderData),
55+
)
56+
}
57+
r.config = client
58+
}
59+
60+
func (r *resourceTFEVaultOIDCConfiguration) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
61+
resp.TypeName = req.ProviderTypeName + "_vault_oidc_configuration"
62+
}
63+
64+
func (r *resourceTFEVaultOIDCConfiguration) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
65+
resp.Schema = schema.Schema{
66+
Attributes: map[string]schema.Attribute{
67+
"id": schema.StringAttribute{
68+
Description: "The ID of the Vault OIDC configuration.",
69+
Computed: true,
70+
PlanModifiers: []planmodifier.String{
71+
stringplanmodifier.UseStateForUnknown(),
72+
},
73+
},
74+
"address": schema.StringAttribute{
75+
Description: "The full address of your Vault instance.",
76+
Required: true,
77+
PlanModifiers: []planmodifier.String{
78+
stringplanmodifier.RequiresReplace(),
79+
},
80+
},
81+
"role_name": schema.StringAttribute{
82+
Description: "The name of a role in your Vault JWT auth path, with permission to encrypt and decrypt with a Transit secrets engine key.",
83+
Required: true,
84+
PlanModifiers: []planmodifier.String{
85+
stringplanmodifier.RequiresReplace(),
86+
},
87+
},
88+
"namespace": schema.StringAttribute{
89+
Description: "The namespace your JWT auth path is mounted in.",
90+
Required: true,
91+
PlanModifiers: []planmodifier.String{
92+
stringplanmodifier.RequiresReplace(),
93+
},
94+
},
95+
"auth_path": schema.StringAttribute{
96+
Description: `The mounting path of JWT auth path of JWT auth. Defaults to "jwt".`,
97+
Required: true,
98+
PlanModifiers: []planmodifier.String{
99+
stringplanmodifier.RequiresReplace(),
100+
},
101+
},
102+
"encoded_cacert": schema.StringAttribute{
103+
Description: "A base64 encoded certificate which can be used to authenticate your Vault certificate. Only needed for self-hosted Vault Enterprise instances with a self-signed certificate.",
104+
Optional: true,
105+
PlanModifiers: []planmodifier.String{
106+
stringplanmodifier.RequiresReplace(),
107+
},
108+
},
109+
"organization": schema.StringAttribute{
110+
Description: "Name of the organization to which the TFE Vault OIDC configuration belongs.",
111+
Optional: true,
112+
Computed: true,
113+
PlanModifiers: []planmodifier.String{
114+
stringplanmodifier.RequiresReplace(),
115+
},
116+
},
117+
},
118+
Description: "Generates a new TFE Vault OIDC Configuration.",
119+
}
120+
}
121+
122+
func (r *resourceTFEVaultOIDCConfiguration) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
123+
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
124+
}
125+
126+
func (r *resourceTFEVaultOIDCConfiguration) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
127+
// Read Terraform plan into the model
128+
var plan modelTFEVaultOIDCConfiguration
129+
diags := req.Plan.Get(ctx, &plan)
130+
resp.Diagnostics.Append(diags...)
131+
if resp.Diagnostics.HasError() {
132+
return
133+
}
134+
135+
// Get the organization name from resource or provider config
136+
var orgName string
137+
resp.Diagnostics.Append(r.config.dataOrDefaultOrganization(ctx, req.Config, &orgName)...)
138+
if resp.Diagnostics.HasError() {
139+
return
140+
}
141+
142+
options := tfe.VaultOIDCConfigurationCreateOptions{
143+
Address: plan.Address.ValueString(),
144+
RoleName: plan.RoleName.ValueString(),
145+
Namespace: plan.Namespace.ValueString(),
146+
JWTAuthPath: plan.JWTAuthPath.ValueString(),
147+
TLSCACertificate: plan.TLSCACertificate.ValueString(),
148+
}
149+
150+
tflog.Debug(ctx, fmt.Sprintf("Create TFE Vault OIDC Configuration for organization %s", orgName))
151+
oidc, err := r.config.Client.VaultOIDCConfigurations.Create(ctx, orgName, options)
152+
if err != nil {
153+
resp.Diagnostics.AddError("Error creating TFE Vault OIDC Configuration", err.Error())
154+
return
155+
}
156+
result := modelFromTFEVaultOIDCConfiguration(oidc)
157+
resp.Diagnostics.Append(resp.State.Set(ctx, result)...)
158+
}
159+
160+
func (r *resourceTFEVaultOIDCConfiguration) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
161+
// Read Terraform state into the model
162+
var state modelTFEVaultOIDCConfiguration
163+
diags := req.State.Get(ctx, &state)
164+
resp.Diagnostics.Append(diags...)
165+
if resp.Diagnostics.HasError() {
166+
return
167+
}
168+
169+
oidcID := state.ID.ValueString()
170+
tflog.Debug(ctx, fmt.Sprintf("Read Vault OIDC configuration: %s", oidcID))
171+
oidc, err := r.config.Client.VaultOIDCConfigurations.Read(ctx, state.ID.ValueString())
172+
if err != nil {
173+
if errors.Is(err, tfe.ErrResourceNotFound) {
174+
tflog.Debug(ctx, fmt.Sprintf("Vault OIDC configuration %s no longer exists", oidcID))
175+
resp.State.RemoveResource(ctx)
176+
return
177+
}
178+
resp.Diagnostics.AddError(
179+
fmt.Sprintf("Error reading Vault OIDC configuration %s", oidcID),
180+
err.Error(),
181+
)
182+
return
183+
}
184+
result := modelFromTFEVaultOIDCConfiguration(oidc)
185+
resp.Diagnostics.Append(resp.State.Set(ctx, result)...)
186+
}
187+
188+
func (r *resourceTFEVaultOIDCConfiguration) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
189+
var plan modelTFEVaultOIDCConfiguration
190+
diags := req.Plan.Get(ctx, &plan)
191+
resp.Diagnostics.Append(diags...)
192+
193+
var state modelTFEVaultOIDCConfiguration
194+
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
195+
if resp.Diagnostics.HasError() {
196+
return
197+
}
198+
199+
options := tfe.VaultOIDCConfigurationUpdateOptions{
200+
Address: plan.Address.ValueStringPointer(),
201+
RoleName: plan.RoleName.ValueStringPointer(),
202+
Namespace: plan.Namespace.ValueStringPointer(),
203+
JWTAuthPath: plan.JWTAuthPath.ValueStringPointer(),
204+
TLSCACertificate: plan.TLSCACertificate.ValueStringPointer(),
205+
}
206+
207+
oidcID := state.ID.ValueString()
208+
tflog.Debug(ctx, fmt.Sprintf("Update TFE Vault OIDC Configuration %s", oidcID))
209+
oidc, err := r.config.Client.VaultOIDCConfigurations.Update(ctx, oidcID, options)
210+
if err != nil {
211+
resp.Diagnostics.AddError("Error updating TFE Vault OIDC Configuration", err.Error())
212+
return
213+
}
214+
215+
result := modelFromTFEVaultOIDCConfiguration(oidc)
216+
resp.Diagnostics.Append(resp.State.Set(ctx, result)...)
217+
}
218+
219+
func (r *resourceTFEVaultOIDCConfiguration) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
220+
var state modelTFEVaultOIDCConfiguration
221+
diags := req.State.Get(ctx, &state)
222+
resp.Diagnostics.Append(diags...)
223+
if resp.Diagnostics.HasError() {
224+
return
225+
}
226+
227+
oidcID := state.ID.ValueString()
228+
tflog.Debug(ctx, fmt.Sprintf("Delete TFE Vault OIDC configuration: %s", oidcID))
229+
err := r.config.Client.VaultOIDCConfigurations.Delete(ctx, oidcID)
230+
if err != nil {
231+
if errors.Is(err, tfe.ErrResourceNotFound) {
232+
tflog.Debug(ctx, fmt.Sprintf("TFE Vault OIDC configuration %s no longer exists", oidcID))
233+
}
234+
235+
resp.Diagnostics.AddError("Error deleting TFE Vault OIDC Configuration", err.Error())
236+
return
237+
}
238+
}
239+
240+
func modelFromTFEVaultOIDCConfiguration(p *tfe.VaultOIDCConfiguration) modelTFEVaultOIDCConfiguration {
241+
return modelTFEVaultOIDCConfiguration{
242+
ID: types.StringValue(p.ID),
243+
Address: types.StringValue(p.Address),
244+
RoleName: types.StringValue(p.RoleName),
245+
Namespace: types.StringValue(p.Namespace),
246+
JWTAuthPath: types.StringValue(p.JWTAuthPath),
247+
TLSCACertificate: types.StringValue(p.TLSCACertificate),
248+
Organization: types.StringValue(p.Organization.Name),
249+
}
250+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package provider
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"testing"
7+
8+
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
9+
"github.com/hashicorp/terraform-plugin-testing/terraform"
10+
)
11+
12+
func TestAccTFEVaultOIDCConfiguration_basic(t *testing.T) {
13+
orgName := os.Getenv("HYOK_ORGANIZATION_NAME")
14+
15+
if orgName == "" {
16+
t.Skip("Test skipped: HYOK_ORGANIZATION_NAME environment variable is not set")
17+
}
18+
19+
originalAddress := "https://vault.example.com"
20+
updatedAddress := "https://vault.example2.com"
21+
originalRoleName := "role-name-1"
22+
updatedRoleName := "role-name-2"
23+
originalNamespace := "admin-1"
24+
updatedNamespace := "admin-2"
25+
originalAuthPath := "jwt"
26+
updatedAuthPath := "jwt2"
27+
originalCACert := ""
28+
updatedCACert := "some-cert"
29+
30+
resource.Test(t, resource.TestCase{
31+
PreCheck: func() { testAccPreCheck(t) },
32+
ProtoV5ProviderFactories: testAccMuxedProviders,
33+
CheckDestroy: testAccCheckTFEVaultOIDCConfigurationDestroy,
34+
Steps: []resource.TestStep{
35+
{
36+
Config: testAccTFEVaultOIDCConfigurationConfig(orgName, originalAddress, originalRoleName, originalNamespace, originalAuthPath, originalCACert),
37+
Check: resource.ComposeAggregateTestCheckFunc(
38+
resource.TestCheckResourceAttrSet("tfe_vault_oidc_configuration.test", "id"),
39+
resource.TestCheckResourceAttr("tfe_vault_oidc_configuration.test", "address", originalAddress),
40+
resource.TestCheckResourceAttr("tfe_vault_oidc_configuration.test", "role_name", originalRoleName),
41+
resource.TestCheckResourceAttr("tfe_vault_oidc_configuration.test", "namespace", originalNamespace),
42+
resource.TestCheckResourceAttr("tfe_vault_oidc_configuration.test", "auth_path", originalAuthPath),
43+
resource.TestCheckResourceAttr("tfe_vault_oidc_configuration.test", "encoded_cacert", originalCACert),
44+
),
45+
},
46+
// Import
47+
{
48+
ResourceName: "tfe_vault_oidc_configuration.test",
49+
ImportState: true,
50+
ImportStateVerify: true,
51+
},
52+
// Update
53+
{
54+
Config: testAccTFEVaultOIDCConfigurationConfig(orgName, updatedAddress, updatedRoleName, updatedNamespace, updatedAuthPath, updatedCACert),
55+
Check: resource.ComposeAggregateTestCheckFunc(
56+
resource.TestCheckResourceAttrSet("tfe_vault_oidc_configuration.test", "id"),
57+
resource.TestCheckResourceAttr("tfe_vault_oidc_configuration.test", "address", updatedAddress),
58+
resource.TestCheckResourceAttr("tfe_vault_oidc_configuration.test", "role_name", updatedRoleName),
59+
resource.TestCheckResourceAttr("tfe_vault_oidc_configuration.test", "namespace", updatedNamespace),
60+
resource.TestCheckResourceAttr("tfe_vault_oidc_configuration.test", "auth_path", updatedAuthPath),
61+
resource.TestCheckResourceAttr("tfe_vault_oidc_configuration.test", "encoded_cacert", updatedCACert),
62+
),
63+
},
64+
},
65+
})
66+
}
67+
68+
func testAccTFEVaultOIDCConfigurationConfig(orgName string, address string, roleName string, namespace string, authPath string, cacert string) string {
69+
return fmt.Sprintf(`
70+
resource "tfe_vault_oidc_configuration" "test" {
71+
address = "%s"
72+
role_name = "%s"
73+
namespace = "%s"
74+
auth_path = "%s"
75+
encoded_cacert = "%s"
76+
organization = "%s"
77+
}
78+
`, address, roleName, namespace, authPath, cacert, orgName)
79+
}
80+
81+
func testAccCheckTFEVaultOIDCConfigurationDestroy(s *terraform.State) error {
82+
for _, rs := range s.RootModule().Resources {
83+
if rs.Type != "tfe_vault_oidc_configuration" {
84+
continue
85+
}
86+
87+
if rs.Primary.ID == "" {
88+
return fmt.Errorf("no instance ID is set")
89+
}
90+
91+
_, err := testAccConfiguredClient.Client.VaultOIDCConfigurations.Read(ctx, rs.Primary.ID)
92+
if err == nil {
93+
return fmt.Errorf("TFE Vault OIDC Configuration %s still exists", rs.Primary.ID)
94+
}
95+
}
96+
97+
return nil
98+
}

0 commit comments

Comments
 (0)