spaceforge 0.1.0.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- spaceforge/README.md +279 -0
- spaceforge/__init__.py +23 -0
- spaceforge/__main__.py +33 -0
- spaceforge/_version.py +81 -0
- spaceforge/cls.py +198 -0
- spaceforge/cls_test.py +17 -0
- spaceforge/generator.py +362 -0
- spaceforge/generator_test.py +671 -0
- spaceforge/plugin.py +275 -0
- spaceforge/plugin_test.py +621 -0
- spaceforge/runner.py +115 -0
- spaceforge/runner_test.py +605 -0
- spaceforge/schema.json +371 -0
- spaceforge-0.1.0.dev0.dist-info/METADATA +163 -0
- spaceforge-0.1.0.dev0.dist-info/RECORD +19 -0
- spaceforge-0.1.0.dev0.dist-info/WHEEL +5 -0
- spaceforge-0.1.0.dev0.dist-info/entry_points.txt +2 -0
- spaceforge-0.1.0.dev0.dist-info/licenses/LICENSE +21 -0
- spaceforge-0.1.0.dev0.dist-info/top_level.txt +1 -0
spaceforge/README.md
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
# Spaceforge Framework
|
|
2
|
+
|
|
3
|
+
A Python framework for building Spacelift plugins with hook-based functionality.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Spaceforge provides a simple, declarative way to create Spacelift plugins by inheriting from the `SpaceforgePlugin` base class and implementing hook methods. The framework automatically handles parameter loading, logging, and YAML generation.
|
|
8
|
+
|
|
9
|
+
## Architecture
|
|
10
|
+
|
|
11
|
+
### Core Components
|
|
12
|
+
|
|
13
|
+
- **SpaceforgePlugin** (`plugin.py`) - Base class with hook methods and utilities
|
|
14
|
+
- **PluginRunner** (`runner.py`) - Executes hook methods, loads parameters from environment
|
|
15
|
+
- **PluginGenerator** (`generator.py`) - Analyzes Python plugins and generates `plugin.yaml`
|
|
16
|
+
- **CLI Interface** (`__main__.py`) - Click-based CLI with `generate` and `runner` subcommands
|
|
17
|
+
- **Pydantic Dataclasses** (`cls.py`) - Type-safe data structures with validation
|
|
18
|
+
|
|
19
|
+
### Data Validation
|
|
20
|
+
|
|
21
|
+
The framework uses pydantic dataclasses for all plugin definitions, providing:
|
|
22
|
+
- **Type safety**: All plugin components are strongly typed
|
|
23
|
+
- **Automatic validation**: Data structures are validated during object creation
|
|
24
|
+
- **JSON schema generation**: Plugin manifests include JSON schema for validation
|
|
25
|
+
- **Runtime checks**: For example, Variables must have either `value` or `value_from_parameter`
|
|
26
|
+
|
|
27
|
+
### Plugin Structure
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
from spaceforge import SpaceforgePlugin, Parameter, Variable, Context, Webhook, Policy, MountedFile
|
|
31
|
+
|
|
32
|
+
class MyPlugin(SpaceforgePlugin):
|
|
33
|
+
# Plugin metadata
|
|
34
|
+
__plugin_name__ = "my-plugin"
|
|
35
|
+
__version__ = "1.0.0"
|
|
36
|
+
__author__ = "Your Name"
|
|
37
|
+
|
|
38
|
+
# Parameter definitions using pydantic dataclasses
|
|
39
|
+
__parameters__ = [
|
|
40
|
+
Parameter(
|
|
41
|
+
name="api_key",
|
|
42
|
+
description="API key for authentication",
|
|
43
|
+
required=True,
|
|
44
|
+
sensitive=True
|
|
45
|
+
)
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
# Context definitions using pydantic dataclasses
|
|
49
|
+
__contexts__ = [
|
|
50
|
+
Context(
|
|
51
|
+
name="main",
|
|
52
|
+
description="Main plugin context",
|
|
53
|
+
env=[
|
|
54
|
+
Variable(
|
|
55
|
+
key="API_KEY",
|
|
56
|
+
value_from_parameter="api_key",
|
|
57
|
+
sensitive=True
|
|
58
|
+
)
|
|
59
|
+
],
|
|
60
|
+
hooks={
|
|
61
|
+
"after_plan": ["echo 'Custom command here'"]
|
|
62
|
+
},
|
|
63
|
+
mounted_files=[
|
|
64
|
+
MountedFile(
|
|
65
|
+
path="config.yaml",
|
|
66
|
+
content="key: value",
|
|
67
|
+
sensitive=False
|
|
68
|
+
)
|
|
69
|
+
]
|
|
70
|
+
)
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
# Webhook definitions using pydantic dataclasses
|
|
74
|
+
__webhooks__ = [
|
|
75
|
+
Webhook(
|
|
76
|
+
name="my_webhook",
|
|
77
|
+
endpoint="https://example.com/webhook",
|
|
78
|
+
secrets=[
|
|
79
|
+
Variable(
|
|
80
|
+
key="WEBHOOK_SECRET",
|
|
81
|
+
value_from_parameter="api_key"
|
|
82
|
+
)
|
|
83
|
+
]
|
|
84
|
+
)
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
# Policy definitions using pydantic dataclasses
|
|
88
|
+
__policies__ = [
|
|
89
|
+
Policy(
|
|
90
|
+
name="my_policy",
|
|
91
|
+
type="notification",
|
|
92
|
+
body="package spacelift\n# Policy content here",
|
|
93
|
+
labels={"type": "security"}
|
|
94
|
+
)
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
def after_plan(self):
|
|
98
|
+
self.logger.info("Running after plan")
|
|
99
|
+
# Your plugin logic here
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Available Hooks
|
|
103
|
+
|
|
104
|
+
Override these methods in your plugin:
|
|
105
|
+
|
|
106
|
+
- `before_init()` - Before Terraform init
|
|
107
|
+
- `after_init()` - After Terraform init
|
|
108
|
+
- `before_plan()` - Before Terraform plan
|
|
109
|
+
- `after_plan()` - After Terraform plan
|
|
110
|
+
- `before_apply()` - Before Terraform apply
|
|
111
|
+
- `after_apply()` - After Terraform apply
|
|
112
|
+
- `before_perform()` - Before the run performs
|
|
113
|
+
- `after_perform()` - After the run performs
|
|
114
|
+
- `before_destroy()` - Before Terraform destroy
|
|
115
|
+
- `after_destroy()` - After Terraform destroy
|
|
116
|
+
- `after_run()` - After the run completes
|
|
117
|
+
|
|
118
|
+
## Plugin Features
|
|
119
|
+
|
|
120
|
+
### Logging
|
|
121
|
+
|
|
122
|
+
Built-in colored logging with run ID and plugin name:
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
self.logger.info("Information message")
|
|
126
|
+
self.logger.debug("Debug message") # Only shown when SPACELIFT_DEBUG=true
|
|
127
|
+
self.logger.warning("Warning message")
|
|
128
|
+
self.logger.error("Error message")
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### CLI Execution
|
|
132
|
+
|
|
133
|
+
Run external commands with logging:
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
self.run_cli("terraform", "plan", "-out=tfplan")
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Spacelift API Integration
|
|
140
|
+
|
|
141
|
+
Query the Spacelift GraphQL API:
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
# Requires SPACELIFT_API_TOKEN and SPACELIFT_DOMAIN environment variables
|
|
145
|
+
result = self.query_api("""
|
|
146
|
+
query {
|
|
147
|
+
stack(id: "stack-id") {
|
|
148
|
+
name
|
|
149
|
+
state
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
""")
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Plan and State Access
|
|
156
|
+
|
|
157
|
+
Access Terraform plan and state data:
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
plan = self.get_plan_json() # Returns parsed spacelift.plan.json
|
|
161
|
+
state = self.get_state_before_json() # Returns parsed spacelift.state.before.json
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Parameter System
|
|
165
|
+
|
|
166
|
+
Parameters are defined using the `Parameter` pydantic dataclass and automatically loaded from environment variables:
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
from spaceforge import Parameter
|
|
170
|
+
|
|
171
|
+
__parameters__ = [
|
|
172
|
+
Parameter(
|
|
173
|
+
name="database_url",
|
|
174
|
+
description="Database connection URL",
|
|
175
|
+
required=True,
|
|
176
|
+
sensitive=True,
|
|
177
|
+
default="postgresql://localhost:5432/mydb"
|
|
178
|
+
)
|
|
179
|
+
]
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Parameters are interpolated by spacelift at install time, allowing you to reference them in contexts and hooks using `${param.name}` syntax.
|
|
183
|
+
|
|
184
|
+
## Context System
|
|
185
|
+
|
|
186
|
+
Contexts define Spacelift environments using the `Context` pydantic dataclass:
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
from spaceforge import Context, Variable, MountedFile
|
|
190
|
+
|
|
191
|
+
__contexts__ = [
|
|
192
|
+
Context(
|
|
193
|
+
name="production",
|
|
194
|
+
description="Production environment",
|
|
195
|
+
labels={
|
|
196
|
+
"environment": "production"
|
|
197
|
+
},
|
|
198
|
+
env=[
|
|
199
|
+
Variable(
|
|
200
|
+
key="DATABASE_URL",
|
|
201
|
+
value_from_parameter="database_url",
|
|
202
|
+
sensitive=True
|
|
203
|
+
)
|
|
204
|
+
],
|
|
205
|
+
hooks={
|
|
206
|
+
"after_plan": [
|
|
207
|
+
"echo 'Running production validation'"
|
|
208
|
+
]
|
|
209
|
+
},
|
|
210
|
+
mounted_files=[
|
|
211
|
+
MountedFile(
|
|
212
|
+
path="app.conf",
|
|
213
|
+
content="production config",
|
|
214
|
+
sensitive=False
|
|
215
|
+
)
|
|
216
|
+
]
|
|
217
|
+
)
|
|
218
|
+
]
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
**Important**: Variables must have either a `value` or `value_from_parameter` field. The framework automatically validates this during plugin generation.
|
|
222
|
+
|
|
223
|
+
## CLI Usage
|
|
224
|
+
|
|
225
|
+
### Generate Plugin YAML
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
# Generate from plugin.py (default)
|
|
229
|
+
python -m spaceforge generate
|
|
230
|
+
|
|
231
|
+
# Generate from specific file
|
|
232
|
+
python -m spaceforge generate my_plugin.py
|
|
233
|
+
|
|
234
|
+
# Specify output file
|
|
235
|
+
python -m spaceforge generate my_plugin.py -o my_plugin.yaml
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Test Plugin Hooks
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
# Set plugin parameters
|
|
242
|
+
export API_KEY="your-key"
|
|
243
|
+
|
|
244
|
+
# Run specific hook
|
|
245
|
+
python -m spaceforge runner after_plan
|
|
246
|
+
|
|
247
|
+
# Run with specific plugin file
|
|
248
|
+
python -m spaceforge runner --plugin-file my_plugin.py before_apply
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Get Help
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
python -m spaceforge --help
|
|
255
|
+
python -m spaceforge generate --help
|
|
256
|
+
python -m spaceforge runner --help
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Generated YAML Structure
|
|
260
|
+
|
|
261
|
+
The framework automatically generates standard Spacelift plugin YAML:
|
|
262
|
+
|
|
263
|
+
## Development Tips
|
|
264
|
+
|
|
265
|
+
1. **Requirements**: If your plugin has dependencies, create a `requirements.txt` file. The generator will automatically add a `before_init` hook to install them.
|
|
266
|
+
|
|
267
|
+
2. **Testing**: Use the runner command to test individual hooks during development.
|
|
268
|
+
|
|
269
|
+
3. **Debugging**: Set `SPACELIFT_DEBUG=true` to enable debug logging.
|
|
270
|
+
|
|
271
|
+
4. **API Access**: Export `SPACELIFT_API_TOKEN` and `SPACELIFT_DOMAIN` to enable Spacelift API queries.
|
|
272
|
+
|
|
273
|
+
## Error Handling
|
|
274
|
+
|
|
275
|
+
The framework provides built-in error handling:
|
|
276
|
+
- Failed CLI commands are logged with return codes
|
|
277
|
+
- API errors are logged and returned in the response
|
|
278
|
+
- Missing files and import errors are handled gracefully
|
|
279
|
+
- Hook execution errors are caught and re-raised with context
|
spaceforge/__init__.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Spaceforge - Spacelift Plugin Framework
|
|
3
|
+
|
|
4
|
+
A Python framework for building Spacelift plugins with hook-based functionality.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from ._version import get_version
|
|
8
|
+
from .cls import Binary, Context, MountedFile, Parameter, Policy, Variable, Webhook
|
|
9
|
+
from .plugin import SpaceforgePlugin
|
|
10
|
+
from .runner import PluginRunner
|
|
11
|
+
|
|
12
|
+
__version__ = get_version()
|
|
13
|
+
__all__ = [
|
|
14
|
+
"SpaceforgePlugin",
|
|
15
|
+
"PluginRunner",
|
|
16
|
+
"Parameter",
|
|
17
|
+
"Variable",
|
|
18
|
+
"Context",
|
|
19
|
+
"Webhook",
|
|
20
|
+
"Policy",
|
|
21
|
+
"MountedFile",
|
|
22
|
+
"Binary",
|
|
23
|
+
]
|
spaceforge/__main__.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Main entry point for spaceforge module.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from ._version import get_version
|
|
8
|
+
from .generator import generate_command
|
|
9
|
+
from .runner import runner_command
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@click.group()
|
|
13
|
+
@click.version_option(version=get_version(), prog_name="spaceforge")
|
|
14
|
+
def cli() -> None:
|
|
15
|
+
"""Spaceforge - Spacelift Plugin Framework
|
|
16
|
+
|
|
17
|
+
A Python framework for building Spacelift plugins with hook-based functionality.
|
|
18
|
+
"""
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# Add subcommands
|
|
23
|
+
cli.add_command(generate_command)
|
|
24
|
+
cli.add_command(runner_command)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def main() -> None:
|
|
28
|
+
"""Main entry point."""
|
|
29
|
+
cli()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
if __name__ == "__main__":
|
|
33
|
+
main()
|
spaceforge/_version.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Dynamic version detection from git tags.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import subprocess
|
|
6
|
+
import sys
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_git_version() -> Optional[str]:
|
|
11
|
+
"""
|
|
12
|
+
Get version from git tags.
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
Version string (without 'v' prefix) or None if not available
|
|
16
|
+
"""
|
|
17
|
+
try:
|
|
18
|
+
# Try to get the current tag
|
|
19
|
+
result = subprocess.run(
|
|
20
|
+
["git", "describe", "--tags", "--exact-match"],
|
|
21
|
+
capture_output=True,
|
|
22
|
+
text=True,
|
|
23
|
+
check=True,
|
|
24
|
+
)
|
|
25
|
+
tag = result.stdout.strip()
|
|
26
|
+
# Remove 'v' prefix if present
|
|
27
|
+
return tag[1:] if tag.startswith("v") else tag
|
|
28
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
29
|
+
# Fall back to describe with commit info
|
|
30
|
+
try:
|
|
31
|
+
result = subprocess.run(
|
|
32
|
+
["git", "describe", "--tags", "--always"],
|
|
33
|
+
capture_output=True,
|
|
34
|
+
text=True,
|
|
35
|
+
check=True,
|
|
36
|
+
)
|
|
37
|
+
tag = result.stdout.strip()
|
|
38
|
+
# Remove 'v' prefix if present
|
|
39
|
+
return tag[1:] if tag.startswith("v") else tag
|
|
40
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def get_version() -> str:
|
|
45
|
+
"""
|
|
46
|
+
Get the package version.
|
|
47
|
+
|
|
48
|
+
Tries git tags first, then setuptools-scm, falls back to default version.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Version string
|
|
52
|
+
"""
|
|
53
|
+
# Try git version first
|
|
54
|
+
git_version = get_git_version()
|
|
55
|
+
if git_version:
|
|
56
|
+
return git_version
|
|
57
|
+
|
|
58
|
+
# Try setuptools-scm generated version file
|
|
59
|
+
try:
|
|
60
|
+
from ._version_scm import version # type: ignore[import-not-found]
|
|
61
|
+
|
|
62
|
+
return str(version)
|
|
63
|
+
except ImportError:
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
# Try setuptools-scm directly
|
|
67
|
+
try:
|
|
68
|
+
from setuptools_scm import (
|
|
69
|
+
get_version as scm_get_version, # type: ignore[import-untyped]
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
result = scm_get_version(root="..", relative_to=__file__)
|
|
73
|
+
return str(result)
|
|
74
|
+
except ImportError:
|
|
75
|
+
pass
|
|
76
|
+
except Exception:
|
|
77
|
+
# setuptools_scm might fail in various ways, ignore
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
# Fall back to default version for development
|
|
81
|
+
return "0.1.0-dev"
|
spaceforge/cls.py
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
from typing import Dict, List, Literal, Optional
|
|
2
|
+
|
|
3
|
+
from pydantic import Field
|
|
4
|
+
from pydantic.dataclasses import dataclass as pydantic_dataclass
|
|
5
|
+
|
|
6
|
+
# For truly optional fields without default: null in schema
|
|
7
|
+
optional_field = Field(default_factory=lambda: None, exclude=True)
|
|
8
|
+
|
|
9
|
+
BinaryType = Literal[
|
|
10
|
+
"amd64",
|
|
11
|
+
"arm64",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pydantic_dataclass
|
|
16
|
+
class Binary:
|
|
17
|
+
"""
|
|
18
|
+
A class to represent a binary file.
|
|
19
|
+
|
|
20
|
+
Attributes:
|
|
21
|
+
name (str): The name of the binary file.
|
|
22
|
+
path (str): The path to the binary file.
|
|
23
|
+
sensitive (bool): Whether the binary file is sensitive.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
name: str
|
|
27
|
+
download_urls: Dict[BinaryType, str]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@pydantic_dataclass
|
|
31
|
+
class Parameter:
|
|
32
|
+
"""
|
|
33
|
+
A class to represent a parameter with a name and value.
|
|
34
|
+
|
|
35
|
+
Attributes:
|
|
36
|
+
name (str): The name of the parameter.
|
|
37
|
+
description (str): A description of the parameter.
|
|
38
|
+
sensitive (bool): Whether the parameter contains sensitive information.
|
|
39
|
+
required (bool): Whether the parameter is required.
|
|
40
|
+
default (Optional[str]): The default value of the parameter, if any. (required if sensitive is False)
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
name: str
|
|
44
|
+
description: str
|
|
45
|
+
sensitive: bool = False
|
|
46
|
+
required: bool = False
|
|
47
|
+
default: Optional[str] = None
|
|
48
|
+
|
|
49
|
+
def __post_init__(self) -> None:
|
|
50
|
+
if not self.required and self.default is None:
|
|
51
|
+
raise ValueError(
|
|
52
|
+
f"Default value for parameter {self.name} should be set if parameter is optional."
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@pydantic_dataclass
|
|
57
|
+
class Variable:
|
|
58
|
+
"""
|
|
59
|
+
A class to represent an environment variable.
|
|
60
|
+
|
|
61
|
+
Attributes:
|
|
62
|
+
key (str): The key of the environment variable.
|
|
63
|
+
value (Optional[str]): The value of the environment variable, if set.
|
|
64
|
+
value_from_parameter (Optional[str]): The name of the plugin variable to use as the value.
|
|
65
|
+
sensitive (bool): Whether the environment variable is sensitive.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
key: str
|
|
69
|
+
value: Optional[str] = optional_field
|
|
70
|
+
value_from_parameter: Optional[str] = optional_field
|
|
71
|
+
sensitive: bool = False
|
|
72
|
+
|
|
73
|
+
def __post_init__(self) -> None:
|
|
74
|
+
if self.value is None and self.value_from_parameter is None:
|
|
75
|
+
raise ValueError(
|
|
76
|
+
"Either value or value_from_parameter must be set for EnvVariable."
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@pydantic_dataclass
|
|
81
|
+
class MountedFile:
|
|
82
|
+
"""
|
|
83
|
+
A class to represent a mounted file.
|
|
84
|
+
|
|
85
|
+
Attributes:
|
|
86
|
+
path (str): The path of the mounted file.
|
|
87
|
+
content (str): The content of the mounted file.
|
|
88
|
+
sensitive (bool): Whether the content of the file is sensitive.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
path: str
|
|
92
|
+
content: str
|
|
93
|
+
sensitive: bool = False
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
HookType = Literal[
|
|
97
|
+
"before_init",
|
|
98
|
+
"after_init",
|
|
99
|
+
"before_plan",
|
|
100
|
+
"after_plan",
|
|
101
|
+
"before_apply",
|
|
102
|
+
"after_apply",
|
|
103
|
+
"before_perform",
|
|
104
|
+
"after_perform",
|
|
105
|
+
"before_destroy",
|
|
106
|
+
"after_destroy",
|
|
107
|
+
"after_run",
|
|
108
|
+
]
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@pydantic_dataclass
|
|
112
|
+
class Context:
|
|
113
|
+
"""
|
|
114
|
+
A class to represent a context for a plugin.
|
|
115
|
+
|
|
116
|
+
Attributes:
|
|
117
|
+
name_prefix (str): The name of the context, will be appended with a unique ID.
|
|
118
|
+
description (str): A description of the context.
|
|
119
|
+
labels (dict): Labels associated with the context.
|
|
120
|
+
env (list): List of variables associated with the context.
|
|
121
|
+
hooks (dict): Hooks associated with the context.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
name_prefix: str
|
|
125
|
+
description: str
|
|
126
|
+
env: Optional[List[Variable]] = optional_field
|
|
127
|
+
mounted_files: Optional[List[MountedFile]] = optional_field
|
|
128
|
+
hooks: Optional[Dict[HookType, List[str]]] = optional_field
|
|
129
|
+
labels: Optional[Dict[str, str]] = optional_field
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@pydantic_dataclass
|
|
133
|
+
class Webhook:
|
|
134
|
+
"""
|
|
135
|
+
A class to represent a webhook configuration.
|
|
136
|
+
|
|
137
|
+
Attributes:
|
|
138
|
+
name_prefix (str): The name of the webhook, will be appended with a unique ID.
|
|
139
|
+
endpoint (str): The URL endpoint for the webhook.
|
|
140
|
+
labels (Optional[dict]): Labels associated with the webhook.
|
|
141
|
+
secrets (Optional[list[Variable]]): List of secrets associated with the webhook.
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
name_prefix: str
|
|
145
|
+
endpoint: str
|
|
146
|
+
labels: Optional[Dict[str, str]] = optional_field
|
|
147
|
+
secrets: Optional[List[Variable]] = optional_field
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@pydantic_dataclass
|
|
151
|
+
class Policy:
|
|
152
|
+
"""
|
|
153
|
+
A class to represent a policy configuration.
|
|
154
|
+
|
|
155
|
+
Attributes:
|
|
156
|
+
name_prefix (str): The name of the policy, will be appended with a unique ID.
|
|
157
|
+
type (str): The type of the policy (e.g., "terraform", "kubernetes").
|
|
158
|
+
body (str): The body of the policy, typically a configuration or script.
|
|
159
|
+
labels (Optional[dict[str, str]]): Labels associated with the policy.
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
name_prefix: str
|
|
163
|
+
type: str
|
|
164
|
+
body: str
|
|
165
|
+
labels: Optional[Dict[str, str]] = optional_field
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@pydantic_dataclass
|
|
169
|
+
class PluginManifest:
|
|
170
|
+
"""
|
|
171
|
+
A class to represent the manifest of a Spacelift plugin.
|
|
172
|
+
|
|
173
|
+
Attributes:
|
|
174
|
+
name_prefix (str): The name of the plugin, will be appended with a unique ID.
|
|
175
|
+
description (str): A description of the plugin.
|
|
176
|
+
author (str): The author of the plugin.
|
|
177
|
+
parameters (list[Parameter]): List of parameters for the plugin.
|
|
178
|
+
contexts (list[Context]): List of contexts for the plugin.
|
|
179
|
+
webhooks (list[Webhook]): List of webhooks for the plugin.
|
|
180
|
+
policies (list[Policy]): List of policies for the plugin.
|
|
181
|
+
"""
|
|
182
|
+
|
|
183
|
+
name_prefix: str
|
|
184
|
+
version: str
|
|
185
|
+
description: str
|
|
186
|
+
author: str
|
|
187
|
+
parameters: Optional[List[Parameter]] = optional_field
|
|
188
|
+
contexts: Optional[List[Context]] = optional_field
|
|
189
|
+
webhooks: Optional[List[Webhook]] = optional_field
|
|
190
|
+
policies: Optional[List[Policy]] = optional_field
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
if __name__ == "__main__":
|
|
194
|
+
import json
|
|
195
|
+
|
|
196
|
+
from pydantic import TypeAdapter
|
|
197
|
+
|
|
198
|
+
print(json.dumps(TypeAdapter(PluginManifest).json_schema(), indent=2))
|
spaceforge/cls_test.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from spaceforge import Parameter, Variable
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_ensure_optional_parameters_require_default_values() -> None:
|
|
7
|
+
with pytest.raises(ValueError):
|
|
8
|
+
Parameter(
|
|
9
|
+
name="optional_default",
|
|
10
|
+
description="default value",
|
|
11
|
+
required=False,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_ensure_variables_have_either_value_or_value_from_parameter() -> None:
|
|
16
|
+
with pytest.raises(ValueError):
|
|
17
|
+
Variable(key="test_var", sensitive=False)
|