kdep 0.1.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 +7 -0
- data/LICENSE.txt +21 -0
- data/exe/kdep +3 -0
- data/lib/kdep/cli.rb +72 -0
- data/lib/kdep/cluster_health.rb +44 -0
- data/lib/kdep/commands/apply.rb +147 -0
- data/lib/kdep/commands/build.rb +103 -0
- data/lib/kdep/commands/bump.rb +190 -0
- data/lib/kdep/commands/diff.rb +133 -0
- data/lib/kdep/commands/eject.rb +97 -0
- data/lib/kdep/commands/init.rb +99 -0
- data/lib/kdep/commands/log.rb +145 -0
- data/lib/kdep/commands/push.rb +94 -0
- data/lib/kdep/commands/render.rb +130 -0
- data/lib/kdep/commands/restart.rb +109 -0
- data/lib/kdep/commands/scale.rb +136 -0
- data/lib/kdep/commands/secrets.rb +223 -0
- data/lib/kdep/commands/sh.rb +117 -0
- data/lib/kdep/commands/status.rb +187 -0
- data/lib/kdep/config.rb +69 -0
- data/lib/kdep/context_guard.rb +23 -0
- data/lib/kdep/dashboard/health_panel.rb +53 -0
- data/lib/kdep/dashboard/layout.rb +40 -0
- data/lib/kdep/dashboard/log_panel.rb +54 -0
- data/lib/kdep/dashboard/panel.rb +135 -0
- data/lib/kdep/dashboard/resources_panel.rb +91 -0
- data/lib/kdep/dashboard/rollout_panel.rb +75 -0
- data/lib/kdep/dashboard/screen.rb +29 -0
- data/lib/kdep/dashboard.rb +258 -0
- data/lib/kdep/defaults.rb +21 -0
- data/lib/kdep/discovery.rb +29 -0
- data/lib/kdep/docker.rb +29 -0
- data/lib/kdep/kubectl.rb +41 -0
- data/lib/kdep/preset.rb +33 -0
- data/lib/kdep/registry.rb +94 -0
- data/lib/kdep/renderer.rb +53 -0
- data/lib/kdep/template_context.rb +22 -0
- data/lib/kdep/ui.rb +48 -0
- data/lib/kdep/validator.rb +73 -0
- data/lib/kdep/version.rb +3 -0
- data/lib/kdep/version_tagger.rb +27 -0
- data/lib/kdep/writer.rb +26 -0
- data/lib/kdep.rb +49 -0
- data/templates/init/app.yml.erb +49 -0
- data/templates/init/gitignore +3 -0
- data/templates/init/secrets.yml +7 -0
- data/templates/presets/cronjob +4 -0
- data/templates/presets/job +4 -0
- data/templates/presets/web +7 -0
- data/templates/presets/worker +4 -0
- data/templates/resources/configmap.yml.erb +7 -0
- data/templates/resources/cronjob.yml.erb +42 -0
- data/templates/resources/deployment.yml.erb +71 -0
- data/templates/resources/ingress.yml.erb +47 -0
- data/templates/resources/job.yml.erb +35 -0
- data/templates/resources/secret.yml.erb +15 -0
- data/templates/resources/service.yml.erb +13 -0
- metadata +142 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
require "optparse"
|
|
2
|
+
|
|
3
|
+
module Kdep
|
|
4
|
+
module Commands
|
|
5
|
+
class Diff
|
|
6
|
+
def self.option_parser
|
|
7
|
+
OptionParser.new do |opts|
|
|
8
|
+
opts.banner = "Usage: kdep diff [deploy] [env]"
|
|
9
|
+
opts.separator ""
|
|
10
|
+
opts.separator "Shows differences between rendered manifests and live cluster state."
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def initialize(global_options:, command_options:, args:)
|
|
15
|
+
@global_options = global_options
|
|
16
|
+
@command_options = command_options
|
|
17
|
+
@args = args
|
|
18
|
+
@ui = Kdep::UI.new(color: false)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def execute
|
|
22
|
+
deploy_name = @args[0]
|
|
23
|
+
env = @args[1]
|
|
24
|
+
|
|
25
|
+
# Discover kdep/ directory
|
|
26
|
+
discovery = Kdep::Discovery.new
|
|
27
|
+
kdep_dir = discovery.find_kdep_dir
|
|
28
|
+
|
|
29
|
+
unless kdep_dir
|
|
30
|
+
@ui.error("No kdep/ directory found")
|
|
31
|
+
exit 1
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Resolve deploy directory
|
|
35
|
+
deploy_dir = resolve_deploy_dir(kdep_dir, deploy_name, discovery)
|
|
36
|
+
unless deploy_dir
|
|
37
|
+
exit 1
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Load config
|
|
41
|
+
config = Kdep::Config.new(deploy_dir, env).load
|
|
42
|
+
|
|
43
|
+
# Validate context before any cluster operation
|
|
44
|
+
begin
|
|
45
|
+
Kdep::ContextGuard.new(config["context"]).validate!
|
|
46
|
+
rescue Kdep::Kubectl::Error => e
|
|
47
|
+
@ui.error(e.message)
|
|
48
|
+
exit 1
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Load preset resources and render
|
|
52
|
+
preset = Kdep::Preset.new(config["preset"], deploy_dir)
|
|
53
|
+
resources = preset.resources
|
|
54
|
+
|
|
55
|
+
repo_root = File.dirname(kdep_dir)
|
|
56
|
+
output_dir = File.join(repo_root, ".rendered")
|
|
57
|
+
|
|
58
|
+
writer = Kdep::Writer.new(output_dir)
|
|
59
|
+
writer.clean
|
|
60
|
+
renderer = Kdep::Renderer.new(config, deploy_dir)
|
|
61
|
+
|
|
62
|
+
resources.each_with_index do |resource_name, idx|
|
|
63
|
+
index = idx + 1
|
|
64
|
+
begin
|
|
65
|
+
content = renderer.render_resource(resource_name)
|
|
66
|
+
rescue => e
|
|
67
|
+
@ui.error("#{resource_name}: #{e.message}")
|
|
68
|
+
next
|
|
69
|
+
end
|
|
70
|
+
writer.write(resource_name, content, index)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Get sorted rendered files
|
|
74
|
+
rendered_files = Dir.glob(File.join(output_dir, "*.yml")).sort
|
|
75
|
+
|
|
76
|
+
if rendered_files.empty?
|
|
77
|
+
@ui.info("No rendered files to diff")
|
|
78
|
+
return
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Diff each file against live cluster state
|
|
82
|
+
has_diffs = false
|
|
83
|
+
errors = []
|
|
84
|
+
|
|
85
|
+
rendered_files.each do |file_path|
|
|
86
|
+
begin
|
|
87
|
+
diff_output = Kdep::Kubectl.diff(file_path)
|
|
88
|
+
if diff_output
|
|
89
|
+
has_diffs = true
|
|
90
|
+
@ui.info("--- #{File.basename(file_path)} ---")
|
|
91
|
+
@ui.info(diff_output)
|
|
92
|
+
end
|
|
93
|
+
rescue Kdep::Kubectl::Error => e
|
|
94
|
+
errors << { "file" => File.basename(file_path), "error" => e.message }
|
|
95
|
+
@ui.error("#{File.basename(file_path)}: #{e.message}")
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
unless has_diffs || errors.any?
|
|
100
|
+
@ui.info("No changes -- cluster state matches rendered manifests.")
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
private
|
|
105
|
+
|
|
106
|
+
def resolve_deploy_dir(kdep_dir, deploy_name, discovery)
|
|
107
|
+
if deploy_name
|
|
108
|
+
deploy_dir = File.join(kdep_dir, deploy_name)
|
|
109
|
+
unless File.directory?(deploy_dir)
|
|
110
|
+
@ui.error("Deploy target not found: #{deploy_name}")
|
|
111
|
+
deploys = discovery.find_deploys
|
|
112
|
+
if deploys.any?
|
|
113
|
+
@ui.info("Available deploys: #{deploys.join(', ')}")
|
|
114
|
+
end
|
|
115
|
+
return nil
|
|
116
|
+
end
|
|
117
|
+
deploy_dir
|
|
118
|
+
else
|
|
119
|
+
deploys = discovery.find_deploys
|
|
120
|
+
if deploys.length == 1
|
|
121
|
+
File.join(kdep_dir, deploys[0])
|
|
122
|
+
elsif deploys.length > 1
|
|
123
|
+
@ui.error("Multiple deploys found, specify one: #{deploys.join(', ')}")
|
|
124
|
+
nil
|
|
125
|
+
else
|
|
126
|
+
@ui.error("No deploy targets found in kdep/")
|
|
127
|
+
nil
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
require "optparse"
|
|
2
|
+
require "fileutils"
|
|
3
|
+
|
|
4
|
+
module Kdep
|
|
5
|
+
module Commands
|
|
6
|
+
class Eject
|
|
7
|
+
def self.option_parser
|
|
8
|
+
OptionParser.new do |opts|
|
|
9
|
+
opts.banner = "Usage: kdep eject [resource_name]"
|
|
10
|
+
opts.separator ""
|
|
11
|
+
opts.separator "Copies a built-in template to your deploy target for customization."
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def initialize(global_options:, command_options:, args:)
|
|
16
|
+
@global_options = global_options
|
|
17
|
+
@command_options = command_options
|
|
18
|
+
@args = args
|
|
19
|
+
@ui = Kdep::UI.new
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def execute
|
|
23
|
+
resource_name = @args[0]
|
|
24
|
+
|
|
25
|
+
if resource_name.nil?
|
|
26
|
+
list_available_resources
|
|
27
|
+
return
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Find kdep dir
|
|
31
|
+
discovery = Kdep::Discovery.new
|
|
32
|
+
kdep_dir = discovery.find_kdep_dir
|
|
33
|
+
|
|
34
|
+
unless kdep_dir
|
|
35
|
+
@ui.error("No kdep/ directory found (run kdep init first)")
|
|
36
|
+
exit 1
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Find deploy directory
|
|
40
|
+
deploys = discovery.find_deploys
|
|
41
|
+
if deploys.empty?
|
|
42
|
+
@ui.error("No deploy targets found in #{kdep_dir}/")
|
|
43
|
+
exit 1
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
deploy_name = deploys.first
|
|
47
|
+
deploy_dir = File.join(kdep_dir, deploy_name)
|
|
48
|
+
|
|
49
|
+
# Check built-in template exists
|
|
50
|
+
source = File.join(Kdep.templates_dir, "resources", "#{resource_name}.yml.erb")
|
|
51
|
+
unless File.exist?(source)
|
|
52
|
+
@ui.error("Unknown resource: #{resource_name}")
|
|
53
|
+
@ui.info("Available resources: #{available_resources.join(', ')}")
|
|
54
|
+
exit 1
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Target path
|
|
58
|
+
resources_dir = File.join(deploy_dir, "resources")
|
|
59
|
+
target = File.join(resources_dir, "#{resource_name}.yml.erb")
|
|
60
|
+
|
|
61
|
+
if File.exist?(target)
|
|
62
|
+
@ui.warn("#{resource_name}.yml.erb already exists in resources/ -- skipping (delete it first to re-eject)")
|
|
63
|
+
return
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Create resources dir and copy
|
|
67
|
+
FileUtils.mkdir_p(resources_dir)
|
|
68
|
+
FileUtils.cp(source, target)
|
|
69
|
+
|
|
70
|
+
@ui.file_written(target)
|
|
71
|
+
@ui.info("Edit #{target} to customize the #{resource_name} template")
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def list_available_resources
|
|
77
|
+
resources = available_resources
|
|
78
|
+
if resources.empty?
|
|
79
|
+
@ui.info("No built-in resource templates found")
|
|
80
|
+
else
|
|
81
|
+
@ui.info("Available resources to eject:")
|
|
82
|
+
resources.each { |r| @ui.info(" #{r}") }
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def available_resources
|
|
87
|
+
resources_dir = File.join(Kdep.templates_dir, "resources")
|
|
88
|
+
return [] unless File.directory?(resources_dir)
|
|
89
|
+
|
|
90
|
+
Dir.entries(resources_dir)
|
|
91
|
+
.select { |f| f.end_with?(".yml.erb") }
|
|
92
|
+
.map { |f| f.sub(".yml.erb", "") }
|
|
93
|
+
.sort
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
require "optparse"
|
|
2
|
+
require "erb"
|
|
3
|
+
require "fileutils"
|
|
4
|
+
|
|
5
|
+
module Kdep
|
|
6
|
+
module Commands
|
|
7
|
+
class Init
|
|
8
|
+
def self.option_parser
|
|
9
|
+
OptionParser.new do |opts|
|
|
10
|
+
opts.banner = "Usage: kdep init [preset] [name]"
|
|
11
|
+
opts.separator ""
|
|
12
|
+
opts.separator "Scaffolds a new kdep deploy target."
|
|
13
|
+
opts.on("--namespace=NAMESPACE", "Kubernetes namespace")
|
|
14
|
+
opts.on("--registry=REGISTRY", "Container registry URL")
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def initialize(global_options:, command_options:, args:)
|
|
19
|
+
@global_options = global_options
|
|
20
|
+
@command_options = command_options
|
|
21
|
+
@args = args
|
|
22
|
+
@ui = Kdep::UI.new
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def execute
|
|
26
|
+
preset = @args[0]
|
|
27
|
+
deploy_name = @args[1]
|
|
28
|
+
|
|
29
|
+
unless preset
|
|
30
|
+
@ui.error("Missing preset and name arguments")
|
|
31
|
+
@ui.info("Usage: kdep init [preset] [name]")
|
|
32
|
+
@ui.info("Available presets: #{Kdep::Preset::BUILT_IN.join(', ')}")
|
|
33
|
+
exit 1
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
unless deploy_name
|
|
37
|
+
@ui.error("Missing name argument")
|
|
38
|
+
@ui.info("Usage: kdep init [preset] [name]")
|
|
39
|
+
exit 1
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
unless Kdep::Preset::BUILT_IN.include?(preset)
|
|
43
|
+
@ui.error("Unknown preset: #{preset}")
|
|
44
|
+
@ui.info("Available presets: #{Kdep::Preset::BUILT_IN.join(', ')}")
|
|
45
|
+
exit 1
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
target_dir = File.join(Dir.pwd, "kdep", deploy_name)
|
|
49
|
+
|
|
50
|
+
if File.exist?(target_dir)
|
|
51
|
+
@ui.error("kdep/#{deploy_name} already exists")
|
|
52
|
+
exit 1
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
FileUtils.mkdir_p(target_dir)
|
|
56
|
+
|
|
57
|
+
# Render app.yml from ERB template
|
|
58
|
+
namespace = @command_options[:namespace]
|
|
59
|
+
registry = @command_options[:registry]
|
|
60
|
+
|
|
61
|
+
template_path = File.join(Kdep.templates_dir, "init", "app.yml.erb")
|
|
62
|
+
template = File.read(template_path)
|
|
63
|
+
erb = if RUBY_VERSION >= "2.6"
|
|
64
|
+
ERB.new(template, trim_mode: "-")
|
|
65
|
+
else
|
|
66
|
+
ERB.new(template, nil, "-")
|
|
67
|
+
end
|
|
68
|
+
binding_ctx = erb_binding(preset: preset, deploy_name: deploy_name,
|
|
69
|
+
namespace: namespace, registry: registry)
|
|
70
|
+
app_yml_content = erb.result(binding_ctx)
|
|
71
|
+
|
|
72
|
+
File.write(File.join(target_dir, "app.yml"), app_yml_content)
|
|
73
|
+
@ui.file_written("kdep/#{deploy_name}/app.yml")
|
|
74
|
+
|
|
75
|
+
# Copy secrets.yml
|
|
76
|
+
FileUtils.cp(
|
|
77
|
+
File.join(Kdep.templates_dir, "init", "secrets.yml"),
|
|
78
|
+
File.join(target_dir, "secrets.yml")
|
|
79
|
+
)
|
|
80
|
+
@ui.file_written("kdep/#{deploy_name}/secrets.yml")
|
|
81
|
+
|
|
82
|
+
# Copy .gitignore
|
|
83
|
+
FileUtils.cp(
|
|
84
|
+
File.join(Kdep.templates_dir, "init", "gitignore"),
|
|
85
|
+
File.join(target_dir, ".gitignore")
|
|
86
|
+
)
|
|
87
|
+
@ui.file_written("kdep/#{deploy_name}/.gitignore")
|
|
88
|
+
|
|
89
|
+
@ui.success("Initialized kdep/#{deploy_name} with #{preset} preset")
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
def erb_binding(preset:, deploy_name:, namespace:, registry:)
|
|
95
|
+
binding
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
require "optparse"
|
|
2
|
+
|
|
3
|
+
module Kdep
|
|
4
|
+
module Commands
|
|
5
|
+
class Log
|
|
6
|
+
def self.option_parser
|
|
7
|
+
OptionParser.new do |opts|
|
|
8
|
+
opts.banner = "Usage: kdep log [deploy]"
|
|
9
|
+
opts.separator ""
|
|
10
|
+
opts.separator "Streams pod logs with error highlighting."
|
|
11
|
+
opts.separator ""
|
|
12
|
+
opts.on("-f", "--[no-]follow", "Follow log output (default: true)")
|
|
13
|
+
opts.on("-c", "--container=NAME", "Container name for multi-container pods")
|
|
14
|
+
opts.on("--tail=N", Integer, "Number of lines to show (default: 100)")
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def initialize(global_options:, command_options:, args:)
|
|
19
|
+
@global_options = global_options
|
|
20
|
+
@command_options = command_options
|
|
21
|
+
@args = args
|
|
22
|
+
@ui = Kdep::UI.new(color: false)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def execute
|
|
26
|
+
deploy_name = @args[0]
|
|
27
|
+
|
|
28
|
+
# Discover kdep/ directory
|
|
29
|
+
discovery = Kdep::Discovery.new
|
|
30
|
+
kdep_dir = discovery.find_kdep_dir
|
|
31
|
+
unless kdep_dir
|
|
32
|
+
@ui.error("No kdep/ directory found")
|
|
33
|
+
exit 1
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Resolve deploy directory
|
|
37
|
+
deploy_dir = resolve_deploy_dir(kdep_dir, deploy_name, discovery)
|
|
38
|
+
unless deploy_dir
|
|
39
|
+
exit 1
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Load config
|
|
43
|
+
config = Kdep::Config.new(deploy_dir, nil).load
|
|
44
|
+
|
|
45
|
+
# Validate context
|
|
46
|
+
Kdep::ContextGuard.new(config["context"]).validate!
|
|
47
|
+
|
|
48
|
+
# Find running pod
|
|
49
|
+
namespace = config["namespace"]
|
|
50
|
+
app_name = config["name"]
|
|
51
|
+
pod_name = find_pod(namespace, app_name)
|
|
52
|
+
|
|
53
|
+
# Stream logs
|
|
54
|
+
stream_logs(pod_name, namespace)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def find_pod(namespace, app_name)
|
|
60
|
+
data = Kdep::Kubectl.run_json(
|
|
61
|
+
"get", "pods",
|
|
62
|
+
"-n", namespace,
|
|
63
|
+
"-l", "app=#{app_name}",
|
|
64
|
+
"--field-selector", "status.phase=Running"
|
|
65
|
+
)
|
|
66
|
+
pods = data["items"]
|
|
67
|
+
|
|
68
|
+
if pods.nil? || pods.empty?
|
|
69
|
+
@ui.error("No running pods found for #{app_name} in #{namespace}")
|
|
70
|
+
exit 1
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
pods[0]["metadata"]["name"]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def stream_logs(pod_name, namespace)
|
|
77
|
+
follow = @command_options.fetch(:follow, true)
|
|
78
|
+
tail = @command_options.fetch(:tail, 100)
|
|
79
|
+
container = @command_options[:container]
|
|
80
|
+
|
|
81
|
+
args = ["kubectl", "logs", pod_name, "-n", namespace]
|
|
82
|
+
args << "-f" if follow
|
|
83
|
+
args << "--tail=#{tail}"
|
|
84
|
+
|
|
85
|
+
if container
|
|
86
|
+
args << "-c"
|
|
87
|
+
args << container
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
io = IO.popen(args, "r")
|
|
91
|
+
|
|
92
|
+
Signal.trap("INT") { io.close rescue nil; exit 0 }
|
|
93
|
+
|
|
94
|
+
begin
|
|
95
|
+
io.each_line do |line|
|
|
96
|
+
puts highlight_line(line.chomp)
|
|
97
|
+
end
|
|
98
|
+
rescue Errno::EPIPE
|
|
99
|
+
# Clean exit on broken pipe
|
|
100
|
+
ensure
|
|
101
|
+
io.close rescue nil
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def highlight_line(line)
|
|
106
|
+
color_enabled = @ui.instance_variable_get(:@color)
|
|
107
|
+
return line unless color_enabled
|
|
108
|
+
|
|
109
|
+
if line =~ /\b(ERROR|FATAL|PANIC)\b/
|
|
110
|
+
"\e[31m#{line}\e[0m"
|
|
111
|
+
elsif line =~ /\b(WARN|WARNING)\b/
|
|
112
|
+
"\e[33m#{line}\e[0m"
|
|
113
|
+
else
|
|
114
|
+
line
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def resolve_deploy_dir(kdep_dir, deploy_name, discovery)
|
|
119
|
+
if deploy_name
|
|
120
|
+
deploy_dir = File.join(kdep_dir, deploy_name)
|
|
121
|
+
unless File.directory?(deploy_dir)
|
|
122
|
+
@ui.error("Deploy target not found: #{deploy_name}")
|
|
123
|
+
deploys = discovery.find_deploys
|
|
124
|
+
if deploys.any?
|
|
125
|
+
@ui.info("Available deploys: #{deploys.join(', ')}")
|
|
126
|
+
end
|
|
127
|
+
return nil
|
|
128
|
+
end
|
|
129
|
+
deploy_dir
|
|
130
|
+
else
|
|
131
|
+
deploys = discovery.find_deploys
|
|
132
|
+
if deploys.length == 1
|
|
133
|
+
File.join(kdep_dir, deploys[0])
|
|
134
|
+
elsif deploys.length > 1
|
|
135
|
+
@ui.error("Multiple deploys found, specify one: #{deploys.join(', ')}")
|
|
136
|
+
nil
|
|
137
|
+
else
|
|
138
|
+
@ui.error("No deploy targets found in kdep/")
|
|
139
|
+
nil
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
require "optparse"
|
|
2
|
+
|
|
3
|
+
module Kdep
|
|
4
|
+
module Commands
|
|
5
|
+
class Push
|
|
6
|
+
def self.option_parser
|
|
7
|
+
OptionParser.new do |opts|
|
|
8
|
+
opts.banner = "Usage: kdep push [deploy] [env]"
|
|
9
|
+
opts.separator ""
|
|
10
|
+
opts.separator "Pushes a built Docker image to the configured registry."
|
|
11
|
+
opts.separator ""
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def initialize(global_options:, command_options:, args:)
|
|
16
|
+
@global_options = global_options
|
|
17
|
+
@command_options = command_options
|
|
18
|
+
@args = args
|
|
19
|
+
@ui = Kdep::UI.new(color: false)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def execute
|
|
23
|
+
deploy_name = @args[0]
|
|
24
|
+
env = @args[1]
|
|
25
|
+
|
|
26
|
+
# Discover kdep/ directory
|
|
27
|
+
discovery = Kdep::Discovery.new
|
|
28
|
+
kdep_dir = discovery.find_kdep_dir
|
|
29
|
+
|
|
30
|
+
unless kdep_dir
|
|
31
|
+
@ui.error("No kdep/ directory found")
|
|
32
|
+
exit 1
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Resolve deploy directory
|
|
36
|
+
deploy_dir = resolve_deploy_dir(kdep_dir, deploy_name, discovery)
|
|
37
|
+
unless deploy_dir
|
|
38
|
+
exit 1
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Load config
|
|
42
|
+
config = Kdep::Config.new(deploy_dir, env).load
|
|
43
|
+
|
|
44
|
+
# Construct full image tag
|
|
45
|
+
image = config["image"] || config["name"]
|
|
46
|
+
tag = config["tag"] || "latest"
|
|
47
|
+
registry = config["registry"]
|
|
48
|
+
|
|
49
|
+
if registry && !registry.to_s.empty?
|
|
50
|
+
full_tag = "#{registry}/#{image}:#{tag}"
|
|
51
|
+
else
|
|
52
|
+
full_tag = "#{image}:#{tag}"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Push
|
|
56
|
+
begin
|
|
57
|
+
Kdep::Docker.push(full_tag)
|
|
58
|
+
@ui.info("Pushed: #{full_tag}")
|
|
59
|
+
rescue Kdep::Docker::Error => e
|
|
60
|
+
@ui.error(e.message)
|
|
61
|
+
exit 1
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def resolve_deploy_dir(kdep_dir, deploy_name, discovery)
|
|
68
|
+
if deploy_name
|
|
69
|
+
deploy_dir = File.join(kdep_dir, deploy_name)
|
|
70
|
+
unless File.directory?(deploy_dir)
|
|
71
|
+
@ui.error("Deploy target not found: #{deploy_name}")
|
|
72
|
+
deploys = discovery.find_deploys
|
|
73
|
+
if deploys.any?
|
|
74
|
+
@ui.info("Available deploys: #{deploys.join(', ')}")
|
|
75
|
+
end
|
|
76
|
+
return nil
|
|
77
|
+
end
|
|
78
|
+
deploy_dir
|
|
79
|
+
else
|
|
80
|
+
deploys = discovery.find_deploys
|
|
81
|
+
if deploys.length == 1
|
|
82
|
+
File.join(kdep_dir, deploys[0])
|
|
83
|
+
elsif deploys.length > 1
|
|
84
|
+
@ui.error("Multiple deploys found, specify one: #{deploys.join(', ')}")
|
|
85
|
+
nil
|
|
86
|
+
else
|
|
87
|
+
@ui.error("No deploy targets found in kdep/")
|
|
88
|
+
nil
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
require "optparse"
|
|
2
|
+
|
|
3
|
+
module Kdep
|
|
4
|
+
module Commands
|
|
5
|
+
class Render
|
|
6
|
+
def self.option_parser
|
|
7
|
+
OptionParser.new do |opts|
|
|
8
|
+
opts.banner = "Usage: kdep render [deploy] [env]"
|
|
9
|
+
opts.separator ""
|
|
10
|
+
opts.separator "Renders Kubernetes manifests for the specified deploy target and environment."
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def initialize(global_options:, command_options:, args:)
|
|
15
|
+
@global_options = global_options
|
|
16
|
+
@command_options = command_options
|
|
17
|
+
@args = args
|
|
18
|
+
@ui = Kdep::UI.new(color: false)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def execute
|
|
22
|
+
deploy_name = @args[0]
|
|
23
|
+
env = @args[1]
|
|
24
|
+
|
|
25
|
+
# Discover kdep/ directory
|
|
26
|
+
discovery = Kdep::Discovery.new
|
|
27
|
+
kdep_dir = discovery.find_kdep_dir
|
|
28
|
+
|
|
29
|
+
unless kdep_dir
|
|
30
|
+
@ui.error("No kdep/ directory found")
|
|
31
|
+
exit 1
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Resolve deploy directory
|
|
35
|
+
deploy_dir = resolve_deploy_dir(kdep_dir, deploy_name, discovery)
|
|
36
|
+
unless deploy_dir
|
|
37
|
+
exit 1
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Load config
|
|
41
|
+
config = Kdep::Config.new(deploy_dir, env).load
|
|
42
|
+
|
|
43
|
+
# Load preset resources
|
|
44
|
+
preset = Kdep::Preset.new(config["preset"], deploy_dir)
|
|
45
|
+
resources = preset.resources
|
|
46
|
+
|
|
47
|
+
# Determine output directory (.rendered/ in repo root)
|
|
48
|
+
repo_root = File.dirname(kdep_dir)
|
|
49
|
+
output_dir = File.join(repo_root, ".rendered")
|
|
50
|
+
|
|
51
|
+
# Set up writer and renderer
|
|
52
|
+
writer = Kdep::Writer.new(output_dir)
|
|
53
|
+
writer.clean
|
|
54
|
+
renderer = Kdep::Renderer.new(config, deploy_dir)
|
|
55
|
+
validator = Kdep::Validator.new
|
|
56
|
+
|
|
57
|
+
files_written = 0
|
|
58
|
+
errors = []
|
|
59
|
+
|
|
60
|
+
resources.each_with_index do |resource_name, idx|
|
|
61
|
+
index = idx + 1
|
|
62
|
+
|
|
63
|
+
# Render
|
|
64
|
+
begin
|
|
65
|
+
content = renderer.render_resource(resource_name)
|
|
66
|
+
rescue => e
|
|
67
|
+
errors << {"resource" => resource_name, "error" => e.message}
|
|
68
|
+
next
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Validate (skip empty content like ingress with no domains)
|
|
72
|
+
unless content.nil? || content.strip.empty?
|
|
73
|
+
result = validator.validate(content, resource_name)
|
|
74
|
+
unless result["valid"]
|
|
75
|
+
result["errors"].each do |err|
|
|
76
|
+
errors << {"resource" => resource_name, "error" => err}
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Write
|
|
82
|
+
path = writer.write(resource_name, content, index)
|
|
83
|
+
if path
|
|
84
|
+
relative_path = path.sub(repo_root + "/", "")
|
|
85
|
+
@ui.file_written(relative_path)
|
|
86
|
+
files_written += 1
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Print errors inline
|
|
91
|
+
errors.each do |err|
|
|
92
|
+
@ui.error("#{err["resource"]}: #{err["error"]}")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Print summary
|
|
96
|
+
@ui.summary(files_written, errors.length)
|
|
97
|
+
|
|
98
|
+
exit 1 if errors.length > 0
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
private
|
|
102
|
+
|
|
103
|
+
def resolve_deploy_dir(kdep_dir, deploy_name, discovery)
|
|
104
|
+
if deploy_name
|
|
105
|
+
deploy_dir = File.join(kdep_dir, deploy_name)
|
|
106
|
+
unless File.directory?(deploy_dir)
|
|
107
|
+
@ui.error("Deploy target not found: #{deploy_name}")
|
|
108
|
+
deploys = discovery.find_deploys
|
|
109
|
+
if deploys.any?
|
|
110
|
+
@ui.info("Available deploys: #{deploys.join(', ')}")
|
|
111
|
+
end
|
|
112
|
+
return nil
|
|
113
|
+
end
|
|
114
|
+
deploy_dir
|
|
115
|
+
else
|
|
116
|
+
deploys = discovery.find_deploys
|
|
117
|
+
if deploys.length == 1
|
|
118
|
+
File.join(kdep_dir, deploys[0])
|
|
119
|
+
elsif deploys.length > 1
|
|
120
|
+
@ui.error("Multiple deploys found, specify one: #{deploys.join(', ')}")
|
|
121
|
+
nil
|
|
122
|
+
else
|
|
123
|
+
@ui.error("No deploy targets found in kdep/")
|
|
124
|
+
nil
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|