Skip to content

feat(poc): replace Docker-based smoke tests with native Node.js harness#2231

Draft
rostalan wants to merge 2 commits intoredhat-developer:mainfrom
rostalan:smoke-test-poc
Draft

feat(poc): replace Docker-based smoke tests with native Node.js harness#2231
rostalan wants to merge 2 commits intoredhat-developer:mainfrom
rostalan:smoke-test-poc

Conversation

@rostalan
Copy link
Copy Markdown
Contributor

@rostalan rostalan commented Apr 9, 2026

Boot a minimal Backstage backend directly on the runner using createBackend() + dynamicPluginsFeatureLoader, probe /api/<pluginId> routes, and report results as structured JSON. Includes core bundled plugins (catalog, auth, permission, scaffolder, events, search, proxy) so dynamic plugins resolve their dependencies correctly.

Frontend plugins are also validated via two layers:

  • Static bundle checks — verifies dist-scalprum/ exists and contains JavaScript files after OCI download
  • Loaded-plugin probe — an inline backend plugin queries dynamicPluginsServiceRef to confirm frontend plugins were registered without errors

Results are merged into a single report with per-plugin status (pass, fail-bundle, fail-load, warn, skip) and the process exits non-zero on any failure.

Made-with: Cursor

Boot a minimal Backstage backend directly on the runner using
createBackend() + dynamicPluginsFeatureLoader, probe /api/<pluginId>
routes, and report results as structured JSON. Includes core bundled
plugins (catalog, auth, permission, scaffolder, events, search, proxy)
so dynamic plugins resolve their dependencies correctly.

Made-with: Cursor
@openshift-ci
Copy link
Copy Markdown

openshift-ci bot commented Apr 9, 2026

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

@rostalan
Copy link
Copy Markdown
Contributor Author

rostalan commented Apr 9, 2026

/publish

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 9, 2026

PR action (/publish) cancelled: PR doesn't touch only 1 workspace.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 9, 2026

⚠️
Smoke test workflow skipped: PR doesn't touch exactly one workspace.

@github-actions github-actions bot added the non-workspace-changes PR changes files outside workspace directories label Apr 9, 2026
…aded-plugin probe

Add two-layer frontend plugin validation to the smoke test harness:
- Layer 1: static validation of dist-scalprum/ after OCI download
- Layer 2: inline backend probe plugin querying dynamicPluginsServiceRef

Made-with: Cursor
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud bot commented Apr 9, 2026

Quality Gate Failed Quality Gate failed

Failed conditions
3 Security Hotspots

See analysis details on SonarQube Cloud

@rostalan rostalan changed the title Replace Docker-based smoke tests with native Node.js harness feat(poc): replace Docker-based smoke tests with native Node.js harness Apr 9, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 9, 2026

The file versions.json could not be synced from branch main into this because your PR is from a fork.

You should update the versions.json file with the following content:

{
    "backstage": "1.48.3",
    "node": "22.22.0",
    "cli": "1.10.4",
    "cliPackage": "@red-hat-developer-hub/cli"
}

@openshift-ci
Copy link
Copy Markdown

openshift-ci bot commented Apr 9, 2026

PR needs rebase.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

}

// ---------------------------------------------------------------------------
// OCI Download (mirrors install-dynamic-plugins.py §663-715)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment itself acknowledges this mirrors install-dynamic-plugins.py. Should we just call the Python script directly as a pre-step instead of reimplementing it in JS?

The overlay repo currently gets the script from the RHDH container image — by removing Docker, we lose access to it. But we could copy the script here (with a version marker) as a short-term solution. That would:

  • Eliminate ~60 lines of JS OCI code
  • Inherit all security protections for free (zip bomb, symlink traversal, integrity checks)
  • Leverage the 130KB+ test suite that already covers this logic

Longer-term options: publish as a pip package or container tool so any repo can consume it.

For context, there's also a parallel POC in RHDH core (redhat-developer/rhdh#4523) that extends the same Python script with parallel downloads (ThreadPoolExecutor) achieving ~34% speedup. Reusing it here would get that performance benefit too.


function extractPlugin(tarFile, pluginPath, dest) {
mkdirSync(join(dest, pluginPath), { recursive: true });
execSync(`tar xf "${tarFile}" -C "${dest}" "${pluginPath}/"`, {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This extracts tar contents with a bare execSync('tar xf ...') — no validation of archive members before extraction. The Python script in RHDH core (install-dynamic-plugins.py) checks for:

  • Zip bomb: member.size > MAX_ENTRY_SIZE (20MB default per entry)
  • Symlink traversal: os.path.realpath() validation against the destination directory
  • Hardlink traversal: same check
  • Device files/FIFOs: rejected entirely
  • Safe tar filter: tar.extractall(..., filter='tar')

Even in CI, a crafted OCI layer could exploit path traversal via symlinks. If we keep the JS implementation, all of these checks need to be ported.

if (existsSync(p)) process.argv.push("--config", p);
}

const { createBackend } = await import("@backstage/backend-defaults");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

createBackend() is the production API. Backstage provides startTestBackend() from @backstage/backend-test-utils specifically for this use case — it handles lifecycle management, automatic cleanup, built-in in-memory SQLite, and exposes mock services. It would also reduce the manual plugin registration below (~20 lines of backend.add() calls).

redhat-developer/rhdh#4523 uses startTestBackend() for the same plugin loadability validation and it works well.

// Config generation
// ---------------------------------------------------------------------------

function deepMerge(src, dst) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This reimplements config deep-merge that already exists in install-dynamic-plugins.py (the merge() function). The Python version also detects key conflicts and raises errors — this one silently overwrites. If we use the Python script as a pre-step, this function becomes unnecessary since the script already generates app-config.dynamic-plugins.yaml with all plugin configs merged.

);

backend.add(
createBackendPlugin({
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This probe plugin concept is great — using dynamicPluginsServiceRef to verify frontend plugins actually loaded at runtime is more thorough than filesystem-only checks. Worth keeping regardless of the OCI download approach. The sanity check POC in RHDH core (redhat-developer/rhdh#4523) currently only validates bundles via filesystem (dist-scalprum/plugin-manifest.json); this runtime probe approach would complement it nicely.

});
}

async function downloadPlugins(plugins, dest) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Downloads are sequential here. The install-dynamic-plugins-fast.py in redhat-developer/rhdh#4523 uses ThreadPoolExecutor for parallel OCI downloads with a shared image cache (one download per unique image, not per plugin), achieving ~34% speedup. Another reason to reuse the existing script rather than maintaining a separate implementation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants