Skip to content

Commit f0e2546

Browse files
ehelmsclaude
andcommitted
Add Podman quadlet .image file support
Introduce .image quadlet units to decouple image sourcing from container definitions. Container roles now reference Image=<name>.image instead of full registry URLs, and a new images role handles deployment with a three-tier precedence model: admin overrides (/etc/foremanctl/images.d/) > vendor RPMs (/usr/share/foremanctl/images.d/) > generated defaults. Resolves: #277 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent e531086 commit f0e2546

69 files changed

Lines changed: 507 additions & 276 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ jobs:
135135
vagrant ssh quadlet -- sudo systemctl restart fapolicyd
136136
- name: Run image pull
137137
run: |
138-
./foremanctl pull-images
138+
./foremanctl pull-images ${{ matrix.database == 'external' && '--database-mode=external' || '' }}
139139
- name: Run deployment
140140
run: |
141141
./foremanctl deploy \

docs/developer/deployment.md

Lines changed: 100 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -52,23 +52,109 @@ IOP (Insights Operating Platform) deploys on-premise Insights services for advis
5252

5353
See [IOP Architecture](iop.md) for details on the services deployed and configuration options.
5454

55-
### Authenticated Registry Handling
55+
### Image Management
5656

57-
If you need to pull images from private or authenticated container registries, you can configure registry authentication using Podman's auth file.
57+
foremanctl uses Podman quadlet `.image` units to separate image sourcing from container definitions. Each unique container image (foreman, candlepin, pulp, etc.) gets a corresponding `.image` file deployed to `/etc/containers/systemd/`. Container roles reference these by name rather than by full image URL:
5858

59-
#### Setting up Registry Authentication
59+
```ini
60+
# /etc/containers/systemd/foreman.image
61+
[Image]
62+
Image=quay.io/foreman/foreman:nightly
63+
```
6064

61-
1. **Login to your registry** using Podman and save credentials to the default auth file location:
62-
```bash
63-
podman login <registry> --authfile=/etc/foreman/registry-auth.json
65+
```ini
66+
# /etc/containers/systemd/foreman.container (excerpt)
67+
[Container]
68+
Image=foreman.image
6469
```
6570

66-
2. **Deploy as usual** - foremanctl will automatically detect and use the authentication file:
67-
```bash
68-
./foremanctl deploy
71+
All containers that share a base image (e.g., foreman, dynflow-sidekiq, foreman-recurring) reference the same `.image` unit. systemd ensures the image is pulled before any dependent container starts.
72+
73+
#### Image Overrides via Drop-ins
74+
75+
foremanctl uses quadlet's native drop-in mechanism for image overrides. Each `.image` file has a corresponding `.image.d/` directory. Drop-in `.conf` files placed there are merged on top of the base in lexicographic order — last wins.
76+
77+
The quadlet generator reads from two directory tiers, with `/etc/` taking precedence over `/usr/share/`:
78+
79+
```
80+
/usr/share/containers/systemd/
81+
foreman.image.d/
82+
10-product.conf # vendor/RPM layer
83+
20-archive.conf # ISO/archive layer
84+
85+
/etc/containers/systemd/
86+
foreman.image # base, always generated by foremanctl
87+
foreman.image.d/
88+
90-user.conf # user override layer
89+
```
90+
91+
Precedence (last wins):
92+
93+
1. `foreman.image` — foremanctl default from `images.yml`
94+
2. `10-product.conf` — vendor/RPM provided
95+
3. `20-archive.conf` — ISO or archive extraction provided
96+
4. `90-user.conf` — user provided (highest priority)
97+
98+
#### Use Cases
99+
100+
**Upstream default (no user action):** foremanctl generates `.image` files from its built-in `images.yml`:
101+
102+
```ini
103+
# /etc/containers/systemd/foreman.image (generated by foremanctl)
104+
[Image]
105+
Image=quay.io/foreman/foreman:nightly
106+
```
107+
108+
**RPM-provided images:** A product RPM ships numbered drop-ins to `/usr/share/containers/systemd/` pointing at the product registry. No user action required beyond installing the RPM:
109+
110+
```ini
111+
# /usr/share/containers/systemd/foreman.image.d/10-product.conf (from RPM)
112+
[Image]
113+
Image=registry.example.com/org/foreman:6.17
114+
AuthFile=/etc/foreman/registry-auth.json
69115
```
70116

71-
This approach integrates seamlessly with both the happy path and advanced deployment paths described above. The authentication is handled transparently during image pulling operations.
117+
**Disconnected install from ISO:** The ISO extraction adds a higher-numbered drop-in alongside the RPM layer, redirecting pulls to local archives:
118+
119+
```ini
120+
# /usr/share/containers/systemd/foreman.image.d/20-archive.conf (from ISO)
121+
[Image]
122+
Image=docker-archive:/opt/foreman/images/foreman-6.17.tar
123+
```
124+
125+
**User's own registry:** For redirecting all images to a private registry that mirrors upstream image names, use a `registries.conf.d` entry — one file covers all images:
126+
127+
```ini
128+
# /etc/containers/registries.conf.d/50-foremanctl-mirror.conf
129+
[[registry]]
130+
prefix = "quay.io/theforeman"
131+
location = "katello.example.com/Default_Organization"
132+
```
133+
134+
If image names or tags differ from upstream, use per-image drop-ins instead:
135+
136+
```ini
137+
# /etc/containers/systemd/foreman.image.d/90-user.conf
138+
[Image]
139+
Image=katello.example.com/Default_Organization/foreman:6.17
140+
AuthFile=/etc/foreman/registry-auth.json
141+
```
142+
143+
**Developer testing a container build:** The developer creates a `90-user.conf` drop-in for the image under test. All other images are unaffected:
144+
145+
```ini
146+
# /etc/containers/systemd/foreman.image.d/90-user.conf
147+
[Image]
148+
Image=quay.io/foreman/foreman:pr-12345
149+
```
150+
151+
#### Authenticated Registry Handling
152+
153+
foremanctl configures all image units to use `/etc/foreman/registry-auth.json` as the credential file. When pulling images from an authenticated registry, log in once before deploying:
154+
155+
```bash
156+
podman login <registry> --authfile=/etc/foreman/registry-auth.json
157+
```
72158

73159
## Deployer Stages
74160

@@ -81,7 +167,7 @@ Some of the stages will be made available to the user to run independently.
81167
a. system requirements
82168
b. tuning requirements
83169
c. certificate requirements
84-
4. Place `.container` files
170+
4. Place `.image` and `.container` files
85171
5. Create podman secrets
86172
6. Reload systemd
87173
7. (re)start services
@@ -103,7 +189,9 @@ When the user provides parameters to alter the deployment, the deployment utilit
103189

104190
## Container changes (Upgrades)
105191

106-
When the running containers change because the stream was changed in the configuration, the deployment utility will pull the new images and use the new images when starting services.
192+
When the running containers change because the stream was changed in the configuration, the deployment utility regenerates `.image` units with the new image references and restarts services to pull and use the updated images.
193+
194+
User drop-in overrides in `.image.d/90-user.conf` take precedence over the base `.image` values — if a user-provided drop-in pins a specific tag, it will not be changed by an upgrade.
107195

108196
As there is currently no way for the deployment utility to verify which image version is used by a running service, the user is advised to stop all services before performing an upgrade.
109197

src/playbooks/pull-images/pull-images.yaml

Lines changed: 42 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,27 +11,49 @@
1111
roles:
1212
- role: pre_install
1313
post_tasks:
14-
- name: Pull an image
15-
containers.podman.podman_image:
14+
- name: Deploy core image units
15+
ansible.builtin.include_role:
1616
name: "{{ item }}"
17-
environment:
18-
REGISTRY_AUTH_FILE: "{{ registry_auth_file }}"
19-
loop: "{{ images }}"
17+
tasks_from: image.yaml
18+
loop:
19+
- foreman
20+
- candlepin
21+
- pulp
22+
- redis
2023

21-
- name: Pull foreman_proxy images
22-
containers.podman.podman_image:
23-
name: "{{ item }}"
24-
environment:
25-
REGISTRY_AUTH_FILE: "{{ registry_auth_file }}"
26-
loop: "{{ foreman_proxy_images }}"
27-
when:
28-
- "'foreman-proxy' in enabled_features"
24+
- name: Deploy database image units
25+
ansible.builtin.include_role:
26+
name: postgresql
27+
tasks_from: image.yaml
28+
when: database_mode == 'internal'
29+
30+
- name: Deploy proxy image units
31+
ansible.builtin.include_role:
32+
name: foreman_proxy
33+
tasks_from: image.yaml
34+
when: "'foreman-proxy' in enabled_features"
2935

30-
- name: Pull database images
31-
containers.podman.podman_image:
36+
- name: Deploy IOP image units
37+
ansible.builtin.include_role:
3238
name: "{{ item }}"
33-
environment:
34-
REGISTRY_AUTH_FILE: "{{ registry_auth_file }}"
35-
loop: "{{ database_images }}"
36-
when:
37-
- database_mode == 'internal'
39+
tasks_from: image.yaml
40+
loop:
41+
- iop_kafka
42+
- iop_ingress
43+
- iop_puptoo
44+
- iop_yuptoo
45+
- iop_engine
46+
- iop_gateway
47+
- iop_inventory
48+
- iop_advisor
49+
- iop_remediation
50+
- iop_vmaas
51+
- iop_vulnerability
52+
- iop_advisor_frontend
53+
- iop_inventory_frontend
54+
- iop_vulnerability_frontend
55+
when: "'iop' in enabled_features"
56+
57+
- name: Run daemon reload
58+
ansible.builtin.systemd:
59+
daemon_reload: true

src/roles/candlepin/defaults/main.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ candlepin_ciphers:
1414
- TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256
1515
candlepin_container_image: quay.io/foreman/candlepin
1616
candlepin_container_tag: "4.4.14"
17-
candlepin_registry_auth_file: /etc/foreman/registry-auth.json
1817

1918
candlepin_database_host: localhost
2019
candlepin_database_port: 5432
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
- name: Deploy candlepin image unit
3+
ansible.builtin.include_role:
4+
name: images
5+
tasks_from: deploy_image.yaml
6+
vars:
7+
images_definition:
8+
name: candlepin
9+
image: "{{ candlepin_container_image }}:{{ candlepin_container_tag }}"

src/roles/candlepin/tasks/main.yml

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
---
2+
- name: Deploy candlepin image
3+
ansible.builtin.include_tasks: image.yaml
4+
25
- name: Create log directories
36
ansible.builtin.file:
47
path: "{{ item }}"
@@ -55,17 +58,10 @@
5558
notify:
5659
- Restart candlepin
5760

58-
- name: Pull the Candlepin container image
59-
containers.podman.podman_image:
60-
name: "{{ candlepin_container_image }}:{{ candlepin_container_tag }}"
61-
state: present
62-
environment:
63-
REGISTRY_AUTH_FILE: "{{ candlepin_registry_auth_file }}"
64-
6561
- name: Deploy Candlepin quadlet
6662
containers.podman.podman_container:
6763
name: "candlepin"
68-
image: "{{ candlepin_container_image }}:{{ candlepin_container_tag }}"
64+
image: candlepin.image
6965
state: quadlet
7066
network: host
7167
hostname: "{{ ansible_facts['hostname'] }}.local"

src/roles/foreman/defaults/main.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
---
22
foreman_container_image: "quay.io/foreman/foreman"
33
foreman_container_tag: "nightly"
4-
foreman_registry_auth_file: /etc/foreman/registry-auth.json
54

65
foreman_database_name: foreman
76
foreman_database_user: foreman

src/roles/foreman/tasks/image.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
- name: Deploy foreman image unit
3+
ansible.builtin.include_role:
4+
name: images
5+
tasks_from: deploy_image.yaml
6+
vars:
7+
images_definition:
8+
name: foreman
9+
image: "{{ foreman_container_image }}:{{ foreman_container_tag }}"

src/roles/foreman/tasks/main.yaml

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
---
2-
- name: Pull the Foreman container image
3-
containers.podman.podman_image:
4-
name: "{{ foreman_container_image }}:{{ foreman_container_tag }}"
5-
state: present
6-
environment:
7-
REGISTRY_AUTH_FILE: "{{ foreman_registry_auth_file }}"
2+
- name: Deploy foreman image
3+
ansible.builtin.include_tasks: image.yaml
84

95
- name: Create secret for DATABASE_URL
106
containers.podman.podman_secret:
@@ -98,7 +94,7 @@
9894
- name: Deploy Foreman Container
9995
containers.podman.podman_container:
10096
name: "foreman"
101-
image: "{{ foreman_container_image }}:{{ foreman_container_tag }}"
97+
image: foreman.image
10298
state: quadlet
10399
sdnotify: true
104100
network: host
@@ -136,7 +132,7 @@
136132
containers.podman.podman_container:
137133
name: "dynflow-sidekiq-%i"
138134
quadlet_filename: "dynflow-sidekiq@"
139-
image: "{{ foreman_container_image }}:{{ foreman_container_tag }}"
135+
image: foreman.image
140136
state: quadlet
141137
sdnotify: true
142138
network: host
@@ -191,7 +187,7 @@
191187
name: "foreman-recurring-{{ item.instance }}"
192188
quadlet_filename: "foreman-recurring@{{ item.instance }}"
193189
state: quadlet
194-
image: "{{ foreman_container_image }}:{{ foreman_container_tag }}"
190+
image: foreman.image
195191
sdnotify: false
196192
network: host
197193
hostname: "{{ ansible_facts['hostname'] }}.local"

src/roles/foreman_proxy/defaults/main.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
---
22
foreman_proxy_container_image: "quay.io/foreman/foreman-proxy"
33
foreman_proxy_container_tag: "nightly"
4-
foreman_proxy_registry_auth_file: /etc/foreman/registry-auth.json
54

65
foreman_proxy_name: "{{ ansible_facts['fqdn'] }}"
76
foreman_proxy_https_port: 8443

0 commit comments

Comments
 (0)