import os
from pathlib import Path
from typing import Literal, Optional, Sequence, Union
import yaml
from pydantic import TypeAdapter
from pydantic.dataclasses import dataclass
from pretiac.exceptions import PretiacException
from pretiac.object_types import Payload
[docs]
@dataclass(config={"extra": "forbid"})
class ObjectConfig:
"""
Bundles all configuration required to create an object.
"""
templates: Optional[Sequence[str]] = None
"""
Import existing configuration templates for this
object type. Note: These templates must either be statically
configured or provided in config packages.
"""
attrs: Optional["Payload"] = None
"""Set specific object attributes for this object type."""
[docs]
@dataclass(config={"extra": "forbid"})
class Config:
"""
:see: `pretiac (JS) <https://github.com/Josef-Friedrich/PREtty-Typed-Icinga2-Api-Client_js/blob/722c6308d79f603a9ad7678609cd907b932c64ab/src/client.ts#L7-L15>`__
"""
config_file: Optional[Path] = None
"""The path of the loaded configuration file."""
api_endpoint_host: Optional[str] = None
"""
The domain or the IP address of the API endpoint, e. g. ``icinga.example.com``,
``localhost`` or ``127.0.0.1``.
"""
api_endpoint_port: Optional[int] = None
"""The TCP port of the API endpoint, for example ``5665``.
:see: `Icinca Object Types (apilistener) <https://icinga.com/docs/icinga-2/latest/doc/09-object-types/#apilistener>`__
"""
http_basic_username: Optional[str] = None
"""
The name of the API user used in the HTTP basic authentification, e. g. ``apiuser``.
.. code-block ::
object ApiUser "apiuser" {
...
}
:see: `Icinca Object Types (apiuser) <https://icinga.com/docs/icinga-2/latest/doc/09-object-types/#apiuser>`__
"""
http_basic_password: Optional[str] = None
"""
The password of the API user used in the HTTP basic authentification, e. g. ``password``.
.. code-block ::
object ApiUser "apiuser" {
password = "password"
}
:see: `Icinca Object Types <https://icinga.com/docs/icinga-2/latest/doc/09-object-types/#apiuser>`__
"""
client_private_key: Optional[str] = None
"""
The file path of the client’s **private RSA key**, for example
``/etc/pretiac/api-client.key.pem``.
The RSA private key is created with this command:
.. code-block ::
icinga2 pki new-cert \\
--cn api-client \\
--key api-client.key.pem \\
--csr api-client.csr.pem
"""
client_certificate: Optional[str] = None
"""
The file path of the client **certificate**.
The certificate is created with this command:
.. code-block ::
icinga2 pki sign-csr \\
--csr api-client.csr.pem \\
--cert api-client.cert.pem
"""
ca_certificate: Optional[str] = None
"""
The file path of the Icinga **CA (Certification Authority)**.
The CA certificate is located at ``/var/lib/icinga2/certs/ca.crt``. This
command copies the certificate to the local host.
.. code-block ::
scp icinga-master:/var/lib/icinga2/certs/ca.crt .
"""
suppress_exception: Optional[bool] = None
"""
If set to ``True``, no exceptions are thrown.
"""
new_host_defaults: Optional[ObjectConfig] = None
"""If a new host needs to be created, use this defaults."""
new_service_defaults: Optional[ObjectConfig] = None
"""If a new service needs to be created, use this defaults."""
[docs]
def check(self) -> None:
"""Check if all required values are set."""
if self.api_endpoint_host is None:
raise PretiacException("Specify an API endpoint host (api_endpoint_host)!")
if (self.http_basic_username or self.http_basic_password) and (
self.client_private_key or self.client_certificate or self.ca_certificate
):
raise PretiacException(
"Specify HTTP basic OR certificate authentification. Not both!"
)
[docs]
def load_config_file(config_file: Optional[Union[str, Path]] = None) -> Config:
"""
Load the configuration file in YAML format.
The file path of the loaded configuration file is determined in this order:
1. The parameter ``config_file`` of this function.
2. The file path in the environment variable ``PRETIAC_CONFIG_FILE``.
3. The configuration file in the home folder ``~/.pretiac.yml``.
4. The configuration file at ``/etc/pretiac/config.yml``.
:param config_file: The path of the configuration file to load.
"""
config_files: list[Path] = []
if config_file:
if isinstance(config_file, str):
config_files.append(Path(config_file))
else:
config_files.append(config_file)
if "PRETIAC_CONFIG_FILE" in os.environ:
config_files.append(Path(os.environ["PRETIAC_CONFIG_FILE"]))
config_files.append(Path.cwd() / ".pretiac.yml")
config_files.append(Path("/etc/pretiac/config.yml"))
for path in config_files:
if path.exists():
config_file = path
break
adapter = TypeAdapter(Config)
if not config_file:
return adapter.validate_python({})
with open(config_file, "r") as file:
config_raw = yaml.safe_load(file)
config_raw["config_file"] = str(config_file)
return adapter.validate_python(config_raw)
[docs]
def load_config(
config: Optional[Config] = None,
config_file: Optional[Union[str, Path, Literal[False]]] = None,
api_endpoint_host: Optional[str] = None,
api_endpoint_port: Optional[int] = None,
http_basic_username: Optional[str] = None,
http_basic_password: Optional[str] = None,
client_private_key: Optional[str] = None,
client_certificate: Optional[str] = None,
ca_certificate: Optional[str] = None,
suppress_exception: Optional[bool] = None,
new_host_defaults: Optional[ObjectConfig] = None,
new_service_defaults: Optional[ObjectConfig] = None,
) -> Config:
"""
:param config: A configuration object that has already been populated.
:param config_file: The path of the configuration file to load.
If this value is set to false no configuration file will be loaded.
:param api_endpoint_host: The domain or the IP address of the API
endpoint, e. g. ``icinga.example.com``, ``localhost`` or ``127.0.0.1``.
:param api_endpoint_port: The TCP port of the API endpoint, for example
``5665``.
:param http_basic_username: The name of the API user used in the HTTP basic
authentification, e. g. ``apiuser``.
:param http_basic_password: The password of the API user used in the HTTP
basic authentification, e. g. ``password``.
:param client_private_key: The file path of the client’s **private RSA
key**, for example ``/etc/pretiac/api-client.key.pem``.
:param client_certificate: The file path of the client’s **certificate**,
for example ``/etc/pretiac/api-client.cert.pem``.
:param ca_certificate: The file path of the Icinga **CA (Certification
Authority)**, for example ``/var/lib/icinga2/certs/ca.crt``.
:param suppress_exception: If set to ``True``, no exceptions are thrown.
:param new_host_defaults: If a new host needs to be created, use this
defaults.
:param new_service_defaults: If a new service needs to be created, use
this defaults.
"""
if config is not None and (config_file is not None and config_file is not False):
raise PretiacException("Specify config OR config_file. Not both!")
c: Optional[Config] = None
if config:
c = config
elif config_file is False:
c = Config()
else:
c = load_config_file(config_file)
if api_endpoint_host is not None:
c.api_endpoint_host = api_endpoint_host
if api_endpoint_port is not None:
c.api_endpoint_port = api_endpoint_port
if c.api_endpoint_port is None:
c.api_endpoint_port = 5665
if http_basic_username is not None:
c.http_basic_username = http_basic_username
if http_basic_password is not None:
c.http_basic_password = http_basic_password
if client_private_key is not None:
c.client_private_key = client_private_key
if client_certificate is not None:
c.client_certificate = client_certificate
if ca_certificate is not None:
c.ca_certificate = ca_certificate
if suppress_exception is not None:
c.suppress_exception = suppress_exception
if new_host_defaults is not None:
c.new_host_defaults = new_host_defaults
if new_service_defaults is not None:
c.new_service_defaults = new_service_defaults
c.check()
return c