Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ Environment variables:
| KC_ASSIGNED_REALM_ROLES | Space separated listed of realm roles assigned to client's service account. Created if missing. |
| KC_PROVIDES_CLIENT_ROLES | Space separated listed of client roles (e.g, 'role') to create that are associated with this client. Assigned as 'client_id/role'. |
| KC_ASSIGNED_CLIENT_ROLES | Space separated listed of client roles (e.g, 'client_id/role') assigned to client's service account. NOT created if missing. |
| KC_SKIP_DEFAULT_SETUP | If set to true, skips the default setup scripts (the unmodified copies of [defaults](https://github.com/JeffersonLab/keycloak/tree/main/scripts/defaults) in `/container-entrypoint-initdb.d`), so Keycloak starts with only the master realm and bootstrap admin. Other scripts in `/container-entrypoint-initdb.d` still run (optional) |

**Notes on Default Configuration:**
- Additional environment variables are set in [00_config.env](https://github.com/JeffersonLab/keycloak/blob/main/scripts/defaults/00_config.env). These can only be overridden by replacing this file or providing an additional env file to be sourced after.
Expand All @@ -56,6 +57,12 @@ Environment variables:
- All users have ${KC_RESOURCE}-user role. jdoe and tbrown have ${KC_RESOURCE}-admin role
- Client has KC_UPDATE_CLIENT_ROLES_MAPPER unset by default

**Notes on Setup and Persistence:**
- The setup scripts run once, after Keycloak first starts, and then the entrypoint writes a `setup-complete` marker file. The container is healthy only once the marker exists and Keycloak responds at KC_BACKEND_URL.
- With the default embedded (dev-file) database the marker is written to `/opt/keycloak/data/setup-complete`, next to the database. If you mount a volume at `/opt/keycloak/data`, a recreated container keeps its realm and skips setup. Without a volume, a new container starts fresh and runs setup again.
- With an external database (e.g. `KC_DB=oracle`) the marker is written to `/opt/keycloak/setup-complete`, in the container itself, so a recreated container runs the setup scripts again against the existing database. Set `KC_SKIP_DEFAULT_SETUP=true` (or make your own scripts idempotent) if the database outlives the container.
- The entrypoint runs until Keycloak exits and then exits with Keycloak's status, so Docker restart policies (e.g. `restart: unless-stopped`) apply if Keycloak stops. `docker stop` is passed on to Keycloak for a clean shutdown.

## Release
1. Bump the version number in the VERSION file and commit and push to GitHub (using [Semantic Versioning](https://semver.org/)).
2. The [CD](https://github.com/JeffersonLab/keycloak/blob/main/.github/workflows/cd.yaml) GitHub Action should run automatically invoking:
Expand Down
57 changes: 51 additions & 6 deletions scripts/container-entrypoint.sh
Original file line number Diff line number Diff line change
@@ -1,23 +1,51 @@
#!/bin/bash

# Marks that the setup scripts have run. With the default dev-file database the marker lives in the data
# directory next to the database, so both persist (or not) together when a volume is mounted on
# ${KC_HOME}/data. With any other database (e.g. KC_DB=oracle) the marker stays in the container's own layer.
# Keep in sync with container-healthcheck.sh.
if [[ -z "${KC_DB}" || "${KC_DB}" == "dev-file" ]]; then
SETUP_MARKER="${KC_HOME}/data/setup-complete"
else
SETUP_MARKER="${KC_HOME}/setup-complete"
fi

# Pass docker stop's SIGTERM (or Ctrl-C's SIGINT) on to Keycloak, wait for it to shut down, and exit with its status
function stop_keycloak {
echo "CONTAINER: Stopping Keycloak..."
kill -TERM "${KC_PID}" 2>/dev/null
wait "${KC_PID}"
exit $?
}

trap stop_keycloak TERM INT

echo "--------------------------"
echo "| Step 1: Start Keycloak |"
echo "--------------------------"

# start-dev implies --hostname-strict false --http-enabled true
${KC_HOME}/bin/kc.sh start-dev --hostname ${KC_FRONTEND_URL} --hostname-admin ${KC_FRONTEND_URL} --hostname-backchannel-dynamic true --hostname-debug true --http-relative-path ${KC_HTTP_RELATIVE_PATH} &
KC_PID=$!

echo "--------------------------------------"
echo "| Step 2: Wait for Keycloak to start |"
echo "--------------------------------------"

if [[ -z "${KC_BACKEND_URL}" ]]; then
echo "Skipping Keycloak Setup: Must provide KC_BACKEND_URL in environment"
return 0
wait "${KC_PID}"
exit $?
fi

until curl ${KC_BACKEND_URL} -sf -o /dev/null;
do
if ! kill -0 "${KC_PID}" 2>/dev/null; then
wait "${KC_PID}"
status=$?
echo "CONTAINER: Keycloak exited with status ${status} before it started"
exit ${status}
fi
echo $(date) " Still waiting for Keycloak to start..."
sleep 5
done
Expand Down Expand Up @@ -52,13 +80,26 @@ function run_custom_scripts {
fi;
}

# True when KC_SKIP_DEFAULT_SETUP=true and the file is an unmodified copy of one of the image's /defaults files
# at the top of /container-entrypoint-initdb.d, so user provided scripts still run
function is_skipped_default {
local d="/defaults/$(basename "${1}")"
[ "${KC_SKIP_DEFAULT_SETUP}" == "true" ] && [ "$(dirname "${1}")" == "/container-entrypoint-initdb.d" ] \
&& [ -f "${1}" ] && [ -f "${d}" ] && [ "$(< "${1}")" == "$(< "${d}")" ]
}

# This recursive function traverses through sub directories by calling itself with them
# usage: run_custom_scripts_recursive PATH
# ie: run_custom_scripts_recursive /container-entrypoint-initdb.d/001_subdir
# This runs *.sh files and traverses in sub directories
function run_custom_scripts_recursive {
local f
for f in "${1}"/*; do
if is_skipped_default "${f}"; then
echo -e "\nCONTAINER: skipping default ${f} (KC_SKIP_DEFAULT_SETUP=true)"
echo "";
continue
fi;
case "${f}" in
*.sh)
if [ -x "${f}" ]; then
Expand All @@ -68,7 +109,7 @@ function run_custom_scripts_recursive {

*.env)
if [ -f "${f}" ]; then
echo -e "\nCONTAINER: sourcing ${f} ..."; . "${f}" echo "CONTAINER: DONE: sourcing ${f}"
echo -e "\nCONTAINER: sourcing ${f} ..."; . "${f}"; echo "CONTAINER: DONE: sourcing ${f}"
fi;
;;

Expand All @@ -84,10 +125,11 @@ function run_custom_scripts_recursive {
done
}

if [ ! -f /${KC_HOME}/setup-complete ]; then
if [ ! -f "${SETUP_MARKER}" ]; then
echo -e "Running setup scripts"
run_custom_scripts "/container-entrypoint-initdb.d"
touch /${KC_HOME}/setup-complete
mkdir -p "$(dirname "${SETUP_MARKER}")"
touch "${SETUP_MARKER}"
else
echo -e "Setup already run; skipping"
fi
Expand All @@ -96,5 +138,8 @@ echo "----------"
echo "| READY! |"
echo "----------"

sleep infinity

# Run until Keycloak exits, so the container stops with it and Docker's restart policy applies
wait "${KC_PID}"
status=$?
echo "CONTAINER: Keycloak exited with status ${status}"
exit ${status}
9 changes: 8 additions & 1 deletion scripts/container-healthcheck.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
#!/bin/bash

test -f ${KC_HOME}/setup-complete && curl ${KC_BACKEND_URL} -sf -o /dev/null
# Same marker location as container-entrypoint.sh
if [[ -z "${KC_DB}" || "${KC_DB}" == "dev-file" ]]; then
SETUP_MARKER="${KC_HOME}/data/setup-complete"
else
SETUP_MARKER="${KC_HOME}/setup-complete"
fi

test -f "${SETUP_MARKER}" && curl ${KC_BACKEND_URL} -sf -o /dev/null
Loading