walheim 0.1.2 → 0.2.1
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.
- checksums.yaml +4 -4
- data/bin/whctl +8 -695
- data/lib/walheim/cli/base_command.rb +171 -0
- data/lib/walheim/cli/helpers.rb +113 -0
- data/lib/walheim/cli/legacy_context.rb +206 -0
- data/lib/walheim/cli/resource_command.rb +53 -0
- data/lib/walheim/cli.rb +63 -0
- data/lib/walheim/cluster_resource.rb +5 -5
- data/lib/walheim/config.rb +36 -34
- data/lib/walheim/handler_registry.rb +26 -0
- data/lib/walheim/namespaced_resource.rb +49 -13
- data/lib/walheim/resource.rb +16 -11
- data/lib/walheim/resources/apps.rb +132 -113
- data/lib/walheim/resources/configmaps.rb +7 -7
- data/lib/walheim/resources/namespaces.rb +67 -10
- data/lib/walheim/resources/secrets.rb +9 -9
- data/lib/walheim/sync.rb +5 -5
- data/lib/walheim/version.rb +1 -1
- data/lib/walheim.rb +14 -11
- metadata +21 -2
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative
|
|
4
|
-
require_relative
|
|
5
|
-
require_relative
|
|
3
|
+
require_relative "../namespaced_resource"
|
|
4
|
+
require_relative "../sync"
|
|
5
|
+
require_relative "../handler_registry"
|
|
6
6
|
|
|
7
7
|
module Resources
|
|
8
8
|
class Apps < Walheim::NamespacedResource
|
|
9
|
-
def initialize(
|
|
10
|
-
super
|
|
11
|
-
|
|
9
|
+
def initialize(data_dir: Dir.pwd)
|
|
10
|
+
super(data_dir: data_dir)
|
|
11
|
+
# Sync needs the full path to namespaces directory
|
|
12
|
+
@namespaces_dir = File.join(data_dir, "namespaces")
|
|
13
|
+
@syncer = Walheim::Sync.new(namespaces_dir: @namespaces_dir)
|
|
12
14
|
end
|
|
13
15
|
|
|
14
16
|
def self.kind_info
|
|
15
17
|
{
|
|
16
|
-
plural:
|
|
17
|
-
singular:
|
|
18
|
+
plural: "apps",
|
|
19
|
+
singular: "app",
|
|
18
20
|
aliases: %w[application applications]
|
|
19
21
|
}
|
|
20
22
|
end
|
|
@@ -31,11 +33,11 @@ module Resources
|
|
|
31
33
|
{
|
|
32
34
|
image: lambda { |manifest|
|
|
33
35
|
# Extract first service's image from compose spec
|
|
34
|
-
manifest.dig(
|
|
36
|
+
manifest.dig("spec", "compose", "services")&.values&.first&.dig("image") || "N/A"
|
|
35
37
|
},
|
|
36
38
|
status: lambda { |_manifest|
|
|
37
39
|
# Could check if app is running, for now just show 'Configured'
|
|
38
|
-
|
|
40
|
+
"Configured"
|
|
39
41
|
}
|
|
40
42
|
}
|
|
41
43
|
end
|
|
@@ -44,32 +46,47 @@ module Resources
|
|
|
44
46
|
# Start with base operations
|
|
45
47
|
ops = super
|
|
46
48
|
|
|
49
|
+
namespace_opt = { type: :string, aliases: [ :n ], desc: "Target namespace", required: true }
|
|
50
|
+
|
|
47
51
|
# Add apps-specific operations
|
|
48
52
|
ops.merge({
|
|
49
53
|
import: {
|
|
50
|
-
description:
|
|
51
|
-
usage: [
|
|
54
|
+
description: "Import docker-compose as Walheim App",
|
|
55
|
+
usage: [ "import app {name} -n {namespace} -f {docker-compose.yml}" ],
|
|
56
|
+
options: {
|
|
57
|
+
namespace: namespace_opt,
|
|
58
|
+
file: { type: :string, aliases: [ :f ], desc: "docker-compose.yml path", required: true }
|
|
59
|
+
}
|
|
52
60
|
},
|
|
53
61
|
start: {
|
|
54
|
-
description:
|
|
55
|
-
usage: [
|
|
62
|
+
description: "Compile, sync, and start app on host",
|
|
63
|
+
usage: [ "start app {name} -n {namespace}" ],
|
|
64
|
+
options: { namespace: namespace_opt }
|
|
56
65
|
},
|
|
57
66
|
pause: {
|
|
58
|
-
description:
|
|
59
|
-
usage: [
|
|
67
|
+
description: "Stop app containers (keep files)",
|
|
68
|
+
usage: [ "pause app {name} -n {namespace}" ],
|
|
69
|
+
options: { namespace: namespace_opt }
|
|
60
70
|
},
|
|
61
71
|
stop: {
|
|
62
|
-
description:
|
|
63
|
-
usage: [
|
|
72
|
+
description: "Stop app and remove files from host",
|
|
73
|
+
usage: [ "stop app {name} -n {namespace}" ],
|
|
74
|
+
options: { namespace: namespace_opt }
|
|
64
75
|
},
|
|
65
76
|
logs: {
|
|
66
|
-
description:
|
|
77
|
+
description: "View logs from remote containers",
|
|
67
78
|
usage: [
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
]
|
|
79
|
+
"logs app {name} -n {namespace}",
|
|
80
|
+
"logs app {name} -n {namespace} --follow",
|
|
81
|
+
"logs app {name} -n {namespace} --tail 100",
|
|
82
|
+
"logs app {name} -n {namespace} --timestamps"
|
|
83
|
+
],
|
|
84
|
+
options: {
|
|
85
|
+
namespace: namespace_opt,
|
|
86
|
+
follow: { type: :boolean, desc: "Follow log output" },
|
|
87
|
+
tail: { type: :numeric, desc: "Number of lines from end" },
|
|
88
|
+
timestamps: { type: :boolean, desc: "Show timestamps" }
|
|
89
|
+
}
|
|
73
90
|
}
|
|
74
91
|
})
|
|
75
92
|
end
|
|
@@ -86,14 +103,14 @@ module Resources
|
|
|
86
103
|
|
|
87
104
|
# Convert docker-compose to Walheim App manifest
|
|
88
105
|
app_manifest = {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
106
|
+
"apiVersion" => "walheim/v1alpha1",
|
|
107
|
+
"kind" => "App",
|
|
108
|
+
"metadata" => {
|
|
109
|
+
"name" => name,
|
|
110
|
+
"namespace" => namespace
|
|
94
111
|
},
|
|
95
|
-
|
|
96
|
-
|
|
112
|
+
"spec" => {
|
|
113
|
+
"compose" => compose_manifest
|
|
97
114
|
}
|
|
98
115
|
}
|
|
99
116
|
|
|
@@ -114,7 +131,7 @@ module Resources
|
|
|
114
131
|
generate_compose_file(namespace, name, app_manifest)
|
|
115
132
|
|
|
116
133
|
# Sync files to remote
|
|
117
|
-
result = @syncer.sync(namespace: namespace, kind:
|
|
134
|
+
result = @syncer.sync(namespace: namespace, kind: "apps", name: name)
|
|
118
135
|
|
|
119
136
|
# Run docker compose up
|
|
120
137
|
remote_host = result[:username] ? "#{result[:username]}@#{result[:hostname]}" : result[:hostname]
|
|
@@ -123,7 +140,7 @@ module Resources
|
|
|
123
140
|
compose_result = system(ssh_command)
|
|
124
141
|
|
|
125
142
|
unless compose_result
|
|
126
|
-
warn
|
|
143
|
+
warn "Error: docker compose up failed"
|
|
127
144
|
exit 1
|
|
128
145
|
end
|
|
129
146
|
|
|
@@ -133,8 +150,8 @@ module Resources
|
|
|
133
150
|
def pause(namespace:, name:)
|
|
134
151
|
# Get namespace config
|
|
135
152
|
namespace_config = load_namespace_config(namespace)
|
|
136
|
-
username = namespace_config[
|
|
137
|
-
hostname = namespace_config[
|
|
153
|
+
username = namespace_config["username"]
|
|
154
|
+
hostname = namespace_config["hostname"]
|
|
138
155
|
|
|
139
156
|
remote_host = username ? "#{username}@#{hostname}" : hostname
|
|
140
157
|
remote_dir = "/data/walheim/apps/#{name}"
|
|
@@ -153,7 +170,7 @@ module Resources
|
|
|
153
170
|
result = system(ssh_command)
|
|
154
171
|
|
|
155
172
|
unless result
|
|
156
|
-
warn
|
|
173
|
+
warn "Error: docker compose down failed"
|
|
157
174
|
exit 1
|
|
158
175
|
end
|
|
159
176
|
|
|
@@ -166,8 +183,8 @@ module Resources
|
|
|
166
183
|
|
|
167
184
|
# Then remove files from remote
|
|
168
185
|
namespace_config = load_namespace_config(namespace)
|
|
169
|
-
username = namespace_config[
|
|
170
|
-
hostname = namespace_config[
|
|
186
|
+
username = namespace_config["username"]
|
|
187
|
+
hostname = namespace_config["hostname"]
|
|
171
188
|
|
|
172
189
|
remote_host = username ? "#{username}@#{hostname}" : hostname
|
|
173
190
|
remote_dir = "/data/walheim/apps/#{name}"
|
|
@@ -177,7 +194,7 @@ module Resources
|
|
|
177
194
|
result = system(ssh_command)
|
|
178
195
|
|
|
179
196
|
unless result
|
|
180
|
-
warn
|
|
197
|
+
warn "Error: failed to remove remote files"
|
|
181
198
|
exit 1
|
|
182
199
|
end
|
|
183
200
|
|
|
@@ -187,8 +204,8 @@ module Resources
|
|
|
187
204
|
def logs(namespace:, name:, follow: false, tail: nil, timestamps: false)
|
|
188
205
|
# Get namespace config
|
|
189
206
|
namespace_config = load_namespace_config(namespace)
|
|
190
|
-
username = namespace_config[
|
|
191
|
-
hostname = namespace_config[
|
|
207
|
+
username = namespace_config["username"]
|
|
208
|
+
hostname = namespace_config["hostname"]
|
|
192
209
|
|
|
193
210
|
remote_host = username ? "#{username}@#{hostname}" : hostname
|
|
194
211
|
remote_dir = "/data/walheim/apps/#{name}"
|
|
@@ -203,10 +220,10 @@ module Resources
|
|
|
203
220
|
end
|
|
204
221
|
|
|
205
222
|
# Build docker compose logs command with options
|
|
206
|
-
logs_cmd =
|
|
207
|
-
logs_cmd +=
|
|
223
|
+
logs_cmd = "docker compose logs"
|
|
224
|
+
logs_cmd += " --follow" if follow
|
|
208
225
|
logs_cmd += " --tail #{tail}" if tail
|
|
209
|
-
logs_cmd +=
|
|
226
|
+
logs_cmd += " --timestamps" if timestamps
|
|
210
227
|
|
|
211
228
|
# Execute logs command (this will stream output to terminal)
|
|
212
229
|
ssh_command = "ssh #{remote_host} 'cd #{remote_dir} && #{logs_cmd}'"
|
|
@@ -219,12 +236,12 @@ module Resources
|
|
|
219
236
|
private
|
|
220
237
|
|
|
221
238
|
def load_app_manifest(namespace, name)
|
|
222
|
-
app_dir = File.join(@namespaces_dir, namespace,
|
|
223
|
-
app_yaml_path = File.join(app_dir,
|
|
239
|
+
app_dir = File.join(@namespaces_dir, namespace, "apps", name)
|
|
240
|
+
app_yaml_path = File.join(app_dir, ".app.yaml")
|
|
224
241
|
|
|
225
242
|
unless File.exist?(app_yaml_path)
|
|
226
243
|
warn "Error: No app manifest found at #{app_yaml_path}"
|
|
227
|
-
warn
|
|
244
|
+
warn "Use 'whctl import' to convert docker-compose.yml to Walheim App format"
|
|
228
245
|
exit 1
|
|
229
246
|
end
|
|
230
247
|
|
|
@@ -234,58 +251,58 @@ module Resources
|
|
|
234
251
|
validate_k8s_manifest(manifest, namespace, name)
|
|
235
252
|
|
|
236
253
|
{
|
|
237
|
-
metadata: manifest[
|
|
238
|
-
env_from: manifest[
|
|
239
|
-
env: manifest[
|
|
240
|
-
compose: manifest[
|
|
254
|
+
metadata: manifest["metadata"],
|
|
255
|
+
env_from: manifest["spec"]["envFrom"] || [],
|
|
256
|
+
env: manifest["spec"]["env"] || [],
|
|
257
|
+
compose: manifest["spec"]["compose"]
|
|
241
258
|
}
|
|
242
259
|
end
|
|
243
260
|
|
|
244
261
|
def validate_k8s_manifest(manifest, namespace, name)
|
|
245
262
|
# Check required top-level fields
|
|
246
|
-
unless manifest[
|
|
263
|
+
unless manifest["apiVersion"] == "walheim/v1alpha1"
|
|
247
264
|
warn "Error: apiVersion must be 'walheim/v1alpha1', got '#{manifest['apiVersion']}'"
|
|
248
265
|
exit 1
|
|
249
266
|
end
|
|
250
267
|
|
|
251
|
-
unless manifest[
|
|
268
|
+
unless manifest["kind"] == "App"
|
|
252
269
|
warn "Error: kind must be 'App', got '#{manifest['kind']}'"
|
|
253
270
|
exit 1
|
|
254
271
|
end
|
|
255
272
|
|
|
256
|
-
unless manifest[
|
|
257
|
-
warn
|
|
273
|
+
unless manifest["metadata"]
|
|
274
|
+
warn "Error: metadata is required"
|
|
258
275
|
exit 1
|
|
259
276
|
end
|
|
260
277
|
|
|
261
|
-
unless manifest[
|
|
262
|
-
warn
|
|
278
|
+
unless manifest["spec"]
|
|
279
|
+
warn "Error: spec is required"
|
|
263
280
|
exit 1
|
|
264
281
|
end
|
|
265
282
|
|
|
266
283
|
# Check metadata fields
|
|
267
|
-
metadata_name = manifest[
|
|
284
|
+
metadata_name = manifest["metadata"]["name"]
|
|
268
285
|
unless metadata_name == name
|
|
269
286
|
warn "Error: metadata.name '#{metadata_name}' must match directory name '#{name}'"
|
|
270
287
|
exit 1
|
|
271
288
|
end
|
|
272
289
|
|
|
273
|
-
metadata_namespace = manifest[
|
|
290
|
+
metadata_namespace = manifest["metadata"]["namespace"]
|
|
274
291
|
unless metadata_namespace == namespace
|
|
275
292
|
warn "Error: metadata.namespace '#{metadata_namespace}' must match parent namespace '#{namespace}'"
|
|
276
293
|
exit 1
|
|
277
294
|
end
|
|
278
295
|
|
|
279
296
|
# Check spec.compose exists
|
|
280
|
-
return if manifest[
|
|
297
|
+
return if manifest["spec"]["compose"]
|
|
281
298
|
|
|
282
|
-
warn
|
|
299
|
+
warn "Error: spec.compose is required"
|
|
283
300
|
exit 1
|
|
284
301
|
end
|
|
285
302
|
|
|
286
303
|
def generate_compose_file(namespace, name, app_manifest)
|
|
287
|
-
app_dir = File.join(@namespaces_dir, namespace,
|
|
288
|
-
compose_path = File.join(app_dir,
|
|
304
|
+
app_dir = File.join(@namespaces_dir, namespace, "apps", name)
|
|
305
|
+
compose_path = File.join(app_dir, "docker-compose.yml")
|
|
289
306
|
|
|
290
307
|
# Start with base compose content
|
|
291
308
|
compose_content = deep_copy(app_manifest[:compose])
|
|
@@ -295,13 +312,13 @@ module Resources
|
|
|
295
312
|
|
|
296
313
|
# Process envFrom and inject into compose (lower precedence)
|
|
297
314
|
unless app_manifest[:env_from].empty?
|
|
298
|
-
puts
|
|
315
|
+
puts "Processing envFrom..."
|
|
299
316
|
compose_content = inject_env_from(compose_content, app_manifest[:env_from], namespace)
|
|
300
317
|
end
|
|
301
318
|
|
|
302
319
|
# Process env and inject into compose (higher precedence)
|
|
303
320
|
unless app_manifest[:env].empty?
|
|
304
|
-
puts
|
|
321
|
+
puts "Processing env..."
|
|
305
322
|
compose_content = inject_env(compose_content, app_manifest[:env])
|
|
306
323
|
end
|
|
307
324
|
|
|
@@ -312,27 +329,29 @@ module Resources
|
|
|
312
329
|
|
|
313
330
|
def inject_walheim_labels(compose, namespace, name)
|
|
314
331
|
# Process each service and inject Walheim metadata labels
|
|
315
|
-
compose[
|
|
316
|
-
service_config[
|
|
332
|
+
compose["services"]&.each_value do |service_config|
|
|
333
|
+
service_config["labels"] ||= []
|
|
317
334
|
|
|
318
335
|
# Define Walheim metadata labels (stable labels only to avoid unnecessary restarts)
|
|
319
336
|
walheim_labels = [
|
|
320
|
-
|
|
337
|
+
"walheim.managed=true",
|
|
321
338
|
"walheim.namespace=#{namespace}",
|
|
322
339
|
"walheim.app=#{name}"
|
|
323
340
|
]
|
|
324
341
|
|
|
325
342
|
# Inject labels (handle both array and hash formats)
|
|
326
|
-
if service_config[
|
|
343
|
+
if service_config["labels"].is_a?(Array)
|
|
327
344
|
# Remove any existing walheim.* labels first to avoid duplicates
|
|
328
|
-
service_config[
|
|
345
|
+
service_config["labels"].reject! do |label|
|
|
346
|
+
label.to_s.start_with?("walheim.managed=", "walheim.namespace=", "walheim.app=")
|
|
347
|
+
end
|
|
329
348
|
# Add new labels
|
|
330
|
-
service_config[
|
|
349
|
+
service_config["labels"].concat(walheim_labels)
|
|
331
350
|
else
|
|
332
351
|
# Hash format
|
|
333
|
-
service_config[
|
|
334
|
-
service_config[
|
|
335
|
-
service_config[
|
|
352
|
+
service_config["labels"]["walheim.managed"] = "true"
|
|
353
|
+
service_config["labels"]["walheim.namespace"] = namespace
|
|
354
|
+
service_config["labels"]["walheim.app"] = name
|
|
336
355
|
end
|
|
337
356
|
end
|
|
338
357
|
|
|
@@ -343,13 +362,13 @@ module Resources
|
|
|
343
362
|
return compose if env_from_list.empty?
|
|
344
363
|
|
|
345
364
|
# Process each service
|
|
346
|
-
compose[
|
|
347
|
-
service_config[
|
|
348
|
-
service_config[
|
|
365
|
+
compose["services"]&.each do |service_name, service_config|
|
|
366
|
+
service_config["environment"] ||= {}
|
|
367
|
+
service_config["labels"] ||= []
|
|
349
368
|
|
|
350
369
|
# Convert array-style to hash if needed
|
|
351
|
-
if service_config[
|
|
352
|
-
service_config[
|
|
370
|
+
if service_config["environment"].is_a?(Array)
|
|
371
|
+
service_config["environment"] = array_env_to_hash(service_config["environment"])
|
|
353
372
|
end
|
|
354
373
|
|
|
355
374
|
# Track total injected for this service
|
|
@@ -358,27 +377,27 @@ module Resources
|
|
|
358
377
|
# Inject from each envFrom source
|
|
359
378
|
env_from_list.each do |source|
|
|
360
379
|
# Check if this service should receive injections from this source
|
|
361
|
-
service_names = source[
|
|
380
|
+
service_names = source["serviceNames"]
|
|
362
381
|
next if service_names && !service_names.empty? && !service_names.include?(service_name)
|
|
363
382
|
|
|
364
|
-
if source[
|
|
365
|
-
secret_name = source[
|
|
366
|
-
injected_keys = inject_from_secret(service_config[
|
|
383
|
+
if source["secretRef"]
|
|
384
|
+
secret_name = source["secretRef"]["name"]
|
|
385
|
+
injected_keys = inject_from_secret(service_config["environment"], secret_name, namespace)
|
|
367
386
|
|
|
368
387
|
# Add tracking label if any keys were injected
|
|
369
388
|
if injected_keys.any?
|
|
370
|
-
add_tracking_label(service_config[
|
|
389
|
+
add_tracking_label(service_config["labels"], "walheim.injected-env.secret.#{secret_name}", injected_keys)
|
|
371
390
|
puts " #{service_name}: Injected #{injected_keys.size} variable(s) from secret #{secret_name}: #{injected_keys.join(', ')}"
|
|
372
391
|
end
|
|
373
392
|
|
|
374
393
|
total_injected += injected_keys.size
|
|
375
|
-
elsif source[
|
|
376
|
-
configmap_name = source[
|
|
377
|
-
injected_keys = inject_from_configmap(service_config[
|
|
394
|
+
elsif source["configMapRef"]
|
|
395
|
+
configmap_name = source["configMapRef"]["name"]
|
|
396
|
+
injected_keys = inject_from_configmap(service_config["environment"], configmap_name, namespace)
|
|
378
397
|
|
|
379
398
|
# Add tracking label if any keys were injected
|
|
380
399
|
if injected_keys.any?
|
|
381
|
-
add_tracking_label(service_config[
|
|
400
|
+
add_tracking_label(service_config["labels"], "walheim.injected-env.configmap.#{configmap_name}",
|
|
382
401
|
injected_keys)
|
|
383
402
|
puts " #{service_name}: Injected #{injected_keys.size} variable(s) from configmap #{configmap_name}: #{injected_keys.join(', ')}"
|
|
384
403
|
end
|
|
@@ -394,8 +413,8 @@ module Resources
|
|
|
394
413
|
def array_env_to_hash(env_array)
|
|
395
414
|
env_hash = {}
|
|
396
415
|
env_array.each do |env_line|
|
|
397
|
-
if env_line.include?(
|
|
398
|
-
key, value = env_line.split(
|
|
416
|
+
if env_line.include?("=")
|
|
417
|
+
key, value = env_line.split("=", 2)
|
|
399
418
|
env_hash[key] = value
|
|
400
419
|
elsif env_line.is_a?(Hash)
|
|
401
420
|
env_hash.merge!(env_line)
|
|
@@ -435,7 +454,7 @@ module Resources
|
|
|
435
454
|
end
|
|
436
455
|
|
|
437
456
|
def add_tracking_label(labels, label_key, injected_keys)
|
|
438
|
-
label_value = injected_keys.join(
|
|
457
|
+
label_value = injected_keys.join(",")
|
|
439
458
|
|
|
440
459
|
# Add label (handle both array and hash formats)
|
|
441
460
|
if labels.is_a?(Array)
|
|
@@ -449,13 +468,13 @@ module Resources
|
|
|
449
468
|
return compose if env_list.empty?
|
|
450
469
|
|
|
451
470
|
# Process each service
|
|
452
|
-
compose[
|
|
453
|
-
service_config[
|
|
454
|
-
service_config[
|
|
471
|
+
compose["services"]&.each do |service_name, service_config|
|
|
472
|
+
service_config["environment"] ||= {}
|
|
473
|
+
service_config["labels"] ||= []
|
|
455
474
|
|
|
456
475
|
# Convert array-style to hash if needed
|
|
457
|
-
if service_config[
|
|
458
|
-
service_config[
|
|
476
|
+
if service_config["environment"].is_a?(Array)
|
|
477
|
+
service_config["environment"] = array_env_to_hash(service_config["environment"])
|
|
459
478
|
end
|
|
460
479
|
|
|
461
480
|
# Track which keys were set by spec.env
|
|
@@ -464,23 +483,23 @@ module Resources
|
|
|
464
483
|
# Process each env entry
|
|
465
484
|
env_list.each do |env_entry|
|
|
466
485
|
# Check if this service should receive this env var
|
|
467
|
-
service_names = env_entry[
|
|
486
|
+
service_names = env_entry["serviceNames"]
|
|
468
487
|
next if service_names && !service_names.empty? && !service_names.include?(service_name)
|
|
469
488
|
|
|
470
|
-
var_name = env_entry[
|
|
471
|
-
var_value = env_entry[
|
|
489
|
+
var_name = env_entry["name"]
|
|
490
|
+
var_value = env_entry["value"]
|
|
472
491
|
|
|
473
492
|
# Perform variable substitution using current environment
|
|
474
|
-
substituted_value = substitute_variables(var_value, service_config[
|
|
493
|
+
substituted_value = substitute_variables(var_value, service_config["environment"])
|
|
475
494
|
|
|
476
495
|
# Always overwrite (highest precedence)
|
|
477
|
-
service_config[
|
|
496
|
+
service_config["environment"][var_name] = substituted_value
|
|
478
497
|
injected_keys << var_name
|
|
479
498
|
end
|
|
480
499
|
|
|
481
500
|
# Add tracking label if any keys were set
|
|
482
501
|
if injected_keys.any?
|
|
483
|
-
add_tracking_label(service_config[
|
|
502
|
+
add_tracking_label(service_config["labels"], "walheim.injected-env.override", injected_keys)
|
|
484
503
|
puts " #{service_name}: Set #{injected_keys.size} variable(s) from spec.env: #{injected_keys.join(', ')}"
|
|
485
504
|
end
|
|
486
505
|
end
|
|
@@ -498,9 +517,9 @@ module Resources
|
|
|
498
517
|
end
|
|
499
518
|
|
|
500
519
|
def load_configmap_data(namespace, configmap_name)
|
|
501
|
-
require
|
|
520
|
+
require "base64"
|
|
502
521
|
|
|
503
|
-
configmap_path = File.join(@namespaces_dir, namespace,
|
|
522
|
+
configmap_path = File.join(@namespaces_dir, namespace, "configmaps", configmap_name, "configmap.yaml")
|
|
504
523
|
|
|
505
524
|
unless File.exist?(configmap_path)
|
|
506
525
|
warn "Error: configmap '#{configmap_name}' not found at #{configmap_path}"
|
|
@@ -510,7 +529,7 @@ module Resources
|
|
|
510
529
|
configmap = YAML.load_file(configmap_path)
|
|
511
530
|
|
|
512
531
|
# Extract data (plaintext only for configmaps)
|
|
513
|
-
configmap[
|
|
532
|
+
configmap["data"] || {}
|
|
514
533
|
end
|
|
515
534
|
|
|
516
535
|
def deep_copy(obj)
|
|
@@ -518,18 +537,18 @@ module Resources
|
|
|
518
537
|
end
|
|
519
538
|
|
|
520
539
|
def manifest_filename
|
|
521
|
-
|
|
540
|
+
".app.yaml"
|
|
522
541
|
end
|
|
523
542
|
|
|
524
543
|
def validate_manifest(manifest, namespace, name)
|
|
525
|
-
manifest_hash = YAML.
|
|
526
|
-
validate_k8s_manifest(manifest_hash, namespace, name) if manifest_hash[
|
|
544
|
+
manifest_hash = YAML.safe_load(manifest)
|
|
545
|
+
validate_k8s_manifest(manifest_hash, namespace, name) if manifest_hash["kind"] == "App"
|
|
527
546
|
end
|
|
528
547
|
|
|
529
548
|
def load_secret_data(namespace, secret_name)
|
|
530
|
-
require
|
|
549
|
+
require "base64"
|
|
531
550
|
|
|
532
|
-
secret_path = File.join(@namespaces_dir, namespace,
|
|
551
|
+
secret_path = File.join(@namespaces_dir, namespace, "secrets", secret_name, "secret.yaml")
|
|
533
552
|
|
|
534
553
|
unless File.exist?(secret_path)
|
|
535
554
|
warn "Error: secret '#{secret_name}' not found at #{secret_path}"
|
|
@@ -539,8 +558,8 @@ module Resources
|
|
|
539
558
|
secret = YAML.load_file(secret_path)
|
|
540
559
|
|
|
541
560
|
# Extract data (base64-encoded) and stringData (plaintext)
|
|
542
|
-
data = secret[
|
|
543
|
-
string_data = secret[
|
|
561
|
+
data = secret["data"] || {}
|
|
562
|
+
string_data = secret["stringData"] || {}
|
|
544
563
|
|
|
545
564
|
# Decode base64 data
|
|
546
565
|
decoded_data = {}
|
|
@@ -553,7 +572,7 @@ module Resources
|
|
|
553
572
|
end
|
|
554
573
|
|
|
555
574
|
def load_namespace_config(namespace)
|
|
556
|
-
config_path = File.join(@namespaces_dir, namespace,
|
|
575
|
+
config_path = File.join(@namespaces_dir, namespace, ".namespace.yaml")
|
|
557
576
|
|
|
558
577
|
unless File.exist?(config_path)
|
|
559
578
|
warn "Error: namespace config '#{config_path}' not found"
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative
|
|
4
|
-
require_relative
|
|
3
|
+
require_relative "../namespaced_resource"
|
|
4
|
+
require_relative "../handler_registry"
|
|
5
5
|
|
|
6
6
|
module Resources
|
|
7
7
|
class ConfigMaps < Walheim::NamespacedResource
|
|
8
8
|
def self.kind_info
|
|
9
9
|
{
|
|
10
|
-
plural:
|
|
11
|
-
singular:
|
|
12
|
-
aliases: [
|
|
10
|
+
plural: "configmaps",
|
|
11
|
+
singular: "configmap",
|
|
12
|
+
aliases: [ "cm" ] # Abbreviation
|
|
13
13
|
}
|
|
14
14
|
end
|
|
15
15
|
|
|
@@ -24,7 +24,7 @@ module Resources
|
|
|
24
24
|
def self.summary_fields
|
|
25
25
|
{
|
|
26
26
|
keys: lambda { |manifest|
|
|
27
|
-
(manifest[
|
|
27
|
+
(manifest["data"] || {}).keys.join(", ")
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
end
|
|
@@ -32,7 +32,7 @@ module Resources
|
|
|
32
32
|
private
|
|
33
33
|
|
|
34
34
|
def manifest_filename
|
|
35
|
-
|
|
35
|
+
"configmap.yaml"
|
|
36
36
|
end
|
|
37
37
|
end
|
|
38
38
|
end
|