Skip to content

Commit f22f2df

Browse files
authored
ci: validate loaded plugins after RHDH startup (#262)
1 parent 21b0777 commit f22f2df

2 files changed

Lines changed: 216 additions & 0 deletions

File tree

.github/actions/rhdh-local-compose-test/action.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,18 @@ runs:
240240
# Custom components.override.yaml
241241
cp configs/catalog-entities/components.override.example.yaml configs/catalog-entities/components.override.yaml
242242
243+
# Orchestrator plugins must be added directly to the override file.
244+
# See orchestrator/README.md#setup-orchestrator-and-workflow-examples
245+
- name: Add orchestrator plugins to dynamic-plugins override
246+
if: ${{ env.SKIP_TEST != 'true' && contains(inputs.compose_config_name, 'orchestrator') }}
247+
shell: bash
248+
run: |
249+
OVERRIDE="configs/dynamic-plugins/dynamic-plugins.override.yaml"
250+
if [ ! -f "$OVERRIDE" ]; then
251+
cp configs/dynamic-plugins/dynamic-plugins.yaml "$OVERRIDE"
252+
fi
253+
yq -i '.plugins += load("orchestrator/configs/dynamic-plugins/dynamic-plugins.yaml").plugins' "$OVERRIDE"
254+
243255
- name: Create dynamic plugins directory
244256
if: ${{ env.SKIP_TEST != 'true' && inputs.compose_config_name == 'dynamic-plugins-root' }}
245257
shell: bash
@@ -276,6 +288,11 @@ runs:
276288
echo "[$(date)] RHDH is ready"
277289
curl -i --insecure http://localhost:7007
278290
291+
- name: Validate loaded plugins
292+
if: ${{ env.SKIP_TEST != 'true' }}
293+
shell: bash
294+
run: ./tests/validate-loaded-plugins.sh
295+
279296
- name: curl from RHDH Container (for troubleshooting)
280297
if: ${{ failure() && env.SKIP_TEST != 'true' }}
281298
shell: bash

tests/validate-loaded-plugins.sh

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
#!/bin/bash
2+
# Validates that explicitly configured dynamic plugins are actually loaded
3+
# in the running RHDH instance by querying /api/extensions/loaded-plugins.
4+
#
5+
# Usage: ./tests/validate-loaded-plugins.sh [additional-config-files...]
6+
# Environment:
7+
# RHDH_URL - Base URL of the RHDH instance (default: http://localhost:7007)
8+
#
9+
# Authenticates via guest auth (GET /api/auth/guest/refresh) to obtain
10+
# a Backstage identity token, then queries /api/extensions/loaded-plugins.
11+
#
12+
# The script determines the effective dynamic-plugins config
13+
# (override if it exists, else default) and parses it using yq.
14+
# Any extra config files (e.g., orchestrator) can be passed as arguments.
15+
# Assumes CWD is the repository root.
16+
17+
set -euo pipefail
18+
19+
tmpfile=$(mktemp)
20+
trap 'rm -f "$tmpfile"' EXIT
21+
22+
for cmd in yq jq curl; do
23+
if ! command -v "$cmd" &>/dev/null; then
24+
echo "ERROR: Required command '$cmd' not found." >&2
25+
exit 1
26+
fi
27+
done
28+
29+
RHDH_URL="${RHDH_URL:-http://localhost:7007}"
30+
DYN_PLUGINS_DIR="configs/dynamic-plugins"
31+
32+
primary_config="$DYN_PLUGINS_DIR/dynamic-plugins.yaml"
33+
if [[ -f "$DYN_PLUGINS_DIR/dynamic-plugins.override.yaml" ]]; then
34+
primary_config="$DYN_PLUGINS_DIR/dynamic-plugins.override.yaml"
35+
fi
36+
37+
config_files=("$primary_config")
38+
for extra in "$@"; do
39+
if [[ -f "$extra" ]]; then
40+
config_files+=("$extra")
41+
else
42+
echo "WARNING: Extra config file not found, skipping: $extra"
43+
fi
44+
done
45+
46+
echo "Config files: ${config_files[*]}"
47+
48+
# Extract the raw plugin name from a package reference.
49+
# ./dynamic-plugins/dist/foo → foo
50+
# oci://host/path/foo:tag → foo
51+
# oci://host/path/foo:tag!bar → bar
52+
extract_plugin_name() {
53+
local pkg="$1"
54+
if [[ "$pkg" == *"!"* ]]; then
55+
echo "${pkg##*!}"
56+
return
57+
fi
58+
local name="${pkg##*/}"
59+
name="${name%%:*}" # strip tag (e.g. :v1.2.3 or :{{inherit}})
60+
echo "$name"
61+
}
62+
63+
# Normalize a plugin name so OCI image names and npm scoped package names
64+
# can be compared. For example:
65+
# @backstage-community/plugin-quay-backend-dynamic → backstage-community-plugin-quay-backend
66+
# backstage-community-plugin-quay-backend → backstage-community-plugin-quay-backend
67+
normalize_plugin_name() {
68+
local name="$1"
69+
name="${name#@}"
70+
name="${name//\//-}"
71+
name="${name%-dynamic}"
72+
echo "$name"
73+
}
74+
75+
expected_plugins=()
76+
disabled_plugins=()
77+
for cfg in "${config_files[@]}"; do
78+
if ! yq -e '.plugins' "$cfg" > /dev/null 2>&1; then
79+
echo "Config $cfg has no .plugins array, skipping."
80+
continue
81+
fi
82+
83+
enabled_output=$(yq -r '.plugins[] | select(.disabled != true and .enabled != false) | .package' "$cfg") || {
84+
echo "ERROR: Failed to extract enabled plugins from $cfg" >&2
85+
exit 1
86+
}
87+
while IFS= read -r raw_pkg; do
88+
[[ -z "$raw_pkg" ]] && continue
89+
expected_plugins+=("$(extract_plugin_name "$raw_pkg")")
90+
done <<< "$enabled_output"
91+
92+
disabled_output=$(yq -r '.plugins[] | select(.disabled == true or .enabled == false) | .package' "$cfg") || {
93+
echo "ERROR: Failed to extract disabled plugins from $cfg" >&2
94+
exit 1
95+
}
96+
while IFS= read -r raw_pkg; do
97+
[[ -z "$raw_pkg" ]] && continue
98+
disabled_plugins+=("$(extract_plugin_name "$raw_pkg")")
99+
done <<< "$disabled_output"
100+
done
101+
102+
if [[ ${#expected_plugins[@]} -eq 0 ]] && [[ ${#disabled_plugins[@]} -eq 0 ]]; then
103+
echo "WARNING: No plugins found in config files. Nothing to validate."
104+
exit 0
105+
fi
106+
107+
echo ""
108+
echo "Obtaining guest auth token from ${RHDH_URL}/api/auth/guest/refresh ..."
109+
guest_response=$(curl -sS -f "${RHDH_URL}/api/auth/guest/refresh" \
110+
-H "Accept: application/json")
111+
RHDH_TOKEN=$(echo "$guest_response" | jq -r '.backstageIdentity.token // empty' 2>/dev/null)
112+
if [[ -z "$RHDH_TOKEN" ]]; then
113+
echo "ERROR: Could not obtain guest auth token." >&2
114+
echo "Response: $guest_response" >&2
115+
exit 1
116+
fi
117+
echo "Guest auth token obtained."
118+
119+
echo ""
120+
echo "Fetching loaded plugins from ${RHDH_URL}/api/extensions/loaded-plugins ..."
121+
http_code=$(curl -sS -o "$tmpfile" -w "%{http_code}" \
122+
-H "Authorization: Bearer ${RHDH_TOKEN}" \
123+
"${RHDH_URL}/api/extensions/loaded-plugins")
124+
125+
if [[ "$http_code" != "200" ]]; then
126+
echo "ERROR: Could not reach loaded-plugins endpoint (HTTP $http_code)." >&2
127+
echo "Response body:" >&2
128+
cat "$tmpfile" >&2 2>/dev/null || true
129+
exit 1
130+
fi
131+
132+
loaded_names=$(jq -r '.[].name' "$tmpfile" 2>/dev/null)
133+
if [[ -z "$loaded_names" ]]; then
134+
echo "ERROR: Loaded plugins response was empty or could not be parsed." >&2
135+
echo "Response body:" >&2
136+
cat "$tmpfile" >&2 2>/dev/null || true
137+
exit 1
138+
fi
139+
140+
# Build a list of normalized loaded plugin names for matching.
141+
normalized_loaded=()
142+
while IFS= read -r loaded; do
143+
normalized_loaded+=("$(normalize_plugin_name "$loaded")")
144+
done <<< "$loaded_names"
145+
146+
failed=0
147+
148+
if [[ ${#expected_plugins[@]} -gt 0 ]]; then
149+
echo ""
150+
echo "Validating ${#expected_plugins[@]} expected plugin(s) are loaded:"
151+
for name in "${expected_plugins[@]}"; do
152+
norm_expected=$(normalize_plugin_name "$name")
153+
found=false
154+
for norm_loaded in "${normalized_loaded[@]}"; do
155+
if [[ "$norm_expected" = "$norm_loaded" ]]; then
156+
found=true
157+
break
158+
fi
159+
done
160+
if $found; then
161+
echo " [PASS] $name"
162+
else
163+
echo " [FAIL] $name (normalized: $norm_expected) -- not found in loaded plugins" >&2
164+
failed=$((failed + 1))
165+
fi
166+
done
167+
fi
168+
169+
if [[ ${#disabled_plugins[@]} -gt 0 ]]; then
170+
echo ""
171+
echo "Validating ${#disabled_plugins[@]} disabled plugin(s) are NOT loaded:"
172+
for name in "${disabled_plugins[@]}"; do
173+
norm_disabled=$(normalize_plugin_name "$name")
174+
found=false
175+
for norm_loaded in "${normalized_loaded[@]}"; do
176+
if [[ "$norm_disabled" = "$norm_loaded" ]]; then
177+
found=true
178+
break
179+
fi
180+
done
181+
if $found; then
182+
echo " [FAIL] $name (normalized: $norm_disabled) -- should not be loaded" >&2
183+
failed=$((failed + 1))
184+
else
185+
echo " [PASS] $name"
186+
fi
187+
done
188+
fi
189+
190+
echo ""
191+
if [[ $failed -gt 0 ]]; then
192+
echo "FAILED: $failed plugin validation(s) failed." >&2
193+
echo ""
194+
echo "Loaded plugins:"
195+
jq -r '.[].name' "$tmpfile" | sort
196+
exit 1
197+
fi
198+
199+
echo "All plugin validations passed."

0 commit comments

Comments
 (0)