├── molecule └── default │ ├── verify.yml │ ├── converge.yml │ ├── molecule.yml │ └── INSTALL.rst ├── .yamllint ├── .github └── workflows │ └── ci.yml ├── main.yml └── README.md /molecule/default/verify.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Verify 3 | hosts: all 4 | 5 | tasks: 6 | - name: Verify Apache is serving web requests. 7 | uri: 8 | url: http://localhost/ 9 | status_code: 200 10 | -------------------------------------------------------------------------------- /molecule/default/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | 5 | tasks: 6 | - name: Update apt cache (on Debian). 7 | apt: 8 | update_cache: true 9 | cache_valid_time: 3600 10 | when: ansible_os_family == 'Debian' 11 | 12 | - import_playbook: ../../main.yml 13 | -------------------------------------------------------------------------------- /molecule/default/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | driver: 5 | name: docker 6 | lint: | 7 | set -e 8 | yamllint . 9 | ansible-lint 10 | platforms: 11 | - name: instance 12 | image: geerlingguy/docker-${MOLECULE_DISTRO:-centos8}-ansible:latest 13 | command: "" 14 | volumes: 15 | - /sys/fs/cgroup:/sys/fs/cgroup:ro 16 | privileged: true 17 | pre_build_image: true 18 | provisioner: 19 | name: ansible 20 | verifier: 21 | name: ansible 22 | -------------------------------------------------------------------------------- /molecule/default/INSTALL.rst: -------------------------------------------------------------------------------- 1 | ******* 2 | Docker driver installation guide 3 | ******* 4 | 5 | Requirements 6 | ============ 7 | 8 | * Docker Engine 9 | 10 | Install 11 | ======= 12 | 13 | Please refer to the `Virtual environment`_ documentation for installation best 14 | practices. If not using a virtual environment, please consider passing the 15 | widely recommended `'--user' flag`_ when invoking ``pip``. 16 | 17 | .. _Virtual environment: https://virtualenv.pypa.io/en/latest/ 18 | .. _'--user' flag: https://packaging.python.org/tutorials/installing-packages/#installing-to-the-user-site 19 | 20 | .. code-block:: bash 21 | 22 | $ python3 -m pip install 'molecule[docker]' 23 | -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | --- 2 | # Based on ansible-lint config 3 | extends: default 4 | 5 | rules: 6 | braces: 7 | max-spaces-inside: 1 8 | level: error 9 | brackets: 10 | max-spaces-inside: 1 11 | level: error 12 | colons: 13 | max-spaces-after: -1 14 | level: error 15 | commas: 16 | max-spaces-after: -1 17 | level: error 18 | comments: disable 19 | comments-indentation: disable 20 | document-start: disable 21 | empty-lines: 22 | max: 3 23 | level: error 24 | hyphens: 25 | level: error 26 | indentation: disable 27 | key-duplicates: enable 28 | line-length: disable 29 | new-line-at-end-of-file: disable 30 | new-lines: 31 | type: unix 32 | trailing-spaces: disable 33 | truthy: disable 34 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CI 3 | 'on': 4 | pull_request: 5 | push: 6 | branches: 7 | - master 8 | 9 | jobs: 10 | 11 | test: 12 | name: Molecule 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | distro: 17 | - centos8 18 | - debian10 19 | 20 | steps: 21 | - name: Check out the codebase. 22 | uses: actions/checkout@v2 23 | 24 | - name: Set up Python 3. 25 | uses: actions/setup-python@v2 26 | with: 27 | python-version: '3.x' 28 | 29 | - name: Install test dependencies. 30 | run: pip3 install ansible molecule[docker] docker yamllint ansible-lint 31 | 32 | - name: Run Molecule tests. 33 | run: molecule test 34 | env: 35 | PY_COLORS: '1' 36 | ANSIBLE_FORCE_COLOR: '1' 37 | MOLECULE_DISTRO: ${{ matrix.distro }} 38 | -------------------------------------------------------------------------------- /main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install Apache. 3 | hosts: all 4 | become: true 5 | 6 | vars: 7 | apache_package: apache2 8 | apache_service: apache2 9 | 10 | handlers: 11 | - name: restart apache 12 | service: 13 | name: "{{ apache_service }}" 14 | state: restarted 15 | 16 | pre_tasks: 17 | - name: Override Apache vars for Red Hat. 18 | set_fact: 19 | apache_package: httpd 20 | apache_service: httpd 21 | when: ansible_os_family == 'RedHat' 22 | 23 | tasks: 24 | - name: Ensure Apache is installed. 25 | package: 26 | name: "{{ apache_package }}" 27 | state: present 28 | 29 | - name: Copy a web page. 30 | copy: 31 | content: | 32 | 33 | Hello world! 34 | Hello world! 35 | 36 | dest: "/var/www/html/index.html" 37 | mode: '0644' 38 | notify: restart apache 39 | 40 | - name: Ensure Apache is running and starts at boot. 41 | service: 42 | name: "{{ apache_service }}" 43 | state: started 44 | enabled: true 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ansible 101 - Molecule playbook testing 2 | 3 | > _This example is derived from Chapter 12 of [Ansible for DevOps](https://www.ansiblefordevops.com), version 1.23_ 4 | 5 | This playbook installs Apache and should work on the latest versions of Red Hat-based and Debian-based OSes, at a minimum: 6 | 7 | - CentOS 8 8 | - Debian 10 9 | 10 | Create a new molecule scenario: 11 | 12 | molecule init scenario 13 | 14 | Edit the `converge.yml` playbook: 15 | 16 | ```yaml 17 | --- 18 | - name: Converge 19 | hosts: all 20 | 21 | tasks: 22 | - name: Update apt cache (on Debian). 23 | apt: 24 | update_cache: true 25 | cache_valid_time: 3600 26 | when: ansible_os_family == 'Debian' 27 | 28 | - import_playbook: ../../main.yml 29 | ``` 30 | 31 | Then run: 32 | 33 | molecule converge 34 | 35 | Uh oh... something failed. Log in and check it out: 36 | 37 | ``` 38 | $ molecule login 39 | [root@instance /]# systemctl status httpd 40 | Failed to get D-Bus connection: Operation not permitted 41 | ``` 42 | 43 | Yikes, I can't take this entire episode to explain the details of what's going on, but at a basic level, Molecule's default configuration sets up a container and runs the command: 44 | 45 | bash -c "while true; do sleep 10000; done" 46 | 47 | (use `docker ps` to confirm that.) 48 | 49 | Unfortunately, this means the process that's running in this container is _not_ systemd's init system, meaning you can't use systemd to manage services like Apache. 50 | 51 | In normal container usage, this is perfectly fine—running an init system in a container is kind of an anti-best-practice. 52 | 53 | But in our case, we're using containers for testing. And lucky for you, I maintain a large set of Ansible testing containers that have systemd built in and ready to go! So for these tests, since we want to make sure Ansible can manage the Apache service correctly, we need to change a few things in Molecule's `molecule.yml` configuration. 54 | 55 | Go ahead and exit and destroy the environment. 56 | 57 | ``` 58 | [root@instance /]# exit 59 | $ molecule destroy 60 | ``` 61 | 62 | Now edit the `platforms` in `molecule.yml`: 63 | 64 | ```yaml 65 | platforms: 66 | - name: instance 67 | image: "geerlingguy/docker-${MOLECULE_DISTRO:-centos7}-ansible:latest" 68 | command: "" 69 | volumes: 70 | - /sys/fs/cgroup:/sys/fs/cgroup:ro 71 | privileged: true 72 | pre_build_image: true 73 | ``` 74 | 75 | Now try running it again: 76 | 77 | molecule test 78 | 79 | Yay, it works! 80 | 81 | Now, since we added an environment variable, `MOLECULE_DISTRO`, we can substitute whatever distro we want there, assuming I maintain a Docker image for that distro: 82 | 83 | MOLECULE_DISTRO=debian10 molecule test 84 | 85 | Yay, still works! 86 | 87 | ## Verify with Molecule 88 | 89 | Now add to `verify.yml`: 90 | 91 | ```yaml 92 | --- 93 | - name: Verify 94 | hosts: all 95 | 96 | tasks: 97 | - name: Verify Apache is serving web requests. 98 | uri: 99 | url: http://localhost/ 100 | status_code: 200 101 | ``` 102 | 103 | You can just run the verify playbook with `molecule verify`. 104 | 105 | ## Add lint configuration 106 | 107 | Assuming you have `yamllint` and `ansible-lint` installed, you can configure Molecule to run them in the `lint` build stage: 108 | 109 | ```yaml 110 | lint: | 111 | set -e 112 | yamllint . 113 | ansible-lint 114 | ``` 115 | 116 | Then run `molecule lint`. It doesn't even need to build an environment to do the linting. 117 | 118 | ## GitHub Actions Testing 119 | 120 | Go ahead and slap this repo up on GitHub. Do it! 121 | 122 | Now add a `.github/workflows` folder. 123 | 124 | Add a `ci.yml` workflow file. 125 | 126 | Here's the whole workflow we're targeting: 127 | 128 | ```yaml 129 | --- 130 | name: CI 131 | 'on': 132 | pull_request: 133 | push: 134 | branches: 135 | - master 136 | 137 | jobs: 138 | 139 | test: 140 | name: Molecule 141 | runs-on: ubuntu-latest 142 | strategy: 143 | matrix: 144 | distro: 145 | - centos8 146 | - debian10 147 | 148 | steps: 149 | - name: Check out the codebase. 150 | uses: actions/checkout@v2 151 | 152 | - name: Set up Python 3. 153 | uses: actions/setup-python@v2 154 | with: 155 | python-version: '3.x' 156 | 157 | - name: Install test dependencies. 158 | run: pip3 install molecule docker yamllint ansible-lint 159 | 160 | - name: Run Molecule tests. 161 | run: molecule test 162 | env: 163 | PY_COLORS: '1' 164 | ANSIBLE_FORCE_COLOR: '1' 165 | MOLECULE_DISTRO: ${{ matrix.distro }} 166 | ``` 167 | --------------------------------------------------------------------------------