spaceforge 0.0.5__tar.gz → 0.0.7__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.
- {spaceforge-0.0.5 → spaceforge-0.0.7}/PKG-INFO +2 -1
- spaceforge-0.0.7/plugins/enviroment_manager/plugin.py +248 -0
- spaceforge-0.0.7/plugins/enviroment_manager/plugin.yaml +416 -0
- spaceforge-0.0.7/plugins/enviroment_manager/requirements.txt +1 -0
- {spaceforge-0.0.5 → spaceforge-0.0.7}/plugins/infracost/plugin.py +1 -1
- {spaceforge-0.0.5 → spaceforge-0.0.7}/plugins/infracost/plugin.yaml +2 -2
- {spaceforge-0.0.5 → spaceforge-0.0.7}/plugins/sops/plugin.py +1 -1
- {spaceforge-0.0.5 → spaceforge-0.0.7}/plugins/sops/plugin.yaml +5 -3
- {spaceforge-0.0.5 → spaceforge-0.0.7}/plugins/wiz/plugin.py +25 -5
- {spaceforge-0.0.5 → spaceforge-0.0.7}/plugins/wiz/plugin.yaml +52 -10
- {spaceforge-0.0.5 → spaceforge-0.0.7}/pyproject.toml +1 -0
- {spaceforge-0.0.5 → spaceforge-0.0.7}/setup.py +1 -0
- {spaceforge-0.0.5 → spaceforge-0.0.7}/spaceforge/_version_scm.py +3 -3
- {spaceforge-0.0.5 → spaceforge-0.0.7}/spaceforge/cls.py +4 -1
- {spaceforge-0.0.5 → spaceforge-0.0.7}/spaceforge/generator.py +16 -8
- {spaceforge-0.0.5 → spaceforge-0.0.7}/spaceforge/plugin.py +22 -17
- {spaceforge-0.0.5 → spaceforge-0.0.7}/spaceforge/schema.json +7 -0
- {spaceforge-0.0.5 → spaceforge-0.0.7}/spaceforge/templates/ensure_spaceforge_and_run.sh.j2 +3 -1
- {spaceforge-0.0.5 → spaceforge-0.0.7}/spaceforge/test_generator.py +2 -2
- {spaceforge-0.0.5 → spaceforge-0.0.7}/spaceforge/test_plugin.py +7 -7
- {spaceforge-0.0.5 → spaceforge-0.0.7}/spaceforge.egg-info/PKG-INFO +2 -1
- {spaceforge-0.0.5 → spaceforge-0.0.7}/spaceforge.egg-info/SOURCES.txt +3 -0
- {spaceforge-0.0.5 → spaceforge-0.0.7}/spaceforge.egg-info/requires.txt +1 -0
- {spaceforge-0.0.5 → spaceforge-0.0.7}/.github/workflows/ci.yml +0 -0
- {spaceforge-0.0.5 → spaceforge-0.0.7}/.github/workflows/release.yml +0 -0
- {spaceforge-0.0.5 → spaceforge-0.0.7}/.gitignore +0 -0
- {spaceforge-0.0.5 → spaceforge-0.0.7}/LICENSE +0 -0
- {spaceforge-0.0.5 → spaceforge-0.0.7}/MANIFEST.in +0 -0
- {spaceforge-0.0.5 → spaceforge-0.0.7}/README.md +0 -0
- {spaceforge-0.0.5 → spaceforge-0.0.7}/go.mod +0 -0
- {spaceforge-0.0.5 → spaceforge-0.0.7}/plugins/sops/requirements.txt +0 -0
- {spaceforge-0.0.5 → spaceforge-0.0.7}/setup.cfg +0 -0
- {spaceforge-0.0.5 → spaceforge-0.0.7}/spaceforge/README.md +0 -0
- {spaceforge-0.0.5 → spaceforge-0.0.7}/spaceforge/__init__.py +0 -0
- {spaceforge-0.0.5 → spaceforge-0.0.7}/spaceforge/__main__.py +0 -0
- {spaceforge-0.0.5 → spaceforge-0.0.7}/spaceforge/_version.py +0 -0
- {spaceforge-0.0.5 → spaceforge-0.0.7}/spaceforge/conftest.py +0 -0
- {spaceforge-0.0.5 → spaceforge-0.0.7}/spaceforge/runner.py +0 -0
- {spaceforge-0.0.5 → spaceforge-0.0.7}/spaceforge/templates/binary_install.sh.j2 +0 -0
- {spaceforge-0.0.5 → spaceforge-0.0.7}/spaceforge/test_cls.py +0 -0
- {spaceforge-0.0.5 → spaceforge-0.0.7}/spaceforge/test_generator_binaries.py +0 -0
- {spaceforge-0.0.5 → spaceforge-0.0.7}/spaceforge/test_generator_core.py +0 -0
- {spaceforge-0.0.5 → spaceforge-0.0.7}/spaceforge/test_generator_hooks.py +0 -0
- {spaceforge-0.0.5 → spaceforge-0.0.7}/spaceforge/test_generator_parameters.py +0 -0
- {spaceforge-0.0.5 → spaceforge-0.0.7}/spaceforge/test_plugin_file_operations.py +0 -0
- {spaceforge-0.0.5 → spaceforge-0.0.7}/spaceforge/test_plugin_hooks.py +0 -0
- {spaceforge-0.0.5 → spaceforge-0.0.7}/spaceforge/test_plugin_inheritance.py +0 -0
- {spaceforge-0.0.5 → spaceforge-0.0.7}/spaceforge/test_runner.py +0 -0
- {spaceforge-0.0.5 → spaceforge-0.0.7}/spaceforge/test_runner_cli.py +0 -0
- {spaceforge-0.0.5 → spaceforge-0.0.7}/spaceforge/test_runner_core.py +0 -0
- {spaceforge-0.0.5 → spaceforge-0.0.7}/spaceforge/test_runner_execution.py +0 -0
- {spaceforge-0.0.5 → spaceforge-0.0.7}/spaceforge.egg-info/dependency_links.txt +0 -0
- {spaceforge-0.0.5 → spaceforge-0.0.7}/spaceforge.egg-info/entry_points.txt +0 -0
- {spaceforge-0.0.5 → spaceforge-0.0.7}/spaceforge.egg-info/not-zip-safe +0 -0
- {spaceforge-0.0.5 → spaceforge-0.0.7}/spaceforge.egg-info/top_level.txt +0 -0
- {spaceforge-0.0.5 → spaceforge-0.0.7}/templates.go +0 -0
- {spaceforge-0.0.5 → spaceforge-0.0.7}/test.sh +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: spaceforge
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.7
|
|
4
4
|
Summary: A Python framework for building Spacelift plugins
|
|
5
5
|
Home-page: https://github.com/spacelift-io/plugins
|
|
6
6
|
Author: Spacelift
|
|
@@ -29,6 +29,7 @@ Requires-Dist: PyYAML>=6.0
|
|
|
29
29
|
Requires-Dist: click>=8.0.0
|
|
30
30
|
Requires-Dist: pydantic>=2.11.7
|
|
31
31
|
Requires-Dist: Jinja2>=3.1.0
|
|
32
|
+
Requires-Dist: mergedeep>=1.3.4
|
|
32
33
|
Provides-Extra: dev
|
|
33
34
|
Requires-Dist: pytest>=6.0; extra == "dev"
|
|
34
35
|
Requires-Dist: pytest-cov; extra == "dev"
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
from spaceforge import SpaceforgePlugin, MountedFile, Context
|
|
2
|
+
|
|
3
|
+
import yaml
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
class EnvironmentManagerPlugin(SpaceforgePlugin):
|
|
7
|
+
"""
|
|
8
|
+
# Spacelift Environment Variable Manager
|
|
9
|
+
This plugin allows you to manage Spacelift environment variables using a centralized YAML configuration file for multiple stacks.
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
- Centralized management of environment variables across multiple stacks.
|
|
13
|
+
- Supports sensitive variables.
|
|
14
|
+
- Preview of environment variable changes across stacks before applying changes here.
|
|
15
|
+
|
|
16
|
+
**Hint:** Use this in combination with the `sops` plugin to manage secrets in your environment variables.
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
Add this plugin to an **administrative** stack in your Spacelift account, the stack **must** have the `Spacelift` OpenTofu/Terraform provider configured.
|
|
20
|
+
example:
|
|
21
|
+
```hcl
|
|
22
|
+
terraform {
|
|
23
|
+
required_providers {
|
|
24
|
+
spacelift = {
|
|
25
|
+
source = "spacelift-io/spacelift"
|
|
26
|
+
version = "~> 1.0"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
provider "spacelift" {}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
1. **YAML Configuration**: Environment variables are defined in `vars.yaml` using the following structure:
|
|
35
|
+
```yaml
|
|
36
|
+
stack-id:
|
|
37
|
+
- name: VARIABLE_NAME
|
|
38
|
+
value: variable_value
|
|
39
|
+
sensitive: false
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
2. **Terraform Processing**: The main Terraform configuration:
|
|
43
|
+
- Reads and parses the YAML file using `yamldecode(file("vars.yaml"))`
|
|
44
|
+
- Flattens the structure into a list of variables with their associated stack IDs
|
|
45
|
+
- Creates `spacelift_environment_variable` resources for each variable
|
|
46
|
+
|
|
47
|
+
3. **Stack Association**: Variables are automatically associated with their respective stacks based on the stack ids defined in the YAML file.
|
|
48
|
+
|
|
49
|
+
**note:** when using this plugin, if you open a PR to your variables file, the changes of the child stacks will be previewed and linked.
|
|
50
|
+
|
|
51
|
+
## Example Configuration
|
|
52
|
+
|
|
53
|
+
### vars.yaml
|
|
54
|
+
```yaml
|
|
55
|
+
env-var-yaml-1:
|
|
56
|
+
- name: KUBECONFIG
|
|
57
|
+
value: /home/joey/.kube/config
|
|
58
|
+
sensitive: false
|
|
59
|
+
|
|
60
|
+
env-var-yaml-2:
|
|
61
|
+
- name: AWS_PROFILE
|
|
62
|
+
value: test
|
|
63
|
+
sensitive: false
|
|
64
|
+
- name: MY_AWESOME_SECRET
|
|
65
|
+
value: HelloWorld
|
|
66
|
+
sensitive: true
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The above configuration will create the following in a plan:
|
|
70
|
+
|
|
71
|
+
```ansi
|
|
72
|
+
# spacelift_environment_variable.this["env-var-yaml-1_KUBECONFIG"] will be created
|
|
73
|
+
+ resource "spacelift_environment_variable" "this" {
|
|
74
|
+
+ checksum = (known after apply)
|
|
75
|
+
+ id = (known after apply)
|
|
76
|
+
+ name = "KUBECONFIG"
|
|
77
|
+
+ stack_id = "env-var-yaml-1"
|
|
78
|
+
+ value = (sensitive value)
|
|
79
|
+
+ write_only = false
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
# spacelift_environment_variable.this["env-var-yaml-2_AWS_PROFILE"] will be created
|
|
83
|
+
+ resource "spacelift_environment_variable" "this" {
|
|
84
|
+
+ checksum = (known after apply)
|
|
85
|
+
+ id = (known after apply)
|
|
86
|
+
+ name = "AWS_PROFILE"
|
|
87
|
+
+ stack_id = "env-var-yaml-2"
|
|
88
|
+
+ value = (sensitive value)
|
|
89
|
+
+ write_only = false
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
# spacelift_environment_variable.this["env-var-yaml-2_MY_AWESOME_SECRET"] will be created
|
|
93
|
+
+ resource "spacelift_environment_variable" "this" {
|
|
94
|
+
+ checksum = (known after apply)
|
|
95
|
+
+ id = (known after apply)
|
|
96
|
+
+ name = "MY_AWESOME_SECRET"
|
|
97
|
+
+ stack_id = "env-var-yaml-2"
|
|
98
|
+
+ value = (sensitive value)
|
|
99
|
+
+ write_only = true
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
# Plugin metadata
|
|
105
|
+
__plugin_name__ = "Environment Manager"
|
|
106
|
+
__labels__ = ["management", "infrastructure"]
|
|
107
|
+
__version__ = "1.0.0"
|
|
108
|
+
__author__ = "Spacelift Team"
|
|
109
|
+
|
|
110
|
+
__contexts__ = [
|
|
111
|
+
Context(
|
|
112
|
+
name_prefix="Environment Manager",
|
|
113
|
+
description="Environment Manager Plugin",
|
|
114
|
+
hooks = {
|
|
115
|
+
"before_init": [
|
|
116
|
+
"mv /mnt/workspace/__environment_manager.tf /mnt/workspace/source/$TF_VAR_spacelift_project_root/__environment_manager.tf",
|
|
117
|
+
]
|
|
118
|
+
},
|
|
119
|
+
mounted_files=[
|
|
120
|
+
MountedFile(
|
|
121
|
+
path="__environment_manager.tf",
|
|
122
|
+
content="""
|
|
123
|
+
locals {
|
|
124
|
+
__env_vars = {
|
|
125
|
+
for obj in flatten([
|
|
126
|
+
for stack_id, values in yamldecode(file("${path.module}/vars.yaml")) : [
|
|
127
|
+
for v in values : {
|
|
128
|
+
stack_id = stack_id
|
|
129
|
+
name = v.name
|
|
130
|
+
value = v.value
|
|
131
|
+
write_only = v.write_only
|
|
132
|
+
}
|
|
133
|
+
]
|
|
134
|
+
]) : "${obj.stack_id}_${obj.name}" => obj
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
resource "spacelift_environment_variable" "__this" {
|
|
139
|
+
for_each = local.__env_vars
|
|
140
|
+
|
|
141
|
+
stack_id = each.value.stack_id
|
|
142
|
+
name = each.value.name
|
|
143
|
+
value = each.value.value
|
|
144
|
+
write_only = each.value.write_only
|
|
145
|
+
}
|
|
146
|
+
"""
|
|
147
|
+
)
|
|
148
|
+
]
|
|
149
|
+
)
|
|
150
|
+
]
|
|
151
|
+
|
|
152
|
+
def load_yaml_file(self, file_path):
|
|
153
|
+
"""Load YAML file and return parsed content"""
|
|
154
|
+
try:
|
|
155
|
+
with open(file_path, 'r') as file:
|
|
156
|
+
return yaml.safe_load(file)
|
|
157
|
+
except FileNotFoundError:
|
|
158
|
+
self.logger.error(f"Error: File {file_path} not found")
|
|
159
|
+
exit(1)
|
|
160
|
+
except yaml.YAMLError as e:
|
|
161
|
+
self.logger.error(f"Error parsing YAML: {e}")
|
|
162
|
+
exit(1)
|
|
163
|
+
|
|
164
|
+
def convert_to_runtime_config(self, yaml_data):
|
|
165
|
+
"""Convert YAML data to Spacelift runtime config format"""
|
|
166
|
+
runtime_config = {}
|
|
167
|
+
|
|
168
|
+
for stack_id, env_vars in yaml_data.items():
|
|
169
|
+
if not isinstance(env_vars, list):
|
|
170
|
+
self.logger.error(f"Error: Environment variables for stack '{stack_id}' must be a list")
|
|
171
|
+
exit(1)
|
|
172
|
+
|
|
173
|
+
runtime_config[stack_id] = {
|
|
174
|
+
"environment": {}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
for var in env_vars:
|
|
178
|
+
runtime_config[stack_id]["environment"][var["name"]] = var["value"]
|
|
179
|
+
|
|
180
|
+
return runtime_config
|
|
181
|
+
|
|
182
|
+
def trigger_stack_previews(self, runtime_config: dict):
|
|
183
|
+
"""Trigger stack previews using Spacelift API"""
|
|
184
|
+
|
|
185
|
+
markdown = []
|
|
186
|
+
|
|
187
|
+
for stack_id, env in runtime_config.items():
|
|
188
|
+
|
|
189
|
+
# Get the current tracked sha from the stack
|
|
190
|
+
query = "{ stack(id: \"" + stack_id + "\") { trackedCommit { hash } } }"
|
|
191
|
+
response = self.query_api(query)
|
|
192
|
+
if "errors" in response:
|
|
193
|
+
self.logger.error("Error fetching stack tracked commit:", response["errors"])
|
|
194
|
+
continue
|
|
195
|
+
|
|
196
|
+
# Ensure we have a tracked commit
|
|
197
|
+
try:
|
|
198
|
+
tracked_commit = response["data"]["stack"]["trackedCommit"]["hash"]
|
|
199
|
+
except TypeError:
|
|
200
|
+
tracked_commit = None
|
|
201
|
+
if tracked_commit is None:
|
|
202
|
+
self.logger.error(f"Stack {stack_id} has no tracked commit. Skipping.")
|
|
203
|
+
continue
|
|
204
|
+
|
|
205
|
+
# Trigger the stack preview with the current tracked commit SHA
|
|
206
|
+
query = """
|
|
207
|
+
mutation TriggerStackPreview($stack: ID!, $commitSHA: String!, $runtimeConfig: String!) {
|
|
208
|
+
runTrigger(stack: $stack, commitSha: $commitSHA, runType: PROPOSED, runtimeConfig: { yaml: $runtimeConfig }) {
|
|
209
|
+
id
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
"""
|
|
213
|
+
|
|
214
|
+
variables = {
|
|
215
|
+
"stack": stack_id,
|
|
216
|
+
"commitSHA": tracked_commit,
|
|
217
|
+
"runtimeConfig": yaml.dump(env)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
response = self.query_api(query, variables)
|
|
221
|
+
if "errors" in response:
|
|
222
|
+
self.logger.error(f"Error triggering stack preview for {stack_id}:", response["errors"])
|
|
223
|
+
else:
|
|
224
|
+
url = f"https://{self.spacelift_domain}/stack/{stack_id}/run/{response['data']['runTrigger']['id']}"
|
|
225
|
+
markdown.append(f"- Triggered [stack preview]({url}) for {stack_id} with commit {tracked_commit}.")
|
|
226
|
+
|
|
227
|
+
if len(markdown) > 0:
|
|
228
|
+
mdown = "# Stack Previews Triggered\n\n" + "\n".join(markdown)
|
|
229
|
+
success = self.send_markdown(mdown)
|
|
230
|
+
if not success:
|
|
231
|
+
self.logger.error("Failed to send markdown message with stack previews.")
|
|
232
|
+
self.logger.info(mdown)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def before_init(self):
|
|
236
|
+
# ensure we are in a proposed run
|
|
237
|
+
if os.getenv("TF_VAR_spacelift_run_type") != "PROPOSED":
|
|
238
|
+
# This script should only be run in a proposed run context.
|
|
239
|
+
return
|
|
240
|
+
|
|
241
|
+
# Load YAML data
|
|
242
|
+
yaml_data = self.load_yaml_file("vars.yaml")
|
|
243
|
+
|
|
244
|
+
# Convert to runtime config
|
|
245
|
+
runtime_config = self.convert_to_runtime_config(yaml_data)
|
|
246
|
+
|
|
247
|
+
# Trigger stack previews
|
|
248
|
+
self.trigger_stack_previews(runtime_config)
|
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
name: Environment Manager
|
|
2
|
+
version: 1.0.0
|
|
3
|
+
description: |-
|
|
4
|
+
# Spacelift Environment Variable Manager
|
|
5
|
+
This plugin allows you to manage Spacelift environment variables using a centralized YAML configuration file for multiple stacks.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
- Centralized management of environment variables across multiple stacks.
|
|
9
|
+
- Supports sensitive variables.
|
|
10
|
+
- Preview of environment variable changes across stacks before applying changes here.
|
|
11
|
+
|
|
12
|
+
**Hint:** Use this in combination with the `sops` plugin to manage secrets in your environment variables.
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
Add this plugin to an **administrative** stack in your Spacelift account, the stack **must** have the `Spacelift` OpenTofu/Terraform provider configured.
|
|
16
|
+
example:
|
|
17
|
+
```hcl
|
|
18
|
+
terraform {
|
|
19
|
+
required_providers {
|
|
20
|
+
spacelift = {
|
|
21
|
+
source = "spacelift-io/spacelift"
|
|
22
|
+
version = "~> 1.0"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
provider "spacelift" {}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
1. **YAML Configuration**: Environment variables are defined in `vars.yaml` using the following structure:
|
|
31
|
+
```yaml
|
|
32
|
+
stack-id:
|
|
33
|
+
- name: VARIABLE_NAME
|
|
34
|
+
value: variable_value
|
|
35
|
+
sensitive: false
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
2. **Terraform Processing**: The main Terraform configuration:
|
|
39
|
+
- Reads and parses the YAML file using `yamldecode(file("vars.yaml"))`
|
|
40
|
+
- Flattens the structure into a list of variables with their associated stack IDs
|
|
41
|
+
- Creates `spacelift_environment_variable` resources for each variable
|
|
42
|
+
|
|
43
|
+
3. **Stack Association**: Variables are automatically associated with their respective stacks based on the stack ids defined in the YAML file.
|
|
44
|
+
|
|
45
|
+
**note:** when using this plugin, if you open a PR to your variables file, the changes of the child stacks will be previewed and linked.
|
|
46
|
+
|
|
47
|
+
## Example Configuration
|
|
48
|
+
|
|
49
|
+
### vars.yaml
|
|
50
|
+
```yaml
|
|
51
|
+
env-var-yaml-1:
|
|
52
|
+
- name: KUBECONFIG
|
|
53
|
+
value: /home/joey/.kube/config
|
|
54
|
+
sensitive: false
|
|
55
|
+
|
|
56
|
+
env-var-yaml-2:
|
|
57
|
+
- name: AWS_PROFILE
|
|
58
|
+
value: test
|
|
59
|
+
sensitive: false
|
|
60
|
+
- name: MY_AWESOME_SECRET
|
|
61
|
+
value: HelloWorld
|
|
62
|
+
sensitive: true
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The above configuration will create the following in a plan:
|
|
66
|
+
|
|
67
|
+
```ansi
|
|
68
|
+
# spacelift_environment_variable.this["env-var-yaml-1_KUBECONFIG"] will be created
|
|
69
|
+
+ resource "spacelift_environment_variable" "this" {
|
|
70
|
+
+ checksum = (known after apply)
|
|
71
|
+
+ id = (known after apply)
|
|
72
|
+
+ name = "KUBECONFIG"
|
|
73
|
+
+ stack_id = "env-var-yaml-1"
|
|
74
|
+
+ value = (sensitive value)
|
|
75
|
+
+ write_only = false
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# spacelift_environment_variable.this["env-var-yaml-2_AWS_PROFILE"] will be created
|
|
79
|
+
+ resource "spacelift_environment_variable" "this" {
|
|
80
|
+
+ checksum = (known after apply)
|
|
81
|
+
+ id = (known after apply)
|
|
82
|
+
+ name = "AWS_PROFILE"
|
|
83
|
+
+ stack_id = "env-var-yaml-2"
|
|
84
|
+
+ value = (sensitive value)
|
|
85
|
+
+ write_only = false
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
# spacelift_environment_variable.this["env-var-yaml-2_MY_AWESOME_SECRET"] will be created
|
|
89
|
+
+ resource "spacelift_environment_variable" "this" {
|
|
90
|
+
+ checksum = (known after apply)
|
|
91
|
+
+ id = (known after apply)
|
|
92
|
+
+ name = "MY_AWESOME_SECRET"
|
|
93
|
+
+ stack_id = "env-var-yaml-2"
|
|
94
|
+
+ value = (sensitive value)
|
|
95
|
+
+ write_only = true
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
author: Spacelift Team
|
|
99
|
+
labels:
|
|
100
|
+
- management
|
|
101
|
+
- infrastructure
|
|
102
|
+
contexts:
|
|
103
|
+
- name_prefix: Environment Manager
|
|
104
|
+
description: Environment Manager Plugin
|
|
105
|
+
env: []
|
|
106
|
+
mounted_files:
|
|
107
|
+
- path: __environment_manager.tf
|
|
108
|
+
content: |-
|
|
109
|
+
locals {
|
|
110
|
+
__env_vars = {
|
|
111
|
+
for obj in flatten([
|
|
112
|
+
for stack_id, values in yamldecode(file("${path.module}/vars.yaml")) : [
|
|
113
|
+
for v in values : {
|
|
114
|
+
stack_id = stack_id
|
|
115
|
+
name = v.name
|
|
116
|
+
value = v.value
|
|
117
|
+
write_only = v.write_only
|
|
118
|
+
}
|
|
119
|
+
]
|
|
120
|
+
]) : "${obj.stack_id}_${obj.name}" => obj
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
resource "spacelift_environment_variable" "__this" {
|
|
125
|
+
for_each = local.__env_vars
|
|
126
|
+
|
|
127
|
+
stack_id = each.value.stack_id
|
|
128
|
+
name = each.value.name
|
|
129
|
+
value = each.value.value
|
|
130
|
+
write_only = each.value.write_only
|
|
131
|
+
}
|
|
132
|
+
sensitive: false
|
|
133
|
+
- path: /mnt/workspace/plugins/environment_manager/requirements.txt
|
|
134
|
+
content: pyyaml==6.0.2
|
|
135
|
+
sensitive: false
|
|
136
|
+
- path: /mnt/workspace/plugins/environment_manager/plugin.py
|
|
137
|
+
content: |-
|
|
138
|
+
from spaceforge import SpaceforgePlugin, MountedFile, Context
|
|
139
|
+
|
|
140
|
+
import yaml
|
|
141
|
+
import os
|
|
142
|
+
|
|
143
|
+
class EnvironmentManagerPlugin(SpaceforgePlugin):
|
|
144
|
+
"""
|
|
145
|
+
# Spacelift Environment Variable Manager
|
|
146
|
+
This plugin allows you to manage Spacelift environment variables using a centralized YAML configuration file for multiple stacks.
|
|
147
|
+
|
|
148
|
+
## Features
|
|
149
|
+
- Centralized management of environment variables across multiple stacks.
|
|
150
|
+
- Supports sensitive variables.
|
|
151
|
+
- Preview of environment variable changes across stacks before applying changes here.
|
|
152
|
+
|
|
153
|
+
**Hint:** Use this in combination with the `sops` plugin to manage secrets in your environment variables.
|
|
154
|
+
|
|
155
|
+
## Usage
|
|
156
|
+
Add this plugin to an **administrative** stack in your Spacelift account, the stack **must** have the `Spacelift` OpenTofu/Terraform provider configured.
|
|
157
|
+
example:
|
|
158
|
+
```hcl
|
|
159
|
+
terraform {
|
|
160
|
+
required_providers {
|
|
161
|
+
spacelift = {
|
|
162
|
+
source = "spacelift-io/spacelift"
|
|
163
|
+
version = "~> 1.0"
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
provider "spacelift" {}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
1. **YAML Configuration**: Environment variables are defined in `vars.yaml` using the following structure:
|
|
172
|
+
```yaml
|
|
173
|
+
stack-id:
|
|
174
|
+
- name: VARIABLE_NAME
|
|
175
|
+
value: variable_value
|
|
176
|
+
sensitive: false
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
2. **Terraform Processing**: The main Terraform configuration:
|
|
180
|
+
- Reads and parses the YAML file using `yamldecode(file("vars.yaml"))`
|
|
181
|
+
- Flattens the structure into a list of variables with their associated stack IDs
|
|
182
|
+
- Creates `spacelift_environment_variable` resources for each variable
|
|
183
|
+
|
|
184
|
+
3. **Stack Association**: Variables are automatically associated with their respective stacks based on the stack ids defined in the YAML file.
|
|
185
|
+
|
|
186
|
+
**note:** when using this plugin, if you open a PR to your variables file, the changes of the child stacks will be previewed and linked.
|
|
187
|
+
|
|
188
|
+
## Example Configuration
|
|
189
|
+
|
|
190
|
+
### vars.yaml
|
|
191
|
+
```yaml
|
|
192
|
+
env-var-yaml-1:
|
|
193
|
+
- name: KUBECONFIG
|
|
194
|
+
value: /home/joey/.kube/config
|
|
195
|
+
sensitive: false
|
|
196
|
+
|
|
197
|
+
env-var-yaml-2:
|
|
198
|
+
- name: AWS_PROFILE
|
|
199
|
+
value: test
|
|
200
|
+
sensitive: false
|
|
201
|
+
- name: MY_AWESOME_SECRET
|
|
202
|
+
value: HelloWorld
|
|
203
|
+
sensitive: true
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
The above configuration will create the following in a plan:
|
|
207
|
+
|
|
208
|
+
```ansi
|
|
209
|
+
# spacelift_environment_variable.this["env-var-yaml-1_KUBECONFIG"] will be created
|
|
210
|
+
+ resource "spacelift_environment_variable" "this" {
|
|
211
|
+
+ checksum = (known after apply)
|
|
212
|
+
+ id = (known after apply)
|
|
213
|
+
+ name = "KUBECONFIG"
|
|
214
|
+
+ stack_id = "env-var-yaml-1"
|
|
215
|
+
+ value = (sensitive value)
|
|
216
|
+
+ write_only = false
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
# spacelift_environment_variable.this["env-var-yaml-2_AWS_PROFILE"] will be created
|
|
220
|
+
+ resource "spacelift_environment_variable" "this" {
|
|
221
|
+
+ checksum = (known after apply)
|
|
222
|
+
+ id = (known after apply)
|
|
223
|
+
+ name = "AWS_PROFILE"
|
|
224
|
+
+ stack_id = "env-var-yaml-2"
|
|
225
|
+
+ value = (sensitive value)
|
|
226
|
+
+ write_only = false
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
# spacelift_environment_variable.this["env-var-yaml-2_MY_AWESOME_SECRET"] will be created
|
|
230
|
+
+ resource "spacelift_environment_variable" "this" {
|
|
231
|
+
+ checksum = (known after apply)
|
|
232
|
+
+ id = (known after apply)
|
|
233
|
+
+ name = "MY_AWESOME_SECRET"
|
|
234
|
+
+ stack_id = "env-var-yaml-2"
|
|
235
|
+
+ value = (sensitive value)
|
|
236
|
+
+ write_only = true
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
"""
|
|
240
|
+
|
|
241
|
+
# Plugin metadata
|
|
242
|
+
__plugin_name__ = "Environment Manager"
|
|
243
|
+
__labels__ = ["management", "infrastructure"]
|
|
244
|
+
__version__ = "1.0.0"
|
|
245
|
+
__author__ = "Spacelift Team"
|
|
246
|
+
|
|
247
|
+
__contexts__ = [
|
|
248
|
+
Context(
|
|
249
|
+
name_prefix="Environment Manager",
|
|
250
|
+
description="Environment Manager Plugin",
|
|
251
|
+
hooks = {
|
|
252
|
+
"before_init": [
|
|
253
|
+
"mv /mnt/workspace/__environment_manager.tf /mnt/workspace/source/$TF_VAR_spacelift_project_root/__environment_manager.tf",
|
|
254
|
+
]
|
|
255
|
+
},
|
|
256
|
+
mounted_files=[
|
|
257
|
+
MountedFile(
|
|
258
|
+
path="__environment_manager.tf",
|
|
259
|
+
content="""
|
|
260
|
+
locals {
|
|
261
|
+
__env_vars = {
|
|
262
|
+
for obj in flatten([
|
|
263
|
+
for stack_id, values in yamldecode(file("${path.module}/vars.yaml")) : [
|
|
264
|
+
for v in values : {
|
|
265
|
+
stack_id = stack_id
|
|
266
|
+
name = v.name
|
|
267
|
+
value = v.value
|
|
268
|
+
write_only = v.write_only
|
|
269
|
+
}
|
|
270
|
+
]
|
|
271
|
+
]) : "${obj.stack_id}_${obj.name}" => obj
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
resource "spacelift_environment_variable" "__this" {
|
|
276
|
+
for_each = local.__env_vars
|
|
277
|
+
|
|
278
|
+
stack_id = each.value.stack_id
|
|
279
|
+
name = each.value.name
|
|
280
|
+
value = each.value.value
|
|
281
|
+
write_only = each.value.write_only
|
|
282
|
+
}
|
|
283
|
+
"""
|
|
284
|
+
)
|
|
285
|
+
]
|
|
286
|
+
)
|
|
287
|
+
]
|
|
288
|
+
|
|
289
|
+
def load_yaml_file(self, file_path):
|
|
290
|
+
"""Load YAML file and return parsed content"""
|
|
291
|
+
try:
|
|
292
|
+
with open(file_path, 'r') as file:
|
|
293
|
+
return yaml.safe_load(file)
|
|
294
|
+
except FileNotFoundError:
|
|
295
|
+
self.logger.error(f"Error: File {file_path} not found")
|
|
296
|
+
exit(1)
|
|
297
|
+
except yaml.YAMLError as e:
|
|
298
|
+
self.logger.error(f"Error parsing YAML: {e}")
|
|
299
|
+
exit(1)
|
|
300
|
+
|
|
301
|
+
def convert_to_runtime_config(self, yaml_data):
|
|
302
|
+
"""Convert YAML data to Spacelift runtime config format"""
|
|
303
|
+
runtime_config = {}
|
|
304
|
+
|
|
305
|
+
for stack_id, env_vars in yaml_data.items():
|
|
306
|
+
if not isinstance(env_vars, list):
|
|
307
|
+
self.logger.error(f"Error: Environment variables for stack '{stack_id}' must be a list")
|
|
308
|
+
exit(1)
|
|
309
|
+
|
|
310
|
+
runtime_config[stack_id] = {
|
|
311
|
+
"environment": {}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
for var in env_vars:
|
|
315
|
+
runtime_config[stack_id]["environment"][var["name"]] = var["value"]
|
|
316
|
+
|
|
317
|
+
return runtime_config
|
|
318
|
+
|
|
319
|
+
def trigger_stack_previews(self, runtime_config: dict):
|
|
320
|
+
"""Trigger stack previews using Spacelift API"""
|
|
321
|
+
|
|
322
|
+
markdown = []
|
|
323
|
+
|
|
324
|
+
for stack_id, env in runtime_config.items():
|
|
325
|
+
|
|
326
|
+
# Get the current tracked sha from the stack
|
|
327
|
+
query = "{ stack(id: \"" + stack_id + "\") { trackedCommit { hash } } }"
|
|
328
|
+
response = self.query_api(query)
|
|
329
|
+
if "errors" in response:
|
|
330
|
+
self.logger.error("Error fetching stack tracked commit:", response["errors"])
|
|
331
|
+
continue
|
|
332
|
+
|
|
333
|
+
# Ensure we have a tracked commit
|
|
334
|
+
try:
|
|
335
|
+
tracked_commit = response["data"]["stack"]["trackedCommit"]["hash"]
|
|
336
|
+
except TypeError:
|
|
337
|
+
tracked_commit = None
|
|
338
|
+
if tracked_commit is None:
|
|
339
|
+
self.logger.error(f"Stack {stack_id} has no tracked commit. Skipping.")
|
|
340
|
+
continue
|
|
341
|
+
|
|
342
|
+
# Trigger the stack preview with the current tracked commit SHA
|
|
343
|
+
query = """
|
|
344
|
+
mutation TriggerStackPreview($stack: ID!, $commitSHA: String!, $runtimeConfig: String!) {
|
|
345
|
+
runTrigger(stack: $stack, commitSha: $commitSHA, runType: PROPOSED, runtimeConfig: { yaml: $runtimeConfig }) {
|
|
346
|
+
id
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
"""
|
|
350
|
+
|
|
351
|
+
variables = {
|
|
352
|
+
"stack": stack_id,
|
|
353
|
+
"commitSHA": tracked_commit,
|
|
354
|
+
"runtimeConfig": yaml.dump(env)
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
response = self.query_api(query, variables)
|
|
358
|
+
if "errors" in response:
|
|
359
|
+
self.logger.error(f"Error triggering stack preview for {stack_id}:", response["errors"])
|
|
360
|
+
else:
|
|
361
|
+
url = f"https://{self.spacelift_domain}/stack/{stack_id}/run/{response['data']['runTrigger']['id']}"
|
|
362
|
+
markdown.append(f"- Triggered [stack preview]({url}) for {stack_id} with commit {tracked_commit}.")
|
|
363
|
+
|
|
364
|
+
if len(markdown) > 0:
|
|
365
|
+
mdown = "# Stack Previews Triggered\n\n" + "\n".join(markdown)
|
|
366
|
+
success = self.send_markdown(mdown)
|
|
367
|
+
if not success:
|
|
368
|
+
self.logger.error("Failed to send markdown message with stack previews.")
|
|
369
|
+
self.logger.info(mdown)
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def before_init(self):
|
|
373
|
+
# ensure we are in a proposed run
|
|
374
|
+
if os.getenv("TF_VAR_spacelift_run_type") != "PROPOSED":
|
|
375
|
+
# This script should only be run in a proposed run context.
|
|
376
|
+
return
|
|
377
|
+
|
|
378
|
+
# Load YAML data
|
|
379
|
+
yaml_data = self.load_yaml_file("vars.yaml")
|
|
380
|
+
|
|
381
|
+
# Convert to runtime config
|
|
382
|
+
runtime_config = self.convert_to_runtime_config(yaml_data)
|
|
383
|
+
|
|
384
|
+
# Trigger stack previews
|
|
385
|
+
self.trigger_stack_previews(runtime_config)
|
|
386
|
+
sensitive: false
|
|
387
|
+
- path: /mnt/workspace/plugins/environment_manager/before_init.sh
|
|
388
|
+
content: |-
|
|
389
|
+
#!/bin/sh
|
|
390
|
+
|
|
391
|
+
set -e
|
|
392
|
+
|
|
393
|
+
cd /mnt/workspace/plugins/environment_manager
|
|
394
|
+
|
|
395
|
+
if [ ! -d "./venv" ]; then
|
|
396
|
+
python -m venv ./venv
|
|
397
|
+
fi
|
|
398
|
+
. venv/bin/activate
|
|
399
|
+
|
|
400
|
+
if ! command -v spaceforge; then
|
|
401
|
+
pip install spaceforge
|
|
402
|
+
fi
|
|
403
|
+
|
|
404
|
+
if [ -f requirements.txt ] && [ ! -f .spaceforge_installed_requirements ]; then
|
|
405
|
+
pip install -r requirements.txt
|
|
406
|
+
touch .spaceforge_installed_requirements
|
|
407
|
+
fi
|
|
408
|
+
|
|
409
|
+
cd /mnt/workspace/source/$TF_VAR_spacelift_project_root
|
|
410
|
+
spaceforge runner --plugin-file /mnt/workspace/plugins/environment_manager/plugin.py before_init
|
|
411
|
+
sensitive: false
|
|
412
|
+
hooks:
|
|
413
|
+
before_init:
|
|
414
|
+
- mv /mnt/workspace/__environment_manager.tf /mnt/workspace/source/$TF_VAR_spacelift_project_root/__environment_manager.tf
|
|
415
|
+
- mkdir -p /mnt/workspace/plugins/environment_manager
|
|
416
|
+
- chmod +x /mnt/workspace/plugins/environment_manager/before_init.sh && /mnt/workspace/plugins/environment_manager/before_init.sh
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pyyaml==6.0.2
|