diff options
Diffstat (limited to 'ansible_collections/community/rabbitmq')
151 files changed, 11954 insertions, 0 deletions
diff --git a/ansible_collections/community/rabbitmq/.azure-pipelines/README.md b/ansible_collections/community/rabbitmq/.azure-pipelines/README.md new file mode 100644 index 000000000..385e70bac --- /dev/null +++ b/ansible_collections/community/rabbitmq/.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/community/rabbitmq/.azure-pipelines/azure-pipelines.yml b/ansible_collections/community/rabbitmq/.azure-pipelines/azure-pipelines.yml new file mode 100644 index 000000000..7002adffa --- /dev/null +++ b/ansible_collections/community/rabbitmq/.azure-pipelines/azure-pipelines.yml @@ -0,0 +1,361 @@ +trigger: + batch: true + branches: + include: + - main + - stable-* + +pr: + autoCancel: true + branches: + include: + - main + - stable-* + +schedules: + - cron: 0 9 * * * + displayName: Nightly + always: true + branches: + include: + - main + - stable-* + +variables: + - name: checkoutPath + value: ansible_collections/community/rabbitmq + - 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 & units + - stage: Ansible_devel + displayName: Sanity & Units devel + dependsOn: [] + jobs: + - template: templates/matrix.yml + parameters: + targets: + - name: Sanity + test: 'devel/sanity/1' + - name: Units + test: 'devel/units/1' + + - stage: Ansible_2_14 + displayName: Sanity & Units 2.14 + dependsOn: [] + jobs: + - template: templates/matrix.yml + parameters: + targets: + - name: Sanity + test: '2.14/sanity/1' + - name: Units + test: '2.14/units/1' + + - stage: Ansible_2_13 + displayName: Sanity & Units 2.13 + dependsOn: [] + jobs: + - template: templates/matrix.yml + parameters: + targets: + - name: Sanity + test: '2.13/sanity/1' + - name: Units + test: '2.13/units/1' + + - stage: Ansible_2_12 + displayName: Sanity & Units 2.12 + dependsOn: [] + jobs: + - template: templates/matrix.yml + parameters: + targets: + - name: Sanity + test: '2.12/sanity/1' + - name: Units + test: '2.12/units/1' + + - stage: Ansible_2_11 + displayName: Sanity & Units 2.11 + dependsOn: [] + jobs: + - template: templates/matrix.yml + parameters: + targets: + - name: Sanity + test: '2.11/sanity/1' + - name: Units + test: '2.11/units/1' + + - stage: Ansible_2_10 + displayName: Sanity & Units 2.10 + dependsOn: [] + jobs: + - template: templates/matrix.yml + parameters: + targets: + - name: Sanity + test: '2.10/sanity/1' + - name: Units + test: '2.10/units/1' + + - stage: Ansible_2_9 + displayName: Sanity & Units 2.9 + dependsOn: [] + jobs: + - template: templates/matrix.yml + parameters: + targets: + - name: Sanity + test: '2.9/sanity/1' + - name: Units + test: '2.9/units/1' + +### Docker + - stage: Docker_devel + displayName: Docker devel + dependsOn: [] + jobs: + - template: templates/matrix.yml + parameters: + # $ansible_version/$script/$test => devel / linux / ubuntu2004/1 + # shippable/linux.sh target=shippable/posix/group1 + # ansible-test integration ... shippable/posix/group1 --docker ubuntu2004 + # dir: integration/targets/ + testFormat: devel/linux/{0}/1 + targets: + # NOTE: update integration roles to support platform before enabling here. + #- name: CentOS 6 + # test: centos6 + #- name: CentOS 7 + # test: centos7 + #- name: Fedora 33 + # test: fedora33 + #- name: Fedora 34 + # test: fedora34 + #- name: openSUSE 15 py2 + # test: opensuse15py2 + #- name: openSUSE 15 py3 + # test: opensuse15 + #- name: Ubuntu 18.04 + # test: ubuntu1804 + - name: Ubuntu 22.04 + test: ubuntu2204 + # Currently 20.04 is causing devel to fail. This maybe due to Ubuntu 20.04 running python + # 3.8, however, ansible-test requires 3.9+. This means ansible test spins up a controller + # and target container which is probably why rabbitmq_publish is not able to connect to + # rabbitmq on localhost. + #- name: Ubuntu 20.04 + # test: ubuntu2004 + + - stage: Docker_2_14 + displayName: Docker 2.14 + dependsOn: [] + jobs: + - template: templates/matrix.yml + parameters: + testFormat: 2.14/linux/{0}/1 + targets: + - name: Ubuntu 22.04 + test: ubuntu2204 + # Currently 20.04 is causing devel to fail. This maybe due to Ubuntu 20.04 running python + # 3.8, however, ansible-test requires 3.9+. This means ansible test spins up a controller + # and target container which is probably why rabbitmq_publish is not able to connect to + # rabbitmq on localhost. + #- name: Ubuntu 20.04 + # test: ubuntu2004 + + - stage: Docker_2_13 + displayName: Docker 2.13 + dependsOn: [] + jobs: + - template: templates/matrix.yml + parameters: + testFormat: 2.13/linux/{0}/1 + targets: + #- name: Ubuntu 18.04 + # test: ubuntu1804 + - name: Ubuntu 20.04 + test: ubuntu2004 + + - stage: Docker_2_12 + displayName: Docker 2.12 + dependsOn: [] + jobs: + - template: templates/matrix.yml + parameters: + testFormat: 2.12/linux/{0}/1 + targets: + #- name: Ubuntu 18.04 + # test: ubuntu1804 + - name: Ubuntu 20.04 + test: ubuntu2004 + + - stage: Docker_2_11 + displayName: Docker 2.11 + dependsOn: [] + jobs: + - template: templates/matrix.yml + parameters: + testFormat: 2.11/linux/{0}/1 + targets: + # NOTE: update integration roles to support platform before enabling here. + #- name: CentOS 6 + # test: centos6 + #- name: CentOS 7 + # test: centos7 + #- name: Fedora 32 + # test: fedora32 + #- name: Fedora 33 + # test: fedora33 + #- name: openSUSE 15 py2 + # test: opensuse15py2 + #- name: openSUSE 15 py3 + # test: opensuse15 + - name: Ubuntu 18.04 + test: ubuntu1804 + - name: Ubuntu 20.04 + test: ubuntu2004 + + - stage: Docker_2_10 + displayName: Docker 2.10 + dependsOn: [] + jobs: + - template: templates/matrix.yml + parameters: + testFormat: 2.10/linux/{0}/1 + targets: + # NOTE: update integration roles to support platform before enabling here. + #- name: CentOS 6 + # test: centos6 + #- name: CentOS 7 + # test: centos7 + #- name: Fedora 31 + # test: fedora31 + #- name: Fedora 32 + # test: fedora32 + #- name: openSUSE 15 py2 + # test: opensuse15py2 + #- name: openSUSE 15 py3 + # test: opensuse15 + - name: Ubuntu 18.04 + test: ubuntu1804 + - name: Ubuntu 20.04 + test: ubuntu2004 + + - stage: Docker_2_9 + displayName: Docker 2.9 + dependsOn: [] + jobs: + - template: templates/matrix.yml + parameters: + testFormat: 2.9/linux/{0}/1 + targets: + # NOTE: update integration roles to support platform before adding here. + - name: Ubuntu 18.04 + test: ubuntu1804 # freezes in rabbitmq_setup + # ansible-test 2.9 does not support 20.04 (image not included in its test-containers list) + +### Remote +# - stage: Remote_devel +# displayName: Remote devel +# dependsOn: [] +# jobs: +# - template: templates/matrix.yml +# parameters: +# testFormat: devel/{0}/1 +# targets: +# - name: RHEL 7.9 +# test: rhel/7.9 +# - name: RHEL 8.3 +# test: rhel/8.3 +# - name: FreeBSD 11.4 +# test: freebsd/11.4 +# - name: FreeBSD 12.2 +# test: freebsd/12.2 +# - stage: Remote_2_11 +# displayName: Remote 2.11 +# dependsOn: [] +# jobs: +# - template: templates/matrix.yml +# parameters: +# testFormat: 2.11/{0}/1 +# targets: +# - name: RHEL 7.9 +# test: rhel/7.9 +# - name: RHEL 8.3 +# test: rhel/8.3 +# - name: FreeBSD 11.4 +# test: freebsd/11.4 +# - name: FreeBSD 12.2 +# test: freebsd/12.2 +# - stage: Remote_2_10 +# displayName: Remote 2.10 +# dependsOn: [] +# jobs: +# - template: templates/matrix.yml +# parameters: +# testFormat: 2.10/{0}/1 +# targets: +# - name: RHEL 7.8 +# test: rhel/7.8 +# - name: RHEL 8.2 +# test: rhel/8.2 +# - name: FreeBSD 11.1 +# test: freebsd/11.1 +# - name: FreeBSD 12.1 +# test: freebsd/12.1 +# - stage: Remote_2_9 +# displayName: Remote 2.9 +# dependsOn: [] +# jobs: +# - template: templates/matrix.yml +# parameters: +# testFormat: 2.9/{0}/1 +# targets: +# - name: RHEL 7.8 +# test: rhel/7.8 +# - name: RHEL 8.2 +# test: rhel/8.2 +# - name: FreeBSD 11.1 +# test: freebsd/11.1 +# - name: FreeBSD 12.1 +# test: freebsd/12.1 + - stage: Summary + condition: succeededOrFailed() + dependsOn: + - Ansible_devel + - Ansible_2_14 + - Ansible_2_13 + - Ansible_2_12 + - Ansible_2_11 + - Ansible_2_10 + - Ansible_2_9 + - Docker_devel + - Docker_2_14 + - Docker_2_13 + - Docker_2_12 + - Docker_2_11 + - Docker_2_10 + - Docker_2_9 + #- Remote_devel + #- Remote_2_11 + #- Remote_2_10 + #- Remote_2_9 + jobs: + - template: templates/coverage.yml diff --git a/ansible_collections/community/rabbitmq/.azure-pipelines/scripts/aggregate-coverage.sh b/ansible_collections/community/rabbitmq/.azure-pipelines/scripts/aggregate-coverage.sh new file mode 100755 index 000000000..f3113dd0a --- /dev/null +++ b/ansible_collections/community/rabbitmq/.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/community/rabbitmq/.azure-pipelines/scripts/combine-coverage.py b/ansible_collections/community/rabbitmq/.azure-pipelines/scripts/combine-coverage.py new file mode 100755 index 000000000..506ade646 --- /dev/null +++ b/ansible_collections/community/rabbitmq/.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/community/rabbitmq/.azure-pipelines/scripts/process-results.sh b/ansible_collections/community/rabbitmq/.azure-pipelines/scripts/process-results.sh new file mode 100755 index 000000000..f3f1d1bae --- /dev/null +++ b/ansible_collections/community/rabbitmq/.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/community/rabbitmq/.azure-pipelines/scripts/publish-codecov.sh b/ansible_collections/community/rabbitmq/.azure-pipelines/scripts/publish-codecov.sh new file mode 100755 index 000000000..6d184f0b8 --- /dev/null +++ b/ansible_collections/community/rabbitmq/.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/community/rabbitmq/.azure-pipelines/scripts/report-coverage.sh b/ansible_collections/community/rabbitmq/.azure-pipelines/scripts/report-coverage.sh new file mode 100755 index 000000000..1bd91bdc9 --- /dev/null +++ b/ansible_collections/community/rabbitmq/.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/community/rabbitmq/.azure-pipelines/scripts/run-tests.sh b/ansible_collections/community/rabbitmq/.azure-pipelines/scripts/run-tests.sh new file mode 100755 index 000000000..a947fdf01 --- /dev/null +++ b/ansible_collections/community/rabbitmq/.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/community/rabbitmq/.azure-pipelines/scripts/time-command.py b/ansible_collections/community/rabbitmq/.azure-pipelines/scripts/time-command.py new file mode 100755 index 000000000..5e8eb8d4c --- /dev/null +++ b/ansible_collections/community/rabbitmq/.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/community/rabbitmq/.azure-pipelines/templates/coverage.yml b/ansible_collections/community/rabbitmq/.azure-pipelines/templates/coverage.yml new file mode 100644 index 000000000..1864e4441 --- /dev/null +++ b/ansible_collections/community/rabbitmq/.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/community/rabbitmq/.azure-pipelines/templates/matrix.yml b/ansible_collections/community/rabbitmq/.azure-pipelines/templates/matrix.yml new file mode 100644 index 000000000..4e9555dd3 --- /dev/null +++ b/ansible_collections/community/rabbitmq/.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/community/rabbitmq/.azure-pipelines/templates/test.yml b/ansible_collections/community/rabbitmq/.azure-pipelines/templates/test.yml new file mode 100644 index 000000000..5250ed802 --- /dev/null +++ b/ansible_collections/community/rabbitmq/.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/community/rabbitmq/.gitignore b/ansible_collections/community/rabbitmq/.gitignore new file mode 100644 index 000000000..77a083559 --- /dev/null +++ b/ansible_collections/community/rabbitmq/.gitignore @@ -0,0 +1,5 @@ +# ansible-test stuff +/tests/output/ + +# antsibull-changelog stuff +/changelogs/.plugin-cache.yaml diff --git a/ansible_collections/community/rabbitmq/CHANGELOG.rst b/ansible_collections/community/rabbitmq/CHANGELOG.rst new file mode 100644 index 000000000..2972a9aa7 --- /dev/null +++ b/ansible_collections/community/rabbitmq/CHANGELOG.rst @@ -0,0 +1,116 @@ +================================ +Community.Rabbitmq Release Notes +================================ + +.. contents:: Topics + + +v1.2.3 +====== + +Release Summary +--------------- + +This is the minor release of the ``community.rabbitmq`` collection. +This changelog contains all changes to the modules and plugins in this collection +that have been made after the 1.2.2 release. + + +Minor Changes +------------- + +- rabbitmq_exchange - adding ability to specify exchange types that are enabled via plugins. I(x-random), I(x-consistent-hash) and I(x-recent-history) (https://github.com/ansible-collections/community.rabbitmq/pull/142). +- rabbitmq_publish - fixing issue with publishing to exchanges and adding exchange documentation examples. Publishing to an exchange or queue is now mutually exclusive (https://github.com/ansible-collections/community.rabbitmq/pull/140). + +Bugfixes +-------- + +- rabbitmq_queue - fixing an issue where a special character in the queue name would result in an API error (https://github.com/ansible-collections/community.rabbitmq/issues/114). +- Various CI fixes (https://github.com/ansible-collections/community.rabbitmq/pull/139 & https://github.com/ansible-collections/community.rabbitmq/pull/141). + +v1.2.2 +====== + +Release Summary +--------------- + +This is the minor release of the ``community.rabbitmq`` collection. +This changelog contains all changes to the modules and plugins in this collection +that have been made after the 1.2.1 release. + +Bugfixes +-------- + +- user module - set supports_check_mode flag to False, as the module does not actually support check mode. + +v1.2.1 +====== + +Release Summary +--------------- + +This is the minor release of the ``community.rabbitmq`` collection. +This changelog contains all changes to the modules and plugins in this collection +that have been made after the 1.2.0 release. + +Bugfixes +-------- + +- Include ``PSF-license.txt`` file for ``plugins/module_utils/_version.py``. + +v1.2.0 +====== + +Release Summary +--------------- + +This is the minor release of the ``community.rabbitmq`` collection. +This changelog contains all changes to the modules and plugins in this collection +that have been made after the 1.1.0 release. + +Minor Changes +------------- + +- rabbitmq_user - add support for `topic authorization <https://www.rabbitmq.com/access-control.html#topic-authorisation>`_ (featured in RabbitMQ 3.7.0) (https://github.com/ansible-collections/community.rabbitmq/pull/73). + +Bugfixes +-------- + +- Collection core functions - use vendored version of ``distutils.version`` instead of the deprecated Python standard library ``distutils``. + +v1.1.0 +====== + +Release Summary +--------------- + +This is the minor release of the ``community.rabbitmq`` collection. +This changelog contains all changes to the modules and plugins in this collection +that have been made after release 1.0.3. + +Bugfixes +-------- + +- rabbitmq_policy - The ``_policy_check`` piece of the policy module (``policy_data``) is typically list based on a split of the variable ``policy``. However ``policy`` in some cases does not contain data. The fix allows ``tags`` to attempt to load as json first but in the case of failure, assign ``tags`` without using the json loader (https://github.com/ansible-collections/community.rabbitmq/pull/28). + +New Modules +----------- + +- community.rabbitmq.rabbitmq_feature_flag - Enables feature flag +- community.rabbitmq.rabbitmq_upgrade - Execute rabbitmq-upgrade commands +- community.rabbitmq.rabbitmq_user_limits - Manage RabbitMQ user limits + +v1.0.0 +====== + +Minor Changes +------------- + +- rabbitmq_publish - Support for connecting with SSL certificates. + +Bugfixes +-------- + +- Refactor RabbitMQ user module to first check the version of the daemon and then, when possible add flags to `rabbitmqctl` so that a machine readable output is returned. Also, depending on the version, parse the output in correctly. Expands tests accordingly. (https://github.com/ansible/ansible/issues/48890) +- rabbitmq lookup plugin - Fix for rabbitmq lookups failing when using pika v1.0.0 and newer. +- rabbitmq_publish - Fix to ensure the module works correctly for pika v1.0.0 and later. (https://github.com/ansible/ansible/pull/61960) diff --git a/ansible_collections/community/rabbitmq/CONTRIBUTING.md b/ansible_collections/community/rabbitmq/CONTRIBUTING.md new file mode 100644 index 000000000..70cd5557e --- /dev/null +++ b/ansible_collections/community/rabbitmq/CONTRIBUTING.md @@ -0,0 +1,5 @@ +# Contributing + +Refer to the [Ansible Contributing guidelines](https://docs.ansible.com/ansible/devel/community/index.html) to learn how to contribute to this collection. + +Refer to the [review checklist](https://docs.ansible.com/ansible/devel/community/collection_contributors/collection_reviewing.html) when triaging issues or reviewing PRs. diff --git a/ansible_collections/community/rabbitmq/COPYING b/ansible_collections/community/rabbitmq/COPYING new file mode 100644 index 000000000..f288702d2 --- /dev/null +++ b/ansible_collections/community/rabbitmq/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <https://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 <https://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 +<https://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 +<https://www.gnu.org/licenses/why-not-lgpl.html>. diff --git a/ansible_collections/community/rabbitmq/FILES.json b/ansible_collections/community/rabbitmq/FILES.json new file mode 100644 index 000000000..386bcdee6 --- /dev/null +++ b/ansible_collections/community/rabbitmq/FILES.json @@ -0,0 +1,1566 @@ +{ + "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": "fcad40ddb0bcc93a39f7d1a5085a0cda0b6c16cfcc52d8874a23ddc090f0c57f", + "format": 1 + }, + { + "name": "changelogs", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "changelogs/changelog.yaml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2c1ac1a9869d7c8d4e39046dab344e152b41c4568e830c835c5a160c5c7f4d9f", + "format": 1 + }, + { + "name": "changelogs/config.yaml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1c6e8ebc7f2f2664502fc5df6d12761510a0ec8194e1127ca5dc088397aabc80", + "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": "df18179bb2f5447a56ac92261a911649b96821c0b2c08eea62d5cc6b0195203f", + "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/rabbitmq.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "822d0d3404f7e110538441d3b6ad9b8146a5d0c508873fcb5273805513a1de88", + "format": 1 + }, + { + "name": "plugins/lookup", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/lookup/rabbitmq.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b9c692de8e40a03374e0ca861a7878a0fd62e030b6b318dfc92f4b8ae20a688e", + "format": 1 + }, + { + "name": "plugins/module_utils", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/module_utils/_version.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "fc99ad0772eb55f555acad68a9f9dd55c2eea2d230cf5673bec8383e444a11a7", + "format": 1 + }, + { + "name": "plugins/module_utils/rabbitmq.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e0f334c9bff8d4198ee16bec49f13f08da5b9c43191109cab7c17d26bbddf218", + "format": 1 + }, + { + "name": "plugins/module_utils/version.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "83a80fdb81b2bb048db40d1db1dc364d84e209c17ef87a161bf69e39855040a6", + "format": 1 + }, + { + "name": "plugins/modules", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/modules/rabbitmq_binding.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ad5ff96c1abeac73de95e819c3b9f98486ecc7d92cc086192cad9533faa4cf95", + "format": 1 + }, + { + "name": "plugins/modules/rabbitmq_exchange.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "26ba3cb69031f0a92804f5db0f5b7b9b78b7a6cbef64cddf640707e157ee7d88", + "format": 1 + }, + { + "name": "plugins/modules/rabbitmq_feature_flag.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "36c470e97c58cd89fe6e544abab4a08257fa8b85d2076bf461bbf13bff4ff44a", + "format": 1 + }, + { + "name": "plugins/modules/rabbitmq_global_parameter.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "284e17b8dae90c112d1e1146ff79d6863fdfaf389fcde2a4734773db53242bb1", + "format": 1 + }, + { + "name": "plugins/modules/rabbitmq_parameter.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2cd6c3187093db8314603108d9d91c318c784af4bd07cc4f196a6bc01dcddc33", + "format": 1 + }, + { + "name": "plugins/modules/rabbitmq_plugin.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e34fffbbf9f8f50d61205c7c34a64a952d8a52e85a619e10dacb25b9115f46e6", + "format": 1 + }, + { + "name": "plugins/modules/rabbitmq_policy.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f66e3159e234a211e6e533085755173acaa60af2e9386ed02fbbbf66832b2bc2", + "format": 1 + }, + { + "name": "plugins/modules/rabbitmq_publish.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d5585f87fd00075bad137a8b185cd5d9fabc8ae7576629e8b01c200610e8177a", + "format": 1 + }, + { + "name": "plugins/modules/rabbitmq_queue.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "63c304c8c9747d70a61fe1bcb1b5134ae21c860c4a8d4adaf56a9d7c8f890134", + "format": 1 + }, + { + "name": "plugins/modules/rabbitmq_upgrade.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0e6c6f8f2b9a395d93acf2b9e5ed63792b686874ca9e9fa4a82f7c0d81f855c0", + "format": 1 + }, + { + "name": "plugins/modules/rabbitmq_user.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5fe7cd8150b3b76e87f8f86fe164f89070d9f62a61411d6b84e004c18949451f", + "format": 1 + }, + { + "name": "plugins/modules/rabbitmq_user_limits.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a4950c5bb880bdc14bda10ea6bbc3c54ef15e91dafb8da6cfd746ec64667c0d1", + "format": 1 + }, + { + "name": "plugins/modules/rabbitmq_vhost.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "56ae10b61350a1c80c1fdf38852386a23a4267427cd6cb94250b24a8c14f551d", + "format": 1 + }, + { + "name": "plugins/modules/rabbitmq_vhost_limits.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "854b495270b50f80735fa001285893af9a10b1c6cb8ea02ef0d75f4f0e8d048a", + "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/lookup_rabbitmq", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/lookup_rabbitmq/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/lookup_rabbitmq/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6b11845fec8855951fdbf1ad471fa8174a6ba299ea4ee3d62a47a1605eaea95e", + "format": 1 + }, + { + "name": "tests/integration/targets/lookup_rabbitmq/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/lookup_rabbitmq/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4191eddb2b4316a026b0b6073752948393447541f7cd615799e765ab6bca6fb0", + "format": 1 + }, + { + "name": "tests/integration/targets/lookup_rabbitmq/tasks/ubuntu.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4074e6429e25b89b6c1c11de78f4e28f99c0401f8a20181d3e5ef020dbf7e4fb", + "format": 1 + }, + { + "name": "tests/integration/targets/lookup_rabbitmq/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6d2d1b24a167fc8fa4bcc147c7a73b991761c18232c99fd21208a32e1ee26f1d", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_binding", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_binding/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_binding/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6b11845fec8855951fdbf1ad471fa8174a6ba299ea4ee3d62a47a1605eaea95e", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_binding/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_binding/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c7c75c47bbd83a71d800e78edfd01d5e8d00dd71dc3490178e679e1e2f9226ff", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_binding/tasks/tests.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4ecbfe68f4cd668cbf7c05ff3c3e4075d62c279b9842518fefbd912a8e821193", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_binding/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4aae3a6bb79d44474ef4771afbd51d52222428d5cde402c6156d85e7f7c9f067", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_exchange", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_exchange/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_exchange/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5175b4209935100fc9ff5cc8370cab247318cd974df9bed2653198844b5d918d", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_exchange/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_exchange/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "68b36cb955e374e65c2597c4389c6f7203652b146169e60d215ec354a3fd42bc", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_feature_flag", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_feature_flag/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_feature_flag/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6b11845fec8855951fdbf1ad471fa8174a6ba299ea4ee3d62a47a1605eaea95e", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_feature_flag/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_feature_flag/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "989a41fcbcfa0ebf3d94089cedabdae0a78272436964bfa3d5408db7b13b4104", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_feature_flag/tasks/tests.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a4907111ff8dadeb4182e1ad75fd2f2eb05033b412cd713be14623dd9fff3181", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_feature_flag/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4aae3a6bb79d44474ef4771afbd51d52222428d5cde402c6156d85e7f7c9f067", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_global_parameter", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_global_parameter/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_global_parameter/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6b11845fec8855951fdbf1ad471fa8174a6ba299ea4ee3d62a47a1605eaea95e", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_global_parameter/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_global_parameter/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "989a41fcbcfa0ebf3d94089cedabdae0a78272436964bfa3d5408db7b13b4104", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_global_parameter/tasks/tests.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5a2fb48b2cc0259808f4988d65c853b2950ab5aed1cc3927f0c02df1c54ad2fd", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_global_parameter/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4aae3a6bb79d44474ef4771afbd51d52222428d5cde402c6156d85e7f7c9f067", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_plugin", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_plugin/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_plugin/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6b11845fec8855951fdbf1ad471fa8174a6ba299ea4ee3d62a47a1605eaea95e", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_plugin/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_plugin/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "989a41fcbcfa0ebf3d94089cedabdae0a78272436964bfa3d5408db7b13b4104", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_plugin/tasks/tests.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "875aa7430bc2f242ede00e56ef4aad6c1e424d5e8dcc71f8b4c9a6c290ef6ee3", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_plugin/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4aae3a6bb79d44474ef4771afbd51d52222428d5cde402c6156d85e7f7c9f067", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_policy", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_policy/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_policy/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6b11845fec8855951fdbf1ad471fa8174a6ba299ea4ee3d62a47a1605eaea95e", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_policy/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_policy/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "989a41fcbcfa0ebf3d94089cedabdae0a78272436964bfa3d5408db7b13b4104", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_policy/tasks/tests.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "10fd7f8f72231cdb6acfd0e03badbb2a60289b8cebee8314c2a941491f4c646f", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_policy/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4aae3a6bb79d44474ef4771afbd51d52222428d5cde402c6156d85e7f7c9f067", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_publish", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_publish/files", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_publish/files/image.gif", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d1132eda6d50ba2a89d60279ef2f891eca341bfbbca8df136c2c56d5e73c8a4c", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_publish/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_publish/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5175b4209935100fc9ff5cc8370cab247318cd974df9bed2653198844b5d918d", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_publish/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_publish/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4191eddb2b4316a026b0b6073752948393447541f7cd615799e765ab6bca6fb0", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_publish/tasks/ubuntu.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f0a53840f7b4166c770203e889f012d076e2c69998affb0410c65722fdd08ff4", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_publish/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4aae3a6bb79d44474ef4771afbd51d52222428d5cde402c6156d85e7f7c9f067", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_queue", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_queue/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_queue/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5175b4209935100fc9ff5cc8370cab247318cd974df9bed2653198844b5d918d", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_queue/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_queue/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4191eddb2b4316a026b0b6073752948393447541f7cd615799e765ab6bca6fb0", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_queue/tasks/ubuntu.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3384d94d03e4ceed81df64e4a09f61aadc1d241148623f2f4730ca718945a096", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_queue/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4aae3a6bb79d44474ef4771afbd51d52222428d5cde402c6156d85e7f7c9f067", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_upgrade", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_upgrade/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_upgrade/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6b11845fec8855951fdbf1ad471fa8174a6ba299ea4ee3d62a47a1605eaea95e", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_upgrade/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_upgrade/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "989a41fcbcfa0ebf3d94089cedabdae0a78272436964bfa3d5408db7b13b4104", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_upgrade/tasks/tests.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0724b8ee35258a48fb5a6719a32e991c67b73de1e01ba007acd70945a610e0b9", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_upgrade/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4aae3a6bb79d44474ef4771afbd51d52222428d5cde402c6156d85e7f7c9f067", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_user", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_user/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_user/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6b11845fec8855951fdbf1ad471fa8174a6ba299ea4ee3d62a47a1605eaea95e", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_user/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_user/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "27b82af4f4e91ff9565ee5a834d7b93b0322b25dec30ab06a23d07b178f515c9", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_user/tasks/tests.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6eba5be030eaa62aa211ac790514149e413e740afb80fe723ef774bfc3001875", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_user/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4aae3a6bb79d44474ef4771afbd51d52222428d5cde402c6156d85e7f7c9f067", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_user_limits", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_user_limits/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_user_limits/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6b11845fec8855951fdbf1ad471fa8174a6ba299ea4ee3d62a47a1605eaea95e", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_user_limits/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_user_limits/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4191eddb2b4316a026b0b6073752948393447541f7cd615799e765ab6bca6fb0", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_user_limits/tasks/ubuntu.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3fdb204a8eec80b41c13577166c74b89a5329c3b9425860c65ed8819960ee044", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_user_limits/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4aae3a6bb79d44474ef4771afbd51d52222428d5cde402c6156d85e7f7c9f067", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_vhost", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_vhost/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_vhost/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6b11845fec8855951fdbf1ad471fa8174a6ba299ea4ee3d62a47a1605eaea95e", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_vhost/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_vhost/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "989a41fcbcfa0ebf3d94089cedabdae0a78272436964bfa3d5408db7b13b4104", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_vhost/tasks/tests.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6cee71b73c0e8b53de9ed8efb744d83ac99016294e3c5c569ba9024be3476da2", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_vhost/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4aae3a6bb79d44474ef4771afbd51d52222428d5cde402c6156d85e7f7c9f067", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_vhost_limits", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_vhost_limits/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_vhost_limits/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6b11845fec8855951fdbf1ad471fa8174a6ba299ea4ee3d62a47a1605eaea95e", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_vhost_limits/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_vhost_limits/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4191eddb2b4316a026b0b6073752948393447541f7cd615799e765ab6bca6fb0", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_vhost_limits/tasks/ubuntu.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "99954f1917f3d6122c326ad3aa641f23a9961e60f537375b7beeefb501b393d0", + "format": 1 + }, + { + "name": "tests/integration/targets/rabbitmq_vhost_limits/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4aae3a6bb79d44474ef4771afbd51d52222428d5cde402c6156d85e7f7c9f067", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_rabbitmq", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_rabbitmq/files", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_rabbitmq/files/rabbitmq.conf", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d0d57e67b9c0b7631b7a16b7461848272eaff9855c7b9e2dd5fdf3af2fb02bc1", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_rabbitmq/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_rabbitmq/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "018110439f4fa79f060ac894fe54567fd9c3eb410aedbdf0b4aaeee1ad5fd705", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_rabbitmq/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_rabbitmq/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ab25ad3c62f9a27542e013194b8560faf072fdb8556cb0e958183f65079c31f4", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_rabbitmq/tasks/ubuntu.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c1d493236cf2275a38757b0482984cca82fe4a42ee21a139990a7e9d36248944", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_remote_tmp_dir", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_remote_tmp_dir/handlers", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_remote_tmp_dir/handlers/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "050157a29c48915cf220b3cdcf5a032e53e359bdc4a210cd457c4836e8e32a4d", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_remote_tmp_dir/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_remote_tmp_dir/tasks/default-cleanup.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e273324ab90d72180a971d99b9ab69f08689c8be2e6adb991154fc294cf1056e", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_remote_tmp_dir/tasks/default.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2441ac1753320d2cd3bea299c160540e6ae31739ed235923ca478284d1fcfe09", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_remote_tmp_dir/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "766ab141899717320ba54e2bb1a6ba8cbc3cc7642d0023670154b49981ed1a91", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_tls", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_tls/files", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_tls/files/ca_certificate.pem", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "47ddc514d030d2dd28b98eb257b690b8aa94abc7b657b43caf6e32e2e5a6bf9d", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_tls/files/ca_key.pem", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0157029faae2207eaec99b67360db8ca46fe6964eb98165a0ca4ac56cbed7ebb", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_tls/files/client_certificate.pem", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1c88ee01e59fe19f497b74f0fb15a6d705bbac6df554d16f2f80fc25d2723bad", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_tls/files/client_key.pem", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1ffc8420355a69fecd60242feb89bfef5517292aa9129ea79e99bb36ffd80dc6", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_tls/files/server_certificate.pem", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a93a860161059bf8b6d065d2b01a5218a7beefdb075fa704e0139d4f96bdb61c", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_tls/files/server_key.pem", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0bb0b33983d37d5b6404c0feb969e80d0787331f774d2b8024570133d65851f6", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_tls/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_tls/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "093e768c82c92883583078a39f31a82ae4ad264242d1f9f61bf66abbc0a27db3", + "format": 1 + }, + { + "name": "tests/sanity", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/sanity/ignore-2.10.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8b8815dcc93330716312b23095ad47d984bd7a039d20a5baa1127ce6d73ca6e8", + "format": 1 + }, + { + "name": "tests/sanity/ignore-2.11.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8b8815dcc93330716312b23095ad47d984bd7a039d20a5baa1127ce6d73ca6e8", + "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/sanity/ignore-2.9.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8b8815dcc93330716312b23095ad47d984bd7a039d20a5baa1127ce6d73ca6e8", + "format": 1 + }, + { + "name": "tests/sanity/ignore.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8b8815dcc93330716312b23095ad47d984bd7a039d20a5baa1127ce6d73ca6e8", + "format": 1 + }, + { + "name": "tests/unit", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/unit/compat", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/unit/compat/__init__.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/unit/compat/builtins.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0ca4cac919e166b25e601e11acb01f6957dddd574ff0a62569cb994a5ecb63e1", + "format": 1 + }, + { + "name": "tests/unit/compat/mock.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0af958450cf6de3fbafe94b1111eae8ba5a8dbe1d785ffbb9df81f26e4946d99", + "format": 1 + }, + { + "name": "tests/unit/compat/unittest.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5401a046e5ce71fa19b6d905abd0f9bdf816c0c635f7bdda6730b3ef06e67096", + "format": 1 + }, + { + "name": "tests/unit/mock", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/unit/mock/__init__.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/unit/mock/loader.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "cfe3480f0eae6d3723ee62d01d00a0e9f58fcdc082ea1d8e4836157c56d4fa95", + "format": 1 + }, + { + "name": "tests/unit/mock/path.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3ab46a5b17bf8fd082ea5bce19af80497a3f306893acb77cb97b022abfe175a3", + "format": 1 + }, + { + "name": "tests/unit/mock/procenv.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "abce72c74680f152cea9fc38f2ea0b24676eeeb446d64907a5a8336345013e33", + "format": 1 + }, + { + "name": "tests/unit/mock/vault_helper.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4535613601c419f7d20f0c21e638dabccf69b4a7fac99d5f6f9b81d1519dafd6", + "format": 1 + }, + { + "name": "tests/unit/mock/yaml_helper.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "fada9f3506c951e21c60c2a0e68d3cdf3cadd71c8858b2d14a55c4b778f10983", + "format": 1 + }, + { + "name": "tests/unit/modules", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/unit/modules/rabbitmq_user_fixtures.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0293b05d8c9f9be64e8ef404ff42687741e152b1deedad5ffedd617d376e54b2", + "format": 1 + }, + { + "name": "tests/unit/modules/test_rabbitmq_feature_flag.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0417a46e4a82e2e09ff7f34c411f5af8aea2b88ce2a07a44ec93a3a46ae5a831", + "format": 1 + }, + { + "name": "tests/unit/modules/test_rabbitmq_global_parameter.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "af992118a10118bea070ad74f6b0386441f7a61c8b8825baee56b0c845c87fb3", + "format": 1 + }, + { + "name": "tests/unit/modules/test_rabbitmq_upgrade.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "10d2fcf9aef8ee60dd096dbd07c5bb022b4ceb19f012cbb67e6abb4c543eac45", + "format": 1 + }, + { + "name": "tests/unit/modules/test_rabbitmq_user.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2319af2d9d24012d31307d76fd2ddfcefb943788625a5b8c692057a4b74613f1", + "format": 1 + }, + { + "name": "tests/unit/modules/utils.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a1dc0fb8eb3acde716643491e2913972b86bc1c944690331af7eaac99e93b48c", + "format": 1 + }, + { + "name": "tests/utils", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/utils/shippable", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/utils/shippable/freebsd.sh", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2a140a1cea2fbf3b291009694bcfcf0f2877e92ec01c7e929e787f5b4cdd6d92", + "format": 1 + }, + { + "name": "tests/utils/shippable/osx.sh", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2a140a1cea2fbf3b291009694bcfcf0f2877e92ec01c7e929e787f5b4cdd6d92", + "format": 1 + }, + { + "name": "tests/utils/shippable/rhel.sh", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2a140a1cea2fbf3b291009694bcfcf0f2877e92ec01c7e929e787f5b4cdd6d92", + "format": 1 + }, + { + "name": "tests/utils/shippable/check_matrix.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8043bab6fe1eac483793c2cf3dcf55df8d2f4842c98c834282af667249b16dfd", + "format": 1 + }, + { + "name": "tests/utils/shippable/cloud.sh", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "dd953f7e779b9962e76492c389142e03174e84a8115f53e56628e2af9e66b818", + "format": 1 + }, + { + "name": "tests/utils/shippable/linux.sh", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "07aa5e07a0b732a671bf9fdadfe073dd310b81857b897328ce2fa829e2c76315", + "format": 1 + }, + { + "name": "tests/utils/shippable/remote.sh", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2a140a1cea2fbf3b291009694bcfcf0f2877e92ec01c7e929e787f5b4cdd6d92", + "format": 1 + }, + { + "name": "tests/utils/shippable/sanity.sh", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a01d92ca36ea457c0e7032ece03a0b485377eef8c8598d8f7c04a185fba279ed", + "format": 1 + }, + { + "name": "tests/utils/shippable/shippable.sh", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "fbd386cce60ec181967826a2accc28b054021c67b124b056120dc37fca1c9a10", + "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/utils/shippable/units.sh", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "00169bf628f2336b63e274821cd691569feeaaecd6d29afe7b0a3fa483a44157", + "format": 1 + }, + { + "name": ".gitignore", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e2a78d88f86332d4e84527f98a0ccbf19c34e40b8378b89bc58d03f8b879922d", + "format": 1 + }, + { + "name": "CHANGELOG.rst", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0ebfd06c05555985fd1766d51b262d7d16bdcbb7e62a556533a583c7804cf664", + "format": 1 + }, + { + "name": "CONTRIBUTING.md", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b0c50cf3715d59964a341dc651a6f626322209ef9fa8c0d03047d3a2b2e420a4", + "format": 1 + }, + { + "name": "COPYING", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3972dc9744f6499f0f9b2dbf76696f2ae7ad8af9b23dde66d6af86c9dfb36986", + "format": 1 + }, + { + "name": "MAINTAINERS", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "337aee6694b1c51c01a2783897425d562d80c341ae2afbd3d3e4d537d8b8a6c8", + "format": 1 + }, + { + "name": "MAINTAINING.md", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2435665a6562d5f3841fff1631970f95f0466c498e949d2b8579ccc2a0b810ad", + "format": 1 + }, + { + "name": "PSF-license.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "83b042fc7d6aca0f10d68e45efa56b9bc0a1496608e7e7728fe09d1a534a054a", + "format": 1 + }, + { + "name": "README.md", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3cc931333ef23a15c6d255c7abc52d0bac6e147285706fa4c4d320482f1d036", + "format": 1 + } + ], + "format": 1 +}
\ No newline at end of file diff --git a/ansible_collections/community/rabbitmq/MAINTAINERS b/ansible_collections/community/rabbitmq/MAINTAINERS new file mode 100644 index 000000000..08541bf8b --- /dev/null +++ b/ansible_collections/community/rabbitmq/MAINTAINERS @@ -0,0 +1,4 @@ +cognifloyd +odyssey4me +jgkirschbaum +Im0 diff --git a/ansible_collections/community/rabbitmq/MAINTAINING.md b/ansible_collections/community/rabbitmq/MAINTAINING.md new file mode 100644 index 000000000..9fad0d343 --- /dev/null +++ b/ansible_collections/community/rabbitmq/MAINTAINING.md @@ -0,0 +1,3 @@ +# Maintaining this collection + +Refer to the [Maintainer guidelines](https://github.com/ansible/community-docs/blob/main/maintaining.rst). diff --git a/ansible_collections/community/rabbitmq/MANIFEST.json b/ansible_collections/community/rabbitmq/MANIFEST.json new file mode 100644 index 000000000..aef8fe4bf --- /dev/null +++ b/ansible_collections/community/rabbitmq/MANIFEST.json @@ -0,0 +1,31 @@ +{ + "collection_info": { + "namespace": "community", + "name": "rabbitmq", + "version": "1.2.3", + "authors": [ + "Ansible (https://github.com/ansible)", + "community.rabbitmq" + ], + "readme": "README.md", + "tags": [ + "messaging" + ], + "description": null, + "license": [], + "license_file": "COPYING", + "dependencies": {}, + "repository": "https://github.com/ansible-collections/community.rabbitmq", + "documentation": "https://docs.ansible.com/ansible/latest/collections/community/rabbitmq/", + "homepage": "https://github.com/ansible-collections/community.rabbitmq", + "issues": "https://github.com/ansible-collections/community.rabbitmq/issues" + }, + "file_manifest_file": { + "name": "FILES.json", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5ccdbf6355d35529c8944cfa5f572e21e987b5050135b9262c754cdcbc1c25d7", + "format": 1 + }, + "format": 1 +}
\ No newline at end of file diff --git a/ansible_collections/community/rabbitmq/PSF-license.txt b/ansible_collections/community/rabbitmq/PSF-license.txt new file mode 100644 index 000000000..35acd7fb5 --- /dev/null +++ b/ansible_collections/community/rabbitmq/PSF-license.txt @@ -0,0 +1,48 @@ +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +-------------------------------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation +("PSF"), and the Individual or Organization ("Licensee") accessing and +otherwise using this software ("Python") in source or binary form and +its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF hereby +grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, +analyze, test, perform and/or display publicly, prepare derivative works, +distribute, and otherwise use Python alone or in any derivative version, +provided, however, that PSF's License Agreement and PSF's notice of copyright, +i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Python Software Foundation; +All Rights Reserved" are retained in Python alone or in any derivative version +prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python. + +4. PSF is making Python available to Licensee on an "AS IS" +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between PSF and +Licensee. This License Agreement does not grant permission to use PSF +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using Python, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. diff --git a/ansible_collections/community/rabbitmq/README.md b/ansible_collections/community/rabbitmq/README.md new file mode 100644 index 000000000..5090e0fc7 --- /dev/null +++ b/ansible_collections/community/rabbitmq/README.md @@ -0,0 +1,212 @@ +# community.rabbitmq Collection +[![Build Status]( +https://dev.azure.com/ansible/community.rabbitmq/_apis/build/status/CI?branchName=main)](https://dev.azure.com/ansible/community.rabbitmq/_build?definitionId=29) + +<!-- Add CI and code coverage badges here. --> +<!-- Describe the collection and why a user would want to use it. What does the collection do? --> + +This repository hosts the `community.rabbitmq` Ansible Collection. + +The collection includes the rabbitmq modules and plugins supported by Ansible +rabbitmq community to help the management of rabbitmq infrastructure. + +This collection is a part of the Ansible package. + +## Code of Conduct + +We follow the [Ansible Code of Conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html) in all our interactions within this project. + +If you encounter abusive behavior violating the [Ansible Code of Conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html), please refer to the [policy violations](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html#policy-violations) section of the Code of Conduct for information on how to raise a complaint. + +## Included content +<!-- Galaxy will eventually list the module docs within the UI, but until that is ready, you may need to either describe your plugins etc here, or point to an external docsite to cover that information. --> +- Modules: + - `rabbitmq_binding`: Manage rabbitMQ bindings. + - `rabbitmq_exchange`: Manage rabbitMQ exchanges. + - `rabbitmq_feature_flag`: Enables feature flag. + - `rabbitmq_global_parameter`: Manage RabbitMQ global parameters. + - `rabbitmq_parameter`: Manage RabbitMQ parameters. + - `rabbitmq_plugin`: Manage RabbitMQ plugins. + - `rabbitmq_policy`: Manage the state of policies in RabbitMQ. + - `rabbitmq_publish`: Publish a message to a RabbitMQ queue. + - `rabbitmq_queue`: Manage rabbitMQ queues. + - `rabbitmq_upgrade`: Execute rabbitmq-upgrade commands. + - `rabbitmq_user_limits`: Manage RabbitMQ user limits. + - `rabbitmq_user`: Manage RabbitMQ users. + - `rabbitmq_vhost_limits`: Manage the state of virtual host limits in RabbitMQ. + - `rabbitmq_vhost`: Manage the state of a virtual host in RabbitMQ. + +- Lookup: + - `rabbitmq`: Retrieve messages from an AMQP/AMQPS RabbitMQ queue. + +## Using this collection +<!--Include some quick examples that cover the most common use cases for your collection content. --> + +### Installing the Collection from Ansible Galaxy + +Before using the collection, you need to install it with the Ansible Galaxy command-line tool: + +```bash +ansible-galaxy collection install community.rabbitmq +``` + +You can include it in a `requirements.yml` file and install it via `ansible-galaxy collection install -r requirements.yml`, using the format: + +```yaml +--- +collections: + - name: community.rabbitmq +``` + +You can also download the tarball from [Ansible Galaxy](https://galaxy.ansible.com/community/rabbitmq) and install the collection manually wherever you need. + +Note that if you install the collection from Ansible Galaxy with the command-line tool or tarball, it will not be upgraded automatically when you upgrade the Ansible package. To upgrade the collection to the latest available version, run the following command: + +```bash +ansible-galaxy collection install community.rabbitmq --upgrade +``` + +You can also install a specific version of the collection, for example, if you need to downgrade when something is broken in the latest version (please report an issue in this repository). Use the following syntax: + +```bash +ansible-galaxy collection install community.rabbitmq:==X.Y.Z +``` + +See [Ansible Using collections](https://docs.ansible.com/ansible/latest/user_guide/collections_using.html) for more details. + +## Contributing to this collection +<!--Describe how the community can contribute to your collection. At a minimum, include how and where users can create issues to report problems or request features for this collection. List contribution requirements, including preferred workflows and necessary testing, so you can benefit from community PRs. --> + +The content of this collection is made by people just like you, a community of individuals collaborating on making the world better through developing automation software. + +We are actively accepting new contributors. + +All types of contributions are very welcome. + +You don't know how to start? Refer to our [contribution guide](https://github.com/ansible-collections/community.rabbitmq/blob/main/CONTRIBUTING.md)! + +The aspiration is to follow the following general guidelines: + +- Changes should include tests and documentation where appropriate. +- Changes will be lint tested using standard python lint tests. +- No changes which do not pass CI testing will be approved/merged. +- The collection plugins must provide the same coverage of python support as + the versions of Ansible supported. +- The versions of Ansible supported by the collection must be the same as + those in developed, or those maintained, as shown in the Ansible [Release and Maintenance][4] documentation. + +[4]: https://docs.ansible.com/ansible/latest/reference_appendices/release_and_maintenance.html + +We use the following guidelines: + +* [CONTRIBUTING.md](https://github.com/ansible-collections/community.rabbitmq/blob/main/CONTRIBUTING.md) +* [Ansible Community Guide](https://docs.ansible.com/ansible/latest/community/index.html) +* [Ansible Development Guide](https://docs.ansible.com/ansible/devel/dev_guide/index.html) +* [Ansible Collection Development Guide](https://docs.ansible.com/ansible/devel/dev_guide/developing_collections.html#contributing-to-collections) + +### Local Testing + +* Requirements + * [Python 3.5+](https://www.python.org/) + * [pip](https://pypi.org/project/pip/) + * [virtualenv](https://virtualenv.pypa.io/en/latest/) or [pipenv](https://pypi.org/project/pipenv/) if you prefer. + * [git](https://git-scm.com/) + * [docker](https://www.docker.com/) + +* Useful Links + * [Pip & Virtual Environments](https://docs.python-guide.org/dev/virtualenvs/) + * [Ansible Integration Tests](https://docs.ansible.com/ansible/latest/dev_guide/testing_integration.html) + +Local testing is done with the ``ansible-test`` tool which requires a specific +directory hierarchy to function correctly so please follow carefully. + +```bash +# These base directory environment variables can be adjusted to suit personal preferences +SRC_BASE_DIR="~/code" +VENV_BASE_DIR="~/.venvs" + +# These should not be altered +COLL_DIR="${SRC_BASE_DIR}/ansible/ansible_collections/community/rabbitmq" +VENV_DIR="${VENV_BASE_DIR}/ansible" + +# Create the required directory structure +mkdir -p $(basename ${COLL_DIR}) + +# Clone the collection repository +git clone https://github.com/ansible-collections/community.rabbitmq.git ${COLL_DIR} + +# Create and activate a virtual environment. +virtualenv ${VENV_DIR} +source ${VENV_DIR}/bin/activate + +# Install the devel branch of ansible-base +pip install https://github.com/ansible/ansible/archive/devel.tar.gz --disable-pip-version-check + +# Switch into the collection directory +cd ${COLL_DIR} + +# Run the integration tests +ansible-test integration --docker default -v --color --python 3.6 + +# Run the unit tests +ansible-test units --docker default -v --color --python 3.6 +``` + +## Collection maintenance + +The current maintainers (contributors with `write` or higher access) are listed in the [MAINTAINERS](https://github.com/ansible-collections/community.rabbitmq/blob/main/MAINTAINERS) file. If you have questions or need help, feel free to mention them in the proposals. + +To learn how to maintain / become a maintainer of this collection, refer to the [Maintainer guidelines](https://github.com/ansible-collections/community.rabbitmq/blob/main/MAINTAINING.md). + +It is necessary for maintainers of this collection to be subscribed to: + +* The collection itself (the `Watch` button -> `All Activity` in the upper right corner of the repository's homepage). +* The "Changes Impacting Collection Contributors and Maintainers" [issue](https://github.com/ansible-collections/overview/issues/45). + +They also should be subscribed to Ansible's [The Bullhorn newsletter](https://docs.ansible.com/ansible/devel/community/communication.html#the-bullhorn). + +### Publishing New Version + +See the [Releasing guidelines](https://github.com/ansible/community-docs/blob/main/releasing_collections_without_release_branches.rst). + +## Tested with Ansible +<!-- List the versions of Ansible the collection has been tested with. Must match what is in galaxy.yml. --> +TBD + +## External requirements +<!-- List any external resources the collection depends on, for example minimum versions of an OS, libraries, or utilities. Do not list other Ansible collections here. --> +TBD + +### Supported connections +<!-- Optional. If your collection supports only specific connection types (such as HTTPAPI, netconf, or others), list them here. --> +TBD + +## More Information +<!-- List out where the user can find additional information, such as working group meeting times, slack/IRC channels, or documentation for the product this collection automates. --> + +### Communication + +To communicate, we use: + +- The `#ansible-community` [Libera.Chat](https://libera.chat/) IRC channel. +- [Issues](https://github.com/ansible-collections/rabbitmq/issues) in this repository. + +We announce important development changes and releases through Ansible's [The Bullhorn newsletter](https://docs.ansible.com/ansible/devel/community/communication.html#the-bullhorn). If you are a collection developer, be sure you are subscribed. + +We take part in the global quarterly [Ansible Contributor Summit](https://github.com/ansible/community/wiki/Contributor-Summit) virtually or in-person. Track [The Bullhorn newsletter](https://docs.ansible.com/ansible/devel/community/communication.html#the-bullhorn) and join us. + +For more information about communication, refer to the [Ansible Communication guide](https://docs.ansible.com/ansible/devel/community/communication.html). + +### Reference + +- [Ansible Collection overview](https://github.com/ansible-collections/overview) +- [Ansible User guide](https://docs.ansible.com/ansible/latest/user_guide/index.html) +- [Ansible Developer guide](https://docs.ansible.com/ansible/latest/dev_guide/index.html) +- [Ansible Community code of conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html) + +## License +<!-- Include the appropriate license information here and a pointer to the full licensing details. If the collection contains modules migrated from the ansible/ansible repo, you must use the same license that existed in the ansible/ansible repo. See the GNU license example below. --> + +GNU General Public License v3.0 or later. + +See [LICENCE](https://www.gnu.org/licenses/gpl-3.0.txt) to see the full text. diff --git a/ansible_collections/community/rabbitmq/changelogs/changelog.yaml b/ansible_collections/community/rabbitmq/changelogs/changelog.yaml new file mode 100644 index 000000000..ee3c424ba --- /dev/null +++ b/ansible_collections/community/rabbitmq/changelogs/changelog.yaml @@ -0,0 +1,120 @@ +ancestor: null +releases: + 1.0.0: + changes: + bugfixes: + - Refactor RabbitMQ user module to first check the version of the daemon and + then, when possible add flags to `rabbitmqctl` so that a machine readable output + is returned. Also, depending on the version, parse the output in correctly. + Expands tests accordingly. (https://github.com/ansible/ansible/issues/48890) + - rabbitmq lookup plugin - Fix for rabbitmq lookups failing when using pika + v1.0.0 and newer. + - rabbitmq_publish - Fix to ensure the module works correctly for pika v1.0.0 + and later. (https://github.com/ansible/ansible/pull/61960) + minor_changes: + - rabbitmq_publish - Support for connecting with SSL certificates. + fragments: + - 55919-rabbitmq_publish-fix-for-recent-pika-versions.yml + - 66876-parse_post_rabbitmq_3.7_output_as_json.yaml + - lookup_rabbitmq-is_closing-bug.yml + - rabbitmq_publish-certificate-checks.yml + release_date: '2020-08-18' + 1.1.0: + changes: + bugfixes: + - rabbitmq_policy - The ``_policy_check`` piece of the policy module (``policy_data``) + is typically list based on a split of the variable ``policy``. However ``policy`` + in some cases does not contain data. The fix allows ``tags`` to attempt to + load as json first but in the case of failure, assign ``tags`` without using + the json loader (https://github.com/ansible-collections/community.rabbitmq/pull/28). + release_summary: 'This is the minor release of the ``community.rabbitmq`` collection. + + This changelog contains all changes to the modules and plugins in this collection + + that have been made after release 1.0.3.' + fragments: + - 1.1.0.yml + - 86-fix-tags-json-in-rmq-policy.yml + modules: + - description: Enables feature flag + name: rabbitmq_feature_flag + namespace: '' + - description: Execute rabbitmq-upgrade commands + name: rabbitmq_upgrade + namespace: '' + - description: Manage RabbitMQ user limits + name: rabbitmq_user_limits + namespace: '' + release_date: '2021-07-29' + 1.2.0: + changes: + bugfixes: + - Collection core functions - use vendored version of ``distutils.version`` + instead of the deprecated Python standard library ``distutils``. + minor_changes: + - rabbitmq_user - add support for `topic authorization <https://www.rabbitmq.com/access-control.html#topic-authorisation>`_ + (featured in RabbitMQ 3.7.0) (https://github.com/ansible-collections/community.rabbitmq/pull/73). + release_summary: 'This is the minor release of the ``community.rabbitmq`` collection. + + This changelog contains all changes to the modules and plugins in this collection + + that have been made after the 1.1.0 release.' + fragments: + - 0-copy_ignore_txt.yml + - 1.2.0.yml + - 115-use_vendored_looseversion.yml + - 73-topic-authorization.yml + release_date: '2022-05-12' + 1.2.1: + changes: + bugfixes: + - Include ``PSF-license.txt`` file for ``plugins/module_utils/_version.py``. + release_summary: 'This is the minor release of the ``community.rabbitmq`` collection. + + This changelog contains all changes to the modules and plugins in this collection + + that have been made after the 1.2.0 release.' + fragments: + - 1.2.1.yml + - psf-license.yml + release_date: '2022-05-16' + 1.2.2: + changes: + bugfixes: + - user module - set supports_check_mode flag to False, as the module does not + actually support check mode. + release_summary: 'This is the minor release of the ``community.rabbitmq`` collection. + + This changelog contains all changes to the modules and plugins in this collection + + that have been made after the 1.2.1 release.' + fragments: + - 1.2.2.yml + - 107-user_disable_check_support.yml + release_date: '2022-07-13' + 1.2.3: + changes: + bugfixes: + - rabbitmq_queue - fixing an issue where a special character in the queue name + would result in an API error (https://github.com/ansible-collections/community.rabbitmq/issues/114). + minor_changes: + - rabbitmq_exchange - adding ability to specify exchange types that are enabled + via plugins. I(x-random), I(x-consistent-hash) and I(x-recent-history) + (https://github.com/ansible-collections/community.rabbitmq/pull/142). + - rabbitmq_publish - fixing issue with publishing to exchanges and adding exchange + documentation examples. Publishing to an exchange or queue is now mutually + exclusive (https://github.com/ansible-collections/community.rabbitmq/pull/140). + - Various CI fixes (https://github.com/ansible-collections/community.rabbitmq/pull/139 & + https://github.com/ansible-collections/community.rabbitmq/pull/141). + release_summary: 'This is the minor release of the ``community.rabbitmq`` collection. + + This changelog contains all changes to the modules and plugins in this collection + + that have been made after the 1.2.1 release.' + fragments: + - 1.2.3.yml + - 114-queue-name-escape.yml + - 139_ci_add_stable_214.yml + - 140-fixing-publishing-to-exchanges.yaml + - 142-new-plugin-exchanges.yml + release_date: '2022-11-04' diff --git a/ansible_collections/community/rabbitmq/changelogs/config.yaml b/ansible_collections/community/rabbitmq/changelogs/config.yaml new file mode 100644 index 000000000..e47ffd370 --- /dev/null +++ b/ansible_collections/community/rabbitmq/changelogs/config.yaml @@ -0,0 +1,31 @@ +changelog_filename_template: ../CHANGELOG.rst +changelog_filename_version_depth: 0 +changes_file: changelog.yaml +changes_format: combined +ignore_other_fragment_extensions: true +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 +title: Community.Rabbitmq +trivial_section_name: trivial +use_fqcn: true diff --git a/ansible_collections/community/rabbitmq/meta/runtime.yml b/ansible_collections/community/rabbitmq/meta/runtime.yml new file mode 100644 index 000000000..2ee3c9fa9 --- /dev/null +++ b/ansible_collections/community/rabbitmq/meta/runtime.yml @@ -0,0 +1,2 @@ +--- +requires_ansible: '>=2.9.10' diff --git a/ansible_collections/community/rabbitmq/plugins/doc_fragments/__init__.py b/ansible_collections/community/rabbitmq/plugins/doc_fragments/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/community/rabbitmq/plugins/doc_fragments/__init__.py diff --git a/ansible_collections/community/rabbitmq/plugins/doc_fragments/rabbitmq.py b/ansible_collections/community/rabbitmq/plugins/doc_fragments/rabbitmq.py new file mode 100644 index 000000000..acae82e87 --- /dev/null +++ b/ansible_collections/community/rabbitmq/plugins/doc_fragments/rabbitmq.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- + +# Copyright: (c) 2016, Jorge Rodriguez <jorge.rodriguez@tiriel.eu> +# 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): + # Parameters for RabbitMQ modules + DOCUMENTATION = r''' +options: + login_user: + description: + - RabbitMQ user for connection. + type: str + default: guest + login_password: + description: + - RabbitMQ password for connection. + type: str + default: guest + login_host: + description: + - RabbitMQ host for connection. + type: str + default: localhost + login_port: + description: + - RabbitMQ management API port. + type: str + default: '15672' + login_protocol: + description: + - RabbitMQ management API protocol. + type: str + choices: [ http , https ] + default: http + ca_cert: + description: + - CA certificate to verify SSL connection to management API. + type: path + aliases: [ cacert ] + client_cert: + description: + - Client certificate to send on SSL connections to management API. + type: path + aliases: [ cert ] + client_key: + description: + - Private key matching the client certificate. + type: path + aliases: [ key ] + vhost: + description: + - RabbitMQ virtual host. + type: str + default: "/" +''' diff --git a/ansible_collections/community/rabbitmq/plugins/lookup/rabbitmq.py b/ansible_collections/community/rabbitmq/plugins/lookup/rabbitmq.py new file mode 100644 index 000000000..9ecd97e5f --- /dev/null +++ b/ansible_collections/community/rabbitmq/plugins/lookup/rabbitmq.py @@ -0,0 +1,189 @@ +# (c) 2018, John Imison <john+github@imison.net> +# 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 = ''' + name: rabbitmq + author: John Imison (@Im0) + short_description: Retrieve messages from an AMQP/AMQPS RabbitMQ queue. + description: + - This lookup uses a basic get to retrieve all, or a limited number C(count), messages from a RabbitMQ queue. + options: + url: + description: + - An URI connection string to connect to the AMQP/AMQPS RabbitMQ server. + - For more information refer to the URI spec U(https://www.rabbitmq.com/uri-spec.html). + required: True + queue: + description: + - The queue to get messages from. + required: True + count: + description: + - How many messages to collect from the queue. + - If not set, defaults to retrieving all the messages from the queue. + requirements: + - The python pika package U(https://pypi.org/project/pika/). + notes: + - This lookup implements BlockingChannel.basic_get to get messages from a RabbitMQ server. + - After retrieving a message from the server, receipt of the message is acknowledged and the message on the server is deleted. + - Pika is a pure-Python implementation of the AMQP 0-9-1 protocol that tries to stay fairly independent of the underlying network support library. + - More information about pika can be found at U(https://pika.readthedocs.io/en/stable/). + - This plugin is tested against RabbitMQ. Other AMQP 0.9.1 protocol based servers may work but not tested/guaranteed. + - Assigning the return messages to a variable under C(vars) may result in unexpected results as the lookup is evaluated every time the + variable is referenced. + - Currently this plugin only handles text based messages from a queue. Unexpected results may occur when retrieving binary data. +''' + + +EXAMPLES = """ +- name: Get all messages off a queue + debug: + msg: "{{ lookup('community.rabbitmq.rabbitmq', url='amqp://guest:guest@192.168.0.10:5672/%2F', queue='hello') }}" + + +# If you are intending on using the returned messages as a variable in more than +# one task (eg. debug, template), it is recommended to set_fact. + +- name: Get 2 messages off a queue and set a fact for re-use + set_fact: + messages: "{{ lookup('community.rabbitmq.rabbiotmq', url='amqp://guest:guest@192.168.0.10:5672/%2F', queue='hello', count=2) }}" + +- name: Dump out contents of the messages + debug: + var: messages + +""" + +RETURN = """ + _list: + description: + - A list of dictionaries with keys and value from the queue. + type: list + contains: + content_type: + description: The content_type on the message in the queue. + type: str + delivery_mode: + description: The delivery_mode on the message in the queue. + type: str + delivery_tag: + description: The delivery_tag on the message in the queue. + type: str + exchange: + description: The exchange the message came from. + type: str + message_count: + description: The message_count for the message on the queue. + type: str + msg: + description: The content of the message. + type: str + redelivered: + description: The redelivered flag. True if the message has been delivered before. + type: bool + routing_key: + description: The routing_key on the message in the queue. + type: str + headers: + description: The headers for the message returned from the queue. + type: dict + json: + description: If application/json is specified in content_type, json will be loaded into variables. + type: dict + +""" + +import json + +from ansible.errors import AnsibleError, AnsibleParserError +from ansible.plugins.lookup import LookupBase +from ansible.module_utils._text import to_native, to_text +from ansible.utils.display import Display + +try: + import pika + from pika import spec + HAS_PIKA = True +except ImportError: + HAS_PIKA = False + +display = Display() + + +class LookupModule(LookupBase): + + def run(self, terms, variables=None, url=None, queue=None, count=None): + if not HAS_PIKA: + raise AnsibleError('pika python package is required for rabbitmq lookup.') + if not url: + raise AnsibleError('URL is required for rabbitmq lookup.') + if not queue: + raise AnsibleError('Queue is required for rabbitmq lookup.') + + display.vvv(u"terms:%s : variables:%s url:%s queue:%s count:%s" % (terms, variables, url, queue, count)) + + try: + parameters = pika.URLParameters(url) + except Exception as e: + raise AnsibleError("URL malformed: %s" % to_native(e)) + + try: + connection = pika.BlockingConnection(parameters) + except Exception as e: + raise AnsibleError("Connection issue: %s" % to_native(e)) + + try: + conn_channel = connection.channel() + except pika.exceptions.AMQPChannelError as e: + try: + connection.close() + except pika.exceptions.AMQPConnectionError as ie: + raise AnsibleError("Channel and connection closing issues: %s / %s" % to_native(e), to_native(ie)) + raise AnsibleError("Channel issue: %s" % to_native(e)) + + ret = [] + idx = 0 + + while True: + method_frame, properties, body = conn_channel.basic_get(queue=queue) + if method_frame: + display.vvv(u"%s, %s, %s " % (method_frame, properties, to_text(body))) + + # TODO: In the future consider checking content_type and handle text/binary data differently. + msg_details = dict({ + 'msg': to_text(body), + 'message_count': method_frame.message_count, + 'routing_key': method_frame.routing_key, + 'delivery_tag': method_frame.delivery_tag, + 'redelivered': method_frame.redelivered, + 'exchange': method_frame.exchange, + 'delivery_mode': properties.delivery_mode, + 'content_type': properties.content_type, + 'headers': properties.headers + }) + if properties.content_type == 'application/json': + try: + msg_details['json'] = json.loads(msg_details['msg']) + except ValueError as e: + raise AnsibleError("Unable to decode JSON for message %s: %s" % (method_frame.delivery_tag, to_native(e))) + + ret.append(msg_details) + conn_channel.basic_ack(method_frame.delivery_tag) + idx += 1 + if method_frame.message_count == 0 or idx == count: + break + # If we didn't get a method_frame, exit. + else: + break + + if connection.is_closed: + return [ret] + else: + try: + connection.close() + except pika.exceptions.AMQPConnectionError: + pass + return [ret] diff --git a/ansible_collections/community/rabbitmq/plugins/module_utils/_version.py b/ansible_collections/community/rabbitmq/plugins/module_utils/_version.py new file mode 100644 index 000000000..bf994226a --- /dev/null +++ b/ansible_collections/community/rabbitmq/plugins/module_utils/_version.py @@ -0,0 +1,343 @@ +# Vendored copy of distutils/version.py from CPython 3.9.5 +# +# Implements multiple version numbering conventions for the +# Python Module Distribution Utilities. +# +# PSF License (see PSF-license.txt or https://opensource.org/licenses/Python-2.0) +# + +"""Provides classes to represent module version numbers (one class for +each style of version numbering). There are currently two such classes +implemented: StrictVersion and LooseVersion. + +Every version number class implements the following interface: + * the 'parse' method takes a string and parses it to some internal + representation; if the string is an invalid version number, + 'parse' raises a ValueError exception + * the class constructor takes an optional string argument which, + if supplied, is passed to 'parse' + * __str__ reconstructs the string that was passed to 'parse' (or + an equivalent string -- ie. one that will generate an equivalent + version number instance) + * __repr__ generates Python code to recreate the version number instance + * _cmp compares the current instance with either another instance + of the same class or a string (which will be parsed to an instance + of the same class, thus must follow the same rules) +""" + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import re + +try: + RE_FLAGS = re.VERBOSE | re.ASCII # type: ignore[attr-defined] +except AttributeError: + RE_FLAGS = re.VERBOSE + + +class Version: + """Abstract base class for version numbering classes. Just provides + constructor (__init__) and reproducer (__repr__), because those + seem to be the same for all version numbering classes; and route + rich comparisons to _cmp. + """ + + def __init__(self, vstring=None): + if vstring: + self.parse(vstring) + + def __repr__(self): + return "%s ('%s')" % (self.__class__.__name__, str(self)) + + def __eq__(self, other): + c = self._cmp(other) + if c is NotImplemented: + return c + return c == 0 + + def __lt__(self, other): + c = self._cmp(other) + if c is NotImplemented: + return c + return c < 0 + + def __le__(self, other): + c = self._cmp(other) + if c is NotImplemented: + return c + return c <= 0 + + def __gt__(self, other): + c = self._cmp(other) + if c is NotImplemented: + return c + return c > 0 + + def __ge__(self, other): + c = self._cmp(other) + if c is NotImplemented: + return c + return c >= 0 + + +# Interface for version-number classes -- must be implemented +# by the following classes (the concrete ones -- Version should +# be treated as an abstract class). +# __init__ (string) - create and take same action as 'parse' +# (string parameter is optional) +# parse (string) - convert a string representation to whatever +# internal representation is appropriate for +# this style of version numbering +# __str__ (self) - convert back to a string; should be very similar +# (if not identical to) the string supplied to parse +# __repr__ (self) - generate Python code to recreate +# the instance +# _cmp (self, other) - compare two version numbers ('other' may +# be an unparsed version string, or another +# instance of your version class) + + +class StrictVersion(Version): + """Version numbering for anal retentives and software idealists. + Implements the standard interface for version number classes as + described above. A version number consists of two or three + dot-separated numeric components, with an optional "pre-release" tag + on the end. The pre-release tag consists of the letter 'a' or 'b' + followed by a number. If the numeric components of two version + numbers are equal, then one with a pre-release tag will always + be deemed earlier (lesser) than one without. + + The following are valid version numbers (shown in the order that + would be obtained by sorting according to the supplied cmp function): + + 0.4 0.4.0 (these two are equivalent) + 0.4.1 + 0.5a1 + 0.5b3 + 0.5 + 0.9.6 + 1.0 + 1.0.4a3 + 1.0.4b1 + 1.0.4 + + The following are examples of invalid version numbers: + + 1 + 2.7.2.2 + 1.3.a4 + 1.3pl1 + 1.3c4 + + The rationale for this version numbering system will be explained + in the distutils documentation. + """ + + version_re = re.compile(r'^(\d+) \. (\d+) (\. (\d+))? ([ab](\d+))?$', + RE_FLAGS) + + def parse(self, vstring): + match = self.version_re.match(vstring) + if not match: + raise ValueError("invalid version number '%s'" % vstring) + + (major, minor, patch, prerelease, prerelease_num) = \ + match.group(1, 2, 4, 5, 6) + + if patch: + self.version = tuple(map(int, [major, minor, patch])) + else: + self.version = tuple(map(int, [major, minor])) + (0,) + + if prerelease: + self.prerelease = (prerelease[0], int(prerelease_num)) + else: + self.prerelease = None + + def __str__(self): + if self.version[2] == 0: + vstring = '.'.join(map(str, self.version[0:2])) + else: + vstring = '.'.join(map(str, self.version)) + + if self.prerelease: + vstring = vstring + self.prerelease[0] + str(self.prerelease[1]) + + return vstring + + def _cmp(self, other): + if isinstance(other, str): + other = StrictVersion(other) + elif not isinstance(other, StrictVersion): + return NotImplemented + + if self.version != other.version: + # numeric versions don't match + # prerelease stuff doesn't matter + if self.version < other.version: + return -1 + else: + return 1 + + # have to compare prerelease + # case 1: neither has prerelease; they're equal + # case 2: self has prerelease, other doesn't; other is greater + # case 3: self doesn't have prerelease, other does: self is greater + # case 4: both have prerelease: must compare them! + + if (not self.prerelease and not other.prerelease): + return 0 + elif (self.prerelease and not other.prerelease): + return -1 + elif (not self.prerelease and other.prerelease): + return 1 + elif (self.prerelease and other.prerelease): + if self.prerelease == other.prerelease: + return 0 + elif self.prerelease < other.prerelease: + return -1 + else: + return 1 + else: + raise AssertionError("never get here") + +# end class StrictVersion + +# The rules according to Greg Stein: +# 1) a version number has 1 or more numbers separated by a period or by +# sequences of letters. If only periods, then these are compared +# left-to-right to determine an ordering. +# 2) sequences of letters are part of the tuple for comparison and are +# compared lexicographically +# 3) recognize the numeric components may have leading zeroes +# +# The LooseVersion class below implements these rules: a version number +# string is split up into a tuple of integer and string components, and +# comparison is a simple tuple comparison. This means that version +# numbers behave in a predictable and obvious way, but a way that might +# not necessarily be how people *want* version numbers to behave. There +# wouldn't be a problem if people could stick to purely numeric version +# numbers: just split on period and compare the numbers as tuples. +# However, people insist on putting letters into their version numbers; +# the most common purpose seems to be: +# - indicating a "pre-release" version +# ('alpha', 'beta', 'a', 'b', 'pre', 'p') +# - indicating a post-release patch ('p', 'pl', 'patch') +# but of course this can't cover all version number schemes, and there's +# no way to know what a programmer means without asking them. +# +# The problem is what to do with letters (and other non-numeric +# characters) in a version number. The current implementation does the +# obvious and predictable thing: keep them as strings and compare +# lexically within a tuple comparison. This has the desired effect if +# an appended letter sequence implies something "post-release": +# eg. "0.99" < "0.99pl14" < "1.0", and "5.001" < "5.001m" < "5.002". +# +# However, if letters in a version number imply a pre-release version, +# the "obvious" thing isn't correct. Eg. you would expect that +# "1.5.1" < "1.5.2a2" < "1.5.2", but under the tuple/lexical comparison +# implemented here, this just isn't so. +# +# Two possible solutions come to mind. The first is to tie the +# comparison algorithm to a particular set of semantic rules, as has +# been done in the StrictVersion class above. This works great as long +# as everyone can go along with bondage and discipline. Hopefully a +# (large) subset of Python module programmers will agree that the +# particular flavour of bondage and discipline provided by StrictVersion +# provides enough benefit to be worth using, and will submit their +# version numbering scheme to its domination. The free-thinking +# anarchists in the lot will never give in, though, and something needs +# to be done to accommodate them. +# +# Perhaps a "moderately strict" version class could be implemented that +# lets almost anything slide (syntactically), and makes some heuristic +# assumptions about non-digits in version number strings. This could +# sink into special-case-hell, though; if I was as talented and +# idiosyncratic as Larry Wall, I'd go ahead and implement a class that +# somehow knows that "1.2.1" < "1.2.2a2" < "1.2.2" < "1.2.2pl3", and is +# just as happy dealing with things like "2g6" and "1.13++". I don't +# think I'm smart enough to do it right though. +# +# In any case, I've coded the test suite for this module (see +# ../test/test_version.py) specifically to fail on things like comparing +# "1.2a2" and "1.2". That's not because the *code* is doing anything +# wrong, it's because the simple, obvious design doesn't match my +# complicated, hairy expectations for real-world version numbers. It +# would be a snap to fix the test suite to say, "Yep, LooseVersion does +# the Right Thing" (ie. the code matches the conception). But I'd rather +# have a conception that matches common notions about version numbers. + + +class LooseVersion(Version): + """Version numbering for anarchists and software realists. + Implements the standard interface for version number classes as + described above. A version number consists of a series of numbers, + separated by either periods or strings of letters. When comparing + version numbers, the numeric components will be compared + numerically, and the alphabetic components lexically. The following + are all valid version numbers, in no particular order: + + 1.5.1 + 1.5.2b2 + 161 + 3.10a + 8.02 + 3.4j + 1996.07.12 + 3.2.pl0 + 3.1.1.6 + 2g6 + 11g + 0.960923 + 2.2beta29 + 1.13++ + 5.5.kw + 2.0b1pl0 + + In fact, there is no such thing as an invalid version number under + this scheme; the rules for comparison are simple and predictable, + but may not always give the results you want (for some definition + of "want"). + """ + + component_re = re.compile(r'(\d+ | [a-z]+ | \.)', re.VERBOSE) + + def __init__(self, vstring=None): + if vstring: + self.parse(vstring) + + def parse(self, vstring): + # I've given up on thinking I can reconstruct the version string + # from the parsed tuple -- so I just store the string here for + # use by __str__ + self.vstring = vstring + components = [x for x in self.component_re.split(vstring) if x and x != '.'] + for i, obj in enumerate(components): + try: + components[i] = int(obj) + except ValueError: + pass + + self.version = components + + def __str__(self): + return self.vstring + + def __repr__(self): + return "LooseVersion ('%s')" % str(self) + + def _cmp(self, other): + if isinstance(other, str): + other = LooseVersion(other) + elif not isinstance(other, LooseVersion): + return NotImplemented + + if self.version == other.version: + return 0 + if self.version < other.version: + return -1 + if self.version > other.version: + return 1 + +# end class LooseVersion diff --git a/ansible_collections/community/rabbitmq/plugins/module_utils/rabbitmq.py b/ansible_collections/community/rabbitmq/plugins/module_utils/rabbitmq.py new file mode 100644 index 000000000..f981c8539 --- /dev/null +++ b/ansible_collections/community/rabbitmq/plugins/module_utils/rabbitmq.py @@ -0,0 +1,230 @@ +# -*- coding: utf-8 -*- +# +# Copyright: (c) 2016, Jorge Rodriguez <jorge.rodriguez@tiriel.eu> +# Copyright: (c) 2018, John Imison <john+github@imison.net> +# +# 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 + +from ansible.module_utils._text import to_native +from ansible.module_utils.basic import missing_required_lib +from ansible.module_utils.six.moves.urllib import parse as urllib_parse +from mimetypes import MimeTypes + +import os +import json +import traceback + +PIKA_IMP_ERR = None +try: + import pika + import pika.exceptions + from pika import spec + HAS_PIKA = True +except ImportError: + PIKA_IMP_ERR = traceback.format_exc() + HAS_PIKA = False + + +def rabbitmq_argument_spec(): + return dict( + login_user=dict(type='str', default='guest'), + login_password=dict(type='str', default='guest', no_log=True), + login_host=dict(type='str', default='localhost'), + login_port=dict(type='str', default='15672'), + login_protocol=dict(type='str', default='http', choices=['http', 'https']), + ca_cert=dict(type='path', aliases=['cacert']), + client_cert=dict(type='path', aliases=['cert']), + client_key=dict(type='path', aliases=['key']), + vhost=dict(type='str', default='/'), + ) + + +# notification/rabbitmq_basic_publish.py +class RabbitClient(): + def __init__(self, module): + self.module = module + self.params = module.params + self.check_required_library() + self.check_host_params() + self.url = self.params['url'] + self.proto = self.params['proto'] + self.username = self.params['username'] + self.password = self.params['password'] + self.host = self.params['host'] + self.port = self.params['port'] + self.vhost = self.params['vhost'] + self.queue = self.params['queue'] + self.exchange = self.params['exchange'] + self.routing_key = self.params['routing_key'] + self.headers = self.params['headers'] + self.cafile = self.params['cafile'] + self.certfile = self.params['certfile'] + self.keyfile = self.params['keyfile'] + + if self.host is not None: + self.build_url() + + if self.cafile is not None: + self.append_ssl_certs() + + self.connect_to_rabbitmq() + + def check_required_library(self): + if not HAS_PIKA: + self.module.fail_json(msg=missing_required_lib("pika"), exception=PIKA_IMP_ERR) + + def check_host_params(self): + # Fail if url is specified and other conflicting parameters have been specified + if self.params['url'] is not None and any(self.params[k] is not None for k in ['proto', 'host', 'port', 'password', 'username', 'vhost']): + self.module.fail_json(msg="url and proto, host, port, vhost, username or password cannot be specified at the same time.") + + # Fail if url not specified and there is a missing parameter to build the url + if self.params['url'] is None and any(self.params[k] is None for k in ['proto', 'host', 'port', 'password', 'username', 'vhost']): + self.module.fail_json(msg="Connection parameters must be passed via url, or, proto, host, port, vhost, username or password.") + + def append_ssl_certs(self): + ssl_options = {} + if self.cafile: + ssl_options['cafile'] = self.cafile + if self.certfile: + ssl_options['certfile'] = self.certfile + if self.keyfile: + ssl_options['keyfile'] = self.keyfile + + self.url = self.url + '?ssl_options=' + urllib_parse.quote(json.dumps(ssl_options)) + + @staticmethod + def rabbitmq_argument_spec(): + return dict( + url=dict(type='str'), + proto=dict(type='str', choices=['amqp', 'amqps']), + host=dict(type='str'), + port=dict(type='int'), + username=dict(type='str'), + password=dict(type='str', no_log=True), + vhost=dict(type='str'), + queue=dict(type='str') + ) + + ''' Consider some file size limits here ''' + def _read_file(self, path): + try: + with open(path, "rb") as file_handle: + return file_handle.read() + except IOError as e: + self.module.fail_json(msg="Unable to open file %s: %s" % (path, to_native(e))) + + @staticmethod + def _check_file_mime_type(path): + mime = MimeTypes() + return mime.guess_type(path) + + def build_url(self): + self.url = '{0}://{1}:{2}@{3}:{4}/{5}'.format(self.proto, + self.username, + self.password, + self.host, + self.port, + self.vhost) + + def connect_to_rabbitmq(self): + """ + Function to connect to rabbitmq using username and password + """ + try: + parameters = pika.URLParameters(self.url) + except Exception as e: + self.module.fail_json(msg="URL malformed: %s" % to_native(e)) + + try: + self.connection = pika.BlockingConnection(parameters) + except Exception as e: + self.module.fail_json(msg="Connection issue: %s" % to_native(e)) + + try: + self.conn_channel = self.connection.channel() + except pika.exceptions.AMQPChannelError as e: + self.close_connection() + self.module.fail_json(msg="Channel issue: %s" % to_native(e)) + + def close_connection(self): + try: + self.connection.close() + except pika.exceptions.AMQPConnectionError: + pass + + def basic_publish(self): + self.content_type = self.params.get("content_type") + + if self.params.get("body") is not None: + args = dict( + body=self.params.get("body"), + properties=pika.BasicProperties(content_type=self.content_type, delivery_mode=1, headers=self.headers)) + + # If src (file) is defined and content_type is left as default, do a mime lookup on the file + if self.params.get("src") is not None and self.content_type == 'text/plain': + self.content_type = RabbitClient._check_file_mime_type(self.params.get("src"))[0] + self.headers.update( + filename=os.path.basename(self.params.get("src")) + ) + + args = dict( + body=self._read_file(self.params.get("src")), + properties=pika.BasicProperties(content_type=self.content_type, + delivery_mode=1, + headers=self.headers + )) + elif self.params.get("src") is not None: + args = dict( + body=self._read_file(self.params.get("src")), + properties=pika.BasicProperties(content_type=self.content_type, + delivery_mode=1, + headers=self.headers + )) + + try: + # If queue and exchange is not defined post to random queue, RabbitMQ will return the queue name of the automatically generated queue. + if self.queue is None and self.exchange is None: + result = self.conn_channel.queue_declare(queue='', + durable=self.params.get("durable"), + exclusive=self.params.get("exclusive"), + auto_delete=self.params.get("auto_delete")) + self.conn_channel.confirm_delivery() + self.queue = result.method.queue + elif self.queue is not None and self.exchange is None: + self.conn_channel.queue_declare(queue=self.queue, + durable=self.params.get("durable"), + exclusive=self.params.get("exclusive"), + auto_delete=self.params.get("auto_delete")) + self.conn_channel.confirm_delivery() + except Exception as e: + self.module.fail_json(msg="Queue declare issue: %s" % to_native(e)) + + # https://github.com/ansible/ansible/blob/devel/lib/ansible/module_utils/cloudstack.py#L150 + # If routing key is not defined, but, the queue is... we will use the queue name as routing_key. + if self.routing_key is not None: + args['routing_key'] = self.routing_key + elif self.routing_key is None and self.queue is not None: + args['routing_key'] = self.queue + elif self.routing_key is None and self.exchange is not None: + args['routing_key'] = self.exchange + else: + args['routing_key'] = '' + + # If exchange is not specified use the default/nameless exchange + if self.exchange is None: + args['exchange'] = '' + else: + args['exchange'] = self.exchange + if self.routing_key is None: + args['routing_key'] = self.exchange + + # self.module.fail_json(msg="%s %s %s" % (to_native(self.queue), to_native(self.exchange), to_native(self.routing_key))) + try: + self.conn_channel.basic_publish(**args) + return True + except pika.exceptions.UnroutableError: + return False diff --git a/ansible_collections/community/rabbitmq/plugins/module_utils/version.py b/ansible_collections/community/rabbitmq/plugins/module_utils/version.py new file mode 100644 index 000000000..c771682b8 --- /dev/null +++ b/ansible_collections/community/rabbitmq/plugins/module_utils/version.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +# Copyright: (c) 2021, Felix Fontein <felix@fontein.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +"""Provide version object to compare version numbers.""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +# Once we drop support for Ansible 2.9, ansible-base 2.10, and ansible-core 2.11, we can +# remove the _version.py file, and replace the following import by +# +# from ansible.module_utils.compat.version import LooseVersion + +from ._version import LooseVersion, StrictVersion diff --git a/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_binding.py b/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_binding.py new file mode 100644 index 000000000..6826c3af3 --- /dev/null +++ b/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_binding.py @@ -0,0 +1,298 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2015, Manuel Sousa <manuel.sousa@gmail.com> +# 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''' +--- +module: rabbitmq_binding +author: Manuel Sousa (@manuel-sousa) + +short_description: Manage rabbitMQ bindings +description: + - This module uses rabbitMQ REST APIs to create / delete bindings. +requirements: [ "requests >= 1.0.0" ] +options: + state: + description: + - Whether the bindings should be present or absent. + type: str + choices: [ "present", "absent" ] + default: present + name: + description: + - source exchange to create binding on. + type: str + required: true + aliases: [ "src", "source" ] + destination: + description: + - destination exchange or queue for the binding. + type: str + required: true + aliases: [ "dst", "dest" ] + destination_type: + description: + - Either queue or exchange. + type: str + required: true + choices: [ "queue", "exchange" ] + aliases: [ "type", "dest_type" ] + routing_key: + description: + - routing key for the binding. + type: str + default: "#" + arguments: + description: + - extra arguments for exchange. If defined this argument is a key/value dictionary + type: dict + required: false + default: {} +extends_documentation_fragment: + - community.rabbitmq.rabbitmq + +''' + +EXAMPLES = r''' +- name: Bind myQueue to directExchange with routing key info + community.rabbitmq.rabbitmq_binding: + name: directExchange + destination: myQueue + type: queue + routing_key: info + +- name: Bind directExchange to topicExchange with routing key *.info + community.rabbitmq.rabbitmq_binding: + name: topicExchange + destination: topicExchange + type: exchange + routing_key: '*.info' +''' + +import json +import traceback + +REQUESTS_IMP_ERR = None +try: + import requests + HAS_REQUESTS = True +except ImportError: + REQUESTS_IMP_ERR = traceback.format_exc() + HAS_REQUESTS = False + +from ansible.module_utils.six.moves.urllib import parse as urllib_parse +from ansible.module_utils.basic import AnsibleModule, missing_required_lib +from ansible_collections.community.rabbitmq.plugins.module_utils.rabbitmq import rabbitmq_argument_spec + + +class RabbitMqBinding(object): + def __init__(self, module): + """ + :param module: + """ + self.module = module + self.name = self.module.params['name'] + self.login_user = self.module.params['login_user'] + self.login_password = self.module.params['login_password'] + self.login_host = self.module.params['login_host'] + self.login_port = self.module.params['login_port'] + self.login_protocol = self.module.params['login_protocol'] + self.vhost = self.module.params['vhost'] + self.destination = self.module.params['destination'] + self.destination_type = 'q' if self.module.params['destination_type'] == 'queue' else 'e' + self.routing_key = self.module.params['routing_key'] + self.arguments = self.module.params['arguments'] + self.verify = self.module.params['ca_cert'] + self.cert = self.module.params['client_cert'] + self.key = self.module.params['client_key'] + self.props = urllib_parse.quote(self.routing_key) if self.routing_key != '' else '~' + self.base_url = '{0}://{1}:{2}/api/bindings'.format(self.login_protocol, + self.login_host, + self.login_port) + self.url = '{0}/{1}/e/{2}/{3}/{4}/{5}'.format(self.base_url, + urllib_parse.quote(self.vhost, safe=''), + urllib_parse.quote(self.name, safe=''), + self.destination_type, + urllib_parse.quote(self.destination, safe=''), + self.props) + self.result = { + 'changed': False, + 'name': self.module.params['name'], + } + self.authentication = ( + self.login_user, + self.login_password + ) + self.request = requests + self.http_check_states = { + 200: True, + 404: False, + } + self.http_actionable_states = { + 201: True, + 204: True, + } + self.api_result = self.request.get(self.url, auth=self.authentication, verify=self.verify, cert=(self.cert, self.key)) + + def run(self): + """ + :return: + """ + self.check_presence() + self.check_mode() + self.action_mode() + + def check_presence(self): + """ + :return: + """ + if self.check_should_throw_fail(): + self.fail() + + def change_required(self): + """ + :return: + """ + if self.module.params['state'] == 'present': + if not self.is_present(): + return True + elif self.module.params['state'] == 'absent': + if self.is_present(): + return True + return False + + def is_present(self): + """ + :return: + """ + return self.http_check_states.get(self.api_result.status_code, False) + + def check_mode(self): + """ + :return: + """ + if self.module.check_mode: + result = self.result + result['changed'] = self.change_required() + result['details'] = self.api_result.json() if self.is_present() else self.api_result.text + result['arguments'] = self.module.params['arguments'] + self.module.exit_json(**result) + + def check_reply_is_correct(self): + """ + :return: + """ + if self.api_result.status_code in self.http_check_states: + return True + return False + + def check_should_throw_fail(self): + """ + :return: + """ + if not self.is_present(): + if not self.check_reply_is_correct(): + return True + return False + + def action_mode(self): + """ + :return: + """ + result = self.result + if self.change_required(): + if self.module.params['state'] == 'present': + self.create() + if self.module.params['state'] == 'absent': + self.remove() + if self.action_should_throw_fail(): + self.fail() + result['changed'] = True + result['destination'] = self.module.params['destination'] + self.module.exit_json(**result) + else: + result['changed'] = False + self.module.exit_json(**result) + + def action_reply_is_correct(self): + """ + :return: + """ + if self.api_result.status_code in self.http_actionable_states: + return True + return False + + def action_should_throw_fail(self): + """ + :return: + """ + if not self.action_reply_is_correct(): + return True + return False + + def create(self): + """ + :return: + """ + self.url = '{0}/{1}/e/{2}/{3}/{4}'.format(self.base_url, + urllib_parse.quote(self.vhost, safe=''), + urllib_parse.quote(self.name, safe=''), + self.destination_type, + urllib_parse.quote(self.destination, safe='')) + self.api_result = self.request.post(self.url, + auth=self.authentication, + verify=self.verify, + cert=(self.cert, self.key), + headers={"content-type": "application/json"}, + data=json.dumps({ + 'routing_key': self.routing_key, + 'arguments': self.arguments + })) + + def remove(self): + """ + :return: + """ + self.api_result = self.request.delete(self.url, auth=self.authentication, verify=self.verify, cert=(self.cert, self.key)) + + def fail(self): + """ + :return: + """ + self.module.fail_json( + msg="Unexpected reply from API", + status=self.api_result.status_code, + details=self.api_result.text + ) + + +def main(): + + argument_spec = rabbitmq_argument_spec() + argument_spec.update( + dict( + state=dict(default='present', choices=['present', 'absent'], type='str'), + name=dict(required=True, aliases=["src", "source"], type='str'), + destination=dict(required=True, aliases=["dst", "dest"], type='str'), + destination_type=dict(required=True, aliases=["type", "dest_type"], choices=["queue", "exchange"], + type='str'), + routing_key=dict(default='#', type='str', no_log=False), + arguments=dict(default=dict(), type='dict') + ) + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + if not HAS_REQUESTS: + module.fail_json(msg=missing_required_lib("requests"), exception=REQUESTS_IMP_ERR) + + RabbitMqBinding(module).run() + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_exchange.py b/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_exchange.py new file mode 100644 index 000000000..41f3f742c --- /dev/null +++ b/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_exchange.py @@ -0,0 +1,234 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2015, Manuel Sousa <manuel.sousa@gmail.com> +# 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''' +--- +module: rabbitmq_exchange +author: Manuel Sousa (@manuel-sousa) + +short_description: Manage rabbitMQ exchange +description: + - This module uses rabbitMQ Rest API to create/delete exchanges +requirements: [ "requests >= 1.0.0" ] +options: + name: + description: + - Name of the exchange to create. + type: str + required: true + state: + description: + - Whether the exchange should be present or absent. + type: str + choices: [ "present", "absent" ] + required: false + default: present + durable: + description: + - Whether exchange is durable or not. + type: bool + required: false + default: true + exchange_type: + description: + - Type for the exchange. + - If using I(x-delayed-message), I(x-random), I(x-consistent-hash) or I(x-recent-history) the respective plugin on + - the RabbitMQ server must be enabled. + type: str + required: false + choices: [ "fanout", "direct", "headers", "topic", "x-delayed-message", "x-random", "x-consistent-hash", "x-recent-history" ] + aliases: [ "type" ] + default: direct + auto_delete: + description: + - If the exchange should delete itself after all queues/exchanges unbound from it. + type: bool + required: false + default: false + internal: + description: + - Exchange is available only for other exchanges. + type: bool + required: false + default: false + arguments: + description: + - Extra arguments for exchange. If defined this argument is a key/value dictionary. + type: dict + required: false + default: {} +extends_documentation_fragment: + - community.rabbitmq.rabbitmq + +''' + +EXAMPLES = r''' +- name: Create direct exchange + community.rabbitmq.rabbitmq_exchange: + name: directExchange + +- name: Create topic exchange on vhost + community.rabbitmq.rabbitmq_exchange: + name: topicExchange + type: topic + vhost: myVhost +''' + +import json +import traceback + +REQUESTS_IMP_ERR = None +try: + import requests + HAS_REQUESTS = True +except ImportError: + REQUESTS_IMP_ERR = traceback.format_exc() + HAS_REQUESTS = False + +from ansible.module_utils.basic import AnsibleModule, missing_required_lib +from ansible.module_utils.six.moves.urllib import parse as urllib_parse +from ansible_collections.community.rabbitmq.plugins.module_utils.rabbitmq import rabbitmq_argument_spec + + +def main(): + + argument_spec = rabbitmq_argument_spec() + argument_spec.update( + dict( + state=dict(default='present', choices=['present', 'absent'], type='str'), + name=dict(required=True, type='str'), + durable=dict(default=True, type='bool'), + auto_delete=dict(default=False, type='bool'), + internal=dict(default=False, type='bool'), + exchange_type=dict(default='direct', aliases=['type'], + choices=['fanout', 'direct', 'headers', 'topic', 'x-delayed-message', + 'x-random', 'x-consistent-hash', 'x-recent-history'], + type='str'), + arguments=dict(default=dict(), type='dict') + ) + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + url = "%s://%s:%s/api/exchanges/%s/%s" % ( + module.params['login_protocol'], + module.params['login_host'], + module.params['login_port'], + urllib_parse.quote(module.params['vhost'], ''), + urllib_parse.quote(module.params['name'], '') + ) + + if not HAS_REQUESTS: + module.fail_json(msg=missing_required_lib("requests"), exception=REQUESTS_IMP_ERR) + + # exchange plugin type to plugin name mapping + exchange_plugins = {'x-consistent-hash': 'rabbitmq_consistent_hash_exchange', + 'x-random': 'rabbitmq_random_exchange', + 'x-delayed-message': 'rabbitmq_delayed_message_exchange', + 'x-recent-history': 'rabbitmq_recent_history_exchange'} + result = dict(changed=False, name=module.params['name']) + + # Check if exchange already exists + r = requests.get(url, auth=(module.params['login_user'], module.params['login_password']), + verify=module.params['ca_cert'], cert=(module.params['client_cert'], module.params['client_key'])) + + if r.status_code == 200: + exchange_exists = True + response = r.json() + elif r.status_code == 404: + exchange_exists = False + response = r.text + else: + module.fail_json( + msg="Invalid response from RESTAPI when trying to check if exchange exists", + details=r.text + ) + + if module.params['state'] == 'present': + change_required = not exchange_exists + else: + change_required = exchange_exists + + # Check if attributes change on existing exchange + if not change_required and r.status_code == 200 and module.params['state'] == 'present': + if not ( + response['durable'] == module.params['durable'] and + response['auto_delete'] == module.params['auto_delete'] and + response['internal'] == module.params['internal'] and + response['type'] == module.params['exchange_type'] + ): + module.fail_json( + msg="RabbitMQ RESTAPI doesn't support attribute changes for existing exchanges" + ) + + # Exit if check_mode + if module.check_mode: + result['changed'] = change_required + result['details'] = response + result['arguments'] = module.params['arguments'] + module.exit_json(**result) + + # Do changes + if change_required: + if module.params['state'] == 'present': + r = requests.put( + url, + auth=(module.params['login_user'], module.params['login_password']), + headers={"content-type": "application/json"}, + data=json.dumps({ + "durable": module.params['durable'], + "auto_delete": module.params['auto_delete'], + "internal": module.params['internal'], + "type": module.params['exchange_type'], + "arguments": module.params['arguments'] + }), + verify=module.params['ca_cert'], + cert=(module.params['client_cert'], module.params['client_key']) + ) + elif module.params['state'] == 'absent': + r = requests.delete(url, auth=(module.params['login_user'], module.params['login_password']), + verify=module.params['ca_cert'], cert=(module.params['client_cert'], module.params['client_key'])) + + # RabbitMQ 3.6.7 changed this response code from 204 to 201 + if r.status_code == 204 or r.status_code == 201: + result['changed'] = True + module.exit_json(**result) + else: + rjson = r.json() + if (rjson['reason'].startswith('unknown exchange type')): + try: + module.fail_json( + msg=("Error creating exchange. You may need to enable the '%s' plugin for exchange type %s" % + (exchange_plugins[module.params['exchange_type']], module.params['exchange_type'])), + status=r.status_code, + details=r.text + ) + except KeyError: + module.fail_json( + msg=("Error creating exchange. You may need to enable a plugin for exchange type %s" % + module.params['exchange_type']), + status=r.status_code, + details=r.text + ) + else: + module.fail_json( + msg="Error creating exchange", + status=r.status_code, + details=r.text + ) + + else: + module.exit_json( + changed=False, + name=module.params['name'] + ) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_feature_flag.py b/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_feature_flag.py new file mode 100644 index 000000000..23db08b60 --- /dev/null +++ b/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_feature_flag.py @@ -0,0 +1,98 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2021, Damian Dabrowski <damian@dabrowski.cloud> +# 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''' +--- +module: rabbitmq_feature_flag +short_description: Enables feature flag +description: + - Allows to enable specified feature flag. +author: "Damian Dabrowski (@damiandabrowski5)" +version_added: '1.1.0' +options: + name: + description: + - Feature flag name. + type: str + required: true + node: + description: + - Erlang node name of the target rabbit node. + type: str + default: rabbit +''' + +EXAMPLES = r''' +- name: Enable the 'maintenance_mode_status' feature flag on 'rabbit@node-1' + community.rabbitmq.rabbitmq_feature_flag: + name: maintenance_mode_status + node: rabbit@node-1 +''' + +from ansible.module_utils.basic import AnsibleModule + + +class RabbitMqFeatureFlag(object): + + def __init__(self, module, name, node): + self.module = module + self.name = name + self.node = node + self._rabbitmqctl = module.get_bin_path('rabbitmqctl', True) + self.state = self.get_flag_state() + + def _exec(self, args, force_exec_in_check_mode=False): + if not self.module.check_mode or (self.module.check_mode and force_exec_in_check_mode): + cmd = [self._rabbitmqctl, '-q', '-n', self.node] + rc, out, err = self.module.run_command(cmd + args, check_rc=True) + return out.splitlines() + return list() + + def get_flag_state(self): + global_parameters = self._exec(['list_feature_flags'], True) + + for param_item in global_parameters: + name, state = param_item.split('\t') + if name == self.name: + if state == 'enabled': + return 'enabled' + return 'disabled' + return 'unavailable' + + def enable(self): + self._exec(['enable_feature_flag', self.name]) + + +def main(): + arg_spec = dict( + name=dict(type='str', required=True), + node=dict(type='str', default='rabbit') + ) + module = AnsibleModule( + argument_spec=arg_spec, + supports_check_mode=True + ) + + name = module.params['name'] + node = module.params['node'] + + result = dict(changed=False) + rabbitmq_feature_flag = RabbitMqFeatureFlag(module, name, node) + + if rabbitmq_feature_flag.state == 'disabled': + rabbitmq_feature_flag.enable() + result['changed'] = True + if rabbitmq_feature_flag.state == 'unavailable': + module.fail_json(msg="%s feature flag is not available" % (name)) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_global_parameter.py b/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_global_parameter.py new file mode 100644 index 000000000..8cecce5fa --- /dev/null +++ b/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_global_parameter.py @@ -0,0 +1,158 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2013, Chatham Financial <oss@chathamfinancial.com> +# Copyright: (c) 2017, Juergen Kirschbaum <jk@jk-itc.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''' +--- +module: rabbitmq_global_parameter +short_description: Manage RabbitMQ global parameters +description: + - Manage dynamic, cluster-wide global parameters for RabbitMQ +author: "Juergen Kirschbaum (@jgkirschbaum)" +options: + name: + description: + - Name of the global parameter being set + type: str + required: true + default: null + value: + description: + - Value of the global parameter, as a JSON term + type: str + required: false + default: null + node: + description: + - erlang node name of the rabbit we wish to configure + type: str + required: false + default: rabbit + state: + description: + - Specify if global parameter is to be added or removed + type: str + required: false + default: present + choices: [ 'present', 'absent'] +''' + +EXAMPLES = r''' +- name: Set the global parameter 'cluster_name' to a value of 'mq-cluster' (in quotes) + community.rabbitmq.rabbitmq_global_parameter: + name: cluster_name + value: "{{ 'mq-cluster' | to_json }}" + state: present +''' + +RETURN = r''' +name: + description: name of the global parameter being set + returned: success + type: str + sample: "cluster_name" +value: + description: value of the global parameter, as a JSON term + returned: changed + type: str + sample: "the-cluster-name" +''' + +import json +from ansible.module_utils.basic import AnsibleModule + + +class RabbitMqGlobalParameter(object): + def __init__(self, module, name, value, node): + self.module = module + self.name = name + self.value = value + self.node = node + + self._value = None + + self._rabbitmqctl = module.get_bin_path('rabbitmqctl', True) + + def _exec(self, args, force_exec_in_check_mode=False): + if not self.module.check_mode or (self.module.check_mode and force_exec_in_check_mode): + cmd = [self._rabbitmqctl, '-q', '-n', self.node] + rc, out, err = self.module.run_command(cmd + args, check_rc=True) + return out.splitlines() + return list() + + def get(self): + global_parameters = [param for param in self._exec(['list_global_parameters'], True) if param.strip()] + + for idx, param_item in enumerate(global_parameters): + name, value = param_item.split('\t') + # RabbitMQ 3.8.x and above return table header, ignore it + if idx == 0 and name == 'name' and value == 'value': + continue + + if name == self.name: + self._value = json.loads(value) + return True + return False + + def set(self): + self._exec(['set_global_parameter', + self.name, + json.dumps(self.value)]) + + def delete(self): + self._exec(['clear_global_parameter', self.name]) + + def has_modifications(self): + return self.value != self._value + + +def main(): + arg_spec = dict( + name=dict(type='str', required=True), + value=dict(type='str', default=None), + state=dict(default='present', choices=['present', 'absent']), + node=dict(type='str', default='rabbit') + ) + module = AnsibleModule( + argument_spec=arg_spec, + supports_check_mode=True + ) + + name = module.params['name'] + value = module.params['value'] + if isinstance(value, str): + value = json.loads(value) + state = module.params['state'] + node = module.params['node'] + + result = dict(changed=False) + rabbitmq_global_parameter = RabbitMqGlobalParameter(module, name, value, node) + + if rabbitmq_global_parameter.get(): + if state == 'absent': + rabbitmq_global_parameter.delete() + result['changed'] = True + else: + if rabbitmq_global_parameter.has_modifications(): + rabbitmq_global_parameter.set() + result['changed'] = True + elif state == 'present': + rabbitmq_global_parameter.set() + result['changed'] = True + + result['name'] = name + result['value'] = value + result['state'] = state + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_parameter.py b/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_parameter.py new file mode 100644 index 000000000..5ecbd4ec4 --- /dev/null +++ b/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_parameter.py @@ -0,0 +1,155 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2013, Chatham Financial <oss@chathamfinancial.com> +# 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''' +--- +module: rabbitmq_parameter +short_description: Manage RabbitMQ parameters +description: + - Manage dynamic, cluster-wide parameters for RabbitMQ +author: Chris Hoffman (@chrishoffman) +options: + component: + description: + - Name of the component of which the parameter is being set + type: str + required: true + name: + description: + - Name of the parameter being set + type: str + required: true + value: + description: + - Value of the parameter, as a JSON term + type: str + vhost: + description: + - vhost to apply access privileges. + type: str + default: / + node: + description: + - erlang node name of the rabbit we wish to configure + type: str + default: rabbit + state: + description: + - Specify if parameter is to be added or removed + type: str + default: present + choices: [ 'present', 'absent'] +''' + +EXAMPLES = r""" +- name: Set the federation parameter 'local_username' to a value of 'guest' (in quotes) + community.rabbitmq.rabbitmq_parameter: + component: federation + name: local-username + value: '"guest"' + state: present +""" +import json +from ansible.module_utils.basic import AnsibleModule + + +class RabbitMqParameter(object): + def __init__(self, module, component, name, value, vhost, node): + self.module = module + self.component = component + self.name = name + self.value = value + self.vhost = vhost + self.node = node + + self._value = None + + self._rabbitmqctl = module.get_bin_path('rabbitmqctl', True) + + def _exec(self, args, force_exec_in_check_mode=False): + if not self.module.check_mode or (self.module.check_mode and force_exec_in_check_mode): + cmd = [self._rabbitmqctl, '-q', '-n', self.node] + rc, out, err = self.module.run_command(cmd + args, check_rc=True) + return out.strip().splitlines() + return list() + + def get(self): + parameters = [param for param in self._exec(['list_parameters', '-p', self.vhost], True) if param.strip()] + + for param_item in parameters: + component, name, value = param_item.split('\t') + + if component == self.component and name == self.name: + self._value = json.loads(value) + return True + return False + + def set(self): + self._exec(['set_parameter', + '-p', + self.vhost, + self.component, + self.name, + json.dumps(self.value)]) + + def delete(self): + self._exec(['clear_parameter', '-p', self.vhost, self.component, self.name]) + + def has_modifications(self): + return self.value != self._value + + +def main(): + arg_spec = dict( + component=dict(required=True), + name=dict(required=True), + value=dict(default=None), + vhost=dict(default='/'), + state=dict(default='present', choices=['present', 'absent']), + node=dict(default='rabbit') + ) + module = AnsibleModule( + argument_spec=arg_spec, + supports_check_mode=True + ) + + component = module.params['component'] + name = module.params['name'] + value = module.params['value'] + if isinstance(value, str): + value = json.loads(value) + vhost = module.params['vhost'] + state = module.params['state'] + node = module.params['node'] + + result = dict(changed=False) + rabbitmq_parameter = RabbitMqParameter(module, component, name, value, vhost, node) + + if rabbitmq_parameter.get(): + if state == 'absent': + rabbitmq_parameter.delete() + result['changed'] = True + else: + if rabbitmq_parameter.has_modifications(): + rabbitmq_parameter.set() + result['changed'] = True + elif state == 'present': + rabbitmq_parameter.set() + result['changed'] = True + + result['component'] = component + result['name'] = name + result['vhost'] = vhost + result['state'] = state + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_plugin.py b/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_plugin.py new file mode 100644 index 000000000..86a5b6aeb --- /dev/null +++ b/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_plugin.py @@ -0,0 +1,187 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2013, Chatham Financial <oss@chathamfinancial.com> +# 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''' +--- +module: rabbitmq_plugin +short_description: Manage RabbitMQ plugins +description: + - This module can be used to enable or disable RabbitMQ plugins. +author: + - Chris Hoffman (@chrishoffman) +options: + names: + description: + - Comma-separated list of plugin names. Also, accepts plugin name. + type: str + required: true + aliases: [name] + new_only: + description: + - Only enable missing plugins. + - Does not disable plugins that are not in the names list. + type: bool + default: false + state: + description: + - Specify if plugins are to be enabled or disabled. + type: str + default: enabled + choices: [enabled, disabled] + broker_state: + description: + - Specify whether the broker should be online or offline for the plugin change. + type: str + default: online + choices: [online, offline] + prefix: + description: + - Specify a custom install prefix to a Rabbit. + type: str +''' + +EXAMPLES = r''' +- name: Enables the rabbitmq_management plugin + community.rabbitmq.rabbitmq_plugin: + names: rabbitmq_management + state: enabled + +- name: Enable multiple rabbitmq plugins + community.rabbitmq.rabbitmq_plugin: + names: rabbitmq_management,rabbitmq_management_visualiser + state: enabled + +- name: Disable plugin + community.rabbitmq.rabbitmq_plugin: + names: rabbitmq_management + state: disabled + +- name: Enable every plugin in list with existing plugins + community.rabbitmq.rabbitmq_plugin: + names: rabbitmq_management,rabbitmq_management_visualiser,rabbitmq_shovel,rabbitmq_shovel_management + state: enabled + new_only: true + +- name: Enables the rabbitmq_peer_discovery_aws plugin without requiring a broker connection. + community.rabbitmq.rabbitmq_plugin: + names: rabbitmq_peer_discovery_aws_plugin + state: enabled + broker_state: offline +''' + +RETURN = r''' +enabled: + description: list of plugins enabled during task run + returned: always + type: list + sample: ["rabbitmq_management"] +disabled: + description: list of plugins disabled during task run + returned: always + type: list + sample: ["rabbitmq_management"] +''' + +import os +from ansible.module_utils.basic import AnsibleModule + + +class RabbitMqPlugins(object): + + def __init__(self, module): + self.module = module + bin_path = '' + if module.params['prefix']: + if os.path.isdir(os.path.join(module.params['prefix'], 'bin')): + bin_path = os.path.join(module.params['prefix'], 'bin') + elif os.path.isdir(os.path.join(module.params['prefix'], 'sbin')): + bin_path = os.path.join(module.params['prefix'], 'sbin') + else: + # No such path exists. + module.fail_json(msg="No binary folder in prefix %s" % module.params['prefix']) + + self._rabbitmq_plugins = os.path.join(bin_path, "rabbitmq-plugins") + else: + self._rabbitmq_plugins = module.get_bin_path('rabbitmq-plugins', True) + + def _exec(self, args, force_exec_in_check_mode=False): + if not self.module.check_mode or (self.module.check_mode and force_exec_in_check_mode): + cmd = [self._rabbitmq_plugins] + rc, out, err = self.module.run_command(cmd + args, check_rc=True) + return out.splitlines() + return list() + + def get_all(self): + list_output = self._exec(['list', '-E', '-m'], True) + plugins = [] + for plugin in list_output: + if not plugin: + break + plugins.append(plugin) + + return plugins + + def enable(self, name): + self._exec(['enable', "--%s" % self.module.params['broker_state'], name]) + + def disable(self, name): + self._exec(['disable', "--%s" % self.module.params['broker_state'], name]) + + +def main(): + arg_spec = dict( + names=dict(required=True, aliases=['name']), + new_only=dict(default='no', type='bool'), + state=dict(default='enabled', choices=['enabled', 'disabled']), + broker_state=dict(default='online', choices=['online', 'offline']), + prefix=dict(required=False, default=None) + ) + module = AnsibleModule( + argument_spec=arg_spec, + supports_check_mode=True + ) + + result = dict() + names = module.params['names'].split(',') + new_only = module.params['new_only'] + state = module.params['state'] + + rabbitmq_plugins = RabbitMqPlugins(module) + enabled_plugins = rabbitmq_plugins.get_all() + + enabled = [] + disabled = [] + if state == 'enabled': + if not new_only: + for plugin in enabled_plugins: + if " " in plugin: + continue + if plugin not in names: + rabbitmq_plugins.disable(plugin) + disabled.append(plugin) + + for name in names: + if name not in enabled_plugins: + rabbitmq_plugins.enable(name) + enabled.append(name) + else: + for plugin in enabled_plugins: + if plugin in names: + rabbitmq_plugins.disable(plugin) + disabled.append(plugin) + + result['changed'] = len(enabled) > 0 or len(disabled) > 0 + result['enabled'] = enabled + result['disabled'] = disabled + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_policy.py b/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_policy.py new file mode 100644 index 000000000..441a72ce7 --- /dev/null +++ b/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_policy.py @@ -0,0 +1,253 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2013, John Dewey <john@dewey.ws> +# 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''' +--- +module: rabbitmq_policy +short_description: Manage the state of policies in RabbitMQ +description: + - Manage the state of a policy in RabbitMQ. +author: John Dewey (@retr0h) +options: + name: + description: + - The name of the policy to manage. + type: str + required: true + vhost: + description: + - The name of the vhost to apply to. + type: str + default: / + apply_to: + description: + - What the policy applies to. Requires RabbitMQ 3.2.0 or later. + type: str + default: all + choices: [all, exchanges, queues] + pattern: + description: + - A regex of queues to apply the policy to. Required when + C(state=present). This option is no longer required as of Ansible 2.9. + type: str + required: false + default: null + tags: + description: + - A dict or string describing the policy. Required when + C(state=present). This option is no longer required as of Ansible 2.9. + type: dict + required: false + default: null + priority: + description: + - The priority of the policy. + type: str + default: '0' + node: + description: + - Erlang node name of the rabbit we wish to configure. + type: str + default: rabbit + state: + description: + - The state of the policy. + type: str + default: present + choices: [present, absent] +''' + +EXAMPLES = r''' +- name: ensure the default vhost contains the HA policy via a dict + community.rabbitmq.rabbitmq_policy: + name: HA + pattern: .* + args: + tags: + ha-mode: all + +- name: ensure the default vhost contains the HA policy + community.rabbitmq.rabbitmq_policy: + name: HA + pattern: .* + tags: + ha-mode: all +''' + +import json +import re +from ansible_collections.community.rabbitmq.plugins.module_utils.version import LooseVersion as Version + +from ansible.module_utils.basic import AnsibleModule + + +class RabbitMqPolicy(object): + + def __init__(self, module, name): + self._module = module + self._name = name + self._vhost = module.params['vhost'] + self._pattern = module.params['pattern'] + self._apply_to = module.params['apply_to'] + self._tags = module.params['tags'] + self._priority = module.params['priority'] + self._node = module.params['node'] + self._rabbitmqctl = module.get_bin_path('rabbitmqctl', True) + + self._version = self._rabbit_version() + + def _exec(self, + args, + force_exec_in_check_mode=False, + split_lines=True, + add_vhost=True): + if (not self._module.check_mode + or (self._module.check_mode and force_exec_in_check_mode)): + cmd = [self._rabbitmqctl, '-q', '-n', self._node] + + if add_vhost: + args.insert(1, '-p') + args.insert(2, self._vhost) + + rc, out, err = self._module.run_command(cmd + args, check_rc=True) + if split_lines: + return out.splitlines() + + return out + return list() + + def _rabbit_version(self): + status = self._exec(['status'], True, False, False) + + # 3.7.x erlang style output + version_match = re.search('{rabbit,".*","(?P<version>.*)"}', status) + if version_match: + return Version(version_match.group('version')) + + # 3.8.x style ouput + version_match = re.search('RabbitMQ version: (?P<version>.*)', status) + if version_match: + return Version(version_match.group('version')) + + return None + + def _list_policies(self): + if self._version and self._version >= Version('3.7.9'): + # Remove first header line from policies list for version > 3.7.9 + return self._exec(['list_policies'], True)[1:] + + return self._exec(['list_policies'], True) + + def has_modifications(self): + if self._pattern is None or self._tags is None: + self._module.fail_json( + msg=('pattern and tags are required for ' + 'state=present')) + + if self._version and self._version >= Version('3.7.0'): + # Change fields order in rabbitmqctl output in version 3.7 + return not any( + self._policy_check(policy, apply_to_fno=3, pattern_fno=2) + for policy in self._list_policies()) + else: + return not any( + self._policy_check(policy) for policy in self._list_policies()) + + def should_be_deleted(self): + return any( + self._policy_check_by_name(policy) + for policy in self._list_policies()) + + def set(self): + args = ['set_policy'] + args.append(self._name) + args.append(self._pattern) + args.append(json.dumps(self._tags)) + args.append('--priority') + args.append(self._priority) + if self._apply_to != 'all': + args.append('--apply-to') + args.append(self._apply_to) + return self._exec(args) + + def clear(self): + return self._exec(['clear_policy', self._name]) + + def _policy_check(self, + policy, + name_fno=1, + apply_to_fno=2, + pattern_fno=3, + tags_fno=4, + priority_fno=5): + if not policy: + return False + + policy_data = policy.split('\t') + + policy_name = policy_data[name_fno] + apply_to = policy_data[apply_to_fno] + pattern = policy_data[pattern_fno].replace('\\\\', '\\') + + try: + tags = json.loads(policy_data[tags_fno]) + except json.decoder.JSONDecodeError: + tags = policy_data[tags_fno] + + priority = policy_data[priority_fno] + + return (policy_name == self._name and apply_to == self._apply_to + and tags == self._tags and priority == self._priority + and pattern == self._pattern) + + def _policy_check_by_name(self, policy): + if not policy: + return False + + policy_name = policy.split('\t')[1] + + return policy_name == self._name + + +def main(): + arg_spec = dict( + name=dict(required=True), + vhost=dict(default='/'), + pattern=dict(required=False, default=None), + apply_to=dict(default='all', choices=['all', 'exchanges', 'queues']), + tags=dict(type='dict', required=False, default=None), + priority=dict(default='0'), + node=dict(default='rabbit'), + state=dict(default='present', choices=['present', 'absent']), + ) + + module = AnsibleModule( + argument_spec=arg_spec, + supports_check_mode=True + ) + + name = module.params['name'] + state = module.params['state'] + rabbitmq_policy = RabbitMqPolicy(module, name) + + result = dict(changed=False, name=name, state=state) + + if state == 'present' and rabbitmq_policy.has_modifications(): + rabbitmq_policy.set() + result['changed'] = True + elif state == 'absent' and rabbitmq_policy.should_be_deleted(): + rabbitmq_policy.clear() + result['changed'] = True + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_publish.py b/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_publish.py new file mode 100644 index 000000000..38b5a64bb --- /dev/null +++ b/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_publish.py @@ -0,0 +1,247 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2018, John Imison <john+github@imison.net> +# 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''' +--- +module: rabbitmq_publish +short_description: Publish a message to a RabbitMQ queue. +description: + - Publish a message on a RabbitMQ queue using a blocking connection. +options: + url: + description: + - An URL connection string to connect to the RabbitMQ server. + - I(url) and I(host)/I(port)/I(user)/I(pass)/I(vhost) are mutually exclusive, use either or but not both. + type: str + proto: + description: + - The protocol to use. + type: str + choices: [amqps, amqp] + host: + description: + - The RabbitMQ server hostname or IP. + type: str + port: + description: + - The RabbitMQ server port. + type: int + username: + description: + - The RabbitMQ username. + type: str + password: + description: + - The RabbitMQ password. + type: str + vhost: + description: + - The virtual host to target. + - If default vhost is required, use C('%2F'). + type: str + queue: + description: + - The queue to publish a message to. If no queue is specified, RabbitMQ will return a random queue name. + - A C(queue) cannot be provided if an C(exchange) is specified. + type: str + exchange: + description: + - The exchange to publish a message to. + - An C(exchange) cannot be provided if a C(queue) is specified. + type: str + routing_key: + description: + - The routing key. + type: str + body: + description: + - The body of the message. + - A C(body) cannot be provided if a C(src) is specified. + type: str + src: + description: + - A file to upload to the queue. Automatic mime type detection is attempted if content_type is not defined (left as default). + - A C(src) cannot be provided if a C(body) is specified. + - The filename is added to the headers of the posted message to RabbitMQ. Key being the C(filename), value is the filename. + type: path + aliases: ['file'] + content_type: + description: + - The content type of the body. + type: str + default: text/plain + durable: + description: + - Set the queue to be durable. + type: bool + default: False + exclusive: + description: + - Set the queue to be exclusive. + type: bool + default: False + auto_delete: + description: + - Set the queue to auto delete. + type: bool + default: False + headers: + description: + - A dictionary of headers to post with the message. + type: dict + default: {} + cafile: + description: + - CA file used during connection to the RabbitMQ server over SSL. + - If this option is specified, also I(certfile) and I(keyfile) must be specified. + type: str + certfile: + description: + - Client certificate to establish SSL connection. + - If this option is specified, also I(cafile) and I(keyfile) must be specified. + type: str + keyfile: + description: + - Client key to establish SSL connection. + - If this option is specified, also I(cafile) and I(certfile) must be specified. + type: str + + + +requirements: [ pika ] +notes: + - This module requires the pika python library U(https://pika.readthedocs.io/). + - Pika is a pure-Python implementation of the AMQP 0-9-1 protocol that tries to stay fairly independent of the underlying network support library. + - This module is tested against RabbitMQ. Other AMQP 0.9.1 protocol based servers may work but not tested/guaranteed. + - The certificate authentication was tested with certificates created + via U(https://www.rabbitmq.com/ssl.html#automated-certificate-generation) and RabbitMQ + configuration variables C(ssl_options.verify = verify_peer) & C(ssl_options.fail_if_no_peer_cert = true). +author: "John Imison (@Im0)" +''' + +EXAMPLES = r''' +- name: Publish to an exchange + community.rabbitmq.rabbitmq_publish: + exchange: exchange1 + url: "amqp://guest:guest@192.168.0.32:5672/%2F" + body: "Hello exchange from ansible module rabbitmq_publish" + content_type: "text/plain" + +- name: Publish to an exchange with routing_key + community.rabbitmq.rabbitmq_publish: + exchange: exchange1 + routing_key: queue1 + url: "amqp://guest:guest@192.168.0.32:5672/%2F" + body: "Hello queue via exchange routing_key from ansible module rabbitmq_publish" + content_type: "text/plain" + +- name: Publish a message to a queue with headers + community.rabbitmq.rabbitmq_publish: + url: "amqp://guest:guest@192.168.0.32:5672/%2F" + queue: 'test' + body: "Hello world from ansible module rabbitmq_publish" + content_type: "text/plain" + headers: + myHeader: myHeaderValue + +- name: Publish a file to a queue + community.rabbitmq.rabbitmq_publish: + url: "amqp://guest:guest@192.168.0.32:5672/%2F" + queue: 'images' + file: 'path/to/logo.gif' + +- name: RabbitMQ auto generated queue + community.rabbitmq.rabbitmq_publish: + url: "amqp://guest:guest@192.168.0.32:5672/%2F" + body: "Hello world random queue from ansible module rabbitmq_publish" + content_type: "text/plain" + +- name: Publish with certs + community.rabbitmq.rabbitmq_publish: + url: "amqps://guest:guest@192.168.0.32:5671/%2F" + body: "Hello test queue from ansible module rabbitmq_publish via SSL certs" + queue: 'test' + content_type: "text/plain" + cafile: 'ca_certificate.pem' + certfile: 'client_certificate.pem' + keyfile: 'client_key.pem' + +''' + +RETURN = r''' +result: + description: + - If posted to an exchange, the result contains the status I(msg), content type I(content_type) the exchange name I(exchange) + - and the routing key I(routing_key). + - If posted to a queue, the result contains the status I(msg), content type I(content_type) and the queue name I(queue). + returned: success + type: dict + sample: | + 'result': { 'content_type': 'text/plain', 'msg': 'Successfully published to queue test', 'queue': 'test' } +''' + +try: + import pika + HAS_PIKA = True +except ImportError: + HAS_PIKA = False + + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native, to_text +from ansible_collections.community.rabbitmq.plugins.module_utils.rabbitmq import RabbitClient + + +def main(): + argument_spec = RabbitClient.rabbitmq_argument_spec() + argument_spec.update( + exchange=dict(type='str'), + routing_key=dict(type='str', required=False, no_log=False), + body=dict(type='str', required=False), + src=dict(aliases=['file'], type='path', required=False), + content_type=dict(default="text/plain", type='str'), + durable=dict(default=False, type='bool'), + exclusive=dict(default=False, type='bool'), + auto_delete=dict(default=False, type='bool'), + headers=dict(default={}, type='dict'), + cafile=dict(type='str', required=False), + certfile=dict(type='str', required=False), + keyfile=dict(type='str', required=False, no_log=False), + ) + module = AnsibleModule( + argument_spec=argument_spec, + mutually_exclusive=[['body', 'src'], ['queue', 'exchange']], + required_together=[['cafile', 'certfile', 'keyfile']], + supports_check_mode=False + ) + + rabbitmq = RabbitClient(module) + + if rabbitmq.basic_publish(): + rabbitmq.close_connection() + if (rabbitmq.queue is not None): + module.exit_json(changed=True, result={"msg": "Successfully published to queue %s" % rabbitmq.queue, + "queue": rabbitmq.queue, "content_type": rabbitmq.content_type} + ) + elif (rabbitmq.exchange is not None): + module.exit_json(changed=True, result={"msg": "Successfully published to exchange %s" % rabbitmq.exchange, + "routing_key": rabbitmq.routing_key, "exchange": rabbitmq.exchange, "content_type": rabbitmq.content_type} + ) + + else: + rabbitmq.close_connection() + if (rabbitmq.queue is not None): + module.fail_json(changed=False, msg="Unsuccessful publishing to queue %s" % rabbitmq.queue) + elif (rabbitmq.exchange is not None): + module.fail_json(changed=False, msg="Unsuccessful publishing to exchange %s" % rabbitmq.exchange) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_queue.py b/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_queue.py new file mode 100644 index 000000000..1290f4713 --- /dev/null +++ b/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_queue.py @@ -0,0 +1,264 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2015, Manuel Sousa <manuel.sousa@gmail.com> +# 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''' +--- +module: rabbitmq_queue +author: Manuel Sousa (@manuel-sousa) + +short_description: Manage rabbitMQ queues +description: + - This module uses rabbitMQ Rest API to create/delete queues. + - Due to limitations in the API, it cannot modify existing queues. +requirements: [ "requests >= 1.0.0" ] +options: + name: + description: + - Name of the queue. + type: str + required: true + state: + description: + - Whether the queue should be present or absent. + type: str + choices: [ "present", "absent" ] + default: present + durable: + description: + - whether queue is durable or not. + type: bool + default: true + auto_delete: + description: + - if the queue should delete itself after all queues/queues unbound from it. + type: bool + default: false + message_ttl: + description: + - How long a message can live in queue before it is discarded (milliseconds). + type: int + auto_expires: + description: + - How long a queue can be unused before it is automatically deleted (milliseconds). + type: int + max_length: + description: + - How many messages can the queue contain before it starts rejecting. + type: int + dead_letter_exchange: + description: + - Optional name of an exchange to which messages will be republished if they + - are rejected or expire. + type: str + dead_letter_routing_key: + description: + - Optional replacement routing key to use when a message is dead-lettered. + - Original routing key will be used if unset. + type: str + max_priority: + description: + - Maximum number of priority levels for the queue to support. + - If not set, the queue will not support message priorities. + - Larger numbers indicate higher priority. + type: int + arguments: + description: + - extra arguments for queue. If defined this argument is a key/value dictionary + - Arguments here take precedence over parameters. If both are defined, the + argument will be used. + type: dict + default: {} +extends_documentation_fragment: +- community.rabbitmq.rabbitmq + +''' + +EXAMPLES = r''' +- name: Create a queue + community.rabbitmq.rabbitmq_queue: + name: myQueue + +- name: Create a queue on remote host + community.rabbitmq.rabbitmq_queue: + name: myRemoteQueue + login_user: user + login_password: secret + login_host: remote.example.org + +# You may specify different types of queues using the arguments parameter. +- name: Create RabbitMQ stream + become: yes + community.rabbitmq.rabbitmq_queue: + name: test-x + arguments: + x-queue-type: stream + x-max-age: 24h +''' + +import json +import traceback + +REQUESTS_IMP_ERR = None +try: + import requests + HAS_REQUESTS = True +except ImportError: + REQUESTS_IMP_ERR = traceback.format_exc() + HAS_REQUESTS = False + +from ansible.module_utils.basic import AnsibleModule, missing_required_lib +from ansible.module_utils.six.moves.urllib import parse as urllib_parse +from ansible_collections.community.rabbitmq.plugins.module_utils.rabbitmq import rabbitmq_argument_spec + + +def check_if_arg_changed(module, current_args, desired_args, arg_name): + if arg_name not in current_args: + if arg_name in desired_args: + module.fail_json( + msg=("RabbitMQ RESTAPI doesn't support attribute changes for existing queues." + "Attempting to set %s which is not currently set." % arg_name), + ) + # else don't care + else: # arg_name in current_args + if arg_name in desired_args: + if current_args[arg_name] != desired_args[arg_name]: + module.fail_json( + msg=("RabbitMQ RESTAPI doesn't support attribute changes for existing queues.\n" + "Attempting to change %s from '%s' to '%s'" % (arg_name, current_args[arg_name], desired_args[arg_name])) + ) + else: + module.fail_json( + msg=("RabbitMQ RESTAPI doesn't support attribute changes for existing queues." + "Attempting to unset %s which is currently set to '%s'." % (arg_name, current_args[arg_name])), + ) + + +def main(): + + argument_spec = rabbitmq_argument_spec() + argument_spec.update( + dict( + state=dict(default='present', choices=['present', 'absent'], type='str'), + name=dict(required=True, type='str'), + durable=dict(default=True, type='bool'), + auto_delete=dict(default=False, type='bool'), + message_ttl=dict(default=None, type='int'), + auto_expires=dict(default=None, type='int'), + max_length=dict(default=None, type='int'), + dead_letter_exchange=dict(default=None, type='str'), + dead_letter_routing_key=dict(default=None, type='str', no_log=False), + arguments=dict(default=dict(), type='dict'), + max_priority=dict(default=None, type='int') + ) + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + url = "%s://%s:%s/api/queues/%s/%s" % ( + module.params['login_protocol'], + module.params['login_host'], + module.params['login_port'], + urllib_parse.quote(module.params['vhost'], ''), + urllib_parse.quote(module.params['name'], '') + ) + + if not HAS_REQUESTS: + module.fail_json(msg=missing_required_lib("requests"), exception=REQUESTS_IMP_ERR) + + result = dict(changed=False, name=module.params['name']) + + # Check if queue already exists + r = requests.get(url, auth=(module.params['login_user'], module.params['login_password']), + verify=module.params['ca_cert'], cert=(module.params['client_cert'], module.params['client_key'])) + + if r.status_code == 200: + queue_exists = True + response = r.json() + elif r.status_code == 404: + queue_exists = False + response = r.text + else: + module.fail_json( + msg="Invalid response from RESTAPI when trying to check if queue exists", + details=r.text + ) + + arg_map = { + 'message_ttl': 'x-message-ttl', + 'auto_expires': 'x-expires', + 'max_length': 'x-max-length', + 'dead_letter_exchange': 'x-dead-letter-exchange', + 'dead_letter_routing_key': 'x-dead-letter-routing-key', + 'max_priority': 'x-max-priority' + } + + # Sync arguments with parameters (the final request uses module.params['arguments']) + for k, v in arg_map.items(): + if module.params[k] is not None: + module.params['arguments'][v] = module.params[k] + + if module.params['state'] == 'present': + add_or_delete_required = not queue_exists + else: + add_or_delete_required = queue_exists + + # Check if attributes change on existing queue + if not add_or_delete_required and r.status_code == 200 and module.params['state'] == 'present': + check_if_arg_changed(module, response, module.params, 'durable') + check_if_arg_changed(module, response, module.params, 'auto_delete') + + for arg in arg_map.values(): + check_if_arg_changed(module, response['arguments'], module.params['arguments'], arg) + + # Exit if check_mode + if module.check_mode: + result['changed'] = add_or_delete_required + result['details'] = response + result['arguments'] = module.params['arguments'] + module.exit_json(**result) + + # Do changes + if add_or_delete_required: + if module.params['state'] == 'present': + r = requests.put( + url, + auth=(module.params['login_user'], module.params['login_password']), + headers={"content-type": "application/json"}, + data=json.dumps({ + "durable": module.params['durable'], + "auto_delete": module.params['auto_delete'], + "arguments": module.params['arguments'] + }), + verify=module.params['ca_cert'], + cert=(module.params['client_cert'], module.params['client_key']) + ) + elif module.params['state'] == 'absent': + r = requests.delete(url, auth=(module.params['login_user'], module.params['login_password']), + verify=module.params['ca_cert'], cert=(module.params['client_cert'], module.params['client_key'])) + + # RabbitMQ 3.6.7 changed this response code from 204 to 201 + if r.status_code == 204 or r.status_code == 201: + result['changed'] = True + module.exit_json(**result) + else: + module.fail_json( + msg="Error creating queue", + status=r.status_code, + details=r.text + ) + + else: + module.exit_json( + changed=False, + name=module.params['name'] + ) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_upgrade.py b/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_upgrade.py new file mode 100644 index 000000000..e44d7e09e --- /dev/null +++ b/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_upgrade.py @@ -0,0 +1,127 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2021, Damian Dabrowski <damian@dabrowski.cloud> +# 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''' +--- +module: rabbitmq_upgrade +short_description: Execute rabbitmq-upgrade commands +description: + - Allows to execute rabbitmq-upgrade commands +author: "Damian Dabrowski (@damiandabrowski5)" +version_added: '1.1.0' +options: + action: + description: + - Specify action to be executed. + type: str + required: true + choices: ['await_online_quorum_plus_one','await_online_synchronized_mirror','post_upgrade','drain','revive'] + node: + description: + - Erlang node name of the target rabbit node. + type: str + required: false + default: rabbit +''' + +EXAMPLES = r''' +- name: Drain 'rabbit@node-1' node (in other words, put it into maintenance mode) + community.rabbitmq.rabbitmq_upgrade: + action: drain + node: rabbit@node-1 +''' + +import json +from ansible.module_utils.basic import AnsibleModule + + +class RabbitMqUpgrade(object): + + def __init__(self, module, action, node, result): + self.module = module + self.action = action + self.node = node + self.result = result + + def _exec(self, binary, args, force_exec_in_check_mode=False): + if not self.module.check_mode or (self.module.check_mode and force_exec_in_check_mode): + cmd = [self.module.get_bin_path(binary, True)] + rc, out, err = self.module.run_command(cmd + args, check_rc=True) + return out.splitlines() + return list() + + def is_node_under_maintenance(self): + cmd = self._exec('rabbitmq-diagnostics', + ['--formatter', 'json', 'status', '-n', self.node], True) + node_status = json.loads("".join(cmd)) + maint_enabled = node_status['is_under_maintenance'] + if maint_enabled: + return True + return False + + def is_maint_flag_enabled(self): + feature_flags = self._exec('rabbitmqctl', ['list_feature_flags', '-q'], True) + for param_item in feature_flags: + name, state = param_item.split('\t') + if name == 'maintenance_mode_status' and state == 'enabled': + return True + return False + + def drain(self): + if not self.is_maint_flag_enabled(): + self.module.fail_json(msg='maintenance_mode_status feature_flag is disabled.') + if not self.is_node_under_maintenance(): + self._exec('rabbitmq-upgrade', ['drain', '-n', self.node]) + self.result['changed'] = True + + def revive(self): + if not self.is_maint_flag_enabled(): + self.module.fail_json(msg='maintenance_mode_status feature_flag is disabled.') + if self.is_node_under_maintenance(): + self._exec('rabbitmq-upgrade', ['revive', '-n', self.node]) + self.result['changed'] = True + + def await_online_quorum_plus_one(self): + self._exec('rabbitmq-upgrade', ['await_online_quorum_plus_one']) + self.result['changed'] = True + + def await_online_synchronized_mirror(self): + self._exec('rabbitmq-upgrade', ['await_online_synchronized_mirror']) + self.result['changed'] = True + + def post_upgrade(self): + self._exec('rabbitmq-upgrade', ['post_upgrade']) + self.result['changed'] = True + + +def main(): + arg_spec = dict( + action=dict( + choices=['await_online_quorum_plus_one', 'await_online_synchronized_mirror', 'post_upgrade', 'drain', 'revive'], + required=True), + node=dict(type='str', default='rabbit') + ) + module = AnsibleModule( + argument_spec=arg_spec, + supports_check_mode=True + ) + + action = module.params['action'] + node = module.params['node'] + result = dict(changed=False) + + rabbitmq_upgrade = RabbitMqUpgrade(module, action, node, result) + + getattr(rabbitmq_upgrade, action)() + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_user.py b/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_user.py new file mode 100644 index 000000000..87d6864fc --- /dev/null +++ b/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_user.py @@ -0,0 +1,567 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2013, Chatham Financial <oss@chathamfinancial.com> +# 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''' +--- +module: rabbitmq_user +short_description: Manage RabbitMQ users +description: + - Add or remove users to RabbitMQ and assign permissions +author: Chris Hoffman (@chrishoffman) +options: + user: + description: + - Name of user to add + type: str + required: true + aliases: [username, name] + password: + description: + - Password of user to add. + - To change the password of an existing user, you must also specify + C(update_password=always). + type: str + tags: + description: + - User tags specified as comma delimited + type: str + permissions: + description: + - a list of dicts, each dict contains vhost, configure_priv, write_priv, and read_priv, + and represents a permission rule for that vhost. + - This option should be preferable when you care about all permissions of the user. + - You should use vhost, configure_priv, write_priv, and read_priv options instead + if you care about permissions for just some vhosts. + type: list + elements: dict + default: [] + vhost: + description: + - vhost to apply access privileges. + - This option will be ignored when permissions option is used. + type: str + default: / + node: + description: + - erlang node name of the rabbit we wish to configure + type: str + default: rabbit + configure_priv: + description: + - Regular expression to restrict configure actions on a resource + for the specified vhost. + - By default all actions are restricted. + - This option will be ignored when permissions option is used. + type: str + default: '^$' + write_priv: + description: + - Regular expression to restrict configure actions on a resource + for the specified vhost. + - By default all actions are restricted. + - This option will be ignored when permissions option is used. + type: str + default: '^$' + read_priv: + description: + - Regular expression to restrict configure actions on a resource + for the specified vhost. + - By default all actions are restricted. + - This option will be ignored when permissions option is used. + type: str + default: '^$' + topic_permissions: + description: + - A list of dicts, each dict contains vhost, exchange, read_priv and write_priv, + and represents a topic permission rule for that vhost. + - By default vhost is C(/) and exchange is C(amq.topic). + - Supported since RabbitMQ 3.7.0. If RabbitMQ is older and topic_permissions are + set, the module will fail. + type: list + elements: dict + default: [] + version_added: '1.2.0' + force: + description: + - Deletes and recreates the user. + type: bool + default: false + state: + description: + - Specify if user is to be added or removed + type: str + default: present + choices: ['present', 'absent'] + update_password: + description: + - C(on_create) will only set the password for newly created users. C(always) will update passwords if they differ. + type: str + required: false + default: on_create + choices: ['on_create', 'always'] +''' + +EXAMPLES = r''' +- name: |- + Add user to server and assign full access control on / vhost. + The user might have permission rules for other vhost but you don't care. + community.rabbitmq.rabbitmq_user: + user: joe + password: changeme + vhost: / + configure_priv: .* + read_priv: .* + write_priv: .* + state: present + +- name: |- + Add user to server and assign full access control on / vhost. + The user doesn't have permission rules for other vhosts + community.rabbitmq.rabbitmq_user: + user: joe + password: changeme + permissions: + - vhost: / + configure_priv: .* + read_priv: .* + write_priv: .* + state: present + +- name: |- + Add user to server and assign some topic permissions on / vhost. + The user doesn't have topic permission rules for other vhosts + community.rabbitmq.rabbitmq_user: + user: joe + password: changeme + topic_permissions: + - vhost: / + exchange: amq.topic + read_priv: .* + write_priv: 'prod\\.logging\\..*' + state: present +''' + +import ansible_collections.community.rabbitmq.plugins.module_utils.version as Version +import json +import re + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.common.collections import count + + +def normalized_permissions(vhost_permission_list): + """Older versions of RabbitMQ output permissions with slightly different names. + + In older versions of RabbitMQ, the names of the permissions had the `_priv` suffix, which was removed in versions + >= 3.7.6. For simplicity we only check the `configure` permission. If it's in the old format then all the other + ones will be wrong too. + """ + for vhost_permission in vhost_permission_list: + if 'configure_priv' in vhost_permission: + yield { + 'configure': vhost_permission['configure_priv'], + 'read': vhost_permission['read_priv'], + 'write': vhost_permission['write_priv'], + 'vhost': vhost_permission['vhost'] + } + else: + yield vhost_permission + + +def as_permission_dict(vhost_permission_list): + return dict([(vhost_permission['vhost'], vhost_permission) for vhost_permission + in normalized_permissions(vhost_permission_list)]) + + +def as_topic_permission_dict(topic_permission_list): + return dict([((perm['vhost'], perm['exchange']), perm) for perm + in topic_permission_list]) + + +def only(vhost, vhost_permissions): + return {vhost: vhost_permissions.get(vhost, {})} + + +def first(iterable): + return next(iter(iterable)) + + +class RabbitMqUser(object): + def __init__(self, module, username, password, tags, permissions, + topic_permissions, node, bulk_permissions=False): + self.module = module + self.username = username + self.password = password or '' + self.node = node + self.tags = list() if not tags else tags.replace(' ', '').split(',') + self.permissions = as_permission_dict(permissions) + self.topic_permissions = as_topic_permission_dict(topic_permissions) + self.bulk_permissions = bulk_permissions + + self.existing_tags = None + self.existing_permissions = dict() + self.existing_topic_permissions = dict() + self._rabbitmqctl = module.get_bin_path('rabbitmqctl', True) + self._version = self._check_version() + + def _check_version(self): + """Get the version of the RabbitMQ server.""" + version = self._rabbitmq_version_post_3_7(fail_on_error=False) + if not version: + version = self._rabbitmq_version_pre_3_7(fail_on_error=False) + if not version: + self.module.fail_json(msg="Could not determine the version of the RabbitMQ server.") + return version + + def _fail(self, msg, stop_execution=False): + if stop_execution: + self.module.fail_json(msg=msg) + # This is a dummy return to prevent linters from throwing errors. + return None + + def _rabbitmq_version_post_3_7(self, fail_on_error=False): + """Use the JSON formatter to get a machine readable output of the version. + + At this point we do not know which RabbitMQ server version we are dealing with and which + version of `rabbitmqctl` we are using, so we will try to use the JSON formatter and see + what happens. In some versions of + """ + def int_list_to_str(ints): + return ''.join([chr(i) for i in ints]) + + rc, output, err = self._exec(['status', '--formatter', 'json'], check_rc=False) + if rc != 0: + return self._fail(msg="Could not parse the version of the RabbitMQ server, " + "because `rabbitmqctl status` returned no output.", + stop_execution=fail_on_error) + try: + status_json = json.loads(output) + if 'rabbitmq_version' in status_json: + return Version.StrictVersion(status_json['rabbitmq_version']) + for application in status_json.get('running_applications', list()): + if application[0] == 'rabbit': + if isinstance(application[1][0], int): + return Version.StrictVersion(int_list_to_str(application[2])) + else: + return Version.StrictVersion(application[1]) + return self._fail(msg="Could not find RabbitMQ version of `rabbitmqctl status` command.", + stop_execution=fail_on_error) + except ValueError as e: + return self._fail(msg="Could not parse output of `rabbitmqctl status` as JSON: {exc}.".format(exc=repr(e)), + stop_execution=fail_on_error) + + def _rabbitmq_version_pre_3_7(self, fail_on_error=False): + """Get the version of the RabbitMQ Server. + + Before version 3.7.6 the `rabbitmqctl` utility did not support the + `--formatter` flag, so the output has to be parsed using regular expressions. + """ + version_reg_ex = r"{rabbit,\"RabbitMQ\",\"([0-9]+\.[0-9]+\.[0-9]+)\"}" + rc, output, err = self._exec(['status'], check_rc=False) + if rc != 0: + if fail_on_error: + self.module.fail_json(msg="Could not parse the version of the RabbitMQ server, because" + " `rabbitmqctl status` returned no output.") + else: + return None + reg_ex_res = re.search(version_reg_ex, output, re.IGNORECASE) + if not reg_ex_res: + return self._fail(msg="Could not parse the version of the RabbitMQ server from the output of " + "`rabbitmqctl status` command: {output}.".format(output=output), + stop_execution=fail_on_error) + try: + return Version.StrictVersion(reg_ex_res.group(1)) + except ValueError as e: + return self._fail(msg="Could not parse the version of the RabbitMQ server: {exc}.".format(exc=repr(e)), + stop_execution=fail_on_error) + + def _exec(self, args, check_rc=True): + """Execute a command using the `rabbitmqctl` utility. + + By default the _exec call will cause the module to fail, if the error code is non-zero. If the `check_rc` + flag is set to False, then the exit_code, stdout and stderr will be returned to the calling function to + perform whatever error handling it needs. + + :param args: the arguments to pass to the `rabbitmqctl` utility + :param check_rc: when set to True, fail if the utility's exit code is non-zero + :return: the output of the command or all the outputs plus the error code in case of error + """ + cmd = [self._rabbitmqctl, '-q'] + if self.node: + cmd.extend(['-n', self.node]) + rc, out, err = self.module.run_command(cmd + args) + if check_rc and rc != 0: + # check_rc is not passed to the `run_command` method directly to allow for more fine grained checking of + # error messages returned by `rabbitmqctl`. + user_error_msg_regex = r"(Only root or .* .* run rabbitmqctl)" + user_error_msg = re.search(user_error_msg_regex, out) + if user_error_msg: + self.module.fail_json(msg="Wrong user used to run the `rabbitmqctl` utility: {err}" + .format(err=user_error_msg.group(1))) + else: + self.module.fail_json(msg="rabbitmqctl exited with non-zero code: {err}".format(err=err), + rc=rc, stdout=out) + return out if check_rc else (rc, out, err) + + def get(self): + """Retrieves the list of registered users from the node. + + If the user is already present, the node will also be queried for the user's permissions and topic + permissions. + If the version of the node is >= 3.7.6 the JSON formatter will be used, otherwise the plaintext will be + parsed. + """ + if self._version >= Version.StrictVersion('3.7.6'): + users = dict([(user_entry['user'], user_entry['tags']) + for user_entry in json.loads(self._exec(['list_users', '--formatter', 'json']))]) + else: + users = self._exec(['list_users']) + + def process_tags(tags): + if not tags: + return list() + return tags.replace('[', '').replace(']', '').replace(' ', '').strip('\t').split(',') + + users_and_tags = [user_entry.split('\t') for user_entry in users.strip().split('\n')] + + users = dict() + for user_parts in users_and_tags: + users[user_parts[0]] = process_tags(user_parts[1]) if len(user_parts) > 1 else [] + + self.existing_tags = users.get(self.username, list()) + self.existing_permissions = self._get_permissions() if self.username in users else dict() + self.existing_topic_permissions = self._get_topic_permissions() if self.username in users else dict() + return self.username in users + + def _get_permissions(self): + """Get permissions of the user from RabbitMQ.""" + if self._version >= Version.StrictVersion('3.7.6'): + permissions = json.loads(self._exec(['list_user_permissions', self.username, '--formatter', 'json'])) + else: + output = self._exec(['list_user_permissions', self.username]).strip().split('\n') + perms_out = [perm.split('\t') for perm in output if perm.strip()] + # Filter out headers from the output of the command in case they are still present + perms_out = [perm for perm in perms_out if perm != ["vhost", "configure", "write", "read"]] + + permissions = list() + for vhost, configure, write, read in perms_out: + permissions.append(dict(vhost=vhost, configure=configure, write=write, read=read)) + + if self.bulk_permissions: + return as_permission_dict(permissions) + else: + return only(first(self.permissions.keys()), as_permission_dict(permissions)) + + def _get_topic_permissions(self): + """Get topic permissions of the user from RabbitMQ.""" + if self._version < Version.StrictVersion('3.7.0'): + return dict() + if self._version >= Version.StrictVersion('3.7.6'): + permissions = json.loads(self._exec(['list_user_topic_permissions', self.username, '--formatter', 'json'])) + else: + output = self._exec(['list_user_topic_permissions', self.username]).strip().split('\n') + perms_out = [perm.split('\t') for perm in output if perm.strip()] + permissions = list() + for vhost, exchange, write, read in perms_out: + permissions.append(dict(vhost=vhost, exchange=exchange, write=write, read=read)) + return as_topic_permission_dict(permissions) + + def check_password(self): + """Return `True` if the user can authenticate successfully.""" + rc, out, err = self._exec(['authenticate_user', self.username, self.password], check_rc=False) + return rc == 0 + + def add(self): + self._exec(['add_user', self.username, self.password or '']) + if not self.password: + self._exec(['clear_password', self.username]) + + def delete(self): + self._exec(['delete_user', self.username]) + + def change_password(self): + if self.password: + self._exec(['change_password', self.username, self.password]) + else: + self._exec(['clear_password', self.username]) + + def set_tags(self): + self._exec(['set_user_tags', self.username] + self.tags) + + def set_permissions(self): + permissions_to_add = list() + for vhost, permission_dict in self.permissions.items(): + if permission_dict != self.existing_permissions.get(vhost, {}): + permissions_to_add.append(permission_dict) + + permissions_to_clear = list() + for vhost in self.existing_permissions.keys(): + if vhost not in self.permissions: + permissions_to_clear.append(vhost) + + for vhost in permissions_to_clear: + cmd = 'clear_permissions -p {vhost} {username}'.format(username=self.username, vhost=vhost) + self._exec(cmd.split(' ')) + for permissions in permissions_to_add: + cmd = ('set_permissions -p {vhost} {username} {configure} {write} {read}' + .format(username=self.username, **permissions)) + self._exec(cmd.split(' ')) + self.existing_permissions = self._get_permissions() + + def set_topic_permissions(self): + permissions_to_add = list() + for vhost_exchange, permission_dict in self.topic_permissions.items(): + if permission_dict != self.existing_topic_permissions.get(vhost_exchange, {}): + permissions_to_add.append(permission_dict) + + permissions_to_clear = list() + for vhost_exchange in self.existing_topic_permissions.keys(): + if vhost_exchange not in self.topic_permissions: + permissions_to_clear.append(vhost_exchange) + + for vhost_exchange in permissions_to_clear: + vhost, exchange = vhost_exchange + cmd = ('clear_topic_permissions -p {vhost} {username} {exchange}' + .format(username=self.username, vhost=vhost, exchange=exchange)) + self._exec(cmd.split(' ')) + for permissions in permissions_to_add: + cmd = ('set_topic_permissions -p {vhost} {username} {exchange} {write} {read}' + .format(username=self.username, **permissions)) + self._exec(cmd.split(' ')) + self.existing_topic_permissions = self._get_topic_permissions() + + def has_tags_modifications(self): + return set(self.tags) != set(self.existing_tags) + + def has_permissions_modifications(self): + return self.existing_permissions != self.permissions + + def has_topic_permissions_modifications(self): + return self.existing_topic_permissions != self.topic_permissions + + +def main(): + arg_spec = dict( + user=dict(required=True, aliases=['username', 'name']), + password=dict(default=None, no_log=True), + tags=dict(default=None), + permissions=dict(default=list(), type='list', elements='dict'), + vhost=dict(default='/'), + configure_priv=dict(default='^$'), + write_priv=dict(default='^$'), + read_priv=dict(default='^$'), + topic_permissions=dict(default=list(), type='list', elements='dict'), + force=dict(default='no', type='bool'), + state=dict(default='present', choices=['present', 'absent']), + node=dict(default='rabbit'), + update_password=dict(default='on_create', choices=['on_create', 'always'], no_log=False) + ) + module = AnsibleModule( + argument_spec=arg_spec, + supports_check_mode=False + ) + + username = module.params['user'] + password = module.params['password'] + tags = module.params['tags'] + permissions = module.params['permissions'] + vhost = module.params['vhost'] + configure_priv = module.params['configure_priv'] + write_priv = module.params['write_priv'] + read_priv = module.params['read_priv'] + topic_permissions = module.params['topic_permissions'] + force = module.params['force'] + state = module.params['state'] + node = module.params['node'] + update_password = module.params['update_password'] + + if permissions: + vhosts = [permission.get('vhost', '/') for permission in permissions] + if any(vhost_count > 1 for vhost_count in count(vhosts).values()): + module.fail_json(msg="Error parsing vhost permissions: You can't " + "have two permission dicts for the same vhost") + bulk_permissions = True + else: + perm = { + 'vhost': vhost, + 'configure_priv': configure_priv, + 'write_priv': write_priv, + 'read_priv': read_priv + } + permissions.append(perm) + bulk_permissions = False + + if topic_permissions: + vhost_exchanges = [ + (permission.get('vhost', '/'), permission.get('exchange')) + for permission in topic_permissions + ] + if any(ve_count > 1 for ve_count in count(vhost_exchanges).values()): + module.fail_json(msg="Error parsing vhost topic_permissions: You can't " + "have two topic permission dicts for the same vhost " + "and the same exchange") + + for permission in permissions: + if not permission['vhost']: + module.fail_json(msg="Error parsing vhost permissions: You can't" + "have an empty vhost when setting permissions") + + for permission in topic_permissions: + permission.setdefault('vhost', '/') + permission.setdefault('exchange', 'amq.topic') + # Normalize the arguments + for perm_name in ("read", "write"): + suffixed_perm_name = "{perm_name}_priv".format(perm_name=perm_name) + if suffixed_perm_name in permission: + permission[perm_name] = permission.pop(suffixed_perm_name) + + rabbitmq_user = RabbitMqUser(module, username, password, tags, permissions, + topic_permissions, node, + bulk_permissions=bulk_permissions) + + result = dict(changed=False, user=username, state=state) + if rabbitmq_user.get(): + if state == 'absent': + rabbitmq_user.delete() + result['changed'] = True + else: + if force: + rabbitmq_user.delete() + rabbitmq_user.add() + rabbitmq_user.get() + result['changed'] = True + elif update_password == 'always': + if not rabbitmq_user.check_password(): + rabbitmq_user.change_password() + result['changed'] = True + + if rabbitmq_user.has_tags_modifications(): + rabbitmq_user.set_tags() + result['changed'] = True + + if rabbitmq_user.has_permissions_modifications(): + rabbitmq_user.set_permissions() + result['changed'] = True + + if rabbitmq_user.has_topic_permissions_modifications(): + rabbitmq_user.set_topic_permissions() + result['changed'] = True + elif state == 'present': + rabbitmq_user.add() + rabbitmq_user.set_tags() + rabbitmq_user.set_permissions() + rabbitmq_user.set_topic_permissions() + result['changed'] = True + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_user_limits.py b/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_user_limits.py new file mode 100644 index 000000000..9a92ad3fc --- /dev/null +++ b/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_user_limits.py @@ -0,0 +1,217 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2013, Chatham Financial <oss@chathamfinancial.com> +# 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''' +--- +module: rabbitmq_user_limits +short_description: Manage RabbitMQ user limits +description: + - Manage the state of user limits in RabbitMQ. Supported since RabbitMQ version 3.8.10. +author: Aitor Pazos (@aitorpazos) +version_added: '1.1.0' +options: + user: + description: + - Name of user to manage limits for. + type: str + required: true + aliases: [username, name] + max_connections: + description: + - Max number of concurrent client connections. + - Negative value means "no limit". + - Ignored when the I(state) is C(absent). + type: int + default: -1 + max_channels: + description: + - Max number of channels. + - Negative value means "no limit". + - Ignored when the I(state) is C(absent). + type: int + default: -1 + node: + description: + - Name of the RabbitMQ Erlang node to manage. + type: str + state: + description: + - Specify whether the limits are to be set or cleared. + - If set to C(absent), the limits of both I(max_connections) and I(max_channels) will be cleared. + type: str + default: present + choices: [present, absent] +notes: + - Supports C(check_mode). +''' + +EXAMPLES = r''' +- name: Limit both of the max number of connections and channels on the user 'guest'. + community.rabbitmq.rabbitmq_user_limits: + user: guest + max_connections: 64 + max_channels: 256 + state: present + +# This task implicitly clears the max number of channels limit using default value: -1. +- name: Limit the max number of connections on the user 'guest'. + community.rabbitmq.rabbitmq_user_limits: + user: guest + max_connections: 64 + state: present + +- name: Clear the limits on the user 'guest'. + community.rabbitmq.rabbitmq_user_limits: + user: guest + state: absent +''' + +RETURN = r''' # ''' + +import json +import re +from ansible_collections.community.rabbitmq.plugins.module_utils.version import LooseVersion as Version +from ansible.module_utils.basic import AnsibleModule + + +class RabbitMqUserLimits(object): + def __init__(self, module): + self._module = module + self._max_connections = module.params['max_connections'] + self._max_channels = module.params['max_channels'] + self._node = module.params['node'] + self._state = module.params['state'] + self._user = module.params['user'] + self._rabbitmqctl = module.get_bin_path('rabbitmqctl', True) + + self._version = self._rabbit_version() + + def _exec(self, + args, + force_exec_in_check_mode=False): + + if not self._module.check_mode or (self._module.check_mode and force_exec_in_check_mode): + cmd = [self._rabbitmqctl, '-q'] + if self._node is not None: + cmd.extend(['-n', self._node]) + rc, out, err = self._module.run_command(cmd + args, check_rc=True) + + return out + return "" + + def _rabbit_version(self): + status = self._exec(['status'], True) + + # 3.7.x erlang style output + version_match = re.search('{rabbit,".*","(?P<version>.*)"}', status) + if version_match: + return Version(version_match.group('version')) + + # 3.8.x style output + version_match = re.search('RabbitMQ version: (?P<version>.*)', status) + if version_match: + return Version(version_match.group('version')) + + return None + + def _assert_version(self): + if self._version and self._version < Version('3.8.10'): + self._module.fail_json(changed=False, + msg="User limits are only available for RabbitMQ >= 3.8.10. Detected version: %s" % self._version) + + def list(self): + self._assert_version() + + exec_result = self._exec(['list_user_limits', '--user', self._user], False) + max_connections = None + max_channels = None + if exec_result: + user_limits = json.loads(exec_result) + if 'max-connections' in user_limits: + max_connections = user_limits['max-connections'] + if 'max-channels' in user_limits: + max_channels = user_limits['max-channels'] + return dict( + max_connections=max_connections, + max_channels=max_channels + ) + + def set(self): + self._assert_version() + + if self._module.check_mode: + return + + if self._max_connections != -1: + json_str = '{{"max-connections": {0}}}'.format(self._max_connections) + self._exec(['set_user_limits', self._user, json_str]) + else: + self._exec(['clear_user_limits', self._user, "max-connections"]) + + if self._max_channels != -1: + json_str = '{{"max-channels": {0}}}'.format(self._max_channels) + self._exec(['set_user_limits', self._user, json_str]) + else: + self._exec(['clear_user_limits', self._user, "max-channels"]) + + def clear(self): + self._assert_version() + + if self._module.check_mode: + return + + return self._exec(['clear_user_limits', self._user, 'all']) + + +def main(): + arg_spec = dict( + user=dict(required=True, type='str', aliases=['username', 'name']), + max_connections=dict(default=-1, type='int'), + max_channels=dict(default=-1, type='int'), + state=dict(default='present', choices=['present', 'absent'], type='str'), + node=dict(default=None, type='str') + ) + + module = AnsibleModule( + argument_spec=arg_spec, + supports_check_mode=True + ) + + max_connections = module.params['max_connections'] + max_channels = module.params['max_channels'] + state = module.params['state'] + + module_result = dict(changed=False) + rabbitmq_user_limits = RabbitMqUserLimits(module) + current_status = rabbitmq_user_limits.list() + + if state == 'present': + wanted_status = dict( + max_connections=max_connections, + max_channels=max_channels + ) + else: # state == 'absent' + wanted_status = dict( + max_connections=None, + max_channels=None + ) + + if current_status != wanted_status: + module_result['changed'] = True + if state == 'present': + rabbitmq_user_limits.set() + else: # state == 'absent' + rabbitmq_user_limits.clear() + + module.exit_json(**module_result) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_vhost.py b/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_vhost.py new file mode 100644 index 000000000..cfcecc6a5 --- /dev/null +++ b/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_vhost.py @@ -0,0 +1,142 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2013, Chatham Financial <oss@chathamfinancial.com> +# 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''' +--- +module: rabbitmq_vhost +short_description: Manage the state of a virtual host in RabbitMQ +description: + - Manage the state of a virtual host in RabbitMQ +author: Chris Hoffman (@chrishoffman) +options: + name: + description: + - The name of the vhost to manage + type: str + required: true + aliases: [vhost] + node: + description: + - erlang node name of the rabbit we wish to configure + type: str + default: rabbit + tracing: + description: + - Enable/disable tracing for a vhost + type: bool + default: false + aliases: [trace] + state: + description: + - The state of vhost + type: str + default: present + choices: [present, absent] +''' + +EXAMPLES = r''' +- name: Ensure that the vhost /test exists. + community.rabbitmq.rabbitmq_vhost: + name: /test + state: present +''' + +from ansible.module_utils.basic import AnsibleModule + + +class RabbitMqVhost(object): + def __init__(self, module, name, tracing, node): + self.module = module + self.name = name + self.tracing = tracing + self.node = node + + self._tracing = False + self._rabbitmqctl = module.get_bin_path('rabbitmqctl', True) + + def _exec(self, args, force_exec_in_check_mode=False): + if not self.module.check_mode or (self.module.check_mode and force_exec_in_check_mode): + cmd = [self._rabbitmqctl, '-q', '-n', self.node] + rc, out, err = self.module.run_command(cmd + args, check_rc=True) + return out.splitlines() + return list() + + def get(self): + vhosts = self._exec(['list_vhosts', 'name', 'tracing'], True) + + for vhost in vhosts: + if '\t' not in vhost: + continue + + name, tracing = vhost.split('\t') + if name == self.name: + self._tracing = self.module.boolean(tracing) + return True + return False + + def add(self): + return self._exec(['add_vhost', self.name]) + + def delete(self): + return self._exec(['delete_vhost', self.name]) + + def set_tracing(self): + if self.tracing != self._tracing: + if self.tracing: + self._enable_tracing() + else: + self._disable_tracing() + return True + return False + + def _enable_tracing(self): + return self._exec(['trace_on', '-p', self.name]) + + def _disable_tracing(self): + return self._exec(['trace_off', '-p', self.name]) + + +def main(): + arg_spec = dict( + name=dict(required=True, aliases=['vhost']), + tracing=dict(default='off', aliases=['trace'], type='bool'), + state=dict(default='present', choices=['present', 'absent']), + node=dict(default='rabbit'), + ) + + module = AnsibleModule( + argument_spec=arg_spec, + supports_check_mode=True + ) + + name = module.params['name'] + tracing = module.params['tracing'] + state = module.params['state'] + node = module.params['node'] + result = dict(changed=False, name=name, state=state) + rabbitmq_vhost = RabbitMqVhost(module, name, tracing, node) + + if rabbitmq_vhost.get(): + if state == 'absent': + rabbitmq_vhost.delete() + result['changed'] = True + else: + if rabbitmq_vhost.set_tracing(): + result['changed'] = True + elif state == 'present': + rabbitmq_vhost.add() + rabbitmq_vhost.set_tracing() + result['changed'] = True + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_vhost_limits.py b/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_vhost_limits.py new file mode 100644 index 000000000..e2f369606 --- /dev/null +++ b/ansible_collections/community/rabbitmq/plugins/modules/rabbitmq_vhost_limits.py @@ -0,0 +1,174 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2018, Hiroyuki Matsuo <h.matsuo.engineer@gmail.com> +# 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''' +--- +module: rabbitmq_vhost_limits +author: Hiroyuki Matsuo (@h-matsuo) + +short_description: Manage the state of virtual host limits in RabbitMQ +description: + - This module sets/clears certain limits on a virtual host. + - The configurable limits are I(max_connections) and I(max-queues). + +options: + max_connections: + description: + - Max number of concurrent client connections. + - Negative value means "no limit". + - Ignored when the I(state) is C(absent). + type: int + default: -1 + max_queues: + description: + - Max number of queues. + - Negative value means "no limit". + - Ignored when the I(state) is C(absent). + type: int + default: -1 + node: + description: + - Name of the RabbitMQ Erlang node to manage. + type: str + state: + description: + - Specify whether the limits are to be set or cleared. + - If set to C(absent), the limits of both I(max_connections) and I(max-queues) will be cleared. + type: str + default: present + choices: [present, absent] + vhost: + description: + - Name of the virtual host to manage. + type: str + default: / +''' + +EXAMPLES = r''' +- name: Limit both of the max number of connections and queues on the vhost '/'. + community.rabbitmq.rabbitmq_vhost_limits: + vhost: / + max_connections: 64 + max_queues: 256 + state: present + +- name: |- + Limit the max number of connections on the vhost '/'. + This task implicitly clears the max number of queues limit using default value: -1. + community.rabbitmq.rabbitmq_vhost_limits: + vhost: / + max_connections: 64 + state: present + +- name: Clear the limits on the vhost '/'. + community.rabbitmq.rabbitmq_vhost_limits: + vhost: / + state: absent +''' + +RETURN = r''' # ''' + + +import json +from ansible.module_utils.basic import AnsibleModule + + +class RabbitMqVhostLimits(object): + def __init__(self, module): + self._module = module + self._max_connections = module.params['max_connections'] + self._max_queues = module.params['max_queues'] + self._node = module.params['node'] + self._state = module.params['state'] + self._vhost = module.params['vhost'] + self._rabbitmqctl = module.get_bin_path('rabbitmqctl', True) + + def _exec(self, args): + cmd = [self._rabbitmqctl, '-q', '-p', self._vhost] + if self._node is not None: + cmd.extend(['-n', self._node]) + rc, out, err = self._module.run_command(cmd + args, check_rc=True) + return dict(rc=rc, out=out.splitlines(), err=err.splitlines()) + + def list(self): + exec_result = self._exec(['list_vhost_limits']) + vhost_limits = exec_result['out'][0] + max_connections = None + max_queues = None + if vhost_limits: + vhost_limits = json.loads(vhost_limits) + if 'max-connections' in vhost_limits: + max_connections = vhost_limits['max-connections'] + if 'max-queues' in vhost_limits: + max_queues = vhost_limits['max-queues'] + return dict( + max_connections=max_connections, + max_queues=max_queues + ) + + def set(self): + if self._module.check_mode: + return + json_str = '{{"max-connections": {0}, "max-queues": {1}}}'.format(self._max_connections, self._max_queues) + self._exec(['set_vhost_limits', json_str]) + + def clear(self): + if self._module.check_mode: + return + self._exec(['clear_vhost_limits']) + + +def main(): + arg_spec = dict( + max_connections=dict(default=-1, type='int'), + max_queues=dict(default=-1, type='int'), + node=dict(default=None, type='str'), + state=dict(default='present', choices=['present', 'absent'], type='str'), + vhost=dict(default='/', type='str') + ) + + module = AnsibleModule( + argument_spec=arg_spec, + supports_check_mode=True + ) + + max_connections = module.params['max_connections'] + max_queues = module.params['max_queues'] + node = module.params['node'] + state = module.params['state'] + vhost = module.params['vhost'] + + module_result = dict(changed=False) + rabbitmq_vhost_limits = RabbitMqVhostLimits(module) + current_status = rabbitmq_vhost_limits.list() + + if state == 'present': + wanted_status = dict( + max_connections=max_connections, + max_queues=max_queues + ) + else: # state == 'absent' + wanted_status = dict( + max_connections=None, + max_queues=None + ) + + if current_status != wanted_status: + module_result['changed'] = True + if state == 'present': + rabbitmq_vhost_limits.set() + else: # state == 'absent' + rabbitmq_vhost_limits.clear() + + module.exit_json(**module_result) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/lookup_rabbitmq/aliases b/ansible_collections/community/rabbitmq/tests/integration/targets/lookup_rabbitmq/aliases new file mode 100644 index 000000000..ea7b5233d --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/lookup_rabbitmq/aliases @@ -0,0 +1,7 @@ +destructive +shippable/posix/group1 +skip/aix +skip/osx +skip/freebsd +skip/rhel +skip/python2.6 # lookups are controller only, and we no longer support Python 2.6 on the controller diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/lookup_rabbitmq/meta/main.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/lookup_rabbitmq/meta/main.yml new file mode 100644 index 000000000..05ab59000 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/lookup_rabbitmq/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_rabbitmq diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/lookup_rabbitmq/tasks/main.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/lookup_rabbitmq/tasks/main.yml new file mode 100644 index 000000000..740f89980 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/lookup_rabbitmq/tasks/main.yml @@ -0,0 +1,5 @@ +# Rabbitmq lookup +- include: ubuntu.yml + when: + - ansible_distribution == 'Ubuntu' + - ansible_distribution_release != 'trusty' diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/lookup_rabbitmq/tasks/ubuntu.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/lookup_rabbitmq/tasks/ubuntu.yml new file mode 100644 index 000000000..98f0a0217 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/lookup_rabbitmq/tasks/ubuntu.yml @@ -0,0 +1,152 @@ +- name: Test failure without pika installed + set_fact: + rabbit_missing_pika: "{{ lookup('community.rabbitmq.rabbitmq', url='amqp://guest:guest@localhost:5672/%2F', queue='hello', count=3) }}" + ignore_errors: yes + register: rabbitmq_missing_pika_error + delegate_to: localhost + +- assert: + that: + - "'pika python package is required' in rabbitmq_missing_pika_error.msg" + +- name: Install pika and requests + pip: + name: pika==1.3.0,requests + state: latest + delegate_to: localhost + +- name: Test that giving an incorrect amqp protocol in URL will error + set_fact: + rabbitmq_test_protocol: "{{ lookup('community.rabbitmq.rabbitmq', url='zzzamqp://guest:guest@localhost:5672/%2F', queue='hello', count=3) }}" + ignore_errors: yes + register: rabbitmq_protocol_error + delegate_to: localhost + +- assert: + that: + - "rabbitmq_protocol_error is failed" + - "'URL malformed' in rabbitmq_protocol_error.msg" + +- name: Test that giving an incorrect IP address in URL will error + set_fact: + rabbitmq_test_protocol: "{{ lookup('community.rabbitmq.rabbitmq', url='amqp://guest:guest@xxxxx192.112312368.250.1:5672/%2F', queue='hello', count=3) }}" + ignore_errors: yes + register: rabbitmq_ip_error + delegate_to: localhost + +- assert: + that: + - "rabbitmq_ip_error is failed" + - "'Connection issue' in rabbitmq_ip_error.msg" + +- name: Test missing parameters will error + set_fact: + rabbitmq_test_protocol: "{{ lookup('community.rabbitmq.rabbitmq') }}" + ignore_errors: yes + register: rabbitmq_params_error + delegate_to: localhost + +- assert: + that: + - "rabbitmq_params_error is failed" + - "'URL is required for rabbitmq lookup.' in rabbitmq_params_error.msg" + +- name: Test missing queue will error + set_fact: + rabbitmq_queue_protocol: "{{ lookup('community.rabbitmq.rabbitmq', url='amqp://guest:guest@localhost:5672/%2F') }}" + ignore_errors: yes + register: rabbitmq_queue_error + delegate_to: localhost + +- assert: + that: + - "rabbitmq_queue_error is failed" + - "'Queue is required for rabbitmq lookup' in rabbitmq_queue_error.msg" + +- name: Enables the rabbitmq_management plugin + rabbitmq_plugin: + names: rabbitmq_management + state: enabled + delegate_to: localhost + +- name: Setup test queue + rabbitmq_queue: + name: hello + delegate_to: localhost + +- name: Post test message to the exchange (string) + uri: + url: http://localhost:15672/api/exchanges/%2f/amq.default/publish + method: POST + body: '{"properties":{},"routing_key":"hello","payload":"ansible-test","payload_encoding":"string"}' + user: guest + password: guest + force_basic_auth: yes + return_content: yes + headers: + Content-Type: "application/json" + register: post_data + delegate_to: localhost + + +- name: Post test message to the exchange (json) + uri: + url: http://localhost:15672/api/exchanges/%2f/amq.default/publish + method: POST + body: '{"properties":{"content_type": "application/json"},"routing_key":"hello","payload":"{\"key\": \"value\" }","payload_encoding":"string"}' + user: guest + password: guest + force_basic_auth: yes + return_content: yes + headers: + Content-Type: "application/json" + register: post_data_json + delegate_to: localhost + +- name: Test retrieve messages + set_fact: + rabbitmq_msg: "{{ lookup('community.rabbitmq.rabbitmq', url='amqp://guest:guest@localhost:5672/%2f/hello', queue='hello') }}" + ignore_errors: yes + register: rabbitmq_msg_error + delegate_to: localhost + +- name: Ensure two messages received + assert: + that: + - "rabbitmq_msg_error is not failed" + - rabbitmq_msg | length == 2 + +- name: Ensure first message is a string + assert: + that: + - rabbitmq_msg[0].msg == "ansible-test" + +- name: Ensure second message is json + assert: + that: + - rabbitmq_msg[1].json.key == "value" + +- name: Test missing vhost + set_fact: + rabbitmq_msg: "{{ lookup('community.rabbitmq.rabbitmq', url='amqp://guest:guest@localhost:5672/missing/', queue='hello') }}" + ignore_errors: yes + register: rabbitmq_vhost_error + delegate_to: localhost + +- assert: + that: + - "rabbitmq_vhost_error is failed" + - ("'NOT_ALLOWED' in rabbitmq_vhost_error.msg") or ("'Connection issue' in rabbitmq_vhost_error.msg") + +# Tidy up +- name: Uninstall pika and requests + pip: + name: pika,requests + state: absent + delegate_to: localhost + +- name: Disable the rabbitmq_management plugin + rabbitmq_plugin: + names: rabbitmq_management + state: disabled + delegate_to: localhost diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_binding/aliases b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_binding/aliases new file mode 100644 index 000000000..f37e6c6fb --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_binding/aliases @@ -0,0 +1,6 @@ +destructive +shippable/posix/group1 +skip/aix +skip/osx +skip/freebsd +skip/rhel diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_binding/meta/main.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_binding/meta/main.yml new file mode 100644 index 000000000..05ab59000 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_binding/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_rabbitmq diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_binding/tasks/main.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_binding/tasks/main.yml new file mode 100644 index 000000000..4d3414ead --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_binding/tasks/main.yml @@ -0,0 +1,3 @@ +--- +- import_tasks: tests.yml + when: ansible_distribution == 'Ubuntu' diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_binding/tasks/tests.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_binding/tasks/tests.yml new file mode 100644 index 000000000..5021ed225 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_binding/tasks/tests.yml @@ -0,0 +1,132 @@ +--- +- name: Add test requisites + block: + - name: Add exchange + rabbitmq_exchange: + name: "{{ item }}" + type: direct + with_items: + - exchange-foo + - exchange-bar + + - name: Add queue + rabbitmq_queue: + name: queue-foo + +- name: Test add binding in check mode + block: + - name: Add binding + rabbitmq_binding: + source: exchange-foo + destination: queue-foo + type: queue + check_mode: true + register: add_binding + + - name: Check that binding succeeds with a change + assert: + that: + - add_binding.changed == true + +- name: Test add binding + block: + - name: Add binding + rabbitmq_binding: + source: exchange-foo + destination: queue-foo + type: queue + register: add_binding + + - name: Check that binding succeeds with a change + assert: + that: + - add_binding.changed == true + +- name: Test add binding idempotence + block: + - name: Add binding + rabbitmq_binding: + source: exchange-foo + destination: queue-foo + type: queue + register: add_binding + + - name: Check that binding succeeds without a change + assert: + that: + - add_binding.changed == false + +- name: Test remove binding in check mode + block: + - name: Remove binding + rabbitmq_binding: + source: exchange-foo + destination: queue-foo + type: queue + state: absent + check_mode: true + register: remove_binding + + - name: Check that binding succeeds with a change + assert: + that: + - remove_binding.changed == true + +- name: Test remove binding + block: + - name: Remove binding + rabbitmq_binding: + source: exchange-foo + destination: queue-foo + type: queue + state: absent + register: remove_binding + + - name: Check that binding succeeds with a change + assert: + that: + - remove_binding.changed == true + +- name: Test remove binding idempotence + block: + - name: Remove binding + rabbitmq_binding: + source: exchange-foo + destination: queue-foo + type: queue + state: absent + register: remove_binding + + - name: Check that binding succeeds with a change + assert: + that: + - remove_binding.changed == false + +- name: Test add exchange to exchange binding + block: + - name: Add binding + rabbitmq_binding: + source: exchange-foo + destination: exchange-bar + type: exchange + register: add_binding + + - name: Check that binding succeeds with a change + assert: + that: + - add_binding.changed == true + +- name: Test remove exchange to exchange binding + block: + - name: Remove binding + rabbitmq_binding: + source: exchange-foo + destination: exchange-bar + type: exchange + state: absent + register: remove_binding + + - name: Check that binding succeeds with a change + assert: + that: + - remove_binding.changed == true diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_exchange/meta/main.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_exchange/meta/main.yml new file mode 100644 index 000000000..f0eb571b9 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_exchange/meta/main.yml @@ -0,0 +1,3 @@ +dependencies: + - setup_rabbitmq + - setup_remote_tmp_dir diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_exchange/tasks/main.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_exchange/tasks/main.yml new file mode 100644 index 000000000..8a826ca3c --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_exchange/tasks/main.yml @@ -0,0 +1,31 @@ +# Note: these tests are just for the 3 plugin exchange types. Additional +# rabbitmq_exchange tests occur within some of the other module tests. +- name: Fail creating an x-random exchange type + community.rabbitmq.rabbitmq_exchange: + name: test_x_random_exchange + exchange_type: "x-random" + register: fail_x_random_exchange + ignore_errors: true + +- name: Ensure x_random exchange failed when plugin not enabled + assert: + that: + - fail_x_random_exchange is failed + - "'You may need to enable the' in fail_x_random_exchange.msg" + +- name: Enable x-random exchange plugin + community.rabbitmq.rabbitmq_plugin: + names: rabbitmq_random_exchange + new_only: yes + state: enabled + +- name: Succeed creating an x-random exchange type + community.rabbitmq.rabbitmq_exchange: + name: test_x_random_exchange + exchange_type: "x-random" + register: success_x_random_exchange + +- name: Ensure x_random exchange success when plugin enabled + assert: + that: + - success_x_random_exchange is not failed diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_feature_flag/aliases b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_feature_flag/aliases new file mode 100644 index 000000000..f37e6c6fb --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_feature_flag/aliases @@ -0,0 +1,6 @@ +destructive +shippable/posix/group1 +skip/aix +skip/osx +skip/freebsd +skip/rhel diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_feature_flag/meta/main.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_feature_flag/meta/main.yml new file mode 100644 index 000000000..05ab59000 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_feature_flag/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_rabbitmq diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_feature_flag/tasks/main.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_feature_flag/tasks/main.yml new file mode 100644 index 000000000..593906fb7 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_feature_flag/tasks/main.yml @@ -0,0 +1,2 @@ +- import_tasks: tests.yml + when: ansible_distribution == 'Ubuntu' diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_feature_flag/tasks/tests.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_feature_flag/tasks/tests.yml new file mode 100644 index 000000000..c531853e0 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_feature_flag/tasks/tests.yml @@ -0,0 +1,61 @@ +- block: + - set_fact: + parameter_name: maintenance_mode_status + parameter_node: rabbit + + - name: Enable feature flag (check_mode) + rabbitmq_feature_flag: + name: "{{ parameter_name }}" + node: "{{ parameter_node }}" + register: result_check_mode + check_mode: yes + + - name: Check if feature_flag module was completed successfully (check_mode) + assert: + that: + - result_check_mode is success + + - name: Enable feature flag + rabbitmq_feature_flag: + name: "{{ parameter_name }}" + node: "{{ parameter_node }}" + register: result + + - name: Check if feature_flag module was completed successfully + assert: + that: + - result is success + + - name: Ensure that module reported the same 'changed' value with or without check mode + assert: + that: + - result.changed == result_check_mode.changed + + - name: Check if specified feature flag is enabled + shell: "rabbitmqctl -q list_feature_flags | grep {{ parameter_name }}" + register: ctl_result + + - name: Idempotent - Enable feature flag (check_mode) + rabbitmq_feature_flag: + name: "{{ parameter_name }}" + node: "{{ parameter_node }}" + register: result_check_mode + check_mode: yes + + - name: Idempotent - Check if feature_flag module was completed successfully (check_mode) + assert: + that: + - result_check_mode is success + - result_check_mode is not changed + + - name: Idempotent - Enable feature flag + rabbitmq_feature_flag: + name: "{{ parameter_name }}" + node: "{{ parameter_node }}" + register: result + + - name: Idempotent - Check if feature_flag module was completed successfully + assert: + that: + - result is success + - result is not changed diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_global_parameter/aliases b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_global_parameter/aliases new file mode 100644 index 000000000..f37e6c6fb --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_global_parameter/aliases @@ -0,0 +1,6 @@ +destructive +shippable/posix/group1 +skip/aix +skip/osx +skip/freebsd +skip/rhel diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_global_parameter/meta/main.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_global_parameter/meta/main.yml new file mode 100644 index 000000000..05ab59000 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_global_parameter/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_rabbitmq diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_global_parameter/tasks/main.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_global_parameter/tasks/main.yml new file mode 100644 index 000000000..593906fb7 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_global_parameter/tasks/main.yml @@ -0,0 +1,2 @@ +- import_tasks: tests.yml + when: ansible_distribution == 'Ubuntu' diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_global_parameter/tasks/tests.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_global_parameter/tasks/tests.yml new file mode 100644 index 000000000..d689f0b25 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_global_parameter/tasks/tests.yml @@ -0,0 +1,67 @@ +- block: + - set_fact: + parameter_name: cluster_name + parameter_value: "{{ 'integration-test' | to_json }}" + + - name: Add global parameter + rabbitmq_global_parameter: + name: "{{ parameter_name }}" + value: "{{ parameter_value }}" + state: present + register: result + + - name: Check that the global parameter was created successfuly + shell: "rabbitmqctl -q list_global_parameters | grep {{ parameter_name }}" + register: ctl_result + + - name: Check that the global parameter is added + assert: + that: + - result is changed + - result is success + + - name: Add global parameter (idempotency) + rabbitmq_global_parameter: + name: "{{ parameter_name }}" + value: "{{ parameter_value }}" + state: present + register: result + + - name: Check idempotency + assert: + that: + - result is not changed + + - name: Remove global parameter + rabbitmq_global_parameter: + name: "{{ parameter_name }}" + state: absent + register: result + + - name: Get rabbitmqctl output + shell: "rabbitmqctl -q list_global_parameters | grep {{ parameter_name }}" + register: ctl_result + failed_when: ctl_result.rc == 0 + + - name: Check that the global parameter is removed + assert: + that: + - result is changed + - result is success + + - name: Remove global parameter (idempotency) + rabbitmq_global_parameter: + name: "{{ parameter_name }}" + state: absent + register: result + + - name: Check idempotency + assert: + that: + - result is not changed + + always: + - name: Remove global parameter + rabbitmq_global_parameter: + name: "{{ parameter_name }}" + state: absent diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_plugin/aliases b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_plugin/aliases new file mode 100644 index 000000000..f37e6c6fb --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_plugin/aliases @@ -0,0 +1,6 @@ +destructive +shippable/posix/group1 +skip/aix +skip/osx +skip/freebsd +skip/rhel diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_plugin/meta/main.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_plugin/meta/main.yml new file mode 100644 index 000000000..05ab59000 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_plugin/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_rabbitmq diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_plugin/tasks/main.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_plugin/tasks/main.yml new file mode 100644 index 000000000..593906fb7 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_plugin/tasks/main.yml @@ -0,0 +1,2 @@ +- import_tasks: tests.yml + when: ansible_distribution == 'Ubuntu' diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_plugin/tasks/tests.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_plugin/tasks/tests.yml new file mode 100644 index 000000000..148f1914d --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_plugin/tasks/tests.yml @@ -0,0 +1,137 @@ +- block: + - set_fact: + plugin_name: rabbitmq_top + + - name: Enable plugin [online] + rabbitmq_plugin: + name: "{{ plugin_name }}" + state: enabled + new_only: True + register: result + + - name: Get rabbitmq-plugins output + shell: "rabbitmq-plugins list | grep {{ plugin_name }}" + register: cli_result + + - name: Check that the plugin is enabled + assert: + that: + - result is changed + - result is success + - '"{{ plugin_name }}" in result.enabled' + - result.disabled == [] + - '"[E" in cli_result.stdout' + + - name: Enable plugin [online] (idempotency) + rabbitmq_plugin: + name: "{{ plugin_name }}" + state: enabled + new_only: True + register: result + + - name: Check idempotency + assert: + that: + - result is not changed + + - name: Disable plugin [online] + rabbitmq_plugin: + name: "{{ plugin_name }}" + state: disabled + register: result + + - name: Get rabbitmq-plugins output + shell: "rabbitmq-plugins list | grep {{ plugin_name }}" + register: cli_result + + - name: Check that the plugin is disabled + assert: + that: + - result is changed + - result is success + - result.enabled == [] + - '"{{ plugin_name }}" in result.disabled' + - '"[E" not in cli_result.stdout' + + - name: Disable plugin [online] (idempotency) + rabbitmq_plugin: + name: "{{ plugin_name }}" + state: disabled + register: result + + - name: Check idempotency + assert: + that: + - result is not changed + + - name: Enable plugin [offline] + rabbitmq_plugin: + name: "{{ plugin_name }}" + state: enabled + broker_state: offline + new_only: True + register: result + + - name: Get rabbitmq-plugins output + shell: "rabbitmq-plugins list | grep {{ plugin_name }}" + register: cli_result + + - name: Check that the plugin is enabled + assert: + that: + - result is changed + - result is success + - '"{{ plugin_name }}" in result.enabled' + - result.disabled == [] + - '"[E" in cli_result.stdout' + + - name: Enable plugin [offline] (idempotency) + rabbitmq_plugin: + name: "{{ plugin_name }}" + state: enabled + broker_state: offline + new_only: True + register: result + + - name: Check idempotency + assert: + that: + - result is not changed + + - name: Disable plugin [offline] + rabbitmq_plugin: + name: "{{ plugin_name }}" + state: disabled + broker_state: offline + register: result + + - name: Get rabbitmq-plugins output + shell: "rabbitmq-plugins list | grep {{ plugin_name }}" + register: cli_result + + - name: Check that the plugin is disabled + assert: + that: + - result is changed + - result is success + - result.enabled == [] + - '"{{ plugin_name }}" in result.disabled' + - '"[E" not in cli_result.stdout' + + - name: Disable plugin [offline] (idempotency) + rabbitmq_plugin: + name: "{{ plugin_name }}" + state: disabled + broker_state: offline + register: result + + - name: Check idempotency + assert: + that: + - result is not changed + + always: + - name: Disable plugin + rabbitmq_plugin: + name: "{{ plugin_name }}" + state: disabled diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_policy/aliases b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_policy/aliases new file mode 100644 index 000000000..f37e6c6fb --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_policy/aliases @@ -0,0 +1,6 @@ +destructive +shippable/posix/group1 +skip/aix +skip/osx +skip/freebsd +skip/rhel diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_policy/meta/main.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_policy/meta/main.yml new file mode 100644 index 000000000..05ab59000 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_policy/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_rabbitmq diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_policy/tasks/main.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_policy/tasks/main.yml new file mode 100644 index 000000000..593906fb7 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_policy/tasks/main.yml @@ -0,0 +1,2 @@ +- import_tasks: tests.yml + when: ansible_distribution == 'Ubuntu' diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_policy/tasks/tests.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_policy/tasks/tests.yml new file mode 100644 index 000000000..ca13fa741 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_policy/tasks/tests.yml @@ -0,0 +1,92 @@ +- block: + - set_fact: + vhost_name: /policytest + ha_policy_name: HA + + - name: Add host + rabbitmq_vhost: + name: "{{ vhost_name }}" + state: present + register: result + + - name: Check that the host was created successfuly + shell: "rabbitmqctl list_vhosts name tracing | grep {{ vhost_name }}" + register: ctl_result + + - name: Check that the host is added + assert: + that: + - result is changed + - result is success + - '"false" in ctl_result.stdout' + + - name: Add host (idempotency) + rabbitmq_vhost: + name: "{{ vhost_name }}" + state: present + register: result + + - name: Check idempotency + assert: + that: + - result is not changed + + - name: Add an HA Policy + rabbitmq_policy: + name: "{{ ha_policy_name }}" + apply_to: queues + pattern: ".*" + tags: + ha-mode: all + ha-sync-mode: automatic + ha-sync-batch-size: 10000 + vhost: "{{ vhost_name }}" + register: add_policy + + - name: Check that the policy is added + assert: + that: + - add_policy is changed + - add_policy is success + + - name: Add an HA Policy (idempotency) + rabbitmq_policy: + name: "{{ ha_policy_name }}" + apply_to: queues + pattern: ".*" + tags: + ha-mode: all + ha-sync-mode: automatic + ha-sync-batch-size: 10000 + vhost: "{{ vhost_name }}" + register: add_policy + + - name: Check policy idempotency + assert: + that: + - add_policy is not changed + + - name: Remove the HA policy + rabbitmq_policy: + name: "{{ ha_policy_name }}" + state: absent + vhost: "{{ vhost_name }}" + register: remove_policy + + - name: Check that the policy is removed + assert: + that: + - remove_policy is changed + - remove_policy is success + + - name: Remove the HA Policy (idempotency) + rabbitmq_policy: + name: "{{ ha_policy_name }}" + state: absent + vhost: "{{ vhost_name }}" + register: remove_policy + + - name: Check that the policy is removed (idempotency) + assert: + that: + - remove_policy is not changed diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_publish/aliases b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_publish/aliases new file mode 100644 index 000000000..f37e6c6fb --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_publish/aliases @@ -0,0 +1,6 @@ +destructive +shippable/posix/group1 +skip/aix +skip/osx +skip/freebsd +skip/rhel diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_publish/files/image.gif b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_publish/files/image.gif Binary files differnew file mode 100644 index 000000000..0589d2082 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_publish/files/image.gif diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_publish/meta/main.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_publish/meta/main.yml new file mode 100644 index 000000000..f0eb571b9 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_publish/meta/main.yml @@ -0,0 +1,3 @@ +dependencies: + - setup_rabbitmq + - setup_remote_tmp_dir diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_publish/tasks/main.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_publish/tasks/main.yml new file mode 100644 index 000000000..740f89980 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_publish/tasks/main.yml @@ -0,0 +1,5 @@ +# Rabbitmq lookup +- include: ubuntu.yml + when: + - ansible_distribution == 'Ubuntu' + - ansible_distribution_release != 'trusty' diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_publish/tasks/ubuntu.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_publish/tasks/ubuntu.yml new file mode 100644 index 000000000..4136ae1e5 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_publish/tasks/ubuntu.yml @@ -0,0 +1,274 @@ +- name: Install requests and pika + pip: + name: requests,pika==1.3.0 + state: present + delegate_to: localhost + +- name: RabbitMQ basic publish test + rabbitmq_publish: + url: "amqp://guest:guest@127.0.0.1:5672/%2F" + queue: 'publish_test' + body: "Hello world from ansible module rabbitmq_publish" + content_type: "text/plain" + register: rabbit_basic_output1 + delegate_to: localhost + +- assert: + that: + - "rabbit_basic_output1 is not failed" + - "'publish_test' in rabbit_basic_output1.result.msg" + - "'publish_test' in rabbit_basic_output1.result.queue" + - "'text/plain' in rabbit_basic_output1.result.content_type" + + +# Testing random queue +- name: Publish to random queue + rabbitmq_publish: + url: "amqp://guest:guest@127.0.0.1:5672/%2F" + body: "RANDOM QUEUE POST" + content_type: "text/plain" + register: rabbit_random_queue_output + delegate_to: localhost + +- assert: + that: + - "rabbit_random_queue_output is not failed" + - "'amq.gen' in rabbit_random_queue_output.result.msg" + - "'amq.gen' in rabbit_random_queue_output.result.queue" + - "'text/plain' in rabbit_random_queue_output.result.content_type" + +- name: Copy binary to remote + copy: + src: "{{ role_path }}/files/image.gif" + dest: "{{ remote_tmp_dir }}/image.gif" + delegate_to: localhost + +- name: Publish binary to a queue + rabbitmq_publish: + url: "amqp://guest:guest@127.0.0.1:5672/%2F" + queue: publish_test + src: "{{ remote_tmp_dir }}/image.gif" + register: rabbitmq_publish_file + delegate_to: localhost + +- assert: + that: + - "rabbitmq_publish_file is not failed" + - "'publish_test' in rabbitmq_publish_file.result.queue" + - "'image/gif' in rabbitmq_publish_file.result.content_type" + +- name: Raise error for src and body defined + rabbitmq_publish: + url: "amqp://guest:guest@127.0.0.1:5672/%2F" + queue: 'publish_test' + src: "{{ remote_tmp_dir }}/image.gif" + body: blah + register: rabbit_basic_fail_output1 + ignore_errors: yes + delegate_to: localhost + +- assert: + that: + - "rabbit_basic_fail_output1 is failed" + - "'parameters are mutually exclusive' in rabbit_basic_fail_output1.msg" + +- name: Publish a file that does not exist + rabbitmq_publish: + url: "amqp://guest:guest@127.0.0.1:5672/%2F" + queue: 'publish_test' + src: 'aaaaaaajax-loader.gif' + register: file_missing_fail + ignore_errors: yes + delegate_to: localhost + +- assert: + that: + - "file_missing_fail is failed" + - "'Unable to open file' in file_missing_fail.msg" + +- name: Publish with proto/host/port/user/pass + rabbitmq_publish: + proto: amqp + host: localhost + port: 5672 + username: guest + password: guest + vhost: '%2F' + queue: publish_test + body: Testing with proto/host/port/username/password/vhost + register: host_port_output + delegate_to: localhost + +- assert: + that: + - "host_port_output is not failed" + +- name: Publish with host/port/user but missing proto + rabbitmq_publish: + host: localhost + port: 5672 + username: guest + password: guest + vhost: '%2F' + queue: publish_test + body: Testing with proto/host/port/username/password/vhost + ignore_errors: yes + register: host_port_missing_proto_output + delegate_to: localhost + +- assert: + that: + - "host_port_missing_proto_output is failed" + - "'Connection parameters must be passed via' in host_port_missing_proto_output.msg" + +- name: Publish with proto/host/port/user and url + rabbitmq_publish: + url: "amqp://guest:guest@127.0.0.1:5672/%2F" + proto: amqp + host: localhost + port: 5672 + username: guest + password: guest + vhost: '%2F' + queue: publish_test + body: Testing with proto/host/port/username/password/vhost + ignore_errors: yes + register: host_and_url_output + delegate_to: localhost + +- assert: + that: + - "host_and_url_output is failed" + - "'cannot be specified at the same time' in host_and_url_output.msg" + +- name: Publish headers to queue + rabbitmq_publish: + url: "amqp://guest:guest@127.0.0.1:5672/%2F" + queue: 'publish_test' + body: blah + headers: + myHeader: Value1 + secondHeader: Value2 + register: test_headers1 + ignore_errors: yes + delegate_to: localhost + +- name: Publish headers with file + rabbitmq_publish: + url: "amqp://guest:guest@127.0.0.1:5672/%2F" + queue: 'publish_test' + src: "{{ remote_tmp_dir }}/image.gif" + headers: + myHeader: Value1 + secondHeader: Value2 + register: test_headers2 + ignore_errors: yes + delegate_to: localhost + +- name: Collect all messages off the publish queue + set_fact: + messages: "{{ lookup('community.rabbitmq.rabbitmq', url='amqp://guest:guest@127.0.0.1:5672/%2F', queue='publish_test') }}" + delegate_to: localhost + +- name: Check contents of published messages + assert: + that: + - messages|length == 5 + - "'Hello world from ansible module rabbitmq_publish' in messages[0]['msg']" + - "'text/plain' in messages[0]['content_type']" + - "'image/gif' in messages[1]['content_type']" + - "'image.gif' in messages[1]['headers']['filename']" + - "'Testing with proto/host/port/username/password/vhost' in messages[2]['msg']" +# - messages[3]['headers']['myHeader'] is defined +# - messages[4]['headers']['filename'] is defined +# - messages[4]['headers']['secondHeader'] is defined +# + +- name: Check that queue and exchange are mutually exclusive + rabbitmq_publish: + url: "amqp://guest:guest@127.0.0.1:5672/%2F" + queue: 'fail_queue' + exchange: 'fail_exchange' + body: "Queue and exchange mutually exclusive should fail" + content_type: "text/plain" + register: rabbit_mutually_exclusive + delegate_to: localhost + ignore_errors: true + +- assert: + that: + - "rabbit_basic_fail_output1 is failed" + - "'parameters are mutually exclusive' in rabbit_mutually_exclusive.msg" + +# Publish to exchange testing +# - Create an exchange (pub_test_exchange) and a queue (pub_test_exchange_queue) +# - Bind exchange and queue +# - Send message to exchange +# - Check message arrived in queue + +- name: Add test requisites + block: + - name: Add exchange + rabbitmq_exchange: + name: "pub_test_exchange" + type: fanout + + - name: Add queue + rabbitmq_queue: + name: pub_test_exchange_queue + + - name: Add binding + rabbitmq_binding: + source: pub_test_exchange + destination: pub_test_exchange_queue + type: queue + register: add_binding + + - name: Check that binding succeeds with a change + assert: + that: + - add_binding.changed == true + +- name: RabbitMQ basic exchange publish test + rabbitmq_publish: + url: "amqp://guest:guest@127.0.0.1:5672/%2F" + exchange: 'pub_test_exchange' + body: "Hello world pub_test_exchange exchange from ansible module rabbitmq_publish" + content_type: "text/plain" + register: rabbit_exchange_output1 + delegate_to: localhost + +- assert: + that: + - "rabbit_exchange_output1 is not failed" + - "'pub_test_exchange' in rabbit_exchange_output1.result.msg" + +- name: Wait 2 seconds to make sure message has been delivered + pause: + seconds: 2 + +- name: Retrieve messages from pub_test_exchange_queue + set_fact: + rabbitmq_exchange_msg: "{{ lookup('community.rabbitmq.rabbitmq', url='amqp://guest:guest@127.0.0.1:5672/%2f/pub_test_exchange_queue', queue='pub_test_exchange_queue') }}" + ignore_errors: yes + register: rabbitmq_pub_test_exchange_queue + delegate_to: localhost + +- name: Debug out rabbitmq_pub_test_exchange_queue task result + debug: + var: rabbitmq_pub_test_exchange_queue + +- name: Debug out the set_fact rabbitmq_exchange_msg + debug: + var: rabbitmq_exchange_msg + +- name: Ensure one message was received + assert: + that: + - "rabbitmq_pub_test_exchange_queue is not failed" + - rabbitmq_exchange_msg | length == 1 + +- name: Ensure first message contains a string + assert: + that: + - "'pub_test_exchange exchange' in rabbitmq_exchange_msg[0].msg" diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_queue/aliases b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_queue/aliases new file mode 100644 index 000000000..f37e6c6fb --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_queue/aliases @@ -0,0 +1,6 @@ +destructive +shippable/posix/group1 +skip/aix +skip/osx +skip/freebsd +skip/rhel diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_queue/meta/main.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_queue/meta/main.yml new file mode 100644 index 000000000..f0eb571b9 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_queue/meta/main.yml @@ -0,0 +1,3 @@ +dependencies: + - setup_rabbitmq + - setup_remote_tmp_dir diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_queue/tasks/main.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_queue/tasks/main.yml new file mode 100644 index 000000000..740f89980 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_queue/tasks/main.yml @@ -0,0 +1,5 @@ +# Rabbitmq lookup +- include: ubuntu.yml + when: + - ansible_distribution == 'Ubuntu' + - ansible_distribution_release != 'trusty' diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_queue/tasks/ubuntu.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_queue/tasks/ubuntu.yml new file mode 100644 index 000000000..634189f6e --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_queue/tasks/ubuntu.yml @@ -0,0 +1,31 @@ +- name: Install requests + pip: + name: requests + state: present + delegate_to: localhost + +- name: Create RabbitMQ stream + community.rabbitmq.rabbitmq_queue: + name: test-x/test-y + arguments: + x-queue-type: stream + x-max-age: 24h + register: testqueue + +- name: Assert that queue with special characters was created + assert: + that: + - "testqueue is changed" + +- name: Create RabbitMQ stream + community.rabbitmq.rabbitmq_queue: + name: test-x/test-y + arguments: + x-queue-type: stream + x-max-age: 24h + register: testqueue + +- name: Assert that queue creation is idempotent + assert: + that: + - "testqueue is not changed" diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_upgrade/aliases b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_upgrade/aliases new file mode 100644 index 000000000..f37e6c6fb --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_upgrade/aliases @@ -0,0 +1,6 @@ +destructive +shippable/posix/group1 +skip/aix +skip/osx +skip/freebsd +skip/rhel diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_upgrade/meta/main.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_upgrade/meta/main.yml new file mode 100644 index 000000000..05ab59000 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_upgrade/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_rabbitmq diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_upgrade/tasks/main.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_upgrade/tasks/main.yml new file mode 100644 index 000000000..593906fb7 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_upgrade/tasks/main.yml @@ -0,0 +1,2 @@ +- import_tasks: tests.yml + when: ansible_distribution == 'Ubuntu' diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_upgrade/tasks/tests.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_upgrade/tasks/tests.yml new file mode 100644 index 000000000..c8c25f553 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_upgrade/tasks/tests.yml @@ -0,0 +1,182 @@ +- block: + - set_fact: + parameter_node: rabbit + + - name: Drain node (check_mode) + rabbitmq_upgrade: + action: drain + node: "{{ parameter_node }}" + register: result + check_mode: yes + + - name: Check if node was properly drained (check mode) + assert: + that: + - result is success + - result is changed + + - name: Drain node + rabbitmq_upgrade: + action: drain + node: "{{ parameter_node }}" + register: result + + - name: Check if node was properly drained + assert: + that: + - result is success + - result is changed + + - name: Ensure node is under maintenance + shell: "rabbitmq-diagnostics -n {{ parameter_node }} status | grep 'Is under maintenance?: true'" + + - name: Idempotent - Drain node (check mode) + rabbitmq_upgrade: + action: drain + node: "{{ parameter_node }}" + register: result + check_mode: yes + + - name: Idempotent - Check if node was properly drained (check mode) + assert: + that: + - result is success + - result is not changed + + - name: Idempotent - Drain node + rabbitmq_upgrade: + action: drain + node: "{{ parameter_node }}" + register: result + + - name: Idempotent - Check if node was properly drained + assert: + that: + - result is success + - result is not changed + + - name: Revive node (check mode) + rabbitmq_upgrade: + action: revive + node: "{{ parameter_node }}" + register: result + check_mode: yes + + - name: Check if node was properly revived (check mode) + assert: + that: + - result is success + - result is changed + + - name: Revive node + rabbitmq_upgrade: + action: revive + node: "{{ parameter_node }}" + register: result + + - name: Check if node was properly revived + assert: + that: + - result is success + - result is changed + + - name: Ensure node is under maintenance + shell: "rabbitmq-diagnostics -n {{ parameter_node }} status | grep 'Is under maintenance?: false'" + + - name: Idempotent - Revive node (check mode) + rabbitmq_upgrade: + action: revive + node: "{{ parameter_node }}" + register: result + check_mode: yes + + - name: Idempotent - Check if node was properly revived (check mode) + assert: + that: + - result is not changed + + - name: Idempotent - Revive node + rabbitmq_upgrade: + action: revive + node: "{{ parameter_node }}" + register: result + + - name: Idempotent - Check if node was properly revived + assert: + that: + - result is not changed + + - name: Execute await_online_quorum_plus_one (check mode) + rabbitmq_upgrade: + action: await_online_quorum_plus_one + node: "{{ parameter_node }}" + register: result + check_mode: yes + + - name: Check the result of await_online_quorum_plus_one (check mode) + assert: + that: + - result is success + - result is changed + + - name: Execute await_online_quorum_plus_one + rabbitmq_upgrade: + action: await_online_quorum_plus_one + node: "{{ parameter_node }}" + register: result + + - name: Check the result of await_online_quorum_plus_one + assert: + that: + - result is success + - result is changed + + - name: Execute await_online_synchronized_mirror (check mode) + rabbitmq_upgrade: + action: await_online_synchronized_mirror + node: "{{ parameter_node }}" + register: result + check_mode: yes + + - name: Check the result of await_online_synchronized_mirror (check mode) + assert: + that: + - result is success + - result is changed + + - name: Execute await_online_synchronized_mirror + rabbitmq_upgrade: + action: await_online_synchronized_mirror + node: "{{ parameter_node }}" + register: result + + - name: Check the result of await_online_synchronized_mirror + assert: + that: + - result is success + - result is changed + + - name: Execute post_upgrade (check_mode) + rabbitmq_upgrade: + action: post_upgrade + node: "{{ parameter_node }}" + register: result + check_mode: yes + + - name: Check the result of post_upgrade (check mode) + assert: + that: + - result is success + - result is changed + + - name: Execute post_upgrade + rabbitmq_upgrade: + action: post_upgrade + node: "{{ parameter_node }}" + register: result + + - name: Check the result of post_upgrade + assert: + that: + - result is success + - result is changed diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_user/aliases b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_user/aliases new file mode 100644 index 000000000..f37e6c6fb --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_user/aliases @@ -0,0 +1,6 @@ +destructive +shippable/posix/group1 +skip/aix +skip/osx +skip/freebsd +skip/rhel diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_user/meta/main.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_user/meta/main.yml new file mode 100644 index 000000000..05ab59000 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_user/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_rabbitmq diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_user/tasks/main.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_user/tasks/main.yml new file mode 100644 index 000000000..e03d4c7aa --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_user/tasks/main.yml @@ -0,0 +1,10 @@ +--- + +- when: ansible_distribution == 'Ubuntu' + block: + + - import_tasks: tests.yml + + - import_tasks: tests.yml + environment: + RABBITMQ_NODENAME: test diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_user/tasks/tests.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_user/tasks/tests.yml new file mode 100644 index 000000000..919aa4062 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_user/tasks/tests.yml @@ -0,0 +1,125 @@ +--- + +- name: Test add user + block: + - name: Add user + rabbitmq_user: user=joe password=changeme + register: add_user + + - name: Check that user adding succeeds with a change + assert: + that: + - add_user.changed == true + +- name: Test add user idempotence + block: + - name: Add user + rabbitmq_user: user=joe password=changeme + register: add_user + + - name: Check that user adding succeeds without a change + assert: + that: + - add_user.changed == false + +- name: Test change user permissions + block: + - name: Add user with permissions + rabbitmq_user: user=joe password=changeme vhost=/ configure_priv=.* read_priv=.* write_priv=.* + register: add_user + + - name: Check that changing permissions succeeds with a change + assert: + that: + - add_user.changed == true + +- name: Test change user permissions idempotence + block: + - name: Add user with permissions + rabbitmq_user: user=joe password=changeme vhost=/ configure_priv=.* read_priv=.* write_priv=.* + register: add_user + + - name: Check that changing permissions succeeds without a change + assert: + that: + - add_user.changed == false + +- name: Test change user topic permissions + block: + - name: Add user with topic permissions + rabbitmq_user: + user: joe + password: changeme + topic_permissions: + - vhost: / + exchange: amq.topic + read_priv: .* + write_priv: .* + register: add_user + + - name: Check that changing topic permissions succeeds with a change + assert: + that: + - add_user.changed == true + +- name: Test change user topic permissions idempotence + block: + - name: Add user with topic permissions + rabbitmq_user: + user: joe + password: changeme + topic_permissions: + - vhost: / + exchange: amq.topic + read_priv: .* + write_priv: .* + register: add_user + + - name: Check that changing topic permissions succeeds without a change + assert: + that: + - add_user.changed == false + +- name: Test add user tags + block: + - name: Add user with tags + rabbitmq_user: user=joe password=changeme vhost=/ configure_priv=.* read_priv=.* write_priv=.* tags=management,administrator + register: add_user + + - name: Check that adding tags succeeds with a change + assert: + that: + - add_user.changed == true + +- name: Test add user tags idempotence + block: + - name: Add user with tags + rabbitmq_user: user=joe password=changeme vhost=/ configure_priv=.* read_priv=.* write_priv=.* tags=administrator,management + register: add_user + + - name: Check that adding tags succeeds without a change + assert: + that: + - add_user.changed == false + +- name: Test remove user + block: + - name: Remove user + rabbitmq_user: user=joe state=absent + register: remove_user + + - name: Check that user removing succeeds with a change + assert: + that: + - remove_user.changed == true + +- name: Test remove user idempotence + block: + - name: Remove user + rabbitmq_user: user=joe state=absent + register: remove_user + + - name: Check that user removing succeeds without a change + assert: + that: + - remove_user.changed == false diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_user_limits/aliases b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_user_limits/aliases new file mode 100644 index 000000000..f37e6c6fb --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_user_limits/aliases @@ -0,0 +1,6 @@ +destructive +shippable/posix/group1 +skip/aix +skip/osx +skip/freebsd +skip/rhel diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_user_limits/meta/main.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_user_limits/meta/main.yml new file mode 100644 index 000000000..05ab59000 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_user_limits/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_rabbitmq diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_user_limits/tasks/main.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_user_limits/tasks/main.yml new file mode 100644 index 000000000..740f89980 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_user_limits/tasks/main.yml @@ -0,0 +1,5 @@ +# Rabbitmq lookup +- include: ubuntu.yml + when: + - ansible_distribution == 'Ubuntu' + - ansible_distribution_release != 'trusty' diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_user_limits/tasks/ubuntu.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_user_limits/tasks/ubuntu.yml new file mode 100644 index 000000000..75990332c --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_user_limits/tasks/ubuntu.yml @@ -0,0 +1,163 @@ +--- + +- name: Test setting user limits in check mode + block: + - name: Set user limits in check mode + rabbitmq_user_limits: + user: guest + max_connections: 64 + max_channels: 256 + state: present + check_mode: true + register: module_result + + - name: Check that the module's result is correct + assert: + that: + - module_result is changed + - module_result is success + + - name: Get a list of configured user limits + shell: "rabbitmqctl list_user_limits" + register: shell_result + + - name: Check that the check mode does not make any changes + assert: + that: + - shell_result is success + - "'\"max-connections\":64' not in shell_result.stdout" + - "'\"max-channels\":256' not in shell_result.stdout" + +- name: Test setting user limits + block: + - name: Set user limits + rabbitmq_user_limits: + user: guest + max_connections: 64 + max_channels: 256 + state: present + register: module_result + + - name: Check that the module's result is correct + assert: + that: + - module_result is changed + - module_result is success + + - name: Get a list of configured user limits + shell: "rabbitmqctl list_user_limits" + register: shell_result + + - name: Check that the user limits are actually set + assert: + that: + - shell_result is success + - "'\"max-connections\":64' in shell_result.stdout" + - "'\"max-channels\":256' in shell_result.stdout" + +- name: Test setting user limits (idempotence) + block: + - name: Set user limits (idempotence) + rabbitmq_user_limits: + user: guest + max_connections: 64 + max_channels: 256 + state: present + register: module_result + + - name: Check the idempotence + assert: + that: + - module_result is not changed + - module_result is success + +- name: Test changing user limits + block: + - name: Change user limits + rabbitmq_user_limits: + user: guest + max_connections: 32 + state: present + register: module_result + + - name: Check that the module's result is correct + assert: + that: + - module_result is changed + - module_result is success + + - name: Get a list of configured user limits + shell: "rabbitmqctl list_user_limits" + register: shell_result + + - name: Check that the user limits are actually set + assert: + that: + - shell_result is success + - "'\"max-connections\":32' in shell_result.stdout" + - "'\"max-channels\"' not in shell_result.stdout" + +- name: Test clearing user limits in check mode + block: + - name: Clear user limits in check mode + rabbitmq_user_limits: + user: guest + state: absent + check_mode: true + register: module_result + + - name: Check that the module's result is correct + assert: + that: + - module_result is not changed + - module_result is success + + - name: Get a list of configured user limits + shell: "rabbitmqctl list_user_limits" + register: shell_result + + - name: Check that the check mode does not make any changes + assert: + that: + - shell_result is success + - "'\"max-connections\":32' in shell_result.stdout" + - "'\"max-channels\"' not in shell_result.stdout" + +- name: Test clearing user limits + block: + - name: Clear user limits + rabbitmq_user_limits: + user: guest + state: absent + register: module_result + + - name: Check that the module's result is correct + assert: + that: + - module_result is changed + - module_result is success + + - name: Get a list of configured user limits + shell: "rabbitmqctl list_user_limits" + register: shell_result + + - name: Check that the user limits are actually cleared + assert: + that: + - shell_result is success + - "'\"max-connections\":' not in shell_result.stdout" + - "'\"max-channels\":' not in shell_result.stdout" + +- name: Test clearing user limits (idempotence) + block: + - name: Clear user limits (idempotence) + rabbitmq_user_limits: + user: guest + state: absent + register: module_result + + - name: Check the idempotence + assert: + that: + - module_result is not changed + - module_result is success diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_vhost/aliases b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_vhost/aliases new file mode 100644 index 000000000..f37e6c6fb --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_vhost/aliases @@ -0,0 +1,6 @@ +destructive +shippable/posix/group1 +skip/aix +skip/osx +skip/freebsd +skip/rhel diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_vhost/meta/main.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_vhost/meta/main.yml new file mode 100644 index 000000000..05ab59000 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_vhost/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_rabbitmq diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_vhost/tasks/main.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_vhost/tasks/main.yml new file mode 100644 index 000000000..593906fb7 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_vhost/tasks/main.yml @@ -0,0 +1,2 @@ +- import_tasks: tests.yml + when: ansible_distribution == 'Ubuntu' diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_vhost/tasks/tests.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_vhost/tasks/tests.yml new file mode 100644 index 000000000..019c5edee --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_vhost/tasks/tests.yml @@ -0,0 +1,121 @@ +- block: + - set_fact: + vhost_name: /test + + - name: Add host + rabbitmq_vhost: + name: "{{ vhost_name }}" + state: present + register: result + + - name: Check that the host was created successfuly + shell: "rabbitmqctl list_vhosts name tracing | grep {{ vhost_name }}" + register: ctl_result + + - name: Check that the host is added + assert: + that: + - result is changed + - result is success + - '"false" in ctl_result.stdout' # value for tracing, false is disabled + + - name: Add host (idempotency) + rabbitmq_vhost: + name: "{{ vhost_name }}" + state: present + register: result + + - name: Check idempotency + assert: + that: + - result is not changed + + - name: Enable tracing + rabbitmq_vhost: + name: "{{ vhost_name }}" + tracing: yes + register: result + + - name: Get rabbitmqctl output + shell: "rabbitmqctl list_vhosts name tracing | grep {{ vhost_name }}" + register: ctl_result + + - name: Check that tracing is enabled + assert: + that: + - result is changed + - result is success + - '"true" in ctl_result.stdout' # value for tracing, true is enabled + + - name: Enable tracing (idempotency) + rabbitmq_vhost: + name: "{{ vhost_name }}" + tracing: yes + register: result + + - name: Check idempotency + assert: + that: + - result is not changed + + - name: Disable tracing + rabbitmq_vhost: + name: "{{ vhost_name }}" + tracing: no + register: result + + - name: Get rabbitmqctl output + shell: "rabbitmqctl list_vhosts name tracing | grep {{ vhost_name }}" + register: ctl_result + + - name: Check that tracing is disabled + assert: + that: + - result is changed + - result is success + - '"false" in ctl_result.stdout' # value for tracing, false is disabled + + - name: Disable tracing (idempotency) + rabbitmq_vhost: + name: "{{ vhost_name }}" + tracing: no + register: result + + - name: Check idempotency + assert: + that: + - result is not changed + + - name: Remove host + rabbitmq_vhost: + name: "{{ vhost_name }}" + state: absent + register: result + + - name: Get rabbitmqctl output + shell: "rabbitmqctl list_vhosts name tracing | grep {{ vhost_name }}" + register: ctl_result + failed_when: ctl_result.rc == 0 + + - name: Check that the host is removed + assert: + that: + - result is changed + - result is success + + - name: Remove host (idempotency) + rabbitmq_vhost: + name: "{{ vhost_name }}" + state: absent + register: result + + - name: Check idempotency + assert: + that: + - result is not changed + + always: + - name: Remove host + rabbitmq_vhost: + name: "{{ vhost_name }}" + state: absent diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_vhost_limits/aliases b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_vhost_limits/aliases new file mode 100644 index 000000000..f37e6c6fb --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_vhost_limits/aliases @@ -0,0 +1,6 @@ +destructive +shippable/posix/group1 +skip/aix +skip/osx +skip/freebsd +skip/rhel diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_vhost_limits/meta/main.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_vhost_limits/meta/main.yml new file mode 100644 index 000000000..05ab59000 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_vhost_limits/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_rabbitmq diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_vhost_limits/tasks/main.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_vhost_limits/tasks/main.yml new file mode 100644 index 000000000..740f89980 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_vhost_limits/tasks/main.yml @@ -0,0 +1,5 @@ +# Rabbitmq lookup +- include: ubuntu.yml + when: + - ansible_distribution == 'Ubuntu' + - ansible_distribution_release != 'trusty' diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_vhost_limits/tasks/ubuntu.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_vhost_limits/tasks/ubuntu.yml new file mode 100644 index 000000000..8d0a64802 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/rabbitmq_vhost_limits/tasks/ubuntu.yml @@ -0,0 +1,163 @@ +--- + +- name: Test setting virtual host limits in check mode + block: + - name: Set virtual host limits in check mode + rabbitmq_vhost_limits: + vhost: / + max_connections: 64 + max_queues: 256 + state: present + check_mode: true + register: module_result + + - name: Check that the module's result is correct + assert: + that: + - module_result is changed + - module_result is success + + - name: Get a list of configured virtual host limits + shell: "rabbitmqctl list_vhost_limits" + register: shell_result + + - name: Check that the check mode does not make any changes + assert: + that: + - shell_result is success + - "'\"max-connections\":64' not in shell_result.stdout" + - "'\"max-queues\":256' not in shell_result.stdout" + +- name: Test setting virtual host limits + block: + - name: Set virtual host limits + rabbitmq_vhost_limits: + vhost: / + max_connections: 64 + max_queues: 256 + state: present + register: module_result + + - name: Check that the module's result is correct + assert: + that: + - module_result is changed + - module_result is success + + - name: Get a list of configured virtual host limits + shell: "rabbitmqctl list_vhost_limits" + register: shell_result + + - name: Check that the virtual host limits are actually set + assert: + that: + - shell_result is success + - "'\"max-connections\":64' in shell_result.stdout" + - "'\"max-queues\":256' in shell_result.stdout" + +- name: Test setting virtual host limits (idempotence) + block: + - name: Set virtual host limits (idempotence) + rabbitmq_vhost_limits: + vhost: / + max_connections: 64 + max_queues: 256 + state: present + register: module_result + + - name: Check the idempotence + assert: + that: + - module_result is not changed + - module_result is success + +- name: Test changing virtual host limits + block: + - name: Change virtual host limits + rabbitmq_vhost_limits: + vhost: / + max_connections: 32 + state: present + register: module_result + + - name: Check that the module's result is correct + assert: + that: + - module_result is changed + - module_result is success + + - name: Get a list of configured virtual host limits + shell: "rabbitmqctl list_vhost_limits" + register: shell_result + + - name: Check that the virtual host limits are actually set + assert: + that: + - shell_result is success + - "'\"max-connections\":32' in shell_result.stdout" + - "'\"max-queues\":-1' in shell_result.stdout" + +- name: Test clearing virtual host limits in check mode + block: + - name: Clear virtual host limits in check mode + rabbitmq_vhost_limits: + vhost: / + state: absent + check_mode: true + register: module_result + + - name: Check that the module's result is correct + assert: + that: + - module_result is changed + - module_result is success + + - name: Get a list of configured virtual host limits + shell: "rabbitmqctl list_vhost_limits" + register: shell_result + + - name: Check that the check mode does not make any changes + assert: + that: + - shell_result is success + - "'\"max-connections\":32' in shell_result.stdout" + - "'\"max-queues\":-1' in shell_result.stdout" + +- name: Test clearing virtual host limits + block: + - name: Clear virtual host limits + rabbitmq_vhost_limits: + vhost: / + state: absent + register: module_result + + - name: Check that the module's result is correct + assert: + that: + - module_result is changed + - module_result is success + + - name: Get a list of configured virtual host limits + shell: "rabbitmqctl list_vhost_limits" + register: shell_result + + - name: Check that the virtual host limits are actually cleared + assert: + that: + - shell_result is success + - "'\"max-connections\":' not in shell_result.stdout" + - "'\"max-queues\":' not in shell_result.stdout" + +- name: Test clearing virtual host limits (idempotence) + block: + - name: Clear virtual host limits (idempotence) + rabbitmq_vhost_limits: + vhost: / + state: absent + register: module_result + + - name: Check the idempotence + assert: + that: + - module_result is not changed + - module_result is success diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/setup_rabbitmq/files/rabbitmq.conf b/ansible_collections/community/rabbitmq/tests/integration/targets/setup_rabbitmq/files/rabbitmq.conf new file mode 100644 index 000000000..12b036c35 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/setup_rabbitmq/files/rabbitmq.conf @@ -0,0 +1,9 @@ +listeners.ssl.default = 5671 +listeners.tcp.1 = :::5672 + +ssl_options.cacertfile = /tls/ca_certificate.pem +ssl_options.certfile = /tls/server_certificate.pem +ssl_options.keyfile = /tls/server_key.pem +ssl_options.password = bunnies +ssl_options.verify = verify_peer +ssl_options.fail_if_no_peer_cert = false diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/setup_rabbitmq/meta/main.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/setup_rabbitmq/meta/main.yml new file mode 100644 index 000000000..af05db79d --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/setup_rabbitmq/meta/main.yml @@ -0,0 +1,3 @@ +--- +dependencies: + - setup_tls diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/setup_rabbitmq/tasks/main.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/setup_rabbitmq/tasks/main.yml new file mode 100644 index 000000000..e0e11d10f --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/setup_rabbitmq/tasks/main.yml @@ -0,0 +1,10 @@ +--- +- name: Run platform specific install + include_tasks: "{{ lookup('first_found', params) }}" + vars: + params: + files: + - '{{ ansible_facts.distribution | lower }}.yml' + - '{{ ansible_facts.os_family | lower }}.yml' + paths: + - tasks diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/setup_rabbitmq/tasks/ubuntu.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/setup_rabbitmq/tasks/ubuntu.yml new file mode 100644 index 000000000..876a045c2 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/setup_rabbitmq/tasks/ubuntu.yml @@ -0,0 +1,105 @@ +--- +# https://stackoverflow.com/questions/25193161/chfn-pam-system-error-intermittently-in-docker-hub-builds/25267015 +- name: Disable chfn + file: + path: /usr/bin/chfn + src: /bin/true + state: link + force: yes + +# https://www.rabbitmq.com/install-debian.html#apt-pinning +- name: Install Essential Dependencies + apt: + name: + - gnupg + - debian-keyring + - debian-archive-keyring + - apt-transport-https + - python3-apt + # Required by the rabbitmq modules that uses the management API + - python3-requests + state: present + force: yes + +- name: Add RabbitMQ main release signing key + apt_key: + keyserver: "keyserver.ubuntu.com" + id: "0x0A9AF2115F4687BD29803A206B73A36E6026DFCA" + state: present + # The key for RPM release signing is different than this one. + # These URIs each have the same *RPM* signing key: + # "https://github.com/rabbitmq/signing-keys/releases/download/2.0/rabbitmq-release-signing-key.asc" + # "https://ansible-ci-files.s3.amazonaws.com/test/integration/targets/setup_rabbitmq/rabbitmq-release-signing-key.asc" + +- name: Add RabbitMQ repository signing key + apt_key: + url: "{{ item }}" + state: present + loop: + # Cloudsmith: modern Erlang repository + - "https://dl.cloudsmith.io/public/rabbitmq/rabbitmq-erlang/gpg.E495BB49CC4BBE5B.key" + # Cloudsmith: RabbitMQ repository + - "https://dl.cloudsmith.io/public/rabbitmq/rabbitmq-server/gpg.9F4587F226208342.key" + +- name: Add RabbitMQ Erlang repository + apt_repository: + repo: "deb https://dl.cloudsmith.io/public/rabbitmq/rabbitmq-erlang/deb/ubuntu {{ ansible_facts.distribution_release }} main" + filename: 'rabbitmq-erlang' + state: present + update_cache: yes + +- name: Add RabbitMQ Server repository + apt_repository: + repo: "deb https://dl.cloudsmith.io/public/rabbitmq/rabbitmq-server/deb/ubuntu {{ ansible_facts.distribution_release }} main" + filename: 'rabbitmq-server' + state: present + update_cache: yes + +# Ubuntu > 22.04 uses libssl version > 3 +- name: Select version of libbssl to use + set_fact: + ssl_ver: "{{ 'libssl3' if ansible_distribution_major_version == '22' else 'libssl1.1' }}" + +- name: Install RabbitMQ Erlang dependencies + apt: + name: + # Make sure libcrypto new enough for erlang + - "{{ ssl_ver }}" + # Base + - erlang-base + # TLS + - erlang-asn1 + - erlang-crypto + - erlang-public-key + - erlang-ssl + # etc (maybe not needed?) + - erlang-mnesia + - erlang-os-mon + - erlang-parsetools + - erlang-runtime-tools + - erlang-snmp + - erlang-syntax-tools + - erlang-tftp + - erlang-tools + - erlang-xmerl + state: latest + +- name: Install RabbitMQ Server + apt: + name: rabbitmq-server + state: fixed + # policy.rc.d => do not start the service yet + policy_rc_d: 101 + +- name: Ensure TLS config + copy: + src: rabbitmq.conf + dest: /etc/rabbitmq/rabbitmq.conf + +- name: Start RabbitMQ service + service: + name: rabbitmq-server + state: started + +- name: Enable management + command: rabbitmq-plugins enable --online rabbitmq_management diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/setup_remote_tmp_dir/handlers/main.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/setup_remote_tmp_dir/handlers/main.yml new file mode 100644 index 000000000..229037c8b --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/setup_remote_tmp_dir/handlers/main.yml @@ -0,0 +1,5 @@ +- name: delete temporary directory + include_tasks: default-cleanup.yml + +- name: delete temporary directory (windows) + include_tasks: windows-cleanup.yml diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/setup_remote_tmp_dir/tasks/default-cleanup.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/setup_remote_tmp_dir/tasks/default-cleanup.yml new file mode 100644 index 000000000..39872d749 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/setup_remote_tmp_dir/tasks/default-cleanup.yml @@ -0,0 +1,5 @@ +- name: delete temporary directory + file: + path: "{{ remote_tmp_dir }}" + state: absent + no_log: yes diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/setup_remote_tmp_dir/tasks/default.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/setup_remote_tmp_dir/tasks/default.yml new file mode 100644 index 000000000..1e0f51b89 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/setup_remote_tmp_dir/tasks/default.yml @@ -0,0 +1,11 @@ +- name: create temporary directory + tempfile: + state: directory + suffix: .test + register: remote_tmp_dir + notify: + - delete temporary directory + +- name: record temporary directory + set_fact: + remote_tmp_dir: "{{ remote_tmp_dir.path }}" diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/setup_remote_tmp_dir/tasks/main.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/setup_remote_tmp_dir/tasks/main.yml new file mode 100644 index 000000000..f8df391b5 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/setup_remote_tmp_dir/tasks/main.yml @@ -0,0 +1,10 @@ +- name: make sure we have the ansible_os_family and ansible_distribution_version facts + setup: + gather_subset: distribution + when: ansible_facts == {} + +- include_tasks: "{{ lookup('first_found', files)}}" + vars: + files: + - "{{ ansible_os_family | lower }}.yml" + - "default.yml" diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/setup_tls/files/ca_certificate.pem b/ansible_collections/community/rabbitmq/tests/integration/targets/setup_tls/files/ca_certificate.pem new file mode 100644 index 000000000..a438d9266 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/setup_tls/files/ca_certificate.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDAjCCAeqgAwIBAgIJANguFROhaWocMA0GCSqGSIb3DQEBCwUAMDExIDAeBgNV +BAMMF1RMU0dlblNlbGZTaWduZWR0Um9vdENBMQ0wCwYDVQQHDAQkJCQkMB4XDTE5 +MDExMTA4MzMxNVoXDTI5MDEwODA4MzMxNVowMTEgMB4GA1UEAwwXVExTR2VuU2Vs +ZlNpZ25lZHRSb290Q0ExDTALBgNVBAcMBCQkJCQwggEiMA0GCSqGSIb3DQEBAQUA +A4IBDwAwggEKAoIBAQDqVt84czSxWnWW4Ng6hmKE3NarbLsycwtjrYBokV7Kk7Mp +7PrBbYF05FOgSdJLvL6grlRSQK2VPsXdLfEv5uFXX6gyd2WQwKCiGGf4UY4ZIl4l +JVpSDsBV2orR4pOIf1s1+iSwvcRQkX46SVjoKWbDUc4VLo1uy8UvavQI+DMioYyy +0K2MbRs7oG2rdKks8zisfT0ymKnrFTdVeUjIrg0sStaMnf9VVkcEeYkfNY0vWqdn +CV5wPfDBlnnxGMgqGdLSpzfyJ7qafFET+q+gOvjsEqzn7DvlPkmk86hIIWXKi3aM +A9swknL3rnagJL6GioWRpYUwKdRKmZxdyr4I2JTTAgMBAAGjHTAbMAwGA1UdEwQF +MAMBAf8wCwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBCwUAA4IBAQACTpPBf5WSwZ7r +hrbPUN3qVh70HI0ZNK2jlK6b5fpSdw3JI/GQl0Kw3eGICLzwTByWvhD62U7IigL5 +0UWxWuEod310Y/qo/7OxRVPp5PH/0oNGoKHhEzas2ii0heQYGsHQUKGzYNNyVfjy +nqBFz5AcKf067LcXivYqod6JDQHqFq/5/hWlIsHHrZIeijqqtthPq39GlGAYO+AB +U66nzlH7YQgmfYfy6l7O4LsjXf/bz9rWvueO3NqCsmXV+FacDkOkwWA5Kf6rcgNL +3G+2HAVTRIXDnO4ShnK6aYMW+UklpYRlVYBBUOdwoNIp5gI+BlSc1IuF6PdLVt3q +VdjN1MjY +-----END CERTIFICATE----- diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/setup_tls/files/ca_key.pem b/ansible_collections/community/rabbitmq/tests/integration/targets/setup_tls/files/ca_key.pem new file mode 100644 index 000000000..0a950eda0 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/setup_tls/files/ca_key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDqVt84czSxWnWW +4Ng6hmKE3NarbLsycwtjrYBokV7Kk7Mp7PrBbYF05FOgSdJLvL6grlRSQK2VPsXd +LfEv5uFXX6gyd2WQwKCiGGf4UY4ZIl4lJVpSDsBV2orR4pOIf1s1+iSwvcRQkX46 +SVjoKWbDUc4VLo1uy8UvavQI+DMioYyy0K2MbRs7oG2rdKks8zisfT0ymKnrFTdV +eUjIrg0sStaMnf9VVkcEeYkfNY0vWqdnCV5wPfDBlnnxGMgqGdLSpzfyJ7qafFET ++q+gOvjsEqzn7DvlPkmk86hIIWXKi3aMA9swknL3rnagJL6GioWRpYUwKdRKmZxd +yr4I2JTTAgMBAAECggEBALpg9ZDUMCiOpc+mbNO/ZkP90M7u38Q0M+7HY8XHOPkt +l+XUkWueSMRLhSeLDzMlnwf1HyN8RZLaJkzP6XAL1VXEwuXAiIskaZ4Cg07Arp/W +8cHhf4CcMuUVuCtOZcC+ajD4Do5zn9vkm9yH0ap0o0LdoWa/a8WfU+luy0EHBsSW +6qqI+nqNFmISluVbfWt7t3zp273+8sir6YeHQu9G91/jzggv8rHmu4EHhi3cnU0K +vY6OPCGBL7nrg9Rv1LSFpH95TvlIM6/Cm0AjgW7m6XwWUTaI9p+GvKzrYUSLd9L/ +QxlmAwiu/sBTXLrsWyr8XEtj+lVGxQ6eFbf6E+lUm8ECgYEA+8Wgmhf3VsC3gvJz +w2jApEoOioD5iGOWGClGVURkfaBhFELr4XCTVMdBuCtxT7LYTMHTAlBqIbdWDjB4 +m/E417hLGogSDy7j0R0Mx75OOGEitxYUhe0VGDNoytgCNd2UnTMt42lp+9vAHZag +INhVDOnxRNdtNTf1yYkWUMEbh1sCgYEA7kZNJXPVYJtR78+km/Gcv64Umci7KUV+ +hYc7chR5xv3cXvXg5eojKa4G7CyMQTX7VnRa6CiQKdN73AbIAhS4Oy5UlCOKtmb8 +xnBiOAYwSpOfIeZhjq0RvEeZX0t6u7XsErBZ03rEPKXF2nNDo1x8byrlKPtlUzwJ +gb5yjmK/mekCgYEA1TWQAs5m4+2Bun+tbv7nnHkmhT4hktGays0xRYYMf6Jwc6MU +dC5MZg/zZI5Nf8uZhq7hDWWh6vmCA7QifxSxKWVlHIu8l2UDAhRSvVg4j2Aa8Obe +7GdQZNUsWhLBFHKXpuQvaRTc7q8yqxvicM4igDQg4EZ6sgW4vDm+TxapRF8CgYAz +n6mhPqpxRtWGxo8cdkmGwfmWpAXg2DykQ3teqQ8FTQUM0erLBWJe6mR3kONGUaLF +xWnYuMkbNsW0EwgMY17S+6O5gMXR5RhJChpNlxGpZrhoiNiEJ/0atMyG9/x8ZNrj +5a9ggU248hWe0bBK2YPgNgP2UBlQ4kYRBSkerkhi2QKBgF+tlpyqcU+0iY82qRS2 +wMf7oI2pWR8nX9LPAY/nnvwWvqwcAFJPMlSMTu8Ext6h7l9yu+7JGL6JWwsO57Lb +Gm/RxbuZ/kG/13+lSNmZiyHrhj6hZhkAMeFM34fpT4+DBXqSxZuvdrmwBc5B2jYg +F9Bv8gcmZlGhqONL23evr9Gu +-----END PRIVATE KEY----- diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/setup_tls/files/client_certificate.pem b/ansible_collections/community/rabbitmq/tests/integration/targets/setup_tls/files/client_certificate.pem new file mode 100644 index 000000000..501d83897 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/setup_tls/files/client_certificate.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDRjCCAi6gAwIBAgIBAjANBgkqhkiG9w0BAQsFADAxMSAwHgYDVQQDDBdUTFNH +ZW5TZWxmU2lnbmVkdFJvb3RDQTENMAsGA1UEBwwEJCQkJDAeFw0xOTAxMTEwODMz +MThaFw0yOTAxMDgwODMzMThaMC0xGjAYBgNVBAMMEWFuc2libGUudGxzLnRlc3Rz +MQ8wDQYDVQQKDAZjbGllbnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQCoM+OQ3HCnCUAAz9KGGTwWB9hQbUfAZXm/stlb2/uOAp3rNwxAlCs/giymBHE6 +Iu6mrK006Vn+Z9ibqIrD2LuCOxcu25y8goqG62TgdP5sa9wR+597s0XssnwnaY8y +bJ3p2zWAJvMgqQ0iNW/ZynpWbO85K5SryUykF7FAeNU9ogGGlIwCPjHhPvnwjkqd +yDqaA1VaJKDUWIF9joI7sV4VLgGhQvzXRrHULsTeIF2m0+ebL0PTNEWHQ0dtgLYX +kW7YO4Y6+n3cjHNH4qTof8V30EK8pk8kTdJ/x6ubwf+klFCAyroOxNOaxUy299Oo +yD6qIPJPnGkPhrKtWnWIhNzJAgMBAAGjbTBrMAkGA1UdEwQCMAAwCwYDVR0PBAQD +AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMCMDwGA1UdEQQ1MDOCEWFuc2libGUudGxz +LnRlc3RzghNNYWNCb29rLVByby00LmxvY2Fsgglsb2NhbGhvc3QwDQYJKoZIhvcN +AQELBQADggEBAK214+VVXnGnsUlvd9Q6A2Ea6UGrr6b7xkmlnIaNd+6xoUsDsHob +srHYm7UC0uLi1KwSunI7AU5ZELVEUfAmJzh3O4d6C5sQyqKYPqd5harWOQ3BOD0I +plHpp7qMtsPDuJBtmE/bmvF85eto0H7pPz+cTTXRlOaVVeiHjMggFcXdy1MzGo9C +X/4wLQmsFeypTfe+ZGqvDh99VV+ffNMIsMh+opWEloaKiHmDKB6S9aC/MsVVM4RR +nHm/UKTOukaGE9QIPkSSaygv3sBkVnQ2SHMvvtnjPHVHlizNoq6+YTnuOvKpo4o5 +V7Bij+W7rkBQLsEfwv2IC+gzmRz2yxr2tXk= +-----END CERTIFICATE----- diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/setup_tls/files/client_key.pem b/ansible_collections/community/rabbitmq/tests/integration/targets/setup_tls/files/client_key.pem new file mode 100644 index 000000000..850260a87 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/setup_tls/files/client_key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAqDPjkNxwpwlAAM/Shhk8FgfYUG1HwGV5v7LZW9v7jgKd6zcM +QJQrP4IspgRxOiLupqytNOlZ/mfYm6iKw9i7gjsXLtucvIKKhutk4HT+bGvcEfuf +e7NF7LJ8J2mPMmyd6ds1gCbzIKkNIjVv2cp6VmzvOSuUq8lMpBexQHjVPaIBhpSM +Aj4x4T758I5Kncg6mgNVWiSg1FiBfY6CO7FeFS4BoUL810ax1C7E3iBdptPnmy9D +0zRFh0NHbYC2F5Fu2DuGOvp93IxzR+Kk6H/Fd9BCvKZPJE3Sf8erm8H/pJRQgMq6 +DsTTmsVMtvfTqMg+qiDyT5xpD4ayrVp1iITcyQIDAQABAoIBAHPszzpXs4xr46Cr +mvyxB6hnX76OkpUXWwGz0fptcsI9K3mhRuB7PhNXNE53YVIgITreZ8G/0jZ0e+VM +E9dG2HS5JRE2ap/BmJfERJIuD+vJqrL6KMCondi0arz/E6I9GdjDK+xW69nmqRaa +nawM0KQgD//m+WAsLJYrfg5hORZwI2SHaahawnCp0QaMmz3bdDWKRacM3q0UFX46 +Ze6CaZkUn+e1rHsTMcZBvxQWIVzysFNXh150idIB/PxL5YfCQqTSAj1c/nxaxz6a +BvHFlpaYR3tvXXlexxfjglCwsGyckbvTyP1cBZqpv5oES+VKt2PrOve9Zyax+CYT +0uQf6cECgYEA09+46QHXLfWh6jiJYu9skC9UrLU5czfCNB6PrUtFcjPFMYjZDcw9 +inJmcuTPXmfplxc47YDfpwotU+szTJDF+R8kknnfw9zVr/sIwZ5wsFfUQl/56Svn +AIOVvHHvcvMX95XKGiuTsoCIJZNjJN3l3ztu/bRciuiVLyizglwIVrMCgYEAyzvK +PFlWilbp3GPJlnW7x1bUxe1ziLE/Um+ujZx96+fy34hJLFdNdNzpNUjoOf3IDTGq +6xl+vXcf12gimWMFcD3qNIGKHBDM9cIB2RDbb6YcqI8lOqopsmOyGmVLPkRpCoUK +72kacQwvw6M9xjmpiG3dN8lE881jDmZi+hyCnJMCgYEAoIQnQAhP8Jbeo2dP1q+T +bS0elnX532uH6xqYOW8EXwAPznZiEw0ANspzCWqGHHzXQMusKmtvhcq1CpXvWHt6 +MUHB4GMK/wVosxmZya5yq3bu7ZZu7JOBQCdwosMi6NB5AO7vnaIUFLFB9E3UWBLw +243YicdCMU8B7yeD0ChPfPcCgYA1dYHKBBn+g8Q6Y8lIGaoOUmnfsok8gJtOfPAm +ce6xmi7J29iboE9QmTeC+62Sa44u4ky6UNeE0QwAJnVLcb+hebfcneKNZWH0l1bT +GVsPcFuDfzvkxZP4R782sERtmaMj0EFDHpuE9xatWIhMVyigKX4SSZAorXML+6S3 +c75rnwKBgBR+WU934wS+DbwTLlUB2mJWqJMEbOH/CUwPC7+VN4h1h3/i455iAeiU +BizLS0SlD+MoSbC7URcZuquqGkmMlnJXoxF+NdxoWZK78tYNftryWoR87TloiVc/ +LhkxZxje4tgW/mTLqH3zKDoyyzDzG6Q6tAUN2ZTjJFEws7qF30Qe +-----END RSA PRIVATE KEY----- diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/setup_tls/files/server_certificate.pem b/ansible_collections/community/rabbitmq/tests/integration/targets/setup_tls/files/server_certificate.pem new file mode 100644 index 000000000..4a0ebc6ec --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/setup_tls/files/server_certificate.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDRjCCAi6gAwIBAgIBATANBgkqhkiG9w0BAQsFADAxMSAwHgYDVQQDDBdUTFNH +ZW5TZWxmU2lnbmVkdFJvb3RDQTENMAsGA1UEBwwEJCQkJDAeFw0xOTAxMTEwODMz +MTZaFw0yOTAxMDgwODMzMTZaMC0xGjAYBgNVBAMMEWFuc2libGUudGxzLnRlc3Rz +MQ8wDQYDVQQKDAZzZXJ2ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQDIwErHwAesRBfd9HiZkmB3VYh28c1QkE9I8nYyHJKX2ZBUhAzK+h80BkcTJJ94 +265qWyACH/wl54Xe/ofFUFrGa4vz0qz4UkL/KI0OGw28Y4qnKdorb9DumbiIPB+9 +I9TJT9vhtXTxBNlBTpv3ONHL8EzdV6ZmuvELU11H27oQ4xoUYhfXPXLMLK0sOnXZ +lt0BOMMd5fVpJVa8fvXiw3626a0aXCr4e/MWUsBFRnzrXfgoW+AjYoTjKKS2hLYo +8//MM05h7ROIXrNe990sf9C1G+fOThmOMszK9sjMhu2xHranRcz5aA0UTfyOjTs8 +9WexUYhC5VorYyRWtVZu2mDjAgMBAAGjbTBrMAkGA1UdEwQCMAAwCwYDVR0PBAQD +AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMDwGA1UdEQQ1MDOCEWFuc2libGUudGxz +LnRlc3RzghNNYWNCb29rLVByby00LmxvY2Fsgglsb2NhbGhvc3QwDQYJKoZIhvcN +AQELBQADggEBAFoPBeB6tQhFS1198sia5NDHDDrghDOIlE0QbaoA+MSKzsaIy8Mu +mNcM2ewYpT600XXTBxcqF6/vuKL9OEbvivtRYQu1YfkifN1jzREoWTieUkR5ytzt +8ATfFkgTWJmiRiOIb/fNgewvhd+aKxep0OGwDiSKKl1ab6F17Cp4iK8sDBWmnUb6 +0Wf7pfver1Gl0Gp8vRXGUuc8a7udA9a8mV70HJlLkMdMvR9U8Bqih0+iRaqNWXRZ +7Lc6v5LbzrW/ntilmgU6F0lwxPydg49MY4UrSXcjYLZs9T4iYHwTfLxFjFMIgGwn +peYMKRj18akP9i2mjj5O2mRu4K+ecuUSOGI= +-----END CERTIFICATE----- diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/setup_tls/files/server_key.pem b/ansible_collections/community/rabbitmq/tests/integration/targets/setup_tls/files/server_key.pem new file mode 100644 index 000000000..c79ab6480 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/setup_tls/files/server_key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAyMBKx8AHrEQX3fR4mZJgd1WIdvHNUJBPSPJ2MhySl9mQVIQM +yvofNAZHEySfeNuualsgAh/8JeeF3v6HxVBaxmuL89Ks+FJC/yiNDhsNvGOKpyna +K2/Q7pm4iDwfvSPUyU/b4bV08QTZQU6b9zjRy/BM3VemZrrxC1NdR9u6EOMaFGIX +1z1yzCytLDp12ZbdATjDHeX1aSVWvH714sN+tumtGlwq+HvzFlLARUZ86134KFvg +I2KE4yiktoS2KPP/zDNOYe0TiF6zXvfdLH/QtRvnzk4ZjjLMyvbIzIbtsR62p0XM ++WgNFE38jo07PPVnsVGIQuVaK2MkVrVWbtpg4wIDAQABAoIBAHw3wA3pnNXTLJGC +fD1KfbZZjp9K76gyI10X6lsHow2i6dPiAah3LGecms4VkzfNdxcIW7303Kj3obZh ++ND277RnR6oPakgdXqdUCDP6OX2gemMFWqIWBkodhDmIOntmeHw4le4LwdiBD42B +frBy0B5JCsbLPYPDmPNRGh8krvVS+Eir4hb4tK95TPMSL0vEjvHYFbCxv7//Ri1p +3CROGp2CGX0WZ+Zs0crRNoIhRRM6kLAhROcqejtnEy6o7l5CWpCAL2vxlE9y8/kL +iRawSZRFZnz/zGnqpx0vswgvijkuPfcNGMSzdwaiDgQz8D0GkJ7s9VgzZJazNy+1 +ET/4YIECgYEA612rwP9Ar9qdYbmmMPaJzITnaIrNGfO2JvaQqZt+DG8sVgdxL7V5 +D6emcw406drKRZvFAxnW6ZW2bVpmit02osl0re2A/nOTXLNuo338Qkap/hG8YZrF +bw7w75pFa/rwlDtedjBnGHO2KbRXeU5Hn5wLoKjYgJoF6Ht+PPdL0IsCgYEA2lnC +pQEhM51iRMDqNdmVJyvsTNU1ikoO8HaXHq+LwOQETaKMnDwp4Bn14E815CTulAc/ +tsDTKSDk6umZ+IufG1a2v7CqgKVwkB4HkgxKFQs2gQdTFfoMi5eeHR+njuNtklp1 +9fWfKHsP/ddrg+iTVTRZBLWexgKK89IMHYalpAkCgYEAy0Q3a9NF81mTJ+3kOE8C +zO1OyLtuzGXsvxOb9c6C+owctyNwPeq05a89EgqH6hr5K0qOx9HOCCcyyJgVDQJl +CAuByB/gkmAQOTQBbhMFA9vxPanljknTDsnRjKwoHkw2712ig+Hjd3ufK79C+FGB +i7eBVzva1p2uUowshsxv3mcCgYAOFiRciMofjlO8o8V4W+Undcn02vxtQ4HbOYte +S2z0sMEmUQpJOghpkMMwCWwsn8VUf3M40w/MY3bhQNjSFA/br6hyjW8yhXnRkl5i +qbBN0z9c66AMlukgSFPHBTfGHB4Bhxx9Fa+C6Q2LDs6839BBevMTPrRTie509GQb +s4gUIQKBgAvE8wLcmozno0GLDnBdKRZP/C7tmVnAINuraITPUBTASwI+Qo8ILigQ +LRLaDqF84BEpjb8vdzkYFQqRQSZ8BI8NydfuKEFSBfL27sBvSGMYQJVm6bryUmPq +T3ayaeZ4Wb3FFDijgtM9dRKyf7p4hQPOqM44QrntAtb43b2Q5L7M +-----END RSA PRIVATE KEY----- diff --git a/ansible_collections/community/rabbitmq/tests/integration/targets/setup_tls/tasks/main.yml b/ansible_collections/community/rabbitmq/tests/integration/targets/setup_tls/tasks/main.yml new file mode 100644 index 000000000..c5b7a23a2 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/integration/targets/setup_tls/tasks/main.yml @@ -0,0 +1,21 @@ +--- +# Generated certificate with: https://github.com/michaelklishin/tls-gen +# ~/tls-gen/basic# make PASSWORD=bunnies CN=ansible.tls.tests +# verify with: make info + +- name: ensure target directory is present + file: + path: /tls + state: directory + +- name: ensure TLS files are present + copy: + src: "{{ item }}" + dest: "/tls/{{ item }}" + loop: + - ca_certificate.pem + - ca_key.pem + - client_certificate.pem + - client_key.pem + - server_certificate.pem + - server_key.pem diff --git a/ansible_collections/community/rabbitmq/tests/sanity/ignore-2.10.txt b/ansible_collections/community/rabbitmq/tests/sanity/ignore-2.10.txt new file mode 100644 index 000000000..caf221794 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/sanity/ignore-2.10.txt @@ -0,0 +1,2 @@ +tests/utils/shippable/check_matrix.py replace-urlopen +tests/utils/shippable/timing.py shebang diff --git a/ansible_collections/community/rabbitmq/tests/sanity/ignore-2.11.txt b/ansible_collections/community/rabbitmq/tests/sanity/ignore-2.11.txt new file mode 100644 index 000000000..caf221794 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/sanity/ignore-2.11.txt @@ -0,0 +1,2 @@ +tests/utils/shippable/check_matrix.py replace-urlopen +tests/utils/shippable/timing.py shebang diff --git a/ansible_collections/community/rabbitmq/tests/sanity/ignore-2.12.txt b/ansible_collections/community/rabbitmq/tests/sanity/ignore-2.12.txt new file mode 100644 index 000000000..caf221794 --- /dev/null +++ b/ansible_collections/community/rabbitmq/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/community/rabbitmq/tests/sanity/ignore-2.13.txt b/ansible_collections/community/rabbitmq/tests/sanity/ignore-2.13.txt new file mode 100644 index 000000000..caf221794 --- /dev/null +++ b/ansible_collections/community/rabbitmq/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/community/rabbitmq/tests/sanity/ignore-2.14.txt b/ansible_collections/community/rabbitmq/tests/sanity/ignore-2.14.txt new file mode 100644 index 000000000..caf221794 --- /dev/null +++ b/ansible_collections/community/rabbitmq/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/community/rabbitmq/tests/sanity/ignore-2.15.txt b/ansible_collections/community/rabbitmq/tests/sanity/ignore-2.15.txt new file mode 100644 index 000000000..caf221794 --- /dev/null +++ b/ansible_collections/community/rabbitmq/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/community/rabbitmq/tests/sanity/ignore-2.9.txt b/ansible_collections/community/rabbitmq/tests/sanity/ignore-2.9.txt new file mode 100644 index 000000000..caf221794 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/sanity/ignore-2.9.txt @@ -0,0 +1,2 @@ +tests/utils/shippable/check_matrix.py replace-urlopen +tests/utils/shippable/timing.py shebang diff --git a/ansible_collections/community/rabbitmq/tests/sanity/ignore.txt b/ansible_collections/community/rabbitmq/tests/sanity/ignore.txt new file mode 100644 index 000000000..caf221794 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/sanity/ignore.txt @@ -0,0 +1,2 @@ +tests/utils/shippable/check_matrix.py replace-urlopen +tests/utils/shippable/timing.py shebang diff --git a/ansible_collections/community/rabbitmq/tests/unit/compat/__init__.py b/ansible_collections/community/rabbitmq/tests/unit/compat/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/unit/compat/__init__.py diff --git a/ansible_collections/community/rabbitmq/tests/unit/compat/builtins.py b/ansible_collections/community/rabbitmq/tests/unit/compat/builtins.py new file mode 100644 index 000000000..f60ee6782 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/unit/compat/builtins.py @@ -0,0 +1,33 @@ +# (c) 2014, Toshio Kuratomi <tkuratomi@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/>. + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +# +# Compat for python2.7 +# + +# One unittest needs to import builtins via __import__() so we need to have +# the string that represents it +try: + import __builtin__ +except ImportError: + BUILTINS = 'builtins' +else: + BUILTINS = '__builtin__' diff --git a/ansible_collections/community/rabbitmq/tests/unit/compat/mock.py b/ansible_collections/community/rabbitmq/tests/unit/compat/mock.py new file mode 100644 index 000000000..0972cd2e8 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/unit/compat/mock.py @@ -0,0 +1,122 @@ +# (c) 2014, Toshio Kuratomi <tkuratomi@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/>. + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +''' +Compat module for Python3.x's unittest.mock module +''' +import sys + +# Python 2.7 + +# Note: Could use the pypi mock library on python3.x as well as python2.x. It +# is the same as the python3 stdlib mock library + +try: + # Allow wildcard import because we really do want to import all of mock's + # symbols into this compat shim + # pylint: disable=wildcard-import,unused-wildcard-import + from unittest.mock import * +except ImportError: + # Python 2 + # pylint: disable=wildcard-import,unused-wildcard-import + try: + from mock import * + except ImportError: + print('You need the mock library installed on python2.x to run tests') + + +# Prior to 3.4.4, mock_open cannot handle binary read_data +if sys.version_info >= (3,) and sys.version_info < (3, 4, 4): + file_spec = None + + def _iterate_read_data(read_data): + # Helper for mock_open: + # Retrieve lines from read_data via a generator so that separate calls to + # readline, read, and readlines are properly interleaved + sep = b'\n' if isinstance(read_data, bytes) else '\n' + data_as_list = [l + sep for l in read_data.split(sep)] + + if data_as_list[-1] == sep: + # If the last line ended in a newline, the list comprehension will have an + # extra entry that's just a newline. Remove this. + data_as_list = data_as_list[:-1] + else: + # If there wasn't an extra newline by itself, then the file being + # emulated doesn't have a newline to end the last line remove the + # newline that our naive format() added + data_as_list[-1] = data_as_list[-1][:-1] + + for line in data_as_list: + yield line + + def mock_open(mock=None, read_data=''): + """ + A helper function to create a mock to replace the use of `open`. It works + for `open` called directly or used as a context manager. + + The `mock` argument is the mock object to configure. If `None` (the + default) then a `MagicMock` will be created for you, with the API limited + to methods or attributes available on standard file handles. + + `read_data` is a string for the `read` methoddline`, and `readlines` of the + file handle to return. This is an empty string by default. + """ + def _readlines_side_effect(*args, **kwargs): + if handle.readlines.return_value is not None: + return handle.readlines.return_value + return list(_data) + + def _read_side_effect(*args, **kwargs): + if handle.read.return_value is not None: + return handle.read.return_value + return type(read_data)().join(_data) + + def _readline_side_effect(): + if handle.readline.return_value is not None: + while True: + yield handle.readline.return_value + for line in _data: + yield line + + global file_spec + if file_spec is None: + import _io + file_spec = list(set(dir(_io.TextIOWrapper)).union(set(dir(_io.BytesIO)))) + + if mock is None: + mock = MagicMock(name='open', spec=open) + + handle = MagicMock(spec=file_spec) + handle.__enter__.return_value = handle + + _data = _iterate_read_data(read_data) + + handle.write.return_value = None + handle.read.return_value = None + handle.readline.return_value = None + handle.readlines.return_value = None + + handle.read.side_effect = _read_side_effect + handle.readline.side_effect = _readline_side_effect() + handle.readlines.side_effect = _readlines_side_effect + + mock.return_value = handle + return mock diff --git a/ansible_collections/community/rabbitmq/tests/unit/compat/unittest.py b/ansible_collections/community/rabbitmq/tests/unit/compat/unittest.py new file mode 100644 index 000000000..98f08ad6a --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/unit/compat/unittest.py @@ -0,0 +1,38 @@ +# (c) 2014, Toshio Kuratomi <tkuratomi@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/>. + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +''' +Compat module for Python2.7's unittest module +''' + +import sys + +# Allow wildcard import because we really do want to import all of +# unittests's symbols into this compat shim +# pylint: disable=wildcard-import,unused-wildcard-import +if sys.version_info < (2, 7): + try: + # Need unittest2 on python2.6 + from unittest2 import * + except ImportError: + print('You need unittest2 installed on python2.6.x to run tests') +else: + from unittest import * diff --git a/ansible_collections/community/rabbitmq/tests/unit/mock/__init__.py b/ansible_collections/community/rabbitmq/tests/unit/mock/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/unit/mock/__init__.py diff --git a/ansible_collections/community/rabbitmq/tests/unit/mock/loader.py b/ansible_collections/community/rabbitmq/tests/unit/mock/loader.py new file mode 100644 index 000000000..00a584127 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/unit/mock/loader.py @@ -0,0 +1,116 @@ +# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.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/>. + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os + +from ansible.errors import AnsibleParserError +from ansible.parsing.dataloader import DataLoader +from ansible.module_utils._text import to_bytes, to_text + + +class DictDataLoader(DataLoader): + + def __init__(self, file_mapping=None): + file_mapping = {} if file_mapping is None else file_mapping + assert type(file_mapping) == dict + + super(DictDataLoader, self).__init__() + + self._file_mapping = file_mapping + self._build_known_directories() + self._vault_secrets = None + + def load_from_file(self, path, cache=True, unsafe=False): + path = to_text(path) + if path in self._file_mapping: + return self.load(self._file_mapping[path], path) + return None + + # TODO: the real _get_file_contents returns a bytestring, so we actually convert the + # unicode/text it's created with to utf-8 + def _get_file_contents(self, file_name): + file_name = to_text(file_name) + if file_name in self._file_mapping: + return (to_bytes(self._file_mapping[file_name]), False) + else: + raise AnsibleParserError("file not found: %s" % file_name) + + def path_exists(self, path): + path = to_text(path) + return path in self._file_mapping or path in self._known_directories + + def is_file(self, path): + path = to_text(path) + return path in self._file_mapping + + def is_directory(self, path): + path = to_text(path) + return path in self._known_directories + + def list_directory(self, path): + ret = [] + path = to_text(path) + for x in (list(self._file_mapping.keys()) + self._known_directories): + if x.startswith(path): + if os.path.dirname(x) == path: + ret.append(os.path.basename(x)) + return ret + + def is_executable(self, path): + # FIXME: figure out a way to make paths return true for this + return False + + def _add_known_directory(self, directory): + if directory not in self._known_directories: + self._known_directories.append(directory) + + def _build_known_directories(self): + self._known_directories = [] + for path in self._file_mapping: + dirname = os.path.dirname(path) + while dirname not in ('/', ''): + self._add_known_directory(dirname) + dirname = os.path.dirname(dirname) + + def push(self, path, content): + rebuild_dirs = False + if path not in self._file_mapping: + rebuild_dirs = True + + self._file_mapping[path] = content + + if rebuild_dirs: + self._build_known_directories() + + def pop(self, path): + if path in self._file_mapping: + del self._file_mapping[path] + self._build_known_directories() + + def clear(self): + self._file_mapping = dict() + self._known_directories = [] + + def get_basedir(self): + return os.getcwd() + + def set_vault_secrets(self, vault_secrets): + self._vault_secrets = vault_secrets diff --git a/ansible_collections/community/rabbitmq/tests/unit/mock/path.py b/ansible_collections/community/rabbitmq/tests/unit/mock/path.py new file mode 100644 index 000000000..e988116b1 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/unit/mock/path.py @@ -0,0 +1,8 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible_collections.ansible.rabbitmq.tests.unit.compat.mock import MagicMock +from ansible.utils.path import unfrackpath + + +mock_unfrackpath_noop = MagicMock(spec_set=unfrackpath, side_effect=lambda x, *args, **kwargs: x) diff --git a/ansible_collections/community/rabbitmq/tests/unit/mock/procenv.py b/ansible_collections/community/rabbitmq/tests/unit/mock/procenv.py new file mode 100644 index 000000000..56d44b015 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/unit/mock/procenv.py @@ -0,0 +1,90 @@ +# (c) 2016, Matt Davis <mdavis@ansible.com> +# (c) 2016, Toshio Kuratomi <tkuratomi@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/>. + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import sys +import json + +from contextlib import contextmanager +from io import BytesIO, StringIO +from ansible_collections.ansible.rabbitmq.tests.unit.compat import unittest +from ansible.module_utils.six import PY3 +from ansible.module_utils._text import to_bytes + + +@contextmanager +def swap_stdin_and_argv(stdin_data='', argv_data=tuple()): + """ + context manager that temporarily masks the test runner's values for stdin and argv + """ + real_stdin = sys.stdin + real_argv = sys.argv + + if PY3: + fake_stream = StringIO(stdin_data) + fake_stream.buffer = BytesIO(to_bytes(stdin_data)) + else: + fake_stream = BytesIO(to_bytes(stdin_data)) + + try: + sys.stdin = fake_stream + sys.argv = argv_data + + yield + finally: + sys.stdin = real_stdin + sys.argv = real_argv + + +@contextmanager +def swap_stdout(): + """ + context manager that temporarily replaces stdout for tests that need to verify output + """ + old_stdout = sys.stdout + + if PY3: + fake_stream = StringIO() + else: + fake_stream = BytesIO() + + try: + sys.stdout = fake_stream + + yield fake_stream + finally: + sys.stdout = old_stdout + + +class ModuleTestCase(unittest.TestCase): + def setUp(self, module_args=None): + if module_args is None: + module_args = {'_ansible_remote_tmp': '/tmp', '_ansible_keep_remote_files': False} + + args = json.dumps(dict(ANSIBLE_MODULE_ARGS=module_args)) + + # unittest doesn't have a clean place to use a context manager, so we have to enter/exit manually + self.stdin_swap = swap_stdin_and_argv(stdin_data=args) + self.stdin_swap.__enter__() + + def tearDown(self): + # unittest doesn't have a clean place to use a context manager, so we have to enter/exit manually + self.stdin_swap.__exit__(None, None, None) diff --git a/ansible_collections/community/rabbitmq/tests/unit/mock/vault_helper.py b/ansible_collections/community/rabbitmq/tests/unit/mock/vault_helper.py new file mode 100644 index 000000000..dcce9c784 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/unit/mock/vault_helper.py @@ -0,0 +1,39 @@ +# 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/>. + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.module_utils._text import to_bytes + +from ansible.parsing.vault import VaultSecret + + +class TextVaultSecret(VaultSecret): + '''A secret piece of text. ie, a password. Tracks text encoding. + + The text encoding of the text may not be the default text encoding so + we keep track of the encoding so we encode it to the same bytes.''' + + def __init__(self, text, encoding=None, errors=None, _bytes=None): + super(TextVaultSecret, self).__init__() + self.text = text + self.encoding = encoding or 'utf-8' + self._bytes = _bytes + self.errors = errors or 'strict' + + @property + def bytes(self): + '''The text encoded with encoding, unless we specifically set _bytes.''' + return self._bytes or to_bytes(self.text, encoding=self.encoding, errors=self.errors) diff --git a/ansible_collections/community/rabbitmq/tests/unit/mock/yaml_helper.py b/ansible_collections/community/rabbitmq/tests/unit/mock/yaml_helper.py new file mode 100644 index 000000000..1ef172159 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/unit/mock/yaml_helper.py @@ -0,0 +1,124 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import io +import yaml + +from ansible.module_utils.six import PY3 +from ansible.parsing.yaml.loader import AnsibleLoader +from ansible.parsing.yaml.dumper import AnsibleDumper + + +class YamlTestUtils(object): + """Mixin class to combine with a unittest.TestCase subclass.""" + def _loader(self, stream): + """Vault related tests will want to override this. + + Vault cases should setup a AnsibleLoader that has the vault password.""" + return AnsibleLoader(stream) + + def _dump_stream(self, obj, stream, dumper=None): + """Dump to a py2-unicode or py3-string stream.""" + if PY3: + return yaml.dump(obj, stream, Dumper=dumper) + else: + return yaml.dump(obj, stream, Dumper=dumper, encoding=None) + + def _dump_string(self, obj, dumper=None): + """Dump to a py2-unicode or py3-string""" + if PY3: + return yaml.dump(obj, Dumper=dumper) + else: + return yaml.dump(obj, Dumper=dumper, encoding=None) + + def _dump_load_cycle(self, obj): + # Each pass though a dump or load revs the 'generation' + # obj to yaml string + string_from_object_dump = self._dump_string(obj, dumper=AnsibleDumper) + + # wrap a stream/file like StringIO around that yaml + stream_from_object_dump = io.StringIO(string_from_object_dump) + loader = self._loader(stream_from_object_dump) + # load the yaml stream to create a new instance of the object (gen 2) + obj_2 = loader.get_data() + + # dump the gen 2 objects directory to strings + string_from_object_dump_2 = self._dump_string(obj_2, + dumper=AnsibleDumper) + + # The gen 1 and gen 2 yaml strings + self.assertEqual(string_from_object_dump, string_from_object_dump_2) + # the gen 1 (orig) and gen 2 py object + self.assertEqual(obj, obj_2) + + # again! gen 3... load strings into py objects + stream_3 = io.StringIO(string_from_object_dump_2) + loader_3 = self._loader(stream_3) + obj_3 = loader_3.get_data() + + string_from_object_dump_3 = self._dump_string(obj_3, dumper=AnsibleDumper) + + self.assertEqual(obj, obj_3) + # should be transitive, but... + self.assertEqual(obj_2, obj_3) + self.assertEqual(string_from_object_dump, string_from_object_dump_3) + + def _old_dump_load_cycle(self, obj): + '''Dump the passed in object to yaml, load it back up, dump again, compare.''' + stream = io.StringIO() + + yaml_string = self._dump_string(obj, dumper=AnsibleDumper) + self._dump_stream(obj, stream, dumper=AnsibleDumper) + + yaml_string_from_stream = stream.getvalue() + + # reset stream + stream.seek(0) + + loader = self._loader(stream) + # loader = AnsibleLoader(stream, vault_password=self.vault_password) + obj_from_stream = loader.get_data() + + stream_from_string = io.StringIO(yaml_string) + loader2 = self._loader(stream_from_string) + # loader2 = AnsibleLoader(stream_from_string, vault_password=self.vault_password) + obj_from_string = loader2.get_data() + + stream_obj_from_stream = io.StringIO() + stream_obj_from_string = io.StringIO() + + if PY3: + yaml.dump(obj_from_stream, stream_obj_from_stream, Dumper=AnsibleDumper) + yaml.dump(obj_from_stream, stream_obj_from_string, Dumper=AnsibleDumper) + else: + yaml.dump(obj_from_stream, stream_obj_from_stream, Dumper=AnsibleDumper, encoding=None) + yaml.dump(obj_from_stream, stream_obj_from_string, Dumper=AnsibleDumper, encoding=None) + + yaml_string_stream_obj_from_stream = stream_obj_from_stream.getvalue() + yaml_string_stream_obj_from_string = stream_obj_from_string.getvalue() + + stream_obj_from_stream.seek(0) + stream_obj_from_string.seek(0) + + if PY3: + yaml_string_obj_from_stream = yaml.dump(obj_from_stream, Dumper=AnsibleDumper) + yaml_string_obj_from_string = yaml.dump(obj_from_string, Dumper=AnsibleDumper) + else: + yaml_string_obj_from_stream = yaml.dump(obj_from_stream, Dumper=AnsibleDumper, encoding=None) + yaml_string_obj_from_string = yaml.dump(obj_from_string, Dumper=AnsibleDumper, encoding=None) + + assert yaml_string == yaml_string_obj_from_stream + assert yaml_string == yaml_string_obj_from_stream == yaml_string_obj_from_string + assert (yaml_string == yaml_string_obj_from_stream == yaml_string_obj_from_string == yaml_string_stream_obj_from_stream == + yaml_string_stream_obj_from_string) + assert obj == obj_from_stream + assert obj == obj_from_string + assert obj == yaml_string_obj_from_stream + assert obj == yaml_string_obj_from_string + assert obj == obj_from_stream == obj_from_string == yaml_string_obj_from_stream == yaml_string_obj_from_string + return {'obj': obj, + 'yaml_string': yaml_string, + 'yaml_string_from_stream': yaml_string_from_stream, + 'obj_from_stream': obj_from_stream, + 'obj_from_string': obj_from_string, + 'yaml_string_obj_from_string': yaml_string_obj_from_string} diff --git a/ansible_collections/community/rabbitmq/tests/unit/modules/rabbitmq_user_fixtures.py b/ansible_collections/community/rabbitmq/tests/unit/modules/rabbitmq_user_fixtures.py new file mode 100644 index 000000000..c2b8925a2 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/unit/modules/rabbitmq_user_fixtures.py @@ -0,0 +1,181 @@ +rabbitmq_3_6_status = ''' +Status of node rabbit@vagrant +[{pid,5519}, + {running_applications, + [{rabbit,"RabbitMQ","version_num"}, + {mnesia,"MNESIA CXC 138 12","4.15.3"}, + {ranch,"Socket acceptor pool for TCP protocols.","1.3.0"}, + {ssl,"Erlang/OTP SSL application","8.2.3"}, + {public_key,"Public key infrastructure","1.5.2"}, + {asn1,"The Erlang ASN1 compiler version 5.0.4","5.0.4"}, + {rabbit_common, + "Modules shared by rabbitmq-server and rabbitmq-erlang-client", + "3.6.10"}, + {xmerl,"XML parser","1.3.16"}, + {crypto,"CRYPTO","4.2"}, + {compiler,"ERTS CXC 138 10","7.1.4"}, + {os_mon,"CPO CXC 138 46","2.4.4"}, + {syntax_tools,"Syntax tools","2.1.4"}, + {sasl,"SASL CXC 138 11","3.1.1"}, + {stdlib,"ERTS CXC 138 10","3.4.3"}, + {kernel,"ERTS CXC 138 10","5.4.1"}]}, + {os,{unix,linux}}, + {erlang_version, + "Erlang/OTP 20 [erts-9.2] [source] [64-bit] [smp:1:1] [ds:1:1:10] [async-threads:64] [kernel-poll:true]\n"}, + {memory, + [{total,49712064}, + {connection_readers,0}, + {connection_writers,0}, + {connection_channels,0}, + {connection_other,0}, + {queue_procs,2744}, + {queue_slave_procs,0}, + {plugins,0}, + {other_proc,17493000}, + {mnesia,65128}, + {metrics,184272}, + {mgmt_db,0}, + {msg_index,41832}, + {other_ets,1766176}, + {binary,43576}, + {code,21390833}, + {atom,891849}, + {other_system,8014118}]}, + {alarms,[]}, + {listeners,[{clustering,25672,"::"},{amqp,5672,"::"}]}, + {vm_memory_high_watermark,0.4}, + {vm_memory_limit,413340467}, + {disk_free_limit,50000000}, + {disk_free,61216505856}, + {file_descriptors, + [{total_limit,65436}, + {total_used,2}, + {sockets_limit,58890}, + {sockets_used,0}]}, + {processes,[{limit,1048576},{used,153}]}, + {run_queue,0}, + {uptime,1795}, + {kernel,{net_ticktime,60}}] +root@vagrant:/home/vagrant# rabbitmqctl -q status +[{pid,5519}, + {running_applications, + [{rabbit,"RabbitMQ","3.6.10"}, + {mnesia,"MNESIA CXC 138 12","4.15.3"}, + {ranch,"Socket acceptor pool for TCP protocols.","1.3.0"}, + {ssl,"Erlang/OTP SSL application","8.2.3"}, + {public_key,"Public key infrastructure","1.5.2"}, + {asn1,"The Erlang ASN1 compiler version 5.0.4","5.0.4"}, + {rabbit_common, + "Modules shared by rabbitmq-server and rabbitmq-erlang-client", + "3.6.10"}, + {xmerl,"XML parser","1.3.16"}, + {crypto,"CRYPTO","4.2"}, + {compiler,"ERTS CXC 138 10","7.1.4"}, + {os_mon,"CPO CXC 138 46","2.4.4"}, + {syntax_tools,"Syntax tools","2.1.4"}, + {sasl,"SASL CXC 138 11","3.1.1"}, + {stdlib,"ERTS CXC 138 10","3.4.3"}, + {kernel,"ERTS CXC 138 10","5.4.1"}]}, + {os,{unix,linux}}, + {erlang_version, + "Erlang/OTP 20 [erts-9.2] [source] [64-bit] [smp:1:1] [ds:1:1:10] [async-threads:64] [kernel-poll:true]\n"}, + {memory, + [{total,49770912}, + {connection_readers,0}, + {connection_writers,0}, + {connection_channels,0}, + {connection_other,0}, + {queue_procs,2744}, + {queue_slave_procs,0}, + {plugins,0}, + {other_proc,17554528}, + {mnesia,65128}, + {metrics,184272}, + {mgmt_db,0}, + {msg_index,41832}, + {other_ets,1766176}, + {binary,42816}, + {code,21390833}, + {atom,891849}, + {other_system,8012198}]}, + {alarms,[]}, + {listeners,[{clustering,25672,"::"},{amqp,5672,"::"}]}, + {vm_memory_high_watermark,0.4}, + {vm_memory_limit,413340467}, + {disk_free_limit,50000000}, + {disk_free,61216497664}, + {file_descriptors, + [{total_limit,65436}, + {total_used,2}, + {sockets_limit,58890}, + {sockets_used,0}]}, + {processes,[{limit,1048576},{used,153}]}, + {run_queue,0}, + {uptime,17139}, + {kernel,{net_ticktime,60}}]''' + +rabbitmq_3_7_status = \ + '{"pid":31701,"running_applications":[' \ + '["rabbit",[82,97,98,98,105,116,77,81],version_num],' \ + '["rabbit_common",[77,111,100,117,108,101,115,32,115,104,97,114,101,100,32,98,121,32,114,97,98,98,105,116,109,' \ + '113,45,115,101,114,118,101,114,32,97,110,100,32,114,97,98,98,105,116,109,113,45,101,114,108,97,110,103,45,99,' \ + '108,105,101,110,116],[51,46,55,46,54]],' \ + '["ranch_proxy_protocol",[82,97,110,99,104,32,80,114,111,120,121,32,80,114,111,116,111,99,111,108,32,84,114,97,' \ + '110,115,112,111,114,116],[49,46,53,46,48]],' \ + '["ranch",[83,111,99,107,101,116,32,97,99,99,101,112,116,111,114,32,112,111,111,108,32,102,111,114,32,84,67,80,' \ + '32,112,114,111,116,111,99,111,108,115,46],[49,46,53,46,48]],["ssl",[69,114,108,97,110,103,47,79,84,80,32,83,83,' \ + '76,32,97,112,112,108,105,99,97,116,105,111,110],[56,46,50,46,51]],' \ + '["public_key",[80,117,98,108,105,99,32,107,101,121,32,105,110,102,114,97,115,116,114,117,99,116,117,114,101],' \ + '[49,46,53,46,50]],' \ + '["asn1",[84,104,101,32,69,114,108,97,110,103,32,65,83,78,49,32,99,111,109,112,105,108,101,114,32,118,101,114,' \ + '115,105,111,110,32,53,46,48,46,52],[53,46,48,46,52]],' \ + '["crypto",[67,82,89,80,84,79],[52,46,50]],["xmerl",[88,77,76,32,112,97,114,115,101,114],[49,46,51,46,49,54]],' \ + '["recon",[68,105,97,103,110,111,115,116,105,99,32,116,111,111,108,115,32,102,111,114,32,112,114,111,100,117,99,' \ + '116,105,111,110,32,117,115,101],[50,46,51,46,50]],' \ + '["inets",[73,78,69,84,83,32,32,67,88,67,32,49,51,56,32,52,57],[54,46,52,46,53]],' \ + '["jsx",[97,32,115,116,114,101,97,109,105,110,103,44,32,101,118,101,110,116,101,100,32,106,115,111,110,32,112,97,' \ + '114,115,105,110,103,32,116,111,111,108,107,105,116],[50,46,56,46,50]],["os_mon",[67,80,79,32,32,67,88,67,32,49,' \ + '51,56,32,52,54],[50,46,52,46,52]],' \ + '["mnesia",[77,78,69,83,73,65,32,32,67,88,67,32,49,51,56,32,49,50],[52,46,49,53,46,51]],' \ + '["lager",[69,114,108,97,110,103,32,108,111,103,103,105,110,103,32,102,114,97,109,101,119,111,114,107],' \ + '[51,46,53,46,49]],' \ + '["goldrush",[69,114,108,97,110,103,32,101,118,101,110,116,32,115,116,114,101,97,109,32,112,114,111,99,101,115,' \ + '115,111,114],[48,46,49,46,57]],["compiler",[69,82,84,83,32,32,67,88,67,32,49,51,56,32,49,48],[55,46,49,46,52]],' \ + '["syntax_tools",[83,121,110,116,97,120,32,116,111,111,108,115],[50,46,49,46,52]],' \ + '["syslog",[65,110,32,82,70,67,32,51,49,54,52,32,97,110,100,32,82,70,67,32,53,52,50,52,32,99,111,109,112,108,' \ + '105,97,110,116,32,108,111,103,103,105,110,103,32,102,114,97,109,101,119,111,114,107,46],[51,46,52,46,50]],' \ + '["sasl",[83,65,83,76,32,32,67,88,67,32,49,51,56,32,49,49],[51,46,49,46,49]],' \ + '["stdlib",[69,82,84,83,32,32,67,88,67,32,49,51,56,32,49,48],[51,46,52,46,51]],' \ + '["kernel",[69,82,84,83,32,32,67,88,67,32,49,51,56,32,49,48],[53,46,52,46,49]]],' \ + '"os":["unix","linux"],"erlang_version":[69,114,108,97,110,103,47,79,84,80,32,50,48,32,91,101,114,116,115,45,57,' \ + '46,50,93,32,91,115,111,117,114,99,101,93,32,91,54,52,45,98,105,116,93,32,91,115,109,112,58,49,58,49,93,32,91,' \ + '100,115,58,49,58,49,58,49,48,93,32,91,97,115,121,110,99,45,116,104,114,101,97,100,115,58,54,52,93,32,91,107,' \ + '101,114,110,101,108,45,112,111,108,108,58,116,114,117,101,93,10],' \ + '"memory":{"connection_readers":0,"connection_writers":0,"connection_channels":0,' \ + '"connection_other":0,"queue_procs":0,"queue_slave_procs":0,"plugins":5736,"other_proc":23159832,' \ + '"metrics":184608,"mgmt_db":0,"mnesia":76896,"other_ets":1882856,"binary":64120,"msg_index":57184,' \ + '"code":24981937,"atom":1041593,"other_system":8993494,"allocated_unused":13066752,"reserved_unallocated":0,' \ + '"strategy":"rss","total":{"erlang":60448256,"rss":72720384,"allocated":73515008}},"alarms":[],' \ + '"listeners":[["clustering",25672,[58,58]],["amqp",5672,[58,58]]],"vm_memory_calculation_strategy":"rss",' \ + '"vm_memory_high_watermark":0.4,"vm_memory_limit":413340467,"disk_free_limit":50000000,"disk_free":61108576256,' \ + '"file_descriptors":{"total_limit":924,"total_used":4,"sockets_limit":829,"sockets_used":0},' \ + '"processes":{"limit":1048576,"used":214},"run_queue":0,"uptime":173,"kernel":["net_ticktime",60]}' + +rabbitmq_3_8_status = \ + '{"active_plugins":[],"alarms":[],"config_files":[],"data_directory":"/var/lib/rabbitmq/mnesia/rabbit@vagrant",' \ + '"disk_free":60898615296,"disk_free_limit":50000000,"enabled_plugin_file":"/etc/rabbitmq/enabled_plugins",' \ + '"erlang_version":"Erlang/OTP 21 [erts-10.3.5.8] [source] [64-bit] [smp:1:1] [ds:1:1:10] [async-threads:64]",' \ + '"file_descriptors":{"sockets_limit":29399,"sockets_used":0,"total_limit":32668,"total_used":4},' \ + '"listeners":[{"interface":"[::]","node":"rabbit@vagrant","port":25672,"protocol":"clustering",' \ + '"purpose":"inter-node and CLI tool communication"},{"interface":"[::]","node":"rabbit@vagrant",' \ + '"port":5672,"protocol":"amqp","purpose":"AMQP 0-9-1 and AMQP 1.0"}],' \ + '"log_files":["/var/log/rabbitmq/rabbit@vagrant.log","/var/log/rabbitmq/rabbit@vagrant_upgrade.log"],' \ + '"memory":{"allocated_unused":14962432,"atom":1180881,"binary":82304,"code":26631176,"connection_channels":0,' \ + '"connection_other":0,"connection_readers":0,"connection_writers":0,"metrics":195308,"mgmt_db":0,"mnesia":76896,' \ + '"msg_index":57088,"other_ets":2666736,"other_proc":25333896,"other_system":10068879,"plugins":11732,' \ + '"queue_procs":0,"queue_slave_procs":0,"quorum_ets":42368,"quorum_queue_procs":0,"reserved_unallocated":0,' \ + '"strategy":"rss","total":{"erlang":66347264,"rss":80506880,"allocated":81309696}},"net_ticktime":60,' \ + '"os":"Linux","pid":9829,"processes":{"limit":1048576,"used":259},"rabbitmq_version":"version_num","run_queue":1,' \ + '"totals":{"virtual_host_count":2,"connection_count":0,"queue_count":0},"uptime":66,' \ + '"vm_memory_calculation_strategy":"rss","vm_memory_high_watermark_limit":413340467,' \ + '"vm_memory_high_watermark_setting":{"relative":0.4}}' diff --git a/ansible_collections/community/rabbitmq/tests/unit/modules/test_rabbitmq_feature_flag.py b/ansible_collections/community/rabbitmq/tests/unit/modules/test_rabbitmq_feature_flag.py new file mode 100644 index 000000000..b37e1c044 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/unit/modules/test_rabbitmq_feature_flag.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible_collections.community.rabbitmq.plugins.modules import rabbitmq_feature_flag + +from ansible_collections.community.rabbitmq.tests.unit.compat.mock import patch +from ansible_collections.community.rabbitmq.tests.unit.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args + + +class TestRabbitMQFeatureFlagModule(ModuleTestCase): + def setUp(self): + super(TestRabbitMQFeatureFlagModule, self).setUp() + self.module = rabbitmq_feature_flag + + def tearDown(self): + super(TestRabbitMQFeatureFlagModule, self).tearDown() + + def _assert(self, exc, attribute, expected_value, msg=''): + value = exc.message[attribute] if hasattr(exc, attribute) else exc.args[0][attribute] + assert value == expected_value, msg + + def test_without_required_parameters(self): + """Failure must occur when all parameters are missing.""" + with self.assertRaises(AnsibleFailJson): + set_module_args({}) + self.module.main() + + @patch('ansible.module_utils.basic.AnsibleModule.get_bin_path') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_feature_flag.RabbitMqFeatureFlag._exec') + def test_enable_feature_flag(self, _exec, get_bin_path): + """Test enabling feature flag.""" + set_module_args({ + 'name': 'maintenance_mode_status', + 'node': 'rabbit@node-1', + }) + get_bin_path.return_value = '/rabbitmqctl' + + for out in 'name\tstate\nmaintenance_mode_status\tdisabled', 'name\tstate\nmaintenance_mode_status\tdisabled\n': + _exec.return_value = out.splitlines() + try: + self.module.main() + except AnsibleExitJson as e: + self._assert(e, 'changed', True) + + @patch('ansible.module_utils.basic.AnsibleModule.get_bin_path') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_feature_flag.RabbitMqFeatureFlag._exec') + def test_enable_no_change_feature_flag(self, _exec, get_bin_path): + """Test that there is no change when enabling feature flag which is already enabled""" + set_module_args({ + 'name': 'maintenance_mode_status', + 'node': 'rabbit@node-1', + }) + get_bin_path.return_value = '/rabbitmqctl' + + for out in 'name\tstate\nmaintenance_mode_status\tenabled', 'name\tstate\nmaintenance_mode_status\tenabled\n': + _exec.return_value = out.splitlines() + try: + self.module.main() + except AnsibleExitJson as e: + self._assert(e, 'changed', False) diff --git a/ansible_collections/community/rabbitmq/tests/unit/modules/test_rabbitmq_global_parameter.py b/ansible_collections/community/rabbitmq/tests/unit/modules/test_rabbitmq_global_parameter.py new file mode 100644 index 000000000..6ddabbf77 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/unit/modules/test_rabbitmq_global_parameter.py @@ -0,0 +1,127 @@ +# -*- coding: utf-8 -*- + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible_collections.community.rabbitmq.plugins.modules import rabbitmq_global_parameter + +from ansible_collections.community.rabbitmq.tests.unit.compat.mock import patch +from ansible_collections.community.rabbitmq.tests.unit.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args + + +class TestRabbitMQGlobalParameterModule(ModuleTestCase): + def setUp(self): + super(TestRabbitMQGlobalParameterModule, self).setUp() + self.module = rabbitmq_global_parameter + + def tearDown(self): + super(TestRabbitMQGlobalParameterModule, self).tearDown() + + def _assert(self, exc, attribute, expected_value, msg=''): + value = exc.message[attribute] if hasattr(exc, attribute) else exc.args[0][attribute] + assert value == expected_value, msg + + def test_without_required_parameters(self): + """Failure must occur when all parameters are missing.""" + with self.assertRaises(AnsibleFailJson): + set_module_args({}) + self.module.main() + + @patch('ansible.module_utils.basic.AnsibleModule.get_bin_path') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_global_parameter.RabbitMqGlobalParameter._exec') + def test_read_without_initial_global_parameters(self, _exec, get_bin_path): + """Test that the code to read the global parameters does not fail anymore for RabbitMQ 3.7.x.""" + set_module_args({ + 'name': 'cluster_name', + 'state': 'absent', + }) + get_bin_path.return_value = '/rabbitmqctl' + + # command list_global_parameters returns: + # - RabbitMQ 3.6.x: '' + # - RabbitMQ 3.7.x: '\n' + # - RabbitMQ 3.8.x: 'name\tvalue\n' table header + for out in '', '\n', 'name\tvalue\n': + _exec.return_value = out.splitlines() + try: + self.module.main() + except AnsibleExitJson as e: + self._assert(e, 'changed', False) + self._assert(e, 'state', 'absent') + + @patch('ansible.module_utils.basic.AnsibleModule.get_bin_path') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_global_parameter.RabbitMqGlobalParameter._exec') + def test_remove_global_parameter(self, _exec, get_bin_path): + """Test removal of global parameters.""" + set_module_args({ + 'name': 'cluster_name', + 'state': 'absent', + }) + get_bin_path.return_value = '/rabbitmqctl' + + # command list_global_parameters returns: + # - RabbitMQ 3.6.x: '' + # - RabbitMQ 3.7.x: '\n' + # - RabbitMQ 3.8.x: 'name\tvalue\n' table header + for out in 'cluster_name\t"rabbitmq-test"', 'cluster_name\t"rabbitmq-test"\n', 'name\tvalue\ncluster_name\t"rabbitmq-test"\n': + _exec.return_value = out.splitlines() + try: + self.module.main() + except AnsibleExitJson as e: + self._assert(e, 'changed', True) + self._assert(e, 'state', 'absent') + + @patch('ansible.module_utils.basic.AnsibleModule.get_bin_path') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_global_parameter.RabbitMqGlobalParameter._exec') + def test_set_global_parameter(self, _exec, get_bin_path): + """Test setting of global parameters.""" + set_module_args({ + 'name': 'cluster_name', + 'value': '"rabbitmq-test"', + 'state': 'present', + }) + get_bin_path.return_value = '/rabbitmqctl' + + versions = ['3.6', '3.7', '3.8'] + for version_num in versions: + def side_effect(args, check_rc=True): + if 'list_global_parameters' in args: + if version_num == '3.6': + return 'other_param\t"other_value"\ncluster_name\t"another_name"'.splitlines() + elif version_num == '3.7': + return 'other_param\t"other_value"\ncluster_name\t"another_name"\n'.splitlines() + else: + return 'name\tvalue\nother_param\t"other_value"\ncluster_name\t"another_name"\n'.splitlines() + elif 'clear_global_parameter' in args or 'set_global_parameter' in args: + return ''.splitlines() + _exec.side_effect = side_effect + try: + self.module.main() + except AnsibleExitJson as e: + self._assert(e, 'changed', True) + self._assert(e, 'state', 'present') + self._assert(e, 'value', 'rabbitmq-test') + + @patch('ansible.module_utils.basic.AnsibleModule.get_bin_path') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_global_parameter.RabbitMqGlobalParameter._exec') + def test_set_no_change_global_parameter(self, _exec, get_bin_path): + """Test that there is no change when setting the same global parameter.""" + set_module_args({ + 'name': 'cluster_name', + 'value': '"rabbitmq-test"', + 'state': 'present', + }) + get_bin_path.return_value = '/rabbitmqctl' + + def side_effect(args, check_rc=True): + if 'list_global_parameters' in args: + return 'other_param\t"other_value"\ncluster_name\t"rabbitmq-test"'.splitlines() + elif 'clear_global_parameter' in args or 'set_global_parameter' in args: + return ''.splitlines() + _exec.side_effect = side_effect + try: + self.module.main() + except AnsibleExitJson as e: + self._assert(e, 'changed', False) + self._assert(e, 'state', 'present') + self._assert(e, 'value', 'rabbitmq-test') diff --git a/ansible_collections/community/rabbitmq/tests/unit/modules/test_rabbitmq_upgrade.py b/ansible_collections/community/rabbitmq/tests/unit/modules/test_rabbitmq_upgrade.py new file mode 100644 index 000000000..c24115307 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/unit/modules/test_rabbitmq_upgrade.py @@ -0,0 +1,201 @@ +# -*- coding: utf-8 -*- + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible_collections.community.rabbitmq.plugins.modules import rabbitmq_upgrade + +from ansible_collections.community.rabbitmq.tests.unit.compat.mock import patch +from ansible_collections.community.rabbitmq.tests.unit.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args + + +class TestRabbitMQUpgradeModule(ModuleTestCase): + def setUp(self): + super(TestRabbitMQUpgradeModule, self).setUp() + self.module = rabbitmq_upgrade + + def tearDown(self): + super(TestRabbitMQUpgradeModule, self).tearDown() + + def _assert(self, exc, attribute, expected_value, msg=''): + value = exc.message[attribute] if hasattr(exc, attribute) else exc.args[0][attribute] + assert value == expected_value, msg + + def test_without_required_parameters(self): + """Failure must occur when all parameters are missing.""" + with self.assertRaises(AnsibleFailJson): + set_module_args({}) + self.module.main() + + @patch('ansible.module_utils.basic.AnsibleModule.get_bin_path') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_upgrade.RabbitMqUpgrade._exec') + def test_without_maitenance_mode_status_feature_flag(self, _exec, get_bin_path): + """Failure must occur when maintenance_mode_status feature_flag is disabled/not available""" + with self.assertRaises(AnsibleFailJson): + set_module_args({ + 'action': 'drain', + 'node': 'rabbit@node-1', + }) + get_bin_path.return_value = '/rabbitmqctl' + + def side_effect(*args, **kwargs): + if args[0] == 'rabbitmq-diagnostics': + out = '{"active_plugins": ["rabbitmq_management", "amqp_client", "rabbitmq_web_dispatch", "cowboy",'\ + '"cowlib", "rabbitmq_management_agent"], "is_under_maintenance": false}' + elif args[0] == 'rabbitmqctl': + out = 'name\tstate\nmaintenance_mode_status\tdisabled' + else: + out = '' + return out.splitlines() + + _exec.side_effect = side_effect + self.module.main() + + @patch('ansible.module_utils.basic.AnsibleModule.get_bin_path') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_upgrade.RabbitMqUpgrade._exec') + def test_drain_node(self, _exec, get_bin_path): + """Execute action: drain on active node""" + set_module_args({ + 'action': 'drain', + 'node': 'rabbit@node-1', + }) + get_bin_path.return_value = '/rabbitmqctl' + + def side_effect(*args, **kwargs): + if args[0] == 'rabbitmq-diagnostics': + out = '{"active_plugins": ["rabbitmq_management", "amqp_client", "rabbitmq_web_dispatch", "cowboy",'\ + '"cowlib", "rabbitmq_management_agent"], "is_under_maintenance": false}' + elif args[0] == 'rabbitmqctl': + out = 'name\tstate\nmaintenance_mode_status\tenabled' + else: + out = '' + return out.splitlines() + + _exec.side_effect = side_effect + try: + self.module.main() + except AnsibleExitJson as e: + self._assert(e, 'changed', True) + + @patch('ansible.module_utils.basic.AnsibleModule.get_bin_path') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_upgrade.RabbitMqUpgrade._exec') + def test_no_change_drain_node(self, _exec, get_bin_path): + """Execute action: drain on already disabled node""" + set_module_args({ + 'action': 'drain', + 'node': 'rabbit@node-1', + }) + get_bin_path.return_value = '/rabbitmqctl' + + def side_effect(*args, **kwargs): + if args[0] == 'rabbitmq-diagnostics': + out = '{"active_plugins": ["rabbitmq_management", "amqp_client", "rabbitmq_web_dispatch", "cowboy",'\ + '"cowlib", "rabbitmq_management_agent"], "is_under_maintenance": true}' + elif args[0] == 'rabbitmqctl': + out = 'name\tstate\nmaintenance_mode_status\tenabled' + else: + out = '' + return out.splitlines() + + _exec.side_effect = side_effect + try: + self.module.main() + except AnsibleExitJson as e: + self._assert(e, 'changed', False) + + @patch('ansible.module_utils.basic.AnsibleModule.get_bin_path') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_upgrade.RabbitMqUpgrade._exec') + def test_revive_node(self, _exec, get_bin_path): + """Execute action: revive on disabled node""" + set_module_args({ + 'action': 'revive', + 'node': 'rabbit@node-1', + }) + get_bin_path.return_value = '/rabbitmqctl' + + def side_effect(*args, **kwargs): + if args[0] == 'rabbitmq-diagnostics': + out = '{"active_plugins": ["rabbitmq_management", "amqp_client", "rabbitmq_web_dispatch", "cowboy",'\ + '"cowboy", "cowlib", "rabbitmq_management_agent"], "is_under_maintenance": true}' + elif args[0] == 'rabbitmqctl': + out = 'name\tstate\nmaintenance_mode_status\tenabled' + else: + out = '' + return out.splitlines() + + _exec.side_effect = side_effect + try: + self.module.main() + except AnsibleExitJson as e: + self._assert(e, 'changed', True) + + @patch('ansible.module_utils.basic.AnsibleModule.get_bin_path') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_upgrade.RabbitMqUpgrade._exec') + def test_no_change_revive_node(self, _exec, get_bin_path): + """Execute action: revive on active node""" + set_module_args({ + 'action': 'revive', + 'node': 'rabbit@node-1', + }) + get_bin_path.return_value = '/rabbitmqctl' + + def side_effect(*args, **kwargs): + if args[0] == 'rabbitmq-diagnostics': + out = '{"active_plugins": ["rabbitmq_management", "amqp_client", "rabbitmq_web_dispatch", "cowboy",'\ + '"cowlib", "rabbitmq_management_agent"], "is_under_maintenance": false}' + elif args[0] == 'rabbitmqctl': + out = 'name\tstate\nmaintenance_mode_status\tenabled' + else: + out = '' + return out.splitlines() + + _exec.side_effect = side_effect + try: + self.module.main() + except AnsibleExitJson as e: + self._assert(e, 'changed', False) + + @patch('ansible.module_utils.basic.AnsibleModule.get_bin_path') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_upgrade.RabbitMqUpgrade._exec') + def test_await_online_quorum_plus_one(self, _exec, get_bin_path): + """Execute action: await_online_quorum_plus_one""" + set_module_args({ + 'action': 'await_online_quorum_plus_one', + 'node': 'rabbit@node-1', + }) + get_bin_path.return_value = '/rabbitmqctl' + + try: + self.module.main() + except AnsibleExitJson as e: + self._assert(e, 'changed', True) + + @patch('ansible.module_utils.basic.AnsibleModule.get_bin_path') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_upgrade.RabbitMqUpgrade._exec') + def test_await_online_synchronized_mirror(self, _exec, get_bin_path): + """Execute action: await_online_synchronized_mirror""" + set_module_args({ + 'action': 'await_online_synchronized_mirror', + 'node': 'rabbit@node-1', + }) + get_bin_path.return_value = '/rabbitmqctl' + + try: + self.module.main() + except AnsibleExitJson as e: + self._assert(e, 'changed', True) + + @patch('ansible.module_utils.basic.AnsibleModule.get_bin_path') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_upgrade.RabbitMqUpgrade._exec') + def test_post_upgrade(self, _exec, get_bin_path): + """Execute action: post_upgrade""" + set_module_args({ + 'action': 'post_upgrade', + 'node': 'rabbit@node-1', + }) + get_bin_path.return_value = '/rabbitmqctl' + + try: + self.module.main() + except AnsibleExitJson as e: + self._assert(e, 'changed', True) diff --git a/ansible_collections/community/rabbitmq/tests/unit/modules/test_rabbitmq_user.py b/ansible_collections/community/rabbitmq/tests/unit/modules/test_rabbitmq_user.py new file mode 100644 index 000000000..eb82a0bdc --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/unit/modules/test_rabbitmq_user.py @@ -0,0 +1,533 @@ +# -*- coding: utf-8 -*- + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible_collections.community.rabbitmq.plugins.module_utils import version +from ansible_collections.community.rabbitmq.plugins.modules import rabbitmq_user +from ansible.module_utils import six +from itertools import chain + +if six.PY3: + from itertools import zip_longest +else: + from itertools import izip_longest as zip_longest + +from ansible_collections.community.rabbitmq.tests.unit.compat.mock import patch +from ansible_collections.community.rabbitmq.tests.unit.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args + +from ansible_collections.community.rabbitmq.tests.unit.modules.rabbitmq_user_fixtures import ( + rabbitmq_3_6_status, + rabbitmq_3_7_status, + rabbitmq_3_8_status) + + +def flatten(args): + return list(chain(*args)) + + +def lists_equal(l1, l2): + return all(map(lambda t: t[0] == t[1], zip_longest(l1, l2))) + + +class TestRabbitMQUserModule(ModuleTestCase): + def setUp(self): + super(TestRabbitMQUserModule, self).setUp() + self.module = rabbitmq_user + + def tearDown(self): + super(TestRabbitMQUserModule, self).tearDown() + + def _assert(self, exc, attribute, expected_value, msg=''): + value = exc.message[attribute] if hasattr(exc, attribute) else exc.args[0][attribute] + assert value == expected_value, msg + + def test_without_required_parameters(self): + """Failure must occur when all parameters are missing.""" + with self.assertRaises(AnsibleFailJson): + set_module_args({}) + self.module.main() + + @patch('ansible.module_utils.basic.AnsibleModule.get_bin_path') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_user.RabbitMqUser._check_version') + def test_permissions_with_same_vhost(self, _check_version, get_bin_path): + set_module_args({ + 'user': 'someuser', + 'password': 'somepassword', + 'state': 'present', + 'permissions': [{'vhost': '/'}, {'vhost': '/'}], + }) + _check_version.return_value = version.StrictVersion('3.6.10') + get_bin_path.return_value = '/rabbitmqctl' + try: + self.module.main() + except AnsibleFailJson as e: + self._assert(e, 'failed', True) + self._assert(e, 'msg', "Error parsing vhost " + "permissions: You can't have two permission dicts for the same vhost") + + @patch('ansible.module_utils.basic.AnsibleModule.get_bin_path') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_user.RabbitMqUser._check_version') + def test_topic_permissions_with_same_vhost(self, _check_version, get_bin_path): + set_module_args({ + 'user': 'someuser', + 'password': 'somepassword', + 'state': 'present', + 'topic_permissions': [{'vhost': '/', 'exchange': 'amq.topic'}, {'vhost': '/', 'exchange': 'amq.topic'}], + }) + _check_version.return_value = version.StrictVersion('3.6.10') + get_bin_path.return_value = '/rabbitmqctl' + try: + self.module.main() + except AnsibleFailJson as e: + self._assert(e, 'failed', True) + self._assert(e, 'msg', "Error parsing vhost topic_permissions: " + "You can't have two topic permission dicts for " + "the same vhost and the same exchange") + + @patch('ansible.module_utils.basic.AnsibleModule.get_bin_path') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_user.RabbitMqUser.get') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_user.RabbitMqUser._check_version') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_user.RabbitMqUser.check_password') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_user.RabbitMqUser.has_tags_modifications') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_user.RabbitMqUser.has_permissions_modifications') + def test_password_changes_only_when_needed(self, + has_permissions_modifications, + has_tags_modifications, + check_password, + _check_version, + get, + get_bin_path): + set_module_args({ + 'user': 'someuser', + 'password': 'somepassword', + 'state': 'present', + 'update_password': 'always', + }) + get.return_value = True + _check_version.return_value = version.StrictVersion('3.6.10') + get_bin_path.return_value = '/rabbitmqctl' + check_password.return_value = True + has_tags_modifications.return_value = False + has_permissions_modifications.return_value = False + try: + self.module.main() + except AnsibleExitJson as e: + self._assert(e, 'changed', False) + self._assert(e, 'state', 'present') + + @patch('ansible.module_utils.basic.AnsibleModule.get_bin_path') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_user.RabbitMqUser._exec') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_user.RabbitMqUser._check_version') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_user.RabbitMqUser._get_permissions') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_user.RabbitMqUser.has_tags_modifications') + def test_same_permissions_not_changing(self, + has_tags_modifications, + _get_permissions, + _check_version, + _exec, + get_bin_path): + set_module_args({ + 'user': 'someuser', + 'password': 'somepassword', + 'state': 'present', + 'permissions': [{'vhost': '/', 'configure_priv': '.*', 'write_priv': '.*', 'read_priv': '.*'}], + }) + _get_permissions.return_value = {'/': {'read': '.*', 'write': '.*', 'configure': '.*', 'vhost': '/'}} + _exec.return_value = 'someuser\t[]' + _check_version.return_value = version.StrictVersion('3.6.10') + get_bin_path.return_value = '/rabbitmqctl' + has_tags_modifications.return_value = False + try: + self.module.main() + except AnsibleExitJson as e: + self._assert(e, 'changed', False) + self._assert(e, 'state', 'present') + + @patch('ansible.module_utils.basic.AnsibleModule.get_bin_path') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_user.RabbitMqUser._exec') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_user.RabbitMqUser._check_version') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_user.RabbitMqUser._get_topic_permissions') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_user.RabbitMqUser._get_permissions') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_user.RabbitMqUser.has_tags_modifications') + def test_same_topic_permissions_not_changing(self, + has_tags_modifications, + _get_permissions, + _get_topic_permissions, + _check_version, + _exec, + get_bin_path): + set_module_args({ + 'user': 'someuser', + 'password': 'somepassword', + 'state': 'present', + 'topic_permissions': [{'vhost': '/', 'exchange': 'amq.topic', 'write': '.*', 'read': '.*'}], + }) + _get_permissions.return_value = {'/': {'configure': '^$', 'read': '^$', 'write': '^$', 'vhost': '/'}} + _get_topic_permissions.return_value = {('/', 'amq.topic'): {'read': '.*', 'write': '.*', 'vhost': '/', 'exchange': 'amq.topic'}} + _exec.return_value = 'someuser\t[]' + _check_version.return_value = version.StrictVersion('3.6.10') + get_bin_path.return_value = '/rabbitmqctl' + has_tags_modifications.return_value = False + try: + self.module.main() + except AnsibleExitJson as e: + self._assert(e, 'changed', False) + self._assert(e, 'state', 'present') + + @patch('ansible.module_utils.basic.AnsibleModule') + def test_status_can_be_parsed(self, module): + """Test correct parsing of the output of the status command.""" + module.get_bin_path.return_value = '/rabbitmqctl' + module.check_mode = False + + versions = ['3.6.10', '3.6.16'] + for version_num in versions: + def side_effect(args): + assert '-q' in args + if '--formatter' in args: + return 64, '', '' + return 0, rabbitmq_3_6_status.replace('version_num', version_num), '' + + module.run_command.side_effect = side_effect + user_controller = rabbitmq_user.RabbitMqUser(module, 'someuser', 'somepassword', list(), list(), list(), 'rabbit') + self.assertEqual(len(module.run_command.call_args_list), 2) + last_call_args = flatten(module.run_command.call_args_list[-1][0]) + self.assertTrue('-q' in last_call_args) + self.assertTrue('--formatter' not in last_call_args) + self.assertEqual(user_controller._version, version.StrictVersion(version_num)) + module.run_command.reset_mock() + + versions = ['3.7.6', '3.7.7', '3.7.8', '3.7.9', '3.7.10', '3.7.11', '3.7.12', '3.7.13', '3.7.14', '3.7.15', + '3.7.16', '3.7.17', '3.7.18', '3.7.19', '3.7.20', '3.7.21', '3.7.22', '3.7.23'] + for version_num in versions: + def side_effect(args): + self.assertTrue('-q' in args) + self.assertTrue('--formatter' in args) + self.assertTrue('json' in args) + return 0, rabbitmq_3_7_status.replace('version_num', str([ord(c) for c in version_num])), '' + + module.run_command.side_effect = side_effect + user_controller = rabbitmq_user.RabbitMqUser(module, 'someuser', 'somepassword', list(), list(), list(), 'rabbit') + self.assertEqual(1, module.run_command.call_count) + self.assertEqual(user_controller._version, version.StrictVersion(version_num)) + module.run_command.reset_mock() + + versions = ['3.8.0', '3.8.1', '3.8.2'] + for version_num in versions: + def side_effect(args): + self.assertTrue('-q' in args) + self.assertTrue('--formatter' in args) + self.assertTrue('json' in args) + return 0, rabbitmq_3_8_status.replace('version_num', version_num), '' + + module.run_command.side_effect = side_effect + user_controller = rabbitmq_user.RabbitMqUser(module, 'someuser', 'somepassword', list(), list(), list(), 'rabbit') + self.assertEqual(1, module.run_command.call_count) + self.assertEqual(user_controller._version, version.StrictVersion(version_num)) + module.run_command.reset_mock() + + @patch('ansible.module_utils.basic.AnsibleModule.get_bin_path') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_user.RabbitMqUser._exec') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_user.RabbitMqUser._check_version') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_user.RabbitMqUser._get_permissions') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_user.RabbitMqUser.has_tags_modifications') + def test_permissions_are_fixed(self, + has_tags_modifications, + _get_permissions, + _check_version, + _exec, + get_bin_path): + """Test changes in permissions are fixed. + + Ensure that permissions that do not need to be changed are not, permissions with differences are + fixed and permissions are cleared when needed, with the minimum number of operations. The + permissions are fed into the module using the pre-3.7 version format. + """ + set_module_args({ + 'user': 'someuser', + 'password': 'somepassword', + 'state': 'present', + 'permissions': [ + {'vhost': '/', 'configure_priv': '.*', 'write_priv': '.*', 'read_priv': '.*'}, + {'vhost': '/ok', 'configure': '^$', 'write': '^$', 'read': '^$'} + ], + }) + get_bin_path.return_value = '/rabbitmqctl' + has_tags_modifications.return_value = False + _check_version.return_value = version.StrictVersion('3.6.10') + _get_permissions.return_value = { + '/wrong_vhost': {'vhost': '/wrong_vhost', 'configure': '', 'write': '', 'read': ''}, + '/ok': {'vhost': '/ok', 'configure': '^$', 'write': '^$', 'read': '^$'} + } + + def side_effect(args): + if 'list_users' in args: + self.assertTrue('--formatter' not in args) + self.assertTrue('json' not in args) + return 'someuser\t[administrator, management]' + if 'clear_permissions' in args: + self.assertTrue('someuser' in args) + self.assertTrue('/wrong_vhost' in args) + return '' + if 'set_permissions' in args: + self.assertTrue('someuser' in args) + self.assertTrue('/' in args) + self.assertTrue(['.*', '.*', '.*'] == args[-3:]) + return '' + _exec.side_effect = side_effect + + try: + self.module.main() + except AnsibleExitJson as e: + self._assert(e, 'changed', True) + self._assert(e, 'state', 'present') + self.assertEqual(_exec.call_count, 3) + self.assertTrue(['clear_permissions', '-p', '/wrong_vhost', 'someuser'] == + flatten(_exec.call_args_list[-2][0])) + self.assertTrue(['set_permissions', '-p', '/', 'someuser', '.*', '.*', '.*'] == + flatten(_exec.call_args_list[-1][0])) + + @patch('ansible.module_utils.basic.AnsibleModule.get_bin_path') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_user.RabbitMqUser._exec') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_user.RabbitMqUser._check_version') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_user.RabbitMqUser._get_topic_permissions') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_user.RabbitMqUser._get_permissions') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_user.RabbitMqUser.has_tags_modifications') + def test_topic_permissions_are_fixed(self, + has_tags_modifications, + _get_permissions, + _get_topic_permissions, + _check_version, + _exec, + get_bin_path): + """Test changes in topic permissions are fixed. + + Ensure that topic permissions that do not need to be changed are not, topic permissions with differences are + fixed and topic permissions are cleared when needed, with the minimum number of operations. + """ + set_module_args({ + 'user': 'someuser', + 'password': 'somepassword', + 'state': 'present', + 'topic_permissions': [ + {'vhost': '/', 'exchange': 'amq.topic', 'write_priv': '.*', 'read_priv': '.*'}, + {'vhost': '/ok', 'exchange': 'amq.topic', 'write': '^$', 'read': '^$'} + ], + }) + get_bin_path.return_value = '/rabbitmqctl' + has_tags_modifications.return_value = False + _check_version.return_value = version.StrictVersion('3.6.10') + _get_permissions.return_value = {} + _get_topic_permissions.return_value = { + ('/wrong_vhost', 'amq.topic'): {'vhost': '/wrong_vhost', 'exchange': 'amq.topic', 'write': '', 'read': ''}, + ('/ok', 'amq.topic'): {'vhost': '/ok', 'exchange': 'amq.topic', 'write': '^$', 'read': '^$'} + } + + def side_effect(args): + if 'list_users' in args: + self.assertTrue('--formatter' not in args) + self.assertTrue('json' not in args) + return 'someuser\t[administrator, management]' + if 'clear_topic_permissions' in args: + self.assertTrue('someuser' in args) + self.assertTrue('/wrong_vhost' in args) + return '' + if 'set_topic_permissions' in args: + self.assertTrue('someuser' in args) + self.assertTrue('/' in args, args) + self.assertTrue(['amq.topic', '.*', '.*'] == args[-3:]) + return '' + _exec.side_effect = side_effect + + try: + self.module.main() + except AnsibleExitJson as e: + self._assert(e, 'changed', True) + self._assert(e, 'state', 'present') + self.assertEqual(_exec.call_count, 4) + self.assertTrue(['clear_topic_permissions', '-p', '/wrong_vhost', 'someuser', 'amq.topic'] == + flatten(_exec.call_args_list[-2][0])) + self.assertTrue(['set_topic_permissions', '-p', '/', 'someuser', 'amq.topic', '.*', '.*'] == + flatten(_exec.call_args_list[-1][0])) + + @patch('ansible.module_utils.basic.AnsibleModule.get_bin_path') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_user.RabbitMqUser._exec') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_user.RabbitMqUser._check_version') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_user.RabbitMqUser._get_topic_permissions') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_user.RabbitMqUser._get_permissions') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_user.RabbitMqUser.has_tags_modifications') + def test_topic_permissions_defaults(self, + has_tags_modifications, + _get_permissions, + _get_topic_permissions, + _check_version, + _exec, + get_bin_path): + """Test that the topic permissions defaults are set.""" + set_module_args({ + 'user': 'someuser', + 'password': 'somepassword', + 'state': 'present', + 'topic_permissions': [ + {'write_priv': '.*', 'read_priv': '.*'}, + ], + }) + get_bin_path.return_value = '/rabbitmqctl' + has_tags_modifications.return_value = False + _check_version.return_value = version.StrictVersion('3.6.10') + _get_permissions.return_value = {} + _get_topic_permissions.return_value = {} + + def side_effect(args): + if 'list_users' in args: + self.assertTrue('--formatter' not in args) + self.assertTrue('json' not in args) + return 'someuser\t[administrator, management]' + if 'set_topic_permissions' in args: + self.assertTrue('someuser' in args) + self.assertTrue('/' in args, args) + self.assertTrue(['amq.topic', '.*', '.*'] == args[-3:]) + return '' + _exec.side_effect = side_effect + + try: + self.module.main() + except AnsibleExitJson as e: + self._assert(e, 'changed', True) + self._assert(e, 'state', 'present') + self.assertEqual(_exec.call_count, 3) + self.assertTrue(['set_topic_permissions', '-p', '/', 'someuser', 'amq.topic', '.*', '.*'] == + flatten(_exec.call_args_list[-1][0])) + + @patch('ansible.module_utils.basic.AnsibleModule.get_bin_path') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_user.RabbitMqUser._exec') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_user.RabbitMqUser._check_version') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_user.RabbitMqUser._get_permissions') + def test_tags_are_fixed(self, _get_permissions, _check_version, _exec, get_bin_path): + """Test user tags are fixed.""" + set_module_args({ + 'user': 'someuser', + 'password': 'somepassword', + 'state': 'present', + 'tags': 'tag1,tags2', + }) + get_bin_path.return_value = '/rabbitmqctl' + _check_version.return_value = version.StrictVersion('3.6.10') + _get_permissions.return_value = {'/': {'vhost': '/', 'configure': '^$', 'write': '^$', 'read': '^$'}} + + def side_effect(args): + if 'list_users' in args: + self.assertTrue('--formatter' not in args) + self.assertTrue('json' not in args) + return 'someuser\t[tag1, tag3]' + return '' + _exec.side_effect = side_effect + + try: + self.module.main() + except AnsibleExitJson as e: + self._assert(e, 'changed', True) + self._assert(e, 'state', 'present') + self.assertEqual(_exec.call_count, 2) + self.assertTrue(lists_equal(['set_user_tags', 'someuser', 'tag1', 'tags2'], + flatten(_exec.call_args_list[-1][0]))) + + @patch('ansible.module_utils.basic.AnsibleModule') + def test_user_json_data_can_be_parsed(self, module): + """Ensure that user json data can be parsed. + + From version 3.7 onwards `rabbitmqctl` can output the user data in proper json format. Check that parsing + works correctly. + """ + + def side_effect(args): + self.assertTrue('-q' in args) + self.assertTrue('--formatter' in args) + self.assertTrue('json' in args) + if 'status' in args: + return 0, rabbitmq_3_8_status.replace('version_num', '3.8.1'), '' + if 'list_users' in args: + return 0, '''[ +{"user":"someuser","tags":["administrator","management"]} +]''', '' + if 'list_user_permissions' in args: + return 0, '''[ +{"vhost":"/test","configure":"^$","write":"^$","read":"^$"} +,{"vhost":"/","configure":"^$","write":"^$","read":"^$"} +]''', '' + if 'list_user_topic_permissions' in args: + return 0, '''[ +{"vhost":"/test","exchange":"amq.topic","write":"^$","read":"^$"} +,{"vhost":"/","exchange":"amq.topic","write":"^$","read":"^$"} +]''', '' + return 100, '', '' + + module.run_command.side_effect = side_effect + user_controller = rabbitmq_user.RabbitMqUser( + module, 'someuser', 'somepassword', list(), + [{'vhost': '/', 'configure': '^$', 'write': '^$', 'read': '^$'}], + [{'vhost': '/', 'exchange': 'amq.topic', 'write': '^$', 'read': '^$'}], + 'rabbit', bulk_permissions=True) + self.assertTrue(user_controller.get()) + self.assertTrue(user_controller._version, version.StrictVersion('3.8.1')) + self.assertTrue(user_controller.existing_tags, ["administrator", "management"]) + self.assertTrue(user_controller.existing_permissions == { + '/test': {'vhost': '/test', 'configure': '^$', 'write': '^$', 'read': '^$'}, + '/': {'vhost': '/', 'configure': '^$', 'write': '^$', 'read': '^$'}}) + self.assertTrue(user_controller.existing_topic_permissions == { + ('/test', 'amq.topic'): {'vhost': '/test', 'exchange': 'amq.topic', 'write': '^$', 'read': '^$'}, + ('/', 'amq.topic'): {'vhost': '/', 'exchange': 'amq.topic', 'write': '^$', 'read': '^$'}}) + self.assertEqual(module.run_command.call_count, 4) + + @patch('ansible.module_utils.basic.AnsibleModule') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_user.RabbitMqUser._exec') + @patch('ansible_collections.community.rabbitmq.plugins.modules.rabbitmq_user.RabbitMqUser._check_version') + def test_non_bulk_permissions_are_parsed_and_set(self, _check_version, _exec, module): + """Test that non bulk permissions are parsed correctly. + + Non-bulk permissions mean that only the permissions of the VHost specified will be changed if needed. + If the same user has permissions in other VHosts, these will not be modified. + """ + module.get_bin_path.return_value = '/rabbitmqctl' + module.check_mode = False + _check_version.return_value = version.StrictVersion('3.8.0') + + def side_effect(args): + self.assertTrue('--formatter' in args, args) + self.assertTrue('json' in args, args) + if 'list_users' in args: + return '''[ +{"user":"someuser","tags":["administrator","management"]} +]''' + if 'list_user_permissions' in args: + self.assertTrue('someuser' in args, args) + return '''[ +{"vhost":"/test","configure":"^$","write":"^$","read":"^$"} +,{"vhost":"/","configure":"^$","write":"^$","read":"^$"} +]''' + if 'list_user_topic_permissions' in args: + self.assertTrue('someuser' in args, args) + return '''[ +{"vhost":"/test","exchange":"amq.topic","write":"^$","read":"^$"} +,{"vhost":"/","exchange":"amq.topic","write":"^$","read":"^$"} +]''' + raise Exception('wrong command: ' + str(args)) + + _exec.side_effect = side_effect + user_controller = rabbitmq_user.RabbitMqUser( + module, 'someuser', 'somepassword', list(), [{ + 'vhost': '/', + 'configure_priv': '.*', + 'write_priv': '.*', + 'read_priv': '.*' + }], [], 'rabbit' + ) + user_controller.get() + + self.assertEqual(_exec.call_count, 3) + self.assertListEqual(list(user_controller.existing_permissions.keys()), ['/']) + self.assertEqual(user_controller.existing_permissions['/']['write'], '^$') + self.assertEqual(user_controller.existing_permissions['/']['read'], '^$') + self.assertEqual(user_controller.existing_permissions['/']['configure'], '^$') + self.assertTrue(user_controller.has_permissions_modifications()) diff --git a/ansible_collections/community/rabbitmq/tests/unit/modules/utils.py b/ansible_collections/community/rabbitmq/tests/unit/modules/utils.py new file mode 100644 index 000000000..6261a177f --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/unit/modules/utils.py @@ -0,0 +1,50 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json + +from ansible_collections.community.rabbitmq.tests.unit.compat import unittest +from ansible_collections.community.rabbitmq.tests.unit.compat.mock import patch +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes + + +def set_module_args(args): + if '_ansible_remote_tmp' not in args: + args['_ansible_remote_tmp'] = '/tmp' + if '_ansible_keep_remote_files' not in args: + args['_ansible_keep_remote_files'] = False + + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) + + +class AnsibleExitJson(Exception): + pass + + +class AnsibleFailJson(Exception): + pass + + +def exit_json(*args, **kwargs): + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class ModuleTestCase(unittest.TestCase): + + def setUp(self): + self.mock_module = patch.multiple(basic.AnsibleModule, exit_json=exit_json, fail_json=fail_json) + self.mock_module.start() + self.mock_sleep = patch('time.sleep') + self.mock_sleep.start() + set_module_args({}) + self.addCleanup(self.mock_module.stop) + self.addCleanup(self.mock_sleep.stop) diff --git a/ansible_collections/community/rabbitmq/tests/utils/shippable/check_matrix.py b/ansible_collections/community/rabbitmq/tests/utils/shippable/check_matrix.py new file mode 100755 index 000000000..4dd03fc3c --- /dev/null +++ b/ansible_collections/community/rabbitmq/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/community.rabbitmq' + + 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/community/rabbitmq/tests/utils/shippable/cloud.sh b/ansible_collections/community/rabbitmq/tests/utils/shippable/cloud.sh new file mode 100755 index 000000000..d76c32282 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/utils/shippable/cloud.sh @@ -0,0 +1,19 @@ +#!/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}" + +# 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}" diff --git a/ansible_collections/community/rabbitmq/tests/utils/shippable/freebsd.sh b/ansible_collections/community/rabbitmq/tests/utils/shippable/freebsd.sh new file mode 100755 index 000000000..cd3014cca --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/utils/shippable/freebsd.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +set -o pipefail -eux + +declare -a args +IFS='/:' read -ra args <<< "$1" + +platform="${args[0]}" +version="${args[1]}" + +if [ "${#args[@]}" -gt 2 ]; then + target="shippable/posix/group${args[2]}/" +else + target="shippable/posix/" +fi + +stage="${S:-prod}" +provider="${P:-default}" + +# shellcheck disable=SC2086 +ansible-test integration --color -v --retry-on-error "${target}" ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} ${UNSTABLE:+"$UNSTABLE"} \ + --remote "${platform}/${version}" --remote-terminate always --remote-stage "${stage}" --remote-provider "${provider}" diff --git a/ansible_collections/community/rabbitmq/tests/utils/shippable/linux.sh b/ansible_collections/community/rabbitmq/tests/utils/shippable/linux.sh new file mode 100755 index 000000000..9cc2f966c --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/utils/shippable/linux.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +set -o pipefail -eux + +declare -a args +IFS='/:' read -ra args <<< "$1" + +image="${args[1]}" + +if [ "${#args[@]}" -gt 2 ]; then + target="shippable/posix/group${args[2]}/" +else + target="shippable/posix/" +fi + +# shellcheck disable=SC2086 +ansible-test integration --color -v --retry-on-error "${target}" ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} ${UNSTABLE:+"$UNSTABLE"} \ + --docker "${image}" diff --git a/ansible_collections/community/rabbitmq/tests/utils/shippable/osx.sh b/ansible_collections/community/rabbitmq/tests/utils/shippable/osx.sh new file mode 100755 index 000000000..cd3014cca --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/utils/shippable/osx.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +set -o pipefail -eux + +declare -a args +IFS='/:' read -ra args <<< "$1" + +platform="${args[0]}" +version="${args[1]}" + +if [ "${#args[@]}" -gt 2 ]; then + target="shippable/posix/group${args[2]}/" +else + target="shippable/posix/" +fi + +stage="${S:-prod}" +provider="${P:-default}" + +# shellcheck disable=SC2086 +ansible-test integration --color -v --retry-on-error "${target}" ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} ${UNSTABLE:+"$UNSTABLE"} \ + --remote "${platform}/${version}" --remote-terminate always --remote-stage "${stage}" --remote-provider "${provider}" diff --git a/ansible_collections/community/rabbitmq/tests/utils/shippable/remote.sh b/ansible_collections/community/rabbitmq/tests/utils/shippable/remote.sh new file mode 100755 index 000000000..cd3014cca --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/utils/shippable/remote.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +set -o pipefail -eux + +declare -a args +IFS='/:' read -ra args <<< "$1" + +platform="${args[0]}" +version="${args[1]}" + +if [ "${#args[@]}" -gt 2 ]; then + target="shippable/posix/group${args[2]}/" +else + target="shippable/posix/" +fi + +stage="${S:-prod}" +provider="${P:-default}" + +# shellcheck disable=SC2086 +ansible-test integration --color -v --retry-on-error "${target}" ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} ${UNSTABLE:+"$UNSTABLE"} \ + --remote "${platform}/${version}" --remote-terminate always --remote-stage "${stage}" --remote-provider "${provider}" diff --git a/ansible_collections/community/rabbitmq/tests/utils/shippable/rhel.sh b/ansible_collections/community/rabbitmq/tests/utils/shippable/rhel.sh new file mode 100755 index 000000000..cd3014cca --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/utils/shippable/rhel.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +set -o pipefail -eux + +declare -a args +IFS='/:' read -ra args <<< "$1" + +platform="${args[0]}" +version="${args[1]}" + +if [ "${#args[@]}" -gt 2 ]; then + target="shippable/posix/group${args[2]}/" +else + target="shippable/posix/" +fi + +stage="${S:-prod}" +provider="${P:-default}" + +# shellcheck disable=SC2086 +ansible-test integration --color -v --retry-on-error "${target}" ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} ${UNSTABLE:+"$UNSTABLE"} \ + --remote "${platform}/${version}" --remote-terminate always --remote-stage "${stage}" --remote-provider "${provider}" diff --git a/ansible_collections/community/rabbitmq/tests/utils/shippable/sanity.sh b/ansible_collections/community/rabbitmq/tests/utils/shippable/sanity.sh new file mode 100755 index 000000000..c216220e8 --- /dev/null +++ b/ansible_collections/community/rabbitmq/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 ../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/community/rabbitmq/tests/utils/shippable/shippable.sh b/ansible_collections/community/rabbitmq/tests/utils/shippable/shippable.sh new file mode 100755 index 000000000..f9b187212 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/utils/shippable/shippable.sh @@ -0,0 +1,212 @@ +#!/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/community/rabbitmq" + mkdir -p "${TEST_DIR}" + cp -aT "${SHIPPABLE_BUILD_DIR}" "${TEST_DIR}" + cd "${TEST_DIR}" +else + # AZP + export ANSIBLE_COLLECTIONS_PATHS="$PWD/../../../" +fi + +# START: HACK install integration test dependencies +if [ "${script}" != "units" ] && [ "${script}" != "sanity" ] && [ "${ansible_version}" != "2.9" ]; then + retry ansible-galaxy -vvv collection install community.general +fi +# 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 31525df8-da26-4e61-b31f-05e3df48b091 \ + -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=50 +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/community/rabbitmq/tests/utils/shippable/timing.py b/ansible_collections/community/rabbitmq/tests/utils/shippable/timing.py new file mode 100755 index 000000000..fb538271b --- /dev/null +++ b/ansible_collections/community/rabbitmq/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/community/rabbitmq/tests/utils/shippable/timing.sh b/ansible_collections/community/rabbitmq/tests/utils/shippable/timing.sh new file mode 100755 index 000000000..77e257830 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/utils/shippable/timing.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +set -o pipefail -eu + +"$@" 2>&1 | "$(dirname "$0")/timing.py" diff --git a/ansible_collections/community/rabbitmq/tests/utils/shippable/units.sh b/ansible_collections/community/rabbitmq/tests/utils/shippable/units.sh new file mode 100755 index 000000000..4bf9e05c4 --- /dev/null +++ b/ansible_collections/community/rabbitmq/tests/utils/shippable/units.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +set -o pipefail -eux + +if [[ "${COVERAGE:-}" == "--coverage" ]]; then + timeout=90 +else + timeout=30 +fi + +ansible-test env --timeout "${timeout}" --color -v + +# shellcheck disable=SC2086 +ansible-test units --color -v --docker default ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} \ |