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
4 changes: 4 additions & 0 deletions .changeset/add-platformatic-world.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
---

Add Platformatic to `worlds-manifest.json` as a community world, and add a generic `docker` service type to the community-world CI so worlds can declare arbitrary Docker containers in their manifest `services` array. Platformatic's CI job is gated by the existing `if: false` on `e2e-community` until community worlds ship CBOR queue transport support.
64 changes: 63 additions & 1 deletion .github/workflows/e2e-community-world.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ on:
type: string
default: '{}'
service-type:
description: 'Service container to run (none, mongodb, redis)'
description: 'Service container to run (none, mongodb, redis, docker)'
required: false
type: string
default: 'none'
Expand All @@ -39,6 +39,11 @@ on:
required: false
type: string
default: ''
services:
description: 'JSON array of service definitions from the manifest (used when service-type is docker)'
required: false
type: string
default: '[]'

jobs:
e2e:
Expand Down Expand Up @@ -82,6 +87,58 @@ jobs:
sleep 2
done

- name: Start Docker services
if: ${{ inputs.service-type == 'docker' }}
Copy link
Copy Markdown
Member

@TooTallNate TooTallNate Apr 17, 2026

Choose a reason for hiding this comment

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

Unnecessary eval + fragile quoting.

cmd="docker run -d --name $name --network host"
for key in $(echo "$svc" | jq -r '.env // {} | keys[]'); do
  val=$(echo "$svc" | jq -r ".env[\"$key\"]")
  cmd="$cmd -e $key=$val"
done
cmd="$cmd $image"
eval "$cmd"

Issues:

  1. eval is unnecessary here. Every variable already works with direct expansion. Replace with an array:

    args=(docker run -d --name "$name" --network host)
    while IFS='=' read -r key val; do
      args+=(-e "$key=$val")
    done < <(echo "$svc" | jq -r '.env // {} | to_entries[] | "\(.key)=\(.value)"')
    args+=("$image")
    "${args[@]}"

    This avoids all shell re-interpretation of values.

  2. Values with spaces/special chars break. If a manifest env value contains a space or quote, cmd="$cmd -e $key=$val" concatenates it unquoted, and eval then splits on whitespace. Not currently exploited by the Platformatic manifest, but a booby trap for future world submissions.

  3. No set -e. A failing docker run (e.g. image pull failure) lets the loop continue and the test run proceeds anyway. Consider set -euo pipefail at the top of the run block.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Rewrote as args=(docker run ...) array — no eval, env values with quotes/spaces survive intact. Also added set -euo pipefail so any failure in the step aborts.

# Services start serially in manifest order. Each service's health check
# must pass before the next one starts, so any service that depends on
# another (e.g. a workflow service that needs postgres) must be listed
# AFTER its dependency in the manifest's `services` array.
#
# `healthCheck.cmd` is executed via `sh -c` below, which means the
# manifest is a shell-execution surface. Safe here because
# `worlds-manifest.json` is in-repo and PR-reviewed.
run: |
set -euo pipefail
SERVICES='${{ inputs.services }}'
count=$(echo "$SERVICES" | jq '. | length')
for idx in $(seq 0 $((count - 1))); do
svc=$(echo "$SERVICES" | jq -c ".[$idx]")
name=$(echo "$svc" | jq -r '.name')
image=$(echo "$svc" | jq -r '.image')
health_cmd=$(echo "$svc" | jq -r '.healthCheck.cmd // empty')
retries=$(echo "$svc" | jq -r '.healthCheck.retries // 10')

# Build docker run as an array so env values with spaces/quotes
# survive intact (no shell re-interpretation via eval).
args=(docker run -d --name "$name" --network host)
env_lines=$(echo "$svc" | jq -r '.env // {} | to_entries[] | "\(.key)=\(.value)"')
if [ -n "$env_lines" ]; then
while IFS= read -r line; do
args+=(-e "$line")
done <<< "$env_lines"
fi
args+=("$image")

echo "Starting $name ($image)..."
"${args[@]}"

# Health check runs on the host (--network host shares the network).
if [ -n "$health_cmd" ]; then
echo "Waiting for $name to be ready..."
for i in $(seq 1 "$retries"); do
if sh -c "$health_cmd" &>/dev/null; then
Copy link
Copy Markdown
Member

@TooTallNate TooTallNate Apr 17, 2026

Choose a reason for hiding this comment

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

sh -c "$health_cmd" executes manifest-provided string as shell.

This is explicitly executing the healthCheck.cmd field from the manifest. That's fine given the repo trust boundary (manifest entries are PR-reviewed), but worth being explicit about it in a comment — otherwise a reviewer of a future world PR might not realize the health check string is an executable surface.

Also: the health check uses retries but ignores the manifest's interval and timeout fields (which the Platformatic world declares as "interval": "10s", "timeout": "5s"). The hardcoded sleep 2 between retries means interval is ignored. If you're not going to honor them, consider removing them from the manifest (or document them as informational).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Added an explicit comment at the top of the step calling this out so the assumption is visible.

echo "$name is ready"
break
fi
if [ "$i" -eq "$retries" ]; then
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.

Nit: when a health check exhausts retries, this logs WARNING but the job continues, so the subsequent E2E tests will run against a broken service and fail with less actionable errors. Consider exit 1 here so the failure is diagnosed at the health-check step.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good catch. Now exit 1 when retries are exhausted.

echo "ERROR: $name health check did not pass after $retries retries"
exit 1
fi
sleep 2
done
fi
done

- name: Setup environment
uses: ./.github/actions/setup-workflow-dev
with:
Expand Down Expand Up @@ -135,3 +192,8 @@ jobs:
run: |
docker stop mongodb 2>/dev/null || true
docker stop redis 2>/dev/null || true
if [ '${{ inputs.service-type }}' = 'docker' ]; then
echo '${{ inputs.services }}' | jq -r '.[].name' 2>/dev/null | while read -r name; do
docker stop "$name" 2>/dev/null || true
done
fi
1 change: 1 addition & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,7 @@ jobs:
service-type: ${{ matrix.world.service-type }}
env-vars: ${{ matrix.world.env-vars }}
setup-command: ${{ matrix.world.setup-command }}
services: ${{ matrix.world.services }}
secrets: inherit

# Final job: Aggregate all E2E results and update PR comment
Expand Down
18 changes: 12 additions & 6 deletions scripts/create-community-worlds-matrix.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,19 @@ const testableWorlds = manifest.worlds.filter((world) => {
// Build the matrix
const matrix = {
world: testableWorlds.map((world) => {
// Determine service type based on services array
// Determine service type based on services array.
// `mongodb` and `redis` have dedicated single-service CI steps; anything
// else (or any multi-service world) goes through the generic `docker`
// path driven by the manifest's `services` array.
let serviceType = 'none';
if (world.services && world.services.length > 0) {
// Use the first service's name as the service type
// Currently supports: mongodb, redis
const serviceName = world.services[0].name;
if (['mongodb', 'redis'].includes(serviceName)) {
serviceType = serviceName;
if (world.services.length === 1) {
const serviceName = world.services[0].name;
serviceType = ['mongodb', 'redis'].includes(serviceName)
? serviceName
: 'docker';
} else {
Copy link
Copy Markdown
Member

@TooTallNate TooTallNate Apr 17, 2026

Choose a reason for hiding this comment

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

Nit: the fallback-to-docker logic means that if a future world entry has services [{ name: "mongodb" }, { name: "redis" }] (two services, neither built-in), the first service name determines the path. With your change, only the first service's name is checked — if it's mongodb or redis, the built-in path is used and the other services are silently ignored. Consider either:

  1. Picking docker if services.length > 1, OR
  2. Explicitly warning / erroring if a non-trivial services array includes a built-in name

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Added an explicit services.length > 1 branch that routes straight to the docker path, so any multi-service combo (including the [mongodb, redis] case you raised) gets generic handling instead of the single-service fallback.

serviceType = 'docker';
}
}

Expand All @@ -64,6 +69,7 @@ const matrix = {
'service-type': serviceType,
'env-vars': JSON.stringify(world.env || {}),
'setup-command': world.setup || '',
services: JSON.stringify(world.services || []),
};
}),
};
Expand Down
45 changes: 45 additions & 0 deletions worlds-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,51 @@
"services": [],
"requiresCredentials": true,
"credentialsNote": "Requires JAZZ_API_KEY, JAZZ_WORKER_ACCOUNT, and JAZZ_WORKER_SECRET from Jazz Cloud"
},
{
"id": "platformatic",
"type": "community",
"package": "@platformatic/world",
"name": "Platformatic",
"description": "Platformatic World backed by a centralized workflow service with PostgreSQL for version-safe orchestration on Kubernetes",
"repository": "https://github.com/platformatic/platformatic-world",
"docs": "https://github.com/platformatic/platformatic-world",
"features": [],
"env": {
"WORKFLOW_TARGET_WORLD": "@platformatic/world",
"PLT_WORLD_SERVICE_URL": "http://localhost:3042",
"PLT_WORLD_APP_ID": "default",
"PLT_WORLD_DEPLOYMENT_VERSION": "local"
},
"services": [
{
"name": "postgres",
"image": "postgres:18-alpine",
"ports": ["5432:5432"],
"env": {
"POSTGRES_USER": "wf",
"POSTGRES_PASSWORD": "wf",
"POSTGRES_DB": "workflow"
},
"healthCheck": {
"cmd": "pg_isready -h localhost",
"retries": 5
}
},
{
"name": "platformatic-workflow",
"image": "platformatic/workflow:0.7.0",
"ports": ["3042:3042"],
"env": {
"DATABASE_URL": "postgres://wf:wf@localhost:5432/workflow",
"PORT": "3042"
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.

No explicit service dependency declaration. The platformatic-workflow container depends on postgres being ready (it opens DB connections on startup), but the workflow file starts services serially in manifest order and health-checks each one before starting the next. So this works only because postgres is listed first.

If a future world entry lists services in a different order, this will silently break. Consider:

  1. Documenting the ordering requirement in a comment in the YAML, or
  2. Adding an optional dependsOn field per service (future work)

For this PR, just documenting the implicit ordering invariant somewhere would be enough.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Services now start serially and each health check must pass before the next starts, so postgres (listed first) is guaranteed ready before platformatic-workflow boots.
Added a comment in the workflow documenting this ordering requirement.

},
"healthCheck": {
"cmd": "curl -sf http://localhost:3042/status",
"retries": 10
}
}
]
}
]
}