kdep 0.3.4 → 0.3.6
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 +56 -10
- 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_gitignore_canonical.rb +30 -0
- data/lib/kdep/doctor/check_state_gitignored.rb +28 -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 +19 -0
- data/lib/kdep/old_format.rb +33 -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 +12 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8194d84a25eda1737a9bdab8891db08ec092cfc71c86764fac24ff044b977626
|
|
4
|
+
data.tar.gz: d247a4155dea0105240416bf2af475d5a7e22b71917467f230bc13123d4c79b5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e3fb390e4470e4daf795717de1cac5c32142eb208dc58e529eabccba079bfeb2dd737ee74ad92ea92a675298911091dbd52ea007f9c38cbc8e624ea8d0bbf13d
|
|
7
|
+
data.tar.gz: 36e1bc46602c74aa2027e0c4377b951d0602ce24ca05470f2f0a989fe6266288da5017f3f45e91d3923efe1e884830709718a7b75aa5db8fa3c3408e20f8e2a4
|
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
|
|
|
@@ -43,7 +44,24 @@ module Kdep
|
|
|
43
44
|
|
|
44
45
|
config = Kdep::Config.new(deploy_dir, env).load
|
|
45
46
|
|
|
46
|
-
# Step 2:
|
|
47
|
+
# Step 2: Local config sanity — read state.yml BEFORE touching the
|
|
48
|
+
# cluster. A missing/corrupt state.yml is a local config error and
|
|
49
|
+
# should fail fast without waking kubectl.
|
|
50
|
+
begin
|
|
51
|
+
current_tag = Kdep::State.tag(deploy_dir) || handle_missing_state(deploy_dir)
|
|
52
|
+
rescue Kdep::State::Error => e
|
|
53
|
+
@ui.error(e.message)
|
|
54
|
+
exit 1
|
|
55
|
+
end
|
|
56
|
+
# to_fix.md #3 — warn if someone has gitignored state.yml
|
|
57
|
+
require "kdep/doctor/check_state_gitignored"
|
|
58
|
+
gitignore_result = Kdep::Doctor::CheckStateGitignored.new(deploy_dir).run
|
|
59
|
+
if gitignore_result.severity == :error
|
|
60
|
+
@ui.warn(gitignore_result.message)
|
|
61
|
+
@ui.warn(gitignore_result.hint) if gitignore_result.hint
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Step 3: Context guard (bump touches the cluster)
|
|
47
65
|
begin
|
|
48
66
|
Kdep::ContextGuard.new(config["context"]).validate!
|
|
49
67
|
rescue Kdep::Kubectl::Error => e
|
|
@@ -63,14 +81,7 @@ module Kdep
|
|
|
63
81
|
repo_root = File.dirname(kdep_dir)
|
|
64
82
|
|
|
65
83
|
begin
|
|
66
|
-
# Step
|
|
67
|
-
state_path = File.join(deploy_dir, "state.yml")
|
|
68
|
-
if File.exist?(state_path)
|
|
69
|
-
state = YAML.safe_load(File.read(state_path)) || {}
|
|
70
|
-
current_tag = state["tag"] || "0.0"
|
|
71
|
-
else
|
|
72
|
-
current_tag = "0.0"
|
|
73
|
-
end
|
|
84
|
+
# Step 4: Increment minor version.
|
|
74
85
|
parsed = Kdep::VersionTagger.parse(current_tag)
|
|
75
86
|
next_version = parsed ? "#{parsed[0]}.#{parsed[1] + 1}" : "0.1"
|
|
76
87
|
@ui.info("#{current_tag} -> #{next_version}")
|
|
@@ -159,7 +170,7 @@ module Kdep
|
|
|
159
170
|
@ui.info("#{applied} files applied, 0 errors")
|
|
160
171
|
|
|
161
172
|
# Save new tag to state.yml
|
|
162
|
-
|
|
173
|
+
Kdep::State.write(deploy_dir, tag: next_version)
|
|
163
174
|
|
|
164
175
|
# Launch TUI dashboard for live monitoring
|
|
165
176
|
unless @command_options[:"no-dashboard"]
|
|
@@ -185,6 +196,41 @@ module Kdep
|
|
|
185
196
|
|
|
186
197
|
private
|
|
187
198
|
|
|
199
|
+
def handle_missing_state(deploy_dir)
|
|
200
|
+
if @command_options[:"init-state"]
|
|
201
|
+
seed_initial_state(deploy_dir, reason: :flag)
|
|
202
|
+
elsif $stdin.tty?
|
|
203
|
+
prompt_for_init(deploy_dir)
|
|
204
|
+
else
|
|
205
|
+
@ui.error(
|
|
206
|
+
"state.yml not found in #{deploy_dir}.\n" \
|
|
207
|
+
"Re-run with --init-state to seed at \"#{Kdep::State::INITIAL_TAG}\" " \
|
|
208
|
+
"(first deploy will be tag \"0.1\"), or commit a state.yml manually."
|
|
209
|
+
)
|
|
210
|
+
exit 1
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def prompt_for_init(deploy_dir)
|
|
215
|
+
@ui.warn("No state.yml found in #{deploy_dir}.")
|
|
216
|
+
@ui.warn("If prod is already running a higher tag, this will regress it.")
|
|
217
|
+
print %Q(Seed state.yml at "#{Kdep::State::INITIAL_TAG}" and proceed? [y/N] )
|
|
218
|
+
answer = $stdin.gets&.strip&.downcase
|
|
219
|
+
unless %w[y yes].include?(answer)
|
|
220
|
+
@ui.info("Aborted.")
|
|
221
|
+
exit 0
|
|
222
|
+
end
|
|
223
|
+
seed_initial_state(deploy_dir, reason: :prompt)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def seed_initial_state(deploy_dir, reason:)
|
|
227
|
+
tag = Kdep::State::INITIAL_TAG
|
|
228
|
+
Kdep::State.write(deploy_dir, tag: tag)
|
|
229
|
+
source = reason == :flag ? "--init-state flag" : "user confirmation"
|
|
230
|
+
@ui.info("Seeded state.yml at #{tag} (#{source}). First deploy will be tag 0.1.")
|
|
231
|
+
tag
|
|
232
|
+
end
|
|
233
|
+
|
|
188
234
|
def resolve_deploy_dir(kdep_dir, deploy_name, discovery)
|
|
189
235
|
if deploy_name
|
|
190
236
|
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,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,28 @@
|
|
|
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
|
+
# capture3 swallows stderr so "fatal: not a git repository" noise
|
|
12
|
+
# never reaches the user when the caller runs outside a repo.
|
|
13
|
+
_, _, status = Open3.capture3("git", "check-ignore", "-q", state_path)
|
|
14
|
+
if status.success?
|
|
15
|
+
Result.new(
|
|
16
|
+
id: ID, severity: :error,
|
|
17
|
+
message: "#{state_path} is gitignored",
|
|
18
|
+
hint: "Remove 'state.yml' from any .gitignore (local or ancestor) and 'git add' the file so fresh clones see the last-deployed tag."
|
|
19
|
+
)
|
|
20
|
+
else
|
|
21
|
+
Result.ok(ID)
|
|
22
|
+
end
|
|
23
|
+
rescue Errno::ENOENT
|
|
24
|
+
Result.ok(ID)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
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,19 @@
|
|
|
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
|
+
|
|
9
|
+
module Kdep
|
|
10
|
+
module Doctor
|
|
11
|
+
CHECKS = [
|
|
12
|
+
CheckStatePresent,
|
|
13
|
+
CheckStateParseable,
|
|
14
|
+
CheckStateTagFormat,
|
|
15
|
+
CheckStateGitignored,
|
|
16
|
+
CheckGitignoreCanonical,
|
|
17
|
+
].freeze
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/kdep/old_format.rb
CHANGED
|
@@ -258,8 +258,41 @@ 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. Uses rpartition so registry URLs with
|
|
265
|
+
# explicit ports (host:5000/org/app:0.44) aren't misparsed.
|
|
266
|
+
def extracted_tag
|
|
267
|
+
deployed_images.each do |img|
|
|
268
|
+
before, sep, after = img.to_s.rpartition(":")
|
|
269
|
+
# rpartition returns ["", "", img] when no ":" exists
|
|
270
|
+
next if sep.empty?
|
|
271
|
+
# A host:port with no tag (e.g. "host:5000/img") has sep but the
|
|
272
|
+
# "tag" part contains a "/" — not a real tag.
|
|
273
|
+
next if after.empty? || after.include?("/")
|
|
274
|
+
next if after == "latest"
|
|
275
|
+
# before will include any host:port prefix intact
|
|
276
|
+
_ = before
|
|
277
|
+
return after
|
|
278
|
+
end
|
|
279
|
+
nil
|
|
280
|
+
end
|
|
281
|
+
|
|
261
282
|
private
|
|
262
283
|
|
|
284
|
+
def deployed_images
|
|
285
|
+
return @deployed_images if defined?(@deployed_images)
|
|
286
|
+
@deployed_images = []
|
|
287
|
+
each_manifest do |doc|
|
|
288
|
+
containers = doc.dig("spec", "template", "spec", "containers") || []
|
|
289
|
+
containers.each do |c|
|
|
290
|
+
@deployed_images << c["image"] if c["image"]
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
@deployed_images
|
|
294
|
+
end
|
|
295
|
+
|
|
263
296
|
def extract_from_manifests(result)
|
|
264
297
|
has_service = false
|
|
265
298
|
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.6
|
|
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,21 @@ 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_gitignore_canonical.rb
|
|
102
|
+
- lib/kdep/doctor/check_state_gitignored.rb
|
|
103
|
+
- lib/kdep/doctor/check_state_parseable.rb
|
|
104
|
+
- lib/kdep/doctor/check_state_present.rb
|
|
105
|
+
- lib/kdep/doctor/check_state_tag_format.rb
|
|
106
|
+
- lib/kdep/doctor/result.rb
|
|
98
107
|
- lib/kdep/helm.rb
|
|
99
108
|
- lib/kdep/kubectl.rb
|
|
100
109
|
- lib/kdep/old_format.rb
|
|
101
110
|
- lib/kdep/preset.rb
|
|
102
111
|
- lib/kdep/registry.rb
|
|
103
112
|
- lib/kdep/renderer.rb
|
|
113
|
+
- lib/kdep/state.rb
|
|
104
114
|
- lib/kdep/template_context.rb
|
|
105
115
|
- lib/kdep/ui.rb
|
|
106
116
|
- lib/kdep/update_check.rb
|