diff --git a/scripts/restore-keycloak-cohort-backups.sh b/scripts/restore-keycloak-cohort-backups.sh index 6ee6993..322158e 100755 --- a/scripts/restore-keycloak-cohort-backups.sh +++ b/scripts/restore-keycloak-cohort-backups.sh @@ -40,7 +40,7 @@ declare -A category_tables=( [components]='component component_config' [required_actions]='required_action_provider' ) -expected=$(for slug in "${!databases[@]}"; do printf '%s.dump\n' "${databases[$slug]}"; done | sort) +expected=$(for expected_slug in "${!databases[@]}"; do printf '%s.dump\n' "${databases[$expected_slug]}"; done | sort) observed=$(find "${backup_dir}" -mindepth 1 -maxdepth 1 -type f -printf '%f\n' | sort) [[ "${observed}" == "${expected}" ]] || { echo "Backup input is not the exact five-database cohort." >&2; exit 1; } if find "${backup_dir}" -mindepth 1 -maxdepth 1 -type l -print -quit | grep -q .; then echo "Backup input contains a symlink." >&2; exit 1; fi @@ -111,7 +111,7 @@ fingerprint_configuration() { { for table in ${category_tables[$category]}; do printf 'table\t%s\n' "${table}" - db_query "SELECT to_jsonb(value)::text FROM ${table} AS value ORDER BY to_jsonb(value)::text" + db_query "SELECT to_jsonb(value)::text FROM ${table} AS value ORDER BY to_jsonb(value)::text" | tee "${result_dir}/.runtime/${slug}-${destination_name}-${table}.jsonl" done } | sha256sum | cut -d' ' -f1 ) @@ -201,7 +201,24 @@ PY fingerprint_configuration after fingerprints=() for category in $(printf '%s\n' "${!category_tables[@]}" | sort); do - [[ "${after[$category]}" == "${before[$category]}" ]] || { echo "Keycloak ${category} configuration regression detected for ${slug}." >&2; exit 1; } + if [[ "${after[$category]}" != "${before[$category]}" ]]; then + python3 - "${result_dir}/.runtime" "${slug}" "${category_tables[$category]}" <<'PYDIFF' +import json, pathlib, sys +root, slug = pathlib.Path(sys.argv[1]), sys.argv[2] +for table in sys.argv[3].split(): + before = [json.loads(line) for line in (root/f"{slug}-before-{table}.jsonl").read_text().splitlines()] + after = [json.loads(line) for line in (root/f"{slug}-after-{table}.jsonl").read_text().splitlines()] + if before == after: continue + columns = sorted({key for row in before+after for key in row}) + keys = ['id'] if 'id' in columns else [key for key in columns if key.endswith('_id') or key=='name'] + def index(rows): return {tuple(str(row.get(key)) for key in keys):row for row in rows} + left, right = index(before), index(after) + changed = sorted({col for key in left.keys() & right.keys() for col in columns if left[key].get(col) != right[key].get(col)}) + print(json.dumps({'table':table,'before_rows':len(before),'after_rows':len(after),'changed_columns':changed,'added_rows':len(right.keys()-left.keys()),'removed_rows':len(left.keys()-right.keys())}), file=sys.stderr) +PYDIFF + echo "Keycloak ${category} configuration regression detected for ${slug}." >&2 + exit 1 + fi fingerprints+=("${category}=${after[$category]}") done combined=$(printf '%s\n' "${fingerprints[@]}" | sha256sum | cut -d' ' -f1)