automizor 0.3.1__py3-none-any.whl → 0.4.0__py3-none-any.whl
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.
- automizor/__init__.py +1 -1
- automizor/exceptions.py +69 -0
- automizor/job/__init__.py +0 -2
- automizor/job/_job.py +14 -17
- automizor/storage/__init__.py +100 -2
- automizor/storage/_storage.py +169 -34
- automizor/utils/__init__.py +31 -0
- automizor/vault/__init__.py +35 -6
- automizor/vault/_container.py +3 -2
- automizor/vault/_vault.py +68 -68
- {automizor-0.3.1.dist-info → automizor-0.4.0.dist-info}/METADATA +1 -1
- automizor-0.4.0.dist-info/RECORD +15 -0
- {automizor-0.3.1.dist-info → automizor-0.4.0.dist-info}/WHEEL +1 -1
- automizor/job/_exceptions.py +0 -2
- automizor/job.py +0 -132
- automizor/storage/_exceptions.py +0 -2
- automizor/storage.py +0 -0
- automizor/vault/_exceptions.py +0 -2
- automizor/vault/_secret.py +0 -45
- automizor/vault.py +0 -190
- automizor-0.3.1.dist-info/RECORD +0 -20
- {automizor-0.3.1.dist-info → automizor-0.4.0.dist-info}/LICENSE +0 -0
- {automizor-0.3.1.dist-info → automizor-0.4.0.dist-info}/top_level.txt +0 -0
automizor/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
version = "0.
|
1
|
+
version = "0.4.0"
|
automizor/exceptions.py
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
from requests import Response
|
2
|
+
|
3
|
+
|
4
|
+
class AutomizorError(Exception):
|
5
|
+
def __init__(self, message, error=None):
|
6
|
+
if error:
|
7
|
+
message = f"{message}: {error}"
|
8
|
+
super().__init__(message)
|
9
|
+
|
10
|
+
def __str__(self):
|
11
|
+
return f"{self.args[0]}"
|
12
|
+
|
13
|
+
@classmethod
|
14
|
+
def from_response(cls, response: Response, message: str):
|
15
|
+
_STATUS_EXCEPTION_MAP = {
|
16
|
+
400: InvalidRequest,
|
17
|
+
401: Unauthorized,
|
18
|
+
403: Forbidden,
|
19
|
+
404: NotFound,
|
20
|
+
429: RateLimitExceeded,
|
21
|
+
500: InternalServerError,
|
22
|
+
502: BadGateway,
|
23
|
+
503: ServiceUnavailable,
|
24
|
+
}
|
25
|
+
|
26
|
+
try:
|
27
|
+
error = dict(response.json()).get("detail", "Unknown error.")
|
28
|
+
except Exception: # pylint: disable=broad-except
|
29
|
+
error = response.text
|
30
|
+
|
31
|
+
return _STATUS_EXCEPTION_MAP.get(response.status_code, UnexpectedError)(
|
32
|
+
message, error
|
33
|
+
)
|
34
|
+
|
35
|
+
|
36
|
+
class BadGateway(AutomizorError):
|
37
|
+
pass
|
38
|
+
|
39
|
+
|
40
|
+
class Forbidden(AutomizorError):
|
41
|
+
pass
|
42
|
+
|
43
|
+
|
44
|
+
class InternalServerError(AutomizorError):
|
45
|
+
pass
|
46
|
+
|
47
|
+
|
48
|
+
class InvalidRequest(AutomizorError):
|
49
|
+
pass
|
50
|
+
|
51
|
+
|
52
|
+
class NotFound(AutomizorError):
|
53
|
+
pass
|
54
|
+
|
55
|
+
|
56
|
+
class RateLimitExceeded(AutomizorError):
|
57
|
+
pass
|
58
|
+
|
59
|
+
|
60
|
+
class ServiceUnavailable(AutomizorError):
|
61
|
+
pass
|
62
|
+
|
63
|
+
|
64
|
+
class Unauthorized(AutomizorError):
|
65
|
+
pass
|
66
|
+
|
67
|
+
|
68
|
+
class UnexpectedError(AutomizorError):
|
69
|
+
pass
|
automizor/job/__init__.py
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
from functools import lru_cache
|
2
2
|
|
3
|
-
from ._exceptions import AutomizorJobError
|
4
3
|
from ._job import JSON
|
5
4
|
|
6
5
|
|
@@ -44,7 +43,6 @@ def set_result(name: str, value: JSON):
|
|
44
43
|
|
45
44
|
|
46
45
|
__all__ = [
|
47
|
-
"AutomizorJobError",
|
48
46
|
"get_context",
|
49
47
|
"set_result",
|
50
48
|
]
|
automizor/job/_job.py
CHANGED
@@ -4,7 +4,8 @@ from typing import Dict, List, Union
|
|
4
4
|
|
5
5
|
import requests
|
6
6
|
|
7
|
-
from .
|
7
|
+
from automizor.exceptions import AutomizorError
|
8
|
+
from automizor.utils import get_api_config, get_headers
|
8
9
|
|
9
10
|
JSON = Union[str, int, float, bool, None, Dict[str, "JSON"], List["JSON"]]
|
10
11
|
|
@@ -30,8 +31,7 @@ class Job:
|
|
30
31
|
To use this class effectively, ensure that the following environment variables are
|
31
32
|
set in your environment:
|
32
33
|
|
33
|
-
- ``
|
34
|
-
- ``AUTOMIZOR_API_TOKEN``: The authentication token for API access.
|
34
|
+
- ``AUTOMIZOR_AGENT_TOKEN``: The token for authenticating against the `Automizor API`.
|
35
35
|
- ``AUTOMIZOR_CONTEXT_FILE``: The path to a local file containing job context, if used.
|
36
36
|
- ``AUTOMIZOR_JOB_ID``: The identifier for the current job, used when fetching context via API.
|
37
37
|
|
@@ -50,18 +50,12 @@ class Job:
|
|
50
50
|
"""
|
51
51
|
|
52
52
|
def __init__(self):
|
53
|
-
self.
|
54
|
-
self.
|
55
|
-
self._context_file = os.getenv("AUTOMIZOR_CONTEXT_FILE")
|
56
|
-
self._job_id = os.getenv("AUTOMIZOR_JOB_ID")
|
53
|
+
self._context_file = os.getenv("AUTOMIZOR_CONTEXT_FILE", None)
|
54
|
+
self._job_id = os.getenv("AUTOMIZOR_JOB_ID", None)
|
57
55
|
|
56
|
+
self.url, self.token = get_api_config()
|
58
57
|
self.session = requests.Session()
|
59
|
-
self.session.headers.update(
|
60
|
-
{
|
61
|
-
"Authorization": f"Token {self._api_token}",
|
62
|
-
"Content-Type": "application/json",
|
63
|
-
}
|
64
|
-
)
|
58
|
+
self.session.headers.update(get_headers(self.token))
|
65
59
|
|
66
60
|
def get_context(self) -> dict:
|
67
61
|
"""
|
@@ -73,8 +67,7 @@ class Job:
|
|
73
67
|
This is useful in environments where direct access to the `Automizor API` is
|
74
68
|
not possible or preferred.
|
75
69
|
2. The `Automizor API`, using the job ID (`AUTOMIZOR_JOB_ID`) to fetch the specific
|
76
|
-
job context.
|
77
|
-
(`AUTOMIZOR_API_TOKEN`) settings.
|
70
|
+
job context.
|
78
71
|
|
79
72
|
Returns:
|
80
73
|
A dictionary containing the job context.
|
@@ -125,10 +118,14 @@ class Job:
|
|
125
118
|
return json.load(file)
|
126
119
|
|
127
120
|
def _read_job_context(self) -> dict:
|
128
|
-
url = f"https://{self.
|
121
|
+
url = f"https://{self.url}/api/v1/rpa/job/{self._job_id}/"
|
129
122
|
try:
|
130
123
|
response = self.session.get(url, timeout=10)
|
131
124
|
response.raise_for_status()
|
132
125
|
return response.json().get("context", {})
|
126
|
+
except requests.HTTPError as exc:
|
127
|
+
raise AutomizorError.from_response(
|
128
|
+
exc.response, "Failed to get job context"
|
129
|
+
) from exc
|
133
130
|
except Exception as exc:
|
134
|
-
raise
|
131
|
+
raise AutomizorError("Failed to get job context") from exc
|
automizor/storage/__init__.py
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
+
import json
|
2
|
+
import mimetypes
|
1
3
|
from functools import lru_cache
|
4
|
+
from pathlib import Path
|
2
5
|
|
3
|
-
from ._exceptions import AutomizorStorageError
|
4
6
|
from ._storage import JSON
|
5
7
|
|
6
8
|
|
@@ -11,6 +13,30 @@ def _get_storage():
|
|
11
13
|
return Storage()
|
12
14
|
|
13
15
|
|
16
|
+
def list_assets() -> list[str]:
|
17
|
+
"""
|
18
|
+
Retrieves a list of all asset names.
|
19
|
+
|
20
|
+
Returns:
|
21
|
+
A list of all asset names.
|
22
|
+
"""
|
23
|
+
|
24
|
+
storage = _get_storage()
|
25
|
+
return storage.list_assets()
|
26
|
+
|
27
|
+
|
28
|
+
def delete_asset(name: str) -> None:
|
29
|
+
"""
|
30
|
+
Deletes the specified asset.
|
31
|
+
|
32
|
+
Parameters:
|
33
|
+
name: The name identifier of the asset to delete.
|
34
|
+
"""
|
35
|
+
|
36
|
+
storage = _get_storage()
|
37
|
+
storage.delete_asset(name)
|
38
|
+
|
39
|
+
|
14
40
|
def get_bytes(name: str) -> bytes:
|
15
41
|
"""
|
16
42
|
Retrieves the specified asset as raw bytes.
|
@@ -72,10 +98,82 @@ def get_text(name: str) -> str:
|
|
72
98
|
return storage.get_text(name)
|
73
99
|
|
74
100
|
|
101
|
+
def set_bytes(name: str, data: bytes, content_type="application/octet-stream") -> None:
|
102
|
+
"""
|
103
|
+
Uploads raw bytes as an asset.
|
104
|
+
|
105
|
+
Parameters:
|
106
|
+
name: The name identifier of the asset to upload.
|
107
|
+
data: The raw byte content to upload.
|
108
|
+
content_type: The MIME type of the asset.
|
109
|
+
"""
|
110
|
+
|
111
|
+
storage = _get_storage()
|
112
|
+
storage.set_bytes(name, data, content_type)
|
113
|
+
|
114
|
+
|
115
|
+
def set_file(name: str, path: str, content_type: str = None) -> None:
|
116
|
+
"""
|
117
|
+
Uploads a file as an asset.
|
118
|
+
|
119
|
+
Parameters:
|
120
|
+
name: The name identifier of the asset to upload.
|
121
|
+
path: The filesystem path of the file to upload.
|
122
|
+
content_type: The MIME type of the asset.
|
123
|
+
"""
|
124
|
+
|
125
|
+
content = Path(path).read_bytes()
|
126
|
+
if content_type is None:
|
127
|
+
content_type, _ = mimetypes.guess_type(path)
|
128
|
+
if content_type is None:
|
129
|
+
content_type = "application/octet-stream"
|
130
|
+
|
131
|
+
storage = _get_storage()
|
132
|
+
storage.set_bytes(name, content, content_type)
|
133
|
+
|
134
|
+
|
135
|
+
def set_json(name: str, value: JSON, **kwargs) -> None:
|
136
|
+
"""
|
137
|
+
Uploads JSON data as an asset.
|
138
|
+
|
139
|
+
Parameters:
|
140
|
+
name: The name identifier of the asset to upload.
|
141
|
+
value: The JSON data to upload.
|
142
|
+
kwargs: Additional keyword arguments to pass to json.dumps.
|
143
|
+
"""
|
144
|
+
|
145
|
+
content = json.dumps(value, **kwargs).encode("utf-8")
|
146
|
+
content_type = "application/json"
|
147
|
+
|
148
|
+
storage = _get_storage()
|
149
|
+
storage.set_bytes(name, content, content_type)
|
150
|
+
|
151
|
+
|
152
|
+
def set_text(name: str, text: str) -> None:
|
153
|
+
"""
|
154
|
+
Uploads text content as an asset.
|
155
|
+
|
156
|
+
Parameters:
|
157
|
+
name: The name identifier of the asset to upload.
|
158
|
+
text: The text content to upload.
|
159
|
+
"""
|
160
|
+
|
161
|
+
content = text.encode("utf-8")
|
162
|
+
content_type = "text/plain"
|
163
|
+
|
164
|
+
storage = _get_storage()
|
165
|
+
storage.set_bytes(name, content, content_type)
|
166
|
+
|
167
|
+
|
75
168
|
__all__ = [
|
76
|
-
"
|
169
|
+
"list_assets",
|
170
|
+
"delete_asset",
|
77
171
|
"get_bytes",
|
78
172
|
"get_file",
|
79
173
|
"get_json",
|
80
174
|
"get_text",
|
175
|
+
"set_bytes",
|
176
|
+
"set_file",
|
177
|
+
"set_json",
|
178
|
+
"set_text",
|
81
179
|
]
|
automizor/storage/_storage.py
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
-
import os
|
2
1
|
from typing import Dict, List, Union
|
3
2
|
|
4
3
|
import requests
|
5
4
|
|
6
|
-
from .
|
5
|
+
from automizor.exceptions import AutomizorError, NotFound
|
6
|
+
from automizor.utils import get_api_config, get_headers
|
7
7
|
|
8
8
|
JSON = Union[str, int, float, bool, None, Dict[str, "JSON"], List["JSON"]]
|
9
9
|
|
@@ -23,8 +23,7 @@ class Storage:
|
|
23
23
|
To use this class effectively, ensure that the following environment variables are
|
24
24
|
set in your environment:
|
25
25
|
|
26
|
-
- ``
|
27
|
-
- ``AUTOMIZOR_API_TOKEN``: Provides the token required for API authentication.
|
26
|
+
- ``AUTOMIZOR_AGENT_TOKEN``: The token for authenticating against the `Automizor API`.
|
28
27
|
|
29
28
|
Example usage:
|
30
29
|
|
@@ -32,30 +31,82 @@ class Storage:
|
|
32
31
|
|
33
32
|
from automizor import storage
|
34
33
|
|
35
|
-
# To
|
36
|
-
|
34
|
+
# To list all assets
|
35
|
+
asset_names = storage.list_assets()
|
37
36
|
|
38
|
-
# To
|
39
|
-
|
37
|
+
# To delete an asset
|
38
|
+
storage.delete_asset("AssetName")
|
40
39
|
|
41
|
-
#
|
42
|
-
|
40
|
+
# Save an asset
|
41
|
+
storage.set_bytes("AssetName", b"Hello, World!")
|
42
|
+
storage.set_file("AssetName", "/path/to/file")
|
43
|
+
storage.set_json("AssetName", {"key": "value"})
|
44
|
+
storage.set_text("AssetName", "Hello, World!")
|
43
45
|
|
44
|
-
#
|
45
|
-
|
46
|
+
# Get an asset
|
47
|
+
bytes_data = storage.get_bytes("AssetName")
|
48
|
+
file_path = storage.get_file("AssetName", "/path/to/save/file")
|
49
|
+
json_data = storage.get_json("AssetName")
|
50
|
+
text_data = storage.get_text("AssetName")
|
46
51
|
"""
|
47
52
|
|
48
53
|
def __init__(self):
|
49
|
-
self.
|
50
|
-
self._api_token = os.getenv("AUTOMIZOR_API_TOKEN")
|
51
|
-
|
54
|
+
self.url, self.token = get_api_config()
|
52
55
|
self.session = requests.Session()
|
53
|
-
self.session.headers.update(
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
56
|
+
self.session.headers.update(get_headers(self.token))
|
57
|
+
|
58
|
+
def list_assets(self) -> List[str]:
|
59
|
+
"""
|
60
|
+
Retrieves a list of all asset names.
|
61
|
+
|
62
|
+
This function fetches the names of all assets stored in the storage service,
|
63
|
+
providing a convenient way to list and identify the available assets.
|
64
|
+
|
65
|
+
Returns:
|
66
|
+
A list of all asset names.
|
67
|
+
"""
|
68
|
+
url = f"https://{self.url}/api/v1/storage/asset/"
|
69
|
+
asset_names = []
|
70
|
+
|
71
|
+
try:
|
72
|
+
while url:
|
73
|
+
response = self.session.get(url, timeout=10)
|
74
|
+
response.raise_for_status()
|
75
|
+
data = response.json()
|
76
|
+
|
77
|
+
for asset in data["results"]:
|
78
|
+
asset_names.append(asset["name"])
|
79
|
+
url = data["next"]
|
80
|
+
except requests.HTTPError as exc:
|
81
|
+
raise AutomizorError.from_response(
|
82
|
+
exc.response, "Failed to list assets"
|
83
|
+
) from exc
|
84
|
+
except Exception as exc:
|
85
|
+
raise AutomizorError("Failed to list assets") from exc
|
86
|
+
return asset_names
|
87
|
+
|
88
|
+
def delete_asset(self, name: str) -> None:
|
89
|
+
"""
|
90
|
+
Deletes the specified asset.
|
91
|
+
|
92
|
+
This function deletes the asset identified by `name` from the storage service.
|
93
|
+
It is useful for removing assets that are no longer needed or should be cleaned
|
94
|
+
up to free up storage space.
|
95
|
+
|
96
|
+
Parameters:
|
97
|
+
name: The name identifier of the asset to delete.
|
98
|
+
"""
|
99
|
+
|
100
|
+
url = f"https://{self.url}/api/v1/storage/asset/{name}/"
|
101
|
+
try:
|
102
|
+
response = self.session.delete(url, timeout=10)
|
103
|
+
response.raise_for_status()
|
104
|
+
except requests.HTTPError as exc:
|
105
|
+
raise AutomizorError.from_response(
|
106
|
+
exc.response, "Failed to delete asset"
|
107
|
+
) from exc
|
108
|
+
except Exception as exc:
|
109
|
+
raise AutomizorError("Failed to delete asset") from exc
|
59
110
|
|
60
111
|
def get_bytes(self, name: str) -> bytes:
|
61
112
|
"""
|
@@ -72,8 +123,7 @@ class Storage:
|
|
72
123
|
The raw byte content of the asset.
|
73
124
|
"""
|
74
125
|
|
75
|
-
|
76
|
-
return self._download_file(url, mode="content")
|
126
|
+
return self._download_file(name, mode="content")
|
77
127
|
|
78
128
|
def get_file(self, name: str, path: str) -> str:
|
79
129
|
"""
|
@@ -92,8 +142,7 @@ class Storage:
|
|
92
142
|
The path to the saved file, confirming the operation's success.
|
93
143
|
"""
|
94
144
|
|
95
|
-
|
96
|
-
content = self._download_file(url, mode="content")
|
145
|
+
content = self._download_file(name, mode="content")
|
97
146
|
with open(path, "wb") as file:
|
98
147
|
file.write(content)
|
99
148
|
return path
|
@@ -113,8 +162,7 @@ class Storage:
|
|
113
162
|
The parsed JSON data, which can be a dict, list, or primitive data type.
|
114
163
|
"""
|
115
164
|
|
116
|
-
|
117
|
-
return self._download_file(url, mode="json")
|
165
|
+
return self._download_file(name, mode="json")
|
118
166
|
|
119
167
|
def get_text(self, name: str) -> str:
|
120
168
|
"""
|
@@ -131,13 +179,62 @@ class Storage:
|
|
131
179
|
The content of the asset as a text string.
|
132
180
|
"""
|
133
181
|
|
182
|
+
return self._download_file(name, mode="text")
|
183
|
+
|
184
|
+
def set_bytes(self, name: str, content: bytes, content_type: str) -> None:
|
185
|
+
"""
|
186
|
+
Uploads the specified content as a new asset.
|
187
|
+
|
188
|
+
This function uploads the provided `content` as a new asset with the specified
|
189
|
+
`name`. It is useful for creating new assets or updating existing ones with
|
190
|
+
fresh content.
|
191
|
+
|
192
|
+
Parameters:
|
193
|
+
name: The name identifier of the asset to create.
|
194
|
+
content: The raw byte content of the asset.
|
195
|
+
content_type: The MIME type of the asset content.
|
196
|
+
"""
|
197
|
+
|
198
|
+
try:
|
199
|
+
self._update_asset(name, content, content_type)
|
200
|
+
except NotFound:
|
201
|
+
self._create_asset(name, content, content_type)
|
202
|
+
|
203
|
+
def _create_asset(self, name: str, content: bytes, content_type: str) -> None:
|
204
|
+
"""
|
205
|
+
Creates a new asset with the specified content.
|
206
|
+
|
207
|
+
This function creates a new asset with the specified `name` and `content` in the
|
208
|
+
storage service. It is useful for uploading new assets or updating existing ones
|
209
|
+
with fresh content.
|
210
|
+
|
211
|
+
Parameters:
|
212
|
+
name: The name identifier of the asset to create.
|
213
|
+
content: The raw byte content of the asset.
|
214
|
+
content_type: The MIME type of the asset content.
|
215
|
+
"""
|
216
|
+
|
217
|
+
url = f"https://{self.url}/api/v1/storage/asset/"
|
218
|
+
try:
|
219
|
+
data = {
|
220
|
+
"content_type": content_type,
|
221
|
+
"name": name,
|
222
|
+
}
|
223
|
+
files = {"file": ("text.txt", content, content_type)}
|
224
|
+
response = self.session.post(url, files=files, data=data, timeout=10)
|
225
|
+
response.raise_for_status()
|
226
|
+
except requests.HTTPError as exc:
|
227
|
+
raise AutomizorError.from_response(
|
228
|
+
exc.response, "Failed to create asset"
|
229
|
+
) from exc
|
230
|
+
except Exception as exc:
|
231
|
+
raise AutomizorError("Failed to create asset") from exc
|
232
|
+
|
233
|
+
def _download_file(self, name: str, mode: str = "content"):
|
134
234
|
url = self._get_asset_url(name)
|
135
|
-
return self._download_file(url, mode="text")
|
136
235
|
|
137
|
-
def _download_file(self, url: str, mode: str = "content"):
|
138
236
|
try:
|
139
|
-
|
140
|
-
response = session.get(url, timeout=10)
|
237
|
+
response = requests.get(url=url, timeout=10)
|
141
238
|
response.raise_for_status()
|
142
239
|
|
143
240
|
match mode:
|
@@ -148,11 +245,15 @@ class Storage:
|
|
148
245
|
case "text":
|
149
246
|
return response.text
|
150
247
|
raise RuntimeError(f"Invalid mode {mode}")
|
248
|
+
except requests.HTTPError as exc:
|
249
|
+
raise AutomizorError.from_response(
|
250
|
+
exc.response, "Failed to download asset"
|
251
|
+
) from exc
|
151
252
|
except Exception as exc:
|
152
|
-
raise
|
253
|
+
raise AutomizorError("Failed to download asset") from exc
|
153
254
|
|
154
255
|
def _get_asset_url(self, name: str) -> str:
|
155
|
-
url = f"https://{self.
|
256
|
+
url = f"https://{self.url}/api/v1/storage/asset/{name}/"
|
156
257
|
try:
|
157
258
|
response = self.session.get(url, timeout=10)
|
158
259
|
response.raise_for_status()
|
@@ -161,5 +262,39 @@ class Storage:
|
|
161
262
|
if url:
|
162
263
|
return url
|
163
264
|
raise RuntimeError("Url not found")
|
265
|
+
except requests.HTTPError as exc:
|
266
|
+
raise AutomizorError.from_response(
|
267
|
+
exc.response, "Failed to get asset URL"
|
268
|
+
) from exc
|
269
|
+
except Exception as exc:
|
270
|
+
raise AutomizorError("Failed to get asset URL") from exc
|
271
|
+
|
272
|
+
def _update_asset(self, name: str, content: bytes, content_type: str) -> None:
|
273
|
+
"""
|
274
|
+
Updates the specified asset with new content.
|
275
|
+
|
276
|
+
This function updates the asset identified by `name` with fresh content
|
277
|
+
provided as `content`. It is useful for modifying existing assets without
|
278
|
+
creating a new asset, ensuring that the asset's content is up-to-date.
|
279
|
+
|
280
|
+
Parameters:
|
281
|
+
name: The name identifier of the asset to update.
|
282
|
+
content: The raw byte content of the asset.
|
283
|
+
content_type: The MIME type of the asset content.
|
284
|
+
"""
|
285
|
+
|
286
|
+
url = f"https://{self.url}/api/v1/storage/asset/{name}/"
|
287
|
+
try:
|
288
|
+
data = {
|
289
|
+
"content_type": content_type,
|
290
|
+
"name": name,
|
291
|
+
}
|
292
|
+
files = {"file": ("text.txt", content, content_type)}
|
293
|
+
response = self.session.put(url, files=files, data=data, timeout=10)
|
294
|
+
response.raise_for_status()
|
295
|
+
except requests.HTTPError as exc:
|
296
|
+
raise AutomizorError.from_response(
|
297
|
+
exc.response, "Failed to update asset"
|
298
|
+
) from exc
|
164
299
|
except Exception as exc:
|
165
|
-
raise
|
300
|
+
raise AutomizorError("Failed to update asset") from exc
|
@@ -0,0 +1,31 @@
|
|
1
|
+
import os
|
2
|
+
import platform
|
3
|
+
|
4
|
+
from automizor import version
|
5
|
+
from automizor.exceptions import AutomizorError
|
6
|
+
|
7
|
+
OS_SYSTEM, OS_RELEASE, _ = platform.system_alias(
|
8
|
+
platform.system(), platform.release(), platform.version()
|
9
|
+
)
|
10
|
+
|
11
|
+
|
12
|
+
def get_api_config() -> tuple[str, str]:
|
13
|
+
token_string = os.getenv("AUTOMIZOR_AGENT_TOKEN")
|
14
|
+
|
15
|
+
if not token_string:
|
16
|
+
raise AutomizorError("AUTOMIZOR_AGENT_TOKEN is not set.")
|
17
|
+
|
18
|
+
try:
|
19
|
+
token, url = token_string.strip().split("@")
|
20
|
+
except ValueError as exc:
|
21
|
+
raise AutomizorError(
|
22
|
+
"AUTOMIZOR_AGENT_TOKEN is not in the correct format."
|
23
|
+
) from exc
|
24
|
+
return url, token
|
25
|
+
|
26
|
+
|
27
|
+
def get_headers(token: str) -> dict:
|
28
|
+
return {
|
29
|
+
"Authorization": f"Token {token}",
|
30
|
+
"User-Agent": f"Automizor/{version} {OS_SYSTEM}/{OS_RELEASE}",
|
31
|
+
}
|
automizor/vault/__init__.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
from functools import lru_cache
|
2
|
+
from typing import Any, Dict
|
2
3
|
|
3
4
|
from ._container import SecretContainer
|
4
|
-
from ._exceptions import AutomizorVaultError
|
5
5
|
|
6
6
|
|
7
7
|
@lru_cache
|
@@ -11,10 +11,40 @@ def _get_vault():
|
|
11
11
|
return Vault()
|
12
12
|
|
13
13
|
|
14
|
+
def create_secret(
|
15
|
+
name: str,
|
16
|
+
value: Dict[str, Any],
|
17
|
+
description: str = "",
|
18
|
+
) -> SecretContainer:
|
19
|
+
"""
|
20
|
+
Creates a new secret. Stores the secret in the `Automizor API`.
|
21
|
+
If the secret already exists, it will be updated.
|
22
|
+
|
23
|
+
Args:
|
24
|
+
name: The name of the secret.
|
25
|
+
value: The value of the secret.
|
26
|
+
description: The description of the secret.
|
27
|
+
|
28
|
+
Returns:
|
29
|
+
The created secret.
|
30
|
+
|
31
|
+
Raises:
|
32
|
+
AutomizorVaultError: If creating the secret fails.
|
33
|
+
"""
|
34
|
+
|
35
|
+
secret = SecretContainer(
|
36
|
+
name=name,
|
37
|
+
description=description,
|
38
|
+
value=value,
|
39
|
+
)
|
40
|
+
|
41
|
+
vault = _get_vault()
|
42
|
+
return vault.create_secret(secret)
|
43
|
+
|
44
|
+
|
14
45
|
def get_secret(name: str) -> SecretContainer:
|
15
46
|
"""
|
16
|
-
Retrieves a secret by its name. Fetches from
|
17
|
-
`Automizor API`, based on configuration.
|
47
|
+
Retrieves a secret by its name. Fetches from the `Automizor API`.
|
18
48
|
|
19
49
|
Args:
|
20
50
|
name: The name of the secret to retrieve.
|
@@ -32,8 +62,7 @@ def get_secret(name: str) -> SecretContainer:
|
|
32
62
|
|
33
63
|
def set_secret(secret: SecretContainer) -> SecretContainer:
|
34
64
|
"""
|
35
|
-
Updates an existing secret. Updates to
|
36
|
-
`Automizor API`, based on configuration.
|
65
|
+
Updates an existing secret. Updates to the `Automizor API`.
|
37
66
|
|
38
67
|
Args:
|
39
68
|
secret: The secret to update.
|
@@ -50,8 +79,8 @@ def set_secret(secret: SecretContainer) -> SecretContainer:
|
|
50
79
|
|
51
80
|
|
52
81
|
__all__ = [
|
53
|
-
"AutomizorVaultError",
|
54
82
|
"SecretContainer",
|
83
|
+
"create_secret",
|
55
84
|
"get_secret",
|
56
85
|
"set_secret",
|
57
86
|
]
|
automizor/vault/_container.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
from dataclasses import dataclass, fields
|
2
|
+
from typing import Any, Dict
|
2
3
|
|
3
4
|
|
4
5
|
def ignore_extra_fields(cls):
|
@@ -29,7 +30,7 @@ class SecretContainer:
|
|
29
30
|
"""
|
30
31
|
|
31
32
|
name: str
|
32
|
-
value:
|
33
|
+
value: Dict[str, Any]
|
33
34
|
description: str = ""
|
34
35
|
|
35
36
|
def get(self, key, default=None):
|
@@ -44,7 +45,7 @@ class SecretContainer:
|
|
44
45
|
"""Secret.keys() -> a set-like object providing a view on secret's keys."""
|
45
46
|
return self.value.keys()
|
46
47
|
|
47
|
-
def update(self, pairs:
|
48
|
+
def update(self, pairs: Dict[str, Any]) -> None:
|
48
49
|
"""Update the secret's dictionary with the key-value pairs from pairs."""
|
49
50
|
self.value.update(pairs)
|
50
51
|
|
automizor/vault/_vault.py
CHANGED
@@ -1,42 +1,26 @@
|
|
1
|
-
import json
|
2
|
-
import os
|
3
1
|
from dataclasses import asdict
|
4
2
|
|
5
3
|
import requests
|
6
4
|
|
5
|
+
from automizor.exceptions import AutomizorError, NotFound
|
6
|
+
from automizor.utils import get_api_config, get_headers
|
7
|
+
|
7
8
|
from ._container import SecretContainer
|
8
|
-
from ._exceptions import AutomizorVaultError
|
9
9
|
|
10
10
|
|
11
11
|
class Vault:
|
12
12
|
"""
|
13
13
|
`Vault` is a secure storage class within the `Automizor Platform` for managing
|
14
14
|
secrets such as API keys, passwords, and other sensitive information. It offers
|
15
|
-
functionality to securely retrieve and update secrets
|
16
|
-
|
17
|
-
the operational environment and configuration.
|
15
|
+
functionality to securely retrieve and update secrets through direct interaction
|
16
|
+
with the `Automizor API`.
|
18
17
|
|
19
18
|
Configuration for accessing and manipulating these secrets is driven by environment
|
20
19
|
variables, which are essential for specifying the API's host and token for
|
21
|
-
authentication purposes
|
22
|
-
local storage be preferred over API interaction.
|
20
|
+
authentication purposes.
|
23
21
|
|
24
22
|
Environment variables requisite for operation include:
|
25
|
-
- ``
|
26
|
-
- ``AUTOMIZOR_API_TOKEN``: The token for authenticate against the `Automizor API`.
|
27
|
-
- ``AUTOMIZOR_SECRET_FILE``: Optionally specifies the path to a local file where
|
28
|
-
secrets are stored, enabling operations in environments where direct API access
|
29
|
-
may be restricted or unavailable.
|
30
|
-
|
31
|
-
Example of a local secret file:
|
32
|
-
|
33
|
-
.. code-block:: json
|
34
|
-
|
35
|
-
{
|
36
|
-
"my_secret_name": {
|
37
|
-
"key": "value"
|
38
|
-
}
|
39
|
-
}
|
23
|
+
- ``AUTOMIZOR_AGENT_TOKEN``: The token for authenticating against the `Automizor API`.
|
40
24
|
|
41
25
|
Example usage:
|
42
26
|
|
@@ -44,33 +28,48 @@ class Vault:
|
|
44
28
|
|
45
29
|
from automizor import vault
|
46
30
|
|
31
|
+
# Create a new secret
|
32
|
+
vault.create_secret(name="MySecret", value={"username": "admin", "password": "*****"})
|
33
|
+
|
47
34
|
# Retrieve a secret by its name
|
48
|
-
secret = vault.get_secret("
|
49
|
-
print(secret
|
35
|
+
secret = vault.get_secret("MySecret")
|
36
|
+
print(secret.get("username")) # Output: "admin"
|
37
|
+
print(secret.get("password")) # Output: "*****"
|
50
38
|
|
51
39
|
# Update a existing secret
|
52
|
-
secret = vault.get_secret("
|
53
|
-
secret
|
40
|
+
secret = vault.get_secret("MySecret")
|
41
|
+
secret.update({"username": "user"})
|
54
42
|
vault.set_secret(secret)
|
55
43
|
"""
|
56
44
|
|
57
45
|
def __init__(self):
|
58
|
-
self.
|
59
|
-
self._api_token = os.getenv("AUTOMIZOR_API_TOKEN")
|
60
|
-
self._secret_file = os.getenv("AUTOMIZOR_SECRET_FILE")
|
61
|
-
|
46
|
+
self.url, self.token = get_api_config()
|
62
47
|
self.session = requests.Session()
|
63
|
-
self.session.headers.update(
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
48
|
+
self.session.headers.update(get_headers(self.token))
|
49
|
+
|
50
|
+
def create_secret(self, secret: SecretContainer) -> SecretContainer:
|
51
|
+
"""
|
52
|
+
Creates a new secret. Stores the secret in the `Automizor API`.
|
53
|
+
If the secret already exists, it will be updated.
|
54
|
+
|
55
|
+
Args:
|
56
|
+
secret: The secret to create.
|
57
|
+
|
58
|
+
Returns:
|
59
|
+
The created secret.
|
60
|
+
|
61
|
+
Raises:
|
62
|
+
AutomizorVaultError: If creating the secret fails.
|
63
|
+
"""
|
64
|
+
|
65
|
+
try:
|
66
|
+
return self._update_secret(secret)
|
67
|
+
except NotFound:
|
68
|
+
return self._create_secret(secret)
|
69
69
|
|
70
70
|
def get_secret(self, name) -> SecretContainer:
|
71
71
|
"""
|
72
|
-
Retrieves a secret by its name. Fetches from
|
73
|
-
`Automizor API`, based on configuration.
|
72
|
+
Retrieves a secret by its name. Fetches from the `Automizor API`.
|
74
73
|
|
75
74
|
Args:
|
76
75
|
name: The name of the secret to retrieve.
|
@@ -82,14 +81,11 @@ class Vault:
|
|
82
81
|
AutomizorVaultError: If retrieving the secret fails.
|
83
82
|
"""
|
84
83
|
|
85
|
-
|
86
|
-
return self._read_file_secret(name)
|
87
|
-
return self._read_vault_secret(name)
|
84
|
+
return self._get_secret(name)
|
88
85
|
|
89
86
|
def set_secret(self, secret: SecretContainer) -> SecretContainer:
|
90
87
|
"""
|
91
|
-
Updates an existing secret. Updates to
|
92
|
-
`Automizor API`, based on configuration.
|
88
|
+
Updates an existing secret. Updates to the `Automizor API`.
|
93
89
|
|
94
90
|
Args:
|
95
91
|
secret: The secret to update.
|
@@ -101,39 +97,43 @@ class Vault:
|
|
101
97
|
AutomizorVaultError: If updating the secret fails.
|
102
98
|
"""
|
103
99
|
|
104
|
-
|
105
|
-
return self._write_file_secret(secret)
|
106
|
-
return self._write_vault_secret(secret)
|
100
|
+
return self._update_secret(secret)
|
107
101
|
|
108
|
-
def
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
102
|
+
def _create_secret(self, secret: SecretContainer) -> SecretContainer:
|
103
|
+
url = f"https://{self.url}/api/v1/vault/secret/"
|
104
|
+
try:
|
105
|
+
response = self.session.post(url, timeout=10, json=asdict(secret))
|
106
|
+
response.raise_for_status()
|
107
|
+
return SecretContainer(**response.json())
|
108
|
+
except requests.HTTPError as exc:
|
109
|
+
raise AutomizorError.from_response(
|
110
|
+
exc.response, "Failed to create secret"
|
111
|
+
) from exc
|
112
|
+
except Exception as exc:
|
113
|
+
raise AutomizorError("Failed to create secret") from exc
|
113
114
|
|
114
|
-
def
|
115
|
-
url = f"https://{self.
|
115
|
+
def _get_secret(self, name: str) -> SecretContainer:
|
116
|
+
url = f"https://{self.url}/api/v1/vault/secret/{name}/"
|
116
117
|
try:
|
117
118
|
response = self.session.get(url, timeout=10)
|
118
119
|
response.raise_for_status()
|
119
120
|
return SecretContainer(**response.json())
|
121
|
+
except requests.HTTPError as exc:
|
122
|
+
raise AutomizorError.from_response(
|
123
|
+
exc.response, "Failed to get secret"
|
124
|
+
) from exc
|
120
125
|
except Exception as exc:
|
121
|
-
raise
|
122
|
-
|
123
|
-
def
|
124
|
-
|
125
|
-
secrets = json.load(file)
|
126
|
-
secrets[secret.name] = secret.value
|
127
|
-
file.seek(0)
|
128
|
-
file.write(json.dumps(secrets, indent=4))
|
129
|
-
file.truncate()
|
130
|
-
return secret
|
131
|
-
|
132
|
-
def _write_vault_secret(self, secret: SecretContainer) -> SecretContainer:
|
133
|
-
url = f"https://{self._api_host}/api/v1/vault/secret/{secret.name}/"
|
126
|
+
raise AutomizorError("Failed to get secret") from exc
|
127
|
+
|
128
|
+
def _update_secret(self, secret: SecretContainer) -> SecretContainer:
|
129
|
+
url = f"https://{self.url}/api/v1/vault/secret/{secret.name}/"
|
134
130
|
try:
|
135
131
|
response = self.session.put(url, timeout=10, json=asdict(secret))
|
136
132
|
response.raise_for_status()
|
137
133
|
return SecretContainer(**response.json())
|
134
|
+
except requests.HTTPError as exc:
|
135
|
+
raise AutomizorError.from_response(
|
136
|
+
exc.response, "Failed to update secret"
|
137
|
+
) from exc
|
138
138
|
except Exception as exc:
|
139
|
-
raise
|
139
|
+
raise AutomizorError("Failed to update secret") from exc
|
@@ -0,0 +1,15 @@
|
|
1
|
+
automizor/__init__.py,sha256=yhiWOz0HoJGRRI9-JQ2eh_0AbByy-6psK08-kpTSHJw,18
|
2
|
+
automizor/exceptions.py,sha256=P5imySIOtG3ZIk2kh41Yod4RnlgTj7Vf0P3M-RuxQJs,1382
|
3
|
+
automizor/job/__init__.py,sha256=DNRuT6cyPQBaPRG4vNalCmEvcJQl-73b5ZDFOfNOwIg,1019
|
4
|
+
automizor/job/_job.py,sha256=62T_eMEqsJlxhlQ8_3nupTvvwsEysgx5JZAz7ui1WLo,5245
|
5
|
+
automizor/storage/__init__.py,sha256=DUVVvetBgsrF69mfiM9iu1PvSBjFPLxrgH1o18mvReA,4000
|
6
|
+
automizor/storage/_storage.py,sha256=rKWxaCA_kFr5cW_RrHqL4uEVzNqdWjTQAOXFTfIxlfY,11021
|
7
|
+
automizor/utils/__init__.py,sha256=2trRoR5lljYKbYdxmioSlvzajjQM0Wnoq3bvF9lEZ6w,811
|
8
|
+
automizor/vault/__init__.py,sha256=UjRiW3J0R9ABXc1gXIPyS3cqNCwMWxx0l-C0PsIg7R0,1699
|
9
|
+
automizor/vault/_container.py,sha256=TXwNhjK-qrfu_5iDJm1J0Kkuyq22dLdES1DqjAAKnWA,1953
|
10
|
+
automizor/vault/_vault.py,sha256=uRsjOjzsstZpYwJoHNg_cpv803Dzo4T2oF6hwiG3Eww,4688
|
11
|
+
automizor-0.4.0.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
12
|
+
automizor-0.4.0.dist-info/METADATA,sha256=Ky_rwJrB2JUutCKAYo9Rzkwo_j6UPyQaS8HRORjr6O8,661
|
13
|
+
automizor-0.4.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
14
|
+
automizor-0.4.0.dist-info/top_level.txt,sha256=gScDy4I3tP6BMYAsTAlBXrxVh3E00zV0UioxwXJOI3Y,10
|
15
|
+
automizor-0.4.0.dist-info/RECORD,,
|
automizor/job/_exceptions.py
DELETED
automizor/job.py
DELETED
@@ -1,132 +0,0 @@
|
|
1
|
-
import json
|
2
|
-
import os
|
3
|
-
from typing import Any, Dict, List, Union
|
4
|
-
|
5
|
-
import requests
|
6
|
-
|
7
|
-
JSONType = Union[str, int, float, bool, None, Dict[str, Any], List[Any]]
|
8
|
-
|
9
|
-
|
10
|
-
class AutomizorJobError(RuntimeError):
|
11
|
-
"""Exception raised for errors encountered while interacting with the Job."""
|
12
|
-
|
13
|
-
|
14
|
-
class Job:
|
15
|
-
"""
|
16
|
-
Represents a job in the `Automizor Platform`, managing the retrieval and storage of job
|
17
|
-
context and results.
|
18
|
-
|
19
|
-
This class provides functionality to interact with the `Automizor API` or local files to
|
20
|
-
obtain job context and persist job results.
|
21
|
-
|
22
|
-
For testing purposes, you may want to set the environment variables in your local environment.
|
23
|
-
The required variables are:
|
24
|
-
|
25
|
-
- ``AUTOMIZOR_API_HOST``: The host URL of the `Automizor API`
|
26
|
-
- ``AUTOMIZOR_API_TOKEN``: The token used for authenticating with the `Automizor API`
|
27
|
-
- ``AUTOMIZOR_JOB_ID``: The ID of the job from which to retrieve context
|
28
|
-
|
29
|
-
Additionally, you can specify a local context file; in this case, you don't need to set the
|
30
|
-
above environment variables:
|
31
|
-
|
32
|
-
- ``AUTOMIZOR_CONTEXT_FILE``: The path to a local file containing the job context.
|
33
|
-
|
34
|
-
Example of a local context file:
|
35
|
-
|
36
|
-
.. code-block:: json
|
37
|
-
|
38
|
-
{
|
39
|
-
"key": "value"
|
40
|
-
}
|
41
|
-
|
42
|
-
Example usage:
|
43
|
-
|
44
|
-
.. code-block:: python
|
45
|
-
|
46
|
-
from automizor.job import Job
|
47
|
-
|
48
|
-
job = Job()
|
49
|
-
|
50
|
-
def read_context():
|
51
|
-
context = job.get_job_context()
|
52
|
-
print(context["key"]) # Output: "value"
|
53
|
-
|
54
|
-
def save_result():
|
55
|
-
job.set_job_result("result_key", "result_value")
|
56
|
-
|
57
|
-
"""
|
58
|
-
|
59
|
-
def __init__(self):
|
60
|
-
self._api_host = os.getenv("AUTOMIZOR_API_HOST")
|
61
|
-
self._api_token = os.getenv("AUTOMIZOR_API_TOKEN")
|
62
|
-
self._context_file = os.getenv("AUTOMIZOR_CONTEXT_FILE")
|
63
|
-
self._job_id = os.getenv("AUTOMIZOR_JOB_ID")
|
64
|
-
|
65
|
-
@property
|
66
|
-
def headers(self) -> dict:
|
67
|
-
"""Headers for API requests, including Authorization and Content-Type."""
|
68
|
-
return {
|
69
|
-
"Authorization": f"Token {self._api_token}",
|
70
|
-
"Content-Type": "application/json",
|
71
|
-
}
|
72
|
-
|
73
|
-
def get_job_context(self) -> dict:
|
74
|
-
"""
|
75
|
-
Retrieves the job's context from either a local file or via an API call, based on
|
76
|
-
the configuration.
|
77
|
-
|
78
|
-
If a local context file is specified (via `AUTOMIZOR_CONTEXT_FILE`), it reads the context
|
79
|
-
from the file. Otherwise, it fetches the context from the Automizor API using the job ID
|
80
|
-
and API credentials.
|
81
|
-
|
82
|
-
Returns:
|
83
|
-
A dictionary with the job's context.
|
84
|
-
|
85
|
-
Raises:
|
86
|
-
AutomizorJobError: If retrieving the job context fails.
|
87
|
-
"""
|
88
|
-
|
89
|
-
if self._context_file:
|
90
|
-
return self._read_file_context()
|
91
|
-
return self._read_job_context()
|
92
|
-
|
93
|
-
def set_job_result(self, name: str, value: JSONType):
|
94
|
-
"""
|
95
|
-
Saves a job result into a JSON file (`output/result.json`).
|
96
|
-
|
97
|
-
Updates the file with the new result, creating or overwriting the file as necessary.
|
98
|
-
If the file exists and contains data, it merges the new result with the existing data.
|
99
|
-
|
100
|
-
Parameters:
|
101
|
-
name (str): The key under which to store the result.
|
102
|
-
value (JSONType): The result value, must be JSON serializable.
|
103
|
-
|
104
|
-
Note: Errors during file operations will raise unhandled exceptions.
|
105
|
-
"""
|
106
|
-
|
107
|
-
data = {}
|
108
|
-
file_path = "output/result.json"
|
109
|
-
try:
|
110
|
-
if os.path.exists(file_path):
|
111
|
-
with open(file_path, "r", encoding="utf-8") as file:
|
112
|
-
data = json.load(file)
|
113
|
-
except json.JSONDecodeError:
|
114
|
-
pass
|
115
|
-
|
116
|
-
data[name] = value
|
117
|
-
|
118
|
-
with open(file_path, "w", encoding="utf-8") as file:
|
119
|
-
json.dump(data, file, ensure_ascii=False, indent=4)
|
120
|
-
|
121
|
-
def _read_file_context(self) -> dict:
|
122
|
-
with open(self._context_file, "r", encoding="utf-8") as file:
|
123
|
-
return json.load(file)
|
124
|
-
|
125
|
-
def _read_job_context(self) -> dict:
|
126
|
-
url = f"https://{self._api_host}/api/v1/rpa/job/{self._job_id}/"
|
127
|
-
try:
|
128
|
-
response = requests.get(url, headers=self.headers, timeout=10)
|
129
|
-
response.raise_for_status()
|
130
|
-
return response.json().get("context", {})
|
131
|
-
except Exception as exc:
|
132
|
-
raise AutomizorJobError(f"Failed to get job context: {exc}") from exc
|
automizor/storage/_exceptions.py
DELETED
automizor/storage.py
DELETED
File without changes
|
automizor/vault/_exceptions.py
DELETED
automizor/vault/_secret.py
DELETED
@@ -1,45 +0,0 @@
|
|
1
|
-
from dataclasses import dataclass
|
2
|
-
|
3
|
-
|
4
|
-
@dataclass
|
5
|
-
class Secret:
|
6
|
-
"""
|
7
|
-
Represents a secret, comprising a name and its associated values.
|
8
|
-
|
9
|
-
Attributes:
|
10
|
-
name: The name of the secret.
|
11
|
-
value: The secret's values, stored in a dictionary as key-value pairs.
|
12
|
-
"""
|
13
|
-
|
14
|
-
name: str
|
15
|
-
value: dict
|
16
|
-
|
17
|
-
def get(self, key, default=None):
|
18
|
-
"""Return the value for key if key is in the dictionary, else default."""
|
19
|
-
return self.value.get(key, default)
|
20
|
-
|
21
|
-
def items(self):
|
22
|
-
"""secret.items() -> a set-like object providing a view on secret's items."""
|
23
|
-
return self.value.items()
|
24
|
-
|
25
|
-
def update(self, pairs: dict) -> None:
|
26
|
-
self.value.update(pairs)
|
27
|
-
|
28
|
-
def __getitem__(self, key):
|
29
|
-
return self.value[key]
|
30
|
-
|
31
|
-
def __setitem__(self, key, value):
|
32
|
-
self.value[key] = value
|
33
|
-
|
34
|
-
def __contains__(self, key):
|
35
|
-
return key in self.value
|
36
|
-
|
37
|
-
def __iter__(self):
|
38
|
-
return iter(self.value)
|
39
|
-
|
40
|
-
def __len__(self):
|
41
|
-
return len(self.value)
|
42
|
-
|
43
|
-
def __repr__(self):
|
44
|
-
keys = ", ".join(self.value.keys())
|
45
|
-
return f"Secret(name={self.name}, keys={keys})"
|
automizor/vault.py
DELETED
@@ -1,190 +0,0 @@
|
|
1
|
-
import json
|
2
|
-
import os
|
3
|
-
from dataclasses import asdict, dataclass
|
4
|
-
|
5
|
-
import requests
|
6
|
-
|
7
|
-
|
8
|
-
class AutomizorVaultError(RuntimeError):
|
9
|
-
"""Exception raised for errors encountered while interacting with the Vault."""
|
10
|
-
|
11
|
-
|
12
|
-
@dataclass
|
13
|
-
class Secret:
|
14
|
-
"""
|
15
|
-
Represents a secret, comprising a name and its associated values.
|
16
|
-
|
17
|
-
Attributes:
|
18
|
-
name (str): The name of the secret.
|
19
|
-
value (dict): The secret's values, stored in a dictionary as key-value pairs.
|
20
|
-
"""
|
21
|
-
|
22
|
-
name: str
|
23
|
-
value: dict
|
24
|
-
|
25
|
-
def get(self, key, default=None):
|
26
|
-
"""Return the value for key if key is in the dictionary, else default."""
|
27
|
-
return self.value.get(key, default)
|
28
|
-
|
29
|
-
def items(self):
|
30
|
-
"""secret.items() -> a set-like object providing a view on secret's items."""
|
31
|
-
return self.value.items()
|
32
|
-
|
33
|
-
def update(self, pairs: dict) -> None:
|
34
|
-
self.value.update(pairs)
|
35
|
-
|
36
|
-
def __getitem__(self, key):
|
37
|
-
return self.value[key]
|
38
|
-
|
39
|
-
def __setitem__(self, key, value):
|
40
|
-
self.value[key] = value
|
41
|
-
|
42
|
-
def __contains__(self, key):
|
43
|
-
return key in self.value
|
44
|
-
|
45
|
-
def __iter__(self):
|
46
|
-
return iter(self.value)
|
47
|
-
|
48
|
-
def __len__(self):
|
49
|
-
return len(self.value)
|
50
|
-
|
51
|
-
def __repr__(self):
|
52
|
-
keys = ", ".join(self.value.keys())
|
53
|
-
return f"Secret(name={self.name}, keys={keys})"
|
54
|
-
|
55
|
-
|
56
|
-
class Vault:
|
57
|
-
"""
|
58
|
-
`Vault` is a library to manage secrets within an the `Automizor Platform`,
|
59
|
-
providing functionality to retrieve and update secrets. It supports interaction
|
60
|
-
with the `Vault API` (by default) or a local file for secret storage, determined
|
61
|
-
by environment variable configuration.
|
62
|
-
|
63
|
-
The Vault class uses environment variables to configure the API host, API token,
|
64
|
-
which are set by the `Automizor Agent`.
|
65
|
-
|
66
|
-
You may want to set the environment variables in your local environment for testing
|
67
|
-
purposes. The variables which must exist are:
|
68
|
-
|
69
|
-
- ``AUTOMIZOR_API_HOST``: The host URL of the `Automizor API`
|
70
|
-
- ``AUTOMIZOR_API_TOKEN``: The token used for authenticating with the `Automizor API`
|
71
|
-
|
72
|
-
In addition, you can set the following environment variable to use a local file for
|
73
|
-
secret storage:
|
74
|
-
|
75
|
-
- ``AUTOMIZOR_SECRET_FILE``: The path to a local file where secrets are stored.
|
76
|
-
|
77
|
-
Example of a local secret file:
|
78
|
-
|
79
|
-
.. code-block:: json
|
80
|
-
|
81
|
-
{
|
82
|
-
"my_secret_name": {
|
83
|
-
"key": "value"
|
84
|
-
}
|
85
|
-
}
|
86
|
-
|
87
|
-
Example usage:
|
88
|
-
|
89
|
-
.. code-block:: python
|
90
|
-
|
91
|
-
from automizor.vault import Vault
|
92
|
-
|
93
|
-
vault = Vault()
|
94
|
-
|
95
|
-
def read_secret():
|
96
|
-
secret = vault.get_secret("my_secret_name")
|
97
|
-
print(secret["key"]) # Output: "value"
|
98
|
-
|
99
|
-
def update_secret():
|
100
|
-
secret = vault.get_secret("my_secret_name")
|
101
|
-
secret["new_key"] = "new_value"
|
102
|
-
vault.set_secret(secret)
|
103
|
-
|
104
|
-
"""
|
105
|
-
|
106
|
-
def __init__(self):
|
107
|
-
self._api_host = os.getenv("AUTOMIZOR_API_HOST")
|
108
|
-
self._api_token = os.getenv("AUTOMIZOR_API_TOKEN")
|
109
|
-
self._secret_file = os.getenv("AUTOMIZOR_SECRET_FILE")
|
110
|
-
|
111
|
-
@property
|
112
|
-
def headers(self) -> dict:
|
113
|
-
"""Headers for API requests, including Authorization and Content-Type."""
|
114
|
-
return {
|
115
|
-
"Authorization": f"Token {self._api_token}",
|
116
|
-
"Content-Type": "application/json",
|
117
|
-
}
|
118
|
-
|
119
|
-
def get_secret(self, name) -> Secret:
|
120
|
-
"""
|
121
|
-
Retrieves a secret by its name. Fetches from a local file or queries the
|
122
|
-
`Automizor API`, based on configuration.
|
123
|
-
|
124
|
-
Args:
|
125
|
-
name (str): The name of the secret to retrieve.
|
126
|
-
|
127
|
-
Returns:
|
128
|
-
Secret: The retrieved secret.
|
129
|
-
|
130
|
-
Raises:
|
131
|
-
AutomizorVaultError: If retrieving the secret fails.
|
132
|
-
"""
|
133
|
-
|
134
|
-
if self._secret_file:
|
135
|
-
return self._read_file_secret(name)
|
136
|
-
return self._read_vault_secret(name)
|
137
|
-
|
138
|
-
def set_secret(self, secret: Secret) -> Secret:
|
139
|
-
"""
|
140
|
-
Updates a secret. Writes to a local file or sends to the `Automizor API`,
|
141
|
-
based on configuration.
|
142
|
-
|
143
|
-
Args:
|
144
|
-
secret (Secret): The secret to update.
|
145
|
-
|
146
|
-
Returns:
|
147
|
-
Secret: The updated secret.
|
148
|
-
|
149
|
-
Raises:
|
150
|
-
AutomizorVaultError: If updating the secret fails.
|
151
|
-
"""
|
152
|
-
|
153
|
-
if self._secret_file:
|
154
|
-
return self._write_file_secret(secret)
|
155
|
-
return self._write_vault_secret(secret)
|
156
|
-
|
157
|
-
def _read_file_secret(self, name: str) -> Secret:
|
158
|
-
with open(self._secret_file, "r", encoding="utf-8") as file:
|
159
|
-
secrets = json.load(file)
|
160
|
-
value = secrets.get(name, {})
|
161
|
-
return Secret(name=name, value=value)
|
162
|
-
|
163
|
-
def _read_vault_secret(self, name: str) -> Secret:
|
164
|
-
url = f"https://{self._api_host}/api/v1/vault/secret/{name}/"
|
165
|
-
try:
|
166
|
-
response = requests.get(url, headers=self.headers, timeout=10)
|
167
|
-
response.raise_for_status()
|
168
|
-
return Secret(**response.json())
|
169
|
-
except Exception as exc:
|
170
|
-
raise AutomizorVaultError(f"Failed to get secret: {exc}") from exc
|
171
|
-
|
172
|
-
def _write_file_secret(self, secret: Secret):
|
173
|
-
with open(self._secret_file, "r+", encoding="utf-8") as file:
|
174
|
-
secrets = json.load(file)
|
175
|
-
secrets[secret.name] = secret.value
|
176
|
-
file.seek(0)
|
177
|
-
file.write(json.dumps(secrets, indent=4))
|
178
|
-
file.truncate()
|
179
|
-
return secret
|
180
|
-
|
181
|
-
def _write_vault_secret(self, secret: Secret) -> Secret:
|
182
|
-
url = f"https://{self._api_host}/api/v1/vault/secret/{secret.name}/"
|
183
|
-
try:
|
184
|
-
response = requests.put(
|
185
|
-
url, headers=self.headers, timeout=10, json=asdict(secret)
|
186
|
-
)
|
187
|
-
response.raise_for_status()
|
188
|
-
return Secret(**response.json())
|
189
|
-
except Exception as exc:
|
190
|
-
raise AutomizorVaultError(f"Failed to set secret: {exc}") from exc
|
automizor-0.3.1.dist-info/RECORD
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
automizor/__init__.py,sha256=sEAhGxRzEBE5t0VjAcJ-336II62pGIQ0eLrs42I-sGU,18
|
2
|
-
automizor/job.py,sha256=L2NkM-BkvJpeO_SH0BMgternD9M83K_Yv_ANhf1k3FI,4354
|
3
|
-
automizor/storage.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
|
-
automizor/vault.py,sha256=mluaCcJCMxu2g0iTBJ6ntoZATn8eotfZb4rdzWsBslU,5845
|
5
|
-
automizor/job/__init__.py,sha256=g-n56j50AGPCE23Nm0QFZCrkCa-c_weqvPcEX3I1NQY,1087
|
6
|
-
automizor/job/_exceptions.py,sha256=zngd7Vv4dkCfwmkigaHiyu5gKAgmcRHt095h55KlbDg,121
|
7
|
-
automizor/job/_job.py,sha256=NkoNnJxmdkqdF-Qxm4taal-Go0COVFo57tZAaWM1ihI,5365
|
8
|
-
automizor/storage/__init__.py,sha256=KuWO-Pb4FQXj68Ewv8QZR9XeKsROCR-wusdWf0osaLw,1674
|
9
|
-
automizor/storage/_exceptions.py,sha256=LOtgshWg3gOFhDZlcMWhXLT_q11zpTBEA85NqKnSi4A,129
|
10
|
-
automizor/storage/_storage.py,sha256=IwTw6PYYNwJtGI4ZfiqY0-SkgCoaYnykkP_hHVuy9IU,5777
|
11
|
-
automizor/vault/__init__.py,sha256=Y3FsdG5cdksVO8nBzIPt8Wwxg8VkFtkpu-kn1xWxrGo,1140
|
12
|
-
automizor/vault/_container.py,sha256=QgTZtBQrX8wZSEjJqTkn2_S-rwIRxukdBQYIRd7md_g,1904
|
13
|
-
automizor/vault/_exceptions.py,sha256=Wblvmaj6F0pIiTAH7X3JuxqTprUA5tvuuRAs9YgbiBI,125
|
14
|
-
automizor/vault/_secret.py,sha256=pks_3uvD1IhYirOaZ2cAOxX2r9vzvXqLa-aDzJysreE,1136
|
15
|
-
automizor/vault/_vault.py,sha256=G1IryuPfXeMUMoAFmGgPibD70zn307KQLNcNitd3D1A,4825
|
16
|
-
automizor-0.3.1.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
17
|
-
automizor-0.3.1.dist-info/METADATA,sha256=SkNNe2kHyA9aGX2275j7RFOy6GqKb9eN3WwQBT3sRpM,661
|
18
|
-
automizor-0.3.1.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
19
|
-
automizor-0.3.1.dist-info/top_level.txt,sha256=gScDy4I3tP6BMYAsTAlBXrxVh3E00zV0UioxwXJOI3Y,10
|
20
|
-
automizor-0.3.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|