From 80d6d66f43bbec437e32537df31b97f7b84361d2 Mon Sep 17 00:00:00 2001 From: Aaron Guise Date: Sat, 6 Jan 2024 23:02:10 +1300 Subject: [PATCH] Initial Import --- README.md | 55 +++++++++++++++++++ cookiecutter.json | 14 +++++ hooks/post_gen_project.py | 9 +++ .../.woodpecker/cron.yml | 28 ++++++++++ .../.woodpecker/lint.yml | 43 +++++++++++++++ .../.woodpecker/test.yml | 46 ++++++++++++++++ .../.woodpecker/z.ntfy-cron.yml | 16 ++++++ .../.woodpecker/z.ntfy.yml | 20 +++++++ .../.woodpecker/zz.ntfy-cron-failed.yml | 16 ++++++ {{ cookiecutter.role_name }}/Makefile | 36 ++++++++++++ {{ cookiecutter.role_name }}/README.md | 35 ++++++++++++ .../defaults/main.yml | 1 + {{ cookiecutter.role_name }}/files/keep | 0 .../handlers/main.yml | 0 {{ cookiecutter.role_name }}/meta/main.yml | 17 ++++++ .../molecule/default/converge.yml | 7 +++ .../molecule/default/molecule.yml | 18 ++++++ .../molecule/default/verify.yml | 6 ++ .../molecule/requirements.yml | 6 ++ .../molecule/yamllint.yml | 25 +++++++++ {{ cookiecutter.role_name }}/tasks/main.yml | 0 {{ cookiecutter.role_name }}/templates/keep | 0 {{ cookiecutter.role_name }}/vars/main.yml | 1 + 23 files changed, 399 insertions(+) create mode 100644 README.md create mode 100644 cookiecutter.json create mode 100644 hooks/post_gen_project.py create mode 100644 {{ cookiecutter.role_name }}/.woodpecker/cron.yml create mode 100644 {{ cookiecutter.role_name }}/.woodpecker/lint.yml create mode 100644 {{ cookiecutter.role_name }}/.woodpecker/test.yml create mode 100644 {{ cookiecutter.role_name }}/.woodpecker/z.ntfy-cron.yml create mode 100644 {{ cookiecutter.role_name }}/.woodpecker/z.ntfy.yml create mode 100644 {{ cookiecutter.role_name }}/.woodpecker/zz.ntfy-cron-failed.yml create mode 100644 {{ cookiecutter.role_name }}/Makefile create mode 100644 {{ cookiecutter.role_name }}/README.md create mode 100644 {{ cookiecutter.role_name }}/defaults/main.yml create mode 100644 {{ cookiecutter.role_name }}/files/keep create mode 100644 {{ cookiecutter.role_name }}/handlers/main.yml create mode 100644 {{ cookiecutter.role_name }}/meta/main.yml create mode 100644 {{ cookiecutter.role_name }}/molecule/default/converge.yml create mode 100644 {{ cookiecutter.role_name }}/molecule/default/molecule.yml create mode 100644 {{ cookiecutter.role_name }}/molecule/default/verify.yml create mode 100644 {{ cookiecutter.role_name }}/molecule/requirements.yml create mode 100644 {{ cookiecutter.role_name }}/molecule/yamllint.yml create mode 100644 {{ cookiecutter.role_name }}/tasks/main.yml create mode 100644 {{ cookiecutter.role_name }}/templates/keep create mode 100644 {{ cookiecutter.role_name }}/vars/main.yml diff --git a/README.md b/README.md new file mode 100644 index 0000000..56215c7 --- /dev/null +++ b/README.md @@ -0,0 +1,55 @@ +# Ansible Role Cookiecutter # + +This is a [cookiecutter project] for creating [Ansible roles]. It includes tests using [Molecule] and [YAML Lint] to work with [Azure Pipelines]. + +To use this, first install `cookiecutter`, then run `cookiecutter git+https://hub.cybercinch.nz/guisea/cookiecutter-ansible-role` and answer the prompts. + +## Development Workflow ## + +Molecule uses Docker by default to spin up local containers for testing. I have created [several containers] work well for testing Ansible roles. Feel free to use them or change to your own in `molecule/default/molecule.yml`. + +To test your role, you first need to install Molecule by running `pip install molecule`, or using your package manager of choice. You may also `pip install molecule[docker]` to also install the `docker` Python library, or `pip install molecule[lint]` to install `ansible-lint`, `yamllint`, and `flake8`. + +Next, run `molecule test`. This will run the entire default test scenario, which creates a test container, runs the linters, runs the role twice, then destroys the container. + +I frequently find I want to interact with the test instance rather than destroying it. To create a test instance, run your role, then leave the container running, run `molecule converge`. You can then interact with the container using `molecule login`. To rerun your role against the existing container, just run `molecule converge` again. + +Once you are done with the container, run `molecule destroy` to remove it. + +### Customizing the Test ### + +The default scenario runs `molecule/default/converge.yml`. You can customize this playbook to suit your needs. + +The `molecule.yml` file is setup to accept three environment variables: + +`MOLECULE_DISTRIBUTION`: controls the distribution to test against +`MOLECULE_COMMAND`: the command to run inside the container +`MOLECULE_PLAYBOOK`: the name of the playbook to run + +Valid values for `MOLECULE_DISTRIBUTION` based on how I name my test containers are: + - centos7 + - centos8 (the default) + - ubuntu16 + - ubuntu18 + - ubuntu20 + - debian9 + - debian10 + - fedora30 + - fedora31 + +For example, to test your role on Debian 10, run `env MOLECULE_DISTRIBUTION=debian10 molecule test`. + + +## Thanks ## + +A special thank you to [Jeff Geerling] for being a trailblazer with Molecule (and Ansible role testing in general). I would be lost without his amazing work. + +[cookiecutter project]: https://github.com/audreyr/cookiecutter +[Ansible roles]:https://docs.ansible.com/ansible/latest/user_guide/playbooks_reuse_roles.html +[molecule]: https://molecule.readthedocs.io +[Ansible Lint]: https://docs.ansible.com/ansible-lint/ +[YAML Lint]:https://yamllint.readthedocs.io/en/stable/ +[Azure Pipelines]: https://dev.azure.com +[several containers]: https://quay.io/user/samdoran +[Jeff Geerling]: https://www.jeffgeerling.com/blog/2018/testing-your-ansible-roles-molecule +[linting behavior changed]: https://molecule.readthedocs.io/en/latest/configuration.html#lint diff --git a/cookiecutter.json b/cookiecutter.json new file mode 100644 index 0000000..4fd9be8 --- /dev/null +++ b/cookiecutter.json @@ -0,0 +1,14 @@ +{ + "role_title": "{{ cookiecutter.role_title }}", + "role_name": "{{ cookiecutter.role_title | lower | replace(' ','_') }}", + "repo_name": "ansible-role-{{ cookiecutter.role_name }}", + "branch_name": "main", + "author": "Aaron Guise", + "short_description": "This role does amazing things", + "company": "Acme, Inc.", + "galaxy_username": "{{ cookiecutter.author | lower | replace(' ', '') }}", + "azp_username": "{{ cookiecutter.author | lower | replace(' ', '') }}", + "azp_branch": "main", + "license": "Apache 2.0", + "min_ansible_version": "2.9" +} diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py new file mode 100644 index 0000000..18269f1 --- /dev/null +++ b/hooks/post_gen_project.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python + +import os + +for dirpath, dirs, files in os.walk(os.getcwd()): + if 'keep' in files: + for file in files: + path = os.path.join(dirpath, file) + os.unlink(path) diff --git a/{{ cookiecutter.role_name }}/.woodpecker/cron.yml b/{{ cookiecutter.role_name }}/.woodpecker/cron.yml new file mode 100644 index 0000000..c94f015 --- /dev/null +++ b/{{ cookiecutter.role_name }}/.woodpecker/cron.yml @@ -0,0 +1,28 @@ +matrix: + include: + - MOLECULE_DISTRO: centos7 + - MOLECULE_DISTRO: almalinux8 + +clone: + git: + image: woodpeckerci/plugin-git + settings: + recursive: true + submodule_update_remote: true +when: + event: [ cron ] + +steps: + test: + name: Test on ${MOLECULE_DISTRO} + image: guisea/ansible-molecule + pull: true + environment: + PY_COLORS: '1' + ANSIBLE_FORCE_COLOR: '1' + volumes: + - /var/run/docker.sock:/var/run/docker.sock + commands: + - molecule test --scenario-name ${MOLECULE_SCENARIO:-default} + when: + event: [ cron ] diff --git a/{{ cookiecutter.role_name }}/.woodpecker/lint.yml b/{{ cookiecutter.role_name }}/.woodpecker/lint.yml new file mode 100644 index 0000000..ca629f6 --- /dev/null +++ b/{{ cookiecutter.role_name }}/.woodpecker/lint.yml @@ -0,0 +1,43 @@ +clone: + git: + image: woodpeckerci/plugin-git + settings: + recursive: true + submodule_update_remote: true +when: + event: [ push, manual ] + +steps: + ansible-lint: + group: test + name: "Lint: Ansible-lint" + image: guisea/ansible-molecule + environment: + PY_COLORS: '1' + ANSIBLE_FORCE_COLOR: '1' + commands: + - ansible-lint -c ".ansible-lint" + when: + event: [ push, manual ] + yamllint: + group: test + name: "Lint: Yamllint" + image: guisea/ansible-molecule + commands: + - yamllint -f colored . + when: + event: [ push, manual ] + ntfy: + image: codeberg.org/l-x/woodpecker-ntfy + settings: + url: https://ntfy.cybercinch.nz/ci-status + title: "Lint failed for ${CI_REPO_NAME}" + priority: urgent + icon: https://woodpecker-ci.org/img/logo.svg + tags: robot,warning,rotating_light,${CI_BUILD_EVENT},${CI_REPO_NAME} + message: > + 📝 Commit by ${CI_COMMIT_AUTHOR} on ${CI_COMMIT_BRANCH}: + ${CI_COMMIT_MESSAGE} + when: + event: [ push, manual ] + status: [ failure ] diff --git a/{{ cookiecutter.role_name }}/.woodpecker/test.yml b/{{ cookiecutter.role_name }}/.woodpecker/test.yml new file mode 100644 index 0000000..bbeea2b --- /dev/null +++ b/{{ cookiecutter.role_name }}/.woodpecker/test.yml @@ -0,0 +1,46 @@ +matrix: + include: + # - MOLECULE_DISTRO: almalinux8 + - MOLECULE_DISTRO: almalinux9 + +clone: + git: + image: woodpeckerci/plugin-git + settings: + recursive: true + submodule_update_remote: true +when: + event: [ push, manual ] + +steps: + test: + name: Test on ${MOLECULE_DISTRO} + image: guisea/ansible-molecule + pull: true + environment: + PY_COLORS: '1' + ANSIBLE_FORCE_COLOR: '1' + volumes: + - /var/run/docker.sock:/var/run/docker.sock + commands: + - molecule test --scenario-name ${MOLECULE_SCENARIO:-default} + when: + event: + - push + - manual + ntfy: + image: codeberg.org/l-x/woodpecker-ntfy + settings: + url: https://ntfy.cybercinch.nz/ci-status + title: "Test failed for ${CI_REPO_NAME} - Distro: ${MOLECULE_DISTRO} Scenario: ${MOLECULE_SCENARIO:-default}" + priority: urgent + icon: https://woodpecker-ci.org/img/logo.svg + tags: robot,warning,rotating_light,${CI_BUILD_EVENT},${CI_REPO_NAME} + message: > + 📝 Commit by ${CI_COMMIT_AUTHOR} on ${CI_COMMIT_BRANCH}: + ${CI_COMMIT_MESSAGE} + when: + event: [ push, manual ] + status: [ failure ] +depends_on: + - lint diff --git a/{{ cookiecutter.role_name }}/.woodpecker/z.ntfy-cron.yml b/{{ cookiecutter.role_name }}/.woodpecker/z.ntfy-cron.yml new file mode 100644 index 0000000..ac77a50 --- /dev/null +++ b/{{ cookiecutter.role_name }}/.woodpecker/z.ntfy-cron.yml @@ -0,0 +1,16 @@ +skip_clone: true + +steps: + ntfy-success: + image: codeberg.org/l-x/woodpecker-ntfy + settings: + url: https://ntfy.cybercinch.nz/ci-status + title: Build succeeded on ${CI_REPO_NAME} + priority: urgent + icon: https://woodpecker-ci.org/img/logo.svg + tags: robot,white_check_mark,${CI_BUILD_EVENT},${CI_REPO_NAME} + message: > + Test success when run by cron for ${CI_REPO_NAME}. +depends_on: + - "cron" +runs_on: [ success ] diff --git a/{{ cookiecutter.role_name }}/.woodpecker/z.ntfy.yml b/{{ cookiecutter.role_name }}/.woodpecker/z.ntfy.yml new file mode 100644 index 0000000..296d39e --- /dev/null +++ b/{{ cookiecutter.role_name }}/.woodpecker/z.ntfy.yml @@ -0,0 +1,20 @@ +skip_clone: true + +steps: + ntfy: + image: codeberg.org/l-x/woodpecker-ntfy + settings: + url: https://ntfy.cybercinch.nz/ci-status + title: "Build completed for ${CI_REPO_NAME}" + priority: urgent + icon: https://woodpecker-ci.org/img/logo.svg + tags: robot,tada,white_check_mark,${CI_BUILD_EVENT},${CI_REPO_NAME} + message: > + 📝 Commit by ${CI_COMMIT_AUTHOR} on ${CI_COMMIT_BRANCH}: + ${CI_COMMIT_MESSAGE} + when: + event: [ push, manual ] + status: [ success ] +depends_on: + - lint + - test diff --git a/{{ cookiecutter.role_name }}/.woodpecker/zz.ntfy-cron-failed.yml b/{{ cookiecutter.role_name }}/.woodpecker/zz.ntfy-cron-failed.yml new file mode 100644 index 0000000..ffe3f43 --- /dev/null +++ b/{{ cookiecutter.role_name }}/.woodpecker/zz.ntfy-cron-failed.yml @@ -0,0 +1,16 @@ +skip_clone: true + +steps: + ntfy-failed: + image: codeberg.org/l-x/woodpecker-ntfy + settings: + url: https://ntfy.cybercinch.nz/ci-status + title: Build failed on ${CI_REPO_NAME} + priority: urgent + icon: https://woodpecker-ci.org/img/logo.svg + tags: robot,rotating_light,no_entry,${CI_BUILD_EVENT},${CI_REPO_NAME} + message: > + Test failed when run by cron for ${CI_REPO_NAME}. +depends_on: + - "cron" +runs_on: [ failure ] diff --git a/{{ cookiecutter.role_name }}/Makefile b/{{ cookiecutter.role_name }}/Makefile new file mode 100644 index 0000000..49e0321 --- /dev/null +++ b/{{ cookiecutter.role_name }}/Makefile @@ -0,0 +1,36 @@ +.PHONY: clean virtualenv lint test docker dist dist-upload + +clean: + find . -name '*.py[co]' -delete + +virtualenv: + virtualenv --prompt '|> ansible-role-common <| ' .venv + .venv/bin/pip install --upgrade pip + .venv/bin/pip install -r requirements.txt + .venv/bin/ansible-galaxy collection install -r requirements.yml + @echo + @echo "VirtualENV Setup Complete. Now run: source .venv/bin/activate" + @echo + +test: + for distro in almalinux9 ; do \ + MOLECULE_DISTRO=$$distro molecule test --all ; \ + done + +lint: + @echo "Linting with Ansible-lint" + @echo + ansible-lint -c ".ansible-lint" --exclude ".venv" + @echo + @echo "Linting with Yamllint" + @echo + yamllint . + @echo + +dist: clean + rm -rf dist/* + python setup.py sdist + python setup.py bdist_wheel + +dist-upload: + twine upload dist/* diff --git a/{{ cookiecutter.role_name }}/README.md b/{{ cookiecutter.role_name }}/README.md new file mode 100644 index 0000000..cc79685 --- /dev/null +++ b/{{ cookiecutter.role_name }}/README.md @@ -0,0 +1,35 @@ +{{ cookiecutter.role_title | title }} +========= +[![Galaxy](https://img.shields.io/badge/galaxy-{{ cookiecutter.galaxy_username }}.{{ cookiecutter.role_name }}-blue.svg?style=flat)](https://galaxy.ansible.com/{{ cookiecutter.galaxy_username }}/{{ cookiecutter.role_name }}) +[![Build Status](https://dev.azure.com/{{ cookiecutter.azp_username }}/{{ cookiecutter.repo_name }}/_apis/build/status/CI?branchName={{ cookiecutter.azp_branch }})](https://dev.azure.com/{{ cookiecutter.azp_username }}/{{ cookiecutter.repo_name }}/_build/latest?definitionId=3&branchName={{ cookiecutter.azp_branch }}) +A brief description of the role goes here. + +Requirements +------------ + +Any pre-requisites that may not be covered by Ansible itself or the role should be mentioned here. For instance, if the role uses the EC2 module, it may be a good idea to mention in this section that the boto package is required. + +Role Variables +-------------- + +| Name | Default Value | Description | +|-------------------|---------------------|----------------------| +| `` | `` | | + + +Dependencies +------------ + +A list of other roles hosted on Galaxy should go here, plus any details in regards to parameters that may need to be set for other roles, or variables that are used from other roles. + +Example Playbook +---------------- + + - hosts: all + roles: + - {{ cookiecutter.galaxy_username }}.{{ cookiecutter.role_name }} + +License +------- + +{{ cookiecutter.license }} diff --git a/{{ cookiecutter.role_name }}/defaults/main.yml b/{{ cookiecutter.role_name }}/defaults/main.yml new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/{{ cookiecutter.role_name }}/defaults/main.yml @@ -0,0 +1 @@ + diff --git a/{{ cookiecutter.role_name }}/files/keep b/{{ cookiecutter.role_name }}/files/keep new file mode 100644 index 0000000..e69de29 diff --git a/{{ cookiecutter.role_name }}/handlers/main.yml b/{{ cookiecutter.role_name }}/handlers/main.yml new file mode 100644 index 0000000..e69de29 diff --git a/{{ cookiecutter.role_name }}/meta/main.yml b/{{ cookiecutter.role_name }}/meta/main.yml new file mode 100644 index 0000000..d1a8aeb --- /dev/null +++ b/{{ cookiecutter.role_name }}/meta/main.yml @@ -0,0 +1,17 @@ +galaxy_info: + role_name: {{ cookiecutter.role_name | lower | replace(' ', '_') }} + author: {{ cookiecutter.author }} + description: {{ cookiecutter.short_description }} + company: {{ cookiecutter.company }} + license: {{ cookiecutter.license }} + min_ansible_version: {{ cookiecutter.min_ansible_version }} + + platforms: + - name: EL + versions: + - 8 + + galaxy_tags: + - system + +dependencies: [] diff --git a/{{ cookiecutter.role_name }}/molecule/default/converge.yml b/{{ cookiecutter.role_name }}/molecule/default/converge.yml new file mode 100644 index 0000000..aac252a --- /dev/null +++ b/{{ cookiecutter.role_name }}/molecule/default/converge.yml @@ -0,0 +1,7 @@ +--- +- name: Converge + hosts: all + tasks: + - name: "Include common" + include_role: + name: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') | basename }}" diff --git a/{{ cookiecutter.role_name }}/molecule/default/molecule.yml b/{{ cookiecutter.role_name }}/molecule/default/molecule.yml new file mode 100644 index 0000000..6f8aa0e --- /dev/null +++ b/{{ cookiecutter.role_name }}/molecule/default/molecule.yml @@ -0,0 +1,18 @@ +--- +dependency: + name: galaxy +driver: + name: docker +platforms: + - name: {{ cookiecutter.role_name }}-test-${MOLECULE_DISTRO:-almalinux8} + image: "cybercinch/docker-${MOLECULE_DISTRO:-almalinux8}-ansible:latest" + command: ${MOLECULE_DOCKER_COMMAND:-""} + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup:rw + cgroupns_mode: host + privileged: true + pre_build_image: true +provisioner: + name: ansible + env: + MOLECULE_NO_LOG: true \ No newline at end of file diff --git a/{{ cookiecutter.role_name }}/molecule/default/verify.yml b/{{ cookiecutter.role_name }}/molecule/default/verify.yml new file mode 100644 index 0000000..77f75a6 --- /dev/null +++ b/{{ cookiecutter.role_name }}/molecule/default/verify.yml @@ -0,0 +1,6 @@ +- name: Verify role + hosts: all + become: yes + gather_facts: no + + tasks: [] diff --git a/{{ cookiecutter.role_name }}/molecule/requirements.yml b/{{ cookiecutter.role_name }}/molecule/requirements.yml new file mode 100644 index 0000000..f05969c --- /dev/null +++ b/{{ cookiecutter.role_name }}/molecule/requirements.yml @@ -0,0 +1,6 @@ +collections: + - name: ansible.posix + - name: community.general + - name: community.docker + +roles: [] diff --git a/{{ cookiecutter.role_name }}/molecule/yamllint.yml b/{{ cookiecutter.role_name }}/molecule/yamllint.yml new file mode 100644 index 0000000..100def0 --- /dev/null +++ b/{{ cookiecutter.role_name }}/molecule/yamllint.yml @@ -0,0 +1,25 @@ +extends: default + +ignore: .cache + +rules: + braces: + max-spaces-inside: 1 + level: error + brackets: + max-spaces-inside: 1 + level: error + comments-indentation: disable + document-start: disable + line-length: disable + truthy: + allowed-values: + - 'yes' + - 'on' + - 'true' + - 'True' + - 'no' + - 'off' + - 'false' + - 'False' + check-keys: no diff --git a/{{ cookiecutter.role_name }}/tasks/main.yml b/{{ cookiecutter.role_name }}/tasks/main.yml new file mode 100644 index 0000000..e69de29 diff --git a/{{ cookiecutter.role_name }}/templates/keep b/{{ cookiecutter.role_name }}/templates/keep new file mode 100644 index 0000000..e69de29 diff --git a/{{ cookiecutter.role_name }}/vars/main.yml b/{{ cookiecutter.role_name }}/vars/main.yml new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/{{ cookiecutter.role_name }}/vars/main.yml @@ -0,0 +1 @@ +