-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
New Data Source: azurerm_key_vault_secret_versions
#27393
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,216 @@ | ||||||||||
// Copyright (c) HashiCorp, Inc. | ||||||||||
// SPDX-License-Identifier: MPL-2.0 | ||||||||||
|
||||||||||
package keyvault | ||||||||||
|
||||||||||
import ( | ||||||||||
"context" | ||||||||||
"fmt" | ||||||||||
"sort" | ||||||||||
"strings" | ||||||||||
"time" | ||||||||||
|
||||||||||
"github.com/hashicorp/go-azure-helpers/resourcemanager/commonids" | ||||||||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" | ||||||||||
"github.com/hashicorp/terraform-provider-azurerm/internal/sdk" | ||||||||||
keyVaultValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/validate" | ||||||||||
"github.com/tombuildsstuff/kermit/sdk/keyvault/7.4/keyvault" | ||||||||||
) | ||||||||||
|
||||||||||
var _ sdk.DataSource = KeyVaultSecretVersionsDataSource{} | ||||||||||
|
||||||||||
type KeyVaultSecretVersionsDataSource struct{} | ||||||||||
|
||||||||||
type KeyVaultSecretVersionsDataSourceModel struct { | ||||||||||
Name string `tfschema:"name"` | ||||||||||
KeyVaultId string `tfschema:"key_vault_id"` | ||||||||||
MaxResults int64 `tfschema:"max_results"` | ||||||||||
Versions []secretVersionModel `tfschema:"versions"` | ||||||||||
} | ||||||||||
|
||||||||||
type secretVersionModel struct { | ||||||||||
ID string `tfschema:"id"` | ||||||||||
CreatedDate string `tfschema:"created_date"` | ||||||||||
Enabled bool `tfschema:"enabled"` | ||||||||||
NotBeforeDate string `tfschema:"not_before_date"` | ||||||||||
ExpirationDate string `tfschema:"expiration_date"` | ||||||||||
UpdatedDate string `tfschema:"updated_date"` | ||||||||||
Uri string `tfschema:"uri"` | ||||||||||
} | ||||||||||
|
||||||||||
func (r KeyVaultSecretVersionsDataSource) Arguments() map[string]*schema.Schema { | ||||||||||
return map[string]*schema.Schema{ | ||||||||||
"name": { | ||||||||||
Type: schema.TypeString, | ||||||||||
Required: true, | ||||||||||
ValidateFunc: keyVaultValidate.NestedItemName, | ||||||||||
}, | ||||||||||
|
||||||||||
"key_vault_id": { | ||||||||||
Type: schema.TypeString, | ||||||||||
Required: true, | ||||||||||
ValidateFunc: commonids.ValidateKeyVaultID, | ||||||||||
}, | ||||||||||
|
||||||||||
"max_results": { | ||||||||||
Type: schema.TypeInt, | ||||||||||
Optional: true, | ||||||||||
Default: 25, | ||||||||||
}, | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
func (r KeyVaultSecretVersionsDataSource) Attributes() map[string]*schema.Schema { | ||||||||||
return map[string]*schema.Schema{ | ||||||||||
"versions": { | ||||||||||
Type: schema.TypeList, | ||||||||||
Computed: true, | ||||||||||
Elem: &schema.Resource{ | ||||||||||
Schema: map[string]*schema.Schema{ | ||||||||||
"id": { | ||||||||||
Type: schema.TypeString, | ||||||||||
Computed: true, | ||||||||||
}, | ||||||||||
|
||||||||||
"created_date": { | ||||||||||
Type: schema.TypeString, | ||||||||||
Computed: true, | ||||||||||
}, | ||||||||||
|
||||||||||
"enabled": { | ||||||||||
Type: schema.TypeBool, | ||||||||||
Computed: true, | ||||||||||
}, | ||||||||||
|
||||||||||
"not_before_date": { | ||||||||||
Type: schema.TypeString, | ||||||||||
Computed: true, | ||||||||||
}, | ||||||||||
|
||||||||||
"expiration_date": { | ||||||||||
Type: schema.TypeString, | ||||||||||
Computed: true, | ||||||||||
}, | ||||||||||
|
||||||||||
"updated_date": { | ||||||||||
Type: schema.TypeString, | ||||||||||
Computed: true, | ||||||||||
}, | ||||||||||
"uri": { | ||||||||||
Type: schema.TypeString, | ||||||||||
Computed: true, | ||||||||||
}, | ||||||||||
}, | ||||||||||
}, | ||||||||||
}, | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
func (r KeyVaultSecretVersionsDataSource) ResourceType() string { | ||||||||||
return "azurerm_key_vault_secret_versions" | ||||||||||
} | ||||||||||
|
||||||||||
func (r KeyVaultSecretVersionsDataSource) ModelObject() interface{} { | ||||||||||
return &KeyVaultSecretVersionsDataSourceModel{} | ||||||||||
} | ||||||||||
|
||||||||||
func (r KeyVaultSecretVersionsDataSource) Read() sdk.ResourceFunc { | ||||||||||
return sdk.ResourceFunc{ | ||||||||||
Timeout: 5 * time.Minute, | ||||||||||
|
||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { | ||||||||||
client := metadata.Client.KeyVault.ManagementClient | ||||||||||
|
||||||||||
var model KeyVaultSecretVersionsDataSourceModel | ||||||||||
|
||||||||||
if err := metadata.Decode(&model); err != nil { | ||||||||||
return fmt.Errorf("decoding: %+v", err) | ||||||||||
} | ||||||||||
|
||||||||||
keyVaultId, err := commonids.ParseKeyVaultID(model.KeyVaultId) | ||||||||||
if err != nil { | ||||||||||
return err | ||||||||||
} | ||||||||||
|
||||||||||
keyVaultUri, err := metadata.Client.KeyVault.BaseUriForKeyVault(ctx, *keyVaultId) | ||||||||||
if err != nil { | ||||||||||
return err | ||||||||||
} | ||||||||||
|
||||||||||
maxResults32 := int32(model.MaxResults) | ||||||||||
|
||||||||||
resp, err := client.GetSecretVersions(ctx, *keyVaultUri, model.Name, &maxResults32) | ||||||||||
Comment on lines
+140
to
+142
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since this variable is only referenced once we would prefer to use the
Suggested change
|
||||||||||
if err != nil { | ||||||||||
return fmt.Errorf("making List Versions request on Azure KeyVault Secret %s: %+v", model.Name, err) | ||||||||||
} | ||||||||||
|
||||||||||
if resp.Values() != nil { | ||||||||||
for resp.NotDone() { | ||||||||||
for _, v := range resp.Values() { | ||||||||||
model.Versions = append(model.Versions, expandSecretVersion(&v)) | ||||||||||
} | ||||||||||
err = resp.NextWithContext(ctx) | ||||||||||
if err != nil { | ||||||||||
return fmt.Errorf("iterating over Secret Versions: %+v", err) | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
var errors []error | ||||||||||
model.Versions, errors = sortSecretVersions(model.Versions) | ||||||||||
if len(errors) > 0 { | ||||||||||
return fmt.Errorf("sorting Secret Versions: %+v", errors) | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
metadata.ResourceData.SetId(fmt.Sprintf("%s/%s", model.KeyVaultId, model.Name)) | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can generate the ID using existing functions in the keyvault package, we can then use the
Suggested change
|
||||||||||
|
||||||||||
return metadata.Encode(&model) | ||||||||||
}, | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
func expandSecretVersion(v *keyvault.SecretItem) secretVersionModel { | ||||||||||
var item secretVersionModel | ||||||||||
item.Uri = *v.ID | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From what I understand it's highly unlikely for keyvault that these fields are not returned or empty, but we can still use
Suggested change
|
||||||||||
item.ID = (*v.ID)[strings.LastIndex(*v.ID, "/")+1:] | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it'd be good to only do this transformation if |
||||||||||
item.CreatedDate = time.Time(*v.Attributes.Created).Format(time.RFC3339) | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We're nil checking the other dates/times below before casting, I think we should do that here as well
Suggested change
|
||||||||||
item.Enabled = *v.Attributes.Enabled | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same for this
Suggested change
|
||||||||||
if notBefore := v.Attributes.NotBefore; notBefore != nil { | ||||||||||
item.NotBeforeDate = time.Time(*notBefore).Format(time.RFC3339) | ||||||||||
} | ||||||||||
if expires := v.Attributes.Expires; expires != nil { | ||||||||||
item.ExpirationDate = time.Time(*expires).Format(time.RFC3339) | ||||||||||
} | ||||||||||
if updated := v.Attributes.Updated; updated != nil { | ||||||||||
item.UpdatedDate = time.Time(*updated).Format(time.RFC3339) | ||||||||||
} | ||||||||||
|
||||||||||
return item | ||||||||||
} | ||||||||||
|
||||||||||
func sortSecretVersions(values []secretVersionModel) ([]secretVersionModel, []error) { | ||||||||||
errors := make([]error, 0) | ||||||||||
sort.Slice(values, func(i, j int) bool { | ||||||||||
// Sort by CreatedDate in descending order | ||||||||||
timeA, err := time.Parse(time.RFC3339, values[i].CreatedDate) | ||||||||||
if err != nil { | ||||||||||
errors = append(errors, err) | ||||||||||
return false | ||||||||||
} | ||||||||||
|
||||||||||
timeB, err := time.Parse(time.RFC3339, values[j].CreatedDate) | ||||||||||
if err != nil { | ||||||||||
errors = append(errors, err) | ||||||||||
return false | ||||||||||
} | ||||||||||
|
||||||||||
return timeA.After(timeB) | ||||||||||
|
||||||||||
}) | ||||||||||
|
||||||||||
if len(errors) > 0 { | ||||||||||
return values, errors | ||||||||||
} | ||||||||||
|
||||||||||
return values, nil | ||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
// Copyright (c) HashiCorp, Inc. | ||
// SPDX-License-Identifier: MPL-2.0 | ||
|
||
package keyvault_test | ||
|
||
import ( | ||
"fmt" | ||
"regexp" | ||
"testing" | ||
|
||
"github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" | ||
"github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check" | ||
) | ||
|
||
type KeyVaultSecretVersionsDataSource struct{} | ||
|
||
func TestAccDataSourceKeyVaultSecretVersions_basic(t *testing.T) { | ||
data := acceptance.BuildTestData(t, "data.azurerm_key_vault_secret_versions", "test") | ||
r := KeyVaultSecretVersionsDataSource{} | ||
|
||
data.DataSourceTest(t, []acceptance.TestStep{ | ||
{ | ||
Config: r.basic(data), | ||
Check: acceptance.ComposeTestCheckFunc( | ||
check.That(data.ResourceName).Key("versions.#").HasValue("1"), | ||
check.That(data.ResourceName).Key("versions.0.enabled").HasValue("true"), | ||
check.That(data.ResourceName).Key("versions.0.created_date").MatchesRegex(regexp.MustCompile(`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$`)), | ||
check.That(data.ResourceName).Key("versions.0.id").MatchesRegex(regexp.MustCompile(`^\w+$`)), | ||
), | ||
}, | ||
}) | ||
} | ||
|
||
func TestAccDataSourceKeyVaultSecretVersions_complete(t *testing.T) { | ||
data := acceptance.BuildTestData(t, "data.azurerm_key_vault_secret_versions", "test") | ||
r := KeyVaultSecretVersionsDataSource{} | ||
|
||
data.DataSourceTest(t, []acceptance.TestStep{ | ||
{ | ||
Config: r.complete(data), | ||
Check: acceptance.ComposeTestCheckFunc( | ||
check.That(data.ResourceName).Key("versions.#").HasValue("1"), | ||
check.That(data.ResourceName).Key("versions.0.enabled").HasValue("true"), | ||
check.That(data.ResourceName).Key("versions.0.created_date").MatchesRegex(regexp.MustCompile(`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$`)), | ||
check.That(data.ResourceName).Key("versions.0.not_before_date").HasValue("2019-01-01T01:02:03Z"), | ||
check.That(data.ResourceName).Key("versions.0.expiration_date").HasValue("2020-01-01T01:02:03Z"), | ||
check.That(data.ResourceName).Key("versions.0.updated_date").MatchesRegex(regexp.MustCompile(`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$`)), | ||
check.That(data.ResourceName).Key("versions.0.id").MatchesRegex(regexp.MustCompile(`^\w+$`)), | ||
check.That(data.ResourceName).Key("versions.0.uri").MatchesRegex(regexp.MustCompile(`^https://acctestkv-\w+.vault.azure.net/secrets/secret-\w+/\w+$`)), | ||
), | ||
}, | ||
}) | ||
} | ||
|
||
func (KeyVaultSecretVersionsDataSource) basic(data acceptance.TestData) string { | ||
return fmt.Sprintf(` | ||
%s | ||
|
||
data "azurerm_key_vault_secret_versions" "test" { | ||
name = azurerm_key_vault_secret.test.name | ||
key_vault_id = azurerm_key_vault.test.id | ||
} | ||
`, KeyVaultSecretResource{}.basic(data)) | ||
} | ||
|
||
func (KeyVaultSecretVersionsDataSource) complete(data acceptance.TestData) string { | ||
return fmt.Sprintf(` | ||
%s | ||
|
||
data "azurerm_key_vault_secret_versions" "test" { | ||
name = azurerm_key_vault_secret.test.name | ||
key_vault_id = azurerm_key_vault.test.id | ||
} | ||
`, KeyVaultSecretResource{}.complete(data)) | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,73 @@ | ||||||||||||||||||
--- | ||||||||||||||||||
subcategory: "Key Vault" | ||||||||||||||||||
layout: "azurerm" | ||||||||||||||||||
page_title: "Azure Resource Manager: Data Source: azurerm_key_vault_secret_versions" | ||||||||||||||||||
description: |- | ||||||||||||||||||
Get a list of versions for an existing Key Vault Secret. | ||||||||||||||||||
--- | ||||||||||||||||||
|
||||||||||||||||||
# Data Source: azurerm_key_vault_secret_versions | ||||||||||||||||||
|
||||||||||||||||||
Use this data source to access information about an existing Key Vault Secret's versions. The secret version values is not included. The `key_vault_secret` data source can be used to retrieve the value of a given secret version using it's `id`. | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With the introduction of ephemeral resources we might want to point users towards the ephemeral resource instead of the data source
Suggested change
|
||||||||||||||||||
|
||||||||||||||||||
## Example Usage | ||||||||||||||||||
|
||||||||||||||||||
```hcl | ||||||||||||||||||
data "azurerm_key_vault" "example" { | ||||||||||||||||||
name = "mykeyvault" | ||||||||||||||||||
resource_group_name = "some-resource-group" | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
data "azurerm_key_vault_secret_versions" "example" { | ||||||||||||||||||
name = "mysecret" | ||||||||||||||||||
key_vault_id = data.azurerm_key_vault.example.id | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
output "versions" { | ||||||||||||||||||
value = data.azurerm_key_vault_secret_versions.example.versions | ||||||||||||||||||
} | ||||||||||||||||||
``` | ||||||||||||||||||
|
||||||||||||||||||
## Arguments Reference | ||||||||||||||||||
|
||||||||||||||||||
The following arguments are supported: | ||||||||||||||||||
|
||||||||||||||||||
* `key_vault_id` - (Required) The ID of the Key Vault containing the secret. | ||||||||||||||||||
|
||||||||||||||||||
* `name` - (Required) The name of the Key Vault Secret to retrieve versions from. | ||||||||||||||||||
|
||||||||||||||||||
--- | ||||||||||||||||||
|
||||||||||||||||||
* `max_results` - (Optional) Maximum number of versions to retrieve. Defaults to `25`. | ||||||||||||||||||
Comment on lines
+37
to
+41
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A space here is sufficient
Suggested change
|
||||||||||||||||||
|
||||||||||||||||||
## Attributes Reference | ||||||||||||||||||
|
||||||||||||||||||
In addition to the Arguments listed above - the following Attributes are exported: | ||||||||||||||||||
|
||||||||||||||||||
* `id` - The ID of the Key Vault Secret. | ||||||||||||||||||
|
||||||||||||||||||
* `versions` - A `versions` list as defined below. The list of versions are sorted by `created_date` descending, meaning the most recently created secret version will be first in the list. | ||||||||||||||||||
|
||||||||||||||||||
--- | ||||||||||||||||||
|
||||||||||||||||||
The `versions` entries in the list export the following: | ||||||||||||||||||
|
||||||||||||||||||
* `created_date` - The date and time when the Key Vault Secret version was created. | ||||||||||||||||||
|
||||||||||||||||||
* `enabled` - Is the version enabled? Returns a `bool` value. | ||||||||||||||||||
|
||||||||||||||||||
* `expiration_date` - The date and time at which the Key Vault Secret version expires and is no longer valid. | ||||||||||||||||||
|
||||||||||||||||||
* `id` - The Key Vault Secret version ID. | ||||||||||||||||||
|
||||||||||||||||||
* `not_before_date` - The earliest date and time at which the Key Vault Secret version can be used. | ||||||||||||||||||
|
||||||||||||||||||
* `updated_date` - The date and time when the Key Vault Secret version was last updated. | ||||||||||||||||||
|
||||||||||||||||||
* `uri` - The full URI of the secret version. | ||||||||||||||||||
|
||||||||||||||||||
## Timeouts | ||||||||||||||||||
|
||||||||||||||||||
The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/language/resources/syntax#operation-timeouts) for certain actions: | ||||||||||||||||||
|
||||||||||||||||||
* `read` - (Defaults to 5 minutes) Used when retrieving the Key Vault Secret Versions. |
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.
We should reference kermit from jackofallops