From 915b23f7dd5fed8eeb9ec97bb50c8ac992d5991f Mon Sep 17 00:00:00 2001 From: dbolack <43891975+dbolack@users.noreply.github.com> Date: Sat, 27 Feb 2021 21:02:01 -0600 Subject: [PATCH] add attribute support for keycloak_role resource (#475) --- docs/resources/role.md | 27 +++++++++++ keycloak/keycloak_client.go | 3 +- keycloak/role.go | 5 ++- provider/data_source_keycloak_role.go | 10 +++++ provider/resource_keycloak_role.go | 25 +++++++++-- provider/resource_keycloak_role_test.go | 59 +++++++++++++++++++++++++ 6 files changed, 123 insertions(+), 6 deletions(-) diff --git a/docs/resources/role.md b/docs/resources/role.md index d17545e5..1303c208 100644 --- a/docs/resources/role.md +++ b/docs/resources/role.md @@ -20,6 +20,9 @@ resource "keycloak_role" "realm_role" { realm_id = keycloak_realm.realm.id name = "my-realm-role" description = "My Realm Role" + attributes = { + key = "value" + } } ``` @@ -49,6 +52,9 @@ resource "keycloak_role" "client_role" { client_id = keycloak_client.openid_client.id name = "my-client-role" description = "My Client Role" + attributes = { + key = "value" + } } ``` @@ -65,21 +71,33 @@ resource "keycloak_realm" "realm" { resource "keycloak_role" "create_role" { realm_id = keycloak_realm.realm.id name = "create" + attributes = { + key = "value" + } } resource "keycloak_role" "read_role" { realm_id = keycloak_realm.realm.id name = "read" + attributes = { + key = "value" + } } resource "keycloak_role" "update_role" { realm_id = keycloak_realm.realm.id name = "update" + attributes = { + key = "value" + } } resource "keycloak_role" "delete_role" { realm_id = keycloak_realm.realm.id name = "delete" + attributes = { + key = "value" + } } # client role @@ -102,6 +120,10 @@ resource "keycloak_role" "client_role" { client_id = keycloak_client.openid_client.id name = "my-client-role" description = "My Client Role" + + attributes = { + key = "value" + } } resource "keycloak_role" "admin_role" { @@ -114,6 +136,10 @@ resource "keycloak_role" "admin_role" { keycloak_role.delete_role.id, keycloak_role.client_role.id, ] + + attributes = { + key = "value" + } } ``` @@ -124,6 +150,7 @@ resource "keycloak_role" "admin_role" { - `client_id` - (Optional) When specified, this role will be created as a client role attached to the client with the provided ID - `description` - (Optional) The description of the role - `composite_roles` - (Optional) When specified, this role will be a composite role, composed of all roles that have an ID present within this list. +- `attributes` - (Optional) Attribute key/value pairs ## Import diff --git a/keycloak/keycloak_client.go b/keycloak/keycloak_client.go index ca1b45da..1fdbc0cd 100644 --- a/keycloak/keycloak_client.go +++ b/keycloak/keycloak_client.go @@ -6,7 +6,6 @@ import ( "crypto/x509" "encoding/json" "fmt" - "github.com/hashicorp/go-version" "io/ioutil" "log" "net/http" @@ -15,6 +14,8 @@ import ( "strings" "time" + "github.com/hashicorp/go-version" + "golang.org/x/net/publicsuffix" ) diff --git a/keycloak/role.go b/keycloak/role.go index 655b6441..8b9aa59e 100644 --- a/keycloak/role.go +++ b/keycloak/role.go @@ -16,6 +16,8 @@ type Role struct { ClientRole bool `json:"clientRole"` ContainerId string `json:"containerId"` Composite bool `json:"composite"` + //extra attributes of a role + Attributes map[string][]string `json:"attributes"` } type UsersInRole struct { @@ -58,7 +60,8 @@ func (keycloakClient *KeycloakClient) CreateRole(role *Role) error { role.Id = createdRole.Id - return nil + // seems like role attributes aren't respected on create, so a following update is needed + return keycloakClient.UpdateRole(role) } func (keycloakClient *KeycloakClient) GetRealmRoles(realmId string) ([]*Role, error) { diff --git a/provider/data_source_keycloak_role.go b/provider/data_source_keycloak_role.go index f9293cb6..938924f3 100644 --- a/provider/data_source_keycloak_role.go +++ b/provider/data_source_keycloak_role.go @@ -25,6 +25,16 @@ func dataSourceKeycloakRole() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "composite_roles": { + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + Computed: true, + }, + "attributes": { + Type: schema.TypeMap, + Computed: true, + }, }, } } diff --git a/provider/resource_keycloak_role.go b/provider/resource_keycloak_role.go index 7cc6ce17..c9e7061c 100644 --- a/provider/resource_keycloak_role.go +++ b/provider/resource_keycloak_role.go @@ -2,9 +2,10 @@ package provider import ( "fmt" + "strings" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/mrparkers/terraform-provider-keycloak/keycloak" - "strings" ) func resourceKeycloakRole() *schema.Resource { @@ -43,29 +44,47 @@ func resourceKeycloakRole() *schema.Resource { Set: schema.HashString, Optional: true, }, + // misc attributes + "attributes": { + Type: schema.TypeMap, + Optional: true, + }, }, } } func mapFromDataToRole(data *schema.ResourceData) *keycloak.Role { + attributes := map[string][]string{} + if v, ok := data.GetOk("attributes"); ok { + for key, value := range v.(map[string]interface{}) { + attributes[key] = splitLen(value.(string), MAX_ATTRIBUTE_VALUE_LEN) + } + } + role := &keycloak.Role{ Id: data.Id(), RealmId: data.Get("realm_id").(string), ClientId: data.Get("client_id").(string), Name: data.Get("name").(string), Description: data.Get("description").(string), + Attributes: attributes, } return role } func mapFromRoleToData(data *schema.ResourceData, role *keycloak.Role) { + attributes := map[string]string{} + for k, v := range role.Attributes { + attributes[k] = strings.Join(v, "") + } data.SetId(role.Id) data.Set("realm_id", role.RealmId) data.Set("client_id", role.ClientId) data.Set("name", role.Name) data.Set("description", role.Description) + data.Set("attributes", attributes) } func resourceKeycloakRoleCreate(data *schema.ResourceData, meta interface{}) error { @@ -206,9 +225,7 @@ func resourceKeycloakRoleUpdate(data *schema.ResourceData, meta interface{}) err } } - mapFromRoleToData(data, role) - - return nil + return resourceKeycloakRoleRead(data, meta) } func resourceKeycloakRoleDelete(data *schema.ResourceData, meta interface{}) error { diff --git a/provider/resource_keycloak_role_test.go b/provider/resource_keycloak_role_test.go index 0cf3c188..35d21840 100644 --- a/provider/resource_keycloak_role_test.go +++ b/provider/resource_keycloak_role_test.go @@ -263,6 +263,34 @@ func TestAccKeycloakRole_composites(t *testing.T) { }) } +func TestAccKeycloakRole_basicWithAttributes(t *testing.T) { + t.Parallel() + roleName := acctest.RandomWithPrefix("tf-acc") + attributeName := acctest.RandomWithPrefix("tf-acc") + attributeValue := acctest.RandomWithPrefix("tf-acc") + + resource.Test(t, resource.TestCase{ + ProviderFactories: testAccProviderFactories, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccCheckKeycloakRoleDestroy(), + Steps: []resource.TestStep{ + { + Config: testKeycloakRole_basicWithAttributes(roleName, attributeName, attributeValue), + Check: resource.ComposeTestCheckFunc( + testAccCheckKeycloakRoleExists("keycloak_role.role"), + testAccCheckKeycloakRoleHasAttribute("keycloak_role.role", attributeName, attributeValue), + ), + }, + { + ResourceName: "keycloak_role.role", + ImportState: true, + ImportStateVerify: true, + ImportStateIdPrefix: testAccRealm.Realm + "/", + }, + }, + }) +} + func testAccCheckKeycloakRoleExists(resourceName string) resource.TestCheckFunc { return func(s *terraform.State) error { _, err := getRoleFromState(s, resourceName) @@ -310,6 +338,21 @@ func testAccCheckKeycloakRoleFetch(resourceName string, role *keycloak.Role) res } } +func testAccCheckKeycloakRoleHasAttribute(resourceName, attributeName, attributeValue string) resource.TestCheckFunc { + return func(state *terraform.State) error { + role, err := getRoleFromState(state, resourceName) + if err != nil { + return err + } + + if len(role.Attributes) != 1 || role.Attributes[attributeName][0] != attributeValue { + return fmt.Errorf("expected role %s to have attribute %s with value %s", role.Name, attributeName, attributeValue) + } + + return nil + } +} + func testAccCheckKeycloakRoleHasComposites(resourceName string, compositeRoleNames []string) resource.TestCheckFunc { return func(state *terraform.State) error { role, err := getRoleFromState(state, resourceName) @@ -519,3 +562,19 @@ resource "keycloak_role" "role_with_composites" { } `, testAccRealm.Realm, clientOne, clientTwo, roleOne, roleTwo, roleThree, roleFour, roleWithComposites, tfComposites) } + +func testKeycloakRole_basicWithAttributes(role, attributeName, attributeValue string) string { + return fmt.Sprintf(` +data "keycloak_realm" "realm" { + realm = "%s" +} + +resource "keycloak_role" "role" { + name = "%s" + realm_id = data.keycloak_realm.realm.id + attributes = { + "%s" = "%s" + } +} + `, testAccRealm.Realm, role, attributeName, attributeValue) +}