Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Import Role by Name #206

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

denniskniep
Copy link
Contributor

@denniskniep denniskniep commented Jan 13, 2025

Description of your changes

There is a feature request around how to import objects via <realm/name> instead of < uuid >

see: #126

There are already some workarounds in place:

  1. Using terraform import property (only a few resources supports that!)
  2. Using crossplane functions to import the built-in objects of keycloak (see here)

This is a proposal how to import resources by name. Code changes are only affecting role resource as an example and foundation for discussions.

We could use a custom implementation of ExternalName config (see here and here)

  • GetIDFn: Use defined props to lookup the Id used in the specific keycloak instance for that resource (i.e. by realmId, clientId & roleId). This could be done by using the terraform projects´s keycloakClient (i.e. kcClient.GetRoleByName(ctx, realmId.(string), clientId.(string), name.(string)))
  • GetExternalNameFn: Builds the ExternalName based on deterministic props (i.e. <realmId>/<clientId>/<roleId>)

What do you think about this approach?

Topics to clarify:

  • External Name and some props are overlapping. In case of role resource realmId, clientId & roleId are specified in properties as well as in external name. As far as I know there is a concept to mitigate that: OmittedFields & SetIdentifierArgumentFn. Issue with that is, that we can´t specify ClientIDRef which is a big downside!
  • Renewing tf-state creates new resource instead of updating existing when renaming one of the identifying properties

Fixes #126

@Breee
Copy link
Collaborator

Breee commented Jan 22, 2025

i like the idea.

Importing by <realmId>/<clientId>/<roleId> would be perfect, especially if there are no weird keycloak UUIDS involved.

Changing this might break this function:

https://gitlab.com/corewire/images/crossplane/function-keycloak-builtin-objects

but we have to test it

@denniskniep
Copy link
Contributor Author

denniskniep commented Jan 30, 2025

@Breee I stumbled upon an case where the change lead to probably unwanted/unexpected behavior:

When one of the identifying properties are changed i.e. realmId or clientId in the OidcClient.
and
the provider pod is not currently running and started after that change (or the tf-state in workspace is renewed for any different reason)
then
a new OidcClient will be created and the old is left over.

Examples:

  1. when provider pod is running and therefore tf-state is persisted in workspace. Renaming ClientId property from abc to 123 will result in an update of the existing client
  2. when provider pod is not running. Renaming ClientId property from abc to 123. Then starting the provider pod will result in an newly created client 123 and the old client abc still exists.

What if we still maintain the UUID´s in external-name, but if UUID is empty or resource with that UUID is not existing, then utilize the identifying properties to retrieve the UUID?

We could apply the following logic:

  1. Check if external-name is set
  2. If external-name is set: try to resolve resource by UUID (get UUID from external-name)
  3. If resource can be resolved by UUID: return UUID
  4. If resource can NOT be resolved by UUID OR external-name is NOT set: try to resolve resource by identifying properties (realmId, clientId, etc.)
  5. If resource can be resolved by identifying properties: return UUID
  6. return indicator to create resource, because it does not exists

@Breee
Copy link
Collaborator

Breee commented Jan 31, 2025

yes

@Breee I stumbled upon an case where the change lead to probably unwanted/unexpected behavior:

When one of the identifying properties are changed i.e. realmId or clientId in the OidcClient. and the provider pod is not currently running and started after that change (or the tf-state in workspace is renewed for any different reason) then a new OidcClient will be created and the old is left over.

Examples:

  1. when provider pod is running and therefore tf-state is persisted in workspace. Renaming ClientId property from abc to 123 will result in an update of the existing client
  2. when provider pod is not running. Renaming ClientId property from abc to 123. Then starting the provider pod will result in an newly created client 123 and the old client abc still exists.

What if we still maintain the UUID´s in external-name, but if UUID is empty or resource with that UUID is not existing, then utilize the identifying properties to retrieve the UUID?

We could apply the following logic:

  1. Check if external-name is set
  2. If external-name is set: try to resolve resource by UUID (get UUID from external-name)
  3. If resource can be resolved by UUID: return UUID
  4. If resource can NOT be resolved by UUID OR external-name is NOT set: try to resolve resource by identifying properties (realmId, clientId, etc.)
  5. If resource can be resolved by identifying properties: return UUID
  6. return indicator to create resource, because it does not exists

Yes, good catch. If people mess with the objects in keycloak that's probably in more cases a problem (it's a shame that they can not be made immutable on the UI)

I like the logic you proposed.

We then also have to test, how this affects existing deployments

Fixed that renewing tf-state creates new resource
instead of updating existing when renaming
one of the identifying properties

Signed-off-by: Dennis Kniep <[email protected]>
@denniskniep
Copy link
Contributor Author

@Breee seems to be that make generate throws errors when I am using generated types in config module:

	r := x.RoleParameters{}
	err = utils.UnmarshalTerraformParamsToObject(parameters, &r)
	if err != nil {
		return "", err
	}

	if r.RealmID == nil {
		return "", errors.New("realmId not set")
	}

	if r.Name == nil {
		return "", errors.New("name not set")
	}

	clientId := ""
	if r.ClientID != nil {
		clientId = *r.ClientID
	}

Any ideas how to solve that? Else I will switch back to my previous approach:

        realmID, realmIdExists := parameters["realm_id"]
	if !realmIdExists {
		return "", errors.New("realmId not set")
	}

	name, nameExists := parameters["name"]
	if !nameExists {
		return "", errors.New("name not set")
	}

	clientID, clientIdExists := parameters["client_id"]
	if !clientIdExists {
		clientID = ""
	}

...then we won´t get any compiler errors once those used attribute names are changed.

  1. But I guess that is a very rare case for identifying properties.
  2. Thats already the case in provider.go.KnownReferencers()

@denniskniep denniskniep force-pushed the feature/import-role-by-name branch from 27d3742 to a1ec3ff Compare February 2, 2025 21:05
+ Fixed bug keycloak_saml_client_default_scopes.client_id point to keycloak_saml_client
+ Added Ref fields for clientpolicy, grouppolicy, rolepolicy, userpolicy

Signed-off-by: Dennis Kniep <[email protected]>
@denniskniep
Copy link
Contributor Author

@Breee
I am getting closer...Only the ldap-resources are missing.

Can you already have a look at it?
BTW: It was necessary to access a private KeycloakClient method, because not every exposed API is implemented by the client. I would like to tackle this once we have a final version. Maybe terraform-keycloak-provider is willing to accept those additional methods.

How should we proceed? Does it make sense to release a new major version along with that feature to highlight the change?
Furthermore we could release it as ReleaseCandidate/PreRelease i.e "2.0.0-rc.1" and improve it further, but already ask for input from the community and offer users an opportunity to experiment with that changed approach.

What do you think?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

feature: Import objects via <realm/name> instead of <uuid>
2 participants