From a89493beca9a37809d8c21023077891f90d0eab7 Mon Sep 17 00:00:00 2001 From: Arvind Jangir Date: Sun, 14 Jun 2026 21:13:18 +0530 Subject: [PATCH 01/22] Initial scaffold for flavor foreman-proxy-content --- .github/workflows/test.yml | 52 +++++++++++++++++++ Vagrantfile | 10 ++++ development/playbooks/test/test.yaml | 2 +- src/features.d/content.yaml | 26 ++++++++++ src/features.yaml | 8 +++ src/filter_plugins/foremanctl.py | 6 +++ .../_flavor_features/metadata.obsah.yaml | 1 + src/playbooks/deploy/deploy.yaml | 20 ++++--- src/playbooks/deploy/metadata.obsah.yaml | 18 +++++++ src/roles/foreman_proxy/defaults/main.yaml | 11 +++- src/roles/foreman_proxy/tasks/main.yaml | 1 + .../foreman_proxy/templates/settings.yml.j2 | 2 +- src/roles/httpd/defaults/main.yml | 8 +++ src/roles/httpd/tasks/main.yml | 20 ++++++- .../httpd/templates/pulpcore-vhost.conf.j2 | 26 ++++++++++ src/roles/pulp/defaults/main.yaml | 13 +++-- src/roles/pulp/tasks/main.yaml | 2 + src/vars/base.yaml | 1 + src/vars/database.yml | 6 +-- src/vars/flavors/foreman-proxy-content.yml | 9 ++++ tests/candlepin_test.py | 5 ++ tests/conftest.py | 8 ++- tests/foreman_api_test.py | 17 +++--- tests/foreman_compute_resources_test.py | 2 + tests/foreman_plugins_test.py | 2 + tests/foreman_test.py | 2 + tests/hammer_test.py | 5 ++ tests/httpd_test.py | 7 +++ tests/postgresql_test.py | 26 ++++++---- 29 files changed, 280 insertions(+), 36 deletions(-) create mode 100644 src/features.d/content.yaml create mode 100644 src/roles/httpd/templates/pulpcore-vhost.conf.j2 create mode 100644 src/vars/flavors/foreman-proxy-content.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e98679216..f5cbaaebc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -372,6 +372,57 @@ jobs: limit-access-to-actor: true wait-timeout-minutes: 5 + foreman-proxy-content-tests: + runs-on: ubuntu-24.04 + name: "Tests (flavor: foreman-proxy-content)" + steps: + - uses: actions/checkout@v6 + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: '3.12' + - name: Setup libvirt for Vagrant + uses: voxpupuli/setup-vagrant@v0 + with: + configure_dns: true + - name: Install Ansible + run: pip install --upgrade ansible-core + - name: Setup environment + run: ./setup-environment + - name: Start VMs + run: | + ./forge vms start --vms proxy + - name: Run deployment + run: | + OBSAH_ALLOW_EXTRA_VARS=true ./foremanctl deploy \ + --flavor foreman-proxy-content \ + --foreman-url https://quadlet.example.com \ + -e foremanctl_target=proxy + - name: Run tests + run: | + ./forge test --pytest-args="--server-hostname=proxy --flavor=foreman-proxy-content" + - name: Archive smoker report + if: ${{ always() }} + uses: actions/upload-artifact@v7 + with: + name: smoker-proxy-content + path: "/home/runner/smoker/report/" + - name: Generate sos reports + if: ${{ always() }} + run: ./forge sos + - name: Archive sos reports + if: ${{ always() }} + uses: actions/upload-artifact@v7 + with: + name: sosreport-proxy-content + path: sos/ + - name: Setup upterm session + if: ${{ failure() }} + uses: owenthereal/action-upterm@v1 + with: + limit-access-to-actor: true + wait-timeout-minutes: 5 + # A dummy job that you can mark as a required check instead of each individual test test-suite: if: always() @@ -380,6 +431,7 @@ jobs: - devel-tests - upgrade - migration + - foreman-proxy-content-tests - ansible-lint - python-lint runs-on: ubuntu-latest diff --git a/Vagrantfile b/Vagrantfile index cce0a0cc1..b69ff07e2 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -48,6 +48,16 @@ Vagrant.configure("2") do |config| end end + config.vm.define "proxy" do |override| + override.vm.box = "centos/stream9" + override.vm.hostname = "proxy.#{DOMAIN}" + + override.vm.provider "libvirt" do |libvirt, provider| + libvirt.memory = 4096 + libvirt.cpus = 4 + end + end + # Load user-local box definitions from boxes.yaml (gitignored) boxes_yaml = File.join(__dir__, 'boxes.yaml') if File.exist?(boxes_yaml) diff --git a/development/playbooks/test/test.yaml b/development/playbooks/test/test.yaml index 9e1050b18..e60924ac0 100644 --- a/development/playbooks/test/test.yaml +++ b/development/playbooks/test/test.yaml @@ -1,7 +1,7 @@ --- - name: Install test dependencies hosts: - - quadlet + - "{{ foremanctl_target | default('quadlet') }}" become: true tasks: - name: Install testing dependencies diff --git a/src/features.d/content.yaml b/src/features.d/content.yaml new file mode 100644 index 000000000..dcb5d2ce3 --- /dev/null +++ b/src/features.d/content.yaml @@ -0,0 +1,26 @@ +--- +content/rpm: + description: RPM content type for Pulp + internal: true + dependencies: + - pulp +content/deb: + description: Debian content type for Pulp + internal: true + dependencies: + - pulp +content/container: + description: Container content type for Pulp + internal: true + dependencies: + - pulp +content/ansible: + description: Ansible content type for Pulp + internal: true + dependencies: + - pulp +content/python: + description: Python content type for Pulp + internal: true + dependencies: + - pulp diff --git a/src/features.yaml b/src/features.yaml index daf451bdc..4f82adcb4 100644 --- a/src/features.yaml +++ b/src/features.yaml @@ -5,6 +5,12 @@ foreman-proxy: description: Base Foreman Proxy hammer: description: Foreman CLI +pulp: + description: Pulp content management service + internal: true +candlepin: + description: Candlepin subscription management service + internal: true katello: description: Content and Subscription Management plugin for Foreman foreman: @@ -12,6 +18,8 @@ katello: hammer: katello dependencies: - tasks + - pulp + - candlepin google: description: Google Compute Engine plugin for Foreman hammer: foreman_google diff --git a/src/filter_plugins/foremanctl.py b/src/filter_plugins/foremanctl.py index dca8b790e..238658e82 100644 --- a/src/filter_plugins/foremanctl.py +++ b/src/filter_plugins/foremanctl.py @@ -117,6 +117,11 @@ def has_feature(features, feature): or feature in get_dependencies(list(features))) +def databases_for_features(databases, enabled_features): + """Return databases whose feature gate matches enabled_features.""" + return [db for db in databases if has_feature(enabled_features, db['feature'])] + + def to_postgresql_databases(databases): return [{'name': db['database'], 'owner': db['user']} for db in databases] @@ -138,6 +143,7 @@ def filters(self): 'list_all_features': list_all_features, 'invalid_features': invalid_features, 'has_feature': has_feature, + 'databases_for_features': databases_for_features, 'to_postgresql_databases': to_postgresql_databases, 'to_postgresql_users': to_postgresql_users, } diff --git a/src/playbooks/_flavor_features/metadata.obsah.yaml b/src/playbooks/_flavor_features/metadata.obsah.yaml index 4a7ba405d..02e614b4c 100644 --- a/src/playbooks/_flavor_features/metadata.obsah.yaml +++ b/src/playbooks/_flavor_features/metadata.obsah.yaml @@ -4,6 +4,7 @@ variables: help: Base flavor to use in this deployment. choices: - katello + - foreman-proxy-content features: parameter: --add-feature help: Additional features to enable in this deployment. diff --git a/src/playbooks/deploy/deploy.yaml b/src/playbooks/deploy/deploy.yaml index 1454ddab1..12c46a4ae 100644 --- a/src/playbooks/deploy/deploy.yaml +++ b/src/playbooks/deploy/deploy.yaml @@ -1,7 +1,7 @@ --- -- name: Setup quadlet demo machine +- name: Setup quadlet machine hosts: - - quadlet + - "{{ foremanctl_target | default('quadlet') }}" become: true vars_files: - "../../vars/defaults.yml" @@ -17,8 +17,10 @@ - role: checks vars: checks_databases: "{{ all_databases }}" + # TODO: Add tar_file check/extractation step for certificates_source == 'proxy' - role: certificates when: "certificates_source in ['default', 'custom_server']" + # TODO: remove proxy condition and add tar check - role: certificate_checks vars: certificate_checks_certificate: "{{ server_certificate }}" @@ -26,12 +28,15 @@ certificate_checks_ca: "{{ server_ca_certificate }}" - role: postgresql when: - - database_mode == 'internal' + - database_mode == 'internal' - valkey - - candlepin + - role: candlepin + when: enabled_features | has_feature('candlepin') - httpd - - pulp - - foreman + - role: pulp + when: enabled_features | has_feature('pulp') + - role: foreman + when: "'foreman' in enabled_features" - role: systemd_target - role: iop_core when: @@ -43,4 +48,5 @@ - role: hammer when: - "'hammer' in enabled_features" - - post_install + - role: post_install + when: "'foreman' in enabled_features" diff --git a/src/playbooks/deploy/metadata.obsah.yaml b/src/playbooks/deploy/metadata.obsah.yaml index 2b04775e3..f63181794 100644 --- a/src/playbooks/deploy/metadata.obsah.yaml +++ b/src/playbooks/deploy/metadata.obsah.yaml @@ -56,6 +56,24 @@ variables: parameter: --bmc-redfish-verify-ssl help: Verify SSL certificates for Redfish BMC connections. type: Boolean + foreman_proxy_foreman_server_url: + parameter: --foreman-url + help: URL of the parent Foreman server this proxy connects to. + foreman_proxy_trusted_hosts: + help: FQDN of a host trusted by the smart proxy. Can be specified multiple times. + action: append_unique + foreman_oauth_consumer_key: + parameter: --oauth-consumer-key + help: OAuth consumer key. + foreman_oauth_consumer_secret: + parameter: --oauth-consumer-secret + help: OAuth consumer secret. + pulp_rhsm_url: + parameter: --rhsm-url + help: RHSM URL for Pulp smart proxy content mirroring. + pulp_smart_proxy_auth_methods: + parameter: --smart-proxy-auth-methods + help: Authentication methods for Pulp smart proxy. constraints: required_together: diff --git a/src/roles/foreman_proxy/defaults/main.yaml b/src/roles/foreman_proxy/defaults/main.yaml index 4791507b1..c5aaa1676 100644 --- a/src/roles/foreman_proxy/defaults/main.yaml +++ b/src/roles/foreman_proxy/defaults/main.yaml @@ -7,9 +7,12 @@ foreman_proxy_https_port: 8443 foreman_proxy_url: "https://{{ foreman_proxy_name }}:{{ foreman_proxy_https_port }}" # Settings -foreman_proxy_trusted_hosts: +foreman_proxy_default_trusted_hosts: - "{{ foreman_proxy_name }}" +foreman_proxy_trusted_hosts: [] +foreman_proxy_all_trusted_hosts: "{{ (foreman_proxy_default_trusted_hosts + foreman_proxy_trusted_hosts) | unique | list }}" + foreman_proxy_base_features: - logs foreman_proxy_plugins: [] @@ -17,8 +20,12 @@ foreman_proxy_features: "{{ foreman_proxy_base_features + foreman_proxy_plugins foreman_proxy_available_features: "{{ [] | available_foreman_proxy_plugins }}" foreman_proxy_disabled_features: "{{ foreman_proxy_available_features | difference(foreman_proxy_features) }}" -foreman_proxy_foreman_server_url: "https://{{ ansible_facts['fqdn'] }}" +# In case of standalone proxy, we need to add foreman server URL not host fqdn +foreman_proxy_with_foreman: "{{ 'foreman' in enabled_features }}" +foreman_proxy_foreman_server_url: "{{ foreman_proxy_with_foreman | ternary('https://{{ ansible_facts['fqdn'] }}', foreman_proxy_foreman_server_url) }}" # BMC settings foreman_proxy_bmc_ipmi_implementation: ipmitool foreman_proxy_bmc_redfish_verify_ssl: true + +foreman_proxy_with_pulp_mirror: "{{ pulp_mirror | default(false) }}" diff --git a/src/roles/foreman_proxy/tasks/main.yaml b/src/roles/foreman_proxy/tasks/main.yaml index 8033c9457..6e990cc88 100644 --- a/src/roles/foreman_proxy/tasks/main.yaml +++ b/src/roles/foreman_proxy/tasks/main.yaml @@ -74,6 +74,7 @@ oauth1_consumer_key: "{{ foreman_proxy_oauth_consumer_key }}" oauth1_consumer_secret: "{{ foreman_proxy_oauth_consumer_secret }}" ca_path: "{{ foreman_proxy_foreman_ca_certificate | default(omit) }}" + when: not foreman_proxy_with_pulp_mirror - name: Flush handlers to restart services ansible.builtin.meta: flush_handlers diff --git a/src/roles/foreman_proxy/templates/settings.yml.j2 b/src/roles/foreman_proxy/templates/settings.yml.j2 index e7dbe4fbb..867d83679 100644 --- a/src/roles/foreman_proxy/templates/settings.yml.j2 +++ b/src/roles/foreman_proxy/templates/settings.yml.j2 @@ -1,7 +1,7 @@ :settings_directory: /etc/foreman-proxy/settings.d :foreman_url: {{ foreman_proxy_foreman_server_url }} -:trusted_hosts: {{ foreman_proxy_trusted_hosts }} +:trusted_hosts: {{ foreman_proxy_all_trusted_hosts }} :https_port: {{ foreman_proxy_https_port }} :ssl_ca_file: /etc/foreman-proxy/ssl_ca.pem diff --git a/src/roles/httpd/defaults/main.yml b/src/roles/httpd/defaults/main.yml index 7f4f7b35a..1dad1a7f2 100644 --- a/src/roles/httpd/defaults/main.yml +++ b/src/roles/httpd/defaults/main.yml @@ -5,6 +5,10 @@ httpd_foreman_backend: http://localhost:3000 httpd_pub_dir: /var/www/html/pub httpd_enabled_pulp_snippets: [] +httpd_proxy_foreman_url: "{{ foreman_proxy_foreman_server_url }}" +httpd_client_certificate: "{{ client_certificate }}" +httpd_client_key: "{{ client_key }}" + # MPM event module defaults httpd_server_limit: 25 httpd_start_servers: 2 @@ -20,3 +24,7 @@ httpd_ipa_manage_sssd: true httpd_ipa_keytab: /etc/httpd/conf/http.keytab httpd_ipa_pam_service: "{{ external_authentication_pam_service | default('foreman') }}" httpd_ipa_gssapi_local_name: true + +httpd_with_foreman: "{{ 'foreman' in enabled_features }}" +httpd_with_pulpcore: "{{ not httpd_with_foreman and not httpd_with_pulp_mirror }}" +httpd_with_pulp_mirror: "{{ pulp_mirror | default(false) }}" diff --git a/src/roles/httpd/tasks/main.yml b/src/roles/httpd/tasks/main.yml index bbc3d633d..cbe160156 100644 --- a/src/roles/httpd/tasks/main.yml +++ b/src/roles/httpd/tasks/main.yml @@ -70,17 +70,32 @@ - name: Configure external authentication ansible.builtin.include_tasks: "external_auth/{{ httpd_external_authentication }}.yml" - when: httpd_external_authentication in ['ipa', 'ipa_with_api'] + when: + - httpd_with_foreman + - httpd_external_authentication in ['ipa', 'ipa_with_api'] - name: Remove external authentication configuration if not enabled ansible.builtin.include_tasks: external_auth/cleanup.yml - when: httpd_external_authentication is none + when: + - httpd_with_foreman + - httpd_external_authentication is none + - name: Configure foreman vhost ansible.builtin.template: src: foreman-vhost.conf.j2 dest: /etc/httpd/conf.d/foreman.conf mode: "0644" + when: httpd_with_foreman + notify: + - Restart httpd + +- name: Configure pulpcore vhost + ansible.builtin.template: + src: pulpcore-vhost.conf.j2 + dest: /etc/httpd/conf.d/pulpcore.conf + mode: "0644" + when: httpd_with_pulp_mirror notify: - Restart httpd @@ -89,6 +104,7 @@ src: foreman-ssl-vhost.conf.j2 dest: /etc/httpd/conf.d/foreman-ssl.conf mode: "0644" + when: httpd_with_foreman notify: - Restart httpd diff --git a/src/roles/httpd/templates/pulpcore-vhost.conf.j2 b/src/roles/httpd/templates/pulpcore-vhost.conf.j2 new file mode 100644 index 000000000..b17b9317d --- /dev/null +++ b/src/roles/httpd/templates/pulpcore-vhost.conf.j2 @@ -0,0 +1,26 @@ + + ServerName {{ ansible_facts['fqdn'] }} + + ErrorLog "/var/log/httpd/mirror_error.log" + ServerSignature Off + CustomLog "/var/log/httpd/mirror_access.log" combined + + RequestHeader set X-FORWARDED-PROTO "http" + + + RequestHeader unset X-CLIENT-CERT + RequestHeader set X-CLIENT-CERT "%{SSL_CLIENT_CERT}s" env=SSL_CLIENT_CERT + RequestHeader set X-FORWARDED-PROTO expr=%{REQUEST_SCHEME} + ProxyPass {{ httpd_pulp_content_backend }}/pulp/content disablereuse=on timeout=600 + ProxyPassReverse {{ httpd_pulp_content_backend }}/pulp/content + + + Alias /pub {{ httpd_pub_dir }} + + + Options +FollowSymLinks +Indexes + Require all granted + + + AddDefaultCharset UTF-8 + diff --git a/src/roles/pulp/defaults/main.yaml b/src/roles/pulp/defaults/main.yaml index f27b74d83..43fbcde58 100644 --- a/src/roles/pulp/defaults/main.yaml +++ b/src/roles/pulp/defaults/main.yaml @@ -24,9 +24,12 @@ pulp_content_origin: "http://{{ ansible_facts['fqdn'] }}:24816" pulp_pulp_url: "http://{{ ansible_facts['fqdn'] }}:24817" pulp_enable_analytics: false +pulp_mirror: false +pulp_rhsm_url: "https://{{ ansible_facts['fqdn'] }}/rhsm" +pulp_smart_proxy_auth_methods: "['client_certificate']" -pulp_default_import_paths: ["/var/lib/pulp/sync_imports", "/var/lib/pulp/imports"] -pulp_default_export_paths: ["/var/lib/pulp/exports"] +pulp_default_import_paths: "{{ pulp_mirror | ternary(['/var/lib/pulp/sync_imports'], ['/var/lib/pulp/sync_imports', '/var/lib/pulp/imports']) }}" +pulp_default_export_paths: "{{ pulp_mirror | ternary([], ['/var/lib/pulp/exports']) }}" pulp_import_paths: [] pulp_export_paths: [] @@ -69,7 +72,11 @@ pulp_settings_other_env: PULP_REMOTE_USER_ENVIRON_NAME: "HTTP_REMOTE_USER" PULP_REST_FRAMEWORK__DEFAULT_AUTHENTICATION_CLASSES: >- ['rest_framework.authentication.SessionAuthentication', 'pulpcore.app.authentication.PulpRemoteUserAuthentication'] - PULP_SMART_PROXY_PULP_URL: "{{ pulp_pulp_url }}" + SMART_PROXY_PULP_URL: "{{ pulp_pulp_url }}" + SMART_PROXY_MIRROR: "{{ pulp_mirror }}" + SMART_PROXY_RHSM_URL: "{{ pulp_rhsm_url }}" + SMART_PROXY_AUTH_METHODS: >- + {{ pulp_smart_proxy_auth_methods }} PULP_API_WORKERS: "{{ pulp_api_service_worker_count }}" PULP_CONTENT_WORKERS: "{{ pulp_content_service_worker_count }}" PULP_TOKEN_AUTH_DISABLED: "true" diff --git a/src/roles/pulp/tasks/main.yaml b/src/roles/pulp/tasks/main.yaml index a83a6f17c..013a018a6 100644 --- a/src/roles/pulp/tasks/main.yaml +++ b/src/roles/pulp/tasks/main.yaml @@ -32,6 +32,7 @@ state: directory mode: "0755" loop: "{{ pulp_default_export_paths + pulp_export_paths }}" + when: not pulp_mirror - name: Create DB password secret containers.podman.podman_secret: @@ -244,6 +245,7 @@ Type=oneshot RemainAfterExit=yes TimeoutStartSec=10m + when: not pulp_mirror - name: Run daemon reload to load service files ansible.builtin.systemd: diff --git a/src/vars/base.yaml b/src/vars/base.yaml index de4d8d610..366d95fd0 100644 --- a/src/vars/base.yaml +++ b/src/vars/base.yaml @@ -33,6 +33,7 @@ pulp_storage_path: /var/lib/pulp pulp_content_origin: "https://{{ ansible_facts['fqdn'] }}" pulp_pulp_url: "https://{{ ansible_facts['fqdn'] }}" pulp_plugins: "{{ enabled_features | select('contains', 'content/') | map('replace', 'content/', 'pulp_') | list }}" +pulp_rhsm_url: "{{ foreman_proxy_foreman_server_url }}/rhsm" hammer_ca_certificate: "{{ server_ca_certificate }}" hammer_plugins: "{{ enabled_features | features_to_hammer_plugins }}" diff --git a/src/vars/database.yml b/src/vars/database.yml index e788fba22..7efcfb7d8 100644 --- a/src/vars/database.yml +++ b/src/vars/database.yml @@ -91,7 +91,7 @@ databases: password: "{{ candlepin_database_password }}" ssl_mode: "{{ candlepin_database_ssl_mode }}" ssl_ca: "{{ candlepin_database_ssl_ca }}" - feature: katello + feature: candlepin - name: pulp database: "{{ pulp_database_name }}" host: "{{ pulp_database_host }}" @@ -100,7 +100,7 @@ databases: password: "{{ pulp_database_password }}" ssl_mode: "{{ pulp_database_ssl_mode }}" ssl_ca: "{{ pulp_database_ssl_ca }}" - feature: katello + feature: pulp - name: iop_advisor database: "{{ iop_advisor_database_name }}" host: "{{ iop_advisor_database_host }}" @@ -138,7 +138,7 @@ databases: feature: iop all_databases: >- - {{ databases | selectattr('feature', 'in', enabled_features) | list }} + {{ databases | databases_for_features(enabled_features) }} postgresql_databases: >- {{ all_databases | to_postgresql_databases }} diff --git a/src/vars/flavors/foreman-proxy-content.yml b/src/vars/flavors/foreman-proxy-content.yml new file mode 100644 index 000000000..be21b769c --- /dev/null +++ b/src/vars/flavors/foreman-proxy-content.yml @@ -0,0 +1,9 @@ +flavor_features: + - foreman-proxy + - content/rpm + - content/deb + - content/container + - content/ansible + - content/python + +pulp_mirror: true diff --git a/tests/candlepin_test.py b/tests/candlepin_test.py index a61b4f920..5d6b3c8e6 100644 --- a/tests/candlepin_test.py +++ b/tests/candlepin_test.py @@ -1,3 +1,8 @@ +import pytest + +pytestmark = pytest.mark.feature('katello') + + def assert_secret_content(server, secret_name, secret_value): secret = server.run(f'podman secret inspect --format {"{{.SecretData}}"} --showsecret {secret_name}') assert secret.succeeded diff --git a/tests/conftest.py b/tests/conftest.py index b6f954aa1..c84f7f2c1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -44,6 +44,10 @@ def enabled_features(self): return set(feature for feature, status, _desc in self.features if status == 'enabled') +def pytest_addoption(parser): + parser.addoption("--server-hostname", action="store", default="quadlet", help="Hostname of the server VM to test against") + + @pytest.fixture(scope="module") def enabled_features(pytestconfig): return pytestconfig.user_parameters.enabled_features @@ -55,8 +59,8 @@ def fixture_dir(): @pytest.fixture(scope="module") -def server_hostname(): - return 'quadlet' +def server_hostname(pytestconfig): + return pytestconfig.getoption("server_hostname") @pytest.fixture(scope="module") diff --git a/tests/foreman_api_test.py b/tests/foreman_api_test.py index 4429dbbf3..990d6c968 100644 --- a/tests/foreman_api_test.py +++ b/tests/foreman_api_test.py @@ -1,3 +1,8 @@ +import pytest + +pytestmark = pytest.mark.feature('foreman') + + def test_foreman_organization(organization): assert organization @@ -5,7 +10,7 @@ def test_foreman_organization(organization): def test_foreman_product(product): assert product - +@pytest.mark.feature('katello') def test_foreman_yum_repository(yum_repository, foremanapi, local_request): assert yum_repository foremanapi.resource_action('repositories', 'sync', {'id': yum_repository['id']}) @@ -13,23 +18,23 @@ def test_foreman_yum_repository(yum_repository, foremanapi, local_request): assert local_request.get(f'{repo_url}/repodata/repomd.xml', verify=False) assert local_request.get(f'{repo_url}/Packages/b/bear-4.1-1.noarch.rpm', verify=False) - +@pytest.mark.feature('katello') def test_foreman_file_repository(file_repository, foremanapi, local_request): assert file_repository foremanapi.resource_action('repositories', 'sync', {'id': file_repository['id']}) repo_url = file_repository['full_path'] assert local_request.get(f'{repo_url}/1.iso', verify=False) - +@pytest.mark.feature('katello') def test_foreman_container_repository(container_repository, foremanapi): assert container_repository foremanapi.resource_action('repositories', 'sync', {'id': container_repository['id']}) - +@pytest.mark.feature('katello') def test_foreman_lifecycle_environment(lifecycle_environment): assert lifecycle_environment - +@pytest.mark.feature('katello') def test_foreman_content_view(content_view, yum_repository, foremanapi): assert content_view foremanapi.update('content_views', {'id': content_view['id'], 'repository_ids': [yum_repository['id']]}) @@ -42,7 +47,7 @@ def test_foreman_content_view(content_view, yum_repository, foremanapi): foremanapi.resource_action('content_views', 'remove_from_environment', params={'id': content_view['id'], 'environment_id': environment_id}) foremanapi.delete('content_view_versions', version) - +@pytest.mark.feature('katello') def test_foreman_manifest(organization, foremanapi, fixture_dir): manifest_path = fixture_dir / 'manifest.zip' with open(manifest_path, 'rb') as manifest_file: diff --git a/tests/foreman_compute_resources_test.py b/tests/foreman_compute_resources_test.py index 6ed61b1de..db43d9985 100644 --- a/tests/foreman_compute_resources_test.py +++ b/tests/foreman_compute_resources_test.py @@ -1,5 +1,7 @@ import pytest +pytestmark = pytest.mark.feature('foreman') + @pytest.mark.parametrize("compute_resource", ['AzureRm', 'EC2', 'GCE', 'Libvirt', 'Openstack', 'Vmware']) def test_foreman_compute_resources(foremanapi, compute_resource): diff --git a/tests/foreman_plugins_test.py b/tests/foreman_plugins_test.py index b2c787ccb..bab7d595f 100644 --- a/tests/foreman_plugins_test.py +++ b/tests/foreman_plugins_test.py @@ -1,5 +1,7 @@ import pytest +pytestmark = pytest.mark.feature('foreman') + @pytest.mark.parametrize("foreman_plugin", ['foreman_azure_rm', 'foreman_google']) def test_foreman_compute_resources(foremanapi, foreman_plugin): diff --git a/tests/foreman_test.py b/tests/foreman_test.py index 174dd1f66..8dc443b8c 100644 --- a/tests/foreman_test.py +++ b/tests/foreman_test.py @@ -2,6 +2,8 @@ import pytest +pytestmark = pytest.mark.feature('foreman') + FOREMAN_HOST = 'localhost' FOREMAN_PORT = 3000 diff --git a/tests/hammer_test.py b/tests/hammer_test.py index 456f0ba62..6fa4a1997 100644 --- a/tests/hammer_test.py +++ b/tests/hammer_test.py @@ -1,3 +1,8 @@ +import pytest + +pytestmark = pytest.mark.feature('hammer') + + def test_hammer_ping(server): hammer = server.run("hammer ping") assert hammer.succeeded diff --git a/tests/httpd_test.py b/tests/httpd_test.py index c086e683c..b4cb76ec1 100644 --- a/tests/httpd_test.py +++ b/tests/httpd_test.py @@ -1,3 +1,5 @@ +import pytest + HTTP_HOST = 'localhost' HTTP_PORT = 80 HTTPS_PORT = 443 @@ -21,12 +23,14 @@ def test_https_port(server): assert httpd.port(HTTPS_PORT).is_reachable +@pytest.mark.feature('foreman') def test_http_foreman_ping(server, server_fqdn): cmd = server.run(f"{CURL_CMD} --write-out '%{{redirect_url}}' http://{server_fqdn}/api/v2/ping") assert cmd.succeeded assert cmd.stdout == f'https://{server_fqdn}/api/v2/ping' +@pytest.mark.feature('foreman') def test_https_foreman_ping(server, certificates, server_fqdn): cmd = server.run(f"{CURL_CMD} --cacert {certificates['server_ca_certificate']} --write-out '%{{http_code}}' https://{server_fqdn}/api/v2/ping") assert cmd.succeeded @@ -102,12 +106,14 @@ def test_https_pub_server_ca_certificate_downloadable(server, certificates, serv assert cmd.stdout == '200' +@pytest.mark.feature('foreman') def test_http_foreman_login(server, server_fqdn): cmd = server.run(f"{CURL_CMD} --write-out '%{{http_code}}' http://{server_fqdn}/users/login") assert cmd.succeeded assert cmd.stdout == '301' +@pytest.mark.feature('foreman') def test_https_foreman_login(server, certificates, server_fqdn): cmd = server.run(f"{CURL_CMD} --cacert {certificates['server_ca_certificate']} --write-out '%{{http_code}}' https://{server_fqdn}/users/login") assert cmd.succeeded @@ -135,6 +141,7 @@ def test_httpd_config_syntax(server): assert cmd.succeeded +@pytest.mark.feature('foreman') def test_httpd_headers_use_dashes(server): cmd = server.run("grep -rPn 'RequestHeader\\s+set\\s+\\S*_\\S*\\s' /etc/httpd/conf.d/foreman.conf /etc/httpd/conf.d/foreman-ssl.conf /etc/httpd/conf.d/05-foreman.d/ /etc/httpd/conf.d/05-foreman-ssl.d/ 2>/dev/null") assert cmd.stdout.strip() == '', f"HTTP header names should use dashes, not underscores:\n{cmd.stdout}" diff --git a/tests/postgresql_test.py b/tests/postgresql_test.py index 82187466a..285d45219 100644 --- a/tests/postgresql_test.py +++ b/tests/postgresql_test.py @@ -13,18 +13,24 @@ def test_postgresql_port(database): assert postgresql.port("5432").is_reachable -def test_postgresql_databases(database): +def test_postgresql_databases(database, enabled_features): result = database.run("podman exec postgresql psql -U postgres -c '\\l'") - assert "foreman" in result.stdout - assert "candlepin" in result.stdout - assert "pulp" in result.stdout + if 'katello' in enabled_features: + assert "foreman" in result.stdout + assert "candlepin" in result.stdout + assert "pulp" in result.stdout + else: + assert "pulp" in result.stdout -def test_postgresql_users(database): +def test_postgresql_users(database, enabled_features): result = database.run("podman exec postgresql psql -U postgres -c '\\du'") - assert "foreman" in result.stdout - assert "candlepin" in result.stdout - assert "pulp" in result.stdout + if 'katello' in enabled_features: + assert "foreman" in result.stdout + assert "candlepin" in result.stdout + assert "pulp" in result.stdout + else: + assert "pulp" in result.stdout def test_postgresql_password_encryption(database): @@ -35,7 +41,9 @@ def test_postgresql_password_encryption(database): reader = csv.reader(result.stdout.splitlines()) for row in reader: - assert ("SCRAM-SHA-256" in row[6]) + if row[0] == 'postgres': + continue + assert "SCRAM-SHA-256" in row[6] def test_postgresql_missing_with_external(server, database_mode): From 85072c97de91b96ab15d6b3ddaabbf5d222d996a Mon Sep 17 00:00:00 2001 From: Arvind Jangir Date: Mon, 15 Jun 2026 11:23:02 +0530 Subject: [PATCH 02/22] use flavor specific checks --- src/roles/checks/defaults/main.yml | 11 +++++++++++ src/roles/checks/tasks/main.yml | 6 +----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/roles/checks/defaults/main.yml b/src/roles/checks/defaults/main.yml index bdebaac21..adff9a771 100644 --- a/src/roles/checks/defaults/main.yml +++ b/src/roles/checks/defaults/main.yml @@ -1,2 +1,13 @@ --- checks_databases: [] +checks_per_flavor: + katello: + - check_features + - check_hostname + - check_database_connection + - check_system_requirements + + foreman-proxy-content: + - check_features + - check_hostname + - check_database_connection diff --git a/src/roles/checks/tasks/main.yml b/src/roles/checks/tasks/main.yml index 48ae0872c..86cf02a32 100644 --- a/src/roles/checks/tasks/main.yml +++ b/src/roles/checks/tasks/main.yml @@ -1,11 +1,7 @@ --- - name: Execute checks ansible.builtin.include_tasks: execute_check.yml - loop: - - check_features - - check_hostname - - check_database_connection - - check_system_requirements + loop: "{{ checks_per_flavor[flavor] | default(checks_per_flavor['katello']) }}" - name: Run database index integrity checks ansible.builtin.include_role: From 09988ec5cb2b6b34a353f13cc0ecf6c0c7fee9ef Mon Sep 17 00:00:00 2001 From: Arvind Jangir Date: Mon, 15 Jun 2026 13:55:46 +0530 Subject: [PATCH 03/22] add httpd vhost for pulp mirror --- src/roles/httpd/tasks/main.yml | 9 +++ .../templates/rhsm-pulpcore-vhost.conf.j2 | 78 +++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 src/roles/httpd/templates/rhsm-pulpcore-vhost.conf.j2 diff --git a/src/roles/httpd/tasks/main.yml b/src/roles/httpd/tasks/main.yml index cbe160156..245537342 100644 --- a/src/roles/httpd/tasks/main.yml +++ b/src/roles/httpd/tasks/main.yml @@ -108,6 +108,15 @@ notify: - Restart httpd +- name: Configure rhsm-pulpcore vhost + ansible.builtin.template: + src: rhsm-pulpcore-vhost.conf.j2 + dest: /etc/httpd/conf.d/rhsm-pulpcore.conf + mode: "0644" + when: httpd_with_pulp_mirror + notify: + - Restart httpd + - name: Create systemd drop-in directory for httpd ansible.builtin.file: path: /etc/systemd/system/httpd.service.d diff --git a/src/roles/httpd/templates/rhsm-pulpcore-vhost.conf.j2 b/src/roles/httpd/templates/rhsm-pulpcore-vhost.conf.j2 new file mode 100644 index 000000000..52c9267fb --- /dev/null +++ b/src/roles/httpd/templates/rhsm-pulpcore-vhost.conf.j2 @@ -0,0 +1,78 @@ + + ServerName {{ ansible_facts['fqdn'] }} + + ## Logging + ErrorLog "/var/log/httpd/rhsm-pulpcore_error.log" + ServerSignature Off + CustomLog "/var/log/httpd/rhsm-pulpcore_access.log" combined + ErrorDocument 500 '{"displayMessage": "Internal error, contact administrator", "errors": ["Internal error, contact administrator"], "status": "500" }' + ErrorDocument 503 '{"displayMessage": "Service unavailable or restarting, try later", "errors": ["Service unavailable or restarting, try later"], "status": "503" }' + + ## Request header rules + ## as per http://httpd.apache.org/docs/2.4/mod/mod_headers.html#requestheader + RequestHeader set X_RHSM_SSL_CLIENT_CERT "%{SSL_CLIENT_CERT}s" + + # SSL Proxy directives + SSLProxyEngine On + SSLProxyCACertificateFile "{{ httpd_ssl_dir }}/certs/katello-default-ca.crt" + ProxyPass /katello/api/v2/repositories/ {{ httpd_proxy_foreman_url }}/katello/api/v2/repositories/ + + ProxyPassReverse {{ httpd_proxy_foreman_url }}/katello/api/v2/repositories/ + + SSLProxyEngine On + + ProxyPass /pulp_ansible/galaxy/ {{ httpd_pulp_api_backend }}/pulp_ansible/galaxy/ + ProxyPassReverse /pulp_ansible/galaxy/ {{ httpd_pulp_api_backend }}/pulp_ansible/galaxy/ + + + RequestHeader unset REMOTE_USER + RequestHeader unset REMOTE-USER + RequestHeader set REMOTE-USER "admin" "expr=%{SSL_CLIENT_S_DN_CN} == '{{ ansible_facts['fqdn'] }}'" + RequestHeader set REMOTE-USER "admin" "expr=%{SSL_CLIENT_S_DN_CN} == '{{ httpd_proxy_foreman_url }}'" + ProxyPass {{ httpd_pulp_api_backend }}/v2/ + ProxyPassReverse {{ httpd_pulp_api_backend }}/v2/ + + + ProxyPass /pulp/container/ {{ httpd_pulp_content_backend }}/pulp/container/ + ProxyPassReverse /pulp/container/ {{ httpd_pulp_content_backend }}/pulp/container/ + + + RequestHeader unset X-CLIENT-CERT + RequestHeader set X-CLIENT-CERT "%{SSL_CLIENT_CERT}s" env=SSL_CLIENT_CERT + ProxyPass {{ httpd_pulp_content_backend }}/pulp/content disablereuse=on timeout=600 + ProxyPassReverse {{ httpd_pulp_content_backend }}/pulp/content + + + + RequestHeader unset REMOTE_USER + RequestHeader unset REMOTE-USER + RequestHeader set REMOTE-USER "admin" "expr=%{SSL_CLIENT_S_DN_CN} == '{{ ansible_facts['fqdn'] }}'" + RequestHeader set REMOTE-USER "admin" "expr=%{SSL_CLIENT_S_DN_CN} == '{{ httpd_proxy_foreman_url }}'" + ProxyPass {{ httpd_pulp_api_backend }}/pulp/api/v3 timeout=600 + ProxyPassReverse {{ httpd_pulp_api_backend }}/pulp/api/v3 + + + ProxyPass /pulp/assets/ {{ httpd_pulp_api_backend }}/pulp/assets/ + ProxyPassReverse /pulp/assets/ {{ httpd_pulp_api_backend }}/pulp/assets/ + + ## Proxy rules + ProxyRequests Off + ProxyPreserveHost Off + ProxyPass /rhsm {{ httpd_proxy_foreman_url }}/rhsm disablereuse=on retry=0 + ProxyPassReverse /rhsm {{ httpd_proxy_foreman_url }}/rhsm + ProxyPass /redhat_access {{ httpd_proxy_foreman_url }}/redhat_access disablereuse=on retry=0 + ProxyPassReverse /redhat_access {{ httpd_proxy_foreman_url }}/redhat_access + ProxyPass /api/lightspeed {{ httpd_proxy_foreman_url }}/api/lightspeed disablereuse=on retry=0 + ProxyPassReverse /api/lightspeed {{ httpd_proxy_foreman_url }}/api/lightspeed + ProxyPass /api/registration_commands {{ httpd_proxy_foreman_url }}/api/registration_commands disablereuse=on retry=0 + ProxyPassReverse /api/registration_commands {{ httpd_proxy_foreman_url }}/api/registration_commands + + Alias /pub {{ httpd_pub_dir }} + + + Options +FollowSymLinks +Indexes + Require all granted + + + AddDefaultCharset UTF-8 + From 721c51cad7848734f953c399ab74b26aee7b67e1 Mon Sep 17 00:00:00 2001 From: Arvind Jangir Date: Mon, 15 Jun 2026 15:27:59 +0530 Subject: [PATCH 04/22] Tests Structure with flavor directory --- .github/workflows/test.yml | 1 - pyproject.toml | 2 ++ tests/conftest.py | 26 +++++++++++++++++++ tests/flavor/__init__.py | 0 .../flavor/foreman-proxy-content/__init__.py | 0 .../foreman-proxy-content/httpd_test.py | 0 .../foreman-proxy-content/postgresql_test.py | 13 ++++++++++ tests/flavor/katello/__init__.py | 0 tests/flavor/katello/postgresql_test.py | 13 ++++++++++ tests/foreman_api_test.py | 6 +++++ tests/postgresql_test.py | 20 -------------- 11 files changed, 60 insertions(+), 21 deletions(-) create mode 100644 pyproject.toml create mode 100644 tests/flavor/__init__.py create mode 100644 tests/flavor/foreman-proxy-content/__init__.py create mode 100644 tests/flavor/foreman-proxy-content/httpd_test.py create mode 100644 tests/flavor/foreman-proxy-content/postgresql_test.py create mode 100644 tests/flavor/katello/__init__.py create mode 100644 tests/flavor/katello/postgresql_test.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f5cbaaebc..3fe24d6ee 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -396,7 +396,6 @@ jobs: run: | OBSAH_ALLOW_EXTRA_VARS=true ./foremanctl deploy \ --flavor foreman-proxy-content \ - --foreman-url https://quadlet.example.com \ -e foremanctl_target=proxy - name: Run tests run: | diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..5bf092d05 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,2 @@ +[tool.pytest.ini_options] +addopts = "--import-mode=importlib" \ No newline at end of file diff --git a/tests/conftest.py b/tests/conftest.py index c84f7f2c1..5489288f1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -18,6 +18,7 @@ SSH_CONFIG = './.tmp/ssh-config' OBSAH_STATE = os.environ.get('OBSAH_STATE', '.var/lib/foremanctl') PARAMETERS_FILE = os.path.join(OBSAH_STATE, 'parameters.yaml') +FLAVOR_TESTS_DIR = py.path.local(__file__).dirpath() / 'flavor' class UserParameters: @@ -46,6 +47,7 @@ def enabled_features(self): def pytest_addoption(parser): parser.addoption("--server-hostname", action="store", default="quadlet", help="Hostname of the server VM to test against") + parser.addoption("--flavor", action="store", default="katello", choices=('foreman', 'katello', 'foreman-proxy-content'), help="Deployment flavor") @pytest.fixture(scope="module") @@ -63,6 +65,11 @@ def server_hostname(pytestconfig): return pytestconfig.getoption("server_hostname") +@pytest.fixture(scope="module") +def flavor(pytestconfig): + return pytestconfig.getoption("flavor") + + @pytest.fixture(scope="module") def server_fqdn(server): return server.check_output('hostname -f') @@ -251,6 +258,25 @@ def pytest_configure(config): config.user_parameters = UserParameters(config) +def pytest_collection_modifyitems(config, items): + flavor = config.getoption("flavor") + active_flavor_dir = FLAVOR_TESTS_DIR / flavor + + deselected = [] + selected = [] + for item in items: + test_path = py.path.local(item.fspath) + if test_path.relto(FLAVOR_TESTS_DIR): + if not test_path.relto(active_flavor_dir): + deselected.append(item) + continue + selected.append(item) + + if deselected: + config.hook.pytest_deselected(items=deselected) + items[:] = selected + + def pytest_runtest_setup(item): feature_markers = set(mark.args[0] for mark in item.iter_markers(name="feature")) if feature_markers: diff --git a/tests/flavor/__init__.py b/tests/flavor/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/flavor/foreman-proxy-content/__init__.py b/tests/flavor/foreman-proxy-content/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/flavor/foreman-proxy-content/httpd_test.py b/tests/flavor/foreman-proxy-content/httpd_test.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/flavor/foreman-proxy-content/postgresql_test.py b/tests/flavor/foreman-proxy-content/postgresql_test.py new file mode 100644 index 000000000..d7bff4c93 --- /dev/null +++ b/tests/flavor/foreman-proxy-content/postgresql_test.py @@ -0,0 +1,13 @@ + +def test_postgresql_databases(database): + result = database.run("podman exec postgresql psql -U postgres -c '\\l'") + assert "pulp" in result.stdout + assert "foreman" not in result.stdout + assert "candlepin" not in result.stdout + + +def test_postgresql_users(database): + result = database.run("podman exec postgresql psql -U postgres -c '\\du'") + assert "pulp" in result.stdout + assert "foreman" not in result.stdout + assert "candlepin" not in result.stdout diff --git a/tests/flavor/katello/__init__.py b/tests/flavor/katello/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/flavor/katello/postgresql_test.py b/tests/flavor/katello/postgresql_test.py new file mode 100644 index 000000000..bab61dbdf --- /dev/null +++ b/tests/flavor/katello/postgresql_test.py @@ -0,0 +1,13 @@ + +def test_postgresql_databases(database): + result = database.run("podman exec postgresql psql -U postgres -c '\\l'") + assert "foreman" in result.stdout + assert "candlepin" in result.stdout + assert "pulp" in result.stdout + + +def test_postgresql_users(database): + result = database.run("podman exec postgresql psql -U postgres -c '\\du'") + assert "pulp" in result.stdout + assert "foreman" in result.stdout + assert "candlepin" in result.stdout diff --git a/tests/foreman_api_test.py b/tests/foreman_api_test.py index 990d6c968..ba674a142 100644 --- a/tests/foreman_api_test.py +++ b/tests/foreman_api_test.py @@ -10,6 +10,7 @@ def test_foreman_organization(organization): def test_foreman_product(product): assert product + @pytest.mark.feature('katello') def test_foreman_yum_repository(yum_repository, foremanapi, local_request): assert yum_repository @@ -18,6 +19,7 @@ def test_foreman_yum_repository(yum_repository, foremanapi, local_request): assert local_request.get(f'{repo_url}/repodata/repomd.xml', verify=False) assert local_request.get(f'{repo_url}/Packages/b/bear-4.1-1.noarch.rpm', verify=False) + @pytest.mark.feature('katello') def test_foreman_file_repository(file_repository, foremanapi, local_request): assert file_repository @@ -25,15 +27,18 @@ def test_foreman_file_repository(file_repository, foremanapi, local_request): repo_url = file_repository['full_path'] assert local_request.get(f'{repo_url}/1.iso', verify=False) + @pytest.mark.feature('katello') def test_foreman_container_repository(container_repository, foremanapi): assert container_repository foremanapi.resource_action('repositories', 'sync', {'id': container_repository['id']}) + @pytest.mark.feature('katello') def test_foreman_lifecycle_environment(lifecycle_environment): assert lifecycle_environment + @pytest.mark.feature('katello') def test_foreman_content_view(content_view, yum_repository, foremanapi): assert content_view @@ -47,6 +52,7 @@ def test_foreman_content_view(content_view, yum_repository, foremanapi): foremanapi.resource_action('content_views', 'remove_from_environment', params={'id': content_view['id'], 'environment_id': environment_id}) foremanapi.delete('content_view_versions', version) + @pytest.mark.feature('katello') def test_foreman_manifest(organization, foremanapi, fixture_dir): manifest_path = fixture_dir / 'manifest.zip' diff --git a/tests/postgresql_test.py b/tests/postgresql_test.py index 285d45219..76a13e6ac 100644 --- a/tests/postgresql_test.py +++ b/tests/postgresql_test.py @@ -13,26 +13,6 @@ def test_postgresql_port(database): assert postgresql.port("5432").is_reachable -def test_postgresql_databases(database, enabled_features): - result = database.run("podman exec postgresql psql -U postgres -c '\\l'") - if 'katello' in enabled_features: - assert "foreman" in result.stdout - assert "candlepin" in result.stdout - assert "pulp" in result.stdout - else: - assert "pulp" in result.stdout - - -def test_postgresql_users(database, enabled_features): - result = database.run("podman exec postgresql psql -U postgres -c '\\du'") - if 'katello' in enabled_features: - assert "foreman" in result.stdout - assert "candlepin" in result.stdout - assert "pulp" in result.stdout - else: - assert "pulp" in result.stdout - - def test_postgresql_password_encryption(database): result = database.run("podman exec postgresql psql -U postgres -c 'SHOW password_encryption'") assert "scram-sha-256" in result.stdout From 8f53a4ac1a5de38c281ed05d77c213921c337bdf Mon Sep 17 00:00:00 2001 From: Arvind Jangir Date: Tue, 16 Jun 2026 12:46:54 +0530 Subject: [PATCH 05/22] enhance structure --- .github/workflows/test.yml | 10 +++++-- Vagrantfile | 2 +- .../_foreman_proxy/metadata.obsah.yaml | 22 ++++++++++++++ src/playbooks/deploy/deploy.yaml | 13 ++++----- src/playbooks/deploy/metadata.obsah.yaml | 29 +------------------ src/roles/checks/defaults/main.yml | 12 +------- src/roles/checks/tasks/main.yml | 2 +- src/roles/foreman_proxy/defaults/main.yaml | 1 - src/roles/foreman_proxy/handlers/main.yml | 1 + .../foreman_proxy/templates/settings.yml.j2 | 5 +++- src/roles/httpd/defaults/main.yml | 2 -- .../tasks/foreman-proxy-content.yaml | 8 +++++ src/roles/post_install/tasks/katello.yaml | 16 ++++++++++ src/roles/post_install/tasks/main.yaml | 17 ++--------- src/roles/pulp/defaults/main.yaml | 4 +-- src/vars/flavors/foreman-proxy-content.yml | 5 ++++ src/vars/flavors/katello.yml | 6 ++++ 17 files changed, 82 insertions(+), 73 deletions(-) create mode 100644 src/playbooks/_foreman_proxy/metadata.obsah.yaml create mode 100644 src/roles/post_install/tasks/foreman-proxy-content.yaml create mode 100644 src/roles/post_install/tasks/katello.yaml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3fe24d6ee..b8ed56a39 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -391,15 +391,19 @@ jobs: run: ./setup-environment - name: Start VMs run: | - ./forge vms start --vms proxy + ./forge vms start - name: Run deployment run: | OBSAH_ALLOW_EXTRA_VARS=true ./foremanctl deploy \ --flavor foreman-proxy-content \ - -e foremanctl_target=proxy + -e foremanctl_target=content-proxy + - name: Deploy features + run: | + ./foremanctl deploy \ + --add-feature remote-execution - name: Run tests run: | - ./forge test --pytest-args="--server-hostname=proxy --flavor=foreman-proxy-content" + ./forge test --pytest-args="--server-hostname=content-proxy --flavor=foreman-proxy-content" - name: Archive smoker report if: ${{ always() }} uses: actions/upload-artifact@v7 diff --git a/Vagrantfile b/Vagrantfile index b69ff07e2..7b1f1ff1a 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -48,7 +48,7 @@ Vagrant.configure("2") do |config| end end - config.vm.define "proxy" do |override| + config.vm.define "content-proxy" do |override| override.vm.box = "centos/stream9" override.vm.hostname = "proxy.#{DOMAIN}" diff --git a/src/playbooks/_foreman_proxy/metadata.obsah.yaml b/src/playbooks/_foreman_proxy/metadata.obsah.yaml new file mode 100644 index 000000000..4d883ca58 --- /dev/null +++ b/src/playbooks/_foreman_proxy/metadata.obsah.yaml @@ -0,0 +1,22 @@ +--- +variables: + foreman_proxy_bmc_ipmi_implementation: + parameter: --bmc-ipmi-implementation + help: IPMI implementation to use for BMC. + choices: + - freeipmi + - ipmitool + foreman_proxy_bmc_redfish_verify_ssl: + parameter: --bmc-redfish-verify-ssl + help: Verify SSL certificates for Redfish BMC connections. + type: Boolean + foreman_proxy_foreman_server_url: + parameter: --foreman-url + help: URL of Foreman server this proxy connects to. + foreman_proxy_trusted_hosts: + help: FQDN of a host trusted by the proxy. + action: append_unique + foreman_proxy_oauth_consumer_key: + help: OAuth key to be used for communication with Foreman. + foreman_proxy_oauth_consumer_secret: + help: OAuth secret to be used for communication with Foreman. diff --git a/src/playbooks/deploy/deploy.yaml b/src/playbooks/deploy/deploy.yaml index 12c46a4ae..91f5440c0 100644 --- a/src/playbooks/deploy/deploy.yaml +++ b/src/playbooks/deploy/deploy.yaml @@ -1,5 +1,5 @@ --- -- name: Setup quadlet machine +- name: Setup machine hosts: - "{{ foremanctl_target | default('quadlet') }}" become: true @@ -36,17 +36,16 @@ - role: pulp when: enabled_features | has_feature('pulp') - role: foreman - when: "'foreman' in enabled_features" + when: enabled_features | has_feature('foreman') - role: systemd_target - role: iop_core when: - - "'iop' in enabled_features" + - enabled_features | has_feature('iop') - database_mode == 'internal' - role: foreman_proxy when: - - "'foreman-proxy' in enabled_features" + - enabled_features | has_feature('foreman-proxy') - role: hammer when: - - "'hammer' in enabled_features" - - role: post_install - when: "'foreman' in enabled_features" + - enabled_features | has_feature('hammer') + - post_install diff --git a/src/playbooks/deploy/metadata.obsah.yaml b/src/playbooks/deploy/metadata.obsah.yaml index f63181794..81b8c3066 100644 --- a/src/playbooks/deploy/metadata.obsah.yaml +++ b/src/playbooks/deploy/metadata.obsah.yaml @@ -46,34 +46,6 @@ variables: type: AbsolutePath parameter: --certificate-server-ca-certificate persist: false - foreman_proxy_bmc_ipmi_implementation: - parameter: --bmc-ipmi-implementation - help: IPMI implementation to use for BMC. - choices: - - freeipmi - - ipmitool - foreman_proxy_bmc_redfish_verify_ssl: - parameter: --bmc-redfish-verify-ssl - help: Verify SSL certificates for Redfish BMC connections. - type: Boolean - foreman_proxy_foreman_server_url: - parameter: --foreman-url - help: URL of the parent Foreman server this proxy connects to. - foreman_proxy_trusted_hosts: - help: FQDN of a host trusted by the smart proxy. Can be specified multiple times. - action: append_unique - foreman_oauth_consumer_key: - parameter: --oauth-consumer-key - help: OAuth consumer key. - foreman_oauth_consumer_secret: - parameter: --oauth-consumer-secret - help: OAuth consumer secret. - pulp_rhsm_url: - parameter: --rhsm-url - help: RHSM URL for Pulp smart proxy content mirroring. - pulp_smart_proxy_auth_methods: - parameter: --smart-proxy-auth-methods - help: Authentication methods for Pulp smart proxy. constraints: required_together: @@ -87,3 +59,4 @@ include: - _tuning - _flavor_features - _pulp + - _foreman_proxy diff --git a/src/roles/checks/defaults/main.yml b/src/roles/checks/defaults/main.yml index adff9a771..9e405bb44 100644 --- a/src/roles/checks/defaults/main.yml +++ b/src/roles/checks/defaults/main.yml @@ -1,13 +1,3 @@ --- checks_databases: [] -checks_per_flavor: - katello: - - check_features - - check_hostname - - check_database_connection - - check_system_requirements - - foreman-proxy-content: - - check_features - - check_hostname - - check_database_connection +checks_to_execute: [] diff --git a/src/roles/checks/tasks/main.yml b/src/roles/checks/tasks/main.yml index 86cf02a32..18765bfe3 100644 --- a/src/roles/checks/tasks/main.yml +++ b/src/roles/checks/tasks/main.yml @@ -1,7 +1,7 @@ --- - name: Execute checks ansible.builtin.include_tasks: execute_check.yml - loop: "{{ checks_per_flavor[flavor] | default(checks_per_flavor['katello']) }}" + loop: "{{ checks_to_execute }}" - name: Run database index integrity checks ansible.builtin.include_role: diff --git a/src/roles/foreman_proxy/defaults/main.yaml b/src/roles/foreman_proxy/defaults/main.yaml index c5aaa1676..f8a267f93 100644 --- a/src/roles/foreman_proxy/defaults/main.yaml +++ b/src/roles/foreman_proxy/defaults/main.yaml @@ -11,7 +11,6 @@ foreman_proxy_default_trusted_hosts: - "{{ foreman_proxy_name }}" foreman_proxy_trusted_hosts: [] -foreman_proxy_all_trusted_hosts: "{{ (foreman_proxy_default_trusted_hosts + foreman_proxy_trusted_hosts) | unique | list }}" foreman_proxy_base_features: - logs diff --git a/src/roles/foreman_proxy/handlers/main.yml b/src/roles/foreman_proxy/handlers/main.yml index 204ba5858..e10d0f8f8 100644 --- a/src/roles/foreman_proxy/handlers/main.yml +++ b/src/roles/foreman_proxy/handlers/main.yml @@ -11,3 +11,4 @@ oauth1_consumer_key: "{{ foreman_proxy_oauth_consumer_key }}" oauth1_consumer_secret: "{{ foreman_proxy_oauth_consumer_secret }}" ca_path: "{{ foreman_proxy_foreman_ca_certificate | default(omit) }}" + when: not foreman_proxy_with_pulp_mirror diff --git a/src/roles/foreman_proxy/templates/settings.yml.j2 b/src/roles/foreman_proxy/templates/settings.yml.j2 index 867d83679..e737a8e82 100644 --- a/src/roles/foreman_proxy/templates/settings.yml.j2 +++ b/src/roles/foreman_proxy/templates/settings.yml.j2 @@ -1,7 +1,10 @@ :settings_directory: /etc/foreman-proxy/settings.d :foreman_url: {{ foreman_proxy_foreman_server_url }} -:trusted_hosts: {{ foreman_proxy_all_trusted_hosts }} +:trusted_hosts: +{% for host in foreman_proxy_default_trusted_hosts | union(foreman_proxy_trusted_hosts) %} + - {{ host }} +{% endfor %} :https_port: {{ foreman_proxy_https_port }} :ssl_ca_file: /etc/foreman-proxy/ssl_ca.pem diff --git a/src/roles/httpd/defaults/main.yml b/src/roles/httpd/defaults/main.yml index 1dad1a7f2..b4ab7dc29 100644 --- a/src/roles/httpd/defaults/main.yml +++ b/src/roles/httpd/defaults/main.yml @@ -6,8 +6,6 @@ httpd_pub_dir: /var/www/html/pub httpd_enabled_pulp_snippets: [] httpd_proxy_foreman_url: "{{ foreman_proxy_foreman_server_url }}" -httpd_client_certificate: "{{ client_certificate }}" -httpd_client_key: "{{ client_key }}" # MPM event module defaults httpd_server_limit: 25 diff --git a/src/roles/post_install/tasks/foreman-proxy-content.yaml b/src/roles/post_install/tasks/foreman-proxy-content.yaml new file mode 100644 index 000000000..45685822f --- /dev/null +++ b/src/roles/post_install/tasks/foreman-proxy-content.yaml @@ -0,0 +1,8 @@ +--- +- name: Mark installation as complete + ansible.builtin.copy: + dest: "{{ post_install_done_flag }}" + content: '' + mode: '0640' + delegate_to: localhost + become: false diff --git a/src/roles/post_install/tasks/katello.yaml b/src/roles/post_install/tasks/katello.yaml new file mode 100644 index 000000000..6327e77ac --- /dev/null +++ b/src/roles/post_install/tasks/katello.yaml @@ -0,0 +1,16 @@ +--- +- name: Admin credentials + ansible.builtin.debug: + msg: + - "{{ _post_install_url_msg }}" + - "{{ _post_install_cred_msg if (post_install_done_flag is not exists) else '' }}" + vars: + _post_install_url_msg: "Foreman is running at {{ foreman_url }}" + _post_install_cred_msg: "Admin credentials: {{ foreman_initial_admin_username }}:{{ foreman_initial_admin_password }}" +- name: Mark installation as complete + ansible.builtin.copy: + dest: "{{ post_install_done_flag }}" + content: '' + mode: '0640' + delegate_to: localhost + become: false diff --git a/src/roles/post_install/tasks/main.yaml b/src/roles/post_install/tasks/main.yaml index 6327e77ac..951ad825e 100644 --- a/src/roles/post_install/tasks/main.yaml +++ b/src/roles/post_install/tasks/main.yaml @@ -1,16 +1,3 @@ --- -- name: Admin credentials - ansible.builtin.debug: - msg: - - "{{ _post_install_url_msg }}" - - "{{ _post_install_cred_msg if (post_install_done_flag is not exists) else '' }}" - vars: - _post_install_url_msg: "Foreman is running at {{ foreman_url }}" - _post_install_cred_msg: "Admin credentials: {{ foreman_initial_admin_username }}:{{ foreman_initial_admin_password }}" -- name: Mark installation as complete - ansible.builtin.copy: - dest: "{{ post_install_done_flag }}" - content: '' - mode: '0640' - delegate_to: localhost - become: false +- name: Run flavor-specific post install tasks + ansible.builtin.include_tasks: "{{ flavor }}.yaml" diff --git a/src/roles/pulp/defaults/main.yaml b/src/roles/pulp/defaults/main.yaml index 43fbcde58..6e9178d61 100644 --- a/src/roles/pulp/defaults/main.yaml +++ b/src/roles/pulp/defaults/main.yaml @@ -26,7 +26,6 @@ pulp_pulp_url: "http://{{ ansible_facts['fqdn'] }}:24817" pulp_enable_analytics: false pulp_mirror: false pulp_rhsm_url: "https://{{ ansible_facts['fqdn'] }}/rhsm" -pulp_smart_proxy_auth_methods: "['client_certificate']" pulp_default_import_paths: "{{ pulp_mirror | ternary(['/var/lib/pulp/sync_imports'], ['/var/lib/pulp/sync_imports', '/var/lib/pulp/imports']) }}" pulp_default_export_paths: "{{ pulp_mirror | ternary([], ['/var/lib/pulp/exports']) }}" @@ -75,8 +74,7 @@ pulp_settings_other_env: SMART_PROXY_PULP_URL: "{{ pulp_pulp_url }}" SMART_PROXY_MIRROR: "{{ pulp_mirror }}" SMART_PROXY_RHSM_URL: "{{ pulp_rhsm_url }}" - SMART_PROXY_AUTH_METHODS: >- - {{ pulp_smart_proxy_auth_methods }} + SMART_PROXY_AUTH_METHODS: ['client_certificate'] PULP_API_WORKERS: "{{ pulp_api_service_worker_count }}" PULP_CONTENT_WORKERS: "{{ pulp_content_service_worker_count }}" PULP_TOKEN_AUTH_DISABLED: "true" diff --git a/src/vars/flavors/foreman-proxy-content.yml b/src/vars/flavors/foreman-proxy-content.yml index be21b769c..0764510aa 100644 --- a/src/vars/flavors/foreman-proxy-content.yml +++ b/src/vars/flavors/foreman-proxy-content.yml @@ -7,3 +7,8 @@ flavor_features: - content/python pulp_mirror: true + +checks_to_execute: + - check_features + - check_hostname + - check_database_connection diff --git a/src/vars/flavors/katello.yml b/src/vars/flavors/katello.yml index ca5cd86d0..a3af7d3c1 100644 --- a/src/vars/flavors/katello.yml +++ b/src/vars/flavors/katello.yml @@ -7,3 +7,9 @@ flavor_features: - content/deb - content/python - content/rpm + +checks_to_execute: + - check_features + - check_hostname + - check_database_connection + - check_system_requirements From cfb7110fa8feaf2df77d8259524b119a039676f3 Mon Sep 17 00:00:00 2001 From: Arvind Jangir Date: Tue, 16 Jun 2026 23:11:48 +0530 Subject: [PATCH 06/22] Flavor aware help output --- .../_certificates_custom/metadata.obsah.yaml | 26 ++++++++++ .../foreman-proxy-content/metadata.obsah.yaml | 7 +++ .../_flavors/katello/metadata.obsah.yaml | 20 ++++++++ src/playbooks/_foreman/metadata.obsah.yaml | 16 +++++++ src/playbooks/_pulp/metadata.obsah.yaml | 2 + src/playbooks/deploy/metadata.obsah.yaml | 48 +------------------ 6 files changed, 72 insertions(+), 47 deletions(-) create mode 100644 src/playbooks/_certificates_custom/metadata.obsah.yaml create mode 100644 src/playbooks/_flavors/foreman-proxy-content/metadata.obsah.yaml create mode 100644 src/playbooks/_flavors/katello/metadata.obsah.yaml create mode 100644 src/playbooks/_foreman/metadata.obsah.yaml diff --git a/src/playbooks/_certificates_custom/metadata.obsah.yaml b/src/playbooks/_certificates_custom/metadata.obsah.yaml new file mode 100644 index 000000000..0b1ef0857 --- /dev/null +++ b/src/playbooks/_certificates_custom/metadata.obsah.yaml @@ -0,0 +1,26 @@ +--- +variables: + certificates_cnames: + help: Additional DNS name to include in Subject Alternative Names for certificates. Can be specified multiple times. + action: append_unique + type: FQDN + parameter: --certificate-cname + certificates_custom_server_certificate: + help: Path to a custom server certificate to use instead of the auto-generated one. + type: AbsolutePath + parameter: --certificate-server-certificate + persist: false + certificates_custom_server_key: + help: Path to the private key for the custom server certificate. + type: AbsolutePath + parameter: --certificate-server-key + persist: false + certificates_custom_server_ca_certificate: + help: Path to the CA certificate that signed the custom server certificate. + type: AbsolutePath + parameter: --certificate-server-ca-certificate + persist: false + +constraints: + required_together: + - [certificates_custom_server_certificate, certificates_custom_server_key, certificates_custom_server_ca_certificate] diff --git a/src/playbooks/_flavors/foreman-proxy-content/metadata.obsah.yaml b/src/playbooks/_flavors/foreman-proxy-content/metadata.obsah.yaml new file mode 100644 index 000000000..c25997627 --- /dev/null +++ b/src/playbooks/_flavors/foreman-proxy-content/metadata.obsah.yaml @@ -0,0 +1,7 @@ +--- +include: + - _certificate_source + - _certificate_validity + - _certificates_custom + - _pulp + - _foreman_proxy diff --git a/src/playbooks/_flavors/katello/metadata.obsah.yaml b/src/playbooks/_flavors/katello/metadata.obsah.yaml new file mode 100644 index 000000000..f239a0801 --- /dev/null +++ b/src/playbooks/_flavors/katello/metadata.obsah.yaml @@ -0,0 +1,20 @@ +--- +variables: + external_authentication: + help: External authentication method to use + choices: + - ipa + - ipa_with_api + external_authentication_pam_service: + help: Name of the PAM service to use for IPA authentication + +include: + - _certificate_source + - _certificate_validity + - _certificates_custom + - _database_mode + - _database_connection + - _foreman + - _foreman_proxy + - _pulp + - _tuning diff --git a/src/playbooks/_foreman/metadata.obsah.yaml b/src/playbooks/_foreman/metadata.obsah.yaml new file mode 100644 index 000000000..fb089a1d5 --- /dev/null +++ b/src/playbooks/_foreman/metadata.obsah.yaml @@ -0,0 +1,16 @@ +--- +variables: + foreman_initial_admin_username: + help: Initial username for the admin user. + parameter: --initial-admin-username + foreman_initial_admin_password: + help: Initial password for the admin user. + parameter: --initial-admin-password + foreman_initial_organization: + help: Name of an initial organization. + parameter: --initial-organization + foreman_initial_location: + help: Name of an initial location. + parameter: --initial-location + foreman_puma_workers: + help: Number of workers for Puma. diff --git a/src/playbooks/_pulp/metadata.obsah.yaml b/src/playbooks/_pulp/metadata.obsah.yaml index 6b6a4a301..a9d519b52 100644 --- a/src/playbooks/_pulp/metadata.obsah.yaml +++ b/src/playbooks/_pulp/metadata.obsah.yaml @@ -10,3 +10,5 @@ variables: action: append_unique type: AbsolutePath parameter: --content-export-path + pulp_worker_count: + help: Number of Pulp workers. Defaults to 8 or the number of CPU cores, whichever is smaller. diff --git a/src/playbooks/deploy/metadata.obsah.yaml b/src/playbooks/deploy/metadata.obsah.yaml index 81b8c3066..e3ac3c475 100644 --- a/src/playbooks/deploy/metadata.obsah.yaml +++ b/src/playbooks/deploy/metadata.obsah.yaml @@ -3,22 +3,6 @@ help: | Install variables: - foreman_initial_admin_username: - help: Initial username for the admin user. - parameter: --initial-admin-username - foreman_initial_admin_password: - help: Initial password for the admin user. - parameter: --initial-admin-password - foreman_initial_organization: - help: Name of an initial organization. - parameter: --initial-organization - foreman_initial_location: - help: Name of an initial location. - parameter: --initial-location - foreman_puma_workers: - help: Number of workers for Puma. - pulp_worker_count: - help: Number of Pulp workers. Defaults to 8 or the number of CPU cores, whichever is smaller. external_authentication: help: External authentication method to use choices: @@ -26,37 +10,7 @@ variables: - ipa_with_api external_authentication_pam_service: help: Name of the PAM service to use for IPA authentication - certificates_cnames: - help: Additional DNS name to include in Subject Alternative Names for certificates. Can be specified multiple times. - action: append_unique - type: FQDN - parameter: --certificate-cname - certificates_custom_server_certificate: - help: Path to a custom server certificate to use instead of the auto-generated one. - type: AbsolutePath - parameter: --certificate-server-certificate - persist: false - certificates_custom_server_key: - help: Path to the private key for the custom server certificate. - type: AbsolutePath - parameter: --certificate-server-key - persist: false - certificates_custom_server_ca_certificate: - help: Path to the CA certificate that signed the custom server certificate. - type: AbsolutePath - parameter: --certificate-server-ca-certificate - persist: false - -constraints: - required_together: - - [certificates_custom_server_certificate, certificates_custom_server_key, certificates_custom_server_ca_certificate] include: - - _certificate_source - - _certificate_validity - - _database_mode - - _database_connection - - _tuning - _flavor_features - - _pulp - - _foreman_proxy + - _flavors/katello From 8cc63e52abf79a7f34eb5bc3aa26f50515b79c32 Mon Sep 17 00:00:00 2001 From: Arvind Jangir Date: Tue, 16 Jun 2026 23:32:16 +0530 Subject: [PATCH 07/22] pull-images based on enabled features --- src/playbooks/pull-images/pull-images.yaml | 33 +++++++++++++++------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/playbooks/pull-images/pull-images.yaml b/src/playbooks/pull-images/pull-images.yaml index 6b84d1a13..630c315be 100644 --- a/src/playbooks/pull-images/pull-images.yaml +++ b/src/playbooks/pull-images/pull-images.yaml @@ -1,7 +1,7 @@ --- - name: Pull images hosts: - - quadlet + - "{{ foremanctl_target | default('quadlet') }}" vars_files: - "../../vars/defaults.yml" - "../../vars/flavors/{{ flavor }}.yml" @@ -11,15 +11,28 @@ roles: - role: pre_install post_tasks: - - name: Deploy core image units + - name: Deploy Foreman image units ansible.builtin.include_role: - name: "{{ item }}" + name: foreman + tasks_from: image.yaml + when: enabled_features | has_feature('foreman') + + - name: Deploy Candlepin image units + ansible.builtin.include_role: + name: candlepin + tasks_from: image.yaml + when: enabled_features | has_feature('candlepin') + + - name: Deploy Pulp image units + ansible.builtin.include_role: + name: pulp + tasks_from: image.yaml + when: enabled_features | has_feature('pulp') + + - name: Deploy Valkey image units + ansible.builtin.include_role: + name: valkey tasks_from: image.yaml - loop: - - foreman - - candlepin - - pulp - - valkey - name: Deploy database image units ansible.builtin.include_role: @@ -31,7 +44,7 @@ ansible.builtin.include_role: name: foreman_proxy tasks_from: image.yaml - when: "'foreman-proxy' in enabled_features" + when: enabled_features | has_feature('foreman-proxy') - name: Deploy IOP image units ansible.builtin.include_role: @@ -52,7 +65,7 @@ - iop_advisor_frontend - iop_inventory_frontend - iop_vulnerability_frontend - when: "'iop' in enabled_features" + when: enabled_features | has_feature('iop') - name: Pull images ansible.builtin.include_role: From 378d740781a02897528a0eb378a0c89bdc9eaf1b Mon Sep 17 00:00:00 2001 From: Arvind Jangir Date: Wed, 17 Jun 2026 16:33:38 +0530 Subject: [PATCH 08/22] Rename hostname for proxy vm --- .github/workflows/test.yml | 2 +- Vagrantfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b8ed56a39..3a1f219bd 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -391,7 +391,7 @@ jobs: run: ./setup-environment - name: Start VMs run: | - ./forge vms start + ./forge vms start --vms "content-proxy quadlet" - name: Run deployment run: | OBSAH_ALLOW_EXTRA_VARS=true ./foremanctl deploy \ diff --git a/Vagrantfile b/Vagrantfile index 7b1f1ff1a..311b73a1b 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -50,7 +50,7 @@ Vagrant.configure("2") do |config| config.vm.define "content-proxy" do |override| override.vm.box = "centos/stream9" - override.vm.hostname = "proxy.#{DOMAIN}" + override.vm.hostname = "content-proxy.#{DOMAIN}" override.vm.provider "libvirt" do |libvirt, provider| libvirt.memory = 4096 From c03de0950892a44a307f6c14c703867304105192 Mon Sep 17 00:00:00 2001 From: Arvind Jangir Date: Wed, 17 Jun 2026 19:54:24 +0530 Subject: [PATCH 09/22] Mark test with relevent features add flavor specific tests --- src/playbooks/health/health.yaml | 11 ++------ src/roles/check_services/tasks/main.yaml | 4 +-- src/vars/flavors/foreman-proxy-content.yml | 5 ++++ src/vars/flavors/katello.yml | 9 ++++++ tests/client_test.py | 5 ++++ tests/conftest.py | 5 ++++ tests/features_test.py | 10 +++---- tests/flavor/katello/images_test.py | 32 ++++++++++++++++++++++ tests/foreman_proxy_test.py | 10 +++++-- tests/health_test.py | 1 + tests/images_test.py | 4 +-- tests/target_lifecycle_test.py | 23 ++++++---------- 12 files changed, 84 insertions(+), 35 deletions(-) create mode 100644 tests/flavor/katello/images_test.py diff --git a/src/playbooks/health/health.yaml b/src/playbooks/health/health.yaml index 011aca516..8164ebcd2 100644 --- a/src/playbooks/health/health.yaml +++ b/src/playbooks/health/health.yaml @@ -1,6 +1,6 @@ --- - name: Run Foreman health checks - hosts: quadlet + hosts: "{{ foremanctl_target | default('quadlet') }}" become: true gather_facts: true vars_files: @@ -13,14 +13,7 @@ ansible.builtin.include_role: name: checks tasks_from: execute_check - loop: - - check_hostname - - check_services - - check_foreman_api - - check_database_connection - - check_foreman_tasks - - check_host_facts_count - - check_duplicate_permissions + loop: "{{ health_checks_to_execute }}" when: >- (item != 'check_foreman_tasks' or not (health_skip_check_foreman_tasks_param | default(false) | bool)) # Add additional skips here diff --git a/src/roles/check_services/tasks/main.yaml b/src/roles/check_services/tasks/main.yaml index 3b3d995bf..ba8c0a211 100644 --- a/src/roles/check_services/tasks/main.yaml +++ b/src/roles/check_services/tasks/main.yaml @@ -1,5 +1,5 @@ --- -- name: Get all Foreman services as JSON +- name: Get all Foreman target services as JSON ansible.builtin.shell: cmd: systemctl list-units --output=json --all $(systemctl list-dependencies --plain foreman.target) register: check_services_all_json @@ -26,7 +26,7 @@ list }} -- name: Verify all Foreman services are active +- name: Verify all Foreman target services are active ansible.builtin.assert: that: - check_services_not_running_list | length == 0 diff --git a/src/vars/flavors/foreman-proxy-content.yml b/src/vars/flavors/foreman-proxy-content.yml index 0764510aa..f3c97dd2f 100644 --- a/src/vars/flavors/foreman-proxy-content.yml +++ b/src/vars/flavors/foreman-proxy-content.yml @@ -12,3 +12,8 @@ checks_to_execute: - check_features - check_hostname - check_database_connection + +health_checks_to_execute: + - check_hostname + - check_database_connection + - check_services diff --git a/src/vars/flavors/katello.yml b/src/vars/flavors/katello.yml index a3af7d3c1..cdc309169 100644 --- a/src/vars/flavors/katello.yml +++ b/src/vars/flavors/katello.yml @@ -13,3 +13,12 @@ checks_to_execute: - check_hostname - check_database_connection - check_system_requirements + +health_checks_to_execute: + - check_hostname + - check_database_connection + - check_services + - check_foreman_api + - check_foreman_tasks + - check_host_facts_count + - check_duplicate_permissions diff --git a/tests/client_test.py b/tests/client_test.py index 1bcef15c0..6116ac8b6 100644 --- a/tests/client_test.py +++ b/tests/client_test.py @@ -1,3 +1,8 @@ +import pytest + +pytestmark = pytest.mark.feature('katello') + + def test_foreman_content_view(client_environment, activation_key, organization, foremanapi, client): client.run('dnf install -y subscription-manager') rcmd = foremanapi.create('registration_commands', {'organization_id': organization['id'], 'insecure': True, 'activation_keys': [activation_key['name']], 'force': True}) diff --git a/tests/conftest.py b/tests/conftest.py index 5489288f1..bb3fd4040 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -55,6 +55,11 @@ def enabled_features(pytestconfig): return pytestconfig.user_parameters.enabled_features +@pytest.fixture(scope="module") +def available_features(pytestconfig): + return pytestconfig.user_parameters.available_features + + @pytest.fixture(scope="module") def fixture_dir(): return py.path.local(__file__).realpath() / '..' / 'fixtures' diff --git a/tests/features_test.py b/tests/features_test.py index 70cb23935..0063aa179 100644 --- a/tests/features_test.py +++ b/tests/features_test.py @@ -1,7 +1,7 @@ import subprocess -def test_foremanctl_features(): +def test_foremanctl_features(available_features): command = ['./foremanctl', 'features'] result = subprocess.run(command, capture_output=True, text=True) @@ -10,18 +10,18 @@ def test_foremanctl_features(): for noise in ['PLAY [', 'TASK [', 'ok:', 'changed:', 'PLAY RECAP']: assert noise not in result.stdout, f"Ansible output not suppressed: found '{noise}'" - for feature in ['foreman', 'foreman-proxy', 'azure-rm']: + for feature in available_features: assert feature in result.stdout, f"Expected feature '{feature}' in output" -def test_foremanctl_features_list_enabled(): +def test_foremanctl_features_list_enabled(enabled_features): command = ['./foremanctl', 'features', '--list-enabled'] result = subprocess.run(command, capture_output=True, text=True) assert result.returncode == 0 - assert 'enabled' in result.stdout - assert 'available' not in result.stdout + for feature in enabled_features: + assert feature in result.stdout, f"Expected feature '{feature}' in output" def test_invalid_feature_rejected(): diff --git a/tests/flavor/katello/images_test.py b/tests/flavor/katello/images_test.py new file mode 100644 index 000000000..d939f32b7 --- /dev/null +++ b/tests/flavor/katello/images_test.py @@ -0,0 +1,32 @@ +import pytest + +FlAVOR_CORE_IMAGES = [ + "pulp", + "candlepin", + "foreman" +] + + +@pytest.fixture(params=FlAVOR_CORE_IMAGES) +def core_image(request): + return request.param + + +def test_image_file_exists(server, core_image): + image_file = server.file(f"/etc/containers/systemd/{core_image}.image") + assert image_file.exists and image_file.is_file + + +def test_image_dropin_directory_exists(server, core_image): + dropin_dir = server.file(f"/etc/containers/systemd/{core_image}.image.d") + assert dropin_dir.exists and dropin_dir.is_directory + + +def test_image_service_exists(server, core_image): + service = server.service(f"{core_image}-image") + assert service.exists + + +def test_image_registry_auth_file(server, core_image): + f = server.file(f"/etc/containers/systemd/{core_image}.image") + assert "REGISTRY_AUTH_FILE" in f.content_string diff --git a/tests/foreman_proxy_test.py b/tests/foreman_proxy_test.py index 71ad8cbc0..d8fe464b1 100644 --- a/tests/foreman_proxy_test.py +++ b/tests/foreman_proxy_test.py @@ -23,8 +23,14 @@ def test_foreman_proxy_features(server, certificates, server_fqdn, enabled_featu assert cmd.succeeded features = json.loads(cmd.stdout) assert "logs" in features - assert "script" in features - assert "dynflow" in features + if 'remote-execution' in enabled_features: + assert "script" in features + else: + assert "script" not in features + if 'dynflow' in enabled_features: + assert "dynflow" in features + else: + assert "dynflow" not in features if 'bmc' in enabled_features: assert "bmc" in features else: diff --git a/tests/health_test.py b/tests/health_test.py index 0087b2fcc..57ed1af53 100644 --- a/tests/health_test.py +++ b/tests/health_test.py @@ -34,6 +34,7 @@ def test_health_checks_pass(): ) +@pytest.mark.feature('foreman') def test_foreman_tasks_check_detects_errors(errored_foreman_task): """Verify foreman tasks check detects errored tasks and fails""" diff --git a/tests/images_test.py b/tests/images_test.py index 52e5cea69..e128e7145 100644 --- a/tests/images_test.py +++ b/tests/images_test.py @@ -1,10 +1,8 @@ import pytest CORE_IMAGES = [ - "foreman", - "candlepin", - "pulp", "valkey", + 'pulp' ] diff --git a/tests/target_lifecycle_test.py b/tests/target_lifecycle_test.py index 618f0873f..55d88cd0c 100644 --- a/tests/target_lifecycle_test.py +++ b/tests/target_lifecycle_test.py @@ -1,21 +1,16 @@ import time -FOREMAN_PING_RETRIES = 90 -FOREMAN_PING_DELAY = 10 +TARGET_ACTIVE_RETRIES = 90 +TARGET_ACTIVE_DELAY = 10 CURL_CMD = "curl --silent --output /dev/null" -def _wait_for_foreman(server, server_fqdn, certificates): - """Poll the Foreman HTTPS frontend until available or timeout.""" - for _ in range(FOREMAN_PING_RETRIES): - cmd = server.run( - f"{CURL_CMD} --cacert {certificates['server_ca_certificate']}" - f" --write-out '%{{http_code}}' https://{server_fqdn}/api/v2/ping" - ) - if cmd.stdout == '200': +def _wait_for_target_active(server, target="foreman.target"): + for _ in range(TARGET_ACTIVE_RETRIES): + if server.service(target).is_running: return - time.sleep(FOREMAN_PING_DELAY) - raise AssertionError("Foreman did not become available after target lifecycle operation") + time.sleep(TARGET_ACTIVE_DELAY) + raise AssertionError(f"{target} did not become active after lifecycle operation") def test_foreman_target_stop_start(server, server_fqdn, certificates): @@ -25,12 +20,12 @@ def test_foreman_target_stop_start(server, server_fqdn, certificates): result = server.run("systemctl start foreman.target") assert result.rc == 0, f"Failed to start foreman.target: {result.stderr}" - _wait_for_foreman(server, server_fqdn, certificates) + _wait_for_target_active(server, "foreman.target") assert server.service("foreman.target").is_running def test_foreman_target_restart(server, server_fqdn, certificates): result = server.run("systemctl restart foreman.target") assert result.rc == 0, f"Failed to restart foreman.target: {result.stderr}" - _wait_for_foreman(server, server_fqdn, certificates) + _wait_for_target_active(server, "foreman.target") assert server.service("foreman.target").is_running From 5ad19fcfc710172b451b6f691f23b0c6349ba7f0 Mon Sep 17 00:00:00 2001 From: Arvind Jangir Date: Wed, 17 Jun 2026 22:16:23 +0530 Subject: [PATCH 10/22] Register pulp smart proxy --- src/roles/foreman_proxy/tasks/main.yaml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/roles/foreman_proxy/tasks/main.yaml b/src/roles/foreman_proxy/tasks/main.yaml index 6e990cc88..bd8877ea8 100644 --- a/src/roles/foreman_proxy/tasks/main.yaml +++ b/src/roles/foreman_proxy/tasks/main.yaml @@ -74,7 +74,16 @@ oauth1_consumer_key: "{{ foreman_proxy_oauth_consumer_key }}" oauth1_consumer_secret: "{{ foreman_proxy_oauth_consumer_secret }}" ca_path: "{{ foreman_proxy_foreman_ca_certificate | default(omit) }}" - when: not foreman_proxy_with_pulp_mirror + +- name: Register Pulp Proxy to Foreman + theforeman.foreman.smart_proxy: + name: "{{ foreman_proxy_name }}-pulp" + url: "https://{{ foreman_proxy_name }}/pulp/api/v3/smart_proxy" + server_url: "{{ foreman_proxy_foreman_server_url }}" + oauth1_consumer_key: "{{ foreman_proxy_oauth_consumer_key }}" + oauth1_consumer_secret: "{{ foreman_proxy_oauth_consumer_secret }}" + ca_path: "{{ foreman_proxy_foreman_ca_certificate | default(omit) }}" + when: foreman_proxy_with_pulp_mirror - name: Flush handlers to restart services ansible.builtin.meta: flush_handlers From c26c0cb1292931e031bb3e89dc586b26e92eb672 Mon Sep 17 00:00:00 2001 From: Arvind Jangir Date: Thu, 18 Jun 2026 14:37:19 +0530 Subject: [PATCH 11/22] Don't generate certificates for proxy flavor --- src/roles/certificates/defaults/main.yml | 1 + src/roles/certificates/tasks/ca.yml | 4 +++- src/roles/certificates/tasks/main.yml | 4 +++- src/vars/flavors/foreman-proxy-content.yml | 1 + 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/roles/certificates/defaults/main.yml b/src/roles/certificates/defaults/main.yml index d460eee7e..6a79cb9bd 100644 --- a/src/roles/certificates/defaults/main.yml +++ b/src/roles/certificates/defaults/main.yml @@ -17,3 +17,4 @@ certificates_ca_validity_days: 7300 certificates_validity_days: 7300 certificates_renew: false certificates_renew_ca: false +certificates_generate: true diff --git a/src/roles/certificates/tasks/ca.yml b/src/roles/certificates/tasks/ca.yml index 145ccb1c4..117a2c6ed 100644 --- a/src/roles/certificates/tasks/ca.yml +++ b/src/roles/certificates/tasks/ca.yml @@ -5,7 +5,9 @@ register: _certificates_ca_cert - name: 'Generate CA' - when: not _certificates_ca_cert.stat.exists or certificates_renew_ca + when: + - (not _certificates_ca_cert.stat.exists or certificates_renew_ca) + - certificates_generate block: - name: 'Create CA key password file' ansible.builtin.copy: diff --git a/src/roles/certificates/tasks/main.yml b/src/roles/certificates/tasks/main.yml index a3a5e22ff..ef4d96393 100644 --- a/src/roles/certificates/tasks/main.yml +++ b/src/roles/certificates/tasks/main.yml @@ -35,7 +35,9 @@ - name: Issue host certificates ansible.builtin.include_tasks: host.yml - when: certificates_hostnames is defined + when: + - certificates_hostnames is defined + - certificates_generate with_items: "{{ certificates_hostnames }}" loop_control: loop_var: certificates_hostname diff --git a/src/vars/flavors/foreman-proxy-content.yml b/src/vars/flavors/foreman-proxy-content.yml index f3c97dd2f..5397421da 100644 --- a/src/vars/flavors/foreman-proxy-content.yml +++ b/src/vars/flavors/foreman-proxy-content.yml @@ -7,6 +7,7 @@ flavor_features: - content/python pulp_mirror: true +certificates_generate: false checks_to_execute: - check_features From fe89b94b62d566ab690a5b8dd7957fd49c376d96 Mon Sep 17 00:00:00 2001 From: Arvind Jangir Date: Thu, 18 Jun 2026 15:06:18 +0530 Subject: [PATCH 12/22] Use obsah >= 1.9.0 --- src/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/requirements.txt b/src/requirements.txt index 52dd0c3e9..a350ba0f4 100644 --- a/src/requirements.txt +++ b/src/requirements.txt @@ -1,3 +1,3 @@ -obsah >= 1.8.1 +obsah >= 1.9.0 requests requests-oauthlib From 0c539148b913e2ab397b5a9c50ecf6c0f414f2cc Mon Sep 17 00:00:00 2001 From: Arvind Jangir Date: Thu, 18 Jun 2026 16:16:26 +0530 Subject: [PATCH 13/22] skip certificate check when we don't generate --- src/playbooks/deploy/deploy.yaml | 2 +- tests/foreman_proxy_test.py | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/playbooks/deploy/deploy.yaml b/src/playbooks/deploy/deploy.yaml index 91f5440c0..c25b7023d 100644 --- a/src/playbooks/deploy/deploy.yaml +++ b/src/playbooks/deploy/deploy.yaml @@ -20,8 +20,8 @@ # TODO: Add tar_file check/extractation step for certificates_source == 'proxy' - role: certificates when: "certificates_source in ['default', 'custom_server']" - # TODO: remove proxy condition and add tar check - role: certificate_checks + when: certificates_generate | default(true) vars: certificate_checks_certificate: "{{ server_certificate }}" certificate_checks_key: "{{ server_key }}" diff --git a/tests/foreman_proxy_test.py b/tests/foreman_proxy_test.py index d8fe464b1..70dc24de4 100644 --- a/tests/foreman_proxy_test.py +++ b/tests/foreman_proxy_test.py @@ -25,12 +25,9 @@ def test_foreman_proxy_features(server, certificates, server_fqdn, enabled_featu assert "logs" in features if 'remote-execution' in enabled_features: assert "script" in features - else: - assert "script" not in features - if 'dynflow' in enabled_features: assert "dynflow" in features else: - assert "dynflow" not in features + assert "script" not in features if 'bmc' in enabled_features: assert "bmc" in features else: From 959a00186d7c253c931bb0c0912aa8ac40ef912e Mon Sep 17 00:00:00 2001 From: Arvind Jangir Date: Fri, 19 Jun 2026 18:36:30 +0530 Subject: [PATCH 14/22] proxy CI --- .github/workflows/test.yml | 39 ++++++++++++++++--- .../_certificates_proxy/metadata.obsah.yaml | 7 ++++ .../foreman-proxy-content/metadata.obsah.yaml | 1 + .../_foreman_proxy/metadata.obsah.yaml | 2 + src/playbooks/deploy/deploy.yaml | 1 - src/playbooks/deploy/metadata.obsah.yaml | 1 + src/roles/certificates/tasks/ca.yml | 1 - src/roles/certificates/tasks/main.yml | 10 ++++- src/roles/certificates/tasks/proxy.yml | 35 +++++++++++++++++ src/roles/foreman_proxy/handlers/main.yml | 1 - src/roles/httpd/defaults/main.yml | 1 + .../templates/rhsm-pulpcore-vhost.conf.j2 | 14 ++++++- src/roles/pulp/defaults/main.yaml | 8 ++-- 13 files changed, 106 insertions(+), 15 deletions(-) create mode 100644 src/playbooks/_certificates_proxy/metadata.obsah.yaml create mode 100644 src/roles/certificates/tasks/proxy.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3a1f219bd..c12433c39 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -391,16 +391,45 @@ jobs: run: ./setup-environment - name: Start VMs run: | - ./forge vms start --vms "content-proxy quadlet" - - name: Run deployment + ./forge vms start --vms "quadlet content-proxy" + - name: Configure repositories + run: | + ./forge setup-repositories + - name: Run image pull + run: | + ./foremanctl pull-images + - name: Deploy Katello on quadlet + run: | + ./foremanctl deploy \ + --initial-admin-password=changeme \ + --tuning development + - name: Deploy features on quadlet + run: | + ./foremanctl deploy \ + --add-feature hammer \ + --add-feature foreman-proxy + - name: Remove old parameters.yaml + run: rm -f .var/lib/foremanctl/parameters.yaml + - name: Generate certificates bundle on quadlet + run: | + ./foremanctl certificate-bundle content-proxy.example.com + - name: Copy certificates bundle to content-proxy + run: | + vagrant ssh quadlet -- sudo cat /var/lib/foremanctl/certs/bundles/content-proxy.example.com.tar.gz | \ + vagrant ssh content-proxy -- "sudo tee /root/content-proxy.example.com.tar.gz > /dev/null" + - name: Deploy foreman-proxy-content on content-proxy run: | OBSAH_ALLOW_EXTRA_VARS=true ./foremanctl deploy \ --flavor foreman-proxy-content \ + --certs-tar-file /root/content-proxy.example.com.tar.gz \ + --foreman-proxy-trusted-hosts quadlet.example.com \ + --foreman-url https://quadlet.example.com \ -e foremanctl_target=content-proxy - - name: Deploy features + - name: Deploy features on content-proxy run: | - ./foremanctl deploy \ - --add-feature remote-execution + OBSAH_ALLOW_EXTRA_VARS=true ./foremanctl deploy \ + --add-feature remote-execution \ + -e foremanctl_target=content-proxy - name: Run tests run: | ./forge test --pytest-args="--server-hostname=content-proxy --flavor=foreman-proxy-content" diff --git a/src/playbooks/_certificates_proxy/metadata.obsah.yaml b/src/playbooks/_certificates_proxy/metadata.obsah.yaml new file mode 100644 index 000000000..b64cfdf17 --- /dev/null +++ b/src/playbooks/_certificates_proxy/metadata.obsah.yaml @@ -0,0 +1,7 @@ +--- +variables: + certificate_tar_file: + help: Path to the certificate tar file generated by 'foremanctl certificate-bundle'. + type: AbsolutePath + parameter: --certs-tar-file + persist: false diff --git a/src/playbooks/_flavors/foreman-proxy-content/metadata.obsah.yaml b/src/playbooks/_flavors/foreman-proxy-content/metadata.obsah.yaml index c25997627..18f5f195e 100644 --- a/src/playbooks/_flavors/foreman-proxy-content/metadata.obsah.yaml +++ b/src/playbooks/_flavors/foreman-proxy-content/metadata.obsah.yaml @@ -3,5 +3,6 @@ include: - _certificate_source - _certificate_validity - _certificates_custom + - _certificates_proxy - _pulp - _foreman_proxy diff --git a/src/playbooks/_foreman_proxy/metadata.obsah.yaml b/src/playbooks/_foreman_proxy/metadata.obsah.yaml index 4d883ca58..0427d8bba 100644 --- a/src/playbooks/_foreman_proxy/metadata.obsah.yaml +++ b/src/playbooks/_foreman_proxy/metadata.obsah.yaml @@ -17,6 +17,8 @@ variables: help: FQDN of a host trusted by the proxy. action: append_unique foreman_proxy_oauth_consumer_key: + parameter: --oauth-consumer-key help: OAuth key to be used for communication with Foreman. foreman_proxy_oauth_consumer_secret: + parameter: --oauth-consumer-secret help: OAuth secret to be used for communication with Foreman. diff --git a/src/playbooks/deploy/deploy.yaml b/src/playbooks/deploy/deploy.yaml index c25b7023d..0747adb55 100644 --- a/src/playbooks/deploy/deploy.yaml +++ b/src/playbooks/deploy/deploy.yaml @@ -17,7 +17,6 @@ - role: checks vars: checks_databases: "{{ all_databases }}" - # TODO: Add tar_file check/extractation step for certificates_source == 'proxy' - role: certificates when: "certificates_source in ['default', 'custom_server']" - role: certificate_checks diff --git a/src/playbooks/deploy/metadata.obsah.yaml b/src/playbooks/deploy/metadata.obsah.yaml index e3ac3c475..eb469a9aa 100644 --- a/src/playbooks/deploy/metadata.obsah.yaml +++ b/src/playbooks/deploy/metadata.obsah.yaml @@ -14,3 +14,4 @@ variables: include: - _flavor_features - _flavors/katello + - _certificates_proxy # Temporary until we dynamically include correct _flavors/* diff --git a/src/roles/certificates/tasks/ca.yml b/src/roles/certificates/tasks/ca.yml index 117a2c6ed..418b84b35 100644 --- a/src/roles/certificates/tasks/ca.yml +++ b/src/roles/certificates/tasks/ca.yml @@ -7,7 +7,6 @@ - name: 'Generate CA' when: - (not _certificates_ca_cert.stat.exists or certificates_renew_ca) - - certificates_generate block: - name: 'Create CA key password file' ansible.builtin.copy: diff --git a/src/roles/certificates/tasks/main.yml b/src/roles/certificates/tasks/main.yml index ef4d96393..d36d83497 100644 --- a/src/roles/certificates/tasks/main.yml +++ b/src/roles/certificates/tasks/main.yml @@ -31,7 +31,9 @@ - name: 'Generate CA certificate' ansible.builtin.include_tasks: ca.yml - when: certificates_ca + when: + - certificates_ca + - certificates_generate - name: Issue host certificates ansible.builtin.include_tasks: host.yml @@ -42,6 +44,12 @@ loop_control: loop_var: certificates_hostname +- name: Extract proxy certificate bundle + ansible.builtin.include_tasks: proxy.yml + when: + - not certificates_generate + - certificate_tar_file is defined + - name: Create CA bundle with internal CA and custom server CA ansible.builtin.assemble: src: "{{ certificates_ca_directory_certs }}" diff --git a/src/roles/certificates/tasks/proxy.yml b/src/roles/certificates/tasks/proxy.yml new file mode 100644 index 000000000..2f34bbb12 --- /dev/null +++ b/src/roles/certificates/tasks/proxy.yml @@ -0,0 +1,35 @@ +--- +- name: Check path to certificate tar file exists + ansible.builtin.stat: + path: "{{ certificate_tar_file }}" + register: _certificate_tar_file_path + +- name: Fail if path to certificate tar file does not exist + ansible.builtin.fail: + msg: "Path to certificate tar file not found: {{ certificate_tar_file }}" + when: not _certificate_tar_file_path.stat.exists + +- name: Extract certificate bundle + ansible.builtin.unarchive: + src: "{{ certificate_tar_file }}" + dest: "{{ certificates_ca_directory }}" + remote_src: true + extra_opts: + - --strip-components=1 + +- name: Set private key directory permissions + ansible.builtin.file: + path: "{{ certificates_ca_directory_keys }}" + state: directory + mode: '0750' + +- name: Restrict private key files + ansible.builtin.shell: + cmd: "chmod 0640 {{ certificates_ca_directory_keys }}/*" + +- name: Create CA bundle + ansible.builtin.assemble: + src: "{{ certificates_ca_directory_certs }}" + dest: "{{ certificates_ca_directory_certs }}/ca-bundle.crt" + regexp: '(ca|server-ca)\.crt$' + mode: '0444' diff --git a/src/roles/foreman_proxy/handlers/main.yml b/src/roles/foreman_proxy/handlers/main.yml index e10d0f8f8..204ba5858 100644 --- a/src/roles/foreman_proxy/handlers/main.yml +++ b/src/roles/foreman_proxy/handlers/main.yml @@ -11,4 +11,3 @@ oauth1_consumer_key: "{{ foreman_proxy_oauth_consumer_key }}" oauth1_consumer_secret: "{{ foreman_proxy_oauth_consumer_secret }}" ca_path: "{{ foreman_proxy_foreman_ca_certificate | default(omit) }}" - when: not foreman_proxy_with_pulp_mirror diff --git a/src/roles/httpd/defaults/main.yml b/src/roles/httpd/defaults/main.yml index b4ab7dc29..f54656aae 100644 --- a/src/roles/httpd/defaults/main.yml +++ b/src/roles/httpd/defaults/main.yml @@ -7,6 +7,7 @@ httpd_enabled_pulp_snippets: [] httpd_proxy_foreman_url: "{{ foreman_proxy_foreman_server_url }}" +httpd_proxy_foreman_fqdn: "{{ httpd_proxy_foreman_url | urlsplit('hostname') }}" # MPM event module defaults httpd_server_limit: 25 httpd_start_servers: 2 diff --git a/src/roles/httpd/templates/rhsm-pulpcore-vhost.conf.j2 b/src/roles/httpd/templates/rhsm-pulpcore-vhost.conf.j2 index 52c9267fb..b254cdf5f 100644 --- a/src/roles/httpd/templates/rhsm-pulpcore-vhost.conf.j2 +++ b/src/roles/httpd/templates/rhsm-pulpcore-vhost.conf.j2 @@ -1,6 +1,16 @@ ServerName {{ ansible_facts['fqdn'] }} + ## SSL directives + SSLEngine on + SSLCertificateFile "{{ httpd_ssl_dir }}/certs/katello-apache.crt" + SSLCertificateKeyFile "{{ httpd_ssl_dir }}/private/katello-apache.key" + SSLCertificateChainFile "{{ httpd_ssl_dir }}/certs/katello-server-ca.crt" + SSLVerifyClient optional + SSLVerifyDepth 10 + SSLCACertificateFile "{{ httpd_ssl_dir }}/certs/katello-default-ca.crt" + SSLOptions +StdEnvVars +ExportCertData + ## Logging ErrorLog "/var/log/httpd/rhsm-pulpcore_error.log" ServerSignature Off @@ -28,7 +38,7 @@ RequestHeader unset REMOTE_USER RequestHeader unset REMOTE-USER RequestHeader set REMOTE-USER "admin" "expr=%{SSL_CLIENT_S_DN_CN} == '{{ ansible_facts['fqdn'] }}'" - RequestHeader set REMOTE-USER "admin" "expr=%{SSL_CLIENT_S_DN_CN} == '{{ httpd_proxy_foreman_url }}'" + RequestHeader set REMOTE-USER "admin" "expr=%{SSL_CLIENT_S_DN_CN} == '{{ httpd_proxy_foreman_fqdn }}'" ProxyPass {{ httpd_pulp_api_backend }}/v2/ ProxyPassReverse {{ httpd_pulp_api_backend }}/v2/ @@ -47,7 +57,7 @@ RequestHeader unset REMOTE_USER RequestHeader unset REMOTE-USER RequestHeader set REMOTE-USER "admin" "expr=%{SSL_CLIENT_S_DN_CN} == '{{ ansible_facts['fqdn'] }}'" - RequestHeader set REMOTE-USER "admin" "expr=%{SSL_CLIENT_S_DN_CN} == '{{ httpd_proxy_foreman_url }}'" + RequestHeader set REMOTE-USER "admin" "expr=%{SSL_CLIENT_S_DN_CN} == '{{ httpd_proxy_foreman_fqdn }}'" ProxyPass {{ httpd_pulp_api_backend }}/pulp/api/v3 timeout=600 ProxyPassReverse {{ httpd_pulp_api_backend }}/pulp/api/v3 diff --git a/src/roles/pulp/defaults/main.yaml b/src/roles/pulp/defaults/main.yaml index 6e9178d61..20f641388 100644 --- a/src/roles/pulp/defaults/main.yaml +++ b/src/roles/pulp/defaults/main.yaml @@ -71,10 +71,10 @@ pulp_settings_other_env: PULP_REMOTE_USER_ENVIRON_NAME: "HTTP_REMOTE_USER" PULP_REST_FRAMEWORK__DEFAULT_AUTHENTICATION_CLASSES: >- ['rest_framework.authentication.SessionAuthentication', 'pulpcore.app.authentication.PulpRemoteUserAuthentication'] - SMART_PROXY_PULP_URL: "{{ pulp_pulp_url }}" - SMART_PROXY_MIRROR: "{{ pulp_mirror }}" - SMART_PROXY_RHSM_URL: "{{ pulp_rhsm_url }}" - SMART_PROXY_AUTH_METHODS: ['client_certificate'] + PULP_SMART_PROXY_PULP_URL: "{{ pulp_pulp_url }}" + PULP_SMART_PROXY_MIRROR: "{{ pulp_mirror }}" + PULP_SMART_PROXY_RHSM_URL: "{{ pulp_rhsm_url }}" + PULP_SMART_PROXY_AUTH_METHODS: ['client_certificate'] PULP_API_WORKERS: "{{ pulp_api_service_worker_count }}" PULP_CONTENT_WORKERS: "{{ pulp_content_service_worker_count }}" PULP_TOKEN_AUTH_DISABLED: "true" From 5561a98171364c53befe940cde931059e92f326a Mon Sep 17 00:00:00 2001 From: Arvind Jangir Date: Sun, 21 Jun 2026 16:24:52 +0530 Subject: [PATCH 15/22] Redesign proxy deployment with proxy node --- .github/workflows/test.yml | 35 +++------- Vagrantfile | 4 +- development/playbooks/test/test.yaml | 2 +- docs/developer/deployment.md | 25 +++++++ docs/developer/testing.md | 35 ++++++++++ .../_certificates_proxy/metadata.obsah.yaml | 7 -- .../_flavor_features/metadata.obsah.yaml | 5 -- .../foreman-proxy-content/metadata.obsah.yaml | 4 -- .../_foreman_proxy/metadata.obsah.yaml | 12 ---- src/playbooks/deploy-proxy/deploy-proxy.yaml | 35 ++++++++++ .../deploy-proxy/metadata.obsah.yaml | 30 ++++++++ src/playbooks/deploy/deploy.yaml | 20 +++--- src/playbooks/deploy/metadata.obsah.yaml | 5 +- src/playbooks/health/health.yaml | 2 +- src/playbooks/pull-images/pull-images.yaml | 2 +- src/roles/certificates/defaults/main.yml | 1 - src/roles/certificates/tasks/extract.yml | 18 +++++ src/roles/certificates/tasks/main.yml | 61 ++++++++-------- src/roles/certificates/tasks/proxy.yml | 35 ---------- .../templates/rhsm-pulpcore-vhost.conf.j2 | 2 +- src/roles/post_install/tasks/main.yaml | 2 +- src/roles/pulp/tasks/main.yaml | 1 - src/vars/flavors/foreman-proxy-content.yml | 1 - tests/certificate_bundle_test.py | 3 + tests/certificates_test.py | 2 + tests/conftest.py | 16 ++--- .../foreman-proxy-content/httpd_test.py | 49 +++++++++++++ .../flavor/foreman-proxy-content/pulp_test.py | 70 +++++++++++++++++++ tests/httpd_test.py | 1 + 29 files changed, 337 insertions(+), 148 deletions(-) delete mode 100644 src/playbooks/_certificates_proxy/metadata.obsah.yaml create mode 100644 src/playbooks/deploy-proxy/deploy-proxy.yaml create mode 100644 src/playbooks/deploy-proxy/metadata.obsah.yaml create mode 100644 src/roles/certificates/tasks/extract.yml delete mode 100644 src/roles/certificates/tasks/proxy.yml create mode 100644 tests/flavor/foreman-proxy-content/pulp_test.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c12433c39..fecf96d7a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -374,7 +374,7 @@ jobs: foreman-proxy-content-tests: runs-on: ubuntu-24.04 - name: "Tests (flavor: foreman-proxy-content)" + name: "Tests Proxy Deployment" steps: - uses: actions/checkout@v6 - name: Set up Python @@ -391,10 +391,7 @@ jobs: run: ./setup-environment - name: Start VMs run: | - ./forge vms start --vms "quadlet content-proxy" - - name: Configure repositories - run: | - ./forge setup-repositories + ./forge vms start --vms "quadlet proxy" - name: Run image pull run: | ./foremanctl pull-images @@ -402,37 +399,27 @@ jobs: run: | ./foremanctl deploy \ --initial-admin-password=changeme \ - --tuning development - - name: Deploy features on quadlet - run: | - ./foremanctl deploy \ - --add-feature hammer \ + --tuning development \ --add-feature foreman-proxy - name: Remove old parameters.yaml run: rm -f .var/lib/foremanctl/parameters.yaml - name: Generate certificates bundle on quadlet run: | - ./foremanctl certificate-bundle content-proxy.example.com + ./foremanctl certificate-bundle proxy.example.com - name: Copy certificates bundle to content-proxy run: | - vagrant ssh quadlet -- sudo cat /var/lib/foremanctl/certs/bundles/content-proxy.example.com.tar.gz | \ - vagrant ssh content-proxy -- "sudo tee /root/content-proxy.example.com.tar.gz > /dev/null" - - name: Deploy foreman-proxy-content on content-proxy + vagrant ssh quadlet -- sudo cat /var/lib/foremanctl/certs/bundles/proxy.example.com.tar.gz | \ + vagrant ssh proxy -- "sudo tee /root/proxy.example.com.tar.gz > /dev/null" + - name: Deploy content proxy run: | - OBSAH_ALLOW_EXTRA_VARS=true ./foremanctl deploy \ + ./foremanctl deploy-proxy \ --flavor foreman-proxy-content \ - --certs-tar-file /root/content-proxy.example.com.tar.gz \ + --certs-tar-file /root/proxy.example.com.tar.gz \ --foreman-proxy-trusted-hosts quadlet.example.com \ - --foreman-url https://quadlet.example.com \ - -e foremanctl_target=content-proxy - - name: Deploy features on content-proxy - run: | - OBSAH_ALLOW_EXTRA_VARS=true ./foremanctl deploy \ - --add-feature remote-execution \ - -e foremanctl_target=content-proxy + --foreman-url https://quadlet.example.com - name: Run tests run: | - ./forge test --pytest-args="--server-hostname=content-proxy --flavor=foreman-proxy-content" + ./forge test --pytest-args="--server-hostname=proxy" - name: Archive smoker report if: ${{ always() }} uses: actions/upload-artifact@v7 diff --git a/Vagrantfile b/Vagrantfile index 311b73a1b..b69ff07e2 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -48,9 +48,9 @@ Vagrant.configure("2") do |config| end end - config.vm.define "content-proxy" do |override| + config.vm.define "proxy" do |override| override.vm.box = "centos/stream9" - override.vm.hostname = "content-proxy.#{DOMAIN}" + override.vm.hostname = "proxy.#{DOMAIN}" override.vm.provider "libvirt" do |libvirt, provider| libvirt.memory = 4096 diff --git a/development/playbooks/test/test.yaml b/development/playbooks/test/test.yaml index e60924ac0..9e1050b18 100644 --- a/development/playbooks/test/test.yaml +++ b/development/playbooks/test/test.yaml @@ -1,7 +1,7 @@ --- - name: Install test dependencies hosts: - - "{{ foremanctl_target | default('quadlet') }}" + - quadlet become: true tasks: - name: Install testing dependencies diff --git a/docs/developer/deployment.md b/docs/developer/deployment.md index cffaba067..ab5f99db5 100644 --- a/docs/developer/deployment.md +++ b/docs/developer/deployment.md @@ -1,5 +1,30 @@ # Deployment Design +## Deployment Types + +foremanctl supports two deployment types: **server** and **proxy**. Each has its own sub-command, flavor, and set of services. + +### Server + +Deploys a Foreman server. This is the primary deployment type and the default entry point. + + +```bash +./foremanctl deploy +``` + +### Proxy + +Deploys a Foreman Smart Proxy node that connects to an foreman server. + +```bash +./foremanctl deploy-proxy \ + --flavor foreman-proxy-content \ + --certs-tar-file=/root/proxy.example.com-certs.tar \ + --foreman-url=https://server.example.com \ + --foreman-proxy-trusted-hosts quadlet.example.com +``` + ## Deployment Paths ### Happy Path diff --git a/docs/developer/testing.md b/docs/developer/testing.md index bb8cdb832..c2c15dbb2 100644 --- a/docs/developer/testing.md +++ b/docs/developer/testing.md @@ -172,6 +172,40 @@ def test_ingress_http_endpoint(server): # ... ``` +### Flavor-specific tests + +Some assertions only apply to a particular deployment flavor (for example, Katello expects `candlepin` and `pulp` databases; but a content proxy does not). Put flavor specific tests under `tests/flavor//`. Tests outside that tree always run regardless of flavor. + +#### How tests are selected + +During collection, `pytest_collection_modifyitems` in `tests/conftest.py` reads the active flavor from the foremanctl parameters file (`.var/lib/foremanctl/parameters.yaml`). It defaults to `katello`. + +For each collected test: + +- If the test file is **not** under `tests/flavor/`, it is kept. +- If it **is** under `tests/flavor/`, it is kept only when it lives in `tests/flavor//`; tests in sibling flavor directories are deselected. + +So a Katello deployment runs `tests/flavor/katello/` plus all shared tests, but skips `tests/flavor/foreman-proxy-content/`. After `./foremanctl deploy-proxy --flavor foreman-proxy-content`, the opposite applies. + +This is separate from [feature guarding](#feature-guarding): feature marks skip tests at runtime based on enabled features; flavor selection happens at collection time from the deployed flavor. + +#### Layout + +``` +tests/ + postgresql_test.py # shared — always collected + flavor/ + katello/ # runs only when flavor is katello + postgresql_test.py + images_test.py + foreman-proxy-content/ # runs only when flavor is foreman-proxy-content + pulp_test.py + httpd_test.py + postgresql_test.py +``` + +Use feature marks when behavior depends on an optional add-on feature; use `tests/flavor//` when the whole deployment type differs (server vs content proxy, different services, different defaults). + ### API test The `foremanapi` fixture is an [apypie](https://github.com/Apipie/apypie) `ForemanApi` client that connects to the deployed Foreman instance(authenticated as `admin`/`changeme`). It maps directly to the Foreman REST API — each method takes a resource name that corresponds to an API endpoint: @@ -232,5 +266,6 @@ def test_dynflow_service_instances(server, instance): | Client registration or content workflows | `tests/client_test.py` | | CLI flags, playbooks, or feature management | `tests/features_test.py` or `tests/playbooks_test.py` | | A standalone script (no deployment needed) | `tests/unit/_test.py` | +| Behavior specific to a deployment flavor | `tests/flavor//_test.py` | When adding a new file, follow the conventions of the nearest existing test file. diff --git a/src/playbooks/_certificates_proxy/metadata.obsah.yaml b/src/playbooks/_certificates_proxy/metadata.obsah.yaml deleted file mode 100644 index b64cfdf17..000000000 --- a/src/playbooks/_certificates_proxy/metadata.obsah.yaml +++ /dev/null @@ -1,7 +0,0 @@ ---- -variables: - certificate_tar_file: - help: Path to the certificate tar file generated by 'foremanctl certificate-bundle'. - type: AbsolutePath - parameter: --certs-tar-file - persist: false diff --git a/src/playbooks/_flavor_features/metadata.obsah.yaml b/src/playbooks/_flavor_features/metadata.obsah.yaml index 02e614b4c..532a92ddf 100644 --- a/src/playbooks/_flavor_features/metadata.obsah.yaml +++ b/src/playbooks/_flavor_features/metadata.obsah.yaml @@ -1,10 +1,5 @@ --- variables: - flavor: - help: Base flavor to use in this deployment. - choices: - - katello - - foreman-proxy-content features: parameter: --add-feature help: Additional features to enable in this deployment. diff --git a/src/playbooks/_flavors/foreman-proxy-content/metadata.obsah.yaml b/src/playbooks/_flavors/foreman-proxy-content/metadata.obsah.yaml index 18f5f195e..073e70a34 100644 --- a/src/playbooks/_flavors/foreman-proxy-content/metadata.obsah.yaml +++ b/src/playbooks/_flavors/foreman-proxy-content/metadata.obsah.yaml @@ -1,8 +1,4 @@ --- include: - - _certificate_source - - _certificate_validity - - _certificates_custom - - _certificates_proxy - _pulp - _foreman_proxy diff --git a/src/playbooks/_foreman_proxy/metadata.obsah.yaml b/src/playbooks/_foreman_proxy/metadata.obsah.yaml index 0427d8bba..c0d3da1c2 100644 --- a/src/playbooks/_foreman_proxy/metadata.obsah.yaml +++ b/src/playbooks/_foreman_proxy/metadata.obsah.yaml @@ -10,15 +10,3 @@ variables: parameter: --bmc-redfish-verify-ssl help: Verify SSL certificates for Redfish BMC connections. type: Boolean - foreman_proxy_foreman_server_url: - parameter: --foreman-url - help: URL of Foreman server this proxy connects to. - foreman_proxy_trusted_hosts: - help: FQDN of a host trusted by the proxy. - action: append_unique - foreman_proxy_oauth_consumer_key: - parameter: --oauth-consumer-key - help: OAuth key to be used for communication with Foreman. - foreman_proxy_oauth_consumer_secret: - parameter: --oauth-consumer-secret - help: OAuth secret to be used for communication with Foreman. diff --git a/src/playbooks/deploy-proxy/deploy-proxy.yaml b/src/playbooks/deploy-proxy/deploy-proxy.yaml new file mode 100644 index 000000000..d726359dd --- /dev/null +++ b/src/playbooks/deploy-proxy/deploy-proxy.yaml @@ -0,0 +1,35 @@ +--- +- name: Setup proxy machine + hosts: + - proxy + become: true + vars_files: + - "../../vars/defaults.yml" + - "../../vars/flavors/{{ flavor }}.yml" + - "../../vars/certificates.yml" + - "../../vars/images.yml" + - "../../vars/tuning/{{ tuning }}.yml" + - "../../vars/database.yml" + - "../../vars/foreman.yml" + - "../../vars/base.yaml" + roles: + - role: pre_install + - role: checks + vars: + checks_databases: "{{ all_databases }}" + - role: certificates + when: "certificates_source in ['default', 'custom_server']" + - role: certificate_checks + vars: + certificate_checks_certificate: "{{ server_certificate }}" + certificate_checks_key: "{{ server_key }}" + certificate_checks_ca: "{{ server_ca_certificate }}" + - role: postgresql + when: + - database_mode == 'internal' + - redis + - httpd + - pulp + - systemd_target + - foreman_proxy + - post_install diff --git a/src/playbooks/deploy-proxy/metadata.obsah.yaml b/src/playbooks/deploy-proxy/metadata.obsah.yaml new file mode 100644 index 000000000..d5ab0eb4e --- /dev/null +++ b/src/playbooks/deploy-proxy/metadata.obsah.yaml @@ -0,0 +1,30 @@ +--- +help: | + Install node + +variables: + flavor: + help: Base flavor to use in this deployment. + choices: + - foreman-proxy-content + certificates_bundle: + help: Path to the certificate bundle tar file. + type: AbsolutePath + parameter: --certs-tar-file + persist: false + foreman_proxy_foreman_server_url: + parameter: --foreman-url + help: URL of Foreman server this proxy connects to. + foreman_proxy_trusted_hosts: + help: FQDN of a host trusted by the proxy. + action: append_unique + foreman_proxy_oauth_consumer_key: + parameter: --oauth-consumer-key + help: OAuth key to be used for communication with Foreman. + foreman_proxy_oauth_consumer_secret: + parameter: --oauth-consumer-secret + help: OAuth secret to be used for communication with Foreman. + +include: + - _flavor_features + - _flavors/foreman-proxy-content diff --git a/src/playbooks/deploy/deploy.yaml b/src/playbooks/deploy/deploy.yaml index 0747adb55..ff531ca57 100644 --- a/src/playbooks/deploy/deploy.yaml +++ b/src/playbooks/deploy/deploy.yaml @@ -1,7 +1,7 @@ --- - name: Setup machine hosts: - - "{{ foremanctl_target | default('quadlet') }}" + - quadlet become: true vars_files: - "../../vars/defaults.yml" @@ -20,31 +20,27 @@ - role: certificates when: "certificates_source in ['default', 'custom_server']" - role: certificate_checks - when: certificates_generate | default(true) vars: certificate_checks_certificate: "{{ server_certificate }}" certificate_checks_key: "{{ server_key }}" certificate_checks_ca: "{{ server_ca_certificate }}" - role: postgresql when: - - database_mode == 'internal' + - database_mode == 'internal' - valkey - - role: candlepin - when: enabled_features | has_feature('candlepin') + - candlepin - httpd - - role: pulp - when: enabled_features | has_feature('pulp') - - role: foreman - when: enabled_features | has_feature('foreman') + - pulp + - foreman - role: systemd_target - role: iop_core when: - - enabled_features | has_feature('iop') + - "'iop' in enabled_features" - database_mode == 'internal' - role: foreman_proxy when: - - enabled_features | has_feature('foreman-proxy') + - "'foreman-proxy' in enabled_features" - role: hammer when: - - enabled_features | has_feature('hammer') + - "'hammer' in enabled_features" - post_install diff --git a/src/playbooks/deploy/metadata.obsah.yaml b/src/playbooks/deploy/metadata.obsah.yaml index eb469a9aa..a378460c1 100644 --- a/src/playbooks/deploy/metadata.obsah.yaml +++ b/src/playbooks/deploy/metadata.obsah.yaml @@ -3,6 +3,10 @@ help: | Install variables: + flavor: + help: Base flavor to use in this deployment. + choices: + - katello external_authentication: help: External authentication method to use choices: @@ -14,4 +18,3 @@ variables: include: - _flavor_features - _flavors/katello - - _certificates_proxy # Temporary until we dynamically include correct _flavors/* diff --git a/src/playbooks/health/health.yaml b/src/playbooks/health/health.yaml index 8164ebcd2..23787790f 100644 --- a/src/playbooks/health/health.yaml +++ b/src/playbooks/health/health.yaml @@ -1,6 +1,6 @@ --- - name: Run Foreman health checks - hosts: "{{ foremanctl_target | default('quadlet') }}" + hosts: quadlet become: true gather_facts: true vars_files: diff --git a/src/playbooks/pull-images/pull-images.yaml b/src/playbooks/pull-images/pull-images.yaml index 630c315be..41baea33f 100644 --- a/src/playbooks/pull-images/pull-images.yaml +++ b/src/playbooks/pull-images/pull-images.yaml @@ -1,7 +1,7 @@ --- - name: Pull images hosts: - - "{{ foremanctl_target | default('quadlet') }}" + - quadlet vars_files: - "../../vars/defaults.yml" - "../../vars/flavors/{{ flavor }}.yml" diff --git a/src/roles/certificates/defaults/main.yml b/src/roles/certificates/defaults/main.yml index 6a79cb9bd..d460eee7e 100644 --- a/src/roles/certificates/defaults/main.yml +++ b/src/roles/certificates/defaults/main.yml @@ -17,4 +17,3 @@ certificates_ca_validity_days: 7300 certificates_validity_days: 7300 certificates_renew: false certificates_renew_ca: false -certificates_generate: true diff --git a/src/roles/certificates/tasks/extract.yml b/src/roles/certificates/tasks/extract.yml new file mode 100644 index 000000000..38a6a435c --- /dev/null +++ b/src/roles/certificates/tasks/extract.yml @@ -0,0 +1,18 @@ +--- +- name: Check path to certificate tar file exists + ansible.builtin.stat: + path: "{{ certificates_bundle }}" + register: _certificates_tar_file_path + +- name: Fail if path to certificate tar file does not exist + ansible.builtin.fail: + msg: "Path to certificate tar file not found: {{ certificates_bundle }}" + when: not _certificates_tar_file_path.stat.exists + +- name: Extract certificate bundle + ansible.builtin.unarchive: + src: "{{ certificates_bundle }}" + dest: "{{ certificates_ca_directory }}" + remote_src: true + extra_opts: + - --strip-components=1 diff --git a/src/roles/certificates/tasks/main.yml b/src/roles/certificates/tasks/main.yml index d36d83497..35b354747 100644 --- a/src/roles/certificates/tasks/main.yml +++ b/src/roles/certificates/tasks/main.yml @@ -29,33 +29,34 @@ state: directory mode: '0755' -- name: 'Generate CA certificate' - ansible.builtin.include_tasks: ca.yml - when: - - certificates_ca - - certificates_generate - -- name: Issue host certificates - ansible.builtin.include_tasks: host.yml - when: - - certificates_hostnames is defined - - certificates_generate - with_items: "{{ certificates_hostnames }}" - loop_control: - loop_var: certificates_hostname - -- name: Extract proxy certificate bundle - ansible.builtin.include_tasks: proxy.yml - when: - - not certificates_generate - - certificate_tar_file is defined - -- name: Create CA bundle with internal CA and custom server CA - ansible.builtin.assemble: - src: "{{ certificates_ca_directory_certs }}" - dest: "{{ certificates_ca_directory_certs }}/ca-bundle.crt" - regexp: '(ca|server-ca)\.crt$' - mode: '0444' - when: - - certificates_source == 'custom_server' - - certificates_custom_server_certificate is defined +- name: 'Generate certificates' + when: certificates_bundle is not defined + block: + - name: 'Generate CA certificate' + ansible.builtin.include_tasks: ca.yml + when: + - certificates_ca + + - name: Issue host certificates + ansible.builtin.include_tasks: host.yml + when: + - certificates_hostnames is defined + with_items: "{{ certificates_hostnames }}" + loop_control: + loop_var: certificates_hostname + + - name: Create CA bundle with internal CA and custom server CA + ansible.builtin.assemble: + src: "{{ certificates_ca_directory_certs }}" + dest: "{{ certificates_ca_directory_certs }}/ca-bundle.crt" + regexp: '(ca|server-ca)\.crt$' + mode: '0444' + when: + - certificates_source == 'custom_server' + - certificates_custom_server_certificate is defined + +- name: 'Consume certs' + when: certificates_bundle is defined + block: + - name: Extract proxy certificate bundle + ansible.builtin.include_tasks: extract.yml diff --git a/src/roles/certificates/tasks/proxy.yml b/src/roles/certificates/tasks/proxy.yml deleted file mode 100644 index 2f34bbb12..000000000 --- a/src/roles/certificates/tasks/proxy.yml +++ /dev/null @@ -1,35 +0,0 @@ ---- -- name: Check path to certificate tar file exists - ansible.builtin.stat: - path: "{{ certificate_tar_file }}" - register: _certificate_tar_file_path - -- name: Fail if path to certificate tar file does not exist - ansible.builtin.fail: - msg: "Path to certificate tar file not found: {{ certificate_tar_file }}" - when: not _certificate_tar_file_path.stat.exists - -- name: Extract certificate bundle - ansible.builtin.unarchive: - src: "{{ certificate_tar_file }}" - dest: "{{ certificates_ca_directory }}" - remote_src: true - extra_opts: - - --strip-components=1 - -- name: Set private key directory permissions - ansible.builtin.file: - path: "{{ certificates_ca_directory_keys }}" - state: directory - mode: '0750' - -- name: Restrict private key files - ansible.builtin.shell: - cmd: "chmod 0640 {{ certificates_ca_directory_keys }}/*" - -- name: Create CA bundle - ansible.builtin.assemble: - src: "{{ certificates_ca_directory_certs }}" - dest: "{{ certificates_ca_directory_certs }}/ca-bundle.crt" - regexp: '(ca|server-ca)\.crt$' - mode: '0444' diff --git a/src/roles/httpd/templates/rhsm-pulpcore-vhost.conf.j2 b/src/roles/httpd/templates/rhsm-pulpcore-vhost.conf.j2 index b254cdf5f..706e52f00 100644 --- a/src/roles/httpd/templates/rhsm-pulpcore-vhost.conf.j2 +++ b/src/roles/httpd/templates/rhsm-pulpcore-vhost.conf.j2 @@ -7,7 +7,7 @@ SSLCertificateKeyFile "{{ httpd_ssl_dir }}/private/katello-apache.key" SSLCertificateChainFile "{{ httpd_ssl_dir }}/certs/katello-server-ca.crt" SSLVerifyClient optional - SSLVerifyDepth 10 + SSLVerifyDepth 3 SSLCACertificateFile "{{ httpd_ssl_dir }}/certs/katello-default-ca.crt" SSLOptions +StdEnvVars +ExportCertData diff --git a/src/roles/post_install/tasks/main.yaml b/src/roles/post_install/tasks/main.yaml index 951ad825e..e8b049b5e 100644 --- a/src/roles/post_install/tasks/main.yaml +++ b/src/roles/post_install/tasks/main.yaml @@ -1,3 +1,3 @@ --- -- name: Run flavor-specific post install tasks +- name: Run post install tasks ansible.builtin.include_tasks: "{{ flavor }}.yaml" diff --git a/src/roles/pulp/tasks/main.yaml b/src/roles/pulp/tasks/main.yaml index 013a018a6..bf4bfe564 100644 --- a/src/roles/pulp/tasks/main.yaml +++ b/src/roles/pulp/tasks/main.yaml @@ -245,7 +245,6 @@ Type=oneshot RemainAfterExit=yes TimeoutStartSec=10m - when: not pulp_mirror - name: Run daemon reload to load service files ansible.builtin.systemd: diff --git a/src/vars/flavors/foreman-proxy-content.yml b/src/vars/flavors/foreman-proxy-content.yml index 5397421da..f3c97dd2f 100644 --- a/src/vars/flavors/foreman-proxy-content.yml +++ b/src/vars/flavors/foreman-proxy-content.yml @@ -7,7 +7,6 @@ flavor_features: - content/python pulp_mirror: true -certificates_generate: false checks_to_execute: - check_features diff --git a/tests/certificate_bundle_test.py b/tests/certificate_bundle_test.py index c6368b1b3..d4d56517d 100644 --- a/tests/certificate_bundle_test.py +++ b/tests/certificate_bundle_test.py @@ -2,6 +2,9 @@ import pytest +pytestmark = pytest.mark.feature('katello') + + HOSTNAME = 'proxy.example.com' TARBALL = f'/var/lib/foremanctl/certs/bundles/{HOSTNAME}.tar.gz' diff --git a/tests/certificates_test.py b/tests/certificates_test.py index e65a813b5..794eaa742 100644 --- a/tests/certificates_test.py +++ b/tests/certificates_test.py @@ -11,6 +11,8 @@ def certificate_info(server, certificate): @pytest.mark.parametrize("certificate_type", ['ca_certificate', 'server_ca_certificate', 'server_certificate', 'client_certificate', 'localhost_certificate']) def test_certificate_expiry(server, certificates, certificate_type): + if certificate_type == 'localhost_certificate' and not server.file(certificates[certificate_type]).exists: + pytest.skip("localhost certificate not present in proxy deployment") openssl_data = certificate_info(server, certificates[certificate_type]) not_after = dateutil.parser.parse(openssl_data['notAfter']) now = datetime.datetime.now(tz=not_after.tzinfo) diff --git a/tests/conftest.py b/tests/conftest.py index bb3fd4040..7ff81379b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -47,7 +47,12 @@ def enabled_features(self): def pytest_addoption(parser): parser.addoption("--server-hostname", action="store", default="quadlet", help="Hostname of the server VM to test against") - parser.addoption("--flavor", action="store", default="katello", choices=('foreman', 'katello', 'foreman-proxy-content'), help="Deployment flavor") + + +def flavor(): + with open(PARAMETERS_FILE) as f: + params = yaml.safe_load(f) + return params.get('flavor', 'katello') @pytest.fixture(scope="module") @@ -70,11 +75,6 @@ def server_hostname(pytestconfig): return pytestconfig.getoption("server_hostname") -@pytest.fixture(scope="module") -def flavor(pytestconfig): - return pytestconfig.getoption("flavor") - - @pytest.fixture(scope="module") def server_fqdn(server): return server.check_output('hostname -f') @@ -264,8 +264,8 @@ def pytest_configure(config): def pytest_collection_modifyitems(config, items): - flavor = config.getoption("flavor") - active_flavor_dir = FLAVOR_TESTS_DIR / flavor + active_flavor = flavor() + active_flavor_dir = FLAVOR_TESTS_DIR / active_flavor deselected = [] selected = [] diff --git a/tests/flavor/foreman-proxy-content/httpd_test.py b/tests/flavor/foreman-proxy-content/httpd_test.py index e69de29bb..17fd10fba 100644 --- a/tests/flavor/foreman-proxy-content/httpd_test.py +++ b/tests/flavor/foreman-proxy-content/httpd_test.py @@ -0,0 +1,49 @@ +CURL_CMD = "curl --silent --output /dev/null" + + +def test_rhsm_pulpcore_vhost_exists(server): + conf = server.file("/etc/httpd/conf.d/rhsm-pulpcore.conf") + assert conf.exists + assert conf.is_file + + +def test_https_pulp_api_with_client_cert(server, certificates, server_fqdn): + cmd = server.run( + f"curl --silent --cacert {certificates['server_ca_certificate']} " + f"--cert {certificates['client_certificate']} " + f"--key {certificates['client_key']} " + f"--write-out '%{{http_code}}' --output /dev/null " + f"https://{server_fqdn}/pulp/api/v3/smart_proxy/v2/features" + ) + assert cmd.succeeded + assert cmd.stdout == '200' + + +def test_https_rhsm_proxy(server, certificates, server_fqdn): + cmd = server.run( + f"{CURL_CMD} --cacert {certificates['server_ca_certificate']} " + f"--write-out '%{{http_code}}' " + f"https://{server_fqdn}/rhsm" + ) + assert cmd.succeeded + assert cmd.stdout not in ('404', '502', '503') + + +def test_https_pulp_content_proxy(server, certificates, server_fqdn): + cmd = server.run( + f"{CURL_CMD} --cacert {certificates['server_ca_certificate']} " + f"--write-out '%{{http_code}}' " + f"https://{server_fqdn}/pulp/content/" + ) + assert cmd.succeeded + assert cmd.stdout == '200' + + +def test_https_pulpcore_registry(server, certificates, server_fqdn): + cmd = server.run( + f"{CURL_CMD} --cacert {certificates['server_ca_certificate']} " + f"--write-out '%{{http_code}}' " + f"https://{server_fqdn}/pulpcore_registry/v2/" + ) + assert cmd.succeeded + assert cmd.stdout not in ('404', '502', '503') diff --git a/tests/flavor/foreman-proxy-content/pulp_test.py b/tests/flavor/foreman-proxy-content/pulp_test.py new file mode 100644 index 000000000..61dd0a9bd --- /dev/null +++ b/tests/flavor/foreman-proxy-content/pulp_test.py @@ -0,0 +1,70 @@ +import json + +import pytest + +FOREMAN_PROXY_PORT = 8443 + + +@pytest.fixture(scope="module") +def pulp_smart_proxy_features(server, certificates, server_fqdn): + cmd = server.run( + f"curl --silent --cacert {certificates['server_ca_certificate']} " + f"--cert {certificates['client_certificate']} " + f"--key {certificates['client_key']} " + f"https://{server_fqdn}/pulp/api/v3/smart_proxy/v2/features" + ) + assert cmd.succeeded, f"Failed to query smart_proxy features: {cmd.stderr}" + return json.loads(cmd.stdout) + + +@pytest.fixture(scope="module") +def pulp_settings(server): + py = ( + 'from django.conf import settings; import json; ' + 'print(json.dumps({"import": list(settings.ALLOWED_IMPORT_PATHS), ' + '"export": list(settings.ALLOWED_EXPORT_PATHS)}))' + ) + result = server.run(f"podman exec pulp-api pulpcore-manager shell -c '{py}'") + assert result.succeeded, f"Failed to read Pulp settings: {result.stderr}" + return json.loads(result.stdout) + + +def test_import_paths_restricted(pulp_settings): + assert '/var/lib/pulp/sync_imports' in pulp_settings['import'] + assert '/var/lib/pulp/imports' not in pulp_settings['import'] + + +def test_sync_imports_directory(server): + assert server.file("/var/lib/pulp/sync_imports").is_directory + + +def test_no_exports_directory(server): + assert not server.file("/var/lib/pulp/exports").exists + + +def test_no_imports_directory(server): + assert not server.file("/var/lib/pulp/imports").exists + + +def test_pulp_smart_proxy_mirror_mode(pulp_smart_proxy_features): + settings = pulp_smart_proxy_features['pulpcore'].get('settings', {}) + assert settings.get('mirror') is True + assert 'client_certificate' in settings.get('client_authentication', []) + + +def test_pulp_smart_proxy_features(pulp_smart_proxy_features): + features = pulp_smart_proxy_features + assert 'pulpcore' in features + capabilities = features['pulpcore'].get('capabilities', []) + for expected in ('core', 'smart_proxy', 'rpm', 'deb', 'ansible', 'python', 'container', 'file', 'certguard'): + assert expected in capabilities, f"Missing capability: {expected}" + + +def test_pulp_api_status(server, certificates, server_fqdn): + cmd = server.run( + f"curl --silent --output /dev/null --cacert {certificates['server_ca_certificate']} " + f"--write-out '%{{http_code}}' " + f"https://{server_fqdn}/pulp/api/v3/status/" + ) + assert cmd.succeeded + assert cmd.stdout == '200' diff --git a/tests/httpd_test.py b/tests/httpd_test.py index b4cb76ec1..9e7e70d43 100644 --- a/tests/httpd_test.py +++ b/tests/httpd_test.py @@ -67,6 +67,7 @@ def test_https_pulp_auth(server, certificates, server_fqdn): assert cmd.stdout == '200' +@pytest.mark.feature('katello') def test_https_pypi_endpoint(server, certificates, server_fqdn): cmd = server.run(f"curl --cacert {certificates['server_ca_certificate']} https://{server_fqdn}/pypi/test/") assert cmd.succeeded From 8e1e2f09065027f65062bf8c3d8661b7450693d8 Mon Sep 17 00:00:00 2001 From: Arvind Jangir Date: Wed, 24 Jun 2026 21:16:07 +0530 Subject: [PATCH 16/22] Cleanup and Dry code --- .github/workflows/test.yml | 5 ++-- .../playbooks/deploy-dev/deploy-dev.yaml | 1 + docs/developer/deployment.md | 5 ++-- src/playbooks/deploy-proxy/deploy-proxy.yaml | 1 - .../deploy-proxy/metadata.obsah.yaml | 11 ++++----- src/playbooks/deploy/deploy.yaml | 3 +-- src/roles/certificates/defaults/main.yml | 1 + src/roles/certificates/tasks/main.yml | 8 +++++-- src/roles/foreman/tasks/main.yaml | 9 ------- src/roles/foreman_proxy/defaults/main.yaml | 3 --- src/roles/foreman_proxy/tasks/main.yaml | 10 -------- .../foreman_proxy/templates/settings.yml.j2 | 4 +--- src/roles/httpd/defaults/main.yml | 3 --- .../templates/rhsm-pulpcore-vhost.conf.j2 | 24 +++++++++---------- src/roles/pulp/defaults/main.yaml | 1 + src/roles/pulp/tasks/main.yaml | 10 ++++++++ src/vars/base.yaml | 6 +++++ src/vars/flavors/foreman-proxy-content.yml | 2 ++ 18 files changed, 49 insertions(+), 58 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fecf96d7a..399ef6e15 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -414,9 +414,8 @@ jobs: run: | ./foremanctl deploy-proxy \ --flavor foreman-proxy-content \ - --certs-tar-file /root/proxy.example.com.tar.gz \ - --foreman-proxy-trusted-hosts quadlet.example.com \ - --foreman-url https://quadlet.example.com + --certificate-bundle /root/proxy.example.com.tar.gz \ + --foreman-fqdn quadlet.example.com - name: Run tests run: | ./forge test --pytest-args="--server-hostname=proxy" diff --git a/development/playbooks/deploy-dev/deploy-dev.yaml b/development/playbooks/deploy-dev/deploy-dev.yaml index a1485f6b6..a4214e10e 100644 --- a/development/playbooks/deploy-dev/deploy-dev.yaml +++ b/development/playbooks/deploy-dev/deploy-dev.yaml @@ -12,6 +12,7 @@ - "../../../src/vars/base.yaml" vars: httpd_foreman_backend: "http://localhost:3000" + pulp_register_foreman_proxy: false pre_tasks: - name: Set development postgresql databases ansible.builtin.set_fact: diff --git a/docs/developer/deployment.md b/docs/developer/deployment.md index ab5f99db5..2dd1af767 100644 --- a/docs/developer/deployment.md +++ b/docs/developer/deployment.md @@ -20,9 +20,8 @@ Deploys a Foreman Smart Proxy node that connects to an foreman server. ```bash ./foremanctl deploy-proxy \ --flavor foreman-proxy-content \ - --certs-tar-file=/root/proxy.example.com-certs.tar \ - --foreman-url=https://server.example.com \ - --foreman-proxy-trusted-hosts quadlet.example.com + --certificate-bundle /root/proxy.example.com-certs.tar \ + --foreman-fqdn quadlet.example.com ``` ## Deployment Paths diff --git a/src/playbooks/deploy-proxy/deploy-proxy.yaml b/src/playbooks/deploy-proxy/deploy-proxy.yaml index d726359dd..38b97755c 100644 --- a/src/playbooks/deploy-proxy/deploy-proxy.yaml +++ b/src/playbooks/deploy-proxy/deploy-proxy.yaml @@ -18,7 +18,6 @@ vars: checks_databases: "{{ all_databases }}" - role: certificates - when: "certificates_source in ['default', 'custom_server']" - role: certificate_checks vars: certificate_checks_certificate: "{{ server_certificate }}" diff --git a/src/playbooks/deploy-proxy/metadata.obsah.yaml b/src/playbooks/deploy-proxy/metadata.obsah.yaml index d5ab0eb4e..32afe749e 100644 --- a/src/playbooks/deploy-proxy/metadata.obsah.yaml +++ b/src/playbooks/deploy-proxy/metadata.obsah.yaml @@ -10,14 +10,11 @@ variables: certificates_bundle: help: Path to the certificate bundle tar file. type: AbsolutePath - parameter: --certs-tar-file + parameter: --certificate-bundle persist: false - foreman_proxy_foreman_server_url: - parameter: --foreman-url - help: URL of Foreman server this proxy connects to. - foreman_proxy_trusted_hosts: - help: FQDN of a host trusted by the proxy. - action: append_unique + foreman_name: + parameter: --foreman-fqdn + help: FQDN of the Foreman server this proxy connects to. foreman_proxy_oauth_consumer_key: parameter: --oauth-consumer-key help: OAuth key to be used for communication with Foreman. diff --git a/src/playbooks/deploy/deploy.yaml b/src/playbooks/deploy/deploy.yaml index ff531ca57..bd3304cb7 100644 --- a/src/playbooks/deploy/deploy.yaml +++ b/src/playbooks/deploy/deploy.yaml @@ -18,7 +18,6 @@ vars: checks_databases: "{{ all_databases }}" - role: certificates - when: "certificates_source in ['default', 'custom_server']" - role: certificate_checks vars: certificate_checks_certificate: "{{ server_certificate }}" @@ -30,8 +29,8 @@ - valkey - candlepin - httpd - - pulp - foreman + - pulp - role: systemd_target - role: iop_core when: diff --git a/src/roles/certificates/defaults/main.yml b/src/roles/certificates/defaults/main.yml index d460eee7e..6a79cb9bd 100644 --- a/src/roles/certificates/defaults/main.yml +++ b/src/roles/certificates/defaults/main.yml @@ -17,3 +17,4 @@ certificates_ca_validity_days: 7300 certificates_validity_days: 7300 certificates_renew: false certificates_renew_ca: false +certificates_generate: true diff --git a/src/roles/certificates/tasks/main.yml b/src/roles/certificates/tasks/main.yml index 35b354747..ac793d261 100644 --- a/src/roles/certificates/tasks/main.yml +++ b/src/roles/certificates/tasks/main.yml @@ -30,7 +30,9 @@ mode: '0755' - name: 'Generate certificates' - when: certificates_bundle is not defined + when: + - certificates_bundle is not defined + - certificates_generate block: - name: 'Generate CA certificate' ansible.builtin.include_tasks: ca.yml @@ -56,7 +58,9 @@ - certificates_custom_server_certificate is defined - name: 'Consume certs' - when: certificates_bundle is defined + when: + - certificates_bundle is defined + - not certificates_generate block: - name: Extract proxy certificate bundle ansible.builtin.include_tasks: extract.yml diff --git a/src/roles/foreman/tasks/main.yaml b/src/roles/foreman/tasks/main.yaml index dd5282760..b74cc3656 100644 --- a/src/roles/foreman/tasks/main.yaml +++ b/src/roles/foreman/tasks/main.yaml @@ -325,12 +325,3 @@ register: foreman_tasks_status when: - "'katello' in foreman_status.json['results']" - -- name: Configure Foreman Proxy - theforeman.foreman.smart_proxy: - name: "{{ ansible_facts['fqdn'] }}-pulp" - url: "https://{{ ansible_facts['fqdn'] }}/pulp/api/v3/smart_proxy" - server_url: "{{ foreman_url }}" - oauth1_consumer_key: "{{ foreman_oauth_consumer_key }}" - oauth1_consumer_secret: "{{ foreman_oauth_consumer_secret }}" - ca_path: "{{ foreman_ca_certificate }}" diff --git a/src/roles/foreman_proxy/defaults/main.yaml b/src/roles/foreman_proxy/defaults/main.yaml index f8a267f93..4ae4068f9 100644 --- a/src/roles/foreman_proxy/defaults/main.yaml +++ b/src/roles/foreman_proxy/defaults/main.yaml @@ -7,9 +7,6 @@ foreman_proxy_https_port: 8443 foreman_proxy_url: "https://{{ foreman_proxy_name }}:{{ foreman_proxy_https_port }}" # Settings -foreman_proxy_default_trusted_hosts: - - "{{ foreman_proxy_name }}" - foreman_proxy_trusted_hosts: [] foreman_proxy_base_features: diff --git a/src/roles/foreman_proxy/tasks/main.yaml b/src/roles/foreman_proxy/tasks/main.yaml index bd8877ea8..8033c9457 100644 --- a/src/roles/foreman_proxy/tasks/main.yaml +++ b/src/roles/foreman_proxy/tasks/main.yaml @@ -75,15 +75,5 @@ oauth1_consumer_secret: "{{ foreman_proxy_oauth_consumer_secret }}" ca_path: "{{ foreman_proxy_foreman_ca_certificate | default(omit) }}" -- name: Register Pulp Proxy to Foreman - theforeman.foreman.smart_proxy: - name: "{{ foreman_proxy_name }}-pulp" - url: "https://{{ foreman_proxy_name }}/pulp/api/v3/smart_proxy" - server_url: "{{ foreman_proxy_foreman_server_url }}" - oauth1_consumer_key: "{{ foreman_proxy_oauth_consumer_key }}" - oauth1_consumer_secret: "{{ foreman_proxy_oauth_consumer_secret }}" - ca_path: "{{ foreman_proxy_foreman_ca_certificate | default(omit) }}" - when: foreman_proxy_with_pulp_mirror - - name: Flush handlers to restart services ansible.builtin.meta: flush_handlers diff --git a/src/roles/foreman_proxy/templates/settings.yml.j2 b/src/roles/foreman_proxy/templates/settings.yml.j2 index e737a8e82..80d49ac0b 100644 --- a/src/roles/foreman_proxy/templates/settings.yml.j2 +++ b/src/roles/foreman_proxy/templates/settings.yml.j2 @@ -2,9 +2,7 @@ :foreman_url: {{ foreman_proxy_foreman_server_url }} :trusted_hosts: -{% for host in foreman_proxy_default_trusted_hosts | union(foreman_proxy_trusted_hosts) %} - - {{ host }} -{% endfor %} + - {{ foreman_proxy_trusted_hosts }} :https_port: {{ foreman_proxy_https_port }} :ssl_ca_file: /etc/foreman-proxy/ssl_ca.pem diff --git a/src/roles/httpd/defaults/main.yml b/src/roles/httpd/defaults/main.yml index f54656aae..aab561ba4 100644 --- a/src/roles/httpd/defaults/main.yml +++ b/src/roles/httpd/defaults/main.yml @@ -5,9 +5,6 @@ httpd_foreman_backend: http://localhost:3000 httpd_pub_dir: /var/www/html/pub httpd_enabled_pulp_snippets: [] -httpd_proxy_foreman_url: "{{ foreman_proxy_foreman_server_url }}" - -httpd_proxy_foreman_fqdn: "{{ httpd_proxy_foreman_url | urlsplit('hostname') }}" # MPM event module defaults httpd_server_limit: 25 httpd_start_servers: 2 diff --git a/src/roles/httpd/templates/rhsm-pulpcore-vhost.conf.j2 b/src/roles/httpd/templates/rhsm-pulpcore-vhost.conf.j2 index 706e52f00..4b5cfd9a0 100644 --- a/src/roles/httpd/templates/rhsm-pulpcore-vhost.conf.j2 +++ b/src/roles/httpd/templates/rhsm-pulpcore-vhost.conf.j2 @@ -25,9 +25,9 @@ # SSL Proxy directives SSLProxyEngine On SSLProxyCACertificateFile "{{ httpd_ssl_dir }}/certs/katello-default-ca.crt" - ProxyPass /katello/api/v2/repositories/ {{ httpd_proxy_foreman_url }}/katello/api/v2/repositories/ + ProxyPass /katello/api/v2/repositories/ {{ httpd_foreman_url }}/katello/api/v2/repositories/ - ProxyPassReverse {{ httpd_proxy_foreman_url }}/katello/api/v2/repositories/ + ProxyPassReverse {{ httpd_foreman_url }}/katello/api/v2/repositories/ SSLProxyEngine On @@ -38,7 +38,7 @@ RequestHeader unset REMOTE_USER RequestHeader unset REMOTE-USER RequestHeader set REMOTE-USER "admin" "expr=%{SSL_CLIENT_S_DN_CN} == '{{ ansible_facts['fqdn'] }}'" - RequestHeader set REMOTE-USER "admin" "expr=%{SSL_CLIENT_S_DN_CN} == '{{ httpd_proxy_foreman_fqdn }}'" + RequestHeader set REMOTE-USER "admin" "expr=%{SSL_CLIENT_S_DN_CN} == '{{ foreman_name }}'" ProxyPass {{ httpd_pulp_api_backend }}/v2/ ProxyPassReverse {{ httpd_pulp_api_backend }}/v2/ @@ -57,7 +57,7 @@ RequestHeader unset REMOTE_USER RequestHeader unset REMOTE-USER RequestHeader set REMOTE-USER "admin" "expr=%{SSL_CLIENT_S_DN_CN} == '{{ ansible_facts['fqdn'] }}'" - RequestHeader set REMOTE-USER "admin" "expr=%{SSL_CLIENT_S_DN_CN} == '{{ httpd_proxy_foreman_fqdn }}'" + RequestHeader set REMOTE-USER "admin" "expr=%{SSL_CLIENT_S_DN_CN} == '{{ foreman_name }}'" ProxyPass {{ httpd_pulp_api_backend }}/pulp/api/v3 timeout=600 ProxyPassReverse {{ httpd_pulp_api_backend }}/pulp/api/v3 @@ -68,14 +68,14 @@ ## Proxy rules ProxyRequests Off ProxyPreserveHost Off - ProxyPass /rhsm {{ httpd_proxy_foreman_url }}/rhsm disablereuse=on retry=0 - ProxyPassReverse /rhsm {{ httpd_proxy_foreman_url }}/rhsm - ProxyPass /redhat_access {{ httpd_proxy_foreman_url }}/redhat_access disablereuse=on retry=0 - ProxyPassReverse /redhat_access {{ httpd_proxy_foreman_url }}/redhat_access - ProxyPass /api/lightspeed {{ httpd_proxy_foreman_url }}/api/lightspeed disablereuse=on retry=0 - ProxyPassReverse /api/lightspeed {{ httpd_proxy_foreman_url }}/api/lightspeed - ProxyPass /api/registration_commands {{ httpd_proxy_foreman_url }}/api/registration_commands disablereuse=on retry=0 - ProxyPassReverse /api/registration_commands {{ httpd_proxy_foreman_url }}/api/registration_commands + ProxyPass /rhsm {{ httpd_foreman_url }}/rhsm disablereuse=on retry=0 + ProxyPassReverse /rhsm {{ httpd_foreman_url }}/rhsm + ProxyPass /redhat_access {{ httpd_foreman_url }}/redhat_access disablereuse=on retry=0 + ProxyPassReverse /redhat_access {{ httpd_foreman_url }}/redhat_access + ProxyPass /api/lightspeed {{ httpd_foreman_url }}/api/lightspeed disablereuse=on retry=0 + ProxyPassReverse /api/lightspeed {{ httpd_foreman_url }}/api/lightspeed + ProxyPass /api/registration_commands {{ httpd_foreman_url }}/api/registration_commands disablereuse=on retry=0 + ProxyPassReverse /api/registration_commands {{ httpd_foreman_url }}/api/registration_commands Alias /pub {{ httpd_pub_dir }} diff --git a/src/roles/pulp/defaults/main.yaml b/src/roles/pulp/defaults/main.yaml index 20f641388..f753373f0 100644 --- a/src/roles/pulp/defaults/main.yaml +++ b/src/roles/pulp/defaults/main.yaml @@ -25,6 +25,7 @@ pulp_pulp_url: "http://{{ ansible_facts['fqdn'] }}:24817" pulp_enable_analytics: false pulp_mirror: false +pulp_register_foreman_proxy: true pulp_rhsm_url: "https://{{ ansible_facts['fqdn'] }}/rhsm" pulp_default_import_paths: "{{ pulp_mirror | ternary(['/var/lib/pulp/sync_imports'], ['/var/lib/pulp/sync_imports', '/var/lib/pulp/imports']) }}" diff --git a/src/roles/pulp/tasks/main.yaml b/src/roles/pulp/tasks/main.yaml index bf4bfe564..866493027 100644 --- a/src/roles/pulp/tasks/main.yaml +++ b/src/roles/pulp/tasks/main.yaml @@ -312,3 +312,13 @@ when: - pulp_existing_workers | length > 0 - (item | regex_replace('^' + pulp_worker_container_name + '@(\\d+)\\.service$', '\\1') | int) > (pulp_worker_count | int) + +- name: Configure Foreman Proxy + theforeman.foreman.smart_proxy: + name: "{{ ansible_facts['fqdn'] }}-pulp" + url: "https://{{ ansible_facts['fqdn'] }}/pulp/api/v3/smart_proxy" + server_url: "{{ pulp_foreman_url }}" + oauth1_consumer_key: "{{ pulp_foreman_oauth_consumer_key }}" + oauth1_consumer_secret: "{{ pulp_foreman_oauth_consumer_secret }}" + ca_path: "{{ pulp_foreman_ca_certificate }}" + when: pulp_register_foreman_proxy diff --git a/src/vars/base.yaml b/src/vars/base.yaml index 366d95fd0..f310679ca 100644 --- a/src/vars/base.yaml +++ b/src/vars/base.yaml @@ -28,12 +28,17 @@ httpd_client_ca_certificate: "{{ client_ca_certificate }}" httpd_server_certificate: "{{ server_certificate }}" httpd_server_key: "{{ server_key }}" httpd_enabled_pulp_snippets: "{{ ['pypi'] if 'pulp_python' in pulp_plugins else [] }}" +httpd_foreman_url: "{{ foreman_url }}" pulp_storage_path: /var/lib/pulp pulp_content_origin: "https://{{ ansible_facts['fqdn'] }}" pulp_pulp_url: "https://{{ ansible_facts['fqdn'] }}" pulp_plugins: "{{ enabled_features | select('contains', 'content/') | map('replace', 'content/', 'pulp_') | list }}" pulp_rhsm_url: "{{ foreman_proxy_foreman_server_url }}/rhsm" +pulp_foreman_url: "{{ foreman_proxy_foreman_server_url }}" +pulp_foreman_ca_certificate: "{{ foreman_ca_certificate }}" +pulp_foreman_oauth_consumer_key: "{{ foreman_proxy_oauth_consumer_key }}" +pulp_foreman_oauth_consumer_secret: "{{ foreman_proxy_oauth_consumer_secret }}" hammer_ca_certificate: "{{ server_ca_certificate }}" hammer_plugins: "{{ enabled_features | features_to_hammer_plugins }}" @@ -44,6 +49,7 @@ foreman_proxy_foreman_server_url: "{{ foreman_url }}" foreman_proxy_foreman_ca_certificate: "{{ foreman_ca_certificate }}" foreman_proxy_oauth_consumer_key: "{{ foreman_oauth_consumer_key }}" foreman_proxy_oauth_consumer_secret: "{{ foreman_oauth_consumer_secret }}" +foreman_proxy_trusted_hosts: "{{ foreman_name }}" iop_core_foreman_url: "{{ foreman_url }}" iop_core_foreman_oauth_consumer_key: "{{ foreman_oauth_consumer_key }}" diff --git a/src/vars/flavors/foreman-proxy-content.yml b/src/vars/flavors/foreman-proxy-content.yml index f3c97dd2f..36668f4cc 100644 --- a/src/vars/flavors/foreman-proxy-content.yml +++ b/src/vars/flavors/foreman-proxy-content.yml @@ -17,3 +17,5 @@ health_checks_to_execute: - check_hostname - check_database_connection - check_services + +certificates_generate: false From 6d28d5e1a029e78d73f05c3ade67f9e6bb11c050 Mon Sep 17 00:00:00 2001 From: Arvind Jangir Date: Thu, 25 Jun 2026 18:30:17 +0530 Subject: [PATCH 17/22] Refactor httpd conf, pulp and tests --- .github/workflows/test.yml | 6 -- src/requirements.txt | 2 +- .../foreman_proxy/templates/settings.yml.j2 | 4 +- src/roles/httpd/tasks/main.yml | 28 ++-------- .../httpd/templates/foreman-ssl-vhost.conf.j2 | 34 +++++++++++- .../httpd/templates/foreman-vhost.conf.j2 | 10 +++- src/roles/pulp/defaults/main.yaml | 3 +- src/roles/pulp/tasks/main.yaml | 1 + src/vars/base.yaml | 5 +- .../foreman-proxy-content/httpd_test.py | 4 +- .../flavor/foreman-proxy-content/pulp_test.py | 11 +--- tests/flavor/katello/images_test.py | 4 +- tests/foreman_api_test.py | 55 ------------------- tests/httpd_test.py | 1 - tests/katello_api_test.py | 52 ++++++++++++++++++ tests/postgresql_test.py | 2 - 16 files changed, 110 insertions(+), 112 deletions(-) create mode 100644 tests/katello_api_test.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 399ef6e15..6d09c3e32 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -419,12 +419,6 @@ jobs: - name: Run tests run: | ./forge test --pytest-args="--server-hostname=proxy" - - name: Archive smoker report - if: ${{ always() }} - uses: actions/upload-artifact@v7 - with: - name: smoker-proxy-content - path: "/home/runner/smoker/report/" - name: Generate sos reports if: ${{ always() }} run: ./forge sos diff --git a/src/requirements.txt b/src/requirements.txt index a350ba0f4..34c9b8e62 100644 --- a/src/requirements.txt +++ b/src/requirements.txt @@ -1,3 +1,3 @@ -obsah >= 1.9.0 +obsah >= 1.9.1 requests requests-oauthlib diff --git a/src/roles/foreman_proxy/templates/settings.yml.j2 b/src/roles/foreman_proxy/templates/settings.yml.j2 index 80d49ac0b..e8dbd421f 100644 --- a/src/roles/foreman_proxy/templates/settings.yml.j2 +++ b/src/roles/foreman_proxy/templates/settings.yml.j2 @@ -2,7 +2,9 @@ :foreman_url: {{ foreman_proxy_foreman_server_url }} :trusted_hosts: - - {{ foreman_proxy_trusted_hosts }} +{% for host in foreman_proxy_trusted_hosts %} + - {{ host }} +{% endfor %} :https_port: {{ foreman_proxy_https_port }} :ssl_ca_file: /etc/foreman-proxy/ssl_ca.pem diff --git a/src/roles/httpd/tasks/main.yml b/src/roles/httpd/tasks/main.yml index 245537342..1c79c4d6b 100644 --- a/src/roles/httpd/tasks/main.yml +++ b/src/roles/httpd/tasks/main.yml @@ -81,39 +81,19 @@ - httpd_external_authentication is none -- name: Configure foreman vhost +- name: Configure vhost ansible.builtin.template: src: foreman-vhost.conf.j2 - dest: /etc/httpd/conf.d/foreman.conf + dest: /etc/httpd/conf.d/{{ 'foreman' if httpd_with_foreman else 'pulpcore' }}.conf mode: "0644" - when: httpd_with_foreman notify: - Restart httpd -- name: Configure pulpcore vhost - ansible.builtin.template: - src: pulpcore-vhost.conf.j2 - dest: /etc/httpd/conf.d/pulpcore.conf - mode: "0644" - when: httpd_with_pulp_mirror - notify: - - Restart httpd - -- name: Configure foreman-ssl vhost +- name: Configure SSL vhost ansible.builtin.template: src: foreman-ssl-vhost.conf.j2 - dest: /etc/httpd/conf.d/foreman-ssl.conf - mode: "0644" - when: httpd_with_foreman - notify: - - Restart httpd - -- name: Configure rhsm-pulpcore vhost - ansible.builtin.template: - src: rhsm-pulpcore-vhost.conf.j2 - dest: /etc/httpd/conf.d/rhsm-pulpcore.conf + dest: /etc/httpd/conf.d/{{ 'foreman-ssl' if httpd_with_foreman else 'pulpcore-ssl' }}.conf mode: "0644" - when: httpd_with_pulp_mirror notify: - Restart httpd diff --git a/src/roles/httpd/templates/foreman-ssl-vhost.conf.j2 b/src/roles/httpd/templates/foreman-ssl-vhost.conf.j2 index 923d09365..15c26470f 100644 --- a/src/roles/httpd/templates/foreman-ssl-vhost.conf.j2 +++ b/src/roles/httpd/templates/foreman-ssl-vhost.conf.j2 @@ -5,12 +5,13 @@ IncludeOptional "/etc/httpd/conf.d/05-foreman-ssl.d/*.conf" ## Logging - ErrorLog "/var/log/httpd/foreman-ssl_error_ssl.log" + ErrorLog "/var/log/httpd/{{ 'foreman-ssl_error_ssl' if httpd_with_foreman else 'pulpcore_error' }}.log" ServerSignature Off - CustomLog "/var/log/httpd/foreman-ssl_access_ssl.log" combined + CustomLog "/var/log/httpd/{{ 'foreman-ssl_access_ssl' if httpd_with_foreman else 'pulpcore_access' }}.log" combined ## Request header rules ## as per http://httpd.apache.org/docs/2.4/mod/mod_headers.html#requestheader +{% if httpd_with_foreman %} RequestHeader set X-FORWARDED-PROTO "https" RequestHeader set SSL-CLIENT-S-DN "%{SSL_CLIENT_S_DN}s" RequestHeader set SSL-CLIENT-CERT "%{SSL_CLIENT_CERT}s" @@ -33,6 +34,9 @@ RequestHeader unset REMOTE-USER_GROUPS RequestHeader unset REMOTE_USER-GROUPS RequestHeader unset REMOTE_USER_GROUPS +{% else %} + RequestHeader set X_RHSM_SSL_CLIENT_CERT "%{SSL_CLIENT_CERT}s" +{% endif %} ## SSL directives SSLEngine on @@ -46,6 +50,13 @@ # SSL Proxy directives SSLProxyEngine On +{% if not httpd_with_foreman %} + SSLProxyCACertificateFile "{{ httpd_ssl_dir }}/certs/katello-default-ca.crt" + ProxyPass /katello/api/v2/repositories/ {{ httpd_foreman_url }}/katello/api/v2/repositories/ + + ProxyPassReverse {{ httpd_foreman_url }}/katello/api/v2/repositories/ + +{% endif %} ProxyPass /pulp_ansible/galaxy/ {{ httpd_pulp_api_backend }}/pulp_ansible/galaxy/ ProxyPassReverse /pulp_ansible/galaxy/ {{ httpd_pulp_api_backend }}/pulp_ansible/galaxy/ @@ -54,6 +65,9 @@ RequestHeader unset REMOTE_USER RequestHeader unset REMOTE-USER RequestHeader set REMOTE-USER "admin" "expr=%{SSL_CLIENT_S_DN_CN} == '{{ ansible_facts['fqdn'] }}'" + {% for httpd_pulp_trusted_host in httpd_pulp_trusted_hosts %} + RequestHeader set REMOTE-USER "admin" "expr=%{SSL_CLIENT_S_DN_CN} == '{{ httpd_pulp_trusted_host }}'" + {% endfor %} ProxyPass {{ httpd_pulp_api_backend }}/v2/ ProxyPassReverse {{ httpd_pulp_api_backend }}/v2/ @@ -83,6 +97,9 @@ RequestHeader unset REMOTE_USER RequestHeader unset REMOTE-USER RequestHeader set REMOTE-USER "admin" "expr=%{SSL_CLIENT_S_DN_CN} == '{{ ansible_facts['fqdn'] }}'" + {% for httpd_pulp_trusted_host in httpd_pulp_trusted_hosts %} + RequestHeader set REMOTE-USER "admin" "expr=%{SSL_CLIENT_S_DN_CN} == '{{ httpd_pulp_trusted_host }}'" + {% endfor %} ProxyPass {{ httpd_pulp_api_backend }}/pulp/api/v3 timeout=600 ProxyPassReverse {{ httpd_pulp_api_backend }}/pulp/api/v3 @@ -99,7 +116,8 @@ ## Proxy rules ProxyRequests Off - ProxyPreserveHost On + ProxyPreserveHost {{ 'On' if httpd_with_foreman else 'Off' }} +{% if httpd_with_foreman %} ProxyAddHeaders On ProxyPass /pulp ! ProxyPass /pub ! @@ -108,6 +126,16 @@ ProxyPass /server-status ! ProxyPass / {{ httpd_foreman_backend }}/ retry=0 timeout=900 upgrade=websocket ProxyPassReverse / {{ httpd_foreman_backend }}/ +{% else %} + ProxyPass /rhsm {{ httpd_foreman_url }}/rhsm disablereuse=on retry=0 + ProxyPassReverse /rhsm {{ httpd_foreman_url }}/rhsm + ProxyPass /redhat_access {{ httpd_foreman_url }}/redhat_access disablereuse=on retry=0 + ProxyPassReverse /redhat_access {{ httpd_foreman_url }}/redhat_access + ProxyPass /api/lightspeed {{ httpd_foreman_url }}/api/lightspeed disablereuse=on retry=0 + ProxyPassReverse /api/lightspeed {{ httpd_foreman_url }}/api/lightspeed + ProxyPass /api/registration_commands {{ httpd_foreman_url }}/api/registration_commands disablereuse=on retry=0 + ProxyPassReverse /api/registration_commands {{ httpd_foreman_url }}/api/registration_commands +{% endif %} AddDefaultCharset UTF-8 diff --git a/src/roles/httpd/templates/foreman-vhost.conf.j2 b/src/roles/httpd/templates/foreman-vhost.conf.j2 index 90065c018..cd0840afd 100644 --- a/src/roles/httpd/templates/foreman-vhost.conf.j2 +++ b/src/roles/httpd/templates/foreman-vhost.conf.j2 @@ -5,13 +5,14 @@ IncludeOptional "/etc/httpd/conf.d/05-foreman.d/*.conf" ## Logging - ErrorLog "/var/log/httpd/foreman_error.log" + ErrorLog "/var/log/httpd/{{ 'foreman_error' if httpd_with_foreman else 'mirror_error' }}.log" ServerSignature Off - CustomLog "/var/log/httpd/foreman_access.log" combined + CustomLog "/var/log/httpd/{{ 'foreman_access' if httpd_with_foreman else 'mirror_access' }}.log" combined ## Request header rules ## as per http://httpd.apache.org/docs/2.4/mod/mod_headers.html#requestheader RequestHeader set X-FORWARDED-PROTO "http" +{% if httpd_with_foreman %} RequestHeader set SSL-CLIENT-S-DN "" RequestHeader set SSL-CLIENT-CERT "" RequestHeader set SSL-CLIENT-VERIFY "" @@ -33,6 +34,7 @@ RequestHeader unset REMOTE-USER_GROUPS RequestHeader unset REMOTE_USER-GROUPS RequestHeader unset REMOTE_USER_GROUPS +{% endif %} RequestHeader unset X-CLIENT-CERT @@ -50,7 +52,7 @@ ProxyPassReverse {{ httpd_pulp_content_backend }}/pulp/content - Alias /pub /var/www/html/pub + Alias /pub {{ httpd_pub_dir }} Options +FollowSymLinks +Indexes @@ -59,6 +61,7 @@ ## Proxy rules ProxyRequests Off +{% if httpd_with_foreman %} ProxyPreserveHost On ProxyAddHeaders On ProxyPass /pulp ! @@ -68,6 +71,7 @@ ProxyPass /server-status ! ProxyPass / {{ httpd_foreman_backend }}/ retry=0 timeout=900 upgrade=websocket ProxyPassReverse / {{ httpd_foreman_backend }}/ +{% endif %} AddDefaultCharset UTF-8 diff --git a/src/roles/pulp/defaults/main.yaml b/src/roles/pulp/defaults/main.yaml index f753373f0..70931702f 100644 --- a/src/roles/pulp/defaults/main.yaml +++ b/src/roles/pulp/defaults/main.yaml @@ -28,7 +28,7 @@ pulp_mirror: false pulp_register_foreman_proxy: true pulp_rhsm_url: "https://{{ ansible_facts['fqdn'] }}/rhsm" -pulp_default_import_paths: "{{ pulp_mirror | ternary(['/var/lib/pulp/sync_imports'], ['/var/lib/pulp/sync_imports', '/var/lib/pulp/imports']) }}" +pulp_default_import_paths: "{{ pulp_mirror | ternary([], ['/var/lib/pulp/sync_imports', '/var/lib/pulp/imports']) }}" pulp_default_export_paths: "{{ pulp_mirror | ternary([], ['/var/lib/pulp/exports']) }}" pulp_import_paths: [] pulp_export_paths: [] @@ -75,7 +75,6 @@ pulp_settings_other_env: PULP_SMART_PROXY_PULP_URL: "{{ pulp_pulp_url }}" PULP_SMART_PROXY_MIRROR: "{{ pulp_mirror }}" PULP_SMART_PROXY_RHSM_URL: "{{ pulp_rhsm_url }}" - PULP_SMART_PROXY_AUTH_METHODS: ['client_certificate'] PULP_API_WORKERS: "{{ pulp_api_service_worker_count }}" PULP_CONTENT_WORKERS: "{{ pulp_content_service_worker_count }}" PULP_TOKEN_AUTH_DISABLED: "true" diff --git a/src/roles/pulp/tasks/main.yaml b/src/roles/pulp/tasks/main.yaml index 866493027..c39e955fa 100644 --- a/src/roles/pulp/tasks/main.yaml +++ b/src/roles/pulp/tasks/main.yaml @@ -25,6 +25,7 @@ state: directory mode: "0755" loop: "{{ pulp_default_import_paths + pulp_import_paths }}" + when: not pulp_mirror - name: Create export directories ansible.builtin.file: diff --git a/src/vars/base.yaml b/src/vars/base.yaml index f310679ca..ed3023aa9 100644 --- a/src/vars/base.yaml +++ b/src/vars/base.yaml @@ -29,6 +29,8 @@ httpd_server_certificate: "{{ server_certificate }}" httpd_server_key: "{{ server_key }}" httpd_enabled_pulp_snippets: "{{ ['pypi'] if 'pulp_python' in pulp_plugins else [] }}" httpd_foreman_url: "{{ foreman_url }}" +httpd_pulp_trusted_hosts: + - "{{ foreman_name }}" pulp_storage_path: /var/lib/pulp pulp_content_origin: "https://{{ ansible_facts['fqdn'] }}" @@ -49,7 +51,8 @@ foreman_proxy_foreman_server_url: "{{ foreman_url }}" foreman_proxy_foreman_ca_certificate: "{{ foreman_ca_certificate }}" foreman_proxy_oauth_consumer_key: "{{ foreman_oauth_consumer_key }}" foreman_proxy_oauth_consumer_secret: "{{ foreman_oauth_consumer_secret }}" -foreman_proxy_trusted_hosts: "{{ foreman_name }}" +foreman_proxy_trusted_hosts: + - "{{ foreman_name }}" iop_core_foreman_url: "{{ foreman_url }}" iop_core_foreman_oauth_consumer_key: "{{ foreman_oauth_consumer_key }}" diff --git a/tests/flavor/foreman-proxy-content/httpd_test.py b/tests/flavor/foreman-proxy-content/httpd_test.py index 17fd10fba..5fce31360 100644 --- a/tests/flavor/foreman-proxy-content/httpd_test.py +++ b/tests/flavor/foreman-proxy-content/httpd_test.py @@ -1,8 +1,8 @@ CURL_CMD = "curl --silent --output /dev/null" -def test_rhsm_pulpcore_vhost_exists(server): - conf = server.file("/etc/httpd/conf.d/rhsm-pulpcore.conf") +def test_pulpcore_vhost_exists(server): + conf = server.file("/etc/httpd/conf.d/pulpcore.conf") assert conf.exists assert conf.is_file diff --git a/tests/flavor/foreman-proxy-content/pulp_test.py b/tests/flavor/foreman-proxy-content/pulp_test.py index 61dd0a9bd..850223b35 100644 --- a/tests/flavor/foreman-proxy-content/pulp_test.py +++ b/tests/flavor/foreman-proxy-content/pulp_test.py @@ -30,19 +30,12 @@ def pulp_settings(server): def test_import_paths_restricted(pulp_settings): - assert '/var/lib/pulp/sync_imports' in pulp_settings['import'] + assert [] == pulp_settings['import'] assert '/var/lib/pulp/imports' not in pulp_settings['import'] -def test_sync_imports_directory(server): - assert server.file("/var/lib/pulp/sync_imports").is_directory - - -def test_no_exports_directory(server): +def test_no_imports_or_exports_directories(server): assert not server.file("/var/lib/pulp/exports").exists - - -def test_no_imports_directory(server): assert not server.file("/var/lib/pulp/imports").exists diff --git a/tests/flavor/katello/images_test.py b/tests/flavor/katello/images_test.py index d939f32b7..c111eeafe 100644 --- a/tests/flavor/katello/images_test.py +++ b/tests/flavor/katello/images_test.py @@ -1,13 +1,13 @@ import pytest -FlAVOR_CORE_IMAGES = [ +FLAVOR_CORE_IMAGES = [ "pulp", "candlepin", "foreman" ] -@pytest.fixture(params=FlAVOR_CORE_IMAGES) +@pytest.fixture(params=FLAVOR_CORE_IMAGES) def core_image(request): return request.param diff --git a/tests/foreman_api_test.py b/tests/foreman_api_test.py index ba674a142..d2b784447 100644 --- a/tests/foreman_api_test.py +++ b/tests/foreman_api_test.py @@ -7,61 +7,6 @@ def test_foreman_organization(organization): assert organization -def test_foreman_product(product): - assert product - - -@pytest.mark.feature('katello') -def test_foreman_yum_repository(yum_repository, foremanapi, local_request): - assert yum_repository - foremanapi.resource_action('repositories', 'sync', {'id': yum_repository['id']}) - repo_url = yum_repository['full_path'] - assert local_request.get(f'{repo_url}/repodata/repomd.xml', verify=False) - assert local_request.get(f'{repo_url}/Packages/b/bear-4.1-1.noarch.rpm', verify=False) - - -@pytest.mark.feature('katello') -def test_foreman_file_repository(file_repository, foremanapi, local_request): - assert file_repository - foremanapi.resource_action('repositories', 'sync', {'id': file_repository['id']}) - repo_url = file_repository['full_path'] - assert local_request.get(f'{repo_url}/1.iso', verify=False) - - -@pytest.mark.feature('katello') -def test_foreman_container_repository(container_repository, foremanapi): - assert container_repository - foremanapi.resource_action('repositories', 'sync', {'id': container_repository['id']}) - - -@pytest.mark.feature('katello') -def test_foreman_lifecycle_environment(lifecycle_environment): - assert lifecycle_environment - - -@pytest.mark.feature('katello') -def test_foreman_content_view(content_view, yum_repository, foremanapi): - assert content_view - foremanapi.update('content_views', {'id': content_view['id'], 'repository_ids': [yum_repository['id']]}) - foremanapi.resource_action('content_views', 'publish', {'id': content_view['id']}) - # do something with the published view - versions = foremanapi.list('content_view_versions', params={'content_view_id': content_view['id']}) - for version in versions: - current_environment_ids = {environment['id'] for environment in version['environments']} - for environment_id in current_environment_ids: - foremanapi.resource_action('content_views', 'remove_from_environment', params={'id': content_view['id'], 'environment_id': environment_id}) - foremanapi.delete('content_view_versions', version) - - -@pytest.mark.feature('katello') -def test_foreman_manifest(organization, foremanapi, fixture_dir): - manifest_path = fixture_dir / 'manifest.zip' - with open(manifest_path, 'rb') as manifest_file: - files = {'content': (str(manifest_path), manifest_file, 'application/zip')} - params = {'organization_id': organization['id']} - foremanapi.resource_action('subscriptions', 'upload', params, files=files) - - def test_foreman_initial_organization(foremanapi): assert foremanapi.list('organizations', search='name="Foreman CI"') diff --git a/tests/httpd_test.py b/tests/httpd_test.py index 9e7e70d43..b4cb76ec1 100644 --- a/tests/httpd_test.py +++ b/tests/httpd_test.py @@ -67,7 +67,6 @@ def test_https_pulp_auth(server, certificates, server_fqdn): assert cmd.stdout == '200' -@pytest.mark.feature('katello') def test_https_pypi_endpoint(server, certificates, server_fqdn): cmd = server.run(f"curl --cacert {certificates['server_ca_certificate']} https://{server_fqdn}/pypi/test/") assert cmd.succeeded diff --git a/tests/katello_api_test.py b/tests/katello_api_test.py new file mode 100644 index 000000000..57cd2ce1b --- /dev/null +++ b/tests/katello_api_test.py @@ -0,0 +1,52 @@ +import pytest + +pytestmark = pytest.mark.feature('katello') + + +def test_foreman_product(product): + assert product + + +def test_foreman_yum_repository(yum_repository, foremanapi, local_request): + assert yum_repository + foremanapi.resource_action('repositories', 'sync', {'id': yum_repository['id']}) + repo_url = yum_repository['full_path'] + assert local_request.get(f'{repo_url}/repodata/repomd.xml', verify=False) + assert local_request.get(f'{repo_url}/Packages/b/bear-4.1-1.noarch.rpm', verify=False) + + +def test_foreman_file_repository(file_repository, foremanapi, local_request): + assert file_repository + foremanapi.resource_action('repositories', 'sync', {'id': file_repository['id']}) + repo_url = file_repository['full_path'] + assert local_request.get(f'{repo_url}/1.iso', verify=False) + + +def test_foreman_container_repository(container_repository, foremanapi): + assert container_repository + foremanapi.resource_action('repositories', 'sync', {'id': container_repository['id']}) + + +def test_foreman_lifecycle_environment(lifecycle_environment): + assert lifecycle_environment + + +def test_foreman_content_view(content_view, yum_repository, foremanapi): + assert content_view + foremanapi.update('content_views', {'id': content_view['id'], 'repository_ids': [yum_repository['id']]}) + foremanapi.resource_action('content_views', 'publish', {'id': content_view['id']}) + # do something with the published view + versions = foremanapi.list('content_view_versions', params={'content_view_id': content_view['id']}) + for version in versions: + current_environment_ids = {environment['id'] for environment in version['environments']} + for environment_id in current_environment_ids: + foremanapi.resource_action('content_views', 'remove_from_environment', params={'id': content_view['id'], 'environment_id': environment_id}) + foremanapi.delete('content_view_versions', version) + + +def test_foreman_manifest(organization, foremanapi, fixture_dir): + manifest_path = fixture_dir / 'manifest.zip' + with open(manifest_path, 'rb') as manifest_file: + files = {'content': (str(manifest_path), manifest_file, 'application/zip')} + params = {'organization_id': organization['id']} + foremanapi.resource_action('subscriptions', 'upload', params, files=files) diff --git a/tests/postgresql_test.py b/tests/postgresql_test.py index 76a13e6ac..55c0185b5 100644 --- a/tests/postgresql_test.py +++ b/tests/postgresql_test.py @@ -21,8 +21,6 @@ def test_postgresql_password_encryption(database): reader = csv.reader(result.stdout.splitlines()) for row in reader: - if row[0] == 'postgres': - continue assert "SCRAM-SHA-256" in row[6] From 055918d4652b370847f3d94dd9cb853fcece6f02 Mon Sep 17 00:00:00 2001 From: Arvind Jangir Date: Fri, 26 Jun 2026 12:23:09 +0530 Subject: [PATCH 18/22] Remove uneccessory confs --- .github/workflows/test.yml | 7 +- .../playbooks/fetch-bundle/fetch-bundle.yaml | 14 +++ .../fetch-bundle/metadata.obsah.yaml | 8 ++ pyproject.toml | 2 - pytest.toml | 2 + .../_flavors/katello/metadata.obsah.yaml | 10 +++ src/playbooks/_pulp/metadata.obsah.yaml | 10 --- src/roles/certificates/tasks/extract.yml | 2 +- .../httpd/templates/foreman-ssl-vhost.conf.j2 | 2 +- .../httpd/templates/pulpcore-vhost.conf.j2 | 26 ------ .../templates/rhsm-pulpcore-vhost.conf.j2 | 88 ------------------- tests/conftest.py | 29 ++++++ .../foreman-proxy-content/httpd_test.py | 34 ++----- .../flavor/foreman-proxy-content/pulp_test.py | 19 +--- tests/foreman_proxy_test.py | 29 +++--- tests/httpd_test.py | 36 ++++---- 16 files changed, 109 insertions(+), 209 deletions(-) create mode 100644 development/playbooks/fetch-bundle/fetch-bundle.yaml create mode 100644 development/playbooks/fetch-bundle/metadata.obsah.yaml delete mode 100644 pyproject.toml create mode 100644 pytest.toml delete mode 100644 src/roles/httpd/templates/pulpcore-vhost.conf.j2 delete mode 100644 src/roles/httpd/templates/rhsm-pulpcore-vhost.conf.j2 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6d09c3e32..cd5bc1e88 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -406,15 +406,14 @@ jobs: - name: Generate certificates bundle on quadlet run: | ./foremanctl certificate-bundle proxy.example.com - - name: Copy certificates bundle to content-proxy + - name: Fetch certificates bundle from quadlet run: | - vagrant ssh quadlet -- sudo cat /var/lib/foremanctl/certs/bundles/proxy.example.com.tar.gz | \ - vagrant ssh proxy -- "sudo tee /root/proxy.example.com.tar.gz > /dev/null" + ./forge fetch-bundle proxy.example.com - name: Deploy content proxy run: | ./foremanctl deploy-proxy \ --flavor foreman-proxy-content \ - --certificate-bundle /root/proxy.example.com.tar.gz \ + --certificate-bundle $(pwd)/.var/lib/foremanctl/proxy.example.com.tar.gz \ --foreman-fqdn quadlet.example.com - name: Run tests run: | diff --git a/development/playbooks/fetch-bundle/fetch-bundle.yaml b/development/playbooks/fetch-bundle/fetch-bundle.yaml new file mode 100644 index 000000000..144c4f08d --- /dev/null +++ b/development/playbooks/fetch-bundle/fetch-bundle.yaml @@ -0,0 +1,14 @@ +--- +- name: Fetch generated bundle from server + hosts: + - quadlet + become: true + vars_files: + - "../../../src/vars/base.yaml" + - "../../../src/vars/certificates.yml" + tasks: + - name: Fetch bundle + ansible.builtin.fetch: + src: "{{ certificates_ca_directory }}/bundles/{{ hostname }}.tar.gz" + dest: "{{ obsah_state_path }}/{{ hostname }}.tar.gz" + flat: true diff --git a/development/playbooks/fetch-bundle/metadata.obsah.yaml b/development/playbooks/fetch-bundle/metadata.obsah.yaml new file mode 100644 index 000000000..f2d2d635c --- /dev/null +++ b/development/playbooks/fetch-bundle/metadata.obsah.yaml @@ -0,0 +1,8 @@ +--- +help: | + Fetch a certificate bundle + +variables: + hostname: + parameter: hostname + help: Hostname to fetch the certificate bundle for. diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index 5bf092d05..000000000 --- a/pyproject.toml +++ /dev/null @@ -1,2 +0,0 @@ -[tool.pytest.ini_options] -addopts = "--import-mode=importlib" \ No newline at end of file diff --git a/pytest.toml b/pytest.toml new file mode 100644 index 000000000..3ffcdc954 --- /dev/null +++ b/pytest.toml @@ -0,0 +1,2 @@ +[pytest] +addopts = ["--import-mode=importlib"] diff --git a/src/playbooks/_flavors/katello/metadata.obsah.yaml b/src/playbooks/_flavors/katello/metadata.obsah.yaml index f239a0801..e96ba2d79 100644 --- a/src/playbooks/_flavors/katello/metadata.obsah.yaml +++ b/src/playbooks/_flavors/katello/metadata.obsah.yaml @@ -7,6 +7,16 @@ variables: - ipa_with_api external_authentication_pam_service: help: Name of the PAM service to use for IPA authentication + pulp_import_paths: + help: Extra file path that Pulp can use for content imports. Argument may be used more than once. + action: append_unique + type: AbsolutePath + parameter: --content-import-path + pulp_export_paths: + help: Extra file path that Pulp can use for content exports. Argument may be used more than once. + action: append_unique + type: AbsolutePath + parameter: --content-export-path include: - _certificate_source diff --git a/src/playbooks/_pulp/metadata.obsah.yaml b/src/playbooks/_pulp/metadata.obsah.yaml index a9d519b52..6a928621c 100644 --- a/src/playbooks/_pulp/metadata.obsah.yaml +++ b/src/playbooks/_pulp/metadata.obsah.yaml @@ -1,14 +1,4 @@ --- variables: - pulp_import_paths: - help: Extra file path that Pulp can use for content imports. Argument may be used more than once. - action: append_unique - type: AbsolutePath - parameter: --content-import-path - pulp_export_paths: - help: Extra file path that Pulp can use for content exports. Argument may be used more than once. - action: append_unique - type: AbsolutePath - parameter: --content-export-path pulp_worker_count: help: Number of Pulp workers. Defaults to 8 or the number of CPU cores, whichever is smaller. diff --git a/src/roles/certificates/tasks/extract.yml b/src/roles/certificates/tasks/extract.yml index 38a6a435c..d370973c4 100644 --- a/src/roles/certificates/tasks/extract.yml +++ b/src/roles/certificates/tasks/extract.yml @@ -3,6 +3,7 @@ ansible.builtin.stat: path: "{{ certificates_bundle }}" register: _certificates_tar_file_path + delegate_to: localhost - name: Fail if path to certificate tar file does not exist ansible.builtin.fail: @@ -13,6 +14,5 @@ ansible.builtin.unarchive: src: "{{ certificates_bundle }}" dest: "{{ certificates_ca_directory }}" - remote_src: true extra_opts: - --strip-components=1 diff --git a/src/roles/httpd/templates/foreman-ssl-vhost.conf.j2 b/src/roles/httpd/templates/foreman-ssl-vhost.conf.j2 index 15c26470f..1288b2251 100644 --- a/src/roles/httpd/templates/foreman-ssl-vhost.conf.j2 +++ b/src/roles/httpd/templates/foreman-ssl-vhost.conf.j2 @@ -50,8 +50,8 @@ # SSL Proxy directives SSLProxyEngine On -{% if not httpd_with_foreman %} SSLProxyCACertificateFile "{{ httpd_ssl_dir }}/certs/katello-default-ca.crt" +{% if not httpd_with_foreman %} ProxyPass /katello/api/v2/repositories/ {{ httpd_foreman_url }}/katello/api/v2/repositories/ ProxyPassReverse {{ httpd_foreman_url }}/katello/api/v2/repositories/ diff --git a/src/roles/httpd/templates/pulpcore-vhost.conf.j2 b/src/roles/httpd/templates/pulpcore-vhost.conf.j2 deleted file mode 100644 index b17b9317d..000000000 --- a/src/roles/httpd/templates/pulpcore-vhost.conf.j2 +++ /dev/null @@ -1,26 +0,0 @@ - - ServerName {{ ansible_facts['fqdn'] }} - - ErrorLog "/var/log/httpd/mirror_error.log" - ServerSignature Off - CustomLog "/var/log/httpd/mirror_access.log" combined - - RequestHeader set X-FORWARDED-PROTO "http" - - - RequestHeader unset X-CLIENT-CERT - RequestHeader set X-CLIENT-CERT "%{SSL_CLIENT_CERT}s" env=SSL_CLIENT_CERT - RequestHeader set X-FORWARDED-PROTO expr=%{REQUEST_SCHEME} - ProxyPass {{ httpd_pulp_content_backend }}/pulp/content disablereuse=on timeout=600 - ProxyPassReverse {{ httpd_pulp_content_backend }}/pulp/content - - - Alias /pub {{ httpd_pub_dir }} - - - Options +FollowSymLinks +Indexes - Require all granted - - - AddDefaultCharset UTF-8 - diff --git a/src/roles/httpd/templates/rhsm-pulpcore-vhost.conf.j2 b/src/roles/httpd/templates/rhsm-pulpcore-vhost.conf.j2 deleted file mode 100644 index 4b5cfd9a0..000000000 --- a/src/roles/httpd/templates/rhsm-pulpcore-vhost.conf.j2 +++ /dev/null @@ -1,88 +0,0 @@ - - ServerName {{ ansible_facts['fqdn'] }} - - ## SSL directives - SSLEngine on - SSLCertificateFile "{{ httpd_ssl_dir }}/certs/katello-apache.crt" - SSLCertificateKeyFile "{{ httpd_ssl_dir }}/private/katello-apache.key" - SSLCertificateChainFile "{{ httpd_ssl_dir }}/certs/katello-server-ca.crt" - SSLVerifyClient optional - SSLVerifyDepth 3 - SSLCACertificateFile "{{ httpd_ssl_dir }}/certs/katello-default-ca.crt" - SSLOptions +StdEnvVars +ExportCertData - - ## Logging - ErrorLog "/var/log/httpd/rhsm-pulpcore_error.log" - ServerSignature Off - CustomLog "/var/log/httpd/rhsm-pulpcore_access.log" combined - ErrorDocument 500 '{"displayMessage": "Internal error, contact administrator", "errors": ["Internal error, contact administrator"], "status": "500" }' - ErrorDocument 503 '{"displayMessage": "Service unavailable or restarting, try later", "errors": ["Service unavailable or restarting, try later"], "status": "503" }' - - ## Request header rules - ## as per http://httpd.apache.org/docs/2.4/mod/mod_headers.html#requestheader - RequestHeader set X_RHSM_SSL_CLIENT_CERT "%{SSL_CLIENT_CERT}s" - - # SSL Proxy directives - SSLProxyEngine On - SSLProxyCACertificateFile "{{ httpd_ssl_dir }}/certs/katello-default-ca.crt" - ProxyPass /katello/api/v2/repositories/ {{ httpd_foreman_url }}/katello/api/v2/repositories/ - - ProxyPassReverse {{ httpd_foreman_url }}/katello/api/v2/repositories/ - - SSLProxyEngine On - - ProxyPass /pulp_ansible/galaxy/ {{ httpd_pulp_api_backend }}/pulp_ansible/galaxy/ - ProxyPassReverse /pulp_ansible/galaxy/ {{ httpd_pulp_api_backend }}/pulp_ansible/galaxy/ - - - RequestHeader unset REMOTE_USER - RequestHeader unset REMOTE-USER - RequestHeader set REMOTE-USER "admin" "expr=%{SSL_CLIENT_S_DN_CN} == '{{ ansible_facts['fqdn'] }}'" - RequestHeader set REMOTE-USER "admin" "expr=%{SSL_CLIENT_S_DN_CN} == '{{ foreman_name }}'" - ProxyPass {{ httpd_pulp_api_backend }}/v2/ - ProxyPassReverse {{ httpd_pulp_api_backend }}/v2/ - - - ProxyPass /pulp/container/ {{ httpd_pulp_content_backend }}/pulp/container/ - ProxyPassReverse /pulp/container/ {{ httpd_pulp_content_backend }}/pulp/container/ - - - RequestHeader unset X-CLIENT-CERT - RequestHeader set X-CLIENT-CERT "%{SSL_CLIENT_CERT}s" env=SSL_CLIENT_CERT - ProxyPass {{ httpd_pulp_content_backend }}/pulp/content disablereuse=on timeout=600 - ProxyPassReverse {{ httpd_pulp_content_backend }}/pulp/content - - - - RequestHeader unset REMOTE_USER - RequestHeader unset REMOTE-USER - RequestHeader set REMOTE-USER "admin" "expr=%{SSL_CLIENT_S_DN_CN} == '{{ ansible_facts['fqdn'] }}'" - RequestHeader set REMOTE-USER "admin" "expr=%{SSL_CLIENT_S_DN_CN} == '{{ foreman_name }}'" - ProxyPass {{ httpd_pulp_api_backend }}/pulp/api/v3 timeout=600 - ProxyPassReverse {{ httpd_pulp_api_backend }}/pulp/api/v3 - - - ProxyPass /pulp/assets/ {{ httpd_pulp_api_backend }}/pulp/assets/ - ProxyPassReverse /pulp/assets/ {{ httpd_pulp_api_backend }}/pulp/assets/ - - ## Proxy rules - ProxyRequests Off - ProxyPreserveHost Off - ProxyPass /rhsm {{ httpd_foreman_url }}/rhsm disablereuse=on retry=0 - ProxyPassReverse /rhsm {{ httpd_foreman_url }}/rhsm - ProxyPass /redhat_access {{ httpd_foreman_url }}/redhat_access disablereuse=on retry=0 - ProxyPassReverse /redhat_access {{ httpd_foreman_url }}/redhat_access - ProxyPass /api/lightspeed {{ httpd_foreman_url }}/api/lightspeed disablereuse=on retry=0 - ProxyPassReverse /api/lightspeed {{ httpd_foreman_url }}/api/lightspeed - ProxyPass /api/registration_commands {{ httpd_foreman_url }}/api/registration_commands disablereuse=on retry=0 - ProxyPassReverse /api/registration_commands {{ httpd_foreman_url }}/api/registration_commands - - Alias /pub {{ httpd_pub_dir }} - - - Options +FollowSymLinks +Indexes - Require all granted - - - AddDefaultCharset UTF-8 - diff --git a/tests/conftest.py b/tests/conftest.py index 7ff81379b..c425dabf5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -19,6 +19,7 @@ OBSAH_STATE = os.environ.get('OBSAH_STATE', '.var/lib/foremanctl') PARAMETERS_FILE = os.path.join(OBSAH_STATE, 'parameters.yaml') FLAVOR_TESTS_DIR = py.path.local(__file__).dirpath() / 'flavor' +FOREMAN_PROXY_PORT = 8443 class UserParameters: @@ -315,3 +316,31 @@ def local_request(ssh_config, server_fqdn): session.mount(f"http://{server_fqdn}", adapter) session.mount(f"https://{server_fqdn}", adapter) return session + + +@pytest.fixture(scope="module") +def proxy_base_url(server_fqdn): + return f"https://{server_fqdn}:{FOREMAN_PROXY_PORT}" + + +@pytest.fixture(scope="module") +def curl_request(server, certificates, server_fqdn): + def _request(path, base_url=None, method=None, data=None, headers=None, return_body=False): + url = f"{base_url or f'https://{server_fqdn}'}/{path}" + curl_opts = ( + f"--cacert {certificates['server_ca_certificate']} " + f"--cert {certificates['client_certificate']} " + f"--key {certificates['client_key']} " + f"--silent " + ) + if not return_body: + curl_opts += "--write-out '%{http_code}' --output /dev/null " + if method: + curl_opts += f"-X {method} " + if data: + curl_opts += f"-d '{data}' " + if headers: + for key, value in headers.items(): + curl_opts += f"--header '{key}: {value}' " + return server.run(f"curl {curl_opts}{url}") + return _request diff --git a/tests/flavor/foreman-proxy-content/httpd_test.py b/tests/flavor/foreman-proxy-content/httpd_test.py index 5fce31360..a545a0ff2 100644 --- a/tests/flavor/foreman-proxy-content/httpd_test.py +++ b/tests/flavor/foreman-proxy-content/httpd_test.py @@ -7,43 +7,25 @@ def test_pulpcore_vhost_exists(server): assert conf.is_file -def test_https_pulp_api_with_client_cert(server, certificates, server_fqdn): - cmd = server.run( - f"curl --silent --cacert {certificates['server_ca_certificate']} " - f"--cert {certificates['client_certificate']} " - f"--key {certificates['client_key']} " - f"--write-out '%{{http_code}}' --output /dev/null " - f"https://{server_fqdn}/pulp/api/v3/smart_proxy/v2/features" - ) +def test_https_pulp_api_with_client_cert(curl_request): + cmd = curl_request("pulp/api/v3/smart_proxy/v2/features") assert cmd.succeeded assert cmd.stdout == '200' -def test_https_rhsm_proxy(server, certificates, server_fqdn): - cmd = server.run( - f"{CURL_CMD} --cacert {certificates['server_ca_certificate']} " - f"--write-out '%{{http_code}}' " - f"https://{server_fqdn}/rhsm" - ) +def test_https_rhsm_proxy(curl_request): + cmd = curl_request("rhsm") assert cmd.succeeded assert cmd.stdout not in ('404', '502', '503') -def test_https_pulp_content_proxy(server, certificates, server_fqdn): - cmd = server.run( - f"{CURL_CMD} --cacert {certificates['server_ca_certificate']} " - f"--write-out '%{{http_code}}' " - f"https://{server_fqdn}/pulp/content/" - ) +def test_https_pulp_content_proxy(curl_request): + cmd = curl_request("pulp/content/") assert cmd.succeeded assert cmd.stdout == '200' -def test_https_pulpcore_registry(server, certificates, server_fqdn): - cmd = server.run( - f"{CURL_CMD} --cacert {certificates['server_ca_certificate']} " - f"--write-out '%{{http_code}}' " - f"https://{server_fqdn}/pulpcore_registry/v2/" - ) +def test_https_pulpcore_registry(curl_request): + cmd = curl_request("pulpcore_registry/v2/") assert cmd.succeeded assert cmd.stdout not in ('404', '502', '503') diff --git a/tests/flavor/foreman-proxy-content/pulp_test.py b/tests/flavor/foreman-proxy-content/pulp_test.py index 850223b35..240e08568 100644 --- a/tests/flavor/foreman-proxy-content/pulp_test.py +++ b/tests/flavor/foreman-proxy-content/pulp_test.py @@ -2,17 +2,10 @@ import pytest -FOREMAN_PROXY_PORT = 8443 - @pytest.fixture(scope="module") -def pulp_smart_proxy_features(server, certificates, server_fqdn): - cmd = server.run( - f"curl --silent --cacert {certificates['server_ca_certificate']} " - f"--cert {certificates['client_certificate']} " - f"--key {certificates['client_key']} " - f"https://{server_fqdn}/pulp/api/v3/smart_proxy/v2/features" - ) +def pulp_smart_proxy_features(curl_request): + cmd = curl_request("pulp/api/v3/smart_proxy/v2/features", return_body=True) assert cmd.succeeded, f"Failed to query smart_proxy features: {cmd.stderr}" return json.loads(cmd.stdout) @@ -53,11 +46,7 @@ def test_pulp_smart_proxy_features(pulp_smart_proxy_features): assert expected in capabilities, f"Missing capability: {expected}" -def test_pulp_api_status(server, certificates, server_fqdn): - cmd = server.run( - f"curl --silent --output /dev/null --cacert {certificates['server_ca_certificate']} " - f"--write-out '%{{http_code}}' " - f"https://{server_fqdn}/pulp/api/v3/status/" - ) +def test_pulp_api_status(curl_request): + cmd = curl_request("pulp/api/v3/status/") assert cmd.succeeded assert cmd.stdout == '200' diff --git a/tests/foreman_proxy_test.py b/tests/foreman_proxy_test.py index 70dc24de4..1189074d0 100644 --- a/tests/foreman_proxy_test.py +++ b/tests/foreman_proxy_test.py @@ -3,23 +3,18 @@ import pytest -FOREMAN_PROXY_PORT = 8443 +from tests.conftest import FOREMAN_PROXY_PORT @pytest.fixture(scope="module") -def proxy_v2_features(server, certificates, server_fqdn): - cmd = server.run( - f"curl --cacert {certificates['server_ca_certificate']} " - f"--cert {certificates['client_certificate']} " - f"--key {certificates['client_key']} " - f"--silent https://{server_fqdn}:{FOREMAN_PROXY_PORT}/v2/features" - ) +def proxy_v2_features(curl_request, proxy_base_url): + cmd = curl_request("v2/features", base_url=proxy_base_url, return_body=True) assert cmd.succeeded, f"Failed to query /v2/features: {cmd.stderr}" return json.loads(cmd.stdout) -def test_foreman_proxy_features(server, certificates, server_fqdn, enabled_features): - cmd = server.run(f"curl --cacert {certificates['server_ca_certificate']} --silent https://{server_fqdn}:{FOREMAN_PROXY_PORT}/features") +def test_foreman_proxy_features(curl_request, proxy_base_url, enabled_features): + cmd = curl_request("features", base_url=proxy_base_url, return_body=True) assert cmd.succeeded features = json.loads(cmd.stdout) assert "logs" in features @@ -45,15 +40,13 @@ def test_foreman_proxy_port(server): @pytest.mark.xfail(reason='Fails until report feature is available') -def test_foreman_proxy_client_auth_to_foreman(server, certificates, server_fqdn): +def test_foreman_proxy_client_auth_to_foreman(curl_request): test_report = {"config_report": {"host": "test.example.com", "reported_at": datetime.datetime.now(datetime.UTC).strftime('%Y-%m-%d %H:%M:%S UTC')}} - cmd = server.run( - f"curl --cacert {certificates['server_ca_certificate']} " - f"--cert {certificates['client_certificate']} " - f"--key {certificates['client_key']} " - f"--output /dev/null --write-out '%{{http_code}}' " - f"--data '{json.dumps(test_report)}' --header 'Content-Type: application/json' " - f"https://{server_fqdn}/api/v2/config_reports" + cmd = curl_request( + "api/v2/config_reports", + method="POST", + data=json.dumps(test_report), + headers={"Content-Type": "application/json"}, ) assert cmd.succeeded assert cmd.stdout == '201' diff --git a/tests/httpd_test.py b/tests/httpd_test.py index b4cb76ec1..70b98383d 100644 --- a/tests/httpd_test.py +++ b/tests/httpd_test.py @@ -24,8 +24,8 @@ def test_https_port(server): @pytest.mark.feature('foreman') -def test_http_foreman_ping(server, server_fqdn): - cmd = server.run(f"{CURL_CMD} --write-out '%{{redirect_url}}' http://{server_fqdn}/api/v2/ping") +def test_http_foreman_ping(curl_request, server_fqdn): + cmd = curl_request("api/v2/ping") assert cmd.succeeded assert cmd.stdout == f'https://{server_fqdn}/api/v2/ping' @@ -61,14 +61,14 @@ def test_https_pulp_content(server, certificates, server_fqdn): assert "Index of /pulp/content/" in cmd.stdout -def test_https_pulp_auth(server, certificates, server_fqdn): - cmd = server.run(f"{CURL_CMD} --cacert {certificates['server_ca_certificate']} --write-out '%{{http_code}}' --cert {certificates['client_certificate']} --key {certificates['client_key']} https://{server_fqdn}/pulp/api/v3/users/") +def test_https_pulp_auth(curl_request): + cmd = curl_request("pulp/api/v3/users/") assert cmd.succeeded assert cmd.stdout == '200' -def test_https_pypi_endpoint(server, certificates, server_fqdn): - cmd = server.run(f"curl --cacert {certificates['server_ca_certificate']} https://{server_fqdn}/pypi/test/") +def test_https_pypi_endpoint(curl_request): + cmd = curl_request("pypi/test/", return_body=True) assert cmd.succeeded # Verify route proxies to Pulp's Python plugin by checking for PythonDistribution in response # (Rails or unconfigured routes would return different errors) @@ -82,40 +82,40 @@ def test_pub_directory_exists(server): assert pub_dir.mode == 0o755 -def test_http_pub_directory_accessible(server, server_fqdn): - cmd = server.run(f"{CURL_CMD} --write-out '%{{http_code}}' http://{server_fqdn}/pub/") +def test_http_pub_directory_accessible(curl_request): + cmd = curl_request("pub/") assert cmd.succeeded assert cmd.stdout == '200' -def test_https_pub_directory_accessible(server, certificates, server_fqdn): - cmd = server.run(f"{CURL_CMD} --cacert {certificates['server_ca_certificate']} --write-out '%{{http_code}}' https://{server_fqdn}/pub/") +def test_https_pub_directory_accessible(curl_request): + cmd = curl_request("pub/") assert cmd.succeeded assert cmd.stdout == '200' -def test_http_pub_server_ca_certificate_downloadable(server, server_fqdn): - cmd = server.run(f"{CURL_CMD} --write-out '%{{http_code}}' http://{server_fqdn}/pub/katello-server-ca.crt") +def test_http_pub_server_ca_certificate_downloadable(curl_request): + cmd = curl_request("pub/katello-server-ca.crt") assert cmd.succeeded assert cmd.stdout == '200' -def test_https_pub_server_ca_certificate_downloadable(server, certificates, server_fqdn): - cmd = server.run(f"{CURL_CMD} --cacert {certificates['server_ca_certificate']} --write-out '%{{http_code}}' https://{server_fqdn}/pub/katello-server-ca.crt") +def test_https_pub_server_ca_certificate_downloadable(curl_request): + cmd = curl_request("pub/katello-server-ca.crt") assert cmd.succeeded assert cmd.stdout == '200' @pytest.mark.feature('foreman') -def test_http_foreman_login(server, server_fqdn): - cmd = server.run(f"{CURL_CMD} --write-out '%{{http_code}}' http://{server_fqdn}/users/login") +def test_http_foreman_login(curl_request): + cmd = curl_request("users/login") assert cmd.succeeded assert cmd.stdout == '301' @pytest.mark.feature('foreman') -def test_https_foreman_login(server, certificates, server_fqdn): - cmd = server.run(f"{CURL_CMD} --cacert {certificates['server_ca_certificate']} --write-out '%{{http_code}}' https://{server_fqdn}/users/login") +def test_https_foreman_login(curl_request): + cmd = curl_request("users/login") assert cmd.succeeded assert cmd.stdout == '200' From a489369de1df59ddfbc1b6a9f480b4ed2984d50f Mon Sep 17 00:00:00 2001 From: Arvind Jangir Date: Fri, 26 Jun 2026 17:04:41 +0530 Subject: [PATCH 19/22] verify custom certs also --- .github/workflows/test.yml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cd5bc1e88..d5db30268 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -373,6 +373,12 @@ jobs: wait-timeout-minutes: 5 foreman-proxy-content-tests: + strategy: + fail-fast: false + matrix: + certificate_source: + - default + - custom_server runs-on: ubuntu-24.04 name: "Tests Proxy Deployment" steps: @@ -395,9 +401,16 @@ jobs: - name: Run image pull run: | ./foremanctl pull-images + - name: Create custom certificates + if: matrix.certificate_source == 'custom_server' + run: | + ./forge custom-certs + ./forge custom-certs --hostname proxy.example.com - name: Deploy Katello on quadlet run: | ./foremanctl deploy \ + --certificate-source=${{ matrix.certificate_source }} \ + ${{ matrix.certificate_source == 'custom_server' && '--certificate-server-certificate /root/custom-certificates/certs/quadlet.example.com.crt --certificate-server-key /root/custom-certificates/private/quadlet.example.com.key --certificate-server-ca-certificate /root/custom-certificates/certs/ca.crt' || '' }} \ --initial-admin-password=changeme \ --tuning development \ --add-feature foreman-proxy @@ -405,7 +418,9 @@ jobs: run: rm -f .var/lib/foremanctl/parameters.yaml - name: Generate certificates bundle on quadlet run: | - ./foremanctl certificate-bundle proxy.example.com + ./foremanctl certificate-bundle proxy.example.com \ + --certificate-source=${{ matrix.certificate_source }} \ + ${{ matrix.certificate_source == 'custom_server' && '--certificate-server-certificate /root/custom-certificates/certs/proxy.example.com.crt --certificate-server-key /root/custom-certificates/private/proxy.example.com.key' || '' }} - name: Fetch certificates bundle from quadlet run: | ./forge fetch-bundle proxy.example.com From 11c0734225a9499de2e8e78d0d00a7855d3e394f Mon Sep 17 00:00:00 2001 From: Arvind Jangir Date: Fri, 26 Jun 2026 18:35:35 +0530 Subject: [PATCH 20/22] fix tests --- src/roles/certificate_bundle/tasks/main.yml | 2 +- tests/certificates_test.py | 2 ++ tests/httpd_test.py | 8 ++++---- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/roles/certificate_bundle/tasks/main.yml b/src/roles/certificate_bundle/tasks/main.yml index ce3c15827..4700d4b7d 100644 --- a/src/roles/certificate_bundle/tasks/main.yml +++ b/src/roles/certificate_bundle/tasks/main.yml @@ -61,7 +61,7 @@ src: "{{ certificate_bundle_server_key }}" dest: "{{ certificate_bundle_build_directory.path }}/certs/private/{{ certificate_bundle_hostname }}.key" remote_src: true - mode: '0600' + mode: '0440' - name: Copy client certificate ansible.builtin.copy: diff --git a/tests/certificates_test.py b/tests/certificates_test.py index 794eaa742..e331e68fe 100644 --- a/tests/certificates_test.py +++ b/tests/certificates_test.py @@ -80,6 +80,8 @@ def test_client_certificate_chain_verifies(server, certificates): def test_localhost_certificate_issued_by_internal_ca(server, certificates, custom_certificates): + if not server.file(certificates['localhost_certificate']).exists: + pytest.skip("localhost certificate not present in proxy deployment") localhost_info = certificate_info(server, certificates['localhost_certificate']) ca_info = certificate_info(server, certificates['ca_certificate']) assert localhost_info['issuer'] == ca_info['subject'], \ diff --git a/tests/httpd_test.py b/tests/httpd_test.py index 70b98383d..b0ec165b3 100644 --- a/tests/httpd_test.py +++ b/tests/httpd_test.py @@ -24,10 +24,10 @@ def test_https_port(server): @pytest.mark.feature('foreman') -def test_http_foreman_ping(curl_request, server_fqdn): +def test_http_foreman_ping(curl_request): cmd = curl_request("api/v2/ping") assert cmd.succeeded - assert cmd.stdout == f'https://{server_fqdn}/api/v2/ping' + assert cmd.stdout == '200' @pytest.mark.feature('foreman') @@ -107,8 +107,8 @@ def test_https_pub_server_ca_certificate_downloadable(curl_request): @pytest.mark.feature('foreman') -def test_http_foreman_login(curl_request): - cmd = curl_request("users/login") +def test_http_foreman_login(server, server_fqdn): + cmd = server.run(f"{CURL_CMD} --write-out '%{{http_code}}' http://{server_fqdn}/users/login") assert cmd.succeeded assert cmd.stdout == '301' From 157fb88a4b4fe0f85c62d8967c46e89c96d58243 Mon Sep 17 00:00:00 2001 From: Arvind Jangir Date: Mon, 29 Jun 2026 14:06:13 +0530 Subject: [PATCH 21/22] Switch to valkey --- src/playbooks/deploy-proxy/deploy-proxy.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/playbooks/deploy-proxy/deploy-proxy.yaml b/src/playbooks/deploy-proxy/deploy-proxy.yaml index 38b97755c..6ea0967e8 100644 --- a/src/playbooks/deploy-proxy/deploy-proxy.yaml +++ b/src/playbooks/deploy-proxy/deploy-proxy.yaml @@ -26,7 +26,7 @@ - role: postgresql when: - database_mode == 'internal' - - redis + - valkey - httpd - pulp - systemd_target From b709d6fafb2b7831c27f4d98280b3e1f3b687a72 Mon Sep 17 00:00:00 2001 From: Arvind Jangir Date: Wed, 1 Jul 2026 11:30:32 +0530 Subject: [PATCH 22/22] Update doc and update certs permission --- docs/developer/deployment.md | 32 ++++++++++++++++----- src/roles/certificate_bundle/tasks/main.yml | 2 +- src/roles/foreman_proxy/defaults/main.yaml | 4 +-- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/docs/developer/deployment.md b/docs/developer/deployment.md index 2dd1af767..cc8f67bc2 100644 --- a/docs/developer/deployment.md +++ b/docs/developer/deployment.md @@ -15,14 +15,32 @@ Deploys a Foreman server. This is the primary deployment type and the default en ### Proxy -Deploys a Foreman Smart Proxy node that connects to an foreman server. +Deploys a Foreman Proxy node that connects to a Foreman server. -```bash -./foremanctl deploy-proxy \ - --flavor foreman-proxy-content \ - --certificate-bundle /root/proxy.example.com-certs.tar \ - --foreman-fqdn quadlet.example.com -``` +Before running the proxy deployment, a certificate bundle must be generated on the Foreman server and copied to the proxy VM: + +1. On the **Foreman server**, generate a certificate bundle for the proxy hostname: + + ```bash + ./foremanctl certificate-bundle proxy.example.com + ``` + + This produces a tar archive at a path like `/var/lib/foremanctl/certs/bundles/.tar.gz`. + +2. Copy the bundle to the **proxy VM**: + + ```bash + scp /var/lib/foremanctl/certs/bundles/proxy.example.com.tar.gz root@proxy.example.com:/root/proxy.example.com.tar.gz + ``` + +3. On the **proxy VM**, run the deployment: + + ```bash + ./foremanctl deploy-proxy \ + --flavor foreman-proxy-content \ + --certificate-bundle /root/proxy.example.com.tar.gz \ + --foreman-fqdn quadlet.example.com + ``` ## Deployment Paths diff --git a/src/roles/certificate_bundle/tasks/main.yml b/src/roles/certificate_bundle/tasks/main.yml index 4700d4b7d..9b352ee45 100644 --- a/src/roles/certificate_bundle/tasks/main.yml +++ b/src/roles/certificate_bundle/tasks/main.yml @@ -75,7 +75,7 @@ src: "{{ certificate_bundle_client_key }}" dest: "{{ certificate_bundle_build_directory.path }}/certs/private/{{ certificate_bundle_hostname }}-client.key" remote_src: true - mode: '0600' + mode: '0440' - name: Create tarball community.general.archive: diff --git a/src/roles/foreman_proxy/defaults/main.yaml b/src/roles/foreman_proxy/defaults/main.yaml index 4ae4068f9..abd664b17 100644 --- a/src/roles/foreman_proxy/defaults/main.yaml +++ b/src/roles/foreman_proxy/defaults/main.yaml @@ -16,9 +16,7 @@ foreman_proxy_features: "{{ foreman_proxy_base_features + foreman_proxy_plugins foreman_proxy_available_features: "{{ [] | available_foreman_proxy_plugins }}" foreman_proxy_disabled_features: "{{ foreman_proxy_available_features | difference(foreman_proxy_features) }}" -# In case of standalone proxy, we need to add foreman server URL not host fqdn -foreman_proxy_with_foreman: "{{ 'foreman' in enabled_features }}" -foreman_proxy_foreman_server_url: "{{ foreman_proxy_with_foreman | ternary('https://{{ ansible_facts['fqdn'] }}', foreman_proxy_foreman_server_url) }}" +foreman_proxy_foreman_server_url: "https://{{ ansible_facts['fqdn'] }}" # BMC settings foreman_proxy_bmc_ipmi_implementation: ipmitool