automizor 0.2.0__py3-none-any.whl → 0.3.1__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/job/__init__.py +50 -0
- automizor/job/_exceptions.py +2 -0
- automizor/job/_job.py +134 -0
- automizor/storage/__init__.py +81 -0
- automizor/storage/_exceptions.py +2 -0
- automizor/storage/_storage.py +165 -0
- automizor/vault/__init__.py +57 -0
- automizor/vault/_container.py +68 -0
- automizor/vault/_exceptions.py +2 -0
- automizor/vault/_secret.py +45 -0
- automizor/vault/_vault.py +139 -0
- {automizor-0.2.0.dist-info → automizor-0.3.1.dist-info}/METADATA +1 -1
- automizor-0.3.1.dist-info/RECORD +20 -0
- automizor-0.2.0.dist-info/RECORD +0 -9
- {automizor-0.2.0.dist-info → automizor-0.3.1.dist-info}/LICENSE +0 -0
- {automizor-0.2.0.dist-info → automizor-0.3.1.dist-info}/WHEEL +0 -0
- {automizor-0.2.0.dist-info → automizor-0.3.1.dist-info}/top_level.txt +0 -0
automizor/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
version = "0.
|
1
|
+
version = "0.3.1"
|
@@ -0,0 +1,50 @@
|
|
1
|
+
from functools import lru_cache
|
2
|
+
|
3
|
+
from ._exceptions import AutomizorJobError
|
4
|
+
from ._job import JSON
|
5
|
+
|
6
|
+
|
7
|
+
@lru_cache
|
8
|
+
def _get_job():
|
9
|
+
from ._job import Job
|
10
|
+
|
11
|
+
return Job()
|
12
|
+
|
13
|
+
|
14
|
+
def get_context() -> dict:
|
15
|
+
"""
|
16
|
+
Retrieves the context of the current job, which contains necessary information
|
17
|
+
for job execution.
|
18
|
+
|
19
|
+
Returns:
|
20
|
+
A dictionary with the job's context.
|
21
|
+
|
22
|
+
Raises:
|
23
|
+
AutomizorJobError: If retrieving the job context fails.
|
24
|
+
"""
|
25
|
+
|
26
|
+
job = _get_job()
|
27
|
+
return job.get_context()
|
28
|
+
|
29
|
+
|
30
|
+
def set_result(name: str, value: JSON):
|
31
|
+
"""
|
32
|
+
Saves the result of the job execution to a local JSON file (`output/result.json`).
|
33
|
+
The `Automizor Agent` uploads this file to the `Automizor Platform` after the job.
|
34
|
+
|
35
|
+
Parameters:
|
36
|
+
name: The key under which to store the result.
|
37
|
+
value: The result value, must be JSON serializable.
|
38
|
+
|
39
|
+
Note: Errors during file operations will raise unhandled exceptions.
|
40
|
+
"""
|
41
|
+
|
42
|
+
job = _get_job()
|
43
|
+
return job.set_result(name, value)
|
44
|
+
|
45
|
+
|
46
|
+
__all__ = [
|
47
|
+
"AutomizorJobError",
|
48
|
+
"get_context",
|
49
|
+
"set_result",
|
50
|
+
]
|
automizor/job/_job.py
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
import json
|
2
|
+
import os
|
3
|
+
from typing import Dict, List, Union
|
4
|
+
|
5
|
+
import requests
|
6
|
+
|
7
|
+
from ._exceptions import AutomizorJobError
|
8
|
+
|
9
|
+
JSON = Union[str, int, float, bool, None, Dict[str, "JSON"], List["JSON"]]
|
10
|
+
|
11
|
+
|
12
|
+
class Job:
|
13
|
+
"""
|
14
|
+
`Job` is a class that facilitates interaction with job-specific data within the
|
15
|
+
`Automizor Platform`, allowing for the retrieval and updating of job context and
|
16
|
+
results. It provides mechanisms to access job context either from a local file or
|
17
|
+
directly from the `Automizor API`, based on the environment configuration. Additionally,
|
18
|
+
it offers functionality to save job results, enhancing automation workflows.
|
19
|
+
|
20
|
+
This class utilizes environment variables for configuration, specifically for setting
|
21
|
+
up the API host and API token, which are essential for authenticating requests made
|
22
|
+
to the `Automizor Storage API`. These variables are typically configured by the
|
23
|
+
`Automizor Agent`.
|
24
|
+
|
25
|
+
The job ID is required to fetch the job context from the `Automizor API`. If a job ID
|
26
|
+
is not available in the environment, the context can be read from a local file by setting
|
27
|
+
the `AUTOMIZOR_CONTEXT_FILE` environment variable. In this case, no other environment
|
28
|
+
variables are required.
|
29
|
+
|
30
|
+
To use this class effectively, ensure that the following environment variables are
|
31
|
+
set in your environment:
|
32
|
+
|
33
|
+
- ``AUTOMIZOR_API_HOST``: The URL of the `Automizor API` host.
|
34
|
+
- ``AUTOMIZOR_API_TOKEN``: The authentication token for API access.
|
35
|
+
- ``AUTOMIZOR_CONTEXT_FILE``: The path to a local file containing job context, if used.
|
36
|
+
- ``AUTOMIZOR_JOB_ID``: The identifier for the current job, used when fetching context via API.
|
37
|
+
|
38
|
+
Example usage:
|
39
|
+
|
40
|
+
.. code-block:: python
|
41
|
+
|
42
|
+
from automizor import job
|
43
|
+
|
44
|
+
# Retrieving job context
|
45
|
+
context = job.get_context()
|
46
|
+
print(context) # Output: {"key": "value"}
|
47
|
+
|
48
|
+
# Saving a job result
|
49
|
+
job.set_result("result_name", {"key": "value"})
|
50
|
+
"""
|
51
|
+
|
52
|
+
def __init__(self):
|
53
|
+
self._api_host = os.getenv("AUTOMIZOR_API_HOST")
|
54
|
+
self._api_token = os.getenv("AUTOMIZOR_API_TOKEN")
|
55
|
+
self._context_file = os.getenv("AUTOMIZOR_CONTEXT_FILE")
|
56
|
+
self._job_id = os.getenv("AUTOMIZOR_JOB_ID")
|
57
|
+
|
58
|
+
self.session = requests.Session()
|
59
|
+
self.session.headers.update(
|
60
|
+
{
|
61
|
+
"Authorization": f"Token {self._api_token}",
|
62
|
+
"Content-Type": "application/json",
|
63
|
+
}
|
64
|
+
)
|
65
|
+
|
66
|
+
def get_context(self) -> dict:
|
67
|
+
"""
|
68
|
+
Retrieves the context of the current job, which contains necessary information
|
69
|
+
for job execution. The context can be fetched from two sources based on the
|
70
|
+
environment configuration:
|
71
|
+
|
72
|
+
1. A local file specified by the `AUTOMIZOR_CONTEXT_FILE` environment variable.
|
73
|
+
This is useful in environments where direct access to the `Automizor API` is
|
74
|
+
not possible or preferred.
|
75
|
+
2. The `Automizor API`, using the job ID (`AUTOMIZOR_JOB_ID`) to fetch the specific
|
76
|
+
job context. This requires valid API host (`AUTOMIZOR_API_HOST`) and token
|
77
|
+
(`AUTOMIZOR_API_TOKEN`) settings.
|
78
|
+
|
79
|
+
Returns:
|
80
|
+
A dictionary containing the job context.
|
81
|
+
|
82
|
+
Raises:
|
83
|
+
AutomizorJobError: If retrieving the job context fails.
|
84
|
+
"""
|
85
|
+
|
86
|
+
if self._context_file:
|
87
|
+
return self._read_file_context()
|
88
|
+
return self._read_job_context()
|
89
|
+
|
90
|
+
def set_result(self, name: str, value: JSON):
|
91
|
+
"""
|
92
|
+
Saves the result of the job execution to a local JSON file (`output/result.json`).
|
93
|
+
The `Automizor Agent` uploads this file to the `Automizor Platform` after the job.
|
94
|
+
|
95
|
+
The result is stored as a key-value pair within the JSON file, where the key is the
|
96
|
+
name of the result and the value is the result itself. If the file already exists,
|
97
|
+
this method updates the file with the new result, merging it with any existing data.
|
98
|
+
If the file does not exist, it is created.
|
99
|
+
|
100
|
+
Parameters:
|
101
|
+
name: The name of the result, used as a key in the JSON file.
|
102
|
+
value: The result value, must be JSON serializable.
|
103
|
+
|
104
|
+
Note:
|
105
|
+
This method does not handle exceptions related to file access or JSON serialization
|
106
|
+
internally. It is the caller's responsibility to handle such exceptions as needed.
|
107
|
+
"""
|
108
|
+
|
109
|
+
data = {}
|
110
|
+
file_path = "output/result.json"
|
111
|
+
try:
|
112
|
+
if os.path.exists(file_path):
|
113
|
+
with open(file_path, "r", encoding="utf-8") as file:
|
114
|
+
data = json.load(file)
|
115
|
+
except json.JSONDecodeError:
|
116
|
+
pass
|
117
|
+
|
118
|
+
data[name] = value
|
119
|
+
|
120
|
+
with open(file_path, "w", encoding="utf-8") as file:
|
121
|
+
json.dump(data, file, ensure_ascii=False, indent=4)
|
122
|
+
|
123
|
+
def _read_file_context(self) -> dict:
|
124
|
+
with open(self._context_file, "r", encoding="utf-8") as file:
|
125
|
+
return json.load(file)
|
126
|
+
|
127
|
+
def _read_job_context(self) -> dict:
|
128
|
+
url = f"https://{self._api_host}/api/v1/rpa/job/{self._job_id}/"
|
129
|
+
try:
|
130
|
+
response = self.session.get(url, timeout=10)
|
131
|
+
response.raise_for_status()
|
132
|
+
return response.json().get("context", {})
|
133
|
+
except Exception as exc:
|
134
|
+
raise AutomizorJobError(f"Failed to get job context: {exc}") from exc
|
@@ -0,0 +1,81 @@
|
|
1
|
+
from functools import lru_cache
|
2
|
+
|
3
|
+
from ._exceptions import AutomizorStorageError
|
4
|
+
from ._storage import JSON
|
5
|
+
|
6
|
+
|
7
|
+
@lru_cache
|
8
|
+
def _get_storage():
|
9
|
+
from ._storage import Storage
|
10
|
+
|
11
|
+
return Storage()
|
12
|
+
|
13
|
+
|
14
|
+
def get_bytes(name: str) -> bytes:
|
15
|
+
"""
|
16
|
+
Retrieves the specified asset as raw bytes.
|
17
|
+
|
18
|
+
Parameters:
|
19
|
+
name: The name identifier of the asset to retrieve.
|
20
|
+
|
21
|
+
Returns:
|
22
|
+
The raw byte content of the asset.
|
23
|
+
"""
|
24
|
+
|
25
|
+
storage = _get_storage()
|
26
|
+
return storage.get_bytes(name)
|
27
|
+
|
28
|
+
|
29
|
+
def get_file(name: str, path: str) -> str:
|
30
|
+
"""
|
31
|
+
Downloads the specified asset and saves it to a file.
|
32
|
+
|
33
|
+
Parameters:
|
34
|
+
name: The name identifier of the asset to retrieve.
|
35
|
+
path: The filesystem path where the file will be saved.
|
36
|
+
|
37
|
+
Returns:
|
38
|
+
The path to the saved file, confirming the operation's success.
|
39
|
+
"""
|
40
|
+
|
41
|
+
storage = _get_storage()
|
42
|
+
return storage.get_file(name, path)
|
43
|
+
|
44
|
+
|
45
|
+
def get_json(name: str) -> JSON:
|
46
|
+
"""
|
47
|
+
Retrieves the specified asset and parses it as JSON.
|
48
|
+
|
49
|
+
Parameters:
|
50
|
+
name: The name identifier of the asset to retrieve.
|
51
|
+
|
52
|
+
Returns:
|
53
|
+
The parsed JSON data, which can be a dict, list, or primitive data type.
|
54
|
+
"""
|
55
|
+
|
56
|
+
storage = _get_storage()
|
57
|
+
return storage.get_json(name)
|
58
|
+
|
59
|
+
|
60
|
+
def get_text(name: str) -> str:
|
61
|
+
"""
|
62
|
+
Retrieves the specified asset as a text string.
|
63
|
+
|
64
|
+
Parameters:
|
65
|
+
name: The name identifier of the asset to retrieve.
|
66
|
+
|
67
|
+
Returns:
|
68
|
+
The content of the asset as a text string.
|
69
|
+
"""
|
70
|
+
|
71
|
+
storage = _get_storage()
|
72
|
+
return storage.get_text(name)
|
73
|
+
|
74
|
+
|
75
|
+
__all__ = [
|
76
|
+
"AutomizorStorageError",
|
77
|
+
"get_bytes",
|
78
|
+
"get_file",
|
79
|
+
"get_json",
|
80
|
+
"get_text",
|
81
|
+
]
|
@@ -0,0 +1,165 @@
|
|
1
|
+
import os
|
2
|
+
from typing import Dict, List, Union
|
3
|
+
|
4
|
+
import requests
|
5
|
+
|
6
|
+
from ._exceptions import AutomizorStorageError
|
7
|
+
|
8
|
+
JSON = Union[str, int, float, bool, None, Dict[str, "JSON"], List["JSON"]]
|
9
|
+
|
10
|
+
|
11
|
+
class Storage:
|
12
|
+
"""
|
13
|
+
`Storage` is a class designed to interact with the `Automizor Platform` for managing
|
14
|
+
digital assets, facilitating the retrieval of files in various formats such as bytes,
|
15
|
+
files, JSON, and text. It leverages the `Automizor Storage API` to access and download
|
16
|
+
these assets securely.
|
17
|
+
|
18
|
+
This class utilizes environment variables for configuration, specifically for setting
|
19
|
+
up the API host and API token, which are essential for authenticating requests made
|
20
|
+
to the `Automizor Storage API`. These variables are typically configured by the
|
21
|
+
`Automizor Agent`.
|
22
|
+
|
23
|
+
To use this class effectively, ensure that the following environment variables are
|
24
|
+
set in your environment:
|
25
|
+
|
26
|
+
- ``AUTOMIZOR_API_HOST``: Specifies the host URL of the `Automizor Storage API`.
|
27
|
+
- ``AUTOMIZOR_API_TOKEN``: Provides the token required for API authentication.
|
28
|
+
|
29
|
+
Example usage:
|
30
|
+
|
31
|
+
.. code-block:: python
|
32
|
+
|
33
|
+
from automizor import storage
|
34
|
+
|
35
|
+
# To get bytes of an asset
|
36
|
+
bytes_data = storage.get_bytes("asset_name")
|
37
|
+
|
38
|
+
# To save an asset to a file
|
39
|
+
file_path = storage.get_file("asset_name", "/path/to/save/file")
|
40
|
+
|
41
|
+
# To retrieve an asset as JSON
|
42
|
+
json_data = storage.get_json("asset_name")
|
43
|
+
|
44
|
+
# To get the text content of an asset
|
45
|
+
text_data = storage.get_text("asset_name")
|
46
|
+
"""
|
47
|
+
|
48
|
+
def __init__(self):
|
49
|
+
self._api_host = os.getenv("AUTOMIZOR_API_HOST")
|
50
|
+
self._api_token = os.getenv("AUTOMIZOR_API_TOKEN")
|
51
|
+
|
52
|
+
self.session = requests.Session()
|
53
|
+
self.session.headers.update(
|
54
|
+
{
|
55
|
+
"Authorization": f"Token {self._api_token}",
|
56
|
+
"Content-Type": "application/json",
|
57
|
+
}
|
58
|
+
)
|
59
|
+
|
60
|
+
def get_bytes(self, name: str) -> bytes:
|
61
|
+
"""
|
62
|
+
Retrieves the specified asset as raw bytes.
|
63
|
+
|
64
|
+
This function fetches the asset identified by `name` from the storage service
|
65
|
+
and returns it as a byte stream. It is useful for binary files or for data
|
66
|
+
that is intended to be processed or stored in its raw form.
|
67
|
+
|
68
|
+
Parameters:
|
69
|
+
name: The name identifier of the asset to retrieve.
|
70
|
+
|
71
|
+
Returns:
|
72
|
+
The raw byte content of the asset.
|
73
|
+
"""
|
74
|
+
|
75
|
+
url = self._get_asset_url(name)
|
76
|
+
return self._download_file(url, mode="content")
|
77
|
+
|
78
|
+
def get_file(self, name: str, path: str) -> str:
|
79
|
+
"""
|
80
|
+
Downloads the specified asset and saves it to a file.
|
81
|
+
|
82
|
+
This function fetches the asset identified by `name` and saves it directly
|
83
|
+
to the filesystem at the location specified by `path`. It is useful for
|
84
|
+
downloading files that need to be preserved in the file system, such as
|
85
|
+
documents, images, or other files.
|
86
|
+
|
87
|
+
Parameters:
|
88
|
+
name: The name identifier of the asset to retrieve.
|
89
|
+
path: The filesystem path where the file will be saved.
|
90
|
+
|
91
|
+
Returns:
|
92
|
+
The path to the saved file, confirming the operation's success.
|
93
|
+
"""
|
94
|
+
|
95
|
+
url = self._get_asset_url(name)
|
96
|
+
content = self._download_file(url, mode="content")
|
97
|
+
with open(path, "wb") as file:
|
98
|
+
file.write(content)
|
99
|
+
return path
|
100
|
+
|
101
|
+
def get_json(self, name: str) -> JSON:
|
102
|
+
"""
|
103
|
+
Retrieves the specified asset and parses it as JSON.
|
104
|
+
|
105
|
+
This function fetches the asset identified by `name` from the storage service
|
106
|
+
and parses it as JSON. It is useful for assets stored in JSON format, allowing
|
107
|
+
for easy access and manipulation of structured data.
|
108
|
+
|
109
|
+
Parameters:
|
110
|
+
name: The name identifier of the asset to retrieve.
|
111
|
+
|
112
|
+
Returns:
|
113
|
+
The parsed JSON data, which can be a dict, list, or primitive data type.
|
114
|
+
"""
|
115
|
+
|
116
|
+
url = self._get_asset_url(name)
|
117
|
+
return self._download_file(url, mode="json")
|
118
|
+
|
119
|
+
def get_text(self, name: str) -> str:
|
120
|
+
"""
|
121
|
+
Retrieves the specified asset as a text string.
|
122
|
+
|
123
|
+
This function fetches the asset identified by `name` from the storage service
|
124
|
+
and returns it as a text string. It is useful for text-based files, such as
|
125
|
+
configuration files, CSVs, or plain text documents.
|
126
|
+
|
127
|
+
Parameters:
|
128
|
+
name: The name identifier of the asset to retrieve.
|
129
|
+
|
130
|
+
Returns:
|
131
|
+
The content of the asset as a text string.
|
132
|
+
"""
|
133
|
+
|
134
|
+
url = self._get_asset_url(name)
|
135
|
+
return self._download_file(url, mode="text")
|
136
|
+
|
137
|
+
def _download_file(self, url: str, mode: str = "content"):
|
138
|
+
try:
|
139
|
+
session = requests.Session()
|
140
|
+
response = session.get(url, timeout=10)
|
141
|
+
response.raise_for_status()
|
142
|
+
|
143
|
+
match mode:
|
144
|
+
case "content":
|
145
|
+
return response.content
|
146
|
+
case "json":
|
147
|
+
return response.json()
|
148
|
+
case "text":
|
149
|
+
return response.text
|
150
|
+
raise RuntimeError(f"Invalid mode {mode}")
|
151
|
+
except Exception as exc:
|
152
|
+
raise AutomizorStorageError(f"Failed to download asset: {exc}") from exc
|
153
|
+
|
154
|
+
def _get_asset_url(self, name: str) -> str:
|
155
|
+
url = f"https://{self._api_host}/api/v1/storage/asset/{name}/"
|
156
|
+
try:
|
157
|
+
response = self.session.get(url, timeout=10)
|
158
|
+
response.raise_for_status()
|
159
|
+
|
160
|
+
url = response.json().get("file")
|
161
|
+
if url:
|
162
|
+
return url
|
163
|
+
raise RuntimeError("Url not found")
|
164
|
+
except Exception as exc:
|
165
|
+
raise AutomizorStorageError(f"Failed to get asset url: {exc}") from exc
|
@@ -0,0 +1,57 @@
|
|
1
|
+
from functools import lru_cache
|
2
|
+
|
3
|
+
from ._container import SecretContainer
|
4
|
+
from ._exceptions import AutomizorVaultError
|
5
|
+
|
6
|
+
|
7
|
+
@lru_cache
|
8
|
+
def _get_vault():
|
9
|
+
from ._vault import Vault
|
10
|
+
|
11
|
+
return Vault()
|
12
|
+
|
13
|
+
|
14
|
+
def get_secret(name: str) -> SecretContainer:
|
15
|
+
"""
|
16
|
+
Retrieves a secret by its name. Fetches from a local file or queries the
|
17
|
+
`Automizor API`, based on configuration.
|
18
|
+
|
19
|
+
Args:
|
20
|
+
name: The name of the secret to retrieve.
|
21
|
+
|
22
|
+
Returns:
|
23
|
+
The retrieved secret.
|
24
|
+
|
25
|
+
Raises:
|
26
|
+
AutomizorVaultError: If retrieving the secret fails.
|
27
|
+
"""
|
28
|
+
|
29
|
+
vault = _get_vault()
|
30
|
+
return vault.get_secret(name)
|
31
|
+
|
32
|
+
|
33
|
+
def set_secret(secret: SecretContainer) -> SecretContainer:
|
34
|
+
"""
|
35
|
+
Updates an existing secret. Updates to a local file or to the
|
36
|
+
`Automizor API`, based on configuration.
|
37
|
+
|
38
|
+
Args:
|
39
|
+
secret: The secret to update.
|
40
|
+
|
41
|
+
Returns:
|
42
|
+
The updated secret.
|
43
|
+
|
44
|
+
Raises:
|
45
|
+
AutomizorVaultError: If updating the secret fails.
|
46
|
+
"""
|
47
|
+
|
48
|
+
vault = _get_vault()
|
49
|
+
return vault.set_secret(secret)
|
50
|
+
|
51
|
+
|
52
|
+
__all__ = [
|
53
|
+
"AutomizorVaultError",
|
54
|
+
"SecretContainer",
|
55
|
+
"get_secret",
|
56
|
+
"set_secret",
|
57
|
+
]
|
@@ -0,0 +1,68 @@
|
|
1
|
+
from dataclasses import dataclass, fields
|
2
|
+
|
3
|
+
|
4
|
+
def ignore_extra_fields(cls):
|
5
|
+
original_post_init = getattr(cls, "__post_init__", None)
|
6
|
+
|
7
|
+
def __init__(self, **kwargs):
|
8
|
+
cls_fields = {field.name: field for field in fields(cls)}
|
9
|
+
for name, value in kwargs.items():
|
10
|
+
if name in cls_fields:
|
11
|
+
setattr(self, name, value)
|
12
|
+
if original_post_init:
|
13
|
+
original_post_init(self)
|
14
|
+
|
15
|
+
setattr(cls, "__init__", __init__)
|
16
|
+
return cls
|
17
|
+
|
18
|
+
|
19
|
+
@ignore_extra_fields
|
20
|
+
@dataclass
|
21
|
+
class SecretContainer:
|
22
|
+
"""
|
23
|
+
Represents a secret, comprising a name and its associated values.
|
24
|
+
|
25
|
+
Attributes:
|
26
|
+
description: A description of the secret.
|
27
|
+
name: The name of the secret.
|
28
|
+
value: The secret's values, stored in a dictionary as key-value pairs.
|
29
|
+
"""
|
30
|
+
|
31
|
+
name: str
|
32
|
+
value: dict
|
33
|
+
description: str = ""
|
34
|
+
|
35
|
+
def get(self, key, default=None):
|
36
|
+
"""Return the value for key if key is in the dictionary, else default."""
|
37
|
+
return self.value.get(key, default)
|
38
|
+
|
39
|
+
def items(self):
|
40
|
+
"""Secret.items() -> a set-like object providing a view on secret's items."""
|
41
|
+
return self.value.items()
|
42
|
+
|
43
|
+
def keys(self):
|
44
|
+
"""Secret.keys() -> a set-like object providing a view on secret's keys."""
|
45
|
+
return self.value.keys()
|
46
|
+
|
47
|
+
def update(self, pairs: dict) -> None:
|
48
|
+
"""Update the secret's dictionary with the key-value pairs from pairs."""
|
49
|
+
self.value.update(pairs)
|
50
|
+
|
51
|
+
def __getitem__(self, key):
|
52
|
+
return self.value[key]
|
53
|
+
|
54
|
+
def __setitem__(self, key, value):
|
55
|
+
self.value[key] = value
|
56
|
+
|
57
|
+
def __contains__(self, key):
|
58
|
+
return key in self.value
|
59
|
+
|
60
|
+
def __iter__(self):
|
61
|
+
return iter(self.value)
|
62
|
+
|
63
|
+
def __len__(self):
|
64
|
+
return len(self.value)
|
65
|
+
|
66
|
+
def __repr__(self):
|
67
|
+
keys = ", ".join(self.keys())
|
68
|
+
return f"Secret(name={self.name}, keys=[{keys}])"
|
@@ -0,0 +1,45 @@
|
|
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})"
|
@@ -0,0 +1,139 @@
|
|
1
|
+
import json
|
2
|
+
import os
|
3
|
+
from dataclasses import asdict
|
4
|
+
|
5
|
+
import requests
|
6
|
+
|
7
|
+
from ._container import SecretContainer
|
8
|
+
from ._exceptions import AutomizorVaultError
|
9
|
+
|
10
|
+
|
11
|
+
class Vault:
|
12
|
+
"""
|
13
|
+
`Vault` is a secure storage class within the `Automizor Platform` for managing
|
14
|
+
secrets such as API keys, passwords, and other sensitive information. It offers
|
15
|
+
functionality to securely retrieve and update secrets, either through direct
|
16
|
+
interaction with the `Automizor API` or via a local secrets file, depending on
|
17
|
+
the operational environment and configuration.
|
18
|
+
|
19
|
+
Configuration for accessing and manipulating these secrets is driven by environment
|
20
|
+
variables, which are essential for specifying the API's host and token for
|
21
|
+
authentication purposes, alongside the location of a local secrets file, should
|
22
|
+
local storage be preferred over API interaction.
|
23
|
+
|
24
|
+
Environment variables requisite for operation include:
|
25
|
+
- ``AUTOMIZOR_API_HOST``: The host URL for the `Automizor API`.
|
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
|
+
}
|
40
|
+
|
41
|
+
Example usage:
|
42
|
+
|
43
|
+
.. code-block:: python
|
44
|
+
|
45
|
+
from automizor import vault
|
46
|
+
|
47
|
+
# Retrieve a secret by its name
|
48
|
+
secret = vault.get_secret("my_secret_name")
|
49
|
+
print(secret["key"]) # Output: "value"
|
50
|
+
|
51
|
+
# Update a existing secret
|
52
|
+
secret = vault.get_secret("my_secret_name")
|
53
|
+
secret["new_key"] = "new_value"
|
54
|
+
vault.set_secret(secret)
|
55
|
+
"""
|
56
|
+
|
57
|
+
def __init__(self):
|
58
|
+
self._api_host = os.getenv("AUTOMIZOR_API_HOST")
|
59
|
+
self._api_token = os.getenv("AUTOMIZOR_API_TOKEN")
|
60
|
+
self._secret_file = os.getenv("AUTOMIZOR_SECRET_FILE")
|
61
|
+
|
62
|
+
self.session = requests.Session()
|
63
|
+
self.session.headers.update(
|
64
|
+
{
|
65
|
+
"Authorization": f"Token {self._api_token}",
|
66
|
+
"Content-Type": "application/json",
|
67
|
+
}
|
68
|
+
)
|
69
|
+
|
70
|
+
def get_secret(self, name) -> SecretContainer:
|
71
|
+
"""
|
72
|
+
Retrieves a secret by its name. Fetches from a local file or queries the
|
73
|
+
`Automizor API`, based on configuration.
|
74
|
+
|
75
|
+
Args:
|
76
|
+
name: The name of the secret to retrieve.
|
77
|
+
|
78
|
+
Returns:
|
79
|
+
The retrieved secret.
|
80
|
+
|
81
|
+
Raises:
|
82
|
+
AutomizorVaultError: If retrieving the secret fails.
|
83
|
+
"""
|
84
|
+
|
85
|
+
if self._secret_file:
|
86
|
+
return self._read_file_secret(name)
|
87
|
+
return self._read_vault_secret(name)
|
88
|
+
|
89
|
+
def set_secret(self, secret: SecretContainer) -> SecretContainer:
|
90
|
+
"""
|
91
|
+
Updates an existing secret. Updates to a local file or to the
|
92
|
+
`Automizor API`, based on configuration.
|
93
|
+
|
94
|
+
Args:
|
95
|
+
secret: The secret to update.
|
96
|
+
|
97
|
+
Returns:
|
98
|
+
The updated secret.
|
99
|
+
|
100
|
+
Raises:
|
101
|
+
AutomizorVaultError: If updating the secret fails.
|
102
|
+
"""
|
103
|
+
|
104
|
+
if self._secret_file:
|
105
|
+
return self._write_file_secret(secret)
|
106
|
+
return self._write_vault_secret(secret)
|
107
|
+
|
108
|
+
def _read_file_secret(self, name: str) -> SecretContainer:
|
109
|
+
with open(self._secret_file, "r", encoding="utf-8") as file:
|
110
|
+
secrets = json.load(file)
|
111
|
+
value = secrets.get(name, {})
|
112
|
+
return SecretContainer(name=name, value=value)
|
113
|
+
|
114
|
+
def _read_vault_secret(self, name: str) -> SecretContainer:
|
115
|
+
url = f"https://{self._api_host}/api/v1/vault/secret/{name}/"
|
116
|
+
try:
|
117
|
+
response = self.session.get(url, timeout=10)
|
118
|
+
response.raise_for_status()
|
119
|
+
return SecretContainer(**response.json())
|
120
|
+
except Exception as exc:
|
121
|
+
raise AutomizorVaultError(f"Failed to get secret: {exc}") from exc
|
122
|
+
|
123
|
+
def _write_file_secret(self, secret: SecretContainer):
|
124
|
+
with open(self._secret_file, "r+", encoding="utf-8") as file:
|
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}/"
|
134
|
+
try:
|
135
|
+
response = self.session.put(url, timeout=10, json=asdict(secret))
|
136
|
+
response.raise_for_status()
|
137
|
+
return SecretContainer(**response.json())
|
138
|
+
except Exception as exc:
|
139
|
+
raise AutomizorVaultError(f"Failed to set secret: {exc}") from exc
|
@@ -0,0 +1,20 @@
|
|
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,,
|
automizor-0.2.0.dist-info/RECORD
DELETED
@@ -1,9 +0,0 @@
|
|
1
|
-
automizor/__init__.py,sha256=XQVhijSHeIVMVzY2S4fY9BKxV2XHSTr9VVHsYltGPvQ,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-0.2.0.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
6
|
-
automizor-0.2.0.dist-info/METADATA,sha256=aRwuipXNcIswJop_TAGjrlM4ZigW7eI8oqWIeI936vc,661
|
7
|
-
automizor-0.2.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
8
|
-
automizor-0.2.0.dist-info/top_level.txt,sha256=gScDy4I3tP6BMYAsTAlBXrxVh3E00zV0UioxwXJOI3Y,10
|
9
|
-
automizor-0.2.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|