xenoslib 0.1.29.24__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {xenoslib-0.1.29.24/xenoslib.egg-info → xenoslib-0.2.0}/PKG-INFO +1 -1
- {xenoslib-0.1.29.24 → xenoslib-0.2.0}/xenoslib/about.py +1 -1
- xenoslib-0.2.0/xenoslib/extend.py +198 -0
- xenoslib-0.2.0/xenoslib/tools/__init__.py +1 -0
- xenoslib-0.2.0/xenoslib/tools/config_loader.py +294 -0
- {xenoslib-0.1.29.24 → xenoslib-0.2.0/xenoslib.egg-info}/PKG-INFO +1 -1
- {xenoslib-0.1.29.24 → xenoslib-0.2.0}/xenoslib.egg-info/SOURCES.txt +3 -1
- xenoslib-0.1.29.24/xenoslib/extend.py +0 -394
- {xenoslib-0.1.29.24 → xenoslib-0.2.0}/LICENSE +0 -0
- {xenoslib-0.1.29.24 → xenoslib-0.2.0}/README.md +0 -0
- {xenoslib-0.1.29.24 → xenoslib-0.2.0}/setup.cfg +0 -0
- {xenoslib-0.1.29.24 → xenoslib-0.2.0}/setup.py +0 -0
- {xenoslib-0.1.29.24 → xenoslib-0.2.0}/xenoslib/__init__.py +0 -0
- {xenoslib-0.1.29.24 → xenoslib-0.2.0}/xenoslib/__main__.py +0 -0
- {xenoslib-0.1.29.24 → xenoslib-0.2.0}/xenoslib/base.py +0 -0
- {xenoslib-0.1.29.24 → xenoslib-0.2.0}/xenoslib/dev.py +0 -0
- {xenoslib-0.1.29.24 → xenoslib-0.2.0}/xenoslib/linux.py +0 -0
- {xenoslib-0.1.29.24 → xenoslib-0.2.0}/xenoslib/mail.py +0 -0
- {xenoslib-0.1.29.24 → xenoslib-0.2.0}/xenoslib/mock.py +0 -0
- {xenoslib-0.1.29.24 → xenoslib-0.2.0}/xenoslib/onedrive.py +0 -0
- {xenoslib-0.1.29.24 → xenoslib-0.2.0}/xenoslib/win_trayicon.py +0 -0
- {xenoslib-0.1.29.24 → xenoslib-0.2.0}/xenoslib/windows.py +0 -0
- {xenoslib-0.1.29.24 → xenoslib-0.2.0}/xenoslib.egg-info/dependency_links.txt +0 -0
- {xenoslib-0.1.29.24 → xenoslib-0.2.0}/xenoslib.egg-info/requires.txt +0 -0
- {xenoslib-0.1.29.24 → xenoslib-0.2.0}/xenoslib.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
import yaml
|
|
8
|
+
import requests
|
|
9
|
+
|
|
10
|
+
from xenoslib.tools import ConfigLoader # noqa compactive
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class YamlConfig(dict):
|
|
17
|
+
"""A thread unsafe yaml file config utility , can work as a dict except __init__"""
|
|
18
|
+
|
|
19
|
+
def __getattr__(self, key):
|
|
20
|
+
return self.get(key)
|
|
21
|
+
|
|
22
|
+
def __setattr__(self, name, value):
|
|
23
|
+
try:
|
|
24
|
+
getattr(super(), name)
|
|
25
|
+
except AttributeError as exc:
|
|
26
|
+
if str(exc).startswith("'super' object has no attribute "):
|
|
27
|
+
self[name] = value
|
|
28
|
+
return
|
|
29
|
+
raise exc
|
|
30
|
+
raise AttributeError(f"'{__class__.__name__}' object attribute '{name}' is read-only")
|
|
31
|
+
|
|
32
|
+
def __str__(self):
|
|
33
|
+
return yaml.safe_dump(self.copy(), allow_unicode=True)
|
|
34
|
+
|
|
35
|
+
def __repr__(self):
|
|
36
|
+
return repr(str(self))
|
|
37
|
+
|
|
38
|
+
def __init__(self, conf_path=None):
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
def __new__(cls, conf_path="config.yml", *args, **kwargs):
|
|
42
|
+
if not hasattr(cls, "_instances"):
|
|
43
|
+
cls._instances = {}
|
|
44
|
+
if cls._instances.get(conf_path) is None:
|
|
45
|
+
cls._instances[conf_path] = super().__new__(cls)
|
|
46
|
+
super().__setattr__(cls._instances[conf_path], "_conf_path", conf_path)
|
|
47
|
+
cls._instances[conf_path]._load_conf()
|
|
48
|
+
return cls._instances[conf_path]
|
|
49
|
+
|
|
50
|
+
def _load_conf(self):
|
|
51
|
+
if os.path.exists(self._conf_path):
|
|
52
|
+
with open(self._conf_path, encoding="utf-8") as r:
|
|
53
|
+
self.update(yaml.safe_load(r))
|
|
54
|
+
|
|
55
|
+
def save(self):
|
|
56
|
+
data = str(self)
|
|
57
|
+
with open(self._conf_path, "w", encoding="utf-8") as w:
|
|
58
|
+
w.write(data)
|
|
59
|
+
# yaml.safe_dump(self.copy(), w, allow_unicode=True)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class RequestAdapter:
|
|
63
|
+
def request(self, method, path, *args, **kwargs):
|
|
64
|
+
"""to-do: support stream=True"""
|
|
65
|
+
url = f"{self.base_url}/{path}"
|
|
66
|
+
logger.debug(url)
|
|
67
|
+
response = self.session.request(method, url, *args, **kwargs)
|
|
68
|
+
logger.debug(response.text)
|
|
69
|
+
response.raise_for_status()
|
|
70
|
+
try:
|
|
71
|
+
return response.json()
|
|
72
|
+
except Exception as exc:
|
|
73
|
+
logger.debug(exc)
|
|
74
|
+
return response
|
|
75
|
+
|
|
76
|
+
def get(self, path, *args, **kwargs):
|
|
77
|
+
return self.request("get", path, *args, **kwargs)
|
|
78
|
+
|
|
79
|
+
def post(self, path, *args, **kwargs):
|
|
80
|
+
return self.request("post", path, *args, **kwargs)
|
|
81
|
+
|
|
82
|
+
def put(self, path, *args, **kwargs):
|
|
83
|
+
return self.request("put", path, *args, **kwargs)
|
|
84
|
+
|
|
85
|
+
def delete(self, path, *args, **kwargs):
|
|
86
|
+
return self.request("delete", path, *args, **kwargs)
|
|
87
|
+
|
|
88
|
+
def patch(self, path, *args, **kwargs):
|
|
89
|
+
return self.request("patch", path, *args, **kwargs)
|
|
90
|
+
|
|
91
|
+
def head(self, path, *args, **kwargs):
|
|
92
|
+
return self.request("head", path, *args, **kwargs)
|
|
93
|
+
|
|
94
|
+
def __init__(self):
|
|
95
|
+
self.session = requests.Session()
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def del_to_recyclebin(filepath, on_fail_delete=False):
|
|
99
|
+
"""delete file to recyclebin if possible"""
|
|
100
|
+
if not sys.platform == "win32":
|
|
101
|
+
if on_fail_delete:
|
|
102
|
+
os.remove(filepath)
|
|
103
|
+
return True
|
|
104
|
+
return False
|
|
105
|
+
from win32com.shell import shell, shellcon
|
|
106
|
+
|
|
107
|
+
res, _ = shell.SHFileOperation(
|
|
108
|
+
(
|
|
109
|
+
0,
|
|
110
|
+
shellcon.FO_DELETE,
|
|
111
|
+
filepath,
|
|
112
|
+
None,
|
|
113
|
+
shellcon.FOF_SILENT | shellcon.FOF_ALLOWUNDO | shellcon.FOF_NOCONFIRMATION,
|
|
114
|
+
None,
|
|
115
|
+
None,
|
|
116
|
+
)
|
|
117
|
+
)
|
|
118
|
+
return res == 0
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def send_notify(msg, key):
|
|
122
|
+
"""send a message for ifttt"""
|
|
123
|
+
url = f"https://maker.ifttt.com/trigger/message/with/key/{key}"
|
|
124
|
+
data = {"value1": msg}
|
|
125
|
+
return requests.post(url, data=data, timeout=(30, 30))
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class IFTTTLogHandler(logging.Handler):
|
|
129
|
+
"""
|
|
130
|
+
log handler for IFTTT
|
|
131
|
+
usage:
|
|
132
|
+
key = 'xxxxx.xxxzx.xxxzx.xxxzx'
|
|
133
|
+
iftttloghandler = IFTTTLogHandler(key, level=logging.INFO)
|
|
134
|
+
logging.getLogger(__name__).addHandler(iftttloghandler)
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
def __init__(self, key, level=logging.CRITICAL, *args, **kwargs):
|
|
138
|
+
self.key = key
|
|
139
|
+
super().__init__(level=level, *args, **kwargs)
|
|
140
|
+
|
|
141
|
+
def emit(self, record):
|
|
142
|
+
try:
|
|
143
|
+
send_notify(self.format(record), self.key)
|
|
144
|
+
except Exception as exc:
|
|
145
|
+
print(exc)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class SlackLogHandler(logging.Handler):
|
|
149
|
+
"""
|
|
150
|
+
log handler for Slack
|
|
151
|
+
usage:
|
|
152
|
+
slackloghandler = SlackLogHandler(webhook_url, level=logging.INFO)
|
|
153
|
+
logging.getLogger(__name__).addHandler(slackloghandler)
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
def __init__(self, webhook_url, level=logging.CRITICAL, *args, **kwargs):
|
|
157
|
+
self.url = webhook_url
|
|
158
|
+
self.headers = {"Content-type": "application/json"}
|
|
159
|
+
super().__init__(level=level, *args, **kwargs)
|
|
160
|
+
|
|
161
|
+
def emit(self, record):
|
|
162
|
+
try:
|
|
163
|
+
data = {"text": self.format(record)}
|
|
164
|
+
requests.post(self.url, headers=self.headers, json=data, timeout=(30, 30))
|
|
165
|
+
except Exception as exc:
|
|
166
|
+
print(exc)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class DingTalkLogHandler(logging.Handler):
|
|
170
|
+
"""
|
|
171
|
+
log handler for DingTalk
|
|
172
|
+
usage:
|
|
173
|
+
token = 'xxxxx.xxxzx.xxxzx.xxxzx'
|
|
174
|
+
dingtalkloghandler = DingTalkLogHandler(token, level=logging.INFO)
|
|
175
|
+
logging.getLogger(__name__).addHandler(dingtalkloghandler)
|
|
176
|
+
"""
|
|
177
|
+
|
|
178
|
+
def __init__(self, token, level=logging.CRITICAL, *args, **kwargs):
|
|
179
|
+
self.token = token
|
|
180
|
+
super().__init__(level=level, *args, **kwargs)
|
|
181
|
+
|
|
182
|
+
def emit(self, record):
|
|
183
|
+
headers = {"Content-Type": "application/json"}
|
|
184
|
+
url = "https://oapi.dingtalk.com/robot/send"
|
|
185
|
+
params = {"access_token": self.token}
|
|
186
|
+
msg = self.format(record)
|
|
187
|
+
data = {"msgtype": "text", "text": {"content": msg}}
|
|
188
|
+
try:
|
|
189
|
+
response = requests.post(
|
|
190
|
+
url, headers=headers, params=params, json=data, timeout=(10, 10)
|
|
191
|
+
)
|
|
192
|
+
print(response.json())
|
|
193
|
+
except Exception as exc:
|
|
194
|
+
print(exc)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
if __name__ == "__main__":
|
|
198
|
+
pass
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .config_loader import ConfigLoader # noqa
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
import os
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
import yaml
|
|
7
|
+
|
|
8
|
+
from xenoslib.base import SingletonWithArgs
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ConfigLoader(SingletonWithArgs):
|
|
15
|
+
"""Centralized configuration management with optional Vault integration.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
config_file_path (str): Path to the YAML configuration file. Defaults to "config.yml".
|
|
19
|
+
vault_secret_id (str, optional): Secret ID for Vault authentication.
|
|
20
|
+
If provided, enables Vault functionality and imports hvac module.
|
|
21
|
+
|
|
22
|
+
Attributes:
|
|
23
|
+
cache (dict): Cache storage for frequently accessed configuration values.
|
|
24
|
+
|
|
25
|
+
Example:
|
|
26
|
+
# Without Vault (hvac not imported)
|
|
27
|
+
>>> config = ConfigLoader("config.yml")
|
|
28
|
+
|
|
29
|
+
# With Vault (hvac imported on demand)
|
|
30
|
+
>>> config = ConfigLoader("config.yml", vault_secret_id="my-secret-id")
|
|
31
|
+
|
|
32
|
+
# Write to Vault using attribute style
|
|
33
|
+
>>> config.test_section.test_key = "new_value"
|
|
34
|
+
|
|
35
|
+
# Write to Vault using dictionary style
|
|
36
|
+
>>> config["test_section"]["test_key"] = "new_value"
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
VAULT_SUFFIX = "@vault"
|
|
40
|
+
KV_MOUNT_POINT = "kv"
|
|
41
|
+
|
|
42
|
+
cache = {}
|
|
43
|
+
vault_client = None
|
|
44
|
+
|
|
45
|
+
def __init__(self, config_file_path="config.yml", vault_secret_id=None):
|
|
46
|
+
"""Initialize the ConfigLoader with a configuration file and optional Vault secret."""
|
|
47
|
+
with open(config_file_path, "r") as f:
|
|
48
|
+
config_data = yaml.safe_load(f)
|
|
49
|
+
self._raw_config = config_data if isinstance(config_data, dict) else {}
|
|
50
|
+
|
|
51
|
+
if vault_secret_id is not None:
|
|
52
|
+
self.vault_secret_id = vault_secret_id
|
|
53
|
+
self._check_and_renew_vault_client()
|
|
54
|
+
|
|
55
|
+
def _init_vault_client(self):
|
|
56
|
+
"""Initialize and authenticate the Vault client (imports hvac on demand)."""
|
|
57
|
+
try:
|
|
58
|
+
import hvac # Lazy import
|
|
59
|
+
except ImportError as e:
|
|
60
|
+
raise ImportError(
|
|
61
|
+
"hvac package is required for Vault integration. Install with: pip install hvac"
|
|
62
|
+
) from e
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
vault_config = self._raw_config.get("vault", {})
|
|
66
|
+
vault_url = vault_config.get("url")
|
|
67
|
+
vault_space = vault_config.get("space")
|
|
68
|
+
vault_role_id = vault_config.get("role_id")
|
|
69
|
+
|
|
70
|
+
if not all([vault_url, vault_space, vault_role_id]):
|
|
71
|
+
raise KeyError("Missing required Vault configuration in config.yml")
|
|
72
|
+
|
|
73
|
+
self.vault_client = hvac.Client(url=vault_url, namespace=vault_space, timeout=45)
|
|
74
|
+
self.vault_client.auth.approle.login(
|
|
75
|
+
role_id=vault_role_id, secret_id=self.vault_secret_id
|
|
76
|
+
)
|
|
77
|
+
except Exception as e:
|
|
78
|
+
self.vault_client = None
|
|
79
|
+
raise Exception(f"Failed to initialize Vault client: {str(e)}") from e
|
|
80
|
+
|
|
81
|
+
def _check_and_renew_vault_client(self):
|
|
82
|
+
if not self.vault_client or not self.vault_client.is_authenticated():
|
|
83
|
+
self._init_vault_client()
|
|
84
|
+
|
|
85
|
+
def _is_vault_reference(self, section_config, key_name):
|
|
86
|
+
"""检查键是否是Vault引用"""
|
|
87
|
+
return f"{key_name}{self.VAULT_SUFFIX}" in section_config
|
|
88
|
+
|
|
89
|
+
def get(self, section, key_name, use_cache=True):
|
|
90
|
+
"""Retrieve a configuration value."""
|
|
91
|
+
section_config = self._raw_config.get(section)
|
|
92
|
+
if section_config is None:
|
|
93
|
+
raise KeyError(f"Section '{section}' not found")
|
|
94
|
+
|
|
95
|
+
if key_name in section_config:
|
|
96
|
+
return section_config[key_name]
|
|
97
|
+
|
|
98
|
+
if self._is_vault_reference(section_config, key_name):
|
|
99
|
+
if self.vault_client is None:
|
|
100
|
+
raise Exception(
|
|
101
|
+
f"Vault access required for {key_name} but Vault is not initialized"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
cache_key = f"{section}:{key_name}"
|
|
105
|
+
if use_cache and cache_key in self.cache:
|
|
106
|
+
return self.cache[cache_key]
|
|
107
|
+
value = self._get_value_from_vault(section, key_name)
|
|
108
|
+
self.cache[cache_key] = value
|
|
109
|
+
return value
|
|
110
|
+
|
|
111
|
+
raise KeyError(f"Key '{key_name}' not found in section '{section}'")
|
|
112
|
+
|
|
113
|
+
def set(self, section, key_name, value, use_cache=True):
|
|
114
|
+
"""Set a configuration value to Vault."""
|
|
115
|
+
section_config = self._raw_config.get(section)
|
|
116
|
+
if section_config is None:
|
|
117
|
+
raise KeyError(f"Section '{section}' not found")
|
|
118
|
+
|
|
119
|
+
if not self._is_vault_reference(section_config, key_name):
|
|
120
|
+
raise KeyError(f"Key '{key_name}' is not a Vault reference in section '{section}'")
|
|
121
|
+
|
|
122
|
+
if self.vault_client is None:
|
|
123
|
+
raise Exception(
|
|
124
|
+
f"Vault access required for {key_name} but Vault is not initialized"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
self._set_value_to_vault(section, key_name, value)
|
|
128
|
+
|
|
129
|
+
cache_key = f"{section}:{key_name}"
|
|
130
|
+
if use_cache:
|
|
131
|
+
self.cache[cache_key] = value
|
|
132
|
+
|
|
133
|
+
def _get_value_from_vault(self, section, key_name):
|
|
134
|
+
"""Retrieve a secret value from Vault."""
|
|
135
|
+
try:
|
|
136
|
+
section_config = self._raw_config[section]
|
|
137
|
+
vault_path = section_config.get("vault_path")
|
|
138
|
+
if not vault_path:
|
|
139
|
+
raise KeyError(f"Missing vault_path in section '{section}'")
|
|
140
|
+
|
|
141
|
+
vault_key_ref = f"{key_name}{self.VAULT_SUFFIX}"
|
|
142
|
+
vault_key = section_config[vault_key_ref]
|
|
143
|
+
|
|
144
|
+
namespace = section_config.get("vault_namespace") or self._raw_config["vault"]["space"]
|
|
145
|
+
self.vault_client.adapter.namespace = namespace
|
|
146
|
+
|
|
147
|
+
data = self.vault_client.secrets.kv.read_secret_version(
|
|
148
|
+
path=vault_path, mount_point=self.KV_MOUNT_POINT, raise_on_deleted_version=True
|
|
149
|
+
)
|
|
150
|
+
return data["data"]["data"][vault_key]
|
|
151
|
+
except Exception as e:
|
|
152
|
+
raise Exception(f"Failed to fetch {key_name} from Vault: {str(e)}") from e
|
|
153
|
+
|
|
154
|
+
def _set_value_to_vault(self, section, key_name, value):
|
|
155
|
+
"""Set a secret value to Vault."""
|
|
156
|
+
try:
|
|
157
|
+
self._check_and_renew_vault_client()
|
|
158
|
+
|
|
159
|
+
section_config = self._raw_config[section]
|
|
160
|
+
vault_path = section_config.get("vault_path")
|
|
161
|
+
if not vault_path:
|
|
162
|
+
raise KeyError(f"Missing vault_path in section '{section}'")
|
|
163
|
+
|
|
164
|
+
vault_key_ref = f"{key_name}{self.VAULT_SUFFIX}"
|
|
165
|
+
vault_key = section_config[vault_key_ref]
|
|
166
|
+
|
|
167
|
+
namespace = section_config.get("vault_namespace") or self._raw_config["vault"]["space"]
|
|
168
|
+
self.vault_client.adapter.namespace = namespace
|
|
169
|
+
|
|
170
|
+
try:
|
|
171
|
+
data = self.vault_client.secrets.kv.read_secret_version(
|
|
172
|
+
path=vault_path, mount_point=self.KV_MOUNT_POINT, raise_on_deleted_version=True
|
|
173
|
+
)
|
|
174
|
+
secret_data = data["data"]["data"]
|
|
175
|
+
except Exception:
|
|
176
|
+
logger.warning(f"Secret not found, creating new secret at {vault_path}")
|
|
177
|
+
secret_data = {}
|
|
178
|
+
|
|
179
|
+
secret_data[vault_key] = value
|
|
180
|
+
|
|
181
|
+
self.vault_client.secrets.kv.create_or_update_secret(
|
|
182
|
+
path=vault_path,
|
|
183
|
+
secret=secret_data,
|
|
184
|
+
mount_point=self.KV_MOUNT_POINT
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
logger.info(f"Updated Vault secret: {vault_path}/{vault_key}")
|
|
188
|
+
except Exception as e:
|
|
189
|
+
raise Exception(f"Failed to set {key_name} to Vault: {str(e)}") from e
|
|
190
|
+
|
|
191
|
+
def __getitem__(self, section):
|
|
192
|
+
"""Dictionary-style access to configuration sections."""
|
|
193
|
+
if section not in self._raw_config:
|
|
194
|
+
raise KeyError(f"Section '{section}' not found")
|
|
195
|
+
return SectionProxy(self, section)
|
|
196
|
+
|
|
197
|
+
def __setitem__(self, section, proxy):
|
|
198
|
+
"""Prevent direct assignment to sections."""
|
|
199
|
+
raise TypeError("ConfigLoader does not support direct section assignment")
|
|
200
|
+
|
|
201
|
+
def __getattr__(self, section):
|
|
202
|
+
"""Attribute-style access to configuration sections."""
|
|
203
|
+
try:
|
|
204
|
+
return self[section]
|
|
205
|
+
except KeyError as e:
|
|
206
|
+
raise AttributeError(str(e))
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class SectionProxy:
|
|
210
|
+
"""Proxy class for configuration section access."""
|
|
211
|
+
|
|
212
|
+
def __init__(self, config_loader, section):
|
|
213
|
+
self._loader = config_loader
|
|
214
|
+
self._section = section
|
|
215
|
+
|
|
216
|
+
def __getitem__(self, key):
|
|
217
|
+
"""Dictionary-style access to configuration values."""
|
|
218
|
+
return self._loader.get(self._section, key)
|
|
219
|
+
|
|
220
|
+
def __setitem__(self, key, value):
|
|
221
|
+
"""Dictionary-style setting of configuration values to Vault."""
|
|
222
|
+
self.set(key, value)
|
|
223
|
+
|
|
224
|
+
def get(self, key, default=None):
|
|
225
|
+
"""Dictionary-style access to configuration values."""
|
|
226
|
+
try:
|
|
227
|
+
return self._loader.get(self._section, key)
|
|
228
|
+
except KeyError:
|
|
229
|
+
return default
|
|
230
|
+
|
|
231
|
+
def set(self, key, value):
|
|
232
|
+
"""Set a configuration value to Vault."""
|
|
233
|
+
self._loader.set(self._section, key, value)
|
|
234
|
+
|
|
235
|
+
def __getattr__(self, key):
|
|
236
|
+
"""Attribute-style access to configuration values."""
|
|
237
|
+
try:
|
|
238
|
+
return self[key]
|
|
239
|
+
except KeyError as e:
|
|
240
|
+
raise AttributeError(str(e))
|
|
241
|
+
|
|
242
|
+
def __setattr__(self, name, value):
|
|
243
|
+
"""Attribute-style setting of configuration values to Vault."""
|
|
244
|
+
if name.startswith('_'):
|
|
245
|
+
super().__setattr__(name, value)
|
|
246
|
+
else:
|
|
247
|
+
self.set(name, value)
|
|
248
|
+
|
|
249
|
+
def __repr__(self):
|
|
250
|
+
"""String representation of the section's configuration."""
|
|
251
|
+
return yaml.dump(self._loader._raw_config[self._section])
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
if __name__ == "__main__":
|
|
255
|
+
config_without_vault = ConfigLoader("config.yml")
|
|
256
|
+
print("Without Vault:", config_without_vault.get("jira", "url"))
|
|
257
|
+
|
|
258
|
+
config_with_vault = ConfigLoader("config.yml", vault_secret_id=os.getenv("VAULT_SECRET_ID"))
|
|
259
|
+
|
|
260
|
+
# 属性方式读取
|
|
261
|
+
print("With Vault (attr):", config_with_vault.test.test)
|
|
262
|
+
|
|
263
|
+
# 字典方式读取
|
|
264
|
+
print("With Vault (dict):", config_with_vault["cis"]["cis_client_id"])
|
|
265
|
+
|
|
266
|
+
# 测试不存在的值
|
|
267
|
+
print("Try val not exists: ", config_with_vault.test.get("not_exists"))
|
|
268
|
+
|
|
269
|
+
# 写入示例 - 属性方式
|
|
270
|
+
try:
|
|
271
|
+
print("Current test value (attr read):", config_with_vault.test.test)
|
|
272
|
+
config_with_vault.test.test = "new_value_123"
|
|
273
|
+
print("After write (attr read):", config_with_vault.test.test)
|
|
274
|
+
except Exception as e:
|
|
275
|
+
print("Attribute write failed:", str(e))
|
|
276
|
+
|
|
277
|
+
# 写入示例 - 字典方式
|
|
278
|
+
try:
|
|
279
|
+
print("Current test value (dict read):", config_with_vault["test"]["test"])
|
|
280
|
+
config_with_vault["test"]["test"] = "new_value_456"
|
|
281
|
+
print("After write (dict read):", config_with_vault["test"]["test"])
|
|
282
|
+
|
|
283
|
+
# 混合方式验证
|
|
284
|
+
print("After write (attr read):", config_with_vault.test.test)
|
|
285
|
+
except Exception as e:
|
|
286
|
+
print("Dictionary write failed:", str(e))
|
|
287
|
+
|
|
288
|
+
# 写入新键示例
|
|
289
|
+
try:
|
|
290
|
+
print("Setting new key...")
|
|
291
|
+
config_with_vault["database"]["new_password"] = "secure123!"
|
|
292
|
+
print("New password:", config_with_vault.database.new_password)
|
|
293
|
+
except Exception as e:
|
|
294
|
+
print("New key write failed:", str(e))
|
|
@@ -17,4 +17,6 @@ xenoslib.egg-info/PKG-INFO
|
|
|
17
17
|
xenoslib.egg-info/SOURCES.txt
|
|
18
18
|
xenoslib.egg-info/dependency_links.txt
|
|
19
19
|
xenoslib.egg-info/requires.txt
|
|
20
|
-
xenoslib.egg-info/top_level.txt
|
|
20
|
+
xenoslib.egg-info/top_level.txt
|
|
21
|
+
xenoslib/tools/__init__.py
|
|
22
|
+
xenoslib/tools/config_loader.py
|
|
@@ -1,394 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
|
-
import os
|
|
4
|
-
import sys
|
|
5
|
-
import logging
|
|
6
|
-
|
|
7
|
-
import yaml
|
|
8
|
-
import requests
|
|
9
|
-
|
|
10
|
-
from xenoslib.base import SingletonWithArgs
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
logger = logging.getLogger(__name__)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class YamlConfig(dict):
|
|
17
|
-
"""A thread unsafe yaml file config utility , can work as a dict except __init__"""
|
|
18
|
-
|
|
19
|
-
def __getattr__(self, key):
|
|
20
|
-
return self.get(key)
|
|
21
|
-
|
|
22
|
-
def __setattr__(self, name, value):
|
|
23
|
-
try:
|
|
24
|
-
getattr(super(), name)
|
|
25
|
-
except AttributeError as exc:
|
|
26
|
-
if str(exc).startswith("'super' object has no attribute "):
|
|
27
|
-
self[name] = value
|
|
28
|
-
return
|
|
29
|
-
raise exc
|
|
30
|
-
raise AttributeError(f"'{__class__.__name__}' object attribute '{name}' is read-only")
|
|
31
|
-
|
|
32
|
-
def __str__(self):
|
|
33
|
-
return yaml.safe_dump(self.copy(), allow_unicode=True)
|
|
34
|
-
|
|
35
|
-
def __repr__(self):
|
|
36
|
-
return repr(str(self))
|
|
37
|
-
|
|
38
|
-
def __init__(self, conf_path=None):
|
|
39
|
-
pass
|
|
40
|
-
|
|
41
|
-
def __new__(cls, conf_path="config.yml", *args, **kwargs):
|
|
42
|
-
if not hasattr(cls, "_instances"):
|
|
43
|
-
cls._instances = {}
|
|
44
|
-
if cls._instances.get(conf_path) is None:
|
|
45
|
-
cls._instances[conf_path] = super().__new__(cls)
|
|
46
|
-
super().__setattr__(cls._instances[conf_path], "_conf_path", conf_path)
|
|
47
|
-
cls._instances[conf_path]._load_conf()
|
|
48
|
-
return cls._instances[conf_path]
|
|
49
|
-
|
|
50
|
-
def _load_conf(self):
|
|
51
|
-
if os.path.exists(self._conf_path):
|
|
52
|
-
with open(self._conf_path, encoding="utf-8") as r:
|
|
53
|
-
self.update(yaml.safe_load(r))
|
|
54
|
-
|
|
55
|
-
def save(self):
|
|
56
|
-
data = str(self)
|
|
57
|
-
with open(self._conf_path, "w", encoding="utf-8") as w:
|
|
58
|
-
w.write(data)
|
|
59
|
-
# yaml.safe_dump(self.copy(), w, allow_unicode=True)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
class RequestAdapter:
|
|
63
|
-
def request(self, method, path, *args, **kwargs):
|
|
64
|
-
"""to-do: support stream=True"""
|
|
65
|
-
url = f"{self.base_url}/{path}"
|
|
66
|
-
logger.debug(url)
|
|
67
|
-
response = self.session.request(method, url, *args, **kwargs)
|
|
68
|
-
logger.debug(response.text)
|
|
69
|
-
response.raise_for_status()
|
|
70
|
-
try:
|
|
71
|
-
return response.json()
|
|
72
|
-
except Exception as exc:
|
|
73
|
-
logger.debug(exc)
|
|
74
|
-
return response
|
|
75
|
-
|
|
76
|
-
def get(self, path, *args, **kwargs):
|
|
77
|
-
return self.request("get", path, *args, **kwargs)
|
|
78
|
-
|
|
79
|
-
def post(self, path, *args, **kwargs):
|
|
80
|
-
return self.request("post", path, *args, **kwargs)
|
|
81
|
-
|
|
82
|
-
def put(self, path, *args, **kwargs):
|
|
83
|
-
return self.request("put", path, *args, **kwargs)
|
|
84
|
-
|
|
85
|
-
def delete(self, path, *args, **kwargs):
|
|
86
|
-
return self.request("delete", path, *args, **kwargs)
|
|
87
|
-
|
|
88
|
-
def patch(self, path, *args, **kwargs):
|
|
89
|
-
return self.request("patch", path, *args, **kwargs)
|
|
90
|
-
|
|
91
|
-
def head(self, path, *args, **kwargs):
|
|
92
|
-
return self.request("head", path, *args, **kwargs)
|
|
93
|
-
|
|
94
|
-
def __init__(self):
|
|
95
|
-
self.session = requests.Session()
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
def del_to_recyclebin(filepath, on_fail_delete=False):
|
|
99
|
-
"""delete file to recyclebin if possible"""
|
|
100
|
-
if not sys.platform == "win32":
|
|
101
|
-
if on_fail_delete:
|
|
102
|
-
os.remove(filepath)
|
|
103
|
-
return True
|
|
104
|
-
return False
|
|
105
|
-
from win32com.shell import shell, shellcon
|
|
106
|
-
|
|
107
|
-
res, _ = shell.SHFileOperation(
|
|
108
|
-
(
|
|
109
|
-
0,
|
|
110
|
-
shellcon.FO_DELETE,
|
|
111
|
-
filepath,
|
|
112
|
-
None,
|
|
113
|
-
shellcon.FOF_SILENT | shellcon.FOF_ALLOWUNDO | shellcon.FOF_NOCONFIRMATION,
|
|
114
|
-
None,
|
|
115
|
-
None,
|
|
116
|
-
)
|
|
117
|
-
)
|
|
118
|
-
return res == 0
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
def send_notify(msg, key):
|
|
122
|
-
"""send a message for ifttt"""
|
|
123
|
-
url = f"https://maker.ifttt.com/trigger/message/with/key/{key}"
|
|
124
|
-
data = {"value1": msg}
|
|
125
|
-
return requests.post(url, data=data, timeout=(30, 30))
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
class IFTTTLogHandler(logging.Handler):
|
|
129
|
-
"""
|
|
130
|
-
log handler for IFTTT
|
|
131
|
-
usage:
|
|
132
|
-
key = 'xxxxx.xxxzx.xxxzx.xxxzx'
|
|
133
|
-
iftttloghandler = IFTTTLogHandler(key, level=logging.INFO)
|
|
134
|
-
logging.getLogger(__name__).addHandler(iftttloghandler)
|
|
135
|
-
"""
|
|
136
|
-
|
|
137
|
-
def __init__(self, key, level=logging.CRITICAL, *args, **kwargs):
|
|
138
|
-
self.key = key
|
|
139
|
-
super().__init__(level=level, *args, **kwargs)
|
|
140
|
-
|
|
141
|
-
def emit(self, record):
|
|
142
|
-
try:
|
|
143
|
-
send_notify(self.format(record), self.key)
|
|
144
|
-
except Exception as exc:
|
|
145
|
-
print(exc)
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
class SlackLogHandler(logging.Handler):
|
|
149
|
-
"""
|
|
150
|
-
log handler for Slack
|
|
151
|
-
usage:
|
|
152
|
-
slackloghandler = SlackLogHandler(webhook_url, level=logging.INFO)
|
|
153
|
-
logging.getLogger(__name__).addHandler(slackloghandler)
|
|
154
|
-
"""
|
|
155
|
-
|
|
156
|
-
def __init__(self, webhook_url, level=logging.CRITICAL, *args, **kwargs):
|
|
157
|
-
self.url = webhook_url
|
|
158
|
-
self.headers = {"Content-type": "application/json"}
|
|
159
|
-
super().__init__(level=level, *args, **kwargs)
|
|
160
|
-
|
|
161
|
-
def emit(self, record):
|
|
162
|
-
try:
|
|
163
|
-
data = {"text": self.format(record)}
|
|
164
|
-
requests.post(self.url, headers=self.headers, json=data, timeout=(30, 30))
|
|
165
|
-
except Exception as exc:
|
|
166
|
-
print(exc)
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
class DingTalkLogHandler(logging.Handler):
|
|
170
|
-
"""
|
|
171
|
-
log handler for DingTalk
|
|
172
|
-
usage:
|
|
173
|
-
token = 'xxxxx.xxxzx.xxxzx.xxxzx'
|
|
174
|
-
dingtalkloghandler = DingTalkLogHandler(token, level=logging.INFO)
|
|
175
|
-
logging.getLogger(__name__).addHandler(dingtalkloghandler)
|
|
176
|
-
"""
|
|
177
|
-
|
|
178
|
-
def __init__(self, token, level=logging.CRITICAL, *args, **kwargs):
|
|
179
|
-
self.token = token
|
|
180
|
-
super().__init__(level=level, *args, **kwargs)
|
|
181
|
-
|
|
182
|
-
def emit(self, record):
|
|
183
|
-
headers = {"Content-Type": "application/json"}
|
|
184
|
-
url = "https://oapi.dingtalk.com/robot/send"
|
|
185
|
-
params = {"access_token": self.token}
|
|
186
|
-
msg = self.format(record)
|
|
187
|
-
data = {"msgtype": "text", "text": {"content": msg}}
|
|
188
|
-
try:
|
|
189
|
-
response = requests.post(
|
|
190
|
-
url, headers=headers, params=params, json=data, timeout=(10, 10)
|
|
191
|
-
)
|
|
192
|
-
print(response.json())
|
|
193
|
-
except Exception as exc:
|
|
194
|
-
print(exc)
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
class ConfigLoader(SingletonWithArgs):
|
|
198
|
-
"""Centralized configuration management with optional Vault integration.
|
|
199
|
-
|
|
200
|
-
Args:
|
|
201
|
-
config_file_path (str): Path to the YAML configuration file. Defaults to "config.yml".
|
|
202
|
-
vault_secret_id (str, optional): Secret ID for Vault authentication.
|
|
203
|
-
If provided, enables Vault functionality and imports hvac module.
|
|
204
|
-
|
|
205
|
-
Attributes:
|
|
206
|
-
cache (dict): Cache storage for frequently accessed configuration values.
|
|
207
|
-
|
|
208
|
-
Example:
|
|
209
|
-
# Without Vault (hvac not imported)
|
|
210
|
-
>>> config = ConfigLoader("config.yml")
|
|
211
|
-
|
|
212
|
-
# With Vault (hvac imported on demand)
|
|
213
|
-
>>> config = ConfigLoader("config.yml", vault_secret_id="my-secret-id")
|
|
214
|
-
"""
|
|
215
|
-
|
|
216
|
-
cache = {}
|
|
217
|
-
vault_client = None
|
|
218
|
-
|
|
219
|
-
def __init__(self, config_file_path="config.yml", vault_secret_id=None):
|
|
220
|
-
"""Initialize the ConfigLoader with a configuration file and optional Vault secret."""
|
|
221
|
-
with open(config_file_path, "r") as f:
|
|
222
|
-
self._raw_config = yaml.safe_load(f) or {}
|
|
223
|
-
|
|
224
|
-
if vault_secret_id is not None:
|
|
225
|
-
self.vault_secret_id = vault_secret_id
|
|
226
|
-
self._check_and_renew_vault_client()
|
|
227
|
-
|
|
228
|
-
def _init_vault_client(self):
|
|
229
|
-
"""Initialize and authenticate the Vault client (imports hvac on demand).
|
|
230
|
-
|
|
231
|
-
Args:
|
|
232
|
-
vault_secret_id (str): Secret ID for Vault authentication.
|
|
233
|
-
|
|
234
|
-
Raises:
|
|
235
|
-
ImportError: If hvac package is not installed.
|
|
236
|
-
KeyError: If required Vault configuration is missing.
|
|
237
|
-
Exception: If Vault authentication fails.
|
|
238
|
-
"""
|
|
239
|
-
try:
|
|
240
|
-
import hvac # Lazy import
|
|
241
|
-
except ImportError as e:
|
|
242
|
-
raise ImportError(
|
|
243
|
-
"hvac package is required for Vault integration. " "Install with: pip install hvac"
|
|
244
|
-
) from e
|
|
245
|
-
|
|
246
|
-
try:
|
|
247
|
-
vault_config = self._raw_config.get("vault", {})
|
|
248
|
-
vault_url = vault_config.get("url")
|
|
249
|
-
vault_space = vault_config.get("space")
|
|
250
|
-
vault_role_id = vault_config.get("role_id")
|
|
251
|
-
|
|
252
|
-
if not all([vault_url, vault_space, vault_role_id]):
|
|
253
|
-
raise KeyError("Missing required Vault configuration in config.yml")
|
|
254
|
-
|
|
255
|
-
self.vault_client = hvac.Client(url=vault_url, namespace=vault_space, timeout=45)
|
|
256
|
-
self.vault_client.auth.approle.login(
|
|
257
|
-
role_id=vault_role_id, secret_id=self.vault_secret_id
|
|
258
|
-
)
|
|
259
|
-
except Exception as e:
|
|
260
|
-
self.vault_client = None
|
|
261
|
-
raise Exception(f"Failed to initialize Vault client: {str(e)}")
|
|
262
|
-
|
|
263
|
-
def _check_and_renew_vault_client(self):
|
|
264
|
-
# 检查当前Token的状态,包括过期时间和可续租性
|
|
265
|
-
if not self.vault_client or not self.vault_client.is_authenticated():
|
|
266
|
-
# 如果当前Token无效,则重新认证
|
|
267
|
-
self._init_vault_client()
|
|
268
|
-
|
|
269
|
-
def get(self, section, key_name, use_cache=True):
|
|
270
|
-
"""Retrieve a configuration value.
|
|
271
|
-
|
|
272
|
-
Args:
|
|
273
|
-
section (str): The configuration section name.
|
|
274
|
-
key_name (str): The key name within the section.
|
|
275
|
-
use_cache (bool): Whether to use cached values. Defaults to True.
|
|
276
|
-
|
|
277
|
-
Returns:
|
|
278
|
-
The configuration value, which may come from:
|
|
279
|
-
- Direct configuration value
|
|
280
|
-
- Vault secret (if Vault is initialized)
|
|
281
|
-
- Cache (if enabled)
|
|
282
|
-
|
|
283
|
-
Raises:
|
|
284
|
-
KeyError: If the section or key is not found.
|
|
285
|
-
Exception: If Vault access is required but not available.
|
|
286
|
-
"""
|
|
287
|
-
if section not in self._raw_config:
|
|
288
|
-
raise KeyError(f"Section '{section}' not found")
|
|
289
|
-
|
|
290
|
-
# Check for direct value first
|
|
291
|
-
if key_name in self._raw_config[section]:
|
|
292
|
-
return self._raw_config[section][key_name]
|
|
293
|
-
|
|
294
|
-
# Handle Vault reference if Vault is enabled
|
|
295
|
-
vault_key = f"{key_name}@vault"
|
|
296
|
-
if vault_key in self._raw_config[section]:
|
|
297
|
-
if self.vault_client is None:
|
|
298
|
-
raise Exception(
|
|
299
|
-
f"Vault access required for {key_name} but Vault is not initialized"
|
|
300
|
-
)
|
|
301
|
-
|
|
302
|
-
cache_key = f"{section}_{key_name}"
|
|
303
|
-
|
|
304
|
-
if use_cache and cache_key in self.cache:
|
|
305
|
-
return self.cache[cache_key]
|
|
306
|
-
value = self._get_value_from_vault(section, key_name)
|
|
307
|
-
self.cache[cache_key] = value
|
|
308
|
-
return value
|
|
309
|
-
|
|
310
|
-
raise KeyError(f"Key '{key_name}' or '{vault_key}' not found in section '{section}'")
|
|
311
|
-
|
|
312
|
-
def _get_value_from_vault(self, section, key_name):
|
|
313
|
-
"""Retrieve a secret value from Vault.
|
|
314
|
-
|
|
315
|
-
Args:
|
|
316
|
-
section (str): The configuration section name.
|
|
317
|
-
key_name (str): The key name within the section.
|
|
318
|
-
|
|
319
|
-
Returns:
|
|
320
|
-
The secret value from Vault.
|
|
321
|
-
|
|
322
|
-
Raises:
|
|
323
|
-
Exception: If Vault access fails.
|
|
324
|
-
"""
|
|
325
|
-
try:
|
|
326
|
-
vault_path = self._raw_config[section]["vault_path"]
|
|
327
|
-
vault_key = self._raw_config[section][f"{key_name}@vault"]
|
|
328
|
-
vault_namepsace = self._raw_config[section].get("vault_namespace")
|
|
329
|
-
if vault_namepsace:
|
|
330
|
-
self.vault_client.adapter.namespace = vault_namepsace
|
|
331
|
-
else:
|
|
332
|
-
self.vault_client.adapter.namespace = self._raw_config["vault"]["space"]
|
|
333
|
-
data = self.vault_client.secrets.kv.read_secret_version(
|
|
334
|
-
path=vault_path, mount_point="kv", raise_on_deleted_version=True
|
|
335
|
-
)
|
|
336
|
-
return data["data"]["data"][vault_key]
|
|
337
|
-
except Exception as e:
|
|
338
|
-
raise Exception(f"Failed to fetch {key_name} from Vault: {str(e)}")
|
|
339
|
-
|
|
340
|
-
def __getitem__(self, section):
|
|
341
|
-
"""Dictionary-style access to configuration sections."""
|
|
342
|
-
if section not in self._raw_config:
|
|
343
|
-
raise KeyError(f"Section '{section}' not found")
|
|
344
|
-
return SectionProxy(self, section)
|
|
345
|
-
|
|
346
|
-
def __getattr__(self, section):
|
|
347
|
-
"""Attribute-style access to configuration sections."""
|
|
348
|
-
try:
|
|
349
|
-
return self[section]
|
|
350
|
-
except KeyError as e:
|
|
351
|
-
raise AttributeError(str(e))
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
class SectionProxy:
|
|
355
|
-
"""Proxy class for configuration section access."""
|
|
356
|
-
|
|
357
|
-
def __init__(self, config_loader, section):
|
|
358
|
-
self._loader = config_loader
|
|
359
|
-
self._section = section
|
|
360
|
-
|
|
361
|
-
def __getitem__(self, key):
|
|
362
|
-
"""Dictionary-style access to configuration values."""
|
|
363
|
-
return self._loader.get(self._section, key)
|
|
364
|
-
|
|
365
|
-
def get(self, key, default=None):
|
|
366
|
-
"""Dictionary-style access to configuration values."""
|
|
367
|
-
try:
|
|
368
|
-
return self._loader.get(self._section, key)
|
|
369
|
-
except KeyError:
|
|
370
|
-
return default
|
|
371
|
-
|
|
372
|
-
def __getattr__(self, key):
|
|
373
|
-
"""Attribute-style access to configuration values."""
|
|
374
|
-
try:
|
|
375
|
-
return self[key]
|
|
376
|
-
except KeyError as e:
|
|
377
|
-
raise AttributeError(str(e))
|
|
378
|
-
|
|
379
|
-
def __repr__(self):
|
|
380
|
-
"""String representation of the section's configuration."""
|
|
381
|
-
return yaml.dump(self._loader._raw_config[self._section])
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
if __name__ == "__main__":
|
|
385
|
-
config_without_vault = ConfigLoader("config.yml")
|
|
386
|
-
print("Without Vault:", config_without_vault.get("jira", "url"))
|
|
387
|
-
|
|
388
|
-
# This will only work if you provide a valid Vault secret ID
|
|
389
|
-
# and hvac package is installed
|
|
390
|
-
config_with_vault = ConfigLoader("config.yml", vault_secret_id=os.getenv("VAULT_SECRET_ID"))
|
|
391
|
-
|
|
392
|
-
print("With Vault:", config_with_vault.test.test)
|
|
393
|
-
print("With Vault:", config_with_vault["cis"]["cis_client_id"])
|
|
394
|
-
print("Try val not exists: ", config_with_vault.test.get("not_exists"))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|