kdep 0.4.9 → 0.6.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 92fded408b48545949833a1473e7b905dc7ce0b8b7c58415ad77b532f0205227
4
- data.tar.gz: 2af6988f918a2ba676a4a2d5780a950581f0e93596e772d4e04f3ca65b9fdba0
3
+ metadata.gz: 0f5fd6a62da416ea9cdccd6d6370050509846001fec09b066b63ac8c79918106
4
+ data.tar.gz: a792a173492ad869938ad623743af357e1a1ee6a8d7c85d2f591bcde7511d378
5
5
  SHA512:
6
- metadata.gz: 8f1d30adf7eb1fe069115dc5cea6458f1e83ef4ef3025fb32912b9fee74f0b68582606fa71aa2e0b306e3ed80a5dda2dacec74760b3e9eb02884e794c18ec75c
7
- data.tar.gz: d6b64df4bbe169c1f80c2169f6c3378d6ab62ce57096f6aa0d4f5cf8f3251f25cde1ffbfce1e490028a60bb9872f1848fc9bef016cc34aed23160e4e0144b99b
6
+ metadata.gz: 4640ab8f5039e3e5b4a9135a11afe42738659bf41376f38f0db463f13d1086317dc9c9631b2fb378157f86a1a255d7a29a58cdf816fcc0fe1d045c6b537d1d4a
7
+ data.tar.gz: bae5c85f87d19d2320cccbc169a0b08bb5f4b56b565dd81a1002c904c5e5a9d4ac570c053fc88ee6163534051809b72931a0395ff4cc0882127c32ad7126a068
@@ -71,6 +71,7 @@ module Kdep
71
71
  writer = Kdep::Writer.new(output_dir)
72
72
  writer.clean
73
73
  renderer = Kdep::Renderer.new(config, deploy_dir, env: env)
74
+ Kdep::Renderer.preflight_secrets!(@ui, renderer, resources)
74
75
  validator = Kdep::Validator.new
75
76
  validation_errors = []
76
77
 
@@ -78,6 +79,10 @@ module Kdep
78
79
  index = idx + 1
79
80
  begin
80
81
  content = renderer.render_resource(resource_name)
82
+ rescue Kdep::Renderer::MissingSecretsError => e
83
+ @ui.error("#{resource_name}: #{e.message}")
84
+ @ui.error("nada foi aplicado")
85
+ exit 1
81
86
  rescue => e
82
87
  @ui.error("#{resource_name}: #{e.message}")
83
88
  next
@@ -3,6 +3,10 @@ require "optparse"
3
3
  module Kdep
4
4
  module Commands
5
5
  class Bump
6
+ DEFAULT_ROLLOUT_TIMEOUT = "5m".freeze
7
+ # kubectl duration: 90s, 5m, 1h30m
8
+ DURATION_FORMAT = /\A(\d+(ms|s|m|h))+\z/
9
+
6
10
  def self.option_parser
7
11
  OptionParser.new do |opts|
8
12
  opts.banner = "Usage: kdep bump [deploy] [env]"
@@ -12,6 +16,8 @@ module Kdep
12
16
  opts.on("--skip-apply", "Build and push only, skip render+apply")
13
17
  opts.on("--dry-run", "Run full pipeline but skip kubectl apply")
14
18
  opts.on("--no-dashboard", "Skip TUI dashboard after deploy")
19
+ opts.on("--wait", "Wait for the rollout instead of the dashboard; exit 1 if it fails (for scripts/CI)")
20
+ opts.on("--timeout=DURATION", "Rollout wait timeout with --wait, e.g. 90s, 10m (default: #{DEFAULT_ROLLOUT_TIMEOUT}; 0 = forever)")
15
21
  opts.on("--platform=PLATFORM", "Target platform (e.g., linux/amd64)")
16
22
  opts.on("--init-state", "Seed state.yml at 0.0 when missing (non-interactive)")
17
23
  opts.on("--no-check", "Skip preflight check: validators")
@@ -29,6 +35,14 @@ module Kdep
29
35
  deploy_name = @args[0]
30
36
  env = @args[1]
31
37
 
38
+ # A malformed duration would only surface in `kubectl rollout status`,
39
+ # after build, push and apply -- reporting a good deploy as failed.
40
+ timeout = @command_options[:timeout]
41
+ if timeout && !(timeout == "0" || timeout.match?(DURATION_FORMAT))
42
+ @ui.error("--timeout=#{timeout}: expected a duration like 90s, 5m or 1h30m")
43
+ exit 1
44
+ end
45
+
32
46
  # Step 1: Discovery + config
33
47
  discovery = Kdep::Discovery.new
34
48
  kdep_dir = discovery.find_kdep_dir
@@ -58,6 +72,7 @@ module Kdep
58
72
  # (context guard, rollout tracking, etc.).
59
73
  preset = config["preset"]
60
74
  if preset == "helm"
75
+ @ui.warn("--wait is not supported for helm presets; the rollout is not awaited") if @command_options[:wait]
61
76
  run_helm_pipeline(deploy_dir, env)
62
77
  return
63
78
  elsif preset == "custom" || config["build"] == false
@@ -106,6 +121,12 @@ module Kdep
106
121
  registry_url = config["registry"]
107
122
  repo_root = File.dirname(kdep_dir)
108
123
 
124
+ # O Secret é conferido ANTES do docker build/push: falha aqui não empurra imagem nem aplica nada.
125
+ unless @command_options[:"skip-apply"]
126
+ Kdep::Renderer.preflight_secrets!(@ui, Kdep::Renderer.new(config, deploy_dir, env: env),
127
+ Kdep::Preset.new(config["preset"], deploy_dir).resources)
128
+ end
129
+
109
130
  begin
110
131
  # Step 4: Increment minor version.
111
132
  parsed = Kdep::VersionTagger.parse(current_tag)
@@ -198,8 +219,13 @@ module Kdep
198
219
  # Save new tag to state.yml
199
220
  Kdep::State.write(deploy_dir, tag: next_version)
200
221
 
201
- # Launch TUI dashboard for live monitoring
202
- unless @command_options[:"no-dashboard"]
222
+ # Follow the rollout: --wait blocks on it (scripts/CI), otherwise
223
+ # the TUI dashboard when there is a terminal to draw it on.
224
+ if @command_options[:wait]
225
+ wait_for_rollout(config, deploy_dir, namespace)
226
+ elsif @command_options[:"no-dashboard"]
227
+ # explicitly opted out
228
+ elsif Kdep::Dashboard.interactive?
203
229
  dashboard = Kdep::Dashboard.new(
204
230
  "name" => config["name"],
205
231
  "namespace" => namespace,
@@ -207,10 +233,16 @@ module Kdep
207
233
  "image" => image
208
234
  )
209
235
  dashboard.run
236
+ else
237
+ @ui.warn("rollout not awaited -- pass --wait to block until it completes (exit 1 if it fails)")
210
238
  end
211
239
  end
212
240
  end
213
241
 
242
+ rescue Kdep::Renderer::MissingSecretsError => e
243
+ @ui.error(e.message)
244
+ @ui.error("nada foi aplicado (a imagem pode já ter sido enviada ao registry)")
245
+ exit 1
214
246
  rescue Kdep::Docker::Error => e
215
247
  @ui.error(e.message)
216
248
  exit 1
@@ -222,6 +254,29 @@ module Kdep
222
254
 
223
255
  private
224
256
 
257
+ def wait_for_rollout(config, deploy_dir, namespace)
258
+ deploy = File.basename(deploy_dir)
259
+ resources = Kdep::Workload.preset_resources(config["preset"], deploy_dir)
260
+ kinds = Kdep::Workload.rollout_kinds(resources)
261
+
262
+ if kinds.empty?
263
+ what = Kdep::Workload.batch_kind(resources) || "no deployment/statefulset/daemonset"
264
+ @ui.warn("--wait: preset '#{config["preset"]}' declares #{what}; no rollout to wait for")
265
+ return
266
+ end
267
+
268
+ timeout = @command_options[:timeout] || DEFAULT_ROLLOUT_TIMEOUT
269
+ kinds.each do |kind|
270
+ name = Kdep::Workload.name_for(kind, config)
271
+ @ui.info("waiting for #{kind}/#{name} rollout (timeout #{timeout})...")
272
+ next if Kdep::Kubectl.rollout_status(kind, name, namespace: namespace, timeout: timeout)
273
+
274
+ @ui.error("rollout of #{kind}/#{name} did not complete within #{timeout}. " \
275
+ "Check `kdep status #{deploy}` and `kdep log #{deploy}`.")
276
+ exit 1
277
+ end
278
+ end
279
+
225
280
  # Feature E: helm-preset bump = helm-install pipeline. Preflight
226
281
  # already ran; context guard runs inside HelmInstall; rollout flush
227
282
  # and configmap overlays are internal to it.
@@ -256,6 +311,7 @@ module Kdep
256
311
  writer.clean
257
312
  renderer = Kdep::Renderer.new(config, deploy_dir, env: env)
258
313
  preset_resources = Kdep::Preset.new(config["preset"], deploy_dir).resources
314
+ Kdep::Renderer.preflight_secrets!(@ui, renderer, preset_resources)
259
315
 
260
316
  preset_resources.each_with_index do |resource, idx|
261
317
  content = renderer.render_resource(resource)
@@ -268,6 +324,8 @@ module Kdep
268
324
  Kdep::Kubectl.apply(file_path, namespace: namespace)
269
325
  @ui.info("applied: #{File.basename(file_path)}")
270
326
  end
327
+
328
+ wait_for_rollout(config, deploy_dir, namespace) if @command_options[:wait]
271
329
  end
272
330
  end
273
331
 
@@ -39,6 +39,13 @@ module Kdep
39
39
  # Load config (nil env -- dashboard monitors live state)
40
40
  config = Kdep::Config.new(deploy_dir, nil).load
41
41
 
42
+ # Checked before any cluster access: nothing to show without a terminal
43
+ unless Kdep::Dashboard.interactive?
44
+ @ui.error("kdep dashboard needs an interactive terminal (stdin and stdout). " \
45
+ "From a script, use `kdep status` or `kdep log`.")
46
+ exit 1
47
+ end
48
+
42
49
  # Validate context before any cluster operation
43
50
  begin
44
51
  Kdep::ContextGuard.new(config["context"]).validate!
@@ -73,6 +73,10 @@ module Kdep
73
73
  exit 1
74
74
  end
75
75
 
76
+ # O Secret extra (preset helm: secret + ingress) é conferido ANTES do helm upgrade.
77
+ Kdep::Renderer.preflight_secrets!(@ui, Kdep::Renderer.new(config, @deploy_dir, env: @env),
78
+ Kdep::Preset.new("helm", @deploy_dir).resources)
79
+
76
80
  # Load secrets for --set interpolation
77
81
  @secrets = load_secrets(@deploy_dir)
78
82
 
@@ -277,6 +281,9 @@ module Kdep
277
281
  index = next_index
278
282
  begin
279
283
  content = renderer.render_resource(resource_name)
284
+ rescue Kdep::Renderer::MissingSecretsError => e
285
+ @ui.error("#{resource_name}: #{e.message}")
286
+ exit 1
280
287
  rescue => e
281
288
  render_errors << "#{resource_name}: #{e.message}"
282
289
  next
@@ -3,9 +3,6 @@ require "optparse"
3
3
  module Kdep
4
4
  module Commands
5
5
  class Log
6
- # kubectl refuses to follow more than --max-log-requests streams (default 5)
7
- MIN_LOG_REQUESTS = 5
8
-
9
6
  def self.option_parser
10
7
  OptionParser.new do |opts|
11
8
  opts.banner = "Usage: kdep log [deploy]"
@@ -64,39 +61,26 @@ module Kdep
64
61
 
65
62
  private
66
63
 
67
- # kubectl logs -l streams every pod the selector matches, whatever its
68
- # phase (Completed leftovers, Pending surge pods), so the stream limit
69
- # must count all of them -- only the guard looks at Running.
70
64
  def count_pods(namespace, app_name)
71
- data = Kdep::Kubectl.run_json(
72
- "get", "pods",
73
- "-n", namespace,
74
- "-l", "app=#{app_name}"
75
- )
76
- pods = data["items"] || []
65
+ pod_count, running = Kdep::PodLogs.count(namespace, app_name)
77
66
 
78
- if pods.none? { |pod| pod.dig("status", "phase") == "Running" }
67
+ if running.zero?
79
68
  @ui.error("No running pods found for #{app_name} in #{namespace}")
80
69
  exit 1
81
70
  end
82
71
 
83
- pods.length
72
+ pod_count
84
73
  end
85
74
 
86
75
  def stream_logs(namespace, app_name, pod_count)
87
- follow = @command_options.fetch(:follow, true)
88
- tail = @command_options.fetch(:tail, 100)
89
- container = @command_options[:container]
90
-
91
- args = ["kubectl", "logs", "-n", namespace, "-l", "app=#{app_name}", "--prefix", "--ignore-errors"]
92
- args << "--max-log-requests=#{[pod_count, MIN_LOG_REQUESTS].max}"
93
- args << "-f" if follow
94
- args << "--tail=#{tail}"
95
-
96
- if container
97
- args << "-c"
98
- args << container
99
- end
76
+ args = Kdep::PodLogs.kubectl_args(
77
+ namespace: namespace,
78
+ app_name: app_name,
79
+ pod_count: pod_count,
80
+ tail: @command_options.fetch(:tail, 100),
81
+ follow: @command_options.fetch(:follow, true),
82
+ container: @command_options[:container]
83
+ )
100
84
 
101
85
  io = IO.popen(args, "r")
102
86
 
@@ -3,9 +3,6 @@ require "optparse"
3
3
  module Kdep
4
4
  module Commands
5
5
  class Restart
6
- WORKLOAD_KINDS = %w[deployment statefulset daemonset].freeze
7
- NON_RESTARTABLE_KINDS = %w[job cronjob].freeze
8
-
9
6
  def self.option_parser
10
7
  OptionParser.new do |opts|
11
8
  opts.banner = "Usage: kdep restart [deploy] [--type TYPE]"
@@ -73,10 +70,7 @@ module Kdep
73
70
 
74
71
  private
75
72
 
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.
73
+ # Kind comes from the preset's resource list (see Kdep::Workload).
80
74
  def determine_resource_type(preset, deploy_dir)
81
75
  if @command_options[:type]
82
76
  return @command_options[:type]
@@ -86,14 +80,14 @@ module Kdep
86
80
  end
87
81
 
88
82
  def resource_type_for_preset(preset, deploy_dir)
89
- resources = preset_resources(preset, deploy_dir)
83
+ resources = Kdep::Workload.preset_resources(preset, deploy_dir)
90
84
 
91
- if (kind = resources.find { |r| NON_RESTARTABLE_KINDS.include?(r) })
85
+ if (kind = Kdep::Workload.batch_kind(resources))
92
86
  @ui.error("#{kind} workloads cannot be restarted (use --type to override)")
93
87
  exit 1
94
88
  end
95
89
 
96
- found = resources & WORKLOAD_KINDS
90
+ found = Kdep::Workload.rollout_kinds(resources)
97
91
 
98
92
  if found.size > 1
99
93
  @ui.error("Multiple workloads in preset '#{preset}': #{found.join(', ')}. Use --type to pick one.")
@@ -106,12 +100,6 @@ module Kdep
106
100
  found.first || "deployment"
107
101
  end
108
102
 
109
- def preset_resources(preset, deploy_dir)
110
- Kdep::Preset.new(preset, deploy_dir).resources
111
- rescue StandardError
112
- []
113
- end
114
-
115
103
  def resolve_deploy_dir(kdep_dir, deploy_name, discovery)
116
104
  if deploy_name
117
105
  deploy_dir = File.join(kdep_dir, deploy_name)
@@ -7,6 +7,7 @@ require "kdep/dashboard/log_panel"
7
7
  require "kdep/dashboard/resources_panel"
8
8
  require "kdep/dashboard/health_panel"
9
9
  require "kdep/kubectl"
10
+ require "kdep/pod_logs"
10
11
 
11
12
  module Kdep
12
13
  class Dashboard
@@ -14,6 +15,13 @@ module Kdep
14
15
  PANEL_NAMES = { rollout: "Rollout", logs: "Logs", resources: "Resources", health: "Health" }.freeze
15
16
  REFRESH_INTERVAL = 0.25
16
17
 
18
+ # The dashboard reads keys through $stdin.raw and draws on $stdout, so
19
+ # both must be terminals -- a script with a piped or /dev/null stdin
20
+ # still has a TTY stdout and used to crash with Errno::ENOTTY.
21
+ def self.interactive?
22
+ $stdin.tty? && $stdout.tty?
23
+ end
24
+
17
25
  def initialize(config)
18
26
  @config = config
19
27
  @name = config["name"]
@@ -29,8 +37,8 @@ module Kdep
29
37
  end
30
38
 
31
39
  def run
32
- unless $stdout.tty?
33
- puts "Dashboard skipped: not a TTY"
40
+ unless self.class.interactive?
41
+ puts "Dashboard skipped: not an interactive terminal"
34
42
  return
35
43
  end
36
44
 
@@ -152,8 +160,9 @@ module Kdep
152
160
  def start_log_thread
153
161
  t = Thread.new do
154
162
  begin
155
- cmd = ["kubectl", "logs", "-n", @namespace, "-l", "app=#{@name}",
156
- "-f", "--tail=50", "--all-containers", "--prefix"]
163
+ pod_count, _running = PodLogs.count(@namespace, @name)
164
+ cmd = PodLogs.kubectl_args(namespace: @namespace, app_name: @name, pod_count: pod_count,
165
+ tail: 50, all_containers: true)
157
166
  io = IO.popen(cmd, "r", err: [:child, :out])
158
167
  @mutex.synchronize { @ios << io }
159
168
  while @running && (line = io.gets)
data/lib/kdep/kubectl.rb CHANGED
@@ -40,6 +40,12 @@ module Kdep
40
40
  end
41
41
  end
42
42
 
43
+ # Streams kubectl's own progress to the terminal; true when the rollout
44
+ # completed within the timeout.
45
+ def self.rollout_status(kind, name, namespace:, timeout:)
46
+ system("kubectl", "rollout", "status", "#{kind}/#{name}", "-n", namespace, "--timeout=#{timeout}")
47
+ end
48
+
43
49
  # Declarative rollout-restart helper used by Kdep::RolloutTracker (Feature F).
44
50
  def self.rollout_restart(kind, name, namespace:)
45
51
  run("rollout", "restart", "#{kind}/#{name}", "-n", namespace)
@@ -0,0 +1,42 @@
1
+ require "kdep/kubectl"
2
+
3
+ module Kdep
4
+ # Shared by `kdep log` and the dashboard log panel: both stream every pod
5
+ # behind the app label instead of picking one.
6
+ module PodLogs
7
+ # kubectl refuses to follow more than --max-log-requests streams (default 5)
8
+ MIN_LOG_REQUESTS = 5
9
+ # The count and `kubectl logs -l` are two selector lookups. Right after a
10
+ # bump the ReplicaSet is creating surge pods between them, so a limit equal
11
+ # to the count gets refused -- leave room for a full surge.
12
+ SURGE_HEADROOM = 2
13
+
14
+ # kubectl logs -l streams every pod the selector matches, whatever its
15
+ # phase (Completed leftovers, Pending surge pods), so the stream limit
16
+ # must count all of them. Returns [matching, running].
17
+ def self.count(namespace, app_name)
18
+ data = Kdep::Kubectl.run_json("get", "pods", "-n", namespace, "-l", "app=#{app_name}")
19
+ pods = data["items"] || []
20
+ running = pods.count { |pod| pod.dig("status", "phase") == "Running" }
21
+ [pods.length, running]
22
+ end
23
+
24
+ # --ignore-errors keeps one pod still in ContainerCreating from ending
25
+ # every other stream mid-rollout.
26
+ def self.kubectl_args(namespace:, app_name:, pod_count:, tail:, follow: true, container: nil, all_containers: false)
27
+ args = ["kubectl", "logs", "-n", namespace, "-l", "app=#{app_name}", "--prefix", "--ignore-errors"]
28
+ args << "--max-log-requests=#{[pod_count * SURGE_HEADROOM, MIN_LOG_REQUESTS].max}"
29
+ args << "-f" if follow
30
+ args << "--tail=#{tail}"
31
+
32
+ if container
33
+ args << "-c"
34
+ args << container
35
+ elsif all_containers
36
+ args << "--all-containers"
37
+ end
38
+
39
+ args
40
+ end
41
+ end
42
+ end
data/lib/kdep/renderer.rb CHANGED
@@ -3,6 +3,22 @@ require "yaml"
3
3
 
4
4
  module Kdep
5
5
  class Renderer
6
+ # O app declara env_from para o Secret que o kdep renderiza, mas não há secrets.yml (ou está vazio):
7
+ # aplicar esse Secret sem `data` apagaria todas as chaves dele no cluster (incidente de 12/11/2026).
8
+ class MissingSecretsError < StandardError; end
9
+
10
+ # Preflight comum a bump/apply/helm: roda ANTES de qualquer build, helm upgrade ou apply.
11
+ # Erro -> mensagem + exit 1, nada aplicado. :skip -> aviso (o Secret do cluster fica intocado).
12
+ def self.preflight_secrets!(ui, renderer, resources)
13
+ return unless resources.include?("secret")
14
+
15
+ ui.warn(renderer.secret_skip_message) if renderer.check_secrets! == :skip
16
+ rescue MissingSecretsError => e
17
+ ui.error(e.message)
18
+ ui.error("nada foi aplicado")
19
+ exit 1
20
+ end
21
+
6
22
  # Convention: Infisical project slug = K8s namespace stripped of -stage/-dev
7
23
  # suffix; envSlug = stage/dev/prod derived from the same suffix.
8
24
  NS_ENV_SUFFIXES = { "-stage" => "stage", "-staging" => "stage", "-dev" => "dev" }.freeze
@@ -39,6 +55,7 @@ module Kdep
39
55
  return nil if secret_keys_for_env.empty?
40
56
  effective_config = @config.merge("infisical" => infisical_render_context)
41
57
  when "secret"
58
+ return nil if check_secrets! == :skip
42
59
  secrets = legacy_load_secrets
43
60
  end
44
61
 
@@ -179,5 +196,59 @@ module Kdep
179
196
  content = File.read(secrets_path)
180
197
  YAML.safe_load(content) || {}
181
198
  end
199
+
200
+ public
201
+
202
+ # Decide o que fazer com o Secret nativo (kdep/<deploy>/secrets.yml). Nunca aplica um Secret que apague chaves:
203
+ # - secrets.yml com chaves, todas com valor -> :render
204
+ # - secrets.yml ausente ou vazio, env_from padrão -> :skip (não renderiza; o Secret do cluster fica intocado)
205
+ # - secrets.yml ausente ou vazio, env_from EXPLÍCITO para este Secret -> MissingSecretsError
206
+ # - alguma chave com valor nil ou "" -> MissingSecretsError (apagaria o valor no cluster)
207
+ # Incidente de 12/11/2026: o secret-mcp-leadfy-bi foi aplicado sem data e perdeu todas as chaves.
208
+ def check_secrets!
209
+ return :render if env_spec
210
+
211
+ secrets = legacy_load_secrets
212
+ unless secrets.is_a?(Hash)
213
+ raise MissingSecretsError, "#{secrets_path} não é um mapa CHAVE: valor"
214
+ end
215
+
216
+ if secrets.empty?
217
+ return :skip unless explicit_secret_ref?
218
+
219
+ state = File.exist?(secrets_path) ? "está vazio" : "não existe"
220
+ raise MissingSecretsError,
221
+ "#{secrets_path} #{state}, mas o app.yml tem env_from secret/#{secret_name}: " \
222
+ "aplicar o Secret sem data apagaria todas as chaves de #{secret_name} no cluster. " \
223
+ "Crie o secrets.yml com as chaves (ou tire o secret do env_from) antes de render/apply/bump."
224
+ end
225
+
226
+ vazias = secrets.select { |_k, v| v.nil? || v.to_s.empty? }.keys
227
+ unless vazias.empty?
228
+ raise MissingSecretsError,
229
+ "#{secrets_path}: chave(s) sem valor (#{vazias.join(", ")}). Aplicar assim apagaria o valor " \
230
+ "delas em #{secret_name} no cluster. Preencha ou remova antes de render/apply/bump."
231
+ end
232
+ :render
233
+ end
234
+
235
+ def secret_skip_message
236
+ "#{secrets_path} não existe (ou está vazio): o Secret #{secret_name} não é renderizado nem aplicado; " \
237
+ "o que estiver no cluster fica intocado (se ele não existir, o pod falha ao subir)."
238
+ end
239
+
240
+ private
241
+
242
+ def secret_name
243
+ @config["secret_name"] || "secret-#{@config["name"]}"
244
+ end
245
+
246
+ def secrets_path
247
+ File.join(@deploy_dir, "secrets.yml")
248
+ end
249
+
250
+ def explicit_secret_ref?
251
+ Array(@config["env_from"]).map(&:to_s).include?("secret/#{secret_name}")
252
+ end
182
253
  end
183
254
  end
data/lib/kdep/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Kdep
2
- VERSION = "0.4.9"
2
+ VERSION = "0.6.0"
3
3
  end
@@ -0,0 +1,37 @@
1
+ require "kdep/preset"
2
+
3
+ module Kdep
4
+ # The preset file lists the resources kdep applies, so the workload kind is
5
+ # declared there -- deriving it from the preset *name* got every
6
+ # non-Deployment target wrong (daemonset, statefulset, custom). Reading the
7
+ # list also handles `custom`, whose resources live in the deploy dir.
8
+ # Shared by `kdep restart` and `kdep bump --wait`.
9
+ module Workload
10
+ KINDS = %w[deployment statefulset daemonset].freeze
11
+ BATCH_KINDS = %w[job cronjob].freeze
12
+
13
+ def self.preset_resources(preset, deploy_dir)
14
+ Kdep::Preset.new(preset, deploy_dir).resources
15
+ rescue StandardError
16
+ []
17
+ end
18
+
19
+ # Workloads that have a rollout (restart / rollout status)
20
+ def self.rollout_kinds(resources)
21
+ resources & KINDS
22
+ end
23
+
24
+ # The workload's metadata.name, resolved exactly as the templates do.
25
+ def self.name_for(kind, config)
26
+ case kind
27
+ when "statefulset" then config["statefulset_name"] || config["deployment_name"] || config["name"]
28
+ when "daemonset" then config["daemonset_name"] || config["name"]
29
+ else config["deployment_name"] || config["name"]
30
+ end
31
+ end
32
+
33
+ def self.batch_kind(resources)
34
+ resources.find { |r| BATCH_KINDS.include?(r) }
35
+ end
36
+ end
37
+ end
data/lib/kdep.rb CHANGED
@@ -5,6 +5,7 @@ require "kdep/yaml_compat"
5
5
  require "kdep/defaults"
6
6
  require "kdep/config"
7
7
  require "kdep/preset"
8
+ require "kdep/workload"
8
9
  require "kdep/discovery"
9
10
  require "kdep/template_context"
10
11
  require "kdep/renderer"
@@ -12,6 +13,7 @@ require "kdep/validator"
12
13
  require "kdep/writer"
13
14
  require "kdep/old_format"
14
15
  require "kdep/kubectl"
16
+ require "kdep/pod_logs"
15
17
  require "kdep/context_guard"
16
18
  require "kdep/cluster_health"
17
19
  require "kdep/docker"
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.9
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Leadfy
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-10-10 00:00:00.000000000 Z
11
+ date: 2026-11-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: envspec
@@ -131,6 +131,7 @@ files:
131
131
  - lib/kdep/kubectl.rb
132
132
  - lib/kdep/old_format.rb
133
133
  - lib/kdep/path_resolver.rb
134
+ - lib/kdep/pod_logs.rb
134
135
  - lib/kdep/preset.rb
135
136
  - lib/kdep/registry.rb
136
137
  - lib/kdep/renderer.rb
@@ -143,6 +144,7 @@ files:
143
144
  - lib/kdep/validator.rb
144
145
  - lib/kdep/version.rb
145
146
  - lib/kdep/version_tagger.rb
147
+ - lib/kdep/workload.rb
146
148
  - lib/kdep/writer.rb
147
149
  - lib/kdep/yaml_compat.rb
148
150
  - lib/kdep/yaml_duplicate_keys.rb