Skip to content

Commit

Permalink
Merge pull request #6 from trustification/molecule
Browse files Browse the repository at this point in the history
Molecule infrastructure for remote test
  • Loading branch information
desmax74 authored Aug 29, 2024
2 parents bf8f9cc + 9196887 commit f04c3aa
Show file tree
Hide file tree
Showing 18 changed files with 403 additions and 23 deletions.
16 changes: 10 additions & 6 deletions .github/workflows/ansible-lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@ jobs:
ansible-lint:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run ansible-lint
run: |
pip install -r requirements-testing.txt
ansible-lint -v
- name: Checkout code
uses: actions/checkout@v4
- name: Run ansible-lint
run: |
python -m pip install --upgrade pip
pip install -r requirements-testing.txt
ansible-galaxy install -r requirements.yml
# we also lint the molecule setup
ansible-galaxy install -r molecule/requirements.yml
ansible-lint -v
12 changes: 6 additions & 6 deletions .github/workflows/ansible-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ jobs:
sanity-test:
runs-on: ubuntu-latest
steps:
- name: Perform testing
uses: ansible-community/ansible-test-gh-action@release/v1
with:
testing-type: sanity
origin-python-version: 3.11
ansible-core-version: stable-2.14
- name: Perform testing
uses: ansible-community/ansible-test-gh-action@release/v1
with:
testing-type: sanity
python-version: 3.11
ansible-core-version: stable-2.14

43 changes: 43 additions & 0 deletions .github/workflows/molecule.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#name: Test the collection using Ansible Molecule
#on:
# push:
# branches:
# - 'main'
# pull_request:
#
#jobs:
# molecule:
# runs-on: testing-farm
# steps:
# - name: Checkout code
# uses: actions/checkout@v4
# - name: Install dependencies
# run: |
# python -m venv venv
# source venv/bin/activate
# python -m pip install --upgrade pip
# pip install -r requirements-testing.txt
# ansible-galaxy install -r requirements.yml
# ansible-galaxy install -r molecule/requirements.yml
# - name: Image mirroring
# run: |
# sed -f vm-testing/dev-images.sed -i roles/tas_single_node/defaults/main.yml
# - name: Run molecule
# env:
# TPA_SINGLE_NODE_REGISTRY_USERNAME: ${{ secrets.TPA_SINGLE_NODE_REGISTRY_USERNAME }}
# TPA_SINGLE_NODE_REGISTRY_PASSWORD: ${{ secrets.TPA_SINGLE_NODE_REGISTRY_PASSWORD }}
# TESTING_FARM_API_TOKEN: ${{ secrets.TESTING_FARM_API_TOKEN }}
# run: |
# eval $(ssh-agent -s) # testing-farm CLI right now mandates that ssh-agent is running
# source venv/bin/activate
# # ensure we wipe out any data potentially still on the runner from previous run
# molecule reset
# # NOTE: for now we don't run "molecule test", because we don't have proper idempotence yet
# molecule -v test --scenario-name default
# - name: Destroy molecule infrastructure
# env:
# TESTING_FARM_API_TOKEN: ${{ secrets.TESTING_FARM_API_TOKEN }}
# if: always()
# run:
# source venv/bin/activate
# molecule destroy
2 changes: 1 addition & 1 deletion ansible.cfg
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[defaults]
inventory = ./inventory.ini
inventory = ./vm-testing/rhel9-vm/inventory.ini
host_key_checking = False
10 changes: 5 additions & 5 deletions galaxy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@ version: 1.0.0

readme: README.md
authors:
- Red Hat
description: TODO
- Max Dessì <[email protected]>
description: Install and configure RHTPA, a downstream redistribution of the Trustification project.
license_file: Apache-2.0
tags: [rhtpa, tpa, trusted profile analyzer, security, application, tools]
# NOTE: when updating, also update dependencies in requirements.yml
dependencies:
containers.podman: ">=1.15.0"
containers.podman: ">=1.15.4"
repository: https://github.com/trustification/trustification-ansible
documentation: http://TODO.com
homepage: https://TODO.com
homepage: https://github.com/trustification/trustification-ansible
issues: https://github.com/trustification/trustification-ansible/issues

build_ignore: [.ansible-lint, .github, .gitignore, .vscode, requirements-testing.txt, vm-testing, .idea]
build_ignore: [.ansible-lint, .github, .gitignore, .vscode, ansible-navigator.log, molecule, requirements-testing.txt, vm-testing, .idea]
56 changes: 56 additions & 0 deletions molecule/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Molecule Testing

This directory contains tests that run using [Ansible Molecule](https://ansible.readthedocs.io/projects/molecule/). By default, [testing-farm](https://docs.testing-farm.io/) is used as the VM provider.

## Using Molecule

### Setup

First of all, create a local Python virtualenv and install molecule and dependencies. In the root directory of this repository, run:

```shell
python3 -m venv venv
source venv/bin/activate
pip3 install -r requirements-testing.txt
ansible-galaxy install -r requirements.yml
ansible-galaxy install -r molecule/requirements.yml
sed -f vm-testing/dev-images.sed -i roles/tas_single_node/defaults/main.yml
```

Next, export following values in your shell environment:

```
export RH_SUBSCRIPTION_USERNAME==<your-username-for-registry.redhat.io>
export RH_SUBSCRIPTION_PASSWORD=<your-password-for-registry.redhat.io>
# get a token at https://testing-farm.io/tokens/
# you can sign in with a Fedora account, but it must be "CLA+1" (signed CLA and a member of at least 1 other group)
export TESTING_FARM_API_TOKEN=<your-testing-farm-token>
```

Last, ask the Testing Farm team to allow your IP to run Molecule tests (run `curl icanhazip.com` to get your IP address).

### Usage

Molecule has [multiple commands](https://ansible.readthedocs.io/projects/molecule/usage/#molecule-list). The most important are:

* `molecule create` - creates the VM and stops
* `molecule destroy` - destroys the VM and stops
* `molecule reset` - wipe out locally cached data about created VMs without destroying them
* `molecule converge` - runs `create` (skips if VM is already created) and then converges (applies `molecule/<scenario>/converge.yml` which applies the role)
* `molecule verify` - run verification steps on a provisioned VM (expects that you already converged it)
* `molecule idempotence` - converges twice; fails if there are `changed` tasks on the second run
* `molecule test` - runs `converge`, `idempotence`, `verify` and `destroy`

When doing local development, it's most useful to run `molecule converge` until your feature works fine and then run full `molecule test` on a clean VM to ensure everything is correct.

Note that each of the above commands can be followed by scenario name, e.g. `molecule converge <scenario>` to test a specific scenario (`default` is the default scenario). Scenarios are subdirectories of this directory.

## Testing Farm/Molecule integration specifics

Testing Farm VMs expire and are destroyed automatically after a period of time (Testing Farm default is 30 minutes). This can be changed in the `molecule.yml` of an individual scenario (e.g. `molecule/default/molecule.yml`). To change this value, set the `duration` attribute of a `platforms` item to number of minutes after which the VM will be destroyed.

When a VM expires, you have to run `molecule reset` to remove locally cached information about it and start over.

## Using a different VM provider

In order to use a different VM provider, one would have to replace the `molecule/<scenario>/create.yml` and `molecule/<scenario>/destroy.yml` and/or provide an [existing molecule provider](https://github.com/ansible-community/molecule-plugins) in `molecule/<scenario>/molecule.yml`.
20 changes: 20 additions & 0 deletions molecule/default/converge.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
- name: Converge
hosts: molecule
gather_facts: true
vars_files:
- vars/vars.yml
tasks:
- name: Error out if registry username is not set
ansible.builtin.fail:
msg: "Username for registry.redhat.io is not set, please provide it via TPA_SINGLE_NODE_REGISTRY_USERNAME env variable"
when: tpa_single_node_registry_username == ""

- name: Error out if registry password is not set
ansible.builtin.fail:
msg: "Password for registry.redhat.io is not set, please provide it via TPA_SINGLE_NODE_REGISTRY_PASSWORD env variable"
when: tpa_single_node_registry_password == ""

- name: Apply tpa_single_node role
ansible.builtin.include_role:
name: tpa_single_node
137 changes: 137 additions & 0 deletions molecule/default/create.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
---
# TODO: improve idempotency and error handling throughout this file,
# specifically for the testing-farm call
- name: Create
hosts: localhost
connection: local
gather_facts: false
vars:
debug_outputs: true
molecule_inventory:
all:
children:
molecule:
hosts: {}
tasks:
- name: Inform user we're about to create the VM
ansible.builtin.debug:
msg: Creating Testing Farm VM instance

- name: Define private key path
ansible.builtin.set_fact:
private_key_path: "{{ lookup('env', 'MOLECULE_EPHEMERAL_DIRECTORY') }}/id_rsa"

- name: "Generate local key pair {{ private_key_path }}"
community.crypto.openssh_keypair:
path: "{{ private_key_path }}"
type: rsa
size: 4096
regenerate: never
backend: cryptography
private_key_format: pkcs1
register: local_keypair

- name: Create the VM using testing-farm
ansible.builtin.command:
cmd: >-
testing-farm reserve
--compose {{ item.compose }}
--arch {{ item.arch | default("x86_64", true) }}
--duration {{ item.duration | default(30, true) }}
--ssh-public-key {{ private_key_path }}.pub
--no-autoconnect
register: tf_results
with_items: "{{ molecule_yml.platforms }}"
changed_when: true
async: 500
poll: 5

# - name: Testing output
# ansible.builtin.command:
# cmd: "echo '0acccef6-7ce6-4bb6-a0b4-3428762aa3ae [email protected]'"
# register: tf_results
# with_items: "{{ molecule_yml.platforms }}"
# changed_when: true

- name: Debug testing-farm results
ansible.builtin.debug:
msg: "{{ tf_results }}"
when: debug_outputs

- name: Initialize variable for parsed testing-farm results
ansible.builtin.set_fact:
tf_parsed_results: []

- name: Parse testing-farm result
ansible.builtin.include_tasks:
file: "tasks/_parse_tf_results.yml"
with_items: "{{ tf_results.results }}"

- name: Debug parsed testing-farm results
ansible.builtin.debug:
msg: "{{ tf_parsed_results }}"
when: debug_outputs

# TODO: idempotency
- name: Populate instance config dict # noqa jinja
ansible.builtin.set_fact:
instance_conf_dict: {
'instance': "{{ item.uuid }}",
'address': "{{ item.ip }}",
# TODO: testing-farm is giving us root by default, ideally we would get non-root to get conditions closer to prod environments
'user': "{{ item.username }}",
'port': "22", # TODO: get it dynamically
'identity_file': "{{ private_key_path }}",
}
with_items: "{{ tf_parsed_results }}"
register: instance_config_dict

- name: Convert instance config dict to a list
ansible.builtin.set_fact:
instance_conf: "{{ instance_config_dict.results | map(attribute='ansible_facts.instance_conf_dict') | list }}"

- name: Debug instance_conf
ansible.builtin.debug:
msg: "{{ instance_conf }}"
when: debug_outputs

- name: Dump instance config
ansible.builtin.copy:
content: |
# Molecule managed
{{ instance_conf | to_json | from_json | to_yaml }}
dest: "{{ molecule_instance_config }}"
mode: "0600"

- name: Add VM to molecule_inventory
vars:
inventory_partial_yaml: |
all:
children:
molecule:
hosts:
"{{ item.address }}":
ansible_user: {{ item.user }}
ansible_ssh_private_key_file: {{ private_key_path }}
ansible.builtin.set_fact:
molecule_inventory: >
{{ molecule_inventory | combine(inventory_partial_yaml | from_yaml, recursive=true) }}
loop: "{{ instance_conf }}"

- name: Dump molecule_inventory
ansible.builtin.copy:
content: |
{{ molecule_inventory | to_yaml }}
dest: "{{ molecule_ephemeral_directory }}/inventory/molecule_inventory.yml"
mode: "0600"

- name: Force inventory refresh
ansible.builtin.meta: refresh_inventory

- name: Fail if molecule group is missing
ansible.builtin.assert:
that: "'molecule' in groups"
fail_msg: |
molecule group was not found inside inventory groups: {{ groups }}
run_once: true # noqa: run-once[task]
48 changes: 48 additions & 0 deletions molecule/default/destroy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
# TODO: improve idempotency and error handling throughout this file,
# specifically for the testing-farm call
- name: Destroy
hosts: localhost
connection: local
gather_facts: false
tasks:
- name: Load instance config
ansible.builtin.set_fact:
instance_configs: "{{ (lookup('file', molecule_instance_config, errors='ignore') or '{}') | from_yaml }}"

- name: Debug instance config
ansible.builtin.debug:
msg: "{{ instance_configs }}"

- name: Cancel testing-farm instances
ansible.builtin.command:
cmd: "testing-farm cancel {{ item['instance'] }}"
with_items: "{{ instance_configs }}"
changed_when: true
failed_when: false
register: cancel_results

- name: Find out if some cancel requests failed
ansible.builtin.set_fact:
cancel_failed:
"{{ cancel_results.results | selectattr('rc', 'ne', 0) | rejectattr('stdout_lines', 'match', '.* already finished.*') }}"

- name: Fail if some cancel requests failed
ansible.builtin.fail:
msg: "Failed to cancel some requests: {{ cancel_failed }}"
when: cancel_failed | length > 0

- name: Dump instance config
ansible.builtin.copy:
content: "{{ {} | to_yaml }}"
dest: "{{ molecule_instance_config }}"
mode: "0600"

- name: Remove dynamic molecule inventory
hosts: localhost
gather_facts: false
tasks:
- name: Remove dynamic inventory file
ansible.builtin.file:
path: "{{ molecule_ephemeral_directory }}/inventory/molecule_inventory.yml"
state: absent
16 changes: 16 additions & 0 deletions molecule/default/molecule.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
dependency:
name: galaxy
options:
requirements-file: molecule/requirements.yml
driver:
name: default
platforms:
- name: instance
compose: CentOS-Stream-9
arch: x86_64
duration: 60
provisioner:
name: ansible
env:
ANSIBLE_ROLES_PATH: "../../roles"
Loading

0 comments on commit f04c3aa

Please sign in to comment.