kdep 0.4.6 → 0.4.8
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/lib/kdep/commands/build.rb +5 -0
- data/lib/kdep/commands/bump.rb +28 -5
- data/lib/kdep/commands/check.rb +14 -0
- data/lib/kdep/commands/push.rb +5 -0
- data/lib/kdep/commands/restart.rb +40 -12
- data/lib/kdep/defaults.rb +3 -0
- data/lib/kdep/doctor/check_yaml_dup_keys.rb +27 -0
- data/lib/kdep/doctor.rb +2 -0
- data/lib/kdep/preset.rb +1 -1
- data/lib/kdep/state.rb +33 -1
- data/lib/kdep/version.rb +1 -1
- data/lib/kdep/yaml_duplicate_keys.rb +72 -0
- data/templates/presets/daemonset +5 -0
- data/templates/resources/daemonset.yml.erb +120 -0
- data/templates/resources/statefulset.yml.erb +16 -0
- metadata +8 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1ed0d8de8dfb58cc372615340a068aecb3f8092aecafdbc6d1c6480c14477d57
|
|
4
|
+
data.tar.gz: 8dde50b29b917321144fcca1d0cb83cff8b66a702442c5bf19375ae527f4cf6e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3669fafcc6315fcdb7af39bb0b31cb7314a953b3185988523af63e1d9ab1fb60a458ba141dc22e3735e00de5df8a821b862b657aaae68d36e8326e0a0f304f2b
|
|
7
|
+
data.tar.gz: 7cbb7445b6909b46e74f47245c88df5fa8d7d93617a949b14059aef3f50c2981941ed129d70f013dede5e2789ad363e55d8768698129059c412c03d631851b81
|
data/lib/kdep/commands/build.rb
CHANGED
|
@@ -42,6 +42,11 @@ module Kdep
|
|
|
42
42
|
# Load config
|
|
43
43
|
config = Kdep::Config.new(deploy_dir, env).load
|
|
44
44
|
|
|
45
|
+
if config["build"] == false
|
|
46
|
+
@ui.error("#{deploy_dir} declares `build: false` — its image is built elsewhere, so there is nothing to build here.")
|
|
47
|
+
exit 1
|
|
48
|
+
end
|
|
49
|
+
|
|
45
50
|
# Resolve tag from state.yml — app.yml has no tag field by design;
|
|
46
51
|
# state.yml is the source of truth for the last bumped version.
|
|
47
52
|
tag = Kdep::State.tag(deploy_dir)
|
data/lib/kdep/commands/bump.rb
CHANGED
|
@@ -48,6 +48,7 @@ module Kdep
|
|
|
48
48
|
# Feature G: preflight validators. Runs for every preset, halts on
|
|
49
49
|
# first failure. --no-check skips.
|
|
50
50
|
unless @command_options[:"no-check"]
|
|
51
|
+
check_duplicate_yaml_keys(deploy_dir)
|
|
51
52
|
run_preflight_checks(config, deploy_dir, kdep_dir)
|
|
52
53
|
end
|
|
53
54
|
|
|
@@ -59,8 +60,13 @@ module Kdep
|
|
|
59
60
|
if preset == "helm"
|
|
60
61
|
run_helm_pipeline(deploy_dir, env)
|
|
61
62
|
return
|
|
62
|
-
elsif preset == "custom"
|
|
63
|
-
|
|
63
|
+
elsif preset == "custom" || config["build"] == false
|
|
64
|
+
# `build: false` marks a target whose image is built elsewhere
|
|
65
|
+
# (upstream images: elasticsearch, fluentd, curator...). It takes the
|
|
66
|
+
# same no-docker route as `custom`, but keeps its own preset's
|
|
67
|
+
# resource list -- the point is to reuse web/worker/statefulset/etc
|
|
68
|
+
# without a Dockerfile. Its tag comes from app.yml, not state.yml.
|
|
69
|
+
run_no_build_pipeline(deploy_dir, config, kdep_dir, env)
|
|
64
70
|
return
|
|
65
71
|
end
|
|
66
72
|
|
|
@@ -229,8 +235,11 @@ module Kdep
|
|
|
229
235
|
helm_install.execute
|
|
230
236
|
end
|
|
231
237
|
|
|
232
|
-
# Feature E:
|
|
233
|
-
|
|
238
|
+
# Feature E: bump with no image to build = render + apply, no docker, no
|
|
239
|
+
# state.yml. Used by the `custom` preset and by any preset with
|
|
240
|
+
# `build: false`; the only difference is which resource list is rendered,
|
|
241
|
+
# which is why this reads config["preset"] instead of hardcoding one.
|
|
242
|
+
def run_no_build_pipeline(deploy_dir, config, kdep_dir, env = nil)
|
|
234
243
|
# Context guard for safety.
|
|
235
244
|
begin
|
|
236
245
|
Kdep::ContextGuard.new(config["context"]).validate!
|
|
@@ -246,7 +255,7 @@ module Kdep
|
|
|
246
255
|
writer = Kdep::Writer.new(output_dir)
|
|
247
256
|
writer.clean
|
|
248
257
|
renderer = Kdep::Renderer.new(config, deploy_dir, env: env)
|
|
249
|
-
preset_resources = Kdep::Preset.new("
|
|
258
|
+
preset_resources = Kdep::Preset.new(config["preset"], deploy_dir).resources
|
|
250
259
|
|
|
251
260
|
preset_resources.each_with_index do |resource, idx|
|
|
252
261
|
content = renderer.render_resource(resource)
|
|
@@ -262,6 +271,20 @@ module Kdep
|
|
|
262
271
|
end
|
|
263
272
|
end
|
|
264
273
|
|
|
274
|
+
# Built-in preflight, unlike the user-declared check: list. The error
|
|
275
|
+
# this catches is made by whoever doesn't know the trap exists, so an
|
|
276
|
+
# opt-in guard would only protect the people who already know. Runs
|
|
277
|
+
# before docker and before the cluster; --no-check is the escape hatch.
|
|
278
|
+
def check_duplicate_yaml_keys(deploy_dir)
|
|
279
|
+
require "kdep/yaml_duplicate_keys"
|
|
280
|
+
dups = Kdep::YamlDuplicateKeys.in_dir(deploy_dir)
|
|
281
|
+
return if dups.empty?
|
|
282
|
+
|
|
283
|
+
dups.each { |dup| @ui.error(dup.to_s) }
|
|
284
|
+
@ui.error("Merge the repeated blocks into one, or re-run with --no-check to bypass.")
|
|
285
|
+
exit 1
|
|
286
|
+
end
|
|
287
|
+
|
|
265
288
|
def run_preflight_checks(config, deploy_dir, kdep_dir)
|
|
266
289
|
checks = config["check"]
|
|
267
290
|
return if checks.nil? || checks.empty?
|
data/lib/kdep/commands/check.rb
CHANGED
|
@@ -35,6 +35,10 @@ module Kdep
|
|
|
35
35
|
deploy_dir = resolve_deploy_dir(kdep_dir, deploy_name, discovery)
|
|
36
36
|
exit 1 unless deploy_dir
|
|
37
37
|
|
|
38
|
+
# Built-in guard, ahead of the user-declared list: a duplicate key is
|
|
39
|
+
# resolved silently by the parser, so nothing downstream can report it.
|
|
40
|
+
check_duplicate_yaml_keys(deploy_dir)
|
|
41
|
+
|
|
38
42
|
config = Kdep::Config.new(deploy_dir, env).load
|
|
39
43
|
checks = config["check"]
|
|
40
44
|
if checks.nil? || checks.empty?
|
|
@@ -58,6 +62,16 @@ module Kdep
|
|
|
58
62
|
|
|
59
63
|
private
|
|
60
64
|
|
|
65
|
+
def check_duplicate_yaml_keys(deploy_dir)
|
|
66
|
+
require "kdep/yaml_duplicate_keys"
|
|
67
|
+
dups = Kdep::YamlDuplicateKeys.in_dir(deploy_dir)
|
|
68
|
+
return if dups.empty?
|
|
69
|
+
|
|
70
|
+
dups.each { |dup| @ui.error(dup.to_s) }
|
|
71
|
+
@ui.error("Merge the repeated blocks into one.")
|
|
72
|
+
exit 1
|
|
73
|
+
end
|
|
74
|
+
|
|
61
75
|
def resolve_deploy_dir(kdep_dir, deploy_name, discovery)
|
|
62
76
|
if deploy_name
|
|
63
77
|
path = File.join(kdep_dir, deploy_name)
|
data/lib/kdep/commands/push.rb
CHANGED
|
@@ -41,6 +41,11 @@ module Kdep
|
|
|
41
41
|
# Load config
|
|
42
42
|
config = Kdep::Config.new(deploy_dir, env).load
|
|
43
43
|
|
|
44
|
+
if config["build"] == false
|
|
45
|
+
@ui.error("#{deploy_dir} declares `build: false` — its image lives in an upstream registry, so there is nothing to push.")
|
|
46
|
+
exit 1
|
|
47
|
+
end
|
|
48
|
+
|
|
44
49
|
# Resolve tag from state.yml — app.yml has no tag field by design;
|
|
45
50
|
# state.yml is the source of truth for the last bumped version.
|
|
46
51
|
tag = Kdep::State.tag(deploy_dir)
|
|
@@ -3,16 +3,16 @@ require "optparse"
|
|
|
3
3
|
module Kdep
|
|
4
4
|
module Commands
|
|
5
5
|
class Restart
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
WORKLOAD_KINDS = %w[deployment statefulset daemonset].freeze
|
|
7
|
+
NON_RESTARTABLE_KINDS = %w[job cronjob].freeze
|
|
8
8
|
|
|
9
9
|
def self.option_parser
|
|
10
10
|
OptionParser.new do |opts|
|
|
11
11
|
opts.banner = "Usage: kdep restart [deploy] [--type TYPE]"
|
|
12
12
|
opts.separator ""
|
|
13
|
-
opts.separator "Triggers a rolling restart of a Deployment or
|
|
13
|
+
opts.separator "Triggers a rolling restart of a Deployment, StatefulSet or DaemonSet."
|
|
14
14
|
opts.separator ""
|
|
15
|
-
opts.on("--type TYPE", "Resource type: deployment, statefulset (overrides preset)") do |_|
|
|
15
|
+
opts.on("--type TYPE", "Resource type: deployment, statefulset, daemonset (overrides preset)") do |_|
|
|
16
16
|
# parsed via into: hash
|
|
17
17
|
end
|
|
18
18
|
end
|
|
@@ -58,30 +58,58 @@ module Kdep
|
|
|
58
58
|
preset = config["preset"]
|
|
59
59
|
|
|
60
60
|
# Determine resource type
|
|
61
|
-
resource_type = determine_resource_type(preset)
|
|
61
|
+
resource_type = determine_resource_type(preset, deploy_dir)
|
|
62
62
|
|
|
63
63
|
# Execute rollout restart
|
|
64
|
-
|
|
64
|
+
begin
|
|
65
|
+
Kdep::Kubectl.run("rollout", "restart", "#{resource_type}/#{app_name}", "-n", namespace)
|
|
66
|
+
rescue Kdep::Kubectl::Error => e
|
|
67
|
+
@ui.error(e.message)
|
|
68
|
+
@ui.info("Hint: use --type <deployment|statefulset|daemonset> if the workload kind differs from the preset")
|
|
69
|
+
exit 1
|
|
70
|
+
end
|
|
65
71
|
@ui.info("Restarted #{resource_type}/#{app_name} in #{namespace}")
|
|
66
72
|
end
|
|
67
73
|
|
|
68
74
|
private
|
|
69
75
|
|
|
70
|
-
|
|
76
|
+
# The preset file lists the resources kdep applies, so the workload kind
|
|
77
|
+
# is declared there -- deriving it from the preset *name* got every
|
|
78
|
+
# non-Deployment target wrong (daemonset, statefulset, custom). Reading
|
|
79
|
+
# the list also handles `custom`, whose resources live in the deploy dir.
|
|
80
|
+
def determine_resource_type(preset, deploy_dir)
|
|
71
81
|
if @command_options[:type]
|
|
72
82
|
return @command_options[:type]
|
|
73
83
|
end
|
|
74
84
|
|
|
75
|
-
resource_type_for_preset(preset)
|
|
85
|
+
resource_type_for_preset(preset, deploy_dir)
|
|
76
86
|
end
|
|
77
87
|
|
|
78
|
-
def resource_type_for_preset(preset)
|
|
79
|
-
|
|
80
|
-
|
|
88
|
+
def resource_type_for_preset(preset, deploy_dir)
|
|
89
|
+
resources = preset_resources(preset, deploy_dir)
|
|
90
|
+
|
|
91
|
+
if (kind = resources.find { |r| NON_RESTARTABLE_KINDS.include?(r) })
|
|
92
|
+
@ui.error("#{kind} workloads cannot be restarted (use --type to override)")
|
|
81
93
|
exit 1
|
|
82
94
|
end
|
|
83
95
|
|
|
84
|
-
|
|
96
|
+
found = resources & WORKLOAD_KINDS
|
|
97
|
+
|
|
98
|
+
if found.size > 1
|
|
99
|
+
@ui.error("Multiple workloads in preset '#{preset}': #{found.join(', ')}. Use --type to pick one.")
|
|
100
|
+
exit 1
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# helm charts declare no workload of their own, and a missing preset is
|
|
104
|
+
# already reported by render/apply -- keep the old guess rather than
|
|
105
|
+
# failing a command that used to work.
|
|
106
|
+
found.first || "deployment"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def preset_resources(preset, deploy_dir)
|
|
110
|
+
Kdep::Preset.new(preset, deploy_dir).resources
|
|
111
|
+
rescue StandardError
|
|
112
|
+
[]
|
|
85
113
|
end
|
|
86
114
|
|
|
87
115
|
def resolve_deploy_dir(kdep_dir, deploy_name, discovery)
|
data/lib/kdep/defaults.rb
CHANGED
|
@@ -13,6 +13,9 @@ module Kdep
|
|
|
13
13
|
"cronjob" => {},
|
|
14
14
|
"statefulset" => {},
|
|
15
15
|
"statefulset_svc" => {},
|
|
16
|
+
# No replicas override: a DaemonSet has no replicas field at all, so the
|
|
17
|
+
# BASE_DEFAULTS value is simply never read by daemonset.yml.erb.
|
|
18
|
+
"daemonset" => {},
|
|
16
19
|
"helm" => {},
|
|
17
20
|
"custom" => {},
|
|
18
21
|
}.freeze
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require "kdep/yaml_duplicate_keys"
|
|
2
|
+
|
|
3
|
+
module Kdep
|
|
4
|
+
module Doctor
|
|
5
|
+
# A duplicate key is not catchable by review -- in a 50-line file with the
|
|
6
|
+
# two blocks 30 lines apart, they are invisible. It's machine work, and
|
|
7
|
+
# kdep is the one place in the flow that sees every deploy's YAML.
|
|
8
|
+
class CheckYamlDupKeys < Check
|
|
9
|
+
ID = "yaml_dup_keys".freeze
|
|
10
|
+
|
|
11
|
+
def run
|
|
12
|
+
dups = Kdep::YamlDuplicateKeys.in_dir(deploy_dir)
|
|
13
|
+
return Result.ok(ID) if dups.empty?
|
|
14
|
+
|
|
15
|
+
Result.new(
|
|
16
|
+
id: ID,
|
|
17
|
+
severity: :error,
|
|
18
|
+
message: dups.map(&:to_s).join("; "),
|
|
19
|
+
# Deliberately not wired into --fix: merging two blocks means
|
|
20
|
+
# deciding which value survives when the inner keys conflict, and a
|
|
21
|
+
# wrong automatic merge is worse than the bug -- it looks reviewed.
|
|
22
|
+
hint: "Merge the repeated blocks into one by hand."
|
|
23
|
+
)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
data/lib/kdep/doctor.rb
CHANGED
|
@@ -5,6 +5,7 @@ require "kdep/doctor/check_state_parseable"
|
|
|
5
5
|
require "kdep/doctor/check_state_tag_format"
|
|
6
6
|
require "kdep/doctor/check_state_gitignored"
|
|
7
7
|
require "kdep/doctor/check_gitignore_canonical"
|
|
8
|
+
require "kdep/doctor/check_yaml_dup_keys"
|
|
8
9
|
|
|
9
10
|
module Kdep
|
|
10
11
|
module Doctor
|
|
@@ -14,6 +15,7 @@ module Kdep
|
|
|
14
15
|
CheckStateTagFormat,
|
|
15
16
|
CheckStateGitignored,
|
|
16
17
|
CheckGitignoreCanonical,
|
|
18
|
+
CheckYamlDupKeys,
|
|
17
19
|
].freeze
|
|
18
20
|
end
|
|
19
21
|
end
|
data/lib/kdep/preset.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module Kdep
|
|
2
2
|
class Preset
|
|
3
|
-
BUILT_IN = %w[web worker job cronjob statefulset statefulset_svc helm custom].freeze
|
|
3
|
+
BUILT_IN = %w[web worker job cronjob statefulset statefulset_svc daemonset helm custom].freeze
|
|
4
4
|
|
|
5
5
|
def initialize(preset_name, deploy_dir)
|
|
6
6
|
raise "preset: is required in app.yml" if preset_name.to_s.strip.empty?
|
data/lib/kdep/state.rb
CHANGED
|
@@ -27,15 +27,47 @@ module Kdep
|
|
|
27
27
|
loaded && loaded["tag"]
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
+
# Rewrites only the `tag:` line, keeping everything else byte-for-byte.
|
|
31
|
+
# The header says "do not edit by hand", but state.yml is the one place a
|
|
32
|
+
# warning sits next to the data it guards -- e.g. a target that shares an
|
|
33
|
+
# image repo with another and must never be re-seeded with --init-state.
|
|
34
|
+
# Rebuilding the file from scratch silently ate those comments on every
|
|
35
|
+
# bump, and a YAML round-trip would too (the parser doesn't carry them).
|
|
30
36
|
def self.write(deploy_dir, tag:)
|
|
31
37
|
path = File.join(deploy_dir, FILENAME)
|
|
32
38
|
tmp = "#{path}.tmp"
|
|
33
|
-
|
|
39
|
+
|
|
40
|
+
content = if File.exist?(path)
|
|
41
|
+
with_header(replace_tag_line(File.read(path), tag))
|
|
42
|
+
else
|
|
43
|
+
"#{HEADER}tag: \"#{tag}\"\n"
|
|
44
|
+
end
|
|
45
|
+
|
|
34
46
|
File.write(tmp, content)
|
|
35
47
|
File.rename(tmp, path)
|
|
36
48
|
path
|
|
37
49
|
end
|
|
38
50
|
|
|
51
|
+
# Appends when the key is absent (state hand-edited into a corrupt shape)
|
|
52
|
+
# rather than discarding what was there -- `load` already raises on that,
|
|
53
|
+
# so the right move here is to not destroy the evidence.
|
|
54
|
+
def self.replace_tag_line(current, tag)
|
|
55
|
+
line = "tag: \"#{tag}\""
|
|
56
|
+
return current.sub(/^tag:.*$/, line) if current.match?(/^tag:.*$/)
|
|
57
|
+
|
|
58
|
+
"#{current.chomp}\n#{line}\n"
|
|
59
|
+
end
|
|
60
|
+
private_class_method :replace_tag_line
|
|
61
|
+
|
|
62
|
+
# A state.yml committed by hand (bump suggests exactly that when none
|
|
63
|
+
# exists) has no banner; give it one instead of leaving it unmarked.
|
|
64
|
+
def self.with_header(content)
|
|
65
|
+
return content if content.include?(HEADER.lines.first.chomp)
|
|
66
|
+
|
|
67
|
+
"#{HEADER}#{content}"
|
|
68
|
+
end
|
|
69
|
+
private_class_method :with_header
|
|
70
|
+
|
|
39
71
|
def self.exists?(deploy_dir)
|
|
40
72
|
File.exist?(File.join(deploy_dir, FILENAME))
|
|
41
73
|
end
|
data/lib/kdep/version.rb
CHANGED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
require "psych"
|
|
2
|
+
|
|
3
|
+
module Kdep
|
|
4
|
+
# Finds keys declared twice in the same mapping, at any depth.
|
|
5
|
+
#
|
|
6
|
+
# YAML resolves a repeated key by keeping the LAST one and discarding the
|
|
7
|
+
# earlier block ENTIRELY, in silence. Nothing in the pipeline warns: helm
|
|
8
|
+
# template, helm upgrade and kdep all receive the hash already collapsed by
|
|
9
|
+
# the parser. In repleadfy/llm-api2#1 a redis values.yaml declared `metrics:`
|
|
10
|
+
# twice; the first block -- the one redirecting the exporter registry to
|
|
11
|
+
# bitnamilegacy, after Bitnami closed the free repo -- was dropped at parse
|
|
12
|
+
# time, and the wrong image ran in production for 73 days, surviving only on
|
|
13
|
+
# a node's image cache.
|
|
14
|
+
#
|
|
15
|
+
# This walks the Psych AST rather than calling Psych.load, because load has
|
|
16
|
+
# already applied last-one-wins and destroyed the evidence. The AST also
|
|
17
|
+
# gives real line numbers on both sides, works at any depth, and knows that
|
|
18
|
+
# `key: value` text inside a block scalar is data, not a key -- which a grep
|
|
19
|
+
# over the raw file cannot.
|
|
20
|
+
module YamlDuplicateKeys
|
|
21
|
+
Duplicate = Struct.new(:file, :key_path, :first_line, :repeat_line) do
|
|
22
|
+
def to_s
|
|
23
|
+
"#{file}:#{repeat_line} duplicate key '#{key_path}' (first declared at line " \
|
|
24
|
+
"#{first_line}) -- YAML keeps the last block and discards the earlier one"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.in_file(path)
|
|
29
|
+
ast = Psych.parse_stream(File.read(path))
|
|
30
|
+
scan(ast).map do |dup|
|
|
31
|
+
Duplicate.new(File.basename(path), dup[:path], dup[:first], dup[:repeat])
|
|
32
|
+
end
|
|
33
|
+
rescue Psych::SyntaxError
|
|
34
|
+
# A file that doesn't parse is a different problem, reported elsewhere
|
|
35
|
+
# with better context (Config#load, helm). Silently collapsing is the
|
|
36
|
+
# failure this scanner exists for; don't turn it into a crash here.
|
|
37
|
+
[]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Top-level only: everything kdep reads as input lives in the deploy dir
|
|
41
|
+
# root (app.yml, app.<env>.yml, secrets.yml, values.yaml), while nested
|
|
42
|
+
# dirs hold rendered output and templates.
|
|
43
|
+
def self.in_dir(dir)
|
|
44
|
+
Dir.glob(File.join(dir, "*.{yml,yaml}")).sort.flat_map { |f| in_file(f) }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def self.scan(node, path = [])
|
|
48
|
+
found = []
|
|
49
|
+
|
|
50
|
+
case node
|
|
51
|
+
when Psych::Nodes::Mapping
|
|
52
|
+
seen = {}
|
|
53
|
+
node.children.each_slice(2) do |key, value|
|
|
54
|
+
name = key.respond_to?(:value) ? key.value : key.to_s
|
|
55
|
+
if seen.key?(name)
|
|
56
|
+
found << {
|
|
57
|
+
:path => (path + [name]).join("."),
|
|
58
|
+
:first => seen[name],
|
|
59
|
+
:repeat => key.start_line + 1,
|
|
60
|
+
}
|
|
61
|
+
end
|
|
62
|
+
seen[name] = key.start_line + 1
|
|
63
|
+
found.concat(scan(value, path + [name]))
|
|
64
|
+
end
|
|
65
|
+
when Psych::Nodes::Sequence, Psych::Nodes::Document, Psych::Nodes::Stream
|
|
66
|
+
node.children.each { |child| found.concat(scan(child, path)) }
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
found
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
apiVersion: apps/v1
|
|
2
|
+
kind: DaemonSet
|
|
3
|
+
metadata:
|
|
4
|
+
name: <%= @config["daemonset_name"] || name %>
|
|
5
|
+
namespace: <%= namespace %>
|
|
6
|
+
labels:
|
|
7
|
+
app: <%= name %>
|
|
8
|
+
spec:
|
|
9
|
+
selector:
|
|
10
|
+
matchLabels:
|
|
11
|
+
app: <%= name %>
|
|
12
|
+
template:
|
|
13
|
+
metadata:
|
|
14
|
+
labels:
|
|
15
|
+
app: <%= name %>
|
|
16
|
+
<% if @config["pod_labels"] -%>
|
|
17
|
+
<% @config["pod_labels"].each do |k, v| -%>
|
|
18
|
+
<%= k %>: "<%= v %>"
|
|
19
|
+
<% end -%>
|
|
20
|
+
<% end -%>
|
|
21
|
+
spec:
|
|
22
|
+
<% if @config["service_account_name"] -%>
|
|
23
|
+
serviceAccountName: <%= @config["service_account_name"] %>
|
|
24
|
+
<% end -%>
|
|
25
|
+
<% if @config["host_network"] -%>
|
|
26
|
+
hostNetwork: <%= @config["host_network"] %>
|
|
27
|
+
<% end -%>
|
|
28
|
+
<% if @config["termination_grace_period"] -%>
|
|
29
|
+
terminationGracePeriodSeconds: <%= @config["termination_grace_period"] %>
|
|
30
|
+
<% end -%>
|
|
31
|
+
<% if @config["node_selector"] -%>
|
|
32
|
+
nodeSelector:
|
|
33
|
+
<%= @config["node_selector"].to_yaml.sub("---\n", "").gsub(/^/, " ").rstrip %>
|
|
34
|
+
<% end -%>
|
|
35
|
+
<% if @config["tolerations"] -%>
|
|
36
|
+
<%# DaemonSets routinely need to land on control-plane/tainted nodes. -%>
|
|
37
|
+
tolerations:
|
|
38
|
+
<%= @config["tolerations"].to_yaml.sub("---\n", "").gsub(/^/, " ").rstrip %>
|
|
39
|
+
<% end -%>
|
|
40
|
+
<% if @config["image_pull_secrets"] -%>
|
|
41
|
+
imagePullSecrets:
|
|
42
|
+
- name: <%= @config["image_pull_secrets"] %>
|
|
43
|
+
<% end -%>
|
|
44
|
+
<% if @config["init_containers"] -%>
|
|
45
|
+
initContainers:
|
|
46
|
+
<%= @config["init_containers"].to_yaml.sub("---\n", "").gsub(/^/, " ").rstrip %>
|
|
47
|
+
<% end -%>
|
|
48
|
+
containers:
|
|
49
|
+
- name: <%= @config["container_name"] || name %>
|
|
50
|
+
image: <%= image_ref %>
|
|
51
|
+
<% if @config["image_pull_policy"] -%>
|
|
52
|
+
imagePullPolicy: <%= @config["image_pull_policy"] %>
|
|
53
|
+
<% end -%>
|
|
54
|
+
<% if @config["command"] -%>
|
|
55
|
+
command: <%= @config["command"].is_a?(Array) ? @config["command"].inspect : "[\"#{@config["command"]}\"]" %>
|
|
56
|
+
<% end -%>
|
|
57
|
+
<% if @config["args"] -%>
|
|
58
|
+
args: <%= @config["args"].is_a?(Array) ? @config["args"].inspect : "[\"#{@config["args"]}\"]" %>
|
|
59
|
+
<% end -%>
|
|
60
|
+
<% if @config["port"] -%>
|
|
61
|
+
ports:
|
|
62
|
+
- containerPort: <%= port %>
|
|
63
|
+
<% if @config["container_port_protocol"] -%>
|
|
64
|
+
protocol: <%= @config["container_port_protocol"] %>
|
|
65
|
+
<% end -%>
|
|
66
|
+
<% end -%>
|
|
67
|
+
<% if @config["security_context"] -%>
|
|
68
|
+
securityContext:
|
|
69
|
+
<%= @config["security_context"].to_yaml.sub("---\n", "").gsub(/^/, " ").rstrip %>
|
|
70
|
+
<% end -%>
|
|
71
|
+
<% if @config["resources"] -%>
|
|
72
|
+
resources:
|
|
73
|
+
<% if @config["resources"]["requests"] -%>
|
|
74
|
+
requests:
|
|
75
|
+
<% @config["resources"]["requests"].each do |k, v| -%>
|
|
76
|
+
<%= k %>: <%= v.is_a?(Numeric) ? v : "\"#{v}\"" %>
|
|
77
|
+
<% end -%>
|
|
78
|
+
<% end -%>
|
|
79
|
+
<% if @config["resources"]["limits"] -%>
|
|
80
|
+
limits:
|
|
81
|
+
<% @config["resources"]["limits"].each do |k, v| -%>
|
|
82
|
+
<%= k %>: <%= v.is_a?(Numeric) ? v : "\"#{v}\"" %>
|
|
83
|
+
<% end -%>
|
|
84
|
+
<% end -%>
|
|
85
|
+
<% end -%>
|
|
86
|
+
<% if @config["volume_mounts"] -%>
|
|
87
|
+
volumeMounts:
|
|
88
|
+
<% @config["volume_mounts"].each do |vm| -%>
|
|
89
|
+
- name: <%= vm["name"] %>
|
|
90
|
+
mountPath: <%= vm["mountPath"] %>
|
|
91
|
+
<% if vm["readOnly"] -%>
|
|
92
|
+
readOnly: <%= vm["readOnly"] %>
|
|
93
|
+
<% end -%>
|
|
94
|
+
<% if vm["subPath"] -%>
|
|
95
|
+
subPath: <%= vm["subPath"] %>
|
|
96
|
+
<% end -%>
|
|
97
|
+
<% end -%>
|
|
98
|
+
<% end -%>
|
|
99
|
+
<% if @config["container_env"] -%>
|
|
100
|
+
<%# Named container_env, not env: `env:` is a backward-compat alias for -%>
|
|
101
|
+
<%# `configmap:` (see Config#apply_defaults), so it never reaches a template. -%>
|
|
102
|
+
env:
|
|
103
|
+
<%= @config["container_env"].to_yaml.sub("---\n", "").gsub(/^/, " ").rstrip %>
|
|
104
|
+
<% end -%>
|
|
105
|
+
envFrom:
|
|
106
|
+
<% env_from_list = @config["env_from"] || ["configmap/config-#{name}", "secret/secret-#{name}"] -%>
|
|
107
|
+
<% env_from_list.each do |ref| -%>
|
|
108
|
+
<% type, ref_name = ref.split("/", 2) -%>
|
|
109
|
+
<% if type == "configmap" -%>
|
|
110
|
+
- configMapRef:
|
|
111
|
+
name: <%= ref_name %>
|
|
112
|
+
<% elsif type == "secret" -%>
|
|
113
|
+
- secretRef:
|
|
114
|
+
name: <%= ref_name %>
|
|
115
|
+
<% end -%>
|
|
116
|
+
<% end -%>
|
|
117
|
+
<% if @config["volumes"] -%>
|
|
118
|
+
volumes:
|
|
119
|
+
<%= @config["volumes"].to_yaml.sub("---\n", "").gsub(/^/, " ").rstrip %>
|
|
120
|
+
<% end -%>
|
|
@@ -27,6 +27,10 @@ spec:
|
|
|
27
27
|
<% if @config["image_pull_secrets"] -%>
|
|
28
28
|
imagePullSecrets:
|
|
29
29
|
- name: <%= @config["image_pull_secrets"] %>
|
|
30
|
+
<% end -%>
|
|
31
|
+
<% if @config["init_containers"] -%>
|
|
32
|
+
initContainers:
|
|
33
|
+
<%= @config["init_containers"].to_yaml.sub("---\n", "").gsub(/^/, " ").rstrip %>
|
|
30
34
|
<% end -%>
|
|
31
35
|
containers:
|
|
32
36
|
- name: <%= @config["container_name"] || name %>
|
|
@@ -106,6 +110,12 @@ spec:
|
|
|
106
110
|
subPath: <%= vm["subPath"] %>
|
|
107
111
|
<% end -%>
|
|
108
112
|
<% end -%>
|
|
113
|
+
<% end -%>
|
|
114
|
+
<% if @config["container_env"] -%>
|
|
115
|
+
<%# Named container_env, not env: `env:` is a backward-compat alias for -%>
|
|
116
|
+
<%# `configmap:` (see Config#apply_defaults), so it never reaches a template. -%>
|
|
117
|
+
env:
|
|
118
|
+
<%= @config["container_env"].to_yaml.sub("---\n", "").gsub(/^/, " ").rstrip %>
|
|
109
119
|
<% end -%>
|
|
110
120
|
envFrom:
|
|
111
121
|
<% env_from_list = @config["env_from"] || ["configmap/config-#{name}", "secret/secret-#{name}"] -%>
|
|
@@ -123,3 +133,9 @@ spec:
|
|
|
123
133
|
volumes:
|
|
124
134
|
<%= @config["volumes"].to_yaml.sub("---\n", "").gsub(/^/, " ").rstrip %>
|
|
125
135
|
<% end -%>
|
|
136
|
+
<% if @config["volume_claim_templates"] -%>
|
|
137
|
+
<%# Sibling of `template:`, not part of the pod spec -- this is the one field -%>
|
|
138
|
+
<%# that makes a StatefulSet a StatefulSet, so it belongs in the built-in. -%>
|
|
139
|
+
volumeClaimTemplates:
|
|
140
|
+
<%= @config["volume_claim_templates"].to_yaml.sub("---\n", "").gsub(/^/, " ").rstrip %>
|
|
141
|
+
<% end -%>
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kdep
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.8
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Leadfy
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-08-15 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: envspec
|
|
@@ -42,14 +42,14 @@ dependencies:
|
|
|
42
42
|
name: minitest
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
|
44
44
|
requirements:
|
|
45
|
-
- - "
|
|
45
|
+
- - ">="
|
|
46
46
|
- !ruby/object:Gem::Version
|
|
47
47
|
version: '5.0'
|
|
48
48
|
type: :development
|
|
49
49
|
prerelease: false
|
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
51
|
requirements:
|
|
52
|
-
- - "
|
|
52
|
+
- - ">="
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
54
|
version: '5.0'
|
|
55
55
|
- !ruby/object:Gem::Dependency
|
|
@@ -125,6 +125,7 @@ files:
|
|
|
125
125
|
- lib/kdep/doctor/check_state_parseable.rb
|
|
126
126
|
- lib/kdep/doctor/check_state_present.rb
|
|
127
127
|
- lib/kdep/doctor/check_state_tag_format.rb
|
|
128
|
+
- lib/kdep/doctor/check_yaml_dup_keys.rb
|
|
128
129
|
- lib/kdep/doctor/result.rb
|
|
129
130
|
- lib/kdep/helm.rb
|
|
130
131
|
- lib/kdep/kubectl.rb
|
|
@@ -144,12 +145,14 @@ files:
|
|
|
144
145
|
- lib/kdep/version_tagger.rb
|
|
145
146
|
- lib/kdep/writer.rb
|
|
146
147
|
- lib/kdep/yaml_compat.rb
|
|
148
|
+
- lib/kdep/yaml_duplicate_keys.rb
|
|
147
149
|
- templates/init/app.yml.erb
|
|
148
150
|
- templates/init/env.spec
|
|
149
151
|
- templates/init/gitignore
|
|
150
152
|
- templates/init/secrets.yml
|
|
151
153
|
- templates/presets/cronjob
|
|
152
154
|
- templates/presets/custom
|
|
155
|
+
- templates/presets/daemonset
|
|
153
156
|
- templates/presets/helm
|
|
154
157
|
- templates/presets/job
|
|
155
158
|
- templates/presets/statefulset
|
|
@@ -158,6 +161,7 @@ files:
|
|
|
158
161
|
- templates/presets/worker
|
|
159
162
|
- templates/resources/configmap.yml.erb
|
|
160
163
|
- templates/resources/cronjob.yml.erb
|
|
164
|
+
- templates/resources/daemonset.yml.erb
|
|
161
165
|
- templates/resources/deployment.yml.erb
|
|
162
166
|
- templates/resources/helm_ingress.yml.erb
|
|
163
167
|
- templates/resources/infisical_secret.yml.erb
|