Skip to content

Commit

Permalink
Add custom metadata support to namespace resource (#2033)
Browse files Browse the repository at this point in the history
Co-authored-by: John-Michael Faircloth <[email protected]>
Co-authored-by: Ben Ash <[email protected]>
  • Loading branch information
3 people authored Oct 13, 2023
1 parent c06a013 commit 6bab488
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 19 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
## Unreleased

FEATURES:
* Add support for `custom_metadata` on `vault_namespace`: ([#2033](https://github.com/hashicorp/terraform-provider-vault/pull/2033))

BUGS:
* Fix panic when readnig client_secret from a public oidc client ([#2048](https://github.com/hashicorp/terraform-provider-vault/pull/2048))
* Fix API request missing `roles` field for `mongodbatlas_secret_role` resource ([#2047](https://github.com/hashicorp/terraform-provider-vault/pull/2047))

IMPROVEMENTS:
* Updated dependencies: ([#2038](https://github.com/hashicorp/terraform-provider-vault/pull/2038))
* `github.com/aws/aws-sdk-go` v1.44.106 -> v1.45.24
Expand Down
96 changes: 77 additions & 19 deletions vault/resource_namespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
package vault

import (
"context"
"fmt"
"log"
"net/http"
"strings"
"time"

"github.com/cenkalti/backoff/v4"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/vault/api"

Expand All @@ -21,10 +23,10 @@ import (

func namespaceResource() *schema.Resource {
return &schema.Resource{
Create: namespaceCreate,
Update: namespaceCreate,
Delete: namespaceDelete,
Read: provider.ReadWrapper(namespaceRead),
CreateContext: namespaceCreate,
UpdateContext: namespaceUpdate,
DeleteContext: namespaceDelete,
ReadContext: provider.ReadContextWrapper(namespaceRead),
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Expand All @@ -48,31 +50,79 @@ func namespaceResource() *schema.Resource {
Optional: true,
Description: "The fully qualified namespace path.",
},
consts.FieldCustomMetadata: {
Type: schema.TypeMap,
Computed: true,
Optional: true,
Description: "Custom metadata describing this namespace. Value type " +
"is map[string]string.",
},
},
}
}

func namespaceCreate(d *schema.ResourceData, meta interface{}) error {
func namespaceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client, e := provider.GetClient(d, meta)
if e != nil {
return e
return diag.FromErr(e)
}

path := d.Get(consts.FieldPath).(string)

var data map[string]interface{}

// data is non-nil only if Vault version >= 1.12
// and custom_metadata is provided
if provider.IsAPISupported(meta, provider.VaultVersion112) {
if v, ok := d.GetOk(consts.FieldCustomMetadata); ok {
data = map[string]interface{}{
consts.FieldCustomMetadata: v,
}
}
}

log.Printf("[DEBUG] Creating namespace %s in Vault", path)
_, err := client.Logical().Write(consts.SysNamespaceRoot+path, nil)
_, err := client.Logical().Write(consts.SysNamespaceRoot+path, data)
if err != nil {
return fmt.Errorf("error writing to Vault: %s", err)
return diag.Errorf("error writing to Vault: %s", err)
}

return namespaceRead(d, meta)
return namespaceRead(ctx, d, meta)
}

func namespaceDelete(d *schema.ResourceData, meta interface{}) error {
func namespaceUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
// Updating a namespace is only supported in
// Vault versions >= 1.12
if !provider.IsAPISupported(meta, provider.VaultVersion112) {
return nil
}

client, e := provider.GetClient(d, meta)
if e != nil {
return e
return diag.FromErr(e)
}

path := d.Get(consts.FieldPath).(string)

var data map[string]interface{}
if v, ok := d.GetOk(consts.FieldCustomMetadata); ok {
data = map[string]interface{}{
consts.FieldCustomMetadata: v,
}
}

log.Printf("[DEBUG] Creating namespace %s in Vault", path)
if _, err := client.Logical().JSONMergePatch(ctx, consts.SysNamespaceRoot+path, data); err != nil {
return diag.Errorf("error writing to Vault: %s", err)
}

return namespaceRead(ctx, d, meta)
}

func namespaceDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client, e := provider.GetClient(d, meta)
if e != nil {
return diag.FromErr(e)
}

path := d.Get(consts.FieldPath).(string)
Expand All @@ -99,11 +149,11 @@ func namespaceDelete(d *schema.ResourceData, meta interface{}) error {
if err := backoff.RetryNotify(deleteNS, bo, func(err error, duration time.Duration) {
log.Printf("[WARN] Deleting namespace %q failed, retrying in %s", path, duration)
}); err != nil {
return fmt.Errorf("error deleting from Vault: %s", err)
return diag.Errorf("error deleting from Vault: %s", err)
}

// wait for the namespace to be gone...
return backoff.RetryNotify(func() error {
return diag.FromErr(backoff.RetryNotify(func() error {
if resp, _ := client.Logical().Read(consts.SysNamespaceRoot + path); resp != nil {
return fmt.Errorf("namespace %q still exists", path)
}
Expand All @@ -115,13 +165,13 @@ func namespaceDelete(d *schema.ResourceData, meta interface{}) error {
"[WARN] Waiting for Vault to garbage collect the %q namespace, retrying in %s",
path, duration)
},
)
))
}

func namespaceRead(d *schema.ResourceData, meta interface{}) error {
func namespaceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client, e := provider.GetClient(d, meta)
if e != nil {
return e
return diag.FromErr(e)
}

upgradeNonPathdNamespaceID(d)
Expand All @@ -130,7 +180,7 @@ func namespaceRead(d *schema.ResourceData, meta interface{}) error {

resp, err := client.Logical().Read(consts.SysNamespaceRoot + path)
if err != nil {
return fmt.Errorf("error reading from Vault: %s", err)
return diag.Errorf("error reading from Vault: %s", err)
}

if resp == nil {
Expand All @@ -142,8 +192,16 @@ func namespaceRead(d *schema.ResourceData, meta interface{}) error {
d.SetId(resp.Data[consts.FieldPath].(string))

toSet := map[string]interface{}{
consts.FieldNamespaceID: resp.Data["id"],
consts.FieldNamespaceID: resp.Data[consts.FieldID],
consts.FieldPath: util.TrimSlashes(path),
// set computed parameter to nil for vault versions <= 1.11
// prevents 'known after apply' drift in TF state since field
// would never be set otherwise
consts.FieldCustomMetadata: nil,
}

if provider.IsAPISupported(meta, provider.VaultVersion112) {
toSet[consts.FieldCustomMetadata] = resp.Data[consts.FieldCustomMetadata]
}

pathFQ := path
Expand All @@ -153,7 +211,7 @@ func namespaceRead(d *schema.ResourceData, meta interface{}) error {
toSet[consts.FieldPathFQ] = pathFQ

if err := util.SetResourceData(d, toSet); err != nil {
return err
return diag.FromErr(err)
}

return nil
Expand Down
24 changes: 24 additions & 0 deletions vault/resource_namespace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,18 @@ func TestAccNamespace(t *testing.T) {
resource.TestCheckResourceAttr(resourceNameParent, consts.FieldPath, namespacePath+"-foo"),
testNamespaceDestroy(namespacePath)),
},
{
SkipFunc: func() (bool, error) {
return !testProvider.Meta().(*provider.ProviderMeta).IsAPISupported(provider.VaultVersion112), nil
},
Config: testNamespaceCustomMetadata(namespacePath + "-cm"),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceNameParent, consts.FieldPath, namespacePath+"-cm"),
resource.TestCheckResourceAttr(resourceNameParent, "custom_metadata.%", "2"),
resource.TestCheckResourceAttr(resourceNameParent, "custom_metadata.foo", "abc"),
resource.TestCheckResourceAttr(resourceNameParent, "custom_metadata.bar", "123"),
testNamespaceDestroy(namespacePath)),
},
},
})
}
Expand Down Expand Up @@ -160,3 +172,15 @@ resource "vault_namespace" "child" {

return config
}

func testNamespaceCustomMetadata(path string) string {
return fmt.Sprintf(`
resource "vault_namespace" "parent" {
path = %q
custom_metadata = {
foo = "abc",
bar = "123"
}
}
`, path)
}
3 changes: 3 additions & 0 deletions website/docs/r/namespace.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ The following arguments are supported:

* `path` - (Required) The path of the namespace. Must not have a trailing `/`.

* `custom_metadata` - (Optional) Custom metadata describing this namespace. Value type
is `map[string]string`. Requires Vault version 1.12+.

## Attributes Reference

In addition to the above arguments, the following attributes are exported:
Expand Down

0 comments on commit 6bab488

Please sign in to comment.