diff --git a/.github/workflows/tox.yml b/.github/workflows/tox.yml index fedce06..b726edf 100644 --- a/.github/workflows/tox.yml +++ b/.github/workflows/tox.yml @@ -22,8 +22,10 @@ jobs: - chrony - disk_wipe - firewalld + - libvirtd - localectl - molecule_docker_ci + - network_scripts - nfs_server - nmcli_add_addrs - package_updater @@ -57,7 +59,10 @@ jobs: - name: Install dependencies run: | set -ex + sudo apt-get update + sudo apt-get install -y libapt-pkg-dev build-essential python3-setuptools python -m pip install --upgrade pip + pip install -U setuptools wheel pip install tox tox-ansible - name: Test with tox run: | diff --git a/plugins/modules/yaml_file.py b/plugins/modules/yaml_file.py new file mode 100644 index 0000000..2054f0c --- /dev/null +++ b/plugins/modules/yaml_file.py @@ -0,0 +1,193 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# TODO: The following items are nice-to-haves that have not yet been found to +# be necessary, but could be beneficial in the future: +# * append to list vs replace. A use case might call for modifying the contents +# of a list rather than needing to replace it every time. An option could be +# added to append to a list +# * truncate a list. Reduce the length of a list to only equal to a given size +# * allow special characters in an identifier, such as ".", with an escape + +from __future__ import (absolute_import, print_function) +__metaclass__ = type + +ANSIBLE_METADATA = {'metadata_version': '1.1', 'status': ['preview']} + +DOCUMENTATION = ''' +--- +module: yaml_file +short_description: add or modify fields in a YAML file +description: + - Modify YAML files programmatically +options: + create: + description: + - Create the file, if not found. Only has meaning if `state` is set to + `present`. + type: bool + default: true + key: + description: + - The key in the file to modify. Child objects should be referenced + with a '.', elements of a list with a '[...]'. Use '[2]' to indicate + the particular index within the list or '[]' to indicate all elements + within the list should be modified. + required: true + path: + description: + - Path of the file to modify + required: true + type: path + aliases: ['file', 'dest'] + state: + description: + - `present` to add/modify values, `absent` to delete them + default: 'present' + choices: ['absent', 'present'] + value: + description: + - The value of the key(s) to set. Required if `state` is `present` +''' + +from ansible.module_utils.basic import AnsibleModule # noqa: E402 +from yaml import load, dump # noqa: E402 +try: + from yaml import CLoader as Loader, CDumper as Dumper +except ImportError: + from yaml import Loader, Dumper +import os # noqa: E402 +import re # noqa: E402 +# Used to deep-compare two dictionaries +import json # noqa: E402 + + +class Yaml(object): + def __init__(self, args): + self.create = args['create'] + self.path = os.path.expanduser(args['path']) + self.state = args['state'] + self.key = args['key'] + self.value = args['value'] + + def check_module_arguments(self): + """Execute a basic set of sanity checks. + + Checks some basic pre-conditions before the module attempts to run so + that these things don't need to be checked for later on. + + :returns: (boolean, string) where the first element says whether the + check passed and the second includes an appropriate error message if it + did not.""" + # Check that file exists if state is 'present' + if self.state == 'present' and not self.create: + if not os.path.isfile(self.path): + return (False, "File not found when state is 'present'. Create" + " disabled") + # Be sure proper arguments are specified + if self.state == 'present' and self.value is None: + return False, 'When state is "present", a value must be specified' + # Parse the key value + try: + self.key_list = re.findall(r'([-\w]+|\[\d*\])', self.key) + if len(self.key_list) == 0: + return False, "No key value parsed. Please check the syntax." + except Exception as ex: + return False, ex.msg + # Massage "value" into expected type + if self.value is not None: + self.value = json.loads(self.value) + self.read_file() + return True, '' + + def read_file(self): + """Read the current state of the file. + + Reads the current YAML file into memory, returns an empty dict if + the file does not exist or the parsed object if it does.""" + if not os.path.isfile(self.path): + self.obj = dict() + else: + with open(self.path, 'r') as stream: + self.obj = load(stream, Loader=Loader) + if self.obj is None: + self.obj = dict() + + def write_file(self): + """Write the object to the target file. + + Writes the value of data to the YAML file. + + :param data: A dict of values to write out + :returns: nothing""" + with open(self.path, 'w') as stream: + dump(self.obj, stream, default_flow_style=False, Dumper=Dumper) + + def present(self): + return self._present(self.obj, self.key_list) + + def _present(self, obj, keys): + """Recursively walks to a specified key in the file. + + :param obj: The current level of the object that is being walked + :param keys: A list of key fragments to walk to + :param value: The value to assign to the given key + :returns: True if changes were made, False otherwise""" + if len(keys) == 1: + if json.dumps(obj.get(keys[0], None), sort_keys=True) == \ + json.dumps(self.value): + return False + else: + obj[keys[0]] = self.value + return True + else: + if keys[0] not in obj: + obj[keys[0]] = dict() + return self._present(obj[keys[0]], keys[1:]) + + def absent(self): + return self._absent(self.obj, self.key_list) + + def _absent(self, obj, keys): + if len(keys) == 1: + if isinstance(obj, dict) and keys[0] in obj: + del obj[keys[0]] + return True + elif not isinstance(obj, dict): + raise Exception("Cannot subscript type found: {}".format(obj)) + else: + return False + else: + if keys[0] in obj: + return self._absent(obj[keys[0]], keys[1:]) + else: + return False + + +def main(): + module = AnsibleModule( + argument_spec=dict( + create=dict(type='bool', default=False), + key=dict(type='str', required=True), + path=dict(type='str', aliases=['file', 'dest'], required=True), + state=dict(type='str', choices=['absent', 'present'], + default='present'), + value=dict(type='json') + ) + ) + yaml = Yaml(module.params) + check = yaml.check_module_arguments() + if not check[0]: + module.fail_json(msg=check[1]) + else: + if module.params['state'] == 'present': + changes = yaml.present() + else: + changes = yaml.absent() + if changes: + yaml.write_file() + module.exit_json(changed=changes) + + +if __name__ == '__main__': + main() diff --git a/roles/libvirt_rhel_vm/.gitignore b/roles/libvirt_rhel_vm/.gitignore new file mode 100644 index 0000000..81b995c --- /dev/null +++ b/roles/libvirt_rhel_vm/.gitignore @@ -0,0 +1,7 @@ +.*.swp +.*.swo +*.pyc +*.pyo +__pycache__/* +molecule/*/junit.xml +molecule/*/pytestdebug.log diff --git a/roles/libvirt_rhel_vm/README.md b/roles/libvirt_rhel_vm/README.md new file mode 100644 index 0000000..3e9e922 --- /dev/null +++ b/roles/libvirt_rhel_vm/README.md @@ -0,0 +1,99 @@ +libvirt +=========== + +Spins up a RHEL/CentOS VM on the local host + +Requirements +------------ + +Ansible 2.8 or higher + +Role Variables +-------------- + +Currently the following variables are supported: + +### General + +* `libvirt_rhel_vm_storage` - Default: `/var/lib/libvirt/images`. The path on + the remote system to upload VM images to. Currently there is no support for + storage pools other than the local directories. +* `libvirt_rhel_vm_domain` - Default: +```yaml +libvirt_rhel_vm_domain: + name: foo.example.com + img_path: ~/CentOS-7-x86_64-GenericCloud.qcow2 + os-variant: rhel7.7 + + ram: 4096 + vcpus: 1 + disk: 20G + + root_passwd: "$6$Fa84yQfpK0gpluDJ$sdfdsfdsfdsfdsfdfdsfdsfddsfdaQpO6MKgioTOV5\ + lRy.2tdA9IexTnvYNK3mP8clpC/sdsfdsfdsfdsfdsfsd60" + root_ssh_pub_keys: + - "ssh-rsa AAAdsfdfdsfdfdEAAAADAQABAAABAQD0yXYYdsfdAOSdIjcRp\ + 8TVOPnFplYJEY8VST+bQeW1Fosdfddfsdfmpmd/RdV9W/0d7sRfymL1diDm6ml3kwddff5Xn7A\ + edsztdRahvZsBD9ADBqnQBli0adop6+PDRsdfdsfBjpFnrwVoe9QZPJVqZle6HBeJYIffffEY6\ + 1vhC8JXyGGDIJi7pdSjPdsfdsfdsfdsfdsfdsfsxTbAp4ddkEuS/9NR8JZ3HJg+h6mKoNffffq\ + RUiikG98dfdfdsfdsfdfdfdfdfdfdsfdsfdsfdsfdsfyPMXK7nD+R0Jx4mmRlFWKmYTjffffSq\ + sdfadfdsfdsfdsf" + + bridges: + - br0 + + nics: # NICs to provision on the VM, using the network_scripts role + - filename: ifcfg-eth0 + NAME: eth0 + DEVICE: eth0 + TYPE: Ethernet + BOOTPROTO: static + IPADDR: 192.168.1.10 + GATEWAY: 192.168.1.1 + NETMASK: 255.255.255.0 + DEFROUTE: !!str yes + IPV6INIT: !!str no + ONBOOT: !!str yes + DNS1: 8.8.8.8 +``` + The VM to spin up. The image pointed to by `libvirt_rhel_vm_domain.img_path` + must already exist on the local system. It will be uploaded to the remote host + in the `libvirt_rhel_vm_storage` directory, resized to the value of `disk`, + spun up with the specified hardware options, fed the `nics` list as config files + from the `network_scripts` role, and configured with the provided passwords + and pubkeys. The provisioning script is relatively tightly bound with RHEL or + CentOS 7, hence the naming of this role. +* `libvirt_become` - Default: true. If this role needs administrator + privileges, then use the Ansible become functionality (based off sudo). +* `libvirt_become_user` - Default: root. If the role uses the become + functionality for privilege escalation, then this is the name of the target + user to change to. +* `libvirt_rhel_vm_nic_config_path` - Default: `null`. Path on the + remote host to install the nic-config scripts into before uploading them to + the VM. If left as `null`, then a tempdir will be created on the remote host + for uploading. If you set this value, then you are responsible to ensure that + the path exists before calling this role. + +Dependencies +------------ + +None + +Example Playbook +---------------- + +```yaml +- hosts: libvirt-servers + roles: + - role: oasis_roles.system.libvirt +``` + +License +------- + +GPLv3 + +Author Information +------------------ + +Greg Hellings diff --git a/roles/libvirt_rhel_vm/defaults/main.yml b/roles/libvirt_rhel_vm/defaults/main.yml new file mode 100644 index 0000000..a692ab1 --- /dev/null +++ b/roles/libvirt_rhel_vm/defaults/main.yml @@ -0,0 +1,39 @@ +libvirt_rhel_vm_become: true +libvirt_rhel_vm_become_user: root +libvirt_rhel_vm_storage: /var/lib/libvirt/images +libvirt_rhel_vm_nic_config_path: null +libvirt_rhel_vm_domain: + name: foo.example.com + img_path: ~/CentOS-7-x86_64-GenericCloud.qcow2 + os-variant: rhel7.7 + + ram: 4096 + vcpus: 1 + disk: 20G + + root_passwd: "$6$Fa84yQfpK0gpluDJ$sdfdsfdsfdsfdsfdfdsfdsfddsfdaQpO6MKgioTOV5\ + lRy.2tdA9IexTnvYNK3mP8clpC/sdsfdsfdsfdsfdsfsd60" + root_ssh_pub_keys: + - "ssh-rsa AAAdsfdfdsfdfdEAAAADAQABAAABAQD0yXYYdsfdAOSdIjcRp\ + 8TVOPnFplYJEY8VST+bQeW1Fosdfddfsdfmpmd/RdV9W/0d7sRfymL1diDm6ml3kwddff5Xn7A\ + edsztdRahvZsBD9ADBqnQBli0adop6+PDRsdfdsfBjpFnrwVoe9QZPJVqZle6HBeJYIffffEY6\ + 1vhC8JXyGGDIJi7pdSjPdsfdsfdsfdsfdsfdsfsxTbAp4ddkEuS/9NR8JZ3HJg+h6mKoNffffq\ + RUiikG98dfdfdsfdsfdfdfdfdfdfdsfdsfdsfdsfdsfyPMXK7nD+R0Jx4mmRlFWKmYTjffffSq\ + sdfadfdsfdsfdsf" + + bridges: + - br0 + + nics: + - filename: ifcfg-eth0 + NAME: eth0 + DEVICE: eth0 + TYPE: Ethernet + BOOTPROTO: static + IPADDR: 192.168.1.10 + GATEWAY: 192.168.1.1 + NETMASK: 255.255.255.0 + DEFROUTE: !!str yes + IPV6INIT: !!str no + ONBOOT: !!str yes + DNS1: 8.8.8.8 diff --git a/roles/libvirt_rhel_vm/handlers/main.yml b/roles/libvirt_rhel_vm/handlers/main.yml new file mode 100644 index 0000000..e260b70 --- /dev/null +++ b/roles/libvirt_rhel_vm/handlers/main.yml @@ -0,0 +1 @@ +# Handlers for libvirt diff --git a/roles/libvirt_rhel_vm/meta/main.yml b/roles/libvirt_rhel_vm/meta/main.yml new file mode 100644 index 0000000..4f71c2c --- /dev/null +++ b/roles/libvirt_rhel_vm/meta/main.yml @@ -0,0 +1,16 @@ +galaxy_info: + author: Greg Hellings + description: |- + Spins up a local VM from the RHEL/CentOS family on the target machine + company: Red Hat, Inc. + license: GPLv3 + min_ansible_version: 2.8 + platforms: + - name: EL + versions: + - 7 + + galaxy_tags: + - oasis + +dependencies: [] diff --git a/roles/libvirt_rhel_vm/molecule/default/molecule.yml b/roles/libvirt_rhel_vm/molecule/default/molecule.yml new file mode 100644 index 0000000..8dda7a1 --- /dev/null +++ b/roles/libvirt_rhel_vm/molecule/default/molecule.yml @@ -0,0 +1,5 @@ +driver: + name: openstack +platforms: + - name: test-libvirt + flavor: ci.m1.large diff --git a/roles/libvirt_rhel_vm/molecule/default/tests/test_running.py b/roles/libvirt_rhel_vm/molecule/default/tests/test_running.py new file mode 100644 index 0000000..594da2e --- /dev/null +++ b/roles/libvirt_rhel_vm/molecule/default/tests/test_running.py @@ -0,0 +1,4 @@ +def test_running(host): + with host.sudo(): + virsh = host.check_output("virsh list") + assert "foo.example.com" in virsh diff --git a/roles/libvirt_rhel_vm/molecule/shared/cleanup.yml b/roles/libvirt_rhel_vm/molecule/shared/cleanup.yml new file mode 100644 index 0000000..a4f5e2e --- /dev/null +++ b/roles/libvirt_rhel_vm/molecule/shared/cleanup.yml @@ -0,0 +1,22 @@ +- name: unregister systems + hosts: all + gather_facts: false + tasks: + - name: wait for host + wait_for_connection: + timeout: 1 + register: waiting + ignore_errors: true + + - block: + - name: fetch facts + setup: {} + + - name: do unregistration + include_role: + name: oasis_roles.system.rhsm + when: ansible_distribution == 'RedHat' + when: waiting is success + vars: + rhsm_unregister: true + rhsm_username: "{{ omit }}" diff --git a/roles/libvirt_rhel_vm/molecule/shared/playbook.yml b/roles/libvirt_rhel_vm/molecule/shared/playbook.yml new file mode 100644 index 0000000..fd3b307 --- /dev/null +++ b/roles/libvirt_rhel_vm/molecule/shared/playbook.yml @@ -0,0 +1,5 @@ +- name: converge + hosts: all + roles: + - role: libvirt_rhel_vm + libvirt_rhel_vm_nic_config_path: /tmp/nic-config diff --git a/roles/libvirt_rhel_vm/molecule/shared/prepare.yml b/roles/libvirt_rhel_vm/molecule/shared/prepare.yml new file mode 100644 index 0000000..f658750 --- /dev/null +++ b/roles/libvirt_rhel_vm/molecule/shared/prepare.yml @@ -0,0 +1,48 @@ +- name: register RHSM + hosts: all + pre_tasks: + - name: install python-apt where necessary + pip: + # yamllint disable-line rule:line-length + name: git+https://git.launchpad.net/ubuntu/+source/python-apt@applied/ubuntu/bionic-updates + state: present + when: ansible_pkg_mgr == "apt" + + - name: download image file + delegate_to: localhost + get_url: + dest: "~/" + # yamllint disable-line rule:line-length + url: https://cloud.centos.org/centos/7/images/CentOS-7-x86_64-GenericCloud.qcow2.xz + + # Is not an archive, just a zipped qcow file + - name: extract file + delegate_to: localhost + command: unxz CentOS-7-x86_64-GenericCloud.qcow2.xz + args: + chdir: "~/" + creates: "~/CentOS-7-x86_64-GenericCloud.qcow2" + + # path used in playbook.yml for creating config scripts to pass to VM + - name: create nic-config directory + file: + path: /tmp/nic-config + state: directory + roles: + - role: oasis_roles.system.rhsm + when: ansible_distribution == 'RedHat' + - role: oasis_roles.system.libvirtd + - role: oasis_roles.system.network_bridge + vars: + rhsm_username: "{{ lookup('env', 'OASIS_RHSM_USERNAME') }}" + rhsm_password: "{{ lookup('env', 'OASIS_RHSM_PASSWORD') }}" + rhsm_server_hostname: "{{ lookup('env', 'OASIS_RHSM_SERVER_HOSTNAME') }}" + rhsm_pool_ids: "{{ lookup('env', 'OASIS_RHSM_POOL_IDS') }}" + rhsm_repositories: + enabled: + - rhel-7-server-rpms + - rhel-7-server-extras-rpms + - rhel-7-server-optional-rpms + network_bridge_devices: + br0: + device: eth0 diff --git a/roles/libvirt_rhel_vm/tasks/images.yml b/roles/libvirt_rhel_vm/tasks/images.yml new file mode 100644 index 0000000..4bae35d --- /dev/null +++ b/roles/libvirt_rhel_vm/tasks/images.yml @@ -0,0 +1,4 @@ +- name: copy image + copy: + src: "{{ libvirt_rhel_vm_domain.img_path }}" + dest: "{{ libvirt_rhel_vm_storage }}" diff --git a/roles/libvirt_rhel_vm/tasks/main.yml b/roles/libvirt_rhel_vm/tasks/main.yml new file mode 100644 index 0000000..e0c165b --- /dev/null +++ b/roles/libvirt_rhel_vm/tasks/main.yml @@ -0,0 +1,12 @@ +- name: install libvirt with domains + become: "{{ libvirt_rhel_vm_become }}" + become_user: "{{ libvirt_rhel_vm_become_user }}" + block: + - name: upload images + import_tasks: images.yml + + - name: configure network scripts + import_tasks: network.yml + + - name: modify image + import_tasks: modify_image.yml diff --git a/roles/libvirt_rhel_vm/tasks/modify_image.yml b/roles/libvirt_rhel_vm/tasks/modify_image.yml new file mode 100644 index 0000000..9afd901 --- /dev/null +++ b/roles/libvirt_rhel_vm/tasks/modify_image.yml @@ -0,0 +1,26 @@ +- name: upload setup-domain.sh + template: + src: setup_domain.sh + dest: /tmp/setup_domain.sh + owner: root + group: root + mode: "0755" + +- block: + - name: run domain install script + command: /tmp/setup_domain.sh + register: _libvirt_shell_output + changed_when: _libvirt_shell_output.rc == 2 + failed_when: _libvirt_shell_output.rc not in [0, 2] + always: + - name: display script stdout + debug: + msg: "{{ _libvirt_shell_output.stdout }}" + + - name: display script stderr + debug: + msg: "{{ _libvirt_shell_output.stderr }}" + rescue: + - name: error out + assert: + that: "{{ 1 == 0 }}" diff --git a/roles/libvirt_rhel_vm/tasks/network.yml b/roles/libvirt_rhel_vm/tasks/network.yml new file mode 100644 index 0000000..bd9dc08 --- /dev/null +++ b/roles/libvirt_rhel_vm/tasks/network.yml @@ -0,0 +1,19 @@ +- block: + - name: create directory to hold domain nic-configs + tempfile: + state: directory + register: _libvirt_tmp_nic_config + + - name: extract destination path + set_fact: + libvirt_rhel_vm_nic_config_path: "{{ _libvirt_tmp_nic_config.path }}" + when: libvirt_rhel_vm_nic_config_path is none + +- block: + - name: call network-scripts role + import_role: + name: network_scripts + vars: + network_scripts_dest_path: "{{ libvirt_rhel_vm_nic_config_path }}" + network_scripts_nics: "{{ libvirt_rhel_vm_domain.nics }}" + network_scripts_restart: false diff --git a/roles/libvirt_rhel_vm/templates/setup_domain.sh b/roles/libvirt_rhel_vm/templates/setup_domain.sh new file mode 100644 index 0000000..3a9dd87 --- /dev/null +++ b/roles/libvirt_rhel_vm/templates/setup_domain.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash + +set -ex -o pipefail + +# exit 0 = success, no changes +# exit 1 = error +# exit 2 = success, changes + +domain_name="{{ libvirt_rhel_vm_domain.name }}" + +if [[ $(virsh domid "${domain_name}") ]]; then + exit 0 +fi + +cd "{{ libvirt_rhel_vm_storage }}" + +# Create an empty qcow2 file for a base image +qemu-img create -f qcow2 "${domain_name}.qcow2" "{{ libvirt_rhel_vm_domain.disk }}" + +# Use virt-resize to dump the guest image into the qcow2 file we just created. +virt-resize \ + --expand /dev/sda1 "{{ libvirt_rhel_vm_domain.img_path | basename }}" \ + "${domain_name}.qcow2" + +# - Remove cloud-init (causes delays and problems when not used on a cloud) +# - Set UseDNS=no for initial login +# - Set root user password +# - Inject root user SSH key +# - Copy in templated domain NIC configs +virt-customize \ + -a "${domain_name}.qcow2" \ + --run-command "yum remove cloud-init* -y; sed -i 's/^#UseDNS.*\$/UseDNS no/g' /etc/ssh/sshd_config" \ + --root-password password:'{{ libvirt_rhel_vm_domain.root_passwd }}' \ +{% for key in libvirt_rhel_vm_domain.root_ssh_pub_keys %} + --ssh-inject root:string:"{{ key }}" \ +{% endfor %} + --copy-in "{{ libvirt_rhel_vm_nic_config_path }}:/etc/sysconfig/" \ + --selinux-relabel + +virt-install \ + --ram "{{ libvirt_rhel_vm_domain.ram | mandatory }}" \ + --vcpus "{{ libvirt_rhel_vm_domain.vcpus | mandatory }}" \ + --os-variant "{{ libvirt_rhel_vm_domain['os-variant'] }}" \ + --disk "path={{ libvirt_rhel_vm_storage }}/${domain_name}.qcow2,device=disk,bus=virtio,format=qcow2" \ + --import \ + --noautoconsole \ + --vnc \ +{% for bridge in libvirt_rhel_vm_domain.bridges %} + --bridge "{{ bridge }}" \ +{% endfor %} + --autostart \ + --name "${domain_name}" + +exit 2 diff --git a/roles/libvirtd/README.md b/roles/libvirtd/README.md new file mode 100644 index 0000000..f6b1ce2 --- /dev/null +++ b/roles/libvirtd/README.md @@ -0,0 +1,46 @@ +libvirtd +=========== + +Installs and configures libvirtd on a target system + +Requirements +------------ + +Ansible 2.8 or higher + +Role Variables +-------------- + +Currently the following variables are supported: + +### General + +* `libvirtd_become` - Default: true. If this role needs administrator + privileges, then use the Ansible become functionality (based off sudo). +* `libvirtd_become_user` - Default: root. If the role uses the become + functionality for privilege escalation, then this is the name of the target + user to change to. + +Dependencies +------------ + +None + +Example Playbook +---------------- + +```yaml +- hosts: libvirtd-servers + roles: + - role: oasis_roles.system.libvirtd +``` + +License +------- + +GPLv3 + +Author Information +------------------ + +Greg Hellings diff --git a/roles/libvirtd/defaults/main.yml b/roles/libvirtd/defaults/main.yml new file mode 100644 index 0000000..5f9f4f7 --- /dev/null +++ b/roles/libvirtd/defaults/main.yml @@ -0,0 +1,2 @@ +libvirtd_become: true +libvirtd_become_user: root diff --git a/roles/libvirtd/handlers/main.yml b/roles/libvirtd/handlers/main.yml new file mode 100644 index 0000000..3643e67 --- /dev/null +++ b/roles/libvirtd/handlers/main.yml @@ -0,0 +1,4 @@ +- name: restart libvirtd + service: + name: libvirtd + state: restarted diff --git a/roles/libvirtd/meta/main.yml b/roles/libvirtd/meta/main.yml new file mode 100644 index 0000000..76976b3 --- /dev/null +++ b/roles/libvirtd/meta/main.yml @@ -0,0 +1,16 @@ +galaxy_info: + author: Greg Hellings + description: |- + Installs and configures libvirtd on a host + company: Red Hat, Inc. + license: GPLv3 + min_ansible_version: 2.8 + platforms: + - name: EL + versions: + - 7 + + galaxy_tags: + - oasis + +dependencies: [] diff --git a/roles/libvirtd/molecule/fedora/molecule.yml b/roles/libvirtd/molecule/fedora/molecule.yml new file mode 100644 index 0000000..674bbef --- /dev/null +++ b/roles/libvirtd/molecule/fedora/molecule.yml @@ -0,0 +1,10 @@ +driver: + name: openstack +platforms: + - name: test-libvirtd-fedora + image: "Fedora 31" +provisioner: + inventory: + host_vars: + test-libvirtd-fedora: + ansible_user: fedora diff --git a/roles/libvirtd/molecule/fedora/tests/test_null.py b/roles/libvirtd/molecule/fedora/tests/test_null.py new file mode 100644 index 0000000..e69de29 diff --git a/roles/libvirtd/molecule/rhel/molecule.yml b/roles/libvirtd/molecule/rhel/molecule.yml new file mode 100644 index 0000000..0fa0db3 --- /dev/null +++ b/roles/libvirtd/molecule/rhel/molecule.yml @@ -0,0 +1,4 @@ +driver: + name: openstack +platforms: + - name: test-libvirtd-rhel diff --git a/roles/libvirtd/molecule/rhel/tests/test_null.py b/roles/libvirtd/molecule/rhel/tests/test_null.py new file mode 100644 index 0000000..e69de29 diff --git a/roles/libvirtd/molecule/shared/cleanup.yml b/roles/libvirtd/molecule/shared/cleanup.yml new file mode 100644 index 0000000..a4f5e2e --- /dev/null +++ b/roles/libvirtd/molecule/shared/cleanup.yml @@ -0,0 +1,22 @@ +- name: unregister systems + hosts: all + gather_facts: false + tasks: + - name: wait for host + wait_for_connection: + timeout: 1 + register: waiting + ignore_errors: true + + - block: + - name: fetch facts + setup: {} + + - name: do unregistration + include_role: + name: oasis_roles.system.rhsm + when: ansible_distribution == 'RedHat' + when: waiting is success + vars: + rhsm_unregister: true + rhsm_username: "{{ omit }}" diff --git a/roles/libvirtd/molecule/shared/playbook.yml b/roles/libvirtd/molecule/shared/playbook.yml new file mode 100644 index 0000000..a88ff11 --- /dev/null +++ b/roles/libvirtd/molecule/shared/playbook.yml @@ -0,0 +1,4 @@ +- name: converge + hosts: all + roles: + - role: libvirtd diff --git a/roles/libvirtd/molecule/shared/prepare.yml b/roles/libvirtd/molecule/shared/prepare.yml new file mode 100644 index 0000000..1fe8a47 --- /dev/null +++ b/roles/libvirtd/molecule/shared/prepare.yml @@ -0,0 +1,15 @@ +- name: register RHSM + hosts: all + roles: + - role: oasis_roles.system.rhsm + when: ansible_distribution == 'RedHat' + vars: + rhsm_username: "{{ lookup('env', 'OASIS_RHSM_USERNAME') }}" + rhsm_password: "{{ lookup('env', 'OASIS_RHSM_PASSWORD') }}" + rhsm_server_hostname: "{{ lookup('env', 'OASIS_RHSM_SERVER_HOSTNAME') }}" + rhsm_pool_ids: "{{ lookup('env', 'OASIS_RHSM_POOL_IDS') }}" + rhsm_repositories: + enabled: + - rhel-7-server-rpms + - rhel-7-server-extras-rpms + - rhel-7-server-optional-rpms diff --git a/roles/libvirtd/molecule/shared/tests/test_libvirtd_running.py b/roles/libvirtd/molecule/shared/tests/test_libvirtd_running.py new file mode 100644 index 0000000..82ac924 --- /dev/null +++ b/roles/libvirtd/molecule/shared/tests/test_libvirtd_running.py @@ -0,0 +1,3 @@ +def test_daemon_running(host): + libvirtd = host.service("libvirtd") + assert libvirtd.is_running diff --git a/roles/libvirtd/molecule/ubuntu/molecule.yml b/roles/libvirtd/molecule/ubuntu/molecule.yml new file mode 100644 index 0000000..64df8c2 --- /dev/null +++ b/roles/libvirtd/molecule/ubuntu/molecule.yml @@ -0,0 +1,9 @@ +driver: + name: docker +platforms: + - name: test-libvirtd-ubuntu + image: ubuntu:latest + command: sleep infinity +provisioner: + playbooks: + converge: playbook.yml diff --git a/roles/libvirtd/molecule/ubuntu/playbook.yml b/roles/libvirtd/molecule/ubuntu/playbook.yml new file mode 100644 index 0000000..1ff9e3e --- /dev/null +++ b/roles/libvirtd/molecule/ubuntu/playbook.yml @@ -0,0 +1,4 @@ +- name: converge + hosts: localhost + roles: + - role: libvirtd diff --git a/roles/libvirtd/molecule/ubuntu/requirements.txt b/roles/libvirtd/molecule/ubuntu/requirements.txt new file mode 100644 index 0000000..d745a11 --- /dev/null +++ b/roles/libvirtd/molecule/ubuntu/requirements.txt @@ -0,0 +1 @@ +git+https://git.launchpad.net/python-apt@1.8.y diff --git a/roles/libvirtd/tasks/install.yml b/roles/libvirtd/tasks/install.yml new file mode 100644 index 0000000..b49051e --- /dev/null +++ b/roles/libvirtd/tasks/install.yml @@ -0,0 +1,7 @@ +- block: + - name: install packages + package: + name: "{{ libvirtd_packages }}" + state: present + become: "{{ libvirtd_become }}" + become_user: "{{ libvirtd_become_user }}" diff --git a/roles/libvirtd/tasks/main.yml b/roles/libvirtd/tasks/main.yml new file mode 100644 index 0000000..bfd0bfc --- /dev/null +++ b/roles/libvirtd/tasks/main.yml @@ -0,0 +1,21 @@ +- name: read environment defaults + include_vars: + file: "{{ item }}" + with_first_found: + - "vars/{{ ansible_distribution }}-{{ + ansible_distribution_major_version }}.yml" + - "vars/{{ ansible_distribution }}.yml" + - "vars/default.yml" + +- name: install daemon + import_tasks: install.yml + +# TODO: Add support for configuring pools, networks, etc + +- name: start service + become: "{{ libvirtd_become }}" + become_user: "{{ libvirtd_become_user }}" + service: + name: libvirtd + state: started + enabled: true diff --git a/roles/libvirtd/vars/RedHat-7.yml b/roles/libvirtd/vars/RedHat-7.yml new file mode 100644 index 0000000..8d94b07 --- /dev/null +++ b/roles/libvirtd/vars/RedHat-7.yml @@ -0,0 +1,8 @@ +# Fedora/CentOS/RedHat/etc +libvirtd_packages: + - libvirt-daemon + - libvirt-client + - libvirt-daemon-driver-qemu + - qemu-img + - libguestfs-tools-c + - virt-install diff --git a/roles/libvirtd/vars/Ubuntu.yml b/roles/libvirtd/vars/Ubuntu.yml new file mode 100644 index 0000000..d4cef53 --- /dev/null +++ b/roles/libvirtd/vars/Ubuntu.yml @@ -0,0 +1,7 @@ +libvirtd_packages: + - libvirt-daemon + - libvirt-daemon-system + - libvirt-clients + - qemu-utils + - libguestfs-tools + - virtinst diff --git a/roles/libvirtd/vars/default.yml b/roles/libvirtd/vars/default.yml new file mode 100644 index 0000000..3d90362 --- /dev/null +++ b/roles/libvirtd/vars/default.yml @@ -0,0 +1,5 @@ +# Fedora/CentOS/RHEL8/etc +libvirtd_packages: + - libvirt-daemon + - libvirt-client + - qemu-img diff --git a/roles/libvirtd/vars/main.yml b/roles/libvirtd/vars/main.yml new file mode 100644 index 0000000..675789f --- /dev/null +++ b/roles/libvirtd/vars/main.yml @@ -0,0 +1 @@ +# Vars for libvirtd diff --git a/roles/network_bridge/.gitignore b/roles/network_bridge/.gitignore new file mode 100644 index 0000000..81b995c --- /dev/null +++ b/roles/network_bridge/.gitignore @@ -0,0 +1,7 @@ +.*.swp +.*.swo +*.pyc +*.pyo +__pycache__/* +molecule/*/junit.xml +molecule/*/pytestdebug.log diff --git a/roles/network_bridge/README.md b/roles/network_bridge/README.md new file mode 100644 index 0000000..8030164 --- /dev/null +++ b/roles/network_bridge/README.md @@ -0,0 +1,79 @@ +network\_bridge +=========== + +Basic description for network\_bridge + +Requirements +------------ + +Ansible 2.8 or higher + +Red Hat Enterprise Linux 7 or equivalent, for Fedora based +system + +Tries to work for Ubuntu, but seems to have issues with DHCP addresses +on those hosts. + +Valid Red Hat Subscriptions + +Role Variables +-------------- + +Currently the following variables are supported: + +### General + +* `network_bridge_become` - Default: true. If this role needs administrator + privileges, then use the Ansible become functionality (based off sudo). +* `network_bridge_become_user` - Default: root. If the role uses the become + functionality for privilege escalation, then this is the name of the target + user to change to. +* `network_bridge_devices` - Default: `{}`. A dictionary that takes the form +of: +```yaml +bridge_name: + device: eth0 + nm_controlled: "no" # optional, default "yes" + onboot: "yes" # optional, default "yes" + ipaddr: 192.168.1.1 # optional, if ignored, DHCP will be set + netmask: 255.255.255.0 # required if ipaddr set, else ignored + gateway: 10.0.0.1 # required if ipaddr set, else ignored + ipv6: 1::1 # optional, no IPv6 configured if absent + dns: # optional + - 8.8.8.8 + - 8.8.4.4 +``` + +Dependencies +------------ + +To run on an Ubuntu system, the remote system will need the Python YAML +library. + +Example Playbook +---------------- + +```yaml +- hosts: network_bridge-servers + roles: + - role: oasis_roles.system.network_bridge + vars: + network_bridge_devices: + br0: + device: eth0 + ipaddr: 192.168.1.10 + netmask: 255.255.255.0 + gateway: 192.168.1.1 + br1: + device: eth1 +``` + +License +------- + +GPLv3 + +Author Information +------------------ + +Greg Hellings diff --git a/roles/network_bridge/defaults/main.yml b/roles/network_bridge/defaults/main.yml new file mode 100644 index 0000000..e687090 --- /dev/null +++ b/roles/network_bridge/defaults/main.yml @@ -0,0 +1,3 @@ +network_bridge_become: true +network_bridge_become_user: root +network_bridge_devices: {} diff --git a/roles/network_bridge/handlers/main.yml b/roles/network_bridge/handlers/main.yml new file mode 100644 index 0000000..2e751fe --- /dev/null +++ b/roles/network_bridge/handlers/main.yml @@ -0,0 +1,18 @@ +- name: restart networking + become: "{{ network_bridge_become }}" + become_user: "{{ network_bridge_become_user }}" + service: + name: "{{ item }}" + state: restarted + notify: wait for connection + loop: + - NetworkManager + - network + +- name: wait for connection + wait_for_connection: + +- name: reload netplan + become: "{{ network_bridge_become }}" + become_user: "{{ network_bridge_become_user }}" + command: netplan apply diff --git a/roles/network_bridge/meta/main.yml b/roles/network_bridge/meta/main.yml new file mode 100644 index 0000000..2204b62 --- /dev/null +++ b/roles/network_bridge/meta/main.yml @@ -0,0 +1,16 @@ +galaxy_info: + author: Greg Hellings + description: |- + Creates a network bridge and attaches specified devices to it + company: Red Hat, Inc. + license: GPLv3 + min_ansible_version: 2.8 + platforms: + - name: EL + versions: + - 7 + + galaxy_tags: + - oasis + +dependencies: [] diff --git a/roles/network_bridge/molecule/default/molecule.yml b/roles/network_bridge/molecule/default/molecule.yml new file mode 100644 index 0000000..ca45aa3 --- /dev/null +++ b/roles/network_bridge/molecule/default/molecule.yml @@ -0,0 +1,14 @@ +driver: + name: openstack +platforms: + - name: test-network_bridge +provisioner: + inventory: + group_vars: + all: + network_bridge_devices: + br0: + dns: + - 8.8.8.8 + - 8.8.4.4 + device: eth0 diff --git a/roles/network_bridge/molecule/default/tests/test_null.py b/roles/network_bridge/molecule/default/tests/test_null.py new file mode 100644 index 0000000..751049d --- /dev/null +++ b/roles/network_bridge/molecule/default/tests/test_null.py @@ -0,0 +1,8 @@ +# Without at least a file here, tests in the additional directory will not +# get picked up. If you add actual tests to this directory, then you can +# safely eliminate this file. Otherwise, it exists only to cause the tests in +# shared/tests to be discovered. +# +# Most tests should be written in the shared/tests directory so that they can +# be captured by all the scenarios. Only add tests here if there are tests +# only relevant to a particular scenario diff --git a/roles/network_bridge/molecule/shared/cleanup.yml b/roles/network_bridge/molecule/shared/cleanup.yml new file mode 100644 index 0000000..a4f5e2e --- /dev/null +++ b/roles/network_bridge/molecule/shared/cleanup.yml @@ -0,0 +1,22 @@ +- name: unregister systems + hosts: all + gather_facts: false + tasks: + - name: wait for host + wait_for_connection: + timeout: 1 + register: waiting + ignore_errors: true + + - block: + - name: fetch facts + setup: {} + + - name: do unregistration + include_role: + name: oasis_roles.system.rhsm + when: ansible_distribution == 'RedHat' + when: waiting is success + vars: + rhsm_unregister: true + rhsm_username: "{{ omit }}" diff --git a/roles/network_bridge/molecule/shared/playbook.yml b/roles/network_bridge/molecule/shared/playbook.yml new file mode 100644 index 0000000..b019499 --- /dev/null +++ b/roles/network_bridge/molecule/shared/playbook.yml @@ -0,0 +1,4 @@ +- name: converge + hosts: all + roles: + - role: network_bridge diff --git a/roles/network_bridge/molecule/shared/prepare.yml b/roles/network_bridge/molecule/shared/prepare.yml new file mode 100644 index 0000000..1fe8a47 --- /dev/null +++ b/roles/network_bridge/molecule/shared/prepare.yml @@ -0,0 +1,15 @@ +- name: register RHSM + hosts: all + roles: + - role: oasis_roles.system.rhsm + when: ansible_distribution == 'RedHat' + vars: + rhsm_username: "{{ lookup('env', 'OASIS_RHSM_USERNAME') }}" + rhsm_password: "{{ lookup('env', 'OASIS_RHSM_PASSWORD') }}" + rhsm_server_hostname: "{{ lookup('env', 'OASIS_RHSM_SERVER_HOSTNAME') }}" + rhsm_pool_ids: "{{ lookup('env', 'OASIS_RHSM_POOL_IDS') }}" + rhsm_repositories: + enabled: + - rhel-7-server-rpms + - rhel-7-server-extras-rpms + - rhel-7-server-optional-rpms diff --git a/roles/network_bridge/molecule/shared/tests/test_eth0_bridged.py b/roles/network_bridge/molecule/shared/tests/test_eth0_bridged.py new file mode 100644 index 0000000..f02d7f0 --- /dev/null +++ b/roles/network_bridge/molecule/shared/tests/test_eth0_bridged.py @@ -0,0 +1,4 @@ +def test_eth0_on_bridge(host): + with host.sudo(): + result = host.run("brctl show br0") + assert "eth0" in result.stdout diff --git a/roles/network_bridge/molecule/ubuntu/molecule.yml b/roles/network_bridge/molecule/ubuntu/molecule.yml new file mode 100644 index 0000000..cab7120 --- /dev/null +++ b/roles/network_bridge/molecule/ubuntu/molecule.yml @@ -0,0 +1,16 @@ +driver: + name: openstack +platforms: + - name: test-network_bridge-ubuntu + image: Ubuntu18.04 +provisioner: + inventory: + group_vars: + all: + network_bridge_devices: + br0: + dns: + - 8.8.8.8 + - 8.8.4.4 + device: ens3 + molecule_openstack_ci_ssh_user: ubuntu diff --git a/roles/network_bridge/molecule/ubuntu/tests/test_null.py b/roles/network_bridge/molecule/ubuntu/tests/test_null.py new file mode 100644 index 0000000..751049d --- /dev/null +++ b/roles/network_bridge/molecule/ubuntu/tests/test_null.py @@ -0,0 +1,8 @@ +# Without at least a file here, tests in the additional directory will not +# get picked up. If you add actual tests to this directory, then you can +# safely eliminate this file. Otherwise, it exists only to cause the tests in +# shared/tests to be discovered. +# +# Most tests should be written in the shared/tests directory so that they can +# be captured by all the scenarios. Only add tests here if there are tests +# only relevant to a particular scenario diff --git a/roles/network_bridge/tasks/main.yml b/roles/network_bridge/tasks/main.yml new file mode 100644 index 0000000..e5a345f --- /dev/null +++ b/roles/network_bridge/tasks/main.yml @@ -0,0 +1,28 @@ +- name: load variables + include_vars: "{{ item }}" + with_first_found: + - "{{ ansible_distribution }}-{{ ansible_distribution_version }}.yml" + - "{{ ansible_distribution }}.yml" + +- name: ensure utils are installed + become: "{{ network_bridge_become }}" + become_user: "{{ network_bridge_become_user }}" + package: + name: "{{ network_bridge_packages }}" + state: present + +- name: create NetworkManager bridges + include_tasks: network_manager.yml + loop: "{{ network_bridge_devices | dict2items }}" + loop_control: + label: "{{ bridge.key }}" + loop_var: bridge + when: network_bridge_provider == 'NetworkManager' + +- name: create netplan bridges + include_tasks: netplan.yml + loop: "{{ network_bridge_devices | dict2items }}" + loop_control: + label: "{{ bridge.key }}" + loop_var: bridge + when: network_bridge_provider == 'netplan' diff --git a/roles/network_bridge/tasks/netplan.yml b/roles/network_bridge/tasks/netplan.yml new file mode 100644 index 0000000..9dab4ac --- /dev/null +++ b/roles/network_bridge/tasks/netplan.yml @@ -0,0 +1,22 @@ +- name: write bridge to file + become: "{{ network_bridge_become }}" + become_user: "{{ network_bridge_become_user }}" + oasis_roles.system.yaml_file: + create: true + path: /etc/netplan/99-network_bridges.yaml + key: network.{{ item.key }} + value: "{{ item.value | to_json }}" + when: item.when | default(True) + loop: + - key: version + value: 2 + - key: bridges.{{ bridge.key }}.dhcp4 + value: "yes" + when: "'ipaddr' not in bridge.value" + - key: bridges.{{ bridge.key }}.interfaces + value: + - "{{ bridge.value.device }}" + - key: bridges.{{ bridge.key }}.nameservers.addresses + value: "{{ bridge.value.dns }}" + when: "'dns' in bridge.value" + #notify: reload netplan diff --git a/roles/network_bridge/tasks/network_manager.yml b/roles/network_bridge/tasks/network_manager.yml new file mode 100644 index 0000000..3edac8b --- /dev/null +++ b/roles/network_bridge/tasks/network_manager.yml @@ -0,0 +1,34 @@ +- block: + - name: create sysctl bridge config + template: + src: sysctl_bridge.conf + dest: /etc/sysctl.d/bridge.conf + owner: root + group: root + mode: "0644" + + - name: create udev rules file + template: + src: 99-bridge.rules + dest: /etc/udev/rules.d/99-bridge.rules + owner: root + group: root + mode: "0644" + + - name: set bridge in bridged connection + lineinfile: + path: "{{ network_bridge_folder }}/ifcfg-{{ bridge.value.device }}" + line: BRIDGE={{ bridge.key }} + regexp: "^BRIDGE=.*$" + notify: restart networking + + - name: create bridge connection + template: + src: ifcfg-bridge + dest: "{{ network_bridge_folder }}/ifcfg-{{ bridge.key }}" + owner: root + group: root + mode: "0644" + notify: restart networking + become: "{{ network_bridge_become }}" + become_user: "{{ network_bridge_become_user }}" diff --git a/roles/network_bridge/templates/99-bridge.rules b/roles/network_bridge/templates/99-bridge.rules new file mode 100644 index 0000000..c1ffe03 --- /dev/null +++ b/roles/network_bridge/templates/99-bridge.rules @@ -0,0 +1 @@ +ACTION=="add", SUBSYSTEM=="module", KERNEL=="bridge", RUN+="/sbin/sysctl -p /etc/sysctl.d/bridge.conf" diff --git a/roles/network_bridge/templates/ifcfg-bridge b/roles/network_bridge/templates/ifcfg-bridge new file mode 100644 index 0000000..3df2763 --- /dev/null +++ b/roles/network_bridge/templates/ifcfg-bridge @@ -0,0 +1,33 @@ +DEVICE={{ bridge.key }} +NAME={{ bridge.key }} +NM_CONTROLLED={{ bridge.nm_controlled | default("yes") }} +ONBOOT={{ bridge.onboot | default("yes") }} +TYPE=Bridge + +{% if 'ipaddr' not in bridge.value %} +# Assign address with DHCP +BOOTPROTO=dhcp +{% else %} +# Set static address +IPADDR={{ bridge.value.ipaddr }} +NETMASK={{ bridge.value.netmask }} +GATEWAY={{ bridge.value.gateway }} +{% endif %} + +{% if bridge.value.ipv6 | default(false) %} +IPV6INIT=yes +IPV6_AUTOCONF=no +IPV6ADDR={{ bridge.value.ipv6 }} +IPV6_DEFAULTGW=fe80::1%{{ bridge.key }} +{% endif %} + +{% for dns in ( bridge.value.dns | default([]) ) %} +DNS{{ loop.index }}={{ dns }} +{% endfor %} + +# Spanning Tree Protocol on or off, can cause problems with routers +# and other hardware if enabled. Be sure you know what you're doing and +# that your network is OK with it before enabling this +STP=off +# Applies only when STP is "on" +DELAY=0 diff --git a/roles/network_bridge/templates/sysctl_bridge.conf b/roles/network_bridge/templates/sysctl_bridge.conf new file mode 100644 index 0000000..c2c17ee --- /dev/null +++ b/roles/network_bridge/templates/sysctl_bridge.conf @@ -0,0 +1,3 @@ +net.bridge.bridge-nf-call-ip6tables=0 +net.bridge.bridge-nf-call-iptables=0 +net.bridge.bridge-nf-call-arptables=0 diff --git a/roles/network_bridge/vars/Fedora.yml b/roles/network_bridge/vars/Fedora.yml new file mode 100644 index 0000000..1d4e27e --- /dev/null +++ b/roles/network_bridge/vars/Fedora.yml @@ -0,0 +1,2 @@ +network_bridge_folder: /etc/sysconfig/network-scripts/ +network_bridge_provider: NetworkManager diff --git a/roles/network_bridge/vars/RedHat.yml b/roles/network_bridge/vars/RedHat.yml new file mode 100644 index 0000000..1d4e27e --- /dev/null +++ b/roles/network_bridge/vars/RedHat.yml @@ -0,0 +1,2 @@ +network_bridge_folder: /etc/sysconfig/network-scripts/ +network_bridge_provider: NetworkManager diff --git a/roles/network_bridge/vars/Ubuntu.yml b/roles/network_bridge/vars/Ubuntu.yml new file mode 100644 index 0000000..d7cda48 --- /dev/null +++ b/roles/network_bridge/vars/Ubuntu.yml @@ -0,0 +1,2 @@ +network_bridge_folder: /etc/network/interfaces +network_bridge_provider: netplan diff --git a/roles/network_bridge/vars/main.yml b/roles/network_bridge/vars/main.yml new file mode 100644 index 0000000..45abe80 --- /dev/null +++ b/roles/network_bridge/vars/main.yml @@ -0,0 +1,2 @@ +network_bridge_packages: + - bridge-utils diff --git a/roles/network_scripts/.gitignore b/roles/network_scripts/.gitignore new file mode 100644 index 0000000..81b995c --- /dev/null +++ b/roles/network_scripts/.gitignore @@ -0,0 +1,7 @@ +.*.swp +.*.swo +*.pyc +*.pyo +__pycache__/* +molecule/*/junit.xml +molecule/*/pytestdebug.log diff --git a/roles/network_scripts/README.md b/roles/network_scripts/README.md new file mode 100644 index 0000000..560056f --- /dev/null +++ b/roles/network_scripts/README.md @@ -0,0 +1,84 @@ +network\_scripts +=========== + +Basic description for network\_scripts + +Requirements +------------ + +Ansible 2.8 or higher + +Red Hat Enterprise Linux 7 or equivalent + +Valid Red Hat Subscriptions + +Role Variables +-------------- + +Currently the following variables are supported: + +### General + +* `network_scripts_become` - Default: true. If this role needs administrator + privileges, then use the Ansible become functionality (based off sudo). +* `network_scripts_become_user` - Default: root. If the role uses the become + functionality for privilege escalation, then this is the name of the target + user to change to. +* `network_scripts_dest_path` - Default: /etc/sysconfig/network-scripts. Set + this to a different path if your distro puts scripts in a different location + or if you want to use the scripts in another manner (e.g. to upload to a VM). + The directory must already exist prior to calling this role +* `network_scripts_restart` - Default: false. Set this to true if networking + should be restarted after detecting a change in the uploaded scripts. +* `network_scripts_clear_existing` - Default: false. Set this to true if you + want this role to remove existing script files in the target location that + match the passed values in the next variable. +* `network_scripts_clear_patterns` - Default: `['ifcfg-*', 'route-*']`. A list + of shell-expanded wildcard patterns for scripts to remove if `network_scripts_clear_existing` + is set to true. +* `network_scripts_nics` - Default: `[]`. A list of the network scripts to create. + Takes a form like the following: + +```yaml +network_scripts_nics: + - filename: ifcfg-eth0 + NAME: eth0 + DEVICE: eth0 + TYPE: Ethernet + BOOTPROTO: static + IPADDR: 192.168.1.10 + GATEWAY: 192.168.1.1 + NETMASK: 255.255.255.0 + DEFROUTE: !!str yes + IPV6INIT: !!str no + ONBOOT: !!str yes + DNS1: 8.8.8.8 +``` + All supported arguments are passed into the template file and match arguments + in the ifcfg syntax of the same name. Check the file `templates/ifcfg` for + any that are supported. If the option you need isn't present, open an issue + or a PR for it + +Dependencies +------------ + +None + +Example Playbook +---------------- + +```yaml +- hosts: network_scripts-servers + roles: + - role: oasis_roles.system.network_scripts +``` + +License +------- + +GPLv3 + +Author Information +------------------ + +Greg Hellings diff --git a/roles/network_scripts/defaults/main.yml b/roles/network_scripts/defaults/main.yml new file mode 100644 index 0000000..b8b61e3 --- /dev/null +++ b/roles/network_scripts/defaults/main.yml @@ -0,0 +1,9 @@ +network_scripts_become: true +network_scripts_become_user: root +network_scripts_dest_path: /etc/sysconfig/network-scripts +network_scripts_nics: [] +network_scripts_restart: false +network_scripts_clear_existing: false +network_scripts_clear_patterns: + - ifcfg-* + - route-* diff --git a/roles/network_scripts/handlers/main.yml b/roles/network_scripts/handlers/main.yml new file mode 100644 index 0000000..aa74492 --- /dev/null +++ b/roles/network_scripts/handlers/main.yml @@ -0,0 +1,5 @@ +- name: restart network + service: + name: network + state: restarted + when: network_scripts_restart | bool diff --git a/roles/network_scripts/meta/main.yml b/roles/network_scripts/meta/main.yml new file mode 100644 index 0000000..24d2a3c --- /dev/null +++ b/roles/network_scripts/meta/main.yml @@ -0,0 +1,16 @@ +galaxy_info: + author: Greg Hellings + description: |- + Installs, and optionally removes old, network configuration scripts + company: Red Hat, Inc. + license: GPLv3 + min_ansible_version: 2.8 + platforms: + - name: EL + versions: + - 7 + + galaxy_tags: + - oasis + +dependencies: [] diff --git a/roles/network_scripts/molecule/default/molecule.yml b/roles/network_scripts/molecule/default/molecule.yml new file mode 100644 index 0000000..b7e11cb --- /dev/null +++ b/roles/network_scripts/molecule/default/molecule.yml @@ -0,0 +1,5 @@ +driver: + name: docker +platforms: + - name: network_scripts + image: centos:7 diff --git a/roles/network_scripts/molecule/default/tests/test_config_correct.py b/roles/network_scripts/molecule/default/tests/test_config_correct.py new file mode 100644 index 0000000..e5dd12e --- /dev/null +++ b/roles/network_scripts/molecule/default/tests/test_config_correct.py @@ -0,0 +1,3 @@ +def test_tmp_file(host): + script = host.file("/tmp/network-scripts/ifcfg-eth0") + assert script.contains("TYPE=Ethernet") diff --git a/roles/network_scripts/molecule/shared/cleanup.yml b/roles/network_scripts/molecule/shared/cleanup.yml new file mode 100644 index 0000000..a4f5e2e --- /dev/null +++ b/roles/network_scripts/molecule/shared/cleanup.yml @@ -0,0 +1,22 @@ +- name: unregister systems + hosts: all + gather_facts: false + tasks: + - name: wait for host + wait_for_connection: + timeout: 1 + register: waiting + ignore_errors: true + + - block: + - name: fetch facts + setup: {} + + - name: do unregistration + include_role: + name: oasis_roles.system.rhsm + when: ansible_distribution == 'RedHat' + when: waiting is success + vars: + rhsm_unregister: true + rhsm_username: "{{ omit }}" diff --git a/roles/network_scripts/molecule/shared/playbook.yml b/roles/network_scripts/molecule/shared/playbook.yml new file mode 100644 index 0000000..cc5a848 --- /dev/null +++ b/roles/network_scripts/molecule/shared/playbook.yml @@ -0,0 +1,19 @@ +- name: converge + hosts: all + roles: + - role: network_scripts + vars: + network_scripts_nics: + - filename: ifcfg-eth0 + NAME: eth0 + DEVICE: eth0 + TYPE: Ethernet + BOOTPROTO: static + IPADDR: 192.168.1.10 + GATEWAY: 192.168.1.1 + NETMASK: 255.255.255.0 + DEFROUTE: !!str yes + IPV6INIT: !!str no + ONBOOT: !!str yes + DNS1: 8.8.8.8 + network_scripts_dest_path: /tmp/network-scripts/ diff --git a/roles/network_scripts/molecule/shared/prepare.yml b/roles/network_scripts/molecule/shared/prepare.yml new file mode 100644 index 0000000..e8cfbe0 --- /dev/null +++ b/roles/network_scripts/molecule/shared/prepare.yml @@ -0,0 +1,20 @@ +- name: register RHSM + hosts: all + tasks: + - name: create directory + file: + path: /tmp/network-scripts + state: directory + roles: + - role: oasis_roles.system.rhsm + when: ansible_distribution == 'RedHat' + vars: + rhsm_username: "{{ lookup('env', 'OASIS_RHSM_USERNAME') }}" + rhsm_password: "{{ lookup('env', 'OASIS_RHSM_PASSWORD') }}" + rhsm_server_hostname: "{{ lookup('env', 'OASIS_RHSM_SERVER_HOSTNAME') }}" + rhsm_pool_ids: "{{ lookup('env', 'OASIS_RHSM_POOL_IDS') }}" + rhsm_repositories: + enabled: + - rhel-7-server-rpms + - rhel-7-server-extras-rpms + - rhel-7-server-optional-rpms diff --git a/roles/network_scripts/tasks/main.yml b/roles/network_scripts/tasks/main.yml new file mode 100644 index 0000000..0c4547f --- /dev/null +++ b/roles/network_scripts/tasks/main.yml @@ -0,0 +1,41 @@ +# Optionally clear out all other files +- block: + - name: create temp directory + tempfile: + state: directory + suffix: "{{ lookup('pipe', 'date +%y.%m.%d-%H.%M.%S') }}" + register: _network_scripts_temp_dir + + - name: find files to copy + find: + path: "{{ network_scripts_dest_path }}" + patterns: "{{ network_scripts_clear_patterns }}" + register: _network_scripts_find + + - name: copy found files to temp directory + copy: + remote_src: true + src: "{{ item.path }}" + dest: "{{ _network_scripts_temp_dir.path }}" + loop: "{{ _network_scripts_find.files }}" + + - name: remove found files from source + file: + path: "{{ item.path }}" + state: absent + loop: "{{ _network_scripts_find.files }}" + when: network_scripts_clear_existing | bool + become: "{{ network_scripts_become }}" + become_user: "{{ network_scripts_become_user }}" + +- name: template ifcfg files for passed nics + become: "{{ network_scripts_become }}" + become_user: "{{ network_scripts_become_user }}" + template: + src: ifcfg + dest: "{{ network_scripts_dest_path }}/{{ item.filename }}" + owner: root + group: root + mode: "0644" + loop: "{{ network_scripts_nics }}" + notify: restart network diff --git a/roles/network_scripts/templates/ifcfg b/roles/network_scripts/templates/ifcfg new file mode 100644 index 0000000..2fbb916 --- /dev/null +++ b/roles/network_scripts/templates/ifcfg @@ -0,0 +1,81 @@ +{% if 'block' in item %} +{{ item['block']|string }} +{% endif %} +{% if 'BONDING_OPTS' in item %} +BONDING_OPTS="{{ item['BONDING_OPTS']|string }}" +{% endif %} +{% if 'BOOTPROTO' in item %} +BOOTPROTO={{ item['BOOTPROTO']|string }} +{% endif %} +{% if 'BRIDGE' in item %} +BRIDGE={{ item['BRIDGE']|string }} +{% endif %} +{% if 'BROADCAST' in item %} +BROADCAST={{ item['BROADCAST']|string }} +{% endif %} +{% if 'DEFROUTE' in item %} +DEFROUTE={{ item['DEFROUTE']|string }} +{% endif %} +{% if 'DELAY' in item %} +DELAY={{ item['DELAY']|string }} +{% endif %} +{% if 'DEVICE' in item %} +DEVICE={{ item['DEVICE']|string }} +{% endif %} +{% if 'DNS1' in item %} +DNS1={{ item['DNS1']|string }} +{% endif %} +{% if 'DNS2' in item %} +DNS2={{ item['DNS2']|string }} +{% endif %} +{% if 'GATEWAY' in item %} +GATEWAY={{ item['GATEWAY']|string }} +{% endif %} +{% if 'HWADDR' in item %} +HWADDR={{ item['HWADDR']|string }} +{% endif %} +{% if 'IPADDR' in item %} +IPADDR={{ item['IPADDR']|string }} +{% endif %} +{% if 'IPV6INIT' in item %} +IPV6INIT={{ item['IPV6INIT']|string }} +{% endif %} +{% if 'MASTER' in item %} +MASTER={{ item['MASTER']|string }} +{% endif %} +{% if 'NAME' in item %} +NAME={{ item['NAME']|string }} +{% endif %} +{% if 'NETMASK' in item %} +NETMASK={{ item['NETMASK']|string }} +{% endif %} +{% if 'NETWORK' in item %} +NETWORK={{ item['NETWORK']|string }} +{% endif %} +{% if 'NM_CONTROLLED' in item %} +NM_CONTROLLED={{ item['NM_CONTROLLED']|string }} +{% endif %} +{% if 'ONBOOT' in item %} +ONBOOT={{ item['ONBOOT']|string }} +{% endif %} +{% if 'PEERDNS' in item %} +PEERDNS={{ item['PEERDNS']|string }} +{% endif %} +{% if 'PREFIX' in item %} +PREFIX={{ item['PREFIX']|string }} +{% endif %} +{% if 'SLAVE' in item %} +SLAVE={{ item['SLAVE']|string }} +{% endif %} +{% if 'TYPE' in item %} +TYPE={{ item['TYPE']|string }} +{% endif %} +{% if 'USERCTL' in item %} +USERCTL={{ item['USERCTL']|string }} +{% endif %} +{% if 'UUID' in item %} +UUID={{ item['UUID']|string }} +{% endif %} +{% if 'ZONE' in item %} +ZONE={{ item['ZONE']|string }} +{% endif %} diff --git a/roles/network_scripts/vars/main.yml b/roles/network_scripts/vars/main.yml new file mode 100644 index 0000000..93af81d --- /dev/null +++ b/roles/network_scripts/vars/main.yml @@ -0,0 +1 @@ +# Vars for network_scripts