-
Notifications
You must be signed in to change notification settings - Fork 540
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
Add vault_config_ui_custom_message resource #2154
Merged
marcboudreau
merged 11 commits into
main
from
marcboudreau/VAULT-22504/custom-message-resource
Mar 19, 2024
Merged
Changes from 10 commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
038f19b
add custom message resource
ee56e5f
fix filename
b1de368
corrected field type for link
c5fe3d7
adjusted link field value based on ssh_backend_role example
670b52e
fixed error with reading link value from API response
c9be60e
add CHANGELOG entry
0972440
fix failing unit test
24fae95
rename files to non-plural form
6eb6bfe
improve change description and moved it to existing FEATURES section
0129c2d
add documentation for new resource
759323a
remove custom-message resource from state if it doesn't exist in Vault
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,277 @@ | ||||||
// 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: provider.MountCreateContextWrapper(configUICustomMessageCreate, provider.VaultVersion116), | ||||||
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: "The unique ID for the custom message", | ||||||
}, | ||||||
consts.FieldTitle: { | ||||||
Type: schema.TypeString, | ||||||
Required: true, | ||||||
Description: "The title of the custom message", | ||||||
}, | ||||||
consts.FieldMessageBase64: { | ||||||
Type: schema.TypeString, | ||||||
Required: true, | ||||||
Description: "The base64-encoded content of the custom message", | ||||||
}, | ||||||
consts.FieldAuthenticated: { | ||||||
Type: schema.TypeBool, | ||||||
Optional: true, | ||||||
|
||||||
Default: true, | ||||||
Description: "A flag indicating whether the custom message is displayed 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: "The 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. Can be omitted for non-expiring 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 { | ||||||
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) | ||||||
} | ||||||
|
||||||
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() | ||||||
|
||||||
log.Printf("[DEBUG] Reading custom message %q", id) | ||||||
secret, e := client.Sys().ReadUICustomMessage(id) | ||||||
if e != nil { | ||||||
return diag.FromErr(e) | ||||||
} | ||||||
|
||||||
if secret == nil || secret.Data == nil { | ||||||
log.Printf("response from Vault server is empty") | ||||||
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 need to prepend the log with a level. I would recommend either DEBUG or WARN:
Suggested change
|
||||||
d.SetId("") | ||||||
return nil | ||||||
} | ||||||
|
||||||
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 []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 = append(linkValue, 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 linkValue != nil { | ||||||
d.Set(consts.FieldLink, linkValue) | ||||||
} | ||||||
|
||||||
d.Set(consts.FieldOptions, secretData[consts.FieldOptions]) | ||||||
|
||||||
log.Printf("[DEBUG] Read custom message %q", id) | ||||||
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) { | ||||||
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) | ||||||
} | ||||||
|
||||||
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 | ||||||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this endpoint return a 404 error if the resource isn't found or is the next if block how we know the resource does not exist in Vault? If Vault does return a 404, we should remove it from TF state (
d.setId("")
) here as well.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.
Here is an example of doing this check:
terraform-provider-vault/vault/resource_approle_auth_backend_role.go
Lines 299 to 307 in 9f97824
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.
Vault does indeed return an HTTP 404 error if no message with the ID provided to the ReadUICustomMessage function exists.