splight-runner 1.0.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.
- splight_runner-1.0.0/PKG-INFO +127 -0
- splight_runner-1.0.0/README.md +110 -0
- splight_runner-1.0.0/pyproject.toml +35 -0
- splight_runner-1.0.0/src/splight_runner/__init__.py +3 -0
- splight_runner-1.0.0/src/splight_runner/api/__init__.py +0 -0
- splight_runner-1.0.0/src/splight_runner/api/component_reporter.py +55 -0
- splight_runner-1.0.0/src/splight_runner/api/logging_interceptor.py +85 -0
- splight_runner-1.0.0/src/splight_runner/api/settings.py +63 -0
- splight_runner-1.0.0/src/splight_runner/bootstrap/__init__.py +0 -0
- splight_runner-1.0.0/src/splight_runner/bootstrap/sitecustomize.py +39 -0
- splight_runner-1.0.0/src/splight_runner/commands/__init__.py +0 -0
- splight_runner-1.0.0/src/splight_runner/commands/admin.py +136 -0
- splight_runner-1.0.0/src/splight_runner/commands/execute_agent.py +43 -0
- splight_runner-1.0.0/src/splight_runner/commands/execute_component.py +84 -0
- splight_runner-1.0.0/src/splight_runner/config.py +72 -0
- splight_runner-1.0.0/src/splight_runner/hooks/__init__.py +150 -0
- splight_runner-1.0.0/src/splight_runner/hooks/constants.py +1 -0
- splight_runner-1.0.0/src/splight_runner/hooks/finder.py +46 -0
- splight_runner-1.0.0/src/splight_runner/hooks/loader.py +88 -0
- splight_runner-1.0.0/src/splight_runner/hooks/meta_paths.py +16 -0
- splight_runner-1.0.0/src/splight_runner/hooks/registry.py +39 -0
- splight_runner-1.0.0/src/splight_runner/hooks/utils.py +5 -0
- splight_runner-1.0.0/src/splight_runner/log_streamer/__init__.py +0 -0
- splight_runner-1.0.0/src/splight_runner/log_streamer/log_buffer.py +55 -0
- splight_runner-1.0.0/src/splight_runner/log_streamer/log_client.py +30 -0
- splight_runner-1.0.0/src/splight_runner/log_streamer/log_streamer.py +91 -0
- splight_runner-1.0.0/src/splight_runner/logging.py +13 -0
- splight_runner-1.0.0/src/splight_runner/runner.py +30 -0
- splight_runner-1.0.0/src/splight_runner/version.py +3 -0
- splight_runner-1.0.0/src/splight_runner/wrapper/__init__.py +0 -0
- splight_runner-1.0.0/src/splight_runner/wrapper/healthcheck.py +24 -0
- splight_runner-1.0.0/src/splight_runner/wrapper/hooks.py +16 -0
- splight_runner-1.0.0/src/splight_runner/wrapper/logging.py +27 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: splight-runner
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Splight Runner
|
|
5
|
+
Author: Splight Dev
|
|
6
|
+
Author-email: dev@splight-ae.com
|
|
7
|
+
Requires-Python: >=3.8,<4.0
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Requires-Dist: requests (>=2.26.0,<3.0.0)
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# Splight Runner
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
The `splight-runner` package is a tool for running processes that may interact
|
|
22
|
+
with the **Splight Engine** in order to report monitoring information of the
|
|
23
|
+
process.
|
|
24
|
+
|
|
25
|
+
## Description
|
|
26
|
+
|
|
27
|
+
The `splight-runner` main functionality is to run different kinds of *Python*
|
|
28
|
+
processes related in some way to the **Splight Engine**, for example,
|
|
29
|
+
*components* that were developed using the **Splight Library**.
|
|
30
|
+
|
|
31
|
+
For this reason, the different features in the runner are for reporting the
|
|
32
|
+
status of the process and centralizing logs to be accessed through the
|
|
33
|
+
**Splight Engine**. So the `splight-runner` is used when the process is
|
|
34
|
+
launched from the **Splight Engine**, this means that it should not be used
|
|
35
|
+
during the development of the component even though the process will be
|
|
36
|
+
running under the `splight-runner`.
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
|
|
40
|
+
The package has different commands that can be used. Here you can find a list
|
|
41
|
+
of all the commands with a short description and how to use them.
|
|
42
|
+
|
|
43
|
+
### Running Components
|
|
44
|
+
|
|
45
|
+
The first command is used for running components that use the **Splight Library**.
|
|
46
|
+
The command is
|
|
47
|
+
```bash
|
|
48
|
+
splight-runner run-component <path>/main.py --component-id=<component_id>
|
|
49
|
+
```
|
|
50
|
+
This command will execute the component whose main file is called `main.py` and
|
|
51
|
+
will modify some default functioning in order to report the status of the component and
|
|
52
|
+
send the logs of the component to the **Splight Engine**. In the next sections, you can
|
|
53
|
+
find how these modifications are done by the `splight-runner`.
|
|
54
|
+
|
|
55
|
+
In order to this command work as expected some environment variables are needed.
|
|
56
|
+
The following are the list of variables that should be configured before running the
|
|
57
|
+
command:
|
|
58
|
+
```bash
|
|
59
|
+
SPLIGHT_ACCESS_ID=<access_id>
|
|
60
|
+
SPLIGHT_SECRET_KEY=<secret_key
|
|
61
|
+
SPLIGHT_PLATFORM_API_HOST=https://api.splight-ai.com
|
|
62
|
+
COMPONENT_ID=<component_id>
|
|
63
|
+
```
|
|
64
|
+
Some of the variables are pointing to the production environment, but if you are
|
|
65
|
+
interested in using another environment like "integration" or maybe a local
|
|
66
|
+
environment you need to modify the corresponding variable value.
|
|
67
|
+
|
|
68
|
+
It is important to mention that the `splight-runner` should be used for components that
|
|
69
|
+
use the **Splight Lib** and **Splight CLI** grater than version `4.0.0`. For components
|
|
70
|
+
with older versions we can't ensure the proper functioning.
|
|
71
|
+
|
|
72
|
+
## Structure
|
|
73
|
+
|
|
74
|
+
You may be asking what the `splight-runner` does in order to do whatever it does.
|
|
75
|
+
Well, that's not an easy question to respond but let's try to explain a little bit.
|
|
76
|
+
|
|
77
|
+
So far, the package has two main functionalities, sending logs and reporting status.
|
|
78
|
+
With the two features we already have a lot of magic is happening, but maybe in the future
|
|
79
|
+
we will add some more.
|
|
80
|
+
|
|
81
|
+
`splight-runner` works as a wrapper around the process that will be executed,
|
|
82
|
+
this means some configurations are done, so the process in question is executed.
|
|
83
|
+
|
|
84
|
+
For example, when the command `run-component` is used, the environment variable
|
|
85
|
+
`PYTHONPATH` is modified in order to include the directory where the file `sitecustomize.py`
|
|
86
|
+
of the `splight-runner` is located. That file is a *Python* configuration file that can
|
|
87
|
+
be used to customize the behavior of Python's site module. The site module is
|
|
88
|
+
responsible for setting up Python's runtime environment, including configuring
|
|
89
|
+
paths, importing modules, and performing other site-specific tasks.
|
|
90
|
+
|
|
91
|
+
So, using this file we can modify the default behaviors of libraries, for example, we
|
|
92
|
+
can modify the import process of modules or libraries in order to change or add
|
|
93
|
+
a new behavior. This way we can modify the `logging` module and intercept
|
|
94
|
+
the logging process to show the logs messages and also send the logs messages
|
|
95
|
+
to the **Splight Engine**. Some similar implementation is used for
|
|
96
|
+
reporting the component's status.
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
## Development and Contributing
|
|
100
|
+
|
|
101
|
+
Since the main goal of the tool is to be used for running different kinds of
|
|
102
|
+
`Python` processes, is mandatory not to interfere with the process's
|
|
103
|
+
dependencies, this should be considered when adding new features for the
|
|
104
|
+
`splight-runner`. This is the reason why some basic functionalities
|
|
105
|
+
are implemented in the source code and not imported from a third-party
|
|
106
|
+
library, for example, a command parser for the different commands was created
|
|
107
|
+
in the code and no library was used like `click` or `typer`.
|
|
108
|
+
|
|
109
|
+
The package is fully dockerized, for testing new features you can use the
|
|
110
|
+
docker image that can be build with
|
|
111
|
+
the command
|
|
112
|
+
```bash
|
|
113
|
+
make build
|
|
114
|
+
```
|
|
115
|
+
For running the docker container the command is
|
|
116
|
+
```bash
|
|
117
|
+
make start
|
|
118
|
+
```
|
|
119
|
+
and to stop the container:
|
|
120
|
+
```bash
|
|
121
|
+
make stop
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
It is important to note that testing new features in the `splight-runner` is
|
|
125
|
+
not an easy task, you may need to modify the docker-compose file in order to
|
|
126
|
+
include new volumes.
|
|
127
|
+
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Splight Runner
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
The `splight-runner` package is a tool for running processes that may interact
|
|
6
|
+
with the **Splight Engine** in order to report monitoring information of the
|
|
7
|
+
process.
|
|
8
|
+
|
|
9
|
+
## Description
|
|
10
|
+
|
|
11
|
+
The `splight-runner` main functionality is to run different kinds of *Python*
|
|
12
|
+
processes related in some way to the **Splight Engine**, for example,
|
|
13
|
+
*components* that were developed using the **Splight Library**.
|
|
14
|
+
|
|
15
|
+
For this reason, the different features in the runner are for reporting the
|
|
16
|
+
status of the process and centralizing logs to be accessed through the
|
|
17
|
+
**Splight Engine**. So the `splight-runner` is used when the process is
|
|
18
|
+
launched from the **Splight Engine**, this means that it should not be used
|
|
19
|
+
during the development of the component even though the process will be
|
|
20
|
+
running under the `splight-runner`.
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
The package has different commands that can be used. Here you can find a list
|
|
25
|
+
of all the commands with a short description and how to use them.
|
|
26
|
+
|
|
27
|
+
### Running Components
|
|
28
|
+
|
|
29
|
+
The first command is used for running components that use the **Splight Library**.
|
|
30
|
+
The command is
|
|
31
|
+
```bash
|
|
32
|
+
splight-runner run-component <path>/main.py --component-id=<component_id>
|
|
33
|
+
```
|
|
34
|
+
This command will execute the component whose main file is called `main.py` and
|
|
35
|
+
will modify some default functioning in order to report the status of the component and
|
|
36
|
+
send the logs of the component to the **Splight Engine**. In the next sections, you can
|
|
37
|
+
find how these modifications are done by the `splight-runner`.
|
|
38
|
+
|
|
39
|
+
In order to this command work as expected some environment variables are needed.
|
|
40
|
+
The following are the list of variables that should be configured before running the
|
|
41
|
+
command:
|
|
42
|
+
```bash
|
|
43
|
+
SPLIGHT_ACCESS_ID=<access_id>
|
|
44
|
+
SPLIGHT_SECRET_KEY=<secret_key
|
|
45
|
+
SPLIGHT_PLATFORM_API_HOST=https://api.splight-ai.com
|
|
46
|
+
COMPONENT_ID=<component_id>
|
|
47
|
+
```
|
|
48
|
+
Some of the variables are pointing to the production environment, but if you are
|
|
49
|
+
interested in using another environment like "integration" or maybe a local
|
|
50
|
+
environment you need to modify the corresponding variable value.
|
|
51
|
+
|
|
52
|
+
It is important to mention that the `splight-runner` should be used for components that
|
|
53
|
+
use the **Splight Lib** and **Splight CLI** grater than version `4.0.0`. For components
|
|
54
|
+
with older versions we can't ensure the proper functioning.
|
|
55
|
+
|
|
56
|
+
## Structure
|
|
57
|
+
|
|
58
|
+
You may be asking what the `splight-runner` does in order to do whatever it does.
|
|
59
|
+
Well, that's not an easy question to respond but let's try to explain a little bit.
|
|
60
|
+
|
|
61
|
+
So far, the package has two main functionalities, sending logs and reporting status.
|
|
62
|
+
With the two features we already have a lot of magic is happening, but maybe in the future
|
|
63
|
+
we will add some more.
|
|
64
|
+
|
|
65
|
+
`splight-runner` works as a wrapper around the process that will be executed,
|
|
66
|
+
this means some configurations are done, so the process in question is executed.
|
|
67
|
+
|
|
68
|
+
For example, when the command `run-component` is used, the environment variable
|
|
69
|
+
`PYTHONPATH` is modified in order to include the directory where the file `sitecustomize.py`
|
|
70
|
+
of the `splight-runner` is located. That file is a *Python* configuration file that can
|
|
71
|
+
be used to customize the behavior of Python's site module. The site module is
|
|
72
|
+
responsible for setting up Python's runtime environment, including configuring
|
|
73
|
+
paths, importing modules, and performing other site-specific tasks.
|
|
74
|
+
|
|
75
|
+
So, using this file we can modify the default behaviors of libraries, for example, we
|
|
76
|
+
can modify the import process of modules or libraries in order to change or add
|
|
77
|
+
a new behavior. This way we can modify the `logging` module and intercept
|
|
78
|
+
the logging process to show the logs messages and also send the logs messages
|
|
79
|
+
to the **Splight Engine**. Some similar implementation is used for
|
|
80
|
+
reporting the component's status.
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
## Development and Contributing
|
|
84
|
+
|
|
85
|
+
Since the main goal of the tool is to be used for running different kinds of
|
|
86
|
+
`Python` processes, is mandatory not to interfere with the process's
|
|
87
|
+
dependencies, this should be considered when adding new features for the
|
|
88
|
+
`splight-runner`. This is the reason why some basic functionalities
|
|
89
|
+
are implemented in the source code and not imported from a third-party
|
|
90
|
+
library, for example, a command parser for the different commands was created
|
|
91
|
+
in the code and no library was used like `click` or `typer`.
|
|
92
|
+
|
|
93
|
+
The package is fully dockerized, for testing new features you can use the
|
|
94
|
+
docker image that can be build with
|
|
95
|
+
the command
|
|
96
|
+
```bash
|
|
97
|
+
make build
|
|
98
|
+
```
|
|
99
|
+
For running the docker container the command is
|
|
100
|
+
```bash
|
|
101
|
+
make start
|
|
102
|
+
```
|
|
103
|
+
and to stop the container:
|
|
104
|
+
```bash
|
|
105
|
+
make stop
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
It is important to note that testing new features in the `splight-runner` is
|
|
109
|
+
not an easy task, you may need to modify the docker-compose file in order to
|
|
110
|
+
include new volumes.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "splight-runner"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
description = "Splight Runner"
|
|
5
|
+
authors = ["Splight Dev <dev@splight-ae.com>"]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
|
|
8
|
+
[tool.poetry.dependencies]
|
|
9
|
+
python = "^3.8"
|
|
10
|
+
requests = "^2.26.0"
|
|
11
|
+
|
|
12
|
+
[tool.poetry.group.dev.dependencies]
|
|
13
|
+
black = "23.3.0"
|
|
14
|
+
isort = "5.12.0"
|
|
15
|
+
ipdb = "^0.13.13"
|
|
16
|
+
ipython = "8.12.2"
|
|
17
|
+
|
|
18
|
+
[tool.poetry.scripts]
|
|
19
|
+
splight-runner = "splight_runner.runner:runner"
|
|
20
|
+
|
|
21
|
+
[build-system]
|
|
22
|
+
requires = ["poetry-core"]
|
|
23
|
+
build-backend = "poetry.core.masonry.api"
|
|
24
|
+
|
|
25
|
+
[tool.black]
|
|
26
|
+
line-length = 79
|
|
27
|
+
force-exclude = ".*_pb2.*py.*"
|
|
28
|
+
|
|
29
|
+
[tool.isort]
|
|
30
|
+
profile = "black"
|
|
31
|
+
line_length = 79
|
|
32
|
+
skip_glob = ["*_pb2*.py*"]
|
|
33
|
+
|
|
34
|
+
[tool.ruff]
|
|
35
|
+
line-length = 79
|
|
File without changes
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
|
|
3
|
+
from splight_runner.api.settings import settings
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class StatusReporter:
|
|
7
|
+
"""Class responsible for updating the component status each time the
|
|
8
|
+
healthcheck method is called.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
_SPL_PREFIX = "Splight"
|
|
12
|
+
_BASE_PATH = "v2/engine/component/components"
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self, api_host: str, access_id: str, secret_key: str, component_id: str
|
|
16
|
+
):
|
|
17
|
+
self._api_host = api_host.rstrip("/")
|
|
18
|
+
self._component_id = component_id
|
|
19
|
+
self._auth_header = {
|
|
20
|
+
"Authorization": f"{self._SPL_PREFIX} {access_id} {secret_key}"
|
|
21
|
+
}
|
|
22
|
+
self._prev_status: str = "Unknown"
|
|
23
|
+
|
|
24
|
+
def report_status(self, status: str) -> None:
|
|
25
|
+
"""Updates the status of the component in the Splight Platform making
|
|
26
|
+
POST request to the API.
|
|
27
|
+
|
|
28
|
+
Parameters
|
|
29
|
+
----------
|
|
30
|
+
status : str
|
|
31
|
+
The status of the component.
|
|
32
|
+
It can be "Running", "Stopped" or "Succeeded".
|
|
33
|
+
"""
|
|
34
|
+
if self._prev_status == status:
|
|
35
|
+
return None
|
|
36
|
+
url = f"{self._api_host}/{self._BASE_PATH}"
|
|
37
|
+
url = f"{url}/{self._component_id}/update-status/"
|
|
38
|
+
response = requests.post(
|
|
39
|
+
url, headers=self._auth_header, data={"deployment_status": status}
|
|
40
|
+
)
|
|
41
|
+
try:
|
|
42
|
+
response.raise_for_status()
|
|
43
|
+
except requests.exceptions.HTTPError as exc:
|
|
44
|
+
print("Unable to update component status")
|
|
45
|
+
print(exc)
|
|
46
|
+
self._prev_status = status
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
reporter = StatusReporter(
|
|
51
|
+
api_host=settings.splight_platform_api_host,
|
|
52
|
+
access_id=settings.access_id,
|
|
53
|
+
secret_key=settings.secret_key,
|
|
54
|
+
component_id=settings.process_id,
|
|
55
|
+
)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import traceback
|
|
2
|
+
from logging import LogRecord
|
|
3
|
+
|
|
4
|
+
from splight_runner.api.settings import settings
|
|
5
|
+
from splight_runner.log_streamer.log_streamer import ComponentLogsStreamer
|
|
6
|
+
from splight_runner.logging import log
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ApplicationLogInterceptor:
|
|
10
|
+
"""Class responsible for intercept logs records from the logging module
|
|
11
|
+
and append them into a queue for further processing. In particular for
|
|
12
|
+
sending the logs events to Splight GRPC API.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
host: str,
|
|
18
|
+
access_id: str,
|
|
19
|
+
secret_key: str,
|
|
20
|
+
process_id: str,
|
|
21
|
+
sender_type: str,
|
|
22
|
+
):
|
|
23
|
+
self._streamer = ComponentLogsStreamer(
|
|
24
|
+
host=host,
|
|
25
|
+
access_id=access_id,
|
|
26
|
+
secret_key=secret_key,
|
|
27
|
+
process_id=process_id,
|
|
28
|
+
)
|
|
29
|
+
self._sender_type = sender_type
|
|
30
|
+
self._streamer.start()
|
|
31
|
+
|
|
32
|
+
def save_record(self, record: LogRecord) -> None:
|
|
33
|
+
"""Saves the logging record to be sent.
|
|
34
|
+
|
|
35
|
+
Parameters
|
|
36
|
+
----------
|
|
37
|
+
record: LogRecord
|
|
38
|
+
The log record.
|
|
39
|
+
"""
|
|
40
|
+
exc_info = None
|
|
41
|
+
try:
|
|
42
|
+
message = str(record.msg) % record.args
|
|
43
|
+
except Exception as exc:
|
|
44
|
+
log(exc)
|
|
45
|
+
message = str(record.msg)
|
|
46
|
+
|
|
47
|
+
if record.exc_info:
|
|
48
|
+
exc_info = "".join(
|
|
49
|
+
traceback.format_exception(*record.exc_info)
|
|
50
|
+
).replace('"', "'")
|
|
51
|
+
message = str(record.msg)
|
|
52
|
+
else:
|
|
53
|
+
exc_info = ""
|
|
54
|
+
|
|
55
|
+
# tags attribute is not default in logger
|
|
56
|
+
tags = None
|
|
57
|
+
if hasattr(record, "tags"):
|
|
58
|
+
tags = getattr(record, "tags")
|
|
59
|
+
event = {
|
|
60
|
+
"sender_type": self._sender_type,
|
|
61
|
+
"name": record.name,
|
|
62
|
+
"message": message,
|
|
63
|
+
"loglevel": record.levelname,
|
|
64
|
+
"filename": record.filename,
|
|
65
|
+
"traceback": exc_info,
|
|
66
|
+
"tags": tags,
|
|
67
|
+
}
|
|
68
|
+
self._streamer.insert_message(event)
|
|
69
|
+
|
|
70
|
+
def flush(self) -> None:
|
|
71
|
+
"""Flushes the log records."""
|
|
72
|
+
self._streamer.flush()
|
|
73
|
+
|
|
74
|
+
def stop(self) -> None:
|
|
75
|
+
"""Stops the thread."""
|
|
76
|
+
self._streamer.stop()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
interceptor = ApplicationLogInterceptor(
|
|
80
|
+
host=settings.splight_platform_api_host,
|
|
81
|
+
access_id=settings.access_id,
|
|
82
|
+
secret_key=settings.secret_key,
|
|
83
|
+
process_id=settings.process_id,
|
|
84
|
+
sender_type=settings.process_type,
|
|
85
|
+
)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
from functools import partial
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
SECRET_DIR = "/etc/config"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class MissingEnvVariable(Exception):
|
|
10
|
+
...
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def load_variable(var_name: str) -> str:
|
|
14
|
+
secret_file = os.path.join(SECRET_DIR, var_name)
|
|
15
|
+
if os.path.exists(secret_file):
|
|
16
|
+
with open(secret_file) as f:
|
|
17
|
+
variable = f.read().strip()
|
|
18
|
+
elif os.getenv(var_name):
|
|
19
|
+
variable = os.getenv(var_name)
|
|
20
|
+
else:
|
|
21
|
+
raise MissingEnvVariable(f"{var_name} is missing")
|
|
22
|
+
return variable
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def load_multiple_variable(var_names: List[str]) -> str:
|
|
26
|
+
value = None
|
|
27
|
+
for var_name in var_names:
|
|
28
|
+
try:
|
|
29
|
+
value = load_variable(var_name)
|
|
30
|
+
break
|
|
31
|
+
except MissingEnvVariable:
|
|
32
|
+
continue
|
|
33
|
+
|
|
34
|
+
if value is None:
|
|
35
|
+
raise MissingEnvVariable(f"On of {var_names} is missing")
|
|
36
|
+
return value
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class SplightSettings:
|
|
41
|
+
"""Class for holding Splight Runner settings."""
|
|
42
|
+
|
|
43
|
+
access_id: str = field(
|
|
44
|
+
default_factory=partial(load_variable, "SPLIGHT_ACCESS_ID")
|
|
45
|
+
)
|
|
46
|
+
secret_key: str = field(
|
|
47
|
+
default_factory=partial(load_variable, "SPLIGHT_SECRET_KEY")
|
|
48
|
+
)
|
|
49
|
+
splight_platform_api_host: str = field(
|
|
50
|
+
default_factory=partial(load_variable, "SPLIGHT_PLATFORM_API_HOST")
|
|
51
|
+
)
|
|
52
|
+
process_id: str = field(
|
|
53
|
+
default_factory=partial(
|
|
54
|
+
load_multiple_variable,
|
|
55
|
+
["COMPONENT_ID", "COMPUTE_NODE_ID", "AGENT_ID"],
|
|
56
|
+
)
|
|
57
|
+
)
|
|
58
|
+
process_type: str = field(
|
|
59
|
+
default_factory=partial(load_variable, "PROCESS_TYPE")
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
settings = SplightSettings()
|
|
File without changes
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# This file is used for configuring splight runner in runtime. In particular
|
|
2
|
+
# loads some env vars and activate hooks for sending logs.
|
|
3
|
+
# All hooks or configurations for the Splight Runner should be loaded in
|
|
4
|
+
# this file.
|
|
5
|
+
|
|
6
|
+
import ast
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
runner_active = ast.literal_eval(os.getenv("SPLIGHT_RUNNER_ACTIVE", "False"))
|
|
11
|
+
|
|
12
|
+
boot_directory = os.path.dirname(__file__)
|
|
13
|
+
root_directory = os.path.dirname(os.path.dirname(boot_directory))
|
|
14
|
+
|
|
15
|
+
path = list(sys.path)
|
|
16
|
+
|
|
17
|
+
if boot_directory in path:
|
|
18
|
+
del path[path.index(boot_directory)]
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
from importlib.machinery import PathFinder
|
|
22
|
+
|
|
23
|
+
module_spec = PathFinder.find_spec("sitecustomize", path=path)
|
|
24
|
+
except ImportError as exc:
|
|
25
|
+
sys.stdout.write("Unable to import Splight Runner sitecustomie", exc)
|
|
26
|
+
sys.stdout.flush()
|
|
27
|
+
else:
|
|
28
|
+
if module_spec is not None:
|
|
29
|
+
module_spec.loader.load_module("sitecustomize")
|
|
30
|
+
|
|
31
|
+
if runner_active:
|
|
32
|
+
do_insert = root_directory not in sys.path
|
|
33
|
+
if do_insert:
|
|
34
|
+
sys.path.insert(0, root_directory)
|
|
35
|
+
|
|
36
|
+
import splight_runner.config
|
|
37
|
+
|
|
38
|
+
if do_insert:
|
|
39
|
+
del sys.path[sys.path.index(root_directory)]
|
|
File without changes
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import sys
|
|
3
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MissingCommandError(Exception):
|
|
7
|
+
"""Exception"""
|
|
8
|
+
|
|
9
|
+
...
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class MissingArgument(Exception):
|
|
13
|
+
"""Exception raises when there is a missing argument for a command"""
|
|
14
|
+
|
|
15
|
+
...
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CommandAdmin:
|
|
19
|
+
"""Class respobsible for taking registry of all the commands and callbaks"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, name: str, version: str):
|
|
22
|
+
"""Constructor
|
|
23
|
+
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
name : str
|
|
27
|
+
Name of the application
|
|
28
|
+
version : str
|
|
29
|
+
The application version
|
|
30
|
+
"""
|
|
31
|
+
self._name = name
|
|
32
|
+
self._version = version
|
|
33
|
+
self._commands: Dict[str, Callable] = {}
|
|
34
|
+
self._callback: Dict[str, Callable] = {}
|
|
35
|
+
|
|
36
|
+
def command(self, name: Optional[str] = None) -> Callable:
|
|
37
|
+
"""Decorator to register a command
|
|
38
|
+
|
|
39
|
+
Parameters
|
|
40
|
+
----------
|
|
41
|
+
name: Optional[str]
|
|
42
|
+
The name for the command
|
|
43
|
+
|
|
44
|
+
Returns
|
|
45
|
+
-------
|
|
46
|
+
Callable
|
|
47
|
+
The decorated function
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def decorator(func: Callable) -> Callable:
|
|
51
|
+
func_name = name or func.__name__
|
|
52
|
+
self._commands.update({func_name: func})
|
|
53
|
+
return func
|
|
54
|
+
|
|
55
|
+
return decorator
|
|
56
|
+
|
|
57
|
+
def callback(self, name: Optional[str] = None) -> Callable:
|
|
58
|
+
"""Decorator to register a callback
|
|
59
|
+
|
|
60
|
+
Parameters
|
|
61
|
+
----------
|
|
62
|
+
name: Optional[str]
|
|
63
|
+
The name for the command
|
|
64
|
+
|
|
65
|
+
Returns
|
|
66
|
+
-------
|
|
67
|
+
Callable
|
|
68
|
+
The decorated function
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
def decorator(func: Callable) -> Callable:
|
|
72
|
+
func_name = name or func.__name__
|
|
73
|
+
self._callback.update({func_name: func})
|
|
74
|
+
return func
|
|
75
|
+
|
|
76
|
+
return decorator
|
|
77
|
+
|
|
78
|
+
def print_commands(self) -> None:
|
|
79
|
+
"""Prints the commands and callbacks"""
|
|
80
|
+
title = f"{self._name} - {self._version}\n"
|
|
81
|
+
sys.stdout.write(title)
|
|
82
|
+
sys.stdout.write("{:=<{}}\n".format("", len(title)))
|
|
83
|
+
sys.stdout.write("Commands:\n")
|
|
84
|
+
for command in self._commands:
|
|
85
|
+
sys.stdout.write(f"{command: >20}\n")
|
|
86
|
+
sys.stdout.write("Callbacks:\n")
|
|
87
|
+
for callback in self._callback:
|
|
88
|
+
name = f"--{callback}"
|
|
89
|
+
sys.stdout.write(f"{name: >20}\n")
|
|
90
|
+
|
|
91
|
+
def __call__(self, *args: Any, **kwargs: Any):
|
|
92
|
+
"""Dunder method to run a given command."""
|
|
93
|
+
if len(sys.argv) == 1:
|
|
94
|
+
raise MissingCommandError("No command provided")
|
|
95
|
+
|
|
96
|
+
command_name = sys.argv[1]
|
|
97
|
+
raw_args = sys.argv[2:]
|
|
98
|
+
if command_name.startswith("--"):
|
|
99
|
+
command = self._retrieve_callback(command_name.lstrip("--"))
|
|
100
|
+
else:
|
|
101
|
+
command = self._retrieve_command(command_name)
|
|
102
|
+
|
|
103
|
+
signature = inspect.signature(command)
|
|
104
|
+
command_args = self._parse_arguments(raw_args, signature=signature)
|
|
105
|
+
command(**command_args)
|
|
106
|
+
|
|
107
|
+
def _parse_arguments(
|
|
108
|
+
self, raw_args: List, signature: inspect.Signature
|
|
109
|
+
) -> Dict:
|
|
110
|
+
arguments = {}
|
|
111
|
+
for idx, (name, parameter) in enumerate(signature.parameters.items()):
|
|
112
|
+
try:
|
|
113
|
+
raw_value = raw_args[idx]
|
|
114
|
+
except IndexError as exc:
|
|
115
|
+
raise MissingArgument(f"Missing argument {name}") from exc
|
|
116
|
+
|
|
117
|
+
argument = (
|
|
118
|
+
raw_value.split("=")[-1] if "=" in raw_value else raw_value
|
|
119
|
+
)
|
|
120
|
+
arg_value = parameter.annotation(argument)
|
|
121
|
+
arguments.update({name: arg_value})
|
|
122
|
+
return arguments
|
|
123
|
+
|
|
124
|
+
def _retrieve_command(self, command_name: str) -> Callable:
|
|
125
|
+
try:
|
|
126
|
+
command = self._commands[command_name]
|
|
127
|
+
except KeyError as exc:
|
|
128
|
+
sys.stderr.write(f"Command {command_name} not found: {exc}")
|
|
129
|
+
return command
|
|
130
|
+
|
|
131
|
+
def _retrieve_callback(self, command_name: str) -> Callable:
|
|
132
|
+
try:
|
|
133
|
+
command = self._callback[command_name]
|
|
134
|
+
except KeyError as exc:
|
|
135
|
+
sys.stderr.write(f"Callback {command_name} not found: {exc}")
|
|
136
|
+
return command
|