Skip to content
Draft
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 development/ansible.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ host_key_checking = False
stdout_callback=debug
stderr_callback=debug
roles_path = ./roles:../src/roles
filter_plugins = ../src/filter_plugins
display_skipped_hosts = no
12 changes: 12 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 @@ -55,6 +60,7 @@
foreman_development_enabled_plugins: "{{ foreman_development_enabled_plugins + ['foreman_ansible'] }}"
roles:
- role: pre_install
- role: check_features
- role: certificates
- role: postgresql
- role: redis
Expand All @@ -72,6 +78,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_user: "{{ foreman_development_admin_user }}"
cloud_connector_password: "{{ foreman_development_admin_password }}"
post_tasks:
- name: Stop Foreman development service
ansible.builtin.include_role:
Expand Down
7 changes: 7 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,13 @@ 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.
foreman_development_preserve_plugin_branches:
parameter: --preserve-plugin-branches
help: Skip git checkout for plugins, preserving local branches and changes.
type: Boolean

include:
- _flavor_features
Expand Down
3 changes: 2 additions & 1 deletion forge
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ OBSAH_BASE=$(dirname $(readlink -f $0))
OBSAH_DATA=${OBSAH_BASE}/development
OBSAH_INVENTORY=${OBSAH_BASE}/inventories
OBSAH_STATE=${OBSAH_BASE}/.var/lib/foremanctl
export OBSAH_NAME OBSAH_DATA OBSAH_INVENTORY OBSAH_STATE
OBSAH_PERSIST_PARAMS=true
export OBSAH_NAME OBSAH_DATA OBSAH_INVENTORY OBSAH_STATE OBSAH_PERSIST_PARAMS

ANSIBLE_COLLECTIONS_PATH=${OBSAH_BASE}/build/collections/forge
ANSIBLE_COLLECTIONS_SCAN_SYS_PATH=false
Expand Down
6 changes: 6 additions & 0 deletions src/features.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ rh-cloud:
hammer: foreman_rh_cloud
dependencies:
- katello
cloud-connector:
description: Cloud Connector for Red Hat Hybrid Cloud Console
dependencies:
- rh-cloud
conflicts:
- iop
iop:
description: iop services
dependencies:
Expand Down
16 changes: 16 additions & 0 deletions src/filter_plugins/foremanctl.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,21 @@ def invalid_features(features):
return [feature for feature in features if feature not in FEATURE_MAP]


def conflicting_features(enabled_features):
"""Return list of conflict descriptions for any mutually exclusive features."""
conflicts = []
seen = set()
enabled_set = set(enabled_features)
for feature in enabled_features:
for conflict in FEATURE_MAP.get(feature, {}).get('conflicts', []):
if conflict in enabled_set:
pair = tuple(sorted([feature, conflict]))
if pair not in seen:
seen.add(pair)
conflicts.append(f"{pair[0]} conflicts with {pair[1]}")
return conflicts


def hammer_plugins(value):
dependencies = list(get_dependencies(filter_features(value)))
plugins = [FEATURE_MAP.get(feature, {}).get('hammer') for feature in filter_features(value + dependencies)]
Expand Down Expand Up @@ -127,5 +142,6 @@ def filters(self):
'available_foreman_proxy_plugins': available_foreman_proxy_plugins,
'list_all_features': list_all_features,
'invalid_features': invalid_features,
'conflicting_features': conflicting_features,
'has_feature': has_feature,
}
3 changes: 3 additions & 0 deletions src/playbooks/deploy/deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,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
38 changes: 38 additions & 0 deletions src/roles/check_cloud_connector/tasks/main.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
- name: Check cloud-connector prerequisites
when: "'cloud-connector' in enabled_features"
block:
- 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.
11 changes: 11 additions & 0 deletions src/roles/check_features/tasks/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,14 @@
vars:
found_invalid_features: "{{ features | invalid_features }}"
when: features | length > 0

- name: Validate no conflicting features
ansible.builtin.assert:
that:
- found_conflicting_features | length == 0
fail_msg: |
ERROR: Conflicting features enabled: {{ found_conflicting_features | join(', ') }}

Remove one of the conflicting features with --remove-feature before deploying.
vars:
found_conflicting_features: "{{ enabled_features | conflicting_features }}"
1 change: 1 addition & 0 deletions src/roles/checks/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
ansible.builtin.include_tasks: execute_check.yml
loop:
- check_features
- check_cloud_connector
- check_hostname
- check_database_connection
- check_system_requirements
Expand Down
5 changes: 5 additions & 0 deletions src/roles/cloud_connector/defaults/main.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
cloud_connector_url: "https://{{ ansible_facts['fqdn'] }}"
cloud_connector_user: admin
cloud_connector_password: changeme
cloud_connector_config_file: /etc/rhc/workers/foreman_rh_cloud.toml
6 changes: 6 additions & 0 deletions src/roles/cloud_connector/handlers/main.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
- name: Restart rhcd
ansible.builtin.service:
name: rhcd
state: restarted
daemon_reload: true
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
103 changes: 103 additions & 0 deletions src/roles/cloud_connector/tasks/main.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
---
- name: Install rhc and yggdrasil-worker-forwarder
ansible.builtin.package:
name:
- rhc
- yggdrasil-worker-forwarder
disable_plugin: foreman-protector

- name: Create workers directory
ansible.builtin.file:
state: directory
path: /etc/rhc/workers
owner: root
group: root
mode: '0755'

- 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
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: Restart rhcd

- name: Update system CA trust
ansible.builtin.command: update-ca-trust
changed_when: true

- 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

- name: Set rhc_instance_id in Foreman
ansible.builtin.uri:
url: "{{ cloud_connector_url }}/api/settings/rhc_instance_id"
user: "{{ cloud_connector_user }}"
password: "{{ cloud_connector_password }}"
body:
setting:
value: "{{ cloud_connector_client_id }}"
method: PUT
ca_path: "{{ foreman_ca_certificate }}"
force_basic_auth: true
body_format: json
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
ansible.builtin.uri:
url: "{{ cloud_connector_url }}/api/settings/allow_auto_inventory_upload"
user: "{{ cloud_connector_user }}"
password: "{{ cloud_connector_password }}"
body:
setting:
value: true
method: PUT
ca_path: "{{ foreman_ca_certificate }}"
force_basic_auth: true
body_format: json

- name: Announce to Sources
ansible.builtin.uri:
url: "{{ cloud_connector_url }}/api/v2/rh_cloud/announce_to_sources"
user: "{{ cloud_connector_user }}"
password: "{{ cloud_connector_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_user }}",
"FORWARDER_PASSWORD={{ cloud_connector_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') }}
4 changes: 4 additions & 0 deletions src/vars/base.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,7 @@ foreman_proxy_oauth_consumer_secret: "{{ foreman_oauth_consumer_secret }}"
iop_core_foreman_url: "{{ foreman_url }}"
iop_core_foreman_oauth_consumer_key: "{{ foreman_oauth_consumer_key }}"
iop_core_foreman_oauth_consumer_secret: "{{ foreman_oauth_consumer_secret }}"

cloud_connector_url: "{{ foreman_url }}"
cloud_connector_user: "{{ foreman_initial_admin_username }}"
cloud_connector_password: "{{ foreman_initial_admin_password }}"
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
Loading