kube_cluster 0.3.7 → 0.3.9
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/Gemfile.lock +13 -9
- data/README.md +133 -10
- data/Rakefile +7 -0
- data/bin/test +2 -13
- data/kube_cluster.gemspec +4 -3
- data/lib/kube/cluster/manifest.rb +205 -0
- data/lib/kube/cluster/middleware/annotations.rb +61 -0
- data/lib/kube/cluster/middleware/hpa_for_deployment.rb +160 -0
- data/lib/kube/cluster/middleware/ingress_for_service.rb +101 -0
- data/lib/kube/cluster/middleware/labels.rb +83 -0
- data/lib/kube/cluster/middleware/namespace.rb +75 -0
- data/lib/kube/cluster/middleware/pod_anti_affinity.rb +126 -0
- data/lib/kube/cluster/middleware/resource_preset.rb +164 -0
- data/lib/kube/cluster/middleware/security_context.rb +143 -0
- data/lib/kube/cluster/middleware/service_for_deployment.rb +144 -0
- data/lib/kube/cluster/resource/dirty_tracking.rb +535 -0
- data/lib/kube/cluster/version.rb +1 -1
- data/lib/kube/cluster.rb +7 -1
- data/lib/kube/helm/chart.rb +412 -1
- data/lib/kube/helm/endpoint.rb +80 -0
- data/lib/kube/helm/repo.rb +196 -0
- metadata +10 -9
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require "kube/cluster"
|
|
5
|
+
|
|
3
6
|
module Kube
|
|
4
7
|
module Cluster
|
|
5
8
|
class Resource < Kube::Schema::Resource
|
|
@@ -111,3 +114,535 @@ module Kube
|
|
|
111
114
|
end
|
|
112
115
|
end
|
|
113
116
|
end
|
|
117
|
+
|
|
118
|
+
test do
|
|
119
|
+
require "json"
|
|
120
|
+
|
|
121
|
+
# ---------------------------------------------------------------------------
|
|
122
|
+
# Fake ctl that records every command and returns canned responses.
|
|
123
|
+
# The test wires this into the cluster → connection → ctl chain so that
|
|
124
|
+
# Persistence#kubectl goes through it without touching a real cluster.
|
|
125
|
+
# ---------------------------------------------------------------------------
|
|
126
|
+
class FakeCtl
|
|
127
|
+
attr_reader :commands
|
|
128
|
+
|
|
129
|
+
def initialize
|
|
130
|
+
@commands = []
|
|
131
|
+
@responses = {}
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Queue a response for the next command that includes +substring+.
|
|
135
|
+
def stub_response(substring, response)
|
|
136
|
+
@responses[substring] = response
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def run(string)
|
|
140
|
+
@commands << string
|
|
141
|
+
|
|
142
|
+
@responses.each do |substring, response|
|
|
143
|
+
if string.include?(substring)
|
|
144
|
+
return response
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
"" # default: empty response
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# ---------------------------------------------------------------------------
|
|
153
|
+
# Minimal cluster double that provides .connection.ctl
|
|
154
|
+
# ---------------------------------------------------------------------------
|
|
155
|
+
class FakeConnection
|
|
156
|
+
attr_reader :ctl
|
|
157
|
+
|
|
158
|
+
def initialize(ctl)
|
|
159
|
+
@ctl = ctl
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
class FakeCluster
|
|
164
|
+
attr_reader :connection
|
|
165
|
+
|
|
166
|
+
def initialize(ctl)
|
|
167
|
+
@connection = FakeConnection.new(ctl)
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# ---------------------------------------------------------------------------
|
|
172
|
+
# Helper to build a resource wired to a fake cluster.
|
|
173
|
+
# ---------------------------------------------------------------------------
|
|
174
|
+
module ResourceHelper
|
|
175
|
+
def build_resource(hash = {})
|
|
176
|
+
ctl = FakeCtl.new
|
|
177
|
+
cluster = FakeCluster.new(ctl)
|
|
178
|
+
resource = Kube::Cluster["ConfigMap"].new(hash.merge(kind: "ConfigMap", cluster: cluster))
|
|
179
|
+
[resource, ctl]
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Simulate what kubectl returns: the server adds extra fields.
|
|
183
|
+
def server_state(resource_hash, extra = {})
|
|
184
|
+
merged = resource_hash.merge(extra)
|
|
185
|
+
JSON.generate(stringify_keys(merged))
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
private
|
|
189
|
+
|
|
190
|
+
def stringify_keys(obj)
|
|
191
|
+
case obj
|
|
192
|
+
when Hash then obj.each_with_object({}) { |(k, v), h| h[k.to_s] = stringify_keys(v) }
|
|
193
|
+
when Array then obj.map { |v| stringify_keys(v) }
|
|
194
|
+
else obj
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
include ResourceHelper
|
|
200
|
+
|
|
201
|
+
# -------------------------------------------------------------------------
|
|
202
|
+
# Full lifecycle: apply → mutate → detect changes → patch → clean
|
|
203
|
+
# -------------------------------------------------------------------------
|
|
204
|
+
|
|
205
|
+
it "full_apply_mutate_patch_lifecycle" do
|
|
206
|
+
resource, ctl = build_resource(metadata: { name: "app-config", namespace: "production" }, spec: { key: "original" })
|
|
207
|
+
|
|
208
|
+
# Stub the reload after apply — server echoes back what we sent
|
|
209
|
+
ctl.stub_response("get", server_state(
|
|
210
|
+
metadata: { name: "app-config", namespace: "production", resourceVersion: "100" },
|
|
211
|
+
spec: { key: "original" }
|
|
212
|
+
))
|
|
213
|
+
|
|
214
|
+
resource.apply
|
|
215
|
+
|
|
216
|
+
# Mutate
|
|
217
|
+
resource.instance_variable_get(:@data).spec.key = "updated"
|
|
218
|
+
|
|
219
|
+
# Stub reload after patch
|
|
220
|
+
ctl.stub_response("get", server_state(
|
|
221
|
+
metadata: { name: "app-config", namespace: "production", resourceVersion: "101" },
|
|
222
|
+
spec: { key: "updated" }
|
|
223
|
+
))
|
|
224
|
+
|
|
225
|
+
result = resource.patch
|
|
226
|
+
result.should == true
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# -------------------------------------------------------------------------
|
|
230
|
+
# Patch returns false when nothing changed
|
|
231
|
+
# -------------------------------------------------------------------------
|
|
232
|
+
|
|
233
|
+
it "patch_returns_false_when_clean" do
|
|
234
|
+
resource, ctl = build_resource(metadata: { name: "app-config", namespace: "default" }, spec: { key: "value" })
|
|
235
|
+
|
|
236
|
+
ctl.stub_response("get", server_state(
|
|
237
|
+
metadata: { name: "app-config", namespace: "default" }, spec: { key: "value" }
|
|
238
|
+
))
|
|
239
|
+
|
|
240
|
+
result = resource.patch
|
|
241
|
+
result.should == false
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# -------------------------------------------------------------------------
|
|
245
|
+
# Patch sends only the diff, not the full resource
|
|
246
|
+
# -------------------------------------------------------------------------
|
|
247
|
+
|
|
248
|
+
it "patch_sends_only_changed_fields" do
|
|
249
|
+
resource, ctl = build_resource(
|
|
250
|
+
metadata: { name: "my-config", namespace: "staging" },
|
|
251
|
+
spec: { db_host: "old-db.internal", db_port: "5432", cache_ttl: "300" }
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
# Mutate one field
|
|
255
|
+
resource.instance_variable_get(:@data).spec.db_host = "new-db.internal"
|
|
256
|
+
|
|
257
|
+
ctl.stub_response("get", server_state(
|
|
258
|
+
metadata: { name: "my-config", namespace: "staging" },
|
|
259
|
+
spec: { db_host: "new-db.internal", db_port: "5432", cache_ttl: "300" }
|
|
260
|
+
))
|
|
261
|
+
|
|
262
|
+
resource.patch
|
|
263
|
+
|
|
264
|
+
# Find the patch command
|
|
265
|
+
patch_cmd = ctl.commands.find { |c| c.include?("patch") }
|
|
266
|
+
|
|
267
|
+
# Extract the JSON payload from the command (last arg after -p)
|
|
268
|
+
json_start = patch_cmd.index("-p ") + 3
|
|
269
|
+
payload = JSON.parse(patch_cmd[json_start..])
|
|
270
|
+
|
|
271
|
+
# The payload should contain the spec subtree but NOT metadata
|
|
272
|
+
payload.key?("spec").should.be.true
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# -------------------------------------------------------------------------
|
|
276
|
+
# Reload resets dirty state from server response
|
|
277
|
+
# -------------------------------------------------------------------------
|
|
278
|
+
|
|
279
|
+
it "reload_resets_dirty_state" do
|
|
280
|
+
resource, ctl = build_resource(metadata: { name: "my-config", namespace: "default" }, spec: { key: "v1" })
|
|
281
|
+
|
|
282
|
+
# Local mutation
|
|
283
|
+
resource.instance_variable_get(:@data).spec.key = "local-change"
|
|
284
|
+
|
|
285
|
+
# Server still has original
|
|
286
|
+
ctl.stub_response("get", server_state(
|
|
287
|
+
metadata: { name: "my-config", namespace: "default" }, spec: { key: "v1" }
|
|
288
|
+
))
|
|
289
|
+
|
|
290
|
+
resource.reload
|
|
291
|
+
|
|
292
|
+
# After reload, local changes are gone and resource is clean
|
|
293
|
+
resource.to_h[:spec][:key].should == "v1"
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
it "reload_picks_up_server_side_changes" do
|
|
297
|
+
resource, ctl = build_resource(metadata: { name: "my-config", namespace: "default" }, spec: { key: "v1" })
|
|
298
|
+
|
|
299
|
+
# Server has been mutated externally
|
|
300
|
+
ctl.stub_response("get", server_state(
|
|
301
|
+
metadata: { name: "my-config", namespace: "default", resourceVersion: "200" },
|
|
302
|
+
spec: { key: "server-updated" }
|
|
303
|
+
))
|
|
304
|
+
|
|
305
|
+
resource.reload
|
|
306
|
+
|
|
307
|
+
# Resource reflects server state and is clean
|
|
308
|
+
resource.to_h[:spec][:key].should == "server-updated"
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
# -------------------------------------------------------------------------
|
|
312
|
+
# Apply snapshots after the server round-trip
|
|
313
|
+
# -------------------------------------------------------------------------
|
|
314
|
+
|
|
315
|
+
it "apply_snapshots_server_response" do
|
|
316
|
+
resource, ctl = build_resource(metadata: { name: "my-config" }, spec: { key: "v1" })
|
|
317
|
+
|
|
318
|
+
# Server adds metadata on apply
|
|
319
|
+
ctl.stub_response("get", server_state(
|
|
320
|
+
metadata: { name: "my-config", resourceVersion: "1", uid: "abc-123" },
|
|
321
|
+
spec: { key: "v1" }
|
|
322
|
+
))
|
|
323
|
+
|
|
324
|
+
resource.apply
|
|
325
|
+
|
|
326
|
+
# The snapshot should include server-added fields, so mutating
|
|
327
|
+
# the original field shows the correct old value
|
|
328
|
+
resource.instance_variable_get(:@data).spec.key = "v2"
|
|
329
|
+
changes = resource.changes
|
|
330
|
+
|
|
331
|
+
# changes[:spec] is [old_hash, new_hash]
|
|
332
|
+
old_spec, new_spec = changes[:spec]
|
|
333
|
+
old_spec[:key].should == "v1"
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
# -------------------------------------------------------------------------
|
|
337
|
+
# Error cases: unpersisted resources
|
|
338
|
+
# -------------------------------------------------------------------------
|
|
339
|
+
|
|
340
|
+
it "patch_raises_on_unpersisted_resource" do
|
|
341
|
+
resource, _ctl = build_resource(spec: { key: "value" })
|
|
342
|
+
|
|
343
|
+
lambda { resource.patch }.should.raise Kube::CommandError
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
it "delete_raises_on_unpersisted_resource" do
|
|
347
|
+
resource, _ctl = build_resource(spec: { key: "value" })
|
|
348
|
+
|
|
349
|
+
lambda { resource.delete }.should.raise Kube::CommandError
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
it "reload_raises_on_unpersisted_resource" do
|
|
353
|
+
resource, _ctl = build_resource(spec: { key: "value" })
|
|
354
|
+
|
|
355
|
+
lambda { resource.reload }.should.raise Kube::CommandError
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
# -------------------------------------------------------------------------
|
|
359
|
+
# Nested mutation flows through patch_data correctly
|
|
360
|
+
# -------------------------------------------------------------------------
|
|
361
|
+
|
|
362
|
+
it "nested_mutation_produces_nested_patch" do
|
|
363
|
+
resource, ctl = build_resource(
|
|
364
|
+
metadata: { name: "my-config", namespace: "default", labels: { app: "web", tier: "frontend" } }
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
# Mutate only a nested field
|
|
368
|
+
resource.instance_variable_get(:@data).metadata.labels.tier = "backend"
|
|
369
|
+
|
|
370
|
+
patch = resource.patch_data
|
|
371
|
+
patch[:metadata][:labels][:tier].should == ["frontend", "backend"]
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
it "deeply_nested_no_change_produces_empty_patch" do
|
|
375
|
+
resource, _ctl = build_resource(
|
|
376
|
+
metadata: { name: "my-config", labels: { app: "web" } }
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
resource.patch_data.should == {}
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
# -------------------------------------------------------------------------
|
|
383
|
+
# Multiple mutations before patch coalesce into a single diff
|
|
384
|
+
# -------------------------------------------------------------------------
|
|
385
|
+
|
|
386
|
+
it "multiple_mutations_coalesce_in_single_patch" do
|
|
387
|
+
resource, ctl = build_resource(
|
|
388
|
+
metadata: { name: "my-config", namespace: "default" },
|
|
389
|
+
data: { host: "db-1", port: "5432", pool: "5" }
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
d = resource.instance_variable_get(:@data).data
|
|
393
|
+
d.host = "db-2"
|
|
394
|
+
d.port = "5433"
|
|
395
|
+
d.pool = "10"
|
|
396
|
+
|
|
397
|
+
ctl.stub_response("get", server_state(
|
|
398
|
+
metadata: { name: "my-config", namespace: "default" },
|
|
399
|
+
data: { host: "db-2", port: "5433", pool: "10" }
|
|
400
|
+
))
|
|
401
|
+
|
|
402
|
+
resource.patch
|
|
403
|
+
|
|
404
|
+
# Exactly one patch command
|
|
405
|
+
patch_commands = ctl.commands.select { |c| c.include?("patch") }
|
|
406
|
+
patch_commands.size.should == 1
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
# -------------------------------------------------------------------------
|
|
410
|
+
# changes_applied mid-workflow resets the baseline
|
|
411
|
+
# -------------------------------------------------------------------------
|
|
412
|
+
|
|
413
|
+
it "changes_applied_resets_baseline_without_server_roundtrip" do
|
|
414
|
+
resource, _ctl = build_resource(metadata: { name: "my-config" }, spec: { key: "v1" })
|
|
415
|
+
|
|
416
|
+
resource.instance_variable_get(:@data).spec.key = "v2"
|
|
417
|
+
|
|
418
|
+
# Accept changes locally (no kubectl call)
|
|
419
|
+
resource.changes_applied
|
|
420
|
+
|
|
421
|
+
resource.changes.should == {}
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
it "changes_applied_then_patch_sends_only_subsequent_changes" do
|
|
425
|
+
resource, ctl = build_resource(
|
|
426
|
+
metadata: { name: "my-config", namespace: "default" },
|
|
427
|
+
data: { a: "1", b: "2", c: "3" }
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
# First wave of changes
|
|
431
|
+
resource.instance_variable_get(:@data).data.a = "changed-a"
|
|
432
|
+
resource.changes_applied
|
|
433
|
+
|
|
434
|
+
# Second wave — only b changes from the new baseline
|
|
435
|
+
resource.instance_variable_get(:@data).data.b = "changed-b"
|
|
436
|
+
|
|
437
|
+
ctl.stub_response("get", server_state(
|
|
438
|
+
metadata: { name: "my-config", namespace: "default" },
|
|
439
|
+
data: { a: "changed-a", b: "changed-b", c: "3" }
|
|
440
|
+
))
|
|
441
|
+
|
|
442
|
+
resource.patch
|
|
443
|
+
|
|
444
|
+
patch_cmd = ctl.commands.find { |c| c.include?("patch") }
|
|
445
|
+
payload = JSON.parse(patch_cmd.split("-p ").last)
|
|
446
|
+
|
|
447
|
+
# Only b should be in the patch, not a (already accepted via changes_applied)
|
|
448
|
+
payload["data"]["b"].should == ["2", "changed-b"]
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
# -------------------------------------------------------------------------
|
|
452
|
+
# Dynamic attr_changed? tracks through full lifecycle
|
|
453
|
+
# -------------------------------------------------------------------------
|
|
454
|
+
|
|
455
|
+
it "attr_changed_through_apply_mutate_patch_cycle" do
|
|
456
|
+
resource, ctl = build_resource(metadata: { name: "my-config", namespace: "default" }, spec: { key: "v1" })
|
|
457
|
+
|
|
458
|
+
ctl.stub_response("get", server_state(
|
|
459
|
+
metadata: { name: "my-config", namespace: "default" },
|
|
460
|
+
spec: { key: "v1" }
|
|
461
|
+
))
|
|
462
|
+
|
|
463
|
+
resource.apply
|
|
464
|
+
|
|
465
|
+
resource.instance_variable_get(:@data).spec.key = "v2"
|
|
466
|
+
|
|
467
|
+
ctl.stub_response("get", server_state(
|
|
468
|
+
metadata: { name: "my-config", namespace: "default" },
|
|
469
|
+
spec: { key: "v2" }
|
|
470
|
+
))
|
|
471
|
+
|
|
472
|
+
resource.patch
|
|
473
|
+
|
|
474
|
+
resource.spec_changed?.should.be.false
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
it "respond_to_for_dynamic_changed_predicates" do
|
|
478
|
+
resource, _ctl = build_resource(metadata: { name: "test" })
|
|
479
|
+
|
|
480
|
+
resource.should.respond_to :metadata_changed?
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
# -------------------------------------------------------------------------
|
|
484
|
+
# Snapshot isolation: reload doesn't leak into captured references
|
|
485
|
+
# -------------------------------------------------------------------------
|
|
486
|
+
|
|
487
|
+
it "reload_does_not_corrupt_previously_captured_changes" do
|
|
488
|
+
resource, ctl = build_resource(metadata: { name: "my-config", namespace: "default" }, spec: { key: "v1" })
|
|
489
|
+
|
|
490
|
+
resource.instance_variable_get(:@data).spec.key = "v2"
|
|
491
|
+
|
|
492
|
+
# Capture changes before reload
|
|
493
|
+
changes_before = resource.changes
|
|
494
|
+
patch_before = resource.patch_data
|
|
495
|
+
|
|
496
|
+
# Reload with different server state
|
|
497
|
+
ctl.stub_response("get", server_state(
|
|
498
|
+
metadata: { name: "my-config", namespace: "default" },
|
|
499
|
+
spec: { key: "v3-from-server" }
|
|
500
|
+
))
|
|
501
|
+
|
|
502
|
+
resource.reload
|
|
503
|
+
|
|
504
|
+
# Previously captured hashes should be unaffected
|
|
505
|
+
extract_nested_value(changes_before, :spec, :key, 1).should == "v2"
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
it "snapshot_isolation_across_multiple_changes_applied" do
|
|
509
|
+
resource, _ctl = build_resource(metadata: { name: "test" }, data: { counter: "1" })
|
|
510
|
+
|
|
511
|
+
resource.instance_variable_get(:@data).data.counter = "2"
|
|
512
|
+
snapshot_1_changes = resource.changes
|
|
513
|
+
|
|
514
|
+
resource.changes_applied
|
|
515
|
+
|
|
516
|
+
resource.instance_variable_get(:@data).data.counter = "3"
|
|
517
|
+
snapshot_2_changes = resource.changes
|
|
518
|
+
|
|
519
|
+
# Each snapshot's changes should be independent
|
|
520
|
+
extract_nested_value(snapshot_1_changes, :data, :counter, 0).should == "1"
|
|
521
|
+
end
|
|
522
|
+
|
|
523
|
+
# -------------------------------------------------------------------------
|
|
524
|
+
# Edge case: resource with no initial spec data
|
|
525
|
+
# -------------------------------------------------------------------------
|
|
526
|
+
|
|
527
|
+
it "empty_resource_tracks_all_additions" do
|
|
528
|
+
resource, _ctl = build_resource(metadata: { name: "empty-config" })
|
|
529
|
+
|
|
530
|
+
resource.instance_variable_get(:@data).spec.key = "added"
|
|
531
|
+
|
|
532
|
+
resource.changed.should.include :spec
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
# -------------------------------------------------------------------------
|
|
536
|
+
# Edge case: patch type parameter is forwarded
|
|
537
|
+
# -------------------------------------------------------------------------
|
|
538
|
+
|
|
539
|
+
it "patch_forwards_type_parameter" do
|
|
540
|
+
resource, ctl = build_resource(metadata: { name: "my-config", namespace: "default" }, spec: { key: "v1" })
|
|
541
|
+
|
|
542
|
+
resource.instance_variable_get(:@data).spec.key = "v2"
|
|
543
|
+
|
|
544
|
+
ctl.stub_response("get", server_state(
|
|
545
|
+
metadata: { name: "my-config", namespace: "default" },
|
|
546
|
+
spec: { key: "v2" }
|
|
547
|
+
))
|
|
548
|
+
|
|
549
|
+
resource.patch(type: "merge")
|
|
550
|
+
|
|
551
|
+
patch_cmd = ctl.commands.find { |c| c.include?("patch") }
|
|
552
|
+
patch_cmd.should.include "--type merge"
|
|
553
|
+
end
|
|
554
|
+
|
|
555
|
+
it "patch_defaults_to_strategic_type" do
|
|
556
|
+
resource, ctl = build_resource(metadata: { name: "my-config", namespace: "default" }, spec: { key: "v1" })
|
|
557
|
+
|
|
558
|
+
resource.instance_variable_get(:@data).spec.key = "v2"
|
|
559
|
+
|
|
560
|
+
ctl.stub_response("get", server_state(
|
|
561
|
+
metadata: { name: "my-config", namespace: "default" },
|
|
562
|
+
spec: { key: "v2" }
|
|
563
|
+
))
|
|
564
|
+
|
|
565
|
+
resource.patch
|
|
566
|
+
|
|
567
|
+
patch_cmd = ctl.commands.find { |c| c.include?("patch") }
|
|
568
|
+
patch_cmd.should.include "--type strategic"
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
# -------------------------------------------------------------------------
|
|
572
|
+
# Edge case: namespace flags are included correctly
|
|
573
|
+
# -------------------------------------------------------------------------
|
|
574
|
+
|
|
575
|
+
it "patch_includes_namespace_flags" do
|
|
576
|
+
resource, ctl = build_resource(metadata: { name: "my-config", namespace: "kube-system" }, spec: { key: "v1" })
|
|
577
|
+
|
|
578
|
+
resource.instance_variable_get(:@data).spec.key = "v2"
|
|
579
|
+
|
|
580
|
+
ctl.stub_response("get", server_state(
|
|
581
|
+
metadata: { name: "my-config", namespace: "kube-system" },
|
|
582
|
+
spec: { key: "v2" }
|
|
583
|
+
))
|
|
584
|
+
|
|
585
|
+
resource.patch
|
|
586
|
+
|
|
587
|
+
patch_cmd = ctl.commands.find { |c| c.include?("patch") }
|
|
588
|
+
patch_cmd.should.include "--namespace kube-system"
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
it "reload_includes_namespace_flags" do
|
|
592
|
+
resource, ctl = build_resource(metadata: { name: "my-config", namespace: "monitoring" }, spec: { key: "v1" })
|
|
593
|
+
|
|
594
|
+
ctl.stub_response("get", server_state(
|
|
595
|
+
metadata: { name: "my-config", namespace: "monitoring" },
|
|
596
|
+
spec: { key: "v1" }
|
|
597
|
+
))
|
|
598
|
+
|
|
599
|
+
resource.reload
|
|
600
|
+
|
|
601
|
+
get_cmd = ctl.commands.find { |c| c.include?("get") }
|
|
602
|
+
get_cmd.should.include "--namespace monitoring"
|
|
603
|
+
end
|
|
604
|
+
|
|
605
|
+
# -------------------------------------------------------------------------
|
|
606
|
+
# Edge case: delete on persisted resource issues command
|
|
607
|
+
# -------------------------------------------------------------------------
|
|
608
|
+
|
|
609
|
+
it "delete_issues_kubectl_delete" do
|
|
610
|
+
resource, ctl = build_resource(metadata: { name: "my-config", namespace: "default" })
|
|
611
|
+
|
|
612
|
+
result = resource.delete
|
|
613
|
+
|
|
614
|
+
delete_cmd = ctl.commands.find { |c| c.include?("delete") }
|
|
615
|
+
delete_cmd.should.include "my-config"
|
|
616
|
+
end
|
|
617
|
+
|
|
618
|
+
# -------------------------------------------------------------------------
|
|
619
|
+
# Regression: the original bug — build_changes used `result` instead of `hash`
|
|
620
|
+
# -------------------------------------------------------------------------
|
|
621
|
+
|
|
622
|
+
it "changes_does_not_raise_name_error" do
|
|
623
|
+
resource, _ctl = build_resource(metadata: { name: "my-config" }, spec: { key: "v1" })
|
|
624
|
+
|
|
625
|
+
resource.instance_variable_get(:@data).spec.key = "v2"
|
|
626
|
+
|
|
627
|
+
# This would raise NameError with the original bug
|
|
628
|
+
changes = resource.changes
|
|
629
|
+
|
|
630
|
+
changes.should.be.kind_of Hash
|
|
631
|
+
end
|
|
632
|
+
|
|
633
|
+
private
|
|
634
|
+
|
|
635
|
+
# Navigate into nested change structures.
|
|
636
|
+
# changes[:spec] could be [old_hash, new_hash] or a nested diff hash.
|
|
637
|
+
def extract_nested_value(hash, top_key, nested_key, index)
|
|
638
|
+
val = hash[top_key]
|
|
639
|
+
case val
|
|
640
|
+
when Array
|
|
641
|
+
# [old_hash, new_hash]
|
|
642
|
+
val[index].is_a?(Hash) ? val[index][nested_key] : val[index]
|
|
643
|
+
when Hash
|
|
644
|
+
# nested diff: { key: [old, new] }
|
|
645
|
+
val[nested_key].is_a?(Array) ? val[nested_key][index] : val[nested_key]
|
|
646
|
+
end
|
|
647
|
+
end
|
|
648
|
+
end
|
data/lib/kube/cluster/version.rb
CHANGED
data/lib/kube/cluster.rb
CHANGED
|
@@ -6,8 +6,8 @@ require_relative "cluster/version"
|
|
|
6
6
|
require_relative "cluster/connection"
|
|
7
7
|
require_relative "cluster/instance"
|
|
8
8
|
require_relative "cluster/resource"
|
|
9
|
-
require_relative "cluster/manifest"
|
|
10
9
|
require_relative "cluster/middleware"
|
|
10
|
+
require_relative "cluster/manifest"
|
|
11
11
|
require 'kube/ctl'
|
|
12
12
|
require_relative 'helm/repo'
|
|
13
13
|
|
|
@@ -44,3 +44,9 @@ module Kube
|
|
|
44
44
|
end
|
|
45
45
|
end
|
|
46
46
|
end
|
|
47
|
+
|
|
48
|
+
test do
|
|
49
|
+
it "version" do
|
|
50
|
+
Kube::Cluster::VERSION.should.not.be.nil
|
|
51
|
+
end
|
|
52
|
+
end
|