From 6725fca40aba0ee8752a41bd9401b343db331e78 Mon Sep 17 00:00:00 2001 From: Gabriel Rimes Date: Tue, 21 Jan 2025 22:43:53 -0300 Subject: [PATCH 01/12] add minimal structure for generic secret item --- internal/consts/consts.go | 2 + vault/data_source_generic_secret_item.go | 75 ++++++++++++++ vault/provider.go | 8 ++ vault/resource_generic_secret_item.go | 125 +++++++++++++++++++++++ 4 files changed, 210 insertions(+) create mode 100644 vault/data_source_generic_secret_item.go create mode 100644 vault/resource_generic_secret_item.go diff --git a/internal/consts/consts.go b/internal/consts/consts.go index 8f33f0a5e..f30073ef0 100644 --- a/internal/consts/consts.go +++ b/internal/consts/consts.go @@ -52,6 +52,8 @@ const ( FieldLeaseRenewable = "lease_renewable" FieldDepth = "depth" FieldDataJSON = "data_json" + FieldKey = "key" + FieldValue = "value" FieldDN = "dn" FieldRole = "role" FieldRoles = "roles" diff --git a/vault/data_source_generic_secret_item.go b/vault/data_source_generic_secret_item.go new file mode 100644 index 000000000..4b9bd1e25 --- /dev/null +++ b/vault/data_source_generic_secret_item.go @@ -0,0 +1,75 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package vault + +import ( + "fmt" + "log" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/hashicorp/terraform-provider-vault/internal/consts" + "github.com/hashicorp/terraform-provider-vault/internal/provider" +) + +func genericSecretItemDataSource() *schema.Resource { + return &schema.Resource{ + Read: provider.ReadWrapper(genericSecretItemDataSourceRead), + + Schema: map[string]*schema.Schema{ + consts.FieldPath: { + Type: schema.TypeString, + Required: true, + Description: "Full path from which a secret will be read.", + }, + + "id": { + Type: schema.TypeString, + Computed: true, + }, + + consts.FieldKey: { + Type: schema.TypeString, + Required: true, + Description: "Name of the secret item to read.", + }, + + consts.FieldValue: { + Type: schema.TypeString, + Required: false, + Computed: true, + Description: "Content of the secret item to read.", + Sensitive: false, + }, + }, + } +} + +func genericSecretItemDataSourceRead(d *schema.ResourceData, meta interface{}) error { + client, e := provider.GetClient(d, meta) + if e != nil { + return e + } + + path := d.Get("path").(string) + + secret, err := versionedSecret(-1, path, client) + if err != nil { + return fmt.Errorf("error reading from Vault: %s", err) + } + if secret == nil { + return fmt.Errorf("no secret found at %q", path) + } + + log.Println("__________>", d.Get("id").(string)) + if err := d.Set(consts.FieldKey, d.Get(consts.FieldKey)); err != nil { + return err + } + + if err := d.Set(consts.FieldValue, secret.Data[d.Get(consts.FieldKey).(string)]); err != nil { + return err + } + + return nil +} diff --git a/vault/provider.go b/vault/provider.go index 1a1ced330..31012dc95 100644 --- a/vault/provider.go +++ b/vault/provider.go @@ -107,6 +107,10 @@ var ( Resource: UpdateSchemaResource(genericSecretDataSource()), PathInventory: []string{"/secret/data/{path}"}, }, + "vault_generic_secret_item": { + Resource: UpdateSchemaResource(genericSecretItemDataSource()), + PathInventory: []string{"/secret/data/{path}"}, + }, "vault_policy_document": { Resource: UpdateSchemaResource(policyDocumentDataSource()), PathInventory: []string{"/sys/policy/{name}"}, @@ -385,6 +389,10 @@ var ( Resource: UpdateSchemaResource(genericSecretResource("vault_generic_secret")), PathInventory: []string{GenericPath}, }, + "vault_generic_secret_item": { + Resource: UpdateSchemaResource(genericSecretItemResource("vault_generic_secret_item")), + PathInventory: []string{GenericPath}, + }, "vault_jwt_auth_backend": { Resource: UpdateSchemaResource(jwtAuthBackendResource()), PathInventory: []string{"/auth/jwt/config"}, diff --git a/vault/resource_generic_secret_item.go b/vault/resource_generic_secret_item.go new file mode 100644 index 000000000..1b56f4a8a --- /dev/null +++ b/vault/resource_generic_secret_item.go @@ -0,0 +1,125 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package vault + +import ( + "encoding/json" + "fmt" + "log" + + "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/hashicorp/terraform-provider-vault/internal/consts" + "github.com/hashicorp/terraform-provider-vault/internal/provider" +) + +func genericSecretItemResource(name string) *schema.Resource { + return &schema.Resource{ + SchemaVersion: 1, + + Create: genericSecretItemResourceWrite, + Update: genericSecretItemResourceWrite, + Delete: genericSecretItemResourceDelete, + Read: provider.ReadWrapper(genericSecretItemResourceRead), + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + MigrateState: resourceGenericSecretMigrateState, + + Schema: map[string]*schema.Schema{ + consts.FieldPath: { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Full path where the generic secret will be written.", + }, + + "id": { + Type: schema.TypeString, + Computed: true, + }, + + consts.FieldKey: { + Type: schema.TypeString, + Required: true, + Description: "Name of the secret to write.", + }, + + consts.FieldValue: { + Type: schema.TypeString, + Required: true, + Description: "Content of the secret to write.", + Sensitive: true, + }, + }, + } +} + +func genericSecretItemResourceWrite(d *schema.ResourceData, meta interface{}) error { + client, e := provider.GetClient(d, meta) + if e != nil { + return e + } + + path := d.Get(consts.FieldPath).(string) + + secret, err := versionedSecret(-1, path, client) + if err != nil { + return fmt.Errorf("error reading from Vault: %s", err) + } + if secret == nil { + return fmt.Errorf("no secret found at %q", path) + } + + identifier := uuid.New().String() + + d.SetId(identifier) + d.Set("id", identifier) + + return nil +} + +func genericSecretItemResourceDelete(d *schema.ResourceData, meta interface{}) error { + log.Println("genericSecretItemResourceDelete") + return nil +} + +func genericSecretItemResourceRead(d *schema.ResourceData, meta interface{}) error { + client, e := provider.GetClient(d, meta) + if e != nil { + return e + } + + path := d.Get(consts.FieldPath).(string) + + secret, err := versionedSecret(-1, path, client) + if err != nil { + return fmt.Errorf("error reading from Vault: %s", err) + } + if secret == nil { + return fmt.Errorf("no secret found at %q", path) + } + + data := secret.Data + jsonData, err := json.Marshal(secret.Data) + if err != nil { + return fmt.Errorf("error marshaling JSON for %q: %s", path, err) + } + + fmt.Println("data--------------", data, jsonData) + + // if err := d.Set(consts.FieldDataJSON, string(jsonData)); err != nil { + // return err + // } + // if err := d.Set(consts.FieldPath, path); err != nil { + // return err + // } + + // if err := d.Set("data", data); err != nil { + // return err + // } + + return nil +} From f93ce5b33dc8290d378610a3dbfa0c94c5f92300 Mon Sep 17 00:00:00 2001 From: Gabriel Rimes Date: Wed, 22 Jan 2025 11:51:43 -0300 Subject: [PATCH 02/12] feat: add logic to create, update and delete a secret item --- vault/data_source_generic_secret_item.go | 20 ++-- vault/resource_generic_secret_item.go | 120 ++++++++++++++++------- 2 files changed, 97 insertions(+), 43 deletions(-) diff --git a/vault/data_source_generic_secret_item.go b/vault/data_source_generic_secret_item.go index 4b9bd1e25..b8cb2cfcb 100644 --- a/vault/data_source_generic_secret_item.go +++ b/vault/data_source_generic_secret_item.go @@ -5,7 +5,6 @@ package vault import ( "fmt" - "log" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -24,11 +23,6 @@ func genericSecretItemDataSource() *schema.Resource { Description: "Full path from which a secret will be read.", }, - "id": { - Type: schema.TypeString, - Computed: true, - }, - consts.FieldKey: { Type: schema.TypeString, Required: true, @@ -40,7 +34,7 @@ func genericSecretItemDataSource() *schema.Resource { Required: false, Computed: true, Description: "Content of the secret item to read.", - Sensitive: false, + Sensitive: true, }, }, } @@ -52,7 +46,8 @@ func genericSecretItemDataSourceRead(d *schema.ResourceData, meta interface{}) e return e } - path := d.Get("path").(string) + path := d.Get(consts.FieldPath).(string) + key := d.Get(consts.FieldKey).(string) secret, err := versionedSecret(-1, path, client) if err != nil { @@ -62,8 +57,13 @@ func genericSecretItemDataSourceRead(d *schema.ResourceData, meta interface{}) e return fmt.Errorf("no secret found at %q", path) } - log.Println("__________>", d.Get("id").(string)) - if err := d.Set(consts.FieldKey, d.Get(consts.FieldKey)); err != nil { + if _, ok := secret.Data[key]; !ok { + return fmt.Errorf("no secret item named %q was found", key) + } + + d.SetId(key) + + if err := d.Set(consts.FieldKey, key); err != nil { return err } diff --git a/vault/resource_generic_secret_item.go b/vault/resource_generic_secret_item.go index 1b56f4a8a..04ab02975 100644 --- a/vault/resource_generic_secret_item.go +++ b/vault/resource_generic_secret_item.go @@ -4,15 +4,13 @@ package vault import ( - "encoding/json" "fmt" - "log" - "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-vault/internal/consts" "github.com/hashicorp/terraform-provider-vault/internal/provider" + "github.com/hashicorp/terraform-provider-vault/util" ) func genericSecretItemResource(name string) *schema.Resource { @@ -26,31 +24,25 @@ func genericSecretItemResource(name string) *schema.Resource { Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, - MigrateState: resourceGenericSecretMigrateState, Schema: map[string]*schema.Schema{ consts.FieldPath: { Type: schema.TypeString, Required: true, ForceNew: true, - Description: "Full path where the generic secret will be written.", - }, - - "id": { - Type: schema.TypeString, - Computed: true, + Description: "Full path where the generic secret item will be written.", }, consts.FieldKey: { Type: schema.TypeString, Required: true, - Description: "Name of the secret to write.", + Description: "Name of the secret item to write.", }, consts.FieldValue: { Type: schema.TypeString, Required: true, - Description: "Content of the secret to write.", + Description: "Content of the secret item to write.", Sensitive: true, }, }, @@ -64,6 +56,7 @@ func genericSecretItemResourceWrite(d *schema.ResourceData, meta interface{}) er } path := d.Get(consts.FieldPath).(string) + key := d.Get(consts.FieldKey).(string) secret, err := versionedSecret(-1, path, client) if err != nil { @@ -73,26 +66,50 @@ func genericSecretItemResourceWrite(d *schema.ResourceData, meta interface{}) er return fmt.Errorf("no secret found at %q", path) } - identifier := uuid.New().String() + d.SetId(key) - d.SetId(identifier) - d.Set("id", identifier) + for k, v := range secret.Data { + if k == key { + if v == d.Get(consts.FieldValue).(string) { + return nil + } - return nil -} + break + } + } -func genericSecretItemResourceDelete(d *schema.ResourceData, meta interface{}) error { - log.Println("genericSecretItemResourceDelete") - return nil + secret.Data[key] = d.Get(consts.FieldValue).(string) + + data := secret.Data + + mountPath, v2, err := isKVv2(path, client) + if err != nil { + return fmt.Errorf("error determining if it's a v2 path: %s", err) + } + + if v2 { + path = addPrefixToVKVPath(path, mountPath, "data") + data = map[string]interface{}{ + "data": data, + "options": map[string]interface{}{}, + } + } + + if _, err := util.RetryWrite(client, path, data, util.DefaultRequestOpts()); err != nil { + return err + } + + return genericSecretItemResourceRead(d, meta) } -func genericSecretItemResourceRead(d *schema.ResourceData, meta interface{}) error { +func genericSecretItemResourceDelete(d *schema.ResourceData, meta interface{}) error { client, e := provider.GetClient(d, meta) if e != nil { return e } path := d.Get(consts.FieldPath).(string) + key := d.Get(consts.FieldKey).(string) secret, err := versionedSecret(-1, path, client) if err != nil { @@ -102,24 +119,61 @@ func genericSecretItemResourceRead(d *schema.ResourceData, meta interface{}) err return fmt.Errorf("no secret found at %q", path) } + for k := range secret.Data { + if k == key { + delete(secret.Data, k) + break + } + } + data := secret.Data - jsonData, err := json.Marshal(secret.Data) + + mountPath, v2, err := isKVv2(path, client) if err != nil { - return fmt.Errorf("error marshaling JSON for %q: %s", path, err) + return fmt.Errorf("error determining if it's a v2 path: %s", err) } - fmt.Println("data--------------", data, jsonData) + if v2 { + path = addPrefixToVKVPath(path, mountPath, "data") + data = map[string]interface{}{ + "data": data, + "options": map[string]interface{}{}, + } + } - // if err := d.Set(consts.FieldDataJSON, string(jsonData)); err != nil { - // return err - // } - // if err := d.Set(consts.FieldPath, path); err != nil { - // return err - // } + if _, err := util.RetryWrite(client, path, data, util.DefaultRequestOpts()); err != nil { + return err + } - // if err := d.Set("data", data); err != nil { - // return err - // } + return nil +} + +func genericSecretItemResourceRead(d *schema.ResourceData, meta interface{}) error { + client, e := provider.GetClient(d, meta) + if e != nil { + return e + } + + path := d.Get(consts.FieldPath).(string) + key := d.Get(consts.FieldKey).(string) + + secret, err := versionedSecret(-1, path, client) + if err != nil { + return fmt.Errorf("error reading from Vault: %s", err) + } + if secret == nil { + return fmt.Errorf("no secret found at %q", path) + } + + d.SetId(key) + + if err := d.Set(consts.FieldKey, key); err != nil { + return err + } + + if err := d.Set(consts.FieldValue, secret.Data[d.Get(consts.FieldKey).(string)]); err != nil { + return err + } return nil } From ed2e656ff702f9cac144338f1c6d70909ddaf5e0 Mon Sep 17 00:00:00 2001 From: Gabriel Rimes Date: Wed, 22 Jan 2025 18:43:16 -0300 Subject: [PATCH 03/12] feat: add mutexes to control concurrency on loops --- vault/provider.go | 2 +- vault/resource_generic_secret_item.go | 49 +++++++++++++++++++++------ 2 files changed, 39 insertions(+), 12 deletions(-) diff --git a/vault/provider.go b/vault/provider.go index 31012dc95..f633d1652 100644 --- a/vault/provider.go +++ b/vault/provider.go @@ -390,7 +390,7 @@ var ( PathInventory: []string{GenericPath}, }, "vault_generic_secret_item": { - Resource: UpdateSchemaResource(genericSecretItemResource("vault_generic_secret_item")), + Resource: UpdateSchemaResource(genericSecretItemResource()), PathInventory: []string{GenericPath}, }, "vault_jwt_auth_backend": { diff --git a/vault/resource_generic_secret_item.go b/vault/resource_generic_secret_item.go index 04ab02975..c0d85b8dd 100644 --- a/vault/resource_generic_secret_item.go +++ b/vault/resource_generic_secret_item.go @@ -5,15 +5,23 @@ package vault import ( "fmt" + "sync" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/vault/api" "github.com/hashicorp/terraform-provider-vault/internal/consts" "github.com/hashicorp/terraform-provider-vault/internal/provider" "github.com/hashicorp/terraform-provider-vault/util" ) -func genericSecretItemResource(name string) *schema.Resource { +var ( + genericSecretItemResourceWriteMutex sync.Mutex + genericSecretItemResourceDeleteMutex sync.Mutex + genericSecretItemResourceReadMutex sync.Mutex +) + +func genericSecretItemResource() *schema.Resource { return &schema.Resource{ SchemaVersion: 1, @@ -50,6 +58,9 @@ func genericSecretItemResource(name string) *schema.Resource { } func genericSecretItemResourceWrite(d *schema.ResourceData, meta interface{}) error { + genericSecretItemResourceWriteMutex.Lock() + defer genericSecretItemResourceWriteMutex.Unlock() + client, e := provider.GetClient(d, meta) if e != nil { return e @@ -58,27 +69,37 @@ func genericSecretItemResourceWrite(d *schema.ResourceData, meta interface{}) er path := d.Get(consts.FieldPath).(string) key := d.Get(consts.FieldKey).(string) + d.SetId(key) + secret, err := versionedSecret(-1, path, client) if err != nil { return fmt.Errorf("error reading from Vault: %s", err) } + + var shouldCreateSecret bool + if secret == nil { - return fmt.Errorf("no secret found at %q", path) + shouldCreateSecret = true + secret = &api.Secret{ + Data: map[string]interface{}{ + key: d.Get(consts.FieldValue).(string), + }, + } } - d.SetId(key) + if !shouldCreateSecret { + for k, v := range secret.Data { + if k == key { + if v == d.Get(consts.FieldValue).(string) { + return nil + } - for k, v := range secret.Data { - if k == key { - if v == d.Get(consts.FieldValue).(string) { - return nil + break } - - break } - } - secret.Data[key] = d.Get(consts.FieldValue).(string) + secret.Data[key] = d.Get(consts.FieldValue).(string) + } data := secret.Data @@ -103,6 +124,9 @@ func genericSecretItemResourceWrite(d *schema.ResourceData, meta interface{}) er } func genericSecretItemResourceDelete(d *schema.ResourceData, meta interface{}) error { + genericSecretItemResourceDeleteMutex.Lock() + defer genericSecretItemResourceDeleteMutex.Unlock() + client, e := provider.GetClient(d, meta) if e != nil { return e @@ -149,6 +173,9 @@ func genericSecretItemResourceDelete(d *schema.ResourceData, meta interface{}) e } func genericSecretItemResourceRead(d *schema.ResourceData, meta interface{}) error { + genericSecretItemResourceReadMutex.Lock() + defer genericSecretItemResourceReadMutex.Unlock() + client, e := provider.GetClient(d, meta) if e != nil { return e From 92692485fbff2d4d84f84009ef8f82d2e733fbdd Mon Sep 17 00:00:00 2001 From: Gabriel Rimes Date: Thu, 23 Jan 2025 10:14:05 -0300 Subject: [PATCH 04/12] docs: add generic_secret_item pages --- website/docs/d/generic_secret_item.html.md | 72 ++++++++++++++++++++ website/docs/r/generic_secret_item.html.md | 78 ++++++++++++++++++++++ 2 files changed, 150 insertions(+) create mode 100644 website/docs/d/generic_secret_item.html.md create mode 100644 website/docs/r/generic_secret_item.html.md diff --git a/website/docs/d/generic_secret_item.html.md b/website/docs/d/generic_secret_item.html.md new file mode 100644 index 000000000..a885d6f71 --- /dev/null +++ b/website/docs/d/generic_secret_item.html.md @@ -0,0 +1,72 @@ +--- +layout: "vault" +page_title: "Vault: vault_generic_secret_item data source" +sidebar_current: "docs-vault-datasource-generic-secret-item" +description: |- + Reads key/value from a given path/key in Vault. +--- + +# vault\_generic\_secret\_item + +Reads key/value from a given path/key in Vault. + +This resource is primarily intended to be used with +[Vault's "generic" secret backend](https://www.vaultproject.io/docs/secrets/generic/index.html), +but it is also compatible with any other Vault endpoint that supports +the `vault read` command. + +~> **Important** All data retrieved from Vault will be +written in cleartext to state file generated by Terraform, will appear in +the console output when Terraform runs, and may be included in plan files +if secrets are interpolated into any resource attributes. +Protect these artifacts accordingly. See +[the main provider documentation](../index.html) +for more details. + +## Example Usage + +### Generic secret item + +```hcl +data "vault_generic_secret_item" "rundeck_auth" { + path = "secret/rundeck_auth" + key = "rundeck_token" +} + +# Rundeck Provider, for example +# For this example, in Vault there is a key named "rundeck_token" and the value is the token we need to keep secret. + +provider "rundeck" { + url = "http://rundeck.example.com/" + auth_token = data.vault_generic_secret_item.rundeck_auth.value +} +``` + +## Argument Reference + +The following arguments are supported: + +* `namespace` - (Optional) The namespace of the target resource. + The value should not contain leading or trailing forward slashes. + The `namespace` is always relative to the provider's configured [namespace](/docs/providers/vault/index.html#namespace). + *Available only for Vault Enterprise*. + +* `path` - (Required) The full logical path at which to write the given data. + To write data into the "generic" secret backend mounted in Vault by default, + this should be prefixed with `secret/`. Writing to other backends with this + resource is possible; consult each backend's documentation to see which + endpoints support the `PUT` and `DELETE` methods. + +* `key` - (Required) String containing the name of the secret item to be read. + +## Required Vault Capabilities + +Use of this resource requires the `read` capability on the given path. + +## Attributes Reference + +The following attributes are exported: + +* `key` - A string containing the name of the Vault item within the secret. + +* `value` - A string containing the value of the Vault item within the secret. diff --git a/website/docs/r/generic_secret_item.html.md b/website/docs/r/generic_secret_item.html.md new file mode 100644 index 000000000..b8196dd05 --- /dev/null +++ b/website/docs/r/generic_secret_item.html.md @@ -0,0 +1,78 @@ +--- +layout: "vault" +page_title: "Vault: vault_generic_secret_item resource" +sidebar_current: "docs-vault-resource-generic-secret-item" +description: |- + This resource writes key/value entries on a secret in Vault's "generic" secret backend. +--- + +# vault\_generic\_secret\_item + +Writes and manages secret items stored in +[Vault's "generic" secret backend](https://www.vaultproject.io/docs/secrets/generic/index.html) + +This resource is primarily intended to be used with both v1 and v2 of +[Vault's "generic" secret backend](https://www.vaultproject.io/docs/secrets/generic/index.html). +While it is also compatible, with some limitations, with other Vault +endpoints that support the `vault write` command to create and the +`vault delete` command to delete, see also +the [generic endpoint resource](generic_endpoint.html) for a more +flexible way to manage arbitrary data. + +~> **Important** All data provided in the resource configuration will be +written in cleartext to state and plan files generated by Terraform, and +will appear in the console output when Terraform runs. Protect these +artifacts accordingly. See +[the main provider documentation](../index.html) +for more details. + +## Example Usage + +```hcl +resource "vault_generic_secret_item" "example" { + path = "secret/foo" + key = "foo" + value = "bar" +``` + +## Argument Reference + +The following arguments are supported: + +* `namespace` - (Optional) The namespace to provision the resource in. + The value should not contain leading or trailing forward slashes. + The `namespace` is always relative to the provider's configured [namespace](/docs/providers/vault/index.html#namespace). + *Available only for Vault Enterprise*. + +* `path` - (Required) The full logical path at which to write the given data. + To write data into the "generic" secret backend mounted in Vault by default, + this should be prefixed with `secret/`. Writing to other backends with this + resource is possible; consult each backend's documentation to see which + endpoints support the `PUT` and `DELETE` methods. + +* `key` - (Required) String containing the name of the secret item to be written. + +* `value` - (Required) String with the content of the secret item to be written. + +## Required Vault Capabilities + +Use of this resource requires the `create` or `update` capability +(depending on whether the resource already exists) on the given path, +the `delete` capability if the resource is removed from configuration, +and the `read` capability for drift detection (by default). + +## Attributes Reference + +The following attributes are exported: + +* `key` - A string containing the name of the Vault item within the secret. + +* `value` - A string containing the value of the Vault item within the secret. + +## Import + +Generic secret items can be imported using the `path`, e.g. + +``` +$ terraform import vault_generic_secret_item.example secret_item_key +``` From 1b54021c54b39676ed853132ac207d14e080494b Mon Sep 17 00:00:00 2001 From: Gabriel Rimes Date: Thu, 23 Jan 2025 14:02:46 -0300 Subject: [PATCH 05/12] chore: update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4a37bd6a..95c76c78c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## Unreleased +FEATURES: + +* Add resource `vault_generic_secret_item` to allow a more granular management of Vault generic secrets ([#2394](https://github.com/hashicorp/terraform-provider-vault/pull/2394)) + ## 4.6.0 (Jan 15, 2025) FEATURES: From cdada6478f003a3fc4b726ec07ece405721b69c8 Mon Sep 17 00:00:00 2001 From: Gabriel Rimes Date: Thu, 23 Jan 2025 22:29:25 -0300 Subject: [PATCH 06/12] test: add data source file for generic secret item --- vault/data_source_generic_secret_item_test.go | 167 ++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 vault/data_source_generic_secret_item_test.go diff --git a/vault/data_source_generic_secret_item_test.go b/vault/data_source_generic_secret_item_test.go new file mode 100644 index 000000000..009da9c91 --- /dev/null +++ b/vault/data_source_generic_secret_item_test.go @@ -0,0 +1,167 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package vault + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + + "github.com/hashicorp/terraform-provider-vault/testutil" +) + +func TestDataSourceGenericSecretItem(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProviderFactories: providerFactories, + PreCheck: func() { testutil.TestAccPreCheck(t) }, + Steps: []resource.TestStep{ + { + Config: testDataSourceGenericSecretItem_config, + Check: testDataSourceGenericSecretItem_check, + }, + }, + }) +} + +func TestDataSourceGenericSecretItem_v2(t *testing.T) { + mount := acctest.RandomWithPrefix("tf-acctest-kv/") + path := acctest.RandomWithPrefix("foo") + resource.Test(t, resource.TestCase{ + ProviderFactories: providerFactories, + PreCheck: func() { testutil.TestAccPreCheck(t) }, + Steps: []resource.TestStep{ + { + Config: testDataSourceV2SecretItem_config(mount, path), + Check: testDataSourceGenericSecretItem_check, + }, + { + Config: testDataSourceV2SecretItemUpdated_config(mount, path), + Check: testDataSourceGenericSecretItemUpdated_check, + }, + }, + }) +} + +func testDataSourceV2SecretItem_config(mount, path string) string { + return fmt.Sprintf(` +resource "vault_mount" "test" { + path = "%s" + type = "kv" + description = "This is an example mount" + options = { + "version" = "2" + } +} + +resource "vault_generic_secret_item" "test" { + path = "${vault_mount.test.path}/%s" + key = "foo" + value = "bar" +} + +data "vault_generic_secret_item" "test" { + path = vault_generic_secret_item.test.path + key = "foo" +} +`, mount, path) +} + +func testDataSourceV2SecretItemUpdated_config(mount, path string) string { + return fmt.Sprintf(` +resource "vault_mount" "test" { + path = "%s" + type = "kv" + description = "This is an example mount" + options = { + "version" = "2" + } +} + +resource "vault_generic_secret_item" "test" { + path = "${vault_mount.test.path}/%s" + key = "foo" + value = "baz" +} + +data "vault_generic_secret_item" "test" { + path = vault_generic_secret_item.test.path + key = "foo" +} +`, mount, path) +} + +var testDataSourceGenericSecretItem_config = ` + +resource "vault_mount" "v1" { + path = "secretsv1" + type = "kv" + options = { + version = "1" + } +} + +resource "vault_generic_secret_item" "test" { + path = "${vault_mount.v1.path}/foo" + key = "foo" + value = "bar" +} + +data "vault_generic_secret_item" "test" { + path = vault_generic_secret_item.test.path + key = "foo" +} + +` + +func testDataSourceGenericSecretItem_check(s *terraform.State) error { + resourceState := s.Modules[0].Resources["data.vault_generic_secret_item.test"] + if resourceState == nil { + return fmt.Errorf("resource not found in state %v", s.Modules[0].Resources) + } + + iState := resourceState.Primary + + if iState == nil { + return fmt.Errorf("resource has no primary instance") + } + + wantKey := "foo" + if got, want := iState.Attributes["key"], wantKey; got != want { + return fmt.Errorf("key contains %s; want %s", got, want) + } + + wantVal := "bar" + if got, want := iState.Attributes["value"], wantVal; got != want { + return fmt.Errorf("value contains %s; want %s", got, want) + } + + return nil +} + +func testDataSourceGenericSecretItemUpdated_check(s *terraform.State) error { + resourceState := s.Modules[0].Resources["data.vault_generic_secret_item.test"] + if resourceState == nil { + return fmt.Errorf("resource not found in state %v", s.Modules[0].Resources) + } + + iState := resourceState.Primary + if iState == nil { + return fmt.Errorf("resource has no primary instance") + } + + wantKey := "foo" + if got, want := iState.Attributes["key"], wantKey; got != want { + return fmt.Errorf("key contains %s; want %s", got, want) + } + + wantVal := "baz" + if got, want := iState.Attributes["value"], wantVal; got != want { + return fmt.Errorf("value contains %s; want %s", got, want) + } + + return nil +} From 3aa8d6ac7e4c3766b4cf03d14408a2d14f3c742e Mon Sep 17 00:00:00 2001 From: Gabriel Rimes Date: Thu, 23 Jan 2025 22:47:36 -0300 Subject: [PATCH 07/12] test: add resource file for generic secret item --- vault/resource_generic_secret_item_test.go | 302 +++++++++++++++++++++ 1 file changed, 302 insertions(+) create mode 100644 vault/resource_generic_secret_item_test.go diff --git a/vault/resource_generic_secret_item_test.go b/vault/resource_generic_secret_item_test.go new file mode 100644 index 000000000..5fc9b05e3 --- /dev/null +++ b/vault/resource_generic_secret_item_test.go @@ -0,0 +1,302 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package vault + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-vault/internal/provider" + "github.com/hashicorp/terraform-provider-vault/testutil" +) + +func TestResourceGenericSecretItem(t *testing.T) { + mount := acctest.RandomWithPrefix("secretsv1") + name := acctest.RandomWithPrefix("test") + path := fmt.Sprintf("%s/%s", mount, name) + resourceName := "vault_generic_secret_item.test" + resource.Test(t, resource.TestCase{ + ProviderFactories: providerFactories, + PreCheck: func() { testutil.TestAccPreCheck(t) }, + Steps: []resource.TestStep{ + { + Config: testResourceGenericSecretItem_initialConfig(mount, name), + Check: testResourceGenericSecretItem_initialCheck(path), + }, + { + Config: testResourceGenericSecretItem_updateConfig(mount, name), + Check: testResourceGenericSecretItem_updateCheck, + }, + { + ImportState: true, + ResourceName: resourceName, + }, + }, + }) +} + +func TestResourceGenericSecretItem_deleted(t *testing.T) { + resourceName := "vault_generic_secret_item.test" + + mount := acctest.RandomWithPrefix("secretsv1") + name := acctest.RandomWithPrefix("test") + path := fmt.Sprintf("%s/%s", mount, name) + resource.Test(t, resource.TestCase{ + ProviderFactories: providerFactories, + PreCheck: func() { testutil.TestAccPreCheck(t) }, + Steps: []resource.TestStep{ + { + Config: testResourceGenericSecretItem_initialConfig(mount, name), + Check: testResourceGenericSecretItem_initialCheck(path), + }, + { + ImportState: true, + ResourceName: resourceName, + }, + { + PreConfig: func() { + client := testProvider.Meta().(*provider.ProviderMeta).MustGetClient() + + _, err := client.Logical().Delete(path) + if err != nil { + t.Fatalf("unable to manually delete the secret via the SDK: %s", err) + } + }, + Config: testResourceGenericSecretItem_initialConfig(mount, name), + Check: testResourceGenericSecretItem_initialCheck(path), + }, + { + ImportState: true, + ResourceName: resourceName, + }, + }, + }) +} + +func TestResourceGenericSecretItem_deleteAllVersions(t *testing.T) { + path := acctest.RandomWithPrefix("secretsv2/test") + resourceName := "vault_generic_secret_item.test" + + resource.Test(t, resource.TestCase{ + ProviderFactories: providerFactories, + PreCheck: func() { testutil.TestAccPreCheck(t) }, + CheckDestroy: testAllVersionDestroy, + Steps: []resource.TestStep{ + { + Config: testResourceGenericSecretItem_initialConfig_v2(path, false), + Check: testResourceGenericSecretItem_initialCheck_v2(path, "zap", 1), + }, + { + ImportState: true, + ResourceName: resourceName, + }, + { + Config: testResourceGenericSecretItem_initialConfig_v2(path, true), + Check: testResourceGenericSecretItem_initialCheck_v2(path, "zoop", 2), + }, + { + ImportState: true, + ResourceName: resourceName, + }, + }, + }) +} + +func testResourceGenericSecretItem_initialConfig(mount, name string) string { + return fmt.Sprintf(` +resource "vault_mount" "v1" { + path = "%s" + type = "kv" + options = { + version = "1" + } +} + +resource "vault_generic_secret_item" "test" { + path = "${vault_mount.v1.path}/%s" + key = "foo" + value = "bar" +}`, mount, name) +} + +func testResourceGenericSecretItem_updateConfig(mount, name string) string { + return fmt.Sprintf(` +resource "vault_mount" "v1" { + path = "%s" + type = "kv" + options = { + version = "1" + } +} + +resource "vault_generic_secret_item" "test" { + path = "${vault_mount.v1.path}/%s" + key = "foo" + value = "baz" +} +`, mount, name) +} + +func testResourceGenericSecretItem_initialConfig_v2(path string, isUpdate bool) string { + result := fmt.Sprintf(` +resource "vault_mount" "v2" { + path = "secretsv2" + type = "kv" + options = { + version = "2" + } +} + +`) + if !isUpdate { + result += fmt.Sprintf(` +resource "vault_generic_secret_item" "test" { + depends_on = ["vault_mount.v2"] + + path = "%s" + key = "foo" + value = "bar" +EOT +}`, path) + } else { + result += fmt.Sprintf(` +resource "vault_generic_secret_item" "test" { + depends_on = ["vault_mount.v2"] + + path = "%s" + key = "foo" + value = "baz" +}`, path) + } + + return result +} + +func testResourceGenericSecretItem_initialCheck(expectedPath string) resource.TestCheckFunc { + return func(s *terraform.State) error { + resourceState := s.Modules[0].Resources["vault_generic_secret_item.test"] + if resourceState == nil { + return fmt.Errorf("resource not found in state") + } + + state := resourceState.Primary + if state == nil { + return fmt.Errorf("resource has no primary instance") + } + + key := state.ID + path := state.Attributes["path"] + + if key != state.Attributes["key"] { + return fmt.Errorf("id doesn't match key") + } + if path != expectedPath { + return fmt.Errorf("unexpected secret path") + } + + client, err := provider.GetClient(state, testProvider.Meta()) + if err != nil { + return err + } + + secret, err := client.Logical().Read(path) + if err != nil { + return fmt.Errorf("error reading back secret: %s", err) + } + + if got, want := secret.Data["foo"], "bar"; got != want { + return fmt.Errorf("'foo' data is %q; want %q", got, want) + } + + return nil + } +} + +func testResourceGenericSecretItem_initialCheck_v2(expectedPath string, wantValue string, versionCount int) resource.TestCheckFunc { + return func(s *terraform.State) error { + resourceState := s.Modules[0].Resources["vault_generic_secret_item.test"] + if resourceState == nil { + return fmt.Errorf("resource not found in state") + } + + instanceState := resourceState.Primary + if instanceState == nil { + return fmt.Errorf("resource has no primary instance") + } + + path := instanceState.ID + + if path != instanceState.Attributes["path"] { + return fmt.Errorf("id doesn't match path") + } + if path != expectedPath { + return fmt.Errorf("unexpected secret path") + } + + client, e := provider.GetClient(instanceState, testProvider.Meta()) + if e != nil { + return e + } + + // Checking KV-V2 Secrets + resp, err := client.Logical().List("secretsv2/metadata") + if err != nil { + return fmt.Errorf("unable to list secrets metadata: %s", err) + } + + if resp == nil { + return fmt.Errorf("expected kv-v2 secrets, got nil") + } + keys := resp.Data["keys"].([]interface{}) + secret, err := client.Logical().Read(fmt.Sprintf("secretsv2/data/%s", keys[0])) + if secret == nil { + return fmt.Errorf("no secret found at secretsv2/data/%s", keys[0]) + } + + data := secret.Data["data"].(map[string]interface{}) + + // Confirm number of versions + err = testResourceGenericSecret_checkVersions(client, keys[0].(string), versionCount) + if err != nil { + return fmt.Errorf("Version error: %s", err) + } + + // Test the JSON + if got := data["zip"]; got != wantValue { + return fmt.Errorf("'zip' data is %q; want %q", got, wantValue) + } + + // Test the map + if got := instanceState.Attributes["data.zip"]; got != wantValue { + return fmt.Errorf("data[\"zip\"] contains %s; want %s", got, wantValue) + } + return nil + } +} + +func testResourceGenericSecretItem_updateCheck(s *terraform.State) error { + resourceState := s.Modules[0].Resources["vault_generic_secret_item.test"] + state := resourceState.Primary + + path := state.ID + + client, err := provider.GetClient(state, testProvider.Meta()) + if err != nil { + return err + } + + secret, err := client.Logical().Read(path) + if err != nil { + return fmt.Errorf("error reading back secret: %s", err) + } + + if got, want := secret.Data["foo"], "baz"; got != want { + return fmt.Errorf("'foo' data is %q; want %q", got, want) + } + + return nil +} From 0438b41ae6e66c907c10a22864c3c0a1b3dd2b87 Mon Sep 17 00:00:00 2001 From: Gabriel Rimes Date: Mon, 27 Jan 2025 18:32:14 -0300 Subject: [PATCH 08/12] fix: remove unwanted code from tests --- vault/resource_generic_secret_item_test.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/vault/resource_generic_secret_item_test.go b/vault/resource_generic_secret_item_test.go index 5fc9b05e3..21c2ad54b 100644 --- a/vault/resource_generic_secret_item_test.go +++ b/vault/resource_generic_secret_item_test.go @@ -88,7 +88,7 @@ func TestResourceGenericSecretItem_deleteAllVersions(t *testing.T) { Steps: []resource.TestStep{ { Config: testResourceGenericSecretItem_initialConfig_v2(path, false), - Check: testResourceGenericSecretItem_initialCheck_v2(path, "zap", 1), + Check: testResourceGenericSecretItem_initialCheck_v2(path, "bar", 1), }, { ImportState: true, @@ -96,7 +96,7 @@ func TestResourceGenericSecretItem_deleteAllVersions(t *testing.T) { }, { Config: testResourceGenericSecretItem_initialConfig_v2(path, true), - Check: testResourceGenericSecretItem_initialCheck_v2(path, "zoop", 2), + Check: testResourceGenericSecretItem_initialCheck_v2(path, "baz", 2), }, { ImportState: true, @@ -160,7 +160,6 @@ resource "vault_generic_secret_item" "test" { path = "%s" key = "foo" value = "bar" -EOT }`, path) } else { result += fmt.Sprintf(` From fbd47731c8fa086e640ade40db82bb2ff6bcbedb Mon Sep 17 00:00:00 2001 From: Gabriel Rimes Date: Tue, 28 Jan 2025 10:12:14 -0300 Subject: [PATCH 09/12] fix: update importer state to use StateContext for generic secret item --- vault/resource_generic_secret_item.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vault/resource_generic_secret_item.go b/vault/resource_generic_secret_item.go index c0d85b8dd..31b7c2ff0 100644 --- a/vault/resource_generic_secret_item.go +++ b/vault/resource_generic_secret_item.go @@ -30,7 +30,7 @@ func genericSecretItemResource() *schema.Resource { Delete: genericSecretItemResourceDelete, Read: provider.ReadWrapper(genericSecretItemResourceRead), Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, + StateContext: schema.ImportStatePassthroughContext, }, Schema: map[string]*schema.Schema{ From 776a007d14f4c90f1fa7feedd6a1cdb1c3154e64 Mon Sep 17 00:00:00 2001 From: Gabriel Rimes Date: Tue, 28 Jan 2025 13:34:11 -0300 Subject: [PATCH 10/12] feat: remove the whole secret when the current deletion makes it empty --- vault/resource_generic_secret_item.go | 11 +++++++++++ vault/resource_generic_secret_item_test.go | 20 ++++++++++---------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/vault/resource_generic_secret_item.go b/vault/resource_generic_secret_item.go index 31b7c2ff0..3144fc645 100644 --- a/vault/resource_generic_secret_item.go +++ b/vault/resource_generic_secret_item.go @@ -5,6 +5,7 @@ package vault import ( "fmt" + "log" "sync" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -165,6 +166,16 @@ func genericSecretItemResourceDelete(d *schema.ResourceData, meta interface{}) e } } + if len(data) == 0 { + log.Printf("[DEBUG] Deleting empty vault_generic_secret from %q", path) + _, err = client.Logical().Delete(path) + if err != nil { + return fmt.Errorf("error deleting %q from Vault: %q", path, err) + } + + return nil + } + if _, err := util.RetryWrite(client, path, data, util.DefaultRequestOpts()); err != nil { return err } diff --git a/vault/resource_generic_secret_item_test.go b/vault/resource_generic_secret_item_test.go index 21c2ad54b..957203f6e 100644 --- a/vault/resource_generic_secret_item_test.go +++ b/vault/resource_generic_secret_item_test.go @@ -227,10 +227,11 @@ func testResourceGenericSecretItem_initialCheck_v2(expectedPath string, wantValu return fmt.Errorf("resource has no primary instance") } - path := instanceState.ID + key := instanceState.ID + path := instanceState.Attributes["path"] - if path != instanceState.Attributes["path"] { - return fmt.Errorf("id doesn't match path") + if key != instanceState.Attributes["key"] { + return fmt.Errorf("id doesn't match key") } if path != expectedPath { return fmt.Errorf("unexpected secret path") @@ -264,15 +265,10 @@ func testResourceGenericSecretItem_initialCheck_v2(expectedPath string, wantValu return fmt.Errorf("Version error: %s", err) } - // Test the JSON - if got := data["zip"]; got != wantValue { - return fmt.Errorf("'zip' data is %q; want %q", got, wantValue) + if got := data["foo"]; got != wantValue { + return fmt.Errorf("'foo' data is %q; want %q", got, wantValue) } - // Test the map - if got := instanceState.Attributes["data.zip"]; got != wantValue { - return fmt.Errorf("data[\"zip\"] contains %s; want %s", got, wantValue) - } return nil } } @@ -293,6 +289,10 @@ func testResourceGenericSecretItem_updateCheck(s *terraform.State) error { return fmt.Errorf("error reading back secret: %s", err) } + if secret == nil { + return nil + } + if got, want := secret.Data["foo"], "baz"; got != want { return fmt.Errorf("'foo' data is %q; want %q", got, want) } From fe2eb9e760724880015396c2fde81d1e5de8202f Mon Sep 17 00:00:00 2001 From: Gabriel Rimes Date: Wed, 29 Jan 2025 22:20:09 -0300 Subject: [PATCH 11/12] chore: remove unused tests and obsolete code --- vault/resource_generic_secret_item.go | 3 - vault/resource_generic_secret_item_test.go | 165 +-------------------- 2 files changed, 1 insertion(+), 167 deletions(-) diff --git a/vault/resource_generic_secret_item.go b/vault/resource_generic_secret_item.go index 3144fc645..5468c5d47 100644 --- a/vault/resource_generic_secret_item.go +++ b/vault/resource_generic_secret_item.go @@ -30,9 +30,6 @@ func genericSecretItemResource() *schema.Resource { Update: genericSecretItemResourceWrite, Delete: genericSecretItemResourceDelete, Read: provider.ReadWrapper(genericSecretItemResourceRead), - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, Schema: map[string]*schema.Schema{ consts.FieldPath: { diff --git a/vault/resource_generic_secret_item_test.go b/vault/resource_generic_secret_item_test.go index 957203f6e..ffc1d7bad 100644 --- a/vault/resource_generic_secret_item_test.go +++ b/vault/resource_generic_secret_item_test.go @@ -18,7 +18,7 @@ func TestResourceGenericSecretItem(t *testing.T) { mount := acctest.RandomWithPrefix("secretsv1") name := acctest.RandomWithPrefix("test") path := fmt.Sprintf("%s/%s", mount, name) - resourceName := "vault_generic_secret_item.test" + resource.Test(t, resource.TestCase{ ProviderFactories: providerFactories, PreCheck: func() { testutil.TestAccPreCheck(t) }, @@ -31,77 +31,6 @@ func TestResourceGenericSecretItem(t *testing.T) { Config: testResourceGenericSecretItem_updateConfig(mount, name), Check: testResourceGenericSecretItem_updateCheck, }, - { - ImportState: true, - ResourceName: resourceName, - }, - }, - }) -} - -func TestResourceGenericSecretItem_deleted(t *testing.T) { - resourceName := "vault_generic_secret_item.test" - - mount := acctest.RandomWithPrefix("secretsv1") - name := acctest.RandomWithPrefix("test") - path := fmt.Sprintf("%s/%s", mount, name) - resource.Test(t, resource.TestCase{ - ProviderFactories: providerFactories, - PreCheck: func() { testutil.TestAccPreCheck(t) }, - Steps: []resource.TestStep{ - { - Config: testResourceGenericSecretItem_initialConfig(mount, name), - Check: testResourceGenericSecretItem_initialCheck(path), - }, - { - ImportState: true, - ResourceName: resourceName, - }, - { - PreConfig: func() { - client := testProvider.Meta().(*provider.ProviderMeta).MustGetClient() - - _, err := client.Logical().Delete(path) - if err != nil { - t.Fatalf("unable to manually delete the secret via the SDK: %s", err) - } - }, - Config: testResourceGenericSecretItem_initialConfig(mount, name), - Check: testResourceGenericSecretItem_initialCheck(path), - }, - { - ImportState: true, - ResourceName: resourceName, - }, - }, - }) -} - -func TestResourceGenericSecretItem_deleteAllVersions(t *testing.T) { - path := acctest.RandomWithPrefix("secretsv2/test") - resourceName := "vault_generic_secret_item.test" - - resource.Test(t, resource.TestCase{ - ProviderFactories: providerFactories, - PreCheck: func() { testutil.TestAccPreCheck(t) }, - CheckDestroy: testAllVersionDestroy, - Steps: []resource.TestStep{ - { - Config: testResourceGenericSecretItem_initialConfig_v2(path, false), - Check: testResourceGenericSecretItem_initialCheck_v2(path, "bar", 1), - }, - { - ImportState: true, - ResourceName: resourceName, - }, - { - Config: testResourceGenericSecretItem_initialConfig_v2(path, true), - Check: testResourceGenericSecretItem_initialCheck_v2(path, "baz", 2), - }, - { - ImportState: true, - ResourceName: resourceName, - }, }, }) } @@ -141,40 +70,6 @@ resource "vault_generic_secret_item" "test" { `, mount, name) } -func testResourceGenericSecretItem_initialConfig_v2(path string, isUpdate bool) string { - result := fmt.Sprintf(` -resource "vault_mount" "v2" { - path = "secretsv2" - type = "kv" - options = { - version = "2" - } -} - -`) - if !isUpdate { - result += fmt.Sprintf(` -resource "vault_generic_secret_item" "test" { - depends_on = ["vault_mount.v2"] - - path = "%s" - key = "foo" - value = "bar" -}`, path) - } else { - result += fmt.Sprintf(` -resource "vault_generic_secret_item" "test" { - depends_on = ["vault_mount.v2"] - - path = "%s" - key = "foo" - value = "baz" -}`, path) - } - - return result -} - func testResourceGenericSecretItem_initialCheck(expectedPath string) resource.TestCheckFunc { return func(s *terraform.State) error { resourceState := s.Modules[0].Resources["vault_generic_secret_item.test"] @@ -215,64 +110,6 @@ func testResourceGenericSecretItem_initialCheck(expectedPath string) resource.Te } } -func testResourceGenericSecretItem_initialCheck_v2(expectedPath string, wantValue string, versionCount int) resource.TestCheckFunc { - return func(s *terraform.State) error { - resourceState := s.Modules[0].Resources["vault_generic_secret_item.test"] - if resourceState == nil { - return fmt.Errorf("resource not found in state") - } - - instanceState := resourceState.Primary - if instanceState == nil { - return fmt.Errorf("resource has no primary instance") - } - - key := instanceState.ID - path := instanceState.Attributes["path"] - - if key != instanceState.Attributes["key"] { - return fmt.Errorf("id doesn't match key") - } - if path != expectedPath { - return fmt.Errorf("unexpected secret path") - } - - client, e := provider.GetClient(instanceState, testProvider.Meta()) - if e != nil { - return e - } - - // Checking KV-V2 Secrets - resp, err := client.Logical().List("secretsv2/metadata") - if err != nil { - return fmt.Errorf("unable to list secrets metadata: %s", err) - } - - if resp == nil { - return fmt.Errorf("expected kv-v2 secrets, got nil") - } - keys := resp.Data["keys"].([]interface{}) - secret, err := client.Logical().Read(fmt.Sprintf("secretsv2/data/%s", keys[0])) - if secret == nil { - return fmt.Errorf("no secret found at secretsv2/data/%s", keys[0]) - } - - data := secret.Data["data"].(map[string]interface{}) - - // Confirm number of versions - err = testResourceGenericSecret_checkVersions(client, keys[0].(string), versionCount) - if err != nil { - return fmt.Errorf("Version error: %s", err) - } - - if got := data["foo"]; got != wantValue { - return fmt.Errorf("'foo' data is %q; want %q", got, wantValue) - } - - return nil - } -} - func testResourceGenericSecretItem_updateCheck(s *terraform.State) error { resourceState := s.Modules[0].Resources["vault_generic_secret_item.test"] state := resourceState.Primary From 30b77861847a808fc3da479d11f010995c4a33f3 Mon Sep 17 00:00:00 2001 From: Gabriel Rimes Date: Wed, 29 Jan 2025 22:21:45 -0300 Subject: [PATCH 12/12] docs: remove import instructions (not supported) --- website/docs/r/generic_secret_item.html.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/website/docs/r/generic_secret_item.html.md b/website/docs/r/generic_secret_item.html.md index b8196dd05..aff8619be 100644 --- a/website/docs/r/generic_secret_item.html.md +++ b/website/docs/r/generic_secret_item.html.md @@ -68,11 +68,3 @@ The following attributes are exported: * `key` - A string containing the name of the Vault item within the secret. * `value` - A string containing the value of the Vault item within the secret. - -## Import - -Generic secret items can be imported using the `path`, e.g. - -``` -$ terraform import vault_generic_secret_item.example secret_item_key -```