spaceforge 0.1.0.dev0__py3-none-any.whl → 1.0.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.
- spaceforge/__init__.py +12 -4
- spaceforge/__main__.py +3 -3
- spaceforge/_version.py +0 -1
- spaceforge/_version_scm.py +34 -0
- spaceforge/cls.py +24 -14
- spaceforge/conftest.py +89 -0
- spaceforge/generator.py +129 -56
- spaceforge/plugin.py +199 -22
- spaceforge/runner.py +3 -15
- spaceforge/schema.json +45 -22
- spaceforge/templates/binary_install.sh.j2 +24 -0
- spaceforge/templates/ensure_spaceforge_and_run.sh.j2 +24 -0
- spaceforge/{generator_test.py → test_generator.py} +265 -53
- spaceforge/test_generator_binaries.py +194 -0
- spaceforge/test_generator_core.py +180 -0
- spaceforge/test_generator_hooks.py +90 -0
- spaceforge/test_generator_parameters.py +59 -0
- spaceforge/test_plugin.py +357 -0
- spaceforge/test_plugin_file_operations.py +118 -0
- spaceforge/test_plugin_hooks.py +100 -0
- spaceforge/test_plugin_inheritance.py +102 -0
- spaceforge/{runner_test.py → test_runner.py} +5 -68
- spaceforge/test_runner_cli.py +69 -0
- spaceforge/test_runner_core.py +124 -0
- spaceforge/test_runner_execution.py +169 -0
- spaceforge-1.0.1.dist-info/METADATA +606 -0
- spaceforge-1.0.1.dist-info/RECORD +33 -0
- spaceforge/plugin_test.py +0 -621
- spaceforge-0.1.0.dev0.dist-info/METADATA +0 -163
- spaceforge-0.1.0.dev0.dist-info/RECORD +0 -19
- /spaceforge/{cls_test.py → test_cls.py} +0 -0
- {spaceforge-0.1.0.dev0.dist-info → spaceforge-1.0.1.dist-info}/WHEEL +0 -0
- {spaceforge-0.1.0.dev0.dist-info → spaceforge-1.0.1.dist-info}/entry_points.txt +0 -0
- {spaceforge-0.1.0.dev0.dist-info → spaceforge-1.0.1.dist-info}/licenses/LICENSE +0 -0
- {spaceforge-0.1.0.dev0.dist-info → spaceforge-1.0.1.dist-info}/top_level.txt +0 -0
spaceforge/__init__.py
CHANGED
|
@@ -4,10 +4,18 @@ Spaceforge - Spacelift Plugin Framework
|
|
|
4
4
|
A Python framework for building Spacelift plugins with hook-based functionality.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
from ._version import get_version
|
|
8
|
-
from .cls import
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
from spaceforge._version import get_version
|
|
8
|
+
from spaceforge.cls import (
|
|
9
|
+
Binary,
|
|
10
|
+
Context,
|
|
11
|
+
MountedFile,
|
|
12
|
+
Parameter,
|
|
13
|
+
Policy,
|
|
14
|
+
Variable,
|
|
15
|
+
Webhook,
|
|
16
|
+
)
|
|
17
|
+
from spaceforge.plugin import SpaceforgePlugin
|
|
18
|
+
from spaceforge.runner import PluginRunner
|
|
11
19
|
|
|
12
20
|
__version__ = get_version()
|
|
13
21
|
__all__ = [
|
spaceforge/__main__.py
CHANGED
|
@@ -4,9 +4,9 @@ Main entry point for spaceforge module.
|
|
|
4
4
|
|
|
5
5
|
import click
|
|
6
6
|
|
|
7
|
-
from ._version import get_version
|
|
8
|
-
from .generator import generate_command
|
|
9
|
-
from .runner import runner_command
|
|
7
|
+
from spaceforge._version import get_version
|
|
8
|
+
from spaceforge.generator import generate_command
|
|
9
|
+
from spaceforge.runner import runner_command
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
@click.group()
|
spaceforge/_version.py
CHANGED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
"__version__",
|
|
6
|
+
"__version_tuple__",
|
|
7
|
+
"version",
|
|
8
|
+
"version_tuple",
|
|
9
|
+
"__commit_id__",
|
|
10
|
+
"commit_id",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
TYPE_CHECKING = False
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from typing import Tuple
|
|
16
|
+
from typing import Union
|
|
17
|
+
|
|
18
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
19
|
+
COMMIT_ID = Union[str, None]
|
|
20
|
+
else:
|
|
21
|
+
VERSION_TUPLE = object
|
|
22
|
+
COMMIT_ID = object
|
|
23
|
+
|
|
24
|
+
version: str
|
|
25
|
+
__version__: str
|
|
26
|
+
__version_tuple__: VERSION_TUPLE
|
|
27
|
+
version_tuple: VERSION_TUPLE
|
|
28
|
+
commit_id: COMMIT_ID
|
|
29
|
+
__commit_id__: COMMIT_ID
|
|
30
|
+
|
|
31
|
+
__version__ = version = '1.0.1'
|
|
32
|
+
__version_tuple__ = version_tuple = (1, 0, 1)
|
|
33
|
+
|
|
34
|
+
__commit_id__ = commit_id = None
|
spaceforge/cls.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import uuid
|
|
1
2
|
from typing import Dict, List, Literal, Optional
|
|
2
3
|
|
|
3
4
|
from pydantic import Field
|
|
@@ -19,8 +20,7 @@ class Binary:
|
|
|
19
20
|
|
|
20
21
|
Attributes:
|
|
21
22
|
name (str): The name of the binary file.
|
|
22
|
-
|
|
23
|
-
sensitive (bool): Whether the binary file is sensitive.
|
|
23
|
+
download_urls (Dict[BinaryType, str]): A dictionary mapping binary types to their download URLs.
|
|
24
24
|
"""
|
|
25
25
|
|
|
26
26
|
name: str
|
|
@@ -38,6 +38,7 @@ class Parameter:
|
|
|
38
38
|
sensitive (bool): Whether the parameter contains sensitive information.
|
|
39
39
|
required (bool): Whether the parameter is required.
|
|
40
40
|
default (Optional[str]): The default value of the parameter, if any. (required if sensitive is False)
|
|
41
|
+
id (str): Unique identifier for the parameter.
|
|
41
42
|
"""
|
|
42
43
|
|
|
43
44
|
name: str
|
|
@@ -45,6 +46,7 @@ class Parameter:
|
|
|
45
46
|
sensitive: bool = False
|
|
46
47
|
required: bool = False
|
|
47
48
|
default: Optional[str] = None
|
|
49
|
+
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
|
48
50
|
|
|
49
51
|
def __post_init__(self) -> None:
|
|
50
52
|
if not self.required and self.default is None:
|
|
@@ -116,7 +118,7 @@ class Context:
|
|
|
116
118
|
Attributes:
|
|
117
119
|
name_prefix (str): The name of the context, will be appended with a unique ID.
|
|
118
120
|
description (str): A description of the context.
|
|
119
|
-
labels (
|
|
121
|
+
labels (Optional[List[str]]): Labels associated with the context.
|
|
120
122
|
env (list): List of variables associated with the context.
|
|
121
123
|
hooks (dict): Hooks associated with the context.
|
|
122
124
|
"""
|
|
@@ -126,7 +128,7 @@ class Context:
|
|
|
126
128
|
env: Optional[List[Variable]] = optional_field
|
|
127
129
|
mounted_files: Optional[List[MountedFile]] = optional_field
|
|
128
130
|
hooks: Optional[Dict[HookType, List[str]]] = optional_field
|
|
129
|
-
labels: Optional[
|
|
131
|
+
labels: Optional[List[str]] = optional_field
|
|
130
132
|
|
|
131
133
|
|
|
132
134
|
@pydantic_dataclass
|
|
@@ -137,14 +139,17 @@ class Webhook:
|
|
|
137
139
|
Attributes:
|
|
138
140
|
name_prefix (str): The name of the webhook, will be appended with a unique ID.
|
|
139
141
|
endpoint (str): The URL endpoint for the webhook.
|
|
140
|
-
labels (Optional[
|
|
141
|
-
|
|
142
|
+
labels (Optional[List[str]]): Labels associated with the webhook.
|
|
143
|
+
secret (str): the ID of the parameter where the webhook secret is retrieved from
|
|
142
144
|
"""
|
|
143
145
|
|
|
144
146
|
name_prefix: str
|
|
145
147
|
endpoint: str
|
|
146
|
-
|
|
147
|
-
|
|
148
|
+
secretFromParameter: Optional[str] = optional_field
|
|
149
|
+
labels: Optional[List[str]] = optional_field
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
PolicyTypes = Literal["PUSH", "PLAN", "TRIGGER", "APPROVAL", "NOTIFICATION"]
|
|
148
153
|
|
|
149
154
|
|
|
150
155
|
@pydantic_dataclass
|
|
@@ -156,13 +161,13 @@ class Policy:
|
|
|
156
161
|
name_prefix (str): The name of the policy, will be appended with a unique ID.
|
|
157
162
|
type (str): The type of the policy (e.g., "terraform", "kubernetes").
|
|
158
163
|
body (str): The body of the policy, typically a configuration or script.
|
|
159
|
-
labels (Optional[
|
|
164
|
+
labels (Optional[List[str]]): Labels associated with the policy.
|
|
160
165
|
"""
|
|
161
166
|
|
|
162
167
|
name_prefix: str
|
|
163
|
-
type:
|
|
168
|
+
type: PolicyTypes
|
|
164
169
|
body: str
|
|
165
|
-
labels: Optional[
|
|
170
|
+
labels: Optional[List[str]] = optional_field
|
|
166
171
|
|
|
167
172
|
|
|
168
173
|
@pydantic_dataclass
|
|
@@ -171,19 +176,21 @@ class PluginManifest:
|
|
|
171
176
|
A class to represent the manifest of a Spacelift plugin.
|
|
172
177
|
|
|
173
178
|
Attributes:
|
|
174
|
-
|
|
179
|
+
name (str): The name of the plugin, will be appended with a unique ID.
|
|
175
180
|
description (str): A description of the plugin.
|
|
176
181
|
author (str): The author of the plugin.
|
|
182
|
+
labels (list[str]): List of labels for the plugin.
|
|
177
183
|
parameters (list[Parameter]): List of parameters for the plugin.
|
|
178
184
|
contexts (list[Context]): List of contexts for the plugin.
|
|
179
185
|
webhooks (list[Webhook]): List of webhooks for the plugin.
|
|
180
186
|
policies (list[Policy]): List of policies for the plugin.
|
|
181
187
|
"""
|
|
182
188
|
|
|
183
|
-
|
|
189
|
+
name: str
|
|
184
190
|
version: str
|
|
185
191
|
description: str
|
|
186
192
|
author: str
|
|
193
|
+
labels: Optional[List[str]] = optional_field
|
|
187
194
|
parameters: Optional[List[Parameter]] = optional_field
|
|
188
195
|
contexts: Optional[List[Context]] = optional_field
|
|
189
196
|
webhooks: Optional[List[Webhook]] = optional_field
|
|
@@ -195,4 +202,7 @@ if __name__ == "__main__":
|
|
|
195
202
|
|
|
196
203
|
from pydantic import TypeAdapter
|
|
197
204
|
|
|
198
|
-
|
|
205
|
+
schema = TypeAdapter(PluginManifest).json_schema()
|
|
206
|
+
schema["$schema"] = "http://json-schema.org/draft-07/schema#"
|
|
207
|
+
|
|
208
|
+
print(json.dumps(schema, indent=2))
|
spaceforge/conftest.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Shared test fixtures for spaceforge tests."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import tempfile
|
|
5
|
+
from typing import Dict, Generator, List
|
|
6
|
+
from unittest.mock import Mock
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from spaceforge.plugin import SpaceforgePlugin
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.fixture
|
|
14
|
+
def temp_dir() -> Generator[str, None, None]:
|
|
15
|
+
"""Provide a temporary directory for test files."""
|
|
16
|
+
temp_dir = tempfile.mkdtemp()
|
|
17
|
+
yield temp_dir
|
|
18
|
+
import shutil
|
|
19
|
+
|
|
20
|
+
shutil.rmtree(temp_dir, ignore_errors=True)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@pytest.fixture
|
|
24
|
+
def test_plugin_content() -> str:
|
|
25
|
+
"""Basic test plugin content."""
|
|
26
|
+
return """
|
|
27
|
+
from spaceforge import SpaceforgePlugin, Parameter
|
|
28
|
+
|
|
29
|
+
class TestPlugin(SpaceforgePlugin):
|
|
30
|
+
__plugin_name__ = "test"
|
|
31
|
+
__version__ = "1.0.0"
|
|
32
|
+
__author__ = "Test Author"
|
|
33
|
+
|
|
34
|
+
__parameters__ = [
|
|
35
|
+
Parameter(
|
|
36
|
+
name="test_param",
|
|
37
|
+
description="Test parameter",
|
|
38
|
+
required=False,
|
|
39
|
+
default="default_value"
|
|
40
|
+
)
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
def after_plan(self) -> None:
|
|
44
|
+
pass
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@pytest.fixture
|
|
49
|
+
def test_plugin_file(temp_dir: str, test_plugin_content: str) -> str:
|
|
50
|
+
"""Create a test plugin file."""
|
|
51
|
+
plugin_path = os.path.join(temp_dir, "plugin.py")
|
|
52
|
+
with open(plugin_path, "w") as f:
|
|
53
|
+
f.write(test_plugin_content)
|
|
54
|
+
return plugin_path
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@pytest.fixture
|
|
58
|
+
def mock_env() -> Dict[str, str]:
|
|
59
|
+
"""Common test environment variables."""
|
|
60
|
+
return {
|
|
61
|
+
"SPACELIFT_API_TOKEN": "test_token",
|
|
62
|
+
"TF_VAR_spacelift_graphql_endpoint": "https://test.spacelift.io",
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@pytest.fixture
|
|
67
|
+
def mock_api_response() -> Mock:
|
|
68
|
+
"""Mock API response for testing."""
|
|
69
|
+
mock_response = Mock()
|
|
70
|
+
mock_response.read.return_value = b'{"data": {"test": "result"}}'
|
|
71
|
+
return mock_response
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class ExampleTestPlugin(SpaceforgePlugin):
|
|
75
|
+
"""Reusable test plugin class."""
|
|
76
|
+
|
|
77
|
+
__plugin_name__ = "example"
|
|
78
|
+
__version__ = "1.0.0"
|
|
79
|
+
__author__ = "Test"
|
|
80
|
+
|
|
81
|
+
def __init__(self) -> None:
|
|
82
|
+
super().__init__()
|
|
83
|
+
self.hook_calls: List[str] = []
|
|
84
|
+
|
|
85
|
+
def after_plan(self) -> None:
|
|
86
|
+
self.hook_calls.append("after_plan")
|
|
87
|
+
|
|
88
|
+
def before_apply(self) -> None:
|
|
89
|
+
self.hook_calls.append("before_apply")
|
spaceforge/generator.py
CHANGED
|
@@ -4,9 +4,11 @@ YAML generator for Spacelift plugins.
|
|
|
4
4
|
|
|
5
5
|
import importlib.util
|
|
6
6
|
import os
|
|
7
|
-
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Type
|
|
7
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Type, Union
|
|
8
8
|
|
|
9
9
|
import yaml
|
|
10
|
+
from jinja2 import Environment, PackageLoader, select_autoescape
|
|
11
|
+
from mergedeep import Strategy, merge # type: ignore
|
|
10
12
|
|
|
11
13
|
if TYPE_CHECKING:
|
|
12
14
|
from .plugin import SpaceforgePlugin
|
|
@@ -46,6 +48,10 @@ class PluginGenerator:
|
|
|
46
48
|
self.plugin_class: Optional[Type[SpaceforgePlugin]] = None
|
|
47
49
|
self.plugin_instance: Optional[SpaceforgePlugin] = None
|
|
48
50
|
self.plugin_working_directory: Optional[str] = None
|
|
51
|
+
self.config: Optional[Dict[str, Any]] = None
|
|
52
|
+
self.jinja = Environment(
|
|
53
|
+
loader=PackageLoader("spaceforge"), autoescape=select_autoescape()
|
|
54
|
+
)
|
|
49
55
|
|
|
50
56
|
def load_plugin(self) -> None:
|
|
51
57
|
"""Load the plugin class from the specified path."""
|
|
@@ -78,10 +84,18 @@ class PluginGenerator:
|
|
|
78
84
|
self.plugin_class = plugin_class
|
|
79
85
|
self.plugin_instance = plugin_class()
|
|
80
86
|
self.plugin_working_directory = (
|
|
81
|
-
"/mnt/workspace/plugins/"
|
|
87
|
+
"/mnt/workspace/plugins/"
|
|
88
|
+
+ plugin_class.__plugin_name__.lower().replace(" ", "_")
|
|
82
89
|
)
|
|
90
|
+
self.config = {
|
|
91
|
+
"setup_virtual_env": (
|
|
92
|
+
f"cd {self.plugin_working_directory} && python -m venv ./venv && "
|
|
93
|
+
+ "source venv/bin/activate && (command -v spaceforge &> /dev/null || pip install spaceforge)"
|
|
94
|
+
),
|
|
95
|
+
"plugin_mounted_path": f"{self.plugin_working_directory}/{os.path.basename(self.plugin_path)}",
|
|
96
|
+
}
|
|
83
97
|
|
|
84
|
-
def get_plugin_metadata(self) -> Dict[str, str]:
|
|
98
|
+
def get_plugin_metadata(self) -> Dict[str, Union[str, List[str]]]:
|
|
85
99
|
"""Extract metadata from the plugin class."""
|
|
86
100
|
if self.plugin_class is None:
|
|
87
101
|
raise ValueError("Plugin class not loaded. Call load_plugin() first.")
|
|
@@ -97,6 +111,7 @@ class PluginGenerator:
|
|
|
97
111
|
self.plugin_class.__name__.lower().replace("plugin", ""),
|
|
98
112
|
),
|
|
99
113
|
"version": getattr(self.plugin_class, "__version__", "1.0.0"),
|
|
114
|
+
"labels": getattr(self.plugin_class, "__labels__", []),
|
|
100
115
|
"description": doc
|
|
101
116
|
or f"A Spacelift plugin built with {self.plugin_class.__name__}",
|
|
102
117
|
"author": getattr(self.plugin_class, "__author__", "Unknown"),
|
|
@@ -128,22 +143,27 @@ class PluginGenerator:
|
|
|
128
143
|
|
|
129
144
|
return hook_methods
|
|
130
145
|
|
|
131
|
-
def
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
+
def _add_to_mounted_files(
|
|
147
|
+
self,
|
|
148
|
+
hooks: Dict[str, List[str]],
|
|
149
|
+
mounted_files: List[MountedFile],
|
|
150
|
+
phase: str,
|
|
151
|
+
filepath: str,
|
|
152
|
+
filecontent: str,
|
|
153
|
+
) -> None:
|
|
154
|
+
file = f"{self.plugin_working_directory}/{filepath}"
|
|
155
|
+
hooks[phase].append(f"chmod +x {file} && {file}")
|
|
156
|
+
mounted_files.append(
|
|
157
|
+
MountedFile(
|
|
158
|
+
path=f"{self.plugin_working_directory}/{filepath}",
|
|
159
|
+
content=filecontent,
|
|
160
|
+
sensitive=False,
|
|
146
161
|
)
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
def _update_with_requirements(self, mounted_files: List[MountedFile]) -> None:
|
|
165
|
+
"""Update the plugin hooks if there is a requirements.txt"""
|
|
166
|
+
if os.path.exists("requirements.txt") and self.config is not None:
|
|
147
167
|
# read the requirements.txt file
|
|
148
168
|
with open("requirements.txt", "r") as f:
|
|
149
169
|
mounted_files.append(
|
|
@@ -154,33 +174,84 @@ class PluginGenerator:
|
|
|
154
174
|
)
|
|
155
175
|
)
|
|
156
176
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
)
|
|
161
|
-
if os.path.exists(self.plugin_path):
|
|
177
|
+
def _update_with_python_file(self, mounted_files: List[MountedFile]) -> None:
|
|
178
|
+
"""Ensure the plugin file itself is mounted."""
|
|
179
|
+
if os.path.exists(self.plugin_path) and self.config is not None:
|
|
162
180
|
with open(self.plugin_path, "r") as f:
|
|
163
181
|
mounted_files.append(
|
|
164
182
|
MountedFile(
|
|
165
|
-
path=plugin_mounted_path,
|
|
183
|
+
path=self.config["plugin_mounted_path"],
|
|
166
184
|
content=f.read(),
|
|
167
185
|
sensitive=False,
|
|
168
186
|
)
|
|
169
187
|
)
|
|
170
188
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
189
|
+
def _add_spaceforge_hooks(
|
|
190
|
+
self,
|
|
191
|
+
hooks: Dict[str, List[str]],
|
|
192
|
+
mounted_files: List[MountedFile],
|
|
193
|
+
has_binaries: bool,
|
|
194
|
+
) -> None:
|
|
175
195
|
# Add the spaceforge hook to actually run the plugin
|
|
196
|
+
if self.config is None:
|
|
197
|
+
raise ValueError("Plugin config not set. Call load_plugin() first.")
|
|
198
|
+
|
|
176
199
|
available_hooks = self.get_available_hooks()
|
|
177
200
|
for hook in available_hooks:
|
|
178
201
|
# Ensure the hook exists in the first context
|
|
179
202
|
if hook not in hooks:
|
|
180
203
|
hooks[hook] = []
|
|
181
|
-
|
|
182
|
-
|
|
204
|
+
|
|
205
|
+
directory = os.path.dirname(self.config["plugin_mounted_path"])
|
|
206
|
+
template = self.jinja.get_template("ensure_spaceforge_and_run.sh.j2")
|
|
207
|
+
render = template.render(
|
|
208
|
+
plugin_path=directory,
|
|
209
|
+
plugin_file=self.config["plugin_mounted_path"],
|
|
210
|
+
phase=hook,
|
|
211
|
+
has_binaries=has_binaries,
|
|
183
212
|
)
|
|
213
|
+
self._add_to_mounted_files(hooks, mounted_files, hook, f"{hook}.sh", render)
|
|
214
|
+
|
|
215
|
+
def _map_variables_to_parameters(self, contexts: List[Context]) -> None:
|
|
216
|
+
for context in contexts:
|
|
217
|
+
# Get the variables from the plugin and change the value_from_parameter to the ID of the parameter
|
|
218
|
+
# based on its name.
|
|
219
|
+
if context.env is None:
|
|
220
|
+
continue
|
|
221
|
+
|
|
222
|
+
for variable in context.env:
|
|
223
|
+
if variable.value_from_parameter:
|
|
224
|
+
parameter_name = variable.value_from_parameter
|
|
225
|
+
parameters = self.get_plugin_parameters()
|
|
226
|
+
if parameters:
|
|
227
|
+
parameter = next(
|
|
228
|
+
(
|
|
229
|
+
p
|
|
230
|
+
for p in parameters
|
|
231
|
+
if p.name == parameter_name or p.id == parameter_name
|
|
232
|
+
),
|
|
233
|
+
None,
|
|
234
|
+
)
|
|
235
|
+
if parameter:
|
|
236
|
+
variable.value_from_parameter = parameter.id
|
|
237
|
+
else:
|
|
238
|
+
raise ValueError(
|
|
239
|
+
f"Parameter {parameter_name} not found for variable {variable.key}"
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
def get_plugin_contexts(self) -> List[Context]:
|
|
243
|
+
"""Get context definitions from the plugin class."""
|
|
244
|
+
|
|
245
|
+
f"cd {self.plugin_working_directory}"
|
|
246
|
+
hooks: Dict[str, List[str]] = {
|
|
247
|
+
"before_init": [f"mkdir -p {self.plugin_working_directory}"]
|
|
248
|
+
}
|
|
249
|
+
mounted_files: List[MountedFile] = []
|
|
250
|
+
|
|
251
|
+
self._update_with_requirements(mounted_files)
|
|
252
|
+
self._update_with_python_file(mounted_files)
|
|
253
|
+
has_binaries = self._generate_binary_install_command(hooks, mounted_files)
|
|
254
|
+
self._add_spaceforge_hooks(hooks, mounted_files, has_binaries)
|
|
184
255
|
|
|
185
256
|
# Get the contexts and append the hooks and mounted files to it.
|
|
186
257
|
if self.plugin_class is None:
|
|
@@ -205,45 +276,46 @@ class PluginGenerator:
|
|
|
205
276
|
contexts[0].env = []
|
|
206
277
|
|
|
207
278
|
# Add the hooks and mounted files to the first context
|
|
208
|
-
contexts[0].hooks.
|
|
209
|
-
contexts[0].mounted_files
|
|
279
|
+
merge(contexts[0].hooks, hooks, strategy=Strategy.TYPESAFE_ADDITIVE)
|
|
280
|
+
contexts[0].mounted_files += mounted_files
|
|
281
|
+
|
|
282
|
+
self._map_variables_to_parameters(contexts)
|
|
210
283
|
|
|
211
284
|
return contexts
|
|
212
285
|
|
|
213
|
-
def
|
|
286
|
+
def _generate_binary_install_command(
|
|
287
|
+
self, hooks: Dict[str, List[str]], mounted_files: List[MountedFile]
|
|
288
|
+
) -> bool:
|
|
214
289
|
binaries = self.get_plugin_binaries()
|
|
215
290
|
if binaries is None:
|
|
216
|
-
return
|
|
291
|
+
return False
|
|
217
292
|
|
|
218
|
-
binary_cmd = ""
|
|
219
|
-
if len(binaries) > 0:
|
|
220
|
-
binary_cmd = f"mkdir -p {static_binary_directory} && cd {static_binary_directory} && "
|
|
221
293
|
for i, binary in enumerate(binaries):
|
|
222
294
|
amd64_url = binary.download_urls.get("amd64", None)
|
|
223
295
|
arm64_url = binary.download_urls.get("arm64", None)
|
|
296
|
+
binary_path = f"{static_binary_directory}/{binary.name}"
|
|
224
297
|
if amd64_url is None and arm64_url is None:
|
|
225
298
|
raise ValueError(
|
|
226
299
|
f"Binary {binary.name} must have at least one download URL defined (amd64 or arm64)"
|
|
227
300
|
)
|
|
228
301
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
302
|
+
template = self.jinja.get_template("binary_install.sh.j2")
|
|
303
|
+
render = template.render(
|
|
304
|
+
binary=binary,
|
|
305
|
+
amd64_url=amd64_url,
|
|
306
|
+
arm64_url=arm64_url,
|
|
307
|
+
binary_path=binary_path,
|
|
308
|
+
static_binary_directory=static_binary_directory,
|
|
234
309
|
)
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
310
|
+
self._add_to_mounted_files(
|
|
311
|
+
hooks,
|
|
312
|
+
mounted_files,
|
|
313
|
+
"before_init",
|
|
314
|
+
f"binary_install_{binary.name}.sh",
|
|
315
|
+
render,
|
|
239
316
|
)
|
|
240
317
|
|
|
241
|
-
|
|
242
|
-
amd64_download_command, arm64_download_command
|
|
243
|
-
)
|
|
244
|
-
if i < len(binaries) - 1:
|
|
245
|
-
binary_cmd += " && "
|
|
246
|
-
return binary_cmd
|
|
318
|
+
return True
|
|
247
319
|
|
|
248
320
|
def get_plugin_binaries(self) -> Optional[List[Binary]]:
|
|
249
321
|
"""Get binary definitions from the plugin class."""
|
|
@@ -265,10 +337,11 @@ class PluginGenerator:
|
|
|
265
337
|
metadata = self.get_plugin_metadata()
|
|
266
338
|
|
|
267
339
|
return PluginManifest(
|
|
268
|
-
|
|
269
|
-
version=metadata.get("version", "1.0.0"),
|
|
270
|
-
description=metadata.get("description", ""),
|
|
271
|
-
author=metadata.get("author", "Unknown"),
|
|
340
|
+
name=str(metadata.get("name_prefix", "unknown")),
|
|
341
|
+
version=str(metadata.get("version", "1.0.0")),
|
|
342
|
+
description=str(metadata.get("description", "")),
|
|
343
|
+
author=str(metadata.get("author", "Unknown")),
|
|
344
|
+
labels=list(metadata.get("labels", [])),
|
|
272
345
|
parameters=self.get_plugin_parameters(),
|
|
273
346
|
contexts=self.get_plugin_contexts(),
|
|
274
347
|
webhooks=self.get_plugin_webhooks(),
|