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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
cspell.json
.ansible/*
.claude/*
.vscode/*
.DS_Store
**.DS_Store
Expand Down
3 changes: 2 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,11 @@ Also requires the `jmespath` Python module on the control node.
| `certify_redis.yml` | `itential.deployer.certify_redis` | Generate Redis/Sentinel installation certification reports |
| `certify_mongodb.yml` | `itential.deployer.certify_mongodb` | Generate MongoDB installation certification reports |
| `certify_platform.yml` | `itential.deployer.certify_platform` | Generate Platform installation certification reports |
| `verify.yml` | `itential.deployer.verify` | Pre-install environment verification (OS, HW specs, proxy) for all components |
| `verify.yml` | `itential.deployer.verify` | Pre-install environment verification (OS, HW specs, proxy, required repository connectivity) for all components |
| `verify_redis.yml` | `itential.deployer.verify_redis` | Pre-install verification for Redis hosts |
| `verify_mongodb.yml` | `itential.deployer.verify_mongodb` | Pre-install verification for MongoDB hosts |
| `verify_platform.yml` | `itential.deployer.verify_platform` | Pre-install verification for Platform hosts |
| `verify_gateway.yml` | `itential.deployer.verify_gateway` | Pre-install verification for Gateway hosts |
| `download_packages_site.yml` | `itential.deployer.download_packages_site` | Download all packages for offline install (Platform stack + Gateway) |
| `download_packages_platform_site.yml` | `itential.deployer.download_packages_platform_site` | Download all packages for Platform stack offline install |
| `download_packages_platform.yml` | `itential.deployer.download_packages_platform` | Download Platform packages for offline install |
Expand Down
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,6 @@ the Deployer will either install the required repository or download the package
| Redis | <https://dl.fedoraproject.org> | TCP | EPEL YUM RPMs<br>When installing Redis from the Remi repository |
| Redis | <https://github.com> | TCP | Redis source packages <br>When installing Redis from source |
| Redis | <https://codeload.github.com> | TCP | Redis source packages<br>When installing Redis from source |
| Vault | <https://rpm.releases.hashicorp.com> | TCP | Vault YUM RPMs |

If internal YUM repositories are used, refer to the
[Using Internal YUM Repositories](#using-internal-yum-repositories) section.
Expand All @@ -248,6 +247,17 @@ If internal YUM repositories are used, refer to the
> proxy or other such method to restrict access, this list may not represent the final URLs that are
> required.

To verify connectivity to these repositories from the target hosts (excluding the Ansible
Control Node rows, which are the control node's own responsibility), run the `verify` playbook:

```bash
ansible-playbook itential.deployer.verify -i <inventory>
```

This checks each component's target hosts (`gateway`, `platform*`, `mongodb*`, `redis_master`/
`redis_replica`) against the rows in the table above. See
[Confirm Requirements](#confirm-requirements) for details.

### Ports and Networking

In a clustered environment where components are installed on more than one host, the following
Expand Down Expand Up @@ -390,7 +400,7 @@ applicable, IAG). For more information, refer to the [Itential Dependencies] pag
nodes.
- **SSH Access**: The control node must have SSH connectivity to all managed nodes.

The deployer includes a playbook that can be used to confirm the environment is suitable and ready for installation.
The deployer includes a playbook that can be used to confirm the environment is suitable and ready for installation. This includes checking outbound connectivity from each component's target hosts to the URLs listed in the [Required Public Repositories](#required-public-repositories) table above.

```bash
# Verify everything
Expand All @@ -404,6 +414,9 @@ ansible-playbook -i <path-to-inventory> itential.deployer.verify_mongodb

# Verify Platform
ansible-playbook -i <path-to-inventory> itential.deployer.verify_platform

# Verify Gateway
ansible-playbook -i <path-to-inventory> itential.deployer.verify_gateway
```

**&#9432; Note:**
Expand Down
23 changes: 23 additions & 0 deletions playbooks/verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,26 @@

- name: Verify Platform Installation
import_playbook: itential.deployer.verify_platform

- name: Verify Gateway Installation
import_playbook: itential.deployer.verify_gateway

# Each component's own final assert is non-fatal (ignore_errors) so that a failure in one
# component (e.g. verify_redis) doesn't abort the ansible-playbook run before the remaining
# import_playbooks above get a chance to execute -- Ansible stops the whole run, not just the
# current play, when a play ends with 100% of its hosts failed. This final play is the one
# place that actually fails the run, and since nothing runs after it, that's safe to do.
- name: Report Overall Verification Result
hosts: all
gather_facts: false
tasks:
- name: Display per-component verification failures for this host
ansible.builtin.debug:
var: component_validation_errors
when: component_validation_errors is defined and component_validation_errors | length > 0

- name: Fail if any verification check failed for this host
ansible.builtin.assert:
that: verification_passed | default(true)
fail_msg: "One or more Itential Deployer verification checks failed for this host. See output above for details."
success_msg: "All Itential Deployer verification checks passed for this host."
13 changes: 13 additions & 0 deletions playbooks/verify_gateway.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright (c) 2026, Itential, Inc
# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
---

- name: Run Gateway Verification Tasks
hosts: gateway
gather_facts: true
become: true
tasks:
- name: Verify Gateway Installation # noqa run-once
ansible.builtin.import_role:
name: itential.deployer.gateway
tasks_from: verify-gateway
48 changes: 40 additions & 8 deletions roles/common/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@

## Purpose

Provides shared default variables consumed by all other roles. Has no `main.yml` task entry point — it is imported by playbooks solely to inject its defaults into the variable scope. Also contains the shared `verify-host.yml` task file used by the `verify_*` playbooks.
Provides shared default variables consumed by all other roles. Has no `main.yml` task entry point — it is imported by playbooks solely to inject its defaults into the variable scope. Also contains the shared `verify-host.yml`, `verify-connectivity.yml`, and `verify-results.yml` task files used by the `verify_*` playbooks. Has no `vars/main.yml` — each component role owns its own variables (e.g. required-repository lists live in `roles/<component>/vars/main.yml`, not here); `common` only ships `defaults/`.

## Entry Point Tasks

There is no `tasks/main.yml`. The only task file is:
There is no `tasks/main.yml`. The task files are:

- `tasks/verify-host.yml` — imported by `verify_redis`, `verify_mongodb`, and `verify_platform` task files via `tasks_from:`. It is not called by any role's `main.yml`.
- `tasks/verify-host.yml` — imported by `verify-mongodb`, `verify-redis`, `verify-platform`, and `verify-gateway` task files via `tasks_from:`. Checks OS/arch/HW specs/proxy and initializes `validation_errors`.
- `tasks/verify-connectivity.yml` — imported by `verify-mongodb`, `verify-redis`, `verify-platform`, and `verify-gateway` (all four components) via `tasks_from:`. Checks outbound connectivity to the URLs in the caller's own `<component>_required_repositories` list, passed in as `required_repositories`. See "verify-connectivity.yml Logic" below.
- `tasks/verify-results.yml` — imported by `verify-mongodb`, `verify-redis`, `verify-sentinel`, `verify-platform`, and `verify-gateway` (all five verify task files) to print `validation_errors` and assert `cpu_validation`/`memory_validation`/`disk_validation`/`os_validation`/`arch_validation`/`proxy_validation`/`connectivity_validation` all passed. Each lookup is wrapped in `| default({'failed': false})` before the `is failed`/`is not failed` test, since not every caller registers every one of these (e.g. `verify-sentinel.yml` never calls `verify-connectivity.yml`, so `connectivity_validation` is undefined there). The final assert itself uses `ignore_errors: true` and sets a per-host `verification_passed` fact — see "Non-Fatal Verify Design" below. It also expects a `component_name` var from the caller (e.g. `"Redis"`, `"Redis Sentinel"`, `"MongoDB"`, `"Platform"`, `"Gateway"`) and merges this component's `validation_errors` into a per-host `component_validation_errors` dict keyed by that name, so `playbooks/verify.yml`'s final report can show which component failed which check, not just an overall pass/fail.

None of these task files are called by any role's `main.yml`.

## verify-host.yml Logic

Expand All @@ -20,15 +24,34 @@ Expects two variables from the caller:
| `hw_specs_var_name` | `"redis_hw_specs"` | Name of the hardware specs dict variable to validate against |

Execution order:
1. Assert `platform_release` and `env` are defined, `env` in `['dev','test','prod']`
1. Assert `platform_release` and `env` are defined, `env` in `['dev','test','prod']` — these two are the only remaining hard (non-`ignore_errors`) asserts in the file, since they represent inventory misconfiguration rather than a per-host result. See "Non-Fatal Verify Design" below for why that distinction matters.
2. Call `itential.deployer.gather_host_information` module to collect OS/arch/hardware facts
3. Validate OS: RedHat/Rocky/OracleLinux 8 or 9, or Amazon Linux 2023
4. Validate architecture: `x86_64` or `aarch64`
3. Validate OS: RedHat/Rocky/OracleLinux 8 or 9, or Amazon Linux 2023 (`ignore_errors: true`, registers `os_validation`, collects failures into `validation_errors`)
4. Validate architecture: `x86_64` or `aarch64` (`ignore_errors: true`, registers `arch_validation`, collects failures into `validation_errors`)
5. Validate CPU, RAM, and disk against the `hw_specs_var_name[env]` dict (uses `ignore_errors: true` and collects failures into `validation_errors`)
6. Check for proxy settings in env vars, `/etc/environment`, `/etc/profile.d/`
7. Assert all validations passed
7. Assert all validations passed (this is `verify-results.yml`'s final assert, not part of this file)

The hardware specs dicts (e.g., `redis_hw_specs`, `platform_hw_specs`, `mongodb_hw_specs`) are defined in `roles/<component>/vars/platform-release-<N>.yml` and keyed by `env` value. `gateway_hw_specs` is the exception — it's defined in `roles/gateway/vars/main.yml` instead, since Gateway's hardware requirements are not release-specific.

`verify-gateway.yml` now calls `common:verify-host` too (all four components — Redis, MongoDB, Platform, Gateway — follow the identical `verify-host` → `verify-connectivity` → `verify-results` sequence).

## verify-connectivity.yml Logic

Expects one variable from the caller:

| Variable | Example | Purpose |
|----------|---------|---------|
| `required_repositories` | `"{{ redis_required_repositories }}"` | The list of repository dicts to check — owned by the calling component's role, not by `common` |

Each component role defines its own `<component>_required_repositories` list in `roles/<component>/vars/main.yml` (`redis_required_repositories`, `mongodb_required_repositories`, `platform_required_repositories`, `gateway_required_repositories`), auto-loaded whenever that role runs. Together they mirror the "Required Public Repositories" table in `README.md`, excluding the Ansible Control Node rows. Each entry has a `url`, `type` (`bare` or `path`), optional `check_target`, and `notes`. Redis Sentinel-only hosts reuse `redis_required_repositories` (`verify-sentinel.yml`) since Sentinel is installed from the same Redis source/package.

Execution order:
1. Check baseline reachability of every entry's bare `url` via `ansible.builtin.uri` — any real HTTP status (100-599) counts as reachable; only a connection-level failure (DNS/TCP/TLS/timeout, which `fetch_url` reports as `status: -1`) counts as unreachable. Append failures to `validation_errors`.
2. For entries with `type: path` that passed step 1, request the specific `check_target` resource and expect an actual `[200, 301, 302]` response. Append failures to `validation_errors`.
3. Assert both checks passed (`ignore_errors: true`, registers `connectivity_validation`), following the same pattern as `cpu_validation`/`memory_validation`/etc. in `verify-host.yml`.

The hardware specs dicts (e.g., `redis_hw_specs`, `platform_hw_specs`, `mongodb_hw_specs`) are defined in `roles/<component>/vars/platform-release-<N>.yml` and keyed by `env` value.
Called from `verify-mongodb.yml`, `verify-redis.yml`, `verify-sentinel.yml`, `verify-platform.yml`, and `verify-gateway.yml`.

## Key Variables

Expand All @@ -41,6 +64,12 @@ The hardware specs dicts (e.g., `redis_hw_specs`, `platform_hw_specs`, `mongodb_
| `offline_control_node_root` | `{{ playbook_dir }}/files` | `defaults/main/offline.yml` | Root on control node where offline packages are staged. |
| `offline_itential_packages_path` | `itential_packages/{{ ansible_distribution \| lower }}_{{ ansible_distribution_major_version }}` | `defaults/main/offline.yml` | OS-specific subdirectory under the offline roots. |

## Non-Fatal Verify Design

`playbooks/verify.yml` chains `verify_redis`/`verify_mongodb`/`verify_platform`/`verify_gateway` together via `import_playbook`. Ansible aborts the *entire* `ansible-playbook` run (not just the current play) the moment a play ends with 100% of its hosts failed — this happens even for later plays targeting completely unrelated hosts. So every task in the verify chain that can fail on a genuine per-host/per-component result (OS/arch checks, the final combined assert in `verify-results.yml`, Gateway's own final assert) uses `ignore_errors: true` + `register:` instead of failing hard, and the final assert in each also sets a per-host `verification_passed` fact and merges `validation_errors` into a per-host `component_validation_errors` dict (keyed by `component_name`, via `combine()` so a later-running component can't erase an earlier one's failures on the same host). `playbooks/verify.yml` ends with one extra `hosts: all` play ("Report Overall Verification Result") that prints `component_validation_errors` (when non-empty) and then asserts `verification_passed | default(true)` for real (no `ignore_errors`) — since nothing runs after it, failing there is safe and is what actually gives the overall run a non-zero exit code, while still letting every component's checks run against every host regardless of earlier failures.

The two asserts in `verify-host.yml` that check `platform_release`/`env` are deliberately left hard-failing — they indicate the inventory itself is misconfigured (not a specific host failing a real check), typically affects every host identically, and there's little value in continuing past it.

## Dependencies / Assumptions

- The `common` role has no task dependencies.
Expand All @@ -52,3 +81,6 @@ The hardware specs dicts (e.g., `redis_hw_specs`, `platform_hw_specs`, `mongodb_
- `offline_install_enabled` defaults to `false` here but the `download_packages_*` playbooks override it to `false` explicitly at the play level — the download playbooks always run online even when deploying to offline targets.
- The `common_itential_release_file` check in the `os` role skips OS package installation if the file already exists, making the `os` role effectively idempotent for re-runs.
- `verify-host.yml` uses `ignore_errors: true` on individual assertions and collects them, then does a final combined assert. This means a failing host will show all failures rather than stopping at the first.
- `verify-connectivity.yml`'s baseline check deliberately does NOT use `ansible.builtin.uri`'s `status_code: -1` — that is not a wildcard; `status_code` is a literal list of acceptable HTTP codes (default `[200]`). Instead it passes `status_code: "{{ range(100, 600) | list }}"` so any real HTTP response passes, and relies on `fetch_url` setting `status: -1` (which is never in that range) to detect genuine connection failures.
- `verify-connectivity.yml` deliberately avoids requiring the `git` binary (e.g. via `git ls-remote`) for the `gitlab.com`/`github.com` rows, since verification runs pre-install and `git` may not be installed on the target yet. It uses plain HTTPS `check_target` requests instead (a real gitlab.com group page, and a `codeload.github.com` tarball URL that also exercises the `github.com` → `codeload.github.com` redirect).
- `verify-results.yml` previously asserted on `platform_validation is not failed`, but nothing ever registered a variable by that name (Platform's own TLS checks register `platform_tls_dir_validation`/`platform_tls_contents_validation` instead) — this made `verify_redis`/`verify_mongodb`/`verify_platform` fail with "the 'failed' test expects a dictionary" on an undefined variable. It was replaced with `connectivity_validation is not failed`.
78 changes: 78 additions & 0 deletions roles/common/tasks/verify-connectivity.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Copyright (c) 2026, Itential, Inc
# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
---
# Verifies outbound connectivity to the public repositories a component needs, as listed in
# the "Required Public Repositories" table in README.md (excluding the Ansible Control Node
# rows, which are the control node's responsibility, not a target host's).
#
# Expects one variable from the caller:
# - required_repositories: list of repository dicts -- each component owns its own list in
# roles/<component>/vars/main.yml (e.g. redis_required_repositories), with url, type
# (bare|path), optional check_target, and notes.
#
# Appends a message to validation_errors for every failure, then sets connectivity_validation
# via an ignore_errors assert, following the same pattern as verify-host.yml.

- name: Check baseline reachability of required repositories
ansible.builtin.uri:
url: "{{ item.url }}"
method: GET
# Accept any real HTTP status code (100-599) as "reachable" -- fetch_url only returns
# status -1 when the request never got a response at all (DNS/TCP/TLS/timeout failure).
status_code: "{{ range(100, 600) | list }}"
validate_certs: true
timeout: 10
register: repository_reachability
loop: "{{ required_repositories }}"
loop_control:
label: "{{ item.url }}"
ignore_errors: true

- name: Add unreachable repository errors to list
ansible.builtin.set_fact:
validation_errors: "{{ validation_errors + ['Connectivity: ' ~ item.item.url ~ ' (' ~
item.item.notes ~ ') is unreachable: ' ~ (item.msg | default('unknown error'))] }}"
loop: "{{ repository_reachability.results }}"
loop_control:
label: "{{ item.item.url }}"
when: item is failed

- name: Identify repositories needing a resource-specific check
ansible.builtin.set_fact:
repositories_needing_path_check: "{{ repository_reachability.results
| rejectattr('failed')
| map(attribute='item')
| selectattr('type', 'equalto', 'path')
| list }}"

- name: Check resource-specific access for path-type repositories
ansible.builtin.uri:
url: "{{ item.check_target }}"
method: GET
status_code: [200, 301, 302]
validate_certs: true
timeout: 10
register: repository_path_check
loop: "{{ repositories_needing_path_check }}"
loop_control:
label: "{{ item.check_target }}"
ignore_errors: true

- name: Add resource-specific check failures to list
ansible.builtin.set_fact:
validation_errors: "{{ validation_errors + ['Connectivity: ' ~ item.item.check_target ~ ' (' ~
item.item.notes ~ ') did not return a successful response'] }}"
loop: "{{ repository_path_check.results | default([]) }}"
loop_control:
label: "{{ item.item.check_target }}"
when: item is failed

- name: Verify that repository connectivity checks passed
ansible.builtin.assert:
that:
- repository_reachability.results | selectattr('failed') | list | length == 0
- repository_path_check.results | default([]) | selectattr('failed') | list | length == 0
fail_msg: "One or more required repository connectivity checks failed! See above."
quiet: true
ignore_errors: true
register: connectivity_validation
Loading
Loading