kdep 0.1.3 → 0.2.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: ed01f5bcc6ebac09d59dd1d732d73e1913f29ce3f2a9efc7df67fbb914686973
4
- data.tar.gz: 8b9191bdad263a0902c76a49ef1446e184ab7eae44a269c39947bff1387863fc
3
+ metadata.gz: f971ace54be5f10abde07364654600377be07f9f77c1b7afb5bb9f73e5b3ddfe
4
+ data.tar.gz: f2040905cec594653386a4a5f9049e6217628a29925d5f7fcc8aa0724eb873dc
5
5
  SHA512:
6
- metadata.gz: 70b166608d549d46929c98a76b1930a5a23d42a0fbf71d118d649aecbbe7ca694524327b2cb3d58a1c19f1a1dc8bc28ac8a578740ffc3d55bc9cb0f17458c721
7
- data.tar.gz: eedf3f6bfd98c1bd8820db578a5431abe919f11102575fa6dccbef6f3f2af84411968c797cae193974136749021502325ad270d3c0fdfc54b3c35086156403db
6
+ metadata.gz: 0cb2245173f84ca39286e957c7fce8a83491f403adc8c591c40da30cbe0f3ee26bf48ce432fa7c5deac669a43fdaa328212d3e1e00ec8266248164d76779957e
7
+ data.tar.gz: b4cbb931a8a08250b1489ad22e9020a4642650330b1efbf79329294f777f2f1cfeb85b9b02c97bd1a0901318efa0b6f2deaa3ee5791b05eaf7a4cf02339a4a69
data/lib/kdep/cli.rb CHANGED
@@ -18,6 +18,7 @@ module Kdep
18
18
  "restart" => Commands::Restart,
19
19
  "scale" => Commands::Scale,
20
20
  "migrate" => Commands::Migrate,
21
+ "helm-install" => Commands::HelmInstall,
21
22
  "dashboard" => Commands::Dashboard,
22
23
  }.freeze
23
24
 
@@ -55,7 +55,7 @@ module Kdep
55
55
  resources = preset.resources
56
56
 
57
57
  repo_root = File.dirname(kdep_dir)
58
- output_dir = File.join(repo_root, ".rendered")
58
+ output_dir = File.join(deploy_dir, ".rendered")
59
59
 
60
60
  writer = Kdep::Writer.new(output_dir)
61
61
  writer.clean
@@ -63,14 +63,17 @@ module Kdep
63
63
  repo_root = File.dirname(kdep_dir)
64
64
 
65
65
  begin
66
- # Step 3: Query registry
67
- registry = Kdep::Registry.new(registry_url)
68
- tags = registry.list_tags(image)
69
- @ui.info("Current tags: #{tags.length} found")
70
-
71
- # Step 4: Increment version
72
- next_version = Kdep::VersionTagger.next_tag(tags)
73
- @ui.info("Next version: #{next_version}")
66
+ # Step 3: Read current tag from state.yml, increment minor
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
74
+ parsed = Kdep::VersionTagger.parse(current_tag)
75
+ next_version = parsed ? "#{parsed[0]}.#{parsed[1] + 1}" : "0.1"
76
+ @ui.info("#{current_tag} -> #{next_version}")
74
77
 
75
78
  # Step 5: Build
76
79
  if registry_url && !registry_url.to_s.empty?
@@ -84,7 +87,8 @@ module Kdep
84
87
  context_dir: repo_root,
85
88
  dockerfile: config["dockerfile"],
86
89
  target: config["target"],
87
- platform: @command_options[:platform] || config["platform"]
90
+ platform: @command_options[:platform] || config["platform"],
91
+ build_args: config["build_args"]
88
92
  )
89
93
  @ui.info("Built: #{full_tag}")
90
94
 
@@ -99,7 +103,7 @@ module Kdep
99
103
  # Render manifests
100
104
  preset = Kdep::Preset.new(config["preset"], deploy_dir)
101
105
  resources = preset.resources
102
- output_dir = File.join(repo_root, ".rendered")
106
+ output_dir = File.join(deploy_dir, ".rendered")
103
107
 
104
108
  writer = Kdep::Writer.new(output_dir)
105
109
  writer.clean
@@ -161,6 +165,9 @@ module Kdep
161
165
  if errors.empty?
162
166
  @ui.info("#{applied} files applied, 0 errors")
163
167
 
168
+ # Save new tag to state.yml
169
+ File.write(File.join(deploy_dir, "state.yml"), YAML.dump({"tag" => next_version}))
170
+
164
171
  # Launch TUI dashboard for live monitoring
165
172
  unless @command_options[:"no-dashboard"]
166
173
  dashboard = Kdep::Dashboard.new(
@@ -53,7 +53,7 @@ module Kdep
53
53
  resources = preset.resources
54
54
 
55
55
  repo_root = File.dirname(kdep_dir)
56
- output_dir = File.join(repo_root, ".rendered")
56
+ output_dir = File.join(deploy_dir, ".rendered")
57
57
 
58
58
  writer = Kdep::Writer.new(output_dir)
59
59
  writer.clean
@@ -0,0 +1,286 @@
1
+ require "optparse"
2
+
3
+ module Kdep
4
+ module Commands
5
+ class HelmInstall
6
+ def self.option_parser
7
+ OptionParser.new do |opts|
8
+ opts.banner = "Usage: kdep helm-install [deploy] [env]"
9
+ opts.separator ""
10
+ opts.separator "Runs helm upgrade --install for a helm preset deploy target,"
11
+ opts.separator "then renders and applies any additional k8s resources (ingress, secrets)."
12
+ opts.separator ""
13
+ opts.separator "Supports single chart (chart:) or multiple charts (charts: array)."
14
+ opts.separator ""
15
+ opts.on("--dry-run", "Show what would be run without executing")
16
+ end
17
+ end
18
+
19
+ def initialize(global_options:, command_options:, args:)
20
+ @global_options = global_options
21
+ @command_options = command_options
22
+ @args = args
23
+ @ui = Kdep::UI.new(color: false)
24
+ end
25
+
26
+ def execute
27
+ deploy_name = @args[0]
28
+ env = @args[1]
29
+ @dry_run = @command_options[:"dry-run"]
30
+
31
+ # Discover kdep/ directory
32
+ discovery = Kdep::Discovery.new
33
+ kdep_dir = discovery.find_kdep_dir
34
+
35
+ unless kdep_dir
36
+ @ui.error("No kdep/ directory found")
37
+ exit 1
38
+ end
39
+
40
+ # Resolve deploy directory
41
+ @deploy_dir = resolve_deploy_dir(kdep_dir, deploy_name, discovery)
42
+ unless @deploy_dir
43
+ exit 1
44
+ end
45
+
46
+ # Load config
47
+ config = Kdep::Config.new(@deploy_dir, env).load
48
+ @namespace = config["namespace"]
49
+
50
+ unless config["preset"] == "helm"
51
+ @ui.error("#{deploy_name} is not a helm preset (preset: #{config["preset"]})")
52
+ exit 1
53
+ end
54
+
55
+ # Validate context
56
+ begin
57
+ Kdep::ContextGuard.new(config["context"]).validate!
58
+ rescue Kdep::Kubectl::Error => e
59
+ @ui.error(e.message)
60
+ exit 1
61
+ end
62
+
63
+ # Load secrets for --set interpolation
64
+ @secrets = load_secrets(@deploy_dir)
65
+
66
+ # Add helm repos
67
+ repos = config["helm_repos"] || {}
68
+ # Also support singular helm_repo for backward compat
69
+ if config["helm_repo"]
70
+ repo_name, repo_url = config["helm_repo"].split("=", 2)
71
+ repos[repo_name] = repo_url if repo_name && repo_url
72
+ end
73
+
74
+ repos.each do |repo_name, repo_url|
75
+ @ui.info("Adding helm repo: #{repo_name} -> #{repo_url}")
76
+ Kdep::Helm.repo_add(repo_name, repo_url) unless @dry_run
77
+ end
78
+
79
+ # Install charts
80
+ charts = build_chart_list(config)
81
+
82
+ if charts.empty?
83
+ @ui.error("app.yml has no chart or charts defined")
84
+ exit 1
85
+ end
86
+
87
+ charts.each do |chart_conf|
88
+ install_chart(chart_conf)
89
+ end
90
+
91
+ # Render and apply additional k8s resources (ingress, secret, etc.)
92
+ apply_extra_resources(config, kdep_dir)
93
+ end
94
+
95
+ private
96
+
97
+ # Build a normalized list of chart configs.
98
+ # Supports:
99
+ # chart: livekit/livekit-server (single)
100
+ # charts: (multiple)
101
+ # - release: livekit-server
102
+ # chart: livekit/livekit-server
103
+ # values: server-values.yaml
104
+ # sets: { ... }
105
+ def build_chart_list(config)
106
+ if config["charts"]
107
+ config["charts"].map do |c|
108
+ {
109
+ release: c["release"] || config["name"],
110
+ chart: c["chart"],
111
+ values: c["values"],
112
+ sets: c["sets"] || {},
113
+ }
114
+ end
115
+ elsif config["chart"]
116
+ [{
117
+ release: config["release"] || config["name"],
118
+ chart: config["chart"],
119
+ values: nil, # auto-detect values.yml in deploy dir
120
+ sets: config["helm_sets"] || {},
121
+ }]
122
+ else
123
+ []
124
+ end
125
+ end
126
+
127
+ def install_chart(chart_conf)
128
+ release = chart_conf[:release]
129
+ chart = chart_conf[:chart]
130
+
131
+ # Resolve values file
132
+ values_file = resolve_values_file(chart_conf[:values])
133
+
134
+ # Build --set arguments, interpolating secrets
135
+ sets = {}
136
+ chart_conf[:sets].each do |key, value|
137
+ sets[key] = interpolate(value.to_s, @secrets)
138
+ end
139
+
140
+ @ui.info("helm upgrade --install #{release} #{chart} -n #{@namespace}")
141
+ @ui.info(" values: #{values_file}") if values_file
142
+ sets.each { |k, v| @ui.info(" --set #{k}=#{mask(v)}") }
143
+
144
+ begin
145
+ output = Kdep::Helm.upgrade_install(
146
+ release: release,
147
+ chart: chart,
148
+ namespace: @namespace,
149
+ values_file: values_file,
150
+ sets: sets,
151
+ dry_run: @dry_run
152
+ )
153
+ @ui.info(output) unless output.strip.empty?
154
+ @ui.success("helm install complete: #{release}")
155
+ rescue Kdep::Helm::Error => e
156
+ @ui.error(e.message)
157
+ exit 1
158
+ end
159
+ end
160
+
161
+ def resolve_values_file(explicit_name)
162
+ if explicit_name
163
+ path = File.join(@deploy_dir, explicit_name)
164
+ return path if File.exist?(path)
165
+ @ui.warn("Values file not found: #{explicit_name}")
166
+ return nil
167
+ end
168
+
169
+ # Auto-detect values.yml or values.yaml
170
+ %w[values.yml values.yaml].each do |name|
171
+ path = File.join(@deploy_dir, name)
172
+ return path if File.exist?(path)
173
+ end
174
+ nil
175
+ end
176
+
177
+ def apply_extra_resources(config, kdep_dir)
178
+ preset = Kdep::Preset.new("helm", @deploy_dir)
179
+ resources = preset.resources
180
+ return if resources.empty?
181
+
182
+ repo_root = File.dirname(kdep_dir)
183
+ output_dir = File.join(@deploy_dir, ".rendered")
184
+
185
+ writer = Kdep::Writer.new(output_dir)
186
+ writer.clean
187
+ renderer = Kdep::Renderer.new(config, @deploy_dir)
188
+ validator = Kdep::Validator.new
189
+
190
+ files_written = 0
191
+ render_errors = []
192
+
193
+ resources.each_with_index do |resource_name, idx|
194
+ index = idx + 1
195
+ begin
196
+ content = renderer.render_resource(resource_name)
197
+ rescue => e
198
+ render_errors << "#{resource_name}: #{e.message}"
199
+ next
200
+ end
201
+
202
+ unless content.nil? || content.strip.empty?
203
+ result = validator.validate(content, resource_name)
204
+ unless result["valid"]
205
+ result["errors"].each do |err|
206
+ render_errors << "#{resource_name}: #{err}"
207
+ end
208
+ end
209
+ end
210
+
211
+ path = writer.write(resource_name, content, index)
212
+ if path
213
+ @ui.file_written(path.sub(repo_root + "/", ""))
214
+ files_written += 1
215
+ end
216
+ end
217
+
218
+ render_errors.each { |err| @ui.error(err) } unless render_errors.empty?
219
+
220
+ unless @dry_run
221
+ rendered_files = Dir.glob(File.join(output_dir, "*.yml")).sort
222
+ applied = 0
223
+ apply_errors = []
224
+
225
+ rendered_files.each do |file_path|
226
+ begin
227
+ Kdep::Kubectl.apply(file_path, namespace: @namespace)
228
+ @ui.info("applied: #{File.basename(file_path)}")
229
+ applied += 1
230
+ rescue Kdep::Kubectl::Error => e
231
+ apply_errors << e.message
232
+ @ui.error("#{File.basename(file_path)}: #{e.message}")
233
+ end
234
+ end
235
+
236
+ @ui.info("#{applied} additional resources applied") if applied > 0
237
+ exit 1 if apply_errors.any?
238
+ end
239
+ end
240
+
241
+ def interpolate(value, secrets)
242
+ value.gsub(/\$\{(\w+)\}/) do
243
+ key = $1
244
+ secrets[key] || ENV[key] || "${#{key}}"
245
+ end
246
+ end
247
+
248
+ def mask(value)
249
+ return value if value.length <= 8
250
+ value[0..3] + "****"
251
+ end
252
+
253
+ def load_secrets(deploy_dir)
254
+ secrets_path = File.join(deploy_dir, "secrets.yml")
255
+ return {} unless File.exist?(secrets_path)
256
+
257
+ content = File.read(secrets_path)
258
+ YAML.safe_load(content) || {}
259
+ end
260
+
261
+ def resolve_deploy_dir(kdep_dir, deploy_name, discovery)
262
+ if deploy_name
263
+ deploy_dir = File.join(kdep_dir, deploy_name)
264
+ unless File.directory?(deploy_dir)
265
+ @ui.error("Deploy target not found: #{deploy_name}")
266
+ deploys = discovery.find_deploys
267
+ @ui.info("Available deploys: #{deploys.join(', ')}") if deploys.any?
268
+ return nil
269
+ end
270
+ deploy_dir
271
+ else
272
+ deploys = discovery.find_deploys
273
+ if deploys.length == 1
274
+ File.join(kdep_dir, deploys[0])
275
+ elsif deploys.length > 1
276
+ @ui.error("Multiple deploys found, specify one: #{deploys.join(', ')}")
277
+ nil
278
+ else
279
+ @ui.error("No deploy targets found in kdep/")
280
+ nil
281
+ end
282
+ end
283
+ end
284
+ end
285
+ end
286
+ end