From 35bfa8bc6a643fedad6afab98aba1947b214576b Mon Sep 17 00:00:00 2001 From: Robert Kaussow Date: Tue, 19 Sep 2023 09:00:30 +0200 Subject: [PATCH] feat: add option to authenticate with api token instead of password (#460) Co-authored-by: Bruno MATEU Co-authored-by: Bruno MATEU --- docs/content/configuration/defaults.md | 14 +++++- docs/content/configuration/env.md | 2 + prometheuspvesd/cli.py | 12 ++++- prometheuspvesd/client.py | 12 +++++ prometheuspvesd/config.py | 12 +++++ prometheuspvesd/model.py | 6 ++- prometheuspvesd/test/data/config.yml | 2 + prometheuspvesd/test/fixtures/fixtures.py | 14 ++++++ prometheuspvesd/test/unit/test_cli.py | 59 +++++++++++++++++++++++ prometheuspvesd/test/unit/test_config.py | 2 + pyproject.toml | 2 +- 11 files changed, 131 insertions(+), 6 deletions(-) diff --git a/docs/content/configuration/defaults.md b/docs/content/configuration/defaults.md index 32561c0..90914c3 100644 --- a/docs/content/configuration/defaults.md +++ b/docs/content/configuration/defaults.md @@ -35,18 +35,30 @@ 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 +# Example with password # 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 ``` diff --git a/docs/content/configuration/env.md b/docs/content/configuration/env.md index 7d9396c..fc9c49f 100644 --- a/docs/content/configuration/env.md +++ b/docs/content/configuration/env.md @@ -36,6 +36,8 @@ 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 ``` diff --git a/prometheuspvesd/cli.py b/prometheuspvesd/cli.py index 1e0fb8d..4c18666 100644 --- a/prometheuspvesd/cli.py +++ b/prometheuspvesd/cli.py @@ -114,12 +114,20 @@ class PrometheusSD: self.log.sysexit_with_message(f"Can not set log level.\n{e!s}") required = [("pve.server", config.config["pve"]["server"]), - ("pve.user", config.config["pve"]["user"]), - ("pve.password", config.config["pve"]["password"])] + ("pve.user", config.config["pve"]["user"])] for name, value in required: if not value: 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}") return config diff --git a/prometheuspvesd/client.py b/prometheuspvesd/client.py index 4d7c862..ac7c4af 100644 --- a/prometheuspvesd/client.py +++ b/prometheuspvesd/client.py @@ -44,6 +44,18 @@ 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"], diff --git a/prometheuspvesd/config.py b/prometheuspvesd/config.py index 492bdee..be45ee6 100644 --- a/prometheuspvesd/config.py +++ b/prometheuspvesd/config.py @@ -135,6 +135,18 @@ class Config: "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 + }, "pve.auth_timeout": { "default": 5, "env": "PVE_AUTH_TIMEOUT", diff --git a/prometheuspvesd/model.py b/prometheuspvesd/model.py index e2ee8b6..2eb4394 100644 --- a/prometheuspvesd/model.py +++ b/prometheuspvesd/model.py @@ -19,8 +19,10 @@ class Host: self.add_label("vmid", vmid) def __str__(self): - return f"{self.hostname}({self.vmid}): {self.pve_type} \ - {self.ipv4_address} {self.ipv6_address}" + return ( + f"{self.hostname}({self.vmid}): " + f"{self.pve_type} {self.ipv4_address} {self.ipv6_address}" + ) def add_label(self, key, value): key = key.replace("-", "_").replace(" ", "_") diff --git a/prometheuspvesd/test/data/config.yml b/prometheuspvesd/test/data/config.yml index 94fe5b0..29b8d6f 100644 --- a/prometheuspvesd/test/data/config.yml +++ b/prometheuspvesd/test/data/config.yml @@ -24,5 +24,7 @@ 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 diff --git a/prometheuspvesd/test/fixtures/fixtures.py b/prometheuspvesd/test/fixtures/fixtures.py index 6949dc0..bed5716 100644 --- a/prometheuspvesd/test/fixtures/fixtures.py +++ b/prometheuspvesd/test/fixtures/fixtures.py @@ -114,6 +114,18 @@ def builtins(): "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 + }, "pve.auth_timeout": { "default": 5, "env": "PVE_AUTH_TIMEOUT", @@ -154,6 +166,8 @@ def defaults(): "password": "", "server": "", "user": "", + "token_name": "", + "token_value": "", "verify_ssl": True }, "service": True, diff --git a/prometheuspvesd/test/unit/test_cli.py b/prometheuspvesd/test/unit/test_cli.py index ccf234e..6133028 100644 --- a/prometheuspvesd/test/unit/test_cli.py +++ b/prometheuspvesd/test/unit/test_cli.py @@ -29,6 +29,65 @@ 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, 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): mocker.patch( "prometheuspvesd.config.SingleConfig.__init__", diff --git a/prometheuspvesd/test/unit/test_config.py b/prometheuspvesd/test/unit/test_config.py index c8849ac..9d99f25 100644 --- a/prometheuspvesd/test/unit/test_config.py +++ b/prometheuspvesd/test/unit/test_config.py @@ -19,6 +19,8 @@ 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 diff --git a/pyproject.toml b/pyproject.toml index 6735756..ef025cd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -66,7 +66,7 @@ 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 --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 = [ "ignore::FutureWarning", "ignore::DeprecationWarning",