Skip to content
Open
Show file tree
Hide file tree
Changes from 12 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
11 changes: 11 additions & 0 deletions development/playbooks/deploy-dev/deploy-dev.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
vars:
httpd_foreman_backend: "http://localhost:3000"
pre_tasks:
- name: Check cloud-connector prerequisites
ansible.builtin.include_role:
name: check_cloud_connector
when: "'cloud-connector' in enabled_features"

- name: Set development postgresql databases
ansible.builtin.set_fact:
postgresql_databases: >-
Expand Down Expand Up @@ -57,6 +62,12 @@
vars:
iop_core_foreman_oauth_consumer_key: "{{ foreman_oauth_consumer_key }}"
iop_core_foreman_oauth_consumer_secret: "{{ foreman_oauth_consumer_secret }}"
- role: cloud_connector
when:
- "'cloud-connector' in enabled_features"
vars:
cloud_connector_admin_user: "{{ foreman_development_admin_user }}"
cloud_connector_admin_password: "{{ foreman_development_admin_password }}"
post_tasks:
- name: Stop Foreman development service
ansible.builtin.include_role:
Expand Down
3 changes: 3 additions & 0 deletions development/playbooks/deploy-dev/metadata.obsah.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ variables:
foreman_development_github_username:
help: GitHub username to add as additional remote for git checkouts
action: store
cloud_connector_http_proxy:
parameter: --cloud-connector-http-proxy
help: HTTP proxy URL for the cloud connector rhcd service.

include:
- _flavor_features
Expand Down
4 changes: 4 additions & 0 deletions src/features.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ rh-cloud:
hammer: foreman_rh_cloud
dependencies:
- katello
cloud-connector:
description: Cloud Connector for Red Hat Hybrid Cloud Console
dependencies:
- rh-cloud
iop:
description: iop services
dependencies:
Expand Down
3 changes: 3 additions & 0 deletions src/playbooks/deploy/deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,7 @@
- role: hammer
when:
- "'hammer' in enabled_features"
- role: cloud_connector
when:
- "'cloud-connector' in enabled_features"
- post_install
3 changes: 3 additions & 0 deletions src/playbooks/deploy/metadata.obsah.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ variables:
parameter: --bmc-redfish-verify-ssl
help: Verify SSL certificates for Redfish BMC connections.
type: Boolean
cloud_connector_http_proxy:
parameter: --cloud-connector-http-proxy
help: HTTP proxy URL for the cloud connector rhcd service.

constraints:
required_together:
Expand Down
35 changes: 35 additions & 0 deletions src/roles/check_cloud_connector/tasks/main.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
- name: Verify cloud-connector is not used with iop
ansible.builtin.assert:
that:
- "'iop' not in enabled_features"
fail_msg: >-
The cloud-connector feature cannot be used together with the iop feature.
Remove one of them with --remove-feature before deploying.

- name: Check that consumer certificate exists
ansible.builtin.stat:
path: /etc/pki/consumer/cert.pem
register: check_cloud_connector_consumer_cert

- name: Verify consumer certificate exists
ansible.builtin.assert:
that:
- check_cloud_connector_consumer_cert.stat.exists
fail_msg: >-
/etc/pki/consumer/cert.pem not found.
The system must be registered with subscription-manager.

- name: Check that yggdrasil-worker-forwarder package is available
ansible.builtin.command: dnf info yggdrasil-worker-forwarder
changed_when: false
failed_when: false
register: check_cloud_connector_pkg_check

- name: Verify yggdrasil-worker-forwarder is available
ansible.builtin.assert:
that:
- check_cloud_connector_pkg_check.rc == 0
fail_msg: >-
The yggdrasil-worker-forwarder package is not available.
Ensure the appropriate repository is enabled.
5 changes: 5 additions & 0 deletions src/roles/checks/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
- check_database_connection
- check_system_requirements

- name: Run cloud connector checks
ansible.builtin.include_role:
name: check_cloud_connector
when: "'cloud-connector' in enabled_features"

- name: Run database index integrity checks
ansible.builtin.include_role:
name: check_database_index
Expand Down
7 changes: 7 additions & 0 deletions src/roles/cloud_connector/defaults/main.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
cloud_connector_url: "https://{{ ansible_facts['fqdn'] }}"
cloud_connector_admin_user: admin
cloud_connector_admin_password: changeme # noqa: no-static-secrets
cloud_connector_service_user: cloud_connector_admin_user
Comment thread
jeremylenz marked this conversation as resolved.
Outdated
cloud_connector_service_password: changeme # noqa: no-static-secrets
cloud_connector_config_file: /etc/rhc/workers/foreman_rh_cloud.toml
12 changes: 12 additions & 0 deletions src/roles/cloud_connector/handlers/main.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
- name: Update system CA trust
ansible.builtin.command: update-ca-trust
changed_when: true # noqa: no-changed-when
listen: Foreman CA changed

- name: Restart rhcd
ansible.builtin.systemd_service:
name: rhcd
state: restarted
daemon_reload: true
Comment thread
jeremylenz marked this conversation as resolved.
listen: Foreman CA changed
17 changes: 17 additions & 0 deletions src/roles/cloud_connector/tasks/http_proxy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
- name: Create systemd drop-in directory for rhcd
ansible.builtin.file:
state: directory
path: /etc/systemd/system/rhcd.service.d
owner: root
group: root
mode: '0755'

- name: Deploy HTTP proxy systemd drop-in for rhcd
ansible.builtin.template:
src: proxy.conf.j2
dest: /etc/systemd/system/rhcd.service.d/proxy.conf
owner: root
group: root
mode: '0644'
notify: Restart rhcd
110 changes: 110 additions & 0 deletions src/roles/cloud_connector/tasks/main.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
---
- name: Install rhc and yggdrasil-worker-forwarder
ansible.builtin.package:
name:
- rhc
Comment thread
jeremylenz marked this conversation as resolved.
- yggdrasil-worker-forwarder
disable_plugin: foreman-protector

- name: Create cloud connector role
theforeman.foreman.role:
name: Cloud Connector
filters:
- permissions:
- dispatch_cloud_requests
server_url: "{{ cloud_connector_url }}"
username: "{{ cloud_connector_admin_user }}"
password: "{{ cloud_connector_admin_password }}"
ca_path: "{{ foreman_ca_certificate }}"
state: present

- name: Create cloud connector service user
theforeman.foreman.user:
login: "{{ cloud_connector_service_user }}"
user_password: "{{ cloud_connector_service_password }}"
mail: "{{ cloud_connector_service_user }}@localhost"
auth_source: Internal
roles:
- Cloud Connector
admin: false
server_url: "{{ cloud_connector_url }}"
username: "{{ cloud_connector_admin_user }}"
password: "{{ cloud_connector_admin_password }}"
ca_path: "{{ foreman_ca_certificate }}"
state: present

- name: Configure foreman-rh-cloud worker
ansible.builtin.template:
src: foreman_rh_cloud.toml.j2
dest: "{{ cloud_connector_config_file }}"
owner: root
group: root
mode: '0640'
notify: Restart rhcd

- name: Create rhcd worker script
ansible.builtin.copy:
dest: /usr/libexec/rhc/foreman-rh-cloud-worker
content: |
#!/bin/bash

CONFIG_FILE="{{ cloud_connector_config_file }}" exec /usr/libexec/yggdrasil-worker-forwarder
owner: root
group: root
mode: '0755'

- name: Add Foreman CA to system trust store

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.

Is this how we do it today? This is an anti-pattern we have been trying to avoid.

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.

foreman-installer does the same thing via puppet-certs:

We need it here because yggdrasil-worker-forwarder is a Go binary that uses the OS trust store — no way to pass it a CA path. That said, we could move this into the certificates role so it's done once globally instead of per-feature. Would that be cleaner?

@jeremylenz jeremylenz Jun 12, 2026

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.

I was apprehensive about this too. I figured we can change it later..
Claude reply above

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.

These are both 404s because they were moved quite a bit ago. I think we need to consider udpating yggdrasil-worker-forwarder vs. starting this trend of relying on the system store. Let's phone a friend for another opinion. @evgeni ☎️

@jeremylenz jeremylenz Jun 15, 2026

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.

We need to update it anyway to use DBUS (so it can run on RHEL 10), so we can probably just tack that change on there.

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.

https://github.com/theforeman/yggdrasil-worker-forwarder doesn't seem to use Github Issues, so I will create an internal Jira for that.

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.

I updated the description of https://redhat.atlassian.net/browse/SAT-27307. However, ideally I would like this PR to be merged and use the "incorrect" approach temporarily, to move things forward,.

ansible.builtin.copy:
src: "{{ foreman_ca_certificate }}"
dest: /etc/pki/ca-trust/source/anchors/foreman-ca.pem
remote_src: true
owner: root
group: root
mode: '0644'
notify: Foreman CA changed

- name: Ensure rhcd started and enabled
ansible.builtin.service:
name: rhcd
state: started
enabled: true

- name: Read client ID from CN of consumer certificate
ansible.builtin.command: openssl x509 -in /etc/pki/consumer/cert.pem -subject -noout
register: cloud_connector_cert_output
changed_when: false

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.

Same as above but opposite. Idempotency?

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.

This is correct — changed_when: false means "this command never changes anything," which is right for a read-only openssl x509 call. Standard Ansible pattern for info-gathering tasks.


- name: Set rhc_instance_id in Foreman
Comment thread
jeremylenz marked this conversation as resolved.
theforeman.foreman.setting:
name: rhc_instance_id
value: "{{ cloud_connector_client_id }}"
server_url: "{{ cloud_connector_url }}"
username: "{{ cloud_connector_admin_user }}"
password: "{{ cloud_connector_admin_password }}"
ca_path: "{{ foreman_ca_certificate }}"
vars:
cloud_connector_client_id: "{{ cloud_connector_cert_output.stdout | regex_search('CN\\s?=\\s?([a-z0-9-]+)', '\\1') | first }}"

- name: Enable automatic inventory upload
theforeman.foreman.setting:
name: allow_auto_inventory_upload
value: true
server_url: "{{ cloud_connector_url }}"
username: "{{ cloud_connector_admin_user }}"
password: "{{ cloud_connector_admin_password }}"
ca_path: "{{ foreman_ca_certificate }}"

- name: Announce to Sources
ansible.builtin.uri:
url: "{{ cloud_connector_url }}/api/v2/rh_cloud/announce_to_sources"
user: "{{ cloud_connector_admin_user }}"
password: "{{ cloud_connector_admin_password }}"
method: POST
ca_path: "{{ foreman_ca_certificate }}"
force_basic_auth: true
body_format: json
status_code: [200, 201]

- name: Configure HTTP proxy for rhcd
ansible.builtin.include_tasks: http_proxy.yaml
when: cloud_connector_http_proxy is defined
8 changes: 8 additions & 0 deletions src/roles/cloud_connector/templates/foreman_rh_cloud.toml.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
exec = "/usr/libexec/yggdrasil-worker-forwarder"
protocol = "grpc"
env = [
"FORWARDER_USER={{ cloud_connector_service_user }}",
"FORWARDER_PASSWORD={{ cloud_connector_service_password }}",
"FORWARDER_URL={{ cloud_connector_url }}/api/v2/rh_cloud/cloud_request",
"FORWARDER_HANDLER=foreman_rh_cloud"
]
3 changes: 3 additions & 0 deletions src/roles/cloud_connector/templates/proxy.conf.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[Service]
Environment=HTTPS_PROXY={{ cloud_connector_http_proxy }}
Environment=NO_PROXY={{ cloud_connector_url | ansible.builtin.urlsplit('hostname') }}
9 changes: 9 additions & 0 deletions src/vars/base.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,12 @@ backup_foreman_oauth_consumer_secret: "{{ foreman_oauth_consumer_secret }}"
backup_foreman_ca_certificate: "{{ foreman_ca_certificate }}"
backup_foreman_client_certificate: "{{ foreman_client_certificate }}"
backup_foreman_client_key: "{{ foreman_client_key }}"

cloud_connector_url: "{{ foreman_url }}"
cloud_connector_admin_user: "{{ foreman_initial_admin_username }}"
cloud_connector_admin_password: "{{ foreman_initial_admin_password }}"
cloud_connector_service_user: cloud_connector_user
cloud_connector_service_password_file: "{{ obsah_state_path }}/cloud-connector-service-password"
cloud_connector_service_password: >-
{{ lookup('ansible.builtin.password', cloud_connector_service_password_file,
chars=['ascii_letters', 'digits'], length=32) }}
38 changes: 38 additions & 0 deletions tests/cloud_connector_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import pytest

pytestmark = pytest.mark.feature("cloud-connector")


def test_rhc_package_installed(server):
assert server.package("rhc").is_installed


def test_yggdrasil_worker_forwarder_package_installed(server):
assert server.package("yggdrasil-worker-forwarder").is_installed


def test_workers_directory_exists(server):
workers_dir = server.file("/etc/rhc/workers")
assert workers_dir.is_directory
assert workers_dir.mode == 0o755


def test_worker_config_exists(server):
config = server.file("/etc/rhc/workers/foreman_rh_cloud.toml")
assert config.is_file
assert config.mode == 0o640
assert config.contains("FORWARDER_HANDLER=foreman_rh_cloud")
assert config.contains("/api/v2/rh_cloud/cloud_request")


def test_worker_script_exists(server):
script = server.file("/usr/libexec/rhc/foreman-rh-cloud-worker")
assert script.is_file
assert script.mode == 0o755
assert script.contains("yggdrasil-worker-forwarder")


def test_rhcd_service_running(server):
rhcd = server.service("rhcd")
assert rhcd.is_running
assert rhcd.is_enabled
Loading