diff --git a/CHANGELOG.md b/CHANGELOG.md index f6c786c45..2521da340 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## [Unreleased] - Fix secret handling `elasticstack_fleet_integration_policy` resource. ([#821](https://github.com/elastic/terraform-provider-elasticstack/pull/821)) +- Add `description` attribute to `elasticstack_elasticsearch_security_role` resource. ([#824](https://github.com/elastic/terraform-provider-elasticstack/pull/824)) - Fix merge values for `elasticstack_kibana_synthetics_monitor` monitor locations ([#823](https://github.com/elastic/terraform-provider-elasticstack/pull/823) ## [0.11.8] - 2024-10-02 diff --git a/docs/resources/elasticsearch_security_role.md b/docs/resources/elasticsearch_security_role.md index bddcc1cda..31cd2a4c4 100644 --- a/docs/resources/elasticsearch_security_role.md +++ b/docs/resources/elasticsearch_security_role.md @@ -18,8 +18,9 @@ provider "elasticstack" { } resource "elasticstack_elasticsearch_security_role" "role" { - name = "testrole" - cluster = ["all"] + name = "testrole" + description = "Role for testing" + cluster = ["all"] indices { names = ["index1", "index2"] @@ -55,6 +56,7 @@ output "role" { - `applications` (Block Set) A list of application privilege entries. (see [below for nested schema](#nestedblock--applications)) - `cluster` (Set of String) A list of cluster privileges. These privileges define the cluster level actions that users with this role are able to execute. +- `description` (String) The description of the role. - `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)) - `global` (String) An object defining global privileges. - `indices` (Block Set) A list of indices permissions entries. (see [below for nested schema](#nestedblock--indices)) diff --git a/examples/resources/elasticstack_elasticsearch_security_role/resource.tf b/examples/resources/elasticstack_elasticsearch_security_role/resource.tf index bc6c94415..75579d200 100644 --- a/examples/resources/elasticstack_elasticsearch_security_role/resource.tf +++ b/examples/resources/elasticstack_elasticsearch_security_role/resource.tf @@ -3,8 +3,9 @@ provider "elasticstack" { } resource "elasticstack_elasticsearch_security_role" "role" { - name = "testrole" - cluster = ["all"] + name = "testrole" + description = "Role for testing" + cluster = ["all"] indices { names = ["index1", "index2"] diff --git a/internal/elasticsearch/security/role.go b/internal/elasticsearch/security/role.go index dde719db0..41953ce3f 100644 --- a/internal/elasticsearch/security/role.go +++ b/internal/elasticsearch/security/role.go @@ -18,6 +18,7 @@ import ( ) var minSupportedRemoteIndicesVersion = version.Must(version.NewVersion("8.10.0")) +var minSupportedDescriptionVersion = version.Must(version.NewVersion("8.15.0")) func ResourceRole() *schema.Resource { roleSchema := map[string]*schema.Schema{ @@ -32,6 +33,11 @@ func ResourceRole() *schema.Resource { Required: true, ForceNew: true, }, + "description": { + Description: "The description of the role.", + Type: schema.TypeString, + Optional: true, + }, "applications": { Description: "A list of application privilege entries.", Type: schema.TypeSet, @@ -258,6 +264,18 @@ func resourceSecurityRolePut(ctx context.Context, d *schema.ResourceData, meta i } var role models.Role role.Name = roleId + + // Add description to the role + if v, ok := d.GetOk("description"); ok { + // Return an error if the server version is less than the minimum supported version + if serverVersion.LessThan(minSupportedDescriptionVersion) { + return diag.FromErr(fmt.Errorf("'description' is supported only for Elasticsearch v%s and above", minSupportedDescriptionVersion.String())) + } + + description := v.(string) + role.Description = &description + } + if v, ok := d.GetOk("applications"); ok { definedApps := v.(*schema.Set) applications := make([]models.Application, definedApps.Len()) @@ -364,37 +382,37 @@ func resourceSecurityRolePut(ctx context.Context, d *schema.ResourceData, meta i if definedRemoteIndices.Len() > 0 && serverVersion.LessThan(minSupportedRemoteIndicesVersion) { return diag.FromErr(fmt.Errorf("'remote_indices' is supported only for Elasticsearch v%s and above", minSupportedRemoteIndicesVersion.String())) } - remote_indices := make([]models.RemoteIndexPerms, definedRemoteIndices.Len()) + remoteIndices := make([]models.RemoteIndexPerms, definedRemoteIndices.Len()) for i, idx := range definedRemoteIndices.List() { - remote_index := idx.(map[string]interface{}) + remoteIndex := idx.(map[string]interface{}) - definedRemoteNames := remote_index["names"].(*schema.Set) - remote_names := make([]string, definedRemoteNames.Len()) + definedRemoteNames := remoteIndex["names"].(*schema.Set) + remoteNames := make([]string, definedRemoteNames.Len()) for i, name := range definedRemoteNames.List() { - remote_names[i] = name.(string) + remoteNames[i] = name.(string) } - definedRemoteClusters := remote_index["clusters"].(*schema.Set) - remote_clusters := make([]string, definedRemoteClusters.Len()) + definedRemoteClusters := remoteIndex["clusters"].(*schema.Set) + remoteClusters := make([]string, definedRemoteClusters.Len()) for i, cluster := range definedRemoteClusters.List() { - remote_clusters[i] = cluster.(string) + remoteClusters[i] = cluster.(string) } - definedRemotePrivs := remote_index["privileges"].(*schema.Set) - remote_privs := make([]string, definedRemotePrivs.Len()) + definedRemotePrivs := remoteIndex["privileges"].(*schema.Set) + remotePrivs := make([]string, definedRemotePrivs.Len()) for i, pr := range definedRemotePrivs.List() { - remote_privs[i] = pr.(string) + remotePrivs[i] = pr.(string) } newRemoteIndex := models.RemoteIndexPerms{ - Names: remote_names, - Clusters: remote_clusters, - Privileges: remote_privs, + Names: remoteNames, + Clusters: remoteClusters, + Privileges: remotePrivs, } - if query := remote_index["query"].(string); query != "" { + if query := remoteIndex["query"].(string); query != "" { newRemoteIndex.Query = &query } - if fieldSec := remote_index["field_security"].([]interface{}); len(fieldSec) > 0 { - remote_fieldSecurity := models.FieldSecurity{} + if fieldSec := remoteIndex["field_security"].([]interface{}); len(fieldSec) > 0 { + remoteFieldSecurity := models.FieldSecurity{} // there must be only 1 entry definedRemoteFieldSec := fieldSec[0].(map[string]interface{}) @@ -404,7 +422,7 @@ func resourceSecurityRolePut(ctx context.Context, d *schema.ResourceData, meta i for i, grant := range gr.List() { grants[i] = grant.(string) } - remote_fieldSecurity.Grant = grants + remoteFieldSecurity.Grant = grants } // except if exp := definedRemoteFieldSec["except"].(*schema.Set); exp != nil { @@ -412,14 +430,14 @@ func resourceSecurityRolePut(ctx context.Context, d *schema.ResourceData, meta i for i, except := range exp.List() { excepts[i] = except.(string) } - remote_fieldSecurity.Except = excepts + remoteFieldSecurity.Except = excepts } - newRemoteIndex.FieldSecurity = &remote_fieldSecurity + newRemoteIndex.FieldSecurity = &remoteFieldSecurity } - remote_indices[i] = newRemoteIndex + remoteIndices[i] = newRemoteIndex } - role.RemoteIndices = remote_indices + role.RemoteIndices = remoteIndices } if v, ok := d.GetOk("metadata"); ok { @@ -473,6 +491,13 @@ func resourceSecurityRoleRead(ctx context.Context, d *schema.ResourceData, meta return diag.FromErr(err) } + // Set the description if it exists + if role.Description != nil { + if err := d.Set("description", *role.Description); err != nil { + return diag.FromErr(err) + } + } + apps := role.Applications applications := flattenApplicationsData(&apps) if err := d.Set("applications", applications); err != nil { diff --git a/internal/elasticsearch/security/role_test.go b/internal/elasticsearch/security/role_test.go index 40d9da263..aa00155c3 100644 --- a/internal/elasticsearch/security/role_test.go +++ b/internal/elasticsearch/security/role_test.go @@ -14,11 +14,13 @@ import ( ) var minSupportedRemoteIndicesVersion = version.Must(version.NewSemver("8.10.0")) +var minSupportedDescriptionVersion = version.Must(version.NewVersion("8.15.0")) func TestAccResourceSecurityRole(t *testing.T) { // generate a random username roleName := sdkacctest.RandStringFromCharSet(10, sdkacctest.CharSetAlphaNum) roleNameRemoteIndices := sdkacctest.RandStringFromCharSet(10, sdkacctest.CharSetAlphaNum) + roleNameDescription := sdkacctest.RandStringFromCharSet(10, sdkacctest.CharSetAlphaNum) resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -81,6 +83,22 @@ func TestAccResourceSecurityRole(t *testing.T) { resource.TestCheckTypeSetElemAttr("elasticstack_elasticsearch_security_role.test", "remote_indices.*.names.*", "sample2"), ), }, + { + SkipFunc: versionutils.CheckIfVersionIsUnsupported(minSupportedDescriptionVersion), + Config: testAccResourceSecurityRoleDescriptionCreate(roleNameDescription), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("elasticstack_elasticsearch_security_role.test", "name", roleNameDescription), + resource.TestCheckResourceAttr("elasticstack_elasticsearch_security_role.test", "description", "test description"), + ), + }, + { + SkipFunc: versionutils.CheckIfVersionIsUnsupported(minSupportedDescriptionVersion), + Config: testAccResourceSecurityRoleDescriptionUpdate(roleNameDescription), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("elasticstack_elasticsearch_security_role.test", "name", roleNameDescription), + resource.TestCheckResourceAttr("elasticstack_elasticsearch_security_role.test", "description", "updated test description"), + ), + }, }, }) } @@ -92,7 +110,8 @@ provider "elasticstack" { } resource "elasticstack_elasticsearch_security_role" "test" { - name = "%s" + name = "%s" + cluster = ["all"] indices { @@ -123,7 +142,8 @@ provider "elasticstack" { } resource "elasticstack_elasticsearch_security_role" "test" { - name = "%s" + name = "%s" + cluster = ["all"] indices { @@ -211,6 +231,32 @@ resource "elasticstack_elasticsearch_security_role" "test" { `, roleName) } +func testAccResourceSecurityRoleDescriptionCreate(roleName string) string { + return fmt.Sprintf(` +provider "elasticstack" { + elasticsearch {} +} + +resource "elasticstack_elasticsearch_security_role" "test" { + name = "%s" + description = "test description" +} + `, roleName) +} + +func testAccResourceSecurityRoleDescriptionUpdate(roleName string) string { + return fmt.Sprintf(` +provider "elasticstack" { + elasticsearch {} +} + +resource "elasticstack_elasticsearch_security_role" "test" { + name = "%s" + description = "updated test description" +} + `, roleName) +} + func checkResourceSecurityRoleDestroy(s *terraform.State) error { client, err := clients.NewAcceptanceTestingClient() if err != nil { @@ -234,7 +280,7 @@ func checkResourceSecurityRoleDestroy(s *terraform.State) error { } if res.StatusCode != 404 { - return fmt.Errorf("Role (%s) still exists", compId.ResourceId) + return fmt.Errorf("role (%s) still exists", compId.ResourceId) } } return nil diff --git a/internal/models/models.go b/internal/models/models.go index 67b083975..669c0dd10 100644 --- a/internal/models/models.go +++ b/internal/models/models.go @@ -74,6 +74,7 @@ type UserPassword struct { type Role struct { Name string `json:"-"` + Description *string `json:"description,omitempty"` Applications []Application `json:"applications,omitempty"` Global map[string]interface{} `json:"global,omitempty"` Cluster []string `json:"cluster,omitempty"`