Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,83 @@ jobs:
limit-access-to-actor: true
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:
- 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 "quadlet proxy"
- 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
- name: Remove old parameters.yaml
run: rm -f .var/lib/foremanctl/parameters.yaml

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why do we have to do this?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

To simulate deployment on a different box basically

@arvind4501 arvind4501 Jun 29, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Because since earlier foremanctl deploy writes its params and they share same parameters.yaml and as obsah resuses existing params, so its good to use a clean empty params file for deploy-proxy
and as evgeni mentioned(i didn't refresh earlier)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is going to be a pain locally. Obviously doesn't block this PR but we either need to namespace this or what I have been feeling is that this should live on the target. I don't think that would break our design to be able to run foremanctl from a remote location. And, the parameters.yaml feels like information that should exist on the machine itself given the level of important information that it stores. Really, the whole directory should.

- name: Generate certificates bundle on quadlet
run: |
./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
- name: Deploy content proxy
run: |
./foremanctl deploy-proxy \
--flavor foreman-proxy-content \
--certificate-bundle $(pwd)/.var/lib/foremanctl/proxy.example.com.tar.gz \
--foreman-fqdn quadlet.example.com
- name: Run tests
run: |
./forge test --pytest-args="--server-hostname=proxy"
- 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()
Expand All @@ -380,6 +457,7 @@ jobs:
- devel-tests
- upgrade
- migration
- foreman-proxy-content-tests
- ansible-lint
- python-lint
runs-on: ubuntu-latest
Expand Down
10 changes: 10 additions & 0 deletions Vagrantfile
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions development/playbooks/deploy-dev/deploy-dev.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
14 changes: 14 additions & 0 deletions development/playbooks/fetch-bundle/fetch-bundle.yaml
Original file line number Diff line number Diff line change
@@ -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
8 changes: 8 additions & 0 deletions development/playbooks/fetch-bundle/metadata.obsah.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
help: |
Fetch a certificate bundle
variables:
hostname:
parameter: hostname
help: Hostname to fetch the certificate bundle for.
42 changes: 42 additions & 0 deletions docs/developer/deployment.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,47 @@
# Deployment Design

## Deployment Types

foremanctl supports two deployment types: **server** and **proxy**. Each has its own sub-command, flavor, and set of services.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@ehelms was exploring whether we could give the external proxy a better name ("node", "capsule", whatever).
I am not sure how far he got with that (and I am sure we shouldn't block/wait on it), but that will probably produce some follow up work.

Additionally the current PR uses "Foreman Proxy", "node" etc rather inconsistently in the docs/help output.
Should probably cleaned up, but obviously doesn't break the workflow.


### Server

Deploys a Foreman server. This is the primary deployment type and the default entry point.


```bash
./foremanctl deploy
```

### Proxy

Deploys a Foreman Proxy node that connects to a Foreman server.

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/<hostname>.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

### Happy Path
Expand Down
35 changes: 35 additions & 0 deletions docs/developer/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<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/<active_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/<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:
Expand Down Expand Up @@ -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/<name>_test.py` |
| Behavior specific to a deployment flavor | `tests/flavor/<flavor>/<name>_test.py` |

When adding a new file, follow the conventions of the nearest existing test file.
2 changes: 2 additions & 0 deletions pytest.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[pytest]
addopts = ["--import-mode=importlib"]
26 changes: 26 additions & 0 deletions src/features.d/content.yaml
Comment thread
evgeni marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -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
8 changes: 8 additions & 0 deletions src/features.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,21 @@ 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:
plugin_name: katello
hammer: katello
dependencies:
- tasks
- pulp
- candlepin
google:
description: Google Compute Engine plugin for Foreman
hammer: foreman_google
Expand Down
6 changes: 6 additions & 0 deletions src/filter_plugins/foremanctl.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand All @@ -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,
}
26 changes: 26 additions & 0 deletions src/playbooks/_certificates_custom/metadata.obsah.yaml
Original file line number Diff line number Diff line change
@@ -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]
4 changes: 0 additions & 4 deletions src/playbooks/_flavor_features/metadata.obsah.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
---
variables:
flavor:
help: Base flavor to use in this deployment.
choices:
- katello
features:
parameter: --add-feature
help: Additional features to enable in this deployment.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
include:
- _pulp
Comment thread
evgeni marked this conversation as resolved.
- _foreman_proxy
30 changes: 30 additions & 0 deletions src/playbooks/_flavors/katello/metadata.obsah.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
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
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
- _certificate_validity
- _certificates_custom
- _database_mode
- _database_connection
- _foreman
- _foreman_proxy
- _pulp
- _tuning
16 changes: 16 additions & 0 deletions src/playbooks/_foreman/metadata.obsah.yaml
Original file line number Diff line number Diff line change
@@ -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.
Loading