Skip to content

Commit

Permalink
adds increased coverage test for keycloak-operator (#2697)
Browse files Browse the repository at this point in the history
  • Loading branch information
mritunjaysharma394 authored May 16, 2024
1 parent d89a200 commit 76c83f0
Showing 1 changed file with 287 additions and 18 deletions.
305 changes: 287 additions & 18 deletions images/keycloak-operator/tests/keycloak-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,276 @@

set -o errexit -o nounset -o errtrace -o pipefail -x

# Function to retry a command until it succeeds or reaches max attempts
# Arguments:
# $1: max_attempts
# $2: interval (seconds)
# $3: description of the operation
# ${@:4}: command to execute
retry_command() {
local max_attempts=$1
local interval=$2
local description=$3
local cmd="${@:4}"
local attempt=1

echo "Retrying: $description"
while [ $attempt -le $max_attempts ]; do
echo "Attempt $attempt: $cmd"
if eval $cmd; then
echo "Success on attempt $attempt for: $description"
return 0
else
echo "Failure on attempt $attempt for: $description"
sleep $interval
fi
((attempt++))
done

# In the event we fail, print out the status of resources in the cluster.
kubectl get all --all-namespaces

echo "Error: Failed after $max_attempts attempts for: $description"
return 1
}

# Define variables for Keycloak hostname and port
KEYCLOAK_HOSTNAME="localhost"
KEYCLOAK_PORT=8443
KEYCLOAK_URL="https://${KEYCLOAK_HOSTNAME}:${KEYCLOAK_PORT}"

TMPDIR="$(mktemp -d)"
NAMESPACE="keycloak-test"
apk add openssl
REQUEST_RETRIES=5
RETRY_DELAY=10

apk add jq curl openssl openssl-config yq

# Create Namespace and CRDs
# Function to test Keycloak API
TEST_keycloak_api() {
local attempt=0
local success=false

# Retrieve the initial admin credentials
local admin_username=$(kubectl get secret example-kc-initial-admin -n ${NAMESPACE} -o jsonpath='{.data.username}' | base64 -d)
local admin_password=$(kubectl get secret example-kc-initial-admin -n ${NAMESPACE} -o jsonpath='{.data.password}' | base64 -d)

while (( attempt < ${REQUEST_RETRIES} )); do
# Get an API token for Keycloak.
local response=$(
curl --http1.1 -k -w "\nHTTP_STATUS_CODE:%{http_code}\n" \
-X POST \
"${KEYCLOAK_URL}/realms/master/protocol/openid-connect/token" \
--user "admin-cli:Psip5UvTO1EXUEwzb15nxLWnwdU1Nlcg" \
-H "content-type: application/x-www-form-urlencoded" \
-d "username=${admin_username}" \
-d "password=${admin_password}" \
-d "grant_type=password"
)

local response_code=$(echo "${response}" | grep "HTTP_STATUS_CODE:" | cut -d':' -f2)
local content=$(echo "${response}" | sed '/HTTP_STATUS_CODE/d')

if [[ "${response_code}" == "200" ]]; then
local access_token=$(echo "${content}" | jq --raw-output '.access_token')

# Invoke Keycloak's API to retrieve a list of users.
local users_output=$(curl --http1.1 -kv -X GET "${KEYCLOAK_URL}/admin/realms/master/users" \
-H "Authorization: Bearer ${access_token}")
success=true
break
else
sleep ${RETRY_DELAY}
fi
attempt=$((attempt+1))
done

if ! $success; then
echo "ERROR: Failed to get user data from the API after ${REQUEST_RETRIES} attempts." >&2
return 1
fi

# Ensure that an 'admin' user was returned in the API response.
extracted_username=$(echo "${users_output}" | jq -r '.[] | select(.username=="admin") | .username')
if [[ "${extracted_username}" == "admin" ]]; then
echo "Keycloak API correctly returned 'admin' user details."
else
echo "FAILED: No entry with username 'admin' found in the response: ${users_output}"
exit 1
fi
}

# Function to create a new realm if it doesn't exist
create_realm_if_not_exists() {
local realm_name="test-realm"
local admin_username=$(kubectl get secret example-kc-initial-admin -n ${NAMESPACE} -o jsonpath='{.data.username}' | base64 -d)
local admin_password=$(kubectl get secret example-kc-initial-admin -n ${NAMESPACE} -o jsonpath='{.data.password}' | base64 -d)

# Get an API token for Keycloak.
local token_response
token_response=$(curl --http1.1 -k -sS -w "\nHTTP_STATUS_CODE:%{http_code}\n" \
-X POST "${KEYCLOAK_URL}/realms/master/protocol/openid-connect/token" \
--user "admin-cli:Psip5UvTO1EXUEwzb15nxLWnwdU1Nlcg" \
-H "content-type: application/x-www-form-urlencoded" \
-d "username=${admin_username}" \
-d "password=${admin_password}" \
-d "grant_type=password")

local response_code=$(echo "${token_response}" | grep "HTTP_STATUS_CODE:" | cut -d':' -f2)
local token_content=$(echo "${token_response}" | sed '/HTTP_STATUS_CODE/d')
local access_token=$(echo "${token_content}" | jq --raw-output '.access_token')

if [[ "${response_code}" != "200" ]]; then
echo "ERROR: Failed to get access token for Keycloak API." >&2
return 1
fi

# Check if the realm exists
local check_response
check_response=$(curl --http1.1 -k -sS -w "\nHTTP_STATUS_CODE:%{http_code}\n" \
-X GET "${KEYCLOAK_URL}/admin/realms/${realm_name}" \
-H "Authorization: Bearer ${access_token}" \
-H "Accept: application/json")

local check_code=$(echo "${check_response}" | grep "HTTP_STATUS_CODE:" | cut -d':' -f2)

if [[ "${check_code}" == "404" ]]; then
# Create the realm
local create_response
create_response=$(curl --http1.1 -k -sS -w "\nHTTP_STATUS_CODE:%{http_code}\n" \
-X POST "${KEYCLOAK_URL}/admin/realms" \
-H "Authorization: Bearer ${access_token}" \
-H "Content-Type: application/json" \
-d '{
"realm": "'"${realm_name}"'",
"enabled": true,
"users": [
{
"username": "test-user",
"enabled": true,
"emailVerified": true,
"firstName": "Test",
"lastName": "User",
"email": "test-user@example.com",
"credentials": [
{
"type": "password",
"value": "test-password",
"temporary": false
}
]
}
]
}')

local create_code=$(echo "${create_response}" | grep "HTTP_STATUS_CODE:" | cut -d':' -f2)

if [[ "${create_code}" != "201" ]]; then
echo "ERROR: Failed to create the realm." >&2
return 1
fi
elif [[ "${check_code}" != "200" ]]; then
echo "ERROR: Failed to check the existence of the realm." >&2
return 1
fi
}


# Function to test Keycloak Realm Import
TEST_keycloak_realm_import() {
local realm_name="test-realm"
local attempt=0
local success=false

# Retrieve the initial admin credentials
local admin_username=$(kubectl get secret example-kc-initial-admin -n ${NAMESPACE} -o jsonpath='{.data.username}' | base64 -d)
local admin_password=$(kubectl get secret example-kc-initial-admin -n ${NAMESPACE} -o jsonpath='{.data.password}' | base64 -d)

# Get an API token for Keycloak.
local token_response
token_response=$(curl --http1.1 -k -sS -w "\nHTTP_STATUS_CODE:%{http_code}\n" \
-X POST "${KEYCLOAK_URL}/realms/master/protocol/openid-connect/token" \
--user "admin-cli:Psip5UvTO1EXUEwzb15nxLWnwdU1Nlcg" \
-H "content-type: application/x-www-form-urlencoded" \
-d "username=${admin_username}" \
-d "password=${admin_password}" \
-d "grant_type=password")

# Check if the namespace exists by searching for its name in the list of all namespaces
local response_code=$(echo "${token_response}" | grep "HTTP_STATUS_CODE:" | cut -d':' -f2)
local token_content=$(echo "${token_response}" | sed '/HTTP_STATUS_CODE/d')
local access_token=$(echo "${token_content}" | jq --raw-output '.access_token')

if [[ "${response_code}" != "200" ]]; then
echo "ERROR: Failed to get access token for Keycloak API." >&2
return 1
fi

# Export the existing realm
local export_response
export_response=$(curl --http1.1 -k -sS -w "\nHTTP_STATUS_CODE:%{http_code}\n" \
-X GET "${KEYCLOAK_URL}/admin/realms/${realm_name}" \
-H "Authorization: Bearer ${access_token}" \
-H "Accept: application/json")

local export_code=$(echo "${export_response}" | grep "HTTP_STATUS_CODE:" | cut -d':' -f2)
local export_content=$(echo "${export_response}" | sed '/HTTP_STATUS_CODE/d')

if [[ "${export_code}" != "200" ]]; then
echo "ERROR: Failed to export the existing realm." >&2
return 1
fi

echo "${export_content}" > "${TMPDIR}/${realm_name}-realm-export.json"

# Delete the existing realm
local delete_response
delete_response=$(curl --http1.1 -k -sS -w "\nHTTP_STATUS_CODE:%{http_code}\n" \
-X DELETE "${KEYCLOAK_URL}/admin/realms/${realm_name}" \
-H "Authorization: Bearer ${access_token}")

local delete_code=$(echo "${delete_response}" | grep "HTTP_STATUS_CODE:" | cut -d':' -f2)

if [[ "${delete_code}" != "204" ]]; then
echo "ERROR: Failed to delete the existing realm." >&2
return 1
fi

# Convert the exported JSON to YAML
jq '.' "${TMPDIR}/${realm_name}-realm-export.json" | yq -P > "${TMPDIR}/${realm_name}-realm-export.yaml"

# Create a new KeycloakRealmImport CR to import the realm from YAML
cat <<EOF | kubectl apply -f -
apiVersion: k8s.keycloak.org/v2alpha1
kind: KeycloakRealmImport
metadata:
name: ${realm_name}-realm-import
namespace: ${NAMESPACE}
spec:
keycloakCRName: example-kc
realm:
$(cat "${TMPDIR}/${realm_name}-realm-export.yaml" | sed 's/^/ /')
EOF

# Wait for the realm import to complete
retry_command 5 15 "keycloak-realm-import" kubectl wait --for=condition=complete job/test-realm-realm-import -n ${NAMESPACE} --timeout=2m
# Wait for the realm import logs to populate
for (( attempt=0; attempt<${REQUEST_RETRIES}; attempt++ )); do
sleep ${RETRY_DELAY}
local realm_logs=$(kubectl logs -l app=keycloak-realm-import -n ${NAMESPACE})
if echo "${realm_logs}" | grep -q "Realm '${realm_name}' imported"; then
success=true
break
fi
done

if ! $success; then
echo "ERROR: Failed to import the realm configuration after ${REQUEST_RETRIES} attempts." >&2
return 1
fi
}


# Create Namespace and CRDs
if kubectl get ns | grep -qw $NAMESPACE; then
echo "Namespace $NAMESPACE already exists."
else
Expand Down Expand Up @@ -399,14 +662,13 @@ spec:
EOF
kubectl create -f "${TMPDIR}/minimal-keycloak-operator-manifest.yaml" -n ${NAMESPACE}

sleep 15
retry_command 5 15 "keycloak-operator pod readiness" kubectl wait --for=condition=ready pod --selector app.kubernetes.io/name=keycloak-operator --namespace ${NAMESPACE} --timeout=5m

kubectl wait --for=condition=ready pod --selector app.kubernetes.io/name=keycloak-operator --namespace ${NAMESPACE} --timeout=5m
# using sleep as retry_command failed for logs to populate
sleep 5

logs=$(kubectl logs -l app.kubernetes.io/name=keycloak-operator -n ${NAMESPACE})

sleep 10

echo "$logs" | grep "Listening on: http://0.0.0.0:8080"

cat <<EOF > "${TMPDIR}/example-postgres.yaml"
Expand Down Expand Up @@ -461,9 +723,7 @@ EOF

kubectl apply -f "${TMPDIR}"/example-postgres.yaml -n ${NAMESPACE}

sleep 15

kubectl wait --for=condition=ready pod --selector statefulset.kubernetes.io/pod-name=postgresql-db-0 -n ${NAMESPACE} --timeout=5m
retry_command 5 15 "postgresql-db-0 readiness" kubectl wait --for=condition=ready pod --selector statefulset.kubernetes.io/pod-name=postgresql-db-0 -n ${NAMESPACE} --timeout=5m

chmod -R 777 "${TMPDIR}"

Expand All @@ -474,7 +734,6 @@ kubectl create secret generic keycloak-db-secret -n ${NAMESPACE} \
--from-literal=username=testuser \
--from-literal=password=testpassword


cat <<EOF > "${TMPDIR}/example-kc.yaml"
apiVersion: k8s.keycloak.org/v2alpha1
kind: Keycloak
Expand All @@ -495,21 +754,31 @@ spec:
http:
tlsSecret: example-tls-secret
hostname:
hostname: test.keycloak.org
hostname: ${KEYCLOAK_HOSTNAME}
proxy:
headers: xforwarded # double check your reverse proxy sets and overwrites the X-Forwarded-* headers
EOF

kubectl apply -f "${TMPDIR}"/example-kc.yaml -n ${NAMESPACE}

kubectl get keycloaks/example-kc -o go-template='{{range .status.conditions}}CONDITION: {{.type}}{{"\n"}} STATUS: {{.status}}{{"\n"}} MESSAGE: {{.message}}{{"\n"}}{{end}}' -n ${NAMESPACE}

sleep 10
retry_command 5 15 "example-kc pod readiness" kubectl wait --for=condition=ready pod --selector statefulset.kubernetes.io/pod-name=example-kc-0 -n ${NAMESPACE} --timeout=5m

kubectl wait --for=condition=ready pod --selector statefulset.kubernetes.io/pod-name=example-kc-0 -n ${NAMESPACE} --timeout=5m
# using sleep as retry_command failed for logs to populate
sleep 5

klogs=$(kubectl logs -l app=keycloak -n ${NAMESPACE})

sleep 10
echo "$klogs" | grep "Listening on: https://0.0.0.0:8443"

# Set up port forwarding
kubectl port-forward svc/example-kc-service ${KEYCLOAK_PORT}:8443 -n ${NAMESPACE} &

# Wait for port forwarding
sleep 5

# Call the Keycloak API test function
TEST_keycloak_api

echo "$klogs" | grep "Listening on: https://0.0.0.0:8443"
# Call the Keycloak Realm Import test function
create_realm_if_not_exists
TEST_keycloak_realm_import

0 comments on commit 76c83f0

Please sign in to comment.