Eureka Early Adopter Investigation
The Aggies team intends to provide a Scratch environment that is intended to host the latest FOLIO code, which includes Eureka.
This describes the progress and the state of the Aggies deployment of Eureka during the Early Adopter investigation.
The process being followed is described in Kubernetes Example Deployment.
Some information is also derived from the similar process described in Single Server Setup.
- 1 Registration and Configuration Steps
- 1.1 Step 1: Build Application Descriptor
- 1.2 Step 2: Keycloak Master Access Token
- 1.3 Step 3: Register Applications
- 1.4 Step 4: Register Module Discovery
- 1.5 Step 5: Create Tenant
- 1.6 Step 6: Create Entitlement
- 1.7 Step 7: Keycloak Side Car Access Token
- 1.8 Step 8: Create User
- 1.9 Step 9: Assign User Password
- 1.10 Step 10: Create Role
- 1.11 Step 11: Assign Role Capabilities
- 1.12 Step 12: Assign Roles to User
- 1.13 Step 13: Get Tenant Realm
- 1.14 Step 14: Assign Tenant Realm
- 1.15 Step 15: Next Steps
- 2 Eureka Stripes UI Steps
- 3 Kubernetes, Rancher, and Fleet Steps
- 4 Going Further, Setting up App Platform Complete (APC)
- 4.1 APC Step 1: Build Application Descriptor - Pass 1
- 4.2 APC Step 2: Register Application Descriptor - Pass 1
- 4.3 APC Step 3: Build Module Discovery - Pass 1
- 4.4 APC Step 4: Register Module Discovery - Pass 1
- 4.5 APC Step 5: Create Entitlement - Pass 1
- 4.6 APC Step 6: Reset Previous Steps
- 4.7 APC Step 7: Build Application Descriptors - Pass 2
- 4.8 APC Step 8: Register Application Descriptors - Pass 2
- 4.9 APC Step 9: Build Module Discoveries - Pass 2
- 4.10 APC Step 10: Register Module Discoveries - Pass 2
- 4.11 APC Step 11: Re-register “Complete” Application Descriptor - Pass 3
- 4.12 APC Step 12: Register Module Discoveries - Pass 3
- 4.13 APC Step 13: Create Entitlement - Pass 3
- 4.14 APC Step 14: Build Application Descriptors - Pass 4
- 4.15 APC Step 15: Register Application Descriptors - Pass 4
- 4.16 APC Step 16: Build Module Discoveries - Pass 4
- 4.17 APC Step 17: Register Module Discoveries - Pass 4
- 4.18 APC Step 18: Make Sure Modules are Deployed - Pass 4
- 4.19 APC Step 19: Build Fleet Manifests - Pass 4
- 4.20 APC Step 20: Register Applications and Modules - Pass 5
- 4.21 APC Step 21: Build and Deploy Fleet - Pass 5
- 4.22 APC Step 22: Create Entitlements - Pass 5
- 5 Significant Problems
- 6 Additional Notes
Registration and Configuration Steps
This desribes the operations performed and the related success, failures, and the solutions for registering and setting up the environment.
This does not describe the Kubernetes, Rancher, and Fleet Steps (see section below).
This documentation may provide some Curl script examples that are run as a Bash script.
The base variable represents the base URL with a trailing slash like: https://kong.folio.example/.
This URL should point to a Kong instance.
The path variable will be described for each particular step, which could be something like: applications (there is no leading slash).
Step 1: Build Application Descriptor
The Application Descriptor must be generated automatically.
The FOLIO Application Generator can be used to generate the Application Descriptor.
A project or GitHub repository, such as the Application Platform Minimal, can be setup and used for building the Application Descriptor.
The Application Descriptor should be used to describe a single application, however the Application Platform Minimal is a convenient way to provide all of the core applications via a single descriptors.
While this behavior may change in the future, the Application Platform Minimal is ideal for testing Eureka for the Early Adopter investigation.
The Application Platform Minimal must reference modules from some registry.
The Aggies have chosen to provide the FOLIO Module Descriptor Registry on GitHub.
The FOLIO Module Descriptor Registry operates using GitHub Actions via a locally hoster runner to generate pages hosted via GitHub Pages.
A series of Bash scripts are used as a minimalistic way to achieve Module Descriptor generation.
The Module Descriptors are being generated and deployed automatically using GitHub Actions.
The FOLIO Application Generator didn’t provide a way to use a registry hosted on a static GitHub Page website.
A custom Simple Registry Type was added to FOLIO Application Generator in order to accomplish this goal (now merged and released in folio-application-generator-1.1.0).
With this in place the following simple modification was made to the Application Platform Minimal in order to generate the Application Descriptor.
<configuration>
<templatePath>${basedir}/app-platform-minimal.template.json</templatePath>
<moduleRegistries>
<registry>
<type>simple</type>
<url>https://tamulib.github.io/folio-module-descriptor-registry/snapshot/</url>
</registry>
</moduleRegistries>
</configuration>Step 1: Build Application Descriptor has worked without any notable issues.
See the file for an example descriptor.
We later on ended up switching to a more explicitly selected set of project versions.
Please see the Selecting Versions for Investigation (Sunflower) for the recommended versions of projects and packages.
Step 2: Keycloak Master Access Token
The Master Access Token is used as both the Bearer Token and as the Okapi Token.
Once retrieved, this token should be stored in an environment variable, which for this process MASTER_TOKEN was chosen as the environment variable name for the Master Access Token.
There are Access Token time outs that need to be considered and this token may need to be re-fetched periodically during the entire registration and configuration process.
At this point the Keycloak, Kong, MGR Applications, MGR Tenants, and MGR Tenant Entitlements must be up and running as described in the Kubernetes, Rancher, and Fleet Steps.
This token requires the KC_SERVICE_CLIENT_ID and the KC_SERVICE_CLIENT_SECRET that are stored in Keycloak under the master realm.
In the master realm, under the Manage -> Clients, look for and expand folio-backend-admin-client (this will be the KC_SERVICE_CLIENT_ID).
From there, look under the Credentials tab and copy the Client Secret (this will be the KC_SERVICE_CLIENT_SECRET).
The first attempt at getting the Access Token failed because the Kong URL was not being used.
Using the Kong URL may not be strictly required, but it is the easiest and best way to go about this entire process.
The following is a snippet of the Curl script being used to perform this task:
path="realms/${realm}/protocol/openid-connect/token"
response=$(curl -X POST --silent \
--header "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "client_id=${KC_SERVICE_CLIENT_ID}" \
--data-urlencode "grant_type=client_credentials" \
--data-urlencode "client_secret=${KC_SERVICE_CLIENT_SECRET}" \
"${base}${path}"
)
if [[ ${?} -ne 0 ]] ; then
export MASTER_TOKEN=$(echo ${response} | jq -r ".access_token")
fiThe realm must be set to master for when using the Master Access Token.
The value of realm may change later to the Tenant Name for when using the Side Car Access Token.
When using the above Curl command inside of a script, that script may need to be executed via the source command to ensure that the MASTER_OKEN is properly exported into the callers environment.
The Step 3: Register Applications attempts kept failing for unknown reasons.
It turns out that the default time out for the Access Token is too short for manual scripting (especially while one is perusing documentation before running a command).
This can be fixed by going to the master realm of Keycloak and selecting Realm settings.
Scroll down to Access tokens. The default value happened to be 1 minute in our case. We changed this value to something larger, like 5 minutes.
The master realm is only used for the steps up to the Step 6: Create Entitlements.
After that step the realm should now be set to the name of the tenant, such as scratch.
See Step 7: Side Car Access Token for further details.
Step 2: Keycloak Master Access Token worked while using the Kong URL.
Step 3: Register Applications
For the purposes of this investigation only the Application Platform Minimal is being registered, initially.
This utilizes the descriptors generated from Step 1: Build Application Descriptor above.
The registration is a POST to /applications.
The following is a snippet of the Curl script being used to perform this task:
path="applications"
response=$(curl -X POST --silent \
--header "Authorization: Bearer ${MASTER_TOKEN}" \
--header "Content-Type: application/json" \
--data "@${json}" \
"${base}${path}"
)
if [[ ${?} -eq 0 ]] ; then
echo ${response} | jq
else
echo ${response}
fiThe json variable contains the location of the Application Descriptor that was created in Step 1: Build Application Descriptor.
We are are using app-platform-minimal-2.1.0-SNAPSHOT.json as the json file.
The initial attempts failed but these failed not because of the end point, the Curl script, or the desciptor but instead due to the time out issue described in Step 2: Keycloak Master Access Token.
Step 3: Register Applications worked once the Master Access Token time out (see Step 2: Keycloak Master Access Token) was increased to something more reasonable.
Step 4: Register Module Discovery
Performing Step 3 also registers the modules as part of registering the application.
This step is not about registering the module.
Instead, this step is about registering the Module Discovery.
The Module Discovery describes where the modules may be accessed (such as http://mod-permissions.folio-modules.svc).
The registration is a POST to /modules/discovery.
We are unaware of an automated way to generate the Module Discovery and have opted to make a copy of the Application Descriptor, filtering out only the modules.
The location was then manually added, see .
The following is a snippet of the Curl script being used to perform this task:
path="modules/discovery"
response=$(curl -X POST --silent \
--header "Authorization: Bearer ${MASTER_TOKEN}" \
--header "Content-Type: application/json" \
--data "@${json}" \
"${base}${path}"
)
if [[ ${?} -eq 0 ]] ; then
echo ${response} | jq
else
echo ${response}
fiThe json variable contains the location of the Module Discovery file that was manually constructed.
The initial attempts had the paths to the Rancher Deployment Pods setup to contain the version information in the name.
This caused problems where certain characters in the name had to be replaced for the URLs to work (namely the . needed to become a -), such as http://mod-configuration-5-13-0-SNAPSHOT-139.folio-modules.svc:19002.
This led to problems with the Module Discovery script being manually created.
This was easily resolved by ensuring that the URL path to a particular pod did not have version information as part of the path, such as http://mod-configuration.folio-modules.svc:19002.
The ports chosen were the standard :8081 ports.
It turns out that the Module Discovery ports need to point to the side car ports, such as http://mod-configuration.folio-modules.svc:19002.
This process was solved by making the side car ports set to port 80 and then removing the port reference from the HTTP URL, such as http://mod-configuration.folio-modules.svc.
(This port change was ultimately reverted during later iterations where we used he standar :8081.)
Step 4: Register Module Deployment worked once we were using the correct URLs, such as http://mod-permissions.folio-modules.svc.
Step 5: Create Tenant
The Tenant now needs to be created.
The creation is performed via a POST to /tenants.
The following is a snippet of the Curl script being used to perform this task:
path="tenants"
response=$(curl -X POST --silent \
--header "Authorization: Bearer ${MASTER_TOKEN}" \
--header "Content-Type: application/json" \
--data "{ \"name\": \"${tenant}\", \"description\": \"${description}\" }" \
"${base}${path}"
)
if [[ ${?} -eq 0 ]] ; then
echo ${response} | jq
else
echo ${response}
fiThe tenant, in our case, is set to scratch to represent Scratch environment that we are building.
The description is to describe that environment. Something as simple as The generated tenant. is being used for our investigation.
Our initial attempts at creating this resulted in numerous failures.
We were using unmodified base images for our deployments, such as Keycloak.
One problem turns out to be a problem because there are special modifications that are requiring and we cannot use the unmodified base images.
Another problem turns out to be a lack of environment variables being setup.
Error snippets:
Caused by: feign.FeignException$NotImplemented: [501 ] during [PUT] to [https://folio-keycloak-scratch.library.tamu.edu/admin/realms/scratch/users-management-permissions] [KeycloakClient#updateRealmUserManagementPermission(String,UserManagementPermission,String)]:
[{"error":"Feature not enabled","error_description":"For more on this error consult the server log."}]org.folio.tm.integration.keycloak.exception.KeycloakException: Failed to enable user management in Keycloak for realm: scratchQuotes from the support group:
”It seems you have an issue with the connection between the mgr-tenant and Keycloak. During the tenant creation process, mgr-tenant creates a new realm, client, and so on. The error states that it can't enable user management during the realm creation process”
”It seems you have a lack of env variables in your ConfigMap.”
”The only case to obtain such kind of error is a wrong image of keycloak or wrong envs in docker:”
“I highly recommend switching to the folioci or folioorg image (Kong as well) because those images have numerous predefined settings in line with Eureka platform expectations.”
Once we discovered and solved the problems described above, we completely dropped all of our databases.
This allowed us to make it through to other steps, like the Step 6: Create Entitlement, before we had our next set of failures.
It turns out that the database roles too must be dropped.
The reason for this is documented in RMB-1025.
Be sure to drop all relevant databases and relevant roles when waanting to completely start over with a fresh database.
The Tenant name should not have underscores in the name due to how Keycloak handles underscores as directory names.
Having underscores produces weird directory structures in Keycloak.
The Tenant name should not be called test.
The name test may have unusual behavior in Keycloak.
Step 5: Create Tenant worked from a fresh database (with all databases and roles being removed before repeating Step 1: Build Application Descriptor and so forth) while using the custom FOLIO specific images.
Our generated Tenant looks like:
{
"tenants": [
{
"id": "693cfb39-831f-431e-93c8-ec466a922b88",
"name": "scratch",
"description": "The generated tenant.",
"type": "default",
"attributes": [],
"metadata": {
"createdDate": "2025-04-08T15:36:10.449+00:00",
"createdBy": "00000000-0000-0000-0000-000000000000"
}
}
],
"totalRecords": 1
Step 6: Create Entitlement
The Tenant now needs to be entitled.
The entitling is performed via a POST to /entitlements.
The following is a snippet of the Curl script being used to perform this task:
path="tenants"
response=$(curl -X POST --silent \
--header "Authorization: Bearer ${MASTER_TOKEN}" \
--header "Content-Type: application/json" \
--header "x-okapi-token: ${MASTER_TOKEN}" \
--data "@${json}" \
"${base}${path}"
)
if [[ ${?} -eq 0 ]] ; then
echo ${response} | jq
else
echo ${response}
fiThe json is a path to a manually crafted file containing the entitlements, such as:
{
"tenantId": "693cfb39-831f-431e-93c8-ec466a922b88",
"applications": [
"app-platform-minimal-2.1.0-SNAPSHOT"
]
}Additional applications, such as app-platform-complete-2.1.0-SNAPSHOT or perhaps actual individual applications will ultimately be added to the list at this step.
Only the app-platform-minimal-2.1.0-SNAPSHOT is being used here to keep the investigation simple.
We have had significant failures with the entitlements process.
This turned out to be a problem with the roles not being created in the database.
This situation is described in Step 5: Create Tenant above.
Step 6: Create Entitlements worked from a fresh database (with all databases and roles being removed before repeating Step 1: Build Application Descriptor and so forth).
A successful response looks like:
{
"totalRecords": 1,
"flowId": "8b27df5f-cfd1-4c62-86f0-eb799d9fd07f",
"entitlements": [
{
"applicationId": "app-platform-minimal-2.1.0-SNAPSHOT",
"tenantId": "6611a201-78c8-442d-a58e-eb0e3c6e52bd",
"modules": []
}
]
}The flowId can be used to fetch additional details on how the entitlement creation process opererated.
Example request:
path="entitlements/8b27df5f-cfd1-4c62-86f0-eb799d9fd07f"
response=$(curl -X GET --silent \
--header "Authorization: Bearer ${MASTER_TOKEN}" \
--header "Content-Type: application/json" \
--header "x-okapi-token: ${MASTER_TOKEN}" \
"${base}${path}"
)See the file for an example flow response.
Step 7: Keycloak Side Car Access Token
The Side Car Access Token is just like the Master Access Token, except that it is used for Tenant specific actions (usually involving side cars).
Once retrieved, this token should be stored in an environment variable, which for this process SIDECAR_TOKEN was chosen as the environment variable name for the Side Car Access Token.
The following is a snippet of the Curl script being used to perform this task:
path="realms/${tenant}/protocol/openid-connect/token"
response=$(curl -X POST --silent \
--header "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "client_id=${SC_SERVICE_CLIENT_ID}" \
--data-urlencode "grant_type=client_credentials" \
--data-urlencode "client_secret=${SC_SERVICE_CLIENT_SECRET}" \
"${base}${path}"
)
if [[ ${?} -ne 0 ]] ; then
export SIDECAR_TOKEN=$(echo ${response} | jq -r ".access_token")
fiThe realm must be set to the Tenant name for the Side Car Access Token, therefore the variable ${tenant} is used instead of ${realm} in the above Curl example snippet.
Look in the Tenant realm (such as scratch), under the Manage -> Clients, look for and expand sidecar-module-access-client (this will be the SC_SERVICE_CLIENT_ID).
From there, look under the Credentials tab and copy the Client Secret (this will be the SC_SERVICE_CLIENT_SECRET).
When using the above Curl command inside of a script, that script may need to be executed via the source command to ensure that the SIDECAR_TOKEN is properly exported into the callers environment.
Step 7: Keycloak Side Car Access Token worked.
Step 8: Create User
The administration user now needs to be added.
The creation is performed via a POST to /users-keycloak/users.
Be sure to now start using the Side Car Access Token from this point forward.
The following is a snippet of the Curl script being used to perform this task:
path="users-keycloak/users"
response=$(curl -X POST --silent \
--header "Authorization: Bearer ${SIDECAR_TOKEN}" \
--header "Content-Type: application/json" \
--header "x-okapi-tenant: ${tenant}" \
--data-raw "@${json}" \
"${base}${path}"
)
if [[ ${?} -eq 0 ]] ; then
echo ${response} | jq
else
echo ${response}
fiThe tenant continues to being scratch.
The json is a manually crafted JSON file.
{
"username": "scratch_admin",
"active": true,
"personal": {
"lastName": "admin",
"firstName": "scratch",
"email": "scratch_admin@localhost",
"addresses": []
},
"type": "staff",
"patronGroup": "",
"preferredContactTypeId": "002"
}The patronGroup is from Reference Data derived from the following repository:
Other fields like "preferredContactTypeId": "002" are copied literally from the example shown in the Kubernetes Example Deployment Add User Section documentation.
The data-raw Curl parameter was also chosen based on that example.
We had a problem with using this.
See the problem regarding patronGroup that is described below (long story short is that the patronGroup must be an empty string).
The process failed on errors like this:
{
"message": "no Route matched with those values",
"request_id": "afc8375d717f781c4b8fe817a9648557"
}The user-keycloak side cars have errors like:
Exception thrown during asynchronous load: java.util.concurrent.CompletionException: java.util.concurrent.ExecutionException: org.folio.tools.store.exception.NotFoundException: Attribute: mod-users-keycloak not set for folio/scratch
at com.github.benmanes.caffeine.cache.CacheLoader.lambda$asyncLoad$0(CacheLoader.java:113)
at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(Unknown Source)This is the current state of the investigation.
The currently belief is that Kong may have some sort of problem.
The process then failed due to errors like:
{
"errors": [
{
"message": "[400 Bad Request] during [POST] to [http://users] [UsersClient#createUser(User)]: [Cannot add 3684a786-6671-4268-8ed0-9db82ebca60b. Patron group not found]",
"type": "BadRequest",
"code": "validation_error",
"parameters": []
}
],
"total_records": 1
}This is happening because the patronGroup Reference Data is not installed in our environment.
The Kubernets Example Deployment, Add User documentation can be misleading as their example shows "patronGroup": "3684a786-6671-4268-8ed0-9db82ebca60b",.
Using that causes the above error.
The patronGroup cannot be omitted either, therefore, the patronGroup must be set to an empty string.
Additional contributing factors may be that the following environment variables needed to be set for each side car (and each side car has different values for some of these):
ALLOW_CROSS_TENANT_REQUESTS=trueSIDECAR_URL=http://localhost:8082(update port number as needed.)SIDECAR_FORWARD_UNKNOWN_REQUESTS=trueMODULE_NAME= (name of module, such asmod-users-keycloak)MODULE_VERSION= (version of module, such as3.1.0-SNAPSHOT.141)MOD_USERS_BL=http://mod-users-bl:8082(update port number as needed.)
Step 8: Create User worked once we were using the Side Car Access Token instead of the Master Access Token in addition to several Rancher environment variable changes.
A successful response looks like:
{
"username": "scratch_admin",
"id": "38f46649-f77d-4407-bc56-bb95e4542eb4",
"active": true,
"type": "staff",
"departments": [],
"proxyFor": [],
"personal": {
"lastName": "admin",
"firstName": "scratch",
"email": "scratch_admin@localhost",
"addresses": []
},
"createdDate": "2025-04-11T20:42:28.281+00:00",
"updatedDate": "2025-04-11T20:42:28.281+00:00",
"metadata": {
"createdDate": "2025-04-11T20:42:28.266+00:00",
"updatedDate": "2025-04-11T20:42:28.266+00:00"
},
"customFields": {}
}Step 9: Assign User Password
The password assignment process is separate from the create user process and so the administration user must not have the password assigned.
The assignment is performed via a POST to /authn/credentials.
The following is a snippet of the Curl script being used to perform this task:
path="authn/credentials"
response=$(curl -X POST --silent \
--header "Authorization: Bearer ${SIDECAR_TOKEN}" \
--header "Content-Type: application/json" \
--header "x-okapi-tenant: ${tenant}" \
--data "{ \"username\": \"${user}\", \"userId\": \"${user_id}\", \"password\": \"${password}\" }" \
"${base}${path}"
)
if [[ ${?} -eq 0 ]] ; then
echo ${response} | jq
else
echo ${response}
fiThe user is the user name, such as scratch_admin.
The user_id is the user ID that was returned when the user was created.
The password is the desired password.
This process is failing with the following error:
{
"errors": [
{
"message": "Failed to create auth credentials for a username: scratch_admin",
"type": "ServiceException",
"code": "service_error",
"parameters": [
{
"key": "cause",
"value": "[401 ] during [POST] to [https://folio-keycloak-scratch.library.tamu.edu/realms/master/protocol/openid-connect/token] [KeycloakClient#callTokenEndpoint(String,Map,String,String)]: [{\"error\":\"unauthorized_client\",\"error_description\":\"Invalid client or Invalid client credentials\"}]"
}
]
}
],
"total_records": 1
}Step 9: Assign User Password worked once we changed our environment to strictly follow the EBSCO ecosystem.
Step 10: Create Role
The roles now must be created.
The assignment is performed via a POST to /roles.
Existing roles may be found in the mod-keycloak reference data.
The following is a snippet of the Curl script being used to perform this task:
path="roles"
response=$(curl -X POST --silent \
--header "Authorization: Bearer ${SIDECAR_TOKEN}" \
--header "Content-Type: application/json" \
--header "x-okapi-tenant: ${tenant}" \
--data "{ \"name\": \"${name}\", \"description\": \"${description}\" }" \
"${base}${path}"
)
if [[ ${?} -eq 0 ]] ; then
echo ${response} | jq
else
echo ${response}
fiThe name is the role name.
The description describes the role.
This can be done in parallel to the Step 9: Assign User Password step.
Problems at that step may not prevent performing this step.
Step 10: Create Role worked as expected.
A successful response looks like:
{
"id": "bf00c9ed-2fdf-4107-b5c1-07f2dc028c2f",
"name": "adminRole",
"description": "Administration role.",
"type": "REGULAR",
"metadata": {
"createdDate": "2025-04-15T15:41:11.780+00:00",
"updatedDate": "2025-04-15T15:41:11.780+00:00"
}
}Step 11: Assign Role Capabilities
The capabilities assignment process must also be performed for the administrative role.
The assignment is performed via a POST to /roles/capabilities.
The following is a snippet of the Curl script being used to perform this task:
path="roles/capabilities"
response=$(curl -X POST --silent \
--header "Authorization: Bearer ${SIDECAR_TOKEN}" \
--header "Content-Type: application/json" \
--header "x-okapi-tenant: ${tenant}" \
--data "@${json}" \
"${base}${path}"
)
if [[ ${?} -eq 0 ]] ; then
echo ${response} | jq
else
echo ${response}
fiThe json is a manually crafted JSON file containing the capabilities structure.
The capabilities added to the json file can be retrieved via a GET to /capabilities.
The following is a snippet of the Curl script being used to perform this task:
path="capabilities"
response=$(curl -X POST --silent \
--header "Authorization: Bearer ${SIDECAR_TOKEN}" \
--header "Content-Type: application/json" \
--header "x-okapi-tenant: ${tenant}" \
"${base}${path}?limit=${limit}"
)
if [[ ${?} -eq 0 ]] ; then
echo ${response} | jq
else
echo ${response}
fiWhere limit is the maximum amount of capabilities to select, such as 5000.
Initial attempt failed with this response:
{
"errors": [
{
"message": "HTTP 502 Bad Gateway",
"type": "ServerErrorException",
"code": "unknown_error",
"parameters": []
}
],
"total_records": 1
}The logs for mod-roles-keycloak has the stack trace, such as this snippet:
jakarta.ws.rs.ServerErrorException: HTTP 502 Bad Gateway
at org.jboss.resteasy.client.jaxrs.internal.ClientInvocation.handleErrorStatus(ClientInvocation.java:260) ~[resteasy-client-6.2.9.Final.jar!/:6.2.9.Final]
at org.jboss.resteasy.client.jaxrs.internal.ClientInvocation.extractResult(ClientInvocation.java:216) ~[resteasy-client-6.2.9.Final.jar!/:6.2.9.Final]
at org.jboss.resteasy.client.jaxrs.internal.proxy.extractors.BodyEntityExtractor.extractEntity(BodyEntityExtractor.java:59) ~[resteasy-client-6.2.9.Final.jar!/:6.2.9.Final]
at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientInvoker.invokeSync(ClientInvoker.java:136) ~[resteasy-client-6.2.9.Final.jar!/:6.2.9.Final]
at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientInvoker.invoke(ClientInvoker.java:103) ~[resteasy-client-6.2.9.Final.jar!/:6.2.9.Final]
at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientProxy.invoke(ClientProxy.java:102) ~[resteasy-client-6.2.9.Final.jar!/:6.2.9.Final]
at jdk.proxy4/jdk.proxy4.$Proxy333.find(Unknown Source) ~[?:?]
at org.folio.roles.integration.keyclock.KeycloakAuthorizationService.getAuthResourceByStaticPath(KeycloakAuthorizationService.java:98) ~[!/:3.1.0-SNAPSHOT]
at org.folio.roles.integration.keyclock.KeycloakAuthorizationService.createPermissions(KeycloakAuthorizationService.java:57) ~[!/:3.1.0-SNAPSHOT]
at org.folio.roles.service.permission.RolePermissionService.createPermissions(RolePermissionService.java:46) ~[!/:3.1.0-SNAPSHOT]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source) ~[?:?]
at java.base/java.lang.reflect.Method.invoke(Unknown Source) ~[?:?]After waiting an hour so so, I ran the Curl request again with the same data and I got the following error:
{
"errors": [
{
"message": "Error during scope-based permission creation in Keycloak. Details: status = 500, message = Internal Server Error",
"type": "ServiceException",
"code": "service_error",
"parameters": [
{
"key": "permission",
"value": "GET access for role 'bf00c9ed-2fdf-4107-b5c1-07f2dc028c2f' to '/users-keycloak/users'"
}
]
}
],
"total_records": 1
}The mod-roles-keycloak logs show lots of entries like the following snippet:
18:33:39 [278036/roles] [scratch] [] [mod-roles-keycloak] INFO AuthorizationService Permission already exists in Keycloak [name: GET access for role 'bf00c9ed-2fdf-4107-b5c1-07f2dc028c2f' to '/_/discovery/health/{serviceId}']
18:33:39 [278036/roles] [scratch] [] [mod-roles-keycloak] INFO AuthorizationService Permission already exists in Keycloak [name: GET access for role 'bf00c9ed-2fdf-4107-b5c1-07f2dc028c2f' to '/_/discovery/health/{serviceId}/{instanceId}']There is no HTTP 502 Bad Gateway on this second attempt.
Additional attempts were made to solve this problem that were unsuccessful.
However, the changes resulted in prior steps failing.
See the Problems with Entire Ecosystem Setup section for further details.
Step 11: Assign Role Capabilities worked once we changed our environment to strictly follow the EBSCO ecosystem.
{
"totalRecords": 337,
"roleCapabilities": [
{
"roleId": "6645d56e-c627-420e-8e76-2d1228a579a5",
"capabilityId": "006067e5-f8e4-47e4-989f-d95e9683c673",
"metadata": {
"createdDate": "2025-05-06T20:34:26.883+00:00",
"updatedDate": "2025-05-06T20:34:26.883+00:00"
}
},
...
}Step 12: Assign Roles to User
The roles must now be assigned to the administration user.
The assignment is performed via a POST to /roles/users.
The following is a snippet of the Curl script being used to perform this task:
path="roles/users"
response=$(curl -X POST --silent \
--header "Authorization: Bearer ${SIDECAR_TOKEN}" \
--header "Content-Type: application/json" \
--header "x-okapi-tenant: ${tenant}" \
--data "{ \"userId\": \"${user_id}\", \"roleIds\": [ \"${role_id}\" ] }" \
"${base}${path}"
)
if [[ ${?} -eq 0 ]] ; then
echo ${response} | jq
else
echo ${response}
fiThe role_id is the role ID that was returned when the role was created. There may be multiple roles.
The user_id is the user ID that was returned when the user was created.
We skipped the failing Step 11: Assign Role Capabilities to test and confirm if this step works or not regardless of that step.
Step 12: Assign Roles to User worked as expected.
A successful response looks like:
{
"userRoles": [
{
"userId": "38f46649-f77d-4407-bc56-bb95e4542eb4",
"roleId": "bf00c9ed-2fdf-4107-b5c1-07f2dc028c2f",
"metadata": {
"createdDate": "2025-04-15T16:06:04.877+00:00",
"updatedDate": "2025-04-15T16:06:04.877+00:00"
}
}
],
"totalRecords": 1
}Step 13: Get Tenant Realm
The Step 4: Logging Into Stripes UI requires additional configuration via Curl requests.
This first step requires obtaining the Client UUID.
The Client UUID is the UUID from the id property at the top-level of the JSON response to the Curl request below.
Do not confuse the id with the clientId property.
The clientId property must not be used.