Ansible Satellite and Foreman Integration: Complete Automation Guide
By Luca Berton · Published 2024-01-01 · Category: installation
Integrate Ansible with Red Hat Satellite and Foreman for enterprise server lifecycle management.
Red Hat Satellite (and its upstream project Foreman) provides enterprise-grade server lifecycle management — provisioning, patching, content management, and compliance. Integrating Ansible with Satellite/Foreman creates a powerful automation platform that combines Satellite's inventory and content management with Ansible's configuration automation. This guide covers the complete integration with production-ready examples.
Architecture Overview
Satellite/Foreman serves as the central management platform while Ansible provides the execution engine:
┌──────────────────────────┐
│ Red Hat Satellite 6.x │
│ (or Foreman + Katello) │
│ │
│ ┌─────────────────────┐ │
│ │ Content Management │ │ ┌─────────────────────┐
│ │ - Repos & Products │ │ │ Ansible Controller │
│ │ - Content Views │ │◄──►│ (AAP / AWX) │
│ │ - Lifecycle Envs │ │ │ │
│ ├─────────────────────┤ │ │ - Satellite Inventory│
│ │ Host Management │ │ │ - Playbooks │
│ │ - Provisioning │ │ │ - Roles & Collections│
│ │ - Host Groups │ │ └─────────────────────┘
│ │ - Parameters │ │ │
│ ├─────────────────────┤ │ ▼
│ │ Ansible Integration │ │ ┌─────────────────────┐
│ │ - Roles Assignment │ │ │ Managed Hosts │
│ │ - Reports Callback │ │ │ (RHEL / CentOS) │
│ └─────────────────────┘ │ └─────────────────────┘
└──────────────────────────┘
Key Collections
# Install required collections
ansible-galaxy collection install redhat.satellite
ansible-galaxy collection install theforeman.foreman
# Verify installation
ansible-galaxy collection list | grep -E 'satellite|foreman'
See also: Ansible Patch Management: Automated OS Patching Across Linux and Windows Enterprise Fleets
Authentication Setup
# group_vars/all.yml - Satellite connection vars
satellite_url: "https://satellite.example.com"
satellite_username: "admin"
satellite_password: "{{ vault_satellite_password }}"
satellite_organization: "MyOrg"
satellite_location: "Default Location"
satellite_validate_certs: true
Content Management Automation
Create Products and Repositories
---
- name: Configure Satellite content
hosts: localhost
vars_files:
- vault.yml
module_defaults:
group/redhat.satellite.foreman:
server_url: "{{ satellite_url }}"
username: "{{ satellite_username }}"
password: "{{ satellite_password }}"
validate_certs: "{{ satellite_validate_certs }}"
tasks:
- name: Create custom product
redhat.satellite.product:
name: "Internal Applications"
organization: "{{ satellite_organization }}"
description: "Internal application packages"
state: present
- name: Create YUM repository
redhat.satellite.repository:
name: "Internal Apps - RHEL 9"
product: "Internal Applications"
organization: "{{ satellite_organization }}"
content_type: yum
url: "https://repo.internal.example.com/rhel9/apps/"
download_policy: on_demand
mirror_on_sync: true
state: present
- name: Sync repository
redhat.satellite.repository_sync:
product: "Internal Applications"
repository: "Internal Apps - RHEL 9"
organization: "{{ satellite_organization }}"
Content Views and Lifecycle Environments
---
- name: Manage content views and lifecycle
hosts: localhost
module_defaults:
group/redhat.satellite.foreman:
server_url: "{{ satellite_url }}"
username: "{{ satellite_username }}"
password: "{{ satellite_password }}"
validate_certs: "{{ satellite_validate_certs }}"
tasks:
- name: Create lifecycle environments
redhat.satellite.lifecycle_environment:
name: "{{ item.name }}"
prior: "{{ item.prior }}"
organization: "{{ satellite_organization }}"
description: "{{ item.description }}"
state: present
loop:
- { name: "Development", prior: "Library", description: "Development environment" }
- { name: "Testing", prior: "Development", description: "QA and testing" }
- { name: "Staging", prior: "Testing", description: "Pre-production staging" }
- { name: "Production", prior: "Staging", description: "Production environment" }
- name: Create content view
redhat.satellite.content_view:
name: "RHEL9-BaseOS-AppStream"
organization: "{{ satellite_organization }}"
repositories:
- name: "Red Hat Enterprise Linux 9 for x86_64 - BaseOS RPMs 9"
product: "Red Hat Enterprise Linux for x86_64"
- name: "Red Hat Enterprise Linux 9 for x86_64 - AppStream RPMs 9"
product: "Red Hat Enterprise Linux for x86_64"
- name: "Internal Apps - RHEL 9"
product: "Internal Applications"
state: present
- name: Publish content view
redhat.satellite.content_view_version:
content_view: "RHEL9-BaseOS-AppStream"
organization: "{{ satellite_organization }}"
description: "Monthly patch release {{ ansible_date_time.date }}"
lifecycle_environments:
- Development
- name: Promote content view to Testing
redhat.satellite.content_view_version:
content_view: "RHEL9-BaseOS-AppStream"
organization: "{{ satellite_organization }}"
current_lifecycle_environment: Development
lifecycle_environments:
- Testing
See also: RHSB-2024–001 Leaky Vessels — runc — (CVE-2024–21626)
Host Provisioning
Host Groups and Configuration
---
- name: Configure host groups
hosts: localhost
module_defaults:
group/redhat.satellite.foreman:
server_url: "{{ satellite_url }}"
username: "{{ satellite_username }}"
password: "{{ satellite_password }}"
validate_certs: "{{ satellite_validate_certs }}"
tasks:
- name: Create host group for web servers
redhat.satellite.hostgroup:
name: "RHEL9/WebServers"
organization: "{{ satellite_organization }}"
locations:
- "{{ satellite_location }}"
lifecycle_environment: "Production"
content_view: "RHEL9-BaseOS-AppStream"
content_source: "satellite.example.com"
compute_resource: "VMware vCenter"
compute_profile: "Medium - 4CPU 8GB"
domain: "example.com"
subnet: "Production-VLAN100"
architecture: "x86_64"
operatingsystem: "RHEL 9.4"
ptable: "Kickstart default"
root_pass: "{{ vault_root_password }}"
parameters:
- name: "app_role"
value: "webserver"
- name: "monitoring"
value: "enabled"
state: present
- name: Provision new host
redhat.satellite.host:
name: "web-prod-05.example.com"
organization: "{{ satellite_organization }}"
location: "{{ satellite_location }}"
hostgroup: "RHEL9/WebServers"
build: true
managed: true
compute_resource: "VMware vCenter"
compute_attributes:
cpus: 4
memory_mb: 8192
cluster: "Production-Cluster"
volumes_attributes:
0:
size_gb: 50
datastore: "SAN-Prod"
state: present
Satellite as Dynamic Inventory
Configure Satellite Inventory Plugin
# satellite_inventory.yml
plugin: redhat.satellite.foreman
url: https://satellite.example.com
user: admin
password: "{{ lookup('env', 'SATELLITE_PASSWORD') }}"
validate_certs: true
# Group hosts by these Satellite attributes
group_prefix: satellite_
want_hostcollections: true
want_params: true
want_facts: true
# Organize hosts by lifecycle environment
keyed_groups:
- key: foreman_lifecycle_environment
prefix: lifecycle
separator: "_"
- key: foreman_content_view
prefix: cv
separator: "_"
- key: foreman_hostgroup_title | replace("/", "_")
prefix: hostgroup
separator: "_"
- key: foreman_location
prefix: location
separator: "_"
- key: foreman_params.app_role | default('unassigned')
prefix: role
separator: "_"
# Only include managed hosts
filters:
- managed = true
Usage:
# List inventory from Satellite
ansible-inventory -i satellite_inventory.yml --list
# Run playbook against Satellite-managed hosts
ansible-playbook -i satellite_inventory.yml site.yml
See also: A Preview of Ansible Journey in 2024
Patch Management
Automated Patching Workflow
---
- name: Satellite-driven patch management
hosts: all
become: true
serial: "25%"
vars:
satellite_url: "https://satellite.example.com"
satellite_username: "admin"
satellite_password: "{{ vault_satellite_password }}"
pre_patch_snapshot: true
reboot_after_patch: true
patch_timeout: 3600
tasks:
- name: Pre-patch health check
block:
- name: Check disk space
ansible.builtin.command:
cmd: df -h / --output=pcent
register: disk_check
changed_when: false
- name: Fail if disk full
ansible.builtin.fail:
msg: "Insufficient disk space: {{ disk_check.stdout_lines[-1] | trim }}"
when: (disk_check.stdout_lines[-1] | trim | replace('%','') | int) > 90
- name: Create pre-patch snapshot (if VMware)
community.vmware.vmware_guest_snapshot:
hostname: "{{ vcenter_hostname }}"
username: "{{ vcenter_username }}"
password: "{{ vcenter_password }}"
name: "{{ inventory_hostname }}"
snapshot_name: "pre-patch-{{ ansible_date_time.date }}"
description: "Snapshot before patching"
state: present
delegate_to: localhost
when: pre_patch_snapshot | bool
- name: Apply security errata
ansible.builtin.dnf:
name: "*"
security: true
state: latest
update_cache: true
register: patch_result
timeout: "{{ patch_timeout }}"
- name: Display patched packages
ansible.builtin.debug:
msg: "Updated {{ patch_result.results | length }} packages"
when: patch_result.changed
- name: Check if reboot required
ansible.builtin.command:
cmd: needs-restarting -r
register: reboot_check
changed_when: false
failed_when: false
- name: Reboot if required
ansible.builtin.reboot:
reboot_timeout: 600
msg: "Rebooting for kernel/system updates"
when:
- reboot_after_patch | bool
- reboot_check.rc != 0
- name: Post-patch verification
block:
- name: Verify critical services
ansible.builtin.systemd:
name: "{{ item }}"
state: started
loop: "{{ critical_services | default(['sshd', 'chronyd']) }}"
- name: Report patch status to Satellite
ansible.builtin.uri:
url: "{{ satellite_url }}/api/v2/hosts/{{ ansible_fqdn }}/host_parameters"
method: POST
user: "{{ satellite_username }}"
password: "{{ satellite_password }}"
validate_certs: true
body_format: json
body:
parameter:
name: "last_patched"
value: "{{ ansible_date_time.iso8601 }}"
status_code: [200, 201, 422]
delegate_to: localhost
Errata Management
---
- name: Check and apply specific errata
hosts: all
become: true
tasks:
- name: List applicable errata
ansible.builtin.command:
cmd: dnf updateinfo list --security
register: errata_list
changed_when: false
- name: Parse critical errata
ansible.builtin.set_fact:
critical_errata: "{{ errata_list.stdout_lines | select('search', 'Critical') | list }}"
important_errata: "{{ errata_list.stdout_lines | select('search', 'Important') | list }}"
- name: Report errata counts
ansible.builtin.debug:
msg: |
Critical: {{ critical_errata | length }}
Important: {{ important_errata | length }}
Total security: {{ errata_list.stdout_lines | length }}
- name: Apply critical errata only
ansible.builtin.dnf:
name: "*"
security: true
bugfix: false
state: latest
when: critical_errata | length > 0
Ansible Roles in Satellite
Assign Ansible Roles to Host Groups
---
- name: Configure Ansible roles in Satellite
hosts: localhost
module_defaults:
group/redhat.satellite.foreman:
server_url: "{{ satellite_url }}"
username: "{{ satellite_username }}"
password: "{{ satellite_password }}"
validate_certs: "{{ satellite_validate_certs }}"
tasks:
- name: Import Ansible roles into Satellite
redhat.satellite.ansible_roles:
organization: "{{ satellite_organization }}"
state: present
register: roles_import
- name: Assign roles to host group
ansible.builtin.uri:
url: "{{ satellite_url }}/api/v2/hostgroups/{{ hostgroup_id }}/assign_ansible_roles"
method: POST
user: "{{ satellite_username }}"
password: "{{ satellite_password }}"
validate_certs: true
body_format: json
body:
ansible_role_ids: "{{ role_ids }}"
status_code: [200, 204]
Satellite API Automation
Bulk Operations via API
---
- name: Satellite API bulk operations
hosts: localhost
vars:
satellite_api: "{{ satellite_url }}/api/v2"
tasks:
- name: Get all hosts needing patches
ansible.builtin.uri:
url: "{{ satellite_api }}/hosts?search=applicable_errata%3E0&per_page=100"
user: "{{ satellite_username }}"
password: "{{ satellite_password }}"
validate_certs: true
register: hosts_needing_patches
- name: Display hosts needing patches
ansible.builtin.debug:
msg: "{{ item.name }}: {{ item.content_facet_attributes.errata_counts.total }} errata"
loop: "{{ hosts_needing_patches.json.results }}"
loop_control:
label: "{{ item.name }}"
- name: Get content view versions
ansible.builtin.uri:
url: "{{ satellite_api }}/content_views?organization_id=1"
user: "{{ satellite_username }}"
password: "{{ satellite_password }}"
validate_certs: true
register: content_views
- name: Generate patch compliance report
ansible.builtin.template:
src: patch-report.html.j2
dest: "/tmp/patch-report-{{ ansible_date_time.date }}.html"
vars:
hosts: "{{ hosts_needing_patches.json.results }}"
report_date: "{{ ansible_date_time.iso8601 }}"
Foreman-Specific Integration
For environments using Foreman (upstream) instead of Satellite:
---
- name: Foreman provisioning
hosts: localhost
collections:
- theforeman.foreman
module_defaults:
group/theforeman.foreman.foreman:
server_url: "https://foreman.example.com"
username: "admin"
password: "{{ vault_foreman_password }}"
validate_certs: true
tasks:
- name: Create compute profile
theforeman.foreman.compute_profile:
name: "Standard Web Server"
compute_attributes:
- compute_resource: "Libvirt"
vm_attrs:
cpus: 2
memory: 4294967296
volumes_attributes:
0:
pool_name: default
capacity: 50G
format_type: qcow2
state: present
- name: Create Foreman host
theforeman.foreman.host:
name: "web-01.example.com"
hostgroup: "WebServers"
build: true
state: present
Best Practices
1. Use Content Views for Consistency
Always pin content to specific versions via Content Views rather than syncing directly to latest. This ensures all environments get identical packages.
2. Lifecycle Promotion Pipeline
Library → Development → Testing → Staging → Production
Never skip environments. Use Ansible to automate the promotion after test validation.
3. Satellite Callback Plugin
Enable the Satellite callback plugin in ansible.cfg to report playbook results back to Satellite:
[defaults]
callback_whitelist = redhat.satellite.foreman
[callback_foreman]
url = https://satellite.example.com
ssl_cert = /etc/foreman-proxy/ssl_cert.pem
ssl_key = /etc/foreman-proxy/ssl_key.pem
verify_certs = /etc/foreman-proxy/ssl_ca.pem
4. Separate Credentials
# Use Ansible Vault for Satellite credentials
ansible-vault create group_vars/all/vault.yml
# Or use environment variables
satellite_password: "{{ lookup('env', 'SATELLITE_PASSWORD') }}"
Frequently Asked Questions
What is the difference between Red Hat Satellite and Foreman?
Red Hat Satellite is the commercial, supported product built on top of Foreman (provisioning), Katello (content management), and Candlepin (subscription management). Foreman is the open-source upstream project. Both integrate with Ansible using similar collections.
How do I use Satellite as a dynamic inventory for Ansible?
Install the redhat.satellite collection, create a satellite_inventory.yml file with the redhat.satellite.foreman plugin configuration, and pass it as your inventory source: ansible-playbook -i satellite_inventory.yml playbook.yml.
Can I run Ansible playbooks directly from Satellite?
Yes. Satellite 6.x includes Remote Execution (REX) which can run Ansible roles and playbooks on managed hosts. You can assign Ansible roles to host groups and trigger runs from the Satellite UI or API.
How does Satellite patching work with Ansible?
Satellite manages content (repositories, errata, content views) while Ansible executes the actual patching. Ansible reads applicable errata from Satellite's API, applies patches via dnf/yum modules, and reports results back via the callback plugin.
What collections do I need for Satellite automation?
Use redhat.satellite for Red Hat Satellite or theforeman.foreman for Foreman. Both provide modules for managing hosts, content views, lifecycle environments, repositories, and host groups.
Related Articles
• Ansible Automation Platform RBAC • Ansible Patch Management • Ansible Compliance Automation CIS STIG PCI DSS • Ansible VMware vSphere AutomationConclusion
Integrating Ansible with Red Hat Satellite or Foreman creates a comprehensive server lifecycle management platform. Satellite handles content management, host provisioning, and subscription tracking while Ansible provides flexible, code-driven configuration automation. Together, they give enterprises a complete solution for managing thousands of servers with consistent patching, provisioning, and compliance — all automated and auditable.
Category: installation