From b0346e00838e0f7fa1b93a6d84ae2e5a6b6d349b Mon Sep 17 00:00:00 2001 From: gziv Date: Mon, 11 May 2026 11:15:51 +0300 Subject: [PATCH 1/7] feat: add rh-developer-rhel-deploy evaluation submission --- .../rh-developer-rhel-deploy/CLAUDE.md | 67 ++ .../docs/builder-images.md | 308 ++++++++ .../docs/debugging-patterns.md | 478 ++++++++++++ .../docs/dynamic-validation.md | 259 +++++++ .../docs/human-in-the-loop.md | 98 +++ .../docs/image-selection-criteria.md | 221 ++++++ .../docs/prerequisites.md | 212 ++++++ .../docs/python-s2i-entrypoints.md | 70 ++ .../docs/rhel-deployment.md | 580 ++++++++++++++ .../docs/selinux-troubleshooting.md | 387 ++++++++++ .../rh-developer-rhel-deploy/instruction.md | 12 + .../rh-developer-rhel-deploy/mcps.json | 79 ++ .../rh-developer-rhel-deploy/metadata.yaml | 13 + .../skills/rhel-deploy/SKILL.md | 483 ++++++++++++ .../supportive/.mcp.json | 12 + .../mcp-servers/mock-openshift-mcp.py | 717 ++++++++++++++++++ .../mcp-servers/mock-rhel-host-mcp.py | 230 ++++++ .../templates/buildconfig.yaml.template | 38 + .../templates/deployment.yaml.template | 61 ++ .../templates/helm/Chart.yaml.template | 13 + .../helm/templates/NOTES.txt.template | 32 + .../helm/templates/_helpers.tpl.template | 60 ++ .../helm/templates/deployment.yaml.template | 61 ++ .../helm/templates/route.yaml.template | 24 + .../helm/templates/service.yaml.template | 15 + .../templates/helm/values.yaml.template | 67 ++ .../templates/imagestream.yaml.template | 13 + .../templates/route.yaml.template | 21 + .../templates/service.yaml.template | 20 + .../systemd/systemd-container-rootful.service | 27 + .../systemd-container-rootless.service | 27 + .../templates/systemd/systemd-native.service | 39 + .../tests/llm_judge.py | 108 +++ .../tests/test_outputs.py | 98 +++ 34 files changed, 4950 insertions(+) create mode 100644 submissions/rh-developer-rhel-deploy/CLAUDE.md create mode 100644 submissions/rh-developer-rhel-deploy/docs/builder-images.md create mode 100644 submissions/rh-developer-rhel-deploy/docs/debugging-patterns.md create mode 100644 submissions/rh-developer-rhel-deploy/docs/dynamic-validation.md create mode 100644 submissions/rh-developer-rhel-deploy/docs/human-in-the-loop.md create mode 100644 submissions/rh-developer-rhel-deploy/docs/image-selection-criteria.md create mode 100644 submissions/rh-developer-rhel-deploy/docs/prerequisites.md create mode 100644 submissions/rh-developer-rhel-deploy/docs/python-s2i-entrypoints.md create mode 100644 submissions/rh-developer-rhel-deploy/docs/rhel-deployment.md create mode 100644 submissions/rh-developer-rhel-deploy/docs/selinux-troubleshooting.md create mode 100644 submissions/rh-developer-rhel-deploy/instruction.md create mode 100644 submissions/rh-developer-rhel-deploy/mcps.json create mode 100644 submissions/rh-developer-rhel-deploy/metadata.yaml create mode 100644 submissions/rh-developer-rhel-deploy/skills/rhel-deploy/SKILL.md create mode 100644 submissions/rh-developer-rhel-deploy/supportive/.mcp.json create mode 100644 submissions/rh-developer-rhel-deploy/supportive/mcp-servers/mock-openshift-mcp.py create mode 100644 submissions/rh-developer-rhel-deploy/supportive/mcp-servers/mock-rhel-host-mcp.py create mode 100644 submissions/rh-developer-rhel-deploy/templates/buildconfig.yaml.template create mode 100644 submissions/rh-developer-rhel-deploy/templates/deployment.yaml.template create mode 100644 submissions/rh-developer-rhel-deploy/templates/helm/Chart.yaml.template create mode 100644 submissions/rh-developer-rhel-deploy/templates/helm/templates/NOTES.txt.template create mode 100644 submissions/rh-developer-rhel-deploy/templates/helm/templates/_helpers.tpl.template create mode 100644 submissions/rh-developer-rhel-deploy/templates/helm/templates/deployment.yaml.template create mode 100644 submissions/rh-developer-rhel-deploy/templates/helm/templates/route.yaml.template create mode 100644 submissions/rh-developer-rhel-deploy/templates/helm/templates/service.yaml.template create mode 100644 submissions/rh-developer-rhel-deploy/templates/helm/values.yaml.template create mode 100644 submissions/rh-developer-rhel-deploy/templates/imagestream.yaml.template create mode 100644 submissions/rh-developer-rhel-deploy/templates/route.yaml.template create mode 100644 submissions/rh-developer-rhel-deploy/templates/service.yaml.template create mode 100644 submissions/rh-developer-rhel-deploy/templates/systemd/systemd-container-rootful.service create mode 100644 submissions/rh-developer-rhel-deploy/templates/systemd/systemd-container-rootless.service create mode 100644 submissions/rh-developer-rhel-deploy/templates/systemd/systemd-native.service create mode 100644 submissions/rh-developer-rhel-deploy/tests/llm_judge.py create mode 100644 submissions/rh-developer-rhel-deploy/tests/test_outputs.py diff --git a/submissions/rh-developer-rhel-deploy/CLAUDE.md b/submissions/rh-developer-rhel-deploy/CLAUDE.md new file mode 100644 index 0000000..8a11178 --- /dev/null +++ b/submissions/rh-developer-rhel-deploy/CLAUDE.md @@ -0,0 +1,67 @@ +# rh-developer Plugin + +You are an application developer assistant for Red Hat platforms. You help users build, containerize, deploy, and troubleshoot applications on OpenShift clusters and standalone RHEL/Fedora/CentOS systems. + +## Skill-First Rule + +ALWAYS use the appropriate skill for developer tasks. Do NOT call MCP tools (openshift, podman, github, lightspeed-mcp) directly — skills handle error recovery, human-in-the-loop confirmations, and fallbacks automatically. + +To invoke a skill, use the Skill tool with the skill name (e.g., `/deploy`). + +## Intent Routing + +Match the user's request to the correct skill: + +| When the user asks about... | Use skill | +|---|---| +| Detect language, framework, analyze project, scan repo, identify runtime | `/detect-project` | +| Recommend builder image, S2I image, base image, image selection | `/recommend-image` | +| S2I build, source-to-image, BuildConfig, build container image | `/s2i-build` | +| Deploy to OpenShift, create Deployment, Service, Route, expose app | `/deploy` | +| Helm chart, Helm deploy, Helm install, Helm values, chart template | `/helm-deploy` | +| Deploy to RHEL, Fedora, CentOS, SSH deploy, systemd service, Podman on RHEL | `/rhel-deploy` | +| End-to-end deployment, containerize and deploy, full workflow, deploy from source | `/containerize-deploy` | +| Build failure, BuildConfig error, S2I error, build logs, failed build | `/debug-build` | +| Pod failure, CrashLoopBackOff, ImagePullBackOff, OOMKilled, Pending pod | `/debug-pod` | +| Container issue, Podman/Docker failure, local container debug, container crash | `/debug-container` | +| Network issue, DNS, Service connectivity, Route, NetworkPolicy, ingress | `/debug-network` | +| Pipeline failure, Tekton, PipelineRun, TaskRun error, pipeline logs | `/debug-pipeline` | +| RHEL issue, systemd, SELinux, firewall, journal logs, system service | `/debug-rhel` | +| Incident investigation, root cause analysis, triage alert, five whys, outage diagnosis, multi-resource issue | `/incident-triage` | +| Check tools, verify cluster access, validate environment, prerequisites | `/validate-environment` | + +If the request doesn't clearly match one skill, ask the user to clarify. For complex or multi-resource issues where the root cause is unclear, prefer `/incident-triage` over individual debug skills. + +## Skill Chaining + +Some workflows require multiple skills in sequence: + +- **Full app deployment (S2I)**: `/detect-project` -> `/recommend-image` (optional) -> `/s2i-build` -> `/deploy` +- **Helm deployment**: `/detect-project` -> `/helm-deploy` +- **RHEL deployment**: `/detect-project` -> `/rhel-deploy` +- **Unified workflow**: `/containerize-deploy` (orchestrates all above based on user selection) +- **Pre-flight check**: Run `/validate-environment` before any deployment skill +- **Build failure recovery**: `/debug-build` -> fix -> `/s2i-build` retry +- **Pod failure recovery**: `/debug-pod` or `/debug-network` -> fix -> `/deploy` retry +- **RHEL failure recovery**: `/debug-rhel` or `/debug-container` -> fix -> `/rhel-deploy` retry +- **Incident triage**: `/incident-triage` -> identifies root cause -> routes to `/debug-pod`, `/debug-network`, or `/deploy` for targeted fix + +After completing a skill, suggest relevant next-step skills to the user. + +## MCP Servers + +Five MCP servers are available. Skills manage these automatically — do not call their tools directly. + +- **openshift** (Required) — Kubernetes resource CRUD, pod logs, events, Helm operations. The reliable foundation. +- **observability** (Required) — Prometheus metric discovery, metadata, series, and PromQL queries. Used by `/incident-triage` for trend analysis and saturation detection. +- **podman** (Required) — Local container builds and image management via Podman. +- **github** (Optional) — Remote repository browsing and code analysis. Used by `/detect-project` for GitHub URLs. +- **lightspeed-mcp** (Optional) — CVE vulnerability data, advisor rules, RHEL lifecycle checks. Used by `/rhel-deploy` and `/debug-rhel`. + +## Global Rules + +1. **Never expose credentials** — do not display API keys, passwords, tokens, or secret values in output. Only report whether they exist. +2. **Confirm before creating resources** — always show the resource manifest (with credentials redacted) and wait for explicit user approval before creating, modifying, or deleting cluster or system resources. +3. **Never auto-delete** — destructive operations (delete Deployment, remove systemd service, delete BuildConfig) always require user confirmation with a data-loss warning. +4. **Report fallbacks transparently** — if a preferred tool fails and a fallback is used, briefly note it. +5. **Suggest next steps** — after completing a skill, suggest related skills the user might want to run next. diff --git a/submissions/rh-developer-rhel-deploy/docs/builder-images.md b/submissions/rh-developer-rhel-deploy/docs/builder-images.md new file mode 100644 index 0000000..6561c5c --- /dev/null +++ b/submissions/rh-developer-rhel-deploy/docs/builder-images.md @@ -0,0 +1,308 @@ +--- +title: S2I Builder Image Reference +category: containers +sources: + - title: Red Hat Container Catalog + url: https://catalog.redhat.com/software/containers/search + sections: UBI images, S2I builders + date_accessed: 2026-02-08 + - title: OpenShift Source-to-Image (S2I) + url: https://docs.openshift.com/container-platform/latest/openshift_images/using_images/using-s21-images.html + sections: S2I builder images, Language detection + date_accessed: 2026-02-08 + - title: Red Hat Universal Base Images + url: https://developers.redhat.com/products/rhel/ubi + sections: UBI9 images, Language runtimes + date_accessed: 2026-02-08 +--- + +# S2I Builder Image Reference + +Use this reference when recommending S2I builder images to users. + +> **Note:** Versions marked "Recommended" may change. Always verify with `skopeo inspect` before use. Prefer matching the project's version requirements over these defaults. + +For use-case-aware image selection, use the `/recommend-image` skill. + +--- + +## Dynamic Lookup and Verification + +**This reference may be outdated.** Always verify image availability before recommending. + +### Verify with Skopeo (Recommended) + +```bash +# Check if an image exists and get metadata +skopeo inspect docker://registry.access.redhat.com/ubi9/nodejs-20 + +# Get specific fields +skopeo inspect docker://registry.access.redhat.com/ubi9/nodejs-20 --format '{{.Created}}' +skopeo inspect docker://registry.access.redhat.com/ubi9/nodejs-20 --format '{{.Architecture}}' + +# List all available tags +skopeo list-tags docker://registry.access.redhat.com/ubi9/nodejs-20 +``` + +**If skopeo is not installed**, prompt the user: +``` +Install with: sudo dnf install skopeo (Fedora/RHEL) + sudo apt install skopeo (Ubuntu/Debian) + brew install skopeo (macOS) +``` + +### Check Security Status (Red Hat Security Data API) + +Query CVE information (no authentication required): + +```bash +# Check for critical CVEs affecting UBI9 +curl -s "https://access.redhat.com/hydra/rest/securitydata/cve.json?product=Red%20Hat%20Universal%20Base%20Image%209&severity=critical" | jq 'length' + +# Get CVE details +curl -s "https://access.redhat.com/hydra/rest/securitydata/cve.json?product=Red%20Hat%20Universal%20Base%20Image%209&severity=critical" | jq '.[] | {cve: .CVE, severity: .severity}' +``` + +### Verify with Red Hat Catalog API (Alternative) + +```bash +# Search for available Node.js images +curl -s "https://catalog.redhat.com/api/containers/v1/repositories?filter=repository=like=ubi9/nodejs" | jq '.data[].repository' + +# Search for available Python images +curl -s "https://catalog.redhat.com/api/containers/v1/repositories?filter=repository=like=ubi9/python" | jq '.data[].repository' +``` + +--- + +## Project Detection and Version Mapping + +### Extract Version from Project Files + +Before recommending an image, check the project's version requirements: + +| Project File | How to Extract Version | +|--------------|------------------------| +| `package.json` | `.engines.node` field | +| `requirements.txt` | `python_requires` or comments | +| `pyproject.toml` | `[project].requires-python` | +| `pom.xml` | `` or `` | +| `go.mod` | `go` directive (e.g., `go 1.21`) | +| `*.csproj` | `` (e.g., `net8.0`) | + +### Detect Language from Files + +| Indicator File(s) | Language | Framework | Version Source | +|-------------------|----------|-----------|----------------| +| `package.json` | Node.js | - | `.engines.node` | +| `package.json` + `next.config.js` | Node.js | Next.js | `.engines.node` | +| `package.json` + `angular.json` | Node.js | Angular | `.engines.node` | +| `pom.xml` | Java | Maven | `` or `` | +| `pom.xml` + quarkus dep | Java | Quarkus | `` (prefer 21+) | +| `pom.xml` + spring-boot dep | Java | Spring Boot | `` | +| `build.gradle` / `build.gradle.kts` | Java | Gradle | `sourceCompatibility` or `java.toolchain` | +| `requirements.txt` | Python | - | `python_requires` or shebang | +| `Pipfile` | Python | Pipenv | `[requires].python_version` | +| `pyproject.toml` | Python | Poetry/Modern | `[project].requires-python` | +| `go.mod` | Go | - | `go` directive line | +| `Gemfile` | Ruby | - | `ruby` directive or `.ruby-version` | +| `*.csproj` / `*.sln` | .NET | - | `` (e.g., net8.0 → 80) | +| `composer.json` | PHP | - | `require.php` field | +| `Cargo.toml` | Rust | - | Custom (no official S2I) | + +### Map Version to Image + +**Quick lookup pattern:** `ubi9/{language}-{version}` (e.g., `ubi9/nodejs-20`, `ubi9/python-311`) + +| Language | Version Mapping | Image Pattern | +|----------|-----------------|---------------| +| Node.js | 18.x → 18, 20.x → 20, 22.x → 22 | `ubi9/nodejs-{major}` | +| Python | 3.9 → 39, 3.11 → 311, 3.12 → 312 | `ubi9/python-{majmin}` | +| Java | 11, 17, 21 (use nearest LTS) | `ubi9/openjdk-{version}` | +| Go | 1.21 → 1.21, 1.22 → 1.22 | `ubi9/go-toolset:{version}` | +| Ruby | 3.1 → 31, 3.3 → 33 | `ubi9/ruby-{majmin}` | +| .NET | net6.0 → 60, net8.0 → 80 | `ubi9/dotnet-{version}` | +| PHP | 8.0 → 80, 8.1 → 81 | `ubi9/php-{majmin}` | + +### Verify and Fallback + +1. **Verify image exists**: `skopeo inspect docker://registry.access.redhat.com/ubi9/{image}` +2. **If version not found**: Use nearest available LTS version +3. **If no version in project**: Use current LTS (check catalog API) + +--- + +## Red Hat UBI-based Images + +### Node.js + +| Version | Full Image | Minimal Image | Use Case | +|---------|------------|---------------|----------| +| 18 LTS | `registry.access.redhat.com/ubi9/nodejs-18` | `registry.access.redhat.com/ubi9/nodejs-18-minimal` | Long-term support | +| 20 LTS | `registry.access.redhat.com/ubi9/nodejs-20` | `registry.access.redhat.com/ubi9/nodejs-20-minimal` | **Recommended** | +| 22 | `registry.access.redhat.com/ubi9/nodejs-22` | `registry.access.redhat.com/ubi9/nodejs-22-minimal` | Current | + +**Choose minimal for:** Production, security-focused, smaller image size +**Choose full for:** Development, native module compilation + +### Python + +| Version | Image | Notes | +|---------|-------|-------| +| 3.9 | `registry.access.redhat.com/ubi9/python-39` | | +| 3.11 | `registry.access.redhat.com/ubi9/python-311` | **Recommended** | +| 3.12 | `registry.access.redhat.com/ubi9/python-312` | Latest | + +### Java / OpenJDK + +| Version | Build Image | Runtime Image | Notes | +|---------|-------------|---------------|-------| +| 11 LTS | `registry.access.redhat.com/ubi8/openjdk-11` | `registry.access.redhat.com/ubi8/openjdk-11-runtime` | LTS | +| 17 LTS | `registry.access.redhat.com/ubi9/openjdk-17` | `registry.access.redhat.com/ubi9/openjdk-17-runtime` | **Recommended** | +| 21 LTS | `registry.access.redhat.com/ubi9/openjdk-21` | `registry.access.redhat.com/ubi9/openjdk-21-runtime` | Latest LTS | + +**Choose runtime for:** Production with pre-built JARs, smallest footprint +**Choose build for:** S2I builds, Maven/Gradle compilation needed + +### Go + +| Version | Image | Notes | +|---------|-------|-------| +| 1.20 | `registry.access.redhat.com/ubi9/go-toolset:1.20` | | +| 1.21 | `registry.access.redhat.com/ubi9/go-toolset:1.21` | **Recommended** | + +### Ruby + +| Version | Image | Notes | +|---------|-------|-------| +| 3.1 | `registry.access.redhat.com/ubi9/ruby-31` | | +| 3.3 | `registry.access.redhat.com/ubi9/ruby-33` | **Recommended** | + +### .NET + +| Version | Build Image | Runtime Image | Notes | +|---------|-------------|---------------|-------| +| 6.0 LTS | `registry.access.redhat.com/ubi8/dotnet-60` | `registry.access.redhat.com/ubi8/dotnet-60-runtime` | LTS | +| 7.0 | `registry.access.redhat.com/ubi8/dotnet-70` | `registry.access.redhat.com/ubi8/dotnet-70-runtime` | | +| 8.0 LTS | `registry.access.redhat.com/ubi9/dotnet-80` | `registry.access.redhat.com/ubi9/dotnet-80-runtime` | **Recommended** | + +**Choose runtime for:** Production with pre-built assemblies +**Choose build for:** S2I builds, dotnet build/publish needed + +### PHP + +| Version | Image | Notes | +|---------|-------|-------| +| 8.0 | `registry.access.redhat.com/ubi9/php-80` | | +| 8.1 | `registry.access.redhat.com/ubi9/php-81` | **Recommended** | + +### Perl + +| Version | Image | Notes | +|---------|-------|-------| +| 5.32 | `registry.access.redhat.com/ubi9/perl-532` | | + +--- + +## Image Variants and Use-Case Selection + +### Quick Use-Case Matrix + +| Use Case | Variant | Priority | Example | +|----------|---------|----------|---------| +| Production | Minimal/Runtime | Security, Size | `nodejs-20-minimal` | +| Development | Full | Tools, Debug | `nodejs-20` | +| Serverless | Minimal | Startup Time | `openjdk-21-runtime` | +| Edge/IoT | Minimal | Size | `nodejs-20-minimal` | + +### Image Variants + +| Variant | Description | Has Build Tools | Size | +|---------|-------------|-----------------|------| +| Full | Complete development environment | Yes | Largest | +| Minimal | Essential packages only | Limited | Medium | +| Runtime | Runtime only, no build tools | No | Smallest | + +**Availability by language:** + +| Language | Full | Minimal | Runtime | +|----------|------|---------|---------| +| Node.js | `nodejs-{ver}` | `nodejs-{ver}-minimal` | - | +| Python | `python-{ver}` | - | - | +| Java | `openjdk-{ver}` | - | `openjdk-{ver}-runtime` | +| Go | `go-toolset:{ver}` | - | (produces static binary) | +| .NET | `dotnet-{ver}` | - | `dotnet-{ver}-runtime` | +| Ruby | `ruby-{ver}` | - | - | +| PHP | `php-{ver}` | - | - | + +### When to Recommend Each Variant + +**Full variant:** +- User needs to compile native extensions +- Development/debugging environment +- CI/CD build stages + +**Minimal variant:** +- Production deployments +- Security-focused environments +- When size matters but some tools needed + +**Runtime variant:** +- Pre-compiled applications (JARs, .NET assemblies) +- Maximum security posture +- Smallest possible footprint + +--- + +## OpenShift Built-in ImageStreams + +These are often pre-configured in OpenShift clusters under the `openshift` namespace: + +| ImageStream | Usage | +|-------------|-------| +| `nodejs:20-ubi9` | Node.js 20 on UBI 9 | +| `python:3.11-ubi9` | Python 3.11 on UBI 9 | +| `openjdk-17-ubi8` | Java 17 on UBI 8 | +| `ruby:3.1-ubi9` | Ruby 3.1 on UBI 9 | +| `php:8.0-ubi9` | PHP 8.0 on UBI 9 | + +When using OpenShift ImageStreams, reference them as: +```yaml +from: + kind: ImageStreamTag + namespace: openshift + name: nodejs:20-ubi9 +``` + +--- + +## Framework-Specific Recommendations + +### Quarkus (Java) +- **Native build**: `quay.io/quarkus/ubi-quarkus-mandrel-builder-image:jdk-21` +- **JVM build**: `registry.access.redhat.com/ubi9/openjdk-21` + +### Spring Boot (Java) +- Use: `registry.access.redhat.com/ubi9/openjdk-17` or `openjdk-21` +- Ensure `spring-boot-maven-plugin` is configured for packaging + +### Next.js / React (Node.js) +- Use: `registry.access.redhat.com/ubi9/nodejs-20` +- Ensure build outputs to `build/` or `.next/` + +### Django / Flask (Python) +- Use: `registry.access.redhat.com/ubi9/python-311` +- Ensure `requirements.txt` or `Pipfile` exists at root + +### Express.js (Node.js) +- Use: `registry.access.redhat.com/ubi9/nodejs-18` or higher +- Ensure `npm start` script is defined in `package.json` + +--- + +## Python S2I Entry Point Requirements + +**Quick reference:** +- Default entry point: `app.py` (works without configuration) +- Custom entry points require: `gunicorn` + `APP_MODULE` environment variable +- Format: `APP_MODULE=module:variable` (e.g., `main:app`) diff --git a/submissions/rh-developer-rhel-deploy/docs/debugging-patterns.md b/submissions/rh-developer-rhel-deploy/docs/debugging-patterns.md new file mode 100644 index 0000000..2863d55 --- /dev/null +++ b/submissions/rh-developer-rhel-deploy/docs/debugging-patterns.md @@ -0,0 +1,478 @@ +--- +title: Debugging Patterns +category: references +sources: + - title: Kubernetes Debugging Pods + url: https://kubernetes.io/docs/tasks/debug/debug-application/debug-pods/ + sections: Debugging Pods, Common Errors + date_accessed: 2026-02-16 + - title: OpenShift Troubleshooting Guide + url: https://docs.openshift.com/container-platform/latest/support/troubleshooting/troubleshooting-operator-issues.html + sections: Pod issues, Build issues + date_accessed: 2026-02-16 + - title: OpenShift Pipelines Troubleshooting + url: https://docs.openshift.com/pipelines/latest/about/about-openshift-pipelines.html + sections: Troubleshooting, PipelineRun status, TaskRun status + date_accessed: 2026-02-25 + - title: Podman Troubleshooting + url: https://github.com/containers/podman/blob/main/troubleshooting.md + sections: Common Issues + date_accessed: 2026-02-16 +--- + +# Debugging Patterns + +This document provides common error patterns, exit codes, and troubleshooting decision trees for the debugging skills. + +## Exit Code Reference + +### Container/Process Exit Codes + +| Exit Code | Signal | Meaning | Common Cause | +|-----------|--------|---------|--------------| +| 0 | - | Success | Normal termination | +| 1 | - | General error | Application error, unhandled exception | +| 2 | - | Misuse of shell | Invalid arguments, syntax error | +| 126 | - | Permission denied | Cannot execute command | +| 127 | - | Command not found | Binary/script missing in PATH | +| 128 | - | Invalid exit argument | Exit called with non-integer | +| 128+N | Signal N | Killed by signal | See signal table below | +| 137 | SIGKILL (9) | Force killed | OOM kill, manual kill, timeout | +| 139 | SIGSEGV (11) | Segmentation fault | Memory corruption, null pointer | +| 143 | SIGTERM (15) | Terminated | Graceful shutdown request | + +### Signal Reference (128+N) + +| Signal | Number | Exit Code | Typical Cause | +|--------|--------|-----------|---------------| +| SIGHUP | 1 | 129 | Terminal closed | +| SIGINT | 2 | 130 | Ctrl+C | +| SIGQUIT | 3 | 131 | Ctrl+\ | +| SIGKILL | 9 | 137 | OOM, forced termination | +| SIGSEGV | 11 | 139 | Segmentation fault | +| SIGTERM | 15 | 143 | Graceful stop request | + +## Pod Failure Patterns + +### CrashLoopBackOff + +**Symptom:** Pod repeatedly crashes and restarts + +**Diagnosis Flow:** +``` +CrashLoopBackOff +├─ Check exit code +│ ├─ 0 → Application exits normally (missing loop/server?) +│ ├─ 1 → Application error (check logs) +│ ├─ 127 → Command not found (check entrypoint) +│ └─ 137 → OOM killed (check memory limits) +├─ Check logs (current + previous) +│ ├─ Import errors → Missing dependencies +│ ├─ Connection errors → External service down +│ └─ Config errors → Missing env vars/secrets +└─ Check events + └─ FailedMount → Missing secrets/configmaps +``` + +**Common Causes:** +1. Application crashes on startup (dependency errors) +2. Memory limit too low (OOMKilled) +3. Missing environment variables or secrets +4. Database/service connection failures +5. Health probe failing immediately + +### ImagePullBackOff + +**Symptom:** Cannot pull container image + +**Diagnosis Flow:** +``` +ImagePullBackOff +├─ Check event message +│ ├─ "unauthorized" → Registry authentication +│ │ └─ Check imagePullSecrets +│ ├─ "not found" → Wrong image name/tag +│ │ └─ Verify image exists in registry +│ ├─ "timeout" → Network/registry issue +│ │ └─ Check cluster network egress +│ └─ "manifest unknown" → Tag doesn't exist +│ └─ Verify tag in registry +└─ Check image reference + ├─ Missing registry prefix? + ├─ Typo in image name? + └─ Tag exists? +``` + +**Common Causes:** +1. Private registry without imagePullSecret +2. Image tag doesn't exist +3. Registry URL typo +4. Network policy blocking egress +5. Registry rate limiting + +### Pending Pod + +**Symptom:** Pod stuck in Pending state + +**Diagnosis Flow:** +``` +Pending +├─ Check events +│ ├─ "FailedScheduling" +│ │ ├─ "Insufficient cpu/memory" → Scale cluster or reduce requests +│ │ ├─ "node selector" → No matching nodes +│ │ ├─ "taints" → Need tolerations +│ │ └─ "PVC not bound" → Storage issue +│ └─ No events → Check resourceQuota +└─ Check node status + └─ All nodes NotReady? → Node issue +``` + +**Common Causes:** +1. Insufficient cluster resources +2. Node selector doesn't match any nodes +3. PersistentVolumeClaim not bound +4. Resource quota exceeded +5. Affinity/anti-affinity rules too strict + +### OOMKilled + +**Symptom:** Container terminated with exit code 137 + +**Diagnosis Flow:** +``` +OOMKilled (exit 137) +├─ Check container state +│ └─ OOMKilled: true → Memory exhaustion confirmed +├─ Compare memory usage vs limit +│ ├─ Limit too low → Increase memory limit +│ └─ Memory leak → Profile application +└─ Check for: + ├─ Java → Heap size (-Xmx) exceeds limit + ├─ Node.js → --max-old-space-size too high + └─ Python → Large data structures in memory +``` + +**Common Causes:** +1. Memory limit set too low for application +2. Memory leak in application +3. Java heap size exceeds container limit +4. Processing large files/datasets in memory + +## Build Failure Patterns + +### S2I Build Phases + +| Phase | What Happens | Common Failures | +|-------|--------------|-----------------| +| **fetch-source** | Clone git repository | Auth failure, repo not found | +| **pull-builder** | Pull S2I builder image | Image not found, auth | +| **assemble** | Run S2I assemble script | Dependency install, build errors | +| **commit** | Create image layer | Disk space | +| **push** | Push to internal registry | Auth, quota | + +### Assemble Phase Failures + +**Node.js:** +``` +npm ERR! 404 Not Found +└─ Package doesn't exist in registry + → Check package.json for typos + +npm ERR! code ERESOLVE +└─ Dependency conflict + → Run npm install --legacy-peer-deps + +npm ERR! code ENOENT +└─ File not found + → Check paths in package.json +``` + +**Python:** +``` +ERROR: Could not find a version that satisfies the requirement +└─ Package not found + → Check requirements.txt spelling + +ModuleNotFoundError: No module named 'X' +└─ APP_MODULE misconfigured + → See docs/python-s2i-entrypoints.md + +gunicorn: command not found +└─ gunicorn not in requirements + → Add gunicorn to requirements.txt +``` + +**Java:** +``` +[ERROR] Failed to execute goal +└─ Maven/Gradle build failure + → Check pom.xml or build.gradle + +java.lang.OutOfMemoryError: Java heap space +└─ Build needs more memory + → Add MAVEN_OPTS=-Xmx512m +``` + +## Pipeline/Tekton Failure Patterns + +### PipelineRun Failure Decision Tree + +``` +PipelineRun Failed +├─ Check PipelineRun status conditions +│ ├─ "PipelineRunTimeout" → Increase spec.timeouts.pipeline +│ ├─ "CouldntGetPipeline" → Pipeline reference invalid, check name/namespace +│ ├─ "PipelineRunCancelled" → Check if timeout or manual cancellation +│ └─ "Failed" → Check which TaskRun failed (see below) +├─ Check failed TaskRun +│ ├─ Step failure (non-zero exit) +│ │ ├─ git-clone step → Auth/URL issue (check SA secrets) +│ │ ├─ build step → Compilation/dependency error +│ │ ├─ push step → Registry auth (check SA dockerconfigjson secret) +│ │ └─ test step → Test failures +│ ├─ Pod scheduling failure → Resource constraints (FailedScheduling event) +│ ├─ Workspace issue → PVC not bound or permission denied +│ └─ Step image pull failure → ImagePullBackOff on step container +└─ Pipeline stuck (Running too long) + ├─ TaskRun pending → Pod can't be scheduled + ├─ Step running indefinitely → Check logs for hang/deadlock + └─ Custom task waiting → Check custom task controller +``` + +### TaskRun Failure Analysis + +``` +TaskRun Failed +├─ Pod not created → Check ServiceAccount exists, resource quotas +├─ Pod pending → Scheduling issue (see Pod Failure Patterns) +├─ Pod terminated → Check step statuses +│ ├─ Exit 1 → Script/application error (check step logs) +│ ├─ Exit 125-127 → Entrypoint/command issue in step image +│ └─ Exit 137 → OOM killed (increase step resources) +└─ Workspace binding failure + ├─ PVC not found → Create PVC or fix workspace binding + ├─ RWO blocks parallel tasks → Use RWX or separate workspaces + └─ Permission denied → Check fsGroup, runAsUser in pod security context +``` + +### Common Tekton Error Messages + +| Error Message | Fix | +|--------------|-----| +| `task "X" not found` | Verify Task name, kind (Task vs ClusterTask), namespace | +| `could not read Username for...` | Add git-credentials secret (annotated with `tekton.dev/git-0`) to ServiceAccount | +| `unauthorized: access denied` (push) | Add dockerconfigjson secret (annotated with `tekton.dev/docker-0`) to ServiceAccount | +| `persistentvolumeclaim "X" not found` | Create PVC or change workspace binding to emptyDir | +| `exceeded timeout` | Increase timeouts in PipelineRun spec (`spec.timeouts.pipeline` / `spec.timeouts.tasks`) | +| `missing required parameter "X"` | Add parameter value to PipelineRun spec | +| `couldn't find remote ref` | Fix git `revision` parameter (branch/tag name) | +| `unable to open Containerfile/Dockerfile` | Fix `DOCKERFILE` param path relative to workspace root | + +## Network Troubleshooting + +### Service Has No Endpoints + +**Diagnosis Flow:** +``` +No endpoints +├─ Check service selector +│ └─ Compare with pod labels +│ ├─ Labels don't match → Fix selector or pod labels +│ └─ Labels match → Check pod readiness +├─ Check pod status +│ ├─ Pods not running → Debug pods first +│ └─ Pods running but not ready → Check readiness probe +└─ Check readiness probe + ├─ HTTP probe failing → Application not listening + └─ TCP probe failing → Wrong port +``` + +### Route Returning 503 + +**Diagnosis Flow:** +``` +503 Service Unavailable +├─ Check endpoints +│ └─ No endpoints → Pods not ready +├─ Check backend pods +│ ├─ All pods failing readiness → Application issue +│ └─ Some pods ready → Load balancer issue +└─ Check route configuration + └─ Wrong service or port → Fix route spec +``` + +### Connection Refused + +**Diagnosis Flow:** +``` +Connection refused +├─ Is service created? → oc get svc +├─ Does service have endpoints? → oc get endpoints +├─ Is pod running? → oc get pods +├─ Is application listening? → Check container port +└─ Is port correct? → Compare service port vs container port +``` + +## RHEL System Patterns + +### systemd Service Failures + +| Exit Code | Meaning | Common Fix | +|-----------|---------|------------| +| 1 | General error | Check application logs | +| 126 | Permission | Check ExecStart permissions | +| 127 | Not found | Check binary path in ExecStart | +| 203 | EXEC | Wrong architecture or format | +| 217 | USER | Service user doesn't exist | + +### SELinux Denial Patterns + +| Denial Type | Example | Typical Fix | +|-------------|---------|-------------| +| Port binding | `httpd_t` bind `port_t` | `semanage port -a -t http_port_t -p tcp [port]` | +| File read | `httpd_t` read `user_home_t` | `semanage fcontext` + `restorecon` | +| Network connect | `httpd_t` connect | `setsebool -P httpd_can_network_connect on` | +| Container | `container_t` manage | `setsebool -P container_manage_cgroup on` | + +See [selinux-troubleshooting.md](selinux-troubleshooting.md) for detailed SELinux guidance. + +## Troubleshooting Decision Tree + +### Application Not Accessible + +``` +Cannot access application +├─ Internal (from cluster)? +│ ├─ Yes, works internally → Route/Ingress issue +│ │ ├─ Check route admitted +│ │ ├─ Check route host/path +│ │ └─ Check TLS configuration +│ └─ No, fails internally too → Service/Pod issue +│ ├─ Check service endpoints +│ ├─ Check pod status +│ └─ Check pod readiness +└─ Neither works? + └─ Debug pod first (/debug-pod) +``` + +### Build Keeps Failing + +``` +Build failures +├─ Which phase? +│ ├─ fetch-source → Git access issue +│ │ ├─ Check source secret +│ │ └─ Verify git URL +│ ├─ pull-builder → Builder image issue +│ │ ├─ Check image reference +│ │ └─ Import ImageStream +│ ├─ assemble → Build script issue +│ │ ├─ Check dependencies +│ │ └─ Check language-specific config +│ └─ push → Registry issue +│ └─ Check push secret +└─ Same failure pattern? + └─ Compare with last successful build +``` + +### Pipeline Keeps Failing + +``` +Pipeline failures +├─ Same task always fails? +│ ├─ git-clone → Check ServiceAccount secrets, git URL, revision +│ ├─ build step → Check source code, Containerfile path, build context +│ └─ push step → Check ServiceAccount imagePullSecrets, registry URL +├─ Different tasks fail? +│ ├─ Resource exhaustion → Reduce parallel tasks or increase quotas +│ └─ Workspace contention → Use RWX PVC or separate workspaces +├─ Pipeline hangs? +│ ├─ TaskRun pending → Pod can't be scheduled +│ └─ Step running indefinitely → Check step logs +└─ Pipeline never triggers? + ├─ EventListener pod not running → Check EL deployment/logs + ├─ Webhook misconfigured → Verify webhook URL and secret + └─ TriggerBinding wrong → Check CEL expression param extraction +``` + +## Quick Reference Commands + +### OpenShift Debugging + +```bash +# Pod status and events +oc describe pod [pod-name] + +# Pod logs (current) +oc logs [pod-name] + +# Pod logs (previous container) +oc logs [pod-name] --previous + +# All events in namespace +oc get events --sort-by='.lastTimestamp' + +# Check endpoints +oc get endpoints [service-name] + +# Build logs +oc logs build/[build-name] +``` + +### Pipeline/Tekton Debugging + +```bash +# List PipelineRuns (oldest first) +oc get pipelinerun --sort-by='.metadata.creationTimestamp' + +# Get PipelineRun details +oc get pipelinerun [name] -o yaml + +# List TaskRuns for a PipelineRun +oc get taskrun -l tekton.dev/pipelineRun=[pipelinerun-name] + +# Get TaskRun pod logs for a specific step +oc logs [taskrun-name]-pod -c step-[step-name] + +# Get events for pipeline resources +oc get events --field-selector involvedObject.kind=PipelineRun + +# Describe EventListener +oc get eventlistener [name] -o yaml +``` + +### RHEL Debugging + +```bash +# Service status +systemctl status [service] + +# Journal logs +journalctl -u [service] -n 100 + +# SELinux denials +ausearch -m AVC -ts recent + +# Firewall rules +firewall-cmd --list-all + +# SELinux context +ls -lZ [path] +``` + +### Container Debugging + +```bash +# List all containers +podman ps -a + +# Container inspect +podman inspect [container] + +# Container logs +podman logs [container] + +# Run interactively for debugging +podman run -it --entrypoint /bin/sh [image] +``` diff --git a/submissions/rh-developer-rhel-deploy/docs/dynamic-validation.md b/submissions/rh-developer-rhel-deploy/docs/dynamic-validation.md new file mode 100644 index 0000000..a027f0c --- /dev/null +++ b/submissions/rh-developer-rhel-deploy/docs/dynamic-validation.md @@ -0,0 +1,259 @@ +--- +title: Dynamic Image Validation Reference +category: containers +sources: + - title: Skopeo Documentation + url: https://github.com/containers/skopeo + sections: Inspecting images, Copying images + date_accessed: 2026-02-08 + - title: Red Hat Security Data API + url: https://access.redhat.com/documentation/en-us/red_hat_security_data_api/1.0 + sections: CVE queries, Product filtering + date_accessed: 2026-02-08 +--- + +# Dynamic Image Validation Reference + +This document provides detailed patterns for validating container images using Skopeo and the Red Hat Security Data API. + +## Skopeo Commands + +Skopeo inspects container images without downloading them, providing real-time metadata. + +### Prerequisites + +**Check if skopeo is installed:** +```bash +which skopeo +# or +skopeo --version +``` + +**Installation:** +| OS | Command | +|----|---------| +| Fedora/RHEL/CentOS | `sudo dnf install skopeo` | +| Ubuntu/Debian | `sudo apt install skopeo` | +| macOS (Homebrew) | `brew install skopeo` | + +### Basic Inspection + +```bash +# Inspect an image (full JSON output) +skopeo inspect docker://registry.access.redhat.com/ubi9/nodejs-20 + +# The docker:// transport is OCI-standard and works with all registries +# (Docker Hub, Red Hat, Quay, Podman registries, etc.) +``` + +### Extracting Specific Fields + +```bash +# Get creation date +skopeo inspect docker://registry.access.redhat.com/ubi9/nodejs-20 --format '{{.Created}}' + +# Get architecture +skopeo inspect docker://registry.access.redhat.com/ubi9/nodejs-20 --format '{{.Architecture}}' + +# Get all labels +skopeo inspect docker://registry.access.redhat.com/ubi9/nodejs-20 --format '{{.Labels}}' + +# Get specific label (e.g., version) +skopeo inspect docker://registry.access.redhat.com/ubi9/nodejs-20 --format '{{index .Labels "version"}}' + +# Get layer count +skopeo inspect docker://registry.access.redhat.com/ubi9/nodejs-20 --format '{{len .Layers}}' +``` + +### Listing Available Tags + +```bash +# List all tags for an image +skopeo list-tags docker://registry.access.redhat.com/ubi9/nodejs-20 + +# Output includes all available versions/tags +``` + +### Image Transport Options + +```bash +# Remote registry (most common) +skopeo inspect docker://registry.access.redhat.com/ubi9/nodejs-20 + +# Local Podman storage +skopeo inspect containers-storage:localhost/myimage:latest + +# OCI layout directory +skopeo inspect oci:/path/to/oci-layout:tag + +# Docker archive +skopeo inspect docker-archive:/path/to/image.tar +``` + +### Useful Metadata Fields + +| Field | Description | Use Case | +|-------|-------------|----------| +| `Created` | Image build timestamp | Freshness indicator | +| `Architecture` | CPU architecture | Verify ARM64/x86_64 support | +| `Os` | Operating system | Should be "linux" for UBI | +| `Labels` | Image labels (version, maintainer, etc.) | Verify language version | +| `Layers` | Layer digests | Calculate approximate size | +| `Digest` | Immutable image hash | Pin exact version | + +### Error Handling + +**Image not found:** +``` +Error: Error reading manifest: ... 404 Not Found +``` +→ Image does not exist at specified tag + +**Authentication required:** +``` +Error: Error reading manifest: unauthorized +``` +→ Private registry, need `skopeo login` first + +**Network error:** +``` +Error: Error initializing source: pinging container registry +``` +→ Network connectivity issue + +--- + +## Red Hat Security Data API + +The Security Data API provides CVE information without authentication. + +### Base Endpoint + +``` +https://access.redhat.com/hydra/rest/securitydata/ +``` + +### Query CVEs + +```bash +# Get all CVEs for UBI 9 (may return many results) +curl -s "https://access.redhat.com/hydra/rest/securitydata/cve.json?product=Red%20Hat%20Universal%20Base%20Image%209" + +# Filter by severity (critical, important, moderate, low) +curl -s "https://access.redhat.com/hydra/rest/securitydata/cve.json?product=Red%20Hat%20Universal%20Base%20Image%209&severity=critical" + +# Filter by date (CVEs after a specific date) +curl -s "https://access.redhat.com/hydra/rest/securitydata/cve.json?product=Red%20Hat%20Universal%20Base%20Image%209&after=2025-01-01" + +# Count critical CVEs +curl -s "https://access.redhat.com/hydra/rest/securitydata/cve.json?product=Red%20Hat%20Universal%20Base%20Image%209&severity=critical" | jq 'length' +``` + +### Product Names for Queries + +| Image Base | Product Name (URL-encoded) | +|------------|---------------------------| +| UBI 9 | `Red%20Hat%20Universal%20Base%20Image%209` | +| UBI 8 | `Red%20Hat%20Universal%20Base%20Image%208` | +| RHEL 9 | `Red%20Hat%20Enterprise%20Linux%209` | +| RHEL 8 | `Red%20Hat%20Enterprise%20Linux%208` | + +### Response Fields + +Each CVE object contains: + +| Field | Description | +|-------|-------------| +| `CVE` | CVE identifier (e.g., CVE-2024-1234) | +| `severity` | critical, important, moderate, low | +| `public_date` | When CVE was disclosed | +| `advisories` | Related Red Hat advisories | +| `bugzilla` | Bugzilla tracking URL | +| `affected_packages` | Packages affected by CVE | + +### Parsing Examples + +```bash +# Get CVE IDs and severities +curl -s "https://access.redhat.com/hydra/rest/securitydata/cve.json?product=Red%20Hat%20Universal%20Base%20Image%209&severity=critical" | jq '.[] | {cve: .CVE, severity: .severity}' + +# Get most recent CVE date +curl -s "https://access.redhat.com/hydra/rest/securitydata/cve.json?product=Red%20Hat%20Universal%20Base%20Image%209&severity=critical" | jq '.[0].public_date' + +# Check if any critical CVEs exist +CRITICAL_COUNT=$(curl -s "https://access.redhat.com/hydra/rest/securitydata/cve.json?product=Red%20Hat%20Universal%20Base%20Image%209&severity=critical" | jq 'length') +if [ "$CRITICAL_COUNT" -gt 0 ]; then + echo "Warning: $CRITICAL_COUNT critical CVEs found" +fi +``` + +--- + +## Validation Workflow + +### Complete Validation Sequence + +``` +1. Check if skopeo is installed + ├── Yes → Continue to step 2 + └── No → Prompt user to install, offer to continue with static data + +2. For each candidate image: + a. Run: skopeo inspect docker://registry.access.redhat.com/ubi9/[image] + b. If fails → Remove from candidates, try next + c. If succeeds → Extract: Created, Architecture, Labels + +3. Query Security Data API for UBI base: + a. Run: curl CVE query for critical severity + b. Parse count of critical CVEs + c. If count > 0 → Add warning to recommendation + +4. Compile results: + - Image metadata (from skopeo) + - Security status (from API) + - Static scoring data (from reference tables) + +5. Present recommendation with sources indicated +``` + +### Fallback Behavior + +| Scenario | Action | +|----------|--------| +| Skopeo not installed | Prompt installation, offer static-only mode | +| Skopeo command fails | Note "unable to verify", use static data | +| Security API unavailable | Note "security not verified", proceed | +| Image not found | Remove from candidates, suggest alternatives | +| Network offline | Use static data only, note limitations | + +--- + +## Integration with Recommendation Output + +### When Dynamic Data Available + +```markdown +| Property | Value | Source | +|----------|-------|--------| +| Size | 147 MB | Skopeo | +| Built | 2026-01-28 | Skopeo | +| Architecture | amd64, arm64 | Skopeo | + +**Security Status:** No critical CVEs +- Last checked: 2026-02-03 +- Source: Red Hat Security Data API +``` + +### When Dynamic Data Unavailable + +```markdown +| Property | Value | Source | +|----------|-------|--------| +| Size | ~150 MB (estimate) | Static | +| Built | Unknown | - | +| Architecture | Assumed amd64 | Static | + +**Security Status:** Not verified (warning) +- Skopeo not installed - install for accurate metadata +- Run: `sudo dnf install skopeo` +``` diff --git a/submissions/rh-developer-rhel-deploy/docs/human-in-the-loop.md b/submissions/rh-developer-rhel-deploy/docs/human-in-the-loop.md new file mode 100644 index 0000000..696fccf --- /dev/null +++ b/submissions/rh-developer-rhel-deploy/docs/human-in-the-loop.md @@ -0,0 +1,98 @@ +# Human-in-the-Loop Requirements + +This document defines mandatory checkpoint behavior for all rh-developer skills. + +## Critical Requirements + +**IMPORTANT:** All skills require explicit user confirmation at each step. You MUST: + +1. **Wait for user confirmation** before executing any actions +2. **Do NOT proceed** to the next step until the user explicitly approves +3. **Present options clearly** (yes/no/modify) and wait for response +4. **Never auto-execute** resource creation, builds, or deployments +5. **Never skip configuration questions** even if user seems to know what they want + +If the user says "no" or wants modifications, address their concerns before proceeding. + +## Anti-Patterns to Avoid + +**CRITICAL - DO NOT DO THIS:** + +| Anti-Pattern | Why It's Wrong | +|--------------|----------------| +| User says "yes do X to namespace Y" → Skip config questions | Strategy ≠ Configuration. User chose WHAT, not HOW | +| User seems experienced → Assume they've considered all options | Even experts benefit from checklists | +| User provides multiple answers at once → Skip individual confirmations | Each checkpoint exists for a reason | +| User is in a hurry → Rush through phases | Speed causes mistakes in production | + +## When User Provides Multiple Answers + +If user says: "yes do helm deployment to test-app namespace" + +**DO NOT** skip phases. Instead: + +1. Acknowledge: "Great, you've chosen Helm strategy and test-app namespace." +2. Continue: "Let me confirm the configuration details..." +3. Still ask: Environment type, config approach, resources, etc. +4. Get explicit confirmation for each phase + +**The user specifying WHAT to deploy does not mean they've decided HOW to configure it.** + +## Standard Checkpoint Language + +Use this exact pattern after EVERY step/phase: + +```markdown +**WAIT for user confirmation before proceeding.** Do NOT continue to the next phase until user explicitly confirms. + +- If user says "yes" → Proceed to next phase +- If user says "no" → Ask what they would like to change +- If user says "modify" → Update configuration and show again for confirmation +- If user gives multiple answers at once → Still confirm each remaining checkpoint individually +``` + +## Mandatory Configuration Questions + +Before ANY resource creation, these questions should be asked: + +| Question | Why It Matters | +|----------|----------------| +| Environment type (dev/staging/prod) | Affects image tags, resources, replicas | +| Runtime vs build-time config | Affects flexibility and rebuild frequency | +| Resource limits | Prevents OOM, ensures fair scheduling | +| Replicas | Affects availability and cost | + +## Include in Your Skill + +Add this section after Prerequisites in your SKILL.md: + +```markdown +## Critical: Human-in-the-Loop Requirements + +See [Human-in-the-Loop Requirements](../docs/human-in-the-loop.md) for mandatory checkpoint behavior. + +**Key Rules:** +1. WAIT for explicit user confirmation at each phase +2. Never skip configuration questions, even if user specifies strategy upfront +3. Strategy choice ≠ Configuration approval +``` + +## Phase Execution Rules + +**MANDATORY:** Execute phases in order. Each phase MUST: + +1. Display the phase information to the user +2. Ask the specific question for that phase +3. Wait for user response +4. Only then proceed to next phase + +**Even if user provides information for multiple phases at once:** +- Acknowledge what they said +- But still display each phase's confirmation prompt +- Get explicit "yes" for each phase before executing + +Example: +- User: "yes do helm to test-app namespace" +- AI: "Great, you've chosen Helm strategy and test-app namespace. Let me confirm the configuration details..." +- [Still show Configuration Review phase] +- [Still ask environment type, config approach, etc.] diff --git a/submissions/rh-developer-rhel-deploy/docs/image-selection-criteria.md b/submissions/rh-developer-rhel-deploy/docs/image-selection-criteria.md new file mode 100644 index 0000000..184b7f5 --- /dev/null +++ b/submissions/rh-developer-rhel-deploy/docs/image-selection-criteria.md @@ -0,0 +1,221 @@ +--- +title: Image Selection Criteria Reference +category: containers +sources: + - title: Red Hat Container Best Practices + url: https://developers.redhat.com/articles/2023/02/14/best-practices-building-images-pass-red-hat-container-certification + sections: Image sizing, Security considerations + date_accessed: 2026-02-08 + - title: OpenShift Image Guidelines + url: https://docs.openshift.com/container-platform/latest/openshift_images/create-images.html + sections: Image creation, Optimization + date_accessed: 2026-02-08 +--- + +# Image Selection Criteria Reference + +This document provides detailed criteria for selecting the optimal container image based on use case requirements. + +## Scoring Matrix + +Use this matrix to score image options based on user requirements. + +### Criteria Weights by Environment + +| Criteria | Production | Development | Edge/IoT | Serverless | +|----------|------------|-------------|----------|------------| +| Image Size | 3 | 1 | 5 | 4 | +| Security Posture | 5 | 2 | 4 | 3 | +| Build Tools | 1 | 5 | 1 | 1 | +| Startup Time | 3 | 1 | 3 | 5 | +| LTS Status | 5 | 2 | 4 | 3 | +| Debug Tools | 1 | 5 | 1 | 1 | + +**Scale:** 1 (low importance) to 5 (high importance) + +### Image Variant Scores + +| Variant | Size | Security | Build Tools | Startup | Debug | +|---------|------|----------|-------------|---------|-------| +| Full | 2 | 2 | 5 | 2 | 5 | +| Minimal | 4 | 4 | 2 | 4 | 2 | +| Runtime | 5 | 5 | 1 | 5 | 1 | + +**Scale:** 1 (poor) to 5 (excellent) + +## Image Size Reference + +Approximate compressed image sizes: + +### Node.js +| Image | Size | +|-------|------| +| `ubi9/nodejs-20` | ~250MB | +| `ubi9/nodejs-20-minimal` | ~150MB | + +### Python +| Image | Size | +|-------|------| +| `ubi9/python-311` | ~280MB | + +### Java +| Image | Size | +|-------|------| +| `ubi9/openjdk-17` | ~400MB | +| `ubi9/openjdk-17-runtime` | ~200MB | + +### Go +| Image | Size | +|-------|------| +| `ubi9/go-toolset:1.21` | ~500MB | +| Final binary | ~10-50MB | + +### .NET +| Image | Size | +|-------|------| +| `ubi9/dotnet-80` | ~350MB | +| `ubi9/dotnet-80-runtime` | ~150MB | + +## LTS Support Timeline + +### Node.js +| Version | Status | End of Life | +|---------|--------|-------------| +| 18 LTS | Active | April 2025 | +| 20 LTS | Active | April 2026 | +| 22 LTS | Active | April 2027 | + +### Python +| Version | Status | End of Life | +|---------|--------|-------------| +| 3.9 | Security | October 2025 | +| 3.11 | Active | October 2027 | +| 3.12 | Active | October 2028 | + +### Java (OpenJDK) +| Version | Status | Extended Support | +|---------|--------|------------------| +| 11 LTS | Active | Red Hat until 2027 | +| 17 LTS | Active | Red Hat until 2029 | +| 21 LTS | Active | Red Hat until 2031 | + +### .NET +| Version | Status | End of Life | +|---------|--------|-------------| +| 6.0 LTS | Active | November 2024 | +| 8.0 LTS | Active | November 2026 | + +## Security Considerations + +### Minimal Images - When to Use +- Fewer installed packages = smaller attack surface +- Recommended for production workloads +- May lack debugging tools when issues occur + +### Full Images - When to Use +- Include development tools (gcc, make, etc.) +- Needed for native extensions (Python C extensions, Node native modules) +- Better for development and debugging + +### Runtime Images - When to Use +- No build tools at all +- Smallest possible footprint +- Requires pre-compiled application (JAR, static binary) + +## Framework-Specific Considerations + +### Quarkus (Java) +**For JVM mode:** +- Use `ubi9/openjdk-21` for build +- Use `ubi9/openjdk-21-runtime` for production + +**For Native mode:** +- Build: `quay.io/quarkus/ubi-quarkus-mandrel-builder-image:jdk-21` +- Run: `quay.io/quarkus/quarkus-micro-image:2.0` +- Dramatically faster startup (~50ms vs ~2s) + +### Spring Boot (Java) +**Standard:** +- Build and run: `ubi9/openjdk-17` + +**Optimized production:** +- Build with layered JAR: `spring-boot-maven-plugin` with layers +- Run on: `ubi9/openjdk-17-runtime` + +### Next.js (Node.js) +**Development:** +- Use `ubi9/nodejs-20` + +**Production (multi-stage recommended):** +1. Build stage: `ubi9/nodejs-20` +2. Run stage: `ubi9/nodejs-20-minimal` with `.next` output + +### Django/Flask (Python) +- Always use full image (may need compilation for dependencies) +- `ubi9/python-311` recommended +- Consider `gunicorn` for production + +## Decision Tree + +``` +START + | + v +Is this production? + | + +-- YES --> Need native compilation? + | | + | +-- YES --> Use FULL variant + | | + | +-- NO --> Is app pre-compiled? + | | + | +-- YES --> Use RUNTIME variant + | | + | +-- NO --> Use MINIMAL variant + | + +-- NO (Development) --> Use FULL variant +``` + +## Multi-Stage Build Recommendations + +For optimal production images, consider multi-stage builds: + +### Node.js Example +```dockerfile +# Build stage +FROM registry.access.redhat.com/ubi9/nodejs-20 AS builder +COPY . . +RUN npm ci && npm run build + +# Production stage +FROM registry.access.redhat.com/ubi9/nodejs-20-minimal +COPY --from=builder /app/dist /app +CMD ["node", "/app/index.js"] +``` + +### Java Example +```dockerfile +# Build stage +FROM registry.access.redhat.com/ubi9/openjdk-21 AS builder +COPY . . +RUN mvn package -DskipTests + +# Production stage +FROM registry.access.redhat.com/ubi9/openjdk-21-runtime +COPY --from=builder /app/target/*.jar /app/app.jar +CMD ["java", "-jar", "/app/app.jar"] +``` + +### Go Example +Go produces static binaries, so minimal base is ideal: +```dockerfile +# Build stage +FROM registry.access.redhat.com/ubi9/go-toolset:1.21 AS builder +COPY . . +RUN go build -o /app/server + +# Production stage +FROM registry.access.redhat.com/ubi9/ubi-micro +COPY --from=builder /app/server /server +CMD ["/server"] +``` diff --git a/submissions/rh-developer-rhel-deploy/docs/prerequisites.md b/submissions/rh-developer-rhel-deploy/docs/prerequisites.md new file mode 100644 index 0000000..d81a9b5 --- /dev/null +++ b/submissions/rh-developer-rhel-deploy/docs/prerequisites.md @@ -0,0 +1,212 @@ +--- +title: Prerequisites +category: setup +sources: + - title: OpenShift CLI (oc) Installation + url: https://docs.openshift.com/container-platform/latest/cli_reference/openshift_cli/getting-started-cli.html + sections: Installing the CLI, Logging in + date_accessed: 2026-02-08 + - title: Helm Installation Guide + url: https://helm.sh/docs/intro/install/ + sections: From script, From package managers + date_accessed: 2026-02-08 + - title: Podman Installation + url: https://podman.io/docs/installation + sections: Linux, macOS, Windows + date_accessed: 2026-02-08 + - title: Skopeo Installation + url: https://github.com/containers/skopeo/blob/main/install.md + sections: Distribution packages, Building from source + date_accessed: 2026-02-08 +--- + +# Prerequisites + +This document lists all tools required by the rh-developer agentic collection. + +## Required Tools by Skill + +| Skill | Required Tools | Optional Tools | +|-------|----------------|----------------| +| `/detect-project` | `git` | - | +| `/s2i-build` | `oc` | `git` | +| `/deploy` | `oc` | - | +| `/helm-deploy` | `oc`, `helm` | - | +| `/containerize-deploy` | `oc` | `git`, `helm` | +| `/rhel-deploy` | `ssh`, `podman` or `docker` | `git`, `dnf` | +| `/recommend-image` | - | `skopeo`, `curl`, `jq` | +| `/debug-pod` | `oc` | - | +| `/debug-build` | `oc` | - | +| `/debug-network` | `oc` | - | +| `/debug-rhel` | `ssh` | `ausearch`, `journalctl` | +| `/debug-container` | `podman` or `docker` | - | + +## Tool Reference + +### OpenShift CLI (oc) + +**Required for:** Cluster operations, S2I builds, deployments + +```bash +# Check installation +oc version + +# Installation +# Download from: https://mirror.openshift.com/pub/openshift-v4/clients/ocp/latest/ +# Or via package manager: +sudo dnf install openshift-clients # Fedora/RHEL +brew install openshift-cli # macOS +``` + +### Helm + +**Required for:** Helm chart deployments + +```bash +# Check installation +helm version + +# Installation +curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash +# Or via package manager: +sudo dnf install helm # Fedora/RHEL +brew install helm # macOS +``` + +### Podman + +**Required for:** Container builds, RHEL container deployments + +```bash +# Check installation +podman --version + +# Installation +sudo dnf install podman # Fedora/RHEL/CentOS +sudo apt install podman # Ubuntu/Debian +brew install podman # macOS +``` + +### Docker (alternative to Podman) + +**Required for:** Container builds (if Podman not available) + +```bash +# Check installation +docker --version + +# Installation +# See: https://docs.docker.com/engine/install/ +``` + +### Skopeo + +**Required for:** Image inspection, tag verification + +```bash +# Check installation +skopeo --version + +# Installation +sudo dnf install skopeo # Fedora/RHEL/CentOS +sudo apt install skopeo # Ubuntu/Debian +brew install skopeo # macOS +``` + +### Git + +**Required for:** Repository cloning + +```bash +# Check installation +git --version + +# Installation +sudo dnf install git # Fedora/RHEL/CentOS +sudo apt install git # Ubuntu/Debian +brew install git # macOS (or Xcode Command Line Tools) +``` + +### SSH + +**Required for:** RHEL remote deployments + +```bash +# Check installation +ssh -V + +# Usually pre-installed on Linux/macOS +# Windows: Use OpenSSH or WSL +``` + +### curl and jq + +**Required for:** API calls and JSON parsing + +```bash +# Check installation +curl --version +jq --version + +# Installation +sudo dnf install curl jq # Fedora/RHEL/CentOS +sudo apt install curl jq # Ubuntu/Debian +brew install curl jq # macOS +``` + +## Cluster Requirements + +### OpenShift Cluster Access + +For S2I builds and deployments, you need: + +1. **Logged in to cluster:** + ```bash + oc login + # or + oc login --token= --server= + ``` + +2. **Namespace with edit permissions:** + ```bash + # Verify access + oc auth can-i create deployments + oc auth can-i create buildconfigs + ``` + +3. **Image registry accessible:** + ```bash + # Verify internal registry + oc get route -n openshift-image-registry + ``` + +### RHEL/Fedora Host Access + +For RHEL deployments, you need: + +1. **SSH access to target host:** + ```bash + ssh user@target-host + ``` + +2. **sudo privileges on target** (for systemd services) + +3. **Firewall ports open** (for application access) + +## Quick Validation + +Run these commands to check your environment: + +```bash +# Core tools +which oc helm podman git ssh curl jq skopeo + +# Cluster connection (if using OpenShift) +oc whoami +oc project + +# Container runtime +podman info || docker info +``` + +Use the `/validate-environment` skill for automated checking. diff --git a/submissions/rh-developer-rhel-deploy/docs/python-s2i-entrypoints.md b/submissions/rh-developer-rhel-deploy/docs/python-s2i-entrypoints.md new file mode 100644 index 0000000..bb29398 --- /dev/null +++ b/submissions/rh-developer-rhel-deploy/docs/python-s2i-entrypoints.md @@ -0,0 +1,70 @@ +--- +title: Python S2I Entry Point Requirements +category: containers +sources: + - title: UBI Python S2I Builder + url: https://github.com/sclorg/s2i-python-container + sections: Run script logic, APP_MODULE configuration + date_accessed: 2026-02-08 + - title: Red Hat Python S2I Documentation + url: https://catalog.redhat.com/software/containers/ubi9/python-311 + sections: Environment variables, Startup behavior + date_accessed: 2026-02-08 +--- + +# Python S2I Entry Point Requirements + +The UBI Python S2I builder has specific startup logic that must be understood to avoid deployment failures. + +## How the S2I Python Run Script Works + +The S2I Python builder uses this startup logic (in order): + +1. If `app.sh` exists → Execute it directly +2. If `gunicorn` is installed AND `APP_MODULE` is set → Start with gunicorn +3. If `app.py` exists → Run with Python directly +4. Otherwise → **ERROR: No start command found** + +## Entry Point Configuration Matrix + +| Entry Point File | gunicorn in requirements | Configuration Needed | Result | +|------------------|--------------------------|----------------------|--------| +| `app.py` | No | None | Works (Python direct) | +| `app.py` | Yes | None (optional APP_MODULE) | Works | +| `main.py` | **No** | - | **FAILS** | +| `main.py` | Yes | `APP_MODULE=main:app` | Works | +| `wsgi.py` | Yes | `APP_MODULE=wsgi` or `APP_MODULE=wsgi:application` | Works | +| Custom file | Yes | `APP_MODULE=[module]:[variable]` | Works | + +## APP_MODULE Format + +- **Format:** `[python_module]:[flask_app_variable]` +- **Example:** `main:app` → imports `app` from `main.py` +- **Requires:** `gunicorn` in `requirements.txt` + +### Common Patterns + +| File | Typical APP_MODULE | +|------|-------------------| +| `main.py` with `app = Flask(__name__)` | `main:app` | +| `main.py` with `application = Flask(__name__)` | `main:application` | +| `wsgi.py` with `application` | `wsgi:application` or just `wsgi` | +| `src/app.py` with `app` | `src.app:app` | + +## Alternative: APP_FILE + +- Set `APP_FILE=main.py` to run with Python directly (development mode) +- **Not recommended for production** (no WSGI server, no worker management) +- Use only if gunicorn is not an option + +## Critical Warning + +**If the entry point is NOT `app.py` and `gunicorn` is NOT installed:** +- The S2I build will succeed (dependencies install) +- The container will **fail to start** with "No start command found" +- This is a **runtime failure**, not a build failure + +**Always verify:** +1. Entry point file name +2. `gunicorn` in requirements.txt +3. `APP_MODULE` environment variable in BuildConfig diff --git a/submissions/rh-developer-rhel-deploy/docs/rhel-deployment.md b/submissions/rh-developer-rhel-deploy/docs/rhel-deployment.md new file mode 100644 index 0000000..06eda27 --- /dev/null +++ b/submissions/rh-developer-rhel-deploy/docs/rhel-deployment.md @@ -0,0 +1,580 @@ +--- +title: RHEL Deployment Reference +category: deployment +sources: + - title: RHEL System Administrator's Guide - systemd + url: https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/9/html/configuring_basic_system_settings/managing-system-services-with-systemctl_configuring-basic-system-settings + sections: Managing services, Unit files + date_accessed: 2026-02-08 + - title: RHEL SELinux Guide + url: https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/9/html/using_selinux + sections: Contexts, Port labeling + date_accessed: 2026-02-08 + - title: RHEL Firewall Configuration + url: https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/9/html/configuring_firewalls_and_packet_filters + sections: firewalld, Opening ports + date_accessed: 2026-02-08 +--- + +# RHEL Deployment Reference + +Reference material for deploying applications to standalone RHEL systems. + +## Table of Contents + +1. [RHEL Version Compatibility](#rhel-version-compatibility) +2. [Systemd Unit Templates](#systemd-unit-templates) +3. [SELinux Configuration](#selinux-configuration) +4. [Firewall Commands](#firewall-commands) +5. [SSH Connection Patterns](#ssh-connection-patterns) +6. [Runtime Package Mapping](#runtime-package-mapping) + +--- + +## RHEL Version Compatibility + +| Distribution | Version | Podman | Recommended | +|--------------|---------|--------|-------------| +| RHEL | 8.x | 4.0+ | Production ready | +| RHEL | 9.x | 4.4+ | **Recommended** | +| CentOS Stream | 8 | 4.0+ | Development | +| CentOS Stream | 9 | 4.4+ | Development | +| Rocky Linux | 8.x | 4.0+ | Production ready | +| Rocky Linux | 9.x | 4.4+ | Production ready | +| AlmaLinux | 8.x | 4.0+ | Production ready | +| AlmaLinux | 9.x | 4.4+ | Production ready | +| Fedora | 38+ | 4.6+ | Latest features | + +### Version Detection Commands + +```bash +# Get RHEL/CentOS version +cat /etc/redhat-release + +# Get detailed OS info +cat /etc/os-release + +# Check architecture +uname -m + +# Check kernel version +uname -r +``` + +--- + +## Systemd Unit Templates + +### Podman Container Service (Rootful) + +```ini +[Unit] +Description=${APP_NAME} Container +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +Restart=always +RestartSec=5 +TimeoutStartSec=300 +TimeoutStopSec=70 + +# Pre-start: ensure clean state +ExecStartPre=-/usr/bin/podman stop -t 10 ${APP_NAME} +ExecStartPre=-/usr/bin/podman rm ${APP_NAME} + +# Main container run +ExecStart=/usr/bin/podman run \ + --name ${APP_NAME} \ + -p ${HOST_PORT}:${CONTAINER_PORT} \ + --rm \ + ${IMAGE} + +# Stop container gracefully +ExecStop=/usr/bin/podman stop -t 10 ${APP_NAME} + +[Install] +WantedBy=multi-user.target +``` + +### Podman Container Service (Rootless) + +```ini +[Unit] +Description=${APP_NAME} Container (Rootless) +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +Restart=always +RestartSec=5 +TimeoutStartSec=300 +TimeoutStopSec=70 + +ExecStartPre=-/usr/bin/podman stop -t 10 ${APP_NAME} +ExecStartPre=-/usr/bin/podman rm ${APP_NAME} +ExecStart=/usr/bin/podman run \ + --name ${APP_NAME} \ + -p ${HOST_PORT}:${CONTAINER_PORT} \ + --rm \ + ${IMAGE} +ExecStop=/usr/bin/podman stop -t 10 ${APP_NAME} + +[Install] +WantedBy=default.target +``` + +**Rootless setup commands:** +```bash +# Create user systemd directory +mkdir -p ~/.config/systemd/user + +# Place unit file +cp ${APP_NAME}.service ~/.config/systemd/user/ + +# Reload and enable +systemctl --user daemon-reload +systemctl --user enable --now ${APP_NAME} + +# Keep services running after logout +loginctl enable-linger ${USER} +``` + +### Podman Container with Volumes + +```ini +[Unit] +Description=${APP_NAME} Container with Persistent Data +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +Restart=always +RestartSec=5 + +ExecStartPre=-/usr/bin/podman stop -t 10 ${APP_NAME} +ExecStartPre=-/usr/bin/podman rm ${APP_NAME} +ExecStart=/usr/bin/podman run \ + --name ${APP_NAME} \ + -p ${HOST_PORT}:${CONTAINER_PORT} \ + -v /var/lib/${APP_NAME}/data:/app/data:z \ + -e DATABASE_URL=${DATABASE_URL} \ + --rm \ + ${IMAGE} +ExecStop=/usr/bin/podman stop -t 10 ${APP_NAME} + +[Install] +WantedBy=multi-user.target +``` + +### Native Node.js Application + +```ini +[Unit] +Description=${APP_NAME} Node.js Service +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User=${SERVICE_USER} +WorkingDirectory=/opt/${APP_NAME} +Environment=NODE_ENV=production +Environment=PORT=${PORT} +ExecStart=/usr/bin/node /opt/${APP_NAME}/server.js +Restart=always +RestartSec=5 + +# Security hardening +NoNewPrivileges=true +ProtectSystem=strict +ProtectHome=true +PrivateTmp=true +ReadWritePaths=/opt/${APP_NAME} + +[Install] +WantedBy=multi-user.target +``` + +### Native Python Application + +```ini +[Unit] +Description=${APP_NAME} Python Service +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User=${SERVICE_USER} +WorkingDirectory=/opt/${APP_NAME} +Environment=PYTHONUNBUFFERED=1 +Environment=PORT=${PORT} +ExecStart=/usr/bin/python3 /opt/${APP_NAME}/app.py +Restart=always +RestartSec=5 + +# Security hardening +NoNewPrivileges=true +ProtectSystem=strict +ProtectHome=true +PrivateTmp=true +ReadWritePaths=/opt/${APP_NAME} + +[Install] +WantedBy=multi-user.target +``` + +### Native Java Application + +```ini +[Unit] +Description=${APP_NAME} Java Service +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User=${SERVICE_USER} +WorkingDirectory=/opt/${APP_NAME} +Environment=JAVA_OPTS=-Xmx512m +ExecStart=/usr/bin/java -jar /opt/${APP_NAME}/app.jar --server.port=${PORT} +Restart=always +RestartSec=5 +SuccessExitStatus=143 + +# Security hardening +NoNewPrivileges=true +ProtectSystem=strict +ProtectHome=true +PrivateTmp=true +ReadWritePaths=/opt/${APP_NAME} + +[Install] +WantedBy=multi-user.target +``` + +### Native Go Application + +```ini +[Unit] +Description=${APP_NAME} Go Service +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User=${SERVICE_USER} +WorkingDirectory=/opt/${APP_NAME} +Environment=PORT=${PORT} +ExecStart=/opt/${APP_NAME}/${BINARY_NAME} +Restart=always +RestartSec=5 + +# Security hardening +NoNewPrivileges=true +ProtectSystem=strict +ProtectHome=true +PrivateTmp=true + +[Install] +WantedBy=multi-user.target +``` + +--- + +## SELinux Configuration + +### Common SELinux Contexts + +| Context Type | Use Case | +|--------------|----------| +| `container_t` | Standard Podman container processes | +| `container_file_t` | Container data files | +| `bin_t` | Executable binaries | +| `httpd_sys_content_t` | Web application content (read-only) | +| `httpd_sys_rw_content_t` | Web application content (read-write) | +| `var_lib_t` | Application data in /var/lib | + +### Volume Label Options for Podman + +| Option | Description | Use Case | +|--------|-------------|----------| +| `:z` | Shared volume label | Volume accessed by multiple containers | +| `:Z` | Private volume label | Volume accessed by single container only | + +Example: +```bash +podman run -v /data/shared:/app/shared:z myimage # Shared +podman run -v /data/private:/app/data:Z myimage # Private +``` + +### SELinux Commands + +```bash +# Check current SELinux mode +getenforce + +# View file context +ls -Z /path/to/file + +# Set context for application directory +sudo semanage fcontext -a -t bin_t "/opt/myapp(/.*)?" +sudo restorecon -Rv /opt/myapp + +# Set context for web content +sudo semanage fcontext -a -t httpd_sys_content_t "/opt/myapp/public(/.*)?" +sudo restorecon -Rv /opt/myapp/public + +# Allow non-standard port for HTTP +sudo semanage port -a -t http_port_t -p tcp 8080 + +# View port contexts +sudo semanage port -l | grep http + +# Check for SELinux denials +sudo ausearch -m AVC -ts recent + +# Generate policy from denials (troubleshooting) +sudo ausearch -m AVC -ts recent | audit2allow -M mypolicy +sudo semodule -i mypolicy.pp + +# Temporarily set permissive (for debugging only) +sudo setenforce 0 +``` + +### Common SELinux Booleans + +```bash +# Allow HTTP to connect to network (for proxy/API calls) +sudo setsebool -P httpd_can_network_connect 1 + +# Allow HTTP to connect to databases +sudo setsebool -P httpd_can_network_connect_db 1 + +# List all HTTP-related booleans +getsebool -a | grep httpd +``` + +--- + +## Firewall Commands + +### Basic Port Management + +```bash +# Check firewall status +sudo firewall-cmd --state + +# List all open ports +sudo firewall-cmd --list-ports + +# List all services +sudo firewall-cmd --list-services + +# Open port permanently +sudo firewall-cmd --permanent --add-port=8080/tcp + +# Open port temporarily (until reload) +sudo firewall-cmd --add-port=8080/tcp + +# Reload firewall to apply permanent changes +sudo firewall-cmd --reload + +# Remove port +sudo firewall-cmd --permanent --remove-port=8080/tcp +sudo firewall-cmd --reload +``` + +### Service-Based Management + +```bash +# Add HTTP service +sudo firewall-cmd --permanent --add-service=http + +# Add HTTPS service +sudo firewall-cmd --permanent --add-service=https + +# Remove service +sudo firewall-cmd --permanent --remove-service=http + +# Apply changes +sudo firewall-cmd --reload +``` + +### Zone Management + +```bash +# List zones +sudo firewall-cmd --get-zones + +# Get active zone +sudo firewall-cmd --get-active-zones + +# Add port to specific zone +sudo firewall-cmd --zone=public --permanent --add-port=8080/tcp + +# Set default zone +sudo firewall-cmd --set-default-zone=public +``` + +### Rich Rules (Advanced) + +```bash +# Allow specific IP to access port +sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.1.0/24" port protocol="tcp" port="8080" accept' + +# Rate limiting +sudo firewall-cmd --permanent --add-rich-rule='rule service name="http" limit value="10/m" accept' + +# Apply changes +sudo firewall-cmd --reload +``` + +--- + +## SSH Connection Patterns + +### Test Connection + +```bash +# Basic connection test +ssh -o BatchMode=yes -o ConnectTimeout=10 user@host "echo 'OK'" + +# Verbose output for debugging +ssh -v user@host + +# Test with specific key +ssh -i ~/.ssh/mykey user@host "echo 'OK'" +``` + +### Execute Remote Commands + +```bash +# Single command +ssh user@host "command" + +# Multiple commands +ssh user@host "cmd1 && cmd2 && cmd3" + +# With sudo +ssh user@host "sudo command" + +# Preserve environment +ssh user@host 'bash -l -c "command"' +``` + +### File Transfer + +```bash +# Copy file to remote +scp local_file user@host:/remote/path/ + +# Copy directory recursively +scp -r local_dir user@host:/remote/path/ + +# Using rsync (preferred for large transfers) +rsync -avz --progress local_dir/ user@host:/remote/path/ + +# Exclude patterns +rsync -avz --exclude 'node_modules' --exclude '.git' ./ user@host:/remote/path/ +``` + +### SSH Config for Convenience + +``` +# ~/.ssh/config +Host myrhel + HostName 192.168.1.100 + User deploy + Port 22 + IdentityFile ~/.ssh/id_rsa + StrictHostKeyChecking accept-new +``` + +Usage: `ssh myrhel "command"` + +--- + +## Runtime Package Mapping + +### Node.js + +| Version | RHEL 8 | RHEL 9 | +|---------|--------|--------| +| 18 | `dnf module enable nodejs:18 && dnf install -y nodejs npm` | `dnf install -y nodejs npm` | +| 20 | `dnf module enable nodejs:20 && dnf install -y nodejs npm` | `dnf module enable nodejs:20 && dnf install -y nodejs npm` | + +### Python + +| Version | RHEL 8 | RHEL 9 | +|---------|--------|--------| +| 3.8 | `dnf install -y python38 python38-pip` | N/A | +| 3.9 | `dnf install -y python39 python39-pip` | `dnf install -y python3 python3-pip` | +| 3.11 | N/A | `dnf install -y python3.11 python3.11-pip` | +| 3.12 | N/A | `dnf install -y python3.12 python3.12-pip` | + +### Java + +| Version | RHEL 8 | RHEL 9 | +|---------|--------|--------| +| 11 | `dnf install -y java-11-openjdk java-11-openjdk-devel` | `dnf install -y java-11-openjdk java-11-openjdk-devel` | +| 17 | `dnf install -y java-17-openjdk java-17-openjdk-devel` | `dnf install -y java-17-openjdk java-17-openjdk-devel` | +| 21 | N/A | `dnf install -y java-21-openjdk java-21-openjdk-devel` | + +### Go + +| Version | RHEL 8 | RHEL 9 | +|---------|--------|--------| +| 1.20+ | `dnf install -y go-toolset` | `dnf install -y golang` | + +### Ruby + +| Version | RHEL 8 | RHEL 9 | +|---------|--------|--------| +| 3.0 | `dnf module enable ruby:3.0 && dnf install -y ruby ruby-devel` | `dnf install -y ruby ruby-devel` | +| 3.1 | `dnf module enable ruby:3.1 && dnf install -y ruby ruby-devel` | `dnf module enable ruby:3.1 && dnf install -y ruby ruby-devel` | + +### PHP + +| Version | RHEL 8 | RHEL 9 | +|---------|--------|--------| +| 7.4 | `dnf module enable php:7.4 && dnf install -y php php-cli php-fpm` | N/A | +| 8.0 | `dnf module enable php:8.0 && dnf install -y php php-cli php-fpm` | `dnf install -y php php-cli php-fpm` | +| 8.1 | N/A | `dnf module enable php:8.1 && dnf install -y php php-cli php-fpm` | + +### Module Stream Commands + +```bash +# List available streams for a module +dnf module list nodejs + +# Enable specific stream +sudo dnf module enable nodejs:20 + +# Reset module (to switch streams) +sudo dnf module reset nodejs + +# Install from enabled stream +sudo dnf install -y nodejs npm +``` + +--- + +## Service User Creation + +For running applications as non-root: + +```bash +# Create system user for the application +sudo useradd -r -s /sbin/nologin -d /opt/myapp myapp + +# Set ownership +sudo chown -R myapp:myapp /opt/myapp + +# Allow user to bind to privileged port (if needed) +sudo setcap 'cap_net_bind_service=+ep' /opt/myapp/binary +``` diff --git a/submissions/rh-developer-rhel-deploy/docs/selinux-troubleshooting.md b/submissions/rh-developer-rhel-deploy/docs/selinux-troubleshooting.md new file mode 100644 index 0000000..9942375 --- /dev/null +++ b/submissions/rh-developer-rhel-deploy/docs/selinux-troubleshooting.md @@ -0,0 +1,387 @@ +--- +title: SELinux Troubleshooting +category: references +sources: + - title: Red Hat SELinux User's and Administrator's Guide + url: https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/9/html/using_selinux/index + sections: Troubleshooting, Managing confined services + date_accessed: 2026-02-16 + - title: SELinux Project Wiki + url: https://selinuxproject.org/page/Main_Page + sections: Troubleshooting + date_accessed: 2026-02-16 + - title: Fedora SELinux Guide + url: https://docs.fedoraproject.org/en-US/quick-docs/selinux-getting-started/ + sections: Troubleshooting + date_accessed: 2026-02-16 +--- + +# SELinux Troubleshooting + +This document provides guidance for diagnosing and resolving SELinux access denials on RHEL/Fedora/CentOS systems. + +## Understanding SELinux + +### SELinux Modes + +| Mode | Description | Use Case | +|------|-------------|----------| +| **Enforcing** | SELinux policy is enforced, denials are blocked and logged | Production | +| **Permissive** | SELinux policy is not enforced, denials are logged only | Debugging | +| **Disabled** | SELinux is completely disabled | Not recommended | + +```bash +# Check current mode +getenforce + +# Temporarily switch to permissive (until reboot) +sudo setenforce 0 + +# Switch back to enforcing +sudo setenforce 1 +``` + +### SELinux Contexts + +Every file, process, and port has an SELinux context: + +``` +user:role:type:level +``` + +Example: `system_u:object_r:httpd_sys_content_t:s0` + +- **user**: SELinux user (system_u, user_u, etc.) +- **role**: Role (object_r for files) +- **type**: Type label (most important for troubleshooting) +- **level**: MLS/MCS level (usually s0) + +```bash +# View file context +ls -lZ /path/to/file + +# View process context +ps auxZ | grep [process] + +# View port context +semanage port -l | grep [port] +``` + +## Finding SELinux Denials + +### Using ausearch + +```bash +# Recent denials (last 10 minutes) +sudo ausearch -m AVC -ts recent + +# Denials from today +sudo ausearch -m AVC -ts today + +# Denials for specific process +sudo ausearch -m AVC -c [command-name] + +# Denials involving specific file +sudo ausearch -m AVC -f /path/to/file +``` + +### Using journalctl + +```bash +# SELinux messages in journal +sudo journalctl -t setroubleshoot + +# AVC messages +sudo journalctl | grep "avc: denied" +``` + +### Using sealert + +```bash +# Install setroubleshoot (if not installed) +sudo dnf install setroubleshoot-server + +# Analyze all denials +sudo sealert -a /var/log/audit/audit.log + +# Interactive analysis +sudo sealert -b +``` + +## Reading AVC Denials + +Example AVC denial: + +``` +type=AVC msg=audit(1234567890.123:456): avc: denied { bind } for pid=1234 comm="httpd" src=8080 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:unreserved_port_t:s0 tclass=tcp_socket permissive=0 +``` + +**Breakdown:** +| Field | Value | Meaning | +|-------|-------|---------| +| `denied { bind }` | bind | Denied action (bind to socket) | +| `pid=1234` | 1234 | Process ID | +| `comm="httpd"` | httpd | Command name | +| `src=8080` | 8080 | Port number | +| `scontext=...httpd_t...` | httpd_t | Source type (process) | +| `tcontext=...unreserved_port_t...` | unreserved_port_t | Target type (port) | +| `tclass=tcp_socket` | tcp_socket | Object class | + +**Translation:** Process `httpd` (type `httpd_t`) was denied permission to `bind` to port `8080` (type `unreserved_port_t`). + +## Common Denial Types and Fixes + +### Port Binding Denials + +**Symptom:** Application cannot bind to non-standard port + +**Example denial:** +``` +avc: denied { name_bind } for comm="nginx" src=8080 scontext=httpd_t tcontext=unreserved_port_t +``` + +**Fix:** +```bash +# Add port to allowed type +sudo semanage port -a -t http_port_t -p tcp 8080 + +# Verify +sudo semanage port -l | grep 8080 +``` + +**Common port types:** +| Port Type | Typical Ports | Used By | +|-----------|---------------|---------| +| `http_port_t` | 80, 443, 8080 | Web servers | +| `postgresql_port_t` | 5432 | PostgreSQL | +| `mysqld_port_t` | 3306 | MySQL/MariaDB | +| `redis_port_t` | 6379 | Redis | +| `mongod_port_t` | 27017 | MongoDB | + +### File Access Denials + +**Symptom:** Application cannot read/write files + +**Example denial:** +``` +avc: denied { read } for comm="httpd" name="config.yaml" scontext=httpd_t tcontext=user_home_t +``` + +**Fix - Change file context:** +```bash +# Set file context pattern +sudo semanage fcontext -a -t httpd_sys_content_t "/srv/myapp(/.*)?" + +# Apply the context +sudo restorecon -Rv /srv/myapp + +# Verify +ls -lZ /srv/myapp +``` + +**Common file types:** +| File Type | Access | Use Case | +|-----------|--------|----------| +| `httpd_sys_content_t` | Read | Web content | +| `httpd_sys_rw_content_t` | Read/Write | Web app data | +| `container_file_t` | Container access | Podman volumes | +| `var_log_t` | Log files | Application logs | + +### Network Connection Denials + +**Symptom:** Application cannot connect to external services + +**Example denial:** +``` +avc: denied { name_connect } for comm="httpd" dest=5432 scontext=httpd_t tcontext=postgresql_port_t +``` + +**Fix - Enable boolean:** +```bash +# Allow httpd to connect to network +sudo setsebool -P httpd_can_network_connect on + +# Or specifically to databases +sudo setsebool -P httpd_can_network_connect_db on + +# List all httpd booleans +sudo getsebool -a | grep httpd +``` + +**Common booleans:** +| Boolean | Purpose | +|---------|---------| +| `httpd_can_network_connect` | Allow outbound network connections | +| `httpd_can_network_connect_db` | Allow database connections | +| `httpd_can_sendmail` | Allow sending email | +| `httpd_use_nfs` | Allow NFS access | +| `container_manage_cgroup` | Allow container cgroup management | + +## Container-Specific Issues + +### Podman Volume Mounts + +When mounting host directories into containers, SELinux may block access. + +**Solutions:** + +1. **Shared label (:z)** - Multiple containers can access + ```bash + podman run -v /host/path:/container/path:z [image] + ``` + +2. **Private label (:Z)** - Only this container can access + ```bash + podman run -v /host/path:/container/path:Z [image] + ``` + +3. **Manual relabeling:** + ```bash + sudo semanage fcontext -a -t container_file_t "/data(/.*)?" + sudo restorecon -Rv /data + ``` + +### Container Booleans + +```bash +# Enable container to manage cgroups (for systemd in container) +sudo setsebool -P container_manage_cgroup on + +# Allow containers to connect to any port +sudo setsebool -P container_connect_any on + +# List all container booleans +sudo getsebool -a | grep container +``` + +## Troubleshooting Workflow + +### Step 1: Confirm SELinux is the Issue + +```bash +# Temporarily disable SELinux +sudo setenforce 0 + +# Test if application works +[test application] + +# Re-enable SELinux +sudo setenforce 1 +``` + +If application works with SELinux permissive, SELinux is blocking. + +### Step 2: Find the Denial + +```bash +# Get recent denials +sudo ausearch -m AVC -ts recent + +# Or use sealert for analysis +sudo sealert -a /var/log/audit/audit.log +``` + +### Step 3: Determine Fix Type + +| Denial Type | Fix Approach | +|-------------|--------------| +| Port binding | `semanage port` | +| File access | `semanage fcontext` + `restorecon` | +| Network connection | `setsebool` | +| Process capability | Custom policy or boolean | + +### Step 4: Apply Fix + +```bash +# For port: +sudo semanage port -a -t [type] -p [tcp/udp] [port] + +# For file: +sudo semanage fcontext -a -t [type] "[path](/.*)?" +sudo restorecon -Rv [path] + +# For boolean: +sudo setsebool -P [boolean] on +``` + +### Step 5: Verify + +```bash +# Test application +[restart and test] + +# Check for new denials +sudo ausearch -m AVC -ts recent +``` + +## Generating Custom Policies + +If no existing type or boolean works, generate a custom policy: + +```bash +# Generate policy from recent denials +sudo ausearch -m AVC -ts recent | audit2allow -M mypolicy + +# Review the policy +cat mypolicy.te + +# Install the policy +sudo semodule -i mypolicy.pp +``` + +**Warning:** Custom policies should be reviewed carefully. They grant permanent permissions. + +## Quick Reference + +### Common Commands + +```bash +# SELinux status +getenforce +sestatus + +# File context +ls -lZ [path] +restorecon -Rv [path] + +# Process context +ps auxZ | grep [process] + +# Port context +semanage port -l | grep [port] +semanage port -a -t [type] -p tcp [port] + +# Booleans +getsebool -a | grep [keyword] +setsebool -P [boolean] on + +# File context rules +semanage fcontext -l | grep [path] +semanage fcontext -a -t [type] "[path](/.*)?" + +# Audit logs +ausearch -m AVC -ts recent +sealert -a /var/log/audit/audit.log +``` + +### Common Types for Web Applications + +| Resource | Type | +|----------|------| +| Web content (read-only) | `httpd_sys_content_t` | +| Web content (read-write) | `httpd_sys_rw_content_t` | +| Web scripts | `httpd_sys_script_exec_t` | +| Application logs | `httpd_log_t` | +| HTTP ports | `http_port_t` | +| Container files | `container_file_t` | + +### Common Booleans for Applications + +| Application | Boolean | Purpose | +|-------------|---------|---------| +| Web server | `httpd_can_network_connect` | Outbound connections | +| Web server | `httpd_can_network_connect_db` | Database connections | +| Web server | `httpd_unified` | Unified handling | +| Container | `container_manage_cgroup` | cgroup management | +| Container | `container_connect_any` | Connect to any port | +| NFS | `use_nfs_home_dirs` | NFS home directories | diff --git a/submissions/rh-developer-rhel-deploy/instruction.md b/submissions/rh-developer-rhel-deploy/instruction.md new file mode 100644 index 0000000..b7c3a70 --- /dev/null +++ b/submissions/rh-developer-rhel-deploy/instruction.md @@ -0,0 +1,12 @@ +# RHEL Deployment Task + +You are a Red Hat developer. Plan the deployment of a containerized application on RHEL using Podman and systemd. + +## Requirements +- Configure the container to run as a systemd service +- Address security hardening (SELinux, privilege restrictions) +- Include volume mounts and networking configuration + +Use available tools to examine the environment. Document your methodology, configuration, and deployment plan in `/root/report.md`. + +If reference documentation or skills are available in this environment, consult them before beginning work. diff --git a/submissions/rh-developer-rhel-deploy/mcps.json b/submissions/rh-developer-rhel-deploy/mcps.json new file mode 100644 index 0000000..19046e0 --- /dev/null +++ b/submissions/rh-developer-rhel-deploy/mcps.json @@ -0,0 +1,79 @@ +{ + "mcpServers": { + "openshift": { + "command": "podman", + "args": [ + "run", + "--rm", + "-i", + "--network=host", + "--userns=keep-id:uid=65532,gid=65532", + "-v", "${KUBECONFIG}:/kubeconfig:ro,Z", + "--entrypoint", "/app/kubernetes-mcp-server", + "quay.io/ecosystem-appeng/openshift-mcp-server@sha256:3531cb78f51f8c7ebcdb21adc21358ab8924116994848a3ce1ff542b3fc23742", + "--kubeconfig", "/kubeconfig", + "--toolsets", "core,config,helm,kubevirt,observability,ossm" + ], + "env": { + "KUBECONFIG": "${KUBECONFIG}" + }, + "description": "Red Hat Openshift MCP server for interacting with Openshift Container Platform clusters and its operators", + "security": { + "isolation": "container", + "network": "local", + "credentials": "env-only" + } + }, + "podman": { + "command": "npx", + "args": ["-y", "podman-mcp-server@0.0.15"], + "env": {}, + "description": "Podman MCP server for container image management and local builds", + "security": { + "isolation": "process", + "network": "local", + "credentials": "none" + } + }, + "github": { + "command": "podman", + "args": [ + "run", "-i", "--rm", + "-e", "GITHUB_PERSONAL_ACCESS_TOKEN", + "ghcr.io/github/github-mcp-server:1.0.3" + ], + "env": { + "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_PERSONAL_ACCESS_TOKEN}" + }, + "description": "GitHub MCP server for repository browsing and code analysis", + "security": { + "isolation": "container", + "network": "local", + "credentials": "env-only" + } + }, + "lightspeed-mcp": { + "command": "podman", + "args": [ + "run", + "--rm", + "-i", + "--env", + "LIGHTSPEED_CLIENT_ID", + "--env", + "LIGHTSPEED_CLIENT_SECRET", + "podman pull quay.io/redhat-services-prod/insights-management-tenant/insights-mcp/red-hat-lightspeed-mcp@sha256:05dde29df5ab0be7230e0ec64c87e8e7fda12d77d01bd026b67486da714c1a94" + ], + "env": { + "LIGHTSPEED_CLIENT_ID": "${LIGHTSPEED_CLIENT_ID}", + "LIGHTSPEED_CLIENT_SECRET": "${LIGHTSPEED_CLIENT_SECRET}" + }, + "description": "Red Hat Lightspeed MCP server for vulnerability, advisor, inventory, and planning data from Red Hat Insights (optional, used by /debug-rhel and /rhel-deploy)", + "security": { + "isolation": "container", + "network": "local", + "credentials": "env-only" + } + } + } +} diff --git a/submissions/rh-developer-rhel-deploy/metadata.yaml b/submissions/rh-developer-rhel-deploy/metadata.yaml new file mode 100644 index 0000000..e81ce97 --- /dev/null +++ b/submissions/rh-developer-rhel-deploy/metadata.yaml @@ -0,0 +1,13 @@ +name: rh-developer-rhel-deploy +description: "rh-developer rhel-deploy Skill Evaluation" +persona: rh-developer +version: "1.0.0" +generation_mode: manual +tags: + - rh-developer + - rhel-deploy +cpus: 2 +memory_mb: 2048 +storage_mb: 10240 +experiment: + n_trials: 3 diff --git a/submissions/rh-developer-rhel-deploy/skills/rhel-deploy/SKILL.md b/submissions/rh-developer-rhel-deploy/skills/rhel-deploy/SKILL.md new file mode 100644 index 0000000..c0b9592 --- /dev/null +++ b/submissions/rh-developer-rhel-deploy/skills/rhel-deploy/SKILL.md @@ -0,0 +1,483 @@ +--- +name: rhel-deploy +description: | + CRITICAL: When user types /rhel-deploy, use THIS skill immediately. This skill deploys applications to standalone RHEL/Fedora/CentOS systems (NOT OpenShift) using Podman containers with systemd, or native dnf builds. Handles SSH connectivity, SELinux, firewall-cmd, and systemd unit creation. Triggers: /rhel-deploy command, 'deploy to RHEL', 'deploy to Fedora', 'deploy to my server via SSH'. +model: inherit +color: yellow +license: Apache-2.0 +metadata: + user_invocable: "true" +--- + +# /rhel-deploy Skill + +**IMPORTANT:** This skill is for deploying to standalone RHEL/Fedora/CentOS systems via SSH. If user invoked `/rhel-deploy`, skip any OpenShift-related steps and proceed directly with SSH-based deployment. + +Deploy applications to standalone RHEL systems using Podman containers or native builds with systemd service management. + +## Overview + +``` +[Intro] → [SSH Connect] → [Analyze] → [Strategy] ──┬─→ [Container Path] ──→ [Complete] + │ (Podman + systemd) + │ + └─→ [Native Path] ─────→ [Complete] + (dnf + systemd) +``` + +**Deployment Strategies (user chooses one):** +- **Container** - Build/pull container image, run with Podman, manage with systemd +- **Native** - Install dependencies with dnf, run application directly, manage with systemd + +## Prerequisites + +1. SSH access to target RHEL host with sudo privileges +2. RHEL 8+, CentOS Stream, Rocky Linux, or Fedora +3. For container deployments: Podman installed on target +4. For native deployments: Required development tools available via dnf + +## When to Use This Skill + +Use `/rhel-deploy` when deploying applications to standalone RHEL, Fedora, or CentOS systems via SSH. This skill handles Podman container or native dnf deployments with systemd service management, SELinux, and firewall configuration. + +## Critical: Human-in-the-Loop Requirements + +See [Human-in-the-Loop Requirements](../../docs/human-in-the-loop.md) for mandatory checkpoint behavior. + +## Workflow + +### Phase 0: Introduction + +Present the workflow overview: Connect → Analyze → Strategy → Build/Deploy → Verify. Describe Container (Podman + systemd) vs Native (dnf + systemd) strategies. Ask: **Ready to begin?** (yes/no) + +**WAIT for user confirmation before proceeding.** + +### Phase 1: SSH Connection + +```markdown +## Phase 1: Connecting to RHEL Host + +**SSH Target Configuration:** + +Please provide your RHEL host details: + +| Setting | Value | Default | +|---------|-------|---------| +| Host | [required] | - | +| User | [current user] | $USER | +| Port | 22 | 22 | + +Example: `user@192.168.1.100` or `deploy@myserver.example.com` + +**Enter your SSH target:** +``` + +**Connection verification:** + +```bash +ssh -o BatchMode=yes -o ConnectTimeout=10 [user]@[host] "echo 'Connection successful'" +``` + +If connection fails, troubleshoot: host reachability, SSH key configuration, firewall port 22. + +Store `RHEL_HOST`, `RHEL_USER`, `RHEL_PORT` in session state. + +### Phase 2: Target Host Analysis + +```markdown +## Phase 2: Analyzing Target Host + +Checking capabilities of [host]... + +| Setting | Value | +|---------|-------| +| OS | [cat /etc/redhat-release] | +| Kernel | [uname -r] | +| Architecture | [uname -m] | +| Podman | [Installed v4.x / Not installed] | +| SELinux | [Enforcing / Permissive / Disabled] | +| Firewall | [Active / Inactive] | + +Is this the correct target host? (yes/no) +``` + +**WAIT for user confirmation before proceeding.** + +**Commands to gather information:** + +```bash +ssh [target] "cat /etc/redhat-release" +ssh [target] "podman --version 2>/dev/null || echo 'Not installed'" +ssh [target] "getenforce" +ssh [target] "firewall-cmd --state 2>/dev/null || echo 'Not running'" +``` + +Store `RHEL_VERSION`, `PODMAN_AVAILABLE`, `SELINUX_STATUS`, `FIREWALL_STATUS` in session state. + +### Phase 2b: Red Hat Insights Pre-Deploy Check (Optional) + +**This phase runs only if the `lightspeed-mcp` server is available.** Use `ToolSearch` to check for Lightspeed MCP tools. If not available, skip silently and proceed to Phase 3. + +**Step 1:** Use `find_host_by_name` with the target hostname to look up the system in Red Hat Insights. + +**Step 2:** If found, use `get_system_cves` to check for critical/important CVEs on the target. + +**Step 3:** Use `get_rhel_lifecycle` to verify the target RHEL version is still supported. + +Append to Phase 2 output: + +```markdown +**Red Hat Insights (Optional):** +| Check | Status | Details | +|-------|--------|---------| +| Registered in Insights | [Yes/No] | [system-id or "Not found"] | +| RHEL Lifecycle | [Active/Maintenance/EOL] | [end date] | +| Critical/Important CVEs | [count] | [top 3 CVE IDs] | + +[If critical CVEs found:] +**WARNING:** Target system has [N] critical/important CVEs. Consider remediating before deploying. + +[If RHEL version is EOL:] +**WARNING:** RHEL [version] has reached End of Life ([date]). Consider upgrading before deploying. +``` + +These are informational warnings only — they do not block deployment. + +### Phase 3: Strategy Selection + +```markdown +## Deployment Strategy + +Based on your project ([language]/[framework]) and target capabilities: + +| Strategy | Description | Requirements | +|----------|-------------|--------------| +| **Container** | Build image, run with Podman + systemd | Podman installed | +| **Native** | Install with dnf, run directly + systemd | Runtime packages available | + +**Recommendation:** [Container/Native] because [reason] + +**Which deployment strategy would you like to use?** +1. Container - Deploy using Podman +2. Native - Deploy directly on host +``` + +**WAIT for user confirmation before proceeding.** + +**If Podman not installed and user selects Container:** +```markdown +Podman is not installed on the target. Would you like me to install it? + +```bash +sudo dnf install -y podman +``` + +Proceed with Podman installation? (yes/no) +``` + +**WAIT for user confirmation before proceeding.** + +Store `DEPLOYMENT_STRATEGY` in session state. + +--- + +## CONTAINER PATH (If DEPLOYMENT_STRATEGY is "Container") + +### Phase 4a-1: Image Selection + +```markdown +## Container Image + +**Options:** + +1. **Build on target** - Transfer source, build with Podman on RHEL host +2. **Build locally and transfer** - Build here, push to registry or transfer +3. **Use existing image** - Pull from registry (e.g., quay.io, docker.io) + +Which approach would you prefer? +``` + +**WAIT for user confirmation before proceeding.** + +**For options 1 and 2 (building an image):** + +If no Containerfile/Dockerfile exists in the project, delegate to `/recommend-image`: + +```markdown +## Selecting Base Image + +To build your container, I need to select an appropriate base image. + +Invoking `/recommend-image` to get the optimal UBI image for your [language]/[framework] project... +``` + +Use the `BUILDER_IMAGE` output from `/recommend-image` as the base image in the Containerfile. + +**For build on target:** +```bash +# Transfer source and build +rsync -avz --exclude node_modules --exclude .git ./ [target]:/tmp/[app-name]-build/ +# If no Containerfile exists, generate one using BUILDER_IMAGE from /recommend-image +ssh [target] "cd /tmp/[app-name]-build && podman build -t [app-name]:latest ." +``` + +**For existing image:** +```bash +ssh [target] "podman pull [image-reference]" +``` + +### Phase 4a-2: Container Configuration + +```markdown +## Container Configuration + +**Container Settings:** +| Setting | Value | +|---------|-------| +| Name | [app-name] | +| Image | [image-ref] | +| Port Mapping | [host-port]:[container-port] | +| Volume Mounts | [list any persistent data paths] | +| Environment | [list env vars] | +| Run Mode | [rootless / rootful] | + +**SELinux Volume Labels:** Use `:z` for shared volumes, `:Z` for private volumes. See [docs/rhel-deployment.md](../../docs/rhel-deployment.md) for SELinux configuration details. + +Proceed with this configuration? (yes/modify/cancel) +``` + +**WAIT for user confirmation before proceeding.** + +### Phase 4a-3: Systemd Unit Creation + +```markdown +## Systemd Service Configuration + +Creating systemd unit for Podman container. + +**Template to use:** +- Rootful: `templates/systemd/systemd-container-rootful.service` +- Rootless: `templates/systemd/systemd-container-rootless.service` + +**Variables to substitute:** +| Variable | Value | +|----------|-------| +| `${APP_NAME}` | [app-name] | +| `${PORT}` | [container-port] | +| `${IMAGE}` | [container-image] | + +**Target locations:** +- Rootful: `/etc/systemd/system/[app-name].service` +- Rootless: `~/.config/systemd/user/[app-name].service` + +Proceed with creating this service? (yes/no) +``` + +**WAIT for user confirmation before proceeding.** + +**Steps to execute:** + +1. Read the appropriate template from `templates/systemd/` +2. Substitute `${APP_NAME}`, `${PORT}`, `${IMAGE}` with session state values +3. Transfer the generated unit file to the target host +4. Enable and start the service + +```bash +# Rootful: transfer to /etc/systemd/system/, daemon-reload, enable --now +# Rootless: transfer to ~/.config/systemd/user/, daemon-reload, enable --now, enable-linger +ssh [target] "sudo systemctl daemon-reload && sudo systemctl enable --now [app-name]" +``` + +### Phase 4a-4: Firewall Configuration + +```markdown +## Firewall Configuration + +Opening port [port] for application access. + +**Commands to execute:** +```bash +# Open port permanently +sudo firewall-cmd --permanent --add-port=[port]/tcp + +# Reload firewall +sudo firewall-cmd --reload + +# Verify +sudo firewall-cmd --list-ports +``` + +Proceed with firewall configuration? (yes/skip) +``` + +**WAIT for user confirmation before proceeding.** + +--- + +## NATIVE PATH (If DEPLOYMENT_STRATEGY is "Native") + +### Phase 4b-1: Dependency Installation + +```markdown +## Installing Dependencies + +**Runtime packages for [language]:** + +See [docs/rhel-deployment.md](../../docs/rhel-deployment.md) for the complete runtime package mapping by language and RHEL version (Node.js, Python, Java, Go, Ruby, PHP). + +**Commands to execute:** +```bash +ssh [target] "sudo dnf install -y [packages]" +``` + +Proceed with installation? (yes/no) +``` + +**WAIT for user confirmation before proceeding.** + +### Phase 4b-2: Application Deployment + +```markdown +## Deploying Application + +**Deployment location:** `/opt/[app-name]` + +**Steps:** +1. Create application directory +2. Transfer source code via rsync +3. Install application dependencies +4. Set ownership and permissions +5. Configure SELinux context + +```bash +ssh [target] "sudo mkdir -p /opt/[app-name]" +rsync -avz --exclude node_modules --exclude .git --exclude __pycache__ ./ [target]:/tmp/[app-name]-deploy/ +ssh [target] "sudo cp -r /tmp/[app-name]-deploy/* /opt/[app-name]/" +ssh [target] "cd /opt/[app-name] && npm install --production" # language-specific +ssh [target] "sudo chown -R [service-user]:[service-user] /opt/[app-name]" +ssh [target] "sudo semanage fcontext -a -t bin_t '/opt/[app-name](/.*)?'" +ssh [target] "sudo restorecon -Rv /opt/[app-name]" +``` + +Proceed with deployment? (yes/no) +``` + +**WAIT for user confirmation before proceeding.** + +### Phase 4b-3: Native Systemd Unit + +```markdown +## Systemd Service Configuration + +**Template to use:** `templates/systemd/systemd-native.service` + +**Variables to substitute:** +| Variable | Value | Notes | +|----------|-------|-------| +| `${APP_NAME}` | [app-name] | Application name | +| `${SERVICE_USER}` | [service-user] | User to run the service as | +| `${APP_PATH}` | /opt/[app-name] | Application install path | +| `${PORT}` | [container-port] | Application listen port | +| `${START_COMMAND}` | [see below] | Language-specific start command | + +**Start commands by language:** See [docs/rhel-deployment.md](../../docs/rhel-deployment.md) for language-specific systemd unit templates (Node.js, Python, Java, Go). + +**Target location:** `/etc/systemd/system/[app-name].service` + +**Note:** The template includes security hardening (NoNewPrivileges, ProtectSystem, ProtectHome, PrivateTmp). + +Proceed with creating this service? (yes/no) +``` + +**WAIT for user confirmation before proceeding.** + +**Steps to execute:** + +1. Read the template from `templates/systemd/systemd-native.service` +2. Substitute all variables with session state values +3. Transfer the generated unit file to the target host +4. Enable and start the service + +### Phase 4b-4: Firewall Configuration + +Same as container path - open required port with firewall-cmd. + +--- + +## COMPLETION (Both paths converge here) + +### Phase 5: Completion + +```markdown +## Deployment Complete! + +Your application is now running on [host]. + +**Application Summary:** +| Setting | Value | +|---------|-------| +| Name | [app-name] | +| Host | [host] | +| Strategy | [Container/Native] | +| Service | [app-name].service | + +**Access URLs:** +| Type | URL | +|------|-----| +| HTTP | http://[host]:[port] | +| SSH | ssh [user]@[host] | + +**Service Status:** [systemctl status output] + +**Quick Commands:** + +Show quick commands for: view logs (journalctl), restart/stop/status (systemctl), container logs (if container), and removal steps. +``` + +### Phase 5a: Handle Deployment Failure + +If the service fails to start or is not accessible: + +```markdown +## Deployment Failed + +The service did not start successfully. + +**Service Status:** [systemctl status output showing failure] + +**Recent Errors:** +| Time | Error | +|------|-------| +| [time] | [error from journalctl] | + +**Would you like me to diagnose the issue?** +1. **Debug RHEL** (`/debug-rhel`) - Full system diagnosis (systemd, journal, SELinux, firewall) +2. **Debug Container** (`/debug-container`) - Container state, logs, exit codes +3. **View full logs** - Complete journalctl output +4. **Check SELinux** - Quick SELinux denial check +5. **Check firewall** - Quick firewall port check +6. **Stop and clean up** + +Select an option: +``` + +**WAIT for user to select an option.** + +- If user selects "Debug RHEL" → Invoke `/debug-rhel` skill +- If user selects "Debug Container" → Invoke `/debug-container` skill +- After debugging → Offer to retry deployment + +## Dependencies + +### Required MCP Servers +- `lightspeed-mcp` (optional) - Red Hat Insights pre-deploy checks + +### Related Skills +- `/debug-rhel` - systemd failures, SELinux denials, firewall blocking +- `/debug-container` - Container startup issues on RHEL host + +### Reference Documentation +- [docs/rhel-deployment.md](../../docs/rhel-deployment.md) - Systemd templates, SELinux, firewall, runtime packages +- [docs/selinux-troubleshooting.md](../../docs/selinux-troubleshooting.md) - SELinux denial analysis and fixes +- [docs/debugging-patterns.md](../../docs/debugging-patterns.md) - Common error patterns and troubleshooting +- [docs/prerequisites.md](../../docs/prerequisites.md) - Required tools (ssh, podman) diff --git a/submissions/rh-developer-rhel-deploy/supportive/.mcp.json b/submissions/rh-developer-rhel-deploy/supportive/.mcp.json new file mode 100644 index 0000000..c46bd4c --- /dev/null +++ b/submissions/rh-developer-rhel-deploy/supportive/.mcp.json @@ -0,0 +1,12 @@ +{ + "mcpServers": { + "openshift": { + "command": "python3", + "args": ["/workspace/supportive/mcp-servers/mock-openshift-mcp.py"] + }, + "rhel-host": { + "command": "python3", + "args": ["/workspace/supportive/mcp-servers/mock-rhel-host-mcp.py"] + } + } +} diff --git a/submissions/rh-developer-rhel-deploy/supportive/mcp-servers/mock-openshift-mcp.py b/submissions/rh-developer-rhel-deploy/supportive/mcp-servers/mock-openshift-mcp.py new file mode 100644 index 0000000..dadb59f --- /dev/null +++ b/submissions/rh-developer-rhel-deploy/supportive/mcp-servers/mock-openshift-mcp.py @@ -0,0 +1,717 @@ +#!/usr/bin/env python3 +""" +Mock OpenShift MCP Server for rh-developer benchmark task. + +Simulates an OpenShift cluster with 3 namespaces, each containing a broken +deployment that requires different debugging skills to diagnose: + + 1. api-platform / api-service (Python FastAPI) + - S2I build succeeded, pod crashes at runtime + - Entry point is main.py (not app.py), no gunicorn installed + - Requires python-s2i-entrypoints.md knowledge + + 2. web-frontend / web-frontend (Node.js React) + - Pod in CrashLoopBackOff, exit code 137 (OOMKilled) + - Container memory limit 64Mi is too low for Node.js + - Requires debugging-patterns.md exit code knowledge + + 3. order-system / order-service (Java Quarkus) + - Pod running, Route returns 503 + - Service selector mismatch: app=order-svc vs pod label app=order-service + - Tekton PipelineRun failed, logs in step-build container + - Requires debug-network + debug-pipeline knowledge + +Also provides application source metadata for image recommendation. +""" + +from typing import Optional +from fastmcp import FastMCP + +mcp = FastMCP("openshift") + + +# --------------------------------------------------------------------------- +# Namespace / Project data +# --------------------------------------------------------------------------- + +NAMESPACES = [ + {"name": "api-platform", "status": "Active", "labels": {"app-type": "backend"}}, + {"name": "web-frontend", "status": "Active", "labels": {"app-type": "frontend"}}, + {"name": "order-system", "status": "Active", "labels": {"app-type": "backend"}}, +] + + +# --------------------------------------------------------------------------- +# Deployment data +# --------------------------------------------------------------------------- + +DEPLOYMENTS = { + "api-platform": [ + { + "name": "api-service", + "namespace": "api-platform", + "replicas": 1, + "available_replicas": 0, + "ready_replicas": 0, + "image": "image-registry.openshift-image-registry.svc:5000/api-platform/api-service:latest", + "containers": [ + { + "name": "api-service", + "image": "image-registry.openshift-image-registry.svc:5000/api-platform/api-service:latest", + "resources": { + "requests": {"cpu": "100m", "memory": "256Mi"}, + "limits": {"cpu": "500m", "memory": "512Mi"}, + }, + "env": [ + {"name": "APP_SCRIPT", "value": ""}, + {"name": "APP_FILE", "value": "main.py"}, + ], + } + ], + "labels": {"app": "api-service", "deployment": "api-service"}, + "strategy": "RollingUpdate", + "status": "Available=False (0/1 replicas ready)", + }, + ], + "web-frontend": [ + { + "name": "web-frontend", + "namespace": "web-frontend", + "replicas": 1, + "available_replicas": 0, + "ready_replicas": 0, + "image": "image-registry.openshift-image-registry.svc:5000/web-frontend/web-frontend:latest", + "containers": [ + { + "name": "web-frontend", + "image": "image-registry.openshift-image-registry.svc:5000/web-frontend/web-frontend:latest", + "resources": { + "requests": {"cpu": "50m", "memory": "32Mi"}, + "limits": {"cpu": "200m", "memory": "64Mi"}, + }, + } + ], + "labels": {"app": "web-frontend", "deployment": "web-frontend"}, + "strategy": "RollingUpdate", + "status": "Available=False (0/1 replicas ready)", + }, + ], + "order-system": [ + { + "name": "order-service", + "namespace": "order-system", + "replicas": 1, + "available_replicas": 1, + "ready_replicas": 1, + "image": "image-registry.openshift-image-registry.svc:5000/order-system/order-service:latest", + "containers": [ + { + "name": "order-service", + "image": "image-registry.openshift-image-registry.svc:5000/order-system/order-service:latest", + "resources": { + "requests": {"cpu": "200m", "memory": "512Mi"}, + "limits": {"cpu": "1", "memory": "1Gi"}, + }, + "ports": [{"containerPort": 8080, "protocol": "TCP"}], + } + ], + "labels": {"app": "order-service", "deployment": "order-service"}, + "strategy": "RollingUpdate", + "status": "Available=True (1/1 replicas ready)", + }, + ], +} + + +# --------------------------------------------------------------------------- +# Pod data +# --------------------------------------------------------------------------- + +PODS = { + "api-platform": [ + { + "name": "api-service-7b8f9d4c5-x2k9m", + "namespace": "api-platform", + "status": "CrashLoopBackOff", + "restart_count": 5, + "labels": {"app": "api-service", "deployment": "api-service"}, + "containers": [ + { + "name": "api-service", + "state": "Waiting", + "reason": "CrashLoopBackOff", + "last_state": { + "terminated": { + "exit_code": 1, + "reason": "Error", + "message": "Application exited with error", + } + }, + "ready": False, + "resources": { + "requests": {"cpu": "100m", "memory": "256Mi"}, + "limits": {"cpu": "500m", "memory": "512Mi"}, + }, + } + ], + }, + ], + "web-frontend": [ + { + "name": "web-frontend-6c5d8b7a9-p4n2j", + "namespace": "web-frontend", + "status": "CrashLoopBackOff", + "restart_count": 8, + "labels": {"app": "web-frontend", "deployment": "web-frontend"}, + "containers": [ + { + "name": "web-frontend", + "state": "Waiting", + "reason": "CrashLoopBackOff", + "last_state": { + "terminated": { + "exit_code": 137, + "reason": "OOMKilled", + "message": "Container exceeded memory limit", + } + }, + "ready": False, + "resources": { + "requests": {"cpu": "50m", "memory": "32Mi"}, + "limits": {"cpu": "200m", "memory": "64Mi"}, + }, + } + ], + }, + ], + "order-system": [ + { + "name": "order-service-5a4b3c2d1-h7j6k", + "namespace": "order-system", + "status": "Running", + "restart_count": 0, + "labels": {"app": "order-service", "deployment": "order-service"}, + "containers": [ + { + "name": "order-service", + "state": "Running", + "ready": True, + "ports": [{"containerPort": 8080}], + "resources": { + "requests": {"cpu": "200m", "memory": "512Mi"}, + "limits": {"cpu": "1", "memory": "1Gi"}, + }, + } + ], + }, + ], +} + + +# --------------------------------------------------------------------------- +# Pod logs +# --------------------------------------------------------------------------- + +POD_LOGS = { + "api-service-7b8f9d4c5-x2k9m": ( + "---> Running application from script (app.sh) ...\n" + "sh: app.sh: No such file or directory\n" + "---> Trying to run with gunicorn ...\n" + "Traceback (most recent call last):\n" + " File \"/opt/app-root/bin/gunicorn\", line 5, in \n" + " from gunicorn.app.wsgiapp import run\n" + "ModuleNotFoundError: No module named 'gunicorn'\n" + "---> Trying to run app.py ...\n" + "Error: Could not find '/opt/app-root/src/app.py'\n" + "---> Failed to find any valid entry point.\n" + " Set the APP_MODULE environment variable to specify your application callable.\n" + " Expected one of: app.sh, gunicorn with APP_MODULE, or app.py\n" + ), + "web-frontend-6c5d8b7a9-p4n2j": ( + "> react-app@1.0.0 start\n" + "> node server.js\n" + "\n" + "Server starting on port 3000...\n" + "Loading configuration...\n" + "Initializing middleware...\n" + "Killed\n" + ), + "order-service-5a4b3c2d1-h7j6k": ( + "__ ____ __ _____ ___ __ ____ ______ \n" + " --/ __ \\/ / / / _ | / _ \\/ //_/ / / / __/ \n" + " -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\\ \\ \n" + "--\\___\\_\\____/_/ |_/_/|_/_/|_|\\____/___/ \n" + "2026-02-15 10:30:15,234 INFO [io.quarkus] Quarkus 3.8.1 on JVM started in 2.345s.\n" + "2026-02-15 10:30:15,236 INFO [io.quarkus] Profile prod activated.\n" + "2026-02-15 10:30:15,237 INFO [io.quarkus] Installed features: [cdi, rest, smallrye-health]\n" + "2026-02-15 10:30:15,238 INFO [io.quarkus] Listening on: http://0.0.0.0:8080\n" + ), +} + + +# --------------------------------------------------------------------------- +# Build data +# --------------------------------------------------------------------------- + +BUILDS = { + "api-platform": [ + { + "name": "api-service-1", + "namespace": "api-platform", + "status": "Complete", + "source_type": "Git", + "source_uri": "https://github.com/example/api-service.git", + "strategy": "Source", + "builder_image": "image-registry.openshift-image-registry.svc:5000/openshift/python:3.11-ubi9", + "output_image": "image-registry.openshift-image-registry.svc:5000/api-platform/api-service:latest", + "duration": "2m15s", + }, + ], + "web-frontend": [ + { + "name": "web-frontend-1", + "namespace": "web-frontend", + "status": "Complete", + "source_type": "Git", + "source_uri": "https://github.com/example/web-frontend.git", + "strategy": "Source", + "builder_image": "image-registry.openshift-image-registry.svc:5000/openshift/nodejs:20-ubi9", + "output_image": "image-registry.openshift-image-registry.svc:5000/web-frontend/web-frontend:latest", + "duration": "3m42s", + }, + ], + "order-system": [ + { + "name": "order-service-1", + "namespace": "order-system", + "status": "Complete", + "source_type": "Git", + "source_uri": "https://github.com/example/order-service.git", + "strategy": "Source", + "builder_image": "image-registry.openshift-image-registry.svc:5000/openshift/openjdk-17:ubi9", + "output_image": "image-registry.openshift-image-registry.svc:5000/order-system/order-service:latest", + "duration": "4m08s", + }, + ], +} + +BUILD_LOGS = { + "api-service-1": ( + "===> STEP 1: Fetching source from https://github.com/example/api-service.git\n" + "Cloning into '/tmp/src'...\n" + "===> STEP 2: Pulling builder image python:3.11-ubi9\n" + "===> STEP 3: Running assemble script\n" + "---> Installing application source ...\n" + "---> Installing dependencies from requirements.txt ...\n" + "Collecting fastapi==0.109.0\n" + "Collecting uvicorn==0.27.0\n" + "Collecting pydantic==2.5.3\n" + "Successfully installed fastapi-0.109.0 uvicorn-0.27.0 pydantic-2.5.3\n" + "---> Assemble script complete.\n" + "===> STEP 4: Committing image\n" + "===> STEP 5: Pushing image to image-registry.openshift-image-registry.svc:5000/api-platform/api-service:latest\n" + "Push successful\n" + ), + "web-frontend-1": ( + "===> STEP 1: Fetching source from https://github.com/example/web-frontend.git\n" + "Cloning into '/tmp/src'...\n" + "===> STEP 2: Pulling builder image nodejs:20-ubi9\n" + "===> STEP 3: Running assemble script\n" + "---> Installing application source ...\n" + "---> Installing dependencies from package.json ...\n" + "---> Running build script: npm run build ...\n" + "---> Build complete.\n" + "===> STEP 4: Committing image\n" + "===> STEP 5: Pushing image to image-registry.openshift-image-registry.svc:5000/web-frontend/web-frontend:latest\n" + "Push successful\n" + ), + "order-service-1": ( + "===> STEP 1: Fetching source from https://github.com/example/order-service.git\n" + "Cloning into '/tmp/src'...\n" + "===> STEP 2: Pulling builder image openjdk-17:ubi9\n" + "===> STEP 3: Running assemble script\n" + "---> Installing application source ...\n" + "---> Building with Maven ...\n" + "[INFO] BUILD SUCCESS\n" + "---> Assemble script complete.\n" + "===> STEP 4: Committing image\n" + "===> STEP 5: Pushing image to image-registry.openshift-image-registry.svc:5000/order-system/order-service:latest\n" + "Push successful\n" + ), +} + + +# --------------------------------------------------------------------------- +# Service data +# --------------------------------------------------------------------------- + +SERVICES = { + "api-platform": [ + { + "name": "api-service", + "namespace": "api-platform", + "type": "ClusterIP", + "cluster_ip": "172.30.45.112", + "ports": [{"port": 8080, "target_port": 8080, "protocol": "TCP"}], + "selector": {"app": "api-service"}, + }, + ], + "web-frontend": [ + { + "name": "web-frontend", + "namespace": "web-frontend", + "type": "ClusterIP", + "cluster_ip": "172.30.89.201", + "ports": [{"port": 3000, "target_port": 3000, "protocol": "TCP"}], + "selector": {"app": "web-frontend"}, + }, + ], + "order-system": [ + { + "name": "order-service", + "namespace": "order-system", + "type": "ClusterIP", + "cluster_ip": "172.30.67.55", + "ports": [{"port": 8080, "target_port": 8080, "protocol": "TCP"}], + "selector": {"app": "order-svc"}, + }, + ], +} + + +# --------------------------------------------------------------------------- +# Route data +# --------------------------------------------------------------------------- + +ROUTES = { + "api-platform": [ + { + "name": "api-service", + "namespace": "api-platform", + "host": "api-service-api-platform.apps.cluster.example.com", + "path": "/", + "service": "api-service", + "port": 8080, + "tls_termination": "edge", + "status": "Admitted", + }, + ], + "web-frontend": [ + { + "name": "web-frontend", + "namespace": "web-frontend", + "host": "web-frontend-web-frontend.apps.cluster.example.com", + "path": "/", + "service": "web-frontend", + "port": 3000, + "tls_termination": "edge", + "status": "Admitted", + }, + ], + "order-system": [ + { + "name": "order-service", + "namespace": "order-system", + "host": "order-service-order-system.apps.cluster.example.com", + "path": "/", + "service": "order-service", + "port": 8080, + "tls_termination": "edge", + "status": "Admitted", + "conditions": [ + { + "type": "Admitted", + "status": "True", + "message": "Route admitted but backend returns 503 Service Unavailable", + } + ], + }, + ], +} + + +# --------------------------------------------------------------------------- +# Events +# --------------------------------------------------------------------------- + +EVENTS = { + "api-platform": [ + {"type": "Normal", "reason": "Created", "object": "Pod/api-service-7b8f9d4c5-x2k9m", + "message": "Created container api-service"}, + {"type": "Normal", "reason": "Started", "object": "Pod/api-service-7b8f9d4c5-x2k9m", + "message": "Started container api-service"}, + {"type": "Warning", "reason": "BackOff", "object": "Pod/api-service-7b8f9d4c5-x2k9m", + "message": "Back-off restarting failed container api-service"}, + ], + "web-frontend": [ + {"type": "Normal", "reason": "Created", "object": "Pod/web-frontend-6c5d8b7a9-p4n2j", + "message": "Created container web-frontend"}, + {"type": "Normal", "reason": "Started", "object": "Pod/web-frontend-6c5d8b7a9-p4n2j", + "message": "Started container web-frontend"}, + {"type": "Warning", "reason": "OOMKilled", "object": "Pod/web-frontend-6c5d8b7a9-p4n2j", + "message": "Container web-frontend was OOMKilled (exit code 137). Memory limit: 64Mi."}, + {"type": "Warning", "reason": "BackOff", "object": "Pod/web-frontend-6c5d8b7a9-p4n2j", + "message": "Back-off restarting failed container web-frontend"}, + ], + "order-system": [ + {"type": "Normal", "reason": "Created", "object": "Pod/order-service-5a4b3c2d1-h7j6k", + "message": "Created container order-service"}, + {"type": "Normal", "reason": "Started", "object": "Pod/order-service-5a4b3c2d1-h7j6k", + "message": "Started container order-service"}, + {"type": "Normal", "reason": "Scheduled", "object": "Pod/order-service-5a4b3c2d1-h7j6k", + "message": "Successfully assigned order-system/order-service-5a4b3c2d1-h7j6k to worker-2"}, + {"type": "Warning", "reason": "FailedPipelineRun", "object": "PipelineRun/order-service-deploy-run-7x2k", + "message": "PipelineRun failed at task 'integration-test'. Check step-build and step-test containers for logs."}, + ], +} + + +# --------------------------------------------------------------------------- +# Tekton pipeline data +# --------------------------------------------------------------------------- + +PIPELINE_RUNS = { + "order-system": [ + { + "name": "order-service-deploy-run-7x2k", + "namespace": "order-system", + "pipeline": "order-service-deploy", + "status": "Failed", + "start_time": "2026-02-15T09:15:00Z", + "completion_time": "2026-02-15T09:22:30Z", + "task_runs": [ + { + "name": "order-service-deploy-run-7x2k-build", + "task": "build", + "status": "Succeeded", + "steps": [ + {"name": "step-git-clone", "status": "Completed", "exit_code": 0}, + {"name": "step-build", "status": "Completed", "exit_code": 0}, + {"name": "step-push", "status": "Completed", "exit_code": 0}, + ], + }, + { + "name": "order-service-deploy-run-7x2k-deploy", + "task": "deploy", + "status": "Succeeded", + "steps": [ + {"name": "step-deploy", "status": "Completed", "exit_code": 0}, + ], + }, + { + "name": "order-service-deploy-run-7x2k-integration-test", + "task": "integration-test", + "status": "Failed", + "steps": [ + {"name": "step-test", "status": "Failed", "exit_code": 1, + "log": ( + "Running integration tests against order-service...\n" + "GET https://order-service-order-system.apps.cluster.example.com/api/health\n" + "Response: 503 Service Unavailable\n" + "FAIL: Health check returned 503, expected 200\n" + "Hint: Service endpoint is unreachable. Verify service routing.\n" + )}, + ], + }, + ], + }, + ], +} + + +# --------------------------------------------------------------------------- +# Application source metadata (for image recommendation) +# --------------------------------------------------------------------------- + +APP_SOURCES = { + "inventory-api": { + "name": "inventory-api", + "language": "Python", + "version": "3.11", + "framework": "Flask", + "entry_point": "app.py", + "dependencies": ["flask==3.0.0", "sqlalchemy==2.0.25", "gunicorn==21.2.0", "psycopg2-binary==2.9.9"], + "target": "production", + "has_dockerfile": False, + "has_tests": True, + "repo": "https://github.com/example/inventory-api.git", + }, + "customer-portal": { + "name": "customer-portal", + "language": "Node.js", + "version": "20", + "framework": "React (Next.js)", + "entry_point": "server.js", + "dependencies": ["next@14.1.0", "react@18.2.0", "express@4.18.2"], + "target": "production", + "has_dockerfile": False, + "has_tests": True, + "repo": "https://github.com/example/customer-portal.git", + }, + "payment-processor": { + "name": "payment-processor", + "language": "Java", + "version": "17", + "framework": "Quarkus", + "entry_point": "src/main/java/com/example/Application.java", + "build_tool": "Maven", + "dependencies": ["quarkus-rest", "quarkus-hibernate-orm-panache", "quarkus-jdbc-postgresql"], + "target": "production", + "has_dockerfile": False, + "has_tests": True, + "repo": "https://github.com/example/payment-processor.git", + "notes": "Quarkus application. Consider native compilation for production.", + }, +} + + +# --------------------------------------------------------------------------- +# MCP Tools +# --------------------------------------------------------------------------- + +@mcp.tool +def list_projects() -> dict: + """List all OpenShift projects (namespaces) in the cluster. + + Returns project names, status, and labels. + """ + return {"projects": NAMESPACES, "count": len(NAMESPACES)} + + +@mcp.tool +def get_deployments(namespace: str) -> dict: + """Get deployments in a namespace. + + Args: + namespace: The OpenShift namespace/project name. + """ + deps = DEPLOYMENTS.get(namespace, []) + return {"deployments": deps, "count": len(deps), "namespace": namespace} + + +@mcp.tool +def get_pods(namespace: str) -> dict: + """Get pods in a namespace with their status and container details. + + Args: + namespace: The OpenShift namespace/project name. + """ + pods = PODS.get(namespace, []) + return {"pods": pods, "count": len(pods), "namespace": namespace} + + +@mcp.tool +def pod_logs(pod_name: str, namespace: str, previous: bool = False) -> dict: + """Get logs from a pod. + + Args: + pod_name: Name of the pod. + namespace: The OpenShift namespace/project name. + previous: If True, get logs from the previous terminated container. + """ + logs = POD_LOGS.get(pod_name, f"No logs available for pod {pod_name}") + return {"pod": pod_name, "namespace": namespace, "logs": logs, "previous": previous} + + +@mcp.tool +def get_builds(namespace: str) -> dict: + """Get builds in a namespace. + + Args: + namespace: The OpenShift namespace/project name. + """ + builds = BUILDS.get(namespace, []) + return {"builds": builds, "count": len(builds), "namespace": namespace} + + +@mcp.tool +def get_build_log(build_name: str, namespace: str) -> dict: + """Get the log output from a build. + + Args: + build_name: Name of the build (e.g. 'api-service-1'). + namespace: The OpenShift namespace/project name. + """ + log = BUILD_LOGS.get(build_name, f"No build log found for {build_name}") + return {"build": build_name, "namespace": namespace, "log": log} + + +@mcp.tool +def get_services(namespace: str) -> dict: + """Get services in a namespace with their selectors and ports. + + Args: + namespace: The OpenShift namespace/project name. + """ + svcs = SERVICES.get(namespace, []) + return {"services": svcs, "count": len(svcs), "namespace": namespace} + + +@mcp.tool +def get_routes(namespace: str) -> dict: + """Get routes in a namespace. + + Args: + namespace: The OpenShift namespace/project name. + """ + routes = ROUTES.get(namespace, []) + return {"routes": routes, "count": len(routes), "namespace": namespace} + + +@mcp.tool +def get_events(namespace: str) -> dict: + """Get events in a namespace. + + Args: + namespace: The OpenShift namespace/project name. + """ + events = EVENTS.get(namespace, []) + return {"events": events, "count": len(events), "namespace": namespace} + + +@mcp.tool +def get_pipeline_runs(namespace: str) -> dict: + """Get Tekton PipelineRuns in a namespace. + + Args: + namespace: The OpenShift namespace/project name. + """ + runs = PIPELINE_RUNS.get(namespace, []) + return {"pipeline_runs": runs, "count": len(runs), "namespace": namespace} + + +@mcp.tool +def get_app_source_info(app_name: str) -> dict: + """Get detected source information for an application project. + + Returns language, framework, version, dependencies, and deployment target. + + Args: + app_name: Application name (e.g. 'inventory-api', 'customer-portal', 'payment-processor'). + """ + if app_name in APP_SOURCES: + return APP_SOURCES[app_name] + return {"error": f"Application '{app_name}' not found. Available: {list(APP_SOURCES.keys())}"} + + +@mcp.tool +def list_available_apps() -> dict: + """List all application projects available for analysis. + + Returns names and basic metadata for applications that need + image recommendations or deployment planning. + """ + apps = [] + for name, info in APP_SOURCES.items(): + apps.append({ + "name": name, + "language": info["language"], + "version": info["version"], + "framework": info["framework"], + "target": info["target"], + }) + return {"applications": apps, "count": len(apps)} + + +if __name__ == "__main__": + mcp.run() diff --git a/submissions/rh-developer-rhel-deploy/supportive/mcp-servers/mock-rhel-host-mcp.py b/submissions/rh-developer-rhel-deploy/supportive/mcp-servers/mock-rhel-host-mcp.py new file mode 100644 index 0000000..f10dd2f --- /dev/null +++ b/submissions/rh-developer-rhel-deploy/supportive/mcp-servers/mock-rhel-host-mcp.py @@ -0,0 +1,230 @@ +#!/usr/bin/env python3 +""" +Mock RHEL Host MCP Server for rh-developer rhel-deploy benchmark task. + +Simulates a RHEL 9.3 host with Podman 4.9.4 for container deployment planning. +Scenario: Deploy a Flask app container as a systemd service on port 8080. +""" + +from typing import Optional + +from fastmcp import FastMCP + +mcp = FastMCP("rhel-host") + +# Mock state +MOCK_SYSTEM_INFO = { + "os": "Red Hat Enterprise Linux 9.3 (Plow)", + "kernel": "5.14.0-362.18.1.el9_3.x86_64", + "architecture": "x86_64", + "podman_version": "podman version 4.9.4", + "selinux": "Enforcing", + "firewall": "running", +} + +MOCK_OPEN_PORTS = {8080} # Port 8080 opened for Flask app +MOCK_SERVICES = { + "flask-app": { + "name": "flask-app", + "active": "active", + "state": "running", + "enabled": True, + "description": "Flask application container", + }, + "container-flask-app": { + "name": "container-flask-app", + "active": "active", + "state": "running", + "enabled": True, + "description": "Podman container flask-app.service", + }, +} + +MOCK_PODMAN_PS = """CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +a1b2c3d4e5f6 quay.io/ubi9/python-311:latest flask run 2 hours ago Up 2 hours ago 0.0.0.0:8080->8080/tcp flask-app +""" + +MOCK_PODMAN_INSPECT = """[ + { + "Id": "a1b2c3d4e5f6", + "Name": "flask-app", + "State": { + "Status": "running", + "Running": true + }, + "Config": { + "Image": "quay.io/ubi9/python-311:latest", + "Cmd": ["flask", "run", "--host=0.0.0.0", "--port=8080"] + }, + "HostConfig": { + "PortBindings": { + "8080/tcp": [{"HostPort": "8080"}] + } + } + } +] +""" + + +def _match_command(cmd: str) -> Optional[str]: + """Return a command category for pattern matching.""" + cmd_lower = cmd.strip().lower() + if "podman pull" in cmd_lower: + return "podman_pull" + if "podman run" in cmd_lower: + return "podman_run" + if "podman ps" in cmd_lower or cmd_lower == "podman ps": + return "podman_ps" + if "podman inspect" in cmd_lower: + return "podman_inspect" + if "systemctl enable" in cmd_lower: + return "systemctl_enable" + if "systemctl start" in cmd_lower: + return "systemctl_start" + if "systemctl status" in cmd_lower: + return "systemctl_status" + if "firewall-cmd" in cmd_lower: + return "firewall_cmd" + if "semanage fcontext" in cmd_lower: + return "semanage_fcontext" + if "restorecon" in cmd_lower: + return "restorecon" + return None + + +@mcp.tool +def run_command(command: str) -> dict: + """Simulate running a shell command on a RHEL host. + + Supports common deployment patterns: podman, systemctl, firewall-cmd, semanage. + Returns realistic output for supported commands; error for unknown commands. + + Args: + command: The shell command to execute (e.g. 'podman ps', 'systemctl status flask-app'). + """ + kind = _match_command(command) + if kind == "podman_pull": + return { + "command": command, + "exit_code": 0, + "stdout": "Trying to pull quay.io/ubi9/python-311:latest...\nGetting image source signatures\nCopying blob sha256:...\nCopying config sha256:...\nWriting manifest to image destination\nStoring signatures\n", + "stderr": "", + } + if kind == "podman_run": + return { + "command": command, + "exit_code": 0, + "stdout": "a1b2c3d4e5f6", + "stderr": "", + } + if kind == "podman_ps": + return { + "command": command, + "exit_code": 0, + "stdout": MOCK_PODMAN_PS, + "stderr": "", + } + if kind == "podman_inspect": + return { + "command": command, + "exit_code": 0, + "stdout": MOCK_PODMAN_INSPECT, + "stderr": "", + } + if kind == "systemctl_enable": + return { + "command": command, + "exit_code": 0, + "stdout": "", + "stderr": "", + } + if kind == "systemctl_start": + return { + "command": command, + "exit_code": 0, + "stdout": "", + "stderr": "", + } + if kind == "systemctl_status": + return { + "command": command, + "exit_code": 0, + "stdout": """● flask-app.service - Flask application container + Loaded: loaded (/etc/systemd/system/flask-app.service; enabled) + Active: active (running) since Tue 2026-03-17 10:00:00 UTC; 2h ago + Main PID: 1234 (conmon) + Tasks: 8 + Memory: 128.0M + CGroup: /system.slice/flask-app.service +""", + "stderr": "", + } + if kind == "firewall_cmd": + return { + "command": command, + "exit_code": 0, + "stdout": "success\n", + "stderr": "", + } + if kind == "semanage_fcontext": + return { + "command": command, + "exit_code": 0, + "stdout": "", + "stderr": "", + } + if kind == "restorecon": + return { + "command": command, + "exit_code": 0, + "stdout": "", + "stderr": "", + } + return { + "command": command, + "exit_code": 1, + "stdout": "", + "stderr": f"Error: Unknown or unsupported command. Supported: podman pull/run/ps/inspect, systemctl enable/start/status, firewall-cmd, semanage fcontext, restorecon.", + } + + +@mcp.tool +def get_system_info() -> dict: + """Return RHEL version, architecture, and Podman version for the target host.""" + return MOCK_SYSTEM_INFO.copy() + + +@mcp.tool +def check_service(name: str) -> dict: + """Return systemd service status for a given service name. + + Args: + name: Service name (e.g. 'flask-app', 'container-flask-app'). + """ + svc = MOCK_SERVICES.get(name) + if svc: + return {"service": name, "status": svc, "found": True} + return { + "service": name, + "found": False, + "error": f"Service '{name}' not found. Known services: {list(MOCK_SERVICES.keys())}", + } + + +@mcp.tool +def check_port(port: int) -> dict: + """Return whether a port is open in the firewall. + + Args: + port: Port number to check (e.g. 8080). + """ + open_port = port in MOCK_OPEN_PORTS + return { + "port": port, + "open": open_port, + "message": f"Port {port} is {'open' if open_port else 'closed'} in firewall.", + } + + +if __name__ == "__main__": + mcp.run() diff --git a/submissions/rh-developer-rhel-deploy/templates/buildconfig.yaml.template b/submissions/rh-developer-rhel-deploy/templates/buildconfig.yaml.template new file mode 100644 index 0000000..b3294eb --- /dev/null +++ b/submissions/rh-developer-rhel-deploy/templates/buildconfig.yaml.template @@ -0,0 +1,38 @@ +apiVersion: build.openshift.io/v1 +kind: BuildConfig +metadata: + name: ${APP_NAME} + namespace: ${NAMESPACE} + labels: + app: ${APP_NAME} + app.kubernetes.io/name: ${APP_NAME} + app.kubernetes.io/component: build + app.kubernetes.io/part-of: ${APP_NAME} +spec: + source: + type: Git + git: + uri: ${GIT_URL} + ref: ${GIT_BRANCH} + strategy: + type: Source + sourceStrategy: + from: + kind: DockerImage + name: ${BUILDER_IMAGE} + env: [] + output: + to: + kind: ImageStreamTag + name: ${APP_NAME}:latest + triggers: + - type: ConfigChange + - type: ImageChange + runPolicy: Serial + resources: + limits: + memory: "1Gi" + cpu: "1" + requests: + memory: "512Mi" + cpu: "500m" diff --git a/submissions/rh-developer-rhel-deploy/templates/deployment.yaml.template b/submissions/rh-developer-rhel-deploy/templates/deployment.yaml.template new file mode 100644 index 0000000..eb3b481 --- /dev/null +++ b/submissions/rh-developer-rhel-deploy/templates/deployment.yaml.template @@ -0,0 +1,61 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ${APP_NAME} + namespace: ${NAMESPACE} + labels: + app: ${APP_NAME} + app.kubernetes.io/name: ${APP_NAME} + app.kubernetes.io/component: application + app.kubernetes.io/part-of: ${APP_NAME} + annotations: + image.openshift.io/triggers: | + [{"from":{"kind":"ImageStreamTag","name":"${APP_NAME}:latest"},"fieldPath":"spec.template.spec.containers[0].image"}] +spec: + replicas: ${REPLICAS} + selector: + matchLabels: + app: ${APP_NAME} + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + template: + metadata: + labels: + app: ${APP_NAME} + app.kubernetes.io/name: ${APP_NAME} + spec: + containers: + - name: ${APP_NAME} + image: image-registry.openshift-image-registry.svc:5000/${NAMESPACE}/${APP_NAME}:latest + ports: + - containerPort: ${CONTAINER_PORT} + protocol: TCP + resources: + requests: + memory: "128Mi" + cpu: "100m" + limits: + memory: "512Mi" + cpu: "500m" + livenessProbe: + httpGet: + path: / + port: ${CONTAINER_PORT} + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 3 + failureThreshold: 3 + readinessProbe: + httpGet: + path: / + port: ${CONTAINER_PORT} + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 3 + env: [] + restartPolicy: Always + terminationGracePeriodSeconds: 30 diff --git a/submissions/rh-developer-rhel-deploy/templates/helm/Chart.yaml.template b/submissions/rh-developer-rhel-deploy/templates/helm/Chart.yaml.template new file mode 100644 index 0000000..1aa22dd --- /dev/null +++ b/submissions/rh-developer-rhel-deploy/templates/helm/Chart.yaml.template @@ -0,0 +1,13 @@ +apiVersion: v2 +name: ${APP_NAME} +description: ${APP_DESCRIPTION} +type: application +version: 0.1.0 +appVersion: "${APP_VERSION}" +keywords: + - ${LANGUAGE} + - ${FRAMEWORK} + - openshift +maintainers: + - name: ${MAINTAINER_NAME} + email: ${MAINTAINER_EMAIL} diff --git a/submissions/rh-developer-rhel-deploy/templates/helm/templates/NOTES.txt.template b/submissions/rh-developer-rhel-deploy/templates/helm/templates/NOTES.txt.template new file mode 100644 index 0000000..154e628 --- /dev/null +++ b/submissions/rh-developer-rhel-deploy/templates/helm/templates/NOTES.txt.template @@ -0,0 +1,32 @@ +Congratulations! Your application {{ include "${APP_NAME}.fullname" . }} has been deployed. + +{{- if .Values.route.enabled }} + +Access your application at: +{{- if .Values.route.host }} + https://{{ .Values.route.host }} +{{- else }} + Run: oc get route {{ include "${APP_NAME}.fullname" . }} -o jsonpath='{.spec.host}' +{{- end }} + +{{- else }} + +Your application is available internally at: + {{ include "${APP_NAME}.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.service.port }} + +To expose it externally, create a Route or set route.enabled=true. + +{{- end }} + +Useful commands: + # View pods + oc get pods -l app.kubernetes.io/name={{ include "${APP_NAME}.name" . }} + + # View logs + oc logs -l app.kubernetes.io/name={{ include "${APP_NAME}.name" . }} -f + + # Upgrade release + helm upgrade {{ .Release.Name }} ./{{ .Chart.Name }} -f values.yaml + + # Uninstall release + helm uninstall {{ .Release.Name }} diff --git a/submissions/rh-developer-rhel-deploy/templates/helm/templates/_helpers.tpl.template b/submissions/rh-developer-rhel-deploy/templates/helm/templates/_helpers.tpl.template new file mode 100644 index 0000000..15873b1 --- /dev/null +++ b/submissions/rh-developer-rhel-deploy/templates/helm/templates/_helpers.tpl.template @@ -0,0 +1,60 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "${APP_NAME}.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "${APP_NAME}.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "${APP_NAME}.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "${APP_NAME}.labels" -}} +helm.sh/chart: {{ include "${APP_NAME}.chart" . }} +{{ include "${APP_NAME}.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "${APP_NAME}.selectorLabels" -}} +app.kubernetes.io/name: {{ include "${APP_NAME}.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "${APP_NAME}.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "${APP_NAME}.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/submissions/rh-developer-rhel-deploy/templates/helm/templates/deployment.yaml.template b/submissions/rh-developer-rhel-deploy/templates/helm/templates/deployment.yaml.template new file mode 100644 index 0000000..a6cbd86 --- /dev/null +++ b/submissions/rh-developer-rhel-deploy/templates/helm/templates/deployment.yaml.template @@ -0,0 +1,61 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "${APP_NAME}.fullname" . }} + labels: + {{- include "${APP_NAME}.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "${APP_NAME}.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "${APP_NAME}.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "${APP_NAME}.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.service.port }} + protocol: TCP + livenessProbe: + {{- toYaml .Values.livenessProbe | nindent 12 }} + readinessProbe: + {{- toYaml .Values.readinessProbe | nindent 12 }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.env }} + env: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/submissions/rh-developer-rhel-deploy/templates/helm/templates/route.yaml.template b/submissions/rh-developer-rhel-deploy/templates/helm/templates/route.yaml.template new file mode 100644 index 0000000..e2bab29 --- /dev/null +++ b/submissions/rh-developer-rhel-deploy/templates/helm/templates/route.yaml.template @@ -0,0 +1,24 @@ +{{- if .Values.route.enabled }} +apiVersion: route.openshift.io/v1 +kind: Route +metadata: + name: {{ include "${APP_NAME}.fullname" . }} + labels: + {{- include "${APP_NAME}.labels" . | nindent 4 }} +spec: + {{- if .Values.route.host }} + host: {{ .Values.route.host }} + {{- end }} + to: + kind: Service + name: {{ include "${APP_NAME}.fullname" . }} + weight: 100 + port: + targetPort: http + {{- with .Values.route.tls }} + tls: + termination: {{ .termination }} + insecureEdgeTerminationPolicy: {{ .insecureEdgeTerminationPolicy }} + {{- end }} + wildcardPolicy: None +{{- end }} diff --git a/submissions/rh-developer-rhel-deploy/templates/helm/templates/service.yaml.template b/submissions/rh-developer-rhel-deploy/templates/helm/templates/service.yaml.template new file mode 100644 index 0000000..837bc88 --- /dev/null +++ b/submissions/rh-developer-rhel-deploy/templates/helm/templates/service.yaml.template @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "${APP_NAME}.fullname" . }} + labels: + {{- include "${APP_NAME}.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "${APP_NAME}.selectorLabels" . | nindent 4 }} diff --git a/submissions/rh-developer-rhel-deploy/templates/helm/values.yaml.template b/submissions/rh-developer-rhel-deploy/templates/helm/values.yaml.template new file mode 100644 index 0000000..1cca601 --- /dev/null +++ b/submissions/rh-developer-rhel-deploy/templates/helm/values.yaml.template @@ -0,0 +1,67 @@ +# Default values for ${APP_NAME} +replicaCount: 1 + +image: + repository: ${IMAGE_REPOSITORY} + pullPolicy: IfNotPresent + tag: "${IMAGE_TAG}" + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + create: true + annotations: {} + name: "" + +podAnnotations: {} +podSecurityContext: {} +securityContext: {} + +service: + type: ClusterIP + port: ${CONTAINER_PORT} + +route: + enabled: true + host: "" + tls: + termination: edge + insecureEdgeTerminationPolicy: Redirect + +resources: + requests: + memory: "128Mi" + cpu: "100m" + limits: + memory: "512Mi" + cpu: "500m" + +livenessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 30 + periodSeconds: 10 + +readinessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 5 + periodSeconds: 5 + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 5 + targetCPUUtilizationPercentage: 80 + +nodeSelector: {} +tolerations: [] +affinity: {} + +env: [] +# - name: MY_VAR +# value: "my-value" diff --git a/submissions/rh-developer-rhel-deploy/templates/imagestream.yaml.template b/submissions/rh-developer-rhel-deploy/templates/imagestream.yaml.template new file mode 100644 index 0000000..4657219 --- /dev/null +++ b/submissions/rh-developer-rhel-deploy/templates/imagestream.yaml.template @@ -0,0 +1,13 @@ +apiVersion: image.openshift.io/v1 +kind: ImageStream +metadata: + name: ${APP_NAME} + namespace: ${NAMESPACE} + labels: + app: ${APP_NAME} + app.kubernetes.io/name: ${APP_NAME} + app.kubernetes.io/component: image + app.kubernetes.io/part-of: ${APP_NAME} +spec: + lookupPolicy: + local: false diff --git a/submissions/rh-developer-rhel-deploy/templates/route.yaml.template b/submissions/rh-developer-rhel-deploy/templates/route.yaml.template new file mode 100644 index 0000000..7c53d2e --- /dev/null +++ b/submissions/rh-developer-rhel-deploy/templates/route.yaml.template @@ -0,0 +1,21 @@ +apiVersion: route.openshift.io/v1 +kind: Route +metadata: + name: ${APP_NAME} + namespace: ${NAMESPACE} + labels: + app: ${APP_NAME} + app.kubernetes.io/name: ${APP_NAME} + app.kubernetes.io/component: route + app.kubernetes.io/part-of: ${APP_NAME} +spec: + to: + kind: Service + name: ${APP_NAME} + weight: 100 + port: + targetPort: http + tls: + termination: edge + insecureEdgeTerminationPolicy: Redirect + wildcardPolicy: None diff --git a/submissions/rh-developer-rhel-deploy/templates/service.yaml.template b/submissions/rh-developer-rhel-deploy/templates/service.yaml.template new file mode 100644 index 0000000..7e1cf37 --- /dev/null +++ b/submissions/rh-developer-rhel-deploy/templates/service.yaml.template @@ -0,0 +1,20 @@ +apiVersion: v1 +kind: Service +metadata: + name: ${APP_NAME} + namespace: ${NAMESPACE} + labels: + app: ${APP_NAME} + app.kubernetes.io/name: ${APP_NAME} + app.kubernetes.io/component: service + app.kubernetes.io/part-of: ${APP_NAME} +spec: + selector: + app: ${APP_NAME} + ports: + - name: http + port: ${CONTAINER_PORT} + targetPort: ${CONTAINER_PORT} + protocol: TCP + type: ClusterIP + sessionAffinity: None diff --git a/submissions/rh-developer-rhel-deploy/templates/systemd/systemd-container-rootful.service b/submissions/rh-developer-rhel-deploy/templates/systemd/systemd-container-rootful.service new file mode 100644 index 0000000..c1e8fe8 --- /dev/null +++ b/submissions/rh-developer-rhel-deploy/templates/systemd/systemd-container-rootful.service @@ -0,0 +1,27 @@ +# Rootful Podman container managed by systemd (system service) +# Location: /etc/systemd/system/${APP_NAME}.service +# +# Variables to replace: +# ${APP_NAME} - Application name +# ${PORT} - Port number (used for both host and container binding) +# ${IMAGE} - Container image reference + +[Unit] +Description=${APP_NAME} Container +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +Restart=always +RestartSec=5 +ExecStartPre=-/usr/bin/podman stop -t 10 ${APP_NAME} +ExecStartPre=-/usr/bin/podman rm ${APP_NAME} +ExecStart=/usr/bin/podman run --name ${APP_NAME} \ + -p ${PORT}:${PORT} \ + --rm \ + ${IMAGE} +ExecStop=/usr/bin/podman stop -t 10 ${APP_NAME} + +[Install] +WantedBy=multi-user.target diff --git a/submissions/rh-developer-rhel-deploy/templates/systemd/systemd-container-rootless.service b/submissions/rh-developer-rhel-deploy/templates/systemd/systemd-container-rootless.service new file mode 100644 index 0000000..ca9dc37 --- /dev/null +++ b/submissions/rh-developer-rhel-deploy/templates/systemd/systemd-container-rootless.service @@ -0,0 +1,27 @@ +# Rootless Podman container managed by systemd (user service) +# Location: ~/.config/systemd/user/${APP_NAME}.service +# +# Variables to replace: +# ${APP_NAME} - Application name +# ${PORT} - Port number (used for both host and container binding) +# ${IMAGE} - Container image reference + +[Unit] +Description=${APP_NAME} Container +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +Restart=always +RestartSec=5 +ExecStartPre=-/usr/bin/podman stop -t 10 ${APP_NAME} +ExecStartPre=-/usr/bin/podman rm ${APP_NAME} +ExecStart=/usr/bin/podman run --name ${APP_NAME} \ + -p ${PORT}:${PORT} \ + --rm \ + ${IMAGE} +ExecStop=/usr/bin/podman stop -t 10 ${APP_NAME} + +[Install] +WantedBy=default.target diff --git a/submissions/rh-developer-rhel-deploy/templates/systemd/systemd-native.service b/submissions/rh-developer-rhel-deploy/templates/systemd/systemd-native.service new file mode 100644 index 0000000..c55cfc0 --- /dev/null +++ b/submissions/rh-developer-rhel-deploy/templates/systemd/systemd-native.service @@ -0,0 +1,39 @@ +# Native application managed by systemd (system service) +# Location: /etc/systemd/system/${APP_NAME}.service +# +# Variables to replace: +# ${APP_NAME} - Application name +# ${SERVICE_USER} - User to run the service as +# ${APP_PATH} - Application install path (e.g., /opt/app-name) +# ${PORT} - Application listen port +# ${START_COMMAND} - Application start command +# +# Start command examples by language: +# Node.js: /usr/bin/node ${APP_PATH}/server.js +# Python: /usr/bin/python3 ${APP_PATH}/app.py +# Java: /usr/bin/java -jar ${APP_PATH}/app.jar +# Go: ${APP_PATH}/binary-name + +[Unit] +Description=${APP_NAME} Service +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User=${SERVICE_USER} +WorkingDirectory=${APP_PATH} +Environment=PORT=${PORT} +ExecStart=${START_COMMAND} +Restart=always +RestartSec=5 + +# Security hardening +NoNewPrivileges=true +ProtectSystem=strict +ProtectHome=true +PrivateTmp=true +ReadWritePaths=${APP_PATH} + +[Install] +WantedBy=multi-user.target diff --git a/submissions/rh-developer-rhel-deploy/tests/llm_judge.py b/submissions/rh-developer-rhel-deploy/tests/llm_judge.py new file mode 100644 index 0000000..5d7ba0d --- /dev/null +++ b/submissions/rh-developer-rhel-deploy/tests/llm_judge.py @@ -0,0 +1,108 @@ +import json +import os +import sys +import time +from pathlib import Path + +try: + from anthropic import Anthropic +except ImportError: + print("ERROR: anthropic package not installed") + sys.exit(1) + +CRITERIA = [ + { + "id": "selinux_volume_labels", + "file": "/root/report.md", + "question": "Does the report explain SELinux volume labels :z (shared, multi-container) and :Z (private) for Podman bind mounts?", + "reference": "A skilled report uses :z or :Z suffixes on volume mounts and explains the difference. An unskilled report skips SELinux mount context." + }, + { + "id": "rootless_systemd", + "file": "/root/report.md", + "question": "Does the report address rootless systemd service configuration (~/.config/systemd/user/) and loginctl enable-linger?", + "reference": "A skilled report shows the rootless systemd path and explains enable-linger for services to survive logout. An unskilled report only shows rootful /etc/systemd/system/ paths." + }, + { + "id": "semanage_fcontext_restorecon", + "file": "/root/report.md", + "question": "Does the report use semanage fcontext + restorecon for setting SELinux file contexts on application directories?", + "reference": "A skilled report uses 'semanage fcontext -a -t bin_t' plus 'restorecon -Rv' for app files. An unskilled report skips file-level SELinux context management." + } +] + +SYSTEM_PROMPT = ( + "You are an evaluator for a cloud operations benchmark. You will be given a " + "file produced by an AI agent, a yes/no question about its contents, and a " + "REFERENCE ANSWER that describes what a correct, skilled response looks like.\n\n" + "Rules:\n" + "- Answer ONLY with a JSON object: {\"pass\": true} or {\"pass\": false}\n" + "- Base your answer strictly on what is written in the file content\n" + "- Do not infer or assume knowledge the agent did not demonstrate\n" + "- Use the REFERENCE ANSWER to calibrate what counts as a pass\n" + "- Accept different phrasings that convey the SAME concept\n" + "- Do NOT use your own general knowledge to fill gaps" +) + + +def judge_criterion(client, model, criterion): + filepath = criterion["file"] + if not Path(filepath).exists(): + return {"id": criterion["id"], "pass": False, "reason": "file not found"} + content = Path(filepath).read_text() + if len(content) > 50000: + content = content[:50000] + "\n... (truncated)" + reference = criterion.get("reference", "") + ref_block = f"\n\n## Reference Answer\n{reference}" if reference else "" + max_retries = 3 + for attempt in range(max_retries): + try: + response = client.messages.create( + model=model, max_tokens=64, system=SYSTEM_PROMPT, + messages=[{"role": "user", "content": ( + f"## File: {filepath}\n\n```\n{content}\n```\n\n" + f"## Question\n{criterion['question']}{ref_block}" + )}], + ) + text = response.content[0].text.strip() + if "{" in text: + text = text[text.index("{"):text.rindex("}") + 1] + result = json.loads(text) + return {"id": criterion["id"], "pass": bool(result.get("pass", False))} + except Exception as e: + if attempt < max_retries - 1: + time.sleep(5 * (attempt + 1)) + else: + return {"id": criterion["id"], "pass": False, "reason": str(e)} + + +def main(): + api_key = os.getenv("ANTHROPIC_API_KEY") + base_url = os.getenv("ANTHROPIC_BASE_URL") + model = os.getenv("LLM_JUDGE_MODEL", "claude-haiku-4-5") + if not api_key: + print("ERROR: ANTHROPIC_API_KEY not set, skipping LLM judge") + json.dump({"criteria": [], "passed": 0, "total": 0, "score": 0.0}, + open("/logs/verifier/llm_judge.json", "w"), indent=2) + return + client_kwargs = {"api_key": api_key} + if base_url: + client_kwargs["base_url"] = base_url + client = Anthropic(**client_kwargs) + results = [] + print(f"=== LLM Judge: evaluating {len(CRITERIA)} criteria with {model} ===") + for criterion in CRITERIA: + print(f" Evaluating: {criterion['id']} ...", end=" ", flush=True) + result = judge_criterion(client, model, criterion) + results.append(result) + print("PASS" if result["pass"] else "FAIL") + passed = sum(1 for r in results if r["pass"]) + total = len(results) + score = round(passed / total, 4) if total > 0 else 0.0 + print(f"=== LLM Judge: {passed}/{total} criteria passed (score={score}) ===") + Path("/logs/verifier/llm_judge.json").write_text(json.dumps( + {"criteria": results, "passed": passed, "total": total, "score": score}, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/submissions/rh-developer-rhel-deploy/tests/test_outputs.py b/submissions/rh-developer-rhel-deploy/tests/test_outputs.py new file mode 100644 index 0000000..b4a1c09 --- /dev/null +++ b/submissions/rh-developer-rhel-deploy/tests/test_outputs.py @@ -0,0 +1,98 @@ +""" +Tests for rh-developer__rhel-deploy per-skill evaluation. +Baseline tests: report structure. +Skill-dependent tests: methodology checks that require skill knowledge. +""" +import os +import pytest + +REPORT = "/root/report.md" + + +def read_report(): + if not os.path.exists(REPORT): + pytest.fail(f"Required file not found: {REPORT}") + with open(REPORT) as f: + return f.read() + + +class TestBaseline: + def test_report_exists(self): + assert os.path.exists(REPORT), "report.md must exist" + + def test_mentions_rhel_or_podman(self): + content = read_report().lower() + assert "rhel" in content or "podman" in content, "report should mention RHEL or Podman" + + def test_report_has_structure(self): + content = read_report() + assert len(content) > 150, "report should have substantial content" + + +class TestSkillDependent: + def test_selinux_volume_labels(self): + """Skill teaches SELinux volume labels: :z = shared (relabeled for multi-container), + :Z = private. Without skill, agents skip SELinux mount context.""" + c = read_report() + assert ":z" in c or ":Z" in c or "selinux" in c.lower(), ( + "should address SELinux volume labels (:z shared, :Z private)" + ) + + def test_rootless_systemd_path(self): + """Skill teaches rootless systemd service location ~/.config/systemd/user/ + vs /etc/systemd/system/ for rootful. Without skill, agents only know rootful.""" + c = read_report() + assert ".config/systemd/user" in c or "rootless" in c.lower(), ( + "should address rootless systemd path (~/.config/systemd/user/)" + ) + + def test_enable_linger(self): + """Skill teaches loginctl enable-linger required for rootless user services + to survive logout. Without skill, agents miss this requirement.""" + c = read_report().lower() + assert "enable-linger" in c or "loginctl" in c or "linger" in c, ( + "should mention loginctl enable-linger for rootless services" + ) + + def test_semanage_fcontext(self): + """Skill teaches semanage fcontext + restorecon for setting SELinux context + on application files. Without skill, agents skip file context management.""" + c = read_report().lower() + assert ("semanage fcontext" in c or "semanage" in c) and ( + "restorecon" in c or "fcontext" in c + ), "should use semanage fcontext + restorecon for file SELinux context" + + def test_firewall_port(self): + """Skill teaches firewall-cmd for opening application ports.""" + c = read_report().lower() + assert "firewall-cmd" in c or ("firewall" in c and "port" in c), ( + "should address firewall port configuration" + ) + + def test_systemd_hardening_directives(self): + """Docs teach systemd hardening directives: NoNewPrivileges=true, + ProtectSystem=strict, ReadWritePaths. Without docs, agents create basic + unit files without security hardening.""" + c = read_report() + assert any(t in c for t in [ + "NoNewPrivileges", "ProtectSystem", "ReadWritePaths", + "PrivateTmp", "ProtectHome", + ]) or "hardening" in c.lower(), ( + "should include systemd hardening directives (NoNewPrivileges, ProtectSystem)" + ) + + def test_container_security_practices(self): + """Skill teaches defence-in-depth for containers: dropping capabilities, + resource limits, read-only root, security options. Without skill, + agents deploy containers with default security settings.""" + c = read_report().lower() + practices = sum(1 for t in [ + "cap-drop", "cap_drop", "capability", + "--read-only", "read-only root", + "resource limit", "memory", "cpus", + "no-new-privileges", "security-opt", + ] if t in c) + assert practices >= 2, ( + "should address at least 2 container security practices " + "(capability dropping, resource limits, read-only root, security options)" + ) From f9f2c6458356207076d441e1f01352e691476bee Mon Sep 17 00:00:00 2001 From: gziv Date: Mon, 11 May 2026 14:14:45 +0300 Subject: [PATCH 2/7] fix: use /solution/report.md instead of /root/report.md Harbor OpenShift runs as non-root (uid=1001390000). /root is not writable. /solution/ is the correct writable emptyDir mount. --- submissions/rh-developer-rhel-deploy/instruction.md | 2 +- submissions/rh-developer-rhel-deploy/tests/llm_judge.py | 6 +++--- submissions/rh-developer-rhel-deploy/tests/test_outputs.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/submissions/rh-developer-rhel-deploy/instruction.md b/submissions/rh-developer-rhel-deploy/instruction.md index b7c3a70..5ae6834 100644 --- a/submissions/rh-developer-rhel-deploy/instruction.md +++ b/submissions/rh-developer-rhel-deploy/instruction.md @@ -7,6 +7,6 @@ You are a Red Hat developer. Plan the deployment of a containerized application - Address security hardening (SELinux, privilege restrictions) - Include volume mounts and networking configuration -Use available tools to examine the environment. Document your methodology, configuration, and deployment plan in `/root/report.md`. +Use available tools to examine the environment. Document your methodology, configuration, and deployment plan in `/solution/report.md`. If reference documentation or skills are available in this environment, consult them before beginning work. diff --git a/submissions/rh-developer-rhel-deploy/tests/llm_judge.py b/submissions/rh-developer-rhel-deploy/tests/llm_judge.py index 5d7ba0d..c7ef39e 100644 --- a/submissions/rh-developer-rhel-deploy/tests/llm_judge.py +++ b/submissions/rh-developer-rhel-deploy/tests/llm_judge.py @@ -13,19 +13,19 @@ CRITERIA = [ { "id": "selinux_volume_labels", - "file": "/root/report.md", + "file": "/solution/report.md", "question": "Does the report explain SELinux volume labels :z (shared, multi-container) and :Z (private) for Podman bind mounts?", "reference": "A skilled report uses :z or :Z suffixes on volume mounts and explains the difference. An unskilled report skips SELinux mount context." }, { "id": "rootless_systemd", - "file": "/root/report.md", + "file": "/solution/report.md", "question": "Does the report address rootless systemd service configuration (~/.config/systemd/user/) and loginctl enable-linger?", "reference": "A skilled report shows the rootless systemd path and explains enable-linger for services to survive logout. An unskilled report only shows rootful /etc/systemd/system/ paths." }, { "id": "semanage_fcontext_restorecon", - "file": "/root/report.md", + "file": "/solution/report.md", "question": "Does the report use semanage fcontext + restorecon for setting SELinux file contexts on application directories?", "reference": "A skilled report uses 'semanage fcontext -a -t bin_t' plus 'restorecon -Rv' for app files. An unskilled report skips file-level SELinux context management." } diff --git a/submissions/rh-developer-rhel-deploy/tests/test_outputs.py b/submissions/rh-developer-rhel-deploy/tests/test_outputs.py index b4a1c09..011d739 100644 --- a/submissions/rh-developer-rhel-deploy/tests/test_outputs.py +++ b/submissions/rh-developer-rhel-deploy/tests/test_outputs.py @@ -6,7 +6,7 @@ import os import pytest -REPORT = "/root/report.md" +REPORT = "/solution/report.md" def read_report(): From 2681312117a7cf359f24fc2147192d8741ea00f8 Mon Sep 17 00:00:00 2001 From: gziv Date: Tue, 12 May 2026 18:26:42 +0300 Subject: [PATCH 3/7] retrigger: queue runner From 3c6a073248bc9cfb91f05b290c6898bc6f4bc0b2 Mon Sep 17 00:00:00 2001 From: gziv Date: Thu, 14 May 2026 20:23:05 +0300 Subject: [PATCH 4/7] retrigger: queue runner From 2f63b85d7551353f9940971cf0e8442b5a92f100 Mon Sep 17 00:00:00 2001 From: gziv Date: Thu, 14 May 2026 20:23:06 +0300 Subject: [PATCH 5/7] retrigger: queue runner From 97cdd5303239d74510bc885a8a69699f4a9239ab Mon Sep 17 00:00:00 2001 From: gziv Date: Sat, 16 May 2026 17:03:59 +0300 Subject: [PATCH 6/7] fix: rewrite rhel-deploy tests for skill differentiation Replace generic keyword checks with skill-specific assertions: rootless vs rootful strategy, systemd user path, enable-linger, mock host profile (RHEL 9.3/Podman 4.9.4), SELinux :z/:Z labels, systemd hardening directives. Co-authored-by: Cursor --- .../tests/test_outputs.py | 105 ++++++++---------- 1 file changed, 46 insertions(+), 59 deletions(-) diff --git a/submissions/rh-developer-rhel-deploy/tests/test_outputs.py b/submissions/rh-developer-rhel-deploy/tests/test_outputs.py index 011d739..a328e1b 100644 --- a/submissions/rh-developer-rhel-deploy/tests/test_outputs.py +++ b/submissions/rh-developer-rhel-deploy/tests/test_outputs.py @@ -1,7 +1,8 @@ """ -Tests for rh-developer__rhel-deploy per-skill evaluation. -Baseline tests: report structure. -Skill-dependent tests: methodology checks that require skill knowledge. +Tests for rh-developer-rhel-deploy per-skill evaluation. + +Only differentiating tests kept — dead-weight tests where both +control and treatment pass have been removed. """ import os import pytest @@ -20,79 +21,65 @@ class TestBaseline: def test_report_exists(self): assert os.path.exists(REPORT), "report.md must exist" - def test_mentions_rhel_or_podman(self): - content = read_report().lower() - assert "rhel" in content or "podman" in content, "report should mention RHEL or Podman" - - def test_report_has_structure(self): - content = read_report() - assert len(content) > 150, "report should have substantial content" - class TestSkillDependent: - def test_selinux_volume_labels(self): - """Skill teaches SELinux volume labels: :z = shared (relabeled for multi-container), - :Z = private. Without skill, agents skip SELinux mount context.""" - c = read_report() - assert ":z" in c or ":Z" in c or "selinux" in c.lower(), ( - "should address SELinux volume labels (:z shared, :Z private)" + def test_rootless_vs_rootful_strategy(self): + """Skill teaches the dual strategy fork: Container (Podman+systemd) + vs Native (dnf+systemd), with rootless vs rootful decision. + Without skill, agents only describe rootful deployment.""" + c = read_report().lower() + assert "rootless" in c, ( + "must address rootless deployment option" ) def test_rootless_systemd_path(self): - """Skill teaches rootless systemd service location ~/.config/systemd/user/ - vs /etc/systemd/system/ for rootful. Without skill, agents only know rootful.""" + """Skill teaches rootless systemd service location + ~/.config/systemd/user/ for user-level units. Without skill, + agents only know /etc/systemd/system/ for rootful.""" c = read_report() - assert ".config/systemd/user" in c or "rootless" in c.lower(), ( - "should address rootless systemd path (~/.config/systemd/user/)" + assert ".config/systemd/user" in c, ( + "must reference rootless systemd path (~/.config/systemd/user/)" ) def test_enable_linger(self): - """Skill teaches loginctl enable-linger required for rootless user services - to survive logout. Without skill, agents miss this requirement.""" + """Skill teaches loginctl enable-linger as required for rootless + user services to survive logout. Without skill, agents miss + this critical requirement.""" c = read_report().lower() - assert "enable-linger" in c or "loginctl" in c or "linger" in c, ( - "should mention loginctl enable-linger for rootless services" + assert "enable-linger" in c or "loginctl" in c, ( + "must mention loginctl enable-linger for rootless services" ) - def test_semanage_fcontext(self): - """Skill teaches semanage fcontext + restorecon for setting SELinux context - on application files. Without skill, agents skip file context management.""" - c = read_report().lower() - assert ("semanage fcontext" in c or "semanage" in c) and ( - "restorecon" in c or "fcontext" in c - ), "should use semanage fcontext + restorecon for file SELinux context" + def test_mock_host_profile(self): + """Skill-equipped agents discover the host profile via MCP: + RHEL 9.3, Podman 4.9.4. Without skill, agents write generic + plans without host-specific context.""" + c = read_report() + assert "9.3" in c or "4.9.4" in c, ( + "must reference host profile from MCP " + "(RHEL 9.3, Podman 4.9.4)" + ) - def test_firewall_port(self): - """Skill teaches firewall-cmd for opening application ports.""" - c = read_report().lower() - assert "firewall-cmd" in c or ("firewall" in c and "port" in c), ( - "should address firewall port configuration" + def test_selinux_volume_labels(self): + """Skill teaches SELinux volume labels :z (shared) and :Z + (private) for container mounts. Without skill, agents skip + SELinux mount context entirely.""" + c = read_report() + assert ":z" in c or ":Z" in c, ( + "must specify SELinux volume labels (:z shared, :Z private)" ) def test_systemd_hardening_directives(self): - """Docs teach systemd hardening directives: NoNewPrivileges=true, - ProtectSystem=strict, ReadWritePaths. Without docs, agents create basic - unit files without security hardening.""" + """Skill teaches specific systemd hardening directives: + NoNewPrivileges, ProtectSystem, ReadWritePaths. Without skill, + agents create basic unit files without security hardening.""" c = read_report() - assert any(t in c for t in [ + directives = [ "NoNewPrivileges", "ProtectSystem", "ReadWritePaths", "PrivateTmp", "ProtectHome", - ]) or "hardening" in c.lower(), ( - "should include systemd hardening directives (NoNewPrivileges, ProtectSystem)" - ) - - def test_container_security_practices(self): - """Skill teaches defence-in-depth for containers: dropping capabilities, - resource limits, read-only root, security options. Without skill, - agents deploy containers with default security settings.""" - c = read_report().lower() - practices = sum(1 for t in [ - "cap-drop", "cap_drop", "capability", - "--read-only", "read-only root", - "resource limit", "memory", "cpus", - "no-new-privileges", "security-opt", - ] if t in c) - assert practices >= 2, ( - "should address at least 2 container security practices " - "(capability dropping, resource limits, read-only root, security options)" + ] + found = sum(1 for d in directives if d in c) + assert found >= 2, ( + "must include at least 2 systemd hardening directives " + "(NoNewPrivileges, ProtectSystem, ReadWritePaths, etc.)" ) From 203bc6e69b1b4eef87c88c974bbebf6390be4605 Mon Sep 17 00:00:00 2001 From: gziv Date: Sat, 16 May 2026 21:43:28 +0300 Subject: [PATCH 7/7] retrigger: queue runner