initial commit
This commit is contained in:
commit
9b49b3881a
162
.drone.jsonnet
Normal file
162
.drone.jsonnet
Normal file
@ -0,0 +1,162 @@
|
||||
local PythonVersion(pyversion='3.5') = {
|
||||
name: 'python' + std.strReplace(pyversion, '.', '') + '-pytest',
|
||||
image: 'python:' + pyversion,
|
||||
environment: {
|
||||
PY_COLORS: 1,
|
||||
},
|
||||
commands: [
|
||||
'pip install -r dev-requirements.txt -qq',
|
||||
'pip install -r test/unit/requirements.txt -qq',
|
||||
'python -m pytest --cov --cov-append --no-cov-on-fail',
|
||||
],
|
||||
depends_on: [
|
||||
'clone',
|
||||
],
|
||||
};
|
||||
|
||||
local PipelineLint = {
|
||||
kind: 'pipeline',
|
||||
name: 'lint',
|
||||
platform: {
|
||||
os: 'linux',
|
||||
arch: 'amd64',
|
||||
},
|
||||
steps: [
|
||||
{
|
||||
name: 'flake8',
|
||||
image: 'python:3.8',
|
||||
environment: {
|
||||
PY_COLORS: 1,
|
||||
},
|
||||
commands: [
|
||||
'pip install -r dev-requirements.txt -qq',
|
||||
'flake8',
|
||||
],
|
||||
},
|
||||
],
|
||||
trigger: {
|
||||
ref: ['refs/heads/master', 'refs/tags/**', 'refs/pull/**'],
|
||||
},
|
||||
};
|
||||
|
||||
local PipelineTest = {
|
||||
kind: 'pipeline',
|
||||
name: 'test',
|
||||
platform: {
|
||||
os: 'linux',
|
||||
arch: 'amd64',
|
||||
},
|
||||
steps: [
|
||||
PythonVersion(pyversion='3.5'),
|
||||
PythonVersion(pyversion='3.6'),
|
||||
PythonVersion(pyversion='3.7'),
|
||||
PythonVersion(pyversion='3.8'),
|
||||
{
|
||||
name: 'codecov',
|
||||
image: 'python:3.8',
|
||||
environment: {
|
||||
PY_COLORS: 1,
|
||||
CODECOV_TOKEN: { from_secret: 'codecov_token' },
|
||||
},
|
||||
commands: [
|
||||
'pip install codecov -qq',
|
||||
'codecov --required -X gcov',
|
||||
],
|
||||
depends_on: [
|
||||
'python35-pytest',
|
||||
'python36-pytest',
|
||||
'python37-pytest',
|
||||
'python38-pytest',
|
||||
],
|
||||
},
|
||||
],
|
||||
depends_on: [
|
||||
'lint',
|
||||
],
|
||||
trigger: {
|
||||
ref: ['refs/heads/master', 'refs/tags/**', 'refs/pull/**'],
|
||||
},
|
||||
};
|
||||
|
||||
local PipelineBuild = {
|
||||
kind: 'pipeline',
|
||||
name: 'build',
|
||||
platform: {
|
||||
os: 'linux',
|
||||
arch: 'amd64',
|
||||
},
|
||||
steps: [
|
||||
{
|
||||
name: 'build',
|
||||
image: 'python:3.8',
|
||||
commands: [
|
||||
'pip install ansible -qq',
|
||||
'ansible-galaxy collection build --output-path dist/',
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'checksum',
|
||||
image: 'alpine',
|
||||
commands: [
|
||||
'cd dist/ && sha256sum * > ../sha256sum.txt',
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'publish-gitea',
|
||||
image: 'plugins/gitea-release',
|
||||
settings: {
|
||||
overwrite: true,
|
||||
api_key: { from_secret: 'gitea_token' },
|
||||
files: ['dist/*', 'sha256sum.txt'],
|
||||
base_url: 'https://gitea.rknet.org',
|
||||
title: '${DRONE_TAG}',
|
||||
note: 'CHANGELOG.md',
|
||||
},
|
||||
when: {
|
||||
ref: ['refs/tags/**'],
|
||||
},
|
||||
},
|
||||
],
|
||||
depends_on: [
|
||||
'security',
|
||||
],
|
||||
trigger: {
|
||||
ref: ['refs/heads/master', 'refs/tags/**', 'refs/pull/**'],
|
||||
},
|
||||
};
|
||||
|
||||
local PipelineNotifications = {
|
||||
kind: 'pipeline',
|
||||
name: 'notifications',
|
||||
platform: {
|
||||
os: 'linux',
|
||||
arch: 'amd64',
|
||||
},
|
||||
steps: [
|
||||
{
|
||||
name: 'matrix',
|
||||
image: 'plugins/matrix',
|
||||
settings: {
|
||||
homeserver: { from_secret: 'matrix_homeserver' },
|
||||
roomid: { from_secret: 'matrix_roomid' },
|
||||
template: 'Status: **{{ build.status }}**<br/> Build: [{{ repo.Owner }}/{{ repo.Name }}]({{ build.link }}) ({{ build.branch }}) by {{ build.author }}<br/> Message: {{ build.message }}',
|
||||
username: { from_secret: 'matrix_username' },
|
||||
password: { from_secret: 'matrix_password' },
|
||||
},
|
||||
},
|
||||
],
|
||||
depends_on: [
|
||||
'docs',
|
||||
],
|
||||
trigger: {
|
||||
ref: ['refs/heads/master', 'refs/tags/**'],
|
||||
status: ['success', 'failure'],
|
||||
},
|
||||
};
|
||||
|
||||
[
|
||||
PipelineLint,
|
||||
PipelineTest,
|
||||
PipelineBuild,
|
||||
PipelineNotifications,
|
||||
]
|
183
.drone.yml
Normal file
183
.drone.yml
Normal file
@ -0,0 +1,183 @@
|
||||
---
|
||||
kind: pipeline
|
||||
name: lint
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
steps:
|
||||
- name: flake8
|
||||
image: python:3.8
|
||||
commands:
|
||||
- pip install -r dev-requirements.txt -qq
|
||||
- flake8
|
||||
environment:
|
||||
PY_COLORS: 1
|
||||
|
||||
trigger:
|
||||
ref:
|
||||
- refs/heads/master
|
||||
- refs/tags/**
|
||||
- refs/pull/**
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
name: test
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
steps:
|
||||
- name: python35-pytest
|
||||
image: python:3.5
|
||||
commands:
|
||||
- pip install -r dev-requirements.txt -qq
|
||||
- pip install -r test/unit/requirements.txt -qq
|
||||
- python -m pytest --cov --cov-append --no-cov-on-fail
|
||||
environment:
|
||||
PY_COLORS: 1
|
||||
depends_on:
|
||||
- clone
|
||||
|
||||
- name: python36-pytest
|
||||
image: python:3.6
|
||||
commands:
|
||||
- pip install -r dev-requirements.txt -qq
|
||||
- pip install -r test/unit/requirements.txt -qq
|
||||
- python -m pytest --cov --cov-append --no-cov-on-fail
|
||||
environment:
|
||||
PY_COLORS: 1
|
||||
depends_on:
|
||||
- clone
|
||||
|
||||
- name: python37-pytest
|
||||
image: python:3.7
|
||||
commands:
|
||||
- pip install -r dev-requirements.txt -qq
|
||||
- pip install -r test/unit/requirements.txt -qq
|
||||
- python -m pytest --cov --cov-append --no-cov-on-fail
|
||||
environment:
|
||||
PY_COLORS: 1
|
||||
depends_on:
|
||||
- clone
|
||||
|
||||
- name: python38-pytest
|
||||
image: python:3.8
|
||||
commands:
|
||||
- pip install -r dev-requirements.txt -qq
|
||||
- pip install -r test/unit/requirements.txt -qq
|
||||
- python -m pytest --cov --cov-append --no-cov-on-fail
|
||||
environment:
|
||||
PY_COLORS: 1
|
||||
depends_on:
|
||||
- clone
|
||||
|
||||
- name: codecov
|
||||
image: python:3.8
|
||||
commands:
|
||||
- pip install codecov -qq
|
||||
- codecov --required -X gcov
|
||||
environment:
|
||||
CODECOV_TOKEN:
|
||||
from_secret: codecov_token
|
||||
PY_COLORS: 1
|
||||
depends_on:
|
||||
- python35-pytest
|
||||
- python36-pytest
|
||||
- python37-pytest
|
||||
- python38-pytest
|
||||
|
||||
trigger:
|
||||
ref:
|
||||
- refs/heads/master
|
||||
- refs/tags/**
|
||||
- refs/pull/**
|
||||
|
||||
depends_on:
|
||||
- lint
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
name: build
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
steps:
|
||||
- name: build
|
||||
image: python:3.8
|
||||
commands:
|
||||
- pip install ansible -qq
|
||||
- ansible-galaxy collection build --output-path dist/
|
||||
|
||||
- name: checksum
|
||||
image: alpine
|
||||
commands:
|
||||
- cd dist/ && sha256sum * > ../sha256sum.txt
|
||||
|
||||
- name: publish-gitea
|
||||
image: plugins/gitea-release
|
||||
settings:
|
||||
api_key:
|
||||
from_secret: gitea_token
|
||||
base_url: https://gitea.rknet.org
|
||||
files:
|
||||
- dist/*
|
||||
- sha256sum.txt
|
||||
note: CHANGELOG.md
|
||||
overwrite: true
|
||||
title: ${DRONE_TAG}
|
||||
when:
|
||||
ref:
|
||||
- refs/tags/**
|
||||
|
||||
trigger:
|
||||
ref:
|
||||
- refs/heads/master
|
||||
- refs/tags/**
|
||||
- refs/pull/**
|
||||
|
||||
depends_on:
|
||||
- security
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
name: notifications
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
steps:
|
||||
- name: matrix
|
||||
image: plugins/matrix
|
||||
settings:
|
||||
homeserver:
|
||||
from_secret: matrix_homeserver
|
||||
password:
|
||||
from_secret: matrix_password
|
||||
roomid:
|
||||
from_secret: matrix_roomid
|
||||
template: "Status: **{{ build.status }}**<br/> Build: [{{ repo.Owner }}/{{ repo.Name }}]({{ build.link }}) ({{ build.branch }}) by {{ build.author }}<br/> Message: {{ build.message }}"
|
||||
username:
|
||||
from_secret: matrix_username
|
||||
|
||||
trigger:
|
||||
ref:
|
||||
- refs/heads/master
|
||||
- refs/tags/**
|
||||
status:
|
||||
- success
|
||||
- failure
|
||||
|
||||
depends_on:
|
||||
- docs
|
||||
|
||||
---
|
||||
kind: signature
|
||||
hmac: 4521a2c60992b46f1bf8ac0400420d6259c7de4fbd3dfec9ee59cacc8cde1ee4
|
||||
|
||||
...
|
18
.flake8
Normal file
18
.flake8
Normal file
@ -0,0 +1,18 @@
|
||||
[flake8]
|
||||
ignore = D101, D102, D103, D107, D202, E402, W503
|
||||
max-line-length = 99
|
||||
inline-quotes = double
|
||||
exclude =
|
||||
.git
|
||||
.tox
|
||||
__pycache__
|
||||
build
|
||||
dist
|
||||
test
|
||||
*.pyc
|
||||
*.egg-info
|
||||
.cache
|
||||
.eggs
|
||||
env*
|
||||
application-import-names = ansiblelater
|
||||
format = ${cyan}%(path)s:%(row)d:%(col)d${reset}: ${red_bold}%(code)s${reset} %(text)s
|
109
.gitignore
vendored
Normal file
109
.gitignore
vendored
Normal file
@ -0,0 +1,109 @@
|
||||
# ---> Python
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
env/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*,cover
|
||||
.hypothesis/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# celery beat schedule file
|
||||
celerybeat-schedule
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# dotenv
|
||||
.env
|
||||
|
||||
# virtualenv
|
||||
.venv
|
||||
venv/
|
||||
ENV/
|
||||
env/
|
||||
env*/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# Ignore ide addons
|
||||
.server-script
|
||||
.on-save.json
|
||||
.vscode
|
||||
.pytest_cache
|
||||
|
||||
pip-wheel-metadata
|
||||
|
||||
# Hugo documentation
|
||||
docs/themes/
|
||||
docs/public/
|
||||
resources/_gen/
|
||||
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Robert Kaussow <mail@thegeeklab.de>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice (including the next
|
||||
paragraph) shall be included in all copies or substantial portions of the
|
||||
Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
|
||||
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
|
||||
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
14
README.md
Normal file
14
README.md
Normal file
@ -0,0 +1,14 @@
|
||||
# xoxys.general
|
||||
|
||||
[![Build Status](https://img.shields.io/drone/build/ansible/xoxys.general?logo=drone&server=https%3A%2F%2Fdrone.rknet.org)](https://drone.rknet.org/ansible/xoxys.general)
|
||||
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg?label=license)](LICENSE)
|
||||
|
||||
Custom general Ansible collection.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
||||
|
||||
## Maintainers and Contributors
|
||||
|
||||
[Robert Kaussow](https://gitea.rknet.org/xoxys)
|
19
dev-requirements.txt
Normal file
19
dev-requirements.txt
Normal file
@ -0,0 +1,19 @@
|
||||
pydocstyle<4.0.0
|
||||
flake8
|
||||
flake8-colors
|
||||
flake8-blind-except
|
||||
flake8-builtins
|
||||
flake8-docstrings<=3.0.0
|
||||
flake8-isort
|
||||
flake8-logging-format
|
||||
flake8-polyfill
|
||||
flake8-quotes
|
||||
flake8-pep3101
|
||||
flake8-eradicate; python_version >= "3.6"
|
||||
pep8-naming
|
||||
wheel
|
||||
pytest
|
||||
pytest-mock
|
||||
pytest-cov
|
||||
bandit
|
||||
yapf
|
15
galaxy.yml
Normal file
15
galaxy.yml
Normal file
@ -0,0 +1,15 @@
|
||||
namespace: xoxys
|
||||
name: general
|
||||
version: 1.0.0
|
||||
readme: README.md
|
||||
authors:
|
||||
- Robert Kaussow <mail@thegeeklab.de>
|
||||
description: Custom general Ansible collection
|
||||
license:
|
||||
- MIT
|
||||
license_file: "LICENSE"
|
||||
tags:
|
||||
- misc
|
||||
dependencies: {}
|
||||
repository: https://gitea.rknet.org/ansible/xoxys.general
|
||||
homepage: https://thegeeklab.de/
|
11
plugins/filters/prefix.py
Normal file
11
plugins/filters/prefix.py
Normal file
@ -0,0 +1,11 @@
|
||||
"""Filter to prefix all itams from a list."""
|
||||
|
||||
|
||||
def prefix(value, prefix="--"):
|
||||
return [prefix + x for x in value]
|
||||
|
||||
|
||||
class FilterModule(object):
|
||||
|
||||
def filters(self):
|
||||
return {"prefix": prefix}
|
11
plugins/filters/wrap.py
Normal file
11
plugins/filters/wrap.py
Normal file
@ -0,0 +1,11 @@
|
||||
"""Filter to wrap all items from a list."""
|
||||
|
||||
|
||||
def wrap(value, wrapper="'"):
|
||||
return [wrapper + x + wrapper for x in value]
|
||||
|
||||
|
||||
class FilterModule(object):
|
||||
|
||||
def filters(self):
|
||||
return {"wrap": wrap}
|
273
plugins/inventory/proxmox.py
Normal file
273
plugins/inventory/proxmox.py
Normal file
@ -0,0 +1,273 @@
|
||||
"""Dynamic inventory plugin for Proxmox VE."""
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2014, Mathieu GAUTHIER-LAFAYE <gauthierl@lapth.cnrs.fr>
|
||||
# Copyright (c) 2016, Matt Harris <matthaeus.harris@gmail.com>
|
||||
# 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)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = """
|
||||
name: proxmox
|
||||
plugin_type: inventory
|
||||
short_description: Proxmox VE inventory source
|
||||
version_added: 1.0.0
|
||||
description:
|
||||
- Get inventory hosts from the proxmox service.
|
||||
- "Uses a configuration file as an inventory source, it must end in C(.proxmox.yml) or C(.proxmox.yaml) and has a C(plugin: proxmox) entry."
|
||||
extends_documentation_fragment:
|
||||
- inventory_cache
|
||||
options:
|
||||
plugin:
|
||||
description: The name of this plugin, it should always be set to C(proxmox) for this plugin to recognize it as it"s own.
|
||||
required: yes
|
||||
choices: ["xoxys.general.proxmox"]
|
||||
server:
|
||||
description: Proxmox VE server url.
|
||||
default: "pve.example.com"
|
||||
required: yes
|
||||
env:
|
||||
- name: PROXMOX_SERVER
|
||||
user:
|
||||
description: Proxmox VE authentication user.
|
||||
required: yes
|
||||
env:
|
||||
- name: PROXMOX_USER
|
||||
password:
|
||||
description: Proxmox VE authentication password
|
||||
required: yes
|
||||
env:
|
||||
- name: PROXMOX_PASSWORD
|
||||
exclude_vmid:
|
||||
description: VMID"s to exclude from inventory
|
||||
type: list
|
||||
default: []
|
||||
elements: str
|
||||
exclude_state:
|
||||
description: VM states to exclude from inventory
|
||||
type: list
|
||||
default: []
|
||||
elements: str
|
||||
group:
|
||||
description: Group to place all hosts into
|
||||
default: proxmox
|
||||
want_facts:
|
||||
description: Toggle, if C(true) the plugin will retrieve host facts from the server
|
||||
type: boolean
|
||||
default: yes
|
||||
""" # noqa
|
||||
|
||||
EXAMPLES = """
|
||||
# proxmox.yml
|
||||
plugin: community.general.proxmox
|
||||
server: pve.example.com
|
||||
user: admin@pve
|
||||
password: secure
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
import socket
|
||||
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.module_utils._text import to_native
|
||||
from ansible.module_utils.six import iteritems
|
||||
from ansible.plugins.inventory import BaseInventoryPlugin
|
||||
from collections import defaultdict
|
||||
from distutils.version import LooseVersion
|
||||
|
||||
try:
|
||||
from proxmoxer import ProxmoxAPI
|
||||
HAS_PROXMOXER = True
|
||||
except ImportError:
|
||||
HAS_PROXMOXER = False
|
||||
|
||||
|
||||
class InventoryModule(BaseInventoryPlugin):
|
||||
NAME = "community.general.proxmox"
|
||||
|
||||
def _auth(self):
|
||||
return ProxmoxAPI(
|
||||
self.get_option("server"),
|
||||
user=self.get_option("user"),
|
||||
password=self.get_option("password"),
|
||||
verify_ssl=False
|
||||
)
|
||||
|
||||
def _get_version(self):
|
||||
return LooseVersion(self.client.version.get()["version"])
|
||||
|
||||
def _get_major(self):
|
||||
return LooseVersion(self.client.version.get()["release"])
|
||||
|
||||
def _get_names(self, pve_list, pve_type):
|
||||
names = []
|
||||
|
||||
if pve_type == "node":
|
||||
names = [node["node"] for node in pve_list]
|
||||
elif pve_type == "pool":
|
||||
names = [pool["poolid"] for pool in pve_list]
|
||||
|
||||
return names
|
||||
|
||||
def _get_variables(self, pve_list, pve_type):
|
||||
variables = {}
|
||||
|
||||
if pve_type in ["qemu", "container"]:
|
||||
for vm in pve_list:
|
||||
nested = {}
|
||||
for key, value in iteritems(vm):
|
||||
nested["proxmox_" + key] = value
|
||||
variables[vm["name"]] = nested
|
||||
|
||||
return variables
|
||||
|
||||
def _get_ip_address(self, pve_type, pve_node, vmid):
|
||||
|
||||
def validate(address):
|
||||
try:
|
||||
# IP address validation
|
||||
if socket.inet_aton(address):
|
||||
# Ignore localhost
|
||||
if address != "127.0.0.1":
|
||||
return address
|
||||
except socket.error:
|
||||
return False
|
||||
|
||||
address = False
|
||||
networks = False
|
||||
if pve_type == "qemu":
|
||||
# If qemu agent is enabled, try to gather the IP address
|
||||
try:
|
||||
if self.client.nodes(pve_node).get(pve_type, vmid, "agent", "info") is not None:
|
||||
networks = self.client.nodes(pve_node).get(
|
||||
"qemu", vmid, "agent", "network-get-interfaces"
|
||||
)["result"]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if networks:
|
||||
if type(networks) is list:
|
||||
for network in networks:
|
||||
for ip_address in network["ip-addresses"]:
|
||||
address = validate(ip_address["ip-address"])
|
||||
else:
|
||||
try:
|
||||
config = self.client.nodes(pve_node).get(pve_type, vmid, "config")
|
||||
address = re.search(r"ip=(\d*\.\d*\.\d*\.\d*)", config["net0"]).group(1)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return address
|
||||
|
||||
def _exclude(self, pve_list):
|
||||
filtered = []
|
||||
for item in pve_list:
|
||||
obj = defaultdict(dict, item)
|
||||
if obj["template"] == 1:
|
||||
continue
|
||||
|
||||
if obj["status"] in self.get_option("exclude_state"):
|
||||
continue
|
||||
|
||||
if obj["vmid"] in self.get_option("exclude_vmid"):
|
||||
continue
|
||||
|
||||
filtered.append(item.copy())
|
||||
return filtered
|
||||
|
||||
def _propagate(self):
|
||||
for node in self._get_names(self.client.nodes.get(), "node"):
|
||||
try:
|
||||
qemu_list = self._exclude(self.client.nodes(node).qemu.get())
|
||||
container_list = self._exclude(self.client.nodes(node).lxc.get())
|
||||
except Exception as e:
|
||||
raise AnsibleError("Proxmoxer API error: {0}".format(to_native(e)))
|
||||
|
||||
# Merge QEMU and Containers lists from this node
|
||||
instances = self._get_variables(qemu_list.copy(), "qemu")
|
||||
instances.update(self._get_variables(container_list, "container"))
|
||||
|
||||
for host in instances:
|
||||
vmid = instances[host]["proxmox_vmid"]
|
||||
|
||||
try:
|
||||
pve_type = instances[host]["proxmox_type"]
|
||||
except KeyError:
|
||||
pve_type = "qemu"
|
||||
|
||||
try:
|
||||
description = self.client.nodes(node).get(pve_type, vmid,
|
||||
"config")["description"]
|
||||
except KeyError:
|
||||
description = None
|
||||
except Exception as e:
|
||||
raise AnsibleError("Proxmoxer API error: {0}".format(to_native(e)))
|
||||
|
||||
try:
|
||||
metadata = json.loads(description)
|
||||
except TypeError:
|
||||
metadata = {}
|
||||
except ValueError:
|
||||
metadata = {"notes": description}
|
||||
|
||||
# Add hosts to default group
|
||||
self.inventory.add_group(group=self.get_option("group"))
|
||||
self.inventory.add_host(group=self.get_option("group"), host=host)
|
||||
|
||||
# Group hosts by status
|
||||
self.inventory.add_group(group=instances[host]["proxmox_status"])
|
||||
self.inventory.add_host(group=instances[host]["proxmox_status"], host=host)
|
||||
|
||||
if "groups" in metadata:
|
||||
for group in metadata["groups"]:
|
||||
self.inventory.add_group(group=group)
|
||||
self.inventory.add_host(group=group, host=host)
|
||||
|
||||
if self.get_option("want_facts"):
|
||||
for attr in instances[host]:
|
||||
if attr not in ["proxmox_template"]:
|
||||
self.inventory.set_variable(host, attr, instances[host][attr])
|
||||
|
||||
address = self._get_ip_address(pve_type, node, vmid)
|
||||
if address:
|
||||
self.inventory.set_variable(host, "ansible_host", address)
|
||||
|
||||
for pool in self._get_names(self.client.pools.get(), "pool"):
|
||||
try:
|
||||
pool_list = self._exclude(self.client.pool(pool).get()["members"])
|
||||
except Exception as e:
|
||||
raise AnsibleError("Proxmoxer API error: {0}".format(to_native(e)))
|
||||
|
||||
members = [
|
||||
member["name"]
|
||||
for member in pool_list
|
||||
if (member["type"] == "qemu" or member["type"] == "lxc")
|
||||
]
|
||||
|
||||
for member in members:
|
||||
self.inventory.add_host(group=pool, host=member)
|
||||
|
||||
def verify_file(self, path):
|
||||
"""Verify the Proxmox VE configuration file."""
|
||||
if super(InventoryModule, self).verify_file(path):
|
||||
endings = ("proxmox.yaml", "proxmox.yml")
|
||||
if any((path.endswith(ending) for ending in endings)):
|
||||
return True
|
||||
return False
|
||||
|
||||
def parse(self, inventory, loader, path, cache=True):
|
||||
"""Dynamically parse the Proxmox VE cloud inventory."""
|
||||
if not HAS_PROXMOXER:
|
||||
raise AnsibleError(
|
||||
"The Proxmox VE dynamic inventory plugin requires proxmoxer: "
|
||||
"https://pypi.org/project/proxmoxer/"
|
||||
)
|
||||
|
||||
super(InventoryModule, self).parse(inventory, loader, path)
|
||||
|
||||
self._read_config_data(path)
|
||||
self.client = self._auth()
|
||||
self._propagate()
|
25
setup.cfg
Normal file
25
setup.cfg
Normal file
@ -0,0 +1,25 @@
|
||||
[isort]
|
||||
default_section = THIRDPARTY
|
||||
known_first_party = ansiblelater
|
||||
sections = FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER
|
||||
force_single_line = true
|
||||
line_length = 99
|
||||
skip_glob = **/.env*,**/env/*,**/docs/*,**/inventory/*
|
||||
|
||||
[yapf]
|
||||
based_on_style = google
|
||||
column_limit = 99
|
||||
dedent_closing_brackets = true
|
||||
coalesce_brackets = true
|
||||
split_before_logical_operator = true
|
||||
|
||||
[tool:pytest]
|
||||
filterwarnings =
|
||||
ignore::FutureWarning
|
||||
ignore:.*collections.*:DeprecationWarning
|
||||
ignore:.*pep8.*:FutureWarning
|
||||
|
||||
[coverage:run]
|
||||
omit =
|
||||
**/test/*
|
||||
**/.env/*
|
1
test/unit/plugins/inventory/__init__.py
Normal file
1
test/unit/plugins/inventory/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
# noqa
|
90
test/unit/plugins/inventory/test_proxmox.py
Normal file
90
test/unit/plugins/inventory/test_proxmox.py
Normal file
@ -0,0 +1,90 @@
|
||||
"""Test inventory plugin proxmox."""
|
||||
# -*- coding: utf-8 -*-
|
||||
# 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)
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
|
||||
import pytest
|
||||
|
||||
proxmox = pytest.importorskip("proxmoxer")
|
||||
|
||||
from ansible.errors import AnsibleError, AnsibleParserError # noqa
|
||||
from plugins.inventory.proxmox import InventoryModule
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def inventory():
|
||||
return InventoryModule()
|
||||
|
||||
|
||||
def test_get_names(inventory):
|
||||
nodes = [{"status": "online", "type": "node", "id": "node/testnode", "node": "testnode"}]
|
||||
pools = [{"poolid": "testpool"}]
|
||||
|
||||
assert ["testnode"] == inventory._get_names(nodes, "node")
|
||||
assert ["testpool"] == inventory._get_names(pools, "pool")
|
||||
|
||||
|
||||
def test_get_variables(inventory):
|
||||
pve_list = [{
|
||||
"status": "running",
|
||||
"vmid": "100",
|
||||
"name": "test",
|
||||
}]
|
||||
|
||||
variables = {
|
||||
"test": {
|
||||
"proxmox_status": "running",
|
||||
"proxmox_vmid": "100",
|
||||
"proxmox_name": "test",
|
||||
}
|
||||
}
|
||||
|
||||
assert variables == inventory._get_variables(pve_list, "qemu")
|
||||
|
||||
|
||||
def test_get_ip_address(inventory, mocker):
|
||||
networks = {
|
||||
"result": [{
|
||||
"ip-addresses": [{
|
||||
"ip-address": "10.0.0.1",
|
||||
"prefix": 26,
|
||||
"ip-address-type": "ipv4"
|
||||
}],
|
||||
"name": "eth0"
|
||||
}]
|
||||
}
|
||||
inventory.client = mocker.MagicMock()
|
||||
inventory.client.nodes.return_value.get.return_value = networks
|
||||
|
||||
assert "10.0.0.1" == inventory._get_ip_address("qemu", None, None)
|
||||
|
||||
|
||||
def test_exclude(inventory, mocker):
|
||||
|
||||
def get_option(name, *args, **kwargs):
|
||||
if name == "exclude_state":
|
||||
return ["stopped"]
|
||||
|
||||
return []
|
||||
|
||||
inventory.get_option = mocker.MagicMock(side_effect=get_option)
|
||||
|
||||
pve_list = [{
|
||||
"status": "running",
|
||||
"vmid": "100",
|
||||
"name": "test",
|
||||
}, {
|
||||
"status": "stopped",
|
||||
"vmid": "101",
|
||||
"name": "stop",
|
||||
}]
|
||||
|
||||
filtered = [{
|
||||
"status": "running",
|
||||
"vmid": "100",
|
||||
"name": "test",
|
||||
}]
|
||||
|
||||
assert filtered == inventory._exclude(pve_list)
|
5
test/unit/requirements.txt
Normal file
5
test/unit/requirements.txt
Normal file
@ -0,0 +1,5 @@
|
||||
ansible
|
||||
|
||||
# requirement for the proxmox module
|
||||
proxmoxer
|
||||
requests
|
Loading…
Reference in New Issue
Block a user