2026-07-16 00:22:02 +09:00
#!/usr/bin/env bash
set -euo pipefail
ROOT = " $( cd " $( dirname " ${ BASH_SOURCE [0] } " ) /../.. " && pwd ) "
TESTBED = " $ROOT /tests/kind "
RUNTIME = " $TESTBED /.runtime "
BIN = " $RUNTIME /bin "
KUBECONFIG_PATH = " $RUNTIME /kubeconfig "
CONTEXT = kind-remora-testbed
CLUSTER = remora-testbed
NERDCTL_NAMESPACE = remora-wp02
2026-07-16 02:36:58 +09:00
FIXTURE_NAMESPACE = remora-testbed
2026-07-16 00:22:02 +09:00
set -a
source " $TESTBED /versions.env "
set +a
user_kubeconfig_digest( ) {
local config_value = " ${ KUBECONFIG :- ${ HOME : ? } /.kube/config } "
local path
local paths
IFS = : read -r -a paths <<< " $config_value "
{
for path in " ${ paths [@] } " ; do
if [ [ -f " $path " ] ] ; then
sha256sum " $path "
else
printf 'missing %s\n' " $path "
fi
done
} | sha256sum | cut -d' ' -f1
}
download_tool( ) {
local destination = " $1 "
local checksum = " $2 "
local url = " $3 "
if [ [ -x " $destination " ] ] && [ [ " $( sha256sum " $destination " | cut -d' ' -f1) " = " $checksum " ] ] ; then
return
fi
curl --fail --location --silent --show-error " $url " --output " $destination .tmp "
test " $( sha256sum " $destination .tmp " | cut -d' ' -f1) " = " $checksum "
chmod 0755 " $destination .tmp "
mv " $destination .tmp " " $destination "
}
prepare_tools( ) {
mkdir -p " $BIN "
download_tool \
" $BIN /kind " \
" $KIND_SHA256 " \
" https://github.com/kubernetes-sigs/kind/releases/download/ $KIND_VERSION /kind-linux-amd64 "
download_tool \
" $BIN /kubectl " \
" $KUBECTL_SHA256 " \
" https://dl.k8s.io/release/ $KUBECTL_VERSION /bin/linux/amd64/kubectl "
test " $( " $BIN /kind " version | awk '{print $2}' ) " = " $KIND_VERSION "
test " $( " $BIN /kubectl " --kubeconfig " $KUBECONFIG_PATH " --context " $CONTEXT " version --client= true -o json | jq -r .clientVersion.gitVersion) " = " $KUBECTL_VERSION "
2026-07-16 02:36:58 +09:00
test " $( nerdctl --version | awk '{print $3}' ) " = " $NERDCTL_VERSION "
test " $( runtime_ctr version | awk '/Client:/{client=1} client && $1=="Version:"{print $2; exit}' ) " = " v $CONTAINERD_VERSION "
test " $( runtime_ctr version | awk '/Server:/{server=1} server && $1=="Version:"{print $2; exit}' ) " = " v $CONTAINERD_VERSION "
2026-07-16 00:22:02 +09:00
}
prepare_provider( ) {
configure_provider
delete_cluster
remove_runtime_namespace
" $REMORA_REAL_NERDCTL " namespace create " $NERDCTL_NAMESPACE " >/dev/null
}
2026-07-16 02:36:58 +09:00
acquire_fixture_lock( ) {
mkdir -p " $RUNTIME "
exec 9>" $RUNTIME /fixture.lock "
if ! flock --nonblock 9; then
printf 'kind test fixture is already in use\n' >& 2
return 1
fi
}
2026-07-16 00:22:02 +09:00
configure_provider( ) {
2026-07-16 02:36:58 +09:00
if [ [ -z " ${ REMORA_REAL_NERDCTL :- } " ] ] ; then
REMORA_REAL_NERDCTL = " $( command -v nerdctl) "
fi
2026-07-16 00:22:02 +09:00
export REMORA_REAL_NERDCTL
export CONTAINERD_NAMESPACE = " $NERDCTL_NAMESPACE "
export KIND_EXPERIMENTAL_PROVIDER = nerdctl
export PATH = " $TESTBED /provider: $PATH "
}
remove_runtime_namespace( ) {
local status = 0
2026-07-16 02:36:58 +09:00
local digests
local digest
local leases
local lease
2026-07-16 00:22:02 +09:00
if ! runtime_namespace_exists; then
return
fi
" $REMORA_REAL_NERDCTL " --namespace " $NERDCTL_NAMESPACE " image prune --all --force >/dev/null || status = 1
2026-07-16 02:36:58 +09:00
leases = " $( runtime_ctr --namespace " $NERDCTL_NAMESPACE " leases list --quiet) " || status = 1
if [ [ -n " $leases " ] ] ; then
while read -r lease; do
runtime_ctr --namespace " $NERDCTL_NAMESPACE " leases delete " $lease " >/dev/null || status = 1
done <<< " $leases "
fi
digests = " $( runtime_ctr --namespace " $NERDCTL_NAMESPACE " content list --quiet) " || status = 1
if [ [ -n " $digests " ] ] ; then
while read -r digest; do
runtime_ctr --namespace " $NERDCTL_NAMESPACE " content remove " $digest " >/dev/null || status = 1
done <<< " $digests "
fi
2026-07-16 00:22:02 +09:00
" $REMORA_REAL_NERDCTL " namespace remove " $NERDCTL_NAMESPACE " >/dev/null || status = 1
if runtime_namespace_exists; then
printf 'containerd namespace %s still exists after teardown\n' " $NERDCTL_NAMESPACE " >& 2
status = 1
fi
return " $status "
}
2026-07-16 02:36:58 +09:00
runtime_ctr( ) {
ctr --address " ${ CONTAINERD_ADDRESS :- ${ XDG_RUNTIME_DIR :- /run/user/ $( id -u) } /containerd/containerd.sock } " " $@ "
}
2026-07-16 00:22:02 +09:00
runtime_namespace_exists( ) {
" $REMORA_REAL_NERDCTL " namespace list --quiet | grep -Fx " $NERDCTL_NAMESPACE " >/dev/null
}
cluster_exists( ) {
if ! runtime_namespace_exists; then
return 1
fi
" $BIN /kind " get clusters | grep -Fx " $CLUSTER " >/dev/null
}
delete_cluster( ) {
if cluster_exists; then
" $BIN /kind " delete cluster --name " $CLUSTER "
fi
if cluster_exists; then
printf 'kind cluster %s still exists after teardown\n' " $CLUSTER " >& 2
return 1
fi
}
load_external_image( ) {
local name = " $1 "
local source = " $2 "
local digest = " ${ source ##*@ } "
local image = " remora-testbed/ $name :sha256- ${ digest #sha256 : } "
local archive = " $RUNTIME /image-archives/ $digest .tar "
" $REMORA_REAL_NERDCTL " image pull --platform linux/amd64 --unpack= false --quiet " $source " >& 2 || return 1
" $REMORA_REAL_NERDCTL " image tag " $source " " $image " || return 1
" $REMORA_REAL_NERDCTL " image save --platform linux/amd64 --output " $archive " " $image " || return 1
printf 'Loading %s from %s\n' " $image " " $source " >& 2
" $BIN /kind " load image-archive " $archive " --name " $CLUSTER " >& 2 || return 1
printf '%s\n' " $image "
}
2026-07-16 02:36:58 +09:00
load_local_image( ) {
local image = " $1 "
local namespace = " ${ 2 :- default } "
local archive = " $RUNTIME /image-archives/package-test.tar "
rm -f " $archive "
" $REMORA_REAL_NERDCTL " --namespace " $namespace " image save \
--platform linux/amd64 \
--output " $archive " \
" $image "
" $BIN /kind " load image-archive " $archive " --name " $CLUSTER "
}
2026-07-16 00:22:02 +09:00
render_manifests( ) {
local postgres_image = " $1 "
local redis_image = " $2 "
local dragonfly_image = " $3 "
local vault_image = " $4 "
local keycloak_image = " $5 "
local zitadel_image = " $6 "
local garage_image = " $7 "
local versitygw_image = " $8 "
local coredns_image = " $9 "
local curl_image = " ${ 10 } "
local aws_cli_image = " ${ 11 } "
2026-07-16 05:39:51 +09:00
local postgres_replicas = " ${ 12 } "
local redis_replicas = " ${ 13 } "
local dragonfly_replicas = " ${ 14 } "
local vault_replicas = " ${ 15 } "
local keycloak_replicas = " ${ 16 } "
local zitadel_replicas = " ${ 17 } "
local garage_replicas = " ${ 18 } "
local versitygw_replicas = " ${ 19 } "
local coredns_replicas = " ${ 20 } "
2026-07-16 00:22:02 +09:00
sed \
-e " s|@@POSTGRES_IMAGE@@| $postgres_image |g " \
-e " s|@@REDIS_IMAGE@@| $redis_image |g " \
-e " s|@@DRAGONFLY_IMAGE@@| $dragonfly_image |g " \
-e " s|@@VAULT_IMAGE@@| $vault_image |g " \
-e " s|@@KEYCLOAK_IMAGE@@| $keycloak_image |g " \
-e " s|@@ZITADEL_IMAGE@@| $zitadel_image |g " \
-e " s|@@GARAGE_IMAGE@@| $garage_image |g " \
-e " s|@@VERSITYGW_IMAGE@@| $versitygw_image |g " \
-e " s|@@COREDNS_IMAGE@@| $coredns_image |g " \
-e " s|@@CURL_IMAGE@@| $curl_image |g " \
-e " s|@@AWS_CLI_IMAGE@@| $aws_cli_image |g " \
2026-07-16 05:39:51 +09:00
-e " s|@@POSTGRES_REPLICAS@@| $postgres_replicas |g " \
-e " s|@@REDIS_REPLICAS@@| $redis_replicas |g " \
-e " s|@@DRAGONFLY_REPLICAS@@| $dragonfly_replicas |g " \
-e " s|@@VAULT_REPLICAS@@| $vault_replicas |g " \
-e " s|@@KEYCLOAK_REPLICAS@@| $keycloak_replicas |g " \
-e " s|@@ZITADEL_REPLICAS@@| $zitadel_replicas |g " \
-e " s|@@GARAGE_REPLICAS@@| $garage_replicas |g " \
-e " s|@@VERSITYGW_REPLICAS@@| $versitygw_replicas |g " \
-e " s|@@COREDNS_REPLICAS@@| $coredns_replicas |g " \
2026-07-16 00:22:02 +09:00
" $TESTBED /dependencies.yaml.in " > " $RUNTIME /dependencies.yaml "
sed \
-e " s|@@POSTGRES_IMAGE@@| $postgres_image |g " \
-e " s|@@REDIS_IMAGE@@| $redis_image |g " \
-e " s|@@VAULT_IMAGE@@| $vault_image |g " \
-e " s|@@CURL_IMAGE@@| $curl_image |g " \
-e " s|@@AWS_CLI_IMAGE@@| $aws_cli_image |g " \
" $TESTBED /probe.yaml.in " > " $RUNTIME /probe.yaml "
2026-07-16 02:36:58 +09:00
{
printf 'POSTGRES_LOCAL_IMAGE=%q\n' " $postgres_image "
printf 'REDIS_LOCAL_IMAGE=%q\n' " $redis_image "
printf 'DRAGONFLY_LOCAL_IMAGE=%q\n' " $dragonfly_image "
printf 'VAULT_LOCAL_IMAGE=%q\n' " $vault_image "
printf 'KEYCLOAK_LOCAL_IMAGE=%q\n' " $keycloak_image "
printf 'ZITADEL_LOCAL_IMAGE=%q\n' " $zitadel_image "
printf 'GARAGE_LOCAL_IMAGE=%q\n' " $garage_image "
printf 'VERSITYGW_LOCAL_IMAGE=%q\n' " $versitygw_image "
printf 'COREDNS_LOCAL_IMAGE=%q\n' " $coredns_image "
printf 'CURL_LOCAL_IMAGE=%q\n' " $curl_image "
printf 'AWS_CLI_LOCAL_IMAGE=%q\n' " $aws_cli_image "
} > " $RUNTIME /fixture.env "
}
2026-07-16 05:39:51 +09:00
select_fixture_families( ) {
local requested = ( " $@ " )
local family
local candidate
local found
local use_all = false
FIXTURE_FAMILIES = ( )
if [ [ " ${# requested [@] } " = 0 ] ] ; then
requested = ( all)
fi
for family in " ${ requested [@] } " ; do
case " $family " in
all)
use_all = true
; ;
durable | coordination | oidc | target | secret) ; ;
*)
printf 'unknown fixture family: %s\n' " $family " >& 2
return 1
; ;
esac
done
if [ [ " $use_all " = true ] ] ; then
requested = ( durable coordination oidc target secret)
fi
for candidate in durable coordination oidc target secret; do
found = false
for family in " ${ requested [@] } " ; do
if [ [ " $family " = " $candidate " ] ] ; then
found = true
fi
done
if [ [ " $found " = true ] ] ; then
FIXTURE_FAMILIES += ( " $candidate " )
fi
done
}
fixture_has_family( ) {
local expected = " $1 "
local family
for family in " ${ FIXTURE_FAMILIES [@] } " ; do
if [ [ " $family " = " $expected " ] ] ; then
return
fi
done
return 1
}
load_selected_image( ) {
local family = " $1 "
local variable = " $2 "
local name = " $3 "
local source = " $4 "
local image
if fixture_has_family " $family " ; then
image = " $( load_external_image " $name " " $source " ) " || return
printf -v " $variable " '%s' " $image "
fi
}
2026-07-16 02:36:58 +09:00
setup_fixture( ) {
2026-07-16 05:39:51 +09:00
select_fixture_families " $@ " || return
2026-07-16 02:36:58 +09:00
mkdir -p " $RUNTIME "
rm -rf " $RUNTIME /image-archives "
mkdir -p " $RUNTIME /image-archives "
prepare_tools
prepare_provider
" $BIN /kind " create cluster \
--name " $CLUSTER " \
--image " $NODE_IMAGE " \
--config " $TESTBED /cluster.yaml " \
--kubeconfig " $KUBECONFIG_PATH " \
--wait 180s
2026-07-16 05:39:51 +09:00
POSTGRES_LOCAL_IMAGE = remora-testbed/unused:postgres
REDIS_LOCAL_IMAGE = remora-testbed/unused:redis
DRAGONFLY_LOCAL_IMAGE = remora-testbed/unused:dragonfly
VAULT_LOCAL_IMAGE = remora-testbed/unused:vault
KEYCLOAK_LOCAL_IMAGE = remora-testbed/unused:keycloak
ZITADEL_LOCAL_IMAGE = remora-testbed/unused:zitadel
GARAGE_LOCAL_IMAGE = remora-testbed/unused:garage
VERSITYGW_LOCAL_IMAGE = remora-testbed/unused:versitygw
COREDNS_LOCAL_IMAGE = remora-testbed/unused:coredns
CURL_LOCAL_IMAGE = remora-testbed/unused:curl
AWS_CLI_LOCAL_IMAGE = remora-testbed/unused:aws-cli
if fixture_has_family durable || fixture_has_family oidc; then
POSTGRES_LOCAL_IMAGE = " $( load_external_image postgres " $POSTGRES_IMAGE " ) "
fi
load_selected_image coordination REDIS_LOCAL_IMAGE redis " $REDIS_IMAGE "
load_selected_image coordination DRAGONFLY_LOCAL_IMAGE dragonfly " $DRAGONFLY_IMAGE "
load_selected_image secret VAULT_LOCAL_IMAGE vault " $VAULT_IMAGE "
load_selected_image oidc KEYCLOAK_LOCAL_IMAGE keycloak " $KEYCLOAK_IMAGE "
load_selected_image oidc ZITADEL_LOCAL_IMAGE zitadel " $ZITADEL_IMAGE "
load_selected_image target GARAGE_LOCAL_IMAGE garage " $GARAGE_IMAGE "
load_selected_image target VERSITYGW_LOCAL_IMAGE versitygw " $VERSITYGW_IMAGE "
load_selected_image oidc COREDNS_LOCAL_IMAGE coredns " $COREDNS_IMAGE "
if fixture_has_family oidc || fixture_has_family target; then
CURL_LOCAL_IMAGE = " $( load_external_image curl " $CURL_IMAGE " ) "
fi
load_selected_image target AWS_CLI_LOCAL_IMAGE aws-cli " $AWS_CLI_IMAGE "
POSTGRES_REPLICAS = 0
REDIS_REPLICAS = 0
DRAGONFLY_REPLICAS = 0
VAULT_REPLICAS = 0
KEYCLOAK_REPLICAS = 0
ZITADEL_REPLICAS = 0
GARAGE_REPLICAS = 0
VERSITYGW_REPLICAS = 0
COREDNS_REPLICAS = 0
if fixture_has_family durable || fixture_has_family oidc; then POSTGRES_REPLICAS = 1; fi
if fixture_has_family coordination; then REDIS_REPLICAS = 1; DRAGONFLY_REPLICAS = 1; fi
if fixture_has_family secret; then VAULT_REPLICAS = 1; fi
if fixture_has_family oidc; then KEYCLOAK_REPLICAS = 1; ZITADEL_REPLICAS = 1; COREDNS_REPLICAS = 1; fi
if fixture_has_family target; then GARAGE_REPLICAS = 1; VERSITYGW_REPLICAS = 1; fi
2026-07-16 02:36:58 +09:00
render_manifests \
" $POSTGRES_LOCAL_IMAGE " \
" $REDIS_LOCAL_IMAGE " \
" $DRAGONFLY_LOCAL_IMAGE " \
" $VAULT_LOCAL_IMAGE " \
" $KEYCLOAK_LOCAL_IMAGE " \
" $ZITADEL_LOCAL_IMAGE " \
" $GARAGE_LOCAL_IMAGE " \
" $VERSITYGW_LOCAL_IMAGE " \
" $COREDNS_LOCAL_IMAGE " \
" $CURL_LOCAL_IMAGE " \
2026-07-16 05:39:51 +09:00
" $AWS_CLI_LOCAL_IMAGE " \
" $POSTGRES_REPLICAS " \
" $REDIS_REPLICAS " \
" $DRAGONFLY_REPLICAS " \
" $VAULT_REPLICAS " \
" $KEYCLOAK_REPLICAS " \
" $ZITADEL_REPLICAS " \
" $GARAGE_REPLICAS " \
" $VERSITYGW_REPLICAS " \
" $COREDNS_REPLICAS "
2026-07-16 02:36:58 +09:00
2026-07-16 05:39:51 +09:00
apply_fixture_component common
if fixture_has_family durable || fixture_has_family oidc; then apply_fixture_component postgres; fi
if fixture_has_family coordination; then apply_fixture_component redis; apply_fixture_component dragonfly; fi
if fixture_has_family secret; then apply_fixture_component vault; fi
if fixture_has_family oidc; then apply_fixture_component keycloak; apply_fixture_component zitadel; apply_fixture_component controlled-dns; fi
if fixture_has_family target; then apply_fixture_component garage; apply_fixture_component versitygw; fi
if fixture_has_family durable || fixture_has_family oidc; then wait_for_deployment postgres; fi
if fixture_has_family coordination; then wait_for_deployment redis; wait_for_deployment dragonfly; fi
if fixture_has_family secret; then wait_for_deployment vault; fi
if fixture_has_family oidc; then wait_for_deployment keycloak; wait_for_deployment zitadel; wait_for_deployment controlled-dns; fi
if fixture_has_family target; then wait_for_deployment garage; wait_for_deployment versitygw; fi
}
apply_fixture_component( ) {
kube apply \
--filename " $RUNTIME /dependencies.yaml " \
--selector " fixture.remora.dev/component= $1 "
}
wait_for_deployment( ) {
kube --namespace " $FIXTURE_NAMESPACE " rollout status " deployment/ $1 " --timeout 240s
}
expand_fixture_matrix( ) {
local coordination_providers = ( -)
local oidc_providers = ( -)
local target_providers = ( -)
local coordination
local oidc
local target
local lane = 0
select_fixture_families " $@ " || return
if fixture_has_family coordination; then coordination_providers = ( redis dragonfly) ; fi
if fixture_has_family oidc; then oidc_providers = ( keycloak zitadel) ; fi
if fixture_has_family target; then target_providers = ( garage versitygw) ; fi
for coordination in " ${ coordination_providers [@] } " ; do
for oidc in " ${ oidc_providers [@] } " ; do
for target in " ${ target_providers [@] } " ; do
lane = $(( lane + 1 ))
printf '%03d\t%s\t%s\t%s\n' " $lane " " $coordination " " $oidc " " $target "
done
done
2026-07-16 02:36:58 +09:00
done
}
2026-07-16 05:39:51 +09:00
reset_lane_durable_store( ) {
local lane = " $1 "
local database = " remora_lane_ $lane "
kube --namespace " $FIXTURE_NAMESPACE " exec deployment/postgres -- \
env PGPASSWORD = synthetic-postgres psql \
--username remora \
--dbname postgres \
--set ON_ERROR_STOP = 1 \
--command " SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = ' $database ' " \
--command " DROP DATABASE IF EXISTS $database " \
--command " CREATE DATABASE $database " >& 2 || return
printf '%s\n' " $database "
}
reset_lane_target_store( ) {
local provider = " $1 "
local bucket = " $2 "
local deployment
local endpoint
if [ [ " $provider " = garage ] ] ; then
deployment = garage
endpoint = http://127.0.0.1:3900
else
deployment = versitygw
endpoint = http://127.0.0.1:10000
fi
if kube --namespace " $FIXTURE_NAMESPACE " exec " deployment/ $deployment " --container s3-readiness -- \
aws --endpoint-url " $endpoint " s3api head-bucket --bucket " $bucket " >/dev/null 2>& 1; then
cleanup_lane_target_store " $provider " " $bucket " || return
fi
kube --namespace " $FIXTURE_NAMESPACE " exec " deployment/ $deployment " --container s3-readiness -- \
aws --endpoint-url " $endpoint " s3api create-bucket --bucket " $bucket " >& 2 || return
}
2026-07-16 18:27:42 +09:00
# Remora storage role for fixture lanes: target-main / target-bucket / models.
remora_fixture_storage_role( ) {
local b64url
b64url( ) {
printf '%s' " $1 " | openssl base64 -A | tr '+/' '-_' | tr -d '='
}
printf 'remora:v1:storage|%s|%s|%s\n' \
" $( b64url target-main) " \
" $( b64url target-bucket) " \
" $( b64url models) "
}
2026-07-16 05:39:51 +09:00
reset_keycloak_lane( ) {
local lane = " $1 "
local realm = " remora-lane- $lane "
local username = " remora-user- $lane "
local client_id = " remora-client- $lane "
2026-07-16 18:27:42 +09:00
local storage_role
local client_uuid
storage_role = " $( remora_fixture_storage_role) "
2026-07-16 05:39:51 +09:00
if kube --namespace " $FIXTURE_NAMESPACE " exec deployment/keycloak -- \
/opt/keycloak/bin/kcadm.sh get " realms/ $realm " \
--no-config \
--server http://127.0.0.1:8080 \
--realm master \
--user remora-admin \
--password synthetic-keycloak >/dev/null 2>& 1; then
cleanup_keycloak_lane " $lane " || return
fi
kube --namespace " $FIXTURE_NAMESPACE " exec deployment/keycloak -- \
/opt/keycloak/bin/kcadm.sh create realms \
--no-config \
--server http://127.0.0.1:8080 \
--realm master \
--user remora-admin \
--password synthetic-keycloak \
--set " realm= $realm " \
2026-07-16 18:27:42 +09:00
--set enabled = true \
--set registrationEmailAsUsername = false \
--set verifyEmail = false \
--set resetPasswordAllowed = false \
--set loginWithEmailAllowed = true \
--set duplicateEmailsAllowed = false \
--set sslRequired = none >& 2 || return
# Keycloak 26 enables VERIFY_PROFILE by default; that blocks ROPC with
# "Account is not fully set up".
kube --namespace " $FIXTURE_NAMESPACE " exec deployment/keycloak -- \
/opt/keycloak/bin/kcadm.sh update authentication/required-actions/VERIFY_PROFILE \
--no-config \
--server http://127.0.0.1:8080 \
--realm master \
--user remora-admin \
--password synthetic-keycloak \
-r " $realm " \
--set enabled = false \
--set defaultAction = false >& 2 || true
# Public client with direct access grants so package Jobs can mint ROPC tokens.
2026-07-16 05:39:51 +09:00
kube --namespace " $FIXTURE_NAMESPACE " exec deployment/keycloak -- \
/opt/keycloak/bin/kcadm.sh create clients \
--no-config \
--server http://127.0.0.1:8080 \
--realm master \
--user remora-admin \
--password synthetic-keycloak \
-r " $realm " \
--set " clientId= $client_id " \
--set publicClient = true \
--set standardFlowEnabled = true \
2026-07-16 18:27:42 +09:00
--set directAccessGrantsEnabled = true \
--set 'redirectUris=["http://localhost/callback"]' \
--set 'attributes={"access.token.lifespan":"3600"}' >& 2 || return
client_uuid = " $( kube --namespace " $FIXTURE_NAMESPACE " exec deployment/keycloak -- \
/opt/keycloak/bin/kcadm.sh get clients \
--no-config \
--server http://127.0.0.1:8080 \
--realm master \
--user remora-admin \
--password synthetic-keycloak \
-r " $realm " \
--query " clientId= $client_id " \
--fields id \
--format csv \
--noquotes) " || return
client_uuid = " $( printf '%s' " $client_uuid " | tr -d '\r' | head -n1) "
[ [ -n " $client_uuid " ] ] || return
# Include this client in access-token `aud` so mandatory audience checks pass.
kube --namespace " $FIXTURE_NAMESPACE " exec deployment/keycloak -- \
/opt/keycloak/bin/kcadm.sh create " clients/ $client_uuid /protocol-mappers/models " \
--no-config \
--server http://127.0.0.1:8080 \
--realm master \
--user remora-admin \
--password synthetic-keycloak \
-r " $realm " \
--set name = remora-audience \
--set protocol = openid-connect \
--set protocolMapper = oidc-audience-mapper \
--set 'config."included.client.audience"=' " $client_id " \
--set 'config."id.token.claim"=false' \
--set 'config."access.token.claim"=true' >& 2 || return
2026-07-16 05:39:51 +09:00
kube --namespace " $FIXTURE_NAMESPACE " exec deployment/keycloak -- \
/opt/keycloak/bin/kcadm.sh create roles \
--no-config \
--server http://127.0.0.1:8080 \
--realm master \
--user remora-admin \
--password synthetic-keycloak \
-r " $realm " \
--set name = test-user >& 2 || return
2026-07-16 18:27:42 +09:00
# Client roles are the only Keycloak claim source Remora trusts (resource_access).
# Use -b JSON so `|` in storage roles is not percent-encoded by --set.
kube --namespace " $FIXTURE_NAMESPACE " exec deployment/keycloak -- \
/opt/keycloak/bin/kcadm.sh create " clients/ $client_uuid /roles " \
--no-config \
--server http://127.0.0.1:8080 \
--realm master \
--user remora-admin \
--password synthetic-keycloak \
-r " $realm " \
-b "{\"name\":\"remora:v1:admin\"}" >& 2 || return
kube --namespace " $FIXTURE_NAMESPACE " exec deployment/keycloak -- \
/opt/keycloak/bin/kcadm.sh create " clients/ $client_uuid /roles " \
--no-config \
--server http://127.0.0.1:8080 \
--realm master \
--user remora-admin \
--password synthetic-keycloak \
-r " $realm " \
-b " {\"name\":\" $storage_role \"} " >& 2 || return
2026-07-16 05:39:51 +09:00
kube --namespace " $FIXTURE_NAMESPACE " exec deployment/keycloak -- \
/opt/keycloak/bin/kcadm.sh create users \
--no-config \
--server http://127.0.0.1:8080 \
--realm master \
--user remora-admin \
--password synthetic-keycloak \
-r " $realm " \
--set " username= $username " \
--set enabled = true \
--set emailVerified = true \
2026-07-16 18:27:42 +09:00
--set " email= $username @remora.test " \
--set 'requiredActions=[]' >& 2 || return
local user_id
user_id = " $( kube --namespace " $FIXTURE_NAMESPACE " exec deployment/keycloak -- \
/opt/keycloak/bin/kcadm.sh get users \
--no-config \
--server http://127.0.0.1:8080 \
--realm master \
--user remora-admin \
--password synthetic-keycloak \
-r " $realm " \
--query " username= $username " \
--fields id \
--format csv \
--noquotes) " || return
user_id = " $( printf '%s' " $user_id " | tr -d '\r' | head -n1) "
[ [ -n " $user_id " ] ] || return
# Clear any required actions and mark email verified for ROPC.
kube --namespace " $FIXTURE_NAMESPACE " exec deployment/keycloak -- \
/opt/keycloak/bin/kcadm.sh update " users/ $user_id " \
--no-config \
--server http://127.0.0.1:8080 \
--realm master \
--user remora-admin \
--password synthetic-keycloak \
-r " $realm " \
--set emailVerified = true \
--set 'requiredActions=[]' \
--set enabled = true >& 2 || return
2026-07-16 05:39:51 +09:00
kube --namespace " $FIXTURE_NAMESPACE " exec deployment/keycloak -- \
/opt/keycloak/bin/kcadm.sh set-password \
--no-config \
--server http://127.0.0.1:8080 \
--realm master \
--user remora-admin \
--password synthetic-keycloak \
-r " $realm " \
2026-07-16 18:27:42 +09:00
--userid " $user_id " \
--new-password Synthetic-OIDC-1! \
--temporary= false >& 2 || return
2026-07-16 05:39:51 +09:00
kube --namespace " $FIXTURE_NAMESPACE " exec deployment/keycloak -- \
/opt/keycloak/bin/kcadm.sh add-roles \
--no-config \
--server http://127.0.0.1:8080 \
--realm master \
--user remora-admin \
--password synthetic-keycloak \
-r " $realm " \
--uusername " $username " \
--rolename test-user >& 2 || return
2026-07-16 18:27:42 +09:00
kube --namespace " $FIXTURE_NAMESPACE " exec deployment/keycloak -- \
/opt/keycloak/bin/kcadm.sh add-roles \
--no-config \
--server http://127.0.0.1:8080 \
--realm master \
--user remora-admin \
--password synthetic-keycloak \
-r " $realm " \
--uusername " $username " \
--cclientid " $client_id " \
--rolename 'remora:v1:admin' \
--rolename " $storage_role " >& 2 || return
# issuer \t tenant \t client_id \t username \t storage_role \t project_id \t user_id \t mgmt_token
# issuer \t tenant \t client_id \t username \t storage_role \t project_id \t user_id \t mgmt_token \t user_key_b64
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
2026-07-16 05:39:51 +09:00
" http://keycloak.remora.test:8080/realms/ $realm " \
" $realm " \
" $client_id " \
2026-07-16 18:27:42 +09:00
" $username " \
" $storage_role " \
"" \
"" \
"" \
""
2026-07-16 05:39:51 +09:00
}
zitadel_request( ) {
local endpoint = " $1 "
local body = " $2 "
local organization = " ${ 3 :- } "
local method = " ${ 4 :- POST } "
kube --namespace " $FIXTURE_NAMESPACE " exec deployment/zitadel --container provisioner -- \
/bin/sh -ec '
endpoint = " $1 "
body = " $2 "
organization = " $3 "
method = " $4 "
token = " $( cat /bootstrap/admin.pat) "
set -- \
--fail \
--silent \
--show-error \
--request " $method " \
2026-07-16 18:27:42 +09:00
--header "Host: zitadel.remora.test:8080" \
2026-07-16 05:39:51 +09:00
--header " Authorization: Bearer $token " \
--header "Content-Type: application/json"
if test -n " $organization " ; then
set -- " $@ " --header " x-zitadel-orgid: $organization "
fi
curl " $@ " --data " $body " " http://127.0.0.1:8080 $endpoint "
' remora-zitadel-request " $endpoint " " $body " " $organization " " $method "
}
json_string_field( ) {
local document = " $1 "
local field = " $2 "
local value
value = " $( sed -nE 's/.*"' " $field " '"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/p' <<< " $document " ) "
if [ [ -z " $value " ] ] ; then
printf 'ZITADEL response omitted %s\n' " $field " >& 2
return 1
fi
printf '%s\n' " $value "
}
reset_zitadel_lane( ) {
local lane = " $1 "
local tenant = " remora-lane- $lane "
local username = " remora-user- $lane "
2026-07-16 18:27:42 +09:00
local storage_role
2026-07-16 05:39:51 +09:00
local response
local organization_id
local project_id
local user_id
local client_id
2026-07-16 18:27:42 +09:00
local mgmt_token
local grant_id
storage_role = " $( remora_fixture_storage_role) "
2026-07-16 05:39:51 +09:00
cleanup_zitadel_lane " $lane " || return
response = " $( zitadel_request /management/v1/orgs " {\"name\":\" $tenant \"} " ) " || return
organization_id = " $( json_string_field " $response " id) " || return
response = " $( zitadel_request /management/v1/projects " {\"name\":\" $tenant \",\"projectRoleAssertion\":true,\"projectRoleCheck\":true} " " $organization_id " ) " || return
project_id = " $( json_string_field " $response " id) " || return
zitadel_request \
" /management/v1/projects/ $project_id /roles " \
'{"roleKey":"test-user","displayName":"Test User","group":"fixture"}' \
" $organization_id " >/dev/null || return
2026-07-16 18:27:42 +09:00
zitadel_request \
" /management/v1/projects/ $project_id /roles " \
'{"roleKey":"remora:v1:admin","displayName":"Remora Admin","group":"remora"}' \
" $organization_id " >/dev/null || return
zitadel_request \
" /management/v1/projects/ $project_id /roles " \
" {\"roleKey\":\" $storage_role \",\"displayName\":\"Remora Storage\",\"group\":\"remora\"} " \
" $organization_id " >/dev/null || return
# Native app: PKCE public client with JWT access tokens and project role assertion.
2026-07-16 05:39:51 +09:00
response = " $( zitadel_request \
" /management/v1/projects/ $project_id /apps/oidc " \
2026-07-16 18:27:42 +09:00
" {\"name\":\" $tenant \",\"redirectUris\":[\"http://localhost/callback\"],\"postLogoutRedirectUris\":[\"http://localhost/logout\"],\"responseTypes\":[\"OIDC_RESPONSE_TYPE_CODE\"],\"grantTypes\":[\"OIDC_GRANT_TYPE_AUTHORIZATION_CODE\",\"OIDC_GRANT_TYPE_REFRESH_TOKEN\"],\"appType\":\"OIDC_APP_TYPE_NATIVE\",\"authMethodType\":\"OIDC_AUTH_METHOD_TYPE_NONE\",\"version\":\"OIDC_VERSION_1_0\",\"devMode\":true,\"accessTokenType\":\"OIDC_TOKEN_TYPE_JWT\",\"accessTokenRoleAssertion\":true,\"idTokenRoleAssertion\":true,\"idTokenUserinfoAssertion\":true} " \
2026-07-16 05:39:51 +09:00
" $organization_id " ) " || return
client_id = " $( json_string_field " $response " clientId) " || return
response = " $( zitadel_request \
/management/v1/users/human \
2026-07-16 18:27:42 +09:00
" {\"userName\":\" $username \",\"profile\":{\"firstName\":\"Remora\",\"lastName\":\"Lane $lane \",\"displayName\":\"Remora Lane $lane \",\"preferredLanguage\":\"en\",\"gender\":\"GENDER_UNSPECIFIED\"},\"email\":{\"email\":\" $username @remora.test\",\"isEmailVerified\":true},\"initialPassword\":\"Synthetic-OIDC-1!\"} " \
2026-07-16 05:39:51 +09:00
" $organization_id " ) " || return
user_id = " $( json_string_field " $response " userId) " || return
2026-07-16 18:27:42 +09:00
# Set a permanent password for Session API checks (initialPassword alone is not enough on all versions).
if ! zitadel_request \
" /management/v1/users/ $user_id /password " \
'{"password":"Synthetic-OIDC-1!","noChangeRequired":true}' \
" $organization_id " >/dev/null; then
# Fallback field name used by older management API payloads.
zitadel_request \
" /management/v1/users/ $user_id /password " \
'{"password":"Synthetic-OIDC-1!","changeRequired":false}' \
" $organization_id " >/dev/null || return
fi
response = " $( zitadel_request \
2026-07-16 05:39:51 +09:00
" /management/v1/users/ $user_id /grants " \
2026-07-16 18:27:42 +09:00
" {\"projectId\":\" $project_id \",\"roleKeys\":[\"test-user\",\"remora:v1:admin\",\" $storage_role \"]} " \
" $organization_id " ) " || return
# Machine user + application key for JWT-bearer access tokens with project roles.
local machine_name = " remora-machine- $lane "
# Bearer defaults to opaque tokens; Remora validates only JWKS-signed JWTs.
response = " $( zitadel_request \
/management/v1/users/machine \
" {\"userName\":\" $machine_name \",\"name\":\"Remora Machine $lane \",\"description\":\"JWT bearer minting\",\"accessTokenType\":\"ACCESS_TOKEN_TYPE_JWT\"} " \
" $organization_id " ) " || return
local machine_id
machine_id = " $( json_string_field " $response " userId) " || return
zitadel_request \
" /management/v1/users/ $machine_id /grants " \
" {\"projectId\":\" $project_id \",\"roleKeys\":[\"remora:v1:admin\",\" $storage_role \"]} " \
2026-07-16 05:39:51 +09:00
" $organization_id " >/dev/null || return
2026-07-16 18:27:42 +09:00
response = " $( zitadel_request \
" /management/v1/users/ $machine_id /keys " \
'{"type":"KEY_TYPE_JSON","expirationDate":"2099-01-01T00:00:00Z"}' \
" $organization_id " ) " || return
local key_details_b64
key_details_b64 = " $( json_string_field " $response " keyDetails) " || return
mgmt_token = " $( kube --namespace " $FIXTURE_NAMESPACE " exec deployment/zitadel --container provisioner -- \
cat /bootstrap/admin.pat) " || return
mgmt_token = " $( printf '%s' " $mgmt_token " | tr -d '\r\n' ) "
# issuer \t tenant \t client_id \t username \t storage_role \t project_id \t user_id \t mgmt_token \t key_details_b64
# user_id is the machine user used for JWT-bearer minting (and grant loss).
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
2026-07-16 05:39:51 +09:00
http://zitadel.remora.test:8080 \
" $organization_id " \
" $client_id " \
2026-07-16 18:27:42 +09:00
" $username " \
" $storage_role " \
" $project_id " \
" $machine_id " \
" $mgmt_token " \
" $key_details_b64 "
2026-07-16 05:39:51 +09:00
}
reset_lane_oidc_provider( ) {
local provider = " $1 "
local lane = " $2 "
if [ [ " $provider " = keycloak ] ] ; then
reset_keycloak_lane " $lane "
else
reset_zitadel_lane " $lane "
fi
}
cleanup_lane_durable_store( ) {
local database = " remora_lane_ $1 "
local remaining
kube --namespace " $FIXTURE_NAMESPACE " exec deployment/postgres -- \
env PGPASSWORD = synthetic-postgres psql \
--username remora \
--dbname postgres \
--set ON_ERROR_STOP = 1 \
--command " SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = ' $database ' " \
--command " DROP DATABASE IF EXISTS $database " >& 2 || return
remaining = " $( kube --namespace " $FIXTURE_NAMESPACE " exec deployment/postgres -- \
env PGPASSWORD = synthetic-postgres psql \
--username remora \
--dbname postgres \
--tuples-only \
--no-align \
--command " SELECT count(*) FROM pg_database WHERE datname = ' $database ' " ) " || return
[ [ " $remaining " = 0 ] ]
}
cleanup_lane_coordination_store( ) {
local provider = " $1 "
local namespace = " $2 "
local keys
local key
while true; do
keys = " $( kube --namespace " $FIXTURE_NAMESPACE " exec " deployment/ $provider " -- \
redis-cli --scan --pattern " $namespace * " ) " || return
[ [ -z " $keys " ] ] && return
while IFS = read -r key; do
kube --namespace " $FIXTURE_NAMESPACE " exec " deployment/ $provider " -- \
redis-cli DEL " $key " >/dev/null || return
done <<< " $keys "
done
}
cleanup_lane_target_store( ) {
local provider = " $1 "
local bucket = " $2 "
local deployment
local endpoint
if [ [ " $provider " = garage ] ] ; then
deployment = garage
endpoint = http://127.0.0.1:3900
else
deployment = versitygw
endpoint = http://127.0.0.1:10000
fi
kube --namespace " $FIXTURE_NAMESPACE " exec " deployment/ $deployment " --container s3-readiness -- \
aws --endpoint-url " $endpoint " s3 rm " s3:// $bucket " --recursive >& 2 || return
kube --namespace " $FIXTURE_NAMESPACE " exec " deployment/ $deployment " --container s3-readiness -- \
aws --endpoint-url " $endpoint " s3api delete-bucket --bucket " $bucket " >& 2 || return
if kube --namespace " $FIXTURE_NAMESPACE " exec " deployment/ $deployment " --container s3-readiness -- \
aws --endpoint-url " $endpoint " s3api head-bucket --bucket " $bucket " >/dev/null 2>& 1; then
return 1
fi
}
cleanup_keycloak_lane( ) {
local realm = " remora-lane- $1 "
kube --namespace " $FIXTURE_NAMESPACE " exec deployment/keycloak -- \
/opt/keycloak/bin/kcadm.sh delete " realms/ $realm " \
--no-config \
--server http://127.0.0.1:8080 \
--realm master \
--user remora-admin \
--password synthetic-keycloak >& 2 || return
}
find_zitadel_lane_organizations( ) {
local lane = " $1 "
local tenant = " remora-lane- $lane "
local response
response = " $( zitadel_request /v2/organizations/_search '{}' ) " || return
jq --raw-output --arg tenant " $tenant " \
'.result[]? | select(.name == $tenant) | .id' <<< " $response "
}
cleanup_zitadel_lane( ) {
local lane = " $1 "
local organization_ids
local organization_id
local status = 0
organization_ids = " $( find_zitadel_lane_organizations " $lane " ) " || return
while IFS = read -r organization_id; do
[ [ -z " $organization_id " ] ] && continue
zitadel_request " /v2/organizations/ $organization_id " '' '' DELETE >/dev/null || status = 1
done <<< " $organization_ids "
return " $status "
}
cleanup_lane_secret_store( ) {
local path = " secret/remora/lane- $1 "
kube --namespace " $FIXTURE_NAMESPACE " exec deployment/vault -- \
env VAULT_ADDR = http://127.0.0.1:8200 VAULT_TOKEN = synthetic-vault-token \
vault kv metadata delete " $path " >/dev/null || return
}
cleanup_lane_state( ) {
local lane = " $1 "
local coordination = " $2 "
local oidc = " $3 "
local target = " $4 "
local status = 0
if fixture_has_family durable && ! cleanup_lane_durable_store " $lane " ; then
printf 'durable store cleanup failed for lane %s\n' " $lane " >& 2
status = 1
fi
if [ [ " $coordination " != - ] ] && ! cleanup_lane_coordination_store " $coordination " " remora:lane: $lane : " ; then
printf 'coordination store cleanup failed for lane %s\n' " $lane " >& 2
status = 1
fi
if [ [ " $oidc " = keycloak ] ] && ! cleanup_keycloak_lane " $lane " ; then
printf 'Keycloak cleanup failed for lane %s\n' " $lane " >& 2
status = 1
fi
if [ [ " $oidc " = zitadel ] ] && ! cleanup_zitadel_lane " $lane " ; then
printf 'ZITADEL cleanup failed for lane %s\n' " $lane " >& 2
status = 1
fi
if [ [ " $target " != - ] ] && ! cleanup_lane_target_store " $target " " remora-lane- $lane " ; then
printf 'target store cleanup failed for lane %s\n' " $lane " >& 2
status = 1
fi
if fixture_has_family secret && ! cleanup_lane_secret_store " $lane " ; then
printf 'secret store cleanup failed for lane %s\n' " $lane " >& 2
status = 1
fi
return " $status "
}
write_lane_profile( ) {
local lane = " $1 "
local coordination = " $2 "
local oidc = " $3 "
local target = " $4 "
local profile_name = " remora-fixture-lane- $lane "
local database =
local database_url =
local coordination_url =
local coordination_namespace =
local oidc_issuer =
local oidc_tenant =
local oidc_client_id =
local oidc_username =
2026-07-16 18:27:42 +09:00
local oidc_storage_role =
local oidc_project_id =
local oidc_user_id =
local oidc_mgmt_token =
local oidc_user_key_b64 =
2026-07-16 05:39:51 +09:00
local oidc_profile
local target_endpoint =
local target_bucket =
local target_prefix =
local target_access_key =
local target_secret_key =
local target_region =
local vault_path =
local profile
if fixture_has_family durable; then
database = " $( reset_lane_durable_store " $lane " ) " || return
database_url = " postgres://remora:synthetic-postgres@postgres:5432/ $database ?sslmode=disable "
fi
if [ [ " $coordination " != - ] ] ; then
coordination_url = " redis:// $coordination :6379 "
coordination_namespace = " remora:lane: $lane : "
cleanup_lane_coordination_store " $coordination " " $coordination_namespace " || return
fi
if [ [ " $oidc " != - ] ] ; then
oidc_profile = " $( reset_lane_oidc_provider " $oidc " " $lane " ) " || return
2026-07-16 18:27:42 +09:00
IFS = $'\t' read -r oidc_issuer oidc_tenant oidc_client_id oidc_username \
oidc_storage_role oidc_project_id oidc_user_id oidc_mgmt_token oidc_user_key_b64 \
<<< " $oidc_profile "
2026-07-16 05:39:51 +09:00
fi
if [ [ " $target " != - ] ] ; then
target_bucket = " remora-lane- $lane "
target_prefix = " lane- $lane / "
reset_lane_target_store " $target " " $target_bucket " || return
if [ [ " $target " = garage ] ] ; then
target_endpoint = http://garage:3900
target_access_key = GK00000000000000000000000000000000
target_secret_key = 0000000000000000000000000000000000000000000000000000000000000000
target_region = garage
else
target_endpoint = http://versitygw:10000
target_access_key = synthetic-s3-access
target_secret_key = synthetic-s3-secret
target_region = us-east-1
fi
fi
if fixture_has_family secret; then
vault_path = " secret/remora/lane- $lane "
cleanup_lane_secret_store " $lane " || return
fi
printf -v profile '%s\n' \
" REMORA_FIXTURE_LANE= $lane " \
" REMORA_COORDINATION_PROVIDER= $coordination " \
" REMORA_COORDINATION_URL= $coordination_url " \
" REMORA_COORDINATION_NAMESPACE= $coordination_namespace " \
" REMORA_OIDC_PROVIDER= $oidc " \
" REMORA_OIDC_ISSUER= $oidc_issuer " \
" REMORA_OIDC_TENANT= $oidc_tenant " \
" REMORA_OIDC_CLIENT_ID= $oidc_client_id " \
" REMORA_OIDC_USERNAME= $oidc_username " \
"REMORA_OIDC_PASSWORD=Synthetic-OIDC-1!" \
2026-07-16 18:27:42 +09:00
" REMORA_OIDC_STORAGE_ROLE= $oidc_storage_role " \
" REMORA_OIDC_PROJECT_ID= $oidc_project_id " \
" REMORA_OIDC_USER_ID= $oidc_user_id " \
" REMORA_OIDC_MGMT_TOKEN= $oidc_mgmt_token " \
" REMORA_OIDC_USER_KEY_B64= $oidc_user_key_b64 " \
2026-07-16 05:39:51 +09:00
" REMORA_TARGET_PROVIDER= $target " \
" REMORA_TARGET_ENDPOINT= $target_endpoint " \
" REMORA_TARGET_BUCKET= $target_bucket " \
" REMORA_TARGET_PREFIX= $target_prefix " \
" REMORA_TARGET_ACCESS_KEY= $target_access_key " \
" REMORA_TARGET_SECRET_KEY= $target_secret_key " \
" REMORA_TARGET_REGION= $target_region " \
2026-07-17 02:54:27 +09:00
"REMORA_TARGET_EXCLUSIVE_WRITER=true" \
2026-07-16 05:39:51 +09:00
"REMORA_VAULT_ADDR=http://vault:8200" \
"REMORA_VAULT_TOKEN=synthetic-vault-token" \
" REMORA_VAULT_PATH= $vault_path " \
" DATABASE_URL= $database_url "
kube --namespace " $FIXTURE_NAMESPACE " delete \
" configmap/ $profile_name " \
" configmap/ $profile_name -file " \
--ignore-not-found \
--wait= true >& 2 || return
kube --namespace " $FIXTURE_NAMESPACE " create configmap " $profile_name " \
--from-literal= " REMORA_FIXTURE_LANE= $lane " \
--from-literal= " REMORA_COORDINATION_PROVIDER= $coordination " \
--from-literal= " REMORA_COORDINATION_URL= $coordination_url " \
--from-literal= " REMORA_COORDINATION_NAMESPACE= $coordination_namespace " \
--from-literal= " REMORA_OIDC_PROVIDER= $oidc " \
--from-literal= " REMORA_OIDC_ISSUER= $oidc_issuer " \
--from-literal= " REMORA_OIDC_TENANT= $oidc_tenant " \
--from-literal= " REMORA_OIDC_CLIENT_ID= $oidc_client_id " \
--from-literal= " REMORA_OIDC_USERNAME= $oidc_username " \
--from-literal= REMORA_OIDC_PASSWORD = Synthetic-OIDC-1! \
2026-07-16 18:27:42 +09:00
--from-literal= " REMORA_OIDC_STORAGE_ROLE= $oidc_storage_role " \
--from-literal= " REMORA_OIDC_PROJECT_ID= $oidc_project_id " \
--from-literal= " REMORA_OIDC_USER_ID= $oidc_user_id " \
--from-literal= " REMORA_OIDC_MGMT_TOKEN= $oidc_mgmt_token " \
--from-literal= " REMORA_OIDC_USER_KEY_B64= $oidc_user_key_b64 " \
2026-07-16 05:39:51 +09:00
--from-literal= " REMORA_TARGET_PROVIDER= $target " \
--from-literal= " REMORA_TARGET_ENDPOINT= $target_endpoint " \
--from-literal= " REMORA_TARGET_BUCKET= $target_bucket " \
--from-literal= " REMORA_TARGET_PREFIX= $target_prefix " \
--from-literal= " REMORA_TARGET_ACCESS_KEY= $target_access_key " \
--from-literal= " REMORA_TARGET_SECRET_KEY= $target_secret_key " \
--from-literal= " REMORA_TARGET_REGION= $target_region " \
2026-07-17 02:54:27 +09:00
--from-literal= REMORA_TARGET_EXCLUSIVE_WRITER = true \
2026-07-16 05:39:51 +09:00
--from-literal= REMORA_VAULT_ADDR = http://vault:8200 \
--from-literal= REMORA_VAULT_TOKEN = synthetic-vault-token \
--from-literal= " REMORA_VAULT_PATH= $vault_path " \
--from-literal= " DATABASE_URL= $database_url " >& 2 || return
kube --namespace " $FIXTURE_NAMESPACE " create configmap " $profile_name -file " \
--from-literal= " profile.env= $profile " >& 2 || return
printf '%s\n' " $profile_name "
}
render_lane_job( ) {
local template = " $1 "
local output = " $2 "
local job = " $3 "
local profile = " $4 "
local image = " $5 "
local postgres_image = " ${ POSTGRES_LOCAL_IMAGE :- remora -testbed/unused : postgres } "
local redis_image = " ${ REDIS_LOCAL_IMAGE :- remora -testbed/unused : redis } "
local vault_image = " ${ VAULT_LOCAL_IMAGE :- remora -testbed/unused : vault } "
local curl_image = " ${ CURL_LOCAL_IMAGE :- remora -testbed/unused : curl } "
local aws_cli_image = " ${ AWS_CLI_LOCAL_IMAGE :- remora -testbed/unused : aws -cli } "
sed \
-e " s|@@JOB_NAME@@| $job |g " \
-e " s|@@PROFILE_NAME@@| $profile |g " \
-e " s|@@TEST_IMAGE@@| $image |g " \
-e " s|@@POSTGRES_IMAGE@@| $postgres_image |g " \
-e " s|@@REDIS_IMAGE@@| $redis_image |g " \
-e " s|@@VAULT_IMAGE@@| $vault_image |g " \
-e " s|@@CURL_IMAGE@@| $curl_image |g " \
-e " s|@@AWS_CLI_IMAGE@@| $aws_cli_image |g " \
" $template " > " $output "
}
run_job_matrix( ) {
local job_base = " $1 "
local template = " $2 "
local image = " $3 "
shift 3
local families = ( " $@ " )
local lane
local coordination
local oidc
local target
local job
local profile
local manifest
select_fixture_families " ${ families [@] } " || return
while IFS = $'\t' read -r lane coordination oidc target; do
job = " $job_base - $lane "
profile = " $( write_lane_profile " $lane " " $coordination " " $oidc " " $target " ) " || return
manifest = " $RUNTIME / $job .yaml "
render_lane_job " $template " " $manifest " " $job " " $profile " " $image " || return
printf 'Running fixture lane %s: coordination=%s oidc=%s target=%s\n' " $lane " " $coordination " " $oidc " " $target "
run_job_manifest " $FIXTURE_NAMESPACE " " $job " " $manifest " || return
cleanup_lane_state " $lane " " $coordination " " $oidc " " $target " || return
kube --namespace " $FIXTURE_NAMESPACE " delete \
" job/ $job " \
" configmap/ $profile " \
" configmap/ $profile -file " \
--wait= true || return
rm -f " $manifest " || return
done < <( expand_fixture_matrix " ${ families [@] } " )
}
2026-07-16 02:36:58 +09:00
verify_dependencies( ) {
2026-07-16 05:39:51 +09:00
local family
for family in durable coordination oidc target secret; do
if ! fixture_has_family " $family " ; then
printf 'released-image dependency probes require the all fixture profile\n' >& 2
return 1
fi
done
2026-07-16 02:36:58 +09:00
kube apply --filename " $RUNTIME /probe.yaml "
for job in state-probe oidc-probe garage-probe versitygw-probe; do
wait_for_job " $FIXTURE_NAMESPACE " " $job "
done
}
destroy_fixture( ) {
configure_provider
delete_cluster
remove_runtime_namespace
rm -rf " $RUNTIME /image-archives "
rm -f \
" $KUBECONFIG_PATH " \
" $RUNTIME /dependencies.yaml " \
" $RUNTIME /fixture.env " \
" $RUNTIME /probe.yaml " \
2026-07-16 03:56:44 +09:00
" $RUNTIME /sample-job.yaml " \
" $RUNTIME /durable-job.yaml "
2026-07-16 05:39:51 +09:00
find " $RUNTIME " -maxdepth 1 -type f -name '*-[0-9][0-9][0-9].yaml' -delete
2026-07-16 00:22:02 +09:00
}
kube( ) {
" $BIN /kubectl " \
--kubeconfig " $KUBECONFIG_PATH " \
--context " $CONTEXT " \
" $@ "
}
wait_for_job( ) {
2026-07-16 02:36:58 +09:00
local namespace = " $1 "
local job = " $2 "
2026-07-16 00:22:02 +09:00
local deadline = $(( SECONDS + 300 ))
local complete
local failed
while ( ( SECONDS < deadline) ) ; do
2026-07-16 02:36:58 +09:00
complete = " $( kube --namespace " $namespace " get " job/ $job " --output jsonpath = '{.status.conditions[?(@.type=="Complete")].status}' ) "
2026-07-16 00:22:02 +09:00
if [ [ " $complete " = True ] ] ; then
2026-07-16 05:39:51 +09:00
print_job_logs " $namespace " " $job "
2026-07-16 00:22:02 +09:00
return
fi
2026-07-16 02:36:58 +09:00
failed = " $( kube --namespace " $namespace " get " job/ $job " --output jsonpath = '{.status.conditions[?(@.type=="Failed")].status}' ) "
2026-07-16 00:22:02 +09:00
if [ [ " $failed " = True ] ] ; then
2026-07-16 05:39:51 +09:00
print_job_logs " $namespace " " $job " >& 2 || true
2026-07-16 00:22:02 +09:00
return 1
fi
sleep 1
done
2026-07-16 05:39:51 +09:00
print_job_logs " $namespace " " $job " >& 2 || true
2026-07-16 00:22:02 +09:00
printf 'job/%s did not complete within 300 seconds\n' " $job " >& 2
return 1
}
2026-07-16 02:36:58 +09:00
2026-07-16 05:39:51 +09:00
print_job_logs( ) {
local namespace = " $1 "
local job = " $2 "
local pod
local containers
local container
local status = 0
pod = " $( kube --namespace " $namespace " get pods \
--selector " job-name= $job " \
--output jsonpath = '{.items[0].metadata.name}' ) " || return
containers = " $( kube --namespace " $namespace " get " pod/ $pod " \
--output jsonpath = '{range .spec.initContainers[*]}{.name}{"\n"}{end}{range .spec.containers[*]}{.name}{"\n"}{end}' ) " || return
while IFS = read -r container; do
[ [ -z " $container " ] ] && continue
kube --namespace " $namespace " logs " pod/ $pod " --container " $container " || status = 1
done <<< " $containers "
return " $status "
}
2026-07-16 02:36:58 +09:00
run_job_manifest( ) {
local namespace = " $1 "
local job = " $2 "
local manifest = " $3 "
2026-07-16 05:39:51 +09:00
kube --namespace " $namespace " delete " job/ $job " --ignore-not-found --wait= true || return
kube --namespace " $namespace " apply --filename " $manifest " || return
wait_for_job " $namespace " " $job " || return
2026-07-16 02:36:58 +09:00
}