/ftl:lab-validator

๐Ÿš€ RHDP E2E Lab Validator

Write solve.yml and validate.yml Ansible playbooks for RHDP Showroom labs. Reads your .adoc modules โ€” including screenshots โ€” and generates working playbooks for E2E testing. Orchestrates 4 agents: content-reader โ†’ solve-writer โ†’ validate-writer โ†’ env-connector.

Self-healing: When UI versions change, Playwright selectors are automatically recovered via vision โ€” no manual selector updates required.


Before You Start

The skill opens with a pre-flight checklist. Make sure both are ready before running it:

1 โ€” Showroom Repo
Sync from showroom_template_nookbag e2e-template branch:

content/supplemental-ui/js/buttons.js
content/supplemental-ui/css/site-extra.css
content/lib/inject-buttons.js
content/lib/dev-mode.js
runtime-automation/module-XX/solve.yml
runtime-automation/module-XX/validate.yml

site.yml must have the Antora extensions and supplemental_files block configured (copy from e2e-template).

Add solve/validate button placeholders to each .adoc module.
2 โ€” AgV Catalog
All lab types:
rhpds-ftl collection ยท showroom v1.6.6+ ยท zt-runner v2.4.2 ยท wetty v3.0

OCP tenant/dedicated:
ocp4_workload_runtime_automation_k8s workload

RHEL VM:
vm_workload_runtime_automation workload

See examples/ in the e2e-template branch for exact common.yaml per lab type.

Workflow

โ–ถ /ftl:lab-validator
โ†“
Pre-flight
Checklist Shown
Skill displays required showroom files, site.yml config, and AgV vars per lab type.
Sync your repo and AgV before continuing.

New: Asks if you already have solve.yml or validate.yml. If yes, uses them as baseline โ€” only generates what's missing.
โ†“
Step 1
Collect Inputs + Environment Setup
Lab type: ocp-tenant ยท ocp-dedicated ยท vm-rhel
Showroom path: local path or GitHub URL
Access โ€” OCP: admin token or oc login + restart Claude
Access โ€” VM: SSH host, user, key path

OCP tenant โ€” order guidance: If no live lab yet, skill tells you to order on demo.redhat.com using your logged-in SSO user and note the GUID.

โš ๏ธ Skill checks dev.yaml for a silent content_git_repo_ref override that kills branch changes.
โ†“
Step 2
Discover Modules
Reads all .adoc files and identifies modules with exercises.
Checks AgV namespaces and runner image version.
Shows a summary โ€” you choose which modules to generate playbooks for.
โ†“
Step 3 โ€” Per module
4-Agent Pipeline: Read โ†’ Solve โ†’ Validate โ†’ Test
content-reader โ€” reads .adoc, vision-analyzes screenshots, classifies each step (k8s_exec โ†’ api โ†’ Playwright โ†’ skip)
solve-writer โ€” generates solve.yml with intent-based Playwright scripts (self-healing on UI changes)
validate-writer โ€” generates validate.yml with rhpds.ftl.validation_check, durable outcome checks
env-connector โ€” pushes, tests, collects screenshot evidence, runs self-healing if Playwright fails

Previews files before writing.
โ†“
Step 4 โ€” Guided
Push โ†’ Restart โ†’ Full Test Cycle
Skill runs the commands for you and shows output at each step:

git push โ†’ oc rollout restart โ†’ extract Showroom URL

Then the full test cycle using SSE streams:
1. Fresh validate โ†’ should โŒ fail
confirms checks test real student state
โ†“
2. Solve โ†’ runs playbook
watch for fatal errors or tracebacks
โ†“
3. Validate again โ†’ should โœ… pass
confirms solve completed the exercise
โ†“
4. Validate once more โ†’ idempotency check
confirms solve left clean state
โ†“
Step 5 โ€” If needed
Fix Loop (with Self-Healing)
Ansible failure โ†’ solve-writer or validate-writer re-invoked with error context
Playwright failure โ†’ env-connector self-healing: screenshot โ†’ vision โ†’ new selector โ†’ retry
Repeats until all modules pass clean. Screenshots stored as evidence.
โ†“
โœ… All Modules Pass Solve + Validate

Ordering Your Lab (OCP Tenant)

The skill guides you through this when no live environment exists yet.

โ–ถ Order on demo.redhat.com
  1. Go to demo.redhat.com and find your catalog item
  2. Order using your Red Hat SSO user โ€” same user you are logged in as with oc login
  3. Note the GUID from the order confirmation
  4. Your showroom namespace will be showroom-<guid>
Once provisioned, return to the skill with the GUID and it will pick up from there.

Testing with curl (SSE Streams)

The skill generates and runs these for you. Each stream closes when the playbook finishes.

# Set your URL once (skill does this automatically)
SHOWROOM=https://showroom-<guid>.apps.<cluster>.<domain>

# 1. Fresh validate โ€” expect โŒ
curl -sk -N $SHOWROOM/stream/validate/module-01

# 2. Solve
curl -sk -N $SHOWROOM/stream/solve/module-01

# 3. Validate after solve โ€” expect โœ…
curl -sk -N $SHOWROOM/stream/validate/module-01

# 4. Validate again (idempotency) โ€” still โœ…
curl -sk -N $SHOWROOM/stream/validate/module-01

How to read the output:


Lab Types

Lab TypeAgV configRunnerKey extravars
OCP Tenantconfig: namespacek8s sidecar pod, namespace-scoped SAk8s_kubeconfig, student_ns, student_user, guid
OCP Dedicatedconfig: openshift-workloadsk8s sidecar pod, cluster-admin SAk8s_kubeconfig, guid (student_ns empty)
RHEL VMconfig: cloud-vms-basePodman container on bastionSSH via /app/.ssh/config, host aliases: node, bastion

Key Patterns

PatternRule
Always pass kubeconfigPlain oc uses showroom SA (no access). Use oc --kubeconfig= in shell tasks or kubeconfig: "" in kubernetes.core modules.
k8s_exec commandMust be a string, not a list. A list makes Kubernetes treat the first item as the executable name.
NetworkPolicy bypassUse k8s_exec into the target pod and call localhost โ€” avoids cross-namespace HTTP blocks.
IdempotencySolve runs every time a student retries. Guard every create operation.
Async operationsTrigger and exit. Validate uses any() not max() to detect completion without new queued tasks blocking results.
Durable outcomesValidate persistent state (file exists, resource created) โ€” not transient state (branch name, pod restarts).
JSON in k8s_execParse via python3 -c "import json,sys..." โ€” Ansible's from_json fails when stdout has deprecation warnings alongside JSON.
regex_search | firstReturns None on no match. None | first crashes. Use separate shell tasks instead.
dev.yaml overrideCheck for content_git_repo_ref in dev.yaml โ€” it silently overrides common.yaml and clones the wrong branch into the showroom pod.