spaceforge 0.1.0.dev0__py3-none-any.whl → 1.0.0__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.
Files changed (35) hide show
  1. spaceforge/__init__.py +12 -4
  2. spaceforge/__main__.py +3 -3
  3. spaceforge/_version.py +0 -1
  4. spaceforge/_version_scm.py +34 -0
  5. spaceforge/cls.py +24 -14
  6. spaceforge/conftest.py +89 -0
  7. spaceforge/generator.py +129 -56
  8. spaceforge/plugin.py +199 -22
  9. spaceforge/runner.py +0 -12
  10. spaceforge/schema.json +45 -22
  11. spaceforge/templates/binary_install.sh.j2 +24 -0
  12. spaceforge/templates/ensure_spaceforge_and_run.sh.j2 +24 -0
  13. spaceforge/{generator_test.py → test_generator.py} +265 -53
  14. spaceforge/test_generator_binaries.py +194 -0
  15. spaceforge/test_generator_core.py +180 -0
  16. spaceforge/test_generator_hooks.py +90 -0
  17. spaceforge/test_generator_parameters.py +59 -0
  18. spaceforge/test_plugin.py +357 -0
  19. spaceforge/test_plugin_file_operations.py +118 -0
  20. spaceforge/test_plugin_hooks.py +100 -0
  21. spaceforge/test_plugin_inheritance.py +102 -0
  22. spaceforge/{runner_test.py → test_runner.py} +2 -65
  23. spaceforge/test_runner_cli.py +69 -0
  24. spaceforge/test_runner_core.py +124 -0
  25. spaceforge/test_runner_execution.py +169 -0
  26. spaceforge-1.0.0.dist-info/METADATA +606 -0
  27. spaceforge-1.0.0.dist-info/RECORD +33 -0
  28. spaceforge/plugin_test.py +0 -621
  29. spaceforge-0.1.0.dev0.dist-info/METADATA +0 -163
  30. spaceforge-0.1.0.dev0.dist-info/RECORD +0 -19
  31. /spaceforge/{cls_test.py → test_cls.py} +0 -0
  32. {spaceforge-0.1.0.dev0.dist-info → spaceforge-1.0.0.dist-info}/WHEEL +0 -0
  33. {spaceforge-0.1.0.dev0.dist-info → spaceforge-1.0.0.dist-info}/entry_points.txt +0 -0
  34. {spaceforge-0.1.0.dev0.dist-info → spaceforge-1.0.0.dist-info}/licenses/LICENSE +0 -0
  35. {spaceforge-0.1.0.dev0.dist-info → spaceforge-1.0.0.dist-info}/top_level.txt +0 -0
spaceforge/plugin.py CHANGED
@@ -9,7 +9,8 @@ import os
9
9
  import subprocess
10
10
  import urllib.request
11
11
  from abc import ABC
12
- from typing import Any, Dict, List, Optional, Tuple, Union
12
+ from typing import Any, Dict, List, Optional, Tuple
13
+ from urllib.error import HTTPError
13
14
 
14
15
 
15
16
  class SpaceforgePlugin(ABC):
@@ -31,29 +32,41 @@ class SpaceforgePlugin(ABC):
31
32
  __author__ = "Spacelift Team"
32
33
 
33
34
  def __init__(self) -> None:
35
+ self._run_id = os.environ.get("TF_VAR_spacelift_run_id", "local")
36
+ self._is_local = self._run_id == "local"
34
37
  self.logger = self._setup_logger()
38
+ self.spacelift_domain = os.environ.get(
39
+ "TF_VAR_spacelift_graphql_endpoint", ""
40
+ ).replace("/graphql", "")
35
41
 
36
- self._api_token = os.environ.get("SPACELIFT_API_TOKEN", False)
37
- self._spacelift_domain = os.environ.get(
38
- "TF_VAR_spacelift_graphql_endpoint", False
42
+ self._api_token = os.environ.get("SPACELIFT_API_TOKEN") or False
43
+ self._api_endpoint = (
44
+ os.environ.get("TF_VAR_spacelift_graphql_endpoint") or False
39
45
  )
40
- self._api_enabled = self._api_token != False and self._spacelift_domain != False
41
- self._workspace_root = os.environ.get("WORKSPACE_ROOT", os.getcwd())
46
+ self._api_enabled = bool(self._api_token and self._api_endpoint)
47
+ self._workspace_root = os.getcwd()
48
+ self._spacelift_markdown_endpoint = None
49
+ self._markdown_endpoint_token = os.environ.get("SPACELIFT_API_TOKEN") or False
42
50
 
43
51
  # This should be the last thing we do in the constructor
44
52
  # because we set api_enabled to false if the domain is set up incorrectly.
45
- if self._spacelift_domain and isinstance(self._spacelift_domain, str):
53
+ if self._api_endpoint and isinstance(self._api_endpoint, str):
46
54
  # this must occur after we check if spacelift domain is false
47
55
  # because the domain could be set but not start with https://
48
- if self._spacelift_domain.startswith("https://"):
49
- if self._spacelift_domain.endswith("/"):
50
- self._spacelift_domain = self._spacelift_domain[:-1]
56
+ if self._api_endpoint.startswith("https://"):
57
+ if self._api_endpoint.endswith("/"):
58
+ self._api_endpoint = self._api_endpoint[:-1]
51
59
  else:
52
60
  self.logger.warning(
53
61
  "SPACELIFT_DOMAIN does not start with https://, api calls will fail."
54
62
  )
55
63
  self._api_enabled = False
56
64
 
65
+ if self._api_enabled:
66
+ self._spacelift_markdown_endpoint = self._api_endpoint.replace(
67
+ "/graphql", "/worker/plugin_logs_url"
68
+ )
69
+
57
70
  def _setup_logger(self) -> logging.Logger:
58
71
  """Set up logging for the plugin."""
59
72
 
@@ -62,7 +75,7 @@ class SpaceforgePlugin(ABC):
62
75
  warn_color = "\033[33m"
63
76
  error_color = "\033[31m"
64
77
  end_color = "\033[0m"
65
- run_id = os.environ.get("TF_VAR_spacelift_run_id", "local")
78
+ run_id = self._run_id
66
79
  plugin_name = self.__plugin_name__
67
80
 
68
81
  class ColorFormatter(logging.Formatter):
@@ -93,13 +106,62 @@ class SpaceforgePlugin(ABC):
93
106
  handler.setFormatter(ColorFormatter())
94
107
 
95
108
  # Always check for debug mode spacelift variable
96
- if os.environ.get("SPACELIFT_DEBUG"):
109
+ if os.environ.get("SPACELIFT_DEBUG") or self._is_local:
97
110
  logger.setLevel(logging.DEBUG)
98
111
  else:
99
112
  logger.setLevel(logging.INFO)
100
113
 
101
114
  return logger
102
115
 
116
+ def use_user_token(self, id: str, token: str) -> None:
117
+ headers = {"Content-Type": "application/json"}
118
+
119
+ query = """
120
+ mutation requestApiKey($id: ID!, $secret: String!){
121
+ apiKeyUser(id: $id, secret: $secret){
122
+ jwt
123
+ }
124
+ }
125
+ """
126
+
127
+ data: Dict[str, Any] = {
128
+ "query": query,
129
+ "variables": {"id": id, "secret": token},
130
+ }
131
+
132
+ req = urllib.request.Request(
133
+ self._api_endpoint, # type: ignore[arg-type]
134
+ json.dumps(data).encode("utf-8"),
135
+ headers,
136
+ )
137
+
138
+ self.logger.debug(f"Sending request to url: {self._api_endpoint}")
139
+ try:
140
+ with urllib.request.urlopen(req) as response:
141
+ resp: Dict[str, Any] = json.loads(response.read().decode("utf-8"))
142
+ except urllib.error.HTTPError as e:
143
+ if hasattr(e, "read"):
144
+ resp = json.loads(e.read().decode("utf-8"))
145
+ else:
146
+ # We should not get here, but if we do re-raise the exception
147
+ self.logger.error(f"HTTP error occurred: ({e.code}) {e.reason} {e.msg}")
148
+ raise e
149
+
150
+ if "errors" in resp:
151
+ self.logger.error(f"Error: {resp['errors']}")
152
+ return
153
+
154
+ if (
155
+ "data" in resp
156
+ and "apiKeyUser" in resp["data"]
157
+ and "jwt" in resp["data"]["apiKeyUser"]
158
+ ):
159
+ self._api_token = resp["data"]["apiKeyUser"]["jwt"]
160
+ self._api_enabled = True
161
+ self.logger.debug("Successfully set user token for API calls.")
162
+ else:
163
+ self.logger.error(f"API call returned no data: {resp}")
164
+
103
165
  def get_available_hooks(self) -> List[str]:
104
166
  """
105
167
  Get list of hook methods available in this plugin.
@@ -136,9 +198,7 @@ class SpaceforgePlugin(ABC):
136
198
  Run a CLI command with the given arguments.
137
199
 
138
200
  Args:
139
- command: The command to run
140
- *args: Positional arguments for the command
141
- **kwargs: Keyword arguments for the command
201
+ *command: The command to run
142
202
  expect_code: Expected return code
143
203
  print_output: Whether to print the output to the logger
144
204
  """
@@ -175,7 +235,7 @@ class SpaceforgePlugin(ABC):
175
235
  ) -> Dict[str, Any]:
176
236
  if not self._api_enabled:
177
237
  self.logger.error(
178
- 'API is not enabled, please export "SPACELIFT_API_TOKEN" and "SPACELIFT_DOMAIN".'
238
+ 'API is not enabled, please export "SPACELIFT_API_TOKEN" and "TF_VAR_spacelift_graphql_endpoint".'
179
239
  )
180
240
  exit(1)
181
241
 
@@ -192,12 +252,22 @@ class SpaceforgePlugin(ABC):
192
252
  data["variables"] = variables
193
253
 
194
254
  req = urllib.request.Request(
195
- f"{self._spacelift_domain}/graphql",
255
+ self._api_endpoint, # type: ignore[arg-type]
196
256
  json.dumps(data).encode("utf-8"),
197
257
  headers,
198
258
  )
199
- with urllib.request.urlopen(req) as response:
200
- resp: Dict[str, Any] = json.loads(response.read().decode("utf-8"))
259
+
260
+ self.logger.debug(f"Sending request to url: {self._api_endpoint}")
261
+ try:
262
+ with urllib.request.urlopen(req) as response:
263
+ resp: Dict[str, Any] = json.loads(response.read().decode("utf-8"))
264
+ except urllib.error.HTTPError as e:
265
+ if hasattr(e, "read"):
266
+ resp = json.loads(e.read().decode("utf-8"))
267
+ else:
268
+ # We should not get here, but if we do re-raise the exception
269
+ self.logger.error(f"HTTP error occurred: ({e.code}) {e.reason} {e.msg}")
270
+ raise e
201
271
 
202
272
  if "errors" in resp:
203
273
  self.logger.error(f"Error: {resp['errors']}")
@@ -225,9 +295,116 @@ class SpaceforgePlugin(ABC):
225
295
  data: Dict[str, Any] = json.load(f)
226
296
  return data
227
297
 
228
- def send_markdown(self, markdown: str) -> None:
229
- # TODO
230
- print(markdown)
298
+ def send_markdown(self, markdown: str) -> bool:
299
+ """
300
+ Send a markdown message to the Spacelift run.
301
+
302
+ Args:
303
+ markdown: The markdown content to send
304
+ """
305
+ if self._is_local:
306
+ self.logger.info(
307
+ "Spacelift run is local. Not uploading markdown. Below is a preview of what would be sent"
308
+ )
309
+ self.logger.info(markdown)
310
+ return True
311
+
312
+ if (
313
+ self._spacelift_markdown_endpoint is None
314
+ or not self._markdown_endpoint_token
315
+ ):
316
+ self.logger.error(
317
+ 'API is not enabled, please export "SPACELIFT_API_TOKEN" and "TF_VAR_spacelift_graphql_endpoint".'
318
+ )
319
+ return False
320
+
321
+ headers = {"Authorization": f"Bearer {self._markdown_endpoint_token}"}
322
+ body = {
323
+ "plugin_name": self.__plugin_name__,
324
+ }
325
+
326
+ # First we get the signed url for uploading
327
+ req = urllib.request.Request(
328
+ self._spacelift_markdown_endpoint,
329
+ json.dumps(body).encode("utf-8"),
330
+ headers,
331
+ method="POST",
332
+ )
333
+
334
+ self.logger.debug(
335
+ f"Sending request to url: {self._spacelift_markdown_endpoint}"
336
+ )
337
+ try:
338
+ with urllib.request.urlopen(req) as response:
339
+ if response.status != 200:
340
+ self.logger.error(
341
+ f"Error getting signed URL for markdown upload: {response}"
342
+ )
343
+ return False
344
+
345
+ raw_response = response.read().decode("utf-8")
346
+ self.logger.debug(raw_response)
347
+ resp: Dict[str, Any] = json.loads(raw_response)
348
+ if "url" not in resp or "headers" not in resp:
349
+ self.logger.error(
350
+ "Markdown signed url response does not contain 'url' or 'headers' key."
351
+ )
352
+ return False
353
+
354
+ signed_url = resp["url"]
355
+ headers = resp["headers"]
356
+ headers["Content-Type"] = "text/markdown"
357
+ headers["Content-Length"] = str(len(markdown.encode("utf-8")))
358
+ except HTTPError as e:
359
+ self.logger.error(f"HTTP error occurred: ({e.code}) {e.reason} {e.msg}")
360
+ return False
361
+
362
+ self.logger.debug("Markdown to send" + markdown)
363
+
364
+ # Now we upload the markdown content to the signed URL
365
+ req = urllib.request.Request(
366
+ signed_url,
367
+ data=markdown.encode("utf-8"),
368
+ headers=headers,
369
+ method="PUT",
370
+ )
371
+
372
+ self.logger.debug(f"Sending request to url: {signed_url}")
373
+ try:
374
+ with urllib.request.urlopen(req) as put_response:
375
+ if put_response.status != 200:
376
+ self.logger.error(
377
+ f"Error uploading markdown content: {put_response.status}"
378
+ )
379
+ return False
380
+ self.logger.debug("Markdown content uploaded successfully.")
381
+ except HTTPError as e:
382
+ self.logger.error(
383
+ f"HTTP error occurred during upload: ({e.code}) {e.reason} {e.msg}"
384
+ )
385
+ return False
386
+
387
+ return True
388
+
389
+ def add_to_policy_input(self, input_name: str, data: Dict[str, Any]) -> None:
390
+ """
391
+ Add data to the policy input for the current Spacelift run.
392
+
393
+ Args:
394
+ input_name: The name of the input to add (will be available as input.third_party_metadata.custom.{input_name} to the policy).
395
+ data: Dictionary containing data to add to the policy input
396
+ """
397
+ if self._is_local:
398
+ self.logger.info(
399
+ "Spacelift run is local. Not writing custom policy input. Below is a preview of what would be written"
400
+ )
401
+ self.logger.info(json.dumps(data, indent=2))
402
+ return
403
+
404
+ with open(
405
+ f"{self._workspace_root}/{input_name}.custom.spacelift.json", "w"
406
+ ) as f:
407
+ f.write(json.dumps(data))
231
408
 
232
409
  # Hook methods - override these in your plugin
233
410
  def before_init(self) -> None:
spaceforge/runner.py CHANGED
@@ -4,7 +4,6 @@ Plugin runner for executing hook methods.
4
4
 
5
5
  import importlib.util
6
6
  import os
7
- import sys
8
7
  from typing import Optional
9
8
 
10
9
 
@@ -102,14 +101,3 @@ def runner_command(hook_name: str, plugin_file: str) -> None:
102
101
  """
103
102
  runner = PluginRunner(plugin_file)
104
103
  runner.run_hook(hook_name)
105
-
106
-
107
- def main() -> None:
108
- """Legacy main entry point for backward compatibility."""
109
- if len(sys.argv) != 2:
110
- print("Usage: python -m spaceforge.runner <hook_name>")
111
- sys.exit(1)
112
-
113
- hook_name = sys.argv[1]
114
- runner = PluginRunner()
115
- runner.run_hook(hook_name)
spaceforge/schema.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$defs": {
3
3
  "Context": {
4
- "description": "A class to represent a context for a plugin.\n\nAttributes:\n name_prefix (str): The name of the context, will be appended with a unique ID.\n description (str): A description of the context.\n labels (dict): Labels associated with the context.\n env (list): List of variables associated with the context.\n hooks (dict): Hooks associated with the context.",
4
+ "description": "A class to represent a context for a plugin.\n\nAttributes:\n name_prefix (str): The name of the context, will be appended with a unique ID.\n description (str): A description of the context.\n labels (Optional[List[str]]): Labels associated with the context.\n env (list): List of variables associated with the context.\n hooks (dict): Hooks associated with the context.",
5
5
  "properties": {
6
6
  "name_prefix": {
7
7
  "title": "Name Prefix",
@@ -74,10 +74,10 @@
74
74
  "labels": {
75
75
  "anyOf": [
76
76
  {
77
- "additionalProperties": {
77
+ "items": {
78
78
  "type": "string"
79
79
  },
80
- "type": "object"
80
+ "type": "array"
81
81
  },
82
82
  {
83
83
  "type": "null"
@@ -118,7 +118,7 @@
118
118
  "type": "object"
119
119
  },
120
120
  "Parameter": {
121
- "description": "A class to represent a parameter with a name and value.\n\nAttributes:\n name (str): The name of the parameter.\n description (str): A description of the parameter.\n sensitive (bool): Whether the parameter contains sensitive information.\n required (bool): Whether the parameter is required.\n default (Optional[str]): The default value of the parameter, if any. (required if sensitive is False)",
121
+ "description": "A class to represent a parameter with a name and value.\n\nAttributes:\n name (str): The name of the parameter.\n description (str): A description of the parameter.\n sensitive (bool): Whether the parameter contains sensitive information.\n required (bool): Whether the parameter is required.\n default (Optional[str]): The default value of the parameter, if any. (required if sensitive is False)\n id (str): Unique identifier for the parameter.",
122
122
  "properties": {
123
123
  "name": {
124
124
  "title": "Name",
@@ -149,6 +149,10 @@
149
149
  ],
150
150
  "default": null,
151
151
  "title": "Default"
152
+ },
153
+ "id": {
154
+ "title": "Id",
155
+ "type": "string"
152
156
  }
153
157
  },
154
158
  "required": [
@@ -159,13 +163,20 @@
159
163
  "type": "object"
160
164
  },
161
165
  "Policy": {
162
- "description": "A class to represent a policy configuration.\n\nAttributes:\n name_prefix (str): The name of the policy, will be appended with a unique ID.\n type (str): The type of the policy (e.g., \"terraform\", \"kubernetes\").\n body (str): The body of the policy, typically a configuration or script.\n labels (Optional[dict[str, str]]): Labels associated with the policy.",
166
+ "description": "A class to represent a policy configuration.\n\nAttributes:\n name_prefix (str): The name of the policy, will be appended with a unique ID.\n type (str): The type of the policy (e.g., \"terraform\", \"kubernetes\").\n body (str): The body of the policy, typically a configuration or script.\n labels (Optional[List[str]]): Labels associated with the policy.",
163
167
  "properties": {
164
168
  "name_prefix": {
165
169
  "title": "Name Prefix",
166
170
  "type": "string"
167
171
  },
168
172
  "type": {
173
+ "enum": [
174
+ "PUSH",
175
+ "PLAN",
176
+ "TRIGGER",
177
+ "APPROVAL",
178
+ "NOTIFICATION"
179
+ ],
169
180
  "title": "Type",
170
181
  "type": "string"
171
182
  },
@@ -176,10 +187,10 @@
176
187
  "labels": {
177
188
  "anyOf": [
178
189
  {
179
- "additionalProperties": {
190
+ "items": {
180
191
  "type": "string"
181
192
  },
182
- "type": "object"
193
+ "type": "array"
183
194
  },
184
195
  {
185
196
  "type": "null"
@@ -238,7 +249,7 @@
238
249
  "type": "object"
239
250
  },
240
251
  "Webhook": {
241
- "description": "A class to represent a webhook configuration.\n\nAttributes:\n name_prefix (str): The name of the webhook, will be appended with a unique ID.\n endpoint (str): The URL endpoint for the webhook.\n labels (Optional[dict]): Labels associated with the webhook.\n secrets (Optional[list[Variable]]): List of secrets associated with the webhook.",
252
+ "description": "A class to represent a webhook configuration.\n\nAttributes:\n name_prefix (str): The name of the webhook, will be appended with a unique ID.\n endpoint (str): The URL endpoint for the webhook.\n labels (Optional[List[str]]): Labels associated with the webhook.\n secret (str): the ID of the parameter where the webhook secret is retrieved from",
242
253
  "properties": {
243
254
  "name_prefix": {
244
255
  "title": "Name Prefix",
@@ -248,25 +259,22 @@
248
259
  "title": "Endpoint",
249
260
  "type": "string"
250
261
  },
251
- "labels": {
262
+ "secretFromParameter": {
252
263
  "anyOf": [
253
264
  {
254
- "additionalProperties": {
255
- "type": "string"
256
- },
257
- "type": "object"
265
+ "type": "string"
258
266
  },
259
267
  {
260
268
  "type": "null"
261
269
  }
262
270
  ],
263
- "title": "Labels"
271
+ "title": "Secretfromparameter"
264
272
  },
265
- "secrets": {
273
+ "labels": {
266
274
  "anyOf": [
267
275
  {
268
276
  "items": {
269
- "$ref": "#/$defs/Variable"
277
+ "type": "string"
270
278
  },
271
279
  "type": "array"
272
280
  },
@@ -274,7 +282,7 @@
274
282
  "type": "null"
275
283
  }
276
284
  ],
277
- "title": "Secrets"
285
+ "title": "Labels"
278
286
  }
279
287
  },
280
288
  "required": [
@@ -285,10 +293,10 @@
285
293
  "type": "object"
286
294
  }
287
295
  },
288
- "description": "A class to represent the manifest of a Spacelift plugin.\n\nAttributes:\n name_prefix (str): The name of the plugin, will be appended with a unique ID.\n description (str): A description of the plugin.\n author (str): The author of the plugin.\n parameters (list[Parameter]): List of parameters for the plugin.\n contexts (list[Context]): List of contexts for the plugin.\n webhooks (list[Webhook]): List of webhooks for the plugin.\n policies (list[Policy]): List of policies for the plugin.",
296
+ "description": "A class to represent the manifest of a Spacelift plugin.\n\nAttributes:\n name (str): The name of the plugin, will be appended with a unique ID.\n description (str): A description of the plugin.\n author (str): The author of the plugin.\n labels (list[str]): List of labels for the plugin.\n parameters (list[Parameter]): List of parameters for the plugin.\n contexts (list[Context]): List of contexts for the plugin.\n webhooks (list[Webhook]): List of webhooks for the plugin.\n policies (list[Policy]): List of policies for the plugin.",
289
297
  "properties": {
290
- "name_prefix": {
291
- "title": "Name Prefix",
298
+ "name": {
299
+ "title": "Name",
292
300
  "type": "string"
293
301
  },
294
302
  "version": {
@@ -303,6 +311,20 @@
303
311
  "title": "Author",
304
312
  "type": "string"
305
313
  },
314
+ "labels": {
315
+ "anyOf": [
316
+ {
317
+ "items": {
318
+ "type": "string"
319
+ },
320
+ "type": "array"
321
+ },
322
+ {
323
+ "type": "null"
324
+ }
325
+ ],
326
+ "title": "Labels"
327
+ },
306
328
  "parameters": {
307
329
  "anyOf": [
308
330
  {
@@ -361,11 +383,12 @@
361
383
  }
362
384
  },
363
385
  "required": [
364
- "name_prefix",
386
+ "name",
365
387
  "version",
366
388
  "description",
367
389
  "author"
368
390
  ],
369
391
  "title": "PluginManifest",
370
- "type": "object"
392
+ "type": "object",
393
+ "$schema": "http://json-schema.org/draft-07/schema#"
371
394
  }
@@ -0,0 +1,24 @@
1
+ #!/bin/sh
2
+
3
+ set -e
4
+
5
+ export PATH="/mnt/workspace/plugins/plugin_binaries:$PATH"
6
+ if command -v {{binary.name}}; then
7
+ echo "{{binary.name}} is already installed."
8
+ return
9
+ fi
10
+
11
+ mkdir -p {{static_binary_directory}}
12
+
13
+ echo "Installing {{binary.name}}..."
14
+ mkdir -p {{static_binary_directory}}
15
+ cd {{static_binary_directory}}
16
+
17
+ if [ "$(arch)" = "x86_64" ]; then
18
+ curl {{amd64_url}} -o {{binary_path}} -L
19
+ else
20
+ curl {{arm64_url}} -o {{binary_path}} -L
21
+ fi
22
+
23
+ chmod +x {{binary_path}}
24
+ cd /mnt/workspace/source/$TF_VAR_spacelift_project_root
@@ -0,0 +1,24 @@
1
+ #!/bin/sh
2
+
3
+ set -e
4
+
5
+ cd {{plugin_path}}
6
+
7
+ if [ ! -d "./venv" ]; then
8
+ python -m venv ./venv
9
+ fi
10
+ . venv/bin/activate
11
+
12
+ if ! command -v spaceforge; then
13
+ pip install spaceforge
14
+ fi
15
+
16
+ if [ -f requirements.txt ] && [ ! -f .spaceforge_installed_requirements ]; then
17
+ pip install -r requirements.txt
18
+ touch .spaceforge_installed_requirements
19
+ fi
20
+ {% if has_binaries %}
21
+ export PATH="/mnt/workspace/plugins/plugin_binaries:$PATH"
22
+ {% endif %}
23
+ cd /mnt/workspace/source/$TF_VAR_spacelift_project_root
24
+ spaceforge runner --plugin-file {{plugin_file}} {{phase}}