summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.zuul.yaml4
-rw-r--r--TESTING.rst32
-rw-r--r--doc/source/drivers/timer.rst17
-rw-r--r--doc/source/examples/keycloak/docker-compose.yaml18
-rw-r--r--doc/source/examples/keycloak/etc_zuul/zuul.conf2
-rw-r--r--doc/source/examples/keycloak/keycloak/zuul-demo-data.json4083
-rw-r--r--doc/source/howtos/openid-with-keycloak.rst13
-rw-r--r--doc/source/tutorials/keycloak.rst15
-rw-r--r--playbooks/zuul-stream/fixtures/test-stream.yaml12
-rw-r--r--playbooks/zuul-stream/functional.yaml37
-rw-r--r--playbooks/zuul-stream/post.yaml1
-rw-r--r--playbooks/zuul-stream/validate.yaml27
-rw-r--r--setup.cfg8
-rw-r--r--tests/fixtures/layouts/special-characters-job.yaml2
-rw-r--r--tests/remote/test_remote_zuul_stream.py2
-rw-r--r--tests/unit/test_gerrit.py56
-rw-r--r--tests/unit/test_scheduler.py12
-rw-r--r--tests/unit/test_web.py5
-rw-r--r--tests/zuul_client/test_zuulclient.py89
-rwxr-xr-xtools/test-setup-docker.sh27
-rw-r--r--tox.ini9
-rw-r--r--web/package.json3
-rw-r--r--web/src/actions/freezejob.js84
-rw-r--r--web/src/actions/jobgraph.js83
-rw-r--r--web/src/actions/pipelines.js62
-rw-r--r--web/src/api.js20
-rw-r--r--web/src/containers/build/Console.jsx13
-rw-r--r--web/src/containers/freezejob/FreezeJobToolbar.jsx200
-rw-r--r--web/src/containers/jobgraph/JobGraph.jsx78
-rw-r--r--web/src/containers/jobgraph/JobGraphDisplay.jsx145
-rw-r--r--web/src/containers/jobgraph/JobGraphToolbar.jsx145
-rw-r--r--web/src/containers/jobs/Jobs.jsx2
-rw-r--r--web/src/containers/project/Project.jsx79
-rw-r--r--web/src/containers/project/ProjectVariant.jsx106
-rw-r--r--web/src/index.css12
-rw-r--r--web/src/pages/FreezeJob.jsx149
-rw-r--r--web/src/pages/Project.jsx99
-rw-r--r--web/src/reducers/freezejob.js55
-rw-r--r--web/src/reducers/index.js6
-rw-r--r--web/src/reducers/jobgraph.js55
-rw-r--r--web/src/reducers/pipelines.js45
-rw-r--r--web/src/routes.js5
-rw-r--r--web/yarn.lock71
-rwxr-xr-xzuul/ansible/2.8/library/command.py10
-rwxr-xr-xzuul/ansible/2.9/library/command.py10
-rw-r--r--zuul/ansible/base/action/command.py28
-rw-r--r--zuul/ansible/base/callback/zuul_stream.py103
-rwxr-xr-xzuul/ansible/base/library/command.py10
-rwxr-xr-xzuul/ansible/base/library/zuul_console.py50
-rw-r--r--zuul/driver/gerrit/gerritconnection.py27
-rw-r--r--zuul/driver/gerrit/gerritmodel.py4
-rw-r--r--zuul/executor/server.py4
-rw-r--r--zuul/merger/merger.py10
-rw-r--r--zuul/scheduler.py4
-rwxr-xr-xzuul/web/__init__.py2
-rw-r--r--zuul/zk/event_queues.py3
56 files changed, 3775 insertions, 2478 deletions
diff --git a/.zuul.yaml b/.zuul.yaml
index 3efcff86b..72684c460 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -1,3 +1,6 @@
+- queue:
+ name: zuul
+
- nodeset:
name: zuul-functional-temp-master
nodes:
@@ -286,6 +289,7 @@
nodeset: ubuntu-jammy
- project:
+ queue: zuul
vars:
node_version: 16
release_python: python3
diff --git a/TESTING.rst b/TESTING.rst
index 6c4c8c0d8..fe9af66f6 100644
--- a/TESTING.rst
+++ b/TESTING.rst
@@ -1,34 +1,30 @@
-===========================
-Testing Your OpenStack Code
-===========================
+============
+Testing Zuul
+============
------------
A Quickstart
------------
-This is designed to be enough information for you to run your first tests.
-Detailed information on testing can be found here: https://wiki.openstack.org/wiki/Testing
+This is designed to be enough information for you to run your first tests on
+an Ubuntu 20.04 (or later) host.
*Install pip*::
- [apt-get | yum] install python-pip
+ sudo apt-get install python3-pip
More information on pip here: http://www.pip-installer.org/en/latest/
-*Use pip to install tox and tox-docker*::
+*Use pip to install tox*::
- pip install tox tox-docker
+ pip install tox
-As of zuul v3, a running zookeeper is required to execute tests. Using the
-``-docker`` suffixed commands will ensure this is started automatically by tox,
-but if you do not wish to use this:
+A running zookeeper is required to execute tests, but it also needs to be
+configured for TLS and a certificate authority set up to handle socket
+authentication. Because of these complexities, it's recommended to use a
+helper script to set up these dependencies, as well as a database servers::
-*Install zookeeper*::
-
- [apt-get | yum] install zookeeperd
-
-*Start zookeeper*::
-
- service zookeeper start
+ sudo apt-get install docker-compose # or podman-compose if preferred
+ ROOTCMD=sudo tools/test-setup-docker.sh
.. note:: Installing and bulding javascript is not required, but tests that
depend on the javascript assets having been built will be skipped
diff --git a/doc/source/drivers/timer.rst b/doc/source/drivers/timer.rst
index 1d7931c5e..57de1573d 100644
--- a/doc/source/drivers/timer.rst
+++ b/doc/source/drivers/timer.rst
@@ -18,6 +18,9 @@ will enqueue an event into its pipeline for every project and branch
defined in the configuration. Any job associated with the pipeline
will run in response to that event.
+Zuul implements the timer using `apscheduler`_, Please check the
+`apscheduler documentation`_ for more information about the syntax.
+
.. attr:: pipeline.trigger.timer
The timer trigger supports the following attributes:
@@ -27,9 +30,17 @@ will run in response to that event.
The time specification in cron syntax. Only the 5 part syntax
is supported, not the symbolic names. Example: ``0 0 * * *``
- runs at midnight. The first weekday is Monday. An optional 6th
- part specifies seconds. The optional 7th part specifies a
- jitter in seconds. This delays the trigger randomly, limited by
+ runs at midnight.
+ An optional 6th part specifies seconds. The optional 7th part specifies
+ a jitter in seconds. This delays the trigger randomly, limited by
the specified value. Example ``0 0 * * * * 60`` runs at
midnight or randomly up to 60 seconds later. The jitter is
applied individually to each project-branch combination.
+
+ .. warning::
+ Be aware the day-of-week value differs from from cron.
+ The first weekday is Monday (0), and the last is Sunday (6).
+
+
+.. _apscheduler: https://apscheduler.readthedocs.io/
+.. _apscheduler documentation: https://apscheduler.readthedocs.io/en/3.x/modules/triggers/cron.html#module-apscheduler.triggers.cron
diff --git a/doc/source/examples/keycloak/docker-compose.yaml b/doc/source/examples/keycloak/docker-compose.yaml
index 917c50231..9e70fec5f 100644
--- a/doc/source/examples/keycloak/docker-compose.yaml
+++ b/doc/source/examples/keycloak/docker-compose.yaml
@@ -7,21 +7,19 @@ version: '2.1'
services:
keycloak:
- image: docker.io/jboss/keycloak
+ image: quay.io/keycloak/keycloak:18.0
environment:
- - KEYCLOAK_USER=admin
- - KEYCLOAK_PASSWORD=kcadmin
+ - KEYCLOAK_ADMIN=admin
+ - KEYCLOAK_ADMIN_PASSWORD=kcadmin
- DB_VENDOR=h2
- - KEYCLOAK_IMPORT=/var/keycloak_import/zuul-demo-data.json
- - JAVA_OPTS_APPEND="-Djboss.socket.binding.port-offset=2"
+ - KC_HTTP_PORT=8082
ports:
- "8082:8082"
volumes:
- - "./keycloak/:/var/keycloak_import/:z"
- entrypoint: |
- /bin/sh -c '\
- /opt/jboss/tools/docker-entrypoint.sh -b 0.0.0.0'
- command: []
+ - "./keycloak/:/opt/keycloak/data/import/:z"
+ command:
+ - start-dev
+ - --import-realm
networks:
- zuul
diff --git a/doc/source/examples/keycloak/etc_zuul/zuul.conf b/doc/source/examples/keycloak/etc_zuul/zuul.conf
index 35cabe807..f2401cc90 100644
--- a/doc/source/examples/keycloak/etc_zuul/zuul.conf
+++ b/doc/source/examples/keycloak/etc_zuul/zuul.conf
@@ -14,7 +14,7 @@ tenant_config=/etc/zuul/main.yaml
default=true
driver=OpenIDConnect
realm=zuul-demo
-issuer_id=http://keycloak:8082/auth/realms/zuul-demo
+issuer_id=http://keycloak:8082/realms/zuul-demo
client_id=zuul
[connection "gerrit"]
diff --git a/doc/source/examples/keycloak/keycloak/zuul-demo-data.json b/doc/source/examples/keycloak/keycloak/zuul-demo-data.json
index 448267eed..194814e1b 100644
--- a/doc/source/examples/keycloak/keycloak/zuul-demo-data.json
+++ b/doc/source/examples/keycloak/keycloak/zuul-demo-data.json
@@ -1,2224 +1,1927 @@
{
- "id": "zuul-demo",
- "realm": "zuul-demo",
- "notBefore": 0,
- "revokeRefreshToken": false,
- "refreshTokenMaxReuse": 0,
- "accessTokenLifespan": 300,
- "accessTokenLifespanForImplicitFlow": 900,
- "ssoSessionIdleTimeout": 1800,
- "ssoSessionMaxLifespan": 36000,
- "ssoSessionIdleTimeoutRememberMe": 0,
- "ssoSessionMaxLifespanRememberMe": 0,
- "offlineSessionIdleTimeout": 2592000,
- "offlineSessionMaxLifespanEnabled": false,
- "offlineSessionMaxLifespan": 5184000,
- "clientSessionIdleTimeout": 0,
- "clientSessionMaxLifespan": 0,
- "clientOfflineSessionIdleTimeout": 0,
- "clientOfflineSessionMaxLifespan": 0,
- "accessCodeLifespan": 60,
- "accessCodeLifespanUserAction": 300,
- "accessCodeLifespanLogin": 1800,
- "actionTokenGeneratedByAdminLifespan": 43200,
- "actionTokenGeneratedByUserLifespan": 300,
- "enabled": true,
- "sslRequired": "external",
- "registrationAllowed": true,
- "registrationEmailAsUsername": false,
- "rememberMe": false,
- "verifyEmail": false,
- "loginWithEmailAllowed": true,
- "duplicateEmailsAllowed": false,
- "resetPasswordAllowed": false,
- "editUsernameAllowed": false,
- "bruteForceProtected": false,
- "permanentLockout": false,
- "maxFailureWaitSeconds": 900,
- "minimumQuickLoginWaitSeconds": 60,
- "waitIncrementSeconds": 60,
- "quickLoginCheckMilliSeconds": 1000,
- "maxDeltaTimeSeconds": 43200,
- "failureFactor": 30,
- "roles": {
- "realm": [
- {
- "id": "b295f2e1-c823-4f30-84b4-c534676c3ded",
- "name": "uma_authorization",
- "composite": false,
- "clientRole": false,
- "containerId": "zuul-demo",
- "attributes": {}
- },
- {
- "id": "047a5001-b3fe-452a-8fd3-985d82f7df31",
- "name": "offline_access",
- "description": "${role_offline-access}",
- "composite": false,
- "clientRole": false,
- "containerId": "zuul-demo",
- "attributes": {}
- }
- ],
- "client": {
- "realm-management": [
- {
- "id": "7e18128e-7a99-4403-8107-5a90ac0b952c",
- "name": "view-identity-providers",
- "description": "${role_view-identity-providers}",
- "composite": false,
- "clientRole": true,
- "containerId": "63ae0908-5213-436d-b3d6-5a611eb24216",
- "attributes": {}
- },
- {
- "id": "95f5f229-415e-48ba-8bfc-ff0a750f119e",
- "name": "view-users",
- "description": "${role_view-users}",
- "composite": true,
- "composites": {
- "client": {
- "realm-management": [
- "query-groups",
- "query-users"
- ]
- }
- },
- "clientRole": true,
- "containerId": "63ae0908-5213-436d-b3d6-5a611eb24216",
- "attributes": {}
- },
- {
- "id": "7cffdbb0-ced3-4fb0-98de-6b1d8506dbe4",
- "name": "manage-identity-providers",
- "description": "${role_manage-identity-providers}",
- "composite": false,
- "clientRole": true,
- "containerId": "63ae0908-5213-436d-b3d6-5a611eb24216",
- "attributes": {}
- },
- {
- "id": "08729d50-a31e-42fd-ad81-120cdee3b1b9",
- "name": "query-clients",
- "description": "${role_query-clients}",
- "composite": false,
- "clientRole": true,
- "containerId": "63ae0908-5213-436d-b3d6-5a611eb24216",
- "attributes": {}
- },
- {
- "id": "69ffc663-32cb-4f4e-a9af-669bd23b4edc",
- "name": "query-groups",
- "description": "${role_query-groups}",
- "composite": false,
- "clientRole": true,
- "containerId": "63ae0908-5213-436d-b3d6-5a611eb24216",
- "attributes": {}
- },
- {
- "id": "2a9f4d72-35bb-4072-b8aa-3fe7f1ab7f26",
- "name": "view-clients",
- "description": "${role_view-clients}",
- "composite": true,
- "composites": {
- "client": {
- "realm-management": [
- "query-clients"
- ]
- }
- },
- "clientRole": true,
- "containerId": "63ae0908-5213-436d-b3d6-5a611eb24216",
- "attributes": {}
- },
- {
- "id": "16c74fae-7feb-446d-9d42-08b31583ddf5",
- "name": "manage-events",
- "description": "${role_manage-events}",
- "composite": false,
- "clientRole": true,
- "containerId": "63ae0908-5213-436d-b3d6-5a611eb24216",
- "attributes": {}
- },
- {
- "id": "30cc0071-f914-446a-9aa3-a3372d9f45c0",
- "name": "manage-authorization",
- "description": "${role_manage-authorization}",
- "composite": false,
- "clientRole": true,
- "containerId": "63ae0908-5213-436d-b3d6-5a611eb24216",
- "attributes": {}
- },
- {
- "id": "d3d32a6d-f1da-410f-8c52-62d1b2ac7abc",
- "name": "query-realms",
- "description": "${role_query-realms}",
- "composite": false,
- "clientRole": true,
- "containerId": "63ae0908-5213-436d-b3d6-5a611eb24216",
- "attributes": {}
- },
- {
- "id": "1f7afd46-36a8-46e8-abc1-8949836ec532",
- "name": "view-authorization",
- "description": "${role_view-authorization}",
- "composite": false,
- "clientRole": true,
- "containerId": "63ae0908-5213-436d-b3d6-5a611eb24216",
- "attributes": {}
- },
- {
- "id": "6384baeb-1ec0-4e69-a5c2-7b632c3bfda5",
- "name": "impersonation",
- "description": "${role_impersonation}",
- "composite": false,
- "clientRole": true,
- "containerId": "63ae0908-5213-436d-b3d6-5a611eb24216",
- "attributes": {}
- },
- {
- "id": "eefc7ff0-e2f2-4381-b034-c96d0bae3769",
- "name": "realm-admin",
- "description": "${role_realm-admin}",
- "composite": true,
- "composites": {
- "client": {
- "realm-management": [
- "view-identity-providers",
- "view-users",
- "manage-identity-providers",
- "query-clients",
- "query-groups",
- "view-clients",
- "manage-events",
- "view-authorization",
- "manage-authorization",
- "query-realms",
- "impersonation",
- "create-client",
- "manage-clients",
- "manage-users",
- "manage-realm",
- "view-events",
- "view-realm",
- "query-users"
- ]
- }
- },
- "clientRole": true,
- "containerId": "63ae0908-5213-436d-b3d6-5a611eb24216",
- "attributes": {}
- },
- {
- "id": "1c104476-dc65-4df5-9f0e-3f9086a75813",
- "name": "create-client",
- "description": "${role_create-client}",
- "composite": false,
- "clientRole": true,
- "containerId": "63ae0908-5213-436d-b3d6-5a611eb24216",
- "attributes": {}
- },
- {
- "id": "f0893b2a-770e-4b21-a430-1a9974ecc01a",
- "name": "manage-clients",
- "description": "${role_manage-clients}",
- "composite": false,
- "clientRole": true,
- "containerId": "63ae0908-5213-436d-b3d6-5a611eb24216",
- "attributes": {}
- },
- {
- "id": "dfddee2e-682a-47ea-924c-59699f338e0c",
- "name": "manage-users",
- "description": "${role_manage-users}",
- "composite": false,
- "clientRole": true,
- "containerId": "63ae0908-5213-436d-b3d6-5a611eb24216",
- "attributes": {}
- },
- {
- "id": "a8198df9-d3cd-460f-a48f-ccede56e08a9",
- "name": "manage-realm",
- "description": "${role_manage-realm}",
- "composite": false,
- "clientRole": true,
- "containerId": "63ae0908-5213-436d-b3d6-5a611eb24216",
- "attributes": {}
- },
- {
- "id": "657b4b26-98c5-4963-a353-db26453f5dd2",
- "name": "view-events",
- "description": "${role_view-events}",
- "composite": false,
- "clientRole": true,
- "containerId": "63ae0908-5213-436d-b3d6-5a611eb24216",
- "attributes": {}
- },
- {
- "id": "693093ae-6004-48be-bb22-8804f61af1c0",
- "name": "view-realm",
- "description": "${role_view-realm}",
- "composite": false,
- "clientRole": true,
- "containerId": "63ae0908-5213-436d-b3d6-5a611eb24216",
- "attributes": {}
- },
- {
- "id": "418abb16-fdc3-402e-8125-52800990445b",
- "name": "query-users",
- "description": "${role_query-users}",
- "composite": false,
- "clientRole": true,
- "containerId": "63ae0908-5213-436d-b3d6-5a611eb24216",
- "attributes": {}
- }
- ],
- "security-admin-console": [],
- "admin-cli": [],
- "account-console": [],
- "zuul": [],
- "broker": [],
- "account": [
- {
- "id": "b839cd34-f23d-4c14-8060-ed635e708b87",
- "name": "view-consent",
- "description": "${role_view-consent}",
- "composite": false,
- "clientRole": true,
- "containerId": "6fd2abe1-4aeb-4834-8a9e-e3f499d64a03",
- "attributes": {}
- },
- {
- "id": "a98bcb3b-1584-45ab-afa8-e431ddfed5e7",
- "name": "view-applications",
- "description": "${role_view-applications}",
- "composite": false,
- "clientRole": true,
- "containerId": "6fd2abe1-4aeb-4834-8a9e-e3f499d64a03",
- "attributes": {}
- },
- {
- "id": "471d721c-872c-4004-bb76-e6399f5e1fd0",
- "name": "delete-account",
- "description": "${role_delete-account}",
- "composite": false,
- "clientRole": true,
- "containerId": "6fd2abe1-4aeb-4834-8a9e-e3f499d64a03",
- "attributes": {}
- },
- {
- "id": "e5a0f953-bdcb-4136-bfbf-0c311797e57f",
- "name": "manage-account",
- "composite": false,
- "clientRole": true,
- "containerId": "6fd2abe1-4aeb-4834-8a9e-e3f499d64a03",
- "attributes": {}
- },
- {
- "id": "c8726cd0-6447-4131-9731-717897ff167a",
- "name": "manage-consent",
- "description": "${role_manage-consent}",
- "composite": true,
- "composites": {
- "client": {
- "account": [
- "view-consent"
- ]
- }
- },
- "clientRole": true,
- "containerId": "6fd2abe1-4aeb-4834-8a9e-e3f499d64a03",
- "attributes": {}
- },
- {
- "id": "eb6c2340-cecf-4095-bdc7-d14f38a9aee6",
- "name": "view-profile",
- "composite": false,
- "clientRole": true,
- "containerId": "6fd2abe1-4aeb-4834-8a9e-e3f499d64a03",
- "attributes": {}
- }
- ],
- }
- },
- "groups": [
- {
- "id": "d372cb0e-f3c8-40be-8527-73775d1f7e47",
- "name": "example-tenant-admin",
- "path": "/example-tenant-admin",
- "attributes": {},
- "realmRoles": [],
- "clientRoles": {},
- "subGroups": []
- }
- ],
- "defaultRoles": [
- "uma_authorization",
- "offline_access"
- ],
- "requiredCredentials": [
- "password"
- ],
- "otpPolicyType": "totp",
- "otpPolicyAlgorithm": "HmacSHA1",
- "otpPolicyInitialCounter": 0,
- "otpPolicyDigits": 6,
- "otpPolicyLookAheadWindow": 1,
- "otpPolicyPeriod": 30,
- "otpSupportedApplications": [
- "FreeOTP",
- "Google Authenticator"
- ],
- "webAuthnPolicyRpEntityName": "keycloak",
- "webAuthnPolicySignatureAlgorithms": [
- "ES256"
- ],
- "webAuthnPolicyRpId": "",
- "webAuthnPolicyAttestationConveyancePreference": "not specified",
- "webAuthnPolicyAuthenticatorAttachment": "not specified",
- "webAuthnPolicyRequireResidentKey": "not specified",
- "webAuthnPolicyUserVerificationRequirement": "not specified",
- "webAuthnPolicyCreateTimeout": 0,
- "webAuthnPolicyAvoidSameAuthenticatorRegister": false,
- "webAuthnPolicyAcceptableAaguids": [],
- "webAuthnPolicyPasswordlessRpEntityName": "keycloak",
- "webAuthnPolicyPasswordlessSignatureAlgorithms": [
- "ES256"
- ],
- "webAuthnPolicyPasswordlessRpId": "",
- "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified",
- "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified",
- "webAuthnPolicyPasswordlessRequireResidentKey": "not specified",
- "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified",
- "webAuthnPolicyPasswordlessCreateTimeout": 0,
- "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false,
- "webAuthnPolicyPasswordlessAcceptableAaguids": [],
- "users": [
- {
- "id": "3defcf9c-a89d-496e-9769-526bc65b6db6",
- "createdTimestamp": 1609929909934,
- "username": "admin",
- "enabled": true,
- "totp": false,
- "emailVerified": false,
- "firstName": "Zuul",
- "lastName": "Administrator",
- "email": "admin@example.com",
- "credentials": [
- {
- "id": "1a05a791-be4d-4060-ae6b-e117c6c8de70",
- "type": "password",
- "createdDate": 1609929922393,
- "secretData": "{\"value\":\"BtE429Fzy8ygtLLD+eEHHY5DzJr3pzDf5wBwR8ZD4HFRPPn6NldxGTy+0AEJBGAQt+dZ0eEqVV8edytNR8PwMw==\",\"salt\":\"jL8euyHG4ZQy2BUMY/LpIg==\",\"additionalParameters\":{}}",
- "credentialData": "{\"hashIterations\":27500,\"algorithm\":\"pbkdf2-sha256\",\"additionalParameters\":{}}"
+ "id" : "zuul-demo",
+ "realm" : "zuul-demo",
+ "notBefore" : 0,
+ "defaultSignatureAlgorithm" : "RS256",
+ "revokeRefreshToken" : false,
+ "refreshTokenMaxReuse" : 0,
+ "accessTokenLifespan" : 300,
+ "accessTokenLifespanForImplicitFlow" : 900,
+ "ssoSessionIdleTimeout" : 1800,
+ "ssoSessionMaxLifespan" : 36000,
+ "ssoSessionIdleTimeoutRememberMe" : 0,
+ "ssoSessionMaxLifespanRememberMe" : 0,
+ "offlineSessionIdleTimeout" : 2592000,
+ "offlineSessionMaxLifespanEnabled" : false,
+ "offlineSessionMaxLifespan" : 5184000,
+ "clientSessionIdleTimeout" : 0,
+ "clientSessionMaxLifespan" : 0,
+ "clientOfflineSessionIdleTimeout" : 0,
+ "clientOfflineSessionMaxLifespan" : 0,
+ "accessCodeLifespan" : 60,
+ "accessCodeLifespanUserAction" : 300,
+ "accessCodeLifespanLogin" : 1800,
+ "actionTokenGeneratedByAdminLifespan" : 43200,
+ "actionTokenGeneratedByUserLifespan" : 300,
+ "oauth2DeviceCodeLifespan" : 600,
+ "oauth2DevicePollingInterval" : 5,
+ "enabled" : true,
+ "sslRequired" : "external",
+ "registrationAllowed" : false,
+ "registrationEmailAsUsername" : false,
+ "rememberMe" : false,
+ "verifyEmail" : false,
+ "loginWithEmailAllowed" : true,
+ "duplicateEmailsAllowed" : false,
+ "resetPasswordAllowed" : false,
+ "editUsernameAllowed" : false,
+ "bruteForceProtected" : false,
+ "permanentLockout" : false,
+ "maxFailureWaitSeconds" : 900,
+ "minimumQuickLoginWaitSeconds" : 60,
+ "waitIncrementSeconds" : 60,
+ "quickLoginCheckMilliSeconds" : 1000,
+ "maxDeltaTimeSeconds" : 43200,
+ "failureFactor" : 30,
+ "roles" : {
+ "realm" : [ {
+ "id" : "56408c69-b6fb-4845-be59-db262fe21dea",
+ "name" : "offline_access",
+ "description" : "${role_offline-access}",
+ "composite" : false,
+ "clientRole" : false,
+ "containerId" : "zuul-demo",
+ "attributes" : { }
+ }, {
+ "id" : "156d7b5b-65fb-4c89-b3d8-c2f82e9ecdd4",
+ "name" : "default-roles-zuul-demo",
+ "description" : "${role_default-roles}",
+ "composite" : true,
+ "composites" : {
+ "realm" : [ "offline_access", "uma_authorization" ],
+ "client" : {
+ "account" : [ "view-profile", "manage-account" ]
}
- ],
- "disableableCredentialTypes": [],
- "requiredActions": [],
- "realmRoles": [
- "uma_authorization",
- "offline_access"
- ],
- "clientRoles": {
- "account": [
- "manage-account",
- "view-profile"
- ]
- },
- "notBefore": 0,
- "groups": []
- },
- {
- "id": "091fbeb0-ac10-47eb-a113-88fbb0a7988f",
- "createdTimestamp": 1609929807691,
- "username": "user1",
- "enabled": true,
- "totp": false,
- "emailVerified": false,
- "firstName": "User",
- "lastName": "One",
- "email": "user1@example.com",
- "credentials": [
- {
- "id": "caec7277-9620-4232-b07f-10076ae32e17",
- "type": "password",
- "createdDate": 1609929824753,
- "secretData": "{\"value\":\"XbD+O1HUVhvqMg2xP36il/haxbzS65GjV2YYwqJWGfJuztQCn2G5ArmeWcqMxEd6BEBnPjZCjM3tpaBSFluLvg==\",\"salt\":\"8dXWJvuUtbqZuBw3ZUL4zA==\",\"additionalParameters\":{}}",
- "credentialData": "{\"hashIterations\":27500,\"algorithm\":\"pbkdf2-sha256\",\"additionalParameters\":{}}"
- }
- ],
- "disableableCredentialTypes": [],
- "requiredActions": [],
- "realmRoles": [
- "uma_authorization",
- "offline_access"
- ],
- "clientRoles": {
- "account": [
- "manage-account",
- "view-profile"
- ]
- },
- "notBefore": 0,
- "groups": [
- "/example-tenant-admin"
- ]
- },
- {
- "id": "633cbfcd-43d9-4588-9c74-7af4ea007dda",
- "createdTimestamp": 1609929870956,
- "username": "user2",
- "enabled": true,
- "totp": false,
- "emailVerified": false,
- "firstName": "User",
- "lastName": "Two",
- "email": "user2@example.com",
- "credentials": [
- {
- "id": "1975173e-f07c-4223-9bf6-fa44dcd27cf0",
- "type": "password",
- "createdDate": 1609929881586,
- "secretData": "{\"value\":\"SZ0ESXObiHfCOo4m9afbnpNaZ52H0k0VVuHe2PecmUZ4FxpAXbCsUimNNsz5VVRdqhAqWJi2AcExCoKFSJzeug==\",\"salt\":\"+pc2TGNg/CjypsBPjH0YJg==\",\"additionalParameters\":{}}",
- "credentialData": "{\"hashIterations\":27500,\"algorithm\":\"pbkdf2-sha256\",\"additionalParameters\":{}}"
- }
- ],
- "disableableCredentialTypes": [],
- "requiredActions": [],
- "realmRoles": [
- "uma_authorization",
- "offline_access"
- ],
- "clientRoles": {
- "account": [
- "manage-account",
- "view-profile"
- ]
- },
- "notBefore": 0,
- "groups": []
- }
- ],
- "scopeMappings": [
- {
- "clientScope": "offline_access",
- "roles": [
- "offline_access"
- ]
- }
- ],
- "clientScopeMappings": {
- "account": [
- {
- "client": "account-console",
- "roles": [
- "manage-account"
- ]
- }
- ]
- },
- "clients": [
- {
- "id": "6fd2abe1-4aeb-4834-8a9e-e3f499d64a03",
- "clientId": "account",
- "name": "${client_account}",
- "rootUrl": "${authBaseUrl}",
- "baseUrl": "/realms/zuul-demo/account/",
- "surrogateAuthRequired": false,
- "enabled": true,
- "alwaysDisplayInConsole": false,
- "clientAuthenticatorType": "client-secret",
- "secret": "**********",
- "defaultRoles": [
- "manage-account",
- "view-profile"
- ],
- "redirectUris": [
- "/realms/zuul-demo/account/*"
- ],
- "webOrigins": [],
- "notBefore": 0,
- "bearerOnly": false,
- "consentRequired": false,
- "standardFlowEnabled": true,
- "implicitFlowEnabled": false,
- "directAccessGrantsEnabled": false,
- "serviceAccountsEnabled": false,
- "publicClient": false,
- "frontchannelLogout": false,
- "protocol": "openid-connect",
- "attributes": {},
- "authenticationFlowBindingOverrides": {},
- "fullScopeAllowed": false,
- "nodeReRegistrationTimeout": 0,
- "defaultClientScopes": [
- "web-origins",
- "role_list",
- "profile",
- "roles",
- "email"
- ],
- "optionalClientScopes": [
- "address",
- "phone",
- "offline_access",
- "microprofile-jwt"
- ]
- },
- {
- "id": "1958e9b1-c3ce-4bec-89bd-3b0050a4dacf",
- "clientId": "account-console",
- "name": "${client_account-console}",
- "rootUrl": "${authBaseUrl}",
- "baseUrl": "/realms/zuul-demo/account/",
- "surrogateAuthRequired": false,
- "enabled": true,
- "alwaysDisplayInConsole": false,
- "clientAuthenticatorType": "client-secret",
- "secret": "e555a236-3a76-4ac9-a55a-48f81a00535f",
- "redirectUris": [
- "/realms/zuul-demo/account/*"
- ],
- "webOrigins": [],
- "notBefore": 0,
- "bearerOnly": false,
- "consentRequired": false,
- "standardFlowEnabled": true,
- "implicitFlowEnabled": false,
- "directAccessGrantsEnabled": false,
- "serviceAccountsEnabled": false,
- "publicClient": true,
- "frontchannelLogout": false,
- "protocol": "openid-connect",
- "attributes": {
- "pkce.code.challenge.method": "S256"
- },
- "authenticationFlowBindingOverrides": {},
- "fullScopeAllowed": false,
- "nodeReRegistrationTimeout": 0,
- "protocolMappers": [
- {
- "id": "5a075254-4849-43dc-b036-53c606571988",
- "name": "audience resolve",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-audience-resolve-mapper",
- "consentRequired": false,
- "config": {}
- }
- ],
- "defaultClientScopes": [
- "web-origins",
- "role_list",
- "profile",
- "roles",
- "email"
- ],
- "optionalClientScopes": [
- "address",
- "phone",
- "offline_access",
- "microprofile-jwt"
- ]
- },
- {
- "id": "3e59f406-d6b9-45bd-b072-ef1650233625",
- "clientId": "admin-cli",
- "name": "${client_admin-cli}",
- "surrogateAuthRequired": false,
- "enabled": true,
- "alwaysDisplayInConsole": false,
- "clientAuthenticatorType": "client-secret",
- "secret": "**********",
- "redirectUris": [],
- "webOrigins": [],
- "notBefore": 0,
- "bearerOnly": false,
- "consentRequired": false,
- "standardFlowEnabled": false,
- "implicitFlowEnabled": false,
- "directAccessGrantsEnabled": true,
- "serviceAccountsEnabled": false,
- "publicClient": true,
- "frontchannelLogout": false,
- "protocol": "openid-connect",
- "attributes": {},
- "authenticationFlowBindingOverrides": {},
- "fullScopeAllowed": false,
- "nodeReRegistrationTimeout": 0,
- "defaultClientScopes": [
- "web-origins",
- "role_list",
- "profile",
- "roles",
- "email"
- ],
- "optionalClientScopes": [
- "address",
- "phone",
- "offline_access",
- "microprofile-jwt"
- ]
- },
- {
- "id": "b607307d-dde7-4563-9e70-a7fa9223c229",
- "clientId": "broker",
- "name": "${client_broker}",
- "surrogateAuthRequired": false,
- "enabled": true,
- "alwaysDisplayInConsole": false,
- "clientAuthenticatorType": "client-secret",
- "secret": "**********",
- "redirectUris": [],
- "webOrigins": [],
- "notBefore": 0,
- "bearerOnly": false,
- "consentRequired": false,
- "standardFlowEnabled": true,
- "implicitFlowEnabled": false,
- "directAccessGrantsEnabled": false,
- "serviceAccountsEnabled": false,
- "publicClient": false,
- "frontchannelLogout": false,
- "protocol": "openid-connect",
- "attributes": {},
- "authenticationFlowBindingOverrides": {},
- "fullScopeAllowed": false,
- "nodeReRegistrationTimeout": 0,
- "defaultClientScopes": [
- "web-origins",
- "role_list",
- "profile",
- "roles",
- "email"
- ],
- "optionalClientScopes": [
- "address",
- "phone",
- "offline_access",
- "microprofile-jwt"
- ]
- },
- {
- "id": "63ae0908-5213-436d-b3d6-5a611eb24216",
- "clientId": "realm-management",
- "name": "${client_realm-management}",
- "surrogateAuthRequired": false,
- "enabled": true,
- "alwaysDisplayInConsole": false,
- "clientAuthenticatorType": "client-secret",
- "secret": "**********",
- "redirectUris": [],
- "webOrigins": [],
- "notBefore": 0,
- "bearerOnly": true,
- "consentRequired": false,
- "standardFlowEnabled": true,
- "implicitFlowEnabled": false,
- "directAccessGrantsEnabled": false,
- "serviceAccountsEnabled": false,
- "publicClient": false,
- "frontchannelLogout": false,
- "protocol": "openid-connect",
- "attributes": {},
- "authenticationFlowBindingOverrides": {},
- "fullScopeAllowed": false,
- "nodeReRegistrationTimeout": 0,
- "defaultClientScopes": [
- "web-origins",
- "role_list",
- "profile",
- "roles",
- "email"
- ],
- "optionalClientScopes": [
- "address",
- "phone",
- "offline_access",
- "microprofile-jwt"
- ]
- },
- {
- "id": "26cb8208-4182-478b-903e-6abe9b555680",
- "clientId": "security-admin-console",
- "name": "${client_security-admin-console}",
- "rootUrl": "${authAdminUrl}",
- "baseUrl": "/admin/zuul-demo/console/",
- "surrogateAuthRequired": false,
- "enabled": true,
- "alwaysDisplayInConsole": false,
- "clientAuthenticatorType": "client-secret",
- "secret": "**********",
- "redirectUris": [
- "/admin/zuul-demo/console/*"
- ],
- "webOrigins": [
- "+"
- ],
- "notBefore": 0,
- "bearerOnly": false,
- "consentRequired": false,
- "standardFlowEnabled": true,
- "implicitFlowEnabled": false,
- "directAccessGrantsEnabled": false,
- "serviceAccountsEnabled": false,
- "publicClient": true,
- "frontchannelLogout": false,
- "protocol": "openid-connect",
- "attributes": {
- "pkce.code.challenge.method": "S256"
- },
- "authenticationFlowBindingOverrides": {},
- "fullScopeAllowed": false,
- "nodeReRegistrationTimeout": 0,
- "protocolMappers": [
- {
- "id": "9077bfa3-32f2-4690-a1f4-fdb2726a6ef2",
- "name": "locale",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-attribute-mapper",
- "consentRequired": false,
- "config": {
- "userinfo.token.claim": "true",
- "user.attribute": "locale",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "claim.name": "locale",
- "jsonType.label": "String"
- }
- }
- ],
- "defaultClientScopes": [
- "web-origins",
- "role_list",
- "profile",
- "roles",
- "email"
- ],
- "optionalClientScopes": [
- "address",
- "phone",
- "offline_access",
- "microprofile-jwt"
- ]
- },
- {
- "id": "118b61cd-e410-414a-a4ee-64e5722725b7",
- "clientId": "zuul",
- "description": "zuul oauth client",
- "rootUrl": "http://localhost:9000",
- "adminUrl": "http://localhost:9000",
- "surrogateAuthRequired": false,
- "enabled": true,
- "alwaysDisplayInConsole": false,
- "clientAuthenticatorType": "client-secret",
- "secret": "**********",
- "redirectUris": [
- "http://127.0.0.1:9000/*",
- "http://localhost:9000/*",
- "http://127.0.0.1:3000/*",
- "http://localhost:3000/*"
- ],
- "webOrigins": [
- "http://localhost:9000",
- "http://localhost:3000"
- ],
- "notBefore": 0,
- "bearerOnly": false,
- "consentRequired": false,
- "standardFlowEnabled": true,
- "implicitFlowEnabled": true,
- "directAccessGrantsEnabled": false,
- "serviceAccountsEnabled": false,
- "publicClient": true,
- "frontchannelLogout": false,
- "protocol": "openid-connect",
- "attributes": {
- "saml.assertion.signature": "false",
- "saml.force.post.binding": "false",
- "saml.multivalued.roles": "false",
- "saml.encrypt": "false",
- "saml.server.signature": "false",
- "saml.server.signature.keyinfo.ext": "false",
- "exclude.session.state.from.auth.response": "true",
- "saml_force_name_id_format": "false",
- "saml.client.signature": "false",
- "tls.client.certificate.bound.access.tokens": "false",
- "saml.authnstatement": "false",
- "display.on.consent.screen": "false",
- "saml.onetimeuse.condition": "false"
- },
- "authenticationFlowBindingOverrides": {},
- "fullScopeAllowed": true,
- "nodeReRegistrationTimeout": -1,
- "protocolMappers": [
- {
- "id": "2389430c-5b5e-4185-a116-a89fe9b2cbe0",
- "name": "groups",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-group-membership-mapper",
- "consentRequired": false,
- "config": {
- "full.path": "false",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "claim.name": "groups",
- "userinfo.token.claim": "true"
- }
- }
- ],
- "defaultClientScopes": [
- "web-origins",
- "role_list",
- "profile",
- "roles",
- "email",
- "zuul_audience"
- ],
- "optionalClientScopes": [
- "address",
- "phone",
- "offline_access",
- "microprofile-jwt"
- ]
- }
- ],
- "clientScopes": [
- {
- "id": "a8ff8d15-7e18-46a7-afe9-cb4b51317f21",
- "name": "address",
- "description": "OpenID Connect built-in scope: address",
- "protocol": "openid-connect",
- "attributes": {
- "include.in.token.scope": "true",
- "display.on.consent.screen": "true",
- "consent.screen.text": "${addressScopeConsentText}"
- },
- "protocolMappers": [
- {
- "id": "2fb91b25-bf0f-4d30-8c32-8da4d6e2e14a",
- "name": "address",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-address-mapper",
- "consentRequired": false,
- "config": {
- "user.attribute.formatted": "formatted",
- "user.attribute.country": "country",
- "user.attribute.postal_code": "postal_code",
- "userinfo.token.claim": "true",
- "user.attribute.street": "street",
- "id.token.claim": "true",
- "user.attribute.region": "region",
- "access.token.claim": "true",
- "user.attribute.locality": "locality"
- }
- }
- ]
- },
- {
- "id": "2491f163-0678-4222-974b-42851cb8bbea",
- "name": "email",
- "description": "OpenID Connect built-in scope: email",
- "protocol": "openid-connect",
- "attributes": {
- "include.in.token.scope": "true",
- "display.on.consent.screen": "true",
- "consent.screen.text": "${emailScopeConsentText}"
},
- "protocolMappers": [
- {
- "id": "247ada79-fd01-4e62-9231-46e09f2de990",
- "name": "email",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-property-mapper",
- "consentRequired": false,
- "config": {
- "userinfo.token.claim": "true",
- "user.attribute": "email",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "claim.name": "email",
- "jsonType.label": "String"
+ "clientRole" : false,
+ "containerId" : "zuul-demo",
+ "attributes" : { }
+ }, {
+ "id" : "a52bd4e0-3f93-4eaf-9792-b5a2b4f564b1",
+ "name" : "uma_authorization",
+ "description" : "${role_uma_authorization}",
+ "composite" : false,
+ "clientRole" : false,
+ "containerId" : "zuul-demo",
+ "attributes" : { }
+ } ],
+ "client" : {
+ "realm-management" : [ {
+ "id" : "76b84ff6-843c-4fbc-903e-56f82324726a",
+ "name" : "query-groups",
+ "description" : "${role_query-groups}",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "a9986aaf-a807-43db-b27e-bfcb4a6ed680",
+ "attributes" : { }
+ }, {
+ "id" : "e95f28d5-0a2c-42e5-ac27-ba8c1e49ca9e",
+ "name" : "manage-users",
+ "description" : "${role_manage-users}",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "a9986aaf-a807-43db-b27e-bfcb4a6ed680",
+ "attributes" : { }
+ }, {
+ "id" : "c57c009d-8cda-40a2-9f94-9faec21684d1",
+ "name" : "view-clients",
+ "description" : "${role_view-clients}",
+ "composite" : true,
+ "composites" : {
+ "client" : {
+ "realm-management" : [ "query-clients" ]
}
},
- {
- "id": "6a89efdc-4a19-4059-943f-fbf4b0c80fbc",
- "name": "email verified",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-property-mapper",
- "consentRequired": false,
- "config": {
- "userinfo.token.claim": "true",
- "user.attribute": "emailVerified",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "claim.name": "email_verified",
- "jsonType.label": "boolean"
- }
- }
- ]
- },
- {
- "id": "77dc1544-6890-4714-acb5-bce0d34c15d5",
- "name": "microprofile-jwt",
- "description": "Microprofile - JWT built-in scope",
- "protocol": "openid-connect",
- "attributes": {
- "include.in.token.scope": "true",
- "display.on.consent.screen": "false"
- },
- "protocolMappers": [
- {
- "id": "f1cc4b3b-6f0d-4564-b157-8e7790764643",
- "name": "groups",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-realm-role-mapper",
- "consentRequired": false,
- "config": {
- "multivalued": "true",
- "userinfo.token.claim": "true",
- "user.attribute": "foo",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "claim.name": "groups",
- "jsonType.label": "String"
- }
- },
- {
- "id": "e9382111-9f81-455b-805a-9252c4c3db24",
- "name": "upn",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-property-mapper",
- "consentRequired": false,
- "config": {
- "userinfo.token.claim": "true",
- "user.attribute": "username",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "claim.name": "upn",
- "jsonType.label": "String"
- }
- }
- ]
- },
- {
- "id": "7652d555-2c47-4312-85bb-b33e3f6d53ac",
- "name": "offline_access",
- "description": "OpenID Connect built-in scope: offline_access",
- "protocol": "openid-connect",
- "attributes": {
- "consent.screen.text": "${offlineAccessScopeConsentText}",
- "display.on.consent.screen": "true"
- }
- },
- {
- "id": "e0d29ae8-b246-4d7c-bb0e-9b1f3b2fb95c",
- "name": "phone",
- "description": "OpenID Connect built-in scope: phone",
- "protocol": "openid-connect",
- "attributes": {
- "include.in.token.scope": "true",
- "display.on.consent.screen": "true",
- "consent.screen.text": "${phoneScopeConsentText}"
- },
- "protocolMappers": [
- {
- "id": "b68bd653-6780-4cd7-a588-31073e2cc88b",
- "name": "phone number verified",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-attribute-mapper",
- "consentRequired": false,
- "config": {
- "userinfo.token.claim": "true",
- "user.attribute": "phoneNumberVerified",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "claim.name": "phone_number_verified",
- "jsonType.label": "boolean"
- }
- },
- {
- "id": "08a83a89-6029-4c5d-9fe1-3aaaed3a1579",
- "name": "phone number",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-attribute-mapper",
- "consentRequired": false,
- "config": {
- "userinfo.token.claim": "true",
- "user.attribute": "phoneNumber",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "claim.name": "phone_number",
- "jsonType.label": "String"
- }
- }
- ]
- },
- {
- "id": "2890097c-f60f-4690-a964-005272790b26",
- "name": "profile",
- "description": "OpenID Connect built-in scope: profile",
- "protocol": "openid-connect",
- "attributes": {
- "include.in.token.scope": "true",
- "display.on.consent.screen": "true",
- "consent.screen.text": "${profileScopeConsentText}"
- },
- "protocolMappers": [
- {
- "id": "c7fa5831-9380-42d8-929a-a31e89b0dca5",
- "name": "given name",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-property-mapper",
- "consentRequired": false,
- "config": {
- "userinfo.token.claim": "true",
- "user.attribute": "firstName",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "claim.name": "given_name",
- "jsonType.label": "String"
- }
- },
- {
- "id": "fcb6f43b-0dfd-42c4-8f96-2b25078be8cf",
- "name": "middle name",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-attribute-mapper",
- "consentRequired": false,
- "config": {
- "userinfo.token.claim": "true",
- "user.attribute": "middleName",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "claim.name": "middle_name",
- "jsonType.label": "String"
- }
- },
- {
- "id": "15cc1330-fc07-4852-bd72-746ea7c70fc1",
- "name": "locale",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-attribute-mapper",
- "consentRequired": false,
- "config": {
- "userinfo.token.claim": "true",
- "user.attribute": "locale",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "claim.name": "locale",
- "jsonType.label": "String"
- }
- },
- {
- "id": "59e8530c-0630-46e3-b1db-629bd8bafa78",
- "name": "birthdate",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-attribute-mapper",
- "consentRequired": false,
- "config": {
- "userinfo.token.claim": "true",
- "user.attribute": "birthdate",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "claim.name": "birthdate",
- "jsonType.label": "String"
- }
- },
- {
- "id": "42a077a7-5a55-4838-b952-33f049fb5fc3",
- "name": "family name",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-property-mapper",
- "consentRequired": false,
- "config": {
- "userinfo.token.claim": "true",
- "user.attribute": "lastName",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "claim.name": "family_name",
- "jsonType.label": "String"
- }
- },
- {
- "id": "bdc4888d-48ae-4d56-b1cd-d256c50d9b64",
- "name": "nickname",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-attribute-mapper",
- "consentRequired": false,
- "config": {
- "userinfo.token.claim": "true",
- "user.attribute": "nickname",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "claim.name": "nickname",
- "jsonType.label": "String"
- }
- },
- {
- "id": "97fb10c8-ff6e-4ccc-a25a-cfae87e783ef",
- "name": "profile",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-attribute-mapper",
- "consentRequired": false,
- "config": {
- "userinfo.token.claim": "true",
- "user.attribute": "profile",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "claim.name": "profile",
- "jsonType.label": "String"
+ "clientRole" : true,
+ "containerId" : "a9986aaf-a807-43db-b27e-bfcb4a6ed680",
+ "attributes" : { }
+ }, {
+ "id" : "8eee1534-e312-4469-803e-ef89a2c4e02b",
+ "name" : "manage-identity-providers",
+ "description" : "${role_manage-identity-providers}",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "a9986aaf-a807-43db-b27e-bfcb4a6ed680",
+ "attributes" : { }
+ }, {
+ "id" : "f9b1241a-c8ba-4842-841c-7c9f6126d13e",
+ "name" : "realm-admin",
+ "description" : "${role_realm-admin}",
+ "composite" : true,
+ "composites" : {
+ "client" : {
+ "realm-management" : [ "query-groups", "view-clients", "manage-users", "view-identity-providers", "manage-identity-providers", "manage-clients", "query-realms", "manage-realm", "view-events", "create-client", "query-clients", "view-users", "view-realm", "manage-authorization", "impersonation", "query-users", "manage-events", "view-authorization" ]
}
},
- {
- "id": "adb8eb09-943b-4e6f-a7ee-bc531a55e359",
- "name": "gender",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-attribute-mapper",
- "consentRequired": false,
- "config": {
- "userinfo.token.claim": "true",
- "user.attribute": "gender",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "claim.name": "gender",
- "jsonType.label": "String"
+ "clientRole" : true,
+ "containerId" : "a9986aaf-a807-43db-b27e-bfcb4a6ed680",
+ "attributes" : { }
+ }, {
+ "id" : "12782ab5-4041-4c49-962a-08d0560fd5ed",
+ "name" : "view-identity-providers",
+ "description" : "${role_view-identity-providers}",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "a9986aaf-a807-43db-b27e-bfcb4a6ed680",
+ "attributes" : { }
+ }, {
+ "id" : "1d33026a-2968-4b14-931a-4d855bb82bbc",
+ "name" : "manage-clients",
+ "description" : "${role_manage-clients}",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "a9986aaf-a807-43db-b27e-bfcb4a6ed680",
+ "attributes" : { }
+ }, {
+ "id" : "74152341-86df-4741-ad3d-2a4cc4037137",
+ "name" : "manage-realm",
+ "description" : "${role_manage-realm}",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "a9986aaf-a807-43db-b27e-bfcb4a6ed680",
+ "attributes" : { }
+ }, {
+ "id" : "56ac5037-efe9-4b66-a8c9-41d6c573227a",
+ "name" : "query-realms",
+ "description" : "${role_query-realms}",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "a9986aaf-a807-43db-b27e-bfcb4a6ed680",
+ "attributes" : { }
+ }, {
+ "id" : "9a648218-df86-4203-bc91-29825d91f802",
+ "name" : "view-events",
+ "description" : "${role_view-events}",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "a9986aaf-a807-43db-b27e-bfcb4a6ed680",
+ "attributes" : { }
+ }, {
+ "id" : "e6b054ff-8f2b-43e2-8d4a-f7003b893203",
+ "name" : "create-client",
+ "description" : "${role_create-client}",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "a9986aaf-a807-43db-b27e-bfcb4a6ed680",
+ "attributes" : { }
+ }, {
+ "id" : "3201e1fc-57b4-4531-a142-adda7c9a132a",
+ "name" : "query-clients",
+ "description" : "${role_query-clients}",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "a9986aaf-a807-43db-b27e-bfcb4a6ed680",
+ "attributes" : { }
+ }, {
+ "id" : "e5e6855e-215a-4741-970c-269d201a7e9c",
+ "name" : "view-realm",
+ "description" : "${role_view-realm}",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "a9986aaf-a807-43db-b27e-bfcb4a6ed680",
+ "attributes" : { }
+ }, {
+ "id" : "c7599b5a-5374-4867-a6f1-5f4608da8196",
+ "name" : "view-users",
+ "description" : "${role_view-users}",
+ "composite" : true,
+ "composites" : {
+ "client" : {
+ "realm-management" : [ "query-groups", "query-users" ]
}
},
- {
- "id": "c2260f1f-eb68-4186-b86c-74c23a450151",
- "name": "zoneinfo",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-attribute-mapper",
- "consentRequired": false,
- "config": {
- "userinfo.token.claim": "true",
- "user.attribute": "zoneinfo",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "claim.name": "zoneinfo",
- "jsonType.label": "String"
+ "clientRole" : true,
+ "containerId" : "a9986aaf-a807-43db-b27e-bfcb4a6ed680",
+ "attributes" : { }
+ }, {
+ "id" : "36aecf8f-3265-41c1-b585-307005ee02af",
+ "name" : "manage-authorization",
+ "description" : "${role_manage-authorization}",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "a9986aaf-a807-43db-b27e-bfcb4a6ed680",
+ "attributes" : { }
+ }, {
+ "id" : "775ac110-9dc8-4dd5-93e7-45b61e9dd29a",
+ "name" : "impersonation",
+ "description" : "${role_impersonation}",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "a9986aaf-a807-43db-b27e-bfcb4a6ed680",
+ "attributes" : { }
+ }, {
+ "id" : "f00e65f4-2e53-4684-9eb4-b55c1c0ef4a3",
+ "name" : "query-users",
+ "description" : "${role_query-users}",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "a9986aaf-a807-43db-b27e-bfcb4a6ed680",
+ "attributes" : { }
+ }, {
+ "id" : "888e5d3f-1cb5-48f6-b3de-90acad8472f1",
+ "name" : "manage-events",
+ "description" : "${role_manage-events}",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "a9986aaf-a807-43db-b27e-bfcb4a6ed680",
+ "attributes" : { }
+ }, {
+ "id" : "125b0c77-45a3-4772-ab9d-cf45803cb3ef",
+ "name" : "view-authorization",
+ "description" : "${role_view-authorization}",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "a9986aaf-a807-43db-b27e-bfcb4a6ed680",
+ "attributes" : { }
+ } ],
+ "security-admin-console" : [ ],
+ "admin-cli" : [ ],
+ "account-console" : [ ],
+ "zuul" : [ ],
+ "broker" : [ {
+ "id" : "ec4aeaa2-8d80-47b3-a863-928f93b0157f",
+ "name" : "read-token",
+ "description" : "${role_read-token}",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "1f0c06e8-299d-4818-aada-4c59e326cbf8",
+ "attributes" : { }
+ } ],
+ "account" : [ {
+ "id" : "2c559af2-6553-42bd-a98b-de61ba35901a",
+ "name" : "view-applications",
+ "description" : "${role_view-applications}",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "38f83427-736e-4570-9f68-25a8b53f0b51",
+ "attributes" : { }
+ }, {
+ "id" : "976602d1-7350-4204-b8a1-00fd5349c7a6",
+ "name" : "view-consent",
+ "description" : "${role_view-consent}",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "38f83427-736e-4570-9f68-25a8b53f0b51",
+ "attributes" : { }
+ }, {
+ "id" : "edfc8de7-a5b8-4e33-8ca8-c577c40bb3e2",
+ "name" : "view-profile",
+ "description" : "${role_view-profile}",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "38f83427-736e-4570-9f68-25a8b53f0b51",
+ "attributes" : { }
+ }, {
+ "id" : "307ea046-f4ae-4cd4-b170-7f583682537b",
+ "name" : "delete-account",
+ "description" : "${role_delete-account}",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "38f83427-736e-4570-9f68-25a8b53f0b51",
+ "attributes" : { }
+ }, {
+ "id" : "0b943331-83af-4fac-b7c9-0ac6f4b73384",
+ "name" : "manage-account",
+ "description" : "${role_manage-account}",
+ "composite" : true,
+ "composites" : {
+ "client" : {
+ "account" : [ "manage-account-links" ]
}
},
- {
- "id": "e618b191-1c20-466d-a412-9b59a221d587",
- "name": "updated at",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-attribute-mapper",
- "consentRequired": false,
- "config": {
- "userinfo.token.claim": "true",
- "user.attribute": "updatedAt",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "claim.name": "updated_at",
- "jsonType.label": "String"
+ "clientRole" : true,
+ "containerId" : "38f83427-736e-4570-9f68-25a8b53f0b51",
+ "attributes" : { }
+ }, {
+ "id" : "4b64f538-1336-44eb-ba18-c26859d7291a",
+ "name" : "manage-account-links",
+ "description" : "${role_manage-account-links}",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "38f83427-736e-4570-9f68-25a8b53f0b51",
+ "attributes" : { }
+ }, {
+ "id" : "12a120c6-1df6-47f6-b0ff-a3196cef8885",
+ "name" : "manage-consent",
+ "description" : "${role_manage-consent}",
+ "composite" : true,
+ "composites" : {
+ "client" : {
+ "account" : [ "view-consent" ]
}
},
- {
- "id": "bf72e820-32b2-41ff-a812-7349dbc97dc1",
- "name": "website",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-attribute-mapper",
- "consentRequired": false,
- "config": {
- "userinfo.token.claim": "true",
- "user.attribute": "website",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "claim.name": "website",
- "jsonType.label": "String"
- }
- },
- {
- "id": "00ec085d-5cdc-4009-968a-bc03843e0418",
- "name": "username",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-property-mapper",
- "consentRequired": false,
- "config": {
- "userinfo.token.claim": "true",
- "user.attribute": "username",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "claim.name": "preferred_username",
- "jsonType.label": "String"
- }
- },
- {
- "id": "e7f8706d-04b2-4ceb-97e0-d6c50f268653",
- "name": "picture",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-attribute-mapper",
- "consentRequired": false,
- "config": {
- "userinfo.token.claim": "true",
- "user.attribute": "picture",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "claim.name": "picture",
- "jsonType.label": "String"
- }
- },
- {
- "id": "dc881508-6029-4af0-9ab4-bc84613d0bfe",
- "name": "full name",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-full-name-mapper",
- "consentRequired": false,
- "config": {
- "id.token.claim": "true",
- "access.token.claim": "true",
- "userinfo.token.claim": "true"
- }
- }
- ]
- },
- {
- "id": "a45a99e9-3413-4dbe-a1bf-da76d82c6887",
- "name": "role_list",
- "description": "SAML role list",
- "protocol": "saml",
- "attributes": {
- "consent.screen.text": "${samlRoleListScopeConsentText}",
- "display.on.consent.screen": "true"
- },
- "protocolMappers": [
- {
- "id": "8fdf3fb9-4eeb-4e14-986e-7c9711838ca5",
- "name": "role list",
- "protocol": "saml",
- "protocolMapper": "saml-role-list-mapper",
- "consentRequired": false,
- "config": {
- "single": "false",
- "attribute.nameformat": "Basic",
- "attribute.name": "Role"
- }
- }
- ]
- },
- {
- "id": "cf8edb74-1db4-4cc0-89fa-ec2a9ef19565",
- "name": "roles",
- "description": "OpenID Connect scope for add user roles to the access token",
- "protocol": "openid-connect",
- "attributes": {
- "include.in.token.scope": "false",
- "display.on.consent.screen": "true",
- "consent.screen.text": "${rolesScopeConsentText}"
- },
- "protocolMappers": [
- {
- "id": "67629d5f-39aa-4521-b936-91964357e630",
- "name": "audience resolve",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-audience-resolve-mapper",
- "consentRequired": false,
- "config": {}
- },
- {
- "id": "75458c67-6f8c-4646-a851-c3a5f6c9c6e1",
- "name": "client roles",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-client-role-mapper",
- "consentRequired": false,
- "config": {
- "user.attribute": "foo",
- "access.token.claim": "true",
- "claim.name": "resource_access.${client_id}.roles",
- "jsonType.label": "String",
- "multivalued": "true"
- }
- },
- {
- "id": "059aa66a-7d91-4116-8b4e-51f5ab57424c",
- "name": "realm roles",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-realm-role-mapper",
- "consentRequired": false,
- "config": {
- "user.attribute": "foo",
- "access.token.claim": "true",
- "claim.name": "realm_access.roles",
- "jsonType.label": "String",
- "multivalued": "true"
- }
- }
- ]
- },
- {
- "id": "2637f0c2-5d96-4057-a032-ce8f11477048",
- "name": "web-origins",
- "description": "OpenID Connect scope for add allowed web origins to the access token",
- "protocol": "openid-connect",
- "attributes": {
- "include.in.token.scope": "false",
- "display.on.consent.screen": "false",
- "consent.screen.text": ""
- },
- "protocolMappers": [
- {
- "id": "33cbf353-644c-4801-91c1-b6ca677f65d2",
- "name": "allowed web origins",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-allowed-origins-mapper",
- "consentRequired": false,
- "config": {}
- }
- ]
- },
- {
- "id": "ace09ede-6cf1-4e36-9557-91beb58ce557",
- "name": "zuul_audience",
- "description": "fix audience claim for Zuul",
- "protocol": "openid-connect",
- "attributes": {
- "include.in.token.scope": "true",
- "display.on.consent.screen": "true"
- },
- "protocolMappers": [
- {
- "id": "9a1029e7-f142-4ff1-ba47-50ca6bb24073",
- "name": "zuul_audience_mapper",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-audience-mapper",
- "consentRequired": false,
- "config": {
- "included.client.audience": "zuul",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "userinfo.token.claim": "true"
- }
- }
- ]
+ "clientRole" : true,
+ "containerId" : "38f83427-736e-4570-9f68-25a8b53f0b51",
+ "attributes" : { }
+ } ]
}
- ],
- "defaultDefaultClientScopes": [
- "email",
- "web-origins",
- "profile",
- "role_list",
- "roles"
- ],
- "defaultOptionalClientScopes": [
- "offline_access",
- "microprofile-jwt",
- "address",
- "phone"
- ],
- "browserSecurityHeaders": {
- "contentSecurityPolicyReportOnly": "",
- "xContentTypeOptions": "nosniff",
- "xRobotsTag": "none",
- "xFrameOptions": "SAMEORIGIN",
- "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';",
- "xXSSProtection": "1; mode=block",
- "strictTransportSecurity": "max-age=31536000; includeSubDomains"
},
- "smtpServer": {},
- "eventsEnabled": false,
- "eventsListeners": [
- "jboss-logging"
- ],
- "enabledEventTypes": [],
- "adminEventsEnabled": false,
- "adminEventsDetailsEnabled": false,
- "identityProviders": [],
- "identityProviderMappers": [],
- "components": {
- "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [
- {
- "id": "909674c7-fb40-458b-af37-8372300725b1",
- "name": "Max Clients Limit",
- "providerId": "max-clients",
- "subType": "anonymous",
- "subComponents": {},
- "config": {
- "max-clients": [
- "200"
- ]
- }
- },
- {
- "id": "b350d08d-4298-4d76-bd94-e120b1aadd28",
- "name": "Allowed Client Scopes",
- "providerId": "allowed-client-templates",
- "subType": "anonymous",
- "subComponents": {},
- "config": {
- "allow-default-scopes": [
- "true"
- ]
- }
- },
- {
- "id": "4c1d186e-250d-497b-ac28-b7dffd021c19",
- "name": "Allowed Protocol Mapper Types",
- "providerId": "allowed-protocol-mappers",
- "subType": "anonymous",
- "subComponents": {},
- "config": {
- "allowed-protocol-mapper-types": [
- "oidc-address-mapper",
- "oidc-sha256-pairwise-sub-mapper",
- "saml-user-attribute-mapper",
- "oidc-full-name-mapper",
- "oidc-usermodel-attribute-mapper",
- "saml-user-property-mapper",
- "oidc-usermodel-property-mapper",
- "saml-role-list-mapper"
- ]
- }
- },
- {
- "id": "74a38dd7-41bf-414e-9858-f4f287c8caf3",
- "name": "Full Scope Disabled",
- "providerId": "scope",
- "subType": "anonymous",
- "subComponents": {},
- "config": {}
- },
- {
- "id": "19082549-290b-4e93-bd77-6ce5cc386b63",
- "name": "Allowed Protocol Mapper Types",
- "providerId": "allowed-protocol-mappers",
- "subType": "authenticated",
- "subComponents": {},
- "config": {
- "allowed-protocol-mapper-types": [
- "saml-user-property-mapper",
- "oidc-usermodel-attribute-mapper",
- "oidc-address-mapper",
- "oidc-usermodel-property-mapper",
- "saml-role-list-mapper",
- "saml-user-attribute-mapper",
- "oidc-full-name-mapper",
- "oidc-sha256-pairwise-sub-mapper"
- ]
- }
- },
- {
- "id": "62c50eea-fc15-40f1-a3c0-9923dcb8a8c7",
- "name": "Trusted Hosts",
- "providerId": "trusted-hosts",
- "subType": "anonymous",
- "subComponents": {},
- "config": {
- "host-sending-registration-request-must-match": [
- "true"
- ],
- "client-uris-must-match": [
- "true"
- ]
- }
- },
- {
- "id": "21095085-2c9b-45ea-99d3-9ce962ab2020",
- "name": "Allowed Client Scopes",
- "providerId": "allowed-client-templates",
- "subType": "authenticated",
- "subComponents": {},
- "config": {
- "allow-default-scopes": [
- "true"
- ]
- }
- },
- {
- "id": "36577457-a002-4b44-8029-9a599f2395a5",
- "name": "Consent Required",
- "providerId": "consent-required",
- "subType": "anonymous",
- "subComponents": {},
- "config": {}
- }
- ],
- "org.keycloak.keys.KeyProvider": [
- {
- "id": "0a6a397a-6eb1-46ab-8c6d-a3d4ea47f085",
- "name": "rsa-generated",
- "providerId": "rsa-generated",
- "subComponents": {},
- "config": {
- "privateKey": [
- "MIIEpQIBAAKCAQEA3q7PYMrX38XUpUScjbEn/4oyv5AI5TsyoFEPhgCFHzGEkeXWop3PNhh3OgFpz2Tn6qEqs9vRWi/8VDVC73lOZJWn70qfCDlr9Acmm1eKhrWkyHH6ONyg7qPj/PSsjapSsto5rVp1tdaOfObgWaLvg9dsOmbF+P6vUT6ggUD1p73KHySdDKiKHUTbaxaOVgDVTzeptDRg2iFaz+E0ETBsFKcJgXuPVd4IlxaXogDFLTKo6j2ITtnU8hw4wlqY3pkLI5OHP9EKmkV94fzmfYIoMxv4vLkwWRdz24hB459OCeDH0su25+JNnA0WA5SPRTUepeAsuZIFletYw2Mq/BFN5QIDAQABAoIBAQCX9e043VOxtjwlyAuZueJUxUdaaH1ZiStEMe0JAgPWRKF2OsVc1ZpZDRsXr204hWCqQe91K7XS+NoV0P7rkvmHNIWUi3S5VQ4xSkvzSCsVQJHB493gvdbo41iq/4Fdb3Td7oPbo9aeD1vPKnLBWKpgazrFI+tHvu1+4OEBM4YwP9KPXK4QOlxKFjzwGIeSgyMd3KnHbC6b9C8rlrf7b9Uj4UPVpGtNuPqTJyOeBTHh8CNJzYWYN0BPHk8DCMPaFkMXx42gSLsAAVOO1Kczby31Ac2IUIVDn/Z5kBmgxd6BWoVDSh1NpAlAYhK2A+LNUzveetE60CC80k387/N0JdlFAoGBAPr1v/e3rlD2pS3YA8UNQTcJjTJAvAuUw9kPXxYQd6G3UtuGwYNRw9DgZSA2E3eojzs6hLuS4W0mIVkmGwa/6SXD5TVbdaWbGO8C+x/Kxz0doRoM2AhA64uMy6zUNMwqIjroNuVn9vj8n5lMND4qqhMifYYuv9CQvyYOlXOSaFZTAoGBAOMnriWZQJqnLcd9zjr3DLxbrp/XCSLVvYRRVEleLkB+tahIFMNez50JHdTJb/TctDRfAFlZT2o0XI+FrHSqFI81UZes/cH2uqaIo+CgwRS4WY7f+Sp/ao6y+WZl2+Ip+Ao/QwjOvQ6u2jL+rCmZrPzoKoQHJaT+7WlHV2zZh9PnAoGBAOfquyCdakX/6N3YxoMPoLjP5uAN2rmJHQQ2pFSrmyKjW7rraWGF6kPZWxrNXmgyKUs+5PLC5fgMSL0t7cPrDfaMdgE9KBeGoSAfzRqwNjdQblS4kdvuwr4PuxlYcgJK3Z86gaC21xF5w7PTLGRW4R0VvpKGGVUQrtTonUxUfH9pAoGBALezd1pgvaZUXOlgDFATSvFpE6egN3s8b696Nje/Ophd4HrfECuPmUbeAIn8/dWARxuzWIzpdRfmkJRg/j667TWDYSDQfcdGyVu0VRNr7bnb/FFXQCHmOT597sOozFHyru1ai9OrnakqLrveyUw7Q3KkLv3m6cKth0IEt+cHZJkxAoGAHIGs8zI/Bp2U4Z4yHedyQ/tGF8Vk+ZehIsaHhOGEp3Cps4af5LBA5bstkArFsz+NJ7oDrxVBMUUNNypyUX4UF6EDcyZ+2vwBeill9fZqKR7BBBdC2AS6mT52gcCbFqBiGMHd+tYQj0tYnsann5e2a0ssqdXTYUn9YJO6iWh8EGY="
- ],
- "certificate": [
- "MIICoTCCAYkCBgF210UjOTANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAl6dXVsLWRlbW8wHhcNMjEwMTA2MTAzNTAxWhcNMzEwMTA2MTAzNjQxWjAUMRIwEAYDVQQDDAl6dXVsLWRlbW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDers9gytffxdSlRJyNsSf/ijK/kAjlOzKgUQ+GAIUfMYSR5dainc82GHc6AWnPZOfqoSqz29FaL/xUNULveU5klafvSp8IOWv0ByabV4qGtaTIcfo43KDuo+P89KyNqlKy2jmtWnW11o585uBZou+D12w6ZsX4/q9RPqCBQPWnvcofJJ0MqIodRNtrFo5WANVPN6m0NGDaIVrP4TQRMGwUpwmBe49V3giXFpeiAMUtMqjqPYhO2dTyHDjCWpjemQsjk4c/0QqaRX3h/OZ9gigzG/i8uTBZF3PbiEHjn04J4MfSy7bn4k2cDRYDlI9FNR6l4Cy5kgWV61jDYyr8EU3lAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAEFDFCBBNEtzOuAs46BRNBzGO2QCvLXsBASKSDFTsv2AMaFJIlc9l5U16wMcrZMTvsPY5MQxuaZvIDyAQH7NaxuRW7siZFBMRtwPmSrPHb/XqGh5OO4lXLX+Vpnx/JuMt9dOgyr9hTIYZ2UCFYxdC7ht21XSlRU6dZfEO8lTu+XEPQil42sZ/aB2efKfgWHieNvzTx3RLfG9lbsP4Dhg7FX+rGDz3Ql8PCl0Puai4pDwMjvvpZcZBjEzonw9pQ1rlSyGx0DLSJxMPMlQ33auVwLYD3MYIrgJplnScA0aKelOYJrnbSFMOlb/DwKxQNvM77banJQGq5Lpn617r2FmHNk="
- ],
- "priority": [
- "100"
- ]
- }
- },
- {
- "id": "78880d4c-8ff2-4619-82b4-02a8fbd4a624",
- "name": "hmac-generated",
- "providerId": "hmac-generated",
- "subComponents": {},
- "config": {
- "kid": [
- "a23a2cdc-8d85-4754-b885-fc14a9e1a7bc"
- ],
- "secret": [
- "k5rhshjW9QaQvYnfC4nZUzQRkLZHKJlW524t125u63s9nza8ptFhPkLl7C3AGYSS6vD3tKSfOvkGuxubsRIJKg"
- ],
- "priority": [
- "100"
- ],
- "algorithm": [
- "HS256"
- ]
- }
- },
- {
- "id": "96281deb-af4d-49f8-a35c-10993007c7df",
- "name": "aes-generated",
- "providerId": "aes-generated",
- "subComponents": {},
- "config": {
- "kid": [
- "074f1951-9f97-40ff-8f0a-4219353d6f3d"
- ],
- "secret": [
- "Csz6nXZD0tf117pBKkJ7qw"
- ],
- "priority": [
- "100"
- ]
- }
- }
- ]
+ "groups" : [ {
+ "id" : "33a7bf20-adbe-47d9-b18c-77aa9ae9f045",
+ "name" : "example-tenant-admin",
+ "path" : "/example-tenant-admin",
+ "attributes" : { },
+ "realmRoles" : [ ],
+ "clientRoles" : { },
+ "subGroups" : [ ]
+ } ],
+ "defaultRole" : {
+ "id" : "156d7b5b-65fb-4c89-b3d8-c2f82e9ecdd4",
+ "name" : "default-roles-zuul-demo",
+ "description" : "${role_default-roles}",
+ "composite" : true,
+ "clientRole" : false,
+ "containerId" : "zuul-demo"
},
- "internationalizationEnabled": false,
- "supportedLocales": [],
- "authenticationFlows": [
- {
- "id": "3e95c1d5-2e16-40ad-89d3-cc13bd514fee",
- "alias": "Account verification options",
- "description": "Method with which to verity the existing account",
- "providerId": "basic-flow",
- "topLevel": false,
- "builtIn": true,
- "authenticationExecutions": [
- {
- "authenticator": "idp-email-verification",
- "requirement": "ALTERNATIVE",
- "priority": 10,
- "userSetupAllowed": false,
- "autheticatorFlow": false
- },
- {
- "requirement": "ALTERNATIVE",
- "priority": 20,
- "flowAlias": "Verify Existing Account by Re-authentication",
- "userSetupAllowed": false,
- "autheticatorFlow": true
- }
- ]
- },
- {
- "id": "7143f715-1076-4c59-a039-34c5ec30d7e8",
- "alias": "Authentication Options",
- "description": "Authentication options.",
- "providerId": "basic-flow",
- "topLevel": false,
- "builtIn": true,
- "authenticationExecutions": [
- {
- "authenticator": "basic-auth",
- "requirement": "REQUIRED",
- "priority": 10,
- "userSetupAllowed": false,
- "autheticatorFlow": false
- },
- {
- "authenticator": "basic-auth-otp",
- "requirement": "DISABLED",
- "priority": 20,
- "userSetupAllowed": false,
- "autheticatorFlow": false
- },
- {
- "authenticator": "auth-spnego",
- "requirement": "DISABLED",
- "priority": 30,
- "userSetupAllowed": false,
- "autheticatorFlow": false
- }
- ]
- },
- {
- "id": "e3804922-4ffa-4e53-aa28-e982730f96a5",
- "alias": "Browser - Conditional OTP",
- "description": "Flow to determine if the OTP is required for the authentication",
- "providerId": "basic-flow",
- "topLevel": false,
- "builtIn": true,
- "authenticationExecutions": [
- {
- "authenticator": "conditional-user-configured",
- "requirement": "REQUIRED",
- "priority": 10,
- "userSetupAllowed": false,
- "autheticatorFlow": false
- },
- {
- "authenticator": "auth-otp-form",
- "requirement": "REQUIRED",
- "priority": 20,
- "userSetupAllowed": false,
- "autheticatorFlow": false
- }
- ]
- },
- {
- "id": "e5ba2a92-70f1-4559-b31a-968363222c72",
- "alias": "Direct Grant - Conditional OTP",
- "description": "Flow to determine if the OTP is required for the authentication",
- "providerId": "basic-flow",
- "topLevel": false,
- "builtIn": true,
- "authenticationExecutions": [
- {
- "authenticator": "conditional-user-configured",
- "requirement": "REQUIRED",
- "priority": 10,
- "userSetupAllowed": false,
- "autheticatorFlow": false
- },
- {
- "authenticator": "direct-grant-validate-otp",
- "requirement": "REQUIRED",
- "priority": 20,
- "userSetupAllowed": false,
- "autheticatorFlow": false
- }
- ]
- },
- {
- "id": "617c8570-c7aa-4b4a-8b52-3bb94dc7ba04",
- "alias": "First broker login - Conditional OTP",
- "description": "Flow to determine if the OTP is required for the authentication",
- "providerId": "basic-flow",
- "topLevel": false,
- "builtIn": true,
- "authenticationExecutions": [
- {
- "authenticator": "conditional-user-configured",
- "requirement": "REQUIRED",
- "priority": 10,
- "userSetupAllowed": false,
- "autheticatorFlow": false
- },
- {
- "authenticator": "auth-otp-form",
- "requirement": "REQUIRED",
- "priority": 20,
- "userSetupAllowed": false,
- "autheticatorFlow": false
- }
- ]
- },
- {
- "id": "7bf3d19b-f551-4c1f-9188-850923a403b1",
- "alias": "Handle Existing Account",
- "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider",
- "providerId": "basic-flow",
- "topLevel": false,
- "builtIn": true,
- "authenticationExecutions": [
- {
- "authenticator": "idp-confirm-link",
- "requirement": "REQUIRED",
- "priority": 10,
- "userSetupAllowed": false,
- "autheticatorFlow": false
- },
- {
- "requirement": "REQUIRED",
- "priority": 20,
- "flowAlias": "Account verification options",
- "userSetupAllowed": false,
- "autheticatorFlow": true
- }
- ]
- },
- {
- "id": "ff1dd822-547c-4e25-8c9e-5b7a3dd30504",
- "alias": "Reset - Conditional OTP",
- "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.",
- "providerId": "basic-flow",
- "topLevel": false,
- "builtIn": true,
- "authenticationExecutions": [
- {
- "authenticator": "conditional-user-configured",
- "requirement": "REQUIRED",
- "priority": 10,
- "userSetupAllowed": false,
- "autheticatorFlow": false
- },
- {
- "authenticator": "reset-otp",
- "requirement": "REQUIRED",
- "priority": 20,
- "userSetupAllowed": false,
- "autheticatorFlow": false
- }
- ]
- },
- {
- "id": "3b586469-9250-4bbf-9af0-f3ad81ce2eaa",
- "alias": "User creation or linking",
- "description": "Flow for the existing/non-existing user alternatives",
- "providerId": "basic-flow",
- "topLevel": false,
- "builtIn": true,
- "authenticationExecutions": [
- {
- "authenticatorConfig": "create unique user config",
- "authenticator": "idp-create-user-if-unique",
- "requirement": "ALTERNATIVE",
- "priority": 10,
- "userSetupAllowed": false,
- "autheticatorFlow": false
- },
- {
- "requirement": "ALTERNATIVE",
- "priority": 20,
- "flowAlias": "Handle Existing Account",
- "userSetupAllowed": false,
- "autheticatorFlow": true
- }
- ]
- },
- {
- "id": "80831f25-c4ab-4ad8-ad6c-f1d7b570d919",
- "alias": "Verify Existing Account by Re-authentication",
- "description": "Reauthentication of existing account",
- "providerId": "basic-flow",
- "topLevel": false,
- "builtIn": true,
- "authenticationExecutions": [
- {
- "authenticator": "idp-username-password-form",
- "requirement": "REQUIRED",
- "priority": 10,
- "userSetupAllowed": false,
- "autheticatorFlow": false
- },
- {
- "requirement": "CONDITIONAL",
- "priority": 20,
- "flowAlias": "First broker login - Conditional OTP",
- "userSetupAllowed": false,
- "autheticatorFlow": true
- }
- ]
- },
- {
- "id": "dec78792-6cf8-4752-9833-18c1386423df",
- "alias": "browser",
- "description": "browser based authentication",
- "providerId": "basic-flow",
- "topLevel": true,
- "builtIn": true,
- "authenticationExecutions": [
- {
- "authenticator": "auth-cookie",
- "requirement": "ALTERNATIVE",
- "priority": 10,
- "userSetupAllowed": false,
- "autheticatorFlow": false
- },
- {
- "authenticator": "auth-spnego",
- "requirement": "DISABLED",
- "priority": 20,
- "userSetupAllowed": false,
- "autheticatorFlow": false
- },
- {
- "authenticator": "identity-provider-redirector",
- "requirement": "ALTERNATIVE",
- "priority": 25,
- "userSetupAllowed": false,
- "autheticatorFlow": false
- },
- {
- "requirement": "ALTERNATIVE",
- "priority": 30,
- "flowAlias": "forms",
- "userSetupAllowed": false,
- "autheticatorFlow": true
- }
- ]
- },
- {
- "id": "14c06286-51e3-4abc-af50-8554c57c9f8f",
- "alias": "clients",
- "description": "Base authentication for clients",
- "providerId": "client-flow",
- "topLevel": true,
- "builtIn": true,
- "authenticationExecutions": [
- {
- "authenticator": "client-secret",
- "requirement": "ALTERNATIVE",
- "priority": 10,
- "userSetupAllowed": false,
- "autheticatorFlow": false
- },
- {
- "authenticator": "client-jwt",
- "requirement": "ALTERNATIVE",
- "priority": 20,
- "userSetupAllowed": false,
- "autheticatorFlow": false
- },
- {
- "authenticator": "client-secret-jwt",
- "requirement": "ALTERNATIVE",
- "priority": 30,
- "userSetupAllowed": false,
- "autheticatorFlow": false
- },
- {
- "authenticator": "client-x509",
- "requirement": "ALTERNATIVE",
- "priority": 40,
- "userSetupAllowed": false,
- "autheticatorFlow": false
- }
- ]
+ "requiredCredentials" : [ "password" ],
+ "otpPolicyType" : "totp",
+ "otpPolicyAlgorithm" : "HmacSHA1",
+ "otpPolicyInitialCounter" : 0,
+ "otpPolicyDigits" : 6,
+ "otpPolicyLookAheadWindow" : 1,
+ "otpPolicyPeriod" : 30,
+ "otpSupportedApplications" : [ "FreeOTP", "Google Authenticator" ],
+ "webAuthnPolicyRpEntityName" : "keycloak",
+ "webAuthnPolicySignatureAlgorithms" : [ "ES256" ],
+ "webAuthnPolicyRpId" : "",
+ "webAuthnPolicyAttestationConveyancePreference" : "not specified",
+ "webAuthnPolicyAuthenticatorAttachment" : "not specified",
+ "webAuthnPolicyRequireResidentKey" : "not specified",
+ "webAuthnPolicyUserVerificationRequirement" : "not specified",
+ "webAuthnPolicyCreateTimeout" : 0,
+ "webAuthnPolicyAvoidSameAuthenticatorRegister" : false,
+ "webAuthnPolicyAcceptableAaguids" : [ ],
+ "webAuthnPolicyPasswordlessRpEntityName" : "keycloak",
+ "webAuthnPolicyPasswordlessSignatureAlgorithms" : [ "ES256" ],
+ "webAuthnPolicyPasswordlessRpId" : "",
+ "webAuthnPolicyPasswordlessAttestationConveyancePreference" : "not specified",
+ "webAuthnPolicyPasswordlessAuthenticatorAttachment" : "not specified",
+ "webAuthnPolicyPasswordlessRequireResidentKey" : "not specified",
+ "webAuthnPolicyPasswordlessUserVerificationRequirement" : "not specified",
+ "webAuthnPolicyPasswordlessCreateTimeout" : 0,
+ "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister" : false,
+ "webAuthnPolicyPasswordlessAcceptableAaguids" : [ ],
+ "users" : [ {
+ "id" : "b19092bd-b767-4175-9d9a-e8749b15012b",
+ "createdTimestamp" : 1658485873780,
+ "username" : "admin",
+ "enabled" : true,
+ "totp" : false,
+ "emailVerified" : false,
+ "firstName" : "Zuul",
+ "lastName" : "Administrator",
+ "email" : "admin@example.com",
+ "credentials" : [ {
+ "id" : "77e16b56-2d90-43f9-8064-ec1330dc9d60",
+ "type" : "password",
+ "createdDate" : 1658485888588,
+ "secretData" : "{\"value\":\"3mxFAhY4UWLcnbooTrxgKZEH3dp5H78VlxVPU0KRFveg6YtIm+onPncXU8qx3PYDcfZjCbKuPghhFSJhaXxVnw==\",\"salt\":\"PomCTIfrYWl4leXhqAkUsQ==\",\"additionalParameters\":{}}",
+ "credentialData" : "{\"hashIterations\":27500,\"algorithm\":\"pbkdf2-sha256\",\"additionalParameters\":{}}"
+ } ],
+ "disableableCredentialTypes" : [ ],
+ "requiredActions" : [ ],
+ "realmRoles" : [ "default-roles-zuul-demo" ],
+ "notBefore" : 0,
+ "groups" : [ ]
+ }, {
+ "id" : "2c3b0f44-0488-42fe-acac-2d5e483b9806",
+ "createdTimestamp" : 1658485982882,
+ "username" : "user1",
+ "enabled" : true,
+ "totp" : false,
+ "emailVerified" : false,
+ "firstName" : "User",
+ "lastName" : "One",
+ "email" : "user1@example.com",
+ "credentials" : [ {
+ "id" : "52b67f84-8383-4684-8d45-ab9838ebda07",
+ "type" : "password",
+ "createdDate" : 1658485994760,
+ "secretData" : "{\"value\":\"RII4dJdPibGBeya28QKmZaZkumVXj0GltwHlUdv7w7IR7X+1JtlPke2Z7UfZYeb09CkHSj4xk3EaCMaQl4uUoA==\",\"salt\":\"C68vc47R5kfemCeGAbOKIQ==\",\"additionalParameters\":{}}",
+ "credentialData" : "{\"hashIterations\":27500,\"algorithm\":\"pbkdf2-sha256\",\"additionalParameters\":{}}"
+ } ],
+ "disableableCredentialTypes" : [ ],
+ "requiredActions" : [ ],
+ "realmRoles" : [ "default-roles-zuul-demo" ],
+ "notBefore" : 0,
+ "groups" : [ "/example-tenant-admin" ]
+ }, {
+ "id" : "832d027d-5ef8-4968-b950-e57944dde6d6",
+ "createdTimestamp" : 1658486020309,
+ "username" : "user2",
+ "enabled" : true,
+ "totp" : false,
+ "emailVerified" : false,
+ "firstName" : "User",
+ "lastName" : "Two",
+ "email" : "user2@example.com",
+ "credentials" : [ {
+ "id" : "304178ea-219a-4495-bca1-76361e841ce0",
+ "type" : "password",
+ "createdDate" : 1658486030056,
+ "secretData" : "{\"value\":\"uDqEwwwGuxSMttycSI+YMRkRxHGozlWxGJ4h2YZ21CXNSpx3BN/i4xAJ47nhhZzDoZKYPVqiTpWXFgVpaYb2jA==\",\"salt\":\"ADhTsq7HIlBEZ0KmurA9/Q==\",\"additionalParameters\":{}}",
+ "credentialData" : "{\"hashIterations\":27500,\"algorithm\":\"pbkdf2-sha256\",\"additionalParameters\":{}}"
+ } ],
+ "disableableCredentialTypes" : [ ],
+ "requiredActions" : [ ],
+ "realmRoles" : [ "default-roles-zuul-demo" ],
+ "notBefore" : 0,
+ "groups" : [ ]
+ } ],
+ "scopeMappings" : [ {
+ "clientScope" : "offline_access",
+ "roles" : [ "offline_access" ]
+ } ],
+ "clientScopeMappings" : {
+ "account" : [ {
+ "client" : "account-console",
+ "roles" : [ "manage-account" ]
+ } ]
+ },
+ "clients" : [ {
+ "id" : "38f83427-736e-4570-9f68-25a8b53f0b51",
+ "clientId" : "account",
+ "name" : "${client_account}",
+ "rootUrl" : "${authBaseUrl}",
+ "baseUrl" : "/realms/zuul-demo/account/",
+ "surrogateAuthRequired" : false,
+ "enabled" : true,
+ "alwaysDisplayInConsole" : false,
+ "clientAuthenticatorType" : "client-secret",
+ "redirectUris" : [ "/realms/zuul-demo/account/*" ],
+ "webOrigins" : [ ],
+ "notBefore" : 0,
+ "bearerOnly" : false,
+ "consentRequired" : false,
+ "standardFlowEnabled" : true,
+ "implicitFlowEnabled" : false,
+ "directAccessGrantsEnabled" : false,
+ "serviceAccountsEnabled" : false,
+ "publicClient" : true,
+ "frontchannelLogout" : false,
+ "protocol" : "openid-connect",
+ "attributes" : { },
+ "authenticationFlowBindingOverrides" : { },
+ "fullScopeAllowed" : false,
+ "nodeReRegistrationTimeout" : 0,
+ "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "email" ],
+ "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ]
+ }, {
+ "id" : "a124c422-d35d-4ad9-bb85-4df87ae56fb3",
+ "clientId" : "account-console",
+ "name" : "${client_account-console}",
+ "rootUrl" : "${authBaseUrl}",
+ "baseUrl" : "/realms/zuul-demo/account/",
+ "surrogateAuthRequired" : false,
+ "enabled" : true,
+ "alwaysDisplayInConsole" : false,
+ "clientAuthenticatorType" : "client-secret",
+ "redirectUris" : [ "/realms/zuul-demo/account/*" ],
+ "webOrigins" : [ ],
+ "notBefore" : 0,
+ "bearerOnly" : false,
+ "consentRequired" : false,
+ "standardFlowEnabled" : true,
+ "implicitFlowEnabled" : false,
+ "directAccessGrantsEnabled" : false,
+ "serviceAccountsEnabled" : false,
+ "publicClient" : true,
+ "frontchannelLogout" : false,
+ "protocol" : "openid-connect",
+ "attributes" : {
+ "pkce.code.challenge.method" : "S256"
},
- {
- "id": "54d29594-0183-486f-abb2-c1998d4e45c5",
- "alias": "direct grant",
- "description": "OpenID Connect Resource Owner Grant",
- "providerId": "basic-flow",
- "topLevel": true,
- "builtIn": true,
- "authenticationExecutions": [
- {
- "authenticator": "direct-grant-validate-username",
- "requirement": "REQUIRED",
- "priority": 10,
- "userSetupAllowed": false,
- "autheticatorFlow": false
- },
- {
- "authenticator": "direct-grant-validate-password",
- "requirement": "REQUIRED",
- "priority": 20,
- "userSetupAllowed": false,
- "autheticatorFlow": false
- },
- {
- "requirement": "CONDITIONAL",
- "priority": 30,
- "flowAlias": "Direct Grant - Conditional OTP",
- "userSetupAllowed": false,
- "autheticatorFlow": true
- }
- ]
+ "authenticationFlowBindingOverrides" : { },
+ "fullScopeAllowed" : false,
+ "nodeReRegistrationTimeout" : 0,
+ "protocolMappers" : [ {
+ "id" : "849fdec6-5827-4e3e-b9bb-25fe91a8c3af",
+ "name" : "audience resolve",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-audience-resolve-mapper",
+ "consentRequired" : false,
+ "config" : { }
+ } ],
+ "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "email" ],
+ "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ]
+ }, {
+ "id" : "24514749-6257-4d58-bc00-e68592d4f8ee",
+ "clientId" : "admin-cli",
+ "name" : "${client_admin-cli}",
+ "surrogateAuthRequired" : false,
+ "enabled" : true,
+ "alwaysDisplayInConsole" : false,
+ "clientAuthenticatorType" : "client-secret",
+ "redirectUris" : [ ],
+ "webOrigins" : [ ],
+ "notBefore" : 0,
+ "bearerOnly" : false,
+ "consentRequired" : false,
+ "standardFlowEnabled" : false,
+ "implicitFlowEnabled" : false,
+ "directAccessGrantsEnabled" : true,
+ "serviceAccountsEnabled" : false,
+ "publicClient" : true,
+ "frontchannelLogout" : false,
+ "protocol" : "openid-connect",
+ "attributes" : { },
+ "authenticationFlowBindingOverrides" : { },
+ "fullScopeAllowed" : false,
+ "nodeReRegistrationTimeout" : 0,
+ "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "email" ],
+ "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ]
+ }, {
+ "id" : "1f0c06e8-299d-4818-aada-4c59e326cbf8",
+ "clientId" : "broker",
+ "name" : "${client_broker}",
+ "surrogateAuthRequired" : false,
+ "enabled" : true,
+ "alwaysDisplayInConsole" : false,
+ "clientAuthenticatorType" : "client-secret",
+ "redirectUris" : [ ],
+ "webOrigins" : [ ],
+ "notBefore" : 0,
+ "bearerOnly" : true,
+ "consentRequired" : false,
+ "standardFlowEnabled" : true,
+ "implicitFlowEnabled" : false,
+ "directAccessGrantsEnabled" : false,
+ "serviceAccountsEnabled" : false,
+ "publicClient" : false,
+ "frontchannelLogout" : false,
+ "protocol" : "openid-connect",
+ "attributes" : { },
+ "authenticationFlowBindingOverrides" : { },
+ "fullScopeAllowed" : false,
+ "nodeReRegistrationTimeout" : 0,
+ "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "email" ],
+ "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ]
+ }, {
+ "id" : "a9986aaf-a807-43db-b27e-bfcb4a6ed680",
+ "clientId" : "realm-management",
+ "name" : "${client_realm-management}",
+ "surrogateAuthRequired" : false,
+ "enabled" : true,
+ "alwaysDisplayInConsole" : false,
+ "clientAuthenticatorType" : "client-secret",
+ "redirectUris" : [ ],
+ "webOrigins" : [ ],
+ "notBefore" : 0,
+ "bearerOnly" : true,
+ "consentRequired" : false,
+ "standardFlowEnabled" : true,
+ "implicitFlowEnabled" : false,
+ "directAccessGrantsEnabled" : false,
+ "serviceAccountsEnabled" : false,
+ "publicClient" : false,
+ "frontchannelLogout" : false,
+ "protocol" : "openid-connect",
+ "attributes" : { },
+ "authenticationFlowBindingOverrides" : { },
+ "fullScopeAllowed" : false,
+ "nodeReRegistrationTimeout" : 0,
+ "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "email" ],
+ "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ]
+ }, {
+ "id" : "952f56db-5191-41fd-ac39-572ba28d9ee5",
+ "clientId" : "security-admin-console",
+ "name" : "${client_security-admin-console}",
+ "rootUrl" : "${authAdminUrl}",
+ "baseUrl" : "/admin/zuul-demo/console/",
+ "surrogateAuthRequired" : false,
+ "enabled" : true,
+ "alwaysDisplayInConsole" : false,
+ "clientAuthenticatorType" : "client-secret",
+ "redirectUris" : [ "/admin/zuul-demo/console/*" ],
+ "webOrigins" : [ "+" ],
+ "notBefore" : 0,
+ "bearerOnly" : false,
+ "consentRequired" : false,
+ "standardFlowEnabled" : true,
+ "implicitFlowEnabled" : false,
+ "directAccessGrantsEnabled" : false,
+ "serviceAccountsEnabled" : false,
+ "publicClient" : true,
+ "frontchannelLogout" : false,
+ "protocol" : "openid-connect",
+ "attributes" : {
+ "pkce.code.challenge.method" : "S256"
},
- {
- "id": "e6734c68-8029-4b27-bf56-7eccb2202903",
- "alias": "docker auth",
- "description": "Used by Docker clients to authenticate against the IDP",
- "providerId": "basic-flow",
- "topLevel": true,
- "builtIn": true,
- "authenticationExecutions": [
- {
- "authenticator": "docker-http-basic-authenticator",
- "requirement": "REQUIRED",
- "priority": 10,
- "userSetupAllowed": false,
- "autheticatorFlow": false
- }
- ]
+ "authenticationFlowBindingOverrides" : { },
+ "fullScopeAllowed" : false,
+ "nodeReRegistrationTimeout" : 0,
+ "protocolMappers" : [ {
+ "id" : "661a797e-0734-46b8-8c4b-17b1a58cf05a",
+ "name" : "locale",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-usermodel-attribute-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "userinfo.token.claim" : "true",
+ "user.attribute" : "locale",
+ "id.token.claim" : "true",
+ "access.token.claim" : "true",
+ "claim.name" : "locale",
+ "jsonType.label" : "String"
+ }
+ } ],
+ "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "email" ],
+ "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ]
+ }, {
+ "id" : "99377c87-2fb8-4d8b-a628-6281350e7e9b",
+ "clientId" : "zuul",
+ "rootUrl" : "http://localhost:9000",
+ "adminUrl" : "http://localhost:9000",
+ "surrogateAuthRequired" : false,
+ "enabled" : true,
+ "alwaysDisplayInConsole" : false,
+ "clientAuthenticatorType" : "client-secret",
+ "redirectUris" : [ "http://localhost:3000/*", "http://localhost:9000/*" ],
+ "webOrigins" : [ "*" ],
+ "notBefore" : 0,
+ "bearerOnly" : false,
+ "consentRequired" : false,
+ "standardFlowEnabled" : true,
+ "implicitFlowEnabled" : true,
+ "directAccessGrantsEnabled" : true,
+ "serviceAccountsEnabled" : false,
+ "publicClient" : true,
+ "frontchannelLogout" : false,
+ "protocol" : "openid-connect",
+ "attributes" : {
+ "saml.force.post.binding" : "false",
+ "saml.multivalued.roles" : "false",
+ "frontchannel.logout.session.required" : "false",
+ "oauth2.device.authorization.grant.enabled" : "false",
+ "backchannel.logout.revoke.offline.tokens" : "false",
+ "saml.server.signature.keyinfo.ext" : "false",
+ "use.refresh.tokens" : "true",
+ "oidc.ciba.grant.enabled" : "false",
+ "backchannel.logout.session.required" : "true",
+ "client_credentials.use_refresh_token" : "false",
+ "require.pushed.authorization.requests" : "false",
+ "saml.client.signature" : "false",
+ "saml.allow.ecp.flow" : "false",
+ "id.token.as.detached.signature" : "false",
+ "saml.assertion.signature" : "false",
+ "saml.encrypt" : "false",
+ "saml.server.signature" : "false",
+ "exclude.session.state.from.auth.response" : "false",
+ "saml.artifact.binding" : "false",
+ "saml_force_name_id_format" : "false",
+ "acr.loa.map" : "{}",
+ "tls.client.certificate.bound.access.tokens" : "false",
+ "saml.authnstatement" : "false",
+ "display.on.consent.screen" : "false",
+ "token.response.type.bearer.lower-case" : "false",
+ "saml.onetimeuse.condition" : "false"
},
- {
- "id": "0e26f9c0-bc34-4d38-b3fe-472580bff946",
- "alias": "first broker login",
- "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account",
- "providerId": "basic-flow",
- "topLevel": true,
- "builtIn": true,
- "authenticationExecutions": [
- {
- "authenticatorConfig": "review profile config",
- "authenticator": "idp-review-profile",
- "requirement": "REQUIRED",
- "priority": 10,
- "userSetupAllowed": false,
- "autheticatorFlow": false
- },
- {
- "requirement": "REQUIRED",
- "priority": 20,
- "flowAlias": "User creation or linking",
- "userSetupAllowed": false,
- "autheticatorFlow": true
- }
- ]
+ "authenticationFlowBindingOverrides" : { },
+ "fullScopeAllowed" : true,
+ "nodeReRegistrationTimeout" : -1,
+ "protocolMappers" : [ {
+ "id" : "ab67e3ef-562f-4cfa-96ad-02d9e29ed483",
+ "name" : "groups",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-usermodel-realm-role-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "multivalued" : "true",
+ "userinfo.token.claim" : "true",
+ "user.attribute" : "foo",
+ "id.token.claim" : "true",
+ "access.token.claim" : "true",
+ "claim.name" : "groups",
+ "jsonType.label" : "String"
+ }
+ } ],
+ "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "zuul_aud", "email" ],
+ "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ]
+ } ],
+ "clientScopes" : [ {
+ "id" : "f9da357f-356a-45a7-8ad8-b0e47413c30e",
+ "name" : "web-origins",
+ "description" : "OpenID Connect scope for add allowed web origins to the access token",
+ "protocol" : "openid-connect",
+ "attributes" : {
+ "include.in.token.scope" : "false",
+ "display.on.consent.screen" : "false",
+ "consent.screen.text" : ""
},
- {
- "id": "6f56d749-5f06-4e2a-a043-cd4103383899",
- "alias": "forms",
- "description": "Username, password, otp and other auth forms.",
- "providerId": "basic-flow",
- "topLevel": false,
- "builtIn": true,
- "authenticationExecutions": [
- {
- "authenticator": "auth-username-password-form",
- "requirement": "REQUIRED",
- "priority": 10,
- "userSetupAllowed": false,
- "autheticatorFlow": false
- },
- {
- "requirement": "CONDITIONAL",
- "priority": 20,
- "flowAlias": "Browser - Conditional OTP",
- "userSetupAllowed": false,
- "autheticatorFlow": true
- }
- ]
+ "protocolMappers" : [ {
+ "id" : "2f31c90f-8237-4a54-b852-a2df857c6fc8",
+ "name" : "allowed web origins",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-allowed-origins-mapper",
+ "consentRequired" : false,
+ "config" : { }
+ } ]
+ }, {
+ "id" : "554bde36-edad-4c3b-a2f8-1461ad4181c8",
+ "name" : "role_list",
+ "description" : "SAML role list",
+ "protocol" : "saml",
+ "attributes" : {
+ "consent.screen.text" : "${samlRoleListScopeConsentText}",
+ "display.on.consent.screen" : "true"
},
- {
- "id": "4a95b320-312e-468a-978e-215747302385",
- "alias": "http challenge",
- "description": "An authentication flow based on challenge-response HTTP Authentication Schemes",
- "providerId": "basic-flow",
- "topLevel": true,
- "builtIn": true,
- "authenticationExecutions": [
- {
- "authenticator": "no-cookie-redirect",
- "requirement": "REQUIRED",
- "priority": 10,
- "userSetupAllowed": false,
- "autheticatorFlow": false
- },
- {
- "requirement": "REQUIRED",
- "priority": 20,
- "flowAlias": "Authentication Options",
- "userSetupAllowed": false,
- "autheticatorFlow": true
- }
- ]
+ "protocolMappers" : [ {
+ "id" : "ab8dfb24-9c7e-4018-bafe-ef3fc2dd10c6",
+ "name" : "role list",
+ "protocol" : "saml",
+ "protocolMapper" : "saml-role-list-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "single" : "false",
+ "attribute.nameformat" : "Basic",
+ "attribute.name" : "Role"
+ }
+ } ]
+ }, {
+ "id" : "c68d1141-1188-4adb-9c88-033ea1bff03a",
+ "name" : "address",
+ "description" : "OpenID Connect built-in scope: address",
+ "protocol" : "openid-connect",
+ "attributes" : {
+ "include.in.token.scope" : "true",
+ "display.on.consent.screen" : "true",
+ "consent.screen.text" : "${addressScopeConsentText}"
},
- {
- "id": "17678e79-9819-47f8-bbfe-3b68efb92f3d",
- "alias": "registration",
- "description": "registration flow",
- "providerId": "basic-flow",
- "topLevel": true,
- "builtIn": true,
- "authenticationExecutions": [
- {
- "authenticator": "registration-page-form",
- "requirement": "REQUIRED",
- "priority": 10,
- "flowAlias": "registration form",
- "userSetupAllowed": false,
- "autheticatorFlow": true
- }
- ]
+ "protocolMappers" : [ {
+ "id" : "e97f7b55-12d6-40cb-9e60-b0cee354ae8f",
+ "name" : "address",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-address-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "user.attribute.formatted" : "formatted",
+ "user.attribute.country" : "country",
+ "user.attribute.postal_code" : "postal_code",
+ "userinfo.token.claim" : "true",
+ "user.attribute.street" : "street",
+ "id.token.claim" : "true",
+ "user.attribute.region" : "region",
+ "access.token.claim" : "true",
+ "user.attribute.locality" : "locality"
+ }
+ } ]
+ }, {
+ "id" : "18ceed25-cdb0-4911-8419-b8228d5947a2",
+ "name" : "zuul_aud",
+ "description" : "zuul audience mapper",
+ "protocol" : "openid-connect",
+ "attributes" : {
+ "include.in.token.scope" : "true",
+ "display.on.consent.screen" : "true"
},
- {
- "id": "7c23bfaa-420a-4975-a547-254bb0873457",
- "alias": "registration form",
- "description": "registration form",
- "providerId": "form-flow",
- "topLevel": false,
- "builtIn": true,
- "authenticationExecutions": [
- {
- "authenticator": "registration-user-creation",
- "requirement": "REQUIRED",
- "priority": 20,
- "userSetupAllowed": false,
- "autheticatorFlow": false
- },
- {
- "authenticator": "registration-profile-action",
- "requirement": "REQUIRED",
- "priority": 40,
- "userSetupAllowed": false,
- "autheticatorFlow": false
- },
- {
- "authenticator": "registration-password-action",
- "requirement": "REQUIRED",
- "priority": 50,
- "userSetupAllowed": false,
- "autheticatorFlow": false
- },
- {
- "authenticator": "registration-recaptcha-action",
- "requirement": "DISABLED",
- "priority": 60,
- "userSetupAllowed": false,
- "autheticatorFlow": false
- }
- ]
+ "protocolMappers" : [ {
+ "id" : "e54ce7f5-fb11-425f-ad3a-628db83c1ef2",
+ "name" : "zuul_aud_mapper",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-audience-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "included.client.audience" : "zuul",
+ "id.token.claim" : "true",
+ "access.token.claim" : "true",
+ "userinfo.token.claim" : "true"
+ }
+ } ]
+ }, {
+ "id" : "1f3ab062-a2bd-4929-9a49-434fa94b7f55",
+ "name" : "microprofile-jwt",
+ "description" : "Microprofile - JWT built-in scope",
+ "protocol" : "openid-connect",
+ "attributes" : {
+ "include.in.token.scope" : "true",
+ "display.on.consent.screen" : "false"
},
- {
- "id": "ef21d1d8-4afd-4baf-a44d-34d657f673a4",
- "alias": "reset credentials",
- "description": "Reset credentials for a user if they forgot their password or something",
- "providerId": "basic-flow",
- "topLevel": true,
- "builtIn": true,
- "authenticationExecutions": [
- {
- "authenticator": "reset-credentials-choose-user",
- "requirement": "REQUIRED",
- "priority": 10,
- "userSetupAllowed": false,
- "autheticatorFlow": false
- },
- {
- "authenticator": "reset-credential-email",
- "requirement": "REQUIRED",
- "priority": 20,
- "userSetupAllowed": false,
- "autheticatorFlow": false
- },
- {
- "authenticator": "reset-password",
- "requirement": "REQUIRED",
- "priority": 30,
- "userSetupAllowed": false,
- "autheticatorFlow": false
- },
- {
- "requirement": "CONDITIONAL",
- "priority": 40,
- "flowAlias": "Reset - Conditional OTP",
- "userSetupAllowed": false,
- "autheticatorFlow": true
- }
- ]
+ "protocolMappers" : [ {
+ "id" : "80212ba7-8894-4b23-b73c-b29fb6157c41",
+ "name" : "groups",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-usermodel-realm-role-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "multivalued" : "true",
+ "userinfo.token.claim" : "true",
+ "user.attribute" : "foo",
+ "id.token.claim" : "true",
+ "access.token.claim" : "true",
+ "claim.name" : "groups",
+ "jsonType.label" : "String"
+ }
+ }, {
+ "id" : "1abd1cea-5b14-41ef-b865-bb81af679932",
+ "name" : "upn",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-usermodel-property-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "userinfo.token.claim" : "true",
+ "user.attribute" : "username",
+ "id.token.claim" : "true",
+ "access.token.claim" : "true",
+ "claim.name" : "upn",
+ "jsonType.label" : "String"
+ }
+ } ]
+ }, {
+ "id" : "220b27ee-6e55-49c0-86a7-e2f915166bc7",
+ "name" : "phone",
+ "description" : "OpenID Connect built-in scope: phone",
+ "protocol" : "openid-connect",
+ "attributes" : {
+ "include.in.token.scope" : "true",
+ "display.on.consent.screen" : "true",
+ "consent.screen.text" : "${phoneScopeConsentText}"
},
- {
- "id": "21fea2f5-c4e3-46af-a573-54f36aa25b4b",
- "alias": "saml ecp",
- "description": "SAML ECP Profile Authentication Flow",
- "providerId": "basic-flow",
- "topLevel": true,
- "builtIn": true,
- "authenticationExecutions": [
- {
- "authenticator": "http-basic-authenticator",
- "requirement": "REQUIRED",
- "priority": 10,
- "userSetupAllowed": false,
- "autheticatorFlow": false
- }
- ]
- }
- ],
- "authenticatorConfig": [
- {
- "id": "d093ee16-2996-4916-ba26-827877401a45",
- "alias": "create unique user config",
- "config": {
- "require.password.update.after.registration": "false"
+ "protocolMappers" : [ {
+ "id" : "82f49e59-01d9-4435-9d57-8b51ba068f95",
+ "name" : "phone number",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-usermodel-attribute-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "userinfo.token.claim" : "true",
+ "user.attribute" : "phoneNumber",
+ "id.token.claim" : "true",
+ "access.token.claim" : "true",
+ "claim.name" : "phone_number",
+ "jsonType.label" : "String"
}
+ }, {
+ "id" : "4b7d2541-a139-4f35-8b0a-7f786abf8e25",
+ "name" : "phone number verified",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-usermodel-attribute-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "userinfo.token.claim" : "true",
+ "user.attribute" : "phoneNumberVerified",
+ "id.token.claim" : "true",
+ "access.token.claim" : "true",
+ "claim.name" : "phone_number_verified",
+ "jsonType.label" : "boolean"
+ }
+ } ]
+ }, {
+ "id" : "f862a715-e18d-491f-ad37-0c77253e1519",
+ "name" : "email",
+ "description" : "OpenID Connect built-in scope: email",
+ "protocol" : "openid-connect",
+ "attributes" : {
+ "include.in.token.scope" : "true",
+ "display.on.consent.screen" : "true",
+ "consent.screen.text" : "${emailScopeConsentText}"
},
- {
- "id": "9cf393f2-4659-473a-a1d8-2318e107fcfe",
- "alias": "review profile config",
- "config": {
- "update.profile.on.first.login": "missing"
+ "protocolMappers" : [ {
+ "id" : "195a1039-aeca-4e57-888b-1a1e802fa64d",
+ "name" : "email",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-usermodel-property-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "userinfo.token.claim" : "true",
+ "user.attribute" : "email",
+ "id.token.claim" : "true",
+ "access.token.claim" : "true",
+ "claim.name" : "email",
+ "jsonType.label" : "String"
+ }
+ }, {
+ "id" : "4ccb27c0-2574-48b7-92bd-ec246306a944",
+ "name" : "email verified",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-usermodel-property-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "userinfo.token.claim" : "true",
+ "user.attribute" : "emailVerified",
+ "id.token.claim" : "true",
+ "access.token.claim" : "true",
+ "claim.name" : "email_verified",
+ "jsonType.label" : "boolean"
}
+ } ]
+ }, {
+ "id" : "a03e0161-18f1-4d82-8482-4c6b1b6803bf",
+ "name" : "offline_access",
+ "description" : "OpenID Connect built-in scope: offline_access",
+ "protocol" : "openid-connect",
+ "attributes" : {
+ "consent.screen.text" : "${offlineAccessScopeConsentText}",
+ "display.on.consent.screen" : "true"
}
- ],
- "requiredActions": [
- {
- "alias": "CONFIGURE_TOTP",
- "name": "Configure OTP",
- "providerId": "CONFIGURE_TOTP",
- "enabled": true,
- "defaultAction": false,
- "priority": 10,
- "config": {}
- },
- {
- "alias": "terms_and_conditions",
- "name": "Terms and Conditions",
- "providerId": "terms_and_conditions",
- "enabled": false,
- "defaultAction": false,
- "priority": 20,
- "config": {}
- },
- {
- "alias": "UPDATE_PASSWORD",
- "name": "Update Password",
- "providerId": "UPDATE_PASSWORD",
- "enabled": true,
- "defaultAction": false,
- "priority": 30,
- "config": {}
+ }, {
+ "id" : "3bc63b52-838d-48bc-b0b6-b0805901c4b0",
+ "name" : "roles",
+ "description" : "OpenID Connect scope for add user roles to the access token",
+ "protocol" : "openid-connect",
+ "attributes" : {
+ "include.in.token.scope" : "false",
+ "display.on.consent.screen" : "true",
+ "consent.screen.text" : "${rolesScopeConsentText}"
},
- {
- "alias": "UPDATE_PROFILE",
- "name": "Update Profile",
- "providerId": "UPDATE_PROFILE",
- "enabled": true,
- "defaultAction": false,
- "priority": 40,
- "config": {}
- },
- {
- "alias": "VERIFY_EMAIL",
- "name": "Verify Email",
- "providerId": "VERIFY_EMAIL",
- "enabled": true,
- "defaultAction": false,
- "priority": 50,
- "config": {}
+ "protocolMappers" : [ {
+ "id" : "f654cdcf-ad35-483d-bc9a-36e27cf2d459",
+ "name" : "client roles",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-usermodel-client-role-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "user.attribute" : "foo",
+ "access.token.claim" : "true",
+ "claim.name" : "resource_access.${client_id}.roles",
+ "jsonType.label" : "String",
+ "multivalued" : "true"
+ }
+ }, {
+ "id" : "f6b687b6-94d6-4cfd-b167-eac16d07d1ff",
+ "name" : "audience resolve",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-audience-resolve-mapper",
+ "consentRequired" : false,
+ "config" : { }
+ }, {
+ "id" : "f59d4345-6702-43ca-82d9-dc2d60c9b66e",
+ "name" : "realm roles",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-usermodel-realm-role-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "user.attribute" : "foo",
+ "access.token.claim" : "true",
+ "claim.name" : "realm_access.roles",
+ "jsonType.label" : "String",
+ "multivalued" : "true"
+ }
+ } ]
+ }, {
+ "id" : "6cdcdd5e-8a80-4366-b38f-49005db03f27",
+ "name" : "acr",
+ "description" : "OpenID Connect scope for add acr (authentication context class reference) to the token",
+ "protocol" : "openid-connect",
+ "attributes" : {
+ "include.in.token.scope" : "false",
+ "display.on.consent.screen" : "false"
},
- {
- "alias": "delete_account",
- "name": "Delete Account",
- "providerId": "delete_account",
- "enabled": false,
- "defaultAction": false,
- "priority": 60,
- "config": {}
+ "protocolMappers" : [ {
+ "id" : "941b04f7-0f19-49b5-a11b-0790dcf83e3a",
+ "name" : "acr loa level",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-acr-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "id.token.claim" : "true",
+ "access.token.claim" : "true",
+ "userinfo.token.claim" : "true"
+ }
+ } ]
+ }, {
+ "id" : "197b457a-28ff-48c2-a879-be4eceeeaed6",
+ "name" : "profile",
+ "description" : "OpenID Connect built-in scope: profile",
+ "protocol" : "openid-connect",
+ "attributes" : {
+ "include.in.token.scope" : "true",
+ "display.on.consent.screen" : "true",
+ "consent.screen.text" : "${profileScopeConsentText}"
},
- {
- "alias": "update_user_locale",
- "name": "Update User Locale",
- "providerId": "update_user_locale",
- "enabled": true,
- "defaultAction": false,
- "priority": 1000,
- "config": {}
+ "protocolMappers" : [ {
+ "id" : "8247fb9a-2a89-4491-ae97-9a3ef846a32d",
+ "name" : "family name",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-usermodel-property-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "userinfo.token.claim" : "true",
+ "user.attribute" : "lastName",
+ "id.token.claim" : "true",
+ "access.token.claim" : "true",
+ "claim.name" : "family_name",
+ "jsonType.label" : "String"
+ }
+ }, {
+ "id" : "7b71b659-4929-454d-a2f1-9e84c3ebd970",
+ "name" : "middle name",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-usermodel-attribute-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "userinfo.token.claim" : "true",
+ "user.attribute" : "middleName",
+ "id.token.claim" : "true",
+ "access.token.claim" : "true",
+ "claim.name" : "middle_name",
+ "jsonType.label" : "String"
+ }
+ }, {
+ "id" : "58bbcd2b-77a8-4a98-b51e-b9802b0d300c",
+ "name" : "zoneinfo",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-usermodel-attribute-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "userinfo.token.claim" : "true",
+ "user.attribute" : "zoneinfo",
+ "id.token.claim" : "true",
+ "access.token.claim" : "true",
+ "claim.name" : "zoneinfo",
+ "jsonType.label" : "String"
+ }
+ }, {
+ "id" : "2bdf1469-3ce1-408d-a6db-616a687efd20",
+ "name" : "picture",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-usermodel-attribute-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "userinfo.token.claim" : "true",
+ "user.attribute" : "picture",
+ "id.token.claim" : "true",
+ "access.token.claim" : "true",
+ "claim.name" : "picture",
+ "jsonType.label" : "String"
+ }
+ }, {
+ "id" : "ee34f510-aa37-47f0-81b6-eee4d0bd2073",
+ "name" : "given name",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-usermodel-property-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "userinfo.token.claim" : "true",
+ "user.attribute" : "firstName",
+ "id.token.claim" : "true",
+ "access.token.claim" : "true",
+ "claim.name" : "given_name",
+ "jsonType.label" : "String"
+ }
+ }, {
+ "id" : "a7e5ad5f-afe7-4e10-833a-db4ca4502083",
+ "name" : "nickname",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-usermodel-attribute-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "userinfo.token.claim" : "true",
+ "user.attribute" : "nickname",
+ "id.token.claim" : "true",
+ "access.token.claim" : "true",
+ "claim.name" : "nickname",
+ "jsonType.label" : "String"
+ }
+ }, {
+ "id" : "6731543a-4fd5-4064-8cd6-438d59f07d98",
+ "name" : "birthdate",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-usermodel-attribute-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "userinfo.token.claim" : "true",
+ "user.attribute" : "birthdate",
+ "id.token.claim" : "true",
+ "access.token.claim" : "true",
+ "claim.name" : "birthdate",
+ "jsonType.label" : "String"
+ }
+ }, {
+ "id" : "dfbc8ef9-be5e-476b-a4cb-8a4130b00ad7",
+ "name" : "website",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-usermodel-attribute-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "userinfo.token.claim" : "true",
+ "user.attribute" : "website",
+ "id.token.claim" : "true",
+ "access.token.claim" : "true",
+ "claim.name" : "website",
+ "jsonType.label" : "String"
+ }
+ }, {
+ "id" : "3c2b96fc-fcff-4847-b7a5-6c915adad250",
+ "name" : "updated at",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-usermodel-attribute-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "userinfo.token.claim" : "true",
+ "user.attribute" : "updatedAt",
+ "id.token.claim" : "true",
+ "access.token.claim" : "true",
+ "claim.name" : "updated_at",
+ "jsonType.label" : "long"
+ }
+ }, {
+ "id" : "6487b361-8548-4131-9636-f5e6c1db962d",
+ "name" : "gender",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-usermodel-attribute-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "userinfo.token.claim" : "true",
+ "user.attribute" : "gender",
+ "id.token.claim" : "true",
+ "access.token.claim" : "true",
+ "claim.name" : "gender",
+ "jsonType.label" : "String"
+ }
+ }, {
+ "id" : "4e93de0c-7f96-499e-8014-fc260fd82960",
+ "name" : "full name",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-full-name-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "id.token.claim" : "true",
+ "access.token.claim" : "true",
+ "userinfo.token.claim" : "true"
+ }
+ }, {
+ "id" : "7e202343-ae40-4149-985b-3f4cbf9b1588",
+ "name" : "username",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-usermodel-property-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "userinfo.token.claim" : "true",
+ "user.attribute" : "username",
+ "id.token.claim" : "true",
+ "access.token.claim" : "true",
+ "claim.name" : "preferred_username",
+ "jsonType.label" : "String"
+ }
+ }, {
+ "id" : "e9175df7-260b-4fe7-8655-db30ccc9c424",
+ "name" : "locale",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-usermodel-attribute-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "userinfo.token.claim" : "true",
+ "user.attribute" : "locale",
+ "id.token.claim" : "true",
+ "access.token.claim" : "true",
+ "claim.name" : "locale",
+ "jsonType.label" : "String"
+ }
+ }, {
+ "id" : "59663d46-72dd-4ea6-b106-7a264d01ebcb",
+ "name" : "profile",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-usermodel-attribute-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "userinfo.token.claim" : "true",
+ "user.attribute" : "profile",
+ "id.token.claim" : "true",
+ "access.token.claim" : "true",
+ "claim.name" : "profile",
+ "jsonType.label" : "String"
+ }
+ } ]
+ } ],
+ "defaultDefaultClientScopes" : [ "profile", "roles", "role_list", "acr", "email", "web-origins" ],
+ "defaultOptionalClientScopes" : [ "microprofile-jwt", "phone", "offline_access", "address" ],
+ "browserSecurityHeaders" : {
+ "contentSecurityPolicyReportOnly" : "",
+ "xContentTypeOptions" : "nosniff",
+ "xRobotsTag" : "none",
+ "xFrameOptions" : "SAMEORIGIN",
+ "contentSecurityPolicy" : "frame-src 'self'; frame-ancestors 'self'; object-src 'none';",
+ "xXSSProtection" : "1; mode=block",
+ "strictTransportSecurity" : "max-age=31536000; includeSubDomains"
+ },
+ "smtpServer" : { },
+ "eventsEnabled" : false,
+ "eventsListeners" : [ "jboss-logging" ],
+ "enabledEventTypes" : [ ],
+ "adminEventsEnabled" : false,
+ "adminEventsDetailsEnabled" : false,
+ "identityProviders" : [ ],
+ "identityProviderMappers" : [ ],
+ "components" : {
+ "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy" : [ {
+ "id" : "c6df0a44-af38-4795-8990-ffc359a2b62b",
+ "name" : "Consent Required",
+ "providerId" : "consent-required",
+ "subType" : "anonymous",
+ "subComponents" : { },
+ "config" : { }
+ }, {
+ "id" : "fa50952d-682b-4bf3-936c-2e35abfd810a",
+ "name" : "Allowed Client Scopes",
+ "providerId" : "allowed-client-templates",
+ "subType" : "authenticated",
+ "subComponents" : { },
+ "config" : {
+ "allow-default-scopes" : [ "true" ]
+ }
+ }, {
+ "id" : "adb59412-5ad2-4531-bcde-cdd3048e8698",
+ "name" : "Max Clients Limit",
+ "providerId" : "max-clients",
+ "subType" : "anonymous",
+ "subComponents" : { },
+ "config" : {
+ "max-clients" : [ "200" ]
+ }
+ }, {
+ "id" : "270f6a69-5b86-4a4b-b54c-1900ea3d325d",
+ "name" : "Allowed Protocol Mapper Types",
+ "providerId" : "allowed-protocol-mappers",
+ "subType" : "anonymous",
+ "subComponents" : { },
+ "config" : {
+ "allowed-protocol-mapper-types" : [ "oidc-sha256-pairwise-sub-mapper", "oidc-full-name-mapper", "saml-user-attribute-mapper", "oidc-usermodel-property-mapper", "saml-role-list-mapper", "oidc-address-mapper", "saml-user-property-mapper", "oidc-usermodel-attribute-mapper" ]
+ }
+ }, {
+ "id" : "a107bb7f-7b95-4c80-bf36-0793913507b9",
+ "name" : "Trusted Hosts",
+ "providerId" : "trusted-hosts",
+ "subType" : "anonymous",
+ "subComponents" : { },
+ "config" : {
+ "host-sending-registration-request-must-match" : [ "true" ],
+ "client-uris-must-match" : [ "true" ]
+ }
+ }, {
+ "id" : "5e3c37e6-455a-4cab-9058-59d7ba1e6a91",
+ "name" : "Full Scope Disabled",
+ "providerId" : "scope",
+ "subType" : "anonymous",
+ "subComponents" : { },
+ "config" : { }
+ }, {
+ "id" : "829ff854-352f-453e-ae71-b3ed893167e5",
+ "name" : "Allowed Protocol Mapper Types",
+ "providerId" : "allowed-protocol-mappers",
+ "subType" : "authenticated",
+ "subComponents" : { },
+ "config" : {
+ "allowed-protocol-mapper-types" : [ "saml-role-list-mapper", "saml-user-property-mapper", "oidc-full-name-mapper", "oidc-address-mapper", "oidc-usermodel-property-mapper", "oidc-usermodel-attribute-mapper", "saml-user-attribute-mapper", "oidc-sha256-pairwise-sub-mapper" ]
+ }
+ }, {
+ "id" : "eb1f66db-d2a2-435f-958d-f385db20740e",
+ "name" : "Allowed Client Scopes",
+ "providerId" : "allowed-client-templates",
+ "subType" : "anonymous",
+ "subComponents" : { },
+ "config" : {
+ "allow-default-scopes" : [ "true" ]
+ }
+ } ],
+ "org.keycloak.keys.KeyProvider" : [ {
+ "id" : "34336282-9f58-471a-9794-440b180296aa",
+ "name" : "rsa-generated",
+ "providerId" : "rsa-generated",
+ "subComponents" : { },
+ "config" : {
+ "privateKey" : [ "MIIEowIBAAKCAQEA0fI5OZ/LCxI/TNxVpWi9q6EeaOv/yshrPOFevhg5eJ52G2uzvv7XWN4FNWMg3ln97qmZPm6PvB6utCSM776WZ5O3t1zy6KXc5LKRSF9WW/RdoLlrIAvo9R2XRpdClfc4906Dl8q8NlxC6klVkP5OwTzuH9fh36qf2BAb39F6AhosY8lfWiE7+QpF7CEvYkZTdp02PtqbV2R7DMjzhPs3aLLbJybu9BVfsdNbHdsk6owauJMtvJhIvcigk5azUk6leVp27010zv+DuWVtbMhzX+qnYy4+SnkyfKPPX1lYtQzw6pDTJuWTjcpSKgZ/68xKyVQWzNqsrSZyGWXEtmsTvwIDAQABAoIBABZMpRliyJ0/bevLB2nPSz4cme9JCd1PFlzcD0Ufq5MNObxF30ytItB0nJcEFWyooFkmOHmyIqA3nhTSQQoB81XDEeNvAvoFDAZalSXbwn934C3/I8q+mRrX7sZ3sIHfRXS3KTEJPp6DhDQAYA9Lg6/wudY2Bu7L37iCVYOba1snnUFaqSGzBqkx68SisYUpjj9LpBHwgoAO4UwTzijN6UUS0MwpxJ1Y10A3c5v3XfzAYTm9FhzT5TyrO6NgXCQIbfC5tbTdSPXbsrYiNucmqlqB/Ym/f1KSM8peBQSFt8h0yjjeVRYe2/oVfiWeDeBHdYZCCpY20RJtSKjScOcwFNkCgYEA8G3xS8DfTZonCx4mR/SU+9Yjx8KU+g/erRHhwUHATY0zV+usz/ZTBz7wfVSBLQ7MDzpbpjOVF0CjFeNgI3kS4ju0smEylwZ8ghlDa578ITArV5Wb9uECdu86ZzNzugeoGx5pZYEkqEvO1MfS3tecX8h02eCwvUCZ26WJTDu1ev0CgYEA34rmzlXqDyrBENvwKNvfL/NfTpQ6lLfAAirRov8UvKjtcRo131PDIfGzsFDMrN3640rKyY1HOiW2SDYK0NrLOmPxzitcXxVjBtqcI879qAQeMWYLdFA3R7kJE1hn8aNoC+hzjpUYjaoBXZFqQrw+HvEl0UTRErhn2FCpnnuhHGsCgYB4zqVp7PXHJmfr3DBcA3dAuy6rVwhIReUbjVqkIoef8A0WZmMk855wi52HPAjUPRUnqakXb9/AGUhY3TEXYeStLFb1MoWbATVaoZsQ3ofNnUiJ77C4cgMtA7OS23WQyfwb9EH9MNDch4rbu7QnKKt4LV8Zgx8LWQR2K007stzTeQKBgEv7ZqBTy48ENUOJvoPk2GPwEUEgWRkbrAQQkdkZYu8NbEe5ZP5X9kefZDhV/nvzp8eCMJN5lfBJyT+aPurms5VRS2uMiYU4O8SkodX6EXX+kFSyHsqWSXPzjQYqUoprxH3nsP+R5hqg4n3vY3LzkRnrVbNf9MCsNX/tSvo3g3epAoGBAM50hFFzEo6V+Tk+grZfGI47vTmQ0w+oTtImHsu6kD8fMbfP/+eq28n5BnI7yBK0kmP5Z2pOByHQfc0Dn90O7J1kwaxi4URwNUIFmWfofL11fRFtRHALH/U/LiFprkDwnmjp8qt8MutlyElcpyuDCKFWidq8lx4CfqnINzFfdWh7" ],
+ "keyUse" : [ "SIG" ],
+ "certificate" : [ "MIICoTCCAYkCBgGCJXQ9oDANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAl6dXVsLWRlbW8wHhcNMjIwNzIyMTAyNzMxWhcNMzIwNzIyMTAyOTExWjAUMRIwEAYDVQQDDAl6dXVsLWRlbW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDR8jk5n8sLEj9M3FWlaL2roR5o6//KyGs84V6+GDl4nnYba7O+/tdY3gU1YyDeWf3uqZk+bo+8Hq60JIzvvpZnk7e3XPLopdzkspFIX1Zb9F2guWsgC+j1HZdGl0KV9zj3ToOXyrw2XELqSVWQ/k7BPO4f1+Hfqp/YEBvf0XoCGixjyV9aITv5CkXsIS9iRlN2nTY+2ptXZHsMyPOE+zdostsnJu70FV+x01sd2yTqjBq4ky28mEi9yKCTlrNSTqV5WnbvTXTO/4O5ZW1syHNf6qdjLj5KeTJ8o89fWVi1DPDqkNMm5ZONylIqBn/rzErJVBbM2qytJnIZZcS2axO/AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAKYr1dxc8eMlgaZWXvd4vbP1ugbqUswt3kgBbrrFmzfzocU1/Hd0f5YW5ziq1oBqB2Ke6ByTt5h7M4q5ss+w6Gb3bLyNV9wmP1SQO0JBk0TvRo7CId68SZwAPNH4Bmo5z9efhdvvDevnM79bKw65D/xu3t4YPUAOHfd+idVQkk38DWdmKSFz/4VZ2luNrQFBQS0KW39K30BaR/65xGRlefNKVqYBFYDb4u9QUfiD6Q4jckXPCMEKgOD4oGlUmHnmTufoRzH9W0jqOy5856gHgXD7+lJtvRjj+alHJ4on7ABk+yPSgEuI3WCVSITGd0KW5gaB8YjyhG7NAV4rKXpPv20=" ],
+ "priority" : [ "100" ]
+ }
+ }, {
+ "id" : "558fe888-18c5-48e2-9c45-a072532315f7",
+ "name" : "hmac-generated",
+ "providerId" : "hmac-generated",
+ "subComponents" : { },
+ "config" : {
+ "kid" : [ "bed016a0-d455-4ef2-827a-e5deb8569f3e" ],
+ "secret" : [ "0Aph42iALP396xPal2boz2dTNi9j8gculw6H_g6ohYO1vdJdwjBUsH-GD-GCBQk49Rtxa1yNZDWrr1f-2inGfw" ],
+ "priority" : [ "100" ],
+ "algorithm" : [ "HS256" ]
+ }
+ }, {
+ "id" : "67d8a3df-b709-4726-a929-e592530f7889",
+ "name" : "rsa-enc-generated",
+ "providerId" : "rsa-enc-generated",
+ "subComponents" : { },
+ "config" : {
+ "privateKey" : [ "MIIEpAIBAAKCAQEAjXU8U4JzDj2z5+L/kAuBZGayBnqwbsRaafkQcDf2OztpEaqPZt9KbQtK7x9eev4VkXepRl6FhNgfqTfwlLWd9CQZl5dXgKXC/9kZo4WhNiKfRpFdfcFZTULKJWROgPddx8pz3tw9yU4wZDrAA5wGo6qC9v7QMu4A/ARTpGjl5gRnEFDswM9OuHGqd7oAxCskn0XPVbOVcAbs2KPB7jNopyuGsUpox3uxv79sAjEMY64Y/Da9lkBZBpCkh8v3hZtGAQTvGf4VZLx8e00U1BrKomuJuBMvaCBrnyFHGGpVxF/f4oFBcVVceJOxIamuW7ZEpWP3uLMqZhs5N02jKEHsMwIDAQABAoIBADxYhRHDFr4+iF8mMYwLvHlObifhcx7t2PxYajDVTs/Z+mq07lQecauvI5p7MV0aw6y49Ze1fxQ69tbDrHhkOiwKsng+8e9Jx7RJs2xU938Wqgf6v32EXEpfcckdz3epD3LJ5UBviFQ2bOJ1RbDEgPiVJyWjcsInUngwiFYdAeZr2oWbdTovPI5bvJFi2uAjbqgMXz4FyVrQshZFpSHNVEeuRowjfmnMVjRD2KFNG4rvF7Cs6yp4/NZ0z4FtyRp+Nw0n7tb1uri2/RSPeSt0pXKRbVtiIVd6NwX4e403SSK2orF01WWSVBbw7BnDZBgwtiY38QkYy9PJp1KlwDGC8bECgYEA2gW//RAIA6+vWktD2ru4LYPZ0237W5C8nWRrGZG+jqlSQIxW5t14NTCte/Mlz1Ixyolj0n4Vqe3p3PYp5kgrpQ/2WcS4St3LQgr+apLO8LNLQpn2/CfbAYRq5MfOyXvC0fTyC39t0Zx/rz4CPbl9NEjRLYGW0+G+EkDeSD3VGM8CgYEAphlA5mi2PJttOL2nWovOhh6ZUM0spg+eSU9qDcQtmBwdhnApvv68viIDvJ/1Yhjg1xEndBUkxm6wppWrIKOJzWlrpqYxgMaesVBPIBeGjhs8CpEadCzFjBeHzLeckf9Tg5uOq/zud8i2O/2gdsW+O/mgXebPFaDKarNJ/RFBx10CgYAbqjceaXZa4WQi1QIbM06/l8eAKKiIieklH6TnbbL8b0fcoPI2AUxLk42v+L6HfCqNRLnB7/XXxjSysRnH+shu/YOJCIlKFrLAAp0d8NsJd/bI0Liibnw/yQm3VhNLRcUG10oPY3AP9QfOuNOX+X2FsOe6GoOFB1QTBOAv0ojDyQKBgQChViJOLCPnlululfl64BYI/KvbapAV9VHAz9yp5oaLG7QqI8TI+tMGtC+BBrbpRFURUl5KSUdhX59DEFoa5/8wgVfJleC7vzHh9aoF+BQA8L9PXEaf1eVFAI4t8aOpxKKwUWcx9AsGWvV4LBGzjLwNsZlt3IYF8hrrrZ8Oq7aDGQKBgQDMDce7sMLGSI2rZcPmn15DqBxKr9fmtHH1/DGGvXlYNcI2UvzI5Lq7bOK4uu87NnHoKhSjd9EsENmFc0iYxf1F9r+3BGtYYLOWClLBhM7GXiQeGFKFGpXso7I2HVZUMpUDJaTmlmF3xlAb+VchOHvfCyFvTO4xMm7wnemDQ/ZJ+g==" ],
+ "keySize" : [ "2048" ],
+ "keyUse" : [ "ENC" ],
+ "certificate" : [ "MIICoTCCAYkCBgGCJXQ99zANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAl6dXVsLWRlbW8wHhcNMjIwNzIyMTAyNzMxWhcNMzIwNzIyMTAyOTExWjAUMRIwEAYDVQQDDAl6dXVsLWRlbW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCNdTxTgnMOPbPn4v+QC4FkZrIGerBuxFpp+RBwN/Y7O2kRqo9m30ptC0rvH156/hWRd6lGXoWE2B+pN/CUtZ30JBmXl1eApcL/2RmjhaE2Ip9GkV19wVlNQsolZE6A913HynPe3D3JTjBkOsADnAajqoL2/tAy7gD8BFOkaOXmBGcQUOzAz064cap3ugDEKySfRc9Vs5VwBuzYo8HuM2inK4axSmjHe7G/v2wCMQxjrhj8Nr2WQFkGkKSHy/eFm0YBBO8Z/hVkvHx7TRTUGsqia4m4Ey9oIGufIUcYalXEX9/igUFxVVx4k7Ehqa5btkSlY/e4sypmGzk3TaMoQewzAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAFIjuy2Aac1Y5jggKdRwZ8Xlg8S4B7RxmvNg15G8DX1m1PaAcb6aZ6lwDp78aEMxlpihn/G5mJ2MEmFL9Y95lJlCXlB0pHSGf4+vKJ6ZgZ9IUdP6tKyLsnCY+wR2Ag4444Upwd4yJGj3QBAArrdIezTOpEoPUn51j8KDJUXJXRkeJFE4Kgl5S3TmCGwvpCJQYfLjj9Lw60Evepi9fjaTjQxp16aDNHDvB/d+7VnJoZnj2ruU/N93FUaUgv4W2AAjcJj+9le8MGTIWLXezhCOgdDYRBMxTiBAwV+BbktXdQZiDvYyJp8tCxp0w9K3WNRgEX+2jS94+yYWMxKVVlHmtWA=" ],
+ "active" : [ "false" ],
+ "priority" : [ "100" ],
+ "enabled" : [ "false" ],
+ "algorithm" : [ "RSA-OAEP" ]
+ }
+ }, {
+ "id" : "18071daa-3312-45d7-9687-80f4b713b351",
+ "name" : "aes-generated",
+ "providerId" : "aes-generated",
+ "subComponents" : { },
+ "config" : {
+ "kid" : [ "ebdbe2a3-cac0-4ceb-a691-349f9a857a4a" ],
+ "secret" : [ "G77Dlvi7V6exa6LZZjv0Aw" ],
+ "priority" : [ "100" ]
+ }
+ } ]
+ },
+ "internationalizationEnabled" : false,
+ "supportedLocales" : [ ],
+ "authenticationFlows" : [ {
+ "id" : "905f0ff4-552b-41cd-a293-54615e1cca5e",
+ "alias" : "Account verification options",
+ "description" : "Method with which to verity the existing account",
+ "providerId" : "basic-flow",
+ "topLevel" : false,
+ "builtIn" : true,
+ "authenticationExecutions" : [ {
+ "authenticator" : "idp-email-verification",
+ "authenticatorFlow" : false,
+ "requirement" : "ALTERNATIVE",
+ "priority" : 10,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticatorFlow" : true,
+ "requirement" : "ALTERNATIVE",
+ "priority" : 20,
+ "autheticatorFlow" : true,
+ "flowAlias" : "Verify Existing Account by Re-authentication",
+ "userSetupAllowed" : false
+ } ]
+ }, {
+ "id" : "56aefed5-e44b-418b-9acf-5c8ad5a99b38",
+ "alias" : "Authentication Options",
+ "description" : "Authentication options.",
+ "providerId" : "basic-flow",
+ "topLevel" : false,
+ "builtIn" : true,
+ "authenticationExecutions" : [ {
+ "authenticator" : "basic-auth",
+ "authenticatorFlow" : false,
+ "requirement" : "REQUIRED",
+ "priority" : 10,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticator" : "basic-auth-otp",
+ "authenticatorFlow" : false,
+ "requirement" : "DISABLED",
+ "priority" : 20,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticator" : "auth-spnego",
+ "authenticatorFlow" : false,
+ "requirement" : "DISABLED",
+ "priority" : 30,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ } ]
+ }, {
+ "id" : "ee2217ce-1a43-45da-9e42-53e967301b68",
+ "alias" : "Browser - Conditional OTP",
+ "description" : "Flow to determine if the OTP is required for the authentication",
+ "providerId" : "basic-flow",
+ "topLevel" : false,
+ "builtIn" : true,
+ "authenticationExecutions" : [ {
+ "authenticator" : "conditional-user-configured",
+ "authenticatorFlow" : false,
+ "requirement" : "REQUIRED",
+ "priority" : 10,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticator" : "auth-otp-form",
+ "authenticatorFlow" : false,
+ "requirement" : "REQUIRED",
+ "priority" : 20,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ } ]
+ }, {
+ "id" : "a2675999-6a95-4eda-b46b-b6c23f897bb7",
+ "alias" : "Direct Grant - Conditional OTP",
+ "description" : "Flow to determine if the OTP is required for the authentication",
+ "providerId" : "basic-flow",
+ "topLevel" : false,
+ "builtIn" : true,
+ "authenticationExecutions" : [ {
+ "authenticator" : "conditional-user-configured",
+ "authenticatorFlow" : false,
+ "requirement" : "REQUIRED",
+ "priority" : 10,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticator" : "direct-grant-validate-otp",
+ "authenticatorFlow" : false,
+ "requirement" : "REQUIRED",
+ "priority" : 20,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ } ]
+ }, {
+ "id" : "7088d6ca-06a0-48bb-a2bb-dc0ecd21bae2",
+ "alias" : "First broker login - Conditional OTP",
+ "description" : "Flow to determine if the OTP is required for the authentication",
+ "providerId" : "basic-flow",
+ "topLevel" : false,
+ "builtIn" : true,
+ "authenticationExecutions" : [ {
+ "authenticator" : "conditional-user-configured",
+ "authenticatorFlow" : false,
+ "requirement" : "REQUIRED",
+ "priority" : 10,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticator" : "auth-otp-form",
+ "authenticatorFlow" : false,
+ "requirement" : "REQUIRED",
+ "priority" : 20,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ } ]
+ }, {
+ "id" : "d5a6c5aa-aa28-4d8a-b5a1-2d0df85a9979",
+ "alias" : "Handle Existing Account",
+ "description" : "Handle what to do if there is existing account with same email/username like authenticated identity provider",
+ "providerId" : "basic-flow",
+ "topLevel" : false,
+ "builtIn" : true,
+ "authenticationExecutions" : [ {
+ "authenticator" : "idp-confirm-link",
+ "authenticatorFlow" : false,
+ "requirement" : "REQUIRED",
+ "priority" : 10,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticatorFlow" : true,
+ "requirement" : "REQUIRED",
+ "priority" : 20,
+ "autheticatorFlow" : true,
+ "flowAlias" : "Account verification options",
+ "userSetupAllowed" : false
+ } ]
+ }, {
+ "id" : "940b59eb-3977-4354-8ad0-a60a513a91ca",
+ "alias" : "Reset - Conditional OTP",
+ "description" : "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.",
+ "providerId" : "basic-flow",
+ "topLevel" : false,
+ "builtIn" : true,
+ "authenticationExecutions" : [ {
+ "authenticator" : "conditional-user-configured",
+ "authenticatorFlow" : false,
+ "requirement" : "REQUIRED",
+ "priority" : 10,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticator" : "reset-otp",
+ "authenticatorFlow" : false,
+ "requirement" : "REQUIRED",
+ "priority" : 20,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ } ]
+ }, {
+ "id" : "653228d9-a3a7-41b1-b6c1-eaea8b2f4e87",
+ "alias" : "User creation or linking",
+ "description" : "Flow for the existing/non-existing user alternatives",
+ "providerId" : "basic-flow",
+ "topLevel" : false,
+ "builtIn" : true,
+ "authenticationExecutions" : [ {
+ "authenticatorConfig" : "create unique user config",
+ "authenticator" : "idp-create-user-if-unique",
+ "authenticatorFlow" : false,
+ "requirement" : "ALTERNATIVE",
+ "priority" : 10,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticatorFlow" : true,
+ "requirement" : "ALTERNATIVE",
+ "priority" : 20,
+ "autheticatorFlow" : true,
+ "flowAlias" : "Handle Existing Account",
+ "userSetupAllowed" : false
+ } ]
+ }, {
+ "id" : "9c6a2dff-b711-4bbd-8606-d8939ba4d9ca",
+ "alias" : "Verify Existing Account by Re-authentication",
+ "description" : "Reauthentication of existing account",
+ "providerId" : "basic-flow",
+ "topLevel" : false,
+ "builtIn" : true,
+ "authenticationExecutions" : [ {
+ "authenticator" : "idp-username-password-form",
+ "authenticatorFlow" : false,
+ "requirement" : "REQUIRED",
+ "priority" : 10,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticatorFlow" : true,
+ "requirement" : "CONDITIONAL",
+ "priority" : 20,
+ "autheticatorFlow" : true,
+ "flowAlias" : "First broker login - Conditional OTP",
+ "userSetupAllowed" : false
+ } ]
+ }, {
+ "id" : "5c564dc9-1ea5-4977-ad52-eb6a1213b88e",
+ "alias" : "browser",
+ "description" : "browser based authentication",
+ "providerId" : "basic-flow",
+ "topLevel" : true,
+ "builtIn" : true,
+ "authenticationExecutions" : [ {
+ "authenticator" : "auth-cookie",
+ "authenticatorFlow" : false,
+ "requirement" : "ALTERNATIVE",
+ "priority" : 10,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticator" : "auth-spnego",
+ "authenticatorFlow" : false,
+ "requirement" : "DISABLED",
+ "priority" : 20,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticator" : "identity-provider-redirector",
+ "authenticatorFlow" : false,
+ "requirement" : "ALTERNATIVE",
+ "priority" : 25,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticatorFlow" : true,
+ "requirement" : "ALTERNATIVE",
+ "priority" : 30,
+ "autheticatorFlow" : true,
+ "flowAlias" : "forms",
+ "userSetupAllowed" : false
+ } ]
+ }, {
+ "id" : "28f28120-2107-4cd5-a2bf-a139653db782",
+ "alias" : "clients",
+ "description" : "Base authentication for clients",
+ "providerId" : "client-flow",
+ "topLevel" : true,
+ "builtIn" : true,
+ "authenticationExecutions" : [ {
+ "authenticator" : "client-secret",
+ "authenticatorFlow" : false,
+ "requirement" : "ALTERNATIVE",
+ "priority" : 10,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticator" : "client-jwt",
+ "authenticatorFlow" : false,
+ "requirement" : "ALTERNATIVE",
+ "priority" : 20,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticator" : "client-secret-jwt",
+ "authenticatorFlow" : false,
+ "requirement" : "ALTERNATIVE",
+ "priority" : 30,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticator" : "client-x509",
+ "authenticatorFlow" : false,
+ "requirement" : "ALTERNATIVE",
+ "priority" : 40,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ } ]
+ }, {
+ "id" : "c7ccf556-1270-4adb-8c9a-d50d0370acda",
+ "alias" : "direct grant",
+ "description" : "OpenID Connect Resource Owner Grant",
+ "providerId" : "basic-flow",
+ "topLevel" : true,
+ "builtIn" : true,
+ "authenticationExecutions" : [ {
+ "authenticator" : "direct-grant-validate-username",
+ "authenticatorFlow" : false,
+ "requirement" : "REQUIRED",
+ "priority" : 10,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticator" : "direct-grant-validate-password",
+ "authenticatorFlow" : false,
+ "requirement" : "REQUIRED",
+ "priority" : 20,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticatorFlow" : true,
+ "requirement" : "CONDITIONAL",
+ "priority" : 30,
+ "autheticatorFlow" : true,
+ "flowAlias" : "Direct Grant - Conditional OTP",
+ "userSetupAllowed" : false
+ } ]
+ }, {
+ "id" : "2dd78821-8a10-4701-8131-22f02eeca193",
+ "alias" : "docker auth",
+ "description" : "Used by Docker clients to authenticate against the IDP",
+ "providerId" : "basic-flow",
+ "topLevel" : true,
+ "builtIn" : true,
+ "authenticationExecutions" : [ {
+ "authenticator" : "docker-http-basic-authenticator",
+ "authenticatorFlow" : false,
+ "requirement" : "REQUIRED",
+ "priority" : 10,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ } ]
+ }, {
+ "id" : "3d5aad69-76b9-4859-8500-9ded3a096e22",
+ "alias" : "first broker login",
+ "description" : "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account",
+ "providerId" : "basic-flow",
+ "topLevel" : true,
+ "builtIn" : true,
+ "authenticationExecutions" : [ {
+ "authenticatorConfig" : "review profile config",
+ "authenticator" : "idp-review-profile",
+ "authenticatorFlow" : false,
+ "requirement" : "REQUIRED",
+ "priority" : 10,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticatorFlow" : true,
+ "requirement" : "REQUIRED",
+ "priority" : 20,
+ "autheticatorFlow" : true,
+ "flowAlias" : "User creation or linking",
+ "userSetupAllowed" : false
+ } ]
+ }, {
+ "id" : "ffd3082c-2a68-425c-a248-95fc076b211c",
+ "alias" : "forms",
+ "description" : "Username, password, otp and other auth forms.",
+ "providerId" : "basic-flow",
+ "topLevel" : false,
+ "builtIn" : true,
+ "authenticationExecutions" : [ {
+ "authenticator" : "auth-username-password-form",
+ "authenticatorFlow" : false,
+ "requirement" : "REQUIRED",
+ "priority" : 10,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticatorFlow" : true,
+ "requirement" : "CONDITIONAL",
+ "priority" : 20,
+ "autheticatorFlow" : true,
+ "flowAlias" : "Browser - Conditional OTP",
+ "userSetupAllowed" : false
+ } ]
+ }, {
+ "id" : "1fcb0e73-4ec2-4605-9e49-cc7588a6c818",
+ "alias" : "http challenge",
+ "description" : "An authentication flow based on challenge-response HTTP Authentication Schemes",
+ "providerId" : "basic-flow",
+ "topLevel" : true,
+ "builtIn" : true,
+ "authenticationExecutions" : [ {
+ "authenticator" : "no-cookie-redirect",
+ "authenticatorFlow" : false,
+ "requirement" : "REQUIRED",
+ "priority" : 10,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticatorFlow" : true,
+ "requirement" : "REQUIRED",
+ "priority" : 20,
+ "autheticatorFlow" : true,
+ "flowAlias" : "Authentication Options",
+ "userSetupAllowed" : false
+ } ]
+ }, {
+ "id" : "39102862-55f7-4134-be4f-918b2cbfa169",
+ "alias" : "registration",
+ "description" : "registration flow",
+ "providerId" : "basic-flow",
+ "topLevel" : true,
+ "builtIn" : true,
+ "authenticationExecutions" : [ {
+ "authenticator" : "registration-page-form",
+ "authenticatorFlow" : true,
+ "requirement" : "REQUIRED",
+ "priority" : 10,
+ "autheticatorFlow" : true,
+ "flowAlias" : "registration form",
+ "userSetupAllowed" : false
+ } ]
+ }, {
+ "id" : "27a49f8e-4a94-4818-991e-eefddf680431",
+ "alias" : "registration form",
+ "description" : "registration form",
+ "providerId" : "form-flow",
+ "topLevel" : false,
+ "builtIn" : true,
+ "authenticationExecutions" : [ {
+ "authenticator" : "registration-user-creation",
+ "authenticatorFlow" : false,
+ "requirement" : "REQUIRED",
+ "priority" : 20,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticator" : "registration-profile-action",
+ "authenticatorFlow" : false,
+ "requirement" : "REQUIRED",
+ "priority" : 40,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticator" : "registration-password-action",
+ "authenticatorFlow" : false,
+ "requirement" : "REQUIRED",
+ "priority" : 50,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticator" : "registration-recaptcha-action",
+ "authenticatorFlow" : false,
+ "requirement" : "DISABLED",
+ "priority" : 60,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ } ]
+ }, {
+ "id" : "c3056f11-5d2d-4e77-9a17-94168854f540",
+ "alias" : "reset credentials",
+ "description" : "Reset credentials for a user if they forgot their password or something",
+ "providerId" : "basic-flow",
+ "topLevel" : true,
+ "builtIn" : true,
+ "authenticationExecutions" : [ {
+ "authenticator" : "reset-credentials-choose-user",
+ "authenticatorFlow" : false,
+ "requirement" : "REQUIRED",
+ "priority" : 10,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticator" : "reset-credential-email",
+ "authenticatorFlow" : false,
+ "requirement" : "REQUIRED",
+ "priority" : 20,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticator" : "reset-password",
+ "authenticatorFlow" : false,
+ "requirement" : "REQUIRED",
+ "priority" : 30,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticatorFlow" : true,
+ "requirement" : "CONDITIONAL",
+ "priority" : 40,
+ "autheticatorFlow" : true,
+ "flowAlias" : "Reset - Conditional OTP",
+ "userSetupAllowed" : false
+ } ]
+ }, {
+ "id" : "a91298df-ebf0-4c98-8587-6c680a2c4ccd",
+ "alias" : "saml ecp",
+ "description" : "SAML ECP Profile Authentication Flow",
+ "providerId" : "basic-flow",
+ "topLevel" : true,
+ "builtIn" : true,
+ "authenticationExecutions" : [ {
+ "authenticator" : "http-basic-authenticator",
+ "authenticatorFlow" : false,
+ "requirement" : "REQUIRED",
+ "priority" : 10,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ } ]
+ } ],
+ "authenticatorConfig" : [ {
+ "id" : "e1a83662-2baf-4955-bf07-6c3d50bc1196",
+ "alias" : "create unique user config",
+ "config" : {
+ "require.password.update.after.registration" : "false"
+ }
+ }, {
+ "id" : "d683f0f4-7685-40e1-9521-5761fdd9e111",
+ "alias" : "review profile config",
+ "config" : {
+ "update.profile.on.first.login" : "missing"
}
- ],
- "browserFlow": "browser",
- "registrationFlow": "registration",
- "directGrantFlow": "direct grant",
- "resetCredentialsFlow": "reset credentials",
- "clientAuthenticationFlow": "clients",
- "dockerAuthenticationFlow": "docker auth",
- "attributes": {
- "clientOfflineSessionMaxLifespan": "0",
- "clientSessionIdleTimeout": "0",
- "clientSessionMaxLifespan": "0",
- "clientOfflineSessionIdleTimeout": "0"
+ } ],
+ "requiredActions" : [ {
+ "alias" : "CONFIGURE_TOTP",
+ "name" : "Configure OTP",
+ "providerId" : "CONFIGURE_TOTP",
+ "enabled" : true,
+ "defaultAction" : false,
+ "priority" : 10,
+ "config" : { }
+ }, {
+ "alias" : "terms_and_conditions",
+ "name" : "Terms and Conditions",
+ "providerId" : "terms_and_conditions",
+ "enabled" : false,
+ "defaultAction" : false,
+ "priority" : 20,
+ "config" : { }
+ }, {
+ "alias" : "UPDATE_PASSWORD",
+ "name" : "Update Password",
+ "providerId" : "UPDATE_PASSWORD",
+ "enabled" : true,
+ "defaultAction" : false,
+ "priority" : 30,
+ "config" : { }
+ }, {
+ "alias" : "UPDATE_PROFILE",
+ "name" : "Update Profile",
+ "providerId" : "UPDATE_PROFILE",
+ "enabled" : true,
+ "defaultAction" : false,
+ "priority" : 40,
+ "config" : { }
+ }, {
+ "alias" : "VERIFY_EMAIL",
+ "name" : "Verify Email",
+ "providerId" : "VERIFY_EMAIL",
+ "enabled" : true,
+ "defaultAction" : false,
+ "priority" : 50,
+ "config" : { }
+ }, {
+ "alias" : "delete_account",
+ "name" : "Delete Account",
+ "providerId" : "delete_account",
+ "enabled" : false,
+ "defaultAction" : false,
+ "priority" : 60,
+ "config" : { }
+ }, {
+ "alias" : "update_user_locale",
+ "name" : "Update User Locale",
+ "providerId" : "update_user_locale",
+ "enabled" : true,
+ "defaultAction" : false,
+ "priority" : 1000,
+ "config" : { }
+ } ],
+ "browserFlow" : "browser",
+ "registrationFlow" : "registration",
+ "directGrantFlow" : "direct grant",
+ "resetCredentialsFlow" : "reset credentials",
+ "clientAuthenticationFlow" : "clients",
+ "dockerAuthenticationFlow" : "docker auth",
+ "attributes" : {
+ "cibaBackchannelTokenDeliveryMode" : "poll",
+ "cibaExpiresIn" : "120",
+ "cibaAuthRequestedUserHint" : "login_hint",
+ "oauth2DeviceCodeLifespan" : "600",
+ "clientOfflineSessionMaxLifespan" : "0",
+ "oauth2DevicePollingInterval" : "5",
+ "clientSessionIdleTimeout" : "0",
+ "parRequestUriLifespan" : "60",
+ "clientSessionMaxLifespan" : "0",
+ "clientOfflineSessionIdleTimeout" : "0",
+ "cibaInterval" : "5"
+ },
+ "keycloakVersion" : "18.0.2",
+ "userManagedAccessAllowed" : false,
+ "clientProfiles" : {
+ "profiles" : [ ]
},
- "keycloakVersion": "12.0.1",
- "userManagedAccessAllowed": false
-}
+ "clientPolicies" : {
+ "policies" : [ ]
+ }
+} \ No newline at end of file
diff --git a/doc/source/howtos/openid-with-keycloak.rst b/doc/source/howtos/openid-with-keycloak.rst
index 74d3a27c0..7fb258fea 100644
--- a/doc/source/howtos/openid-with-keycloak.rst
+++ b/doc/source/howtos/openid-with-keycloak.rst
@@ -73,6 +73,19 @@ Finally, go back to the clients list and pick the ``zuul`` client again. Click
on ``Client Scopes``, and add the ``zuul_aud`` scope to the ``Assigned Default
Client Scopes``.
+Configuring JWT signing algorithms
+..................................
+
+.. note::
+
+ Skip this step if you are using a keycloak version prior to 18.0.
+
+Due to current limitations with the pyJWT library, Zuul does not support every default
+signing algorithm used by Keycloak.
+
+Go to `my_realm->Settings->Keys`, then choose `rsa-enc-generated` (this should be mapped
+to "RSA-OAEP") if available. Then set `enabled` to false and save your changes.
+
(Optional) Set up a social identity provider
............................................
diff --git a/doc/source/tutorials/keycloak.rst b/doc/source/tutorials/keycloak.rst
index a6c011eed..5242a4f05 100644
--- a/doc/source/tutorials/keycloak.rst
+++ b/doc/source/tutorials/keycloak.rst
@@ -28,6 +28,15 @@ to `/etc/hosts`. Make sure you have a line that looks like this:
127.0.0.1 localhost keycloak
+If you are using podman, you need to add the following option in $HOME/.config/containers/containers.conf:
+
+.. code-block::
+
+ [containers]
+ no_hosts=true
+
+This way your /etc/hosts settings will not interfere with podman's networking.
+
Restart Zuul Containers
-----------------------
@@ -37,14 +46,14 @@ that we can update Zuul's configuration to add authentication.
.. code-block:: shell
cd zuul/doc/source/examples
- sudo -E docker-compose -p zuul-tutorial down
+ sudo -E docker-compose-compose -p zuul-tutorial down
Restart the containers with a new Zuul configuration.
.. code-block:: shell
cd zuul/doc/source/examples
- ZUUL_TUTORIAL_CONFIG="./keycloak/etc_zuul/" sudo -E docker-compose -p zuul-tutorial up -d
+ ZUUL_TUTORIAL_CONFIG="./keycloak/etc_zuul/" sudo -E docker-compose-compose -p zuul-tutorial up -d
This tells docker-compose to use these Zuul `config files
<https://opendev.org/zuul/zuul/src/branch/master/doc/source/examples/keycloak>`_.
@@ -58,7 +67,7 @@ with this command:
.. code-block:: shell
cd zuul/doc/source/examples/keycloak
- sudo -E docker-compose -p zuul-tutorial-keycloak up -d
+ sudo -E docker-compose-compose -p zuul-tutorial-keycloak up -d
Once Keycloak is running, you can visit the web interface at
http://localhost:8082/
diff --git a/playbooks/zuul-stream/fixtures/test-stream.yaml b/playbooks/zuul-stream/fixtures/test-stream.yaml
index 0c10304b2..0326ae54e 100644
--- a/playbooks/zuul-stream/fixtures/test-stream.yaml
+++ b/playbooks/zuul-stream/fixtures/test-stream.yaml
@@ -64,3 +64,15 @@
- name: Print binary data
command: echo -e '\x80abc'
+
+ - name: Find any console log files
+ find:
+ paths: /tmp
+ patterns: 'console-*.log'
+ register: _tmp_files
+
+ # We check this list in zuul-stream/functional.yaml to make sure
+ # we're cleaning up console log files.
+ - name: Dump tmp files
+ debug:
+ var: _tmp_files
diff --git a/playbooks/zuul-stream/functional.yaml b/playbooks/zuul-stream/functional.yaml
index 3b79e7b40..b8a44a87c 100644
--- a/playbooks/zuul-stream/functional.yaml
+++ b/playbooks/zuul-stream/functional.yaml
@@ -19,12 +19,37 @@
ZUUL_JOB_LOG_CONFIG: "{{ ansible_user_dir}}/logging.json"
ZUUL_JOBDIR: "{{ ansible_user_dir}}"
PYTHONPATH: "{{ python_path }}"
+ register: _success_output
+
+ - name: Save raw output to file
+ copy:
+ content: '{{ _success_output.stdout }}'
+ dest: 'console-job-output-success-19887.txt'
- name: Save output
shell: |
mv job-output.txt job-output-success-19887.txt
mv job-output.json job-output-success-19887.json
+ - name: Check protocol version
+ assert:
+ that:
+ - "'[node1] Reports streaming version: 1' in _success_output.stdout"
+
+ # Streamer puts out a line like
+ # [node1] Starting to log 916b2084-4bbb-80e5-248e-000000000016-1-node1 for task TASK: Print binary data
+ # One of the tasks in job-output shows find: results;
+ # the console file for this task should not be there.
+ - name: Validate temporary files removed
+ shell: |
+ for f in $(grep 'Starting to log' console-job-output-success-19887.txt | awk '{print $5}'); do
+ echo "Checking ${f}"
+ if grep -q '"path": "/tmp/console-'${f}'.log"' job-output-success-19887.txt; then
+ echo "*** /tmp/${f}.log still exists"
+ exit 1
+ fi
+ done
+
# NOTE(ianw) 2022-07 : we deliberatly have this second step to run
# against the console setup by the infrastructure executor in the
# job pre playbooks as a backwards compatability sanity check.
@@ -37,6 +62,12 @@
ZUUL_JOB_LOG_CONFIG: "{{ ansible_user_dir}}/logging.json"
ZUUL_JOBDIR: "{{ ansible_user_dir}}"
PYTHONPATH: "{{ python_path }}"
+ register: _success_output
+
+ - name: Save raw output to file
+ copy:
+ content: '{{ _success_output.stdout }}'
+ dest: 'console-job-output-success-19885.txt'
- name: Save output
shell: |
@@ -46,8 +77,10 @@
- name: Validate outputs
include_tasks: validate.yaml
loop:
- - job-output-success-19887.txt
- - job-output-success-19885.txt
+ - { node: 'node1', filename: 'job-output-success-19887.txt' }
+ - { node: 'node2', filename: 'job-output-success-19887.txt' }
+ - { node: 'node1', filename: 'job-output-success-19885.txt' }
+ - { node: 'node2', filename: 'job-output-success-19885.txt' }
# failure case
diff --git a/playbooks/zuul-stream/post.yaml b/playbooks/zuul-stream/post.yaml
index a93771881..4beb8d1f9 100644
--- a/playbooks/zuul-stream/post.yaml
+++ b/playbooks/zuul-stream/post.yaml
@@ -18,6 +18,7 @@
with_items:
- logging.json
- ansible.cfg
+ - console-job-output-success-19887.txt
- job-output-success-19887.txt
- job-output-success-19887.json
- job-output-success-19885.txt
diff --git a/playbooks/zuul-stream/validate.yaml b/playbooks/zuul-stream/validate.yaml
index 73ccd873a..81c613406 100644
--- a/playbooks/zuul-stream/validate.yaml
+++ b/playbooks/zuul-stream/validate.yaml
@@ -1,38 +1,29 @@
- name: Validate output - setupvar
shell: |
- egrep "^.*\| node1 \|\s+\"setupvar\": {" {{ item }}
- egrep "^.*\| node2 \|\s+\"setupvar\": {" {{ item }}
+ egrep "^.*\| {{ item.node }} \|\s+\"setupvar\": {" {{ item.filename }}
- name: Validate output - shell task
shell: |
- egrep "^.*\| node1 \| 1: lo:" {{ item }}
- egrep "^.*\| node2 \| 1: lo:" {{ item }}
+ egrep "^.*\| {{ item.node }} \| 1: lo:" {{ item.filename }}
- name: Validate output - loop with items
shell: |
- egrep "^.+\| node1 \| ok: Item: item1" {{ item }}
- egrep "^.+\| node1 \| ok: Item: item2" {{ item }}
- egrep "^.+\| node1 \| ok: Item: item3" {{ item }}
- egrep "^.+\| node2 \| ok: Item: item1" {{ item }}
- egrep "^.+\| node2 \| ok: Item: item2" {{ item }}
- egrep "^.+\| node2 \| ok: Item: item3" {{ item }}
+ egrep "^.+\| {{ item.node }} \| ok: Item: item1" {{ item.filename }}
+ egrep "^.+\| {{ item.node }} \| ok: Item: item2" {{ item.filename }}
+ egrep "^.+\| {{ item.node }} \| ok: Item: item3" {{ item.filename }}
- name: Validate output - loop with complex items
shell: |
- egrep "^.+\| node1 \| ok: Item: Runtime" {{ item }}
- egrep "^.+\| node2 \| ok: Item: Runtime" {{ item }}
+ egrep "^.+\| {{ item.node }} \| ok: Item: Runtime" {{ item.filename }}
- name: Validate output - failed shell task
shell: |
- egrep "^.+\| node1 \| Exception: Test module failure exception task" {{ item }}
- egrep "^.+\| node2 \| Exception: Test module failure exception task" {{ item }}
+ egrep "^.+\| {{ item.node }} \| Exception: Test module failure exception task" {{ item.filename }}
- name: Validate output - item loop with exception
shell: |
- egrep "^.+\| node1 \| Exception: Test module failure exception loop" {{ item }}
- egrep "^.+\| node2 \| Exception: Test module failure exception loop" {{ item }}
+ egrep "^.+\| {{ item.node }} \| Exception: Test module failure exception loop" {{ item.filename }}
- name: Validate output - binary data
shell: |
- egrep "^.*\| node1 \| \\\\x80abc" {{ item }}
- egrep "^.*\| node2 \| \\\\x80abc" {{ item }}
+ egrep "^.*\| {{ item.node }} \| \\\\x80abc" {{ item.filename }}
diff --git a/setup.cfg b/setup.cfg
index 382e12019..74905bf6c 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,12 +1,12 @@
[metadata]
name = zuul
summary = A Project Gating System
-description-file =
+description_file =
README.rst
author = Zuul Team
-author-email = zuul-discuss@lists.zuul-ci.org
-home-page = https://zuul-ci.org/
-python-requires = >=3.8
+author_email = zuul-discuss@lists.zuul-ci.org
+home_page = https://zuul-ci.org/
+python_requires = >=3.8
classifier =
Intended Audience :: Information Technology
Intended Audience :: System Administrators
diff --git a/tests/fixtures/layouts/special-characters-job.yaml b/tests/fixtures/layouts/special-characters-job.yaml
new file mode 100644
index 000000000..20308c6d6
--- /dev/null
+++ b/tests/fixtures/layouts/special-characters-job.yaml
@@ -0,0 +1,2 @@
+- job:
+ name: "a@b/c"
diff --git a/tests/remote/test_remote_zuul_stream.py b/tests/remote/test_remote_zuul_stream.py
index 1f6b7fff7..1c705127e 100644
--- a/tests/remote/test_remote_zuul_stream.py
+++ b/tests/remote/test_remote_zuul_stream.py
@@ -29,7 +29,7 @@ class FunctionalZuulStreamMixIn:
self.log_console_port = 19000 + int(
self.ansible_core_version.split('.')[1])
self.executor_server.log_console_port = self.log_console_port
- self.wait_timeout = 120
+ self.wait_timeout = 180
self.fake_nodepool.remote_ansible = True
ansible_remote = os.environ.get('ZUUL_REMOTE_IPV4')
diff --git a/tests/unit/test_gerrit.py b/tests/unit/test_gerrit.py
index aa2bb1758..47545b9be 100644
--- a/tests/unit/test_gerrit.py
+++ b/tests/unit/test_gerrit.py
@@ -13,6 +13,7 @@
# under the License.
import os
+import threading
import textwrap
from unittest import mock
@@ -868,3 +869,58 @@ class TestGerritFake(ZuulTestCase):
# The Gerrit connection method filters out the queried change
ret = self.fake_gerrit._getSubmittedTogether(C1, None)
self.assertEqual(ret, [(4, 1)])
+
+
+class TestGerritConnection(ZuulTestCase):
+ config_file = 'zuul-gerrit-web.conf'
+ tenant_config_file = 'config/single-tenant/main.yaml'
+
+ def test_zuul_query_ltime(self):
+ # Add a lock around the event queue iterator so that we can
+ # ensure that multiple events arrive before the first is
+ # processed.
+ lock = threading.Lock()
+
+ orig_iterEvents = self.fake_gerrit.gerrit_event_connector.\
+ event_queue._iterEvents
+
+ def _iterEvents(*args, **kw):
+ with lock:
+ return orig_iterEvents(*args, **kw)
+
+ self.patch(self.fake_gerrit.gerrit_event_connector.event_queue,
+ '_iterEvents', _iterEvents)
+
+ A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
+ B = self.fake_gerrit.addFakeChange('org/project', 'master', 'B')
+ B.setDependsOn(A, 1)
+ # Hold the connection queue processing so these events get
+ # processed together
+ with lock:
+ self.fake_gerrit.addEvent(A.addApproval('Code-Review', 2))
+ self.fake_gerrit.addEvent(B.addApproval('Approved', 1))
+ self.fake_gerrit.addEvent(B.addApproval('Code-Review', 2))
+ self.waitUntilSettled()
+ self.assertHistory([])
+ # One query for each change in the above cluster of events.
+ self.assertEqual(A.queried, 1)
+ self.assertEqual(B.queried, 1)
+ self.fake_gerrit.addEvent(A.addApproval('Approved', 1))
+ self.waitUntilSettled()
+ self.assertHistory([
+ dict(name="project-merge", result="SUCCESS", changes="1,1"),
+ dict(name="project-test1", result="SUCCESS", changes="1,1"),
+ dict(name="project-test2", result="SUCCESS", changes="1,1"),
+ dict(name="project-merge", result="SUCCESS", changes="1,1 2,1"),
+ dict(name="project-test1", result="SUCCESS", changes="1,1 2,1"),
+ dict(name="project-test2", result="SUCCESS", changes="1,1 2,1"),
+ ], ordered=False)
+ # One query due to the event on change A, followed by a query
+ # to verify the merge.
+ self.assertEqual(A.queried, 3)
+ # No query for change B necessary since our cache is up to
+ # date with respect for the triggering event. One query to
+ # verify the merge.
+ self.assertEqual(B.queried, 2)
+ self.assertEqual(A.data['status'], 'MERGED')
+ self.assertEqual(B.data['status'], 'MERGED')
diff --git a/tests/unit/test_scheduler.py b/tests/unit/test_scheduler.py
index 5e0385be3..66c508fea 100644
--- a/tests/unit/test_scheduler.py
+++ b/tests/unit/test_scheduler.py
@@ -226,6 +226,18 @@ class TestSchedulerZoneFallback(ZuulTestCase):
def test_jobs_executed(self):
"Test that jobs are executed and a change is merged per zone"
self.hold_jobs_in_queue = True
+
+ # Validate that the reported executor stats are correct. Since
+ # the executor accepts zoned and unzoned job it should be counted
+ # in both metrics.
+ self.assertReportedStat(
+ 'zuul.executors.online', value='1', kind='g')
+ self.assertReportedStat(
+ 'zuul.executors.unzoned.online', value='1', kind='g')
+ self.assertReportedStat(
+ 'zuul.executors.zone.test-provider_vpn.online',
+ value='1', kind='g')
+
A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
A.addApproval('Code-Review', 2)
self.fake_gerrit.addEvent(A.addApproval('Approved', 1))
diff --git a/tests/unit/test_web.py b/tests/unit/test_web.py
index 53546c1de..ba1931436 100644
--- a/tests/unit/test_web.py
+++ b/tests/unit/test_web.py
@@ -1005,6 +1005,11 @@ class TestWeb(BaseTestWeb):
job = self.get_url("api/tenant/tenant-one/job/noop").json()
self.assertEqual("noop", job[0]["name"])
+ @simple_layout('layouts/special-characters-job.yaml')
+ def test_web_job_special_characters(self):
+ job = self.get_url("api/tenant/tenant-one/job/a%40b%2Fc").json()
+ self.assertEqual("a@b/c", job[0]["name"])
+
def test_freeze_jobs(self):
# Test can get a list of the jobs for a given project+pipeline+branch.
resp = self.get_url(
diff --git a/tests/zuul_client/test_zuulclient.py b/tests/zuul_client/test_zuulclient.py
index cc7d789c7..9d381c99b 100644
--- a/tests/zuul_client/test_zuulclient.py
+++ b/tests/zuul_client/test_zuulclient.py
@@ -629,3 +629,92 @@ class TestZuulClientBuildInfo(TestZuulClientQueryData,
x['url'] == 'http://example.com/docs'
for x in artifacts),
output)
+
+
+class TestZuulClientJobGraph(BaseTestWeb):
+ def _split_pretty_table(self, output):
+ lines = output.decode().split('\n')
+ headers = [x.strip() for x in lines[1].split('|') if x != '']
+ # Trim headers and last line of the table
+ return [dict(zip(headers,
+ [x.strip() for x in l.split('|') if x != '']))
+ for l in lines[3:-2]]
+
+ def test_job_graph(self):
+ """Test the job-graph command"""
+ p = subprocess.Popen(
+ ['zuul-client',
+ '--zuul-url', self.base_url,
+ 'job-graph',
+ '--tenant', 'tenant-one',
+ '--pipeline', 'check',
+ '--project', 'org/project1',
+ '--branch', 'master',
+ ],
+ stdout=subprocess.PIPE)
+ output, err = p.communicate()
+ self.assertEqual(p.returncode, 0, (output, err))
+ results = self._split_pretty_table(output)
+ expected = [
+ {'Job': 'project-merge', 'Dependencies': ''},
+ {'Job': 'project-test1', 'Dependencies': 'project-merge'},
+ {'Job': 'project-test2', 'Dependencies': 'project-merge'},
+ {'Job': 'project1-project2-integration',
+ 'Dependencies': 'project-merge'}
+ ]
+ self.assertEqual(results, expected)
+
+ def test_job_graph_dot(self):
+ """Test the job-graph command dot output"""
+ p = subprocess.Popen(
+ ['zuul-client',
+ '--format', 'dot',
+ '--zuul-url', self.base_url,
+ 'job-graph',
+ '--tenant', 'tenant-one',
+ '--pipeline', 'check',
+ '--project', 'org/project1',
+ '--branch', 'master',
+ ],
+ stdout=subprocess.PIPE)
+ output, err = p.communicate()
+ self.assertEqual(p.returncode, 0, (output, err))
+ expected = textwrap.dedent('''\
+ digraph job_graph {
+ rankdir=LR;
+ node [shape=box];
+ "project-merge";
+ "project-merge" -> "project-test1" [dir=back];
+ "project-merge" -> "project-test2" [dir=back];
+ "project-merge" -> "project1-project2-integration" [dir=back];
+ }
+ ''').encode('utf8')
+ self.assertEqual(output.strip(), expected.strip())
+
+
+class TestZuulClientFreezeJob(BaseTestWeb):
+ def test_freeze_job(self):
+ """Test the freeze-job command"""
+ p = subprocess.Popen(
+ ['zuul-client',
+ '--zuul-url', self.base_url,
+ 'freeze-job',
+ '--tenant', 'tenant-one',
+ '--pipeline', 'check',
+ '--project', 'org/project1',
+ '--branch', 'master',
+ '--job', 'project-test1',
+ ],
+ stdout=subprocess.PIPE)
+ output, err = p.communicate()
+ self.assertEqual(p.returncode, 0, (output, err))
+ output = output.decode('utf8')
+ for s in [
+ 'Job: project-test1',
+ 'Branch: master',
+ 'Ansible Version:',
+ 'Workspace Scheme: golang',
+ ('gerrit:common-config:playbooks/project-test1.yaml'
+ '@master [trusted]'),
+ ]:
+ self.assertIn(s, output)
diff --git a/tools/test-setup-docker.sh b/tools/test-setup-docker.sh
index b90a422c2..a0fcf9f5a 100755
--- a/tools/test-setup-docker.sh
+++ b/tools/test-setup-docker.sh
@@ -1,6 +1,18 @@
#!/bin/bash
-set -eu
+# This runs ZooKeeper and databases in docker containers, which are
+# required for tests.
+
+# This setup needs to be run as a user that can run docker or podman, or by
+# setting $ROOTCMD to a user substitution tool like "sudo" in the calling
+# environment.
+
+set -xeu
+
+# Default ROOTCMD to the 'env' command, otherwise variable assignments will be
+# interpreted as command when no ROOTCMD is given. The reason for that is
+# Bash's simple command expansion.
+ROOTCMD=${ROOTCMD:-env}
cd $(dirname $0)
SCRIPT_DIR="$(pwd)"
@@ -29,9 +41,9 @@ fi
MYSQL="${DOCKER} exec zuul-test-mysql mysql -u root -pinsecure_worker"
if [ "${COMPOSE}" == "docker-compose" ]; then
- docker-compose rm -sf
+ ${ROOTCMD} docker-compose rm -sf
else
- podman-compose down
+ ${ROOTCMD} podman-compose down
fi
CA_DIR=$SCRIPT_DIR/ca
@@ -39,15 +51,14 @@ CA_DIR=$SCRIPT_DIR/ca
mkdir -p $CA_DIR
$SCRIPT_DIR/zk-ca.sh $CA_DIR zuul-test-zookeeper
-export USER_ID=$(id -u)
-${COMPOSE} up -d
+${ROOTCMD} USER_ID=$(id -u) ${COMPOSE} up -d
echo "Waiting for mysql"
-timeout 30 bash -c "until ${MYSQL} -e 'show databases'; do sleep 0.5; done"
+timeout 30 bash -c "until ${ROOTCMD} ${MYSQL} -e 'show databases'; do sleep 0.5; done"
echo
echo "Setting up permissions for zuul tests"
-${MYSQL} -e "GRANT ALL PRIVILEGES ON *.* TO 'openstack_citest'@'%' identified by 'openstack_citest' WITH GRANT OPTION;"
-${MYSQL} -u openstack_citest -popenstack_citest -e "SET default_storage_engine=MYISAM; DROP DATABASE IF EXISTS openstack_citest; CREATE DATABASE openstack_citest CHARACTER SET utf8;"
+${ROOTCMD} ${MYSQL} -e "GRANT ALL PRIVILEGES ON *.* TO 'openstack_citest'@'%' identified by 'openstack_citest' WITH GRANT OPTION;"
+${ROOTCMD} ${MYSQL} -u openstack_citest -popenstack_citest -e "SET default_storage_engine=MYISAM; DROP DATABASE IF EXISTS openstack_citest; CREATE DATABASE openstack_citest CHARACTER SET utf8;"
echo "Finished"
diff --git a/tox.ini b/tox.ini
index 56adc201f..8d7fb8c22 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,11 +1,8 @@
[tox]
minversion = 3.2
skipsdist = True
-envlist = linters,py3{-docker}
+envlist = linters,py3
ignore_basepython_conflict = True
-# TODO(stephenfin): It would be good to set 'requires = tox-docker', but doing
-# so borks the tools/pip.sh wrapper we're using here and probably isn't what
-# we want in the gate :(
[testenv]
basepython = python3
@@ -19,7 +16,6 @@ setenv =
SQLALCHEMY_WARN_20={env:SQLALCHEMY_WARN_20:1}
PYTHONWARNINGS=always::DeprecationWarning:zuul.driver.sql.sqlconnection,always::DeprecationWarning:tests.base,always::DeprecationWarning:tests.unit.test_database,always::DeprecationWarning:zuul.driver.sql.alembic.env,always::DeprecationWarning:zuul.driver.sql.alembic.script
passenv =
- DOCKER_*
NODEPOOL_ZK_HOST
OS_LOG_CAPTURE
OS_LOG_DEFAULTS
@@ -40,8 +36,6 @@ whitelist_externals = bash
deps =
-r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
-docker =
- docker: zookeeper:3.4
commands =
bash -c 'stestr run --slowest --concurrency=`python -c "import multiprocessing; print(max(int(multiprocessing.cpu_count()-1),1))"` {posargs}'
@@ -96,7 +90,6 @@ deps =
[testenv:remote]
passenv =
- DOCKER_*
NODEPOOL_ZK_HOST
OS_LOG_CAPTURE
OS_LOG_DEFAULTS
diff --git a/web/package.json b/web/package.json
index 7b6eef9cd..3122e8839 100644
--- a/web/package.json
+++ b/web/package.json
@@ -13,6 +13,7 @@
"@softwarefactory-project/re-ansi": "^0.5.0",
"axios": "^0.26.0",
"broadcast-channel": "^4.5.0",
+ "d3-graphviz": "2.6.1",
"js-yaml": "^3.13.0",
"lodash": "^4.17.10",
"moment": "^2.22.2",
@@ -57,7 +58,7 @@
"start:openstack": "REACT_APP_ZUUL_API='https://zuul.openstack.org/api/' react-scripts start",
"start:multi": "REACT_APP_ZUUL_API='https://softwarefactory-project.io/zuul/api/' react-scripts start",
"start": "react-scripts start",
- "build": "react-scripts build",
+ "build": "react-scripts --max_old_space_size=4096 build",
"test": "react-scripts test --env=jsdom --watchAll=false",
"eject": "react-scripts eject",
"lint": "eslint --ext .js --ext .jsx src",
diff --git a/web/src/actions/freezejob.js b/web/src/actions/freezejob.js
new file mode 100644
index 000000000..13b32f124
--- /dev/null
+++ b/web/src/actions/freezejob.js
@@ -0,0 +1,84 @@
+// Copyright 2018 Red Hat, Inc
+// Copyright 2022 Acme Gating, LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+import * as API from '../api'
+
+export const FREEZE_JOB_FETCH_REQUEST = 'FREEZE_JOB_FETCH_REQUEST'
+export const FREEZE_JOB_FETCH_SUCCESS = 'FREEZE_JOB_FETCH_SUCCESS'
+export const FREEZE_JOB_FETCH_FAIL = 'FREEZE_JOB_FETCH_FAIL'
+
+export const requestFreezeJob = () => ({
+ type: FREEZE_JOB_FETCH_REQUEST
+})
+
+export function makeFreezeJobKey(pipeline, project, branch, job) {
+ return JSON.stringify({
+ pipeline, project, branch, job
+ })
+}
+
+export const receiveFreezeJob = (tenant, freezeJobKey, freezeJob) => {
+ return {
+ type: FREEZE_JOB_FETCH_SUCCESS,
+ tenant: tenant,
+ freezeJobKey: freezeJobKey,
+ freezeJob: freezeJob,
+ receivedAt: Date.now(),
+ }
+}
+
+const failedFreezeJob = error => ({
+ type: FREEZE_JOB_FETCH_FAIL,
+ error
+})
+
+const fetchFreezeJob = (tenant, pipeline, project, branch, job) => dispatch => {
+ dispatch(requestFreezeJob())
+ const freezeJobKey = makeFreezeJobKey(pipeline, project, branch, job)
+ return API.fetchFreezeJob(tenant.apiPrefix,
+ pipeline,
+ project,
+ branch,
+ job)
+ .then(response => dispatch(receiveFreezeJob(
+ tenant.name, freezeJobKey, response.data)))
+ .catch(error => dispatch(failedFreezeJob(error)))
+}
+
+const shouldFetchFreezeJob = (tenant, pipeline, project, branch, job, state) => {
+ const freezeJobKey = makeFreezeJobKey(pipeline, project, branch, job)
+ const tenantFreezeJobs = state.freezejob.freezeJobs[tenant.name]
+ if (tenantFreezeJobs) {
+ const freezeJob = tenantFreezeJobs[freezeJobKey]
+ if (!freezeJob) {
+ return true
+ }
+ if (freezeJob.isFetching) {
+ return false
+ }
+ return false
+ }
+ return true
+}
+
+export const fetchFreezeJobIfNeeded = (tenant, pipeline, project, branch, job,
+ force) => (
+ dispatch, getState) => {
+ if (force || shouldFetchFreezeJob(tenant, pipeline, project, branch, job,
+ getState())) {
+ return dispatch(fetchFreezeJob(tenant, pipeline, project, branch, job))
+ }
+ return Promise.resolve()
+}
diff --git a/web/src/actions/jobgraph.js b/web/src/actions/jobgraph.js
new file mode 100644
index 000000000..44ca2cb07
--- /dev/null
+++ b/web/src/actions/jobgraph.js
@@ -0,0 +1,83 @@
+// Copyright 2018 Red Hat, Inc
+// Copyright 2022 Acme Gating, LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+import * as API from '../api'
+
+export const JOB_GRAPH_FETCH_REQUEST = 'JOB_GRAPH_FETCH_REQUEST'
+export const JOB_GRAPH_FETCH_SUCCESS = 'JOB_GRAPH_FETCH_SUCCESS'
+export const JOB_GRAPH_FETCH_FAIL = 'JOB_GRAPH_FETCH_FAIL'
+
+export const requestJobGraph = () => ({
+ type: JOB_GRAPH_FETCH_REQUEST
+})
+
+export function makeJobGraphKey(project, pipeline, branch) {
+ return JSON.stringify({
+ project: project, pipeline: pipeline, branch: branch
+ })
+}
+
+export const receiveJobGraph = (tenant, jobGraphKey, jobGraph) => {
+ return {
+ type: JOB_GRAPH_FETCH_SUCCESS,
+ tenant: tenant,
+ jobGraphKey: jobGraphKey,
+ jobGraph: jobGraph,
+ receivedAt: Date.now(),
+ }
+}
+
+const failedJobGraph = error => ({
+ type: JOB_GRAPH_FETCH_FAIL,
+ error
+})
+
+const fetchJobGraph = (tenant, project, pipeline, branch) => dispatch => {
+ dispatch(requestJobGraph())
+ const jobGraphKey = makeJobGraphKey(project, pipeline, branch)
+ return API.fetchJobGraph(tenant.apiPrefix,
+ project,
+ pipeline,
+ branch)
+ .then(response => dispatch(receiveJobGraph(
+ tenant.name, jobGraphKey, response.data)))
+ .catch(error => dispatch(failedJobGraph(error)))
+}
+
+const shouldFetchJobGraph = (tenant, project, pipeline, branch, state) => {
+ const jobGraphKey = makeJobGraphKey(project, pipeline, branch)
+ const tenantJobGraphs = state.jobgraph.jobGraphs[tenant.name]
+ if (tenantJobGraphs) {
+ const jobGraph = tenantJobGraphs[jobGraphKey]
+ if (!jobGraph) {
+ return true
+ }
+ if (jobGraph.isFetching) {
+ return false
+ }
+ return false
+ }
+ return true
+}
+
+export const fetchJobGraphIfNeeded = (tenant, project, pipeline, branch,
+ force) => (
+ dispatch, getState) => {
+ if (force || shouldFetchJobGraph(tenant, project, pipeline, branch,
+ getState())) {
+ return dispatch(fetchJobGraph(tenant, project, pipeline, branch))
+ }
+ return Promise.resolve()
+}
diff --git a/web/src/actions/pipelines.js b/web/src/actions/pipelines.js
new file mode 100644
index 000000000..7139524f2
--- /dev/null
+++ b/web/src/actions/pipelines.js
@@ -0,0 +1,62 @@
+// Copyright 2018 Red Hat, Inc
+// Copyright 2022 Acme Gating, LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+import * as API from '../api'
+
+export const PIPELINES_FETCH_REQUEST = 'PIPELINES_FETCH_REQUEST'
+export const PIPELINES_FETCH_SUCCESS = 'PIPELINES_FETCH_SUCCESS'
+export const PIPELINES_FETCH_FAIL = 'PIPELINES_FETCH_FAIL'
+
+export const requestPipelines = () => ({
+ type: PIPELINES_FETCH_REQUEST
+})
+
+export const receivePipelines = (tenant, json) => ({
+ type: PIPELINES_FETCH_SUCCESS,
+ tenant: tenant,
+ pipelines: json,
+ receivedAt: Date.now()
+})
+
+const failedPipelines = error => ({
+ type: PIPELINES_FETCH_FAIL,
+ error
+})
+
+const fetchPipelines = (tenant) => dispatch => {
+ dispatch(requestPipelines())
+ return API.fetchPipelines(tenant.apiPrefix)
+ .then(response => dispatch(receivePipelines(tenant.name, response.data)))
+ .catch(error => dispatch(failedPipelines(error)))
+}
+
+const shouldFetchPipelines = (tenant, state) => {
+ const pipelines = state.pipelines.pipelines[tenant.name]
+ if (!pipelines || pipelines.length === 0) {
+ return true
+ }
+ if (pipelines.isFetching) {
+ return false
+ }
+ return false
+}
+
+export const fetchPipelinesIfNeeded = (tenant, force) => (
+ dispatch, getState) => {
+ if (force || shouldFetchPipelines(tenant, getState())) {
+ return dispatch(fetchPipelines(tenant))
+ }
+ return Promise.resolve()
+}
diff --git a/web/src/api.js b/web/src/api.js
index 7b574a6b6..1ba39998c 100644
--- a/web/src/api.js
+++ b/web/src/api.js
@@ -130,6 +130,13 @@ function fetchStatus(apiPrefix) {
function fetchChangeStatus(apiPrefix, changeId) {
return Axios.get(apiUrl + apiPrefix + 'status/change/' + changeId)
}
+function fetchFreezeJob(apiPrefix, pipelineName, projectName, branchName, jobName) {
+ return Axios.get(apiUrl + apiPrefix +
+ 'pipeline/' + pipelineName +
+ '/project/' + projectName +
+ '/branch/' + branchName +
+ '/freeze-job/' + jobName)
+}
function fetchBuild(apiPrefix, buildId) {
return Axios.get(apiUrl + apiPrefix + 'build/' + buildId)
}
@@ -150,6 +157,9 @@ function fetchBuildsets(apiPrefix, queryString) {
}
return Axios.get(apiUrl + apiPrefix + path)
}
+function fetchPipelines(apiPrefix) {
+ return Axios.get(apiUrl + apiPrefix + 'pipelines')
+}
function fetchProject(apiPrefix, projectName) {
return Axios.get(apiUrl + apiPrefix + 'project/' + projectName)
}
@@ -159,6 +169,13 @@ function fetchProjects(apiPrefix) {
function fetchJob(apiPrefix, jobName) {
return Axios.get(apiUrl + apiPrefix + 'job/' + jobName)
}
+function fetchJobGraph(apiPrefix, projectName, pipelineName, branchName) {
+ return Axios.get(apiUrl + apiPrefix +
+ 'pipeline/' + pipelineName +
+ '/project/' + projectName +
+ '/branch/' + branchName +
+ '/freeze-jobs')
+}
function fetchJobs(apiPrefix) {
return Axios.get(apiUrl + apiPrefix + 'jobs')
}
@@ -305,9 +322,12 @@ export {
fetchBuilds,
fetchBuildset,
fetchBuildsets,
+ fetchFreezeJob,
+ fetchPipelines,
fetchProject,
fetchProjects,
fetchJob,
+ fetchJobGraph,
fetchJobs,
fetchLabels,
fetchNodes,
diff --git a/web/src/containers/build/Console.jsx b/web/src/containers/build/Console.jsx
index 0b365df62..194e314ee 100644
--- a/web/src/containers/build/Console.jsx
+++ b/web/src/containers/build/Console.jsx
@@ -50,6 +50,19 @@ class TaskOutput extends React.Component {
renderResults(value) {
const interesting_results = []
+
+ // This was written to assume "value" is an array of
+ // key/value mappings to output. This seems to be a
+ // good assumption for the most part, but "package:" for
+ // whatever reason outputs a result that is just an array of
+ // strings with what packages were installed. So, if we
+ // see an array of strings as the value, we just swizzle
+ // that into a key/value so it displays usefully.
+ const isAllStrings = value.every(i => typeof i === 'string')
+ if (isAllStrings) {
+ value = [ {output: [...value]} ]
+ }
+
value.forEach((result, idx) => {
const keys = Object.entries(result).filter(
([key, value]) => shouldIncludeKey(
diff --git a/web/src/containers/freezejob/FreezeJobToolbar.jsx b/web/src/containers/freezejob/FreezeJobToolbar.jsx
new file mode 100644
index 000000000..c067b7205
--- /dev/null
+++ b/web/src/containers/freezejob/FreezeJobToolbar.jsx
@@ -0,0 +1,200 @@
+// Copyright 2020 BMW Group
+// Copyright 2022 Acme Gating, LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+import React, { useEffect, useState } from 'react'
+import { connect } from 'react-redux'
+import PropTypes from 'prop-types'
+import {
+ Button,
+ TextInput,
+ Dropdown,
+ DropdownItem,
+ DropdownPosition,
+ DropdownToggle,
+ Toolbar,
+ ToolbarContent,
+ ToolbarGroup,
+ ToolbarItem,
+ ToolbarToggleGroup,
+} from '@patternfly/react-core'
+
+import { fetchPipelinesIfNeeded } from '../../actions/pipelines'
+
+function FreezeJobToolbar(props) {
+ const { tenant, fetchPipelinesIfNeeded } = props
+
+ useEffect(() => {
+ fetchPipelinesIfNeeded(tenant)
+ }, [fetchPipelinesIfNeeded, tenant])
+
+ const tenantPipelines = props.pipelines.pipelines[tenant.name]
+ const pipelines = tenantPipelines ? tenantPipelines.map(p => { return p.name }) : []
+
+ const [isPipelineOpen, setIsPipelineOpen] = useState(false)
+ const [currentPipeline, setCurrentPipeline] = useState(props.defaultPipeline || '')
+ const [currentProject, setCurrentProject] = useState(props.defaultProject || '')
+ const [currentBranch, setCurrentBranch] = useState(props.defaultBranch || '')
+ const [currentJob, setCurrentJob] = useState(props.defaultJob || '')
+
+ if (!currentPipeline && pipelines.length) {
+ // We may have gotten a list of pipelines after we loaded the page
+ setCurrentPipeline(pipelines[0])
+ }
+
+ function handlePipelineSelect(event) {
+ setCurrentPipeline(event.target.innerText)
+ setIsPipelineOpen(false)
+ }
+
+ function handlePipelineToggle(isOpen) {
+ setIsPipelineOpen(isOpen)
+ }
+
+ function handleProjectChange(newValue) {
+ setCurrentProject(newValue)
+ }
+
+ function handleBranchChange(newValue) {
+ setCurrentBranch(newValue)
+ }
+
+ function handleJobChange(newValue) {
+ setCurrentJob(newValue)
+ }
+
+ function handleInputSend(event) {
+ // In case the event comes from a key press, only accept "Enter"
+ if (event.key && event.key !== 'Enter') {
+ return
+ }
+
+ // Ignore empty values
+ if (!currentBranch || !currentProject || !currentJob) {
+ return
+ }
+
+ // Notify the parent component about the filter change
+ props.onChange(currentPipeline, currentProject, currentBranch, currentJob)
+ }
+
+ function renderFreezeJobToolbar () {
+ return <>
+ <Toolbar collapseListedFiltersBreakpoint="md">
+ <ToolbarContent>
+ <ToolbarToggleGroup breakpoint="md">
+ <ToolbarGroup variant="filter-group">
+
+ <ToolbarItem key="pipeline">
+ <Dropdown
+ onSelect={handlePipelineSelect}
+ position={DropdownPosition.left}
+ toggle={
+ <DropdownToggle
+ onToggle={handlePipelineToggle}
+ style={{ width: '100%' }}
+ >
+ Pipeline: {currentPipeline}
+ </DropdownToggle>
+ }
+ isOpen={isPipelineOpen}
+ dropdownItems={pipelines.map((pipeline) => (
+ <DropdownItem key={pipeline}>{pipeline}</DropdownItem>
+ ))}
+ style={{ width: '100%' }}
+ menuAppendTo={document.body}
+ />
+ </ToolbarItem>
+
+ <ToolbarItem key="project">
+ <TextInput
+ name="project"
+ id="project-input"
+ type="search"
+ placeholder="Project"
+ defaultValue={props.defaultProject}
+ onChange={handleProjectChange}
+ onKeyDown={(event) => handleInputSend(event)}
+ />
+ </ToolbarItem>
+
+ <ToolbarItem key="branch">
+ <TextInput
+ name="branch"
+ id="branch-input"
+ type="search"
+ placeholder="Branch"
+ defaultValue={props.defaultBranch}
+ onChange={handleBranchChange}
+ onKeyDown={(event) => handleInputSend(event)}
+ />
+ </ToolbarItem>
+
+ <ToolbarItem key="job">
+ <TextInput
+ name="job"
+ id="job-input"
+ type="search"
+ placeholder="Job"
+ defaultValue={props.defaultJob}
+ onChange={handleJobChange}
+ onKeyDown={(event) => handleInputSend(event)}
+ />
+ </ToolbarItem>
+
+ <ToolbarItem key="button">
+ <Button
+ onClick={(event) => handleInputSend(event)}
+ >
+ Freeze Job
+ </Button>
+ </ToolbarItem>
+
+ </ToolbarGroup>
+ </ToolbarToggleGroup>
+ </ToolbarContent>
+ </Toolbar>
+ </>
+ }
+
+ return (
+ <div>
+ {renderFreezeJobToolbar()}
+ </div>
+ )
+}
+
+FreezeJobToolbar.propTypes = {
+ fetchPipelinesIfNeeded: PropTypes.func,
+ tenant: PropTypes.object,
+ pipelines: PropTypes.object,
+ onChange: PropTypes.func.isRequired,
+ defaultPipeline: PropTypes.string,
+ defaultProject: PropTypes.string,
+ defaultBranch: PropTypes.string,
+ defaultJob: PropTypes.string,
+}
+
+function mapStateToProps(state) {
+ return {
+ tenant: state.tenant,
+ pipelines: state.pipelines,
+ }
+}
+
+const mapDispatchToProps = {
+ fetchPipelinesIfNeeded
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(FreezeJobToolbar)
diff --git a/web/src/containers/jobgraph/JobGraph.jsx b/web/src/containers/jobgraph/JobGraph.jsx
new file mode 100644
index 000000000..75d77161d
--- /dev/null
+++ b/web/src/containers/jobgraph/JobGraph.jsx
@@ -0,0 +1,78 @@
+// Copyright 2022 Acme Gating, LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+import React, { useState } from 'react'
+import PropTypes from 'prop-types'
+import { connect } from 'react-redux'
+import { useHistory, useLocation } from 'react-router-dom'
+
+import JobGraphToolbar from './JobGraphToolbar'
+import JobGraphDisplay from './JobGraphDisplay'
+
+function JobGraph(props) {
+ const [currentPipeline, setCurrentPipeline] = useState()
+ const [currentBranch, setCurrentBranch] = useState()
+ const history = useHistory()
+ const location = useLocation()
+
+ if (!currentBranch) {
+ const urlParams = new URLSearchParams(location.search)
+ const branch = urlParams.get('branch')
+ const pipeline = urlParams.get('pipeline')
+ if (pipeline && branch) {
+ setCurrentPipeline(pipeline)
+ setCurrentBranch(branch)
+ }
+ }
+
+ function onChange(pipeline, branch) {
+ setCurrentPipeline(pipeline)
+ setCurrentBranch(branch)
+
+ const searchParams = new URLSearchParams('')
+ searchParams.append('branch', branch)
+ searchParams.append('pipeline', pipeline)
+ history.push({
+ pathname: location.pathname,
+ search: searchParams.toString(),
+ })
+ }
+
+ return (
+ <>
+ <JobGraphToolbar
+ project={props.project}
+ onChange={onChange}
+ defaultBranch={currentBranch}
+ defaultPipeline={currentPipeline}
+ />
+ {currentPipeline && currentBranch &&
+ <JobGraphDisplay
+ project={props.project}
+ pipeline={currentPipeline}
+ branch={currentBranch}
+ />}
+ </>
+ )
+}
+
+JobGraph.propTypes = {
+ project: PropTypes.object.isRequired,
+ tenant: PropTypes.object,
+ dispatch: PropTypes.func,
+}
+
+export default connect((state) => ({
+ tenant: state.tenant,
+}))(JobGraph)
diff --git a/web/src/containers/jobgraph/JobGraphDisplay.jsx b/web/src/containers/jobgraph/JobGraphDisplay.jsx
new file mode 100644
index 000000000..e5cff9cbc
--- /dev/null
+++ b/web/src/containers/jobgraph/JobGraphDisplay.jsx
@@ -0,0 +1,145 @@
+// Copyright 2022 Acme Gating, LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+import React, { useState, useEffect} from 'react'
+import PropTypes from 'prop-types'
+import { connect } from 'react-redux'
+import * as d3 from 'd3'
+import { useHistory } from 'react-router-dom'
+
+import { makeJobGraphKey, fetchJobGraphIfNeeded } from '../../actions/jobgraph'
+import { graphviz } from 'd3-graphviz'
+
+function makeDot(tenant, pipeline, project, branch, jobGraph) {
+ let ret = 'digraph job_graph {\n'
+ ret += ' rankdir=LR;\n'
+ ret += ' node [shape=box];\n'
+ jobGraph.forEach((job) => {
+ const searchParams = new URLSearchParams('')
+ searchParams.append('pipeline', pipeline)
+ searchParams.append('project', project.name)
+ searchParams.append('job', job.name)
+ searchParams.append('branch', branch)
+ // This will appear in the DOM as an "a href=" but we will
+ // manipulate the DOM later to add an onClick callback to make
+ // this an internal link.
+ const url = (tenant.linkPrefix +
+ '/freeze-job?' + searchParams.toString())
+ // Escape ampersands to get it through graphviz and d3; these will
+ // appear unescaped in the DOM.
+ const escaped_url = url.replace(/&/g, '&amp;')
+ ret += ' "' + job.name + '" [URL="' + escaped_url + '"];\n'
+ if (job.dependencies.length) {
+ job.dependencies.forEach((dep) => {
+ let soft = ' [dir=back]'
+ if (dep.soft) {
+ soft = ' [style=dashed dir=back]'
+ }
+ ret += ' "' + dep.name + '" -> "' + job.name + '"' + soft + ';\n'
+ })
+ }
+ })
+ ret += '}\n'
+ return ret
+}
+
+function GraphViz(props) {
+ const history = useHistory()
+
+ useEffect(() => {
+ const gv = graphviz('#graphviz')
+ .options({
+ fit: false,
+ zoom: true,
+ tweenPaths: false,
+ scale: 0.75,
+ }).renderDot(props.dot)
+
+ // Fix up the initial values of the internal transform data;
+ // without this the first time we pan the graph jumps.
+ const element = d3.select('.zuul-job-graph > svg')
+ const transform = element[0][0].firstElementChild.attributes.transform.value
+ const match = transform.match(/translate\(\d+,(\d+)\).*/)
+ if (match && match.length > 0) {
+ const val = parseInt(match[1])
+ gv._translation.y = val
+ gv._originalTransform.y = val
+ }
+
+ // Disable scroll wheel zooming because it interferes with window
+ // scrolling
+ gv.zoomSelection().on('wheel.zoom', null)
+
+ // Mutate the links to be internal links
+ d3.select('.zuul-job-graph').selectAll('.node a').on('click', event => {
+ d3.event.preventDefault()
+ history.push(event.attributes['xlink:href'])
+ })
+ }, [props.dot, history])
+
+ return (
+ <div className="zuul-job-graph" id="graphviz"/>
+ )
+}
+
+GraphViz.propTypes = {
+ dot: PropTypes.string.isRequired,
+}
+
+function JobGraphDisplay(props) {
+ const [dot, setDot] = useState()
+ const {fetchJobGraphIfNeeded, tenant, project, pipeline, branch} = props
+
+ useEffect(() => {
+ fetchJobGraphIfNeeded(tenant, project.name, pipeline, branch)
+ }, [fetchJobGraphIfNeeded, tenant, project, pipeline, branch])
+
+ const tenantJobGraph = props.jobgraph.jobGraphs[tenant.name]
+ const jobGraphKey = makeJobGraphKey(props.project.name,
+ props.pipeline,
+ props.branch)
+ const jobGraph = tenantJobGraph ? tenantJobGraph[jobGraphKey] : undefined
+ useEffect(() => {
+ if (jobGraph) {
+ setDot(makeDot(tenant, pipeline, project, branch, jobGraph))
+ }
+ }, [tenant, pipeline, project, branch, jobGraph])
+ return (
+ <>
+ {dot && <GraphViz dot={dot}/>}
+ </>
+ )
+}
+
+JobGraphDisplay.propTypes = {
+ fetchJobGraphIfNeeded: PropTypes.func,
+ tenant: PropTypes.object,
+ project: PropTypes.object.isRequired,
+ pipeline: PropTypes.string.isRequired,
+ branch: PropTypes.string.isRequired,
+ jobgraph: PropTypes.object,
+ dispatch: PropTypes.func,
+ state: PropTypes.object,
+}
+function mapStateToProps(state) {
+ return {
+ tenant: state.tenant,
+ jobgraph: state.jobgraph,
+ state: state,
+ }
+}
+
+const mapDispatchToProps = { fetchJobGraphIfNeeded }
+
+export default connect(mapStateToProps, mapDispatchToProps)(JobGraphDisplay)
diff --git a/web/src/containers/jobgraph/JobGraphToolbar.jsx b/web/src/containers/jobgraph/JobGraphToolbar.jsx
new file mode 100644
index 000000000..0e293d56c
--- /dev/null
+++ b/web/src/containers/jobgraph/JobGraphToolbar.jsx
@@ -0,0 +1,145 @@
+// Copyright 2020 BMW Group
+// Copyright 2022 Acme Gating, LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+import React, { useState } from 'react'
+import PropTypes from 'prop-types'
+import {
+ Button,
+ TextInput,
+ Dropdown,
+ DropdownItem,
+ DropdownPosition,
+ DropdownToggle,
+ Toolbar,
+ ToolbarContent,
+ ToolbarGroup,
+ ToolbarItem,
+ ToolbarToggleGroup,
+} from '@patternfly/react-core'
+
+
+function JobGraphToolbar(props) {
+ const pipelineSet = new Set()
+ props.project.configs.forEach((config) => {
+ config.pipelines.forEach((pipeline) => {
+ pipelineSet.add(pipeline.name)
+ })
+ })
+ const pipelines = Array.from(pipelineSet)
+
+ const [isPipelineOpen, setIsPipelineOpen] = useState(false)
+ const [currentPipeline, setCurrentPipeline] = useState(props.defaultPipeline || pipelines[0])
+ const [currentBranch, setCurrentBranch] = useState(props.defaultBranch || '')
+
+ function handlePipelineSelect(event) {
+ setCurrentPipeline(event.target.innerText)
+ setIsPipelineOpen(false)
+ }
+
+ function handlePipelineToggle(isOpen) {
+ setIsPipelineOpen(isOpen)
+ }
+
+ function handleBranchChange(newValue) {
+ setCurrentBranch(newValue)
+ }
+
+ function handleInputSend(event) {
+ // In case the event comes from a key press, only accept "Enter"
+ if (event.key && event.key !== 'Enter') {
+ return
+ }
+
+ // Ignore empty values
+ if (!currentBranch) {
+ return
+ }
+
+ // Clear the input field
+ setCurrentBranch('')
+ // Notify the parent component about the filter change
+ props.onChange(currentPipeline, currentBranch)
+ }
+
+ function renderJobGraphToolbar () {
+ return <>
+ <Toolbar collapseListedFiltersBreakpoint="md">
+ <ToolbarContent>
+ <ToolbarToggleGroup breakpoint="md">
+ <ToolbarGroup variant="filter-group">
+
+ <ToolbarItem key="pipeline">
+ <Dropdown
+ onSelect={handlePipelineSelect}
+ position={DropdownPosition.left}
+ toggle={
+ <DropdownToggle
+ onToggle={handlePipelineToggle}
+ style={{ width: '100%' }}
+ >
+ Pipeline: {currentPipeline}
+ </DropdownToggle>
+ }
+ isOpen={isPipelineOpen}
+ dropdownItems={pipelines.map((pipeline) => (
+ <DropdownItem key={pipeline}>{pipeline}</DropdownItem>
+ ))}
+ style={{ width: '100%' }}
+ menuAppendTo={document.body}
+ />
+ </ToolbarItem>
+
+ <ToolbarItem key="branch">
+ <TextInput
+ name="branch"
+ id="branch-input"
+ type="search"
+ placeholder="Branch"
+ defaultValue={props.defaultBranch}
+ onChange={handleBranchChange}
+ onKeyDown={(event) => handleInputSend(event)}
+ />
+ </ToolbarItem>
+
+ <ToolbarItem key="button">
+ <Button
+ onClick={(event) => handleInputSend(event)}
+ >
+ View Job Graph
+ </Button>
+ </ToolbarItem>
+
+ </ToolbarGroup>
+ </ToolbarToggleGroup>
+ </ToolbarContent>
+ </Toolbar>
+ </>
+ }
+
+ return (
+ <div>
+ {renderJobGraphToolbar()}
+ </div>
+ )
+}
+
+JobGraphToolbar.propTypes = {
+ project: PropTypes.object.isRequired,
+ onChange: PropTypes.func.isRequired,
+ defaultBranch: PropTypes.string,
+ defaultPipeline: PropTypes.string,
+}
+
+export default JobGraphToolbar
diff --git a/web/src/containers/jobs/Jobs.jsx b/web/src/containers/jobs/Jobs.jsx
index d7ab4bc69..71395f1d1 100644
--- a/web/src/containers/jobs/Jobs.jsx
+++ b/web/src/containers/jobs/Jobs.jsx
@@ -62,7 +62,7 @@ class JobsList extends React.Component {
const createNode = (job, extra) => ({
text: (
<React.Fragment>
- <Link to={linkPrefix + job.name}>{job.name}</Link>
+ <Link to={linkPrefix + encodeURIComponent(job.name)}>{job.name}</Link>
{extra && (<span> ({extra})</span>)}
{job.description && (
<span style={{marginLeft: '10px'}}>{job.description}</span>
diff --git a/web/src/containers/project/Project.jsx b/web/src/containers/project/Project.jsx
index c355b6af7..993d4d927 100644
--- a/web/src/containers/project/Project.jsx
+++ b/web/src/containers/project/Project.jsx
@@ -1,4 +1,5 @@
// Copyright 2018 Red Hat, Inc
+// Copyright 2022 Acme Gating, LLC
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
@@ -12,68 +13,44 @@
// License for the specific language governing permissions and limitations
// under the License.
-import * as React from 'react'
+import React, { useState } from 'react'
import PropTypes from 'prop-types'
import {
- Nav,
- NavItem,
- TabContainer,
- TabPane,
- TabContent,
-} from 'patternfly-react'
+ Tabs,
+ Tab,
+} from '@patternfly/react-core'
import ProjectVariant from './ProjectVariant'
-class Project extends React.Component {
- static propTypes = {
- project: PropTypes.object.isRequired,
- }
-
- state = {
- variantIdx: 0,
- }
+function Project(props) {
+ const [variantIdx, setVariantIdx] = useState(0)
+ const { project } = props
- renderVariantTitle (variant, selected) {
- let title = variant.default_branch
- if (selected) {
- title = <strong>{title}</strong>
- }
+ function renderVariantTitle (variant) {
+ let title = variant.source_context.project === project.name ?
+ variant.source_context.branch : variant.source_context.project
return title
}
- render () {
- const { project } = this.props
- const { variantIdx } = this.state
+ return (
+ <React.Fragment>
+ <Tabs activeKey={variantIdx}
+ onSelect={(event, tabIndex) => setVariantIdx(tabIndex)}
+ isBox>
+ {project.configs.map((variant, idx) => (
+ <Tab key={idx} eventKey={idx}
+ title={renderVariantTitle(variant)}>
+ <ProjectVariant variant={variant} />
+ </Tab>
+ ))}
+ </Tabs>
+ </React.Fragment>
+ )
+}
- return (
- <React.Fragment>
- <h2>{project.canonical_name}</h2>
- <TabContainer id="zuul-project">
- <div>
- <Nav bsClass="nav nav-tabs nav-tabs-pf">
- {project.configs.map((variant, idx) => (
- <NavItem
- key={idx}
- onClick={() => this.setState({variantIdx: idx})}>
- <div>
- {this.renderVariantTitle(variant, variantIdx === idx)}
- </div>
- </NavItem>
- ))}
- </Nav>
- <TabContent>
- <TabPane>
- {project.configs[variantIdx] && (
- <ProjectVariant variant={project.configs[variantIdx]} />
- )}
- </TabPane>
- </TabContent>
- </div>
- </TabContainer>
- </React.Fragment>
- )
- }
+Project.propTypes = {
+ project: PropTypes.object.isRequired,
}
export default Project
diff --git a/web/src/containers/project/ProjectVariant.jsx b/web/src/containers/project/ProjectVariant.jsx
index 74be10a55..51a093a32 100644
--- a/web/src/containers/project/ProjectVariant.jsx
+++ b/web/src/containers/project/ProjectVariant.jsx
@@ -18,64 +18,68 @@ import { connect } from 'react-redux'
import { Link } from 'react-router-dom'
-class ProjectVariant extends React.Component {
- static propTypes = {
- tenant: PropTypes.object,
- variant: PropTypes.object.isRequired
- }
+function ProjectVariant(props) {
+ const { tenant, variant } = props
+ const rows = []
- render () {
- const { tenant, variant } = this.props
- const rows = []
+ rows.push({label: 'Merge mode', value: variant.merge_mode})
- rows.push({label: 'Merge mode', value: variant.merge_mode})
+ if (variant.templates.length > 0) {
+ const templateList = (
+ <ul className='list-group'>
+ {variant.templates.map((item, idx) => (
+ <li className='list-group-item' key={idx}>{item}</li>))}
+ </ul>
+ )
+ rows.push({label: 'Templates', value: templateList})
+ }
- if (variant.templates.length > 0) {
- const templateList = (
+ variant.pipelines.forEach(pipeline => {
+ // TODO: either adds job link anchor to load the right variant
+ // and/or show the job variant config in a modal?
+ const jobList = (
+ <React.Fragment>
+ {pipeline.queue_name && (
+ <p><strong>Queue: </strong> {pipeline.queue_name} </p>)}
<ul className='list-group'>
- {variant.templates.map((item, idx) => (
- <li className='list-group-item' key={idx}>{item}</li>))}
+ {pipeline.jobs.map((item, idx) => (
+ <li className='list-group-item' key={idx}>
+ <Link to={tenant.linkPrefix + '/job/' + item[0].name}>
+ {item[0].name}
+ </Link>
+ </li>
+ ))}
</ul>
- )
- rows.push({label: 'Templates', value: templateList})
- }
+ </React.Fragment>
+ )
+ rows.push({label: pipeline.name + ' jobs', value: jobList})
+ })
- variant.pipelines.forEach(pipeline => {
- // TODO: either adds job link anchor to load the right variant
- // and/or show the job variant config in a modal?
- const jobList = (
- <React.Fragment>
- {pipeline.queue_name && (
- <p><strong>Queue: </strong> {pipeline.queue_name} </p>)}
- <ul className='list-group'>
- {pipeline.jobs.map((item, idx) => (
- <li className='list-group-item' key={idx}>
- <Link to={tenant.linkPrefix + '/job/' + item[0].name}>
- {item[0].name}
- </Link>
- </li>
- ))}
- </ul>
- </React.Fragment>
- )
- rows.push({label: pipeline.name + ' jobs', value: jobList})
- })
+ return (
+ <div>
+ <table className='table table-striped table-bordered'>
+ <tbody>
+ {rows.map(item => (
+ <tr key={item.label}>
+ <td style={{width: '10%'}}>{item.label}</td>
+ <td>{item.value}</td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ </div>
+ )
+}
- return (
- <div>
- <table className='table table-striped table-bordered'>
- <tbody>
- {rows.map(item => (
- <tr key={item.label}>
- <td style={{width: '10%'}}>{item.label}</td>
- <td>{item.value}</td>
- </tr>
- ))}
- </tbody>
- </table>
- </div>
- )
+ProjectVariant.propTypes = {
+ tenant: PropTypes.object,
+ variant: PropTypes.object.isRequired
+}
+
+function mapStateToProps(state) {
+ return {
+ tenant: state.tenant,
}
}
-export default connect(state => ({tenant: state.tenant}))(ProjectVariant)
+export default connect(mapStateToProps)(ProjectVariant)
diff --git a/web/src/index.css b/web/src/index.css
index eddbca673..e47cfc63c 100644
--- a/web/src/index.css
+++ b/web/src/index.css
@@ -3,6 +3,12 @@ body {
padding: 0;
}
+/* Make the H2 header inline-block so that the refresh icon/button can
+ share space with it floating on the right. */
+h2 {
+ display: inline-block;
+}
+
.pf-c-title {
padding-bottom: 10px;
}
@@ -443,3 +449,9 @@ details.foldable[open] summary::before {
);
}
}
+
+/* The box size calculation compared to the text size seems off, but
+ this looks better */
+.zuul-job-graph text {
+ font-size: 12px;
+}
diff --git a/web/src/pages/FreezeJob.jsx b/web/src/pages/FreezeJob.jsx
new file mode 100644
index 000000000..a78cc7b56
--- /dev/null
+++ b/web/src/pages/FreezeJob.jsx
@@ -0,0 +1,149 @@
+// Copyright 2022 Acme Gating, LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+import React, { useEffect, useState } from 'react'
+import PropTypes from 'prop-types'
+import { connect } from 'react-redux'
+import { useHistory, useLocation } from 'react-router-dom'
+import {
+ PageSection,
+ PageSectionVariants,
+ Text,
+ TextContent,
+} from '@patternfly/react-core'
+import ReactJson from 'react-json-view'
+
+import FreezeJobToolbar from '../containers/freezejob/FreezeJobToolbar'
+import { makeFreezeJobKey, fetchFreezeJobIfNeeded } from '../actions/freezejob'
+
+function FreezeJobPage(props) {
+ const { tenant, fetchFreezeJobIfNeeded } = props
+
+ const [currentPipeline, setCurrentPipeline] = useState()
+ const [currentProject, setCurrentProject] = useState()
+ const [currentBranch, setCurrentBranch] = useState()
+ const [currentJob, setCurrentJob] = useState()
+ const history = useHistory()
+ const location = useLocation()
+
+ if (!currentBranch) {
+ const urlParams = new URLSearchParams(location.search)
+ const pipeline = urlParams.get('pipeline')
+ const project = urlParams.get('project')
+ const branch = urlParams.get('branch')
+ const job = urlParams.get('job')
+ if (pipeline && branch && project && job) {
+ setCurrentPipeline(pipeline)
+ setCurrentProject(project)
+ setCurrentBranch(branch)
+ setCurrentJob(job)
+ }
+ }
+
+ useEffect(() => {
+ document.title = 'Zuul Frozen Job'
+ if (currentPipeline && currentProject && currentBranch && currentJob) {
+ fetchFreezeJobIfNeeded(tenant, currentPipeline, currentProject,
+ currentBranch, currentJob)
+ }
+ }, [fetchFreezeJobIfNeeded, tenant, currentPipeline, currentProject,
+ currentBranch, currentJob])
+
+ function onChange(pipeline, project, branch, job) {
+ setCurrentPipeline(pipeline)
+ setCurrentProject(project)
+ setCurrentBranch(branch)
+ setCurrentJob(job)
+
+ const searchParams = new URLSearchParams('')
+ searchParams.append('pipeline', pipeline)
+ searchParams.append('project', project)
+ searchParams.append('branch', branch)
+ searchParams.append('job', job)
+ history.push({
+ pathname: location.pathname,
+ search: searchParams.toString(),
+ })
+
+ if (currentPipeline && currentProject && currentBranch && currentJob) {
+ fetchFreezeJobIfNeeded(tenant, currentPipeline, currentProject,
+ currentBranch, currentJob)
+ }
+ }
+
+ const tenantJobs = props.freezejob.freezeJobs[tenant.name]
+ const freezeJobKey = makeFreezeJobKey(currentPipeline,
+ currentProject,
+ currentBranch,
+ currentJob)
+ const job = tenantJobs ? tenantJobs[freezeJobKey] : undefined
+ function renderFrozenJob() {
+ return (
+ <span style={{whiteSpace: 'pre-wrap'}}>
+ <ReactJson
+ src={job}
+ name={null}
+ collapsed={false}
+ sortKeys={true}
+ enableClipboard={false}
+ displayDataTypes={false}/>
+ </span>
+ )
+ }
+
+ return (
+ <>
+ <PageSection variant={PageSectionVariants.light}>
+ <TextContent>
+ <Text component="h1">Freeze Job</Text>
+ <Text component="p">
+ Freezing a job asks Zuul to combine all the
+ <i>project</i> and <i>job</i> configuration
+ stanzas for a job as if a change for a given
+ project and branch were to be enqueued into
+ a specific pipeline. The resulting job
+ configuration is displayed below.
+ </Text>
+ </TextContent>
+ <FreezeJobToolbar
+ onChange={onChange}
+ defaultPipeline={currentPipeline}
+ defaultProject={currentProject}
+ defaultBranch={currentBranch}
+ defaultJob={currentJob}
+ />
+ {job && renderFrozenJob(job)}
+ </PageSection>
+ </>
+ )
+}
+
+FreezeJobPage.propTypes = {
+ fetchFreezeJobIfNeeded: PropTypes.func,
+ tenant: PropTypes.object,
+ freezejob: PropTypes.object,
+}
+
+function mapStateToProps(state) {
+ return {
+ tenant: state.tenant,
+ freezejob: state.freezejob,
+ }
+}
+
+const mapDispatchToProps = {
+ fetchFreezeJobIfNeeded
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(FreezeJobPage)
diff --git a/web/src/pages/Project.jsx b/web/src/pages/Project.jsx
index 8f76e9ff1..06e8612c7 100644
--- a/web/src/pages/Project.jsx
+++ b/web/src/pages/Project.jsx
@@ -1,4 +1,5 @@
// Copyright 2018 Red Hat, Inc
+// Copyright 2022 Acme Gating, LLC
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
@@ -12,62 +13,74 @@
// License for the specific language governing permissions and limitations
// under the License.
-import * as React from 'react'
+import React, { useEffect, useCallback } from 'react'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
-import { PageSection, PageSectionVariants } from '@patternfly/react-core'
-
+import {
+ PageSection,
+ PageSectionVariants,
+ Text,
+ TextContent,
+} from '@patternfly/react-core'
import Project from '../containers/project/Project'
+import JobGraph from '../containers/jobgraph/JobGraph'
import { fetchProjectIfNeeded } from '../actions/project'
import { Fetchable } from '../containers/Fetching'
-class ProjectPage extends React.Component {
- static propTypes = {
- match: PropTypes.object.isRequired,
- tenant: PropTypes.object,
- remoteData: PropTypes.object,
- dispatch: PropTypes.func
- }
-
- updateData = (force) => {
- this.props.dispatch(fetchProjectIfNeeded(
- this.props.tenant, this.props.match.params.projectName, force))
- }
+function ProjectPage(props) {
+ const { tenant, fetchProjectIfNeeded, remoteData } = props
+ const { projectName } = props.match.params
+ const tenantProjects = remoteData.projects[tenant.name]
- componentDidMount () {
- document.title = 'Zuul Project | ' + this.props.match.params.projectName
- if (this.props.tenant.name) {
- this.updateData()
+ const updateData = useCallback((force) => {
+ if (tenant.name) {
+ fetchProjectIfNeeded(tenant, projectName, force)
}
- }
+ }, [tenant, projectName, fetchProjectIfNeeded])
- componentDidUpdate (prevProps) {
- if (this.props.tenant.name !== prevProps.tenant.name) {
- this.updateData()
- }
- }
+ useEffect(() => {
+ document.title = 'Zuul Project | ' + projectName
+ updateData()
+ }, [tenant, projectName, updateData])
- render () {
- const { remoteData } = this.props
- const tenantProjects = remoteData.projects[this.props.tenant.name]
- const projectName = this.props.match.params.projectName
- return (
+ return (
+ <>
<PageSection variant={PageSectionVariants.light}>
- <PageSection style={{paddingRight: '5px'}}>
- <Fetchable
- isFetching={remoteData.isFetching}
- fetchCallback={this.updateData}
- />
- </PageSection>
+ <TextContent>
+ <Text component="h2">Project {projectName}</Text>
+ <Fetchable
+ isFetching={remoteData.isFetching}
+ fetchCallback={updateData}
+ />
+ </TextContent>
{tenantProjects && tenantProjects[projectName] &&
- <Project project={tenantProjects[projectName]} />}
- </PageSection>
- )
+ <>
+ <Project project={tenantProjects[projectName]} />
+ <JobGraph project={tenantProjects[projectName]} />
+ </>
+ }
+ </PageSection>
+ </>
+ )
+}
+
+ProjectPage.propTypes = {
+ match: PropTypes.object.isRequired,
+ tenant: PropTypes.object,
+ remoteData: PropTypes.object,
+ fetchProjectIfNeeded: PropTypes.func,
+}
+
+function mapStateToProps(state) {
+ return {
+ tenant: state.tenant,
+ remoteData: state.project,
}
}
-export default connect(state => ({
- tenant: state.tenant,
- remoteData: state.project,
-}))(ProjectPage)
+const mapDispatchToProps = {
+ fetchProjectIfNeeded,
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(ProjectPage)
diff --git a/web/src/reducers/freezejob.js b/web/src/reducers/freezejob.js
new file mode 100644
index 000000000..6e7db4991
--- /dev/null
+++ b/web/src/reducers/freezejob.js
@@ -0,0 +1,55 @@
+// Copyright 2018 Red Hat, Inc
+// Copyright 2022 Acme Gating, LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+import {
+ FREEZE_JOB_FETCH_FAIL,
+ FREEZE_JOB_FETCH_REQUEST,
+ FREEZE_JOB_FETCH_SUCCESS
+} from '../actions/freezejob'
+
+export default (state = {
+ isFetching: false,
+ freezeJobs: {},
+}, action) => {
+ let stateFreezeJobs
+ switch (action.type) {
+ case FREEZE_JOB_FETCH_REQUEST:
+ return {
+ isFetching: true,
+ freezeJobs: state.freezeJobs,
+ }
+ case FREEZE_JOB_FETCH_SUCCESS:
+ stateFreezeJobs = !state.freezeJobs[action.tenant] ?
+ { ...state.freezeJobs, [action.tenant]: {} } :
+ { ...state.freezeJobs }
+ return {
+ isFetching: false,
+ freezeJobs: {
+ ...stateFreezeJobs,
+ [action.tenant]: {
+ ...stateFreezeJobs[action.tenant],
+ [action.freezeJobKey]: action.freezeJob
+ }
+ }
+ }
+ case FREEZE_JOB_FETCH_FAIL:
+ return {
+ isFetching: false,
+ freezeJobs: state.freezeJobs,
+ }
+ default:
+ return state
+ }
+}
diff --git a/web/src/reducers/index.js b/web/src/reducers/index.js
index 325e4f3dd..83c86c105 100644
--- a/web/src/reducers/index.js
+++ b/web/src/reducers/index.js
@@ -19,16 +19,19 @@ import autoholds from './autoholds'
import configErrors from './configErrors'
import change from './change'
import component from './component'
+import freezejob from './freezejob'
import notifications from './notifications'
import build from './build'
import info from './info'
import job from './job'
+import jobgraph from './jobgraph'
import jobs from './jobs'
import labels from './labels'
import logfile from './logfile'
import nodes from './nodes'
import openapi from './openapi'
import project from './project'
+import pipelines from './pipelines'
import projects from './projects'
import preferences from './preferences'
import status from './status'
@@ -44,14 +47,17 @@ const reducers = {
change,
component,
configErrors,
+ freezejob,
notifications,
info,
job,
+ jobgraph,
jobs,
labels,
logfile,
nodes,
openapi,
+ pipelines,
project,
projects,
status,
diff --git a/web/src/reducers/jobgraph.js b/web/src/reducers/jobgraph.js
new file mode 100644
index 000000000..0a015fb45
--- /dev/null
+++ b/web/src/reducers/jobgraph.js
@@ -0,0 +1,55 @@
+// Copyright 2018 Red Hat, Inc
+// Copyright 2022 Acme Gating, LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+import {
+ JOB_GRAPH_FETCH_FAIL,
+ JOB_GRAPH_FETCH_REQUEST,
+ JOB_GRAPH_FETCH_SUCCESS
+} from '../actions/jobgraph'
+
+export default (state = {
+ isFetching: false,
+ jobGraphs: {},
+}, action) => {
+ let stateJobGraphs
+ switch (action.type) {
+ case JOB_GRAPH_FETCH_REQUEST:
+ return {
+ isFetching: true,
+ jobGraphs: state.jobGraphs,
+ }
+ case JOB_GRAPH_FETCH_SUCCESS:
+ stateJobGraphs = !state.jobGraphs[action.tenant] ?
+ { ...state.jobGraphs, [action.tenant]: {} } :
+ { ...state.jobGraphs }
+ return {
+ isFetching: false,
+ jobGraphs: {
+ ...stateJobGraphs,
+ [action.tenant]: {
+ ...stateJobGraphs[action.tenant],
+ [action.jobGraphKey]: action.jobGraph
+ }
+ }
+ }
+ case JOB_GRAPH_FETCH_FAIL:
+ return {
+ isFetching: false,
+ jobGraphs: state.jobGraphs,
+ }
+ default:
+ return state
+ }
+}
diff --git a/web/src/reducers/pipelines.js b/web/src/reducers/pipelines.js
new file mode 100644
index 000000000..550352572
--- /dev/null
+++ b/web/src/reducers/pipelines.js
@@ -0,0 +1,45 @@
+// Copyright 2018 Red Hat, Inc
+// Copyright 2022 Acme Gating, LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+import {
+ PIPELINES_FETCH_FAIL,
+ PIPELINES_FETCH_REQUEST,
+ PIPELINES_FETCH_SUCCESS
+} from '../actions/pipelines'
+
+export default (state = {
+ isFetching: false,
+ pipelines: {},
+}, action) => {
+ switch (action.type) {
+ case PIPELINES_FETCH_REQUEST:
+ return {
+ isFetching: true,
+ pipelines: state.pipelines,
+ }
+ case PIPELINES_FETCH_SUCCESS:
+ return {
+ isFetching: false,
+ pipelines: { ...state.pipelines, [action.tenant]: action.pipelines },
+ }
+ case PIPELINES_FETCH_FAIL:
+ return {
+ isFetching: false,
+ pipelines: state.pipelines,
+ }
+ default:
+ return state
+ }
+}
diff --git a/web/src/routes.js b/web/src/routes.js
index c2c3a8584..e1bfdae16 100644
--- a/web/src/routes.js
+++ b/web/src/routes.js
@@ -13,6 +13,7 @@
// under the License.
import ComponentsPage from './pages/Components'
+import FreezeJobPage from './pages/FreezeJob'
import StatusPage from './pages/Status'
import ChangeStatusPage from './pages/ChangeStatus'
import ProjectPage from './pages/Project'
@@ -79,6 +80,10 @@ const routes = () => [
component: BuildsetsPage
},
{
+ to: '/freeze-job',
+ component: FreezeJobPage
+ },
+ {
to: '/status/change/:changeId',
component: ChangeStatusPage
},
diff --git a/web/yarn.lock b/web/yarn.lock
index ff3dd3bf8..fc1675a0a 100644
--- a/web/yarn.lock
+++ b/web/yarn.lock
@@ -4711,24 +4711,52 @@ d3-color@1:
resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-1.4.1.tgz#c52002bf8846ada4424d55d97982fef26eb3bc8a"
integrity sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q==
-d3-ease@^1.0.0:
+d3-dispatch@1, d3-dispatch@^1.0.3:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-1.0.6.tgz#00d37bcee4dd8cd97729dd893a0ac29caaba5d58"
+ integrity sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA==
+
+d3-drag@1:
+ version "1.2.5"
+ resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-1.2.5.tgz#2537f451acd39d31406677b7dc77c82f7d988f70"
+ integrity sha512-rD1ohlkKQwMZYkQlYVCrSFxsWPzI97+W+PaEIBNTMxRuxz9RF0Hi5nJWHGVJ3Om9d2fRTe1yOBINJyy/ahV95w==
+ dependencies:
+ d3-dispatch "1"
+ d3-selection "1"
+
+d3-ease@1, d3-ease@^1.0.0:
version "1.0.7"
resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-1.0.7.tgz#9a834890ef8b8ae8c558b2fe55bd57f5993b85e2"
integrity sha512-lx14ZPYkhNx0s/2HX5sLFUI3mbasHjSSpwO/KaaNACweVwxUruKyWVcb293wMv1RqTPZyZ8kSZ2NogUZNcLOFQ==
-d3-format@1:
+d3-format@1, d3-format@^1.2.0:
version "1.4.5"
resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.4.5.tgz#374f2ba1320e3717eb74a9356c67daee17a7edb4"
integrity sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==
-d3-interpolate@1, d3-interpolate@^1.1.1:
+d3-graphviz@2.6.1:
+ version "2.6.1"
+ resolved "https://registry.yarnpkg.com/d3-graphviz/-/d3-graphviz-2.6.1.tgz#61b93fe330e6339198fd2090f8080d7d4282c514"
+ integrity sha512-878AFSagQyr5tTOrM7YiVYeUC2/NoFcOB3/oew+LAML0xekyJSw9j3WOCUMBsc95KYe9XBYZ+SKKuObVya1tJQ==
+ dependencies:
+ d3-dispatch "^1.0.3"
+ d3-format "^1.2.0"
+ d3-interpolate "^1.1.5"
+ d3-path "^1.0.5"
+ d3-selection "^1.1.0"
+ d3-timer "^1.0.6"
+ d3-transition "^1.1.1"
+ d3-zoom "^1.5.0"
+ viz.js "^1.8.2"
+
+d3-interpolate@1, d3-interpolate@^1.1.1, d3-interpolate@^1.1.5:
version "1.4.0"
resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-1.4.0.tgz#526e79e2d80daa383f9e0c1c1c7dcc0f0583e987"
integrity sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA==
dependencies:
d3-color "1"
-d3-path@1:
+d3-path@1, d3-path@^1.0.5:
version "1.0.9"
resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-1.0.9.tgz#48c050bb1fe8c262493a8caf5524e3e9591701cf"
integrity sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==
@@ -4746,6 +4774,11 @@ d3-scale@^1.0.0:
d3-time "1"
d3-time-format "2"
+d3-selection@1, d3-selection@^1.1.0:
+ version "1.4.2"
+ resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-1.4.2.tgz#dcaa49522c0dbf32d6c1858afc26b6094555bc5c"
+ integrity sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg==
+
d3-shape@^1.0.0, d3-shape@^1.2.0:
version "1.3.7"
resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.3.7.tgz#df63801be07bc986bc54f63789b4fe502992b5d7"
@@ -4765,11 +4798,34 @@ d3-time@1:
resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-1.1.0.tgz#b1e19d307dae9c900b7e5b25ffc5dcc249a8a0f1"
integrity sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==
-d3-timer@^1.0.0:
+d3-timer@1, d3-timer@^1.0.0, d3-timer@^1.0.6:
version "1.0.10"
resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-1.0.10.tgz#dfe76b8a91748831b13b6d9c793ffbd508dd9de5"
integrity sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw==
+d3-transition@1, d3-transition@^1.1.1:
+ version "1.3.2"
+ resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-1.3.2.tgz#a98ef2151be8d8600543434c1ca80140ae23b398"
+ integrity sha512-sc0gRU4PFqZ47lPVHloMn9tlPcv8jxgOQg+0zjhfZXMQuvppjG6YuwdMBE0TuqCZjeJkLecku/l9R0JPcRhaDA==
+ dependencies:
+ d3-color "1"
+ d3-dispatch "1"
+ d3-ease "1"
+ d3-interpolate "1"
+ d3-selection "^1.1.0"
+ d3-timer "1"
+
+d3-zoom@^1.5.0:
+ version "1.8.3"
+ resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-1.8.3.tgz#b6a3dbe738c7763121cd05b8a7795ffe17f4fc0a"
+ integrity sha512-VoLXTK4wvy1a0JpH2Il+F2CiOhVu7VRXWF5M/LroMIh3/zBAC3WAt7QoIvPibOavVo20hN6/37vwAsdBejLyKQ==
+ dependencies:
+ d3-dispatch "1"
+ d3-drag "1"
+ d3-interpolate "1"
+ d3-selection "1"
+ d3-transition "1"
+
d3@~3.5.0, d3@~3.5.17:
version "3.5.17"
resolved "https://registry.yarnpkg.com/d3/-/d3-3.5.17.tgz#bc46748004378b21a360c9fc7cf5231790762fb8"
@@ -15208,6 +15264,11 @@ victory-zoom-container@^36.2.1, victory-zoom-container@^36.3.0:
prop-types "^15.5.8"
victory-core "^36.3.0"
+viz.js@^1.8.2:
+ version "1.8.2"
+ resolved "https://registry.yarnpkg.com/viz.js/-/viz.js-1.8.2.tgz#d9cc04cd99f98ec986bf9054db76a6cbcdc5d97a"
+ integrity sha512-W+1+N/hdzLpQZEcvz79n2IgUE9pfx6JLdHh3Kh8RGvLL8P1LdJVQmi2OsDcLdY4QVID4OUy+FPelyerX0nJxIQ==
+
vm-browserify@^1.0.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
diff --git a/zuul/ansible/2.8/library/command.py b/zuul/ansible/2.8/library/command.py
index 26830e4a7..5f7f31947 100755
--- a/zuul/ansible/2.8/library/command.py
+++ b/zuul/ansible/2.8/library/command.py
@@ -168,7 +168,15 @@ _log_lines = []
class Console(object):
def __init__(self, log_uuid):
- self.logfile_name = LOG_STREAM_FILE.format(log_uuid=log_uuid)
+ # The streamer currently will not ask us for output from
+ # loops. This flag uuid was set in the action plugin if this
+ # call was part of a loop. This avoids us leaving behind
+ # files that will never be read, but also means no other
+ # special-casing for any of this path.
+ if log_uuid == 'in-loop-ignore':
+ self.logfile_name = os.devnull
+ else:
+ self.logfile_name = LOG_STREAM_FILE.format(log_uuid=log_uuid)
def __enter__(self):
self.logfile = open(self.logfile_name, 'ab', buffering=0)
diff --git a/zuul/ansible/2.9/library/command.py b/zuul/ansible/2.9/library/command.py
index 26830e4a7..5f7f31947 100755
--- a/zuul/ansible/2.9/library/command.py
+++ b/zuul/ansible/2.9/library/command.py
@@ -168,7 +168,15 @@ _log_lines = []
class Console(object):
def __init__(self, log_uuid):
- self.logfile_name = LOG_STREAM_FILE.format(log_uuid=log_uuid)
+ # The streamer currently will not ask us for output from
+ # loops. This flag uuid was set in the action plugin if this
+ # call was part of a loop. This avoids us leaving behind
+ # files that will never be read, but also means no other
+ # special-casing for any of this path.
+ if log_uuid == 'in-loop-ignore':
+ self.logfile_name = os.devnull
+ else:
+ self.logfile_name = LOG_STREAM_FILE.format(log_uuid=log_uuid)
def __enter__(self):
self.logfile = open(self.logfile_name, 'ab', buffering=0)
diff --git a/zuul/ansible/base/action/command.py b/zuul/ansible/base/action/command.py
index f36da86f6..3bb88d8e5 100644
--- a/zuul/ansible/base/action/command.py
+++ b/zuul/ansible/base/action/command.py
@@ -25,15 +25,21 @@ class ActionModule(command.ActionModule):
if self._task.action in (
'command', 'shell',
'ansible.builtin.command', 'ansible.builtin.shell'):
- # Get a unique key for ZUUL_LOG_ID_MAP. ZUUL_LOG_ID_MAP
- # is read-only since we are forked. Use it to add a
- # counter to the log id so that if we run the same task
- # more than once, we get a unique log file. See comments
- # in paths.py for details.
- log_host = paths._sanitize_filename(
- task_vars.get('inventory_hostname'))
- key = "%s-%s" % (self._task._uuid, log_host)
- count = paths.ZUUL_LOG_ID_MAP.get(key, 0)
- self._task.args['zuul_log_id'] = "%s-%s-%s" % (
- self._task._uuid, count, log_host)
+ # This is a bit lame, but we do not log loops in the
+ # zuul_stream.py callback. This allows us to not write
+ # out command.py output to files that will never be read.
+ if 'ansible_loop_var' in task_vars:
+ self._task.args['zuul_log_id'] = 'in-loop-ignore'
+ else:
+ # Get a unique key for ZUUL_LOG_ID_MAP. ZUUL_LOG_ID_MAP
+ # is read-only since we are forked. Use it to add a
+ # counter to the log id so that if we run the same task
+ # more than once, we get a unique log file. See comments
+ # in paths.py for details.
+ log_host = paths._sanitize_filename(
+ task_vars.get('inventory_hostname'))
+ key = "%s-%s" % (self._task._uuid, log_host)
+ count = paths.ZUUL_LOG_ID_MAP.get(key, 0)
+ self._task.args['zuul_log_id'] = "%s-%s-%s" % (
+ self._task._uuid, count, log_host)
return super(ActionModule, self).run(tmp, task_vars)
diff --git a/zuul/ansible/base/callback/zuul_stream.py b/zuul/ansible/base/callback/zuul_stream.py
index 720261cb2..f31983ed6 100644
--- a/zuul/ansible/base/callback/zuul_stream.py
+++ b/zuul/ansible/base/callback/zuul_stream.py
@@ -48,6 +48,7 @@ from zuul.ansible import paths
from zuul.ansible import logconfig
LOG_STREAM_PORT = int(os.environ.get("ZUUL_CONSOLE_PORT", 19885))
+LOG_STREAM_VERSION = 0
def zuul_filter_result(result):
@@ -103,6 +104,7 @@ class CallbackModule(default.CallbackModule):
self._items_done = False
self._deferred_result = None
self._playbook_name = None
+ self._zuul_console_version = 0
def configure_logger(self):
# ansible appends timestamp, user and pid to the log lines emitted
@@ -129,9 +131,7 @@ class CallbackModule(default.CallbackModule):
else:
self._display.display(msg)
- def _read_log(self, host, ip, port, log_id, task_name, hosts):
- self._log("[%s] Starting to log %s for task %s"
- % (host, log_id, task_name), job=False, executor=True)
+ def _read_log_connect(self, host, ip, port):
logger_retries = 0
while True:
try:
@@ -141,6 +141,7 @@ class CallbackModule(default.CallbackModule):
# logs continously. Without this we can easily trip the 5
# second timeout.
s.settimeout(None)
+ return s
except socket.timeout:
self._log(
"Timeout exception waiting for the logger. "
@@ -151,7 +152,7 @@ class CallbackModule(default.CallbackModule):
"Timeout exception waiting for the logger. "
"Please check connectivity to [%s:%s]"
% (ip, port))
- return
+ return None
except Exception:
if logger_retries % 10 == 0:
self._log("[%s] Waiting on logger" % host,
@@ -159,31 +160,77 @@ class CallbackModule(default.CallbackModule):
logger_retries += 1
time.sleep(0.1)
continue
- msg = "%s\n" % log_id
- s.send(msg.encode("utf-8"))
- buff = s.recv(4096)
- buffering = True
- while buffering:
- if b'\n' in buff:
- (line, buff) = buff.split(b'\n', 1)
- # We can potentially get binary data here. In order to
- # being able to handle that use the backslashreplace
- # error handling method. This decodes unknown utf-8
- # code points to escape sequences which exactly represent
- # the correct data without throwing a decoding exception.
- done = self._log_streamline(
- host, line.decode("utf-8", "backslashreplace"))
- if done:
- return
+
+ def _read_log(self, host, ip, port, log_id, task_name, hosts):
+ self._log("[%s] Starting to log %s for task %s"
+ % (host, log_id, task_name), job=False, executor=True)
+
+ s = self._read_log_connect(host, ip, port)
+ if s is None:
+ # Can't connect; _read_log_connect() already logged an
+ # error for us, just bail
+ return
+
+ # Find out what version we are running against
+ s.send(f'v:{LOG_STREAM_VERSION}\n'.encode('utf-8'))
+ buff = s.recv(1024).decode('utf-8').strip()
+
+ # NOTE(ianw) 2022-07-21 : zuul_console from < 6.3.0 do not
+ # understand this protocol. They will assume the send
+ # above is a log request and send back the not found
+ # message in a loop. So to handle this we disconnect and
+ # reconnect. When we decide to remove this, we can remove
+ # anything in the "version 0" path.
+ if buff == '[Zuul] Log not found':
+ s.close()
+ s = self._read_log_connect(host, ip, port)
+ if s is None:
+ return
+ else:
+ self._zuul_console_version = int(buff)
+ self._log('[%s] Reports streaming version: %d' %
+ (host, self._zuul_console_version),
+ job=False, executor=True)
+
+ if self._zuul_console_version >= 1:
+ msg = 's:%s\n' % log_id
+ else:
+ msg = '%s\n' % log_id
+
+ s.send(msg.encode("utf-8"))
+ buff = s.recv(4096)
+ buffering = True
+ while buffering:
+ if b'\n' in buff:
+ (line, buff) = buff.split(b'\n', 1)
+ # We can potentially get binary data here. In order to
+ # being able to handle that use the backslashreplace
+ # error handling method. This decodes unknown utf-8
+ # code points to escape sequences which exactly represent
+ # the correct data without throwing a decoding exception.
+ done = self._log_streamline(
+ host, line.decode("utf-8", "backslashreplace"))
+ if done:
+ if self._zuul_console_version > 0:
+ try:
+ # reestablish connection and tell console to
+ # clean up
+ s = self._read_log_connect(host, ip, port)
+ s.send(f'f:{log_id}\n'.encode('utf-8'))
+ s.close()
+ except Exception:
+ # Don't worry if this fails
+ pass
+ return
+ else:
+ more = s.recv(4096)
+ if not more:
+ buffering = False
else:
- more = s.recv(4096)
- if not more:
- buffering = False
- else:
- buff += more
- if buff:
- self._log_streamline(
- host, buff.decode("utf-8", "backslashreplace"))
+ buff += more
+ if buff:
+ self._log_streamline(
+ host, buff.decode("utf-8", "backslashreplace"))
def _log_streamline(self, host, line):
if "[Zuul] Task exit code" in line:
diff --git a/zuul/ansible/base/library/command.py b/zuul/ansible/base/library/command.py
index d496b037c..3c22849ae 100755
--- a/zuul/ansible/base/library/command.py
+++ b/zuul/ansible/base/library/command.py
@@ -263,7 +263,15 @@ _log_lines = []
class Console(object):
def __init__(self, log_uuid):
- self.logfile_name = LOG_STREAM_FILE.format(log_uuid=log_uuid)
+ # The streamer currently will not ask us for output from
+ # loops. This flag uuid was set in the action plugin if this
+ # call was part of a loop. This avoids us leaving behind
+ # files that will never be read, but also means no other
+ # special-casing for any of this path.
+ if log_uuid == 'in-loop-ignore':
+ self.logfile_name = os.devnull
+ else:
+ self.logfile_name = LOG_STREAM_FILE.format(log_uuid=log_uuid)
def __enter__(self):
self.logfile = open(self.logfile_name, 'ab', buffering=0)
diff --git a/zuul/ansible/base/library/zuul_console.py b/zuul/ansible/base/library/zuul_console.py
index 9dffbbc3a..aa999cac1 100755
--- a/zuul/ansible/base/library/zuul_console.py
+++ b/zuul/ansible/base/library/zuul_console.py
@@ -24,6 +24,14 @@ import subprocess
import threading
import time
+# This is the version we report to the zuul_stream callback. It is
+# expected that this (zuul_console) process can be long-lived, so if
+# there are updates this ensures a later streaming callback can still
+# talk to us.
+ZUUL_CONSOLE_PROTO_VERSION = 1
+# This is the template for the file name of the log-file written out
+# by the command.py override command in the executor's Ansible
+# install.
LOG_STREAM_FILE = '/tmp/console-{log_uuid}.log'
LOG_STREAM_PORT = 19885
@@ -162,15 +170,49 @@ class Server(object):
ret = buffer.decode('utf-8')
x = ret.find('\n')
if x > 0:
- return ret[:x]
+ return ret[:x].strip()
except UnicodeDecodeError:
pass
- def handleOneConnection(self, conn):
- log_uuid = self.get_command(conn)
+ def _clean_uuid(self, log_uuid):
# use path split to make use the input isn't trying to be clever
# and construct some path like /tmp/console-/../../something
- log_uuid = os.path.split(log_uuid.rstrip())[-1]
+ return os.path.split(log_uuid)[-1]
+
+ def handleOneConnection(self, conn):
+ # V1 protocol
+ # -----------
+ # v:<ver> get version number, <ver> is remote version
+ # s:<uuid> send logs for <uuid>
+ # f:<uuid> finalise/cleanup <uuid>
+ while True:
+ command = self.get_command(conn)
+ if command.startswith('v:'):
+ # NOTE(ianw) : remote sends its version. We currently
+ # don't have anything to do with this value, so ignore
+ # for now.
+ cmd = '%s\n' % (ZUUL_CONSOLE_PROTO_VERSION)
+ conn.send(cmd.encode('utf-8'))
+ continue
+ elif command.startswith('f:'):
+ log_uuid = self._clean_uuid(command[2:])
+ try:
+ os.unlink(self.path.format(log_uuid=log_uuid))
+ except Exception:
+ # something might have cleaned /tmp
+ pass
+ continue
+ elif command.startswith('s:'):
+ log_uuid = self._clean_uuid(command[2:])
+ break
+ else:
+ # NOTE(ianw): 2022-07-21 In releases < 6.3.0 the streaming
+ # side would just send a raw uuid and nothing else; so by
+ # default assume that is what is coming in here. We can
+ # remove this fallback when we decide it is no longer
+ # necessary.
+ log_uuid = self._clean_uuid(command)
+ break
# FIXME: this won't notice disconnects until it tries to send
console = None
diff --git a/zuul/driver/gerrit/gerritconnection.py b/zuul/driver/gerrit/gerritconnection.py
index 1ec334915..2213293f8 100644
--- a/zuul/driver/gerrit/gerritconnection.py
+++ b/zuul/driver/gerrit/gerritconnection.py
@@ -103,10 +103,12 @@ class GerritChangeData(object):
SSH = 1
HTTP = 2
- def __init__(self, fmt, data, related=None, files=None):
+ def __init__(self, fmt, data, related=None, files=None,
+ zuul_query_ltime=None):
self.format = fmt
self.data = data
self.files = files
+ self.zuul_query_ltime = zuul_query_ltime
if fmt == self.SSH:
self.parseSSH(data)
@@ -329,19 +331,20 @@ class GerritEventConnector(threading.Thread):
self.connection.clearConnectionCacheOnBranchEvent(event)
- self._getChange(event)
+ self._getChange(event, connection_event.zuul_event_ltime)
self.connection.logEvent(event)
self.connection.sched.addTriggerEvent(
self.connection.driver_name, event
)
- def _getChange(self, event):
+ def _getChange(self, event, connection_event_ltime):
# Grab the change if we are managing the project or if it exists in the
# cache as it may be a dependency
if event.change_number:
refresh = True
change_key = self.connection.source.getChangeKey(event)
- if self.connection._change_cache.get(change_key) is None:
+ change = self.connection._change_cache.get(change_key)
+ if change is None:
refresh = False
for tenant in self.connection.sched.abide.tenants.values():
# TODO(fungi): it would be better to have some simple means
@@ -353,6 +356,13 @@ class GerritEventConnector(threading.Thread):
event.project_name))):
refresh = True
break
+ else:
+ # We have a cache entry for this change Get the
+ # query ltime for the cache entry; if it's after the
+ # event ltime, we don't need to refresh.
+ if (change.zuul_query_ltime and
+ change.zuul_query_ltime > connection_event_ltime):
+ refresh = False
if refresh:
# Call _getChange for the side effect of updating the
@@ -1418,15 +1428,20 @@ class GerritConnection(ZKChangeCacheMixin, ZKBranchCacheMixin, BaseConnection):
def queryChange(self, number, event=None):
for attempt in range(3):
+ # Get a query ltime -- any events before this point should be
+ # included in our change data.
+ zuul_query_ltime = self.sched.zk_client.getCurrentLtime()
try:
if self.session:
data, related, files = self.queryChangeHTTP(
number, event=event)
return GerritChangeData(GerritChangeData.HTTP,
- data, related, files)
+ data, related, files,
+ zuul_query_ltime=zuul_query_ltime)
else:
data = self.queryChangeSSH(number, event=event)
- return GerritChangeData(GerritChangeData.SSH, data)
+ return GerritChangeData(GerritChangeData.SSH, data,
+ zuul_query_ltime=zuul_query_ltime)
except Exception:
if attempt >= 3:
raise
diff --git a/zuul/driver/gerrit/gerritmodel.py b/zuul/driver/gerrit/gerritmodel.py
index f0ec32f77..0ac3e7f9d 100644
--- a/zuul/driver/gerrit/gerritmodel.py
+++ b/zuul/driver/gerrit/gerritmodel.py
@@ -35,8 +35,10 @@ class GerritChange(Change):
self.approvals = []
self.missing_labels = set()
self.commit = None
+ self.zuul_query_ltime = None
def update(self, data, connection):
+ self.zuul_query_ltime = data.zuul_query_ltime
if data.format == data.SSH:
self.updateFromSSH(data.data, connection)
else:
@@ -51,6 +53,7 @@ class GerritChange(Change):
"approvals": self.approvals,
"missing_labels": list(self.missing_labels),
"commit": self.commit,
+ "zuul_query_ltime": self.zuul_query_ltime,
})
return d
@@ -62,6 +65,7 @@ class GerritChange(Change):
self.approvals = data["approvals"]
self.missing_labels = set(data["missing_labels"])
self.commit = data.get("commit")
+ self.zuul_query_ltime = data.get("zuul_query_ltime")
def updateFromSSH(self, data, connection):
if self.patchset is None:
diff --git a/zuul/executor/server.py b/zuul/executor/server.py
index eac7fa7e5..e00612e9e 100644
--- a/zuul/executor/server.py
+++ b/zuul/executor/server.py
@@ -3157,6 +3157,10 @@ class ExecutorServer(BaseMergeServer):
self.allow_unzoned = get_default(self.config, 'executor',
'allow_unzoned', False)
+ # If this executor has no zone configured it is implicitly unzoned
+ if self.zone is None:
+ self.allow_unzoned = True
+
# Those attributes won't change, so it's enough to set them once on the
# component info.
self.component_info.zone = self.zone
diff --git a/zuul/merger/merger.py b/zuul/merger/merger.py
index c3e20e21b..34b495fcc 100644
--- a/zuul/merger/merger.py
+++ b/zuul/merger/merger.py
@@ -733,10 +733,8 @@ class Repo(object):
return
log = get_annotated_logger(self.log, zuul_event_id)
log.debug("Set remote url to %s", redact_url(url))
+ self._git_set_remote_url(self.createRepoObject(zuul_event_id), url)
self.remote_url = url
- self._git_set_remote_url(
- self.createRepoObject(zuul_event_id),
- self.remote_url)
def mapLine(self, commit, filename, lineno, zuul_event_id=None):
repo = self.createRepoObject(zuul_event_id)
@@ -1244,11 +1242,13 @@ class Merger(object):
item['connection'], item['project'], repo_state,
item['ref'], item['newrev'])
item = items[-1]
- repo = self.getRepo(item['connection'], item['project'])
# A list of branch names the last item appears in.
item_in_branches = []
if item.get('newrev'):
- item_in_branches = repo.contains(item['newrev'])
+ lock = repo_locks.getRepoLock(item['connection'], item['project'])
+ with lock:
+ repo = self.getRepo(item['connection'], item['project'])
+ item_in_branches = repo.contains(item['newrev'])
return (True, repo_state, item_in_branches)
def getFiles(self, connection_name, project_name, branch, files, dirs=[]):
diff --git a/zuul/scheduler.py b/zuul/scheduler.py
index 272235757..dfc922cf1 100644
--- a/zuul/scheduler.py
+++ b/zuul/scheduler.py
@@ -438,12 +438,12 @@ class Scheduler(threading.Thread):
mergers_online = 0
for executor_component in self.component_registry.all("executor"):
- if executor_component.allow_unzoned or not executor_component.zone:
+ if executor_component.allow_unzoned:
if executor_component.state == BaseComponent.RUNNING:
executors_unzoned_online += 1
if executor_component.accepting_work:
executors_unzoned_accepting += 1
- else:
+ if executor_component.zone:
zone_stats = zoned_executor_stats.setdefault(
executor_component.zone,
executor_stats_default.copy())
diff --git a/zuul/web/__init__.py b/zuul/web/__init__.py
index f06dd0b52..644b82bec 100755
--- a/zuul/web/__init__.py
+++ b/zuul/web/__init__.py
@@ -32,6 +32,7 @@ import ssl
import threading
import uuid
import prometheus_client
+import urllib.parse
import zuul.executor.common
from zuul import exceptions
@@ -1170,6 +1171,7 @@ class ZuulWebAPI(object):
@cherrypy.tools.json_out(
content_type='application/json; charset=utf-8', handler=json_handler)
def job(self, tenant_name, job_name):
+ job_name = urllib.parse.unquote_plus(job_name)
tenant = self._getTenantOrRaise(tenant_name)
job_variants = tenant.layout.jobs.get(job_name)
result = []
diff --git a/zuul/zk/event_queues.py b/zuul/zk/event_queues.py
index 52ffd582e..ad7529791 100644
--- a/zuul/zk/event_queues.py
+++ b/zuul/zk/event_queues.py
@@ -909,7 +909,7 @@ class ConnectionEventQueue(ZooKeeperEventQueue):
self._put({'event_data': data})
def __iter__(self):
- for data, ack_ref, _ in self._iterEvents():
+ for data, ack_ref, zstat in self._iterEvents():
if not data:
self.log.warning("Malformed event found: %s", data)
self._remove(ack_ref.path)
@@ -918,6 +918,7 @@ class ConnectionEventQueue(ZooKeeperEventQueue):
event = model.ConnectionEvent.fromDict(
data.get('event_data', data))
event.ack_ref = ack_ref
+ event.zuul_event_ltime = zstat.creation_transaction_id
yield event