refactor: rework ci and testing #3
@ -5,9 +5,10 @@ local PythonVersion(pyversion='3.8') = {
|
|||||||
PY_COLORS: 1,
|
PY_COLORS: 1,
|
||||||
},
|
},
|
||||||
commands: [
|
commands: [
|
||||||
'pip install -r dev-requirements.txt -qq',
|
'pip install poetry -qq',
|
||||||
'pip install -r test/unit/requirements.txt -qq',
|
'poetry config experimental.new-installer false',
|
||||||
'python -m pytest --cov --cov-append --no-cov-on-fail',
|
'poetry install',
|
||||||
|
'poetry run pytest',
|
||||||
],
|
],
|
||||||
depends_on: [
|
depends_on: [
|
||||||
'clone',
|
'clone',
|
||||||
@ -23,14 +24,31 @@ local PipelineLint = {
|
|||||||
},
|
},
|
||||||
steps: [
|
steps: [
|
||||||
{
|
{
|
||||||
name: 'flake8',
|
name: 'check-format',
|
||||||
image: 'python:3.10',
|
image: 'python:3.11',
|
||||||
environment: {
|
environment: {
|
||||||
PY_COLORS: 1,
|
PY_COLORS: 1,
|
||||||
},
|
},
|
||||||
commands: [
|
commands: [
|
||||||
'pip install -r dev-requirements.txt -qq',
|
'git fetch -tq',
|
||||||
'flake8',
|
'pip install poetry -qq',
|
||||||
|
'poetry config experimental.new-installer false',
|
||||||
|
'poetry install',
|
||||||
|
'poetry run yapf -dr ./plugins',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'check-coding',
|
||||||
|
image: 'python:3.11',
|
||||||
|
environment: {
|
||||||
|
PY_COLORS: 1,
|
||||||
|
},
|
||||||
|
commands: [
|
||||||
|
'git fetch -tq',
|
||||||
|
'pip install poetry -qq',
|
||||||
|
'poetry config experimental.new-installer false',
|
||||||
|
'poetry install',
|
||||||
|
'poetry run ruff ./plugins',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -50,6 +68,7 @@ local PipelineTest = {
|
|||||||
PythonVersion(pyversion='3.8'),
|
PythonVersion(pyversion='3.8'),
|
||||||
PythonVersion(pyversion='3.9'),
|
PythonVersion(pyversion='3.9'),
|
||||||
PythonVersion(pyversion='3.10'),
|
PythonVersion(pyversion='3.10'),
|
||||||
|
PythonVersion(pyversion='3.11'),
|
||||||
],
|
],
|
||||||
depends_on: [
|
depends_on: [
|
||||||
'lint',
|
'lint',
|
||||||
@ -69,11 +88,13 @@ local PipelineBuild = {
|
|||||||
steps: [
|
steps: [
|
||||||
{
|
{
|
||||||
name: 'build',
|
name: 'build',
|
||||||
image: 'python:3.9',
|
image: 'python:3.11',
|
||||||
commands: [
|
commands: [
|
||||||
'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 ansible -qq',
|
'pip install poetry -qq',
|
||||||
|
'poetry config experimental.new-installer false',
|
||||||
|
'poetry install',
|
||||||
'ansible-galaxy collection build --output-path dist/',
|
'ansible-galaxy collection build --output-path dist/',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
63
.drone.yml
63
.drone.yml
@ -7,11 +7,25 @@ platform:
|
|||||||
arch: amd64
|
arch: amd64
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: flake8
|
- name: check-format
|
||||||
image: python:3.10
|
image: python:3.11
|
||||||
commands:
|
commands:
|
||||||
- pip install -r dev-requirements.txt -qq
|
- git fetch -tq
|
||||||
- flake8
|
- pip install poetry -qq
|
||||||
|
- poetry config experimental.new-installer false
|
||||||
|
- poetry install
|
||||||
|
- poetry run yapf -dr ./plugins
|
||||||
|
environment:
|
||||||
|
PY_COLORS: 1
|
||||||
|
|
||||||
|
- name: check-coding
|
||||||
|
image: python:3.11
|
||||||
|
commands:
|
||||||
|
- git fetch -tq
|
||||||
|
- pip install poetry -qq
|
||||||
|
- poetry config experimental.new-installer false
|
||||||
|
- poetry install
|
||||||
|
- poetry run ruff ./plugins
|
||||||
environment:
|
environment:
|
||||||
PY_COLORS: 1
|
PY_COLORS: 1
|
||||||
|
|
||||||
@ -33,9 +47,10 @@ steps:
|
|||||||
- name: python38-pytest
|
- name: python38-pytest
|
||||||
image: python:3.8
|
image: python:3.8
|
||||||
commands:
|
commands:
|
||||||
- pip install -r dev-requirements.txt -qq
|
- pip install poetry -qq
|
||||||
- pip install -r test/unit/requirements.txt -qq
|
- poetry config experimental.new-installer false
|
||||||
- python -m pytest --cov --cov-append --no-cov-on-fail
|
- poetry install
|
||||||
|
- poetry run pytest
|
||||||
environment:
|
environment:
|
||||||
PY_COLORS: 1
|
PY_COLORS: 1
|
||||||
depends_on:
|
depends_on:
|
||||||
@ -44,9 +59,10 @@ steps:
|
|||||||
- name: python39-pytest
|
- name: python39-pytest
|
||||||
image: python:3.9
|
image: python:3.9
|
||||||
commands:
|
commands:
|
||||||
- pip install -r dev-requirements.txt -qq
|
- pip install poetry -qq
|
||||||
- pip install -r test/unit/requirements.txt -qq
|
- poetry config experimental.new-installer false
|
||||||
- python -m pytest --cov --cov-append --no-cov-on-fail
|
- poetry install
|
||||||
|
- poetry run pytest
|
||||||
environment:
|
environment:
|
||||||
PY_COLORS: 1
|
PY_COLORS: 1
|
||||||
depends_on:
|
depends_on:
|
||||||
@ -55,9 +71,22 @@ steps:
|
|||||||
- name: python310-pytest
|
- name: python310-pytest
|
||||||
image: python:3.10
|
image: python:3.10
|
||||||
commands:
|
commands:
|
||||||
- pip install -r dev-requirements.txt -qq
|
- pip install poetry -qq
|
||||||
- pip install -r test/unit/requirements.txt -qq
|
- poetry config experimental.new-installer false
|
||||||
- python -m pytest --cov --cov-append --no-cov-on-fail
|
- poetry install
|
||||||
|
- poetry run pytest
|
||||||
|
environment:
|
||||||
|
PY_COLORS: 1
|
||||||
|
depends_on:
|
||||||
|
- clone
|
||||||
|
|
||||||
|
- name: python311-pytest
|
||||||
|
image: python:3.11
|
||||||
|
commands:
|
||||||
|
- pip install poetry -qq
|
||||||
|
- poetry config experimental.new-installer false
|
||||||
|
- poetry install
|
||||||
|
- poetry run pytest
|
||||||
environment:
|
environment:
|
||||||
PY_COLORS: 1
|
PY_COLORS: 1
|
||||||
depends_on:
|
depends_on:
|
||||||
@ -82,11 +111,13 @@ platform:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: build
|
- name: build
|
||||||
image: python:3.9
|
image: python:3.11
|
||||||
commands:
|
commands:
|
||||||
- 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 ansible -qq
|
- pip install poetry -qq
|
||||||
|
- poetry config experimental.new-installer false
|
||||||
|
- poetry install
|
||||||
- ansible-galaxy collection build --output-path dist/
|
- ansible-galaxy collection build --output-path dist/
|
||||||
|
|
||||||
- name: checksum
|
- name: checksum
|
||||||
@ -195,6 +226,6 @@ depends_on:
|
|||||||
|
|
||||||
---
|
---
|
||||||
kind: signature
|
kind: signature
|
||||||
hmac: 93f735c3d2fbaf499fd96b79301f6de2455349051dc320c511e6e62c8ba04a4d
|
hmac: e13ec0f71bb6c29530344335056ad14820d4028a28e6e74b40de0db2afbfe568
|
||||||
|
|
||||||
...
|
...
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -108,3 +108,4 @@ docs/public/
|
|||||||
resources/_gen/
|
resources/_gen/
|
||||||
|
|
||||||
CHANGELOG.md
|
CHANGELOG.md
|
||||||
|
tests/output
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
pydocstyle
|
|
||||||
flake8
|
|
||||||
flake8-blind-except
|
|
||||||
flake8-builtins
|
|
||||||
flake8-docstrings
|
|
||||||
flake8-isort
|
|
||||||
flake8-logging-format
|
|
||||||
flake8-polyfill
|
|
||||||
flake8-quotes
|
|
||||||
flake8-pep3101
|
|
||||||
flake8-eradicate
|
|
||||||
pep8-naming
|
|
||||||
wheel
|
|
||||||
pytest
|
|
||||||
pytest-mock
|
|
||||||
pytest-cov
|
|
||||||
bandit
|
|
||||||
yapf
|
|
@ -1,11 +1,15 @@
|
|||||||
"""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}
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
"""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}
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
# -*- 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>
|
||||||
# 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)
|
||||||
"""Dynamic inventory plugin for Proxmox VE."""
|
"""Dynamic inventory plugin for Proxmox VE."""
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
@ -13,7 +12,7 @@ DOCUMENTATION = """
|
|||||||
name: proxmox
|
name: proxmox
|
||||||
plugin_type: inventory
|
plugin_type: inventory
|
||||||
short_description: Proxmox VE inventory source
|
short_description: Proxmox VE inventory source
|
||||||
version_added: 1.0.0
|
version_added: 1.1.0
|
||||||
description:
|
description:
|
||||||
- Get inventory hosts from the proxmox service.
|
- 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."
|
- "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."
|
||||||
@ -46,7 +45,7 @@ DOCUMENTATION = """
|
|||||||
verify_ssl:
|
verify_ssl:
|
||||||
description: Skip SSL certificate verification.
|
description: Skip SSL certificate verification.
|
||||||
type: boolean
|
type: boolean
|
||||||
default: yes
|
default: True
|
||||||
auth_timeout:
|
auth_timeout:
|
||||||
description: Proxmox VE authentication timeout.
|
description: Proxmox VE authentication timeout.
|
||||||
type: int
|
type: int
|
||||||
@ -68,7 +67,7 @@ DOCUMENTATION = """
|
|||||||
want_facts:
|
want_facts:
|
||||||
description: Toggle, if C(true) the plugin will retrieve host facts from the server
|
description: Toggle, if C(true) the plugin will retrieve host facts from the server
|
||||||
type: boolean
|
type: boolean
|
||||||
default: yes
|
default: True
|
||||||
""" # noqa
|
""" # noqa
|
||||||
|
|
||||||
EXAMPLES = """
|
EXAMPLES = """
|
||||||
@ -82,14 +81,14 @@ password: secure
|
|||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
import socket
|
import socket
|
||||||
|
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 collections import defaultdict
|
|
||||||
from distutils.version import LooseVersion
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from proxmoxer import ProxmoxAPI
|
from proxmoxer import ProxmoxAPI
|
||||||
@ -97,16 +96,33 @@ try:
|
|||||||
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 _auth(self):
|
||||||
|
verify_ssl = boolean(self.get_option("verify_ssl"), strict=False)
|
||||||
|
|
||||||
|
if not verify_ssl and HAS_URLLIB3:
|
||||||
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||||
|
|
||||||
return ProxmoxAPI(
|
return ProxmoxAPI(
|
||||||
self.get_option("server"),
|
self.get_option("server"),
|
||||||
user=self.get_option("user"),
|
user=self.get_option("user"),
|
||||||
password=self.get_option("password"),
|
password=self.get_option("password"),
|
||||||
verify_ssl=boolean(self.get_option("password"), strict=False),
|
verify_ssl=verify_ssl,
|
||||||
timeout=self.get_option("auth_timeout")
|
timeout=self.get_option("auth_timeout")
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -117,14 +133,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 = {}
|
||||||
@ -143,11 +157,9 @@ class InventoryModule(BaseInventoryPlugin):
|
|||||||
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
|
|
||||||
if address != "127.0.0.1":
|
|
||||||
return address
|
return address
|
||||||
except socket.error:
|
except OSError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
address = False
|
address = False
|
||||||
@ -159,11 +171,10 @@ 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 type(networks) is list:
|
||||||
if type(networks) is list:
|
|
||||||
for network in networks:
|
for network in networks:
|
||||||
for ip_address in network["ip-addresses"]:
|
for ip_address in network["ip-addresses"]:
|
||||||
address = validate(ip_address["ip-address"])
|
address = validate(ip_address["ip-address"])
|
||||||
@ -171,7 +182,7 @@ class InventoryModule(BaseInventoryPlugin):
|
|||||||
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
|
||||||
@ -197,8 +208,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()
|
||||||
@ -217,8 +228,8 @@ class InventoryModule(BaseInventoryPlugin):
|
|||||||
"config")["description"]
|
"config")["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)
|
||||||
@ -252,8 +263,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"]
|
||||||
@ -266,13 +277,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(
|
||||||
@ -280,7 +291,7 @@ 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.client = self._auth()
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
IPtables raw module.
|
IPtables raw module.
|
||||||
@ -21,13 +22,17 @@ 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,20 @@ 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
|
||||||
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 +91,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 +126,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 +166,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 +186,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
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
@ -1,25 +1,36 @@
|
|||||||
|
#!/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
|
||||||
|
type: list
|
||||||
|
elements: str
|
||||||
description:
|
description:
|
||||||
- List of CA certificate to include.
|
- List of CA certificate to include.
|
||||||
cert_path:
|
cert_path:
|
||||||
required: False
|
required: False
|
||||||
|
type: path
|
||||||
description:
|
description:
|
||||||
- The path to read certificates and private keys from.
|
- The path to read certificates and private keys from.
|
||||||
Must be in PEM format.
|
Must be in PEM format.
|
||||||
@ -27,61 +38,70 @@ options:
|
|||||||
required: False
|
required: False
|
||||||
default: "export"
|
default: "export"
|
||||||
choices: ["parse", "export"]
|
choices: ["parse", "export"]
|
||||||
|
type: str
|
||||||
description:
|
description:
|
||||||
- Create (export) or parse a PKCS#12.
|
- Create (export) or parse a PKCS#12.
|
||||||
src:
|
src:
|
||||||
required: False
|
required: False
|
||||||
|
type: path
|
||||||
description:
|
description:
|
||||||
- PKCS#12 file path to parse.
|
- PKCS#12 file path to parse.
|
||||||
path:
|
path:
|
||||||
required: True
|
required: True
|
||||||
default: null
|
type: path
|
||||||
description:
|
description:
|
||||||
- Filename to write the PKCS#12 file to.
|
- Filename to write the PKCS#12 file to.
|
||||||
force:
|
force:
|
||||||
required: False
|
required: False
|
||||||
default: False
|
default: False
|
||||||
|
type: bool
|
||||||
description:
|
description:
|
||||||
- Should the file be regenerated even it it already exists.
|
- Should the file be regenerated even it it already exists.
|
||||||
friendly_name:
|
friendly_name:
|
||||||
required: False
|
required: False
|
||||||
default: null
|
type: str
|
||||||
aliases: "name"
|
aliases:
|
||||||
|
- "name"
|
||||||
description:
|
description:
|
||||||
- Specifies the friendly name for the certificate and private key.
|
- Specifies the friendly name for the certificate and private key.
|
||||||
iter_size:
|
iter_size:
|
||||||
required: False
|
required: False
|
||||||
default: 2048
|
default: 2048
|
||||||
|
type: int
|
||||||
description:
|
description:
|
||||||
- Number of times to repeat the encryption step.
|
- Number of times to repeat the encryption step.
|
||||||
maciter_size:
|
maciter_size:
|
||||||
required: False
|
required: False
|
||||||
default: 1
|
default: 1
|
||||||
|
type: int
|
||||||
description:
|
description:
|
||||||
- Number of times to repeat the MAC step.
|
- Number of times to repeat the MAC step.
|
||||||
mode:
|
mode:
|
||||||
required: False
|
required: False
|
||||||
default: 0400
|
default: "0400"
|
||||||
|
type: str
|
||||||
description:
|
description:
|
||||||
- Default mode for the generated PKCS#12 file.
|
- Default mode for the generated PKCS#12 file.
|
||||||
passphrase:
|
passphrase:
|
||||||
required: False
|
required: False
|
||||||
default: null
|
type: str
|
||||||
description:
|
description:
|
||||||
- The PKCS#12 password.
|
- The PKCS#12 password.
|
||||||
privatekey_path:
|
privatekey_path:
|
||||||
required: False
|
required: False
|
||||||
|
type: path
|
||||||
description:
|
description:
|
||||||
- File to read private key from.
|
- File to read private key from.
|
||||||
privatekey_passphrase:
|
privatekey_passphrase:
|
||||||
required: False
|
required: False
|
||||||
default: null
|
type: str
|
||||||
description:
|
description:
|
||||||
- Passphrase source to decrypt any input private keys with.
|
- Passphrase source to decrypt any input private keys with.
|
||||||
state:
|
state:
|
||||||
required: False
|
required: False
|
||||||
default: "present"
|
default: "present"
|
||||||
choices: ["present", "absent"]
|
choices: ["present", "absent"]
|
||||||
|
type: str
|
||||||
description:
|
description:
|
||||||
- Whether the file should exist or not.
|
- Whether the file should exist or not.
|
||||||
"""
|
"""
|
||||||
@ -133,7 +153,7 @@ 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
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -151,11 +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"]
|
||||||
@ -181,36 +201,37 @@ 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:
|
|
||||||
privatekey = crypto.load_privatekey(
|
privatekey = crypto.load_privatekey(
|
||||||
crypto.FILETYPE_PEM,
|
crypto.FILETYPE_PEM,
|
||||||
open(path, "rb").read(), passphrase
|
open(path, "rb").read(), # noqa
|
||||||
|
passphrase
|
||||||
|
) if passphrase else crypto.load_privatekey(
|
||||||
|
crypto.FILETYPE_PEM,
|
||||||
|
open(path, "rb").read() # noqa
|
||||||
)
|
)
|
||||||
else:
|
|
||||||
privatekey = crypto.load_privatekey(crypto.FILETYPE_PEM, open(path, "rb").read())
|
|
||||||
|
|
||||||
return privatekey
|
return privatekey
|
||||||
except (IOError, OSError) as exc:
|
except OSError as exc:
|
||||||
raise PkcsError(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)
|
cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert_content)
|
||||||
return cert
|
return cert
|
||||||
except (IOError, OSError) as exc:
|
except OSError as exc:
|
||||||
raise PkcsError(exc)
|
raise PkcsError(exc) from 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."""
|
||||||
@ -219,8 +240,8 @@ class Pkcs(object):
|
|||||||
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."""
|
||||||
@ -229,8 +250,8 @@ class Pkcs(object):
|
|||||||
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 +285,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 +302,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,11 +321,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
|
pass
|
||||||
|
|
||||||
def check(self, module, perms_required=True):
|
def check(self, module, perms_required=True): # noqa
|
||||||
|
|
||||||
def _check_pkey_passphrase():
|
def _check_pkey_passphrase():
|
||||||
if self.privatekey_passphrase:
|
if self.privatekey_passphrase:
|
||||||
@ -337,19 +356,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 +396,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)
|
||||||
|
@ -1,20 +1,69 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright: (c) 2016, Abdoul Bah (@helldorado) <bahabdoul at gmail.com>
|
# Copyright: (c) 2016, Abdoul Bah (@helldorado) <bahabdoul at gmail.com>
|
||||||
# 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)
|
||||||
"""Module to control Proxmox KVM machines."""
|
"""Control Proxmox KVM machines."""
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
DOCUMENTATION = """
|
DOCUMENTATION = r"""
|
||||||
---
|
---
|
||||||
module: proxmox_kvm
|
module: proxmox_kvm
|
||||||
short_description: Management of Qemu(KVM) Virtual Machines in Proxmox VE cluster.
|
short_description: Management of Qemu(KVM) Virtual Machines in Proxmox VE cluster.
|
||||||
description:
|
description:
|
||||||
- Allows you to create/delete/stop Qemu(KVM) Virtual Machines in Proxmox VE cluster.
|
- Allows you to create/delete/stop Qemu(KVM) Virtual Machines in Proxmox VE cluster.
|
||||||
author: "Abdoul Bah (@helldorado) <bahabdoul at gmail.com>"
|
author: "Abdoul Bah (@helldorado) <bahabdoul at gmail.com>"
|
||||||
|
version_added: 1.1.0
|
||||||
options:
|
options:
|
||||||
|
vmid:
|
||||||
|
description:
|
||||||
|
- Specifies the instance ID.
|
||||||
|
- If not set the next available ID will be fetched from ProxmoxAPI.
|
||||||
|
type: int
|
||||||
|
node:
|
||||||
|
description:
|
||||||
|
- Proxmox VE node on which to operate.
|
||||||
|
- Only required for I(state=present).
|
||||||
|
- For every other states it will be autodiscovered.
|
||||||
|
type: str
|
||||||
|
pool:
|
||||||
|
description:
|
||||||
|
- Add the new VM to the specified pool.
|
||||||
|
type: str
|
||||||
|
api_host:
|
||||||
|
description:
|
||||||
|
- Specify the target host of the Proxmox VE cluster.
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
api_user:
|
||||||
|
description:
|
||||||
|
- Specify the user to authenticate with.
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
api_password:
|
||||||
|
description:
|
||||||
|
- Specify the password to authenticate with.
|
||||||
|
- You can use C(PROXMOX_PASSWORD) environment variable.
|
||||||
|
type: str
|
||||||
|
api_token_id:
|
||||||
|
description:
|
||||||
|
- Specify the token ID.
|
||||||
|
type: str
|
||||||
|
version_added: 1.3.0
|
||||||
|
api_token_secret:
|
||||||
|
description:
|
||||||
|
- Specify the token secret.
|
||||||
|
type: str
|
||||||
|
version_added: 1.3.0
|
||||||
|
validate_certs:
|
||||||
|
description:
|
||||||
|
- If C(false), SSL certificates will not be validated.
|
||||||
|
- This should only be used on personally controlled sites using self-signed certificates.
|
||||||
|
type: bool
|
||||||
|
default: True
|
||||||
acpi:
|
acpi:
|
||||||
description:
|
description:
|
||||||
- Specify if ACPI should be enabled/disabled.
|
- Specify if ACPI should be enabled/disabled.
|
||||||
@ -65,12 +114,10 @@ options:
|
|||||||
description:
|
description:
|
||||||
- 'cloud-init: Specify custom files to replace the automatically generated ones at start.'
|
- 'cloud-init: Specify custom files to replace the automatically generated ones at start.'
|
||||||
type: str
|
type: str
|
||||||
version_added: 1.3.0
|
|
||||||
cipassword:
|
cipassword:
|
||||||
description:
|
description:
|
||||||
- 'cloud-init: password of default user to create.'
|
- 'cloud-init: password of default user to create.'
|
||||||
type: str
|
type: str
|
||||||
version_added: 1.3.0
|
|
||||||
citype:
|
citype:
|
||||||
description:
|
description:
|
||||||
- 'cloud-init: Specifies the cloud-init configuration format.'
|
- 'cloud-init: Specifies the cloud-init configuration format.'
|
||||||
@ -78,12 +125,10 @@ options:
|
|||||||
- We use the C(nocloud) format for Linux, and C(configdrive2) for Windows.
|
- We use the C(nocloud) format for Linux, and C(configdrive2) for Windows.
|
||||||
type: str
|
type: str
|
||||||
choices: ['nocloud', 'configdrive2']
|
choices: ['nocloud', 'configdrive2']
|
||||||
version_added: 1.3.0
|
|
||||||
ciuser:
|
ciuser:
|
||||||
description:
|
description:
|
||||||
- 'cloud-init: username of default user to create.'
|
- 'cloud-init: username of default user to create.'
|
||||||
type: str
|
type: str
|
||||||
version_added: 1.3.0
|
|
||||||
clone:
|
clone:
|
||||||
description:
|
description:
|
||||||
- Name of VM to be cloned. If C(vmid) is setted, C(clone) can take arbitrary value but required for initiating the clone.
|
- Name of VM to be cloned. If C(vmid) is setted, C(clone) can take arbitrary value but required for initiating the clone.
|
||||||
@ -197,7 +242,6 @@ options:
|
|||||||
- For IPv6 the special string 'auto' can be used to use stateless autoconfiguration.
|
- For IPv6 the special string 'auto' can be used to use stateless autoconfiguration.
|
||||||
- If cloud-init is enabled and neither an IPv4 nor an IPv6 address is specified, it defaults to using dhcp on IPv4.
|
- If cloud-init is enabled and neither an IPv4 nor an IPv6 address is specified, it defaults to using dhcp on IPv4.
|
||||||
type: dict
|
type: dict
|
||||||
version_added: 1.3.0
|
|
||||||
keyboard:
|
keyboard:
|
||||||
description:
|
description:
|
||||||
- Sets the keyboard layout for VNC server.
|
- Sets the keyboard layout for VNC server.
|
||||||
@ -249,7 +293,6 @@ options:
|
|||||||
- If unset, PVE host settings are used.
|
- If unset, PVE host settings are used.
|
||||||
type: list
|
type: list
|
||||||
elements: str
|
elements: str
|
||||||
version_added: 1.3.0
|
|
||||||
net:
|
net:
|
||||||
description:
|
description:
|
||||||
- A hash/dictionary of network interfaces for the VM. C(net='{"key":"value", "key":"value"}').
|
- A hash/dictionary of network interfaces for the VM. C(net='{"key":"value", "key":"value"}').
|
||||||
@ -341,7 +384,6 @@ options:
|
|||||||
- If unset, PVE host settings are used.
|
- If unset, PVE host settings are used.
|
||||||
type: list
|
type: list
|
||||||
elements: str
|
elements: str
|
||||||
version_added: 1.3.0
|
|
||||||
serial:
|
serial:
|
||||||
description:
|
description:
|
||||||
- A hash/dictionary of serial device to create inside the VM. C('{"key":"value", "key":"value"}').
|
- A hash/dictionary of serial device to create inside the VM. C('{"key":"value", "key":"value"}').
|
||||||
@ -379,7 +421,6 @@ options:
|
|||||||
description:
|
description:
|
||||||
- 'cloud-init: SSH key to assign to the default user. NOT TESTED with multiple keys but a multi-line value should work.'
|
- 'cloud-init: SSH key to assign to the default user. NOT TESTED with multiple keys but a multi-line value should work.'
|
||||||
type: str
|
type: str
|
||||||
version_added: 1.3.0
|
|
||||||
startdate:
|
startdate:
|
||||||
description:
|
description:
|
||||||
- Sets the initial date of the real time clock.
|
- Sets the initial date of the real time clock.
|
||||||
@ -415,7 +456,6 @@ options:
|
|||||||
- Tags are only available in Proxmox 6+.
|
- Tags are only available in Proxmox 6+.
|
||||||
type: list
|
type: list
|
||||||
elements: str
|
elements: str
|
||||||
version_added: 2.3.0
|
|
||||||
target:
|
target:
|
||||||
description:
|
description:
|
||||||
- Target node. Only allowed if the original VM is on shared storage.
|
- Target node. Only allowed if the original VM is on shared storage.
|
||||||
@ -477,10 +517,6 @@ options:
|
|||||||
choices:
|
choices:
|
||||||
- compatibility
|
- compatibility
|
||||||
- no_defaults
|
- no_defaults
|
||||||
version_added: "1.3.0"
|
|
||||||
extends_documentation_fragment:
|
|
||||||
- xoxys.general.proxmox.documentation
|
|
||||||
- xoxys.general.proxmox.selection
|
|
||||||
""" # noqa
|
""" # noqa
|
||||||
|
|
||||||
EXAMPLES = """
|
EXAMPLES = """
|
||||||
@ -711,7 +747,7 @@ EXAMPLES = """
|
|||||||
name: spynal
|
name: spynal
|
||||||
node: sabrewulf
|
node: sabrewulf
|
||||||
revert: 'template,cpulimit'
|
revert: 'template,cpulimit'
|
||||||
""" # noqa
|
"""
|
||||||
|
|
||||||
RETURN = """
|
RETURN = """
|
||||||
vmid:
|
vmid:
|
||||||
@ -735,9 +771,10 @@ import re
|
|||||||
import string
|
import string
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
from distutils.version import LooseVersion
|
|
||||||
from ansible.module_utils.six.moves.urllib.parse import quote
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
from distutils.version import LooseVersion
|
||||||
|
|
||||||
|
from ansible.module_utils.six.moves.urllib.parse import quote
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from proxmoxer import ProxmoxAPI
|
from proxmoxer import ProxmoxAPI
|
||||||
@ -745,17 +782,17 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
HAS_PROXMOXER = False
|
HAS_PROXMOXER = False
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule, env_fallback
|
|
||||||
from ansible.module_utils._text import to_native
|
from ansible.module_utils._text import to_native
|
||||||
|
from ansible.module_utils.basic import AnsibleModule, env_fallback
|
||||||
|
|
||||||
|
|
||||||
def get_nextvmid(module, proxmox):
|
def get_nextvmid(module, proxmox):
|
||||||
try:
|
try:
|
||||||
vmid = proxmox.cluster.nextid.get()
|
vmid = proxmox.cluster.nextid.get()
|
||||||
return vmid
|
return vmid
|
||||||
except Exception as e:
|
except Exception as e: # noqa
|
||||||
module.fail_json(
|
module.fail_json(
|
||||||
msg="Unable to get next vmid. Failed with exception: {}".format(to_native(e)),
|
msg=f"Unable to get next vmid. Failed with exception: {to_native(e)}",
|
||||||
exception=traceback.format_exc()
|
exception=traceback.format_exc()
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -775,14 +812,13 @@ def node_check(proxmox, node):
|
|||||||
|
|
||||||
|
|
||||||
def get_vminfo(module, proxmox, node, vmid, **kwargs):
|
def get_vminfo(module, proxmox, node, vmid, **kwargs):
|
||||||
global results # noqa
|
global results
|
||||||
results = {}
|
results = {}
|
||||||
try:
|
try:
|
||||||
vm = proxmox.nodes(node).qemu(vmid).config.get()
|
vm = proxmox.nodes(node).qemu(vmid).config.get()
|
||||||
except Exception as e:
|
except Exception as e: # noqa
|
||||||
module.fail_json(
|
module.fail_json(
|
||||||
msg="Getting information for VM with vmid = {} failed with exception: {}".
|
msg=f"Getting information for VM with vmid = {vmid} failed with exception: {e}"
|
||||||
format(vmid, e)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Sanitize kwargs. Remove not defined args and ensure True and False converted to int.
|
# Sanitize kwargs. Remove not defined args and ensure True and False converted to int.
|
||||||
@ -800,16 +836,13 @@ def get_vminfo(module, proxmox, node, vmid, **kwargs):
|
|||||||
results["_raw"] = vm
|
results["_raw"] = vm
|
||||||
|
|
||||||
|
|
||||||
def settings(module, proxmox, vmid, node, name, **kwargs):
|
def settings(module, proxmox, vmid, node, name, **kwargs): # noqa
|
||||||
proxmox_node = proxmox.nodes(node)
|
proxmox_node = proxmox.nodes(node)
|
||||||
|
|
||||||
# Sanitize kwargs. Remove not defined args and ensure True and False converted to int.
|
# Sanitize kwargs. Remove not defined args and ensure True and False converted to int.
|
||||||
kwargs = dict((k, v) for k, v in kwargs.items() if v is not None)
|
kwargs = dict((k, v) for k, v in kwargs.items() if v is not None)
|
||||||
|
|
||||||
if proxmox_node.qemu(vmid).config.set(**kwargs) is None:
|
return (proxmox_node.qemu(vmid).config.set(**kwargs) is None)
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def wait_for_task(module, proxmox, node, taskid):
|
def wait_for_task(module, proxmox, node, taskid):
|
||||||
@ -840,7 +873,7 @@ def create_vm(
|
|||||||
clone_params = {}
|
clone_params = {}
|
||||||
# Default args for vm. Note: -args option is for experts only. It allows you
|
# Default args for vm. Note: -args option is for experts only. It allows you
|
||||||
# to pass arbitrary arguments to kvm.
|
# to pass arbitrary arguments to kvm.
|
||||||
vm_args = "-serial unix:/var/run/qemu-server/{0}.serial,server,nowait".format(vmid)
|
vm_args = f"-serial unix:/var/run/qemu-server/{vmid}.serial,server,nowait"
|
||||||
|
|
||||||
proxmox_node = proxmox.nodes(node)
|
proxmox_node = proxmox.nodes(node)
|
||||||
|
|
||||||
@ -849,13 +882,13 @@ def create_vm(
|
|||||||
kwargs.update(dict([k, int(v)] for k, v in kwargs.items() if isinstance(v, bool)))
|
kwargs.update(dict([k, int(v)] for k, v in kwargs.items() if isinstance(v, bool)))
|
||||||
|
|
||||||
# The features work only on PVE 4+
|
# The features work only on PVE 4+
|
||||||
if PVE_MAJOR_VERSION < 4: # noqa
|
if PVE_MAJOR_VERSION < 4:
|
||||||
for p in only_v4:
|
for p in only_v4:
|
||||||
if p in kwargs:
|
if p in kwargs:
|
||||||
del kwargs[p]
|
del kwargs[p]
|
||||||
|
|
||||||
# The features work only on PVE 6
|
# The features work only on PVE 6
|
||||||
if PVE_MAJOR_VERSION < 6: # noqa
|
if PVE_MAJOR_VERSION < 6:
|
||||||
for p in only_v6:
|
for p in only_v6:
|
||||||
if p in kwargs:
|
if p in kwargs:
|
||||||
del kwargs[p]
|
del kwargs[p]
|
||||||
@ -875,19 +908,19 @@ def create_vm(
|
|||||||
|
|
||||||
# If update, ensure existing disks are not recreated.
|
# If update, ensure existing disks are not recreated.
|
||||||
if update:
|
if update:
|
||||||
for k, v in disks.items():
|
for k, _v in disks.items():
|
||||||
if results["disks"].get(k):
|
if results["disks"].get(k):
|
||||||
kwargs[k.rstrip(string.digits)][k] = "{0}:{1},{2}".format(
|
storage_id = results["disks"][k]["storage_id"]
|
||||||
results["disks"][k]["storage_id"], results["disks"][k]["storage_opts"],
|
storage_opts = results["disks"][k]["storage_opts"]
|
||||||
",".join(disks[k]["opts"])
|
opts = ",".join(disks[k]["opts"])
|
||||||
)
|
kwargs[k.rstrip(string.digits)][k] = f"{storage_id}:{storage_opts},{opts}"
|
||||||
|
|
||||||
for k, v in nets.items():
|
for k, _v in nets.items():
|
||||||
if results["nets"].get(k):
|
if results["nets"].get(k):
|
||||||
kwargs[k.rstrip(string.digits)][k] = "{0}={1},{2}".format(
|
net_id = results["nets"][k]["net_id"]
|
||||||
results["nets"][k]["net_id"], results["nets"][k]["net_opts"],
|
net_opts = results["nets"][k]["net_opts"]
|
||||||
",".join(nets[k]["opts"])
|
opts = ",".join(nets[k]["opts"])
|
||||||
)
|
kwargs[k.rstrip(string.digits)][k] = f"{net_id}={net_opts},{opts}"
|
||||||
|
|
||||||
# Convert all dict in kwargs to elements.
|
# Convert all dict in kwargs to elements.
|
||||||
for k in list(kwargs.keys()):
|
for k in list(kwargs.keys()):
|
||||||
@ -914,12 +947,14 @@ def create_vm(
|
|||||||
if "tags" in kwargs:
|
if "tags" in kwargs:
|
||||||
for tag in kwargs["tags"]:
|
for tag in kwargs["tags"]:
|
||||||
if not re.match(r"^[a-z0-9_][a-z0-9_\-\+\.]*$", tag):
|
if not re.match(r"^[a-z0-9_][a-z0-9_\-\+\.]*$", tag):
|
||||||
module.fail_json(msg="{} is not a valid tag".format(tag))
|
module.fail_json(msg=f"{tag} is not a valid tag")
|
||||||
kwargs["tags"] = ",".join(kwargs["tags"])
|
kwargs["tags"] = ",".join(kwargs["tags"])
|
||||||
|
|
||||||
# -args and skiplock require root@pam user - but can not use api tokens
|
# -args and skiplock require root@pam user - but can not use api tokens
|
||||||
if module.params["api_user"] == "root@pam" and module.params["args"] is None:
|
if (
|
||||||
if not update and module.params["proxmox_default_behavior"] == "compatibility":
|
module.params["api_user"] == "root@pam" and module.params["args"] is None and not update
|
||||||
|
and module.params["proxmox_default_behavior"] == "compatibility"
|
||||||
|
):
|
||||||
kwargs["args"] = vm_args
|
kwargs["args"] = vm_args
|
||||||
elif module.params["api_user"] == "root@pam" and module.params["args"] is not None:
|
elif module.params["api_user"] == "root@pam" and module.params["args"] is not None:
|
||||||
kwargs["args"] = module.params["args"]
|
kwargs["args"] = module.params["args"]
|
||||||
@ -930,13 +965,12 @@ def create_vm(
|
|||||||
module.fail_json(msg="skiplock parameter require root@pam user. ")
|
module.fail_json(msg="skiplock parameter require root@pam user. ")
|
||||||
|
|
||||||
if update:
|
if update:
|
||||||
if proxmox_node.qemu(vmid).config.set(
|
return (
|
||||||
name=name, memory=memory, cpu=cpu, cores=cores, sockets=sockets, **kwargs
|
proxmox_node.qemu(vmid).config.
|
||||||
) is None:
|
set(name=name, memory=memory, cpu=cpu, cores=cores, sockets=sockets, **kwargs) is None
|
||||||
return True
|
)
|
||||||
else:
|
|
||||||
return False
|
if module.params["clone"] is not None:
|
||||||
elif module.params["clone"] is not None:
|
|
||||||
for param in valid_clone_params:
|
for param in valid_clone_params:
|
||||||
if module.params[param] is not None:
|
if module.params[param] is not None:
|
||||||
clone_params[param] = module.params[param]
|
clone_params[param] = module.params[param]
|
||||||
@ -952,8 +986,7 @@ def create_vm(
|
|||||||
if not wait_for_task(module, proxmox, node, taskid):
|
if not wait_for_task(module, proxmox, node, taskid):
|
||||||
module.fail_json(
|
module.fail_json(
|
||||||
msg="Reached timeout while waiting for creating VM."
|
msg="Reached timeout while waiting for creating VM."
|
||||||
"Last line in task before timeout: {}".
|
f"Last line in task before timeout: {proxmox_node.tasks(taskid).log.get()[:1]}"
|
||||||
format(proxmox_node.tasks(taskid).log.get()[:1])
|
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
@ -966,8 +999,7 @@ def start_vm(module, proxmox, vm):
|
|||||||
if not wait_for_task(module, proxmox, vm[0]["node"], taskid):
|
if not wait_for_task(module, proxmox, vm[0]["node"], taskid):
|
||||||
module.fail_json(
|
module.fail_json(
|
||||||
msg="Reached timeout while waiting for starting VM."
|
msg="Reached timeout while waiting for starting VM."
|
||||||
"Last line in task before timeout: {}".
|
f"Last line in task before timeout: {proxmox_node.tasks(taskid).log.get()[:1]}"
|
||||||
format(proxmox_node.tasks(taskid).log.get()[:1])
|
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
@ -980,8 +1012,7 @@ def stop_vm(module, proxmox, vm, force):
|
|||||||
if not wait_for_task(module, proxmox, vm[0]["node"], taskid):
|
if not wait_for_task(module, proxmox, vm[0]["node"], taskid):
|
||||||
module.fail_json(
|
module.fail_json(
|
||||||
msg="Reached timeout while waiting for stopping VM."
|
msg="Reached timeout while waiting for stopping VM."
|
||||||
"Last line in task before timeout: {}".
|
f"Last line in task before timeout: {proxmox_node.tasks(taskid).log.get()[:1]}"
|
||||||
format(proxmox_node.tasks(taskid).log.get()[:1])
|
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
@ -998,7 +1029,7 @@ def main():
|
|||||||
acpi=dict(type="bool"),
|
acpi=dict(type="bool"),
|
||||||
agent=dict(type="bool"),
|
agent=dict(type="bool"),
|
||||||
args=dict(type="str"),
|
args=dict(type="str"),
|
||||||
api_host=dict(required=True),
|
api_host=dict(required=True, type="str"),
|
||||||
api_password=dict(no_log=True, fallback=(env_fallback, ["PROXMOX_PASSWORD"])),
|
api_password=dict(no_log=True, fallback=(env_fallback, ["PROXMOX_PASSWORD"])),
|
||||||
api_token_id=dict(no_log=True),
|
api_token_id=dict(no_log=True),
|
||||||
api_token_secret=dict(no_log=True),
|
api_token_secret=dict(no_log=True),
|
||||||
@ -1044,7 +1075,7 @@ def main():
|
|||||||
nameservers=dict(type="list", elements="str"),
|
nameservers=dict(type="list", elements="str"),
|
||||||
net=dict(type="dict"),
|
net=dict(type="dict"),
|
||||||
newid=dict(type="int"),
|
newid=dict(type="int"),
|
||||||
node=dict(),
|
node=dict(type="str"),
|
||||||
numa=dict(type="dict"),
|
numa=dict(type="dict"),
|
||||||
numa_enabled=dict(type="bool"),
|
numa_enabled=dict(type="bool"),
|
||||||
onboot=dict(type="bool"),
|
onboot=dict(type="bool"),
|
||||||
@ -1076,7 +1107,7 @@ def main():
|
|||||||
sockets=dict(type="int"),
|
sockets=dict(type="int"),
|
||||||
sshkeys=dict(type="str", no_log=False),
|
sshkeys=dict(type="str", no_log=False),
|
||||||
startdate=dict(type="str"),
|
startdate=dict(type="str"),
|
||||||
startup=dict(),
|
startup=dict(type="str"),
|
||||||
state=dict(
|
state=dict(
|
||||||
default="present",
|
default="present",
|
||||||
choices=["present", "absent", "stopped", "started", "restarted", "current"]
|
choices=["present", "absent", "stopped", "started", "restarted", "current"]
|
||||||
@ -1089,7 +1120,7 @@ def main():
|
|||||||
template=dict(type="bool"),
|
template=dict(type="bool"),
|
||||||
timeout=dict(type="int", default=30),
|
timeout=dict(type="int", default=30),
|
||||||
update=dict(type="bool", default=False),
|
update=dict(type="bool", default=False),
|
||||||
validate_certs=dict(type="bool", default=False),
|
validate_certs=dict(type="bool", default=True),
|
||||||
vcpus=dict(type="int"),
|
vcpus=dict(type="int"),
|
||||||
vga=dict(
|
vga=dict(
|
||||||
choices=[
|
choices=[
|
||||||
@ -1099,7 +1130,7 @@ def main():
|
|||||||
),
|
),
|
||||||
virtio=dict(type="dict"),
|
virtio=dict(type="dict"),
|
||||||
vmid=dict(type="int"),
|
vmid=dict(type="int"),
|
||||||
watchdog=dict(),
|
watchdog=dict(type="str"),
|
||||||
proxmox_default_behavior=dict(type="str", choices=["compatibility", "no_defaults"]),
|
proxmox_default_behavior=dict(type="str", choices=["compatibility", "no_defaults"]),
|
||||||
),
|
),
|
||||||
mutually_exclusive=[("delete", "revert"), ("delete", "update"), ("revert", "update"),
|
mutually_exclusive=[("delete", "revert"), ("delete", "update"), ("revert", "update"),
|
||||||
@ -1168,13 +1199,11 @@ def main():
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
proxmox = ProxmoxAPI(api_host, verify_ssl=validate_certs, **auth_args)
|
proxmox = ProxmoxAPI(api_host, verify_ssl=validate_certs, **auth_args)
|
||||||
global PVE_MAJOR_VERSION # noqa
|
global PVE_MAJOR_VERSION
|
||||||
version = proxmox_version(proxmox)
|
version = proxmox_version(proxmox)
|
||||||
PVE_MAJOR_VERSION = 3 if version < LooseVersion("4.0") else version.version[0]
|
PVE_MAJOR_VERSION = 3 if version < LooseVersion("4.0") else version.version[0]
|
||||||
except Exception as e:
|
except Exception as e: # noqa
|
||||||
module.fail_json(
|
module.fail_json(msg=f"authorization on proxmox cluster failed with exception: {e}")
|
||||||
msg="authorization on proxmox cluster failed with exception: {}".format(e)
|
|
||||||
)
|
|
||||||
|
|
||||||
# If vmid is not defined then retrieve its value from the vm name,
|
# If vmid is not defined then retrieve its value from the vm name,
|
||||||
# the cloned vm name or retrieve the next free VM id from ProxmoxAPI.
|
# the cloned vm name or retrieve the next free VM id from ProxmoxAPI.
|
||||||
@ -1182,16 +1211,16 @@ def main():
|
|||||||
if state == "present" and not update and not clone and not delete and not revert:
|
if state == "present" and not update and not clone and not delete and not revert:
|
||||||
try:
|
try:
|
||||||
vmid = get_nextvmid(module, proxmox)
|
vmid = get_nextvmid(module, proxmox)
|
||||||
except Exception:
|
except Exception: # noqa
|
||||||
module.fail_json(
|
module.fail_json(
|
||||||
msg="Can't get the next vmid for VM {0} automatically."
|
msg=f"Can't get the next vmid for VM {name} automatically."
|
||||||
"Ensure your cluster state is good".format(name)
|
"Ensure your cluster state is good"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
clone_target = clone or name
|
clone_target = clone or name
|
||||||
try:
|
try:
|
||||||
vmid = get_vmid(proxmox, clone_target)[0]
|
vmid = get_vmid(proxmox, clone_target)[0]
|
||||||
except Exception:
|
except Exception: # noqa
|
||||||
vmid = -1
|
vmid = -1
|
||||||
|
|
||||||
if clone is not None:
|
if clone is not None:
|
||||||
@ -1199,50 +1228,40 @@ def main():
|
|||||||
if not newid:
|
if not newid:
|
||||||
try:
|
try:
|
||||||
newid = get_nextvmid(module, proxmox)
|
newid = get_nextvmid(module, proxmox)
|
||||||
except Exception:
|
except Exception: # noqa
|
||||||
module.fail_json(
|
module.fail_json(
|
||||||
msg="Can't get the next vmid for VM {0} automatically."
|
msg=f"Can't get the next vmid for VM {name} automatically."
|
||||||
"Ensure your cluster state is good".format(name)
|
"Ensure your cluster state is good"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Ensure source VM name exists when cloning
|
# Ensure source VM name exists when cloning
|
||||||
if -1 == vmid:
|
if -1 == vmid:
|
||||||
module.fail_json(msg="VM with name = {} does not exist in cluster".format(clone))
|
module.fail_json(msg=f"VM with name = {clone} does not exist in cluster")
|
||||||
|
|
||||||
# Ensure source VM id exists when cloning
|
# Ensure source VM id exists when cloning
|
||||||
if not get_vm(proxmox, vmid):
|
if not get_vm(proxmox, vmid):
|
||||||
module.fail_json(
|
module.fail_json(vmid=vmid, msg=f"VM with vmid = {vmid} does not exist in cluster")
|
||||||
vmid=vmid, msg="VM with vmid = {} does not exist in cluster".format(vmid)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Ensure the choosen VM name doesn't already exist when cloning
|
# Ensure the choosen VM name doesn't already exist when cloning
|
||||||
if get_vmid(proxmox, name):
|
if get_vmid(proxmox, name):
|
||||||
module.exit_json(
|
module.exit_json(changed=False, vmid=vmid, msg=f"VM with name <{name}> already exists")
|
||||||
changed=False, vmid=vmid, msg="VM with name <{}> already exists".format(name)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Ensure the choosen VM id doesn't already exist when cloning
|
# Ensure the choosen VM id doesn't already exist when cloning
|
||||||
if get_vm(proxmox, newid):
|
if get_vm(proxmox, newid):
|
||||||
module.exit_json(
|
module.exit_json(
|
||||||
changed=False,
|
changed=False, vmid=vmid, msg=f"vmid {newid} with VM name {name} already exists"
|
||||||
vmid=vmid,
|
|
||||||
msg="vmid {} with VM name {} already exists".format(newid, name)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if delete is not None:
|
if delete is not None:
|
||||||
try:
|
try:
|
||||||
settings(module, proxmox, vmid, node, name, delete=delete)
|
settings(module, proxmox, vmid, node, name, delete=delete)
|
||||||
module.exit_json(
|
module.exit_json(
|
||||||
changed=True,
|
changed=True, vmid=vmid, msg=f"Settings has deleted on VM {name} with vmid {vmid}"
|
||||||
vmid=vmid,
|
|
||||||
msg="Settings has deleted on VM {0} with vmid {1}".format(name, vmid)
|
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e: # noqa
|
||||||
module.fail_json(
|
module.fail_json(
|
||||||
vmid=vmid,
|
vmid=vmid,
|
||||||
msg="Unable to delete settings on VM {0} with vmid {1}: {2}".format(
|
msg=f"Unable to delete settings on VM {name} with vmid {vmid}: {str(e)}"
|
||||||
name, vmid, str(e)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if revert is not None:
|
if revert is not None:
|
||||||
@ -1251,29 +1270,29 @@ def main():
|
|||||||
module.exit_json(
|
module.exit_json(
|
||||||
changed=True,
|
changed=True,
|
||||||
vmid=vmid,
|
vmid=vmid,
|
||||||
msg="Settings has reverted on VM {0} with vmid {1}".format(name, vmid)
|
msg=f"Settings has reverted on VM {name} with vmid {vmid}"
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e: # noqa
|
||||||
module.fail_json(
|
module.fail_json(
|
||||||
vmid=vmid,
|
vmid=vmid,
|
||||||
msg="Unable to revert settings on VM {0} with vmid {1}:"
|
msg=f"Unable to revert settings on VM {name} with vmid {vmid}:"
|
||||||
"Maybe is not a pending task...".format(name, vmid) + str(e)
|
f"Maybe is not a pending task...{str(e)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if state == "present":
|
if state == "present":
|
||||||
try:
|
try:
|
||||||
if get_vm(proxmox, vmid) and not (update or clone):
|
if get_vm(proxmox, vmid) and not (update or clone):
|
||||||
module.exit_json(
|
module.exit_json(
|
||||||
changed=False, vmid=vmid, msg="VM with vmid <{}> already exists".format(vmid)
|
changed=False, vmid=vmid, msg=f"VM with vmid <{vmid}> already exists"
|
||||||
)
|
)
|
||||||
elif get_vmid(proxmox, name) and not (update or clone):
|
elif get_vmid(proxmox, name) and not (update or clone):
|
||||||
module.exit_json(
|
module.exit_json(
|
||||||
changed=False, vmid=vmid, msg="VM with name <{}> already exists".format(name)
|
changed=False, vmid=vmid, msg=f"VM with name <{name}> already exists"
|
||||||
)
|
)
|
||||||
elif not (node, name):
|
elif not (node, name):
|
||||||
module.fail_json(msg="node, name is mandatory for creating/updating vm")
|
module.fail_json(msg="node, name is mandatory for creating/updating vm")
|
||||||
elif not node_check(proxmox, node):
|
elif not node_check(proxmox, node):
|
||||||
module.fail_json(msg="node '{}' does not exist in cluster".format(node))
|
module.fail_json(msg=f"node '{node}' does not exist in cluster")
|
||||||
|
|
||||||
if not clone:
|
if not clone:
|
||||||
get_vminfo(
|
get_vminfo(
|
||||||
@ -1362,130 +1381,100 @@ def main():
|
|||||||
|
|
||||||
if update:
|
if update:
|
||||||
module.exit_json(
|
module.exit_json(
|
||||||
changed=True, vmid=vmid, msg="VM {} with vmid {} updated".format(name, vmid)
|
changed=True, vmid=vmid, msg=f"VM {name} with vmid {vmid} updated"
|
||||||
)
|
)
|
||||||
elif clone is not None:
|
elif clone is not None:
|
||||||
module.exit_json(
|
module.exit_json(
|
||||||
changed=True,
|
changed=True,
|
||||||
vmid=vmid,
|
vmid=vmid,
|
||||||
msg="VM {} with newid {} cloned from vm with vmid {}".format(
|
msg=f"VM {name} with newid {newid} cloned from vm with vmid {vmid}"
|
||||||
name, newid, vmid
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
module.exit_json(
|
module.exit_json(
|
||||||
changed=True,
|
changed=True, msg=f"VM {name} with vmid {vmid} deployed", **results
|
||||||
msg="VM {} with vmid {} deployed".format(name, vmid),
|
|
||||||
**results # noqa
|
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e: # noqa
|
||||||
if update:
|
if update:
|
||||||
module.fail_json(
|
module.fail_json(
|
||||||
vmid=vmid,
|
vmid=vmid, msg=f"Unable to update vm {name} with vmid {vmid}=" + str(e)
|
||||||
msg="Unable to update vm {0} with vmid {1}=".format(name, vmid) + str(e)
|
|
||||||
)
|
)
|
||||||
elif clone is not None:
|
elif clone is not None:
|
||||||
module.fail_json(
|
module.fail_json(
|
||||||
vmid=vmid,
|
vmid=vmid, msg=f"Unable to clone vm {name} from vmid {vmid}=" + str(e)
|
||||||
msg="Unable to clone vm {0} from vmid {1}=".format(name, vmid) + str(e)
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
module.fail_json(
|
module.fail_json(
|
||||||
vmid=vmid,
|
vmid=vmid,
|
||||||
msg="creation of qemu VM {} with vmid {} failed with exception={}".format(
|
msg=f"creation of qemu VM {name} with vmid {vmid} failed with exception={e}"
|
||||||
name, vmid, e
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
elif state == "started":
|
elif state == "started":
|
||||||
status = {}
|
status = {}
|
||||||
try:
|
try:
|
||||||
if -1 == vmid:
|
if -1 == vmid:
|
||||||
module.fail_json(msg="VM with name = {} does not exist in cluster".format(name))
|
module.fail_json(msg=f"VM with name = {name} does not exist in cluster")
|
||||||
vm = get_vm(proxmox, vmid)
|
vm = get_vm(proxmox, vmid)
|
||||||
if not vm:
|
if not vm:
|
||||||
module.fail_json(
|
module.fail_json(vmid=vmid, msg=f"VM with vmid <{vmid}> does not exist in cluster")
|
||||||
vmid=vmid, msg="VM with vmid <{}> does not exist in cluster".format(vmid)
|
|
||||||
)
|
|
||||||
status["status"] = vm[0]["status"]
|
status["status"] = vm[0]["status"]
|
||||||
if vm[0]["status"] == "running":
|
if vm[0]["status"] == "running":
|
||||||
module.exit_json(
|
module.exit_json(
|
||||||
changed=False,
|
changed=False, vmid=vmid, msg=f"VM {vmid} is already running", **status
|
||||||
vmid=vmid,
|
|
||||||
msg="VM {} is already running".format(vmid),
|
|
||||||
**status
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if start_vm(module, proxmox, vm):
|
if start_vm(module, proxmox, vm):
|
||||||
module.exit_json(
|
module.exit_json(changed=True, vmid=vmid, msg=f"VM {vmid} started", **status)
|
||||||
changed=True, vmid=vmid, msg="VM {} started".format(vmid), **status
|
except Exception as e: # noqa
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
module.fail_json(
|
module.fail_json(
|
||||||
vmid=vmid,
|
vmid=vmid, msg=f"starting of VM {vmid} failed with exception: {e}", **status
|
||||||
msg="starting of VM {} failed with exception: {}".format(vmid, e),
|
|
||||||
**status
|
|
||||||
)
|
)
|
||||||
|
|
||||||
elif state == "stopped":
|
elif state == "stopped":
|
||||||
status = {}
|
status = {}
|
||||||
try:
|
try:
|
||||||
if -1 == vmid:
|
if -1 == vmid:
|
||||||
module.fail_json(msg="VM with name = {} does not exist in cluster".format(name))
|
module.fail_json(msg=f"VM with name = {name} does not exist in cluster")
|
||||||
|
|
||||||
vm = get_vm(proxmox, vmid)
|
vm = get_vm(proxmox, vmid)
|
||||||
if not vm:
|
if not vm:
|
||||||
module.fail_json(
|
module.fail_json(vmid=vmid, msg=f"VM with vmid = {vmid} does not exist in cluster")
|
||||||
vmid=vmid, msg="VM with vmid = {} does not exist in cluster".format(vmid)
|
|
||||||
)
|
|
||||||
|
|
||||||
status["status"] = vm[0]["status"]
|
status["status"] = vm[0]["status"]
|
||||||
if vm[0]["status"] == "stopped":
|
if vm[0]["status"] == "stopped":
|
||||||
module.exit_json(
|
module.exit_json(
|
||||||
changed=False,
|
changed=False, vmid=vmid, msg=f"VM {vmid} is already stopped", **status
|
||||||
vmid=vmid,
|
|
||||||
msg="VM {} is already stopped".format(vmid),
|
|
||||||
**status
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if stop_vm(module, proxmox, vm, force=module.params["force"]):
|
if stop_vm(module, proxmox, vm, force=module.params["force"]):
|
||||||
module.exit_json(
|
module.exit_json(
|
||||||
changed=True, vmid=vmid, msg="VM {} is shutting down".format(vmid), **status
|
changed=True, vmid=vmid, msg=f"VM {vmid} is shutting down", **status
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e: # noqa
|
||||||
module.fail_json(
|
module.fail_json(
|
||||||
vmid=vmid,
|
vmid=vmid, msg=f"stopping of VM {vmid} failed with exception: {e}", **status
|
||||||
msg="stopping of VM {} failed with exception: {}".format(vmid, e),
|
|
||||||
**status
|
|
||||||
)
|
)
|
||||||
|
|
||||||
elif state == "restarted":
|
elif state == "restarted":
|
||||||
status = {}
|
status = {}
|
||||||
try:
|
try:
|
||||||
if -1 == vmid:
|
if -1 == vmid:
|
||||||
module.fail_json(msg="VM with name = {} does not exist in cluster".format(name))
|
module.fail_json(msg=f"VM with name = {name} does not exist in cluster")
|
||||||
|
|
||||||
vm = get_vm(proxmox, vmid)
|
vm = get_vm(proxmox, vmid)
|
||||||
if not vm:
|
if not vm:
|
||||||
module.fail_json(
|
module.fail_json(vmid=vmid, msg=f"VM with vmid = {vmid} does not exist in cluster")
|
||||||
vmid=vmid, msg="VM with vmid = {} does not exist in cluster".format(vmid)
|
|
||||||
)
|
|
||||||
status["status"] = vm[0]["status"]
|
status["status"] = vm[0]["status"]
|
||||||
if vm[0]["status"] == "stopped":
|
if vm[0]["status"] == "stopped":
|
||||||
module.exit_json(
|
module.exit_json(
|
||||||
changed=False, vmid=vmid, msg="VM {} is not running".format(vmid), **status
|
changed=False, vmid=vmid, msg=f"VM {vmid} is not running", **status
|
||||||
)
|
)
|
||||||
|
|
||||||
if stop_vm(module, proxmox, vm,
|
if stop_vm(module, proxmox, vm,
|
||||||
force=module.params["force"]) and start_vm(module, proxmox, vm):
|
force=module.params["force"]) and start_vm(module, proxmox, vm):
|
||||||
module.exit_json(
|
module.exit_json(changed=True, vmid=vmid, msg=f"VM {vmid} is restarted", **status)
|
||||||
changed=True, vmid=vmid, msg="VM {} is restarted".format(vmid), **status
|
except Exception as e: # noqa
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
module.fail_json(
|
module.fail_json(
|
||||||
vmid=vmid,
|
vmid=vmid, msg=f"restarting of VM {vmid} failed with exception: {e}", **status
|
||||||
msg="restarting of VM {} failed with exception: {}".format(vmid, e),
|
|
||||||
**status
|
|
||||||
)
|
)
|
||||||
|
|
||||||
elif state == "absent":
|
elif state == "absent":
|
||||||
@ -1504,28 +1493,26 @@ def main():
|
|||||||
module.exit_json(
|
module.exit_json(
|
||||||
changed=False,
|
changed=False,
|
||||||
vmid=vmid,
|
vmid=vmid,
|
||||||
msg="VM {} is running. Stop it before deletion or use force=yes.".
|
msg=f"VM {vmid} is running. Stop it before deletion or use force=yes."
|
||||||
format(vmid)
|
|
||||||
)
|
)
|
||||||
taskid = proxmox_node.qemu.delete(vmid)
|
taskid = proxmox_node.qemu.delete(vmid)
|
||||||
if not wait_for_task(module, proxmox, vm[0]["node"], taskid):
|
if not wait_for_task(module, proxmox, vm[0]["node"], taskid):
|
||||||
module.fail_json(
|
module.fail_json(
|
||||||
msg="Reached timeout while waiting for removing VM."
|
msg="Reached timeout while waiting for removing VM."
|
||||||
"Last line in task before timeout: {}".
|
f"Last line in task before timeout: {proxmox_node.tasks(taskid).log.get()[:1]}"
|
||||||
format(proxmox_node.tasks(taskid).log.get()[:1])
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
module.exit_json(changed=True, vmid=vmid, msg="VM {} removed".format(vmid))
|
module.exit_json(changed=True, vmid=vmid, msg=f"VM {vmid} removed")
|
||||||
except Exception as e:
|
except Exception as e: # noqa
|
||||||
module.fail_json(msg="deletion of VM {} failed with exception: {}".format(vmid, e))
|
module.fail_json(msg=f"deletion of VM {vmid} failed with exception: {e}")
|
||||||
|
|
||||||
elif state == "current":
|
elif state == "current":
|
||||||
status = {}
|
status = {}
|
||||||
if -1 == vmid:
|
if -1 == vmid:
|
||||||
module.fail_json(msg="VM with name = {} does not exist in cluster".format(name))
|
module.fail_json(msg=f"VM with name = {name} does not exist in cluster")
|
||||||
vm = get_vm(proxmox, vmid)
|
vm = get_vm(proxmox, vmid)
|
||||||
if not vm:
|
if not vm:
|
||||||
module.fail_json(msg="VM with vmid = {} does not exist in cluster".format(vmid))
|
module.fail_json(msg=f"VM with vmid = {vmid} does not exist in cluster")
|
||||||
if not name:
|
if not name:
|
||||||
name = vm[0]["name"]
|
name = vm[0]["name"]
|
||||||
current = proxmox.nodes(vm[0]["node"]).qemu(vmid).status.current.get()["status"]
|
current = proxmox.nodes(vm[0]["node"]).qemu(vmid).status.current.get()["status"]
|
||||||
@ -1534,7 +1521,7 @@ def main():
|
|||||||
module.exit_json(
|
module.exit_json(
|
||||||
changed=False,
|
changed=False,
|
||||||
vmid=vmid,
|
vmid=vmid,
|
||||||
msg="VM {} with vmid = {} is {}".format(name, vmid, current),
|
msg=f"VM {name} with vmid = {vmid} is {current}",
|
||||||
**status
|
**status
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1561,8 +1548,10 @@ def _extract_nets(item):
|
|||||||
if re.match(r"net[0-9]", k):
|
if re.match(r"net[0-9]", k):
|
||||||
nets[k]["opts"] = []
|
nets[k]["opts"] = []
|
||||||
for val in v.split(","):
|
for val in v.split(","):
|
||||||
if any(val.startswith(s) for s in ["e1000", "rtl8139", "virtio", "vmxnet3"]):
|
if (
|
||||||
if len(val.split("=")) == 2:
|
any(val.startswith(s) for s in ["e1000", "rtl8139", "virtio", "vmxnet3"])
|
||||||
|
and len(val.split("=")) == 2
|
||||||
|
):
|
||||||
net = val.split("=")
|
net = val.split("=")
|
||||||
nets[k]["net_id"] = net[0]
|
nets[k]["net_id"] = net[0]
|
||||||
nets[k]["net_opts"] = net[1]
|
nets[k]["net_opts"] = net[1]
|
||||||
|
@ -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,7 +14,7 @@ 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)."
|
||||||
@ -15,16 +22,20 @@ options:
|
|||||||
path:
|
path:
|
||||||
description:
|
description:
|
||||||
- Path for the variable
|
- Path for the variable
|
||||||
|
aliases:
|
||||||
|
- name
|
||||||
required: True
|
required: True
|
||||||
default: null
|
type: str
|
||||||
value:
|
value:
|
||||||
description:
|
description:
|
||||||
- New value of the variable
|
- New value of the variable
|
||||||
required: False
|
required: False
|
||||||
|
type: str
|
||||||
state:
|
state:
|
||||||
required: False
|
required: False
|
||||||
default: "present"
|
default: "present"
|
||||||
choices: ["present", "absent"]
|
choices: ["present", "absent"]
|
||||||
|
type: str
|
||||||
description:
|
description:
|
||||||
- Whether the variable should be exist or not.
|
- Whether the variable should be exist or not.
|
||||||
author:
|
author:
|
||||||
@ -49,39 +60,41 @@ 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=""),
|
||||||
@ -94,12 +107,16 @@ 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
|
||||||
|
1202
poetry.lock
generated
Normal file
1202
poetry.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
128
pyproject.toml
Normal file
128
pyproject.toml
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
[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.8",
|
||||||
|
"Programming Language :: Python :: 3.9",
|
||||||
|
"Programming Language :: Python :: 3.10",
|
||||||
|
"Programming Language :: Python :: 3.11",
|
||||||
|
"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.7.0"
|
||||||
|
ansible-core = "<=2.14.0"
|
||||||
|
pyopenssl = "23.0.0"
|
||||||
|
proxmoxer = "2.0.1"
|
||||||
|
hcloud = "1.18.2"
|
||||||
|
|
||||||
|
[tool.poetry.group.dev.dependencies]
|
||||||
|
ruff = "0.0.230"
|
||||||
|
pytest = "7.2.1"
|
||||||
|
pytest-mock = "3.10.0"
|
||||||
|
pytest-cov = "4.0.0"
|
||||||
|
toml = "0.10.2"
|
||||||
|
yapf = "0.32.0"
|
||||||
|
pycodestyle = "2.10.0"
|
||||||
|
yamllint = "1.29.0"
|
||||||
|
pylint = "<=2.15.0"
|
||||||
|
voluptuous = "^0.13.1"
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
addopts = "--cov --cov-report=xml:coverage.xml --cov-report=term --cov-append --no-cov-on-fail"
|
||||||
|
pythonpath = [
|
||||||
|
"."
|
||||||
|
]
|
||||||
|
filterwarnings = [
|
||||||
|
"ignore::FutureWarning",
|
||||||
|
"ignore::DeprecationWarning",
|
||||||
|
"ignore:.*pep8.*:FutureWarning",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.coverage.run]
|
||||||
|
omit = ["**/tests/*"]
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning"]
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
exclude = [
|
||||||
|
".git",
|
||||||
|
"__pycache__",
|
||||||
|
"build",
|
||||||
|
"dist",
|
||||||
|
"tests",
|
||||||
|
"*.pyc",
|
||||||
|
"*.egg-info",
|
||||||
|
".cache",
|
||||||
|
".eggs",
|
||||||
|
"env*",
|
||||||
|
"iptables_raw.py",
|
||||||
|
]
|
||||||
|
# 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)
|
||||||
|
ignore = [
|
||||||
|
"D102",
|
||||||
|
"D103",
|
||||||
|
"D105",
|
||||||
|
"D107",
|
||||||
|
"D202",
|
||||||
|
"D203",
|
||||||
|
"D212",
|
||||||
|
"E402",
|
||||||
|
"SIM105",
|
||||||
|
"C402",
|
||||||
|
"C408",
|
||||||
|
]
|
||||||
|
line-length = 99
|
||||||
|
select = [
|
||||||
|
"D",
|
||||||
|
"E",
|
||||||
|
"F",
|
||||||
|
"Q",
|
||||||
|
"W",
|
||||||
|
"I",
|
||||||
|
"S",
|
||||||
|
"BLE",
|
||||||
|
"N",
|
||||||
|
"UP",
|
||||||
|
"B",
|
||||||
|
"A",
|
||||||
|
"C4",
|
||||||
|
"T20",
|
||||||
|
"SIM",
|
||||||
|
"RET",
|
||||||
|
"ARG",
|
||||||
|
"ERA",
|
||||||
|
"RUF",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.ruff.flake8-quotes]
|
||||||
|
inline-quotes = "double"
|
@ -1,4 +0,0 @@
|
|||||||
ansible-core
|
|
||||||
pyopenssl
|
|
||||||
proxmoxer
|
|
||||||
hcloud
|
|
36
setup.cfg
36
setup.cfg
@ -1,42 +1,6 @@
|
|||||||
[isort]
|
|
||||||
default_section = THIRDPARTY
|
|
||||||
sections = FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER
|
|
||||||
force_single_line = true
|
|
||||||
line_length = 99
|
|
||||||
skip_glob = **/.venv*,**/venv/*,**/docs/*,**/inventory/*,**/modules/*
|
|
||||||
|
|
||||||
[yapf]
|
[yapf]
|
||||||
based_on_style = google
|
based_on_style = google
|
||||||
column_limit = 99
|
column_limit = 99
|
||||||
dedent_closing_brackets = true
|
dedent_closing_brackets = true
|
||||||
coalesce_brackets = true
|
coalesce_brackets = true
|
||||||
split_before_logical_operator = true
|
split_before_logical_operator = true
|
||||||
|
|
||||||
[tool:pytest]
|
|
||||||
filterwarnings =
|
|
||||||
ignore::FutureWarning
|
|
||||||
ignore:.*collections.*:DeprecationWarning
|
|
||||||
ignore:.*pep8.*:FutureWarning
|
|
||||||
|
|
||||||
[coverage:run]
|
|
||||||
omit =
|
|
||||||
**/test/*
|
|
||||||
**/.venv/*
|
|
||||||
|
|
||||||
[flake8]
|
|
||||||
ignore = D101, D102, D103, D105, D107, D202, E402, W503, B902
|
|
||||||
max-line-length = 99
|
|
||||||
inline-quotes = double
|
|
||||||
exclude =
|
|
||||||
.git
|
|
||||||
.tox
|
|
||||||
__pycache__
|
|
||||||
build
|
|
||||||
dist
|
|
||||||
test
|
|
||||||
*.pyc
|
|
||||||
*.egg-info
|
|
||||||
.cache
|
|
||||||
.eggs
|
|
||||||
env*
|
|
||||||
iptables_raw.py
|
|
||||||
|
@ -1 +0,0 @@
|
|||||||
# noqa
|
|
@ -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
2
tests/config.yml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
modules:
|
||||||
|
python_requires: ">=3.8"
|
0
tests/unit/plugins/inventory/__init__.py
Normal file
0
tests/unit/plugins/inventory/__init__.py
Normal file
@ -1,10 +1,11 @@
|
|||||||
"""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")
|
||||||
@ -58,7 +59,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):
|
Loading…
Reference in New Issue
Block a user