internal/elasticsearch/security/user.go (202 lines of code) (raw):
package security
import (
"context"
"encoding/json"
"fmt"
"regexp"
"strings"
"github.com/elastic/terraform-provider-elasticstack/internal/clients"
"github.com/elastic/terraform-provider-elasticstack/internal/clients/elasticsearch"
"github.com/elastic/terraform-provider-elasticstack/internal/models"
"github.com/elastic/terraform-provider-elasticstack/internal/utils"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)
func ResourceUser() *schema.Resource {
userSchema := map[string]*schema.Schema{
"id": {
Description: "Internal identifier of the resource",
Type: schema.TypeString,
Computed: true,
},
"username": {
Description: "An identifier for the user (see https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-put-user.html#security-api-put-user-path-params).",
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.All(
validation.StringLenBetween(1, 1024),
validation.StringMatch(regexp.MustCompile(`^[[:graph:]]+$`), "must contain alphanumeric characters (a-z, A-Z, 0-9), spaces, punctuation, and printable symbols in the Basic Latin (ASCII) block. Leading or trailing whitespace is not allowed"),
),
},
"password": {
Description: "The user’s password. Passwords must be at least 6 characters long.",
Type: schema.TypeString,
Optional: true,
Sensitive: true,
ValidateFunc: validation.StringLenBetween(6, 128),
ConflictsWith: []string{"password_hash"},
},
"password_hash": {
Description: "A hash of the user’s password. This must be produced using the same hashing algorithm as has been configured for password storage (see https://www.elastic.co/guide/en/elasticsearch/reference/current/security-settings.html#hashing-settings).",
Type: schema.TypeString,
Optional: true,
Sensitive: true,
ValidateFunc: validation.StringLenBetween(6, 128),
ConflictsWith: []string{"password"},
},
"full_name": {
Description: "The full name of the user.",
Type: schema.TypeString,
Optional: true,
Default: "",
},
"email": {
Description: "The email of the user.",
Type: schema.TypeString,
Optional: true,
Default: "",
},
"roles": {
Description: "A set of roles the user has. The roles determine the user’s access permissions. Default is [].",
Type: schema.TypeSet,
Required: true,
MinItems: 1,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
"metadata": {
Description: "Arbitrary metadata that you want to associate with the user.",
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateFunc: validation.StringIsJSON,
DiffSuppressFunc: utils.DiffJsonSuppress,
},
"enabled": {
Description: "Specifies whether the user is enabled. The default value is true.",
Type: schema.TypeBool,
Optional: true,
Default: true,
},
}
utils.AddConnectionSchema(userSchema)
return &schema.Resource{
Description: "Adds and updates users in the native realm. These users are commonly referred to as native users. See, https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-put-user.html",
CreateContext: resourceSecurityUserPut,
UpdateContext: resourceSecurityUserPut,
ReadContext: resourceSecurityUserRead,
DeleteContext: resourceSecurityUserDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: userSchema,
}
}
func resourceSecurityUserPut(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client, diags := clients.NewApiClientFromSDKResource(d, meta)
if diags.HasError() {
return diags
}
usernameId := d.Get("username").(string)
id, diags := client.ID(ctx, usernameId)
if diags.HasError() {
return diags
}
var user models.User
user.Username = usernameId
if v, ok := d.GetOk("password"); ok && d.HasChange("password") {
password := v.(string)
user.Password = &password
}
if v, ok := d.GetOk("password_hash"); ok && d.HasChange("password_hash") {
pass_hash := v.(string)
user.PasswordHash = &pass_hash
}
if v, ok := d.GetOk("email"); ok {
user.Email = v.(string)
}
if v, ok := d.GetOk("full_name"); ok {
user.FullName = v.(string)
}
user.Enabled = d.Get("enabled").(bool)
roles := make([]string, 0)
if v, ok := d.GetOk("roles"); ok {
for _, role := range v.(*schema.Set).List() {
roles = append(roles, role.(string))
}
}
user.Roles = roles
if v, ok := d.GetOk("metadata"); ok {
metadata := make(map[string]interface{})
if err := json.NewDecoder(strings.NewReader(v.(string))).Decode(&metadata); err != nil {
return diag.FromErr(err)
}
user.Metadata = metadata
}
if diags := elasticsearch.PutUser(ctx, client, &user); diags.HasError() {
return diags
}
d.SetId(id.String())
return resourceSecurityUserRead(ctx, d, meta)
}
func resourceSecurityUserRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client, diags := clients.NewApiClientFromSDKResource(d, meta)
if diags.HasError() {
return diags
}
compId, diags := clients.CompositeIdFromStr(d.Id())
if diags.HasError() {
return diags
}
usernameId := compId.ResourceId
user, diags := elasticsearch.GetUser(ctx, client, usernameId)
if user == nil && diags == nil {
tflog.Warn(ctx, fmt.Sprintf(`User "%s" not found, removing from state`, compId.ResourceId))
d.SetId("")
return diags
}
if diags.HasError() {
return diags
}
metadata, err := json.Marshal(user.Metadata)
if err != nil {
return diag.FromErr(err)
}
// set the fields
if err := d.Set("username", usernameId); err != nil {
return diag.FromErr(err)
}
if err := d.Set("email", user.Email); err != nil {
return diag.FromErr(err)
}
if err := d.Set("full_name", user.FullName); err != nil {
return diag.FromErr(err)
}
if err := d.Set("roles", user.Roles); err != nil {
return diag.FromErr(err)
}
if err := d.Set("metadata", string(metadata)); err != nil {
return diag.FromErr(err)
}
if err := d.Set("enabled", user.Enabled); err != nil {
return diag.FromErr(err)
}
return diags
}
func resourceSecurityUserDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client, diags := clients.NewApiClientFromSDKResource(d, meta)
if diags.HasError() {
return diags
}
compId, diags := clients.CompositeIdFromStr(d.Id())
if diags.HasError() {
return diags
}
if diags := elasticsearch.DeleteUser(ctx, client, compId.ResourceId); diags.HasError() {
return diags
}
return diags
}