From 4cc564c0709b29040dcaf1557f95966c5beb6e3b Mon Sep 17 00:00:00 2001 From: delores-hashicorp Date: Mon, 22 Apr 2024 15:51:22 +0200 Subject: [PATCH 1/4] Fix HVS ignoring default project change. This change also includes making project_id an optional field in the secrets and sercrets app schemas to allow users choose what project the app or secret should be created in. --- .../resource_vault_secrets_app.go | 44 +++++++++++++++++-- .../resource_vault_secrets_app_test.go | 15 +++++++ .../resource_vault_secrets_secret.go | 44 +++++++++++++++++-- .../resource_vault_secrets_secret_test.go | 29 ++++++++++-- 4 files changed, 120 insertions(+), 12 deletions(-) diff --git a/internal/provider/vaultsecrets/resource_vault_secrets_app.go b/internal/provider/vaultsecrets/resource_vault_secrets_app.go index 01a33f009..dec30768f 100644 --- a/internal/provider/vaultsecrets/resource_vault_secrets_app.go +++ b/internal/provider/vaultsecrets/resource_vault_secrets_app.go @@ -12,11 +12,18 @@ import ( "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" clients "github.com/hashicorp/terraform-provider-hcp/internal/clients" + "github.com/hashicorp/terraform-provider-hcp/internal/provider/modifiers" ) +var _ resource.Resource = &resourceVaultsecretsApp{} +var _ resource.ResourceWithConfigure = &resourceVaultsecretsApp{} +var _ resource.ResourceWithModifyPlan = &resourceVaultsecretsApp{} + func NewVaultSecretsAppResource() resource.Resource { return &resourceVaultsecretsApp{} } @@ -53,6 +60,11 @@ func (r *resourceVaultsecretsApp) Schema(_ context.Context, _ resource.SchemaReq "project_id": schema.StringAttribute{ Description: "The ID of the HCP project where the HCP Vault Secrets app is located.", Computed: true, + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + stringplanmodifier.UseStateForUnknown(), + }, }, "organization_id": schema.StringAttribute{ Description: "The ID of the HCP organization where the project the HCP Vault Secrets app is located.", @@ -77,6 +89,10 @@ func (r *resourceVaultsecretsApp) Configure(_ context.Context, req resource.Conf r.client = client } +func (r *resourceVaultsecretsApp) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { + modifiers.ModifyPlanForDefaultProjectChange(ctx, r.client.Config.ProjectID, req.State, req.Config, req.Plan, resp) +} + type VaultSecretsApp struct { ID types.String `tfsdk:"id"` AppName types.String `tfsdk:"app_name"` @@ -93,9 +109,14 @@ func (r *resourceVaultsecretsApp) Create(ctx context.Context, req resource.Creat return } + projectID := r.client.Config.ProjectID + if !plan.ProjectID.IsUnknown() { + projectID = plan.ProjectID.ValueString() + } + loc := &sharedmodels.HashicorpCloudLocationLocation{ OrganizationID: r.client.Config.OrganizationID, - ProjectID: r.client.Config.ProjectID, + ProjectID: projectID, } res, err := clients.CreateVaultSecretsApp(ctx, r.client, loc, plan.AppName.ValueString(), plan.Description.ValueString()) @@ -121,9 +142,14 @@ func (r *resourceVaultsecretsApp) Read(ctx context.Context, req resource.ReadReq return } + projectID := r.client.Config.ProjectID + if !state.ProjectID.IsUnknown() { + projectID = state.ProjectID.ValueString() + } + loc := &sharedmodels.HashicorpCloudLocationLocation{ OrganizationID: r.client.Config.OrganizationID, - ProjectID: r.client.Config.ProjectID, + ProjectID: projectID, } res, err := clients.GetVaultSecretsApp(ctx, r.client, loc, state.AppName.ValueString()) @@ -145,9 +171,14 @@ func (r *resourceVaultsecretsApp) Update(ctx context.Context, req resource.Updat return } + projectID := r.client.Config.ProjectID + if !plan.ProjectID.IsUnknown() { + projectID = plan.ProjectID.ValueString() + } + loc := &sharedmodels.HashicorpCloudLocationLocation{ OrganizationID: r.client.Config.OrganizationID, - ProjectID: r.client.Config.ProjectID, + ProjectID: projectID, } res, err := clients.UpdateVaultSecretsApp(ctx, r.client, loc, plan.AppName.ValueString(), plan.Description.ValueString()) @@ -173,9 +204,14 @@ func (r *resourceVaultsecretsApp) Delete(ctx context.Context, req resource.Delet return } + projectID := r.client.Config.ProjectID + if !state.ProjectID.IsUnknown() { + projectID = state.ProjectID.ValueString() + } + loc := &sharedmodels.HashicorpCloudLocationLocation{ OrganizationID: r.client.Config.OrganizationID, - ProjectID: r.client.Config.ProjectID, + ProjectID: projectID, } err := clients.DeleteVaultSecretsApp(ctx, r.client, loc, state.AppName.ValueString()) diff --git a/internal/provider/vaultsecrets/resource_vault_secrets_app_test.go b/internal/provider/vaultsecrets/resource_vault_secrets_app_test.go index 67b10f7cb..3a9536ccf 100644 --- a/internal/provider/vaultsecrets/resource_vault_secrets_app_test.go +++ b/internal/provider/vaultsecrets/resource_vault_secrets_app_test.go @@ -5,6 +5,7 @@ package vaultsecrets_test import ( "fmt" + "os" "testing" "github.com/hashicorp/terraform-plugin-testing/helper/resource" @@ -13,9 +14,23 @@ import ( func TestAccVaultSecretsResourceApp(t *testing.T) { testAppName := generateRandomSlug() + projectID := os.Getenv("HCP_PROJECT_ID") resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, Steps: []resource.TestStep{ + { + Config: fmt.Sprintf(` + resource "hcp_vault_secrets_app" "example" { + app_name = %q + description = "Acceptance test run" + project_id = %q + }`, testAppName, projectID), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("hcp_vault_secrets_app.example", "app_name", testAppName), + resource.TestCheckResourceAttr("hcp_vault_secrets_app.example", "description", "Acceptance test run"), + resource.TestCheckResourceAttr("hcp_vault_secrets_app.example", "project_id", projectID), + ), + }, { Config: fmt.Sprintf(` resource "hcp_vault_secrets_app" "example" { diff --git a/internal/provider/vaultsecrets/resource_vault_secrets_secret.go b/internal/provider/vaultsecrets/resource_vault_secrets_secret.go index 7f1a50435..bdc472ec2 100644 --- a/internal/provider/vaultsecrets/resource_vault_secrets_secret.go +++ b/internal/provider/vaultsecrets/resource_vault_secrets_secret.go @@ -12,11 +12,18 @@ import ( "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-provider-hcp/internal/clients" + "github.com/hashicorp/terraform-provider-hcp/internal/provider/modifiers" ) +var _ resource.Resource = &resourceVaultsecretsSecret{} +var _ resource.ResourceWithConfigure = &resourceVaultsecretsSecret{} +var _ resource.ResourceWithModifyPlan = &resourceVaultsecretsSecret{} + func NewVaultSecretsSecretResource() resource.Resource { return &resourceVaultsecretsSecret{} } @@ -78,6 +85,11 @@ func (r *resourceVaultsecretsSecret) Schema(_ context.Context, _ resource.Schema "project_id": schema.StringAttribute{ Description: "The ID of the HCP project where the HCP Vault Secrets secret is located.", Computed: true, + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + stringplanmodifier.UseStateForUnknown(), + }, }, "organization_id": schema.StringAttribute{ Description: "The ID of the HCP organization where the project the HCP Vault Secrets secret is located.", @@ -103,6 +115,10 @@ func (r *resourceVaultsecretsSecret) Configure(_ context.Context, req resource.C r.client = client } +func (r *resourceVaultsecretsSecret) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { + modifiers.ModifyPlanForDefaultProjectChange(ctx, r.client.Config.ProjectID, req.State, req.Config, req.Plan, resp) +} + func (r *resourceVaultsecretsSecret) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { var plan VaultSecretsSecret diags := req.Plan.Get(ctx, &plan) @@ -111,9 +127,14 @@ func (r *resourceVaultsecretsSecret) Create(ctx context.Context, req resource.Cr return } + projectID := r.client.Config.ProjectID + if !plan.ProjectID.IsUnknown() { + projectID = plan.ProjectID.ValueString() + } + loc := &sharedmodels.HashicorpCloudLocationLocation{ OrganizationID: r.client.Config.OrganizationID, - ProjectID: r.client.Config.ProjectID, + ProjectID: projectID, } res, err := clients.CreateVaultSecretsAppSecret(ctx, r.client, loc, plan.AppName.ValueString(), plan.SecretName.ValueString(), plan.SecretValue.ValueString()) @@ -138,9 +159,14 @@ func (r *resourceVaultsecretsSecret) Read(ctx context.Context, req resource.Read return } + projectID := r.client.Config.ProjectID + if !state.ProjectID.IsUnknown() { + projectID = state.ProjectID.ValueString() + } + loc := &sharedmodels.HashicorpCloudLocationLocation{ OrganizationID: r.client.Config.OrganizationID, - ProjectID: r.client.Config.ProjectID, + ProjectID: projectID, } res, err := clients.OpenVaultSecretsAppSecret(ctx, r.client, loc, state.AppName.ValueString(), state.SecretName.ValueString()) @@ -162,9 +188,14 @@ func (r *resourceVaultsecretsSecret) Update(ctx context.Context, req resource.Up return } + projectID := r.client.Config.ProjectID + if !plan.ProjectID.IsUnknown() { + projectID = plan.ProjectID.ValueString() + } + loc := &sharedmodels.HashicorpCloudLocationLocation{ OrganizationID: r.client.Config.OrganizationID, - ProjectID: r.client.Config.ProjectID, + ProjectID: projectID, } res, err := clients.CreateVaultSecretsAppSecret(ctx, r.client, loc, plan.AppName.ValueString(), plan.SecretName.ValueString(), plan.SecretValue.ValueString()) @@ -189,9 +220,14 @@ func (r *resourceVaultsecretsSecret) Delete(ctx context.Context, req resource.De return } + projectID := r.client.Config.ProjectID + if !state.ProjectID.IsUnknown() { + projectID = state.ProjectID.ValueString() + } + loc := &sharedmodels.HashicorpCloudLocationLocation{ OrganizationID: r.client.Config.OrganizationID, - ProjectID: r.client.Config.ProjectID, + ProjectID: projectID, } err := clients.DeleteVaultSecretsAppSecret(ctx, r.client, loc, state.AppName.ValueString(), state.SecretName.ValueString()) diff --git a/internal/provider/vaultsecrets/resource_vault_secrets_secret_test.go b/internal/provider/vaultsecrets/resource_vault_secrets_secret_test.go index 3926a6fe6..a8d04be20 100644 --- a/internal/provider/vaultsecrets/resource_vault_secrets_secret_test.go +++ b/internal/provider/vaultsecrets/resource_vault_secrets_secret_test.go @@ -5,6 +5,7 @@ package vaultsecrets_test import ( "fmt" + "os" "testing" "github.com/hashicorp/terraform-plugin-testing/helper/resource" @@ -12,24 +13,44 @@ import ( ) func TestAccVaultSecretsResourceSecret(t *testing.T) { - testAppName := generateRandomSlug() + testAppName1 := generateRandomSlug() + testAppName2 := generateRandomSlug() + projectID := os.Getenv("HCP_PROJECT_ID") resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, Steps: []resource.TestStep{ + + { + PreConfig: func() { + createTestApp(t, testAppName1) + }, + Config: fmt.Sprintf(` + resource "hcp_vault_secrets_secret" "example" { + app_name = %q + secret_name = "test_secret" + secret_value = "super secret" + }`, testAppName1), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("hcp_vault_secrets_secret.example", "app_name", testAppName1), + resource.TestCheckResourceAttr("hcp_vault_secrets_secret.example", "secret_name", "test_secret"), + resource.TestCheckResourceAttr("hcp_vault_secrets_secret.example", "secret_value", "super secret"), + ), + }, { PreConfig: func() { - createTestApp(t, testAppName) + createTestApp(t, testAppName2) }, Config: fmt.Sprintf(` resource "hcp_vault_secrets_secret" "example" { app_name = %q secret_name = "test_secret" secret_value = "super secret" - }`, testAppName), + }`, testAppName2), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("hcp_vault_secrets_secret.example", "app_name", testAppName), + resource.TestCheckResourceAttr("hcp_vault_secrets_secret.example", "app_name", testAppName2), resource.TestCheckResourceAttr("hcp_vault_secrets_secret.example", "secret_name", "test_secret"), resource.TestCheckResourceAttr("hcp_vault_secrets_secret.example", "secret_value", "super secret"), + resource.TestCheckResourceAttr("hcp_vault_secrets_secret.example", "project_id", projectID), ), }, }, From f5b98bd6666ed97e94e927b77db67062ad28dde2 Mon Sep 17 00:00:00 2001 From: delores-hashicorp Date: Mon, 22 Apr 2024 15:51:55 +0200 Subject: [PATCH 2/4] Generate documentation --- docs/resources/vault_secrets_app.md | 2 +- docs/resources/vault_secrets_secret.md | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/resources/vault_secrets_app.md b/docs/resources/vault_secrets_app.md index 8e24099ee..564ce4763 100644 --- a/docs/resources/vault_secrets_app.md +++ b/docs/resources/vault_secrets_app.md @@ -28,9 +28,9 @@ resource "hcp_vault_secrets_app" "example" { ### Optional - `description` (String) The Vault Secrets app description +- `project_id` (String) The ID of the HCP project where the HCP Vault Secrets app is located. ### Read-Only - `id` (String) Required ID field that is set to the app name. - `organization_id` (String) The ID of the HCP organization where the project the HCP Vault Secrets app is located. -- `project_id` (String) The ID of the HCP project where the HCP Vault Secrets app is located. diff --git a/docs/resources/vault_secrets_secret.md b/docs/resources/vault_secrets_secret.md index ffb3c3d85..fa283028e 100644 --- a/docs/resources/vault_secrets_secret.md +++ b/docs/resources/vault_secrets_secret.md @@ -30,8 +30,11 @@ resource "hcp_vault_secrets_secret" "example" { - `secret_name` (String) The name of the secret - `secret_value` (String, Sensitive) The value of the secret +### Optional + +- `project_id` (String) The ID of the HCP project where the HCP Vault Secrets secret is located. + ### Read-Only - `id` (String) The id of the resource - `organization_id` (String) The ID of the HCP organization where the project the HCP Vault Secrets secret is located. -- `project_id` (String) The ID of the HCP project where the HCP Vault Secrets secret is located. From aef3fdce711d8fa10dcf4266d97f71b1377be549 Mon Sep 17 00:00:00 2001 From: delores-hashicorp Date: Mon, 22 Apr 2024 15:52:15 +0200 Subject: [PATCH 3/4] Add changelog --- .changelog/808.txt | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changelog/808.txt diff --git a/.changelog/808.txt b/.changelog/808.txt new file mode 100644 index 000000000..91f4fb121 --- /dev/null +++ b/.changelog/808.txt @@ -0,0 +1,7 @@ +```release-note:bug +Fixes the case where Vault secret resources ignore provider project changes. +``` + +```release-note:improvement +Vault secret resources can now be created with an optional project ID. If project ID is present, the resource will be created within that project. +``` From a03649be0f1e8f0f8aa832cff1c3ebbbb2ad05d4 Mon Sep 17 00:00:00 2001 From: delores-hashicorp Date: Mon, 22 Apr 2024 19:07:10 +0200 Subject: [PATCH 4/4] Create resources in specified projects --- .../resource_vault_secrets_app_test.go | 11 +++++----- .../resource_vault_secrets_secret_test.go | 20 ++++++++++++------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/internal/provider/vaultsecrets/resource_vault_secrets_app_test.go b/internal/provider/vaultsecrets/resource_vault_secrets_app_test.go index 3a9536ccf..e9cede10b 100644 --- a/internal/provider/vaultsecrets/resource_vault_secrets_app_test.go +++ b/internal/provider/vaultsecrets/resource_vault_secrets_app_test.go @@ -5,7 +5,6 @@ package vaultsecrets_test import ( "fmt" - "os" "testing" "github.com/hashicorp/terraform-plugin-testing/helper/resource" @@ -14,21 +13,23 @@ import ( func TestAccVaultSecretsResourceApp(t *testing.T) { testAppName := generateRandomSlug() - projectID := os.Getenv("HCP_PROJECT_ID") resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, Steps: []resource.TestStep{ { Config: fmt.Sprintf(` + resource "hcp_project" "example" { + name = "test-project" + } resource "hcp_vault_secrets_app" "example" { app_name = %q description = "Acceptance test run" - project_id = %q - }`, testAppName, projectID), + project_id = hcp_project.example.resource_id + }`, testAppName), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("hcp_vault_secrets_app.example", "app_name", testAppName), resource.TestCheckResourceAttr("hcp_vault_secrets_app.example", "description", "Acceptance test run"), - resource.TestCheckResourceAttr("hcp_vault_secrets_app.example", "project_id", projectID), + resource.TestCheckResourceAttrSet("hcp_vault_secrets_app.example", "project_id"), ), }, { diff --git a/internal/provider/vaultsecrets/resource_vault_secrets_secret_test.go b/internal/provider/vaultsecrets/resource_vault_secrets_secret_test.go index a8d04be20..0f1aebdb5 100644 --- a/internal/provider/vaultsecrets/resource_vault_secrets_secret_test.go +++ b/internal/provider/vaultsecrets/resource_vault_secrets_secret_test.go @@ -5,7 +5,6 @@ package vaultsecrets_test import ( "fmt" - "os" "testing" "github.com/hashicorp/terraform-plugin-testing/helper/resource" @@ -15,7 +14,6 @@ import ( func TestAccVaultSecretsResourceSecret(t *testing.T) { testAppName1 := generateRandomSlug() testAppName2 := generateRandomSlug() - projectID := os.Getenv("HCP_PROJECT_ID") resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, Steps: []resource.TestStep{ @@ -37,20 +35,28 @@ func TestAccVaultSecretsResourceSecret(t *testing.T) { ), }, { - PreConfig: func() { - createTestApp(t, testAppName2) - }, Config: fmt.Sprintf(` - resource "hcp_vault_secrets_secret" "example" { + resource "hcp_project" "example" { + name = "test-project" + } + + resource "hcp_vault_secrets_app" "example" { app_name = %q + description = "Acceptance test run" + project_id = hcp_project.example.resource_id + } + + resource "hcp_vault_secrets_secret" "example" { + app_name = hcp_vault_secrets_app.example.app_name secret_name = "test_secret" secret_value = "super secret" + project_id = hcp_project.example.resource_id }`, testAppName2), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("hcp_vault_secrets_secret.example", "app_name", testAppName2), resource.TestCheckResourceAttr("hcp_vault_secrets_secret.example", "secret_name", "test_secret"), resource.TestCheckResourceAttr("hcp_vault_secrets_secret.example", "secret_value", "super secret"), - resource.TestCheckResourceAttr("hcp_vault_secrets_secret.example", "project_id", projectID), + resource.TestCheckResourceAttrSet("hcp_vault_secrets_secret.example", "project_id"), ), }, },