Installing Katello (the upstream project of Red Hat Network Satellite Server 6) and provisioning Foreman proxies (that are the upstream software of the Red Hat Network Satellite Server 6 Capsules), same way as installing software in general, is a typical time consuming and error prone task that is often convenient to automate in some way.

Ansible can be easily exploited to automate Katello installation having it to:

  • ensure that the target system meets the minimal requirements
  • automatically partition the system in the most convenient way
  • install everything taking in account of using the right versions of the involved packages so as to avoid installation failure because of wrong dependencies
  • set up all the configurations that are required to improve the usability of the installed environment
  • take care of issuing all the necessary statements to configure a Foreman proxy (a Capsule) on Katello (the Satellite) and automatically provision it

This post, based on CentOS 7, shows how to structure an Ansible project developing playbooks that either install Katello or install and configure Foreman proxies (Capsules, in  Red Hat Network Satellite Server 6 terms). In addition to that, the playbooks can also be used to reconfigure the provisioned Katello or Foreman proxies, for example to enable plugins. In this post we also see how to install the "theforeman.foreman" Ansible galaxy collection along with its dependencies, that enables the management of Katello using Ansible.

Prerequisites

Katello is a huge step onward from Spacewalk: everything has been completely redesigned to improve software lifecycle management. It is a massive suite that, as you can easily argue, demands a high amount of CPU and RAM.

The very minimum requirements are:

  • 4 cores
  • 20 GB of RAM
  • enough disk space to store the packages that are provided by its repositories

As you can easily figure out, running Katello does require a lot of GB of disk space not because of the Katello software itself, but for the amount of data required by the packages and images it downloads when providing local software repositories. It is straightforward that the best approach is using LVM to manage the disk space, since this system will keep on demanding more and more disk space as time passes by.

In this Lab we use the "system" Volume Group, that is the one used by the base platform, but you may of course prefer using a dedicated Volume Group; about the sizing, in my opinion a LVM Volume Group with at least 100GiB is a good starting point.

If your Volume Group is not big enough, you can attach additional disks, mark them as Physical Volumes and extend the Volume Group over them. For example, to add the "/dev/sdb" disk to the "system" Volume Group issue:

sudo pvcreate /dev/sdb
sudo vgextend system /dev/sdb
In virtual environments, when dealing with disks that are orchestrated by stuff such as storage-vmotion, my advice is to avoid having disks of more than 100GiB in size since they may slow down things when they get migrated.

Install And Configure Ansible On The Katello Host

The community version of Ansible is available in the EPEL repository, so we need to enable it:

sudo yum install -y epel-release

we are ready to install Ansible as follows:

sudo yum install -y ansible

it is better to have a dedicated operating system user to run the Ansible playbooks and commands. In this post this is the "ansible" user - create it as follows:

sudo useradd -r -m -c "Ansible Automation" ansible

most of the tasks performed by Ansible do require administrative rights: let's create the sudo rule that grants the "ansible" user the right to run any kind of command as any user without being asked for a password:

sudo bash -c "cat > /etc/sudoers.d/ansible" <<'EOF'
ansible ALL=(ALL) NOPASSWD: ALL
EOF

Setup The Katello Ansible Project

We are ready to implement the Ansible project that manages the automated installation of Katello.

Its directory layout, as by the Ansible best practices, is as follows:

inventory

a directory tree containing the list of available target hosts along with their properties: the hosts and host-groups are listed in the "hosts" file (in this post we are using the INI syntax), whereas their properties are declared into a dedicated file, with the host name itself as the filename, stored beneath the "host_vars" sub-directory

tasks

a directory where to store the task files that are imported by the main playbook - the rationale is grouping tasks so to keep the project tidy

templates

a directory tree where to store the JINJA template files used by Ansible to generate configuration files from

files

a directory tree where resource files are stored. In this project we store settings files for the different versions of Katello beneath the "versions" directory, whereas we put the patches that apply to specific versions of Katello into the "patches" directory. The "sudo" and the "smartproxies" directories are instead needed when dealing with Foreman proxies (Capsules): "sudo" is used to store the file with the sudo settings for the "ansible" that must be granted on the Foreman proxies hosts so to be able to get administrative rights when installing and configuring them; "smartproxies" is used to store the file with the certificate bundle used when installing the Foreman proxy and a text file with the command that is used by the Ansible automation to perform the configuration.

collections

a directory where to store the "requirements.yml" file with the list of the collections that are necessary to install since they are required to run the playbooks. Doing this way our project is compatible with Red Hat Ansible Tower, enabling you to configure the job to automatically download the collections listed in this file before running the playbooks.

just switch to the user "ansible":

sudo su - ansible

then create the whole directory tree of the project as follows:

mkdir -m 755 katello \
katello/inventory \
katello/inventory/host_vars \
katello/collections \
katello/files \
katello/files/smartproxies \
katello/files/sudo \
katello/files/versions \
katello/files/patches \
katello/files/patches/3.12 \
katello/templates \
katello/tasks

then change directory into it:

cd katello

Configuring The Inventory

Ansible fetches its target hosts from the so called "inventory": since we are going to configure our custom inventory into the "inventory" directory, we  must configure Ansible to use the "inventory" directory as the default inventory lookup path.

Create the ansible.cfg file with the following contents:

[defaults]
inventory      = inventory

Ansible target hosts and groups of hosts are listed in the "hosts" text file of the "inventory" directory. In this project we are using the INI syntax, so add the hostnames of the systems you want to install Katello and provision the Foreman proxies onto into the inventory/hosts file as follows:

sat-ci-up3a002 ansible_connection=local

since in this post the "sat-ci-up3a002" host is both the Ansible management station and the host we install Katello onto, we set the "ansible_connection" attribute to "local" to avoid the unnecessary use of SSH connections to the local system itself.

Create the inventory/host_vars/sat-ci-up3a002.yml file that contains the settings that are specific to the "sat-ci-up3a002" host:

katello_version: "3.12"
katello_scenario: "katello"
katello_admin_user: admin
katello_cli_admin_user: katello
server_domain: mgmt.carcano.local
provisioning_interface: eth0
provisining_network: "10.1.0"
katello_installer_extra_options:
  - "--foreman-proxy-dns true"
  - "--foreman-proxy-dhcp true"
  - "--foreman-proxy-tftp true"
  - "--foreman-proxy-dns-interface {{ provisioning_interface }}"
  - "--foreman-proxy-dhcp-interface {{ provisioning_interface }}"
  - "--foreman-proxy-dns-zone provision.{{ server_domain }}"
  - "--foreman-proxy-dns-forwarders 10.0.2.3"
  - "--foreman-proxy-dns-reverse {{ provisining_network.split('.')[2] }}.{{ provisining_network.split('.')[1] }}.{{ provisining_network.split('.')[0] }}.in-addr.arpa"
  - "--foreman-proxy-dhcp-range '{{ provisining_network }}.10 {{ provisining_network }}.20'"
  - "--foreman-proxy-dhcp-gateway {{ provisining_network }}.1"
  - "--foreman-proxy-dhcp-nameservers {{ provisining_network }}.1"
katello_plugins:
  - "--enable-foreman-plugin-remote-execution"
  - "--enable-foreman-proxy-plugin-remote-execution-ssh"
  - "--foreman-proxy-plugin-remote-execution-ssh-install-key=true"
katello_organizations:
  - name: Carcano
    description: Carcano SA
storage_pools:
  - name: system 
    disks:
      - sda2
      - sdb
    volumes:
      - name: pulp_cache
        size: 13G
        fs_type: xfs
        mount_point: /var/cache/pulp
      - name: pulp_data 
        size: 50G
        fs_type: xfs
        mount_point: /var/lib/pulp 
      - name: mongodb
        size: 20G
        fs_type: xfs
        mount_point: /var/lib/mongodb
      - name: qpidd
        size: 5G
        fs_type: xfs
        mount_point: /var/lib/qpidd 
      - name: pgsql_data
        size: 5G
        fs_type: xfs
        mount_point: /var/lib/pgsql
      - name: squid_data
        size: 5G
        fs_type: xfs
        mount_point: /var/lib/squid

as you see the host specific settings in this file are:

  • filesystem partitioning (lines 27-56)
  • katello version to install (line 1)
  • a configuration switch (katello_scenario) that tells Ansible that this host is a Katello server (line 2)
  • katello administrator username (line 3)
  • katello cli (hammer) administrator username (line 4)
  • infrastructural information (lines 5-19)
  • Katello plugins (lines 20-23)
  • list of Katello organizations to create (lines 24-26)

You can of course extend it with attributes as by your needs.

Configuring The Encrypted Secrets File

As you probably already figured-out, sensitive attributes have not been put inside the previous files because they must be stored using an encrypted format: in this post we use Ansible Vault encrypted files, but Ansible lets you use several other kinds of vaults if necessary.

To generate the files/secrets.yml encrypted file, type the command:

ansible-vault create files/secrets.yml

fill-in it with the  following contents:

katello_admin_password: grimoire
root_user_password: grimoire

doing this way we are keeping both the password of the Katello administrator and of the root user of the foreman proxies hosts safe from malicious eyes.

Configuring Resource Files

Resource files can have several purposes - in this project we have two kind of them:

  • var files, used to  provide variables used by the tasks
  • patch files, that are simply copied to the filesystem of the target host and applied to fix problems that affect specific versions of Katello.

Common Software Repositories

There are settings that remains the same across the various versions of Katello - we store them into the files/repos.yml file:

apypie_url: https://yum.theforeman.org/client/2.0/el7/x86_64/python2-apypie-0.2.1-2.el7.noarch.rpm

it contains only the URL to download the apypie RPM package that is needed by the "theforeman.foreman" Ansible galaxy collection.

Katello Version Specific Software Repositories

Then let's create the files with the URLs from where to download the RPM packages that provides the repository configuration specific to install a specific Katello version:

the files/versions/katello_3_12.yml file is specific to version 3.12:

foreman_release: https://yum.theforeman.org/releases/1.22/el7/x86_64/foreman-release.rpm
katello_repos: https://fedorapeople.org/groups/katello/releases/yum/3.12/katello/el7/x86_64/katello-repos-latest.rpm
puppet_repos: https://yum.puppet.com/puppet5-release-el-7.noarch.rpm

the files/versions/katello_3_16.yml file is specific to version 3.16:

foreman_release: https://yum.theforeman.org/releases/2.1/el7/x86_64/foreman-release.rpm
katello_repos: https://fedorapeople.org/groups/katello/releases/yum/3.16/katello/el7/x86_64/katello-repos-latest.rpm
puppet_repos: https://yum.puppet.com/puppet6-release-el-7.noarch.rpm

Patches

It is known that Katello 3.12 has a problem with subscriptions ending beyond the 2049 year: to fix it, create the files/patches/3.12/rhsm-beyond-2049.patch patch file with the following contents:

--- katello-3.12.3/app/lib/katello/resources/candlepin/product.rb	2022-03-12 16:45:49.423241421 +0100
+++ katello-3.12.3-patched/app/lib/katello/resources/candlepin/product.rb	2022-03-12 16:48:21.638478669 +0100
@@ -72,8 +72,10 @@
 
           def create_unlimited_subscription(owner_key, product_id, start_date)
             start_date ||= Time.now
-            # End it 100 years from now
-            end_date ||= start_date + 10_950.days
+            # python-rhsm - (subscription-manager) can't read certificates with an end date
+            # beyond 2049 year 
+            end_date = Time.parse('2049-12-01 00:00:00 +0000')
+            end_date ||= start_date + 3_650.days
 
             pool = {
               'startDate' => start_date,

Configuration Files

In this post we create the "katello" user specifically to be able to issue hammer statements without having to type the user credentials each time: Hammer can use a credential file indeed - the playbook generates it from the templates/foreman.j2 file with the following contents:

:foreman:
 :username: '{{ katello_admin_user }}'
 :password: '{{ katello_admin_password }}'

Creating The Playbooks

We are finally ready to code the playbook: as we  said, in order to keep it simple to maintain and understand, we split the tasks into several specialized tasks files.

Environment Validation Tasks File

The tasks that performs the validation to make sure that the installation requirements are met can be grouped within the same file: let's put them into the tasks/validate.yml file:

---
- name: add installed packages to the collected facts
  package_facts:
    manager: auto
  tags:
    - never
    - install
- name: fail if katello is already installed
  fail:
    msg: Katello is already installed on this host
  when: "'katello' in ansible_facts.packages"
  tags:
    - never
    - install
- name: make sure the system has enough CPU cores to bare Katello
  fail:
    msg: "This system does not have the minimum required cores to run Katello.\nKatello needs at least 4 cores "
  when: ansible_processor_cores < 4
  tags:
    - never
    - install
- name: make sure the system has enough RAM to bare Katello
  fail:
    msg: "This system does not have the minimum required memory to run Katello.\nKatello needs at least 20GiB of RAM"
  when: ansible_memory_mb.real.total < 19900
  tags:
    - never
    - install

this task list makes sure that:

  • Katello has not been installed yet
  • the system does have at least 4 cores
  • the system does have at least 20GB of RAM

If any of these requirements is not met the playbook fails with an error message specific that highlights which requirement is not met.

Filesystems Creation Tasks File

Before installing it is always wise to create dedicated partitions as suggested by the installation best practices of the product. We put these tasks in the tasks/filesystems.yml file:

---
- block:
    - name: installing rhel-system-roles
      yum:
        name: rhel-system-roles
        state: installed
      delegate_to: localhost
    - include_role:
        name: rhel-system-roles.storage
    - name: removing RPM packages conflicting with Katello
      yum:
        name:
          - libmodulemd
          - libblockdev*
        state: absent
  become: true
  tags:
    - never
    - install

this task list:

  • installs the "rhel-system-roles" RPM packages that provides the official Red Hat roles to manage the system
  • includes and runs the "rhel-system-roles.storage" role that automatically setup partitions as specified by the "storage_pools" variable - this variable is set within the inventory for each specific target host
  • removes some RPM packages that are installed when running the role but that seem to conflict during the installation of Katello

Packages Installation Tasks File

We have reached the core part of the installation: create the tasks/install.yml file with the following contents:

---
- name: updating the system
  become: true
  yum:
    name: '*'
    state: latest
  register: system_output
  tags:
    - never
    - install
- debug:
    var: system_output
    verbosity: 2
  tags:
    - never
    - install
- name: disabling every repository
  become: true
  shell: yum-config-manager --disable "\*"
  tags:
    - never
    - install
- name: installing the packages providing the configuration of the repositories
  become: true
  yum:
    name: "{{ packages }}"
    state: installed
  vars:
    packages:
    - "{{ foreman_release }}"
    - "{{ katello_repos }}"
    - "{{ puppet_repos }}"
    - "epel-release"
  tags:
    - never
    - install
- name: installing the Foreman Release SCL package
  become: true
  yum:
    name: foreman-release-scl
    state: installed
  tags:
    - never
    - install
- name: installing Katello package
  become: true
  yum:
    name: katello
    state: latest
  tags:
    - never
    - install
- name: installing apypie
  become: true
  yum:
    name: "{{ apypie_url }}"
    state: installed
  delegate_to: localhost
  tags:
    - never
    - install
- name: reinstalling RPM packages conflicting with Katello during install
  become: true
  yum:
    name:
      - libmodulemd
      - libblockdev*
    state: latest
  tags:
    - never
    - install

the task list performs a system update, then it installs the RPM packages that configure the repositories from where download Katello and Foreman packages of the selected version. It also takes care of install the "theforeman.foreman" Ansible galaxy collection, installing also its requisite "apypie". In addition to that, it re-installs the RPM packages  needed by "rhel-system-roles.storage" that it uninstalled before to avoid conflicts during the installation of the Katello and Foreman RPM packages.

Since we also apply patches when necessary, we create the tasks file to apply them: these tasks are stored into the  tasks/patches.yml file:

---
- name: copying patch rhsm-beyond-2049.patch to /tmp
  become: true
  copy:
    src: files/patches/3.12/rhsm-beyond-2049.patch
    dest: /tmp
  when: 'katello_version  == "3.12"'
  tags:
    - never
    - install
- name: applying rhsm-beyond-2049.patch
  become: true
  shell: patch -p1 < /tmp/rhsm-beyond-2049.patch
  args:
    chdir: /opt/theforeman/tfm/root/usr/share/gems/gems/katello-3.12.3
  when: katello_version == "3.12"
  tags:
    - never
    - install
- name: removing rhsm-beyond-2049.patch from /tmp
  become: true
  file:
    dest: /tmp/rhsm-beyond-2049.patch
    state: absent
  when: katello_version == "3.12"
  tags:
    - never
    - install

in this post it applies only the "year 2049 bug" patch file to Katello 3.12, but you can extend it with other patches as necessary.

Foreman-Proxy Node Pre-Configuration Task File

This task file is the one that configures the foreman-proxy nodes to be managed using Ansible: create the tasks/prepare-smartproxy.yml with the following contents:

---
- block:
    - name: create ansible user
      user:
        name: ansible
        password: "!"
    - name: create sudo rule
      copy:
        src: files/sudo/ansible
        dest: /etc/sudoers.d
    - name: create /home/ansible/.ssh directory
      become_user: ansible
      file:
        path: /home/ansible/.ssh
        state: directory
        mode: "0700"
    - name: authorize ansible SSH public key
      become_user: ansible
      copy:
        src: /home/ansible/.ssh/id_rsa.pub
        dest: "/home/ansible/.ssh/authorized_keys"
        mode: "0640"
  become: true
  when: katello_scenario != "katello"
  tags:
    - never
    - install

as you see this task file copies the files/sudo/ansible with the sudo rule for the Ansible user to the /etc/sudoers.d directory of the target system. Create the files/sudo/ansible file with the following contents_

ansible ALL=(ALL) NOPASSWD: ALL

Foreman-Proxy Configuration Task File

This task file is the one that creates the foreman-proxy (the Capsule, in Satellite terms) configuration file that is used by foreman-installer to configure the foreman-proxy: create the tasks/configure-smartproxy.yml with the following contents:

---
- block:
    - name: generate smart-proxy certificates bundle
      shell:
        cmd: "foreman-proxy-certs-generate --no-colors --foreman-proxy-fqdn {{ ansible_fqdn }} --certs-tar /home/ansible/katello/files/smartproxies/{{ inventory_hostname }}.tar"
        chdir: /root
      register: foreman_proxy_certs_generate
      delegate_to: localhost
    - name: print the outcome of foreman-proxy-certs-generate
      debug:
        msg: "{{ foreman_proxy_certs_generate }}"
        verbosity: 1
    - set_fact:
        foreman_proxy_install_command: "{{ foreman_proxy_install_command | default([]) + [ item | regex_replace ('\\\\','') | regex_replace(' +',' ') ]  }}" 
      loop: "{{ foreman_proxy_certs_generate.stdout_lines[-10:] }}"
    - set_fact:
        foreman_proxy_install_command: "{{ foreman_proxy_install_command | join(' ') | replace('  ',' ') }}"
    - debug:
        msg: "{{ foreman_proxy_install_command }}"
    - name: print the command to install foreman proxy
      debug:
        msg: "{{ foreman_proxy_install_command }}"
        verbosity: 1
    - name: create files/smartproxies/{{ inventory_hostname }}-capsule-install.txt
      copy:
        content: "{{ foreman_proxy_install_command }}"
        dest: files/smartproxies/{{ inventory_hostname }}-capsule-install.txt
        mode: "0644"
      delegate_to: localhost
    - name: create /root/files/smartproxies direcotry
      file:
        path: /root/files/smartproxies
        state: directory
        recurse: yes
        mode: "0750"
    - name: copy files/smartproxies/{{ inventory_hostname }}.tar to /root/files/smartproxies
      copy:
        src: files/smartproxies/{{ inventory_hostname }}.tar
        dest: /root
  become: true
  when: katello_scenario != "katello"
  tags:
    - never
    - install

Katello Configuration Tasks File

This last task file is the one that configures Katello: create the tasks/setup.yml with the following contents:

---
- name: configure firewalld services
  become: true
  ansible.posix.firewalld:
    service: "{{ item }}"
    permanent: true
    immediate: true
    state: enabled
  loop:
    - dns
    - dhcp
    - tftp
    - http
    - https
  tags:
    - never
    - install
- name: configure firewalld services
  become: true
  ansible.posix.firewalld:
    port: "{{ item }}"
    permanent: true
    immediate: true
    state: enabled
  loop:
    - 5647/tcp
    - 8000/tcp
    - 8140/tcp
    - 8443/tcp
    - 9090/tcp
  tags:
    - never
    - install
- name: adding katello user
  become: true
  user:
    name: katello
    comment: Katello
    shell: /bin/bash
  tags:
    - never
    - install
- name: creating hammer configuration directories for the katello user
  become: true
  become_user: katello
  file:
    dest: "/home/katello/{{ item }}"
    state: directory
  loop:
    - .hammer
    - .hammer/cli.modules.d
    - .hammer/log
  tags:
    - never
    - install
- name: generating hammer configuration file for the katello user
  become: true
  become_user: katello
  template:
    src: templates/foreman.j2
    dest: /home/katello/.hammer/cli.modules.d/foreman.yml
  tags:
    - never
    - install
    - config
- debug:
    msg: "installing katello with options {{ katello_installer_extra_options | join (' ') }}"
    verbosity: 1
  tags:
    - never
    - install
- name: setup Katello
  become: true
  shell: foreman-installer --scenario katello
     --foreman-initial-admin-password {{ katello_admin_password }}
     {{ katello_installer_extra_options | default() | join (' ') }}
  register: katello_setup_output
  when: katello_scenario == "katello"
  tags:
    - never
    - install
- name: setup Foreman Smart-Proxy
  become: true
  command: "{{ foreman_proxy_install_command }} {{ katello_installer_extra_options | default() | join (' ') }}"
  register: katello_setup_output
  when: katello_scenario != "katello"
  tags:
    - never
    - install
- debug:
    var: katello_setup_output.stdout
  tags:
    - never
    - install
    - config
- debug:
    msg: "configuring the plugins {{ katello_plugins | join (' ') }}"
    verbosity: 1
  tags:
    - never
    - config
- name: configure plugins
  become: true
  shell: foreman-installer {{ katello_plugins | join (' ') }}
  register: katello_plugins_output
  tags:
    - never
    - install
    - config
- debug:
    var: katello_plugins_output.stdout
  tags:
    - never
    - install
    - config
- name: configure the katello endpoint to connect to in /etc/hammer/cli.modules.d/foreman.yml file
  become: true
  lineinfile:
    insertafter: ':foreman:'
    path: /etc/hammer/cli.modules.d/foreman.yml
    regexp: "^[ ]*:host:[ ]*.*"
    line: "  :host: 'https://{{ katello_hammer_api_host }}'"
  when: katello_scenario != "katello"
  tags:
    - never
    - install
    - config
- name: add ssl section in /etc/hammer/cli.modules.d/foreman.yml file
  become: true
  lineinfile:
    path: /etc/hammer/cli.modules.d/foreman.yml
    regexp: ':ssl:'
    insertbefore: 'EOF'
    line: ':ssl:'
  when: katello_scenario != "katello"
  tags:
    - never
    - install
    - config
- name: configure ssl_ca_file /etc/hammer/cli.modules.d/foreman.yml in file
  become: true
  lineinfile:
    path: /etc/hammer/cli.modules.d/foreman.yml
    regexp: "^[ ]*:ssl_ca_file:[ ]*'"
    insertafter: ':ssl:'
    line: "  :ssl_ca_file: '/etc/pki/katello/certs/katello-server-ca.crt'"
  when: katello_scenario != "katello"
  tags:
    - never
    - install
    - config

the above tasks:

  • configure firewalld
  • configure Katello
  • configure Katello plugins
  • create and configure the "katello" user as the user dedicated to run hammer commands without having to bother to specify credentials each time

Main Playbook

We are finally ready to create the site.yml file:

---
- name: prepare the Foreman Proxies to be managed by ansible
  gather_facts: false
  hosts: all
  remote_user: root
  vars:
    - ansible_ssh_pass: "{{ root_user_password }}"
  vars_files:
    - files/secrets.yml
  tasks:
    - import_tasks: tasks/prepare-smartproxy.yml

- name: install and configure Katello and/or Foreman Proxies
  gather_facts: true
  hosts: all
  remote_user: ansible
  vars_files:
    - files/repos.yml
    - files/secrets.yml
  tasks:
    - include_vars:
        file: files/versions/katello_{{ katello_version | replace('.','_') }}.yml
      tags: always
    - import_tasks: tasks/validate.yml
    - import_tasks: tasks/filesystems.yml
    - import_tasks: tasks/install.yml
    - import_tasks: tasks/patches.yml
    - import_tasks: tasks/configure-smartproxy.yml
    - import_tasks: tasks/setup.yml

this is the main playbook of this Ansible project, that can be used to both install or re-configure Katello (for example enabling other plugins): as you see it does not contain any particular task: it just loads its settings from the var files and imports the actual tasks from the task files.

Other Playbooks

Besides installing Katello, we can exploit Ansible also to manage it. For example Katello relies on Organizations to segregate software life-cycles: organizations can be created using the Katello Web-UI or using the hammer command line utility, but we can of course use Ansible to manage them.

This can be accomplished by exploiting the "organizations" module of the  "theforeman.foreman" Ansible galaxy collection.

Just to taste the gist of it, let's create the organization.yml playbook with the following contents:

---
- hosts: all
  vars_files:
    - files/secrets.yml
  tasks:
    - include_vars:
        file: files/versions/katello_{{ katello_version | replace('.','_') }}.yml
      tags: always
    - name: create organizations
      theforeman.foreman.organization:
         server_url: "https://{{ inventory_hostname }}"
         validate_certs: no
         username: "{{ katello_admin_user }}"
         password: "{{ katello_admin_password }}"
         name: "{{ item.name }}"
         description: "{{ item.description }}"
         state: present
      loop: "{{ katello_organizations }}"
      delegate_to: localhost

this playbook creates the organizations listed in the "katello_organization" variable that is set in the settings file specific for the host (inventory/host_vars/sat-ci-up3a002.yml).

You can of course create other playbook to manage other stuff, for example Locations. You can get help on each module using the ansible-doc command line utility. For example, to get the documentation of the "theforeman.foreman.location" module type:

ansible-doc theforeman.foreman.location

Installing The Required Ansible Collections

The playbooks we wrote requires the following Ansible collections:

Ansible collections must be listed into the collections/requirements.yml file - create it with the following contents:

---
collections:
 - ansible.posix
 - theforeman.foreman

and then easily installed using the ansible-galaxy command line utility as follows:

sudo ansible-galaxy collection install -r collections/requirements.yml -p /usr/share/ansible/collections

note that if you are using Ansible Tower, you may skip to run the ansible-galaxy command, configuring the job to automatically download the required roles.

Running The Playbooks

We are finally ready to have a go trying everything.

Deploying The Katello Node

We can simply run ansible-playbook with the "install" tag (-t install), limiting the targets only to the "sat-ci-up3a002" host, so installing Katello version "1.12", but we can of course easily install the version "1.16" by setting the value of "katello_version" variable to "1.16" in the inventory/host_vars/sat-ci-up3a002.yml file. This file by the way contains several other settings, such as the partitioning scheme and sizing, that can be adjusted at wish.

When done with the settings, simply issue:

ansible-playbook -t install --ask-vault-pass --limit sat-ci-up3a002 site.yml

You can relax with a cup of tea if you fancy: installing and configuring everything takes quite a lot of time to complete.

Reconfiguring Katello

Besides installing Katello, the site.yml playbook can also be used to re-configure it, for example to enable other Foreman plugins such as the Ansible plugin (a very handy plugin that enables Katello to perform remote executions using Ansible).

This can be easily accomplished by adding the relevant foreman-installer options to the "katello_plugins" list in the inventory/host_vars/sat-ci-up3a002.yml file.

For example, modify the "katello_plugins" list as follows:

katello_plugins:
  - "--enable-foreman-plugin-remote-execution"
  - "--enable-foreman-proxy-plugin-remote-execution-ssh"
  - "--foreman-proxy-plugin-remote-execution-ssh-install-key=true"
  - "--enable-foreman-plugin-ansible"
  - "--enable-foreman-proxy-plugin-ansible"

we just added:

  • the "--enable-foreman-plugin-ansible" option to enable the Ansible feature on Katello itself
  • the "--enable-foreman-proxy-plugin-ansible" option to enable the Ansible feature on the Foreman proxy installed on the same host where Kastello is running

now run the playbook, limiting it to the sat-ci-up3a002 target host:

ansible-playbook -t config --ask-vault-pass --limit sat-ci-up3a002 site.yml

Managing Katello Using Ansible

The organization.yml playbook has been specifically developed to show you how to use the "theforeman.foreman" Ansible galaxy role: this playbook exploits the "organization" module to add to Katello the organizations listed in the "katello_organizations" list contained in the inventory/host_vars/sat-ci-up3a002.yml file.

Let's run it to see it in action:

ansible-playbook --ask-vault-pass --limit sat-ci-up3a002 organization.yml

the output is as follows:

PLAY [all] **************************************************************************************************************************************

TASK [Gathering Facts] **************************************************************************************************************************
ok: [sat-ci-up3a002]

TASK [include_vars] *****************************************************************************************************************************
ok: [sat-ci-up3a002]

TASK [create organizations] *********************************************************************************************************************
changed: [sat-ci-up3a002 -> localhost] => (item={u'name': u'Carcano', u'description': u'Carcano SA'})

PLAY RECAP **************************************************************************************************************************************
sat-ci-up3a002             : ok=4    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 

as you see by the above report, the "create organizations" task has been reported with state changed: this means that at least one Organization has been modified on Katello.

One of the most handy features of Ansible is that you do not have to bother about the outcome of re-running the same task multiple times: every run always produces the same outcome, since modules are idempotent.

Let's see this with our own eyes: run the playbook again - the output this time must be as follows:

PLAY [all] **************************************************************************************************************************************

TASK [Gathering Facts] **************************************************************************************************************************
ok: [sat-ci-up3a002]

TASK [include_vars] *****************************************************************************************************************************
ok: [sat-ci-up3a002]

TASK [create organizations] *********************************************************************************************************************
ok: [sat-ci-up3a002 -> localhost] => (item={u'name': u'Carcano', u'description': u'Carcano SA'})

PLAY RECAP **************************************************************************************************************************************
sat-ci-up3a002             : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

as you see, this time the state of the "create organizations" task is ok, meaning that Ansible has not performed any changes because everything was already set.

Be wary however that Ansible is not "declarative": if you remove an organization from the "katello_organizations" list and run the playbook again, the organization does not get removed - since the state parameter of the module is set to "present", so it only takes care to create the organization that are listed in the variable but does not exist in Katello. If you want to remove an organization, you must create a task that explicitly specify its name and set the state to "absent".

Footnotes

Here it ends this tutorial on installing Katello using Ansible - we also saw how to instantiate modules from the "theforeman.foreman" Ansible galaxy collection. As you see it is very easy to create an Ansible project that installs Katello and reconfigure it if necessary: ... but what about Foreman proxies (Capsules, in Satellite terms)?

The site playbook has been designed so to let you easily provision capsules too, ... but this will be the topic of the next post. Stay tuned!

Writing a post like this takes hours. I'm doing it for the only pleasure of sharing knowledge and thoughts, but all of this does not come for free: it is a time consuming volunteering task. This blog is not affiliated to anybody, does not show advertisements nor sells data of visitors. The only goal of this blog is to make ideas flow. So please, if you liked this post, spend a little of your time to share it on Linkedin or Twitter using the buttons below: seeing that posts are actually read is the only way I have to understand if I'm really sharing thoughts or if I'm just wasting time and I'd better give up.

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>