first commit

This commit is contained in:
Ryan Cuda
2026-01-25 08:31:56 -07:00
commit 1234ef51c0
57 changed files with 2560 additions and 0 deletions

1113
README.md Normal file

File diff suppressed because it is too large Load Diff

BIN
ansible-posix-2.1.0.tar.gz Normal file

Binary file not shown.

BIN
apache.tar.gz Normal file

Binary file not shown.

Binary file not shown.

575
create-lab.yml Normal file
View File

@@ -0,0 +1,575 @@
---
###############################################################################
# Combined Ansible Playbook
###############################################################################
###############################################################################
# Play 1: Deploy multiple KubeVirt VMs from Block PVC template with cloud-init
###############################################################################
- name: Deploy multiple VMs from Block template PVC with cloud-init ISO
hosts: localhost
gather_facts: false
collections:
- kubernetes.core
vars:
namespace: default
vm_domain: lab.example.com
rootdisk_size: 64Gi
disk2_size: 2Gi
disk3_size: 1Gi
vm_list:
- name: controller
ip: 10.4.0.100
source_pvc: rhce-template
- name: node1
ip: 10.4.0.101
source_pvc: rhce-template
- name: node2
ip: 10.4.0.102
source_pvc: rhce-template
- name: node3
ip: 10.4.0.103
source_pvc: rhce-template
- name: node4
ip: 10.4.0.104
source_pvc: rhce-template
- name: node5
ip: 10.4.0.105
source_pvc: rhce-template
- name: utility
ip: 10.4.0.106
source_pvc: utility-template
tasks:
###########################################################################
# Create PVCs
###########################################################################
- name: Create rootdisk PVC from template
k8s:
state: present
definition:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: "{{ item.name }}-rootdisk"
namespace: "{{ namespace }}"
spec:
storageClassName: ocs-storagecluster-ceph-rbd-virtualization
accessModes:
- ReadWriteMany
volumeMode: Block
resources:
requests:
storage: "{{ rootdisk_size }}"
dataSource:
name: "{{ item.source_pvc }}"
kind: PersistentVolumeClaim
apiGroup: ""
loop: "{{ vm_list }}"
loop_control:
label: "{{ item.name }}"
- name: Create disk2 PVC (2Gi)
k8s:
state: present
definition:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: "{{ item.name }}-disk2"
namespace: "{{ namespace }}"
spec:
storageClassName: ocs-storagecluster-ceph-rbd-virtualization
accessModes:
- ReadWriteMany
volumeMode: Block
resources:
requests:
storage: "{{ disk2_size }}"
loop: "{{ vm_list }}"
loop_control:
label: "{{ item.name }}-disk2"
- name: Create disk3 PVC (1Gi)
k8s:
state: present
definition:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: "{{ item.name }}-disk3"
namespace: "{{ namespace }}"
spec:
storageClassName: ocs-storagecluster-ceph-rbd-virtualization
accessModes:
- ReadWriteMany
volumeMode: Block
resources:
requests:
storage: "{{ disk3_size }}"
loop: "{{ vm_list }}"
loop_control:
label: "{{ item.name }}-disk3"
###########################################################################
# Wait for PVCs
###########################################################################
- name: Wait for all PVCs to be bound
k8s_info:
api_version: v1
kind: PersistentVolumeClaim
name: "{{ item.0.name }}-{{ item.1 }}"
namespace: "{{ namespace }}"
register: pvc_status
until: pvc_status.resources[0].status.phase == "Bound"
retries: 30
delay: 5
loop: "{{ vm_list | product(['rootdisk', 'disk2', 'disk3']) | list }}"
loop_control:
label: "{{ item.0.name }}-{{ item.1 }}"
###########################################################################
# Create VirtualMachines
###########################################################################
- name: Create VirtualMachine with additional raw disks
k8s:
state: present
definition:
apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
name: "{{ item.name }}"
namespace: "{{ namespace }}"
spec:
running: true
template:
metadata:
labels:
kubevirt.io/domain: "{{ item.name }}"
spec:
domain:
cpu:
cores: 1
resources:
requests:
memory: 2Gi
devices:
disks:
- name: rootdisk
disk:
bus: virtio
- name: disk2
disk:
bus: virtio
- name: disk3
disk:
bus: virtio
- name: cloudinitdisk
disk:
bus: virtio
interfaces:
- name: default
bridge: {}
networks:
- name: default
multus:
networkName: rhce
volumes:
- name: rootdisk
persistentVolumeClaim:
claimName: "{{ item.name }}-rootdisk"
- name: disk2
persistentVolumeClaim:
claimName: "{{ item.name }}-disk2"
- name: disk3
persistentVolumeClaim:
claimName: "{{ item.name }}-disk3"
- name: cloudinitdisk
cloudInitNoCloud:
hostname: "{{ item.name }}"
fqdn: "{{ item.name }}.{{ vm_domain }}"
manage_etc_hosts: true
networkData: |
version: 2
ethernets:
enp1s0:
dhcp4: false
addresses:
- "{{ item.ip }}/24"
gateway4: 10.4.0.1
nameservers:
search:
- "{{ vm_domain }}"
addresses:
- 10.1.0.1
userData: |
#cloud-config
users:
- name: redhat
sudo: ALL=(ALL) NOPASSWD:ALL
lock_passwd: false
chpasswd:
list: |
redhat:redhat
expire: false
ssh_pwauth: true
user: redhat
password: redhat
loop: "{{ vm_list }}"
loop_control:
label: "{{ item.name }}"
###############################################################################
# Play 2: Add static DNS entries to dnsmasq on OPNsense
###############################################################################
- name: Add static DNS entries to dnsmasq on OPNsense
hosts: opnsense.lab.cudanet.org
become: true
remote_user: root
gather_facts: false
vars:
ansible_python_interpreter: /usr/local/bin/python3
dnsmasq_hosts_file: /usr/local/etc/dnsmasq.conf.d/lab.conf
vms:
- ip: "10.4.0.100"
hostname: "controller.lab.example.com"
- ip: "10.4.0.101"
hostname: "node1.lab.example.com"
- ip: "10.4.0.102"
hostname: "node2.lab.example.com"
- ip: "10.4.0.103"
hostname: "node3.lab.example.com"
- ip: "10.4.0.104"
hostname: "node4.lab.example.com"
- ip: "10.4.0.105"
hostname: "node5.lab.example.com"
- ip: "10.4.0.106"
hostname: "utility.lab.example.com"
tasks:
- name: Ensure dnsmasq hosts file exists
file:
path: "{{ dnsmasq_hosts_file }}"
state: touch
owner: root
group: wheel
mode: "0644"
- name: Add static DNS entries to dnsmasq hosts file
lineinfile:
path: "{{ dnsmasq_hosts_file }}"
line: "address=/{{ item.hostname }}/{{ item.ip }}"
state: present
create: yes
backup: yes
loop: "{{ vms }}"
- name: Reload dnsmasq service
ansible.builtin.shell: pluginctl dns
- name: Ping each host from OPNsense to verify connectivity
ansible.builtin.shell: ping -c 3 {{ item.ip }}
register: ping_result
ignore_errors: yes
loop: "{{ vms }}"
- name: Show ping results
debug:
msg: |
Ping to {{ item.item.hostname }} returned (rc={{ item.rc }}):
{{ item.stdout }}
loop: "{{ ping_result.results }}"
###############################################################################
# Play 3: Register system, configure services, and mirror EE to local registry
###############################################################################
- name: Register system, configure services, and mirror EE to local registry
hosts: utility
become: true
vars:
sat_user: "{{ vault_sat_user }}"
sat_passwd: "{{ vault_sat_passwd }}"
sat_orgid: "{{ vault_sat_orgid }}"
redhat_env: "{{ vault_redhat_env }}"
registry_host: utility.lab.example.com
registry_port: 5000
host_port: 5000
registry_image: docker.io/library/registry:2
podman_user: "{{ vault_podman_user }}"
podman_passwd: "{{ vault_podman_passwd }}"
ee_source_image: registry.redhat.io/ansible-automation-platform-25/ee-supported-rhel9:latest
ee_target_image: "{{ registry_host }}:{{ registry_port }}/ee-supported-rhel9:latest"
tasks:
- name: Register system with Red Hat Subscription Management
community.general.redhat_subscription:
username: "{{ sat_user }}"
password: "{{ sat_passwd }}"
org_id: "{{ sat_orgid }}"
environment: "{{ redhat_env }}"
state: present
- name: Install required packages
ansible.builtin.dnf:
name:
- httpd
- firewalld
- podman
- policycoreutils-python-utils
state: present
- name: Enable and start httpd
ansible.builtin.service:
name: httpd
state: started
enabled: true
- name: Enable and start firewalld
ansible.builtin.service:
name: firewalld
state: started
enabled: true
- name: Allow HTTP service through firewall
ansible.posix.firewalld:
service: http
permanent: true
state: enabled
immediate: true
- name: Allow registry port through firewall
ansible.posix.firewalld:
port: "{{ registry_port }}/tcp"
permanent: true
state: enabled
immediate: true
- name: Ensure correct permissions on web root
ansible.builtin.file:
path: /var/www/html
recurse: true
mode: "0755"
- name: Set SELinux context for Ansible Automation Platform content
community.general.sefcontext:
target: "/var/www/html/ansible-automation-platform(/.*)?"
setype: httpd_sys_content_t
state: present
- name: Set SELinux context for RHEL 9 content
community.general.sefcontext:
target: "/var/www/html/rhel9(/.*)?"
setype: httpd_sys_content_t
state: present
- name: Set SELinux context for files
community.general.sefcontext:
target: "/var/www/html/files(/.*)?"
setype: httpd_sys_content_t
state: present
- name: Restore SELinux contexts
ansible.builtin.command: restorecon -Rv /var/www/html
changed_when: false
- name: Create registry quadlet file
ansible.builtin.copy:
dest: /etc/containers/systemd/registry.container
mode: "0644"
content: |
[Unit]
Description=Registry
[Container]
ContainerName=registry
Image={{ registry_image }}
PublishPort={{ registry_port }}:{{ host_port }}
[Install]
WantedBy=multi-user.target
- name: Reload Systemd Daemons
ansible.builtin.systemd:
daemon_reload: yes
become: true
- name: Start registry.service
ansible.builtin.systemd:
name: registry.service
state: started
become: true
- name: Create containers config directory
ansible.builtin.file:
path: /root/.config/containers
state: directory
mode: "0700"
- name: Configure insecure registry
ansible.builtin.copy:
dest: /root/.config/containers/registries.conf
mode: "0600"
content: |
[[registry]]
location = "{{ registry_host }}:{{ registry_port }}"
insecure = true
- name: Login to Red Hat registry
containers.podman.podman_login:
username: "{{ podman_user }}"
password: "{{ podman_passwd }}"
registry: registry.redhat.io
- name: Pull Execution Environment image
containers.podman.podman_image:
name: "{{ ee_source_image }}"
state: present
- name: Tag EE image for local registry
ansible.builtin.command:
cmd: podman tag {{ ee_source_image }} {{ ee_target_image }}
changed_when: true
- name: Push EE image to local registry
ansible.builtin.command:
cmd: podman push --remove-signatures {{ ee_target_image }}
changed_when: true
- name: Install chrony package
ansible.builtin.package:
name: chrony
state: present
- name: Configure chrony as NTP server
ansible.builtin.lineinfile:
path: /etc/chrony.conf
regexp: '^allow'
line: 'allow 0.0.0.0/0'
state: present
- name: Ensure chrony service is enabled and started
ansible.builtin.service:
name: chronyd
state: started
enabled: true
- name: Open NTP service in firewall
ansible.builtin.firewalld:
service: ntp
permanent: true
state: enabled
immediate: true
when: ansible_facts.services['firewalld.service'] is defined
###############################################################################
# Play 4: Configure the Controller node
###############################################################################
- name: Configure the Controller node
hosts: controller
become: true
vars:
registry_host: utility.lab.example.com
registry_port: 5000
tasks:
- name: Create repo file
ansible.builtin.copy:
content: |
[ansible-automation-platform-2.5]
name=Ansible Automation Platform 2.5
metadata_expire=-1
gpgcheck=1
enabled=1
baseurl=http://utility.lab.example.com/ansible-automation-platform/2.5
gpgkey=http://utility.lab.example.com/rhel9/RPM-GPG-KEY-redhat-release
[BaseOS]
name=BaseOS Packages Red Hat Enterprise Linux 9
metadata_expire=-1
gpgcheck=1
enabled=1
baseurl=http://utility.lab.example.com/rhel9/BaseOS
gpgkey=http://utility.lab.example.com/rhel9/RPM-GPG-KEY-redhat-release
[AppStream]
name=AppStream Packages Red Hat Enterprise Linux 9
metadata_expire=-1
gpgcheck=1
enabled=1
baseurl=http://utility.lab.example.com/rhel9/AppStream/
gpgkey=http://utility.lab.example.com/rhel9/rpm-gpg/RPM-GPG-KEY-redhat-release
dest: /etc/yum.repos.d/rhce.repo
- name: Install required packages
ansible.builtin.dnf:
name:
- podman
- ansible-core
- ansible-navigator
state: present
- name: Create directories
ansible.builtin.file:
path: "{{ item }}"
state: directory
mode: "0700"
owner: ansible
group: ansible
loop:
- /home/ansible/.config
- /home/ansible/.config/containers
- /home/ansible/ansible
- /home/ansible/ansible/roles
- /home/ansible/ansible/mycollections
- name: Configure insecure registry
ansible.builtin.copy:
dest: /home/ansible/.config/containers/registries.conf
mode: "0600"
content: |
[[registry]]
location = "{{ registry_host }}:{{ registry_port }}"
insecure = true
owner: ansible
group: ansible
- name: Configure ansible.cfg
ansible.builtin.copy:
dest: /home/ansible/ansible/ansible.cfg
content: |
[defaults]
inventory = /home/ansible/ansible/inventory
remote_user = ansible
roles_path = /home/ansible/ansible/roles
collections_path = /home/ansible/ansible/mycollections
- name: Configure ansible-navigator.yml
ansible.builtin.copy:
dest: /home/ansible/ansible/ansible-navigator.yml
content: |
---
ansible-navigator:
execution-environment:
image: utility.lab.example.com:5000/ee-supported-rhel9:latest
pull:
policy: missing
playbook-artifact:
enable: false
- name: Create test.yml
ansible.builtin.copy:
dest: /home/ansible/ansible/test.yml
content: |
---
- name: A simple playbook to test that Ansible is configured
hosts: localhost
tasks:
- name: Run test playbook
ansible.builtin.debug:
msg: "If you're reading this, Ansible is configured on your system."

9
debug.yml Normal file
View File

@@ -0,0 +1,9 @@
- name: Print facts
hosts: jump01.lab.cudanet.org
gather_facts: true
remote_user: root
tasks:
- name: print facts
ansible.builtin.debug:
msg: "The default IPv4 address for {{ inventory_hostname }} is {{ ansible_default_ipv4.address }}"

69
destroy-lab.yml Normal file
View File

@@ -0,0 +1,69 @@
---
- name: Delete VMs and all associated disks (PVCs)
hosts: localhost
gather_facts: false
collections:
- kubernetes.core
vars:
namespace: default
vm_names:
- controller
- node1
- node2
- node3
- node4
- node5
- utility
disks:
- rootdisk
- disk2
- disk3
tasks:
###########################################################################
# Delete VMs
###########################################################################
- name: Delete VirtualMachines
k8s:
api_version: kubevirt.io/v1
kind: VirtualMachine
name: "{{ item }}"
namespace: "{{ namespace }}"
state: absent
loop: "{{ vm_names }}"
###########################################################################
# Wait until VMs are actually gone (IMPORTANT)
###########################################################################
- name: Wait for VMs to be fully deleted
k8s_info:
api_version: kubevirt.io/v1
kind: VirtualMachine
name: "{{ item }}"
namespace: "{{ namespace }}"
register: vm_check
until: vm_check.resources | length == 0
retries: 30
delay: 5
loop: "{{ vm_names }}"
loop_control:
label: "{{ item }}"
###########################################################################
# Delete PVCs (no pre-check, safe & idempotent)
###########################################################################
- name: Delete PVCs for all VM disks
k8s:
api_version: v1
kind: PersistentVolumeClaim
name: "{{ item.0 }}-{{ item.1 }}"
namespace: "{{ namespace }}"
state: absent
loop: "{{ vm_names | product(disks) | list }}"
loop_control:
label: "{{ item.0 }}-{{ item.1 }}"
failed_when: false

BIN
haproxy.tar.gz Normal file

Binary file not shown.

7
hwreport.empty Normal file
View File

@@ -0,0 +1,7 @@
HOST=
BIOS=
MEMORY=
VDB=
VDC=
VDE=

14
inventory Normal file
View File

@@ -0,0 +1,14 @@
[satellite]
satellite.lab.cudanet.org
[workstation]
jump01.lab.cudanet.org
[routers]
opnsense.lab.cudanet.org
[utility]
utility.lab.example.com
[controller]
controller.lab.example.com

2
myhosts.txt Normal file
View File

@@ -0,0 +1,2 @@
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6

BIN
phpinfo.tar.gz Normal file

Binary file not shown.

Binary file not shown.

9
salaries.yml Normal file
View File

@@ -0,0 +1,9 @@
$ANSIBLE_VAULT;1.1;AES256
32373538383562646335653030373731386462393436643032363863653637396334376537386337
3434323231616637643462373664313661363037343534660a373236663364363138376435646231
39666263666362623536393261303966393434316130336436613636393137656233616364396532
3036366663333263650a356561386439356364383131356533313834653164346262353934316238
64323934376437663435636666326465393336663535353335353864353663333064343031383264
30393361336533383533316538386539333437316165303964313665353466346164326638326132
33333830646262333662653739306662613662653032626166373730623062373936333239643532
61353739353830626137

View File

@@ -0,0 +1,8 @@
---
ansible-navigator:
execution-environment:
image: utility.lab.example.com:5000/ee-supported-rhel9:latest
pull:
policy: missing
playbook-artifact:
enable: false

21
solutions/ansible.cfg Normal file
View File

@@ -0,0 +1,21 @@
[defaults]
inventory = ./inventory
interpreter_python = /usr/bin/python3
remote_user = ansible
host_key_checking = false
roles_path = /root/ansible/roles
[galaxy]
server_list = automation_hub
[galaxy_server.automation_hub]
url = https://console.redhat.com/api/automation-hub/
auth_url = https://sso.redhat.com/auth/realms/redhat-external/protocol/openid-connect/token
token = "eyJhbGciOiJIUzUxMiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI0NzQzYTkzMC03YmJiLTRkZGQtOTgzMS00ODcxNGRlZDc0YjUifQ.eyJpYXQiOjE3NjYzMzM3MDgsImp0aSI6ImYzMGQ4ZDQ4LTJlYTAtNGI2OS1iZmFhLWViZTNhOGIxZDA0MyIsImlzcyI6Imh0dHBzOi8vc3NvLnJlZGhhdC5jb20vYXV0aC9yZWFsbXMvcmVkaGF0LWV4dGVybmFsIiwiYXVkIjoiaHR0cHM6Ly9zc28ucmVkaGF0LmNvbS9hdXRoL3JlYWxtcy9yZWRoYXQtZXh0ZXJuYWwiLCJzdWIiOiI1NDY5MjM3NiIsInR5cCI6Ik9mZmxpbmUiLCJhenAiOiJjbG91ZC1zZXJ2aWNlcyIsIm5vbmNlIjoiODI2YWZmNTktOTZmNC00ODcyLTg0MDUtNWYzODY0M2M3YzMwIiwic2lkIjoiMjdiZWY4NjMtOGFiZS00YWFlLTk4NGUtN2M5YzU3ODVmM2Y3Iiwic2NvcGUiOiJvcGVuaWQgYXBpLmNvbnNvbGUgYmFzaWMgcm9sZXMgd2ViLW9yaWdpbnMgY2xpZW50X3R5cGUucHJlX2tjMjUgYXBpLmFza19yZWRfaGF0IG9mZmxpbmVfYWNjZXNzIn0.75q6N-IJiOGSxWmMOraXYmeJxmPU4p6iSFJj99jqhOOpKvgDk9_gD-MnM8FU_AGvuifbYn8_zj2QgTSaLlo8hw"
[galaxy_server.galaxy_hub]
url=https://galaxy.ansible.com/
[privilege_escalation]
become = true
become_method = sudo

7
solutions/apache.yml Normal file
View File

@@ -0,0 +1,7 @@
# apache_role.yml
---
- name: Configure Apache web servers
hosts: dev
become: true
roles:
- apache

49
solutions/hwreport.yml Normal file
View File

@@ -0,0 +1,49 @@
---
- name: Generate hardware report
hosts: all
become: yes
tasks:
- name: Download empty hwreport file
get_url:
url: http://utility.lab.example.com/files/hwreport.empty
dest: /root/hwreport.txt
mode: '0644'
- name: Set hostname
lineinfile:
path: /root/hwreport.txt
regexp: '^HOST='
line: "HOST={{ ansible_hostname }}"
- name: Set BIOS version
lineinfile:
path: /root/hwreport.txt
regexp: '^BIOS='
line: "BIOS={{ ansible_bios_version | default('NONE') }}"
- name: Set memory size
lineinfile:
path: /root/hwreport.txt
regexp: '^MEMORY='
line: "MEMORY={{ ansible_memtotal_mb }} MB"
- name: Set vdb disk size
lineinfile:
path: /root/hwreport.txt
regexp: '^VDB='
line: "VDB={{ ansible_devices.vdb.size | default('NONE') }}"
- name: Set vdc disk size
lineinfile:
path: /root/hwreport.txt
regexp: '^VDC='
line: "VDC={{ ansible_devices.vdc.size | default('NONE') }}"
- name: Set vdd disk size (NONE if missing)
lineinfile:
path: /root/hwreport.txt
regexp: '^VDD='
line: >-
VDD={{ ansible_devices.vdd.size if 'vdd' in ansible_devices else 'NONE' }}

20
solutions/install.yml Normal file
View File

@@ -0,0 +1,20 @@
# playbook.yml
- name: Install Packages and Groups
hosts: all
become: true
tasks:
- name: Install packages on test group
ansible.builtin.dnf:
name:
- httpd
- php
state: latest
when: inventory_hostname in groups['test']
- name: Install RPM Development Tools group on dev group
ansible.builtin.dnf:
name: "@RPM Development Tools"
state: latest
when: inventory_hostname in groups['dev']

16
solutions/inventory Normal file
View File

@@ -0,0 +1,16 @@
[dev]
node1
[test]
node2
[prod]
node3
node4
[balancers]
node5
[webservers:children]
prod

28
solutions/issue.yml Normal file
View File

@@ -0,0 +1,28 @@
---
- name: Automatically populate /etc/issue with environment name
hosts:
- dev
- test
- prod
become: true
tasks:
- name: Determine environment name from inventory groups
ansible.builtin.set_fact:
env_name: >-
{% if 'prod' in group_names %}
Production
{% elif 'test' in group_names %}
Testing
{% elif 'dev' in group_names %}
Development
{% endif %}
- name: Populate /etc/issue
ansible.builtin.copy:
dest: /etc/issue
content: |
{{ env_name }}
owner: root
group: root
mode: '0644'

74
solutions/partition.yml Normal file
View File

@@ -0,0 +1,74 @@
---
- name: Configure disk partitions and mounts
hosts: all
become: true
gather_facts: true
tasks:
####################################################################
# /dev/vdb — always create 1500MB partition mounted at /devmount
####################################################################
- name: Create 1500MB partition on /dev/vdb
community.general.parted:
device: /dev/vdb
number: 1
state: present
part_end: 1500MiB
- name: Create XFS filesystem on /dev/vdb1
ansible.builtin.filesystem:
fstype: xfs
dev: /dev/vdb1
- name: Mount /dev/vdb1 at /devmount
ansible.builtin.mount:
path: /devmount
src: /dev/vdb1
fstype: xfs
state: mounted
####################################################################
# /dev/vdc — size-based logic (1500MB or 800MB)
####################################################################
- name: Determine size of /dev/vdc partition
ansible.builtin.set_fact:
vdc_part_size: >-
{{ '1500MiB'
if (ansible_facts.devices.vdc.sectors | int *
ansible_facts.devices.vdc.sectorsize | int) >= (1500 * 1024 * 1024)
else '800MiB' }}
when: "'vdc' in ansible_facts.devices"
- name: Create partition on /dev/vdc
community.general.parted:
device: /dev/vdc
number: 1
state: present
part_end: "{{ vdc_part_size }}"
when: "'vdc' in ansible_facts.devices"
- name: Create XFS filesystem on /dev/vdc1
ansible.builtin.filesystem:
fstype: xfs
dev: /dev/vdc1
when: "'vdc' in ansible_facts.devices"
- name: Mount /dev/vdc1
ansible.builtin.mount:
path: >-
{{ '/devmount1'
if vdc_part_size == '1500MiB'
else '/dev/mount' }}
src: /dev/vdc1
fstype: xfs
state: mounted
when: "'vdc' in ansible_facts.devices"
####################################################################
# /dev/vde presence check
####################################################################
- name: Warn if /dev/vde is not present
ansible.builtin.debug:
msg: "Disk /dev/vde is not present"
when: "'vde' not in ansible_facts.devices"

27
solutions/repos.yml Normal file
View File

@@ -0,0 +1,27 @@
---
# repos.yml
- name: Add BaseOS and AppStream repos to all hosts
hosts: all
become: true
vars:
repos:
- BaseOS
- AppStream
baseurl: http://utility.lab.example.com/rhel9
gpgkey_url: http://utility.lab.example.com/rhel9/RPM-GPG-KEY-redhat-release
repo_file: /etc/yum.repos.d/rhce
tasks:
- name: Add {{ item }} repository
ansible.builtin.yum_repository:
name: "EX294_{{ item }}"
description: "EX294 {{ item }} Repository"
baseurl: "{{ baseurl }}/{{ item }}"
enabled: true
gpgcheck: true
gpgkey: "{{ gpgkey_url }}"
file: "{{ repo_file }}"
loop: "{{ repos }}"

View File

@@ -0,0 +1,23 @@
# requirements.yml
---
roles:
- name: phpinfo
src: http://utility.lab.example.com/files/phpinfo.tar.gz
path: /home/ansible/ansible/roles
- name: balancer
src: http://utility.lab.example.com/files/haproxy.tar.gz
path: /home/ansible/ansible/roles
collections:
- name: ansible.posix
source: http://utility.lab.example.com/files/ansible-posix-2.1.0.tar.gz
type: url
- name: redhat.rhel_system_roles
source: http://utility.lab.example.com/files/redhat-rhel_system_roles-1.108.6.tar.gz
type: url
- name: community.general
source: http://utility.lab.example.com/files/community-general-12.1.0.tar.gz
type: url

15
solutions/rhce.repo Normal file
View File

@@ -0,0 +1,15 @@
[BaseOS]
name=BaseOS Packages Red Hat Enterprise Linux 9
metadata_expire=-1
gpgcheck=1
enabled=1
baseurl=http://utility.lab.example.com/rhel9/BaseOS
gpgkey=http://utility.lab.example.com/rhel9/RPM-GPG-KEY-redhat-release
[AppStream]
name=AppStream Packages Red Hat Enterprise Linux 9
metadata_expire=-1
gpgcheck=1
enabled=1
baseurl=http://utility.lab.example.com/rhel9/AppStream/
gpgkey=http://utility.lab.example.com/rhel9/rpm-gpg/RPM-GPG-KEY-redhat-release

13
solutions/roles.yml Normal file
View File

@@ -0,0 +1,13 @@
# roles.yml
---
- name: Configure load balancer
hosts: balancers
become: yes
roles:
- balancer
- name: Configure web servers
hosts: webservers
become: yes
roles:
- phpinfo

View File

@@ -0,0 +1,38 @@
Role Name
=========
A brief description of the role goes here.
Requirements
------------
Any pre-requisites that may not be covered by Ansible itself or the role should be mentioned here. For instance, if the role uses the EC2 module, it may be a good idea to mention in this section that the boto package is required.
Role Variables
--------------
A description of the settable variables for this role should go here, including any variables that are in defaults/main.yml, vars/main.yml, and any variables that can/should be set via parameters to the role. Any variables that are read from other roles and/or the global scope (ie. hostvars, group vars, etc.) should be mentioned here as well.
Dependencies
------------
A list of other roles hosted on Galaxy should go here, plus any details in regards to parameters that may need to be set for other roles, or variables that are used from other roles.
Example Playbook
----------------
Including an example of how to use your role (for instance, with variables passed in as parameters) is always nice for users too:
- hosts: servers
roles:
- { role: username.rolename, x: 42 }
License
-------
BSD
Author Information
------------------
An optional section for the role authors to include contact information, or a website (HTML is not allowed).

View File

@@ -0,0 +1,5 @@
# defaults/main.yml
---
apache_packages:
- httpd
- firewalld

View File

@@ -0,0 +1,6 @@
# handlers/main.yml
---
- name: restart httpd
ansible.builtin.service:
name: httpd
state: restarted

View File

@@ -0,0 +1,52 @@
galaxy_info:
author: your name
description: your role description
company: your company (optional)
# If the issue tracker for your role is not on github, uncomment the
# next line and provide a value
# issue_tracker_url: http://example.com/issue/tracker
# Choose a valid license ID from https://spdx.org - some suggested licenses:
# - BSD-3-Clause (default)
# - MIT
# - GPL-2.0-or-later
# - GPL-3.0-only
# - Apache-2.0
# - CC-BY-4.0
license: license (GPL-2.0-or-later, MIT, etc)
min_ansible_version: 2.1
# If this a Container Enabled role, provide the minimum Ansible Container version.
# min_ansible_container_version:
#
# Provide a list of supported platforms, and for each platform a list of versions.
# If you don't wish to enumerate all versions for a particular platform, use 'all'.
# To view available platforms and versions (or releases), visit:
# https://galaxy.ansible.com/api/v1/platforms/
#
# platforms:
# - name: Fedora
# versions:
# - all
# - 25
# - name: SomePlatform
# versions:
# - all
# - 1.0
# - 7
# - 99.99
galaxy_tags: []
# List tags for your role here, one per line. A tag is a keyword that describes
# and categorizes the role. Users find roles by searching for tags. Be sure to
# remove the '[]' above, if you add tags to this list.
#
# NOTE: A tag is limited to a single word comprised of alphanumeric characters.
# Maximum 20 tags per role.
dependencies: []
# List your role dependencies here, one per line. Be sure to remove the '[]' above,
# if you add dependencies to this list.

View File

@@ -0,0 +1,34 @@
# tasks/main.yml
---
- name: Install httpd and firewalld
ansible.builtin.package:
name: "{{ apache_packages }}"
state: present
- name: Enable and start firewalld
ansible.builtin.service:
name: firewalld
state: started
enabled: true
- name: Enable and start httpd
ansible.builtin.service:
name: httpd
state: started
enabled: true
- name: Allow HTTP service through firewalld
ansible.posix.firewalld:
service: http
permanent: true
state: enabled
immediate: true
- name: Deploy index.html with FQDN and IPv4
ansible.builtin.template:
src: index.html.j2
dest: /var/www/html/index.html
owner: root
group: root
mode: '0644'
notify: restart httpd

View File

@@ -0,0 +1,12 @@
# templates/index.html.j2
<!DOCTYPE html>
<html>
<head>
<title>Apache Test Page</title>
</head>
<body>
<h1>Apache is working</h1>
<p><strong>FQDN:</strong> {{ ansible_facts.fqdn }}</p>
<p><strong>IPv4 Address:</strong> {{ ansible_facts.default_ipv4.address }}</p>
</body>
</html>

View File

@@ -0,0 +1,2 @@
localhost

View File

@@ -0,0 +1,5 @@
---
- hosts: localhost
remote_user: root
roles:
- apache

View File

@@ -0,0 +1,2 @@
---
# vars file for apache

View File

@@ -0,0 +1,19 @@
{
"collection_info": null,
"dependencies": [],
"format": 1,
"license": "MIT",
"license_file": null,
"name": "balancer",
"namespace": "local",
"readme": "README.md",
"repository": null,
"tags": [
"haproxy",
"loadbalancer",
"networking",
"web"
],
"version": "1.0.0"
}

View File

@@ -0,0 +1,11 @@
---
haproxy_frontend_port: 80
haproxy_backend_servers:
- name: node3
address: node3
port: 80
- name: node4
address: node4
port: 80

View File

@@ -0,0 +1,6 @@
---
- name: Restart haproxy
ansible.builtin.service:
name: haproxy
state: restarted

View File

@@ -0,0 +1,2 @@
install_date: 'Tue 30 Dec 2025 12:53:11 AM '
version: ''

View File

@@ -0,0 +1,21 @@
---
galaxy_info:
role_name: balancer
author: lab
description: Installs and configures HAProxy to load balance Apache servers
license: MIT
min_ansible_version: "2.9"
platforms:
- name: EL
versions:
- "9"
galaxy_tags:
- haproxy
- loadbalancer
- networking
- web
dependencies: []

View File

@@ -0,0 +1,28 @@
---
- name: Install HAProxy
ansible.builtin.dnf:
name: haproxy
state: present
- name: Enable and start HAProxy
ansible.builtin.service:
name: haproxy
state: started
enabled: true
- name: Deploy HAProxy configuration
ansible.builtin.template:
src: haproxy.cfg.j2
dest: /etc/haproxy/haproxy.cfg
owner: root
group: root
mode: '0644'
notify: Restart haproxy
- name: Allow port 80 through firewalld
ansible.posix.firewalld:
service: http
permanent: true
immediate: true
state: enabled

View File

@@ -0,0 +1,25 @@
global
log /dev/log local0
log /dev/log local1 notice
daemon
maxconn 2048
defaults
log global
mode http
option httplog
option dontlognull
timeout connect 5s
timeout client 50s
timeout server 50s
frontend http_front
bind *:80
default_backend webservers
backend webservers
balance roundrobin
option httpchk
server node3 node3:80 check
server node4 node4:80 check

View File

@@ -0,0 +1,17 @@
# phpinfo Role
This role installs Apache, PHP, and Firewalld, opens HTTP access, fixes SELinux
contexts, and deploys a PHP info page.
## Requirements
- RHEL / Rocky / Alma 9
- SELinux enforcing
- firewalld enabled
## Usage
```yaml
- hosts: web
become: true
roles:
- phpinfo

View File

@@ -0,0 +1,4 @@
---
phpinfo_webroot: /var/www/html
phpinfo_file: index.php

View File

@@ -0,0 +1,6 @@
---
- name: Restart httpd
ansible.builtin.service:
name: httpd
state: restarted

View File

@@ -0,0 +1,2 @@
install_date: 'Tue 30 Dec 2025 12:53:11 AM '
version: ''

View File

@@ -0,0 +1,22 @@
---
galaxy_info:
role_name: phpinfo
author: lab
description: Installs Apache, PHP, configures firewalld, fixes SELinux, and deploys a PHP info page
license: MIT
min_ansible_version: "2.9"
platforms:
- name: EL
versions:
- "9"
galaxy_tags:
- php
- httpd
- firewalld
- selinux
- web
dependencies: []

View File

@@ -0,0 +1,41 @@
---
- name: Install required packages
ansible.builtin.dnf:
name:
- httpd
- firewalld
- php
state: present
- name: Enable and start httpd
ansible.builtin.service:
name: httpd
state: started
enabled: true
- name: Enable and start firewalld
ansible.builtin.service:
name: firewalld
state: started
enabled: true
- name: Allow HTTP traffic through firewalld (persistent and immediate)
ansible.posix.firewalld:
service: http
state: enabled
permanent: true
immediate: true
- name: Fix SELinux context on webroot
ansible.builtin.command: restorecon -Rv {{ phpinfo_webroot }}
changed_when: false
- name: Deploy PHP info page
ansible.builtin.template:
src: index.php.j2
dest: "{{ phpinfo_webroot }}/{{ phpinfo_file }}"
owner: root
group: root
mode: '0644'
notify: Restart httpd

View File

@@ -0,0 +1,4 @@
<?php
phpinfo();
?>

View File

@@ -0,0 +1,2 @@
localhost

View File

@@ -0,0 +1,5 @@
---
- hosts: localhost
remote_user: root
roles:
- phpinfo

View File

@@ -0,0 +1,2 @@
---
# vars file for phpinfo

17
solutions/selinux.yml Normal file
View File

@@ -0,0 +1,17 @@
---
- name: Ensure SELinux is enabled and enforcing
hosts: all
become: true
tasks:
- name: Set SELinux to enforcing
ansible.posix.selinux:
policy: targeted
state: enforcing
notify: Reboot if SELinux state changed
handlers:
- name: Reboot if SELinux state changed
ansible.builtin.reboot:
msg: "Rebooting to apply SELinux changes"
reboot_timeout: 600

7
test.yml Normal file
View File

@@ -0,0 +1,7 @@
---
- name: A simple playbook to test that Ansible is configured
hosts: localhost
tasks:
- name: Run test playbook
ansible.builtin.debug:
msg: "If you're reading this, Ansible is configured on your system."

12
user_list.yml Normal file
View File

@@ -0,0 +1,12 @@
users:
- name: Fred
role: manager
- name: Wilma
role: manager
- name: Barney
role: developer
- name: Betty
role: developer

1
vault-password Normal file
View File

@@ -0,0 +1 @@
redhat

51
vault.yml Normal file
View File

@@ -0,0 +1,51 @@
$ANSIBLE_VAULT;1.1;AES256
62613361613532643339306531396533643738343365376535346463663731306536373139306163
6138386139623031656265326165386634323435353261610a303939356634386436366132306663
65616566386162616435626464373063393463666435623832303533333662303966386636613737
3365656432323739620a313463343436326463323632333337373437386237353862353564343438
63346634393830386233343536396563306138626264306330623765393530373763323764363534
38323666633631646666663966303365646266373830636664373366613330346662363134623235
65356537306462643735363033306537613462313236623062623633356132343161373032613264
34323865373564336532663361336239363939333239346539326434316435366362373734303337
32343835373063376530646161333365626138366530313430623265306466353430366537353937
39313032616265616633383061363432366236353434663639343463366435633839363733366134
31356231653162653362303535643666633839633031633763346336376465383030303961393662
38353261313935626565303961663065323030303265626562393830323430353331663361333232
61373664313930363033303334653238666237373562343664366463646338316434346131663230
32303364646432313039383833656436633637633631376335333962633966326332353061393761
65396436636266336662303234663735333838303961373365656132363162353332613133386633
33303633323030383236383630616335663430336363613234643938623962316433646637613032
63323935613766623031303536383663623065353261663132303339626130393635393934363539
63653733353635663664323334346138336231396333366233323766383931313236373365373432
37393934666661373263373538366663636430623131363664333063373338633639623264323334
38623330663531383639323464623933306431373764386461326136323964623465663630663335
38623361633239356637616637393438393139636431373266383330343039666663666366393036
34373239313638623861393663366535613133323036366634336131316537333734636538396138
63393963346536623831373765626232356130323361383936636661323966653764376439653032
64326439643036653534383236623735353336316666623037366338336466323662353065613835
32663566323538346463323033306430643834333330393966653437356564653065613637323836
61666262306265313362383130346636333136396635636234653830333038616566313736633838
65353062613538613761626164346534656161356435313562353130626662366530363037626461
61656461326239633739393564343665346364303437323532343533346537316333333561636331
34613866646562373833313161636431393532313465366562383037396435333366613264343662
35356431313232646162663665316563653461623266613631646435383033343632386166636233
35323934383466323734633230653164373231383861346336316566663231376133653763346130
61373135373738653831363963383831646663383532363038333636303934666333623061373337
31363137366136313937376437663963373963646331393564393661363565346234646230383239
65323564613334623238346638636132346437306637666230646438623236373035313263306163
61616335653164313466386435376437386561333531343632366463313931323366353261613738
62646236333932356433633864313138343764333065396537353438623336616235643536333938
66326137313033333334626437613132323336613335636439333964626631306337383862363730
64396466303064343863336639323335313165303339386639306431633135636665666433623433
36623239623466343163316563386435313136333662643564656566303036386430656637646633
33366139323630653936336463333031613433373139663631306638656463626431383535626237
31386264396461373865353033333237643365653933663532613465613564326333613335383130
32643965393736363236366432353733373764643731373536336633343139366365666366373437
63313664316562623566306663306132376461376433326361626135653933366232303430333933
63373537633764636463326538663939396431353732376239373166316236363431393230353631
65613739366265353637633031646532643934353839393334383332353865343330363537323462
64393662646335366133646239376166633833393861343561353165616132363330626537336461
33353761663134643934393866393235613532316139356335633233326537333163633931356162
65653264333164303438646130636435373261396333366531393432366231633661303534643961
39353939323937643934373332316461353361323863616164373038633261343034636632313362
3931653566633839386332373363663365333765306639353961