splight-cli 4.9.0__tar.gz → 4.11.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_cli-4.9.0 → splight_cli-4.11.0}/PKG-INFO +13 -10
- {splight_cli-4.9.0 → splight_cli-4.11.0}/README.md +11 -8
- {splight_cli-4.9.0 → splight_cli-4.11.0}/pyproject.toml +3 -3
- splight_cli-4.11.0/splight_cli/solution/__init__.py +88 -0
- splight_cli-4.11.0/splight_cli/solution/dict.py +50 -0
- splight_cli-4.11.0/splight_cli/solution/parser.py +103 -0
- splight_cli-4.11.0/splight_cli/solution/resource/__init__.py +5 -0
- splight_cli-4.11.0/splight_cli/solution/resource/diff.py +39 -0
- splight_cli-4.11.0/splight_cli/solution/resource/logger.py +36 -0
- splight_cli-4.11.0/splight_cli/solution/resource/manager.py +237 -0
- splight_cli-4.11.0/splight_cli/solution/resource/models/__init__.py +25 -0
- splight_cli-4.11.0/splight_cli/solution/resource/models/alert.py +14 -0
- splight_cli-4.11.0/splight_cli/solution/resource/models/asset.py +8 -0
- splight_cli-4.11.0/splight_cli/solution/resource/models/attribute.py +8 -0
- splight_cli-4.11.0/splight_cli/solution/resource/models/base.py +77 -0
- splight_cli-4.11.0/splight_cli/solution/resource/models/component.py +12 -0
- splight_cli-4.11.0/splight_cli/solution/resource/models/component_object.py +8 -0
- splight_cli-4.11.0/splight_cli/solution/resource/models/file.py +8 -0
- splight_cli-4.11.0/splight_cli/solution/resource/models/function.py +14 -0
- splight_cli-4.11.0/splight_cli/solution/resource/models/metadata.py +8 -0
- splight_cli-4.11.0/splight_cli/solution/resource/models/routine.py +9 -0
- splight_cli-4.11.0/splight_cli/solution/resource/models/secret.py +38 -0
- splight_cli-4.11.0/splight_cli/solution/state.py +56 -0
- splight_cli-4.9.0/splight_cli/solution/__init__.py +0 -103
- splight_cli-4.9.0/splight_cli/solution/apply_exec.py +0 -180
- splight_cli-4.9.0/splight_cli/solution/destroyer.py +0 -42
- splight_cli-4.9.0/splight_cli/solution/exceptions.py +0 -6
- splight_cli-4.9.0/splight_cli/solution/importer.py +0 -87
- splight_cli-4.9.0/splight_cli/solution/models.py +0 -45
- splight_cli-4.9.0/splight_cli/solution/plan_exec.py +0 -123
- splight_cli-4.9.0/splight_cli/solution/replacer.py +0 -357
- splight_cli-4.9.0/splight_cli/solution/solution.py +0 -466
- splight_cli-4.9.0/splight_cli/solution/solution_checker.py +0 -409
- splight_cli-4.9.0/splight_cli/solution/tests/constants.py +0 -559
- splight_cli-4.9.0/splight_cli/solution/tests/test_apply.py +0 -244
- splight_cli-4.9.0/splight_cli/solution/tests/test_destroy.py +0 -107
- splight_cli-4.9.0/splight_cli/solution/tests/test_import.py +0 -154
- splight_cli-4.9.0/splight_cli/solution/tests/test_plan.py +0 -52
- splight_cli-4.9.0/splight_cli/solution/utils.py +0 -80
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/__init__.py +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/cli.py +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/component/__init__.py +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/component/component.py +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/component/exceptions.py +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/component/loaders.py +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/component/templates/.splightignore +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/component/templates/Initialization +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/component/templates/README.md +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/component/templates/auto_readme.md +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/component/templates/main.py +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/component/templates/spec.json +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/component/templates/tests.py +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/component/tests/__init__.py +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/component/tests/test_component_manager.py +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/component/utils.py +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/config/__init__.py +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/constants.py +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/context/__init__.py +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/context/context.py +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/context/workspace.py +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/engine/__init__.py +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/engine/alert/__init__.py +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/engine/asset/__init__.py +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/engine/attribute/__init__.py +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/engine/component/__init__.py +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/engine/datalake/__init__.py +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/engine/file/__init__.py +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/engine/manager/__init__.py +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/engine/manager/exceptions.py +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/engine/manager/manager.py +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/engine/manager/tests/test_datalake_manager.py +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/engine/manager/tests/test_resource_manager.py +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/engine/secret/__init__.py +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/hub/__init__.py +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/hub/component/__init__.py +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/hub/component/exceptions.py +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/hub/component/hub_manager.py +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/hub/component/tests/test_hub_manager.py +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/settings.py +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/tests/test_component/Initialization +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/tests/test_component/README.md +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/tests/test_component/main.py +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/tests/test_component/spec.json +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/tests/test_component/tests.py +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/utils/__init__.py +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/utils/input.py +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/utils/json.py +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/utils/loader.py +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/utils/pprint.py +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/utils/template.py +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/utils/yaml.py +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/version.py +0 -0
- {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/workspace/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: splight-cli
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.11.0
|
|
4
4
|
Summary: Splight Command Line Interface
|
|
5
5
|
Author: Splight Dev
|
|
6
6
|
Author-email: dev@splight-ae.com
|
|
@@ -15,8 +15,6 @@ Requires-Dist: case-converter (==1.1.0)
|
|
|
15
15
|
Requires-Dist: click (==8.0.4)
|
|
16
16
|
Requires-Dist: click-default-group (==1.2.2)
|
|
17
17
|
Requires-Dist: colorama (==0.4.4)
|
|
18
|
-
Requires-Dist: deepdiff (==6.3.1)
|
|
19
|
-
Requires-Dist: mock (>=5.1.0,<6.0.0)
|
|
20
18
|
Requires-Dist: packaging (==23.0)
|
|
21
19
|
Requires-Dist: pathspec (==0.11.1)
|
|
22
20
|
Requires-Dist: py7zr (==0.20.8)
|
|
@@ -24,14 +22,12 @@ Requires-Dist: requests (==2.31.0)
|
|
|
24
22
|
Requires-Dist: rich (==13.0.1)
|
|
25
23
|
Requires-Dist: setuptools (==60.9.3)
|
|
26
24
|
Requires-Dist: splight-lib (>=5.2.2,<6.0.0)
|
|
25
|
+
Requires-Dist: toposort (>=1.10,<2.0)
|
|
27
26
|
Requires-Dist: typer (==0.9.0)
|
|
28
27
|
Description-Content-Type: text/markdown
|
|
29
28
|
|
|
30
29
|
# Splight CLI
|
|
31
30
|
|
|
32
|
-

|
|
33
|
-

|
|
34
|
-
|
|
35
31
|
---
|
|
36
32
|
|
|
37
33
|
## Table of Content
|
|
@@ -76,6 +72,7 @@ pip install splight-cli
|
|
|
76
72
|
```
|
|
77
73
|
|
|
78
74
|
Once you have installed `splight-cli` you can check the installation with
|
|
75
|
+
|
|
79
76
|
```bash
|
|
80
77
|
splight --version
|
|
81
78
|
```
|
|
@@ -251,12 +248,15 @@ You can also retrieve one of the parameters using the `get` command:
|
|
|
251
248
|
```bash
|
|
252
249
|
splight configure get <parameter>
|
|
253
250
|
```
|
|
251
|
+
|
|
254
252
|
for example for reading the `SPLIGHT_PLATFORM_API_HOST`:
|
|
253
|
+
|
|
255
254
|
```bash
|
|
256
255
|
splight configure get splight_platform_api_host
|
|
257
256
|
```
|
|
258
257
|
|
|
259
258
|
In the same way you can modify one parameter using the `set` command
|
|
259
|
+
|
|
260
260
|
```bash
|
|
261
261
|
splight configure set <parameter> <value>
|
|
262
262
|
```
|
|
@@ -311,6 +311,7 @@ The `engine` command is used for interacting with the Splight Engine. So far, th
|
|
|
311
311
|
subcommands provide an interface for creating, reaading and deleting resources in the engine.
|
|
312
312
|
|
|
313
313
|
The command is
|
|
314
|
+
|
|
314
315
|
```bash
|
|
315
316
|
splight engine <subcommand> <action> [extra args]
|
|
316
317
|
```
|
|
@@ -319,6 +320,7 @@ depending on the which sub-command you use you can get different actions
|
|
|
319
320
|
to perform
|
|
320
321
|
|
|
321
322
|
The valid sub-commands so far are:
|
|
323
|
+
|
|
322
324
|
- `asset`
|
|
323
325
|
- `attribute`
|
|
324
326
|
- `component`
|
|
@@ -344,7 +346,7 @@ The available subcommands are
|
|
|
344
346
|
|
|
345
347
|
- `create <name>` to create a new workspace. After the creation of a new worskpace you need to configure _Splight CLI_ with `splight configure`.
|
|
346
348
|
- `delete <name>` to delete an existing workspace.
|
|
347
|
-
- `list` to list all workspaces. Current workspace displays a '
|
|
349
|
+
- `list` to list all workspaces. Current workspace displays a '\*' to the left.
|
|
348
350
|
- `select <name>` to switch between different configured workspaces.
|
|
349
351
|
- `show <name>` to display the contents of an existing workspace.
|
|
350
352
|
|
|
@@ -580,10 +582,10 @@ splight component run <component directory> --local
|
|
|
580
582
|
```
|
|
581
583
|
|
|
582
584
|
This way, the component will run using local clients for database and datalake. This is extremely
|
|
583
|
-
useful for development since you can create instances of the different database objects in the
|
|
584
|
-
local database for running different scenearios or differents tests. The same can be applied for
|
|
585
|
+
useful for development since you can create instances of the different database objects in the
|
|
586
|
+
local database for running different scenearios or differents tests. The same can be applied for
|
|
585
587
|
datalake data, the local client stores the data in files. In both cases, for database and datalake,
|
|
586
|
-
the files are created in the same directory as the `__init__.py` file of the component, so you
|
|
588
|
+
the files are created in the same directory as the `__init__.py` file of the component, so you
|
|
587
589
|
can modified it based on your needs.
|
|
588
590
|
|
|
589
591
|
You can interact with the local databases using the library, for example
|
|
@@ -605,3 +607,4 @@ db_client.save(attribute)
|
|
|
605
607
|
|
|
606
608
|
df = client.get_dataframe(Number, asset=all_assets[0].id, attribute=attribute.id)
|
|
607
609
|
```
|
|
610
|
+
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
# Splight CLI
|
|
2
2
|
|
|
3
|
-

|
|
4
|
-

|
|
5
|
-
|
|
6
3
|
---
|
|
7
4
|
|
|
8
5
|
## Table of Content
|
|
@@ -47,6 +44,7 @@ pip install splight-cli
|
|
|
47
44
|
```
|
|
48
45
|
|
|
49
46
|
Once you have installed `splight-cli` you can check the installation with
|
|
47
|
+
|
|
50
48
|
```bash
|
|
51
49
|
splight --version
|
|
52
50
|
```
|
|
@@ -222,12 +220,15 @@ You can also retrieve one of the parameters using the `get` command:
|
|
|
222
220
|
```bash
|
|
223
221
|
splight configure get <parameter>
|
|
224
222
|
```
|
|
223
|
+
|
|
225
224
|
for example for reading the `SPLIGHT_PLATFORM_API_HOST`:
|
|
225
|
+
|
|
226
226
|
```bash
|
|
227
227
|
splight configure get splight_platform_api_host
|
|
228
228
|
```
|
|
229
229
|
|
|
230
230
|
In the same way you can modify one parameter using the `set` command
|
|
231
|
+
|
|
231
232
|
```bash
|
|
232
233
|
splight configure set <parameter> <value>
|
|
233
234
|
```
|
|
@@ -282,6 +283,7 @@ The `engine` command is used for interacting with the Splight Engine. So far, th
|
|
|
282
283
|
subcommands provide an interface for creating, reaading and deleting resources in the engine.
|
|
283
284
|
|
|
284
285
|
The command is
|
|
286
|
+
|
|
285
287
|
```bash
|
|
286
288
|
splight engine <subcommand> <action> [extra args]
|
|
287
289
|
```
|
|
@@ -290,6 +292,7 @@ depending on the which sub-command you use you can get different actions
|
|
|
290
292
|
to perform
|
|
291
293
|
|
|
292
294
|
The valid sub-commands so far are:
|
|
295
|
+
|
|
293
296
|
- `asset`
|
|
294
297
|
- `attribute`
|
|
295
298
|
- `component`
|
|
@@ -315,7 +318,7 @@ The available subcommands are
|
|
|
315
318
|
|
|
316
319
|
- `create <name>` to create a new workspace. After the creation of a new worskpace you need to configure _Splight CLI_ with `splight configure`.
|
|
317
320
|
- `delete <name>` to delete an existing workspace.
|
|
318
|
-
- `list` to list all workspaces. Current workspace displays a '
|
|
321
|
+
- `list` to list all workspaces. Current workspace displays a '\*' to the left.
|
|
319
322
|
- `select <name>` to switch between different configured workspaces.
|
|
320
323
|
- `show <name>` to display the contents of an existing workspace.
|
|
321
324
|
|
|
@@ -551,10 +554,10 @@ splight component run <component directory> --local
|
|
|
551
554
|
```
|
|
552
555
|
|
|
553
556
|
This way, the component will run using local clients for database and datalake. This is extremely
|
|
554
|
-
useful for development since you can create instances of the different database objects in the
|
|
555
|
-
local database for running different scenearios or differents tests. The same can be applied for
|
|
557
|
+
useful for development since you can create instances of the different database objects in the
|
|
558
|
+
local database for running different scenearios or differents tests. The same can be applied for
|
|
556
559
|
datalake data, the local client stores the data in files. In both cases, for database and datalake,
|
|
557
|
-
the files are created in the same directory as the `__init__.py` file of the component, so you
|
|
560
|
+
the files are created in the same directory as the `__init__.py` file of the component, so you
|
|
558
561
|
can modified it based on your needs.
|
|
559
562
|
|
|
560
563
|
You can interact with the local databases using the library, for example
|
|
@@ -575,4 +578,4 @@ attribute = Attribute(name="SomeAttribute")
|
|
|
575
578
|
db_client.save(attribute)
|
|
576
579
|
|
|
577
580
|
df = client.get_dataframe(Number, asset=all_assets[0].id, attribute=attribute.id)
|
|
578
|
-
```
|
|
581
|
+
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "splight-cli"
|
|
3
|
-
version = "4.
|
|
3
|
+
version = "4.11.0"
|
|
4
4
|
description = "Splight Command Line Interface"
|
|
5
5
|
authors = ["Splight Dev <dev@splight-ae.com>"]
|
|
6
6
|
readme = "README.md"
|
|
@@ -20,8 +20,7 @@ PyYAML = "6.0.1"
|
|
|
20
20
|
rich = "13.0.1"
|
|
21
21
|
pathspec = "0.11.1"
|
|
22
22
|
packaging = "23.0"
|
|
23
|
-
|
|
24
|
-
mock = "^5.1.0"
|
|
23
|
+
toposort = "^1.10"
|
|
25
24
|
|
|
26
25
|
[tool.poetry.group.dev.dependencies]
|
|
27
26
|
black = "23.3.0"
|
|
@@ -32,6 +31,7 @@ flake8 = "^6.1.0"
|
|
|
32
31
|
pre-commit = "3.2.2"
|
|
33
32
|
pytest = "^7.4.3"
|
|
34
33
|
coverage = "^7.3.2"
|
|
34
|
+
mock = "^5.1.0"
|
|
35
35
|
|
|
36
36
|
[tool.poetry.scripts]
|
|
37
37
|
splight = "splight_cli.cli:app"
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from rich.console import Console
|
|
3
|
+
|
|
4
|
+
from splight_cli.context import check_credentials
|
|
5
|
+
from splight_cli.solution.parser import Parser
|
|
6
|
+
from splight_cli.solution.resource import ResourceManager
|
|
7
|
+
from splight_cli.solution.state import State
|
|
8
|
+
|
|
9
|
+
solution_app = typer.Typer(
|
|
10
|
+
name="Splight Solution",
|
|
11
|
+
add_completion=True,
|
|
12
|
+
rich_markup_mode="rich",
|
|
13
|
+
no_args_is_help=True,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
console = Console()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@solution_app.callback(invoke_without_command=True)
|
|
20
|
+
def callback(ctx: typer.Context):
|
|
21
|
+
check_credentials(ctx)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# FIXME: find a nice way to print the exceptions.
|
|
25
|
+
# Take into account that most exception messages are short and do not have
|
|
26
|
+
# that much content. I would include the traceback, or at least some part of it.
|
|
27
|
+
# Another option is to not catch them, and show the entire error, so the user can
|
|
28
|
+
# debug it.
|
|
29
|
+
@solution_app.command()
|
|
30
|
+
def apply(
|
|
31
|
+
_: typer.Context,
|
|
32
|
+
spec_file: str = typer.Argument(..., help="Path to the spec file."),
|
|
33
|
+
state_file: str = typer.Option(
|
|
34
|
+
"state.json", "--state", "-s", help="Path to the state file."
|
|
35
|
+
),
|
|
36
|
+
) -> None:
|
|
37
|
+
state = State(path=state_file)
|
|
38
|
+
state.load()
|
|
39
|
+
|
|
40
|
+
parser = Parser(spec_file=spec_file)
|
|
41
|
+
specs, dependency_graph = parser.parse()
|
|
42
|
+
|
|
43
|
+
manager = ResourceManager(
|
|
44
|
+
state=state,
|
|
45
|
+
specs=specs,
|
|
46
|
+
dependency_graph=dependency_graph,
|
|
47
|
+
)
|
|
48
|
+
manager.refresh()
|
|
49
|
+
plan = manager.plan()
|
|
50
|
+
if plan:
|
|
51
|
+
manager.apply()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@solution_app.command()
|
|
55
|
+
def plan(
|
|
56
|
+
_: typer.Context,
|
|
57
|
+
spec_file: str = typer.Argument(..., help="Path to the spec file."),
|
|
58
|
+
state_file: str = typer.Option(
|
|
59
|
+
"state.json", "--state", "-s", help="Path to the state file."
|
|
60
|
+
),
|
|
61
|
+
) -> None:
|
|
62
|
+
state = State(path=state_file)
|
|
63
|
+
state.load()
|
|
64
|
+
|
|
65
|
+
parser = Parser(spec_file=spec_file)
|
|
66
|
+
specs, dependency_graph = parser.parse()
|
|
67
|
+
|
|
68
|
+
manager = ResourceManager(
|
|
69
|
+
state=state,
|
|
70
|
+
specs=specs,
|
|
71
|
+
dependency_graph=dependency_graph,
|
|
72
|
+
)
|
|
73
|
+
manager.refresh()
|
|
74
|
+
manager.plan()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@solution_app.command()
|
|
78
|
+
def refresh(
|
|
79
|
+
_: typer.Context,
|
|
80
|
+
state_file: str = typer.Option(
|
|
81
|
+
"state.json", "--state", "-s", help="Path to the state file."
|
|
82
|
+
),
|
|
83
|
+
) -> None:
|
|
84
|
+
state = State(path=state_file)
|
|
85
|
+
state.load()
|
|
86
|
+
|
|
87
|
+
manager = ResourceManager(state=state)
|
|
88
|
+
manager.refresh()
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from typing import Any, Dict, List
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# FIXME: make this faster using a stack
|
|
5
|
+
def walk_dict(data: Dict, current=[]):
|
|
6
|
+
result = []
|
|
7
|
+
|
|
8
|
+
if isinstance(data, Dict):
|
|
9
|
+
for key, value in data.items():
|
|
10
|
+
current.append(key)
|
|
11
|
+
result.extend(walk_dict(value, current.copy()))
|
|
12
|
+
current.pop()
|
|
13
|
+
elif isinstance(data, List):
|
|
14
|
+
for index, item in enumerate(data):
|
|
15
|
+
current.append(index)
|
|
16
|
+
result.extend(walk_dict(item, current.copy()))
|
|
17
|
+
current.pop()
|
|
18
|
+
else:
|
|
19
|
+
result.append((current.copy(), data))
|
|
20
|
+
|
|
21
|
+
return result
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_dict_value(path: List, data: Dict):
|
|
25
|
+
# Assumes the path exists
|
|
26
|
+
current = data
|
|
27
|
+
for key in path:
|
|
28
|
+
if isinstance(current, dict):
|
|
29
|
+
current = current[key]
|
|
30
|
+
elif isinstance(current, list):
|
|
31
|
+
current = current[int(key)]
|
|
32
|
+
else:
|
|
33
|
+
raise ValueError(f"Invalid path: {path}")
|
|
34
|
+
return current
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def set_dict_value(value: Any, path: List, data: Dict):
|
|
38
|
+
# Assumes the path exists
|
|
39
|
+
current = data
|
|
40
|
+
for key in path[:-1]:
|
|
41
|
+
current = current[key]
|
|
42
|
+
|
|
43
|
+
last_key = path[-1]
|
|
44
|
+
|
|
45
|
+
if isinstance(current, dict):
|
|
46
|
+
current[last_key] = value
|
|
47
|
+
elif isinstance(current, list):
|
|
48
|
+
current[int(last_key)] = value
|
|
49
|
+
else:
|
|
50
|
+
raise ValueError(f"Invalid path: {path}")
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import Dict, List, Optional
|
|
3
|
+
|
|
4
|
+
import yaml
|
|
5
|
+
|
|
6
|
+
from splight_cli.solution.dict import walk_dict
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class DuplicateResourceError(Exception):
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def parse_reference(value: str) -> Optional[Dict]:
|
|
14
|
+
"""Return the reference if it matches the regex or None otherwise"""
|
|
15
|
+
# FIXME: make it so we are able to reference the 'name' of a resource.
|
|
16
|
+
# This is super easy to do:
|
|
17
|
+
# 1. Add '.id' and '.name' at the end of the regex
|
|
18
|
+
# 2. Save that accessor to the references dict in each spec.
|
|
19
|
+
# 3. Change the 'replace_references' function in ResourceManager so it
|
|
20
|
+
# gets the correct attribute.
|
|
21
|
+
|
|
22
|
+
# Only process strings
|
|
23
|
+
if isinstance(value, str):
|
|
24
|
+
# Building this with format strings breaks the expression
|
|
25
|
+
pattern = r"^\${{(Asset|Attribute|Metadata|File|Function|Secret|Alert|Component|Routine)\.(\w+)}}$"
|
|
26
|
+
|
|
27
|
+
# Get the matches
|
|
28
|
+
match = re.match(pattern, value)
|
|
29
|
+
|
|
30
|
+
if match:
|
|
31
|
+
type, name = match.groups()
|
|
32
|
+
key = f"{type}:{name}"
|
|
33
|
+
return key
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class Parser:
|
|
37
|
+
def __init__(self, spec_file: str) -> None:
|
|
38
|
+
try:
|
|
39
|
+
with open(spec_file, "r") as fp:
|
|
40
|
+
self._spec_file_data = yaml.safe_load(fp)
|
|
41
|
+
except FileNotFoundError:
|
|
42
|
+
raise Exception(f"Spec file '{spec_file}' not found.")
|
|
43
|
+
|
|
44
|
+
def parse(self) -> List[Dict]:
|
|
45
|
+
# Here we build the dependency graph using the resource keys
|
|
46
|
+
dependency_graph = {}
|
|
47
|
+
|
|
48
|
+
# - Does not destroy all elements.
|
|
49
|
+
specs = {}
|
|
50
|
+
for resource_spec in self._spec_file_data or []:
|
|
51
|
+
name = resource_spec["name"]
|
|
52
|
+
type = resource_spec["type"]
|
|
53
|
+
arguments = resource_spec["arguments"]
|
|
54
|
+
|
|
55
|
+
# Parse each resource leaf value to see if its a reference
|
|
56
|
+
depends_on = set({})
|
|
57
|
+
references = []
|
|
58
|
+
for path, value in walk_dict(resource_spec["arguments"]):
|
|
59
|
+
result = parse_reference(value)
|
|
60
|
+
if result is not None:
|
|
61
|
+
key = result
|
|
62
|
+
|
|
63
|
+
# Must be done here, otherwise it would affect the dependency graph.
|
|
64
|
+
# Paths and values are validated later on, since we may need data
|
|
65
|
+
# that is available after creation.
|
|
66
|
+
# This also prevents trying to delete a resource which has another
|
|
67
|
+
# one referencing it.
|
|
68
|
+
# Also prevents self-referencing.
|
|
69
|
+
if key not in specs:
|
|
70
|
+
raise ValueError(
|
|
71
|
+
f"Reference '{value}' in resource '{name}' of type '{type}' points to a non existing resource."
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
reference = {
|
|
75
|
+
"key": key, # Key of the referenced resource
|
|
76
|
+
"target": path, # Where to put the value
|
|
77
|
+
"string": value, # The string value of the reference
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
references.append(reference)
|
|
81
|
+
depends_on.add(key)
|
|
82
|
+
|
|
83
|
+
key = f"{type}:{name}"
|
|
84
|
+
if key in specs:
|
|
85
|
+
raise DuplicateResourceError(
|
|
86
|
+
f"Resource '{name}' of type '{type}' is defined twice."
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
specs[key] = {
|
|
90
|
+
"name": name,
|
|
91
|
+
"type": type,
|
|
92
|
+
"depends_on": list(depends_on),
|
|
93
|
+
"references": references,
|
|
94
|
+
"arguments": arguments,
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
# Add its dependecies to the graph
|
|
98
|
+
dependency_graph[key] = depends_on
|
|
99
|
+
|
|
100
|
+
# NOTE: Before adding/changing features of the parser, insert a breakpoint
|
|
101
|
+
# here and inspect the specs and dependency_graph objects, to see the shape
|
|
102
|
+
# of the objects that the rest of the code needs.
|
|
103
|
+
return specs, dependency_graph
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from typing import Dict
|
|
2
|
+
|
|
3
|
+
from colorama import Fore, Style
|
|
4
|
+
|
|
5
|
+
from splight_cli.solution.dict import get_dict_value, walk_dict
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def strike_text(text):
|
|
9
|
+
return "".join(["\u0336{}".format(c) for c in str(text)])
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def Diff(new_arguments: Dict, old_arguments: Dict) -> Dict:
|
|
13
|
+
# FIXME: this try catch mechanic could be simplified by improving
|
|
14
|
+
# the 'Dict' related functions.
|
|
15
|
+
|
|
16
|
+
# FIXME: Try replacing the QueryFilter in a function for a single value.
|
|
17
|
+
# The diff will show incorrectly.
|
|
18
|
+
diff = []
|
|
19
|
+
for path, new_value in walk_dict(new_arguments):
|
|
20
|
+
try:
|
|
21
|
+
get_dict_value(path, old_arguments)
|
|
22
|
+
except:
|
|
23
|
+
line = f"\t{Fore.GREEN}+{Style.RESET_ALL} {'.'.join([str(key) for key in path])}: {new_value}"
|
|
24
|
+
diff.append(line)
|
|
25
|
+
continue
|
|
26
|
+
|
|
27
|
+
old_value = get_dict_value(path, old_arguments)
|
|
28
|
+
if new_value != old_value:
|
|
29
|
+
line = f"\t{Fore.YELLOW}~{Style.RESET_ALL} {'.'.join([str(key) for key in path])}: {strike_text(old_value)} -> {new_value} "
|
|
30
|
+
diff.append(line)
|
|
31
|
+
|
|
32
|
+
for path, old_value in walk_dict(old_arguments):
|
|
33
|
+
try:
|
|
34
|
+
get_dict_value(path, new_arguments)
|
|
35
|
+
except:
|
|
36
|
+
line = f"\t{Fore.RED}-{Style.RESET_ALL} {'.'.join([str(key) for key in path])}: {strike_text(old_value)}"
|
|
37
|
+
diff.append(line)
|
|
38
|
+
|
|
39
|
+
return diff
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from colorama import Style
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ResourceLogger:
|
|
5
|
+
template = '{type}["{name}"]: {message} [id={id}]'
|
|
6
|
+
|
|
7
|
+
@staticmethod
|
|
8
|
+
def _bold(message: str) -> str:
|
|
9
|
+
return f"{Style.BRIGHT}{message}{Style.RESET_ALL}"
|
|
10
|
+
|
|
11
|
+
def resource(self, message: str, resource):
|
|
12
|
+
formatted_message = self.template.format(
|
|
13
|
+
type=resource.type,
|
|
14
|
+
name=resource.name,
|
|
15
|
+
id=resource.id,
|
|
16
|
+
message=message,
|
|
17
|
+
)
|
|
18
|
+
print(self._bold(formatted_message))
|
|
19
|
+
|
|
20
|
+
def event(
|
|
21
|
+
self,
|
|
22
|
+
message: str,
|
|
23
|
+
previous_line: bool = False,
|
|
24
|
+
new_line: bool = False,
|
|
25
|
+
bold: bool = False,
|
|
26
|
+
):
|
|
27
|
+
formatted_message = ""
|
|
28
|
+
if previous_line:
|
|
29
|
+
formatted_message += "\n"
|
|
30
|
+
formatted_message += f"{self._bold(message)}" if bold else message
|
|
31
|
+
if new_line:
|
|
32
|
+
formatted_message += "\n"
|
|
33
|
+
print(formatted_message)
|
|
34
|
+
|
|
35
|
+
def diff(self, diff):
|
|
36
|
+
print("\n".join(diff))
|