Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions .github/actions/validate-pipes/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Validate RocketRide Pipelines

Composite GitHub Action that validates `.pipe` pipeline files against a real
RocketRide engine. It:

1. Sets up Python and installs the [`rocketride`](https://pypi.org/project/rocketride/) CLI.
2. Starts the RocketRide engine in Docker and waits for its public `/version`
endpoint to respond (available on every published engine image).
3. Resolves the files to validate — on pull requests, only the `.pipe` files
changed relative to the base branch by default.
4. Runs `rocketride validate` against the local engine.
5. Always removes the engine container, even when validation fails.

The job fails when any pipeline is invalid (CLI exit code `1`) or when the
engine cannot be started or reached (exit code `2`).

## Requirements

- A Linux runner with Docker available (e.g. `ubuntu-latest`).
- The repository checked out before this action runs (`actions/checkout`).
The default `fetch-depth: 1` is fine — the action fetches the PR base
commit itself when `changed-only` is enabled.
- A `rocketride` CLI release that includes the `validate` command. The
install step fails fast with a clear error when the installed release does
not; use the `cli-version` input to pin a release that does.

## Inputs

| Input | Default | Description |
| --- | --- | --- |
| `files` | `**/*.pipe` | Glob pattern(s) selecting the files to validate. Multiple patterns may be separated by whitespace; `**` recurses into subdirectories. |
| `changed-only` | `true` | On `pull_request` events, validate only the matching files changed vs the PR base (via `git diff`). Ignored on other events. |
| `engine-image` | `ghcr.io/rocketride-org/rocketride-engine:latest` | Docker image of the engine to validate against. |
| `engine-port` | `5565` | Host port mapped to the engine container's internal port `5565`. |
| `api-key` | _(empty)_ | Optional API key, passed to the CLI via the `ROCKETRIDE_APIKEY` environment variable (never on the command line). |
| `python-version` | `3.12` | Python version used to run the CLI. |
| `cli-version` | _(empty)_ | pip version specifier for the `rocketride` CLI (e.g. `==1.2.3` or `>=1.2.3`). Must resolve to a release that includes the `validate` subcommand. Installs the latest release when empty. |
| `fail-on-warnings` | `false` | Fail the job when a pipeline validates with warnings (the CLI itself exits `0` on warnings). |

## Usage

```yaml
name: Validate pipelines

on:
pull_request:

permissions:
contents: read

jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: ./.github/actions/validate-pipes
with:
files: 'examples/**/*.pipe'
# api-key: ${{ secrets.ROCKETRIDE_APIKEY }} # if your engine requires auth
```

> [!TIP]
> For reproducible CI results, pin `engine-image` to a released version tag
> (e.g. `ghcr.io/rocketride-org/rocketride-engine:1.3.0`) or an image digest
> instead of `latest`, and bump it deliberately.

## Running the same check locally

```bash
docker run -d --name rocketride-engine -p 5565:5565 \
ghcr.io/rocketride-org/rocketride-engine:latest
curl -fsS --retry 30 --retry-delay 2 --retry-all-errors http://127.0.0.1:5565/version

pip install rocketride
ROCKETRIDE_URI=http://127.0.0.1:5565 rocketride validate examples/*.pipe

docker rm -f rocketride-engine
```

`rocketride validate` exits `0` when all files are valid, `1` when at least
one file fails validation, and `2` on usage errors, unreadable files, or
connection failures. Add `--json` for a machine-readable report.
221 changes: 221 additions & 0 deletions .github/actions/validate-pipes/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
name: 'Validate RocketRide Pipelines'
description: >-
Start a RocketRide engine in Docker and validate .pipe pipeline files with
the rocketride CLI. Fails the job when any pipeline is invalid.
author: 'RocketRide'

branding:
icon: 'check-circle'
color: 'orange'

inputs:
files:
description: >-
Glob pattern(s) selecting the .pipe files to validate. Multiple patterns
may be separated by whitespace. Patterns are expanded with bash globstar,
so '**' recurses into subdirectories.
required: false
default: '**/*.pipe'
changed-only:
description: >-
On pull_request events, validate only the matching .pipe files that
changed relative to the PR base (via git diff). Ignored on other events.
required: false
default: 'true'
engine-image:
description: 'Docker image of the RocketRide engine to validate against.'
required: false
default: 'ghcr.io/rocketride-org/rocketride-engine:latest'
engine-port:
description: "Host port mapped to the engine container's internal port 5565."
required: false
default: '5565'
api-key:
description: >-
Optional API key for the engine (passed to the CLI via the
ROCKETRIDE_APIKEY environment variable, never via argv).
required: false
default: ''
python-version:
description: 'Python version used to run the rocketride CLI.'
required: false
default: '3.12'
cli-version:
description: >-
pip version specifier for the rocketride CLI (e.g. '==1.2.3' or
'>=1.2.3'). Must resolve to a release that includes the validate
subcommand; the install step fails fast when it does not. Installs
the latest release when empty.
required: false
default: ''
fail-on-warnings:
description: >-
Fail the job when a pipeline validates with warnings (the CLI itself
exits 0 on warnings).
required: false
default: 'false'

runs:
using: 'composite'
steps:
- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: ${{ inputs.python-version }}

- name: Install rocketride CLI
shell: bash
env:
CLI_VERSION: ${{ inputs.cli-version }}
run: |
set -euo pipefail
python -m pip install --quiet --upgrade pip
python -m pip install --quiet "rocketride${CLI_VERSION}"
rocketride --help > /dev/null
# Fail fast when the installed release predates the validate command;
# otherwise the failure would surface later as a confusing argparse
# "invalid choice: 'validate'" usage error (exit code 2).
if ! rocketride validate --help > /dev/null 2>&1; then
installed="$(python -m pip show rocketride | sed -n 's/^Version: //p')"
echo "::error::rocketride ${installed:-unknown} does not support the 'validate' subcommand. Set the cli-version input to a release that includes it."
exit 2
fi

- name: Start RocketRide engine
id: engine
shell: bash
env:
ENGINE_IMAGE: ${{ inputs.engine-image }}
ENGINE_PORT: ${{ inputs.engine-port }}
run: |
set -euo pipefail
if ! [[ "${ENGINE_PORT}" =~ ^[0-9]+$ ]]; then
echo "::error::engine-port must be a number, got '${ENGINE_PORT}'"
exit 2
fi
# The engine always listens on 5565 inside the container
# (see docker/docker-compose.yml).
cid="$(docker run -d -p "${ENGINE_PORT}:5565" "${ENGINE_IMAGE}")"
echo "cid=${cid}" >> "${GITHUB_OUTPUT}"
echo "Started engine container ${cid} (${ENGINE_IMAGE}) on host port ${ENGINE_PORT}."

- name: Wait for engine health
shell: bash
env:
ENGINE_PORT: ${{ inputs.engine-port }}
ENGINE_CID: ${{ steps.engine.outputs.cid }}
run: |
set -euo pipefail
# /version is the engine's unconditionally public route (server.py
# registers it outside the standardEndpoints gate), so it works on
# every published engine image; /ping is gated and 404s on some.
deadline=$(( SECONDS + 120 ))
until curl -fsS "http://127.0.0.1:${ENGINE_PORT}/version" > /dev/null 2>&1; do

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Health probe is /version rather than /ping deliberately: server.py registers /version unconditionally public, while /ping sits behind the standardEndpoints gate — on the published engine image it 404s even with valid auth (verified live). The Helm chart's /ping liveness probes may deserve the same scrutiny; noted in #1572.

Also worth a look two steps down: changed-only fetches the PR base SHA explicitly before git diff, so the action works with actions/checkout's default fetch-depth: 1 — no fetch-depth requirement leaks onto consumers.

if (( SECONDS >= deadline )); then
echo "::error::RocketRide engine did not become healthy within 120s."
echo '--- engine logs (last 100 lines) ---'
docker logs --tail 100 "${ENGINE_CID}" || true
exit 2
fi
sleep 2
done
echo 'Engine is healthy.'

- name: Validate pipeline files
shell: bash
env:
FILES_GLOB: ${{ inputs.files }}
CHANGED_ONLY: ${{ inputs.changed-only }}
FAIL_ON_WARNINGS: ${{ inputs.fail-on-warnings }}
EVENT_NAME: ${{ github.event_name }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
ROCKETRIDE_URI: http://127.0.0.1:${{ inputs.engine-port }}
RR_API_KEY_INPUT: ${{ inputs.api-key }}
run: |
set -euo pipefail
shopt -s globstar nullglob

# Expand the configured glob pattern(s).
candidates=()
declare -A seen=()
read -ra patterns <<< "${FILES_GLOB//$'\n'/ }"
for pattern in "${patterns[@]}"; do
# shellcheck disable=SC2086 # intentional glob expansion
for f in ${pattern}; do
if [[ -f "${f}" && -z "${seen[${f}]:-}" ]]; then
seen["${f}"]=1
candidates+=("${f}")
fi
done
done

# On pull requests, optionally narrow to files changed vs the base.
files=()
if [[ "${CHANGED_ONLY}" == 'true' && "${EVENT_NAME}" == 'pull_request' && -n "${BASE_SHA}" ]]; then
if git fetch --quiet --no-tags --depth=1 origin "${BASE_SHA}"; then
declare -A changed=()
while IFS= read -r f; do
if [[ -n "${f}" ]]; then
changed["${f}"]=1
fi
done < <(git diff --name-only --diff-filter=ACMR "${BASE_SHA}" HEAD)
for f in ${candidates[@]+"${candidates[@]}"}; do
if [[ -n "${changed[${f}]:-}" ]]; then
files+=("${f}")
fi
done
else
echo "::warning::Could not fetch PR base ${BASE_SHA}; validating all matching files instead."
files=(${candidates[@]+"${candidates[@]}"})
fi
else
files=(${candidates[@]+"${candidates[@]}"})
fi

if (( ${#files[@]} == 0 )); then
echo "::warning::No .pipe files to validate (files='${FILES_GLOB}', changed-only=${CHANGED_ONLY})."
exit 0
fi

# Pass the API key via the environment only when set, never via argv.
if [[ -n "${RR_API_KEY_INPUT}" ]]; then
export ROCKETRIDE_APIKEY="${RR_API_KEY_INPUT}"
fi

echo "Validating ${#files[@]} file(s) against ${ROCKETRIDE_URI}:"
printf ' %s\n' "${files[@]}"

if [[ "${FAIL_ON_WARNINGS}" == 'true' ]]; then
report="${RUNNER_TEMP}/rocketride-validate-report.json"
rc=0
rocketride validate "${files[@]}" --json > "${report}" || rc=$?
cat "${report}"
if (( rc != 0 )); then
exit "${rc}"
fi
python3 - "${report}" <<'PY'
import json
import sys

with open(sys.argv[1], encoding='utf-8') as fh:
report = json.load(fh)

warned = [f['file'] for f in report.get('files', []) if f.get('warnings')]
for name in warned:
print(f'::error::fail-on-warnings: {name} validated with warnings')
sys.exit(1 if warned else 0)
PY
else
rocketride validate "${files[@]}"
fi

- name: Stop RocketRide engine
if: always()
shell: bash
env:
ENGINE_CID: ${{ steps.engine.outputs.cid }}
run: |
set -euo pipefail
if [[ -n "${ENGINE_CID}" ]]; then
docker rm -f "${ENGINE_CID}" > /dev/null 2>&1 || true
fi
62 changes: 62 additions & 0 deletions docs/README-python-client.md
Original file line number Diff line number Diff line change
Expand Up @@ -552,13 +552,75 @@ rocketride start pipeline.pipe # Start a pipeline
rocketride upload *.pdf --token <token> # Upload files to a running pipeline
rocketride status --token <token> # Monitor task progress
rocketride stop --token <token> # Terminate a running task
rocketride validate examples/*.pipe # Validate pipelines without running them
rocketride list # List all active tasks
rocketride events ALL --token <token> # Stream task events
rocketride rrext_store get_all_projects # List stored projects
```

All commands accept `--uri` and `--apikey` flags, or read from environment variables.

### validate

Validates one or more `.pipe` files against the server without starting them. Glob patterns are expanded by the CLI itself, so wildcards work the same on shells that do not expand them (e.g. Windows). Files must contain a JSON object — either the pipeline config itself or the `{ "pipeline": { ... } }` wrapper that `.pipe` files use; a file that cannot be read or parsed is reported as invalid.

```bash
rocketride validate <files...> [--source <id>] [--json]
```

| Flag / argument | Description |
| --------------- | ---------------------------------------------------------------------------- |
| `files...` | One or more `.pipe` file paths or glob patterns (e.g. `examples/*.pipe`). |
| `--source <id>` | Override the source component ID used for validation. |
| `--json` | Print a machine-readable JSON report to stdout instead of human output. |

Exit codes make `validate` suitable for CI:

| Code | Meaning |
| ---- | ------------------------------------------------------------------------------------------------------------------------------ |
| `0` | All files are valid. |
| `1` | At least one file failed validation (a file that cannot be read or parsed counts as invalid when other files were validated). |
| `2` | Usage error, connection failure, or no file could be processed at all — i.e. no file received a server validation verdict (e.g. every file was missing or not valid JSON). |

**Example:**

```bash
rocketride validate examples/rag-pipeline.pipe
```

```text
✓ examples/rag-pipeline.pipe: valid

Summary: 1 file(s), 1 valid, 0 invalid
```

**Example - JSON output:**

```bash
rocketride validate examples/rag-pipeline.pipe --json
```

```json
{
"files": [
{
"file": "examples/rag-pipeline.pipe",
"valid": true,
"errors": [],
"warnings": []
}
],
"summary": {
"total": 1,
"valid": 1,
"invalid": 0
}
}
```

To validate `.pipe` files automatically on every pull request, use the ready-made GitHub Action at [`.github/actions/validate-pipes`](https://github.com/rocketride-org/rocketride-server/tree/develop/.github/actions/validate-pipes).
It starts an engine container and runs `rocketride validate` on your repository's pipeline files.

## Configuration

| Variable | Description |
Expand Down
Loading
Loading