feat: add option to authenticate with api token instead of password (#460)

Co-authored-by: Bruno MATEU <pro+github@brunomat.eu>
Co-authored-by: Bruno MATEU <mateubruno@gmail.com>
This commit is contained in:
Robert Kaussow 2023-09-19 09:00:30 +02:00 committed by GitHub
parent a6d128d605
commit 35bfa8bc6a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 131 additions and 6 deletions

View File

@ -35,18 +35,30 @@ include_vmid: []
exclude_tags: [] exclude_tags: []
include_tags: [] include_tags: []
# Set either password or token_name and token_value
pve: pve:
server: server:
user: user:
password: password:
token_name:
token_value:
auth_timeout: 5 auth_timeout: 5
verify_ssl: true verify_ssl: true
# Example # Example with password
# pve: # pve:
# server: proxmox.example.com # server: proxmox.example.com
# user: root # user: root
# password: secure # password: secure
# auth_timeout: 5 # auth_timeout: 5
# verify_ssl: true # 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,6 +36,8 @@ PROMETHEUS_PVE_SD_INCLUDE_TAGS=
PROMETHEUS_PVE_SD_PVE_SERVER= PROMETHEUS_PVE_SD_PVE_SERVER=
PROMETHEUS_PVE_SD_PVE_USER= PROMETHEUS_PVE_SD_PVE_USER=
PROMETHEUS_PVE_SD_PVE_PASSWORD= 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_AUTH_TIMEOUT=5
PROMETHEUS_PVE_SD_PVE_VERIFY_SSL=true PROMETHEUS_PVE_SD_PVE_VERIFY_SSL=true
``` ```

View File

@ -114,12 +114,20 @@ class PrometheusSD:
self.log.sysexit_with_message(f"Can not set log level.\n{e!s}") self.log.sysexit_with_message(f"Can not set log level.\n{e!s}")
required = [("pve.server", config.config["pve"]["server"]), required = [("pve.server", config.config["pve"]["server"]),
("pve.user", config.config["pve"]["user"]), ("pve.user", config.config["pve"]["user"])]
("pve.password", config.config["pve"]["password"])]
for name, value in required: for name, value in required:
if not value: if not value:
self.log.sysexit_with_message(f"Option '{name}' is required but not set") self.log.sysexit_with_message(f"Option '{name}' is required but not set")
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(f"Using config file {config.config_file}")
return config return config

View File

@ -44,6 +44,18 @@ class ProxmoxClient:
self.config.config["pve"]["server"], self.config.config["pve"]["user"] 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( return ProxmoxAPI(
self.config.config["pve"]["server"], self.config.config["pve"]["server"],
user=self.config.config["pve"]["user"], user=self.config.config["pve"]["user"],

View File

@ -135,6 +135,18 @@ class Config:
"file": True, "file": True,
"type": environs.Env().str "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
},
"pve.auth_timeout": { "pve.auth_timeout": {
"default": 5, "default": 5,
"env": "PVE_AUTH_TIMEOUT", "env": "PVE_AUTH_TIMEOUT",

View File

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

View File

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

View File

@ -114,6 +114,18 @@ def builtins():
"file": True, "file": True,
"type": environs.Env().str "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
},
"pve.auth_timeout": { "pve.auth_timeout": {
"default": 5, "default": 5,
"env": "PVE_AUTH_TIMEOUT", "env": "PVE_AUTH_TIMEOUT",
@ -154,6 +166,8 @@ def defaults():
"password": "", "password": "",
"server": "", "server": "",
"user": "", "user": "",
"token_name": "",
"token_value": "",
"verify_ssl": True "verify_ssl": True
}, },
"service": True, "service": True,

View File

@ -29,6 +29,65 @@ def test_cli_required_error(mocker, capsys):
assert e.value.code == 1 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, 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)
psd = PrometheusSD()
for key, value in testinput.items():
assert psd.config.config["pve"][key.split(".")[1]] == value
def test_cli_config_error(mocker, capsys): def test_cli_config_error(mocker, capsys):
mocker.patch( mocker.patch(
"prometheuspvesd.config.SingleConfig.__init__", "prometheuspvesd.config.SingleConfig.__init__",

View File

@ -19,6 +19,8 @@ def test_yaml_config(mocker, defaults):
defaults["pve"]["user"] = "root" defaults["pve"]["user"] = "root"
defaults["pve"]["password"] = "secure" defaults["pve"]["password"] = "secure"
defaults["pve"]["server"] = "proxmox.example.com" 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 assert config.config == defaults

View File

@ -66,7 +66,7 @@ sections = ["FUTURE", "STDLIB", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"]
skip_glob = ["**/.env*", "**/env/*", "**/.venv/*", "**/docs/*"] skip_glob = ["**/.env*", "**/env/*", "**/.venv/*", "**/docs/*"]
[tool.pytest.ini_options] [tool.pytest.ini_options]
addopts = "prometheuspvesd --cov=prometheuspvesd --cov-report=xml:coverage.xml --cov-report=term --no-cov-on-fail" addopts = "prometheuspvesd --cov=prometheuspvesd --cov-report=xml:coverage.xml --cov-report=term-missing --no-cov-on-fail --cov-fail-under=80"
filterwarnings = [ filterwarnings = [
"ignore::FutureWarning", "ignore::FutureWarning",
"ignore::DeprecationWarning", "ignore::DeprecationWarning",