From 038f19ba88c667acf4ea66ab4ec1fea236cab3f3 Mon Sep 17 00:00:00 2001 From: Marc Boudreau Date: Thu, 22 Feb 2024 11:48:54 -0500 Subject: [PATCH 01/11] add custom message resource --- internal/consts/consts.go | 6 + vault/provider.go | 4 + ...resource_config_ui_custom_messages copy.go | 269 ++++++++++++++++++ ...resource_config_ui_custom_messages_test.go | 90 ++++++ 4 files changed, 369 insertions(+) create mode 100644 vault/resource_config_ui_custom_messages copy.go create mode 100644 vault/resource_config_ui_custom_messages_test.go diff --git a/internal/consts/consts.go b/internal/consts/consts.go index 1ec36f73c0..cdcaf26cb5 100644 --- a/internal/consts/consts.go +++ b/internal/consts/consts.go @@ -391,6 +391,12 @@ const ( FieldWrappingToken = "wrapping_token" FieldWithWrappedAccessor = "with_wrapped_accessor" FieldAIAPath = "aia_path" + FieldTitle = "title" + FieldMessageBase64 = "message_base64" + FieldAuthenticated = "authenticated" + FieldStartTime = "start_time" + FieldEndTime = "end_time" + FieldLink = "link" /* common environment variables diff --git a/vault/provider.go b/vault/provider.go index e8c0cae496..3b997c856d 100644 --- a/vault/provider.go +++ b/vault/provider.go @@ -761,6 +761,10 @@ var ( Resource: UpdateSchemaResource(secretsSyncAssociationResource()), PathInventory: []string{"/sys/sync/destinations/{type}/{name}/associations/set"}, }, + "vault_config_ui_custom_message": { + Resource: UpdateSchemaResource(configUICustomMessageResource()), + PathInventory: []string{"/sys/config/ui/custom-messages"}, + }, } ) diff --git a/vault/resource_config_ui_custom_messages copy.go b/vault/resource_config_ui_custom_messages copy.go new file mode 100644 index 0000000000..1b1ff16484 --- /dev/null +++ b/vault/resource_config_ui_custom_messages copy.go @@ -0,0 +1,269 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package vault + +import ( + "context" + "log" + + "github.com/hashicorp/go-cty/cty" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "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/vault/api" +) + +func configUICustomMessageResource() *schema.Resource { + return &schema.Resource{ + CreateContext: configUICustomMessageCreate, + ReadContext: configUICustomMessageRead, + UpdateContext: configUICustomMessageUpdate, + DeleteContext: configUICustomMessageDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + consts.FieldID: { + Type: schema.TypeString, + Computed: true, + Description: "Unique ID for the custom message", + }, + consts.FieldTitle: { + Type: schema.TypeString, + Required: true, + Description: "Title of the custom message", + }, + consts.FieldMessageBase64: { + Type: schema.TypeString, + Required: true, + Description: "The base64-encoded message content of the custom message", + }, + consts.FieldAuthenticated: { + Type: schema.TypeBool, + Optional: true, + + Default: true, + Description: "Flag indicating if custom message is pre-login (false) or post-login (true)", + }, + consts.FieldType: { + Type: schema.TypeString, + Optional: true, + Default: "banner", + ValidateDiagFunc: func(value interface{}, _ cty.Path) diag.Diagnostics { + stringValue := value.(string) + switch { + case stringValue != "banner" && stringValue != "modal": + return diag.Diagnostics{diag.Diagnostic{ + Severity: diag.Error, + Summary: "invalid value for \"type\" argument", + Detail: "The \"type\" argument can only be set to \"banner\" or \"modal\".", + }} + } + + return nil + }, + Description: "Display type of custom message. Allowed values are banner and modal", + }, + consts.FieldStartTime: { + Type: schema.TypeString, + Required: true, + Description: "The starting time of the active period of the custom message", + }, + consts.FieldEndTime: { + Type: schema.TypeString, + Optional: true, + Description: "The ending time of the active period of the custom message", + }, + consts.FieldLink: { + Type: schema.TypeSet, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "title": { + Type: schema.TypeString, + Required: true, + Description: "The title of the hyperlink", + }, + "href": { + Type: schema.TypeString, + Required: true, + Description: "The URL of the hyperlink", + }, + }, + }, + Description: "A block containing a hyperlink associated with the custom message", + }, + consts.FieldOptions: { + Type: schema.TypeMap, + Optional: true, + Description: "A map containing additional options for the custom message", + }, + }, + } +} + +func configUICustomMessageCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client, e := provider.GetClient(d, meta) + if e != nil { + return diag.FromErr(e) + } + + secret, e := client.Sys().CreateUICustomMessageWithContext(ctx, buildUICustomMessageRequest(d)) + if e != nil { + return diag.FromErr(e) + } + + if secret == nil || secret.Data == nil { + return diag.Errorf(`response from Vault server is empty`) + } + + id, ok := secret.Data[consts.FieldID] + if !ok { + return diag.Errorf("error creating custom message: %s", secret.Data["error"]) + } + + d.SetId(id.(string)) + + return configUICustomMessageRead(ctx, d, meta) +} + +func configUICustomMessageRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client, e := provider.GetClient(d, meta) + if e != nil { + return diag.FromErr(e) + } + + id := d.Id() + + secret, e := client.Sys().ReadUICustomMessage(id) + if e != nil { + return diag.FromErr(e) + } + + if secret == nil || secret.Data == nil { + return diag.Errorf("response from Vault server is empty") + } + + secretData := secret.Data + + if _, ok := secretData["error"]; ok { + errorList := secretData["error"].([]string) + return diag.Errorf("errors received from Vault server: %s", errorList) + } + + var endTimeValue string + if v, ok := secretData[consts.FieldEndTime]; ok { + if v != nil { + endTimeValue = v.(string) + } + } + + var linkValue []interface{} + var linkMap map[string]interface{} + + if v, ok := secretData[consts.FieldLink]; ok { + if v != nil { + linkMap := v.(map[string]any) + + if len(linkMap) > 1 { + return diag.Errorf(`invalid link specification: only a single link can be specified`) + } + + for k, v := range linkMap { + stringV, ok := v.(string) + if !ok { + return diag.Errorf("invalid href value in link specification: %v", v) + } + if len(k) > 0 && len(stringV) > 0 { + linkValue = []interface{}{ + map[string]interface{}{ + "title": k, + "href": stringV, + }, + } + } + break + } + } + } + + d.Set(consts.FieldTitle, secretData[consts.FieldTitle]) + d.Set(consts.FieldMessageBase64, secretData["message"]) + d.Set(consts.FieldAuthenticated, secretData[consts.FieldAuthenticated]) + d.Set(consts.FieldType, secretData[consts.FieldType]) + d.Set(consts.FieldStartTime, secretData[consts.FieldStartTime]) + d.Set(consts.FieldEndTime, endTimeValue) + + if linkMap != nil { + d.Set(consts.FieldLink, linkValue) + } + + d.Set(consts.FieldOptions, secretData[consts.FieldOptions]) + + return nil +} + +func configUICustomMessageUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client, e := provider.GetClient(d, meta) + if e != nil { + return diag.FromErr(e) + } + + id := d.Id() + + if d.HasChanges(consts.FieldTitle, consts.FieldMessageBase64, consts.FieldAuthenticated, consts.FieldType, consts.FieldStartTime, consts.FieldEndTime, consts.FieldOptions, consts.FieldLink) { + e = client.Sys().UpdateUICustomMessageWithContext(ctx, id, buildUICustomMessageRequest(d)) + if e != nil { + return diag.FromErr(e) + } + } + + return configUICustomMessageRead(ctx, d, meta) +} + +func configUICustomMessageDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client, e := provider.GetClient(d, meta) + if e != nil { + return diag.FromErr(e) + } + + id := d.Id() + + log.Printf("[DEBUG] Deleting custom message %q", id) + e = client.Sys().DeleteUICustomMessageWithContext(ctx, id) + if e != nil { + return diag.Errorf("error deleting custom message %q: %s", id, e) + } + + log.Printf("[DEBUG] Deleted custom message %q", id) + return nil +} + +func buildUICustomMessageRequest(d *schema.ResourceData) api.UICustomMessageRequest { + request := api.UICustomMessageRequest{ + Title: d.Get(consts.FieldTitle).(string), + Message: d.Get(consts.FieldMessageBase64).(string), + Authenticated: d.Get(consts.FieldAuthenticated).(bool), + Type: d.Get(consts.FieldType).(string), + StartTime: d.Get(consts.FieldStartTime).(string), + EndTime: d.Get(consts.FieldEndTime).(string), + Options: d.Get(consts.FieldOptions).(map[string]interface{}), + } + + linkValue := d.Get(consts.FieldLink).(*schema.Set) + if linkValue.Len() == 1 { + slice := linkValue.List() + + m := slice[0].(map[string]interface{}) + linkTitle := m["title"].(string) + linkHref := m["href"].(string) + + request.WithLink(linkTitle, linkHref) + } + + return request +} diff --git a/vault/resource_config_ui_custom_messages_test.go b/vault/resource_config_ui_custom_messages_test.go new file mode 100644 index 0000000000..2b86b37f12 --- /dev/null +++ b/vault/resource_config_ui_custom_messages_test.go @@ -0,0 +1,90 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package vault + +import ( + "encoding/base64" + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-provider-vault/internal/consts" + "github.com/hashicorp/terraform-provider-vault/testutil" +) + +var ( + messageBase64 = base64.StdEncoding.EncodeToString([]byte("Vault will be unavailable for maintenance on 2024-02-28 from 05:00Z to 07:00Z")) +) + +func testConfigUICustomMessageConfig(isUpdate bool) string { + if !isUpdate { + return fmt.Sprintf(` + resource "vault_config_ui_custom_message" "test" { + title = "Maintenance Adviosry" + message_base64 = "%s" + start_time = "2024-02-01T00:00:00Z" + }`, messageBase64) // There's an intentional typo in the title + } else { + return fmt.Sprintf(` + resource "vault_config_ui_custom_message" "test" { + title = "Maintenance Advisory" + message_base64 = "%s" + start_time = "2024-02-01T00:00:00Z" + end_time = "2024-02-27T23:59:59Z" + type = "modal" + authenticated = false + link { + title = "Learn more" + href = "https://www.hashicorp.com" + } + options = { + "background-color" = "red" + } + }`, messageBase64) // That intentional typo in the title is fixed here + } +} + +var f resource.CheckResourceAttrWithFunc + +func TestAccConfigUICustomMessage(t *testing.T) { + resourceName := "vault_config_ui_custom_message.test" + + resource.Test(t, resource.TestCase{ + Providers: testProviders, + PreCheck: func() { testutil.TestAccPreCheck(t) }, + Steps: []resource.TestStep{ + { + Config: testConfigUICustomMessageConfig(false), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, consts.FieldTitle, "Maintenance Adviosry"), // Checking that intentional typo + resource.TestCheckResourceAttr(resourceName, consts.FieldMessageBase64, messageBase64), + resource.TestCheckResourceAttr(resourceName, consts.FieldStartTime, "2024-02-01T00:00:00Z"), + resource.TestCheckResourceAttr(resourceName, consts.FieldEndTime, ""), + resource.TestCheckResourceAttr(resourceName, consts.FieldType, "banner"), + resource.TestCheckResourceAttr(resourceName, consts.FieldAuthenticated, "true"), + ), + }, + { + Config: testConfigUICustomMessageConfig(true), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, consts.FieldTitle, "Maintenance Advisory"), + resource.TestCheckResourceAttr(resourceName, consts.FieldMessageBase64, messageBase64), + resource.TestCheckResourceAttr(resourceName, consts.FieldStartTime, "2024-02-01T00:00:00Z"), + resource.TestCheckResourceAttr(resourceName, consts.FieldEndTime, "2024-02-27T23:59:59Z"), + resource.TestCheckResourceAttr(resourceName, consts.FieldType, "modal"), + resource.TestCheckResourceAttr(resourceName, consts.FieldAuthenticated, "false"), + resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.title", consts.FieldLink), "Learn more"), + resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.href", consts.FieldLink), "https://www.hashicorp.com"), + resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.background-color", consts.FieldOptions), "red"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) + +} From ee56e5f17cc070b0786642afe7442981ffc00ad2 Mon Sep 17 00:00:00 2001 From: Marc Boudreau Date: Thu, 22 Feb 2024 11:59:57 -0500 Subject: [PATCH 02/11] fix filename --- ...tom_messages copy.go => resource_config_ui_custom_messages.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename vault/{resource_config_ui_custom_messages copy.go => resource_config_ui_custom_messages.go} (100%) diff --git a/vault/resource_config_ui_custom_messages copy.go b/vault/resource_config_ui_custom_messages.go similarity index 100% rename from vault/resource_config_ui_custom_messages copy.go rename to vault/resource_config_ui_custom_messages.go From b1de368156c1b648555dfb983083e794dcacf763 Mon Sep 17 00:00:00 2001 From: Marc Boudreau Date: Thu, 22 Feb 2024 16:22:42 -0500 Subject: [PATCH 03/11] corrected field type for link --- vault/resource_config_ui_custom_messages.go | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/vault/resource_config_ui_custom_messages.go b/vault/resource_config_ui_custom_messages.go index 1b1ff16484..6342aaef81 100644 --- a/vault/resource_config_ui_custom_messages.go +++ b/vault/resource_config_ui_custom_messages.go @@ -162,7 +162,7 @@ func configUICustomMessageRead(ctx context.Context, d *schema.ResourceData, meta } } - var linkValue []interface{} + var linkValue *schema.Set var linkMap map[string]interface{} if v, ok := secretData[consts.FieldLink]; ok { @@ -179,12 +179,13 @@ func configUICustomMessageRead(ctx context.Context, d *schema.ResourceData, meta return diag.Errorf("invalid href value in link specification: %v", v) } if len(k) > 0 && len(stringV) > 0 { - linkValue = []interface{}{ + var f schema.SchemaSetFunc // I don't know what to set this to + linkValue = schema.NewSet(f, []interface{}{ map[string]interface{}{ "title": k, "href": stringV, }, - } + }) } break } @@ -215,12 +216,12 @@ func configUICustomMessageUpdate(ctx context.Context, d *schema.ResourceData, me id := d.Id() - if d.HasChanges(consts.FieldTitle, consts.FieldMessageBase64, consts.FieldAuthenticated, consts.FieldType, consts.FieldStartTime, consts.FieldEndTime, consts.FieldOptions, consts.FieldLink) { - e = client.Sys().UpdateUICustomMessageWithContext(ctx, id, buildUICustomMessageRequest(d)) - if e != nil { - return diag.FromErr(e) - } + //if d.HasChanges(consts.FieldTitle, consts.FieldMessageBase64, consts.FieldAuthenticated, consts.FieldType, consts.FieldStartTime, consts.FieldEndTime, consts.FieldOptions, consts.FieldLink) { + e = client.Sys().UpdateUICustomMessageWithContext(ctx, id, buildUICustomMessageRequest(d)) + if e != nil { + return diag.FromErr(e) } + //} return configUICustomMessageRead(ctx, d, meta) } From c5fe3d74d962ac54edfbcf4b8b8c3cdc01c96d16 Mon Sep 17 00:00:00 2001 From: Marc Boudreau Date: Thu, 22 Feb 2024 16:29:51 -0500 Subject: [PATCH 04/11] adjusted link field value based on ssh_backend_role example --- vault/resource_config_ui_custom_messages.go | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/vault/resource_config_ui_custom_messages.go b/vault/resource_config_ui_custom_messages.go index 6342aaef81..bac32bb927 100644 --- a/vault/resource_config_ui_custom_messages.go +++ b/vault/resource_config_ui_custom_messages.go @@ -162,7 +162,7 @@ func configUICustomMessageRead(ctx context.Context, d *schema.ResourceData, meta } } - var linkValue *schema.Set + var linkValue []map[string]interface{} var linkMap map[string]interface{} if v, ok := secretData[consts.FieldLink]; ok { @@ -179,13 +179,11 @@ func configUICustomMessageRead(ctx context.Context, d *schema.ResourceData, meta return diag.Errorf("invalid href value in link specification: %v", v) } if len(k) > 0 && len(stringV) > 0 { - var f schema.SchemaSetFunc // I don't know what to set this to - linkValue = schema.NewSet(f, []interface{}{ - map[string]interface{}{ - "title": k, - "href": stringV, - }, - }) + linkValue = append(linkValue, map[string]interface{}{ + "title": k, + "href": stringV, + }, + ) } break } From 670b52e7657b371f9dea73514691c40e3d88eb96 Mon Sep 17 00:00:00 2001 From: Marc Boudreau Date: Fri, 23 Feb 2024 10:27:39 -0500 Subject: [PATCH 05/11] fixed error with reading link value from API response --- vault/resource_config_ui_custom_messages.go | 29 ++++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/vault/resource_config_ui_custom_messages.go b/vault/resource_config_ui_custom_messages.go index bac32bb927..64c841c252 100644 --- a/vault/resource_config_ui_custom_messages.go +++ b/vault/resource_config_ui_custom_messages.go @@ -29,24 +29,24 @@ func configUICustomMessageResource() *schema.Resource { consts.FieldID: { Type: schema.TypeString, Computed: true, - Description: "Unique ID for the custom message", + Description: "The unique ID for the custom message", }, consts.FieldTitle: { Type: schema.TypeString, Required: true, - Description: "Title of the custom message", + Description: "The title of the custom message", }, consts.FieldMessageBase64: { Type: schema.TypeString, Required: true, - Description: "The base64-encoded message content of the custom message", + Description: "The base64-encoded content of the custom message", }, consts.FieldAuthenticated: { Type: schema.TypeBool, Optional: true, Default: true, - Description: "Flag indicating if custom message is pre-login (false) or post-login (true)", + Description: "A flag indicating whether the custom message is displayed pre-login (false) or post-login (true)", }, consts.FieldType: { Type: schema.TypeString, @@ -65,7 +65,7 @@ func configUICustomMessageResource() *schema.Resource { return nil }, - Description: "Display type of custom message. Allowed values are banner and modal", + Description: "The display type of custom message. Allowed values are banner and modal", }, consts.FieldStartTime: { Type: schema.TypeString, @@ -75,7 +75,7 @@ func configUICustomMessageResource() *schema.Resource { consts.FieldEndTime: { Type: schema.TypeString, Optional: true, - Description: "The ending time of the active period of the custom message", + Description: "The ending time of the active period of the custom message. Can be omitted for non-expiring messages", }, consts.FieldLink: { Type: schema.TypeSet, @@ -139,6 +139,7 @@ func configUICustomMessageRead(ctx context.Context, d *schema.ResourceData, meta id := d.Id() + log.Printf("[DEBUG] Reading custom message %q", id) secret, e := client.Sys().ReadUICustomMessage(id) if e != nil { return diag.FromErr(e) @@ -163,7 +164,6 @@ func configUICustomMessageRead(ctx context.Context, d *schema.ResourceData, meta } var linkValue []map[string]interface{} - var linkMap map[string]interface{} if v, ok := secretData[consts.FieldLink]; ok { if v != nil { @@ -197,12 +197,13 @@ func configUICustomMessageRead(ctx context.Context, d *schema.ResourceData, meta d.Set(consts.FieldStartTime, secretData[consts.FieldStartTime]) d.Set(consts.FieldEndTime, endTimeValue) - if linkMap != nil { + if linkValue != nil { d.Set(consts.FieldLink, linkValue) } d.Set(consts.FieldOptions, secretData[consts.FieldOptions]) + log.Printf("[DEBUG] Read custom message %q", id) return nil } @@ -214,13 +215,15 @@ func configUICustomMessageUpdate(ctx context.Context, d *schema.ResourceData, me id := d.Id() - //if d.HasChanges(consts.FieldTitle, consts.FieldMessageBase64, consts.FieldAuthenticated, consts.FieldType, consts.FieldStartTime, consts.FieldEndTime, consts.FieldOptions, consts.FieldLink) { - e = client.Sys().UpdateUICustomMessageWithContext(ctx, id, buildUICustomMessageRequest(d)) - if e != nil { - return diag.FromErr(e) + if d.HasChanges(consts.FieldTitle, consts.FieldMessageBase64, consts.FieldAuthenticated, consts.FieldType, consts.FieldStartTime, consts.FieldEndTime, consts.FieldOptions, consts.FieldLink) { + log.Printf("[DEBUG] Updating custom message %q", id) + e = client.Sys().UpdateUICustomMessageWithContext(ctx, id, buildUICustomMessageRequest(d)) + if e != nil { + return diag.FromErr(e) + } } - //} + log.Printf("[DEBUG] Updated custom message %q", id) return configUICustomMessageRead(ctx, d, meta) } From c9be60e5f7ef8a54d2285d1fcc73cadf32896dc9 Mon Sep 17 00:00:00 2001 From: Marc Boudreau Date: Fri, 23 Feb 2024 10:54:13 -0500 Subject: [PATCH 06/11] add CHANGELOG entry --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e85629139..b643667af1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ## Unreleased +FEATURES: +* Add new resource `vault_config_ui_custom_message`: ([#2154](https://github.com/hashicorp/terraform-provider-vault/pull/2154)). + BUGS: * Handle graceful destruction of resources when approle is deleted out-of-band ([#2142](https://github.com/hashicorp/terraform-provider-vault/pull/2142)). From 09724406efb5203da87f2252ffd4e5a59f9e50a6 Mon Sep 17 00:00:00 2001 From: Marc Boudreau Date: Fri, 23 Feb 2024 15:00:01 -0500 Subject: [PATCH 07/11] fix failing unit test clean up unnecessary code --- vault/resource_config_ui_custom_messages_test.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/vault/resource_config_ui_custom_messages_test.go b/vault/resource_config_ui_custom_messages_test.go index 2b86b37f12..04681520a8 100644 --- a/vault/resource_config_ui_custom_messages_test.go +++ b/vault/resource_config_ui_custom_messages_test.go @@ -45,14 +45,12 @@ func testConfigUICustomMessageConfig(isUpdate bool) string { } } -var f resource.CheckResourceAttrWithFunc - func TestAccConfigUICustomMessage(t *testing.T) { resourceName := "vault_config_ui_custom_message.test" resource.Test(t, resource.TestCase{ - Providers: testProviders, - PreCheck: func() { testutil.TestAccPreCheck(t) }, + ProviderFactories: providerFactories, + PreCheck: func() { testutil.TestAccPreCheck(t) }, Steps: []resource.TestStep{ { Config: testConfigUICustomMessageConfig(false), From 24fae9533ca8f963b81e968c97c410ee067c1225 Mon Sep 17 00:00:00 2001 From: Marc Boudreau Date: Fri, 8 Mar 2024 10:57:47 -0500 Subject: [PATCH 08/11] rename files to non-plural form --- ...sages.go => resource_config_ui_custom_message.go} | 12 +++++++++--- ....go => resource_config_ui_custom_message_test.go} | 6 +++++- 2 files changed, 14 insertions(+), 4 deletions(-) rename vault/{resource_config_ui_custom_messages.go => resource_config_ui_custom_message.go} (95%) rename vault/{resource_config_ui_custom_messages_test.go => resource_config_ui_custom_message_test.go} (94%) diff --git a/vault/resource_config_ui_custom_messages.go b/vault/resource_config_ui_custom_message.go similarity index 95% rename from vault/resource_config_ui_custom_messages.go rename to vault/resource_config_ui_custom_message.go index 64c841c252..55968cc5c5 100644 --- a/vault/resource_config_ui_custom_messages.go +++ b/vault/resource_config_ui_custom_message.go @@ -17,7 +17,7 @@ import ( func configUICustomMessageResource() *schema.Resource { return &schema.Resource{ - CreateContext: configUICustomMessageCreate, + CreateContext: provider.MountCreateContextWrapper(configUICustomMessageCreate, provider.VaultVersion116), ReadContext: configUICustomMessageRead, UpdateContext: configUICustomMessageUpdate, DeleteContext: configUICustomMessageDelete, @@ -75,7 +75,7 @@ func configUICustomMessageResource() *schema.Resource { consts.FieldEndTime: { Type: schema.TypeString, Optional: true, - Description: "The ending time of the active period of the custom message. Can be omitted for non-expiring messages", + Description: "The ending time of the active period of the custom message. Can be omitted for non-expiring message", }, consts.FieldLink: { Type: schema.TypeSet, @@ -107,6 +107,10 @@ func configUICustomMessageResource() *schema.Resource { } func configUICustomMessageCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + if !provider.IsEnterpriseSupported(meta) { + return diag.Errorf("config_ui_custom_message is not supported by this version of vault") + } + client, e := provider.GetClient(d, meta) if e != nil { return diag.FromErr(e) @@ -146,7 +150,9 @@ func configUICustomMessageRead(ctx context.Context, d *schema.ResourceData, meta } if secret == nil || secret.Data == nil { - return diag.Errorf("response from Vault server is empty") + log.Printf("response from Vault server is empty") + d.SetId("") + return nil } secretData := secret.Data diff --git a/vault/resource_config_ui_custom_messages_test.go b/vault/resource_config_ui_custom_message_test.go similarity index 94% rename from vault/resource_config_ui_custom_messages_test.go rename to vault/resource_config_ui_custom_message_test.go index 04681520a8..8e19fcd22b 100644 --- a/vault/resource_config_ui_custom_messages_test.go +++ b/vault/resource_config_ui_custom_message_test.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-provider-vault/internal/consts" + "github.com/hashicorp/terraform-provider-vault/internal/provider" "github.com/hashicorp/terraform-provider-vault/testutil" ) @@ -50,7 +51,10 @@ func TestAccConfigUICustomMessage(t *testing.T) { resource.Test(t, resource.TestCase{ ProviderFactories: providerFactories, - PreCheck: func() { testutil.TestAccPreCheck(t) }, + PreCheck: func() { + testutil.TestAccPreCheck(t) + SkipIfAPIVersionLT(t, testProvider.Meta(), provider.VaultVersion116) + }, Steps: []resource.TestStep{ { Config: testConfigUICustomMessageConfig(false), From 6eb6bfe445f8a89c582158152a7a46e5961c4bd3 Mon Sep 17 00:00:00 2001 From: Marc Boudreau Date: Fri, 8 Mar 2024 10:58:32 -0500 Subject: [PATCH 09/11] improve change description and moved it to existing FEATURES section --- CHANGELOG.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b643667af1..07c927f409 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,5 @@ ## Unreleased -FEATURES: -* Add new resource `vault_config_ui_custom_message`: ([#2154](https://github.com/hashicorp/terraform-provider-vault/pull/2154)). - BUGS: * Handle graceful destruction of resources when approle is deleted out-of-band ([#2142](https://github.com/hashicorp/terraform-provider-vault/pull/2142)). @@ -11,6 +8,7 @@ FEATURES: * Add support to `enable_templating` in `vault_pki_secret_backend_config_urls` ([#2147](https://github.com/hashicorp/terraform-provider-vault/pull/2147)). * Add support for `skip_import_rotation` and `skip_static_role_import_rotation` in `ldap_secret_backend_static_role` and `ldap_secret_backend` respectively. Requires Vault 1.16+ ([#2128](https://github.com/hashicorp/terraform-provider-vault/pull/2128)). * Improve logging to track full API exchanges between the provider and Vault ([#2139](https://github.com/hashicorp/terraform-provider-vault/pull/2139)) +* Add new resource `vault_config_ui_custom_message`. Requires Vault 1.16+ Enterprise: ([#2154](https://github.com/hashicorp/terraform-provider-vault/pull/2154)). ## 3.25.0 (Feb 14, 2024) From 0129c2dcd6b2fee095ae2de4c92eb1f27ca4e7b1 Mon Sep 17 00:00:00 2001 From: Marc Boudreau Date: Fri, 8 Mar 2024 11:02:25 -0500 Subject: [PATCH 10/11] add documentation for new resource --- .../docs/r/config_ui_custom_messages.html.md | 69 +++++++++++++++++++ website/vault.erb | 4 ++ 2 files changed, 73 insertions(+) create mode 100644 website/docs/r/config_ui_custom_messages.html.md diff --git a/website/docs/r/config_ui_custom_messages.html.md b/website/docs/r/config_ui_custom_messages.html.md new file mode 100644 index 0000000000..85dd0a1d7c --- /dev/null +++ b/website/docs/r/config_ui_custom_messages.html.md @@ -0,0 +1,69 @@ +--- +layout: "vault" +page_title: "Vault: vault_config_ui_custom_message resource" +sidebar_current: "docs-vault-resource-config-ui-custom-message" +description: |- + Manages a UI custom message in Vault. +--- + +# vault\_config\_ui\_custom\_message + +Manages a UI custom message in Vault. Custom messages are displayed in the Vault UI either on the login page or immediately after succesfully logging in. + +## Example Usage + +```hcl +resource "vault_config_ui_custom_message" "maintenance" { + title = "Upcoming maintenance" + message = base64encode("Vault will be offline for planned maintenance on February 1st, 2024 from 05:00Z to 08:00Z") + type = "banner" + authenticated = true + start_time = "2024-01-01T00:00:00.000Z" + end_time = "2024-02-01T05:00:00.000Z" +} +``` + +## 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#namespace). + *Available only for Vault Enterprise*. + +* `title` - (Required) The title of the custom message to create. + +* `message` - (Required) The base64-encoded content of the custom message. + +* `start_time` - (Required) The time when the custom message begins to be active. This value can be set to a future time, but cannot + occur on or after the `end_time` value. + +* `authenticated` - (Optional) The value `true` if the custom message is displayed after logins are completed or `false` if they are + displayed during the login in the Vault UI. The default value is `true`. + +* `type` - (Optional) The presentation type of the custom message. Must be one of the following values: `banner` or `modal`. + +* `end_time` - (Optional) The time when the custom message expires. If this value is not specified, the custom message never expires. + +* `link` - (Optional) A hyperlink to be included with the message. [See below for more details](#link). + +* `options` - (Optional) A map of additional options that can be set on the custom message. + +### Link + +* `title` - (Required) The hyperlink title that is displayed in the custom message. + +* `href` - (Required) The URL set in the hyperlink's href attribute. + +## Attributes Reference + +No additional attributes are exported by this resource. + +## Import + +Custom messages can be imported using their `id` e.g. + +``` +$ terraform import vault_config_ui_custom_message.maintenance df773ef1-2794-45d3-9e25-bcccffe4dbde +``` diff --git a/website/vault.erb b/website/vault.erb index 70bd6a6081..c61b34ee9c 100644 --- a/website/vault.erb +++ b/website/vault.erb @@ -249,6 +249,10 @@ vault_cert_auth_backend_role + > + vault_config_ui_custom_message + + > vault_consul_secret_backend From 759323ab6b4eb73f32102273093bcbdb4a8cb3e6 Mon Sep 17 00:00:00 2001 From: Marc Boudreau Date: Tue, 19 Mar 2024 11:27:44 -0400 Subject: [PATCH 11/11] remove custom-message resource from state if it doesn't exist in Vault fix debug log message format --- vault/resource_config_ui_custom_message.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/vault/resource_config_ui_custom_message.go b/vault/resource_config_ui_custom_message.go index 55968cc5c5..91d6ff9850 100644 --- a/vault/resource_config_ui_custom_message.go +++ b/vault/resource_config_ui_custom_message.go @@ -12,6 +12,7 @@ import ( "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" "github.com/hashicorp/vault/api" ) @@ -146,11 +147,16 @@ func configUICustomMessageRead(ctx context.Context, d *schema.ResourceData, meta log.Printf("[DEBUG] Reading custom message %q", id) secret, e := client.Sys().ReadUICustomMessage(id) if e != nil { + if util.Is404(e) { + log.Printf("[DEBUG] custom message %q not found, removing from state", id) + d.SetId("") + return nil + } return diag.FromErr(e) } if secret == nil || secret.Data == nil { - log.Printf("response from Vault server is empty") + log.Printf("[DEBUG] response from Vault server is empty for %q, removing from state", id) d.SetId("") return nil }