Molecule is an excellent tool for testing Ansible roles, it follows a robust and flexible validation process to ensure a good level of role quality. Almost all of the Molecule documentation focuses on the docker driver, where tests are run against the container server, but while it is a good choice for most use cases, there may be cases where it might be useful to switch to an external cloud backend using a delegated driver .
Unfortunately, the delegated driver documentation is mostly just a few lines in the official doc, whereas a clearer explanation and a few examples can be of immense help to those developers who want to use Molecule in this way.
This post builds on my experience developing a simple Ansible role from 0 to galaxy and focuses on using a delegated driver integrated with Google Cloud Platform. I took the following useful links for my project as a starting point:
- Molecule official doc ( https://molecule.readthedocs.io/en/latest/ #)
- Nice introduction to Molecule by Jeff Gerling ( https://www.jeffgeerling.com/blog/2018/testing-your-ansible-roles-molecule )
- A rare example of a delegated driver from an issue open to the Molecule community ( https://github.com/ansible-community/molecule/issues/1292 )
- ansible Google Cloud Platform . (https://itnext.io/getting-started-with-red-hat-ansible-for-google-cloud-platform-fa666c42a00c)
: Molecule?
, , β Molecule:
instance-config API. instance-config, instance-config.
: instance-config ?
Instance-config β Ansible, YAML Molecule ( $HOME/.cache/molecule/<role-name>/<scenario-name>/instance_config.yml
), :
- address: 10.10.15.17
identity_file: /home/fabio/.ssh/id_rsa # mutually exclusive with
# password
instance: millennium_falcon
port: 22
user: hansolo
# password: ssh_password # mutually exclusive with identity_file
become_method: sudo # optional
# become_pass: password_if_required # optional
, Windows, WinRM.
create.yml
, , instance-config, . , Molecule , molecule init
, :
molecule init scenario -driver-name=delegated
:
. βββ INSTALL.rst βββ converge.yml βββ create.yml βββ destroy.yml βββ molecule.yml βββ verify.yml
molecule.yml
β Molecule, , .create.yml
β Ansible instance-config.destroy.yml
Ansible instance-configconverge.yml
verify.yml
INSTALL.rst
Molecule
create.yml
, Molecule:
--- - name: Create hosts: localhost connection: local gather_facts: false no_log: "{{ molecule_no_log }}" tasks: # Developer must implement. # Developer must map instance config. # Mandatory configuration for Molecule to function. β name: Populate instance config dict set_fact: instance_conf_dict: { 'instance': "{{ }}", 'address': "{{ }}", 'user': "{{ }}", 'port': "{{ }}", 'identity_file': "{{ }}", } with_items: "{{ server.results }}" register: instance_config_dict when: server.changed | bool β name: Convert instance config dict to a list set_fact: instance_conf: {{ instance_config_dict.results | map(attribute='ansible_facts.instance_conf_dict') | list }}" when: server.changed | bool β name: Dump instance config copy: content: "{{ instance_conf | to_json | from_json | molecule_to_yaml | molecule_header }}" dest: "{{ molecule_instance_config }}" when: server.changed | bool
: , , instance-config.yml
. Ansible, ( ) . , github, , VMWare:
β¦ 7 - name: Create molecule instance(s) 8 vmware_guest: 9 hostname: "{{ molecule_yml.driver.hostname }}" 10 esxi_hostname: "{{ molecule_yml.driver.esxi_hostname }}" 11 username: "{{ molecule_yml.driver.username }}" 12 password: "{{ molecule_yml.driver.password }}" 13 datacenter: "{{ molecule_yml.driver.datacenter }}" 14 validate_certs: "{{ molecule_yml.driver.validate_certs }}" 15 resource_pool: "{{ molecule_yml.driver.resource_pool }}" 16 folder: "{{ molecule_yml.driver.folder }}" 17 name: "{{ item.name }}" 18 template: "{{ item.template }}" 19 hardware: 20 memory_mb: "{{ item.memory | default(omit) }}" 21 num_cpus: "{{ item.cpu | default(omit) }}" 22 wait_for_ip_address: "yes" 23 state: poweredon 24 register: server 25 with_items: "{{ molecule_yml.platforms }}" 26 27 - name: Populate instance config dict 28 set_fact: 29 instance_conf_dict: { 30 'instance': "{{ item.instance.hw_name }}", 31 'address': "{{ item.instance.ipv4 }}", 32 'user': "vagrant", 33 'port': "22", 34 'identity_file': 'identity_file': "{{ molecule_yml.driver.ssh_identity_file }}" 35 } 36 with_items: "{{ server.results }}" 37 register: instance_config_dict 38 when: server is changed β¦
vmware_guest
( 7β23) VMWare. , molecule.yml
( 25). , , molecule.yml
, molecule_yml
.
, vmware_guest
, ( 24), , , (instance-config
) ( 27 ). , , .
Google Cloud Platform (GCP)
, , , , docker-secured Ansible. GCP . Ansible GCP , , , .
:
- python 2.7
- ansible 2.9.6
- molecule 3.0.2
- ansible-lint 4.2.0
- yamllint 1.20.0
- flake8 3.7.9 (mccabe: 0.6.1, pycodestyle: 2.5.0, pyflakes: 2.1.1) CPython 2.7.17 Linux
yamllint, ansible-lint flake8 β , .
docker-secured
, API- ssl. , , Docker:
https://success.docker.com/article/how-do-i-enable-the-remote-api-for-dockerd
https://docs.docker.com/engine/security/https/
ssl , , .
, GitHub:
git clone https://github.com/fabiomarinetti/fmarinetti.docker-secured.git
GCP
, GCP, . , GCP . , Ansible GCP.
ansible-272015
service
, secret.json
.
molecule.yml
molecule.yml
.
, molecule.yml
driver
. , , , , GCP, ssh , , , , ad-hoc . molecule_yml
(, molecule_yml.driver.region
).
20 driver: 21 name: delegated 22 gcp_service_account_key: ${GOOGLE_APPLICATION_CREDENTIALS} 23 gcp_project_id: ansible-272015 24 region: us-east1 25 zone: us-east1-c 26 ssh_user: ${SSH_USER} 27 ssh_pub_key_file: "${SSH_ID_FILE}.pub" 28 ssh_key_file: "${SSH_ID_FILE}" 29 network_name: ansible-network 30 subnet_name: ansible-subnet 31 firewall_name: ansible-firewall 32 ip_cidr_range: 172.16.0.0/28
molecule.yml
, (, , , β¦) , . CentOS 7, Ubuntu Xenial 16.04 Ubuntu Bionic 18.04. (, CentOS Ubuntu), Ansible.
41 platforms: 42 - name: "ds-centos7-${TRAVIS_BUILD_ID}" 43 image_family: projects/centos-cloud/global/images/family /centos-7 44 machine_type: n1-standard-1 45 size_gb: 200 46 groups: 47 - centos 48 - name: "ds-ubuntu-bionic-${TRAVIS_BUILD_ID}" 49 image_family: projects/ubuntu-os-cloud/global/images/family /ubuntu-1804-lts 50 machine_type: n1-standard-1 51 size_gb: 200 52 groups: 53 - ubuntu 54 - name: "ds-ubuntu-xenial-${TRAVIS_BUILD_ID}" 55 image_family: projects/ubuntu-os-cloud/global/images/family /ubuntu-1604-lts 56 machine_type: n1-standard-1 57 size_gb: 200 58 groups: 59 - ubuntu
molecule.yml
, .
create.yml
, create.yml
β , . gcp (GCP). GCP - , , , , , module_defaults
gcp.
7 module_defaults: 8 group/gcp: 9 project: "{{ molecule_yml.driver.gcp_project_id }}" 10 auth_kind: serviceaccount 11 service_account_file: "{{ molecule_yml.driver.gcp_service_account_key }}"
, VMWare, GCP β , , : , IP- . , :
16 β name: create instances 17 include_tasks: tasks/create_instance.yml 18 loop: "{{ molecule_yml.platforms }}"
create_instance.yml
IP-, . , , , , , , instance-config.
7 - name: initialize instance facts 8 set_fact: 9 instance_created: 10 instances: [] 11 when: instance_created is not defined ... create the instance and return instance variable ... 56 - name: update instance facts 57 set_fact: 58 instance_created: 59 changed: instance.changed | bool 60 instances: "{{ instance_created.instances + [ instance ]}}"
isntance-config instance_create
:
20 - name: Populate instance config dict 21 set_fact: 22 instance_conf_dict: { 23 'instance': "{{ item.name }}", 24 'address': "{{ item.networkInterfaces[0].accessConfigs[0].natIP }}", 25 'user': "{{ molecule_yml.driver.ssh_user }}", 26 'port': "22", 27 'identity_file': "{{ molecule_yml.driver.ssh_key_file }}", } 28 with_items: "{{ instance_created.instances }}" 29 register: instance_config_dict 30 when: instance_created.changed
, , VMWare, servers is changed
, , :
molecule create --scenario-name=gcp
, , / :
- lint,
- prepare, . ubuntu.
- converge,
- idempotence,
- verify, ,
molecule <phase> --scenario-name=gcp
, , , .
destroy.yml
( ). , , . , :
molecule destroy --scenario-name=gcp
, :
molecule test --scenario-test=gcp
, Molecule, , GCP. : AWS, Azure, Digital Ocean⦠, Molecule. , .