diff --git a/.ansible-lint b/.ansible-lint new file mode 100644 index 00000000..3b924769 --- /dev/null +++ b/.ansible-lint @@ -0,0 +1,44 @@ +--- +# 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 + - 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 +# 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/commands/setup.md b/.claude/commands/setup.md index 79f3000a..4ab9a4df 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 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 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` + - Deploy and cluster `make` targets sync 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 253ac5be..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 @@ -72,8 +74,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 @@ -81,12 +83,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 @@ -121,20 +132,32 @@ make shellcheck ### 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`). 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 +- `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`) + +#### 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/config_baremetal_fencing.sh`: Baremetal fencing topology 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/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..7a906511 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 @@ -75,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: @@ -103,6 +109,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/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..985eea71 --- /dev/null +++ b/config/README.md @@ -0,0 +1,39 @@ +# Central configuration folder + +This folder is the single place to create your configuration files for the toolbox. +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. + +## 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` | +| `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` | + +## Syncing + +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/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..bb496ecd 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 @@ -61,6 +75,22 @@ info: inventory: @./aws-hypervisor/scripts/inventory.sh +# Sync config/ files to canonical locations before targets that consume them. +create init redeploy-cluster \ +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 +$(VALID_CLUSTER_TYPES): sync-config fencing-ipi: @./openshift-clusters/scripts/deploy-cluster.sh --topology fencing --method ipi @@ -88,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)' @@ -127,8 +158,12 @@ 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" + @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/README.md b/deploy/README.md index b6676ec2..ba33c410 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -40,7 +40,9 @@ 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. +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/aws-hypervisor/README.md b/deploy/aws-hypervisor/README.md index c06bea93..62e01d13 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. 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 -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/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 73% rename from deploy/aws-hypervisor/scripts/common.sh rename to deploy/common.sh index e9d7ea14..8f5042eb 100755 --- a/deploy/aws-hypervisor/scripts/common.sh +++ b/deploy/common.sh @@ -1,8 +1,75 @@ #!/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" + "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" + "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 + 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}" + 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 + done +} + +if [[ -f "${COMMON_DIR}/aws-hypervisor/instance.env" ]]; then + # shellcheck source=/dev/null + source "${COMMON_DIR}/aws-hypervisor/instance.env" +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 # Set defaults export STACK_NAME="${STACK_NAME:-${USER}-dev}" @@ -18,13 +85,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" @@ -40,23 +107,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/baremetal-adopt.sh b/deploy/openshift-clusters/scripts/baremetal-adopt.sh index fe77e50d..bb26d1da 100755 --- a/deploy/openshift-clusters/scripts/baremetal-adopt.sh +++ b/deploy/openshift-clusters/scripts/baremetal-adopt.sh @@ -499,26 +499,36 @@ 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}" + if [[ -f "${config_dir}/config_baremetal_fencing.sh" ]]; then + 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 + 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..2b0bbad4 100755 --- a/deploy/openshift-clusters/scripts/deploy-baremetal.sh +++ b/deploy/openshift-clusters/scripts/deploy-baremetal.sh @@ -10,13 +10,16 @@ # Usage: # deploy-baremetal.sh [-- ] +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 -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -OC_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" - 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..2305d3ad 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 @@ -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/deploy-fencing-assisted.sh b/deploy/openshift-clusters/scripts/deploy-fencing-assisted.sh index f4dd8fee..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 @@ -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/doctor.sh b/deploy/openshift-clusters/scripts/doctor.sh new file mode 100755 index 00000000..3e85657e --- /dev/null +++ b/deploy/openshift-clusters/scripts/doctor.sh @@ -0,0 +1,414 @@ +#!/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)" + +REPO_ROOT="$(cd "${DEPLOY_DIR}/.." && pwd)" +CONFIG_DIR="${REPO_ROOT}/config" +INSTANCE_ENV="${DEPLOY_DIR}/aws-hypervisor/instance.env" + +# 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" + +# 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 +NEED_ASSISTED=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 + NEED_ASSISTED=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 ${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)" +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 + 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 + 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 ${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 + # 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 ${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 ${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 + 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" + 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'" + 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 ${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 ${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 ${CONFIG_DIR}/pull-secret.json" +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 ${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 ${CONFIG_DIR}/config_${topology}_example.sh ${CONFIG_DIR}/config_${topology}.sh 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 ${CONFIG_DIR}/pull-secret.json, 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: 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)" + 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 ${CONFIG_DIR}/pull-secret.json" + fi + if [[ -f "${DS_PULL_SECRET}" ]] && ! cmp -s "${KCLI_PULL_SECRET}" "${DS_PULL_SECRET}"; then + check_warn "kcli pull-secret.json differs from the dev-scripts copy" \ + "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 ${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 ${CONFIG_DIR}/pull-secret.json" + 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 "" +echo "Summary: ${PASS_COUNT} passed, ${WARN_COUNT} warnings, ${FAIL_COUNT} failures" +if [[ ${FAIL_COUNT} -gt 0 ]]; then + 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 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 e99c1250..d000f9e3 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}/common.sh" + set -o nounset set -o errexit set -o pipefail 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 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..b23c0f08 --- /dev/null +++ b/hack/ansible-lint.sh @@ -0,0 +1,56 @@ +#!/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}" + shopt -s nullglob + for PLAYBOOK in *.yml; do + ansible-playbook --syntax-check "${PLAYBOOK}" + done + shopt -u nullglob + 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:" diff --git a/helpers/keep-instance.sh b/helpers/keep-instance.sh index 967d90fb..f031603b 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/common.sh" REGION="${REGION:-${AWS_DEFAULT_REGION:-}}" if [[ -z "${REGION}" ]]; then