spaceforge 0.0.3__tar.gz → 0.0.5__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.
Files changed (54) hide show
  1. {spaceforge-0.0.3 → spaceforge-0.0.5}/PKG-INFO +11 -1
  2. {spaceforge-0.0.3 → spaceforge-0.0.5}/README.md +9 -0
  3. {spaceforge-0.0.3 → spaceforge-0.0.5}/plugins/sops/plugin.yaml +53 -4
  4. {spaceforge-0.0.3 → spaceforge-0.0.5}/plugins/wiz/plugin.py +6 -4
  5. {spaceforge-0.0.3 → spaceforge-0.0.5}/plugins/wiz/plugin.yaml +60 -8
  6. {spaceforge-0.0.3 → spaceforge-0.0.5}/pyproject.toml +1 -0
  7. {spaceforge-0.0.3 → spaceforge-0.0.5}/setup.py +1 -0
  8. {spaceforge-0.0.3 → spaceforge-0.0.5}/spaceforge/_version_scm.py +3 -3
  9. {spaceforge-0.0.3 → spaceforge-0.0.5}/spaceforge/cls.py +8 -8
  10. {spaceforge-0.0.3 → spaceforge-0.0.5}/spaceforge/generator.py +53 -40
  11. {spaceforge-0.0.3 → spaceforge-0.0.5}/spaceforge/plugin.py +29 -23
  12. {spaceforge-0.0.3 → spaceforge-0.0.5}/spaceforge/schema.json +15 -24
  13. spaceforge-0.0.5/spaceforge/templates/binary_install.sh.j2 +23 -0
  14. spaceforge-0.0.5/spaceforge/templates/ensure_spaceforge_and_run.sh.j2 +22 -0
  15. {spaceforge-0.0.3 → spaceforge-0.0.5}/spaceforge/test_generator.py +159 -58
  16. {spaceforge-0.0.3 → spaceforge-0.0.5}/spaceforge/test_generator_binaries.py +40 -13
  17. {spaceforge-0.0.3 → spaceforge-0.0.5}/spaceforge.egg-info/PKG-INFO +11 -1
  18. {spaceforge-0.0.3 → spaceforge-0.0.5}/spaceforge.egg-info/SOURCES.txt +3 -1
  19. {spaceforge-0.0.3 → spaceforge-0.0.5}/spaceforge.egg-info/requires.txt +1 -0
  20. {spaceforge-0.0.3 → spaceforge-0.0.5}/test.sh +1 -1
  21. {spaceforge-0.0.3 → spaceforge-0.0.5}/.github/workflows/ci.yml +0 -0
  22. {spaceforge-0.0.3 → spaceforge-0.0.5}/.github/workflows/release.yml +0 -0
  23. {spaceforge-0.0.3 → spaceforge-0.0.5}/.gitignore +0 -0
  24. {spaceforge-0.0.3 → spaceforge-0.0.5}/LICENSE +0 -0
  25. {spaceforge-0.0.3 → spaceforge-0.0.5}/MANIFEST.in +0 -0
  26. {spaceforge-0.0.3 → spaceforge-0.0.5}/go.mod +0 -0
  27. {spaceforge-0.0.3 → spaceforge-0.0.5}/plugins/infracost/plugin.py +0 -0
  28. {spaceforge-0.0.3 → spaceforge-0.0.5}/plugins/infracost/plugin.yaml +0 -0
  29. {spaceforge-0.0.3 → spaceforge-0.0.5}/plugins/sops/plugin.py +0 -0
  30. {spaceforge-0.0.3 → spaceforge-0.0.5}/plugins/sops/requirements.txt +0 -0
  31. {spaceforge-0.0.3 → spaceforge-0.0.5}/setup.cfg +0 -0
  32. {spaceforge-0.0.3 → spaceforge-0.0.5}/spaceforge/README.md +0 -0
  33. {spaceforge-0.0.3 → spaceforge-0.0.5}/spaceforge/__init__.py +0 -0
  34. {spaceforge-0.0.3 → spaceforge-0.0.5}/spaceforge/__main__.py +0 -0
  35. {spaceforge-0.0.3 → spaceforge-0.0.5}/spaceforge/_version.py +0 -0
  36. {spaceforge-0.0.3 → spaceforge-0.0.5}/spaceforge/conftest.py +0 -0
  37. {spaceforge-0.0.3 → spaceforge-0.0.5}/spaceforge/runner.py +0 -0
  38. {spaceforge-0.0.3 → spaceforge-0.0.5}/spaceforge/test_cls.py +0 -0
  39. {spaceforge-0.0.3 → spaceforge-0.0.5}/spaceforge/test_generator_core.py +0 -0
  40. {spaceforge-0.0.3 → spaceforge-0.0.5}/spaceforge/test_generator_hooks.py +0 -0
  41. {spaceforge-0.0.3 → spaceforge-0.0.5}/spaceforge/test_generator_parameters.py +0 -0
  42. {spaceforge-0.0.3 → spaceforge-0.0.5}/spaceforge/test_plugin.py +0 -0
  43. {spaceforge-0.0.3 → spaceforge-0.0.5}/spaceforge/test_plugin_file_operations.py +0 -0
  44. {spaceforge-0.0.3 → spaceforge-0.0.5}/spaceforge/test_plugin_hooks.py +0 -0
  45. {spaceforge-0.0.3 → spaceforge-0.0.5}/spaceforge/test_plugin_inheritance.py +0 -0
  46. {spaceforge-0.0.3 → spaceforge-0.0.5}/spaceforge/test_runner.py +0 -0
  47. {spaceforge-0.0.3 → spaceforge-0.0.5}/spaceforge/test_runner_cli.py +0 -0
  48. {spaceforge-0.0.3 → spaceforge-0.0.5}/spaceforge/test_runner_core.py +0 -0
  49. {spaceforge-0.0.3 → spaceforge-0.0.5}/spaceforge/test_runner_execution.py +0 -0
  50. {spaceforge-0.0.3 → spaceforge-0.0.5}/spaceforge.egg-info/dependency_links.txt +0 -0
  51. {spaceforge-0.0.3 → spaceforge-0.0.5}/spaceforge.egg-info/entry_points.txt +0 -0
  52. {spaceforge-0.0.3 → spaceforge-0.0.5}/spaceforge.egg-info/not-zip-safe +0 -0
  53. {spaceforge-0.0.3 → spaceforge-0.0.5}/spaceforge.egg-info/top_level.txt +0 -0
  54. {spaceforge-0.0.3 → spaceforge-0.0.5}/templates.go +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spaceforge
3
- Version: 0.0.3
3
+ Version: 0.0.5
4
4
  Summary: A Python framework for building Spacelift plugins
5
5
  Home-page: https://github.com/spacelift-io/plugins
6
6
  Author: Spacelift
@@ -28,6 +28,7 @@ License-File: LICENSE
28
28
  Requires-Dist: PyYAML>=6.0
29
29
  Requires-Dist: click>=8.0.0
30
30
  Requires-Dist: pydantic>=2.11.7
31
+ Requires-Dist: Jinja2>=3.1.0
31
32
  Provides-Extra: dev
32
33
  Requires-Dist: pytest>=6.0; extra == "dev"
33
34
  Requires-Dist: pytest-cov; extra == "dev"
@@ -584,6 +585,15 @@ export SPACEFORGE_PARAM_SEVERITY_THRESHOLD="high"
584
585
  spaceforge runner after_plan
585
586
  ```
586
587
 
588
+ ## Speeding up plugin execution
589
+
590
+ There are a few things you can do to speed up plugin execution.
591
+
592
+ 1. Ensure your runner has `spaceforge` preinstalled. This will avoid the overhead of installing it during the run. (15-30 seconds)
593
+ 2. If youre using binaries, we will only install the binary if its not found. You can gain a few seconds by ensuring its already on the runner.
594
+ 3. If your plugin has a lot of dependencies, consider using a prebuilt runner image with your plugin and its dependencies installed. This avoids the overhead of installing them during each run.
595
+ 4. Ensure your runner has enough core resources (CPU, memory) to handle the plugin execution efficiently. If your plugin is resource-intensive, consider using a more powerful runner.
596
+
587
597
  ## Next Steps
588
598
 
589
599
  1. **Install spaceforge:** `pip install spaceforge`
@@ -540,6 +540,15 @@ export SPACEFORGE_PARAM_SEVERITY_THRESHOLD="high"
540
540
  spaceforge runner after_plan
541
541
  ```
542
542
 
543
+ ## Speeding up plugin execution
544
+
545
+ There are a few things you can do to speed up plugin execution.
546
+
547
+ 1. Ensure your runner has `spaceforge` preinstalled. This will avoid the overhead of installing it during the run. (15-30 seconds)
548
+ 2. If youre using binaries, we will only install the binary if its not found. You can gain a few seconds by ensuring its already on the runner.
549
+ 3. If your plugin has a lot of dependencies, consider using a prebuilt runner image with your plugin and its dependencies installed. This avoids the overhead of installing them during each run.
550
+ 4. Ensure your runner has enough core resources (CPU, memory) to handle the plugin execution efficiently. If your plugin is resource-intensive, consider using a more powerful runner.
551
+
543
552
  ## Next Steps
544
553
 
545
554
  1. **Install spaceforge:** `pip install spaceforge`
@@ -131,10 +131,59 @@ contexts:
131
131
  except Exception as e:
132
132
  self.logger.error(f"An unexpected error occurred: {e}")
133
133
  sensitive: false
134
+ - path: /mnt/workspace/plugins/sops/binary_install_sops.sh
135
+ content: |-
136
+ #!/bin/sh
137
+
138
+ set -e
139
+
140
+ if command -v sops; then
141
+ echo "sops is already installed."
142
+ return
143
+ fi
144
+
145
+ mkdir -p /mnt/workspace/plugins/plugin_binaries
146
+
147
+ echo "Installing sops..."
148
+ mkdir -p /mnt/workspace/plugins/plugin_binaries
149
+ cd /mnt/workspace/plugins/plugin_binaries
150
+
151
+ if [ "$(arch)" = "x86_64" ]; then
152
+ curl https://github.com/getsops/sops/releases/download/v3.9.1/sops-v3.9.1.linux.amd64 -o /mnt/workspace/plugins/plugin_binaries/sops -L
153
+ else
154
+ curl https://github.com/getsops/sops/releases/download/v3.9.1/sops-v3.9.1.linux.arm64 -o /mnt/workspace/plugins/plugin_binaries/sops -L
155
+ fi
156
+
157
+ chmod +x /mnt/workspace/plugins/plugin_binaries/sops
158
+ cd /mnt/workspace/source/$TF_VAR_spacelift_project_root
159
+ sensitive: false
160
+ - path: /mnt/workspace/plugins/sops/before_init.sh
161
+ content: |-
162
+ #!/bin/sh
163
+
164
+ set -e
165
+
166
+ cd /mnt/workspace/plugins/sops
167
+
168
+ if [ ! -d "./venv" ]; then
169
+ python -m venv ./venv
170
+ fi
171
+ . venv/bin/activate
172
+
173
+ if ! command -v spaceforge; then
174
+ pip install spaceforge
175
+ fi
176
+
177
+ if [ -f requirements.txt ] && [ ! -f .spaceforge_installed_requirements ]; then
178
+ pip install -r requirements.txt
179
+ touch .spaceforge_installed_requirements
180
+ fi
181
+
182
+ cd /mnt/workspace/source/$TF_VAR_spacelift_project_root
183
+ spaceforge runner --plugin-file /mnt/workspace/plugins/sops/plugin.py before_init
184
+ sensitive: false
134
185
  hooks:
135
186
  before_init:
136
187
  - mkdir -p /mnt/workspace/plugins/sops
137
- - cd /mnt/workspace/plugins/sops && python -m venv ./venv && source venv/bin/activate && pip install spaceforge
138
- - pip install -r requirements.txt
139
- - mkdir -p /mnt/workspace/plugins/plugin_binaries && cd /mnt/workspace/plugins/plugin_binaries && ([[ "$(echo "$(arch)")" == "x86_64" ]] && curl https://github.com/getsops/sops/releases/download/v3.9.1/sops-v3.9.1.linux.amd64 -o /mnt/workspace/plugins/plugin_binaries/sops -L && chmod +x /mnt/workspace/plugins/plugin_binaries/sops || curl https://github.com/getsops/sops/releases/download/v3.9.1/sops-v3.9.1.linux.arm64 -o /mnt/workspace/plugins/plugin_binaries/sops -L && chmod +x /mnt/workspace/plugins/plugin_binaries/sops) && cd /mnt/workspace/source/$TF_VAR_spacelift_project_root
140
- - cd /mnt/workspace/source/$TF_VAR_spacelift_project_root && python -m spaceforge runner --plugin-file /mnt/workspace/plugins/sops/plugin.py before_init
188
+ - chmod +x /mnt/workspace/plugins/sops/binary_install_sops.sh && /mnt/workspace/plugins/sops/binary_install_sops.sh
189
+ - chmod +x /mnt/workspace/plugins/sops/before_init.sh && /mnt/workspace/plugins/sops/before_init.sh
@@ -88,9 +88,9 @@ webhook[{"endpoint_id": "wiz-alert-endpoint"}] {
88
88
  input.run_updated.run.marked_unsafe == true
89
89
  }
90
90
  """,
91
- labels={
92
- "policy_type": "security"
93
- }
91
+ labels=[
92
+ "wiz-plugin"
93
+ ]
94
94
  )
95
95
  ]
96
96
 
@@ -179,4 +179,6 @@ webhook[{"endpoint_id": "wiz-alert-endpoint"}] {
179
179
  markdown += "\n"
180
180
  if "reportUrl" in stdout_json:
181
181
  markdown += f"<a href=\"{stdout_json['reportUrl']}\" rel=\"noopener noreferrer\">View Report</a>\n"
182
- self.send_markdown(markdown)
182
+ result = self.send_markdown(markdown)
183
+ if not result:
184
+ self.logger.error("Failed to send Wiz CLI output to spacelift")
@@ -134,9 +134,9 @@ contexts:
134
134
  input.run_updated.run.marked_unsafe == true
135
135
  }
136
136
  """,
137
- labels={
138
- "policy_type": "security"
139
- }
137
+ labels=[
138
+ "wiz-plugin"
139
+ ]
140
140
  )
141
141
  ]
142
142
 
@@ -225,15 +225,67 @@ contexts:
225
225
  markdown += "\n"
226
226
  if "reportUrl" in stdout_json:
227
227
  markdown += f"<a href=\"{stdout_json['reportUrl']}\" rel=\"noopener noreferrer\">View Report</a>\n"
228
- self.send_markdown(markdown)
228
+ result = self.send_markdown(markdown)
229
+ if not result:
230
+ self.logger.error("Failed to send Wiz CLI output to spacelift")
231
+ sensitive: false
232
+ - path: /mnt/workspace/plugins/wiz/binary_install_wizcli.sh
233
+ content: |-
234
+ #!/bin/sh
235
+
236
+ set -e
237
+
238
+ if command -v wizcli; then
239
+ echo "wizcli is already installed."
240
+ return
241
+ fi
242
+
243
+ mkdir -p /mnt/workspace/plugins/plugin_binaries
244
+
245
+ echo "Installing wizcli..."
246
+ mkdir -p /mnt/workspace/plugins/plugin_binaries
247
+ cd /mnt/workspace/plugins/plugin_binaries
248
+
249
+ if [ "$(arch)" = "x86_64" ]; then
250
+ curl https://downloads.wiz.io/wizcli/0.94.0/wizcli-linux-amd64 -o /mnt/workspace/plugins/plugin_binaries/wizcli -L
251
+ else
252
+ curl https://downloads.wiz.io/wizcli/0.94.0/wizcli-linux-arm64 -o /mnt/workspace/plugins/plugin_binaries/wizcli -L
253
+ fi
254
+
255
+ chmod +x /mnt/workspace/plugins/plugin_binaries/wizcli
256
+ cd /mnt/workspace/source/$TF_VAR_spacelift_project_root
257
+ sensitive: false
258
+ - path: /mnt/workspace/plugins/wiz/before_plan.sh
259
+ content: |-
260
+ #!/bin/sh
261
+
262
+ set -e
263
+
264
+ cd /mnt/workspace/plugins/wiz
265
+
266
+ if [ ! -d "./venv" ]; then
267
+ python -m venv ./venv
268
+ fi
269
+ . venv/bin/activate
270
+
271
+ if ! command -v spaceforge; then
272
+ pip install spaceforge
273
+ fi
274
+
275
+ if [ -f requirements.txt ] && [ ! -f .spaceforge_installed_requirements ]; then
276
+ pip install -r requirements.txt
277
+ touch .spaceforge_installed_requirements
278
+ fi
279
+
280
+ cd /mnt/workspace/source/$TF_VAR_spacelift_project_root
281
+ spaceforge runner --plugin-file /mnt/workspace/plugins/wiz/plugin.py before_plan
229
282
  sensitive: false
230
283
  hooks:
231
284
  before_init:
232
285
  - mkdir -p /mnt/workspace/plugins/wiz
233
- - mkdir -p /mnt/workspace/plugins/plugin_binaries && cd /mnt/workspace/plugins/plugin_binaries && ([[ "$(echo "$(arch)")" == "x86_64" ]] && curl https://downloads.wiz.io/wizcli/0.94.0/wizcli-linux-amd64 -o /mnt/workspace/plugins/plugin_binaries/wizcli -L && chmod +x /mnt/workspace/plugins/plugin_binaries/wizcli || curl https://downloads.wiz.io/wizcli/0.94.0/wizcli-linux-arm64 -o /mnt/workspace/plugins/plugin_binaries/wizcli -L && chmod +x /mnt/workspace/plugins/plugin_binaries/wizcli) && cd /mnt/workspace/source/$TF_VAR_spacelift_project_root
286
+ - chmod +x /mnt/workspace/plugins/wiz/binary_install_wizcli.sh && /mnt/workspace/plugins/wiz/binary_install_wizcli.sh
234
287
  before_plan:
235
- - cd /mnt/workspace/plugins/wiz && python -m venv ./venv && source venv/bin/activate && pip install spaceforge
236
- - cd /mnt/workspace/source/$TF_VAR_spacelift_project_root && python -m spaceforge runner --plugin-file /mnt/workspace/plugins/wiz/plugin.py before_plan
288
+ - chmod +x /mnt/workspace/plugins/wiz/before_plan.sh && /mnt/workspace/plugins/wiz/before_plan.sh
237
289
  policies:
238
290
  - name_prefix: wiz_policy
239
291
  type: notification
@@ -245,4 +297,4 @@ policies:
245
297
  input.run_updated.run.marked_unsafe == true
246
298
  }
247
299
  labels:
248
- policy_type: security
300
+ - wiz-plugin
@@ -32,6 +32,7 @@ dependencies = [
32
32
  "PyYAML>=6.0",
33
33
  "click>=8.0.0",
34
34
  "pydantic>=2.11.7",
35
+ "Jinja2>=3.1.0",
35
36
  ]
36
37
 
37
38
  [project.optional-dependencies]
@@ -43,6 +43,7 @@ setup(
43
43
  "PyYAML>=6.0",
44
44
  "click>=8.0.0",
45
45
  "pydantic>=2.11.7",
46
+ "Jinja2>=3.1.0",
46
47
  ],
47
48
  extras_require={
48
49
  "dev": [
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.0.3'
32
- __version_tuple__ = version_tuple = (0, 0, 3)
31
+ __version__ = version = '0.0.5'
32
+ __version_tuple__ = version_tuple = (0, 0, 5)
33
33
 
34
- __commit_id__ = commit_id = 'ge171bdb88'
34
+ __commit_id__ = commit_id = 'gd83c2c6ce'
@@ -118,7 +118,7 @@ class Context:
118
118
  Attributes:
119
119
  name_prefix (str): The name of the context, will be appended with a unique ID.
120
120
  description (str): A description of the context.
121
- labels (dict): Labels associated with the context.
121
+ labels (Optional[List[str]]): Labels associated with the context.
122
122
  env (list): List of variables associated with the context.
123
123
  hooks (dict): Hooks associated with the context.
124
124
  """
@@ -128,7 +128,7 @@ class Context:
128
128
  env: Optional[List[Variable]] = optional_field
129
129
  mounted_files: Optional[List[MountedFile]] = optional_field
130
130
  hooks: Optional[Dict[HookType, List[str]]] = optional_field
131
- labels: Optional[Dict[str, str]] = optional_field
131
+ labels: Optional[List[str]] = optional_field
132
132
 
133
133
 
134
134
  @pydantic_dataclass
@@ -139,14 +139,14 @@ class Webhook:
139
139
  Attributes:
140
140
  name_prefix (str): The name of the webhook, will be appended with a unique ID.
141
141
  endpoint (str): The URL endpoint for the webhook.
142
- labels (Optional[dict]): Labels associated with the webhook.
143
- secrets (Optional[list[Variable]]): List of secrets associated with the webhook.
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
144
144
  """
145
145
 
146
146
  name_prefix: str
147
147
  endpoint: str
148
- labels: Optional[Dict[str, str]] = optional_field
149
- secrets: Optional[List[Variable]] = optional_field
148
+ secretFromParameter: str
149
+ labels: Optional[List[str]] = optional_field
150
150
 
151
151
 
152
152
  @pydantic_dataclass
@@ -158,13 +158,13 @@ class Policy:
158
158
  name_prefix (str): The name of the policy, will be appended with a unique ID.
159
159
  type (str): The type of the policy (e.g., "terraform", "kubernetes").
160
160
  body (str): The body of the policy, typically a configuration or script.
161
- labels (Optional[dict[str, str]]): Labels associated with the policy.
161
+ labels (Optional[List[str]]): Labels associated with the policy.
162
162
  """
163
163
 
164
164
  name_prefix: str
165
165
  type: str
166
166
  body: str
167
- labels: Optional[Dict[str, str]] = optional_field
167
+ labels: Optional[List[str]] = optional_field
168
168
 
169
169
 
170
170
  @pydantic_dataclass
@@ -7,6 +7,7 @@ import os
7
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
10
11
 
11
12
  if TYPE_CHECKING:
12
13
  from .plugin import SpaceforgePlugin
@@ -47,6 +48,9 @@ class PluginGenerator:
47
48
  self.plugin_instance: Optional[SpaceforgePlugin] = None
48
49
  self.plugin_working_directory: Optional[str] = None
49
50
  self.config: Optional[Dict[str, Any]] = None
51
+ self.jinja = Environment(
52
+ loader=PackageLoader("spaceforge"), autoescape=select_autoescape()
53
+ )
50
54
 
51
55
  def load_plugin(self) -> None:
52
56
  """Load the plugin class from the specified path."""
@@ -84,7 +88,7 @@ class PluginGenerator:
84
88
  self.config = {
85
89
  "setup_virtual_env": (
86
90
  f"cd {self.plugin_working_directory} && python -m venv ./venv && "
87
- + "source venv/bin/activate && pip install spaceforge"
91
+ + "source venv/bin/activate && (command -v spaceforge &> /dev/null || pip install spaceforge)"
88
92
  ),
89
93
  "plugin_mounted_path": f"{self.plugin_working_directory}/{os.path.basename(self.plugin_path)}",
90
94
  }
@@ -137,14 +141,27 @@ class PluginGenerator:
137
141
 
138
142
  return hook_methods
139
143
 
140
- def _update_with_requirements(
141
- self, hooks: Dict[str, List[str]], mounted_files: List[MountedFile]
144
+ def _add_to_mounted_files(
145
+ self,
146
+ hooks: Dict[str, List[str]],
147
+ mounted_files: List[MountedFile],
148
+ phase: str,
149
+ filepath: str,
150
+ filecontent: str,
142
151
  ) -> None:
152
+ file = f"{self.plugin_working_directory}/{filepath}"
153
+ hooks[phase].append(f"chmod +x {file} && {file}")
154
+ mounted_files.append(
155
+ MountedFile(
156
+ path=f"{self.plugin_working_directory}/{filepath}",
157
+ content=filecontent,
158
+ sensitive=False,
159
+ )
160
+ )
161
+
162
+ def _update_with_requirements(self, mounted_files: List[MountedFile]) -> None:
143
163
  """Update the plugin hooks if there is a requirements.txt"""
144
164
  if os.path.exists("requirements.txt") and self.config is not None:
145
- if self.config["setup_virtual_env"] not in hooks["before_init"]:
146
- hooks["before_init"].append(self.config["setup_virtual_env"])
147
- hooks["before_init"].append(f"pip install -r requirements.txt")
148
165
  # read the requirements.txt file
149
166
  with open("requirements.txt", "r") as f:
150
167
  mounted_files.append(
@@ -167,7 +184,9 @@ class PluginGenerator:
167
184
  )
168
185
  )
169
186
 
170
- def _add_spaceforge_hooks(self, hooks: Dict[str, List[str]]) -> None:
187
+ def _add_spaceforge_hooks(
188
+ self, hooks: Dict[str, List[str]], mounted_files: List[MountedFile]
189
+ ) -> None:
171
190
  # Add the spaceforge hook to actually run the plugin
172
191
  if self.config is None:
173
192
  raise ValueError("Plugin config not set. Call load_plugin() first.")
@@ -178,12 +197,14 @@ class PluginGenerator:
178
197
  if hook not in hooks:
179
198
  hooks[hook] = []
180
199
 
181
- if self.config["setup_virtual_env"] not in hooks[hook]:
182
- hooks[hook].append(self.config["setup_virtual_env"])
183
-
184
- hooks[hook].append(
185
- f"cd /mnt/workspace/source/$TF_VAR_spacelift_project_root && python -m spaceforge runner --plugin-file {self.config['plugin_mounted_path']} {hook}"
200
+ directory = os.path.dirname(self.config["plugin_mounted_path"])
201
+ template = self.jinja.get_template("ensure_spaceforge_and_run.sh.j2")
202
+ render = template.render(
203
+ plugin_path=directory,
204
+ plugin_file=self.config["plugin_mounted_path"],
205
+ phase=hook,
186
206
  )
207
+ self._add_to_mounted_files(hooks, mounted_files, hook, f"{hook}.sh", render)
187
208
 
188
209
  def _map_variables_to_parameters(self, contexts: List[Context]) -> None:
189
210
  for context in contexts:
@@ -221,10 +242,10 @@ class PluginGenerator:
221
242
  }
222
243
  mounted_files: List[MountedFile] = []
223
244
 
224
- self._update_with_requirements(hooks, mounted_files)
245
+ self._update_with_requirements(mounted_files)
225
246
  self._update_with_python_file(mounted_files)
226
- self._generate_binary_install_command(hooks)
227
- self._add_spaceforge_hooks(hooks)
247
+ self._generate_binary_install_command(hooks, mounted_files)
248
+ self._add_spaceforge_hooks(hooks, mounted_files)
228
249
 
229
250
  # Get the contexts and append the hooks and mounted files to it.
230
251
  if self.plugin_class is None:
@@ -256,46 +277,38 @@ class PluginGenerator:
256
277
 
257
278
  return contexts
258
279
 
259
- def _generate_binary_install_command(self, hooks: Dict[str, List[str]]) -> None:
280
+ def _generate_binary_install_command(
281
+ self, hooks: Dict[str, List[str]], mounted_files: List[MountedFile]
282
+ ) -> None:
260
283
  binaries = self.get_plugin_binaries()
261
284
  if binaries is None:
262
285
  return None
263
286
 
264
- binary_cmd = ""
265
- if len(binaries) > 0:
266
- binary_cmd = f"mkdir -p {static_binary_directory} && cd {static_binary_directory} && "
267
287
  for i, binary in enumerate(binaries):
268
288
  amd64_url = binary.download_urls.get("amd64", None)
269
289
  arm64_url = binary.download_urls.get("arm64", None)
290
+ binary_path = f"{static_binary_directory}/{binary.name}"
270
291
  if amd64_url is None and arm64_url is None:
271
292
  raise ValueError(
272
293
  f"Binary {binary.name} must have at least one download URL defined (amd64 or arm64)"
273
294
  )
274
295
 
275
- binary_path = f"{static_binary_directory}/{binary.name}"
276
- amd64_download_command = (
277
- f"curl {amd64_url} -o {binary_path} -L && chmod +x {binary_path}"
278
- if amd64_url is not None
279
- else "echo 'amd64 binary not available' && exit 1"
280
- )
281
- arm64_download_command = (
282
- f"curl {arm64_url} -o {binary_path} -L && chmod +x {binary_path}"
283
- if arm64_url is not None
284
- else "echo 'arm64 binary not available' && exit 1"
296
+ template = self.jinja.get_template("binary_install.sh.j2")
297
+ render = template.render(
298
+ binary=binary,
299
+ amd64_url=amd64_url,
300
+ arm64_url=arm64_url,
301
+ binary_path=binary_path,
302
+ static_binary_directory=static_binary_directory,
285
303
  )
286
-
287
- binary_cmd += (
288
- '([[ "$(echo "$(arch)")" == "x86_64" ]] && {} || {}) && '.format(
289
- amd64_download_command, arm64_download_command
290
- )
304
+ self._add_to_mounted_files(
305
+ hooks,
306
+ mounted_files,
307
+ "before_init",
308
+ f"binary_install_{binary.name}.sh",
309
+ render,
291
310
  )
292
311
 
293
- if binary_cmd != "":
294
- binary_cmd += "cd /mnt/workspace/source/$TF_VAR_spacelift_project_root"
295
-
296
- hooks["before_init"].append(binary_cmd)
297
- return None
298
-
299
312
  def get_plugin_binaries(self) -> Optional[List[Binary]]:
300
313
  """Get binary definitions from the plugin class."""
301
314
  return getattr(self.plugin_class, "__binaries__", None)
@@ -233,7 +233,7 @@ class SpaceforgePlugin(ABC):
233
233
  data: Dict[str, Any] = json.load(f)
234
234
  return data
235
235
 
236
- def send_markdown(self, markdown: str) -> None:
236
+ def send_markdown(self, markdown: str) -> bool:
237
237
  """
238
238
  Send a markdown message to the Spacelift run.
239
239
 
@@ -245,13 +245,13 @@ class SpaceforgePlugin(ABC):
245
245
  "Spacelift run is local. Not uploading markdown. Below is a preview of what would be sent"
246
246
  )
247
247
  self.logger.info(markdown)
248
- return
248
+ return True
249
249
 
250
250
  if self._spacelift_markdown_endpoint is None:
251
251
  self.logger.error(
252
252
  'API is not enabled, please export "SPACELIFT_API_TOKEN" and "TF_VAR_spacelift_graphql_endpoint".'
253
253
  )
254
- exit(1)
254
+ return False
255
255
 
256
256
  headers = {"Authorization": f"Bearer {self._api_token}"}
257
257
  body = {
@@ -263,28 +263,33 @@ class SpaceforgePlugin(ABC):
263
263
  self._spacelift_markdown_endpoint,
264
264
  json.dumps(body).encode("utf-8"),
265
265
  headers,
266
+ method="POST",
266
267
  )
267
268
 
268
- with urllib.request.urlopen(req) as response:
269
- if response.status != 200:
270
- self.logger.error(
271
- f"Error getting signed URL for markdown upload: {response.status}"
272
- )
273
- return
274
-
275
- raw_response = response.read().decode("utf-8")
276
- self.logger.debug(raw_response)
277
- resp: Dict[str, Any] = json.loads(raw_response)
278
- if "url" not in resp or "headers" not in resp:
279
- self.logger.error(
280
- "Markdown signed url response does not contain 'url' or 'headers' key."
281
- )
282
- return
269
+ try:
270
+ with urllib.request.urlopen(req) as response:
271
+ if response.status != 200:
272
+ self.logger.error(
273
+ f"Error getting signed URL for markdown upload: {response}"
274
+ )
275
+ return False
276
+
277
+ raw_response = response.read().decode("utf-8")
278
+ self.logger.debug(raw_response)
279
+ resp: Dict[str, Any] = json.loads(raw_response)
280
+ if "url" not in resp or "headers" not in resp:
281
+ self.logger.error(
282
+ "Markdown signed url response does not contain 'url' or 'headers' key."
283
+ )
284
+ return False
283
285
 
284
- signed_url = resp["url"]
285
- headers = resp["headers"]
286
- headers["Content-Type"] = "text/markdown"
287
- headers["Content-Length"] = str(len(markdown))
286
+ signed_url = resp["url"]
287
+ headers = resp["headers"]
288
+ headers["Content-Type"] = "text/markdown"
289
+ headers["Content-Length"] = str(len(markdown))
290
+ except urllib.request.HTTPError as e:
291
+ self.logger.error(f"HTTP error occurred: {e.code} - {e.reason}")
292
+ return False
288
293
 
289
294
  # Now we upload the markdown content to the signed URL
290
295
  req = urllib.request.Request(
@@ -299,8 +304,9 @@ class SpaceforgePlugin(ABC):
299
304
  self.logger.error(
300
305
  f"Error uploading markdown content: {put_response.status}"
301
306
  )
302
- return
307
+ return False
303
308
  self.logger.debug("Markdown content uploaded successfully.")
309
+ return True
304
310
 
305
311
  def add_to_policy_input(self, input_name: str, data: Dict[str, Any]) -> None:
306
312
  """