generated from hashicorp/terraform-provider-scaffolding
-
Notifications
You must be signed in to change notification settings - Fork 50
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
vault secrets | rotated secrets data source #854
Merged
Merged
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
0b797ae
switch some methods over
dhuckins 7aec075
update vault_secrets_secret
dhuckins 2705021
rotated secrets to map
dhuckins f37d2de
add changelog
dhuckins e2ed1ce
Update internal/provider/vaultsecrets/data_source_vault_secrets_secre…
dhuckins 98f0f84
Merge branch 'main' into dh/vault-secrets/rotated-secrets2
dhuckins 846054f
add new data source
dhuckins 8a7c4ad
add some fields
dhuckins 669fd01
not really adding a test...
dhuckins f7c7cfd
remove tests for now
dhuckins a98fdb6
rename
dhuckins d259623
lint
dhuckins 9d170a4
create test
dhuckins 28d011a
rename
dhuckins 2877528
rename for lint
dhuckins 4f32af3
Merge branch 'main' of github.com:hashicorp/terraform-provider-hcp in…
dhuckins 6841583
update api
dhuckins 6d35bc9
Merge branch 'main' of github.com:hashicorp/terraform-provider-hcp in…
dhuckins 3ff8616
added change log
dhuckins 30dbb73
block until the secret is created
dhuckins 8288a7e
break in wrong place
dhuckins 88141af
test passes
dhuckins c089e2a
rename
dhuckins 58d9c33
Merge branch 'main' into dh/vault-secrets/rotated-secrets-data-source
dhuckins File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
```release-note:feature | ||
add vault_secrets_rotating_secret data source | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
148 changes: 148 additions & 0 deletions
148
internal/provider/vaultsecrets/data_source_vault_secrets_rotating_secret.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
// Copyright (c) HashiCorp, Inc. | ||
// SPDX-License-Identifier: MPL-2.0 | ||
|
||
package vaultsecrets | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
sharedmodels "github.com/hashicorp/hcp-sdk-go/clients/cloud-shared/v1/models" | ||
"github.com/hashicorp/terraform-plugin-framework/datasource" | ||
"github.com/hashicorp/terraform-plugin-framework/datasource/schema" | ||
"github.com/hashicorp/terraform-plugin-framework/types" | ||
"github.com/hashicorp/terraform-provider-hcp/internal/clients" | ||
) | ||
|
||
type DataSourceVaultSecretsRotatingSecret struct { | ||
client *clients.Client | ||
} | ||
|
||
type DataSourceVaultSecretsRotatingSecretModel struct { | ||
ID types.String `tfsdk:"id"` | ||
AppName types.String `tfsdk:"app_name"` | ||
ProjectID types.String `tfsdk:"project_id"` | ||
OrgID types.String `tfsdk:"organization_id"` | ||
SecretName types.String `tfsdk:"secret_name"` | ||
SecretValues types.Map `tfsdk:"secret_values"` | ||
SecretVersion types.Int64 `tfsdk:"secret_version"` | ||
SecretProvider types.String `tfsdk:"secret_provider"` | ||
} | ||
|
||
func NewVaultSecretsRotatingSecretDataSource() datasource.DataSource { | ||
return &DataSourceVaultSecretsRotatingSecret{} | ||
} | ||
|
||
func (d *DataSourceVaultSecretsRotatingSecret) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { | ||
resp.TypeName = req.ProviderTypeName + "_vault_secrets_rotating_secret" | ||
} | ||
|
||
func (d *DataSourceVaultSecretsRotatingSecret) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { | ||
resp.Schema = schema.Schema{ | ||
MarkdownDescription: "The Vault Secrets secret data source retrieves a rotating secret with its latest version.", | ||
Attributes: map[string]schema.Attribute{ | ||
"id": schema.StringAttribute{ | ||
Computed: true, | ||
Description: "The ID of this resource.", | ||
}, | ||
"app_name": schema.StringAttribute{ | ||
Description: "The name of the Vault Secrets application.", | ||
Required: true, | ||
}, | ||
"secret_name": schema.StringAttribute{ | ||
Description: "The name of the Vault Secrets secret.", | ||
Required: true, | ||
}, | ||
"secret_values": schema.MapAttribute{ | ||
Description: "The secret values corresponding to the secret name input.", | ||
Computed: true, | ||
Sensitive: true, | ||
ElementType: types.StringType, | ||
}, | ||
"secret_version": schema.Int64Attribute{ | ||
Description: "The version of the Vault Secrets secret.", | ||
Computed: true, | ||
}, | ||
"secret_provider": schema.StringAttribute{ | ||
Description: "The name of the provider this rotating secret is for", | ||
Computed: true, | ||
}, | ||
"organization_id": schema.StringAttribute{ | ||
Description: "The ID of the HCP organization where the Vault Secrets app is located.", | ||
Computed: true, | ||
}, | ||
"project_id": schema.StringAttribute{ | ||
Description: "The ID of the HCP project where the Vault Secrets app is located.", | ||
Computed: true, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func (d *DataSourceVaultSecretsRotatingSecret) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { | ||
if req.ProviderData == nil { | ||
return | ||
} | ||
|
||
client, ok := req.ProviderData.(*clients.Client) | ||
if !ok { | ||
resp.Diagnostics.AddError( | ||
"Unexpected Data Source Configure Type", | ||
fmt.Sprintf("Expected *clients.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), | ||
) | ||
return | ||
} | ||
d.client = client | ||
} | ||
|
||
func (d *DataSourceVaultSecretsRotatingSecret) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { | ||
var data DataSourceVaultSecretsRotatingSecretModel | ||
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) | ||
|
||
client := d.client | ||
if client == nil { | ||
resp.Diagnostics.AddError( | ||
"Unconfigured HCP Client", | ||
"Expected configured HCP client. Please report this issue to the provider developers.", | ||
) | ||
return | ||
} | ||
|
||
loc := &sharedmodels.HashicorpCloudLocationLocation{ | ||
OrganizationID: client.Config.OrganizationID, | ||
ProjectID: client.Config.ProjectID, | ||
} | ||
|
||
openSecret, err := clients.OpenVaultSecretsAppSecret(ctx, client, loc, data.AppName.ValueString(), data.SecretName.ValueString()) | ||
if err != nil { | ||
resp.Diagnostics.AddError(err.Error(), "Unable to open secret") | ||
return | ||
} | ||
|
||
var secretValues map[string]string | ||
var secretVersion int64 | ||
switch { | ||
case openSecret.RotatingVersion != nil: | ||
secretValues = openSecret.RotatingVersion.Values | ||
secretVersion = openSecret.RotatingVersion.Version | ||
default: | ||
resp.Diagnostics.AddError( | ||
"Unsupported HCP Secret type", | ||
fmt.Sprintf("HCP Secrets secret type %q is not currently supported by terraform-provider-hcp", openSecret.Type), | ||
) | ||
return | ||
} | ||
|
||
secretsOutput, diag := types.MapValueFrom(ctx, types.StringType, secretValues) | ||
resp.Diagnostics.Append(diag...) | ||
|
||
// TODO: what is ID supposed to be? | ||
// data.ID = ? | ||
data.OrgID = types.StringValue(client.Config.OrganizationID) | ||
data.ProjectID = types.StringValue(client.Config.ProjectID) | ||
data.SecretValues = secretsOutput | ||
data.SecretVersion = types.Int64Value(secretVersion) | ||
data.SecretProvider = types.StringValue(openSecret.Provider) | ||
|
||
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) | ||
} |
139 changes: 139 additions & 0 deletions
139
internal/provider/vaultsecrets/data_source_vault_secrets_rotating_secret_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
package vaultsecrets_test | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"os" | ||
"testing" | ||
"time" | ||
|
||
sharedmodels "github.com/hashicorp/hcp-sdk-go/clients/cloud-shared/v1/models" | ||
"github.com/hashicorp/hcp-sdk-go/clients/cloud-vault-secrets/preview/2023-11-28/client/secret_service" | ||
secretmodels "github.com/hashicorp/hcp-sdk-go/clients/cloud-vault-secrets/preview/2023-11-28/models" | ||
"github.com/hashicorp/terraform-plugin-testing/helper/resource" | ||
"github.com/hashicorp/terraform-plugin-testing/terraform" | ||
|
||
"github.com/hashicorp/terraform-provider-hcp/internal/clients" | ||
"github.com/hashicorp/terraform-provider-hcp/internal/provider/acctest" | ||
) | ||
|
||
func checkRequiredEnvVarOrFail(t *testing.T, varName string) string { | ||
value, exists := os.LookupEnv(varName) | ||
if !exists { | ||
t.Skipf("%s must be set to execute this test", varName) | ||
} | ||
return value | ||
} | ||
|
||
func TestAcc_dataSourceVaultSecretsRotatingSecret(t *testing.T) { | ||
|
||
mongodbAtlasPublicKey := checkRequiredEnvVarOrFail(t, "MONGODB_ATLAS_API_PUBLIC_KEY") | ||
mongodbAtlasPrivateKey := checkRequiredEnvVarOrFail(t, "MONGODB_ATLAS_API_PRIVATE_KEY") | ||
mongodbAtlasGroupID := checkRequiredEnvVarOrFail(t, "MONGODB_ATLAS_GROUP_ID") | ||
mongodbAtlasDBName := checkRequiredEnvVarOrFail(t, "MONGODB_ATLAS_DB_NAME") | ||
|
||
testAppName := generateRandomSlug() | ||
testIntegrationName := generateRandomSlug() | ||
dataSourceAddress := "data.hcp_vault_secrets_rotating_secret.foo" | ||
|
||
testSecretName := "secret_one" | ||
|
||
tfconfig := fmt.Sprintf(`data "hcp_vault_secrets_rotating_secret" "foo" { | ||
app_name = %q | ||
secret_name = %q | ||
}`, testAppName, testSecretName) | ||
|
||
client := acctest.HCPClients(t) | ||
loc := &sharedmodels.HashicorpCloudLocationLocation{ | ||
OrganizationID: client.Config.OrganizationID, | ||
ProjectID: client.Config.ProjectID, | ||
} | ||
|
||
resource.Test(t, resource.TestCase{ | ||
PreCheck: func() { acctest.PreCheck(t) }, | ||
ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, | ||
Steps: []resource.TestStep{ | ||
{ | ||
PreConfig: func() { | ||
createTestApp(t, testAppName) | ||
ctx := context.Background() | ||
|
||
_, err := clients.CreateMongoDBAtlasRotationIntegration(ctx, client, loc, testIntegrationName, mongodbAtlasPublicKey, mongodbAtlasPrivateKey) | ||
if err != nil { | ||
t.Fatalf("could not create mongodb rotation integration: %v", err) | ||
} | ||
|
||
reqBody := secret_service.CreateMongoDBAtlasRotatingSecretBody{ | ||
SecretName: testSecretName, | ||
RotationIntegrationName: testIntegrationName, | ||
RotationPolicyName: "built-in:30-days-2-active", | ||
MongodbGroupID: mongodbAtlasGroupID, | ||
MongodbRoles: []*secretmodels.Secrets20231128MongoDBRole{ | ||
{ | ||
DatabaseName: mongodbAtlasDBName, | ||
RoleName: "read", | ||
CollectionName: "", | ||
}, | ||
}, | ||
} | ||
_, err = clients.CreateMongoDBAtlasRotatingSecret(ctx, client, loc, testAppName, reqBody) | ||
if err != nil { | ||
t.Fatalf("could not create rotating mongodb atlas secret: %v", err) | ||
} | ||
|
||
// block until the secret is done | ||
timeout := time.AfterFunc(10*time.Minute, func() { | ||
t.Fatalf("timed out waiting for mongodb rotating secret to be created") | ||
}) | ||
|
||
waitForSecret := func() { | ||
for { | ||
state, err := clients.GetRotatingSecretState(ctx, client, loc, testAppName, testSecretName) | ||
if err != nil { | ||
t.Fatalf("could not get rotating secret state: %v", err) | ||
} | ||
switch *state.Status { | ||
case secretmodels.Secrets20231128RotatingSecretStatusERRORED: | ||
t.Fatalf("error rotating secret: %q", state.ErrorMessage) | ||
case secretmodels.Secrets20231128RotatingSecretStatusWAITINGFORNEXTROTATION: | ||
timeout.Stop() | ||
t.Log("secret successfully rotated") | ||
return | ||
default: | ||
t.Log("waiting to check rotating secret state") | ||
time.Sleep(10 * time.Second) | ||
} | ||
} | ||
} | ||
|
||
waitForSecret() | ||
|
||
}, | ||
Config: tfconfig, | ||
Check: resource.ComposeTestCheckFunc( | ||
resource.TestCheckResourceAttrSet(dataSourceAddress, "organization_id"), | ||
resource.TestCheckResourceAttrSet(dataSourceAddress, "project_id"), | ||
resource.TestCheckResourceAttr(dataSourceAddress, "secret_values.%", "2"), // required: check the number of elements in the map | ||
resource.TestCheckResourceAttr(dataSourceAddress, "app_name", testAppName), | ||
resource.TestCheckResourceAttr(dataSourceAddress, "secret_provider", "mongodb-atlas"), | ||
), | ||
}, | ||
}, | ||
CheckDestroy: func(_ *terraform.State) error { | ||
ctx := context.Background() | ||
err := clients.DeleteVaultSecretsAppSecret(ctx, client, loc, testAppName, testSecretName) | ||
if err != nil { | ||
return fmt.Errorf("could not delete rotating secret: %v", err) | ||
} | ||
|
||
err = clients.DeleteMongoDBAtlasRotationIntegration(ctx, client, loc, testIntegrationName) | ||
if err != nil { | ||
return fmt.Errorf("could not delete rotation integration: %v", err) | ||
} | ||
|
||
deleteTestApp(t, testAppName) | ||
|
||
return nil | ||
}, | ||
}) | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this ID actually used for unique identification of data sources or by anything TF-provider-specific? If not, maybe one option is to just remove it altogether.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
im honestly not sure, would like some feedback from @hashicorp/cloud-foundations
otherwise maybe something like
project/<project id>/app/<app name>/secret/<secret name>
wdyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If our goal here is a unique resource identifier, this will almost work. The only scenario where I see it failing is if you delete the secret and re-create it with the same name. However, this is much better than the AppName that we are using elsewhere.
My preferred solution would still be to remove it if possible.