Compare commits

...

4 Commits

Author SHA1 Message Date
Robert Kaussow 8e11973364
temp remove git branch publishing step
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
2023-07-30 22:48:23 +02:00
Robert Kaussow d37d217a78 ci: fix docs and create release branches (#6)
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is failing Details
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
Robert Kaussow 9226ab6209 feat: add hashivault_unseal module (#5)
continuous-integration/drone/push Build is passing Details
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
Robert Kaussow 14999b6f12
fix drone-matrix template
continuous-integration/drone/push Build is passing Details
2023-02-08 21:27:04 +01:00
13 changed files with 1143 additions and 495 deletions

View File

@ -6,7 +6,6 @@ local PythonVersion(pyversion='3.8') = {
}, },
commands: [ commands: [
'pip install poetry -qq', 'pip install poetry -qq',
'poetry config experimental.new-installer false',
'poetry install --all-extras', 'poetry install --all-extras',
'poetry run pytest', 'poetry run pytest',
], ],
@ -18,17 +17,16 @@ local PythonVersion(pyversion='3.8') = {
local AnsibleVersion(version='devel') = { local AnsibleVersion(version='devel') = {
local gitversion = if version == 'devel' then 'devel' else 'stable-' + version, local gitversion = if version == 'devel' then 'devel' else 'stable-' + version,
name: 'ansible-' + std.strReplace(version, '.', ''), name: 'ansible-' + std.strReplace(version, '.', ''),
image: 'python:3.9', image: 'python:3.10',
environment: { environment: {
PY_COLORS: 1, PY_COLORS: 1,
}, },
commands: [ commands: [
'pip install poetry -qq', 'pip install poetry -qq',
'poetry config experimental.new-installer false',
'poetry install', 'poetry install',
'poetry run pip install https://github.com/ansible/ansible/archive/' + gitversion + '.tar.gz --disable-pip-version-check', 'poetry run pip install https://github.com/ansible/ansible/archive/' + gitversion + '.tar.gz --disable-pip-version-check',
'poetry run ansible --version', 'poetry run ansible --version',
'poetry run ansible-test sanity --exclude .chglog/ --exclude .drone.yml --python 3.9', 'poetry run ansible-test sanity --exclude .chglog/ --exclude .drone.yml --python 3.10',
], ],
depends_on: [ depends_on: [
'clone', 'clone',
@ -52,7 +50,6 @@ local PipelineLint = {
commands: [ commands: [
'git fetch -tq', 'git fetch -tq',
'pip install poetry -qq', 'pip install poetry -qq',
'poetry config experimental.new-installer false',
'poetry install --all-extras', 'poetry install --all-extras',
'poetry run yapf -dr ./plugins', 'poetry run yapf -dr ./plugins',
], ],
@ -66,7 +63,6 @@ local PipelineLint = {
commands: [ commands: [
'git fetch -tq', 'git fetch -tq',
'pip install poetry -qq', 'pip install poetry -qq',
'poetry config experimental.new-installer false',
'poetry install --all-extras', 'poetry install --all-extras',
'poetry run ruff ./plugins', 'poetry run ruff ./plugins',
], ],
@ -110,8 +106,8 @@ local PipelineSanityTest = {
}, },
steps: [ steps: [
AnsibleVersion(version='devel'), AnsibleVersion(version='devel'),
AnsibleVersion(version='2.15'),
AnsibleVersion(version='2.14'), AnsibleVersion(version='2.14'),
AnsibleVersion(version='2.13'),
], ],
depends_on: [ depends_on: [
'unit-test', 'unit-test',
@ -136,7 +132,6 @@ local PipelineBuild = {
'GALAXY_VERSION=${DRONE_TAG##v}', 'GALAXY_VERSION=${DRONE_TAG##v}',
"sed -i 's/version: 0.0.0/version: '\"$${GALAXY_VERSION:-0.0.0}\"'/g' galaxy.yml", "sed -i 's/version: 0.0.0/version: '\"$${GALAXY_VERSION:-0.0.0}\"'/g' galaxy.yml",
'pip install poetry -qq', 'pip install poetry -qq',
'poetry config experimental.new-installer false',
'poetry install --all-extras', 'poetry install --all-extras',
'poetry run ansible-galaxy collection build --output-path dist/', 'poetry run ansible-galaxy collection build --output-path dist/',
], ],
@ -198,18 +193,21 @@ local PipelineDocumentation = {
steps: [ steps: [
{ {
name: 'publish', name: 'publish',
image: 'plugins/gh-pages', image: 'thegeeklab/drone-git-action',
settings: { settings: {
action: [
'pages',
],
author_email: 'shipper@rknet.org',
author_name: 'shipper',
branch: 'docs',
message: 'auto-update documentation',
netrc_machine: 'gitea.rknet.org', netrc_machine: 'gitea.rknet.org',
pages_directory: 'docs/', netrc_password: {
password: {
from_secret: 'gitea_token', from_secret: 'gitea_token',
}, },
pages_directory: 'docs/',
remote_url: 'https://gitea.rknet.org/ansible/${DRONE_REPO_NAME}', remote_url: 'https://gitea.rknet.org/ansible/${DRONE_REPO_NAME}',
target_branch: 'docs',
username: {
from_secret: 'gitea_username',
},
}, },
}, },
], ],
@ -235,7 +233,7 @@ local PipelineNotifications = {
settings: { settings: {
homeserver: { from_secret: 'matrix_homeserver' }, homeserver: { from_secret: 'matrix_homeserver' },
roomid: { from_secret: 'matrix_roomid' }, roomid: { from_secret: 'matrix_roomid' },
template: 'Status: **{{ build.Status }}**<br/> Build: [{{ repo.Owner }}/{{ repo.Name }}]({{ build.Link }}){{#if build.Branch}} ({{ build.Branch }}){{/if}} by {{ commit.Author }}<br/> Message: {{ commit.Message.Title }}', template: 'Status: **{{ .Build.Status }}**<br/> Build: [{{ .Repo.Owner }}/{{ .Repo.Name }}]({{ .Build.Link }}){{ if .Build.Branch }} ({{ .Build.Branch }}){{ end }} by {{ .Commit.Author }}<br/> Message: {{ .Commit.Message.Title }}',
username: { from_secret: 'matrix_username' }, username: { from_secret: 'matrix_username' },
password: { from_secret: 'matrix_password' }, password: { from_secret: 'matrix_password' },
}, },

View File

@ -12,7 +12,6 @@ steps:
commands: commands:
- git fetch -tq - git fetch -tq
- pip install poetry -qq - pip install poetry -qq
- poetry config experimental.new-installer false
- poetry install --all-extras - poetry install --all-extras
- poetry run yapf -dr ./plugins - poetry run yapf -dr ./plugins
environment: environment:
@ -23,7 +22,6 @@ steps:
commands: commands:
- git fetch -tq - git fetch -tq
- pip install poetry -qq - pip install poetry -qq
- poetry config experimental.new-installer false
- poetry install --all-extras - poetry install --all-extras
- poetry run ruff ./plugins - poetry run ruff ./plugins
environment: environment:
@ -48,7 +46,6 @@ steps:
image: python:3.8 image: python:3.8
commands: commands:
- pip install poetry -qq - pip install poetry -qq
- poetry config experimental.new-installer false
- poetry install --all-extras - poetry install --all-extras
- poetry run pytest - poetry run pytest
environment: environment:
@ -60,7 +57,6 @@ steps:
image: python:3.9 image: python:3.9
commands: commands:
- pip install poetry -qq - pip install poetry -qq
- poetry config experimental.new-installer false
- poetry install --all-extras - poetry install --all-extras
- poetry run pytest - poetry run pytest
environment: environment:
@ -72,7 +68,6 @@ steps:
image: python:3.10 image: python:3.10
commands: commands:
- pip install poetry -qq - pip install poetry -qq
- poetry config experimental.new-installer false
- poetry install --all-extras - poetry install --all-extras
- poetry run pytest - poetry run pytest
environment: environment:
@ -84,7 +79,6 @@ steps:
image: python:3.11 image: python:3.11
commands: commands:
- pip install poetry -qq - pip install poetry -qq
- poetry config experimental.new-installer false
- poetry install --all-extras - poetry install --all-extras
- poetry run pytest - poetry run pytest
environment: environment:
@ -114,42 +108,39 @@ workspace:
steps: steps:
- name: ansible-devel - name: ansible-devel
image: python:3.9 image: python:3.10
commands: commands:
- pip install poetry -qq - pip install poetry -qq
- poetry config experimental.new-installer false
- poetry install - poetry install
- poetry run pip install https://github.com/ansible/ansible/archive/devel.tar.gz --disable-pip-version-check - poetry run pip install https://github.com/ansible/ansible/archive/devel.tar.gz --disable-pip-version-check
- poetry run ansible --version - poetry run ansible --version
- poetry run ansible-test sanity --exclude .chglog/ --exclude .drone.yml --python 3.9 - poetry run ansible-test sanity --exclude .chglog/ --exclude .drone.yml --python 3.10
environment:
PY_COLORS: 1
depends_on:
- clone
- name: ansible-215
image: python:3.10
commands:
- pip install poetry -qq
- poetry install
- poetry run pip install https://github.com/ansible/ansible/archive/stable-2.15.tar.gz --disable-pip-version-check
- poetry run ansible --version
- poetry run ansible-test sanity --exclude .chglog/ --exclude .drone.yml --python 3.10
environment: environment:
PY_COLORS: 1 PY_COLORS: 1
depends_on: depends_on:
- clone - clone
- name: ansible-214 - name: ansible-214
image: python:3.9 image: python:3.10
commands: commands:
- pip install poetry -qq - pip install poetry -qq
- poetry config experimental.new-installer false
- poetry install - poetry install
- poetry run pip install https://github.com/ansible/ansible/archive/stable-2.14.tar.gz --disable-pip-version-check - poetry run pip install https://github.com/ansible/ansible/archive/stable-2.14.tar.gz --disable-pip-version-check
- poetry run ansible --version - poetry run ansible --version
- poetry run ansible-test sanity --exclude .chglog/ --exclude .drone.yml --python 3.9 - poetry run ansible-test sanity --exclude .chglog/ --exclude .drone.yml --python 3.10
environment:
PY_COLORS: 1
depends_on:
- clone
- name: ansible-213
image: python:3.9
commands:
- pip install poetry -qq
- poetry config experimental.new-installer false
- poetry install
- poetry run pip install https://github.com/ansible/ansible/archive/stable-2.13.tar.gz --disable-pip-version-check
- poetry run ansible --version
- poetry run ansible-test sanity --exclude .chglog/ --exclude .drone.yml --python 3.9
environment: environment:
PY_COLORS: 1 PY_COLORS: 1
depends_on: depends_on:
@ -179,7 +170,6 @@ steps:
- GALAXY_VERSION=${DRONE_TAG##v} - GALAXY_VERSION=${DRONE_TAG##v}
- "sed -i 's/version: 0.0.0/version: '\"$${GALAXY_VERSION:-0.0.0}\"'/g' galaxy.yml" - "sed -i 's/version: 0.0.0/version: '\"$${GALAXY_VERSION:-0.0.0}\"'/g' galaxy.yml"
- pip install poetry -qq - pip install poetry -qq
- poetry config experimental.new-installer false
- poetry install --all-extras - poetry install --all-extras
- poetry run ansible-galaxy collection build --output-path dist/ - poetry run ansible-galaxy collection build --output-path dist/
@ -235,16 +225,19 @@ platform:
steps: steps:
- name: publish - name: publish
image: plugins/gh-pages image: thegeeklab/drone-git-action
settings: settings:
action:
- pages
author_email: shipper@rknet.org
author_name: shipper
branch: docs
message: auto-update documentation
netrc_machine: gitea.rknet.org netrc_machine: gitea.rknet.org
pages_directory: docs/ netrc_password:
password:
from_secret: gitea_token from_secret: gitea_token
pages_directory: docs/
remote_url: https://gitea.rknet.org/ansible/${DRONE_REPO_NAME} remote_url: https://gitea.rknet.org/ansible/${DRONE_REPO_NAME}
target_branch: docs
username:
from_secret: gitea_username
trigger: trigger:
ref: ref:
@ -272,7 +265,7 @@ steps:
from_secret: matrix_password from_secret: matrix_password
roomid: roomid:
from_secret: matrix_roomid from_secret: matrix_roomid
template: "Status: **{{ build.Status }}**<br/> Build: [{{ repo.Owner }}/{{ repo.Name }}]({{ build.Link }}){{#if build.Branch}} ({{ build.Branch }}){{/if}} by {{ commit.Author }}<br/> Message: {{ commit.Message.Title }}" template: "Status: **{{ .Build.Status }}**<br/> Build: [{{ .Repo.Owner }}/{{ .Repo.Name }}]({{ .Build.Link }}){{ if .Build.Branch }} ({{ .Build.Branch }}){{ end }} by {{ .Commit.Author }}<br/> Message: {{ .Commit.Message.Title }}"
username: username:
from_secret: matrix_username from_secret: matrix_username
@ -289,6 +282,6 @@ depends_on:
--- ---
kind: signature kind: signature
hmac: 407d145ab4483651c579eea4e1e9375536caee1aa0579abcdf57b5653e595cb5 hmac: 15e594a6375a31faf1edde6398fed9db285688296d920f2c8b5acc637efb1ec4
... ...

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

@ -99,13 +99,13 @@ import json
import re import re
import socket import socket
from collections import defaultdict from collections import defaultdict
from distutils.version import LooseVersion
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.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 ansible_collections.xoxys.general.plugins.module_utils.version import LooseVersion
try: try:
from proxmoxer import ProxmoxAPI from proxmoxer import ProxmoxAPI

View File

@ -0,0 +1,407 @@
"""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 (type(desired_state) is list):
if ((type(current_state) != list) or (len(desired_state) != len(current_state))):
return False
return set(desired_state) == set(current_state)
if (type(desired_state) is dict):
if (type(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
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)):
differences.append(key)
elif 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

@ -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

@ -216,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.
@ -357,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:
@ -366,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

@ -813,9 +813,9 @@ import string
import time import time
import traceback import traceback
from collections import defaultdict from collections import defaultdict
from distutils.version import LooseVersion
from ansible.module_utils.six.moves.urllib.parse import quote from ansible.module_utils.six.moves.urllib.parse import quote
from ansible_collections.xoxys.general.plugins.module_utils.version import LooseVersion
try: try:
from proxmoxer import ProxmoxAPI from proxmoxer import ProxmoxAPI

918
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -46,6 +46,9 @@ pycodestyle = "2.10.0"
yamllint = "1.29.0" yamllint = "1.29.0"
pylint = "2.15.0" pylint = "2.15.0"
voluptuous = "0.13.1" voluptuous = "0.13.1"
pytest-ansible = "3.1.5"
pytest-forked = "1.6.0"
pytest-xdist = "3.3.1"
[tool.pytest.ini_options] [tool.pytest.ini_options]
addopts = "--cov --cov-report=xml:coverage.xml --cov-report=term --cov-append --no-cov-on-fail" addopts = "--cov --cov-report=xml:coverage.xml --cov-report=term --cov-append --no-cov-on-fail"
@ -59,6 +62,7 @@ filterwarnings = [
"ignore::FutureWarning", "ignore::FutureWarning",
"ignore::DeprecationWarning", "ignore::DeprecationWarning",
"ignore:.*pep8.*:FutureWarning", "ignore:.*pep8.*:FutureWarning",
"ignore:AnsibleCollectionFinder.*:UserWarning"
] ]
[tool.coverage.run] [tool.coverage.run]
@ -80,6 +84,7 @@ exclude = [
".cache", ".cache",
".eggs", ".eggs",
"env*", "env*",
".venv",
"iptables_raw.py", "iptables_raw.py",
] ]
# Explanation of errors # Explanation of errors
@ -114,6 +119,7 @@ ignore = [
"UP001", "UP001",
"UP009", "UP009",
"UP010", "UP010",
"RUF100",
] ]
line-length = 99 line-length = 99
select = [ select = [

View File

@ -10,8 +10,7 @@ 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