Compare commits

...

54 Commits
v1.1.0 ... main

Author SHA1 Message Date
0c6944bb1b
ci: fix gitea release plugin [skip ci]
All checks were successful
ci/woodpecker/tag/lint Pipeline was successful
ci/woodpecker/tag/unit-test Pipeline was successful
ci/woodpecker/tag/sanity-test Pipeline was successful
ci/woodpecker/tag/build-package Pipeline was successful
ci/woodpecker/tag/docs Pipeline was successful
ci/woodpecker/tag/notify Pipeline was successful
2024-03-26 13:44:24 +01:00
cc5a102211
fix: use get to access network key
Some checks failed
ci/woodpecker/push/lint Pipeline was successful
ci/woodpecker/push/unit-test Pipeline was successful
ci/woodpecker/push/sanity-test Pipeline was successful
ci/woodpecker/push/build-package Pipeline was successful
ci/woodpecker/push/docs Pipeline was successful
ci/woodpecker/push/notify Pipeline was successful
ci/woodpecker/tag/lint Pipeline was successful
ci/woodpecker/tag/unit-test Pipeline was successful
ci/woodpecker/tag/sanity-test Pipeline was successful
ci/woodpecker/tag/build-package Pipeline failed
ci/woodpecker/tag/docs unknown status
ci/woodpecker/tag/notify Pipeline was successful
2024-03-26 13:11:35 +01:00
1d7240fff6
cleanup ci syntax
All checks were successful
ci/woodpecker/push/lint Pipeline was successful
ci/woodpecker/push/unit-test Pipeline was successful
ci/woodpecker/push/sanity-test Pipeline was successful
ci/woodpecker/push/build-package Pipeline was successful
ci/woodpecker/push/docs Pipeline was successful
ci/woodpecker/push/notify Pipeline was successful
2024-01-16 14:30:08 +01:00
1acc16352b
remove unnecessary git fetch command from ci
All checks were successful
ci/woodpecker/push/lint Pipeline was successful
ci/woodpecker/push/unit-test Pipeline was successful
ci/woodpecker/push/sanity-test Pipeline was successful
ci/woodpecker/push/build-package Pipeline was successful
ci/woodpecker/push/docs Pipeline was successful
ci/woodpecker/push/notify Pipeline was successful
2024-01-04 21:56:25 +01:00
181dd5ce32 ci: cleanup env vars and fix linting (#8)
All checks were successful
ci/woodpecker/push/lint Pipeline was successful
ci/woodpecker/push/unit-test Pipeline was successful
ci/woodpecker/push/sanity-test Pipeline was successful
ci/woodpecker/push/build-package Pipeline was successful
ci/woodpecker/push/docs Pipeline was successful
ci/woodpecker/push/notify Pipeline was successful
Reviewed-on: #8
Co-authored-by: Robert Kaussow <mail@thegeeklab.de>
Co-committed-by: Robert Kaussow <mail@thegeeklab.de>
2023-12-15 13:05:48 +01:00
4331062148
cleanup readme
All checks were successful
ci/woodpecker/push/lint Pipeline was successful
ci/woodpecker/push/unit-test Pipeline was successful
ci/woodpecker/push/sanity-test Pipeline was successful
ci/woodpecker/push/build-package Pipeline was successful
ci/woodpecker/push/docs Pipeline was successful
ci/woodpecker/push/notify Pipeline was successful
2023-12-13 11:25:39 +01:00
fa92fd43ee ci: migrate to woodpecker ci (#7)
Some checks failed
ci/woodpecker/push/lint Pipeline was successful
ci/woodpecker/push/unit-test Pipeline was successful
ci/woodpecker/push/build-package unknown status
ci/woodpecker/push/docs unknown status
ci/woodpecker/push/sanity-test Pipeline failed
ci/woodpecker/push/notify Pipeline was successful
Reviewed-on: #7
Co-authored-by: Robert Kaussow <mail@thegeeklab.de>
Co-committed-by: Robert Kaussow <mail@thegeeklab.de>
2023-12-13 11:23:28 +01:00
8e11973364
temp remove git branch publishing step
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2023-07-30 22:48:23 +02:00
d37d217a78 ci: fix docs and create release branches (#6)
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is failing
Reviewed-on: #6
Co-authored-by: Robert Kaussow <mail@thegeeklab.de>
Co-committed-by: Robert Kaussow <mail@thegeeklab.de>
2023-07-30 13:05:35 +02:00
9226ab6209 feat: add hashivault_unseal module (#5)
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #5
Co-authored-by: Robert Kaussow <mail@thegeeklab.de>
Co-committed-by: Robert Kaussow <mail@thegeeklab.de>
2023-07-30 12:43:36 +02:00
14999b6f12
fix drone-matrix template
All checks were successful
continuous-integration/drone/push Build is passing
2023-02-08 21:27:04 +01:00
f8edc9269f fix: fix proxmox env var support for inventory plugin and kvm module (#4)
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2023-01-31 21:35:03 +01:00
dcf04af784 refactor: rework ci and testing (#3)
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2023-01-31 20:09:29 +01:00
579a9713c1
chore: switch to main as default branch
All checks were successful
continuous-integration/drone/push Build is passing
2022-06-20 22:22:58 +02:00
cd2e86094d
chore: end of the year maintenance
All checks were successful
continuous-integration/drone/push Build is passing
2021-12-21 11:01:30 +01:00
a3eb8800b0 chore: adjust changelog template to link to prs instead of issues (#2)
All checks were successful
continuous-integration/drone/push Build is passing
Co-authored-by: Robert Kaussow <xoxys@rknet.org>
Co-committed-by: Robert Kaussow <xoxys@rknet.org>
2021-12-12 13:50:04 +01:00
cf36ca664a
fix: avoid recreation of net mac addrs during VM update
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2021-12-06 21:12:30 +01:00
d1f6175719
BREAKING CHANGE: drop support for python 3.6 and 3.7
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2021-12-06 16:42:26 +01:00
22e6546396
fix: avoid recreation of storage devices during VM update
All checks were successful
continuous-integration/drone/push Build is passing
2021-12-05 21:47:23 +01:00
1c62ad049e
chore: remove docker-compose module
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2021-11-28 15:24:50 +01:00
32a0d83997
chore: replace ansible-base by new package ansible-core
Some checks reported errors
continuous-integration/drone/push Build was killed
2021-10-11 21:55:53 +02:00
05592c3dd1
improve changelog template
All checks were successful
continuous-integration/drone/push Build is passing
2021-09-22 11:47:47 +02:00
d65b4d90ae
sign drone config
All checks were successful
continuous-integration/drone/push Build is passing
2021-09-22 09:24:06 +02:00
85cd1071ca
re-generate drone config
Some checks are pending
continuous-integration/drone/push Build is pending
2021-09-22 09:19:30 +02:00
d941eb9150
ci: switch to drone-matrix plugin
All checks were successful
continuous-integration/drone/push Build is passing
2021-09-22 09:08:41 +02:00
31f657a3fa
cleanup descriptions
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2021-05-27 16:59:35 +02:00
855860da0a
apply upstream patches to proxmox_kvm
All checks were successful
continuous-integration/drone/push Build is passing
2021-05-27 16:58:09 +02:00
87180e6f5e
fix: fix idempotency in docker_compose
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2021-05-16 16:47:26 +02:00
3942150d2f
chore: add test requirements for docker compose module
All checks were successful
continuous-integration/drone/push Build is passing
2021-05-16 16:11:33 +02:00
f685450037
fix linting issues
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2021-05-16 15:24:15 +02:00
af571cfb7f
feat: add fork of docker-compose module
Some checks failed
continuous-integration/drone/push Build is failing
2021-05-16 14:52:36 +02:00
0aaf07547a chore: improve generated changelog (#1)
All checks were successful
continuous-integration/drone/push Build is passing
Co-authored-by: Robert Kaussow <mail@geeklabor.de>
Reviewed-on: #1
Co-authored-by: Robert Kaussow <xoxys@rknet.org>
Co-committed-by: Robert Kaussow <xoxys@rknet.org>
2021-05-10 22:21:26 +02:00
c0d0282ead
chore: fix versioning
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2021-03-28 13:26:38 +02:00
4f9f06c715
chore: add build_ignore to cleanup release tarball
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2021-03-27 15:26:04 +01:00
9ddff80af3
strip prefix from galaxy version
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2021-03-27 15:00:02 +01:00
3a0adba650
fix missing collection requirements
All checks were successful
continuous-integration/drone/push Build is passing
2021-03-27 14:45:47 +01:00
b608706c74
ci: auto-replace version in galaxy.yml
Some checks failed
continuous-integration/drone/push Build is failing
2021-03-27 14:35:25 +01:00
12c4dc4e3b
chore: add requirements.txt
All checks were successful
continuous-integration/drone/push Build is passing
2021-03-27 14:12:15 +01:00
983aa3a8a0
chore: remove corenetworks modules
BREAKING CHANGE: The modules `corenetworks_dns` and `corenetworks_token`
were removed.
2021-03-27 14:10:56 +01:00
0cbe2ffdbd
chore: remove flake8-color 2021-03-27 14:10:42 +01:00
5a8a0edeb2
fix missing default value for throttle parameter
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2021-02-19 15:01:11 +01:00
8799a5532e
bump galaxy version
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2021-02-19 14:32:01 +01:00
d47f7023a7
add missing chglog gonfig
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2021-02-19 13:55:40 +01:00
922a7c46d1
ci: use python3.9 for pipeline steps and remove python3.5
Some checks failed
continuous-integration/drone/push Build is failing
2021-02-19 13:47:26 +01:00
8aacd6b34f
chore: exclude B902 in flake8
Some checks failed
continuous-integration/drone/push Build is failing
2021-02-19 13:43:50 +01:00
c024883a2b
feat: add throttle option to corenetworks_dns module
Some checks failed
continuous-integration/drone/push Build is failing
2021-02-19 13:38:30 +01:00
742aed260a
remove downstream trigger
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-06 17:44:59 +01:00
b058ec694f
debug
Some checks failed
continuous-integration/drone/push Build is failing
2020-12-06 17:15:47 +01:00
4c06a3c8c4
remove fork option
Some checks failed
continuous-integration/drone/push Build is failing
2020-12-06 17:09:21 +01:00
fbd43e2c7e
[skip ci] bump release to v1.2.0
All checks were successful
continuous-integration/drone/tag Build is passing
2020-09-04 23:27:25 +02:00
1a45903410
add timeout and ssl verfy parameters
Some checks failed
continuous-integration/drone/tag Build is passing
continuous-integration/drone/push Build is failing
2020-09-04 22:57:25 +02:00
7ecc723ead
fix downstream trigger
All checks were successful
continuous-integration/drone/push Build is passing
2020-08-22 20:38:48 +02:00
d88d71f073
fix docs
Some checks failed
continuous-integration/drone/push Build is failing
2020-08-22 20:29:58 +02:00
dff4b91525
add documentation
Some checks failed
continuous-integration/drone/push Build is failing
2020-08-22 20:07:48 +02:00
44 changed files with 3669 additions and 1725 deletions

2
.dictionary Normal file
View File

@ -0,0 +1,2 @@
Ansible
Kaussow

View File

@ -1,144 +0,0 @@
local PythonVersion(pyversion='3.5') = {
name: 'python' + std.strReplace(pyversion, '.', '') + '-pytest',
image: 'python:' + pyversion,
environment: {
PY_COLORS: 1,
},
commands: [
'pip install -r dev-requirements.txt -qq',
'pip install -r test/unit/requirements.txt -qq',
'python -m pytest --cov --cov-append --no-cov-on-fail',
],
depends_on: [
'clone',
],
};
local PipelineLint = {
kind: 'pipeline',
name: 'lint',
platform: {
os: 'linux',
arch: 'amd64',
},
steps: [
{
name: 'flake8',
image: 'python:3.8',
environment: {
PY_COLORS: 1,
},
commands: [
'pip install -r dev-requirements.txt -qq',
'flake8',
],
},
],
trigger: {
ref: ['refs/heads/master', 'refs/tags/**', 'refs/pull/**'],
},
};
local PipelineTest = {
kind: 'pipeline',
name: 'test',
platform: {
os: 'linux',
arch: 'amd64',
},
steps: [
PythonVersion(pyversion='3.5'),
PythonVersion(pyversion='3.6'),
PythonVersion(pyversion='3.7'),
PythonVersion(pyversion='3.8'),
],
depends_on: [
'lint',
],
trigger: {
ref: ['refs/heads/master', 'refs/tags/**', 'refs/pull/**'],
},
};
local PipelineBuild = {
kind: 'pipeline',
name: 'build',
platform: {
os: 'linux',
arch: 'amd64',
},
steps: [
{
name: 'build',
image: 'python:3.8',
commands: [
'pip install ansible -qq',
'ansible-galaxy collection build --output-path dist/',
],
},
{
name: 'checksum',
image: 'alpine',
commands: [
'cd dist/ && sha256sum * > ../sha256sum.txt',
],
},
{
name: 'publish-gitea',
image: 'plugins/gitea-release',
settings: {
overwrite: true,
api_key: { from_secret: 'gitea_token' },
files: ['dist/*', 'sha256sum.txt'],
base_url: 'https://gitea.rknet.org',
title: '${DRONE_TAG}',
note: 'CHANGELOG.md',
},
when: {
ref: ['refs/tags/**'],
},
},
],
depends_on: [
'test',
],
trigger: {
ref: ['refs/heads/master', 'refs/tags/**', 'refs/pull/**'],
},
};
local PipelineNotifications = {
kind: 'pipeline',
name: 'notifications',
platform: {
os: 'linux',
arch: 'amd64',
},
steps: [
{
name: 'matrix',
image: 'plugins/matrix',
settings: {
homeserver: { from_secret: 'matrix_homeserver' },
roomid: { from_secret: 'matrix_roomid' },
template: 'Status: **{{ build.status }}**<br/> Build: [{{ repo.Owner }}/{{ repo.Name }}]({{ build.link }}) ({{ build.branch }}) by {{ build.author }}<br/> Message: {{ build.message }}',
username: { from_secret: 'matrix_username' },
password: { from_secret: 'matrix_password' },
},
},
],
depends_on: [
'build',
],
trigger: {
ref: ['refs/heads/master', 'refs/tags/**'],
status: ['success', 'failure'],
},
};
[
PipelineLint,
PipelineTest,
PipelineBuild,
PipelineNotifications,
]

View File

@ -1,168 +0,0 @@
---
kind: pipeline
name: lint
platform:
os: linux
arch: amd64
steps:
- name: flake8
image: python:3.8
commands:
- pip install -r dev-requirements.txt -qq
- flake8
environment:
PY_COLORS: 1
trigger:
ref:
- refs/heads/master
- refs/tags/**
- refs/pull/**
---
kind: pipeline
name: test
platform:
os: linux
arch: amd64
steps:
- name: python35-pytest
image: python:3.5
commands:
- pip install -r dev-requirements.txt -qq
- pip install -r test/unit/requirements.txt -qq
- python -m pytest --cov --cov-append --no-cov-on-fail
environment:
PY_COLORS: 1
depends_on:
- clone
- name: python36-pytest
image: python:3.6
commands:
- pip install -r dev-requirements.txt -qq
- pip install -r test/unit/requirements.txt -qq
- python -m pytest --cov --cov-append --no-cov-on-fail
environment:
PY_COLORS: 1
depends_on:
- clone
- name: python37-pytest
image: python:3.7
commands:
- pip install -r dev-requirements.txt -qq
- pip install -r test/unit/requirements.txt -qq
- python -m pytest --cov --cov-append --no-cov-on-fail
environment:
PY_COLORS: 1
depends_on:
- clone
- name: python38-pytest
image: python:3.8
commands:
- pip install -r dev-requirements.txt -qq
- pip install -r test/unit/requirements.txt -qq
- python -m pytest --cov --cov-append --no-cov-on-fail
environment:
PY_COLORS: 1
depends_on:
- clone
trigger:
ref:
- refs/heads/master
- refs/tags/**
- refs/pull/**
depends_on:
- lint
---
kind: pipeline
name: build
platform:
os: linux
arch: amd64
steps:
- name: build
image: python:3.8
commands:
- pip install ansible -qq
- ansible-galaxy collection build --output-path dist/
- name: checksum
image: alpine
commands:
- cd dist/ && sha256sum * > ../sha256sum.txt
- name: publish-gitea
image: plugins/gitea-release
settings:
api_key:
from_secret: gitea_token
base_url: https://gitea.rknet.org
files:
- dist/*
- sha256sum.txt
note: CHANGELOG.md
overwrite: true
title: ${DRONE_TAG}
when:
ref:
- refs/tags/**
trigger:
ref:
- refs/heads/master
- refs/tags/**
- refs/pull/**
depends_on:
- test
---
kind: pipeline
name: notifications
platform:
os: linux
arch: amd64
steps:
- name: matrix
image: plugins/matrix
settings:
homeserver:
from_secret: matrix_homeserver
password:
from_secret: matrix_password
roomid:
from_secret: matrix_roomid
template: "Status: **{{ build.status }}**<br/> Build: [{{ repo.Owner }}/{{ repo.Name }}]({{ build.link }}) ({{ build.branch }}) by {{ build.author }}<br/> Message: {{ build.message }}"
username:
from_secret: matrix_username
trigger:
ref:
- refs/heads/master
- refs/tags/**
status:
- success
- failure
depends_on:
- build
---
kind: signature
hmac: a9bdb4a4a7aaab0c2a68a9c5f48ad4fcd0172a41f299ab7efff9917522ccae77
...

19
.flake8
View File

@ -1,19 +0,0 @@
[flake8]
ignore = D101, D102, D103, D105, D107, D202, E402, W503
max-line-length = 99
inline-quotes = double
exclude =
.git
.tox
__pycache__
build
dist
test
*.pyc
*.egg-info
.cache
.eggs
env*
iptables_raw.py
application-import-names = ansiblelater
format = ${cyan}%(path)s:%(row)d:%(col)d${reset}: ${red_bold}%(code)s${reset} %(text)s

2
.gitignore vendored
View File

@ -107,3 +107,5 @@ docs/themes/
docs/public/ docs/public/
resources/_gen/ resources/_gen/
CHANGELOG.md
tests/output

47
.gitsv/config.yml Normal file
View File

@ -0,0 +1,47 @@
---
version: "1.1"
versioning:
update-major: []
update-minor: [feat]
update-patch: [fix, perf, refactor, chore, test, ci, docs]
tag:
pattern: "v%d.%d.%d"
release-notes:
sections:
- name: Features
commit-types: [feat]
section-type: commits
- name: Bug Fixes
commit-types: [fix]
section-type: commits
- name: Performance Improvements
commit-types: [perf]
section-type: commits
- name: Code Refactoring
commit-types: [refactor]
section-type: commits
- name: Others
commit-types: [chore]
section-type: commits
- name: Testing
commit-types: [test]
section-type: commits
- name: CI Pipeline
commit-types: [ci]
section-type: commits
- name: Documentation
commit-types: [docs]
section-type: commits
- name: Breaking Changes
section-type: breaking-changes
commit-message:
footer:
issue:
key: issue
add-value-prefix: "#"
issue:
regex: "#?[0-9]+"

7
.markdownlint.yml Normal file
View File

@ -0,0 +1,7 @@
---
default: True
MD013: False
MD041: False
MD024: False
MD004:
style: dash

2
.prettierignore Normal file
View File

@ -0,0 +1,2 @@
*.tpl.md
LICENSE

View File

@ -0,0 +1,46 @@
---
when:
- event: [pull_request, tag]
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
steps:
- name: build
image: docker.io/library/python:3.12
commands:
- GALAXY_VERSION=${CI_COMMIT_TAG##v}
- 'sed -i ''s/version: 0.0.0/version: ''"$${GALAXY_VERSION:-0.0.0}"''/g'' galaxy.yml'
- pip install poetry -qq
- poetry install --all-extras --no-root
- poetry run ansible-galaxy collection build --output-path dist/
- name: checksum
image: quay.io/thegeeklab/alpine-tools
commands:
- cd dist/ && sha256sum * > ../sha256sum.txt
- name: changelog
image: quay.io/thegeeklab/git-sv
commands:
- git sv current-version
- git sv release-notes -t ${CI_COMMIT_TAG:-next} -o CHANGELOG.md
- cat CHANGELOG.md
- name: publish-gitea
image: quay.io/thegeeklab/wp-gitea-release
settings:
api_key:
from_secret: gitea_token
base_url: https://gitea.rknet.org
files:
- dist/*
- sha256sum.txt
note: CHANGELOG.md
title: ${CI_COMMIT_TAG}
when:
- event: [tag]
depends_on:
- unit-test
- sanity-test

49
.woodpecker/docs.yml Normal file
View File

@ -0,0 +1,49 @@
---
when:
- event: [pull_request, tag]
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
steps:
- name: markdownlint
image: quay.io/thegeeklab/markdownlint-cli
group: test
commands:
- markdownlint 'docs/**/*.md' 'README.md'
- name: spellcheck
image: quay.io/thegeeklab/alpine-tools
group: test
commands:
- spellchecker --files 'docs/**/*.md' 'README.md' -d .dictionary -p spell indefinite-article syntax-urls frontmatter --frontmatter-keys title tags
environment:
FORCE_COLOR: "true"
- name: link-validation
image: docker.io/lycheeverse/lychee
group: test
commands:
- lychee --no-progress --format detailed docs/ README.md
- name: publish
image: quay.io/thegeeklab/wp-git-action
settings:
action:
- pages
author_email: shipper@rknet.org
author_name: shipper
branch: docs
message: auto-update documentation
netrc_machine: gitea.rknet.org
netrc_password:
from_secret: gitea_token
pages_directory: docs/
remote_url: https://gitea.rknet.org/ansible/${CI_REPO_NAME}
when:
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
depends_on:
- build-package

25
.woodpecker/lint.yml Normal file
View File

@ -0,0 +1,25 @@
---
when:
- event: [pull_request, tag]
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
steps:
- name: check-format
image: docker.io/library/python:3.12
commands:
- pip install poetry -qq
- poetry install --all-extras --no-root
- poetry run ruff format --check --diff ./plugins
environment:
PY_COLORS: "1"
- name: check-coding
image: docker.io/library/python:3.12
commands:
- pip install poetry -qq
- poetry install --all-extras --no-root
- poetry run ruff ./plugins
environment:
PY_COLORS: "1"

26
.woodpecker/notify.yml Normal file
View File

@ -0,0 +1,26 @@
---
when:
- event: [tag]
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
runs_on: [success, failure]
steps:
- name: matrix
image: quay.io/thegeeklab/wp-matrix
settings:
homeserver:
from_secret: matrix_homeserver
password:
from_secret: matrix_password
roomid:
from_secret: matrix_roomid
username:
from_secret: matrix_username
when:
- status: [success, failure]
depends_on:
- docs

View File

@ -0,0 +1,45 @@
---
when:
- event: [pull_request, tag]
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
variables:
- &ansible_base
image: docker.io/library/python:3.10
group: ansible
commands:
- pip install poetry -qq
- poetry install --all-extras --no-root
- poetry run pip install https://github.com/ansible/ansible/archive/$${ANSIBLE_VERSION}.tar.gz --disable-pip-version-check
- poetry run ansible --version
- poetry run ansible-test sanity --exclude .gitsv/ --exclude .woodpecker/ --python 3.10
- &ansible_env
PY_COLORS: "1"
workspace:
base: /woodpecker/src
path: ansible_collections/${CI_REPO_NAME/./\/}
steps:
- name: ansible-devel
<<: *ansible_base
environment:
ANSIBLE_VERSION: "devel"
<<: *ansible_env
- name: ansible-216
<<: *ansible_base
environment:
ANSIBLE_VERSION: "stable-2.16"
<<: *ansible_env
- name: ansible-215
<<: *ansible_base
environment:
ANSIBLE_VERSION: "stable-2.15"
<<: *ansible_env
depends_on:
- lint

36
.woodpecker/unit-test.yml Normal file
View File

@ -0,0 +1,36 @@
---
when:
- event: [pull_request, tag]
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
variables:
- &pytest_base
group: pytest
commands:
- pip install poetry -qq
- poetry install --all-extras --no-root
- poetry run pytest
environment:
PY_COLORS: "1"
steps:
- name: pyton-312
image: docker.io/library/python:3.12
<<: *pytest_base
- name: pyton-311
image: docker.io/library/python:3.11
<<: *pytest_base
- name: pyton-310
image: docker.io/library/python:3.10
<<: *pytest_base
- name: pyton-39
image: docker.io/library/python:3.9
<<: *pytest_base
depends_on:
- lint

View File

@ -1,6 +0,0 @@
- FEATURE
- add custom modules for cornetworks dns
- add custom module iptables_raw
- add custom module openssl_pkcs12
- add custom module proxmox_kvm
- add custom module ucr

View File

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2020 Robert Kaussow <mail@thegeeklab.de> Copyright (c) 2022 Robert Kaussow <mail@thegeeklab.de>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -1,14 +1,10 @@
# xoxys.general # xoxys.general
[![Build Status](https://img.shields.io/drone/build/ansible/xoxys.general?logo=drone&server=https%3A%2F%2Fdrone.rknet.org)](https://drone.rknet.org/ansible/xoxys.general) [![Build Status](https://ci.rknet.org/api/badges/ansible/xoxys.general/status.svg)](https://ci.rknet.org/repos/ansible/xoxys.general)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg?label=license)](LICENSE) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg?label=license)](https://gitea.rknet.org/ansible/xoxys.general/src/branch/main/LICENSE)
Custom general Ansible collection. Custom general Ansible collection.
## License ## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. This project is licensed under the MIT License - see the [LICENSE](https://gitea.rknet.org/ansible/xoxys.general/src/branch/main/LICENSE) file for details.
## Maintainers and Contributors
[Robert Kaussow](https://gitea.rknet.org/xoxys)

View File

@ -1,19 +0,0 @@
pydocstyle<4.0.0
flake8
flake8-colors
flake8-blind-except
flake8-builtins
flake8-docstrings<=3.0.0
flake8-isort
flake8-logging-format
flake8-polyfill
flake8-quotes
flake8-pep3101
flake8-eradicate; python_version >= "3.6"
pep8-naming
wheel
pytest
pytest-mock
pytest-cov
bandit
yapf

12
docs/_index.md Normal file
View File

@ -0,0 +1,12 @@
---
title: general
geekdocFlatSection: true
---
General custom content collection for Ansible.
<!-- spellchecker-disable -->
{{< toc-tree >}}
<!-- spellchecker-enable -->

3
docs/filters/_index.md Normal file
View File

@ -0,0 +1,3 @@
---
title: Filters
---

27
docs/filters/prefix.md Normal file
View File

@ -0,0 +1,27 @@
Simple filter to prefix all items from a list. Default prefix will be `--` but you can also pass a custom one.
## Example
```Yaml
my_list:
- item1
- item2
{{ my_list | prefix | join(' ') }}
# result:
# "--item1 --item2"
```
Or pass a custom prefix:
```Yaml
my_list:
- item1
- item2
{{ my_list | prefix(prefix='-') | join(' ') }}
# result:
# "-item1 -item2"
```

27
docs/filters/wrap.md Normal file
View File

@ -0,0 +1,27 @@
Simple filter to wrap all items form a list. Default will wrap items in single quotes but you can also pass a custom wrapper character.
## Example
```Yaml
my_list:
- item1
- item2
{{ my_list | wrap | join(',') }}
# result:
# "'item1','item2'"
```
Or pass a custom wrapper:
```Yaml
my_list:
- item1
- item2
{{ my_list | wrap(wrapper='/') | join(',') }}
# result:
# "/item1/,/item2/"
```

View File

@ -1,6 +1,8 @@
---
namespace: xoxys namespace: xoxys
name: general name: general
version: 1.1.0 # The version is generated during the release by Woocpecker CI.
version: 0.0.0
readme: README.md readme: README.md
authors: authors:
- Robert Kaussow <mail@thegeeklab.de> - Robert Kaussow <mail@thegeeklab.de>
@ -10,6 +12,13 @@ license:
license_file: "LICENSE" license_file: "LICENSE"
tags: tags:
- misc - misc
dependencies: {} repository: https://gitea.rknet.org/ansible/xoxys.general/
repository: https://gitea.rknet.org/ansible/xoxys.general
homepage: https://thegeeklab.de/ homepage: https://thegeeklab.de/
documentation: https://galaxy.geekdocs.de/collections/general/
build_ignore:
- ".*"
- "*requirements.txt"
- docs
- test
- dist
- setup.cfg

2
meta/runtime.yml Normal file
View File

@ -0,0 +1,2 @@
---
requires_ansible: ">=2.10"

View File

@ -0,0 +1,107 @@
# -*- coding: utf-8 -*-
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""Implement documentation fragment for Hashivault module."""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
class ModuleDocFragment: # noqa
# Standard documentation
DOCUMENTATION = r"""
requirements:
- hvac>=0.10.1
- ansible>=2.0.0
- requests
options:
url:
description:
- URL of the Vault server.
- You can use C(VAULT_ADDR) environment variable.
default: ""
type: str
ca_cert:
description:
- Path to a PEM-encoded CA cert file to use to verify the Vault server
TLS certificate.
- You can use C(VAULT_CACERT) environment variable.
default: ""
type: str
ca_path:
description:
- Path to a directory of PEM-encoded CA cert files to verify the Vault server
TLS certificate. If ca_cert is specified, its value will take precedence.
- You can use C(VAULT_CAPATH) environment variable.
default: ""
type: str
client_cert:
description:
- Path to a PEM-encoded client certificate for TLS authentication to the Vault
server.
- You can use C(VAULT_CLIENT_CERT) environment variable.
default: ""
type: str
client_key:
description:
- Path to an unencrypted PEM-encoded private key matching the client certificate.
- You can use C(VAULT_CLIENT_KEY) environment variable.
default: ""
type: str
verify:
description:
- If set, do not verify presented TLS certificate before communicating with Vault
server. Setting this variable is not recommended except during testing.
- You can use C(VAULT_SKIP_VERIFY) environment variable.
default: false
type: bool
authtype:
description:
- Authentication type.
- You can use C(VAULT_AUTHTYPE) environment variable.
default: "token"
type: str
choices: ["token", "userpass", "github", "ldap", "approle"]
login_mount_point:
description:
- Authentication mount point.
- You can use C(VAULT_LOGIN_MOUNT_POINT) environment variable.
type: str
token:
description:
- Token for vault.
- You can use C(VAULT_TOKEN) environment variable.
type: str
username:
description:
- Username to login to vault.
- You can use C(VAULT_USER) environment variable.
default: ""
type: str
password:
description:
- Password to login to vault.
- You can use C(VAULT_PASSWORD) environment variable.
type: str
role_id:
description:
- Role id for vault.
- You can use C(VAULT_ROLE_ID) environment variable.
type: str
secret_id:
description:
- Secret id for vault.
- You can use C(VAULT_SECRET_ID) environment variable.
type: str
aws_header:
description:
- X-Vault-AWS-IAM-Server-ID Header value to prevent replay attacks.
- You can use C(VAULT_AWS_HEADER) environment variable.
type: str
namespace:
description:
- Namespace for vault.
- You can use C(VAULT_NAMESPACE) environment variable.
type: str
"""

View File

@ -1,11 +1,14 @@
"""Filter to prefix all itams from a list.""" """Filter to prefix all itams from a list."""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
def prefix(value, prefix="--"): def prefix(value, prefix="--"):
return [prefix + x for x in value] return [prefix + x for x in value]
class FilterModule(object): class FilterModule(object): # noqa
def filters(self): def filters(self):
return {"prefix": prefix} return {"prefix": prefix}

View File

@ -1,11 +1,14 @@
"""Filter to wrap all items from a list.""" """Filter to wrap all items from a list."""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
def wrap(value, wrapper="'"): def wrap(value, wrapper="'"):
return [wrapper + x + wrapper for x in value] return [wrapper + x + wrapper for x in value]
class FilterModule(object): class FilterModule(object): # noqa
def filters(self): def filters(self):
return {"wrap": wrap} return {"wrap": wrap}

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2014, Mathieu GAUTHIER-LAFAYE <gauthierl@lapth.cnrs.fr> # Copyright (c) 2014, Mathieu GAUTHIER-LAFAYE <gauthierl@lapth.cnrs.fr>
# Copyright (c) 2016, Matt Harris <matthaeus.harris@gmail.com> # Copyright (c) 2016, Matt Harris <matthaeus.harris@gmail.com>
# Copyright (c) 2020, Robert Kaussow <mail@thegeeklab.de> # Copyright (c) 2020, Robert Kaussow <mail@thegeeklab.de>
@ -10,90 +9,146 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type __metaclass__ = type
DOCUMENTATION = """ DOCUMENTATION = """
name: proxmox ---
plugin_type: inventory name: proxmox
short_description: Proxmox VE inventory source short_description: Proxmox VE inventory source
version_added: 1.0.0 version_added: 1.1.0
description:
- Get inventory hosts from the proxmox service.
- "Uses a configuration file as an inventory source, it must end in C(.proxmox.yml) or C(.proxmox.yaml) and has a C(plugin: xoxys.general.proxmox) entry."
extends_documentation_fragment:
- inventory_cache
options:
plugin:
description: The name of this plugin, it should always be set to C(xoxys.general.proxmox) for this plugin to recognize it as it's own.
required: yes
choices: ["xoxys.general.proxmox"]
api_host:
description: description:
- Get inventory hosts from the proxmox service. - Specify the target host of the Proxmox VE cluster.
- "Uses a configuration file as an inventory source, it must end in C(.proxmox.yml) or C(.proxmox.yaml) and has a C(plugin: proxmox) entry." type: str
extends_documentation_fragment: required: true
- inventory_cache env:
options: - name: PROXMOX_SERVER
plugin: api_user:
description: The name of this plugin, it should always be set to C(proxmox) for this plugin to recognize it as it"s own. description:
required: yes - Specify the user to authenticate with.
choices: ["xoxys.general.proxmox"] type: str
server: required: true
description: Proxmox VE server url. env:
default: "pve.example.com" - name: PROXMOX_USER
required: yes api_password:
env: description:
- name: PROXMOX_SERVER - Specify the password to authenticate with.
user: type: str
description: Proxmox VE authentication user. env:
required: yes - name: PROXMOX_PASSWORD
env: api_token_id:
- name: PROXMOX_USER description:
password: - Specify the token ID.
description: Proxmox VE authentication password type: str
required: yes env:
env: - name: PROXMOX_TOKEN_ID
- name: PROXMOX_PASSWORD api_token_secret:
exclude_vmid: description:
description: VMID"s to exclude from inventory - Specify the token secret.
type: list type: str
default: [] env:
elements: str - name: PROXMOX_TOKEN_SECRET
exclude_state: verify_ssl:
description: VM states to exclude from inventory description:
type: list - If C(false), SSL certificates will not be validated.
default: [] - This should only be used on personally controlled sites using self-signed certificates.
elements: str type: bool
group: default: True
description: Group to place all hosts into auth_timeout:
default: proxmox description: Proxmox VE authentication timeout.
want_facts: type: int
description: Toggle, if C(true) the plugin will retrieve host facts from the server default: 5
type: boolean exclude_vmid:
default: yes description: VMID's to exclude from inventory.
""" # noqa type: list
default: []
elements: str
exclude_state:
description: VM states to exclude from inventory.
type: list
default: []
elements: str
group:
description: Group to place all hosts into.
type: string
default: proxmox
want_facts:
description: Toggle, if C(true) the plugin will retrieve host facts from the server
type: boolean
default: True
requirements:
- "proxmoxer"
""" # noqa
EXAMPLES = """ EXAMPLES = """
# proxmox.yml # proxmox.yml
plugin: xoxys.general.proxmox plugin: xoxys.general.proxmox
server: pve.example.com api_user: root@pam
user: admin@pve api_password: secret
password: secure api_host: helldorado
""" """
import json import json
import re import re
import socket import socket
from collections import defaultdict
from ansible.errors import AnsibleError from ansible.errors import AnsibleError
from ansible.module_utils._text import to_native from ansible.module_utils._text import to_native
from ansible.module_utils.parsing.convert_bool import boolean
from ansible.module_utils.six import iteritems from ansible.module_utils.six import iteritems
from ansible.plugins.inventory import BaseInventoryPlugin from ansible.plugins.inventory import BaseInventoryPlugin
from collections import defaultdict from ansible_collections.xoxys.general.plugins.module_utils.version import LooseVersion
from distutils.version import LooseVersion
try: try:
from proxmoxer import ProxmoxAPI from proxmoxer import ProxmoxAPI
HAS_PROXMOXER = True HAS_PROXMOXER = True
except ImportError: except ImportError:
HAS_PROXMOXER = False HAS_PROXMOXER = False
try:
from requests.packages import urllib3
HAS_URLLIB3 = True
except ImportError:
try:
import urllib3
HAS_URLLIB3 = True
except ImportError:
HAS_URLLIB3 = False
class InventoryModule(BaseInventoryPlugin): class InventoryModule(BaseInventoryPlugin):
"""Provide Proxmox VE inventory."""
NAME = "xoxys.general.proxmox" NAME = "xoxys.general.proxmox"
def _auth(self): def _proxmox_auth(self):
return ProxmoxAPI( auth_args = {"user": self.get_option("api_user")}
self.get_option("server"), if not (self.get_option("api_token_id") and self.get_option("api_token_secret")):
user=self.get_option("user"), auth_args["password"] = self.get_option("api_password")
password=self.get_option("password"), else:
verify_ssl=False auth_args["token_name"] = self.get_option("api_token_id")
auth_args["token_value"] = self.get_option("api_token_secret")
verify_ssl = boolean(self.get_option("verify_ssl"), strict=False)
if not verify_ssl and HAS_URLLIB3:
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
self.client = ProxmoxAPI(
self.get_option("api_host"),
verify_ssl=verify_ssl,
timeout=self.get_option("auth_timeout"),
**auth_args,
) )
def _get_version(self): def _get_version(self):
@ -103,14 +158,12 @@ class InventoryModule(BaseInventoryPlugin):
return LooseVersion(self.client.version.get()["release"]) return LooseVersion(self.client.version.get()["release"])
def _get_names(self, pve_list, pve_type): def _get_names(self, pve_list, pve_type):
names = []
if pve_type == "node": if pve_type == "node":
names = [node["node"] for node in pve_list] return [node["node"] for node in pve_list]
elif pve_type == "pool": if pve_type == "pool":
names = [pool["poolid"] for pool in pve_list] return [pool["poolid"] for pool in pve_list]
return names return []
def _get_variables(self, pve_list, pve_type): def _get_variables(self, pve_list, pve_type):
variables = {} variables = {}
@ -125,15 +178,12 @@ class InventoryModule(BaseInventoryPlugin):
return variables return variables
def _get_ip_address(self, pve_type, pve_node, vmid): def _get_ip_address(self, pve_type, pve_node, vmid):
def validate(address): def validate(address):
try: try:
# IP address validation # IP address validation
if socket.inet_aton(address): if socket.inet_aton(address) and address != "127.0.0.1":
# Ignore localhost return address
if address != "127.0.0.1": except OSError:
return address
except socket.error:
return False return False
address = False address = False
@ -145,19 +195,18 @@ class InventoryModule(BaseInventoryPlugin):
networks = self.client.nodes(pve_node).get( networks = self.client.nodes(pve_node).get(
"qemu", vmid, "agent", "network-get-interfaces" "qemu", vmid, "agent", "network-get-interfaces"
)["result"] )["result"]
except Exception: except Exception: # noqa
pass pass
if networks: if networks and isinstance(networks, list):
if type(networks) is list: for network in networks:
for network in networks: for ip_address in network.get("ip-addresses", []):
for ip_address in network["ip-addresses"]: address = validate(ip_address["ip-address"])
address = validate(ip_address["ip-address"])
else: else:
try: try:
config = self.client.nodes(pve_node).get(pve_type, vmid, "config") config = self.client.nodes(pve_node).get(pve_type, vmid, "config")
address = re.search(r"ip=(\d*\.\d*\.\d*\.\d*)", config["net0"]).group(1) address = re.search(r"ip=(\d*\.\d*\.\d*\.\d*)", config["net0"]).group(1)
except Exception: except Exception: # noqa
pass pass
return address return address
@ -183,8 +232,8 @@ class InventoryModule(BaseInventoryPlugin):
try: try:
qemu_list = self._exclude(self.client.nodes(node).qemu.get()) qemu_list = self._exclude(self.client.nodes(node).qemu.get())
container_list = self._exclude(self.client.nodes(node).lxc.get()) container_list = self._exclude(self.client.nodes(node).lxc.get())
except Exception as e: except Exception as e: # noqa
raise AnsibleError("Proxmoxer API error: {0}".format(to_native(e))) raise AnsibleError(f"Proxmoxer API error: {to_native(e)}") from e
# Merge QEMU and Containers lists from this node # Merge QEMU and Containers lists from this node
instances = self._get_variables(qemu_list, "qemu").copy() instances = self._get_variables(qemu_list, "qemu").copy()
@ -199,12 +248,13 @@ class InventoryModule(BaseInventoryPlugin):
pve_type = "qemu" pve_type = "qemu"
try: try:
description = self.client.nodes(node).get(pve_type, vmid, description = self.client.nodes(node).get(pve_type, vmid, "config")[
"config")["description"] "description"
]
except KeyError: except KeyError:
description = None description = None
except Exception as e: except Exception as e: # noqa
raise AnsibleError("Proxmoxer API error: {0}".format(to_native(e))) raise AnsibleError(f"Proxmoxer API error: {to_native(e)}") from e
try: try:
metadata = json.loads(description) metadata = json.loads(description)
@ -238,8 +288,8 @@ class InventoryModule(BaseInventoryPlugin):
for pool in self._get_names(self.client.pools.get(), "pool"): for pool in self._get_names(self.client.pools.get(), "pool"):
try: try:
pool_list = self._exclude(self.client.pool(pool).get()["members"]) pool_list = self._exclude(self.client.pool(pool).get()["members"])
except Exception as e: except Exception as e: # noqa
raise AnsibleError("Proxmoxer API error: {0}".format(to_native(e))) raise AnsibleError(f"Proxmoxer API error: {to_native(e)}") from e
members = [ members = [
member["name"] member["name"]
@ -252,13 +302,13 @@ class InventoryModule(BaseInventoryPlugin):
def verify_file(self, path): def verify_file(self, path):
"""Verify the Proxmox VE configuration file.""" """Verify the Proxmox VE configuration file."""
if super(InventoryModule, self).verify_file(path): if super().verify_file(path):
endings = ("proxmox.yaml", "proxmox.yml") endings = ("proxmox.yaml", "proxmox.yml")
if any((path.endswith(ending) for ending in endings)): if any(path.endswith(ending) for ending in endings):
return True return True
return False return False
def parse(self, inventory, loader, path, cache=True): def parse(self, inventory, loader, path, cache=True): # noqa
"""Dynamically parse the Proxmox VE cloud inventory.""" """Dynamically parse the Proxmox VE cloud inventory."""
if not HAS_PROXMOXER: if not HAS_PROXMOXER:
raise AnsibleError( raise AnsibleError(
@ -266,8 +316,8 @@ class InventoryModule(BaseInventoryPlugin):
"https://pypi.org/project/proxmoxer/" "https://pypi.org/project/proxmoxer/"
) )
super(InventoryModule, self).parse(inventory, loader, path) super().parse(inventory, loader, path)
self._read_config_data(path) self._read_config_data(path)
self.client = self._auth() self._proxmox_auth()
self._propagate() self._propagate()

View File

@ -0,0 +1,402 @@
"""Provide helper functions for Hashivault module."""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import os
import traceback
from ansible.module_utils.basic import missing_required_lib
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import env_fallback
HVAC_IMP_ERR = None
try:
import hvac
from hvac.exceptions import InvalidPath
HAS_HVAC = True
except ImportError:
HAS_HVAC = False
HVAC_IMP_ERR = traceback.format_exc()
def hashivault_argspec():
return dict(
url=dict(required=False, default=os.environ.get("VAULT_ADDR", ""), type="str"),
ca_cert=dict(required=False, default=os.environ.get("VAULT_CACERT", ""), type="str"),
ca_path=dict(required=False, default=os.environ.get("VAULT_CAPATH", ""), type="str"),
client_cert=dict(
required=False, default=os.environ.get("VAULT_CLIENT_CERT", ""), type="str"
),
client_key=dict(
required=False, default=os.environ.get("VAULT_CLIENT_KEY", ""), type="str", no_log=True
),
verify=dict(
required=False, default=(not os.environ.get("VAULT_SKIP_VERIFY", "False")), type="bool"
),
authtype=dict(
required=False,
default=os.environ.get("VAULT_AUTHTYPE", "token"),
type="str",
choices=["token", "userpass", "github", "ldap", "approle"],
),
login_mount_point=dict(
required=False, default=os.environ.get("VAULT_LOGIN_MOUNT_POINT", None), type="str"
),
token=dict(
required=False,
fallback=(hashivault_default_token, ["VAULT_TOKEN"]),
type="str",
no_log=True,
),
username=dict(required=False, default=os.environ.get("VAULT_USER", ""), type="str"),
password=dict(
required=False, fallback=(env_fallback, ["VAULT_PASSWORD"]), type="str", no_log=True
),
role_id=dict(
required=False, fallback=(env_fallback, ["VAULT_ROLE_ID"]), type="str", no_log=True
),
secret_id=dict(
required=False, fallback=(env_fallback, ["VAULT_SECRET_ID"]), type="str", no_log=True
),
aws_header=dict(
required=False, fallback=(env_fallback, ["VAULT_AWS_HEADER"]), type="str", no_log=True
),
namespace=dict(
required=False, default=os.environ.get("VAULT_NAMESPACE", None), type="str"
),
)
def hashivault_init(
argument_spec,
supports_check_mode=False,
required_if=None,
required_together=None,
required_one_of=None,
mutually_exclusive=None,
):
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=supports_check_mode,
required_if=required_if,
required_together=required_together,
required_one_of=required_one_of,
mutually_exclusive=mutually_exclusive,
)
if not HAS_HVAC:
module.fail_json(msg=missing_required_lib("hvac"), exception=HVAC_IMP_ERR)
module.no_log_values.discard("0")
module.no_log_values.discard(0)
module.no_log_values.discard("1")
module.no_log_values.discard(1)
module.no_log_values.discard(True)
module.no_log_values.discard(False)
module.no_log_values.discard("ttl")
return module
def hashivault_client(params):
url = params.get("url")
ca_cert = params.get("ca_cert")
ca_path = params.get("ca_path")
client_cert = params.get("client_cert")
client_key = params.get("client_key")
cert = (client_cert, client_key)
check_verify = params.get("verify")
namespace = params.get("namespace", None)
if check_verify == "" or check_verify:
if ca_cert:
verify = ca_cert
elif ca_path:
verify = ca_path
else:
verify = check_verify
else:
verify = check_verify
return hvac.Client(url=url, cert=cert, verify=verify, namespace=namespace)
def hashivault_auth(client, params):
token = params.get("token")
authtype = params.get("authtype")
login_mount_point = params.get("login_mount_point", authtype)
if not login_mount_point:
login_mount_point = authtype
username = params.get("username")
password = params.get("password")
secret_id = params.get("secret_id")
role_id = params.get("role_id")
if authtype == "github":
client.auth.github.login(token, mount_point=login_mount_point)
elif authtype == "userpass":
client.auth_userpass(username, password, mount_point=login_mount_point)
elif authtype == "ldap":
client.auth.ldap.login(username, password, mount_point=login_mount_point)
elif authtype == "approle":
client = AppRoleClient(client, role_id, secret_id, mount_point=login_mount_point)
elif authtype == "tls":
client.auth_tls()
else:
client.token = token
return client
def hashivault_auth_client(params):
client = hashivault_client(params)
return hashivault_auth(client, params)
def hashiwrapper(function):
def wrapper(*args, **kwargs):
result = {"changed": False, "rc": 0}
result.update(function(*args, **kwargs))
return result
return wrapper
def hashivault_default_token(env):
"""Get a default Vault token from an environment variable or a file."""
envvar = env[0]
if envvar in os.environ:
return os.environ[envvar]
token_file = os.path.expanduser("~/.vault-token")
if os.path.exists(token_file):
with open(token_file) as f:
return f.read().strip()
return ""
@hashiwrapper
def hashivault_read(params):
result = {"changed": False, "rc": 0}
client = hashivault_auth_client(params)
version = params.get("version")
mount_point = params.get("mount_point")
secret = params.get("secret")
secret_version = params.get("secret_version")
key = params.get("key")
default = params.get("default")
if secret.startswith("/"):
secret = secret.lstrip("/")
mount_point = ""
secret_path = f"{mount_point}/{secret}" if mount_point else secret
try:
if version == 2:
response = client.secrets.kv.v2.read_secret_version(
secret, mount_point=mount_point, version=secret_version
)
else:
response = client.secrets.kv.v1.read_secret(secret, mount_point=mount_point)
except InvalidPath:
response = None
except Exception as e: # noqa: BLE001
result["rc"] = 1
result["failed"] = True
error_string = f"{e.__class__.__name__}({e})"
result["msg"] = f"Error {error_string} reading {secret_path}"
return result
if not response:
if default is not None:
result["value"] = default
return result
result["rc"] = 1
result["failed"] = True
result["msg"] = f"Secret {secret_path} is not in vault"
return result
if version == 2:
try:
data = response.get("data", {})
data = data.get("data", {})
except Exception: # noqa: BLE001
data = str(response)
else:
data = response["data"]
lease_duration = response.get("lease_duration", None)
if lease_duration is not None:
result["lease_duration"] = lease_duration
lease_id = response.get("lease_id", None)
if lease_id is not None:
result["lease_id"] = lease_id
renewable = response.get("renewable", None)
if renewable is not None:
result["renewable"] = renewable
wrap_info = response.get("wrap_info", None)
if wrap_info is not None:
result["wrap_info"] = wrap_info
if key and key not in data:
if default is not None:
result["value"] = default
return result
result["rc"] = 1
result["failed"] = True
result["msg"] = f"Key {key} is not in secret {secret_path}"
return result
value = data[key] if key else data
result["value"] = value
return result
class AppRoleClient:
"""
hvac.Client decorator generate and set a new approle token.
This allows multiple calls to Vault without having to manually
generate and set a token on every Vault call.
"""
def __init__(self, client, role_id, secret_id, mount_point):
object.__setattr__(self, "client", client)
object.__setattr__(self, "role_id", role_id)
object.__setattr__(self, "secret_id", secret_id)
object.__setattr__(self, "login_mount_point", mount_point)
def __setattr__(self, name, val):
client = object.__getattribute__(self, "client")
client.__setattr__(name, val)
def __getattribute__(self, name):
client = object.__getattribute__(self, "client")
attr = client.__getattribute__(name)
role_id = object.__getattribute__(self, "role_id")
secret_id = object.__getattribute__(self, "secret_id")
login_mount_point = object.__getattribute__(self, "login_mount_point")
resp = client.auth_approle(role_id, secret_id=secret_id, mount_point=login_mount_point)
client.token = str(resp["auth"]["client_token"])
return attr
def _compare_state(desired_state, current_state, ignore=None):
"""
Compare desired state to current state.
Returns true if objects are equal.
Recursively walks dict object to compare all keys.
:param desired_state: The state user desires.
:param current_state: The state that currently exists.
:param ignore: Ignore these keys.
:type ignore: list
:return: True if the states are the same.
:rtype: bool
"""
if ignore is None:
ignore = []
if isinstance(desired_state, list):
if not isinstance(current_state, list) or (len(desired_state) != len(current_state)):
return False
return set(desired_state) == set(current_state)
if isinstance(desired_state, dict):
if not isinstance(current_state, dict):
return False
# iterate over dictionary keys
for key in desired_state:
if key in ignore:
continue
v = desired_state[key]
if (key not in current_state) or (not _compare_state(v, current_state.get(key))):
return False
return True
# Lots of things get handled as strings in ansible that aren"t necessarily strings,
# can extend this list later.
if isinstance(desired_state, str) and isinstance(current_state, int):
current_state = str(current_state)
return desired_state == current_state
def _convert_to_seconds(original_value):
try:
value = str(original_value)
seconds = 0
if "h" in value:
ray = value.split("h")
seconds = int(ray.pop(0)) * 3600
value = "".join(ray)
if "m" in value:
ray = value.split("m")
seconds += int(ray.pop(0)) * 60
value = "".join(ray)
if value:
ray = value.split("s")
seconds += int(ray.pop(0))
return seconds
except Exception: # noqa: BLE001,S110
pass
return original_value
def get_keys_updated(desired_state, current_state, ignore=None):
"""
Return list of keys that have different values.
Recursively walks dict object to compare all keys.
:param desired_state: The state user desires.
:type desired_state: dict
:param current_state: The state that currently exists.
:type current_state: dict
:param ignore: Ignore these keys.
:type ignore: list
:return: Different items
:rtype: list
"""
if ignore is None:
ignore = []
differences = []
for key in desired_state:
if key in ignore:
continue
if key not in current_state:
differences.append(key)
continue
new_value = desired_state[key]
old_value = current_state[key]
if (
"ttl" in key and (_convert_to_seconds(old_value) != _convert_to_seconds(new_value))
) or not _compare_state(new_value, old_value):
differences.append(key)
return differences
def is_state_changed(desired_state, current_state, ignore=None): # noqa: ARG001
"""
Return list of keys that have different values.
Recursively walks dict object to compare all keys.
:param desired_state: The state user desires.
:type desired_state: dict
:param current_state: The state that currently exists.
:type current_state: dict
:param ignore: Ignore these keys.
:type ignore: list
:return: Different
:rtype: bool
"""
return len(get_keys_updated(desired_state, current_state)) > 0

View File

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2021, Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
"""Provide version object to compare version numbers."""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible.module_utils.six import raise_from
try:
from ansible.module_utils.compat.version import LooseVersion # noqa: F401,E501 pylint: disable=unused-import
except ImportError:
try:
from distutils.version import LooseVersion # noqa: F401, pylint: disable=unused-import
except ImportError as exc:
msg = (
"To use this plugin or module with ansible-core 2.11, you need to use Python < 3.12 "
"with distutils.version present"
)
raise_from(ImportError(msg), exc)

View File

@ -1,256 +0,0 @@
# -*- coding: utf-8 -*-
"""Module to control corenetworks DNS API."""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"}
DOCUMENTATION = r"""
---
module: corenetworks_dns
short_description: Interface with the DNS API of core-networks.de
description:
- "Manages DNS zones and records via the core networks API, see the docs: U(https://beta.api.core-networks.de/doc/)."
options:
api_user:
description:
- Account API username. If omitted, the environment variables C(CN_API_USER) and C(CN_API_PASSWORD) will be looked for.
- You should prefere to use `api_token` or the `corenetworks_token` module to create one to prevent running into rate limits.
type: str
api_password:
description:
- Account API password.
type: str
api_token:
description:
- Account API token.
type: str
zone:
description:
- The name of the Zone to work with (e.g. "example.com").
- The Zone must already exist.
zone:
type: str
required: true
aliases: [ domain ]
record:
description:
- Used record relative to the given zone.
- Default is C(@) (e.g. the zone name).
type: str
default: "@"
aliases: [ name ]
type:
description:
- The type of DNS record to create.
choices: [ "A", "ALIAS", "CNAME", "MX", "SPF", "URL", "TXT", "NS", "SRV", "NAPTR", "PTR", "AAAA", "SSHFP", "HINFO", "POOL" ]
type: str
ttl:
description:
- The TTL to give the new record in seconds.
default: 3600
type: int
value:
description:
- Record value.
- Must be specified when trying to ensure a record exists.
type: str
solo:
description:
- Whether the record should be the only one for that record type and record name.
- Only use with C(state=present).
- This will delete all other records with the same record name and type.
type: bool
state:
description:
- whether the record should exist or not
choices: [ "present", "absent" ]
default: present
type: str
requirements:
- "corenetworks >= 0.1.4"
author: "Robert Kaussow (@xoxys)"
""" # noqa
EXAMPLES = """
- name: Create a test.my.com A record to point to 127.0.0.1
corenetworks_dns:
zone: my.com
record: test
type: A
value: 127.0.0.1
delegate_to: localhost
register: record
- name: Create a my.com CNAME record to example.com
corenetworks_dns:
zone: my.com
type: CNAME
value: example.com
state: present
delegate_to: localhost
- name: Change TTL value for a record
corenetworks_dns:
zone: my.com
type: CNAME
value: example.com
ttl: 600
state: present
delegate_to: localhost
- name: Delete the record
corenetworks_dns:
zone: my.com
type: CNAME
value: example.com
state: absent
delegate_to: localhost
"""
RETURN = r"""# """
import copy
import traceback
CORENETWORKS_IMP_ERR = None
try:
from corenetworks import CoreNetworks
from corenetworks.exceptions import CoreNetworksException
HAS_CORENETWORKS = True
except ImportError:
CORENETWORKS_IMP_ERR = traceback.format_exc()
HAS_CORENETWORKS = False
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
def delete_records(client, module, zone, params, is_solo=False):
changed = False
search = copy.deepcopy(params)
if is_solo:
search.pop("data", None)
search.pop("ttl", None)
records = client.records(zone, params=search)
for r in records:
r["ttl"] = int(r["ttl"])
if is_solo:
if not (r["data"] == params["data"] and r["ttl"] == params["ttl"]):
changed = True
if not module.check_mode:
client.delete_record(zone, r)
else:
changed = True
if not module.check_mode:
client.delete_record(zone, r)
return changed
def add_record(client, module, zone, params):
changed = False
result = []
records = client.records(zone, params=params)
if len(records) > 1:
module.fail_json(
msg="More than one record already exists for the given attributes. "
"That should be impossible, please open an issue!"
)
if len(records) == 0:
changed = True
if not module.check_mode:
result = client.add_record(zone, params=params)
return result, changed
def main():
module = AnsibleModule(
argument_spec=dict(
api_user=dict(type="str"),
api_password=dict(type="str", no_log=True),
api_token=dict(type="str", no_log=True),
zone=dict(type="str", required=True, aliases=["domain"]),
record=dict(type="str", default="@", aliases=["name"]),
type=dict(
type="str",
choices=[
"A", "ALIAS", "CNAME", "MX", "SPF", "URL", "TXT", "NS", "SRV", "NAPTR", "PTR",
"AAAA", "SSHFP", "HINFO", "POOL"
]
),
ttl=dict(type="int", default=3600),
value=dict(type="str"),
solo=dict(type="bool", default=False),
state=dict(type="str", choices=["present", "absent"], default="present"),
),
required_together=[["record", "value"]],
supports_check_mode=True,
)
if not HAS_CORENETWORKS:
module.fail_json(msg=missing_required_lib("corenetworks"), exception=CORENETWORKS_IMP_ERR)
api_user = module.params.get("api_user")
api_password = module.params.get("api_password")
api_token = module.params.get("api_token")
zone = module.params.get("zone")
record = module.params.get("record")
record_type = module.params.get("type")
ttl = module.params.get("ttl")
value = module.params.get("value")
state = module.params.get("state")
is_solo = module.params.get("solo")
params = {"name": record, "ttl": ttl}
# sanity checks
if not record_type:
if state == "present":
module.fail_json(msg="Missing the record type")
else:
params["type"] = record_type
if not value:
if state == "present":
module.fail_json(msg="Missing the record value")
else:
params["data"] = value
if is_solo and state == "absent":
module.fail_json(msg="solo=true can only be used with state=present")
# perform actions
try:
# request throtteling to workaround the current rate limit
changed = False
if api_token:
client = CoreNetworks(api_token=api_token, auto_commit=True)
else:
client = CoreNetworks(user=api_user, password=api_password, auto_commit=True)
if state == "present":
changed_solo = False
if is_solo:
changed_solo = delete_records(client, module, zone, params, is_solo=True)
result, changed = add_record(client, module, zone, params)
module.exit_json(changed=changed_solo + changed, result=result)
# state is absent
else:
changed = delete_records(client, module, zone, params)
module.exit_json(changed=changed)
except CoreNetworksException as e:
module.fail_json(msg="Failure in core networks API communication: {}".format(str(e)))
module.fail_json(msg="Unknown what you wanted me to do")
if __name__ == "__main__":
main()

View File

@ -1,112 +0,0 @@
# -*- coding: utf-8 -*-
"""Module to control corenetworks DNS API."""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"}
DOCUMENTATION = """
---
module: corenetworks_dns
short_description: Interface with the DNS API of core-networks.de
description:
- "Manages DNS zones and records via the core networks API, see the docs: U(https://beta.api.core-networks.de/doc/)."
options:
api_user:
description:
- Account API username. If omitted, the environment variables C(CN_API_USER) and C(CN_API_PASSWORD) will be looked for.
type: str
api_password:
description:
- Account API password.
type: str
state:
description:
- whether the record should exist or not
choices: [ "present" ]
default: present
type: str
requirements:
- "corenetworks >= 0.1.3"
author: "Robert Kaussow (@xoxys)"
""" # noqa
EXAMPLES = """
- name: Obtain an API token using env variables
corenetworks_token:
delegate_to: localhost
register: my_token
- name: Obtain an API token using username and password attribute
corenetworks_token:
api_user: testuser
api_password: secure
delegate_to: localhost
register: my_token
- debug:
msg: "{{ my_token }}"
- name: Use the token
corenetworks_dns:
api_token: "{{ my_token.session.token }}"
zone: my.com
type: CNAME
value: example.com
state: present
delegate_to: localhost
"""
RETURN = r"""# """
import traceback
CORENETWORKS_IMP_ERR = None
try:
from corenetworks import CoreNetworks
from corenetworks.exceptions import CoreNetworksException
HAS_CORENETWORKS = True
except ImportError:
CORENETWORKS_IMP_ERR = traceback.format_exc()
HAS_CORENETWORKS = False
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
def main():
module = AnsibleModule(
argument_spec=dict(
api_user=dict(type="str"),
api_password=dict(type="str", no_log=True),
state=dict(type="str", choices=["present"], default="present"),
),
supports_check_mode=True,
)
if not HAS_CORENETWORKS:
module.fail_json(msg=missing_required_lib("corenetworks"), exception=CORENETWORKS_IMP_ERR)
api_user = module.params.get("api_user")
api_password = module.params.get("api_password")
# perform actions
try:
# request throtteling to workaround the current rate limit
changed = False
client = CoreNetworks(user=api_user, password=api_password, auto_commit=True)
session = {"token": client._auth.token}
if hasattr(client._auth, "expires"):
session["expires"] = client._auth.expires.strftime("%Y-%m-%d, %H:%M:%S")
module.exit_json(changed=changed, session=session)
except CoreNetworksException as e:
module.fail_json(msg="Failure in core networks API communication: {}".format(str(e)))
module.fail_json(msg="Unknown what you wanted me to do")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,72 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""Unseal Hashicorp Vault servers."""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {"status": ["stableinterface"], "supported_by": "community", "version": "1.1"}
DOCUMENTATION = """
---
module: hashivault_unseal
short_description: Hashicorp Vault unseal module.
version_added: 1.2.0
description:
- "Module to unseal Hashicorp Vault."
options:
keys:
description:
- Vault key shard(s).
type: list
elements: str
required: true
author:
- Robert Kaussow (@xoxys)
extends_documentation_fragment:
- xoxys.general.hashivault
"""
EXAMPLES = """
---
- name: Unseal vault
hashivault_unseal:
keys:
- 26479cc0-54bc-4252-9c34-baca54aa5de7
- 47f942e3-8525-4b44-ba2f-84a4ae81db7d
- 2ee9c868-4275-4836-8747-4f8fb7611aa0
url: https://vault.example.com
"""
from ansible_collections.xoxys.general.plugins.module_utils.hashivault import hashivault_argspec
from ansible_collections.xoxys.general.plugins.module_utils.hashivault import hashivault_client
from ansible_collections.xoxys.general.plugins.module_utils.hashivault import hashivault_init
from ansible_collections.xoxys.general.plugins.module_utils.hashivault import hashiwrapper
def main():
argspec = hashivault_argspec()
argspec["keys"] = dict(required=True, type="list", elements="str", no_log=True)
module = hashivault_init(argspec)
result = hashivault_unseal(module.params)
if result.get("failed"):
module.fail_json(**result)
else:
module.exit_json(**result)
@hashiwrapper
def hashivault_unseal(params):
keys = params.get("keys")
client = hashivault_client(params)
if client.sys.is_sealed():
return {"status": client.sys.submit_unseal_keys(keys), "changed": True}
return {"changed": False}
if __name__ == "__main__":
main()

View File

@ -1,3 +1,4 @@
#!/usr/bin/python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
IPtables raw module. IPtables raw module.
@ -18,16 +19,20 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with Ansible. If not, see <http://www.gnu.org/licenses/>. along with Ansible. If not, see <http://www.gnu.org/licenses/>.
""" """
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
ANSIBLE_METADATA = {'status': ['preview'], 'supported_by': 'community', 'metadata_version': '1.0'} ANSIBLE_METADATA = {'status': ['preview'], 'supported_by': 'community', 'metadata_version': '1.0'}
DOCUMENTATION = r''' DOCUMENTATION = r'''
--- ---
module: iptables_raw module: iptables_raw
short_description: Manage iptables rules short_description: Manage iptables rules
version_added: "2.4" version_added: 1.1.0
description: description:
- Add/remove iptables rules while keeping state. - Add/remove iptables rules while keeping state.
options: options:
@ -35,13 +40,14 @@ options:
description: description:
- Create a backup of the iptables state file before overwriting it. - Create a backup of the iptables state file before overwriting it.
required: false required: false
choices: ["yes", "no"] type: bool
default: "no" default: False
ipversion: ipversion:
description: description:
- Target the IP version this rule is for. - Target the IP version this rule is for.
required: false required: false
default: "4" default: "4"
type: str
choices: ["4", "6"] choices: ["4", "6"]
keep_unmanaged: keep_unmanaged:
description: description:
@ -54,8 +60,8 @@ options:
first time, since if you don't specify correct rules, you can block first time, since if you don't specify correct rules, you can block
yourself out of the managed host." yourself out of the managed host."
required: false required: false
choices: ["yes", "no"] type: bool
default: "yes" default: True
name: name:
description: description:
- Name that will be used as an identifier for these rules. It can contain - Name that will be used as an identifier for these rules. It can contain
@ -64,17 +70,21 @@ options:
C(state=absent) to flush all rules in the selected table, or even all C(state=absent) to flush all rules in the selected table, or even all
tables with C(table=*). tables with C(table=*).
required: true required: true
type: str
rules: rules:
description: description:
- The rules that we want to add. Accepts multiline values. - The rules that we want to add. Accepts multiline values.
- "Note: You can only use C(-A)/C(--append), C(-N)/C(--new-chain), and - "Note: You can only use C(-A)/C(--append), C(-N)/C(--new-chain), and
C(-P)/C(--policy) to specify rules." C(-P)/C(--policy) to specify rules."
required: false required: false
type: str
default: ""
state: state:
description: description:
- The state this rules fragment should be in. - The state this rules fragment should be in.
choices: ["present", "absent"] choices: ["present", "absent"]
required: false required: false
type: str
default: present default: present
table: table:
description: description:
@ -82,12 +92,13 @@ options:
with C(name=*) and C(state=absent) to flush all rules in all tables. with C(name=*) and C(state=absent) to flush all rules in all tables.
choices: ["filter", "nat", "mangle", "raw", "security", "*"] choices: ["filter", "nat", "mangle", "raw", "security", "*"]
required: false required: false
type: str
default: filter default: filter
weight: weight:
description: description:
- Determines the order of the rules. Lower C(weight) means higher - Determines the order of the rules. Lower C(weight) means higher
priority. Supported range is C(0 - 99) priority. Supported range is C(0 - 99)
choices: ["0 - 99"] type: int
required: false required: false
default: 40 default: 40
notes: notes:
@ -116,7 +127,7 @@ EXAMPLES = '''
- iptables_raw: - iptables_raw:
name: default_rules name: default_rules
weight: 10 weight: 10
keep_unmanaged: no keep_unmanaged: false
rules: | rules: |
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -i lo -j ACCEPT -A INPUT -i lo -j ACCEPT
@ -156,12 +167,12 @@ RETURN = '''
state: state:
description: state of the rules description: state of the rules
returned: success returned: success
type: string type: str
sample: present sample: present
name: name:
description: name of the rules description: name of the rules
returned: success returned: success
type: string type: str
sample: open_tcp_80 sample: open_tcp_80
weight: weight:
description: weight of the rules description: weight of the rules
@ -176,22 +187,22 @@ ipversion:
rules: rules:
description: passed rules description: passed rules
returned: success returned: success
type: string type: str
sample: "-A INPUT -p tcp -m tcp --dport 80 -j ACCEPT" sample: "-A INPUT -p tcp -m tcp --dport 80 -j ACCEPT"
table: table:
description: iptables table used description: iptables table used
returned: success returned: success
type: string type: str
sample: filter sample: filter
backup: backup:
description: if the iptables file should backed up description: if the iptables file should backed up
returned: success returned: success
type: boolean type: bool
sample: False sample: False
keep_unmanaged: keep_unmanaged:
description: if it should keep unmanaged rules description: if it should keep unmanaged rules
returned: success returned: success
type: boolean type: bool
sample: True sample: True
''' '''
@ -205,6 +216,7 @@ from collections import defaultdict
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import json from ansible.module_utils.basic import json
from ansible_collections.xoxys.general.plugins.module_utils.version import LooseVersion
# Genereates a diff dictionary from an old and new table dump. # Genereates a diff dictionary from an old and new table dump.
@ -346,7 +358,6 @@ class Iptables:
# Checks if iptables is installed and if we have a correct version. # Checks if iptables is installed and if we have a correct version.
def _check_compatibility(self): def _check_compatibility(self):
from distutils.version import StrictVersion
cmd = [self.bins['iptables'], '--version'] cmd = [self.bins['iptables'], '--version']
rc, stdout, stderr = Iptables.module.run_command(cmd, check_rc=False) rc, stdout, stderr = Iptables.module.run_command(cmd, check_rc=False)
if rc == 0: if rc == 0:
@ -355,7 +366,7 @@ class Iptables:
version = result.group(1) version = result.group(1)
# CentOS 5 ip6tables (v1.3.x) doesn't support comments, # CentOS 5 ip6tables (v1.3.x) doesn't support comments,
# which means it cannot be used with this module. # which means it cannot be used with this module.
if StrictVersion(version) < StrictVersion('1.4'): if LooseVersion(version) < LooseVersion('1.4'):
Iptables.module.fail_json( Iptables.module.fail_json(
msg="This module isn't compatible with ip6tables versions older than 1.4.x" msg="This module isn't compatible with ip6tables versions older than 1.4.x"
) )

View File

@ -1,89 +1,109 @@
#!/usr/bin/python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""OpenSSL PKCS12 module.""" """OpenSSL PKCS12 module."""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {"metadata_version": "1.0", "status": ["preview"], "supported_by": "community"} ANSIBLE_METADATA = {"metadata_version": "1.0", "status": ["preview"], "supported_by": "community"}
DOCUMENTATION = """ DOCUMENTATION = """
--- ---
module: openssl_pkcs12 module: openssl_pkcs12
author: "Guillaume Delpierre (@gdelpierre)" author: "Guillaume Delpierre (@gdelpierre)"
version_added: "2.4" version_added: 1.1.0
short_description: Generate OpenSSL pkcs12 archive. short_description: Generate OpenSSL pkcs12 archive.
description: description:
- "This module allows one to (re-)generate PKCS#12." - "This module allows one to (re-)generate PKCS#12."
requirements: requirements:
- "python-pyOpenSSL" - "python-pyOpenSSL"
extends_documentation_fragment: files
options: options:
ca_certificates: ca_certificates:
required: False required: False
description: type: list
- List of CA certificate to include. elements: str
cert_path: description:
required: False - List of CA certificate to include.
description: cert_path:
- The path to read certificates and private keys from. required: False
Must be in PEM format. type: path
action: description:
required: False - The path to read certificates and private keys from.
default: "export" Must be in PEM format.
choices: ["parse", "export"] action:
description: required: False
- Create (export) or parse a PKCS#12. default: "export"
src: choices: ["parse", "export"]
required: False type: str
description: description:
- PKCS#12 file path to parse. - Create (export) or parse a PKCS#12.
path: src:
required: True required: False
default: null type: path
description: description:
- Filename to write the PKCS#12 file to. - PKCS#12 file path to parse.
force: path:
required: False required: True
default: False type: path
description: description:
- Should the file be regenerated even it it already exists. - Filename to write the PKCS#12 file to.
friendly_name: force:
required: False required: False
default: null default: False
aliases: "name" type: bool
description: description:
- Specifies the friendly name for the certificate and private key. - Should the file be regenerated even it it already exists.
iter_size: friendly_name:
required: False required: False
default: 2048 type: str
description: aliases:
- Number of times to repeat the encryption step. - "name"
maciter_size: description:
required: False - Specifies the friendly name for the certificate and private key.
default: 1 iter_size:
description: required: False
- Number of times to repeat the MAC step. default: 2048
mode: type: int
required: False description:
default: 0400 - Number of times to repeat the encryption step.
description: maciter_size:
- Default mode for the generated PKCS#12 file. required: False
passphrase: default: 1
required: False type: int
default: null description:
description: - Number of times to repeat the MAC step.
- The PKCS#12 password. mode:
privatekey_path: required: False
required: False default: "0400"
description: type: str
- File to read private key from. description:
privatekey_passphrase: - Default mode for the generated PKCS#12 file.
required: False passphrase:
default: null required: False
description: type: str
- Passphrase source to decrypt any input private keys with. description:
state: - The PKCS#12 password.
required: False privatekey_path:
default: "present" required: False
choices: ["present", "absent"] type: path
description: description:
- Whether the file should exist or not. - File to read private key from.
privatekey_passphrase:
required: False
type: str
description:
- Passphrase source to decrypt any input private keys with.
state:
required: False
default: "present"
choices: ["present", "absent"]
type: str
description:
- Whether the file should exist or not.
""" """
EXAMPLES = """ EXAMPLES = """
@ -131,10 +151,10 @@ EXAMPLES = """
RETURN = """ RETURN = """
filename: filename:
description: Path to the generate PKCS#12 file. description: Path to the generate PKCS#12 file.
returned: changed or success returned: changed or success
type: string type: str
sample: /opt/certs/ansible.p12 sample: /opt/certs/ansible.p12
""" """
import errno import errno
@ -151,12 +171,11 @@ else:
pyopenssl_found = True pyopenssl_found = True
class PkcsError(Exception): class PkcsError(Exception): # noqa
pass pass
class Pkcs(object): class Pkcs(object): # noqa
def __init__(self, module): def __init__(self, module):
self.path = module.params["path"] self.path = module.params["path"]
self.force = module.params["force"] self.force = module.params["force"]
@ -181,56 +200,56 @@ class Pkcs(object):
def load_privatekey(self, path, passphrase=None): def load_privatekey(self, path, passphrase=None):
"""Load the specified OpenSSL private key.""" """Load the specified OpenSSL private key."""
try: try:
if passphrase: return (
privatekey = crypto.load_privatekey( crypto.load_privatekey(
crypto.FILETYPE_PEM, crypto.FILETYPE_PEM,
open(path, "rb").read(), passphrase open(path, "rb").read(), # noqa
passphrase,
) )
else: if passphrase
privatekey = crypto.load_privatekey(crypto.FILETYPE_PEM, open(path, "rb").read()) else crypto.load_privatekey(
crypto.FILETYPE_PEM,
return privatekey open(path, "rb").read(), # noqa
except (IOError, OSError) as exc: )
raise PkcsError(exc) )
except OSError as exc:
raise PkcsError(exc) from exc
def load_certificate(self, path): def load_certificate(self, path):
"""Load the specified certificate.""" """Load the specified certificate."""
try: try:
cert_content = open(path, "rb").read() cert_content = open(path, "rb").read() # noqa
cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert_content) return crypto.load_certificate(crypto.FILETYPE_PEM, cert_content)
return cert except OSError as exc:
except (IOError, OSError) as exc: raise PkcsError(exc) from exc
raise PkcsError(exc)
def load_pkcs12(self, path, passphrase=None): def load_pkcs12(self, path, passphrase=None):
"""Load pkcs12 file.""" """Load pkcs12 file."""
try: try:
if passphrase: if passphrase:
return crypto.load_pkcs12(open(path, "rb").read(), passphrase) return crypto.load_pkcs12(open(path, "rb").read(), passphrase) # noqa
else:
return crypto.load_pkcs12(open(path, "rb").read()) return crypto.load_pkcs12(open(path, "rb").read()) # noqa
except (IOError, OSError) as exc: except OSError as exc:
raise PkcsError(exc) raise PkcsError(exc) from exc
def dump_privatekey(self, path): def dump_privatekey(self, path):
"""Dump the specified OpenSSL private key.""" """Dump the specified OpenSSL private key."""
try: try:
return crypto.dump_privatekey( return crypto.dump_privatekey(
crypto.FILETYPE_PEM, crypto.FILETYPE_PEM, self.load_pkcs12(path).get_privatekey()
self.load_pkcs12(path).get_privatekey()
) )
except (IOError, OSError) as exc: except OSError as exc:
raise PkcsError(exc) raise PkcsError(exc) from exc
def dump_certificate(self, path): def dump_certificate(self, path):
"""Dump the specified certificate.""" """Dump the specified certificate."""
try: try:
return crypto.dump_certificate( return crypto.dump_certificate(
crypto.FILETYPE_PEM, crypto.FILETYPE_PEM, self.load_pkcs12(path).get_certificate()
self.load_pkcs12(path).get_certificate()
) )
except (IOError, OSError) as exc: except OSError as exc:
raise PkcsError(exc) raise PkcsError(exc) from exc
def generate(self, module): def generate(self, module):
"""Generate PKCS#12 file archive.""" """Generate PKCS#12 file archive."""
@ -264,9 +283,9 @@ class Pkcs(object):
) )
module.set_mode_if_different(self.path, self.mode, False) module.set_mode_if_different(self.path, self.mode, False)
self.changed = True self.changed = True
except (IOError, OSError) as exc: except OSError as exc:
self.remove() self.remove()
raise PkcsError(exc) raise PkcsError(exc) from exc
file_args = module.load_file_common_arguments(module.params) file_args = module.load_file_common_arguments(module.params)
if module.set_fs_attributes_if_different(file_args, False): if module.set_fs_attributes_if_different(file_args, False):
@ -281,14 +300,12 @@ class Pkcs(object):
with open(self.path, "wb") as content: with open(self.path, "wb") as content:
content.write( content.write(
"{0}{1}".format( f"{self.dump_privatekey(self.src)}{self.dump_certificate(self.src)}"
self.dump_privatekey(self.src), self.dump_certificate(self.src)
)
) )
module.set_mode_if_different(self.path, self.mode, False) module.set_mode_if_different(self.path, self.mode, False)
self.changed = True self.changed = True
except IOError as exc: except OSError as exc:
raise PkcsError(exc) raise PkcsError(exc) from exc
file_args = module.load_file_common_arguments(module.params) file_args = module.load_file_common_arguments(module.params)
if module.set_fs_attributes_if_different(file_args, False): if module.set_fs_attributes_if_different(file_args, False):
@ -302,12 +319,11 @@ class Pkcs(object):
self.changed = True self.changed = True
except OSError as exc: except OSError as exc:
if exc.errno != errno.ENOENT: if exc.errno != errno.ENOENT:
raise PkcsError(exc) raise PkcsError(exc) from exc
else:
pass
def check(self, module, perms_required=True): pass
def check(self, module, perms_required=True): # noqa
def _check_pkey_passphrase(): def _check_pkey_passphrase():
if self.privatekey_passphrase: if self.privatekey_passphrase:
try: try:
@ -337,19 +353,20 @@ class Pkcs(object):
def main(): def main():
argument_spec = dict( argument_spec = dict(
action=dict(default="export", choices=["parse", "export"], type="str"), action=dict(default="export", choices=["parse", "export"], type="str", required=False),
ca_certificates=dict(type="list"), ca_certificates=dict(type="list", elements="str", required=False),
cert_path=dict(type="path"), cert_path=dict(type="path"),
force=dict(default=False, type="bool"), force=dict(default=False, type="bool"),
friendly_name=dict(type="str", aliases=["name"]), friendly_name=dict(type="str", aliases=["name"]),
iter_size=dict(default=2048, type="int"), iter_size=dict(default=2048, type="int"),
maciter_size=dict(default=1, type="int"), maciter_size=dict(default=1, type="int"),
passphrase=dict(type="str", no_log=True), passphrase=dict(type="str", no_log=True),
path=dict(required=True, type="path"), path=dict(type="path", required=True),
privatekey_path=dict(type="path"), privatekey_path=dict(type="path"),
privatekey_passphrase=dict(type="str", no_log=True), privatekey_passphrase=dict(type="str", no_log=True),
state=dict(default="present", choices=["present", "absent"], type="str"), state=dict(default="present", choices=["present", "absent"], type="str"),
src=dict(type="path"), src=dict(type="path"),
mode=dict(default="0400", type="str", required=False),
) )
required_if = [ required_if = [
@ -376,8 +393,7 @@ def main():
if not os.path.isdir(base_dir): if not os.path.isdir(base_dir):
module.fail_json( module.fail_json(
name=base_dir, name=base_dir,
msg="The directory {0} does not exist or " msg=f"The directory {base_dir} does not exist or the file is not a directory",
"the file is not a directory".format(base_dir)
) )
pkcs12 = Pkcs(module) pkcs12 = Pkcs(module)

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,12 @@
#!/usr/bin/python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""Module to control Univention Corporate Registry."""
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""Control Univention Corporate Registry."""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"}
@ -7,28 +14,33 @@ DOCUMENTATION = """
--- ---
module: ucr module: ucr
short_description: Manage variables in univention configuration registry. short_description: Manage variables in univention configuration registry.
version_added: "2.6" version_added: 1.1.0
description: description:
- "This module allows to manage variables inside the univention configuration registry - "This module allows to manage variables inside the univention configuration registry
on a univention corporate server (UCS)." on a univention corporate server (UCS)."
options: options:
path: path:
description: description:
- Path for the variable - Path for the variable
required: True aliases:
default: null - name
value: required: True
description: type: str
- New value of the variable value:
required: False description:
state: - New value of the variable
required: False required: False
default: "present" type: str
choices: ["present", "absent"] default: ""
description: state:
- Whether the variable should be exist or not. required: False
default: "present"
choices: ["present", "absent"]
type: str
description:
- Whether the variable should be exist or not.
author: author:
- Robert Kaussow (@xoxys) - Robert Kaussow (@xoxys)
""" """
EXAMPLES = """ EXAMPLES = """
@ -47,45 +59,48 @@ EXAMPLES = """
RETURN = """ RETURN = """
original_message: original_message:
description: The original name param that was passed in description: The original name param that was passed in
type: str type: str
returned: success
message: message:
description: The output message that the sample module generates description: The output message that the sample module generates
type: str
returned: success
""" """
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
from univention.config_registry import ConfigRegistry # noqa
from univention.config_registry.frontend import ucr_update # noqa try:
from univention.config_registry import ConfigRegistry
from univention.config_registry.frontend import ucr_update
HAS_UNIVENTION = True
except ImportError:
HAS_UNIVENTION = False
def get_variable(ucr, path): def get_variable(ucr, path):
ucr.load() ucr.load()
if path in ucr: return ucr.get(path) if path in ucr else None
value = ucr.get(path)
else:
value = None
return value
def set_variable(ucr, path, value, result): def set_variable(ucr, path, value, result): # noqa
org_value = get_variable(ucr, path) org_value = get_variable(ucr, path)
ucr_update(ucr, {path: value}) ucr_update(ucr, {path: value})
new_value = get_variable(ucr, path) new_value = get_variable(ucr, path)
return not org_value == new_value return org_value != new_value
def dry_variable(ucr, path, value, result): def dry_variable(ucr, path, value, result): # noqa
org_value = get_variable(ucr, path) org_value = get_variable(ucr, path)
return not org_value == value return org_value != value
def main(): def main():
ucr = ConfigRegistry()
module_args = dict( module_args = dict(
path=dict(type="str", required=True, aliases=["name"]), path=dict(type="str", required=True, aliases=["name"]),
value=dict(type="str", required=False, default=""), value=dict(type="str", required=False, default=""),
state=dict(default="present", choices=["present", "absent"], type="str") state=dict(default="present", choices=["present", "absent"], type="str"),
) )
required_if = [["state", "present", ["value"]]] required_if = [["state", "present", ["value"]]]
@ -94,13 +109,17 @@ def main():
argument_spec=module_args, supports_check_mode=True, required_if=required_if argument_spec=module_args, supports_check_mode=True, required_if=required_if
) )
if not HAS_UNIVENTION:
module.fail_json(msg="univention required for this module")
ucr = ConfigRegistry()
result = dict(changed=False, original_message="", message="") result = dict(changed=False, original_message="", message="")
path = module.params["path"] path = module.params["path"]
value = module.params["value"] value = module.params["value"]
if module.params["state"] == "present": if module.params["state"] == "present" and (value is None or value == "None"):
if value is None or value == "None": value = ""
value = ""
elif module.params["state"] == "absent": elif module.params["state"] == "absent":
value = None value = None

1158
poetry.lock generated Normal file

File diff suppressed because it is too large Load Diff

152
pyproject.toml Normal file
View File

@ -0,0 +1,152 @@
[tool.poetry]
authors = ["Robert Kaussow <mail@thegeeklab.de>"]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Environment :: Console",
"License :: OSI Approved :: MIT License",
"Intended Audience :: Developers",
"Intended Audience :: Information Technology",
"Intended Audience :: System Administrators",
"Natural Language :: English",
"Operating System :: POSIX",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Utilities",
"Topic :: Software Development",
"Topic :: Software Development :: Documentation",
]
description = "Build environment for Ansible Collection."
license = "MIT"
name = "xoxys.general"
readme = "README.md"
repository = "https://gitea.rknet.org/ansible/xoxys.general"
version = "0.0.0"
[tool.poetry.dependencies]
python = "^3.9.0"
ansible-core = { version = "<=2.14.0", optional = true }
pyopenssl = "23.0.0"
proxmoxer = "2.0.1"
hcloud = "1.18.2"
[tool.poetry.extras]
ansible = ["ansible-core"]
[tool.poetry.group.dev.dependencies]
ruff = "0.1.7"
pytest = "7.2.1"
pytest-mock = "3.10.0"
pytest-cov = "4.0.0"
toml = "0.10.2"
pycodestyle = "2.10.0"
yamllint = "1.29.0"
pylint = "2.15.0"
voluptuous = "0.13.1"
pytest-ansible = "3.1.5"
pytest-forked = "1.6.0"
pytest-xdist = "3.3.1"
[tool.pytest.ini_options]
addopts = "--cov --cov-report=xml:coverage.xml --cov-report=term --cov-append --no-cov-on-fail"
pythonpath = [
"."
]
testpaths = [
"tests",
]
filterwarnings = [
"ignore::FutureWarning",
"ignore::DeprecationWarning",
"ignore:.*pep8.*:FutureWarning",
"ignore:AnsibleCollectionFinder.*:UserWarning"
]
[tool.coverage.run]
omit = ["**/tests/*"]
[build-system]
build-backend = "poetry_dynamic_versioning.backend"
requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning"]
[tool.ruff]
exclude = [
".git",
"__pycache__",
"build",
"dist",
"tests",
"*.pyc",
"*.egg-info",
".cache",
".eggs",
"env*",
".venv",
"iptables_raw.py",
]
line-length = 99
indent-width = 4
# Explanation of errors
#
# D102: Missing docstring in public method
# D103: Missing docstring in public function
# D105: Missing docstring in magic method
# D107: Missing docstring in __init__
# D202: No blank lines allowed after function docstring
# D203: One blank line required before class docstring
# E402: Module level import not at top of file
# SIM105: Use `contextlib.suppress(Exception)` instead of try-except-pass
# C402: Unnecessary generator (rewrite as a `dict` comprehension)
# C408: Unnecessary `dict` call (rewrite as a literal)
# I001: Import block is un-sorted or un-formatted
# UP001: `__metaclass__ = type` is implied
# UP009: UTF-8 encoding declaration is unnecessary
# UP010: Unnecessary `__future__` imports `absolute_import`, `division`, `print_function` for target Python version
ignore = [
"D102",
"D103",
"D105",
"D107",
"D202",
"D203",
"D212",
"E402",
"SIM105",
"C402",
"C408",
"I001",
"UP001",
"UP009",
"UP010",
"RUF100",
]
select = [
"D",
"E",
"F",
"Q",
"W",
"I",
"S",
"BLE",
"N",
"UP",
"B",
"A",
"C4",
"T20",
"SIM",
"RET",
"ARG",
"ERA",
"RUF",
]
[tool.ruff.format]
quote-style = "double"
indent-style = "space"
line-ending = "lf"

View File

@ -1,25 +0,0 @@
[isort]
default_section = THIRDPARTY
known_first_party = ansiblelater
sections = FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER
force_single_line = true
line_length = 99
skip_glob = **/.env*,**/env/*,**/docs/*,**/inventory/*,**/modules/*
[yapf]
based_on_style = google
column_limit = 99
dedent_closing_brackets = true
coalesce_brackets = true
split_before_logical_operator = true
[tool:pytest]
filterwarnings =
ignore::FutureWarning
ignore:.*collections.*:DeprecationWarning
ignore:.*pep8.*:FutureWarning
[coverage:run]
omit =
**/test/*
**/.env/*

View File

@ -1 +0,0 @@
# noqa

View File

@ -1,11 +0,0 @@
ansible
# requirement for the proxmox modules
proxmoxer
requests
# requirement for the corenetworks modules
corenetworks
# requirement for the openssl_pkcs12 module
pyOpenSSL

2
tests/config.yml Normal file
View File

@ -0,0 +1,2 @@
modules:
python_requires: ">=3.9"

View File

@ -1,16 +1,16 @@
"""Test inventory plugin proxmox.""" """Test inventory plugin proxmox."""
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Robert Kaussow <mail@thegeeklab.de> # Copyright (c) 2020, Robert Kaussow <mail@thegeeklab.de>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # 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) from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import pytest import pytest
proxmox = pytest.importorskip("proxmoxer") proxmox = pytest.importorskip("proxmoxer")
from ansible.errors import AnsibleError, AnsibleParserError # noqa from ansible_collections.xoxys.general.plugins.inventory.proxmox import InventoryModule
from plugins.inventory.proxmox import InventoryModule
@pytest.fixture @pytest.fixture
@ -58,7 +58,7 @@ def test_get_ip_address(inventory, mocker):
inventory.client = mocker.MagicMock() inventory.client = mocker.MagicMock()
inventory.client.nodes.return_value.get.return_value = networks inventory.client.nodes.return_value.get.return_value = networks
assert "10.0.0.1" == inventory._get_ip_address("qemu", None, None) assert inventory._get_ip_address("qemu", None, None) == "10.0.0.1"
def test_exclude(inventory, mocker): def test_exclude(inventory, mocker):