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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
193 changes: 193 additions & 0 deletions .claude/commands/prepare.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
---
description: Prepare a dev-scripts cluster config with a resolved OpenShift release image
---

You are preparing an OpenShift cluster configuration for deployment via dev-scripts. This skill resolves a release image, generates a config file, and validates the environment — but never deploys.

## Arguments

`/prepare <topology> <method> <medium> <version...> [key=value...]`

**Positional (required):**
- **topology**: `tna` / `arbiter`, `tnf` / `fencing`, or `sno`
- **method**: `ipi` or `agent`
- **medium**: `aws` or `external`
- **version**: free text mapped to a resolver spec (see below)

**Optional key=value overrides:**
- `ip-stack=v4|v6|v4v6` (default: v4)
- `arch=x86_64|aarch64` (override auto-detection)
- `force=true` (overwrite existing config)
- `ds-repo=URL` (dev-scripts fork URL)
- `ds-branch=BRANCH` (dev-scripts fork branch)

## Version Mapping

| User says | Resolver spec |
|-----------|---------------|
| `4.21 nightly` | `4.21-nightly` |
| `latest 4.22 EC/RC` | `4.22-prerelease` |
| `4.22 EC` | `4.22-ec` |
| `4.20` or `4.20 GA` | `4.20` |
| `4.20.5` | `4.20.5` |
| Contains `/` or `@sha256:` | `--pullspec` (explicit) |

## Non-Interactive Contract

When all positional arguments are present, **never prompt open-endedly**. If prerequisites are missing, emit one consolidated failure block listing each issue with its fix, then stop:

```
Missing prerequisites:
- pull-secret.json: run /setup dev-scripts
- CI Token: run /setup, CI Token section
- inventory.ini: run /setup external (for external medium)
```

Interactive prompting is only for humans invoking with missing arguments.

## Scope

**Dev-scripts only** (v1). No kcli, assisted, or baremetal-adopt. If the user asks for kcli or assisted, tell them it's out of scope and point to the relevant make targets.

## CI Token Sourcing

1. Check existing `config/config_*.sh` files for an active `export CI_TOKEN="..."` line (any topology)
2. If found and not a placeholder (`<PASTE`), reuse it silently
3. If not found: ask the human / fail the agent path with a pointer to `/setup`
4. **Never echo the token into chat output**

## Architecture Determination

1. `arch=` key wins if provided
2. medium=aws: read `EC2_INSTANCE_TYPE` from `config/instance.env` — Graviton families (`c7g`, `m7g`, `r7g`, `c6g`, `m6g`, `r6g`, etc.) → `aarch64`; otherwise `x86_64`
3. medium=external: default `x86_64` with a note that `arch=aarch64` can override

## Constraint Matrix

| Constraint | Effect |
|------------|--------|
| aarch64 + ip-stack != v4 | **Blocked** — IPv6/dual-stack unsupported on ARM |
| aarch64 + -multi image | **Blocked** — explicit aarch64 payload required |
| agent + ip-stack != v4 | **Warning** — scenario name must exist in dev-scripts e2e list |
| CI_TOKEN missing/placeholder | **Blocked** — always required |

These are enforced in the helper scripts. If the user hits one, explain why and what to change.

## Execution Steps

Run each step as a separate Bash call. Branch on exit codes.

### Step 1: Bootstrap worktree config (worktrees only)

```bash
helpers/sync-worktree-config.sh
```

The script auto-detects whether it is running in a worktree. If so, it copies essential config files (`pull-secret.json`, `instance.env`, `config_*.sh`) from the main checkout into the worktree's `config/` folder — picking the newer of `config/<file>` and the canonical sync destination when both exist. If not a worktree, it exits silently.

After this step, `config/pull-secret.json` must exist. If it is still missing, fail with the standard prerequisites block.

### Step 2: Resolve release image

```bash
helpers/resolve-release-image.sh \
--version <spec> \
--arch <arch> \
$([ "<method>" = "agent" ] && echo "--digest") \
--pull-secret config/pull-secret.json \
--validate-access \
$([ -n "<ci_token>" ] && echo "--ci-token <ci_token>")
```
Comment on lines +92 to +100

@coderabbitai coderabbitai Bot Jul 15, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Do not interpolate the CI token into the generated command.

The contract says never echo the token into chat, but these templates instruct the agent to substitute <ci_token> directly into Bash. Keep the credential in a protected environment/secret channel and avoid rendering its value in the command transcript; ideally support environment-variable or stdin-based token input in the helpers.

Based on learnings, secrets must use environment variables and never be included in code or commits.
As per path instructions, .claude command changes require security-focused review of credential handling.

Also applies to: 99-105

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.claude/commands/prepare.md around lines 82 - 90, Update the command
template around helpers/resolve-release-image.sh so the CI token is supplied
through a protected environment variable or stdin-based mechanism rather than
interpolated into the generated Bash command. Remove the <ci_token> placeholder
from command arguments and preserve conditional token usage without exposing the
credential in chat, logs, code, or commits.

Sources: Path instructions, Learnings

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Won't fix — this is a Claude skill prompt, not executable shell. The <ci_token> placeholders are substituted at runtime. The --ci-token CLI flag is the repo's established helper pattern, and the token is intentionally written to config files (the Ansible role greps for it).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@fonta-rh, thanks for the clarification — that makes sense. Since .claude/commands/prepare.md is a prompt template consumed by the agent (not a shell script), and --ci-token is the established helper pattern with the token intentionally persisted to config/config_*.sh for the Ansible role to consume, the original concern doesn't apply here. Withdrawing this one.


✏️ Learnings added
Learnt from: fonta-rh
Repo: openshift-eng/two-node-toolbox PR: 95
File: .claude/commands/prepare.md:82-90
Timestamp: 2026-07-15T14:17:16.464Z
Learning: In the openshift-eng/two-node-toolbox repository, `.claude/commands/*.md` files (e.g., `.claude/commands/prepare.md`) are Claude skill prompt templates consumed by an AI agent at runtime, not executable shell scripts. Placeholders like `<ci_token>` in embedded bash snippets are substituted by the agent, not literally interpolated into a shell. The `--ci-token` CLI flag is the established pattern for helpers/resolve-release-image.sh and helpers/prepare-config.sh, and the CI token is intentionally written into generated `config/config_*.sh` files because the Ansible role greps for it there.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


Capture stdout (single-line pullspec). On failure:
- Exit 2: invalid version spec → show valid formats
- Exit 3: no matching release → suggest checking version number
- Exit 5: access denied → point to `/setup` for credentials

### Step 3: Ensure instance.env exists (medium=aws only)

Skip this step when medium is not `aws`.

Check if `config/instance.env` exists:
- **If present**: proceed silently (do not re-copy the template over user edits)
- **If missing**: copy the template and warn about defaults

```bash
cp config/instance.env.template config/instance.env
```

After copying, emit a warning (not a failure — the defaults are usable):
```
NOTE: Created config/instance.env from template with defaults:
- EC2_INSTANCE_TYPE=c5n.metal (x86_64 Intel)
- REGION=us-west-2
- AWS_PROFILE=microshift-dev
Review and edit config/instance.env if these defaults are not suitable.
```

Do NOT prompt for edits when all positional args are present (non-interactive contract). Just warn and continue.

### Step 4: Generate config

```bash
helpers/prepare-config.sh \
--topology <topology> \
--method <method> \
--release-image <pullspec from step 2> \
--ci-token <ci_token> \
--ip-stack <stack> \
--arch <arch> \
$([ "<force>" = "true" ] && echo "--force") \
$([ -n "<ds_repo>" ] && echo "--ds-repo <ds_repo>") \
$([ -n "<ds_branch>" ] && echo "--ds-branch <ds_branch>")
Comment on lines +140 to +142

@coderabbitai coderabbitai Bot Jul 15, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Shell-quote and validate free-form repository inputs before execution.

ds_repo and ds_branch are user-controlled values inserted unquoted into shell command substitutions. A value containing shell metacharacters can change the command executed by the agent. Validate the URL/branch grammar and construct an argument vector without raw interpolation or eval.

As per path instructions, .claude command changes require security review of command execution and untrusted input handling.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.claude/commands/prepare.md around lines 107 - 109, Update the command
argument construction around the ds_repo and ds_branch substitutions to validate
each value against the accepted repository URL and branch grammar, then pass
them as safely quoted argument values rather than raw shell interpolation. Avoid
eval and preserve the existing optional-argument behavior; include a security
review of command execution and untrusted input handling for this .claude
command change.

Source: Path instructions

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Won't fix — skill template placeholders are substituted by Claude, not by shell expansion. The helper script's argument parser handles quoting via positional shift, and sed delimiter | is safe for git URLs.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

#!/bin/bash
set -e
sed -n '1,160p' .claude/commands/prepare.md
echo "-----"
fd -e sh . helpers
echo "-----"
cat -n helpers/prepare-config.sh | sed -n '1,80p'
</shell>

<!-- This is an auto-generated reply by CodeRabbit -->

```

On failure:
- Exit 3: constraint violation → explain the specific constraint
- Exit 4: config exists → ask if they want `force=true` (human) or tell agent callers to pass `force=true`
- Exit 5: self-check failed → likely template drift, report verbatim

### Step 5: Run doctor

Before running Make, normalize the topology: if the user said `tnf`, use `fencing`; if `tna`, use `arbiter`. The `<topology>` below must be `arbiter`, `fencing`, or `sno`.

**medium=aws** (default — no AWS= override needed):
```bash
cd deploy && make sync-config && make doctor <topology>-<method>
```

**medium=external:**
```bash
cd deploy && make doctor AWS=0 <topology>-<method>
```

The `sync-config` call propagates `config/instance.env` to the canonical location (`deploy/aws-hypervisor/instance.env`) that doctor reads. `AWS=0` opts out of the instance.env requirement for external hosts.

Relay FAIL lines verbatim. Doctor is read-only — it won't break anything.

### Step 6: Print deployment commands (never run them)

**medium=aws:**
```
cd deploy && make deploy <topology>-<method>
```
Note: if an instance is already running (`make status`), `make <topology>-<method>` reuses it.

**medium=external:**
```
ansible-playbook deploy/openshift-clusters/setup.yml \
-e "topology=<topology>" -e "interactive_mode=false" \
$([ "<method>" = "agent" ] && echo '-e "method=agent"') \
-i deploy/openshift-clusters/inventory.ini
```
Note for first-time external hosts: run `ansible-playbook deploy/openshift-clusters/init-host.yml -i deploy/openshift-clusters/inventory.ini` first.

## After Completion

Report what was done:
- Resolved image (tag or digest)
- Config file path written
- Doctor result (pass/fail)
- Deployment command to run

Do **not** offer to run the deployment. The user decides when to deploy.
72 changes: 72 additions & 0 deletions .claude/commands/prepare/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# /prepare — Installation Preparation

Resolves an OpenShift release image, generates a dev-scripts config file, validates the environment, and prints the deployment command — without deploying.

## Usage

```
/prepare <topology> <method> <medium> <version> [options]
```

### Examples

```
# TNF fencing cluster, IPI on AWS, latest 4.21 GA
/prepare tnf ipi aws 4.21

# Arbiter cluster, agent method on external host, latest nightly
/prepare tna agent external 4.22 nightly

# SNO with specific version, forced overwrite
/prepare sno ipi aws 4.20.5 force=true
Comment thread
coderabbitai[bot] marked this conversation as resolved.

# Fencing on aarch64 Graviton instance
/prepare tnf ipi aws 4.21 arch=aarch64

# Using a dev-scripts fork for testing
/prepare tnf ipi aws 4.21 nightly ds-repo=https://github.com/user/dev-scripts ds-branch=fix/my-change
```
Comment thread
coderabbitai[bot] marked this conversation as resolved.

### Parameters

| Parameter | Values | Default |
|-----------|--------|---------|
| topology | `tna`/`arbiter`, `tnf`/`fencing`, `sno` | required |
| method | `ipi`, `agent` | required |
| medium | `aws`, `external` | required |
| version | `4.21`, `4.21 nightly`, `4.22 EC`, etc. | required |
| ip-stack | `v4`, `v6`, `v4v6` | `v4` |
| arch | `x86_64`, `aarch64` | auto-detected |
| force | `true` | `false` |
| ds-repo | fork URL | none |
| ds-branch | fork branch | none |

## What It Does

1. **Resolves** the version spec to a concrete release image (nightly from CI registry, or GA/EC/RC from quay.io)
2. **Pins by digest** when method=agent (agent installs require digest-pinned images)
3. **Generates** `config/config_<topology>.sh` from the example template with correct vars
4. **Validates** CI token, pull secret auth, and architecture constraints
5. **Runs** `make doctor <topology>-<method>` as a final gate
6. **Prints** the deployment command — but never runs it

## What It Does NOT Do

- Deploy a cluster (that's `make deploy` or `ansible-playbook`)
- Set up credentials or pull secrets (that's `/setup`)
- Manage kcli, assisted, or baremetal-adopt configs (dev-scripts only in v1)

## Helper Scripts

For scripted or CI use, call the helpers directly:

```bash
# Resolve a release image
helpers/resolve-release-image.sh --version 4.21-nightly --arch x86_64

# Generate config
helpers/prepare-config.sh --topology fencing --method ipi \
--release-image <pullspec> --ci-token <token>
```

Run each script with `--help` for full usage.
5 changes: 5 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ make status # Show status dashboard (instance, cluster VMs, proxy, cl
make inventory # Update inventory.ini with current instance IP
make doctor # Validate configuration and prerequisites (read-only)
make doctor <type> # Validate strictly for a specific cluster type (e.g. fencing-kcli)

# Installation preparation (resolve image + generate config)
# /prepare tnf ipi aws 4.21 # Claude skill — interactive or agent-driven
helpers/resolve-release-image.sh --version 4.21 # Resolve version → pullspec
helpers/prepare-config.sh --topology fencing --method ipi --release-image <pullspec> --ci-token <token>
```

### Ansible Deployment Methods
Expand Down
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ ansible-lint:
test-resource-agents:
@./helpers/resource-agents-build/local-build-test.sh $(ARGS)

test-prepare-online:
@./hack/test-prepare-online.sh $(ARGS)

verify:
VALIDATE_ONLY=true $(MAKE) shellcheck
VALIDATE_ONLY=true $(MAKE) yamlfmt
Expand Down
5 changes: 4 additions & 1 deletion deploy/Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# Valid cluster types for 'make deploy <cluster-type>'
VALID_CLUSTER_TYPES := fencing-ipi fencing-agent fencing-assisted arbiter-ipi arbiter-agent arbiter-kcli fencing-kcli sno-ipi sno-agent

# AWS hypervisor checks are on by default for doctor; opt out with AWS=0
AWS ?= 1

Comment on lines +4 to +6

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Make AWS prerequisite enforcement explicit and fail closed.

The current contract can silently run the AWS preparation flow without AWS checks when AWS is inherited or set to an invalid value.

  • deploy/Makefile#L4-L6: reject values other than 0 and 1.
  • deploy/Makefile#L97-L97: ensure only validated AWS=1 emits --aws.
  • .claude/commands/prepare.md#L154-L157: invoke the AWS doctor path with AWS=1 explicitly.

As per coding guidelines, configuration should be validated early and fail fast.

📍 Affects 2 files
  • deploy/Makefile#L4-L6 (this comment)
  • deploy/Makefile#L97-L97
  • .claude/commands/prepare.md#L154-L157
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deploy/Makefile` around lines 4 - 6, Validate AWS immediately in
deploy/Makefile so only 0 or 1 is accepted and invalid or inherited values fail
fast; update the AWS flag emission near line 97 to emit --aws only for validated
AWS=1, and update .claude/commands/prepare.md lines 154-157 to invoke the AWS
doctor path explicitly with AWS=1.

Source: Coding guidelines

# Handle 'make deploy <cluster-type>' pattern
# When 'deploy' is first, validate any following arguments are valid cluster types
ifeq (deploy,$(firstword $(MAKECMDGOALS)))
Expand Down Expand Up @@ -91,7 +94,7 @@ sync-config:
fi'

doctor:
@./openshift-clusters/scripts/doctor.sh $(DOCTOR_ARGS)
@./openshift-clusters/scripts/doctor.sh $(if $(filter 1,$(AWS)),--aws) $(DOCTOR_ARGS)

ifeq (doctor,$(firstword $(MAKECMDGOALS)))
# Cluster types after 'doctor' are arguments to doctor.sh, not deployments
Expand Down
25 changes: 22 additions & 3 deletions deploy/openshift-clusters/scripts/doctor.sh
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,15 @@ section() {
}

usage() {
echo "Usage: $0 [cluster-type...]"
echo "Usage: $0 [--aws] [cluster-type...]"
echo ""
echo "Read-only configuration preflight. With no arguments, runs common checks"
echo "plus every auto-detected section. Naming a cluster type makes its"
echo "deployment method's checks mandatory."
echo ""
echo "Options:"
echo " --aws Require instance.env (promotes missing instance.env to FAIL)"
echo ""
echo "Valid cluster types: fencing-ipi fencing-agent fencing-assisted"
echo " fencing-kcli arbiter-ipi arbiter-agent arbiter-kcli"
echo " sno-ipi sno-agent"
Expand All @@ -83,29 +86,37 @@ TARGETED=0
NEED_DEVSCRIPTS=0
NEED_KCLI=0
NEED_ASSISTED=0
NEED_AWS=0
REQUIRED_TOPOLOGIES=" "
TARGETED_TYPES=""

for arg in "$@"; do
case "${arg}" in
-h|--help)
usage
exit 0
;;
--aws)
NEED_AWS=1
;;
fencing-assisted)
# assisted deploys the hub via fencing-ipi, so it needs the dev-scripts config
TARGETED=1
NEED_DEVSCRIPTS=1
NEED_ASSISTED=1
REQUIRED_TOPOLOGIES+="fencing "
TARGETED_TYPES+=" ${arg}"
;;
arbiter-ipi|arbiter-agent|fencing-ipi|fencing-agent|sno-ipi|sno-agent)
TARGETED=1
NEED_DEVSCRIPTS=1
REQUIRED_TOPOLOGIES+="${arg%-*} "
TARGETED_TYPES+=" ${arg}"
;;
arbiter-kcli|fencing-kcli)
TARGETED=1
NEED_KCLI=1
TARGETED_TYPES+=" ${arg}"
;;
*)
echo "Error: Unknown cluster type: '${arg}'"
Expand All @@ -130,7 +141,10 @@ tool_hint() {

echo "two-node-toolbox configuration preflight (read-only)"
if [[ "${TARGETED}" -eq 1 ]]; then
check_note "validating strictly for: $*"
check_note "validating strictly for:${TARGETED_TYPES}"
fi
if [[ "${NEED_AWS}" -eq 1 ]]; then
check_note "AWS mode: instance.env is required"
fi

# --- Required tools ---
Expand Down Expand Up @@ -235,7 +249,12 @@ fi
section "AWS hypervisor (instance.env)"

if [[ ! -f "${INSTANCE_ENV}" ]]; then
check_note "section skipped: instance.env not found (only needed for the AWS hypervisor flow) - to use it: 'cp ${CONFIG_DIR}/instance.env.template ${CONFIG_DIR}/instance.env' and edit"
if [[ "${NEED_AWS}" -eq 1 ]]; then
check_fail "instance.env missing (required for AWS deployment)" \
"cp ${CONFIG_DIR}/instance.env.template ${CONFIG_DIR}/instance.env && edit it, then run 'make sync-config'"
else
check_note "section skipped: instance.env not found (only needed for the AWS hypervisor flow) - to use it: 'cp ${CONFIG_DIR}/instance.env.template ${CONFIG_DIR}/instance.env' and edit"
fi
else
# set +u: the deployment scripts never source instance.env under nounset,
# so the probe must not be stricter than real usage
Expand Down
Loading