devopsdriver 0.1.32__tar.gz → 0.1.34__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.
- {devopsdriver-0.1.32 → devopsdriver-0.1.34}/PKG-INFO +4 -2
- {devopsdriver-0.1.32 → devopsdriver-0.1.34}/README.md +1 -0
- {devopsdriver-0.1.32 → devopsdriver-0.1.34}/devopsdriver/__init__.py +1 -1
- {devopsdriver-0.1.32 → devopsdriver-0.1.34}/devopsdriver/settings.py +69 -17
- {devopsdriver-0.1.32 → devopsdriver-0.1.34}/devopsdriver.egg-info/PKG-INFO +4 -2
- devopsdriver-0.1.34/devopsdriver.egg-info/requires.txt +2 -0
- {devopsdriver-0.1.32 → devopsdriver-0.1.34}/pyproject.toml +2 -1
- {devopsdriver-0.1.32 → devopsdriver-0.1.34}/tests/test_settings.py +44 -0
- devopsdriver-0.1.32/devopsdriver.egg-info/requires.txt +0 -1
- {devopsdriver-0.1.32 → devopsdriver-0.1.34}/LICENSE +0 -0
- {devopsdriver-0.1.32 → devopsdriver-0.1.34}/devopsdriver.egg-info/SOURCES.txt +0 -0
- {devopsdriver-0.1.32 → devopsdriver-0.1.34}/devopsdriver.egg-info/dependency_links.txt +0 -0
- {devopsdriver-0.1.32 → devopsdriver-0.1.34}/devopsdriver.egg-info/top_level.txt +0 -0
- {devopsdriver-0.1.32 → devopsdriver-0.1.34}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: devopsdriver
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.34
|
|
4
4
|
Summary: DevOps tools
|
|
5
5
|
Author-email: Marc Page <marcallenpage@gmail.com>
|
|
6
6
|
License: This is free and unencumbered software released into the public domain.
|
|
@@ -43,12 +43,14 @@ Classifier: License :: OSI Approved :: The Unlicense (Unlicense)
|
|
|
43
43
|
Classifier: Programming Language :: Python :: 3.12
|
|
44
44
|
Classifier: Topic :: Utilities
|
|
45
45
|
Classifier: Topic :: Software Development
|
|
46
|
-
Requires-Python: >=3.
|
|
46
|
+
Requires-Python: >=3.10
|
|
47
47
|
Description-Content-Type: text/markdown
|
|
48
48
|
License-File: LICENSE
|
|
49
49
|
Requires-Dist: PyYAML==6.0.1
|
|
50
|
+
Requires-Dist: keyring==25.0.0
|
|
50
51
|
|
|
51
52
|

|
|
53
|
+
[](https://pypi.org/project/devopsdriver/0.1.34/)
|
|
52
54
|
[](https://github.com/marcpage/devops-driver?tab=Unlicense-1-ov-file#readme)
|
|
53
55
|
[](https://github.com/marcpage/devops-driver/commits)
|
|
54
56
|
[](https://github.com/marcpage/devops-driver/commits)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|

|
|
2
|
+
[](https://pypi.org/project/devopsdriver/0.1.34/)
|
|
2
3
|
[](https://github.com/marcpage/devops-driver?tab=Unlicense-1-ov-file#readme)
|
|
3
4
|
[](https://github.com/marcpage/devops-driver/commits)
|
|
4
5
|
[](https://github.com/marcpage/devops-driver/commits)
|
|
@@ -82,9 +82,10 @@ from os import environ as os_environ, makedirs as os_makedirs
|
|
|
82
82
|
from re import compile as regex
|
|
83
83
|
from platform import system as os_system
|
|
84
84
|
from sys import argv as sys_argv
|
|
85
|
+
from getpass import getpass as os_getpass
|
|
85
86
|
|
|
86
87
|
from yaml import safe_load
|
|
87
|
-
|
|
88
|
+
from keyring import get_password, set_password
|
|
88
89
|
|
|
89
90
|
# for testing
|
|
90
91
|
ENVIRON = os_environ
|
|
@@ -93,6 +94,9 @@ SYSTEM = os_system
|
|
|
93
94
|
MAKEDIRS = os_makedirs
|
|
94
95
|
SHARED = "devopsdriver"
|
|
95
96
|
PRINT = print
|
|
97
|
+
GET_PASSWORD = get_password
|
|
98
|
+
SET_PASSWORD = set_password
|
|
99
|
+
GET_PASS = os_getpass
|
|
96
100
|
|
|
97
101
|
|
|
98
102
|
def load_json(path: str) -> dict:
|
|
@@ -154,6 +158,30 @@ class Settings:
|
|
|
154
158
|
self.settings = Settings.__find_all_settings(search_info)
|
|
155
159
|
self.opts = {}
|
|
156
160
|
self.environ = {}
|
|
161
|
+
self.secrets = {}
|
|
162
|
+
|
|
163
|
+
def __bypass(self, key: str, name: str, store: dict):
|
|
164
|
+
if name is None:
|
|
165
|
+
for setting_key, store_name in self.settings.get(key, {}).items():
|
|
166
|
+
store[setting_key] = store_name
|
|
167
|
+
return self
|
|
168
|
+
|
|
169
|
+
store[key] = name
|
|
170
|
+
return self
|
|
171
|
+
|
|
172
|
+
def key(self, key: str, name: str = None):
|
|
173
|
+
"""Sets a keychain name to map to a settings value.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
key (str): The settings key it maps to, dotted for inside dictionary
|
|
177
|
+
name (str): Name of the keychain key
|
|
178
|
+
If name is not specified, the key is a settings value
|
|
179
|
+
to lookup up the mappings for keys to switches
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
Settings: Returns self so you can chain calls
|
|
183
|
+
"""
|
|
184
|
+
return self.__bypass(key, name, self.secrets)
|
|
157
185
|
|
|
158
186
|
def cli(self, key: str, name: str = None):
|
|
159
187
|
"""Sets a command line switch to map to a settings value.
|
|
@@ -167,13 +195,7 @@ class Settings:
|
|
|
167
195
|
Returns:
|
|
168
196
|
Settings: Returns self so you can chain calls
|
|
169
197
|
"""
|
|
170
|
-
|
|
171
|
-
for setting_key, env_name in self.settings.get(key, {}).items():
|
|
172
|
-
self.opts[setting_key] = env_name
|
|
173
|
-
return self
|
|
174
|
-
|
|
175
|
-
self.opts[key] = name
|
|
176
|
-
return self
|
|
198
|
+
return self.__bypass(key, name, self.opts)
|
|
177
199
|
|
|
178
200
|
def env(self, key: str, name: str = None):
|
|
179
201
|
"""Sets an environment variable to map to a settings value.
|
|
@@ -187,13 +209,7 @@ class Settings:
|
|
|
187
209
|
Returns:
|
|
188
210
|
Settings: Returns self so you can chain calls
|
|
189
211
|
"""
|
|
190
|
-
|
|
191
|
-
for setting_key, cli_name in self.settings.get(key, {}).items():
|
|
192
|
-
self.environ[setting_key] = cli_name
|
|
193
|
-
return self
|
|
194
|
-
|
|
195
|
-
self.environ[key] = name
|
|
196
|
-
return self
|
|
212
|
+
return self.__bypass(key, name, self.environ)
|
|
197
213
|
|
|
198
214
|
@staticmethod
|
|
199
215
|
def __patch_instance(key: str) -> str:
|
|
@@ -212,6 +228,23 @@ class Settings:
|
|
|
212
228
|
|
|
213
229
|
return value
|
|
214
230
|
|
|
231
|
+
@staticmethod
|
|
232
|
+
def split_key(key: str) -> tuple[str, str]:
|
|
233
|
+
"""Splits a keychain name into service and name
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
key (str): If there is a / then it is service/name
|
|
237
|
+
otherwise it is "system"/name
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
tuple[str, str]: The service and name
|
|
241
|
+
"""
|
|
242
|
+
parts = key.split("/", 1)
|
|
243
|
+
assert len(parts) in {1, 2}, parts
|
|
244
|
+
service = parts[0] if len(parts) == 2 else "system"
|
|
245
|
+
secret_name = parts[1] if len(parts) == 2 else parts[0]
|
|
246
|
+
return (service, secret_name)
|
|
247
|
+
|
|
215
248
|
def __lookup(self, key: str, check: bool, default: any = None) -> any:
|
|
216
249
|
# Settings passed in override everything
|
|
217
250
|
if key in self.overrides:
|
|
@@ -229,6 +262,13 @@ class Settings:
|
|
|
229
262
|
if e_key.lower() == self.environ[key].lower():
|
|
230
263
|
return True if check else ENVIRON[e_key]
|
|
231
264
|
|
|
265
|
+
# Settings in the keychain are next
|
|
266
|
+
if key in self.secrets:
|
|
267
|
+
value = GET_PASSWORD(*Settings.split_key(self.secrets[key]))
|
|
268
|
+
|
|
269
|
+
if value is not None:
|
|
270
|
+
return True if check else value
|
|
271
|
+
|
|
232
272
|
# Last check the files for settings
|
|
233
273
|
keys = key.split(".")
|
|
234
274
|
level = self.settings
|
|
@@ -318,9 +358,21 @@ class Settings:
|
|
|
318
358
|
|
|
319
359
|
def main() -> None:
|
|
320
360
|
"""Get settings values"""
|
|
321
|
-
settings = Settings(__file__, dirname(dirname(__file__)))
|
|
361
|
+
settings = Settings(__file__, dirname(dirname(__file__))).key("secrets")
|
|
362
|
+
|
|
363
|
+
args = list(ARGV[1:])
|
|
364
|
+
|
|
365
|
+
if "--set_secrets" in args:
|
|
366
|
+
args.remove("--set_secrets")
|
|
367
|
+
|
|
368
|
+
for secret, key in settings.secrets.items():
|
|
369
|
+
if not settings.has(secret):
|
|
370
|
+
value = GET_PASS(f"{secret} ({key}): ")
|
|
371
|
+
|
|
372
|
+
if value:
|
|
373
|
+
SET_PASSWORD(*Settings.split_key(key), value)
|
|
322
374
|
|
|
323
|
-
for arg in
|
|
375
|
+
for arg in args:
|
|
324
376
|
PRINT(settings.get(arg))
|
|
325
377
|
|
|
326
378
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: devopsdriver
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.34
|
|
4
4
|
Summary: DevOps tools
|
|
5
5
|
Author-email: Marc Page <marcallenpage@gmail.com>
|
|
6
6
|
License: This is free and unencumbered software released into the public domain.
|
|
@@ -43,12 +43,14 @@ Classifier: License :: OSI Approved :: The Unlicense (Unlicense)
|
|
|
43
43
|
Classifier: Programming Language :: Python :: 3.12
|
|
44
44
|
Classifier: Topic :: Utilities
|
|
45
45
|
Classifier: Topic :: Software Development
|
|
46
|
-
Requires-Python: >=3.
|
|
46
|
+
Requires-Python: >=3.10
|
|
47
47
|
Description-Content-Type: text/markdown
|
|
48
48
|
License-File: LICENSE
|
|
49
49
|
Requires-Dist: PyYAML==6.0.1
|
|
50
|
+
Requires-Dist: keyring==25.0.0
|
|
50
51
|
|
|
51
52
|

|
|
53
|
+
[](https://pypi.org/project/devopsdriver/0.1.34/)
|
|
52
54
|
[](https://github.com/marcpage/devops-driver?tab=Unlicense-1-ov-file#readme)
|
|
53
55
|
[](https://github.com/marcpage/devops-driver/commits)
|
|
54
56
|
[](https://github.com/marcpage/devops-driver/commits)
|
|
@@ -4,9 +4,10 @@ description = "DevOps tools"
|
|
|
4
4
|
readme = "README.md"
|
|
5
5
|
license = {file = "LICENSE"}
|
|
6
6
|
dynamic = ["version"]
|
|
7
|
-
requires-python = ">= 3.
|
|
7
|
+
requires-python = ">= 3.10"
|
|
8
8
|
dependencies = [
|
|
9
9
|
"PyYAML==6.0.1",
|
|
10
|
+
"keyring==25.0.0",
|
|
10
11
|
]
|
|
11
12
|
keywords = ["azure", "devops", "jira", "confluence", "email", "pipelines", "tools"]
|
|
12
13
|
classifiers=[
|
|
@@ -20,6 +20,9 @@ def __setup_settings(os: str = "Linux", shared: str = "test", **pref_dirs) -> No
|
|
|
20
20
|
settings.SYSTEM = lambda: os
|
|
21
21
|
settings.SHARED = shared
|
|
22
22
|
settings.PRINT = lambda s: s
|
|
23
|
+
settings.GET_PASSWORD = lambda s, n: f"{s}:{n}"
|
|
24
|
+
settings.GET_PASS = lambda p: p
|
|
25
|
+
settings.SET_PASSWORD = lambda s, n, p: f"{s} {n} {p}"
|
|
23
26
|
# settings.MAKEDIRS = lambda p: p
|
|
24
27
|
# settings.Settings.FORMATS = None
|
|
25
28
|
settings.Settings.PREF_DIR = pref_dirs
|
|
@@ -292,7 +295,48 @@ def test_main():
|
|
|
292
295
|
settings.main()
|
|
293
296
|
|
|
294
297
|
|
|
298
|
+
def test_secret():
|
|
299
|
+
"""test os secret storage"""
|
|
300
|
+
with TemporaryDirectory() as working_dir:
|
|
301
|
+
base_dir = join(working_dir, "base")
|
|
302
|
+
passwords = {"system": {"john": "setec astronomy"}}
|
|
303
|
+
__setup_settings(
|
|
304
|
+
os="Linux",
|
|
305
|
+
shared="test",
|
|
306
|
+
Linux=join(base_dir, "Linux"),
|
|
307
|
+
Darwin=join(base_dir, "macOS"),
|
|
308
|
+
Windows=join(base_dir, "Windows"),
|
|
309
|
+
)
|
|
310
|
+
settings.GET_PASSWORD = lambda s, e: passwords.get(s, {}).get(e, None)
|
|
311
|
+
__write(join(base_dir, "main.yml"), password="main")
|
|
312
|
+
opts = settings.Settings(join(base_dir, "main.py")).key(
|
|
313
|
+
"password", "system/john"
|
|
314
|
+
)
|
|
315
|
+
assert opts["password"] == "setec astronomy", opts["password"]
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def test_main_set_secret():
|
|
319
|
+
"""test the main entry point when settings keychain secrets"""
|
|
320
|
+
|
|
321
|
+
def set_password(s, n, p):
|
|
322
|
+
assert s == "azure" and n == "token" and p == "setec astronomy", f"{s} {n} {p}"
|
|
323
|
+
|
|
324
|
+
with TemporaryDirectory() as working_dir:
|
|
325
|
+
__setup_settings(shared="test", Linux=join(working_dir, "Linux"))
|
|
326
|
+
settings.ARGV = ["ignore", "--set_secrets"]
|
|
327
|
+
settings.GET_PASSWORD = lambda s, n: None
|
|
328
|
+
settings.GET_PASS = lambda p: "setec astronomy"
|
|
329
|
+
settings.SET_PASSWORD = set_password
|
|
330
|
+
__write(
|
|
331
|
+
join(working_dir, "Linux", "test.yml"),
|
|
332
|
+
secrets={"azure.token": "azure/token"},
|
|
333
|
+
)
|
|
334
|
+
settings.main()
|
|
335
|
+
|
|
336
|
+
|
|
295
337
|
if __name__ == "__main__":
|
|
338
|
+
test_main_set_secret()
|
|
339
|
+
test_secret()
|
|
296
340
|
test_main()
|
|
297
341
|
test_environ_values()
|
|
298
342
|
test_cli_env_in_yaml()
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
PyYAML==6.0.1
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|