Skip to content

Commit

Permalink
feat(user): Importable user by username (#1041)
Browse files Browse the repository at this point in the history
Signed-off-by: Dennis Kniep <kniepdennis@gmail.com>
  • Loading branch information
denniskniep authored Dec 31, 2024
1 parent de36bd6 commit 3ad4d82
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 10 deletions.
1 change: 1 addition & 0 deletions docs/resources/user.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ resource "keycloak_user" "user_with_initial_password" {
- `identity_provider` - (Required) The name of the identity provider
- `user_id` - (Required) The ID of the user defined in the identity provider
- `user_name` - (Required) The user name of the user defined in the identity provider
- `import` - (Optional) When `true`, the user with the specified `username` is assumed to already exist, and it will be imported into state instead of being created. This attribute is useful when dealing with users that Keycloak creates automatically during realm creation, such as `admin`. Note, that the user will not be removed during destruction if `import` is `true`.

## Import

Expand Down
51 changes: 41 additions & 10 deletions provider/resource_keycloak_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"github.com/imdario/mergo"
"strings"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
Expand Down Expand Up @@ -114,6 +115,12 @@ func resourceKeycloakUser() *schema.Resource {
Optional: true,
Default: true,
},
"import": {
Type: schema.TypeBool,
Optional: true,
Default: false,
ForceNew: true,
},
},
}
}
Expand Down Expand Up @@ -204,17 +211,36 @@ func resourceKeycloakUserCreate(ctx context.Context, data *schema.ResourceData,

user := mapFromDataToUser(data)

err := keycloakClient.NewUser(ctx, user)
if err != nil {
return diag.FromErr(err)
}
if !data.Get("import").(bool) {
err := keycloakClient.NewUser(ctx, user)
if err != nil {
return diag.FromErr(err)
}

v, isInitialPasswordSet := data.GetOk("initial_password")
if isInitialPasswordSet {
passwordBlock := v.([]interface{})[0].(map[string]interface{})
passwordValue := passwordBlock["value"].(string)
isPasswordTemporary := passwordBlock["temporary"].(bool)
err := keycloakClient.ResetUserPassword(ctx, user.RealmId, user.Id, passwordValue, isPasswordTemporary)
v, isInitialPasswordSet := data.GetOk("initial_password")
if isInitialPasswordSet {
passwordBlock := v.([]interface{})[0].(map[string]interface{})
passwordValue := passwordBlock["value"].(string)
isPasswordTemporary := passwordBlock["temporary"].(bool)
err := keycloakClient.ResetUserPassword(ctx, user.RealmId, user.Id, passwordValue, isPasswordTemporary)
if err != nil {
return diag.FromErr(err)
}
}
} else {
username := data.Get("username").(string)
existingUser, err := keycloakClient.GetUserByUsername(ctx, data.Get("realm_id").(string), username)
if err != nil {
return diag.FromErr(err)
}
if existingUser == nil {
return diag.FromErr(fmt.Errorf("no user found for username %s", username))
}

if err = mergo.Merge(user, existingUser); err != nil {
return diag.FromErr(err)
}
err = keycloakClient.UpdateUser(ctx, user)
if err != nil {
return diag.FromErr(err)
}
Expand Down Expand Up @@ -257,6 +283,10 @@ func resourceKeycloakUserUpdate(ctx context.Context, data *schema.ResourceData,
}

func resourceKeycloakUserDelete(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
if data.Get("import").(bool) {
return nil
}

keycloakClient := meta.(*keycloak.KeycloakClient)

realmId := data.Get("realm_id").(string)
Expand All @@ -279,6 +309,7 @@ func resourceKeycloakUserImport(ctx context.Context, d *schema.ResourceData, met
}

d.Set("realm_id", parts[0])
d.Set("import", false)
d.SetId(parts[1])

diagnostics := resourceKeycloakUserRead(ctx, d, meta)
Expand Down
68 changes: 68 additions & 0 deletions provider/resource_keycloak_user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,61 @@ func TestAccKeycloakUser_federatedLink(t *testing.T) {
})
}

func TestAccKeycloakUser_import(t *testing.T) {
t.Parallel()

resource.Test(t, resource.TestCase{
ProviderFactories: testAccProviderFactories,
PreCheck: func() { testAccPreCheck(t) },
CheckDestroy: testAccCheckKeycloakUserNotDestroyed(),
Steps: []resource.TestStep{
{
Config: testKeycloakUser_import("master", "non-existing-username"),
ExpectError: regexp.MustCompile("no user found for username non-existing-username"),
},
{
Config: testKeycloakUser_import("master", "service-account-terraform"),
Check: testAccCheckKeycloakUserExistsWithUsername("keycloak_user.user", "service-account-terraform"),
},
},
})
}

func testAccCheckKeycloakUserNotDestroyed() resource.TestCheckFunc {
return func(s *terraform.State) error {
for _, rs := range s.RootModule().Resources {
if rs.Type != "keycloak_user" {
continue
}

id := rs.Primary.ID
realm := rs.Primary.Attributes["realm_id"]

user, _ := keycloakClient.GetUser(testCtx, realm, id)
if user == nil {
return fmt.Errorf("user %s does not exists", id)
}
}

return nil
}
}

func testAccCheckKeycloakUserExistsWithUsername(resourceName, username string) resource.TestCheckFunc {
return func(s *terraform.State) error {
user, err := getUserFromState(s, resourceName)
if err != nil {
return err
}

if user.Username != username {
return fmt.Errorf("no user found for username %s", username)
}

return nil
}
}

func testAccCheckKeycloakUserHasFederationLinkWithSourceUserName(resourceName, sourceUserName string) resource.TestCheckFunc {
return func(s *terraform.State) error {
fetchedUser, err := getUserFromState(s, resourceName)
Expand Down Expand Up @@ -612,3 +667,16 @@ resource "keycloak_user" "destination_user" {
}
`, userProfile, sourceRealmUserName, dependsOn, destinationRealmId, dependsOn)
}

func testKeycloakUser_import(realmId, username string) string {
return fmt.Sprintf(`
data "keycloak_realm" "realm" {
realm = "%s"
}
resource "keycloak_user" "user" {
realm_id = data.keycloak_realm.realm.id
username = "%s"
import = "true"
}
`, realmId, username)
}

0 comments on commit 3ad4d82

Please sign in to comment.