Skip to content

Add cloud-connector as a native foremanctl feature - #569

Merged
ehelms merged 8 commits into
theforeman:masterfrom
jeremylenz:cloud-connector-feature
Aug 27, 2026
Merged

Add cloud-connector as a native foremanctl feature#569
ehelms merged 8 commits into
theforeman:masterfrom
jeremylenz:cloud-connector-feature

Conversation

@jeremylenz

@jeremylenz jeremylenz commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Re-implements the upstream satellite_operations.cloud_connector role natively in foremanctl (SAT-45966 / SAT-44641)
  • New cloud-connector feature gated behind --add-feature cloud-connector, with rh-cloud dependency
  • Installs rhc and yggdrasil-worker-forwarder, configures the worker, starts rhcd, sets rhc_instance_id via the Foreman API, and announces to Sources
  • Works with both foremanctl deploy and forge deploy-dev
  • Uses OAuth for all Foreman API calls during setup, matching the pattern used by other roles (foreman_proxy, iop_core, pulp, backup)
  • Creates a dedicated cloud_connector_user service user with limited dispatch_cloud_requests permission for runtime worker authentication
  • Adds a reusable foremanctl_api custom module for OAuth-authenticated API calls where no FAM module exists
  • Early pre-checks validate consumer certificate presence and mutual exclusion with the iop feature
  • Adds Foreman CA to system trust store so the worker binary can verify Foreman's certificate
  • Optional --cloud-connector-http-proxy flag for environments without direct internet access

Companion PRs:

theforeman/foreman_rh_cloud#1214 (adds the announce_to_sources API endpoint)
theforeman/yggdrasil-worker-forwarder#5 (allows specifying a certificate to use)

Test plan

  • foremanctl deploy --add-feature cloud-connector completes successfully
  • foremanctl features --list-enabled includes cloud-connector
  • systemctl status rhcd shows active/running
  • /etc/rhc/workers/foreman_rh_cloud.toml has correct content with service user credentials
  • hammer settings info --name rhc_instance_id shows the consumer cert CN
  • hammer user info --login cloud_connector_user shows the service user with Cloud Connector role
  • Second foremanctl deploy is idempotent
  • forge deploy-dev --add-feature cloud-connector completes successfully
  • Enabling both cloud-connector and iop fails early with a clear error

🤖 Generated with Claude Code

Comment thread src/roles/check_cloud_connector/tasks/main.yaml Outdated
Comment thread src/roles/check_cloud_connector/tasks/main.yaml Outdated
Comment thread src/roles/check_cloud_connector/tasks/main.yaml Outdated
Comment thread src/roles/cloud_connector/defaults/main.yaml Outdated
Comment thread src/roles/cloud_connector/handlers/main.yaml
Comment thread src/roles/cloud_connector/tasks/main.yaml
Comment thread src/roles/cloud_connector/tasks/main.yaml Outdated
Comment thread src/roles/cloud_connector/tasks/main.yaml Outdated
Comment thread src/roles/cloud_connector/tasks/main.yaml Outdated
Comment thread src/roles/cloud_connector/tasks/main.yaml Outdated
Comment thread src/roles/cloud_connector/tasks/main.yaml Outdated
protocol = "grpc"
env = [
"FORWARDER_USER={{ cloud_connector_user }}",
"FORWARDER_PASSWORD={{ cloud_connector_password }}",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is the part we still need to work out how to properly handle, that is, the authentication.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

As I see it our options are
1 - Keep things as they are here, feeding admin/changeme to rhc - Not acceptable because the admin password ends up in the worker config.toml.
2 - Create a service user with limited permissions - Doable in the existing PRs here and in foreman_rh_cloud.
3 - Create a service user + personal access token and use that, same as the previous architecture - FAM doesn't have a module for personal access tokens, so this would be a fair bit of work

I think Option 2 seems promising and am pushing that update now.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@ehelms Updated to use OAuth, but we still need the service user because yggdrasil-worker-forwarder only supports HTTP basic auth, and doesn't support OAuth.

@jeremylenz

Copy link
Copy Markdown
Contributor Author

Pushed updates addressing review feedback (1bfe640):

  • Handler: switched from ansible.builtin.service to ansible.builtin.systemd_service since we use daemon_reload
  • Workers directory: removed the task — confirmed rhc package on EL9 creates /etc/rhc/workers/
  • Settings API: replaced raw uri calls with theforeman.foreman.setting module for rhc_instance_id and allow_auto_inventory_upload
  • Lint fix: added noqa: no-static-secrets on the defaults password (same pattern as foreman_development)

@jeremylenz
jeremylenz force-pushed the cloud-connector-feature branch from 908e8b1 to 95a9cc9 Compare June 15, 2026 18:13
@jeremylenz
jeremylenz force-pushed the cloud-connector-feature branch from 2092035 to 84335ce Compare June 23, 2026 20:21
@jeremylenz

Copy link
Copy Markdown
Contributor Author

rebased & fixed conflicts

@qcjames53 qcjames53 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.

I'm not enough in the loop to give this a full review but had a few comments that might be helpful. Consider this 80% good points 20% ass-talking. Here's what I was able to test directly:

  • The iop conflict and missing consumer cert checks both work on the positive and negative cases (used manual subscription manager commands for the latter).
  • The yggdrasil-worker-forwarder check works in the negative (was unable to figure out package install on centos9).
  • The cloud connector role works.

Beyond these tests, I have questions about the whole approach of cloud connector even after reading the planning docs and such.

  1. How are we splitting RHEL and Centos 9 deploys? I think the differentiation needs to be clearer since (best I can tell) yggdrasil-worker-forwarder is RHEL only. If the packaged container images are centos9 and the rpms needed for cloud connector are RHEL-only, that seems like a big problem to me!
  2. deploy-dev needs to work with cloud connector. At minimum a procedure but ideally out of the box. I think we really need a procedure for setting up a centos9 deploy-dev environment to build/test cloud connector. If that's not possible, then we need a good procedure for setting up a RHEL environment as deploy-dev.
  3. If it's possible to pass in subscription manager credentials safely, I would greatly prefer if we could deploy an environment with cloud connector immediately instead of requiring manual registration for the quadlet VM, THEN adding the feature. I understand this won't be an issue for customers but this would be HUGE for testing purposes.

I have to gracefully bow out of reviewing this for a few reasons: capacity + artemis stuff + this baby could come any day! Sorry to not complete testing but I put in an honest effort.

Comment thread src/roles/check_cloud_connector/tasks/main.yaml Outdated
Comment thread src/roles/cloud_connector/tasks/main.yaml Outdated
Comment thread src/roles/cloud_connector/tasks/main.yaml Outdated
Comment thread src/roles/checks/tasks/main.yml Outdated
@jeremylenz

jeremylenz commented Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

I'll respond with thoughts off the top of my head :)

  1. How are we splitting RHEL and Centos 9 deploys? I think the differentiation needs to be clearer since (best I can tell) yggdrasil-worker-forwarder is RHEL only. If the packaged container images are centos9 and the rpms needed for cloud connector are RHEL-only, that seems like a big problem to me!

In order for cloud connector to work you need available on your base system:

  1. ansible-runner command - Downstream, from the Satellite repo, e.g. satellite-6.19-for-rhel-9-x86_64-rpms. Upstream, from the Foreman Plugins repo, e.g. https://yum.theforeman.org/plugins/3.19/el9/x86_64/
  2. yggdrasil-worker-forwarder - Downstream, from the Satellite repo. Upstream, from the Foreman Plugins repo, e.g. https://yum.theforeman.org/plugins/3.19/el9/x86_64/
  3. rhc - from AppStream (CentOS or RHEL)

In the previous architecture, these weren't even listed as prerequisites, even in upstream docs. Downstream, you'd already have all of these since the Satellite system must be registered and have those repos enabled. But upstream, anything can happen. It seems this is a documentation gap. cc @Lennonka

  1. deploy-dev needs to work with cloud connector. At minimum a procedure but ideally out of the box. I think we really need a procedure for setting up a centos9 deploy-dev environment to build/test cloud connector. If that's not possible, then we need a good procedure for setting up a RHEL environment as deploy-dev.

I was testing with deploy-dev on RHEL (with --target-host localhost). I think it works fine, as long as you have the repos available that I mentioned above.

@jeremylenz

Copy link
Copy Markdown
Contributor Author

@qcjames53 Addressed 2 comments and replied to the third

Comment thread src/roles/cloud_connector/defaults/main.yaml Outdated
@lfu

lfu commented Jun 26, 2026

Copy link
Copy Markdown

Tested cloud connector feature in development mode. All core functionality works correctly. Found 3 necessary configuration changes for development environment.


🔍 Testing Methodology

Environment: Development mode using forge deploy-dev with Rails source code
Test Scope:

  • Cloud connector deployment
  • Service user authentication
  • API endpoint functionality
  • Worker configuration

🔗 Related/Dependent PRs

Foreman RH Cloud Plugin

Foremanctl Branch Preservation

Note: These PRs should be merged before or alongside this cloud connector feature PR.


✅ What Works

1. Cloud Connector Deployment

  • ✓ Cloud connector role created successfully
  • ✓ Required packages present (rhc, yggdrasil-worker-forwarder) - installed as prerequisite
  • ✓ Worker config file created: /etc/rhc/workers/foreman_rh_cloud.toml
  • ✓ rhcd service running and enabled

Note: The cloud connector role does not install packages - it assumes rhc and yggdrasil-worker-forwarder are already present (installed manually or by prerequisite roles).

2. Service User Authentication

Created service user cloud_connector_user with proper credentials:

User ID: 5
Role: Cloud Connector
Auth Source: Internal
Last Login: Successfully authenticated via API

3. API Endpoint Verification

Manual test of /api/v2/rh_cloud/cloud_request endpoint:

Request:

curl -u cloud_connector_user:$PASSWORD \
  -X POST \
  http://localhost:3000/api/v2/rh_cloud/cloud_request \
  -H 'Content-Type: application/json' \
  -d '{"directive":"foreman_rh_cloud", ...}'

Result:

✓ Authenticated user cloud_connector_user against INTERNAL authentication source
✓ Authorized user cloud_connector_user(cloud_connector_user)
✓ Request processed (playbook URL validation occurred as expected)

4. Configuration Validation

  • rhc_instance_id setting configured: 5fc35187-ee43-48a8-b61a-01c0153d0562
  • ✓ Worker URL correct: http://localhost:3000/api/v2/rh_cloud/cloud_request
  • ✓ Credentials properly stored in worker config

🔧 Required Code Changes

Found 3 necessary changes for development environment support:

Change 1: Development URL Configuration

File: development/playbooks/deploy-dev/deploy-dev.yaml

Issue: Development uses Rails dev server on port 3000, not containerized Foreman

Fix:

vars:
  cloud_connector_url: "http://localhost:3000"
  cloud_connector_admin_user: "{{ foreman_development_admin_user }}"
  cloud_connector_admin_password: "{{ foreman_development_admin_password }}"

Why: Points worker to the correct development Rails server endpoint


Change 2: Disable Certificate Validation for HTTP

File: src/roles/cloud_connector/tasks/main.yaml

Issue: Development uses HTTP (no SSL), certificate validation fails

Fix:

- name: Create cloud connector service user
  theforeman.foreman.user:
    # ... existing parameters ...
    validate_certs: false  # <-- Add this

Why: HTTP connections don't have valid certificates in development


Change 3: Make CA Certificate Path Optional

File: src/roles/cloud_connector/tasks/main.yaml

Issue: No CA certificate exists for HTTP connections, undefined variable error

Fix:

- name: Create cloud connector service user
  theforeman.foreman.user:
    # ... existing parameters ...
    ca_path: "{{ foreman_ca_certificate | default(omit) }}"  # <-- Change this

Why: Prevents Ansible error when foreman_ca_certificate is undefined (HTTP mode)


⚠️ Known Limitations

Automated pytest Suite Skipped

Issue: All 6 cloud connector tests were skipped:

tests/cloud_connector_test.py::test_rhc_package_installed SKIPPED
tests/cloud_connector_test.py::test_yggdrasil_worker_forwarder_package_installed SKIPPED
tests/cloud_connector_test.py::test_workers_directory_exists SKIPPED
tests/cloud_connector_test.py::test_worker_config_exists SKIPPED
tests/cloud_connector_test.py::test_worker_script_exists SKIPPED
tests/cloud_connector_test.py::test_rhcd_service_running SKIPPED

Root Cause:

  • pytest hardcoded to check ./foremanctl features (production state)
  • Development uses ./forge deploy-dev (separate state)
  • Feature flags from --add-feature are temporary, not persisted

Impact:

  • ❌ Automated test suite cannot verify development deployments
  • ✅ Manual verification confirms all test criteria pass

Testing completed with: Claude Code

@jeremylenz

Copy link
Copy Markdown
Contributor Author

Re: Change 1 - cloud_connector_url defaults to foreman_url which is https://{{ fqdn }},
and httpd proxies to the Rails server. The HTTPS URL through httpd works fine in dev — we tested it. Using http://localhost:3000 directly would bypass httpd and also break the ca_path on
all the FAM module calls. The worker TOML would also have an HTTP URL, which means the runtime cloud requests would go over unencrypted HTTP.

Change 2 is not necessary unless I were to do Change 1, which I think is not needed for the reason above.

Updating now with Change 3.

@jeremylenz

Copy link
Copy Markdown
Contributor Author

Btw, Our role does install the packages — line 2-7 of tasks/main.yaml:

  - name: Install rhc and yggdrasil-worker-forwarder
    ansible.builtin.package:
      name:
        - rhc
        - yggdrasil-worker-forwarder

@jeremylenz

Copy link
Copy Markdown
Contributor Author

/packit copr-build

@ekohl ekohl left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Marking as request changes because I think this needs some fundamental things:

  • Stop using theforeman.foreman.setting
  • Resolve the registration task discussion

Comment thread src/roles/cloud_connector/templates/foreman_rh_cloud.toml.j2 Outdated
@jeremylenz

Copy link
Copy Markdown
Contributor Author

Marking as request changes because I think this needs some fundamental things:

  • Stop using theforeman.foreman.setting
  • Resolve the registration task discussion

This no longer uses theforeman.foreman.setting so that part is resolved IMO. See comment above

Added a new commit with the purpose-built module for announce to sources. Tested again and deploy succeeds, so I'm happy with it if you are.

@jeremylenz

Copy link
Copy Markdown
Contributor Author

rebased

@jeremylenz

Copy link
Copy Markdown
Contributor Author

🍏

@jeremylenz

Copy link
Copy Markdown
Contributor Author

@ekohl Are your concerns addressed for now? @ehelms Can we get this in soon? I'll be away starting 5 September so would like to get it merged before then; also having it in will help us with the DBUS rewrite of yggdrasil-worker-forwarder.

Comment thread src/roles/cloud_connector/defaults/main.yaml Outdated
Comment thread src/roles/cloud_connector/tasks/main.yaml Outdated
Comment thread src/roles/cloud_connector/tasks/main.yaml Outdated
Comment thread src/roles/cloud_connector/tasks/main.yaml Outdated
Comment thread src/vars/base.yaml Outdated
jeremylenz and others added 7 commits August 27, 2026 13:14
Re-implements the upstream satellite_operations.cloud_connector role
natively in foremanctl so users can enable it via:
  foremanctl deploy --add-feature cloud-connector

Feature overview:
- Installs rhc and yggdrasil-worker-forwarder packages
- Templates the worker config with FORWARDER_CA_FILE for explicit
  CA trust (requires yggdrasil-worker-forwarder >= 0.0.4)
- Creates a dedicated service user with limited dispatch_cloud_requests
  permission for runtime worker authentication
- Uses OAuth for all Foreman API calls during setup (role, user,
  settings), matching the pattern used by other foremanctl roles
- Adds a reusable foremanctl_api custom module for OAuth-authenticated
  API calls where no FAM module exists
- Sets rhc_instance_id and enables automatic inventory upload
- Announces to Sources via the foreman_rh_cloud API
- Optional HTTP proxy support via --cloud-connector-http-proxy
- Pre-checks validate consumer certificate and iop mutual exclusion
- Works with both foremanctl deploy and forge deploy-dev

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Set worker script mode to 0755 (was 0750) to match test expectation
and allow execution.

Only remove the rhcd systemd drop-in directory when it is actually
empty, preventing accidental deletion of files added by other roles
or packages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace direct requests + requests_oauthlib usage with apypie's built-in OAuth1 support. This simplifies the code by leveraging existing OAuth1 implementation from the theforeman.foreman collection. Also removed the unnecessary status_code parameter from the module and cloud_connector task.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Remove tasks that should be handled by foreman_rh_cloud:
- Role creation (seeded by foreman_rh_cloud)
- Setting allow_auto_inventory_upload (defaults to true, shouldn't be overridden)

Reorganize tests to use feature directory structure for auto-marking.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Ekohl asked for a purpose-built module for announce_to_sources instead
of a generic "call any endpoint" module, so the call can properly wait
for the resulting Foreman task rather than being best-effort.

foremanctl_announce_to_sources uses apypie's resource_action/wait_for_task
to poll the task to completion and fail loudly if it doesn't succeed. It
also clears apypie's local apidoc cache before resolving the action,
working around a case where the server's Apipie-Checksum header didn't
change even though the action had been added to the live apidoc, which
left the stale cached apidoc hiding the new action.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Use trailing commas instead of leading commas for the conditional
FORWARDER_CA_FILE entry, per review feedback.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Per review feedback from ehelms:
- Remove the static "changeme" password default; the real value is
  always auto-generated and persisted via vars/base.yaml, and a role
  invoked without that should fail loudly rather than fall back to a
  known password.
- Use role-scoped variable names (cloud_connector_foreman_oauth_*,
  cloud_connector_foreman_ca_certificate) instead of consuming the
  global foreman_* vars directly in the role's tasks, matching the
  pattern used by backup, iop_core, and pulp.
- Consolidate the HTTP proxy add/remove logic into http_proxy.yaml so
  it's all in one place.
- Remove the cloud_connector_service_user override in vars/base.yaml
  since it was identical to the role default.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@jeremylenz
jeremylenz force-pushed the cloud-connector-feature branch from e5df4b7 to c5eb2f2 Compare August 27, 2026 17:15
@jeremylenz

Copy link
Copy Markdown
Contributor Author
  • Stop using theforeman.foreman.setting

I realized this isn't entirely accurate - we did stop using it to set allow_auto_inventory_upload, but we still use it to set rhc_instance_id. However, rhc_instance_id isn't really a user preference; it's derived from the consumer certificate and is overwritten by foremanctl on every deploy. So I think that satisfies the "controlled by foremanctl" requirement.

@jeremylenz

Copy link
Copy Markdown
Contributor Author

Found a way to make it even better, stand by

foreman_rh_cloud's announce_to_sources action now accepts an
instance_id param and sets Setting[:rhc_instance_id] itself before
triggering the announcement task (it already required that setting to
be non-blank and uses it as the rhc_id payload). This lets us drop the
standalone theforeman.foreman.setting task and stop using that module
in this role entirely.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@jeremylenz

Copy link
Copy Markdown
Contributor Author

@ekohl Okay, found a way to not use theforeman.foreman.setting module at all (see the latest commit). 👍

@ehelms
ehelms merged commit ffa9b2d into theforeman:master Aug 27, 2026
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants