forked from mrparkers/terraform-provider-keycloak
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* implement resource_keycloak_user_groups * implement exhaustive mode of keycloak_user_groups * also remove groups when they are removed from the resource. Even if the user_groups resource is non-exhaustive Co-authored-by: Benedikt <benedikt.suessmann@sva.de>
- Loading branch information
1 parent
ec8e50a
commit b3f52b0
Showing
7 changed files
with
841 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
--- | ||
page_title: "keycloak_user_groups Resource" | ||
--- | ||
|
||
# keycloak\_user\_groups Resource | ||
|
||
Allows for managing a Keycloak user's groups. | ||
|
||
If `exhaustive` is true, this resource attempts to be an **authoritative** source over user groups: groups that are manually added to the user will be removed, and groups that are manually removed from the user group will be added upon the next run of `terraform apply`. | ||
If `exhaustive` is false, this resource is a partial assignation of groups to a user. As a result, you can get multiple `keycloak_user_groups` for the same `user_id`. | ||
|
||
|
||
## Example Usage (exhaustive groups) | ||
```hcl | ||
resource "keycloak_realm" "realm" { | ||
realm = "my-realm" | ||
enabled = true | ||
} | ||
resource "keycloak_group" "group" { | ||
realm_id = keycloak_realm.realm.id | ||
name = "foo" | ||
} | ||
resource "keycloak_user" "user" { | ||
realm_id = keycloak_realm.realm.id | ||
username = "my-user" | ||
} | ||
resource "keycloak_user_groups" "user_groups" { | ||
realm_id = keycloak_realm.realm.id | ||
user_id = keycloak_user.user.id | ||
group_ids = [ | ||
keycloak_group.group.id | ||
] | ||
} | ||
``` | ||
|
||
## Example Usage (non exhaustive groups) | ||
```hcl | ||
resource "keycloak_realm" "realm" { | ||
realm = "my-realm" | ||
enabled = true | ||
} | ||
resource "keycloak_group" "group_foo" { | ||
realm_id = keycloak_realm.realm.id | ||
name = "foo" | ||
} | ||
resource "keycloak_group" "group_bar" { | ||
realm_id = keycloak_realm.realm.id | ||
name = "bar" | ||
} | ||
resource "keycloak_user" "user" { | ||
realm_id = keycloak_realm.realm.id | ||
username = "my-user" | ||
} | ||
resource "keycloak_user_groups" "user_groups_association_1" { | ||
realm_id = keycloak_realm.realm.id | ||
user_id = keycloak_user.user.id | ||
exhaustive = false | ||
group_ids = [ | ||
keycloak_group.group_foo.id | ||
] | ||
} | ||
resource "keycloak_user_groups" "user_groups_association_1" { | ||
realm_id = keycloak_realm.realm.id | ||
user_id = keycloak_user.user.id | ||
exhaustive = false | ||
group_ids = [ | ||
keycloak_group.group_bar.id | ||
] | ||
} | ||
``` | ||
|
||
## Argument Reference | ||
|
||
- `realm_id` - (Required) The realm this group exists in. | ||
- `user_id` - (Required) The ID of the user this resource should manage groups for. | ||
- `group_ids` - (Required) A list of group IDs that the user is member of. | ||
- `exhaustive` - (Optional) | ||
|
||
## Import | ||
|
||
This resource does not support import. Instead of importing, feel free to create this resource | ||
as if it did not already exist on the server. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
package provider | ||
|
||
import ( | ||
"fmt" | ||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" | ||
"github.com/mrparkers/terraform-provider-keycloak/keycloak" | ||
"strings" | ||
) | ||
|
||
func resourceKeycloakUserGroups() *schema.Resource { | ||
return &schema.Resource{ | ||
Create: resourceKeycloakUserGroupsReconcile, | ||
Read: resourceKeycloakUserGroupsRead, | ||
Delete: resourceKeycloakUserGroupsDelete, | ||
Update: resourceKeycloakUserGroupsReconcile, | ||
// This resource can be imported using {{realm}}/{{userId}}. | ||
Importer: &schema.ResourceImporter{ | ||
State: resourceKeycloakUserGroupsImport, | ||
}, | ||
Schema: map[string]*schema.Schema{ | ||
"realm_id": { | ||
Type: schema.TypeString, | ||
Required: true, | ||
ForceNew: true, | ||
}, | ||
"user_id": { | ||
Type: schema.TypeString, | ||
Required: true, | ||
ForceNew: true, | ||
}, | ||
"group_ids": { | ||
Type: schema.TypeSet, | ||
Elem: &schema.Schema{Type: schema.TypeString}, | ||
Set: schema.HashString, | ||
Required: true, | ||
}, | ||
"exhaustive": { | ||
Type: schema.TypeBool, | ||
Default: true, | ||
Optional: true, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func resourceKeycloakUserGroupsRead(data *schema.ResourceData, meta interface{}) error { | ||
keycloakClient := meta.(*keycloak.KeycloakClient) | ||
|
||
realmId := data.Get("realm_id").(string) | ||
userId := data.Get("user_id").(string) | ||
groupIds := data.Get("group_ids").(*schema.Set) | ||
exhaustive := data.Get("exhaustive").(bool) | ||
|
||
userGroups, err := keycloakClient.GetUserGroups(realmId, userId) | ||
if err != nil { | ||
return handleNotFoundError(err, data) | ||
} | ||
|
||
var groups []string | ||
for _, group := range userGroups { | ||
//only add groups that we care about | ||
if exhaustive || groupIds.Contains(group.Id) { | ||
groups = append(groups, group.Id) | ||
} | ||
} | ||
|
||
data.Set("group_ids", groups) | ||
data.SetId(userGroupsId(realmId, userId)) | ||
|
||
return nil | ||
} | ||
|
||
func resourceKeycloakUserGroupsReconcile(data *schema.ResourceData, meta interface{}) error { | ||
keycloakClient := meta.(*keycloak.KeycloakClient) | ||
|
||
realmId := data.Get("realm_id").(string) | ||
userId := data.Get("user_id").(string) | ||
groupIds := interfaceSliceToStringSlice(data.Get("group_ids").(*schema.Set).List()) | ||
exhaustive := data.Get("exhaustive").(bool) | ||
|
||
if data.HasChange("group_ids") { | ||
o, n := data.GetChange("group_ids") | ||
os := o.(*schema.Set) | ||
ns := n.(*schema.Set) | ||
remove := interfaceSliceToStringSlice(os.Difference(ns).List()) | ||
|
||
if err := keycloakClient.RemoveUserFromGroups(remove, userId, realmId); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
userGroups, err := keycloakClient.GetUserGroups(realmId, userId) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
var userGroupsIds []string | ||
for _, group := range userGroups { | ||
userGroupsIds = append(userGroupsIds, group.Id) | ||
} | ||
|
||
remove := stringArrayDifference(userGroupsIds, groupIds) | ||
add := stringArrayDifference(groupIds, userGroupsIds) | ||
|
||
if err := keycloakClient.AddUserToGroups(add, userId, realmId); err != nil { | ||
return err | ||
} | ||
|
||
if exhaustive { | ||
if err := keycloakClient.RemoveUserFromGroups(remove, userId, realmId); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
data.SetId(userGroupsId(realmId, userId)) | ||
return resourceKeycloakUserGroupsRead(data, meta) | ||
} | ||
|
||
func resourceKeycloakUserGroupsDelete(data *schema.ResourceData, meta interface{}) error { | ||
keycloakClient := meta.(*keycloak.KeycloakClient) | ||
|
||
realmId := data.Get("realm_id").(string) | ||
userId := data.Get("user_id").(string) | ||
groupIds := interfaceSliceToStringSlice(data.Get("group_ids").(*schema.Set).List()) | ||
|
||
return keycloakClient.RemoveUserFromGroups(groupIds, userId, realmId) | ||
} | ||
|
||
func resourceKeycloakUserGroupsImport(d *schema.ResourceData, _ interface{}) ([]*schema.ResourceData, error) { | ||
parts := strings.Split(d.Id(), "/") | ||
|
||
if len(parts) != 2 { | ||
return nil, fmt.Errorf("Invalid import. Supported import format: {{realm}}/{{userId}}.") | ||
} | ||
|
||
d.Set("realm_id", parts[0]) | ||
d.Set("user_id", parts[1]) | ||
|
||
d.SetId(userGroupsId(parts[0], parts[1])) | ||
|
||
return []*schema.ResourceData{d}, nil | ||
} | ||
|
||
func userGroupsId(realmId, userId string) string { | ||
return fmt.Sprintf("%s/%s", realmId, userId) | ||
} |
Oops, something went wrong.