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