Compare commits

...

63 Commits
0.1.0 ... main

Author SHA1 Message Date
decentral1se 0dd5032df9
Mark release, point to change log
continuous-integration/drone/push Build is passing Details
2021-09-13 11:31:20 +02:00
Sorin Sbarnea 74540ee975
Remove deprecated molecule cache (#35)
continuous-integration/drone/tag Build is passing Details
This makes the driver version with future versions of molecule,
which will no longer have the cache in them. Same kind of change
was already merged in two other drivers podman and docker.

Related: https://github.com/ansible-community/molecule/pull/3180
2021-09-02 12:49:08 +01:00
decentral1se 22467fd1f4
Merge pull request #33 from hetznercloud/remove-duplicated-playbooks
continuous-integration/drone/push Build is passing Details
Remove duplicated playbook code
2021-07-05 18:02:10 +02:00
Max Rosin b0b5c04fbb Remove duplicated playbook code
By removing the duplicated code in the cookiecutter template the playbooks of the installed driver are used.
Those are then able to find the filter_plugin, which relates #31.
2021-07-02 12:42:22 +02:00
decentral1se ef8c7e3381 Merge pull request 'Handle case of absent instance_config.yml gracefully' (#13) from improved-failure-handling into main
continuous-integration/drone/push Build is passing Details
Reviewed-on: #13
2021-06-28 14:28:14 +02:00
Benjamin Wenzel 8bf5ca3ed2
Handle case of absent instance_config.yml gracefully
continuous-integration/drone/pr Build was killed Details
continuous-integration/drone/push Build is passing Details
i.e., instead of throwing a red fatal error message at the user and
recovering with a rescue block we downgrade the lookup error to a
warning and set the same defaults in that case that were set in the
rescue block before.

See https://github.com/ansible-community/molecule-hetznercloud/issues/31.
2021-06-28 14:20:00 +02:00
decentral1se 6427696ce4
Add that dep
continuous-integration/drone/push Build is passing Details
2021-06-02 13:21:37 +02:00
decentral1se f9f989cf58
Document for all
continuous-integration/drone/push Build is passing Details
2021-06-02 13:14:05 +02:00
decentral1se f43a697629
Use bold in right place and merge
continuous-integration/drone/push Build is passing Details
2021-06-02 13:06:57 +02:00
decentral1se e479213a6e
Add toolset link
continuous-integration/drone/push Build is passing Details
2021-06-02 13:05:25 +02:00
decentral1se f7f843539d
Add warning
continuous-integration/drone/push Build is passing Details
2021-06-02 13:04:04 +02:00
decentral1se 5180ae639d
Some further clean up on the log [ci skip] 2021-06-02 13:03:10 +02:00
decentral1se 9831d31ed8
Add credit
continuous-integration/drone/push Build is passing Details
2021-06-02 12:56:00 +02:00
decentral1se 060d2ad829
Add change log entry
continuous-integration/drone/push Build is passing Details
2021-06-02 12:55:19 +02:00
decentral1se 3b75e39241
Merge branch 'main' of github.com:ansible-community/molecule-hetznercloud into main
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
2021-06-02 12:53:28 +02:00
decentral1se 79567304dd
Merge pull request #30 from hetznercloud/bug/fix-network-deletion
Remove async for network deletion
2021-06-02 12:53:14 +02:00
Martin Reinhardt a1669c0b04 Remove async for network deletion 2021-06-02 11:55:04 +02:00
Martin Reinhardt cfdb264fe5 Remove async for network deletion 2021-06-02 11:48:41 +02:00
decentral1se 534b1427f2
Point to new location 2021-06-02 11:12:56 +02:00
decentral1se 2e9f4b8f06
Disable volume testing for now
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
2021-06-02 11:04:23 +02:00
decentral1se 87c3b71df5
Add change log entry 2021-06-02 11:03:37 +02:00
Martin Reinhardt 6c4c20baa6
Add possibility to create networks
continuous-integration/drone/pr Build is failing Details
continuous-integration/drone/push Build is passing Details
2021-06-02 10:51:44 +02:00
decentral1se 3617624623
Merge pull request #28 from aminvakil/patch-1
continuous-integration/drone/push Build is passing Details
Typo fix
2021-03-30 14:47:25 +02:00
Amin Vakil c6ef65d93b
Typo fix 2021-03-30 17:14:37 +04:30
decentral1se 0cbe1e327a
Add change log entry
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
2021-03-30 14:15:02 +02:00
decentral1se d075adc50d
Relax molecule bounds
continuous-integration/drone/push Build was killed Details
continuous-integration/drone/pr Build is failing Details
See https://github.com/ansible-community/molecule-hetznercloud/pull/27.
2021-03-30 14:05:54 +02:00
Luke Murphy 2e3d6bf892
Install ansible now
continuous-integration/drone/push Build is passing Details
2021-01-06 22:22:42 +01:00
Luke Murphy d8125f4777
Document bug and show more optional arguments 2021-01-06 22:22:28 +01:00
Luke Murphy 7eb47daace
Only cover role init now 2021-01-06 22:22:15 +01:00
Luke Murphy 5e74393578
Add install and upgrade docs 2021-01-06 22:22:00 +01:00
Luke Murphy ec238fe51b
Appease linter
continuous-integration/drone/push Build is passing Details
2021-01-06 15:27:53 +01:00
Luke Murphy 1423840fba
Fixup volume creation functionality
continuous-integration/drone/push Build is failing Details
continuous-integration/drone/tag Build is failing Details
2021-01-06 15:21:42 +01:00
Luke Murphy 5fd3f5ca94
Actually, this is a breaking release, mark it so 2021-01-06 13:29:19 +01:00
Luke Murphy f226bb757a
Install Ansible for integration testing 2021-01-06 13:23:11 +01:00
Luke Murphy 8226a462de
Mark release and remove dev facing issue 2021-01-06 13:16:26 +01:00
Luke Murphy 2fffc4882b
Fix typo 2021-01-06 13:13:51 +01:00
Luke Murphy 781e7ec7bb
Support dropping `sh` dependency
continuous-integration/drone/push Build is failing Details
2021-01-06 13:09:05 +01:00
Luke Murphy 8e06794f3c
Document volume handling
continuous-integration/drone/push Build is failing Details
2021-01-06 12:17:20 +01:00
decentral1se 92f645e43d Merge pull request 'Native ansible filters and volumes handling' (#10) from volumes-handling into main
continuous-integration/drone/push Build is failing Details
Reviewed-on: #10
2021-01-06 12:06:51 +01:00
Luke Murphy d57f80a496
Support volumes handling
continuous-integration/drone/push Build was killed Details
continuous-integration/drone/pr Build was killed Details
2021-01-06 12:01:37 +01:00
Luke Murphy 5efff53088
Use native ansible handlers 2021-01-06 12:01:37 +01:00
Luke Murphy 488469e927
Fix path in docs
continuous-integration/drone/push Build is failing Details
2021-01-06 12:00:34 +01:00
Luke Murphy 9bf4196816
Py39, Molecule 3.2.1 and dep tree upgrade 2021-01-06 11:49:48 +01:00
decentral1se 447585b8c5 Merge pull request 'Support CI for PRs' (#9) from support-drone-prs into main
continuous-integration/drone/push Build is failing Details
Reviewed-on: #9
2021-01-06 10:58:53 +01:00
Luke Murphy f084833ed2
Remove branch trigger focus
continuous-integration/drone/push Build is failing Details
continuous-integration/drone/pr Build is failing Details
2021-01-06 10:55:47 +01:00
Luke Murphy 0464272787
Mark branch change in change log 2021-01-06 10:53:13 +01:00
Luke Murphy 88ffc62389
Point to an open issue tracker
continuous-integration/drone/push Build is passing Details
Closes https://github.com/ansible-community/molecule-hetznercloud/issues/15.
2020-06-15 15:53:27 +02:00
Luke Murphy d98183e095
Add change log entry
continuous-integration/drone/push Build is passing Details
2020-04-29 12:48:06 +02:00
Luke Murphy 03671f5ead
Pin hcloud under testing
continuous-integration/drone/push Build is passing Details
2020-04-29 10:57:18 +02:00
Luke Murphy 33e2a4f2bf
Upgrade Molecule 2020-04-29 10:57:12 +02:00
Luke Murphy 10d26d1518
Only trigger on master builds
continuous-integration/drone/push Build is failing Details
2020-04-27 21:16:59 +02:00
Luke Murphy d2d548b166
Take a clarifying pass at README
continuous-integration/drone/push Build is passing Details
2020-04-27 16:27:23 +02:00
Luke Murphy 837de64be9
Clarify change log entry
continuous-integration/drone/push Build was killed Details
2020-04-27 16:15:17 +02:00
Luke Murphy 5786a60678
Add note about reduced configurations work
continuous-integration/drone/push Build is passing Details
2020-04-27 16:01:00 +02:00
Luke Murphy 671e92cd0b
Actually don't embed prepare.yml
It can be customised if needed, otherwise not used.
2020-04-27 16:00:39 +02:00
Luke Murphy dfb003fdcb
Remove embedded converge.yml
continuous-integration/drone/push Build is passing Details
See https://github.com/ansible-community/molecule/issues/2675.
2020-04-27 15:51:28 +02:00
Luke Murphy a2ee39a2f0
Converge must stay for now
continuous-integration/drone/push Build was killed Details
See https://github.com/ansible-community/molecule/issues/2675.
2020-04-27 15:50:45 +02:00
Luke Murphy 9164e92d38
Add missing change log entry 2020-04-27 15:26:35 +02:00
Luke Murphy 5f57fb2c95
Have all jobs depend on linters to save resources
continuous-integration/drone/push Build is failing Details
2020-04-27 15:25:45 +02:00
Luke Murphy 3a1602382a
Appease linter with spacing
continuous-integration/drone/push Build was killed Details
2020-04-27 15:25:00 +02:00
Luke Murphy 5179b284ba
Add bundled playbooks
continuous-integration/drone/push Build was killed Details
2020-04-27 15:20:47 +02:00
Luke Murphy 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
Luke Murphy a25533d917
Add place holder for new API function 2020-04-27 15:02:43 +02:00
24 changed files with 727 additions and 424 deletions

View File

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

View File

@ -7,6 +7,61 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [1.3.0] - 2021-09-02
- See [here](https://github.com/ansible-community/molecule-hetznercloud/releases/tag/1.3.0)
## [1.2.1] - 2021-06-02
### Fixed
- Remove async task handling for network deletion ([#30](https://github.com/ansible-community/molecule-hetznercloud/pull/30), credit @ggggut)
## [1.2.0] - 2021-06-02
### Added
- Allow to create networks during test runs ([#29](https://github.com/ansible-community/molecule-hetznercloud/pull/29), thanks @ggggut!)
## [1.1.0] - 2021-03-30
## Changed
- Relaxed bounds on Molecule to allow all versions less than `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

123
README.md
View File

@ -21,19 +21,35 @@ useful, please support my maintenance work financially through my
Sponsor profile](https://github.com/sponsors/decentral1se). I do not receive
any financial support from RedHat or Hetzner Cloud for this work.
## Install
```bash
$ pip install molecule-hetznercloud
```
If you're looking for a container approach, see [ansible-community/toolset](https://github.com/ansible-community/toolset).
## Upgrade
Please see the [CHANGELOG.md](./CHANGELOG.md) for migration guides.
```bash
$ pip install --upgrade molecule-hetznercloud
```
## Usage
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).
```bash
$ export HCLOUD_TOKEN=mycoolapitoken
```
Then install the required Python package.
Then create a role using the driver plugin.
```bash
$ pip install molecule-hetznercloud
$ molecule init role myrolename -d hetznercloud
```
@ -46,7 +62,7 @@ dependency:
driver:
name: hetznercloud
platforms:
- name: my-instance-name
- name: instance
server_type: cx11
image: debian-10
provisioner:
@ -71,6 +87,85 @@ $ export MOLECULE_NO_LOG=False # not so verbose, helpful
$ export MOLECULE_DEBUG=True # very verbose, last ditch effort
```
## Volume Handling
> **WARNING**: this feature appears to be broke. See [#24](https://github.com/ansible-community/molecule-hetznercloud/issues/24) for more
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.
```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
Issues will be responded to on both issue trackers.
@ -88,7 +183,7 @@ See [CHANGELOG.md](./CHANGELOG.md).
## Contact
- Ping @decentral1se on the `#ansible-molecule` channel on [Freenode](https://webchat.freenode.net).
- Ping @decentral1se on the `#ansible-molecule` channel on [Libera](https://libera.chat/).
## License
@ -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.
Unit tests and such.
### Unit
```bash
$ pip install tox
$ tox -v
```
Integration tests.
### Integration
(Only doable by [Autonomic Cooperative](https://autonomic.zone/) members.)
```bash
$ sudo apt install -y direnv
$ cp .envrc.sample .envrc
$ direnv allow
$ pip install -e .
$ cd integration-test-role && molecule test
```
git clone https://github.com/ansible-community/molecule-hetznercloud.git
cd molecule-hetznercloud
python3 -m venv .venv && source .venv/bin/activate
pip install -e . "ansible<4" netaddr
export INSTANCE_UUID=$(openssl rand -hex 5)
export HCLOUD_TOKEN=YOURKEY
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

@ -1,56 +0,0 @@
---
- name: Destroy
hosts: localhost
connection: local
gather_facts: false
no_log: "{{ molecule_no_log }}"
tasks:
- name: Populate the instance config
block:
- name: Populate instance config from file
set_fact:
instance_conf: "{{ lookup('file', molecule_instance_config) | molecule_from_yaml }}"
skip_instances: false
rescue:
- name: Populate instance config when file missing
set_fact:
instance_conf: {}
skip_instances: true
- name: Destroy molecule instance(s)
hcloud_server:
name: "{{ item.instance }}"
api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}"
state: absent
register: server
with_items: "{{ instance_conf }}"
when: not skip_instances
async: 7200
poll: 0
- name: Wait for instance(s) deletion to complete
async_status:
jid: "{{ item.ansible_job_id }}"
register: hetzner_jobs
until: hetzner_jobs.finished
retries: 300
with_items: "{{ server.results }}"
- name: Remove registered SSH key
hcloud_ssh_key:
name: "{{ instance_conf[0].ssh_key_name }}"
api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}"
state: absent
when:
- not skip_instances
- instance_conf | length # must contain at least one instance
- name: Populate instance config
set_fact:
instance_conf: {}
- name: Dump instance config
copy:
content: "{{ instance_conf | molecule_to_yaml | molecule_header }}"
dest: "{{ molecule_instance_config }}"
when: server.changed | bool

View File

@ -7,6 +7,21 @@ platforms:
- name: "molecule-hetznercloud-${INSTANCE_UUID}"
server_type: cx11
image: debian-10
# https://github.com/ansible-community/molecule-hetznercloud/issues/24
# 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:
name: ansible
verifier:

View File

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

View File

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

View File

@ -1,87 +0,0 @@
---
{% raw -%}
- 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 }}"
{%- endraw %}

View File

@ -1,58 +0,0 @@
---
{% raw -%}
- name: Destroy
hosts: localhost
connection: local
gather_facts: false
no_log: "{{ molecule_no_log }}"
tasks:
- name: Populate the instance config
block:
- name: Populate instance config from file
set_fact:
instance_conf: "{{ lookup('file', molecule_instance_config) | molecule_from_yaml }}"
skip_instances: false
rescue:
- name: Populate instance config when file missing
set_fact:
instance_conf: {}
skip_instances: true
- name: Destroy molecule instance(s)
hcloud_server:
name: "{{ item.instance }}"
api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}"
state: absent
register: server
with_items: "{{ instance_conf }}"
when: not skip_instances
async: 7200
poll: 0
- name: Wait for instance(s) deletion to complete
async_status:
jid: "{{ item.ansible_job_id }}"
register: hetzner_jobs
until: hetzner_jobs.finished
retries: 300
with_items: "{{ server.results }}"
- name: Remove registered SSH key
hcloud_ssh_key:
name: "{{ instance_conf[0].ssh_key_name }}"
api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}"
state: absent
when:
- not skip_instances
- instance_conf | length # must contain at least one instance
- name: Populate instance config
set_fact:
instance_conf: {}
- name: Dump instance config
copy:
content: "{{ instance_conf | molecule_to_yaml | molecule_header }}"
dest: "{{ molecule_instance_config }}"
when: server.changed | bool
{%- 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

@ -1,8 +1,9 @@
import os
from ansible_compat.ports import cache
from molecule import logger, util
from molecule.api import Driver
from molecule.util import lru_cache, sysexit_with_message
from molecule.util import sysexit_with_message
log = logger.get_logger(__name__)
@ -75,7 +76,7 @@ class HetznerCloud(Driver):
item for item in instance_config_dict if item["instance"] == instance_name
)
@lru_cache()
@cache
def sanity_checks(self):
"""Hetzner Cloud driver sanity checks."""
@ -98,3 +99,8 @@ class HetznerCloud(Driver):
"account API token value"
)
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

@ -0,0 +1,87 @@
---
- name: Destroy
hosts: localhost
connection: local
gather_facts: false
no_log: "{{ molecule_no_log }}"
tasks:
- name: Populate the instance config
set_fact:
instance_conf: "{{ lookup('file', molecule_instance_config, errors='warn') | from_yaml }}"
skip_instances: false
register: instance_config_lookup
- name: Populate instance config when file missing
set_fact:
instance_conf: {}
skip_instances: true
when: not instance_config_lookup.ansible_facts.instance_conf
- name: Destroy molecule instance(s)
hcloud_server:
name: "{{ item.instance }}"
api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}"
state: absent
register: server
with_items: "{{ instance_conf }}"
when: not skip_instances
async: 7200
poll: 0
- name: Wait for instance(s) deletion to complete
async_status:
jid: "{{ item.ansible_job_id }}"
register: hetzner_jobs
until: hetzner_jobs.finished
retries: 300
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) }}"
- name: Remove registered SSH key
hcloud_ssh_key:
name: "{{ instance_conf[0].ssh_key_name }}"
api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}"
state: absent
when:
- not skip_instances
- instance_conf | length # must contain at least one instance
- name: Populate instance config
set_fact:
instance_conf: {}
- name: Dump instance config
copy:
content: |
# Molecule managed
{{ instance_conf | to_json | from_json | to_yaml }}
dest: "{{ molecule_instance_config }}"
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
def run_command(cmd, env=os.environ, log=True):
if log:
cmd = _rebake_command(cmd, env)
cmd = cmd.bake(_truncate_exc=False)
return util.run_command(cmd)
if cmd.__class__.__name__ == "Command":
if log:
cmd = _rebake_command(cmd, env)
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)

View File

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

View File

@ -23,6 +23,7 @@
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)
@ -31,7 +32,6 @@
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) }}"
@ -51,19 +51,84 @@
retries: 300
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
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 }}",
}
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
@ -75,7 +140,10 @@
- name: Dump instance config
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 }}"
when: server.changed | bool
@ -85,4 +153,4 @@
host: "{{ item.address }}"
search_regex: SSH
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:
- name: Populate instance config from file
set_fact:
instance_conf: "{{ lookup('file', molecule_instance_config) | molecule_from_yaml }}"
instance_conf: "{{ lookup('file', molecule_instance_config) | from_yaml }}"
skip_instances: false
rescue:
- name: Populate instance config when file missing
@ -36,15 +36,45 @@
retries: 300
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) }}"
- name: Remove registered SSH key
hcloud_ssh_key:
name: "{{ instance_conf[0].ssh_key_name }}"
api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}"
state: absent
when:
- not skip_instances
- instance_conf # must contain at least one instance
# Mandatory configuration for Molecule to function.
- instance_conf | length # must contain at least one instance
- name: Populate instance config
set_fact:
@ -52,6 +82,9 @@
- name: Dump instance config
copy:
content: "{{ instance_conf | molecule_to_yaml | molecule_header }}"
content: |
# Molecule managed
{{ instance_conf | to_json | from_json | to_yaml }}
dest: "{{ molecule_instance_config }}"
when: server.changed | bool

View File

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

View File

@ -1,7 +1,6 @@
import os
import pytest
from molecule import config
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)
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):
assert "hetznercloud" == hetznercloud_instance.name

View File

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

View File

@ -8,7 +8,7 @@ universal = 1
name = molecule-hetznercloud
url = https://git.autonomic.zone/autonomic-cooperative/molecule-hetznercloud
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/
Source Code = https://git.autonomic.zone/autonomic-cooperative/molecule-hetznercloud
description = Molecule Hetzner Cloud Plugin :: run molecule tests with hetzner cloud
@ -30,7 +30,10 @@ classifiers =
Natural Language :: English
Operating System :: OS Independent
Programming Language :: Python :: 3
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Topic :: System :: Systems Administration
Topic :: Utilities
keywords =
@ -49,23 +52,24 @@ packages = find:
include_package_data = True
zip_safe = False
setup_requires =
setuptools_scm >= 3.5.0
setuptools_scm_git_archive >= 1.1
setuptools_scm
setuptools_scm_git_archive
install_requires =
hcloud >= 1.6.3, < 2
molecule >= 3.0.3, <= 3.1
ansible-compat >= 0.5.0
hcloud >= 1.10.0, < 2
molecule >= 3.2.1, < 4
pyyaml >= 5.3.1, < 6
[options.extras_require]
test =
hcloud >= 1.6.3
hcloud >= 1.10.0, < 2
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-mock >= 3.1.0, < 4
pytest-mock >= 3.5.0, < 4
pytest-verbose-parametrize>=1.7.0, < 2
pytest-xdist>=1.31.0, < 2
pytest>=5.4.1, < 6
pytest-xdist>=2.2.0, < 3
pytest>=6.2.1, < 7
[options.packages.find]
where = .

15
tox.ini
View File

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