spaceforge 0.0.2__py3-none-any.whl → 0.0.4__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 +2 -2
  5. spaceforge/cls.py +16 -12
  6. spaceforge/conftest.py +89 -0
  7. spaceforge/generator.py +119 -54
  8. spaceforge/plugin.py +106 -12
  9. spaceforge/runner.py +0 -12
  10. spaceforge/schema.json +38 -29
  11. spaceforge/templates/binary_install.sh.j2 +23 -0
  12. spaceforge/templates/ensure_spaceforge_and_run.sh.j2 +22 -0
  13. spaceforge/{generator_test.py → test_generator.py} +263 -51
  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-0.0.4.dist-info/METADATA +605 -0
  27. spaceforge-0.0.4.dist-info/RECORD +33 -0
  28. spaceforge/plugin_test.py +0 -621
  29. spaceforge-0.0.2.dist-info/METADATA +0 -163
  30. spaceforge-0.0.2.dist-info/RECORD +0 -20
  31. /spaceforge/{cls_test.py → test_cls.py} +0 -0
  32. {spaceforge-0.0.2.dist-info → spaceforge-0.0.4.dist-info}/WHEEL +0 -0
  33. {spaceforge-0.0.2.dist-info → spaceforge-0.0.4.dist-info}/entry_points.txt +0 -0
  34. {spaceforge-0.0.2.dist-info → spaceforge-0.0.4.dist-info}/licenses/LICENSE +0 -0
  35. {spaceforge-0.0.2.dist-info → spaceforge-0.0.4.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,605 @@
1
+ Metadata-Version: 2.4
2
+ Name: spaceforge
3
+ Version: 0.0.4
4
+ Summary: A Python framework for building Spacelift plugins
5
+ Home-page: https://github.com/spacelift-io/plugins
6
+ Author: Spacelift
7
+ Author-email: Spacelift <support@spacelift.io>
8
+ Maintainer-email: Spacelift <support@spacelift.io>
9
+ License: MIT
10
+ Project-URL: Homepage, https://github.com/spacelift-io/plugins
11
+ Project-URL: Documentation, https://github.com/spacelift-io/plugins#readme
12
+ Project-URL: Repository, https://github.com/spacelift-io/plugins
13
+ Project-URL: Bug Reports, https://github.com/spacelift-io/plugins/issues
14
+ Keywords: spacelift,plugin,framework,infrastructure,devops,spaceforge
15
+ Classifier: Development Status :: 3 - Alpha
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
+ Classifier: Topic :: System :: Systems Administration
25
+ Requires-Python: >=3.9
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Requires-Dist: PyYAML>=6.0
29
+ Requires-Dist: click>=8.0.0
30
+ Requires-Dist: pydantic>=2.11.7
31
+ Requires-Dist: Jinja2>=3.1.0
32
+ Provides-Extra: dev
33
+ Requires-Dist: pytest>=6.0; extra == "dev"
34
+ Requires-Dist: pytest-cov; extra == "dev"
35
+ Requires-Dist: black; extra == "dev"
36
+ Requires-Dist: isort; extra == "dev"
37
+ Requires-Dist: mypy; extra == "dev"
38
+ Requires-Dist: types-PyYAML; extra == "dev"
39
+ Requires-Dist: setuptools-scm[toml]>=6.2; extra == "dev"
40
+ Requires-Dist: autoflake; extra == "dev"
41
+ Dynamic: author
42
+ Dynamic: home-page
43
+ Dynamic: license-file
44
+ Dynamic: requires-python
45
+
46
+ # Spaceforge - Build Spacelift Plugins in Python
47
+
48
+ Spaceforge is a Python framework that makes it easy to build powerful Spacelift plugins using a declarative, hook-based approach. Define your plugin logic in Python, and spaceforge automatically generates the plugin manifest for Spacelift.
49
+
50
+ ## Installation
51
+
52
+ Install spaceforge from PyPI:
53
+
54
+ ```bash
55
+ pip install spaceforge
56
+ ```
57
+
58
+ ## Quick Start
59
+
60
+ ### 1. Create Your Plugin
61
+
62
+ Create a Python file (e.g., `my_plugin.py`) and inherit from `SpaceforgePlugin`:
63
+
64
+ ```python
65
+ from spaceforge import SpaceforgePlugin, Parameter, Variable, Context
66
+ import os
67
+
68
+ class MyPlugin(SpaceforgePlugin):
69
+ # Plugin metadata
70
+ __plugin_name__ = "my-awesome-plugin"
71
+ __version__ = "1.0.0"
72
+ __author__ = "Your Name"
73
+
74
+ # Define plugin parameters
75
+ __parameters__ = [
76
+ Parameter(
77
+ name="api_key",
78
+ description="API key for external service",
79
+ required=True,
80
+ sensitive=True
81
+ ),
82
+ Parameter(
83
+ name="environment",
84
+ description="Target environment",
85
+ required=False,
86
+ default="production"
87
+ )
88
+ ]
89
+
90
+ # Define Spacelift contexts
91
+ __contexts__ = [
92
+ Context(
93
+ name_prefix="my-plugin",
94
+ description="Main plugin context",
95
+ env=[
96
+ Variable(
97
+ key="API_KEY",
98
+ value_from_parameter="api_key",
99
+ sensitive=True
100
+ ),
101
+ Variable(
102
+ key="ENVIRONMENT",
103
+ value_from_parameter="environment"
104
+ )
105
+ ]
106
+ )
107
+ ]
108
+
109
+ def after_plan(self):
110
+ """Run security checks after Terraform plan"""
111
+ # Run external commands
112
+ return_code, stdout, stderr = self.run_cli("my-security-tool", "--scan", "./", '--api', os.environ["API_KEY"])
113
+
114
+ if return_code != 0:
115
+ self.logger.error("Security scan failed!")
116
+ exit(1)
117
+
118
+ self.logger.info("Security scan passed!")
119
+ ```
120
+
121
+ ### 2. Generate Plugin Manifest
122
+
123
+ Generate the Spacelift plugin YAML manifest:
124
+
125
+ ```bash
126
+ spaceforge generate my_plugin.py
127
+ ```
128
+
129
+ This creates `plugin.yaml` that you can upload to Spacelift.
130
+
131
+ ### 3. Test Your Plugin
132
+
133
+ Test individual hooks locally:
134
+
135
+ ```bash
136
+ # Set parameter values
137
+ export API_KEY="your-api-key"
138
+ export ENVIRONMENT="staging"
139
+
140
+ # Test the after_plan hook
141
+ spaceforge runner after_plan
142
+ ```
143
+
144
+ ## Available Hooks
145
+
146
+ Override these methods in your plugin to add custom logic:
147
+
148
+ - `before_init()` - Before Terraform init
149
+ - `after_init()` - After Terraform init
150
+ - `before_plan()` - Before Terraform plan
151
+ - `after_plan()` - After Terraform plan
152
+ - `before_apply()` - Before Terraform apply
153
+ - `after_apply()` - After Terraform apply
154
+ - `before_perform()` - Before the run performs
155
+ - `after_perform()` - After the run performs
156
+ - `before_destroy()` - Before Terraform destroy
157
+ - `after_destroy()` - After Terraform destroy
158
+ - `after_run()` - After the run completes
159
+
160
+ ## Plugin Components
161
+
162
+ ### Parameters
163
+
164
+ Define user-configurable parameters:
165
+
166
+ ```python
167
+ __parameters__ = [
168
+ Parameter(
169
+ name="database_url",
170
+ description="Database connection URL",
171
+ required=True,
172
+ sensitive=True,
173
+ default="postgresql://localhost:5432/mydb"
174
+ ),
175
+ Parameter(
176
+ name="timeout",
177
+ description="Timeout in seconds",
178
+ required=False,
179
+ default=30
180
+ )
181
+ ]
182
+ ```
183
+
184
+ ### Contexts
185
+
186
+ Define Spacelift contexts with environment variables and custom hooks:
187
+
188
+ ```python
189
+ __contexts__ = [
190
+ Context(
191
+ name_prefix="production",
192
+ description="Production environment context",
193
+ labels={"env": "prod"},
194
+ env=[
195
+ Variable(
196
+ key="DATABASE_URL",
197
+ value_from_parameter="database_url",
198
+ sensitive=True
199
+ ),
200
+ Variable(
201
+ key="API_ENDPOINT",
202
+ value="https://api.prod.example.com"
203
+ )
204
+ ],
205
+ hooks={
206
+ "before_apply": [
207
+ "echo 'Starting production deployment'",
208
+ "kubectl get pods"
209
+ ]
210
+ }
211
+ )
212
+ ]
213
+ ```
214
+
215
+ ### Binaries
216
+
217
+ Automatically download and install external tools:
218
+
219
+ ```python
220
+ __binaries__ = [
221
+ Binary(
222
+ name="kubectl",
223
+ download_urls={
224
+ "amd64": "https://dl.k8s.io/release/v1.28.0/bin/linux/amd64/kubectl",
225
+ "arm64": "https://dl.k8s.io/release/v1.28.0/bin/linux/arm64/kubectl"
226
+ }
227
+ )
228
+ ]
229
+ ```
230
+
231
+ ### Policies
232
+
233
+ Define OPA policies for your plugin:
234
+
235
+ ```python
236
+ __policies__ = [
237
+ Policy(
238
+ name_prefix="security-check",
239
+ type="notification",
240
+ body="""
241
+ package spacelift
242
+
243
+ webhook[{"endpoint_id": "security-alerts"}] {
244
+ input.run_updated.run.marked_unsafe == true
245
+ }
246
+ """,
247
+ labels={"type": "security"}
248
+ )
249
+ ]
250
+ ```
251
+
252
+ ### Webhooks
253
+
254
+ Define webhooks to trigger external actions:
255
+
256
+ ```python
257
+ __webhooks__ = [
258
+ Webhook(
259
+ name_prefix="security-alerts",
260
+ description="Send security alerts to external service",
261
+ endpoint="https://alerts.example.com/webhook",
262
+ secrets=[
263
+ Variable(key="amazing", value="secret-value", sensitive=True)
264
+ ],
265
+ )
266
+ ]
267
+ ```
268
+
269
+ ## Plugin Features
270
+
271
+ ### Logging
272
+
273
+ Built-in structured logging with run context:
274
+
275
+ ```python
276
+ def after_plan(self):
277
+ self.logger.info("Starting security scan")
278
+ self.logger.debug("Debug info (only shown when SPACELIFT_DEBUG=true)")
279
+ self.logger.warning("Warning message")
280
+ self.logger.error("Error occurred")
281
+ ```
282
+
283
+ ### CLI Execution
284
+
285
+ Run external commands with automatic logging:
286
+
287
+ ```python
288
+ def before_apply(self):
289
+ # Run command with automatic output capture
290
+ return_code, stdout, stderr = self.run_cli("terraform", "validate")
291
+
292
+ if return_code != 0:
293
+ self.logger.error("Terraform validation failed")
294
+ exit(1)
295
+ ```
296
+
297
+ ### Spacelift API Integration
298
+
299
+ Query the Spacelift GraphQL API (requires `SPACELIFT_API_TOKEN` and `SPACELIFT_DOMAIN`):
300
+
301
+ ```python
302
+ def after_plan(self):
303
+ result = self.query_api("""
304
+ query {
305
+ stack(id: "my-stack-id") {
306
+ name
307
+ state
308
+ latestRun {
309
+ id
310
+ state
311
+ }
312
+ }
313
+ }
314
+ """)
315
+
316
+ self.logger.info(f"Stack state: {result['stack']['state']}")
317
+ ```
318
+
319
+ ### Access Plan and State
320
+
321
+ Access Terraform plan and state data:
322
+
323
+ ```python
324
+ def after_plan(self):
325
+ # Get the current plan
326
+ plan = self.get_plan_json()
327
+
328
+ # Get the state before changes
329
+ state = self.get_state_before_json()
330
+
331
+ # Analyze planned changes
332
+ resource_count = len(plan.get('planned_values', {}).get('root_module', {}).get('resources', []))
333
+ self.logger.info(f"Planning to manage {resource_count} resources")
334
+ ```
335
+
336
+ ### Send Rich Output
337
+
338
+ Send formatted markdown to the Spacelift UI:
339
+
340
+ ```python
341
+ def after_plan(self):
342
+ markdown = """
343
+ # Security Scan Results
344
+
345
+ ✅ **Passed:** 45 checks
346
+ ⚠️ **Warnings:** 3 issues
347
+ ❌ **Failed:** 0 critical issues
348
+
349
+ [View detailed report](https://security.example.com/reports/123)
350
+ """
351
+
352
+ self.send_markdown(markdown)
353
+ ```
354
+
355
+ ### Append to Policy Input
356
+
357
+ Append custom data to the OPA policy input:
358
+
359
+ The following example will create input available via `input.third_party_metadata.custom.my_custom_data` in your OPA policies:
360
+ ```python
361
+ def after_plan(self):
362
+ self.append_policy_input("my_custom_data", {
363
+ "scan_results": {
364
+ "passed": True,
365
+ "issues": []
366
+ }
367
+ })
368
+ ```
369
+
370
+ ## CLI Commands
371
+
372
+ ### Generate Plugin Manifest
373
+
374
+ ```bash
375
+ # Generate from plugin.py (default filename)
376
+ spaceforge generate
377
+
378
+ # Generate from specific file
379
+ spaceforge generate my_plugin.py
380
+
381
+ # Specify output file
382
+ spaceforge generate my_plugin.py -o custom-output.yaml
383
+
384
+ # Get help
385
+ spaceforge generate --help
386
+ ```
387
+
388
+ ### Test Plugin Hooks
389
+
390
+ ```bash
391
+ # Set parameters via environment variables
392
+ export SPACEFORGE_PARAM_API_KEY="test-key"
393
+ export SPACEFORGE_PARAM_TIMEOUT="60"
394
+
395
+ # Test specific hook
396
+ spaceforge runner after_plan
397
+
398
+ # Test with specific plugin file
399
+ spaceforge runner --plugin-file my_plugin.py before_apply
400
+
401
+ # Get help
402
+ spaceforge runner --help
403
+ ```
404
+
405
+ ## Plugin Development Tips
406
+
407
+ ### 1. Handle Dependencies
408
+
409
+ If your plugin needs Python packages, create a `requirements.txt` file. Spaceforge automatically adds a `before_init` hook to install them:
410
+
411
+ ```txt
412
+ requests>=2.28.0
413
+ pydantic>=1.10.0
414
+ ```
415
+
416
+ ### 2. Environment Variables
417
+
418
+ Access Spacelift environment variables in your hooks:
419
+
420
+ ```python
421
+ def after_plan(self):
422
+ run_id = os.environ.get('SPACELIFT_RUN_ID')
423
+ stack_id = os.environ.get('SPACELIFT_STACK_ID')
424
+ self.logger.info(f"Processing run {run_id} for stack {stack_id}")
425
+ ```
426
+
427
+ ### 3. Error Handling
428
+
429
+ Always handle errors gracefully:
430
+
431
+ ```python
432
+ def after_plan(self):
433
+ try:
434
+ # Your plugin logic here
435
+ result = self.run_external_service()
436
+
437
+ except Exception as e:
438
+ self.logger.error(f"Plugin failed: {str(e)}")
439
+ # Exit with non-zero code to fail the run
440
+ exit(1)
441
+ ```
442
+
443
+ ### 4. Testing and Debugging
444
+
445
+ - Set `SPACELIFT_DEBUG=true` to enable debug logging
446
+ - Use the runner command to test hooks during development
447
+ - Test with different parameter combinations
448
+ - Validate your generated YAML before uploading to Spacelift
449
+
450
+ ## Example: Security Scanning Plugin
451
+
452
+ Here's a complete example of a security scanning plugin:
453
+
454
+ ```python
455
+ import os
456
+ import json
457
+ from spaceforge import SpaceforgePlugin, Parameter, Variable, Context, Binary
458
+
459
+ class SecurityScannerPlugin(SpaceforgePlugin):
460
+ __plugin_name__ = "security-scanner"
461
+ __version__ = "1.0.0"
462
+ __author__ = "Security Team"
463
+
464
+ __binaries__ = [
465
+ Binary(
466
+ name="security-cli",
467
+ download_urls={
468
+ "amd64": "https://releases.example.com/security-cli-linux-amd64",
469
+ "arm64": "https://releases.example.com/security-cli-linux-arm64"
470
+ }
471
+ )
472
+ ]
473
+
474
+ __parameters__ = [
475
+ Parameter(
476
+ name="api_token",
477
+ description="Security service API token",
478
+ required=True,
479
+ sensitive=True
480
+ ),
481
+ Parameter(
482
+ name="severity_threshold",
483
+ description="Minimum severity level to report",
484
+ required=False,
485
+ default="medium"
486
+ )
487
+ ]
488
+
489
+ __contexts__ = [
490
+ Context(
491
+ name_prefix="security-scanner",
492
+ description="Security scanning context",
493
+ env=[
494
+ Variable(
495
+ key="SECURITY_API_TOKEN",
496
+ value_from_parameter="api_token",
497
+ sensitive=True
498
+ ),
499
+ Variable(
500
+ key="SEVERITY_THRESHOLD",
501
+ value_from_parameter="severity_threshold"
502
+ )
503
+ ]
504
+ )
505
+ ]
506
+
507
+ def after_plan(self):
508
+ """Run security scan after Terraform plan"""
509
+ self.logger.info("Starting security scan of Terraform plan")
510
+
511
+ # Authenticate with security service
512
+ return_code, stdout, stderr = self.run_cli(
513
+ "security-cli", "auth",
514
+ "--token", os.environ["SECURITY_API_TOKEN"]
515
+ )
516
+
517
+ if return_code != 0:
518
+ self.logger.error("Failed to authenticate with security service")
519
+ exit(1)
520
+
521
+ # Scan the Terraform plan
522
+ return_code, stdout, stderr = self.run_cli(
523
+ "security-cli", "scan", "terraform",
524
+ "--plan-file", "spacelift.plan.json",
525
+ "--format", "json",
526
+ "--severity", os.environ.get("SEVERITY_THRESHOLD", "medium"),
527
+ print_output=False
528
+ )
529
+
530
+ if return_code != 0:
531
+ self.logger.error("Security scan failed")
532
+ for line in stderr:
533
+ self.logger.error(line)
534
+ exit(1)
535
+
536
+ # Parse scan results
537
+ try:
538
+ results = json.loads('\n'.join(stdout))
539
+
540
+ # Generate markdown report
541
+ markdown = self._generate_report(results)
542
+ self.send_markdown(markdown)
543
+
544
+ # Fail run if critical issues found
545
+ if results.get('critical_count', 0) > 0:
546
+ self.logger.error(f"Found {results['critical_count']} critical security issues")
547
+ exit(1)
548
+
549
+ self.logger.info("Security scan completed successfully")
550
+
551
+ except json.JSONDecodeError:
552
+ self.logger.error("Failed to parse scan results")
553
+ exit(1)
554
+
555
+ def _generate_report(self, results):
556
+ """Generate markdown report from scan results"""
557
+ report = "# Security Scan Results\n\n"
558
+
559
+ if results.get('total_issues', 0) == 0:
560
+ report += "✅ **No security issues found!**\n"
561
+ else:
562
+ report += f"Found {results['total_issues']} security issues:\n\n"
563
+
564
+ for severity in ['critical', 'high', 'medium', 'low']:
565
+ count = results.get(f'{severity}_count', 0)
566
+ if count > 0:
567
+ emoji = {'critical': '🔴', 'high': '🟠', 'medium': '🟡', 'low': '🟢'}[severity]
568
+ report += f"- {emoji} **{severity.upper()}:** {count}\n"
569
+
570
+ if results.get('report_url'):
571
+ report += f"\n[View detailed report]({results['report_url']})\n"
572
+
573
+ return report
574
+ ```
575
+
576
+ Generate and test this plugin:
577
+
578
+ ```bash
579
+ # Generate the manifest
580
+ spaceforge generate security_scanner.py
581
+
582
+ # Test locally
583
+ export SPACEFORGE_PARAM_API_TOKEN="your-token"
584
+ export SPACEFORGE_PARAM_SEVERITY_THRESHOLD="high"
585
+ spaceforge runner after_plan
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
+
597
+ ## Next Steps
598
+
599
+ 1. **Install spaceforge:** `pip install spaceforge`
600
+ 2. **Create your plugin:** Start with the quick start example
601
+ 3. **Test locally:** Use the runner command to test your hooks
602
+ 4. **Generate manifest:** Use the generate command to create plugin.yaml
603
+ 5. **Upload to Spacelift:** Add your plugin manifest to your Spacelift account
604
+
605
+ For more advanced examples, see the [plugins](plugins/) directory in this repository.
@@ -0,0 +1,33 @@
1
+ spaceforge/README.md,sha256=8o1Nuyasb4OxX3E7ZycyducOrR4J19bZcHrLvFeoFNg,7730
2
+ spaceforge/__init__.py,sha256=TU-vvm15dK1ucixNW0V42eTT72x3_hmKSyxP4MC1Occ,589
3
+ spaceforge/__main__.py,sha256=c3nAw4WBnHXIcfMlRV6Ja7r87pEhSeK-SAqiSYIasIY,643
4
+ spaceforge/_version.py,sha256=RP_LfUd4ODnrfwn9nam8wB6bR3lM4VwmoRxK08Tkiiw,2155
5
+ spaceforge/_version_scm.py,sha256=QlXZ5JTjE_pgpDaeHk0GTExkc75xUZFmd0hA7kGYCJ0,704
6
+ spaceforge/cls.py,sha256=3Js2i0kfWBfa9g-VthuARc3WGwrbdpiQzbQYij59dMo,5981
7
+ spaceforge/conftest.py,sha256=U-xCavCsgRAQXqflIIOMeq9pcGbeqRviUNkEXgZol8g,2141
8
+ spaceforge/generator.py,sha256=kwWEyVqKaOIgZI4nmu4q30ZefA7w10vj78PmROWW5V4,16484
9
+ spaceforge/plugin.py,sha256=8qPDieG2i6pFBg02GYXSb06DU9xR51b20kU4X9Kv6Ic,13051
10
+ spaceforge/runner.py,sha256=aBQQLG8MFVrqCzM0X8HgERYbhLKbmlOHUPKjV7-xvpA,3163
11
+ spaceforge/schema.json,sha256=syoxxTJuZ9Wn91vLRHeffyDi7xYHjwSZ14AheugLzhY,10336
12
+ spaceforge/test_cls.py,sha256=nXAgbnFnGdFxrtA7vNXiePjNUASuoYW-lEuQGx9WMGs,468
13
+ spaceforge/test_generator.py,sha256=liCccHjGQ7SmKc1RCrceS18bisPA9H0FSvMbgOIZ-Sg,32615
14
+ spaceforge/test_generator_binaries.py,sha256=X_7pPLGE45eQt-Kv9_ku__LsyLgOvViHc_BvVpSCMp0,7263
15
+ spaceforge/test_generator_core.py,sha256=gOqRx0rnME-srGMHun4KidXMN-iaqfKKTyoQ0Tw6b9Q,6253
16
+ spaceforge/test_generator_hooks.py,sha256=2lJs8dYlFb7QehWcYF0O4qg38s5UudEpzJyBi1XiS3k,2542
17
+ spaceforge/test_generator_parameters.py,sha256=77az9rcocFny2AC4O2eTzjCW712fR1DBHzGrgBKeR4w,1878
18
+ spaceforge/test_plugin.py,sha256=pFst9RUpTujuv_zdhWnB9LMeB_vN9qHLNEhnmo8WzkQ,13046
19
+ spaceforge/test_plugin_file_operations.py,sha256=B0qvIo5EcfKMiHLhBv-hAnpSonn83ojcmJHXasydojA,3782
20
+ spaceforge/test_plugin_hooks.py,sha256=rNCZZyd_SDMkm1x3yl5mjQ5tBMGm3YNd1U6h_niWRQs,2962
21
+ spaceforge/test_plugin_inheritance.py,sha256=WHfvU5s-2GtfcI9-1bHXH7bacr77ikq68V3Z3BBQKvQ,3617
22
+ spaceforge/test_runner.py,sha256=HOGJ-RAA1k82zLWPgtYVWseh7KMXJKGs2S2xDf2TgJo,17730
23
+ spaceforge/test_runner_cli.py,sha256=Sf5X0O9Wc9EhGB5L8SzvlmO7QmgQZQoClSdNYefa-lQ,2299
24
+ spaceforge/test_runner_core.py,sha256=eNR9YOwJwv7LsMtNQ4WXXMPIW6RE_A7hUp4bCpzz1Rk,3941
25
+ spaceforge/test_runner_execution.py,sha256=BRlOApCmlpjE9YYvqHc8creSRu2vyPubzOCVXv-on0w,5335
26
+ spaceforge/templates/binary_install.sh.j2,sha256=UjP_kAdvkkATKds2nqk4ouqLhuIfCgM_x0WQIl1hbIo,477
27
+ spaceforge/templates/ensure_spaceforge_and_run.sh.j2,sha256=qU5UvJhxh8toLrTSH3fUv7UPFaKo7Z1mQCV_bpToDB4,458
28
+ spaceforge-0.0.4.dist-info/licenses/LICENSE,sha256=wyljRrfnWY2ggQKkSCg3Nw2hxwPMmupopaKs9Kpgys8,1065
29
+ spaceforge-0.0.4.dist-info/METADATA,sha256=6o_AicFSDgu2TnQZIodI7barxJODWNgfMoRtd9DyLzA,16771
30
+ spaceforge-0.0.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
31
+ spaceforge-0.0.4.dist-info/entry_points.txt,sha256=qawuuKBSNTGg-njnQnhxxFldFvXYAPej6bF_f3iyQ48,56
32
+ spaceforge-0.0.4.dist-info/top_level.txt,sha256=eVw-Lw4Th0oHM8Gx1Y8YetyNgbNbMBU00yWs-kwGeSs,11
33
+ spaceforge-0.0.4.dist-info/RECORD,,