From 002b9083a3ccd49460328cd8253a9b53d61bd9f2 Mon Sep 17 00:00:00 2001 From: Toby Brain Date: Tue, 27 May 2025 22:23:08 +1000 Subject: [PATCH 01/10] Migrate system user to plugin fw --- .../elasticsearch/security/system_user.go | 273 +++++++++++------- provider/plugin_framework.go | 2 + provider/provider.go | 1 - 3 files changed, 165 insertions(+), 111 deletions(-) diff --git a/internal/elasticsearch/security/system_user.go b/internal/elasticsearch/security/system_user.go index fc2b3cf2a..90889aae4 100644 --- a/internal/elasticsearch/security/system_user.go +++ b/internal/elasticsearch/security/system_user.go @@ -9,153 +9,206 @@ import ( "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-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" "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 ResourceSystemUser() *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 system user (see https://www.elastic.co/guide/en/elasticsearch/reference/current/built-in-users.html).", - 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"}, - }, - "enabled": { - Description: "Specifies whether the user is enabled. The default value is true.", - Type: schema.TypeBool, - Optional: true, - Default: true, - }, - } +func NewSystemUserResource() resource.Resource { + return &systemUserResource{} +} + +type systemUserResource struct { + client *clients.ApiClient +} - utils.AddConnectionSchema(userSchema) +func (r *systemUserResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_system_user" +} - return &schema.Resource{ - Description: "Updates system user's password and enablement. See, https://www.elastic.co/guide/en/elasticsearch/reference/current/built-in-users.html", +func (r *systemUserResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Updates system user's password and enablement. See, https://www.elastic.co/guide/en/elasticsearch/reference/current/built-in-users.html", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "Internal identifier of the resource", + Computed: true, + }, + "username": schema.StringAttribute{ + MarkdownDescription: "An identifier for the system user (see https://www.elastic.co/guide/en/elasticsearch/reference/current/built-in-users.html).", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 1024), + stringvalidator.RegexMatches(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": schema.StringAttribute{ + MarkdownDescription: "The user's password. Passwords must be at least 6 characters long.", + Optional: true, + Sensitive: true, + Validators: []validator.String{ + stringvalidator.LengthBetween(6, 128), + }, + }, + "password_hash": schema.StringAttribute{ + MarkdownDescription: "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).", + Optional: true, + Sensitive: true, + Validators: []validator.String{ + stringvalidator.LengthBetween(6, 128), + }, + }, + "enabled": schema.BoolAttribute{ + MarkdownDescription: "Specifies whether the user is enabled. The default value is true.", + Optional: true, + Default: booldefault.StaticBool(true), + }, + }, + } +} - CreateContext: resourceSecuritySystemUserPut, - UpdateContext: resourceSecuritySystemUserPut, - ReadContext: resourceSecuritySystemUserRead, - DeleteContext: resourceSecuritySystemUserDelete, +func (r *systemUserResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + client, diags := clients.ConvertProviderData(req.ProviderData) + resp.Diagnostics.Append(diags...) + r.client = client +} - Schema: userSchema, +func (r *systemUserResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + diags := r.update(ctx, req.Plan, &resp.State) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return } } -func resourceSecuritySystemUserPut(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client, diags := clients.NewApiClientFromSDKResource(d, meta) - if diags.HasError() { - return diags +func (r *systemUserResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data SystemUserData + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return } - usernameId := d.Get("username").(string) - id, diags := client.ID(ctx, usernameId) - if diags.HasError() { - return diags + + compId, diags := clients.CompositeIdFromStrFw(data.Id.ValueString()) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return } + usernameId := compId.ResourceId - user, diags := elasticsearch.GetUser(ctx, client, usernameId) - if diags.HasError() { - return diags + user, sdkDiags := elasticsearch.GetUser(ctx, r.client, usernameId) + diags = utils.ConvertSDKDiagnosticsToFramework(sdkDiags) + if diags == nil && (user == nil || !user.IsSystemUser()) { + tflog.Warn(ctx, fmt.Sprintf(`System user "%s" not found, removing from state`, compId.ResourceId)) + resp.State.RemoveResource(ctx) + return } - if user == nil || !user.IsSystemUser() { - return diag.Errorf(`System user "%s" not found`, usernameId) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return } - var userPassword models.UserPassword - if v, ok := d.GetOk("password"); ok && d.HasChange("password") { - password := v.(string) - userPassword.Password = &password - } - if v, ok := d.GetOk("password_hash"); ok && d.HasChange("password_hash") { - pass_hash := v.(string) - userPassword.PasswordHash = &pass_hash + data.Username = types.StringValue(usernameId) + data.Enabled = types.BoolValue(user.Enabled) + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *systemUserResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + diags := r.update(ctx, req.Plan, &resp.State) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return } - if userPassword.Password != nil || userPassword.PasswordHash != nil { - if diags := elasticsearch.ChangeUserPassword(ctx, client, usernameId, &userPassword); diags.HasError() { - return diags - } +} + +func (r *systemUserResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data SystemUserData + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return } - if d.HasChange("enabled") { - if d.Get("enabled").(bool) { - if diags := elasticsearch.EnableUser(ctx, client, usernameId); diags.HasError() { - return diags - } - } else { - if diags := elasticsearch.DisableUser(ctx, client, usernameId); diags.HasError() { - return diags - } - } + compId, diags := clients.CompositeIdFromStrFw(data.Id.ValueString()) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return } + tflog.Warn(ctx, fmt.Sprintf(`System user '%s' is not deletable, just removing from state`, compId.ResourceId)) +} - d.SetId(id.String()) - return resourceSecuritySystemUserRead(ctx, d, meta) +type SystemUserData struct { + Id types.String `tfsdk:"id"` + Username types.String `tfsdk:"username"` + Password types.String `tfsdk:"password"` + PasswordHash types.String `tfsdk:"password_hash"` + Enabled types.Bool `tfsdk:"enabled"` } -func resourceSecuritySystemUserRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client, diags := clients.NewApiClientFromSDKResource(d, meta) +func (r *systemUserResource) update(ctx context.Context, plan tfsdk.Plan, state *tfsdk.State) diag.Diagnostics { + var data SystemUserData + var diags diag.Diagnostics + diags.Append(plan.Get(ctx, &data)...) if diags.HasError() { return diags } - compId, diags := clients.CompositeIdFromStr(d.Id()) + + usernameId := data.Username.ValueString() + id, sdkDiags := r.client.ID(ctx, usernameId) + diags.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...) if diags.HasError() { return diags } - usernameId := compId.ResourceId - user, diags := elasticsearch.GetUser(ctx, client, usernameId) - if diags == nil && (user == nil || !user.IsSystemUser()) { - tflog.Warn(ctx, fmt.Sprintf(`System user "%s" not found, removing from state`, compId.ResourceId)) - d.SetId("") + user, sdkDiags := elasticsearch.GetUser(ctx, r.client, usernameId) + diags.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...) + if diags.HasError() { return diags } - if diags.HasError() { + if user == nil || !user.IsSystemUser() { + diags.AddError("", fmt.Sprintf(`System user "%s" not found`, usernameId)) return diags } - if err := d.Set("username", usernameId); err != nil { - return diag.FromErr(err) + var userPassword models.UserPassword + if utils.IsKnown(data.Password) && (user.Password == nil || data.Password.ValueString() != *user.Password) { + userPassword.Password = data.Password.ValueStringPointer() } - if err := d.Set("enabled", user.Enabled); err != nil { - return diag.FromErr(err) + if utils.IsKnown(data.PasswordHash) && (user.PasswordHash == nil || data.PasswordHash.ValueString() != *user.PasswordHash) { + userPassword.PasswordHash = data.PasswordHash.ValueStringPointer() + } + if userPassword.Password != nil || userPassword.PasswordHash != nil { + sdkDiags := elasticsearch.ChangeUserPassword(ctx, r.client, usernameId, &userPassword) + diags.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...) + if diags.HasError() { + return diags + } } - return diags -} - -func resourceSecuritySystemUserDelete(ctx context.Context, d *schema.ResourceData, _ interface{}) diag.Diagnostics { - compId, diags := clients.CompositeIdFromStr(d.Id()) - if diags.HasError() { - return diags + if utils.IsKnown(data.Enabled) && !data.Enabled.IsNull() && data.Enabled.ValueBool() != user.Enabled { + if data.Enabled.ValueBool() { + sdkDiags := elasticsearch.EnableUser(ctx, r.client, usernameId) + diags.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...) + } else { + sdkDiags := elasticsearch.DisableUser(ctx, r.client, usernameId) + diags.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...) + } + if diags.HasError() { + return diags + } } - tflog.Warn(ctx, fmt.Sprintf(`System user '%s' is not deletable, just removing from state`, compId.ResourceId)) - return nil + + data.Id = types.StringValue(id.String()) + diags.Append(state.Set(ctx, &data)...) + return diags } diff --git a/provider/plugin_framework.go b/provider/plugin_framework.go index 04752f129..49af3f009 100644 --- a/provider/plugin_framework.go +++ b/provider/plugin_framework.go @@ -8,6 +8,7 @@ import ( "github.com/elastic/terraform-provider-elasticstack/internal/elasticsearch/index/data_stream_lifecycle" "github.com/elastic/terraform-provider-elasticstack/internal/elasticsearch/index/index" "github.com/elastic/terraform-provider-elasticstack/internal/elasticsearch/index/indices" + "github.com/elastic/terraform-provider-elasticstack/internal/elasticsearch/security" "github.com/elastic/terraform-provider-elasticstack/internal/elasticsearch/security/api_key" "github.com/elastic/terraform-provider-elasticstack/internal/fleet/agent_policy" "github.com/elastic/terraform-provider-elasticstack/internal/fleet/enrollment_tokens" @@ -100,5 +101,6 @@ func (p *Provider) Resources(ctx context.Context) []func() resource.Resource { integration_policy.NewResource, output.NewResource, server_host.NewResource, + security.NewSystemUserResource, } } diff --git a/provider/provider.go b/provider/provider.go index 105ee5067..15e5a32b6 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -93,7 +93,6 @@ func New(version string) *schema.Provider { "elasticstack_elasticsearch_security_role": security.ResourceRole(), "elasticstack_elasticsearch_security_role_mapping": security.ResourceRoleMapping(), "elasticstack_elasticsearch_security_user": security.ResourceUser(), - "elasticstack_elasticsearch_security_system_user": security.ResourceSystemUser(), "elasticstack_elasticsearch_snapshot_lifecycle": cluster.ResourceSlm(), "elasticstack_elasticsearch_snapshot_repository": cluster.ResourceSnapshotRepository(), "elasticstack_elasticsearch_script": cluster.ResourceScript(), From 742ddd73672e1b2d24b45afc26eb4b0bdc436bda Mon Sep 17 00:00:00 2001 From: Toby Brain Date: Tue, 27 May 2025 22:47:01 +1000 Subject: [PATCH 02/10] Move the resource to it's own package --- .../elasticsearch/security/system_user.go | 214 ------------------ .../acc_test.go} | 5 +- .../security/system_user/create.go | 15 ++ .../security/system_user/delete.go | 25 ++ .../security/system_user/models.go | 13 ++ .../security/system_user/read.go | 45 ++++ .../security/system_user/resource.go | 26 +++ .../security/system_user/schema.go | 63 ++++++ .../security/system_user/update.go | 80 +++++++ provider/plugin_framework.go | 4 +- 10 files changed, 272 insertions(+), 218 deletions(-) delete mode 100644 internal/elasticsearch/security/system_user.go rename internal/elasticsearch/security/{system_user_test.go => system_user/acc_test.go} (98%) create mode 100644 internal/elasticsearch/security/system_user/create.go create mode 100644 internal/elasticsearch/security/system_user/delete.go create mode 100644 internal/elasticsearch/security/system_user/models.go create mode 100644 internal/elasticsearch/security/system_user/read.go create mode 100644 internal/elasticsearch/security/system_user/resource.go create mode 100644 internal/elasticsearch/security/system_user/schema.go create mode 100644 internal/elasticsearch/security/system_user/update.go diff --git a/internal/elasticsearch/security/system_user.go b/internal/elasticsearch/security/system_user.go deleted file mode 100644 index 90889aae4..000000000 --- a/internal/elasticsearch/security/system_user.go +++ /dev/null @@ -1,214 +0,0 @@ -package security - -import ( - "context" - "fmt" - "regexp" - - "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-framework-validators/stringvalidator" - "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/resource" - "github.com/hashicorp/terraform-plugin-framework/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" - "github.com/hashicorp/terraform-plugin-framework/schema/validator" - "github.com/hashicorp/terraform-plugin-framework/tfsdk" - "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-log/tflog" -) - -func NewSystemUserResource() resource.Resource { - return &systemUserResource{} -} - -type systemUserResource struct { - client *clients.ApiClient -} - -func (r *systemUserResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = req.ProviderTypeName + "_system_user" -} - -func (r *systemUserResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ - MarkdownDescription: "Updates system user's password and enablement. See, https://www.elastic.co/guide/en/elasticsearch/reference/current/built-in-users.html", - Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - MarkdownDescription: "Internal identifier of the resource", - Computed: true, - }, - "username": schema.StringAttribute{ - MarkdownDescription: "An identifier for the system user (see https://www.elastic.co/guide/en/elasticsearch/reference/current/built-in-users.html).", - Required: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - Validators: []validator.String{ - stringvalidator.LengthBetween(1, 1024), - stringvalidator.RegexMatches(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": schema.StringAttribute{ - MarkdownDescription: "The user's password. Passwords must be at least 6 characters long.", - Optional: true, - Sensitive: true, - Validators: []validator.String{ - stringvalidator.LengthBetween(6, 128), - }, - }, - "password_hash": schema.StringAttribute{ - MarkdownDescription: "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).", - Optional: true, - Sensitive: true, - Validators: []validator.String{ - stringvalidator.LengthBetween(6, 128), - }, - }, - "enabled": schema.BoolAttribute{ - MarkdownDescription: "Specifies whether the user is enabled. The default value is true.", - Optional: true, - Default: booldefault.StaticBool(true), - }, - }, - } -} - -func (r *systemUserResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { - client, diags := clients.ConvertProviderData(req.ProviderData) - resp.Diagnostics.Append(diags...) - r.client = client -} - -func (r *systemUserResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - diags := r.update(ctx, req.Plan, &resp.State) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } -} - -func (r *systemUserResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { - var data SystemUserData - resp.Diagnostics.Append(req.State.Get(ctx, &data)...) - if resp.Diagnostics.HasError() { - return - } - - compId, diags := clients.CompositeIdFromStrFw(data.Id.ValueString()) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - usernameId := compId.ResourceId - - user, sdkDiags := elasticsearch.GetUser(ctx, r.client, usernameId) - diags = utils.ConvertSDKDiagnosticsToFramework(sdkDiags) - if diags == nil && (user == nil || !user.IsSystemUser()) { - tflog.Warn(ctx, fmt.Sprintf(`System user "%s" not found, removing from state`, compId.ResourceId)) - resp.State.RemoveResource(ctx) - return - } - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - data.Username = types.StringValue(usernameId) - data.Enabled = types.BoolValue(user.Enabled) - - resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) -} - -func (r *systemUserResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - diags := r.update(ctx, req.Plan, &resp.State) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } -} - -func (r *systemUserResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { - var data SystemUserData - resp.Diagnostics.Append(req.State.Get(ctx, &data)...) - if resp.Diagnostics.HasError() { - return - } - - compId, diags := clients.CompositeIdFromStrFw(data.Id.ValueString()) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - tflog.Warn(ctx, fmt.Sprintf(`System user '%s' is not deletable, just removing from state`, compId.ResourceId)) -} - -type SystemUserData struct { - Id types.String `tfsdk:"id"` - Username types.String `tfsdk:"username"` - Password types.String `tfsdk:"password"` - PasswordHash types.String `tfsdk:"password_hash"` - Enabled types.Bool `tfsdk:"enabled"` -} - -func (r *systemUserResource) update(ctx context.Context, plan tfsdk.Plan, state *tfsdk.State) diag.Diagnostics { - var data SystemUserData - var diags diag.Diagnostics - diags.Append(plan.Get(ctx, &data)...) - if diags.HasError() { - return diags - } - - usernameId := data.Username.ValueString() - id, sdkDiags := r.client.ID(ctx, usernameId) - diags.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...) - if diags.HasError() { - return diags - } - - user, sdkDiags := elasticsearch.GetUser(ctx, r.client, usernameId) - diags.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...) - if diags.HasError() { - return diags - } - if user == nil || !user.IsSystemUser() { - diags.AddError("", fmt.Sprintf(`System user "%s" not found`, usernameId)) - return diags - } - - var userPassword models.UserPassword - if utils.IsKnown(data.Password) && (user.Password == nil || data.Password.ValueString() != *user.Password) { - userPassword.Password = data.Password.ValueStringPointer() - } - if utils.IsKnown(data.PasswordHash) && (user.PasswordHash == nil || data.PasswordHash.ValueString() != *user.PasswordHash) { - userPassword.PasswordHash = data.PasswordHash.ValueStringPointer() - } - if userPassword.Password != nil || userPassword.PasswordHash != nil { - sdkDiags := elasticsearch.ChangeUserPassword(ctx, r.client, usernameId, &userPassword) - diags.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...) - if diags.HasError() { - return diags - } - } - - if utils.IsKnown(data.Enabled) && !data.Enabled.IsNull() && data.Enabled.ValueBool() != user.Enabled { - if data.Enabled.ValueBool() { - sdkDiags := elasticsearch.EnableUser(ctx, r.client, usernameId) - diags.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...) - } else { - sdkDiags := elasticsearch.DisableUser(ctx, r.client, usernameId) - diags.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...) - } - if diags.HasError() { - return diags - } - } - - data.Id = types.StringValue(id.String()) - diags.Append(state.Set(ctx, &data)...) - return diags -} diff --git a/internal/elasticsearch/security/system_user_test.go b/internal/elasticsearch/security/system_user/acc_test.go similarity index 98% rename from internal/elasticsearch/security/system_user_test.go rename to internal/elasticsearch/security/system_user/acc_test.go index fef75b269..336a09657 100644 --- a/internal/elasticsearch/security/system_user_test.go +++ b/internal/elasticsearch/security/system_user/acc_test.go @@ -1,4 +1,4 @@ -package security_test +package system_user_test import ( "regexp" @@ -53,7 +53,8 @@ resource "elasticstack_elasticsearch_security_system_user" "remote_monitoring_us username = "remote_monitoring_user" password = "new_password" } - ` +` + const testAccResourceSecuritySystemUserUpdate = ` provider "elasticstack" { elasticsearch {} diff --git a/internal/elasticsearch/security/system_user/create.go b/internal/elasticsearch/security/system_user/create.go new file mode 100644 index 000000000..5c40eb5d2 --- /dev/null +++ b/internal/elasticsearch/security/system_user/create.go @@ -0,0 +1,15 @@ +package system_user + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/resource" +) + +func (r *systemUserResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + diags := r.update(ctx, req.Plan, &resp.State) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} diff --git a/internal/elasticsearch/security/system_user/delete.go b/internal/elasticsearch/security/system_user/delete.go new file mode 100644 index 000000000..9d2bb9e37 --- /dev/null +++ b/internal/elasticsearch/security/system_user/delete.go @@ -0,0 +1,25 @@ +package system_user + +import ( + "context" + "fmt" + + "github.com/elastic/terraform-provider-elasticstack/internal/clients" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +func (r *systemUserResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data SystemUserData + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) // Keep this one + if resp.Diagnostics.HasError() { + return + } + + compId, diags := clients.CompositeIdFromStrFw(data.Id.ValueString()) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + tflog.Warn(ctx, fmt.Sprintf(`System user '%s' is not deletable, just removing from state`, compId.ResourceId)) +} diff --git a/internal/elasticsearch/security/system_user/models.go b/internal/elasticsearch/security/system_user/models.go new file mode 100644 index 000000000..8b51d99ea --- /dev/null +++ b/internal/elasticsearch/security/system_user/models.go @@ -0,0 +1,13 @@ +package system_user + +import ( + "github.com/hashicorp/terraform-plugin-framework/types" +) + +type SystemUserData struct { + Id types.String `tfsdk:"id"` + Username types.String `tfsdk:"username"` + Password types.String `tfsdk:"password"` + PasswordHash types.String `tfsdk:"password_hash"` + Enabled types.Bool `tfsdk:"enabled"` +} diff --git a/internal/elasticsearch/security/system_user/read.go b/internal/elasticsearch/security/system_user/read.go new file mode 100644 index 000000000..817e52df1 --- /dev/null +++ b/internal/elasticsearch/security/system_user/read.go @@ -0,0 +1,45 @@ +package system_user + +import ( + "context" + "fmt" + + "github.com/elastic/terraform-provider-elasticstack/internal/clients" + "github.com/elastic/terraform-provider-elasticstack/internal/clients/elasticsearch" + "github.com/elastic/terraform-provider-elasticstack/internal/utils" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +func (r *systemUserResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data SystemUserData + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) // Keep this one + if resp.Diagnostics.HasError() { + return + } + + compId, diags := clients.CompositeIdFromStrFw(data.Id.ValueString()) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + usernameId := compId.ResourceId + + user, sdkDiags := elasticsearch.GetUser(ctx, r.client, usernameId) + resp.Diagnostics.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...) // Keep this one + if resp.Diagnostics.HasError() { + return + } + + if user == nil || !user.IsSystemUser() { + tflog.Warn(ctx, fmt.Sprintf(`System user "%s" not found, removing from state`, compId.ResourceId)) + resp.State.RemoveResource(ctx) + return + } + + data.Username = types.StringValue(usernameId) + data.Enabled = types.BoolValue(user.Enabled) + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) // Keep this one +} diff --git a/internal/elasticsearch/security/system_user/resource.go b/internal/elasticsearch/security/system_user/resource.go new file mode 100644 index 000000000..63ef38e36 --- /dev/null +++ b/internal/elasticsearch/security/system_user/resource.go @@ -0,0 +1,26 @@ +package system_user + +import ( + "context" + + "github.com/elastic/terraform-provider-elasticstack/internal/clients" + "github.com/hashicorp/terraform-plugin-framework/resource" +) + +func NewSystemUserResource() resource.Resource { + return &systemUserResource{} +} + +type systemUserResource struct { + client *clients.ApiClient +} + +func (r *systemUserResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_elasticsearch_security_system_user" +} + +func (r *systemUserResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + client, diags := clients.ConvertProviderData(req.ProviderData) + resp.Diagnostics.Append(diags...) + r.client = client +} diff --git a/internal/elasticsearch/security/system_user/schema.go b/internal/elasticsearch/security/system_user/schema.go new file mode 100644 index 000000000..38c2073d3 --- /dev/null +++ b/internal/elasticsearch/security/system_user/schema.go @@ -0,0 +1,63 @@ +package system_user + +import ( + "context" + "regexp" + + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +func (r *systemUserResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = GetSchema() +} + +func GetSchema() schema.Schema { + return schema.Schema{ + MarkdownDescription: "Updates system user's password and enablement. See, https://www.elastic.co/guide/en/elasticsearch/reference/current/built-in-users.html", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "Internal identifier of the resource", + Computed: true, + }, + "username": schema.StringAttribute{ + MarkdownDescription: "An identifier for the system user (see https://www.elastic.co/guide/en/elasticsearch/reference/current/built-in-users.html).", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 1024), + stringvalidator.RegexMatches(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": schema.StringAttribute{ + MarkdownDescription: "The user's password. Passwords must be at least 6 characters long.", + Optional: true, + Sensitive: true, + Validators: []validator.String{ + stringvalidator.LengthBetween(6, 128), + }, + }, + "password_hash": schema.StringAttribute{ + MarkdownDescription: "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).", + Optional: true, + Sensitive: true, + Validators: []validator.String{ + stringvalidator.LengthBetween(6, 128), + }, + }, + "enabled": schema.BoolAttribute{ + MarkdownDescription: "Specifies whether the user is enabled. The default value is true.", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(true), + }, + }, + } +} diff --git a/internal/elasticsearch/security/system_user/update.go b/internal/elasticsearch/security/system_user/update.go new file mode 100644 index 000000000..e1470148f --- /dev/null +++ b/internal/elasticsearch/security/system_user/update.go @@ -0,0 +1,80 @@ +package system_user + +import ( + "context" + "fmt" + + "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-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +func (r *systemUserResource) update(ctx context.Context, plan tfsdk.Plan, state *tfsdk.State) diag.Diagnostics { + var data SystemUserData + var diags diag.Diagnostics + diags.Append(plan.Get(ctx, &data)...) // Keep this one + if diags.HasError() { + return diags + } + + usernameId := data.Username.ValueString() + id, sdkDiags := r.client.ID(ctx, usernameId) + diags.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...) // Keep this one + if diags.HasError() { + return diags + } + + user, sdkDiags := elasticsearch.GetUser(ctx, r.client, usernameId) + diags.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...) // Keep this one + if diags.HasError() { + return diags + } + if user == nil || !user.IsSystemUser() { + diags.AddError("", fmt.Sprintf(`System user "%s" not found`, usernameId)) + return diags + } + + var userPassword models.UserPassword + if utils.IsKnown(data.Password) && (user.Password == nil || data.Password.ValueString() != *user.Password) { + userPassword.Password = data.Password.ValueStringPointer() + } + if utils.IsKnown(data.PasswordHash) && (user.PasswordHash == nil || data.PasswordHash.ValueString() != *user.PasswordHash) { + userPassword.PasswordHash = data.PasswordHash.ValueStringPointer() + } + if userPassword.Password != nil || userPassword.PasswordHash != nil { + sdkDiags := elasticsearch.ChangeUserPassword(ctx, r.client, usernameId, &userPassword) + diags.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...) // Keep this one + if diags.HasError() { + return diags + } + } + + if utils.IsKnown(data.Enabled) && !data.Enabled.IsNull() && data.Enabled.ValueBool() != user.Enabled { + if data.Enabled.ValueBool() { + sdkDiags := elasticsearch.EnableUser(ctx, r.client, usernameId) + diags.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...) // Keep this one + } else { + sdkDiags := elasticsearch.DisableUser(ctx, r.client, usernameId) + diags.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...) // Keep this one + } + if diags.HasError() { + return diags + } + } + + data.Id = types.StringValue(id.String()) + diags.Append(state.Set(ctx, &data)...) // Keep this one + return diags +} + +func (r *systemUserResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + diags := r.update(ctx, req.Plan, &resp.State) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} diff --git a/provider/plugin_framework.go b/provider/plugin_framework.go index 49af3f009..07c1781c4 100644 --- a/provider/plugin_framework.go +++ b/provider/plugin_framework.go @@ -8,8 +8,8 @@ import ( "github.com/elastic/terraform-provider-elasticstack/internal/elasticsearch/index/data_stream_lifecycle" "github.com/elastic/terraform-provider-elasticstack/internal/elasticsearch/index/index" "github.com/elastic/terraform-provider-elasticstack/internal/elasticsearch/index/indices" - "github.com/elastic/terraform-provider-elasticstack/internal/elasticsearch/security" "github.com/elastic/terraform-provider-elasticstack/internal/elasticsearch/security/api_key" + "github.com/elastic/terraform-provider-elasticstack/internal/elasticsearch/security/system_user" "github.com/elastic/terraform-provider-elasticstack/internal/fleet/agent_policy" "github.com/elastic/terraform-provider-elasticstack/internal/fleet/enrollment_tokens" "github.com/elastic/terraform-provider-elasticstack/internal/fleet/integration" @@ -101,6 +101,6 @@ func (p *Provider) Resources(ctx context.Context) []func() resource.Resource { integration_policy.NewResource, output.NewResource, server_host.NewResource, - security.NewSystemUserResource, + system_user.NewSystemUserResource, } } From d86a3ab84912a18be5a52b5a0fad1545c9d0d7bc Mon Sep 17 00:00:00 2001 From: Toby Brain Date: Wed, 28 May 2025 06:35:30 +1000 Subject: [PATCH 03/10] Add missing conflict validators --- internal/elasticsearch/security/system_user/models.go | 8 ++++---- internal/elasticsearch/security/system_user/schema.go | 3 +++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/internal/elasticsearch/security/system_user/models.go b/internal/elasticsearch/security/system_user/models.go index 8b51d99ea..d5e335137 100644 --- a/internal/elasticsearch/security/system_user/models.go +++ b/internal/elasticsearch/security/system_user/models.go @@ -5,9 +5,9 @@ import ( ) type SystemUserData struct { - Id types.String `tfsdk:"id"` - Username types.String `tfsdk:"username"` - Password types.String `tfsdk:"password"` + Id types.String `tfsdk:"id"` + Username types.String `tfsdk:"username"` + Password types.String `tfsdk:"password"` PasswordHash types.String `tfsdk:"password_hash"` - Enabled types.Bool `tfsdk:"enabled"` + Enabled types.Bool `tfsdk:"enabled"` } diff --git a/internal/elasticsearch/security/system_user/schema.go b/internal/elasticsearch/security/system_user/schema.go index 38c2073d3..8f781c29d 100644 --- a/internal/elasticsearch/security/system_user/schema.go +++ b/internal/elasticsearch/security/system_user/schema.go @@ -5,6 +5,7 @@ import ( "regexp" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" @@ -42,6 +43,7 @@ func GetSchema() schema.Schema { Sensitive: true, Validators: []validator.String{ stringvalidator.LengthBetween(6, 128), + stringvalidator.ConflictsWith(path.MatchRoot("password_hash")), }, }, "password_hash": schema.StringAttribute{ @@ -50,6 +52,7 @@ func GetSchema() schema.Schema { Sensitive: true, Validators: []validator.String{ stringvalidator.LengthBetween(6, 128), + stringvalidator.ConflictsWith(path.MatchRoot("password")), }, }, "enabled": schema.BoolAttribute{ From 872bbbae0280c0ef20ac40e5d6a31945b586adb1 Mon Sep 17 00:00:00 2001 From: Toby Brain Date: Wed, 28 May 2025 06:44:25 +1000 Subject: [PATCH 04/10] Add missing elasticsearch connection block make docs-generate --- docs/resources/elasticsearch_security_system_user.md | 6 +++--- internal/elasticsearch/security/system_user/schema.go | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/resources/elasticsearch_security_system_user.md b/docs/resources/elasticsearch_security_system_user.md index 2d00da36d..27c9910bf 100644 --- a/docs/resources/elasticsearch_security_system_user.md +++ b/docs/resources/elasticsearch_security_system_user.md @@ -41,10 +41,10 @@ resource "elasticstack_elasticsearch_security_system_user" "kibana_system" { ### Optional -- `elasticsearch_connection` (Block List, Max: 1, Deprecated) Elasticsearch connection configuration block. This property will be removed in a future provider version. Configure the Elasticsearch connection via the provider configuration instead. (see [below for nested schema](#nestedblock--elasticsearch_connection)) +- `elasticsearch_connection` (Block List, Deprecated) Elasticsearch connection configuration block. (see [below for nested schema](#nestedblock--elasticsearch_connection)) - `enabled` (Boolean) Specifies whether the user is enabled. The default value is true. -- `password` (String, Sensitive) The user’s password. Passwords must be at least 6 characters long. -- `password_hash` (String, Sensitive) 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). +- `password` (String, Sensitive) The user's password. Passwords must be at least 6 characters long. +- `password_hash` (String, Sensitive) 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). ### Read-Only diff --git a/internal/elasticsearch/security/system_user/schema.go b/internal/elasticsearch/security/system_user/schema.go index 8f781c29d..6b529eceb 100644 --- a/internal/elasticsearch/security/system_user/schema.go +++ b/internal/elasticsearch/security/system_user/schema.go @@ -12,6 +12,8 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + providerschema "github.com/elastic/terraform-provider-elasticstack/internal/schema" ) func (r *systemUserResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { @@ -21,6 +23,9 @@ func (r *systemUserResource) Schema(_ context.Context, _ resource.SchemaRequest, func GetSchema() schema.Schema { return schema.Schema{ MarkdownDescription: "Updates system user's password and enablement. See, https://www.elastic.co/guide/en/elasticsearch/reference/current/built-in-users.html", + Blocks: map[string]schema.Block{ + "elasticsearch_connection": providerschema.GetEsFWConnectionBlock("elasticsearch_connection", false), + }, Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ MarkdownDescription: "Internal identifier of the resource", From 85a8353fb5455ab9c132a35e4cbd87d9848b281a Mon Sep 17 00:00:00 2001 From: Toby Brain Date: Wed, 28 May 2025 07:05:11 +1000 Subject: [PATCH 05/10] Use elasticsearch_connection when provided --- internal/elasticsearch/security/system_user/models.go | 11 ++++++----- internal/elasticsearch/security/system_user/read.go | 8 +++++++- internal/elasticsearch/security/system_user/update.go | 9 ++++++++- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/internal/elasticsearch/security/system_user/models.go b/internal/elasticsearch/security/system_user/models.go index d5e335137..d754a0149 100644 --- a/internal/elasticsearch/security/system_user/models.go +++ b/internal/elasticsearch/security/system_user/models.go @@ -5,9 +5,10 @@ import ( ) type SystemUserData struct { - Id types.String `tfsdk:"id"` - Username types.String `tfsdk:"username"` - Password types.String `tfsdk:"password"` - PasswordHash types.String `tfsdk:"password_hash"` - Enabled types.Bool `tfsdk:"enabled"` + Id types.String `tfsdk:"id"` + ElasticsearchConnection types.List `tfsdk:"elasticsearch_connection"` + Username types.String `tfsdk:"username"` + Password types.String `tfsdk:"password"` + PasswordHash types.String `tfsdk:"password_hash"` + Enabled types.Bool `tfsdk:"enabled"` } diff --git a/internal/elasticsearch/security/system_user/read.go b/internal/elasticsearch/security/system_user/read.go index 817e52df1..a7e9c3eee 100644 --- a/internal/elasticsearch/security/system_user/read.go +++ b/internal/elasticsearch/security/system_user/read.go @@ -26,7 +26,13 @@ func (r *systemUserResource) Read(ctx context.Context, req resource.ReadRequest, } usernameId := compId.ResourceId - user, sdkDiags := elasticsearch.GetUser(ctx, r.client, usernameId) + client, diags := clients.MaybeNewApiClientFromFrameworkResource(ctx, data.ElasticsearchConnection, r.client) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + user, sdkDiags := elasticsearch.GetUser(ctx, client, usernameId) resp.Diagnostics.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...) // Keep this one if resp.Diagnostics.HasError() { return diff --git a/internal/elasticsearch/security/system_user/update.go b/internal/elasticsearch/security/system_user/update.go index e1470148f..3c401dda3 100644 --- a/internal/elasticsearch/security/system_user/update.go +++ b/internal/elasticsearch/security/system_user/update.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "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" @@ -28,7 +29,13 @@ func (r *systemUserResource) update(ctx context.Context, plan tfsdk.Plan, state return diags } - user, sdkDiags := elasticsearch.GetUser(ctx, r.client, usernameId) + client, diags := clients.MaybeNewApiClientFromFrameworkResource(ctx, data.ElasticsearchConnection, r.client) + diags.Append(diags...) + if diags.HasError() { + return diags + } + + user, sdkDiags := elasticsearch.GetUser(ctx, client, usernameId) diags.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...) // Keep this one if diags.HasError() { return diags From 6e7c53902b8f0464d9d82843b3779e03e4e2781a Mon Sep 17 00:00:00 2001 From: Toby Brain Date: Wed, 28 May 2025 08:54:08 +1000 Subject: [PATCH 06/10] Don't keep this one comment though --- .../elasticsearch/security/system_user/delete.go | 2 +- .../elasticsearch/security/system_user/read.go | 6 +++--- .../elasticsearch/security/system_user/update.go | 14 +++++++------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/internal/elasticsearch/security/system_user/delete.go b/internal/elasticsearch/security/system_user/delete.go index 9d2bb9e37..998ccde55 100644 --- a/internal/elasticsearch/security/system_user/delete.go +++ b/internal/elasticsearch/security/system_user/delete.go @@ -11,7 +11,7 @@ import ( func (r *systemUserResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { var data SystemUserData - resp.Diagnostics.Append(req.State.Get(ctx, &data)...) // Keep this one + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) if resp.Diagnostics.HasError() { return } diff --git a/internal/elasticsearch/security/system_user/read.go b/internal/elasticsearch/security/system_user/read.go index a7e9c3eee..530b9f040 100644 --- a/internal/elasticsearch/security/system_user/read.go +++ b/internal/elasticsearch/security/system_user/read.go @@ -14,7 +14,7 @@ import ( func (r *systemUserResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { var data SystemUserData - resp.Diagnostics.Append(req.State.Get(ctx, &data)...) // Keep this one + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) if resp.Diagnostics.HasError() { return } @@ -33,7 +33,7 @@ func (r *systemUserResource) Read(ctx context.Context, req resource.ReadRequest, } user, sdkDiags := elasticsearch.GetUser(ctx, client, usernameId) - resp.Diagnostics.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...) // Keep this one + resp.Diagnostics.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...) if resp.Diagnostics.HasError() { return } @@ -47,5 +47,5 @@ func (r *systemUserResource) Read(ctx context.Context, req resource.ReadRequest, data.Username = types.StringValue(usernameId) data.Enabled = types.BoolValue(user.Enabled) - resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) // Keep this one + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } diff --git a/internal/elasticsearch/security/system_user/update.go b/internal/elasticsearch/security/system_user/update.go index 3c401dda3..84e8afd21 100644 --- a/internal/elasticsearch/security/system_user/update.go +++ b/internal/elasticsearch/security/system_user/update.go @@ -17,14 +17,14 @@ import ( func (r *systemUserResource) update(ctx context.Context, plan tfsdk.Plan, state *tfsdk.State) diag.Diagnostics { var data SystemUserData var diags diag.Diagnostics - diags.Append(plan.Get(ctx, &data)...) // Keep this one + diags.Append(plan.Get(ctx, &data)...) if diags.HasError() { return diags } usernameId := data.Username.ValueString() id, sdkDiags := r.client.ID(ctx, usernameId) - diags.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...) // Keep this one + diags.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...) if diags.HasError() { return diags } @@ -36,7 +36,7 @@ func (r *systemUserResource) update(ctx context.Context, plan tfsdk.Plan, state } user, sdkDiags := elasticsearch.GetUser(ctx, client, usernameId) - diags.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...) // Keep this one + diags.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...) if diags.HasError() { return diags } @@ -54,7 +54,7 @@ func (r *systemUserResource) update(ctx context.Context, plan tfsdk.Plan, state } if userPassword.Password != nil || userPassword.PasswordHash != nil { sdkDiags := elasticsearch.ChangeUserPassword(ctx, r.client, usernameId, &userPassword) - diags.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...) // Keep this one + diags.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...) if diags.HasError() { return diags } @@ -63,10 +63,10 @@ func (r *systemUserResource) update(ctx context.Context, plan tfsdk.Plan, state if utils.IsKnown(data.Enabled) && !data.Enabled.IsNull() && data.Enabled.ValueBool() != user.Enabled { if data.Enabled.ValueBool() { sdkDiags := elasticsearch.EnableUser(ctx, r.client, usernameId) - diags.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...) // Keep this one + diags.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...) } else { sdkDiags := elasticsearch.DisableUser(ctx, r.client, usernameId) - diags.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...) // Keep this one + diags.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...) } if diags.HasError() { return diags @@ -74,7 +74,7 @@ func (r *systemUserResource) update(ctx context.Context, plan tfsdk.Plan, state } data.Id = types.StringValue(id.String()) - diags.Append(state.Set(ctx, &data)...) // Keep this one + diags.Append(state.Set(ctx, &data)...) return diags } From 284a2e2ee2e864a643d4fe5400ba2563a6c438d0 Mon Sep 17 00:00:00 2001 From: Toby Brain Date: Wed, 4 Jun 2025 15:24:04 +1000 Subject: [PATCH 07/10] Add acctest to cover sdk->pf migration --- .../security/system_user/acc_test.go | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/internal/elasticsearch/security/system_user/acc_test.go b/internal/elasticsearch/security/system_user/acc_test.go index 336a09657..0bfdcecd9 100644 --- a/internal/elasticsearch/security/system_user/acc_test.go +++ b/internal/elasticsearch/security/system_user/acc_test.go @@ -44,6 +44,37 @@ func TestAccResourceSecuritySystemUserNotFound(t *testing.T) { }) } +func TestAccResourceSecuritySystemUserFromSDK(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + Steps: []resource.TestStep{ + { + // Create the system user with the last provider version where the system user resource was built on the SDK + ExternalProviders: map[string]resource.ExternalProvider{ + "elasticstack": { + Source: "elastic/elasticstack", + VersionConstraint: "0.11.15", + }, + }, + ProtoV6ProviderFactories: acctest.Providers, + Config: testAccResourceSecuritySystemUserCreate, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("elasticstack_elasticsearch_security_system_user.remote_monitoring_user", "username", "remote_monitoring_user"), + resource.TestCheckResourceAttr("elasticstack_elasticsearch_security_system_user.remote_monitoring_user", "enabled", "true"), + ), + }, + { + ProtoV6ProviderFactories: acctest.Providers, + Config: testAccResourceSecuritySystemUserCreate, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("elasticstack_elasticsearch_security_system_user.remote_monitoring_user", "username", "remote_monitoring_user"), + resource.TestCheckResourceAttr("elasticstack_elasticsearch_security_system_user.remote_monitoring_user", "enabled", "true"), + ), + }, + }, + }) +} + const testAccResourceSecuritySystemUserCreate = ` provider "elasticstack" { elasticsearch {} From 9d4eaa7d2c10e46a79351a09f10860fb3c9be0fe Mon Sep 17 00:00:00 2001 From: Toby Brain Date: Wed, 4 Jun 2025 15:29:07 +1000 Subject: [PATCH 08/10] Update system user client funcs to use fw diags --- internal/clients/elasticsearch/security.go | 60 ++++++++++++++----- .../security/system_user/update.go | 10 ++-- internal/utils/diag.go | 15 +++++ 3 files changed, 63 insertions(+), 22 deletions(-) diff --git a/internal/clients/elasticsearch/security.go b/internal/clients/elasticsearch/security.go index 118005492..6ce83aa09 100644 --- a/internal/clients/elasticsearch/security.go +++ b/internal/clients/elasticsearch/security.go @@ -89,49 +89,73 @@ func DeleteUser(ctx context.Context, apiClient *clients.ApiClient, username stri return diags } -func EnableUser(ctx context.Context, apiClient *clients.ApiClient, username string) diag.Diagnostics { - var diags diag.Diagnostics +func EnableUser(ctx context.Context, apiClient *clients.ApiClient, username string) fwdiag.Diagnostics { + var diags fwdiag.Diagnostics esClient, err := apiClient.GetESClient() if err != nil { - return diag.FromErr(err) + diags.AddError( + "Unable to get Elasticsearch client", + err.Error(), + ) + return diags } res, err := esClient.Security.EnableUser(username, esClient.Security.EnableUser.WithContext(ctx)) if err != nil { - return diag.FromErr(err) + diags.AddError( + "Unable to enable system user", + err.Error(), + ) + return diags } defer res.Body.Close() - if diags := utils.CheckError(res, "Unable to enable system user"); diags.HasError() { + if diags := utils.CheckErrorFromFW(res, "Unable to enable system user"); diags.HasError() { return diags } return diags } -func DisableUser(ctx context.Context, apiClient *clients.ApiClient, username string) diag.Diagnostics { - var diags diag.Diagnostics +func DisableUser(ctx context.Context, apiClient *clients.ApiClient, username string) fwdiag.Diagnostics { + var diags fwdiag.Diagnostics esClient, err := apiClient.GetESClient() if err != nil { - return diag.FromErr(err) + diags.AddError( + "Unable to get Elasticsearch client", + err.Error(), + ) + return diags } res, err := esClient.Security.DisableUser(username, esClient.Security.DisableUser.WithContext(ctx)) if err != nil { - return diag.FromErr(err) + diags.AddError( + "Unable to disable system user", + err.Error(), + ) + return diags } defer res.Body.Close() - if diags := utils.CheckError(res, "Unable to disable system user"); diags.HasError() { + if diags := utils.CheckErrorFromFW(res, "Unable to disable system user"); diags.HasError() { return diags } return diags } -func ChangeUserPassword(ctx context.Context, apiClient *clients.ApiClient, username string, userPassword *models.UserPassword) diag.Diagnostics { - var diags diag.Diagnostics +func ChangeUserPassword(ctx context.Context, apiClient *clients.ApiClient, username string, userPassword *models.UserPassword) fwdiag.Diagnostics { + var diags fwdiag.Diagnostics userPasswordBytes, err := json.Marshal(userPassword) if err != nil { - return diag.FromErr(err) + diags.AddError( + "Unable to marshal user password", + err.Error(), + ) + return diags } esClient, err := apiClient.GetESClient() if err != nil { - return diag.FromErr(err) + diags.AddError( + "Unable to get Elasticsearch client", + err.Error(), + ) + return diags } res, err := esClient.Security.ChangePassword( bytes.NewReader(userPasswordBytes), @@ -139,10 +163,14 @@ func ChangeUserPassword(ctx context.Context, apiClient *clients.ApiClient, usern esClient.Security.ChangePassword.WithContext(ctx), ) if err != nil { - return diag.FromErr(err) + diags.AddError( + "Unable to change user password", + err.Error(), + ) + return diags } defer res.Body.Close() - if diags := utils.CheckError(res, "Unable to change user's password"); diags.HasError() { + if diags := utils.CheckErrorFromFW(res, "Unable to change user's password"); diags.HasError() { return diags } return diags diff --git a/internal/elasticsearch/security/system_user/update.go b/internal/elasticsearch/security/system_user/update.go index 84e8afd21..6c7e3a61c 100644 --- a/internal/elasticsearch/security/system_user/update.go +++ b/internal/elasticsearch/security/system_user/update.go @@ -46,6 +46,7 @@ func (r *systemUserResource) update(ctx context.Context, plan tfsdk.Plan, state } var userPassword models.UserPassword + // TB TODO Fix up this logic. It should only set the password when it's set in config if utils.IsKnown(data.Password) && (user.Password == nil || data.Password.ValueString() != *user.Password) { userPassword.Password = data.Password.ValueStringPointer() } @@ -53,8 +54,7 @@ func (r *systemUserResource) update(ctx context.Context, plan tfsdk.Plan, state userPassword.PasswordHash = data.PasswordHash.ValueStringPointer() } if userPassword.Password != nil || userPassword.PasswordHash != nil { - sdkDiags := elasticsearch.ChangeUserPassword(ctx, r.client, usernameId, &userPassword) - diags.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...) + diags.Append(elasticsearch.ChangeUserPassword(ctx, r.client, usernameId, &userPassword)...) if diags.HasError() { return diags } @@ -62,11 +62,9 @@ func (r *systemUserResource) update(ctx context.Context, plan tfsdk.Plan, state if utils.IsKnown(data.Enabled) && !data.Enabled.IsNull() && data.Enabled.ValueBool() != user.Enabled { if data.Enabled.ValueBool() { - sdkDiags := elasticsearch.EnableUser(ctx, r.client, usernameId) - diags.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...) + diags.Append(elasticsearch.EnableUser(ctx, r.client, usernameId)...) } else { - sdkDiags := elasticsearch.DisableUser(ctx, r.client, usernameId) - diags.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...) + diags.Append(elasticsearch.DisableUser(ctx, r.client, usernameId)...) } if diags.HasError() { return diags diff --git a/internal/utils/diag.go b/internal/utils/diag.go index 4a2b158b5..7e7011911 100644 --- a/internal/utils/diag.go +++ b/internal/utils/diag.go @@ -42,6 +42,21 @@ func CheckError(res *esapi.Response, errMsg string) sdkdiag.Diagnostics { return diags } +func CheckErrorFromFW(res *esapi.Response, errMsg string) fwdiag.Diagnostics { + var diags fwdiag.Diagnostics + + if res.IsError() { + body, err := io.ReadAll(res.Body) + if err != nil { + diags.AddError(errMsg, err.Error()) + return diags + } + diags.AddError(errMsg, fmt.Sprintf("Failed with: %s", body)) + return diags + } + return diags +} + func CheckHttpError(res *http.Response, errMsg string) sdkdiag.Diagnostics { var diags sdkdiag.Diagnostics From 7423341e2143f3b7678df7d957a7ffedc1d7315c Mon Sep 17 00:00:00 2001 From: Toby Brain Date: Sun, 6 Jul 2025 08:00:30 +1000 Subject: [PATCH 09/10] Address PR feedback --- go.mod | 16 +++---- go.sum | 32 ++++++------- .../checks/resource_list.go} | 2 +- internal/acctest/checks/user_auth.go | 42 +++++++++++++++++ .../security/role_data_source_test.go | 14 +++--- .../security/role_mapping_data_source_test.go | 4 +- .../security/role_mapping_test.go | 6 +-- .../security/system_user/acc_test.go | 9 ++-- .../security/system_user/update.go | 1 - internal/elasticsearch/security/user_test.go | 40 ++-------------- internal/kibana/role_data_source_test.go | 22 ++++----- internal/kibana/role_test.go | 46 +++++++++---------- 12 files changed, 121 insertions(+), 113 deletions(-) rename internal/{utils/testutils.go => acctest/checks/resource_list.go} (96%) create mode 100644 internal/acctest/checks/user_auth.go diff --git a/go.mod b/go.mod index 00f5d3ff1..9bfb677f8 100644 --- a/go.mod +++ b/go.mod @@ -31,7 +31,7 @@ require ( github.com/agext/levenshtein v1.2.3 // indirect github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect - github.com/cloudflare/circl v1.6.0 // indirect + github.com/cloudflare/circl v1.6.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fatih/color v1.17.0 // indirect github.com/go-logr/logr v1.4.2 // indirect @@ -68,18 +68,18 @@ require ( github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect - github.com/zclconf/go-cty v1.16.2 // indirect + github.com/zclconf/go-cty v1.16.3 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/otel v1.34.0 // indirect go.opentelemetry.io/otel/metric v1.34.0 // indirect go.opentelemetry.io/otel/trace v1.34.0 // indirect - golang.org/x/crypto v0.38.0 // indirect - golang.org/x/mod v0.24.0 // indirect - golang.org/x/net v0.39.0 // indirect - golang.org/x/sync v0.14.0 // indirect + golang.org/x/crypto v0.39.0 // indirect + golang.org/x/mod v0.25.0 // indirect + golang.org/x/net v0.40.0 // indirect + golang.org/x/sync v0.15.0 // indirect golang.org/x/sys v0.33.0 // indirect - golang.org/x/text v0.25.0 // indirect - golang.org/x/tools v0.23.0 // indirect + golang.org/x/text v0.26.0 // indirect + golang.org/x/tools v0.33.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect google.golang.org/grpc v1.72.1 // indirect diff --git a/go.sum b/go.sum index 1a5f0781e..70f586fc1 100644 --- a/go.sum +++ b/go.sum @@ -15,8 +15,8 @@ github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmms github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= -github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk= -github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= +github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -186,8 +186,8 @@ github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7V github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/zclconf/go-cty v1.16.2 h1:LAJSwc3v81IRBZyUVQDUdZ7hs3SYs9jv0eZJDWHD/70= -github.com/zclconf/go-cty v1.16.2/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +github.com/zclconf/go-cty v1.16.3 h1:osr++gw2T61A8KVYHoQiFbFd1Lh3JOCXc/jFLJXKTxk= +github.com/zclconf/go-cty v1.16.3/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= @@ -206,22 +206,22 @@ go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= -golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= +golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= +golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= -golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= +golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= -golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= -golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -245,15 +245,15 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= -golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= -golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= +golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= +golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= diff --git a/internal/utils/testutils.go b/internal/acctest/checks/resource_list.go similarity index 96% rename from internal/utils/testutils.go rename to internal/acctest/checks/resource_list.go index c3f2ef1cc..c488c1677 100644 --- a/internal/utils/testutils.go +++ b/internal/acctest/checks/resource_list.go @@ -1,4 +1,4 @@ -package utils +package checks import ( "fmt" diff --git a/internal/acctest/checks/user_auth.go b/internal/acctest/checks/user_auth.go new file mode 100644 index 000000000..5200ebf0a --- /dev/null +++ b/internal/acctest/checks/user_auth.go @@ -0,0 +1,42 @@ +package checks + +import ( + "encoding/base64" + "fmt" + "io" + + "github.com/elastic/terraform-provider-elasticstack/internal/clients" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func CheckUserCanAuthenticate(username string, password string) func(*terraform.State) error { + return func(s *terraform.State) error { + client, err := clients.NewAcceptanceTestingClient() + if err != nil { + return err + } + + esClient, err := client.GetESClient() + if err != nil { + return err + } + + credentials := fmt.Sprintf("%s:%s", username, password) + authHeader := fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(credentials))) + + req := esClient.Security.Authenticate.WithHeader(map[string]string{"Authorization": authHeader}) + resp, err := esClient.Security.Authenticate(req) + if err != nil { + return err + } + + defer resp.Body.Close() + + if resp.IsError() { + body, err := io.ReadAll(resp.Body) + + return fmt.Errorf("failed to authenticate as test user [%s] %s %s", username, body, err) + } + return nil + } +} diff --git a/internal/elasticsearch/security/role_data_source_test.go b/internal/elasticsearch/security/role_data_source_test.go index 1fbebe705..aeccdf30e 100644 --- a/internal/elasticsearch/security/role_data_source_test.go +++ b/internal/elasticsearch/security/role_data_source_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/elastic/terraform-provider-elasticstack/internal/acctest" - "github.com/elastic/terraform-provider-elasticstack/internal/utils" + "github.com/elastic/terraform-provider-elasticstack/internal/acctest/checks" "github.com/elastic/terraform-provider-elasticstack/internal/versionutils" "github.com/hashicorp/go-version" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -21,11 +21,11 @@ func TestAccDataSourceSecurityRole(t *testing.T) { Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_security_role.test", "name", "data_source_test"), resource.TestCheckTypeSetElemAttr("data.elasticstack_elasticsearch_security_role.test", "cluster.*", "all"), - utils.TestCheckResourceListAttr("data.elasticstack_elasticsearch_security_role.test", "indices.0.names", []string{"index1", "index2"}), + checks.TestCheckResourceListAttr("data.elasticstack_elasticsearch_security_role.test", "indices.0.names", []string{"index1", "index2"}), resource.TestCheckTypeSetElemAttr("data.elasticstack_elasticsearch_security_role.test", "indices.0.privileges.*", "all"), resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_security_role.test", "indices.0.allow_restricted_indices", "true"), resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_security_role.test", "applications.0.application", "myapp"), - utils.TestCheckResourceListAttr("data.elasticstack_elasticsearch_security_role.test", "applications.0.privileges", []string{"admin", "read"}), + checks.TestCheckResourceListAttr("data.elasticstack_elasticsearch_security_role.test", "applications.0.privileges", []string{"admin", "read"}), resource.TestCheckTypeSetElemAttr("data.elasticstack_elasticsearch_security_role.test", "applications.0.resources.*", "*"), resource.TestCheckTypeSetElemAttr("data.elasticstack_elasticsearch_security_role.test", "run_as.*", "other_user"), resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_security_role.test", "metadata", `{"version":1}`), @@ -37,11 +37,11 @@ func TestAccDataSourceSecurityRole(t *testing.T) { Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_security_role.test", "name", "data_source_test"), resource.TestCheckTypeSetElemAttr("data.elasticstack_elasticsearch_security_role.test", "cluster.*", "all"), - utils.TestCheckResourceListAttr("data.elasticstack_elasticsearch_security_role.test", "indices.0.names", []string{"index1", "index2"}), + checks.TestCheckResourceListAttr("data.elasticstack_elasticsearch_security_role.test", "indices.0.names", []string{"index1", "index2"}), resource.TestCheckTypeSetElemAttr("data.elasticstack_elasticsearch_security_role.test", "indices.0.privileges.*", "all"), resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_security_role.test", "indices.0.allow_restricted_indices", "true"), resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_security_role.test", "applications.0.application", "myapp"), - utils.TestCheckResourceListAttr("data.elasticstack_elasticsearch_security_role.test", "applications.0.privileges", []string{"admin", "read"}), + checks.TestCheckResourceListAttr("data.elasticstack_elasticsearch_security_role.test", "applications.0.privileges", []string{"admin", "read"}), resource.TestCheckTypeSetElemAttr("data.elasticstack_elasticsearch_security_role.test", "applications.0.resources.*", "*"), resource.TestCheckTypeSetElemAttr("data.elasticstack_elasticsearch_security_role.test", "run_as.*", "other_user"), resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_security_role.test", "metadata", `{"version":1}`), @@ -55,11 +55,11 @@ func TestAccDataSourceSecurityRole(t *testing.T) { Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_security_role.test", "name", "data_source_test"), resource.TestCheckTypeSetElemAttr("data.elasticstack_elasticsearch_security_role.test", "cluster.*", "all"), - utils.TestCheckResourceListAttr("data.elasticstack_elasticsearch_security_role.test", "indices.0.names", []string{"index1", "index2"}), + checks.TestCheckResourceListAttr("data.elasticstack_elasticsearch_security_role.test", "indices.0.names", []string{"index1", "index2"}), resource.TestCheckTypeSetElemAttr("data.elasticstack_elasticsearch_security_role.test", "indices.0.privileges.*", "all"), resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_security_role.test", "indices.0.allow_restricted_indices", "true"), resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_security_role.test", "applications.0.application", "myapp"), - utils.TestCheckResourceListAttr("data.elasticstack_elasticsearch_security_role.test", "applications.0.privileges", []string{"admin", "read"}), + checks.TestCheckResourceListAttr("data.elasticstack_elasticsearch_security_role.test", "applications.0.privileges", []string{"admin", "read"}), resource.TestCheckTypeSetElemAttr("data.elasticstack_elasticsearch_security_role.test", "applications.0.resources.*", "*"), resource.TestCheckTypeSetElemAttr("data.elasticstack_elasticsearch_security_role.test", "run_as.*", "other_user"), resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_security_role.test", "metadata", `{"version":1}`), diff --git a/internal/elasticsearch/security/role_mapping_data_source_test.go b/internal/elasticsearch/security/role_mapping_data_source_test.go index 821df8146..154aec605 100644 --- a/internal/elasticsearch/security/role_mapping_data_source_test.go +++ b/internal/elasticsearch/security/role_mapping_data_source_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/elastic/terraform-provider-elasticstack/internal/acctest" - "github.com/elastic/terraform-provider-elasticstack/internal/utils" + "github.com/elastic/terraform-provider-elasticstack/internal/acctest/checks" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) @@ -18,7 +18,7 @@ func TestAccDataSourceSecurityRoleMapping(t *testing.T) { Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_security_role_mapping.test", "name", "data_source_test"), resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_security_role_mapping.test", "enabled", "true"), - utils.TestCheckResourceListAttr("data.elasticstack_elasticsearch_security_role_mapping.test", "roles", []string{"admin"}), + checks.TestCheckResourceListAttr("data.elasticstack_elasticsearch_security_role_mapping.test", "roles", []string{"admin"}), resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_security_role_mapping.test", "rules", `{"any":[{"field":{"username":"esadmin"}},{"field":{"groups":"cn=admins,dc=example,dc=com"}}]}`), resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_security_role_mapping.test", "metadata", `{"version":1}`), ), diff --git a/internal/elasticsearch/security/role_mapping_test.go b/internal/elasticsearch/security/role_mapping_test.go index c88b0df7c..dcde6ebff 100644 --- a/internal/elasticsearch/security/role_mapping_test.go +++ b/internal/elasticsearch/security/role_mapping_test.go @@ -6,8 +6,8 @@ import ( "testing" "github.com/elastic/terraform-provider-elasticstack/internal/acctest" + "github.com/elastic/terraform-provider-elasticstack/internal/acctest/checks" "github.com/elastic/terraform-provider-elasticstack/internal/clients" - "github.com/elastic/terraform-provider-elasticstack/internal/utils" sdkacctest "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" @@ -25,7 +25,7 @@ func TestResourceRoleMapping(t *testing.T) { Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("elasticstack_elasticsearch_security_role_mapping.test", "name", roleMappingName), resource.TestCheckResourceAttr("elasticstack_elasticsearch_security_role_mapping.test", "enabled", "true"), - utils.TestCheckResourceListAttr("elasticstack_elasticsearch_security_role_mapping.test", "roles", []string{"admin"}), + checks.TestCheckResourceListAttr("elasticstack_elasticsearch_security_role_mapping.test", "roles", []string{"admin"}), resource.TestCheckResourceAttr("elasticstack_elasticsearch_security_role_mapping.test", "rules", `{"any":[{"field":{"username":"esadmin"}},{"field":{"groups":"cn=admins,dc=example,dc=com"}}]}`), resource.TestCheckResourceAttr("elasticstack_elasticsearch_security_role_mapping.test", "metadata", `{"version":1}`), ), @@ -35,7 +35,7 @@ func TestResourceRoleMapping(t *testing.T) { Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("elasticstack_elasticsearch_security_role_mapping.test", "name", roleMappingName), resource.TestCheckResourceAttr("elasticstack_elasticsearch_security_role_mapping.test", "enabled", "false"), - utils.TestCheckResourceListAttr("elasticstack_elasticsearch_security_role_mapping.test", "roles", []string{"admin", "user"}), + checks.TestCheckResourceListAttr("elasticstack_elasticsearch_security_role_mapping.test", "roles", []string{"admin", "user"}), resource.TestCheckResourceAttr("elasticstack_elasticsearch_security_role_mapping.test", "rules", `{"any":[{"field":{"username":"esadmin"}},{"field":{"groups":"cn=admins,dc=example,dc=com"}}]}`), resource.TestCheckResourceAttr("elasticstack_elasticsearch_security_role_mapping.test", "metadata", `{}`), ), diff --git a/internal/elasticsearch/security/system_user/acc_test.go b/internal/elasticsearch/security/system_user/acc_test.go index 0bfdcecd9..60c33b09e 100644 --- a/internal/elasticsearch/security/system_user/acc_test.go +++ b/internal/elasticsearch/security/system_user/acc_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/elastic/terraform-provider-elasticstack/internal/acctest" + "github.com/elastic/terraform-provider-elasticstack/internal/acctest/checks" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) @@ -24,7 +25,8 @@ func TestAccResourceSecuritySystemUser(t *testing.T) { Config: testAccResourceSecuritySystemUserUpdate, Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("elasticstack_elasticsearch_security_system_user.remote_monitoring_user", "username", "remote_monitoring_user"), - resource.TestCheckResourceAttr("elasticstack_elasticsearch_security_system_user.remote_monitoring_user", "enabled", "false"), + resource.TestCheckResourceAttr("elasticstack_elasticsearch_security_system_user.remote_monitoring_user", "enabled", "true"), + checks.CheckUserCanAuthenticate("remote_monitoring_user", "new_password"), ), }, }, @@ -56,8 +58,7 @@ func TestAccResourceSecuritySystemUserFromSDK(t *testing.T) { VersionConstraint: "0.11.15", }, }, - ProtoV6ProviderFactories: acctest.Providers, - Config: testAccResourceSecuritySystemUserCreate, + Config: testAccResourceSecuritySystemUserCreate, Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("elasticstack_elasticsearch_security_system_user.remote_monitoring_user", "username", "remote_monitoring_user"), resource.TestCheckResourceAttr("elasticstack_elasticsearch_security_system_user.remote_monitoring_user", "enabled", "true"), @@ -82,7 +83,6 @@ provider "elasticstack" { resource "elasticstack_elasticsearch_security_system_user" "remote_monitoring_user" { username = "remote_monitoring_user" - password = "new_password" } ` @@ -94,7 +94,6 @@ provider "elasticstack" { resource "elasticstack_elasticsearch_security_system_user" "remote_monitoring_user" { username = "remote_monitoring_user" password = "new_password" - enabled = false } ` const testAccResourceSecuritySystemUserNotFound = ` diff --git a/internal/elasticsearch/security/system_user/update.go b/internal/elasticsearch/security/system_user/update.go index 6c7e3a61c..1537f2f87 100644 --- a/internal/elasticsearch/security/system_user/update.go +++ b/internal/elasticsearch/security/system_user/update.go @@ -46,7 +46,6 @@ func (r *systemUserResource) update(ctx context.Context, plan tfsdk.Plan, state } var userPassword models.UserPassword - // TB TODO Fix up this logic. It should only set the password when it's set in config if utils.IsKnown(data.Password) && (user.Password == nil || data.Password.ValueString() != *user.Password) { userPassword.Password = data.Password.ValueStringPointer() } diff --git a/internal/elasticsearch/security/user_test.go b/internal/elasticsearch/security/user_test.go index 531b75a3b..9311d3ca1 100644 --- a/internal/elasticsearch/security/user_test.go +++ b/internal/elasticsearch/security/user_test.go @@ -2,13 +2,13 @@ package security_test import ( "context" - "encoding/base64" "fmt" "io" "strings" "testing" "github.com/elastic/terraform-provider-elasticstack/internal/acctest" + "github.com/elastic/terraform-provider-elasticstack/internal/acctest/checks" "github.com/elastic/terraform-provider-elasticstack/internal/clients" sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -103,7 +103,7 @@ func TestAccImportedUserDoesNotResetPassword(t *testing.T) { return nil }, - Check: checkUserCanAuthenticate(username, initialPassword), + Check: checks.CheckUserCanAuthenticate(username, initialPassword), }, { Config: testAccResourceSecurityUserUpdateNoPassword(username), @@ -112,7 +112,7 @@ func TestAccImportedUserDoesNotResetPassword(t *testing.T) { resource.TestCheckTypeSetElemAttr("elasticstack_elasticsearch_security_user.test", "roles.*", "kibana_admin"), resource.TestCheckNoResourceAttr("elasticstack_elasticsearch_security_user.test", "password"), resource.TestCheckResourceAttr("elasticstack_elasticsearch_security_user.test", "full_name", "Test User"), - checkUserCanAuthenticate(username, initialPassword), + checks.CheckUserCanAuthenticate(username, initialPassword), ), }, { @@ -153,45 +153,13 @@ func TestAccImportedUserDoesNotResetPassword(t *testing.T) { resource.TestCheckResourceAttr("elasticstack_elasticsearch_security_user.test", "email", "test@example.com"), resource.TestCheckResourceAttrSet("elasticstack_elasticsearch_security_user.test", "password"), resource.TestCheckTypeSetElemAttr("elasticstack_elasticsearch_security_user.test", "roles.*", "kibana_user"), - checkUserCanAuthenticate(username, userUpdatedPassword), + checks.CheckUserCanAuthenticate(username, userUpdatedPassword), ), }, }, }) } -func checkUserCanAuthenticate(username string, password string) func(*terraform.State) error { - return func(s *terraform.State) error { - client, err := clients.NewAcceptanceTestingClient() - if err != nil { - return err - } - - esClient, err := client.GetESClient() - if err != nil { - return err - } - - credentials := fmt.Sprintf("%s:%s", username, password) - authHeader := fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(credentials))) - - req := esClient.Security.Authenticate.WithHeader(map[string]string{"Authorization": authHeader}) - resp, err := esClient.Security.Authenticate(req) - if err != nil { - return err - } - - defer resp.Body.Close() - - if resp.IsError() { - body, err := io.ReadAll(resp.Body) - - return fmt.Errorf("failed to authenticate as test user [%s] %s %s", username, body, err) - } - return nil - } -} - func testAccResourceSecurityUserCreate(username string) string { return fmt.Sprintf(` provider "elasticstack" { diff --git a/internal/kibana/role_data_source_test.go b/internal/kibana/role_data_source_test.go index 4b3339858..d54414186 100644 --- a/internal/kibana/role_data_source_test.go +++ b/internal/kibana/role_data_source_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/elastic/terraform-provider-elasticstack/internal/acctest" - "github.com/elastic/terraform-provider-elasticstack/internal/utils" + "github.com/elastic/terraform-provider-elasticstack/internal/acctest/checks" "github.com/elastic/terraform-provider-elasticstack/internal/versionutils" "github.com/hashicorp/go-version" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -22,9 +22,9 @@ func TestAccDataSourceKibanaSecurityRole(t *testing.T) { resource.TestCheckResourceAttr("data.elasticstack_kibana_security_role.test", "name", "data_source_test"), resource.TestCheckNoResourceAttr("data.elasticstack_kibana_security_role.test", "kibana.0.feature.#"), resource.TestCheckNoResourceAttr("data.elasticstack_kibana_security_role.test", "elasticsearch.0.indices.0.field_security.#"), - utils.TestCheckResourceListAttr("data.elasticstack_kibana_security_role.test", "elasticsearch.0.run_as", []string{"elastic", "kibana"}), - utils.TestCheckResourceListAttr("data.elasticstack_kibana_security_role.test", "kibana.0.base", []string{"all"}), - utils.TestCheckResourceListAttr("data.elasticstack_kibana_security_role.test", "kibana.0.spaces", []string{"default"}), + checks.TestCheckResourceListAttr("data.elasticstack_kibana_security_role.test", "elasticsearch.0.run_as", []string{"elastic", "kibana"}), + checks.TestCheckResourceListAttr("data.elasticstack_kibana_security_role.test", "kibana.0.base", []string{"all"}), + checks.TestCheckResourceListAttr("data.elasticstack_kibana_security_role.test", "kibana.0.spaces", []string{"default"}), ), }, { @@ -34,13 +34,13 @@ func TestAccDataSourceKibanaSecurityRole(t *testing.T) { resource.TestCheckResourceAttr("data.elasticstack_kibana_security_role.test", "name", "data_source_test2"), resource.TestCheckNoResourceAttr("data.elasticstack_kibana_security_role.test", "kibana.0.feature.#"), resource.TestCheckNoResourceAttr("data.elasticstack_kibana_security_role.test", "elasticsearch.0.indices.0.field_security.#"), - utils.TestCheckResourceListAttr("data.elasticstack_kibana_security_role.test", "elasticsearch.0.run_as", []string{"elastic", "kibana"}), - utils.TestCheckResourceListAttr("data.elasticstack_kibana_security_role.test", "kibana.0.base", []string{"all"}), - utils.TestCheckResourceListAttr("data.elasticstack_kibana_security_role.test", "kibana.0.spaces", []string{"default"}), - utils.TestCheckResourceListAttr("data.elasticstack_kibana_security_role.test", "elasticsearch.0.remote_indices.0.clusters", []string{"test-cluster"}), - utils.TestCheckResourceListAttr("data.elasticstack_kibana_security_role.test", "elasticsearch.0.remote_indices.0.field_security.0.grant", []string{"sample"}), - utils.TestCheckResourceListAttr("data.elasticstack_kibana_security_role.test", "elasticsearch.0.remote_indices.0.names", []string{"sample"}), - utils.TestCheckResourceListAttr("data.elasticstack_kibana_security_role.test", "elasticsearch.0.remote_indices.0.privileges", []string{"create", "read", "write"}), + checks.TestCheckResourceListAttr("data.elasticstack_kibana_security_role.test", "elasticsearch.0.run_as", []string{"elastic", "kibana"}), + checks.TestCheckResourceListAttr("data.elasticstack_kibana_security_role.test", "kibana.0.base", []string{"all"}), + checks.TestCheckResourceListAttr("data.elasticstack_kibana_security_role.test", "kibana.0.spaces", []string{"default"}), + checks.TestCheckResourceListAttr("data.elasticstack_kibana_security_role.test", "elasticsearch.0.remote_indices.0.clusters", []string{"test-cluster"}), + checks.TestCheckResourceListAttr("data.elasticstack_kibana_security_role.test", "elasticsearch.0.remote_indices.0.field_security.0.grant", []string{"sample"}), + checks.TestCheckResourceListAttr("data.elasticstack_kibana_security_role.test", "elasticsearch.0.remote_indices.0.names", []string{"sample"}), + checks.TestCheckResourceListAttr("data.elasticstack_kibana_security_role.test", "elasticsearch.0.remote_indices.0.privileges", []string{"create", "read", "write"}), ), }, }, diff --git a/internal/kibana/role_test.go b/internal/kibana/role_test.go index 8c05983f6..70669df32 100644 --- a/internal/kibana/role_test.go +++ b/internal/kibana/role_test.go @@ -5,8 +5,8 @@ import ( "testing" "github.com/elastic/terraform-provider-elasticstack/internal/acctest" + "github.com/elastic/terraform-provider-elasticstack/internal/acctest/checks" "github.com/elastic/terraform-provider-elasticstack/internal/clients" - "github.com/elastic/terraform-provider-elasticstack/internal/utils" "github.com/elastic/terraform-provider-elasticstack/internal/versionutils" "github.com/hashicorp/go-version" sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" @@ -31,10 +31,10 @@ func TestAccResourceKibanaSecurityRole(t *testing.T) { resource.TestCheckResourceAttr("elasticstack_kibana_security_role.test", "name", roleName), resource.TestCheckNoResourceAttr("elasticstack_kibana_security_role.test", "kibana.0.base.#"), resource.TestCheckNoResourceAttr("elasticstack_kibana_security_role.test", "elasticsearch.0.run_as.#"), - utils.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "elasticsearch.0.indices.0.names", []string{"sample"}), - utils.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "elasticsearch.0.indices.0.field_security.0.grant", []string{"sample"}), - utils.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "kibana.0.feature.2.privileges", []string{"minimal_read", "store_search_session", "url_create"}), - utils.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "kibana.0.spaces", []string{"default"}), + checks.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "elasticsearch.0.indices.0.names", []string{"sample"}), + checks.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "elasticsearch.0.indices.0.field_security.0.grant", []string{"sample"}), + checks.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "kibana.0.feature.2.privileges", []string{"minimal_read", "store_search_session", "url_create"}), + checks.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "kibana.0.spaces", []string{"default"}), ), }, { @@ -43,9 +43,9 @@ func TestAccResourceKibanaSecurityRole(t *testing.T) { resource.TestCheckResourceAttr("elasticstack_kibana_security_role.test", "name", roleName), resource.TestCheckNoResourceAttr("elasticstack_kibana_security_role.test", "kibana.0.feature.#"), resource.TestCheckNoResourceAttr("elasticstack_kibana_security_role.test", "elasticsearch.0.indices.0.field_security.#"), - utils.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "elasticsearch.0.run_as", []string{"elastic", "kibana"}), - utils.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "kibana.0.base", []string{"all"}), - utils.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "kibana.0.spaces", []string{"default"}), + checks.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "elasticsearch.0.run_as", []string{"elastic", "kibana"}), + checks.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "kibana.0.base", []string{"all"}), + checks.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "kibana.0.spaces", []string{"default"}), ), }, { @@ -55,14 +55,14 @@ func TestAccResourceKibanaSecurityRole(t *testing.T) { resource.TestCheckResourceAttr("elasticstack_kibana_security_role.test", "name", roleNameRemoteIndices), resource.TestCheckNoResourceAttr("elasticstack_kibana_security_role.test", "kibana.0.base.#"), resource.TestCheckNoResourceAttr("elasticstack_kibana_security_role.test", "elasticsearch.0.run_as.#"), - utils.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "elasticsearch.0.indices.0.names", []string{"sample"}), - utils.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "elasticsearch.0.indices.0.field_security.0.grant", []string{"sample"}), - utils.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "kibana.0.feature.2.privileges", []string{"minimal_read", "store_search_session", "url_create"}), - utils.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "kibana.0.spaces", []string{"default"}), - utils.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "elasticsearch.0.remote_indices.0.clusters", []string{"test-cluster"}), - utils.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "elasticsearch.0.remote_indices.0.field_security.0.grant", []string{"sample"}), - utils.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "elasticsearch.0.remote_indices.0.names", []string{"sample"}), - utils.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "elasticsearch.0.remote_indices.0.privileges", []string{"create", "read", "write"}), + checks.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "elasticsearch.0.indices.0.names", []string{"sample"}), + checks.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "elasticsearch.0.indices.0.field_security.0.grant", []string{"sample"}), + checks.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "kibana.0.feature.2.privileges", []string{"minimal_read", "store_search_session", "url_create"}), + checks.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "kibana.0.spaces", []string{"default"}), + checks.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "elasticsearch.0.remote_indices.0.clusters", []string{"test-cluster"}), + checks.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "elasticsearch.0.remote_indices.0.field_security.0.grant", []string{"sample"}), + checks.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "elasticsearch.0.remote_indices.0.names", []string{"sample"}), + checks.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "elasticsearch.0.remote_indices.0.privileges", []string{"create", "read", "write"}), ), }, { @@ -72,13 +72,13 @@ func TestAccResourceKibanaSecurityRole(t *testing.T) { resource.TestCheckResourceAttr("elasticstack_kibana_security_role.test", "name", roleNameRemoteIndices), resource.TestCheckNoResourceAttr("elasticstack_kibana_security_role.test", "kibana.0.feature.#"), resource.TestCheckNoResourceAttr("elasticstack_kibana_security_role.test", "elasticsearch.0.indices.0.field_security.#"), - utils.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "elasticsearch.0.run_as", []string{"elastic", "kibana"}), - utils.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "kibana.0.base", []string{"all"}), - utils.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "kibana.0.spaces", []string{"default"}), - utils.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "elasticsearch.0.remote_indices.0.clusters", []string{"test-cluster2"}), - utils.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "elasticsearch.0.remote_indices.0.field_security.0.grant", []string{"sample2"}), - utils.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "elasticsearch.0.remote_indices.0.names", []string{"sample2"}), - utils.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "elasticsearch.0.remote_indices.0.privileges", []string{"create", "read", "write"}), + checks.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "elasticsearch.0.run_as", []string{"elastic", "kibana"}), + checks.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "kibana.0.base", []string{"all"}), + checks.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "kibana.0.spaces", []string{"default"}), + checks.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "elasticsearch.0.remote_indices.0.clusters", []string{"test-cluster2"}), + checks.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "elasticsearch.0.remote_indices.0.field_security.0.grant", []string{"sample2"}), + checks.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "elasticsearch.0.remote_indices.0.names", []string{"sample2"}), + checks.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "elasticsearch.0.remote_indices.0.privileges", []string{"create", "read", "write"}), ), }, }, From bda18fd06f6cf5a1f530a2ec26eec8979f561817 Mon Sep 17 00:00:00 2001 From: Toby Brain Date: Sun, 6 Jul 2025 14:14:41 +1000 Subject: [PATCH 10/10] Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08d44ca49..fdd20ff15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## [Unreleased] - Add `headers` for the provider connection ([#1057](https://github.com/elastic/terraform-provider-elasticstack/pull/1057)) +- Migrate `elasticstack_elasticsearch_system_user` resource to Terraform plugin framework ([#1154](https://github.com/elastic/terraform-provider-elasticstack/pull/1154)) ## [0.11.15] - 2025-04-23