kdep 0.3.4 → 0.3.5
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/cli.rb +1 -0
- data/lib/kdep/commands/bump.rb +49 -7
- data/lib/kdep/commands/diff.rb +27 -23
- data/lib/kdep/commands/doctor.rb +99 -0
- data/lib/kdep/commands/migrate.rb +22 -0
- data/lib/kdep/commands/render.rb +6 -0
- data/lib/kdep/doctor/check.rb +15 -0
- data/lib/kdep/doctor/check_ancestor_ignores_state.rb +26 -0
- data/lib/kdep/doctor/check_gitignore_canonical.rb +30 -0
- data/lib/kdep/doctor/check_state_gitignored.rb +26 -0
- data/lib/kdep/doctor/check_state_parseable.rb +19 -0
- data/lib/kdep/doctor/check_state_present.rb +21 -0
- data/lib/kdep/doctor/check_state_tag_format.rb +20 -0
- data/lib/kdep/doctor/result.rb +26 -0
- data/lib/kdep/doctor.rb +21 -0
- data/lib/kdep/old_format.rb +27 -0
- data/lib/kdep/state.rb +48 -0
- data/lib/kdep/version.rb +1 -1
- data/lib/kdep.rb +3 -0
- data/templates/init/gitignore +2 -1
- metadata +13 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2f2546356a99516a639dc29b0d25a6494898c351a2dd40ca58a3510da93aa49d
|
|
4
|
+
data.tar.gz: ea992fe366d4d6875b1edf6d976d2c8ece688854d76dfd12ab87031c85ffda6d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 122d63cdabc4fda2ff9f7f52f546dc5288d26209ec9f7025ee93cbdd0109e4de094e37af1b5073266b6b5a5d939ca15fcd65004dc79c5eda286709bc4fb059d3
|
|
7
|
+
data.tar.gz: 2aee7b30512d9102f86bf1150b6ee5054f741497bc4009784dbaf38f2d2f222ae6b8c3a2249090c3a9dad5a31fcdf013c8c753e7f07dd761051ccc189e0450f8
|
data/lib/kdep/cli.rb
CHANGED
data/lib/kdep/commands/bump.rb
CHANGED
|
@@ -13,6 +13,7 @@ module Kdep
|
|
|
13
13
|
opts.on("--dry-run", "Run full pipeline but skip kubectl apply")
|
|
14
14
|
opts.on("--no-dashboard", "Skip TUI dashboard after deploy")
|
|
15
15
|
opts.on("--platform=PLATFORM", "Target platform (e.g., linux/amd64)")
|
|
16
|
+
opts.on("--init-state", "Seed state.yml at 0.0 when missing (non-interactive)")
|
|
16
17
|
end
|
|
17
18
|
end
|
|
18
19
|
|
|
@@ -64,12 +65,18 @@ module Kdep
|
|
|
64
65
|
|
|
65
66
|
begin
|
|
66
67
|
# Step 3: Read current tag from state.yml, increment minor
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
68
|
+
begin
|
|
69
|
+
current_tag = Kdep::State.tag(deploy_dir) || handle_missing_state(deploy_dir)
|
|
70
|
+
rescue Kdep::State::Error => e
|
|
71
|
+
@ui.error(e.message)
|
|
72
|
+
exit 1
|
|
73
|
+
end
|
|
74
|
+
# to_fix.md #3 — warn if someone has gitignored state.yml
|
|
75
|
+
require "kdep/doctor/check_state_gitignored"
|
|
76
|
+
gitignore_result = Kdep::Doctor::CheckStateGitignored.new(deploy_dir).run
|
|
77
|
+
if gitignore_result.severity == :error
|
|
78
|
+
@ui.warn(gitignore_result.message)
|
|
79
|
+
@ui.warn(gitignore_result.hint) if gitignore_result.hint
|
|
73
80
|
end
|
|
74
81
|
parsed = Kdep::VersionTagger.parse(current_tag)
|
|
75
82
|
next_version = parsed ? "#{parsed[0]}.#{parsed[1] + 1}" : "0.1"
|
|
@@ -159,7 +166,7 @@ module Kdep
|
|
|
159
166
|
@ui.info("#{applied} files applied, 0 errors")
|
|
160
167
|
|
|
161
168
|
# Save new tag to state.yml
|
|
162
|
-
|
|
169
|
+
Kdep::State.write(deploy_dir, tag: next_version)
|
|
163
170
|
|
|
164
171
|
# Launch TUI dashboard for live monitoring
|
|
165
172
|
unless @command_options[:"no-dashboard"]
|
|
@@ -185,6 +192,41 @@ module Kdep
|
|
|
185
192
|
|
|
186
193
|
private
|
|
187
194
|
|
|
195
|
+
def handle_missing_state(deploy_dir)
|
|
196
|
+
if @command_options[:"init-state"]
|
|
197
|
+
seed_initial_state(deploy_dir, reason: :flag)
|
|
198
|
+
elsif $stdin.tty?
|
|
199
|
+
prompt_for_init(deploy_dir)
|
|
200
|
+
else
|
|
201
|
+
@ui.error(
|
|
202
|
+
"state.yml not found in #{deploy_dir}.\n" \
|
|
203
|
+
"Re-run with --init-state to seed at \"#{Kdep::State::INITIAL_TAG}\" " \
|
|
204
|
+
"(first deploy will be tag \"0.1\"), or commit a state.yml manually."
|
|
205
|
+
)
|
|
206
|
+
exit 1
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def prompt_for_init(deploy_dir)
|
|
211
|
+
@ui.warn("No state.yml found in #{deploy_dir}.")
|
|
212
|
+
@ui.warn("If prod is already running a higher tag, this will regress it.")
|
|
213
|
+
print %Q(Seed state.yml at "#{Kdep::State::INITIAL_TAG}" and proceed? [y/N] )
|
|
214
|
+
answer = $stdin.gets&.strip&.downcase
|
|
215
|
+
unless %w[y yes].include?(answer)
|
|
216
|
+
@ui.info("Aborted.")
|
|
217
|
+
exit 0
|
|
218
|
+
end
|
|
219
|
+
seed_initial_state(deploy_dir, reason: :prompt)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def seed_initial_state(deploy_dir, reason:)
|
|
223
|
+
tag = Kdep::State::INITIAL_TAG
|
|
224
|
+
Kdep::State.write(deploy_dir, tag: tag)
|
|
225
|
+
source = reason == :flag ? "--init-state flag" : "user confirmation"
|
|
226
|
+
@ui.info("Seeded state.yml at #{tag} (#{source}). First deploy will be tag 0.1.")
|
|
227
|
+
tag
|
|
228
|
+
end
|
|
229
|
+
|
|
188
230
|
def resolve_deploy_dir(kdep_dir, deploy_name, discovery)
|
|
189
231
|
if deploy_name
|
|
190
232
|
deploy_dir = File.join(kdep_dir, deploy_name)
|
data/lib/kdep/commands/diff.rb
CHANGED
|
@@ -48,37 +48,43 @@ module Kdep
|
|
|
48
48
|
exit 1
|
|
49
49
|
end
|
|
50
50
|
|
|
51
|
-
#
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
writer = Kdep::Writer.new(output_dir)
|
|
59
|
-
writer.clean
|
|
60
|
-
renderer = Kdep::Renderer.new(config, deploy_dir)
|
|
51
|
+
# Inject tag from state.yml if present (to_fix.md #1)
|
|
52
|
+
if (state_tag = Kdep::State.tag(deploy_dir))
|
|
53
|
+
config["tag"] = state_tag
|
|
54
|
+
else
|
|
55
|
+
@ui.warn("No state.yml in #{deploy_dir} -- diff will compare preset default tag against live cluster.")
|
|
56
|
+
end
|
|
61
57
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
58
|
+
# Render into a temp dir so we never clobber .rendered/ (owned by bump/render)
|
|
59
|
+
require "tmpdir"
|
|
60
|
+
Dir.mktmpdir("kdep-diff-") do |tmpdir|
|
|
61
|
+
preset = Kdep::Preset.new(config["preset"], deploy_dir)
|
|
62
|
+
writer = Kdep::Writer.new(tmpdir)
|
|
63
|
+
renderer = Kdep::Renderer.new(config, deploy_dir)
|
|
64
|
+
|
|
65
|
+
preset.resources.each_with_index do |resource_name, idx|
|
|
66
|
+
begin
|
|
67
|
+
content = renderer.render_resource(resource_name)
|
|
68
|
+
rescue => e
|
|
69
|
+
@ui.error("#{resource_name}: #{e.message}")
|
|
70
|
+
next
|
|
71
|
+
end
|
|
72
|
+
writer.write(resource_name, content, idx + 1)
|
|
69
73
|
end
|
|
70
|
-
|
|
74
|
+
|
|
75
|
+
rendered_files = Dir.glob(File.join(tmpdir, "*.yml")).sort
|
|
76
|
+
run_diff(rendered_files)
|
|
71
77
|
end
|
|
78
|
+
end
|
|
72
79
|
|
|
73
|
-
|
|
74
|
-
rendered_files = Dir.glob(File.join(output_dir, "*.yml")).sort
|
|
80
|
+
private
|
|
75
81
|
|
|
82
|
+
def run_diff(rendered_files)
|
|
76
83
|
if rendered_files.empty?
|
|
77
84
|
@ui.info("No rendered files to diff")
|
|
78
85
|
return
|
|
79
86
|
end
|
|
80
87
|
|
|
81
|
-
# Diff each file against live cluster state
|
|
82
88
|
has_diffs = false
|
|
83
89
|
errors = []
|
|
84
90
|
|
|
@@ -101,8 +107,6 @@ module Kdep
|
|
|
101
107
|
end
|
|
102
108
|
end
|
|
103
109
|
|
|
104
|
-
private
|
|
105
|
-
|
|
106
110
|
def resolve_deploy_dir(kdep_dir, deploy_name, discovery)
|
|
107
111
|
if deploy_name
|
|
108
112
|
deploy_dir = File.join(kdep_dir, deploy_name)
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
require "optparse"
|
|
2
|
+
require "json"
|
|
3
|
+
require "fileutils"
|
|
4
|
+
|
|
5
|
+
module Kdep
|
|
6
|
+
module Commands
|
|
7
|
+
class Doctor
|
|
8
|
+
def self.option_parser
|
|
9
|
+
OptionParser.new do |opts|
|
|
10
|
+
opts.banner = "Usage: kdep doctor [deploy]"
|
|
11
|
+
opts.separator ""
|
|
12
|
+
opts.separator "Runs health checks against the kdep/ directory."
|
|
13
|
+
opts.separator "With a deploy name, checks only that deploy; otherwise, checks all."
|
|
14
|
+
opts.separator ""
|
|
15
|
+
opts.on("--fix", "Auto-fix safe issues (missing .gitignore only)")
|
|
16
|
+
opts.on("--only=CHECKS", "Comma-separated check IDs to run")
|
|
17
|
+
opts.on("--format=FORMAT", "Output format: text (default) or json")
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def initialize(global_options:, command_options:, args:)
|
|
22
|
+
@global_options = global_options
|
|
23
|
+
@command_options = command_options
|
|
24
|
+
@args = args
|
|
25
|
+
@ui = Kdep::UI.new(color: false)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def execute
|
|
29
|
+
deploy_name = @args[0]
|
|
30
|
+
discovery = Kdep::Discovery.new
|
|
31
|
+
kdep_dir = discovery.find_kdep_dir
|
|
32
|
+
|
|
33
|
+
unless kdep_dir
|
|
34
|
+
@ui.error("No kdep/ directory found")
|
|
35
|
+
exit 1
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
deploys = deploy_name ? [deploy_name] : discovery.find_deploys
|
|
39
|
+
only = (@command_options[:only] || "").split(",").map(&:strip).reject(&:empty?)
|
|
40
|
+
format = @command_options[:format] || "text"
|
|
41
|
+
|
|
42
|
+
all_results = []
|
|
43
|
+
deploys.each do |name|
|
|
44
|
+
deploy_dir = File.join(kdep_dir, name)
|
|
45
|
+
unless File.directory?(deploy_dir)
|
|
46
|
+
@ui.error("Deploy not found: #{name}")
|
|
47
|
+
exit 1
|
|
48
|
+
end
|
|
49
|
+
Kdep::Doctor::CHECKS.each do |check_class|
|
|
50
|
+
id = check_class.const_get(:ID)
|
|
51
|
+
next if !only.empty? && !only.include?(id)
|
|
52
|
+
result = check_class.new(deploy_dir).run
|
|
53
|
+
all_results << { deploy: name, result: result }
|
|
54
|
+
apply_fix(check_class, deploy_dir, result) if @command_options[:fix]
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
if format == "json"
|
|
59
|
+
puts JSON.pretty_generate(all_results.map { |r| r[:result].to_h.merge(deploy: r[:deploy]) })
|
|
60
|
+
else
|
|
61
|
+
render_text(all_results)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
exit 1 if all_results.any? { |r| r[:result].severity == :error }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def render_text(entries)
|
|
70
|
+
entries.each do |entry|
|
|
71
|
+
r = entry[:result]
|
|
72
|
+
prefix = case r.severity
|
|
73
|
+
when :ok then "OK "
|
|
74
|
+
when :info then "INFO "
|
|
75
|
+
when :warn then "WARN "
|
|
76
|
+
when :error then "ERR "
|
|
77
|
+
else r.severity.to_s.upcase.ljust(5)
|
|
78
|
+
end
|
|
79
|
+
line = "#{prefix} [#{entry[:deploy]}] #{r.id}"
|
|
80
|
+
line += ": #{r.message}" if r.message
|
|
81
|
+
puts line
|
|
82
|
+
puts " -> #{r.hint}" if r.hint
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def apply_fix(check_class, deploy_dir, result)
|
|
87
|
+
return unless result.severity != :ok
|
|
88
|
+
return unless check_class == Kdep::Doctor::CheckGitignoreCanonical
|
|
89
|
+
path = File.join(deploy_dir, ".gitignore")
|
|
90
|
+
return if File.exist?(path)
|
|
91
|
+
FileUtils.cp(
|
|
92
|
+
File.join(Kdep.templates_dir, "init", "gitignore"),
|
|
93
|
+
path
|
|
94
|
+
)
|
|
95
|
+
@ui.file_written(path)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -57,6 +57,28 @@ module Kdep
|
|
|
57
57
|
File.write(File.join(target_dir, "app.yml"), YAML.dump(kdep_config))
|
|
58
58
|
@ui.file_written("kdep/#{name}/app.yml")
|
|
59
59
|
|
|
60
|
+
# Copy canonical .gitignore so the migrated deploy stops
|
|
61
|
+
# at the same ignores as 'kdep init' uses (no state.yml!).
|
|
62
|
+
FileUtils.cp(
|
|
63
|
+
File.join(Kdep.templates_dir, "init", "gitignore"),
|
|
64
|
+
File.join(target_dir, ".gitignore")
|
|
65
|
+
)
|
|
66
|
+
@ui.file_written("kdep/#{name}/.gitignore")
|
|
67
|
+
|
|
68
|
+
# Seed state.yml from the live deploy's image tag so the
|
|
69
|
+
# first 'kdep bump' post-migration produces the NEXT tag,
|
|
70
|
+
# not 0.1 (which would regress prod).
|
|
71
|
+
if (tag = old.extracted_tag)
|
|
72
|
+
Kdep::State.write(target_dir, tag: tag)
|
|
73
|
+
@ui.file_written("kdep/#{name}/state.yml")
|
|
74
|
+
@ui.info("Seeded state.yml from live deploy tag: #{tag}")
|
|
75
|
+
unless Kdep::State.parseable_tag?(tag)
|
|
76
|
+
@ui.warn("Tag #{tag} is not kdep's major.minor format. Verify state.yml before bumping; next bump may produce 0.1.")
|
|
77
|
+
end
|
|
78
|
+
else
|
|
79
|
+
@ui.warn("Could not extract tag from old deploy (latest/missing). Next 'kdep bump' will prompt to seed state.")
|
|
80
|
+
end
|
|
81
|
+
|
|
60
82
|
# Extract and write env vars from ConfigMap
|
|
61
83
|
env = old.extract_env
|
|
62
84
|
unless env.empty?
|
data/lib/kdep/commands/render.rb
CHANGED
|
@@ -40,6 +40,12 @@ module Kdep
|
|
|
40
40
|
# Load config
|
|
41
41
|
config = Kdep::Config.new(deploy_dir, env).load
|
|
42
42
|
|
|
43
|
+
if (state_tag = Kdep::State.tag(deploy_dir))
|
|
44
|
+
config["tag"] = state_tag
|
|
45
|
+
else
|
|
46
|
+
@ui.warn("No state.yml in #{deploy_dir} -- rendering with preset default tag. Run 'kdep bump' to establish state.")
|
|
47
|
+
end
|
|
48
|
+
|
|
43
49
|
# Load preset resources
|
|
44
50
|
preset = Kdep::Preset.new(config["preset"], deploy_dir)
|
|
45
51
|
resources = preset.resources
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
require "open3"
|
|
2
|
+
require "kdep/state"
|
|
3
|
+
|
|
4
|
+
module Kdep
|
|
5
|
+
module Doctor
|
|
6
|
+
class CheckAncestorIgnoresState < Check
|
|
7
|
+
ID = "ancestor_ignores_state".freeze
|
|
8
|
+
|
|
9
|
+
def run
|
|
10
|
+
state_path = File.join(deploy_dir, Kdep::State::FILENAME)
|
|
11
|
+
_, status = Open3.capture2("git", "check-ignore", "-v", state_path)
|
|
12
|
+
if status.success?
|
|
13
|
+
Result.new(
|
|
14
|
+
id: ID, severity: :error,
|
|
15
|
+
message: "An ancestor .gitignore is ignoring #{state_path}",
|
|
16
|
+
hint: "Run 'git check-ignore -v #{state_path}' to find which file, then remove the pattern."
|
|
17
|
+
)
|
|
18
|
+
else
|
|
19
|
+
Result.ok(ID)
|
|
20
|
+
end
|
|
21
|
+
rescue Errno::ENOENT
|
|
22
|
+
Result.ok(ID)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module Kdep
|
|
2
|
+
module Doctor
|
|
3
|
+
class CheckGitignoreCanonical < Check
|
|
4
|
+
ID = "gitignore_canonical".freeze
|
|
5
|
+
|
|
6
|
+
def run
|
|
7
|
+
path = File.join(deploy_dir, ".gitignore")
|
|
8
|
+
template_path = File.join(Kdep.templates_dir, "init", "gitignore")
|
|
9
|
+
|
|
10
|
+
unless File.exist?(path)
|
|
11
|
+
return Result.new(
|
|
12
|
+
id: ID, severity: :info,
|
|
13
|
+
message: "#{deploy_dir}/.gitignore missing",
|
|
14
|
+
hint: "Run 'kdep doctor --fix' to create it from the canonical template."
|
|
15
|
+
)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
if File.read(path) == File.read(template_path)
|
|
19
|
+
Result.ok(ID)
|
|
20
|
+
else
|
|
21
|
+
Result.new(
|
|
22
|
+
id: ID, severity: :info,
|
|
23
|
+
message: "#{path} differs from canonical template",
|
|
24
|
+
hint: "Review differences; add new entries under the kdep-generated comment."
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
require "open3"
|
|
2
|
+
require "kdep/state"
|
|
3
|
+
|
|
4
|
+
module Kdep
|
|
5
|
+
module Doctor
|
|
6
|
+
class CheckStateGitignored < Check
|
|
7
|
+
ID = "state_gitignored".freeze
|
|
8
|
+
|
|
9
|
+
def run
|
|
10
|
+
state_path = File.join(deploy_dir, Kdep::State::FILENAME)
|
|
11
|
+
_, status = Open3.capture2("git", "check-ignore", "-q", state_path)
|
|
12
|
+
if status.success?
|
|
13
|
+
Result.new(
|
|
14
|
+
id: ID, severity: :error,
|
|
15
|
+
message: "#{state_path} is gitignored",
|
|
16
|
+
hint: "Remove 'state.yml' from .gitignore and 'git add' the file so fresh clones see the last-deployed tag."
|
|
17
|
+
)
|
|
18
|
+
else
|
|
19
|
+
Result.ok(ID)
|
|
20
|
+
end
|
|
21
|
+
rescue Errno::ENOENT
|
|
22
|
+
Result.ok(ID)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require "kdep/state"
|
|
2
|
+
|
|
3
|
+
module Kdep
|
|
4
|
+
module Doctor
|
|
5
|
+
class CheckStateParseable < Check
|
|
6
|
+
ID = "state_parseable".freeze
|
|
7
|
+
|
|
8
|
+
def run
|
|
9
|
+
return Result.ok(ID) unless Kdep::State.exists?(deploy_dir)
|
|
10
|
+
begin
|
|
11
|
+
Kdep::State.load(deploy_dir)
|
|
12
|
+
Result.ok(ID)
|
|
13
|
+
rescue Kdep::State::Error => e
|
|
14
|
+
Result.new(id: ID, severity: :error, message: e.message)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
require "kdep/state"
|
|
2
|
+
|
|
3
|
+
module Kdep
|
|
4
|
+
module Doctor
|
|
5
|
+
class CheckStatePresent < Check
|
|
6
|
+
ID = "state_present".freeze
|
|
7
|
+
|
|
8
|
+
def run
|
|
9
|
+
if Kdep::State.exists?(deploy_dir)
|
|
10
|
+
Result.ok(ID)
|
|
11
|
+
else
|
|
12
|
+
Result.new(
|
|
13
|
+
id: ID, severity: :warn,
|
|
14
|
+
message: "state.yml missing in #{deploy_dir}",
|
|
15
|
+
hint: "Run 'kdep bump --init-state' to seed, or commit a state.yml manually."
|
|
16
|
+
)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require "kdep/state"
|
|
2
|
+
|
|
3
|
+
module Kdep
|
|
4
|
+
module Doctor
|
|
5
|
+
class CheckStateTagFormat < Check
|
|
6
|
+
ID = "state_tag_format".freeze
|
|
7
|
+
|
|
8
|
+
def run
|
|
9
|
+
return Result.ok(ID) unless Kdep::State.exists?(deploy_dir)
|
|
10
|
+
tag = Kdep::State.tag(deploy_dir)
|
|
11
|
+
return Result.ok(ID) if Kdep::State.parseable_tag?(tag)
|
|
12
|
+
Result.new(
|
|
13
|
+
id: ID, severity: :warn,
|
|
14
|
+
message: "state.yml tag #{tag.inspect} is not kdep's major.minor format",
|
|
15
|
+
hint: "Next 'kdep bump' will fall back to 0.1 for this deploy."
|
|
16
|
+
)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module Kdep
|
|
2
|
+
module Doctor
|
|
3
|
+
class Result
|
|
4
|
+
attr_reader :id, :severity, :message, :hint
|
|
5
|
+
|
|
6
|
+
def initialize(id:, severity:, message: nil, hint: nil)
|
|
7
|
+
@id = id
|
|
8
|
+
@severity = severity
|
|
9
|
+
@message = message
|
|
10
|
+
@hint = hint
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def ok?
|
|
14
|
+
@severity == :ok
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.ok(id)
|
|
18
|
+
new(id: id, severity: :ok)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def to_h
|
|
22
|
+
{ id: id, severity: severity, message: message, hint: hint }
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
data/lib/kdep/doctor.rb
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
require "kdep/doctor/result"
|
|
2
|
+
require "kdep/doctor/check"
|
|
3
|
+
require "kdep/doctor/check_state_present"
|
|
4
|
+
require "kdep/doctor/check_state_parseable"
|
|
5
|
+
require "kdep/doctor/check_state_tag_format"
|
|
6
|
+
require "kdep/doctor/check_state_gitignored"
|
|
7
|
+
require "kdep/doctor/check_gitignore_canonical"
|
|
8
|
+
require "kdep/doctor/check_ancestor_ignores_state"
|
|
9
|
+
|
|
10
|
+
module Kdep
|
|
11
|
+
module Doctor
|
|
12
|
+
CHECKS = [
|
|
13
|
+
CheckStatePresent,
|
|
14
|
+
CheckStateParseable,
|
|
15
|
+
CheckStateTagFormat,
|
|
16
|
+
CheckStateGitignored,
|
|
17
|
+
CheckGitignoreCanonical,
|
|
18
|
+
CheckAncestorIgnoresState,
|
|
19
|
+
].freeze
|
|
20
|
+
end
|
|
21
|
+
end
|
data/lib/kdep/old_format.rb
CHANGED
|
@@ -258,8 +258,35 @@ module Kdep
|
|
|
258
258
|
env
|
|
259
259
|
end
|
|
260
260
|
|
|
261
|
+
# Extract the image tag from the live deploy (e.g., "0.44" from
|
|
262
|
+
# "ghcr.io/org/app:0.44"). Returns nil if the tag is "latest",
|
|
263
|
+
# empty, or absent. On multi-container deployments, returns the
|
|
264
|
+
# first usable tag found.
|
|
265
|
+
def extracted_tag
|
|
266
|
+
deployed_images.each do |img|
|
|
267
|
+
parts = img.to_s.split(":", 2)
|
|
268
|
+
next if parts.length < 2
|
|
269
|
+
tag = parts[1]
|
|
270
|
+
next if tag.nil? || tag.empty? || tag == "latest"
|
|
271
|
+
return tag
|
|
272
|
+
end
|
|
273
|
+
nil
|
|
274
|
+
end
|
|
275
|
+
|
|
261
276
|
private
|
|
262
277
|
|
|
278
|
+
def deployed_images
|
|
279
|
+
return @deployed_images if defined?(@deployed_images)
|
|
280
|
+
@deployed_images = []
|
|
281
|
+
each_manifest do |doc|
|
|
282
|
+
containers = doc.dig("spec", "template", "spec", "containers") || []
|
|
283
|
+
containers.each do |c|
|
|
284
|
+
@deployed_images << c["image"] if c["image"]
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
@deployed_images
|
|
288
|
+
end
|
|
289
|
+
|
|
263
290
|
def extract_from_manifests(result)
|
|
264
291
|
has_service = false
|
|
265
292
|
has_ingress = false
|
data/lib/kdep/state.rb
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
require "yaml"
|
|
2
|
+
|
|
3
|
+
module Kdep
|
|
4
|
+
module State
|
|
5
|
+
class Error < StandardError; end
|
|
6
|
+
|
|
7
|
+
FILENAME = "state.yml".freeze
|
|
8
|
+
INITIAL_TAG = "0.0".freeze
|
|
9
|
+
|
|
10
|
+
HEADER = <<~HEADER
|
|
11
|
+
# Last tag deployed by kdep bump -- do not edit by hand.
|
|
12
|
+
# This file is safe to commit. See: https://github.com/repleadfy/kdep
|
|
13
|
+
HEADER
|
|
14
|
+
|
|
15
|
+
def self.load(deploy_dir)
|
|
16
|
+
path = File.join(deploy_dir, FILENAME)
|
|
17
|
+
return nil unless File.exist?(path)
|
|
18
|
+
data = YAML.safe_load(File.read(path)) || {}
|
|
19
|
+
unless data.is_a?(Hash) && data["tag"]
|
|
20
|
+
raise Error, "#{path}: missing required key 'tag'"
|
|
21
|
+
end
|
|
22
|
+
data
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.tag(deploy_dir)
|
|
26
|
+
loaded = load(deploy_dir)
|
|
27
|
+
loaded && loaded["tag"]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.write(deploy_dir, tag:)
|
|
31
|
+
path = File.join(deploy_dir, FILENAME)
|
|
32
|
+
tmp = "#{path}.tmp"
|
|
33
|
+
content = "#{HEADER}tag: \"#{tag}\"\n"
|
|
34
|
+
File.write(tmp, content)
|
|
35
|
+
File.rename(tmp, path)
|
|
36
|
+
path
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.exists?(deploy_dir)
|
|
40
|
+
File.exist?(File.join(deploy_dir, FILENAME))
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.parseable_tag?(tag)
|
|
44
|
+
require "kdep/version_tagger"
|
|
45
|
+
!!Kdep::VersionTagger.parse(tag)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
data/lib/kdep/version.rb
CHANGED
data/lib/kdep.rb
CHANGED
|
@@ -17,6 +17,8 @@ require "kdep/docker"
|
|
|
17
17
|
require "kdep/helm"
|
|
18
18
|
require "kdep/registry"
|
|
19
19
|
require "kdep/version_tagger"
|
|
20
|
+
require "kdep/state"
|
|
21
|
+
require "kdep/doctor"
|
|
20
22
|
require "kdep/commands/render"
|
|
21
23
|
require "kdep/commands/init"
|
|
22
24
|
require "kdep/commands/eject"
|
|
@@ -34,6 +36,7 @@ require "kdep/commands/scale"
|
|
|
34
36
|
require "kdep/commands/migrate"
|
|
35
37
|
require "kdep/commands/helm_install"
|
|
36
38
|
require "kdep/commands/dashboard"
|
|
39
|
+
require "kdep/commands/doctor"
|
|
37
40
|
require "kdep/dashboard/screen"
|
|
38
41
|
require "kdep/dashboard/layout"
|
|
39
42
|
require "kdep/dashboard/panel"
|
data/templates/init/gitignore
CHANGED
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.3.
|
|
4
|
+
version: 0.3.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Leadfy
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-04-
|
|
11
|
+
date: 2026-04-22 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -70,6 +70,7 @@ files:
|
|
|
70
70
|
- lib/kdep/commands/bump.rb
|
|
71
71
|
- lib/kdep/commands/dashboard.rb
|
|
72
72
|
- lib/kdep/commands/diff.rb
|
|
73
|
+
- lib/kdep/commands/doctor.rb
|
|
73
74
|
- lib/kdep/commands/eject.rb
|
|
74
75
|
- lib/kdep/commands/helm_install.rb
|
|
75
76
|
- lib/kdep/commands/init.rb
|
|
@@ -95,12 +96,22 @@ files:
|
|
|
95
96
|
- lib/kdep/defaults.rb
|
|
96
97
|
- lib/kdep/discovery.rb
|
|
97
98
|
- lib/kdep/docker.rb
|
|
99
|
+
- lib/kdep/doctor.rb
|
|
100
|
+
- lib/kdep/doctor/check.rb
|
|
101
|
+
- lib/kdep/doctor/check_ancestor_ignores_state.rb
|
|
102
|
+
- lib/kdep/doctor/check_gitignore_canonical.rb
|
|
103
|
+
- lib/kdep/doctor/check_state_gitignored.rb
|
|
104
|
+
- lib/kdep/doctor/check_state_parseable.rb
|
|
105
|
+
- lib/kdep/doctor/check_state_present.rb
|
|
106
|
+
- lib/kdep/doctor/check_state_tag_format.rb
|
|
107
|
+
- lib/kdep/doctor/result.rb
|
|
98
108
|
- lib/kdep/helm.rb
|
|
99
109
|
- lib/kdep/kubectl.rb
|
|
100
110
|
- lib/kdep/old_format.rb
|
|
101
111
|
- lib/kdep/preset.rb
|
|
102
112
|
- lib/kdep/registry.rb
|
|
103
113
|
- lib/kdep/renderer.rb
|
|
114
|
+
- lib/kdep/state.rb
|
|
104
115
|
- lib/kdep/template_context.rb
|
|
105
116
|
- lib/kdep/ui.rb
|
|
106
117
|
- lib/kdep/update_check.rb
|