From 9b50840145eee44571fbaee242ef3d14ff919fce Mon Sep 17 00:00:00 2001 From: helmi77 Date: Tue, 26 Jul 2022 14:23:28 +0200 Subject: [PATCH] Upgrade kubernetes-client library to 6.0.0 --- pom.xml | 18 ++- .../keycloak/controller/Application.java | 4 +- .../controller/KubernetesController.java | 32 ++--- .../controller/client/ClientController.java | 29 +++-- .../controller/client/ClientMapper.java | 13 ++ .../controller/client/ClientResource.java | 111 ++++-------------- .../client/ClientResourceStatus.java | 9 ++ .../controller/client/ClientRole.java | 12 ++ .../controller/client/ClientSpec.java | 32 +++++ .../client/ServiceAccountRoleAssignment.java | 4 - .../clientscope/ClientScopeController.java | 26 ++-- .../clientscope/ClientScopeMapper.java | 12 ++ .../clientscope/ClientScopeResource.java | 95 ++++----------- .../ClientScopeResourceStatus.java | 8 ++ .../clientscope/ClientScopeSpec.java | 20 ++++ .../keycloak/KeycloakController.java | 23 ++-- .../controller/keycloak/KeycloakResource.java | 77 ++++-------- .../keycloak/KeycloakResourceStatus.java | 17 +++ .../controller/keycloak/KeycloakSpec.java | 21 ++++ .../controller/realm/RealmController.java | 18 +-- .../controller/realm/RealmResource.java | 72 +++--------- .../controller/realm/RealmResourceStatus.java | 8 ++ .../keycloak/controller/realm/RealmSpec.java | 14 +++ .../AssignedClientScopesSyncerTest.java | 2 +- ...rviceAccountRoleAssignmentTestObjects.java | 18 +-- 25 files changed, 344 insertions(+), 351 deletions(-) create mode 100644 src/main/java/com/kiwigrid/keycloak/controller/client/ClientMapper.java create mode 100644 src/main/java/com/kiwigrid/keycloak/controller/client/ClientResourceStatus.java create mode 100644 src/main/java/com/kiwigrid/keycloak/controller/client/ClientRole.java create mode 100644 src/main/java/com/kiwigrid/keycloak/controller/client/ClientSpec.java create mode 100644 src/main/java/com/kiwigrid/keycloak/controller/clientscope/ClientScopeMapper.java create mode 100644 src/main/java/com/kiwigrid/keycloak/controller/clientscope/ClientScopeResourceStatus.java create mode 100644 src/main/java/com/kiwigrid/keycloak/controller/clientscope/ClientScopeSpec.java create mode 100644 src/main/java/com/kiwigrid/keycloak/controller/keycloak/KeycloakResourceStatus.java create mode 100644 src/main/java/com/kiwigrid/keycloak/controller/keycloak/KeycloakSpec.java create mode 100644 src/main/java/com/kiwigrid/keycloak/controller/realm/RealmResourceStatus.java create mode 100644 src/main/java/com/kiwigrid/keycloak/controller/realm/RealmSpec.java diff --git a/pom.xml b/pom.xml index 8e34afd..4b39390 100644 --- a/pom.xml +++ b/pom.xml @@ -41,13 +41,14 @@ + 3.12.0 1.3.7 11.0.2 3.6.3.Final 1.2.3 0.1.5 - 4.9.2 - 1.18.16 + 6.0.0 + 1.18.24 1.4.10 3.1.2 3.7.7 @@ -82,6 +83,12 @@ + + org.apache.commons + commons-lang3 + ${version.commons} + + io.micronaut @@ -150,6 +157,13 @@ io.fabric8 kubernetes-client ${version.k8s-client} + runtime + + + io.fabric8 + kubernetes-client-api + ${version.k8s-client} + compile diff --git a/src/main/java/com/kiwigrid/keycloak/controller/Application.java b/src/main/java/com/kiwigrid/keycloak/controller/Application.java index b544733..76e115c 100644 --- a/src/main/java/com/kiwigrid/keycloak/controller/Application.java +++ b/src/main/java/com/kiwigrid/keycloak/controller/Application.java @@ -1,7 +1,7 @@ package com.kiwigrid.keycloak.controller; -import io.fabric8.kubernetes.client.DefaultKubernetesClient; import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.KubernetesClientBuilder; import io.micronaut.context.annotation.Bean; import io.micronaut.context.annotation.Factory; import io.micronaut.runtime.Micronaut; @@ -17,6 +17,6 @@ public static void main(String[] args) { @Singleton @Bean(preDestroy = "close") KubernetesClient kubernetesClient() { - return new DefaultKubernetesClient(); + return new KubernetesClientBuilder().build(); } } \ No newline at end of file diff --git a/src/main/java/com/kiwigrid/keycloak/controller/KubernetesController.java b/src/main/java/com/kiwigrid/keycloak/controller/KubernetesController.java index 51d0e93..4b18397 100644 --- a/src/main/java/com/kiwigrid/keycloak/controller/KubernetesController.java +++ b/src/main/java/com/kiwigrid/keycloak/controller/KubernetesController.java @@ -3,34 +3,39 @@ import java.util.HashMap; import java.util.Map; -import io.fabric8.kubernetes.api.model.apiextensions.CustomResourceDefinition; +import io.fabric8.kubernetes.api.model.KubernetesResourceList; import io.fabric8.kubernetes.client.*; import io.fabric8.kubernetes.client.dsl.MixedOperation; +import io.fabric8.kubernetes.client.dsl.Resource; +import io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext; import io.fabric8.kubernetes.internal.KubernetesDeserializer; import io.micronaut.context.annotation.Value; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public abstract class KubernetesController implements Watcher { +public abstract class KubernetesController> implements Watcher { protected final Logger log = LoggerFactory.getLogger(getClass()); protected final KubernetesClient kubernetes; protected final Map resources = new HashMap<>(); - protected final MixedOperation, ?, ?> customResources; + protected final MixedOperation, Resource> customResources; @Value("${controller.namespaced:true}") protected boolean namespaced; - protected KubernetesController(KubernetesClient kubernetes, - CustomResourceDefinition crd, - Class type, - Class> listType, - Class> doneableType) { + protected KubernetesController( + KubernetesClient kubernetes, + Class type + ) { this.kubernetes = kubernetes; - this.customResources = kubernetes.customResources(crd, type, listType, doneableType); + this.customResources = kubernetes.resources(type); + + CustomResourceDefinitionContext context = CustomResourceDefinitionContext.fromCustomResourceType(type); + KubernetesDeserializer.registerCustomKind( - crd.getSpec().getGroup() + "/" + crd.getSpec().getVersion(), - crd.getSpec().getNames().getKind(), type); + context.getGroup() + "/" + context.getVersion(), + context.getKind(), + type); } public abstract void apply(T resource); @@ -43,7 +48,7 @@ protected KubernetesController(KubernetesClient kubernetes, public Watch watch() { log.trace("Start watcher."); - if(namespaced) { + if (namespaced) { return customResources.watch(this); } return customResources.inAnyNamespace().watch(this); @@ -51,7 +56,6 @@ public Watch watch() { @Override public void eventReceived(Action action, T resource) { - var id = resource.getMetadata().getNamespace() + "/" + resource.getMetadata().getName(); log.trace("Received event {} for {}.", action, id); @@ -73,7 +77,7 @@ public void eventReceived(Action action, T resource) { } @Override - public void onClose(KubernetesClientException cause) { + public void onClose(WatcherException cause) { if (cause != null) { log.error("Unexpectedly closed watcher.", cause); } diff --git a/src/main/java/com/kiwigrid/keycloak/controller/client/ClientController.java b/src/main/java/com/kiwigrid/keycloak/controller/client/ClientController.java index 4fa3f49..1231ef3 100644 --- a/src/main/java/com/kiwigrid/keycloak/controller/client/ClientController.java +++ b/src/main/java/com/kiwigrid/keycloak/controller/client/ClientController.java @@ -11,6 +11,9 @@ import com.kiwigrid.keycloak.controller.KubernetesController; import com.kiwigrid.keycloak.controller.keycloak.KeycloakController; + +import io.fabric8.kubernetes.api.model.Secret; +import io.fabric8.kubernetes.api.model.SecretBuilder; import io.fabric8.kubernetes.client.KubernetesClient; import javax.inject.Singleton; import javax.ws.rs.NotFoundException; @@ -34,8 +37,7 @@ public ClientController(KeycloakController keycloak, AssignedClientScopesSyncer assignedClientScopesSyncer, ServiceAccountRoleAssignmentSynchronizer serviceAccountRoleAssignmentSynchronizer) { - super(kubernetes, ClientResource.DEFINITION, ClientResource.class, ClientResource.ClientResourceList.class, - ClientResource.ClientResourceDoneable.class); + super(kubernetes, ClientResource.class); this.keycloak = keycloak; this.assignedClientScopesSyncer = assignedClientScopesSyncer; this.serviceAccountRoleAssignmentSynchronizer = serviceAccountRoleAssignmentSynchronizer; @@ -138,7 +140,7 @@ public void delete(ClientResource clientResource) { @Override public void retry() { customResources.list().getItems().stream() - .filter(r -> r.getStatus().getError() != null) + .filter(r -> r.getStatus() != null && r.getStatus().getError() != null) .forEach(this::apply); } @@ -146,6 +148,9 @@ public void retry() { void updateStatus(ClientResource clientResource, String error) { + if (clientResource.getStatus() == null) { + clientResource.setStatus(new ClientResourceStatus()); + } // skip if nothing changed if (clientResource.getStatus().getTimestamp() != null && Objects.equals(clientResource.getStatus().getError(), error)) { @@ -156,7 +161,8 @@ void updateStatus(ClientResource clientResource, String error) { clientResource.getStatus().setError(error); clientResource.getStatus().setTimestamp(Instant.now().truncatedTo(ChronoUnit.SECONDS).toString()); - customResources.withName(clientResource.getMetadata().getName()).replace(clientResource); + + kubernetes.resource(clientResource).replace(); } Optional realm(String keycloakName, String realmName) { @@ -173,7 +179,7 @@ Optional realm(String keycloakName, String realmName) { }); } - boolean map(boolean create, ClientResource.ClientResourceSpec spec, ClientRepresentation client) { + boolean map(boolean create, ClientSpec spec, ClientRepresentation client) { var changed = false; if (changed |= changed(create, spec, "name", spec.getName(), client.getName())) { @@ -240,7 +246,7 @@ boolean map(boolean create, ClientResource.ClientResourceSpec spec, ClientRepres return changed; } - boolean changed(boolean create, ClientResource.ClientResourceSpec spec, String name, Object specValue, Object clientValue) { + boolean changed(boolean create, ClientSpec spec, String name, Object specValue, Object clientValue) { boolean changed = specValue != null && !specValue.equals(clientValue); if (changed) { if (create) { @@ -279,12 +285,15 @@ void manageSecret(RealmResource realmResource, String clientUuid, ClientResource if (kubernetesSecret == null) { - kubernetesSecretsInNamespace.createOrReplaceWithNew() + Secret newSecret = new SecretBuilder() .withNewMetadata() .withName(secretName) .endMetadata() .addToData(secretKey, Base64.getEncoder().encodeToString(keycloakSecretValue.getBytes())) - .done(); + .build(); + + var secretResource = kubernetes.resource(newSecret).inNamespace(secretNamespace); + secretResource.createOrReplace(); log.info("{}/{}/{}: kubernetes secret {}/{} created", keycloak, realm, clientId, secretNamespace, secretName); @@ -355,7 +364,7 @@ void manageMapper(RealmResource realmResource, String clientUuid, ClientResource // remove obsolete mappers - var names = specMappers.stream().map(ClientResource.ClientMapper::getName).collect(Collectors.toSet()); + var names = specMappers.stream().map(ClientMapper::getName).collect(Collectors.toSet()); for (var mapper : keycloakMappers) { if (!names.contains(mapper.getName())) { keycloakResource.delete(mapper.getId()); @@ -383,7 +392,7 @@ private void manageRoles(RealmResource realmResource, String clientUuid, ClientR // remove obsolete roles - var specRoleNames = specRoles.stream().map(ClientResource.ClientRole::getName).collect(Collectors.toSet()); + var specRoleNames = specRoles.stream().map(ClientRole::getName).collect(Collectors.toSet()); for (var clientRoleRepresentation : clientRoleRepresentations) { if (!specRoleNames.contains(clientRoleRepresentation.getName())) { clientRolesResource.deleteRole(clientRoleRepresentation.getName()); diff --git a/src/main/java/com/kiwigrid/keycloak/controller/client/ClientMapper.java b/src/main/java/com/kiwigrid/keycloak/controller/client/ClientMapper.java new file mode 100644 index 0000000..2cb10a4 --- /dev/null +++ b/src/main/java/com/kiwigrid/keycloak/controller/client/ClientMapper.java @@ -0,0 +1,13 @@ +package com.kiwigrid.keycloak.controller.client; + +import java.util.Map; + +@lombok.Getter +@lombok.Setter +@lombok.EqualsAndHashCode +public class ClientMapper { + + private String name; + private String protocolMapper; + private Map config; +} \ No newline at end of file diff --git a/src/main/java/com/kiwigrid/keycloak/controller/client/ClientResource.java b/src/main/java/com/kiwigrid/keycloak/controller/client/ClientResource.java index 102b0fd..a6462e2 100644 --- a/src/main/java/com/kiwigrid/keycloak/controller/client/ClientResource.java +++ b/src/main/java/com/kiwigrid/keycloak/controller/client/ClientResource.java @@ -1,100 +1,29 @@ package com.kiwigrid.keycloak.controller.client; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; -import io.fabric8.kubernetes.api.builder.Function; -import io.fabric8.kubernetes.api.model.apiextensions.CustomResourceDefinition; -import io.fabric8.kubernetes.api.model.apiextensions.CustomResourceDefinitionBuilder; -import io.fabric8.kubernetes.api.model.apiextensions.CustomResourceDefinitionSpec; -import io.fabric8.kubernetes.api.model.apiextensions.CustomResourceDefinitionStatus; +import io.fabric8.kubernetes.api.model.Namespaced; import io.fabric8.kubernetes.client.CustomResource; -import io.fabric8.kubernetes.client.CustomResourceDoneable; -import io.fabric8.kubernetes.client.CustomResourceList; +import io.fabric8.kubernetes.model.annotation.Group; +import io.fabric8.kubernetes.model.annotation.Kind; +import io.fabric8.kubernetes.model.annotation.Plural; +import io.fabric8.kubernetes.model.annotation.ShortNames; +import io.fabric8.kubernetes.model.annotation.Singular; +import io.fabric8.kubernetes.model.annotation.Version; -@SuppressWarnings("serial") @lombok.Getter @lombok.Setter -@lombok.EqualsAndHashCode(of = "spec", callSuper = false) -public class ClientResource extends CustomResource { - - public static final CustomResourceDefinition DEFINITION = new CustomResourceDefinitionBuilder() - .withNewSpec() - .withScope("Namespaced") - .withGroup("k8s.kiwigrid.com") - .withVersion("v1beta1") - .withNewNames() - .withKind("KeycloakClient") - .withSingular("keycloakclient") - .withPlural("keycloakclients") - .withShortNames("kcc") - .endNames() - .endSpec().build(); - - private ClientResourceSpec spec = new ClientResourceSpec(); - private ClientResourceStatus status = new ClientResourceStatus(); - - @lombok.Getter - @lombok.Setter - @lombok.EqualsAndHashCode(callSuper = false) - public static class ClientResourceSpec extends CustomResourceDefinitionSpec { - - private String keycloak = "default"; - private String realm; - private String clientId; - private ClientType clientType; - private String name; - private Boolean enabled; - private Boolean directAccessGrantsEnabled; - private Boolean standardFlowEnabled; - private Boolean implicitFlowEnabled; - private Boolean serviceAccountsEnabled; - private List defaultRoles = new ArrayList<>(); - private List defaultClientScopes = new ArrayList<>(); - private List optionalClientScopes = new ArrayList<>(); - private List webOrigins = new ArrayList<>(); - private List redirectUris = new ArrayList<>(); - private String secretNamespace; - private String secretName; - private String secretKey = "secret"; - private List mapper = new ArrayList<>(); - private List roles = new ArrayList<>(); - private List serviceAccountRealmRoles = new ArrayList<>(); - } - - @lombok.Getter - @lombok.Setter - @lombok.EqualsAndHashCode - public static class ClientMapper { - - private String name; - private String protocolMapper; - private Map config; - } - - @lombok.Getter - @lombok.Setter - @lombok.EqualsAndHashCode - public static class ClientRole { - - private String name; - private List realmRoles; - } - - @lombok.Getter - @lombok.Setter - public static class ClientResourceStatus extends CustomResourceDefinitionStatus { - - private String timestamp; - private String error; - } - - public static class ClientResourceList extends CustomResourceList {} - - public static class ClientResourceDoneable extends CustomResourceDoneable { - public ClientResourceDoneable(ClientResource resource, Function function) { - super(resource, function); - } +@lombok.EqualsAndHashCode(callSuper = false) +@Group("k8s.kiwigrid.com") +@Version("v1beta1") +@Kind("KeycloakClient") +@Singular("keycloakclient") +@Plural("keycloakclients") +@ShortNames("kcc") +public class ClientResource extends CustomResource implements Namespaced { + + public String toString() { + return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE); } } \ No newline at end of file diff --git a/src/main/java/com/kiwigrid/keycloak/controller/client/ClientResourceStatus.java b/src/main/java/com/kiwigrid/keycloak/controller/client/ClientResourceStatus.java new file mode 100644 index 0000000..ab17189 --- /dev/null +++ b/src/main/java/com/kiwigrid/keycloak/controller/client/ClientResourceStatus.java @@ -0,0 +1,9 @@ +package com.kiwigrid.keycloak.controller.client; + +@lombok.Getter +@lombok.Setter +public class ClientResourceStatus { + + private String timestamp; + private String error; +} \ No newline at end of file diff --git a/src/main/java/com/kiwigrid/keycloak/controller/client/ClientRole.java b/src/main/java/com/kiwigrid/keycloak/controller/client/ClientRole.java new file mode 100644 index 0000000..07b8ff4 --- /dev/null +++ b/src/main/java/com/kiwigrid/keycloak/controller/client/ClientRole.java @@ -0,0 +1,12 @@ +package com.kiwigrid.keycloak.controller.client; + +import java.util.List; + +@lombok.Getter +@lombok.Setter +@lombok.EqualsAndHashCode +public class ClientRole { + + private String name; + private List realmRoles; +} diff --git a/src/main/java/com/kiwigrid/keycloak/controller/client/ClientSpec.java b/src/main/java/com/kiwigrid/keycloak/controller/client/ClientSpec.java new file mode 100644 index 0000000..97df261 --- /dev/null +++ b/src/main/java/com/kiwigrid/keycloak/controller/client/ClientSpec.java @@ -0,0 +1,32 @@ +package com.kiwigrid.keycloak.controller.client; + +import java.util.ArrayList; +import java.util.List; + +@lombok.Getter +@lombok.Setter +@lombok.EqualsAndHashCode(callSuper = false) +public class ClientSpec { + private String keycloak = "default"; + private String realm; + private String clientId; + private ClientType clientType; + private String name; + private Boolean enabled; + private Boolean directAccessGrantsEnabled; + private Boolean standardFlowEnabled; + private Boolean implicitFlowEnabled; + private Boolean serviceAccountsEnabled; + private List defaultRoles = new ArrayList<>(); + private List defaultClientScopes = new ArrayList<>(); + private List optionalClientScopes = new ArrayList<>(); + private List webOrigins = new ArrayList<>(); + private List redirectUris = new ArrayList<>(); + private String secretNamespace; + private String secretName; + private String secretKey = "secret"; + private List mapper = new ArrayList<>(); + private List roles = new ArrayList<>(); + private List serviceAccountRealmRoles = new ArrayList<>(); + +} \ No newline at end of file diff --git a/src/main/java/com/kiwigrid/keycloak/controller/client/ServiceAccountRoleAssignment.java b/src/main/java/com/kiwigrid/keycloak/controller/client/ServiceAccountRoleAssignment.java index a8afa69..3acc0ee 100644 --- a/src/main/java/com/kiwigrid/keycloak/controller/client/ServiceAccountRoleAssignment.java +++ b/src/main/java/com/kiwigrid/keycloak/controller/client/ServiceAccountRoleAssignment.java @@ -7,13 +7,9 @@ import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.admin.client.resource.RoleMappingResource; import org.keycloak.representations.idm.RoleRepresentation; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; @Singleton public class ServiceAccountRoleAssignment { - private final Logger LOG = LoggerFactory.getLogger(getClass()); - public List findAssignedRolesToRemoveWith(RealmResource realmResource, ClientResource clientResource, String clientUuid) { diff --git a/src/main/java/com/kiwigrid/keycloak/controller/clientscope/ClientScopeController.java b/src/main/java/com/kiwigrid/keycloak/controller/clientscope/ClientScopeController.java index 3cfecee..0f06866 100644 --- a/src/main/java/com/kiwigrid/keycloak/controller/clientscope/ClientScopeController.java +++ b/src/main/java/com/kiwigrid/keycloak/controller/clientscope/ClientScopeController.java @@ -29,8 +29,7 @@ public class ClientScopeController extends KubernetesController keycloakMappers = keycloakResource.getMappers(); - List specMappers = clientScopeResource.getSpec().getMappers(); + List specMappers = clientScopeResource.getSpec().getMappers(); handleSpecMappers(keycloak, realm, clientScopeName, keycloakResource, keycloakMappers, specMappers); removeObsoleteMappers(keycloak, realm, clientScopeName, keycloakResource, keycloakMappers, specMappers); } - private void handleSpecMappers(String keycloak, String realm, String clientScopeName, ProtocolMappersResource keycloakResource, List keycloakMappers, List specMappers) { - for (ClientScopeResource.ClientScopeMapper specMapper : specMappers) { + private void handleSpecMappers(String keycloak, String realm, String clientScopeName, ProtocolMappersResource keycloakResource, List keycloakMappers, List specMappers) { + for (ClientScopeMapper specMapper : specMappers) { String mapperName = specMapper.getName(); Optional protocolMapperOptional = keycloakMappers.stream().filter(m -> m.getName().equals(mapperName)).findFirst(); if (protocolMapperOptional.isEmpty()) { @@ -137,8 +141,8 @@ private void handleSpecMappers(String keycloak, String realm, String clientScope } } - private void removeObsoleteMappers(String keycloak, String realm, String clientScopeName, ProtocolMappersResource keycloakResource, List keycloakMappers, List specMappers) { - Set names = specMappers.stream().map(ClientScopeResource.ClientScopeMapper::getName).collect(Collectors.toSet()); + private void removeObsoleteMappers(String keycloak, String realm, String clientScopeName, ProtocolMappersResource keycloakResource, List keycloakMappers, List specMappers) { + Set names = specMappers.stream().map(ClientScopeMapper::getName).collect(Collectors.toSet()); for (ProtocolMapperRepresentation mapper : keycloakMappers) { if (!names.contains(mapper.getName())) { keycloakResource.delete(mapper.getId()); @@ -205,11 +209,11 @@ private Optional getClientScopeFromRealm(String clien @Override public void retry() { customResources.list().getItems().stream() - .filter(r -> r.getStatus().getError() != null) + .filter(r -> r.getStatus() != null && r.getStatus().getError() != null) .forEach(this::apply); } - boolean map(ClientScopeResource.ClientScopeResourceSpec sourceSpec, ClientScopeRepresentation targetRepresentation, boolean create) { + boolean map(ClientScopeSpec sourceSpec, ClientScopeRepresentation targetRepresentation, boolean create) { boolean changed = false; if (changed |= changed(create, sourceSpec, "name", sourceSpec.getName(), targetRepresentation.getName())) { @@ -231,7 +235,7 @@ boolean map(ClientScopeResource.ClientScopeResourceSpec sourceSpec, ClientScopeR return changed; } - boolean changed(boolean create, ClientScopeResource.ClientScopeResourceSpec spec, String name, Object specValue, Object clientValue) { + boolean changed(boolean create, ClientScopeSpec spec, String name, Object specValue, Object clientValue) { boolean changed = specValue != null && !specValue.equals(clientValue); if (changed) { if (create) { diff --git a/src/main/java/com/kiwigrid/keycloak/controller/clientscope/ClientScopeMapper.java b/src/main/java/com/kiwigrid/keycloak/controller/clientscope/ClientScopeMapper.java new file mode 100644 index 0000000..605eaed --- /dev/null +++ b/src/main/java/com/kiwigrid/keycloak/controller/clientscope/ClientScopeMapper.java @@ -0,0 +1,12 @@ +package com.kiwigrid.keycloak.controller.clientscope; + +import java.util.Map; + +@lombok.Getter +@lombok.Setter +@lombok.EqualsAndHashCode +public class ClientScopeMapper { + private String name; + private String protocolMapper; + private Map config; +} diff --git a/src/main/java/com/kiwigrid/keycloak/controller/clientscope/ClientScopeResource.java b/src/main/java/com/kiwigrid/keycloak/controller/clientscope/ClientScopeResource.java index f335533..f1000e8 100644 --- a/src/main/java/com/kiwigrid/keycloak/controller/clientscope/ClientScopeResource.java +++ b/src/main/java/com/kiwigrid/keycloak/controller/clientscope/ClientScopeResource.java @@ -1,80 +1,29 @@ package com.kiwigrid.keycloak.controller.clientscope; -import com.kiwigrid.keycloak.controller.client.ClientResource; -import com.kiwigrid.keycloak.controller.client.ClientType; -import io.fabric8.kubernetes.api.builder.Function; -import io.fabric8.kubernetes.api.model.apiextensions.CustomResourceDefinition; -import io.fabric8.kubernetes.api.model.apiextensions.CustomResourceDefinitionBuilder; -import io.fabric8.kubernetes.api.model.apiextensions.CustomResourceDefinitionSpec; -import io.fabric8.kubernetes.api.model.apiextensions.CustomResourceDefinitionStatus; -import io.fabric8.kubernetes.client.CustomResource; -import io.fabric8.kubernetes.client.CustomResourceDoneable; -import io.fabric8.kubernetes.client.CustomResourceList; -import org.keycloak.representations.idm.ProtocolMapperRepresentation; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; +import io.fabric8.kubernetes.api.model.Namespaced; +import io.fabric8.kubernetes.client.CustomResource; +import io.fabric8.kubernetes.model.annotation.Group; +import io.fabric8.kubernetes.model.annotation.Kind; +import io.fabric8.kubernetes.model.annotation.Plural; +import io.fabric8.kubernetes.model.annotation.ShortNames; +import io.fabric8.kubernetes.model.annotation.Singular; +import io.fabric8.kubernetes.model.annotation.Version; @lombok.Getter @lombok.Setter -@lombok.EqualsAndHashCode(of = "spec", callSuper = false) -public class ClientScopeResource extends CustomResource { - - public static final CustomResourceDefinition DEFINITION = new CustomResourceDefinitionBuilder() - .withNewSpec() - .withScope("Namespaced") - .withGroup("k8s.kiwigrid.com") - .withVersion("v1beta1") - .withNewNames() - .withKind("KeycloakClientScope") - .withSingular("keycloakclientscope") - .withPlural("keycloakclientscopes") - .withShortNames("kccs") - .endNames() - .endSpec().build(); - - - private ClientScopeResource.ClientScopeResourceSpec spec = new ClientScopeResource.ClientScopeResourceSpec(); - private ClientScopeResource.ClientScopeResourceStatus status = new ClientScopeResource.ClientScopeResourceStatus(); - - @lombok.Getter - @lombok.Setter - @lombok.EqualsAndHashCode(callSuper = false) - public static class ClientScopeResourceSpec extends CustomResourceDefinitionSpec { - private String keycloak = "default"; - private String realm; - - private String id; - private String name; - private String description; - private List mappers = new ArrayList<>(); - private String protocol; - protected Map attributes; - } - - @lombok.Getter - @lombok.Setter - @lombok.EqualsAndHashCode - public static class ClientScopeMapper { - - private String name; - private String protocolMapper; - private Map config; - } - - @lombok.Getter - @lombok.Setter - public static class ClientScopeResourceStatus extends CustomResourceDefinitionStatus { - private String timestamp; - private String error; - } - - public static class ClientScopeResourceList extends CustomResourceList {} - - public static class ClientScopeResourceDoneable extends CustomResourceDoneable { - public ClientScopeResourceDoneable(ClientScopeResource resource, Function function) { - super(resource, function); - } - } +@lombok.EqualsAndHashCode(callSuper = false) +@Group("k8s.kiwigrid.com") +@Version("v1beta1") +@Kind("KeycloakClientScope") +@Singular("keycloakclientscope") +@Plural("keycloakclientscopes") +@ShortNames("kccs") +public class ClientScopeResource extends CustomResource implements Namespaced { + + public String toString() { + return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE); + } } diff --git a/src/main/java/com/kiwigrid/keycloak/controller/clientscope/ClientScopeResourceStatus.java b/src/main/java/com/kiwigrid/keycloak/controller/clientscope/ClientScopeResourceStatus.java new file mode 100644 index 0000000..661e379 --- /dev/null +++ b/src/main/java/com/kiwigrid/keycloak/controller/clientscope/ClientScopeResourceStatus.java @@ -0,0 +1,8 @@ +package com.kiwigrid.keycloak.controller.clientscope; + +@lombok.Getter +@lombok.Setter +public class ClientScopeResourceStatus { + private String timestamp; + private String error; +} diff --git a/src/main/java/com/kiwigrid/keycloak/controller/clientscope/ClientScopeSpec.java b/src/main/java/com/kiwigrid/keycloak/controller/clientscope/ClientScopeSpec.java new file mode 100644 index 0000000..5700a92 --- /dev/null +++ b/src/main/java/com/kiwigrid/keycloak/controller/clientscope/ClientScopeSpec.java @@ -0,0 +1,20 @@ +package com.kiwigrid.keycloak.controller.clientscope; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@lombok.Getter +@lombok.Setter +@lombok.EqualsAndHashCode(callSuper = false) +public class ClientScopeSpec { + private String keycloak = "default"; + private String realm; + + private String id; + private String name; + private String description; + private List mappers = new ArrayList<>(); + private String protocol; + protected Map attributes; +} diff --git a/src/main/java/com/kiwigrid/keycloak/controller/keycloak/KeycloakController.java b/src/main/java/com/kiwigrid/keycloak/controller/keycloak/KeycloakController.java index ed8d82a..16c4757 100644 --- a/src/main/java/com/kiwigrid/keycloak/controller/keycloak/KeycloakController.java +++ b/src/main/java/com/kiwigrid/keycloak/controller/keycloak/KeycloakController.java @@ -19,8 +19,7 @@ public class KeycloakController extends KubernetesController { final Map clients = new HashMap<>(); public KeycloakController(KubernetesClient kubernetes) { - super(kubernetes, KeycloakResource.DEFINITION, - KeycloakResource.class, KeycloakResource.KeycloakResourceList.class, KeycloakResource.KeycloakResourceDoneable.class); + super(kubernetes, KeycloakResource.class); } @Override @@ -28,6 +27,10 @@ public void apply(KeycloakResource resource) { var name = resource.getMetadata().getName(); var status = resource.getStatus(); + if (status == null) { + status = new KeycloakResourceStatus(); + resource.setStatus(status); + } try { @@ -38,11 +41,10 @@ public void apply(KeycloakResource resource) { log.info("Connected to {} in version {}.", name, status.getVersion()); } catch (RuntimeException e) { - + log.error(e.getMessage(), e); delete(resource); updateStatus(resource, resource.getStatus().getVersion(), e.getMessage()); log.warn("Connecting to {} failed: {}", name, status.getError()); - } } @@ -56,14 +58,14 @@ public void delete(KeycloakResource resource) { @Override public void retry() { - customResources.list().getItems().stream() - .filter(r -> r.getStatus().getError() != null) + customResources.inAnyNamespace().list().getItems().stream() + .filter(r -> r.getStatus() != null && r.getStatus().getError() != null) .forEach(this::apply); } public Optional get(String keycloak) { if (!clients.containsKey(keycloak)) { - customResources.list().getItems().stream() + customResources.inAnyNamespace().list().getItems().stream() .filter(r -> r.getMetadata().getName().equals(keycloak)) .forEach(this::apply); } @@ -74,6 +76,10 @@ public Optional get(String keycloak) { void updateStatus(KeycloakResource resource, String version, String error) { + if (resource.getStatus() == null) { + resource.setStatus(new KeycloakResourceStatus()); + } + // skip if nothing changed if (resource.getStatus().getTimestamp() != null @@ -87,7 +93,8 @@ void updateStatus(KeycloakResource resource, String version, String error) { resource.getStatus().setError(error); resource.getStatus().setTimestamp(Instant.now().truncatedTo(ChronoUnit.SECONDS).toString()); resource.getStatus().setVersion(version); - customResources.withName(resource.getMetadata().getName()).replace(resource); + + kubernetes.resource(resource).replace(); } Keycloak connect(KeycloakResource resource) { diff --git a/src/main/java/com/kiwigrid/keycloak/controller/keycloak/KeycloakResource.java b/src/main/java/com/kiwigrid/keycloak/controller/keycloak/KeycloakResource.java index a23c66d..9fe1e7d 100644 --- a/src/main/java/com/kiwigrid/keycloak/controller/keycloak/KeycloakResource.java +++ b/src/main/java/com/kiwigrid/keycloak/controller/keycloak/KeycloakResource.java @@ -1,65 +1,28 @@ package com.kiwigrid.keycloak.controller.keycloak; -import io.fabric8.kubernetes.api.builder.Function; -import io.fabric8.kubernetes.api.model.apiextensions.CustomResourceDefinition; -import io.fabric8.kubernetes.api.model.apiextensions.CustomResourceDefinitionBuilder; -import io.fabric8.kubernetes.api.model.apiextensions.CustomResourceDefinitionSpec; -import io.fabric8.kubernetes.api.model.apiextensions.CustomResourceDefinitionStatus; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + import io.fabric8.kubernetes.client.CustomResource; -import io.fabric8.kubernetes.client.CustomResourceDoneable; -import io.fabric8.kubernetes.client.CustomResourceList; +import io.fabric8.kubernetes.model.annotation.Group; +import io.fabric8.kubernetes.model.annotation.Kind; +import io.fabric8.kubernetes.model.annotation.Plural; +import io.fabric8.kubernetes.model.annotation.ShortNames; +import io.fabric8.kubernetes.model.annotation.Singular; +import io.fabric8.kubernetes.model.annotation.Version; -@SuppressWarnings("serial") @lombok.Getter @lombok.Setter -@lombok.EqualsAndHashCode(of = "spec", callSuper = false) -public class KeycloakResource extends CustomResource { - - public static final CustomResourceDefinition DEFINITION = new CustomResourceDefinitionBuilder() - .withNewSpec() - .withScope("Cluster") - .withGroup("k8s.kiwigrid.com") - .withVersion("v1beta1") - .withNewNames() - .withKind("Keycloak") - .withSingular("keycloak") - .withPlural("keycloaks") - .withShortNames("kc") - .endNames() - .endSpec().build(); - - private KeycloakResourceSpec spec = new KeycloakResourceSpec(); - private KeycloakResourceStatus status = new KeycloakResourceStatus(); - - @lombok.Getter - @lombok.Setter - @lombok.EqualsAndHashCode(callSuper = false) - public static class KeycloakResourceSpec extends CustomResourceDefinitionSpec { - - private String url; - private String realm = "master"; - private String clientId = "admin-cli"; - private String username = "admin"; - private String passwordSecretNamespace = "default"; - private String passwordSecretName = "keycloak-http"; - private String passwordSecretKey = "password"; - } - - @lombok.Getter - @lombok.Setter - public static class KeycloakResourceStatus extends CustomResourceDefinitionStatus { - - private String timestamp; - private String version; - private String error; - } - - public static class KeycloakResourceList extends CustomResourceList {} - - public static class KeycloakResourceDoneable extends CustomResourceDoneable { - public KeycloakResourceDoneable(KeycloakResource resource, - Function function) { - super(resource, function); - } +@lombok.EqualsAndHashCode(callSuper = false) +@Group("k8s.kiwigrid.com") +@Kind("Keycloak") +@Version("v1beta1") +@ShortNames("kc") +@Singular("keycloak") +@Plural("keycloaks") +public class KeycloakResource extends CustomResource { + + public String toString() { + return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE); } } \ No newline at end of file diff --git a/src/main/java/com/kiwigrid/keycloak/controller/keycloak/KeycloakResourceStatus.java b/src/main/java/com/kiwigrid/keycloak/controller/keycloak/KeycloakResourceStatus.java new file mode 100644 index 0000000..f888c05 --- /dev/null +++ b/src/main/java/com/kiwigrid/keycloak/controller/keycloak/KeycloakResourceStatus.java @@ -0,0 +1,17 @@ +package com.kiwigrid.keycloak.controller.keycloak; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +@lombok.Getter +@lombok.Setter +@lombok.EqualsAndHashCode(callSuper = false) +public class KeycloakResourceStatus { + private String timestamp; + private String version; + private String error; + + public String toString() { + return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE); + } +} diff --git a/src/main/java/com/kiwigrid/keycloak/controller/keycloak/KeycloakSpec.java b/src/main/java/com/kiwigrid/keycloak/controller/keycloak/KeycloakSpec.java new file mode 100644 index 0000000..eb2fa11 --- /dev/null +++ b/src/main/java/com/kiwigrid/keycloak/controller/keycloak/KeycloakSpec.java @@ -0,0 +1,21 @@ +package com.kiwigrid.keycloak.controller.keycloak; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +@lombok.Getter +@lombok.Setter +@lombok.EqualsAndHashCode(callSuper = false) +public class KeycloakSpec { + private String url; + private String realm = "master"; + private String clientId = "admin-cli"; + private String username = "admin"; + private String passwordSecretNamespace = "default"; + private String passwordSecretName = "keycloak-http"; + private String passwordSecretKey = "password"; + + public String toString() { + return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE); + } +} diff --git a/src/main/java/com/kiwigrid/keycloak/controller/realm/RealmController.java b/src/main/java/com/kiwigrid/keycloak/controller/realm/RealmController.java index ea6cf6d..a1dd1ec 100644 --- a/src/main/java/com/kiwigrid/keycloak/controller/realm/RealmController.java +++ b/src/main/java/com/kiwigrid/keycloak/controller/realm/RealmController.java @@ -7,10 +7,9 @@ import com.kiwigrid.keycloak.controller.KubernetesController; import com.kiwigrid.keycloak.controller.keycloak.KeycloakController; -import com.kiwigrid.keycloak.controller.realm.RealmResource.RealmResourceDoneable; -import com.kiwigrid.keycloak.controller.realm.RealmResource.RealmResourceList; -import com.kiwigrid.keycloak.controller.realm.RealmResource.RealmResourceSpec; + import io.fabric8.kubernetes.client.KubernetesClient; + import javax.inject.Singleton; import javax.ws.rs.NotFoundException; import javax.ws.rs.WebApplicationException; @@ -24,8 +23,7 @@ public class RealmController extends KubernetesController { final KeycloakController keycloak; public RealmController(KeycloakController keycloak, KubernetesClient kubernetes) { - super(kubernetes, RealmResource.DEFINITION, RealmResource.class, RealmResourceList.class, - RealmResourceDoneable.class); + super(kubernetes, RealmResource.class); this.keycloak = keycloak; } @@ -81,7 +79,7 @@ public void delete(RealmResource resource) { @Override public void retry() { customResources.list().getItems().stream() - .filter(r -> r.getStatus().getError() != null) + .filter(r -> r.getStatus() != null && r.getStatus().getError() != null) .forEach(this::apply); } @@ -91,6 +89,10 @@ void updateStatus(RealmResource resource, String error) { // skip if nothing changed + if (resource.getStatus() == null) { + resource.setStatus(new RealmResourceStatus()); + } + if (resource.getStatus().getTimestamp() != null && Objects.equals(resource.getStatus().getError(), error)) { return; } @@ -99,10 +101,10 @@ void updateStatus(RealmResource resource, String error) { resource.getStatus().setError(error); resource.getStatus().setTimestamp(Instant.now().truncatedTo(ChronoUnit.SECONDS).toString()); - customResources.withName(resource.getMetadata().getName()).replace(resource); + kubernetes.resource(resource).replace(); } - void manageRealmRoles(Keycloak keycloak, RealmResourceSpec spec) { + void manageRealmRoles(Keycloak keycloak, RealmSpec spec) { // no roles to handle? diff --git a/src/main/java/com/kiwigrid/keycloak/controller/realm/RealmResource.java b/src/main/java/com/kiwigrid/keycloak/controller/realm/RealmResource.java index e3b886f..4236562 100644 --- a/src/main/java/com/kiwigrid/keycloak/controller/realm/RealmResource.java +++ b/src/main/java/com/kiwigrid/keycloak/controller/realm/RealmResource.java @@ -1,62 +1,28 @@ package com.kiwigrid.keycloak.controller.realm; -import java.util.ArrayList; -import java.util.List; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; -import io.fabric8.kubernetes.api.builder.Function; -import io.fabric8.kubernetes.api.model.apiextensions.CustomResourceDefinition; -import io.fabric8.kubernetes.api.model.apiextensions.CustomResourceDefinitionBuilder; -import io.fabric8.kubernetes.api.model.apiextensions.CustomResourceDefinitionSpec; -import io.fabric8.kubernetes.api.model.apiextensions.CustomResourceDefinitionStatus; import io.fabric8.kubernetes.client.CustomResource; -import io.fabric8.kubernetes.client.CustomResourceDoneable; -import io.fabric8.kubernetes.client.CustomResourceList; +import io.fabric8.kubernetes.model.annotation.Group; +import io.fabric8.kubernetes.model.annotation.Kind; +import io.fabric8.kubernetes.model.annotation.Plural; +import io.fabric8.kubernetes.model.annotation.ShortNames; +import io.fabric8.kubernetes.model.annotation.Singular; +import io.fabric8.kubernetes.model.annotation.Version; -@SuppressWarnings("serial") @lombok.Getter @lombok.Setter -@lombok.EqualsAndHashCode(of = "spec", callSuper = false) -public class RealmResource extends CustomResource { - - public static final CustomResourceDefinition DEFINITION = new CustomResourceDefinitionBuilder() - .withNewSpec() - .withScope("Cluster") - .withGroup("k8s.kiwigrid.com") - .withVersion("v1beta1") - .withNewNames() - .withKind("KeycloakRealm") - .withSingular("keycloakrealm") - .withPlural("keycloakrealms") - .withShortNames("kcr") - .endNames() - .endSpec().build(); - - private RealmResourceSpec spec = new RealmResourceSpec(); - private RealmResourceStatus status = new RealmResourceStatus(); - - @lombok.Getter - @lombok.Setter - @lombok.EqualsAndHashCode(callSuper = true) - public static class RealmResourceSpec extends CustomResourceDefinitionSpec { - - private String keycloak = "keycloak"; - private String realm; - private List roles = new ArrayList<>(); - } - - @lombok.Getter - @lombok.Setter - public static class RealmResourceStatus extends CustomResourceDefinitionStatus { - - private String timestamp; - private String error; - } - - public static class RealmResourceList extends CustomResourceList {} - - public static class RealmResourceDoneable extends CustomResourceDoneable { - public RealmResourceDoneable(RealmResource resource, Function function) { - super(resource, function); - } +@lombok.EqualsAndHashCode(callSuper = false) +@Group("k8s.kiwigrid.com") +@Version("v1beta1") +@Kind("KeycloakRealm") +@Singular("keycloakrealm") +@Plural("keycloakrealms") +@ShortNames("kcr") +public class RealmResource extends CustomResource { + + public String toString() { + return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE); } } \ No newline at end of file diff --git a/src/main/java/com/kiwigrid/keycloak/controller/realm/RealmResourceStatus.java b/src/main/java/com/kiwigrid/keycloak/controller/realm/RealmResourceStatus.java new file mode 100644 index 0000000..5cdfd56 --- /dev/null +++ b/src/main/java/com/kiwigrid/keycloak/controller/realm/RealmResourceStatus.java @@ -0,0 +1,8 @@ +package com.kiwigrid.keycloak.controller.realm; + +@lombok.Getter +@lombok.Setter +public class RealmResourceStatus { + private String timestamp; + private String error; +} \ No newline at end of file diff --git a/src/main/java/com/kiwigrid/keycloak/controller/realm/RealmSpec.java b/src/main/java/com/kiwigrid/keycloak/controller/realm/RealmSpec.java new file mode 100644 index 0000000..8d58bd8 --- /dev/null +++ b/src/main/java/com/kiwigrid/keycloak/controller/realm/RealmSpec.java @@ -0,0 +1,14 @@ +package com.kiwigrid.keycloak.controller.realm; + +import java.util.ArrayList; +import java.util.List; + +@lombok.Getter +@lombok.Setter +@lombok.EqualsAndHashCode(callSuper = false) +public class RealmSpec { + private String keycloak; + private String realm; + + private List roles = new ArrayList<>(); +} \ No newline at end of file diff --git a/src/test/java/com/kiwigrid/keycloak/controller/client/AssignedClientScopesSyncerTest.java b/src/test/java/com/kiwigrid/keycloak/controller/client/AssignedClientScopesSyncerTest.java index ae2c588..87ade58 100644 --- a/src/test/java/com/kiwigrid/keycloak/controller/client/AssignedClientScopesSyncerTest.java +++ b/src/test/java/com/kiwigrid/keycloak/controller/client/AssignedClientScopesSyncerTest.java @@ -132,7 +132,7 @@ private ClientScopeRepresentation mapToClientRepresentation(String cs) { private com.kiwigrid.keycloak.controller.client.ClientResource createKubernetesClientResource( List defaultClientScopes, List optionalClientScopes) { com.kiwigrid.keycloak.controller.client.ClientResource clientResourceK8s = new com.kiwigrid.keycloak.controller.client.ClientResource(); - clientResourceK8s.setSpec(new com.kiwigrid.keycloak.controller.client.ClientResource.ClientResourceSpec()); + clientResourceK8s.setSpec(new com.kiwigrid.keycloak.controller.client.ClientSpec()); clientResourceK8s.getSpec().setDefaultClientScopes(defaultClientScopes); clientResourceK8s.getSpec().setOptionalClientScopes(optionalClientScopes); clientResourceK8s.getSpec().setRealm("realm"); diff --git a/src/test/java/com/kiwigrid/keycloak/controller/client/ServiceAccountRoleAssignmentTestObjects.java b/src/test/java/com/kiwigrid/keycloak/controller/client/ServiceAccountRoleAssignmentTestObjects.java index 6fa6111..49f2786 100644 --- a/src/test/java/com/kiwigrid/keycloak/controller/client/ServiceAccountRoleAssignmentTestObjects.java +++ b/src/test/java/com/kiwigrid/keycloak/controller/client/ServiceAccountRoleAssignmentTestObjects.java @@ -3,23 +3,7 @@ import java.util.List; import java.util.stream.Collectors; -import org.keycloak.admin.client.resource.ClientsResource; -import org.keycloak.admin.client.resource.RealmResource; -import org.keycloak.admin.client.resource.RoleMappingResource; -import org.keycloak.admin.client.resource.RoleScopeResource; -import org.keycloak.admin.client.resource.RolesResource; -import org.keycloak.admin.client.resource.UserResource; -import org.keycloak.admin.client.resource.UsersResource; -import org.keycloak.representations.idm.MappingsRepresentation; import org.keycloak.representations.idm.RoleRepresentation; -import org.keycloak.representations.idm.UserRepresentation; -import org.mockito.Mockito; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyList; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.doAnswer; public class ServiceAccountRoleAssignmentTestObjects { @@ -39,7 +23,7 @@ private RoleRepresentation toRoleRepresentation(String nameName) { } com.kiwigrid.keycloak.controller.client.ClientResource createK8sClientResourceWith(List rolesToAssign) { - var clientResourceSpecification = new com.kiwigrid.keycloak.controller.client.ClientResource.ClientResourceSpec(); + var clientResourceSpecification = new com.kiwigrid.keycloak.controller.client.ClientSpec(); clientResourceSpecification.setServiceAccountRealmRoles(rolesToAssign); var clientResource = new com.kiwigrid.keycloak.controller.client.ClientResource();