Skip to content
This repository was archived by the owner on Sep 9, 2026. It is now read-only.

MGMT-24176: add BMH-based agent import playbook with AAP config-as-code - #279

Merged
openshift-merge-bot[bot] merged 1 commit into
osac-project:mainfrom
danmanor:feat/generalized-agent-import
May 4, 2026
Merged

openshift-merge-bot[bot] merged 1 commit into
osac-project:mainfrom
danmanor:feat/generalized-agent-import

Conversation

@danmanor

@danmanor danmanor commented May 3, 2026 •

Copy link
Copy Markdown
Contributor

Single-file playbook that imports bare metal servers as Assisted Installer agents using BareMetalHost CRs via Ironic. Reads server inventory from a mounted ConfigMap, reconciles BMH CRs (create missing, delete stale), waits for Agent registration, and labels agents with resource class and Netris server name metadata.

Config-as-code changes add a job template, a 10-minute periodic schedule, and mount the import-agents-inventory ConfigMap into the cluster-fulfillment-ig pod spec.

Includes a sample inventory file for local testing:

  ansible-playbook playbook_osac_import_agents.yml \
    -e import_agents_inventory_path=samples/import_agents_servers.yml

Summary by CodeRabbit

  • New Features

    • Bare metal server import feature to automatically provision Agent resources from inventory.
    • Scheduled import job in AAP running every 10 minutes.
    • Automatic reconciliation managing resource lifecycle (creation and cleanup).
  • Documentation

    • New guide for bare metal server import workflow, setup, and operations.
  • Chores

    • Sample configuration files for BMC credentials and import parameters.

@openshift-ci-robot

openshift-ci-robot commented May 3, 2026 •

Copy link
Copy Markdown

@danmanor: This pull request references MGMT-24176 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the task to target the "5.0.0" version, but no target version was set.

Details

In response to this:

Single-file playbook that imports bare metal servers as Assisted Installer agents using BareMetalHost CRs via Ironic. Reads server inventory from a mounted ConfigMap, reconciles BMH CRs (create missing, delete stale), waits for Agent registration, and labels agents with resource class and Netris server name metadata.

Config-as-code changes add a job template, a 10-minute periodic schedule, and mount the import-agents-inventory ConfigMap into the cluster-fulfillment-ig pod spec.

Includes a sample inventory file for local testing:

 ansible-playbook playbook_osac_import_agents.yml \
   -e import_agents_inventory_path=samples/import_agents_servers.yml

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci

openshift-ci Bot commented May 3, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: danmanor

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci openshift-ci Bot added the approved label May 3, 2026
@coderabbitai

coderabbitai Bot commented May 3, 2026 •

Copy link
Copy Markdown

Warning

Rate limit exceeded

@danmanor has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 32 minutes and 49 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c637c497-a4a4-40ae-a317-da9c278f64c8

📥 Commits

Reviewing files that changed from the base of the PR and between ba34734 and 7f5111e.

📒 Files selected for processing (6)
  • collections/ansible_collections/osac/config_as_code/playbooks/vars/config.yml
  • collections/ansible_collections/osac/config_as_code/roles/aap/vars/controller.yml
  • docs/import-agents.md
  • playbook_osac_import_agents.yml
  • samples/import_agents_bmc_secrets.yml
  • samples/import_agents_extra_vars.yml

Walkthrough

This PR introduces a complete bare metal server import workflow for OpenShift Assisted Installer Agent resources. It adds a new Ansible variable to enable the feature via environment variable, extends AAP controller configuration with a new job template, scheduled periodic reconciliation job, and required volume mounts for configuration data. A detailed documentation file explains the workflow, prerequisites, and operational procedures. The core playbook loads server inventory from a ConfigMap, reconciles bare metal host and agent resources by creating missing entries, deleting stale entries, waiting for agent registration, and updating agent metadata with hostname and resource labels.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

lgtm, approved

Suggested reviewers

  • eranco74
  • tzvatot
  • larsks
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main changes: adding a BMH-based agent import playbook with AAP config-as-code integration, matching the primary objectives of the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 32 minutes and 49 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
playbook_osac_import_agents.yml (1)

112-128: 💤 Low value

Redundant when clause on stale agent deletion.

The when: import_agents_stale_agents.results | length > 0 check on line 128 is redundant because the loop on lines 119-124 already produces an empty list when there are no stale agents, causing the task to be skipped naturally. This is harmless but could be removed for clarity.

♻️ Optional: Remove redundant when clause
       loop_control:
         loop_var: agent
         label: "{{ agent.metadata.name }}"
-      when: import_agents_stale_agents.results | length > 0
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@playbook_osac_import_agents.yml` around lines 112 - 128, Remove the redundant
"when: import_agents_stale_agents.results | length > 0" from the "Delete stale
Agent CRs" task; locate the task with name "Delete stale Agent CRs" that loops
over import_agents_stale_agents.results (loop_var: agent) and delete the when
clause so the task relies on the loop producing an empty list to skip execution
naturally.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@samples/import_agents_extra_vars.yml`:
- Around line 1-5: Remove the trailing whitespace at the end of the first line
that starts with "# To run:" (the comment block containing the
KUBECONFIG/ansible-playbook example) so the file no longer has trailing spaces;
commit the change so the pre-commit hook / CI no longer flags the file.

---

Nitpick comments:
In `@playbook_osac_import_agents.yml`:
- Around line 112-128: Remove the redundant "when:
import_agents_stale_agents.results | length > 0" from the "Delete stale Agent
CRs" task; locate the task with name "Delete stale Agent CRs" that loops over
import_agents_stale_agents.results (loop_var: agent) and delete the when clause
so the task relies on the loop producing an empty list to skip execution
naturally.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a5147c15-80e7-4f34-81c7-d31f36d3664a

📥 Commits

Reviewing files that changed from the base of the PR and between 86e2f56 and ba34734.

📒 Files selected for processing (6)
  • collections/ansible_collections/osac/config_as_code/playbooks/vars/config.yml
  • collections/ansible_collections/osac/config_as_code/roles/aap/vars/controller.yml
  • docs/import-agents.md
  • playbook_osac_import_agents.yml
  • samples/import_agents_bmc_secrets.yml
  • samples/import_agents_extra_vars.yml

Comment thread samples/import_agents_extra_vars.yml
@danmanor
danmanor force-pushed the feat/generalized-agent-import branch from ba34734 to 5eecb74 Compare May 3, 2026 10:52

@tzvatot tzvatot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

PR #279: MGMT-24176: add BMH-based agent import playbook with AAP config-as-code

This PR adds a new single-file playbook that reconciles a file-based server inventory against BareMetalHost and Agent CRs — creating missing BMHs, deleting stale ones, waiting for agent registration, and labeling agents. It includes AAP config-as-code for a job template with a 10-minute schedule, documentation, and sample files. The overall structure is sound and the reconciliation logic is well thought out (delete-agent-before-BMH ordering, managed-by label scoping, MAC-based agent matching).

Category Count
🔴 Critical 0
🟡 Important 3
💡 Suggestion 2

🟡 BMH updates silently skipped for existing servers

File: playbook_osac_import_agents.yml:67-90

The when: server.name not in import_agents_existing_bmh_names guard skips the kubernetes.core.k8s task entirely for any server whose BMH already exists. This means if an operator changes a server's bmc_url, bmc_secret, or boot_mac in the inventory, the update will never be applied — the playbook will silently skip it.

Since kubernetes.core.k8s with state: present already does an apply (create-or-patch), removing the when clause would make the task idempotent for both creation and updates. The only cost is extra API calls on each run, but these are lightweight patch operations.

Recommendation: Remove the when guard and let the k8s module handle idempotency:

    - name: Reconcile BareMetalHost for each server
      kubernetes.core.k8s:
        state: present
        definition:
          # ... same definition ...
      loop: "{{ import_agents_servers }}"
      loop_control:
        loop_var: server
        label: "{{ server.name }}"

If the extra API calls are a concern (many servers on a 10-minute schedule), consider a changed_when annotation or a separate "diff" task — but don't silently skip updates.


🟡 Job template missing allow_simultaneous and ask_variables_on_launch

File: collections/ansible_collections/osac/config_as_code/roles/aap/vars/controller.yml:225-234

Every other job template in this file sets allow_simultaneous: true and ask_variables_on_launch: true. The new import-agents template omits both.

For allow_simultaneous: omitting it is arguably correct for a reconciliation playbook (you don't want concurrent runs contending on the same BMH/Agent CRs). But this relies on the AAP default rather than being explicit, and breaks the pattern established by every other template. If it's intentional, make it explicit.

For ask_variables_on_launch: without this, operators can't override variables (e.g., import_agents_namespace, import_agents_inventory_path) when manually triggering the job template. This is particularly useful for debugging or testing against a different namespace.

Recommendation:

  - name: "{{ aap_prefix }}-import-agents"
    # ...existing fields...
    allow_simultaneous: false
    ask_variables_on_launch: true
    verbosity: 0

🟡 Local testing command in docs will fail — missing required variables

File: docs/import-agents.md:160-162 and samples/import_agents_extra_vars.yml

The documented local testing command is:

ansible-playbook playbook_osac_import_agents.yml \
  -e import_agents_inventory_path=samples/import_agents_extra_vars.yml

But the playbook vars reference default_agent_namespace and hosted_cluster_default_infraenv, which are defined in group_vars/all/infrastructure.yaml. When running locally without the AAP inventory, these are undefined and the playbook will fail with an "undefined variable" error.

Recommendation: Either add these to the sample extra vars file:

# samples/import_agents_extra_vars.yml
default_agent_namespace: "hardware-inventory"
hosted_cluster_default_infraenv: "infraenv"
servers:
  - name: ostest-extraworker-0
    # ...

Or use literal defaults directly in the playbook vars (like other playbooks in this repo do):

vars:
    import_agents_namespace: "hardware-inventory"
    import_agents_infraenv_name: "infraenv"

💡 ConfigMap mounted at /var/secrets/ path

File: collections/ansible_collections/osac/config_as_code/roles/aap/vars/controller.yml:455-461

The import-agents-inventory ConfigMap is mounted at /var/secrets/import-agents/. Using /var/secrets/ for a ConfigMap is misleading — it suggests sensitive data when the content is a server inventory (names, MACs, resource classes). Consider /var/config/import-agents/ to match the data source type.


💡 Schedule frequency vs. wait timeout

File: collections/ansible_collections/osac/config_as_code/roles/aap/vars/controller.yml:402 and playbook_osac_import_agents.yml:143-155

The schedule runs every 10 minutes, but the wait task allows up to 15 minutes per server (90 retries * 10s delay). With multiple new servers, a single playbook run can exceed 10 minutes, potentially queuing scheduled runs. This is handled correctly because allow_simultaneous defaults to false, so AAP will skip/queue. But worth documenting this behavior — operators should know that adding many servers at once will naturally take longer than the schedule interval.


Overall: Well-structured playbook with good reconciliation logic and thorough documentation. The three important items are straightforward to address. Approve with requested changes.

@eranco74 eranco74 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nice work — the reconciliation logic is solid. tzvatot already covered the main items so I'm only adding what wasn't mentioned yet.

Comment thread playbook_osac_import_agents.yml
Comment thread playbook_osac_import_agents.yml
Comment thread playbook_osac_import_agents.yml
Address review feedback:
- Add pre-flight check for missing inventory ConfigMap
- Remove when guard on BMH creation for idempotent updates
- Make disableCertificateVerification configurable per-server
- Remove redundant when clause on stale agent deletion
- Add failed_when guard against None agent name race condition
- Add allow_simultaneous and ask_variables_on_launch to job template
- Add missing vars to sample extra vars for local testing
- Change ConfigMap mount path from /var/secrets/ to /var/config/
@danmanor
danmanor force-pushed the feat/generalized-agent-import branch from 5eecb74 to 7f5111e Compare May 4, 2026 12:46
@danmanor
danmanor requested review from eranco74 and tzvatot May 4, 2026 13:08
@eranco74

eranco74 commented May 4, 2026

Copy link
Copy Markdown
Contributor

/lgtm

@tzumainn

tzumainn commented May 4, 2026

Copy link
Copy Markdown
Contributor

Just a note that we currently have an equivalent role for OpenStack at https://github.com/osac-project/osac-aap/blob/main/collections/ansible_collections/osac/service/roles/manage_agents/tasks/import_agents.yaml; do we want to make this parallel to that?

@openshift-merge-bot
openshift-merge-bot Bot merged commit f65cf31 into osac-project:main May 4, 2026
7 checks passed

@tzvatot tzvatot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Re-review

All findings from the previous round were addressed in the merged commit: pre-flight inventory check, configurable per-server BMC TLS verification, explicit allow_simultaneous: false / ask_variables_on_launch: true, failed_when guard on agent labeling, ConfigMap mount at /var/config/ instead of /var/secrets/, and no redundant when clauses. Solid turnaround.

Category Count
🔴 Critical 0
🟡 Important 1
💡 Suggestion 1

🟡 Local testing command in docs still won't work

File: docs/import-agents.md:160-162 and samples/import_agents_extra_vars.yml

The documented local testing command is:

ansible-playbook playbook_osac_import_agents.yml \
  -e @samples/import_agents_extra_vars.yml

The extra vars file now correctly defines default_agent_namespace and hosted_cluster_default_infraenv (addressing the original feedback), but it does not override import_agents_inventory_path. The playbook will still try to load the server inventory from the default path /var/config/import-agents/servers.yml, hit the pre-flight stat check, and fail.

The PR body references a different file (samples/import_agents_servers.yml) that doesn't exist in the repo, which suggests this was the intended inventory file but got consolidated into the extra vars file.

Recommendation: Split samples/import_agents_extra_vars.yml into two files — one for the variables (import_agents_extra_vars.yml) and one for the inventory (import_agents_servers.yml):

# samples/import_agents_extra_vars.yml
default_agent_namespace: "hardware-inventory"
hosted_cluster_default_infraenv: "infraenv"
import_agents_inventory_path: "samples/import_agents_servers.yml"
# samples/import_agents_servers.yml
servers:
  - name: ostest-extraworker-0
    bmc_url: "redfish-virtualmedia+https://..."
    bmc_secret: "ostest-extraworker-0-bmc-secret"
    boot_mac: "00:8f:09:3e:24:af"
    netris_server_name: "extraworker-0"
    resource_class: "x86_64"
  # ...

This also makes the sample structure match the production layout (extra vars separate from the inventory ConfigMap content).


💡 failed_when on agent labeling fires after the API call, not before

File: playbook_osac_import_agents.yml:190-194

The failed_when guard correctly catches the case where mac_to_agent_name returns None, but Ansible evaluates failed_when after the task executes. So the kubernetes.core.k8s module will first attempt to apply a definition with name: "None" (the stringified Jinja2 result), get a 404 or validation error from the API server, and then failed_when marks it failed.

In practice this is harmless — the wait task at line 143 ensures all agents exist before we reach this point, so this is a defense-in-depth guard that should rarely fire. But if it does fire, the user will see two errors (the API error and the failed_when), which can be confusing.

A cleaner alternative is a when guard that skips + warns instead of failing after-the-fact:

      when: >-
        [server.boot_mac] |
        osac.service.mac_to_agent_name(import_agents_final_agents.resources) is not none
      register: import_agents_label_result

    - name: Warn about unlabeled agents
      ansible.builtin.debug:
        msg: "Agent not found for server {{ server.name }} (MAC {{ server.boot_mac }}) — skipping label"
      loop: "{{ import_agents_servers }}"
      loop_control:
        loop_var: server
        label: "{{ server.name }}"
      when: >-
        [server.boot_mac] |
        osac.service.mac_to_agent_name(import_agents_final_agents.resources) is none

But the current approach is functional. Up to you.


Overall: Previous review feedback was fully addressed. The reconciliation logic, stale-resource cleanup ordering, managed-by label scoping, and AAP integration are all clean. The local testing docs are the only actionable item remaining.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants