diff --git a/submissions/rh-developer-detect-project/CLAUDE.md b/submissions/rh-developer-detect-project/CLAUDE.md new file mode 100644 index 0000000..8a11178 --- /dev/null +++ b/submissions/rh-developer-detect-project/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-detect-project/docs/builder-images.md b/submissions/rh-developer-detect-project/docs/builder-images.md new file mode 100644 index 0000000..6561c5c --- /dev/null +++ b/submissions/rh-developer-detect-project/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-detect-project/docs/debugging-patterns.md b/submissions/rh-developer-detect-project/docs/debugging-patterns.md new file mode 100644 index 0000000..2863d55 --- /dev/null +++ b/submissions/rh-developer-detect-project/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-detect-project/docs/dynamic-validation.md b/submissions/rh-developer-detect-project/docs/dynamic-validation.md new file mode 100644 index 0000000..a027f0c --- /dev/null +++ b/submissions/rh-developer-detect-project/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-detect-project/docs/human-in-the-loop.md b/submissions/rh-developer-detect-project/docs/human-in-the-loop.md new file mode 100644 index 0000000..696fccf --- /dev/null +++ b/submissions/rh-developer-detect-project/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-detect-project/docs/image-selection-criteria.md b/submissions/rh-developer-detect-project/docs/image-selection-criteria.md new file mode 100644 index 0000000..184b7f5 --- /dev/null +++ b/submissions/rh-developer-detect-project/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-detect-project/docs/prerequisites.md b/submissions/rh-developer-detect-project/docs/prerequisites.md new file mode 100644 index 0000000..d81a9b5 --- /dev/null +++ b/submissions/rh-developer-detect-project/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-detect-project/docs/python-s2i-entrypoints.md b/submissions/rh-developer-detect-project/docs/python-s2i-entrypoints.md new file mode 100644 index 0000000..bb29398 --- /dev/null +++ b/submissions/rh-developer-detect-project/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-detect-project/docs/rhel-deployment.md b/submissions/rh-developer-detect-project/docs/rhel-deployment.md new file mode 100644 index 0000000..06eda27 --- /dev/null +++ b/submissions/rh-developer-detect-project/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-detect-project/docs/selinux-troubleshooting.md b/submissions/rh-developer-detect-project/docs/selinux-troubleshooting.md new file mode 100644 index 0000000..9942375 --- /dev/null +++ b/submissions/rh-developer-detect-project/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-detect-project/instruction.md b/submissions/rh-developer-detect-project/instruction.md new file mode 100644 index 0000000..8898fc9 --- /dev/null +++ b/submissions/rh-developer-detect-project/instruction.md @@ -0,0 +1,13 @@ +# Project Detection Task + +You are a Red Hat developer. A colleague has handed you a source repository and asked you to figure out what it is and how to deploy it to OpenShift. + +## Requirements +- Examine the project files to identify the programming language, version, and package manager +- Detect the application framework (e.g., Flask, Express, Spring) and build system +- Based on what you find, recommend a deployment strategy: which builder image or base image to use, what build process to follow, and how the application should be started +- Explain your reasoning for the recommended approach + +Document your analysis and deployment recommendation in `/solution/report.md`. + +Use available tools to examine the environment. If reference documentation or skills are available in this environment, consult them before beginning work. diff --git a/submissions/rh-developer-detect-project/mcps.json b/submissions/rh-developer-detect-project/mcps.json new file mode 100644 index 0000000..19046e0 --- /dev/null +++ b/submissions/rh-developer-detect-project/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-detect-project/metadata.yaml b/submissions/rh-developer-detect-project/metadata.yaml new file mode 100644 index 0000000..d4bb5b4 --- /dev/null +++ b/submissions/rh-developer-detect-project/metadata.yaml @@ -0,0 +1,13 @@ +name: rh-developer-detect-project +description: "rh-developer detect-project Skill Evaluation" +persona: rh-developer +version: "1.0.0" +generation_mode: manual +tags: + - rh-developer + - detect-project +cpus: 2 +memory_mb: 2048 +storage_mb: 10240 +experiment: + n_trials: 3 diff --git a/submissions/rh-developer-detect-project/skills/detect-project/SKILL.md b/submissions/rh-developer-detect-project/skills/detect-project/SKILL.md new file mode 100644 index 0000000..d1a18f7 --- /dev/null +++ b/submissions/rh-developer-detect-project/skills/detect-project/SKILL.md @@ -0,0 +1,278 @@ +--- +name: detect-project +description: | + Analyze a project folder or GitHub repository to detect programming language, framework, and version requirements. Use this skill when containerizing an application, selecting an S2I builder image, deploying to OpenShift or RHEL, or determining a project's tech stack. Supports Node.js, Python, Java, Go, Ruby, .NET, PHP, and Perl. Triggers on /detect-project command or when user needs build strategy recommendations. Run before /s2i-build or /rhel-deploy. +model: inherit +color: cyan +license: Apache-2.0 +metadata: + user_invocable: "true" +--- + +# /detect-project Skill + +## Critical Restrictions +- **DO NOT CLONE** remote repositories unless the user explicitly selects the "Clone & Inspect" option. +- **ALWAYS** use `github-mcp-server` tools (`list_directory`, `get_file_contents`) for initial analysis of remote URLs. +- **NEVER** assume you have permission to write to the local filesystem for analysis purposes. + +Analyze the project to detect language/framework and recommend a build strategy. This skill handles both local project directories and remote Git repositories. + +## When to Use This Skill + +- User wants to containerize or deploy an application and needs language/framework detection +- User asks what tech stack a project uses or needs a build strategy recommendation +- Run before `/s2i-build`, `/recommend-image`, or `/rhel-deploy` to identify project type + +## Critical: Human-in-the-Loop Requirements + +See [Human-in-the-Loop Requirements](../../docs/human-in-the-loop.md) for mandatory checkpoint behavior. + +## Workflow + +### Step 1: Context Analysis + +**Scenario A: Local Files Available** +If you are in a project directory with source code: +1. Proceed to **Step 2: Scan Project Files**. + +**Scenario B: Remote Git URL Provided** +If the user provided a Git URL (e.g., `https://github.com/...`): + +Use the **github-mcp-server** to analyze the repository directly without cloning: + +1. Use `mcp_github_get_file_contents(owner, repo, path="/")` to list repository contents +2. Read key files using `fetch_mcp_resource` with URI format: `repo://{owner}/{repo}/contents/{file-path}` + - Example: `repo://myorg/myrepo/contents/package.json` +3. Proceed with analysis as if local files + +```markdown +## Analyzing Remote Repository + +I'm inspecting the repository: `[git-url]` + +Using GitHub API to analyze the project structure... + +[Use github MCP to get_file_contents for indicator files] + +**Files Found:** +- [list files from repo root] + +[Continue with Step 2: Scan Project Files using the remote file contents] +``` + +If GitHub MCP is unavailable or repo is private without access: + +```markdown +## Remote Repository Access + +I see you want to deploy from: `[git-url]` + +I couldn't access the repository directly. Options: + +1. **Remote S2I Build** (Recommended for standard apps) + - OpenShift will clone and build the code directly. + - I need you to confirm the language/framework. + +2. **Remote Podman Build** (Recommended if Containerfile/Dockerfile exists) + - OpenShift will use the Containerfile/Dockerfile in the repo. + - Best if you already have a custom build process. + +3. **Clone & Inspect** + - I will clone the repo locally to analyze it first. + - This helps if you're unsure about the project details. + +**Which approach do you prefer?** +``` + +**WAIT for user confirmation before proceeding.** + +**Scenario C: No Context** +If no files and no URL: +1. Ask the user for the Git URL or to navigate to a project folder. + +### Step 2: Scan Project Files (Local Only) + +Look for these indicator files in the project root: + +| File | Language | Framework Hint | +|---|----|----| +| `Chart.yaml` | Helm Chart | Existing Helm deployment available | +| `package.json` | Node.js | Check for next, angular, vue, react | +| `pom.xml` | Java | Check for spring-boot, quarkus deps | +| `build.gradle` / `build.gradle.kts` | Java | Check for spring, quarkus plugins | +| `requirements.txt` | Python | - | +| `Pipfile` | Python | Pipenv | +| `pyproject.toml` | Python | Poetry or modern Python | +| `go.mod` | Go | - | +| `Gemfile` | Ruby | Check for rails | +| `composer.json` | PHP | Check for laravel, symfony | +| `*.csproj` / `*.sln` | .NET | - | +| `Cargo.toml` | Rust | No official S2I | +| `Dockerfile` / `Containerfile` | Pre-containerized | May not need S2I | + +### Helm Chart Detection + +Also check for Helm charts in these locations (in order): + +| Priority | Path | Description | +|----------|------|-------------| +| 1 | `./Chart.yaml` | Root directory | +| 2 | `./chart/Chart.yaml` | Chart subdirectory | +| 3 | `./charts/*/Chart.yaml` | Charts directory | +| 4 | `./helm/Chart.yaml` | Helm subdirectory | +| 5 | `./deploy/helm/Chart.yaml` | Deploy directory | + +If Chart.yaml is found, parse it to extract: +- `name`: Chart name +- `version`: Chart version (SemVer) +- `appVersion`: Application version +- `description`: Chart description + +Also check for: +- `values.yaml`: Default configuration +- `templates/`: Template files + +### Step 3: Detect Version Requirements + +For each detected language, extract version info: + +**Node.js:** +- Check `engines.node` in package.json +- Example: `"engines": { "node": ">=18" }` + +**Python:** +- Check `python_requires` in pyproject.toml +- Check `runtime.txt` for version +- Check `.python-version` file + +**Java:** +- Check `` or `` in pom.xml +- Check `sourceCompatibility` in build.gradle + +**Go:** +- Check `go` directive in go.mod +- Example: `go 1.21` + +### Step 4: Detect Framework + +Look for framework-specific indicators: + +**Node.js frameworks:** +- `next.config.js` or `next.config.mjs` → Next.js +- `angular.json` → Angular +- `vue.config.js` or `vite.config.ts` with vue → Vue.js +- `remix.config.js` → Remix + +**Java frameworks:** +- `quarkus` in dependencies → Quarkus +- `spring-boot` in dependencies → Spring Boot +- `micronaut` in dependencies → Micronaut + +**Python frameworks:** +- `django` in requirements → Django +- `flask` in requirements → Flask +- `fastapi` in requirements → FastAPI + +### Step 4.5: Detect Python Entry Point (Python projects only) + +For Python projects, detect the application entry point to ensure proper S2I configuration: + +**Check for entry point files (in order of S2I preference):** +1. `app.py` - Default S2I Python entry point (no config needed) +2. `application.py` - Alternative default +3. `wsgi.py` - WSGI module +4. `main.py` - Common alternative (requires APP_MODULE config) +5. Any file with `if __name__ == "__main__"` and Flask/FastAPI app + +**Check requirements.txt/Pipfile/pyproject.toml for WSGI server:** +- `gunicorn` - Required for APP_MODULE to work with S2I Python +- `uwsgi` - Alternative WSGI server + +### Step 5: Present Findings + +Format your response: + +```markdown +## Project Analysis Results + +**Detected Language:** [Language] +**Framework:** [Framework or "None detected"] +**Version:** [Version or "Not specified"] + +**Detection Confidence:** [High/Medium/Low] +- High: Clear indicator file with version info +- Medium: Indicator file found but no version specified +- Low: Multiple conflicting indicators or unusual setup + +**Indicator Files Found:** +- [list of files] + +--- + +**Recommended S2I Builder Image:** +`registry.access.redhat.com/ubi9/[image-name]` + +**Why this image:** +- [Brief explanation] + +**Alternative Options:** +1. `[alternative-1]` - [when to choose] +2. `[alternative-2]` - [when to choose] + +--- + +**Suggested App Name:** `[derived-name]` +(based on [folder name / package.json name / pom artifactId]) + +--- + +**Image Selection Options:** +- **quick** - Use the recommended image above (good for most cases) +- **smart** - Run `/recommend-image` for use-case aware selection (production vs dev, security, performance) + +Please confirm: +1. Is the detected language/framework correct? +2. Image selection: quick or smart? +3. Is the app name acceptable? + +Type 'yes' to confirm all with quick image selection, 'smart' for tailored recommendation, or tell me what to change. +``` + +**WAIT for user confirmation before proceeding.** + +- If user says "yes" → Save configuration with quick image selection +- If user says "smart" → Invoke `/recommend-image` skill +- If user provides corrections → Update values and show again for confirmation + +**Note:** If the user selects "smart", invoke the `/recommend-image` skill with the detected `LANGUAGE`, `FRAMEWORK`, and `VERSION` values. + +## Output Variables + +After successful detection, these values should be available for other skills: + +| Variable | Description | Example | +|----|----|---| +| `APP_NAME` | Application name | `my-nodejs-app` | +| `LANGUAGE` | Detected language | `nodejs` | +| `FRAMEWORK` | Detected framework | `express` | +| `VERSION` | Language version | `20` | +| `BUILDER_IMAGE` | Full S2I image reference | `registry.access.redhat.com/ubi9/nodejs-20` | +| `BUILD_STRATEGY` | Build strategy | `Source` (S2I) or `Podman` | +| `CONTAINER_PORT` | Application listen port | `8080` | +| `HELM_CHART_PATH` | Path to Helm chart | `./chart` | + +## Dependencies + +### Required MCP Servers +- `github` - Remote repository analysis via GitHub API (for URL-based detection) + +### Related Skills +- `/s2i-build` - Build with the detected S2I builder image +- `/recommend-image` - Advanced image selection based on detection results +- `/rhel-deploy` - Deploy to RHEL using detected project info + +### Reference Documentation +- [docs/builder-images.md](../../docs/builder-images.md) - Language detection matrix, version-to-image mapping, S2I builder selection +- [docs/python-s2i-entrypoints.md](../../docs/python-s2i-entrypoints.md) - Python entry point detection, APP_MODULE configuration +- [docs/prerequisites.md](../../docs/prerequisites.md) - Required tools (git) diff --git a/submissions/rh-developer-detect-project/supportive/.mcp.json b/submissions/rh-developer-detect-project/supportive/.mcp.json new file mode 100644 index 0000000..53738df --- /dev/null +++ b/submissions/rh-developer-detect-project/supportive/.mcp.json @@ -0,0 +1,8 @@ +{ + "mcpServers": { + "openshift": { + "command": "python3", + "args": ["/workspace/supportive/mcp-servers/mock-openshift-mcp.py"] + } + } +} diff --git a/submissions/rh-developer-detect-project/supportive/mcp-servers/mock-openshift-mcp.py b/submissions/rh-developer-detect-project/supportive/mcp-servers/mock-openshift-mcp.py new file mode 100644 index 0000000..dadb59f --- /dev/null +++ b/submissions/rh-developer-detect-project/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-detect-project/supportive/sample-project/.s2i/environment b/submissions/rh-developer-detect-project/supportive/sample-project/.s2i/environment new file mode 100644 index 0000000..a16a265 --- /dev/null +++ b/submissions/rh-developer-detect-project/supportive/sample-project/.s2i/environment @@ -0,0 +1 @@ +APP_FILE=app.py diff --git a/submissions/rh-developer-detect-project/supportive/sample-project/Dockerfile b/submissions/rh-developer-detect-project/supportive/sample-project/Dockerfile new file mode 100644 index 0000000..a7fb87b --- /dev/null +++ b/submissions/rh-developer-detect-project/supportive/sample-project/Dockerfile @@ -0,0 +1,9 @@ +FROM python:3.11-slim + +WORKDIR /app +COPY requirements.txt . +RUN pip install -r requirements.txt +COPY . . + +EXPOSE 8080 +CMD ["gunicorn", "-b", "0.0.0.0:8080", "app:app"] diff --git a/submissions/rh-developer-detect-project/supportive/sample-project/app.py b/submissions/rh-developer-detect-project/supportive/sample-project/app.py new file mode 100644 index 0000000..4761fe8 --- /dev/null +++ b/submissions/rh-developer-detect-project/supportive/sample-project/app.py @@ -0,0 +1,12 @@ +from flask import Flask + +app = Flask(__name__) + + +@app.route("/") +def hello(): + return "Hello, World!" + + +if __name__ == "__main__": + app.run(host="0.0.0.0", port=8080) diff --git a/submissions/rh-developer-detect-project/supportive/sample-project/requirements.txt b/submissions/rh-developer-detect-project/supportive/sample-project/requirements.txt new file mode 100644 index 0000000..cb04ebd --- /dev/null +++ b/submissions/rh-developer-detect-project/supportive/sample-project/requirements.txt @@ -0,0 +1,3 @@ +flask +gunicorn +psycopg2-binary diff --git a/submissions/rh-developer-detect-project/supportive/sample-project/tests/test_app.py b/submissions/rh-developer-detect-project/supportive/sample-project/tests/test_app.py new file mode 100644 index 0000000..5e8fbc9 --- /dev/null +++ b/submissions/rh-developer-detect-project/supportive/sample-project/tests/test_app.py @@ -0,0 +1,9 @@ +import pytest +from app import app + + +def test_hello(): + with app.test_client() as client: + r = client.get("/") + assert r.status_code == 200 + assert b"Hello" in r.data diff --git a/submissions/rh-developer-detect-project/templates/buildconfig.yaml.template b/submissions/rh-developer-detect-project/templates/buildconfig.yaml.template new file mode 100644 index 0000000..b3294eb --- /dev/null +++ b/submissions/rh-developer-detect-project/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-detect-project/templates/deployment.yaml.template b/submissions/rh-developer-detect-project/templates/deployment.yaml.template new file mode 100644 index 0000000..eb3b481 --- /dev/null +++ b/submissions/rh-developer-detect-project/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-detect-project/templates/helm/Chart.yaml.template b/submissions/rh-developer-detect-project/templates/helm/Chart.yaml.template new file mode 100644 index 0000000..1aa22dd --- /dev/null +++ b/submissions/rh-developer-detect-project/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-detect-project/templates/helm/templates/NOTES.txt.template b/submissions/rh-developer-detect-project/templates/helm/templates/NOTES.txt.template new file mode 100644 index 0000000..154e628 --- /dev/null +++ b/submissions/rh-developer-detect-project/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-detect-project/templates/helm/templates/_helpers.tpl.template b/submissions/rh-developer-detect-project/templates/helm/templates/_helpers.tpl.template new file mode 100644 index 0000000..15873b1 --- /dev/null +++ b/submissions/rh-developer-detect-project/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-detect-project/templates/helm/templates/deployment.yaml.template b/submissions/rh-developer-detect-project/templates/helm/templates/deployment.yaml.template new file mode 100644 index 0000000..a6cbd86 --- /dev/null +++ b/submissions/rh-developer-detect-project/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-detect-project/templates/helm/templates/route.yaml.template b/submissions/rh-developer-detect-project/templates/helm/templates/route.yaml.template new file mode 100644 index 0000000..e2bab29 --- /dev/null +++ b/submissions/rh-developer-detect-project/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-detect-project/templates/helm/templates/service.yaml.template b/submissions/rh-developer-detect-project/templates/helm/templates/service.yaml.template new file mode 100644 index 0000000..837bc88 --- /dev/null +++ b/submissions/rh-developer-detect-project/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-detect-project/templates/helm/values.yaml.template b/submissions/rh-developer-detect-project/templates/helm/values.yaml.template new file mode 100644 index 0000000..1cca601 --- /dev/null +++ b/submissions/rh-developer-detect-project/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-detect-project/templates/imagestream.yaml.template b/submissions/rh-developer-detect-project/templates/imagestream.yaml.template new file mode 100644 index 0000000..4657219 --- /dev/null +++ b/submissions/rh-developer-detect-project/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-detect-project/templates/route.yaml.template b/submissions/rh-developer-detect-project/templates/route.yaml.template new file mode 100644 index 0000000..7c53d2e --- /dev/null +++ b/submissions/rh-developer-detect-project/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-detect-project/templates/service.yaml.template b/submissions/rh-developer-detect-project/templates/service.yaml.template new file mode 100644 index 0000000..7e1cf37 --- /dev/null +++ b/submissions/rh-developer-detect-project/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-detect-project/templates/systemd/systemd-container-rootful.service b/submissions/rh-developer-detect-project/templates/systemd/systemd-container-rootful.service new file mode 100644 index 0000000..c1e8fe8 --- /dev/null +++ b/submissions/rh-developer-detect-project/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-detect-project/templates/systemd/systemd-container-rootless.service b/submissions/rh-developer-detect-project/templates/systemd/systemd-container-rootless.service new file mode 100644 index 0000000..ca9dc37 --- /dev/null +++ b/submissions/rh-developer-detect-project/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-detect-project/templates/systemd/systemd-native.service b/submissions/rh-developer-detect-project/templates/systemd/systemd-native.service new file mode 100644 index 0000000..c55cfc0 --- /dev/null +++ b/submissions/rh-developer-detect-project/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-detect-project/tests/llm_judge.py b/submissions/rh-developer-detect-project/tests/llm_judge.py new file mode 100644 index 0000000..eb2efbf --- /dev/null +++ b/submissions/rh-developer-detect-project/tests/llm_judge.py @@ -0,0 +1,102 @@ +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": "s2i_entry_point_sequence", + "file": "/solution/report.md", + "question": "Does the report describe the S2I Python builder's entry point detection order — specifically mentioning that the builder checks for files like app.sh before falling back to app.py, and how app.py being the default entry point affects startup?", + "reference": "A skilled report describes the S2I Python startup sequence (check app.sh first, then application.py, then app.py) and explains that since app.py is found, gunicorn will serve it automatically. An unskilled report mentions app.py as the entry point without describing the detection sequence the builder follows." + }, + { + "id": "app_module_gunicorn_link", + "file": "/solution/report.md", + "question": "Does the report explain the connection between gunicorn in requirements.txt and APP_MODULE configuration for the S2I Python builder — specifically that gunicorn is required for APP_MODULE to work?", + "reference": "A skilled report connects gunicorn to APP_MODULE, explaining that the S2I Python builder needs gunicorn in requirements.txt to serve the app specified by APP_MODULE (e.g., app:app). An unskilled report mentions gunicorn as a generic web server without connecting it to S2I builder mechanics." + } +] + +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-detect-project/tests/test_outputs.py b/submissions/rh-developer-detect-project/tests/test_outputs.py new file mode 100644 index 0000000..df50431 --- /dev/null +++ b/submissions/rh-developer-detect-project/tests/test_outputs.py @@ -0,0 +1,53 @@ +""" +Tests for rh-developer-detect-project per-skill evaluation. + +Reduced to strongest differentiating tests. Removed tests where both +agents fail equally (s2i_entry_point, chart_yaml, builder_image_output). +""" +import os +import pytest + +REPORT = "/solution/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" + + +class TestSkillDependent: + def test_app_module_format(self): + """Skill teaches APP_MODULE format 'module:callable' (e.g., app:app) + for S2I Python. Without skill, agents don't know this S2I builder + configuration variable.""" + c = read_report() + assert "APP_MODULE" in c or "app_module" in c.lower(), ( + "must specify APP_MODULE configuration for S2I Python" + ) + + def test_gunicorn_s2i_coupling(self): + """Skill teaches that gunicorn must be in requirements.txt for + the S2I Python builder to use APP_MODULE. Without skill, agents + mention gunicorn generically without the S2I connection.""" + c = read_report().lower() + assert "gunicorn" in c and ("s2i" in c or "app_module" in c or "builder" in c), ( + "must connect gunicorn to S2I/APP_MODULE builder requirement" + ) + + def test_mock_app_metadata(self): + """Skill-equipped agents use MCP to discover app metadata from + the cluster (inventory-api, customer-portal). Without skill, + agents analyze only local files.""" + c = read_report().lower() + apps = ["inventory-api", "customer-portal", "payment-processor"] + found = sum(1 for a in apps if a in c) + assert found >= 1, ( + "must reference cluster app metadata discovered via MCP" + )