From b40957dbc3da4d0ad42b252b470d6044efa56268 Mon Sep 17 00:00:00 2001 From: Pablo Fontanilla Date: Wed, 8 Jul 2026 17:30:10 +0200 Subject: [PATCH 01/15] feat: add ansible-lint and playbook syntax checks to make verify Add hack/ansible-lint.sh following the containerized pattern of the existing hack scripts. It runs ansible-lint (configured by the new .ansible-lint, with pre-existing findings baselined in a skip list) and ansible-playbook --syntax-check for every playbook, caching the required collections in ~/.cache/tnt-ansible-collections. Also validate topology and method in setup.yml pre_tasks regardless of interactive_mode, so bad -e values fail fast instead of erroring deep inside a role. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01AUa6KmTHSMNx9SibfQrLNm --- .ansible-lint | 41 ++++++++++++++++++++++ CLAUDE.md | 15 ++++++-- Makefile | 4 +++ README.md | 13 +++++++ deploy/openshift-clusters/setup.yml | 14 ++++++++ hack/ansible-lint.sh | 54 +++++++++++++++++++++++++++++ hack/pre-commit | 3 ++ 7 files changed, 141 insertions(+), 3 deletions(-) create mode 100644 .ansible-lint create mode 100755 hack/ansible-lint.sh diff --git a/.ansible-lint b/.ansible-lint new file mode 100644 index 00000000..b13a0483 --- /dev/null +++ b/.ansible-lint @@ -0,0 +1,41 @@ +--- +# Configuration for ansible-lint, run via `make ansible-lint` (hack/ansible-lint.sh). +profile: basic + +# Collections are pre-installed by hack/ansible-lint.sh; never reach out to galaxy. +offline: true + +exclude_paths: + # Claude Code skill/command content, not Ansible + - .claude/ + # CloudFormation templates, not Ansible + - deploy/aws-hypervisor/templates/ + +# Roles are resolved relative to deploy/openshift-clusters/ at runtime, which +# ansible-lint cannot see when invoked from the repository root. +mock_roles: + - git-user + +# Baseline debt: rules that fire on the pre-existing tree. New violations of any +# rule NOT listed here fail `make verify`. Burn these down over time by fixing +# the findings and removing entries from this list. +skip_list: + - command-instead-of-module + - command-instead-of-shell + - jinja[spacing] + - key-order[task] + - literal-compare + - name[casing] + - name[missing] + - name[play] + - name[template] + - role-name + - role-name[path] + - schema[playbook] + - var-naming[no-role-prefix] + - var-naming[read-only] + - yaml[empty-lines] + - yaml[line-length] + - yaml[new-line-at-end-of-file] + - yaml[trailing-spaces] + - yaml[truthy] diff --git a/CLAUDE.md b/CLAUDE.md index 253ac5be..416968fd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -81,12 +81,21 @@ make deploy fencing-assisted ### Linting and Validation ```bash -# Shell script linting (from repository root) +# Run all checks (from repository root) +make verify + +# Shell script linting make shellcheck -# or manually: -./hack/shellcheck.sh + +# YAML formatting +make yamlfmt + +# Ansible linting and playbook syntax checks +make ansible-lint ``` +`make ansible-lint` runs ansible-lint (configured by `.ansible-lint`) plus `ansible-playbook --syntax-check` for every playbook. Pre-existing findings are baselined in the `.ansible-lint` skip list; do not add new entries to the skip list — fix new violations instead. + ## Architecture and Structure ### Core Components diff --git a/Makefile b/Makefile index 70a02dad..9d038198 100644 --- a/Makefile +++ b/Makefile @@ -5,12 +5,16 @@ shellcheck: yamlfmt: @./hack/yamlfmt.sh +ansible-lint: + @./hack/ansible-lint.sh + test-resource-agents: @./helpers/resource-agents-build/local-build-test.sh $(ARGS) verify: VALIDATE_ONLY=true $(MAKE) shellcheck VALIDATE_ONLY=true $(MAKE) yamlfmt + $(MAKE) ansible-lint install-pre-commit: @echo "Installing pre-commit hook..." diff --git a/README.md b/README.md index b941006f..fff82ad5 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,19 @@ Alternatively, `make patch-nodes` (from `deploy/`) clones the resource-agents re See [helpers/README.md](helpers/README.md) for full documentation. +## Development + +Verify changes before committing (runs shellcheck, yamlfmt, and Ansible validation): + +```bash +make verify # run all checks +make shellcheck # shell script linting +make yamlfmt # YAML formatting +make ansible-lint # ansible-lint and playbook syntax checks +``` + +`make ansible-lint` runs [ansible-lint](https://ansible.readthedocs.io/projects/lint/) (configured by `.ansible-lint`) and `ansible-playbook --syntax-check` for every playbook in a container. Pre-existing findings are baselined in the `.ansible-lint` skip list; new violations fail verification. Install the pre-commit hook with `make install-pre-commit` to run `make verify` automatically. + ## Troubleshooting with Claude Code If you're using [Claude Code](https://claude.ai/code), it can help you troubleshoot etcd issues on two-node fencing clusters. Simply ask Claude to diagnose your etcd problems and it will automatically collect diagnostics, analyze the cluster state, and recommend remediation steps. See [.claude/commands/etcd/README.md](.claude/commands/etcd/README.md) for details. diff --git a/deploy/openshift-clusters/setup.yml b/deploy/openshift-clusters/setup.yml index 01ae6fe7..649b0a33 100644 --- a/deploy/openshift-clusters/setup.yml +++ b/deploy/openshift-clusters/setup.yml @@ -53,6 +53,20 @@ ansible.builtin.set_fact: method: "{{ prompt_result_method.user_input | default('ipi') }}" + # These asserts always run, so bad values passed via -e in non-interactive + # mode fail fast instead of erroring deep inside a role. + - name: Validate the topology value + ansible.builtin.assert: + that: topology in supported_topologies + fail_msg: "Unsupported topology '{{ topology }}', please select one of ({{ supported_topologies_pretty_print }})." + run_once: true + + - name: Validate the method value + ansible.builtin.assert: + that: method in supported_methods + fail_msg: "Unsupported method '{{ method }}', please select one of ({{ supported_methods_pretty_print }})." + run_once: true + - name: Pre-load variables from the 'install-dev' role ansible.builtin.include_vars: dir: roles/dev-scripts/install-dev/vars diff --git a/hack/ansible-lint.sh b/hack/ansible-lint.sh new file mode 100755 index 00000000..6b859c94 --- /dev/null +++ b/hack/ansible-lint.sh @@ -0,0 +1,54 @@ +#!/usr/bin/bash + +set -euo pipefail + +CONTAINER_ENGINE=${CONTAINER_ENGINE:-podman} +CONTAINER_IMAGE="ghcr.io/ansible/community-ansible-dev-tools:latest" +# Host-side cache so the collections download is a one-time cost +COLLECTIONS_CACHE=${COLLECTIONS_CACHE:-${HOME}/.cache/tnt-ansible-collections} +COLLECTIONS_REQUIREMENTS="deploy/openshift-clusters/collections/requirements.yml" + +# Directories containing playbooks; syntax-check runs from inside each one so +# ansible.cfg and relative role paths resolve. +PLAYBOOK_DIRS=( + "deploy/openshift-clusters" + "helpers" + "helpers/etcd/playbooks" +) + +if [ "${OPENSHIFT_CI:-}" != "" ]; then + export ANSIBLE_COLLECTIONS_PATH="${COLLECTIONS_CACHE}" + + # One-time download; delete the cache directory to force a refresh after + # changing collections/requirements.yml. + if [ ! -d "${COLLECTIONS_CACHE}/ansible_collections" ]; then + echo "Installing Ansible collections into ${COLLECTIONS_CACHE}..." + ansible-galaxy collection install \ + -r "${COLLECTIONS_REQUIREMENTS}" \ + -p "${COLLECTIONS_CACHE}" + fi + + echo "Running ansible-lint..." + ansible-lint + + REPO_ROOT="${PWD}" + for DIR in "${PLAYBOOK_DIRS[@]}"; do + echo "Syntax-checking playbooks in ${DIR}..." + cd "${REPO_ROOT}/${DIR}" + for PLAYBOOK in *.yml; do + ansible-playbook --syntax-check "${PLAYBOOK}" + done + done + cd "${REPO_ROOT}" +else + mkdir -p "${COLLECTIONS_CACHE}" + $CONTAINER_ENGINE run --rm \ + --env OPENSHIFT_CI=TRUE \ + --env COLLECTIONS_CACHE=/var/cache/tnt-ansible-collections \ + --volume "${PWD}:/workdir:z" \ + --volume "${COLLECTIONS_CACHE}:/var/cache/tnt-ansible-collections:z" \ + --entrypoint bash \ + --workdir /workdir \ + $CONTAINER_IMAGE \ + hack/ansible-lint.sh "${@}" +fi diff --git a/hack/pre-commit b/hack/pre-commit index 82782934..923900a6 100755 --- a/hack/pre-commit +++ b/hack/pre-commit @@ -14,6 +14,9 @@ if ! make verify; then echo "To fix shell script issues, run:" echo " make shellcheck" echo "" + echo "To see Ansible lint and syntax issues, run:" + echo " make ansible-lint" + echo "" echo "After fixing issues, stage your changes and commit again." echo "" echo "To bypass this hook (not recommended), use:" From f152d1efe1f1e0c3aabafc56fcde5f9104cad51b Mon Sep 17 00:00:00 2001 From: Pablo Fontanilla Date: Wed, 8 Jul 2026 17:30:10 +0200 Subject: [PATCH 02/15] feat: add central config/ folder with sync-to-canonical Add a single config/ folder at the repository root where users create their configuration files. Every make-accessible script syncs config/ files to their canonical locations (newer-wins, never deletes) before doing anything else; the playbooks and roles keep reading from the canonical locations unchanged. - Move all templates and example configs into config/ (single source): instance.env.template, config_*_example.sh, kcli.yml.template, assisted.yml.template, plus a README map and a .gitignore that prevents real config files (including the pull secret) from ever being committed - Add sync_config_files() to aws-hypervisor/scripts/common.sh with a manifest of src:dest[:dest2] entries; the pull secret fans out to both the dev-scripts and kcli role locations - Restructure common.sh so the sync runs before instance.env is sourced, and replace the raw missing-file source error with an actionable hint - Add 'make sync-config' for users who invoke ansible-playbook directly, and register init-host.yml.local in the manifest (it is consumed by init-host.yml) - Source common.sh from get-tnf-logs.sh and helpers/keep-instance.sh so every make target inherits the sync - Gitignore vars/assisted.yml (contains credentials, was not ignored) - Update all docs and the /setup command to point at config/, keeping a note that editing canonical locations directly still works Backwards compatible: config/ ships with templates only, so the sync is a no-op until the user explicitly creates a file there. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Mk1uuxv3YkApKm8w55Kxjd --- .claude/commands/setup.md | 71 ++++++++-------- .claude/commands/setup/README.md | 12 +-- CLAUDE.md | 24 ++++-- README.md | 4 + config/.gitignore | 7 ++ config/README.md | 34 ++++++++ .../vars => config}/assisted.yml.template | 0 .../config_arbiter_example.sh | 0 .../config_fencing_example.sh | 0 .../files => config}/config_sno_example.sh | 0 .../instance.env.template | 0 .../vars => config}/kcli.yml.template | 0 deploy/Makefile | 5 ++ deploy/README.md | 2 +- deploy/aws-hypervisor/README.md | 11 ++- deploy/aws-hypervisor/scripts/common.sh | 81 ++++++++++++++----- deploy/openshift-clusters/.gitignore | 3 +- .../README-external-host.md | 7 +- deploy/openshift-clusters/README-kcli.md | 15 ++-- deploy/openshift-clusters/README.md | 4 +- .../openshift-clusters/assisted-install.yml | 3 +- .../roles/assisted/assisted-spoke/README.md | 8 +- .../roles/kcli/kcli-install/README.md | 18 ++--- .../scripts/deploy-fencing-assisted.sh | 3 +- .../scripts/get-tnf-logs.sh | 3 + helpers/keep-instance.sh | 9 +-- 26 files changed, 218 insertions(+), 106 deletions(-) create mode 100644 config/.gitignore create mode 100644 config/README.md rename {deploy/openshift-clusters/vars => config}/assisted.yml.template (100%) rename {deploy/openshift-clusters/roles/dev-scripts/install-dev/files => config}/config_arbiter_example.sh (100%) rename {deploy/openshift-clusters/roles/dev-scripts/install-dev/files => config}/config_fencing_example.sh (100%) rename {deploy/openshift-clusters/roles/dev-scripts/install-dev/files => config}/config_sno_example.sh (100%) rename {deploy/aws-hypervisor => config}/instance.env.template (100%) rename {deploy/openshift-clusters/vars => config}/kcli.yml.template (100%) diff --git a/.claude/commands/setup.md b/.claude/commands/setup.md index 79f3000a..a6a88f71 100644 --- a/.claude/commands/setup.md +++ b/.claude/commands/setup.md @@ -37,7 +37,7 @@ All deployment methods require an SSH key on the local machine. Check for any of If no SSH key exists, create one with: `ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519` -**Note for non-default key paths:** Set `SSH_PUBLIC_KEY` in [deploy/aws-hypervisor/instance.env](deploy/aws-hypervisor/instance.env). This is used by both the AWS hypervisor and dev-scripts deployments. +**Note for non-default key paths:** Set `SSH_PUBLIC_KEY` in [config/instance.env](config/instance.env). This is used by both the AWS hypervisor and dev-scripts deployments. ### Inventory File @@ -60,22 +60,19 @@ Required for: external, kcli, dev-scripts Required for: kcli, dev-scripts -The same pull secret is used by both kcli and dev-scripts (stored in different locations). - -- kcli location: [deploy/openshift-clusters/roles/kcli/kcli-install/files/pull-secret.json](deploy/openshift-clusters/roles/kcli/kcli-install/files/pull-secret.json) -- dev-scripts location: [deploy/openshift-clusters/roles/dev-scripts/install-dev/files/pull-secret.json](deploy/openshift-clusters/roles/dev-scripts/install-dev/files/pull-secret.json) +The same pull secret is used by both kcli and dev-scripts. Create it once in the central config folder: [config/pull-secret.json](config/pull-secret.json). The sync (run by every `make` target, or explicitly via `cd deploy && make sync-config`) distributes it to both role locations automatically. **Setup logic:** -- Check if file exists in either location -- If it exists in one location, offer to copy it to the other +- Check if [config/pull-secret.json](config/pull-secret.json) exists +- If not, check the canonical locations (`deploy/openshift-clusters/roles/dev-scripts/install-dev/files/pull-secret.json` and `deploy/openshift-clusters/roles/kcli/kcli-install/files/pull-secret.json`) - a file there means the user configured it the direct-edit way, which is still valid - If it doesn't exist anywhere: - User gets pull secret from: https://cloud.redhat.com/openshift/install/pull-secret - **For CI builds**: Must include `registry.ci.openshift.org` access (see [CI Token](#ci-token) below) - - Suggest creating file and pasting content + - Suggest creating [config/pull-secret.json](config/pull-secret.json) and pasting content **Validation:** -- Validate pull secret is valid JSON: `jq . < ` -- Check for CI registry access (if needed): `jq '.auths | has("registry.ci.openshift.org")' < ` +- Validate pull secret is valid JSON: `jq . < config/pull-secret.json` +- Check for CI registry access (if needed): `jq '.auths | has("registry.ci.openshift.org")' < config/pull-secret.json` ### CI Token @@ -107,10 +104,11 @@ Required for: dev-scripts (when using CI builds) - `export RHSM_ORG="your-org-id"` - Guide: https://access.redhat.com/solutions/3341191 - - **Option B**: Local config file [vars/init-host.yml.local](vars/init-host.yml.local) - - Template: [vars/init-host.yml.sample](vars/init-host.yml.sample) - - Check if exists - - Suggest copying: `cp vars/init-host.yml.sample vars/init-host.yml.local` + - **Option B**: Local config file [config/init-host.yml.local](config/init-host.yml.local) + - Base file: [deploy/openshift-clusters/vars/init-host.yml](deploy/openshift-clusters/vars/init-host.yml) + - Check if exists (also accept the direct-edit location `deploy/openshift-clusters/vars/init-host.yml.local`) + - Suggest copying: `cp deploy/openshift-clusters/vars/init-host.yml config/init-host.yml.local` + - The sync distributes it to `deploy/openshift-clusters/vars/init-host.yml.local`; suggest `cd deploy && make sync-config` before running the playbook directly - **Option C**: Command line (they'll add when running playbook) @@ -135,17 +133,18 @@ Required for: dev-scripts (when using CI builds) **Files to configure:** -1. **Instance environment**: [deploy/aws-hypervisor/instance.env](deploy/aws-hypervisor/instance.env) - - Template: [deploy/aws-hypervisor/instance.env.template](deploy/aws-hypervisor/instance.env.template) - - Check if it exists +1. **Instance environment**: [config/instance.env](config/instance.env) + - Template: [config/instance.env.template](config/instance.env.template) + - Check if it exists (also accept the direct-edit location [deploy/aws-hypervisor/instance.env](deploy/aws-hypervisor/instance.env)) - User must edit ALL variables with their specific values - Optional: Set `RHSM_ACTIVATION_KEY` and `RHSM_ORG` for hands-off deployment - Guide: https://access.redhat.com/solutions/3341191 - - Suggest copying: `cp deploy/aws-hypervisor/instance.env.template deploy/aws-hypervisor/instance.env` + - Suggest copying: `cp config/instance.env.template config/instance.env` + - Every `make` target syncs it to `deploy/aws-hypervisor/instance.env` where the scripts read it **Validation:** -- Verify [deploy/aws-hypervisor/instance.env](deploy/aws-hypervisor/instance.env) exists -- Test by sourcing: `source deploy/aws-hypervisor/instance.env` (should not error) +- Verify [config/instance.env](config/instance.env) (or `deploy/aws-hypervisor/instance.env`) exists +- Test by sourcing: `source config/instance.env` (should not error) **Next steps:** - Quick deploy: `cd deploy && make deploy arbiter-ipi` @@ -165,11 +164,12 @@ Required for: dev-scripts (when using CI builds) **Files to configure:** -1. **Optional persistent config**: [deploy/openshift-clusters/vars/kcli.yml](deploy/openshift-clusters/vars/kcli.yml) - - Template: [deploy/openshift-clusters/vars/kcli.yml.template](deploy/openshift-clusters/vars/kcli.yml.template) - - Check if exists +1. **Optional persistent config**: [config/kcli.yml](config/kcli.yml) + - Template: [config/kcli.yml.template](config/kcli.yml.template) + - Check if exists (also accept the direct-edit location [deploy/openshift-clusters/vars/kcli.yml](deploy/openshift-clusters/vars/kcli.yml)) - For setting preferred defaults (cluster name, resources, etc.) - - Suggest copying: `cp deploy/openshift-clusters/vars/kcli.yml.template deploy/openshift-clusters/vars/kcli.yml` + - Suggest copying: `cp config/kcli.yml.template config/kcli.yml` + - The sync distributes it to `deploy/openshift-clusters/vars/kcli.yml`; suggest `cd deploy && make sync-config` before running the playbook directly **Validation:** - Verify inventory file exists @@ -192,28 +192,29 @@ Required for: dev-scripts (when using CI builds) **Files to configure:** -1. **Topology config files** in [deploy/openshift-clusters/roles/dev-scripts/install-dev/files/](deploy/openshift-clusters/roles/dev-scripts/install-dev/files/): +1. **Topology config files** in [config/](config/): - a. **Arbiter config**: [config_arbiter.sh](deploy/openshift-clusters/roles/dev-scripts/install-dev/files/config_arbiter.sh) - - Template: [config_arbiter_example.sh](deploy/openshift-clusters/roles/dev-scripts/install-dev/files/config_arbiter_example.sh) - - Check if exists + a. **Arbiter config**: [config/config_arbiter.sh](config/config_arbiter.sh) + - Template: [config/config_arbiter_example.sh](config/config_arbiter_example.sh) + - Check if exists (also accept the direct-edit location `deploy/openshift-clusters/roles/dev-scripts/install-dev/files/config_arbiter.sh`) - User must set: - `OPENSHIFT_RELEASE_IMAGE` (e.g., `quay.io/openshift-release-dev/ocp-release:4.19.0-rc.5-multi-x86_64`) - `CI_TOKEN` (see [CI Token](#ci-token)) unless using `OPENSHIFT_CI="True"` - - Suggest copying: `cp deploy/openshift-clusters/roles/dev-scripts/install-dev/files/config_arbiter_example.sh deploy/openshift-clusters/roles/dev-scripts/install-dev/files/config_arbiter.sh` + - Suggest copying: `cp config/config_arbiter_example.sh config/config_arbiter.sh` - b. **Fencing config**: [config_fencing.sh](deploy/openshift-clusters/roles/dev-scripts/install-dev/files/config_fencing.sh) - - Template: [config_fencing_example.sh](deploy/openshift-clusters/roles/dev-scripts/install-dev/files/config_fencing_example.sh) - - Check if exists + b. **Fencing config**: [config/config_fencing.sh](config/config_fencing.sh) + - Template: [config/config_fencing_example.sh](config/config_fencing_example.sh) + - Check if exists (also accept the direct-edit location `deploy/openshift-clusters/roles/dev-scripts/install-dev/files/config_fencing.sh`) - Same requirements as arbiter config - - Suggest copying: `cp deploy/openshift-clusters/roles/dev-scripts/install-dev/files/config_fencing_example.sh deploy/openshift-clusters/roles/dev-scripts/install-dev/files/config_fencing.sh` + - Suggest copying: `cp config/config_fencing_example.sh config/config_fencing.sh` + - The sync distributes these to `deploy/openshift-clusters/roles/dev-scripts/install-dev/files/`; suggest `cd deploy && make sync-config` before running the playbook directly - **Config reference**: https://github.com/openshift-metal3/dev-scripts/blob/master/config_example.sh **Validation:** - Verify inventory file exists -- For arbiter: Check [config_arbiter.sh](deploy/openshift-clusters/roles/dev-scripts/install-dev/files/config_arbiter.sh) exists -- For fencing: Check [config_fencing.sh](deploy/openshift-clusters/roles/dev-scripts/install-dev/files/config_fencing.sh) exists +- For arbiter: Check [config/config_arbiter.sh](config/config_arbiter.sh) (or the direct-edit location) exists +- For fencing: Check [config/config_fencing.sh](config/config_fencing.sh) (or the direct-edit location) exists - Verify pull secret exists and is valid JSON **Next steps:** diff --git a/.claude/commands/setup/README.md b/.claude/commands/setup/README.md index e9e2472b..2fd1eb7d 100644 --- a/.claude/commands/setup/README.md +++ b/.claude/commands/setup/README.md @@ -40,7 +40,7 @@ Configures deployment to your own RHEL 9 server (non-AWS). Configures automated RHEL hypervisor deployment in AWS EC2. **Files configured:** -- `deploy/aws-hypervisor/instance.env` - AWS instance configuration +- `config/instance.env` - AWS instance configuration (synced to `deploy/aws-hypervisor/instance.env`) **Use when:** You want to deploy a hypervisor in AWS @@ -49,8 +49,8 @@ Configures kcli-based OpenShift deployment (fencing topology). **Files configured:** - `deploy/openshift-clusters/inventory.ini` - Target host details -- `deploy/openshift-clusters/roles/kcli/kcli-install/files/pull-secret.json` - OpenShift pull secret -- `deploy/openshift-clusters/vars/kcli.yml` (optional) - Persistent kcli preferences +- `config/pull-secret.json` - OpenShift pull secret (synced to the kcli role files directory) +- `config/kcli.yml` (optional) - Persistent kcli preferences (synced to `deploy/openshift-clusters/vars/kcli.yml`) - SSH key validation **Use when:** You want to deploy using the modern kcli method @@ -60,9 +60,9 @@ Configures traditional dev-scripts deployment (arbiter or fencing topology). **Files configured:** - `deploy/openshift-clusters/inventory.ini` - Target host details -- `deploy/openshift-clusters/roles/dev-scripts/install-dev/files/config_arbiter.sh` - Arbiter config -- `deploy/openshift-clusters/roles/dev-scripts/install-dev/files/config_fencing.sh` - Fencing config -- `deploy/openshift-clusters/roles/dev-scripts/install-dev/files/pull-secret.json` - OpenShift pull secret +- `config/config_arbiter.sh` - Arbiter config (synced to the dev-scripts role files directory) +- `config/config_fencing.sh` - Fencing config (synced to the dev-scripts role files directory) +- `config/pull-secret.json` - OpenShift pull secret (synced to both the dev-scripts and kcli role files directories) - Ansible collections installation - SSH key validation diff --git a/CLAUDE.md b/CLAUDE.md index 416968fd..5c6857b0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -72,8 +72,8 @@ ansible-playbook kcli-install.yml -i inventory.ini -e "force_cleanup=true" #### Assisted Installer Method (Spoke TNF via ACM) ```bash -# Copy and customize the configuration template -cp vars/assisted.yml.template vars/assisted.yml +# Copy and customize the configuration template (from the repository root) +cp config/assisted.yml.template config/assisted.yml # Deploy hub + spoke TNF cluster via assisted installer make deploy fencing-assisted @@ -130,20 +130,30 @@ make ansible-lint ### Key Configuration Files -#### Dev-scripts Method -- `inventory.ini`: Ansible inventory (copy from `inventory.ini.sample`) +#### Central config/ Folder +User configuration files are created in `config/` at the repository root (templates and examples live there; see `config/README.md`). Every make-accessible script syncs `config/` files to the canonical locations below before doing anything else (newer-wins, never deletes); `make sync-config` runs the sync explicitly. Editing the canonical locations directly still works: a `config/` file only becomes authoritative once the user creates it. + +- `config/instance.env`: AWS hypervisor settings (copy from `config/instance.env.template`) +- `config/pull-secret.json`: OpenShift pull secret, fanned out to both dev-scripts and kcli role locations +- `config/config_arbiter.sh`, `config/config_fencing.sh`, `config/config_sno.sh`: dev-scripts topology configs (copy from the `config/config_*_example.sh` files) +- `config/kcli.yml`: kcli variable overrides (copy from `config/kcli.yml.template`) +- `config/assisted.yml`: assisted installer variables (copy from `config/assisted.yml.template`) +- `config/init-host.yml.local`: external-host RHSM overrides (copy from `deploy/openshift-clusters/vars/init-host.yml`) + +#### Dev-scripts Method (canonical locations) +- `inventory.ini`: Ansible inventory (copy from `inventory.ini.sample`; generated by `make inventory` on AWS) - `roles/dev-scripts/install-dev/files/config_arbiter.sh`: Arbiter topology config - `roles/dev-scripts/install-dev/files/config_fencing.sh`: Fencing topology config - `roles/dev-scripts/install-dev/files/config_sno.sh`: Single Node OpenShift config - `roles/dev-scripts/install-dev/files/pull-secret.json`: OpenShift pull secret -#### Kcli Method +#### Kcli Method (canonical locations) - `vars/kcli.yml`: Variable override file for persistent configuration - `roles/kcli/kcli-install/files/pull-secret.json`: OpenShift pull secret - SSH key automatically read from `~/.ssh/id_ed25519.pub` on ansible controller -#### Assisted Installer Method -- `vars/assisted.yml`: Variable override file (copy from `vars/assisted.yml.template`) +#### Assisted Installer Method (canonical locations) +- `vars/assisted.yml`: Variable override file (create as `config/assisted.yml`) - Hub cluster must be deployed first via dev-scripts (`make deploy fencing-ipi`) - Spoke credentials output to `~//auth/` on hypervisor - Hub proxy preserved as `hub-proxy.env` diff --git a/README.md b/README.md index fff82ad5..d92a14bf 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ This repository provides automation for deploying and managing two-node OpenShif ## Quick Start +Configuration files (instance settings, pull secret, topology configs) are created in the central [config/](config/) folder; see [config/README.md](config/README.md) for the full list. Every `make` command syncs them to the locations where the automation reads them. Editing the canonical locations directly still works as before. + ### Option 1: AWS Hypervisor (Automated) If you have AWS access, use the automated workflow. Most lifecycle operations can be performed from the [deploy](deploy/) folder using `make`: @@ -48,6 +50,8 @@ ansible-playbook setup.yml -i inventory.ini # dev-scripts (arbiter or fen ansible-playbook kcli-install.yml -i inventory.ini # kcli (fencing only) ``` +If you created your configuration files in [config/](config/), run `make sync-config` from the [deploy/](deploy/) folder first to distribute them, since the playbooks are invoked directly here. + See [deploy/openshift-clusters/README-external-host.md](deploy/openshift-clusters/README-external-host.md) for detailed instructions. ## Deployment Methods diff --git a/config/.gitignore b/config/.gitignore new file mode 100644 index 00000000..fc4d2769 --- /dev/null +++ b/config/.gitignore @@ -0,0 +1,7 @@ +# Real config files (created by the user, may contain credentials) must never +# be committed. Only templates, examples, the README, and this file are tracked. +* +!.gitignore +!README.md +!*.template +!config_*_example.sh diff --git a/config/README.md b/config/README.md new file mode 100644 index 00000000..a1c0189d --- /dev/null +++ b/config/README.md @@ -0,0 +1,34 @@ +# Central configuration folder + +This folder is the single place to create your configuration files for the toolbox. +Every `make` target (and `make sync-config` explicitly) syncs files from here to the +locations where the scripts and playbooks actually read them. A file is only copied +when it is missing at the destination or the `config/` copy is newer, so editing the +canonical locations directly still works as before. + +Only templates and examples are tracked in git; the `.gitignore` in this folder +prevents your real config files (including the pull secret) from ever being committed. + +## Files + +| Create this file | Used by | Synced to | How to create | +|---|---|---|---| +| `instance.env` | AWS hypervisor (all `make` instance targets) | `deploy/aws-hypervisor/instance.env` | `cp config/instance.env.template config/instance.env` | +| `pull-secret.json` | dev-scripts and kcli deployments | `deploy/openshift-clusters/roles/dev-scripts/install-dev/files/pull-secret.json` and `deploy/openshift-clusters/roles/kcli/kcli-install/files/pull-secret.json` | Paste your pull secret from | +| `config_arbiter.sh` | dev-scripts arbiter topology | `deploy/openshift-clusters/roles/dev-scripts/install-dev/files/config_arbiter.sh` | `cp config/config_arbiter_example.sh config/config_arbiter.sh` | +| `config_fencing.sh` | dev-scripts fencing topology | `deploy/openshift-clusters/roles/dev-scripts/install-dev/files/config_fencing.sh` | `cp config/config_fencing_example.sh config/config_fencing.sh` | +| `config_sno.sh` | dev-scripts SNO topology | `deploy/openshift-clusters/roles/dev-scripts/install-dev/files/config_sno.sh` | `cp config/config_sno_example.sh config/config_sno.sh` | +| `kcli.yml` | kcli deployment (optional overrides) | `deploy/openshift-clusters/vars/kcli.yml` | `cp config/kcli.yml.template config/kcli.yml` | +| `assisted.yml` | assisted installer spoke deployment | `deploy/openshift-clusters/vars/assisted.yml` | `cp config/assisted.yml.template config/assisted.yml` | +| `init-host.yml.local` | external host initialization (optional RHSM overrides) | `deploy/openshift-clusters/vars/init-host.yml.local` | `cp deploy/openshift-clusters/vars/init-host.yml config/init-host.yml.local` | + +## Syncing + +The sync runs automatically at the start of every script invoked through the +`deploy/` Makefile. To run it on its own (for example before invoking +`ansible-playbook` directly): + +```bash +cd deploy/ +make sync-config +``` diff --git a/deploy/openshift-clusters/vars/assisted.yml.template b/config/assisted.yml.template similarity index 100% rename from deploy/openshift-clusters/vars/assisted.yml.template rename to config/assisted.yml.template diff --git a/deploy/openshift-clusters/roles/dev-scripts/install-dev/files/config_arbiter_example.sh b/config/config_arbiter_example.sh similarity index 100% rename from deploy/openshift-clusters/roles/dev-scripts/install-dev/files/config_arbiter_example.sh rename to config/config_arbiter_example.sh diff --git a/deploy/openshift-clusters/roles/dev-scripts/install-dev/files/config_fencing_example.sh b/config/config_fencing_example.sh similarity index 100% rename from deploy/openshift-clusters/roles/dev-scripts/install-dev/files/config_fencing_example.sh rename to config/config_fencing_example.sh diff --git a/deploy/openshift-clusters/roles/dev-scripts/install-dev/files/config_sno_example.sh b/config/config_sno_example.sh similarity index 100% rename from deploy/openshift-clusters/roles/dev-scripts/install-dev/files/config_sno_example.sh rename to config/config_sno_example.sh diff --git a/deploy/aws-hypervisor/instance.env.template b/config/instance.env.template similarity index 100% rename from deploy/aws-hypervisor/instance.env.template rename to config/instance.env.template diff --git a/deploy/openshift-clusters/vars/kcli.yml.template b/config/kcli.yml.template similarity index 100% rename from deploy/openshift-clusters/vars/kcli.yml.template rename to config/kcli.yml.template diff --git a/deploy/Makefile b/deploy/Makefile index 98872591..2b48959e 100644 --- a/deploy/Makefile +++ b/deploy/Makefile @@ -61,6 +61,9 @@ info: inventory: @./aws-hypervisor/scripts/inventory.sh +sync-config: + @bash -c 'source ./aws-hypervisor/scripts/common.sh; if [ "$${CONFIG_SYNCED_COUNT:-0}" -eq 0 ]; then echo "Config files: everything up to date."; fi' + fencing-ipi: @./openshift-clusters/scripts/deploy-cluster.sh --topology fencing --method ipi @@ -129,6 +132,8 @@ help: @echo " inventory - Update inventory.ini with current instance IP" @echo " keep-instance - Tag instance with keep-N retention (default: 2, max: 5)" @echo " Usage: make keep-instance or make keep-instance DAYS=5" + @echo " sync-config - Sync files from config/ to their canonical locations" + @echo " (runs automatically before every other target)" @echo "" @echo "OpenShift Cluster Deployment:" @echo " fencing-ipi - Deploy fencing IPI cluster (non-interactive)" diff --git a/deploy/README.md b/deploy/README.md index b6676ec2..955c5200 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -40,7 +40,7 @@ Additionally, if you're using Mac OS, you might not have `timeout`, so you might ## Configuration -Before deployment, configure your environment by setting up the `aws-hypervisor/instance.env` file. Copy `aws-hypervisor/instance.env.template` to `aws-hypervisor/instance.env` and set all variables to valid values for your user. +Before deployment, configure your environment by creating an `instance.env` file in the central [config/](../config/) folder. Copy `config/instance.env.template` to `config/instance.env` and set all variables to valid values for your user. Every `make` target syncs `config/` files to the locations the scripts read them from (`aws-hypervisor/instance.env` in this case); run `make sync-config` to sync explicitly. Editing `aws-hypervisor/instance.env` directly still works as before. See [config/README.md](../config/README.md) for all available configuration files. ## Available Commands diff --git a/deploy/aws-hypervisor/README.md b/deploy/aws-hypervisor/README.md index c06bea93..bf175203 100644 --- a/deploy/aws-hypervisor/README.md +++ b/deploy/aws-hypervisor/README.md @@ -5,13 +5,16 @@ This directory contains scripts for managing EC2 instances used as hypervisors f ## Configuration ### Environment Setup -Copy the `instance.env.template` file to `instance.env` and set all variables to valid values for your user. +Copy the `config/instance.env.template` file at the repository root to `config/instance.env` and set all variables to valid values for your user. Every `make` target syncs it to `instance.env` in this directory, where the scripts read it. ```bash -cp instance.env.template instance.env -# Edit instance.env with your specific values +# From the repository root +cp config/instance.env.template config/instance.env +# Edit config/instance.env with your specific values ``` +Editing `instance.env` in this directory directly still works as before. + #### Automated RHSM Registration (Hands-off Deployment) For a completely automated deployment without manual intervention, you can configure Red Hat Subscription Manager (RHSM) activation key variables in your `instance.env` file: @@ -143,7 +146,7 @@ if [ "$(uname -m)" = "aarch64" ]; then fi ``` -See `config_fencing_example.sh` for a complete example. To rebuild these images, use `helpers/build-metal3-arm64.sh` (see [helpers/README.md](../../helpers/README.md)). +See `config/config_fencing_example.sh` at the repository root for a complete example. To rebuild these images, use `helpers/build-metal3-arm64.sh` (see [helpers/README.md](../../helpers/README.md)). ### Known Limitations diff --git a/deploy/aws-hypervisor/scripts/common.sh b/deploy/aws-hypervisor/scripts/common.sh index e9d7ea14..23501396 100755 --- a/deploy/aws-hypervisor/scripts/common.sh +++ b/deploy/aws-hypervisor/scripts/common.sh @@ -1,8 +1,68 @@ #!/bin/bash SCRIPT_DIR=$(dirname "$0") COMMON_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -# shellcheck source=/dev/null -source "${COMMON_DIR}/../instance.env" +REPO_ROOT="$(cd "${COMMON_DIR}/../../.." && pwd)" + +readonly COLOR_RED='\033[0;31m' +readonly COLOR_YELLOW='\033[0;33m' +readonly COLOR_BLUE='\033[0;34m' +readonly COLOR_CLEAR='\033[0m' + +function msg_err() { + echo -e "${COLOR_RED}ERROR: ${1}${COLOR_CLEAR}" >&2 +} + +function msg_warning() { + echo -e "${COLOR_YELLOW}WARNING: ${1}${COLOR_CLEAR}" >&2 +} + +function msg_info() { + echo -e "${COLOR_BLUE}INFO: ${1}${COLOR_CLEAR}" >&2 +} + +# Config sync manifest: "src:dest[:dest2]" entries, where src is relative to +# config/ at the repository root and destinations are relative to the +# repository root. +CONFIG_SYNC_MANIFEST=( + "instance.env:deploy/aws-hypervisor/instance.env" + "config_arbiter.sh:deploy/openshift-clusters/roles/dev-scripts/install-dev/files/config_arbiter.sh" + "config_fencing.sh:deploy/openshift-clusters/roles/dev-scripts/install-dev/files/config_fencing.sh" + "config_sno.sh:deploy/openshift-clusters/roles/dev-scripts/install-dev/files/config_sno.sh" + "pull-secret.json:deploy/openshift-clusters/roles/dev-scripts/install-dev/files/pull-secret.json:deploy/openshift-clusters/roles/kcli/kcli-install/files/pull-secret.json" + "kcli.yml:deploy/openshift-clusters/vars/kcli.yml" + "assisted.yml:deploy/openshift-clusters/vars/assisted.yml" + "init-host.yml.local:deploy/openshift-clusters/vars/init-host.yml.local" +) + +# Copies files the user created in config/ to the canonical locations the +# scripts and playbooks read from. A file is only copied when the destination +# is missing or the config/ copy is newer, so direct edits to the canonical +# locations are never overwritten. Never deletes or writes back into config/. +function sync_config_files() { + export CONFIG_SYNCED_COUNT=0 + local entry src dest + local -a fields + for entry in "${CONFIG_SYNC_MANIFEST[@]}"; do + IFS=':' read -r -a fields <<< "${entry}" + src="${REPO_ROOT}/config/${fields[0]}" + [[ -f "${src}" ]] || continue + for dest in "${fields[@]:1}"; do + if [[ ! -f "${REPO_ROOT}/${dest}" || "${src}" -nt "${REPO_ROOT}/${dest}" ]]; then + msg_info "config/${fields[0]} is newer, updating ${dest}" + cp "${src}" "${REPO_ROOT}/${dest}" + CONFIG_SYNCED_COUNT=$((CONFIG_SYNCED_COUNT + 1)) + fi + done + done +} +sync_config_files + +if [[ -f "${COMMON_DIR}/../instance.env" ]]; then + # shellcheck source=/dev/null + source "${COMMON_DIR}/../instance.env" +else + msg_warning "instance.env not found (only needed for AWS hypervisor targets). To create it: cp config/instance.env.template config/instance.env and edit it." +fi # Set defaults export STACK_NAME="${STACK_NAME:-${USER}-dev}" @@ -40,23 +100,6 @@ function print_proxy_instructions() { echo "3. Access the cluster console if needed" } -readonly COLOR_RED='\033[0;31m' -readonly COLOR_YELLOW='\033[0;33m' -readonly COLOR_BLUE='\033[0;34m' -readonly COLOR_CLEAR='\033[0m' - -function msg_err() { - echo -e "${COLOR_RED}ERROR: ${1}${COLOR_CLEAR}" >&2 -} - -function msg_warning() { - echo -e "${COLOR_YELLOW}WARNING: ${1}${COLOR_CLEAR}" >&2 -} - -function msg_info() { - echo -e "${COLOR_BLUE}INFO: ${1}${COLOR_CLEAR}" >&2 -} - function get_ami_arch() { local arch="${RHEL_HOST_ARCHITECTURE}" if [[ "${arch}" == "aarch64" ]]; then diff --git a/deploy/openshift-clusters/.gitignore b/deploy/openshift-clusters/.gitignore index c5ba337d..6cc26b3e 100644 --- a/deploy/openshift-clusters/.gitignore +++ b/deploy/openshift-clusters/.gitignore @@ -11,4 +11,5 @@ clusters/ # Local variable override files vars/*.local vars/*-local.yml -vars/*.yml.local \ No newline at end of file +vars/*.yml.local +vars/assisted.yml \ No newline at end of file diff --git a/deploy/openshift-clusters/README-external-host.md b/deploy/openshift-clusters/README-external-host.md index f32ed963..eeee50be 100644 --- a/deploy/openshift-clusters/README-external-host.md +++ b/deploy/openshift-clusters/README-external-host.md @@ -57,9 +57,12 @@ See [hands-off deployment](../aws-hypervisor/README.md#automated-rhsm-registrati #### Option B: Local Variable File ```bash -cp vars/init-host.yml vars/init-host.yml.local -# Edit vars/init-host.yml.local with your credentials +# From the repository root +cp deploy/openshift-clusters/vars/init-host.yml config/init-host.yml.local +# Edit config/init-host.yml.local with your credentials, then distribute it: +cd deploy/ && make sync-config ``` +Creating `vars/init-host.yml.local` directly still works as before. #### Option C: Command Line ```bash diff --git a/deploy/openshift-clusters/README-kcli.md b/deploy/openshift-clusters/README-kcli.md index 8bf299c8..78a27fd3 100644 --- a/deploy/openshift-clusters/README-kcli.md +++ b/deploy/openshift-clusters/README-kcli.md @@ -53,19 +53,16 @@ The kcli-install role automatically handles target host setup including: #### Pull Secret -Place your pull secret in the role files directory: +Place your pull secret in the central `config/` folder at the repository root: ```bash -# Navigate to the kcli-install files directory -cd roles/kcli/kcli-install/files/ - -# Create pull secret file (paste your pull secret content) -cat > pull-secret.json << EOF +# From the repository root, create the pull secret file (paste your pull secret content) +cat > config/pull-secret.json << EOF {"auths":{"your-pull-secret-content-here"}} EOF ``` -The deployment will automatically copy the pull secret from the files directory to the remote host during deployment. +The `make` targets sync it to the role files directory (`roles/kcli/kcli-install/files/pull-secret.json`); if you run `ansible-playbook` directly, run `make sync-config` from the `deploy/` directory first. Creating the file directly in the role files directory still works as before. The deployment will automatically copy the pull secret from the files directory to the remote host during deployment. #### SSH Key (Automatic from Localhost) The deployment automatically reads your SSH public key from `~/.ssh/id_ed25519.pub` on your **local machine** (ansible controller) and: @@ -91,9 +88,9 @@ You can configure the deployment using any combination of these methods (in prec 1. **Command line variables** (highest precedence) 2. **Playbook vars section** 3. **vars/kcli.yml** (user configuration file) -4. **Role defaults** (lowest precedence) (`vars/kcli.yml.template`) +4. **Role defaults** (lowest precedence) (`config/kcli.yml.template` at the repository root) -For simple overrides, the command line is recommended. For setting your preferred permanent config, copy [kcli.yml.template](vars/kcli.yml.template) to [kcli.yml](vars/kcli.yml) and update the values to your preference. This file is not tracked by Git and will persist between TNT updates. +For simple overrides, the command line is recommended. For setting your preferred permanent config, copy [kcli.yml.template](../../config/kcli.yml.template) at the repository root to `config/kcli.yml` and update the values to your preference. The `make` targets sync it to `vars/kcli.yml`, where the playbook reads it; if you run `ansible-playbook` directly, run `make sync-config` from the `deploy/` directory first (creating `vars/kcli.yml` directly still works). This file is not tracked by Git and will persist between TNT updates. You can find more information on the official ansible documentation https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_variables.html: diff --git a/deploy/openshift-clusters/README.md b/deploy/openshift-clusters/README.md index 33f251ee..18f9b6d9 100644 --- a/deploy/openshift-clusters/README.md +++ b/deploy/openshift-clusters/README.md @@ -59,7 +59,7 @@ The deployment process involves updating configuration files and running an Ansi **Tip**: To skip the `-i inventory.ini` argument in all ansible commands, copy the inventory file to Ansible's default location (`/etc/ansible/hosts` on Linux, may vary on other operating systems). #### Config files for dev-scripts -- In `roles/dev-scripts/install-dev/files/`, review `config_XXXXX_example.sh` files and copy them to `config_XXXXX.sh` as needed, removing the `_example` from the filename. +- In the `config/` folder at the repository root, review the `config_XXXXX_example.sh` files and copy them to `config/config_XXXXX.sh` as needed, removing the `_example` from the filename. The `make` targets sync them to `roles/dev-scripts/install-dev/files/`, where the playbooks read them; if you run `ansible-playbook` directly, run `make sync-config` from the `deploy/` directory first. Creating `config_XXXXX.sh` directly in `roles/dev-scripts/install-dev/files/` still works as before. - The config file for each topology is slightly different. Sample `config_arbiter_example.sh` and `config_fencing_example.sh` files are provided, ready to use with the AWS dev hypervisor. You can change the variables inside (see Note below), but when copying them, the expected file names are `config_arbiter.sh` and `config_fencing.sh`. - The arbiter config file contains separate configuration sections for IPI and Agent-based installations. Use the appropriate section based on your chosen installation method. - Unless you're using `OPENSHIFT_CI="True"` to avoid using private images, you should fill CI_TOKEN with your own token. You can get it from https://console-openshift-console.apps.ci.l2s4.p1.openshiftapps.com. Start by clicking your name in the top right and clicking "copy login command." At this point, a new window will open, and you should click on "Display Token." It should now display an API token you can copy over to your profile. @@ -69,7 +69,7 @@ The deployment process involves updating configuration files and running an Ansi > Note: The config.sh file is passed to metal-scripts. A full list of acceptable values can be found by checking the linked config_example.sh file in the [openshift-metal3/dev-scripts/config_example.sh](https://github.com/openshift-metal3/dev-scripts/blob/master/config_example.sh) repository. #### Pull secret -- Create `pull-secret.json`: Create a file named pull-secret.json in the `roles/dev-scripts/install-dev/files/` directory and paste your pull secret JSON string into it. +- Create `pull-secret.json`: Create a file named pull-secret.json in the `config/` folder at the repository root and paste your pull secret JSON string into it. The sync distributes it to both the dev-scripts and kcli role locations. (Creating it directly in `roles/dev-scripts/install-dev/files/` still works as before.) #### SSH access (optional) diff --git a/deploy/openshift-clusters/assisted-install.yml b/deploy/openshift-clusters/assisted-install.yml index ccff6420..b03410bd 100644 --- a/deploy/openshift-clusters/assisted-install.yml +++ b/deploy/openshift-clusters/assisted-install.yml @@ -2,7 +2,8 @@ # Deploy a spoke TNF cluster via ACM/assisted installer on an existing hub cluster. # # Prerequisites: -# - vars/assisted.yml exists (copy from vars/assisted.yml.template) +# - vars/assisted.yml exists (copy config/assisted.yml.template to +# config/assisted.yml at the repo root; make targets sync it here) # # Usage: # make deploy fencing-assisted diff --git a/deploy/openshift-clusters/roles/assisted/assisted-spoke/README.md b/deploy/openshift-clusters/roles/assisted/assisted-spoke/README.md index 067b3d8f..7b9ec42d 100644 --- a/deploy/openshift-clusters/roles/assisted/assisted-spoke/README.md +++ b/deploy/openshift-clusters/roles/assisted/assisted-spoke/README.md @@ -97,13 +97,15 @@ ansible-playbook assisted-install.yml -i inventory.ini ### Configuration -Copy and customize the variables template: +Copy and customize the variables template in the central `config/` folder at the repository root: ```bash -cp vars/assisted.yml.template vars/assisted.yml -# Edit vars/assisted.yml with desired spoke configuration +cp config/assisted.yml.template config/assisted.yml +# Edit config/assisted.yml with desired spoke configuration ``` +The make targets sync it to `vars/assisted.yml`, where the playbook reads it (creating `vars/assisted.yml` directly still works). + ### Accessing the Spoke Cluster After deployment: diff --git a/deploy/openshift-clusters/roles/kcli/kcli-install/README.md b/deploy/openshift-clusters/roles/kcli/kcli-install/README.md index 51762859..0101098b 100644 --- a/deploy/openshift-clusters/roles/kcli/kcli-install/README.md +++ b/deploy/openshift-clusters/roles/kcli/kcli-install/README.md @@ -49,7 +49,7 @@ Required Ansible collections (install with `ansible-galaxy collection install -r This role follows the same authentication file conventions as the dev-scripts role for consistency: -- **Pull Secret**: Must be placed in role `files/pull-secret.json` and will be automatically copied to remote host user home directory +- **Pull Secret**: Read from role `files/pull-secret.json` and automatically copied to remote host user home directory. Create it as `config/pull-secret.json` at the repository root (the make targets sync it here), or place it in `files/` directly - **SSH Key**: Read from localhost (`~/.ssh/id_ed25519.pub` on ansible controller) and copied to remote host for kcli, plus installed as authorized key via config role ## Role Variables @@ -60,7 +60,7 @@ This role follows the same authentication file conventions as the dev-scripts ro - `topology`: Cluster topology - "fencing" or "arbiter" (matches install-dev role) - `domain`: Base domain for the cluster - `pull_secret_path`: Path to OpenShift pull secret file in role files directory (default: `{{ role_path }}/files/pull-secret.json`) - - Place your pull secret as `pull-secret.json` in the `files/` directory + - Create your pull secret as `config/pull-secret.json` at the repository root (synced here by the make targets), or place it as `pull-secret.json` in the `files/` directory directly ### Cluster Configuration @@ -78,7 +78,7 @@ This role follows the same authentication file conventions as the dev-scripts ro - `vm_disk_size`: Disk size per node in GB (default: 120) ### OpenShift Version -See [defaults](../../../vars/kcli.yml.template) for default values +See [defaults](../../../../../config/kcli.yml.template) for default values If you're installing a specific openshift release image, you will need to set the proper channel in ocp_version - `ocp_version`: OpenShift version channel @@ -132,15 +132,15 @@ If you're installing a specific openshift release image, you will need to set th ansible-galaxy collection install -r collections/requirements.yml ``` -2. Download OpenShift pull secret and place in role files directory: +2. Download OpenShift pull secret and place it in the central `config/` folder: ```bash -# Navigate to the kcli-install files directory -cd roles/kcli/kcli-install/files/ - -# Create pull secret file (paste your pull secret content) -cat > pull-secret.json << EOF +# From the repository root, create the pull secret file (paste your pull secret content) +cat > config/pull-secret.json << EOF {"auths":{"your-pull-secret-content-here"}} EOF + +# Distribute it to the role files directory (or let any make target do it) +cd deploy/ && make sync-config && cd ../ ``` - For CI builds: Ensure pull secret includes `registry.ci.openshift.org` access diff --git a/deploy/openshift-clusters/scripts/deploy-fencing-assisted.sh b/deploy/openshift-clusters/scripts/deploy-fencing-assisted.sh index f4dd8fee..6c1e5dfe 100755 --- a/deploy/openshift-clusters/scripts/deploy-fencing-assisted.sh +++ b/deploy/openshift-clusters/scripts/deploy-fencing-assisted.sh @@ -30,7 +30,8 @@ fi if [[ ! -f "${DEPLOY_DIR}/openshift-clusters/vars/assisted.yml" ]]; then echo "Error: vars/assisted.yml not found." echo "Copy the template and customize it:" - echo " cp ${DEPLOY_DIR}/openshift-clusters/vars/assisted.yml.template ${DEPLOY_DIR}/openshift-clusters/vars/assisted.yml" + echo " cp config/assisted.yml.template config/assisted.yml" + echo "(from the repository root; it is synced to vars/assisted.yml on the next run)" exit 1 fi diff --git a/deploy/openshift-clusters/scripts/get-tnf-logs.sh b/deploy/openshift-clusters/scripts/get-tnf-logs.sh index e99c1250..00badb63 100755 --- a/deploy/openshift-clusters/scripts/get-tnf-logs.sh +++ b/deploy/openshift-clusters/scripts/get-tnf-logs.sh @@ -7,6 +7,9 @@ DEPLOY_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)" # Get the repository root directory (one level up from deploy) REPO_ROOT="$(cd "${DEPLOY_DIR}/.." && pwd)" +# shellcheck source=/dev/null +source "${DEPLOY_DIR}/aws-hypervisor/scripts/common.sh" + set -o nounset set -o errexit set -o pipefail diff --git a/helpers/keep-instance.sh b/helpers/keep-instance.sh index 967d90fb..ae477129 100755 --- a/helpers/keep-instance.sh +++ b/helpers/keep-instance.sh @@ -13,12 +13,9 @@ if [[ -z "${INSTANCE_ID}" ]]; then exit 1 fi -# Source instance.env for REGION (same as deploy/aws-hypervisor/scripts/common.sh) -INSTANCE_ENV="${SCRIPT_DIR}/../deploy/aws-hypervisor/instance.env" -if [[ -f "${INSTANCE_ENV}" ]]; then - # shellcheck source=/dev/null - source "${INSTANCE_ENV}" -fi +# common.sh syncs config/ files and sources instance.env (for REGION) +# shellcheck source=/dev/null +source "${SCRIPT_DIR}/../deploy/aws-hypervisor/scripts/common.sh" REGION="${REGION:-${AWS_DEFAULT_REGION:-}}" if [[ -z "${REGION}" ]]; then From 114df4ac6b8f76bd560c2a0dbea3b1f5712b5411 Mon Sep 17 00:00:00 2001 From: Pablo Fontanilla Date: Thu, 9 Jul 2026 11:59:02 +0200 Subject: [PATCH 03/15] feat: move common.sh to deploy/, add baremetal config sync and 0-byte guard Move common.sh from deploy/aws-hypervisor/scripts/ to deploy/ so all deploy paths (AWS and baremetal) can source it without reaching into aws-hypervisor internals. Add config_baremetal_fencing.sh to the sync manifest and have baremetal-adopt generate it into config/. Skip syncing 0-byte source files to prevent accidental credential blanking. Co-Authored-By: Claude Opus 4.6 --- CLAUDE.md | 2 ++ config/README.md | 1 + deploy/Makefile | 2 +- deploy/aws-hypervisor/scripts/create.sh | 2 +- deploy/aws-hypervisor/scripts/destroy.sh | 2 +- deploy/aws-hypervisor/scripts/force-stop.sh | 2 +- deploy/aws-hypervisor/scripts/init.sh | 2 +- deploy/aws-hypervisor/scripts/inventory.sh | 2 +- .../scripts/print_instance_data.sh | 2 +- deploy/aws-hypervisor/scripts/ssh.sh | 2 +- deploy/aws-hypervisor/scripts/start.sh | 2 +- deploy/aws-hypervisor/scripts/stop.sh | 2 +- deploy/{aws-hypervisor/scripts => }/common.sh | 19 +++++++++++------- .../scripts/baremetal-adopt.sh | 20 +++++++++++-------- .../openshift-clusters/scripts/clean-spoke.sh | 2 +- deploy/openshift-clusters/scripts/clean.sh | 2 +- .../scripts/deploy-baremetal.sh | 3 +++ .../scripts/deploy-cluster.sh | 2 +- .../scripts/deploy-fencing-assisted.sh | 2 +- .../openshift-clusters/scripts/full-clean.sh | 2 +- .../scripts/get-tnf-logs.sh | 2 +- .../openshift-clusters/scripts/patch-nodes.sh | 2 +- .../scripts/redeploy-cluster.sh | 2 +- .../scripts/shutdown-cluster.sh | 2 +- .../scripts/startup-cluster.sh | 2 +- 25 files changed, 50 insertions(+), 35 deletions(-) rename deploy/{aws-hypervisor/scripts => }/common.sh (93%) diff --git a/CLAUDE.md b/CLAUDE.md index 5c6857b0..58885ae6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -136,6 +136,7 @@ User configuration files are created in `config/` at the repository root (templa - `config/instance.env`: AWS hypervisor settings (copy from `config/instance.env.template`) - `config/pull-secret.json`: OpenShift pull secret, fanned out to both dev-scripts and kcli role locations - `config/config_arbiter.sh`, `config/config_fencing.sh`, `config/config_sno.sh`: dev-scripts topology configs (copy from the `config/config_*_example.sh` files) +- `config/config_baremetal_fencing.sh`: baremetal fencing topology config (generated by `make baremetal-adopt`) - `config/kcli.yml`: kcli variable overrides (copy from `config/kcli.yml.template`) - `config/assisted.yml`: assisted installer variables (copy from `config/assisted.yml.template`) - `config/init-host.yml.local`: external-host RHSM overrides (copy from `deploy/openshift-clusters/vars/init-host.yml`) @@ -145,6 +146,7 @@ User configuration files are created in `config/` at the repository root (templa - `roles/dev-scripts/install-dev/files/config_arbiter.sh`: Arbiter topology config - `roles/dev-scripts/install-dev/files/config_fencing.sh`: Fencing topology config - `roles/dev-scripts/install-dev/files/config_sno.sh`: Single Node OpenShift config +- `roles/dev-scripts/install-dev/files/config_baremetal_fencing.sh`: Baremetal fencing topology config - `roles/dev-scripts/install-dev/files/pull-secret.json`: OpenShift pull secret #### Kcli Method (canonical locations) diff --git a/config/README.md b/config/README.md index a1c0189d..d9344cb7 100644 --- a/config/README.md +++ b/config/README.md @@ -18,6 +18,7 @@ prevents your real config files (including the pull secret) from ever being comm | `config_arbiter.sh` | dev-scripts arbiter topology | `deploy/openshift-clusters/roles/dev-scripts/install-dev/files/config_arbiter.sh` | `cp config/config_arbiter_example.sh config/config_arbiter.sh` | | `config_fencing.sh` | dev-scripts fencing topology | `deploy/openshift-clusters/roles/dev-scripts/install-dev/files/config_fencing.sh` | `cp config/config_fencing_example.sh config/config_fencing.sh` | | `config_sno.sh` | dev-scripts SNO topology | `deploy/openshift-clusters/roles/dev-scripts/install-dev/files/config_sno.sh` | `cp config/config_sno_example.sh config/config_sno.sh` | +| `config_baremetal_fencing.sh` | dev-scripts baremetal fencing topology | `deploy/openshift-clusters/roles/dev-scripts/install-dev/files/config_baremetal_fencing.sh` | Generated by `make baremetal-adopt`, or copy from canonical location | | `kcli.yml` | kcli deployment (optional overrides) | `deploy/openshift-clusters/vars/kcli.yml` | `cp config/kcli.yml.template config/kcli.yml` | | `assisted.yml` | assisted installer spoke deployment | `deploy/openshift-clusters/vars/assisted.yml` | `cp config/assisted.yml.template config/assisted.yml` | | `init-host.yml.local` | external host initialization (optional RHSM overrides) | `deploy/openshift-clusters/vars/init-host.yml.local` | `cp deploy/openshift-clusters/vars/init-host.yml config/init-host.yml.local` | diff --git a/deploy/Makefile b/deploy/Makefile index 2b48959e..392dff0d 100644 --- a/deploy/Makefile +++ b/deploy/Makefile @@ -62,7 +62,7 @@ inventory: @./aws-hypervisor/scripts/inventory.sh sync-config: - @bash -c 'source ./aws-hypervisor/scripts/common.sh; if [ "$${CONFIG_SYNCED_COUNT:-0}" -eq 0 ]; then echo "Config files: everything up to date."; fi' + @bash -c 'source ./common.sh; if [ "$${CONFIG_SYNCED_COUNT:-0}" -eq 0 ]; then echo "Config files: everything up to date."; fi' fencing-ipi: @./openshift-clusters/scripts/deploy-cluster.sh --topology fencing --method ipi diff --git a/deploy/aws-hypervisor/scripts/create.sh b/deploy/aws-hypervisor/scripts/create.sh index 0e5a08c6..804a1514 100755 --- a/deploy/aws-hypervisor/scripts/create.sh +++ b/deploy/aws-hypervisor/scripts/create.sh @@ -2,7 +2,7 @@ SCRIPT_DIR=$(dirname "$0") # shellcheck source=/dev/null -source "${SCRIPT_DIR}/common.sh" +source "${SCRIPT_DIR}/../../common.sh" set -o nounset set -o errexit diff --git a/deploy/aws-hypervisor/scripts/destroy.sh b/deploy/aws-hypervisor/scripts/destroy.sh index 84b3c28e..2027692d 100755 --- a/deploy/aws-hypervisor/scripts/destroy.sh +++ b/deploy/aws-hypervisor/scripts/destroy.sh @@ -2,7 +2,7 @@ SCRIPT_DIR=$(dirname "$0") # shellcheck source=/dev/null -source "${SCRIPT_DIR}/common.sh" +source "${SCRIPT_DIR}/../../common.sh" set -o nounset set -o errexit diff --git a/deploy/aws-hypervisor/scripts/force-stop.sh b/deploy/aws-hypervisor/scripts/force-stop.sh index 0a7e0992..7138845f 100755 --- a/deploy/aws-hypervisor/scripts/force-stop.sh +++ b/deploy/aws-hypervisor/scripts/force-stop.sh @@ -2,7 +2,7 @@ SCRIPT_DIR=$(dirname "$0") # shellcheck source=/dev/null -source "${SCRIPT_DIR}/common.sh" +source "${SCRIPT_DIR}/../../common.sh" set -o nounset set -o errexit diff --git a/deploy/aws-hypervisor/scripts/init.sh b/deploy/aws-hypervisor/scripts/init.sh index a7af87be..2e57e174 100755 --- a/deploy/aws-hypervisor/scripts/init.sh +++ b/deploy/aws-hypervisor/scripts/init.sh @@ -1,7 +1,7 @@ #!/bin/bash SCRIPT_DIR=$(dirname "$0") # shellcheck source=/dev/null -source "${SCRIPT_DIR}/common.sh" +source "${SCRIPT_DIR}/../../common.sh" node_dir="$(get_node_dir)" instance_ip="$(cat "${node_dir}/ssh_user")@$(cat "${node_dir}/public_address")" diff --git a/deploy/aws-hypervisor/scripts/inventory.sh b/deploy/aws-hypervisor/scripts/inventory.sh index 9f8169f0..1306fa77 100755 --- a/deploy/aws-hypervisor/scripts/inventory.sh +++ b/deploy/aws-hypervisor/scripts/inventory.sh @@ -2,7 +2,7 @@ SCRIPT_DIR=$(dirname "$0") # shellcheck source=/dev/null -source "${SCRIPT_DIR}/common.sh" +source "${SCRIPT_DIR}/../../common.sh" set -o nounset set -o errexit diff --git a/deploy/aws-hypervisor/scripts/print_instance_data.sh b/deploy/aws-hypervisor/scripts/print_instance_data.sh index 8ddce8a5..fee3bf02 100755 --- a/deploy/aws-hypervisor/scripts/print_instance_data.sh +++ b/deploy/aws-hypervisor/scripts/print_instance_data.sh @@ -1,7 +1,7 @@ #!/bin/bash SCRIPT_DIR=$(dirname "$0") # shellcheck source=/dev/null -source "${SCRIPT_DIR}/common.sh" +source "${SCRIPT_DIR}/../../common.sh" node_dir="$(get_node_dir)" echo "Stack: $(cat "${node_dir}/rhel_host_stack_name")" diff --git a/deploy/aws-hypervisor/scripts/ssh.sh b/deploy/aws-hypervisor/scripts/ssh.sh index a1411a02..92152941 100755 --- a/deploy/aws-hypervisor/scripts/ssh.sh +++ b/deploy/aws-hypervisor/scripts/ssh.sh @@ -1,7 +1,7 @@ #!/bin/bash SCRIPT_DIR=$(dirname "$0") # shellcheck source=/dev/null -source "${SCRIPT_DIR}/common.sh" +source "${SCRIPT_DIR}/../../common.sh" node_dir="$(get_node_dir)" instance_ip="$(cat "${node_dir}/ssh_user")@$(cat "${node_dir}/public_address")" diff --git a/deploy/aws-hypervisor/scripts/start.sh b/deploy/aws-hypervisor/scripts/start.sh index 4e56928c..a81cf74c 100755 --- a/deploy/aws-hypervisor/scripts/start.sh +++ b/deploy/aws-hypervisor/scripts/start.sh @@ -2,7 +2,7 @@ SCRIPT_DIR=$(dirname "$0") # shellcheck source=/dev/null -source "${SCRIPT_DIR}/common.sh" +source "${SCRIPT_DIR}/../../common.sh" set -o nounset set -o errexit diff --git a/deploy/aws-hypervisor/scripts/stop.sh b/deploy/aws-hypervisor/scripts/stop.sh index d889db75..b020176b 100755 --- a/deploy/aws-hypervisor/scripts/stop.sh +++ b/deploy/aws-hypervisor/scripts/stop.sh @@ -2,7 +2,7 @@ SCRIPT_DIR=$(dirname "$0") # shellcheck source=/dev/null -source "${SCRIPT_DIR}/common.sh" +source "${SCRIPT_DIR}/../../common.sh" set -o nounset set -o errexit diff --git a/deploy/aws-hypervisor/scripts/common.sh b/deploy/common.sh similarity index 93% rename from deploy/aws-hypervisor/scripts/common.sh rename to deploy/common.sh index 23501396..a3b47266 100755 --- a/deploy/aws-hypervisor/scripts/common.sh +++ b/deploy/common.sh @@ -1,7 +1,7 @@ #!/bin/bash SCRIPT_DIR=$(dirname "$0") COMMON_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_ROOT="$(cd "${COMMON_DIR}/../../.." && pwd)" +REPO_ROOT="$(cd "${COMMON_DIR}/.." && pwd)" readonly COLOR_RED='\033[0;31m' readonly COLOR_YELLOW='\033[0;33m' @@ -28,6 +28,7 @@ CONFIG_SYNC_MANIFEST=( "config_arbiter.sh:deploy/openshift-clusters/roles/dev-scripts/install-dev/files/config_arbiter.sh" "config_fencing.sh:deploy/openshift-clusters/roles/dev-scripts/install-dev/files/config_fencing.sh" "config_sno.sh:deploy/openshift-clusters/roles/dev-scripts/install-dev/files/config_sno.sh" + "config_baremetal_fencing.sh:deploy/openshift-clusters/roles/dev-scripts/install-dev/files/config_baremetal_fencing.sh" "pull-secret.json:deploy/openshift-clusters/roles/dev-scripts/install-dev/files/pull-secret.json:deploy/openshift-clusters/roles/kcli/kcli-install/files/pull-secret.json" "kcli.yml:deploy/openshift-clusters/vars/kcli.yml" "assisted.yml:deploy/openshift-clusters/vars/assisted.yml" @@ -46,6 +47,10 @@ function sync_config_files() { IFS=':' read -r -a fields <<< "${entry}" src="${REPO_ROOT}/config/${fields[0]}" [[ -f "${src}" ]] || continue + if [[ ! -s "${src}" ]]; then + msg_warning "config/${fields[0]} is empty (0 bytes), skipping sync" + continue + fi for dest in "${fields[@]:1}"; do if [[ ! -f "${REPO_ROOT}/${dest}" || "${src}" -nt "${REPO_ROOT}/${dest}" ]]; then msg_info "config/${fields[0]} is newer, updating ${dest}" @@ -57,9 +62,9 @@ function sync_config_files() { } sync_config_files -if [[ -f "${COMMON_DIR}/../instance.env" ]]; then +if [[ -f "${COMMON_DIR}/aws-hypervisor/instance.env" ]]; then # shellcheck source=/dev/null - source "${COMMON_DIR}/../instance.env" + source "${COMMON_DIR}/aws-hypervisor/instance.env" else msg_warning "instance.env not found (only needed for AWS hypervisor targets). To create it: cp config/instance.env.template config/instance.env and edit it." fi @@ -78,13 +83,13 @@ export CAPACITY_RESERVATION_DURATION_MINUTES="${CAPACITY_RESERVATION_DURATION_MI export NODE_ID="${NODE_ID:-node-0}" get_shared_dir() { - echo "${COMMON_DIR}/../${SHARED_DIR}" + echo "${COMMON_DIR}/aws-hypervisor/${SHARED_DIR}" } get_node_dir() { - local node_dir="${COMMON_DIR}/../${SHARED_DIR}/${NODE_ID}" - if [[ ! -d "$node_dir" && -f "${COMMON_DIR}/../${SHARED_DIR}/aws-instance-id" ]]; then - echo "${COMMON_DIR}/../${SHARED_DIR}" + local node_dir="${COMMON_DIR}/aws-hypervisor/${SHARED_DIR}/${NODE_ID}" + if [[ ! -d "$node_dir" && -f "${COMMON_DIR}/aws-hypervisor/${SHARED_DIR}/aws-instance-id" ]]; then + echo "${COMMON_DIR}/aws-hypervisor/${SHARED_DIR}" return fi echo "$node_dir" diff --git a/deploy/openshift-clusters/scripts/baremetal-adopt.sh b/deploy/openshift-clusters/scripts/baremetal-adopt.sh index fe77e50d..eea9dceb 100755 --- a/deploy/openshift-clusters/scripts/baremetal-adopt.sh +++ b/deploy/openshift-clusters/scripts/baremetal-adopt.sh @@ -499,26 +499,30 @@ main() { exit 0 fi - # Output alongside existing dev-scripts config files - local output_dir="${OC_DIR}/roles/dev-scripts/install-dev/files" + local repo_root + repo_root="$(cd "${OC_DIR}/../.." && pwd)" + local config_dir="${repo_root}/config" + local files_dir="${OC_DIR}/roles/dev-scripts/install-dev/files" - # Generate artifacts - local nodes_file="${output_dir}/ironic_nodes.json" + # Generate ironic nodes alongside existing dev-scripts files + local nodes_file="${files_dir}/ironic_nodes.json" generate_ironic_nodes_json "${nodes_file}" - # NODES_FILE path on the hypervisor — resolves when dev-scripts sources the config + # Generate config into config/ — sync-config carries it to the canonical location local remote_nodes_path="\${PWD}/ironic_nodes.json" - generate_baremetal_config "${output_dir}/config_baremetal_fencing.sh" "${remote_nodes_path}" + generate_baremetal_config "${config_dir}/config_baremetal_fencing.sh" "${remote_nodes_path}" echo "" info "Adoption complete. Generated artifacts:" echo " ${nodes_file}" - echo " ${output_dir}/config_baremetal_fencing.sh" + echo " ${config_dir}/config_baremetal_fencing.sh" echo "" - echo " Before deploying, verify these values in ${output_dir}/config_baremetal_fencing.sh:" + echo " Before deploying, verify these values in ${config_dir}/config_baremetal_fencing.sh:" echo " - CI_TOKEN (get from console-openshift-console.apps.ci.l2s4.p1.openshiftapps.com)" echo " - OPENSHIFT_RELEASE_IMAGE (find tags at quay.io/openshift-release-dev/ocp-release)" echo "" + echo " The config will be synced to its canonical location automatically by 'make sync-config'." + echo "" echo " Next: deploy to the nodes using one of the baremetal-deploy* options" } diff --git a/deploy/openshift-clusters/scripts/clean-spoke.sh b/deploy/openshift-clusters/scripts/clean-spoke.sh index da28982a..36313f21 100755 --- a/deploy/openshift-clusters/scripts/clean-spoke.sh +++ b/deploy/openshift-clusters/scripts/clean-spoke.sh @@ -7,7 +7,7 @@ SCRIPT_DIR=$(dirname "$0") DEPLOY_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)" # shellcheck source=/dev/null -source "${DEPLOY_DIR}/aws-hypervisor/scripts/common.sh" +source "${DEPLOY_DIR}/common.sh" set -o nounset set -o errexit diff --git a/deploy/openshift-clusters/scripts/clean.sh b/deploy/openshift-clusters/scripts/clean.sh index ccf9e44a..e6f9bcb2 100755 --- a/deploy/openshift-clusters/scripts/clean.sh +++ b/deploy/openshift-clusters/scripts/clean.sh @@ -6,7 +6,7 @@ SCRIPT_DIR=$(dirname "$0") DEPLOY_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)" # shellcheck source=/dev/null -source "${DEPLOY_DIR}/aws-hypervisor/scripts/common.sh" +source "${DEPLOY_DIR}/common.sh" set -o nounset set -o errexit diff --git a/deploy/openshift-clusters/scripts/deploy-baremetal.sh b/deploy/openshift-clusters/scripts/deploy-baremetal.sh index a5e8c161..e330968c 100755 --- a/deploy/openshift-clusters/scripts/deploy-baremetal.sh +++ b/deploy/openshift-clusters/scripts/deploy-baremetal.sh @@ -16,6 +16,9 @@ set -o pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" OC_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" +DEPLOY_DIR="$(cd "${OC_DIR}/.." && pwd)" +# shellcheck source=/dev/null +source "${DEPLOY_DIR}/common.sh" INVENTORY="${OC_DIR}/inventory_baremetal.ini" PLAYBOOK="${OC_DIR}/deploy-baremetal.yml" diff --git a/deploy/openshift-clusters/scripts/deploy-cluster.sh b/deploy/openshift-clusters/scripts/deploy-cluster.sh index 6fc1761a..f2197d7a 100755 --- a/deploy/openshift-clusters/scripts/deploy-cluster.sh +++ b/deploy/openshift-clusters/scripts/deploy-cluster.sh @@ -10,7 +10,7 @@ SCRIPT_DIR=$(dirname "$0") DEPLOY_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)" # shellcheck source=/dev/null -source "${DEPLOY_DIR}/aws-hypervisor/scripts/common.sh" +source "${DEPLOY_DIR}/common.sh" set -o nounset set -o errexit diff --git a/deploy/openshift-clusters/scripts/deploy-fencing-assisted.sh b/deploy/openshift-clusters/scripts/deploy-fencing-assisted.sh index 6c1e5dfe..70113941 100755 --- a/deploy/openshift-clusters/scripts/deploy-fencing-assisted.sh +++ b/deploy/openshift-clusters/scripts/deploy-fencing-assisted.sh @@ -6,7 +6,7 @@ SCRIPT_DIR=$(dirname "$0") DEPLOY_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)" # shellcheck source=/dev/null -source "${DEPLOY_DIR}/aws-hypervisor/scripts/common.sh" +source "${DEPLOY_DIR}/common.sh" set -o nounset set -o errexit diff --git a/deploy/openshift-clusters/scripts/full-clean.sh b/deploy/openshift-clusters/scripts/full-clean.sh index 6fbfdd7f..ad914f2d 100755 --- a/deploy/openshift-clusters/scripts/full-clean.sh +++ b/deploy/openshift-clusters/scripts/full-clean.sh @@ -6,7 +6,7 @@ SCRIPT_DIR=$(dirname "$0") DEPLOY_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)" # shellcheck source=/dev/null -source "${DEPLOY_DIR}/aws-hypervisor/scripts/common.sh" +source "${DEPLOY_DIR}/common.sh" set -o nounset set -o errexit diff --git a/deploy/openshift-clusters/scripts/get-tnf-logs.sh b/deploy/openshift-clusters/scripts/get-tnf-logs.sh index 00badb63..d000f9e3 100755 --- a/deploy/openshift-clusters/scripts/get-tnf-logs.sh +++ b/deploy/openshift-clusters/scripts/get-tnf-logs.sh @@ -8,7 +8,7 @@ DEPLOY_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)" REPO_ROOT="$(cd "${DEPLOY_DIR}/.." && pwd)" # shellcheck source=/dev/null -source "${DEPLOY_DIR}/aws-hypervisor/scripts/common.sh" +source "${DEPLOY_DIR}/common.sh" set -o nounset set -o errexit diff --git a/deploy/openshift-clusters/scripts/patch-nodes.sh b/deploy/openshift-clusters/scripts/patch-nodes.sh index ef138421..48aed102 100755 --- a/deploy/openshift-clusters/scripts/patch-nodes.sh +++ b/deploy/openshift-clusters/scripts/patch-nodes.sh @@ -6,7 +6,7 @@ SCRIPT_DIR=$(dirname "$0") DEPLOY_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)" # shellcheck source=/dev/null -source "${DEPLOY_DIR}/aws-hypervisor/scripts/common.sh" +source "${DEPLOY_DIR}/common.sh" set -o nounset set -o errexit diff --git a/deploy/openshift-clusters/scripts/redeploy-cluster.sh b/deploy/openshift-clusters/scripts/redeploy-cluster.sh index a14efdf7..f9e6cea5 100755 --- a/deploy/openshift-clusters/scripts/redeploy-cluster.sh +++ b/deploy/openshift-clusters/scripts/redeploy-cluster.sh @@ -5,7 +5,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" DEPLOY_DIR="$(cd "${SCRIPT_DIR}/../../" && pwd)" # shellcheck source=/dev/null -source "${DEPLOY_DIR}/aws-hypervisor/scripts/common.sh" +source "${DEPLOY_DIR}/common.sh" set -o nounset set -o errexit diff --git a/deploy/openshift-clusters/scripts/shutdown-cluster.sh b/deploy/openshift-clusters/scripts/shutdown-cluster.sh index 84972a55..ec5c5cb7 100755 --- a/deploy/openshift-clusters/scripts/shutdown-cluster.sh +++ b/deploy/openshift-clusters/scripts/shutdown-cluster.sh @@ -5,7 +5,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" DEPLOY_DIR="$(cd "${SCRIPT_DIR}/../../" && pwd)" # shellcheck source=/dev/null -source "${DEPLOY_DIR}/aws-hypervisor/scripts/common.sh" +source "${DEPLOY_DIR}/common.sh" set -o nounset set -o errexit diff --git a/deploy/openshift-clusters/scripts/startup-cluster.sh b/deploy/openshift-clusters/scripts/startup-cluster.sh index 89d0e082..0e6d2466 100755 --- a/deploy/openshift-clusters/scripts/startup-cluster.sh +++ b/deploy/openshift-clusters/scripts/startup-cluster.sh @@ -5,7 +5,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" DEPLOY_DIR="$(cd "${SCRIPT_DIR}/../../" && pwd)" # shellcheck source=/dev/null -source "${DEPLOY_DIR}/aws-hypervisor/scripts/common.sh" +source "${DEPLOY_DIR}/common.sh" set -o nounset set -o errexit From b42c4315bea94256f72d9f07914f901e89be4fea Mon Sep 17 00:00:00 2001 From: Pablo Fontanilla Date: Thu, 9 Jul 2026 17:15:46 +0200 Subject: [PATCH 04/15] fix: back up existing config before baremetal-adopt regeneration Re-running `make baremetal-adopt` after editing config_baremetal_fencing.sh (e.g. to set CI_TOKEN) would silently overwrite user edits. Back up the existing file with a timestamped suffix before regenerating. Co-Authored-By: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-Authored-By: Claude Opus 4.6 --- deploy/openshift-clusters/scripts/baremetal-adopt.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/deploy/openshift-clusters/scripts/baremetal-adopt.sh b/deploy/openshift-clusters/scripts/baremetal-adopt.sh index eea9dceb..a7791c63 100755 --- a/deploy/openshift-clusters/scripts/baremetal-adopt.sh +++ b/deploy/openshift-clusters/scripts/baremetal-adopt.sh @@ -510,6 +510,11 @@ main() { # Generate config into config/ — sync-config carries it to the canonical location local remote_nodes_path="\${PWD}/ironic_nodes.json" + if [[ -f "${config_dir}/config_baremetal_fencing.sh" ]]; then + local backup="${config_dir}/config_baremetal_fencing.sh.bak.$(date +%s)" + cp "${config_dir}/config_baremetal_fencing.sh" "${backup}" + info "Existing config_baremetal_fencing.sh backed up to ${backup}" + fi generate_baremetal_config "${config_dir}/config_baremetal_fencing.sh" "${remote_nodes_path}" echo "" From 9801c85029dbf8dc8aedb7c6a12dfd16adb1be1d Mon Sep 17 00:00:00 2001 From: Pablo Fontanilla Date: Thu, 9 Jul 2026 17:19:51 +0200 Subject: [PATCH 05/15] fix: address review findings from PR #92 - Fix broken common.sh path in keep-instance.sh after move to deploy/ - Handle cp failures in sync_config_files() so errors are reported instead of silently incrementing the sync count - Add nullglob guard in ansible-lint.sh syntax-check loop Co-Authored-By: Claude Opus 4.6 --- deploy/common.sh | 5 ++++- hack/ansible-lint.sh | 2 ++ helpers/keep-instance.sh | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/deploy/common.sh b/deploy/common.sh index a3b47266..9bc9cab3 100755 --- a/deploy/common.sh +++ b/deploy/common.sh @@ -54,7 +54,10 @@ function sync_config_files() { for dest in "${fields[@]:1}"; do if [[ ! -f "${REPO_ROOT}/${dest}" || "${src}" -nt "${REPO_ROOT}/${dest}" ]]; then msg_info "config/${fields[0]} is newer, updating ${dest}" - cp "${src}" "${REPO_ROOT}/${dest}" + if ! cp "${src}" "${REPO_ROOT}/${dest}"; then + msg_err "Failed to sync config/${fields[0]} to ${dest}" + continue + fi CONFIG_SYNCED_COUNT=$((CONFIG_SYNCED_COUNT + 1)) fi done diff --git a/hack/ansible-lint.sh b/hack/ansible-lint.sh index 6b859c94..2499f6f3 100755 --- a/hack/ansible-lint.sh +++ b/hack/ansible-lint.sh @@ -35,9 +35,11 @@ if [ "${OPENSHIFT_CI:-}" != "" ]; then for DIR in "${PLAYBOOK_DIRS[@]}"; do echo "Syntax-checking playbooks in ${DIR}..." cd "${REPO_ROOT}/${DIR}" + shopt -s nullglob for PLAYBOOK in *.yml; do ansible-playbook --syntax-check "${PLAYBOOK}" done + shopt -u nullglob done cd "${REPO_ROOT}" else diff --git a/helpers/keep-instance.sh b/helpers/keep-instance.sh index ae477129..f031603b 100755 --- a/helpers/keep-instance.sh +++ b/helpers/keep-instance.sh @@ -15,7 +15,7 @@ fi # common.sh syncs config/ files and sources instance.env (for REGION) # shellcheck source=/dev/null -source "${SCRIPT_DIR}/../deploy/aws-hypervisor/scripts/common.sh" +source "${SCRIPT_DIR}/../deploy/common.sh" REGION="${REGION:-${AWS_DEFAULT_REGION:-}}" if [[ -z "${REGION}" ]]; then From ad6094234812053e87594f4b3a7b94229b2e83f3 Mon Sep 17 00:00:00 2001 From: Pablo Fontanilla Date: Thu, 9 Jul 2026 17:31:13 +0200 Subject: [PATCH 06/15] fix: address remaining review findings from PR #92 - Makefile sync-config: add set -e so cp failures propagate - deploy-baremetal.sh: source common.sh before strict flags for consistent sync error handling across scripts - ansible-lint.sh: quote $CONTAINER_ENGINE and $CONTAINER_IMAGE Co-Authored-By: Claude Opus 4.6 --- deploy/Makefile | 2 +- deploy/openshift-clusters/scripts/deploy-baremetal.sh | 8 ++++---- hack/ansible-lint.sh | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/deploy/Makefile b/deploy/Makefile index 392dff0d..86db5902 100644 --- a/deploy/Makefile +++ b/deploy/Makefile @@ -62,7 +62,7 @@ inventory: @./aws-hypervisor/scripts/inventory.sh sync-config: - @bash -c 'source ./common.sh; if [ "$${CONFIG_SYNCED_COUNT:-0}" -eq 0 ]; then echo "Config files: everything up to date."; fi' + @bash -c 'set -e; source ./common.sh; if [ "$${CONFIG_SYNCED_COUNT:-0}" -eq 0 ]; then echo "Config files: everything up to date."; fi' fencing-ipi: @./openshift-clusters/scripts/deploy-cluster.sh --topology fencing --method ipi diff --git a/deploy/openshift-clusters/scripts/deploy-baremetal.sh b/deploy/openshift-clusters/scripts/deploy-baremetal.sh index e330968c..2b0bbad4 100755 --- a/deploy/openshift-clusters/scripts/deploy-baremetal.sh +++ b/deploy/openshift-clusters/scripts/deploy-baremetal.sh @@ -10,16 +10,16 @@ # Usage: # deploy-baremetal.sh [-- ] -set -o nounset -set -o errexit -set -o pipefail - SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" OC_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" DEPLOY_DIR="$(cd "${OC_DIR}/.." && pwd)" # shellcheck source=/dev/null source "${DEPLOY_DIR}/common.sh" +set -o nounset +set -o errexit +set -o pipefail + INVENTORY="${OC_DIR}/inventory_baremetal.ini" PLAYBOOK="${OC_DIR}/deploy-baremetal.yml" diff --git a/hack/ansible-lint.sh b/hack/ansible-lint.sh index 2499f6f3..b23c0f08 100755 --- a/hack/ansible-lint.sh +++ b/hack/ansible-lint.sh @@ -44,13 +44,13 @@ if [ "${OPENSHIFT_CI:-}" != "" ]; then cd "${REPO_ROOT}" else mkdir -p "${COLLECTIONS_CACHE}" - $CONTAINER_ENGINE run --rm \ + "${CONTAINER_ENGINE}" run --rm \ --env OPENSHIFT_CI=TRUE \ --env COLLECTIONS_CACHE=/var/cache/tnt-ansible-collections \ --volume "${PWD}:/workdir:z" \ --volume "${COLLECTIONS_CACHE}:/var/cache/tnt-ansible-collections:z" \ --entrypoint bash \ --workdir /workdir \ - $CONTAINER_IMAGE \ + "${CONTAINER_IMAGE}" \ hack/ansible-lint.sh "${@}" fi From 45003b26dedb94ebfb90264e871be269b10baf67 Mon Sep 17 00:00:00 2001 From: Pablo Fontanilla Date: Thu, 9 Jul 2026 17:32:12 +0200 Subject: [PATCH 07/15] fix: add SKIP_INSTANCE_ENV_WARNING flag to common.sh Callers like doctor.sh that have their own instance.env validation can suppress the generic warning without blanket stderr suppression. Co-Authored-By: Claude Opus 4.6 --- deploy/common.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/common.sh b/deploy/common.sh index 9bc9cab3..366a344e 100755 --- a/deploy/common.sh +++ b/deploy/common.sh @@ -68,7 +68,7 @@ sync_config_files if [[ -f "${COMMON_DIR}/aws-hypervisor/instance.env" ]]; then # shellcheck source=/dev/null source "${COMMON_DIR}/aws-hypervisor/instance.env" -else +elif [[ "${SKIP_INSTANCE_ENV_WARNING:-0}" != "1" ]]; then msg_warning "instance.env not found (only needed for AWS hypervisor targets). To create it: cp config/instance.env.template config/instance.env and edit it." fi From 0a43cbf17f5070d8e03370a5343b9b78be72e0a2 Mon Sep 17 00:00:00 2001 From: Pablo Fontanilla Date: Thu, 9 Jul 2026 17:34:17 +0200 Subject: [PATCH 08/15] fix: add missing mocked roles to .ansible-lint Playbooks assisted-install.yml and kcli-install.yml reference roles via the `role:` syntax that ansible-lint resolves at the repo root. Co-Authored-By: Claude Opus 4.6 --- .ansible-lint | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.ansible-lint b/.ansible-lint index b13a0483..3b924769 100644 --- a/.ansible-lint +++ b/.ansible-lint @@ -15,6 +15,9 @@ exclude_paths: # ansible-lint cannot see when invoked from the repository root. mock_roles: - git-user + - assisted/acm-install + - assisted/assisted-spoke + - kcli/kcli-redfish # Baseline debt: rules that fire on the pre-existing tree. New violations of any # rule NOT listed here fail `make verify`. Burn these down over time by fixing From 814c64e4cf62fae0595a935ec622ed607e55158e Mon Sep 17 00:00:00 2001 From: Pablo Fontanilla Date: Thu, 9 Jul 2026 17:39:29 +0200 Subject: [PATCH 09/15] fix: split local declaration to avoid masking return value (SC2155) Co-Authored-By: Claude Opus 4.6 --- deploy/openshift-clusters/scripts/baremetal-adopt.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deploy/openshift-clusters/scripts/baremetal-adopt.sh b/deploy/openshift-clusters/scripts/baremetal-adopt.sh index a7791c63..bb26d1da 100755 --- a/deploy/openshift-clusters/scripts/baremetal-adopt.sh +++ b/deploy/openshift-clusters/scripts/baremetal-adopt.sh @@ -511,7 +511,8 @@ main() { # Generate config into config/ — sync-config carries it to the canonical location local remote_nodes_path="\${PWD}/ironic_nodes.json" if [[ -f "${config_dir}/config_baremetal_fencing.sh" ]]; then - local backup="${config_dir}/config_baremetal_fencing.sh.bak.$(date +%s)" + local backup + backup="${config_dir}/config_baremetal_fencing.sh.bak.$(date +%s)" cp "${config_dir}/config_baremetal_fencing.sh" "${backup}" info "Existing config_baremetal_fencing.sh backed up to ${backup}" fi From 6b0b331619e23e88ab686aac3c5e26e0dad299a0 Mon Sep 17 00:00:00 2001 From: Pablo Fontanilla Date: Thu, 9 Jul 2026 18:11:07 +0200 Subject: [PATCH 10/15] refactor: move config sync from common.sh auto-call to Makefile prerequisite sync_config_files() was called at module scope whenever any script sourced common.sh, making every import a side-effecting operation. This forced callers like doctor.sh to opt out via SKIP_CONFIG_SYNC=1. Move the sync to a Makefile prerequisite on deploy and cluster targets only, keeping common.sh as a pure library. The sync-config target now explicitly calls sync_config_files, and only targets that actually consume synced config files depend on it. Co-Authored-By: Claude Opus 4.6 --- .claude/commands/setup.md | 4 ++-- CLAUDE.md | 2 +- config/README.md | 16 ++++++++++------ deploy/Makefile | 9 +++++++-- deploy/README.md | 2 +- deploy/aws-hypervisor/README.md | 2 +- deploy/common.sh | 1 - 7 files changed, 22 insertions(+), 14 deletions(-) diff --git a/.claude/commands/setup.md b/.claude/commands/setup.md index a6a88f71..4ab9a4df 100644 --- a/.claude/commands/setup.md +++ b/.claude/commands/setup.md @@ -60,7 +60,7 @@ Required for: external, kcli, dev-scripts Required for: kcli, dev-scripts -The same pull secret is used by both kcli and dev-scripts. Create it once in the central config folder: [config/pull-secret.json](config/pull-secret.json). The sync (run by every `make` target, or explicitly via `cd deploy && make sync-config`) distributes it to both role locations automatically. +The same pull secret is used by both kcli and dev-scripts. Create it once in the central config folder: [config/pull-secret.json](config/pull-secret.json). The sync (run automatically before deploy and cluster `make` targets, or explicitly via `cd deploy && make sync-config`) distributes it to both role locations automatically. **Setup logic:** - Check if [config/pull-secret.json](config/pull-secret.json) exists @@ -140,7 +140,7 @@ Required for: dev-scripts (when using CI builds) - Optional: Set `RHSM_ACTIVATION_KEY` and `RHSM_ORG` for hands-off deployment - Guide: https://access.redhat.com/solutions/3341191 - Suggest copying: `cp config/instance.env.template config/instance.env` - - Every `make` target syncs it to `deploy/aws-hypervisor/instance.env` where the scripts read it + - Deploy and cluster `make` targets sync it to `deploy/aws-hypervisor/instance.env` where the scripts read it **Validation:** - Verify [config/instance.env](config/instance.env) (or `deploy/aws-hypervisor/instance.env`) exists diff --git a/CLAUDE.md b/CLAUDE.md index 58885ae6..fb6ab600 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -131,7 +131,7 @@ make ansible-lint ### Key Configuration Files #### Central config/ Folder -User configuration files are created in `config/` at the repository root (templates and examples live there; see `config/README.md`). Every make-accessible script syncs `config/` files to the canonical locations below before doing anything else (newer-wins, never deletes); `make sync-config` runs the sync explicitly. Editing the canonical locations directly still works: a `config/` file only becomes authoritative once the user creates it. +User configuration files are created in `config/` at the repository root (templates and examples live there; see `config/README.md`). The Makefile runs `sync-config` as a prerequisite before deploy and cluster targets, copying `config/` files to the canonical locations below (newer-wins, never deletes); `make sync-config` runs the sync explicitly. Editing the canonical locations directly still works: a `config/` file only becomes authoritative once the user creates it. - `config/instance.env`: AWS hypervisor settings (copy from `config/instance.env.template`) - `config/pull-secret.json`: OpenShift pull secret, fanned out to both dev-scripts and kcli role locations diff --git a/config/README.md b/config/README.md index d9344cb7..985eea71 100644 --- a/config/README.md +++ b/config/README.md @@ -1,10 +1,11 @@ # Central configuration folder This folder is the single place to create your configuration files for the toolbox. -Every `make` target (and `make sync-config` explicitly) syncs files from here to the -locations where the scripts and playbooks actually read them. A file is only copied -when it is missing at the destination or the `config/` copy is newer, so editing the -canonical locations directly still works as before. +Deploy and lifecycle `make` targets automatically sync files from here to the +locations where the scripts and playbooks actually read them; `make sync-config` +runs the sync explicitly. A file is only copied when it is missing at the +destination or the `config/` copy is newer, so editing the canonical locations +directly still works as before. Only templates and examples are tracked in git; the `.gitignore` in this folder prevents your real config files (including the pull secret) from ever being committed. @@ -25,11 +26,14 @@ prevents your real config files (including the pull secret) from ever being comm ## Syncing -The sync runs automatically at the start of every script invoked through the -`deploy/` Makefile. To run it on its own (for example before invoking +The sync runs automatically as a Makefile prerequisite before deploy and +cluster targets. To run it on its own (for example before invoking `ansible-playbook` directly): ```bash cd deploy/ make sync-config ``` + +Scripts run directly (bypassing `make`) do not auto-sync — run +`make sync-config` first, or use `make` targets. diff --git a/deploy/Makefile b/deploy/Makefile index 86db5902..3b2987f2 100644 --- a/deploy/Makefile +++ b/deploy/Makefile @@ -61,8 +61,13 @@ info: inventory: @./aws-hypervisor/scripts/inventory.sh +# Sync config/ files to canonical locations before targets that consume them. +create init redeploy-cluster \ +fencing-ipi fencing-agent arbiter-ipi arbiter-agent arbiter-kcli fencing-kcli sno-ipi sno-agent fencing-assisted \ +baremetal-adopt baremetal-fencing-agent: sync-config + sync-config: - @bash -c 'set -e; source ./common.sh; if [ "$${CONFIG_SYNCED_COUNT:-0}" -eq 0 ]; then echo "Config files: everything up to date."; fi' + @bash -c 'set -e; source ./common.sh; sync_config_files; if [ "$${CONFIG_SYNCED_COUNT:-0}" -eq 0 ]; then echo "Config files: everything up to date."; fi' fencing-ipi: @./openshift-clusters/scripts/deploy-cluster.sh --topology fencing --method ipi @@ -133,7 +138,7 @@ help: @echo " keep-instance - Tag instance with keep-N retention (default: 2, max: 5)" @echo " Usage: make keep-instance or make keep-instance DAYS=5" @echo " sync-config - Sync files from config/ to their canonical locations" - @echo " (runs automatically before every other target)" + @echo " (runs automatically before deploy and lifecycle targets)" @echo "" @echo "OpenShift Cluster Deployment:" @echo " fencing-ipi - Deploy fencing IPI cluster (non-interactive)" diff --git a/deploy/README.md b/deploy/README.md index 955c5200..7e3e1287 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -40,7 +40,7 @@ Additionally, if you're using Mac OS, you might not have `timeout`, so you might ## Configuration -Before deployment, configure your environment by creating an `instance.env` file in the central [config/](../config/) folder. Copy `config/instance.env.template` to `config/instance.env` and set all variables to valid values for your user. Every `make` target syncs `config/` files to the locations the scripts read them from (`aws-hypervisor/instance.env` in this case); run `make sync-config` to sync explicitly. Editing `aws-hypervisor/instance.env` directly still works as before. See [config/README.md](../config/README.md) for all available configuration files. +Before deployment, configure your environment by creating an `instance.env` file in the central [config/](../config/) folder. Copy `config/instance.env.template` to `config/instance.env` and set all variables to valid values for your user. Deploy and cluster `make` targets automatically sync `config/` files to the locations the scripts read them from (`aws-hypervisor/instance.env` in this case); run `make sync-config` to sync explicitly. Editing `aws-hypervisor/instance.env` directly still works as before. See [config/README.md](../config/README.md) for all available configuration files. ## Available Commands diff --git a/deploy/aws-hypervisor/README.md b/deploy/aws-hypervisor/README.md index bf175203..62e01d13 100644 --- a/deploy/aws-hypervisor/README.md +++ b/deploy/aws-hypervisor/README.md @@ -5,7 +5,7 @@ This directory contains scripts for managing EC2 instances used as hypervisors f ## Configuration ### Environment Setup -Copy the `config/instance.env.template` file at the repository root to `config/instance.env` and set all variables to valid values for your user. Every `make` target syncs it to `instance.env` in this directory, where the scripts read it. +Copy the `config/instance.env.template` file at the repository root to `config/instance.env` and set all variables to valid values for your user. The Makefile syncs it to `instance.env` in this directory before deploy and cluster targets; run `make sync-config` explicitly when invoking scripts directly. ```bash # From the repository root diff --git a/deploy/common.sh b/deploy/common.sh index 366a344e..8f5042eb 100755 --- a/deploy/common.sh +++ b/deploy/common.sh @@ -63,7 +63,6 @@ function sync_config_files() { done done } -sync_config_files if [[ -f "${COMMON_DIR}/aws-hypervisor/instance.env" ]]; then # shellcheck source=/dev/null From c49b695bce20f6dea9b74e8c7b7964ea63a78649 Mon Sep 17 00:00:00 2001 From: pablofontanilla Date: Mon, 6 Jul 2026 14:38:42 +0000 Subject: [PATCH 11/15] feat: add 'make doctor' configuration preflight Read-only preflight that validates the full deployment configuration and prints a concrete fix command for every failure: required tools, SSH key, Ansible collections, inventory.ini, instance.env plus live AWS credentials, dev-scripts topology configs and pull secret, CI registry auth cross-check with live CI token check, and the kcli pull secret. Exits non-zero if and only if a check failed. - 'make doctor' runs common checks plus every auto-detected section - 'make doctor ' validates strictly for that deployment: the method's section becomes mandatory and its method-specific warnings are promoted to failures; cluster-type goals following 'doctor' are neutralized so they never trigger a deployment - inventory.ini stays informational when absent (the AWS flow generates it via 'make inventory'), but a present-but-unedited copy of the sample fails, since that breaks deployment in confusing ways - deploy-cluster.sh preflight error paths now point at 'make doctor' Checks are ported from the validation steps in .claude/commands/setup.md and the in-playbook preflight in roles/dev-scripts/install-dev/tasks/config.yml. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01LSBffYjh3rtLToAMR1pjQT --- CLAUDE.md | 2 + README.md | 2 + deploy/Makefile | 25 ++ deploy/README.md | 2 + .../scripts/deploy-cluster.sh | 2 + deploy/openshift-clusters/scripts/doctor.sh | 388 ++++++++++++++++++ 6 files changed, 421 insertions(+) create mode 100755 deploy/openshift-clusters/scripts/doctor.sh diff --git a/CLAUDE.md b/CLAUDE.md index fb6ab600..23c8d04f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -38,6 +38,8 @@ make patch-nodes # Build resource-agents RPM and patch cluster nodes make ssh # SSH into EC2 instance make info # Display instance information make inventory # Update inventory.ini with current instance IP +make doctor # Validate configuration and prerequisites (read-only) +make doctor # Validate strictly for a specific cluster type (e.g. fencing-kcli) ``` ### Ansible Deployment Methods diff --git a/README.md b/README.md index d92a14bf..7a906511 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,8 @@ If you're using [Claude Code](https://claude.ai/code), use the `/setup` command - Validating your setup Run `/setup` to begin, or `/setup ` to configure a specific deployment method (aws, external, kcli, dev-scripts). + +If you are not using Claude Code, or want a quick non-interactive check, run `make doctor` from the `deploy/` directory. It validates required tools, SSH keys, Ansible collections, and configuration files, printing a fix command for every problem found. `make doctor ` (for example `make doctor arbiter-ipi`) validates strictly for one deployment type. ## Helpers The [helpers/](helpers/) directory contains utilities for cluster operations including resource-agents patching, fencing validation, and containerized build validation. To quickly verify a resource-agents branch compiles on CentOS Stream 9 and 10: diff --git a/deploy/Makefile b/deploy/Makefile index 3b2987f2..0a3f4970 100644 --- a/deploy/Makefile +++ b/deploy/Makefile @@ -13,6 +13,20 @@ ifeq (deploy,$(firstword $(MAKECMDGOALS))) endif endif +# Handle 'make doctor ' pattern +# When 'doctor' is first, following cluster types select strict validation for +# that deployment; the cluster-type goals themselves are neutralized below so +# they don't trigger an actual deployment. +ifeq (doctor,$(firstword $(MAKECMDGOALS))) + DOCTOR_ARGS := $(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS)) + ifneq ($(DOCTOR_ARGS),) + DOCTOR_INVALID_ARGS := $(filter-out $(VALID_CLUSTER_TYPES),$(DOCTOR_ARGS)) + ifneq ($(DOCTOR_INVALID_ARGS),) + $(error Unknown cluster type: '$(DOCTOR_INVALID_ARGS)'. Run 'make help' for more information.) + endif + endif +endif + create: @./aws-hypervisor/scripts/create.sh @@ -69,6 +83,14 @@ baremetal-adopt baremetal-fencing-agent: sync-config sync-config: @bash -c 'set -e; source ./common.sh; sync_config_files; if [ "$${CONFIG_SYNCED_COUNT:-0}" -eq 0 ]; then echo "Config files: everything up to date."; fi' +doctor: + @./openshift-clusters/scripts/doctor.sh $(DOCTOR_ARGS) + +ifeq (doctor,$(firstword $(MAKECMDGOALS))) +# Cluster types after 'doctor' are arguments to doctor.sh, not deployments +$(VALID_CLUSTER_TYPES): + @: +else fencing-ipi: @./openshift-clusters/scripts/deploy-cluster.sh --topology fencing --method ipi @@ -96,6 +118,7 @@ fencing-kcli: fencing-assisted: @$(MAKE) fencing-ipi @./openshift-clusters/scripts/deploy-fencing-assisted.sh +endif keep-instance: @../helpers/keep-instance.sh '$(DAYS)' @@ -135,6 +158,8 @@ help: @echo " ssh - SSH into the EC2 instance" @echo " info - Display instance information" @echo " inventory - Update inventory.ini with current instance IP" + @echo " doctor - Validate configuration and prerequisites (read-only)" + @echo " doctor - Validate strictly for a specific cluster type" @echo " keep-instance - Tag instance with keep-N retention (default: 2, max: 5)" @echo " Usage: make keep-instance or make keep-instance DAYS=5" @echo " sync-config - Sync files from config/ to their canonical locations" diff --git a/deploy/README.md b/deploy/README.md index 7e3e1287..ba33c410 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -40,6 +40,8 @@ Additionally, if you're using Mac OS, you might not have `timeout`, so you might ## Configuration +Run `make doctor` at any time to validate your setup before deploying. It is a read-only preflight that checks required tools, SSH keys, Ansible collections, and every configuration file, and prints a concrete fix command for each problem it finds. Use `make doctor ` (for example `make doctor fencing-kcli`) to validate strictly for a specific deployment type: the checks specific to that deployment method become mandatory. + Before deployment, configure your environment by creating an `instance.env` file in the central [config/](../config/) folder. Copy `config/instance.env.template` to `config/instance.env` and set all variables to valid values for your user. Deploy and cluster `make` targets automatically sync `config/` files to the locations the scripts read them from (`aws-hypervisor/instance.env` in this case); run `make sync-config` to sync explicitly. Editing `aws-hypervisor/instance.env` directly still works as before. See [config/README.md](../config/README.md) for all available configuration files. ## Available Commands diff --git a/deploy/openshift-clusters/scripts/deploy-cluster.sh b/deploy/openshift-clusters/scripts/deploy-cluster.sh index f2197d7a..2305d3ad 100755 --- a/deploy/openshift-clusters/scripts/deploy-cluster.sh +++ b/deploy/openshift-clusters/scripts/deploy-cluster.sh @@ -73,6 +73,7 @@ fi # Check if instance data exists if [[ ! -f "$(get_node_dir)/aws-instance-id" ]]; then echo "Error: No instance found. Please run 'make deploy' first." + echo "Run 'make doctor' to check your setup." exit 1 fi @@ -81,6 +82,7 @@ if [[ ! -f "${DEPLOY_DIR}/openshift-clusters/inventory.ini" ]]; then echo "Error: inventory.ini not found in ${DEPLOY_DIR}/openshift-clusters/" echo "Please ensure the inventory file is properly configured." echo "You can run 'make inventory' to update it with current instance information." + echo "Run 'make doctor' to check your setup." exit 1 fi diff --git a/deploy/openshift-clusters/scripts/doctor.sh b/deploy/openshift-clusters/scripts/doctor.sh new file mode 100755 index 00000000..eff8b191 --- /dev/null +++ b/deploy/openshift-clusters/scripts/doctor.sh @@ -0,0 +1,388 @@ +#!/usr/bin/bash +# +# Configuration preflight for the two-node-toolbox deployment flow. +# Read-only: validates required tools, SSH keys, Ansible collections, and all +# configuration files, printing a concrete fix command for every failure. +# Changes no state and is safe to run at any time. +# +# Usage: doctor.sh [cluster-type...] +# With no arguments, runs common checks plus every auto-detected section. +# With cluster types (e.g. fencing-kcli), the named method's section becomes +# mandatory and its method-specific warnings are promoted to failures. +# +# Exits non-zero if and only if any check FAILed. + +SCRIPT_DIR=$(dirname "$0") +DEPLOY_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)" + +INSTANCE_ENV="${DEPLOY_DIR}/aws-hypervisor/instance.env" + +# common.sh provides the color constants, msg_* helpers, and env defaults, but +# unconditionally sources instance.env; silence that noise when instance.env +# does not exist yet (its absence is reported as a proper check below). +if [[ -f "${INSTANCE_ENV}" ]]; then + # shellcheck source=/dev/null + source "${DEPLOY_DIR}/aws-hypervisor/scripts/common.sh" +else + # shellcheck source=/dev/null + source "${DEPLOY_DIR}/aws-hypervisor/scripts/common.sh" 2>/dev/null +fi + +# Strict flags only after common.sh: it expands variables that may be unset. +# No 'set -e': a failing check must report and let the remaining checks run. +set -uo pipefail + +readonly COLOR_GREEN='\033[0;32m' + +PASS_COUNT=0 +WARN_COUNT=0 +FAIL_COUNT=0 + +# check_pass/check_warn/check_fail take the finding plus an optional fix hint +check_pass() { + printf '%b[PASS]%b %s\n' "${COLOR_GREEN}" "${COLOR_CLEAR:-}" "$1" + PASS_COUNT=$((PASS_COUNT + 1)) +} + +check_warn() { + printf '%b[WARN]%b %s\n' "${COLOR_YELLOW:-}" "${COLOR_CLEAR:-}" "$1" + if [[ $# -gt 1 ]]; then + printf ' fix: %s\n' "$2" + fi + WARN_COUNT=$((WARN_COUNT + 1)) +} + +check_fail() { + printf '%b[FAIL]%b %s\n' "${COLOR_RED:-}" "${COLOR_CLEAR:-}" "$1" + if [[ $# -gt 1 ]]; then + printf ' fix: %s\n' "$2" + fi + FAIL_COUNT=$((FAIL_COUNT + 1)) +} + +check_note() { + printf '%b[NOTE]%b %s\n' "${COLOR_BLUE:-}" "${COLOR_CLEAR:-}" "$1" +} + +section() { + printf '\n--- %s ---\n' "$1" +} + +usage() { + echo "Usage: $0 [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 "Valid cluster types: fencing-ipi fencing-agent fencing-assisted" + echo " fencing-kcli arbiter-ipi arbiter-agent arbiter-kcli" + echo " sno-ipi sno-agent" +} + +# --- Argument parsing (targeted mode) --- + +TARGETED=0 +NEED_DEVSCRIPTS=0 +NEED_KCLI=0 +REQUIRED_TOPOLOGIES=" " + +for arg in "$@"; do + case "${arg}" in + -h|--help) + usage + exit 0 + ;; + fencing-assisted) + # assisted deploys the hub via fencing-ipi, so it needs the dev-scripts config + TARGETED=1 + NEED_DEVSCRIPTS=1 + REQUIRED_TOPOLOGIES+="fencing " + ;; + arbiter-ipi|arbiter-agent|fencing-ipi|fencing-agent|sno-ipi|sno-agent) + TARGETED=1 + NEED_DEVSCRIPTS=1 + REQUIRED_TOPOLOGIES+="${arg%-*} " + ;; + arbiter-kcli|fencing-kcli) + TARGETED=1 + NEED_KCLI=1 + ;; + *) + echo "Error: Unknown cluster type: '${arg}'" + usage + exit 2 + ;; + esac +done + +topology_required() { + [[ "${REQUIRED_TOPOLOGIES}" == *" $1 "* ]] +} + +tool_hint() { + case "$1" in + aws) echo "install the AWS CLI: https://docs.aws.amazon.com/cli/" ;; + ansible-playbook) echo "install Ansible (e.g. 'dnf install ansible-core' or 'pip install ansible')" ;; + go) echo "install Go: https://go.dev/dl/ (package 'golang' on Fedora/RHEL)" ;; + *) echo "install '$1' with your package manager" ;; + esac +} + +echo "two-node-toolbox configuration preflight (read-only)" +if [[ "${TARGETED}" -eq 1 ]]; then + check_note "validating strictly for: $*" +fi + +# --- Required tools --- + +section "Required tools" + +MISSING_TOOLS="" +for tool in make aws jq rsync ansible-playbook go; do + if ! command -v "${tool}" >/dev/null 2>&1; then + check_fail "required tool '${tool}' not found on PATH" "$(tool_hint "${tool}")" + MISSING_TOOLS+="${tool} " + fi +done +if [[ -z "${MISSING_TOOLS}" ]]; then + check_pass "required tools present (make aws jq rsync ansible-playbook go)" +fi + +if command -v timeout >/dev/null 2>&1; then + check_pass "'timeout' present" +else + check_warn "'timeout' not found on PATH" "on macOS install coreutils: 'brew install coreutils'" +fi + +# --- SSH key --- + +section "SSH key" + +if [[ -n "${SSH_PUBLIC_KEY:-}" ]]; then + if [[ -f "${SSH_PUBLIC_KEY}" ]]; then + check_pass "SSH public key found (SSH_PUBLIC_KEY=${SSH_PUBLIC_KEY})" + else + check_fail "SSH_PUBLIC_KEY in instance.env points to a missing file: ${SSH_PUBLIC_KEY}" \ + "fix the path in ${INSTANCE_ENV}, or generate a key: 'ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519'" + fi +elif [[ -f "${HOME}/.ssh/id_ed25519.pub" ]]; then + check_pass "SSH public key found (~/.ssh/id_ed25519.pub)" +else + FOUND_KEY="" + for key in "${HOME}"/.ssh/id_*.pub; do + if [[ -f "${key}" ]]; then + FOUND_KEY="${key}" + break + fi + done + if [[ -n "${FOUND_KEY}" ]]; then + check_pass "SSH public key found (${FOUND_KEY})" + else + check_warn "no SSH public key found in ~/.ssh" \ + "generate one: 'ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519'" + fi +fi + +# --- Ansible collections --- + +section "Ansible collections" + +REQUIREMENTS_FILE="${DEPLOY_DIR}/openshift-clusters/collections/requirements.yml" +if ! command -v ansible-galaxy >/dev/null 2>&1; then + check_warn "collection check skipped: 'ansible-galaxy' not found on PATH" "$(tool_hint ansible-playbook)" +else + INSTALLED_COLLECTIONS="$(ansible-galaxy collection list 2>/dev/null)" + MISSING_COLLECTIONS="" + while read -r name; do + [[ -z "${name}" ]] && continue + if ! grep -Eq "^${name//./\\.}[[:space:]]" <<<"${INSTALLED_COLLECTIONS}"; then + MISSING_COLLECTIONS+="${name} " + fi + done < <(awk '/^[[:space:]]*- name:/ {print $3}' "${REQUIREMENTS_FILE}") + if [[ -z "${MISSING_COLLECTIONS}" ]]; then + check_pass "all Ansible collections from collections/requirements.yml installed" + else + check_fail "missing Ansible collections: ${MISSING_COLLECTIONS% }" \ + "ansible-galaxy collection install -r ${REQUIREMENTS_FILE}" + fi +fi + +# --- Inventory --- + +section "Inventory" + +INVENTORY="${DEPLOY_DIR}/openshift-clusters/inventory.ini" +INVENTORY_SAMPLE="${INVENTORY}.sample" +if [[ ! -f "${INVENTORY}" ]]; then + # Not required until an OpenShift deployment: 'make deploy ' generates + # it in the AWS flow, so absence is informational even in targeted mode. + check_note "inventory.ini not created yet - run 'make inventory' after creating an AWS instance ('make deploy ' generates it automatically), or copy inventory.ini.sample and edit it for an external host" +elif cmp -s "${INVENTORY}" "${INVENTORY_SAMPLE}"; then + check_fail "inventory.ini is an unedited copy of inventory.ini.sample" \ + "edit ${INVENTORY} with your host IP and SSH user, or run 'make inventory' to fill it from the current AWS instance" +elif ! grep -q '^\[metal_machine\]' "${INVENTORY}"; then + check_fail "inventory.ini has no [metal_machine] section" \ + "restore the [metal_machine] group (see inventory.ini.sample), or run 'make inventory'" +else + check_pass "inventory.ini present and edited" +fi + +# --- AWS hypervisor --- + +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 ${INSTANCE_ENV}.template ${INSTANCE_ENV}' and edit" +else + # set +u: the deployment scripts never source instance.env under nounset, + # so the probe must not be stricter than real usage + # shellcheck source=/dev/null + if SOURCE_ERR=$( (set -e; set +u; source "${INSTANCE_ENV}") 2>&1 >/dev/null ); then + check_pass "instance.env sources cleanly" + else + check_fail "instance.env does not source cleanly: ${SOURCE_ERR}" \ + "fix the shell syntax in ${INSTANCE_ENV}" + fi + + if [[ -n "${REGION:-}" && -n "${AWS_PROFILE:-}" ]]; then + check_pass "REGION (${REGION}) and AWS_PROFILE (${AWS_PROFILE}) are set" + else + check_fail "REGION and/or AWS_PROFILE not set by instance.env" \ + "set REGION and AWS_PROFILE in ${INSTANCE_ENV} (see instance.env.template)" + fi + + if ! command -v aws >/dev/null 2>&1 || ! command -v timeout >/dev/null 2>&1; then + check_warn "AWS credential check skipped ('aws' and 'timeout' must be on PATH)" + else + AWS_ERR="$(timeout 15 aws sts get-caller-identity --output json --no-cli-pager 2>&1 >/dev/null)" + AWS_RC=$? + if [[ ${AWS_RC} -eq 0 ]]; then + check_pass "AWS credentials are live (sts get-caller-identity succeeded)" + elif [[ ${AWS_RC} -eq 124 ]]; then + check_warn "AWS credential check timed out after 15s" "check network/VPN connectivity and retry" + else + check_fail "AWS credentials not working: $(head -1 <<<"${AWS_ERR}")" \ + "log in again (e.g. 'aws sso login --profile ${AWS_PROFILE:-}') or check 'aws configure list'" + fi + fi +fi + +# --- dev-scripts configuration --- + +section "dev-scripts configuration" + +DS_FILES_DIR="${DEPLOY_DIR}/openshift-clusters/roles/dev-scripts/install-dev/files" +DS_PULL_SECRET="${DS_FILES_DIR}/pull-secret.json" + +DS_PULL_SECRET_OK=0 +if [[ -f "${DS_PULL_SECRET}" ]]; then + if ! command -v jq >/dev/null 2>&1; then + check_warn "pull-secret.json JSON check skipped: 'jq' not found on PATH" + elif jq . "${DS_PULL_SECRET}" >/dev/null 2>&1; then + check_pass "pull-secret.json present and valid JSON (dev-scripts)" + DS_PULL_SECRET_OK=1 + else + check_fail "pull-secret.json is not valid JSON (dev-scripts)" \ + "re-download it from https://cloud.redhat.com/openshift/install/pull-secret and save it to ${DS_PULL_SECRET}" + fi +elif [[ "${NEED_DEVSCRIPTS}" -eq 1 ]]; then + check_fail "pull-secret.json missing (required for the requested dev-scripts deployment)" \ + "download it from https://cloud.redhat.com/openshift/install/pull-secret and save it to ${DS_PULL_SECRET}" +else + check_warn "pull-secret.json missing (needed for dev-scripts deployments)" \ + "download it from https://cloud.redhat.com/openshift/install/pull-secret and save it to ${DS_PULL_SECRET}" +fi + +for topology in arbiter fencing sno; do + CONFIG_FILE="${DS_FILES_DIR}/config_${topology}.sh" + if [[ ! -f "${CONFIG_FILE}" ]]; then + if topology_required "${topology}"; then + check_fail "config_${topology}.sh missing (required for the requested deployment)" \ + "cp ${DS_FILES_DIR}/config_${topology}_example.sh ${CONFIG_FILE} and set OPENSHIFT_RELEASE_IMAGE / CI_TOKEN" + else + check_warn "config_${topology}.sh missing (only needed to deploy the ${topology} topology with dev-scripts)" \ + "cp ${DS_FILES_DIR}/config_${topology}_example.sh ${CONFIG_FILE} and set OPENSHIFT_RELEASE_IMAGE / CI_TOKEN" + fi + continue + fi + check_pass "config_${topology}.sh present" + + # CI registry cross-check: a config that pulls from the CI registry needs a + # matching auth entry in the pull secret (same rule as install-dev/tasks/config.yml) + if grep -q 'registry.ci.openshift.org' "${CONFIG_FILE}" && [[ ${DS_PULL_SECRET_OK} -eq 1 ]]; then + if jq -e '.auths | has("registry.ci.openshift.org")' "${DS_PULL_SECRET}" >/dev/null 2>&1; then + check_pass "pull secret has registry.ci.openshift.org auth (used by config_${topology}.sh)" + else + check_fail "config_${topology}.sh uses registry.ci.openshift.org but the pull secret has no auth entry for it" \ + "add CI registry credentials to ${DS_PULL_SECRET}, or switch to a public release image (quay.io/openshift-release-dev/ocp-release)" + fi + fi + + # CI token live check: only when CI_TOKEN is set and the release image is on + # the CI registry (same condition as install-dev/tasks/config.yml) + CI_TOKEN="$(sed -nE 's/^export CI_TOKEN="([^"]+)".*/\1/p' "${CONFIG_FILE}" | head -1)" + RELEASE_REGISTRY="$(sed -nE 's|^export OPENSHIFT_RELEASE_IMAGE="?([^/"]+).*|\1|p' "${CONFIG_FILE}" | head -1)" + if [[ -n "${CI_TOKEN}" && "${RELEASE_REGISTRY}" == "registry.ci.openshift.org" ]]; then + if ! command -v curl >/dev/null 2>&1; then + check_warn "CI token check skipped: 'curl' not found on PATH" + else + HTTP_CODE="$(curl -s -o /dev/null -w '%{http_code}' --max-time 10 \ + -H "Authorization: Bearer ${CI_TOKEN}" "https://${RELEASE_REGISTRY}/v2/")" + CURL_RC=$? + if [[ ${CURL_RC} -ne 0 ]]; then + check_warn "CI token check: could not reach ${RELEASE_REGISTRY} (network failure or timeout)" + elif [[ "${HTTP_CODE}" == "401" ]]; then + check_fail "CI token in config_${topology}.sh is invalid or expired (HTTP 401 from ${RELEASE_REGISTRY})" \ + "update CI_TOKEN in ${CONFIG_FILE} (copy a fresh token from the CI cluster console)" + elif [[ "${HTTP_CODE}" == "200" ]]; then + check_pass "CI token in config_${topology}.sh accepted by ${RELEASE_REGISTRY}" + else + check_warn "CI token check: unexpected HTTP ${HTTP_CODE} from ${RELEASE_REGISTRY}" + fi + fi + fi +done + +# --- kcli configuration --- + +section "kcli configuration" + +KCLI_PULL_SECRET="${DEPLOY_DIR}/openshift-clusters/roles/kcli/kcli-install/files/pull-secret.json" +KCLI_VARS="${DEPLOY_DIR}/openshift-clusters/vars/kcli.yml" + +if [[ "${NEED_KCLI}" -eq 0 && ! -f "${KCLI_PULL_SECRET}" && ! -f "${KCLI_VARS}" ]]; then + check_note "section skipped: no kcli configuration detected (vars/kcli.yml or kcli pull secret) - only needed for *-kcli deployments" +else + if [[ -f "${KCLI_PULL_SECRET}" ]]; then + if ! command -v jq >/dev/null 2>&1; then + check_warn "kcli pull-secret.json JSON check skipped: 'jq' not found on PATH" + elif jq . "${KCLI_PULL_SECRET}" >/dev/null 2>&1; then + check_pass "pull-secret.json present and valid JSON (kcli)" + else + check_fail "pull-secret.json is not valid JSON (kcli)" \ + "re-download it from https://cloud.redhat.com/openshift/install/pull-secret and save it to ${KCLI_PULL_SECRET}" + fi + if [[ -f "${DS_PULL_SECRET}" ]] && ! cmp -s "${KCLI_PULL_SECRET}" "${DS_PULL_SECRET}"; then + # per the /setup docs, both locations are meant to hold the same secret + check_warn "kcli pull-secret.json differs from the dev-scripts copy" \ + "cp ${DS_PULL_SECRET} ${KCLI_PULL_SECRET} (or the other way around, whichever is current)" + fi + elif [[ "${NEED_KCLI}" -eq 1 ]]; then + check_fail "pull-secret.json missing (required for the requested kcli deployment)" \ + "download it from https://cloud.redhat.com/openshift/install/pull-secret and save it to ${KCLI_PULL_SECRET}" + else + check_warn "pull-secret.json missing (needed for *-kcli deployments)" \ + "download it from https://cloud.redhat.com/openshift/install/pull-secret and save it to ${KCLI_PULL_SECRET}" + fi +fi + +# --- Summary --- + +echo "" +echo "Summary: ${PASS_COUNT} passed, ${WARN_COUNT} warnings, ${FAIL_COUNT} failures" +if [[ ${FAIL_COUNT} -gt 0 ]]; then + msg_err "configuration is not ready - address the FAIL items above" + exit 1 +fi +exit 0 From 9c51fa451cfc0b62c1779d7e57a2e265df6a615c Mon Sep 17 00:00:00 2001 From: Pablo Fontanilla Date: Thu, 9 Jul 2026 14:30:40 +0200 Subject: [PATCH 12/15] fix: correct common.sh path and use config/ folder in doctor hints After the config-folder PR moved common.sh from deploy/aws-hypervisor/scripts/ to deploy/, doctor.sh referenced the old path and would fail on startup. Fix the source path and replace msg_err with an inline printf so the summary works even when common.sh fails to load. All fix hints now point to the central config/ folder instead of the deep canonical paths, consistent with the config-folder workflow. Co-Authored-By: Claude Opus 4.6 --- deploy/openshift-clusters/scripts/doctor.sh | 39 +++++++++++---------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/deploy/openshift-clusters/scripts/doctor.sh b/deploy/openshift-clusters/scripts/doctor.sh index eff8b191..91761064 100755 --- a/deploy/openshift-clusters/scripts/doctor.sh +++ b/deploy/openshift-clusters/scripts/doctor.sh @@ -15,6 +15,8 @@ SCRIPT_DIR=$(dirname "$0") DEPLOY_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)" +REPO_ROOT="$(cd "${DEPLOY_DIR}/.." && pwd)" +CONFIG_DIR="${REPO_ROOT}/config" INSTANCE_ENV="${DEPLOY_DIR}/aws-hypervisor/instance.env" # common.sh provides the color constants, msg_* helpers, and env defaults, but @@ -22,10 +24,10 @@ INSTANCE_ENV="${DEPLOY_DIR}/aws-hypervisor/instance.env" # does not exist yet (its absence is reported as a proper check below). if [[ -f "${INSTANCE_ENV}" ]]; then # shellcheck source=/dev/null - source "${DEPLOY_DIR}/aws-hypervisor/scripts/common.sh" + source "${DEPLOY_DIR}/common.sh" else # shellcheck source=/dev/null - source "${DEPLOY_DIR}/aws-hypervisor/scripts/common.sh" 2>/dev/null + source "${DEPLOY_DIR}/common.sh" 2>/dev/null fi # Strict flags only after common.sh: it expands variables that may be unset. @@ -164,7 +166,7 @@ if [[ -n "${SSH_PUBLIC_KEY:-}" ]]; then check_pass "SSH public key found (SSH_PUBLIC_KEY=${SSH_PUBLIC_KEY})" else check_fail "SSH_PUBLIC_KEY in instance.env points to a missing file: ${SSH_PUBLIC_KEY}" \ - "fix the path in ${INSTANCE_ENV}, or generate a key: 'ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519'" + "fix the path in ${CONFIG_DIR}/instance.env, or generate a key: 'ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519'" fi elif [[ -f "${HOME}/.ssh/id_ed25519.pub" ]]; then check_pass "SSH public key found (~/.ssh/id_ed25519.pub)" @@ -233,7 +235,7 @@ 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 ${INSTANCE_ENV}.template ${INSTANCE_ENV}' and edit" + 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" else # set +u: the deployment scripts never source instance.env under nounset, # so the probe must not be stricter than real usage @@ -242,14 +244,14 @@ else check_pass "instance.env sources cleanly" else check_fail "instance.env does not source cleanly: ${SOURCE_ERR}" \ - "fix the shell syntax in ${INSTANCE_ENV}" + "fix the shell syntax in ${CONFIG_DIR}/instance.env" fi if [[ -n "${REGION:-}" && -n "${AWS_PROFILE:-}" ]]; then check_pass "REGION (${REGION}) and AWS_PROFILE (${AWS_PROFILE}) are set" else check_fail "REGION and/or AWS_PROFILE not set by instance.env" \ - "set REGION and AWS_PROFILE in ${INSTANCE_ENV} (see instance.env.template)" + "set REGION and AWS_PROFILE in ${CONFIG_DIR}/instance.env (see ${CONFIG_DIR}/instance.env.template)" fi if ! command -v aws >/dev/null 2>&1 || ! command -v timeout >/dev/null 2>&1; then @@ -284,14 +286,14 @@ if [[ -f "${DS_PULL_SECRET}" ]]; then DS_PULL_SECRET_OK=1 else check_fail "pull-secret.json is not valid JSON (dev-scripts)" \ - "re-download it from https://cloud.redhat.com/openshift/install/pull-secret and save it to ${DS_PULL_SECRET}" + "re-download it from https://cloud.redhat.com/openshift/install/pull-secret and save it to ${CONFIG_DIR}/pull-secret.json" fi elif [[ "${NEED_DEVSCRIPTS}" -eq 1 ]]; then check_fail "pull-secret.json missing (required for the requested dev-scripts deployment)" \ - "download it from https://cloud.redhat.com/openshift/install/pull-secret and save it to ${DS_PULL_SECRET}" + "download it from https://cloud.redhat.com/openshift/install/pull-secret and save it to ${CONFIG_DIR}/pull-secret.json" else check_warn "pull-secret.json missing (needed for dev-scripts deployments)" \ - "download it from https://cloud.redhat.com/openshift/install/pull-secret and save it to ${DS_PULL_SECRET}" + "download it from https://cloud.redhat.com/openshift/install/pull-secret and save it to ${CONFIG_DIR}/pull-secret.json" fi for topology in arbiter fencing sno; do @@ -299,10 +301,10 @@ for topology in arbiter fencing sno; do if [[ ! -f "${CONFIG_FILE}" ]]; then if topology_required "${topology}"; then check_fail "config_${topology}.sh missing (required for the requested deployment)" \ - "cp ${DS_FILES_DIR}/config_${topology}_example.sh ${CONFIG_FILE} and set OPENSHIFT_RELEASE_IMAGE / CI_TOKEN" + "cp ${CONFIG_DIR}/config_${topology}_example.sh ${CONFIG_DIR}/config_${topology}.sh and set OPENSHIFT_RELEASE_IMAGE / CI_TOKEN" else check_warn "config_${topology}.sh missing (only needed to deploy the ${topology} topology with dev-scripts)" \ - "cp ${DS_FILES_DIR}/config_${topology}_example.sh ${CONFIG_FILE} and set OPENSHIFT_RELEASE_IMAGE / CI_TOKEN" + "cp ${CONFIG_DIR}/config_${topology}_example.sh ${CONFIG_DIR}/config_${topology}.sh and set OPENSHIFT_RELEASE_IMAGE / CI_TOKEN" fi continue fi @@ -315,7 +317,7 @@ for topology in arbiter fencing sno; do check_pass "pull secret has registry.ci.openshift.org auth (used by config_${topology}.sh)" else check_fail "config_${topology}.sh uses registry.ci.openshift.org but the pull secret has no auth entry for it" \ - "add CI registry credentials to ${DS_PULL_SECRET}, or switch to a public release image (quay.io/openshift-release-dev/ocp-release)" + "add CI registry credentials to ${CONFIG_DIR}/pull-secret.json, or switch to a public release image (quay.io/openshift-release-dev/ocp-release)" fi fi @@ -334,7 +336,7 @@ for topology in arbiter fencing sno; do check_warn "CI token check: could not reach ${RELEASE_REGISTRY} (network failure or timeout)" elif [[ "${HTTP_CODE}" == "401" ]]; then check_fail "CI token in config_${topology}.sh is invalid or expired (HTTP 401 from ${RELEASE_REGISTRY})" \ - "update CI_TOKEN in ${CONFIG_FILE} (copy a fresh token from the CI cluster console)" + "update CI_TOKEN in ${CONFIG_DIR}/config_${topology}.sh (copy a fresh token from the CI cluster console)" elif [[ "${HTTP_CODE}" == "200" ]]; then check_pass "CI token in config_${topology}.sh accepted by ${RELEASE_REGISTRY}" else @@ -361,19 +363,18 @@ else check_pass "pull-secret.json present and valid JSON (kcli)" else check_fail "pull-secret.json is not valid JSON (kcli)" \ - "re-download it from https://cloud.redhat.com/openshift/install/pull-secret and save it to ${KCLI_PULL_SECRET}" + "re-download it from https://cloud.redhat.com/openshift/install/pull-secret and save it to ${CONFIG_DIR}/pull-secret.json" fi if [[ -f "${DS_PULL_SECRET}" ]] && ! cmp -s "${KCLI_PULL_SECRET}" "${DS_PULL_SECRET}"; then - # per the /setup docs, both locations are meant to hold the same secret check_warn "kcli pull-secret.json differs from the dev-scripts copy" \ - "cp ${DS_PULL_SECRET} ${KCLI_PULL_SECRET} (or the other way around, whichever is current)" + "save the correct version to ${CONFIG_DIR}/pull-secret.json and run 'make sync-config' to update both locations" fi elif [[ "${NEED_KCLI}" -eq 1 ]]; then check_fail "pull-secret.json missing (required for the requested kcli deployment)" \ - "download it from https://cloud.redhat.com/openshift/install/pull-secret and save it to ${KCLI_PULL_SECRET}" + "download it from https://cloud.redhat.com/openshift/install/pull-secret and save it to ${CONFIG_DIR}/pull-secret.json" else check_warn "pull-secret.json missing (needed for *-kcli deployments)" \ - "download it from https://cloud.redhat.com/openshift/install/pull-secret and save it to ${KCLI_PULL_SECRET}" + "download it from https://cloud.redhat.com/openshift/install/pull-secret and save it to ${CONFIG_DIR}/pull-secret.json" fi fi @@ -382,7 +383,7 @@ fi echo "" echo "Summary: ${PASS_COUNT} passed, ${WARN_COUNT} warnings, ${FAIL_COUNT} failures" if [[ ${FAIL_COUNT} -gt 0 ]]; then - msg_err "configuration is not ready - address the FAIL items above" + printf '%b%s%b\n' "${COLOR_RED:-}" "ERROR: configuration is not ready - address the FAIL items above" "${COLOR_CLEAR:-}" >&2 exit 1 fi exit 0 From 723eccd8e8b4e22a2d002e02d7e055f0449385b1 Mon Sep 17 00:00:00 2001 From: Pablo Fontanilla Date: Thu, 9 Jul 2026 16:05:39 +0200 Subject: [PATCH 13/15] fix: make doctor read-only and add assisted.yml check Doctor sources common.sh which unconditionally runs sync_config_files, writing to the working tree. Add SKIP_CONFIG_SYNC guard so doctor honours its read-only contract. Also add a missing assisted installer config section so `make doctor fencing-assisted` validates vars/assisted.yml. Co-Authored-By: Claude Opus 4.6 --- deploy/openshift-clusters/scripts/doctor.sh | 27 +++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/deploy/openshift-clusters/scripts/doctor.sh b/deploy/openshift-clusters/scripts/doctor.sh index 91761064..984bf670 100755 --- a/deploy/openshift-clusters/scripts/doctor.sh +++ b/deploy/openshift-clusters/scripts/doctor.sh @@ -22,6 +22,8 @@ INSTANCE_ENV="${DEPLOY_DIR}/aws-hypervisor/instance.env" # common.sh provides the color constants, msg_* helpers, and env defaults, but # unconditionally sources instance.env; silence that noise when instance.env # does not exist yet (its absence is reported as a proper check below). +# SKIP_CONFIG_SYNC: doctor is read-only — suppress the automatic config sync. +export SKIP_CONFIG_SYNC=1 if [[ -f "${INSTANCE_ENV}" ]]; then # shellcheck source=/dev/null source "${DEPLOY_DIR}/common.sh" @@ -87,6 +89,7 @@ usage() { TARGETED=0 NEED_DEVSCRIPTS=0 NEED_KCLI=0 +NEED_ASSISTED=0 REQUIRED_TOPOLOGIES=" " for arg in "$@"; do @@ -99,6 +102,7 @@ for arg in "$@"; do # 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 " ;; arbiter-ipi|arbiter-agent|fencing-ipi|fencing-agent|sno-ipi|sno-agent) @@ -378,6 +382,29 @@ else fi fi +# --- assisted installer configuration --- + +section "assisted installer configuration" + +ASSISTED_VARS="${DEPLOY_DIR}/openshift-clusters/vars/assisted.yml" + +if [[ "${NEED_ASSISTED}" -eq 0 && ! -f "${ASSISTED_VARS}" ]]; then + check_note "section skipped: no assisted config detected (vars/assisted.yml) - only needed for fencing-assisted deployments" +else + if [[ -f "${ASSISTED_VARS}" ]]; then + check_pass "vars/assisted.yml present" + SPOKE_NAME="$(sed -nE 's/^spoke_cluster_name:[[:space:]]*"?([^"]+)"?.*/\1/p' "${ASSISTED_VARS}" | head -1)" + if [[ -n "${SPOKE_NAME}" ]]; then + check_pass "spoke_cluster_name set to '${SPOKE_NAME}'" + else + check_warn "spoke_cluster_name not set in vars/assisted.yml (defaults to 'spoke-tnf')" + fi + elif [[ "${NEED_ASSISTED}" -eq 1 ]]; then + check_fail "vars/assisted.yml missing (required for fencing-assisted deployment)" \ + "cp ${CONFIG_DIR}/assisted.yml.template ${CONFIG_DIR}/assisted.yml and edit it" + fi +fi + # --- Summary --- echo "" From 8cb554e003ec1b0a9ba8d974e5f9988f76d01a61 Mon Sep 17 00:00:00 2001 From: Pablo Fontanilla Date: Thu, 9 Jul 2026 17:34:55 +0200 Subject: [PATCH 14/15] fix: address review findings from PR #93 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove blanket 2>/dev/null when sourcing common.sh; use SKIP_INSTANCE_ENV_WARNING flag for targeted suppression - ansible-galaxy: capture stderr so config errors surface instead of cascading false "missing collection" failures - timeout exit codes ≥125: report as "could not run" not "credentials not working" - curl: include exit code in CI token check warning Co-Authored-By: Claude Opus 4.6 --- deploy/openshift-clusters/scripts/doctor.sh | 24 ++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/deploy/openshift-clusters/scripts/doctor.sh b/deploy/openshift-clusters/scripts/doctor.sh index 984bf670..28805629 100755 --- a/deploy/openshift-clusters/scripts/doctor.sh +++ b/deploy/openshift-clusters/scripts/doctor.sh @@ -19,18 +19,13 @@ REPO_ROOT="$(cd "${DEPLOY_DIR}/.." && pwd)" CONFIG_DIR="${REPO_ROOT}/config" INSTANCE_ENV="${DEPLOY_DIR}/aws-hypervisor/instance.env" -# common.sh provides the color constants, msg_* helpers, and env defaults, but -# unconditionally sources instance.env; silence that noise when instance.env -# does not exist yet (its absence is reported as a proper check below). # SKIP_CONFIG_SYNC: doctor is read-only — suppress the automatic config sync. +# SKIP_INSTANCE_ENV_WARNING: doctor has its own instance.env section with +# better guidance, so suppress the generic warning from common.sh. export SKIP_CONFIG_SYNC=1 -if [[ -f "${INSTANCE_ENV}" ]]; then - # shellcheck source=/dev/null - source "${DEPLOY_DIR}/common.sh" -else - # shellcheck source=/dev/null - source "${DEPLOY_DIR}/common.sh" 2>/dev/null -fi +export SKIP_INSTANCE_ENV_WARNING=1 +# shellcheck source=/dev/null +source "${DEPLOY_DIR}/common.sh" # Strict flags only after common.sh: it expands variables that may be unset. # No 'set -e': a failing check must report and let the remaining checks run. @@ -198,7 +193,10 @@ REQUIREMENTS_FILE="${DEPLOY_DIR}/openshift-clusters/collections/requirements.yml if ! command -v ansible-galaxy >/dev/null 2>&1; then check_warn "collection check skipped: 'ansible-galaxy' not found on PATH" "$(tool_hint ansible-playbook)" else - INSTALLED_COLLECTIONS="$(ansible-galaxy collection list 2>/dev/null)" + if ! INSTALLED_COLLECTIONS="$(ansible-galaxy collection list 2>&1)"; then + check_warn "ansible-galaxy collection list failed; collection check unreliable" \ + "run 'ansible-galaxy collection list' manually to diagnose" + fi MISSING_COLLECTIONS="" while read -r name; do [[ -z "${name}" ]] && continue @@ -267,6 +265,8 @@ else check_pass "AWS credentials are live (sts get-caller-identity succeeded)" elif [[ ${AWS_RC} -eq 124 ]]; then check_warn "AWS credential check timed out after 15s" "check network/VPN connectivity and retry" + elif [[ ${AWS_RC} -ge 125 ]]; then + check_warn "AWS credential check could not run (exit ${AWS_RC})" else check_fail "AWS credentials not working: $(head -1 <<<"${AWS_ERR}")" \ "log in again (e.g. 'aws sso login --profile ${AWS_PROFILE:-}') or check 'aws configure list'" @@ -337,7 +337,7 @@ for topology in arbiter fencing sno; do -H "Authorization: Bearer ${CI_TOKEN}" "https://${RELEASE_REGISTRY}/v2/")" CURL_RC=$? if [[ ${CURL_RC} -ne 0 ]]; then - check_warn "CI token check: could not reach ${RELEASE_REGISTRY} (network failure or timeout)" + check_warn "CI token check: curl failed (exit ${CURL_RC}) reaching ${RELEASE_REGISTRY}" elif [[ "${HTTP_CODE}" == "401" ]]; then check_fail "CI token in config_${topology}.sh is invalid or expired (HTTP 401 from ${RELEASE_REGISTRY})" \ "update CI_TOKEN in ${CONFIG_DIR}/config_${topology}.sh (copy a fresh token from the CI cluster console)" From ceacc18249dd8eeae2cd56470806d0ab990d40f2 Mon Sep 17 00:00:00 2001 From: Pablo Fontanilla Date: Thu, 9 Jul 2026 18:15:05 +0200 Subject: [PATCH 15/15] fix: remove obsolete SKIP_CONFIG_SYNC from doctor.sh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit config-folder's sync refactor moved the sync call from common.sh module scope to a Makefile prerequisite. SKIP_CONFIG_SYNC is now a no-op — remove it. Cluster-type sync prerequisites are scoped to the else branch so 'make doctor ' doesn't trigger them. Co-Authored-By: Claude Opus 4.6 --- deploy/Makefile | 4 ++-- deploy/openshift-clusters/scripts/doctor.sh | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/deploy/Makefile b/deploy/Makefile index 0a3f4970..bb496ecd 100644 --- a/deploy/Makefile +++ b/deploy/Makefile @@ -77,7 +77,6 @@ inventory: # Sync config/ files to canonical locations before targets that consume them. create init redeploy-cluster \ -fencing-ipi fencing-agent arbiter-ipi arbiter-agent arbiter-kcli fencing-kcli sno-ipi sno-agent fencing-assisted \ baremetal-adopt baremetal-fencing-agent: sync-config sync-config: @@ -91,6 +90,7 @@ ifeq (doctor,$(firstword $(MAKECMDGOALS))) $(VALID_CLUSTER_TYPES): @: else +$(VALID_CLUSTER_TYPES): sync-config fencing-ipi: @./openshift-clusters/scripts/deploy-cluster.sh --topology fencing --method ipi @@ -163,7 +163,7 @@ help: @echo " keep-instance - Tag instance with keep-N retention (default: 2, max: 5)" @echo " Usage: make keep-instance or make keep-instance DAYS=5" @echo " sync-config - Sync files from config/ to their canonical locations" - @echo " (runs automatically before deploy and lifecycle targets)" + @echo " (runs automatically before deploy and cluster targets)" @echo "" @echo "OpenShift Cluster Deployment:" @echo " fencing-ipi - Deploy fencing IPI cluster (non-interactive)" diff --git a/deploy/openshift-clusters/scripts/doctor.sh b/deploy/openshift-clusters/scripts/doctor.sh index 28805629..3e85657e 100755 --- a/deploy/openshift-clusters/scripts/doctor.sh +++ b/deploy/openshift-clusters/scripts/doctor.sh @@ -19,10 +19,8 @@ REPO_ROOT="$(cd "${DEPLOY_DIR}/.." && pwd)" CONFIG_DIR="${REPO_ROOT}/config" INSTANCE_ENV="${DEPLOY_DIR}/aws-hypervisor/instance.env" -# SKIP_CONFIG_SYNC: doctor is read-only — suppress the automatic config sync. -# SKIP_INSTANCE_ENV_WARNING: doctor has its own instance.env section with -# better guidance, so suppress the generic warning from common.sh. -export SKIP_CONFIG_SYNC=1 +# Suppress the generic instance.env warning from common.sh — doctor has its +# own instance.env section with better guidance. export SKIP_INSTANCE_ENV_WARNING=1 # shellcheck source=/dev/null source "${DEPLOY_DIR}/common.sh"