53 Commits

Author SHA1 Message Date
6c4c20baa6 Add possibility to create networks
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is passing
2021-06-02 10:51:44 +02:00
3617624623 Merge pull request #28 from aminvakil/patch-1
All checks were successful
continuous-integration/drone/push Build is passing
Typo fix
2021-03-30 14:47:25 +02:00
c6ef65d93b Typo fix 2021-03-30 17:14:37 +04:30
0cbe1e327a Add change log entry
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2021-03-30 14:15:02 +02:00
d075adc50d Relax molecule bounds
Some checks reported errors
continuous-integration/drone/push Build was killed
continuous-integration/drone/pr Build is failing
See https://github.com/ansible-community/molecule-hetznercloud/pull/27.
2021-03-30 14:05:54 +02:00
2e3d6bf892 Install ansible now
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-06 22:22:42 +01:00
d8125f4777 Document bug and show more optional arguments 2021-01-06 22:22:28 +01:00
7eb47daace Only cover role init now 2021-01-06 22:22:15 +01:00
5e74393578 Add install and upgrade docs 2021-01-06 22:22:00 +01:00
ec238fe51b Appease linter
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-06 15:27:53 +01:00
1423840fba Fixup volume creation functionality
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/tag Build is failing
2021-01-06 15:21:42 +01:00
5fd3f5ca94 Actually, this is a breaking release, mark it so 2021-01-06 13:29:19 +01:00
f226bb757a Install Ansible for integration testing 2021-01-06 13:23:11 +01:00
8226a462de Mark release and remove dev facing issue 2021-01-06 13:16:26 +01:00
2fffc4882b Fix typo 2021-01-06 13:13:51 +01:00
781e7ec7bb Support dropping sh dependency
Some checks failed
continuous-integration/drone/push Build is failing
2021-01-06 13:09:05 +01:00
8e06794f3c Document volume handling
Some checks failed
continuous-integration/drone/push Build is failing
2021-01-06 12:17:20 +01:00
92f645e43d Merge pull request 'Native ansible filters and volumes handling' (#10) from volumes-handling into main
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #10
2021-01-06 12:06:51 +01:00
d57f80a496 Support volumes handling
Some checks reported errors
continuous-integration/drone/push Build was killed
continuous-integration/drone/pr Build was killed
2021-01-06 12:01:37 +01:00
5efff53088 Use native ansible handlers 2021-01-06 12:01:37 +01:00
488469e927 Fix path in docs
Some checks failed
continuous-integration/drone/push Build is failing
2021-01-06 12:00:34 +01:00
9bf4196816 Py39, Molecule 3.2.1 and dep tree upgrade 2021-01-06 11:49:48 +01:00
447585b8c5 Merge pull request 'Support CI for PRs' (#9) from support-drone-prs into main
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #9
2021-01-06 10:58:53 +01:00
f084833ed2 Remove branch trigger focus
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2021-01-06 10:55:47 +01:00
0464272787 Mark branch change in change log 2021-01-06 10:53:13 +01:00
88ffc62389 Point to an open issue tracker
All checks were successful
continuous-integration/drone/push Build is passing
Closes https://github.com/ansible-community/molecule-hetznercloud/issues/15.
2020-06-15 15:53:27 +02:00
d98183e095 Add change log entry
All checks were successful
continuous-integration/drone/push Build is passing
2020-04-29 12:48:06 +02:00
03671f5ead Pin hcloud under testing
All checks were successful
continuous-integration/drone/push Build is passing
2020-04-29 10:57:18 +02:00
33e2a4f2bf Upgrade Molecule 2020-04-29 10:57:12 +02:00
10d26d1518 Only trigger on master builds
Some checks failed
continuous-integration/drone/push Build is failing
2020-04-27 21:16:59 +02:00
d2d548b166 Take a clarifying pass at README
All checks were successful
continuous-integration/drone/push Build is passing
2020-04-27 16:27:23 +02:00
837de64be9 Clarify change log entry
Some checks reported errors
continuous-integration/drone/push Build was killed
2020-04-27 16:15:17 +02:00
5786a60678 Add note about reduced configurations work
All checks were successful
continuous-integration/drone/push Build is passing
2020-04-27 16:01:00 +02:00
671e92cd0b Actually don't embed prepare.yml
It can be customised if needed, otherwise not used.
2020-04-27 16:00:39 +02:00
dfb003fdcb Remove embedded converge.yml
All checks were successful
continuous-integration/drone/push Build is passing
See https://github.com/ansible-community/molecule/issues/2675.
2020-04-27 15:51:28 +02:00
a2ee39a2f0 Converge must stay for now
Some checks reported errors
continuous-integration/drone/push Build was killed
See https://github.com/ansible-community/molecule/issues/2675.
2020-04-27 15:50:45 +02:00
9164e92d38 Add missing change log entry 2020-04-27 15:26:35 +02:00
5f57fb2c95 Have all jobs depend on linters to save resources
Some checks failed
continuous-integration/drone/push Build is failing
2020-04-27 15:25:45 +02:00
3a1602382a Appease linter with spacing
Some checks reported errors
continuous-integration/drone/push Build was killed
2020-04-27 15:25:00 +02:00
5179b284ba Add bundled playbooks
Some checks reported errors
continuous-integration/drone/push Build was killed
2020-04-27 15:20:47 +02:00
218073759f Add molecule.yml so we can override
See https://github.com/ansible-community/molecule/pull/2666.

I also formatted the cookiecutter.json.

This will work when Molecule 3.0.4 is released.
2020-04-27 15:12:27 +02:00
a25533d917 Add place holder for new API function 2020-04-27 15:02:43 +02:00
f55edfc0e4 Add change log entry
All checks were successful
continuous-integration/drone/push Build is passing
2020-04-27 13:09:08 +02:00
4d195ee238 Merge branch 'master' of github.com:ansible-community/molecule-hetznercloud 2020-04-27 13:08:04 +02:00
b89c10a593 Allow py36 in setuptools requires
All checks were successful
continuous-integration/drone/push Build is passing
2020-04-27 13:07:18 +02:00
2112fc77d4 Add other py versions for testing 2020-04-27 13:07:01 +02:00
f7eb777efb Add other py versions for testing
Some checks failed
continuous-integration/drone/push Build is failing
2020-04-27 13:00:46 +02:00
3528f12b2b Add missing 's'
[ci skip]
2020-04-26 21:07:06 +02:00
e3c20bdb26 Add missing header
All checks were successful
continuous-integration/drone/push Build is passing
2020-04-26 00:57:33 +02:00
6e9ac10f8e Add devel build to get feedback
Some checks reported errors
continuous-integration/drone/push Build was killed
2020-04-26 00:55:21 +02:00
a117a862cf Add funding.yml and remove old files
[ci skip]
2020-04-25 11:41:35 +02:00
b13d902d1f Merge branch 'master' of github.com:ansible-community/molecule-hetznercloud
All checks were successful
continuous-integration/drone/push Build is passing
2020-04-25 08:37:36 +02:00
778147afa0 Fixed linting (#13) 2020-04-24 12:42:01 +01:00
26 changed files with 830 additions and 279 deletions

View File

@ -3,9 +3,9 @@ kind: pipeline
name: linters name: linters
steps: steps:
- name: tox -e linters - name: tox -e linters
image: python:3.8-buster image: python:3.9-buster
commands: commands:
- pip install tox==3.14.6 - pip install tox
- tox -e linters - tox -e linters
--- ---
@ -13,10 +13,36 @@ kind: pipeline
name: packaging name: packaging
steps: steps:
- name: tox -e packaging - name: tox -e packaging
image: python:3.8-buster image: python:3.9-buster
commands: commands:
- pip install tox==3.14.6 - pip install tox
- tox -e packaging - tox -e packaging
depends_on:
- linters
---
kind: pipeline
name: py36
steps:
- name: tox -e py36
image: python:3.6-buster
commands:
- pip install tox
- tox -e py36
depends_on:
- linters
---
kind: pipeline
name: py37
steps:
- name: tox -e py37
image: python:3.7-buster
commands:
- pip install tox
- tox -e py37
depends_on:
- linters
--- ---
kind: pipeline kind: pipeline
@ -25,24 +51,42 @@ steps:
- name: tox -e py38 - name: tox -e py38
image: python:3.8-buster image: python:3.8-buster
commands: commands:
- pip install tox==3.14.6 - pip install tox
- tox -e py38 - tox -e py38
depends_on:
- linters
---
kind: pipeline
name: devel
steps:
- name: tox -e devel
image: python:3.9-buster
failure: ignore
commands:
- pip install tox
- tox -e devel
depends_on:
- linters
--- ---
kind: pipeline kind: pipeline
name: integration name: integration
steps: steps:
- name: molecule test - name: molecule test
image: python:3.8-buster image: python:3.9-buster
environment: environment:
MOLECULE_NO_LOG: false MOLECULE_NO_LOG: false
HCLOUD_TOKEN: HCLOUD_TOKEN:
from_secret: HCLOUD_TOKEN from_secret: HCLOUD_TOKEN
commands: commands:
- pip install -e . - pip install -e .
- pip install "ansible>=2.10, <2.11" netaddr
- export INSTANCE_UUID=$(openssl rand -hex 5) - export INSTANCE_UUID=$(openssl rand -hex 5)
- cd integration && molecule test - cd integration && molecule test
depends_on: depends_on:
- linters - linters
- packaging - packaging
- py36
- py37
- py38 - py38

3
.github/funding.yml vendored Normal file
View File

@ -0,0 +1,3 @@
---
github: decentral1se
liberapay: decentral1se

39
.github/labels.yml vendored
View File

@ -1,39 +0,0 @@
---
# Format and labels used aim to match those used by Ansible project
# https://github.com/marketplace/actions/github-labeler
- name: bug
color: "fbca04"
description: "This issue/PR relates to a bug."
- name: deprecated
color: "fef2c0"
description: "This issue/PR relates to a deprecated module."
- name: docs
color: "4071a5"
description: "This issue/PR relates to or includes documentation."
- name: enhancement
color: "ededed"
description: "This issue/PR relates to a feature request."
- name: feature
color: "006b75"
description: "This issue/PR relates to a feature request."
- name: major
color: "c6476b"
description: "Marks an important and likely breaking change."
- name: packaging
color: "4071a5"
description: "Packaging category"
- name: performance
color: "555555"
description: "Relates to product or testing performance."
- name: skip-changelog
color: "eeeeee"
description: "Can be missed from the changelog."
- name: stale
color: "eeeeee"
description: "Not updated in long time, will be closed soon."
- name: wontfix
color: "eeeeee"
description: "This will not be worked on"
- name: test
color: "0e8a16"
description: "This PR relates to tests, QA, CI."

View File

@ -1,19 +0,0 @@
---
categories:
- title: 'Features'
labels:
- 'feature'
- 'enhancement'
- title: 'Bug Fixes'
labels:
- 'fix'
- 'bugfix'
- 'bug'
- title: 'Maintenance'
label: 'chore'
exclude-labels:
- 'skip-changelog'
template: |
## Changes
$CHANGES

View File

@ -7,6 +7,51 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## [1.1.0] - 2021-03-30
## Changed
- Relaxed bounds on Molecule to allow all versions < v4 ([#27](https://github.com/ansible-community/molecule-hetznercloud/pull/27))
## [1.0.0] - 2021-01-06
This is a major release with breaking changes for your schema and support for a
new major version of Molecule. If you use the `volumes:` key in your
`molecule.yml` then this change will break your configuration. Please see the
section on "Volume Handling" in the README.md on how to upgrade successfully.
You will now need to install Ansible yourself as Molecule does not do it for
you. If there are any other breaking changes, please report them on the issue
tracker so that we can mention them here.
- Support Python 3.9.
- Support Molecule 3.2.1
- Add volume creation and clean up handling
## [0.2.2] - 2020-06-15
## Fixed
- Point to an open issue tracker
## [0.2.1] - 2020-04-29
### Fixed
- Pinned Molecule to avoid issues with `sh` dependency.
## [0.2.0] - 2020-04-27
### Added
- Add bundled playbooks so as to reduce required configuration on end-user side
- Added an internal `molecule.yml` so that `molecule init role` can get good defaults (will work with Molecule >= 3.0.4)
## [0.1.0] - 2020-04-27
### Added
- py36,37,38 are now supported
## [0.0.1] - 2020-04-25 ## [0.0.1] - 2020-04-25
### Added ### Added

113
README.md
View File

@ -21,19 +21,33 @@ useful, please support my maintenance work financially through my
Sponsor profile](https://github.com/sponsors/decentral1se). I do not receive Sponsor profile](https://github.com/sponsors/decentral1se). I do not receive
any financial support from RedHat or Hetzner Cloud for this work. any financial support from RedHat or Hetzner Cloud for this work.
## Install
```bash
$ pip install molecule-hetznercloud
```
## Upgrade
Please see the [CHANGELOG.md](./CHANGELOG.md) for migration guides.
```bash
$ pip install --upgrade molecule-hetznercloud
```
## Usage ## Usage
You need to expose a `HCLOUD_TOKEN` environment variable in your environment. You need to expose a `HCLOUD_TOKEN` environment variable in your environment.
Find out more about how to get one of those [over here](https://docs.hetzner.cloud/#overview-authentication). Find out more about how to get one of those [over here](https://docs.hetzner.cloud/#overview-authentication).
```bash ```bash
$ export HCLOUD_TOKEN=mycoolapitoken $ export HCLOUD_TOKEN=mycoolapitoken
``` ```
Then install the required Python package. Then create a role using the driver plugin.
```bash ```bash
$ pip install molecule-hetznercloud
$ molecule init role myrolename -d hetznercloud $ molecule init role myrolename -d hetznercloud
``` ```
@ -46,7 +60,7 @@ dependency:
driver: driver:
name: hetznercloud name: hetznercloud
platforms: platforms:
- name: my-instance-name - name: instance
server_type: cx11 server_type: cx11
image: debian-10 image: debian-10
provisioner: provisioner:
@ -63,7 +77,7 @@ Then just run the role.
$ cd myrolename && molecule test $ cd myrolename && molecule test
``` ```
To ease initial debugging for getting thing started, also expose the following To ease initial debugging for getting things started, also expose the following
environment variables. environment variables.
```bash ```bash
@ -71,6 +85,87 @@ $ export MOLECULE_NO_LOG=False # not so verbose, helpful
$ export MOLECULE_DEBUG=True # very verbose, last ditch effort $ export MOLECULE_DEBUG=True # very verbose, last ditch effort
``` ```
## Volume Handling
It is possible to have the driver manage volumes during the test run.
You can add the following stanza to your Molecule configuration to have
Molecule create this volume for the managed VPS. This volume will be cleaned up
after use (**Please note**: there is a bug raised against clean-up right now,
see [#24](https://github.com/ansible-community/molecule-hetznercloud/issues/24)
for more).
```yaml
platforms:
- name: instance
server_type: cx11
image: debian-10
volumes:
- name: "molecule-hetznercloud-volume-1-${INSTANCE_UUID}"
location: /foo/bar
- name: "molecule-hetznercloud-volume-2-${INSTANCE_UUID}"
size: 20
```
Supported keys are:
- **name** (required): name of volume
- **size** (optional, default: `10GB`): size of volume
- **location** (optional, default: `omitted`): path for volume
## Network Creation
This Driver is able to generate networks and subnetworks during the test run.
This can be useful for cluster tests. You can create networks with the
following snippet:
```yaml
platforms:
- name: instance1
server_type: cx11
image: debian-10
networks:
test-network:
ip_range: 10.10.0.0/16
subnet:
ip: 10.10.10.1/24
type: cloud
network_zone: eu-central
test-network-2:
ip_range: 10.20.0.0/16
subnet:
ip: 10.20.10.1/24
- name: instance2
server_type: cx11
image: debian-10
networks:
test-network:
subnet:
ip: 10.10.10.2/24
```
The networks **ip_range** is only important for creating. If you have multiple
hosts, it is okay to only define **ip_range** once. The supported keys are:
- **networks**
- **ip_range** (required): ip range of network (usually `/16`)
- **subnet**
- **ip** (required): ip that should be assigned to host (also generates subnetwork) - prefix mandatory
- **type** (optional, default: `cloud`): type of subnetwork
- **network_zone** (optional, default: `eu-central`): network zone of subnetwork
## Only use `molecule.yml` for configuration
It is being worked on that it is possible to remove all the files except the
`molecule.yml` scenario file in your scenario directory. This is useful when
you only require this plugin to do the default behaviour each time. It is also
useful to reduce maintenance effort for migration of configurations. This
plugin currently embeds the `create.yml` and `destroy.yml` playbooks. All other
playbooks (e.g. prepare, cleanup) can be created as needed and Molecule will
pick them up and run them. Embedding the `converge.yml` awaits [this feature
request](https://github.com/ansible-community/molecule/issues/2675).
## Mirroring ## Mirroring
Issues will be responded to on both issue trackers. Issues will be responded to on both issue trackers.
@ -98,21 +193,21 @@ The [LGPLv3](https://www.gnu.org/licenses/lgpl-3.0.en.html) license.
This is all done on our [drone.autonomic.zone](https://drone.autonomic.zone/autonomic-cooperative/molecule-hetznercloud) setup. This is all done on our [drone.autonomic.zone](https://drone.autonomic.zone/autonomic-cooperative/molecule-hetznercloud) setup.
Unit tests and such. ### Unit
```bash ```bash
$ pip install tox $ pip install tox
$ tox -v $ tox -v
``` ```
Integration tests. ### Integration
(Only doable by [Autonomic Cooperative](https://autonomic.zone/) members.) Only doable by [Autonomic Cooperative](https://autonomic.zone/) members.
```bash ```bash
$ sudo apt install -y direnv $ sudo apt install -y direnv
$ cp .envrc.sample .envrc $ cp .envrc.sample .envrc
$ direnv allow $ direnv allow
$ pip install -e . $ pip install -e . ansible
$ cd integration-test-role && molecule test $ cd integration && molecule test
``` ```

View File

@ -1,85 +0,0 @@
---
- name: Create
hosts: localhost
connection: local
gather_facts: false
no_log: "{{ molecule_no_log }}"
vars:
ssh_port: 22
ssh_user: root
ssh_path: "{{ lookup('env', 'MOLECULE_EPHEMERAL_DIRECTORY') }}/ssh_key"
tasks:
- name: Create SSH key
openssh_keypair:
path: "{{ ssh_path }}"
force: true
register: generated_ssh_key
- name: Register the SSH key name
set_fact:
ssh_key_name: "molecule-generated-{{ 12345 | random | to_uuid }}"
- name: Register SSH key for test instance(s)
hcloud_ssh_key:
name: "{{ ssh_key_name }}"
public_key: "{{ generated_ssh_key.public_key }}"
api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}"
state: present
- name: Create molecule instance(s)
hcloud_server:
name: "{{ item.name }}"
server_type: "{{ item.server_type }}"
ssh_keys:
- "{{ ssh_key_name }}"
volumes: "{{ item.volumes | default(omit) }}"
image: "{{ item.image }}"
location: "{{ item.location | default(omit) }}"
datacenter: "{{ item.datacenter | default(omit) }}"
user_data: "{{ item.user_data | default(omit) }}"
api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}"
state: present
register: server
with_items: "{{ molecule_yml.platforms }}"
async: 7200
poll: 0
- name: Wait for instance(s) creation to complete
async_status:
jid: "{{ item.ansible_job_id }}"
register: hetzner_jobs
until: hetzner_jobs.finished
retries: 300
with_items: "{{ server.results }}"
- name: Populate instance config dict
set_fact:
instance_conf_dict: {
'instance': "{{ item.hcloud_server.name }}",
'ssh_key_name': "{{ ssh_key_name }}",
'address': "{{ item.hcloud_server.ipv4_address }}",
'user': "{{ ssh_user }}",
'port': "{{ ssh_port }}",
'identity_file': "{{ ssh_path }}", }
with_items: "{{ hetzner_jobs.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
- name: Wait for SSH
wait_for:
port: "{{ ssh_port }}"
host: "{{ item.address }}"
search_regex: SSH
delay: 10
with_items: "{{ lookup('file', molecule_instance_config) | molecule_from_yaml }}"

View File

@ -7,6 +7,20 @@ platforms:
- name: "molecule-hetznercloud-${INSTANCE_UUID}" - name: "molecule-hetznercloud-${INSTANCE_UUID}"
server_type: cx11 server_type: cx11
image: debian-10 image: debian-10
volumes:
- name: "molecule-hetznercloud-volume-1-${INSTANCE_UUID}"
# - name: "molecule-hetznercloud-volume-2-${INSTANCE_UUID}"
networks:
molecule-hetznercloud-network-1:
ip_range: 10.10.0.0/16
subnet:
ip: 10.10.10.1/24
type: cloud
network_zone: eu-central
molecule-hetznercloud-network-2:
ip_range: 10.20.0.0/16
subnet:
ip: 10.20.10.1/24
provisioner: provisioner:
name: ansible name: ansible
verifier: verifier:

View File

@ -1,5 +1,5 @@
{ {
"molecule_directory": "molecule", "molecule_directory": "molecule",
"role_name": "OVERRIDDEN", "role_name": "OVERRIDDEN",
"scenario_name": "OVERRIDDEN" "scenario_name": "OVERRIDDEN"
} }

View File

@ -5,7 +5,7 @@ Hetzner Cloud plugin installation guide
Requirements Requirements
============ ============
* Ansible >= 2.9 * Ansible >= 2.10
* ``HCLOUD_TOKEN`` exposed in your environment * ``HCLOUD_TOKEN`` exposed in your environment
Install Install

View File

@ -33,7 +33,6 @@
server_type: "{{ item.server_type }}" server_type: "{{ item.server_type }}"
ssh_keys: ssh_keys:
- "{{ ssh_key_name }}" - "{{ ssh_key_name }}"
volumes: "{{ item.volumes | default(omit) }}"
image: "{{ item.image }}" image: "{{ item.image }}"
location: "{{ item.location | default(omit) }}" location: "{{ item.location | default(omit) }}"
datacenter: "{{ item.datacenter | default(omit) }}" datacenter: "{{ item.datacenter | default(omit) }}"
@ -53,6 +52,73 @@
retries: 300 retries: 300
with_items: "{{ server.results }}" with_items: "{{ server.results }}"
- name: Create volume(s)
hcloud_volume:
name: "{{ item.volumes.name | default(item.name) }}"
server: "{{ item.name }}"
location: "{{ item.volumes.location | default(omit) }}"
size: "{{ item.volumes.size | default(10) }}"
api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}"
state: "present"
with_items: "{{ molecule_yml.platforms }}"
when:
- item.volumes is defined
- item.volumes.create | default(False) | bool
register: volumes
async: 7200
poll: 0
- name: Wait for volume(s) creation to complete
async_status:
jid: "{{ item.ansible_job_id }}"
register: hetzner_volumes
until: hetzner_volumes.finished
retries: 300
when: volumes.changed
with_items: "{{ volumes.results }}"
- name: Create private network(s)
hcloud_network:
name: "{{ item.name }}"
ip_range: "{{ item.ip_range | default(omit) }}"
api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}"
state: "present"
loop: "{{ molecule_yml.platforms|molecule_get_hetznercloud_networks('networks') }}"
register: networks
async: 7200
poll: 0
- name: Wait for network(s) creation to complete
async_status:
jid: "{{ item.ansible_job_id }}"
register: hetzner_networks
until: hetzner_networks.finished
retries: 300
when:
- networks is defined
- networks.changed
with_items: "{{ networks.results }}"
- name: Create private subnetwork(s)
hcloud_subnetwork:
network: "{{ item.network_name }}"
ip_range: "{{ item.ip|ipaddr('network/prefix') }}"
network_zone: "{{ item.network_zone | default('eu-central') }}"
type: "{{ item.type | default('cloud') }}"
api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}"
state: "present"
loop: "{{ molecule_yml.platforms|molecule_get_hetznercloud_networks('subnetworks') }}"
register: subnetworks
- name: Attach Server to Subnetwork(s)
hcloud_server_network:
network: "{{ item.network_name }}"
server: "{{ item.server_name }}"
ip: "{{ item.ip|ipaddr('address') }}"
api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}"
state: "present"
loop: "{{ molecule_yml.platforms|molecule_get_hetznercloud_networks('subnetworks') }}"
- name: Populate instance config dict - name: Populate instance config dict
set_fact: set_fact:
instance_conf_dict: { instance_conf_dict: {
@ -61,7 +127,9 @@
'address': "{{ item.hcloud_server.ipv4_address }}", 'address': "{{ item.hcloud_server.ipv4_address }}",
'user': "{{ ssh_user }}", 'user': "{{ ssh_user }}",
'port': "{{ ssh_port }}", 'port': "{{ ssh_port }}",
'identity_file': "{{ ssh_path }}", } 'identity_file': "{{ ssh_path }}",
'volumes': "{{ item.item.item.volumes | default({}) }}",
'networks': "{{ item.item.item.networks | default({}) | dict2items(key_name='name', value_name='data') }}", }
with_items: "{{ hetzner_jobs.results }}" with_items: "{{ hetzner_jobs.results }}"
register: instance_config_dict register: instance_config_dict
when: server.changed | bool when: server.changed | bool
@ -73,7 +141,10 @@
- name: Dump instance config - name: Dump instance config
copy: copy:
content: "{{ instance_conf | to_json | from_json | molecule_to_yaml | molecule_header }}" content: |
# Molecule managed
{{ instance_conf | to_json | from_json | to_yaml }}
dest: "{{ molecule_instance_config }}" dest: "{{ molecule_instance_config }}"
when: server.changed | bool when: server.changed | bool
@ -83,5 +154,5 @@
host: "{{ item.address }}" host: "{{ item.address }}"
search_regex: SSH search_regex: SSH
delay: 10 delay: 10
with_items: "{{ lookup('file', molecule_instance_config) | molecule_from_yaml }}" with_items: "{{ lookup('file', molecule_instance_config) | from_yaml }}"
{%- endraw %} {%- endraw %}

View File

@ -10,7 +10,7 @@
block: block:
- name: Populate instance config from file - name: Populate instance config from file
set_fact: set_fact:
instance_conf: "{{ lookup('file', molecule_instance_config) | molecule_from_yaml }}" instance_conf: "{{ lookup('file', molecule_instance_config) | from_yaml }}"
skip_instances: false skip_instances: false
rescue: rescue:
- name: Populate instance config when file missing - name: Populate instance config when file missing
@ -37,6 +37,48 @@
retries: 300 retries: 300
with_items: "{{ server.results }}" with_items: "{{ server.results }}"
- name: Destroy volume(s)
hcloud_volume:
name: "{{ item.volumes.name | default(item.instance) }}"
server: "{{ item.instance }}"
api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}"
state: "absent"
register: volumes
with_items: "{{ instance_conf }}"
when:
- item.volumes is defined
- item.volumes.create | default(False) | bool
async: 7200
poll: 0
- name: Wait for volume(s) deletion to complete
async_status:
jid: "{{ item.ansible_job_id }}"
register: hetzner_volumes
until: hetzner_volumes.finished
retries: 300
when: volumes.changed
with_items: "{{ volumes.results }}"
- name: Destroy network(s)
hcloud_network:
name: "{{ item.1.name }}"
api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}"
state: absent
register: networks
loop: "{{ instance_conf|subelements('networks', skip_missing=True) }}"
async: 7200
poll: 0
- name: Wait for network(s) deletion to complete
async_status:
jid: "{{ item.ansible_job_id }}"
register: hetzner_networks
until: hetzner_networks.finished
retries: 300
when: networks.changed
with_items: "{{ networks.results }}"
- name: Remove registered SSH key - name: Remove registered SSH key
hcloud_ssh_key: hcloud_ssh_key:
name: "{{ instance_conf[0].ssh_key_name }}" name: "{{ instance_conf[0].ssh_key_name }}"
@ -52,7 +94,10 @@
- name: Dump instance config - name: Dump instance config
copy: copy:
content: "{{ instance_conf | molecule_to_yaml | molecule_header }}" content: |
# Molecule managed
{{ instance_conf | to_json | from_json | to_yaml }}
dest: "{{ molecule_instance_config }}" dest: "{{ molecule_instance_config }}"
when: server.changed | bool when: server.changed | bool
{%- endraw %} {%- endraw %}

View File

@ -0,0 +1,28 @@
---
dependency:
name: galaxy
driver:
name: hetznercloud
platforms:
- name: "{{ cookiecutter.role_name }}"
server_type: cx11
image: debian-10
volumes:
- name: "molecule-hetznercloud-volume-1"
networks:
molecule-hetznercloud-network-1:
ip_range: 10.10.0.0/16
subnet:
ip: 10.10.10.1/24
type: cloud
network_zone: eu-central
molecule-hetznercloud-network-2:
ip_range: 10.20.0.0/16
subnet:
ip: 10.20.10.1/24
provisioner:
name: ansible
lint: |
set -e
yamllint .
ansible-lint .

View File

@ -98,3 +98,8 @@ class HetznerCloud(Driver):
"account API token value" "account API token value"
) )
sysexit_with_message(msg) sysexit_with_message(msg)
def reset(self):
"""Destroy all resources managed by this plugin."""
# TODO(decentral1se): implement if ever needed
pass

View File

@ -0,0 +1,157 @@
---
- name: Create
hosts: localhost
connection: local
gather_facts: false
no_log: "{{ molecule_no_log }}"
vars:
ssh_port: 22
ssh_user: root
ssh_path: "{{ lookup('env', 'MOLECULE_EPHEMERAL_DIRECTORY') }}/ssh_key"
tasks:
- name: Create SSH key
openssh_keypair:
path: "{{ ssh_path }}"
force: true
register: generated_ssh_key
- name: Register the SSH key name
set_fact:
ssh_key_name: "molecule-generated-{{ 12345 | random | to_uuid }}"
- name: Register SSH key for test instance(s)
hcloud_ssh_key:
name: "{{ ssh_key_name }}"
public_key: "{{ generated_ssh_key.public_key }}"
api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}"
state: present
- name: Create molecule instance(s)
hcloud_server:
name: "{{ item.name }}"
server_type: "{{ item.server_type }}"
ssh_keys:
- "{{ ssh_key_name }}"
image: "{{ item.image }}"
location: "{{ item.location | default(omit) }}"
datacenter: "{{ item.datacenter | default(omit) }}"
user_data: "{{ item.user_data | default(omit) }}"
api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}"
state: present
register: server
with_items: "{{ molecule_yml.platforms }}"
async: 7200
poll: 0
- name: Wait for instance(s) creation to complete
async_status:
jid: "{{ item.ansible_job_id }}"
register: hetzner_jobs
until: hetzner_jobs.finished
retries: 300
with_items: "{{ server.results }}"
- name: Create volume(s)
hcloud_volume:
name: "{{ item.1.name | default(item.0.name) }}"
server: "{{ item.0.name }}"
location: "{{ item.1.location | default(omit) }}"
size: "{{ item.1.size | default(10) }}"
api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}"
state: "present"
loop: "{{ molecule_yml.platforms|subelements('volumes', skip_missing=True)}}"
register: volumes
async: 7200
poll: 0
- name: Wait for volume(s) creation to complete
async_status:
jid: "{{ item.ansible_job_id }}"
register: hetzner_volumes
until: hetzner_volumes.finished
retries: 300
when:
- volumes is defined
- volumes.changed
with_items: "{{ volumes.results }}"
- name: Create private network(s)
hcloud_network:
name: "{{ item.name }}"
ip_range: "{{ item.ip_range | default(omit) }}"
api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}"
state: "present"
loop: "{{ molecule_yml.platforms|molecule_get_hetznercloud_networks('networks') }}"
register: networks
async: 7200
poll: 0
- name: Wait for network(s) creation to complete
async_status:
jid: "{{ item.ansible_job_id }}"
register: hetzner_networks
until: hetzner_networks.finished
retries: 300
when:
- networks is defined
- networks.changed
with_items: "{{ networks.results }}"
- name: Create private subnetwork(s)
hcloud_subnetwork:
network: "{{ item.network_name }}"
ip_range: "{{ item.ip|ipaddr('network/prefix') }}"
network_zone: "{{ item.network_zone | default('eu-central') }}"
type: "{{ item.type | default('cloud') }}"
api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}"
state: "present"
loop: "{{ molecule_yml.platforms|molecule_get_hetznercloud_networks('subnetworks') }}"
register: subnetworks
- name: Attach Server to Subnetwork(s)
hcloud_server_network:
network: "{{ item.network_name }}"
server: "{{ item.server_name }}"
ip: "{{ item.ip|ipaddr('address') }}"
api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}"
state: "present"
loop: "{{ molecule_yml.platforms|molecule_get_hetznercloud_networks('subnetworks') }}"
- name: Populate instance config dict
set_fact:
instance_conf_dict:
{
"instance": "{{ item.hcloud_server.name }}",
"ssh_key_name": "{{ ssh_key_name }}",
"address": "{{ item.hcloud_server.ipv4_address }}",
"user": "{{ ssh_user }}",
"port": "{{ ssh_port }}",
"identity_file": "{{ ssh_path }}",
"volumes": "{{ item.item.item.volumes | default({}) }}",
"networks": "{{ item.item.item.networks | default({}) | dict2items(key_name='name', value_name='data') }}",
}
with_items: "{{ hetzner_jobs.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: |
# Molecule managed
{{ instance_conf | to_json | from_json | to_yaml }}
dest: "{{ molecule_instance_config }}"
when: server.changed | bool
- name: Wait for SSH
wait_for:
port: "{{ ssh_port }}"
host: "{{ item.address }}"
search_regex: SSH
delay: 10
with_items: "{{ lookup('file', molecule_instance_config) | from_yaml }}"

View File

@ -9,7 +9,7 @@
block: block:
- name: Populate instance config from file - name: Populate instance config from file
set_fact: set_fact:
instance_conf: "{{ lookup('file', molecule_instance_config) | molecule_from_yaml }}" instance_conf: "{{ lookup('file', molecule_instance_config) | from_yaml }}"
skip_instances: false skip_instances: false
rescue: rescue:
- name: Populate instance config when file missing - name: Populate instance config when file missing
@ -36,6 +36,45 @@
retries: 300 retries: 300
with_items: "{{ server.results }}" with_items: "{{ server.results }}"
- name: Destroy volume(s)
hcloud_volume:
name: "{{ item.volumes.name | default(item.instance) }}"
server: "{{ item.instance }}"
api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}"
state: absent
register: volumes
with_items: "{{ instance_conf }}"
async: 7200
poll: 0
- name: Wait for volume(s) deletion to complete
async_status:
jid: "{{ item.ansible_job_id }}"
register: hetzner_volumes
until: hetzner_volumes.finished
retries: 300
when: volumes.changed
with_items: "{{ volumes.results }}"
- name: Destroy network(s)
hcloud_network:
name: "{{ item.1.name }}"
api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}"
state: absent
register: networks
loop: "{{ instance_conf|subelements('networks', skip_missing=True) }}"
async: 7200
poll: 0
- name: Wait for network(s) deletion to complete
async_status:
jid: "{{ item.ansible_job_id }}"
register: hetzner_networks
until: hetzner_networks.finished
retries: 300
when: networks.changed
with_items: "{{ networks.results }}"
- name: Remove registered SSH key - name: Remove registered SSH key
hcloud_ssh_key: hcloud_ssh_key:
name: "{{ instance_conf[0].ssh_key_name }}" name: "{{ instance_conf[0].ssh_key_name }}"
@ -51,6 +90,9 @@
- name: Dump instance config - name: Dump instance config
copy: copy:
content: "{{ instance_conf | molecule_to_yaml | molecule_header }}" content: |
# Molecule managed
{{ instance_conf | to_json | from_json | to_yaml }}
dest: "{{ molecule_instance_config }}" dest: "{{ molecule_instance_config }}"
when: server.changed | bool when: server.changed | bool

View File

@ -0,0 +1,54 @@
#!/usr/bin/env python
"""Usage:"""
""" loop: "{{ molecule_yml.platforms|molecule_get_hetznercloud_networks('networks') }}" """ # noqa
""" loop: "{{ molecule_yml.platforms|molecule_get_hetznercloud_networks('subnetworks') }}" """ # noqa
def merge_two_dicts(x, y):
z = x.copy()
z.update(y)
return z
def get_hetznercloud_networks(data, request):
network_list = {}
subnetwork_list = []
if request == "networks":
for platform in data:
if "networks" in platform:
for network_name, values in platform["networks"].items():
del values["subnet"]
values["name"] = network_name
if network_name in network_list:
network_list[network_name] = merge_two_dicts(
network_list[network_name], values
)
else:
network_list[network_name] = values
return [x for x in network_list.values()]
elif request == "subnetworks":
for platform in data:
name = platform["name"]
if "networks" in platform:
for network_name, values in platform["networks"].items():
values["name"] = network_name
if "subnet" in values:
values["subnet"]["server_name"] = name
values["subnet"]["network_name"] = network_name
subnetwork_list.append(values["subnet"])
return subnetwork_list
class FilterModule(object):
"""Core Molecule filter plugins."""
def filters(self):
return {
"molecule_get_hetznercloud_networks": get_hetznercloud_networks,
}

View File

@ -12,13 +12,14 @@ LOG = logger.get_logger(__name__)
@pytest.helpers.register @pytest.helpers.register
def run_command(cmd, env=os.environ, log=True): def run_command(cmd, env=os.environ, log=True):
if log: if cmd.__class__.__name__ == "Command":
cmd = _rebake_command(cmd, env) if log:
cmd = cmd.bake(_truncate_exc=False) cmd = _rebake_command(cmd, env)
return util.run_command(cmd) cmd = cmd.bake(_truncate_exc=False)
return util.run_command(cmd, env=env)
def _rebake_command(cmd, env, out=LOG.out, err=LOG.error): def _rebake_command(cmd, env, out=LOG.info, err=LOG.error):
return cmd.bake(_env=env, _out=out, _err=err) return cmd.bake(_env=env, _out=out, _err=err)

View File

@ -1,13 +1,10 @@
import os import os
import pkg_resources
import shutil import shutil
import pexpect import pexpect
import pkg_resources
import pytest import pytest
import sh from molecule import logger, util
from molecule import logger
from molecule import util
from ..conftest import change_dir_to from ..conftest import change_dir_to
@ -37,9 +34,8 @@ def with_scenario(request, scenario_to_test, driver_name, scenario_name, skip_te
yield yield
if scenario_name: if scenario_name:
msg = "CLEANUP: Destroying instances for all scenario(s)" msg = "CLEANUP: Destroying instances for all scenario(s)"
LOG.out(msg) LOG.info(msg)
options = {"driver_name": driver_name, "all": True} cmd = ["molecule", "destroy", "--driver-name", driver_name, "--all"]
cmd = sh.molecule.bake("destroy", **options)
pytest.helpers.run_command(cmd) pytest.helpers.run_command(cmd)
@ -63,56 +59,56 @@ def skip_test(request, driver_name):
@pytest.helpers.register @pytest.helpers.register
def idempotence(scenario_name): def idempotence(scenario_name):
options = {"scenario_name": scenario_name} cmd = ["molecule", "create", "--scenario-name", scenario_name]
cmd = sh.molecule.bake("create", **options)
pytest.helpers.run_command(cmd) pytest.helpers.run_command(cmd)
options = {"scenario_name": scenario_name} cmd = ["molecule", "converge", "--scenario_name", scenario_name]
cmd = sh.molecule.bake("converge", **options)
pytest.helpers.run_command(cmd) pytest.helpers.run_command(cmd)
options = {"scenario_name": scenario_name} cmd = ["molecule", "--scenario_name", scenario_name]
cmd = sh.molecule.bake("idempotence", **options)
pytest.helpers.run_command(cmd) pytest.helpers.run_command(cmd)
@pytest.helpers.register @pytest.helpers.register
def init_role(temp_dir, driver_name): def init_role(temp_dir, driver_name):
role_directory = os.path.join(temp_dir.strpath, "test-init") cmd = ["molecule", "init", "role", "test-init", "--driver-name", driver_name]
cmd = sh.molecule.bake(
"init", "role", {"driver-name": driver_name, "role-name": "test-init"}
)
pytest.helpers.run_command(cmd) pytest.helpers.run_command(cmd)
role_directory = os.path.join(temp_dir.strpath, "test-init")
pytest.helpers.metadata_lint_update(role_directory) pytest.helpers.metadata_lint_update(role_directory)
with change_dir_to(role_directory): with change_dir_to(role_directory):
options = {"all": True} cmd = ["molecule", "test", "--all"]
cmd = sh.molecule.bake("test", **options)
pytest.helpers.run_command(cmd) pytest.helpers.run_command(cmd)
@pytest.helpers.register @pytest.helpers.register
def init_scenario(temp_dir, driver_name): def init_scenario(temp_dir, driver_name):
role_directory = os.path.join(temp_dir.strpath, "test-init") cmd = ["molecule", "init", "role", "test-init", "--driver-name", driver_name]
cmd = sh.molecule.bake(
"init", "role", {"driver-name": driver_name, "role-name": "test-init"}
)
pytest.helpers.run_command(cmd) pytest.helpers.run_command(cmd)
role_directory = os.path.join(temp_dir.strpath, "test-init")
pytest.helpers.metadata_lint_update(role_directory) pytest.helpers.metadata_lint_update(role_directory)
with change_dir_to(role_directory): with change_dir_to(role_directory):
molecule_directory = pytest.helpers.molecule_directory() molecule_directory = pytest.helpers.molecule_directory()
scenario_directory = os.path.join(molecule_directory, "test-scenario") scenario_directory = os.path.join(molecule_directory, "test-scenario")
options = {"scenario_name": "test-scenario", "role_name": "test-init"} cmd = [
cmd = sh.molecule.bake("init", "scenario", **options) "molecule",
"init",
"scenario",
"test-scenario",
"--role-name",
"test-init",
"--driver-name",
driver_name,
]
pytest.helpers.run_command(cmd) pytest.helpers.run_command(cmd)
assert os.path.isdir(scenario_directory) assert os.path.isdir(scenario_directory)
options = {"scenario_name": "test-scenario", "all": True} cmd = ["molecule", "test", "--scenario-name", "test-scenario", "--all"]
cmd = sh.molecule.bake("test", **options)
pytest.helpers.run_command(cmd) pytest.helpers.run_command(cmd)
@ -125,13 +121,13 @@ def metadata_lint_update(role_directory):
shutil.copy(ansible_lint_src, role_directory) shutil.copy(ansible_lint_src, role_directory)
with change_dir_to(role_directory): with change_dir_to(role_directory):
cmd = sh.ansible_lint.bake(".") cmd = ["ansible-lint", "."]
pytest.helpers.run_command(cmd) pytest.helpers.run_command(cmd)
@pytest.helpers.register @pytest.helpers.register
def list(x): def list(x):
cmd = sh.molecule.bake("list") cmd = ["molecule", "list"]
out = pytest.helpers.run_command(cmd, log=False) out = pytest.helpers.run_command(cmd, log=False)
out = out.stdout.decode("utf-8") out = out.stdout.decode("utf-8")
out = util.strip_ansi_color(out) out = util.strip_ansi_color(out)
@ -142,10 +138,9 @@ def list(x):
@pytest.helpers.register @pytest.helpers.register
def list_with_format_plain(x): def list_with_format_plain(x):
cmd = sh.molecule.bake("list", {"format": "plain"}) cmd = ["molecule", "list", "--format", "plain"]
out = pytest.helpers.run_command(cmd, log=False) result = util.run_command(cmd)
out = out.stdout.decode("utf-8") out = util.strip_ansi_color(result.stdout)
out = util.strip_ansi_color(out)
for l in x.splitlines(): for l in x.splitlines():
assert l in out assert l in out
@ -153,12 +148,10 @@ def list_with_format_plain(x):
@pytest.helpers.register @pytest.helpers.register
def login(login_args, scenario_name="default"): def login(login_args, scenario_name="default"):
options = {"scenario_name": scenario_name} cmd = ["molecule", "destroy", "--scenario-name", scenario_name]
cmd = sh.molecule.bake("destroy", **options)
pytest.helpers.run_command(cmd) pytest.helpers.run_command(cmd)
options = {"scenario_name": scenario_name} cmd = ["molecule", "create", "--scenario-name", scenario_name]
cmd = sh.molecule.bake("create", **options)
pytest.helpers.run_command(cmd) pytest.helpers.run_command(cmd)
for instance, regexp in login_args: for instance, regexp in login_args:
@ -175,31 +168,26 @@ def login(login_args, scenario_name="default"):
@pytest.helpers.register @pytest.helpers.register
def test(driver_name, scenario_name="default", parallel=False): def test(driver_name, scenario_name="default", parallel=False):
options = { cmd = ["molecule", "test", "--scenario-name", scenario_name]
"scenario_name": scenario_name,
"all": scenario_name is None,
"parallel": parallel,
}
if driver_name == "delegated": if driver_name != "delegated":
options = {"scenario_name": scenario_name} if scenario_name is None:
cmd.append("--all")
if parallel:
cmd.append("--parallel")
cmd = sh.molecule.bake("test", **options)
pytest.helpers.run_command(cmd) pytest.helpers.run_command(cmd)
@pytest.helpers.register @pytest.helpers.register
def verify(scenario_name="default"): def verify(scenario_name="default"):
options = {"scenario_name": scenario_name} cmd = ["molecule", "create", "--scenario-name", scenario_name]
cmd = sh.molecule.bake("create", **options)
pytest.helpers.run_command(cmd) pytest.helpers.run_command(cmd)
options = {"scenario_name": scenario_name} cmd = ["molecule", "converge", "--scenario-name", scenario_name]
cmd = sh.molecule.bake("converge", **options)
pytest.helpers.run_command(cmd) pytest.helpers.run_command(cmd)
options = {"scenario_name": scenario_name} cmd = ["molecule", "verify", "--scenario-name", scenario_name]
cmd = sh.molecule.bake("verify", **options)
pytest.helpers.run_command(cmd) pytest.helpers.run_command(cmd)

View File

@ -23,6 +23,7 @@
hcloud_ssh_key: hcloud_ssh_key:
name: "{{ ssh_key_name }}" name: "{{ ssh_key_name }}"
public_key: "{{ generated_ssh_key.public_key }}" public_key: "{{ generated_ssh_key.public_key }}"
api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}"
state: present state: present
- name: Create molecule instance(s) - name: Create molecule instance(s)
@ -31,7 +32,6 @@
server_type: "{{ item.server_type }}" server_type: "{{ item.server_type }}"
ssh_keys: ssh_keys:
- "{{ ssh_key_name }}" - "{{ ssh_key_name }}"
volumes: "{{ item.volumes | default(omit) }}"
image: "{{ item.image }}" image: "{{ item.image }}"
location: "{{ item.location | default(omit) }}" location: "{{ item.location | default(omit) }}"
datacenter: "{{ item.datacenter | default(omit) }}" datacenter: "{{ item.datacenter | default(omit) }}"
@ -51,19 +51,84 @@
retries: 300 retries: 300
with_items: "{{ server.results }}" with_items: "{{ server.results }}"
# Mandatory configuration for Molecule to function. - name: Create volume(s)
hcloud_volume:
name: "{{ item.volumes.name | default(item.name) }}"
server: "{{ item.name }}"
location: "{{ item.volumes.location | default(omit) }}"
size: "{{ item.volumes.size | default(10) }}"
api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}"
state: "present"
with_items: "{{ molecule_yml.platforms }}"
when:
- item.volumes is defined
- item.volumes.create | default(False) | bool
register: volumes
async: 7200
poll: 0
- name: Wait for volume(s) creation to complete
async_status:
jid: "{{ item.ansible_job_id }}"
register: hetzner_volumes
until: hetzner_volumes.finished
retries: 300
when: volumes.changed
with_items: "{{ volumes.results }}"
- name: Create private network(s)
hcloud_network:
name: "{{ item.name }}"
ip_range: "{{ item.ip_range | default(omit) }}"
api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}"
state: "present"
loop: "{{ molecule_yml.platforms|molecule_get_hetznercloud_networks('networks') }}"
register: networks
async: 7200
poll: 0
- name: Wait for network(s) creation to complete
async_status:
jid: "{{ item.ansible_job_id }}"
register: hetzner_networks
until: hetzner_networks.finished
retries: 300
when:
- networks is defined
- networks.changed
with_items: "{{ networks.results }}"
- name: Create private subnetwork(s)
hcloud_subnetwork:
network: "{{ item.network_name }}"
ip_range: "{{ item.ip|ipaddr('network/prefix') }}"
network_zone: "{{ item.network_zone | default('eu-central') }}"
type: "{{ item.type | default('cloud') }}"
api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}"
state: "present"
loop: "{{ molecule_yml.platforms|molecule_get_hetznercloud_networks('subnetworks') }}"
register: subnetworks
- name: Attach Server to Subnetwork(s)
hcloud_server_network:
network: "{{ item.network_name }}"
server: "{{ item.server_name }}"
ip: "{{ item.ip|ipaddr('address') }}"
api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}"
state: "present"
loop: "{{ molecule_yml.platforms|molecule_get_hetznercloud_networks('subnetworks') }}"
- name: Populate instance config dict - name: Populate instance config dict
set_fact: set_fact:
instance_conf_dict: instance_conf_dict: {
{ 'instance': "{{ item.hcloud_server.name }}",
"instance": "{{ item.hcloud_server.name }}", 'ssh_key_name': "{{ ssh_key_name }}",
"ssh_key_name": "{{ ssh_key_name }}", 'address': "{{ item.hcloud_server.ipv4_address }}",
"address": "{{ item.hcloud_server.ipv4_address }}", 'user': "{{ ssh_user }}",
"user": "{{ ssh_user }}", 'port': "{{ ssh_port }}",
"port": "{{ ssh_port }}", 'identity_file': "{{ ssh_path }}",
"identity_file": "{{ ssh_path }}", 'volumes': "{{ item.item.item.volumes | default({}) }}",
} 'networks': "{{ item.item.item.networks | default({}) | dict2items(key_name='name', value_name='data') }}", }
with_items: "{{ hetzner_jobs.results }}" with_items: "{{ hetzner_jobs.results }}"
register: instance_config_dict register: instance_config_dict
when: server.changed | bool when: server.changed | bool
@ -75,7 +140,10 @@
- name: Dump instance config - name: Dump instance config
copy: copy:
content: "{{ instance_conf | to_json | from_json | molecule_to_yaml | molecule_header }}" content: |
# Molecule managed
{{ instance_conf | to_json | from_json | to_yaml }}
dest: "{{ molecule_instance_config }}" dest: "{{ molecule_instance_config }}"
when: server.changed | bool when: server.changed | bool
@ -85,4 +153,4 @@
host: "{{ item.address }}" host: "{{ item.address }}"
search_regex: SSH search_regex: SSH
delay: 10 delay: 10
with_items: "{{ lookup('file', molecule_instance_config) | molecule_from_yaml }}" with_items: "{{ lookup('file', molecule_instance_config) | from_yaml }}"

View File

@ -9,7 +9,7 @@
block: block:
- name: Populate instance config from file - name: Populate instance config from file
set_fact: set_fact:
instance_conf: "{{ lookup('file', molecule_instance_config) | molecule_from_yaml }}" instance_conf: "{{ lookup('file', molecule_instance_config) | from_yaml }}"
skip_instances: false skip_instances: false
rescue: rescue:
- name: Populate instance config when file missing - name: Populate instance config when file missing
@ -36,15 +36,56 @@
retries: 300 retries: 300
with_items: "{{ server.results }}" with_items: "{{ server.results }}"
- name: Destroy volume(s)
hcloud_volume:
name: "{{ item.volumes.name | default(item.instance) }}"
server: "{{ item.instance }}"
api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}"
state: "absent"
register: volumes
with_items: "{{ instance_conf }}"
when:
- item.volumes is defined
- item.volumes.create | default(False) | bool
async: 7200
poll: 0
- name: Wait for volume(s) deletion to complete
async_status:
jid: "{{ item.ansible_job_id }}"
register: hetzner_volumes
until: hetzner_volumes.finished
retries: 300
when: volumes.changed
with_items: "{{ volumes.results }}"
- name: Destroy network(s)
hcloud_network:
name: "{{ item.1.name }}"
api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}"
state: absent
register: networks
loop: "{{ instance_conf|subelements('networks', skip_missing=True) }}"
async: 7200
poll: 0
- name: Wait for network(s) deletion to complete
async_status:
jid: "{{ item.ansible_job_id }}"
register: hetzner_networks
until: hetzner_networks.finished
retries: 300
when: networks.changed
with_items: "{{ networks.results }}"
- name: Remove registered SSH key - name: Remove registered SSH key
hcloud_ssh_key: hcloud_ssh_key:
name: "{{ instance_conf[0].ssh_key_name }}" name: "{{ instance_conf[0].ssh_key_name }}"
api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}"
state: absent state: absent
when: when:
- not skip_instances - not skip_instances
- instance_conf # must contain at least one instance - instance_conf | length # must contain at least one instance
# Mandatory configuration for Molecule to function.
- name: Populate instance config - name: Populate instance config
set_fact: set_fact:
@ -52,6 +93,9 @@
- name: Dump instance config - name: Dump instance config
copy: copy:
content: "{{ instance_conf | molecule_to_yaml | molecule_header }}" content: |
# Molecule managed
{{ instance_conf | to_json | from_json | to_yaml }}
dest: "{{ molecule_instance_config }}" dest: "{{ molecule_instance_config }}"
when: server.changed | bool when: server.changed | bool

View File

@ -1,8 +1,6 @@
import os import os
import pytest import pytest
import sh
from molecule import util from molecule import util
from molecule.command.init import base from molecule.command.init import base
from molecule.model import schema_v3 from molecule.model import schema_v3
@ -57,5 +55,5 @@ def test_drivers(
assert {} == schema_v3.validate(data) assert {} == schema_v3.validate(data)
cmd = sh.yamllint.bake("-s", _molecule_file) cmd = ["yamllint", "-s", _molecule_file]
pytest.helpers.run_command(cmd) pytest.helpers.run_command(cmd)

View File

@ -1,7 +1,6 @@
import os import os
import pytest import pytest
from molecule import config from molecule import config
from molecule_hetznercloud import driver from molecule_hetznercloud import driver
@ -15,13 +14,6 @@ def test_hetznercloud_config_gives_config_object(hetznercloud_instance):
assert isinstance(hetznercloud_instance._config, config.Config) assert isinstance(hetznercloud_instance._config, config.Config)
def test_hetznercloud_testinfra_options_property(hetznercloud_instance):
assert {
"connection": "ansible",
"ansible-inventory": hetznercloud_instance._config.provisioner.inventory_file,
} == hetznercloud_instance.testinfra_options
def test_hetznercloud_name_property(hetznercloud_instance): def test_hetznercloud_name_property(hetznercloud_instance):
assert "hetznercloud" == hetznercloud_instance.name assert "hetznercloud" == hetznercloud_instance.name

View File

@ -10,7 +10,7 @@ def _model_platform_hetznercloud_section_data():
{ {
"name": "instance", "name": "instance",
"server_type": "", "server_type": "",
"volumes": [""], "volumes": [],
"image": "", "image": "",
"location": "", "location": "",
"datacenter": "", "datacenter": "",
@ -35,7 +35,7 @@ def _model_platforms_hetznercloud_errors_section_data():
{ {
"name": 0, "name": 0,
"server_type": 0, "server_type": 0,
"volumes": {}, "volumes": [],
"image": 0, "image": 0,
"location": 0, "location": 0,
"datacenter": 0, "datacenter": 0,

View File

@ -8,7 +8,7 @@ universal = 1
name = molecule-hetznercloud name = molecule-hetznercloud
url = https://git.autonomic.zone/autonomic-cooperative/molecule-hetznercloud url = https://git.autonomic.zone/autonomic-cooperative/molecule-hetznercloud
project_urls = project_urls =
Bug Tracker = https://git.autonomic.zone/autonomic-cooperative/molecule-hetznercloud/issues Bug Tracker = https://github.com/ansible-community/molecule-hetznercloud/issues
CI: Drone = https://drone.autonomic.zone/autonomic-cooperative/molecule-hetznercloud/ CI: Drone = https://drone.autonomic.zone/autonomic-cooperative/molecule-hetznercloud/
Source Code = https://git.autonomic.zone/autonomic-cooperative/molecule-hetznercloud Source Code = https://git.autonomic.zone/autonomic-cooperative/molecule-hetznercloud
description = Molecule Hetzner Cloud Plugin :: run molecule tests with hetzner cloud description = Molecule Hetzner Cloud Plugin :: run molecule tests with hetzner cloud
@ -30,7 +30,10 @@ classifiers =
Natural Language :: English Natural Language :: English
Operating System :: OS Independent Operating System :: OS Independent
Programming Language :: Python :: 3 Programming Language :: Python :: 3
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Topic :: System :: Systems Administration Topic :: System :: Systems Administration
Topic :: Utilities Topic :: Utilities
keywords = keywords =
@ -44,28 +47,28 @@ keywords =
[options] [options]
use_scm_version = True use_scm_version = True
python_requires = >=3.8 python_requires = >=3.6
packages = find: packages = find:
include_package_data = True include_package_data = True
zip_safe = False zip_safe = False
setup_requires = setup_requires =
setuptools_scm >= 3.5.0 setuptools_scm
setuptools_scm_git_archive >= 1.1 setuptools_scm_git_archive
install_requires = install_requires =
hcloud >= 1.6.3, < 2 hcloud >= 1.10.0, < 2
molecule >= 3.0.3, <= 3.1 molecule >= 3.2.1, < 4
pyyaml >= 5.3.1, < 6 pyyaml >= 5.3.1, < 6
[options.extras_require] [options.extras_require]
test = test =
hcloud >= 1.6.3 hcloud >= 1.10.0, < 2
mock >= 4.0.2, < 5 mock >= 4.0.2, < 5
pytest-cov >= 2.8.1, < 3 pytest-cov >= 2.10.1, < 3
pytest-helpers-namespace >= 2019.1.8, < 2020 pytest-helpers-namespace >= 2019.1.8, < 2020
pytest-mock >= 3.1.0, < 4 pytest-mock >= 3.5.0, < 4
pytest-verbose-parametrize>=1.7.0, < 2 pytest-verbose-parametrize>=1.7.0, < 2
pytest-xdist>=1.31.0, < 2 pytest-xdist>=2.2.0, < 3
pytest>=5.4.1, < 6 pytest>=6.2.1, < 7
[options.packages.find] [options.packages.find]
where = . where = .

15
tox.ini
View File

@ -1,12 +1,10 @@
# For more information about tox, see https://tox.readthedocs.io/en/latest/ # For more information about tox, see https://tox.readthedocs.io/en/latest/
[tox] [tox]
minversion = 3.14.0
envlist = envlist =
linters linters
packaging packaging
py{38} py{36,37,38,39}
devel devel
skipsdist = True skipsdist = True
skip_missing_interpreters = False skip_missing_interpreters = False
isolated_build = True isolated_build = True
@ -19,12 +17,11 @@ setenv =
PYTHONDONTWRITEBYTECODE=1 PYTHONDONTWRITEBYTECODE=1
PYTEST_ADDOPTS=molecule_hetznercloud/test/unit/ --cov={toxinidir}/molecule_hetznercloud/ --no-cov-on-fail {env:PYTEST_ADDOPTS:-n auto} PYTEST_ADDOPTS=molecule_hetznercloud/test/unit/ --cov={toxinidir}/molecule_hetznercloud/ --no-cov-on-fail {env:PYTEST_ADDOPTS:-n auto}
deps = deps =
ansible>=2.9,<2.10 ansible>=2.10,<2.11
extras = extras =
test test
commands = commands =
python -m pytest {posargs} python -m pytest {posargs}
whitelist_externals = whitelist_externals =
bash bash
twine twine
@ -34,7 +31,7 @@ whitelist_externals =
[testenv:linters] [testenv:linters]
commands = commands =
python -m pre_commit run {posargs:--all} python -m pre_commit run {posargs:--all}
deps = pre-commit>=1.18.1 deps = pre-commit
skip_install = true skip_install = true
usedevelop = false usedevelop = false
@ -42,9 +39,9 @@ usedevelop = false
usedevelop = false usedevelop = false
skip_install = true skip_install = true
deps = deps =
collective.checkdocs >= 0.2 collective.checkdocs
pep517 >= 0.5.0 pep517
twine >= 2.0.0 twine
commands = commands =
bash -c "rm -rf {toxinidir}/dist/ && mkdir -p {toxinidir}/dist/" bash -c "rm -rf {toxinidir}/dist/ && mkdir -p {toxinidir}/dist/"
python -m pep517.build \ python -m pep517.build \