├── roles └── ensure_welcome │ ├── tests │ ├── inventory │ └── test.yml │ ├── vars │ └── main.yml │ ├── tasks │ └── main.yml │ ├── meta │ └── main.yml │ └── README.md ├── tests ├── integration │ └── targets │ │ └── foo │ │ └── runme.sh └── unit │ └── test_example.py ├── CODE_OF_CONDUCT.md ├── molecule └── default │ ├── verify.yml │ ├── molecule.yml │ ├── converge.yml │ ├── destroy.yml │ └── create.yml ├── .yamllint ├── .github ├── workflows │ ├── release-drafter.yml │ └── tox.yml └── release-drafter.yml ├── plugins └── README.md ├── LICENSE ├── .pre-commit-config.yaml ├── README.md ├── .gitignore ├── galaxy.yml ├── tox.ini └── Makefile /roles/ensure_welcome/tests/inventory: -------------------------------------------------------------------------------- 1 | localhost 2 | -------------------------------------------------------------------------------- /roles/ensure_welcome/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # vars file for ensure_welcome 3 | -------------------------------------------------------------------------------- /tests/integration/targets/foo/runme.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | echo 123 3 | -------------------------------------------------------------------------------- /roles/ensure_welcome/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for ensure_welcome 3 | -------------------------------------------------------------------------------- /roles/ensure_welcome/tests/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: localhost 3 | remote_user: root 4 | roles: 5 | - ensure_welcome 6 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Community Code of Conduct 2 | 3 | Please see the official [Ansible Community Code of Conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html). 4 | -------------------------------------------------------------------------------- /tests/unit/test_example.py: -------------------------------------------------------------------------------- 1 | from __future__ import (absolute_import, division, print_function) 2 | __metaclass__ = type 3 | 4 | 5 | def test_one(): 6 | """That is a test that not really do anything.""" 7 | pass 8 | -------------------------------------------------------------------------------- /molecule/default/verify.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # This is an example playbook to execute Ansible tests. 3 | 4 | - name: Verify 5 | hosts: all 6 | gather_facts: false 7 | tasks: 8 | - name: Example assertion 9 | ansible.builtin.assert: 10 | that: true 11 | -------------------------------------------------------------------------------- /molecule/default/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | driver: 5 | name: delegated 6 | options: 7 | managed: false 8 | ansible_connection_options: 9 | ansible_connection: local 10 | platforms: 11 | - name: localhost 12 | provisioner: 13 | name: ansible 14 | verifier: 15 | name: ansible 16 | -------------------------------------------------------------------------------- /molecule/default/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | # helps ansible resolve our role without being forced to use FQRN notation 5 | collections: 6 | - pycontribs.protogen 7 | tasks: 8 | - name: "Include ensure_welcome" 9 | ansible.builtin.include_role: 10 | name: "ensure_welcome" 11 | -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | extends: default 2 | ignore: | 3 | .tox 4 | 5 | rules: 6 | braces: 7 | max-spaces-inside: 1 8 | level: error 9 | brackets: 10 | max-spaces-inside: 1 11 | level: error 12 | document-start: disable 13 | line-length: 14 | allow-non-breakable-words: true 15 | max: 160 16 | truthy: 17 | allowed-values: ['true', 'false', 'on'] 18 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name: Release Drafter 2 | 3 | on: 4 | push: 5 | # branches to consider in the event; optional, defaults to all 6 | branches: 7 | - master 8 | - 'releases/**' 9 | - 'stable/**' 10 | 11 | jobs: 12 | update_release_draft: 13 | runs-on: ubuntu-latest 14 | steps: 15 | # Drafts your next Release notes as Pull Requests are merged into "master" 16 | - uses: release-drafter/release-drafter@v5 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 19 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | # Format and labels used aim to match those used by Ansible project 2 | categories: 3 | - title: 'Major Changes' 4 | labels: 5 | - 'major' # c6476b 6 | - title: 'Minor Changes' 7 | labels: 8 | - 'feature' # 006b75 9 | - 'enhancement' # ededed 10 | - 'performance' # 555555 11 | - title: 'Bugfixes' 12 | labels: 13 | - 'fix' 14 | - 'bugfix' 15 | - 'bug' # fbca04 16 | - 'docs' # 4071a5 17 | - 'packaging' # 4071a5 18 | - 'test' # #0e8a16 19 | - title: 'Deprecations' 20 | labels: 21 | - 'deprecated' # fef2c0 22 | exclude-labels: 23 | - 'skip-changelog' 24 | template: | 25 | ## Changes 26 | 27 | $CHANGES 28 | -------------------------------------------------------------------------------- /molecule/default/destroy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Destroy 3 | hosts: localhost 4 | connection: local 5 | gather_facts: false 6 | no_log: "{{ molecule_no_log }}" 7 | tasks: 8 | # Developer must implement. 9 | 10 | # Mandatory configuration for Molecule to function. 11 | 12 | - name: Populate instance config 13 | ansible.builtin.set_fact: 14 | instance_conf: {} 15 | 16 | - name: Dump instance config # noqa 503 17 | ansible.builtin.copy: 18 | content: | 19 | # Molecule managed 20 | 21 | {{ instance_conf | to_json | from_json | to_yaml }} 22 | dest: "{{ molecule_instance_config }}" 23 | mode: 0600 24 | when: server.changed | default(false) | bool 25 | -------------------------------------------------------------------------------- /plugins/README.md: -------------------------------------------------------------------------------- 1 | # Collections Plugins Directory 2 | 3 | This directory can be used to ship various plugins inside an Ansible collection. Each plugin is placed in a folder that 4 | is named after the type of plugin it is in. It can also include the `module_utils` and `modules` directory that 5 | would contain module utils and modules respectively. 6 | 7 | Here is an example directory of the majority of plugins currently supported by Ansible: 8 | 9 | ``` 10 | └── plugins 11 | ├── action 12 | ├── become 13 | ├── cache 14 | ├── callback 15 | ├── cliconf 16 | ├── connection 17 | ├── filter 18 | ├── httpapi 19 | ├── inventory 20 | ├── lookup 21 | ├── module_utils 22 | ├── modules 23 | ├── netconf 24 | ├── shell 25 | ├── strategy 26 | ├── terminal 27 | ├── test 28 | └── vars 29 | ``` 30 | 31 | A full list of plugin types can be found at [Working With Plugins](https://docs.ansible.com/ansible/2.9/plugins/plugins.html). 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Sorin Sbarnea 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /roles/ensure_welcome/meta/main.yml: -------------------------------------------------------------------------------- 1 | # TODO(ssbarnea): Most of the fields here are useless when a role is part of 2 | # a collection, creating an insane amount of duplication. 3 | galaxy_info: 4 | author: Sorin Sbarnea 5 | description: This role should display a welcome messsage 6 | 7 | issue_tracker_url: https://github.com/ansible-community/protogen/ 8 | 9 | license: MIT 10 | 11 | min_ansible_version: 2.9 12 | # If this a Container Enabled role, provide the minimum Ansible Container version. 13 | # min_ansible_container_version: 14 | 15 | # 16 | # Provide a list of supported platforms, and for each platform a list of versions. 17 | # If you don't wish to enumerate all versions for a particular platform, use 'all'. 18 | # To view available platforms and versions (or releases), visit: 19 | # https://galaxy.ansible.com/api/v1/platforms/ 20 | # 21 | # TODO(ssbarnea): persuade galaxy team to publish a list of generic platforms 22 | # content creators can use. The API returned list is unusable for practical 23 | # resons. 24 | platforms: 25 | - name: Fedora 26 | versions: 27 | - all 28 | - name: Debian 29 | versions: 30 | - all 31 | 32 | galaxy_tags: [] 33 | 34 | dependencies: [] 35 | -------------------------------------------------------------------------------- /molecule/default/create.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create 3 | hosts: localhost 4 | connection: local 5 | gather_facts: false 6 | no_log: "{{ molecule_no_log }}" 7 | tasks: 8 | 9 | # TODO: Developer must implement and populate 'server' variable 10 | 11 | - name: Perform actions if needed 12 | when: server.changed | default(false) | bool # noqa: no-handler 13 | block: 14 | - name: Populate instance config dict 15 | ansible.builtin.set_fact: 16 | instance_conf_dict: { 17 | 'instance': "{{ }}", 18 | 'address': "{{ }}", 19 | 'user': "{{ }}", 20 | 'port': "{{ }}", 21 | 'identity_file': "{{ }}", } 22 | with_items: "{{ server.results }}" 23 | register: instance_config_dict 24 | 25 | - name: Convert instance config dict to a list 26 | ansible.builtin.set_fact: 27 | instance_conf: "{{ instance_config_dict.results | map(attribute='ansible_facts.instance_conf_dict') | list }}" 28 | 29 | - name: Dump instance config 30 | ansible.builtin.copy: 31 | content: | 32 | # Molecule managed 33 | 34 | {{ instance_conf | to_json | from_json | to_yaml }} 35 | dest: "{{ molecule_instance_config }}" 36 | mode: 0600 37 | -------------------------------------------------------------------------------- /roles/ensure_welcome/README.md: -------------------------------------------------------------------------------- 1 | Role Name 2 | ========= 3 | 4 | A brief description of the role goes here. 5 | 6 | Requirements 7 | ------------ 8 | 9 | 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. 10 | 11 | Role Variables 12 | -------------- 13 | 14 | A description of the settable variables for this role should go here, including any variables that are in defaults/main.yml, vars/main.yml, and any variables that can/should be set via parameters to the role. Any variables that are read from other roles and/or the global scope (ie. hostvars, group vars, etc.) should be mentioned here as well. 15 | 16 | Dependencies 17 | ------------ 18 | 19 | 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. 20 | 21 | Example Playbook 22 | ---------------- 23 | 24 | Including an example of how to use your role (for instance, with variables passed in as parameters) is always nice for users too: 25 | 26 | - hosts: servers 27 | roles: 28 | - { role: username.rolename, x: 42 } 29 | 30 | License 31 | ------- 32 | 33 | BSD 34 | 35 | Author Information 36 | ------------------ 37 | 38 | An optional section for the role authors to include contact information, or a website (HTML is not allowed). 39 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | repos: 3 | - repo: https://github.com/pre-commit/pre-commit-hooks.git 4 | rev: v4.3.0 5 | hooks: 6 | - id: end-of-file-fixer 7 | - id: trailing-whitespace 8 | - id: mixed-line-ending 9 | - id: check-byte-order-marker 10 | - id: check-executables-have-shebangs 11 | - id: check-merge-conflict 12 | - id: debug-statements 13 | language_version: python3 14 | - repo: https://gitlab.com/pycqa/flake8.git 15 | rev: 3.9.2 16 | hooks: 17 | - id: flake8 18 | - repo: https://github.com/PyCQA/doc8 19 | rev: v1.0.0 20 | hooks: 21 | - id: doc8 22 | - repo: https://github.com/openstack-dev/bashate.git 23 | rev: 2.1.0 24 | hooks: 25 | - id: bashate 26 | # Run bashate check for all bash scripts 27 | # Ignores the following rules: 28 | # E006: Line longer than 79 columns (as many scripts use jinja 29 | # templating, this is very difficult) 30 | # E040: Syntax error determined using `bash -n` (as many scripts 31 | # use jinja templating, this will often fail and the syntax 32 | # error will be discovered in execution anyway) 33 | entry: bashate --error . --ignore=E006,E040 34 | - repo: https://github.com/adrienverge/yamllint.git 35 | rev: v1.28.0 36 | hooks: 37 | - id: yamllint 38 | files: \.(yaml|yml)$ 39 | types: [file, yaml] 40 | entry: yamllint --strict 41 | - repo: https://github.com/ansible/ansible-lint 42 | rev: v6.5.2 43 | hooks: 44 | - id: ansible-lint 45 | always_run: true 46 | pass_filenames: false 47 | # plugins is the standard collection dir for modules 48 | entry: env ANSIBLE_LIBRARY=plugins ansible-lint --force-color -p -v 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Protogen sample project 2 | 3 | The main goal of this project is to provide a **reference model** of how to write **maintainable Ansible code**. That includes collections, playbooks, roles, modules, plugins and filters. 4 | 5 | By maintainable it means fully testable on **CI/CD pipelines or locally** using the most appropriate testing method like linting, unit, functional, integration. 6 | 7 | The project will use the most appropriate test isolation solution for each testing stage: tox for unittests, containers for code that can be tested using a container and, as a last resort, VMs run localy using libvirt or remotely on a cloud. 8 | 9 | The project goal is to become a living template or **reference implementation** that can be used by developers for creating new Ansible content or for adopting good practices. 10 | 11 | This project will make use of popular testing tools like: [pytest](https://github.com/pytest-dev/pytest), [ansible-lint](https://github.com/ansible/ansible-lint), [molecule](https://github.com/ansible-community/molecule) in order to achieve its goals. 12 | 13 | If possible, we would be pleased if this project would become part of the official Ansible development documentation, which at this point does not cover some essential cases like: how to write and run unittests for a module inside your collection. 14 | 15 | # Joining this community effort 16 | 17 | If you have an interest about promoting good coding standards, feel free to [join 18 | our group](https://github.com/ansible-community/protogen/issues/1). Even if you do not 19 | have time to propose new changes, your help as a reviewer would be essential in 20 | order to assure we adopt only the best practices. 21 | 22 | # History 23 | 24 | The idea to name the project [Protogen](https://expanse.fandom.com/wiki/Protogen) came from the company behind viral protomolecule from [Expanse TV series](https://en.wikipedia.org/wiki/The_Expanse_(TV_series)). Originally, @ssbarnea considered using protomolecule as name of the project because it was aimting to create a prototype example of molecule use. 25 | 26 | Even the original slogan **"First. Fastest. Furthest"** does apply very well with the **testing** goal of the project. Mainly the project aims to create a set of testing practices that could get a viral adoption among Ansible universe. 27 | 28 | ![protogen-logo](https://repository-images.githubusercontent.com/257321711/e95a5600-832d-11ea-8e55-a81114b6c965) 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /galaxy.yml: -------------------------------------------------------------------------------- 1 | ### REQUIRED 2 | 3 | # The namespace of the collection. This can be a company/brand/organization or product namespace under which all 4 | # content lives. May only contain alphanumeric lowercase characters and underscores. Namespaces cannot start with 5 | # underscores or numbers and cannot contain consecutive underscores 6 | namespace: pycontribs 7 | 8 | # The name of the collection. Has the same character restrictions as 'namespace' 9 | name: protogen 10 | 11 | # The version of the collection. Must be compatible with semantic versioning 12 | version: 0.0.1 13 | 14 | # The path to the Markdown (.md) readme file. This path is relative to the root of the collection 15 | readme: README.md 16 | 17 | # A list of the collection's content authors. Can be just the name or in the format 'Full Name (url) 18 | # @nicks:irc/im.site#channel' 19 | authors: 20 | - Sorin Sbarnea 21 | 22 | ### OPTIONAL but strongly recommended 23 | 24 | # A short summary description of the collection 25 | description: your collection description 26 | 27 | # Either a single license or a list of licenses for content inside of a collection. Ansible Galaxy currently only 28 | # accepts L(SPDX,https://spdx.org/licenses/) licenses. This key is mutually exclusive with 'license_file' 29 | license: 30 | - MIT 31 | 32 | # A list of tags you want to associate with the collection for indexing/searching. A tag name has the same character 33 | # requirements as 'namespace' and 'name' 34 | tags: [] 35 | 36 | # Collections that this collection requires to be installed for it to be usable. The key of the dict is the 37 | # collection label 'namespace.name'. The value is a version range 38 | # L(specifiers,https://python-semanticversion.readthedocs.io/en/latest/#requirement-specification). Multiple version 39 | # range specifiers can be set and are separated by ',' 40 | dependencies: {} 41 | 42 | # The URL of the originating SCM repository 43 | repository: https://github.com/ansible-community/protogen 44 | 45 | # The URL to any online docs 46 | documentation: https://github.com/ansible-community/protogen 47 | 48 | # The URL to the homepage of the collection/project 49 | homepage: https://github.com/ansible-community/protogen 50 | 51 | # The URL to the collection issue tracker 52 | issues: https://github.com/ansible-community/issues 53 | 54 | # that key is not even part of the template: 55 | build_ignore: 56 | - .ansible 57 | - .cache 58 | - .github 59 | - .gitignore 60 | - .pytest_cache 61 | - .vscode 62 | - dist 63 | - tox.ini 64 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | minversion = 3.18.0 3 | envlist = 4 | linters 5 | yolo 6 | requires = 7 | tox-ansible >= 1.0.0a0 8 | 9 | skipsdist = True 10 | # do not enable skip missing to avoid CI false positives 11 | skip_missing_interpreters = False 12 | isolated_build = True 13 | 14 | [testenv] 15 | usedevelop = True 16 | passenv = * 17 | setenv = 18 | PYTHONDONTWRITEBYTECODE=1 19 | # Do not trust docs, correct value does not have plural 20 | ANSIBLE_COLLECTIONS_PATH=~/.ansible/collections/ansible_collections 21 | CLICOLOR=1 22 | # {toxinidir}/.cache/collections 23 | COLLECTION_NAME=pycontribs/protogen 24 | # see https://github.com/ansible-community/tox-ansible/issues/35 25 | skip_install = True 26 | allowlist_externals = 27 | ansible-test 28 | bash 29 | echo 30 | sh 31 | make 32 | commands = 33 | echo {posargs} 34 | 35 | 36 | [testenv:lint] 37 | description = Runs all linting tasks 38 | commands = 39 | python -m pre_commit run {posargs:--all} 40 | deps = pre-commit>=1.18.1 41 | extras = 42 | skip_install = true 43 | usedevelop = false 44 | 45 | [testenv:yolo] 46 | deps = 47 | ansible-base>=2.10 48 | commands = 49 | # clean-up potential leftovers 50 | sh -c "rm -rf dist/* {env:ANSIBLE_COLLECTIONS_PATH}/{env:COLLECTION_NAME} || true" 51 | # build collection 52 | ansible-galaxy collection build -v -f --output-path dist/ 53 | # install collection in custom location 54 | sh -c "ansible-galaxy collection install -f dist/*.tar.gz" 55 | # validates that collection is reported as installed 56 | sh -c "ansible-galaxy collection list | grep 'pycontribs.protogen'" 57 | 58 | [testenv:packaging] 59 | description = Builds the collection 60 | setenv = 61 | # Do not trust docs, correct value does not have plural 62 | ANSIBLE_COLLECTIONS_PATH={toxinidir}/.cache/collections 63 | # for packaging we want latest ansible base, as it may 64 | # contain important build features missing from older versions. 65 | deps = 66 | ansible-base>=2.10 67 | commands = 68 | # clean-up potential leftovers 69 | sh -c "rm -rf dist/* .cache/collections/* || true" 70 | # build collection 71 | ansible-galaxy collection build -v -f --output-path dist/ 72 | # install collection in custom location 73 | sh -c "ansible-galaxy collection install -f dist/*.tar.gz" 74 | # validates that collection is reported as installed 75 | sh -c "ansible-galaxy collection list | grep 'pycontribs.protogen'" 76 | extras = 77 | skip_install = true 78 | usedevelop = false 79 | 80 | [testenv:molecule] 81 | description = Runs `molecule test -s default` directly 82 | # that environment duplicates what tox-ansible does and is kept here only 83 | # for convenienve, to debug possible bugs in tox-ansible. 84 | deps = 85 | {[testenv:packaging]deps} 86 | molecule >= 3.2.0 87 | commands = 88 | # molecule does not know yet to install current collection before running 89 | # https://github.com/ansible-community/molecule/pull/2998 90 | {[testenv:packaging]commands} 91 | molecule test -s default 92 | 93 | [testenv:make] 94 | description = Runs make from inside venv. 95 | commands = 96 | make {posargs} 97 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | DESTDIR ?= 2 | PYTHON ?= $(shell command -v python3 python|head -n1) 3 | PYTHON_VERSION ?= $(shell $(PYTHON) -c 'import sys; print("%s.%s" % (sys.version_info.major, sys.version_info.minor))') 4 | PKG_MANAGER ?= $(shell command -v dnf yum|head -n1) 5 | PIP ?= PIP_DISABLE_VERSION_CHECK=1 $(PYTHON) -m pip 6 | 7 | .EXPORT_ALL_VARIABLES: 8 | 9 | NAMESPACE := $(shell $(PYTHON) -c "import yaml; print(yaml.safe_load(open('galaxy.yml').read())['namespace'])") 10 | COLLECTION := $(shell $(PYTHON) -c "import yaml; print(yaml.safe_load(open('galaxy.yml').read())['name'])") 11 | VERSION := $(shell $(PYTHON) -c "import yaml; print(yaml.safe_load(open('galaxy.yml').read())['version'])") 12 | 13 | ANSIBLE_COLLECTIONS_PATH = $(HOME)/.ansible/collections/ansible_collections 14 | ANSIBLE_TEST = cd $(ANSIBLE_COLLECTIONS_PATH)/$(NAMESPACE)/$(COLLECTION) && ansible-test 15 | ANSIBLE := $(shell command -v ansible) 16 | 17 | 18 | .PHONY: test sanity units env integration shell coverage network-integration windows-integration lint default help build install devenv 19 | 20 | HAS_ANSIBLE := $(shell command -v ansible 2> /dev/null) 21 | 22 | default: help 23 | 24 | define PRINT_HELP_PYSCRIPT 25 | import re, sys 26 | 27 | print("Usage: make ") 28 | cmds = {} 29 | for line in sys.stdin: 30 | match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line) 31 | if match: 32 | target, help = match.groups() 33 | cmds.update({target: help}) 34 | for cmd in sorted(cmds): 35 | print(" * '%s' - %s" % (cmd, cmds[cmd])) 36 | endef 37 | export PRINT_HELP_PYSCRIPT 38 | 39 | help: 40 | @$(PYTHON) -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) 41 | 42 | devenv: # Assures we have all pre-requisites for testing 43 | ifndef HAS_ANSIBLE 44 | $(PIP) install --user "ansible-base>=2.10" 45 | endif 46 | $(ANSIBLE) --version 47 | @# too avoid: Could not build wheels for ... 48 | python3 -m pip install --user wheel 49 | 50 | build: devenv ## Builds collection 51 | @echo === build collection: $(NAMESPACE).$(COLLECTION) $(VERSION) === 52 | exit 53 | sh -c "rm -rf dist/* .cache/collections/* || true" 54 | ansible-galaxy collection build -v -f --output-path dist/ 55 | 56 | install: build ## Installs collection 57 | @echo install collection to $(ANSIBLE_COLLECTIONS_PATH) 58 | ansible-galaxy collection install -f dist/*.tar.gz -p $(ANSIBLE_COLLECTIONS_PATH) 59 | @echo validates that collection is reported as installed 60 | ansible-galaxy collection list | grep '$(NAMESPACE).$(COLLECTION)' 61 | 62 | test: ## Runs ansible-test 63 | $(ANSIBLE_TEST) ansible-test --help 64 | 65 | # BEGIN ansible-test commands 66 | sanity: install ## Runs ansible-test sanity 67 | @# see https://github.com/ansible/ansible/issues/72854 68 | $(PIP) install --user pylint 69 | $(ANSIBLE_TEST) sanity --requirements --python $(PYTHON_VERSION) 70 | 71 | units: install ## Runs ansible-test units 72 | $(ANSIBLE_TEST) units --requirements --python $(PYTHON_VERSION) 73 | 74 | integration: install ## posix integration tests 75 | $(ANSIBLE_TEST) integration --requirements --python $(PYTHON_VERSION) 76 | 77 | network-integration: install ## network integration tests [NOT-IMPLEMENTED] 78 | $(ANSIBLE_TEST) network-integration --requirements 79 | 80 | windows-integration: install ## windows integration tests [NOT-IMPLEMENTED] 81 | $(ANSIBLE_TEST) windows-integration --requirements 82 | 83 | shell: ## open an interactive shell 84 | $(ANSIBLE_TEST) shell 85 | 86 | coverage: units ## code coverage management and reporting 87 | $(ANSIBLE_TEST) units --requirements --python $(PYTHON_VERSION) --coverage 88 | $(ANSIBLE_TEST) integration --requirements --python $(PYTHON_VERSION) --coverage 89 | $(ANSIBLE_TEST) coverage combine 90 | $(ANSIBLE_TEST) coverage report 91 | 92 | env: ## show information about the test environment 93 | $(ANSIBLE_TEST) env 94 | # END of ansible-test commands 95 | 96 | # BEGIN tox commands 97 | lint: ## Lints the code 98 | tox -e linters 99 | 100 | molecule: ## Runs molecule 'default' scenario 101 | tox -e molecule 102 | # END of tox commands 103 | -------------------------------------------------------------------------------- /.github/workflows/tox.yml: -------------------------------------------------------------------------------- 1 | name: tox 2 | 3 | on: 4 | create: # is used for publishing to PyPI and TestPyPI 5 | tags: # any tag regardless of its name, no branches 6 | push: # only publishes pushes to the main branch to TestPyPI 7 | branches: # any integration branch but not tag 8 | - "master" 9 | tags-ignore: 10 | - "**" 11 | pull_request: 12 | schedule: 13 | - cron: 1 0 * * * # Run daily at 0:01 UTC 14 | 15 | jobs: 16 | make: 17 | name: ${{ matrix.name }} 18 | runs-on: ubuntu-20.04 19 | strategy: 20 | fail-fast: false 21 | matrix: 22 | include: 23 | - name: sanity 24 | - name: units 25 | - name: integration 26 | - name: coverage 27 | steps: 28 | - uses: actions/checkout@v1 29 | - name: Run workarounds github-action specific 30 | # https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#environment-files 31 | run: | 32 | sudo apt-get -qq -y remove --purge ansible 33 | mkdir -p ~/.local/bin 34 | export PATH=$HOME/.local/bin:$PATH 35 | # remove ansible 2.9 which comes free on github actions 36 | # assure we have a pip with decent resolver 37 | python3 -m pip install "pip>=20.3.1" 38 | # lets fix some already missing dependencies from gha ubuntu image 39 | pip3 install launchpadlib pygobject 40 | # lets see the goodies... 41 | pip3 check 42 | # to bring pip installed stuff in user PATH, for *subsequent* runs 43 | echo "$HOME/.local/bin" >> $GITHUB_PATH 44 | - name: Run make devenv 45 | run: | 46 | echo "PATH=$PATH" 47 | make devenv 48 | - name: Run make install 49 | run: | 50 | make install 51 | make ${{ matrix.name }} 52 | 53 | tox: 54 | name: ${{ matrix.tox_env }} 55 | runs-on: ubuntu-20.04 56 | strategy: 57 | fail-fast: false 58 | matrix: 59 | include: 60 | - tox_env: lint 61 | # TODO(ssbarnea): replace molecule env with default once molecule 62 | # will be able to install collection being tested: 63 | # https://github.com/ansible-community/molecule/pull/2998 64 | - tox_env: molecule 65 | - tox_env: sanity 66 | - tox_env: units 67 | 68 | steps: 69 | - uses: actions/checkout@v1 70 | - name: Install system dependencies 71 | run: | 72 | sudo apt-get update \ 73 | && sudo apt-get install -y libvirt-dev 74 | - name: Find python version 75 | id: py_ver 76 | shell: python 77 | if: ${{ contains(matrix.tox_env, 'py') }} 78 | run: | 79 | v = '${{ matrix.tox_env }}'.split('-')[0].lstrip('py') 80 | print('::set-output name=version::{0}.{1}'.format(v[0],v[1:])) 81 | # Even our lint and other envs need access to tox 82 | - name: Install a default Python 83 | uses: actions/setup-python@v2 84 | # workaround to avoid getting 3.9 in: 85 | with: 86 | python-version: '3.8' 87 | if: ${{ ! contains(matrix.tox_env, 'py') }} 88 | # Be sure to install the version of python needed by a specific test, if necessary 89 | - name: Set up Python version 90 | uses: actions/setup-python@v2 91 | if: ${{ contains(matrix.tox_env, 'py') }} 92 | with: 93 | python-version: ${{ steps.py_ver.outputs.version }} 94 | - name: Install dependencies 95 | run: | 96 | python -m pip install -U pip 97 | pip install tox 98 | - name: Run tox -e ${{ matrix.tox_env }} 99 | run: | 100 | echo "${{ matrix.PREFIX }} tox -e ${{ matrix.tox_env }}" 101 | ${{ matrix.PREFIX }} tox -e ${{ matrix.tox_env }} 102 | 103 | publish: 104 | name: Publish to PyPI registry 105 | needs: 106 | - tox 107 | - make 108 | runs-on: ubuntu-latest 109 | 110 | env: 111 | PY_COLORS: 1 112 | TOXENV: packaging 113 | 114 | steps: 115 | - name: Switch to using Python 3.6 by default 116 | uses: actions/setup-python@v2 117 | with: 118 | python-version: 3.6 119 | - name: Install tox 120 | run: python -m pip install --user tox 121 | - name: Check out src from Git 122 | uses: actions/checkout@v2 123 | with: 124 | # Get shallow Git history (default) for tag creation events 125 | # but have a complete clone for any other workflows. 126 | # Both options fetch tags but since we're going to remove 127 | # one from HEAD in non-create-tag workflows, we need full 128 | # history for them. 129 | fetch-depth: >- 130 | ${{ 131 | ( 132 | github.event_name == 'create' && 133 | github.event.ref_type == 'tag' 134 | ) && 135 | 1 || 0 136 | }} 137 | - name: Drop Git tags from HEAD for non-tag-create events 138 | if: >- 139 | github.event_name != 'create' || 140 | github.event.ref_type != 'tag' 141 | run: >- 142 | git tag --points-at HEAD 143 | | 144 | xargs git tag --delete 145 | - name: Build dists 146 | run: python -m tox 147 | - name: Publish to test.pypi.org 148 | if: >- 149 | ( 150 | github.event_name == 'push' && 151 | github.ref == format( 152 | 'refs/heads/{0}', github.event.repository.default_branch 153 | ) 154 | ) || 155 | ( 156 | github.event_name == 'create' && 157 | github.event.ref_type == 'tag' 158 | ) 159 | uses: pypa/gh-action-pypi-publish@master 160 | with: 161 | password: ${{ secrets.testpypi_password }} 162 | repository_url: https://test.pypi.org/legacy/ 163 | - name: Publish to pypi.org 164 | if: >- # "create" workflows run separately from "push" & "pull_request" 165 | github.event_name == 'create' && 166 | github.event.ref_type == 'tag' 167 | uses: pypa/gh-action-pypi-publish@master 168 | with: 169 | password: ${{ secrets.pypi_password }} 170 | # No need to publish to quay.io from here as they do it when a new tag 171 | # is pushed on git. 172 | --------------------------------------------------------------------------------