Compare commits

..

No commits in common. "main" and "v2.5.12" have entirely different histories.

49 changed files with 2567 additions and 1670 deletions

23
.chglog/CHANGELOG.tpl.md Executable file
View File

@ -0,0 +1,23 @@
# Changelog
{{ range .Versions -}}
## {{ if .Tag.Previous }}[{{ .Tag.Name }}]({{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }}){{ else }}{{ .Tag.Name }}{{ end }} ({{ datetime "2006-01-02" .Tag.Date }})
{{ range .CommitGroups -}}
### {{ .Title }}
{{ range .Commits -}}
- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ (regexReplaceAll "(.*)/issues/(.*)" (regexReplaceAll "(Co-\\w*-by.*)" .Subject "") "${1}/pull/${2}") | trim }}
{{ end }}
{{- end -}}
{{- if .NoteGroups -}}
{{ range .NoteGroups -}}
### {{ .Title }}
{{ range .Notes }}
{{ .Body }}
{{ end }}
{{ end -}}
{{ end -}}
{{ end -}}

25
.chglog/config.yml Executable file
View File

@ -0,0 +1,25 @@
style: github
template: CHANGELOG.tpl.md
info:
title: CHANGELOG
repository_url: https://github.com/thegeeklab/prometheus-pve-sd
options:
commit_groups:
title_maps:
feat: Features
fix: Bug Fixes
perf: Performance Improvements
refactor: Code Refactoring
chore: Others
test: Testing
ci: CI Pipeline
docs: Documentation
header:
pattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$"
pattern_maps:
- Type
- Scope
- Subject
notes:
keywords:
- BREAKING CHANGE

510
.drone.jsonnet Normal file
View File

@ -0,0 +1,510 @@
local PythonVersion(pyversion='3.7') = {
name: 'python' + std.strReplace(pyversion, '.', '') + '-pytest',
image: 'python:' + pyversion,
environment: {
PY_COLORS: 1,
},
commands: [
'pip install poetry poetry-dynamic-versioning -qq',
'poetry config experimental.new-installer false',
'poetry install',
'poetry run pytest --cov-append',
'poetry version',
'poetry run prometheus-pve-sd --help',
],
depends_on: [
'fetch',
],
};
local PipelineLint = {
kind: 'pipeline',
name: 'lint',
platform: {
os: 'linux',
arch: 'amd64',
},
steps: [
{
name: 'yapf',
image: 'python:3.10',
environment: {
PY_COLORS: 1,
},
commands: [
'git fetch -tq',
'pip install poetry poetry-dynamic-versioning -qq',
'poetry config experimental.new-installer false',
'poetry install',
'poetry run yapf -dr ./prometheuspvesd',
],
},
{
name: 'flake8',
image: 'python:3.10',
environment: {
PY_COLORS: 1,
},
commands: [
'git fetch -tq',
'pip install poetry poetry-dynamic-versioning -qq',
'poetry config experimental.new-installer false',
'poetry install',
'poetry run flake8 ./prometheuspvesd',
],
},
],
trigger: {
ref: ['refs/heads/main', 'refs/tags/**', 'refs/pull/**'],
},
};
local PipelineTest = {
kind: 'pipeline',
name: 'test',
platform: {
os: 'linux',
arch: 'amd64',
},
steps: [
{
name: 'fetch',
image: 'python:3.10',
commands: [
'git fetch -tq',
],
},
PythonVersion(pyversion='3.7'),
PythonVersion(pyversion='3.8'),
PythonVersion(pyversion='3.9'),
PythonVersion(pyversion='3.10'),
{
name: 'codecov',
image: 'python:3.10',
environment: {
PY_COLORS: 1,
CODECOV_TOKEN: { from_secret: 'codecov_token' },
},
commands: [
'pip install codecov -qq',
'codecov --required -X gcov',
],
depends_on: [
'python37-pytest',
'python38-pytest',
'python39-pytest',
'python310-pytest',
],
},
],
depends_on: [
'lint',
],
trigger: {
ref: ['refs/heads/main', 'refs/tags/**', 'refs/pull/**'],
},
};
local PipelineSecurity = {
kind: 'pipeline',
name: 'security',
platform: {
os: 'linux',
arch: 'amd64',
},
steps: [
{
name: 'bandit',
image: 'python:3.10',
environment: {
PY_COLORS: 1,
},
commands: [
'git fetch -tq',
'pip install poetry poetry-dynamic-versioning -qq',
'poetry config experimental.new-installer false',
'poetry install',
'poetry run bandit -r ./prometheuspvesd -x ./prometheuspvesd/test',
],
},
],
depends_on: [
'test',
],
trigger: {
ref: ['refs/heads/main', 'refs/tags/**', 'refs/pull/**'],
},
};
local PipelineBuildPackage = {
kind: 'pipeline',
name: 'build-package',
platform: {
os: 'linux',
arch: 'amd64',
},
steps: [
{
name: 'build',
image: 'python:3.10',
commands: [
'git fetch -tq',
'pip install poetry poetry-dynamic-versioning -qq',
'poetry build',
],
},
{
name: 'checksum',
image: 'alpine',
commands: [
'cd dist/ && sha256sum * > ../sha256sum.txt',
],
},
{
name: 'changelog-generate',
image: 'thegeeklab/git-chglog',
commands: [
'git fetch -tq',
'git-chglog --no-color --no-emoji -o CHANGELOG.md ${DRONE_TAG:---next-tag unreleased unreleased}',
],
},
{
name: 'changelog-format',
image: 'thegeeklab/alpine-tools',
commands: [
'prettier CHANGELOG.md',
'prettier -w CHANGELOG.md',
],
},
{
name: 'publish-github',
image: 'plugins/github-release',
settings: {
overwrite: true,
api_key: { from_secret: 'github_token' },
files: ['dist/*', 'sha256sum.txt'],
title: '${DRONE_TAG}',
note: 'CHANGELOG.md',
},
when: {
ref: ['refs/tags/**'],
},
},
{
name: 'publish-pypi',
image: 'python:3.10',
commands: [
'git fetch -tq',
'pip install poetry poetry-dynamic-versioning -qq',
'poetry publish -n',
],
environment: {
POETRY_HTTP_BASIC_PYPI_USERNAME: { from_secret: 'pypi_username' },
POETRY_HTTP_BASIC_PYPI_PASSWORD: { from_secret: 'pypi_password' },
},
when: {
ref: ['refs/tags/**'],
},
},
],
depends_on: [
'security',
],
trigger: {
ref: ['refs/heads/main', 'refs/tags/**', 'refs/pull/**'],
},
};
local PipelineBuildContainer(arch='amd64') = {
local build = if arch == 'arm' then [{
name: 'build',
image: 'python:3.10-alpine',
commands: [
'apk add -Uq --no-cache build-base openssl-dev libffi-dev musl-dev python3-dev git cargo',
'git fetch -tq',
'pip install poetry poetry-dynamic-versioning -qq',
'poetry build',
],
environment: {
CARGO_NET_GIT_FETCH_WITH_CLI: true,
},
}] else [{
name: 'build',
image: 'python:3.10',
commands: [
'git fetch -tq',
'pip install poetry poetry-dynamic-versioning -qq',
'poetry build',
],
}],
kind: 'pipeline',
name: 'build-container-' + arch,
platform: {
os: 'linux',
arch: arch,
},
steps: build + [
{
name: 'dryrun',
image: 'thegeeklab/drone-docker:19',
settings: {
dry_run: true,
dockerfile: 'docker/Dockerfile.' + arch,
repo: 'thegeeklab/${DRONE_REPO_NAME}',
username: { from_secret: 'docker_username' },
password: { from_secret: 'docker_password' },
},
depends_on: ['build'],
when: {
ref: ['refs/pull/**'],
},
},
{
name: 'publish-dockerhub',
image: 'thegeeklab/drone-docker:19',
settings: {
auto_tag: true,
auto_tag_suffix: arch,
dockerfile: 'docker/Dockerfile.' + arch,
repo: 'thegeeklab/${DRONE_REPO_NAME}',
username: { from_secret: 'docker_username' },
password: { from_secret: 'docker_password' },
},
when: {
ref: ['refs/heads/main', 'refs/tags/**'],
},
depends_on: ['dryrun'],
},
{
name: 'publish-quay',
image: 'thegeeklab/drone-docker:19',
settings: {
auto_tag: true,
auto_tag_suffix: arch,
dockerfile: 'docker/Dockerfile.' + arch,
registry: 'quay.io',
repo: 'quay.io/thegeeklab/${DRONE_REPO_NAME}',
username: { from_secret: 'quay_username' },
password: { from_secret: 'quay_password' },
},
when: {
ref: ['refs/heads/main', 'refs/tags/**'],
},
depends_on: ['dryrun'],
},
],
depends_on: [
'security',
],
trigger: {
ref: ['refs/heads/main', 'refs/tags/**', 'refs/pull/**'],
},
};
local PipelineDocs = {
kind: 'pipeline',
name: 'docs',
platform: {
os: 'linux',
arch: 'amd64',
},
concurrency: {
limit: 1,
},
steps: [
{
name: 'assets',
image: 'thegeeklab/alpine-tools',
commands: [
'make doc',
],
},
{
name: 'markdownlint',
image: 'thegeeklab/markdownlint-cli',
commands: [
"markdownlint 'docs/content/**/*.md' 'README.md' 'CONTRIBUTING.md'",
],
},
{
name: 'spellcheck',
image: 'thegeeklab/alpine-tools',
commands: [
"spellchecker --files 'docs/content/**/*.md' 'README.md' -d .dictionary -p spell indefinite-article syntax-urls --no-suggestions",
],
environment: {
FORCE_COLOR: true,
NPM_CONFIG_LOGLEVEL: 'error',
},
},
{
name: 'testbuild',
image: 'thegeeklab/hugo:0.97.3',
commands: [
'hugo --panicOnWarning -s docs/ -b http://localhost:8000/',
],
},
{
name: 'link-validation',
image: 'thegeeklab/link-validator',
commands: [
'link-validator --nice --external --skip-file .linkcheckignore',
],
environment: {
LINK_VALIDATOR_BASE_DIR: 'docs/public',
},
},
{
name: 'build',
image: 'thegeeklab/hugo:0.97.3',
commands: [
'hugo --panicOnWarning -s docs/',
],
},
{
name: 'beautify',
image: 'thegeeklab/alpine-tools',
commands: [
"html-beautify -r -f 'docs/public/**/*.html'",
],
environment: {
FORCE_COLOR: true,
NPM_CONFIG_LOGLEVEL: 'error',
},
},
{
name: 'publish',
image: 'thegeeklab/drone-s3-sync',
settings: {
access_key: { from_secret: 's3_access_key' },
bucket: 'geekdocs',
delete: true,
endpoint: 'https://sp.rknet.org',
path_style: true,
secret_key: { from_secret: 's3_secret_access_key' },
source: 'docs/public/',
strip_prefix: 'docs/public/',
target: '/${DRONE_REPO_NAME}',
},
when: {
ref: ['refs/heads/main', 'refs/tags/**'],
},
},
],
depends_on: [
'build-package',
'build-container-amd64',
'build-container-arm64',
'build-container-arm',
],
trigger: {
ref: ['refs/heads/main', 'refs/tags/**', 'refs/pull/**'],
},
};
local PipelineNotifications = {
kind: 'pipeline',
name: 'notifications',
platform: {
os: 'linux',
arch: 'amd64',
},
steps: [
{
image: 'plugins/manifest',
name: 'manifest-dockerhub',
settings: {
ignore_missing: true,
auto_tag: true,
username: { from_secret: 'docker_username' },
password: { from_secret: 'docker_password' },
spec: 'docker/manifest.tmpl',
},
when: {
status: ['success'],
},
},
{
image: 'plugins/manifest',
name: 'manifest-quay',
settings: {
ignore_missing: true,
auto_tag: true,
username: { from_secret: 'quay_username' },
password: { from_secret: 'quay_password' },
spec: 'docker/manifest-quay.tmpl',
},
when: {
status: ['success'],
},
},
{
name: 'pushrm-dockerhub',
pull: 'always',
image: 'chko/docker-pushrm:1',
environment: {
DOCKER_PASS: {
from_secret: 'docker_password',
},
DOCKER_USER: {
from_secret: 'docker_username',
},
PUSHRM_FILE: 'README.md',
PUSHRM_SHORT: 'Prometheus Service Discovery for Proxmox VE',
PUSHRM_TARGET: 'thegeeklab/${DRONE_REPO_NAME}',
},
when: {
status: ['success'],
},
},
{
name: 'pushrm-quay',
pull: 'always',
image: 'chko/docker-pushrm:1',
environment: {
APIKEY__QUAY_IO: {
from_secret: 'quay_token',
},
PUSHRM_FILE: 'README.md',
PUSHRM_TARGET: 'quay.io/thegeeklab/${DRONE_REPO_NAME}',
},
when: {
status: ['success'],
},
},
{
name: 'matrix',
image: 'thegeeklab/drone-matrix',
settings: {
homeserver: { from_secret: 'matrix_homeserver' },
roomid: { from_secret: 'matrix_roomid' },
template: 'Status: **{{ build.Status }}**<br/> Build: [{{ repo.Owner }}/{{ repo.Name }}]({{ build.Link }}){{#if build.Branch}} ({{ build.Branch }}){{/if}} by {{ commit.Author }}<br/> Message: {{ commit.Message.Title }}',
username: { from_secret: 'matrix_username' },
password: { from_secret: 'matrix_password' },
},
when: {
status: ['success', 'failure'],
},
},
],
depends_on: [
'docs',
],
trigger: {
ref: ['refs/heads/main', 'refs/tags/**'],
status: ['success', 'failure'],
},
};
[
PipelineLint,
PipelineTest,
PipelineSecurity,
PipelineBuildPackage,
PipelineBuildContainer(arch='amd64'),
PipelineBuildContainer(arch='arm64'),
PipelineBuildContainer(arch='arm'),
PipelineDocs,
PipelineNotifications,
]

652
.drone.yml Normal file
View File

@ -0,0 +1,652 @@
---
kind: pipeline
name: lint
platform:
os: linux
arch: amd64
steps:
- name: yapf
image: python:3.10
commands:
- git fetch -tq
- pip install poetry poetry-dynamic-versioning -qq
- poetry config experimental.new-installer false
- poetry install
- poetry run yapf -dr ./prometheuspvesd
environment:
PY_COLORS: 1
- name: flake8
image: python:3.10
commands:
- git fetch -tq
- pip install poetry poetry-dynamic-versioning -qq
- poetry config experimental.new-installer false
- poetry install
- poetry run flake8 ./prometheuspvesd
environment:
PY_COLORS: 1
trigger:
ref:
- refs/heads/main
- refs/tags/**
- refs/pull/**
---
kind: pipeline
name: test
platform:
os: linux
arch: amd64
steps:
- name: fetch
image: python:3.10
commands:
- git fetch -tq
- name: python37-pytest
image: python:3.7
commands:
- pip install poetry poetry-dynamic-versioning -qq
- poetry config experimental.new-installer false
- poetry install
- poetry run pytest --cov-append
- poetry version
- poetry run prometheus-pve-sd --help
environment:
PY_COLORS: 1
depends_on:
- fetch
- name: python38-pytest
image: python:3.8
commands:
- pip install poetry poetry-dynamic-versioning -qq
- poetry config experimental.new-installer false
- poetry install
- poetry run pytest --cov-append
- poetry version
- poetry run prometheus-pve-sd --help
environment:
PY_COLORS: 1
depends_on:
- fetch
- name: python39-pytest
image: python:3.9
commands:
- pip install poetry poetry-dynamic-versioning -qq
- poetry config experimental.new-installer false
- poetry install
- poetry run pytest --cov-append
- poetry version
- poetry run prometheus-pve-sd --help
environment:
PY_COLORS: 1
depends_on:
- fetch
- name: python310-pytest
image: python:3.10
commands:
- pip install poetry poetry-dynamic-versioning -qq
- poetry config experimental.new-installer false
- poetry install
- poetry run pytest --cov-append
- poetry version
- poetry run prometheus-pve-sd --help
environment:
PY_COLORS: 1
depends_on:
- fetch
- name: codecov
image: python:3.10
commands:
- pip install codecov -qq
- codecov --required -X gcov
environment:
CODECOV_TOKEN:
from_secret: codecov_token
PY_COLORS: 1
depends_on:
- python37-pytest
- python38-pytest
- python39-pytest
- python310-pytest
trigger:
ref:
- refs/heads/main
- refs/tags/**
- refs/pull/**
depends_on:
- lint
---
kind: pipeline
name: security
platform:
os: linux
arch: amd64
steps:
- name: bandit
image: python:3.10
commands:
- git fetch -tq
- pip install poetry poetry-dynamic-versioning -qq
- poetry config experimental.new-installer false
- poetry install
- poetry run bandit -r ./prometheuspvesd -x ./prometheuspvesd/test
environment:
PY_COLORS: 1
trigger:
ref:
- refs/heads/main
- refs/tags/**
- refs/pull/**
depends_on:
- test
---
kind: pipeline
name: build-package
platform:
os: linux
arch: amd64
steps:
- name: build
image: python:3.10
commands:
- git fetch -tq
- pip install poetry poetry-dynamic-versioning -qq
- poetry build
- name: checksum
image: alpine
commands:
- cd dist/ && sha256sum * > ../sha256sum.txt
- name: changelog-generate
image: thegeeklab/git-chglog
commands:
- git fetch -tq
- git-chglog --no-color --no-emoji -o CHANGELOG.md ${DRONE_TAG:---next-tag unreleased unreleased}
- name: changelog-format
image: thegeeklab/alpine-tools
commands:
- prettier CHANGELOG.md
- prettier -w CHANGELOG.md
- name: publish-github
image: plugins/github-release
settings:
api_key:
from_secret: github_token
files:
- dist/*
- sha256sum.txt
note: CHANGELOG.md
overwrite: true
title: ${DRONE_TAG}
when:
ref:
- refs/tags/**
- name: publish-pypi
image: python:3.10
commands:
- git fetch -tq
- pip install poetry poetry-dynamic-versioning -qq
- poetry publish -n
environment:
POETRY_HTTP_BASIC_PYPI_PASSWORD:
from_secret: pypi_password
POETRY_HTTP_BASIC_PYPI_USERNAME:
from_secret: pypi_username
when:
ref:
- refs/tags/**
trigger:
ref:
- refs/heads/main
- refs/tags/**
- refs/pull/**
depends_on:
- security
---
kind: pipeline
name: build-container-amd64
platform:
os: linux
arch: amd64
steps:
- name: build
image: python:3.10
commands:
- git fetch -tq
- pip install poetry poetry-dynamic-versioning -qq
- poetry build
- name: dryrun
image: thegeeklab/drone-docker:19
settings:
dockerfile: docker/Dockerfile.amd64
dry_run: true
password:
from_secret: docker_password
repo: thegeeklab/${DRONE_REPO_NAME}
username:
from_secret: docker_username
when:
ref:
- refs/pull/**
depends_on:
- build
- name: publish-dockerhub
image: thegeeklab/drone-docker:19
settings:
auto_tag: true
auto_tag_suffix: amd64
dockerfile: docker/Dockerfile.amd64
password:
from_secret: docker_password
repo: thegeeklab/${DRONE_REPO_NAME}
username:
from_secret: docker_username
when:
ref:
- refs/heads/main
- refs/tags/**
depends_on:
- dryrun
- name: publish-quay
image: thegeeklab/drone-docker:19
settings:
auto_tag: true
auto_tag_suffix: amd64
dockerfile: docker/Dockerfile.amd64
password:
from_secret: quay_password
registry: quay.io
repo: quay.io/thegeeklab/${DRONE_REPO_NAME}
username:
from_secret: quay_username
when:
ref:
- refs/heads/main
- refs/tags/**
depends_on:
- dryrun
trigger:
ref:
- refs/heads/main
- refs/tags/**
- refs/pull/**
depends_on:
- security
---
kind: pipeline
name: build-container-arm64
platform:
os: linux
arch: arm64
steps:
- name: build
image: python:3.10
commands:
- git fetch -tq
- pip install poetry poetry-dynamic-versioning -qq
- poetry build
- name: dryrun
image: thegeeklab/drone-docker:19
settings:
dockerfile: docker/Dockerfile.arm64
dry_run: true
password:
from_secret: docker_password
repo: thegeeklab/${DRONE_REPO_NAME}
username:
from_secret: docker_username
when:
ref:
- refs/pull/**
depends_on:
- build
- name: publish-dockerhub
image: thegeeklab/drone-docker:19
settings:
auto_tag: true
auto_tag_suffix: arm64
dockerfile: docker/Dockerfile.arm64
password:
from_secret: docker_password
repo: thegeeklab/${DRONE_REPO_NAME}
username:
from_secret: docker_username
when:
ref:
- refs/heads/main
- refs/tags/**
depends_on:
- dryrun
- name: publish-quay
image: thegeeklab/drone-docker:19
settings:
auto_tag: true
auto_tag_suffix: arm64
dockerfile: docker/Dockerfile.arm64
password:
from_secret: quay_password
registry: quay.io
repo: quay.io/thegeeklab/${DRONE_REPO_NAME}
username:
from_secret: quay_username
when:
ref:
- refs/heads/main
- refs/tags/**
depends_on:
- dryrun
trigger:
ref:
- refs/heads/main
- refs/tags/**
- refs/pull/**
depends_on:
- security
---
kind: pipeline
name: build-container-arm
platform:
os: linux
arch: arm
steps:
- name: build
image: python:3.10-alpine
commands:
- apk add -Uq --no-cache build-base openssl-dev libffi-dev musl-dev python3-dev git cargo
- git fetch -tq
- pip install poetry poetry-dynamic-versioning -qq
- poetry build
environment:
CARGO_NET_GIT_FETCH_WITH_CLI: true
- name: dryrun
image: thegeeklab/drone-docker:19
settings:
dockerfile: docker/Dockerfile.arm
dry_run: true
password:
from_secret: docker_password
repo: thegeeklab/${DRONE_REPO_NAME}
username:
from_secret: docker_username
when:
ref:
- refs/pull/**
depends_on:
- build
- name: publish-dockerhub
image: thegeeklab/drone-docker:19
settings:
auto_tag: true
auto_tag_suffix: arm
dockerfile: docker/Dockerfile.arm
password:
from_secret: docker_password
repo: thegeeklab/${DRONE_REPO_NAME}
username:
from_secret: docker_username
when:
ref:
- refs/heads/main
- refs/tags/**
depends_on:
- dryrun
- name: publish-quay
image: thegeeklab/drone-docker:19
settings:
auto_tag: true
auto_tag_suffix: arm
dockerfile: docker/Dockerfile.arm
password:
from_secret: quay_password
registry: quay.io
repo: quay.io/thegeeklab/${DRONE_REPO_NAME}
username:
from_secret: quay_username
when:
ref:
- refs/heads/main
- refs/tags/**
depends_on:
- dryrun
trigger:
ref:
- refs/heads/main
- refs/tags/**
- refs/pull/**
depends_on:
- security
---
kind: pipeline
name: docs
platform:
os: linux
arch: amd64
concurrency:
limit: 1
steps:
- name: assets
image: thegeeklab/alpine-tools
commands:
- make doc
- name: markdownlint
image: thegeeklab/markdownlint-cli
commands:
- markdownlint 'docs/content/**/*.md' 'README.md' 'CONTRIBUTING.md'
- name: spellcheck
image: thegeeklab/alpine-tools
commands:
- spellchecker --files 'docs/content/**/*.md' 'README.md' -d .dictionary -p spell indefinite-article syntax-urls --no-suggestions
environment:
FORCE_COLOR: true
NPM_CONFIG_LOGLEVEL: error
- name: testbuild
image: thegeeklab/hugo:0.97.3
commands:
- hugo --panicOnWarning -s docs/ -b http://localhost:8000/
- name: link-validation
image: thegeeklab/link-validator
commands:
- link-validator --nice --external --skip-file .linkcheckignore
environment:
LINK_VALIDATOR_BASE_DIR: docs/public
- name: build
image: thegeeklab/hugo:0.97.3
commands:
- hugo --panicOnWarning -s docs/
- name: beautify
image: thegeeklab/alpine-tools
commands:
- html-beautify -r -f 'docs/public/**/*.html'
environment:
FORCE_COLOR: true
NPM_CONFIG_LOGLEVEL: error
- name: publish
image: thegeeklab/drone-s3-sync
settings:
access_key:
from_secret: s3_access_key
bucket: geekdocs
delete: true
endpoint: https://sp.rknet.org
path_style: true
secret_key:
from_secret: s3_secret_access_key
source: docs/public/
strip_prefix: docs/public/
target: /${DRONE_REPO_NAME}
when:
ref:
- refs/heads/main
- refs/tags/**
trigger:
ref:
- refs/heads/main
- refs/tags/**
- refs/pull/**
depends_on:
- build-package
- build-container-amd64
- build-container-arm64
- build-container-arm
---
kind: pipeline
name: notifications
platform:
os: linux
arch: amd64
steps:
- name: manifest-dockerhub
image: plugins/manifest
settings:
auto_tag: true
ignore_missing: true
password:
from_secret: docker_password
spec: docker/manifest.tmpl
username:
from_secret: docker_username
when:
status:
- success
- name: manifest-quay
image: plugins/manifest
settings:
auto_tag: true
ignore_missing: true
password:
from_secret: quay_password
spec: docker/manifest-quay.tmpl
username:
from_secret: quay_username
when:
status:
- success
- name: pushrm-dockerhub
pull: always
image: chko/docker-pushrm:1
environment:
DOCKER_PASS:
from_secret: docker_password
DOCKER_USER:
from_secret: docker_username
PUSHRM_FILE: README.md
PUSHRM_SHORT: Prometheus Service Discovery for Proxmox VE
PUSHRM_TARGET: thegeeklab/${DRONE_REPO_NAME}
when:
status:
- success
- name: pushrm-quay
pull: always
image: chko/docker-pushrm:1
environment:
APIKEY__QUAY_IO:
from_secret: quay_token
PUSHRM_FILE: README.md
PUSHRM_TARGET: quay.io/thegeeklab/${DRONE_REPO_NAME}
when:
status:
- success
- name: matrix
image: thegeeklab/drone-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 }}){{#if build.Branch}} ({{ build.Branch }}){{/if}} by {{ commit.Author }}<br/> Message: {{ commit.Message.Title }}"
username:
from_secret: matrix_username
when:
status:
- success
- failure
trigger:
ref:
- refs/heads/main
- refs/tags/**
status:
- success
- failure
depends_on:
- docs
---
kind: signature
hmac: fb17f8e878e4a97dc5654eb0a32d3b23b5154a5b7d1d499997d7dd4f1d827880
...

View File

@ -52,11 +52,7 @@ branches:
required_status_checks:
strict: false
contexts:
- ci/woodpecker/pr/lint
- ci/woodpecker/pr/test
- ci/woodpecker/pr/build-package
- ci/woodpecker/pr/build-container
- ci/woodpecker/pr/docs
enforce_admins: false
- continuous-integration/drone/pr
enforce_admins: true
required_linear_history: true
restrictions: null

1
.gitignore vendored
View File

@ -110,4 +110,3 @@ resources/_gen/
# Misc
CHANGELOG.md
.ruff_cache

View File

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

View File

@ -1 +0,0 @@
https://hub.docker.com/r/thegeeklab/*

View File

@ -1,2 +1,3 @@
.drone.yml
*.tpl.md
LICENSE

View File

@ -1,84 +0,0 @@
---
when:
- event: [pull_request, tag]
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
steps:
- name: build
image: docker.io/library/python:3.13
commands:
- pip install poetry poetry-dynamic-versioning -qq
- poetry build
- name: security-build
image: quay.io/thegeeklab/wp-docker-buildx:6
depends_on: [build]
settings:
containerfile: Containerfile.multiarch
output: type=oci,dest=oci/${CI_REPO_NAME},tar=false
repo: ${CI_REPO}
registry_config:
from_secret: DOCKER_REGISTRY_CONFIG_PULL
- name: security-scan
image: docker.io/aquasec/trivy
depends_on: [security-build]
commands:
- trivy -v
- trivy image --input oci/${CI_REPO_NAME}
environment:
TRIVY_EXIT_CODE: "1"
TRIVY_IGNORE_UNFIXED: "true"
TRIVY_NO_PROGRESS: "true"
TRIVY_SEVERITY: HIGH,CRITICAL
TRIVY_TIMEOUT: 1m
TRIVY_DB_REPOSITORY: docker.io/aquasec/trivy-db:2
- name: publish-dockerhub
image: quay.io/thegeeklab/wp-docker-buildx:6
depends_on: [security-scan]
settings:
auto_tag: true
containerfile: Containerfile.multiarch
password:
from_secret: docker_password
platforms:
- linux/amd64
- linux/arm64
provenance: false
repo: ${CI_REPO}
username:
from_secret: docker_username
when:
- event: [tag]
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
- name: publish-quay
image: quay.io/thegeeklab/wp-docker-buildx:6
depends_on: security-scan
settings:
auto_tag: true
containerfile: Containerfile.multiarch
password:
from_secret: quay_password
platforms:
- linux/amd64
- linux/arm64
provenance: false
registry: quay.io
repo: quay.io/${CI_REPO}
username:
from_secret: quay_username
when:
- event: [tag]
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
depends_on:
- lint
- test

View File

@ -1,56 +0,0 @@
---
when:
- event: [pull_request, tag]
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
steps:
- name: build
image: docker.io/library/python:3.13
commands:
- pip install poetry poetry-dynamic-versioning -qq
- poetry build
- name: checksum
image: quay.io/thegeeklab/alpine-tools
commands:
- cd dist/ && sha256sum * > ../sha256sum.txt
- name: changelog
image: quay.io/thegeeklab/git-sv
commands:
- git sv current-version
- git sv release-notes -t ${CI_COMMIT_TAG:-next} -o CHANGELOG.md
- cat CHANGELOG.md
- name: publish-github
image: docker.io/plugins/github-release
settings:
api_key:
from_secret: github_token
files:
- dist/*
- sha256sum.txt
note: CHANGELOG.md
overwrite: true
title: ${CI_COMMIT_TAG}
when:
- event: [tag]
- name: publish-pypi
image: docker.io/library/python:3.13
environment:
POETRY_HTTP_BASIC_PYPI_PASSWORD:
from_secret: pypi_password
POETRY_HTTP_BASIC_PYPI_USERNAME:
from_secret: pypi_username
commands:
- pip install poetry poetry-dynamic-versioning -qq
- poetry publish -n
when:
- event: [tag]
depends_on:
- lint
- test

View File

@ -1,101 +0,0 @@
---
when:
- event: [pull_request, tag]
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
steps:
- name: assets
image: quay.io/thegeeklab/alpine-tools
commands:
- make doc
- name: markdownlint
image: quay.io/thegeeklab/markdownlint-cli
depends_on: [assets]
commands:
- markdownlint 'README.md' 'CONTRIBUTING.md'
- name: spellcheck
image: quay.io/thegeeklab/alpine-tools
depends_on: [assets]
commands:
- spellchecker --files 'docs/**/*.md' 'README.md' 'CONTRIBUTING.md' -d .dictionary -p spell indefinite-article syntax-urls
environment:
FORCE_COLOR: "true"
- name: link-validation
image: docker.io/lycheeverse/lychee
depends_on: [assets]
commands:
- lychee --no-progress --format detailed docs/content README.md
- name: build
image: quay.io/thegeeklab/hugo:0.136.5
depends_on: [link-validation]
commands:
- hugo --panicOnWarning -s docs/
- name: beautify
image: quay.io/thegeeklab/alpine-tools
depends_on: [build]
commands:
- html-beautify -r -f 'docs/public/**/*.html'
- name: publish
image: quay.io/thegeeklab/wp-s3-action
depends_on: [beautify]
settings:
access_key:
from_secret: s3_access_key
bucket: geekdocs
delete: true
endpoint:
from_secret: s3_endpoint
path_style: true
secret_key:
from_secret: s3_secret_access_key
source: docs/public/
strip_prefix: docs/public/
target: /${CI_REPO_NAME}
when:
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
status: [success, failure]
- name: pushrm-dockerhub
image: docker.io/chko/docker-pushrm:1
depends_on: [publish]
environment:
DOCKER_PASS:
from_secret: docker_password
DOCKER_USER:
from_secret: docker_username
PUSHRM_FILE: README.md
PUSHRM_SHORT: Prometheus Service Discovery for Proxmox VE
PUSHRM_TARGET: ${CI_REPO}
when:
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
status: [success]
- name: pushrm-quay
image: docker.io/chko/docker-pushrm:1
depends_on: [publish]
environment:
APIKEY__QUAY_IO:
from_secret: quay_token
PUSHRM_FILE: README.md
PUSHRM_TARGET: quay.io/${CI_REPO}
when:
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
status: [success]
depends_on:
- build-package
- build-container

View File

@ -1,27 +0,0 @@
---
when:
- event: [pull_request, tag]
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
steps:
- name: check-format
image: docker.io/library/python:3.13
depends_on: []
commands:
- pip install poetry poetry-dynamic-versioning -qq
- poetry install
- poetry run ruff format --check --diff ./${CI_REPO_NAME//-/}
environment:
PY_COLORS: "1"
- name: check-coding
image: docker.io/library/python:3.13
depends_on: []
commands:
- pip install poetry poetry-dynamic-versioning -qq
- poetry install
- poetry run ruff check ./${CI_REPO_NAME//-/}
environment:
PY_COLORS: "1"

View File

@ -1,26 +0,0 @@
---
when:
- event: [tag]
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
runs_on: [success, failure]
steps:
- name: matrix
image: quay.io/thegeeklab/wp-matrix
settings:
homeserver:
from_secret: matrix_homeserver
room_id:
from_secret: matrix_room_id
user_id:
from_secret: matrix_user_id
access_token:
from_secret: matrix_access_token
when:
- status: [success, failure]
depends_on:
- docs

View File

@ -1,35 +0,0 @@
---
when:
- event: [pull_request, tag]
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
variables:
- &pytest_base
depends_on: []
commands:
- pip install poetry poetry-dynamic-versioning -qq
- poetry install
- poetry run pytest --cov-append
- poetry version
- poetry run ${CI_REPO_NAME} --help
environment:
PY_COLORS: "1"
steps:
- name: python-313
image: docker.io/library/python:3.13
<<: *pytest_base
- name: python-312
image: docker.io/library/python:3.12
<<: *pytest_base
- name: python-311
image: docker.io/library/python:3.11
<<: *pytest_base
- name: python-310
image: docker.io/library/python:3.10
<<: *pytest_base

View File

@ -3,7 +3,7 @@
## Security
If you think you have found a **security issue**, please do not mention it in this repository.
Instead, send an email to `security@thegeeklab.de` with as many details as possible so it can be handled confidential.
Instead, send an email to security@thegeeklab.de with as many details as possible so it can be handled confidential.
## Bug Reports and Feature Requests

View File

@ -1,5 +1,5 @@
# renovate: datasource=github-releases depName=thegeeklab/hugo-geekdoc
THEME_VERSION := v1.2.1
THEME_VERSION := v0.35.8
THEME := hugo-geekdoc
BASEDIR := docs
THEMEDIR := $(BASEDIR)/themes

View File

@ -2,12 +2,13 @@
Prometheus Service Discovery for Proxmox VE
[![Build Status](https://ci.thegeeklab.de/api/badges/thegeeklab/prometheus-pve-sd/status.svg)](https://ci.thegeeklab.de/repos/thegeeklab/prometheus-pve-sd)
[![Build Status](https://img.shields.io/drone/build/thegeeklab/prometheus-pve-sd?logo=drone&server=https%3A%2F%2Fdrone.thegeeklab.de)](https://drone.thegeeklab.de/thegeeklab/prometheus-pve-sd)
[![Docker Hub](https://img.shields.io/badge/dockerhub-latest-blue.svg?logo=docker&logoColor=white)](https://hub.docker.com/r/thegeeklab/prometheus-pve-sd)
[![Quay.io](https://img.shields.io/badge/quay-latest-blue.svg?logo=docker&logoColor=white)](https://quay.io/repository/thegeeklab/prometheus-pve-sd)
[![Python Version](https://img.shields.io/pypi/pyversions/prometheus-pve-sd.svg)](https://pypi.org/project/prometheus-pve-sd/)
[![PyPI Status](https://img.shields.io/pypi/status/prometheus-pve-sd.svg)](https://pypi.org/project/prometheus-pve-sd/)
[![PyPI Release](https://img.shields.io/pypi/v/prometheus-pve-sd.svg)](https://pypi.org/project/prometheus-pve-sd/)
[![Codecov](https://img.shields.io/codecov/c/github/thegeeklab/prometheus-pve-sd)](https://codecov.io/gh/thegeeklab/prometheus-pve-sd)
[![GitHub contributors](https://img.shields.io/github/contributors/thegeeklab/prometheus-pve-sd)](https://github.com/thegeeklab/prometheus-pve-sd/graphs/contributors)
[![Source: GitHub](https://img.shields.io/badge/source-github-blue.svg?logo=github&logoColor=white)](https://github.com/thegeeklab/prometheus-pve-sd)
[![License: MIT](https://img.shields.io/github/license/thegeeklab/prometheus-pve-sd)](https://github.com/thegeeklab/prometheus-pve-sd/blob/main/LICENSE)

View File

@ -1,4 +1,4 @@
FROM python:3.13-alpine@sha256:fcbcbbecdeae71d3b77445d9144d1914df55110f825ab62b04a66c7c33c09373
FROM python:3.11-alpine@sha256:2a068b9442f61f4480306d44e3b166bfe3343761e9bd57c38f66302ebf28fd9e
LABEL maintainer="Robert Kaussow <mail@thegeeklab.de>"
LABEL org.opencontainers.image.authors="Robert Kaussow <mail@thegeeklab.de>"

24
docker/Dockerfile.arm Normal file
View File

@ -0,0 +1,24 @@
FROM arm32v7/python:3.11-alpine@sha256:ed2285150d39677d00da2a48c357b0e61153f35974819b267765519c70405e35
LABEL maintainer="Robert Kaussow <mail@thegeeklab.de>"
LABEL org.opencontainers.image.authors="Robert Kaussow <mail@thegeeklab.de>"
LABEL org.opencontainers.image.title="prometheus-pve-sd"
LABEL org.opencontainers.image.url="https://github.com/thegeeklab/prometheus-pve-sd/"
LABEL org.opencontainers.image.source="https://github.com/thegeeklab/prometheus-pve-sd/"
LABEL org.opencontainers.image.documentation="https://github.com/thegeeklab/prometheus-pve-sd/"
ENV PY_COLORS=1
ENV TZ=UTC
ADD dist/prometheus_pve_sd-*.whl /
RUN apk --update add --virtual .build-deps build-base libffi-dev openssl-dev && \
pip install --upgrade --no-cache-dir pip && \
pip install --no-cache-dir $(find / -name "prometheus_pve_sd-*.whl") && \
rm -f prometheus_pve_sd-*.whl && \
rm -rf /var/cache/apk/* && \
rm -rf /root/.cache/
USER root
CMD []
ENTRYPOINT ["/usr/local/bin/prometheus-pve-sd"]

24
docker/Dockerfile.arm64 Normal file
View File

@ -0,0 +1,24 @@
FROM arm64v8/python:3.11-alpine@sha256:8b76406c45a3c49a6ce2d190c771aeb3695648bd07d2147e3ae8d00e2516aa2b
LABEL maintainer="Robert Kaussow <mail@thegeeklab.de>"
LABEL org.opencontainers.image.authors="Robert Kaussow <mail@thegeeklab.de>"
LABEL org.opencontainers.image.title="prometheus-pve-sd"
LABEL org.opencontainers.image.url="https://github.com/thegeeklab/prometheus-pve-sd/"
LABEL org.opencontainers.image.source="https://github.com/thegeeklab/prometheus-pve-sd/"
LABEL org.opencontainers.image.documentation="https://github.com/thegeeklab/prometheus-pve-sd/"
ENV PY_COLORS=1
ENV TZ=UTC
ADD dist/prometheus_pve_sd-*.whl /
RUN apk --update add --virtual .build-deps build-base libffi-dev openssl-dev && \
pip install --upgrade --no-cache-dir pip && \
pip install --no-cache-dir $(find / -name "prometheus_pve_sd-*.whl") && \
rm -f prometheus_pve_sd-*.whl && \
rm -rf /var/cache/apk/* && \
rm -rf /root/.cache/
USER root
CMD []
ENTRYPOINT ["/usr/local/bin/prometheus-pve-sd"]

24
docker/manifest-quay.tmpl Normal file
View File

@ -0,0 +1,24 @@
image: quay.io/thegeeklab/prometheus-pve-sd:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}
{{#if build.tags}}
tags:
{{#each build.tags}}
- {{this}}
{{/each}}
{{/if}}
manifests:
- image: quay.io/thegeeklab/prometheus-pve-sd:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}amd64
platform:
architecture: amd64
os: linux
- image: quay.io/thegeeklab/prometheus-pve-sd:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}arm64
platform:
architecture: arm64
os: linux
variant: v8
- image: quay.io/thegeeklab/prometheus-pve-sd:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}arm
platform:
architecture: arm
os: linux
variant: v7

24
docker/manifest.tmpl Normal file
View File

@ -0,0 +1,24 @@
image: thegeeklab/prometheus-pve-sd:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}
{{#if build.tags}}
tags:
{{#each build.tags}}
- {{this}}
{{/each}}
{{/if}}
manifests:
- image: thegeeklab/prometheus-pve-sd:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}amd64
platform:
architecture: amd64
os: linux
- image: thegeeklab/prometheus-pve-sd:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}arm64
platform:
architecture: arm64
os: linux
variant: v8
- image: thegeeklab/prometheus-pve-sd:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}arm
platform:
architecture: arm
os: linux
variant: v7

View File

@ -2,12 +2,13 @@
title: Documentation
---
[![Build Status](https://ci.thegeeklab.de/api/badges/thegeeklab/prometheus-pve-sd/status.svg)](https://ci.thegeeklab.de/repos/thegeeklab/prometheus-pve-sd)
[![Build Status](https://img.shields.io/drone/build/thegeeklab/prometheus-pve-sd?logo=drone&server=https%3A%2F%2Fdrone.thegeeklab.de)](https://drone.thegeeklab.de/thegeeklab/prometheus-pve-sd)
[![Docker Hub](https://img.shields.io/badge/dockerhub-latest-blue.svg?logo=docker&logoColor=white)](https://hub.docker.com/r/thegeeklab/prometheus-pve-sd)
[![Quay.io](https://img.shields.io/badge/quay-latest-blue.svg?logo=docker&logoColor=white)](https://quay.io/repository/thegeeklab/prometheus-pve-sd)
[![Python Version](https://img.shields.io/pypi/pyversions/prometheus-pve-sd.svg)](https://pypi.org/project/prometheus-pve-sd/)
[![PyPI Status](https://img.shields.io/pypi/status/prometheus-pve-sd.svg)](https://pypi.org/project/prometheus-pve-sd/)
[![PyPI Release](https://img.shields.io/pypi/v/prometheus-pve-sd.svg)](https://pypi.org/project/prometheus-pve-sd/)
[![Codecov](https://img.shields.io/codecov/c/github/thegeeklab/prometheus-pve-sd)](https://codecov.io/gh/thegeeklab/prometheus-pve-sd)
[![GitHub contributors](https://img.shields.io/github/contributors/thegeeklab/prometheus-pve-sd)](https://github.com/thegeeklab/prometheus-pve-sd/graphs/contributors)
[![Source: GitHub](https://img.shields.io/badge/source-github-blue.svg?logo=github&logoColor=white)](https://github.com/thegeeklab/prometheus-pve-sd)
[![License: MIT](https://img.shields.io/github/license/thegeeklab/prometheus-pve-sd)](https://github.com/thegeeklab/prometheus-pve-sd/blob/main/LICENSE)

View File

@ -35,30 +35,18 @@ include_vmid: []
exclude_tags: []
include_tags: []
# Set either password or token_name and token_value
pve:
server:
user:
password:
token_name:
token_value:
auth_timeout: 5
verify_ssl: true
# Example with password
# Example
# pve:
# server: proxmox.example.com
# user: root
# password: secure
# auth_timeout: 5
# verify_ssl: true
# Example with API token
# pve:
# server: proxmox.example.com
# user: root
# token_name: pve_sd
# token_value: 01234567-89ab-cdef-0123-456789abcdef
# auth_timeout: 5
# verify_ssl: true
```

View File

@ -36,8 +36,6 @@ PROMETHEUS_PVE_SD_INCLUDE_TAGS=
PROMETHEUS_PVE_SD_PVE_SERVER=
PROMETHEUS_PVE_SD_PVE_USER=
PROMETHEUS_PVE_SD_PVE_PASSWORD=
PROMETHEUS_PVE_SD_PVE_TOKEN_NAME=
PROMETHEUS_PVE_SD_PVE_TOKEN_VALUE=
PROMETHEUS_PVE_SD_PVE_AUTH_TIMEOUT=5
PROMETHEUS_PVE_SD_PVE_VERIFY_SSL=true
```

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 40 KiB

1447
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -37,9 +37,11 @@ class PrometheusSD:
self.discovery = Discovery()
except APIError as e:
if not self.config.config["service"]:
self.log.sysexit_with_message(f"Proxmoxer API error: {str(e).strip()}")
self.log.sysexit_with_message(
"Proxmoxer API error: {0}".format(str(e).strip())
)
self.logger.error(f"Proxmoxer API error: {str(e).strip()}")
self.logger.error("Proxmoxer API error: {0}".format(str(e).strip()))
sleep(60)
continue
else:
@ -59,7 +61,7 @@ class PrometheusSD:
"--config",
dest="config_file",
action="store",
help="location of configuration file",
help="location of configuration file"
)
parser.add_argument(
"-o", "--output", dest="output_file", action="store", help="output file"
@ -73,14 +75,14 @@ class PrometheusSD:
dest="loop_delay",
action="store",
type=int,
help="delay between discovery runs",
help="delay between discovery runs"
)
parser.add_argument(
"--no-service",
dest="service",
action="store_false",
default=None,
help="run discovery only once",
help="run discovery only once"
)
parser.add_argument(
"-f",
@ -88,7 +90,7 @@ class PrometheusSD:
dest="logging.format",
metavar="LOG_FORMAT",
action="store",
help="used log format",
help="used log format"
)
parser.add_argument(
"-v", dest="logging.level", action="append_const", const=-1, help="increase log level"
@ -96,7 +98,9 @@ class PrometheusSD:
parser.add_argument(
"-q", dest="logging.level", action="append_const", const=1, help="decrease log level"
)
parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
parser.add_argument(
"--version", action="version", version="%(prog)s {}".format(__version__)
)
return parser.parse_args().__dict__
@ -111,25 +115,16 @@ class PrometheusSD:
config.config["logging"]["level"], config.config["logging"]["format"]
)
except ValueError as e:
self.log.sysexit_with_message(f"Can not set log level.\n{e!s}")
self.log.sysexit_with_message("Can not set log level.\n{}".format(str(e)))
required = [
("pve.server", config.config["pve"]["server"]),
required = [("pve.server", config.config["pve"]["server"]),
("pve.user", config.config["pve"]["user"]),
]
("pve.password", config.config["pve"]["password"])]
for name, value in required:
if not value:
self.log.sysexit_with_message(f"Option '{name}' is required but not set")
self.log.sysexit_with_message("Option '{}' is required but not set".format(name))
if not config.config["pve"]["password"] and not (
config.config["pve"]["token_name"] and config.config["pve"]["token_value"]
):
self.log.sysexit_with_message(
"Either 'pve.password' or 'pve.token_name' and 'pve.token_value' "
"are required but not set"
)
self.logger.info(f"Using config file {config.config_file}")
self.logger.info("Using config file {}".format(config.config_file))
return config
@ -145,16 +140,16 @@ class PrometheusSD:
)
start_http_server(
self.config.config["metrics"]["port"],
addr=self.config.config["metrics"]["address"],
addr=self.config.config["metrics"]["address"]
)
while True:
try:
inventory = self.discovery.propagate()
except APIError as e:
self.logger.error(f"Proxmoxer API error: {str(e).strip()}")
self.logger.error("Proxmoxer API error: {0}".format(str(e).strip()))
except Exception as e: # noqa
self.logger.error(f"Unknown error: {str(e).strip()}")
self.logger.error("Unknown error: {0}".format(str(e).strip()))
else:
self._write(inventory)
@ -174,13 +169,14 @@ class PrometheusSD:
output.append(host.to_sd_json())
# Write to tmp file and move after write
with tempfile.NamedTemporaryFile(mode="w", prefix="prometheus-pve-sd", delete=False) as tf:
temp_file = tempfile.NamedTemporaryFile(mode="w", prefix="prometheus-pve-sd", delete=False)
with temp_file as tf:
json.dump(output, tf, indent=4)
shutil.move(tf.name, self.config.config["output_file"])
shutil.move(temp_file.name, self.config.config["output_file"])
chmod(self.config.config["output_file"], int(self.config.config["output_file_mode"], 8))
def _terminate(self, signal, frame): # noqa
def _terminate(self, signal, frame):
self.log.sysexit_with_message("Terminating", code=0)

View File

@ -1,5 +1,4 @@
"""Proxmox Client."""
import requests
from prometheus_client import Counter
@ -11,7 +10,6 @@ from prometheuspvesd.utils import to_bool
try:
from proxmoxer import ProxmoxAPI
HAS_PROXMOXER = True
except ImportError:
HAS_PROXMOXER = False
@ -46,28 +44,16 @@ class ProxmoxClient:
self.config.config["pve"]["server"], self.config.config["pve"]["user"]
)
)
if self.config.config["pve"]["token_name"]:
self.logger.debug("Using token login")
return ProxmoxAPI(
self.config.config["pve"]["server"],
user=self.config.config["pve"]["user"],
token_name=self.config.config["pve"]["token_name"],
token_value=self.config.config["pve"]["token_value"],
verify_ssl=to_bool(self.config.config["pve"]["verify_ssl"]),
timeout=self.config.config["pve"]["auth_timeout"],
)
return ProxmoxAPI(
self.config.config["pve"]["server"],
user=self.config.config["pve"]["user"],
password=self.config.config["pve"]["password"],
verify_ssl=to_bool(self.config.config["pve"]["verify_ssl"]),
timeout=self.config.config["pve"]["auth_timeout"],
timeout=self.config.config["pve"]["auth_timeout"]
)
except requests.RequestException as e:
PVE_REQUEST_COUNT_ERROR_TOTAL.inc()
raise APIError(str(e)) from e
raise APIError(str(e))
def _do_request(self, *args):
PVE_REQUEST_COUNT_TOTAL.inc()
@ -76,30 +62,29 @@ class ProxmoxClient:
return self.client.get(*("nodes", *args))
except requests.RequestException as e:
PVE_REQUEST_COUNT_ERROR_TOTAL.inc()
raise APIError(str(e)) from e
raise APIError(str(e))
def get_nodes(self):
self.logger.debug("fetching all nodes")
return self._do_request()
def get_all_vms(self, pve_node):
self.logger.debug(f"fetching all vms on node {pve_node}")
self.logger.debug("fetching all vms on node {}".format(pve_node))
return self._do_request(pve_node, "qemu")
def get_all_containers(self, pve_node):
self.logger.debug(f"fetching all containers on node {pve_node}")
self.logger.debug("fetching all containers on node {}".format(pve_node))
return self._do_request(pve_node, "lxc")
def get_instance_config(self, pve_node, pve_type, vmid):
self.logger.debug(f"fetching instance config for {vmid} on {pve_node}")
self.logger.debug("fetching instance config for {} on {}".format(vmid, pve_node))
return self._do_request(pve_node, pve_type, vmid, "config")
def get_agent_info(self, pve_node, pve_type, vmid):
self.logger.debug(f"fetching agent info for {vmid} on {pve_node}")
self.logger.debug("fetching agent info for {} on {}".format(vmid, pve_node))
return self._do_request(pve_node, pve_type, vmid, "agent", "info")["result"]
def get_network_interfaces(self, pve_node, vmid):
self.logger.debug(f"fetching network interfaces for {vmid} on {pve_node}")
return self._do_request(pve_node, "qemu", vmid, "agent", "network-get-interfaces")[
"result"
]
self.logger.debug("fetching network interfaces for {} on {}".format(vmid, pve_node))
return self._do_request(pve_node, "qemu", vmid, "agent",
"network-get-interfaces")["result"]

View File

@ -2,7 +2,8 @@
"""Global settings definition."""
import os
from pathlib import Path, PurePath
from pathlib import Path
from pathlib import PurePath
import anyconfig
import environs
@ -20,7 +21,7 @@ cache_dir = AppDirs("prometheus-pve-sd").user_cache_dir
default_output_file = os.path.join(cache_dir, "pve.json")
class Config:
class Config():
"""
Create an object with all necessary settings.
@ -34,134 +35,122 @@ class Config:
"metrics.enabled": {
"default": True,
"env": "METRICS_ENABLED",
"type": environs.Env().bool,
"type": environs.Env().bool
},
"metrics.address": {
"default": "127.0.0.1",
"env": "METRICS_ADDRESS",
"type": environs.Env().str,
"type": environs.Env().str
},
"metrics.port": {
"default": 8000,
"env": "METRICS_PORT",
"type": environs.Env().int,
"type": environs.Env().int
},
"config_file": {
"default": "",
"env": "CONFIG_FILE",
"type": environs.Env().str,
"type": environs.Env().str
},
"logging.level": {
"default": "WARNING",
"env": "LOG_LEVEL",
"file": True,
"type": environs.Env().str,
"type": environs.Env().str
},
"logging.format": {
"default": "console",
"env": "LOG_FORMAT",
"file": True,
"type": environs.Env().str,
"type": environs.Env().str
},
"output_file": {
"default": default_output_file,
"env": "OUTPUT_FILE",
"file": True,
"type": environs.Env().str,
"type": environs.Env().str
},
"output_file_mode": {
"default": "0640",
"env": "OUTPUT_FILE_MODE",
"file": True,
"type": environs.Env().str,
"type": environs.Env().str
},
"loop_delay": {
"default": 300,
"env": "LOOP_DELAY",
"file": True,
"type": environs.Env().int,
"type": environs.Env().int
},
"service": {
"default": True,
"env": "SERVICE",
"file": True,
"type": environs.Env().bool,
"type": environs.Env().bool
},
"exclude_state": {
"default": [],
"env": "EXCLUDE_STATE",
"file": True,
"type": environs.Env().list,
"type": environs.Env().list
},
"exclude_vmid": {
"default": [],
"env": "EXCLUDE_VMID",
"file": True,
"type": environs.Env().list,
"type": environs.Env().list
},
"include_vmid": {
"default": [],
"env": "INCLUDE_VMID",
"file": True,
"type": environs.Env().list,
"type": environs.Env().list
},
"exclude_tags": {
"default": [],
"env": "EXCLUDE_TAGS",
"file": True,
"type": environs.Env().list,
"type": environs.Env().list
},
"include_tags": {
"default": [],
"env": "INCLUDE_TAGS",
"file": True,
"type": environs.Env().list,
"type": environs.Env().list
},
"pve.server": {
"default": "",
"env": "PVE_SERVER",
"file": True,
"type": environs.Env().str,
"type": environs.Env().str
},
"pve.user": {
"default": "",
"env": "PVE_USER",
"file": True,
"type": environs.Env().str,
"type": environs.Env().str
},
"pve.password": {
"default": "",
"env": "PVE_PASSWORD",
"file": True,
"type": environs.Env().str,
},
"pve.token_name": {
"default": "",
"env": "PVE_TOKEN_NAME",
"file": True,
"type": environs.Env().str,
},
"pve.token_value": {
"default": "",
"env": "PVE_TOKEN_VALUE",
"file": True,
"type": environs.Env().str,
"type": environs.Env().str
},
"pve.auth_timeout": {
"default": 5,
"env": "PVE_AUTH_TIMEOUT",
"file": True,
"type": environs.Env().int,
"type": environs.Env().int
},
"pve.verify_ssl": {
"default": True,
"env": "PVE_VERIFY_SSL",
"file": True,
"type": environs.Env().bool,
"type": environs.Env().bool
},
}
def __init__(self, args=None):
def __init__(self, args={}):
"""
Initialize a new settings class.
@ -170,9 +159,6 @@ class Config:
:returns: None
"""
if args is None:
self._args = {}
else:
self._args = args
self._schema = None
self.config_file = default_config_file
@ -214,12 +200,12 @@ class Config:
value = item["type"](envname)
normalized = self._add_dict_branch(normalized, key.split("."), value)
except environs.EnvError as e:
if f'"{envname}" not set' in str(e):
if '"{}" not set'.format(envname) in str(e):
pass
else:
raise prometheuspvesd.exception.ConfigError(
"Unable to read environment variable", str(e)
) from e
)
return normalized
@ -240,18 +226,17 @@ class Config:
for config in source_files:
if config and os.path.exists(config):
with open(config, encoding="utf8") as stream:
with open(config, "r", encoding="utf8") as stream:
s = stream.read()
try:
file_dict = ruamel.yaml.YAML(typ="safe", pure=True).load(s)
except (
ruamel.yaml.composer.ComposerError,
ruamel.yaml.scanner.ScannerError,
ruamel.yaml.composer.ComposerError, ruamel.yaml.scanner.ScannerError
) as e:
message = f"{e.context} {e.problem}"
message = "{} {}".format(e.context, e.problem)
raise prometheuspvesd.exception.ConfigError(
f"Unable to read config file {config}", message
) from e
"Unable to read config file {}".format(config), message
)
if self._validate(file_dict):
anyconfig.merge(defaults, file_dict, ac_merge=anyconfig.MS_DICTS)
@ -278,26 +263,27 @@ class Config:
if not os.path.isabs(path):
base = os.path.join(os.getcwd(), path)
return os.path.abspath(os.path.expanduser(os.path.expandvars(base)))
else:
return path
def _validate(self, config):
try:
anyconfig.validate(config, self.schema, ac_schema_safe=False)
except jsonschema.exceptions.ValidationError as e:
schema = format_as_index(list(e.relative_schema_path)[:-1], 0)
schema_error = f"Failed validating '{e.validator}' in schema {schema}\n{e.message}"
raise prometheuspvesd.exception.ConfigError("Configuration error", schema_error) from e
schema_error = "Failed validating '{validator}' in schema{schema}\n{message}".format(
validator=e.validator,
schema=format_as_index(list(e.relative_schema_path)[:-1], 0),
message=e.message
)
raise prometheuspvesd.exception.ConfigError("Configuration error", schema_error)
return True
def _add_dict_branch(self, tree, vector, value):
key = vector[0]
tree[key] = (
value
if len(vector) == 1
else self._add_dict_branch(tree.get(key, {}), vector[1:], value)
)
tree[key] = value \
if len(vector) == 1 \
else self._add_dict_branch(tree[key] if key in tree else {}, vector[1:], value)
return tree

View File

@ -6,13 +6,15 @@ import json
import re
from collections import defaultdict
from prometheus_client import Gauge, Summary
from prometheus_client import Gauge
from prometheus_client import Summary
from prometheuspvesd.client import ProxmoxClient
from prometheuspvesd.config import SingleConfig
from prometheuspvesd.exception import APIError
from prometheuspvesd.logger import SingleLog
from prometheuspvesd.model import Host, HostList
from prometheuspvesd.model import Host
from prometheuspvesd.model import HostList
PROPAGATION_TIME = Summary(
"pve_sd_propagate_seconds", "Time spent propagating the inventory from PVE"
@ -20,7 +22,7 @@ PROPAGATION_TIME = Summary(
HOST_GAUGE = Gauge("pve_sd_hosts", "Number of hosts discovered by PVE SD")
class Discovery:
class Discovery():
"""Prometheus PVE Service Discovery."""
def __init__(self):
@ -61,10 +63,10 @@ class Discovery:
try:
if self.client.get_agent_info(pve_node, pve_type, vmid) is not None:
networks = self.client.get_network_interfaces(pve_node, vmid)
except Exception: # noqa
except Exception: # noqa # nosec
pass
if isinstance(networks, list):
if type(networks) is list:
for network in networks:
for ip_address in network.get("ip-addresses", []):
if ip_address["ip-address-type"] == "ipv4" and not ipv4_address:
@ -75,7 +77,7 @@ class Discovery:
config = self.client.get_instance_config(pve_node, pve_type, vmid)
if config and not ipv4_address:
try:
if "ipconfig0" in config:
if "ipconfig0" in config.keys():
sources = [config["net0"], config["ipconfig0"]]
else:
sources = [config["net0"]]
@ -85,12 +87,12 @@ class Discovery:
if find and find.group(1):
ipv4_address = find.group(1)
break
except Exception: # noqa
except Exception: # noqa # nosec
pass
if config and not ipv6_address:
try:
if "ipconfig0" in config:
if "ipconfig0" in config.keys():
sources = [config["net0"], config["ipconfig0"]]
else:
sources = [config["net0"]]
@ -102,7 +104,7 @@ class Discovery:
if find and find.group(1):
ipv6_address = find.group(1)
break
except Exception: # noqa
except Exception: # noqa # nosec
pass
return ipv4_address, ipv6_address
@ -111,22 +113,17 @@ class Discovery:
filtered = []
for item in pve_list:
obj = defaultdict(dict, item)
tags = []
tags_excl = self.config.config["exclude_tags"]
if isinstance(obj["tags"], str):
tags = obj["tags"].split(";")
self.logger.debug(f"vmid {obj['vmid']}: discovered tags: {tags}")
if (
len(self.config.config["include_vmid"]) > 0
and str(obj["vmid"]) not in self.config.config["include_vmid"]
):
continue
if len(self.config.config["include_tags"]) > 0 and (
if (
len(self.config.config["include_tags"]) > 0 and (
bool(obj["tags"]) is False # continue if tags is not set
or set(tags).isdisjoint(self.config.config["include_tags"])
or set(obj["tags"].split(",")).isdisjoint(self.config.config["include_tags"])
)
):
continue
@ -139,11 +136,10 @@ class Discovery:
if str(obj["vmid"]) in self.config.config["exclude_vmid"]:
continue
if isinstance(obj["tags"], str) and not set(tags).isdisjoint(tags_excl):
self.logger.debug(
f"vmid {obj['vmid']}: "
f"excluded by tags: {list(set(tags).intersection(tags_excl))}"
)
if (
isinstance(obj["tags"], str)
and not set(obj["tags"].split(",")).isdisjoint(self.config.config["exclude_tags"])
):
continue
filtered.append(item.copy())
@ -162,21 +158,20 @@ class Discovery:
@PROPAGATION_TIME.time()
def propagate(self):
self.host_list.clear()
nodelist = self._get_names(self.client.get_nodes(), "node")
self.logger.info(f"Discovered nodes: {','.join(nodelist)}")
for node in nodelist:
for node in self._get_names(self.client.get_nodes(), "node"):
try:
qemu_list = self._filter(self.client.get_all_vms(node))
container_list = self._filter(self.client.get_all_containers(node))
except Exception as e:
raise APIError(str(e)) from e
except Exception as e: # noqa
raise APIError(str(e))
# Merge QEMU and Containers lists from this node
instances = self._get_variables(qemu_list, "qemu").copy()
instances.update(self._get_variables(container_list, "container"))
HOST_GAUGE.set(len(instances))
self.logger.info(f"{node}: Found {len(instances)} targets")
self.logger.info("Found {} targets".format(len(instances)))
for host in instances:
host_meta = instances[host]
vmid = host_meta["proxmox_vmid"]
@ -189,11 +184,11 @@ class Discovery:
config = self.client.get_instance_config(node, pve_type, vmid)
try:
description = config["description"]
description = (config["description"])
except KeyError:
description = None
except Exception as e:
raise APIError(str(e)) from e
except Exception as e: # noqa
raise APIError(str(e))
try:
metadata = json.loads(description)
@ -221,6 +216,6 @@ class Discovery:
prom_host.add_label("groups", ",".join(metadata["groups"]))
self.host_list.add_host(prom_host)
self.logger.debug(f"Discovered {prom_host}")
self.logger.debug("Discovered {}".format(prom_host))
return self.host_list

View File

@ -6,7 +6,8 @@ class PrometheusSDError(Exception):
"""Generic exception class for Prometheus-pve-sd."""
def __init__(self, msg, original_exception=""):
super().__init__(f"{msg}\n{original_exception}")
super(PrometheusSDError,
self).__init__("{msg}\n{org}".format(msg=msg, org=original_exception))
self.original_exception = original_exception

View File

@ -8,7 +8,8 @@ import sys
import colorama
from pythonjsonlogger import jsonlogger
from prometheuspvesd.utils import Singleton, to_bool
from prometheuspvesd.utils import Singleton
from prometheuspvesd.utils import to_bool
CONSOLE_FORMAT = "{}{}[%(levelname)s]{} %(message)s"
JSON_FORMAT = "%(asctime)s %(levelname)s %(message)s"
@ -25,7 +26,7 @@ def _should_do_markup():
colorama.init(autoreset=True, strip=not _should_do_markup())
class LogFilter:
class LogFilter(object):
"""A custom log filter which excludes log messages above the logged level."""
def __init__(self, level):
@ -46,22 +47,22 @@ class LogFilter:
class SimpleFormatter(logging.Formatter):
"""Logging Formatter for simple logs."""
def format(self, record):
def format(self, record): # noqa
return logging.Formatter.format(self, record)
class MultilineFormatter(logging.Formatter):
"""Logging Formatter to reset color after newline characters."""
def format(self, record):
record.msg = record.msg.replace("\n", f"\n{colorama.Style.RESET_ALL}... ")
def format(self, record): # noqa
record.msg = record.msg.replace("\n", "\n{}... ".format(colorama.Style.RESET_ALL))
return logging.Formatter.format(self, record)
class MultilineJsonFormatter(jsonlogger.JsonFormatter):
"""Logging Formatter to remove newline characters."""
def format(self, record):
def format(self, record): # noqa
record.msg = record.msg.replace("\n", " ")
return jsonlogger.JsonFormatter.format(self, record)
@ -229,7 +230,7 @@ class Log:
:returns: string
"""
return f"{color}{msg}{colorama.Style.RESET_ALL}"
return "{}{}{}".format(color, msg, colorama.Style.RESET_ALL)
def sysexit(self, code=1):
sys.exit(code)

View File

@ -19,10 +19,8 @@ class Host:
self.add_label("vmid", vmid)
def __str__(self):
return (
f"{self.hostname}({self.vmid}): "
f"{self.pve_type} {self.ipv4_address} {self.ipv6_address}"
)
return f"{self.hostname}({self.vmid}): {self.pve_type} \
{self.ipv4_address} {self.ipv6_address}"
def add_label(self, key, value):
key = key.replace("-", "_").replace(" ", "_")

View File

@ -24,7 +24,5 @@ pve:
server: proxmox.example.com
user: root
password: secure
token_name: pve_sd
token_value: 01234567-89ab-cdef-0123-456789abcdef
auth_timeout: 5
verify_ssl: true

View File

@ -3,7 +3,8 @@
import environs
import pytest
from prometheuspvesd.model import Host, HostList
from prometheuspvesd.model import Host
from prometheuspvesd.model import HostList
@pytest.fixture
@ -12,118 +13,119 @@ def builtins():
"metrics.enabled": {
"default": True,
"env": "METRICS_ENABLED",
"type": environs.Env().bool,
"type": environs.Env().bool
},
"metrics.address": {
"default": "127.0.0.1",
"env": "METRICS_ADDRESS",
"type": environs.Env().str,
"type": environs.Env().str
},
"metrics.port": {
"default": 8000,
"env": "METRICS_PORT",
"type": environs.Env().int
},
"config_file": {
"default": "",
"env": "CONFIG_FILE",
"type": environs.Env().str
},
"metrics.port": {"default": 8000, "env": "METRICS_PORT", "type": environs.Env().int},
"config_file": {"default": "", "env": "CONFIG_FILE", "type": environs.Env().str},
"logging.level": {
"default": "WARNING",
"env": "LOG_LEVEL",
"file": True,
"type": environs.Env().str,
"type": environs.Env().str
},
"logging.format": {
"default": "console",
"env": "LOG_FORMAT",
"file": True,
"type": environs.Env().str,
"type": environs.Env().str
},
"output_file": {
"default": "dummy",
"env": "OUTPUT_FILE",
"file": True,
"type": environs.Env().str,
"type": environs.Env().str
},
"output_file_mode": {
"default": "0640",
"env": "OUTPUT_FILE_MODE",
"file": True,
"type": environs.Env().str,
"type": environs.Env().str
},
"loop_delay": {
"default": 300,
"env": "LOOP_DELAY",
"file": True,
"type": environs.Env().int,
"type": environs.Env().int
},
"service": {
"default": False,
"env": "SERVICE",
"file": True,
"type": environs.Env().bool
},
"service": {"default": False, "env": "SERVICE", "file": True, "type": environs.Env().bool},
"exclude_state": {
"default": [],
"env": "EXCLUDE_STATE",
"file": True,
"type": environs.Env().list,
"type": environs.Env().list
},
"exclude_vmid": {
"default": [],
"env": "EXCLUDE_VMID",
"file": True,
"type": environs.Env().list,
"type": environs.Env().list
},
"exclude_tags": {
"default": [],
"env": "EXCLUDE_TAGS",
"file": True,
"type": environs.Env().list,
"type": environs.Env().list
},
"include_vmid": {
"default": [],
"env": "INCLUDE_VMID",
"file": True,
"type": environs.Env().list,
"type": environs.Env().list
},
"include_tags": {
"default": [],
"env": "INCLUDE_TAGS",
"file": True,
"type": environs.Env().list,
"type": environs.Env().list
},
"pve.server": {
"default": "dummyserver",
"env": "PVE_SERVER",
"file": True,
"type": environs.Env().str,
"type": environs.Env().str
},
"pve.user": {
"default": "dummyuser",
"env": "PVE_USER",
"file": True,
"type": environs.Env().str,
"type": environs.Env().str
},
"pve.password": {
"default": "dummypass",
"env": "PVE_PASSWORD",
"file": True,
"type": environs.Env().str,
},
"pve.token_name": {
"default": "dummyname",
"env": "PVE_TOKEN_NAME",
"file": True,
"type": environs.Env().str,
},
"pve.token_value": {
"default": "dummyvalue",
"env": "PVE_TOKEN_VALUE",
"file": True,
"type": environs.Env().str,
"type": environs.Env().str
},
"pve.auth_timeout": {
"default": 5,
"env": "PVE_AUTH_TIMEOUT",
"file": True,
"type": environs.Env().int,
"type": environs.Env().int
},
"pve.verify_ssl": {
"default": True,
"env": "PVE_VERIFY_SSL",
"file": True,
"type": environs.Env().bool,
},
"type": environs.Env().bool
}
}
@ -135,9 +137,16 @@ def defaults():
"exclude_vmid": [],
"include_tags": [],
"include_vmid": [],
"logging": {"format": "console", "level": "WARNING"},
"logging": {
"format": "console",
"level": "WARNING"
},
"loop_delay": 300,
"metrics": {"address": "127.0.0.1", "enabled": True, "port": 8000},
"metrics": {
"address": "127.0.0.1",
"enabled": True,
"port": 8000
},
"output_file": "dummy",
"output_file_mode": "0640",
"pve": {
@ -145,9 +154,7 @@ def defaults():
"password": "",
"server": "",
"user": "",
"token_name": "",
"token_value": "",
"verify_ssl": True,
"verify_ssl": True
},
"service": True,
}
@ -155,8 +162,7 @@ def defaults():
@pytest.fixture
def nodes():
return [
{
return [{
"level": "",
"id": "node/example-node",
"disk": 4783488,
@ -168,9 +174,8 @@ def nodes():
"type": "node",
"status": "online",
"maxdisk": 504209920,
"uptime": 200,
}
]
"uptime": 200
}]
@pytest.fixture
@ -193,7 +198,7 @@ def qemus():
"status": "running",
"netout": 12159205236,
"mem": 496179157,
"tags": "unmonitored;excluded;postgres",
"tags": "unmonitored,excluded,postgres"
},
{
"diskwrite": 0,
@ -211,7 +216,7 @@ def qemus():
"disk": 0,
"status": "running",
"netout": 12159205236,
"mem": 496179157,
"mem": 496179157
},
{
"diskwrite": 0,
@ -230,7 +235,7 @@ def qemus():
"status": "prelaunch",
"netout": 12159205236,
"mem": 496179157,
"tags": "monitored",
"tags": "monitored"
},
]
@ -242,17 +247,19 @@ def instance_config():
"description": '{"groups": "test-group"}',
"net0": "virtio=D8-85-75-47-2E-8D,bridge=vmbr122,ip=192.0.2.25,ip=2001:db8::666:77:8888",
"cpu": 2,
"cores": 2,
"cores": 2
}
@pytest.fixture
def agent_info():
return {
"supported_commands": [
{"name": "guest-network-get-interfaces", "enabled": True, "success-response": True}
],
"version": "5.2.0",
"supported_commands": [{
"name": "guest-network-get-interfaces",
"enabled": True,
"success-response": True
}],
"version": "5.2.0"
}
@ -289,8 +296,16 @@ def networks():
{
"hardware-address": "00:00:00:00:00:00",
"ip-addresses": [
{"ip-address": "127.0.0.1", "ip-address-type": "ipv4", "prefix": 8},
{"ip-address": "::1", "ip-address-type": "ipv6", "prefix": 128},
{
"ip-address": "127.0.0.1",
"ip-address-type": "ipv4",
"prefix": 8
},
{
"ip-address": "::1",
"ip-address-type": "ipv6",
"prefix": 128
},
],
"name": "lo",
"statistics": {
@ -301,18 +316,26 @@ def networks():
"tx-bytes": 9280,
"tx-dropped": 0,
"tx-errs": 0,
"tx-packets": 92,
},
"tx-packets": 92
}
},
{
"hardware-address": "92:0b:bd:c1:f8:39",
"ip-addresses": [
{"ip-address": "192.0.2.1", "ip-address-type": "ipv4", "prefix": 32},
{"ip-address": "192.0.2.4", "ip-address-type": "ipv4", "prefix": 32},
{
"ip-address": "192.0.2.1",
"ip-address-type": "ipv4",
"prefix": 32
},
{
"ip-address": "192.0.2.4",
"ip-address-type": "ipv4",
"prefix": 32
},
{
"ip-address": "2001:db8:3333:4444:5555:6666:7777:8888",
"ip-address-type": "ipv6",
"prefix": 64,
"prefix": 64
},
],
"name": "eth0",
@ -324,10 +347,13 @@ def networks():
"tx-bytes": 12185866619,
"tx-dropped": 0,
"tx-errs": 0,
"tx-packets": 14423878,
"tx-packets": 14423878
}
},
{
"hardware-address": "ba:97:85:bd:9a:a5",
"name": "eth1"
},
{"hardware-address": "ba:97:85:bd:9a:a5", "name": "eth1"},
]
@ -343,35 +369,31 @@ def inventory():
@pytest.fixture
def labels():
return [
{
return [{
"targets": ["100.example.com"],
"labels": {
"__meta_pve_ipv4": "192.0.2.1",
"__meta_pve_ipv6": "False",
"__meta_pve_name": "100.example.com",
"__meta_pve_type": "qemu",
"__meta_pve_vmid": "100",
},
},
{
"__meta_pve_vmid": "100"
}
}, {
"targets": ["101.example.com"],
"labels": {
"__meta_pve_ipv4": "192.0.2.2",
"__meta_pve_ipv6": "False",
"__meta_pve_name": "101.example.com",
"__meta_pve_type": "qemu",
"__meta_pve_vmid": "101",
},
},
{
"__meta_pve_vmid": "101"
}
}, {
"targets": ["102.example.com"],
"labels": {
"__meta_pve_ipv4": "192.0.2.3",
"__meta_pve_ipv6": "False",
"__meta_pve_name": "102.example.com",
"__meta_pve_type": "qemu",
"__meta_pve_vmid": "102",
},
},
]
"__meta_pve_vmid": "102"
}
}]

View File

@ -1,12 +1,8 @@
"""Pytest conftest fixtures."""
import logging
import os
import sys
from contextlib import contextmanager
import pytest
from _pytest.logging import LogCaptureHandler
from prometheuspvesd.utils import Singleton
@ -18,43 +14,9 @@ def reset_singletons():
@pytest.fixture(autouse=True)
def reset_os_environment():
os.environ.clear()
os.environ = {}
@pytest.fixture(autouse=True)
def reset_sys_argv():
sys.argv = ["prometheus-pve-sd"]
@contextmanager
def local_caplog_fn(level=logging.INFO, name="prometheuspvesd"):
"""
Context manager that captures records from non-propagating loggers.
After the end of the 'with' statement, the log level is restored to its original
value. Code adapted from https://github.com/pytest-dev/pytest/issues/3697#issuecomment-790925527.
:param int level: The level.
:param logging.Logger logger: The logger to update.
"""
logger = logging.getLogger(name)
old_level = logger.level
logger.setLevel(level)
handler = LogCaptureHandler()
logger.addHandler(handler)
try:
yield handler
finally:
logger.setLevel(old_level)
logger.removeHandler(handler)
@pytest.fixture
def local_caplog():
"""Fixture that yields a context manager for capturing records from non-propagating loggers."""
yield local_caplog_fn

View File

@ -1,5 +1,4 @@
"""Test CLI class."""
import json
import pytest
@ -30,68 +29,10 @@ def test_cli_required_error(mocker, capsys):
assert e.value.code == 1
@pytest.mark.parametrize(
"testinput",
[
{"pve.user": "dummy", "pve.password": "", "pve.token_name": "", "pve.token_value": ""},
{
"pve.user": "dummy",
"pve.password": "",
"pve.token_name": "dummy",
"pve.token_value": "",
},
{
"pve.user": "dummy",
"pve.password": "",
"pve.token_name": "",
"pve.token_value": "dummy",
},
],
)
def test_cli_auth_required_error(mocker, capsys, builtins, testinput):
for key, value in testinput.items():
builtins[key]["default"] = value
mocker.patch.dict(Config.SETTINGS, builtins)
mocker.patch.object(ProxmoxClient, "_auth", return_value=mocker.create_autospec(ProxmoxAPI))
mocker.patch.object(PrometheusSD, "_fetch", return_value=True)
with pytest.raises(SystemExit) as e:
PrometheusSD()
stdout, stderr = capsys.readouterr()
assert (
"Either 'pve.password' or 'pve.token_name' and 'pve.token_value' are required but not set"
in stderr
)
assert e.value.code == 1
@pytest.mark.parametrize(
"testinput",
[
{"pve.password": "dummy", "pve.token_name": "", "pve.token_value": ""},
{"pve.password": "", "pve.token_name": "dummy", "pve.token_value": "dummy"},
],
)
def test_cli_auth_no_error(mocker, builtins, testinput):
for key, value in testinput.items():
builtins[key]["default"] = value
mocker.patch.dict(Config.SETTINGS, builtins)
mocker.patch.object(ProxmoxClient, "_auth", return_value=mocker.create_autospec(ProxmoxAPI))
mocker.patch.object(PrometheusSD, "_fetch", return_value=True)
psd = PrometheusSD()
for key, value in testinput.items():
assert psd.config.config["pve"][key.split(".")[1]] == value
def test_cli_config_error(mocker, capsys):
mocker.patch(
"prometheuspvesd.config.SingleConfig.__init__",
side_effect=prometheuspvesd.exception.ConfigError("Dummy Config Exception"),
side_effect=prometheuspvesd.exception.ConfigError("Dummy Config Exception")
)
mocker.patch.object(ProxmoxClient, "_auth", return_value=mocker.create_autospec(ProxmoxAPI))
mocker.patch.object(PrometheusSD, "_fetch", return_value=True)

View File

@ -1,5 +1,4 @@
"""Test Config class."""
import pytest
import ruamel.yaml
@ -20,13 +19,11 @@ def test_yaml_config(mocker, defaults):
defaults["pve"]["user"] = "root"
defaults["pve"]["password"] = "secure"
defaults["pve"]["server"] = "proxmox.example.com"
defaults["pve"]["token_name"] = "pve_sd"
defaults["pve"]["token_value"] = "01234567-89ab-cdef-0123-456789abcdef"
assert config.config == defaults
def test_yaml_config_error(mocker):
def test_yaml_config_error(mocker, capsys):
mocker.patch(
"prometheuspvesd.config.default_config_file", "./prometheuspvesd/test/data/config.yml"
)

View File

@ -1,7 +1,5 @@
"""Test Discovery class."""
import logging
import pytest
from proxmoxer import ProxmoxAPI
@ -13,10 +11,6 @@ pytest_plugins = [
]
def records_to_messages(records):
return [r.getMessage() for r in records]
@pytest.fixture
def discovery(mocker):
mocker.patch.object(ProxmoxClient, "_auth", return_value=mocker.create_autospec(ProxmoxAPI))
@ -38,27 +32,19 @@ def test_exclude_state(discovery, qemus):
assert len(filtered) == 2
def test_exclude_tags(discovery, qemus, local_caplog):
def test_exclude_tags(discovery, qemus):
discovery.config.config["exclude_tags"] = ["unmonitored"]
with local_caplog(level=logging.DEBUG) as caplog:
filtered = discovery._filter(qemus)
assert (
"vmid 100: discovered tags: ['unmonitored', 'excluded', 'postgres']"
in records_to_messages(caplog.records)
)
assert "vmid 100: excluded by tags: ['unmonitored']"
assert len(filtered) == 2
@pytest.mark.parametrize(
"testinput,expected",
[
"testinput,expected", [
(["monitored"], 1),
(["monitored", "postgres"], 2),
([], 3),
],
]
)
def test_include_tags(discovery, qemus, testinput, expected):
discovery.config.config["include_tags"] = testinput
@ -67,13 +53,10 @@ def test_include_tags(discovery, qemus, testinput, expected):
assert len(filtered) == expected
@pytest.mark.parametrize(
"testinput,expected",
[
@pytest.mark.parametrize("testinput,expected", [
(["101", "100"], 2),
([], 3),
],
)
])
def test_include_vmid(discovery, qemus, testinput, expected):
discovery.config.config["include_vmid"] = testinput
filtered = discovery._filter(qemus)

View File

@ -1,5 +1,4 @@
"""Test Host class."""
import pytest
from prometheuspvesd.model import Host
@ -10,41 +9,34 @@ pytest_plugins = [
@pytest.mark.parametrize(
"testinput,expected",
[
(
{
"testinput,expected", [
({
"vmid": 101,
"hostname": "host1",
"ipv4_address": False,
"ipv6_address": False,
"pve_type": "qemu",
},
{
}, {
"__meta_pve_vmid": "101",
"__meta_pve_name": "host1",
"__meta_pve_ipv4": "False",
"__meta_pve_ipv6": "False",
"__meta_pve_type": "qemu",
},
),
(
{
}),
({
"vmid": "202",
"hostname": "host2",
"ipv4_address": "129.168.0.1",
"ipv6_address": "2001:db8:3333:4444:5555:6666:7777:8888",
"pve_type": "qemu",
},
{
}, {
"__meta_pve_vmid": "202",
"__meta_pve_name": "host2",
"__meta_pve_ipv4": "129.168.0.1",
"__meta_pve_ipv6": "2001:db8:3333:4444:5555:6666:7777:8888",
"__meta_pve_type": "qemu",
},
),
],
}),
]
)
def test_host(testinput, expected):
host = Host(

View File

@ -1,29 +1,7 @@
#!/usr/bin/env python3
"""Global utility methods and classes."""
def strtobool(value):
"""Convert a string representation of truth to true or false."""
_map = {
"y": True,
"yes": True,
"t": True,
"true": True,
"on": True,
"1": True,
"n": False,
"no": False,
"f": False,
"false": False,
"off": False,
"0": False,
}
try:
return _map[str(value).lower()]
except KeyError as err:
raise ValueError(f'"{value}" is not a valid bool value') from err
from distutils.util import strtobool
def to_bool(string):
@ -37,5 +15,5 @@ class Singleton(type):
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]

View File

@ -10,10 +10,10 @@ classifiers = [
"Natural Language :: English",
"Operating System :: POSIX",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Utilities",
]
description = "Prometheus Service Discovery for Proxmox VE."
@ -29,108 +29,66 @@ repository = "https://github.com/thegeeklab/prometheus-pve-sd/"
version = "0.0.0"
[tool.poetry.dependencies]
anyconfig = "0.14.0"
anyconfig = "0.13.0"
appdirs = "1.4.4"
colorama = "0.4.6"
environs = "11.2.0"
jsonschema = "4.23.0"
environs = "9.5.0"
jsonschema = "4.17.0"
nested-lookup = "0.2.25"
prometheus-client = "0.21.0"
proxmoxer = "2.1.0"
python = "^3.10.0"
python-json-logger = "2.0.7"
requests = "2.32.3"
"ruamel.yaml" = "0.18.6"
prometheus-client = "0.15.0"
proxmoxer = "1.3.1"
python = "^3.7.0"
python-json-logger = "2.0.4"
requests = "2.28.1"
"ruamel.yaml" = "0.17.21"
[tool.poetry.dev-dependencies]
bandit = "1.7.4"
flake8 = "5.0.4"
flake8-blind-except = "0.2.1"
flake8-builtins = "2.0.0"
flake8-docstrings = "1.6.0"
flake8-eradicate = "1.4.0"
flake8-isort = "5.0.0"
flake8-logging-format = "0.8.1"
flake8-pep3101 = "2.0.0"
flake8-polyfill = "1.0.2"
flake8-quotes = "3.3.1"
pep8-naming = "0.13.2"
pydocstyle = "6.1.1"
pytest = "7.2.0"
pytest-cov = "4.0.0"
pytest-mock = "3.10.0"
yapf = "0.32.0"
toml = "0.10.2"
[tool.poetry.scripts]
prometheus-pve-sd = "prometheuspvesd.cli:main"
[tool.poetry.group.dev.dependencies]
ruff = "0.7.3"
pytest = "8.3.3"
pytest-mock = "3.14.0"
pytest-cov = "6.0.0"
toml = "0.10.2"
[tool.poetry-dynamic-versioning]
enable = true
style = "semver"
vcs = "git"
[tool.isort]
default_section = "THIRDPARTY"
force_single_line = true
line_length = 99
sections = ["FUTURE", "STDLIB", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"]
skip_glob = ["**/.env*", "**/env/*", "**/.venv/*", "**/docs/*"]
[tool.pytest.ini_options]
addopts = "prometheuspvesd --cov=prometheuspvesd --cov-report=xml:coverage.xml --cov-report=term-missing --no-cov-on-fail --cov-fail-under=80"
filterwarnings = ["ignore::FutureWarning", "ignore::DeprecationWarning"]
addopts = "prometheuspvesd --cov=prometheuspvesd --cov-report=xml:coverage.xml --cov-report=term --no-cov-on-fail"
filterwarnings = [
"ignore::FutureWarning",
"ignore:.*distutils.*:DeprecationWarning",
"ignore:.*collections.*:DeprecationWarning",
"ignore:.*pep8.*:FutureWarning",
]
[tool.coverage.run]
omit = ["**/test/*"]
[build-system]
build-backend = "poetry_dynamic_versioning.backend"
build-backend = "poetry.core.masonry.api"
requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning"]
[tool.ruff]
exclude = [
".git",
"__pycache__",
"build",
"dist",
"*.pyc",
"*.egg-info",
".cache",
".eggs",
"env*",
]
line-length = 99
indent-width = 4
[tool.ruff.lint]
# 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
# D212: Multi-line docstring summary should start at the first line
ignore = [
"D102",
"D103",
"D105",
"D107",
"D202",
"D203",
"D212",
"UP038",
"RUF012",
]
select = [
"D",
"E",
"F",
"Q",
"W",
"I",
"S",
"BLE",
"N",
"UP",
"B",
"A",
"C4",
"T20",
"SIM",
"RET",
"ARG",
"ERA",
"RUF",
]
[tool.ruff.lint.per-file-ignores]
"test_*.py" = ["S"]
[tool.ruff.format]
quote-style = "double"
indent-style = "space"
line-ending = "lf"

View File

@ -1,12 +1,4 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["github>thegeeklab/renovate-presets"],
"packageRules": [
{
"matchManagers": ["woodpecker"],
"matchFileNames": [".woodpecker/test.yml"],
"matchPackageNames": ["docker.io/library/python"],
"enabled": false
}
]
"extends": ["github>thegeeklab/renovate-presets"]
}

24
setup.cfg Normal file
View File

@ -0,0 +1,24 @@
[flake8]
# 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
# G001: Logging statements should not use string.format() for their first argument
# G004: Logging statements should not use f"..." for their first argument
# W503: Line break occurred before a binary operator
ignore = D102, D103, D105, D107, D202, G001, G004, W503
max-line-length = 99
inline-quotes = double
exclude = .git, __pycache__, build, dist, *.pyc, *.egg-info, .cache, .eggs, env*
[yapf]
based_on_style = google
column_limit = 99
dedent_closing_brackets = true
coalesce_brackets = true
split_before_logical_operator = true
indent_dictionary_value = true
allow_split_before_dict_value = false