Skip to content

Commit 9d977c0

Browse files
committed
[Issue-1111] Move Realm Attributes to its own Resource
this will help decouple the realms attributes and allow reuse in modules without relying on the entire realm configuration. Signed-off-by: Ryan H <[email protected]>
1 parent b1f323a commit 9d977c0

File tree

8 files changed

+222
-24
lines changed

8 files changed

+222
-24
lines changed

docs/resources/realm.md

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,6 @@ resource "keycloak_realm" "realm" {
2424
2525
ssl_required = "external"
2626
password_policy = "upperCase(1) and length(8) and forceExpiredPasswordChange(365) and notUsername"
27-
attributes = {
28-
mycustomAttribute = "myCustomValue"
29-
}
3027
3128
smtp_server {
3229
host = "smtp.example.com"
@@ -84,7 +81,7 @@ resource "keycloak_realm" "realm" {
8481
- `display_name_html` - (Optional) The display name for the realm that is rendered as HTML on the screen when logging in to the admin console.
8582
- `user_managed_access` - (Optional) When `true`, users are allowed to manage their own resources. Defaults to `false`.
8683
- `organizations_enabled` - (Optional) When `true`, organization support is enabled. Defaults to `false`.
87-
- `attributes` - (Optional) A map of custom attributes to add to the realm.
84+
- `attributes` - [Moved to its own resource see](./realm_attributes.md)
8885
- `internal_id` - (Optional) When specified, this will be used as the realm's internal ID within Keycloak. When not specified, the realm's internal ID will be set to the realm's name.
8986

9087
### Login Settings

docs/resources/realm_attributes.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
page_title: "keycloak_realm_attributes Resource"
3+
---
4+
5+
# keycloak\_realm_attributes Resource
6+
7+
Allows for creating and managing Realm attributes within Keycloak.
8+
9+
## Example Usage
10+
11+
```hcl
12+
resource "keycloak_realm" "realm_example" {
13+
realm = "realm-example"
14+
enabled = true
15+
}
16+
17+
resource "keycloak_realm_attributes" "realm_attributes" {
18+
realm_id = keycloak_realm.realm_example.id
19+
attributes = {
20+
baz = "bat"
21+
qux = "quux"
22+
}
23+
}
24+
```

example/main.tf

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,6 @@ resource "keycloak_realm" "test" {
7777
ssl_required = "external"
7878
password_policy = "upperCase(1) and length(8) and forceExpiredPasswordChange(365) and notUsername"
7979

80-
attributes = {
81-
mycustomAttribute = "myCustomValue"
82-
userProfileEnabled = true
83-
}
84-
8580
web_authn_policy {
8681
relying_party_entity_name = "Example"
8782
relying_party_id = "keycloak.example.com"
@@ -101,6 +96,15 @@ resource "keycloak_realm" "test" {
10196
}
10297
}
10398

99+
resource "keycloak_realm_attributes" "test_attributes" {
100+
realm_id = keycloak_realm.test.id
101+
102+
attributes = {
103+
mycustomAttribute = "myCustomValue"
104+
userProfileEnabled = true
105+
}
106+
}
107+
104108
resource "keycloak_realm_localization" "test_translation" {
105109
realm_id = keycloak_realm.test.id
106110
locale = "en"

keycloak/realm_attributes.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package keycloak
2+
3+
import (
4+
"context"
5+
"fmt"
6+
)
7+
8+
type RealmAttributes struct {
9+
RealmId string `json:"-"`
10+
Attributes map[string]interface{} `json:"attributes"`
11+
}
12+
13+
func (keycloakClient *KeycloakClient) GetRealmAttributes(ctx context.Context, realmId string) (*RealmAttributes, error) {
14+
realm, err := keycloakClient.GetRealm(ctx, realmId)
15+
if err != nil {
16+
return nil, err
17+
}
18+
19+
return &RealmAttributes{
20+
RealmId: realmId,
21+
Attributes: realm.Attributes,
22+
}, nil
23+
}
24+
25+
func (keycloakClient *KeycloakClient) UpdateRealmAttributes(ctx context.Context, realmId string, attributes *RealmAttributes) error {
26+
return keycloakClient.put(ctx, fmt.Sprintf("/realms/%s", realmId), attributes)
27+
}

provider/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ func KeycloakProvider(client *keycloak.KeycloakClient) *schema.Provider {
3232
},
3333
ResourcesMap: map[string]*schema.Resource{
3434
"keycloak_realm": resourceKeycloakRealm(),
35+
"keycloak_realm_attributes": resourceKeycloakRealmAttributes(),
3536
"keycloak_realm_events": resourceKeycloakRealmEvents(),
3637
"keycloak_realm_default_client_scopes": resourceKeycloakRealmDefaultClientScopes(),
3738
"keycloak_realm_optional_client_scopes": resourceKeycloakRealmOptionalClientScopes(),

provider/resource_keycloak_realm.go

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -626,12 +626,6 @@ func resourceKeycloakRealm() *schema.Resource {
626626
Computed: true,
627627
},
628628

629-
// misc attributes
630-
"attributes": {
631-
Type: schema.TypeMap,
632-
Optional: true,
633-
},
634-
635629
// default default client scopes
636630
"default_default_client_scopes": {
637631
Type: schema.TypeSet,
@@ -1365,15 +1359,6 @@ func setRealmData(data *schema.ResourceData, realm *keycloak.Realm, keycloakVers
13651359
webAuthnPasswordlessPolicy["user_verification_requirement"] = realm.WebAuthnPolicyPasswordlessUserVerificationRequirement
13661360
data.Set("web_authn_passwordless_policy", []interface{}{webAuthnPasswordlessPolicy})
13671361

1368-
attributes := map[string]interface{}{}
1369-
if v, ok := data.GetOk("attributes"); ok {
1370-
for key := range v.(map[string]interface{}) {
1371-
attributes[key] = realm.Attributes[key]
1372-
//We are only interested in attributes managed in terraform (Keycloak returns a lot of doubles values in the attributes...)
1373-
}
1374-
}
1375-
data.Set("attributes", attributes)
1376-
13771362
// default and optional client scope mappings
13781363
data.Set("default_default_client_scopes", realm.DefaultDefaultClientScopes)
13791364
data.Set("default_optional_client_scopes", realm.DefaultOptionalClientScopes)
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package provider
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
8+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
9+
"github.com/keycloak/terraform-provider-keycloak/keycloak"
10+
)
11+
12+
func resourceKeycloakRealmAttributes() *schema.Resource {
13+
return &schema.Resource{
14+
CreateContext: resourceKeycloakRealmAttributesCreate,
15+
ReadContext: resourceKeycloakRealmAttributesRead,
16+
UpdateContext: resourceKeycloakRealmAttributesUpdate,
17+
DeleteContext: resourceKeycloakRealmAttributesDelete,
18+
Schema: map[string]*schema.Schema{
19+
"realm_id": {
20+
Type: schema.TypeString,
21+
Required: true,
22+
},
23+
"attributes": {
24+
Type: schema.TypeMap,
25+
Required: true,
26+
Description: "A map of attributes for the realm",
27+
},
28+
},
29+
}
30+
}
31+
func resourceKeycloakRealmAttributesCreate(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
32+
keycloakClient := meta.(*keycloak.KeycloakClient)
33+
realmId := data.Get("realm_id").(string)
34+
attributes := mapFromDataToRealmAttributes(data)
35+
36+
err := keycloakClient.UpdateRealmAttributes(ctx, realmId, attributes)
37+
if err != nil {
38+
return diag.FromErr(err)
39+
}
40+
41+
data.SetId(realmId)
42+
43+
return resourceKeycloakRealmAttributesRead(ctx, data, meta)
44+
}
45+
46+
func resourceKeycloakRealmAttributesRead(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
47+
keycloakClient := meta.(*keycloak.KeycloakClient)
48+
realmId := data.Get("realm_id").(string)
49+
attributes, err := keycloakClient.GetRealmAttributes(ctx, realmId)
50+
if err != nil {
51+
return diag.FromErr(err)
52+
}
53+
if attributes == nil {
54+
return nil
55+
}
56+
57+
mapFromRealmAttributesToData(attributes, data)
58+
59+
return nil
60+
}
61+
62+
func resourceKeycloakRealmAttributesUpdate(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
63+
keycloakClient := meta.(*keycloak.KeycloakClient)
64+
realmId := data.Get("realm_id").(string)
65+
attributes := mapFromDataToRealmAttributes(data)
66+
67+
err := keycloakClient.UpdateRealmAttributes(ctx, realmId, attributes)
68+
if err != nil {
69+
return diag.FromErr(err)
70+
}
71+
72+
return resourceKeycloakRealmAttributesRead(ctx, data, meta)
73+
}
74+
75+
func resourceKeycloakRealmAttributesDelete(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
76+
keycloakClient := meta.(*keycloak.KeycloakClient)
77+
realmId := data.Get("realm_id").(string)
78+
attributes := &keycloak.RealmAttributes{
79+
Attributes: map[string]interface{}{},
80+
}
81+
82+
err := keycloakClient.UpdateRealmAttributes(ctx, realmId, attributes)
83+
if err != nil {
84+
return diag.FromErr(err)
85+
}
86+
87+
data.SetId("")
88+
89+
return nil
90+
}
91+
92+
func mapFromDataToRealmAttributes(data *schema.ResourceData) *keycloak.RealmAttributes {
93+
return &keycloak.RealmAttributes{
94+
RealmId: data.Get("realm_id").(string),
95+
Attributes: data.Get("attributes").(map[string]interface{}),
96+
}
97+
}
98+
99+
func mapFromRealmAttributesToData(attributes *keycloak.RealmAttributes, data *schema.ResourceData) {
100+
_attributes := map[string]interface{}{}
101+
if v, ok := data.GetOk("attributes"); ok {
102+
for key := range v.(map[string]interface{}) {
103+
_attributes[key] = attributes.Attributes[key]
104+
}
105+
}
106+
if err := data.Set("attributes", _attributes); err != nil {
107+
panic(fmt.Sprintf("Failed to set attributes: %v", err))
108+
}
109+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package provider
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
8+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
9+
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
10+
)
11+
12+
func TestAccKeycloakRealmAttributes(t *testing.T) {
13+
realmName := acctest.RandomWithPrefix("tf-acc")
14+
15+
resource.Test(t, resource.TestCase{
16+
ProviderFactories: testAccProviderFactories,
17+
PreCheck: func() { testAccPreCheck(t) },
18+
Steps: []resource.TestStep{
19+
{
20+
Config: testKeycloakRealmAttributes_basic(realmName, "foo", "bar"),
21+
Check: testAccCheckKeycloakRealmAttributesExists(realmName),
22+
},
23+
},
24+
})
25+
}
26+
27+
func testKeycloakRealmAttributes_basic(realm string, name string, description string) string {
28+
return fmt.Sprintf(`
29+
resource "keycloak_realm" "realm" {
30+
realm = "%s"
31+
}
32+
33+
resource "keycloak_realm_attributes" "attributes" {
34+
realm_id = keycloak_realm.realm.id
35+
attributes = {
36+
name = "%s"
37+
description = "%s"
38+
}
39+
}`, realm, name, description)
40+
}
41+
42+
func testAccCheckKeycloakRealmAttributesExists(realm string) resource.TestCheckFunc {
43+
return func(s *terraform.State) error {
44+
_, err := keycloakClient.GetRealmAttributes(testCtx, realm)
45+
if err != nil {
46+
return fmt.Errorf("Realm attributes not found: %s", realm)
47+
}
48+
49+
return nil
50+
}
51+
}

0 commit comments

Comments
 (0)