diff options
Diffstat (limited to '')
209 files changed, 21264 insertions, 0 deletions
diff --git a/ansible_collections/hetzner/hcloud/.azure-pipelines/README.md b/ansible_collections/hetzner/hcloud/.azure-pipelines/README.md new file mode 100644 index 000000000..385e70bac --- /dev/null +++ b/ansible_collections/hetzner/hcloud/.azure-pipelines/README.md @@ -0,0 +1,3 @@ +## Azure Pipelines Configuration + +Please see the [Documentation](https://github.com/ansible/community/wiki/Testing:-Azure-Pipelines) for more information. diff --git a/ansible_collections/hetzner/hcloud/.azure-pipelines/azure-pipelines.yml b/ansible_collections/hetzner/hcloud/.azure-pipelines/azure-pipelines.yml new file mode 100644 index 000000000..d55524f27 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/.azure-pipelines/azure-pipelines.yml @@ -0,0 +1,147 @@ +trigger: + batch: true + branches: + include: + - main + +pr: + autoCancel: true + branches: + include: + - main + +schedules: + - cron: 0 9 * * * + displayName: Nightly + always: true + branches: + include: + - main + +variables: + - name: checkoutPath + value: ansible_collections/hetzner/hcloud + - name: coverageBranches + value: main + - name: pipelinesCoverage + value: coverage + - name: entryPoint + value: tests/utils/shippable/shippable.sh + - name: fetchDepth + value: 0 + +resources: + containers: + - container: default + image: quay.io/ansible/azure-pipelines-test-container:3.0.0 + +pool: Standard + +stages: +### Sanity + - stage: Ansible_devel + displayName: Sanity devel + dependsOn: [] + jobs: + - template: templates/matrix.yml + parameters: + targets: + - name: Sanity + test: 'devel/sanity/1' + + - stage: Ansible_2_14 + displayName: Sanity 2.14 + dependsOn: [] + jobs: + - template: templates/matrix.yml + parameters: + targets: + - name: Sanity + test: '2.14/sanity/1' + - stage: Ansible_2_13 + displayName: Sanity 2.13 + dependsOn: [] + jobs: + - template: templates/matrix.yml + parameters: + targets: + - name: Sanity + test: '2.13/sanity/1' + - stage: Ansible_2_12 + displayName: Sanity 2.12 + dependsOn: [] + jobs: + - template: templates/matrix.yml + parameters: + targets: + - name: Sanity + test: '2.12/sanity/1' + + +## Integration tests (remote) + - stage: Hetzner_devel + displayName: Hetzner devel + dependsOn: [] + jobs: + - template: templates/matrix.yml + parameters: + groups: + - 1 + - 2 + targets: + - name: hcloud + test: 'devel/hcloud/3.9' + + - stage: Hetzner_2_14 + displayName: Hetzner 2.14 + dependsOn: [] + jobs: + - template: templates/matrix.yml + parameters: + groups: + - 1 + - 2 + targets: + - name: hcloud + test: '2.14/hcloud/3.9' + + - stage: Hetzner_2_13 + displayName: Hetzner 2.13 + dependsOn: [] + jobs: + - template: templates/matrix.yml + parameters: + groups: + - 1 + - 2 + targets: + - name: hcloud + test: '2.13/hcloud/3.9' + + - stage: Hetzner_2_12 + displayName: Hetzner 2.12 + dependsOn: [] + jobs: + - template: templates/matrix.yml + parameters: + groups: + - 1 + - 2 + targets: + - name: hcloud + test: '2.12/hcloud/3.9' + +### Finally + - stage: Summary + condition: succeededOrFailed() + dependsOn: + - Ansible_devel + - Ansible_2_14 + - Ansible_2_13 + - Ansible_2_12 + - Hetzner_devel + - Hetzner_2_14 + - Hetzner_2_13 + - Hetzner_2_12 + jobs: + - template: templates/coverage.yml diff --git a/ansible_collections/hetzner/hcloud/.azure-pipelines/scripts/aggregate-coverage.sh b/ansible_collections/hetzner/hcloud/.azure-pipelines/scripts/aggregate-coverage.sh new file mode 100755 index 000000000..f3113dd0a --- /dev/null +++ b/ansible_collections/hetzner/hcloud/.azure-pipelines/scripts/aggregate-coverage.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# Aggregate code coverage results for later processing. + +set -o pipefail -eu + +agent_temp_directory="$1" + +PATH="${PWD}/bin:${PATH}" + +mkdir "${agent_temp_directory}/coverage/" + +options=(--venv --venv-system-site-packages --color -v) + +ansible-test coverage combine --export "${agent_temp_directory}/coverage/" "${options[@]}" + +if ansible-test coverage analyze targets generate --help >/dev/null 2>&1; then + # Only analyze coverage if the installed version of ansible-test supports it. + # Doing so allows this script to work unmodified for multiple Ansible versions. + ansible-test coverage analyze targets generate "${agent_temp_directory}/coverage/coverage-analyze-targets.json" "${options[@]}" +fi diff --git a/ansible_collections/hetzner/hcloud/.azure-pipelines/scripts/combine-coverage.py b/ansible_collections/hetzner/hcloud/.azure-pipelines/scripts/combine-coverage.py new file mode 100755 index 000000000..506ade646 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/.azure-pipelines/scripts/combine-coverage.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python +""" +Combine coverage data from multiple jobs, keeping the data only from the most recent attempt from each job. +Coverage artifacts must be named using the format: "Coverage $(System.JobAttempt) {StableUniqueNameForEachJob}" +The recommended coverage artifact name format is: Coverage $(System.JobAttempt) $(System.StageDisplayName) $(System.JobDisplayName) +Keep in mind that Azure Pipelines does not enforce unique job display names (only names). +It is up to pipeline authors to avoid name collisions when deviating from the recommended format. +""" + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import re +import shutil +import sys + + +def main(): + """Main program entry point.""" + source_directory = sys.argv[1] + + if '/ansible_collections/' in os.getcwd(): + output_path = "tests/output" + else: + output_path = "test/results" + + destination_directory = os.path.join(output_path, 'coverage') + + if not os.path.exists(destination_directory): + os.makedirs(destination_directory) + + jobs = {} + count = 0 + + for name in os.listdir(source_directory): + match = re.search('^Coverage (?P<attempt>[0-9]+) (?P<label>.+)$', name) + label = match.group('label') + attempt = int(match.group('attempt')) + jobs[label] = max(attempt, jobs.get(label, 0)) + + for label, attempt in jobs.items(): + name = 'Coverage {attempt} {label}'.format(label=label, attempt=attempt) + source = os.path.join(source_directory, name) + source_files = os.listdir(source) + + for source_file in source_files: + source_path = os.path.join(source, source_file) + destination_path = os.path.join(destination_directory, source_file + '.' + label) + print('"%s" -> "%s"' % (source_path, destination_path)) + shutil.copyfile(source_path, destination_path) + count += 1 + + print('Coverage file count: %d' % count) + print('##vso[task.setVariable variable=coverageFileCount]%d' % count) + print('##vso[task.setVariable variable=outputPath]%s' % output_path) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/hetzner/hcloud/.azure-pipelines/scripts/process-results.sh b/ansible_collections/hetzner/hcloud/.azure-pipelines/scripts/process-results.sh new file mode 100755 index 000000000..f3f1d1bae --- /dev/null +++ b/ansible_collections/hetzner/hcloud/.azure-pipelines/scripts/process-results.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# Check the test results and set variables for use in later steps. + +set -o pipefail -eu + +if [[ "$PWD" =~ /ansible_collections/ ]]; then + output_path="tests/output" +else + output_path="test/results" +fi + +echo "##vso[task.setVariable variable=outputPath]${output_path}" + +if compgen -G "${output_path}"'/junit/*.xml' > /dev/null; then + echo "##vso[task.setVariable variable=haveTestResults]true" +fi + +if compgen -G "${output_path}"'/bot/ansible-test-*' > /dev/null; then + echo "##vso[task.setVariable variable=haveBotResults]true" +fi + +if compgen -G "${output_path}"'/coverage/*' > /dev/null; then + echo "##vso[task.setVariable variable=haveCoverageData]true" +fi diff --git a/ansible_collections/hetzner/hcloud/.azure-pipelines/scripts/publish-codecov.sh b/ansible_collections/hetzner/hcloud/.azure-pipelines/scripts/publish-codecov.sh new file mode 100755 index 000000000..6d184f0b8 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/.azure-pipelines/scripts/publish-codecov.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# Upload code coverage reports to codecov.io. +# Multiple coverage files from multiple languages are accepted and aggregated after upload. +# Python coverage, as well as PowerShell and Python stubs can all be uploaded. + +set -o pipefail -eu + +output_path="$1" + +curl --silent --show-error https://ansible-ci-files.s3.us-east-1.amazonaws.com/codecov/codecov.sh > codecov.sh + +for file in "${output_path}"/reports/coverage*.xml; do + name="${file}" + name="${name##*/}" # remove path + name="${name##coverage=}" # remove 'coverage=' prefix if present + name="${name%.xml}" # remove '.xml' suffix + + bash codecov.sh \ + -f "${file}" \ + -n "${name}" \ + -X coveragepy \ + -X gcov \ + -X fix \ + -X search \ + -X xcode \ + || echo "Failed to upload code coverage report to codecov.io: ${file}" +done diff --git a/ansible_collections/hetzner/hcloud/.azure-pipelines/scripts/report-coverage.sh b/ansible_collections/hetzner/hcloud/.azure-pipelines/scripts/report-coverage.sh new file mode 100755 index 000000000..1bd91bdc9 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/.azure-pipelines/scripts/report-coverage.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +# Generate code coverage reports for uploading to Azure Pipelines and codecov.io. + +set -o pipefail -eu + +PATH="${PWD}/bin:${PATH}" + +if ! ansible-test --help >/dev/null 2>&1; then + # Install the devel version of ansible-test for generating code coverage reports. + # This is only used by Ansible Collections, which are typically tested against multiple Ansible versions (in separate jobs). + # Since a version of ansible-test is required that can work the output from multiple older releases, the devel version is used. + pip install https://github.com/ansible/ansible/archive/devel.tar.gz --disable-pip-version-check +fi + +ansible-test coverage xml --stub --venv --venv-system-site-packages --color -v diff --git a/ansible_collections/hetzner/hcloud/.azure-pipelines/scripts/run-tests.sh b/ansible_collections/hetzner/hcloud/.azure-pipelines/scripts/run-tests.sh new file mode 100755 index 000000000..a947fdf01 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/.azure-pipelines/scripts/run-tests.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# Configure the test environment and run the tests. + +set -o pipefail -eu + +entry_point="$1" +test="$2" +read -r -a coverage_branches <<< "$3" # space separated list of branches to run code coverage on for scheduled builds + +export COMMIT_MESSAGE +export COMPLETE +export COVERAGE +export IS_PULL_REQUEST + +if [ "${SYSTEM_PULLREQUEST_TARGETBRANCH:-}" ]; then + IS_PULL_REQUEST=true + COMMIT_MESSAGE=$(git log --format=%B -n 1 HEAD^2) +else + IS_PULL_REQUEST= + COMMIT_MESSAGE=$(git log --format=%B -n 1 HEAD) +fi + +COMPLETE= +COVERAGE= + +if [ "${BUILD_REASON}" = "Schedule" ]; then + COMPLETE=yes + + if printf '%s\n' "${coverage_branches[@]}" | grep -q "^${BUILD_SOURCEBRANCHNAME}$"; then + COVERAGE=yes + fi +fi + +"${entry_point}" "${test}" 2>&1 | "$(dirname "$0")/time-command.py" diff --git a/ansible_collections/hetzner/hcloud/.azure-pipelines/scripts/time-command.py b/ansible_collections/hetzner/hcloud/.azure-pipelines/scripts/time-command.py new file mode 100755 index 000000000..5e8eb8d4c --- /dev/null +++ b/ansible_collections/hetzner/hcloud/.azure-pipelines/scripts/time-command.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python +"""Prepends a relative timestamp to each input line from stdin and writes it to stdout.""" + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import sys +import time + + +def main(): + """Main program entry point.""" + start = time.time() + + sys.stdin.reconfigure(errors='surrogateescape') + sys.stdout.reconfigure(errors='surrogateescape') + + for line in sys.stdin: + seconds = time.time() - start + sys.stdout.write('%02d:%02d %s' % (seconds // 60, seconds % 60, line)) + sys.stdout.flush() + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/hetzner/hcloud/.azure-pipelines/templates/coverage.yml b/ansible_collections/hetzner/hcloud/.azure-pipelines/templates/coverage.yml new file mode 100644 index 000000000..1864e4441 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/.azure-pipelines/templates/coverage.yml @@ -0,0 +1,39 @@ +# This template adds a job for processing code coverage data. +# It will upload results to Azure Pipelines and codecov.io. +# Use it from a job stage that completes after all other jobs have completed. +# This can be done by placing it in a separate summary stage that runs after the test stage(s) have completed. + +jobs: + - job: Coverage + displayName: Code Coverage + container: default + workspace: + clean: all + steps: + - checkout: self + fetchDepth: $(fetchDepth) + path: $(checkoutPath) + - task: DownloadPipelineArtifact@2 + displayName: Download Coverage Data + inputs: + path: coverage/ + patterns: "Coverage */*=coverage.combined" + - bash: .azure-pipelines/scripts/combine-coverage.py coverage/ + displayName: Combine Coverage Data + - bash: .azure-pipelines/scripts/report-coverage.sh + displayName: Generate Coverage Report + condition: gt(variables.coverageFileCount, 0) + - task: PublishCodeCoverageResults@1 + inputs: + codeCoverageTool: Cobertura + # Azure Pipelines only accepts a single coverage data file. + # That means only Python or PowerShell coverage can be uploaded, but not both. + # Set the "pipelinesCoverage" variable to determine which type is uploaded. + # Use "coverage" for Python and "coverage-powershell" for PowerShell. + summaryFileLocation: "$(outputPath)/reports/$(pipelinesCoverage).xml" + displayName: Publish to Azure Pipelines + condition: gt(variables.coverageFileCount, 0) + - bash: .azure-pipelines/scripts/publish-codecov.sh "$(outputPath)" + displayName: Publish to codecov.io + condition: gt(variables.coverageFileCount, 0) + continueOnError: true diff --git a/ansible_collections/hetzner/hcloud/.azure-pipelines/templates/matrix.yml b/ansible_collections/hetzner/hcloud/.azure-pipelines/templates/matrix.yml new file mode 100644 index 000000000..4e9555dd3 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/.azure-pipelines/templates/matrix.yml @@ -0,0 +1,55 @@ +# This template uses the provided targets and optional groups to generate a matrix which is then passed to the test template. +# If this matrix template does not provide the required functionality, consider using the test template directly instead. + +parameters: + # A required list of dictionaries, one per test target. + # Each item in the list must contain a "test" or "name" key. + # Both may be provided. If one is omitted, the other will be used. + - name: targets + type: object + + # An optional list of values which will be used to multiply the targets list into a matrix. + # Values can be strings or numbers. + - name: groups + type: object + default: [] + + # An optional format string used to generate the job name. + # - {0} is the name of an item in the targets list. + - name: nameFormat + type: string + default: "{0}" + + # An optional format string used to generate the test name. + # - {0} is the name of an item in the targets list. + - name: testFormat + type: string + default: "{0}" + + # An optional format string used to add the group to the job name. + # {0} is the formatted name of an item in the targets list. + # {{1}} is the group -- be sure to include the double "{{" and "}}". + - name: nameGroupFormat + type: string + default: "{0} - {{1}}" + + # An optional format string used to add the group to the test name. + # {0} is the formatted test of an item in the targets list. + # {{1}} is the group -- be sure to include the double "{{" and "}}". + - name: testGroupFormat + type: string + default: "{0}/{{1}}" + +jobs: + - template: test.yml + parameters: + jobs: + - ${{ if eq(length(parameters.groups), 0) }}: + - ${{ each target in parameters.targets }}: + - name: ${{ format(parameters.nameFormat, coalesce(target.name, target.test)) }} + test: ${{ format(parameters.testFormat, coalesce(target.test, target.name)) }} + - ${{ if not(eq(length(parameters.groups), 0)) }}: + - ${{ each group in parameters.groups }}: + - ${{ each target in parameters.targets }}: + - name: ${{ format(format(parameters.nameGroupFormat, parameters.nameFormat), coalesce(target.name, target.test), group) }} + test: ${{ format(format(parameters.testGroupFormat, parameters.testFormat), coalesce(target.test, target.name), group) }} diff --git a/ansible_collections/hetzner/hcloud/.azure-pipelines/templates/test.yml b/ansible_collections/hetzner/hcloud/.azure-pipelines/templates/test.yml new file mode 100644 index 000000000..5250ed802 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/.azure-pipelines/templates/test.yml @@ -0,0 +1,45 @@ +# This template uses the provided list of jobs to create test one or more test jobs. +# It can be used directly if needed, or through the matrix template. + +parameters: + # A required list of dictionaries, one per test job. + # Each item in the list must contain a "job" and "name" key. + - name: jobs + type: object + +jobs: + - ${{ each job in parameters.jobs }}: + - job: test_${{ replace(replace(replace(job.test, '/', '_'), '.', '_'), '-', '_') }} + displayName: ${{ job.name }} + container: default + workspace: + clean: all + steps: + - checkout: self + fetchDepth: $(fetchDepth) + path: $(checkoutPath) + - bash: .azure-pipelines/scripts/run-tests.sh "$(entryPoint)" "${{ job.test }}" "$(coverageBranches)" + displayName: Run Tests + - bash: .azure-pipelines/scripts/process-results.sh + condition: succeededOrFailed() + displayName: Process Results + - bash: .azure-pipelines/scripts/aggregate-coverage.sh "$(Agent.TempDirectory)" + condition: eq(variables.haveCoverageData, 'true') + displayName: Aggregate Coverage Data + - task: PublishTestResults@2 + condition: eq(variables.haveTestResults, 'true') + inputs: + testResultsFiles: "$(outputPath)/junit/*.xml" + displayName: Publish Test Results + - task: PublishPipelineArtifact@1 + condition: eq(variables.haveBotResults, 'true') + displayName: Publish Bot Results + inputs: + targetPath: "$(outputPath)/bot/" + artifactName: "Bot $(System.JobAttempt) $(System.StageDisplayName) $(System.JobDisplayName)" + - task: PublishPipelineArtifact@1 + condition: eq(variables.haveCoverageData, 'true') + displayName: Publish Coverage Data + inputs: + targetPath: "$(Agent.TempDirectory)/coverage/" + artifactName: "Coverage $(System.JobAttempt) $(System.StageDisplayName) $(System.JobDisplayName)" diff --git a/ansible_collections/hetzner/hcloud/.github/workflows/check-changelog-fragments.yml b/ansible_collections/hetzner/hcloud/.github/workflows/check-changelog-fragments.yml new file mode 100644 index 000000000..037ff7ec0 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/.github/workflows/check-changelog-fragments.yml @@ -0,0 +1,20 @@ +name: antsibull-changelog tests + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + name: antsibull-changelog-lint + steps: + - uses: actions/checkout@v2 + - name: Setup python + uses: actions/setup-python@v3 + with: + python-version: "3.10" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install antsibull-changelog + - name: test with lint + run: antsibull-changelog lint diff --git a/ansible_collections/hetzner/hcloud/.gitignore b/ansible_collections/hetzner/hcloud/.gitignore new file mode 100644 index 000000000..d6a37e5c5 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/.gitignore @@ -0,0 +1,389 @@ + +# Created by https://www.gitignore.io/api/git,linux,pydev,python,windows,pycharm+all,jupyternotebook,vim,webstorm,emacs,dotenv +# Edit at https://www.gitignore.io/?templates=git,linux,pydev,python,windows,pycharm+all,jupyternotebook,vim,webstorm,emacs,dotenv + +### dotenv ### +.env + +### Emacs ### +# -*- mode: gitignore; -*- +*~ +\#*\# +/.emacs.desktop +/.emacs.desktop.lock +*.elc +auto-save-list +tramp +.\#* + +# Org-mode +.org-id-locations +*_archive + +# flymake-mode +*_flymake.* + +# eshell files +/eshell/history +/eshell/lastdir + +# elpa packages +/elpa/ + +# reftex files +*.rel + +# AUCTeX auto folder +/auto/ + +# cask packages +.cask/ +dist/ + +# Flycheck +flycheck_*.el + +# server auth directory +/server/ + +# projectiles files +.projectile + +# directory configuration +.dir-locals.el + +# network security +/network-security.data + + +### Git ### +# Created by git for backups. To disable backups in Git: +# $ git config --global mergetool.keepBackup false +*.orig + +# Created by git when using merge tools for conflicts +*.BACKUP.* +*.BASE.* +*.LOCAL.* +*.REMOTE.* +*_BACKUP_*.txt +*_BASE_*.txt +*_LOCAL_*.txt +*_REMOTE_*.txt + +#!! ERROR: jupyternotebook is undefined. Use list command to see defined gitignore types !!# + +### Linux ### + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### PyCharm+all ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### PyCharm+all Patch ### +# Ignores the whole .idea folder and all .iml files +# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 + +.idea/ + +# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 + +*.iml +modules.xml +.idea/misc.xml +*.ipr + +# Sonarlint plugin +.idea/sonarlint + +### pydev ### +.pydevproject + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# Mr Developer +.mr.developer.cfg +.project + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +### Vim ### +# Swap +[._]*.s[a-v][a-z] +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] + +# Session +Session.vim +Sessionx.vim + +# Temporary +.netrwhist +# Auto-generated tag files +tags +# Persistent undo +[._]*.un~ + +### WebStorm ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff + +# Generated files + +# Sensitive or high-churn files + +# Gradle + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake + +# Mongo Explorer plugin + +# File-based project format + +# IntelliJ + +# mpeltonen/sbt-idea plugin + +# JIRA plugin + +# Cursive Clojure plugin + +# Crashlytics plugin (for Android Studio and IntelliJ) + +# Editor-based Rest Client + +# Android studio 3.1+ serialized cache file + +### WebStorm Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +.idea/**/sonarlint/ + +# SonarQube Plugin +.idea/**/sonarIssues.xml + +# Markdown Navigator plugin +.idea/**/markdown-navigator.xml +.idea/**/markdown-navigator/ + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.gitignore.io/api/git,linux,pydev,python,windows,pycharm+all,jupyternotebook,vim,webstorm,emacs,dotenv + +cloud-config-hcloud.ini diff --git a/ansible_collections/hetzner/hcloud/.gitlab-ci.yml b/ansible_collections/hetzner/hcloud/.gitlab-ci.yml new file mode 100644 index 000000000..7748f7237 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/.gitlab-ci.yml @@ -0,0 +1,34 @@ +stages: + - sanity + - integration + +variables: + PYTHON_VERSION: "3.9" + +default: + image: python:$PYTHON_VERSION + +sanity: + stage: sanity + allow_failure: true + except: + - tags + parallel: + matrix: + - ANSIBLE_VERSION: ["devel", "2.12", "2.13", "2.14"] + GROUP: [1] + script: + - bash tests/utils/gitlab/gitlab.sh ${ANSIBLE_VERSION}/sanity/${GROUP} + +integration: + stage: integration + except: + - tags + parallel: + matrix: + - ANSIBLE_VERSION: ["devel"] + GROUP: [1, 2, 3] + script: + - echo "$HCLOUD_TOKEN" >> "$(pwd)/hcloud_token.txt" + - echo "$CI_JOB_ID" >> "$(pwd)/prefix.txt" + - bash tests/utils/gitlab/gitlab.sh ${ANSIBLE_VERSION}/hcloud/${PYTHON_VERSION}/${GROUP} diff --git a/ansible_collections/hetzner/hcloud/CHANGELOG.rst b/ansible_collections/hetzner/hcloud/CHANGELOG.rst new file mode 100644 index 000000000..e9eff3cad --- /dev/null +++ b/ansible_collections/hetzner/hcloud/CHANGELOG.rst @@ -0,0 +1,289 @@ +============================================== +Hetzner Cloud Ansible Collection Release Notes +============================================== + +.. contents:: Topics + + +v1.11.0 +======= + +Minor Changes +------------- + +- hcloud_image_info - Add cpu architecture field to return value. +- hcloud_image_info - Allow filtering images by cpu architecture. +- hcloud_server - Select matching image for the cpu architecture of the server type on create & rebuild. +- hcloud_server_type_info - Add cpu architecture field to return value. +- inventory plugin - Add cpu architecture to server variables. + +v1.10.1 +======= + +Bugfixes +-------- + +- hcloud_server - Prevent backups from being disabled when undefined +- hcloud_server - Server locked after attaching to placement group + +v1.10.0 +======= + +Minor Changes +------------- + +- hcloud_server - add private_networks_info containing name and private ip in responses +- hcloud_server_info - add private_networks_info containing name and private ip in responses +- inventory plugin - Add list of all private networks to server variables. +- inventory plugin - Add new connect_with setting public_ipv6 to connect to discovered servers via public IPv6 address. +- inventory plugin - Add public IPv6 address to server variables. +- inventory plugin - Log warning instead of crashing when some servers do not work with global connect_with setting. + +Breaking Changes / Porting Guide +-------------------------------- + +- inventory plugin - Python v3.5+ is now required. + +v1.9.1 +====== + +Bugfixes +-------- + +- hcloud_server - externally attached networks (using hcloud_server_network) were removed when not specified in the hcloud_server resource + +v1.9.0 +====== + +Minor Changes +------------- + +- dynamic inventory - add support changing the name of the top level group all servers are added to +- hcloud_firewall - add support for esp and gre protocols + +Bugfixes +-------- + +- hcloud_firewall - the deletion could fail if the firewall was referenced right before +- hcloud_server - fix backup window was given out as "None" instead of null +- hcloud_server_info - fix backup window was given out as "None" instead of null +- hcloud_volume - fix server name was given out as "None" instead of null if no server was attached +- hcloud_volume_info - fix server name was given out as "None" instead of null if no server was attached + +v1.8.2 +====== + +Bugfixes +-------- + +- dynamic inventory - fix crash when having servers without IPs (flexible networks) +- hcloud_server - When state stopped and server is created, do not start the server +- hcloud_server_info - fix crash when having servers without IPs (flexible networks) + +v1.8.1 +====== + +v1.8.0 +====== + +New Modules +----------- + +Hetzner +~~~~~~~ + +hcloud +^^^^^^ + +- hetzner.hcloud.hcloud_primary_ip - Create and manage cloud Primary IPs on the Hetzner Cloud. + +v1.7.1 +====== + +Minor Changes +------------- + +- inventory - allow filtering by server status + +Bugfixes +-------- + +- hcloud_server_network - fixes changed alias_ips by using sorted + +v1.7.0 +====== + +Minor Changes +------------- + +- inventory - support jinjia templating within `network` + +v1.6.0 +====== + +Minor Changes +------------- + +- hcloud_rdns Add support for load balancer + +v1.5.0 +====== + +Major Changes +------------- + +- Introduction of placement groups + +Minor Changes +------------- + +- hcloud_firewall Add description field to firewall rules + +Bugfixes +-------- + +- hcloud_rdns improve error message on not existing server/Floating IP +- hcloud_server backups property defaults to None now instead of False + +v1.4.4 +====== + +Bugfixes +-------- + +- hcloud_server Improve Error Message when attaching a not existing firewall to a server +- hcloud_volume Force detaching of volumes on servers before deletion + +v1.4.3 +====== + +Bugfixes +-------- + +- hcloud_server Fix incompatbility with python < 3.6 +- hcloud_server Improve error handling when using not existing server types + +v1.4.2 +====== + +Bugfixes +-------- + +- inventory fix image name was set as server type instead of the correct server type + +v1.4.1 +====== + +Minor Changes +------------- + +- hcloud_server - improve the handling of deprecated images +- hcloud_server - improve the validation and error response for not existing images +- inventory - support jinjia templating within `token` + +v1.4.0 +====== + +Security Fixes +-------------- + +- hcloud_certificate - mark the ``private_key`` parameter as ``no_log`` to prevent potential leaking of secret values (https://github.com/ansible-collections/hetzner.hcloud/pull/70). + +Bugfixes +-------- + +- hcloud_firewall - fix idempotence related to rules comparison (https://github.com/ansible-collections/hetzner.hcloud/pull/71). +- hcloud_load_balancer_service - fix imported wrong HealthCheck from hcloud-python (https://github.com/ansible-collections/hetzner.hcloud/pull/73). +- hcloud_server - fix idempotence related to firewall handling (https://github.com/ansible-collections/hetzner.hcloud/pull/71). + +v1.3.1 +====== + +Bugfixes +-------- + +- hcloud_server - fix a crash related to check mode if ``state=started`` or ``state=stopped`` (https://github.com/ansible-collections/hetzner.hcloud/issues/54). + +v1.3.0 +====== + +Minor Changes +------------- + +- Add firewalls to hcloud_server module + +New Modules +----------- + +- hcloud_firewall - Manage Hetzner Cloud Firewalls + +v1.2.1 +====== + +Bugfixes +-------- + +- Inventory Restore Python 2.7 compatibility + +v1.2.0 +====== + +Minor Changes +------------- + +- Dynamic Inventory Add option to specifiy the token_env variable which is used for identification if now token is set +- Improve imports of API Exception +- hcloud_server_network Allow updating alias ips +- hcloud_subnetwork Allow creating vswitch subnetworks + +New Modules +----------- + +- hcloud_load_balancer_info - Gather infos about your Hetzner Cloud load_balancers. + +v1.1.0 +====== + +Minor Changes +------------- + +- hcloud_floating_ip Allow creating Floating IP with protection +- hcloud_load_balancer Allow creating Load Balancer with protection +- hcloud_network Allow creating Network with protection +- hcloud_server Allow creating server with protection +- hcloud_volume Allow creating Volumes with protection + +Bugfixes +-------- + +- hcloud_floating_ip Fix idempotency when floating ip is assigned to server + +v1.0.0 +====== + +Minor Changes +------------- + +- hcloud_load_balancer Allow changing the type of a Load Balancer +- hcloud_server Allow the creation of servers with enabled backups + +v0.2.0 +====== + +Bugfixes +-------- + +- hcloud inventory plugin - Allow usage of hcloud.yml and hcloud.yaml - this was removed by error within the migration from build-in ansible to our collection + +v0.1.0 +====== + +New Modules +----------- + +- hcloud_floating_ip - Create and manage cloud Floating IPs on the Hetzner Cloud. +- hcloud_load_balancer - Create and manage cloud Load Balancers on the Hetzner Cloud. +- hcloud_load_balancer_network - Manage the relationship between Hetzner Cloud Networks and Load Balancers +- hcloud_load_balancer_service - Create and manage the services of cloud Load Balancers on the Hetzner Cloud. +- hcloud_load_balancer_target - Manage Hetzner Cloud Load Balancer targets +- hcloud_load_balancer_type_info - Gather infos about the Hetzner Cloud Load Balancer types. diff --git a/ansible_collections/hetzner/hcloud/COPYING b/ansible_collections/hetzner/hcloud/COPYING new file mode 100644 index 000000000..10926e87f --- /dev/null +++ b/ansible_collections/hetzner/hcloud/COPYING @@ -0,0 +1,675 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + <program> Copyright (C) <year> <name of author> + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +<http://www.gnu.org/licenses/>. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +<http://www.gnu.org/philosophy/why-not-lgpl.html>. + diff --git a/ansible_collections/hetzner/hcloud/FILES.json b/ansible_collections/hetzner/hcloud/FILES.json new file mode 100644 index 000000000..5d393e7b2 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/FILES.json @@ -0,0 +1,2441 @@ +{ + "files": [ + { + "name": ".", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": ".azure-pipelines", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": ".azure-pipelines/scripts", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": ".azure-pipelines/scripts/aggregate-coverage.sh", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "820353ffde6fd3ad655118772547549d84ccf0a7ba951e8fb1325f912ef640a0", + "format": 1 + }, + { + "name": ".azure-pipelines/scripts/combine-coverage.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e34d4e863a65b9f53c4ca8ae37655858969898a949e050e9cb3cb0d5f02342d0", + "format": 1 + }, + { + "name": ".azure-pipelines/scripts/process-results.sh", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c03d7273fe58882a439b6723e92ab89f1e127772b5ce35aa67c546dd62659741", + "format": 1 + }, + { + "name": ".azure-pipelines/scripts/publish-codecov.sh", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "70c795c8dbca2534b7909b17911630b7afaa693bbd7154e63a51340bc8b28dad", + "format": 1 + }, + { + "name": ".azure-pipelines/scripts/report-coverage.sh", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f6a373322759ccc2736fb25d25d8c402dfe16b5d9a57cfccb1ca8cb136e09663", + "format": 1 + }, + { + "name": ".azure-pipelines/scripts/run-tests.sh", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "cb08a3ec5715b00d476ae6d63ca22e11a9ad8887239439937d2a7ea342e5a623", + "format": 1 + }, + { + "name": ".azure-pipelines/scripts/time-command.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0232f415efeb583ddff907c058986963b775441eaf129d7162aee0acb0d36834", + "format": 1 + }, + { + "name": ".azure-pipelines/templates", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": ".azure-pipelines/templates/coverage.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "daf1930264760d47b54588f05c6339fd69ca2d239c77c44bc4cee3c4e9f76447", + "format": 1 + }, + { + "name": ".azure-pipelines/templates/matrix.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4fb0d3ffb2125d5806c7597e4f9d4b2af69cf8c337e9d57803081eddd4a6b081", + "format": 1 + }, + { + "name": ".azure-pipelines/templates/test.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2cfa1271f94c71f05ffa0b1f763d8946394b5636e14579cda8ee14bb38bbcf1c", + "format": 1 + }, + { + "name": ".azure-pipelines/README.md", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "61f20decd3c8fb34ac2cc6ff79f598fc5136e642130a7ba065ccc5aa37960cd2", + "format": 1 + }, + { + "name": ".azure-pipelines/azure-pipelines.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3c838b484ce8f956163c6fd6ae930f4b792075d5c1b6ddd1130196a48b9333d6", + "format": 1 + }, + { + "name": ".github", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": ".github/workflows", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": ".github/workflows/check-changelog-fragments.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c0c78648b73d122ec9cbc96b6b1640ef97fbf31cc8cbb847667ed4b68637f26d", + "format": 1 + }, + { + "name": "changelogs", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "changelogs/fragments", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "changelogs/fragments/.keep", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "changelogs/.gitignore", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "919ef00776e7d2ff349950ac4b806132aa9faf006e214d5285de54533e443b33", + "format": 1 + }, + { + "name": "changelogs/changelog.yaml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3310467c01307e817d9efba1a431034cd39df741f4c758842fce4dd899822d6b", + "format": 1 + }, + { + "name": "changelogs/config.yaml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "cfa06a718f8b3c226c944d5f33fd3ba5a07415293b07146631503539bea5d1ea", + "format": 1 + }, + { + "name": "meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "meta/runtime.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3758e45dc3de5da271325c52c3f1a95a8bb518de67721dc634a85e67e93ecc69", + "format": 1 + }, + { + "name": "plugins", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/doc_fragments", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/doc_fragments/__init__.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "plugins/doc_fragments/hcloud.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "40624302c695078a48d9883e47133f0b29809c188584af3a314005eb6c495c2d", + "format": 1 + }, + { + "name": "plugins/inventory", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/inventory/__init__.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "plugins/inventory/hcloud.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2a457686e85db10e3bde0e48a603988fbb64a121ba11372a869f66c988ea5338", + "format": 1 + }, + { + "name": "plugins/module_utils", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/module_utils/__init__.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "plugins/module_utils/hcloud.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8246c7b016b767dbf0d5bffe0e27d5f2992b83cce4393255f664e41663e2e7dd", + "format": 1 + }, + { + "name": "plugins/modules", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/modules/hcloud_datacenter_facts.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e35fd6cad5a6e867260be027a13e985623e1e147ec6b25bce848b2cf1d0d1e5f", + "format": 1 + }, + { + "name": "plugins/modules/hcloud_floating_ip_facts.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6f6b9bd26b6f508d71824832ece8b1463e7ff8bc2e9225d1f8694006793b7c16", + "format": 1 + }, + { + "name": "plugins/modules/hcloud_image_facts.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2383873b050200488ca7c90e6506a7c11539bbc60948fe914eafe1c606047c48", + "format": 1 + }, + { + "name": "plugins/modules/hcloud_location_facts.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7e4c87b1fac74d9b15dd76c462aab1cdb219ddd14009c392162e0e7ce18aecca", + "format": 1 + }, + { + "name": "plugins/modules/hcloud_server_facts.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "69f52a3daa13373f32f14074dc1ef14d586fb5d87d653438dae06d78ef12975f", + "format": 1 + }, + { + "name": "plugins/modules/hcloud_server_type_facts.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8716f5f31f6fc0c868ea9526812b99b0d67e6645f6e993983967a2a04544e5b6", + "format": 1 + }, + { + "name": "plugins/modules/hcloud_ssh_key_facts.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6b33f5766ad194010c3bf1b872d27ae6e8bf2fdb5ed7f00fcd998451b7b86528", + "format": 1 + }, + { + "name": "plugins/modules/hcloud_volume_facts.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c2e4545af69cd1ef6dc10ffbf5901b4ba1cae2cb693f5ab4bbde6603e25e6aec", + "format": 1 + }, + { + "name": "plugins/modules/__init__.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "plugins/modules/hcloud_certificate.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "73e1556df459f2da780aa9aacfd95884cec28e1e14e688aee12df4d521b04825", + "format": 1 + }, + { + "name": "plugins/modules/hcloud_certificate_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "181d2abb4c961c065a4661173835d48795e04ed569b2ac91788c5db2b0e9faeb", + "format": 1 + }, + { + "name": "plugins/modules/hcloud_datacenter_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e35fd6cad5a6e867260be027a13e985623e1e147ec6b25bce848b2cf1d0d1e5f", + "format": 1 + }, + { + "name": "plugins/modules/hcloud_firewall.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a2541dd2758819ab99904da21c29dcb7f973069d9d228ae16f77ea4bb312b4b7", + "format": 1 + }, + { + "name": "plugins/modules/hcloud_floating_ip.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6668354c432db61272b71c8424a9a2ad38fc9179bb7e5b9c88d049012cf79259", + "format": 1 + }, + { + "name": "plugins/modules/hcloud_floating_ip_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6f6b9bd26b6f508d71824832ece8b1463e7ff8bc2e9225d1f8694006793b7c16", + "format": 1 + }, + { + "name": "plugins/modules/hcloud_image_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2383873b050200488ca7c90e6506a7c11539bbc60948fe914eafe1c606047c48", + "format": 1 + }, + { + "name": "plugins/modules/hcloud_load_balancer.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9040e4d20e62211a45f5e87886d0d4274be88fa5642f7ca838bad6e9e4da85a9", + "format": 1 + }, + { + "name": "plugins/modules/hcloud_load_balancer_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "19c0f2673a29c02a85280f75f3cad34ac070ee611eb87f6b12a6ec5d15066349", + "format": 1 + }, + { + "name": "plugins/modules/hcloud_load_balancer_network.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1add8639020a29332c474f5d590b523b16c214cfc312aeb9c2fb476e6d6043ba", + "format": 1 + }, + { + "name": "plugins/modules/hcloud_load_balancer_service.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c36342c4f33995aed50a5c31de45a14c6636d4c3188edee4fd85ae1d0a4041b1", + "format": 1 + }, + { + "name": "plugins/modules/hcloud_load_balancer_target.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f228d4c8c9e3cbabb11c003db01ba4399195d5881a9e001b24c2e83cec4aed2f", + "format": 1 + }, + { + "name": "plugins/modules/hcloud_load_balancer_type_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "44b9672195eb0ba06bacd81c871595ca31455ca343887b964d3d6e473509c41a", + "format": 1 + }, + { + "name": "plugins/modules/hcloud_location_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7e4c87b1fac74d9b15dd76c462aab1cdb219ddd14009c392162e0e7ce18aecca", + "format": 1 + }, + { + "name": "plugins/modules/hcloud_network.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9e18800b2b9d77a3e14d02460263809ca5859288f4a5db9ac3f5487d590edab2", + "format": 1 + }, + { + "name": "plugins/modules/hcloud_network_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c672dbebee7fce20c3ace097e427e52e1aa240c3ac32b256a79edee38c7d89c2", + "format": 1 + }, + { + "name": "plugins/modules/hcloud_placement_group.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ac5dcbe68d6775b17a3731be4eae060ab191d649dc4d93ef5bd608d1795c3855", + "format": 1 + }, + { + "name": "plugins/modules/hcloud_primary_ip.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b9c4bb5f7c6cb8daae694c649910d2191aeb5577b04e5032e9fd4e1cb54acfde", + "format": 1 + }, + { + "name": "plugins/modules/hcloud_rdns.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f1ff9bb22dd97a9188cfdfda654bedefb0f261b7ec6b469642e93b0c7ac588db", + "format": 1 + }, + { + "name": "plugins/modules/hcloud_route.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d3b94be64a305628502f5798b6125f6f8f6ae7ab2f249d30c3773c03bf4cab2a", + "format": 1 + }, + { + "name": "plugins/modules/hcloud_server.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "60ce8badfaa9266dded57290497619e234904a555b6f61d18cfac7f0d581a780", + "format": 1 + }, + { + "name": "plugins/modules/hcloud_server_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "69f52a3daa13373f32f14074dc1ef14d586fb5d87d653438dae06d78ef12975f", + "format": 1 + }, + { + "name": "plugins/modules/hcloud_server_network.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8e6d0b574c0c7704b61d89e06b7ea7cae10d3f61f71807e4694f6cfdcf8abb7c", + "format": 1 + }, + { + "name": "plugins/modules/hcloud_server_type_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8716f5f31f6fc0c868ea9526812b99b0d67e6645f6e993983967a2a04544e5b6", + "format": 1 + }, + { + "name": "plugins/modules/hcloud_ssh_key.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "753f50cadb0aff1240c92302ba83bfe36601d80c08b131f2b975781bf2e0f2f6", + "format": 1 + }, + { + "name": "plugins/modules/hcloud_ssh_key_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6b33f5766ad194010c3bf1b872d27ae6e8bf2fdb5ed7f00fcd998451b7b86528", + "format": 1 + }, + { + "name": "plugins/modules/hcloud_subnetwork.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3100c0f9fe4f184386b57ff90462437649eaab8513631e7ed26db3f4c9514220", + "format": 1 + }, + { + "name": "plugins/modules/hcloud_volume.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7b80037e2b1ab8e38dfeb86dcaef129711436ae8314e82288cec7c3be358a94c", + "format": 1 + }, + { + "name": "plugins/modules/hcloud_volume_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c2e4545af69cd1ef6dc10ffbf5901b4ba1cae2cb693f5ab4bbde6603e25e6aec", + "format": 1 + }, + { + "name": "tests", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_certificate", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_certificate/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_certificate/defaults/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8c7f6588b06981201aaac29d9812da66019603bb613dd38f415355e6ea3f2f17", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_certificate/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_certificate/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a613e3027e9e2dd78cda228949082fa43284dae03c297b946e974ee3c0e76129", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_certificate/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_certificate/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8fb8130a3dfba9804a0d0cdb26aea93c41ae4fc555c6e0cd1cef303a6556150e", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_certificate/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b02a426443d5b1f10b9edc49bad4cdaf57c66f422bdaf60f1b4b904529431fb5", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_certificate_info", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_certificate_info/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_certificate_info/defaults/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "54abe03862bd73dde2caed2d60073ae9a9f43aa16642f877891afde936dbadd1", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_certificate_info/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_certificate_info/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "55efb3bbb7018cf5810a5c93d39d3647f245e2e46db1fda2a4a45332e951767b", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_certificate_info/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_certificate_info/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a63d83d5da4d2fbea8fd4849cadab7dadb4ade729f6b9fcc6fe5c789e766d725", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_certificate_info/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b02a426443d5b1f10b9edc49bad4cdaf57c66f422bdaf60f1b4b904529431fb5", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_datacenter_info", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_datacenter_info/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_datacenter_info/defaults/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "40e03f6534c2f7203b2b3f2aaa720b36d1ef10bfb6e6ab7f46e34bed8c976ec0", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_datacenter_info/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_datacenter_info/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_datacenter_info/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_datacenter_info/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d340f77bcf4217306d8913fed14a3fbfcc1c07960e79c9967e194386778bb2c1", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_datacenter_info/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b02a426443d5b1f10b9edc49bad4cdaf57c66f422bdaf60f1b4b904529431fb5", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_firewall", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_firewall/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_firewall/defaults/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "61d34ce86e49d5276fafb8777f7bae73bbfa4a19634bd3b20acb7a0478518c62", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_firewall/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_firewall/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_firewall/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_firewall/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "123c9c4f92f86644b19fcb25c3be36c460b4c70354f0fbfef84de7ad580954ce", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_firewall/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b02a426443d5b1f10b9edc49bad4cdaf57c66f422bdaf60f1b4b904529431fb5", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_floating_ip", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_floating_ip/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_floating_ip/defaults/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "fbd1af75fee59098d3aad4eb8d3c33da014855310457f3b28309ea0f6d601782", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_floating_ip/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_floating_ip/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_floating_ip/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_floating_ip/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0276c136a340f6ab58b9d3b70f4c977a65fb71c097afda3085bddfcb388de6b9", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_floating_ip/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b02a426443d5b1f10b9edc49bad4cdaf57c66f422bdaf60f1b4b904529431fb5", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_floating_ip_info", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_floating_ip_info/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_floating_ip_info/defaults/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8da453a6164fb45ef21bc3c098f838b2fc8d12d218b9a5181033f9d9fe1f10bc", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_floating_ip_info/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_floating_ip_info/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_floating_ip_info/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_floating_ip_info/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6046d0d6217a122060d5475f76a7cf78577850caca478faa95e8483cbb53d753", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_floating_ip_info/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b02a426443d5b1f10b9edc49bad4cdaf57c66f422bdaf60f1b4b904529431fb5", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_image_info", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_image_info/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_image_info/defaults/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8c980497c38e44bc1a09ec895a592835525728c6b43efd9e5ae77242fd0e17d3", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_image_info/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_image_info/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_image_info/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_image_info/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "54275a2526a90d1dd179f685f0a80970f34a6b714bd3ff988a6faa0613ff639d", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_image_info/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b02a426443d5b1f10b9edc49bad4cdaf57c66f422bdaf60f1b4b904529431fb5", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer/defaults/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ffd88985ada5f3e746640b7767ad9fd1b2de04da44dad808cb4c44005be83136", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "37823961c8a8ad27ade27df4d929423373e9ad5d8037240f6f137747e39f3c72", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "899ec496fc016f5e221bdf39f63bcf73374fe136f199b14cac9dd23375c989e4", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer_info", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer_info/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer_info/defaults/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b8e931c775c02f0df653a7f9b077d0baa6ff8cba2651e7e7e37b2770259b1590", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer_info/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer_info/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer_info/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer_info/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c595101d0d1838a92198660b95c472514fc903757989378c3905cb1d644e5fcd", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer_info/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "899ec496fc016f5e221bdf39f63bcf73374fe136f199b14cac9dd23375c989e4", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer_network", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer_network/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer_network/defaults/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5f84c924f92341fe78976aac4e941b2b12b351b19db1e2a1ad111d7c75143844", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer_network/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer_network/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer_network/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer_network/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5969da2934af118e3359860f30d323073a266f82adc0c1cf85335fc1c387d306", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer_network/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "899ec496fc016f5e221bdf39f63bcf73374fe136f199b14cac9dd23375c989e4", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer_service", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer_service/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer_service/defaults/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "04105693afa5180466c36776a37b716b03bf6a359f433d9a52f4e91845f8ba8a", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer_service/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer_service/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer_service/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer_service/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3107b8864c2eaeaa97d3568f7a52122b64bea694f9903151e073924b1b45574e", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer_service/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "899ec496fc016f5e221bdf39f63bcf73374fe136f199b14cac9dd23375c989e4", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer_target", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer_target/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer_target/defaults/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ecbfca90616286afa88ca58f5bd84522a9adb959fb673893e6e4298c6e07ab37", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer_target/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer_target/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer_target/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer_target/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3338b80159baa36a184a00750e6943d5cf7770febdc2db91d047f803c9410f6c", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer_target/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "899ec496fc016f5e221bdf39f63bcf73374fe136f199b14cac9dd23375c989e4", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer_type_info", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer_type_info/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer_type_info/defaults/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e5641db8ac5de1ea6954d78949fbf6ac9829f86f9135ae9361859aa8220d1892", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer_type_info/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer_type_info/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer_type_info/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer_type_info/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "56fc91b4ed15be279423b2859d700b70589bb1e10bee32a829fa263253917fa2", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_load_balancer_type_info/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b02a426443d5b1f10b9edc49bad4cdaf57c66f422bdaf60f1b4b904529431fb5", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_location_info", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_location_info/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_location_info/defaults/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "49468959c5285a8c2c8bccefa9c1d9dca5f0239158eb292bb8f4b768af0fc387", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_location_info/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_location_info/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_location_info/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_location_info/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c56ca0af25e0ff72b7f1eb84a71924b3ba7b65a659f47e2c2ee75e797dfd3ad7", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_location_info/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b02a426443d5b1f10b9edc49bad4cdaf57c66f422bdaf60f1b4b904529431fb5", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_network", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_network/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_network/defaults/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c7c1cb312be5bbd4d22ec450029303bd989ec1701b9776f03787f96539fcdd1c", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_network/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_network/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_network/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_network/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "93baf9ebc898fb93f961dad90033a1a50e0aa9aa4c9cc197aa78587738e42616", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_network/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "67633416742aa9e9480b796190982c057f5a9b7cff7c57625029c3958b07bfb4", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_network_info", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_network_info/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_network_info/defaults/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c35ec449e5b6556c7c7205e598fdc98b7dff12a9b4a6be290b3fffd936422dc4", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_network_info/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_network_info/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_network_info/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_network_info/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "cd53b64efc25ba17d0b8dd5db034e63ddd2bc30bd912766fd8e6f4bed585510a", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_network_info/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "899ec496fc016f5e221bdf39f63bcf73374fe136f199b14cac9dd23375c989e4", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_placement_group", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_placement_group/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_placement_group/defaults/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "17ff18cf59a1d19ba682a9ec20ea00af243721108fb8ae635d26a4f7c8159bdb", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_placement_group/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_placement_group/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_placement_group/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_placement_group/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "aeb396dd105fc91adda4785d275eef4a421b2a4d8512ae0ad74532452253162f", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_placement_group/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b02a426443d5b1f10b9edc49bad4cdaf57c66f422bdaf60f1b4b904529431fb5", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_primary_ip", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_primary_ip/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_primary_ip/defaults/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e6bd2109171a870ae7429ace6bf18a6c0280f188c0030623a1eb7ec5b8c2f72e", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_primary_ip/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_primary_ip/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_primary_ip/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_primary_ip/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f757913b19fad8b1256fc52046dc8f7bc286e135832837452d54e1744bf6236b", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_primary_ip/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b02a426443d5b1f10b9edc49bad4cdaf57c66f422bdaf60f1b4b904529431fb5", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_rdns", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_rdns/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_rdns/defaults/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8eb41c71669a809fdc595161f96c374cd0514a0fe232db2af105c5b97c86f7d3", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_rdns/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_rdns/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "cf9085335e8c310c57255c35c9a7f9ed61437277b0eb23af810565ed3a1b63d9", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_rdns/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_rdns/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "90b0be18a534d3700c7fefb669b463786e8c3321377b14b909135ba59629f46a", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_rdns/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "899ec496fc016f5e221bdf39f63bcf73374fe136f199b14cac9dd23375c989e4", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_route", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_route/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_route/defaults/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "73f973bef8b6a33b117aaac954c339df4a6f8c5d482db92f4cc1e95e30153001", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_route/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_route/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "cf9085335e8c310c57255c35c9a7f9ed61437277b0eb23af810565ed3a1b63d9", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_route/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_route/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "754a7fee0573b3a5c27cba4c53e7f66bb36ca485e153305efbd3b332d52c36d7", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_route/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "899ec496fc016f5e221bdf39f63bcf73374fe136f199b14cac9dd23375c989e4", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_server", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_server/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_server/defaults/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "96ffda2413e62b64c60f03e5861ebeb6bf0f2837775a412cd2f6c166329ce40b", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_server/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_server/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_server/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_server/tasks/basic.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "19feb42f8f21128cb8cf3fc0efe562d4d8b2a5d0126bde222ff1fa674204be67", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_server/tasks/firewalls.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b43203d049ab787df703e53808f6bcaed4917283ee0a6c5108fab3bc7a0533e9", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_server/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6be3a83aea87ad5a7d89ebf84718d1881d9106e95b55d0895fc58e8aaad4bb6e", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_server/tasks/primary_ips.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "67a5ed8f27f4c55daa46c128212ccffda58b15dd9e1adea5fd75d027820d45ee", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_server/tasks/private_network_only.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6fe7897ff35cd82e4bef8d5a1fa44a6901a16e75b84f605984cd8881120e41fc", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_server/tasks/validation.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4ca7c4b3b4460686b05dc9878c6365c99dd2f0e290f6b79563312ac9e48a45d7", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_server/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "899ec496fc016f5e221bdf39f63bcf73374fe136f199b14cac9dd23375c989e4", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_server_info", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_server_info/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_server_info/defaults/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "86d40e0b83f231ac4c5d3d94657d4fb529311261efbba7c0db0093959d82e3a8", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_server_info/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_server_info/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_server_info/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_server_info/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9b22f51084b84013a65066696e8e389643dc7bbf35a64545072bf5c6767b71c1", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_server_info/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b02a426443d5b1f10b9edc49bad4cdaf57c66f422bdaf60f1b4b904529431fb5", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_server_network", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_server_network/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_server_network/defaults/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0d2464557ed4ade951ffc241834816068b4869198c79cb12d58c3d297f3f9d62", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_server_network/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_server_network/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_server_network/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_server_network/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4aaf9b7f5c70031287488817d492fb19e9b23cb8e2f886bcf9c7f2b6bb647bf0", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_server_network/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "864269fd363e663625df637c1cca2ad3a47ca364bbbad59da58cb75c6002a827", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_server_type_info", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_server_type_info/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_server_type_info/defaults/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1be0206ddc23c1059392c89a7d0e321ceccf2f3f04a0a33f2d72ff209fb51b62", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_server_type_info/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_server_type_info/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_server_type_info/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_server_type_info/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "cbc4ccf4a22f6d7cf98a9c1c5710a93611f4992817dd6ffd8260e7c503274852", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_server_type_info/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b02a426443d5b1f10b9edc49bad4cdaf57c66f422bdaf60f1b4b904529431fb5", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_ssh_key", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_ssh_key/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_ssh_key/defaults/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e9edea10b3c410594ee50a56d4ee65e460170c493ac0af8b0cd64e515af2bcde", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_ssh_key/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_ssh_key/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2267e95d9e1412bfa94ca2ecb76ecf5ea027753e4b50f87cf59c9450f9090b00", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_ssh_key/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_ssh_key/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f70d83fc7b47354a328da3c929ef9c7c0eda4d9b98707e5c591399963c15c262", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_ssh_key/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b02a426443d5b1f10b9edc49bad4cdaf57c66f422bdaf60f1b4b904529431fb5", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_ssh_key_info", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_ssh_key_info/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_ssh_key_info/defaults/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "622b6f801a80c1b2c9e0f1743cee078a93689245b8b05744a6797e52cd152272", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_ssh_key_info/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_ssh_key_info/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2267e95d9e1412bfa94ca2ecb76ecf5ea027753e4b50f87cf59c9450f9090b00", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_ssh_key_info/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_ssh_key_info/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f2ac15fb16f8e251a10741c938b839bbe4786accfa7ac34a52624f1b03c4d1a9", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_ssh_key_info/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b02a426443d5b1f10b9edc49bad4cdaf57c66f422bdaf60f1b4b904529431fb5", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_subnetwork", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_subnetwork/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_subnetwork/defaults/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "48ec8ba489041f8a7f69a1bdca4f5e2a4fea038317fb8cafd870b9b2dc2aaea9", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_subnetwork/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_subnetwork/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_subnetwork/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_subnetwork/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a1b71650fde2c710274a8e017cca68e43832ed6360f5aa83eb5909f8f8dc00fd", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_subnetwork/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a48fdbe183742154c38aa6c1658c907b2fac5a90170aca3f813c65234a33845e", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_volume", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_volume/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_volume/defaults/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4be0ed3631b304c66f46a7443dcc0f05b5d7eb99c8d55dc98cf3a4ec7a513a44", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_volume/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_volume/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_volume/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_volume/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b41df455c2d9c29dad9308de40a386808471e6c386ca104bb9f3fcd0f7b86555", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_volume/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b02a426443d5b1f10b9edc49bad4cdaf57c66f422bdaf60f1b4b904529431fb5", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_volume_info", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_volume_info/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_volume_info/defaults/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "59d614a7e95cd4313b26fb93a5bc68fc1fd8591c08a103af883e885858232d0f", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_volume_info/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_volume_info/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_volume_info/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_volume_info/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "85ad9de1a55e08bfa43a2fc065a515eda84b9c4cc411fee92084c7f47ad7485b", + "format": 1 + }, + { + "name": "tests/integration/targets/hcloud_volume_info/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b02a426443d5b1f10b9edc49bad4cdaf57c66f422bdaf60f1b4b904529431fb5", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_selfsigned_certificate", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_selfsigned_certificate/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_selfsigned_certificate/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e4d4d2cd1069f55d65c9e6c8017b934023575ecb0540445ac65714d85d422323", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_sshkey", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_sshkey/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_sshkey/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "016d4c789eb18f7d6cd03c5cd32300a5e1ccec5bb6695aa35b60105c0a5d5d2d", + "format": 1 + }, + { + "name": "tests/integration/constraints.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "67495540cfc7f8c60daeb40ff4607f95f44163cd1ee5d29726042ae7e6c7f45b", + "format": 1 + }, + { + "name": "tests/integration/requirements.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f8c3336e9a6315d376c59719374e5aced8a1be5d81934e0b39bc9483fdb49dc6", + "format": 1 + }, + { + "name": "tests/sanity", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/sanity/ignore-2.12.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8b8815dcc93330716312b23095ad47d984bd7a039d20a5baa1127ce6d73ca6e8", + "format": 1 + }, + { + "name": "tests/sanity/ignore-2.13.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8b8815dcc93330716312b23095ad47d984bd7a039d20a5baa1127ce6d73ca6e8", + "format": 1 + }, + { + "name": "tests/sanity/ignore-2.14.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8b8815dcc93330716312b23095ad47d984bd7a039d20a5baa1127ce6d73ca6e8", + "format": 1 + }, + { + "name": "tests/sanity/ignore-2.15.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8b8815dcc93330716312b23095ad47d984bd7a039d20a5baa1127ce6d73ca6e8", + "format": 1 + }, + { + "name": "tests/utils", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/utils/gitlab", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/utils/gitlab/gitlab.sh", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4649d4c2fc11e85bf786a6a5894aebdb114f32866006f18d3878058b04318501", + "format": 1 + }, + { + "name": "tests/utils/gitlab/integration.sh", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "944d27147a31dfcee01b9a94003b06d1f0ba0c7a396b8fe53f606045ad80d186", + "format": 1 + }, + { + "name": "tests/utils/gitlab/sanity.sh", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d3de6463986d6023f096a2cd43900cecbe697e7b46b4014a6d1c22a7403a7017", + "format": 1 + }, + { + "name": "tests/utils/shippable", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/utils/shippable/check_matrix.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "eca2864515b1854d55dd78b481962969ac030fa8d10a4fd4edb9833797ee85fb", + "format": 1 + }, + { + "name": "tests/utils/shippable/hcloud.sh", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "99db6946e47cf9e49ab2fccbe0aca8ffc9aaa0918fdc9e3ef543601c55a98713", + "format": 1 + }, + { + "name": "tests/utils/shippable/sanity.sh", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5815f3e7f2275a6a218e94c6529b0bff8dd6c162b2fece0dacca65cf734c0b22", + "format": 1 + }, + { + "name": "tests/utils/shippable/shippable.sh", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "76af8f1e1f106ddd5128405aed79a4cfc15f26ee090b1f20c0b82281efecaf51", + "format": 1 + }, + { + "name": "tests/utils/shippable/timing.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ebb7d3553349747ad41d80899ed353e13cf32fcbecbb6566cf36e9d2bc33703e", + "format": 1 + }, + { + "name": "tests/utils/shippable/timing.sh", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f3f3cc03a997cdba719b0542fe668fc612451841cbe840ab36865f30aa54a1bd", + "format": 1 + }, + { + "name": "tests/.gitignore", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b5726d3ec9335a09c124469eca039523847a6b0f08a083efaefd002b83326600", + "format": 1 + }, + { + "name": "tests/requirements.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "bdf5cf6c8dad0da0a4e726e341c0ba44124759ce387da8c1ab158a6662cb2bb6", + "format": 1 + }, + { + "name": ".gitignore", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c30262d952e58b561e2f59039f3dee6b5b16ff266c5e948fa7ea36049a19acfb", + "format": 1 + }, + { + "name": ".gitlab-ci.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1fcd5b6555665ac7b3c1a8ec7c0183fc3c9e35f40d8e549faf02a86b948338bc", + "format": 1 + }, + { + "name": "CHANGELOG.rst", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5c0249d66834ac8b3f72c326bf8666fc3c46ca8d8f023b9363745a0b7c51e358", + "format": 1 + }, + { + "name": "COPYING", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0ae0485a5bd37a63e63603596417e4eb0e653334fa6c7f932ca3a0e85d4af227", + "format": 1 + }, + { + "name": "README.md", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "036ac8c2ec4dcbbdea4b3b5e8bb5ad1e0576ffa4795ad7dc6245da27f8f7998e", + "format": 1 + } + ], + "format": 1 +}
\ No newline at end of file diff --git a/ansible_collections/hetzner/hcloud/MANIFEST.json b/ansible_collections/hetzner/hcloud/MANIFEST.json new file mode 100644 index 000000000..3a02e99d8 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/MANIFEST.json @@ -0,0 +1,36 @@ +{ + "collection_info": { + "namespace": "hetzner", + "name": "hcloud", + "version": "1.11.0", + "authors": [ + "Hetzner Cloud (github.com/hetznercloud)" + ], + "readme": "README.md", + "tags": [ + "hetzner", + "cloud", + "hcloud" + ], + "description": "A Collection for managing Hetzner Cloud resources", + "license": [ + "GPL-3.0-or-later" + ], + "license_file": null, + "dependencies": { + "ansible.netcommon": ">=0.0.1" + }, + "repository": "https://github.com/ansible-collections/hetzner.hcloud", + "documentation": "https://docs.ansible.com/ansible/latest/collections/hetzner/hcloud", + "homepage": "https://github.com/ansible-collections/hetzner.hcloud", + "issues": "https://github.com/ansible-collections/hetzner.hcloud/issues" + }, + "file_manifest_file": { + "name": "FILES.json", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c08aa88abd3883a1ce05b7b35a56516e099b1d2b8acbc37dc495d39e2874c31a", + "format": 1 + }, + "format": 1 +}
\ No newline at end of file diff --git a/ansible_collections/hetzner/hcloud/README.md b/ansible_collections/hetzner/hcloud/README.md new file mode 100644 index 000000000..f57ae7be3 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/README.md @@ -0,0 +1,62 @@ +[![Build Status](https://dev.azure.com/ansible/hetzner.hcloud/_apis/build/status/CI?branchName=master)](https://dev.azure.com/ansible/hetzner.hcloud/_build?definitionId=35) +[![Codecov](https://img.shields.io/codecov/c/github/ansible-collections/hetzner.hcloud)](https://codecov.io/gh/ansible-collections/hetzner.hcloud) + +Ansible Collection: hetzner.hcloud +================================================= + +Ansible Hetzner Cloud Collection for controlling your Hetzner Cloud Resources. + +## Release notes + +See [here](https://github.com/ansible-collections/hetzner.hcloud/tree/master/CHANGELOG.rst). + +## Documentation + +The documentation for all modules are available through `ansible-doc`. + +Sample: `ansible-doc hetzner.hcloud.hcloud_server` shows the documentation for the `hcloud_server` module. + +For all modules that were part of Ansible directly (before Ansible 2.11) we also have the documentation published in the +Ansible documentation: https://docs.ansible.com/ansible/latest/collections/hetzner/hcloud/ + +# Development + +## Requirements + +You should place the collection (clone the repository) into the Ansible collection path. Normally this +is `~/.ansible/collections/ansible_collections/<namespace>/<collection`, so for our collection it would +be: `~/.ansible/collections/ansible_collections/hetzner/hcloud`. + +``` +git clone git@github.com:ansible-collections/hetzner.hcloud.git ~/.ansible/collections/ansible_collections/hetzner/hcloud +``` + +After this you just need `ansible` installed. + +## Testing + +Testing is done via `ansible-test`. Make sure to have a `cloud-config-hcloud.ini` file in `tests/integration` which +contains the hcloud API token: + +``` +[default] +hcloud_api_token=<token> +``` + +After this you should be able to use `ansible-test integration` to perform the integration tests for a specific module. +Sample: + +``` +ansible-test integration --color --local -vvv hcloud_server // Executed all integration tests for hcloud_server module +``` + +## Releasing a new version + +### Generating changelog from fragments + +1. Check if the changelog fragments are available (there should be files in `changelogs/fragments`) +2. Run `antsibull-changelog release --version <version>`, it should remove all fragments and change + the `changelogs/changlog.yaml` and `CHANGELOG.rst` +3. Push the changes to the main branch +4. Tag the release through the Github UI, after this the Github Actions will run and publish the collection to Ansible + Galaxy diff --git a/ansible_collections/hetzner/hcloud/changelogs/.gitignore b/ansible_collections/hetzner/hcloud/changelogs/.gitignore new file mode 100644 index 000000000..6be6b5331 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/changelogs/.gitignore @@ -0,0 +1 @@ +/.plugin-cache.yaml diff --git a/ansible_collections/hetzner/hcloud/changelogs/changelog.yaml b/ansible_collections/hetzner/hcloud/changelogs/changelog.yaml new file mode 100644 index 000000000..0a4f123d0 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/changelogs/changelog.yaml @@ -0,0 +1,275 @@ +ancestor: null +releases: + 0.1.0: + modules: + - description: Create and manage cloud Floating IPs on the Hetzner Cloud. + name: hcloud_floating_ip + namespace: '' + - description: Create and manage cloud Load Balancers on the Hetzner Cloud. + name: hcloud_load_balancer + namespace: '' + - description: Manage the relationship between Hetzner Cloud Networks and Load + Balancers + name: hcloud_load_balancer_network + namespace: '' + - description: Create and manage the services of cloud Load Balancers on the Hetzner + Cloud. + name: hcloud_load_balancer_service + namespace: '' + - description: Manage Hetzner Cloud Load Balancer targets + name: hcloud_load_balancer_target + namespace: '' + - description: Gather infos about the Hetzner Cloud Load Balancer types. + name: hcloud_load_balancer_type_info + namespace: '' + release_date: '2020-06-29' + 0.2.0: + changes: + bugfixes: + - hcloud inventory plugin - Allow usage of hcloud.yml and hcloud.yaml - this + was removed by error within the migration from build-in ansible to our collection + fragments: + - inventory-allow-usage-of-pre-migration-configuration-fuiles.yml + release_date: '2020-06-30' + 1.0.0: + changes: + minor_changes: + - hcloud_load_balancer Allow changing the type of a Load Balancer + - hcloud_server Allow the creation of servers with enabled backups + fragments: + - gh7-allow-enabling-of-backups-on-server-creation.yml + - lb-allow-change-type.yml + release_date: '2020-08-11' + 1.1.0: + changes: + bugfixes: + - hcloud_floating_ip Fix idempotency when floating ip is assigned to server + minor_changes: + - hcloud_floating_ip Allow creating Floating IP with protection + - hcloud_load_balancer Allow creating Load Balancer with protection + - hcloud_network Allow creating Network with protection + - hcloud_server Allow creating server with protection + - hcloud_volume Allow creating Volumes with protection + fragments: + - fix-idempotency-floating-ip.yml + - gh-28-allow-setting-of-protection-on-creation.yml + release_date: '2020-10-05' + 1.10.0: + changes: + breaking_changes: + - inventory plugin - Python v3.5+ is now required. + minor_changes: + - hcloud_server - add private_networks_info containing name and private ip in + responses + - hcloud_server_info - add private_networks_info containing name and private + ip in responses + - inventory plugin - Add list of all private networks to server variables. + - inventory plugin - Add new connect_with setting public_ipv6 to connect to + discovered servers via public IPv6 address. + - inventory plugin - Add public IPv6 address to server variables. + - inventory plugin - Log warning instead of crashing when some servers do not + work with global connect_with setting. + fragments: + - inventory-connect-via-ipv6.yml + - inventory-handle-invalid-connect-with.yml + - inventory-ipv6-adress-variable.yml + - inventory-private-network-info.yml + - server-private-networks-info.yml + release_date: '2023-02-01' + 1.10.1: + changes: + bugfixes: + - hcloud_server - Prevent backups from being disabled when undefined + - hcloud_server - Server locked after attaching to placement group + fragments: + - server-fix-backups.yml + - server-race-condition-pg-attach.yml + release_date: '2023-04-03' + 1.11.0: + changes: + minor_changes: + - hcloud_image_info - Add cpu architecture field to return value. + - hcloud_image_info - Allow filtering images by cpu architecture. + - hcloud_server - Select matching image for the cpu architecture of the server + type on create & rebuild. + - hcloud_server_type_info - Add cpu architecture field to return value. + - inventory plugin - Add cpu architecture to server variables. + fragments: + - arm-features.yaml + release_date: '2023-04-11' + 1.2.0: + changes: + minor_changes: + - Dynamic Inventory Add option to specifiy the token_env variable which is used + for identification if now token is set + - Improve imports of API Exception + - hcloud_server_network Allow updating alias ips + - hcloud_subnetwork Allow creating vswitch subnetworks + modules: + - description: Gather infos about your Hetzner Cloud load_balancers. + name: hcloud_load_balancer_info + namespace: '' + release_date: '2020-12-01' + 1.2.1: + changes: + bugfixes: + - Inventory Restore Python 2.7 compatibility + release_date: '2020-12-16' + 1.3.0: + changes: + minor_changes: + - Add firewalls to hcloud_server module + modules: + - description: Manage Hetzner Cloud Firewalls + name: hcloud_firewall + namespace: '' + release_date: '2021-03-11' + 1.3.1: + changes: + bugfixes: + - hcloud_server - fix a crash related to check mode if ``state=started`` or + ``state=stopped`` (https://github.com/ansible-collections/hetzner.hcloud/issues/54). + fragments: + - 64-hcloud_server_fix_checkmode_state_started.yml + release_date: '2021-03-18' + 1.4.0: + changes: + bugfixes: + - hcloud_firewall - fix idempotence related to rules comparison (https://github.com/ansible-collections/hetzner.hcloud/pull/71). + - hcloud_load_balancer_service - fix imported wrong HealthCheck from hcloud-python + (https://github.com/ansible-collections/hetzner.hcloud/pull/73). + - hcloud_server - fix idempotence related to firewall handling (https://github.com/ansible-collections/hetzner.hcloud/pull/71). + security_fixes: + - hcloud_certificate - mark the ``private_key`` parameter as ``no_log`` to prevent + potential leaking of secret values (https://github.com/ansible-collections/hetzner.hcloud/pull/70). + fragments: + - 70-no_log_security_fixes.yml + - 71-hcloud_firewall_fix_idempotence.yml + - 73-hcloud_load_balancer_service_fix_wrong_import.yml + release_date: '2021-04-06' + 1.4.1: + changes: + minor_changes: + - hcloud_server - improve the handling of deprecated images + - hcloud_server - improve the validation and error response for not existing + images + - inventory - support jinjia templating within `token` + fragments: + - 74-hcloud_server-improve-error-message-images.yml + release_date: '2021-04-07' + 1.4.2: + changes: + bugfixes: + - inventory fix image name was set as server type instead of the correct server + type + fragments: + - inventory-fix-server-type-wrong-value.yml + release_date: '2021-04-14' + 1.4.3: + changes: + bugfixes: + - hcloud_server Fix incompatbility with python < 3.6 + - hcloud_server Improve error handling when using not existing server types + fragments: + - hcloud-server-py36.yaml + - hcloud-server-server-type.yaml + release_date: '2021-04-22' + 1.4.4: + changes: + bugfixes: + - hcloud_server Improve Error Message when attaching a not existing firewall + to a server + - hcloud_volume Force detaching of volumes on servers before deletion + fragments: + - hcloud_server-improve-error-message-on-not-existing-firewall.yml + - hcloud_volume-force-detach-before-deletion.yml + release_date: '2021-07-19' + 1.5.0: + changes: + bugfixes: + - hcloud_rdns improve error message on not existing server/Floating IP + - hcloud_server backups property defaults to None now instead of False + major_changes: + - Introduction of placement groups + minor_changes: + - hcloud_firewall Add description field to firewall rules + fragments: + - hcloud_firewall-add-description-field-to-rules.yml + - hcloud_placement_group.yml + - hcloud_rdns-improve-validation-of-input.yml + - hcloud_server_default-backups-to-none.yml + release_date: '2021-08-16' + 1.6.0: + changes: + minor_changes: + - hcloud_rdns Add support for load balancer + fragments: + - hcloud_rdns-add-support-for-load-balancers.yml + release_date: '2021-08-17' + 1.7.0: + changes: + minor_changes: + - inventory - support jinjia templating within `network` + fragments: + - inventory-network-templating.yml + release_date: '2022-06-09' + 1.7.1: + changes: + bugfixes: + - hcloud_server_network - fixes changed alias_ips by using sorted + minor_changes: + - inventory - allow filtering by server status + fragments: + - hcloud_server_network-alias-ips.yaml + - inventory-filter-by-status.yaml + release_date: '2022-06-13' + 1.8.0: + modules: + - description: Create and manage cloud Primary IPs on the Hetzner Cloud. + name: hcloud_primary_ip + namespace: hetzner.hcloud + release_date: '2022-06-29' + 1.8.1: + release_date: '2022-06-29' + 1.8.2: + changes: + bugfixes: + - dynamic inventory - fix crash when having servers without IPs (flexible networks) + - hcloud_server - When state stopped and server is created, do not start the + server + - hcloud_server_info - fix crash when having servers without IPs (flexible networks) + fragments: + - flexible-networks-hcloud-server-info.yml + - inventory.yml + release_date: '2022-09-14' + 1.9.0: + changes: + bugfixes: + - hcloud_firewall - the deletion could fail if the firewall was referenced right + before + - hcloud_server - fix backup window was given out as "None" instead of null + - hcloud_server_info - fix backup window was given out as "None" instead of + null + - hcloud_volume - fix server name was given out as "None" instead of null if + no server was attached + - hcloud_volume_info - fix server name was given out as "None" instead of null + if no server was attached + minor_changes: + - dynamic inventory - add support changing the name of the top level group all + servers are added to + - hcloud_firewall - add support for esp and gre protocols + fragments: + - hcloud_firewall-deletion.yml + - hcloud_firewall-esp-gre.yml + - hcloud_inventory.yml + - hcloud_server_backup_window.yml + - hcloud_volume_server_none.yml + release_date: '2022-11-10' + 1.9.1: + changes: + bugfixes: + - hcloud_server - externally attached networks (using hcloud_server_network) + were removed when not specified in the hcloud_server resource + fragments: + - hcloud_server-removed-networks.yml + release_date: '2022-12-20' diff --git a/ansible_collections/hetzner/hcloud/changelogs/config.yaml b/ansible_collections/hetzner/hcloud/changelogs/config.yaml new file mode 100644 index 000000000..739603a44 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/changelogs/config.yaml @@ -0,0 +1,28 @@ +title: Hetzner Cloud Ansible Collection +changelog_filename_template: ../CHANGELOG.rst +changelog_filename_version_depth: 0 +changes_file: changelog.yaml +changes_format: combined +keep_fragments: false +mention_ancestor: true +new_plugins_after_name: removed_features +notesdir: fragments +prelude_section_name: release_summary +prelude_section_title: Release Summary +sections: +- - major_changes + - Major Changes +- - minor_changes + - Minor Changes +- - breaking_changes + - Breaking Changes / Porting Guide +- - deprecated_features + - Deprecated Features +- - removed_features + - Removed Features (previously deprecated) +- - security_fixes + - Security Fixes +- - bugfixes + - Bugfixes +- - known_issues + - Known Issues diff --git a/ansible_collections/hetzner/hcloud/changelogs/fragments/.keep b/ansible_collections/hetzner/hcloud/changelogs/fragments/.keep new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/hetzner/hcloud/changelogs/fragments/.keep diff --git a/ansible_collections/hetzner/hcloud/meta/runtime.yml b/ansible_collections/hetzner/hcloud/meta/runtime.yml new file mode 100644 index 000000000..d37a1864a --- /dev/null +++ b/ansible_collections/hetzner/hcloud/meta/runtime.yml @@ -0,0 +1,35 @@ +requires_ansible: '>=2.12.0' +plugin_routing: + modules: + hcloud_location_facts: + deprecation: + removal_version: 2.0.0 + warning_text: see plugin documentation for details + hcloud_server_type_facts: + deprecation: + removal_version: 2.0.0 + warning_text: see plugin documentation for details + hcloud_image_facts: + deprecation: + removal_version: 2.0.0 + warning_text: see plugin documentation for details + hcloud_volume_facts: + deprecation: + removal_version: 2.0.0 + warning_text: see plugin documentation for details + hcloud_floating_ip_facts: + deprecation: + removal_version: 2.0.0 + warning_text: see plugin documentation for details + hcloud_ssh_key_facts: + deprecation: + removal_version: 2.0.0 + warning_text: see plugin documentation for details + hcloud_datacenter_facts: + deprecation: + removal_version: 2.0.0 + warning_text: see plugin documentation for details + hcloud_server_facts: + deprecation: + removal_version: 2.0.0 + warning_text: see plugin documentation for details diff --git a/ansible_collections/hetzner/hcloud/plugins/doc_fragments/__init__.py b/ansible_collections/hetzner/hcloud/plugins/doc_fragments/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/doc_fragments/__init__.py diff --git a/ansible_collections/hetzner/hcloud/plugins/doc_fragments/hcloud.py b/ansible_collections/hetzner/hcloud/plugins/doc_fragments/hcloud.py new file mode 100644 index 000000000..d19e64616 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/doc_fragments/hcloud.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + + +class ModuleDocFragment(object): + DOCUMENTATION = ''' +options: + api_token: + description: + - This is the API Token for the Hetzner Cloud. + - You can also set this option by using the environment variable HCLOUD_TOKEN + required: True + type: str + endpoint: + description: + - This is the API Endpoint for the Hetzner Cloud. + default: https://api.hetzner.cloud/v1 + type: str +requirements: + - hcloud-python >= 1.0.0 +seealso: +- name: Documentation for Hetzner Cloud API + description: Complete reference for the Hetzner Cloud API. + link: https://docs.hetzner.cloud/ +''' diff --git a/ansible_collections/hetzner/hcloud/plugins/inventory/__init__.py b/ansible_collections/hetzner/hcloud/plugins/inventory/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/inventory/__init__.py diff --git a/ansible_collections/hetzner/hcloud/plugins/inventory/hcloud.py b/ansible_collections/hetzner/hcloud/plugins/inventory/hcloud.py new file mode 100644 index 000000000..390dedc4c --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/inventory/hcloud.py @@ -0,0 +1,351 @@ +# Copyright (c) 2019 Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + +DOCUMENTATION = r''' + name: hcloud + author: + - Lukas Kaemmerling (@lkaemmerling) + short_description: Ansible dynamic inventory plugin for the Hetzner Cloud. + requirements: + - python >= 3.5 + - hcloud-python >= 1.0.0 + description: + - Reads inventories from the Hetzner Cloud API. + - Uses a YAML configuration file that ends with hcloud.(yml|yaml). + extends_documentation_fragment: + - constructed + options: + plugin: + description: marks this as an instance of the "hcloud" plugin + required: true + choices: ["hcloud", "hetzner.hcloud.hcloud"] + token: + description: The Hetzner Cloud API Token. + required: false + group: + description: The group all servers are automatically added to. + default: hcloud + type: str + required: false + token_env: + description: Environment variable to load the Hetzner Cloud API Token from. + default: HCLOUD_TOKEN + type: str + required: false + connect_with: + description: | + Connect to the server using the value from this field. This sets the `ansible_host` + variable to the value indicated, if that value is available. If you need further + customization, like falling back to private ipv4 if the server has no public ipv4, + you can use `compose` top-level key. + default: public_ipv4 + type: str + choices: + - public_ipv4 + - public_ipv6 + - hostname + - ipv4_dns_ptr + - private_ipv4 + locations: + description: Populate inventory with instances in this location. + default: [] + type: list + elements: str + required: false + types: + description: Populate inventory with instances with this type. + default: [] + type: list + elements: str + required: false + images: + description: Populate inventory with instances with this image name, only available for system images. + default: [] + type: list + elements: str + required: false + label_selector: + description: Populate inventory with instances with this label. + default: "" + type: str + required: false + network: + description: Populate inventory with instances which are attached to this network name or ID. + default: "" + type: str + required: false + status: + description: Populate inventory with instances with this status. + default: [] + type: list + elements: str + required: false +''' + +EXAMPLES = r""" +# Minimal example. `HCLOUD_TOKEN` is exposed in environment. +plugin: hcloud + +# Example with templated token, e.g. provided through extra vars. +plugin: hcloud +token: "{{ hetzner_apitoken }}" + +# Example with locations, types, status and token +plugin: hcloud +token: foobar +locations: + - nbg1 +types: + - cx11 +status: + - running + +# Group by a location with prefix e.g. "hcloud_location_nbg1" +# and image_os_flavor without prefix and separator e.g. "ubuntu" +# and status with prefix e.g. "server_status_running" +plugin: hcloud +keyed_groups: + - key: location + prefix: hcloud_location + - key: image_os_flavor + separator: "" + - key: status + prefix: server_status +""" + +import os +from ansible.errors import AnsibleError +from ansible.module_utils._text import to_native +from ansible.plugins.inventory import BaseInventoryPlugin, Constructable +from ansible.release import __version__ +from ipaddress import IPv6Network + +try: + from hcloud import hcloud + from hcloud import APIException + HAS_HCLOUD = True +except ImportError: + HAS_HCLOUD = False + + +class InventoryModule(BaseInventoryPlugin, Constructable): + NAME = 'hetzner.hcloud.hcloud' + + def _configure_hcloud_client(self): + self.token_env = self.get_option("token_env") + self.templar.available_variables = self._vars + self.api_token = self.templar.template(self.get_option("token"), fail_on_undefined=False) or os.getenv(self.token_env) + if self.api_token is None: + raise AnsibleError( + "Please specify a token, via the option token, via environment variable HCLOUD_TOKEN " + "or via custom environment variable set by token_env option." + ) + + self.endpoint = os.getenv("HCLOUD_ENDPOINT") or "https://api.hetzner.cloud/v1" + + self.client = hcloud.Client(token=self.api_token, + api_endpoint=self.endpoint, + application_name="ansible-inventory", + application_version=__version__) + + def _test_hcloud_token(self): + try: + # We test the API Token against the location API, because this is the API with the smallest result + # and not controllable from the customer. + self.client.locations.get_all() + except APIException: + raise AnsibleError("Invalid Hetzner Cloud API Token.") + + def _get_servers(self): + if len(self.get_option("label_selector")) > 0: + self.servers = self.client.servers.get_all(label_selector=self.get_option("label_selector")) + else: + self.servers = self.client.servers.get_all() + + def _filter_servers(self): + if self.get_option("network"): + network = self.templar.template(self.get_option("network"), fail_on_undefined=False) or self.get_option("network") + try: + self.network = self.client.networks.get_by_name(network) + if self.network is None: + self.network = self.client.networks.get_by_id(network) + except APIException: + raise AnsibleError( + "The given network is not found.") + + tmp = [] + for server in self.servers: + for server_private_network in server.private_net: + if server_private_network.network.id == self.network.id: + tmp.append(server) + self.servers = tmp + + if self.get_option("locations"): + tmp = [] + for server in self.servers: + if server.datacenter.location.name in self.get_option("locations"): + tmp.append(server) + self.servers = tmp + + if self.get_option("types"): + tmp = [] + for server in self.servers: + if server.server_type.name in self.get_option("types"): + tmp.append(server) + self.servers = tmp + + if self.get_option("images"): + tmp = [] + for server in self.servers: + if server.image is not None and server.image.os_flavor in self.get_option("images"): + tmp.append(server) + self.servers = tmp + + if self.get_option("status"): + tmp = [] + for server in self.servers: + if server.status in self.get_option("status"): + tmp.append(server) + self.servers = tmp + + def _set_server_attributes(self, server): + self.inventory.set_variable(server.name, "id", to_native(server.id)) + self.inventory.set_variable(server.name, "name", to_native(server.name)) + self.inventory.set_variable(server.name, "status", to_native(server.status)) + self.inventory.set_variable(server.name, "type", to_native(server.server_type.name)) + self.inventory.set_variable(server.name, "architecture", to_native(server.server_type.architecture)) + + # Network + if server.public_net.ipv4: + self.inventory.set_variable(server.name, "ipv4", to_native(server.public_net.ipv4.ip)) + + if server.public_net.ipv6: + self.inventory.set_variable(server.name, "ipv6_network", to_native(server.public_net.ipv6.network)) + self.inventory.set_variable(server.name, "ipv6_network_mask", to_native(server.public_net.ipv6.network_mask)) + self.inventory.set_variable(server.name, "ipv6", to_native(self._first_ipv6_address(server.public_net.ipv6.ip))) + + self.inventory.set_variable( + server.name, + "private_networks", + [ + {"name": n.network.name, "id": n.network.id, "ip": n.ip} + for n in server.private_net + ], + ) + + if self.get_option("network"): + for server_private_network in server.private_net: + # Set private_ipv4 if user filtered for one network + if server_private_network.network.id == self.network.id: + self.inventory.set_variable(server.name, "private_ipv4", to_native(server_private_network.ip)) + + try: + self.inventory.set_variable(server.name, "ansible_host", self._get_server_ansible_host(server)) + except AnsibleError as e: + # Log warning that for this host can not be connected to, using the + # method specified in `connect_with`. Users might use `compose` to + # override the connection method, or implement custom logic, so we + # do not need to abort if nothing matched. + self.display.v("[hcloud] %s" % e, server.name) + + # Server Type + if server.server_type is not None: + self.inventory.set_variable(server.name, "server_type", to_native(server.server_type.name)) + + # Datacenter + self.inventory.set_variable(server.name, "datacenter", to_native(server.datacenter.name)) + self.inventory.set_variable(server.name, "location", to_native(server.datacenter.location.name)) + + # Image + if server.image is not None: + self.inventory.set_variable(server.name, "image_id", to_native(server.image.id)) + self.inventory.set_variable(server.name, "image_os_flavor", to_native(server.image.os_flavor)) + if server.image.name is not None: + self.inventory.set_variable(server.name, "image_name", to_native(server.image.name)) + else: + self.inventory.set_variable(server.name, "image_name", to_native(server.image.description)) + else: + self.inventory.set_variable(server.name, "image_id", to_native("No Image ID found")) + self.inventory.set_variable(server.name, "image_name", to_native("No Image Name found")) + self.inventory.set_variable(server.name, "image_os_flavor", to_native("No Image OS Flavor found")) + + # Labels + self.inventory.set_variable(server.name, "labels", dict(server.labels)) + + def _get_server_ansible_host(self, server): + if self.get_option("connect_with") == "public_ipv4": + if server.public_net.ipv4: + return to_native(server.public_net.ipv4.ip) + else: + raise AnsibleError("Server has no public ipv4, but connect_with=public_ipv4 was specified") + + if self.get_option("connect_with") == "public_ipv6": + if server.public_net.ipv6: + return to_native(self._first_ipv6_address(server.public_net.ipv6.ip)) + else: + raise AnsibleError("Server has no public ipv6, but connect_with=public_ipv6 was specified") + + elif self.get_option("connect_with") == "hostname": + # every server has a name, no need to guard this + return to_native(server.name) + + elif self.get_option("connect_with") == "ipv4_dns_ptr": + if server.public_net.ipv4: + return to_native(server.public_net.ipv4.dns_ptr) + else: + raise AnsibleError("Server has no public ipv4, but connect_with=ipv4_dns_ptr was specified") + + elif self.get_option("connect_with") == "private_ipv4": + if self.get_option("network"): + for server_private_network in server.private_net: + if server_private_network.network.id == self.network.id: + return to_native(server_private_network.ip) + + else: + raise AnsibleError( + "You can only connect via private IPv4 if you specify a network") + + def _first_ipv6_address(self, network): + return next(IPv6Network(network).hosts()) + + def verify_file(self, path): + """Return the possibly of a file being consumable by this plugin.""" + return ( + super(InventoryModule, self).verify_file(path) and + path.endswith(("hcloud.yaml", "hcloud.yml")) + ) + + def parse(self, inventory, loader, path, cache=True): + super(InventoryModule, self).parse(inventory, loader, path, cache) + + if not HAS_HCLOUD: + raise AnsibleError("The Hetzner Cloud dynamic inventory plugin requires hcloud-python.") + + self._read_config_data(path) + self._configure_hcloud_client() + self._test_hcloud_token() + self._get_servers() + self._filter_servers() + + # Add a top group + self.inventory.add_group(group=self.get_option("group")) + + for server in self.servers: + self.inventory.add_host(server.name, group=self.get_option("group")) + self._set_server_attributes(server) + + # Use constructed if applicable + strict = self.get_option('strict') + + # Composed variables + self._set_composite_vars(self.get_option('compose'), self.inventory.get_host(server.name).get_vars(), server.name, strict=strict) + + # Complex groups based on jinja2 conditionals, hosts that meet the conditional are added to group + self._add_host_to_composed_groups(self.get_option('groups'), {}, server.name, strict=strict) + + # Create groups based on variable values and add the corresponding hosts to it + self._add_host_to_keyed_groups(self.get_option('keyed_groups'), {}, server.name, strict=strict) diff --git a/ansible_collections/hetzner/hcloud/plugins/module_utils/__init__.py b/ansible_collections/hetzner/hcloud/plugins/module_utils/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/module_utils/__init__.py diff --git a/ansible_collections/hetzner/hcloud/plugins/module_utils/hcloud.py b/ansible_collections/hetzner/hcloud/plugins/module_utils/hcloud.py new file mode 100644 index 000000000..932b0c529 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/module_utils/hcloud.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> + +# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +from ansible.module_utils.ansible_release import __version__ +from ansible.module_utils.basic import env_fallback, missing_required_lib + +try: + import hcloud + + HAS_HCLOUD = True +except ImportError: + HAS_HCLOUD = False + + +class Hcloud(object): + def __init__(self, module, represent): + self.module = module + self.represent = represent + self.result = {"changed": False, self.represent: None} + if not HAS_HCLOUD: + module.fail_json(msg=missing_required_lib("hcloud-python")) + self._build_client() + + def _build_client(self): + self.client = hcloud.Client( + token=self.module.params["api_token"], + api_endpoint=self.module.params["endpoint"], + application_name="ansible-module", + application_version=__version__, + ) + + def _mark_as_changed(self): + self.result["changed"] = True + + @staticmethod + def base_module_arguments(): + return { + "api_token": { + "type": "str", + "required": True, + "fallback": (env_fallback, ["HCLOUD_TOKEN"]), + "no_log": True, + }, + "endpoint": {"type": "str", "default": "https://api.hetzner.cloud/v1"}, + } + + def _prepare_result(self): + """Prepare the result for every module + + :return: dict + """ + return {} + + def get_result(self): + if getattr(self, self.represent) is not None: + self.result[self.represent] = self._prepare_result() + return self.result diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/__init__.py b/ansible_collections/hetzner/hcloud/plugins/modules/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/__init__.py diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_certificate.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_certificate.py new file mode 100644 index 000000000..0f6dcf0f2 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_certificate.py @@ -0,0 +1,291 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: hcloud_certificate + +short_description: Create and manage certificates on the Hetzner Cloud. + + +description: + - Create, update and manage certificates on the Hetzner Cloud. + +author: + - Lukas Kaemmerling (@lkaemmerling) + +options: + id: + description: + - The ID of the Hetzner Cloud certificate to manage. + - Only required if no certificate I(name) is given + type: int + name: + description: + - The Name of the Hetzner Cloud certificate to manage. + - Only required if no certificate I(id) is given or a certificate does not exist. + type: str + labels: + description: + - User-defined labels (key-value pairs) + type: dict + certificate: + description: + - Certificate and chain in PEM format, in order so that each record directly certifies the one preceding. + - Required if certificate does not exist. + type: str + private_key: + description: + - Certificate key in PEM format. + - Required if certificate does not exist. + type: str + domain_names: + description: + - Certificate key in PEM format. + - Required if certificate does not exist. + type: list + default: [ ] + elements: str + type: + description: + - Choose between uploading a Certificate in PEM format or requesting a managed Let's Encrypt Certificate. + default: uploaded + choices: [ uploaded, managed ] + type: str + state: + description: + - State of the certificate. + default: present + choices: [ absent, present ] + type: str +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +''' + +EXAMPLES = """ +- name: Create a basic certificate + hcloud_certificate: + name: my-certificate + certificate: "ssh-rsa AAAjjk76kgf...Xt" + private_key: "ssh-rsa AAAjjk76kgf...Xt" + state: present + +- name: Create a certificate with labels + hcloud_certificate: + name: my-certificate + certificate: "ssh-rsa AAAjjk76kgf...Xt" + private_key: "ssh-rsa AAAjjk76kgf...Xt" + labels: + key: value + mylabel: 123 + state: present + +- name: Ensure the certificate is absent (remove if needed) + hcloud_certificate: + name: my-certificate + state: absent +""" + +RETURN = """ +hcloud_certificate: + description: The certificate instance + returned: Always + type: complex + contains: + id: + description: Numeric identifier of the certificate + returned: always + type: int + sample: 1937415 + name: + description: Name of the certificate + returned: always + type: str + sample: my website cert + fingerprint: + description: Fingerprint of the certificate + returned: always + type: str + sample: "03:c7:55:9b:2a:d1:04:17:09:f6:d0:7f:18:34:63:d4:3e:5f" + certificate: + description: Certificate and chain in PEM format + returned: always + type: str + sample: "-----BEGIN CERTIFICATE-----..." + domain_names: + description: List of Domains and Subdomains covered by the Certificate + returned: always + type: dict + not_valid_before: + description: Point in time when the Certificate becomes valid (in ISO-8601 format) + returned: always + type: str + not_valid_after: + description: Point in time when the Certificate stops being valid (in ISO-8601 format) + returned: always + type: str + labels: + description: User-defined labels (key-value pairs) + returned: always + type: dict +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud + + +class AnsibleHcloudCertificate(Hcloud): + def __init__(self, module): + Hcloud.__init__(self, module, "hcloud_certificate") + self.hcloud_certificate = None + + def _prepare_result(self): + return { + "id": to_native(self.hcloud_certificate.id), + "name": to_native(self.hcloud_certificate.name), + "type": to_native(self.hcloud_certificate.type), + "fingerprint": to_native(self.hcloud_certificate.fingerprint), + "certificate": to_native(self.hcloud_certificate.certificate), + "not_valid_before": to_native(self.hcloud_certificate.not_valid_before), + "not_valid_after": to_native(self.hcloud_certificate.not_valid_after), + "domain_names": [to_native(domain) for domain in self.hcloud_certificate.domain_names], + "labels": self.hcloud_certificate.labels + } + + def _get_certificate(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_certificate = self.client.certificates.get_by_id( + self.module.params.get("id") + ) + elif self.module.params.get("name") is not None: + self.hcloud_certificate = self.client.certificates.get_by_name( + self.module.params.get("name") + ) + + except Exception as e: + self.module.fail_json(msg=e.message) + + def _create_certificate(self): + self.module.fail_on_missing_params( + required_params=["name"] + ) + + params = { + "name": self.module.params.get("name"), + "labels": self.module.params.get("labels") + } + if self.module.params.get('type') == 'uploaded': + self.module.fail_on_missing_params( + required_params=["certificate", "private_key"] + ) + params["certificate"] = self.module.params.get("certificate") + params["private_key"] = self.module.params.get("private_key") + if not self.module.check_mode: + try: + self.client.certificates.create(**params) + except Exception as e: + self.module.fail_json(msg=e.message) + else: + self.module.fail_on_missing_params( + required_params=["domain_names"] + ) + params["domain_names"] = self.module.params.get("domain_names") + if not self.module.check_mode: + try: + resp = self.client.certificates.create_managed(**params) + resp.action.wait_until_finished(max_retries=1000) + except Exception as e: + self.module.fail_json(msg=e.message) + + self._mark_as_changed() + self._get_certificate() + + def _update_certificate(self): + try: + name = self.module.params.get("name") + if name is not None and self.hcloud_certificate.name != name: + self.module.fail_on_missing_params( + required_params=["id"] + ) + if not self.module.check_mode: + self.hcloud_certificate.update(name=name) + self._mark_as_changed() + + labels = self.module.params.get("labels") + if labels is not None and self.hcloud_certificate.labels != labels: + if not self.module.check_mode: + self.hcloud_certificate.update(labels=labels) + self._mark_as_changed() + except Exception as e: + self.module.fail_json(msg=e.message) + self._get_certificate() + + def present_certificate(self): + self._get_certificate() + if self.hcloud_certificate is None: + self._create_certificate() + else: + self._update_certificate() + + def delete_certificate(self): + self._get_certificate() + if self.hcloud_certificate is not None: + if not self.module.check_mode: + try: + self.client.certificates.delete(self.hcloud_certificate) + except Exception as e: + self.module.fail_json(msg=e.message) + self._mark_as_changed() + self.hcloud_certificate = None + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + type={ + "choices": ["uploaded", "managed"], + "default": "uploaded", + }, + domain_names={"type": "list", "elements": "str", "default": []}, + certificate={"type": "str"}, + private_key={"type": "str", "no_log": True}, + labels={"type": "dict"}, + state={ + "choices": ["absent", "present"], + "default": "present", + }, + **Hcloud.base_module_arguments() + ), + required_one_of=[['id', 'name']], + required_if=[['state', 'present', ['name']]], + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHcloudCertificate.define_module() + + hcloud = AnsibleHcloudCertificate(module) + state = module.params.get("state") + if state == "absent": + hcloud.delete_certificate() + elif state == "present": + hcloud.present_certificate() + + module.exit_json(**hcloud.get_result()) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_certificate_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_certificate_info.py new file mode 100644 index 000000000..855706f1f --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_certificate_info.py @@ -0,0 +1,162 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: hcloud_certificate_info +short_description: Gather infos about your Hetzner Cloud certificates. +description: + - Gather facts about your Hetzner Cloud certificates. +author: + - Lukas Kaemmerling (@LKaemmerling) +options: + id: + description: + - The ID of the certificate you want to get. + type: int + name: + description: + - The name of the certificate you want to get. + type: str + label_selector: + description: + - The label selector for the certificate you want to get. + type: str +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +''' + +EXAMPLES = """ +- name: Gather hcloud certificate infos + hcloud_certificate_info: + register: output +- name: Print the gathered infos + debug: + var: output.hcloud_certificate_info +""" + +RETURN = """ +hcloud_certificate_info: + description: The certificate instances + returned: Always + type: complex + contains: + id: + description: Numeric identifier of the certificate + returned: always + type: int + sample: 1937415 + name: + description: Name of the certificate + returned: always + type: str + sample: my website cert + fingerprint: + description: Fingerprint of the certificate + returned: always + type: str + sample: "03:c7:55:9b:2a:d1:04:17:09:f6:d0:7f:18:34:63:d4:3e:5f" + certificate: + description: Certificate and chain in PEM format + returned: always + type: str + sample: "-----BEGIN CERTIFICATE-----..." + domain_names: + description: List of Domains and Subdomains covered by the Certificate + returned: always + type: dict + not_valid_before: + description: Point in time when the Certificate becomes valid (in ISO-8601 format) + returned: always + type: str + not_valid_after: + description: Point in time when the Certificate stops being valid (in ISO-8601 format) + returned: always + type: str + labels: + description: User-defined labels (key-value pairs) + returned: always + type: dict +""" +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud + + +class AnsibleHcloudCertificateInfo(Hcloud): + def __init__(self, module): + Hcloud.__init__(self, module, "hcloud_certificate_info") + self.hcloud_certificate_info = None + + def _prepare_result(self): + certificates = [] + + for certificate in self.hcloud_certificate_info: + if certificate: + certificates.append({ + "id": to_native(certificate.id), + "name": to_native(certificate.name), + "fingerprint": to_native(certificate.fingerprint), + "certificate": to_native(certificate.certificate), + "not_valid_before": to_native(certificate.not_valid_before), + "not_valid_after": to_native(certificate.not_valid_after), + "domain_names": [to_native(domain) for domain in certificate.domain_names], + "labels": certificate.labels + }) + return certificates + + def get_certificates(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_certificate_info = [self.client.certificates.get_by_id( + self.module.params.get("id") + )] + elif self.module.params.get("name") is not None: + self.hcloud_certificate_info = [self.client.certificates.get_by_name( + self.module.params.get("name") + )] + elif self.module.params.get("label_selector") is not None: + self.hcloud_certificate_info = self.client.certificates.get_all( + label_selector=self.module.params.get("label_selector")) + else: + self.hcloud_certificate_info = self.client.certificates.get_all() + + except Exception as e: + self.module.fail_json(msg=e.message) + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + label_selector={"type": "str"}, + **Hcloud.base_module_arguments() + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHcloudCertificateInfo.define_module() + + hcloud = AnsibleHcloudCertificateInfo(module) + hcloud.get_certificates() + result = hcloud.get_result() + + ansible_info = { + 'hcloud_certificate_info': result['hcloud_certificate_info'] + } + module.exit_json(**ansible_info) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_datacenter_facts.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_datacenter_facts.py new file mode 100644 index 000000000..8cebabf8c --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_datacenter_facts.py @@ -0,0 +1,160 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: hcloud_datacenter_info + +short_description: Gather info about the Hetzner Cloud datacenters. + +description: + - Gather info about your Hetzner Cloud datacenters. + - This module was called C(hcloud_datacenter_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_datacenter_facts). + Note that the M(hetzner.hcloud.hcloud_datacenter_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_datacenter_info)! + +author: + - Lukas Kaemmerling (@LKaemmerling) + +options: + id: + description: + - The ID of the datacenter you want to get. + type: int + name: + description: + - The name of the datacenter you want to get. + type: str +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +''' + +EXAMPLES = """ +- name: Gather hcloud datacenter info + hcloud_datacenter_info: + register: output +- name: Print the gathered info + debug: + var: output +""" + +RETURN = """ +hcloud_datacenter_info: + description: + - The datacenter info as list + - This module was called C(hcloud_datacenter_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_datacenter_facts). + Note that the M(hetzner.hcloud.hcloud_datacenter_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_datacenter_info)! + returned: always + type: complex + contains: + id: + description: Numeric identifier of the datacenter + returned: always + type: int + sample: 1937415 + name: + description: Name of the datacenter + returned: always + type: str + sample: fsn1-dc8 + description: + description: Detail description of the datacenter + returned: always + type: str + sample: Falkenstein DC 8 + location: + description: Name of the location where the datacenter resides in + returned: always + type: str + sample: fsn1 + city: + description: City of the location + returned: always + type: str + sample: fsn1 +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud + + +class AnsibleHcloudDatacenterInfo(Hcloud): + def __init__(self, module): + Hcloud.__init__(self, module, "hcloud_datacenter_info") + self.hcloud_datacenter_info = None + + def _prepare_result(self): + tmp = [] + + for datacenter in self.hcloud_datacenter_info: + if datacenter is not None: + tmp.append({ + "id": to_native(datacenter.id), + "name": to_native(datacenter.name), + "description": to_native(datacenter.description), + "location": to_native(datacenter.location.name) + }) + + return tmp + + def get_datacenters(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_datacenter_info = [self.client.datacenters.get_by_id( + self.module.params.get("id") + )] + elif self.module.params.get("name") is not None: + self.hcloud_datacenter_info = [self.client.datacenters.get_by_name( + self.module.params.get("name") + )] + else: + self.hcloud_datacenter_info = self.client.datacenters.get_all() + + except Exception as e: + self.module.fail_json(msg=e.message) + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + **Hcloud.base_module_arguments() + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHcloudDatacenterInfo.define_module() + + is_old_facts = module._name == 'hcloud_datacenter_facts' + if is_old_facts: + module.deprecate("The 'hcloud_datacenter_facts' module has been renamed to 'hcloud_datacenter_info', " + "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud") + hcloud = AnsibleHcloudDatacenterInfo(module) + + hcloud.get_datacenters() + result = hcloud.get_result() + if is_old_facts: + ansible_info = { + 'hcloud_datacenter_facts': result['hcloud_datacenter_info'] + } + module.exit_json(ansible_facts=ansible_info) + else: + ansible_info = { + 'hcloud_datacenter_info': result['hcloud_datacenter_info'] + } + module.exit_json(**ansible_info) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_datacenter_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_datacenter_info.py new file mode 100644 index 000000000..8cebabf8c --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_datacenter_info.py @@ -0,0 +1,160 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: hcloud_datacenter_info + +short_description: Gather info about the Hetzner Cloud datacenters. + +description: + - Gather info about your Hetzner Cloud datacenters. + - This module was called C(hcloud_datacenter_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_datacenter_facts). + Note that the M(hetzner.hcloud.hcloud_datacenter_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_datacenter_info)! + +author: + - Lukas Kaemmerling (@LKaemmerling) + +options: + id: + description: + - The ID of the datacenter you want to get. + type: int + name: + description: + - The name of the datacenter you want to get. + type: str +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +''' + +EXAMPLES = """ +- name: Gather hcloud datacenter info + hcloud_datacenter_info: + register: output +- name: Print the gathered info + debug: + var: output +""" + +RETURN = """ +hcloud_datacenter_info: + description: + - The datacenter info as list + - This module was called C(hcloud_datacenter_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_datacenter_facts). + Note that the M(hetzner.hcloud.hcloud_datacenter_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_datacenter_info)! + returned: always + type: complex + contains: + id: + description: Numeric identifier of the datacenter + returned: always + type: int + sample: 1937415 + name: + description: Name of the datacenter + returned: always + type: str + sample: fsn1-dc8 + description: + description: Detail description of the datacenter + returned: always + type: str + sample: Falkenstein DC 8 + location: + description: Name of the location where the datacenter resides in + returned: always + type: str + sample: fsn1 + city: + description: City of the location + returned: always + type: str + sample: fsn1 +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud + + +class AnsibleHcloudDatacenterInfo(Hcloud): + def __init__(self, module): + Hcloud.__init__(self, module, "hcloud_datacenter_info") + self.hcloud_datacenter_info = None + + def _prepare_result(self): + tmp = [] + + for datacenter in self.hcloud_datacenter_info: + if datacenter is not None: + tmp.append({ + "id": to_native(datacenter.id), + "name": to_native(datacenter.name), + "description": to_native(datacenter.description), + "location": to_native(datacenter.location.name) + }) + + return tmp + + def get_datacenters(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_datacenter_info = [self.client.datacenters.get_by_id( + self.module.params.get("id") + )] + elif self.module.params.get("name") is not None: + self.hcloud_datacenter_info = [self.client.datacenters.get_by_name( + self.module.params.get("name") + )] + else: + self.hcloud_datacenter_info = self.client.datacenters.get_all() + + except Exception as e: + self.module.fail_json(msg=e.message) + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + **Hcloud.base_module_arguments() + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHcloudDatacenterInfo.define_module() + + is_old_facts = module._name == 'hcloud_datacenter_facts' + if is_old_facts: + module.deprecate("The 'hcloud_datacenter_facts' module has been renamed to 'hcloud_datacenter_info', " + "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud") + hcloud = AnsibleHcloudDatacenterInfo(module) + + hcloud.get_datacenters() + result = hcloud.get_result() + if is_old_facts: + ansible_info = { + 'hcloud_datacenter_facts': result['hcloud_datacenter_info'] + } + module.exit_json(ansible_facts=ansible_info) + else: + ansible_info = { + 'hcloud_datacenter_info': result['hcloud_datacenter_info'] + } + module.exit_json(**ansible_info) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_firewall.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_firewall.py new file mode 100644 index 000000000..34608977e --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_firewall.py @@ -0,0 +1,359 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: hcloud_firewall + +short_description: Create and manage firewalls on the Hetzner Cloud. + + +description: + - Create, update and manage firewalls on the Hetzner Cloud. + +author: + - Lukas Kaemmerling (@lkaemmerling) + +options: + id: + description: + - The ID of the Hetzner Cloud firewall to manage. + - Only required if no firewall I(name) is given + type: int + name: + description: + - The Name of the Hetzner Cloud firewall to manage. + - Only required if no firewall I(id) is given, or a firewall does not exist. + type: str + labels: + description: + - User-defined labels (key-value pairs) + type: dict + rules: + description: + - List of rules the firewall should contain. + type: list + elements: dict + suboptions: + direction: + description: + - The direction of the firewall rule. + type: str + choices: [ in, out ] + port: + description: + - The port of the firewall rule. + type: str + protocol: + description: + - The protocol of the firewall rule. + type: str + choices: [ icmp, tcp, udp, esp, gre ] + source_ips: + description: + - List of CIDRs that are allowed within this rule + type: list + elements: str + default: [ ] + destination_ips: + description: + - List of CIDRs that are allowed within this rule + type: list + elements: str + default: [ ] + description: + description: + - User defined description of this rule. + type: str + state: + description: + - State of the firewall. + default: present + choices: [ absent, present ] + type: str +extends_documentation_fragment: +- hetzner.hcloud.hcloud +''' + +EXAMPLES = """ +- name: Create a basic firewall + hcloud_firewall: + name: my-firewall + state: present + +- name: Create a firewall with rules + hcloud_firewall: + name: my-firewall + rules: + - direction: in + protocol: icmp + source_ips: + - 0.0.0.0/0 + - ::/0 + description: allow icmp in + state: present + +- name: Create a firewall with labels + hcloud_firewall: + name: my-firewall + labels: + key: value + mylabel: 123 + state: present + +- name: Ensure the firewall is absent (remove if needed) + hcloud_firewall: + name: my-firewall + state: absent +""" + +RETURN = """ +hcloud_firewall: + description: The firewall instance + returned: Always + type: complex + contains: + id: + description: Numeric identifier of the firewall + returned: always + type: int + sample: 1937415 + name: + description: Name of the firewall + returned: always + type: str + sample: my firewall + rules: + description: List of Rules within this Firewall + returned: always + type: complex + contains: + direction: + description: Direction of the Firewall Rule + type: str + returned: always + sample: in + protocol: + description: Protocol of the Firewall Rule + type: str + returned: always + sample: icmp + port: + description: Port of the Firewall Rule, None/Null if protocol is icmp + type: str + returned: always + sample: in + source_ips: + description: Source IPs of the Firewall + type: list + elements: str + returned: always + destination_ips: + description: Source IPs of the Firewall + type: list + elements: str + returned: always + description: + description: User defined description of the Firewall Rule + type: str + returned: always + labels: + description: User-defined labels (key-value pairs) + returned: always + type: dict +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud +import time + +try: + from hcloud.firewalls.domain import FirewallRule + from hcloud import APIException +except ImportError: + APIException = None + FirewallRule = None + + +class AnsibleHcloudFirewall(Hcloud): + def __init__(self, module): + Hcloud.__init__(self, module, "hcloud_firewall") + self.hcloud_firewall = None + + def _prepare_result(self): + return { + "id": to_native(self.hcloud_firewall.id), + "name": to_native(self.hcloud_firewall.name), + "rules": [self._prepare_result_rule(rule) for rule in self.hcloud_firewall.rules], + "labels": self.hcloud_firewall.labels + } + + def _prepare_result_rule(self, rule): + return { + "direction": rule.direction, + "protocol": to_native(rule.protocol), + "port": to_native(rule.port) if rule.port is not None else None, + "source_ips": [to_native(cidr) for cidr in rule.source_ips], + "destination_ips": [to_native(cidr) for cidr in rule.destination_ips], + "description": to_native(rule.description) if rule.description is not None else None, + } + + def _get_firewall(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_firewall = self.client.firewalls.get_by_id( + self.module.params.get("id") + ) + elif self.module.params.get("name") is not None: + self.hcloud_firewall = self.client.firewalls.get_by_name( + self.module.params.get("name") + ) + + except Exception as e: + self.module.fail_json(msg=e.message) + + def _create_firewall(self): + self.module.fail_on_missing_params( + required_params=["name"] + ) + params = { + "name": self.module.params.get("name"), + "labels": self.module.params.get("labels") + } + rules = self.module.params.get("rules") + if rules is not None: + params["rules"] = [ + FirewallRule( + direction=rule["direction"], + protocol=rule["protocol"], + source_ips=rule["source_ips"] if rule["source_ips"] is not None else [], + destination_ips=rule["destination_ips"] if rule["destination_ips"] is not None else [], + port=rule["port"], + description=rule["description"], + ) + for rule in rules + ] + if not self.module.check_mode: + try: + self.client.firewalls.create(**params) + except Exception as e: + self.module.fail_json(msg=e.message, **params) + self._mark_as_changed() + self._get_firewall() + + def _update_firewall(self): + name = self.module.params.get("name") + if name is not None and self.hcloud_firewall.name != name: + self.module.fail_on_missing_params( + required_params=["id"] + ) + if not self.module.check_mode: + self.hcloud_firewall.update(name=name) + self._mark_as_changed() + + labels = self.module.params.get("labels") + if labels is not None and self.hcloud_firewall.labels != labels: + if not self.module.check_mode: + self.hcloud_firewall.update(labels=labels) + self._mark_as_changed() + + rules = self.module.params.get("rules") + if rules is not None and rules != [self._prepare_result_rule(rule) for rule in self.hcloud_firewall.rules]: + if not self.module.check_mode: + new_rules = [ + FirewallRule( + direction=rule["direction"], + protocol=rule["protocol"], + source_ips=rule["source_ips"] if rule["source_ips"] is not None else [], + destination_ips=rule["destination_ips"] if rule["destination_ips"] is not None else [], + port=rule["port"], + description=rule["description"], + ) + for rule in rules + ] + self.hcloud_firewall.set_rules(new_rules) + self._mark_as_changed() + self._get_firewall() + + def present_firewall(self): + self._get_firewall() + if self.hcloud_firewall is None: + self._create_firewall() + else: + self._update_firewall() + + def delete_firewall(self): + self._get_firewall() + if self.hcloud_firewall is not None: + if not self.module.check_mode: + retry_count = 0 + while retry_count < 10: + try: + self.client.firewalls.delete(self.hcloud_firewall) + break + except APIException as e: + if "is still in use" in e.message: + retry_count = retry_count + 1 + time.sleep(0.5 * retry_count) + else: + self.module.fail_json(msg=e.message) + except Exception as e: + self.module.fail_json(msg=e.message) + self._mark_as_changed() + self.hcloud_firewall = None + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + rules=dict( + type="list", + elements="dict", + options=dict( + direction={"type": "str", "choices": ["in", "out"]}, + protocol={"type": "str", "choices": ["icmp", "udp", "tcp", "esp", "gre"]}, + port={"type": "str"}, + source_ips={"type": "list", "elements": "str", "default": []}, + destination_ips={"type": "list", "elements": "str", "default": []}, + description={"type": "str"}, + ), + required_together=[["direction", "protocol"]], + ), + labels={"type": "dict"}, + state={ + "choices": ["absent", "present"], + "default": "present", + }, + **Hcloud.base_module_arguments() + ), + required_one_of=[['id', 'name']], + required_if=[['state', 'present', ['name']]], + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHcloudFirewall.define_module() + + hcloud = AnsibleHcloudFirewall(module) + state = module.params.get("state") + if state == "absent": + hcloud.delete_firewall() + elif state == "present": + hcloud.present_firewall() + + module.exit_json(**hcloud.get_result()) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_floating_ip.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_floating_ip.py new file mode 100644 index 000000000..1ee61ea13 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_floating_ip.py @@ -0,0 +1,355 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: hcloud_floating_ip + +short_description: Create and manage cloud Floating IPs on the Hetzner Cloud. + + +description: + - Create, update and manage cloud Floating IPs on the Hetzner Cloud. + +author: + - Lukas Kaemmerling (@lkaemmerling) +version_added: 0.1.0 +options: + id: + description: + - The ID of the Hetzner Cloud Floating IPs to manage. + - Only required if no Floating IP I(name) is given. + type: int + name: + description: + - The Name of the Hetzner Cloud Floating IPs to manage. + - Only required if no Floating IP I(id) is given or a Floating IP does not exist. + type: str + description: + description: + - The Description of the Hetzner Cloud Floating IPs. + type: str + home_location: + description: + - Home Location of the Hetzner Cloud Floating IP. + - Required if no I(server) is given and Floating IP does not exist. + type: str + server: + description: + - Server Name the Floating IP should be assigned to. + - Required if no I(home_location) is given and Floating IP does not exist. + type: str + type: + description: + - Type of the Floating IP. + - Required if Floating IP does not exist + choices: [ ipv4, ipv6 ] + type: str + force: + description: + - Force the assignment or deletion of the Floating IP. + type: bool + delete_protection: + description: + - Protect the Floating IP for deletion. + type: bool + labels: + description: + - User-defined labels (key-value pairs). + type: dict + state: + description: + - State of the Floating IP. + default: present + choices: [ absent, present ] + type: str + +requirements: + - hcloud-python >= 1.6.0 + +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +''' + +EXAMPLES = """ +- name: Create a basic IPv4 Floating IP + hcloud_floating_ip: + name: my-floating-ip + home_location: fsn1 + type: ipv4 + state: present +- name: Create a basic IPv6 Floating IP + hcloud_floating_ip: + name: my-floating-ip + home_location: fsn1 + type: ipv6 + state: present +- name: Assign a Floating IP to a server + hcloud_floating_ip: + name: my-floating-ip + server: 1234 + state: present +- name: Assign a Floating IP to another server + hcloud_floating_ip: + name: my-floating-ip + server: 1234 + force: yes + state: present +- name: Floating IP should be absent + hcloud_floating_ip: + name: my-floating-ip + state: absent +""" + +RETURN = """ +hcloud_floating_ip: + description: The Floating IP instance + returned: Always + type: complex + contains: + id: + description: ID of the Floating IP + type: int + returned: Always + sample: 12345 + name: + description: Name of the Floating IP + type: str + returned: Always + sample: my-floating-ip + description: + description: Description of the Floating IP + type: str + returned: Always + sample: my-floating-ip + ip: + description: IP Address of the Floating IP + type: str + returned: Always + sample: 116.203.104.109 + type: + description: Type of the Floating IP + type: str + returned: Always + sample: ipv4 + home_location: + description: Name of the home location of the Floating IP + type: str + returned: Always + sample: fsn1 + server: + description: Name of the server the Floating IP is assigned to. + type: str + returned: Always + sample: "my-server" + delete_protection: + description: True if Floating IP is protected for deletion + type: bool + returned: always + sample: false + version_added: "0.1.0" + labels: + description: User-defined labels (key-value pairs) + type: dict + returned: Always + sample: + key: value + mylabel: 123 +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud + + +class AnsibleHcloudFloatingIP(Hcloud): + def __init__(self, module): + Hcloud.__init__(self, module, "hcloud_floating_ip") + self.hcloud_floating_ip = None + + def _prepare_result(self): + server = None + + if self.hcloud_floating_ip.server is not None: + server = to_native(self.hcloud_floating_ip.server.name) + return { + "id": to_native(self.hcloud_floating_ip.id), + "name": to_native(self.hcloud_floating_ip.name), + "description": to_native(self.hcloud_floating_ip.description), + "ip": to_native(self.hcloud_floating_ip.ip), + "type": to_native(self.hcloud_floating_ip.type), + "home_location": to_native(self.hcloud_floating_ip.home_location.name), + "labels": self.hcloud_floating_ip.labels, + "server": server, + "delete_protection": self.hcloud_floating_ip.protection["delete"], + } + + def _get_floating_ip(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_floating_ip = self.client.floating_ips.get_by_id( + self.module.params.get("id") + ) + else: + self.hcloud_floating_ip = self.client.floating_ips.get_by_name( + self.module.params.get("name") + ) + except Exception as e: + self.module.fail_json(msg=e.message) + + def _create_floating_ip(self): + self.module.fail_on_missing_params( + required_params=["type"] + ) + try: + params = { + "description": self.module.params.get("description"), + "type": self.module.params.get("type"), + "name": self.module.params.get("name"), + } + if self.module.params.get("home_location") is not None: + params["home_location"] = self.client.locations.get_by_name( + self.module.params.get("home_location") + ) + elif self.module.params.get("server") is not None: + params["server"] = self.client.servers.get_by_name( + self.module.params.get("server") + ) + else: + self.module.fail_json(msg="one of the following is required: home_location, server") + + if self.module.params.get("labels") is not None: + params["labels"] = self.module.params.get("labels") + if not self.module.check_mode: + resp = self.client.floating_ips.create(**params) + self.hcloud_floating_ip = resp.floating_ip + + delete_protection = self.module.params.get("delete_protection") + if delete_protection is not None: + self.hcloud_floating_ip.change_protection(delete=delete_protection).wait_until_finished() + except Exception as e: + self.module.fail_json(msg=e.message) + self._mark_as_changed() + self._get_floating_ip() + + def _update_floating_ip(self): + try: + labels = self.module.params.get("labels") + if labels is not None and labels != self.hcloud_floating_ip.labels: + if not self.module.check_mode: + self.hcloud_floating_ip.update(labels=labels) + self._mark_as_changed() + + description = self.module.params.get("description") + if description is not None and description != self.hcloud_floating_ip.description: + if not self.module.check_mode: + self.hcloud_floating_ip.update(description=description) + self._mark_as_changed() + + server = self.module.params.get("server") + if server is not None and self.hcloud_floating_ip.server is not None: + if self.module.params.get("force") and server != self.hcloud_floating_ip.server.name: + if not self.module.check_mode: + self.hcloud_floating_ip.assign( + self.client.servers.get_by_name(server) + ) + self._mark_as_changed() + elif server != self.hcloud_floating_ip.server.name: + self.module.warn( + "Floating IP is already assigned to another server %s. You need to unassign the Floating IP or use force=yes." + % self.hcloud_floating_ip.server.name + ) + self._mark_as_changed() + elif server is not None and self.hcloud_floating_ip.server is None: + if not self.module.check_mode: + self.hcloud_floating_ip.assign( + self.client.servers.get_by_name(server) + ) + self._mark_as_changed() + elif server is None and self.hcloud_floating_ip.server is not None: + if not self.module.check_mode: + self.hcloud_floating_ip.unassign() + self._mark_as_changed() + + delete_protection = self.module.params.get("delete_protection") + if delete_protection is not None and delete_protection != self.hcloud_floating_ip.protection["delete"]: + if not self.module.check_mode: + self.hcloud_floating_ip.change_protection(delete=delete_protection).wait_until_finished() + self._mark_as_changed() + + self._get_floating_ip() + except Exception as e: + self.module.fail_json(msg=e.message) + + def present_floating_ip(self): + self._get_floating_ip() + if self.hcloud_floating_ip is None: + self._create_floating_ip() + else: + self._update_floating_ip() + + def delete_floating_ip(self): + try: + self._get_floating_ip() + if self.hcloud_floating_ip is not None: + if self.module.params.get("force") or self.hcloud_floating_ip.server is None: + if not self.module.check_mode: + self.client.floating_ips.delete(self.hcloud_floating_ip) + else: + self.module.warn( + "Floating IP is currently assigned to server %s. You need to unassign the Floating IP or use force=yes." + % self.hcloud_floating_ip.server.name + ) + self._mark_as_changed() + self.hcloud_floating_ip = None + except Exception as e: + self.module.fail_json(msg=e.message) + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + description={"type": "str"}, + server={"type": "str"}, + home_location={"type": "str"}, + force={"type": "bool"}, + type={"choices": ["ipv4", "ipv6"]}, + labels={"type": "dict"}, + delete_protection={"type": "bool"}, + state={ + "choices": ["absent", "present"], + "default": "present", + }, + **Hcloud.base_module_arguments() + ), + required_one_of=[['id', 'name']], + mutually_exclusive=[['home_location', 'server']], + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHcloudFloatingIP.define_module() + + hcloud = AnsibleHcloudFloatingIP(module) + state = module.params["state"] + if state == "absent": + hcloud.delete_floating_ip() + elif state == "present": + hcloud.present_floating_ip() + + module.exit_json(**hcloud.get_result()) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_floating_ip_facts.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_floating_ip_facts.py new file mode 100644 index 000000000..2ec359600 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_floating_ip_facts.py @@ -0,0 +1,185 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: hcloud_floating_ip_info + +short_description: Gather infos about the Hetzner Cloud Floating IPs. + +description: + - Gather facts about your Hetzner Cloud Floating IPs. + - This module was called C(hcloud_floating_ip_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_floating_ip_facts). + Note that the M(hetzner.hcloud.hcloud_floating_ip_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_floating_ip_info)! + +author: + - Lukas Kaemmerling (@LKaemmerling) + +options: + id: + description: + - The ID of the Floating IP you want to get. + type: int + label_selector: + description: + - The label selector for the Floating IP you want to get. + type: str +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +''' + +EXAMPLES = """ +- name: Gather hcloud Floating ip infos + hcloud_floating_ip_info: + register: output +- name: Print the gathered infos + debug: + var: output +""" + +RETURN = """ +hcloud_floating_ip_info: + description: The Floating ip infos as list + returned: always + type: complex + contains: + id: + description: Numeric identifier of the Floating IP + returned: always + type: int + sample: 1937415 + name: + description: Name of the Floating IP + returned: Always + type: str + sample: my-floating-ip + version_added: "0.1.0" + description: + description: Description of the Floating IP + returned: always + type: str + sample: Falkenstein DC 8 + ip: + description: IP address of the Floating IP + returned: always + type: str + sample: 131.232.99.1 + type: + description: Type of the Floating IP + returned: always + type: str + sample: ipv4 + server: + description: Name of the server where the Floating IP is assigned to. + returned: always + type: str + sample: my-server + home_location: + description: Location the Floating IP was created in + returned: always + type: str + sample: fsn1 + delete_protection: + description: True if the Floating IP is protected for deletion + returned: always + type: bool + version_added: "0.1.0" + labels: + description: User-defined labels (key-value pairs) + returned: always + type: dict +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud + + +class AnsibleHcloudFloatingIPInfo(Hcloud): + def __init__(self, module): + Hcloud.__init__(self, module, "hcloud_floating_ip_info") + self.hcloud_floating_ip_info = None + + def _prepare_result(self): + tmp = [] + + for floating_ip in self.hcloud_floating_ip_info: + if floating_ip is not None: + server_name = None + if floating_ip.server is not None: + server_name = floating_ip.server.name + tmp.append({ + "id": to_native(floating_ip.id), + "name": to_native(floating_ip.name), + "description": to_native(floating_ip.description), + "ip": to_native(floating_ip.ip), + "type": to_native(floating_ip.type), + "server": to_native(server_name), + "home_location": to_native(floating_ip.home_location.name), + "labels": floating_ip.labels, + "delete_protection": floating_ip.protection["delete"], + }) + + return tmp + + def get_floating_ips(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_floating_ip_info = [self.client.floating_ips.get_by_id( + self.module.params.get("id") + )] + elif self.module.params.get("label_selector") is not None: + self.hcloud_floating_ip_info = self.client.floating_ips.get_all( + label_selector=self.module.params.get("label_selector")) + else: + self.hcloud_floating_ip_info = self.client.floating_ips.get_all() + + except Exception as e: + self.module.fail_json(msg=e.message) + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + label_selector={"type": "str"}, + **Hcloud.base_module_arguments() + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHcloudFloatingIPInfo.define_module() + + is_old_facts = module._name == 'hcloud_floating_ip_facts' + if is_old_facts: + module.deprecate("The 'hcloud_floating_ip_facts' module has been renamed to 'hcloud_floating_ip_info', " + "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud") + + hcloud = AnsibleHcloudFloatingIPInfo(module) + + hcloud.get_floating_ips() + result = hcloud.get_result() + if is_old_facts: + ansible_info = { + 'hcloud_floating_ip_facts': result['hcloud_floating_ip_info'] + } + module.exit_json(ansible_facts=ansible_info) + else: + ansible_info = { + 'hcloud_floating_ip_info': result['hcloud_floating_ip_info'] + } + module.exit_json(**ansible_info) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_floating_ip_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_floating_ip_info.py new file mode 100644 index 000000000..2ec359600 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_floating_ip_info.py @@ -0,0 +1,185 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: hcloud_floating_ip_info + +short_description: Gather infos about the Hetzner Cloud Floating IPs. + +description: + - Gather facts about your Hetzner Cloud Floating IPs. + - This module was called C(hcloud_floating_ip_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_floating_ip_facts). + Note that the M(hetzner.hcloud.hcloud_floating_ip_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_floating_ip_info)! + +author: + - Lukas Kaemmerling (@LKaemmerling) + +options: + id: + description: + - The ID of the Floating IP you want to get. + type: int + label_selector: + description: + - The label selector for the Floating IP you want to get. + type: str +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +''' + +EXAMPLES = """ +- name: Gather hcloud Floating ip infos + hcloud_floating_ip_info: + register: output +- name: Print the gathered infos + debug: + var: output +""" + +RETURN = """ +hcloud_floating_ip_info: + description: The Floating ip infos as list + returned: always + type: complex + contains: + id: + description: Numeric identifier of the Floating IP + returned: always + type: int + sample: 1937415 + name: + description: Name of the Floating IP + returned: Always + type: str + sample: my-floating-ip + version_added: "0.1.0" + description: + description: Description of the Floating IP + returned: always + type: str + sample: Falkenstein DC 8 + ip: + description: IP address of the Floating IP + returned: always + type: str + sample: 131.232.99.1 + type: + description: Type of the Floating IP + returned: always + type: str + sample: ipv4 + server: + description: Name of the server where the Floating IP is assigned to. + returned: always + type: str + sample: my-server + home_location: + description: Location the Floating IP was created in + returned: always + type: str + sample: fsn1 + delete_protection: + description: True if the Floating IP is protected for deletion + returned: always + type: bool + version_added: "0.1.0" + labels: + description: User-defined labels (key-value pairs) + returned: always + type: dict +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud + + +class AnsibleHcloudFloatingIPInfo(Hcloud): + def __init__(self, module): + Hcloud.__init__(self, module, "hcloud_floating_ip_info") + self.hcloud_floating_ip_info = None + + def _prepare_result(self): + tmp = [] + + for floating_ip in self.hcloud_floating_ip_info: + if floating_ip is not None: + server_name = None + if floating_ip.server is not None: + server_name = floating_ip.server.name + tmp.append({ + "id": to_native(floating_ip.id), + "name": to_native(floating_ip.name), + "description": to_native(floating_ip.description), + "ip": to_native(floating_ip.ip), + "type": to_native(floating_ip.type), + "server": to_native(server_name), + "home_location": to_native(floating_ip.home_location.name), + "labels": floating_ip.labels, + "delete_protection": floating_ip.protection["delete"], + }) + + return tmp + + def get_floating_ips(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_floating_ip_info = [self.client.floating_ips.get_by_id( + self.module.params.get("id") + )] + elif self.module.params.get("label_selector") is not None: + self.hcloud_floating_ip_info = self.client.floating_ips.get_all( + label_selector=self.module.params.get("label_selector")) + else: + self.hcloud_floating_ip_info = self.client.floating_ips.get_all() + + except Exception as e: + self.module.fail_json(msg=e.message) + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + label_selector={"type": "str"}, + **Hcloud.base_module_arguments() + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHcloudFloatingIPInfo.define_module() + + is_old_facts = module._name == 'hcloud_floating_ip_facts' + if is_old_facts: + module.deprecate("The 'hcloud_floating_ip_facts' module has been renamed to 'hcloud_floating_ip_info', " + "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud") + + hcloud = AnsibleHcloudFloatingIPInfo(module) + + hcloud.get_floating_ips() + result = hcloud.get_result() + if is_old_facts: + ansible_info = { + 'hcloud_floating_ip_facts': result['hcloud_floating_ip_info'] + } + module.exit_json(ansible_facts=ansible_info) + else: + ansible_info = { + 'hcloud_floating_ip_info': result['hcloud_floating_ip_info'] + } + module.exit_json(**ansible_info) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_image_facts.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_image_facts.py new file mode 100644 index 000000000..8acd8846a --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_image_facts.py @@ -0,0 +1,219 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: hcloud_image_info + +short_description: Gather infos about your Hetzner Cloud images. + + +description: + - Gather infos about your Hetzner Cloud images. + - This module was called C(hcloud_location_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_location_facts). + Note that the M(hetzner.hcloud.hcloud_image_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_image_info)! + +author: + - Lukas Kaemmerling (@LKaemmerling) + +options: + id: + description: + - The ID of the image you want to get. + type: int + name: + description: + - The name of the image you want to get. + type: str + label_selector: + description: + - The label selector for the images you want to get. + type: str + type: + description: + - The type for the images you want to get. + default: system + choices: [ system, snapshot, backup ] + type: str + architecture: + description: + - The architecture for the images you want to get. + type: str + choices: [ x86, arm ] +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +''' + +EXAMPLES = """ +- name: Gather hcloud image infos + hcloud_image_info: + register: output + +- name: Print the gathered infos + debug: + var: output +""" + +RETURN = """ +hcloud_image_info: + description: The image infos as list + returned: always + type: complex + contains: + id: + description: Numeric identifier of the image + returned: always + type: int + sample: 1937415 + type: + description: Type of the image + returned: always + type: str + sample: system + status: + description: Status of the image + returned: always + type: str + sample: available + name: + description: Name of the image + returned: always + type: str + sample: ubuntu-18.04 + description: + description: Detail description of the image + returned: always + type: str + sample: Ubuntu 18.04 Standard 64 bit + os_flavor: + description: OS flavor of the image + returned: always + type: str + sample: ubuntu + os_version: + description: OS version of the image + returned: always + type: str + sample: 18.04 + architecture: + description: Image is compatible with this architecture + returned: always + type: str + sample: x86 + labels: + description: User-defined labels (key-value pairs) + returned: always + type: dict +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud + + +class AnsibleHcloudImageInfo(Hcloud): + def __init__(self, module): + Hcloud.__init__(self, module, "hcloud_image_info") + self.hcloud_image_info = None + + def _prepare_result(self): + tmp = [] + + for image in self.hcloud_image_info: + if image is not None: + tmp.append({ + "id": to_native(image.id), + "status": to_native(image.status), + "type": to_native(image.type), + "name": to_native(image.name), + "description": to_native(image.description), + "os_flavor": to_native(image.os_flavor), + "os_version": to_native(image.os_version), + "architecture": to_native(image.architecture), + "labels": image.labels, + }) + return tmp + + def get_images(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_image_info = [self.client.images.get_by_id( + self.module.params.get("id") + )] + elif self.module.params.get("name") is not None and self.module.params.get("architecture") is not None: + self.hcloud_image_info = [self.client.images.get_by_name_and_architecture( + self.module.params.get("name"), + self.module.params.get("architecture") + )] + elif self.module.params.get("name") is not None: + self.hcloud_image_info = [self.client.images.get_by_name( + self.module.params.get("name") + )] + else: + params = {} + label_selector = self.module.params.get("label_selector") + if label_selector: + params["label_selector"] = label_selector + + image_type = self.module.params.get("type") + if image_type: + params["type"] = image_type + + architecture = self.module.params.get("architecture") + if architecture: + params["architecture"] = architecture + + self.hcloud_image_info = self.client.images.get_all(**params) + + except Exception as e: + self.module.fail_json(msg=e.message) + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + label_selector={"type": "str"}, + type={"choices": ["system", "snapshot", "backup"], "default": "system", "type": "str"}, + architecture={"choices": ["x86", "arm"], "type": "str"}, + **Hcloud.base_module_arguments() + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHcloudImageInfo.define_module() + + is_old_facts = module._name == 'hcloud_image_facts' + if is_old_facts: + module.deprecate("The 'hcloud_image_facts' module has been renamed to 'hcloud_image_info', " + "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud") + + hcloud = AnsibleHcloudImageInfo(module) + hcloud.get_images() + result = hcloud.get_result() + + if is_old_facts: + ansible_info = { + 'hcloud_imagen_facts': result['hcloud_image_info'] + } + module.exit_json(ansible_facts=ansible_info) + else: + ansible_info = { + 'hcloud_image_info': result['hcloud_image_info'] + } + module.exit_json(**ansible_info) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_image_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_image_info.py new file mode 100644 index 000000000..8acd8846a --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_image_info.py @@ -0,0 +1,219 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: hcloud_image_info + +short_description: Gather infos about your Hetzner Cloud images. + + +description: + - Gather infos about your Hetzner Cloud images. + - This module was called C(hcloud_location_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_location_facts). + Note that the M(hetzner.hcloud.hcloud_image_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_image_info)! + +author: + - Lukas Kaemmerling (@LKaemmerling) + +options: + id: + description: + - The ID of the image you want to get. + type: int + name: + description: + - The name of the image you want to get. + type: str + label_selector: + description: + - The label selector for the images you want to get. + type: str + type: + description: + - The type for the images you want to get. + default: system + choices: [ system, snapshot, backup ] + type: str + architecture: + description: + - The architecture for the images you want to get. + type: str + choices: [ x86, arm ] +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +''' + +EXAMPLES = """ +- name: Gather hcloud image infos + hcloud_image_info: + register: output + +- name: Print the gathered infos + debug: + var: output +""" + +RETURN = """ +hcloud_image_info: + description: The image infos as list + returned: always + type: complex + contains: + id: + description: Numeric identifier of the image + returned: always + type: int + sample: 1937415 + type: + description: Type of the image + returned: always + type: str + sample: system + status: + description: Status of the image + returned: always + type: str + sample: available + name: + description: Name of the image + returned: always + type: str + sample: ubuntu-18.04 + description: + description: Detail description of the image + returned: always + type: str + sample: Ubuntu 18.04 Standard 64 bit + os_flavor: + description: OS flavor of the image + returned: always + type: str + sample: ubuntu + os_version: + description: OS version of the image + returned: always + type: str + sample: 18.04 + architecture: + description: Image is compatible with this architecture + returned: always + type: str + sample: x86 + labels: + description: User-defined labels (key-value pairs) + returned: always + type: dict +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud + + +class AnsibleHcloudImageInfo(Hcloud): + def __init__(self, module): + Hcloud.__init__(self, module, "hcloud_image_info") + self.hcloud_image_info = None + + def _prepare_result(self): + tmp = [] + + for image in self.hcloud_image_info: + if image is not None: + tmp.append({ + "id": to_native(image.id), + "status": to_native(image.status), + "type": to_native(image.type), + "name": to_native(image.name), + "description": to_native(image.description), + "os_flavor": to_native(image.os_flavor), + "os_version": to_native(image.os_version), + "architecture": to_native(image.architecture), + "labels": image.labels, + }) + return tmp + + def get_images(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_image_info = [self.client.images.get_by_id( + self.module.params.get("id") + )] + elif self.module.params.get("name") is not None and self.module.params.get("architecture") is not None: + self.hcloud_image_info = [self.client.images.get_by_name_and_architecture( + self.module.params.get("name"), + self.module.params.get("architecture") + )] + elif self.module.params.get("name") is not None: + self.hcloud_image_info = [self.client.images.get_by_name( + self.module.params.get("name") + )] + else: + params = {} + label_selector = self.module.params.get("label_selector") + if label_selector: + params["label_selector"] = label_selector + + image_type = self.module.params.get("type") + if image_type: + params["type"] = image_type + + architecture = self.module.params.get("architecture") + if architecture: + params["architecture"] = architecture + + self.hcloud_image_info = self.client.images.get_all(**params) + + except Exception as e: + self.module.fail_json(msg=e.message) + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + label_selector={"type": "str"}, + type={"choices": ["system", "snapshot", "backup"], "default": "system", "type": "str"}, + architecture={"choices": ["x86", "arm"], "type": "str"}, + **Hcloud.base_module_arguments() + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHcloudImageInfo.define_module() + + is_old_facts = module._name == 'hcloud_image_facts' + if is_old_facts: + module.deprecate("The 'hcloud_image_facts' module has been renamed to 'hcloud_image_info', " + "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud") + + hcloud = AnsibleHcloudImageInfo(module) + hcloud.get_images() + result = hcloud.get_result() + + if is_old_facts: + ansible_info = { + 'hcloud_imagen_facts': result['hcloud_image_info'] + } + module.exit_json(ansible_facts=ansible_info) + else: + ansible_info = { + 'hcloud_image_info': result['hcloud_image_info'] + } + module.exit_json(**ansible_info) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer.py new file mode 100644 index 000000000..9c6c2bbaf --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer.py @@ -0,0 +1,318 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: hcloud_load_balancer + +short_description: Create and manage cloud Load Balancers on the Hetzner Cloud. + + +description: + - Create, update and manage cloud Load Balancers on the Hetzner Cloud. + +author: + - Lukas Kaemmerling (@LKaemmerling) +version_added: 0.1.0 +options: + id: + description: + - The ID of the Hetzner Cloud Load Balancer to manage. + - Only required if no Load Balancer I(name) is given + type: int + name: + description: + - The Name of the Hetzner Cloud Load Balancer to manage. + - Only required if no Load Balancer I(id) is given or a Load Balancer does not exist. + type: str + load_balancer_type: + description: + - The Load Balancer Type of the Hetzner Cloud Load Balancer to manage. + - Required if Load Balancer does not exist. + type: str + location: + description: + - Location of Load Balancer. + - Required if no I(network_zone) is given and Load Balancer does not exist. + type: str + network_zone: + description: + - Network Zone of Load Balancer. + - Required of no I(location) is given and Load Balancer does not exist. + type: str + labels: + description: + - User-defined labels (key-value pairs). + type: dict + disable_public_interface: + description: + - Disables the public interface. + type: bool + default: False + delete_protection: + description: + - Protect the Load Balancer for deletion. + type: bool + state: + description: + - State of the Load Balancer. + default: present + choices: [ absent, present ] + type: str +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +requirements: + - hcloud-python >= 1.8.0 +''' + +EXAMPLES = """ +- name: Create a basic Load Balancer + hcloud_load_balancer: + name: my-Load Balancer + load_balancer_type: lb11 + location: fsn1 + state: present + +- name: Ensure the Load Balancer is absent (remove if needed) + hcloud_load_balancer: + name: my-Load Balancer + state: absent + +""" + +RETURN = """ +hcloud_load_balancer: + description: The Load Balancer instance + returned: Always + type: complex + contains: + id: + description: Numeric identifier of the Load Balancer + returned: always + type: int + sample: 1937415 + name: + description: Name of the Load Balancer + returned: always + type: str + sample: my-Load-Balancer + status: + description: Status of the Load Balancer + returned: always + type: str + sample: running + load_balancer_type: + description: Name of the Load Balancer type of the Load Balancer + returned: always + type: str + sample: cx11 + ipv4_address: + description: Public IPv4 address of the Load Balancer + returned: always + type: str + sample: 116.203.104.109 + ipv6_address: + description: Public IPv6 address of the Load Balancer + returned: always + type: str + sample: 2a01:4f8:1c1c:c140::1 + location: + description: Name of the location of the Load Balancer + returned: always + type: str + sample: fsn1 + labels: + description: User-defined labels (key-value pairs) + returned: always + type: dict + delete_protection: + description: True if Load Balancer is protected for deletion + type: bool + returned: always + sample: false + disable_public_interface: + description: True if Load Balancer public interface is disabled + type: bool + returned: always + sample: false +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud + + +class AnsibleHcloudLoadBalancer(Hcloud): + def __init__(self, module): + Hcloud.__init__(self, module, "hcloud_load_balancer") + self.hcloud_load_balancer = None + + def _prepare_result(self): + private_ipv4_address = None if len(self.hcloud_load_balancer.private_net) == 0 else to_native( + self.hcloud_load_balancer.private_net[0].ip) + return { + "id": to_native(self.hcloud_load_balancer.id), + "name": to_native(self.hcloud_load_balancer.name), + "ipv4_address": to_native(self.hcloud_load_balancer.public_net.ipv4.ip), + "ipv6_address": to_native(self.hcloud_load_balancer.public_net.ipv6.ip), + "private_ipv4_address": private_ipv4_address, + "load_balancer_type": to_native(self.hcloud_load_balancer.load_balancer_type.name), + "location": to_native(self.hcloud_load_balancer.location.name), + "labels": self.hcloud_load_balancer.labels, + "delete_protection": self.hcloud_load_balancer.protection["delete"], + "disable_public_interface": False if self.hcloud_load_balancer.public_net.enabled else True, + } + + def _get_load_balancer(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_load_balancer = self.client.load_balancers.get_by_id( + self.module.params.get("id") + ) + else: + self.hcloud_load_balancer = self.client.load_balancers.get_by_name( + self.module.params.get("name") + ) + except Exception as e: + self.module.fail_json(msg=e.message) + + def _create_load_balancer(self): + + self.module.fail_on_missing_params( + required_params=["name", "load_balancer_type"] + ) + try: + params = { + "name": self.module.params.get("name"), + "load_balancer_type": self.client.load_balancer_types.get_by_name( + self.module.params.get("load_balancer_type") + ), + "labels": self.module.params.get("labels"), + } + + if self.module.params.get("location") is None and self.module.params.get("network_zone") is None: + self.module.fail_json(msg="one of the following is required: location, network_zone") + elif self.module.params.get("location") is not None and self.module.params.get("network_zone") is None: + params["location"] = self.client.locations.get_by_name( + self.module.params.get("location") + ) + elif self.module.params.get("location") is None and self.module.params.get("network_zone") is not None: + params["network_zone"] = self.module.params.get("network_zone") + + if not self.module.check_mode: + resp = self.client.load_balancers.create(**params) + resp.action.wait_until_finished(max_retries=1000) + + delete_protection = self.module.params.get("delete_protection") + if delete_protection is not None: + self._get_load_balancer() + self.hcloud_load_balancer.change_protection(delete=delete_protection).wait_until_finished() + except Exception as e: + self.module.fail_json(msg=e.message) + self._mark_as_changed() + self._get_load_balancer() + + def _update_load_balancer(self): + try: + labels = self.module.params.get("labels") + if labels is not None and labels != self.hcloud_load_balancer.labels: + if not self.module.check_mode: + self.hcloud_load_balancer.update(labels=labels) + self._mark_as_changed() + + delete_protection = self.module.params.get("delete_protection") + if delete_protection is not None and delete_protection != self.hcloud_load_balancer.protection["delete"]: + if not self.module.check_mode: + self.hcloud_load_balancer.change_protection(delete=delete_protection).wait_until_finished() + self._mark_as_changed() + self._get_load_balancer() + + disable_public_interface = self.module.params.get("disable_public_interface") + if disable_public_interface is not None and disable_public_interface != (not self.hcloud_load_balancer.public_net.enabled): + if not self.module.check_mode: + if disable_public_interface is True: + self.hcloud_load_balancer.disable_public_interface().wait_until_finished() + else: + self.hcloud_load_balancer.enable_public_interface().wait_until_finished() + self._mark_as_changed() + + load_balancer_type = self.module.params.get("load_balancer_type") + if load_balancer_type is not None and self.hcloud_load_balancer.load_balancer_type.name != load_balancer_type: + new_load_balancer_type = self.client.load_balancer_types.get_by_name(load_balancer_type) + if not new_load_balancer_type: + self.module.fail_json(msg="unknown load balancer type") + if not self.module.check_mode: + self.hcloud_load_balancer.change_type( + load_balancer_type=new_load_balancer_type, + ).wait_until_finished(max_retries=1000) + + self._mark_as_changed() + self._get_load_balancer() + except Exception as e: + self.module.fail_json(msg=e.message) + + def present_load_balancer(self): + self._get_load_balancer() + if self.hcloud_load_balancer is None: + self._create_load_balancer() + else: + self._update_load_balancer() + + def delete_load_balancer(self): + try: + self._get_load_balancer() + if self.hcloud_load_balancer is not None: + if not self.module.check_mode: + self.client.load_balancers.delete(self.hcloud_load_balancer) + self._mark_as_changed() + self.hcloud_load_balancer = None + except Exception as e: + self.module.fail_json(msg=e.message) + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + load_balancer_type={"type": "str"}, + location={"type": "str"}, + network_zone={"type": "str"}, + labels={"type": "dict"}, + delete_protection={"type": "bool"}, + disable_public_interface={"type": "bool", "default": False}, + state={ + "choices": ["absent", "present"], + "default": "present", + }, + **Hcloud.base_module_arguments() + ), + required_one_of=[['id', 'name']], + mutually_exclusive=[["location", "network_zone"]], + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHcloudLoadBalancer.define_module() + + hcloud = AnsibleHcloudLoadBalancer(module) + state = module.params.get("state") + if state == "absent": + hcloud.delete_load_balancer() + elif state == "present": + hcloud.present_load_balancer() + + module.exit_json(**hcloud.get_result()) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_info.py new file mode 100644 index 000000000..159dad258 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_info.py @@ -0,0 +1,398 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: hcloud_load_balancer_info + +short_description: Gather infos about your Hetzner Cloud Load Balancers. + + +description: + - Gather infos about your Hetzner Cloud Load Balancers.. + +author: + - Lukas Kaemmerling (@LKaemmerling) + +options: + id: + description: + - The ID of the Load Balancers you want to get. + type: int + name: + description: + - The name of the Load Balancers you want to get. + type: str + label_selector: + description: + - The label selector for the Load Balancers you want to get. + type: str +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +''' + +EXAMPLES = """ +- name: Gather hcloud load_balancer infos + hcloud_load_balancer_info: + register: output + +- name: Print the gathered infos + debug: + var: output +""" + +RETURN = """ +hcloud_load_balancer_info: + description: The load_balancer infos as list + returned: always + type: complex + contains: + id: + description: Numeric identifier of the Load Balancer + returned: always + type: int + sample: 1937415 + name: + description: Name of the Load Balancer + returned: always + type: str + sample: my-Load-Balancer + status: + description: Status of the Load Balancer + returned: always + type: str + sample: running + load_balancer_type: + description: Name of the Load Balancer type of the Load Balancer + returned: always + type: str + sample: cx11 + ipv4_address: + description: Public IPv4 address of the Load Balancer + returned: always + type: str + sample: 116.203.104.109 + ipv6_address: + description: Public IPv6 address of the Load Balancer + returned: always + type: str + sample: 2a01:4f8:1c1c:c140::1 + location: + description: Name of the location of the Load Balancer + returned: always + type: str + sample: fsn1 + labels: + description: User-defined labels (key-value pairs) + returned: always + type: dict + delete_protection: + description: True if Load Balancer is protected for deletion + type: bool + returned: always + sample: false + disable_public_interface: + description: True if Load Balancer public interface is disabled + type: bool + returned: always + sample: false + targets: + description: The targets of the Load Balancer + returned: always + type: complex + contains: + type: + description: Type of the Load Balancer Target + type: str + returned: always + sample: server + load_balancer: + description: Name of the Load Balancer + type: str + returned: always + sample: my-LoadBalancer + server: + description: Name of the Server + type: str + returned: if I(type) is server + sample: my-server + label_selector: + description: Label Selector + type: str + returned: if I(type) is label_selector + sample: application=backend + ip: + description: IP of the dedicated server + type: str + returned: if I(type) is ip + sample: 127.0.0.1 + use_private_ip: + description: + - Route the traffic over the private IP of the Load Balancer through a Hetzner Cloud Network. + - Load Balancer needs to be attached to a network. See M(hetzner.hcloud.hcloud.hcloud_load_balancer_network) + type: bool + sample: true + returned: always + services: + description: all services from this Load Balancer + returned: Always + type: complex + contains: + listen_port: + description: The port the service listens on, i.e. the port users can connect to. + returned: always + type: int + sample: 443 + protocol: + description: Protocol of the service + returned: always + type: str + sample: http + destination_port: + description: + - The port traffic is forwarded to, i.e. the port the targets are listening and accepting connections on. + returned: always + type: int + sample: 80 + proxyprotocol: + description: + - Enable the PROXY protocol. + returned: always + type: bool + sample: false + http: + description: Configuration for HTTP and HTTPS services + returned: always + type: complex + contains: + cookie_name: + description: Name of the cookie which will be set when you enable sticky sessions + returned: always + type: str + sample: HCLBSTICKY + cookie_lifetime: + description: Lifetime of the cookie which will be set when you enable sticky sessions, in seconds + returned: always + type: int + sample: 3600 + certificates: + description: List of Names or IDs of certificates + returned: always + type: list + elements: str + sticky_sessions: + description: Enable or disable sticky_sessions + returned: always + type: bool + sample: true + redirect_http: + description: Redirect Traffic from Port 80 to Port 443, only available if protocol is https + returned: always + type: bool + sample: false + health_check: + description: Configuration for health checks + returned: always + type: complex + contains: + protocol: + description: Protocol the health checks will be performed over + returned: always + type: str + sample: http + port: + description: Port the health check will be performed on + returned: always + type: int + sample: 80 + interval: + description: Interval of health checks, in seconds + returned: always + type: int + sample: 15 + timeout: + description: Timeout of health checks, in seconds + returned: always + type: int + sample: 10 + retries: + description: Number of retries until a target is marked as unhealthy + returned: always + type: int + sample: 3 + http: + description: Additional Configuration of health checks with protocol http/https + returned: always + type: complex + contains: + domain: + description: Domain we will set within the HTTP HOST header + returned: always + type: str + sample: example.com + path: + description: Path we will try to access + returned: always + type: str + sample: / + response: + description: Response we expect, if response is not within the health check response the target is unhealthy + returned: always + type: str + status_codes: + description: List of HTTP status codes we expect to get when we perform the health check. + returned: always + type: list + elements: str + sample: ["2??","3??"] + tls: + description: Verify the TLS certificate, only available if health check protocol is https + returned: always + type: bool + sample: false +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud + + +class AnsibleHcloudLoadBalancerInfo(Hcloud): + def __init__(self, module): + Hcloud.__init__(self, module, "hcloud_load_balancer_info") + self.hcloud_load_balancer_info = None + + def _prepare_result(self): + tmp = [] + + for load_balancer in self.hcloud_load_balancer_info: + if load_balancer is not None: + services = [self._prepare_service_result(service) for service in load_balancer.services] + targets = [self._prepare_target_result(target) for target in load_balancer.targets] + + private_ipv4_address = None if len(load_balancer.private_net) == 0 else to_native( + load_balancer.private_net[0].ip) + tmp.append({ + "id": to_native(load_balancer.id), + "name": to_native(load_balancer.name), + "ipv4_address": to_native(load_balancer.public_net.ipv4.ip), + "ipv6_address": to_native(load_balancer.public_net.ipv6.ip), + "private_ipv4_address": private_ipv4_address, + "load_balancer_type": to_native(load_balancer.load_balancer_type.name), + "location": to_native(load_balancer.location.name), + "labels": load_balancer.labels, + "delete_protection": load_balancer.protection["delete"], + "disable_public_interface": False if load_balancer.public_net.enabled else True, + "targets": targets, + "services": services + }) + return tmp + + @staticmethod + def _prepare_service_result(service): + http = None + if service.protocol != "tcp": + http = { + "cookie_name": to_native(service.http.cookie_name), + "cookie_lifetime": service.http.cookie_name, + "redirect_http": service.http.redirect_http, + "sticky_sessions": service.http.sticky_sessions, + "certificates": [to_native(certificate.name) for certificate in + service.http.certificates], + } + health_check = { + "protocol": to_native(service.health_check.protocol), + "port": service.health_check.port, + "interval": service.health_check.interval, + "timeout": service.health_check.timeout, + "retries": service.health_check.retries, + } + if service.health_check.protocol != "tcp": + health_check["http"] = { + "domain": to_native(service.health_check.http.domain), + "path": to_native(service.health_check.http.path), + "response": to_native(service.health_check.http.response), + "certificates": [to_native(status_code) for status_code in + service.health_check.http.status_codes], + "tls": service.health_check.http.tls, + } + return { + "protocol": to_native(service.protocol), + "listen_port": service.listen_port, + "destination_port": service.destination_port, + "proxyprotocol": service.proxyprotocol, + "http": http, + "health_check": health_check, + } + + @staticmethod + def _prepare_target_result(target): + result = { + "type": to_native(target.type), + "use_private_ip": target.use_private_ip + } + if target.type == "server": + result["server"] = to_native(target.server.name) + elif target.type == "label_selector": + result["label_selector"] = to_native(target.label_selector.selector) + elif target.type == "ip": + result["ip"] = to_native(target.ip.ip) + return result + + def get_load_balancers(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_load_balancer_info = [self.client.load_balancers.get_by_id( + self.module.params.get("id") + )] + elif self.module.params.get("name") is not None: + self.hcloud_load_balancer_info = [self.client.load_balancers.get_by_name( + self.module.params.get("name") + )] + else: + params = {} + label_selector = self.module.params.get("label_selector") + if label_selector: + params["label_selector"] = label_selector + + self.hcloud_load_balancer_info = self.client.load_balancers.get_all(**params) + + except Exception as e: + self.module.fail_json(msg=e.message) + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + label_selector={"type": "str"}, + **Hcloud.base_module_arguments() + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHcloudLoadBalancerInfo.define_module() + + hcloud = AnsibleHcloudLoadBalancerInfo(module) + hcloud.get_load_balancers() + result = hcloud.get_result() + + ansible_info = { + 'hcloud_load_balancer_info': result['hcloud_load_balancer_info'] + } + module.exit_json(**ansible_info) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_network.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_network.py new file mode 100644 index 000000000..63a7c5471 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_network.py @@ -0,0 +1,209 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: hcloud_load_balancer_network + +short_description: Manage the relationship between Hetzner Cloud Networks and Load Balancers + + +description: + - Create and delete the relationship Hetzner Cloud Networks and Load Balancers + +author: + - Lukas Kaemmerling (@lkaemmerling) +version_added: 0.1.0 +options: + network: + description: + - The name of the Hetzner Cloud Networks. + type: str + required: true + load_balancer: + description: + - The name of the Hetzner Cloud Load Balancer. + type: str + required: true + ip: + description: + - The IP the Load Balancer should have. + type: str + state: + description: + - State of the load_balancer_network. + default: present + choices: [ absent, present ] + type: str + +requirements: + - hcloud-python >= 1.8.1 + +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +''' + +EXAMPLES = """ +- name: Create a basic Load Balancer network + hcloud_load_balancer_network: + network: my-network + load_balancer: my-LoadBalancer + state: present + +- name: Create a Load Balancer network and specify the ip address + hcloud_load_balancer_network: + network: my-network + load_balancer: my-LoadBalancer + ip: 10.0.0.1 + state: present + +- name: Ensure the Load Balancer network is absent (remove if needed) + hcloud_load_balancer_network: + network: my-network + load_balancer: my-LoadBalancer + state: absent +""" + +RETURN = """ +hcloud_load_balancer_network: + description: The relationship between a Load Balancer and a network + returned: always + type: complex + contains: + network: + description: Name of the Network + type: str + returned: always + sample: my-network + load_balancer: + description: Name of the Load Balancer + type: str + returned: always + sample: my-LoadBalancer + ip: + description: IP of the Load Balancer within the Network ip range + type: str + returned: always + sample: 10.0.0.8 +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud + + +class AnsibleHcloudLoadBalancerNetwork(Hcloud): + def __init__(self, module): + Hcloud.__init__(self, module, "hcloud_load_balancer_network") + self.hcloud_network = None + self.hcloud_load_balancer = None + self.hcloud_load_balancer_network = None + + def _prepare_result(self): + return { + "network": to_native(self.hcloud_network.name), + "load_balancer": to_native(self.hcloud_load_balancer.name), + "ip": to_native(self.hcloud_load_balancer_network.ip), + } + + def _get_load_balancer_and_network(self): + try: + network = self.module.params.get("network") + self.hcloud_network = self.client.networks.get_by_name(network) + if not self.hcloud_network: + self.module.fail_json(msg="Network does not exist: %s" % network) + + load_balancer_name = self.module.params.get("load_balancer") + self.hcloud_load_balancer = self.client.load_balancers.get_by_name( + load_balancer_name + ) + if not self.hcloud_load_balancer: + self.module.fail_json(msg="Load balancer does not exist: %s" % load_balancer_name) + + self.hcloud_load_balancer_network = None + except Exception as e: + self.module.fail_json(msg=e.message) + + def _get_load_balancer_network(self): + for privateNet in self.hcloud_load_balancer.private_net: + if privateNet.network.id == self.hcloud_network.id: + self.hcloud_load_balancer_network = privateNet + + def _create_load_balancer_network(self): + params = { + "network": self.hcloud_network + } + + if self.module.params.get("ip") is not None: + params["ip"] = self.module.params.get("ip") + + if not self.module.check_mode: + try: + self.hcloud_load_balancer.attach_to_network(**params).wait_until_finished() + except Exception as e: + self.module.fail_json(msg=e.message) + + self._mark_as_changed() + self._get_load_balancer_and_network() + self._get_load_balancer_network() + + def present_load_balancer_network(self): + self._get_load_balancer_and_network() + self._get_load_balancer_network() + if self.hcloud_load_balancer_network is None: + self._create_load_balancer_network() + + def delete_load_balancer_network(self): + self._get_load_balancer_and_network() + self._get_load_balancer_network() + if self.hcloud_load_balancer_network is not None and self.hcloud_load_balancer is not None: + if not self.module.check_mode: + try: + self.hcloud_load_balancer.detach_from_network( + self.hcloud_load_balancer_network.network).wait_until_finished() + self._mark_as_changed() + except Exception as e: + self.module.fail_json(msg=e.message) + + self.hcloud_load_balancer_network = None + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + network={"type": "str", "required": True}, + load_balancer={"type": "str", "required": True}, + ip={"type": "str"}, + state={ + "choices": ["absent", "present"], + "default": "present", + }, + **Hcloud.base_module_arguments() + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHcloudLoadBalancerNetwork.define_module() + + hcloud = AnsibleHcloudLoadBalancerNetwork(module) + state = module.params["state"] + if state == "absent": + hcloud.delete_load_balancer_network() + elif state == "present": + hcloud.present_load_balancer_network() + + module.exit_json(**hcloud.get_result()) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_service.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_service.py new file mode 100644 index 000000000..b5edcc6b5 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_service.py @@ -0,0 +1,620 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: hcloud_load_balancer_service + +short_description: Create and manage the services of cloud Load Balancers on the Hetzner Cloud. + + +description: + - Create, update and manage the services of cloud Load Balancers on the Hetzner Cloud. + +author: + - Lukas Kaemmerling (@LKaemmerling) +version_added: 0.1.0 +options: + load_balancer: + description: + - The Name of the Hetzner Cloud Load Balancer the service belongs to + type: str + required: true + listen_port: + description: + - The port the service listens on, i.e. the port users can connect to. + type: int + required: true + destination_port: + description: + - The port traffic is forwarded to, i.e. the port the targets are listening and accepting connections on. + - Required if services does not exist and protocol is tcp. + type: int + protocol: + description: + - Protocol of the service. + - Required if Load Balancer does not exist. + type: str + choices: [ http, https, tcp ] + proxyprotocol: + description: + - Enable the PROXY protocol. + type: bool + default: False + http: + description: + - Configuration for HTTP and HTTPS services + type: dict + suboptions: + cookie_name: + description: + - Name of the cookie which will be set when you enable sticky sessions + type: str + cookie_lifetime: + description: + - Lifetime of the cookie which will be set when you enable sticky sessions, in seconds + type: int + certificates: + description: + - List of Names or IDs of certificates + type: list + elements: str + sticky_sessions: + description: + - Enable or disable sticky_sessions + type: bool + default: False + redirect_http: + description: + - Redirect Traffic from Port 80 to Port 443, only available if protocol is https + type: bool + default: False + health_check: + description: + - Configuration for health checks + type: dict + suboptions: + protocol: + description: + - Protocol the health checks will be performed over + type: str + choices: [ http, https, tcp ] + port: + description: + - Port the health check will be performed on + type: int + interval: + description: + - Interval of health checks, in seconds + type: int + timeout: + description: + - Timeout of health checks, in seconds + type: int + retries: + description: + - Number of retries until a target is marked as unhealthy + type: int + http: + description: + - Additional Configuration of health checks with protocol http/https + type: dict + suboptions: + domain: + description: + - Domain we will set within the HTTP HOST header + type: str + path: + description: + - Path we will try to access + type: str + response: + description: + - Response we expect, if response is not within the health check response the target is unhealthy + type: str + status_codes: + description: + - List of HTTP status codes we expect to get when we perform the health check. + type: list + elements: str + tls: + description: + - Verify the TLS certificate, only available if health check protocol is https + type: bool + default: False + state: + description: + - State of the Load Balancer. + default: present + choices: [ absent, present ] + type: str +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +requirements: + - hcloud-python >= 1.8.1 +''' + +EXAMPLES = """ +- name: Create a basic Load Balancer service with Port 80 + hcloud_load_balancer_service: + load_balancer: my-load-balancer + protocol: http + listen_port: 80 + state: present + +- name: Ensure the Load Balancer is absent (remove if needed) + hcloud_load_balancer_service: + load_balancer: my-Load Balancer + protocol: http + listen_port: 80 + state: absent +""" + +RETURN = """ +hcloud_load_balancer_service: + description: The Load Balancer service instance + returned: Always + type: complex + contains: + load_balancer: + description: The name of the Load Balancer where the service belongs to + returned: always + type: str + sample: my-load-balancer + listen_port: + description: The port the service listens on, i.e. the port users can connect to. + returned: always + type: int + sample: 443 + protocol: + description: Protocol of the service + returned: always + type: str + sample: http + destination_port: + description: + - The port traffic is forwarded to, i.e. the port the targets are listening and accepting connections on. + returned: always + type: int + sample: 80 + proxyprotocol: + description: + - Enable the PROXY protocol. + returned: always + type: bool + sample: false + http: + description: Configuration for HTTP and HTTPS services + returned: always + type: complex + contains: + cookie_name: + description: Name of the cookie which will be set when you enable sticky sessions + returned: always + type: str + sample: HCLBSTICKY + cookie_lifetime: + description: Lifetime of the cookie which will be set when you enable sticky sessions, in seconds + returned: always + type: int + sample: 3600 + certificates: + description: List of Names or IDs of certificates + returned: always + type: list + elements: str + sticky_sessions: + description: Enable or disable sticky_sessions + returned: always + type: bool + sample: true + redirect_http: + description: Redirect Traffic from Port 80 to Port 443, only available if protocol is https + returned: always + type: bool + sample: false + health_check: + description: Configuration for health checks + returned: always + type: complex + contains: + protocol: + description: Protocol the health checks will be performed over + returned: always + type: str + sample: http + port: + description: Port the health check will be performed on + returned: always + type: int + sample: 80 + interval: + description: Interval of health checks, in seconds + returned: always + type: int + sample: 15 + timeout: + description: Timeout of health checks, in seconds + returned: always + type: int + sample: 10 + retries: + description: Number of retries until a target is marked as unhealthy + returned: always + type: int + sample: 3 + http: + description: Additional Configuration of health checks with protocol http/https + returned: always + type: complex + contains: + domain: + description: Domain we will set within the HTTP HOST header + returned: always + type: str + sample: example.com + path: + description: Path we will try to access + returned: always + type: str + sample: / + response: + description: Response we expect, if response is not within the health check response the target is unhealthy + returned: always + type: str + status_codes: + description: List of HTTP status codes we expect to get when we perform the health check. + returned: always + type: list + elements: str + sample: ["2??","3??"] + tls: + description: Verify the TLS certificate, only available if health check protocol is https + returned: always + type: bool + sample: false +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud + +try: + from hcloud.load_balancers.domain import LoadBalancerService, LoadBalancerServiceHttp, \ + LoadBalancerHealthCheck, LoadBalancerHealtCheckHttp + from hcloud import APIException +except ImportError: + APIException = None + + +class AnsibleHcloudLoadBalancerService(Hcloud): + def __init__(self, module): + Hcloud.__init__(self, module, "hcloud_load_balancer_service") + self.hcloud_load_balancer = None + self.hcloud_load_balancer_service = None + + def _prepare_result(self): + http = None + if self.hcloud_load_balancer_service.protocol != "tcp": + http = { + "cookie_name": to_native(self.hcloud_load_balancer_service.http.cookie_name), + "cookie_lifetime": self.hcloud_load_balancer_service.http.cookie_name, + "redirect_http": self.hcloud_load_balancer_service.http.redirect_http, + "sticky_sessions": self.hcloud_load_balancer_service.http.sticky_sessions, + "certificates": [to_native(certificate.name) for certificate in + self.hcloud_load_balancer_service.http.certificates], + } + health_check = { + "protocol": to_native(self.hcloud_load_balancer_service.health_check.protocol), + "port": self.hcloud_load_balancer_service.health_check.port, + "interval": self.hcloud_load_balancer_service.health_check.interval, + "timeout": self.hcloud_load_balancer_service.health_check.timeout, + "retries": self.hcloud_load_balancer_service.health_check.retries, + } + if self.hcloud_load_balancer_service.health_check.protocol != "tcp": + health_check["http"] = { + "domain": to_native(self.hcloud_load_balancer_service.health_check.http.domain), + "path": to_native(self.hcloud_load_balancer_service.health_check.http.path), + "response": to_native(self.hcloud_load_balancer_service.health_check.http.response), + "certificates": [to_native(status_code) for status_code in + self.hcloud_load_balancer_service.health_check.http.status_codes], + "tls": self.hcloud_load_balancer_service.health_check.http.tls, + } + return { + "load_balancer": to_native(self.hcloud_load_balancer.name), + "protocol": to_native(self.hcloud_load_balancer_service.protocol), + "listen_port": self.hcloud_load_balancer_service.listen_port, + "destination_port": self.hcloud_load_balancer_service.destination_port, + "proxyprotocol": self.hcloud_load_balancer_service.proxyprotocol, + "http": http, + "health_check": health_check, + } + + def _get_load_balancer(self): + try: + load_balancer_name = self.module.params.get("load_balancer") + self.hcloud_load_balancer = self.client.load_balancers.get_by_name( + load_balancer_name + ) + if not self.hcloud_load_balancer: + self.module.fail_json(msg="Load balancer does not exist: %s" % load_balancer_name) + + self._get_load_balancer_service() + except Exception as e: + self.module.fail_json(msg=e.message) + + def _create_load_balancer_service(self): + + self.module.fail_on_missing_params( + required_params=["protocol"] + ) + if self.module.params.get("protocol") == "tcp": + self.module.fail_on_missing_params( + required_params=["destination_port"] + ) + + params = { + "protocol": self.module.params.get("protocol"), + "listen_port": self.module.params.get("listen_port"), + "proxyprotocol": self.module.params.get("proxyprotocol") + } + + if self.module.params.get("destination_port"): + params["destination_port"] = self.module.params.get("destination_port") + + if self.module.params.get("http"): + params["http"] = self.__get_service_http(http_arg=self.module.params.get("http")) + + if self.module.params.get("health_check"): + params["health_check"] = self.__get_service_health_checks( + health_check=self.module.params.get("health_check")) + + if not self.module.check_mode: + try: + self.hcloud_load_balancer.add_service(LoadBalancerService(**params)).wait_until_finished( + max_retries=1000) + except Exception as e: + self.module.fail_json(msg=e.message) + self._mark_as_changed() + self._get_load_balancer() + self._get_load_balancer_service() + + def __get_service_http(self, http_arg): + service_http = LoadBalancerServiceHttp(certificates=[]) + if http_arg.get("cookie_name") is not None: + service_http.cookie_name = http_arg.get("cookie_name") + if http_arg.get("cookie_lifetime") is not None: + service_http.cookie_lifetime = http_arg.get("cookie_lifetime") + if http_arg.get("sticky_sessions") is not None: + service_http.sticky_sessions = http_arg.get("sticky_sessions") + if http_arg.get("redirect_http") is not None: + service_http.redirect_http = http_arg.get("redirect_http") + if http_arg.get("certificates") is not None: + certificates = http_arg.get("certificates") + if certificates is not None: + for certificate in certificates: + hcloud_cert = None + try: + try: + hcloud_cert = self.client.certificates.get_by_name( + certificate + ) + except Exception: + hcloud_cert = self.client.certificates.get_by_id( + certificate + ) + except Exception as e: + self.module.fail_json(msg=e.message) + service_http.certificates.append(hcloud_cert) + + return service_http + + def __get_service_health_checks(self, health_check): + service_health_check = LoadBalancerHealthCheck() + if health_check.get("protocol") is not None: + service_health_check.protocol = health_check.get("protocol") + if health_check.get("port") is not None: + service_health_check.port = health_check.get("port") + if health_check.get("interval") is not None: + service_health_check.interval = health_check.get("interval") + if health_check.get("timeout") is not None: + service_health_check.timeout = health_check.get("timeout") + if health_check.get("retries") is not None: + service_health_check.retries = health_check.get("retries") + if health_check.get("http") is not None: + health_check_http = health_check.get("http") + service_health_check.http = LoadBalancerHealtCheckHttp() + if health_check_http.get("domain") is not None: + service_health_check.http.domain = health_check_http.get("domain") + if health_check_http.get("path") is not None: + service_health_check.http.path = health_check_http.get("path") + if health_check_http.get("response") is not None: + service_health_check.http.response = health_check_http.get("response") + if health_check_http.get("status_codes") is not None: + service_health_check.http.status_codes = health_check_http.get("status_codes") + if health_check_http.get("tls") is not None: + service_health_check.http.tls = health_check_http.get("tls") + + return service_health_check + + def _update_load_balancer_service(self): + changed = False + try: + params = { + "listen_port": self.module.params.get("listen_port"), + } + + if self.module.params.get("destination_port") is not None: + if self.hcloud_load_balancer_service.destination_port != self.module.params.get("destination_port"): + params["destination_port"] = self.module.params.get("destination_port") + changed = True + + if self.module.params.get("protocol") is not None: + if self.hcloud_load_balancer_service.protocol != self.module.params.get("protocol"): + params["protocol"] = self.module.params.get("protocol") + changed = True + + if self.module.params.get("proxyprotocol") is not None: + if self.hcloud_load_balancer_service.proxyprotocol != self.module.params.get("proxyprotocol"): + params["proxyprotocol"] = self.module.params.get("proxyprotocol") + changed = True + + if self.module.params.get("http") is not None: + params["http"] = self.__get_service_http(http_arg=self.module.params.get("http")) + changed = True + + if self.module.params.get("health_check") is not None: + params["health_check"] = self.__get_service_health_checks( + health_check=self.module.params.get("health_check")) + changed = True + + if not self.module.check_mode: + self.hcloud_load_balancer.update_service(LoadBalancerService(**params)).wait_until_finished( + max_retries=1000) + except Exception as e: + self.module.fail_json(msg=e.message) + self._get_load_balancer() + + if changed: + self._mark_as_changed() + + def _get_load_balancer_service(self): + for service in self.hcloud_load_balancer.services: + if self.module.params.get("listen_port") == service.listen_port: + self.hcloud_load_balancer_service = service + + def present_load_balancer_service(self): + self._get_load_balancer() + if self.hcloud_load_balancer_service is None: + self._create_load_balancer_service() + else: + self._update_load_balancer_service() + + def delete_load_balancer_service(self): + try: + self._get_load_balancer() + if self.hcloud_load_balancer_service is not None: + if not self.module.check_mode: + try: + self.hcloud_load_balancer.delete_service(self.hcloud_load_balancer_service).wait_until_finished( + max_retries=1000) + except Exception as e: + self.module.fail_json(msg=e.message) + self._mark_as_changed() + self.hcloud_load_balancer_service = None + except APIException as e: + self.module.fail_json(msg=e.message) + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + load_balancer={"type": "str", "required": True}, + listen_port={"type": "int", "required": True}, + destination_port={"type": "int"}, + protocol={ + "type": "str", + "choices": ["http", "https", "tcp"], + }, + proxyprotocol={"type": "bool", "default": False}, + http={ + "type": "dict", + "options": dict( + cookie_name={ + "type": "str" + }, + cookie_lifetime={ + "type": "int" + }, + sticky_sessions={ + "type": "bool", + "default": False + }, + redirect_http={ + "type": "bool", + "default": False + }, + certificates={ + "type": "list", + "elements": "str" + }, + + ) + }, + health_check={ + "type": "dict", + "options": dict( + protocol={ + "type": "str", + "choices": ["http", "https", "tcp"], + }, + port={ + "type": "int" + }, + interval={ + "type": "int" + }, + timeout={ + "type": "int" + }, + retries={ + "type": "int" + }, + http={ + "type": "dict", + "options": dict( + domain={ + "type": "str" + }, + path={ + "type": "str" + }, + response={ + "type": "str" + }, + status_codes={ + "type": "list", + "elements": "str" + }, + tls={ + "type": "bool", + "default": False + }, + ) + } + ) + + }, + state={ + "choices": ["absent", "present"], + "default": "present", + }, + **Hcloud.base_module_arguments() + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHcloudLoadBalancerService.define_module() + + hcloud = AnsibleHcloudLoadBalancerService(module) + state = module.params.get("state") + if state == "absent": + hcloud.delete_load_balancer_service() + elif state == "present": + hcloud.present_load_balancer_service() + + module.exit_json(**hcloud.get_result()) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_target.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_target.py new file mode 100644 index 000000000..760884466 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_target.py @@ -0,0 +1,321 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: hcloud_load_balancer_target + +short_description: Manage Hetzner Cloud Load Balancer targets + + +description: + - Create and delete Hetzner Cloud Load Balancer targets + +author: + - Lukas Kaemmerling (@lkaemmerling) +version_added: 0.1.0 +options: + type: + description: + - The type of the target. + type: str + choices: [ server, label_selector, ip ] + required: true + load_balancer: + description: + - The name of the Hetzner Cloud Load Balancer. + type: str + required: true + server: + description: + - The name of the Hetzner Cloud Server. + - Required if I(type) is server + type: str + label_selector: + description: + - A Label Selector that will be used to determine the targets dynamically + - Required if I(type) is label_selector + type: str + ip: + description: + - An IP from a Hetzner Dedicated Server, needs to belongs to the same user as the project. + - Required if I(type) is ip + type: str + use_private_ip: + description: + - Route the traffic over the private IP of the Load Balancer through a Hetzner Cloud Network. + - Load Balancer needs to be attached to a network. See M(hetzner.hcloud.hcloud.hcloud_load_balancer_network) + type: bool + default: False + state: + description: + - State of the load_balancer_network. + default: present + choices: [ absent, present ] + type: str + +requirements: + - hcloud-python >= 1.8.1 + +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +''' + +EXAMPLES = """ +- name: Create a server Load Balancer target + hetzner.hcloud.hcloud_load_balancer_target: + type: server + load_balancer: my-LoadBalancer + server: my-server + state: present + +- name: Create a label_selector Load Balancer target + hetzner.hcloud.hcloud_load_balancer_target: + type: label_selector + load_balancer: my-LoadBalancer + label_selector: application=backend + state: present + +- name: Create an IP Load Balancer target + hetzner.hcloud.hcloud_load_balancer_target: + type: ip + load_balancer: my-LoadBalancer + ip: 127.0.0.1 + state: present + +- name: Ensure the Load Balancer target is absent (remove if needed) + hetzner.hcloud.hcloud_load_balancer_target: + type: server + load_balancer: my-LoadBalancer + server: my-server + state: absent +""" + +RETURN = """ +hcloud_load_balancer_target: + description: The relationship between a Load Balancer and a network + returned: always + type: complex + contains: + type: + description: Type of the Load Balancer Target + type: str + returned: always + sample: server + load_balancer: + description: Name of the Load Balancer + type: str + returned: always + sample: my-LoadBalancer + server: + description: Name of the Server + type: str + returned: if I(type) is server + sample: my-server + label_selector: + description: Label Selector + type: str + returned: if I(type) is label_selector + sample: application=backend + ip: + description: IP of the dedicated server + type: str + returned: if I(type) is ip + sample: 127.0.0.1 + use_private_ip: + description: + - Route the traffic over the private IP of the Load Balancer through a Hetzner Cloud Network. + - Load Balancer needs to be attached to a network. See M(hetzner.hcloud.hcloud.hcloud_load_balancer_network) + type: bool + sample: true + returned: always +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud + +try: + from hcloud.load_balancers.domain import LoadBalancerTarget, LoadBalancerTargetLabelSelector, LoadBalancerTargetIP +except ImportError: + LoadBalancerTarget = None + LoadBalancerTargetLabelSelector = None + LoadBalancerTargetIP = None + + +class AnsibleHcloudLoadBalancerTarget(Hcloud): + def __init__(self, module): + Hcloud.__init__(self, module, "hcloud_load_balancer_target") + self.hcloud_load_balancer = None + self.hcloud_load_balancer_target = None + self.hcloud_server = None + + def _prepare_result(self): + result = { + "type": to_native(self.hcloud_load_balancer_target.type), + "load_balancer": to_native(self.hcloud_load_balancer.name), + "use_private_ip": self.hcloud_load_balancer_target.use_private_ip + } + + if self.hcloud_load_balancer_target.type == "server": + result["server"] = to_native(self.hcloud_load_balancer_target.server.name) + elif self.hcloud_load_balancer_target.type == "label_selector": + result["label_selector"] = to_native(self.hcloud_load_balancer_target.label_selector.selector) + elif self.hcloud_load_balancer_target.type == "ip": + result["ip"] = to_native(self.hcloud_load_balancer_target.ip.ip) + return result + + def _get_load_balancer_and_target(self): + try: + load_balancer_name = self.module.params.get("load_balancer") + self.hcloud_load_balancer = self.client.load_balancers.get_by_name( + load_balancer_name + ) + if not self.hcloud_load_balancer: + self.module.fail_json(msg="Load balancer does not exist: %s" % load_balancer_name) + + if self.module.params.get("type") == "server": + server_name = self.module.params.get("server") + self.hcloud_server = self.client.servers.get_by_name(server_name) + if not self.hcloud_server: + self.module.fail_json(msg="Server not found: %s" % server_name) + + self.hcloud_load_balancer_target = None + except Exception as e: + self.module.fail_json(msg=e.message) + + def _get_load_balancer_target(self): + for target in self.hcloud_load_balancer.targets: + if self.module.params.get("type") == "server" and target.type == "server": + if target.server.id == self.hcloud_server.id: + self.hcloud_load_balancer_target = target + elif self.module.params.get("type") == "label_selector" and target.type == "label_selector": + if target.label_selector.selector == self.module.params.get("label_selector"): + self.hcloud_load_balancer_target = target + elif self.module.params.get("type") == "ip" and target.type == "ip": + if target.ip.ip == self.module.params.get("ip"): + self.hcloud_load_balancer_target = target + + def _create_load_balancer_target(self): + params = { + "target": None + } + + if self.module.params.get("type") == "server": + self.module.fail_on_missing_params( + required_params=["server"] + ) + params["target"] = LoadBalancerTarget(type=self.module.params.get("type"), server=self.hcloud_server, + use_private_ip=self.module.params.get("use_private_ip")) + elif self.module.params.get("type") == "label_selector": + self.module.fail_on_missing_params( + required_params=["label_selector"] + ) + params["target"] = LoadBalancerTarget(type=self.module.params.get("type"), + label_selector=LoadBalancerTargetLabelSelector( + selector=self.module.params.get("label_selector")), + use_private_ip=self.module.params.get("use_private_ip")) + elif self.module.params.get("type") == "ip": + self.module.fail_on_missing_params( + required_params=["ip"] + ) + params["target"] = LoadBalancerTarget(type=self.module.params.get("type"), + ip=LoadBalancerTargetIP(ip=self.module.params.get("ip")), + use_private_ip=False) + + if not self.module.check_mode: + try: + self.hcloud_load_balancer.add_target(**params).wait_until_finished() + except Exception as e: + if e.code == "locked" or e.code == "conflict": + self._create_load_balancer_target() + else: + self.module.fail_json(msg=e.message) + + self._mark_as_changed() + self._get_load_balancer_and_target() + self._get_load_balancer_target() + + def present_load_balancer_target(self): + self._get_load_balancer_and_target() + self._get_load_balancer_target() + if self.hcloud_load_balancer_target is None: + self._create_load_balancer_target() + + def delete_load_balancer_target(self): + self._get_load_balancer_and_target() + self._get_load_balancer_target() + if self.hcloud_load_balancer_target is not None and self.hcloud_load_balancer is not None: + if not self.module.check_mode: + target = None + if self.module.params.get("type") == "server": + self.module.fail_on_missing_params( + required_params=["server"] + ) + target = LoadBalancerTarget(type=self.module.params.get("type"), + server=self.hcloud_server) + elif self.module.params.get("type") == "label_selector": + self.module.fail_on_missing_params( + required_params=["label_selector"] + ) + target = LoadBalancerTarget(type=self.module.params.get("type"), + label_selector=LoadBalancerTargetLabelSelector( + selector=self.module.params.get("label_selector")), + use_private_ip=self.module.params.get("use_private_ip")) + elif self.module.params.get("type") == "ip": + self.module.fail_on_missing_params( + required_params=["ip"] + ) + target = LoadBalancerTarget(type=self.module.params.get("type"), + ip=LoadBalancerTargetIP(ip=self.module.params.get("ip")), + use_private_ip=False) + try: + self.hcloud_load_balancer.remove_target(target).wait_until_finished() + except Exception as e: + self.module.fail_json(msg=e.message) + self._mark_as_changed() + self.hcloud_load_balancer_target = None + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + type={"type": "str", "required": True, "choices": ["server", "label_selector", "ip"]}, + load_balancer={"type": "str", "required": True}, + server={"type": "str"}, + label_selector={"type": "str"}, + ip={"type": "str"}, + use_private_ip={"type": "bool", "default": False}, + state={ + "choices": ["absent", "present"], + "default": "present", + }, + **Hcloud.base_module_arguments() + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHcloudLoadBalancerTarget.define_module() + + hcloud = AnsibleHcloudLoadBalancerTarget(module) + state = module.params["state"] + if state == "absent": + hcloud.delete_load_balancer_target() + elif state == "present": + hcloud.present_load_balancer_target() + + module.exit_json(**hcloud.get_result()) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_type_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_type_info.py new file mode 100644 index 000000000..a481ea9c9 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_type_info.py @@ -0,0 +1,158 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: hcloud_load_balancer_type_info + +short_description: Gather infos about the Hetzner Cloud Load Balancer types. + + +description: + - Gather infos about your Hetzner Cloud Load Balancer types. + +author: + - Lukas Kaemmerling (@LKaemmerling) +version_added: 0.1.0 +options: + id: + description: + - The ID of the Load Balancer type you want to get. + type: int + name: + description: + - The name of the Load Balancer type you want to get. + type: str +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +''' + +EXAMPLES = """ +- name: Gather hcloud Load Balancer type infos + hcloud_load_balancer_type_info: + register: output + +- name: Print the gathered infos + debug: + var: output.hcloud_load_balancer_type_info +""" + +RETURN = """ +hcloud_load_balancer_type_info: + description: The Load Balancer type infos as list + returned: always + type: complex + contains: + id: + description: Numeric identifier of the Load Balancer type + returned: always + type: int + sample: 1937415 + name: + description: Name of the Load Balancer type + returned: always + type: str + sample: lb11 + description: + description: Description of the Load Balancer type + returned: always + type: str + sample: LB11 + max_connections: + description: Number of maximum simultaneous open connections + returned: always + type: int + sample: 1 + max_services: + description: Number of services a Load Balancer of this type can have + returned: always + type: int + sample: 1 + max_targets: + description: Number of targets a single Load Balancer can have + returned: always + type: int + sample: 25 + max_assigned_certificates: + description: Number of SSL Certificates that can be assigned to a single Load Balancer + returned: always + type: int + sample: 5 +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud + + +class AnsibleHcloudLoadBalancerTypeInfo(Hcloud): + def __init__(self, module): + Hcloud.__init__(self, module, "hcloud_load_balancer_type_info") + self.hcloud_load_balancer_type_info = None + + def _prepare_result(self): + tmp = [] + + for load_balancer_type in self.hcloud_load_balancer_type_info: + if load_balancer_type is not None: + tmp.append({ + "id": to_native(load_balancer_type.id), + "name": to_native(load_balancer_type.name), + "description": to_native(load_balancer_type.description), + "max_connections": load_balancer_type.max_connections, + "max_services": load_balancer_type.max_services, + "max_targets": load_balancer_type.max_targets, + "max_assigned_certificates": load_balancer_type.max_assigned_certificates + }) + return tmp + + def get_load_balancer_types(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_load_balancer_type_info = [self.client.load_balancer_types.get_by_id( + self.module.params.get("id") + )] + elif self.module.params.get("name") is not None: + self.hcloud_load_balancer_type_info = [self.client.load_balancer_types.get_by_name( + self.module.params.get("name") + )] + else: + self.hcloud_load_balancer_type_info = self.client.load_balancer_types.get_all() + + except Exception as e: + self.module.fail_json(msg=e.message) + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + **Hcloud.base_module_arguments() + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHcloudLoadBalancerTypeInfo.define_module() + + hcloud = AnsibleHcloudLoadBalancerTypeInfo(module) + hcloud.get_load_balancer_types() + result = hcloud.get_result() + ansible_info = { + 'hcloud_load_balancer_type_info': result['hcloud_load_balancer_type_info'] + } + module.exit_json(**ansible_info) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_location_facts.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_location_facts.py new file mode 100644 index 000000000..623c6ab68 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_location_facts.py @@ -0,0 +1,159 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: hcloud_location_info + +short_description: Gather infos about your Hetzner Cloud locations. + + +description: + - Gather infos about your Hetzner Cloud locations. + - This module was called C(hcloud_location_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_location_facts). + Note that the M(hetzner.hcloud.hcloud_location_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_location_info)! + +author: + - Lukas Kaemmerling (@LKaemmerling) + +options: + id: + description: + - The ID of the location you want to get. + type: int + name: + description: + - The name of the location you want to get. + type: str +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +''' + +EXAMPLES = """ +- name: Gather hcloud location infos + hcloud_location_info: + register: output + +- name: Print the gathered infos + debug: + var: output +""" + +RETURN = """ +hcloud_location_info: + description: The location infos as list + returned: always + type: complex + contains: + id: + description: Numeric identifier of the location + returned: always + type: int + sample: 1937415 + name: + description: Name of the location + returned: always + type: str + sample: fsn1 + description: + description: Detail description of the location + returned: always + type: str + sample: Falkenstein DC Park 1 + country: + description: Country code of the location + returned: always + type: str + sample: DE + city: + description: City of the location + returned: always + type: str + sample: Falkenstein +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud + + +class AnsibleHcloudLocationInfo(Hcloud): + def __init__(self, module): + Hcloud.__init__(self, module, "hcloud_location_info") + self.hcloud_location_info = None + + def _prepare_result(self): + tmp = [] + + for location in self.hcloud_location_info: + if location is not None: + tmp.append({ + "id": to_native(location.id), + "name": to_native(location.name), + "description": to_native(location.description), + "city": to_native(location.city), + "country": to_native(location.country) + }) + return tmp + + def get_locations(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_location_info = [self.client.locations.get_by_id( + self.module.params.get("id") + )] + elif self.module.params.get("name") is not None: + self.hcloud_location_info = [self.client.locations.get_by_name( + self.module.params.get("name") + )] + else: + self.hcloud_location_info = self.client.locations.get_all() + + except Exception as e: + self.module.fail_json(msg=e.message) + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + **Hcloud.base_module_arguments() + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHcloudLocationInfo.define_module() + + is_old_facts = module._name == 'hcloud_location_facts' + if is_old_facts: + module.deprecate("The 'hcloud_location_info' module has been renamed to 'hcloud_location_info', " + "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud") + + hcloud = AnsibleHcloudLocationInfo(module) + hcloud.get_locations() + result = hcloud.get_result() + if is_old_facts: + ansible_info = { + 'hcloud_location_facts': result['hcloud_location_info'] + } + module.exit_json(ansible_facts=ansible_info) + else: + ansible_info = { + 'hcloud_location_info': result['hcloud_location_info'] + } + module.exit_json(**ansible_info) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_location_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_location_info.py new file mode 100644 index 000000000..623c6ab68 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_location_info.py @@ -0,0 +1,159 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: hcloud_location_info + +short_description: Gather infos about your Hetzner Cloud locations. + + +description: + - Gather infos about your Hetzner Cloud locations. + - This module was called C(hcloud_location_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_location_facts). + Note that the M(hetzner.hcloud.hcloud_location_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_location_info)! + +author: + - Lukas Kaemmerling (@LKaemmerling) + +options: + id: + description: + - The ID of the location you want to get. + type: int + name: + description: + - The name of the location you want to get. + type: str +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +''' + +EXAMPLES = """ +- name: Gather hcloud location infos + hcloud_location_info: + register: output + +- name: Print the gathered infos + debug: + var: output +""" + +RETURN = """ +hcloud_location_info: + description: The location infos as list + returned: always + type: complex + contains: + id: + description: Numeric identifier of the location + returned: always + type: int + sample: 1937415 + name: + description: Name of the location + returned: always + type: str + sample: fsn1 + description: + description: Detail description of the location + returned: always + type: str + sample: Falkenstein DC Park 1 + country: + description: Country code of the location + returned: always + type: str + sample: DE + city: + description: City of the location + returned: always + type: str + sample: Falkenstein +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud + + +class AnsibleHcloudLocationInfo(Hcloud): + def __init__(self, module): + Hcloud.__init__(self, module, "hcloud_location_info") + self.hcloud_location_info = None + + def _prepare_result(self): + tmp = [] + + for location in self.hcloud_location_info: + if location is not None: + tmp.append({ + "id": to_native(location.id), + "name": to_native(location.name), + "description": to_native(location.description), + "city": to_native(location.city), + "country": to_native(location.country) + }) + return tmp + + def get_locations(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_location_info = [self.client.locations.get_by_id( + self.module.params.get("id") + )] + elif self.module.params.get("name") is not None: + self.hcloud_location_info = [self.client.locations.get_by_name( + self.module.params.get("name") + )] + else: + self.hcloud_location_info = self.client.locations.get_all() + + except Exception as e: + self.module.fail_json(msg=e.message) + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + **Hcloud.base_module_arguments() + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHcloudLocationInfo.define_module() + + is_old_facts = module._name == 'hcloud_location_facts' + if is_old_facts: + module.deprecate("The 'hcloud_location_info' module has been renamed to 'hcloud_location_info', " + "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud") + + hcloud = AnsibleHcloudLocationInfo(module) + hcloud.get_locations() + result = hcloud.get_result() + if is_old_facts: + ansible_info = { + 'hcloud_location_facts': result['hcloud_location_info'] + } + module.exit_json(ansible_facts=ansible_info) + else: + ansible_info = { + 'hcloud_location_info': result['hcloud_location_info'] + } + module.exit_json(**ansible_info) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_network.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_network.py new file mode 100644 index 000000000..9c005d29f --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_network.py @@ -0,0 +1,243 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: hcloud_network + +short_description: Create and manage cloud Networks on the Hetzner Cloud. + + +description: + - Create, update and manage cloud Networks on the Hetzner Cloud. + - You need at least hcloud-python 1.3.0. + +author: + - Lukas Kaemmerling (@lkaemmerling) + +options: + id: + description: + - The ID of the Hetzner Cloud Networks to manage. + - Only required if no Network I(name) is given. + type: int + name: + description: + - The Name of the Hetzner Cloud Network to manage. + - Only required if no Network I(id) is given or a Network does not exist. + type: str + ip_range: + description: + - IP range of the Network. + - Required if Network does not exist. + type: str + labels: + description: + - User-defined labels (key-value pairs). + type: dict + delete_protection: + description: + - Protect the Network for deletion. + type: bool + state: + description: + - State of the Network. + default: present + choices: [ absent, present ] + type: str + +requirements: + - hcloud-python >= 1.3.0 + +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +''' + +EXAMPLES = """ +- name: Create a basic network + hcloud_network: + name: my-network + ip_range: 10.0.0.0/8 + state: present + +- name: Ensure the Network is absent (remove if needed) + hcloud_network: + name: my-network + state: absent +""" + +RETURN = """ +hcloud_network: + description: The Network + returned: always + type: complex + contains: + id: + description: ID of the Network + type: int + returned: always + sample: 12345 + name: + description: Name of the Network + type: str + returned: always + sample: my-volume + ip_range: + description: IP range of the Network + type: str + returned: always + sample: 10.0.0.0/8 + delete_protection: + description: True if Network is protected for deletion + type: bool + returned: always + sample: false + version_added: "0.1.0" + labels: + description: User-defined labels (key-value pairs) + type: dict + returned: always + sample: + key: value + mylabel: 123 +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud + + +class AnsibleHcloudNetwork(Hcloud): + def __init__(self, module): + Hcloud.__init__(self, module, "hcloud_network") + self.hcloud_network = None + + def _prepare_result(self): + return { + "id": to_native(self.hcloud_network.id), + "name": to_native(self.hcloud_network.name), + "ip_range": to_native(self.hcloud_network.ip_range), + "delete_protection": self.hcloud_network.protection["delete"], + "labels": self.hcloud_network.labels, + } + + def _get_network(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_network = self.client.networks.get_by_id( + self.module.params.get("id") + ) + else: + self.hcloud_network = self.client.networks.get_by_name( + self.module.params.get("name") + ) + except Exception as e: + self.module.fail_json(msg=e.message) + + def _create_network(self): + + self.module.fail_on_missing_params( + required_params=["name", "ip_range"] + ) + params = { + "name": self.module.params.get("name"), + "ip_range": self.module.params.get("ip_range"), + "labels": self.module.params.get("labels"), + } + try: + if not self.module.check_mode: + self.client.networks.create(**params) + + delete_protection = self.module.params.get("delete_protection") + if delete_protection is not None: + self._get_network() + self.hcloud_network.change_protection(delete=delete_protection).wait_until_finished() + except Exception as e: + self.module.fail_json(msg=e.message) + self._mark_as_changed() + self._get_network() + + def _update_network(self): + try: + labels = self.module.params.get("labels") + if labels is not None and labels != self.hcloud_network.labels: + if not self.module.check_mode: + self.hcloud_network.update(labels=labels) + self._mark_as_changed() + + ip_range = self.module.params.get("ip_range") + if ip_range is not None and ip_range != self.hcloud_network.ip_range: + if not self.module.check_mode: + self.hcloud_network.change_ip_range(ip_range=ip_range).wait_until_finished() + self._mark_as_changed() + + delete_protection = self.module.params.get("delete_protection") + if delete_protection is not None and delete_protection != self.hcloud_network.protection["delete"]: + if not self.module.check_mode: + self.hcloud_network.change_protection(delete=delete_protection).wait_until_finished() + self._mark_as_changed() + except Exception as e: + self.module.fail_json(msg=e.message) + self._get_network() + + def present_network(self): + self._get_network() + if self.hcloud_network is None: + self._create_network() + else: + self._update_network() + + def delete_network(self): + try: + self._get_network() + if self.hcloud_network is not None: + if not self.module.check_mode: + self.client.networks.delete(self.hcloud_network) + self._mark_as_changed() + except Exception as e: + self.module.fail_json(msg=e.message) + self.hcloud_network = None + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + ip_range={"type": "str"}, + labels={"type": "dict"}, + delete_protection={"type": "bool"}, + state={ + "choices": ["absent", "present"], + "default": "present", + }, + **Hcloud.base_module_arguments() + ), + required_one_of=[['id', 'name']], + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHcloudNetwork.define_module() + + hcloud = AnsibleHcloudNetwork(module) + state = module.params["state"] + if state == "absent": + hcloud.delete_network() + elif state == "present": + hcloud.present_network() + + module.exit_json(**hcloud.get_result()) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_network_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_network_info.py new file mode 100644 index 000000000..382e447aa --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_network_info.py @@ -0,0 +1,293 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: hcloud_network_info + +short_description: Gather info about your Hetzner Cloud networks. + + +description: + - Gather info about your Hetzner Cloud networks. + +author: + - Christopher Schmitt (@cschmitt-hcloud) + +options: + id: + description: + - The ID of the network you want to get. + type: int + name: + description: + - The name of the network you want to get. + type: str + label_selector: + description: + - The label selector for the network you want to get. + type: str +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +''' + +EXAMPLES = """ +- name: Gather hcloud network info + local_action: + module: hcloud_network_info + +- name: Print the gathered info + debug: + var: hcloud_network_info +""" + +RETURN = """ +hcloud_network_info: + description: The network info as list + returned: always + type: complex + contains: + id: + description: Numeric identifier of the network + returned: always + type: int + sample: 1937415 + name: + description: Name of the network + returned: always + type: str + sample: awesome-network + ip_range: + description: IP range of the network + returned: always + type: str + sample: 10.0.0.0/16 + subnetworks: + description: Subnetworks belonging to the network + returned: always + type: complex + contains: + type: + description: Type of the subnetwork. + returned: always + type: str + sample: cloud + network_zone: + description: Network of the subnetwork. + returned: always + type: str + sample: eu-central + ip_range: + description: IP range of the subnetwork + returned: always + type: str + sample: 10.0.0.0/24 + gateway: + description: Gateway of this subnetwork + returned: always + type: str + sample: 10.0.0.1 + routes: + description: Routes belonging to the network + returned: always + type: complex + contains: + ip_range: + description: Destination network or host of this route. + returned: always + type: str + sample: 10.0.0.0/16 + gateway: + description: Gateway of this route + returned: always + type: str + sample: 10.0.0.1 + servers: + description: Servers attached to the network + returned: always + type: complex + contains: + id: + description: Numeric identifier of the server + returned: always + type: int + sample: 1937415 + name: + description: Name of the server + returned: always + type: str + sample: my-server + status: + description: Status of the server + returned: always + type: str + sample: running + server_type: + description: Name of the server type of the server + returned: always + type: str + sample: cx11 + ipv4_address: + description: Public IPv4 address of the server, None if not existing + returned: always + type: str + sample: 116.203.104.109 + ipv6: + description: IPv6 network of the server, None if not existing + returned: always + type: str + sample: 2a01:4f8:1c1c:c140::/64 + location: + description: Name of the location of the server + returned: always + type: str + sample: fsn1 + datacenter: + description: Name of the datacenter of the server + returned: always + type: str + sample: fsn1-dc14 + rescue_enabled: + description: True if rescue mode is enabled, Server will then boot into rescue system on next reboot + returned: always + type: bool + sample: false + backup_window: + description: Time window (UTC) in which the backup will run, or null if the backups are not enabled + returned: always + type: bool + sample: 22-02 + labels: + description: User-defined labels (key-value pairs) + returned: always + type: dict + delete_protection: + description: True if the network is protected for deletion + returned: always + type: bool + version_added: "0.1.0" + labels: + description: Labels of the network + returned: always + type: dict +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud + + +class AnsibleHcloudNetworkInfo(Hcloud): + def __init__(self, module): + Hcloud.__init__(self, module, "hcloud_network_info") + self.hcloud_network_info = None + + def _prepare_result(self): + tmp = [] + + for network in self.hcloud_network_info: + if network is not None: + subnets = [] + for subnet in network.subnets: + prepared_subnet = { + "type": subnet.type, + "ip_range": subnet.ip_range, + "network_zone": subnet.network_zone, + "gateway": subnet.gateway, + } + subnets.append(prepared_subnet) + routes = [] + for route in network.routes: + prepared_route = { + "destination": route.destination, + "gateway": route.gateway + } + routes.append(prepared_route) + + servers = [] + for server in network.servers: + image = None if server.image is None else to_native(server.image.name) + ipv4_address = None if server.public_net.ipv4 is None else to_native(server.public_net.ipv4.ip) + ipv6 = None if server.public_net.ipv6 is None else to_native(server.public_net.ipv6.ip) + prepared_server = { + "id": to_native(server.id), + "name": to_native(server.name), + "ipv4_address": ipv4_address, + "ipv6": ipv6, + "image": image, + "server_type": to_native(server.server_type.name), + "datacenter": to_native(server.datacenter.name), + "location": to_native(server.datacenter.location.name), + "rescue_enabled": server.rescue_enabled, + "backup_window": to_native(server.backup_window), + "labels": server.labels, + "status": to_native(server.status), + } + servers.append(prepared_server) + + tmp.append({ + "id": to_native(network.id), + "name": to_native(network.name), + "ip_range": to_native(network.ip_range), + "subnetworks": subnets, + "routes": routes, + "servers": servers, + "labels": network.labels, + "delete_protection": network.protection["delete"], + }) + return tmp + + def get_networks(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_network_info = [self.client.networks.get_by_id( + self.module.params.get("id") + )] + elif self.module.params.get("name") is not None: + self.hcloud_network_info = [self.client.networks.get_by_name( + self.module.params.get("name") + )] + elif self.module.params.get("label_selector") is not None: + self.hcloud_network_info = self.client.networks.get_all( + label_selector=self.module.params.get("label_selector")) + else: + self.hcloud_network_info = self.client.networks.get_all() + + except Exception as e: + self.module.fail_json(msg=e.message) + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + label_selector={"type": "str"}, + **Hcloud.base_module_arguments() + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHcloudNetworkInfo.define_module() + + hcloud = AnsibleHcloudNetworkInfo(module) + hcloud.get_networks() + result = hcloud.get_result() + info = { + 'hcloud_network_info': result['hcloud_network_info'] + } + module.exit_json(**info) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_placement_group.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_placement_group.py new file mode 100644 index 000000000..522bb679d --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_placement_group.py @@ -0,0 +1,230 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = """ +--- +module: hcloud_placement_group + +short_description: Create and manage placement groups on the Hetzner Cloud. + + +description: + - Create, update and manage placement groups on the Hetzner Cloud. + +author: + - Adrian Huber (@Adi146) + +options: + id: + description: + - The ID of the Hetzner Cloud placement group to manage. + - Only required if no placement group I(name) is given + type: int + name: + description: + - The Name of the Hetzner Cloud placement group to manage. + - Only required if no placement group I(id) is given, or a placement group does not exist. + type: str + labels: + description: + - User-defined labels (key-value pairs) + type: dict + type: + description: + - The Type of the Hetzner Cloud placement group. + type: str + state: + description: + - State of the placement group. + default: present + choices: [ absent, present ] + type: str + +requirements: + - hcloud-python >= 1.15.0 + +extends_documentation_fragment: +- hetzner.hcloud.hcloud +""" + +EXAMPLES = """ +- name: Create a basic placement group + hcloud_placement_group: + name: my-placement-group + state: present + type: spread + +- name: Create a placement group with labels + hcloud_placement_group: + name: my-placement-group + type: spread + labels: + key: value + mylabel: 123 + state: present + +- name: Ensure the placement group is absent (remove if needed) + hcloud_placement_group: + name: my-placement-group + state: absent +""" + +RETURN = """ +hcloud_placement_group: + description: The placement group instance + returned: Always + type: complex + contains: + id: + description: Numeric identifier of the placement group + returned: always + type: int + sample: 1937415 + name: + description: Name of the placement group + returned: always + type: str + sample: my placement group + labels: + description: User-defined labels (key-value pairs) + returned: always + type: dict + type: + description: Type of the placement group + returned: always + type: str + sample: spread + servers: + description: Server IDs of the placement group + returned: always + type: list + elements: int + sample: + - 4711 + - 4712 +""" + +from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native + + +class AnsibleHcloudPlacementGroup(Hcloud): + def __init__(self, module): + Hcloud.__init__(self, module, "hcloud_placement_group") + self.hcloud_placement_group = None + + def _prepare_result(self): + return { + "id": to_native(self.hcloud_placement_group.id), + "name": to_native(self.hcloud_placement_group.name), + "labels": self.hcloud_placement_group.labels, + "type": to_native(self.hcloud_placement_group.type), + "servers": self.hcloud_placement_group.servers, + } + + def _get_placement_group(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_placement_group = self.client.placement_groups.get_by_id( + self.module.params.get("id") + ) + elif self.module.params.get("name") is not None: + self.hcloud_placement_group = self.client.placement_groups.get_by_name( + self.module.params.get("name") + ) + except Exception as e: + self.module.fail_json(msg=e.message) + + def _create_placement_group(self): + self.module.fail_on_missing_params( + required_params=["name"] + ) + params = { + "name": self.module.params.get("name"), + "type": self.module.params.get("type"), + "labels": self.module.params.get("labels"), + } + if not self.module.check_mode: + try: + self.client.placement_groups.create(**params) + except Exception as e: + self.module.fail_json(msg=e.message, **params) + self._mark_as_changed() + self._get_placement_group() + + def _update_placement_group(self): + name = self.module.params.get("name") + if name is not None and self.hcloud_placement_group.name != name: + self.module.fail_on_missing_params( + required_params=["id"] + ) + if not self.module.check_mode: + self.hcloud_placement_group.update(name=name) + self._mark_as_changed() + + labels = self.module.params.get("labels") + if labels is not None and self.hcloud_placement_group.labels != labels: + if not self.module.check_mode: + self.hcloud_placement_group.update(labels=labels) + self._mark_as_changed() + + self._get_placement_group() + + def present_placement_group(self): + self._get_placement_group() + if self.hcloud_placement_group is None: + self._create_placement_group() + else: + self._update_placement_group() + + def delete_placement_group(self): + self._get_placement_group() + if self.hcloud_placement_group is not None: + if not self.module.check_mode: + self.client.placement_groups.delete(self.hcloud_placement_group) + self._mark_as_changed() + self.hcloud_placement_group = None + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + labels={"type": "dict"}, + type={"type": "str"}, + state={ + "choices": ["absent", "present"], + "default": "present", + }, + **Hcloud.base_module_arguments() + ), + required_one_of=[['id', 'name']], + required_if=[['state', 'present', ['name']]], + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHcloudPlacementGroup.define_module() + + hcloud = AnsibleHcloudPlacementGroup(module) + state = module.params.get("state") + if state == "absent": + hcloud.delete_placement_group() + elif state == "present": + hcloud.present_placement_group() + + module.exit_json(**hcloud.get_result()) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_primary_ip.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_primary_ip.py new file mode 100644 index 000000000..c192d5fec --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_primary_ip.py @@ -0,0 +1,271 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2022, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: hcloud_primary_ip + +short_description: Create and manage cloud Primary IPs on the Hetzner Cloud. + + +description: + - Create, update and manage cloud Primary IPs on the Hetzner Cloud. + +author: + - Lukas Kaemmerling (@lkaemmerling) +version_added: 1.8.0 +options: + id: + description: + - The ID of the Hetzner Cloud Primary IPs to manage. + - Only required if no Primary IP I(name) is given. + type: int + name: + description: + - The Name of the Hetzner Cloud Primary IPs to manage. + - Only required if no Primary IP I(id) is given or a Primary IP does not exist. + type: str + datacenter: + description: + - Home Location of the Hetzner Cloud Primary IP. + - Required if no I(server) is given and Primary IP does not exist. + type: str + type: + description: + - Type of the Primary IP. + - Required if Primary IP does not exist + choices: [ ipv4, ipv6 ] + type: str + auto_delete: + description: + - Delete this Primary IP when the resource it is assigned to is deleted + type: bool + default: no + delete_protection: + description: + - Protect the Primary IP for deletion. + type: bool + labels: + description: + - User-defined labels (key-value pairs). + type: dict + state: + description: + - State of the Primary IP. + default: present + choices: [ absent, present ] + type: str + +requirements: + - hcloud-python >= 1.9.0 + +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +''' + +EXAMPLES = """ +- name: Create a basic IPv4 Primary IP + hcloud_primary_ip: + name: my-primary-ip + datacenter: fsn1-dc14 + type: ipv4 + state: present +- name: Create a basic IPv6 Primary IP + hcloud_primary_ip: + name: my-primary-ip + datacenter: fsn1-dc14 + type: ipv6 + state: present +- name: Primary IP should be absent + hcloud_primary_ip: + name: my-primary-ip + state: absent +""" + +RETURN = """ +hcloud_primary_ip: + description: The Primary IP instance + returned: Always + type: complex + contains: + id: + description: ID of the Primary IP + type: int + returned: Always + sample: 12345 + name: + description: Name of the Primary IP + type: str + returned: Always + sample: my-primary-ip + ip: + description: IP Address of the Primary IP + type: str + returned: Always + sample: 116.203.104.109 + type: + description: Type of the Primary IP + type: str + returned: Always + sample: ipv4 + datacenter: + description: Name of the datacenter of the Primary IP + type: str + returned: Always + sample: fsn1-dc14 + delete_protection: + description: True if Primary IP is protected for deletion + type: bool + returned: always + sample: false + labels: + description: User-defined labels (key-value pairs) + type: dict + returned: Always + sample: + key: value + mylabel: 123 +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud + + +class AnsibleHcloudPrimaryIP(Hcloud): + def __init__(self, module): + Hcloud.__init__(self, module, "hcloud_primary_ip") + self.hcloud_primary_ip = None + + def _prepare_result(self): + return { + "id": to_native(self.hcloud_primary_ip.id), + "name": to_native(self.hcloud_primary_ip.name), + "ip": to_native(self.hcloud_primary_ip.ip), + "type": to_native(self.hcloud_primary_ip.type), + "datacenter": to_native(self.hcloud_primary_ip.datacenter.name), + "labels": self.hcloud_primary_ip.labels, + "delete_protection": self.hcloud_primary_ip.protection["delete"], + } + + def _get_primary_ip(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_primary_ip = self.client.primary_ips.get_by_id( + self.module.params.get("id") + ) + else: + self.hcloud_primary_ip = self.client.primary_ips.get_by_name( + self.module.params.get("name") + ) + except Exception as e: + self.module.fail_json(msg=e.message) + + def _create_primary_ip(self): + self.module.fail_on_missing_params( + required_params=["type", "datacenter"] + ) + try: + params = { + "type": self.module.params.get("type"), + "name": self.module.params.get("name"), + "datacenter": self.client.datacenters.get_by_name( + self.module.params.get("datacenter") + ) + } + + if self.module.params.get("labels") is not None: + params["labels"] = self.module.params.get("labels") + if not self.module.check_mode: + resp = self.client.primary_ips.create(**params) + self.hcloud_primary_ip = resp.primary_ip + + delete_protection = self.module.params.get("delete_protection") + if delete_protection is not None: + self.hcloud_primary_ip.change_protection(delete=delete_protection).wait_until_finished() + except Exception as e: + self.module.fail_json(msg=e) + self._mark_as_changed() + self._get_primary_ip() + + def _update_primary_ip(self): + try: + labels = self.module.params.get("labels") + if labels is not None and labels != self.hcloud_primary_ip.labels: + if not self.module.check_mode: + self.hcloud_primary_ip.update(labels=labels) + self._mark_as_changed() + + delete_protection = self.module.params.get("delete_protection") + if delete_protection is not None and delete_protection != self.hcloud_primary_ip.protection["delete"]: + if not self.module.check_mode: + self.hcloud_primary_ip.change_protection(delete=delete_protection).wait_until_finished() + self._mark_as_changed() + + self._get_primary_ip() + except Exception as e: + self.module.fail_json(msg=e.message) + + def present_primary_ip(self): + self._get_primary_ip() + if self.hcloud_primary_ip is None: + self._create_primary_ip() + else: + self._update_primary_ip() + + def delete_primary_ip(self): + try: + self._get_primary_ip() + if self.hcloud_primary_ip is not None: + if not self.module.check_mode: + self.client.primary_ips.delete(self.hcloud_primary_ip) + self._mark_as_changed() + self.hcloud_primary_ip = None + except Exception as e: + self.module.fail_json(msg=e.message) + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + datacenter={"type": "str"}, + auto_delete={"type": "bool", "default": False}, + type={"choices": ["ipv4", "ipv6"]}, + labels={"type": "dict"}, + delete_protection={"type": "bool"}, + state={ + "choices": ["absent", "present"], + "default": "present", + }, + **Hcloud.base_module_arguments() + ), + required_one_of=[['id', 'name']], + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHcloudPrimaryIP.define_module() + + hcloud = AnsibleHcloudPrimaryIP(module) + state = module.params["state"] + if state == "absent": + hcloud.delete_primary_ip() + elif state == "present": + hcloud.present_primary_ip() + + module.exit_json(**hcloud.get_result()) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_rdns.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_rdns.py new file mode 100644 index 000000000..9f79fbe70 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_rdns.py @@ -0,0 +1,360 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: hcloud_rdns + +short_description: Create and manage reverse DNS entries on the Hetzner Cloud. + + +description: + - Create, update and delete reverse DNS entries on the Hetzner Cloud. + +author: + - Lukas Kaemmerling (@lkaemmerling) + +options: + server: + description: + - The name of the Hetzner Cloud server you want to add the reverse DNS entry to. + type: str + floating_ip: + description: + - The name of the Hetzner Cloud Floating IP you want to add the reverse DNS entry to. + type: str + primary_ip: + description: + - The name of the Hetzner Cloud Primary IP you want to add the reverse DNS entry to. + type: str + load_balancer: + description: + - The name of the Hetzner Cloud Load Balancer you want to add the reverse DNS entry to. + type: str + ip_address: + description: + - The IP address that should point to I(dns_ptr). + type: str + required: true + dns_ptr: + description: + - The DNS address the I(ip_address) should resolve to. + - Omit the param to reset the reverse DNS entry to the default value. + type: str + state: + description: + - State of the reverse DNS entry. + default: present + choices: [ absent, present ] + type: str + +requirements: + - hcloud-python >= 1.3.0 + +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +''' + +EXAMPLES = """ +- name: Create a reverse DNS entry for a server + hcloud_rdns: + server: my-server + ip_address: 123.123.123.123 + dns_ptr: example.com + state: present + +- name: Create a reverse DNS entry for a Floating IP + hcloud_rdns: + floating_ip: my-floating-ip + ip_address: 123.123.123.123 + dns_ptr: example.com + state: present + +- name: Create a reverse DNS entry for a Primary IP + hcloud_rdns: + primary_ip: my-primary-ip + ip_address: 123.123.123.123 + dns_ptr: example.com + state: present + +- name: Create a reverse DNS entry for a Load Balancer + hcloud_rdns: + load_balancer: my-load-balancer + ip_address: 123.123.123.123 + dns_ptr: example.com + state: present + +- name: Ensure the reverse DNS entry is absent (remove if needed) + hcloud_rdns: + server: my-server + ip_address: 123.123.123.123 + dns_ptr: example.com + state: absent +""" + +RETURN = """ +hcloud_rdns: + description: The reverse DNS entry + returned: always + type: complex + contains: + server: + description: Name of the server + type: str + returned: always + sample: my-server + floating_ip: + description: Name of the Floating IP + type: str + returned: always + sample: my-floating-ip + primary_ip: + description: Name of the Primary IP + type: str + returned: always + sample: my-primary-ip + load_balancer: + description: Name of the Load Balancer + type: str + returned: always + sample: my-load-balancer + ip_address: + description: The IP address that point to the DNS ptr + type: str + returned: always + sample: 123.123.123.123 + dns_ptr: + description: The DNS that resolves to the IP + type: str + returned: always + sample: example.com +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils + + +class AnsibleHcloudReverseDNS(Hcloud): + def __init__(self, module): + Hcloud.__init__(self, module, "hcloud_rdns") + self.hcloud_resource = None + self.hcloud_rdns = None + + def _prepare_result(self): + result = { + "server": None, + "floating_ip": None, + "load_balancer": None, + "ip_address": to_native(self.hcloud_rdns["ip_address"]), + "dns_ptr": to_native(self.hcloud_rdns["dns_ptr"]), + } + + if self.module.params.get("server"): + result["server"] = to_native(self.hcloud_resource.name) + elif self.module.params.get("floating_ip"): + result["floating_ip"] = to_native(self.hcloud_resource.name) + elif self.module.params.get("load_balancer"): + result["load_balancer"] = to_native(self.hcloud_resource.name) + elif self.module.params.get("primary_ip"): + result["primary_ip"] = to_native(self.hcloud_resource.name) + return result + + def _get_resource(self): + try: + if self.module.params.get("server"): + self.hcloud_resource = self.client.servers.get_by_name( + self.module.params.get("server") + ) + if self.hcloud_resource is None: + self.module.fail_json(msg="The selected server does not exist") + elif self.module.params.get("floating_ip"): + self.hcloud_resource = self.client.floating_ips.get_by_name( + self.module.params.get("floating_ip") + ) + if self.hcloud_resource is None: + self.module.fail_json(msg="The selected Floating IP does not exist") + elif self.module.params.get("primary_ip"): + self.hcloud_resource = self.client.primary_ips.get_by_name( + self.module.params.get("primary_ip") + ) + if self.hcloud_resource is None: + self.module.fail_json(msg="The selected Floating IP does not exist") + elif self.module.params.get("load_balancer"): + self.hcloud_resource = self.client.load_balancers.get_by_name( + self.module.params.get("load_balancer") + ) + if self.hcloud_resource is None: + self.module.fail_json(msg="The selected Load Balancer does not exist") + except Exception as e: + self.module.fail_json(msg=e.message) + + def _get_rdns(self): + ip_address = self.module.params.get("ip_address") + if utils.validate_ip_address(ip_address): + if self.module.params.get("server"): + if self.hcloud_resource.public_net.ipv4.ip == ip_address: + self.hcloud_rdns = { + "ip_address": self.hcloud_resource.public_net.ipv4.ip, + "dns_ptr": self.hcloud_resource.public_net.ipv4.dns_ptr, + } + else: + self.module.fail_json(msg="The selected server does not have this IP address") + elif self.module.params.get("floating_ip"): + if self.hcloud_resource.ip == ip_address: + self.hcloud_rdns = { + "ip_address": self.hcloud_resource.ip, + "dns_ptr": self.hcloud_resource.dns_ptr[0]["dns_ptr"], + } + else: + self.module.fail_json(msg="The selected Floating IP does not have this IP address") + elif self.module.params.get("primary_ip"): + if self.hcloud_resource.ip == ip_address: + self.hcloud_rdns = { + "ip_address": self.hcloud_resource.ip, + "dns_ptr": self.hcloud_resource.dns_ptr[0]["dns_ptr"], + } + else: + self.module.fail_json(msg="The selected Primary IP does not have this IP address") + elif self.module.params.get("load_balancer"): + if self.hcloud_resource.public_net.ipv4.ip == ip_address: + self.hcloud_rdns = { + "ip_address": self.hcloud_resource.public_net.ipv4.ip, + "dns_ptr": self.hcloud_resource.public_net.ipv4.dns_ptr, + } + else: + self.module.fail_json(msg="The selected Load Balancer does not have this IP address") + + elif utils.validate_ip_v6_address(ip_address): + if self.module.params.get("server"): + for ipv6_address_dns_ptr in self.hcloud_resource.public_net.ipv6.dns_ptr: + if ipv6_address_dns_ptr["ip"] == ip_address: + self.hcloud_rdns = { + "ip_address": ipv6_address_dns_ptr["ip"], + "dns_ptr": ipv6_address_dns_ptr["dns_ptr"], + } + elif self.module.params.get("floating_ip"): + for ipv6_address_dns_ptr in self.hcloud_resource.dns_ptr: + if ipv6_address_dns_ptr["ip"] == ip_address: + self.hcloud_rdns = { + "ip_address": ipv6_address_dns_ptr["ip"], + "dns_ptr": ipv6_address_dns_ptr["dns_ptr"], + } + elif self.module.params.get("primary_ip"): + for ipv6_address_dns_ptr in self.hcloud_resource.dns_ptr: + if ipv6_address_dns_ptr["ip"] == ip_address: + self.hcloud_rdns = { + "ip_address": ipv6_address_dns_ptr["ip"], + "dns_ptr": ipv6_address_dns_ptr["dns_ptr"], + } + elif self.module.params.get("load_balancer"): + for ipv6_address_dns_ptr in self.hcloud_resource.public_net.ipv6.dns_ptr: + if ipv6_address_dns_ptr["ip"] == ip_address: + self.hcloud_rdns = { + "ip_address": ipv6_address_dns_ptr["ip"], + "dns_ptr": ipv6_address_dns_ptr["dns_ptr"], + } + else: + self.module.fail_json(msg="The given IP address is not valid") + + def _create_rdns(self): + self.module.fail_on_missing_params( + required_params=["dns_ptr"] + ) + params = { + "ip": self.module.params.get("ip_address"), + "dns_ptr": self.module.params.get("dns_ptr"), + } + + if not self.module.check_mode: + try: + self.hcloud_resource.change_dns_ptr(**params).wait_until_finished() + except Exception as e: + self.module.fail_json(msg=e.message) + self._mark_as_changed() + self._get_resource() + self._get_rdns() + + def _update_rdns(self): + dns_ptr = self.module.params.get("dns_ptr") + if dns_ptr != self.hcloud_rdns["dns_ptr"]: + params = { + "ip": self.module.params.get("ip_address"), + "dns_ptr": dns_ptr, + } + + if not self.module.check_mode: + try: + self.hcloud_resource.change_dns_ptr(**params).wait_until_finished() + except Exception as e: + self.module.fail_json(msg=e.message) + self._mark_as_changed() + self._get_resource() + self._get_rdns() + + def present_rdns(self): + self._get_resource() + self._get_rdns() + if self.hcloud_rdns is None: + self._create_rdns() + else: + self._update_rdns() + + def delete_rdns(self): + self._get_resource() + self._get_rdns() + if self.hcloud_rdns is not None: + if not self.module.check_mode: + try: + self.hcloud_resource.change_dns_ptr(ip=self.hcloud_rdns['ip_address'], dns_ptr=None) + except Exception as e: + self.module.fail_json(msg=e.message) + self._mark_as_changed() + self.hcloud_rdns = None + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + server={"type": "str"}, + floating_ip={"type": "str"}, + load_balancer={"type": "str"}, + primary_ip={"type": "str"}, + ip_address={"type": "str", "required": True}, + dns_ptr={"type": "str"}, + state={ + "choices": ["absent", "present"], + "default": "present", + }, + **Hcloud.base_module_arguments() + ), + required_one_of=[['server', 'floating_ip', 'load_balancer', 'primary_ip']], + mutually_exclusive=[["server", "floating_ip", 'load_balancer', 'primary_ip']], + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHcloudReverseDNS.define_module() + + hcloud = AnsibleHcloudReverseDNS(module) + state = module.params["state"] + if state == "absent": + hcloud.delete_rdns() + elif state == "present": + hcloud.present_rdns() + + module.exit_json(**hcloud.get_result()) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_route.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_route.py new file mode 100644 index 000000000..c75177953 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_route.py @@ -0,0 +1,196 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: hcloud_route + +short_description: Create and delete cloud routes on the Hetzner Cloud. + + +description: + - Create, update and delete cloud routes on the Hetzner Cloud. + +author: + - Lukas Kaemmerling (@lkaemmerling) + +options: + network: + description: + - The name of the Hetzner Cloud Network. + type: str + required: true + destination: + description: + - Destination network or host of this route. + type: str + required: true + gateway: + description: + - Gateway for the route. + type: str + required: true + state: + description: + - State of the route. + default: present + choices: [ absent, present ] + type: str + +requirements: + - hcloud-python >= 1.3.0 + +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +''' + +EXAMPLES = """ +- name: Create a basic route + hcloud_route: + network: my-network + destination: 10.100.1.0/24 + gateway: 10.0.1.1 + state: present + +- name: Ensure the route is absent + hcloud_route: + network: my-network + destination: 10.100.1.0/24 + gateway: 10.0.1.1 + state: absent +""" + +RETURN = """ +hcloud_route: + description: One Route of a Network + returned: always + type: complex + contains: + network: + description: Name of the Network + type: str + returned: always + sample: my-network + destination: + description: Destination network or host of this route + type: str + returned: always + sample: 10.0.0.0/8 + gateway: + description: Gateway of the route + type: str + returned: always + sample: 10.0.0.1 +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud + +try: + from hcloud.networks.domain import NetworkRoute +except ImportError: + NetworkRoute = None + + +class AnsibleHcloudRoute(Hcloud): + def __init__(self, module): + Hcloud.__init__(self, module, "hcloud_route") + self.hcloud_network = None + self.hcloud_route = None + + def _prepare_result(self): + return { + "network": to_native(self.hcloud_network.name), + "destination": to_native(self.hcloud_route.destination), + "gateway": self.hcloud_route.gateway, + } + + def _get_network(self): + try: + self.hcloud_network = self.client.networks.get_by_name(self.module.params.get("network")) + self.hcloud_route = None + except Exception as e: + self.module.fail_json(msg=e.message) + + def _get_route(self): + destination = self.module.params.get("destination") + gateway = self.module.params.get("gateway") + for route in self.hcloud_network.routes: + if route.destination == destination and route.gateway == gateway: + self.hcloud_route = route + + def _create_route(self): + route = NetworkRoute( + destination=self.module.params.get("destination"), + gateway=self.module.params.get('gateway') + ) + + if not self.module.check_mode: + try: + self.hcloud_network.add_route(route=route).wait_until_finished() + except Exception as e: + self.module.fail_json(msg=e.message) + + self._mark_as_changed() + self._get_network() + self._get_route() + + def present_route(self): + self._get_network() + self._get_route() + if self.hcloud_route is None: + self._create_route() + + def delete_route(self): + self._get_network() + self._get_route() + if self.hcloud_route is not None and self.hcloud_network is not None: + if not self.module.check_mode: + try: + self.hcloud_network.delete_route(self.hcloud_route).wait_until_finished() + except Exception as e: + self.module.fail_json(msg=e.message) + self._mark_as_changed() + self.hcloud_route = None + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + network={"type": "str", "required": True}, + gateway={"type": "str", "required": True}, + destination={"type": "str", "required": True}, + state={ + "choices": ["absent", "present"], + "default": "present", + }, + **Hcloud.base_module_arguments() + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHcloudRoute.define_module() + + hcloud = AnsibleHcloudRoute(module) + state = module.params["state"] + if state == "absent": + hcloud.delete_route() + elif state == "present": + hcloud.present_route() + + module.exit_json(**hcloud.get_result()) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server.py new file mode 100644 index 000000000..3a77da695 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server.py @@ -0,0 +1,928 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: hcloud_server + +short_description: Create and manage cloud servers on the Hetzner Cloud. + + +description: + - Create, update and manage cloud servers on the Hetzner Cloud. + +author: + - Lukas Kaemmerling (@LKaemmerling) + +options: + id: + description: + - The ID of the Hetzner Cloud server to manage. + - Only required if no server I(name) is given + type: int + name: + description: + - The Name of the Hetzner Cloud server to manage. + - Only required if no server I(id) is given or a server does not exist. + type: str + server_type: + description: + - The Server Type of the Hetzner Cloud server to manage. + - Required if server does not exist. + type: str + ssh_keys: + description: + - List of SSH key names + - The key names correspond to the SSH keys configured for your + Hetzner Cloud account access. + type: list + elements: str + volumes: + description: + - List of Volumes IDs that should be attached to the server on server creation. + type: list + elements: str + firewalls: + description: + - List of Firewall IDs that should be attached to the server on server creation. + type: list + elements: str + image: + description: + - Image the server should be created from. + - Required if server does not exist. + type: str + location: + description: + - Location of Server. + - Required if no I(datacenter) is given and server does not exist. + type: str + datacenter: + description: + - Datacenter of Server. + - Required of no I(location) is given and server does not exist. + type: str + backups: + description: + - Enable or disable Backups for the given Server. + type: bool + upgrade_disk: + description: + - Resize the disk size, when resizing a server. + - If you want to downgrade the server later, this value should be False. + type: bool + default: no + enable_ipv4: + description: + - Enables the public ipv4 address + type: bool + default: yes + enable_ipv6: + description: + - Enables the public ipv6 address + type: bool + default: yes + ipv4: + description: + - ID of the ipv4 Primary IP to use. If omitted and enable_ipv4 is true, a new ipv4 Primary IP will automatically be created + type: str + ipv6: + description: + - ID of the ipv6 Primary IP to use. If omitted and enable_ipv6 is true, a new ipv6 Primary IP will automatically be created. + type: str + private_networks: + description: + - List of private networks the server is attached to (name or ID) + - If None, private networks are left as they are (e.g. if previously added by hcloud_server_network), + if it has any other value (including []), only those networks are attached to the server. + type: list + elements: str + force_upgrade: + description: + - Deprecated + - Force the upgrade of the server. + - Power off the server if it is running on upgrade. + type: bool + default: no + force: + description: + - Force the update of the server. + - May power off the server if update. + type: bool + default: no + allow_deprecated_image: + description: + - Allows the creation of servers with deprecated images. + type: bool + default: no + user_data: + description: + - User Data to be passed to the server on creation. + - Only used if server does not exist. + type: str + rescue_mode: + description: + - Add the Hetzner rescue system type you want the server to be booted into. + type: str + labels: + description: + - User-defined labels (key-value pairs). + type: dict + delete_protection: + description: + - Protect the Server for deletion. + - Needs to be the same as I(rebuild_protection). + type: bool + rebuild_protection: + description: + - Protect the Server for rebuild. + - Needs to be the same as I(delete_protection). + type: bool + placement_group: + description: + - Placement Group of the server. + type: str + state: + description: + - State of the server. + default: present + choices: [ absent, present, restarted, started, stopped, rebuild ] + type: str +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +''' + +EXAMPLES = """ +- name: Create a basic server + hcloud_server: + name: my-server + server_type: cx11 + image: ubuntu-18.04 + state: present + +- name: Create a basic server with ssh key + hcloud_server: + name: my-server + server_type: cx11 + image: ubuntu-18.04 + location: fsn1 + ssh_keys: + - me@myorganisation + state: present + +- name: Resize an existing server + hcloud_server: + name: my-server + server_type: cx21 + upgrade_disk: yes + state: present + +- name: Ensure the server is absent (remove if needed) + hcloud_server: + name: my-server + state: absent + +- name: Ensure the server is started + hcloud_server: + name: my-server + state: started + +- name: Ensure the server is stopped + hcloud_server: + name: my-server + state: stopped + +- name: Ensure the server is restarted + hcloud_server: + name: my-server + state: restarted + +- name: Ensure the server is will be booted in rescue mode and therefore restarted + hcloud_server: + name: my-server + rescue_mode: linux64 + state: restarted + +- name: Ensure the server is rebuild + hcloud_server: + name: my-server + image: ubuntu-18.04 + state: rebuild + +- name: Add server to placement group + hcloud_server: + name: my-server + placement_group: my-placement-group + force: True + state: present + +- name: Remove server from placement group + hcloud_server: + name: my-server + placement_group: null + state: present + +- name: Add server with private network only + hcloud_server: + name: my-server + enable_ipv4: false + enable_ipv6: false + private_networks: + - my-network + - 4711 + state: present +""" + +RETURN = """ +hcloud_server: + description: The server instance + returned: Always + type: complex + contains: + id: + description: Numeric identifier of the server + returned: always + type: int + sample: 1937415 + name: + description: Name of the server + returned: always + type: str + sample: my-server + status: + description: Status of the server + returned: always + type: str + sample: running + server_type: + description: Name of the server type of the server + returned: always + type: str + sample: cx11 + ipv4_address: + description: Public IPv4 address of the server + returned: always + type: str + sample: 116.203.104.109 + ipv6: + description: IPv6 network of the server + returned: always + type: str + sample: 2a01:4f8:1c1c:c140::/64 + private_networks: + description: List of private networks the server is attached to (name or ID) + returned: always + type: list + elements: str + sample: ['my-network', 'another-network', '4711'] + private_networks_info: + description: List of private networks the server is attached to (dict with name and ip) + returned: always + type: list + elements: dict + sample: [{'name': 'my-network', 'ip': '192.168.1.1'}, {'name': 'another-network', 'ip': '10.185.50.40'}] + location: + description: Name of the location of the server + returned: always + type: str + sample: fsn1 + placement_group: + description: Placement Group of the server + type: str + returned: always + sample: 4711 + version_added: "1.5.0" + datacenter: + description: Name of the datacenter of the server + returned: always + type: str + sample: fsn1-dc14 + rescue_enabled: + description: True if rescue mode is enabled, Server will then boot into rescue system on next reboot + returned: always + type: bool + sample: false + backup_window: + description: Time window (UTC) in which the backup will run, or null if the backups are not enabled + returned: always + type: bool + sample: 22-02 + labels: + description: User-defined labels (key-value pairs) + returned: always + type: dict + delete_protection: + description: True if server is protected for deletion + type: bool + returned: always + sample: false + version_added: "0.1.0" + rebuild_protection: + description: True if server is protected for rebuild + type: bool + returned: always + sample: false + version_added: "0.1.0" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud +from datetime import timedelta + +try: + from hcloud.volumes.domain import Volume + from hcloud.ssh_keys.domain import SSHKey + from hcloud.servers.domain import Server, ServerCreatePublicNetwork + from hcloud.firewalls.domain import FirewallResource +except ImportError: + Volume = None + SSHKey = None + Server = None + ServerCreatePublicNetwork = None + FirewallResource = None + + +class AnsibleHcloudServer(Hcloud): + def __init__(self, module): + Hcloud.__init__(self, module, "hcloud_server") + self.hcloud_server = None + + def _prepare_result(self): + image = None if self.hcloud_server.image is None else to_native(self.hcloud_server.image.name) + placement_group = None if self.hcloud_server.placement_group is None else to_native( + self.hcloud_server.placement_group.name) + ipv4_address = None if self.hcloud_server.public_net.ipv4 is None else to_native( + self.hcloud_server.public_net.ipv4.ip) + ipv6 = None if self.hcloud_server.public_net.ipv6 is None else to_native(self.hcloud_server.public_net.ipv6.ip) + backup_window = None if self.hcloud_server.backup_window is None else to_native(self.hcloud_server.backup_window) + return { + "id": to_native(self.hcloud_server.id), + "name": to_native(self.hcloud_server.name), + "ipv4_address": ipv4_address, + "ipv6": ipv6, + "private_networks": [to_native(net.network.name) for net in self.hcloud_server.private_net], + "private_networks_info": [{"name": to_native(net.network.name), "ip": net.ip} for net in self.hcloud_server.private_net], + "image": image, + "server_type": to_native(self.hcloud_server.server_type.name), + "datacenter": to_native(self.hcloud_server.datacenter.name), + "location": to_native(self.hcloud_server.datacenter.location.name), + "placement_group": placement_group, + "rescue_enabled": self.hcloud_server.rescue_enabled, + "backup_window": backup_window, + "labels": self.hcloud_server.labels, + "delete_protection": self.hcloud_server.protection["delete"], + "rebuild_protection": self.hcloud_server.protection["rebuild"], + "status": to_native(self.hcloud_server.status), + } + + def _get_server(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_server = self.client.servers.get_by_id( + self.module.params.get("id") + ) + else: + self.hcloud_server = self.client.servers.get_by_name( + self.module.params.get("name") + ) + except Exception as e: + self.module.fail_json(msg=e.message) + + def _create_server(self): + self.module.fail_on_missing_params( + required_params=["name", "server_type", "image"] + ) + + server_type = self._get_server_type() + + params = { + "name": self.module.params.get("name"), + "server_type": server_type, + "user_data": self.module.params.get("user_data"), + "labels": self.module.params.get("labels"), + "image": self._get_image(server_type), + "placement_group": self._get_placement_group(), + "public_net": ServerCreatePublicNetwork( + enable_ipv4=self.module.params.get("enable_ipv4"), + enable_ipv6=self.module.params.get("enable_ipv6") + ) + } + + if self.module.params.get("ipv4") is not None: + p = self.client.primary_ips.get_by_name(self.module.params.get("ipv4")) + if not p: + p = self.client.primary_ips.get_by_id(self.module.params.get("ipv4")) + params["public_net"].ipv4 = p + + if self.module.params.get("ipv6") is not None: + p = self.client.primary_ips.get_by_name(self.module.params.get("ipv6")) + if not p: + p = self.client.primary_ips.get_by_id(self.module.params.get("ipv6")) + params["public_net"].ipv6 = p + + if self.module.params.get("private_networks") is not None: + _networks = [] + for network_name_or_id in self.module.params.get("private_networks"): + _networks.append( + self.client.networks.get_by_name(network_name_or_id) + or self.client.networks.get_by_id(network_name_or_id) + ) + params["networks"] = _networks + + if self.module.params.get("ssh_keys") is not None: + params["ssh_keys"] = [ + SSHKey(name=ssh_key_name) + for ssh_key_name in self.module.params.get("ssh_keys") + ] + + if self.module.params.get("volumes") is not None: + params["volumes"] = [ + Volume(id=volume_id) for volume_id in self.module.params.get("volumes") + ] + if self.module.params.get("firewalls") is not None: + params["firewalls"] = [] + for fw in self.module.params.get("firewalls"): + f = self.client.firewalls.get_by_name(fw) + if f is not None: + # When firewall name is not available look for id instead + params["firewalls"].append(f) + else: + params["firewalls"].append(self.client.firewalls.get_by_id(fw)) + + if self.module.params.get("location") is None and self.module.params.get("datacenter") is None: + # When not given, the API will choose the location. + params["location"] = None + params["datacenter"] = None + elif self.module.params.get("location") is not None and self.module.params.get("datacenter") is None: + params["location"] = self.client.locations.get_by_name( + self.module.params.get("location") + ) + elif self.module.params.get("location") is None and self.module.params.get("datacenter") is not None: + params["datacenter"] = self.client.datacenters.get_by_name( + self.module.params.get("datacenter") + ) + + if self.module.params.get("state") == "stopped": + params["start_after_create"] = False + if not self.module.check_mode: + try: + resp = self.client.servers.create(**params) + self.result["root_password"] = resp.root_password + resp.action.wait_until_finished(max_retries=1000) + [action.wait_until_finished() for action in resp.next_actions] + + rescue_mode = self.module.params.get("rescue_mode") + if rescue_mode: + self._get_server() + self._set_rescue_mode(rescue_mode) + + backups = self.module.params.get("backups") + if backups: + self._get_server() + self.hcloud_server.enable_backup().wait_until_finished() + + delete_protection = self.module.params.get("delete_protection") + rebuild_protection = self.module.params.get("rebuild_protection") + if delete_protection is not None and rebuild_protection is not None: + self._get_server() + self.hcloud_server.change_protection(delete=delete_protection, + rebuild=rebuild_protection).wait_until_finished() + except Exception as e: + self.module.fail_json(msg=e.message) + self._mark_as_changed() + self._get_server() + + def _get_image(self, server_type): + image_resp = self.client.images.get_list(name=self.module.params.get("image"), architecture=server_type.architecture, include_deprecated=True) + images = getattr(image_resp, 'images') + image = None + if images is not None and len(images) > 0: + # If image name is not available look for id instead + image = images[0] + else: + try: + image = self.client.images.get_by_id(self.module.params.get("image")) + except Exception: + self.module.fail_json(msg="Image %s was not found" % self.module.params.get('image')) + if image.deprecated is not None: + available_until = image.deprecated + timedelta(days=90) + if self.module.params.get("allow_deprecated_image"): + self.module.warn( + "You try to use a deprecated image. The image %s will continue to be available until %s.") % ( + image.name, available_until.strftime('%Y-%m-%d')) + else: + self.module.fail_json( + msg=("You try to use a deprecated image. The image %s will continue to be available until %s." + + " If you want to use this image use allow_deprecated_image=yes." + ) % (image.name, available_until.strftime('%Y-%m-%d'))) + return image + + def _get_server_type(self): + server_type = self.client.server_types.get_by_name( + self.module.params.get("server_type") + ) + if server_type is None: + try: + server_type = self.client.server_types.get_by_id(self.module.params.get("server_type")) + except Exception: + self.module.fail_json(msg="server_type %s was not found" % self.module.params.get('server_type')) + + return server_type + + def _get_placement_group(self): + if self.module.params.get("placement_group") is None: + return None + + placement_group = self.client.placement_groups.get_by_name( + self.module.params.get("placement_group") + ) + if placement_group is None: + try: + placement_group = self.client.placement_groups.get_by_id(self.module.params.get("placement_group")) + except Exception: + self.module.fail_json( + msg="placement_group %s was not found" % self.module.params.get("placement_group")) + + return placement_group + + def _get_primary_ip(self, field): + if self.module.params.get(field) is None: + return None + + primary_ip = self.client.primary_ips.get_by_name( + self.module.params.get(field) + ) + if primary_ip is None: + try: + primary_ip = self.client.primary_ips.get_by_id(self.module.params.get(field)) + except Exception as e: + self.module.fail_json( + msg="primary_ip %s was not found" % self.module.params.get(field)) + + return primary_ip + + def _update_server(self): + if "force_upgrade" in self.module.params: + self.module.warn("force_upgrade is deprecated, use force instead") + + try: + previous_server_status = self.hcloud_server.status + + rescue_mode = self.module.params.get("rescue_mode") + if rescue_mode and self.hcloud_server.rescue_enabled is False: + if not self.module.check_mode: + self._set_rescue_mode(rescue_mode) + self._mark_as_changed() + elif not rescue_mode and self.hcloud_server.rescue_enabled is True: + if not self.module.check_mode: + self.hcloud_server.disable_rescue().wait_until_finished() + self._mark_as_changed() + + backups = self.module.params.get("backups") + if backups and self.hcloud_server.backup_window is None: + if not self.module.check_mode: + self.hcloud_server.enable_backup().wait_until_finished() + self._mark_as_changed() + elif backups is not None and not backups and self.hcloud_server.backup_window is not None: + if not self.module.check_mode: + self.hcloud_server.disable_backup().wait_until_finished() + self._mark_as_changed() + + labels = self.module.params.get("labels") + if labels is not None and labels != self.hcloud_server.labels: + if not self.module.check_mode: + self.hcloud_server.update(labels=labels) + self._mark_as_changed() + + wanted_firewalls = self.module.params.get("firewalls") + if wanted_firewalls is not None: + # Removing existing but not wanted firewalls + for current_firewall in self.hcloud_server.public_net.firewalls: + if current_firewall.firewall.name not in wanted_firewalls: + self._mark_as_changed() + if not self.module.check_mode: + r = FirewallResource(type="server", server=self.hcloud_server) + actions = self.client.firewalls.remove_from_resources(current_firewall.firewall, [r]) + for a in actions: + a.wait_until_finished() + + # Adding wanted firewalls that doesn't exist yet + for fname in wanted_firewalls: + found = False + for f in self.hcloud_server.public_net.firewalls: + if f.firewall.name == fname: + found = True + break + + if not found: + self._mark_as_changed() + if not self.module.check_mode: + fw = self.client.firewalls.get_by_name(fname) + if fw is None: + self.module.fail_json(msg="firewall %s was not found" % fname) + r = FirewallResource(type="server", server=self.hcloud_server) + actions = self.client.firewalls.apply_to_resources(fw, [r]) + for a in actions: + a.wait_until_finished() + + if "placement_group" in self.module.params: + if self.module.params["placement_group"] is None and self.hcloud_server.placement_group is not None: + if not self.module.check_mode: + self.hcloud_server.remove_from_placement_group().wait_until_finished() + self._mark_as_changed() + else: + placement_group = self._get_placement_group() + if ( + placement_group is not None and + ( + self.hcloud_server.placement_group is None or + self.hcloud_server.placement_group.id != placement_group.id + ) + ): + self.stop_server_if_forced() + if not self.module.check_mode: + self.hcloud_server.add_to_placement_group(placement_group).wait_until_finished() + self._mark_as_changed() + + if "ipv4" in self.module.params: + if ( + self.module.params["ipv4"] is None and + self.hcloud_server.public_net.primary_ipv4 is not None and + not self.module.params.get("enable_ipv4") + ): + self.stop_server_if_forced() + if not self.module.check_mode: + self.hcloud_server.public_net.primary_ipv4.unassign().wait_until_finished() + self._mark_as_changed() + else: + primary_ip = self._get_primary_ip("ipv4") + if ( + primary_ip is not None and + ( + self.hcloud_server.public_net.primary_ipv4 is None or + self.hcloud_server.public_net.primary_ipv4.id != primary_ip.id + ) + ): + self.stop_server_if_forced() + if not self.module.check_mode: + if self.hcloud_server.public_net.primary_ipv4: + self.hcloud_server.public_net.primary_ipv4.unassign().wait_until_finished() + primary_ip.assign(self.hcloud_server.id, "server").wait_until_finished() + self._mark_as_changed() + if "ipv6" in self.module.params: + if ( + (self.module.params["ipv6"] is None or self.module.params["ipv6"] == "") and + self.hcloud_server.public_net.primary_ipv6 is not None and + not self.module.params.get("enable_ipv6") + ): + self.stop_server_if_forced() + if not self.module.check_mode: + self.hcloud_server.public_net.primary_ipv6.unassign().wait_until_finished() + self._mark_as_changed() + else: + primary_ip = self._get_primary_ip("ipv6") + if ( + primary_ip is not None and + ( + self.hcloud_server.public_net.primary_ipv6 is None or + self.hcloud_server.public_net.primary_ipv6.id != primary_ip.id + ) + ): + self.stop_server_if_forced() + if not self.module.check_mode: + if self.hcloud_server.public_net.primary_ipv6 is not None: + self.hcloud_server.public_net.primary_ipv6.unassign().wait_until_finished() + primary_ip.assign(self.hcloud_server.id, "server").wait_until_finished() + self._mark_as_changed() + if "private_networks" in self.module.params and self.module.params["private_networks"] is not None: + if not bool(self.module.params["private_networks"]): + # This handles None, "" and [] + networks_target = {} + else: + _networks = {} + for network_name_or_id in self.module.params.get("private_networks"): + _found_network = self.client.networks.get_by_name(network_name_or_id) \ + or self.client.networks.get_by_id(network_name_or_id) + _networks.update( + {_found_network.id: _found_network} + ) + networks_target = _networks + networks_is = dict() + for p_network in self.hcloud_server.private_net: + networks_is.update({p_network.network.id: p_network.network}) + for network_id in set(list(networks_is) + list(networks_target)): + if network_id in networks_is and network_id not in networks_target: + self.stop_server_if_forced() + if not self.module.check_mode: + self.hcloud_server.detach_from_network(networks_is[network_id]).wait_until_finished() + self._mark_as_changed() + elif network_id in networks_target and network_id not in networks_is: + self.stop_server_if_forced() + if not self.module.check_mode: + self.hcloud_server.attach_to_network(networks_target[network_id]).wait_until_finished() + self._mark_as_changed() + + server_type = self.module.params.get("server_type") + if server_type is not None and self.hcloud_server.server_type.name != server_type: + self.stop_server_if_forced() + + timeout = 100 + if self.module.params.get("upgrade_disk"): + timeout = ( + 1000 + ) # When we upgrade the disk to the resize progress takes some more time. + if not self.module.check_mode: + self.hcloud_server.change_type( + server_type=self._get_server_type(), + upgrade_disk=self.module.params.get("upgrade_disk"), + ).wait_until_finished(timeout) + self._mark_as_changed() + + if ( + not self.module.check_mode and + ( + ( + self.module.params.get("state") == "present" and + previous_server_status == Server.STATUS_RUNNING + ) or + self.module.params.get("state") == "started" + ) + ): + self.start_server() + + delete_protection = self.module.params.get("delete_protection") + rebuild_protection = self.module.params.get("rebuild_protection") + if (delete_protection is not None and rebuild_protection is not None) and ( + delete_protection != self.hcloud_server.protection["delete"] or rebuild_protection != + self.hcloud_server.protection["rebuild"]): + if not self.module.check_mode: + self.hcloud_server.change_protection(delete=delete_protection, + rebuild=rebuild_protection).wait_until_finished() + self._mark_as_changed() + self._get_server() + except Exception as e: + self.module.fail_json(msg=e) + + def _set_rescue_mode(self, rescue_mode): + if self.module.params.get("ssh_keys"): + resp = self.hcloud_server.enable_rescue(type=rescue_mode, + ssh_keys=[self.client.ssh_keys.get_by_name(ssh_key_name).id + for + ssh_key_name in + self.module.params.get("ssh_keys")]) + else: + resp = self.hcloud_server.enable_rescue(type=rescue_mode) + resp.action.wait_until_finished() + self.result["root_password"] = resp.root_password + + def start_server(self): + try: + if self.hcloud_server: + if self.hcloud_server.status != Server.STATUS_RUNNING: + if not self.module.check_mode: + self.client.servers.power_on(self.hcloud_server).wait_until_finished() + self._mark_as_changed() + self._get_server() + except Exception as e: + self.module.fail_json(msg=e.message) + + def stop_server(self): + try: + if self.hcloud_server: + if self.hcloud_server.status != Server.STATUS_OFF: + if not self.module.check_mode: + self.client.servers.power_off(self.hcloud_server).wait_until_finished() + self._mark_as_changed() + self._get_server() + except Exception as e: + self.module.fail_json(msg=e.message) + + def stop_server_if_forced(self): + previous_server_status = self.hcloud_server.status + if previous_server_status == Server.STATUS_RUNNING and not self.module.check_mode: + if ( + self.module.params.get("force_upgrade") or + self.module.params.get("force") or + self.module.params.get("state") == "stopped" + ): + self.stop_server() # Only stopped server can be upgraded + return previous_server_status + else: + self.module.warn( + "You can not upgrade a running instance %s. You need to stop the instance or use force=yes." + % self.hcloud_server.name + ) + + return None + + def rebuild_server(self): + self.module.fail_on_missing_params( + required_params=["image"] + ) + try: + if not self.module.check_mode: + image = self._get_image(self.hcloud_server.server_type) + self.client.servers.rebuild(self.hcloud_server, image).wait_until_finished(1000) # When we rebuild the server progress takes some more time. + self._mark_as_changed() + + self._get_server() + except Exception as e: + self.module.fail_json(msg=e.message) + + def present_server(self): + self._get_server() + if self.hcloud_server is None: + self._create_server() + else: + self._update_server() + + def delete_server(self): + try: + self._get_server() + if self.hcloud_server is not None: + if not self.module.check_mode: + self.client.servers.delete(self.hcloud_server).wait_until_finished() + self._mark_as_changed() + self.hcloud_server = None + except Exception as e: + self.module.fail_json(msg=e.message) + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + image={"type": "str"}, + server_type={"type": "str"}, + location={"type": "str"}, + datacenter={"type": "str"}, + user_data={"type": "str"}, + ssh_keys={"type": "list", "elements": "str", "no_log": False}, + volumes={"type": "list", "elements": "str"}, + firewalls={"type": "list", "elements": "str"}, + labels={"type": "dict"}, + backups={"type": "bool"}, + upgrade_disk={"type": "bool", "default": False}, + enable_ipv4={"type": "bool", "default": True}, + enable_ipv6={"type": "bool", "default": True}, + ipv4={"type": "str"}, + ipv6={"type": "str"}, + private_networks={"type": "list", "elements": "str", "default": None}, + force={"type": "bool", "default": False}, + force_upgrade={"type": "bool", "default": False}, + allow_deprecated_image={"type": "bool", "default": False}, + rescue_mode={"type": "str"}, + delete_protection={"type": "bool"}, + rebuild_protection={"type": "bool"}, + placement_group={"type": "str"}, + state={ + "choices": ["absent", "present", "restarted", "started", "stopped", "rebuild"], + "default": "present", + }, + **Hcloud.base_module_arguments() + ), + required_one_of=[['id', 'name']], + mutually_exclusive=[["location", "datacenter"]], + required_together=[["delete_protection", "rebuild_protection"]], + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHcloudServer.define_module() + + hcloud = AnsibleHcloudServer(module) + state = module.params.get("state") + if state == "absent": + hcloud.delete_server() + elif state == "present": + hcloud.present_server() + elif state == "started": + hcloud.present_server() + hcloud.start_server() + elif state == "stopped": + hcloud.present_server() + hcloud.stop_server() + elif state == "restarted": + hcloud.present_server() + hcloud.stop_server() + hcloud.start_server() + elif state == "rebuild": + hcloud.present_server() + hcloud.rebuild_server() + + module.exit_json(**hcloud.get_result()) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_facts.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_facts.py new file mode 100644 index 000000000..102ceec0d --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_facts.py @@ -0,0 +1,244 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: hcloud_server_info + +short_description: Gather infos about your Hetzner Cloud servers. + + +description: + - Gather infos about your Hetzner Cloud servers. + - This module was called C(hcloud_server_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_server_facts). + Note that the M(hetzner.hcloud.hcloud_server_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_server_info)! + +author: + - Lukas Kaemmerling (@LKaemmerling) + +options: + id: + description: + - The ID of the server you want to get. + type: int + name: + description: + - The name of the server you want to get. + type: str + label_selector: + description: + - The label selector for the server you want to get. + type: str +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +''' + +EXAMPLES = """ +- name: Gather hcloud server infos + hcloud_server_info: + register: output + +- name: Print the gathered infos + debug: + var: output.hcloud_server_info +""" + +RETURN = """ +hcloud_server_info: + description: The server infos as list + returned: always + type: complex + contains: + id: + description: Numeric identifier of the server + returned: always + type: int + sample: 1937415 + name: + description: Name of the server + returned: always + type: str + sample: my-server + status: + description: Status of the server + returned: always + type: str + sample: running + server_type: + description: Name of the server type of the server + returned: always + type: str + sample: cx11 + ipv4_address: + description: Public IPv4 address of the server + returned: always + type: str + sample: 116.203.104.109 + ipv6: + description: IPv6 network of the server + returned: always + type: str + sample: 2a01:4f8:1c1c:c140::/64 + private_networks: + description: List of private networks the server is attached to (name) + returned: always + type: list + elements: str + sample: ['my-network', 'another-network'] + private_networks_info: + description: List of private networks the server is attached to (dict with name and ip) + returned: always + type: list + elements: dict + sample: [{'name': 'my-network', 'ip': '192.168.1.1'}, {'name': 'another-network', 'ip': '10.185.50.40'}] + location: + description: Name of the location of the server + returned: always + type: str + sample: fsn1 + placement_group: + description: Placement Group of the server + type: str + returned: always + sample: 4711 + version_added: "1.5.0" + datacenter: + description: Name of the datacenter of the server + returned: always + type: str + sample: fsn1-dc14 + rescue_enabled: + description: True if rescue mode is enabled, Server will then boot into rescue system on next reboot + returned: always + type: bool + sample: false + backup_window: + description: Time window (UTC) in which the backup will run, or null if the backups are not enabled + returned: always + type: bool + sample: 22-02 + labels: + description: User-defined labels (key-value pairs) + returned: always + type: dict + delete_protection: + description: True if server is protected for deletion + type: bool + returned: always + sample: false + version_added: "0.1.0" + rebuild_protection: + description: True if server is protected for rebuild + type: bool + returned: always + sample: false + version_added: "0.1.0" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud + + +class AnsibleHcloudServerInfo(Hcloud): + def __init__(self, module): + Hcloud.__init__(self, module, "hcloud_server_info") + self.hcloud_server_info = None + + def _prepare_result(self): + tmp = [] + + for server in self.hcloud_server_info: + if server is not None: + image = None if server.image is None else to_native(server.image.name) + placement_group = None if server.placement_group is None else to_native(server.placement_group.name) + ipv4_address = None if server.public_net.ipv4 is None else to_native(server.public_net.ipv4.ip) + ipv6 = None if server.public_net.ipv6 is None else to_native(server.public_net.ipv6.ip) + backup_window = None if server.backup_window is None else to_native(server.backup_window) + tmp.append({ + "id": to_native(server.id), + "name": to_native(server.name), + "ipv4_address": ipv4_address, + "ipv6": ipv6, + "private_networks": [to_native(net.network.name) for net in server.private_net], + "private_networks_info": [{"name": to_native(net.network.name), "ip": net.ip} for net in server.private_net], + "image": image, + "server_type": to_native(server.server_type.name), + "datacenter": to_native(server.datacenter.name), + "location": to_native(server.datacenter.location.name), + "placement_group": placement_group, + "rescue_enabled": server.rescue_enabled, + "backup_window": backup_window, + "labels": server.labels, + "status": to_native(server.status), + "delete_protection": server.protection["delete"], + "rebuild_protection": server.protection["rebuild"], + }) + return tmp + + def get_servers(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_server_info = [self.client.servers.get_by_id( + self.module.params.get("id") + )] + elif self.module.params.get("name") is not None: + self.hcloud_server_info = [self.client.servers.get_by_name( + self.module.params.get("name") + )] + elif self.module.params.get("label_selector") is not None: + self.hcloud_server_info = self.client.servers.get_all( + label_selector=self.module.params.get("label_selector")) + else: + self.hcloud_server_info = self.client.servers.get_all() + + except Exception as e: + self.module.fail_json(msg=e.message) + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + label_selector={"type": "str"}, + **Hcloud.base_module_arguments() + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHcloudServerInfo.define_module() + + is_old_facts = module._name == 'hcloud_server_facts' + if is_old_facts: + module.deprecate("The 'hcloud_server_facts' module has been renamed to 'hcloud_server_info', " + "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud") + + hcloud = AnsibleHcloudServerInfo(module) + hcloud.get_servers() + result = hcloud.get_result() + + if is_old_facts: + ansible_info = { + 'hcloud_server_facts': result['hcloud_server_info'] + } + module.exit_json(ansible_facts=ansible_info) + else: + ansible_info = { + 'hcloud_server_info': result['hcloud_server_info'] + } + module.exit_json(**ansible_info) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_info.py new file mode 100644 index 000000000..102ceec0d --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_info.py @@ -0,0 +1,244 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: hcloud_server_info + +short_description: Gather infos about your Hetzner Cloud servers. + + +description: + - Gather infos about your Hetzner Cloud servers. + - This module was called C(hcloud_server_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_server_facts). + Note that the M(hetzner.hcloud.hcloud_server_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_server_info)! + +author: + - Lukas Kaemmerling (@LKaemmerling) + +options: + id: + description: + - The ID of the server you want to get. + type: int + name: + description: + - The name of the server you want to get. + type: str + label_selector: + description: + - The label selector for the server you want to get. + type: str +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +''' + +EXAMPLES = """ +- name: Gather hcloud server infos + hcloud_server_info: + register: output + +- name: Print the gathered infos + debug: + var: output.hcloud_server_info +""" + +RETURN = """ +hcloud_server_info: + description: The server infos as list + returned: always + type: complex + contains: + id: + description: Numeric identifier of the server + returned: always + type: int + sample: 1937415 + name: + description: Name of the server + returned: always + type: str + sample: my-server + status: + description: Status of the server + returned: always + type: str + sample: running + server_type: + description: Name of the server type of the server + returned: always + type: str + sample: cx11 + ipv4_address: + description: Public IPv4 address of the server + returned: always + type: str + sample: 116.203.104.109 + ipv6: + description: IPv6 network of the server + returned: always + type: str + sample: 2a01:4f8:1c1c:c140::/64 + private_networks: + description: List of private networks the server is attached to (name) + returned: always + type: list + elements: str + sample: ['my-network', 'another-network'] + private_networks_info: + description: List of private networks the server is attached to (dict with name and ip) + returned: always + type: list + elements: dict + sample: [{'name': 'my-network', 'ip': '192.168.1.1'}, {'name': 'another-network', 'ip': '10.185.50.40'}] + location: + description: Name of the location of the server + returned: always + type: str + sample: fsn1 + placement_group: + description: Placement Group of the server + type: str + returned: always + sample: 4711 + version_added: "1.5.0" + datacenter: + description: Name of the datacenter of the server + returned: always + type: str + sample: fsn1-dc14 + rescue_enabled: + description: True if rescue mode is enabled, Server will then boot into rescue system on next reboot + returned: always + type: bool + sample: false + backup_window: + description: Time window (UTC) in which the backup will run, or null if the backups are not enabled + returned: always + type: bool + sample: 22-02 + labels: + description: User-defined labels (key-value pairs) + returned: always + type: dict + delete_protection: + description: True if server is protected for deletion + type: bool + returned: always + sample: false + version_added: "0.1.0" + rebuild_protection: + description: True if server is protected for rebuild + type: bool + returned: always + sample: false + version_added: "0.1.0" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud + + +class AnsibleHcloudServerInfo(Hcloud): + def __init__(self, module): + Hcloud.__init__(self, module, "hcloud_server_info") + self.hcloud_server_info = None + + def _prepare_result(self): + tmp = [] + + for server in self.hcloud_server_info: + if server is not None: + image = None if server.image is None else to_native(server.image.name) + placement_group = None if server.placement_group is None else to_native(server.placement_group.name) + ipv4_address = None if server.public_net.ipv4 is None else to_native(server.public_net.ipv4.ip) + ipv6 = None if server.public_net.ipv6 is None else to_native(server.public_net.ipv6.ip) + backup_window = None if server.backup_window is None else to_native(server.backup_window) + tmp.append({ + "id": to_native(server.id), + "name": to_native(server.name), + "ipv4_address": ipv4_address, + "ipv6": ipv6, + "private_networks": [to_native(net.network.name) for net in server.private_net], + "private_networks_info": [{"name": to_native(net.network.name), "ip": net.ip} for net in server.private_net], + "image": image, + "server_type": to_native(server.server_type.name), + "datacenter": to_native(server.datacenter.name), + "location": to_native(server.datacenter.location.name), + "placement_group": placement_group, + "rescue_enabled": server.rescue_enabled, + "backup_window": backup_window, + "labels": server.labels, + "status": to_native(server.status), + "delete_protection": server.protection["delete"], + "rebuild_protection": server.protection["rebuild"], + }) + return tmp + + def get_servers(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_server_info = [self.client.servers.get_by_id( + self.module.params.get("id") + )] + elif self.module.params.get("name") is not None: + self.hcloud_server_info = [self.client.servers.get_by_name( + self.module.params.get("name") + )] + elif self.module.params.get("label_selector") is not None: + self.hcloud_server_info = self.client.servers.get_all( + label_selector=self.module.params.get("label_selector")) + else: + self.hcloud_server_info = self.client.servers.get_all() + + except Exception as e: + self.module.fail_json(msg=e.message) + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + label_selector={"type": "str"}, + **Hcloud.base_module_arguments() + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHcloudServerInfo.define_module() + + is_old_facts = module._name == 'hcloud_server_facts' + if is_old_facts: + module.deprecate("The 'hcloud_server_facts' module has been renamed to 'hcloud_server_info', " + "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud") + + hcloud = AnsibleHcloudServerInfo(module) + hcloud.get_servers() + result = hcloud.get_result() + + if is_old_facts: + ansible_info = { + 'hcloud_server_facts': result['hcloud_server_info'] + } + module.exit_json(ansible_facts=ansible_info) + else: + ansible_info = { + 'hcloud_server_info': result['hcloud_server_info'] + } + module.exit_json(**ansible_info) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_network.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_network.py new file mode 100644 index 000000000..79f6838fd --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_network.py @@ -0,0 +1,246 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: hcloud_server_network + +short_description: Manage the relationship between Hetzner Cloud Networks and servers + + +description: + - Create and delete the relationship Hetzner Cloud Networks and servers + +author: + - Lukas Kaemmerling (@lkaemmerling) + +options: + network: + description: + - The name of the Hetzner Cloud Networks. + type: str + required: true + server: + description: + - The name of the Hetzner Cloud server. + type: str + required: true + ip: + description: + - The IP the server should have. + type: str + alias_ips: + description: + - Alias IPs the server has. + type: list + elements: str + state: + description: + - State of the server_network. + default: present + choices: [ absent, present ] + type: str + +requirements: + - hcloud-python >= 1.3.0 + +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +''' + +EXAMPLES = """ +- name: Create a basic server network + hcloud_server_network: + network: my-network + server: my-server + state: present + +- name: Create a server network and specify the ip address + hcloud_server_network: + network: my-network + server: my-server + ip: 10.0.0.1 + state: present + +- name: Create a server network and add alias ips + hcloud_server_network: + network: my-network + server: my-server + ip: 10.0.0.1 + alias_ips: + - 10.1.0.1 + - 10.2.0.1 + state: present + +- name: Ensure the server network is absent (remove if needed) + hcloud_server_network: + network: my-network + server: my-server + state: absent +""" + +RETURN = """ +hcloud_server_network: + description: The relationship between a server and a network + returned: always + type: complex + contains: + network: + description: Name of the Network + type: str + returned: always + sample: my-network + server: + description: Name of the server + type: str + returned: always + sample: my-server + ip: + description: IP of the server within the Network ip range + type: str + returned: always + sample: 10.0.0.8 + alias_ips: + description: Alias IPs of the server within the Network ip range + type: str + returned: always + sample: [10.1.0.1, ...] +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud + +try: + from hcloud import APIException +except ImportError: + APIException = None + + +class AnsibleHcloudServerNetwork(Hcloud): + def __init__(self, module): + Hcloud.__init__(self, module, "hcloud_server_network") + self.hcloud_network = None + self.hcloud_server = None + self.hcloud_server_network = None + + def _prepare_result(self): + return { + "network": to_native(self.hcloud_network.name), + "server": to_native(self.hcloud_server.name), + "ip": to_native(self.hcloud_server_network.ip), + "alias_ips": self.hcloud_server_network.alias_ips, + } + + def _get_server_and_network(self): + try: + self.hcloud_network = self.client.networks.get_by_name(self.module.params.get("network")) + self.hcloud_server = self.client.servers.get_by_name(self.module.params.get("server")) + self.hcloud_server_network = None + except Exception as e: + self.module.fail_json(msg=e.message) + + def _get_server_network(self): + for privateNet in self.hcloud_server.private_net: + if privateNet.network.id == self.hcloud_network.id: + self.hcloud_server_network = privateNet + + def _create_server_network(self): + params = { + "network": self.hcloud_network + } + + if self.module.params.get("ip") is not None: + params["ip"] = self.module.params.get("ip") + if self.module.params.get("alias_ips") is not None: + params["alias_ips"] = self.module.params.get("alias_ips") + + if not self.module.check_mode: + try: + self.hcloud_server.attach_to_network(**params).wait_until_finished() + except Exception as e: + self.module.fail_json(msg=e.message) + + self._mark_as_changed() + self._get_server_and_network() + self._get_server_network() + + def _update_server_network(self): + params = { + "network": self.hcloud_network + } + alias_ips = self.module.params.get("alias_ips") + if alias_ips is not None and sorted(self.hcloud_server_network.alias_ips) != sorted(alias_ips): + params["alias_ips"] = alias_ips + + if not self.module.check_mode: + try: + self.hcloud_server.change_alias_ips(**params).wait_until_finished() + except APIException as e: + self.module.fail_json(msg=e.message) + + self._mark_as_changed() + self._get_server_and_network() + self._get_server_network() + + def present_server_network(self): + self._get_server_and_network() + self._get_server_network() + if self.hcloud_server_network is None: + self._create_server_network() + else: + self._update_server_network() + + def delete_server_network(self): + self._get_server_and_network() + self._get_server_network() + if self.hcloud_server_network is not None and self.hcloud_server is not None: + if not self.module.check_mode: + try: + self.hcloud_server.detach_from_network(self.hcloud_server_network.network).wait_until_finished() + except Exception as e: + self.module.fail_json(msg=e.message) + self._mark_as_changed() + self.hcloud_server_network = None + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + network={"type": "str", "required": True}, + server={"type": "str", "required": True}, + ip={"type": "str"}, + alias_ips={"type": "list", "elements": "str"}, + state={ + "choices": ["absent", "present"], + "default": "present", + }, + **Hcloud.base_module_arguments() + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHcloudServerNetwork.define_module() + + hcloud = AnsibleHcloudServerNetwork(module) + state = module.params["state"] + if state == "absent": + hcloud.delete_server_network() + elif state == "present": + hcloud.present_server_network() + + module.exit_json(**hcloud.get_result()) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_type_facts.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_type_facts.py new file mode 100644 index 000000000..a84067c32 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_type_facts.py @@ -0,0 +1,183 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: hcloud_server_type_info + +short_description: Gather infos about the Hetzner Cloud server types. + + +description: + - Gather infos about your Hetzner Cloud server types. + - This module was called C(hcloud_server_type_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_server_type_facts). + Note that the M(hetzner.hcloud.hcloud_server_type_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_server_type_info)! + +author: + - Lukas Kaemmerling (@LKaemmerling) + +options: + id: + description: + - The ID of the server type you want to get. + type: int + name: + description: + - The name of the server type you want to get. + type: str +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +''' + +EXAMPLES = """ +- name: Gather hcloud server type infos + hcloud_server_type_info: + register: output + +- name: Print the gathered infos + debug: + var: output.hcloud_server_type_info +""" + +RETURN = """ +hcloud_server_type_info: + description: The server type infos as list + returned: always + type: complex + contains: + id: + description: Numeric identifier of the server type + returned: always + type: int + sample: 1937415 + name: + description: Name of the server type + returned: always + type: str + sample: fsn1 + description: + description: Detail description of the server type + returned: always + type: str + sample: Falkenstein DC Park 1 + cores: + description: Number of cpu cores a server of this type will have + returned: always + type: int + sample: 1 + memory: + description: Memory a server of this type will have in GB + returned: always + type: int + sample: 1 + disk: + description: Disk size a server of this type will have in GB + returned: always + type: int + sample: 25 + storage_type: + description: Type of server boot drive + returned: always + type: str + sample: local + cpu_type: + description: Type of cpu + returned: always + type: str + sample: shared + architecture: + description: Architecture of cpu + returned: always + type: str + sample: x86 +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud + + +class AnsibleHcloudServerTypeInfo(Hcloud): + def __init__(self, module): + Hcloud.__init__(self, module, "hcloud_server_type_info") + self.hcloud_server_type_info = None + + def _prepare_result(self): + tmp = [] + + for server_type in self.hcloud_server_type_info: + if server_type is not None: + tmp.append({ + "id": to_native(server_type.id), + "name": to_native(server_type.name), + "description": to_native(server_type.description), + "cores": server_type.cores, + "memory": server_type.memory, + "disk": server_type.disk, + "storage_type": to_native(server_type.storage_type), + "cpu_type": to_native(server_type.cpu_type), + "architecture": to_native(server_type.architecture) + }) + return tmp + + def get_server_types(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_server_type_info = [self.client.server_types.get_by_id( + self.module.params.get("id") + )] + elif self.module.params.get("name") is not None: + self.hcloud_server_type_info = [self.client.server_types.get_by_name( + self.module.params.get("name") + )] + else: + self.hcloud_server_type_info = self.client.server_types.get_all() + + except Exception as e: + self.module.fail_json(msg=e.message) + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + **Hcloud.base_module_arguments() + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHcloudServerTypeInfo.define_module() + + is_old_facts = module._name == 'hcloud_server_type_facts' + if is_old_facts: + module.deprecate("The 'hcloud_server_type_info' module has been renamed to 'hcloud_server_type_info', " + "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud") + + hcloud = AnsibleHcloudServerTypeInfo(module) + hcloud.get_server_types() + result = hcloud.get_result() + if is_old_facts: + ansible_info = { + 'hcloud_server_type_info': result['hcloud_server_type_info'] + } + module.exit_json(ansible_facts=ansible_info) + else: + ansible_info = { + 'hcloud_server_type_info': result['hcloud_server_type_info'] + } + module.exit_json(**ansible_info) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_type_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_type_info.py new file mode 100644 index 000000000..a84067c32 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_type_info.py @@ -0,0 +1,183 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: hcloud_server_type_info + +short_description: Gather infos about the Hetzner Cloud server types. + + +description: + - Gather infos about your Hetzner Cloud server types. + - This module was called C(hcloud_server_type_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_server_type_facts). + Note that the M(hetzner.hcloud.hcloud_server_type_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_server_type_info)! + +author: + - Lukas Kaemmerling (@LKaemmerling) + +options: + id: + description: + - The ID of the server type you want to get. + type: int + name: + description: + - The name of the server type you want to get. + type: str +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +''' + +EXAMPLES = """ +- name: Gather hcloud server type infos + hcloud_server_type_info: + register: output + +- name: Print the gathered infos + debug: + var: output.hcloud_server_type_info +""" + +RETURN = """ +hcloud_server_type_info: + description: The server type infos as list + returned: always + type: complex + contains: + id: + description: Numeric identifier of the server type + returned: always + type: int + sample: 1937415 + name: + description: Name of the server type + returned: always + type: str + sample: fsn1 + description: + description: Detail description of the server type + returned: always + type: str + sample: Falkenstein DC Park 1 + cores: + description: Number of cpu cores a server of this type will have + returned: always + type: int + sample: 1 + memory: + description: Memory a server of this type will have in GB + returned: always + type: int + sample: 1 + disk: + description: Disk size a server of this type will have in GB + returned: always + type: int + sample: 25 + storage_type: + description: Type of server boot drive + returned: always + type: str + sample: local + cpu_type: + description: Type of cpu + returned: always + type: str + sample: shared + architecture: + description: Architecture of cpu + returned: always + type: str + sample: x86 +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud + + +class AnsibleHcloudServerTypeInfo(Hcloud): + def __init__(self, module): + Hcloud.__init__(self, module, "hcloud_server_type_info") + self.hcloud_server_type_info = None + + def _prepare_result(self): + tmp = [] + + for server_type in self.hcloud_server_type_info: + if server_type is not None: + tmp.append({ + "id": to_native(server_type.id), + "name": to_native(server_type.name), + "description": to_native(server_type.description), + "cores": server_type.cores, + "memory": server_type.memory, + "disk": server_type.disk, + "storage_type": to_native(server_type.storage_type), + "cpu_type": to_native(server_type.cpu_type), + "architecture": to_native(server_type.architecture) + }) + return tmp + + def get_server_types(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_server_type_info = [self.client.server_types.get_by_id( + self.module.params.get("id") + )] + elif self.module.params.get("name") is not None: + self.hcloud_server_type_info = [self.client.server_types.get_by_name( + self.module.params.get("name") + )] + else: + self.hcloud_server_type_info = self.client.server_types.get_all() + + except Exception as e: + self.module.fail_json(msg=e.message) + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + **Hcloud.base_module_arguments() + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHcloudServerTypeInfo.define_module() + + is_old_facts = module._name == 'hcloud_server_type_facts' + if is_old_facts: + module.deprecate("The 'hcloud_server_type_info' module has been renamed to 'hcloud_server_type_info', " + "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud") + + hcloud = AnsibleHcloudServerTypeInfo(module) + hcloud.get_server_types() + result = hcloud.get_result() + if is_old_facts: + ansible_info = { + 'hcloud_server_type_info': result['hcloud_server_type_info'] + } + module.exit_json(ansible_facts=ansible_info) + else: + ansible_info = { + 'hcloud_server_type_info': result['hcloud_server_type_info'] + } + module.exit_json(**ansible_info) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_ssh_key.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_ssh_key.py new file mode 100644 index 000000000..59a5197f5 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_ssh_key.py @@ -0,0 +1,243 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: hcloud_ssh_key + +short_description: Create and manage ssh keys on the Hetzner Cloud. + + +description: + - Create, update and manage ssh keys on the Hetzner Cloud. + +author: + - Lukas Kaemmerling (@LKaemmerling) + +options: + id: + description: + - The ID of the Hetzner Cloud ssh_key to manage. + - Only required if no ssh_key I(name) is given + type: int + name: + description: + - The Name of the Hetzner Cloud ssh_key to manage. + - Only required if no ssh_key I(id) is given or a ssh_key does not exist. + type: str + fingerprint: + description: + - The Fingerprint of the Hetzner Cloud ssh_key to manage. + - Only required if no ssh_key I(id) or I(name) is given. + type: str + labels: + description: + - User-defined labels (key-value pairs) + type: dict + public_key: + description: + - The Public Key to add. + - Required if ssh_key does not exist. + type: str + state: + description: + - State of the ssh_key. + default: present + choices: [ absent, present ] + type: str +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +''' + +EXAMPLES = """ +- name: Create a basic ssh_key + hcloud_ssh_key: + name: my-ssh_key + public_key: "ssh-rsa AAAjjk76kgf...Xt" + state: present + +- name: Create a ssh_key with labels + hcloud_ssh_key: + name: my-ssh_key + public_key: "ssh-rsa AAAjjk76kgf...Xt" + labels: + key: value + mylabel: 123 + state: present + +- name: Ensure the ssh_key is absent (remove if needed) + hcloud_ssh_key: + name: my-ssh_key + state: absent +""" + +RETURN = """ +hcloud_ssh_key: + description: The ssh_key instance + returned: Always + type: complex + contains: + id: + description: ID of the ssh_key + type: int + returned: Always + sample: 12345 + name: + description: Name of the ssh_key + type: str + returned: Always + sample: my-ssh-key + fingerprint: + description: Fingerprint of the ssh_key + type: str + returned: Always + sample: b7:2f:30:a0:2f:6c:58:6c:21:04:58:61:ba:06:3b:2f + public_key: + description: Public key of the ssh_key + type: str + returned: Always + sample: "ssh-rsa AAAjjk76kgf...Xt" + labels: + description: User-defined labels (key-value pairs) + type: dict + returned: Always + sample: + key: value + mylabel: 123 +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud + + +class AnsibleHcloudSSHKey(Hcloud): + def __init__(self, module): + Hcloud.__init__(self, module, "hcloud_ssh_key") + self.hcloud_ssh_key = None + + def _prepare_result(self): + return { + "id": to_native(self.hcloud_ssh_key.id), + "name": to_native(self.hcloud_ssh_key.name), + "fingerprint": to_native(self.hcloud_ssh_key.fingerprint), + "public_key": to_native(self.hcloud_ssh_key.public_key), + "labels": self.hcloud_ssh_key.labels, + } + + def _get_ssh_key(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_ssh_key = self.client.ssh_keys.get_by_id( + self.module.params.get("id") + ) + elif self.module.params.get("fingerprint") is not None: + self.hcloud_ssh_key = self.client.ssh_keys.get_by_fingerprint( + self.module.params.get("fingerprint") + ) + elif self.module.params.get("name") is not None: + self.hcloud_ssh_key = self.client.ssh_keys.get_by_name( + self.module.params.get("name") + ) + + except Exception as e: + self.module.fail_json(msg=e.message) + + def _create_ssh_key(self): + self.module.fail_on_missing_params( + required_params=["name", "public_key"] + ) + params = { + "name": self.module.params.get("name"), + "public_key": self.module.params.get("public_key"), + "labels": self.module.params.get("labels") + } + + if not self.module.check_mode: + try: + self.client.ssh_keys.create(**params) + except Exception as e: + self.module.fail_json(msg=e.message) + self._mark_as_changed() + self._get_ssh_key() + + def _update_ssh_key(self): + name = self.module.params.get("name") + if name is not None and self.hcloud_ssh_key.name != name: + self.module.fail_on_missing_params( + required_params=["id"] + ) + if not self.module.check_mode: + self.hcloud_ssh_key.update(name=name) + self._mark_as_changed() + + labels = self.module.params.get("labels") + if labels is not None and self.hcloud_ssh_key.labels != labels: + if not self.module.check_mode: + self.hcloud_ssh_key.update(labels=labels) + self._mark_as_changed() + + self._get_ssh_key() + + def present_ssh_key(self): + self._get_ssh_key() + if self.hcloud_ssh_key is None: + self._create_ssh_key() + else: + self._update_ssh_key() + + def delete_ssh_key(self): + self._get_ssh_key() + if self.hcloud_ssh_key is not None: + if not self.module.check_mode: + try: + self.client.ssh_keys.delete(self.hcloud_ssh_key) + except Exception as e: + self.module.fail_json(msg=e.message) + self._mark_as_changed() + self.hcloud_ssh_key = None + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + public_key={"type": "str"}, + fingerprint={"type": "str"}, + labels={"type": "dict"}, + state={ + "choices": ["absent", "present"], + "default": "present", + }, + **Hcloud.base_module_arguments() + ), + required_one_of=[['id', 'name', 'fingerprint']], + required_if=[['state', 'present', ['name']]], + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHcloudSSHKey.define_module() + + hcloud = AnsibleHcloudSSHKey(module) + state = module.params.get("state") + if state == "absent": + hcloud.delete_ssh_key() + elif state == "present": + hcloud.present_ssh_key() + + module.exit_json(**hcloud.get_result()) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_ssh_key_facts.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_ssh_key_facts.py new file mode 100644 index 000000000..aab98ed60 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_ssh_key_facts.py @@ -0,0 +1,169 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: hcloud_ssh_key_info +short_description: Gather infos about your Hetzner Cloud ssh_keys. +description: + - Gather facts about your Hetzner Cloud ssh_keys. + - This module was called C(hcloud_ssh_key_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_ssh_key_facts). + Note that the M(hetzner.hcloud.hcloud_ssh_key_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_ssh_key_info)! +author: + - Christopher Schmitt (@cschmitt-hcloud) +options: + id: + description: + - The ID of the ssh key you want to get. + type: int + name: + description: + - The name of the ssh key you want to get. + type: str + fingerprint: + description: + - The fingerprint of the ssh key you want to get. + type: str + label_selector: + description: + - The label selector for the ssh key you want to get. + type: str +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +''' + +EXAMPLES = """ +- name: Gather hcloud sshkey infos + hcloud_ssh_key_info: + register: output +- name: Print the gathered infos + debug: + var: output.hcloud_ssh_key_info +""" + +RETURN = """ +hcloud_ssh_key_info: + description: The ssh key instances + returned: Always + type: complex + contains: + id: + description: Numeric identifier of the ssh_key + returned: always + type: int + sample: 1937415 + name: + description: Name of the ssh_key + returned: always + type: str + sample: my-ssh-key + fingerprint: + description: Fingerprint of the ssh key + returned: always + type: str + sample: 0e:e0:bd:c7:2d:1f:69:49:94:44:91:f1:19:fd:35:f3 + public_key: + description: The actual public key + returned: always + type: str + sample: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGpl/tnk74nnQJxxLAtutUApUZMRJxryKh7VXkNbd4g9 john@example.com" + labels: + description: User-defined labels (key-value pairs) + returned: always + type: dict +""" +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud + + +class AnsibleHcloudSSHKeyInfo(Hcloud): + def __init__(self, module): + Hcloud.__init__(self, module, "hcloud_ssh_key_info") + self.hcloud_ssh_key_info = None + + def _prepare_result(self): + ssh_keys = [] + + for ssh_key in self.hcloud_ssh_key_info: + if ssh_key: + ssh_keys.append({ + "id": to_native(ssh_key.id), + "name": to_native(ssh_key.name), + "fingerprint": to_native(ssh_key.fingerprint), + "public_key": to_native(ssh_key.public_key), + "labels": ssh_key.labels + }) + return ssh_keys + + def get_ssh_keys(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_ssh_key_info = [self.client.ssh_keys.get_by_id( + self.module.params.get("id") + )] + elif self.module.params.get("name") is not None: + self.hcloud_ssh_key_info = [self.client.ssh_keys.get_by_name( + self.module.params.get("name") + )] + elif self.module.params.get("fingerprint") is not None: + self.hcloud_ssh_key_info = [self.client.ssh_keys.get_by_fingerprint( + self.module.params.get("fingerprint") + )] + elif self.module.params.get("label_selector") is not None: + self.hcloud_ssh_key_info = self.client.ssh_keys.get_all( + label_selector=self.module.params.get("label_selector")) + else: + self.hcloud_ssh_key_info = self.client.ssh_keys.get_all() + + except Exception as e: + self.module.fail_json(msg=e.message) + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + fingerprint={"type": "str"}, + label_selector={"type": "str"}, + **Hcloud.base_module_arguments() + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHcloudSSHKeyInfo.define_module() + + is_old_facts = module._name == 'hcloud_ssh_key_facts' + if is_old_facts: + module.deprecate("The 'hcloud_ssh_key_facts' module has been renamed to 'hcloud_ssh_key_info', " + "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud") + + hcloud = AnsibleHcloudSSHKeyInfo(module) + hcloud.get_ssh_keys() + result = hcloud.get_result() + + if is_old_facts: + ansible_info = { + 'hcloud_ssh_key_facts': result['hcloud_ssh_key_info'] + } + module.exit_json(ansible_facts=ansible_info) + else: + ansible_info = { + 'hcloud_ssh_key_info': result['hcloud_ssh_key_info'] + } + module.exit_json(**ansible_info) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_ssh_key_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_ssh_key_info.py new file mode 100644 index 000000000..aab98ed60 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_ssh_key_info.py @@ -0,0 +1,169 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: hcloud_ssh_key_info +short_description: Gather infos about your Hetzner Cloud ssh_keys. +description: + - Gather facts about your Hetzner Cloud ssh_keys. + - This module was called C(hcloud_ssh_key_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_ssh_key_facts). + Note that the M(hetzner.hcloud.hcloud_ssh_key_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_ssh_key_info)! +author: + - Christopher Schmitt (@cschmitt-hcloud) +options: + id: + description: + - The ID of the ssh key you want to get. + type: int + name: + description: + - The name of the ssh key you want to get. + type: str + fingerprint: + description: + - The fingerprint of the ssh key you want to get. + type: str + label_selector: + description: + - The label selector for the ssh key you want to get. + type: str +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +''' + +EXAMPLES = """ +- name: Gather hcloud sshkey infos + hcloud_ssh_key_info: + register: output +- name: Print the gathered infos + debug: + var: output.hcloud_ssh_key_info +""" + +RETURN = """ +hcloud_ssh_key_info: + description: The ssh key instances + returned: Always + type: complex + contains: + id: + description: Numeric identifier of the ssh_key + returned: always + type: int + sample: 1937415 + name: + description: Name of the ssh_key + returned: always + type: str + sample: my-ssh-key + fingerprint: + description: Fingerprint of the ssh key + returned: always + type: str + sample: 0e:e0:bd:c7:2d:1f:69:49:94:44:91:f1:19:fd:35:f3 + public_key: + description: The actual public key + returned: always + type: str + sample: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGpl/tnk74nnQJxxLAtutUApUZMRJxryKh7VXkNbd4g9 john@example.com" + labels: + description: User-defined labels (key-value pairs) + returned: always + type: dict +""" +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud + + +class AnsibleHcloudSSHKeyInfo(Hcloud): + def __init__(self, module): + Hcloud.__init__(self, module, "hcloud_ssh_key_info") + self.hcloud_ssh_key_info = None + + def _prepare_result(self): + ssh_keys = [] + + for ssh_key in self.hcloud_ssh_key_info: + if ssh_key: + ssh_keys.append({ + "id": to_native(ssh_key.id), + "name": to_native(ssh_key.name), + "fingerprint": to_native(ssh_key.fingerprint), + "public_key": to_native(ssh_key.public_key), + "labels": ssh_key.labels + }) + return ssh_keys + + def get_ssh_keys(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_ssh_key_info = [self.client.ssh_keys.get_by_id( + self.module.params.get("id") + )] + elif self.module.params.get("name") is not None: + self.hcloud_ssh_key_info = [self.client.ssh_keys.get_by_name( + self.module.params.get("name") + )] + elif self.module.params.get("fingerprint") is not None: + self.hcloud_ssh_key_info = [self.client.ssh_keys.get_by_fingerprint( + self.module.params.get("fingerprint") + )] + elif self.module.params.get("label_selector") is not None: + self.hcloud_ssh_key_info = self.client.ssh_keys.get_all( + label_selector=self.module.params.get("label_selector")) + else: + self.hcloud_ssh_key_info = self.client.ssh_keys.get_all() + + except Exception as e: + self.module.fail_json(msg=e.message) + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + fingerprint={"type": "str"}, + label_selector={"type": "str"}, + **Hcloud.base_module_arguments() + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHcloudSSHKeyInfo.define_module() + + is_old_facts = module._name == 'hcloud_ssh_key_facts' + if is_old_facts: + module.deprecate("The 'hcloud_ssh_key_facts' module has been renamed to 'hcloud_ssh_key_info', " + "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud") + + hcloud = AnsibleHcloudSSHKeyInfo(module) + hcloud.get_ssh_keys() + result = hcloud.get_result() + + if is_old_facts: + ansible_info = { + 'hcloud_ssh_key_facts': result['hcloud_ssh_key_info'] + } + module.exit_json(ansible_facts=ansible_info) + else: + ansible_info = { + 'hcloud_ssh_key_info': result['hcloud_ssh_key_info'] + } + module.exit_json(**ansible_info) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_subnetwork.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_subnetwork.py new file mode 100644 index 000000000..c2ba66d80 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_subnetwork.py @@ -0,0 +1,247 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: hcloud_subnetwork + +short_description: Manage cloud subnetworks on the Hetzner Cloud. + + +description: + - Create, update and delete cloud subnetworks on the Hetzner Cloud. + +author: + - Lukas Kaemmerling (@lkaemmerling) + +options: + network: + description: + - The ID or Name of the Hetzner Cloud Networks. + type: str + required: true + ip_range: + description: + - IP range of the subnetwork. + type: str + required: true + type: + description: + - Type of subnetwork. + type: str + choices: [ server, cloud, vswitch ] + required: true + network_zone: + description: + - Name of network zone. + type: str + required: true + vswitch_id: + description: + - ID of the vSwitch you want to couple with your Network. + - Required if type == vswitch + type: int + state: + description: + - State of the subnetwork. + default: present + choices: [ absent, present ] + type: str + +requirements: + - hcloud-python >= 1.10.0 + +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +''' + +EXAMPLES = """ +- name: Create a basic subnetwork + hcloud_subnetwork: + network: my-network + ip_range: 10.0.0.0/16 + network_zone: eu-central + type: cloud + state: present + +- name: Create a basic subnetwork + hcloud_subnetwork: + network: my-vswitch-network + ip_range: 10.0.0.0/24 + network_zone: eu-central + type: vswitch + vswitch_id: 123 + state: present + +- name: Ensure the subnetwork is absent (remove if needed) + hcloud_subnetwork: + network: my-network + ip_range: 10.0.0.0/8 + network_zone: eu-central + type: cloud + state: absent +""" + +RETURN = """ +hcloud_subnetwork: + description: One Subnet of a Network + returned: always + type: complex + contains: + network: + description: Name of the Network + type: str + returned: always + sample: my-network + ip_range: + description: IP range of the Network + type: str + returned: always + sample: 10.0.0.0/8 + type: + description: Type of subnetwork + type: str + returned: always + sample: server + network_zone: + description: Name of network zone + type: str + returned: always + sample: eu-central + vswitch_id: + description: ID of the vswitch, null if not type vswitch + type: int + returned: always + sample: 123 + gateway: + description: Gateway of the subnetwork + type: str + returned: always + sample: 10.0.0.1 +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud + +try: + from hcloud.networks.domain import NetworkSubnet +except ImportError: + NetworkSubnet = None + + +class AnsibleHcloudSubnetwork(Hcloud): + def __init__(self, module): + Hcloud.__init__(self, module, "hcloud_subnetwork") + self.hcloud_network = None + self.hcloud_subnetwork = None + + def _prepare_result(self): + return { + "network": to_native(self.hcloud_network.name), + "ip_range": to_native(self.hcloud_subnetwork.ip_range), + "type": to_native(self.hcloud_subnetwork.type), + "network_zone": to_native(self.hcloud_subnetwork.network_zone), + "gateway": self.hcloud_subnetwork.gateway, + "vswitch_id": self.hcloud_subnetwork.vswitch_id, + } + + def _get_network(self): + try: + self.hcloud_network = self.client.networks.get_by_name(self.module.params.get("network")) + self.hcloud_subnetwork = None + except Exception as e: + self.module.fail_json(msg=e.message) + + def _get_subnetwork(self): + subnet_ip_range = self.module.params.get("ip_range") + for subnetwork in self.hcloud_network.subnets: + if subnetwork.ip_range == subnet_ip_range: + self.hcloud_subnetwork = subnetwork + + def _create_subnetwork(self): + params = { + "ip_range": self.module.params.get("ip_range"), + "type": self.module.params.get('type'), + "network_zone": self.module.params.get('network_zone') + } + if self.module.params.get('type') == NetworkSubnet.TYPE_VSWITCH: + self.module.fail_on_missing_params( + required_params=["vswitch_id"] + ) + params["vswitch_id"] = self.module.params.get('vswitch_id') + + if not self.module.check_mode: + try: + self.hcloud_network.add_subnet(subnet=NetworkSubnet(**params)).wait_until_finished() + except Exception as e: + self.module.fail_json(msg=e.message) + + self._mark_as_changed() + self._get_network() + self._get_subnetwork() + + def present_subnetwork(self): + self._get_network() + self._get_subnetwork() + if self.hcloud_subnetwork is None: + self._create_subnetwork() + + def delete_subnetwork(self): + self._get_network() + self._get_subnetwork() + if self.hcloud_subnetwork is not None and self.hcloud_network is not None: + if not self.module.check_mode: + try: + self.hcloud_network.delete_subnet(self.hcloud_subnetwork).wait_until_finished() + except Exception as e: + self.module.fail_json(msg=e.message) + self._mark_as_changed() + self.hcloud_subnetwork = None + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + network={"type": "str", "required": True}, + network_zone={"type": "str", "required": True}, + type={ + "type": "str", + "required": True, + "choices": ["server", "cloud", "vswitch"] + }, + ip_range={"type": "str", "required": True}, + vswitch_id={"type": "int"}, + state={ + "choices": ["absent", "present"], + "default": "present", + }, + **Hcloud.base_module_arguments() + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHcloudSubnetwork.define_module() + + hcloud = AnsibleHcloudSubnetwork(module) + state = module.params["state"] + if state == "absent": + hcloud.delete_subnetwork() + elif state == "present": + hcloud.present_subnetwork() + + module.exit_json(**hcloud.get_result()) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_volume.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_volume.py new file mode 100644 index 000000000..623a399b4 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_volume.py @@ -0,0 +1,340 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: hcloud_volume + +short_description: Create and manage block Volume on the Hetzner Cloud. + + +description: + - Create, update and attach/detach block Volume on the Hetzner Cloud. + +author: + - Christopher Schmitt (@cschmitt-hcloud) + +options: + id: + description: + - The ID of the Hetzner Cloud Block Volume to manage. + - Only required if no volume I(name) is given + type: int + name: + description: + - The Name of the Hetzner Cloud Block Volume to manage. + - Only required if no volume I(id) is given or a volume does not exist. + type: str + size: + description: + - The size of the Block Volume in GB. + - Required if volume does not yet exists. + type: int + automount: + description: + - Automatically mount the Volume. + type: bool + default: False + format: + description: + - Automatically Format the volume on creation + - Can only be used in case the Volume does not exist. + type: str + choices: [xfs, ext4] + location: + description: + - Location of the Hetzner Cloud Volume. + - Required if no I(server) is given and Volume does not exist. + type: str + server: + description: + - Server Name the Volume should be assigned to. + - Required if no I(location) is given and Volume does not exist. + type: str + delete_protection: + description: + - Protect the Volume for deletion. + type: bool + labels: + description: + - User-defined key-value pairs. + type: dict + state: + description: + - State of the Volume. + default: present + choices: [absent, present] + type: str +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +''' + +EXAMPLES = """ +- name: Create a Volume + hcloud_volume: + name: my-volume + location: fsn1 + size: 100 + state: present +- name: Create a Volume and format it with ext4 + hcloud_volume: + name: my-volume + location: fsn + format: ext4 + size: 100 + state: present +- name: Mount a existing Volume and automount + hcloud_volume: + name: my-volume + server: my-server + automount: yes + state: present +- name: Mount a existing Volume and automount + hcloud_volume: + name: my-volume + server: my-server + automount: yes + state: present +- name: Ensure the Volume is absent (remove if needed) + hcloud_volume: + name: my-volume + state: absent +""" + +RETURN = """ +hcloud_volume: + description: The block Volume + returned: Always + type: complex + contains: + id: + description: ID of the Volume + type: int + returned: Always + sample: 12345 + name: + description: Name of the Volume + type: str + returned: Always + sample: my-volume + size: + description: Size in GB of the Volume + type: int + returned: Always + sample: 1337 + linux_device: + description: Path to the device that contains the Volume. + returned: always + type: str + sample: /dev/disk/by-id/scsi-0HC_Volume_12345 + version_added: "0.1.0" + location: + description: Location name where the Volume is located at + type: str + returned: Always + sample: "fsn1" + labels: + description: User-defined labels (key-value pairs) + type: dict + returned: Always + sample: + key: value + mylabel: 123 + server: + description: Server name where the Volume is attached to + type: str + returned: Always + sample: "my-server" + delete_protection: + description: True if Volume is protected for deletion + type: bool + returned: always + sample: false + version_added: "0.1.0" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud + + +class AnsibleHcloudVolume(Hcloud): + def __init__(self, module): + Hcloud.__init__(self, module, "hcloud_volume") + self.hcloud_volume = None + + def _prepare_result(self): + server_name = None + if self.hcloud_volume.server is not None: + server_name = to_native(self.hcloud_volume.server.name) + + return { + "id": to_native(self.hcloud_volume.id), + "name": to_native(self.hcloud_volume.name), + "size": self.hcloud_volume.size, + "location": to_native(self.hcloud_volume.location.name), + "labels": self.hcloud_volume.labels, + "server": server_name, + "linux_device": to_native(self.hcloud_volume.linux_device), + "delete_protection": self.hcloud_volume.protection["delete"], + } + + def _get_volume(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_volume = self.client.volumes.get_by_id( + self.module.params.get("id") + ) + else: + self.hcloud_volume = self.client.volumes.get_by_name( + self.module.params.get("name") + ) + except Exception as e: + self.module.fail_json(msg=e.message) + + def _create_volume(self): + self.module.fail_on_missing_params( + required_params=["name", "size"] + ) + params = { + "name": self.module.params.get("name"), + "size": self.module.params.get("size"), + "automount": self.module.params.get("automount"), + "format": self.module.params.get("format"), + "labels": self.module.params.get("labels") + } + if self.module.params.get("server") is not None: + params['server'] = self.client.servers.get_by_name(self.module.params.get("server")) + elif self.module.params.get("location") is not None: + params['location'] = self.client.locations.get_by_name(self.module.params.get("location")) + else: + self.module.fail_json(msg="server or location is required") + + if not self.module.check_mode: + try: + resp = self.client.volumes.create(**params) + resp.action.wait_until_finished() + [action.wait_until_finished() for action in resp.next_actions] + delete_protection = self.module.params.get("delete_protection") + if delete_protection is not None: + self._get_volume() + self.hcloud_volume.change_protection(delete=delete_protection).wait_until_finished() + except Exception as e: + self.module.fail_json(msg=e.message) + self._mark_as_changed() + self._get_volume() + + def _update_volume(self): + try: + size = self.module.params.get("size") + if size: + if self.hcloud_volume.size < size: + if not self.module.check_mode: + self.hcloud_volume.resize(size).wait_until_finished() + self._mark_as_changed() + elif self.hcloud_volume.size > size: + self.module.warn("Shrinking of volumes is not supported") + + server_name = self.module.params.get("server") + if server_name: + server = self.client.servers.get_by_name(server_name) + if self.hcloud_volume.server is None or self.hcloud_volume.server.name != server.name: + if not self.module.check_mode: + automount = self.module.params.get("automount", False) + self.hcloud_volume.attach(server, automount=automount).wait_until_finished() + self._mark_as_changed() + else: + if self.hcloud_volume.server is not None: + if not self.module.check_mode: + self.hcloud_volume.detach().wait_until_finished() + self._mark_as_changed() + + labels = self.module.params.get("labels") + if labels is not None and labels != self.hcloud_volume.labels: + if not self.module.check_mode: + self.hcloud_volume.update(labels=labels) + self._mark_as_changed() + + delete_protection = self.module.params.get("delete_protection") + if delete_protection is not None and delete_protection != self.hcloud_volume.protection["delete"]: + if not self.module.check_mode: + self.hcloud_volume.change_protection(delete=delete_protection).wait_until_finished() + self._mark_as_changed() + + self._get_volume() + except Exception as e: + self.module.fail_json(msg=e.message) + + def present_volume(self): + self._get_volume() + if self.hcloud_volume is None: + self._create_volume() + else: + self._update_volume() + + def delete_volume(self): + try: + self._get_volume() + if self.hcloud_volume is not None: + if not self.module.check_mode: + if self.hcloud_volume.server is not None: + self.hcloud_volume.detach().wait_until_finished() + self.client.volumes.delete(self.hcloud_volume) + self._mark_as_changed() + self.hcloud_volume = None + except Exception as e: + self.module.fail_json(msg=e.message) + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + size={"type": "int"}, + location={"type": "str"}, + server={"type": "str"}, + labels={"type": "dict"}, + automount={"type": "bool", "default": False}, + format={"type": "str", + "choices": ['xfs', 'ext4'], + }, + delete_protection={"type": "bool"}, + state={ + "choices": ["absent", "present"], + "default": "present", + }, + **Hcloud.base_module_arguments() + ), + required_one_of=[['id', 'name']], + mutually_exclusive=[["location", "server"]], + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHcloudVolume.define_module() + + hcloud = AnsibleHcloudVolume(module) + state = module.params.get("state") + if state == "absent": + module.fail_on_missing_params( + required_params=["name"] + ) + hcloud.delete_volume() + else: + hcloud.present_volume() + + module.exit_json(**hcloud.get_result()) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_volume_facts.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_volume_facts.py new file mode 100644 index 000000000..9520bfa14 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_volume_facts.py @@ -0,0 +1,186 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: hcloud_volume_info + +short_description: Gather infos about your Hetzner Cloud Volumes. + +description: + - Gather infos about your Hetzner Cloud Volumes. + +author: + - Lukas Kaemmerling (@LKaemmerling) + +options: + id: + description: + - The ID of the Volume you want to get. + type: int + name: + description: + - The name of the Volume you want to get. + type: str + label_selector: + description: + - The label selector for the Volume you want to get. + type: str +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +''' + +EXAMPLES = """ +- name: Gather hcloud Volume infos + hcloud_volume_info: + register: output +- name: Print the gathered infos + debug: + var: output.hcloud_volume_info +""" + +RETURN = """ +hcloud_volume_info: + description: The Volume infos as list + returned: always + type: complex + contains: + id: + description: Numeric identifier of the Volume + returned: always + type: int + sample: 1937415 + name: + description: Name of the Volume + returned: always + type: str + sample: my-volume + size: + description: Size of the Volume + returned: always + type: str + sample: 10 + linux_device: + description: Path to the device that contains the Volume. + returned: always + type: str + sample: /dev/disk/by-id/scsi-0HC_Volume_12345 + version_added: "0.1.0" + location: + description: Name of the location where the Volume resides in + returned: always + type: str + sample: fsn1 + server: + description: Name of the server where the Volume is attached to + returned: always + type: str + sample: my-server + delete_protection: + description: True if the Volume is protected for deletion + returned: always + type: bool + version_added: "0.1.0" + labels: + description: User-defined labels (key-value pairs) + returned: always + type: dict +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud + + +class AnsibleHcloudVolumeInfo(Hcloud): + def __init__(self, module): + Hcloud.__init__(self, module, "hcloud_volume_info") + self.hcloud_volume_info = None + + def _prepare_result(self): + tmp = [] + + for volume in self.hcloud_volume_info: + if volume is not None: + server_name = None + if volume.server is not None: + server_name = to_native(volume.server.name) + tmp.append({ + "id": to_native(volume.id), + "name": to_native(volume.name), + "size": volume.size, + "location": to_native(volume.location.name), + "labels": volume.labels, + "server": server_name, + "linux_device": to_native(volume.linux_device), + "delete_protection": volume.protection["delete"], + }) + + return tmp + + def get_volumes(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_volume_info = [self.client.volumes.get_by_id( + self.module.params.get("id") + )] + elif self.module.params.get("name") is not None: + self.hcloud_volume_info = [self.client.volumes.get_by_name( + self.module.params.get("name") + )] + elif self.module.params.get("label_selector") is not None: + self.hcloud_volume_info = self.client.volumes.get_all( + label_selector=self.module.params.get("label_selector")) + else: + self.hcloud_volume_info = self.client.volumes.get_all() + + except Exception as e: + self.module.fail_json(msg=e.message) + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + label_selector={"type": "str"}, + **Hcloud.base_module_arguments() + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHcloudVolumeInfo.define_module() + + is_old_facts = module._name == 'hcloud_volume_facts' + if is_old_facts: + module.deprecate("The 'hcloud_volume_facts' module has been renamed to 'hcloud_volume_info', " + "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud") + + hcloud = AnsibleHcloudVolumeInfo(module) + + hcloud.get_volumes() + result = hcloud.get_result() + if is_old_facts: + ansible_info = { + 'hcloud_volume_facts': result['hcloud_volume_info'] + } + module.exit_json(ansible_facts=ansible_info) + else: + ansible_info = { + 'hcloud_volume_info': result['hcloud_volume_info'] + } + module.exit_json(**ansible_info) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_volume_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_volume_info.py new file mode 100644 index 000000000..9520bfa14 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_volume_info.py @@ -0,0 +1,186 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: hcloud_volume_info + +short_description: Gather infos about your Hetzner Cloud Volumes. + +description: + - Gather infos about your Hetzner Cloud Volumes. + +author: + - Lukas Kaemmerling (@LKaemmerling) + +options: + id: + description: + - The ID of the Volume you want to get. + type: int + name: + description: + - The name of the Volume you want to get. + type: str + label_selector: + description: + - The label selector for the Volume you want to get. + type: str +extends_documentation_fragment: +- hetzner.hcloud.hcloud + +''' + +EXAMPLES = """ +- name: Gather hcloud Volume infos + hcloud_volume_info: + register: output +- name: Print the gathered infos + debug: + var: output.hcloud_volume_info +""" + +RETURN = """ +hcloud_volume_info: + description: The Volume infos as list + returned: always + type: complex + contains: + id: + description: Numeric identifier of the Volume + returned: always + type: int + sample: 1937415 + name: + description: Name of the Volume + returned: always + type: str + sample: my-volume + size: + description: Size of the Volume + returned: always + type: str + sample: 10 + linux_device: + description: Path to the device that contains the Volume. + returned: always + type: str + sample: /dev/disk/by-id/scsi-0HC_Volume_12345 + version_added: "0.1.0" + location: + description: Name of the location where the Volume resides in + returned: always + type: str + sample: fsn1 + server: + description: Name of the server where the Volume is attached to + returned: always + type: str + sample: my-server + delete_protection: + description: True if the Volume is protected for deletion + returned: always + type: bool + version_added: "0.1.0" + labels: + description: User-defined labels (key-value pairs) + returned: always + type: dict +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud + + +class AnsibleHcloudVolumeInfo(Hcloud): + def __init__(self, module): + Hcloud.__init__(self, module, "hcloud_volume_info") + self.hcloud_volume_info = None + + def _prepare_result(self): + tmp = [] + + for volume in self.hcloud_volume_info: + if volume is not None: + server_name = None + if volume.server is not None: + server_name = to_native(volume.server.name) + tmp.append({ + "id": to_native(volume.id), + "name": to_native(volume.name), + "size": volume.size, + "location": to_native(volume.location.name), + "labels": volume.labels, + "server": server_name, + "linux_device": to_native(volume.linux_device), + "delete_protection": volume.protection["delete"], + }) + + return tmp + + def get_volumes(self): + try: + if self.module.params.get("id") is not None: + self.hcloud_volume_info = [self.client.volumes.get_by_id( + self.module.params.get("id") + )] + elif self.module.params.get("name") is not None: + self.hcloud_volume_info = [self.client.volumes.get_by_name( + self.module.params.get("name") + )] + elif self.module.params.get("label_selector") is not None: + self.hcloud_volume_info = self.client.volumes.get_all( + label_selector=self.module.params.get("label_selector")) + else: + self.hcloud_volume_info = self.client.volumes.get_all() + + except Exception as e: + self.module.fail_json(msg=e.message) + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + id={"type": "int"}, + name={"type": "str"}, + label_selector={"type": "str"}, + **Hcloud.base_module_arguments() + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHcloudVolumeInfo.define_module() + + is_old_facts = module._name == 'hcloud_volume_facts' + if is_old_facts: + module.deprecate("The 'hcloud_volume_facts' module has been renamed to 'hcloud_volume_info', " + "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud") + + hcloud = AnsibleHcloudVolumeInfo(module) + + hcloud.get_volumes() + result = hcloud.get_result() + if is_old_facts: + ansible_info = { + 'hcloud_volume_facts': result['hcloud_volume_info'] + } + module.exit_json(ansible_facts=ansible_info) + else: + ansible_info = { + 'hcloud_volume_info': result['hcloud_volume_info'] + } + module.exit_json(**ansible_info) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/hetzner/hcloud/tests/.gitignore b/ansible_collections/hetzner/hcloud/tests/.gitignore new file mode 100644 index 000000000..ea1472ec1 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/.gitignore @@ -0,0 +1 @@ +output/ diff --git a/ansible_collections/hetzner/hcloud/tests/integration/constraints.txt b/ansible_collections/hetzner/hcloud/tests/integration/constraints.txt new file mode 100644 index 000000000..19d5ecf28 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/constraints.txt @@ -0,0 +1 @@ +hcloud >= 1.10.0 # minimum version diff --git a/ansible_collections/hetzner/hcloud/tests/integration/requirements.txt b/ansible_collections/hetzner/hcloud/tests/integration/requirements.txt new file mode 100644 index 000000000..d3249deff --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/requirements.txt @@ -0,0 +1,2 @@ +netaddr +hcloud diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate/aliases new file mode 100644 index 000000000..55ec821a4 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate/aliases @@ -0,0 +1,2 @@ +cloud/hcloud +shippable/hcloud/group2 diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate/defaults/main.yml new file mode 100644 index 000000000..58312aec1 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate/defaults/main.yml @@ -0,0 +1,6 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +hcloud_prefix: "tests" +hcloud_certificate_name: "{{hcloud_prefix}}-integration" +hcloud_dns_test_domain: "{{hcloud_prefix | truncate(19, False, 'ans')}}-{{100 | random }}.hc-certs.de" diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate/meta/main.yml new file mode 100644 index 000000000..e531064ca --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate/meta/main.yml @@ -0,0 +1,5 @@ +dependencies: + - setup_selfsigned_certificate +collections: + - community.general.ipfilter + - hetzner.cloud diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate/tasks/main.yml new file mode 100644 index 000000000..615c89d0e --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate/tasks/main.yml @@ -0,0 +1,155 @@ +# Copyright: (c) 2020, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +- name: test missing required parameters on create certificate + hcloud_certificate: + name: "{{ hcloud_certificate_name }}" + register: result + ignore_errors: yes +- name: verify fail test missing required parameters on create certificate + assert: + that: + - result is failed + - 'result.msg == "missing required arguments: certificate, private_key"' + +- name: test create certificate with check mode + hcloud_certificate: + name: "{{ hcloud_certificate_name }}" + certificate: "{{ certificate_example_com }}" + private_key: "{{ certificate_example_com_key }}" + register: result + check_mode: yes +- name: test create certificate with check mode + assert: + that: + - result is changed + +- name: test create certificate + hcloud_certificate: + name: "{{ hcloud_certificate_name }}" + certificate: "{{ certificate_example_com }}" + private_key: "{{ certificate_example_com_key }}" + labels: + key: value + my-label: label + register: certificate +- name: verify create certificate + assert: + that: + - certificate is changed + - certificate.hcloud_certificate.name == "{{ hcloud_certificate_name }}" + - certificate.hcloud_certificate.domain_names[0] == "www.example.com" + - certificate.hcloud_certificate.labels.key == "value" + +- name: test create certificate idempotence + hcloud_certificate: + name: "{{ hcloud_certificate_name }}" + certificate: "{{ certificate_example_com }}" + private_key: "{{ certificate_example_com_key }}" + register: result +- name: verify create certificate idempotence + assert: + that: + - result is not changed + +- name: test update certificate with check mode + hcloud_certificate: + id: "{{ certificate.hcloud_certificate.id }}" + name: "changed-{{ hcloud_certificate_name }}" + register: result + check_mode: yes +- name: test create certificate with check mode + assert: + that: + - result is changed + +- name: test update certificate + hcloud_certificate: + id: "{{ certificate.hcloud_certificate.id }}" + name: "changed-{{ hcloud_certificate_name }}" + labels: + key: value + register: result +- name: test update certificate + assert: + that: + - result is changed + - result.hcloud_certificate.name == "changed-{{ hcloud_certificate_name }}" + +- name: test update certificate with same labels + hcloud_certificate: + id: "{{ certificate.hcloud_certificate.id }}" + name: "changed-{{ hcloud_certificate_name }}" + labels: + key: value + register: result +- name: test update certificate with same labels + assert: + that: + - result is not changed + +- name: test update certificate with other labels + hcloud_certificate: + id: "{{ certificate.hcloud_certificate.id }}" + name: "changed-{{ hcloud_certificate_name }}" + labels: + key: value + test: "val123" + register: result +- name: test update certificate with other labels + assert: + that: + - result is changed + +- name: test rename certificate + hcloud_certificate: + id: "{{ certificate.hcloud_certificate.id }}" + name: "{{ hcloud_certificate_name }}" + register: result +- name: test rename certificate + assert: + that: + - result is changed + - result.hcloud_certificate.name == "{{ hcloud_certificate_name }}" + +- name: absent certificate + hcloud_certificate: + id: "{{ certificate.hcloud_certificate.id }}" + state: absent + register: result +- name: verify absent server + assert: + that: + - result is success + +- name: generate dns domain name + set_fact: + # hcloud_dns_test_domain uses random, which generates a new random number + # on every invocation, by saving it into a fact we generate the number once + hcloud_dns_test_domain: "{{ hcloud_dns_test_domain }}" + +- name: test create managed certificate + hcloud_certificate: + name: "{{ hcloud_certificate_name }}" + domain_names: + - "{{ hcloud_dns_test_domain }}" + type: managed + labels: + HC-Use-Staging-CA: "true" + register: result +- name: verify create managed certificate + assert: + that: + - result is changed + - result.hcloud_certificate.name == "{{ hcloud_certificate_name }}" + - result.hcloud_certificate.domain_names[0] == "{{ hcloud_dns_test_domain }}" + +- name: absent certificate + hcloud_certificate: + id: "{{ result.hcloud_certificate.id }}" + state: absent + register: result +- name: verify absent certificate + assert: + that: + - result is success diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate_info/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate_info/aliases new file mode 100644 index 000000000..55ec821a4 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate_info/aliases @@ -0,0 +1,2 @@ +cloud/hcloud +shippable/hcloud/group2 diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate_info/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate_info/defaults/main.yml new file mode 100644 index 000000000..6205b19b4 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate_info/defaults/main.yml @@ -0,0 +1,5 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +hcloud_prefix: "tests" +hcloud_certificate_name: "always-there-cert" diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate_info/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate_info/meta/main.yml new file mode 100644 index 000000000..34657c1c2 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate_info/meta/main.yml @@ -0,0 +1,4 @@ +dependencies: + - setup_selfsigned_certificate +collections: + - hetzner.cloud diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate_info/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate_info/tasks/main.yml new file mode 100644 index 000000000..d7128db34 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate_info/tasks/main.yml @@ -0,0 +1,66 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- + +- name: create certificate + hcloud_certificate: + name: "{{ hcloud_certificate_name }}" + certificate: "{{ certificate_example_com }}" + private_key: "{{ certificate_example_com_key }}" + labels: + key: value + my-label: label + register: certificate +- name: verify create certificate + assert: + that: + - certificate is changed + - certificate.hcloud_certificate.name == "{{ hcloud_certificate_name }}" + - certificate.hcloud_certificate.domain_names[0] == "www.example.com" + - certificate.hcloud_certificate.labels.key == "value" + +- name: test gather hcloud certificate infos in check mode + hcloud_certificate_info: + register: hcloud_certificate + check_mode: yes +- name: verify test gather hcloud certificate infos in check mode + assert: + that: + - hcloud_certificate.hcloud_certificate_info| list | count >= 1 + +- name: test gather hcloud certificate infos + hcloud_certificate_info: + register: hcloud_certificate + check_mode: yes +- name: verify test gather hcloud certificate infos + assert: + that: + - hcloud_certificate.hcloud_certificate_info| list | count >= 1 + +- name: test gather hcloud certificate infos with correct label selector + hcloud_certificate_info: + label_selector: "key=value" + register: hcloud_certificate +- name: verify test gather hcloud certificate infos with correct label selector + assert: + that: + - hcloud_certificate.hcloud_certificate_info|selectattr('name','equalto','{{ hcloud_certificate_name }}') | list | count == 1 + +- name: test gather hcloud certificate infos with wrong label selector + hcloud_certificate_info: + label_selector: "key!=value" + register: hcloud_certificate +- name: verify test gather hcloud certificate infos with wrong label selector + assert: + that: + - hcloud_certificate.hcloud_certificate_info | list | count == 0 + +- name: absent certificate + hcloud_certificate: + id: "{{ certificate.hcloud_certificate.id }}" + state: absent + register: result +- name: verify absent certificate + assert: + that: + - result is success diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_datacenter_info/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_datacenter_info/aliases new file mode 100644 index 000000000..55ec821a4 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_datacenter_info/aliases @@ -0,0 +1,2 @@ +cloud/hcloud +shippable/hcloud/group2 diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_datacenter_info/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_datacenter_info/defaults/main.yml new file mode 100644 index 000000000..b9e045f40 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_datacenter_info/defaults/main.yml @@ -0,0 +1,6 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +hcloud_datacenter_name: "fsn1-dc14" +hcloud_datacenter_id: 4 +hcloud_location_name: "fsn1" diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_datacenter_info/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_datacenter_info/meta/main.yml new file mode 100644 index 000000000..407c9018a --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_datacenter_info/meta/main.yml @@ -0,0 +1,3 @@ +collections: + - community.general.ipfilter + - hetzner.cloud diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_datacenter_info/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_datacenter_info/tasks/main.yml new file mode 100644 index 000000000..3d144ae47 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_datacenter_info/tasks/main.yml @@ -0,0 +1,39 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +- name: test gather hcloud datacenter infos + hcloud_datacenter_info: + register: hcloud_datacenters + +- name: verify test gather hcloud datacenter infos + assert: + that: + - hcloud_datacenters.hcloud_datacenter_info| list | count >= 5 + +- name: test gather hcloud datacenter infos in check mode + hcloud_datacenter_info: + register: hcloud_datacenters + check_mode: yes + +- name: verify test gather hcloud datacenter infos in check mode + assert: + that: + - hcloud_datacenters.hcloud_datacenter_info| list | count >= 5 + +- name: test gather hcloud datacenter infos with correct name + hcloud_datacenter_info: + name: "{{hcloud_datacenter_name}}" + register: hcloud_datacenter +- name: verify test gather hcloud datacenter with correct name + assert: + that: + - hcloud_datacenter.hcloud_datacenter_info|selectattr('name','equalto','{{ hcloud_datacenter_name }}') |selectattr('location','equalto','{{ hcloud_location_name }}') | list | count == 1 + +- name: test gather hcloud datacenter infos with correct id + hcloud_datacenter_info: + id: "{{hcloud_datacenter_id}}" + register: hcloud_datacenter +- name: verify test gather hcloud datacenter with correct id + assert: + that: + - hcloud_datacenter.hcloud_datacenter_info|selectattr('name','equalto','{{ hcloud_datacenter_name }}') | list | count == 1 diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_firewall/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_firewall/aliases new file mode 100644 index 000000000..55ec821a4 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_firewall/aliases @@ -0,0 +1,2 @@ +cloud/hcloud +shippable/hcloud/group2 diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_firewall/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_firewall/defaults/main.yml new file mode 100644 index 000000000..e7eff02a7 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_firewall/defaults/main.yml @@ -0,0 +1,5 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +hcloud_prefix: "tests" +hcloud_firewall_name: "{{hcloud_prefix}}-integration" diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_firewall/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_firewall/meta/main.yml new file mode 100644 index 000000000..407c9018a --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_firewall/meta/main.yml @@ -0,0 +1,3 @@ +collections: + - community.general.ipfilter + - hetzner.cloud diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_firewall/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_firewall/tasks/main.yml new file mode 100644 index 000000000..f54d351b2 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_firewall/tasks/main.yml @@ -0,0 +1,210 @@ +# Copyright: (c) 2020, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +- name: setup firewall to be absent + hcloud_firewall: + name: "{{ hcloud_firewall_name }}" + state: absent + +- name: test missing required parameters on create firewall + hcloud_firewall: + register: result + ignore_errors: yes +- name: verify fail test missing required parameters on create firewall + assert: + that: + - result is failed + - 'result.msg == "one of the following is required: id, name"' + +- name: test create firewall with check mode + hcloud_firewall: + name: "{{ hcloud_firewall_name }}" + register: result + check_mode: yes +- name: test create firewall with check mode + assert: + that: + - result is changed + +- name: test create firewall + hcloud_firewall: + name: "{{ hcloud_firewall_name }}" + rules: + - direction: in + protocol: icmp + source_ips: + - 0.0.0.0/0 + - ::/0 + description: "allow icmp in" + labels: + key: value + my-label: label + register: firewall +- name: verify create firewall + assert: + that: + - firewall is changed + - firewall.hcloud_firewall.name == "{{ hcloud_firewall_name }}" + - firewall.hcloud_firewall.rules | list | count == 1 + - firewall.hcloud_firewall.rules | selectattr('direction','equalto','in') | list | count == 1 + - firewall.hcloud_firewall.rules | selectattr('protocol','equalto','icmp') | list | count == 1 + - firewall.hcloud_firewall.rules | selectattr('description', 'equalto', 'allow icmp in') | list | count == 1 + +- name: test create firewall idempotence + hcloud_firewall: + name: "{{ hcloud_firewall_name }}" + rules: + - direction: in + protocol: icmp + source_ips: + - 0.0.0.0/0 + - ::/0 + description: "allow icmp in" + labels: + key: value + my-label: label + register: result +- name: verify create firewall idempotence + assert: + that: + - result is not changed + +- name: test update firewall rules + hcloud_firewall: + name: "{{ hcloud_firewall_name }}" + rules: + - direction: in + protocol: icmp + source_ips: + - 0.0.0.0/0 + - ::/0 + - direction: in + protocol: tcp + port: 80 + source_ips: + - 0.0.0.0/0 + - ::/0 + - direction: out + protocol: tcp + port: 80 + destination_ips: + - 0.0.0.0/0 + - ::/0 + description: allow tcp out + labels: + key: value + my-label: label + register: firewall +- name: verify update firewall rules + assert: + that: + - firewall is changed + - firewall.hcloud_firewall.name == "{{ hcloud_firewall_name }}" + - firewall.hcloud_firewall.rules | list | count == 3 + - firewall.hcloud_firewall.rules | selectattr('direction','equalto','in') | list | count == 2 + - firewall.hcloud_firewall.rules | selectattr('direction','equalto','out') | list | count == 1 + - firewall.hcloud_firewall.rules | selectattr('protocol','equalto','icmp') | list | count == 1 + - firewall.hcloud_firewall.rules | selectattr('protocol','equalto','tcp') | list | count == 2 + - firewall.hcloud_firewall.rules | selectattr('port','equalto','80') | list | count == 2 + - firewall.hcloud_firewall.rules | selectattr('description', 'equalto', 'allow tcp out') | list | count == 1 + +- name: test update firewall rules idempotence + hcloud_firewall: + name: "{{ hcloud_firewall_name }}" + rules: + - direction: in + protocol: icmp + source_ips: + - 0.0.0.0/0 + - ::/0 + - direction: in + protocol: tcp + port: 80 + source_ips: + - 0.0.0.0/0 + - ::/0 + - direction: out + protocol: tcp + port: 80 + destination_ips: + - 0.0.0.0/0 + - ::/0 + description: allow tcp out + labels: + key: value + my-label: label + register: result +- name: verify update firewall rules idempotence + assert: + that: + - result is not changed + +- name: test update firewall with check mode + hcloud_firewall: + id: "{{ firewall.hcloud_firewall.id }}" + name: "changed-{{ hcloud_firewall_name }}" + register: result + check_mode: yes +- name: test create firewall with check mode + assert: + that: + - result is changed + +- name: test update firewall + hcloud_firewall: + id: "{{ firewall.hcloud_firewall.id }}" + name: "changed-{{ hcloud_firewall_name }}" + labels: + key: value + register: result +- name: test update firewall + assert: + that: + - result is changed + - result.hcloud_firewall.name == "changed-{{ hcloud_firewall_name }}" + +- name: test update firewall with same labels + hcloud_firewall: + id: "{{ firewall.hcloud_firewall.id }}" + name: "changed-{{ hcloud_firewall_name }}" + labels: + key: value + register: result +- name: test update firewall with same labels + assert: + that: + - result is not changed + +- name: test update firewall with other labels + hcloud_firewall: + id: "{{ firewall.hcloud_firewall.id }}" + name: "changed-{{ hcloud_firewall_name }}" + labels: + key: value + test: "val123" + register: result +- name: test update firewall with other labels + assert: + that: + - result is changed + +- name: test rename firewall + hcloud_firewall: + id: "{{ firewall.hcloud_firewall.id }}" + name: "{{ hcloud_firewall_name }}" + register: result +- name: test rename firewall + assert: + that: + - result is changed + - result.hcloud_firewall.name == "{{ hcloud_firewall_name }}" + +- name: absent firewall + hcloud_firewall: + id: "{{ firewall.hcloud_firewall.id }}" + state: absent + register: result +- name: verify absent server + assert: + that: + - result is success diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip/aliases new file mode 100644 index 000000000..55ec821a4 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip/aliases @@ -0,0 +1,2 @@ +cloud/hcloud +shippable/hcloud/group2 diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip/defaults/main.yml new file mode 100644 index 000000000..ebd5ccc38 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip/defaults/main.yml @@ -0,0 +1,6 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +hcloud_prefix: "tests" +hcloud_floating_ip_name: "{{hcloud_prefix}}-i" +hcloud_server_name: "{{ hcloud_prefix | truncate(45, True, '', 0) }}-fip-t" diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip/meta/main.yml new file mode 100644 index 000000000..407c9018a --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip/meta/main.yml @@ -0,0 +1,3 @@ +collections: + - community.general.ipfilter + - hetzner.cloud diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip/tasks/main.yml new file mode 100644 index 000000000..8ada4172f --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip/tasks/main.yml @@ -0,0 +1,470 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +- name: setup server + hcloud_server: + name: "{{ hcloud_server_name }}" + server_type: cx11 + image: ubuntu-18.04 + state: started + location: "fsn1" + register: main_server +- name: verify setup server + assert: + that: + - main_server is changed + +- name: setup another server + hcloud_server: + name: "{{ hcloud_server_name }}2" + server_type: cx11 + image: ubuntu-18.04 + state: started + register: main_server2 +- name: verify setup another server + assert: + that: + - main_server2 is changed + +- name: test missing type parameter on create Floating IP + hcloud_floating_ip: + name: "{{ hcloud_floating_ip_name }}" + register: result + ignore_errors: yes +- name: verify fail test missing type parameter on create Floating IP + assert: + that: + - result is failed + - 'result.msg == "missing required arguments: type"' + +- name: test missing required parameters on create Floating IP + hcloud_floating_ip: + name: "{{ hcloud_floating_ip_name }}" + type: ipv4 + register: result + ignore_errors: yes +- name: verify fail test missing required parameters on create Floating IP + assert: + that: + - result is failed + - 'result.msg == "one of the following is required: home_location, server"' + +- name: test missing type parameter on delete Floating IP + hcloud_floating_ip: + type: ipv4 + home_location: "fsn1" + state: "absent" + register: result + ignore_errors: yes +- name: verify fail test missing type parameter on delete Floating IP + assert: + that: + - result is failed + - 'result.msg == "one of the following is required: id, name"' + + +- name: test invalid type + hcloud_floating_ip: + name: "{{ hcloud_floating_ip_name }}" + type: ipv5 + home_location: "fsn1" + register: result + ignore_errors: yes +- name: verify invalid type + assert: + that: + - result is failed + - 'result.msg == "value of type must be one of: ipv4, ipv6, got: ipv5"' + +- name: test invalid location + hcloud_floating_ip: + name: "{{ hcloud_floating_ip_name }}" + type: ipv4 + home_location: "abc" + register: result + ignore_errors: yes +- name: verify invalid location + assert: + that: + - result is failed + - result.msg == "invalid input in fields 'server', 'home_location'" + +- name: test create Floating IP with check mode + hcloud_floating_ip: + name: "{{ hcloud_floating_ip_name }}" + description: "Web Server" + type: ipv4 + home_location: "fsn1" + register: floatingIP + check_mode: yes +- name: verify test create Floating IP with check mode + assert: + that: + - floatingIP is changed + +- name: test create Floating IP + hcloud_floating_ip: + name: "{{ hcloud_floating_ip_name }}" + description: "Web Server" + type: ipv4 + home_location: "fsn1" + register: floatingIP +- name: verify test create Floating IP + assert: + that: + - floatingIP is changed + - floatingIP.hcloud_floating_ip.name ==hcloud_floating_ip_name + - floatingIP.hcloud_floating_ip.description == "Web Server" + - floatingIP.hcloud_floating_ip.home_location == "fsn1" + +- name: test create Floating IP idempotency + hcloud_floating_ip: + name: "{{ hcloud_floating_ip_name }}" + description: "Web Server" + type: ipv4 + home_location: "fsn1" + register: floatingIP +- name: verify test create Floating IP idempotency + assert: + that: + - floatingIP is not changed + +- name: test update Floating IP with check mode + hcloud_floating_ip: + name: "{{ hcloud_floating_ip_name }}" + description: "changed-description" + type: ipv4 + home_location: "fsn1" + check_mode: yes + register: floatingIP +- name: verify test create Floating IP with check mode + assert: + that: + - floatingIP is changed + - floatingIP.hcloud_floating_ip.description == "Web Server" + +- name: test update Floating IP + hcloud_floating_ip: + name: "{{ hcloud_floating_ip_name }}" + description: "changed-description" + type: ipv4 + home_location: "fsn1" + labels: + key: value + register: floatingIP +- name: verify test update Floating IP + assert: + that: + - floatingIP is changed + - floatingIP.hcloud_floating_ip.description == "changed-description" + +- name: test update Floating IP idempotency + hcloud_floating_ip: + name: "{{ hcloud_floating_ip_name }}" + description: "changed-description" + type: ipv4 + home_location: "fsn1" + labels: + key: value + register: floatingIP +- name: verify test update Floating IP idempotency + assert: + that: + - floatingIP is not changed + +- name: test update Floating IP with same labels + hcloud_floating_ip: + name: "{{ hcloud_floating_ip_name }}" + type: ipv4 + home_location: "fsn1" + labels: + key: value + register: floatingIP +- name: verify test update Floating IP with same labels + assert: + that: + - floatingIP is not changed + +- name: test update Floating IP with other labels + hcloud_floating_ip: + name: "{{ hcloud_floating_ip_name }}" + type: ipv4 + home_location: "fsn1" + labels: + key: value + other: label + register: floatingIP +- name: verify test update Floating IP with other labels + assert: + that: + - floatingIP is changed + +- name: test update Floating IP with other labels in different order + hcloud_floating_ip: + name: "{{ hcloud_floating_ip_name }}" + type: ipv4 + home_location: "fsn1" + labels: + other: label + key: value + register: floatingIP +- name: verify test update Floating IP with other labels in different order + assert: + that: + - floatingIP is not changed + +- name: test assign Floating IP with checkmode + hcloud_floating_ip: + name: "{{ hcloud_floating_ip_name }}" + description: "changed-description" + type: ipv4 + server: "{{ main_server.hcloud_server.name }}" + check_mode: yes + register: floatingIP +- name: verify test assign Floating IP with checkmode + assert: + that: + - floatingIP is changed + - floatingIP.hcloud_floating_ip.server != "{{ main_server.hcloud_server.name }}" + +- name: test assign Floating IP + hcloud_floating_ip: + name: "{{ hcloud_floating_ip_name }}" + description: "changed-description" + type: ipv4 + server: "{{ main_server.hcloud_server.name }}" + register: floatingIP +- name: verify test assign Floating IP + assert: + that: + - floatingIP is changed + - floatingIP.hcloud_floating_ip.server == "{{ main_server.hcloud_server.name }}" + +- name: test assign Floating IP idempotency + hcloud_floating_ip: + name: "{{ hcloud_floating_ip_name }}" + description: "changed-description" + type: ipv4 + server: "{{ main_server.hcloud_server.name }}" + register: floatingIP +- name: verify test unassign Floating IPidempotency + assert: + that: + - floatingIP is not changed + +- name: test unassign Floating IP + hcloud_floating_ip: + name: "{{ hcloud_floating_ip_name }}" + type: ipv4 + home_location: "fsn1" + register: floatingIP +- name: verify test unassign Floating IP + assert: + that: + - floatingIP is changed + - floatingIP.hcloud_floating_ip.server != "{{ main_server.hcloud_server.name }}" + +- name: test unassign Floating IP idempotency + hcloud_floating_ip: + name: "{{ hcloud_floating_ip_name }}" + type: ipv4 + home_location: "fsn1" + register: floatingIP +- name: verify test unassign Floating IPidempotency + assert: + that: + - floatingIP is not changed + +- name: test assign Floating IP again + hcloud_floating_ip: + name: "{{ hcloud_floating_ip_name }}" + type: ipv4 + server: "{{ main_server.hcloud_server.name }}" + register: floatingIP +- name: verify test assign Floating IP again + assert: + that: + - floatingIP is changed + - floatingIP.hcloud_floating_ip.server == "{{ main_server.hcloud_server.name }}" + +- name: test already assigned Floating IP assign without force + hcloud_floating_ip: + name: "{{ hcloud_floating_ip_name }}" + type: ipv4 + server: "{{ main_server2.hcloud_server.name }}" + register: floatingIP +- name: verify test already assigned Floating IP assign without force + assert: + that: + - floatingIP is changed + - floatingIP.hcloud_floating_ip.server == "{{ main_server.hcloud_server.name }}" + +- name: test already assigned Floating IP assign with force + hcloud_floating_ip: + name: "{{ hcloud_floating_ip_name }}" + type: ipv4 + force: yes + server: "{{ main_server2.hcloud_server.name }}" + register: floatingIP +- name: verify test already assigned Floating IP assign with force + assert: + that: + - floatingIP is changed + - floatingIP.hcloud_floating_ip.server == "{{ main_server2.hcloud_server.name }}" + +- name: test update Floating IP delete protection + hcloud_floating_ip: + name: "{{ hcloud_floating_ip_name }}" + type: ipv4 + delete_protection: true + register: floatingIP +- name: verify update Floating IP delete protection + assert: + that: + - floatingIP is changed + - floatingIP.hcloud_floating_ip.delete_protection is sameas true + +- name: test update Floating IP delete protection idempotency + hcloud_floating_ip: + name: "{{ hcloud_floating_ip_name }}" + type: ipv4 + delete_protection: true + register: floatingIP +- name: verify update Floating IP delete protection idempotency + assert: + that: + - floatingIP is not changed + - floatingIP.hcloud_floating_ip.delete_protection is sameas true + +- name: test Floating IP without delete protection set to be idempotent + hcloud_floating_ip: + name: "{{ hcloud_floating_ip_name }}" + type: ipv4 + register: floatingIP +- name: verify Floating IP without delete protection set to be idempotent + assert: + that: + - floatingIP is not changed + - floatingIP.hcloud_floating_ip.delete_protection is sameas true + +- name: test delete Floating IP fails if it is protected + hcloud_floating_ip: + name: "{{ hcloud_floating_ip_name }}" + state: "absent" + register: result + ignore_errors: yes +- name: verify test delete floating ip + assert: + that: + - result is failed + - 'result.msg == "Floating IP deletion is protected"' + +- name: test update Floating IP delete protection + hcloud_floating_ip: + name: "{{ hcloud_floating_ip_name }}" + type: ipv4 + delete_protection: false + register: floatingIP +- name: verify update Floating IP delete protection + assert: + that: + - floatingIP is changed + - floatingIP.hcloud_floating_ip.delete_protection is sameas false + +- name: test delete floating ip + hcloud_floating_ip: + name: "{{ hcloud_floating_ip_name }}" + state: "absent" + register: result +- name: verify test delete floating ip + assert: + that: + - result is changed + +- name: test create ipv6 floating ip + hcloud_floating_ip: + name: "{{ hcloud_floating_ip_name }}" + type: ipv6 + home_location: "fsn1" + state: "present" + register: result +- name: verify test create ipv6 floating ip + assert: + that: + - result is changed + +- name: test delete ipv6 floating ip + hcloud_floating_ip: + name: "{{ hcloud_floating_ip_name }}" + state: "absent" + register: result +- name: verify test delete ipv6 floating ip + assert: + that: + - result is changed + +- name: cleanup + hcloud_server: + name: "{{ hcloud_server_name }}" + state: absent + register: result +- name: verify cleanup + assert: + that: + - result is changed +- name: cleanup another server + hcloud_server: + name: "{{ main_server2.hcloud_server.name }}" + state: absent + register: result +- name: verify cleanup another server + assert: + that: + - result is changed + +- name: test create Floating IP with delete protection + hcloud_floating_ip: + name: "{{ hcloud_floating_ip_name }}" + type: ipv4 + home_location: fsn1 + delete_protection: true + register: floatingIP +- name: verify create Floating IP with delete protection + assert: + that: + - floatingIP is changed + - floatingIP.hcloud_floating_ip.delete_protection is sameas true + +- name: test delete Floating IP fails if it is protected + hcloud_floating_ip: + name: "{{ hcloud_floating_ip_name }}" + state: "absent" + register: result + ignore_errors: yes +- name: verify test delete floating ip + assert: + that: + - result is failed + - 'result.msg == "Floating IP deletion is protected"' + +- name: test update Floating IP delete protection + hcloud_floating_ip: + name: "{{ hcloud_floating_ip_name }}" + type: ipv4 + delete_protection: false + register: floatingIP +- name: verify update Floating IP delete protection + assert: + that: + - floatingIP is changed + - floatingIP.hcloud_floating_ip.delete_protection is sameas false + +- name: test delete floating ip + hcloud_floating_ip: + name: "{{ hcloud_floating_ip_name }}" + state: "absent" + register: result +- name: verify test delete floating ip + assert: + that: + - result is changed diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip_info/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip_info/aliases new file mode 100644 index 000000000..55ec821a4 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip_info/aliases @@ -0,0 +1,2 @@ +cloud/hcloud +shippable/hcloud/group2 diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip_info/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip_info/defaults/main.yml new file mode 100644 index 000000000..f4c6a9fc9 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip_info/defaults/main.yml @@ -0,0 +1,5 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +hcloud_prefix: "tests" +hcloud_floating_ip_name: "{{hcloud_prefix}}-i" diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip_info/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip_info/meta/main.yml new file mode 100644 index 000000000..407c9018a --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip_info/meta/main.yml @@ -0,0 +1,3 @@ +collections: + - community.general.ipfilter + - hetzner.cloud diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip_info/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip_info/tasks/main.yml new file mode 100644 index 000000000..9ca1c2a4a --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip_info/tasks/main.yml @@ -0,0 +1,87 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +- name: setup ensure floating ip is absent + hcloud_floating_ip: + name: "{{ hcloud_floating_ip_name }}" + state: absent + +- name: setup floating ip + hcloud_floating_ip: + name: "{{ hcloud_floating_ip_name }}" + home_location: "fsn1" + type: ipv4 + labels: + key: value + register: test_floating_ip + +- name: verify setup floating ip + assert: + that: + - test_floating_ip is changed + +- name: test gather hcloud floating ip infos + hcloud_floating_ip_info: + register: hcloud_floating_ips +- name: verify test gather hcloud floating ip infos + assert: + that: + - hcloud_floating_ips.hcloud_floating_ip_info| list | count >= 1 + +- name: test gather hcloud floating ip infos in check mode + hcloud_floating_ip_info: + check_mode: yes + register: hcloud_floating_ips + +- name: verify test gather hcloud floating ip infos in check mode + assert: + that: + - hcloud_floating_ips.hcloud_floating_ip_info| list | count >= 1 + + +- name: test gather hcloud floating ip infos with correct label selector + hcloud_floating_ip_info: + label_selector: "key=value" + register: hcloud_floating_ips +- name: verify test gather hcloud floating ip with correct label selector + assert: + that: + - hcloud_floating_ips.hcloud_floating_ip_info|selectattr('name','equalto','{{ test_floating_ip.hcloud_floating_ip.name }}') | list | count == 1 + +- name: test gather hcloud floating ip infos with wrong label selector + hcloud_floating_ip_info: + label_selector: "key!=value" + register: hcloud_floating_ips +- name: verify test gather hcloud floating ip with wrong label selector + assert: + that: + - hcloud_floating_ips.hcloud_floating_ip_info | list | count == 0 + +- name: test gather hcloud floating ip infos with correct id + hcloud_floating_ip_info: + id: "{{test_floating_ip.hcloud_floating_ip.id}}" + register: hcloud_floating_ips +- name: verify test gather hcloud floating ip with correct id + assert: + that: + - hcloud_floating_ips.hcloud_floating_ip_info|selectattr('name','equalto','{{ test_floating_ip.hcloud_floating_ip.name }}') | list | count == 1 + +- name: test gather hcloud floating ip infos with wrong id + hcloud_floating_ip_info: + id: "{{test_floating_ip.hcloud_floating_ip.id}}1" + register: result + ignore_errors: yes +- name: verify test gather hcloud floating ip with wrong id + assert: + that: + - result is failed + +- name: cleanup + hcloud_floating_ip: + id: "{{ test_floating_ip.hcloud_floating_ip.id }}" + state: absent + register: result +- name: verify cleanup + assert: + that: + - result is success diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_image_info/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_image_info/aliases new file mode 100644 index 000000000..55ec821a4 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_image_info/aliases @@ -0,0 +1,2 @@ +cloud/hcloud +shippable/hcloud/group2 diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_image_info/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_image_info/defaults/main.yml new file mode 100644 index 000000000..7c25d171d --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_image_info/defaults/main.yml @@ -0,0 +1,7 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +hcloud_prefix: "tests" +hcloud_test_image_name: "always-there-snapshot" +hcloud_test_image_id: 10164049 +hcloud_test_image_name_os: "ubuntu-22.04" diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_image_info/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_image_info/meta/main.yml new file mode 100644 index 000000000..407c9018a --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_image_info/meta/main.yml @@ -0,0 +1,3 @@ +collections: + - community.general.ipfilter + - hetzner.cloud diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_image_info/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_image_info/tasks/main.yml new file mode 100644 index 000000000..16ed44a28 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_image_info/tasks/main.yml @@ -0,0 +1,93 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +- name: test gather hcloud image infos with type system + hcloud_image_info: + register: hcloud_images +- name: verify test gather hcloud image infos in check mode + assert: + that: + - hcloud_images.hcloud_image_info| list | count > 2 + +- name: test gather hcloud image infos in check mode + hcloud_image_info: + check_mode: yes + register: hcloud_images + +- name: verify test gather hcloud image infos in check mode + assert: + that: + - hcloud_images.hcloud_image_info| list | count > 2 + + +- name: test gather hcloud image infos with correct label selector + hcloud_image_info: + label_selector: "key=value" + type: snapshot + register: hcloud_images +- name: verify test gather hcloud image with correct label selector + assert: + that: + - hcloud_images.hcloud_image_info|selectattr('description','equalto','{{ hcloud_test_image_name }}') | list | count == 1 + +- name: test gather hcloud image infos with wrong label selector + hcloud_image_info: + label_selector: "key!=value" + type: snapshot + register: hcloud_images +- name: verify test gather hcloud image with wrong label selector + assert: + that: + - hcloud_images.hcloud_image_info | list | count == 0 + +- name: test gather hcloud image infos with correct id + hcloud_image_info: + id: "{{hcloud_test_image_id}}" + type: snapshot + register: hcloud_images +- name: verify test gather hcloud image with correct id + assert: + that: + - hcloud_images.hcloud_image_info|selectattr('description','equalto','{{ hcloud_test_image_name }}') | list | count == 1 + +- name: test gather hcloud image infos with wrong id + hcloud_image_info: + id: "{{hcloud_test_image_id}}1" + type: snapshot + ignore_errors: yes + register: result +- name: verify test gather hcloud image with wrong id + assert: + that: + - result is failed + +- name: test gather hcloud image infos with name + hcloud_image_info: + name: "{{ hcloud_test_image_name_os }}" + register: hcloud_images +- name: verify test gather hcloud image infos with name + assert: + that: + - hcloud_images.hcloud_image_info | list | count == 1 + - hcloud_images.hcloud_image_info[0].architecture == "x86" + +- name: test gather hcloud image infos with name and architecture + hcloud_image_info: + name: "{{ hcloud_test_image_name_os }}" + architecture: arm + register: hcloud_images +- name: verify test gather hcloud image infos with name + assert: + that: + - hcloud_images.hcloud_image_info | list | count == 1 + - hcloud_images.hcloud_image_info[0].architecture == "arm" + +- name: test gather hcloud image infos with architecture + hcloud_image_info: + architecture: arm + register: hcloud_images +- name: verify test gather hcloud image infos with name + assert: + that: + - hcloud_images.hcloud_image_info | selectattr('architecture','equalto','x86') | list | count == 0 + - hcloud_images.hcloud_image_info | selectattr('architecture','equalto','arm') | list | count > 2 diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer/aliases new file mode 100644 index 000000000..18dc30b6c --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer/aliases @@ -0,0 +1,2 @@ +cloud/hcloud +shippable/hcloud/group1 diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer/defaults/main.yml new file mode 100644 index 000000000..7f431cd8d --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer/defaults/main.yml @@ -0,0 +1,5 @@ +# Copyright: (c) 2020, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +hcloud_prefix: "tests" +hcloud_load_balancer_name: "{{hcloud_prefix}}-i" diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer/meta/main.yml new file mode 100644 index 000000000..407c9018a --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer/meta/main.yml @@ -0,0 +1,3 @@ +collections: + - community.general.ipfilter + - hetzner.cloud diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer/tasks/main.yml new file mode 100644 index 000000000..a25e550d0 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer/tasks/main.yml @@ -0,0 +1,247 @@ +# Copyright: (c) 2020, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +- name: setup + hcloud_load_balancer: + name: "{{ hcloud_load_balancer_name }}" + state: absent + register: result +- name: verify setup + assert: + that: + - result is success +- name: test missing required parameters on create Load Balancer + hcloud_load_balancer: + name: "{{ hcloud_load_balancer_name }}" + register: result + ignore_errors: yes +- name: verify fail test missing required parameters on create Load Balancer + assert: + that: + - result is failed + - 'result.msg == "missing required arguments: load_balancer_type"' + +- name: test create Load Balancer with check mode + hcloud_load_balancer: + name: "{{ hcloud_load_balancer_name }}" + load_balancer_type: lb11 + network_zone: eu-central + state: present + register: result + check_mode: yes +- name: test create Load Balancer with check mode + assert: + that: + - result is changed + +- name: test create Load Balancer + hcloud_load_balancer: + name: "{{ hcloud_load_balancer_name}}" + load_balancer_type: lb11 + network_zone: eu-central + state: present + register: main_load_balancer +- name: verify create Load Balancer + assert: + that: + - main_load_balancer is changed + - main_load_balancer.hcloud_load_balancer.name == "{{ hcloud_load_balancer_name }}" + - main_load_balancer.hcloud_load_balancer.load_balancer_type == "lb11" + +- name: test create Load Balancer idempotence + hcloud_load_balancer: + name: "{{ hcloud_load_balancer_name }}" + load_balancer_type: lb11 + network_zone: eu-central + state: present + register: result +- name: verify create Load Balancer idempotence + assert: + that: + - result is not changed + +- name: test change Load Balancer type + hcloud_load_balancer: + name: "{{ hcloud_load_balancer_name }}" + load_balancer_type: lb21 + state: present + register: result_after_test + ignore_errors: true +- name: verify change Load Balancer type + assert: + that: + - result_after_test is changed + - result_after_test.hcloud_load_balancer.load_balancer_type == "lb21" + +- name: test Load Balancer without type set to be idempotent + hcloud_load_balancer: + name: "{{hcloud_load_balancer_name}}" + register: result_after_test +- name: verify test Load Balancer without type set to be idempotent + assert: + that: + - result_after_test is not changed + - result_after_test.hcloud_load_balancer.load_balancer_type == "lb21" + +- name: test update Load Balancer protection + hcloud_load_balancer: + name: "{{ hcloud_load_balancer_name }}" + delete_protection: true + state: present + register: result_after_test + ignore_errors: true +- name: verify update Load Balancer protection + assert: + that: + - result_after_test is changed + - result_after_test.hcloud_load_balancer.delete_protection is sameas true + +- name: test Load Balancer without protection set to be idempotent + hcloud_load_balancer: + name: "{{hcloud_load_balancer_name}}" + register: result_after_test +- name: verify test Load Balancer without protection set to be idempotent + assert: + that: + - result_after_test is not changed + - result_after_test.hcloud_load_balancer.delete_protection is sameas true + +- name: test delete Load Balancer fails if it is protected + hcloud_load_balancer: + name: "{{hcloud_load_balancer_name}}" + state: absent + ignore_errors: yes + register: result +- name: verify delete Load Balancer fails if it is protected + assert: + that: + - result is failed + - 'result.msg == "load balancer deletion is protected"' + +- name: test remove Load Balancer protection + hcloud_load_balancer: + name: "{{ hcloud_load_balancer_name }}" + delete_protection: false + state: present + register: result_after_test + ignore_errors: true +- name: verify remove Load Balancer protection + assert: + that: + - result_after_test is changed + - result_after_test.hcloud_load_balancer.delete_protection is sameas false + +- name: absent Load Balancer + hcloud_load_balancer: + name: "{{ hcloud_load_balancer_name }}" + state: absent + register: result +- name: verify absent Load Balancer + assert: + that: + - result is success + +- name: test create Load Balancer with labels + hcloud_load_balancer: + name: "{{ hcloud_load_balancer_name}}" + load_balancer_type: lb11 + network_zone: eu-central + labels: + key: value + mylabel: "val123" + state: present + register: main_load_balancer +- name: verify create Load Balancer with labels + assert: + that: + - main_load_balancer is changed + - main_load_balancer.hcloud_load_balancer.labels.key == "value" + - main_load_balancer.hcloud_load_balancer.labels.mylabel == "val123" + +- name: test update Load Balancer with labels + hcloud_load_balancer: + name: "{{ hcloud_load_balancer_name}}" + load_balancer_type: lb11 + network_zone: eu-central + labels: + key: other + mylabel: "val123" + state: present + register: main_load_balancer +- name: verify update Load Balancer with labels + assert: + that: + - main_load_balancer is changed + - main_load_balancer.hcloud_load_balancer.labels.key == "other" + - main_load_balancer.hcloud_load_balancer.labels.mylabel == "val123" + +- name: test update Load Balancer with labels in other order + hcloud_load_balancer: + name: "{{ hcloud_load_balancer_name}}" + load_balancer_type: lb11 + network_zone: eu-central + labels: + mylabel: "val123" + key: other + state: present + register: main_load_balancer +- name: verify update Load Balancer with labels in other order + assert: + that: + - main_load_balancer is not changed + +- name: cleanup with labels + hcloud_load_balancer: + name: "{{ hcloud_load_balancer_name }}" + state: absent + register: result +- name: verify cleanup + assert: + that: + - result is success + +- name: test create Load Balancer with delete protection + hcloud_load_balancer: + name: "{{ hcloud_load_balancer_name }}" + load_balancer_type: lb11 + network_zone: eu-central + delete_protection: true + register: main_load_balancer +- name: verify create Load Balancer with delete protection + assert: + that: + - main_load_balancer is changed + - main_load_balancer.hcloud_load_balancer.delete_protection is sameas true + +- name: test delete Load Balancer fails if it is protected + hcloud_load_balancer: + name: "{{ hcloud_load_balancer_name }}" + state: "absent" + register: result + ignore_errors: yes +- name: verify test delete Load Balancer + assert: + that: + - result is failed + - 'result.msg == "load balancer deletion is protected"' + +- name: test update Load Balancer delete protection + hcloud_load_balancer: + name: "{{ hcloud_load_balancer_name }}" + delete_protection: false + register: main_load_balancer +- name: verify update Load Balancer delete protection + assert: + that: + - main_load_balancer is changed + - main_load_balancer.hcloud_load_balancer.delete_protection is sameas false + +- name: test delete Load Balancer + hcloud_load_balancer: + name: "{{ hcloud_load_balancer_name }}" + state: "absent" + register: result +- name: verify test delete Load Balancer + assert: + that: + - result is changed diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_info/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_info/aliases new file mode 100644 index 000000000..18dc30b6c --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_info/aliases @@ -0,0 +1,2 @@ +cloud/hcloud +shippable/hcloud/group1 diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_info/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_info/defaults/main.yml new file mode 100644 index 000000000..326973a78 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_info/defaults/main.yml @@ -0,0 +1,6 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +hcloud_prefix: "tests" +hcloud_load_balancer_name: "{{hcloud_prefix}}-i" +hcloud_server_name: "{{ hcloud_prefix | truncate(45, True, '', 0) }}-lb-i" diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_info/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_info/meta/main.yml new file mode 100644 index 000000000..407c9018a --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_info/meta/main.yml @@ -0,0 +1,3 @@ +collections: + - community.general.ipfilter + - hetzner.cloud diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_info/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_info/tasks/main.yml new file mode 100644 index 000000000..9e6528858 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_info/tasks/main.yml @@ -0,0 +1,128 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +- name: setup ensure Load Balancer is absent + hcloud_load_balancer: + name: "{{ hcloud_load_balancer_name }}" + state: absent +- name: setup server + hcloud_server: + name: "{{hcloud_server_name}}" + server_type: cx11 + image: ubuntu-20.04 + state: started + register: server +- name: verify setup server + assert: + that: + - server is success +- name: setup Load Balancer + hcloud_load_balancer: + name: "{{ hcloud_load_balancer_name }}" + load_balancer_type: lb11 + network_zone: eu-central + labels: + key: value + register: test_load_balancer + +- name: verify setup Load Balancer + assert: + that: + - test_load_balancer is changed + +- name: test create load_balancer target + hcloud_load_balancer_target: + type: "server" + load_balancer: "{{hcloud_load_balancer_name}}" + server: "{{hcloud_server_name}}" + state: present + register: load_balancer_target +- name: verify create load_balancer target + assert: + that: + - load_balancer_target is success +- name: test create load_balancer service + hcloud_load_balancer_service: + load_balancer: "{{hcloud_load_balancer_name}}" + protocol: "http" + listen_port: 80 + state: present + register: load_balancer_service +- name: verify create load_balancer service + assert: + that: + - load_balancer_service is success + +- name: test gather hcloud Load Balancer infos + hcloud_load_balancer_info: + id: "{{test_load_balancer.hcloud_load_balancer.id}}" + register: hcloud_load_balancers +- name: verify test gather hcloud Load Balancer infos + assert: + that: + - hcloud_load_balancers.hcloud_load_balancer_info| list | count >= 1 + - hcloud_load_balancers.hcloud_load_balancer_info[0].targets | list | count == 1 + - hcloud_load_balancers.hcloud_load_balancer_info[0].targets | selectattr('type','equalto','server') | list | count == 1 + - hcloud_load_balancers.hcloud_load_balancer_info[0].targets | selectattr('server','equalto','{{ hcloud_server_name }}') | list | count == 1 + - hcloud_load_balancers.hcloud_load_balancer_info[0].services | list | count == 1 + - hcloud_load_balancers.hcloud_load_balancer_info[0].services | selectattr('protocol','equalto','http') | list | count == 1 + - hcloud_load_balancers.hcloud_load_balancer_info[0].services | selectattr('listen_port','equalto',80) | list | count == 1 + - hcloud_load_balancers.hcloud_load_balancer_info[0].services | selectattr('destination_port','equalto',80) | list | count == 1 + +- name: test gather hcloud Load Balancer infos in check mode + hcloud_load_balancer_info: + check_mode: yes + register: hcloud_load_balancers + +- name: verify test gather hcloud Load Balancer infos in check mode + assert: + that: + - hcloud_load_balancers.hcloud_load_balancer_info| list | count >= 1 + + +- name: test gather hcloud Load Balancer infos with correct label selector + hcloud_load_balancer_info: + label_selector: "key=value" + register: hcloud_load_balancers +- name: verify test gather hcloud Load Balancer with correct label selector + assert: + that: + - hcloud_load_balancers.hcloud_load_balancer_info|selectattr('name','equalto','{{ test_load_balancer.hcloud_load_balancer.name }}') | list | count == 1 + +- name: test gather hcloud Load Balancer infos with wrong label selector + hcloud_load_balancer_info: + label_selector: "key!=value" + register: hcloud_load_balancers +- name: verify test gather hcloud Load Balancer with wrong label selector + assert: + that: + - hcloud_load_balancers.hcloud_load_balancer_info | list | count == 0 + +- name: test gather hcloud Load Balancer infos with correct id + hcloud_load_balancer_info: + id: "{{test_load_balancer.hcloud_load_balancer.id}}" + register: hcloud_load_balancers +- name: verify test gather hcloud Load Balancer with correct id + assert: + that: + - hcloud_load_balancers.hcloud_load_balancer_info|selectattr('name','equalto','{{ test_load_balancer.hcloud_load_balancer.name }}') | list | count == 1 + +- name: test gather hcloud Load Balancer infos with wrong id + hcloud_load_balancer_info: + id: "{{test_load_balancer.hcloud_load_balancer.id}}1" + register: result + ignore_errors: yes +- name: verify test gather hcloud Load Balancer with wrong id + assert: + that: + - result is failed + +- name: cleanup + hcloud_load_balancer: + id: "{{ test_load_balancer.hcloud_load_balancer.id }}" + state: absent + register: result +- name: verify cleanup + assert: + that: + - result is success diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_network/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_network/aliases new file mode 100644 index 000000000..18dc30b6c --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_network/aliases @@ -0,0 +1,2 @@ +cloud/hcloud +shippable/hcloud/group1 diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_network/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_network/defaults/main.yml new file mode 100644 index 000000000..6abf9ceec --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_network/defaults/main.yml @@ -0,0 +1,6 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +hcloud_prefix: "tests" +hcloud_network_name: "{{hcloud_prefix}}-lb-n" +hcloud_load_balancer_name: "{{hcloud_prefix}}-lb-n" diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_network/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_network/meta/main.yml new file mode 100644 index 000000000..407c9018a --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_network/meta/main.yml @@ -0,0 +1,3 @@ +collections: + - community.general.ipfilter + - hetzner.cloud diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_network/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_network/tasks/main.yml new file mode 100644 index 000000000..9a1bf5175 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_network/tasks/main.yml @@ -0,0 +1,181 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +- name: setup network + hcloud_network: + name: "{{ hcloud_network_name }}" + ip_range: "10.0.0.0/8" + state: present + register: network +- name: verify setup network + assert: + that: + - network is success + +- name: setup subnetwork + hcloud_subnetwork: + network: "{{ hcloud_network_name }}" + ip_range: "10.0.0.0/16" + type: "cloud" + network_zone: "eu-central" + state: present + register: subnetwork +- name: verify subnetwork + assert: + that: + - subnetwork is success + +- name: setup load_balancer + hcloud_load_balancer: + name: "{{hcloud_load_balancer_name}}" + load_balancer_type: lb11 + state: present + location: "fsn1" + register: load_balancer +- name: verify setup load_balancer + assert: + that: + - load_balancer is success + +- name: test missing required parameters on create load_balancer network + hcloud_load_balancer_network: + state: present + register: result + ignore_errors: yes +- name: verify fail test missing required parameters on create load_balancer network + assert: + that: + - result is failed + - '"missing required arguments:" in result.msg' + +- name: test fail load balancer does not exist + hetzner.hcloud.hcloud_load_balancer_network: + network: "{{ hcloud_network_name }}" + load_balancer: does-not-exist + state: present + register: result + ignore_errors: true +- name: verify test fail load_balancer does not exist + assert: + that: + - result is failed + - "result.msg == 'Load balancer does not exist: does-not-exist'" + +- name: test fail network does not exist + hetzner.hcloud.hcloud_load_balancer_network: + network: does-not-exist + load_balancer: "{{ hcloud_load_balancer_name }}" + state: present + register: result + ignore_errors: true +- name: verify test fail network does not exist + assert: + that: + - result is failed + - "result.msg == 'Network does not exist: does-not-exist'" + +- name: test create load_balancer network with checkmode + hcloud_load_balancer_network: + network: "{{ hcloud_network_name }}" + load_balancer: "{{hcloud_load_balancer_name}}" + state: present + register: result + check_mode: yes +- name: verify test create load_balancer network with checkmode + assert: + that: + - result is changed + +- name: test create load_balancer network + hcloud_load_balancer_network: + network: "{{ hcloud_network_name }}" + load_balancer: "{{hcloud_load_balancer_name}}" + state: present + register: load_balancerNetwork +- name: verify create load_balancer network + assert: + that: + - load_balancerNetwork is changed + - load_balancerNetwork.hcloud_load_balancer_network.network == hcloud_network_name + - load_balancerNetwork.hcloud_load_balancer_network.load_balancer == hcloud_load_balancer_name + +- name: test create load_balancer network idempotency + hcloud_load_balancer_network: + network: "{{ hcloud_network_name }}" + load_balancer: "{{hcloud_load_balancer_name}}" + state: present + register: load_balancerNetwork +- name: verify create load_balancer network idempotency + assert: + that: + - load_balancerNetwork is not changed + +- name: test absent load_balancer network + hcloud_load_balancer_network: + network: "{{ hcloud_network_name }}" + load_balancer: "{{hcloud_load_balancer_name}}" + state: absent + register: result +- name: verify test absent load_balancer network + assert: + that: + - result is changed + +- name: test create load_balancer network with specified ip + hcloud_load_balancer_network: + network: "{{ hcloud_network_name }}" + load_balancer: "{{hcloud_load_balancer_name}}" + ip: "10.0.0.2" + state: present + register: load_balancerNetwork +- name: verify create load_balancer network with specified ip + assert: + that: + - load_balancerNetwork is changed + - load_balancerNetwork.hcloud_load_balancer_network.network == hcloud_network_name + - load_balancerNetwork.hcloud_load_balancer_network.load_balancer == hcloud_load_balancer_name + - load_balancerNetwork.hcloud_load_balancer_network.ip == "10.0.0.2" + +- name: cleanup create load_balancer network with specified ip + hcloud_load_balancer_network: + network: "{{ hcloud_network_name }}" + load_balancer: "{{hcloud_load_balancer_name}}" + state: absent + register: result +- name: verify cleanup create load_balancer network with specified ip + assert: + that: + - result is changed + +- name: cleanup load_balancer + hcloud_load_balancer: + name: "{{ hcloud_load_balancer_name }}" + state: absent + register: result +- name: verify cleanup load_balancer + assert: + that: + - result is success + +- name: cleanup subnetwork + hcloud_subnetwork: + network: "{{ hcloud_network_name }}" + ip_range: "10.0.0.0/16" + type: "cloud" + network_zone: "eu-central" + state: absent + register: result +- name: verify cleanup subnetwork + assert: + that: + - result is changed + +- name: cleanup + hcloud_network: + name: "{{hcloud_network_name}}" + state: absent + register: result +- name: verify cleanup + assert: + that: + - result is success diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_service/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_service/aliases new file mode 100644 index 000000000..18dc30b6c --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_service/aliases @@ -0,0 +1,2 @@ +cloud/hcloud +shippable/hcloud/group1 diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_service/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_service/defaults/main.yml new file mode 100644 index 000000000..ebf456312 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_service/defaults/main.yml @@ -0,0 +1,5 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +hcloud_prefix: "tests" +hcloud_load_balancer_name: "{{hcloud_prefix}}-lb-target" diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_service/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_service/meta/main.yml new file mode 100644 index 000000000..407c9018a --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_service/meta/main.yml @@ -0,0 +1,3 @@ +collections: + - community.general.ipfilter + - hetzner.cloud diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_service/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_service/tasks/main.yml new file mode 100644 index 000000000..b0db6bb63 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_service/tasks/main.yml @@ -0,0 +1,126 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +- name: setup load_balancer + hcloud_load_balancer: + name: "{{hcloud_load_balancer_name}}" + load_balancer_type: lb11 + state: present + location: "fsn1" + register: load_balancer +- name: verify setup load_balancer + assert: + that: + - load_balancer is success + +- name: test fail load balancer does not exist + hetzner.hcloud.hcloud_load_balancer_service: + load_balancer: does-not-exist + protocol: http + listen_port: 80 + state: present + register: result + ignore_errors: true +- name: verify test fail load_balancer does not exist + assert: + that: + - result is failed + - "result.msg == 'Load balancer does not exist: does-not-exist'" + +- name: test create load_balancer service with checkmode + hcloud_load_balancer_service: + load_balancer: "{{hcloud_load_balancer_name}}" + protocol: "http" + listen_port: 80 + state: present + register: result + check_mode: yes +- name: verify test create load_balancer service with checkmode + assert: + that: + - result is changed + +- name: test create load_balancer service + hcloud_load_balancer_service: + load_balancer: "{{hcloud_load_balancer_name}}" + protocol: "http" + listen_port: 80 + state: present + register: load_balancer_service +- name: verify create load_balancer service + assert: + that: + - load_balancer_service is changed + - load_balancer_service.hcloud_load_balancer_service.protocol == "http" + - load_balancer_service.hcloud_load_balancer_service.listen_port == 80 + - load_balancer_service.hcloud_load_balancer_service.destination_port == 80 + - load_balancer_service.hcloud_load_balancer_service.proxyprotocol is sameas false + +- name: test create load_balancer service idempotency + hcloud_load_balancer_service: + load_balancer: "{{hcloud_load_balancer_name}}" + protocol: "http" + listen_port: 80 + state: present + register: load_balancer_service +- name: verify create load_balancer service idempotency + assert: + that: + - load_balancer_service is not changed + +- name: test update load_balancer service + hcloud_load_balancer_service: + load_balancer: "{{hcloud_load_balancer_name}}" + protocol: "tcp" + listen_port: 80 + state: present + register: load_balancer_service +- name: verify create load_balancer service + assert: + that: + - load_balancer_service is changed + - load_balancer_service.hcloud_load_balancer_service.protocol == "tcp" + - load_balancer_service.hcloud_load_balancer_service.listen_port == 80 + - load_balancer_service.hcloud_load_balancer_service.destination_port == 80 + - load_balancer_service.hcloud_load_balancer_service.proxyprotocol is sameas false + +- name: test absent load_balancer service + hcloud_load_balancer_service: + load_balancer: "{{hcloud_load_balancer_name}}" + protocol: "http" + listen_port: 80 + state: absent + register: result +- name: verify test absent load_balancer service + assert: + that: + - result is changed + +- name: test create load_balancer service with http + hcloud_load_balancer_service: + load_balancer: "{{hcloud_load_balancer_name}}" + protocol: "http" + listen_port: 80 + http: + cookie_name: "Test" + sticky_sessions: yes + state: present + register: load_balancer_service +- name: verify create load_balancer service + assert: + that: + - load_balancer_service is changed + - load_balancer_service.hcloud_load_balancer_service.protocol == "http" + - load_balancer_service.hcloud_load_balancer_service.listen_port == 80 + - load_balancer_service.hcloud_load_balancer_service.destination_port == 80 + - load_balancer_service.hcloud_load_balancer_service.proxyprotocol is sameas false + +- name: cleanup load_balancer + hcloud_load_balancer: + name: "{{ hcloud_load_balancer_name }}" + state: absent + register: result +- name: verify cleanup load_balancer + assert: + that: + - result is success diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_target/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_target/aliases new file mode 100644 index 000000000..18dc30b6c --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_target/aliases @@ -0,0 +1,2 @@ +cloud/hcloud +shippable/hcloud/group1 diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_target/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_target/defaults/main.yml new file mode 100644 index 000000000..180133fde --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_target/defaults/main.yml @@ -0,0 +1,7 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +hcloud_prefix: "tests" +hcloud_server_name: "{{ hcloud_prefix | truncate(45, True, '', 0) }}-lb-t" +hcloud_load_balancer_name: "{{hcloud_prefix}}-lb-target" +hcloud_testing_ip: "176.9.59.39" diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_target/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_target/meta/main.yml new file mode 100644 index 000000000..407c9018a --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_target/meta/main.yml @@ -0,0 +1,3 @@ +collections: + - community.general.ipfilter + - hetzner.cloud diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_target/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_target/tasks/main.yml new file mode 100644 index 000000000..bd96c1a54 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_target/tasks/main.yml @@ -0,0 +1,154 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +- name: setup server + hcloud_server: + name: "{{hcloud_server_name}}" + server_type: cx11 + image: ubuntu-20.04 + state: started + location: "fsn1" + register: server +- name: verify setup server + assert: + that: + - server is success + +- name: setup load_balancer + hcloud_load_balancer: + name: "{{hcloud_load_balancer_name}}" + load_balancer_type: lb11 + state: present + location: "fsn1" + register: load_balancer +- name: verify setup load_balancer + assert: + that: + - load_balancer is success + +- name: test fail load balancer does not exist + hetzner.hcloud.hcloud_load_balancer_target: + type: server + load_balancer: does-not-exist + server: "{{ hcloud_server_name }}" + register: result + ignore_errors: true +- name: verify test fail load_balancer does not exist + assert: + that: + - result is failed + - "result.msg == 'Load balancer does not exist: does-not-exist'" + +- name: test fail server does not exist + hetzner.hcloud.hcloud_load_balancer_target: + type: server + load_balancer: "{{ hcloud_load_balancer_name }}" + server: does-not-exist + register: result + ignore_errors: true +- name: verify test fail server does not exist + assert: + that: + - result is failed + - "result.msg == 'Server not found: does-not-exist'" + +- name: test create load_balancer target with checkmode + hcloud_load_balancer_target: + type: "server" + load_balancer: "{{hcloud_load_balancer_name}}" + server: "{{hcloud_server_name}}" + state: present + register: result + check_mode: yes +- name: verify test create load_balancer target with checkmode + assert: + that: + - result is changed + +- name: test create load_balancer target + hcloud_load_balancer_target: + type: "server" + load_balancer: "{{hcloud_load_balancer_name}}" + server: "{{hcloud_server_name}}" + state: present + register: load_balancer_target +- name: verify create load_balancer target + assert: + that: + - load_balancer_target is changed + - load_balancer_target.hcloud_load_balancer_target.type == "server" + - load_balancer_target.hcloud_load_balancer_target.server == hcloud_server_name + - load_balancer_target.hcloud_load_balancer_target.load_balancer == hcloud_load_balancer_name + +- name: test create load_balancer target idempotency + hcloud_load_balancer_target: + type: "server" + load_balancer: "{{hcloud_load_balancer_name}}" + server: "{{hcloud_server_name}}" + state: present + register: load_balancer_target +- name: verify create load_balancer target idempotency + assert: + that: + - load_balancer_target is not changed + +- name: test absent load_balancer target + hcloud_load_balancer_target: + type: "server" + load_balancer: "{{hcloud_load_balancer_name}}" + server: "{{hcloud_server_name}}" + state: absent + register: result +- name: verify test absent load_balancer target + assert: + that: + - result is changed + +- name: test create label_selector target + hcloud_load_balancer_target: + type: "label_selector" + load_balancer: "{{hcloud_load_balancer_name}}" + label_selector: "application=backend" + state: present + register: load_balancer_target +- name: verify create label_selector target + assert: + that: + - load_balancer_target is changed + - load_balancer_target.hcloud_load_balancer_target.type == "label_selector" + - load_balancer_target.hcloud_load_balancer_target.label_selector == "application=backend" + - load_balancer_target.hcloud_load_balancer_target.load_balancer == hcloud_load_balancer_name + +- name: test create ip target + hcloud_load_balancer_target: + type: "ip" + load_balancer: "{{hcloud_load_balancer_name}}" + ip: "{{hcloud_testing_ip}}" + state: present + register: load_balancer_target +- name: verify create ip target + assert: + that: + - load_balancer_target is changed + - load_balancer_target.hcloud_load_balancer_target.type == "ip" + - load_balancer_target.hcloud_load_balancer_target.ip == hcloud_testing_ip + - load_balancer_target.hcloud_load_balancer_target.load_balancer == hcloud_load_balancer_name + +- name: cleanup load_balancer + hcloud_load_balancer: + name: "{{ hcloud_load_balancer_name }}" + state: absent + register: result + until: result is not failed + retries: 5 + delay: 2 + +- name: cleanup + hcloud_server: + name: "{{hcloud_server_name}}" + state: absent + register: result +- name: verify cleanup + assert: + that: + - result is success diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_type_info/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_type_info/aliases new file mode 100644 index 000000000..55ec821a4 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_type_info/aliases @@ -0,0 +1,2 @@ +cloud/hcloud +shippable/hcloud/group2 diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_type_info/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_type_info/defaults/main.yml new file mode 100644 index 000000000..b7fd86316 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_type_info/defaults/main.yml @@ -0,0 +1,5 @@ +# Copyright: (c) 2020, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +hcloud_load_balancer_type_name: "lb11" +hcloud_load_balancer_type_id: 1 diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_type_info/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_type_info/meta/main.yml new file mode 100644 index 000000000..407c9018a --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_type_info/meta/main.yml @@ -0,0 +1,3 @@ +collections: + - community.general.ipfilter + - hetzner.cloud diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_type_info/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_type_info/tasks/main.yml new file mode 100644 index 000000000..bcd805a83 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_type_info/tasks/main.yml @@ -0,0 +1,38 @@ +# Copyright: (c) 2020, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +- name: test gather hcloud Load Balancer type infos + hcloud_load_balancer_type_info: + register: hcloud_load_balancer_types +- name: verify test gather hcloud Load Balancer type infos + assert: + that: + - hcloud_load_balancer_types.hcloud_load_balancer_type_info| list | count >= 1 + +- name: test gather hcloud Load Balancer type infos in check mode + hcloud_load_balancer_type_info: + check_mode: yes + register: hcloud_load_balancer_types + +- name: verify test gather hcloud Load Balancer type infos in check mode + assert: + that: + - hcloud_load_balancer_types.hcloud_load_balancer_type_info| list | count >= 1 + +- name: test gather hcloud Load Balancer type infos with name + hcloud_load_balancer_type_info: + name: "{{hcloud_load_balancer_type_name}}" + register: hcloud_load_balancer_types +- name: verify test gather hcloud Load Balancer type with name + assert: + that: + - hcloud_load_balancer_types.hcloud_load_balancer_type_info|selectattr('name','equalto','{{ hcloud_load_balancer_type_name }}') | list | count == 1 + +- name: test gather hcloud Load Balancer type infos with correct id + hcloud_load_balancer_type_info: + id: "{{hcloud_load_balancer_type_id}}" + register: hcloud_load_balancer_types +- name: verify test gather hcloud Load Balancer type with correct id + assert: + that: + - hcloud_load_balancer_types.hcloud_load_balancer_type_info|selectattr('name','equalto','{{ hcloud_load_balancer_type_name }}') | list | count == 1 diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_location_info/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_location_info/aliases new file mode 100644 index 000000000..55ec821a4 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_location_info/aliases @@ -0,0 +1,2 @@ +cloud/hcloud +shippable/hcloud/group2 diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_location_info/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_location_info/defaults/main.yml new file mode 100644 index 000000000..0d72a75c2 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_location_info/defaults/main.yml @@ -0,0 +1,5 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +hcloud_location_name: "fsn1" +hcloud_location_id: 1 diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_location_info/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_location_info/meta/main.yml new file mode 100644 index 000000000..407c9018a --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_location_info/meta/main.yml @@ -0,0 +1,3 @@ +collections: + - community.general.ipfilter + - hetzner.cloud diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_location_info/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_location_info/tasks/main.yml new file mode 100644 index 000000000..99d5880ab --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_location_info/tasks/main.yml @@ -0,0 +1,57 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +- name: test gather hcloud location infos + hcloud_location_info: + register: hcloud_location + +- name: verify test gather hcloud location infos + assert: + that: + - hcloud_location.hcloud_location_info | list | count >= 5 + +- name: test gather hcloud location infos in check mode + hcloud_location_info: + check_mode: yes + register: hcloud_location + +- name: verify test gather hcloud location infos in check mode + assert: + that: + - hcloud_location.hcloud_location_info | list | count >= 5 + +- name: test gather hcloud location infos with correct name + hcloud_location_info: + name: "{{hcloud_location_name}}" + register: hcloud_location +- name: verify test gather hcloud location with correct name + assert: + that: + - hcloud_location.hcloud_location_info|selectattr('name','equalto','{{ hcloud_location_name }}') | list | count == 1 + +- name: test gather hcloud location infos with wrong name + hcloud_location_info: + name: "{{hcloud_location_name}}1" + register: hcloud_location +- name: verify test gather hcloud location with wrong name + assert: + that: + - hcloud_location.hcloud_location_info | list | count == 0 + +- name: test gather hcloud location infos with correct id + hcloud_location_info: + id: "{{hcloud_location_id}}" + register: hcloud_location +- name: verify test gather hcloud location with correct id + assert: + that: + - hcloud_location.hcloud_location_info|selectattr('name','equalto','{{ hcloud_location_name }}') | list | count == 1 + +- name: test gather hcloud location infos with wrong id + hcloud_location_info: + name: "4711" + register: hcloud_location +- name: verify test gather hcloud location with wrong id + assert: + that: + - hcloud_location.hcloud_location_info | list | count == 0 diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network/aliases new file mode 100644 index 000000000..4b3a9b36f --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network/aliases @@ -0,0 +1,3 @@ +cloud/hcloud +shippable/hcloud/group1 +disabled diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network/defaults/main.yml new file mode 100644 index 000000000..081eb1472 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network/defaults/main.yml @@ -0,0 +1,5 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +hcloud_prefix: "tests" +hcloud_network_name: "{{hcloud_prefix}}-i" diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network/meta/main.yml new file mode 100644 index 000000000..407c9018a --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network/meta/main.yml @@ -0,0 +1,3 @@ +collections: + - community.general.ipfilter + - hetzner.cloud diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network/tasks/main.yml new file mode 100644 index 000000000..6c40e4e01 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network/tasks/main.yml @@ -0,0 +1,215 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +- name: setup + hcloud_network: + name: "{{ hcloud_network_name }}" + state: absent + register: result +- name: verify setup + assert: + that: + - result is success + +- name: test missing ip_range parameter on create Network + hcloud_network: + name: "{{hcloud_network_name}}" + register: result + ignore_errors: yes +- name: verify fail missing ip_range parameter on create Network result + assert: + that: + - result is failed + - 'result.msg == "missing required arguments: ip_range"' + +- name: test create Network with check mode + hcloud_network: + name: "{{hcloud_network_name}}" + ip_range: "10.0.0.0/16" + register: result + check_mode: yes +- name: verify create Network with check mode result + assert: + that: + - result is changed + +- name: test create Network + hcloud_network: + name: "{{hcloud_network_name}}" + ip_range: "10.0.0.0/16" + register: network +- name: verify test create Network result + assert: + that: + - network is changed + - network.hcloud_network.name == "{{hcloud_network_name}}" + - network.hcloud_network.ip_range == "10.0.0.0/16" + +- name: test create Network idempotence + hcloud_network: + name: "{{hcloud_network_name}}" + ip_range: "10.0.0.0/16" + register: network +- name: verify test create network + assert: + that: + - network is not changed + +- name: test update Network label + hcloud_network: + name: "{{hcloud_network_name}}" + labels: + key: value + register: network +- name: verify test update Network label + assert: + that: + - network is changed + - network.hcloud_network.labels.key == "value" + +- name: test update Network label idempotency + hcloud_network: + name: "{{hcloud_network_name}}" + labels: + key: value + register: network +- name: verify test update Network label idempotency + assert: + that: + - network is not changed + +- name: test update Network ip range + hcloud_network: + name: "{{hcloud_network_name}}" + ip_range: "10.0.0.0/8" + register: network +- name: verify test update Network ip range + assert: + that: + - network is changed + - network.hcloud_network.ip_range == "10.0.0.0/8" + +- name: test update Network ip range idempotency + hcloud_network: + name: "{{hcloud_network_name}}" + ip_range: "10.0.0.0/8" + register: network +- name: verify test update Network ip range idempotency + assert: + that: + - network is not changed + +- name: test update Network delete protection + hcloud_network: + name: "{{hcloud_network_name}}" + ip_range: "10.0.0.0/8" + delete_protection: true + register: network +- name: verify test update Network delete protection + assert: + that: + - network is changed + - network.hcloud_network.delete_protection is sameas true + +- name: test update Network delete protection idempotency + hcloud_network: + name: "{{hcloud_network_name}}" + ip_range: "10.0.0.0/8" + delete_protection: true + register: network +- name: verify test update Network delete protection idempotency + assert: + that: + - network is not changed + - network.hcloud_network.delete_protection is sameas true + +- name: test Network without delete protection set to be idempotent + hcloud_network: + name: "{{hcloud_network_name}}" + ip_range: "10.0.0.0/8" + register: network +- name: verify test Network without delete protection set to be idempotent + assert: + that: + - network is not changed + - network.hcloud_network.delete_protection is sameas true + +- name: test delete Network fails if it is protected + hcloud_network: + name: "{{hcloud_network_name}}" + state: absent + ignore_errors: yes + register: result +- name: verify delete Network + assert: + that: + - result is failed + - 'result.msg == "network deletion is protected"' + +- name: test update Network delete protection + hcloud_network: + name: "{{hcloud_network_name}}" + ip_range: "10.0.0.0/8" + delete_protection: false + register: network +- name: verify test update Network delete protection + assert: + that: + - network is changed + - network.hcloud_network.delete_protection is sameas false + +- name: test delete Network + hcloud_network: + name: "{{hcloud_network_name}}" + state: absent + register: result +- name: verify delete Network + assert: + that: + - result is success + + +- name: test create Network with delete protection + hcloud_network: + name: "{{hcloud_network_name}}" + ip_range: "10.0.0.0/8" + delete_protection: true + register: network +- name: verify create Network with delete protection + assert: + that: + - network is changed + - network.hcloud_network.delete_protection is sameas true + +- name: test delete Network fails if it is protected + hcloud_network: + name: "{{hcloud_network_name}}" + state: absent + ignore_errors: yes + register: result +- name: verify delete Network + assert: + that: + - result is failed + - 'result.msg == "network deletion is protected"' + +- name: test update Network delete protection + hcloud_network: + name: "{{hcloud_network_name}}" + delete_protection: false + register: network +- name: verify test update Network delete protection + assert: + that: + - network is changed + - network.hcloud_network.delete_protection is sameas false + +- name: test delete Network + hcloud_network: + name: "{{hcloud_network_name}}" + state: absent + register: result +- name: verify delete Network + assert: + that: + - result is success diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network_info/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network_info/aliases new file mode 100644 index 000000000..18dc30b6c --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network_info/aliases @@ -0,0 +1,2 @@ +cloud/hcloud +shippable/hcloud/group1 diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network_info/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network_info/defaults/main.yml new file mode 100644 index 000000000..f8a5279fb --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network_info/defaults/main.yml @@ -0,0 +1,5 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +hcloud_prefix: "tests" +hcloud_network_name: "{{hcloud_prefix}}-integration" diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network_info/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network_info/meta/main.yml new file mode 100644 index 000000000..407c9018a --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network_info/meta/main.yml @@ -0,0 +1,3 @@ +collections: + - community.general.ipfilter + - hetzner.cloud diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network_info/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network_info/tasks/main.yml new file mode 100644 index 000000000..e7924a8d0 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network_info/tasks/main.yml @@ -0,0 +1,117 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- + +- name: setup ensure network is absent + hcloud_network: + name: "{{ hcloud_network_name }}" + state: absent + register: result + +- name: create network + hcloud_network: + name: "{{ hcloud_network_name }}" + ip_range: "10.0.0.0/16" + labels: + key: value + register: main_network +- name: verify create network + assert: + that: + - main_network is changed + - main_network.hcloud_network.name == "{{ hcloud_network_name }}" + - main_network.hcloud_network.ip_range == "10.0.0.0/16" +- name: create subnetwork + hcloud_subnetwork: + network: "{{ hcloud_network_name }}" + type: server + network_zone: eu-central + ip_range: "10.0.1.0/24" + register: main_subnetwork +- name: verify create subnetwork + assert: + that: + - main_subnetwork is changed + - main_subnetwork.hcloud_subnetwork.network == "{{ hcloud_network_name }}" +- name: create route + hcloud_route: + network: "{{ hcloud_network_name }}" + destination: "10.0.3.0/24" + gateway: "10.0.2.1" + register: main_route +- name: verify create route + assert: + that: + - main_route is changed + - main_route.hcloud_route.network == "{{ hcloud_network_name }}" + +- name: test gather hcloud network info in check mode + hcloud_network_info: + check_mode: yes + register: hcloud_network +- name: verify test gather hcloud network info in check mode + assert: + that: + - hcloud_network.hcloud_network_info | selectattr('name','equalto','{{ hcloud_network_name }}') | list | count >= 1 + + +- name: test gather hcloud network info with correct label selector + hcloud_network_info: + label_selector: "key=value" + register: hcloud_network +- name: verify test gather hcloud network with correct label selector + assert: + that: + - hcloud_network.hcloud_network_info | selectattr('name','equalto','{{ hcloud_network_name }}') | list | count >= 1 + +- name: test gather hcloud network info with wrong label selector + hcloud_network_info: + label_selector: "key!=value" + register: hcloud_network +- name: verify test gather hcloud network with wrong label selector + assert: + that: + - hcloud_network.hcloud_network_info | list | count == 0 + +- name: test gather hcloud network info with correct name + hcloud_network_info: + name: "{{hcloud_network_name}}" + register: hcloud_network +- name: verify test gather hcloud network with correct name + assert: + that: + - hcloud_network.hcloud_network_info | selectattr('name','equalto','{{ hcloud_network_name }}') | list | count == 1 + - hcloud_network.hcloud_network_info[0].subnetworks | list | count >= 1 + - hcloud_network.hcloud_network_info[0].routes | list | count >= 1 + +- name: test gather hcloud network info with wrong name + hcloud_network_info: + name: "{{hcloud_network_name}}1" + register: hcloud_network +- name: verify test gather hcloud network with wrong name + assert: + that: + - hcloud_network.hcloud_network_info | list | count == 0 + +- name: test gather hcloud network info with correct id + hcloud_network_info: + id: "{{main_network.hcloud_network.id}}" + register: hcloud_network +- name: verify test gather hcloud network with correct id + assert: + that: + - hcloud_network.hcloud_network_info | selectattr('name','equalto','{{ hcloud_network_name }}') | list | count == 1 + +- name: test gather hcloud network info with wrong id + hcloud_network_info: + name: "4711" + register: hcloud_network +- name: verify test gather hcloud network with wrong id + assert: + that: + - hcloud_network.hcloud_network_info | list | count == 0 + +- name: cleanup + hcloud_network: + name: "{{ hcloud_network_name }}" + state: absent diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_placement_group/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_placement_group/aliases new file mode 100644 index 000000000..55ec821a4 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_placement_group/aliases @@ -0,0 +1,2 @@ +cloud/hcloud +shippable/hcloud/group2 diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_placement_group/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_placement_group/defaults/main.yml new file mode 100644 index 000000000..21ce3429a --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_placement_group/defaults/main.yml @@ -0,0 +1,6 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +hcloud_prefix: "tests" +hcloud_placement_group_name: "{{hcloud_prefix}}-i" +hcloud_server_name: "{{ hcloud_prefix | truncate(45, True, '', 0) }}-i" diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_placement_group/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_placement_group/meta/main.yml new file mode 100644 index 000000000..407c9018a --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_placement_group/meta/main.yml @@ -0,0 +1,3 @@ +collections: + - community.general.ipfilter + - hetzner.cloud diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_placement_group/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_placement_group/tasks/main.yml new file mode 100644 index 000000000..d79aa0c35 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_placement_group/tasks/main.yml @@ -0,0 +1,169 @@ +# Copyright: (c) 2020, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +- name: setup placement group to be absent + hcloud_placement_group: + name: "{{ hcloud_placement_group_name }}" + state: absent + +- name: setup server to be absent + hcloud_server: + name: "{{ hcloud_server_name }}" + state: absent + +- name: test missing required parameters on create placement group + hcloud_placement_group: + register: result + ignore_errors: yes +- name: verify fail test missing required parameters on create placement group + assert: + that: + - result is failed + - 'result.msg == "one of the following is required: id, name"' + +- name: test create placement group with check mode + hcloud_placement_group: + name: "{{ hcloud_placement_group_name }}" + type: spread + register: result + check_mode: yes +- name: test create placement group with check mode + assert: + that: + - result is changed + +- name: test create placement group + hcloud_placement_group: + name: "{{ hcloud_placement_group_name }}" + type: spread + labels: + key: value + my-label: label + register: placement_group +- name: verify create placement group + assert: + that: + - placement_group is changed + - placement_group.hcloud_placement_group.name == "{{ hcloud_placement_group_name }}" + - placement_group.hcloud_placement_group.type == "spread" + - placement_group.hcloud_placement_group.servers | list | count == 0 + +- name: test create placement group idempotence + hcloud_placement_group: + name: "{{ hcloud_placement_group_name }}" + type: spread + labels: + key: value + my-label: label + register: result +- name: verify create placement group idempotence + assert: + that: + - result is not changed + +- name: test create server with placement group + hcloud_server: + name: "{{ hcloud_server_name }}" + server_type: cpx11 + placement_group: "{{ hcloud_placement_group_name }}" + image: "ubuntu-20.04" + ssh_keys: + - ci@ansible.hetzner.cloud + state: present + register: server +- name: verify create server with placement group + assert: + that: + - server is changed + - server.hcloud_server.placement_group == "{{ hcloud_placement_group_name }}" + +- name: test remove server from placement group + hcloud_server: + name: "{{ hcloud_server_name }}" + placement_group: null + state: present + register: result +- name: verify remove server from placement group + assert: + that: + - result is changed + - result.hcloud_server.placement_group == None + +- name: test add server to placement group + hcloud_server: + name: "{{ hcloud_server_name }}" + placement_group: "{{ hcloud_placement_group_name }}" + force: True + state: present + register: result +- name: verify add server to placement group + assert: + that: + - result is changed + - result.hcloud_server.placement_group == "{{ hcloud_placement_group_name }}" + - result.hcloud_server.status == "running" + +- name: test add server to placement group idempotence + hcloud_server: + name: "{{ hcloud_server_name }}" + placement_group: "{{ hcloud_placement_group_name }}" + force: True + state: present + register: result +- name: verify add server to placement group idempotence + assert: + that: + - result is not changed + - result.hcloud_server.placement_group == "{{ hcloud_placement_group_name }}" + - result.hcloud_server.status == "running" + +- name: test update placement group with check mode + hcloud_placement_group: + id: "{{ placement_group.hcloud_placement_group.id }}" + name: "changed-{{ hcloud_placement_group_name }}" + register: result + check_mode: yes +- name: verify update placement group with check mode + assert: + that: + - result is changed + +- name: test update placement group + hcloud_placement_group: + id: "{{ placement_group.hcloud_placement_group.id }}" + name: "changed-{{ hcloud_placement_group_name }}" + labels: + key: value + register: result +- name: verify update placement group + assert: + that: + - result is changed + - result.hcloud_placement_group.name == "changed-{{ hcloud_placement_group_name }}" + +- name: test update placement group idempotence + hcloud_placement_group: + id: "{{ placement_group.hcloud_placement_group.id }}" + name: "changed-{{ hcloud_placement_group_name }}" + labels: + key: value + register: result +- name: verify update placement group idempotence + assert: + that: + - result is not changed + +- name: absent server + hcloud_server: + id: "{{ server.hcloud_server.id }}" + state: absent + +- name: absent placement group + hcloud_placement_group: + id: "{{ placement_group.hcloud_placement_group.id }}" + state: absent + register: result +- name: verify absent placement group + assert: + that: + - result is success diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_primary_ip/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_primary_ip/aliases new file mode 100644 index 000000000..55ec821a4 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_primary_ip/aliases @@ -0,0 +1,2 @@ +cloud/hcloud +shippable/hcloud/group2 diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_primary_ip/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_primary_ip/defaults/main.yml new file mode 100644 index 000000000..98aa28eea --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_primary_ip/defaults/main.yml @@ -0,0 +1,6 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +hcloud_prefix: "tests" +hcloud_primary_ip_name: "{{hcloud_prefix}}-i" +hcloud_server_name: "{{ hcloud_prefix | truncate(45, True, '', 0) }}-fip-t" diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_primary_ip/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_primary_ip/meta/main.yml new file mode 100644 index 000000000..407c9018a --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_primary_ip/meta/main.yml @@ -0,0 +1,3 @@ +collections: + - community.general.ipfilter + - hetzner.cloud diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_primary_ip/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_primary_ip/tasks/main.yml new file mode 100644 index 000000000..d4efc606b --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_primary_ip/tasks/main.yml @@ -0,0 +1,243 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +- name: test create Primary IP with check mode + hcloud_primary_ip: + name: "{{ hcloud_primary_ip_name }}" + type: ipv4 + datacenter: "fsn1-dc14" + register: primaryIP + check_mode: yes +- name: verify test create Primary IP with check mode + assert: + that: + - primaryIP is changed + +- name: test create Primary IP + hcloud_primary_ip: + name: "{{ hcloud_primary_ip_name }}" + type: ipv4 + datacenter: "fsn1-dc14" + register: primaryIP +- name: verify test create Primary IP + assert: + that: + - primaryIP is changed + - primaryIP.hcloud_primary_ip.name ==hcloud_primary_ip_name + - primaryIP.hcloud_primary_ip.datacenter == "fsn1-dc14" + +- name: test create Primary IP idempotency + hcloud_primary_ip: + name: "{{ hcloud_primary_ip_name }}" + type: ipv4 + datacenter: "fsn1-dc14" + register: primaryIP +- name: verify test create Primary IP idempotency + assert: + that: + - primaryIP is not changed + +- name: test update Primary IP + hcloud_primary_ip: + name: "{{ hcloud_primary_ip_name }}" + type: ipv4 + datacenter: "fsn1-dc14" + labels: + key: value + register: primaryIP +- name: verify test update Primary IP + assert: + that: + - primaryIP is changed + +- name: test update Primary IP idempotency + hcloud_primary_ip: + name: "{{ hcloud_primary_ip_name }}" + type: ipv4 + datacenter: "fsn1-dc14" + labels: + key: value + register: primaryIP +- name: verify test update Primary IP idempotency + assert: + that: + - primaryIP is not changed + +- name: test update Primary IP with same labels + hcloud_primary_ip: + name: "{{ hcloud_primary_ip_name }}" + type: ipv4 + datacenter: "fsn1-dc14" + labels: + key: value + register: primaryIP +- name: verify test update Primary IP with same labels + assert: + that: + - primaryIP is not changed + +- name: test update Primary IP with other labels + hcloud_primary_ip: + name: "{{ hcloud_primary_ip_name }}" + type: ipv4 + datacenter: "fsn1-dc14" + labels: + key: value + other: label + register: primaryIP +- name: verify test update Primary IP with other labels + assert: + that: + - primaryIP is changed + +- name: test update Primary IP with other labels in different order + hcloud_primary_ip: + name: "{{ hcloud_primary_ip_name }}" + type: ipv4 + datacenter: "fsn1-dc14" + labels: + other: label + key: value + register: primaryIP +- name: verify test update Primary IP with other labels in different order + assert: + that: + - primaryIP is not changed + +- name: test update Primary IP delete protection + hcloud_primary_ip: + name: "{{ hcloud_primary_ip_name }}" + type: ipv4 + delete_protection: true + register: primaryIP +- name: verify update Primary IP delete protection + assert: + that: + - primaryIP is changed + - primaryIP.hcloud_primary_ip.delete_protection is sameas true + +- name: test update Primary IP delete protection idempotency + hcloud_primary_ip: + name: "{{ hcloud_primary_ip_name }}" + type: ipv4 + delete_protection: true + register: primaryIP +- name: verify update Primary IP delete protection idempotency + assert: + that: + - primaryIP is not changed + - primaryIP.hcloud_primary_ip.delete_protection is sameas true + +- name: test Primary IP without delete protection set to be idempotent + hcloud_primary_ip: + name: "{{ hcloud_primary_ip_name }}" + type: ipv4 + register: primaryIP +- name: verify Primary IP without delete protection set to be idempotent + assert: + that: + - primaryIP is not changed + - primaryIP.hcloud_primary_ip.delete_protection is sameas true + +- name: test delete Primary IP fails if it is protected + hcloud_primary_ip: + name: "{{ hcloud_primary_ip_name }}" + state: "absent" + register: result + ignore_errors: yes +- name: verify test delete primary ip + assert: + that: + - result is failed + - 'result.msg == "Primary IP deletion is protected"' + +- name: test update Primary IP delete protection + hcloud_primary_ip: + name: "{{ hcloud_primary_ip_name }}" + type: ipv4 + delete_protection: false + register: primaryIP +- name: verify update Primary IP delete protection + assert: + that: + - primaryIP is changed + - primaryIP.hcloud_primary_ip.delete_protection is sameas false + +- name: test delete primary ip + hcloud_primary_ip: + name: "{{ hcloud_primary_ip_name }}" + state: "absent" + register: result +- name: verify test delete primary ip + assert: + that: + - result is changed + +- name: test create ipv6 primary ip + hcloud_primary_ip: + name: "{{ hcloud_primary_ip_name }}" + type: ipv6 + datacenter: "fsn1-dc14" + state: "present" + register: result +- name: verify test create ipv6 primary ip + assert: + that: + - result is changed + +- name: test delete ipv6 primary ip + hcloud_primary_ip: + name: "{{ hcloud_primary_ip_name }}" + state: "absent" + register: result +- name: verify test delete ipv6 primary ip + assert: + that: + - result is changed + +- name: test create Primary IP with delete protection + hcloud_primary_ip: + name: "{{ hcloud_primary_ip_name }}" + type: ipv4 + datacenter: fsn1-dc14 + delete_protection: true + register: primaryIP +- name: verify create Primary IP with delete protection + assert: + that: + - primaryIP is changed + - primaryIP.hcloud_primary_ip.delete_protection is sameas true + +- name: test delete Primary IP fails if it is protected + hcloud_primary_ip: + name: "{{ hcloud_primary_ip_name }}" + state: "absent" + register: result + ignore_errors: yes +- name: verify test delete primary ip + assert: + that: + - result is failed + - 'result.msg == "Primary IP deletion is protected"' + +- name: test update Primary IP delete protection + hcloud_primary_ip: + name: "{{ hcloud_primary_ip_name }}" + type: ipv4 + delete_protection: false + register: primaryIP +- name: verify update Primary IP delete protection + assert: + that: + - primaryIP is changed + - primaryIP.hcloud_primary_ip.delete_protection is sameas false + +- name: test delete primary ip + hcloud_primary_ip: + name: "{{ hcloud_primary_ip_name }}" + state: "absent" + register: result +- name: verify test delete primary ip + assert: + that: + - result is changed diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_rdns/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_rdns/aliases new file mode 100644 index 000000000..18dc30b6c --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_rdns/aliases @@ -0,0 +1,2 @@ +cloud/hcloud +shippable/hcloud/group1 diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_rdns/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_rdns/defaults/main.yml new file mode 100644 index 000000000..50117a8a5 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_rdns/defaults/main.yml @@ -0,0 +1,8 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +hcloud_prefix: "tests" +hcloud_server_name: "{{ hcloud_prefix | truncate(45, True, '', 0) }}" +hcloud_floating_ip_name: "{{hcloud_prefix}}" +hcloud_primary_ip_name: "{{hcloud_prefix}}" +hcloud_load_balancer_name: "{{hcloud_prefix}}" diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_rdns/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_rdns/meta/main.yml new file mode 100644 index 000000000..67d54d732 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_rdns/meta/main.yml @@ -0,0 +1,3 @@ +collections: + - ansible.netcommon + - hetzner.cloud diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_rdns/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_rdns/tasks/main.yml new file mode 100644 index 000000000..dddbac0d0 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_rdns/tasks/main.yml @@ -0,0 +1,224 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +- name: setup + hcloud_server: + name: "{{ hcloud_server_name }}" + server_type: cx11 + image: "ubuntu-22.04" + ssh_keys: + - ci@ansible.hetzner.cloud + state: present + register: setup +- name: verify setup + assert: + that: + - setup is success + +- name: setup Floating IP + hcloud_floating_ip: + name: "{{ hcloud_floating_ip_name }}" + type: ipv4 + home_location: "fsn1" + register: floatingIP +- name: verify setup Floating IP + assert: + that: + - floatingIP is success + +- name: setup Load Balancer + hcloud_load_balancer: + name: "{{ hcloud_load_balancer_name}}" + load_balancer_type: lb11 + network_zone: eu-central + state: present + register: load_balancer +- name: verify setup + assert: + that: + - load_balancer is success + +- name: setup Primary IP + hcloud_primary_ip: + name: "{{ hcloud_primary_ip_name }}" + type: ipv4 + datacenter: "fsn1-dc14" + register: primaryIP +- name: verify setup Primary IP + assert: + that: + - primaryIP is success + +- name: test missing required parameter + hcloud_rdns: + state: present + register: result + ignore_errors: yes +- name: verify fail test missing required parameters + assert: + that: + - result is failed + - 'result.msg == "missing required arguments: ip_address"' +- name: test fail on not existing resource + hcloud_rdns: + server: "not-existing" + ip_address: "127.0.0.1" + state: present + register: result + ignore_errors: yes +- name: verify fail on not existing resou + assert: + that: + - result is failed + - 'result.msg == "The selected server does not exist"' +- name: test create rdns + hcloud_rdns: + server: "{{ hcloud_server_name }}" + ip_address: "{{ setup.hcloud_server.ipv6 | ansible.netcommon.ipaddr('next_usable') }}" + dns_ptr: "example.com" + state: present + register: rdns +- name: verify create rdns + assert: + that: + - rdns is changed + - rdns.hcloud_rdns.server == "{{ hcloud_server_name }}" + - rdns.hcloud_rdns.ip_address == "{{ setup.hcloud_server.ipv6 | ansible.netcommon.ipaddr('next_usable') }}" + - rdns.hcloud_rdns.dns_ptr == "example.com" + +- name: test create rdns idempotency + hcloud_rdns: + server: "{{ hcloud_server_name }}" + ip_address: "{{ setup.hcloud_server.ipv6 | ansible.netcommon.ipaddr('next_usable') }}" + dns_ptr: "example.com" + state: present + register: result +- name: verify create rdns idempotency + assert: + that: + - result is not changed + +- name: test absent rdns + hcloud_rdns: + server: "{{ hcloud_server_name }}" + ip_address: "{{ setup.hcloud_server.ipv6 | ansible.netcommon.ipaddr('next_usable') }}" + state: absent + register: result +- name: verify test absent rdns + assert: + that: + - result is changed + +- name: test update rdns + hcloud_rdns: + server: "{{ hcloud_server_name }}" + ip_address: "{{ setup.hcloud_server.ipv4_address }}" + dns_ptr: "example.com" + state: present + register: rdns +- name: verify update rdns + assert: + that: + - rdns is changed + - rdns.hcloud_rdns.server == "{{ hcloud_server_name }}" + - rdns.hcloud_rdns.ip_address == "{{ setup.hcloud_server.ipv4_address }}" + - rdns.hcloud_rdns.dns_ptr == "example.com" + +- name: test reset rdns + hcloud_rdns: + server: "{{ hcloud_server_name }}" + ip_address: "{{ setup.hcloud_server.ipv4_address }}" + state: present + register: rdns +- name: verify reset rdns + assert: + that: + - rdns is changed + - rdns.hcloud_rdns.server == "{{ hcloud_server_name }}" + - rdns.hcloud_rdns.ip_address == "{{ setup.hcloud_server.ipv4_address }}" + - rdns.hcloud_rdns.dns_ptr != "example.com" + +- name: test create rdns with floating IP + hcloud_rdns: + floating_ip: "{{ hcloud_floating_ip_name }}" + ip_address: "{{ floatingIP.hcloud_floating_ip.ip}}" + dns_ptr: "example.com" + state: present + register: rdns +- name: verify create rdns + assert: + that: + - rdns is changed + - rdns.hcloud_rdns.floating_ip == "{{ hcloud_floating_ip_name }}" + - rdns.hcloud_rdns.ip_address == "{{ floatingIP.hcloud_floating_ip.ip}}" + - rdns.hcloud_rdns.dns_ptr == "example.com" + +- name: test create rdns with primary IP + hcloud_rdns: + primary_ip: "{{ hcloud_primary_ip_name }}" + ip_address: "{{ primaryIP.hcloud_primary_ip.ip}}" + dns_ptr: "example.com" + state: present + register: rdns +- name: verify create rdns + assert: + that: + - rdns is changed + - rdns.hcloud_rdns.primary_ip == "{{ hcloud_primary_ip_name }}" + - rdns.hcloud_rdns.ip_address == "{{ primaryIP.hcloud_primary_ip.ip}}" + - rdns.hcloud_rdns.dns_ptr == "example.com" + +- name: test create rdns with load balancer + hcloud_rdns: + load_balancer: "{{ hcloud_load_balancer_name }}" + ip_address: "{{ load_balancer.hcloud_load_balancer.ipv4_address }}" + dns_ptr: "example.com" + state: present + register: rdns +- name: verify create rdns with load balancer + assert: + that: + - rdns is changed + - rdns.hcloud_rdns.load_balancer == "{{ hcloud_load_balancer_name }}" + - rdns.hcloud_rdns.ip_address == "{{ load_balancer.hcloud_load_balancer.ipv4_address }}" + - rdns.hcloud_rdns.dns_ptr == "example.com" + +- name: cleanup + hcloud_server: + name: "{{ hcloud_server_name }}" + state: absent + register: result +- name: verify cleanup + assert: + that: + - result is success + +- name: cleanup + hcloud_floating_ip: + name: "{{ hcloud_floating_ip_name }}" + state: absent + register: result +- name: verify cleanup + assert: + that: + - result is success + +- name: cleanup + hcloud_primary_ip: + name: "{{ hcloud_primary_ip_name }}" + state: absent + register: result +- name: verify cleanup + assert: + that: + - result is success + +- name: cleanup + hcloud_load_balancer: + name: "{{ hcloud_load_balancer_name }}" + state: absent + register: result +- name: verify cleanup + assert: + that: + - result is success diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_route/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_route/aliases new file mode 100644 index 000000000..18dc30b6c --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_route/aliases @@ -0,0 +1,2 @@ +cloud/hcloud +shippable/hcloud/group1 diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_route/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_route/defaults/main.yml new file mode 100644 index 000000000..c93c7495e --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_route/defaults/main.yml @@ -0,0 +1,5 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +hcloud_prefix: "tests" +hcloud_network_name: "{{hcloud_prefix}}-ro" diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_route/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_route/meta/main.yml new file mode 100644 index 000000000..67d54d732 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_route/meta/main.yml @@ -0,0 +1,3 @@ +collections: + - ansible.netcommon + - hetzner.cloud diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_route/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_route/tasks/main.yml new file mode 100644 index 000000000..7d816bf5c --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_route/tasks/main.yml @@ -0,0 +1,99 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +- name: setup + hcloud_network: + name: "{{ hcloud_network_name }}" + ip_range: "10.0.0.0/8" + state: present + register: network +- name: verify setup + assert: + that: + - network is success + +- name: test missing required parameters on create route + hcloud_route: + state: present + register: result + ignore_errors: yes +- name: verify fail test missing required parameters on create route + assert: + that: + - result is failed + - 'result.msg == "missing required arguments: destination, gateway, network"' + +- name: test create route with checkmode + hcloud_route: + network: "{{ hcloud_network_name }}" + destination: "10.100.1.0/24" + gateway: "10.0.1.1" + state: present + register: result + check_mode: yes +- name: verify test create route with checkmode + assert: + that: + - result is changed + +- name: test create route + hcloud_route: + network: "{{ hcloud_network_name }}" + destination: "10.100.1.0/24" + gateway: "10.0.1.1" + state: present + register: route +- name: verify create route + assert: + that: + - route is changed + - route.hcloud_route.network == "{{ hcloud_network_name }}" + - route.hcloud_route.destination == "10.100.1.0/24" + - route.hcloud_route.gateway == "10.0.1.1" + +- name: test create route idempotency + hcloud_route: + network: "{{ hcloud_network_name }}" + destination: "10.100.1.0/24" + gateway: "10.0.1.1" + state: present + register: result +- name: verify create route idempotency + assert: + that: + - result is not changed + +- name: test fail create route with wrong gateway + hcloud_route: + network: "{{ hcloud_network_name }}" + destination: "10.100.1.0/24" + gateway: "10.0.1.2" + state: present + register: route + ignore_errors: yes +- name: verfiy fail create route with wrong gateway + assert: + that: + - route is failed + +- name: test absent route + hcloud_route: + network: "{{ hcloud_network_name }}" + destination: "10.100.1.0/24" + gateway: "10.0.1.1" + state: absent + register: result +- name: verify test absent route + assert: + that: + - result is changed + +- name: cleanup + hcloud_network: + name: "{{hcloud_network_name}}" + state: absent + register: result +- name: verify cleanup + assert: + that: + - result is success diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/aliases new file mode 100644 index 000000000..18dc30b6c --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/aliases @@ -0,0 +1,2 @@ +cloud/hcloud +shippable/hcloud/group1 diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/defaults/main.yml new file mode 100644 index 000000000..4e1c4dc45 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/defaults/main.yml @@ -0,0 +1,8 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +hcloud_prefix: "tests" +hcloud_server_name: "{{ hcloud_prefix | truncate(45, True, '', 0) }}-i" +hcloud_firewall_name: "{{hcloud_prefix}}-i" +hcloud_primary_ip_name: "{{hcloud_prefix}}-i" +hcloud_network_name: "{{hcloud_prefix}}-i" diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/meta/main.yml new file mode 100644 index 000000000..407c9018a --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/meta/main.yml @@ -0,0 +1,3 @@ +collections: + - community.general.ipfilter + - hetzner.cloud diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/tasks/basic.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/tasks/basic.yml new file mode 100644 index 000000000..ac609fc6c --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/tasks/basic.yml @@ -0,0 +1,615 @@ +- name: test create server with check mode + hcloud_server: + name: "{{ hcloud_server_name }}" + server_type: cx11 + image: ubuntu-20.04 + state: present + register: result + check_mode: yes +- name: test create server server + assert: + that: + - result is changed + +- name: test create server + hcloud_server: + name: "{{ hcloud_server_name}}" + server_type: cx11 + image: ubuntu-20.04 + enable_ipv6: False + state: started + register: main_server +- name: verify create server + assert: + that: + - main_server is changed + - main_server.hcloud_server.name == "{{ hcloud_server_name }}" + - main_server.hcloud_server.server_type == "cx11" + - main_server.hcloud_server.status == "running" + - main_server.root_password != "" + +- name: test create server idempotence + hcloud_server: + name: "{{ hcloud_server_name }}" + state: started + register: result +- name: verify create server idempotence + assert: + that: + - result is not changed + +- name: test stop server with check mode + hcloud_server: + name: "{{ hcloud_server_name }}" + state: stopped + register: result + check_mode: yes +- name: verify stop server with check mode + assert: + that: + - result is changed + - result.hcloud_server.status == "running" + +- name: test stop server + hcloud_server: + name: "{{ hcloud_server_name }}" + state: stopped + register: result +- name: verify stop server + assert: + that: + - result is changed + - result.hcloud_server.status == "off" + +- name: test start server with check mode + hcloud_server: + name: "{{ hcloud_server_name }}" + state: started + register: result + check_mode: true +- name: verify start server with check mode + assert: + that: + - result is changed + +- name: test start server + hcloud_server: + name: "{{ hcloud_server_name }}" + state: started + register: result +- name: verify start server + assert: + that: + - result is changed + - result.hcloud_server.status == "running" + +- name: test start server idempotence + hcloud_server: + name: "{{ hcloud_server_name }}" + state: started + register: result +- name: verify start server idempotence + assert: + that: + - result is not changed + - result.hcloud_server.status == "running" + +- name: test stop server by its id + hcloud_server: + id: "{{ main_server.hcloud_server.id }}" + state: stopped + register: result +- name: verify stop server by its id + assert: + that: + - result is changed + - result.hcloud_server.status == "off" + +- name: test resize server running without force + hcloud_server: + name: "{{ hcloud_server_name }}" + server_type: "cx21" + state: present + register: result + check_mode: true +- name: verify test resize server running without force + assert: + that: + - result is changed + - result.hcloud_server.server_type == "cx11" + +- name: test resize server with check mode + hcloud_server: + name: "{{ hcloud_server_name }}" + server_type: "cx21" + state: stopped + register: result + check_mode: true +- name: verify resize server with check mode + assert: + that: + - result is changed + +- name: test resize server without disk + hcloud_server: + name: "{{ hcloud_server_name }}" + server_type: "cx21" + state: stopped + register: result +- name: verify resize server without disk + assert: + that: + - result is changed + - result.hcloud_server.server_type == "cx21" + +- name: test resize server idempotence + hcloud_server: + name: "{{ hcloud_server_name }}" + server_type: "cx21" + state: stopped + register: result +- name: verify resize server idempotence + assert: + that: + - result is not changed + +- name: test resize server to smaller plan + hcloud_server: + name: "{{ hcloud_server_name }}" + server_type: "cx11" + state: stopped + register: result +- name: verify resize server to smaller plan + assert: + that: + - result is changed + - result.hcloud_server.server_type == "cx11" + +- name: test resize server with disk + hcloud_server: + name: "{{ hcloud_server_name }}" + server_type: "cx21" + upgrade_disk: true + state: stopped + register: result +- name: verify resize server with disk + assert: + that: + - result is changed + - result.hcloud_server.server_type == "cx21" + +- name: test enable backups with check mode + hcloud_server: + name: "{{ hcloud_server_name }}" + backups: true + state: stopped + register: result + check_mode: true +- name: verify enable backups with check mode + assert: + that: + - result is changed + +- name: test enable backups + hcloud_server: + name: "{{ hcloud_server_name }}" + backups: true + state: stopped + register: result +- name: verify enable backups + assert: + that: + - result is changed + - result.hcloud_server.backup_window != "" + +- name: test enable backups idempotence + hcloud_server: + name: "{{ hcloud_server_name }}" + backups: true + state: stopped + register: result +- name: verify enable backups idempotence + assert: + that: + - result is not changed + - result.hcloud_server.backup_window != "" + +- name: test backups are not accidentally disabled + hcloud_server: + name: "{{ hcloud_server_name }}" + # Make sure that backups are not disabled because a partial server object without "backups" was supplied somewhere + # to update some unrelated properties. + # Regression test for https://github.com/ansible-collections/hetzner.hcloud/pull/196 + # backups: true + state: stopped + register: result +- name: verify backups are not accidentally disabled + assert: + that: + - result is not changed + - result.hcloud_server.backup_window != "" + +- name: test rebuild server + hcloud_server: + name: "{{ hcloud_server_name }}" + image: ubuntu-20.04 + state: rebuild + register: result_after_test +- name: verify rebuild server + assert: + that: + - result_after_test is changed + - result.hcloud_server.id == result_after_test.hcloud_server.id + +- name: test rebuild server with check mode + hcloud_server: + name: "{{ hcloud_server_name }}" + image: ubuntu-20.04 + state: rebuild + register: result_after_test + check_mode: true +- name: verify rebuild server with check mode + assert: + that: + - result_after_test is changed + +- name: test update server protection booth protection arguments are required + hcloud_server: + name: "{{ hcloud_server_name }}" + delete_protection: true + state: present + register: result_after_test + ignore_errors: true +- name: verify update server protection booth protection arguments are required + assert: + that: + - result_after_test is failed + - 'result_after_test.msg == "parameters are required together: delete_protection, rebuild_protection"' + +- name: test update server protection fails if they are not the same + hcloud_server: + name: "{{ hcloud_server_name }}" + delete_protection: true + rebuild_protection: false + state: present + register: result_after_test + ignore_errors: true +- name: verify update server protection fails if they are not the same + assert: + that: + - result_after_test is failed + +- name: test update server protection + hcloud_server: + name: "{{ hcloud_server_name }}" + delete_protection: true + rebuild_protection: true + state: present + register: result_after_test + ignore_errors: true +- name: verify update server protection + assert: + that: + - result_after_test is changed + - result_after_test.hcloud_server.delete_protection is sameas true + - result_after_test.hcloud_server.rebuild_protection is sameas true + +- name: test server without protection set to be idempotent + hcloud_server: + name: "{{hcloud_server_name}}" + register: result_after_test +- name: verify test server without protection set to be idempotent + assert: + that: + - result_after_test is not changed + - result_after_test.hcloud_server.delete_protection is sameas true + - result_after_test.hcloud_server.rebuild_protection is sameas true + +- name: test delete server fails if it is protected + hcloud_server: + name: "{{hcloud_server_name}}" + state: absent + ignore_errors: yes + register: result +- name: verify delete server fails if it is protected + assert: + that: + - result is failed + - 'result.msg == "server deletion is protected"' + +- name: test rebuild server fails if it is protected + hcloud_server: + name: "{{hcloud_server_name}}" + image: ubuntu-20.04 + state: rebuild + ignore_errors: yes + register: result +- name: verify rebuild server fails if it is protected + assert: + that: + - result is failed + - 'result.msg == "server rebuild is protected"' + +- name: test remove server protection + hcloud_server: + name: "{{ hcloud_server_name }}" + delete_protection: false + rebuild_protection: false + state: present + register: result_after_test + ignore_errors: true +- name: verify remove server protection + assert: + that: + - result_after_test is changed + - result_after_test.hcloud_server.delete_protection is sameas false + - result_after_test.hcloud_server.rebuild_protection is sameas false + +- name: absent server + hcloud_server: + name: "{{ hcloud_server_name }}" + state: absent + register: result +- name: verify absent server + assert: + that: + - result is success + +- name: test create server with ssh key + hcloud_server: + name: "{{ hcloud_server_name}}" + server_type: cx11 + image: "ubuntu-20.04" + ssh_keys: + - ci@ansible.hetzner.cloud + state: started + register: main_server +- name: verify create server with ssh key + assert: + that: + - main_server is changed + - main_server.hcloud_server.name == "{{ hcloud_server_name }}" + - main_server.hcloud_server.server_type == "cx11" + - main_server.hcloud_server.status == "running" + - main_server.root_password != "" + + +- name: test activate rescue mode with check_mode + hcloud_server: + name: "{{ hcloud_server_name }}" + rescue_mode: "linux64" + ssh_keys: + - ci@ansible.hetzner.cloud + state: present + register: main_server + check_mode: true +- name: verify activate rescue mode + assert: + that: + - main_server is changed + +- name: test activate rescue mode + hcloud_server: + name: "{{ hcloud_server_name }}" + rescue_mode: "linux64" + ssh_keys: + - ci@ansible.hetzner.cloud + state: present + register: main_server +- name: verify activate rescue mode + assert: + that: + - main_server is changed + - main_server.hcloud_server.rescue_enabled is sameas true + +- name: test disable rescue mode + hcloud_server: + name: "{{ hcloud_server_name }}" + ssh_keys: + - ci@ansible.hetzner.cloud + state: present + register: main_server +- name: verify activate rescue mode + assert: + that: + - main_server is changed + - main_server.hcloud_server.rescue_enabled is sameas false + +- name: test activate rescue mode without ssh keys + hcloud_server: + name: "{{ hcloud_server_name }}" + rescue_mode: "linux64" + state: present + register: main_server +- name: verify activate rescue mode without ssh keys + assert: + that: + - main_server is changed + - main_server.hcloud_server.rescue_enabled is sameas true + +- name: absent server + hcloud_server: + name: "{{ hcloud_server_name }}" + state: absent + register: result +- name: verify absent server + assert: + that: + - result is success + +- name: test create server with rescue_mode + hcloud_server: + name: "{{ hcloud_server_name}}" + server_type: cx11 + image: "ubuntu-20.04" + ssh_keys: + - ci@ansible.hetzner.cloud + rescue_mode: "linux64" + state: started + register: main_server +- name: verify create server with rescue_mode + assert: + that: + - main_server is changed + - main_server.hcloud_server.name == "{{ hcloud_server_name }}" + - main_server.hcloud_server.server_type == "cx11" + - main_server.hcloud_server.status == "running" + - main_server.root_password != "" + - main_server.hcloud_server.rescue_enabled is sameas true + + +- name: absent server + hcloud_server: + name: "{{ hcloud_server_name }}" + state: absent + register: result +- name: verify absent server + assert: + that: + - result is success +- name: test create server with labels + hcloud_server: + name: "{{ hcloud_server_name}}" + server_type: cx11 + image: "ubuntu-20.04" + ssh_keys: + - ci@ansible.hetzner.cloud + labels: + key: value + mylabel: "val123" + state: started + register: main_server +- name: verify create server with labels + assert: + that: + - main_server is changed + - main_server.hcloud_server.labels.key == "value" + - main_server.hcloud_server.labels.mylabel == "val123" + +- name: test update server with labels + hcloud_server: + name: "{{ hcloud_server_name}}" + server_type: cx11 + image: "ubuntu-20.04" + ssh_keys: + - ci@ansible.hetzner.cloud + labels: + key: other + mylabel: "val123" + state: started + register: main_server +- name: verify update server with labels + assert: + that: + - main_server is changed + - main_server.hcloud_server.labels.key == "other" + - main_server.hcloud_server.labels.mylabel == "val123" + +- name: test update server with labels in other order + hcloud_server: + name: "{{ hcloud_server_name}}" + server_type: cx11 + image: "ubuntu-20.04" + ssh_keys: + - ci@ansible.hetzner.cloud + labels: + mylabel: "val123" + key: other + state: started + register: main_server +- name: verify update server with labels in other order + assert: + that: + - main_server is not changed + +- name: cleanup with labels + hcloud_server: + name: "{{ hcloud_server_name }}" + state: absent + register: result +- name: verify cleanup + assert: + that: + - result is success + +- name: test create server with enabled backups + hcloud_server: + name: "{{ hcloud_server_name }}" + server_type: cpx11 + backups: true + image: "ubuntu-20.04" + ssh_keys: + - ci@ansible.hetzner.cloud + state: present + register: result +- name: verify enable backups + assert: + that: + - result is changed + - result.hcloud_server.backup_window != "" + +- name: cleanup test create server with enabled backups + hcloud_server: + name: "{{ hcloud_server_name }}" + state: absent + register: result +- name: verify cleanup + assert: + that: + - result is success + +- name: test create server with protection + hcloud_server: + name: "{{ hcloud_server_name }}" + delete_protection: true + rebuild_protection: true + server_type: cpx11 + image: "ubuntu-20.04" + ssh_keys: + - ci@ansible.hetzner.cloud + state: present + register: result_after_test + ignore_errors: true +- name: verify create server with protection + assert: + that: + - result_after_test is changed + - result_after_test.hcloud_server.delete_protection is sameas true + - result_after_test.hcloud_server.rebuild_protection is sameas true + +- name: test delete server fails if it is protected + hcloud_server: + name: "{{hcloud_server_name}}" + state: absent + ignore_errors: yes + register: result +- name: verify delete server fails if it is protected + assert: + that: + - result is failed + - 'result.msg == "server deletion is protected"' + +- name: remove protection from server + hcloud_server: + name: "{{ hcloud_server_name }}" + delete_protection: false + rebuild_protection: false + state: present + register: result_after_test + ignore_errors: true +- name: verify update server protection + assert: + that: + - result_after_test is changed + - result_after_test.hcloud_server.delete_protection is sameas false + - result_after_test.hcloud_server.rebuild_protection is sameas false + +- name: cleanup + hcloud_server: + name: "{{ hcloud_server_name }}" + state: absent + register: result +- name: verify cleanup + assert: + that: + - result is success diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/tasks/firewalls.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/tasks/firewalls.yml new file mode 100644 index 000000000..18fa89e25 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/tasks/firewalls.yml @@ -0,0 +1,105 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +- name: test add not existing firewall should fail + hcloud_server: + name: "{{ hcloud_server_name }}" + firewalls: + - not-existing + state: present + ignore_errors: yes + register: result +- name: verify add not existing firewall should fail + assert: + that: + - result is failed + - 'result.msg == "firewall not-existing was not found"' +- name: setup create firewalls + hcloud_firewall: + name: "{{ item }}" + rules: + - direction: in + protocol: icmp + source_ips: + - 0.0.0.0/0 + - ::/0 + with_items: + - "{{ hcloud_firewall_name }}" + - "{{ hcloud_firewall_name }}2" + +- name: test create server with firewalls + hcloud_server: + name: "{{ hcloud_server_name }}" + server_type: cpx11 + firewalls: + - "{{ hcloud_firewall_name }}" + image: "ubuntu-20.04" + ssh_keys: + - ci@ansible.hetzner.cloud + state: present + register: result +- name: verify test create server with firewalls + assert: + that: + - result is changed + +- name: test create server with firewalls idempotence + hcloud_server: + name: "{{ hcloud_server_name }}" + server_type: cpx11 + firewalls: + - "{{ hcloud_firewall_name }}" + image: "ubuntu-20.04" + ssh_keys: + - ci@ansible.hetzner.cloud + state: present + register: result +- name: verify test create server with firewalls idempotence + assert: + that: + - result is not changed + +- name: test update server with firewalls + hcloud_server: + name: "{{ hcloud_server_name }}" + server_type: cpx11 + firewalls: + - "{{ hcloud_firewall_name }}2" + image: "ubuntu-20.04" + ssh_keys: + - ci@ansible.hetzner.cloud + state: present + register: result +- name: verify test update server with firewalls + assert: + that: + - result is changed + +- name: test update server with firewalls idempotence + hcloud_server: + name: "{{ hcloud_server_name }}" + server_type: cpx11 + firewalls: + - "{{ hcloud_firewall_name }}2" + image: "ubuntu-20.04" + ssh_keys: + - ci@ansible.hetzner.cloud + state: present + register: result +- name: verify test update server with firewalls idempotence + assert: + that: + - result is not changed + +- name: cleanup server with firewalls + hcloud_server: + name: "{{ hcloud_server_name }}" + state: absent + +- name: cleanup test create firewall + hcloud_firewall: + name: "{{ item }}" + state: absent + with_items: + - "{{ hcloud_firewall_name }}" + - "{{ hcloud_firewall_name }}2" diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/tasks/main.yml new file mode 100644 index 000000000..209d9bd48 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/tasks/main.yml @@ -0,0 +1,8 @@ +# Copyright: (c) 2022, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +#- ansible.builtin.include_tasks: validation.yml +- ansible.builtin.include_tasks: basic.yml +#- ansible.builtin.include_tasks: firewalls.yml +- ansible.builtin.include_tasks: primary_ips.yml +- ansible.builtin.include_tasks: private_network_only.yml diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/tasks/primary_ips.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/tasks/primary_ips.yml new file mode 100644 index 000000000..000c294de --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/tasks/primary_ips.yml @@ -0,0 +1,82 @@ +# Copyright: (c) 2022, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +- name: setup create primary ipv4 + hcloud_primary_ip: + name: "{{ hcloud_primary_ip_name }}v4" + type: ipv4 + datacenter: "fsn1-dc14" + register: primaryIPv4 + +- name: setup create second primary ipv4 + hcloud_primary_ip: + name: "{{ hcloud_primary_ip_name }}v42" + type: ipv4 + datacenter: "fsn1-dc14" + register: secondPrimaryIPv4 + +- name: setup create primary ipv6 + hcloud_primary_ip: + name: "{{ hcloud_primary_ip_name }}v6" + type: ipv6 + datacenter: "fsn1-dc14" + register: primaryIPv6 + +- name: test create server with primary ips + hcloud_server: + name: "{{ hcloud_server_name }}" + server_type: cpx11 + datacenter: "fsn1-dc14" + image: "ubuntu-20.04" + ipv4: "{{primaryIPv4.hcloud_primary_ip.id}}" + ipv6: "{{primaryIPv6.hcloud_primary_ip.id}}" + ssh_keys: + - ci@ansible.hetzner.cloud + state: stopped + register: result +- name: verify test create server with primary ips + assert: + that: + - result is changed + +- name: test update server with primary ips + hcloud_server: + name: "{{ hcloud_server_name }}" + server_type: cpx11 + datacenter: "fsn1-dc14" + image: "ubuntu-20.04" + ipv4: "{{secondPrimaryIPv4.hcloud_primary_ip.id}}" + ipv6: "" + enable_ipv6: no + ssh_keys: + - ci@ansible.hetzner.cloud + state: stopped + register: result +- name: verify test create server with primary ips + assert: + that: + - result is changed + +- name: cleanup server with primary ips + hcloud_server: + name: "{{ hcloud_server_name }}" + state: absent + +- name: cleanup test create primary ips + hcloud_primary_ip: + name: "{{ hcloud_server_name }}v4" + state: absent +- name: cleanup test create primary ips + hcloud_primary_ip: + name: "{{ hcloud_server_name }}v42" + state: absent + until: result is not failed + retries: 5 + delay: 2 +- name: cleanup test create primary ips + hcloud_primary_ip: + name: "{{ hcloud_server_name }}v6" + state: absent + until: result is not failed + retries: 5 + delay: 2 diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/tasks/private_network_only.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/tasks/private_network_only.yml new file mode 100644 index 000000000..a56832873 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/tasks/private_network_only.yml @@ -0,0 +1,135 @@ +# Copyright: (c) 2022, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +- name: setup network 1 to be absent + hcloud_network: + name: "{{ hcloud_network_name }}-1" + state: absent + +- name: setup network 2 to be absent + hcloud_network: + name: "{{ hcloud_network_name }}-2" + state: absent + +- name: setup server to be absent + hcloud_server: + name: "{{ hcloud_server_name }}" + state: absent + +- name: setup create network + hcloud_network: + name: "{{ hcloud_network_name }}-1" + ip_range: 192.168.0.0/23 + register: primaryNetwork + +- name: setup create network subnet 1 + hcloud_subnetwork: + network: "{{ hcloud_network_name }}-1" + ip_range: 192.168.0.0/24 + network_zone: eu-central + type: cloud + state: present + +- name: setup create network subnet 2 + hcloud_subnetwork: + network: "{{ hcloud_network_name }}-1" + ip_range: 192.168.1.0/24 + network_zone: eu-central + type: cloud + state: present + +- name: setup create secondary network + hcloud_network: + name: "{{ hcloud_network_name }}-2" + ip_range: 192.168.2.0/23 + register: secondaryNetwork + +- name: setup create secondary network subnet 1 + hcloud_subnetwork: + network: "{{ hcloud_network_name }}-2" + ip_range: 192.168.2.0/24 + network_zone: eu-central + type: cloud + state: present + +- name: setup create secondary network subnet 2 + hcloud_subnetwork: + network: "{{ hcloud_network_name }}-2" + ip_range: 192.168.3.0/24 + network_zone: eu-central + type: cloud + state: present + +- name: test create server with primary network and no internet + hcloud_server: + name: "{{ hcloud_server_name }}" + server_type: cpx11 + datacenter: "fsn1-dc14" + image: "ubuntu-20.04" + enable_ipv4: no + enable_ipv6: no + private_networks: + - "{{ primaryNetwork.hcloud_network.name }}" + ssh_keys: + - ci@ansible.hetzner.cloud + state: stopped + register: result +- name: verify test create server with primary network + assert: + that: + - result is changed + +- name: test update server by adding secondary network + hcloud_server: + name: "{{ hcloud_server_name }}" + server_type: cpx11 + datacenter: "fsn1-dc14" + image: "ubuntu-20.04" + enable_ipv4: no + enable_ipv6: no + private_networks: + - "{{ primaryNetwork.hcloud_network.name }}" + - "{{ secondaryNetwork.hcloud_network.id }}" + ssh_keys: + - ci@ansible.hetzner.cloud + state: stopped + register: result +- name: verify test update server by adding secondary network + assert: + that: + - result is changed + +- name: test update server idem + hcloud_server: + name: "{{ hcloud_server_name }}" + server_type: cpx11 + datacenter: "fsn1-dc14" + image: "ubuntu-20.04" + enable_ipv4: no + enable_ipv6: no + private_networks: + - "{{ primaryNetwork.hcloud_network.name }}" + - "{{ secondaryNetwork.hcloud_network.id }}" + ssh_keys: + - ci@ansible.hetzner.cloud + state: stopped + register: result +- name: verify test update server idem + assert: + that: + - result is not changed + +- name: cleanup server + hcloud_server: + name: "{{ hcloud_server_name }}" + state: absent +- name: cleanup networks + hcloud_network: + name: "{{ item }}" + state: absent + with_items: + - "{{ primaryNetwork.hcloud_network.name }}" + - "{{ secondaryNetwork.hcloud_network.id }}" + until: result is not failed + retries: 5 + delay: 2 diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/tasks/validation.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/tasks/validation.yml new file mode 100644 index 000000000..f507e87cf --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/tasks/validation.yml @@ -0,0 +1,51 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +- name: setup + hcloud_server: + name: "{{ hcloud_server_name }}" + state: absent + register: result +- name: verify setup + assert: + that: + - result is success + +- name: test missing required parameters on create server + hcloud_server: + name: "{{ hcloud_server_name }}" + register: result + ignore_errors: yes +- name: verify fail test missing required parameters on create server + assert: + that: + - result is failed + - 'result.msg == "missing required arguments: server_type, image"' + +- name: test create server with not existing server type + hcloud_server: + name: "{{ hcloud_server_name }}" + server_type: not-existing-server-type + image: ubuntu-20.04 + state: present + register: result + ignore_errors: yes +- name: verify fail test create server with not existing server type + assert: + that: + - result is failed + - 'result.msg == "server_type not-existing-server-type was not found"' + +- name: test create server with not existing image + hcloud_server: + name: "{{ hcloud_server_name }}" + server_type: cx11 + image: my-not-existing-image-20.04 + state: present + register: result + ignore_errors: yes +- name: verify fail test create server with not existing image + assert: + that: + - result is failed + - 'result.msg == "Image my-not-existing-image-20.04 was not found"' diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_info/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_info/aliases new file mode 100644 index 000000000..55ec821a4 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_info/aliases @@ -0,0 +1,2 @@ +cloud/hcloud +shippable/hcloud/group2 diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_info/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_info/defaults/main.yml new file mode 100644 index 000000000..aa27d6452 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_info/defaults/main.yml @@ -0,0 +1,5 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +hcloud_prefix: "tests" +hcloud_server_name: "{{ hcloud_prefix | truncate(45, True, '', 0) }}-ii" diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_info/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_info/meta/main.yml new file mode 100644 index 000000000..407c9018a --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_info/meta/main.yml @@ -0,0 +1,3 @@ +collections: + - community.general.ipfilter + - hetzner.cloud diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_info/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_info/tasks/main.yml new file mode 100644 index 000000000..b425b4127 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_info/tasks/main.yml @@ -0,0 +1,128 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +- name: setup ensure server is absent + hcloud_server: + name: "{{ hcloud_server_name }}" + state: absent + register: result + +- name: create server + hcloud_server: + name: "{{ hcloud_server_name }}" + server_type: cx11 + image: ubuntu-22.04 + state: started + labels: + key: value + register: main_server +- name: verify create server + assert: + that: + - main_server is changed + - main_server.hcloud_server.name == "{{ hcloud_server_name }}" + - main_server.hcloud_server.server_type == "cx11" + - main_server.hcloud_server.status == "running" + - main_server.root_password != "" + + +- name: test gather hcloud server infos in check mode + hcloud_server_info: + register: server + check_mode: yes + +- name: verify test gather hcloud server infos in check mode + assert: + that: + - server.hcloud_server_info|selectattr('name','equalto','{{ hcloud_server_name }}') | list | count == 1 + + +- name: test gather hcloud server infos with correct label selector + hcloud_server_info: + label_selector: "key=value" + register: server +- name: verify test gather hcloud server infos with correct label selector + assert: + that: + - server.hcloud_server_info|selectattr('name','equalto','{{ hcloud_server_name }}') | list | count == 1 + +- name: test gather hcloud server infos with wrong label selector + hcloud_server_info: + label_selector: "key!=value" + register: server +- name: verify test gather hcloud server infos with wrong label selector + assert: + that: + - server.hcloud_server_info | list | count == 0 + +- name: test gather hcloud server infos with correct name + hcloud_server_info: + name: "{{hcloud_server_name}}" + register: server +- name: verify test gather hcloud server infos with correct name + assert: + that: + - server.hcloud_server_info|selectattr('name','equalto','{{ hcloud_server_name }}') | list | count == 1 + +- name: test gather hcloud server infos with wrong name + hcloud_server_info: + name: "{{hcloud_server_name}}1" + register: server +- name: verify test gather hcloud server infos with wrong name + assert: + that: + - server.hcloud_server_info | list | count == 0 + +- name: test gather hcloud server infos with correct id + hcloud_server_info: + id: "{{main_server.hcloud_server.id}}" + register: server +- name: verify test gather hcloud server infos with correct id + assert: + that: + - server.hcloud_server_info|selectattr('name','equalto','{{ hcloud_server_name }}') | list | count == 1 + +- name: test gather hcloud server infos with wrong id + hcloud_server_info: + name: "4711" + register: server +- name: verify test gather hcloud server infos with wrong id + assert: + that: + - server.hcloud_server_info | list | count == 0 + +- name: cleanup + hcloud_server: + name: "{{ hcloud_server_name }}" + state: absent + +- name: create server without ips + hcloud_server: + name: "{{ hcloud_server_name }}" + server_type: cx11 + image: ubuntu-22.04 + state: stopped + labels: + key: value + enable_ipv4: no + enable_ipv6: no + register: main_server +- name: verify create server + assert: + that: + - main_server is changed + - main_server.hcloud_server.name == "{{ hcloud_server_name }}" + - main_server.hcloud_server.server_type == "cx11" + - main_server.root_password != "" +- name: test gather hcloud server infos with correct id + hcloud_server_info: + id: "{{main_server.hcloud_server.id}}" + register: server +- name: verify test gather hcloud server infos with correct id + assert: + that: + - server.hcloud_server_info|selectattr('name','equalto','{{ hcloud_server_name }}') | list | count == 1 +- name: cleanup + hcloud_server: + name: "{{ hcloud_server_name }}" + state: absent diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_network/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_network/aliases new file mode 100644 index 000000000..7f17468b0 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_network/aliases @@ -0,0 +1,3 @@ +cloud/hcloud +shippable/hcloud/group2 +disabled diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_network/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_network/defaults/main.yml new file mode 100644 index 000000000..2e020c495 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_network/defaults/main.yml @@ -0,0 +1,6 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +hcloud_prefix: "tests" +hcloud_network_name: "{{hcloud_prefix}}-sn" +hcloud_server_name: "{{ hcloud_prefix | truncate(45, True, '', 0) }}-sn" diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_network/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_network/meta/main.yml new file mode 100644 index 000000000..407c9018a --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_network/meta/main.yml @@ -0,0 +1,3 @@ +collections: + - community.general.ipfilter + - hetzner.cloud diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_network/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_network/tasks/main.yml new file mode 100644 index 000000000..754018a66 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_network/tasks/main.yml @@ -0,0 +1,222 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +- name: setup network + hcloud_network: + name: "{{ hcloud_network_name }}" + ip_range: "10.0.0.0/8" + state: present + register: network +- name: verify setup network + assert: + that: + - network is success + +- name: setup subnetwork + hcloud_subnetwork: + network: "{{ hcloud_network_name }}" + ip_range: "10.0.0.0/16" + type: "server" + network_zone: "eu-central" + state: present + register: subnetwork +- name: verify subnetwork + assert: + that: + - subnetwork is success + +- name: setup server + hcloud_server: + name: "{{hcloud_server_name}}" + server_type: cx11 + image: ubuntu-18.04 + state: started + location: "fsn1" + register: server +- name: verify setup server + assert: + that: + - server is success + +- name: test missing required parameters on create server network + hcloud_server_network: + state: present + register: result + ignore_errors: yes +- name: verify fail test missing required parameters on create server network + assert: + that: + - result is failed + - 'result.msg == "missing required arguments: network, server"' + +- name: test create server network with checkmode + hcloud_server_network: + network: "{{ hcloud_network_name }}" + server: "{{hcloud_server_name}}" + state: present + register: result + check_mode: yes +- name: verify test create server network with checkmode + assert: + that: + - result is changed + +- name: test create server network + hcloud_server_network: + network: "{{ hcloud_network_name }}" + server: "{{hcloud_server_name}}" + state: present + register: serverNetwork +- name: verify create server network + assert: + that: + - serverNetwork is changed + - serverNetwork.hcloud_server_network.network == hcloud_network_name + - serverNetwork.hcloud_server_network.server == hcloud_server_name + +- name: test create server network idempotency + hcloud_server_network: + network: "{{ hcloud_network_name }}" + server: "{{hcloud_server_name}}" + state: present + register: serverNetwork +- name: verify create server network idempotency + assert: + that: + - serverNetwork is not changed + +- name: test absent server network + hcloud_server_network: + network: "{{ hcloud_network_name }}" + server: "{{hcloud_server_name}}" + state: absent + register: result +- name: verify test absent server network + assert: + that: + - result is changed + +- name: test create server network with specified ip + hcloud_server_network: + network: "{{ hcloud_network_name }}" + server: "{{hcloud_server_name}}" + ip: "10.0.0.2" + state: present + register: serverNetwork +- name: verify create server network with specified ip + assert: + that: + - serverNetwork is changed + - serverNetwork.hcloud_server_network.network == hcloud_network_name + - serverNetwork.hcloud_server_network.server == hcloud_server_name + - serverNetwork.hcloud_server_network.ip == "10.0.0.2" + +- name: cleanup create server network with specified ip + hcloud_server_network: + network: "{{ hcloud_network_name }}" + server: "{{hcloud_server_name}}" + state: absent + register: result +- name: verify cleanup create server network with specified ip + assert: + that: + - result is changed + +- name: test create server network with alias ips + hcloud_server_network: + network: "{{ hcloud_network_name }}" + server: "{{hcloud_server_name}}" + ip: "10.0.0.2" + alias_ips: + - "10.0.1.2" + - "10.0.2.3" + state: present + register: serverNetwork +- name: verify create server network with alias ips + assert: + that: + - serverNetwork is changed + - serverNetwork.hcloud_server_network.network == hcloud_network_name + - serverNetwork.hcloud_server_network.server == hcloud_server_name + - serverNetwork.hcloud_server_network.ip == "10.0.0.2" + - 'serverNetwork.hcloud_server_network.alias_ips[0] == "10.0.2.3"' + - 'serverNetwork.hcloud_server_network.alias_ips[1] == "10.0.1.2"' + +- name: test update server network with alias ips + hcloud_server_network: + network: "{{ hcloud_network_name }}" + server: "{{hcloud_server_name}}" + ip: "10.0.0.2" + alias_ips: + - "10.0.2.3" + - "10.0.3.1" + state: present + register: serverNetwork +- name: verify create server network with alias ips + assert: + that: + - serverNetwork is changed + - serverNetwork.hcloud_server_network.network == hcloud_network_name + - serverNetwork.hcloud_server_network.server == hcloud_server_name + - serverNetwork.hcloud_server_network.ip == "10.0.0.2" + - 'serverNetwork.hcloud_server_network.alias_ips[0] == "10.0.2.3"' + - 'serverNetwork.hcloud_server_network.alias_ips[1] == "10.0.3.1"' + +- name: test update server network with alias ips idempotency + hcloud_server_network: + network: "{{ hcloud_network_name }}" + server: "{{hcloud_server_name}}" + ip: "10.0.0.2" + alias_ips: + - "10.0.2.3" + - "10.0.3.1" + state: present + register: serverNetwork +- name: verify create server network with alias ips idempotency + assert: + that: + - serverNetwork is not changed + +- name: cleanup create server network with alias ips + hcloud_server_network: + network: "{{ hcloud_network_name }}" + server: "{{hcloud_server_name}}" + state: absent + register: result +- name: verify cleanup create server network with alias ips + assert: + that: + - result is changed + +- name: cleanup server + hcloud_server: + name: "{{ hcloud_server_name }}" + state: absent + register: result +- name: verify cleanup server + assert: + that: + - result is success + +- name: cleanup subnetwork + hcloud_subnetwork: + network: "{{ hcloud_network_name }}" + ip_range: "10.0.0.0/16" + type: "server" + network_zone: "eu-central" + state: absent + register: result +- name: verify cleanup subnetwork + assert: + that: + - result is changed + +- name: cleanup + hcloud_network: + name: "{{hcloud_network_name}}" + state: absent + register: result +- name: verify cleanup + assert: + that: + - result is success diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_type_info/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_type_info/aliases new file mode 100644 index 000000000..55ec821a4 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_type_info/aliases @@ -0,0 +1,2 @@ +cloud/hcloud +shippable/hcloud/group2 diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_type_info/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_type_info/defaults/main.yml new file mode 100644 index 000000000..05502aa91 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_type_info/defaults/main.yml @@ -0,0 +1,5 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +hcloud_server_type_name: "cx11" +hcloud_server_type_id: 1 diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_type_info/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_type_info/meta/main.yml new file mode 100644 index 000000000..407c9018a --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_type_info/meta/main.yml @@ -0,0 +1,3 @@ +collections: + - community.general.ipfilter + - hetzner.cloud diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_type_info/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_type_info/tasks/main.yml new file mode 100644 index 000000000..3c1fce8c0 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_type_info/tasks/main.yml @@ -0,0 +1,38 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +- name: test gather hcloud server type infos + hcloud_server_type_info: + register: hcloud_server_types +- name: verify test gather hcloud server type infos + assert: + that: + - hcloud_server_types.hcloud_server_type_info| list | count > 2 + +- name: test gather hcloud server type infos in check mode + hcloud_server_type_info: + check_mode: yes + register: hcloud_server_types + +- name: verify test gather hcloud server type infos in check mode + assert: + that: + - hcloud_server_types.hcloud_server_type_info| list | count > 2 + +- name: test gather hcloud server type infos with name + hcloud_server_type_info: + name: "{{hcloud_server_type_name}}" + register: hcloud_server_types +- name: verify test gather hcloud server type with name + assert: + that: + - hcloud_server_types.hcloud_server_type_info|selectattr('name','equalto','{{ hcloud_server_type_name }}') | list | count == 1 + +- name: test gather hcloud server type infos with correct id + hcloud_server_type_info: + id: "{{hcloud_server_type_id}}" + register: hcloud_server_types +- name: verify test gather hcloud server type with correct id + assert: + that: + - hcloud_server_types.hcloud_server_type_info|selectattr('name','equalto','{{ hcloud_server_type_name }}') | list | count == 1 diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key/aliases new file mode 100644 index 000000000..55ec821a4 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key/aliases @@ -0,0 +1,2 @@ +cloud/hcloud +shippable/hcloud/group2 diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key/defaults/main.yml new file mode 100644 index 000000000..cee1d4691 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key/defaults/main.yml @@ -0,0 +1,11 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +hcloud_prefix: "tests" +hcloud_server_name: "{{ hcloud_prefix | truncate(45, True, '', 0) }}" +hcloud_ssh_key_name: "{{hcloud_prefix}}" +hcloud_ssh_key_public_key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDnaTPfKaX1QKcRLOfr34buVLh5FhJAThI9NYB0xNdXsMd4Y0zLyyCQzHbx4eWCVZxym/s6csWSeLaAhO1GOHeAw3hQFMqf1oTBx6Y8g0pKpeotKPa/PDSUzdZF9Lc+DadtpQd8kFVHAu1Kd3zoEUnk1u6kP7I4qu4Z/6F9qBDF+M3aobiPVxdS7GwaVRW3nZu+FcQDLiBiNOjuRDyjHcDfEUkoh2SOu25RrFtGPzFu5mGmBJwotKpWAocLGfHzyn/fAHxgw3jKZVH/t+XWQFnl82Ie8yE3Z1EZ7oDkNRqFQT9AdXEQOLycTTYTQMJZpgeFTv3sAo6lPRCusiFmmLcf ci@ansible.hetzner.cloud" +hcloud_ssh_key_fingerprint: "56:89:c4:d6:a7:4a:79:82:f4:c2:58:9c:e1:d2:2d:4e" + +hcloud_doubled_ssh_key_public_key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC1AiuN3UMQKzOs4tNudmlDSkSebC+savc6CivoHGflUKeli7nKb5pKgGiqH+zeWZc+8+flUa2BxsJWmi7d1nGJ++W4BnzmqW78ApelpJnGtuX8IKNcq/trhVTQyaShPiLluoBs7bXyyZpAKNGkk3jHrgwwYD/QQDN0CJnQUM18fjH5CUes2vmaG/kkhn7ctuVHDOvDcEy8KdBX3fYyrtXw5GgWDC5borG6yT1f3E9AXfRPL9OQjMTeC+G4FHscJAZjNnYav+jLrQLdV1xJ0JgbjRyBgTAfBszx9oKIjzCUPvpj4npju0WFGu10pIh0w7bluMoVn1tS6Y3gxE/Cepwt ci@ansible.hetzner.cloud" +hcloud_doubled_ssh_key_fingerprint: "f9:33:40:ff:77:f3:3e:85:f2:9e:8f:98:71:fd:a0:58" diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key/meta/main.yml new file mode 100644 index 000000000..5dcc0725d --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key/meta/main.yml @@ -0,0 +1,5 @@ +dependencies: + - setup_sshkey +collections: + - community.general.ipfilter + - hetzner.cloud diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key/tasks/main.yml new file mode 100644 index 000000000..9208e143d --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key/tasks/main.yml @@ -0,0 +1,156 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +- name: test missing required parameters on create ssh_key + hcloud_ssh_key: + name: "{{ hcloud_ssh_key_name }}" + register: result + ignore_errors: yes +- name: verify fail test missing required parameters on create ssh_key + assert: + that: + - result is failed + - 'result.msg == "missing required arguments: public_key"' + +- name: test create ssh key with check mode + hcloud_ssh_key: + name: "{{ hcloud_ssh_key_name }}" + public_key: "{{ key_material }}" + register: result + check_mode: yes +- name: test create ssh key with check mode + assert: + that: + - result is changed + +- name: test create ssh key + hcloud_ssh_key: + name: "{{ hcloud_ssh_key_name }}" + public_key: "{{ key_material }}" + labels: + key: value + my-label: label + register: sshKey +- name: verify create ssh key + assert: + that: + - sshKey is changed + - sshKey.hcloud_ssh_key.name == "{{ hcloud_ssh_key_name }}" + - sshKey.hcloud_ssh_key.public_key == "{{ key_material }}" + - sshKey.hcloud_ssh_key.labels.key == "value" + +- name: test create ssh key idempotence + hcloud_ssh_key: + name: "{{ hcloud_ssh_key_name }}" + public_key: "{{ key_material }}" + register: result +- name: verify create ssh key idempotence + assert: + that: + - result is not changed + +- name: test update ssh key with check mode + hcloud_ssh_key: + id: "{{ sshKey.hcloud_ssh_key.id }}" + name: "changed-{{ hcloud_ssh_key_name }}" + register: result + check_mode: yes +- name: test create ssh key with check mode + assert: + that: + - result is changed + +- name: test update ssh key + hcloud_ssh_key: + id: "{{ sshKey.hcloud_ssh_key.id }}" + name: "changed-{{ hcloud_ssh_key_name }}" + labels: + key: value + register: result +- name: test update ssh key + assert: + that: + - result is changed + - result.hcloud_ssh_key.name == "changed-{{ hcloud_ssh_key_name }}" + +- name: test update ssh key with same labels + hcloud_ssh_key: + id: "{{ sshKey.hcloud_ssh_key.id }}" + name: "changed-{{ hcloud_ssh_key_name }}" + labels: + key: value + register: result +- name: test update ssh key with same labels + assert: + that: + - result is not changed + +- name: test update ssh key with other labels + hcloud_ssh_key: + id: "{{ sshKey.hcloud_ssh_key.id }}" + name: "changed-{{ hcloud_ssh_key_name }}" + labels: + key: value + test: "val123" + register: result +- name: test update ssh key with other labels + assert: + that: + - result is changed + +- name: test rename ssh key + hcloud_ssh_key: + id: "{{ sshKey.hcloud_ssh_key.id }}" + name: "{{ hcloud_ssh_key_name }}" + register: result +- name: test rename ssh key + assert: + that: + - result is changed + - result.hcloud_ssh_key.name == "{{ hcloud_ssh_key_name }}" + +- name: test create server with ssh key + hcloud_server: + name: "{{ hcloud_server_name }}" + server_type: cx11 + image: "ubuntu-20.04" + ssh_keys: + - "{{ hcloud_ssh_key_name }}" + state: started + register: main_server +- name: verify create server with ssh key + assert: + that: + - main_server is changed + +- name: absent ssh key + hcloud_ssh_key: + id: "{{ sshKey.hcloud_ssh_key.id }}" + state: absent + register: result +- name: verify absent sshkey + assert: + that: + - result is success + +- name: test fail cleanly on double created ssh key + hcloud_ssh_key: + name: "{{ hcloud_ssh_key_name }}othername" + public_key: "{{ hcloud_doubled_ssh_key_public_key }}" + register: result + ignore_errors: yes +- name: verify failed correctly + assert: + that: + - result is failed + - 'result.msg == "SSH key with the same fingerprint already exists"' + +- name: cleanup + hcloud_server: + name: "{{ hcloud_server_name }}" + state: absent + register: result +- name: verify cleanup + assert: + that: + - result is success diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key_info/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key_info/aliases new file mode 100644 index 000000000..55ec821a4 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key_info/aliases @@ -0,0 +1,2 @@ +cloud/hcloud +shippable/hcloud/group2 diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key_info/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key_info/defaults/main.yml new file mode 100644 index 000000000..15188e181 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key_info/defaults/main.yml @@ -0,0 +1,5 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +hcloud_prefix: "tests" +hcloud_ssh_key_name: "{{hcloud_prefix}}-f" diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key_info/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key_info/meta/main.yml new file mode 100644 index 000000000..5dcc0725d --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key_info/meta/main.yml @@ -0,0 +1,5 @@ +dependencies: + - setup_sshkey +collections: + - community.general.ipfilter + - hetzner.cloud diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key_info/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key_info/tasks/main.yml new file mode 100644 index 000000000..87cbd2626 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key_info/tasks/main.yml @@ -0,0 +1,68 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- + +- name: setup ensure ssh key is absent + hcloud_ssh_key: + name: "{{ hcloud_ssh_key_name }}" + state: absent + register: result + +- name: setup test ssh_key + hcloud_ssh_key: + name: "{{hcloud_ssh_key_name}}" + public_key: "{{ key_material }}" + labels: + key: value + register: result +- name: verify create test ssh_key + assert: + that: + - result is changed + - result.hcloud_ssh_key.public_key == "{{ key_material }}" + +- name: test gather hcloud ssh key infos in check mode + hcloud_ssh_key_info: + register: hcloud_ssh_key + check_mode: yes +- name: verify test gather hcloud ssh key infos in check mode + assert: + that: + - hcloud_ssh_key.hcloud_ssh_key_info| list | count >= 1 + +- name: test gather hcloud ssh key infos + hcloud_ssh_key_info: + register: hcloud_ssh_key + check_mode: yes +- name: verify test gather hcloud ssh key infos + assert: + that: + - hcloud_ssh_key.hcloud_ssh_key_info| list | count >= 1 + +- name: test gather hcloud ssh key infos with correct label selector + hcloud_ssh_key_info: + label_selector: "key=value" + register: hcloud_ssh_key +- name: verify test gather hcloud ssh key infos with correct label selector + assert: + that: + - hcloud_ssh_key.hcloud_ssh_key_info|selectattr('name','equalto','{{ hcloud_ssh_key_name }}') | list | count == 1 + +- name: test gather hcloud ssh key infos with wrong label selector + hcloud_ssh_key_info: + label_selector: "key!=value" + register: hcloud_ssh_key +- name: verify test gather hcloud ssh key infos with wrong label selector + assert: + that: + - hcloud_ssh_key.hcloud_ssh_key_info | list | count == 0 + +- name: cleanup + hcloud_ssh_key: + name: "{{hcloud_ssh_key_name}}" + state: absent + register: result +- name: verify cleanup + assert: + that: + - result is success diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_subnetwork/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_subnetwork/aliases new file mode 100644 index 000000000..af1d98c3d --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_subnetwork/aliases @@ -0,0 +1,2 @@ +cloud/hcloud +shippable/hcloud/group3 diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_subnetwork/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_subnetwork/defaults/main.yml new file mode 100644 index 000000000..79f0d8783 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_subnetwork/defaults/main.yml @@ -0,0 +1,6 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +hcloud_prefix: "tests" +hcloud_network_name: "{{hcloud_prefix}}-s" +hetzner_vswitch_id: 15311 diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_subnetwork/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_subnetwork/meta/main.yml new file mode 100644 index 000000000..407c9018a --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_subnetwork/meta/main.yml @@ -0,0 +1,3 @@ +collections: + - community.general.ipfilter + - hetzner.cloud diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_subnetwork/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_subnetwork/tasks/main.yml new file mode 100644 index 000000000..0453f9d13 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_subnetwork/tasks/main.yml @@ -0,0 +1,125 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +- name: setup + hcloud_network: + name: "{{ hcloud_network_name }}" + ip_range: "10.0.0.0/8" + state: present + register: network +- name: verify setup + assert: + that: + - network is success + +- name: test missing required parameters on create route + hcloud_subnetwork: + network: "{{ hcloud_network_name }}" + state: present + register: result + ignore_errors: yes +- name: verify fail test missing required parameters on create route + assert: + that: + - result is failed + - 'result.msg == "missing required arguments: ip_range, network_zone, type"' + +- name: test create subnetwork with checkmode + hcloud_subnetwork: + network: "{{ hcloud_network_name }}" + ip_range: "10.0.0.0/16" + type: "server" + network_zone: "eu-central" + state: present + register: result + check_mode: yes +- name: verify test create subnetwork with checkmode + assert: + that: + - result is changed + +- name: test create subnetwork + hcloud_subnetwork: + network: "{{ hcloud_network_name }}" + ip_range: "10.0.0.0/16" + type: "cloud" + network_zone: "eu-central" + state: present + register: subnet +- name: verify create subnetwork + assert: + that: + - subnet is changed + - subnet.hcloud_subnetwork.network == "{{ hcloud_network_name }}" + - subnet.hcloud_subnetwork.ip_range == "10.0.0.0/16" + - subnet.hcloud_subnetwork.type == "cloud" + - subnet.hcloud_subnetwork.network_zone == "eu-central" + +- name: test create subnetwork idempotency + hcloud_subnetwork: + network: "{{ hcloud_network_name }}" + ip_range: "10.0.0.0/16" + type: "cloud" + network_zone: "eu-central" + state: present + register: result +- name: verify create subnetwork idempotency + assert: + that: + - result is not changed + +- name: test absent subnetwork + hcloud_subnetwork: + network: "{{ hcloud_network_name }}" + ip_range: "10.0.0.0/16" + type: "cloud" + network_zone: "eu-central" + state: absent + register: result +- name: verify test absent subnetwork + assert: + that: + - result is changed + +- name: test vswitch subnetwork + hcloud_subnetwork: + network: "{{ hcloud_network_name }}" + ip_range: "10.0.0.0/16" + type: "vswitch" + network_zone: "eu-central" + vswitch_id: "{{ hetzner_vswitch_id }}" + state: present + register: subnet +- name: verify test vswitch subnetwork + assert: + that: + - subnet is changed + - subnet.hcloud_subnetwork.network == "{{ hcloud_network_name }}" + - subnet.hcloud_subnetwork.ip_range == "10.0.0.0/16" + - subnet.hcloud_subnetwork.type == "vswitch" + - subnet.hcloud_subnetwork.network_zone == "eu-central" + - subnet.hcloud_subnetwork.vswitch_id == hetzner_vswitch_id + +- name: test absent subnetwork + hcloud_subnetwork: + network: "{{ hcloud_network_name }}" + ip_range: "10.0.0.0/16" + type: "vswitch" + network_zone: "eu-central" + vswitch_id: "{{ hetzner_vswitch_id }}" + state: absent + register: subnet +- name: verify test absent subnetwork + assert: + that: + - result is changed + +- name: cleanup + hcloud_network: + name: "{{hcloud_network_name}}" + state: absent + register: result +- name: verify cleanup + assert: + that: + - result is success diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume/aliases new file mode 100644 index 000000000..55ec821a4 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume/aliases @@ -0,0 +1,2 @@ +cloud/hcloud +shippable/hcloud/group2 diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume/defaults/main.yml new file mode 100644 index 000000000..ff16ce28f --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume/defaults/main.yml @@ -0,0 +1,6 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +hcloud_prefix: "tests" +hcloud_volume_name: "{{ hcloud_prefix | truncate(60, True, '', 0) }}-i" +hcloud_server_name: "{{ hcloud_prefix | truncate(45, True, '', 0) }}-vs" diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume/meta/main.yml new file mode 100644 index 000000000..407c9018a --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume/meta/main.yml @@ -0,0 +1,3 @@ +collections: + - community.general.ipfilter + - hetzner.cloud diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume/tasks/main.yml new file mode 100644 index 000000000..f763a52f2 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume/tasks/main.yml @@ -0,0 +1,289 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +- name: setup server + hcloud_server: + name: "{{hcloud_server_name}}" + server_type: cx11 + image: ubuntu-18.04 + state: started + location: "fsn1" + register: vol_server +- name: verify setup server + assert: + that: + - vol_server is changed + +- name: test missing size parameter on create Volume + hcloud_volume: + name: "{{hcloud_volume_name}}" + server: "{{hcloud_server_name}}" + register: result + ignore_errors: yes +- name: verify fail test missing size parameter on create Volume + assert: + that: + - result is failed + - 'result.msg == "missing required arguments: size"' + +- name: test create Volume with check mode + hcloud_volume: + name: "{{hcloud_volume_name}}" + size: 10 + location: "fsn1" + register: result + check_mode: yes +- name: verify create Volume with check mode result + assert: + that: + - result is changed + +- name: test create Volume + hcloud_volume: + name: "{{hcloud_volume_name}}" + size: 10 + location: "fsn1" + register: volume +- name: verify test create Volume + assert: + that: + - volume is changed + - volume.hcloud_volume.name == "{{hcloud_volume_name}}" + - volume.hcloud_volume.location == "fsn1" + - volume.hcloud_volume.size == 10 + - volume.hcloud_volume.server != "{{hcloud_server_name}}" + - volume.hcloud_volume.linux_device is defined + +- name: test create Volume idempotence + hcloud_volume: + name: "{{hcloud_volume_name}}" + size: 10 + location: "fsn1" + register: volume +- name: verify test create Volume + assert: + that: + - volume is not changed + +- name: test attach Volume with checkmode + hcloud_volume: + name: "{{hcloud_volume_name}}" + server: "{{hcloud_server_name}}" + check_mode: yes + register: volume +- name: verify test attach Volume with checkmode + assert: + that: + - volume is changed + - volume.hcloud_volume.server != "{{hcloud_server_name}}" + +- name: test attach Volume + hcloud_volume: + name: "{{hcloud_volume_name}}" + server: "{{hcloud_server_name}}" + register: volume +- name: verify attach volume + assert: + that: + - volume is changed + - volume.hcloud_volume.server == "{{hcloud_server_name}}" + +- name: test attach Volume idempotence + hcloud_volume: + name: "{{hcloud_volume_name}}" + server: "{{hcloud_server_name}}" + register: volume +- name: verify attach Volume idempotence + assert: + that: + - volume is not changed + - volume.hcloud_volume.server == "{{hcloud_server_name}}" + +- name: test detach Volume with checkmode + hcloud_volume: + name: "{{hcloud_volume_name}}" + check_mode: yes + register: volume +- name: verify detach Volume with checkmode + assert: + that: + - volume is changed + - volume.hcloud_volume.server == "{{hcloud_server_name}}" + +- name: test detach Volume + hcloud_volume: + name: "{{hcloud_volume_name}}" + register: volume +- name: verify detach volume + assert: + that: + - volume is changed + - volume.hcloud_volume.location == "fsn1" + - volume.hcloud_volume.server != "{{hcloud_server_name}}" + +- name: test update Volume label + hcloud_volume: + name: "{{hcloud_volume_name}}" + labels: + key: value + register: volume +- name: verify test update Volume label + assert: + that: + - volume is changed + - volume.hcloud_volume.labels.key == "value" + +- name: test update Volume label with the same label + hcloud_volume: + name: "{{hcloud_volume_name}}" + labels: + key: value + register: volume +- name: verify test update Volume lable with the same label + assert: + that: + - volume is not changed + +- name: test increase Volume size + hcloud_volume: + name: "{{hcloud_volume_name}}" + size: 11 + register: volume +- name: verify test increase Volume size + assert: + that: + - volume is changed + - volume.hcloud_volume.size == 11 + +- name: test decreace Volume size + hcloud_volume: + name: "{{hcloud_volume_name}}" + size: 10 + register: volume +- name: verify test decreace Volume size + assert: + that: + - volume is not changed + - volume.hcloud_volume.size == 11 + +- name: test update Volume delete protection + hcloud_volume: + name: "{{hcloud_volume_name}}" + delete_protection: true + register: volume +- name: verify test update Volume delete protection + assert: + that: + - volume is changed + - volume.hcloud_volume.delete_protection is sameas true + +- name: test update Volume delete protection idempotency + hcloud_volume: + name: "{{hcloud_volume_name}}" + delete_protection: true + register: volume +- name: verify test update Volume delete protection idempotency + assert: + that: + - volume is not changed + - volume.hcloud_volume.delete_protection is sameas true + +- name: test Volume without delete protection set to be idempotent + hcloud_volume: + name: "{{hcloud_volume_name}}" + register: volume +- name: verify test Volume without delete protection set to be idempotent + assert: + that: + - volume is not changed + - volume.hcloud_volume.delete_protection is sameas true + +- name: test delete Volume fails if it is protected + hcloud_volume: + name: "{{hcloud_volume_name}}" + state: absent + ignore_errors: yes + register: result +- name: verify delete Volume fails if it is protected + assert: + that: + - result is failed + - 'result.msg == "volume deletion is protected"' + +- name: test update Volume delete protection + hcloud_volume: + name: "{{hcloud_volume_name}}" + delete_protection: false + register: volume +- name: verify test update Volume delete protection + assert: + that: + - volume is changed + - volume.hcloud_volume.delete_protection is sameas false + +- name: test delete Volume + hcloud_volume: + name: "{{hcloud_volume_name}}" + state: absent + register: result +- name: verify delete Volume + assert: + that: + - result is success + + +- name: test create Volume with delete protection + hcloud_volume: + name: "{{hcloud_volume_name}}" + size: 10 + location: "fsn1" + delete_protection: true + register: volume +- name: verify create Volume with delete protection + assert: + that: + - volume is changed + - volume.hcloud_volume.delete_protection is sameas true + +- name: test delete Volume fails if it is protected + hcloud_volume: + name: "{{hcloud_volume_name}}" + state: absent + ignore_errors: yes + register: result +- name: verify delete Volume fails if it is protected + assert: + that: + - result is failed + - 'result.msg == "volume deletion is protected"' + +- name: test update Volume delete protection + hcloud_volume: + name: "{{hcloud_volume_name}}" + delete_protection: false + register: volume +- name: verify test update Volume delete protection + assert: + that: + - volume is changed + - volume.hcloud_volume.delete_protection is sameas false + +- name: test delete Volume + hcloud_volume: + name: "{{hcloud_volume_name}}" + state: absent + register: result +- name: verify delete Volume + assert: + that: + - result is success + +- name: cleanup + hcloud_server: + name: "{{ hcloud_server_name }}" + state: absent + register: result +- name: verify cleanup + assert: + that: + - result is success diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume_info/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume_info/aliases new file mode 100644 index 000000000..55ec821a4 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume_info/aliases @@ -0,0 +1,2 @@ +cloud/hcloud +shippable/hcloud/group2 diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume_info/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume_info/defaults/main.yml new file mode 100644 index 000000000..52c468eeb --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume_info/defaults/main.yml @@ -0,0 +1,5 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +hcloud_prefix: "tests" +hcloud_volume_name: "{{ hcloud_prefix | truncate(60, True, '', 0) }}-i" diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume_info/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume_info/meta/main.yml new file mode 100644 index 000000000..407c9018a --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume_info/meta/main.yml @@ -0,0 +1,3 @@ +collections: + - community.general.ipfilter + - hetzner.cloud diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume_info/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume_info/tasks/main.yml new file mode 100644 index 000000000..ecea7ad31 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume_info/tasks/main.yml @@ -0,0 +1,101 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +- name: setup ensure volume is absent + hcloud_volume: + name: "{{ hcloud_volume_name }}" + state: absent + register: result + +- name: setup volume + hcloud_volume: + name: "{{hcloud_volume_name}}" + size: 10 + location: "fsn1" + labels: + key: value + register: main_volume +- name: verify setup volume + assert: + that: + - main_volume is changed + +- name: test gather hcloud volume infos in check mode + hcloud_volume_info: + register: hcloud_volume + check_mode: yes + +- name: verify test gather hcloud volume infos in check mode + vars: + volume: "{{ hcloud_volume.hcloud_volume_info|selectattr('name','equalto',hcloud_volume_name) | first }}" + assert: + that: + - hcloud_volume.hcloud_volume_info|selectattr('name','equalto','{{ hcloud_volume_name }}') | list | count == 1 + - volume.name == "{{hcloud_volume_name}}" + - volume.location == "fsn1" + - volume.size == 10 + - volume.linux_device is defined + +- name: test gather hcloud volume infos with correct label selector + hcloud_volume_info: + label_selector: "key=value" + register: hcloud_volume +- name: verify test gather hcloud volume infos with correct label selector + assert: + that: + - hcloud_volume.hcloud_volume_info|selectattr('name','equalto','{{ hcloud_volume_name }}') | list | count == 1 + +- name: test gather hcloud volume infos with wrong label selector + hcloud_volume_info: + label_selector: "key!=value" + register: hcloud_volume +- name: verify test gather hcloud volume infos with wrong label selector + assert: + that: + - hcloud_volume.hcloud_volume_info | list | count == 0 + +- name: test gather hcloud volume infos with correct name + hcloud_volume_info: + name: "{{hcloud_volume_name}}" + register: hcloud_volume +- name: verify test gather hcloud volume infos with correct name + assert: + that: + - hcloud_volume.hcloud_volume_info|selectattr('name','equalto','{{ hcloud_volume_name }}') | list | count == 1 + +- name: test gather hcloud volume infos with wrong name + hcloud_volume_info: + name: "{{hcloud_volume_name}}1" + register: hcloud_volume +- name: verify test gather hcloud volume infos with wrong name + assert: + that: + - hcloud_volume.hcloud_volume_info | list | count == 0 + +- name: test gather hcloud volume facts with correct id + hcloud_volume_info: + id: "{{main_volume.hcloud_volume.id}}" + register: hcloud_volume +- name: verify test gather hcloud volume with correct id + assert: + that: + - hcloud_volume.hcloud_volume_info|selectattr('name','equalto','{{ hcloud_volume_name }}') | list | count == 1 + +- name: test gather hcloud volume infos with wrong id + hcloud_volume_info: + name: "4711" + register: hcloud_volume +- name: verify test gather hcloud volume infos with wrong id + assert: + that: + - hcloud_volume.hcloud_volume_info | list | count == 0 + +- name: cleanup + hcloud_volume: + name: "{{ hcloud_volume_name }}" + state: absent + register: result +- name: verify cleanup + assert: + that: + - result is success diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/setup_selfsigned_certificate/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/setup_selfsigned_certificate/tasks/main.yml new file mode 100644 index 000000000..27defe44c --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/setup_selfsigned_certificate/tasks/main.yml @@ -0,0 +1,27 @@ +# Copyright: (c) 2020, Hetzner Cloud GmbH <info@hetzner-cloud.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: create a cert temp file + tempfile: + state: file + register: certificate_example_com + tags: + - prepare +- name: create a key temp file + tempfile: + state: file + register: certificate_example_com_key + tags: + - prepare + - +- name: generate certificate + shell: openssl req -nodes -new -x509 -keyout {{ certificate_example_com_key.path }} -out {{ certificate_example_com.path }} -subj "/C=DE/ST=Munich/L=Bavaria/O=Dis/CN=www.example.com" + tags: + - prepare + +- name: set facts for future roles + set_fact: + certificate_example_com: "{{ lookup('file',certificate_example_com.path) }}" + certificate_example_com_key: "{{ lookup('file',certificate_example_com_key.path) }}" + tags: + - prepare diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/setup_sshkey/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/setup_sshkey/tasks/main.yml new file mode 100644 index 000000000..18c571b67 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/setup_sshkey/tasks/main.yml @@ -0,0 +1,55 @@ +# (c) 2014, James Laska <jlaska@ansible.com> + +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +- name: create a temp file + tempfile: + state: file + register: sshkey_file + tags: + - prepare + +- name: generate sshkey + shell: echo 'y' | ssh-keygen -P '' -f {{ sshkey_file.path }} + tags: + - prepare + +- name: create another temp file + tempfile: + state: file + register: another_sshkey_file + tags: + - prepare + +- name: generate another_sshkey + shell: echo 'y' | ssh-keygen -P '' -f {{ another_sshkey_file.path }} + tags: + - prepare + +- name: record fingerprint + shell: openssl rsa -in {{ sshkey_file.path }} -pubout -outform DER 2>/dev/null | openssl md5 -c + register: fingerprint + tags: + - prepare + +- name: set facts for future roles + set_fact: + sshkey: '{{ sshkey_file.path }}' + key_material: "{{ lookup('file', sshkey_file.path ~ '.pub') }}" + another_key_material: "{{ lookup('file', another_sshkey_file.path ~ '.pub') }}" + fingerprint: '{{ fingerprint.stdout.split()[1] }}' + tags: + - prepare diff --git a/ansible_collections/hetzner/hcloud/tests/requirements.yml b/ansible_collections/hetzner/hcloud/tests/requirements.yml new file mode 100644 index 000000000..6c49d0d9e --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/requirements.yml @@ -0,0 +1,3 @@ +integration_tests_dependencies: +- community.general +- ansible.netcommon diff --git a/ansible_collections/hetzner/hcloud/tests/sanity/ignore-2.12.txt b/ansible_collections/hetzner/hcloud/tests/sanity/ignore-2.12.txt new file mode 100644 index 000000000..caf221794 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/sanity/ignore-2.12.txt @@ -0,0 +1,2 @@ +tests/utils/shippable/check_matrix.py replace-urlopen +tests/utils/shippable/timing.py shebang diff --git a/ansible_collections/hetzner/hcloud/tests/sanity/ignore-2.13.txt b/ansible_collections/hetzner/hcloud/tests/sanity/ignore-2.13.txt new file mode 100644 index 000000000..caf221794 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/sanity/ignore-2.13.txt @@ -0,0 +1,2 @@ +tests/utils/shippable/check_matrix.py replace-urlopen +tests/utils/shippable/timing.py shebang diff --git a/ansible_collections/hetzner/hcloud/tests/sanity/ignore-2.14.txt b/ansible_collections/hetzner/hcloud/tests/sanity/ignore-2.14.txt new file mode 100644 index 000000000..caf221794 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/sanity/ignore-2.14.txt @@ -0,0 +1,2 @@ +tests/utils/shippable/check_matrix.py replace-urlopen +tests/utils/shippable/timing.py shebang diff --git a/ansible_collections/hetzner/hcloud/tests/sanity/ignore-2.15.txt b/ansible_collections/hetzner/hcloud/tests/sanity/ignore-2.15.txt new file mode 100644 index 000000000..caf221794 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/sanity/ignore-2.15.txt @@ -0,0 +1,2 @@ +tests/utils/shippable/check_matrix.py replace-urlopen +tests/utils/shippable/timing.py shebang diff --git a/ansible_collections/hetzner/hcloud/tests/utils/gitlab/gitlab.sh b/ansible_collections/hetzner/hcloud/tests/utils/gitlab/gitlab.sh new file mode 100755 index 000000000..b09bd2f3a --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/utils/gitlab/gitlab.sh @@ -0,0 +1,99 @@ +#!/usr/bin/env bash + +set -o pipefail -eux + +declare -a args + +IFS='/:' read -ra args <<< "$1" + +ansible_version="${args[0]}" +# shellcheck disable=SC2034 +script="${args[1]}" + +function join { + local IFS="$1"; + shift; + echo "$*"; +} + +test="$(join / "${args[@]:1}")" +command -v python +python -V + +function retry +{ + # shellcheck disable=SC2034 + for repetition in 1 2 3; do + set +e + "$@" + result=$? + set -e + if [ ${result} == 0 ]; then + return ${result} + fi + echo "@* -> ${result}" + done + echo "Command '@*' failed 3 times!" + exit 1 +} + +command -v pip +pip --version +pip list --disable-pip-version-check +if [ "${ansible_version}" == "devel" ]; then + retry pip install https://github.com/ansible/ansible/archive/devel.tar.gz --disable-pip-version-check +else + retry pip install "https://github.com/ansible/ansible/archive/stable-${ansible_version}.tar.gz" --disable-pip-version-check +fi +export ANSIBLE_COLLECTIONS_PATHS="${HOME}/.ansible" +# shellcheck disable=SC2034 +SHIPPABLE_RESULT_DIR="$(pwd)/shippable" +TEST_DIR="${ANSIBLE_COLLECTIONS_PATHS}/ansible_collections/hetzner/hcloud" +rm -rf "${TEST_DIR}" +mkdir -p "${TEST_DIR}" +cp -r "." "${TEST_DIR}" +cd "${TEST_DIR}" + +# STAR: HACK install dependencies +retry ansible-galaxy -vvv collection install community.general +retry ansible-galaxy -vvv collection install ansible.netcommon +retry ansible-galaxy -vvv collection install community.internal_test_tools +retry pip install netaddr --disable-pip-version-check +retry pip install hcloud +retry pip install rstcheck +# END: HACK + +export PYTHONIOENCODING='utf-8' + +if [ "${JOB_TRIGGERED_BY_NAME:-}" == "nightly-trigger" ]; then + COMPLETE=yes +fi + + +if [ -n "${COMPLETE:-}" ]; then + # disable change detection triggered by setting the COMPLETE environment variable to a non-empty value + export CHANGED="" +elif [[ "${CI_COMMIT_MESSAGE}" =~ ci_complete ]]; then + # disable change detection triggered by having 'ci_complete' in the latest commit message + export CHANGED="" +else + # enable change detection (default behavior) + export CHANGED="" +fi + + +export UNSTABLE="--allow-unstable-changed" + +# remove empty core/extras module directories from PRs created prior to the repo-merge +find plugins -type d -empty -print -delete + +ansible-test env --dump --show --timeout "50" --color -v + +group="${args[1]}" +echo "$test" +if [[ "${test}" =~ hcloud ]]; then + group="${args[3]}" + bash tests/utils/gitlab/integration.sh "shippable/hcloud/group${group}/" +else + bash tests/utils/gitlab/sanity.sh "sanity/${group}" +fi diff --git a/ansible_collections/hetzner/hcloud/tests/utils/gitlab/integration.sh b/ansible_collections/hetzner/hcloud/tests/utils/gitlab/integration.sh new file mode 100755 index 000000000..c94392b85 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/utils/gitlab/integration.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +target="$1" + +HCLOUD_TOKEN=$(cat hcloud_token.txt) +# shellcheck disable=SC2034,SC2154 +changed_all_target="shippable/${cloud}/smoketest/" + +# shellcheck disable=SC2046 +echo "[default] +hcloud_api_token=${HCLOUD_TOKEN} +" >> $(pwd)/tests/integration/cloud-config-hcloud.ini +export SHIPPABLE="true" + +# shellcheck disable=SC2155 +export SHIPPABLE_BUILD_NUMBER="gl-$(cat prefix.txt)" + +# shellcheck disable=SC2155,SC2002 +export SHIPPABLE_JOB_NUMBER="$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 2 | head -n 1)" +ansible-test integration --color --local -vv "${target}" ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} ${UNSTABLE:+"$UNSTABLE"} diff --git a/ansible_collections/hetzner/hcloud/tests/utils/gitlab/sanity.sh b/ansible_collections/hetzner/hcloud/tests/utils/gitlab/sanity.sh new file mode 100755 index 000000000..4ee96aefe --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/utils/gitlab/sanity.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash + +set -o pipefail -eux + +declare -a args +IFS='/:' read -ra args <<< "$1" + +group="${args[1]}" + +if [ "${BASE_BRANCH:-}" ]; then + base_branch="origin/${BASE_BRANCH}" +else + base_branch="" +fi + +if [ "${group}" == "extra" ]; then + ../internal_test_tools/tools/run.py --color + exit +fi + +case "${group}" in + 1) options=(--skip-test pylint --skip-test ansible-doc --skip-test validate-modules) ;; + 2) options=( --test ansible-doc --test validate-modules) ;; + 3) options=(--test pylint plugins/modules/) ;; + 4) options=(--test pylint --exclude plugins/modules/) ;; +esac + +# allow collection migration sanity tests for groups 3 and 4 to pass without updating this script during migration +network_path="lib/ansible/modules/network/" + +if [ -d "${network_path}" ]; then + if [ "${group}" -eq 3 ]; then + options+=(--exclude "${network_path}") + elif [ "${group}" -eq 4 ]; then + options+=("${network_path}") + fi +fi + +pip install pycodestyle +pip install yamllint +pip install voluptuous +pip install pylint==2.5.3 +# shellcheck disable=SC2086 +ansible-test sanity --color -v --junit ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} \ + --base-branch "${base_branch}" \ + --exclude tests/utils/ \ + "${options[@]}" --allow-disabled diff --git a/ansible_collections/hetzner/hcloud/tests/utils/shippable/check_matrix.py b/ansible_collections/hetzner/hcloud/tests/utils/shippable/check_matrix.py new file mode 100755 index 000000000..dfcca3e6d --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/utils/shippable/check_matrix.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python +"""Verify the currently executing Shippable test matrix matches the one defined in the "shippable.yml" file.""" +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import datetime +import json +import os +import re +import sys +import time + +try: + from typing import NoReturn +except ImportError: + NoReturn = None + +try: + # noinspection PyCompatibility + from urllib2 import urlopen # pylint: disable=ansible-bad-import-from +except ImportError: + # noinspection PyCompatibility + from urllib.request import urlopen + + +def main(): # type: () -> None + """Main entry point.""" + repo_full_name = os.environ['REPO_FULL_NAME'] + required_repo_full_name = 'ansible-collections/hetzner.hcloud' + + if repo_full_name != required_repo_full_name: + sys.stderr.write('Skipping matrix check on repo "%s" which is not "%s".\n' % (repo_full_name, required_repo_full_name)) + return + + with open('shippable.yml', 'rb') as yaml_file: + yaml = yaml_file.read().decode('utf-8').splitlines() + + defined_matrix = [match.group(1) for match in [re.search(r'^ *- env: T=(.*)$', line) for line in yaml] if match and match.group(1) != 'none'] + + if not defined_matrix: + fail('No matrix entries found in the "shippable.yml" file.', + 'Did you modify the "shippable.yml" file?') + + run_id = os.environ['SHIPPABLE_BUILD_ID'] + sleep = 1 + jobs = [] + + for attempts_remaining in range(4, -1, -1): + try: + jobs = json.loads(urlopen('https://api.shippable.com/jobs?runIds=%s' % run_id).read()) + + if not isinstance(jobs, list): + raise Exception('Shippable run %s data is not a list.' % run_id) + + break + except Exception as ex: + if not attempts_remaining: + fail('Unable to retrieve Shippable run %s matrix.' % run_id, + str(ex)) + + sys.stderr.write('Unable to retrieve Shippable run %s matrix: %s\n' % (run_id, ex)) + sys.stderr.write('Trying again in %d seconds...\n' % sleep) + time.sleep(sleep) + sleep *= 2 + + if len(jobs) != len(defined_matrix): + if len(jobs) == 1: + hint = '\n\nMake sure you do not use the "Rebuild with SSH" option.' + else: + hint = '' + + fail('Shippable run %s has %d jobs instead of the expected %d jobs.' % (run_id, len(jobs), len(defined_matrix)), + 'Try re-running the entire matrix.%s' % hint) + + actual_matrix = dict((job.get('jobNumber'), dict(tuple(line.split('=', 1)) for line in job.get('env', [])).get('T', '')) for job in jobs) + errors = [(job_number, test, actual_matrix.get(job_number)) for job_number, test in enumerate(defined_matrix, 1) if actual_matrix.get(job_number) != test] + + if len(errors): + error_summary = '\n'.join('Job %s expected "%s" but found "%s" instead.' % (job_number, expected, actual) for job_number, expected, actual in errors) + + fail('Shippable run %s has a job matrix mismatch.' % run_id, + 'Try re-running the entire matrix.\n\n%s' % error_summary) + + +def fail(message, output): # type: (str, str) -> NoReturn + # Include a leading newline to improve readability on Shippable "Tests" tab. + # Without this, the first line becomes indented. + output = '\n' + output.strip() + + timestamp = datetime.datetime.utcnow().replace(microsecond=0).isoformat() + + # hack to avoid requiring junit-xml, which isn't pre-installed on Shippable outside our test containers + xml = ''' +<?xml version="1.0" encoding="utf-8"?> +<testsuites disabled="0" errors="1" failures="0" tests="1" time="0.0"> +\t<testsuite disabled="0" errors="1" failures="0" file="None" log="None" name="ansible-test" skipped="0" tests="1" time="0" timestamp="%s" url="None"> +\t\t<testcase classname="timeout" name="timeout"> +\t\t\t<error message="%s" type="error">%s</error> +\t\t</testcase> +\t</testsuite> +</testsuites> +''' % (timestamp, message, output) + + path = 'shippable/testresults/check-matrix.xml' + dir_path = os.path.dirname(path) + + if not os.path.exists(dir_path): + os.makedirs(dir_path) + + with open(path, 'w') as junit_fd: + junit_fd.write(xml.lstrip()) + + sys.stderr.write(message + '\n') + sys.stderr.write(output + '\n') + + sys.exit(1) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/hetzner/hcloud/tests/utils/shippable/hcloud.sh b/ansible_collections/hetzner/hcloud/tests/utils/shippable/hcloud.sh new file mode 100755 index 000000000..da037e09e --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/utils/shippable/hcloud.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +set -o pipefail -eux + +declare -a args +IFS='/:' read -ra args <<< "$1" + +cloud="${args[0]}" +python="${args[1]}" +group="${args[2]}" + +target="shippable/${cloud}/group${group}/" + +stage="${S:-prod}" + +changed_all_target="shippable/${cloud}/smoketest/" + +if ! ansible-test integration "${changed_all_target}" --list-targets > /dev/null 2>&1; then + # no smoketest tests are available for this cloud + changed_all_target="none" +fi + +if [ "${group}" == "1" ]; then + # only run smoketest tests for group1 + changed_all_mode="include" +else + # smoketest tests already covered by group1 + changed_all_mode="exclude" +fi + +# shellcheck disable=SC2086 +ansible-test integration --color -v --retry-on-error "${target}" ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} ${UNSTABLE:+"$UNSTABLE"} \ + --remote-terminate always --remote-stage "${stage}" \ + --docker --python "${python}" --changed-all-target "${changed_all_target}" --changed-all-mode "${changed_all_mode}" diff --git a/ansible_collections/hetzner/hcloud/tests/utils/shippable/sanity.sh b/ansible_collections/hetzner/hcloud/tests/utils/shippable/sanity.sh new file mode 100755 index 000000000..9339aeda4 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/utils/shippable/sanity.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +set -o pipefail -eux + +declare -a args +IFS='/:' read -ra args <<< "$1" + +group="${args[1]}" + +if [ "${BASE_BRANCH:-}" ]; then + base_branch="origin/${BASE_BRANCH}" +else + base_branch="" +fi + +if [ "${group}" == "extra" ]; then + # ansible-galaxy -vvv collection install community.internal_test_tools + git clone --single-branch --depth 1 https://github.com/ansible-collections/community.internal_test_tools.git ../../community/internal_test_tools + + ../internal_test_tools/tools/run.py --color + exit +fi + +# shellcheck disable=SC2086 +ansible-test sanity --color -v --junit ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} \ + --docker --base-branch "${base_branch}" \ + --allow-disabled diff --git a/ansible_collections/hetzner/hcloud/tests/utils/shippable/shippable.sh b/ansible_collections/hetzner/hcloud/tests/utils/shippable/shippable.sh new file mode 100755 index 000000000..8c0bd6deb --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/utils/shippable/shippable.sh @@ -0,0 +1,213 @@ +#!/usr/bin/env bash + +set -o pipefail -eux + +declare -a args +IFS='/:' read -ra args <<< "$1" + +ansible_version="${args[0]}" +script="${args[1]}" + +function join { + local IFS="$1"; + shift; + echo "$*"; +} + +# Ensure we can write other collections to this dir +sudo chown "$(whoami)" "${PWD}/../../" + +test="$(join / "${args[@]:1}")" + +docker images ansible/ansible +docker images quay.io/ansible/* +docker ps + +for container in $(docker ps --format '{{.Image}} {{.ID}}' | grep -v -e '^drydock/' -e '^quay.io/ansible/azure-pipelines-test-container:' | sed 's/^.* //'); do + docker rm -f "${container}" || true # ignore errors +done + +docker ps + +if [ -d /home/shippable/cache/ ]; then + ls -la /home/shippable/cache/ +fi + +command -v python +python -V + +function retry +{ + # shellcheck disable=SC2034 + for repetition in 1 2 3; do + set +e + "$@" + result=$? + set -e + if [ ${result} == 0 ]; then + return ${result} + fi + echo "@* -> ${result}" + done + echo "Command '@*' failed 3 times!" + exit 1 +} + +command -v pip +pip --version +pip list --disable-pip-version-check +if [ "${ansible_version}" == "devel" ]; then + retry pip install https://github.com/ansible/ansible/archive/devel.tar.gz --disable-pip-version-check +else + retry pip install "https://github.com/ansible/ansible/archive/stable-${ansible_version}.tar.gz" --disable-pip-version-check +fi + +if [ "${SHIPPABLE_BUILD_ID:-}" ]; then + export ANSIBLE_COLLECTIONS_PATHS="${HOME}/.ansible" + SHIPPABLE_RESULT_DIR="$(pwd)/shippable" + TEST_DIR="${ANSIBLE_COLLECTIONS_PATHS}/ansible_collections/hetzner/hcloud" + mkdir -p "${TEST_DIR}" + cp -aT "${SHIPPABLE_BUILD_DIR}" "${TEST_DIR}" + cd "${TEST_DIR}" +else + export ANSIBLE_COLLECTIONS_PATHS="${PWD}/../../../" +fi + +# STAR: HACK install dependencies +retry ansible-galaxy -vvv collection install community.general +retry ansible-galaxy -vvv collection install ansible.netcommon + +retry pip install hcloud +retry pip install netaddr --disable-pip-version-check +retry ansible-galaxy -vvv collection install community.internal_test_tools +# END: HACK + +export PYTHONIOENCODING='utf-8' + +if [ "${JOB_TRIGGERED_BY_NAME:-}" == "nightly-trigger" ]; then + COVERAGE=yes + COMPLETE=yes +fi + +if [ -n "${COVERAGE:-}" ]; then + # on-demand coverage reporting triggered by setting the COVERAGE environment variable to a non-empty value + export COVERAGE="--coverage" +elif [[ "${COMMIT_MESSAGE}" =~ ci_coverage ]]; then + # on-demand coverage reporting triggered by having 'ci_coverage' in the latest commit message + export COVERAGE="--coverage" +else + # on-demand coverage reporting disabled (default behavior, always-on coverage reporting remains enabled) + export COVERAGE="--coverage-check" +fi + +if [ -n "${COMPLETE:-}" ]; then + # disable change detection triggered by setting the COMPLETE environment variable to a non-empty value + export CHANGED="" +elif [[ "${COMMIT_MESSAGE}" =~ ci_complete ]]; then + # disable change detection triggered by having 'ci_complete' in the latest commit message + export CHANGED="" +else + # enable change detection (default behavior) + export CHANGED="--changed" +fi + +if [ "${IS_PULL_REQUEST:-}" == "true" ]; then + # run unstable tests which are targeted by focused changes on PRs + export UNSTABLE="--allow-unstable-changed" +else + # do not run unstable tests outside PRs + export UNSTABLE="" +fi + +# remove empty core/extras module directories from PRs created prior to the repo-merge +find plugins -type d -empty -print -delete + +function cleanup +{ + # for complete on-demand coverage generate a report for all files with no coverage on the "sanity/5" job so we only have one copy + if [ "${COVERAGE}" == "--coverage" ] && [ "${CHANGED}" == "" ] && [ "${test}" == "sanity/5" ]; then + stub="--stub" + # trigger coverage reporting for stubs even if no other coverage data exists + mkdir -p tests/output/coverage/ + else + stub="" + fi + + if [ -d tests/output/coverage/ ]; then + if find tests/output/coverage/ -mindepth 1 -name '.*' -prune -o -print -quit | grep -q .; then + process_coverage='yes' # process existing coverage files + elif [ "${stub}" ]; then + process_coverage='yes' # process coverage when stubs are enabled + else + process_coverage='' + fi + + if [ "${process_coverage}" ]; then + # use python 3.7 for coverage to avoid running out of memory during coverage xml processing + # only use it for coverage to avoid the additional overhead of setting up a virtual environment for a potential no-op job + virtualenv --python /usr/bin/python3.7 ~/ansible-venv + set +ux + . ~/ansible-venv/bin/activate + set -ux + + # shellcheck disable=SC2086 + ansible-test coverage xml --color -v --requirements --group-by command --group-by version ${stub:+"$stub"} + cp -a tests/output/reports/coverage=*.xml "$SHIPPABLE_RESULT_DIR/codecoverage/" + + if [ "${ansible_version}" != "2.9" ]; then + # analyze and capture code coverage aggregated by integration test target + ansible-test coverage analyze targets generate -v "$SHIPPABLE_RESULT_DIR/testresults/coverage-analyze-targets.json" + fi + + # upload coverage report to codecov.io only when using complete on-demand coverage + if [ "${COVERAGE}" == "--coverage" ] && [ "${CHANGED}" == "" ]; then + for file in tests/output/reports/coverage=*.xml; do + flags="${file##*/coverage=}" + flags="${flags%-powershell.xml}" + flags="${flags%.xml}" + # remove numbered component from stub files when converting to tags + flags="${flags//stub-[0-9]*/stub}" + flags="${flags//=/,}" + flags="${flags//[^a-zA-Z0-9_,]/_}" + + bash <(curl -s https://ansible-ci-files.s3.us-east-1.amazonaws.com/codecov/codecov.sh) \ + -f "${file}" \ + -F "${flags}" \ + -n "${test}" \ + -t 8a86e979-f37b-4d5d-95a4-960c280d5eaa \ + -X coveragepy \ + -X gcov \ + -X fix \ + -X search \ + -X xcode \ + || echo "Failed to upload code coverage report to codecov.io: ${file}" + done + fi + fi + fi + + if [ -d tests/output/junit/ ]; then + cp -aT tests/output/junit/ "$SHIPPABLE_RESULT_DIR/testresults/" + fi + + if [ -d tests/output/data/ ]; then + cp -a tests/output/data/ "$SHIPPABLE_RESULT_DIR/testresults/" + fi + + if [ -d tests/output/bot/ ]; then + cp -aT tests/output/bot/ "$SHIPPABLE_RESULT_DIR/testresults/" + fi +} + +if [ "${SHIPPABLE_BUILD_ID:-}" ]; then trap cleanup EXIT; fi + +if [[ "${COVERAGE:-}" == "--coverage" ]]; then + timeout=60 +else + timeout=45 +fi + +ansible-test env --dump --show --timeout "${timeout}" --color -v + +if [ "${SHIPPABLE_BUILD_ID:-}" ]; then "tests/utils/shippable/check_matrix.py"; fi +"tests/utils/shippable/${script}.sh" "${test}" diff --git a/ansible_collections/hetzner/hcloud/tests/utils/shippable/timing.py b/ansible_collections/hetzner/hcloud/tests/utils/shippable/timing.py new file mode 100755 index 000000000..fb538271b --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/utils/shippable/timing.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3.7 +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import sys +import time + +start = time.time() + +sys.stdin.reconfigure(errors='surrogateescape') +sys.stdout.reconfigure(errors='surrogateescape') + +for line in sys.stdin: + seconds = time.time() - start + sys.stdout.write('%02d:%02d %s' % (seconds // 60, seconds % 60, line)) + sys.stdout.flush() diff --git a/ansible_collections/hetzner/hcloud/tests/utils/shippable/timing.sh b/ansible_collections/hetzner/hcloud/tests/utils/shippable/timing.sh new file mode 100755 index 000000000..77e257830 --- /dev/null +++ b/ansible_collections/hetzner/hcloud/tests/utils/shippable/timing.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +set -o pipefail -eu + +"$@" 2>&1 | "$(dirname "$0")/timing.py" |