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