mirror of
https://github.com/ansible-collections/community.general.git
synced 2026-04-28 09:26:44 +00:00
Compare commits
3 Commits
3.3.1
...
0.3.0-expe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
436023be95 | ||
|
|
ffe16f5fd1 | ||
|
|
0ec76242c6 |
@@ -1,3 +0,0 @@
|
||||
## Azure Pipelines Configuration
|
||||
|
||||
Please see the [Documentation](https://github.com/ansible/community/wiki/Testing:-Azure-Pipelines) for more information.
|
||||
@@ -1,410 +0,0 @@
|
||||
trigger:
|
||||
batch: true
|
||||
branches:
|
||||
include:
|
||||
- main
|
||||
- stable-*
|
||||
|
||||
pr:
|
||||
autoCancel: true
|
||||
branches:
|
||||
include:
|
||||
- main
|
||||
- stable-*
|
||||
|
||||
schedules:
|
||||
- cron: 0 8 * * *
|
||||
displayName: Nightly (main)
|
||||
always: true
|
||||
branches:
|
||||
include:
|
||||
- main
|
||||
- cron: 0 10 * * *
|
||||
displayName: Nightly (active stable branches)
|
||||
always: true
|
||||
branches:
|
||||
include:
|
||||
- stable-2
|
||||
- stable-3
|
||||
- cron: 0 11 * * 0
|
||||
displayName: Weekly (old stable branches)
|
||||
always: true
|
||||
branches:
|
||||
include:
|
||||
- stable-1
|
||||
|
||||
variables:
|
||||
- name: checkoutPath
|
||||
value: ansible_collections/community/general
|
||||
- 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:1.9.0
|
||||
|
||||
pool: Standard
|
||||
|
||||
stages:
|
||||
### Sanity
|
||||
- stage: Sanity_devel
|
||||
displayName: Sanity devel
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
nameFormat: Test {0}
|
||||
testFormat: devel/sanity/{0}
|
||||
targets:
|
||||
- test: 1
|
||||
- test: 2
|
||||
- test: 3
|
||||
- test: 4
|
||||
- test: extra
|
||||
- stage: Sanity_2_11
|
||||
displayName: Sanity 2.11
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
nameFormat: Test {0}
|
||||
testFormat: 2.11/sanity/{0}
|
||||
targets:
|
||||
- test: 1
|
||||
- test: 2
|
||||
- test: 3
|
||||
- test: 4
|
||||
- stage: Sanity_2_10
|
||||
displayName: Sanity 2.10
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
nameFormat: Test {0}
|
||||
testFormat: 2.10/sanity/{0}
|
||||
targets:
|
||||
- test: 1
|
||||
- test: 2
|
||||
- test: 3
|
||||
- test: 4
|
||||
- stage: Sanity_2_9
|
||||
displayName: Sanity 2.9
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
nameFormat: Test {0}
|
||||
testFormat: 2.9/sanity/{0}
|
||||
targets:
|
||||
- test: 1
|
||||
- test: 2
|
||||
- test: 3
|
||||
- test: 4
|
||||
### Units
|
||||
- stage: Units_devel
|
||||
displayName: Units devel
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
nameFormat: Python {0}
|
||||
testFormat: devel/units/{0}/1
|
||||
targets:
|
||||
- test: 2.6
|
||||
- test: 2.7
|
||||
- test: 3.5
|
||||
- test: 3.6
|
||||
- test: 3.7
|
||||
- test: 3.8
|
||||
- test: 3.9
|
||||
- test: '3.10'
|
||||
- stage: Units_2_11
|
||||
displayName: Units 2.11
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
nameFormat: Python {0}
|
||||
testFormat: 2.11/units/{0}/1
|
||||
targets:
|
||||
- test: 2.6
|
||||
- test: 2.7
|
||||
- test: 3.5
|
||||
- test: 3.6
|
||||
- test: 3.7
|
||||
- test: 3.8
|
||||
- test: 3.9
|
||||
- stage: Units_2_10
|
||||
displayName: Units 2.10
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
nameFormat: Python {0}
|
||||
testFormat: 2.10/units/{0}/1
|
||||
targets:
|
||||
- test: 2.6
|
||||
- test: 2.7
|
||||
- test: 3.5
|
||||
- test: 3.6
|
||||
- test: 3.7
|
||||
- test: 3.8
|
||||
- test: 3.9
|
||||
- stage: Units_2_9
|
||||
displayName: Units 2.9
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
nameFormat: Python {0}
|
||||
testFormat: 2.9/units/{0}/1
|
||||
targets:
|
||||
- test: 2.6
|
||||
- test: 2.7
|
||||
- test: 3.5
|
||||
- test: 3.6
|
||||
- test: 3.7
|
||||
- test: 3.8
|
||||
|
||||
## Remote
|
||||
- stage: Remote_devel
|
||||
displayName: Remote devel
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
testFormat: devel/{0}
|
||||
targets:
|
||||
- name: macOS 11.1
|
||||
test: macos/11.1
|
||||
- name: RHEL 7.9
|
||||
test: rhel/7.9
|
||||
- name: RHEL 8.3
|
||||
test: rhel/8.3
|
||||
- name: FreeBSD 12.2
|
||||
test: freebsd/12.2
|
||||
- name: FreeBSD 13.0
|
||||
test: freebsd/13.0
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- stage: Remote_2_11
|
||||
displayName: Remote 2.11
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
testFormat: 2.11/{0}
|
||||
targets:
|
||||
- name: macOS 11.1
|
||||
test: macos/11.1
|
||||
- name: RHEL 7.9
|
||||
test: rhel/7.9
|
||||
- name: RHEL 8.3
|
||||
test: rhel/8.3
|
||||
- name: FreeBSD 12.2
|
||||
test: freebsd/12.2
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
- stage: Remote_2_10
|
||||
displayName: Remote 2.10
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
testFormat: 2.10/{0}
|
||||
targets:
|
||||
- name: OS X 10.11
|
||||
test: osx/10.11
|
||||
- name: macOS 10.15
|
||||
test: macos/10.15
|
||||
- name: macOS 11.1
|
||||
test: macos/11.1
|
||||
- name: RHEL 7.8
|
||||
test: rhel/7.8
|
||||
- name: RHEL 8.2
|
||||
test: rhel/8.2
|
||||
- name: FreeBSD 12.1
|
||||
test: freebsd/12.1
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
- stage: Remote_2_9
|
||||
displayName: Remote 2.9
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
testFormat: 2.9/{0}
|
||||
targets:
|
||||
- name: RHEL 8.2
|
||||
test: rhel/8.2
|
||||
- name: FreeBSD 12.0
|
||||
test: freebsd/12.0
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
|
||||
### Docker
|
||||
- stage: Docker_devel
|
||||
displayName: Docker devel
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
testFormat: devel/linux/{0}
|
||||
targets:
|
||||
- name: CentOS 6
|
||||
test: centos6
|
||||
- name: CentOS 7
|
||||
test: centos7
|
||||
- name: CentOS 8
|
||||
test: centos8
|
||||
- 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 20.04
|
||||
test: ubuntu2004
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- stage: Docker_2_11
|
||||
displayName: Docker 2.11
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
testFormat: 2.11/linux/{0}
|
||||
targets:
|
||||
- name: CentOS 8
|
||||
test: centos8
|
||||
- name: Fedora 33
|
||||
test: fedora33
|
||||
- name: openSUSE 15 py3
|
||||
test: opensuse15
|
||||
- name: Ubuntu 20.04
|
||||
test: ubuntu2004
|
||||
groups:
|
||||
- 2
|
||||
- 3
|
||||
- stage: Docker_2_10
|
||||
displayName: Docker 2.10
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
testFormat: 2.10/linux/{0}
|
||||
targets:
|
||||
- name: CentOS 8
|
||||
test: centos8
|
||||
- name: Fedora 32
|
||||
test: fedora32
|
||||
- name: openSUSE 15 py3
|
||||
test: opensuse15
|
||||
- name: Ubuntu 16.04
|
||||
test: ubuntu1604
|
||||
groups:
|
||||
- 2
|
||||
- 3
|
||||
- stage: Docker_2_9
|
||||
displayName: Docker 2.9
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
testFormat: 2.9/linux/{0}
|
||||
targets:
|
||||
- name: CentOS 8
|
||||
test: centos8
|
||||
- name: Fedora 31
|
||||
test: fedora31
|
||||
- name: openSUSE 15 py3
|
||||
test: opensuse15
|
||||
groups:
|
||||
- 2
|
||||
- 3
|
||||
|
||||
### Cloud
|
||||
- stage: Cloud_devel
|
||||
displayName: Cloud devel
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
nameFormat: Python {0}
|
||||
testFormat: devel/cloud/{0}/1
|
||||
targets:
|
||||
- test: 3.8
|
||||
- stage: Cloud_2_11
|
||||
displayName: Cloud 2.11
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
nameFormat: Python {0}
|
||||
testFormat: 2.11/cloud/{0}/1
|
||||
targets:
|
||||
- test: 2.7
|
||||
- test: 3.6
|
||||
- stage: Cloud_2_10
|
||||
displayName: Cloud 2.10
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
nameFormat: Python {0}
|
||||
testFormat: 2.10/cloud/{0}/1
|
||||
targets:
|
||||
- test: 3.6
|
||||
- stage: Cloud_2_9
|
||||
displayName: Cloud 2.9
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
nameFormat: Python {0}
|
||||
testFormat: 2.9/cloud/{0}/1
|
||||
targets:
|
||||
- test: 3.6
|
||||
- stage: Summary
|
||||
condition: succeededOrFailed()
|
||||
dependsOn:
|
||||
- Sanity_devel
|
||||
- Sanity_2_9
|
||||
- Sanity_2_10
|
||||
- Sanity_2_11
|
||||
- Units_devel
|
||||
- Units_2_9
|
||||
- Units_2_10
|
||||
- Units_2_11
|
||||
- Remote_devel
|
||||
- Remote_2_9
|
||||
- Remote_2_10
|
||||
- Remote_2_11
|
||||
- Docker_devel
|
||||
- Docker_2_9
|
||||
- Docker_2_10
|
||||
- Docker_2_11
|
||||
- Cloud_devel
|
||||
- Cloud_2_9
|
||||
- Cloud_2_10
|
||||
- Cloud_2_11
|
||||
jobs:
|
||||
- template: templates/coverage.yml
|
||||
@@ -1,20 +0,0 @@
|
||||
#!/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
|
||||
@@ -1,60 +0,0 @@
|
||||
#!/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()
|
||||
@@ -1,24 +0,0 @@
|
||||
#!/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
|
||||
@@ -1,27 +0,0 @@
|
||||
#!/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
|
||||
@@ -1,15 +0,0 @@
|
||||
#!/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
|
||||
@@ -1,34 +0,0 @@
|
||||
#!/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"
|
||||
@@ -1,25 +0,0 @@
|
||||
#!/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()
|
||||
@@ -1,39 +0,0 @@
|
||||
# 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
|
||||
@@ -1,55 +0,0 @@
|
||||
# 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) }}
|
||||
@@ -1,45 +0,0 @@
|
||||
# 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)"
|
||||
1220
.github/BOTMETA.yml
vendored
1220
.github/BOTMETA.yml
vendored
File diff suppressed because it is too large
Load Diff
135
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
135
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -1,135 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
description: Create a report to help us improve
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
⚠
|
||||
Verify first that your issue is not [already reported on GitHub][issue search].
|
||||
Also test if the latest release and devel branch are affected too.
|
||||
*Complete **all** sections as described, this form is processed automatically.*
|
||||
|
||||
[issue search]: https://github.com/ansible-collections/community.general/search?q=is%3Aissue&type=issues
|
||||
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Summary
|
||||
description: Explain the problem briefly below.
|
||||
placeholder: >-
|
||||
When I try to do X with the collection from the main branch on GitHub, Y
|
||||
breaks in a way Z under the env E. Here are all the details I know
|
||||
about this problem...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Issue Type
|
||||
# FIXME: Once GitHub allows defining the default choice, update this
|
||||
options:
|
||||
- Bug Report
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
# For smaller collections we could use a multi-select and hardcode the list
|
||||
# May generate this list via GitHub action and walking files under https://github.com/ansible-collections/community.general/tree/main/plugins
|
||||
# Select from list, filter as you type (`mysql` would only show the 3 mysql components)
|
||||
# OR freeform - doesn't seem to be supported in adaptivecards
|
||||
label: Component Name
|
||||
description: >-
|
||||
Write the short name of the module, plugin, task or feature below,
|
||||
*use your best guess if unsure*.
|
||||
placeholder: dnf, apt, yum, pip, user etc.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Ansible Version
|
||||
description: >-
|
||||
Paste verbatim output from `ansible --version` between
|
||||
tripple backticks.
|
||||
value: |
|
||||
```console (paste below)
|
||||
$ ansible --version
|
||||
|
||||
```
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Configuration
|
||||
description: >-
|
||||
If this issue has an example piece of YAML that can help to reproduce this problem, please provide it.
|
||||
This can be a piece of YAML from, e.g., an automation, script, scene or configuration.
|
||||
Paste verbatim output from `ansible-config dump --only-changed` between quotes
|
||||
value: |
|
||||
```console (paste below)
|
||||
$ ansible-config dump --only-changed
|
||||
|
||||
```
|
||||
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: OS / Environment
|
||||
description: >-
|
||||
Provide all relevant information below, e.g. target OS versions,
|
||||
network device firmware, etc.
|
||||
placeholder: RHEL 8, CentOS Stream etc.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps to Reproduce
|
||||
description: |
|
||||
Describe exactly how to reproduce the problem, using a minimal test-case. It would *really* help us understand your problem if you could also pased any playbooks, configs and commands you used.
|
||||
|
||||
**HINT:** You can paste https://gist.github.com links for larger files.
|
||||
value: |
|
||||
<!--- Paste example playbooks or commands between quotes below -->
|
||||
```yaml (paste below)
|
||||
|
||||
```
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Expected Results
|
||||
description: >-
|
||||
Describe what you expected to happen when running the steps above.
|
||||
placeholder: >-
|
||||
I expected X to happen because I assumed Y.
|
||||
that it did not.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Actual Results
|
||||
description: |
|
||||
Describe what actually happened. If possible run with extra verbosity (`-vvvv`).
|
||||
|
||||
Paste verbatim command output between quotes.
|
||||
value: |
|
||||
```console (paste below)
|
||||
|
||||
```
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Code of Conduct
|
||||
description: |
|
||||
Read the [Ansible Code of Conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html?utm_medium=github&utm_source=issue_form--ansible-collections) first.
|
||||
options:
|
||||
- label: I agree to follow the Ansible Code of Conduct
|
||||
required: true
|
||||
...
|
||||
27
.github/ISSUE_TEMPLATE/config.yml
vendored
27
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,27 +0,0 @@
|
||||
---
|
||||
# Ref: https://help.github.com/en/github/building-a-strong-community/configuring-issue-templates-for-your-repository#configuring-the-template-chooser
|
||||
blank_issues_enabled: false # default: true
|
||||
contact_links:
|
||||
- name: Security bug report
|
||||
url: https://docs.ansible.com/ansible-core/devel/community/reporting_bugs_and_features.html?utm_medium=github&utm_source=issue_template_chooser_ansible_collections
|
||||
about: |
|
||||
Please learn how to report security vulnerabilities here.
|
||||
|
||||
For all security related bugs, email security@ansible.com
|
||||
instead of using this issue tracker and you will receive
|
||||
a prompt response.
|
||||
|
||||
For more information, see
|
||||
https://docs.ansible.com/ansible/latest/community/reporting_bugs_and_features.html
|
||||
- name: Ansible Code of Conduct
|
||||
url: https://docs.ansible.com/ansible/latest/community/code_of_conduct.html?utm_medium=github&utm_source=issue_template_chooser_ansible_collections
|
||||
about: Be nice to other members of the community.
|
||||
- name: Talks to the community
|
||||
url: https://docs.ansible.com/ansible/latest/community/communication.html?utm_medium=github&utm_source=issue_template_chooser#mailing-list-information
|
||||
about: Please ask and answer usage questions here
|
||||
- name: Working groups
|
||||
url: https://github.com/ansible/community/wiki
|
||||
about: Interested in improving a specific area? Become a part of a working group!
|
||||
- name: For Enterprise
|
||||
url: https://www.ansible.com/products/engine?utm_medium=github&utm_source=issue_template_chooser_ansible_collections
|
||||
about: Red Hat offers support for the Ansible Automation Platform
|
||||
111
.github/ISSUE_TEMPLATE/documentation_report.yml
vendored
111
.github/ISSUE_TEMPLATE/documentation_report.yml
vendored
@@ -1,111 +0,0 @@
|
||||
---
|
||||
name: Documentation Report
|
||||
description: Ask us about docs
|
||||
# NOTE: issue body is enabled to allow screenshots
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
⚠
|
||||
Verify first that your issue is not [already reported on GitHub][issue search].
|
||||
Also test if the latest release and devel branch are affected too.
|
||||
*Complete **all** sections as described, this form is processed automatically.*
|
||||
|
||||
[issue search]: https://github.com/ansible-collections/community.general/search?q=is%3Aissue&type=issues
|
||||
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Summary
|
||||
description: |
|
||||
Explain the problem briefly below, add suggestions to wording or structure.
|
||||
|
||||
**HINT:** Did you know the documentation has an `Edit on GitHub` link on every page?
|
||||
placeholder: >-
|
||||
I was reading the Collection documentation of version X and I'm having
|
||||
problems understanding Y. It would be very helpful if that got
|
||||
rephrased as Z.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Issue Type
|
||||
# FIXME: Once GitHub allows defining the default choice, update this
|
||||
options:
|
||||
- Documentation Report
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: Component Name
|
||||
description: >-
|
||||
Write the short name of the rst file, module, plugin, task or
|
||||
feature below, *use your best guess if unsure*.
|
||||
placeholder: mysql_user
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Ansible Version
|
||||
description: >-
|
||||
Paste verbatim output from `ansible --version` between
|
||||
tripple backticks.
|
||||
value: |
|
||||
```console (paste below)
|
||||
$ ansible --version
|
||||
|
||||
```
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Configuration
|
||||
description: >-
|
||||
Paste verbatim output from `ansible-config dump --only-changed` between quotes.
|
||||
value: |
|
||||
```console (paste below)
|
||||
$ ansible-config dump --only-changed
|
||||
|
||||
```
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: OS / Environment
|
||||
description: >-
|
||||
Provide all relevant information below, e.g. OS version,
|
||||
browser, etc.
|
||||
placeholder: Fedora 33, Firefox etc.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional Information
|
||||
description: |
|
||||
Describe how this improves the documentation, e.g. before/after situation or screenshots.
|
||||
|
||||
**Tip:** It's not possible to upload the screenshot via this field directly but you can use the last textarea in this form to attach them.
|
||||
|
||||
**HINT:** You can paste https://gist.github.com links for larger files.
|
||||
placeholder: >-
|
||||
When the improvement is applied, it makes it more straightforward
|
||||
to understand X.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Code of Conduct
|
||||
description: |
|
||||
Read the [Ansible Code of Conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html?utm_medium=github&utm_source=issue_form--ansible-collections) first.
|
||||
options:
|
||||
- label: I agree to follow the Ansible Code of Conduct
|
||||
required: true
|
||||
...
|
||||
69
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
69
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -1,69 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
description: Suggest an idea for this project
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
⚠
|
||||
Verify first that your issue is not [already reported on GitHub][issue search].
|
||||
Also test if the latest release and devel branch are affected too.
|
||||
*Complete **all** sections as described, this form is processed automatically.*
|
||||
|
||||
[issue search]: https://github.com/ansible-collections/community.general/search?q=is%3Aissue&type=issues
|
||||
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Summary
|
||||
description: Describe the new feature/improvement briefly below.
|
||||
placeholder: >-
|
||||
I am trying to do X with the collection from the main branch on GitHub and
|
||||
I think that implementing a feature Y would be very helpful for me and
|
||||
every other user of ansible-core because of Z.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Issue Type
|
||||
# FIXME: Once GitHub allows defining the default choice, update this
|
||||
options:
|
||||
- Feature Idea
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: Component Name
|
||||
description: >-
|
||||
Write the short name of the module, plugin, task or feature below,
|
||||
*use your best guess if unsure*.
|
||||
placeholder: dnf, apt, yum, pip, user etc.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional Information
|
||||
description: |
|
||||
Describe how the feature would be used, why it is needed and what it would solve.
|
||||
|
||||
**HINT:** You can paste https://gist.github.com links for larger files.
|
||||
value: |
|
||||
<!--- Paste example playbooks or commands between quotes below -->
|
||||
```yaml (paste below)
|
||||
|
||||
```
|
||||
validations:
|
||||
required: false
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Code of Conduct
|
||||
description: |
|
||||
Read the [Ansible Code of Conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html?utm_medium=github&utm_source=issue_form--ansible-collections) first.
|
||||
options:
|
||||
- label: I agree to follow the Ansible Code of Conduct
|
||||
required: true
|
||||
...
|
||||
5
.github/patchback.yml
vendored
5
.github/patchback.yml
vendored
@@ -1,5 +0,0 @@
|
||||
---
|
||||
backport_branch_prefix: patchback/backports/
|
||||
backport_label_prefix: backport-
|
||||
target_branch_prefix: stable-
|
||||
...
|
||||
49
.github/workflows/codeql-analysis.yml
vendored
49
.github/workflows/codeql-analysis.yml
vendored
@@ -1,49 +0,0 @@
|
||||
name: "Code scanning - action"
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '26 19 * * 1'
|
||||
|
||||
jobs:
|
||||
CodeQL-Build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
fetch-depth: 2
|
||||
|
||||
# If this run was triggered by a pull request event, then checkout
|
||||
# the head of the pull request instead of the merge commit.
|
||||
- run: git checkout HEAD^2
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
# Override language selection by uncommenting this and choosing your languages
|
||||
# with:
|
||||
# languages: go, javascript, csharp, python, cpp, java
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
1209
CHANGELOG.rst
1209
CHANGELOG.rst
File diff suppressed because it is too large
Load Diff
@@ -1,36 +0,0 @@
|
||||
# Contributing
|
||||
|
||||
We follow [Ansible Code of Conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html) in all our contributions and interactions within this repository.
|
||||
|
||||
If you are a committer, also refer to the [collection's committer guidelines](https://github.com/ansible-collections/community.general/blob/main/commit-rights.md).
|
||||
|
||||
## Issue tracker
|
||||
|
||||
Whether you are looking for an opportunity to contribute or you found a bug and already know how to solve it, please go to the [issue tracker](https://github.com/ansible-collections/community.general/issues).
|
||||
There you can find feature ideas to implement, reports about bugs to solve, or submit an issue to discuss your idea before implementing it which can help choose a right direction at the beginning of your work and potentially save a lot of time and effort.
|
||||
Also somebody may already have started discussing or working on implementing the same or a similar idea,
|
||||
so you can cooperate to create a better solution together.
|
||||
|
||||
* If you are interested in starting with an easy issue, look for [issues with an `easyfix` label](https://github.com/ansible-collections/community.general/labels/easyfix).
|
||||
* Often issues that are waiting for contributors to pick up have [the `waiting_on_contributor` label](https://github.com/ansible-collections/community.general/labels/waiting_on_contributor).
|
||||
|
||||
## Open pull requests
|
||||
|
||||
Look through currently [open pull requests](https://github.com/ansible-collections/community.general/pulls).
|
||||
You can help by reviewing them. Reviews help move pull requests to merge state. Some good pull requests cannot be merged only due to a lack of reviews. And it is always worth saying that good reviews are often more valuable than pull requests themselves.
|
||||
Note that reviewing does not only mean code review, but also offering comments on new interfaces added to existing plugins/modules, interfaces of new plugins/modules, improving language (not everyone is a native english speaker), or testing bugfixes and new features!
|
||||
|
||||
Also, consider taking up a valuable, reviewed, but abandoned pull request which you could politely ask the original authors to complete yourself.
|
||||
|
||||
* Try committing your changes with an informative but short commit message.
|
||||
* All commits of a pull request branch will be squashed into one commit at last. That does not mean you must have only one commit on your pull request, though!
|
||||
* Please try not to force-push if it is not needed, so reviewers and other users looking at your pull request later can see the pull request commit history.
|
||||
* Do not add merge commits to your PR. The bot will complain and you will have to rebase ([instructions for rebasing](https://docs.ansible.com/ansible/latest/dev_guide/developing_rebasing.html)) to remove them before your PR can be merged. To avoid that git automatically does merges during pulls, you can configure it to do rebases instead by running `git config pull.rebase true` inside the respository checkout.
|
||||
|
||||
You can also read [our Quick-start development guide](https://github.com/ansible/community-docs/blob/main/create_pr_quick_start_guide.rst).
|
||||
|
||||
## Test pull requests
|
||||
|
||||
If you want to test a PR locally, refer to [our testing guide](https://github.com/ansible/community-docs/blob/main/test_pr_locally_guide.rst) for instructions on how do it quickly.
|
||||
|
||||
If you find any inconsistencies or places in this document which can be improved, feel free to raise an issue or pull request to fix it.
|
||||
104
README.md
104
README.md
@@ -1,37 +1,17 @@
|
||||
# Community General Collection
|
||||
[](https://app.shippable.com/github/ansible-collections/community.general/dashboard) [](https://codecov.io/gh/ansible-collections/community.general)
|
||||
|
||||
[](https://dev.azure.com/ansible/community.general/_build?definitionId=31)
|
||||
[](https://codecov.io/gh/ansible-collections/community.general)
|
||||
# Ansible Collection: community.general
|
||||
|
||||
This repository contains the `community.general` Ansible Collection. The collection is a part of the Ansible package and includes many modules and plugins supported by Ansible community which are not part of more specialized community collections.
|
||||
This repo contains the `community.general` Ansible Collection.
|
||||
|
||||
You can find [documentation for this collection on the Ansible docs site](https://docs.ansible.com/ansible/latest/collections/community/general/).
|
||||
The collection includes the modules and plugins supported by Ansible community.
|
||||
|
||||
Please note that this collection does **not** support Windows targets. Only connection plugins included in this collection might support Windows targets, and will explicitly mention that in their documentation if they do so.
|
||||
|
||||
## Code of Conduct
|
||||
## Installation and Usage
|
||||
|
||||
We follow [Ansible Code of Conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html) in all our interactions within this project.
|
||||
### Installing the Collection from Ansible Galaxy
|
||||
|
||||
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.
|
||||
|
||||
## Tested with Ansible
|
||||
|
||||
Tested with the current Ansible 2.9, ansible-base 2.10 and ansible-core 2.11 releases and the current development version of ansible-core. Ansible versions before 2.9.10 are not supported.
|
||||
|
||||
## External requirements
|
||||
|
||||
Some modules and plugins require external libraries. Please check the requirements for each plugin or module you use in the documentation to find out which requirements are needed.
|
||||
|
||||
## Included content
|
||||
|
||||
Please check the included content on the [Ansible Galaxy page for this collection](https://galaxy.ansible.com/community/general) or the [documentation on the Ansible docs site](https://docs.ansible.com/ansible/latest/collections/community/general/).
|
||||
|
||||
## Using this collection
|
||||
|
||||
This collection is shipped with the Ansible package. So if you have it installed, no more action is required.
|
||||
|
||||
If you have a minimal installation (only Ansible Core installed) or you want to use the latest version of the collection along with the whole Ansible package, you need to install the collection from [Ansible Galaxy](https://galaxy.ansible.com/community/general) manually with the `ansible-galaxy` command-line tool:
|
||||
Before using the General community collection, you need to install the collection with the `ansible-galaxy` CLI:
|
||||
|
||||
ansible-galaxy collection install community.general
|
||||
|
||||
@@ -42,51 +22,21 @@ collections:
|
||||
- name: community.general
|
||||
```
|
||||
|
||||
Note that if you install the collection manually, 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:
|
||||
## Testing and Development
|
||||
|
||||
```bash
|
||||
ansible-galaxy collection install community.general --upgrade
|
||||
```
|
||||
If you want to develop new content for this collection or improve what is already here, the easiest way to work on the collection is to clone it into one of the configured [`COLLECTIONS_PATHS`](https://docs.ansible.com/ansible/latest/reference_appendices/config.html#collections-paths), and work on it there.
|
||||
|
||||
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 where `X.Y.Z` can be any [available version](https://galaxy.ansible.com/community/general):
|
||||
You can find more information in the [developer guide for collections](https://docs.ansible.com/ansible/devel/dev_guide/developing_collections.html#contributing-to-collections)
|
||||
|
||||
```bash
|
||||
ansible-galaxy collection install community.general:==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
|
||||
|
||||
The content of this collection is made by good people like you, a community of individuals collaborating on making the world better through developing automation software.
|
||||
|
||||
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.general/blob/main/CONTRIBUTING.md)!
|
||||
|
||||
The current maintainers are listed in the [commit-rights.md](https://github.com/ansible-collections/community.general/blob/main/commit-rights.md#people) file. If you have questions or need help, feel free to mention them in the proposals.
|
||||
|
||||
You can find more information in the [developer guide for collections](https://docs.ansible.com/ansible/devel/dev_guide/developing_collections.html#contributing-to-collections), and in the [Ansible Community Guide](https://docs.ansible.com/ansible/latest/community/index.html).
|
||||
|
||||
Also for some notes specific to this collection see [our CONTRIBUTING documentation](https://github.com/ansible-collections/community.general/blob/main/CONTRIBUTING.md).
|
||||
|
||||
### Running tests
|
||||
### Testing with `ansible-test`
|
||||
|
||||
See [here](https://docs.ansible.com/ansible/devel/dev_guide/developing_collections.html#testing-collections).
|
||||
|
||||
### Communication
|
||||
## Release notes
|
||||
|
||||
We announce important development changes and releases through Ansible's [The Bullhorn newsletter](https://eepurl.com/gZmiEP). If you are a collection developer, be sure you are subscribed.
|
||||
See [here](https://github.com/ansible-collections/community.general/tree/master/CHANGELOG.rst).
|
||||
|
||||
Join us in the `#ansible` (general use questions and support), `#ansible-community` (community and collection development questions), and other [IRC channels](https://docs.ansible.com/ansible/devel/community/communication.html#irc-channels) on [Libera.chat](https://libera.chat).
|
||||
|
||||
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://eepurl.com/gZmiEP) and join us.
|
||||
|
||||
For more information about communities, meetings and agendas see [Community Wiki](https://github.com/ansible/community/wiki/Community).
|
||||
|
||||
For more information about communication, refer to the [Ansible communication guide](https://docs.ansible.com/ansible/devel/community/communication.html).
|
||||
|
||||
### Publishing New Version
|
||||
## Publishing New Version
|
||||
|
||||
Basic instructions without release branches:
|
||||
|
||||
@@ -95,25 +45,25 @@ Basic instructions without release branches:
|
||||
3. Make sure `CHANGELOG.rst` and `changelogs/changelog.yaml` are added to git, and the deleted fragments have been removed.
|
||||
4. Tag the commit with `<version>`. Push changes and tag to the main repository.
|
||||
|
||||
## Release notes
|
||||
## More Information
|
||||
|
||||
See the [changelog](https://github.com/ansible-collections/community.general/blob/stable-3/CHANGELOG.rst).
|
||||
TBD
|
||||
|
||||
## Roadmap
|
||||
## Communication
|
||||
|
||||
See [this issue](https://github.com/ansible-collections/community.general/issues/582) for information on releasing, versioning and deprecation.
|
||||
We have a dedicated Working Group for Ansible development.
|
||||
|
||||
In general, we plan to release a major version every six months, and minor versions every two months. Major versions can contain breaking changes, while minor versions only contain new features and bugfixes.
|
||||
You can find other people interested on the following Freenode IRC channels -
|
||||
- `#ansible` - For general use questions and support.
|
||||
- `#ansible-devel` - For discussions on developer topics and code related to features or bugs.
|
||||
- `#ansible-community` - For discussions on community topics and community meetings.
|
||||
|
||||
## More information
|
||||
For more information about communities, meetings and agendas see [Community Wiki](https://github.com/ansible/community/wiki/Community).
|
||||
|
||||
- [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)
|
||||
For more information about [communication](https://docs.ansible.com/ansible/latest/community/communication.html)
|
||||
|
||||
## Licensing
|
||||
## License
|
||||
|
||||
GNU General Public License v3.0 or later.
|
||||
GNU General Public License v3.0 or later
|
||||
|
||||
See [COPYING](https://www.gnu.org/licenses/gpl-3.0.txt) to see the full text.
|
||||
See [LICENSE](COPYING) to see the full text.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,4 @@
|
||||
minor_changes:
|
||||
- log_plays callback - use v2 methods (https://github.com/ansible-collections/community.general/pull/442).
|
||||
breaking_changes:
|
||||
- log_plays callback - add missing information to the logs generated by the callback plugin. This changes the log message format (https://github.com/ansible-collections/community.general/pull/442).
|
||||
3
changelogs/fragments/55903_kubevirt.yml
Normal file
3
changelogs/fragments/55903_kubevirt.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
---
|
||||
bugfixes:
|
||||
- kubevirt - Add aliases 'interface_name' for network_name (https://github.com/ansible/ansible/issues/55641).
|
||||
3
changelogs/fragments/airbrake_deployment_add_version.yml
Normal file
3
changelogs/fragments/airbrake_deployment_add_version.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
---
|
||||
minor_changes:
|
||||
- "airbrake_deployment - add ``version`` param; clarified docs on ``revision`` param (https://github.com/ansible-collections/community.general/pull/583)."
|
||||
@@ -1,74 +0,0 @@
|
||||
Committers Guidelines for community.general
|
||||
===========================================
|
||||
|
||||
This document is based on the [Ansible committer guidelines](https://github.com/ansible/ansible/blob/b57444af14062ec96e0af75fdfc2098c74fe2d9a/docs/docsite/rst/community/committer_guidelines.rst) ([latest version](https://docs.ansible.com/ansible/devel/community/committer_guidelines.html)).
|
||||
|
||||
These are the guidelines for people with commit privileges on the Ansible Community General Collection GitHub repository. Please read the guidelines before you commit.
|
||||
|
||||
These guidelines apply to everyone. At the same time, this is NOT a process document. So just use good judgment. You have been given commit access because we trust your judgment.
|
||||
|
||||
That said, use the trust wisely.
|
||||
|
||||
If you abuse the trust and break components and builds, and so on, the trust level falls and you may be asked not to commit or you may lose your commit privileges.
|
||||
|
||||
Our workflow on GitHub
|
||||
----------------------
|
||||
|
||||
As a committer, you may already know this, but our workflow forms a lot of our team policies. Please ensure you are aware of the following workflow steps:
|
||||
|
||||
* Fork the repository upon which you want to do some work to your own personal repository
|
||||
* Work on the specific branch upon which you need to commit
|
||||
* Create a Pull Request back to the collection repository and await reviews
|
||||
* Adjust code as necessary based on the Comments provided
|
||||
* Ask someone from the other committers to do a final review and merge
|
||||
|
||||
Sometimes, committers merge their own pull requests. This section is a set of guidelines. If you are changing a comma in a doc or making a very minor change, you can use your best judgement. This is another trust thing. The process is critical for any major change, but for little things or getting something done quickly, use your best judgement and make sure people on the team are aware of your work.
|
||||
|
||||
Roles
|
||||
-----
|
||||
* Release managers: Merge pull requests to `stable-X` branches, create tags to do releases.
|
||||
* Committers: Fine to do PRs for most things, but we should have a timebox. Hanging PRs may merge on the judgement of these devs.
|
||||
* Module maintainers: Module maintainers own specific modules and have indirect commit access through the current module PR mechanisms. This is primary [ansibullbot](https://github.com/ansibullbot)'s `shipit` mechanism.
|
||||
|
||||
General rules
|
||||
-------------
|
||||
Individuals with direct commit access to this collection repository are entrusted with powers that allow them to do a broad variety of things--probably more than we can write down. Rather than rules, treat these as general *guidelines*, individuals with this power are expected to use their best judgement.
|
||||
|
||||
* Do NOTs:
|
||||
|
||||
- Do not commit directly.
|
||||
- Do not merge your own PRs. Someone else should have a chance to review and approve the PR merge. You have a small amount of leeway here for very minor changes.
|
||||
- Do not forget about non-standard / alternate environments. Consider the alternatives. Yes, people have bad/unusual/strange environments (like binaries from multiple init systems installed), but they are the ones who need us the most.
|
||||
- Do not drag your community team members down. Discuss the technical merits of any pull requests you review. Avoid negativity and personal comments. For more guidance on being a good community member, read the [Ansible Community Code of Conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html).
|
||||
- Do not forget about the maintenance burden. High-maintenance features may not be worth adding.
|
||||
- Do not break playbooks. Always keep backwards compatibility in mind.
|
||||
- Do not forget to keep it simple. Complexity breeds all kinds of problems.
|
||||
- Do not merge to branches other than `main`, especially not to `stable-X`, if you do not have explicit permission to do so.
|
||||
- Do not create tags. Tags are used in the release process, and should only be created by the people responsible for managing the stable branches.
|
||||
|
||||
* Do:
|
||||
|
||||
- Squash, avoid merges whenever possible, use GitHub's squash commits or cherry pick if needed (bisect thanks you).
|
||||
- Be active. Committers who have no activity on the project (through merges, triage, commits, and so on) will have their permissions suspended.
|
||||
- Consider backwards compatibility (goes back to "do not break existing playbooks").
|
||||
- Write tests. PRs with tests are looked at with more priority than PRs without tests that should have them included. While not all changes require tests, be sure to add them for bug fixes or functionality changes.
|
||||
- Discuss with other committers, specially when you are unsure of something.
|
||||
- Document! If your PR is a new feature or a change to behavior, make sure you've updated all associated documentation or have notified the right people to do so.
|
||||
- Consider scope, sometimes a fix can be generalized.
|
||||
- Keep it simple, then things are maintainable, debuggable and intelligible.
|
||||
|
||||
Committers are expected to continue to follow the same community and contribution guidelines followed by the rest of the Ansible community.
|
||||
|
||||
|
||||
People
|
||||
------
|
||||
|
||||
Individuals who have been asked to become a part of this group have generally been contributing in significant ways to the community.general collection for some time. Should they agree, they are requested to add their names and GitHub IDs to this file, in the section below, through a pull request. Doing so indicates that these individuals agree to act in the ways that their fellow committers trust that they will act.
|
||||
|
||||
| Name | GitHub ID | IRC Nick | Other |
|
||||
| ------------------- | -------------------- | ------------------ | -------------------- |
|
||||
| Alexei Znamensky | russoz | russoz | |
|
||||
| Amin Vakil | aminvakil | aminvakil | |
|
||||
| Andrew Klychkov | andersson007 | andersson007_ | |
|
||||
| Felix Fontein | felixfontein | felixfontein | |
|
||||
| John R Barker | gundalow | gundalow | |
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
sections:
|
||||
- title: Guides
|
||||
toctree:
|
||||
- filter_guide
|
||||
@@ -1,753 +0,0 @@
|
||||
.. _ansible_collections.community.general.docsite.filter_guide:
|
||||
|
||||
community.general Filter Guide
|
||||
==============================
|
||||
|
||||
The :ref:`community.general collection <plugins_in_community.general>` offers several useful filter plugins.
|
||||
|
||||
.. contents:: Topics
|
||||
|
||||
Paths
|
||||
-----
|
||||
|
||||
The ``path_join`` filter has been added in ansible-base 2.10. If you want to use this filter, but also need to support Ansible 2.9, you can use ``community.general``'s ``path_join`` shim, ``community.general.path_join``. This filter redirects to ``path_join`` for ansible-base 2.10 and ansible-core 2.11 or newer, and re-implements the filter for Ansible 2.9.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
# ansible-base 2.10 or newer:
|
||||
path: {{ ('/etc', path, 'subdir', file) | path_join }}
|
||||
|
||||
# Also works with Ansible 2.9:
|
||||
path: {{ ('/etc', path, 'subdir', file) | community.general.path_join }}
|
||||
|
||||
.. versionadded:: 3.0.0
|
||||
|
||||
Abstract transformations
|
||||
------------------------
|
||||
|
||||
Dictionaries
|
||||
^^^^^^^^^^^^
|
||||
|
||||
You can use the ``dict_kv`` filter to create a single-entry dictionary with ``value | community.general.dict_kv(key)``:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- name: Create a single-entry dictionary
|
||||
debug:
|
||||
msg: "{{ myvar | community.general.dict_kv('thatsmyvar') }}"
|
||||
vars:
|
||||
myvar: myvalue
|
||||
|
||||
- name: Create a list of dictionaries where the 'server' field is taken from a list
|
||||
debug:
|
||||
msg: >-
|
||||
{{ myservers | map('community.general.dict_kv', 'server')
|
||||
| map('combine', common_config) }}
|
||||
vars:
|
||||
common_config:
|
||||
type: host
|
||||
database: all
|
||||
myservers:
|
||||
- server1
|
||||
- server2
|
||||
|
||||
This produces:
|
||||
|
||||
.. code-block:: ansible-output
|
||||
|
||||
TASK [Create a single-entry dictionary] **************************************************
|
||||
ok: [localhost] => {
|
||||
"msg": {
|
||||
"thatsmyvar": "myvalue"
|
||||
}
|
||||
}
|
||||
|
||||
TASK [Create a list of dictionaries where the 'server' field is taken from a list] *******
|
||||
ok: [localhost] => {
|
||||
"msg": [
|
||||
{
|
||||
"database": "all",
|
||||
"server": "server1",
|
||||
"type": "host"
|
||||
},
|
||||
{
|
||||
"database": "all",
|
||||
"server": "server2",
|
||||
"type": "host"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
.. versionadded:: 2.0.0
|
||||
|
||||
If you need to convert a list of key-value pairs to a dictionary, you can use the ``dict`` function. Unfortunately, this function cannot be used with ``map``. For this, the ``community.general.dict`` filter can be used:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- name: Create a dictionary with the dict function
|
||||
debug:
|
||||
msg: "{{ dict([[1, 2], ['a', 'b']]) }}"
|
||||
|
||||
- name: Create a dictionary with the community.general.dict filter
|
||||
debug:
|
||||
msg: "{{ [[1, 2], ['a', 'b']] | community.general.dict }}"
|
||||
|
||||
- name: Create a list of dictionaries with map and the community.general.dict filter
|
||||
debug:
|
||||
msg: >-
|
||||
{{ values | map('zip', ['k1', 'k2', 'k3'])
|
||||
| map('map', 'reverse')
|
||||
| map('community.general.dict') }}
|
||||
vars:
|
||||
values:
|
||||
- - foo
|
||||
- 23
|
||||
- a
|
||||
- - bar
|
||||
- 42
|
||||
- b
|
||||
|
||||
This produces:
|
||||
|
||||
.. code-block:: ansible-output
|
||||
|
||||
TASK [Create a dictionary with the dict function] ****************************************
|
||||
ok: [localhost] => {
|
||||
"msg": {
|
||||
"1": 2,
|
||||
"a": "b"
|
||||
}
|
||||
}
|
||||
|
||||
TASK [Create a dictionary with the community.general.dict filter] ************************
|
||||
ok: [localhost] => {
|
||||
"msg": {
|
||||
"1": 2,
|
||||
"a": "b"
|
||||
}
|
||||
}
|
||||
|
||||
TASK [Create a list of dictionaries with map and the community.general.dict filter] ******
|
||||
ok: [localhost] => {
|
||||
"msg": [
|
||||
{
|
||||
"k1": "foo",
|
||||
"k2": 23,
|
||||
"k3": "a"
|
||||
},
|
||||
{
|
||||
"k1": "bar",
|
||||
"k2": 42,
|
||||
"k3": "b"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
.. versionadded:: 3.0.0
|
||||
|
||||
Grouping
|
||||
^^^^^^^^
|
||||
|
||||
If you have a list of dictionaries, the Jinja2 ``groupby`` filter allows to group the list by an attribute. This results in a list of ``(grouper, list)`` namedtuples, where ``list`` contains all dictionaries where the selected attribute equals ``grouper``. If you know that for every ``grouper``, there will be a most one entry in that list, you can use the ``community.general.groupby_as_dict`` filter to convert the original list into a dictionary which maps ``grouper`` to the corresponding dictionary.
|
||||
|
||||
One example is ``ansible_facts.mounts``, which is a list of dictionaries where each has one ``device`` element to indicate the device which is mounted. Therefore, ``ansible_facts.mounts | community.general.groupby_as_dict('device')`` is a dictionary mapping a device to the mount information:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- name: Output mount facts grouped by device name
|
||||
debug:
|
||||
var: ansible_facts.mounts | community.general.groupby_as_dict('device')
|
||||
|
||||
- name: Output mount facts grouped by mount point
|
||||
debug:
|
||||
var: ansible_facts.mounts | community.general.groupby_as_dict('mount')
|
||||
|
||||
This produces:
|
||||
|
||||
.. code-block:: ansible-output
|
||||
|
||||
TASK [Output mount facts grouped by device name] ******************************************
|
||||
ok: [localhost] => {
|
||||
"ansible_facts.mounts | community.general.groupby_as_dict('device')": {
|
||||
"/dev/sda1": {
|
||||
"block_available": 2000,
|
||||
"block_size": 4096,
|
||||
"block_total": 2345,
|
||||
"block_used": 345,
|
||||
"device": "/dev/sda1",
|
||||
"fstype": "ext4",
|
||||
"inode_available": 500,
|
||||
"inode_total": 512,
|
||||
"inode_used": 12,
|
||||
"mount": "/boot",
|
||||
"options": "rw,relatime,data=ordered",
|
||||
"size_available": 56821,
|
||||
"size_total": 543210,
|
||||
"uuid": "ab31cade-d9c1-484d-8482-8a4cbee5241a"
|
||||
},
|
||||
"/dev/sda2": {
|
||||
"block_available": 1234,
|
||||
"block_size": 4096,
|
||||
"block_total": 12345,
|
||||
"block_used": 11111,
|
||||
"device": "/dev/sda2",
|
||||
"fstype": "ext4",
|
||||
"inode_available": 1111,
|
||||
"inode_total": 1234,
|
||||
"inode_used": 123,
|
||||
"mount": "/",
|
||||
"options": "rw,relatime",
|
||||
"size_available": 42143,
|
||||
"size_total": 543210,
|
||||
"uuid": "abcdef01-2345-6789-0abc-def012345678"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TASK [Output mount facts grouped by mount point] ******************************************
|
||||
ok: [localhost] => {
|
||||
"ansible_facts.mounts | community.general.groupby_as_dict('mount')": {
|
||||
"/": {
|
||||
"block_available": 1234,
|
||||
"block_size": 4096,
|
||||
"block_total": 12345,
|
||||
"block_used": 11111,
|
||||
"device": "/dev/sda2",
|
||||
"fstype": "ext4",
|
||||
"inode_available": 1111,
|
||||
"inode_total": 1234,
|
||||
"inode_used": 123,
|
||||
"mount": "/",
|
||||
"options": "rw,relatime",
|
||||
"size_available": 42143,
|
||||
"size_total": 543210,
|
||||
"uuid": "bdf50b7d-4859-40af-8665-c637ee7a7808"
|
||||
},
|
||||
"/boot": {
|
||||
"block_available": 2000,
|
||||
"block_size": 4096,
|
||||
"block_total": 2345,
|
||||
"block_used": 345,
|
||||
"device": "/dev/sda1",
|
||||
"fstype": "ext4",
|
||||
"inode_available": 500,
|
||||
"inode_total": 512,
|
||||
"inode_used": 12,
|
||||
"mount": "/boot",
|
||||
"options": "rw,relatime,data=ordered",
|
||||
"size_available": 56821,
|
||||
"size_total": 543210,
|
||||
"uuid": "ab31cade-d9c1-484d-8482-8a4cbee5241a"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.. versionadded: 3.0.0
|
||||
|
||||
Merging lists of dictionaries
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If you have two lists of dictionaries and want to combine them into a list of merged dictionaries, where two dictionaries are merged if they coincide in one attribute, you can use the ``lists_mergeby`` filter.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- name: Merge two lists by common attribute 'name'
|
||||
debug:
|
||||
var: list1 | community.general.lists_mergeby(list2, 'name')
|
||||
vars:
|
||||
list1:
|
||||
- name: foo
|
||||
extra: true
|
||||
- name: bar
|
||||
extra: false
|
||||
- name: meh
|
||||
extra: true
|
||||
list2:
|
||||
- name: foo
|
||||
path: /foo
|
||||
- name: baz
|
||||
path: /bazzz
|
||||
|
||||
This produces:
|
||||
|
||||
.. code-block:: ansible-output
|
||||
|
||||
TASK [Merge two lists by common attribute 'name'] ****************************************
|
||||
ok: [localhost] => {
|
||||
"list1 | community.general.lists_mergeby(list2, 'name')": [
|
||||
{
|
||||
"extra": false,
|
||||
"name": "bar"
|
||||
},
|
||||
{
|
||||
"name": "baz",
|
||||
"path": "/bazzz"
|
||||
},
|
||||
{
|
||||
"extra": true,
|
||||
"name": "foo",
|
||||
"path": "/foo"
|
||||
},
|
||||
{
|
||||
"extra": true,
|
||||
"name": "meh"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
.. versionadded: 2.0.0
|
||||
|
||||
Working with times
|
||||
------------------
|
||||
|
||||
The ``to_time_unit`` filter allows to convert times from a human-readable string to a unit. For example, ``'4h 30min 12second' | community.general.to_time_unit('hour')`` gives the number of hours that correspond to 4 hours, 30 minutes and 12 seconds.
|
||||
|
||||
There are shorthands to directly convert to various units, like ``to_hours``, ``to_minutes``, ``to_seconds``, and so on. The following table lists all units that can be used:
|
||||
|
||||
.. list-table:: Units
|
||||
:widths: 25 25 25 25
|
||||
:header-rows: 1
|
||||
|
||||
* - Unit name
|
||||
- Unit value in seconds
|
||||
- Unit strings for filter
|
||||
- Shorthand filter
|
||||
* - Millisecond
|
||||
- 1/1000 second
|
||||
- ``ms``, ``millisecond``, ``milliseconds``, ``msec``, ``msecs``, ``msecond``, ``mseconds``
|
||||
- ``to_milliseconds``
|
||||
* - Second
|
||||
- 1 second
|
||||
- ``s``, ``sec``, ``secs``, ``second``, ``seconds``
|
||||
- ``to_seconds``
|
||||
* - Minute
|
||||
- 60 seconds
|
||||
- ``m``, ``min``, ``mins``, ``minute``, ``minutes``
|
||||
- ``to_minutes``
|
||||
* - Hour
|
||||
- 60*60 seconds
|
||||
- ``h``, ``hour``, ``hours``
|
||||
- ``to_hours``
|
||||
* - Day
|
||||
- 24*60*60 seconds
|
||||
- ``d``, ``day``, ``days``
|
||||
- ``to_days``
|
||||
* - Week
|
||||
- 7*24*60*60 seconds
|
||||
- ``w``, ``week``, ``weeks``
|
||||
- ``to_weeks``
|
||||
* - Month
|
||||
- 30*24*60*60 seconds
|
||||
- ``mo``, ``month``, ``months``
|
||||
- ``to_months``
|
||||
* - Year
|
||||
- 365*24*60*60 seconds
|
||||
- ``y``, ``year``, ``years``
|
||||
- ``to_years``
|
||||
|
||||
Note that months and years are using a simplified representation: a month is 30 days, and a year is 365 days. If you need different definitions of months or years, you can pass them as keyword arguments. For example, if you want a year to be 365.25 days, and a month to be 30.5 days, you can write ``'11months 4' | community.general.to_years(year=365.25, month=30.5)``. These keyword arguments can be specified to ``to_time_unit`` and to all shorthand filters.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- name: Convert string to seconds
|
||||
debug:
|
||||
msg: "{{ '30h 20m 10s 123ms' | community.general.to_time_unit('seconds') }}"
|
||||
|
||||
- name: Convert string to hours
|
||||
debug:
|
||||
msg: "{{ '30h 20m 10s 123ms' | community.general.to_hours }}"
|
||||
|
||||
- name: Convert string to years (using 365.25 days == 1 year)
|
||||
debug:
|
||||
msg: "{{ '400d 15h' | community.general.to_years(year=365.25) }}"
|
||||
|
||||
This produces:
|
||||
|
||||
.. code-block:: ansible-output
|
||||
|
||||
TASK [Convert string to seconds] **********************************************************
|
||||
ok: [localhost] => {
|
||||
"msg": "109210.123"
|
||||
}
|
||||
|
||||
TASK [Convert string to hours] ************************************************************
|
||||
ok: [localhost] => {
|
||||
"msg": "30.336145277778"
|
||||
}
|
||||
|
||||
TASK [Convert string to years (using 365.25 days == 1 year)] ******************************
|
||||
ok: [localhost] => {
|
||||
"msg": "1.096851471595"
|
||||
}
|
||||
|
||||
.. versionadded: 0.2.0
|
||||
|
||||
Working with versions
|
||||
---------------------
|
||||
|
||||
If you need to sort a list of version numbers, the Jinja ``sort`` filter is problematic. Since it sorts lexicographically, ``2.10`` will come before ``2.9``. To treat version numbers correctly, you can use the ``version_sort`` filter:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- name: Sort list by version number
|
||||
debug:
|
||||
var: ansible_versions | community.general.version_sort
|
||||
vars:
|
||||
ansible_versions:
|
||||
- '2.8.0'
|
||||
- '2.11.0'
|
||||
- '2.7.0'
|
||||
- '2.10.0'
|
||||
- '2.9.0'
|
||||
|
||||
This produces:
|
||||
|
||||
.. code-block:: ansible-output
|
||||
|
||||
TASK [Sort list by version number] ********************************************************
|
||||
ok: [localhost] => {
|
||||
"ansible_versions | community.general.version_sort": [
|
||||
"2.7.0",
|
||||
"2.8.0",
|
||||
"2.9.0",
|
||||
"2.10.0",
|
||||
"2.11.0"
|
||||
]
|
||||
}
|
||||
|
||||
.. versionadded: 2.2.0
|
||||
|
||||
Creating identifiers
|
||||
--------------------
|
||||
|
||||
The following filters allow to create identifiers.
|
||||
|
||||
Hashids
|
||||
^^^^^^^
|
||||
|
||||
`Hashids <https://hashids.org/>`_ allow to convert sequences of integers to short unique string identifiers. This filter needs the `hashids Python library <https://pypi.org/project/hashids/>`_ installed on the controller.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- name: "Create hashid"
|
||||
debug:
|
||||
msg: "{{ [1234, 5, 6] | community.general.hashids_encode }}"
|
||||
|
||||
- name: "Decode hashid"
|
||||
debug:
|
||||
msg: "{{ 'jm2Cytn' | community.general.hashids_decode }}"
|
||||
|
||||
This produces:
|
||||
|
||||
.. code-block:: ansible-output
|
||||
|
||||
TASK [Create hashid] **********************************************************************
|
||||
ok: [localhost] => {
|
||||
"msg": "jm2Cytn"
|
||||
}
|
||||
|
||||
TASK [Decode hashid] **********************************************************************
|
||||
ok: [localhost] => {
|
||||
"msg": [
|
||||
1234,
|
||||
5,
|
||||
6
|
||||
]
|
||||
}
|
||||
|
||||
The hashids filters accept keyword arguments to allow fine-tuning the hashids generated:
|
||||
|
||||
:salt: String to use as salt when hashing.
|
||||
:alphabet: String of 16 or more unique characters to produce a hash.
|
||||
:min_length: Minimum length of hash produced.
|
||||
|
||||
.. versionadded: 3.0.0
|
||||
|
||||
Random MACs
|
||||
^^^^^^^^^^^
|
||||
|
||||
You can use the ``random_mac`` filter to complete a partial `MAC address <https://en.wikipedia.org/wiki/MAC_address>`_ to a random 6-byte MAC address.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- name: "Create a random MAC starting with ff:"
|
||||
debug:
|
||||
msg: "{{ 'FF' | community.general.random_mac }}"
|
||||
|
||||
- name: "Create a random MAC starting with 00:11:22:"
|
||||
debug:
|
||||
msg: "{{ '00:11:22' | community.general.random_mac }}"
|
||||
|
||||
This produces:
|
||||
|
||||
.. code-block:: ansible-output
|
||||
|
||||
TASK [Create a random MAC starting with ff:] **********************************************
|
||||
ok: [localhost] => {
|
||||
"msg": "ff:69:d3:78:7f:b4"
|
||||
}
|
||||
|
||||
TASK [Create a random MAC starting with 00:11:22:] ****************************************
|
||||
ok: [localhost] => {
|
||||
"msg": "00:11:22:71:5d:3b"
|
||||
}
|
||||
|
||||
You can also initialize the random number generator from a seed to create random-but-idempotent MAC addresses:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
"{{ '52:54:00' | community.general.random_mac(seed=inventory_hostname) }}"
|
||||
|
||||
Conversions
|
||||
-----------
|
||||
|
||||
Parsing CSV files
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
Ansible offers the :ref:`community.general.read_csv module <ansible_collections.community.general.read_csv_module>` to read CSV files. Sometimes you need to convert strings to CSV files instead. For this, the ``from_csv`` filter exists.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- name: "Parse CSV from string"
|
||||
debug:
|
||||
msg: "{{ csv_string | community.general.from_csv }}"
|
||||
vars:
|
||||
csv_string: |
|
||||
foo,bar,baz
|
||||
1,2,3
|
||||
you,this,then
|
||||
|
||||
This produces:
|
||||
|
||||
.. code-block:: ansible-output
|
||||
|
||||
TASK [Parse CSV from string] **************************************************************
|
||||
ok: [localhost] => {
|
||||
"msg": [
|
||||
{
|
||||
"bar": "2",
|
||||
"baz": "3",
|
||||
"foo": "1"
|
||||
},
|
||||
{
|
||||
"bar": "this",
|
||||
"baz": "then",
|
||||
"foo": "you"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
The ``from_csv`` filter has several keyword arguments to control its behavior:
|
||||
|
||||
:dialect: Dialect of the CSV file. Default is ``excel``. Other possible choices are ``excel-tab`` and ``unix``. If one of ``delimiter``, ``skipinitialspace`` or ``strict`` is specified, ``dialect`` is ignored.
|
||||
:fieldnames: A set of column names to use. If not provided, the first line of the CSV is assumed to contain the column names.
|
||||
:delimiter: Sets the delimiter to use. Default depends on the dialect used.
|
||||
:skipinitialspace: Set to ``true`` to ignore space directly after the delimiter. Default depends on the dialect used (usually ``false``).
|
||||
:strict: Set to ``true`` to error out on invalid CSV input.
|
||||
|
||||
.. versionadded: 3.0.0
|
||||
|
||||
Converting to JSON
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
`JC <https://pypi.org/project/jc/>`_ is a CLI tool and Python library which allows to interpret output of various CLI programs as JSON. It is also available as a filter in community.general. This filter needs the `jc Python library <https://pypi.org/project/jc/>`_ installed on the controller.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- name: Run 'ls' to list files in /
|
||||
command: ls /
|
||||
register: result
|
||||
|
||||
- name: Parse the ls output
|
||||
debug:
|
||||
msg: "{{ result.stdout | community.general.jc('ls') }}"
|
||||
|
||||
This produces:
|
||||
|
||||
.. code-block:: ansible-output
|
||||
|
||||
TASK [Run 'ls' to list files in /] ********************************************************
|
||||
changed: [localhost]
|
||||
|
||||
TASK [Parse the ls output] ****************************************************************
|
||||
ok: [localhost] => {
|
||||
"msg": [
|
||||
{
|
||||
"filename": "bin"
|
||||
},
|
||||
{
|
||||
"filename": "boot"
|
||||
},
|
||||
{
|
||||
"filename": "dev"
|
||||
},
|
||||
{
|
||||
"filename": "etc"
|
||||
},
|
||||
{
|
||||
"filename": "home"
|
||||
},
|
||||
{
|
||||
"filename": "lib"
|
||||
},
|
||||
{
|
||||
"filename": "proc"
|
||||
},
|
||||
{
|
||||
"filename": "root"
|
||||
},
|
||||
{
|
||||
"filename": "run"
|
||||
},
|
||||
{
|
||||
"filename": "tmp"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
.. versionadded: 2.0.0
|
||||
|
||||
.. _ansible_collections.community.general.docsite.json_query_filter:
|
||||
|
||||
Selecting JSON data: JSON queries
|
||||
---------------------------------
|
||||
|
||||
To select a single element or a data subset from a complex data structure in JSON format (for example, Ansible facts), use the ``json_query`` filter. The ``json_query`` filter lets you query a complex JSON structure and iterate over it using a loop structure.
|
||||
|
||||
.. note:: You must manually install the **jmespath** dependency on the Ansible controller before using this filter. This filter is built upon **jmespath**, and you can use the same syntax. For examples, see `jmespath examples <http://jmespath.org/examples.html>`_.
|
||||
|
||||
Consider this data structure:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
{
|
||||
"domain_definition": {
|
||||
"domain": {
|
||||
"cluster": [
|
||||
{
|
||||
"name": "cluster1"
|
||||
},
|
||||
{
|
||||
"name": "cluster2"
|
||||
}
|
||||
],
|
||||
"server": [
|
||||
{
|
||||
"name": "server11",
|
||||
"cluster": "cluster1",
|
||||
"port": "8080"
|
||||
},
|
||||
{
|
||||
"name": "server12",
|
||||
"cluster": "cluster1",
|
||||
"port": "8090"
|
||||
},
|
||||
{
|
||||
"name": "server21",
|
||||
"cluster": "cluster2",
|
||||
"port": "9080"
|
||||
},
|
||||
{
|
||||
"name": "server22",
|
||||
"cluster": "cluster2",
|
||||
"port": "9090"
|
||||
}
|
||||
],
|
||||
"library": [
|
||||
{
|
||||
"name": "lib1",
|
||||
"target": "cluster1"
|
||||
},
|
||||
{
|
||||
"name": "lib2",
|
||||
"target": "cluster2"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
To extract all clusters from this structure, you can use the following query:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- name: Display all cluster names
|
||||
ansible.builtin.debug:
|
||||
var: item
|
||||
loop: "{{ domain_definition | community.general.json_query('domain.cluster[*].name') }}"
|
||||
|
||||
To extract all server names:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- name: Display all server names
|
||||
ansible.builtin.debug:
|
||||
var: item
|
||||
loop: "{{ domain_definition | community.general.json_query('domain.server[*].name') }}"
|
||||
|
||||
To extract ports from cluster1:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- name: Display all ports from cluster1
|
||||
ansible.builtin.debug:
|
||||
var: item
|
||||
loop: "{{ domain_definition | community.general.json_query(server_name_cluster1_query) }}"
|
||||
vars:
|
||||
server_name_cluster1_query: "domain.server[?cluster=='cluster1'].port"
|
||||
|
||||
.. note:: You can use a variable to make the query more readable.
|
||||
|
||||
To print out the ports from cluster1 in a comma separated string:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- name: Display all ports from cluster1 as a string
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ domain_definition | community.general.json_query('domain.server[?cluster==`cluster1`].port') | join(', ') }}"
|
||||
|
||||
.. note:: In the example above, quoting literals using backticks avoids escaping quotes and maintains readability.
|
||||
|
||||
You can use YAML `single quote escaping <https://yaml.org/spec/current.html#id2534365>`_:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- name: Display all ports from cluster1
|
||||
ansible.builtin.debug:
|
||||
var: item
|
||||
loop: "{{ domain_definition | community.general.json_query('domain.server[?cluster==''cluster1''].port') }}"
|
||||
|
||||
.. note:: Escaping single quotes within single quotes in YAML is done by doubling the single quote.
|
||||
|
||||
To get a hash map with all ports and names of a cluster:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- name: Display all server ports and names from cluster1
|
||||
ansible.builtin.debug:
|
||||
var: item
|
||||
loop: "{{ domain_definition | community.general.json_query(server_name_cluster1_query) }}"
|
||||
vars:
|
||||
server_name_cluster1_query: "domain.server[?cluster=='cluster2'].{name: name, port: port}"
|
||||
|
||||
To extract ports from all clusters with name starting with 'server1':
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- name: Display all ports from cluster1
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ domain_definition | to_json | from_json | community.general.json_query(server_name_query) }}"
|
||||
vars:
|
||||
server_name_query: "domain.server[?starts_with(name,'server1')].port"
|
||||
|
||||
To extract ports from all clusters with name containing 'server1':
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- name: Display all ports from cluster1
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ domain_definition | to_json | from_json | community.general.json_query(server_name_query) }}"
|
||||
vars:
|
||||
server_name_query: "domain.server[?contains(name,'server1')].port"
|
||||
|
||||
.. note:: while using ``starts_with`` and ``contains``, you have to use `` to_json | from_json `` filter for correct parsing of data structure.
|
||||
14
galaxy.yml
14
galaxy.yml
@@ -1,16 +1,20 @@
|
||||
namespace: community
|
||||
name: general
|
||||
version: 3.3.1
|
||||
version: 0.3.0-experimental.meta.redirects
|
||||
readme: README.md
|
||||
authors:
|
||||
- Ansible (https://github.com/ansible)
|
||||
description: null
|
||||
license_file: COPYING
|
||||
tags: [community]
|
||||
# NOTE: No dependencies are expected to be added here
|
||||
# dependencies:
|
||||
tags: null
|
||||
# NOTE: No more dependencies can be added to this list
|
||||
dependencies:
|
||||
ansible.netcommon: '>=0.0.2'
|
||||
ansible.posix: '>=1.0.0'
|
||||
community.kubernetes: '>=0.1.0'
|
||||
google.cloud: '>=0.0.9'
|
||||
repository: https://github.com/ansible-collections/community.general
|
||||
documentation: https://docs.ansible.com/ansible/latest/collections/community/general/
|
||||
#documentation: https://github.com/ansible-collection-migration/community.general/tree/master/docs
|
||||
homepage: https://github.com/ansible-collections/community.general
|
||||
issues: https://github.com/ansible-collections/community.general/issues
|
||||
#type: flatmap
|
||||
|
||||
1992
meta/runtime.yml
1992
meta/runtime.yml
File diff suppressed because it is too large
Load Diff
@@ -1 +0,0 @@
|
||||
./system/iptables_state.py
|
||||
@@ -1 +0,0 @@
|
||||
./system/shutdown.py
|
||||
@@ -1,185 +0,0 @@
|
||||
# Copyright: (c) 2020, quidame <quidame@poivron.org>
|
||||
# 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
|
||||
|
||||
import time
|
||||
|
||||
from ansible.plugins.action import ActionBase
|
||||
from ansible.errors import AnsibleActionFail, AnsibleConnectionFailure
|
||||
from ansible.utils.vars import merge_hash
|
||||
from ansible.utils.display import Display
|
||||
|
||||
display = Display()
|
||||
|
||||
|
||||
class ActionModule(ActionBase):
|
||||
|
||||
# Keep internal params away from user interactions
|
||||
_VALID_ARGS = frozenset(('path', 'state', 'table', 'noflush', 'counters', 'modprobe', 'ip_version', 'wait'))
|
||||
DEFAULT_SUDOABLE = True
|
||||
|
||||
MSG_ERROR__ASYNC_AND_POLL_NOT_ZERO = (
|
||||
"This module doesn't support async>0 and poll>0 when its 'state' param "
|
||||
"is set to 'restored'. To enable its rollback feature (that needs the "
|
||||
"module to run asynchronously on the remote), please set task attribute "
|
||||
"'poll' (=%s) to 0, and 'async' (=%s) to a value >2 and not greater than "
|
||||
"'ansible_timeout' (=%s) (recommended).")
|
||||
MSG_WARNING__NO_ASYNC_IS_NO_ROLLBACK = (
|
||||
"Attempts to restore iptables state without rollback in case of mistake "
|
||||
"may lead the ansible controller to loose access to the hosts and never "
|
||||
"regain it before fixing firewall rules through a serial console, or any "
|
||||
"other way except SSH. Please set task attribute 'poll' (=%s) to 0, and "
|
||||
"'async' (=%s) to a value >2 and not greater than 'ansible_timeout' (=%s) "
|
||||
"(recommended).")
|
||||
MSG_WARNING__ASYNC_GREATER_THAN_TIMEOUT = (
|
||||
"You attempt to restore iptables state with rollback in case of mistake, "
|
||||
"but with settings that will lead this rollback to happen AFTER that the "
|
||||
"controller will reach its own timeout. Please set task attribute 'poll' "
|
||||
"(=%s) to 0, and 'async' (=%s) to a value >2 and not greater than "
|
||||
"'ansible_timeout' (=%s) (recommended).")
|
||||
|
||||
def _async_result(self, async_status_args, task_vars, timeout):
|
||||
'''
|
||||
Retrieve results of the asynchonous task, and display them in place of
|
||||
the async wrapper results (those with the ansible_job_id key).
|
||||
'''
|
||||
async_status = self._task.copy()
|
||||
async_status.args = async_status_args
|
||||
async_status.action = 'ansible.builtin.async_status'
|
||||
async_status.async_val = 0
|
||||
async_action = self._shared_loader_obj.action_loader.get(
|
||||
async_status.action, task=async_status, connection=self._connection,
|
||||
play_context=self._play_context, loader=self._loader, templar=self._templar,
|
||||
shared_loader_obj=self._shared_loader_obj)
|
||||
|
||||
if async_status.args['mode'] == 'cleanup':
|
||||
return async_action.run(task_vars=task_vars)
|
||||
|
||||
# At least one iteration is required, even if timeout is 0.
|
||||
for dummy in range(max(1, timeout)):
|
||||
async_result = async_action.run(task_vars=task_vars)
|
||||
if async_result.get('finished', 0) == 1:
|
||||
break
|
||||
time.sleep(min(1, timeout))
|
||||
|
||||
return async_result
|
||||
|
||||
def run(self, tmp=None, task_vars=None):
|
||||
|
||||
self._supports_check_mode = True
|
||||
self._supports_async = True
|
||||
|
||||
result = super(ActionModule, self).run(tmp, task_vars)
|
||||
del tmp # tmp no longer has any effect
|
||||
|
||||
if not result.get('skipped'):
|
||||
|
||||
# FUTURE: better to let _execute_module calculate this internally?
|
||||
wrap_async = self._task.async_val and not self._connection.has_native_async
|
||||
|
||||
# Set short names for values we'll have to compare or reuse
|
||||
task_poll = self._task.poll
|
||||
task_async = self._task.async_val
|
||||
check_mode = self._play_context.check_mode
|
||||
max_timeout = self._connection._play_context.timeout
|
||||
module_args = self._task.args
|
||||
|
||||
if module_args.get('state', None) == 'restored':
|
||||
if not wrap_async:
|
||||
if not check_mode:
|
||||
display.warning(self.MSG_WARNING__NO_ASYNC_IS_NO_ROLLBACK % (
|
||||
task_poll,
|
||||
task_async,
|
||||
max_timeout))
|
||||
elif task_poll:
|
||||
raise AnsibleActionFail(self.MSG_ERROR__ASYNC_AND_POLL_NOT_ZERO % (
|
||||
task_poll,
|
||||
task_async,
|
||||
max_timeout))
|
||||
else:
|
||||
if task_async > max_timeout and not check_mode:
|
||||
display.warning(self.MSG_WARNING__ASYNC_GREATER_THAN_TIMEOUT % (
|
||||
task_poll,
|
||||
task_async,
|
||||
max_timeout))
|
||||
|
||||
# inject the async directory based on the shell option into the
|
||||
# module args
|
||||
async_dir = self.get_shell_option('async_dir', default="~/.ansible_async")
|
||||
|
||||
# Bind the loop max duration to consistent values on both
|
||||
# remote and local sides (if not the same, make the loop
|
||||
# longer on the controller); and set a backup file path.
|
||||
module_args['_timeout'] = task_async
|
||||
module_args['_back'] = '%s/iptables.state' % async_dir
|
||||
async_status_args = dict(mode='status')
|
||||
confirm_cmd = 'rm -f %s' % module_args['_back']
|
||||
starter_cmd = 'touch %s.starter' % module_args['_back']
|
||||
remaining_time = max(task_async, max_timeout)
|
||||
|
||||
# do work!
|
||||
result = merge_hash(result, self._execute_module(module_args=module_args, task_vars=task_vars, wrap_async=wrap_async))
|
||||
|
||||
# Then the 3-steps "go ahead or rollback":
|
||||
# 1. Catch early errors of the module (in asynchronous task) if any.
|
||||
# Touch a file on the target to signal the module to process now.
|
||||
# 2. Reset connection to ensure a persistent one will not be reused.
|
||||
# 3. Confirm the restored state by removing the backup on the remote.
|
||||
# Retrieve the results of the asynchronous task to return them.
|
||||
if '_back' in module_args:
|
||||
async_status_args['jid'] = result.get('ansible_job_id', None)
|
||||
if async_status_args['jid'] is None:
|
||||
raise AnsibleActionFail("Unable to get 'ansible_job_id'.")
|
||||
|
||||
# Catch early errors due to missing mandatory option, bad
|
||||
# option type/value, missing required system command, etc.
|
||||
result = merge_hash(result, self._async_result(async_status_args, task_vars, 0))
|
||||
|
||||
# The module is aware to not process the main iptables-restore
|
||||
# command before finding (and deleting) the 'starter' cookie on
|
||||
# the host, so the previous query will not reach ssh timeout.
|
||||
dummy = self._low_level_execute_command(starter_cmd, sudoable=self.DEFAULT_SUDOABLE)
|
||||
|
||||
# As the main command is not yet executed on the target, here
|
||||
# 'finished' means 'failed before main command be executed'.
|
||||
if not result['finished']:
|
||||
try:
|
||||
self._connection.reset()
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
for dummy in range(max_timeout):
|
||||
time.sleep(1)
|
||||
remaining_time -= 1
|
||||
# - AnsibleConnectionFailure covers rejected requests (i.e.
|
||||
# by rules with '--jump REJECT')
|
||||
# - ansible_timeout is able to cover dropped requests (due
|
||||
# to a rule or policy DROP) if not lower than async_val.
|
||||
try:
|
||||
dummy = self._low_level_execute_command(confirm_cmd, sudoable=self.DEFAULT_SUDOABLE)
|
||||
break
|
||||
except AnsibleConnectionFailure:
|
||||
continue
|
||||
|
||||
result = merge_hash(result, self._async_result(async_status_args, task_vars, remaining_time))
|
||||
|
||||
# Cleanup async related stuff and internal params
|
||||
for key in ('ansible_job_id', 'results_file', 'started', 'finished'):
|
||||
if result.get(key):
|
||||
del result[key]
|
||||
|
||||
if result.get('invocation', {}).get('module_args'):
|
||||
for key in ('_back', '_timeout', '_async_dir', 'jid'):
|
||||
if result['invocation']['module_args'].get(key):
|
||||
del result['invocation']['module_args'][key]
|
||||
|
||||
async_status_args['mode'] = 'cleanup'
|
||||
dummy = self._async_result(async_status_args, task_vars, 0)
|
||||
|
||||
if not wrap_async:
|
||||
# remove a temporary path we created
|
||||
self._remove_tmp_path(self._connection._shell.tmpdir)
|
||||
|
||||
return result
|
||||
@@ -1,211 +0,0 @@
|
||||
# Copyright: (c) 2020, Amin Vakil <info@aminvakil.com>
|
||||
# Copyright: (c) 2016-2018, Matt Davis <mdavis@ansible.com>
|
||||
# Copyright: (c) 2018, Sam Doran <sdoran@redhat.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
|
||||
|
||||
from ansible.errors import AnsibleError, AnsibleConnectionFailure
|
||||
from ansible.module_utils.common.text.converters import to_native, to_text
|
||||
from ansible.module_utils.common.collections import is_string
|
||||
from ansible.plugins.action import ActionBase
|
||||
from ansible.utils.display import Display
|
||||
|
||||
display = Display()
|
||||
|
||||
|
||||
class TimedOutException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ActionModule(ActionBase):
|
||||
TRANSFERS_FILES = False
|
||||
_VALID_ARGS = frozenset((
|
||||
'msg',
|
||||
'delay',
|
||||
'search_paths'
|
||||
))
|
||||
|
||||
DEFAULT_CONNECT_TIMEOUT = None
|
||||
DEFAULT_PRE_SHUTDOWN_DELAY = 0
|
||||
DEFAULT_SHUTDOWN_MESSAGE = 'Shut down initiated by Ansible'
|
||||
DEFAULT_SHUTDOWN_COMMAND = 'shutdown'
|
||||
DEFAULT_SHUTDOWN_COMMAND_ARGS = '-h {delay_min} "{message}"'
|
||||
DEFAULT_SUDOABLE = True
|
||||
|
||||
SHUTDOWN_COMMANDS = {
|
||||
'alpine': 'poweroff',
|
||||
'vmkernel': 'halt',
|
||||
}
|
||||
|
||||
SHUTDOWN_COMMAND_ARGS = {
|
||||
'alpine': '',
|
||||
'void': '-h +{delay_min} "{message}"',
|
||||
'freebsd': '-h +{delay_sec}s "{message}"',
|
||||
'linux': DEFAULT_SHUTDOWN_COMMAND_ARGS,
|
||||
'macosx': '-h +{delay_min} "{message}"',
|
||||
'openbsd': '-h +{delay_min} "{message}"',
|
||||
'solaris': '-y -g {delay_sec} -i 5 "{message}"',
|
||||
'sunos': '-y -g {delay_sec} -i 5 "{message}"',
|
||||
'vmkernel': '-d {delay_sec}',
|
||||
'aix': '-Fh',
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ActionModule, self).__init__(*args, **kwargs)
|
||||
|
||||
@property
|
||||
def delay(self):
|
||||
return self._check_delay('delay', self.DEFAULT_PRE_SHUTDOWN_DELAY)
|
||||
|
||||
def _check_delay(self, key, default):
|
||||
"""Ensure that the value is positive or zero"""
|
||||
value = int(self._task.args.get(key, default))
|
||||
if value < 0:
|
||||
value = 0
|
||||
return value
|
||||
|
||||
def _get_value_from_facts(self, variable_name, distribution, default_value):
|
||||
"""Get dist+version specific args first, then distribution, then family, lastly use default"""
|
||||
attr = getattr(self, variable_name)
|
||||
value = attr.get(
|
||||
distribution['name'] + distribution['version'],
|
||||
attr.get(
|
||||
distribution['name'],
|
||||
attr.get(
|
||||
distribution['family'],
|
||||
getattr(self, default_value))))
|
||||
return value
|
||||
|
||||
def get_shutdown_command_args(self, distribution):
|
||||
args = self._get_value_from_facts('SHUTDOWN_COMMAND_ARGS', distribution, 'DEFAULT_SHUTDOWN_COMMAND_ARGS')
|
||||
# Convert seconds to minutes. If less that 60, set it to 0.
|
||||
delay_sec = self.delay
|
||||
shutdown_message = self._task.args.get('msg', self.DEFAULT_SHUTDOWN_MESSAGE)
|
||||
return args.format(delay_sec=delay_sec, delay_min=delay_sec // 60, message=shutdown_message)
|
||||
|
||||
def get_distribution(self, task_vars):
|
||||
# FIXME: only execute the module if we don't already have the facts we need
|
||||
distribution = {}
|
||||
display.debug('{action}: running setup module to get distribution'.format(action=self._task.action))
|
||||
module_output = self._execute_module(
|
||||
task_vars=task_vars,
|
||||
module_name='ansible.legacy.setup',
|
||||
module_args={'gather_subset': 'min'})
|
||||
try:
|
||||
if module_output.get('failed', False):
|
||||
raise AnsibleError('Failed to determine system distribution. {0}, {1}'.format(
|
||||
to_native(module_output['module_stdout']).strip(),
|
||||
to_native(module_output['module_stderr']).strip()))
|
||||
distribution['name'] = module_output['ansible_facts']['ansible_distribution'].lower()
|
||||
distribution['version'] = to_text(module_output['ansible_facts']['ansible_distribution_version'].split('.')[0])
|
||||
distribution['family'] = to_text(module_output['ansible_facts']['ansible_os_family'].lower())
|
||||
display.debug("{action}: distribution: {dist}".format(action=self._task.action, dist=distribution))
|
||||
return distribution
|
||||
except KeyError as ke:
|
||||
raise AnsibleError('Failed to get distribution information. Missing "{0}" in output.'.format(ke.args[0]))
|
||||
|
||||
def get_shutdown_command(self, task_vars, distribution):
|
||||
shutdown_bin = self._get_value_from_facts('SHUTDOWN_COMMANDS', distribution, 'DEFAULT_SHUTDOWN_COMMAND')
|
||||
default_search_paths = ['/sbin', '/usr/sbin', '/usr/local/sbin']
|
||||
search_paths = self._task.args.get('search_paths', default_search_paths)
|
||||
|
||||
# FIXME: switch all this to user arg spec validation methods when they are available
|
||||
# Convert bare strings to a list
|
||||
if is_string(search_paths):
|
||||
search_paths = [search_paths]
|
||||
|
||||
# Error if we didn't get a list
|
||||
err_msg = "'search_paths' must be a string or flat list of strings, got {0}"
|
||||
try:
|
||||
incorrect_type = any(not is_string(x) for x in search_paths)
|
||||
if not isinstance(search_paths, list) or incorrect_type:
|
||||
raise TypeError
|
||||
except TypeError:
|
||||
raise AnsibleError(err_msg.format(search_paths))
|
||||
|
||||
display.debug('{action}: running find module looking in {paths} to get path for "{command}"'.format(
|
||||
action=self._task.action,
|
||||
command=shutdown_bin,
|
||||
paths=search_paths))
|
||||
find_result = self._execute_module(
|
||||
task_vars=task_vars,
|
||||
# prevent collection search by calling with ansible.legacy (still allows library/ override of find)
|
||||
module_name='ansible.legacy.find',
|
||||
module_args={
|
||||
'paths': search_paths,
|
||||
'patterns': [shutdown_bin],
|
||||
'file_type': 'any'
|
||||
}
|
||||
)
|
||||
|
||||
full_path = [x['path'] for x in find_result['files']]
|
||||
if not full_path:
|
||||
raise AnsibleError('Unable to find command "{0}" in search paths: {1}'.format(shutdown_bin, search_paths))
|
||||
self._shutdown_command = full_path[0]
|
||||
return self._shutdown_command
|
||||
|
||||
def perform_shutdown(self, task_vars, distribution):
|
||||
result = {}
|
||||
shutdown_result = {}
|
||||
shutdown_command = self.get_shutdown_command(task_vars, distribution)
|
||||
shutdown_command_args = self.get_shutdown_command_args(distribution)
|
||||
shutdown_command_exec = '{0} {1}'.format(shutdown_command, shutdown_command_args)
|
||||
|
||||
self.cleanup(force=True)
|
||||
try:
|
||||
display.vvv("{action}: shutting down server...".format(action=self._task.action))
|
||||
display.debug("{action}: shutting down server with command '{command}'".format(action=self._task.action, command=shutdown_command_exec))
|
||||
if self._play_context.check_mode:
|
||||
shutdown_result['rc'] = 0
|
||||
else:
|
||||
shutdown_result = self._low_level_execute_command(shutdown_command_exec, sudoable=self.DEFAULT_SUDOABLE)
|
||||
except AnsibleConnectionFailure as e:
|
||||
# If the connection is closed too quickly due to the system being shutdown, carry on
|
||||
display.debug('{action}: AnsibleConnectionFailure caught and handled: {error}'.format(action=self._task.action, error=to_text(e)))
|
||||
shutdown_result['rc'] = 0
|
||||
|
||||
if shutdown_result['rc'] != 0:
|
||||
result['failed'] = True
|
||||
result['shutdown'] = False
|
||||
result['msg'] = "Shutdown command failed. Error was {stdout}, {stderr}".format(
|
||||
stdout=to_native(shutdown_result['stdout'].strip()),
|
||||
stderr=to_native(shutdown_result['stderr'].strip()))
|
||||
return result
|
||||
|
||||
result['failed'] = False
|
||||
result['shutdown_command'] = shutdown_command_exec
|
||||
return result
|
||||
|
||||
def run(self, tmp=None, task_vars=None):
|
||||
self._supports_check_mode = True
|
||||
self._supports_async = True
|
||||
|
||||
# If running with local connection, fail so we don't shutdown ourself
|
||||
if self._connection.transport == 'local' and (not self._play_context.check_mode):
|
||||
msg = 'Running {0} with local connection would shutdown the control node.'.format(self._task.action)
|
||||
return {'changed': False, 'elapsed': 0, 'shutdown': False, 'failed': True, 'msg': msg}
|
||||
|
||||
if task_vars is None:
|
||||
task_vars = {}
|
||||
|
||||
result = super(ActionModule, self).run(tmp, task_vars)
|
||||
|
||||
if result.get('skipped', False) or result.get('failed', False):
|
||||
return result
|
||||
|
||||
distribution = self.get_distribution(task_vars)
|
||||
|
||||
# Initiate shutdown
|
||||
shutdown_result = self.perform_shutdown(task_vars, distribution)
|
||||
|
||||
if shutdown_result['failed']:
|
||||
result = shutdown_result
|
||||
return result
|
||||
|
||||
result['shutdown'] = True
|
||||
result['changed'] = True
|
||||
result['shutdown_command'] = shutdown_result['shutdown_command']
|
||||
|
||||
return result
|
||||
@@ -5,7 +5,7 @@ from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: doas
|
||||
become: doas
|
||||
short_description: Do As user
|
||||
description:
|
||||
- This become plugins allows your remote/login user to execute commands as another user via the doas utility.
|
||||
@@ -40,7 +40,7 @@ DOCUMENTATION = '''
|
||||
- name: ANSIBLE_DOAS_EXE
|
||||
become_flags:
|
||||
description: Options to pass to doas
|
||||
default: ''
|
||||
default:
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_flags
|
||||
@@ -81,7 +81,7 @@ DOCUMENTATION = '''
|
||||
|
||||
import re
|
||||
|
||||
from ansible.module_utils.common.text.converters import to_bytes
|
||||
from ansible.module_utils._text import to_bytes
|
||||
from ansible.plugins.become import BecomeBase
|
||||
|
||||
|
||||
@@ -117,8 +117,9 @@ class BecomeModule(BecomeBase):
|
||||
if not self.get_option('become_pass') and '-n' not in flags:
|
||||
flags += ' -n'
|
||||
|
||||
become_user = self.get_option('become_user')
|
||||
user = '-u %s' % (become_user) if become_user else ''
|
||||
user = self.get_option('become_user')
|
||||
if user:
|
||||
user = '-u %s' % (user)
|
||||
|
||||
success_cmd = self._build_success_command(cmd, shell, noexe=True)
|
||||
executable = getattr(shell, 'executable', shell.SHELL_FAMILY)
|
||||
|
||||
@@ -4,7 +4,7 @@ from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: dzdo
|
||||
become: dzdo
|
||||
short_description: Centrify's Direct Authorize
|
||||
description:
|
||||
- This become plugins allows your remote/login user to execute commands as another user via the dzdo utility.
|
||||
@@ -89,7 +89,8 @@ class BecomeModule(BecomeBase):
|
||||
self.prompt = '[dzdo via ansible, key=%s] password:' % self._id
|
||||
flags = '%s -p "%s"' % (flags.replace('-n', ''), self.prompt)
|
||||
|
||||
become_user = self.get_option('become_user')
|
||||
user = '-u %s' % (become_user) if become_user else ''
|
||||
user = self.get_option('become_user')
|
||||
if user:
|
||||
user = '-u %s' % (user)
|
||||
|
||||
return ' '.join([becomecmd, flags, user, self._build_success_command(cmd, shell)])
|
||||
|
||||
@@ -5,7 +5,7 @@ from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: ksu
|
||||
become: ksu
|
||||
short_description: Kerberos substitute user
|
||||
description:
|
||||
- This become plugins allows your remote/login user to execute commands as another user via the ksu utility.
|
||||
@@ -13,6 +13,7 @@ DOCUMENTATION = '''
|
||||
options:
|
||||
become_user:
|
||||
description: User you 'become' to execute the task
|
||||
default: ''
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_user
|
||||
@@ -82,7 +83,7 @@ DOCUMENTATION = '''
|
||||
|
||||
import re
|
||||
|
||||
from ansible.module_utils.common.text.converters import to_bytes
|
||||
from ansible.module_utils._text import to_bytes
|
||||
from ansible.plugins.become import BecomeBase
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: machinectl
|
||||
become: machinectl
|
||||
short_description: Systemd's machinectl privilege escalation
|
||||
description:
|
||||
- This become plugins allows your remote/login user to execute commands as another user via the machinectl utility.
|
||||
|
||||
@@ -5,7 +5,7 @@ from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: pbrun
|
||||
become: pbrun
|
||||
short_description: PowerBroker run
|
||||
description:
|
||||
- This become plugins allows your remote/login user to execute commands as another user via the pbrun utility.
|
||||
@@ -97,8 +97,9 @@ class BecomeModule(BecomeBase):
|
||||
become_exe = self.get_option('become_exe')
|
||||
|
||||
flags = self.get_option('become_flags')
|
||||
become_user = self.get_option('become_user')
|
||||
user = '-u %s' % (become_user) if become_user else ''
|
||||
user = self.get_option('become_user')
|
||||
if user:
|
||||
user = '-u %s' % (user)
|
||||
noexe = not self.get_option('wrap_exe')
|
||||
|
||||
return ' '.join([become_exe, flags, user, self._build_success_command(cmd, shell, noexe=noexe)])
|
||||
|
||||
@@ -5,7 +5,7 @@ from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: pfexec
|
||||
become: pfexec
|
||||
short_description: profile based execution
|
||||
description:
|
||||
- This become plugins allows your remote/login user to execute commands as another user via the pfexec utility.
|
||||
@@ -14,7 +14,7 @@ DOCUMENTATION = '''
|
||||
become_user:
|
||||
description:
|
||||
- User you 'become' to execute the task
|
||||
- This plugin ignores this setting as pfexec uses it's own C(exec_attr) to figure this out,
|
||||
- This plugin ignores this setting as pfexec uses it's own ``exec_attr`` to figure this out,
|
||||
but it is supplied here for Ansible to make decisions needed for the task execution, like file permissions.
|
||||
default: root
|
||||
ini:
|
||||
@@ -80,8 +80,8 @@ DOCUMENTATION = '''
|
||||
- name: ansible_pfexec_wrap_execution
|
||||
env:
|
||||
- name: ANSIBLE_PFEXEC_WRAP_EXECUTION
|
||||
notes:
|
||||
- This plugin ignores I(become_user) as pfexec uses it's own C(exec_attr) to figure this out.
|
||||
note:
|
||||
- This plugin ignores ``become_user`` as pfexec uses it's own ``exec_attr`` to figure this out.
|
||||
'''
|
||||
|
||||
from ansible.plugins.become import BecomeBase
|
||||
|
||||
@@ -5,7 +5,7 @@ from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: pmrun
|
||||
become: pmrun
|
||||
short_description: Privilege Manager run
|
||||
description:
|
||||
- This become plugins allows your remote/login user to execute commands as another user via the pmrun utility.
|
||||
|
||||
@@ -5,7 +5,7 @@ from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: sesu
|
||||
become: sesu
|
||||
short_description: CA Privileged Access Manager
|
||||
description:
|
||||
- This become plugins allows your remote/login user to execute commands as another user via the sesu utility.
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright: (c) 2021, Ansible Project
|
||||
# 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: sudosu
|
||||
short_description: Run tasks using sudo su -
|
||||
description:
|
||||
- This become plugins allows your remote/login user to execute commands as another user via the C(sudo) and C(su) utilities combined.
|
||||
author:
|
||||
- Dag Wieers (@dagwieers)
|
||||
version_added: 2.4.0
|
||||
options:
|
||||
become_user:
|
||||
description: User you 'become' to execute the task.
|
||||
default: root
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_user
|
||||
- section: sudo_become_plugin
|
||||
key: user
|
||||
vars:
|
||||
- name: ansible_become_user
|
||||
- name: ansible_sudo_user
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_USER
|
||||
- name: ANSIBLE_SUDO_USER
|
||||
become_flags:
|
||||
description: Options to pass to C(sudo).
|
||||
default: -H -S -n
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_flags
|
||||
- section: sudo_become_plugin
|
||||
key: flags
|
||||
vars:
|
||||
- name: ansible_become_flags
|
||||
- name: ansible_sudo_flags
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_FLAGS
|
||||
- name: ANSIBLE_SUDO_FLAGS
|
||||
become_pass:
|
||||
description: Password to pass to C(sudo).
|
||||
required: false
|
||||
vars:
|
||||
- name: ansible_become_password
|
||||
- name: ansible_become_pass
|
||||
- name: ansible_sudo_pass
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_PASS
|
||||
- name: ANSIBLE_SUDO_PASS
|
||||
ini:
|
||||
- section: sudo_become_plugin
|
||||
key: password
|
||||
"""
|
||||
|
||||
|
||||
from ansible.plugins.become import BecomeBase
|
||||
|
||||
|
||||
class BecomeModule(BecomeBase):
|
||||
|
||||
name = 'community.general.sudosu'
|
||||
|
||||
# messages for detecting prompted password issues
|
||||
fail = ('Sorry, try again.',)
|
||||
missing = ('Sorry, a password is required to run sudo', 'sudo: a password is required')
|
||||
|
||||
def build_become_command(self, cmd, shell):
|
||||
super(BecomeModule, self).build_become_command(cmd, shell)
|
||||
|
||||
if not cmd:
|
||||
return cmd
|
||||
|
||||
becomecmd = 'sudo'
|
||||
|
||||
flags = self.get_option('become_flags') or ''
|
||||
prompt = ''
|
||||
if self.get_option('become_pass'):
|
||||
self.prompt = '[sudo via ansible, key=%s] password:' % self._id
|
||||
if flags: # this could be simplified, but kept as is for now for backwards string matching
|
||||
flags = flags.replace('-n', '')
|
||||
prompt = '-p "%s"' % (self.prompt)
|
||||
|
||||
user = self.get_option('become_user') or ''
|
||||
if user:
|
||||
user = '%s' % (user)
|
||||
|
||||
return ' '.join([becomecmd, flags, prompt, 'su -l', user, self._build_success_command(cmd, shell)])
|
||||
18
plugins/cache/memcached.py
vendored
18
plugins/cache/memcached.py
vendored
@@ -6,8 +6,7 @@ from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
author: Unknown (!UNKNOWN)
|
||||
name: memcached
|
||||
cache: memcached
|
||||
short_description: Use memcached DB for cache
|
||||
description:
|
||||
- This cache uses JSON formatted, per host records saved in memcached.
|
||||
@@ -53,14 +52,12 @@ from ansible import constants as C
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.module_utils.common._collections_compat import MutableSet
|
||||
from ansible.plugins.cache import BaseCacheModule
|
||||
from ansible.release import __version__ as ansible_base_version
|
||||
from ansible.utils.display import Display
|
||||
|
||||
try:
|
||||
import memcache
|
||||
HAS_MEMCACHE = True
|
||||
except ImportError:
|
||||
HAS_MEMCACHE = False
|
||||
raise AnsibleError("python-memcached is required for the memcached fact cache")
|
||||
|
||||
display = Display()
|
||||
|
||||
@@ -162,7 +159,7 @@ class CacheModuleKeys(MutableSet):
|
||||
self._cache.set(self.PREFIX, self._keyset)
|
||||
|
||||
def remove_by_timerange(self, s_min, s_max):
|
||||
for k in list(self._keyset.keys()):
|
||||
for k in self._keyset.keys():
|
||||
t = self._keyset[k]
|
||||
if s_min < t < s_max:
|
||||
del self._keyset[k]
|
||||
@@ -181,17 +178,14 @@ class CacheModule(BaseCacheModule):
|
||||
self._timeout = self.get_option('_timeout')
|
||||
self._prefix = self.get_option('_prefix')
|
||||
except KeyError:
|
||||
# TODO: remove once we no longer support Ansible 2.9
|
||||
if not ansible_base_version.startswith('2.9.'):
|
||||
raise AnsibleError("Do not import CacheModules directly. Use ansible.plugins.loader.cache_loader instead.")
|
||||
display.deprecated('Rather than importing CacheModules directly, '
|
||||
'use ansible.plugins.loader.cache_loader',
|
||||
version='2.0.0', collection_name='community.general') # was Ansible 2.12
|
||||
if C.CACHE_PLUGIN_CONNECTION:
|
||||
connection = C.CACHE_PLUGIN_CONNECTION.split(',')
|
||||
self._timeout = C.CACHE_PLUGIN_TIMEOUT
|
||||
self._prefix = C.CACHE_PLUGIN_PREFIX
|
||||
|
||||
if not HAS_MEMCACHE:
|
||||
raise AnsibleError("python-memcached is required for the memcached fact cache")
|
||||
|
||||
self._cache = {}
|
||||
self._db = ProxyClientPool(connection, debug=0)
|
||||
self._keys = CacheModuleKeys(self._db, self._db.get(CacheModuleKeys.PREFIX) or [])
|
||||
|
||||
2
plugins/cache/pickle.py
vendored
2
plugins/cache/pickle.py
vendored
@@ -7,7 +7,7 @@ from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: pickle
|
||||
cache: pickle
|
||||
short_description: Pickle formatted files.
|
||||
description:
|
||||
- This cache uses Python's pickle serialization format, in per host files, saved to the filesystem.
|
||||
|
||||
101
plugins/cache/redis.py
vendored
101
plugins/cache/redis.py
vendored
@@ -5,8 +5,7 @@ from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
author: Unknown (!UNKNOWN)
|
||||
name: redis
|
||||
cache: redis
|
||||
short_description: Use Redis DB for cache
|
||||
description:
|
||||
- This cache uses JSON formatted, per host records saved in Redis.
|
||||
@@ -18,7 +17,6 @@ DOCUMENTATION = '''
|
||||
- A colon separated string of connection information for Redis.
|
||||
- The format is C(host:port:db:password), for example C(localhost:6379:0:changeme).
|
||||
- To use encryption in transit, prefix the connection with C(tls://), as in C(tls://localhost:6379:0:changeme).
|
||||
- To use redis sentinel, use separator C(;), for example C(localhost:26379;localhost:26379;0:changeme). Requires redis>=2.9.0.
|
||||
required: True
|
||||
env:
|
||||
- name: ANSIBLE_CACHE_PLUGIN_CONNECTION
|
||||
@@ -33,23 +31,6 @@ DOCUMENTATION = '''
|
||||
ini:
|
||||
- key: fact_caching_prefix
|
||||
section: defaults
|
||||
_keyset_name:
|
||||
description: User defined name for cache keyset name.
|
||||
default: ansible_cache_keys
|
||||
env:
|
||||
- name: ANSIBLE_CACHE_REDIS_KEYSET_NAME
|
||||
ini:
|
||||
- key: fact_caching_redis_keyset_name
|
||||
section: defaults
|
||||
version_added: 1.3.0
|
||||
_sentinel_service_name:
|
||||
description: The redis sentinel service name (or referenced as cluster name).
|
||||
env:
|
||||
- name: ANSIBLE_CACHE_REDIS_SENTINEL
|
||||
ini:
|
||||
- key: fact_caching_redis_sentinel
|
||||
section: defaults
|
||||
version_added: 1.3.0
|
||||
_timeout:
|
||||
default: 86400
|
||||
description: Expiration timeout in seconds for the cache plugin data. Set to 0 to never expire
|
||||
@@ -61,23 +42,19 @@ DOCUMENTATION = '''
|
||||
type: integer
|
||||
'''
|
||||
|
||||
import re
|
||||
import time
|
||||
import json
|
||||
|
||||
from ansible import constants as C
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.module_utils.common.text.converters import to_native
|
||||
from ansible.parsing.ajson import AnsibleJSONEncoder, AnsibleJSONDecoder
|
||||
from ansible.plugins.cache import BaseCacheModule
|
||||
from ansible.release import __version__ as ansible_base_version
|
||||
from ansible.utils.display import Display
|
||||
|
||||
try:
|
||||
from redis import StrictRedis, VERSION
|
||||
HAS_REDIS = True
|
||||
except ImportError:
|
||||
HAS_REDIS = False
|
||||
raise AnsibleError("The 'redis' python module (version 2.4.5 or newer) is required for the redis fact cache, 'pip install redis'")
|
||||
|
||||
display = Display()
|
||||
|
||||
@@ -91,10 +68,6 @@ class CacheModule(BaseCacheModule):
|
||||
to expire keys. This mechanism is used or a pattern matched 'scan' for
|
||||
performance.
|
||||
"""
|
||||
_sentinel_service_name = None
|
||||
re_url_conn = re.compile(r'^([^:]+|\[[^]]+\]):(\d+):(\d+)(?::(.*))?$')
|
||||
re_sent_conn = re.compile(r'^(.*):(\d+)$')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
uri = ''
|
||||
|
||||
@@ -104,77 +77,25 @@ class CacheModule(BaseCacheModule):
|
||||
uri = self.get_option('_uri')
|
||||
self._timeout = float(self.get_option('_timeout'))
|
||||
self._prefix = self.get_option('_prefix')
|
||||
self._keys_set = self.get_option('_keyset_name')
|
||||
self._sentinel_service_name = self.get_option('_sentinel_service_name')
|
||||
except KeyError:
|
||||
# TODO: remove once we no longer support Ansible 2.9
|
||||
if not ansible_base_version.startswith('2.9.'):
|
||||
raise AnsibleError("Do not import CacheModules directly. Use ansible.plugins.loader.cache_loader instead.")
|
||||
display.deprecated('Rather than importing CacheModules directly, '
|
||||
'use ansible.plugins.loader.cache_loader',
|
||||
version='2.0.0', collection_name='community.general') # was Ansible 2.12
|
||||
if C.CACHE_PLUGIN_CONNECTION:
|
||||
uri = C.CACHE_PLUGIN_CONNECTION
|
||||
self._timeout = float(C.CACHE_PLUGIN_TIMEOUT)
|
||||
self._prefix = C.CACHE_PLUGIN_PREFIX
|
||||
self._keys_set = 'ansible_cache_keys'
|
||||
|
||||
if not HAS_REDIS:
|
||||
raise AnsibleError("The 'redis' python module (version 2.4.5 or newer) is required for the redis fact cache, 'pip install redis'")
|
||||
|
||||
self._cache = {}
|
||||
kw = {}
|
||||
|
||||
# tls connection
|
||||
tlsprefix = 'tls://'
|
||||
if uri.startswith(tlsprefix):
|
||||
kw['ssl'] = True
|
||||
uri = uri[len(tlsprefix):]
|
||||
|
||||
# redis sentinel connection
|
||||
if self._sentinel_service_name:
|
||||
self._db = self._get_sentinel_connection(uri, kw)
|
||||
# normal connection
|
||||
else:
|
||||
connection = self._parse_connection(self.re_url_conn, uri)
|
||||
self._db = StrictRedis(*connection, **kw)
|
||||
|
||||
display.vv('Redis connection: %s' % self._db)
|
||||
|
||||
@staticmethod
|
||||
def _parse_connection(re_patt, uri):
|
||||
match = re_patt.match(uri)
|
||||
if not match:
|
||||
raise AnsibleError("Unable to parse connection string")
|
||||
return match.groups()
|
||||
|
||||
def _get_sentinel_connection(self, uri, kw):
|
||||
"""
|
||||
get sentinel connection details from _uri
|
||||
"""
|
||||
try:
|
||||
from redis.sentinel import Sentinel
|
||||
except ImportError:
|
||||
raise AnsibleError("The 'redis' python module (version 2.9.0 or newer) is required to use redis sentinel.")
|
||||
|
||||
if ';' not in uri:
|
||||
raise AnsibleError('_uri does not have sentinel syntax.')
|
||||
|
||||
# format: "localhost:26379;localhost2:26379;0:changeme"
|
||||
connections = uri.split(';')
|
||||
connection_args = connections.pop(-1)
|
||||
if len(connection_args) > 0: # hanle if no db nr is given
|
||||
connection_args = connection_args.split(':')
|
||||
kw['db'] = connection_args.pop(0)
|
||||
try:
|
||||
kw['password'] = connection_args.pop(0)
|
||||
except IndexError:
|
||||
pass # password is optional
|
||||
|
||||
sentinels = [self._parse_connection(self.re_sent_conn, shost) for shost in connections]
|
||||
display.vv('\nUsing redis sentinels: %s' % sentinels)
|
||||
scon = Sentinel(sentinels, **kw)
|
||||
try:
|
||||
return scon.master_for(self._sentinel_service_name, socket_timeout=0.2)
|
||||
except Exception as exc:
|
||||
raise AnsibleError('Could not connect to redis sentinel: %s' % to_native(exc))
|
||||
connection = uri.split(':')
|
||||
self._db = StrictRedis(*connection, **kw)
|
||||
self._keys_set = 'ansible_cache_keys'
|
||||
|
||||
def _make_key(self, key):
|
||||
return self._prefix + key
|
||||
@@ -227,12 +148,14 @@ class CacheModule(BaseCacheModule):
|
||||
self._db.zrem(self._keys_set, key)
|
||||
|
||||
def flush(self):
|
||||
for key in list(self.keys()):
|
||||
for key in self.keys():
|
||||
self.delete(key)
|
||||
|
||||
def copy(self):
|
||||
# TODO: there is probably a better way to do this in redis
|
||||
ret = dict([(k, self.get(k)) for k in self.keys()])
|
||||
ret = dict()
|
||||
for key in self.keys():
|
||||
ret[key] = self.get(key)
|
||||
return ret
|
||||
|
||||
def __getstate__(self):
|
||||
|
||||
2
plugins/cache/yaml.py
vendored
2
plugins/cache/yaml.py
vendored
@@ -7,7 +7,7 @@ from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: yaml
|
||||
cache: yaml
|
||||
short_description: YAML formatted files.
|
||||
description:
|
||||
- This cache uses YAML formatted, per host, files saved to the filesystem.
|
||||
|
||||
60
plugins/callback/actionable.py
Normal file
60
plugins/callback/actionable.py
Normal file
@@ -0,0 +1,60 @@
|
||||
# (c) 2015, Andrew Gaffney <andrew@agaffney.org>
|
||||
# (c) 2017 Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
callback: actionable
|
||||
type: stdout
|
||||
short_description: shows only items that need attention
|
||||
description:
|
||||
- Use this callback when you dont care about OK nor Skipped.
|
||||
- This callback suppresses any non Failed or Changed status.
|
||||
deprecated:
|
||||
why: The 'default' callback plugin now supports this functionality
|
||||
removed_in: '2.0.0' # was Ansible 2.11
|
||||
alternative: "'default' callback plugin with 'display_skipped_hosts = no' and 'display_ok_hosts = no' options"
|
||||
extends_documentation_fragment:
|
||||
- default_callback
|
||||
requirements:
|
||||
- set as stdout callback in configuration
|
||||
# Override defaults from 'default' callback plugin
|
||||
options:
|
||||
display_skipped_hosts:
|
||||
name: Show skipped hosts
|
||||
description: "Toggle to control displaying skipped task/host results in a task"
|
||||
type: bool
|
||||
default: no
|
||||
env:
|
||||
- name: DISPLAY_SKIPPED_HOSTS
|
||||
deprecated:
|
||||
why: environment variables without "ANSIBLE_" prefix are deprecated
|
||||
version: "2.0.0" # was Ansible 2.12
|
||||
alternatives: the "ANSIBLE_DISPLAY_SKIPPED_HOSTS" environment variable
|
||||
- name: ANSIBLE_DISPLAY_SKIPPED_HOSTS
|
||||
ini:
|
||||
- key: display_skipped_hosts
|
||||
section: defaults
|
||||
display_ok_hosts:
|
||||
name: Show 'ok' hosts
|
||||
description: "Toggle to control displaying 'ok' task/host results in a task"
|
||||
type: bool
|
||||
default: no
|
||||
env:
|
||||
- name: ANSIBLE_DISPLAY_OK_HOSTS
|
||||
ini:
|
||||
- key: display_ok_hosts
|
||||
section: defaults
|
||||
'''
|
||||
|
||||
from ansible.plugins.callback.default import CallbackModule as CallbackModule_default
|
||||
|
||||
|
||||
class CallbackModule(CallbackModule_default):
|
||||
|
||||
CALLBACK_VERSION = 2.0
|
||||
CALLBACK_TYPE = 'stdout'
|
||||
CALLBACK_NAME = 'community.general.actionable'
|
||||
@@ -7,9 +7,8 @@ from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
author: Unknown (!UNKNOWN)
|
||||
name: cgroup_memory_recap
|
||||
type: aggregate
|
||||
callback: cgroup_memory_recap
|
||||
callback_type: aggregate
|
||||
requirements:
|
||||
- whitelist in configuration
|
||||
- cgroups
|
||||
|
||||
@@ -6,8 +6,7 @@ from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
author: Unknown (!UNKNOWN)
|
||||
name: context_demo
|
||||
callback: context_demo
|
||||
type: aggregate
|
||||
short_description: demo callback that adds play/task context
|
||||
description:
|
||||
|
||||
@@ -6,10 +6,14 @@
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from ansible import constants as C
|
||||
from ansible.plugins.callback import CallbackBase
|
||||
from ansible.utils.color import colorize, hostcolor
|
||||
from ansible.template import Templar
|
||||
from ansible.playbook.task_include import TaskInclude
|
||||
|
||||
DOCUMENTATION = '''
|
||||
author: Unknown (!UNKNOWN)
|
||||
name: counter_enabled
|
||||
callback: counter_enabled
|
||||
type: stdout
|
||||
short_description: adds counters to the output items (tasks and hosts/task)
|
||||
description:
|
||||
@@ -22,12 +26,6 @@ DOCUMENTATION = '''
|
||||
- set as stdout callback in ansible.cfg (stdout_callback = counter_enabled)
|
||||
'''
|
||||
|
||||
from ansible import constants as C
|
||||
from ansible.plugins.callback import CallbackBase
|
||||
from ansible.utils.color import colorize, hostcolor
|
||||
from ansible.template import Templar
|
||||
from ansible.playbook.task_include import TaskInclude
|
||||
|
||||
|
||||
class CallbackModule(CallbackBase):
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: dense
|
||||
callback: dense
|
||||
type: stdout
|
||||
short_description: minimal stdout output
|
||||
extends_documentation_fragment:
|
||||
|
||||
@@ -7,8 +7,8 @@ from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
name: diy
|
||||
type: stdout
|
||||
callback: diy
|
||||
callback_type: stdout
|
||||
short_description: Customize the output
|
||||
version_added: 0.2.0
|
||||
description:
|
||||
@@ -629,7 +629,7 @@ playbook.yml: >
|
||||
gather_facts: no
|
||||
tasks:
|
||||
- name: Default plugin output
|
||||
ansible.builtin.debug:
|
||||
debug:
|
||||
msg: default plugin output
|
||||
|
||||
- name: Override from play vars
|
||||
@@ -687,11 +687,11 @@ playbook.yml: >
|
||||
|
||||
tasks:
|
||||
- name: Custom banner with default plugin result output
|
||||
ansible.builtin.debug:
|
||||
debug:
|
||||
msg: "default plugin output: result example"
|
||||
|
||||
- name: Override from task vars
|
||||
ansible.builtin.debug:
|
||||
debug:
|
||||
msg: "example {{ two }}"
|
||||
changed_when: true
|
||||
vars:
|
||||
@@ -703,14 +703,14 @@ playbook.yml: >
|
||||
ansible_callback_diy_runner_on_ok_msg_color: "{{ 'yellow' if ansible_callback_diy.result.is_changed else 'bright green' }}"
|
||||
|
||||
- name: Suppress output
|
||||
ansible.builtin.debug:
|
||||
debug:
|
||||
msg: i should not be displayed
|
||||
vars:
|
||||
ansible_callback_diy_playbook_on_task_start_msg: ""
|
||||
ansible_callback_diy_runner_on_ok_msg: ""
|
||||
|
||||
- name: Using alias vars (see ansible.cfg)
|
||||
ansible.builtin.debug:
|
||||
debug:
|
||||
msg:
|
||||
when: False
|
||||
vars:
|
||||
@@ -719,13 +719,13 @@ playbook.yml: >
|
||||
on_skipped_msg_color: white
|
||||
|
||||
- name: Just stdout
|
||||
ansible.builtin.command: echo some stdout
|
||||
command: echo some stdout
|
||||
vars:
|
||||
ansible_callback_diy_playbook_on_task_start_msg: "\n"
|
||||
ansible_callback_diy_runner_on_ok_msg: "{{ ansible_callback_diy.result.output.stdout }}\n"
|
||||
|
||||
- name: Multiline output
|
||||
ansible.builtin.debug:
|
||||
debug:
|
||||
msg: "{{ multiline }}"
|
||||
vars:
|
||||
ansible_callback_diy_playbook_on_task_start_msg: "\nDIY output(via task vars): task example: {{ ansible_callback_diy.task.name }}"
|
||||
@@ -738,7 +738,7 @@ playbook.yml: >
|
||||
ansible_callback_diy_playbook_on_task_start_msg_color: bright blue
|
||||
|
||||
- name: Indentation
|
||||
ansible.builtin.debug:
|
||||
debug:
|
||||
msg: "{{ item.msg }}"
|
||||
with_items:
|
||||
- { indent: 1, msg: one., color: red }
|
||||
@@ -751,14 +751,14 @@ playbook.yml: >
|
||||
ansible_callback_diy_runner_on_ok_msg_color: bright green
|
||||
|
||||
- name: Using lookup and template as file
|
||||
ansible.builtin.shell: "echo {% raw %}'output from {{ file_name }}'{% endraw %} > {{ file_name }}"
|
||||
shell: "echo {% raw %}'output from {{ file_name }}'{% endraw %} > {{ file_name }}"
|
||||
vars:
|
||||
ansible_callback_diy_playbook_on_task_start_msg: "\nDIY output(via task vars): task example: {{ ansible_callback_diy.task.name }}"
|
||||
file_name: diy_file_template_example
|
||||
ansible_callback_diy_runner_on_ok_msg: "{{ lookup('template', file_name) }}"
|
||||
|
||||
- name: 'Look at top level vars available to the "runner_on_ok" callback'
|
||||
ansible.builtin.debug:
|
||||
debug:
|
||||
msg: ''
|
||||
vars:
|
||||
ansible_callback_diy_playbook_on_task_start_msg: "\nDIY output(via task vars): task example: {{ ansible_callback_diy.task.name }}"
|
||||
@@ -771,7 +771,7 @@ playbook.yml: >
|
||||
ansible_callback_diy_runner_on_ok_msg_color: white
|
||||
|
||||
- name: 'Look at event data available to the "runner_on_ok" callback'
|
||||
ansible.builtin.debug:
|
||||
debug:
|
||||
msg: ''
|
||||
vars:
|
||||
ansible_callback_diy_playbook_on_task_start_msg: "\nDIY output(via task vars): task example: {{ ansible_callback_diy.task.name }}"
|
||||
@@ -792,7 +792,7 @@ from ansible.utils.color import colorize, hostcolor
|
||||
from ansible.template import Templar
|
||||
from ansible.vars.manager import VariableManager
|
||||
from ansible.plugins.callback.default import CallbackModule as Default
|
||||
from ansible.module_utils.common.text.converters import to_text
|
||||
from ansible.module_utils._text import to_text
|
||||
|
||||
|
||||
class DummyStdout(object):
|
||||
@@ -1013,7 +1013,7 @@ class CallbackModule(Default):
|
||||
for attr in _stats_attributes:
|
||||
_ret[self.DIY_NS]['stats'].update({attr: _get_value(obj=stats, attr=attr)})
|
||||
|
||||
_ret[self.DIY_NS].update({'top_level_var_names': list(_ret.keys())})
|
||||
_ret[self.DIY_NS].update({'top_level_var_names': _ret.keys()})
|
||||
|
||||
return _ret
|
||||
|
||||
|
||||
75
plugins/callback/full_skip.py
Normal file
75
plugins/callback/full_skip.py
Normal file
@@ -0,0 +1,75 @@
|
||||
# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
|
||||
# (c) 2017 Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
callback: full_skip
|
||||
type: stdout
|
||||
short_description: suppresses tasks if all hosts skipped
|
||||
description:
|
||||
- Use this plugin when you do not care about any output for tasks that were completely skipped
|
||||
deprecated:
|
||||
why: The 'default' callback plugin now supports this functionality
|
||||
removed_in: '2.0.0' # was Ansible 2.11
|
||||
alternative: "'default' callback plugin with 'display_skipped_hosts = no' option"
|
||||
extends_documentation_fragment:
|
||||
- default_callback
|
||||
requirements:
|
||||
- set as stdout in configuration
|
||||
'''
|
||||
|
||||
from ansible.plugins.callback.default import CallbackModule as CallbackModule_default
|
||||
|
||||
|
||||
class CallbackModule(CallbackModule_default):
|
||||
|
||||
'''
|
||||
This is the default callback interface, which simply prints messages
|
||||
to stdout when new callback events are received.
|
||||
'''
|
||||
|
||||
CALLBACK_VERSION = 2.0
|
||||
CALLBACK_TYPE = 'stdout'
|
||||
CALLBACK_NAME = 'community.general.full_skip'
|
||||
|
||||
def v2_runner_on_skipped(self, result):
|
||||
self.outlines = []
|
||||
|
||||
def v2_playbook_item_on_skipped(self, result):
|
||||
self.outlines = []
|
||||
|
||||
def v2_runner_item_on_skipped(self, result):
|
||||
self.outlines = []
|
||||
|
||||
def v2_runner_on_failed(self, result, ignore_errors=False):
|
||||
self.display()
|
||||
super(CallbackModule, self).v2_runner_on_failed(result, ignore_errors)
|
||||
|
||||
def v2_playbook_on_task_start(self, task, is_conditional):
|
||||
self.outlines = []
|
||||
self.outlines.append("TASK [%s]" % task.get_name().strip())
|
||||
if self._display.verbosity >= 2:
|
||||
path = task.get_path()
|
||||
if path:
|
||||
self.outlines.append("task path: %s" % path)
|
||||
|
||||
def v2_playbook_item_on_ok(self, result):
|
||||
self.display()
|
||||
super(CallbackModule, self).v2_playbook_item_on_ok(result)
|
||||
|
||||
def v2_runner_on_ok(self, result):
|
||||
self.display()
|
||||
super(CallbackModule, self).v2_runner_on_ok(result)
|
||||
|
||||
def display(self):
|
||||
if len(self.outlines) == 0:
|
||||
return
|
||||
(first, rest) = self.outlines[0], self.outlines[1:]
|
||||
self._display.banner(first)
|
||||
for line in rest:
|
||||
self._display.display(line)
|
||||
self.outlines = []
|
||||
@@ -6,9 +6,8 @@ from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
author: Unknown (!UNKNOWN)
|
||||
name: hipchat
|
||||
type: notification
|
||||
callback: hipchat
|
||||
callback_type: notification
|
||||
requirements:
|
||||
- whitelist in configuration.
|
||||
- prettytable (python lib)
|
||||
@@ -173,7 +172,8 @@ class CallbackModule(CallbackBase):
|
||||
# Displays info about playbook being started by a person on an
|
||||
# inventory, as well as Tags, Skip Tags and Limits
|
||||
if not self.printed_playbook:
|
||||
self.playbook_name, dummy = os.path.splitext(os.path.basename(self.play.playbook.filename))
|
||||
self.playbook_name, _ = os.path.splitext(
|
||||
os.path.basename(self.play.playbook.filename))
|
||||
host_list = self.play.playbook.inventory.host_list
|
||||
inventory = os.path.basename(os.path.realpath(host_list))
|
||||
self.send_msg("%s: Playbook initiated by %s against %s" %
|
||||
|
||||
@@ -6,8 +6,7 @@ from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
author: Unknown (!UNKNOWN)
|
||||
name: jabber
|
||||
callback: jabber
|
||||
type: notification
|
||||
short_description: post task events to a jabber server
|
||||
description:
|
||||
|
||||
@@ -6,8 +6,7 @@ from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
author: Unknown (!UNKNOWN)
|
||||
name: log_plays
|
||||
callback: log_plays
|
||||
type: notification
|
||||
short_description: write playbook output to log file
|
||||
description:
|
||||
@@ -31,7 +30,7 @@ import time
|
||||
import json
|
||||
|
||||
from ansible.utils.path import makedirs_safe
|
||||
from ansible.module_utils.common.text.converters import to_bytes
|
||||
from ansible.module_utils._text import to_bytes
|
||||
from ansible.module_utils.common._collections_compat import MutableMapping
|
||||
from ansible.parsing.ajson import AnsibleJSONEncoder
|
||||
from ansible.plugins.callback import CallbackBase
|
||||
|
||||
@@ -1,234 +0,0 @@
|
||||
# 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: loganalytics
|
||||
type: aggregate
|
||||
short_description: Posts task results to Azure Log Analytics
|
||||
author: "Cyrus Li (@zhcli) <cyrus1006@gmail.com>"
|
||||
description:
|
||||
- This callback plugin will post task results in JSON formatted to an Azure Log Analytics workspace.
|
||||
- Credits to authors of splunk callback plugin.
|
||||
version_added: "2.4.0"
|
||||
requirements:
|
||||
- Whitelisting this callback plugin.
|
||||
- An Azure log analytics work space has been established.
|
||||
options:
|
||||
workspace_id:
|
||||
description: Workspace ID of the Azure log analytics workspace.
|
||||
required: true
|
||||
env:
|
||||
- name: WORKSPACE_ID
|
||||
ini:
|
||||
- section: callback_loganalytics
|
||||
key: workspace_id
|
||||
shared_key:
|
||||
description: Shared key to connect to Azure log analytics workspace.
|
||||
required: true
|
||||
env:
|
||||
- name: WORKSPACE_SHARED_KEY
|
||||
ini:
|
||||
- section: callback_loganalytics
|
||||
key: shared_key
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
examples: |
|
||||
Whitelist the plugin in ansible.cfg:
|
||||
[defaults]
|
||||
callback_whitelist = community.general.loganalytics
|
||||
Set the environment variable:
|
||||
export WORKSPACE_ID=01234567-0123-0123-0123-01234567890a
|
||||
export WORKSPACE_SHARED_KEY=dZD0kCbKl3ehZG6LHFMuhtE0yHiFCmetzFMc2u+roXIUQuatqU924SsAAAAPemhjbGlAemhjbGktTUJQAQIDBA==
|
||||
Or configure the plugin in ansible.cfg in the callback_loganalytics block:
|
||||
[callback_loganalytics]
|
||||
workspace_id = 01234567-0123-0123-0123-01234567890a
|
||||
shared_key = dZD0kCbKl3ehZG6LHFMuhtE0yHiFCmetzFMc2u+roXIUQuatqU924SsAAAAPemhjbGlAemhjbGktTUJQAQIDBA==
|
||||
'''
|
||||
|
||||
import hashlib
|
||||
import hmac
|
||||
import base64
|
||||
import logging
|
||||
import json
|
||||
import uuid
|
||||
import socket
|
||||
import getpass
|
||||
|
||||
from datetime import datetime
|
||||
from os.path import basename
|
||||
|
||||
from ansible.module_utils.urls import open_url
|
||||
from ansible.parsing.ajson import AnsibleJSONEncoder
|
||||
from ansible.plugins.callback import CallbackBase
|
||||
|
||||
|
||||
class AzureLogAnalyticsSource(object):
|
||||
def __init__(self):
|
||||
self.ansible_check_mode = False
|
||||
self.ansible_playbook = ""
|
||||
self.ansible_version = ""
|
||||
self.session = str(uuid.uuid4())
|
||||
self.host = socket.gethostname()
|
||||
self.user = getpass.getuser()
|
||||
self.extra_vars = ""
|
||||
|
||||
def __build_signature(self, date, workspace_id, shared_key, content_length):
|
||||
# Build authorisation signature for Azure log analytics API call
|
||||
sigs = "POST\n{0}\napplication/json\nx-ms-date:{1}\n/api/logs".format(
|
||||
str(content_length), date)
|
||||
utf8_sigs = sigs.encode('utf-8')
|
||||
decoded_shared_key = base64.b64decode(shared_key)
|
||||
hmac_sha256_sigs = hmac.new(
|
||||
decoded_shared_key, utf8_sigs, digestmod=hashlib.sha256).digest()
|
||||
encoded_hash = base64.b64encode(hmac_sha256_sigs).decode('utf-8')
|
||||
signature = "SharedKey {0}:{1}".format(workspace_id, encoded_hash)
|
||||
return signature
|
||||
|
||||
def __build_workspace_url(self, workspace_id):
|
||||
return "https://{0}.ods.opinsights.azure.com/api/logs?api-version=2016-04-01".format(workspace_id)
|
||||
|
||||
def __rfc1123date(self):
|
||||
return datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT')
|
||||
|
||||
def send_event(self, workspace_id, shared_key, state, result, runtime):
|
||||
if result._task_fields['args'].get('_ansible_check_mode') is True:
|
||||
self.ansible_check_mode = True
|
||||
|
||||
if result._task_fields['args'].get('_ansible_version'):
|
||||
self.ansible_version = \
|
||||
result._task_fields['args'].get('_ansible_version')
|
||||
|
||||
if result._task._role:
|
||||
ansible_role = str(result._task._role)
|
||||
else:
|
||||
ansible_role = None
|
||||
|
||||
data = {}
|
||||
data['uuid'] = result._task._uuid
|
||||
data['session'] = self.session
|
||||
data['status'] = state
|
||||
data['timestamp'] = self.__rfc1123date()
|
||||
data['host'] = self.host
|
||||
data['user'] = self.user
|
||||
data['runtime'] = runtime
|
||||
data['ansible_version'] = self.ansible_version
|
||||
data['ansible_check_mode'] = self.ansible_check_mode
|
||||
data['ansible_host'] = result._host.name
|
||||
data['ansible_playbook'] = self.ansible_playbook
|
||||
data['ansible_role'] = ansible_role
|
||||
data['ansible_task'] = result._task_fields
|
||||
# Removing args since it can contain sensitive data
|
||||
if 'args' in data['ansible_task']:
|
||||
data['ansible_task'].pop('args')
|
||||
data['ansible_result'] = result._result
|
||||
if 'content' in data['ansible_result']:
|
||||
data['ansible_result'].pop('content')
|
||||
|
||||
# Adding extra vars info
|
||||
data['extra_vars'] = self.extra_vars
|
||||
|
||||
# Preparing the playbook logs as JSON format and send to Azure log analytics
|
||||
jsondata = json.dumps({'event': data}, cls=AnsibleJSONEncoder, sort_keys=True)
|
||||
content_length = len(jsondata)
|
||||
rfc1123date = self.__rfc1123date()
|
||||
signature = self.__build_signature(rfc1123date, workspace_id, shared_key, content_length)
|
||||
workspace_url = self.__build_workspace_url(workspace_id)
|
||||
|
||||
open_url(
|
||||
workspace_url,
|
||||
jsondata,
|
||||
headers={
|
||||
'content-type': 'application/json',
|
||||
'Authorization': signature,
|
||||
'Log-Type': 'ansible_playbook',
|
||||
'x-ms-date': rfc1123date
|
||||
},
|
||||
method='POST'
|
||||
)
|
||||
|
||||
|
||||
class CallbackModule(CallbackBase):
|
||||
CALLBACK_VERSION = 2.0
|
||||
CALLBACK_TYPE = 'aggregate'
|
||||
CALLBACK_NAME = 'loganalytics'
|
||||
CALLBACK_NEEDS_WHITELIST = True
|
||||
|
||||
def __init__(self, display=None):
|
||||
super(CallbackModule, self).__init__(display=display)
|
||||
self.start_datetimes = {} # Collect task start times
|
||||
self.workspace_id = None
|
||||
self.shared_key = None
|
||||
self.loganalytics = AzureLogAnalyticsSource()
|
||||
|
||||
def _seconds_since_start(self, result):
|
||||
return (
|
||||
datetime.utcnow() -
|
||||
self.start_datetimes[result._task._uuid]
|
||||
).total_seconds()
|
||||
|
||||
def set_options(self, task_keys=None, var_options=None, direct=None):
|
||||
super(CallbackModule, self).set_options(task_keys=task_keys, var_options=var_options, direct=direct)
|
||||
self.workspace_id = self.get_option('workspace_id')
|
||||
self.shared_key = self.get_option('shared_key')
|
||||
|
||||
def v2_playbook_on_play_start(self, play):
|
||||
vm = play.get_variable_manager()
|
||||
extra_vars = vm.extra_vars
|
||||
self.loganalytics.extra_vars = extra_vars
|
||||
|
||||
def v2_playbook_on_start(self, playbook):
|
||||
self.loganalytics.ansible_playbook = basename(playbook._file_name)
|
||||
|
||||
def v2_playbook_on_task_start(self, task, is_conditional):
|
||||
self.start_datetimes[task._uuid] = datetime.utcnow()
|
||||
|
||||
def v2_playbook_on_handler_task_start(self, task):
|
||||
self.start_datetimes[task._uuid] = datetime.utcnow()
|
||||
|
||||
def v2_runner_on_ok(self, result, **kwargs):
|
||||
self.loganalytics.send_event(
|
||||
self.workspace_id,
|
||||
self.shared_key,
|
||||
'OK',
|
||||
result,
|
||||
self._seconds_since_start(result)
|
||||
)
|
||||
|
||||
def v2_runner_on_skipped(self, result, **kwargs):
|
||||
self.loganalytics.send_event(
|
||||
self.workspace_id,
|
||||
self.shared_key,
|
||||
'SKIPPED',
|
||||
result,
|
||||
self._seconds_since_start(result)
|
||||
)
|
||||
|
||||
def v2_runner_on_failed(self, result, **kwargs):
|
||||
self.loganalytics.send_event(
|
||||
self.workspace_id,
|
||||
self.shared_key,
|
||||
'FAILED',
|
||||
result,
|
||||
self._seconds_since_start(result)
|
||||
)
|
||||
|
||||
def runner_on_async_failed(self, result, **kwargs):
|
||||
self.loganalytics.send_event(
|
||||
self.workspace_id,
|
||||
self.shared_key,
|
||||
'FAILED',
|
||||
result,
|
||||
self._seconds_since_start(result)
|
||||
)
|
||||
|
||||
def v2_runner_on_unreachable(self, result, **kwargs):
|
||||
self.loganalytics.send_event(
|
||||
self.workspace_id,
|
||||
self.shared_key,
|
||||
'UNREACHABLE',
|
||||
result,
|
||||
self._seconds_since_start(result)
|
||||
)
|
||||
@@ -5,9 +5,8 @@ from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
author: Unknown (!UNKNOWN)
|
||||
name: logdna
|
||||
type: aggregate
|
||||
callback: logdna
|
||||
callback_type: aggregate
|
||||
short_description: Sends playbook logs to LogDNA
|
||||
description:
|
||||
- This callback will report logs from playbook actions, tasks, and events to LogDNA (https://app.logdna.com)
|
||||
|
||||
@@ -5,8 +5,7 @@ from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
author: Unknown (!UNKNOWN)
|
||||
name: logentries
|
||||
callback: logentries
|
||||
type: notification
|
||||
short_description: Sends events to Logentries
|
||||
description:
|
||||
@@ -76,7 +75,7 @@ examples: >
|
||||
To enable, add this to your ansible.cfg file in the defaults block
|
||||
|
||||
[defaults]
|
||||
callback_whitelist = community.general.logentries
|
||||
callback_whitelist = logentries
|
||||
|
||||
Either set the environment variables
|
||||
export LOGENTRIES_API=data.logentries.com
|
||||
@@ -111,7 +110,7 @@ try:
|
||||
except ImportError:
|
||||
HAS_FLATDICT = False
|
||||
|
||||
from ansible.module_utils.common.text.converters import to_bytes, to_text
|
||||
from ansible.module_utils._text import to_bytes, to_text
|
||||
from ansible.plugins.callback import CallbackBase
|
||||
|
||||
# Todo:
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
# (C) 2020, Yevhen Khmelenko <ujenmr@gmail.com>
|
||||
# (C) 2016, Ievgen Khmelenko <ujenmr@gmail.com>
|
||||
# (C) 2017 Ansible Project
|
||||
# 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'''
|
||||
author: Yevhen Khmelenko (@ujenmr)
|
||||
name: logstash
|
||||
DOCUMENTATION = '''
|
||||
callback: logstash
|
||||
type: notification
|
||||
short_description: Sends events to Logstash
|
||||
description:
|
||||
@@ -20,84 +19,27 @@ DOCUMENTATION = r'''
|
||||
description: Address of the Logstash server
|
||||
env:
|
||||
- name: LOGSTASH_SERVER
|
||||
ini:
|
||||
- section: callback_logstash
|
||||
key: server
|
||||
version_added: 1.0.0
|
||||
default: localhost
|
||||
port:
|
||||
description: Port on which logstash is listening
|
||||
env:
|
||||
- name: LOGSTASH_PORT
|
||||
ini:
|
||||
- section: callback_logstash
|
||||
key: port
|
||||
version_added: 1.0.0
|
||||
default: 5000
|
||||
type:
|
||||
description: Message type
|
||||
env:
|
||||
- name: LOGSTASH_TYPE
|
||||
ini:
|
||||
- section: callback_logstash
|
||||
key: type
|
||||
version_added: 1.0.0
|
||||
default: ansible
|
||||
pre_command:
|
||||
description: Executes command before run and result put to ansible_pre_command_output field.
|
||||
version_added: 2.0.0
|
||||
ini:
|
||||
- section: callback_logstash
|
||||
key: pre_command
|
||||
env:
|
||||
- name: LOGSTASH_PRE_COMMAND
|
||||
format_version:
|
||||
description: Logging format
|
||||
type: str
|
||||
version_added: 2.0.0
|
||||
ini:
|
||||
- section: callback_logstash
|
||||
key: format_version
|
||||
env:
|
||||
- name: LOGSTASH_FORMAT_VERSION
|
||||
default: v1
|
||||
choices:
|
||||
- v1
|
||||
- v2
|
||||
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
ansible.cfg: |
|
||||
# Enable Callback plugin
|
||||
[defaults]
|
||||
callback_whitelist = community.general.logstash
|
||||
|
||||
[callback_logstash]
|
||||
server = logstash.example.com
|
||||
port = 5000
|
||||
pre_command = git rev-parse HEAD
|
||||
type = ansible
|
||||
|
||||
11-input-tcp.conf: |
|
||||
# Enable Logstash TCP Input
|
||||
input {
|
||||
tcp {
|
||||
port => 5000
|
||||
codec => json
|
||||
add_field => { "[@metadata][beat]" => "notify" }
|
||||
add_field => { "[@metadata][type]" => "ansible" }
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
import os
|
||||
import json
|
||||
import socket
|
||||
import uuid
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
import logging
|
||||
|
||||
try:
|
||||
import logstash
|
||||
HAS_LOGSTASH = True
|
||||
@@ -108,6 +50,29 @@ from ansible.plugins.callback import CallbackBase
|
||||
|
||||
|
||||
class CallbackModule(CallbackBase):
|
||||
"""
|
||||
ansible logstash callback plugin
|
||||
ansible.cfg:
|
||||
callback_plugins = <path_to_callback_plugins_folder>
|
||||
callback_whitelist = logstash
|
||||
and put the plugin in <path_to_callback_plugins_folder>
|
||||
|
||||
logstash config:
|
||||
input {
|
||||
tcp {
|
||||
port => 5000
|
||||
codec => json
|
||||
}
|
||||
}
|
||||
|
||||
Requires:
|
||||
python-logstash
|
||||
|
||||
This plugin makes use of the following environment variables:
|
||||
LOGSTASH_SERVER (optional): defaults to localhost
|
||||
LOGSTASH_PORT (optional): defaults to 5000
|
||||
LOGSTASH_TYPE (optional): defaults to ansible
|
||||
"""
|
||||
|
||||
CALLBACK_VERSION = 2.0
|
||||
CALLBACK_TYPE = 'aggregate'
|
||||
@@ -119,67 +84,35 @@ class CallbackModule(CallbackBase):
|
||||
|
||||
if not HAS_LOGSTASH:
|
||||
self.disabled = True
|
||||
self._display.warning("The required python-logstash/python3-logstash is not installed. "
|
||||
"pip install python-logstash for Python 2"
|
||||
"pip install python3-logstash for Python 3")
|
||||
|
||||
self.start_time = datetime.utcnow()
|
||||
|
||||
def _init_plugin(self):
|
||||
if not self.disabled:
|
||||
self._display.warning("The required python-logstash is not installed. "
|
||||
"pip install python-logstash")
|
||||
else:
|
||||
self.logger = logging.getLogger('python-logstash-logger')
|
||||
self.logger.setLevel(logging.DEBUG)
|
||||
|
||||
self.handler = logstash.TCPLogstashHandler(
|
||||
self.ls_server,
|
||||
self.ls_port,
|
||||
os.getenv('LOGSTASH_SERVER', 'localhost'),
|
||||
int(os.getenv('LOGSTASH_PORT', 5000)),
|
||||
version=1,
|
||||
message_type=self.ls_type
|
||||
message_type=os.getenv('LOGSTASH_TYPE', 'ansible')
|
||||
)
|
||||
|
||||
self.logger.addHandler(self.handler)
|
||||
self.hostname = socket.gethostname()
|
||||
self.session = str(uuid.uuid4())
|
||||
self.session = str(uuid.uuid1())
|
||||
self.errors = 0
|
||||
|
||||
self.base_data = {
|
||||
'session': self.session,
|
||||
'host': self.hostname
|
||||
}
|
||||
|
||||
if self.ls_pre_command is not None:
|
||||
self.base_data['ansible_pre_command_output'] = os.popen(
|
||||
self.ls_pre_command).read()
|
||||
|
||||
if self._options is not None:
|
||||
self.base_data['ansible_checkmode'] = self._options.check
|
||||
self.base_data['ansible_tags'] = self._options.tags
|
||||
self.base_data['ansible_skip_tags'] = self._options.skip_tags
|
||||
self.base_data['inventory'] = self._options.inventory
|
||||
|
||||
def set_options(self, task_keys=None, var_options=None, direct=None):
|
||||
super(CallbackModule, self).set_options(task_keys=task_keys, var_options=var_options, direct=direct)
|
||||
|
||||
self.ls_server = self.get_option('server')
|
||||
self.ls_port = int(self.get_option('port'))
|
||||
self.ls_type = self.get_option('type')
|
||||
self.ls_pre_command = self.get_option('pre_command')
|
||||
self.ls_format_version = self.get_option('format_version')
|
||||
|
||||
self._init_plugin()
|
||||
self.start_time = datetime.utcnow()
|
||||
|
||||
def v2_playbook_on_start(self, playbook):
|
||||
data = self.base_data.copy()
|
||||
data['ansible_type'] = "start"
|
||||
data['status'] = "OK"
|
||||
data['ansible_playbook'] = playbook._file_name
|
||||
|
||||
if (self.ls_format_version == "v2"):
|
||||
self.logger.info(
|
||||
"START PLAYBOOK | %s", data['ansible_playbook'], extra=data
|
||||
)
|
||||
else:
|
||||
self.logger.info("ansible start", extra=data)
|
||||
self.playbook = playbook._file_name
|
||||
data = {
|
||||
'status': "OK",
|
||||
'host': self.hostname,
|
||||
'session': self.session,
|
||||
'ansible_type': "start",
|
||||
'ansible_playbook': self.playbook,
|
||||
}
|
||||
self.logger.info("ansible start", extra=data)
|
||||
|
||||
def v2_playbook_on_stats(self, stats):
|
||||
end_time = datetime.utcnow()
|
||||
@@ -193,201 +126,103 @@ class CallbackModule(CallbackBase):
|
||||
else:
|
||||
status = "FAILED"
|
||||
|
||||
data = self.base_data.copy()
|
||||
data['ansible_type'] = "finish"
|
||||
data['status'] = status
|
||||
data['ansible_playbook_duration'] = runtime.total_seconds()
|
||||
data['ansible_result'] = json.dumps(summarize_stat) # deprecated field
|
||||
|
||||
if (self.ls_format_version == "v2"):
|
||||
self.logger.info(
|
||||
"FINISH PLAYBOOK | %s", json.dumps(summarize_stat), extra=data
|
||||
)
|
||||
else:
|
||||
self.logger.info("ansible stats", extra=data)
|
||||
|
||||
def v2_playbook_on_play_start(self, play):
|
||||
self.play_id = str(play._uuid)
|
||||
|
||||
if play.name:
|
||||
self.play_name = play.name
|
||||
|
||||
data = self.base_data.copy()
|
||||
data['ansible_type'] = "start"
|
||||
data['status'] = "OK"
|
||||
data['ansible_play_id'] = self.play_id
|
||||
data['ansible_play_name'] = self.play_name
|
||||
|
||||
if (self.ls_format_version == "v2"):
|
||||
self.logger.info("START PLAY | %s", self.play_name, extra=data)
|
||||
else:
|
||||
self.logger.info("ansible play", extra=data)
|
||||
|
||||
def v2_playbook_on_task_start(self, task, is_conditional):
|
||||
self.task_id = str(task._uuid)
|
||||
|
||||
'''
|
||||
Tasks and handler tasks are dealt with here
|
||||
'''
|
||||
data = {
|
||||
'status': status,
|
||||
'host': self.hostname,
|
||||
'session': self.session,
|
||||
'ansible_type': "finish",
|
||||
'ansible_playbook': self.playbook,
|
||||
'ansible_playbook_duration': runtime.total_seconds(),
|
||||
'ansible_result': json.dumps(summarize_stat),
|
||||
}
|
||||
self.logger.info("ansible stats", extra=data)
|
||||
|
||||
def v2_runner_on_ok(self, result, **kwargs):
|
||||
task_name = str(result._task).replace('TASK: ', '').replace('HANDLER: ', '')
|
||||
|
||||
data = self.base_data.copy()
|
||||
if task_name == 'setup':
|
||||
data['ansible_type'] = "setup"
|
||||
data['status'] = "OK"
|
||||
data['ansible_host'] = result._host.name
|
||||
data['ansible_play_id'] = self.play_id
|
||||
data['ansible_play_name'] = self.play_name
|
||||
data['ansible_task'] = task_name
|
||||
data['ansible_facts'] = self._dump_results(result._result)
|
||||
|
||||
if (self.ls_format_version == "v2"):
|
||||
self.logger.info(
|
||||
"SETUP FACTS | %s", self._dump_results(result._result), extra=data
|
||||
)
|
||||
else:
|
||||
self.logger.info("ansible facts", extra=data)
|
||||
else:
|
||||
if 'changed' in result._result.keys():
|
||||
data['ansible_changed'] = result._result['changed']
|
||||
else:
|
||||
data['ansible_changed'] = False
|
||||
|
||||
data['ansible_type'] = "task"
|
||||
data['status'] = "OK"
|
||||
data['ansible_host'] = result._host.name
|
||||
data['ansible_play_id'] = self.play_id
|
||||
data['ansible_play_name'] = self.play_name
|
||||
data['ansible_task'] = task_name
|
||||
data['ansible_task_id'] = self.task_id
|
||||
data['ansible_result'] = self._dump_results(result._result)
|
||||
|
||||
if (self.ls_format_version == "v2"):
|
||||
self.logger.info(
|
||||
"TASK OK | %s | RESULT | %s",
|
||||
task_name, self._dump_results(result._result), extra=data
|
||||
)
|
||||
else:
|
||||
self.logger.info("ansible ok", extra=data)
|
||||
data = {
|
||||
'status': "OK",
|
||||
'host': self.hostname,
|
||||
'session': self.session,
|
||||
'ansible_type': "task",
|
||||
'ansible_playbook': self.playbook,
|
||||
'ansible_host': result._host.name,
|
||||
'ansible_task': result._task,
|
||||
'ansible_result': self._dump_results(result._result)
|
||||
}
|
||||
self.logger.info("ansible ok", extra=data)
|
||||
|
||||
def v2_runner_on_skipped(self, result, **kwargs):
|
||||
task_name = str(result._task).replace('TASK: ', '').replace('HANDLER: ', '')
|
||||
|
||||
data = self.base_data.copy()
|
||||
data['ansible_type'] = "task"
|
||||
data['status'] = "SKIPPED"
|
||||
data['ansible_host'] = result._host.name
|
||||
data['ansible_play_id'] = self.play_id
|
||||
data['ansible_play_name'] = self.play_name
|
||||
data['ansible_task'] = task_name
|
||||
data['ansible_task_id'] = self.task_id
|
||||
data['ansible_result'] = self._dump_results(result._result)
|
||||
|
||||
if (self.ls_format_version == "v2"):
|
||||
self.logger.info("TASK SKIPPED | %s", task_name, extra=data)
|
||||
else:
|
||||
self.logger.info("ansible skipped", extra=data)
|
||||
data = {
|
||||
'status': "SKIPPED",
|
||||
'host': self.hostname,
|
||||
'session': self.session,
|
||||
'ansible_type': "task",
|
||||
'ansible_playbook': self.playbook,
|
||||
'ansible_task': result._task,
|
||||
'ansible_host': result._host.name
|
||||
}
|
||||
self.logger.info("ansible skipped", extra=data)
|
||||
|
||||
def v2_playbook_on_import_for_host(self, result, imported_file):
|
||||
data = self.base_data.copy()
|
||||
data['ansible_type'] = "import"
|
||||
data['status'] = "IMPORTED"
|
||||
data['ansible_host'] = result._host.name
|
||||
data['ansible_play_id'] = self.play_id
|
||||
data['ansible_play_name'] = self.play_name
|
||||
data['imported_file'] = imported_file
|
||||
|
||||
if (self.ls_format_version == "v2"):
|
||||
self.logger.info("IMPORT | %s", imported_file, extra=data)
|
||||
else:
|
||||
self.logger.info("ansible import", extra=data)
|
||||
data = {
|
||||
'status': "IMPORTED",
|
||||
'host': self.hostname,
|
||||
'session': self.session,
|
||||
'ansible_type': "import",
|
||||
'ansible_playbook': self.playbook,
|
||||
'ansible_host': result._host.name,
|
||||
'imported_file': imported_file
|
||||
}
|
||||
self.logger.info("ansible import", extra=data)
|
||||
|
||||
def v2_playbook_on_not_import_for_host(self, result, missing_file):
|
||||
data = self.base_data.copy()
|
||||
data['ansible_type'] = "import"
|
||||
data['status'] = "NOT IMPORTED"
|
||||
data['ansible_host'] = result._host.name
|
||||
data['ansible_play_id'] = self.play_id
|
||||
data['ansible_play_name'] = self.play_name
|
||||
data['imported_file'] = missing_file
|
||||
|
||||
if (self.ls_format_version == "v2"):
|
||||
self.logger.info("NOT IMPORTED | %s", missing_file, extra=data)
|
||||
else:
|
||||
self.logger.info("ansible import", extra=data)
|
||||
data = {
|
||||
'status': "NOT IMPORTED",
|
||||
'host': self.hostname,
|
||||
'session': self.session,
|
||||
'ansible_type': "import",
|
||||
'ansible_playbook': self.playbook,
|
||||
'ansible_host': result._host.name,
|
||||
'missing_file': missing_file
|
||||
}
|
||||
self.logger.info("ansible import", extra=data)
|
||||
|
||||
def v2_runner_on_failed(self, result, **kwargs):
|
||||
task_name = str(result._task).replace('TASK: ', '').replace('HANDLER: ', '')
|
||||
|
||||
data = self.base_data.copy()
|
||||
if 'changed' in result._result.keys():
|
||||
data['ansible_changed'] = result._result['changed']
|
||||
else:
|
||||
data['ansible_changed'] = False
|
||||
|
||||
data['ansible_type'] = "task"
|
||||
data['status'] = "FAILED"
|
||||
data['ansible_host'] = result._host.name
|
||||
data['ansible_play_id'] = self.play_id
|
||||
data['ansible_play_name'] = self.play_name
|
||||
data['ansible_task'] = task_name
|
||||
data['ansible_task_id'] = self.task_id
|
||||
data['ansible_result'] = self._dump_results(result._result)
|
||||
|
||||
data = {
|
||||
'status': "FAILED",
|
||||
'host': self.hostname,
|
||||
'session': self.session,
|
||||
'ansible_type': "task",
|
||||
'ansible_playbook': self.playbook,
|
||||
'ansible_host': result._host.name,
|
||||
'ansible_task': result._task,
|
||||
'ansible_result': self._dump_results(result._result)
|
||||
}
|
||||
self.errors += 1
|
||||
if (self.ls_format_version == "v2"):
|
||||
self.logger.error(
|
||||
"TASK FAILED | %s | HOST | %s | RESULT | %s",
|
||||
task_name, self.hostname,
|
||||
self._dump_results(result._result), extra=data
|
||||
)
|
||||
else:
|
||||
self.logger.error("ansible failed", extra=data)
|
||||
self.logger.error("ansible failed", extra=data)
|
||||
|
||||
def v2_runner_on_unreachable(self, result, **kwargs):
|
||||
task_name = str(result._task).replace('TASK: ', '').replace('HANDLER: ', '')
|
||||
|
||||
data = self.base_data.copy()
|
||||
data['ansible_type'] = "task"
|
||||
data['status'] = "UNREACHABLE"
|
||||
data['ansible_host'] = result._host.name
|
||||
data['ansible_play_id'] = self.play_id
|
||||
data['ansible_play_name'] = self.play_name
|
||||
data['ansible_task'] = task_name
|
||||
data['ansible_task_id'] = self.task_id
|
||||
data['ansible_result'] = self._dump_results(result._result)
|
||||
|
||||
self.errors += 1
|
||||
if (self.ls_format_version == "v2"):
|
||||
self.logger.error(
|
||||
"UNREACHABLE | %s | HOST | %s | RESULT | %s",
|
||||
task_name, self.hostname,
|
||||
self._dump_results(result._result), extra=data
|
||||
)
|
||||
else:
|
||||
self.logger.error("ansible unreachable", extra=data)
|
||||
data = {
|
||||
'status': "UNREACHABLE",
|
||||
'host': self.hostname,
|
||||
'session': self.session,
|
||||
'ansible_type': "task",
|
||||
'ansible_playbook': self.playbook,
|
||||
'ansible_host': result._host.name,
|
||||
'ansible_task': result._task,
|
||||
'ansible_result': self._dump_results(result._result)
|
||||
}
|
||||
self.logger.error("ansible unreachable", extra=data)
|
||||
|
||||
def v2_runner_on_async_failed(self, result, **kwargs):
|
||||
task_name = str(result._task).replace('TASK: ', '').replace('HANDLER: ', '')
|
||||
|
||||
data = self.base_data.copy()
|
||||
data['ansible_type'] = "task"
|
||||
data['status'] = "FAILED"
|
||||
data['ansible_host'] = result._host.name
|
||||
data['ansible_play_id'] = self.play_id
|
||||
data['ansible_play_name'] = self.play_name
|
||||
data['ansible_task'] = task_name
|
||||
data['ansible_task_id'] = self.task_id
|
||||
data['ansible_result'] = self._dump_results(result._result)
|
||||
|
||||
data = {
|
||||
'status': "FAILED",
|
||||
'host': self.hostname,
|
||||
'session': self.session,
|
||||
'ansible_type': "task",
|
||||
'ansible_playbook': self.playbook,
|
||||
'ansible_host': result._host.name,
|
||||
'ansible_task': result._task,
|
||||
'ansible_result': self._dump_results(result._result)
|
||||
}
|
||||
self.errors += 1
|
||||
if (self.ls_format_version == "v2"):
|
||||
self.logger.error(
|
||||
"ASYNC FAILED | %s | HOST | %s | RESULT | %s",
|
||||
task_name, self.hostname,
|
||||
self._dump_results(result._result), extra=data
|
||||
)
|
||||
else:
|
||||
self.logger.error("ansible async", extra=data)
|
||||
self.logger.error("ansible async", extra=data)
|
||||
|
||||
@@ -7,7 +7,7 @@ from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: mail
|
||||
callback: mail
|
||||
type: notification
|
||||
short_description: Sends failure events via email
|
||||
description:
|
||||
@@ -52,7 +52,7 @@ options:
|
||||
ini:
|
||||
- section: callback_mail
|
||||
key: bcc
|
||||
notes:
|
||||
note:
|
||||
- "TODO: expand configuration options now that plugins can leverage Ansible's configuration"
|
||||
'''
|
||||
|
||||
@@ -62,7 +62,7 @@ import re
|
||||
import smtplib
|
||||
|
||||
from ansible.module_utils.six import string_types
|
||||
from ansible.module_utils.common.text.converters import to_bytes
|
||||
from ansible.module_utils._text import to_bytes
|
||||
from ansible.parsing.ajson import AnsibleJSONEncoder
|
||||
from ansible.plugins.callback import CallbackBase
|
||||
|
||||
|
||||
@@ -7,26 +7,25 @@ from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: nrdp
|
||||
callback: nrdp
|
||||
type: notification
|
||||
author: "Remi VERCHERE (@rverchere)"
|
||||
short_description: Post task results to a Nagios server through nrdp
|
||||
short_description: post task result to a nagios server through nrdp
|
||||
description:
|
||||
- This callback send playbook result to Nagios.
|
||||
- Nagios shall use NRDP to recive passive events.
|
||||
- The passive check is sent to a dedicated host/service for Ansible.
|
||||
- this callback send playbook result to nagios
|
||||
- nagios shall use NRDP to recive passive events
|
||||
- the passive check is sent to a dedicated host/service for ansible
|
||||
options:
|
||||
url:
|
||||
description: URL of the nrdp server.
|
||||
required: true
|
||||
description: url of the nrdp server
|
||||
required: True
|
||||
env:
|
||||
- name : NRDP_URL
|
||||
ini:
|
||||
- section: callback_nrdp
|
||||
key: url
|
||||
type: string
|
||||
validate_certs:
|
||||
description: Validate the SSL certificate of the nrdp server. (Used for HTTPS URLs.)
|
||||
description: (bool) validate the SSL certificate of the nrdp server. (For HTTPS url)
|
||||
env:
|
||||
- name: NRDP_VALIDATE_CERTS
|
||||
ini:
|
||||
@@ -34,36 +33,32 @@ DOCUMENTATION = '''
|
||||
key: validate_nrdp_certs
|
||||
- section: callback_nrdp
|
||||
key: validate_certs
|
||||
type: boolean
|
||||
default: false
|
||||
default: False
|
||||
aliases: [ validate_nrdp_certs ]
|
||||
token:
|
||||
description: Token to be allowed to push nrdp events.
|
||||
required: true
|
||||
description: token to be allowed to push nrdp events
|
||||
required: True
|
||||
env:
|
||||
- name: NRDP_TOKEN
|
||||
ini:
|
||||
- section: callback_nrdp
|
||||
key: token
|
||||
type: string
|
||||
hostname:
|
||||
description: Hostname where the passive check is linked to.
|
||||
required: true
|
||||
description: hostname where the passive check is linked to
|
||||
required: True
|
||||
env:
|
||||
- name : NRDP_HOSTNAME
|
||||
ini:
|
||||
- section: callback_nrdp
|
||||
key: hostname
|
||||
type: string
|
||||
servicename:
|
||||
description: Service where the passive check is linked to.
|
||||
required: true
|
||||
description: service where the passive check is linked to
|
||||
required: True
|
||||
env:
|
||||
- name : NRDP_SERVICENAME
|
||||
ini:
|
||||
- section: callback_nrdp
|
||||
key: servicename
|
||||
type: string
|
||||
'''
|
||||
|
||||
import os
|
||||
|
||||
@@ -6,9 +6,8 @@ from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
author: Unknown (!UNKNOWN)
|
||||
name: 'null'
|
||||
type: stdout
|
||||
callback: 'null'
|
||||
callback_type: stdout
|
||||
requirements:
|
||||
- set as main display callback
|
||||
short_description: Don't display stuff to screen
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
say.py
|
||||
@@ -7,8 +7,7 @@ from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
author: Unknown (!UNKNOWN)
|
||||
name: say
|
||||
callback: say
|
||||
type: notification
|
||||
requirements:
|
||||
- whitelisting in configuration
|
||||
|
||||
@@ -6,9 +6,8 @@ from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
author: Unknown (!UNKNOWN)
|
||||
name: selective
|
||||
type: stdout
|
||||
callback: selective
|
||||
callback_type: stdout
|
||||
requirements:
|
||||
- set as main display callback
|
||||
short_description: only print certain tasks
|
||||
@@ -31,8 +30,8 @@ DOCUMENTATION = '''
|
||||
'''
|
||||
|
||||
EXAMPLES = """
|
||||
- ansible.builtin.debug: msg="This will not be printed"
|
||||
- ansible.builtin.debug: msg="But this will"
|
||||
- debug: msg="This will not be printed"
|
||||
- debug: msg="But this will"
|
||||
tags: [print_action]
|
||||
"""
|
||||
|
||||
@@ -40,17 +39,8 @@ import difflib
|
||||
|
||||
from ansible import constants as C
|
||||
from ansible.plugins.callback import CallbackBase
|
||||
from ansible.module_utils.common.text.converters import to_text
|
||||
|
||||
try:
|
||||
codeCodes = C.COLOR_CODES
|
||||
except AttributeError:
|
||||
# This constant was moved to ansible.constants in
|
||||
# https://github.com/ansible/ansible/commit/1202dd000f10b0e8959019484f1c3b3f9628fc67
|
||||
# (will be included in ansible-core 2.11.0). For older Ansible/ansible-base versions,
|
||||
# we include from the original location.
|
||||
from ansible.utils.color import codeCodes
|
||||
|
||||
from ansible.module_utils._text import to_text
|
||||
from ansible.utils.color import codeCodes
|
||||
|
||||
DONT_COLORIZE = False
|
||||
COLORS = {
|
||||
@@ -67,7 +57,7 @@ COLORS = {
|
||||
|
||||
def dict_diff(prv, nxt):
|
||||
"""Return a dict of keys that differ with another config object."""
|
||||
keys = set(list(prv.keys()) + list(nxt.keys()))
|
||||
keys = set(prv.keys() + nxt.keys())
|
||||
result = {}
|
||||
for k in keys:
|
||||
if prv.get(k) != nxt.get(k):
|
||||
@@ -211,7 +201,7 @@ class CallbackModule(CallbackBase):
|
||||
)
|
||||
if 'results' in result._result:
|
||||
for r in result._result['results']:
|
||||
failed = 'failed' in r and r['failed']
|
||||
failed = 'failed' in r
|
||||
|
||||
stderr = [r.get('exception', None), r.get('module_stderr', None)]
|
||||
stderr = "\n".join([e for e in stderr if e]).strip()
|
||||
|
||||
@@ -7,9 +7,8 @@ from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
author: Unknown (!UNKNOWN)
|
||||
name: slack
|
||||
type: notification
|
||||
callback: slack
|
||||
callback_type: notification
|
||||
requirements:
|
||||
- whitelist in configuration
|
||||
- prettytable (python library)
|
||||
@@ -58,7 +57,7 @@ import os
|
||||
import uuid
|
||||
|
||||
from ansible import context
|
||||
from ansible.module_utils.common.text.converters import to_text
|
||||
from ansible.module_utils._text import to_text
|
||||
from ansible.module_utils.urls import open_url
|
||||
from ansible.plugins.callback import CallbackBase
|
||||
|
||||
|
||||
@@ -18,10 +18,10 @@ from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: splunk
|
||||
callback: splunk
|
||||
type: aggregate
|
||||
short_description: Sends task result events to Splunk HTTP Event Collector
|
||||
author: "Stuart Hirst (!UNKNOWN) <support@convergingdata.com>"
|
||||
author: "Stuart Hirst <support@convergingdata.com>"
|
||||
description:
|
||||
- This callback plugin will send task results as JSON formatted events to a Splunk HTTP collector.
|
||||
- The companion Splunk Monitoring & Diagnostics App is available here "https://splunkbase.splunk.com/app/4023/"
|
||||
@@ -45,46 +45,13 @@ DOCUMENTATION = '''
|
||||
ini:
|
||||
- section: callback_splunk
|
||||
key: authtoken
|
||||
validate_certs:
|
||||
description: Whether to validate certificates for connections to HEC. It is not recommended to set to
|
||||
C(false) except when you are sure that nobody can intercept the connection
|
||||
between this plugin and HEC, as setting it to C(false) allows man-in-the-middle attacks!
|
||||
env:
|
||||
- name: SPLUNK_VALIDATE_CERTS
|
||||
ini:
|
||||
- section: callback_splunk
|
||||
key: validate_certs
|
||||
type: bool
|
||||
default: true
|
||||
version_added: '1.0.0'
|
||||
include_milliseconds:
|
||||
description: Whether to include milliseconds as part of the generated timestamp field in the event
|
||||
sent to the Splunk HTTP collector
|
||||
env:
|
||||
- name: SPLUNK_INCLUDE_MILLISECONDS
|
||||
ini:
|
||||
- section: callback_splunk
|
||||
key: include_milliseconds
|
||||
type: bool
|
||||
default: false
|
||||
version_added: 2.0.0
|
||||
batch:
|
||||
description:
|
||||
- Correlation ID which can be set across multiple playbook executions.
|
||||
env:
|
||||
- name: SPLUNK_BATCH
|
||||
ini:
|
||||
- section: callback_splunk
|
||||
key: batch
|
||||
type: str
|
||||
version_added: 3.3.0
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
examples: >
|
||||
To enable, add this to your ansible.cfg file in the defaults block
|
||||
[defaults]
|
||||
callback_whitelist = community.general.splunk
|
||||
callback_whitelist = splunk
|
||||
Set the environment variable
|
||||
export SPLUNK_URL=http://mysplunkinstance.datapaas.io:8088/services/collector/event
|
||||
export SPLUNK_AUTHTOKEN=f23blad6-5965-4537-bf69-5b5a545blabla88
|
||||
@@ -117,7 +84,7 @@ class SplunkHTTPCollectorSource(object):
|
||||
self.ip_address = socket.gethostbyname(socket.gethostname())
|
||||
self.user = getpass.getuser()
|
||||
|
||||
def send_event(self, url, authtoken, validate_certs, include_milliseconds, batch, state, result, runtime):
|
||||
def send_event(self, url, authtoken, state, result, runtime):
|
||||
if result._task_fields['args'].get('_ansible_check_mode') is True:
|
||||
self.ansible_check_mode = True
|
||||
|
||||
@@ -136,16 +103,9 @@ class SplunkHTTPCollectorSource(object):
|
||||
data = {}
|
||||
data['uuid'] = result._task._uuid
|
||||
data['session'] = self.session
|
||||
if batch is not None:
|
||||
data['batch'] = batch
|
||||
data['status'] = state
|
||||
|
||||
if include_milliseconds:
|
||||
time_format = '%Y-%m-%d %H:%M:%S.%f +0000'
|
||||
else:
|
||||
time_format = '%Y-%m-%d %H:%M:%S +0000'
|
||||
|
||||
data['timestamp'] = datetime.utcnow().strftime(time_format)
|
||||
data['timestamp'] = datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S '
|
||||
'+0000')
|
||||
data['host'] = self.host
|
||||
data['ip_address'] = self.ip_address
|
||||
data['user'] = self.user
|
||||
@@ -169,8 +129,7 @@ class SplunkHTTPCollectorSource(object):
|
||||
'Content-type': 'application/json',
|
||||
'Authorization': 'Splunk ' + authtoken
|
||||
},
|
||||
method='POST',
|
||||
validate_certs=validate_certs
|
||||
method='POST'
|
||||
)
|
||||
|
||||
|
||||
@@ -185,9 +144,6 @@ class CallbackModule(CallbackBase):
|
||||
self.start_datetimes = {} # Collect task start times
|
||||
self.url = None
|
||||
self.authtoken = None
|
||||
self.validate_certs = None
|
||||
self.include_milliseconds = None
|
||||
self.batch = None
|
||||
self.splunk = SplunkHTTPCollectorSource()
|
||||
|
||||
def _runtime(self, result):
|
||||
@@ -197,9 +153,7 @@ class CallbackModule(CallbackBase):
|
||||
).total_seconds()
|
||||
|
||||
def set_options(self, task_keys=None, var_options=None, direct=None):
|
||||
super(CallbackModule, self).set_options(task_keys=task_keys,
|
||||
var_options=var_options,
|
||||
direct=direct)
|
||||
super(CallbackModule, self).set_options(task_keys=task_keys, var_options=var_options, direct=direct)
|
||||
|
||||
self.url = self.get_option('url')
|
||||
|
||||
@@ -221,12 +175,6 @@ class CallbackModule(CallbackBase):
|
||||
'`SPLUNK_AUTHTOKEN` environment variable or '
|
||||
'in the ansible.cfg file.')
|
||||
|
||||
self.validate_certs = self.get_option('validate_certs')
|
||||
|
||||
self.include_milliseconds = self.get_option('include_milliseconds')
|
||||
|
||||
self.batch = self.get_option('batch')
|
||||
|
||||
def v2_playbook_on_start(self, playbook):
|
||||
self.splunk.ansible_playbook = basename(playbook._file_name)
|
||||
|
||||
@@ -240,9 +188,6 @@ class CallbackModule(CallbackBase):
|
||||
self.splunk.send_event(
|
||||
self.url,
|
||||
self.authtoken,
|
||||
self.validate_certs,
|
||||
self.include_milliseconds,
|
||||
self.batch,
|
||||
'OK',
|
||||
result,
|
||||
self._runtime(result)
|
||||
@@ -252,9 +197,6 @@ class CallbackModule(CallbackBase):
|
||||
self.splunk.send_event(
|
||||
self.url,
|
||||
self.authtoken,
|
||||
self.validate_certs,
|
||||
self.include_milliseconds,
|
||||
self.batch,
|
||||
'SKIPPED',
|
||||
result,
|
||||
self._runtime(result)
|
||||
@@ -264,9 +206,6 @@ class CallbackModule(CallbackBase):
|
||||
self.splunk.send_event(
|
||||
self.url,
|
||||
self.authtoken,
|
||||
self.validate_certs,
|
||||
self.include_milliseconds,
|
||||
self.batch,
|
||||
'FAILED',
|
||||
result,
|
||||
self._runtime(result)
|
||||
@@ -276,9 +215,6 @@ class CallbackModule(CallbackBase):
|
||||
self.splunk.send_event(
|
||||
self.url,
|
||||
self.authtoken,
|
||||
self.validate_certs,
|
||||
self.include_milliseconds,
|
||||
self.batch,
|
||||
'FAILED',
|
||||
result,
|
||||
self._runtime(result)
|
||||
@@ -288,9 +224,6 @@ class CallbackModule(CallbackBase):
|
||||
self.splunk.send_event(
|
||||
self.url,
|
||||
self.authtoken,
|
||||
self.validate_certs,
|
||||
self.include_milliseconds,
|
||||
self.batch,
|
||||
'UNREACHABLE',
|
||||
result,
|
||||
self._runtime(result)
|
||||
|
||||
70
plugins/callback/stderr.py
Normal file
70
plugins/callback/stderr.py
Normal file
@@ -0,0 +1,70 @@
|
||||
# (c) 2017, Frederic Van Espen <github@freh.be>
|
||||
# (c) 2017 Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
callback: stderr
|
||||
callback_type: stdout
|
||||
requirements:
|
||||
- set as main display callback
|
||||
short_description: Splits output, sending failed tasks to stderr
|
||||
deprecated:
|
||||
why: The 'default' callback plugin now supports this functionality
|
||||
removed_in: '2.0.0' # was Ansible 2.11
|
||||
alternative: "'default' callback plugin with 'display_failed_stderr = yes' option"
|
||||
extends_documentation_fragment:
|
||||
- default_callback
|
||||
description:
|
||||
- This is the stderr callback plugin, it behaves like the default callback plugin but sends error output to stderr.
|
||||
- Also it does not output skipped host/task/item status
|
||||
'''
|
||||
|
||||
from ansible import constants as C
|
||||
from ansible.plugins.callback.default import CallbackModule as CallbackModule_default
|
||||
|
||||
|
||||
class CallbackModule(CallbackModule_default):
|
||||
|
||||
'''
|
||||
This is the stderr callback plugin, which reuses the default
|
||||
callback plugin but sends error output to stderr.
|
||||
'''
|
||||
|
||||
CALLBACK_VERSION = 2.0
|
||||
CALLBACK_TYPE = 'stdout'
|
||||
CALLBACK_NAME = 'community.general.stderr'
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.super_ref = super(CallbackModule, self)
|
||||
self.super_ref.__init__()
|
||||
|
||||
def v2_runner_on_failed(self, result, ignore_errors=False):
|
||||
|
||||
delegated_vars = result._result.get('_ansible_delegated_vars', None)
|
||||
self._clean_results(result._result, result._task.action)
|
||||
|
||||
if self._play.strategy == 'free' and self._last_task_banner != result._task._uuid:
|
||||
self._print_task_banner(result._task)
|
||||
|
||||
self._handle_exception(result._result, use_stderr=True)
|
||||
self._handle_warnings(result._result)
|
||||
|
||||
if result._task.loop and 'results' in result._result:
|
||||
self._process_items(result)
|
||||
|
||||
else:
|
||||
if delegated_vars:
|
||||
self._display.display("fatal: [%s -> %s]: FAILED! => %s" % (result._host.get_name(), delegated_vars['ansible_host'],
|
||||
self._dump_results(result._result)), color=C.COLOR_ERROR,
|
||||
stderr=True)
|
||||
else:
|
||||
self._display.display("fatal: [%s]: FAILED! => %s" % (result._host.get_name(), self._dump_results(result._result)),
|
||||
color=C.COLOR_ERROR, stderr=True)
|
||||
|
||||
if ignore_errors:
|
||||
self._display.display("...ignoring", color=C.COLOR_SKIP)
|
||||
@@ -18,7 +18,7 @@ from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: sumologic
|
||||
callback: sumologic
|
||||
type: aggregate
|
||||
short_description: Sends task result events to Sumologic
|
||||
author: "Ryan Currah (@ryancurrah)"
|
||||
@@ -42,7 +42,7 @@ EXAMPLES = '''
|
||||
examples: >
|
||||
To enable, add this to your ansible.cfg file in the defaults block
|
||||
[defaults]
|
||||
callback_whitelist = community.general.sumologic
|
||||
callback_whitelist = sumologic
|
||||
|
||||
Set the environment variable
|
||||
export SUMOLOGIC_URL=https://endpoint1.collection.us2.sumologic.com/receiver/v1/http/R8moSv1d8EW9LAUFZJ6dbxCFxwLH6kfCdcBfddlfxCbLuL-BN5twcTpMk__pYy_cDmp==
|
||||
|
||||
@@ -6,9 +6,8 @@ from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
author: Unknown (!UNKNOWN)
|
||||
name: syslog_json
|
||||
type: notification
|
||||
callback: syslog_json
|
||||
callback_type: notification
|
||||
requirements:
|
||||
- whitelist in configuration
|
||||
short_description: sends JSON events to syslog
|
||||
|
||||
@@ -7,9 +7,9 @@ from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: unixy
|
||||
callback: unixy
|
||||
type: stdout
|
||||
author: Allyson Bowles (@akatch)
|
||||
author: Allyson Bowles <@akatch>
|
||||
short_description: condensed Ansible output
|
||||
description:
|
||||
- Consolidated Ansible output in the style of LINUX/UNIX startup logs.
|
||||
@@ -22,7 +22,7 @@ DOCUMENTATION = '''
|
||||
from os.path import basename
|
||||
from ansible import constants as C
|
||||
from ansible import context
|
||||
from ansible.module_utils.common.text.converters import to_text
|
||||
from ansible.module_utils._text import to_text
|
||||
from ansible.utils.color import colorize, hostcolor
|
||||
from ansible.plugins.callback.default import CallbackModule as CallbackModule_default
|
||||
|
||||
|
||||
@@ -6,8 +6,7 @@ from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
author: Unknown (!UNKNOWN)
|
||||
name: yaml
|
||||
callback: yaml
|
||||
type: stdout
|
||||
short_description: yaml-ized Ansible screen output
|
||||
description:
|
||||
@@ -25,7 +24,7 @@ import re
|
||||
import string
|
||||
import sys
|
||||
|
||||
from ansible.module_utils.common.text.converters import to_bytes, to_text
|
||||
from ansible.module_utils._text import to_bytes, to_text
|
||||
from ansible.module_utils.six import string_types
|
||||
from ansible.parsing.yaml.dumper import AnsibleDumper
|
||||
from ansible.plugins.callback import CallbackBase, strip_internal_keys, module_response_deepcopy
|
||||
@@ -50,7 +49,7 @@ def my_represent_scalar(self, tag, value, style=None):
|
||||
# ...no trailing space
|
||||
value = value.rstrip()
|
||||
# ...and non-printable characters
|
||||
value = ''.join(x for x in value if x in string.printable or ord(x) >= 0xA0)
|
||||
value = ''.join(x for x in value if x in string.printable)
|
||||
# ...tabs prevent blocks from expanding
|
||||
value = value.expandtabs()
|
||||
# ...and odd bits of whitespace
|
||||
|
||||
@@ -9,8 +9,8 @@ from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
author: Maykel Moya (!UNKNOWN) <mmoya@speedyrails.com>
|
||||
name: chroot
|
||||
author: Maykel Moya <mmoya@speedyrails.com>
|
||||
connection: chroot
|
||||
short_description: Interact with local chroot
|
||||
description:
|
||||
- Run commands or put/fetch files to an existing chroot on the Ansible controller.
|
||||
@@ -54,7 +54,7 @@ from ansible.errors import AnsibleError
|
||||
from ansible.module_utils.basic import is_executable
|
||||
from ansible.module_utils.common.process import get_bin_path
|
||||
from ansible.module_utils.six.moves import shlex_quote
|
||||
from ansible.module_utils.common.text.converters import to_bytes, to_native
|
||||
from ansible.module_utils._text import to_bytes, to_native
|
||||
from ansible.plugins.connection import ConnectionBase, BUFSIZE
|
||||
from ansible.utils.display import Display
|
||||
|
||||
@@ -62,7 +62,7 @@ display = Display()
|
||||
|
||||
|
||||
class Connection(ConnectionBase):
|
||||
""" Local chroot based connections """
|
||||
''' Local chroot based connections '''
|
||||
|
||||
transport = 'community.general.chroot'
|
||||
has_pipelining = True
|
||||
@@ -95,7 +95,7 @@ class Connection(ConnectionBase):
|
||||
raise AnsibleError("%s does not look like a chrootable dir (/bin/sh missing)" % self.chroot)
|
||||
|
||||
def _connect(self):
|
||||
""" connect to the chroot """
|
||||
''' connect to the chroot '''
|
||||
if os.path.isabs(self.get_option('chroot_exe')):
|
||||
self.chroot_cmd = self.get_option('chroot_exe')
|
||||
else:
|
||||
@@ -110,17 +110,17 @@ class Connection(ConnectionBase):
|
||||
self._connected = True
|
||||
|
||||
def _buffered_exec_command(self, cmd, stdin=subprocess.PIPE):
|
||||
""" run a command on the chroot. This is only needed for implementing
|
||||
''' run a command on the chroot. This is only needed for implementing
|
||||
put_file() get_file() so that we don't have to read the whole file
|
||||
into memory.
|
||||
|
||||
compared to exec_command() it looses some niceties like being able to
|
||||
return the process's exit code immediately.
|
||||
"""
|
||||
'''
|
||||
executable = self.get_option('executable')
|
||||
local_cmd = [self.chroot_cmd, self.chroot, executable, '-c', cmd]
|
||||
|
||||
display.vvv("EXEC %s" % local_cmd, host=self.chroot)
|
||||
display.vvv("EXEC %s" % (local_cmd), host=self.chroot)
|
||||
local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]
|
||||
p = subprocess.Popen(local_cmd, shell=False, stdin=stdin,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
@@ -128,17 +128,16 @@ class Connection(ConnectionBase):
|
||||
return p
|
||||
|
||||
def exec_command(self, cmd, in_data=None, sudoable=False):
|
||||
""" run a command on the chroot """
|
||||
''' run a command on the chroot '''
|
||||
super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
|
||||
|
||||
p = self._buffered_exec_command(cmd)
|
||||
|
||||
stdout, stderr = p.communicate(in_data)
|
||||
return p.returncode, stdout, stderr
|
||||
return (p.returncode, stdout, stderr)
|
||||
|
||||
@staticmethod
|
||||
def _prefix_login_path(remote_path):
|
||||
""" Make sure that we put files into a standard path
|
||||
def _prefix_login_path(self, remote_path):
|
||||
''' Make sure that we put files into a standard path
|
||||
|
||||
If a path is relative, then we need to choose where to put it.
|
||||
ssh chooses $HOME but we aren't guaranteed that a home dir will
|
||||
@@ -146,13 +145,13 @@ class Connection(ConnectionBase):
|
||||
This also happens to be the former default.
|
||||
|
||||
Can revisit using $HOME instead if it's a problem
|
||||
"""
|
||||
'''
|
||||
if not remote_path.startswith(os.path.sep):
|
||||
remote_path = os.path.join(os.path.sep, remote_path)
|
||||
return os.path.normpath(remote_path)
|
||||
|
||||
def put_file(self, in_path, out_path):
|
||||
""" transfer a file from local to chroot """
|
||||
''' transfer a file from local to chroot '''
|
||||
super(Connection, self).put_file(in_path, out_path)
|
||||
display.vvv("PUT %s TO %s" % (in_path, out_path), host=self.chroot)
|
||||
|
||||
@@ -178,7 +177,7 @@ class Connection(ConnectionBase):
|
||||
raise AnsibleError("file or module does not exist at: %s" % in_path)
|
||||
|
||||
def fetch_file(self, in_path, out_path):
|
||||
""" fetch a file from chroot to local """
|
||||
''' fetch a file from chroot to local '''
|
||||
super(Connection, self).fetch_file(in_path, out_path)
|
||||
display.vvv("FETCH %s TO %s" % (in_path, out_path), host=self.chroot)
|
||||
|
||||
@@ -202,6 +201,6 @@ class Connection(ConnectionBase):
|
||||
raise AnsibleError("failed to transfer file %s to %s:\n%s\n%s" % (in_path, out_path, stdout, stderr))
|
||||
|
||||
def close(self):
|
||||
""" terminate the connection; nothing to do here """
|
||||
''' terminate the connection; nothing to do here '''
|
||||
super(Connection, self).close()
|
||||
self._connected = False
|
||||
|
||||
364
plugins/connection/docker.py
Normal file
364
plugins/connection/docker.py
Normal file
@@ -0,0 +1,364 @@
|
||||
# Based on the chroot connection plugin by Maykel Moya
|
||||
#
|
||||
# (c) 2014, Lorin Hochstein
|
||||
# (c) 2015, Leendert Brouwer (https://github.com/objectified)
|
||||
# (c) 2015, Toshio Kuratomi <tkuratomi@ansible.com>
|
||||
# Copyright (c) 2017 Ansible Project
|
||||
# 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 = '''
|
||||
author:
|
||||
- Lorin Hochestein
|
||||
- Leendert Brouwer
|
||||
connection: docker
|
||||
short_description: Run tasks in docker containers
|
||||
description:
|
||||
- Run commands or put/fetch files to an existing docker container.
|
||||
options:
|
||||
remote_user:
|
||||
description:
|
||||
- The user to execute as inside the container
|
||||
vars:
|
||||
- name: ansible_user
|
||||
- name: ansible_docker_user
|
||||
docker_extra_args:
|
||||
description:
|
||||
- Extra arguments to pass to the docker command line
|
||||
default: ''
|
||||
remote_addr:
|
||||
description:
|
||||
- The name of the container you want to access.
|
||||
default: inventory_hostname
|
||||
vars:
|
||||
- name: ansible_host
|
||||
- name: ansible_docker_host
|
||||
'''
|
||||
|
||||
import distutils.spawn
|
||||
import fcntl
|
||||
import os
|
||||
import os.path
|
||||
import subprocess
|
||||
import re
|
||||
|
||||
from distutils.version import LooseVersion
|
||||
|
||||
import ansible.constants as C
|
||||
from ansible.compat import selectors
|
||||
from ansible.errors import AnsibleError, AnsibleFileNotFound
|
||||
from ansible.module_utils.six.moves import shlex_quote
|
||||
from ansible.module_utils._text import to_bytes, to_native, to_text
|
||||
from ansible.plugins.connection import ConnectionBase, BUFSIZE
|
||||
from ansible.utils.display import Display
|
||||
|
||||
display = Display()
|
||||
|
||||
|
||||
class Connection(ConnectionBase):
|
||||
''' Local docker based connections '''
|
||||
|
||||
transport = 'community.general.docker'
|
||||
has_pipelining = True
|
||||
|
||||
def __init__(self, play_context, new_stdin, *args, **kwargs):
|
||||
super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs)
|
||||
|
||||
# Note: docker supports running as non-root in some configurations.
|
||||
# (For instance, setting the UNIX socket file to be readable and
|
||||
# writable by a specific UNIX group and then putting users into that
|
||||
# group). Therefore we don't check that the user is root when using
|
||||
# this connection. But if the user is getting a permission denied
|
||||
# error it probably means that docker on their system is only
|
||||
# configured to be connected to by root and they are not running as
|
||||
# root.
|
||||
|
||||
# Windows uses Powershell modules
|
||||
if getattr(self._shell, "_IS_WINDOWS", False):
|
||||
self.module_implementation_preferences = ('.ps1', '.exe', '')
|
||||
|
||||
if 'docker_command' in kwargs:
|
||||
self.docker_cmd = kwargs['docker_command']
|
||||
else:
|
||||
self.docker_cmd = distutils.spawn.find_executable('docker')
|
||||
if not self.docker_cmd:
|
||||
raise AnsibleError("docker command not found in PATH")
|
||||
|
||||
docker_version = self._get_docker_version()
|
||||
if docker_version == u'dev':
|
||||
display.warning(u'Docker version number is "dev". Will assume latest version.')
|
||||
if docker_version != u'dev' and LooseVersion(docker_version) < LooseVersion(u'1.3'):
|
||||
raise AnsibleError('docker connection type requires docker 1.3 or higher')
|
||||
|
||||
# The remote user we will request from docker (if supported)
|
||||
self.remote_user = None
|
||||
# The actual user which will execute commands in docker (if known)
|
||||
self.actual_user = None
|
||||
|
||||
if self._play_context.remote_user is not None:
|
||||
if docker_version == u'dev' or LooseVersion(docker_version) >= LooseVersion(u'1.7'):
|
||||
# Support for specifying the exec user was added in docker 1.7
|
||||
self.remote_user = self._play_context.remote_user
|
||||
self.actual_user = self.remote_user
|
||||
else:
|
||||
self.actual_user = self._get_docker_remote_user()
|
||||
|
||||
if self.actual_user != self._play_context.remote_user:
|
||||
display.warning(u'docker {0} does not support remote_user, using container default: {1}'
|
||||
.format(docker_version, self.actual_user or u'?'))
|
||||
elif self._display.verbosity > 2:
|
||||
# Since we're not setting the actual_user, look it up so we have it for logging later
|
||||
# Only do this if display verbosity is high enough that we'll need the value
|
||||
# This saves overhead from calling into docker when we don't need to
|
||||
self.actual_user = self._get_docker_remote_user()
|
||||
|
||||
@staticmethod
|
||||
def _sanitize_version(version):
|
||||
return re.sub(u'[^0-9a-zA-Z.]', u'', version)
|
||||
|
||||
def _old_docker_version(self):
|
||||
cmd_args = []
|
||||
if self._play_context.docker_extra_args:
|
||||
cmd_args += self._play_context.docker_extra_args.split(' ')
|
||||
|
||||
old_version_subcommand = ['version']
|
||||
|
||||
old_docker_cmd = [self.docker_cmd] + cmd_args + old_version_subcommand
|
||||
p = subprocess.Popen(old_docker_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
cmd_output, err = p.communicate()
|
||||
|
||||
return old_docker_cmd, to_native(cmd_output), err, p.returncode
|
||||
|
||||
def _new_docker_version(self):
|
||||
# no result yet, must be newer Docker version
|
||||
cmd_args = []
|
||||
if self._play_context.docker_extra_args:
|
||||
cmd_args += self._play_context.docker_extra_args.split(' ')
|
||||
|
||||
new_version_subcommand = ['version', '--format', "'{{.Server.Version}}'"]
|
||||
|
||||
new_docker_cmd = [self.docker_cmd] + cmd_args + new_version_subcommand
|
||||
p = subprocess.Popen(new_docker_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
cmd_output, err = p.communicate()
|
||||
return new_docker_cmd, to_native(cmd_output), err, p.returncode
|
||||
|
||||
def _get_docker_version(self):
|
||||
|
||||
cmd, cmd_output, err, returncode = self._old_docker_version()
|
||||
if returncode == 0:
|
||||
for line in to_text(cmd_output, errors='surrogate_or_strict').split(u'\n'):
|
||||
if line.startswith(u'Server version:'): # old docker versions
|
||||
return self._sanitize_version(line.split()[2])
|
||||
|
||||
cmd, cmd_output, err, returncode = self._new_docker_version()
|
||||
if returncode:
|
||||
raise AnsibleError('Docker version check (%s) failed: %s' % (to_native(cmd), to_native(err)))
|
||||
|
||||
return self._sanitize_version(to_text(cmd_output, errors='surrogate_or_strict'))
|
||||
|
||||
def _get_docker_remote_user(self):
|
||||
""" Get the default user configured in the docker container """
|
||||
p = subprocess.Popen([self.docker_cmd, 'inspect', '--format', '{{.Config.User}}', self._play_context.remote_addr],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
out, err = p.communicate()
|
||||
out = to_text(out, errors='surrogate_or_strict')
|
||||
|
||||
if p.returncode != 0:
|
||||
display.warning(u'unable to retrieve default user from docker container: %s %s' % (out, to_text(err)))
|
||||
return None
|
||||
|
||||
# The default exec user is root, unless it was changed in the Dockerfile with USER
|
||||
return out.strip() or u'root'
|
||||
|
||||
def _build_exec_cmd(self, cmd):
|
||||
""" Build the local docker exec command to run cmd on remote_host
|
||||
|
||||
If remote_user is available and is supported by the docker
|
||||
version we are using, it will be provided to docker exec.
|
||||
"""
|
||||
|
||||
local_cmd = [self.docker_cmd]
|
||||
|
||||
if self._play_context.docker_extra_args:
|
||||
local_cmd += self._play_context.docker_extra_args.split(' ')
|
||||
|
||||
local_cmd += [b'exec']
|
||||
|
||||
if self.remote_user is not None:
|
||||
local_cmd += [b'-u', self.remote_user]
|
||||
|
||||
# -i is needed to keep stdin open which allows pipelining to work
|
||||
local_cmd += [b'-i', self._play_context.remote_addr] + cmd
|
||||
|
||||
return local_cmd
|
||||
|
||||
def _connect(self, port=None):
|
||||
""" Connect to the container. Nothing to do """
|
||||
super(Connection, self)._connect()
|
||||
if not self._connected:
|
||||
display.vvv(u"ESTABLISH DOCKER CONNECTION FOR USER: {0}".format(
|
||||
self.actual_user or u'?'), host=self._play_context.remote_addr
|
||||
)
|
||||
self._connected = True
|
||||
|
||||
def exec_command(self, cmd, in_data=None, sudoable=False):
|
||||
""" Run a command on the docker host """
|
||||
super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
|
||||
|
||||
local_cmd = self._build_exec_cmd([self._play_context.executable, '-c', cmd])
|
||||
|
||||
display.vvv(u"EXEC {0}".format(to_text(local_cmd)), host=self._play_context.remote_addr)
|
||||
display.debug("opening command with Popen()")
|
||||
|
||||
local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]
|
||||
|
||||
p = subprocess.Popen(
|
||||
local_cmd,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
display.debug("done running command with Popen()")
|
||||
|
||||
if self.become and self.become.expect_prompt() and sudoable:
|
||||
fcntl.fcntl(p.stdout, fcntl.F_SETFL, fcntl.fcntl(p.stdout, fcntl.F_GETFL) | os.O_NONBLOCK)
|
||||
fcntl.fcntl(p.stderr, fcntl.F_SETFL, fcntl.fcntl(p.stderr, fcntl.F_GETFL) | os.O_NONBLOCK)
|
||||
selector = selectors.DefaultSelector()
|
||||
selector.register(p.stdout, selectors.EVENT_READ)
|
||||
selector.register(p.stderr, selectors.EVENT_READ)
|
||||
|
||||
become_output = b''
|
||||
try:
|
||||
while not self.become.check_success(become_output) and not self.become.check_password_prompt(become_output):
|
||||
events = selector.select(self._play_context.timeout)
|
||||
if not events:
|
||||
stdout, stderr = p.communicate()
|
||||
raise AnsibleError('timeout waiting for privilege escalation password prompt:\n' + to_native(become_output))
|
||||
|
||||
for key, event in events:
|
||||
if key.fileobj == p.stdout:
|
||||
chunk = p.stdout.read()
|
||||
elif key.fileobj == p.stderr:
|
||||
chunk = p.stderr.read()
|
||||
|
||||
if not chunk:
|
||||
stdout, stderr = p.communicate()
|
||||
raise AnsibleError('privilege output closed while waiting for password prompt:\n' + to_native(become_output))
|
||||
become_output += chunk
|
||||
finally:
|
||||
selector.close()
|
||||
|
||||
if not self.become.check_success(become_output):
|
||||
become_pass = self.become.get_option('become_pass', playcontext=self._play_context)
|
||||
p.stdin.write(to_bytes(become_pass, errors='surrogate_or_strict') + b'\n')
|
||||
fcntl.fcntl(p.stdout, fcntl.F_SETFL, fcntl.fcntl(p.stdout, fcntl.F_GETFL) & ~os.O_NONBLOCK)
|
||||
fcntl.fcntl(p.stderr, fcntl.F_SETFL, fcntl.fcntl(p.stderr, fcntl.F_GETFL) & ~os.O_NONBLOCK)
|
||||
|
||||
display.debug("getting output with communicate()")
|
||||
stdout, stderr = p.communicate(in_data)
|
||||
display.debug("done communicating")
|
||||
|
||||
display.debug("done with docker.exec_command()")
|
||||
return (p.returncode, stdout, stderr)
|
||||
|
||||
def _prefix_login_path(self, remote_path):
|
||||
''' Make sure that we put files into a standard path
|
||||
|
||||
If a path is relative, then we need to choose where to put it.
|
||||
ssh chooses $HOME but we aren't guaranteed that a home dir will
|
||||
exist in any given chroot. So for now we're choosing "/" instead.
|
||||
This also happens to be the former default.
|
||||
|
||||
Can revisit using $HOME instead if it's a problem
|
||||
'''
|
||||
if getattr(self._shell, "_IS_WINDOWS", False):
|
||||
import ntpath
|
||||
return ntpath.normpath(remote_path)
|
||||
else:
|
||||
if not remote_path.startswith(os.path.sep):
|
||||
remote_path = os.path.join(os.path.sep, remote_path)
|
||||
return os.path.normpath(remote_path)
|
||||
|
||||
def put_file(self, in_path, out_path):
|
||||
""" Transfer a file from local to docker container """
|
||||
super(Connection, self).put_file(in_path, out_path)
|
||||
display.vvv("PUT %s TO %s" % (in_path, out_path), host=self._play_context.remote_addr)
|
||||
|
||||
out_path = self._prefix_login_path(out_path)
|
||||
if not os.path.exists(to_bytes(in_path, errors='surrogate_or_strict')):
|
||||
raise AnsibleFileNotFound(
|
||||
"file or module does not exist: %s" % to_native(in_path))
|
||||
|
||||
out_path = shlex_quote(out_path)
|
||||
# Older docker doesn't have native support for copying files into
|
||||
# running containers, so we use docker exec to implement this
|
||||
# Although docker version 1.8 and later provide support, the
|
||||
# owner and group of the files are always set to root
|
||||
with open(to_bytes(in_path, errors='surrogate_or_strict'), 'rb') as in_file:
|
||||
if not os.fstat(in_file.fileno()).st_size:
|
||||
count = ' count=0'
|
||||
else:
|
||||
count = ''
|
||||
args = self._build_exec_cmd([self._play_context.executable, "-c", "dd of=%s bs=%s%s" % (out_path, BUFSIZE, count)])
|
||||
args = [to_bytes(i, errors='surrogate_or_strict') for i in args]
|
||||
try:
|
||||
p = subprocess.Popen(args, stdin=in_file,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
except OSError:
|
||||
raise AnsibleError("docker connection requires dd command in the container to put files")
|
||||
stdout, stderr = p.communicate()
|
||||
|
||||
if p.returncode != 0:
|
||||
raise AnsibleError("failed to transfer file %s to %s:\n%s\n%s" %
|
||||
(to_native(in_path), to_native(out_path), to_native(stdout), to_native(stderr)))
|
||||
|
||||
def fetch_file(self, in_path, out_path):
|
||||
""" Fetch a file from container to local. """
|
||||
super(Connection, self).fetch_file(in_path, out_path)
|
||||
display.vvv("FETCH %s TO %s" % (in_path, out_path), host=self._play_context.remote_addr)
|
||||
|
||||
in_path = self._prefix_login_path(in_path)
|
||||
# out_path is the final file path, but docker takes a directory, not a
|
||||
# file path
|
||||
out_dir = os.path.dirname(out_path)
|
||||
|
||||
args = [self.docker_cmd, "cp", "%s:%s" % (self._play_context.remote_addr, in_path), out_dir]
|
||||
args = [to_bytes(i, errors='surrogate_or_strict') for i in args]
|
||||
|
||||
p = subprocess.Popen(args, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
p.communicate()
|
||||
|
||||
if getattr(self._shell, "_IS_WINDOWS", False):
|
||||
import ntpath
|
||||
actual_out_path = ntpath.join(out_dir, ntpath.basename(in_path))
|
||||
else:
|
||||
actual_out_path = os.path.join(out_dir, os.path.basename(in_path))
|
||||
|
||||
if p.returncode != 0:
|
||||
# Older docker doesn't have native support for fetching files command `cp`
|
||||
# If `cp` fails, try to use `dd` instead
|
||||
args = self._build_exec_cmd([self._play_context.executable, "-c", "dd if=%s bs=%s" % (in_path, BUFSIZE)])
|
||||
args = [to_bytes(i, errors='surrogate_or_strict') for i in args]
|
||||
with open(to_bytes(actual_out_path, errors='surrogate_or_strict'), 'wb') as out_file:
|
||||
try:
|
||||
p = subprocess.Popen(args, stdin=subprocess.PIPE,
|
||||
stdout=out_file, stderr=subprocess.PIPE)
|
||||
except OSError:
|
||||
raise AnsibleError("docker connection requires dd command in the container to put files")
|
||||
stdout, stderr = p.communicate()
|
||||
|
||||
if p.returncode != 0:
|
||||
raise AnsibleError("failed to fetch file %s to %s:\n%s\n%s" % (in_path, out_path, stdout, stderr))
|
||||
|
||||
# Rename if needed
|
||||
if actual_out_path != out_path:
|
||||
os.rename(to_bytes(actual_out_path, errors='strict'), to_bytes(out_path, errors='strict'))
|
||||
|
||||
def close(self):
|
||||
""" Terminate the connection. Nothing to do for Docker"""
|
||||
super(Connection, self).close()
|
||||
self._connected = False
|
||||
@@ -9,7 +9,7 @@ __metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
author: Michael Scherer (@msherer) <misc@zarb.org>
|
||||
name: funcd
|
||||
connection: funcd
|
||||
short_description: Use funcd to connect to target
|
||||
description:
|
||||
- This transport permits you to use Ansible over Func.
|
||||
@@ -37,14 +37,13 @@ import tempfile
|
||||
import shutil
|
||||
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.plugins.connection import ConnectionBase
|
||||
from ansible.utils.display import Display
|
||||
|
||||
display = Display()
|
||||
|
||||
|
||||
class Connection(ConnectionBase):
|
||||
""" Func-based connections """
|
||||
class Connection(object):
|
||||
''' Func-based connections '''
|
||||
|
||||
has_pipelining = False
|
||||
|
||||
@@ -53,7 +52,6 @@ class Connection(ConnectionBase):
|
||||
self.host = host
|
||||
# port is unused, this go on func
|
||||
self.port = port
|
||||
self.client = None
|
||||
|
||||
def connect(self, port=None):
|
||||
if not HAVE_FUNC:
|
||||
@@ -63,32 +61,31 @@ class Connection(ConnectionBase):
|
||||
return self
|
||||
|
||||
def exec_command(self, cmd, become_user=None, sudoable=False, executable='/bin/sh', in_data=None):
|
||||
""" run a command on the remote minion """
|
||||
''' run a command on the remote minion '''
|
||||
|
||||
if in_data:
|
||||
raise AnsibleError("Internal Error: this module does not support optimized module pipelining")
|
||||
|
||||
# totally ignores privlege escalation
|
||||
display.vvv("EXEC %s" % cmd, host=self.host)
|
||||
display.vvv("EXEC %s" % (cmd), host=self.host)
|
||||
p = self.client.command.run(cmd)[self.host]
|
||||
return p[0], p[1], p[2]
|
||||
return (p[0], p[1], p[2])
|
||||
|
||||
@staticmethod
|
||||
def _normalize_path(path, prefix):
|
||||
def _normalize_path(self, path, prefix):
|
||||
if not path.startswith(os.path.sep):
|
||||
path = os.path.join(os.path.sep, path)
|
||||
normpath = os.path.normpath(path)
|
||||
return os.path.join(prefix, normpath[1:])
|
||||
|
||||
def put_file(self, in_path, out_path):
|
||||
""" transfer a file from local to remote """
|
||||
''' transfer a file from local to remote '''
|
||||
|
||||
out_path = self._normalize_path(out_path, '/')
|
||||
display.vvv("PUT %s TO %s" % (in_path, out_path), host=self.host)
|
||||
self.client.local.copyfile.send(in_path, out_path)
|
||||
|
||||
def fetch_file(self, in_path, out_path):
|
||||
""" fetch a file from remote to local """
|
||||
''' fetch a file from remote to local '''
|
||||
|
||||
in_path = self._normalize_path(in_path, '/')
|
||||
display.vvv("FETCH %s TO %s" % (in_path, out_path), host=self.host)
|
||||
@@ -101,5 +98,5 @@ class Connection(ConnectionBase):
|
||||
shutil.rmtree(tmpdir)
|
||||
|
||||
def close(self):
|
||||
""" terminate the connection; nothing to do here """
|
||||
''' terminate the connection; nothing to do here '''
|
||||
pass
|
||||
|
||||
@@ -9,8 +9,8 @@ from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
author: Stephan Lohse (!UNKNOWN) <dev-github@ploek.org>
|
||||
name: iocage
|
||||
author: Stephan Lohse <dev-github@ploek.org>
|
||||
connection: iocage
|
||||
short_description: Run tasks in iocage jails
|
||||
description:
|
||||
- Run commands or put/fetch files to an existing iocage jail
|
||||
@@ -32,7 +32,7 @@ DOCUMENTATION = '''
|
||||
import subprocess
|
||||
|
||||
from ansible_collections.community.general.plugins.connection.jail import Connection as Jail
|
||||
from ansible.module_utils.common.text.converters import to_native
|
||||
from ansible.module_utils._text import to_native
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.utils.display import Display
|
||||
|
||||
@@ -40,7 +40,7 @@ display = Display()
|
||||
|
||||
|
||||
class Connection(Jail):
|
||||
""" Local iocage based connections """
|
||||
''' Local iocage based connections '''
|
||||
|
||||
transport = 'community.general.iocage'
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ __metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
author: Ansible Core Team
|
||||
name: jail
|
||||
connection: jail
|
||||
short_description: Run tasks in jails
|
||||
description:
|
||||
- Run commands or put/fetch files to an existing jail
|
||||
@@ -35,10 +35,11 @@ import os
|
||||
import os.path
|
||||
import subprocess
|
||||
import traceback
|
||||
import ansible.constants as C
|
||||
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.module_utils.six.moves import shlex_quote
|
||||
from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
|
||||
from ansible.module_utils._text import to_bytes, to_native, to_text
|
||||
from ansible.plugins.connection import ConnectionBase, BUFSIZE
|
||||
from ansible.utils.display import Display
|
||||
|
||||
@@ -46,7 +47,7 @@ display = Display()
|
||||
|
||||
|
||||
class Connection(ConnectionBase):
|
||||
""" Local BSD Jail based connections """
|
||||
''' Local BSD Jail based connections '''
|
||||
|
||||
modified_jailname_key = 'conn_jail_name'
|
||||
|
||||
@@ -89,20 +90,20 @@ class Connection(ConnectionBase):
|
||||
return to_text(stdout, errors='surrogate_or_strict').split()
|
||||
|
||||
def _connect(self):
|
||||
""" connect to the jail; nothing to do here """
|
||||
''' connect to the jail; nothing to do here '''
|
||||
super(Connection, self)._connect()
|
||||
if not self._connected:
|
||||
display.vvv(u"ESTABLISH JAIL CONNECTION FOR USER: {0}".format(self._play_context.remote_user), host=self.jail)
|
||||
self._connected = True
|
||||
|
||||
def _buffered_exec_command(self, cmd, stdin=subprocess.PIPE):
|
||||
""" run a command on the jail. This is only needed for implementing
|
||||
''' run a command on the jail. This is only needed for implementing
|
||||
put_file() get_file() so that we don't have to read the whole file
|
||||
into memory.
|
||||
|
||||
compared to exec_command() it looses some niceties like being able to
|
||||
return the process's exit code immediately.
|
||||
"""
|
||||
'''
|
||||
|
||||
local_cmd = [self.jexec_cmd]
|
||||
set_env = ''
|
||||
@@ -122,17 +123,16 @@ class Connection(ConnectionBase):
|
||||
return p
|
||||
|
||||
def exec_command(self, cmd, in_data=None, sudoable=False):
|
||||
""" run a command on the jail """
|
||||
''' run a command on the jail '''
|
||||
super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
|
||||
|
||||
p = self._buffered_exec_command(cmd)
|
||||
|
||||
stdout, stderr = p.communicate(in_data)
|
||||
return p.returncode, stdout, stderr
|
||||
return (p.returncode, stdout, stderr)
|
||||
|
||||
@staticmethod
|
||||
def _prefix_login_path(remote_path):
|
||||
""" Make sure that we put files into a standard path
|
||||
def _prefix_login_path(self, remote_path):
|
||||
''' Make sure that we put files into a standard path
|
||||
|
||||
If a path is relative, then we need to choose where to put it.
|
||||
ssh chooses $HOME but we aren't guaranteed that a home dir will
|
||||
@@ -140,13 +140,13 @@ class Connection(ConnectionBase):
|
||||
This also happens to be the former default.
|
||||
|
||||
Can revisit using $HOME instead if it's a problem
|
||||
"""
|
||||
'''
|
||||
if not remote_path.startswith(os.path.sep):
|
||||
remote_path = os.path.join(os.path.sep, remote_path)
|
||||
return os.path.normpath(remote_path)
|
||||
|
||||
def put_file(self, in_path, out_path):
|
||||
""" transfer a file from local to jail """
|
||||
''' transfer a file from local to jail '''
|
||||
super(Connection, self).put_file(in_path, out_path)
|
||||
display.vvv("PUT %s TO %s" % (in_path, out_path), host=self.jail)
|
||||
|
||||
@@ -172,7 +172,7 @@ class Connection(ConnectionBase):
|
||||
raise AnsibleError("file or module does not exist at: %s" % in_path)
|
||||
|
||||
def fetch_file(self, in_path, out_path):
|
||||
""" fetch a file from jail to local """
|
||||
''' fetch a file from jail to local '''
|
||||
super(Connection, self).fetch_file(in_path, out_path)
|
||||
display.vvv("FETCH %s TO %s" % (in_path, out_path), host=self.jail)
|
||||
|
||||
@@ -196,6 +196,6 @@ class Connection(ConnectionBase):
|
||||
raise AnsibleError("failed to transfer file %s to %s:\n%s\n%s" % (in_path, out_path, to_native(stdout), to_native(stderr)))
|
||||
|
||||
def close(self):
|
||||
""" terminate the connection; nothing to do here """
|
||||
''' terminate the connection; nothing to do here '''
|
||||
super(Connection, self).close()
|
||||
self._connected = False
|
||||
|
||||
@@ -6,8 +6,8 @@ from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
author: Joerg Thalheim (!UNKNOWN) <joerg@higgsboson.tk>
|
||||
name: lxc
|
||||
author: Joerg Thalheim <joerg@higgsboson.tk>
|
||||
connection: lxc
|
||||
short_description: Run tasks in lxc containers via lxc python library
|
||||
description:
|
||||
- Run commands or put/fetch files to an existing lxc container using lxc python library
|
||||
@@ -42,13 +42,14 @@ try:
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
from ansible import constants as C
|
||||
from ansible import errors
|
||||
from ansible.module_utils.common.text.converters import to_bytes, to_native
|
||||
from ansible.module_utils._text import to_bytes, to_native
|
||||
from ansible.plugins.connection import ConnectionBase
|
||||
|
||||
|
||||
class Connection(ConnectionBase):
|
||||
""" Local lxc based connections """
|
||||
''' Local lxc based connections '''
|
||||
|
||||
transport = 'community.general.lxc'
|
||||
has_pipelining = True
|
||||
@@ -61,7 +62,7 @@ class Connection(ConnectionBase):
|
||||
self.container = None
|
||||
|
||||
def _connect(self):
|
||||
""" connect to the lxc; nothing to do here """
|
||||
''' connect to the lxc; nothing to do here '''
|
||||
super(Connection, self)._connect()
|
||||
|
||||
if not HAS_LIBLXC:
|
||||
@@ -76,8 +77,7 @@ class Connection(ConnectionBase):
|
||||
if self.container.state == "STOPPED":
|
||||
raise errors.AnsibleError("%s is not running" % self.container_name)
|
||||
|
||||
@staticmethod
|
||||
def _communicate(pid, in_data, stdin, stdout, stderr):
|
||||
def _communicate(self, pid, in_data, stdin, stdout, stderr):
|
||||
buf = {stdout: [], stderr: []}
|
||||
read_fds = [stdout, stderr]
|
||||
if in_data:
|
||||
@@ -86,7 +86,7 @@ class Connection(ConnectionBase):
|
||||
write_fds = []
|
||||
while len(read_fds) > 0 or len(write_fds) > 0:
|
||||
try:
|
||||
ready_reads, ready_writes, dummy = select.select(read_fds, write_fds, [])
|
||||
ready_reads, ready_writes, _ = select.select(read_fds, write_fds, [])
|
||||
except select.error as e:
|
||||
if e.args[0] == errno.EINTR:
|
||||
continue
|
||||
@@ -111,7 +111,7 @@ class Connection(ConnectionBase):
|
||||
return fd
|
||||
|
||||
def exec_command(self, cmd, in_data=None, sudoable=False):
|
||||
""" run a command on the chroot """
|
||||
''' run a command on the chroot '''
|
||||
super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
|
||||
|
||||
# python2-lxc needs bytes. python3-lxc needs text.
|
||||
|
||||
@@ -6,15 +6,15 @@ from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
author: Matt Clay (@mattclay) <matt@mystile.com>
|
||||
name: lxd
|
||||
author: Matt Clay <matt@mystile.com>
|
||||
connection: lxd
|
||||
short_description: Run tasks in lxc containers via lxc CLI
|
||||
description:
|
||||
- Run commands or put/fetch files to an existing lxc container using lxc CLI
|
||||
options:
|
||||
remote_addr:
|
||||
description:
|
||||
- Container identifier.
|
||||
- Container identifier
|
||||
default: inventory_hostname
|
||||
vars:
|
||||
- name: ansible_host
|
||||
@@ -26,19 +26,6 @@ DOCUMENTATION = '''
|
||||
vars:
|
||||
- name: ansible_executable
|
||||
- name: ansible_lxd_executable
|
||||
remote:
|
||||
description:
|
||||
- Name of the LXD remote to use.
|
||||
default: local
|
||||
vars:
|
||||
- name: ansible_lxd_remote
|
||||
version_added: 2.0.0
|
||||
project:
|
||||
description:
|
||||
- Name of the LXD project to use.
|
||||
vars:
|
||||
- name: ansible_lxd_project
|
||||
version_added: 2.0.0
|
||||
'''
|
||||
|
||||
import os
|
||||
@@ -46,7 +33,7 @@ from distutils.spawn import find_executable
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
from ansible.errors import AnsibleError, AnsibleConnectionFailure, AnsibleFileNotFound
|
||||
from ansible.module_utils.common.text.converters import to_bytes, to_text
|
||||
from ansible.module_utils._text import to_bytes, to_text
|
||||
from ansible.plugins.connection import ConnectionBase
|
||||
|
||||
|
||||
@@ -83,15 +70,7 @@ class Connection(ConnectionBase):
|
||||
|
||||
self._display.vvv(u"EXEC {0}".format(cmd), host=self._host)
|
||||
|
||||
local_cmd = [self._lxc_cmd]
|
||||
if self.get_option("project"):
|
||||
local_cmd.extend(["--project", self.get_option("project")])
|
||||
local_cmd.extend([
|
||||
"exec",
|
||||
"%s:%s" % (self.get_option("remote"), self._host),
|
||||
"--",
|
||||
self._play_context.executable, "-c", cmd
|
||||
])
|
||||
local_cmd = [self._lxc_cmd, "exec", self._host, "--", self._play_context.executable, "-c", cmd]
|
||||
|
||||
local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]
|
||||
in_data = to_bytes(in_data, errors='surrogate_or_strict', nonstring='passthru')
|
||||
@@ -119,14 +98,7 @@ class Connection(ConnectionBase):
|
||||
if not os.path.isfile(to_bytes(in_path, errors='surrogate_or_strict')):
|
||||
raise AnsibleFileNotFound("input path is not a file: %s" % in_path)
|
||||
|
||||
local_cmd = [self._lxc_cmd]
|
||||
if self.get_option("project"):
|
||||
local_cmd.extend(["--project", self.get_option("project")])
|
||||
local_cmd.extend([
|
||||
"file", "push",
|
||||
in_path,
|
||||
"%s:%s/%s" % (self.get_option("remote"), self._host, out_path)
|
||||
])
|
||||
local_cmd = [self._lxc_cmd, "file", "push", in_path, self._host + "/" + out_path]
|
||||
|
||||
local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]
|
||||
|
||||
@@ -139,14 +111,7 @@ class Connection(ConnectionBase):
|
||||
|
||||
self._display.vvv(u"FETCH {0} TO {1}".format(in_path, out_path), host=self._host)
|
||||
|
||||
local_cmd = [self._lxc_cmd]
|
||||
if self.get_option("project"):
|
||||
local_cmd.extend(["--project", self.get_option("project")])
|
||||
local_cmd.extend([
|
||||
"file", "pull",
|
||||
"%s:%s/%s" % (self.get_option("remote"), self._host, in_path),
|
||||
out_path
|
||||
])
|
||||
local_cmd = [self._lxc_cmd, "file", "pull", self._host + "/" + in_path, out_path]
|
||||
|
||||
local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]
|
||||
|
||||
|
||||
173
plugins/connection/oc.py
Normal file
173
plugins/connection/oc.py
Normal file
@@ -0,0 +1,173 @@
|
||||
# Based on the docker connection plugin
|
||||
#
|
||||
# Connection plugin for configuring kubernetes containers with kubectl
|
||||
# (c) 2017, XuXinkun <xuxinkun@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/>.
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
author:
|
||||
- xuxinkun
|
||||
|
||||
connection: oc
|
||||
|
||||
short_description: Execute tasks in pods running on OpenShift.
|
||||
|
||||
description:
|
||||
- Use the oc exec command to run tasks in, or put/fetch files to, pods running on the OpenShift
|
||||
container platform.
|
||||
|
||||
|
||||
requirements:
|
||||
- oc (go binary)
|
||||
|
||||
options:
|
||||
oc_pod:
|
||||
description:
|
||||
- Pod name. Required when the host name does not match pod name.
|
||||
default: ''
|
||||
vars:
|
||||
- name: ansible_oc_pod
|
||||
env:
|
||||
- name: K8S_AUTH_POD
|
||||
oc_container:
|
||||
description:
|
||||
- Container name. Required when a pod contains more than one container.
|
||||
default: ''
|
||||
vars:
|
||||
- name: ansible_oc_container
|
||||
env:
|
||||
- name: K8S_AUTH_CONTAINER
|
||||
oc_namespace:
|
||||
description:
|
||||
- The namespace of the pod
|
||||
default: ''
|
||||
vars:
|
||||
- name: ansible_oc_namespace
|
||||
env:
|
||||
- name: K8S_AUTH_NAMESPACE
|
||||
oc_extra_args:
|
||||
description:
|
||||
- Extra arguments to pass to the oc command line.
|
||||
default: ''
|
||||
vars:
|
||||
- name: ansible_oc_extra_args
|
||||
env:
|
||||
- name: K8S_AUTH_EXTRA_ARGS
|
||||
oc_kubeconfig:
|
||||
description:
|
||||
- Path to a oc config file. Defaults to I(~/.kube/conig)
|
||||
default: ''
|
||||
vars:
|
||||
- name: ansible_oc_kubeconfig
|
||||
- name: ansible_oc_config
|
||||
env:
|
||||
- name: K8S_AUTH_KUBECONFIG
|
||||
oc_context:
|
||||
description:
|
||||
- The name of a context found in the K8s config file.
|
||||
default: ''
|
||||
vars:
|
||||
- name: ansible_oc_context
|
||||
env:
|
||||
- name: K8S_AUTH_CONTEXT
|
||||
oc_host:
|
||||
description:
|
||||
- URL for accessing the API.
|
||||
default: ''
|
||||
vars:
|
||||
- name: ansible_oc_host
|
||||
- name: ansible_oc_server
|
||||
env:
|
||||
- name: K8S_AUTH_HOST
|
||||
- name: K8S_AUTH_SERVER
|
||||
oc_token:
|
||||
description:
|
||||
- API authentication bearer token.
|
||||
vars:
|
||||
- name: ansible_oc_token
|
||||
- name: ansible_oc_api_key
|
||||
env:
|
||||
- name: K8S_AUTH_TOKEN
|
||||
- name: K8S_AUTH_API_KEY
|
||||
client_cert:
|
||||
description:
|
||||
- Path to a certificate used to authenticate with the API.
|
||||
default: ''
|
||||
vars:
|
||||
- name: ansible_oc_cert_file
|
||||
- name: ansible_oc_client_cert
|
||||
env:
|
||||
- name: K8S_AUTH_CERT_FILE
|
||||
aliases: [ oc_cert_file ]
|
||||
client_key:
|
||||
description:
|
||||
- Path to a key file used to authenticate with the API.
|
||||
default: ''
|
||||
vars:
|
||||
- name: ansible_oc_key_file
|
||||
- name: ansible_oc_client_key
|
||||
env:
|
||||
- name: K8S_AUTH_KEY_FILE
|
||||
aliases: [ oc_key_file ]
|
||||
ca_cert:
|
||||
description:
|
||||
- Path to a CA certificate used to authenticate with the API.
|
||||
default: ''
|
||||
vars:
|
||||
- name: ansible_oc_ssl_ca_cert
|
||||
- name: ansible_oc_ca_cert
|
||||
env:
|
||||
- name: K8S_AUTH_SSL_CA_CERT
|
||||
aliases: [ oc_ssl_ca_cert ]
|
||||
validate_certs:
|
||||
description:
|
||||
- Whether or not to verify the API server's SSL certificate. Defaults to I(true).
|
||||
default: ''
|
||||
vars:
|
||||
- name: ansible_oc_verify_ssl
|
||||
- name: ansible_oc_validate_certs
|
||||
env:
|
||||
- name: K8S_AUTH_VERIFY_SSL
|
||||
aliases: [ oc_verify_ssl ]
|
||||
'''
|
||||
|
||||
from ansible_collections.community.kubernetes.plugins.connection.kubectl import Connection as KubectlConnection
|
||||
|
||||
|
||||
CONNECTION_TRANSPORT = 'oc'
|
||||
|
||||
CONNECTION_OPTIONS = {
|
||||
'oc_container': '-c',
|
||||
'oc_namespace': '-n',
|
||||
'oc_kubeconfig': '--config',
|
||||
'oc_context': '--context',
|
||||
'oc_host': '--server',
|
||||
'client_cert': '--client-certificate',
|
||||
'client_key': '--client-key',
|
||||
'ca_cert': '--certificate-authority',
|
||||
'validate_certs': '--insecure-skip-tls-verify',
|
||||
'oc_token': '--token'
|
||||
}
|
||||
|
||||
|
||||
class Connection(KubectlConnection):
|
||||
''' Local oc based connections '''
|
||||
transport = CONNECTION_TRANSPORT
|
||||
connection_options = CONNECTION_OPTIONS
|
||||
documentation = DOCUMENTATION
|
||||
@@ -11,7 +11,7 @@ __metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: qubes
|
||||
connection: qubes
|
||||
short_description: Interact with an existing QubesOS AppVM
|
||||
|
||||
description:
|
||||
@@ -37,9 +37,15 @@ DOCUMENTATION = '''
|
||||
# - name: hosts
|
||||
'''
|
||||
|
||||
import shlex
|
||||
import shutil
|
||||
|
||||
import os
|
||||
import base64
|
||||
import subprocess
|
||||
|
||||
from ansible.module_utils.common.text.converters import to_bytes
|
||||
import ansible.constants as C
|
||||
from ansible.module_utils._text import to_bytes, to_native
|
||||
from ansible.plugins.connection import ConnectionBase, ensure_connect
|
||||
from ansible.errors import AnsibleConnectionFailure
|
||||
from ansible.utils.display import Display
|
||||
|
||||
@@ -10,17 +10,19 @@ __metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
author: Michael Scherer (@mscherer) <misc@zarb.org>
|
||||
name: saltstack
|
||||
connection: saltstack
|
||||
short_description: Allow ansible to piggyback on salt minions
|
||||
description:
|
||||
- This allows you to use existing Saltstack infrastructure to connect to targets.
|
||||
'''
|
||||
|
||||
import re
|
||||
import os
|
||||
import base64
|
||||
import pty
|
||||
import subprocess
|
||||
|
||||
from ansible import errors
|
||||
from ansible.plugins.connection import ConnectionBase
|
||||
from ansible.module_utils._text import to_bytes, to_text
|
||||
from ansible.module_utils.six.moves import cPickle
|
||||
|
||||
HAVE_SALTSTACK = False
|
||||
try:
|
||||
@@ -29,9 +31,13 @@ try:
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
import os
|
||||
from ansible import errors
|
||||
from ansible.plugins.connection import ConnectionBase
|
||||
|
||||
|
||||
class Connection(ConnectionBase):
|
||||
""" Salt-based connections """
|
||||
''' Salt-based connections '''
|
||||
|
||||
has_pipelining = False
|
||||
# while the name of the product is salt, naming that module salt cause
|
||||
@@ -51,42 +57,41 @@ class Connection(ConnectionBase):
|
||||
return self
|
||||
|
||||
def exec_command(self, cmd, sudoable=False, in_data=None):
|
||||
""" run a command on the remote minion """
|
||||
''' run a command on the remote minion '''
|
||||
super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
|
||||
|
||||
if in_data:
|
||||
raise errors.AnsibleError("Internal Error: this module does not support optimized module pipelining")
|
||||
|
||||
self._display.vvv("EXEC %s" % cmd, host=self.host)
|
||||
self._display.vvv("EXEC %s" % (cmd), host=self.host)
|
||||
# need to add 'true;' to work around https://github.com/saltstack/salt/issues/28077
|
||||
res = self.client.cmd(self.host, 'cmd.exec_code_all', ['bash', 'true;' + cmd])
|
||||
if self.host not in res:
|
||||
raise errors.AnsibleError("Minion %s didn't answer, check if salt-minion is running and the name is correct" % self.host)
|
||||
|
||||
p = res[self.host]
|
||||
return p['retcode'], p['stdout'], p['stderr']
|
||||
return (p['retcode'], p['stdout'], p['stderr'])
|
||||
|
||||
@staticmethod
|
||||
def _normalize_path(path, prefix):
|
||||
def _normalize_path(self, path, prefix):
|
||||
if not path.startswith(os.path.sep):
|
||||
path = os.path.join(os.path.sep, path)
|
||||
normpath = os.path.normpath(path)
|
||||
return os.path.join(prefix, normpath[1:])
|
||||
|
||||
def put_file(self, in_path, out_path):
|
||||
""" transfer a file from local to remote """
|
||||
''' transfer a file from local to remote '''
|
||||
|
||||
super(Connection, self).put_file(in_path, out_path)
|
||||
|
||||
out_path = self._normalize_path(out_path, '/')
|
||||
self._display.vvv("PUT %s TO %s" % (in_path, out_path), host=self.host)
|
||||
with open(in_path, 'rb') as in_fh:
|
||||
with open(in_path) as in_fh:
|
||||
content = in_fh.read()
|
||||
self.client.cmd(self.host, 'hashutil.base64_decodefile', [base64.b64encode(content), out_path])
|
||||
self.client.cmd(self.host, 'file.write', [out_path, content])
|
||||
|
||||
# TODO test it
|
||||
def fetch_file(self, in_path, out_path):
|
||||
""" fetch a file from remote to local """
|
||||
''' fetch a file from remote to local '''
|
||||
|
||||
super(Connection, self).fetch_file(in_path, out_path)
|
||||
|
||||
@@ -96,5 +101,5 @@ class Connection(ConnectionBase):
|
||||
open(out_path, 'wb').write(content)
|
||||
|
||||
def close(self):
|
||||
""" terminate the connection; nothing to do here """
|
||||
''' terminate the connection; nothing to do here '''
|
||||
pass
|
||||
|
||||
@@ -11,7 +11,7 @@ __metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
author: Ansible Core Team
|
||||
name: zone
|
||||
connection: zone
|
||||
short_description: Run tasks in a zone instance
|
||||
description:
|
||||
- Run commands or put/fetch files to an existing zone
|
||||
@@ -31,9 +31,10 @@ import os.path
|
||||
import subprocess
|
||||
import traceback
|
||||
|
||||
from ansible import constants as C
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.module_utils.six.moves import shlex_quote
|
||||
from ansible.module_utils.common.text.converters import to_bytes
|
||||
from ansible.module_utils._text import to_bytes
|
||||
from ansible.plugins.connection import ConnectionBase, BUFSIZE
|
||||
from ansible.utils.display import Display
|
||||
|
||||
@@ -41,7 +42,7 @@ display = Display()
|
||||
|
||||
|
||||
class Connection(ConnectionBase):
|
||||
""" Local zone based connections """
|
||||
''' Local zone based connections '''
|
||||
|
||||
transport = 'community.general.zone'
|
||||
has_pipelining = True
|
||||
@@ -74,9 +75,9 @@ class Connection(ConnectionBase):
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
zones = []
|
||||
for line in process.stdout.readlines():
|
||||
for l in process.stdout.readlines():
|
||||
# 1:work:running:/zones/work:3126dc59-9a07-4829-cde9-a816e4c5040e:native:shared
|
||||
s = line.split(':')
|
||||
s = l.split(':')
|
||||
if s[1] != 'global':
|
||||
zones.append(s[1])
|
||||
|
||||
@@ -94,20 +95,20 @@ class Connection(ConnectionBase):
|
||||
return path + '/root'
|
||||
|
||||
def _connect(self):
|
||||
""" connect to the zone; nothing to do here """
|
||||
''' connect to the zone; nothing to do here '''
|
||||
super(Connection, self)._connect()
|
||||
if not self._connected:
|
||||
display.vvv("THIS IS A LOCAL ZONE DIR", host=self.zone)
|
||||
self._connected = True
|
||||
|
||||
def _buffered_exec_command(self, cmd, stdin=subprocess.PIPE):
|
||||
""" run a command on the zone. This is only needed for implementing
|
||||
''' run a command on the zone. This is only needed for implementing
|
||||
put_file() get_file() so that we don't have to read the whole file
|
||||
into memory.
|
||||
|
||||
compared to exec_command() it looses some niceties like being able to
|
||||
return the process's exit code immediately.
|
||||
"""
|
||||
'''
|
||||
# NOTE: zlogin invokes a shell (just like ssh does) so we do not pass
|
||||
# this through /bin/sh -c here. Instead it goes through the shell
|
||||
# that zlogin selects.
|
||||
@@ -121,16 +122,16 @@ class Connection(ConnectionBase):
|
||||
return p
|
||||
|
||||
def exec_command(self, cmd, in_data=None, sudoable=False):
|
||||
""" run a command on the zone """
|
||||
''' run a command on the zone '''
|
||||
super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
|
||||
|
||||
p = self._buffered_exec_command(cmd)
|
||||
|
||||
stdout, stderr = p.communicate(in_data)
|
||||
return p.returncode, stdout, stderr
|
||||
return (p.returncode, stdout, stderr)
|
||||
|
||||
def _prefix_login_path(self, remote_path):
|
||||
""" Make sure that we put files into a standard path
|
||||
''' Make sure that we put files into a standard path
|
||||
|
||||
If a path is relative, then we need to choose where to put it.
|
||||
ssh chooses $HOME but we aren't guaranteed that a home dir will
|
||||
@@ -138,13 +139,13 @@ class Connection(ConnectionBase):
|
||||
This also happens to be the former default.
|
||||
|
||||
Can revisit using $HOME instead if it's a problem
|
||||
"""
|
||||
'''
|
||||
if not remote_path.startswith(os.path.sep):
|
||||
remote_path = os.path.join(os.path.sep, remote_path)
|
||||
return os.path.normpath(remote_path)
|
||||
|
||||
def put_file(self, in_path, out_path):
|
||||
""" transfer a file from local to zone """
|
||||
''' transfer a file from local to zone '''
|
||||
super(Connection, self).put_file(in_path, out_path)
|
||||
display.vvv("PUT %s TO %s" % (in_path, out_path), host=self.zone)
|
||||
|
||||
@@ -170,7 +171,7 @@ class Connection(ConnectionBase):
|
||||
raise AnsibleError("file or module does not exist at: %s" % in_path)
|
||||
|
||||
def fetch_file(self, in_path, out_path):
|
||||
""" fetch a file from zone to local """
|
||||
''' fetch a file from zone to local '''
|
||||
super(Connection, self).fetch_file(in_path, out_path)
|
||||
display.vvv("FETCH %s TO %s" % (in_path, out_path), host=self.zone)
|
||||
|
||||
@@ -194,6 +195,6 @@ class Connection(ConnectionBase):
|
||||
raise AnsibleError("failed to transfer file %s to %s:\n%s\n%s" % (in_path, out_path, stdout, stderr))
|
||||
|
||||
def close(self):
|
||||
""" terminate the connection; nothing to do here """
|
||||
''' terminate the connection; nothing to do here '''
|
||||
super(Connection, self).close()
|
||||
self._connected = False
|
||||
|
||||
62
plugins/doc_fragments/_gcp.py
Normal file
62
plugins/doc_fragments/_gcp.py
Normal file
@@ -0,0 +1,62 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2018, Google Inc.
|
||||
# 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):
|
||||
# GCP doc fragment.
|
||||
DOCUMENTATION = r'''
|
||||
options:
|
||||
project:
|
||||
description:
|
||||
- The Google Cloud Platform project to use.
|
||||
type: str
|
||||
auth_kind:
|
||||
description:
|
||||
- The type of credential used.
|
||||
type: str
|
||||
required: true
|
||||
choices: [ application, machineaccount, serviceaccount ]
|
||||
service_account_contents:
|
||||
description:
|
||||
- The contents of a Service Account JSON file, either in a dictionary or as a JSON string that represents it.
|
||||
type: jsonarg
|
||||
service_account_file:
|
||||
description:
|
||||
- The path of a Service Account JSON file if serviceaccount is selected as type.
|
||||
type: path
|
||||
service_account_email:
|
||||
description:
|
||||
- An optional service account email address if machineaccount is selected
|
||||
and the user does not wish to use the default email.
|
||||
type: str
|
||||
scopes:
|
||||
description:
|
||||
- Array of scopes to be used.
|
||||
type: list
|
||||
elements: str
|
||||
env_type:
|
||||
description:
|
||||
- Specifies which Ansible environment you're running this module within.
|
||||
- This should not be set unless you know what you're doing.
|
||||
- This only alters the User Agent string for any API requests.
|
||||
type: str
|
||||
notes:
|
||||
- for authentication, you can set service_account_file using the
|
||||
c(gcp_service_account_file) env variable.
|
||||
- for authentication, you can set service_account_contents using the
|
||||
c(GCP_SERVICE_ACCOUNT_CONTENTS) env variable.
|
||||
- For authentication, you can set service_account_email using the
|
||||
C(GCP_SERVICE_ACCOUNT_EMAIL) env variable.
|
||||
- For authentication, you can set auth_kind using the C(GCP_AUTH_KIND) env
|
||||
variable.
|
||||
- For authentication, you can set scopes using the C(GCP_SCOPES) env variable.
|
||||
- Environment variables values will only be used if the playbook values are
|
||||
not set.
|
||||
- The I(service_account_email) and I(service_account_file) options are
|
||||
mutually exclusive.
|
||||
'''
|
||||
33
plugins/doc_fragments/digital_ocean.py
Normal file
33
plugins/doc_fragments/digital_ocean.py
Normal file
@@ -0,0 +1,33 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2018, Ansible Project
|
||||
# Copyright: (c) 2018, Abhijeet Kasurde (akasurde@redhat.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
|
||||
|
||||
|
||||
class ModuleDocFragment(object):
|
||||
# Parameters for DigitalOcean modules
|
||||
DOCUMENTATION = r'''
|
||||
options:
|
||||
oauth_token:
|
||||
description:
|
||||
- DigitalOcean OAuth token.
|
||||
- "There are several other environment variables which can be used to provide this value."
|
||||
- "i.e., - 'DO_API_TOKEN', 'DO_API_KEY', 'DO_OAUTH_TOKEN' and 'OAUTH_TOKEN'"
|
||||
type: str
|
||||
aliases: [ api_token ]
|
||||
timeout:
|
||||
description:
|
||||
- The timeout in seconds used for polling DigitalOcean's API.
|
||||
type: int
|
||||
default: 30
|
||||
validate_certs:
|
||||
description:
|
||||
- If set to C(no), the SSL certificates will not be validated.
|
||||
- This should only set to C(no) used on personally controlled sites using self-signed certificates.
|
||||
type: bool
|
||||
default: yes
|
||||
'''
|
||||
@@ -19,6 +19,7 @@ options:
|
||||
region:
|
||||
description:
|
||||
- The target region.
|
||||
choices:
|
||||
- Regions are defined in Apache libcloud project [libcloud/common/dimensiondata.py]
|
||||
- They are also listed in U(https://libcloud.readthedocs.io/en/latest/compute/drivers/dimensiondata.html)
|
||||
- Note that the default value "na" stands for "North America".
|
||||
|
||||
136
plugins/doc_fragments/docker.py
Normal file
136
plugins/doc_fragments/docker.py
Normal file
@@ -0,0 +1,136 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# 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):
|
||||
|
||||
# Docker doc fragment
|
||||
DOCUMENTATION = r'''
|
||||
|
||||
options:
|
||||
docker_host:
|
||||
description:
|
||||
- The URL or Unix socket path used to connect to the Docker API. To connect to a remote host, provide the
|
||||
TCP connection string. For example, C(tcp://192.0.2.23:2376). If TLS is used to encrypt the connection,
|
||||
the module will automatically replace C(tcp) in the connection URL with C(https).
|
||||
- If the value is not specified in the task, the value of environment variable C(DOCKER_HOST) will be used
|
||||
instead. If the environment variable is not set, the default value will be used.
|
||||
type: str
|
||||
default: unix://var/run/docker.sock
|
||||
aliases: [ docker_url ]
|
||||
tls_hostname:
|
||||
description:
|
||||
- When verifying the authenticity of the Docker Host server, provide the expected name of the server.
|
||||
- If the value is not specified in the task, the value of environment variable C(DOCKER_TLS_HOSTNAME) will
|
||||
be used instead. If the environment variable is not set, the default value will be used.
|
||||
type: str
|
||||
default: localhost
|
||||
api_version:
|
||||
description:
|
||||
- The version of the Docker API running on the Docker Host.
|
||||
- Defaults to the latest version of the API supported by Docker SDK for Python and the docker daemon.
|
||||
- If the value is not specified in the task, the value of environment variable C(DOCKER_API_VERSION) will be
|
||||
used instead. If the environment variable is not set, the default value will be used.
|
||||
type: str
|
||||
default: auto
|
||||
aliases: [ docker_api_version ]
|
||||
timeout:
|
||||
description:
|
||||
- The maximum amount of time in seconds to wait on a response from the API.
|
||||
- If the value is not specified in the task, the value of environment variable C(DOCKER_TIMEOUT) will be used
|
||||
instead. If the environment variable is not set, the default value will be used.
|
||||
type: int
|
||||
default: 60
|
||||
ca_cert:
|
||||
description:
|
||||
- Use a CA certificate when performing server verification by providing the path to a CA certificate file.
|
||||
- If the value is not specified in the task and the environment variable C(DOCKER_CERT_PATH) is set,
|
||||
the file C(ca.pem) from the directory specified in the environment variable C(DOCKER_CERT_PATH) will be used.
|
||||
type: path
|
||||
aliases: [ tls_ca_cert, cacert_path ]
|
||||
client_cert:
|
||||
description:
|
||||
- Path to the client's TLS certificate file.
|
||||
- If the value is not specified in the task and the environment variable C(DOCKER_CERT_PATH) is set,
|
||||
the file C(cert.pem) from the directory specified in the environment variable C(DOCKER_CERT_PATH) will be used.
|
||||
type: path
|
||||
aliases: [ tls_client_cert, cert_path ]
|
||||
client_key:
|
||||
description:
|
||||
- Path to the client's TLS key file.
|
||||
- If the value is not specified in the task and the environment variable C(DOCKER_CERT_PATH) is set,
|
||||
the file C(key.pem) from the directory specified in the environment variable C(DOCKER_CERT_PATH) will be used.
|
||||
type: path
|
||||
aliases: [ tls_client_key, key_path ]
|
||||
ssl_version:
|
||||
description:
|
||||
- Provide a valid SSL version number. Default value determined by ssl.py module.
|
||||
- If the value is not specified in the task, the value of environment variable C(DOCKER_SSL_VERSION) will be
|
||||
used instead.
|
||||
type: str
|
||||
tls:
|
||||
description:
|
||||
- Secure the connection to the API by using TLS without verifying the authenticity of the Docker host
|
||||
server. Note that if I(validate_certs) is set to C(yes) as well, it will take precedence.
|
||||
- If the value is not specified in the task, the value of environment variable C(DOCKER_TLS) will be used
|
||||
instead. If the environment variable is not set, the default value will be used.
|
||||
type: bool
|
||||
default: no
|
||||
validate_certs:
|
||||
description:
|
||||
- Secure the connection to the API by using TLS and verifying the authenticity of the Docker host server.
|
||||
- If the value is not specified in the task, the value of environment variable C(DOCKER_TLS_VERIFY) will be
|
||||
used instead. If the environment variable is not set, the default value will be used.
|
||||
type: bool
|
||||
default: no
|
||||
aliases: [ tls_verify ]
|
||||
debug:
|
||||
description:
|
||||
- Debug mode
|
||||
type: bool
|
||||
default: no
|
||||
|
||||
notes:
|
||||
- Connect to the Docker daemon by providing parameters with each task or by defining environment variables.
|
||||
You can define C(DOCKER_HOST), C(DOCKER_TLS_HOSTNAME), C(DOCKER_API_VERSION), C(DOCKER_CERT_PATH), C(DOCKER_SSL_VERSION),
|
||||
C(DOCKER_TLS), C(DOCKER_TLS_VERIFY) and C(DOCKER_TIMEOUT). If you are using docker machine, run the script shipped
|
||||
with the product that sets up the environment. It will set these variables for you. See
|
||||
U(https://docs.docker.com/machine/reference/env/) for more details.
|
||||
- When connecting to Docker daemon with TLS, you might need to install additional Python packages.
|
||||
For the Docker SDK for Python, version 2.4 or newer, this can be done by installing C(docker[tls]) with M(ansible.builtin.pip).
|
||||
- Note that the Docker SDK for Python only allows to specify the path to the Docker configuration for very few functions.
|
||||
In general, it will use C($HOME/.docker/config.json) if the C(DOCKER_CONFIG) environment variable is not specified,
|
||||
and use C($DOCKER_CONFIG/config.json) otherwise.
|
||||
'''
|
||||
|
||||
# Additional, more specific stuff for minimal Docker SDK for Python version < 2.0
|
||||
|
||||
DOCKER_PY_1_DOCUMENTATION = r'''
|
||||
options: {}
|
||||
requirements:
|
||||
- "Docker SDK for Python: Please note that the L(docker-py,https://pypi.org/project/docker-py/)
|
||||
Python module has been superseded by L(docker,https://pypi.org/project/docker/)
|
||||
(see L(here,https://github.com/docker/docker-py/issues/1310) for details).
|
||||
For Python 2.6, C(docker-py) must be used. Otherwise, it is recommended to
|
||||
install the C(docker) Python module. Note that both modules should *not*
|
||||
be installed at the same time. Also note that when both modules are installed
|
||||
and one of them is uninstalled, the other might no longer function and a
|
||||
reinstall of it is required."
|
||||
'''
|
||||
|
||||
# Additional, more specific stuff for minimal Docker SDK for Python version >= 2.0.
|
||||
# Note that Docker SDK for Python >= 2.0 requires Python 2.7 or newer.
|
||||
|
||||
DOCKER_PY_2_DOCUMENTATION = r'''
|
||||
options: {}
|
||||
requirements:
|
||||
- "Python >= 2.7"
|
||||
- "Docker SDK for Python: Please note that the L(docker-py,https://pypi.org/project/docker-py/)
|
||||
Python module has been superseded by L(docker,https://pypi.org/project/docker/)
|
||||
(see L(here,https://github.com/docker/docker-py/issues/1310) for details).
|
||||
This module does *not* work with docker-py."
|
||||
'''
|
||||
23
plugins/doc_fragments/hetzner.py
Normal file
23
plugins/doc_fragments/hetzner.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2019 Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
class ModuleDocFragment(object):
|
||||
|
||||
# Standard files documentation fragment
|
||||
DOCUMENTATION = r'''
|
||||
options:
|
||||
hetzner_user:
|
||||
description: The username for the Robot webservice user.
|
||||
type: str
|
||||
required: yes
|
||||
hetzner_password:
|
||||
description: The password for the Robot webservice user.
|
||||
type: str
|
||||
required: yes
|
||||
'''
|
||||
37
plugins/doc_fragments/infinibox.py
Normal file
37
plugins/doc_fragments/infinibox.py
Normal file
@@ -0,0 +1,37 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2016, Gregory Shulov <gregory.shulov@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
|
||||
|
||||
|
||||
class ModuleDocFragment(object):
|
||||
|
||||
# Standard Infinibox documentation fragment
|
||||
DOCUMENTATION = r'''
|
||||
options:
|
||||
system:
|
||||
description:
|
||||
- Infinibox Hostname or IPv4 Address.
|
||||
type: str
|
||||
required: true
|
||||
user:
|
||||
description:
|
||||
- Infinibox User username with sufficient priveledges ( see notes ).
|
||||
required: false
|
||||
password:
|
||||
description:
|
||||
- Infinibox User password.
|
||||
type: str
|
||||
notes:
|
||||
- This module requires infinisdk python library
|
||||
- You must set INFINIBOX_USER and INFINIBOX_PASSWORD environment variables
|
||||
if user and password arguments are not passed to the module directly
|
||||
- Ansible uses the infinisdk configuration file C(~/.infinidat/infinisdk.ini) if no credentials are provided.
|
||||
See U(http://infinisdk.readthedocs.io/en/latest/getting_started.html)
|
||||
requirements:
|
||||
- "python >= 2.7"
|
||||
- infinisdk
|
||||
'''
|
||||
@@ -40,7 +40,6 @@ options:
|
||||
path:
|
||||
description:
|
||||
- The path on which InfluxDB server is accessible
|
||||
- Only available when using python-influxdb >= 5.1.0
|
||||
type: str
|
||||
version_added: '0.2.0'
|
||||
validate_certs:
|
||||
@@ -53,7 +52,6 @@ options:
|
||||
description:
|
||||
- Use https instead of http to connect to InfluxDB server.
|
||||
type: bool
|
||||
default: false
|
||||
timeout:
|
||||
description:
|
||||
- Number of seconds Requests will wait for client to establish a connection.
|
||||
@@ -62,14 +60,12 @@ options:
|
||||
description:
|
||||
- Number of retries client will try before aborting.
|
||||
- C(0) indicates try until success.
|
||||
- Only available when using python-influxdb >= 4.1.0
|
||||
type: int
|
||||
default: 3
|
||||
use_udp:
|
||||
description:
|
||||
- Use UDP to connect to InfluxDB server.
|
||||
type: bool
|
||||
default: false
|
||||
udp_port:
|
||||
description:
|
||||
- UDP port to connect to InfluxDB server.
|
||||
|
||||
@@ -30,6 +30,7 @@ options:
|
||||
description:
|
||||
- Keycloak realm name to authenticate to for API access.
|
||||
type: str
|
||||
required: true
|
||||
|
||||
auth_client_secret:
|
||||
description:
|
||||
@@ -40,6 +41,7 @@ options:
|
||||
description:
|
||||
- Username to authenticate for API access with.
|
||||
type: str
|
||||
required: true
|
||||
aliases:
|
||||
- username
|
||||
|
||||
@@ -47,15 +49,10 @@ options:
|
||||
description:
|
||||
- Password to authenticate for API access with.
|
||||
type: str
|
||||
required: true
|
||||
aliases:
|
||||
- password
|
||||
|
||||
token:
|
||||
description:
|
||||
- Authentication token for Keycloak API.
|
||||
type: str
|
||||
version_added: 3.0.0
|
||||
|
||||
validate_certs:
|
||||
description:
|
||||
- Verify TLS certificates (do not disable this in production).
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user