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,109 @@
|
|
|
1
|
+
require "optparse"
|
|
2
|
+
|
|
3
|
+
module Kdep
|
|
4
|
+
module Commands
|
|
5
|
+
class Restart
|
|
6
|
+
RESTARTABLE_PRESETS = %w[web worker].freeze
|
|
7
|
+
NON_RESTARTABLE_PRESETS = %w[job cronjob].freeze
|
|
8
|
+
|
|
9
|
+
def self.option_parser
|
|
10
|
+
OptionParser.new do |opts|
|
|
11
|
+
opts.banner = "Usage: kdep restart [deploy] [--type TYPE]"
|
|
12
|
+
opts.separator ""
|
|
13
|
+
opts.separator "Triggers a rolling restart of a Deployment or StatefulSet."
|
|
14
|
+
opts.separator ""
|
|
15
|
+
opts.on("--type TYPE", "Resource type: deployment, statefulset (overrides preset)") do |_|
|
|
16
|
+
# parsed via into: hash
|
|
17
|
+
end
|
|
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
|
+
|
|
31
|
+
# Discover kdep/ directory
|
|
32
|
+
discovery = Kdep::Discovery.new
|
|
33
|
+
kdep_dir = discovery.find_kdep_dir
|
|
34
|
+
unless kdep_dir
|
|
35
|
+
@ui.error("No kdep/ directory found")
|
|
36
|
+
exit 1
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Resolve deploy directory
|
|
40
|
+
deploy_dir = resolve_deploy_dir(kdep_dir, deploy_name, discovery)
|
|
41
|
+
unless deploy_dir
|
|
42
|
+
exit 1
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Load config
|
|
46
|
+
config = Kdep::Config.new(deploy_dir, nil).load
|
|
47
|
+
|
|
48
|
+
# Validate context
|
|
49
|
+
Kdep::ContextGuard.new(config["context"]).validate!
|
|
50
|
+
|
|
51
|
+
namespace = config["namespace"]
|
|
52
|
+
app_name = config["name"]
|
|
53
|
+
preset = config["preset"]
|
|
54
|
+
|
|
55
|
+
# Determine resource type
|
|
56
|
+
resource_type = determine_resource_type(preset)
|
|
57
|
+
|
|
58
|
+
# Execute rollout restart
|
|
59
|
+
Kdep::Kubectl.run("rollout", "restart", "#{resource_type}/#{app_name}", "-n", namespace)
|
|
60
|
+
@ui.info("Restarted #{resource_type}/#{app_name} in #{namespace}")
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def determine_resource_type(preset)
|
|
66
|
+
if @command_options[:type]
|
|
67
|
+
return @command_options[:type]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
resource_type_for_preset(preset)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def resource_type_for_preset(preset)
|
|
74
|
+
if NON_RESTARTABLE_PRESETS.include?(preset)
|
|
75
|
+
@ui.error("#{preset} workloads cannot be restarted (use --type to override)")
|
|
76
|
+
exit 1
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
"deployment"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def resolve_deploy_dir(kdep_dir, deploy_name, discovery)
|
|
83
|
+
if deploy_name
|
|
84
|
+
deploy_dir = File.join(kdep_dir, deploy_name)
|
|
85
|
+
unless File.directory?(deploy_dir)
|
|
86
|
+
@ui.error("Deploy target not found: #{deploy_name}")
|
|
87
|
+
deploys = discovery.find_deploys
|
|
88
|
+
if deploys.any?
|
|
89
|
+
@ui.info("Available deploys: #{deploys.join(', ')}")
|
|
90
|
+
end
|
|
91
|
+
return nil
|
|
92
|
+
end
|
|
93
|
+
deploy_dir
|
|
94
|
+
else
|
|
95
|
+
deploys = discovery.find_deploys
|
|
96
|
+
if deploys.length == 1
|
|
97
|
+
File.join(kdep_dir, deploys[0])
|
|
98
|
+
elsif deploys.length > 1
|
|
99
|
+
@ui.error("Multiple deploys found, specify one: #{deploys.join(', ')}")
|
|
100
|
+
nil
|
|
101
|
+
else
|
|
102
|
+
@ui.error("No deploy targets found in kdep/")
|
|
103
|
+
nil
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
require "optparse"
|
|
2
|
+
|
|
3
|
+
module Kdep
|
|
4
|
+
module Commands
|
|
5
|
+
class Scale
|
|
6
|
+
NON_SCALABLE_PRESETS = %w[job cronjob].freeze
|
|
7
|
+
|
|
8
|
+
def self.option_parser
|
|
9
|
+
OptionParser.new do |opts|
|
|
10
|
+
opts.banner = "Usage: kdep scale [deploy] N [--type TYPE]"
|
|
11
|
+
opts.separator ""
|
|
12
|
+
opts.separator "Scales a Deployment or StatefulSet to N replicas."
|
|
13
|
+
opts.separator ""
|
|
14
|
+
opts.on("--type TYPE", "Resource type: deployment, statefulset (overrides preset)") do |_|
|
|
15
|
+
# parsed via into: hash
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def initialize(global_options:, command_options:, args:)
|
|
21
|
+
@global_options = global_options
|
|
22
|
+
@command_options = command_options
|
|
23
|
+
@args = args
|
|
24
|
+
@ui = Kdep::UI.new(color: false)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def execute
|
|
28
|
+
if @args.length == 1 && @args[0] && @args[0].match?(/\A\d+\z/)
|
|
29
|
+
deploy_name = nil
|
|
30
|
+
@replica_count = @args[0].to_i
|
|
31
|
+
else
|
|
32
|
+
deploy_name = @args[0]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Discover kdep/ directory
|
|
36
|
+
discovery = Kdep::Discovery.new
|
|
37
|
+
kdep_dir = discovery.find_kdep_dir
|
|
38
|
+
unless kdep_dir
|
|
39
|
+
@ui.error("No kdep/ directory found")
|
|
40
|
+
exit 1
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Resolve deploy directory
|
|
44
|
+
deploy_dir = resolve_deploy_dir(kdep_dir, deploy_name, discovery)
|
|
45
|
+
unless deploy_dir
|
|
46
|
+
exit 1
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Load config
|
|
50
|
+
config = Kdep::Config.new(deploy_dir, nil).load
|
|
51
|
+
|
|
52
|
+
# Validate context
|
|
53
|
+
Kdep::ContextGuard.new(config["context"]).validate!
|
|
54
|
+
|
|
55
|
+
namespace = config["namespace"]
|
|
56
|
+
app_name = config["name"]
|
|
57
|
+
preset = config["preset"]
|
|
58
|
+
|
|
59
|
+
# Parse and validate replica count
|
|
60
|
+
replicas = parse_replicas
|
|
61
|
+
unless replicas
|
|
62
|
+
@ui.error("Usage: kdep scale [deploy] N -- replica count required")
|
|
63
|
+
exit 1
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Determine resource type
|
|
67
|
+
resource_type = determine_resource_type(preset)
|
|
68
|
+
|
|
69
|
+
# Execute scale
|
|
70
|
+
Kdep::Kubectl.run("scale", "#{resource_type}/#{app_name}", "--replicas=#{replicas}", "-n", namespace)
|
|
71
|
+
@ui.info("Scaled #{resource_type}/#{app_name} to #{replicas} replicas in #{namespace}")
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def parse_replicas
|
|
77
|
+
return @replica_count if @replica_count
|
|
78
|
+
|
|
79
|
+
# Second arg should be the replica count (first arg is deploy name)
|
|
80
|
+
replica_str = @args[1]
|
|
81
|
+
return nil unless replica_str
|
|
82
|
+
|
|
83
|
+
# Must be a non-negative integer
|
|
84
|
+
unless replica_str.match?(/\A\d+\z/)
|
|
85
|
+
@ui.error("Invalid replica count: #{replica_str} (must be a non-negative integer)")
|
|
86
|
+
exit 1
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
replica_str.to_i
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def determine_resource_type(preset)
|
|
93
|
+
if @command_options[:type]
|
|
94
|
+
return @command_options[:type]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
resource_type_for_preset(preset)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def resource_type_for_preset(preset)
|
|
101
|
+
if NON_SCALABLE_PRESETS.include?(preset)
|
|
102
|
+
@ui.error("#{preset} workloads cannot be scaled (use --type to override)")
|
|
103
|
+
exit 1
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
"deployment"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def resolve_deploy_dir(kdep_dir, deploy_name, discovery)
|
|
110
|
+
if deploy_name
|
|
111
|
+
deploy_dir = File.join(kdep_dir, deploy_name)
|
|
112
|
+
unless File.directory?(deploy_dir)
|
|
113
|
+
@ui.error("Deploy target not found: #{deploy_name}")
|
|
114
|
+
deploys = discovery.find_deploys
|
|
115
|
+
if deploys.any?
|
|
116
|
+
@ui.info("Available deploys: #{deploys.join(', ')}")
|
|
117
|
+
end
|
|
118
|
+
return nil
|
|
119
|
+
end
|
|
120
|
+
deploy_dir
|
|
121
|
+
else
|
|
122
|
+
deploys = discovery.find_deploys
|
|
123
|
+
if deploys.length == 1
|
|
124
|
+
File.join(kdep_dir, deploys[0])
|
|
125
|
+
elsif deploys.length > 1
|
|
126
|
+
@ui.error("Multiple deploys found, specify one: #{deploys.join(', ')}")
|
|
127
|
+
nil
|
|
128
|
+
else
|
|
129
|
+
@ui.error("No deploy targets found in kdep/")
|
|
130
|
+
nil
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
require "optparse"
|
|
2
|
+
require "json"
|
|
3
|
+
require "base64"
|
|
4
|
+
|
|
5
|
+
module Kdep
|
|
6
|
+
module Commands
|
|
7
|
+
class Secrets
|
|
8
|
+
def self.option_parser
|
|
9
|
+
OptionParser.new do |opts|
|
|
10
|
+
opts.banner = "Usage: kdep secrets <list|create|check> [deploy]"
|
|
11
|
+
opts.separator ""
|
|
12
|
+
opts.separator "Manage image pull secrets in the deploy namespace."
|
|
13
|
+
opts.separator ""
|
|
14
|
+
opts.separator "Sub-commands:"
|
|
15
|
+
opts.separator " list List image pull secrets"
|
|
16
|
+
opts.separator " create Create a docker-registry secret"
|
|
17
|
+
opts.separator " check Decode and inspect an existing secret"
|
|
18
|
+
opts.separator ""
|
|
19
|
+
opts.separator "Options:"
|
|
20
|
+
opts.on("--server=URL", "Docker registry server URL")
|
|
21
|
+
opts.on("--username=NAME", "Registry username")
|
|
22
|
+
opts.on("--password=PASS", "Registry password")
|
|
23
|
+
opts.on("--name=SECRET_NAME", "Secret name (default: registry-credentials)")
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def initialize(global_options:, command_options:, args:)
|
|
28
|
+
@global_options = global_options
|
|
29
|
+
@command_options = command_options
|
|
30
|
+
@args = args
|
|
31
|
+
@ui = Kdep::UI.new(color: false)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def execute
|
|
35
|
+
sub = @args.shift # Sub-command first (list, create, check)
|
|
36
|
+
deploy_name = @args.shift # Deploy name second (optional)
|
|
37
|
+
|
|
38
|
+
# If no sub-command given, show usage
|
|
39
|
+
unless sub
|
|
40
|
+
show_usage
|
|
41
|
+
return
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Only proceed with deploy resolution for known sub-commands
|
|
45
|
+
unless %w[list create check].include?(sub)
|
|
46
|
+
show_usage
|
|
47
|
+
return
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Discover kdep/ directory
|
|
51
|
+
discovery = Kdep::Discovery.new
|
|
52
|
+
kdep_dir = discovery.find_kdep_dir
|
|
53
|
+
unless kdep_dir
|
|
54
|
+
@ui.error("No kdep/ directory found")
|
|
55
|
+
exit 1
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Resolve deploy directory
|
|
59
|
+
deploy_dir = resolve_deploy_dir(kdep_dir, deploy_name, discovery)
|
|
60
|
+
unless deploy_dir
|
|
61
|
+
exit 1
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Load config
|
|
65
|
+
@config = Kdep::Config.new(deploy_dir, nil).load
|
|
66
|
+
|
|
67
|
+
# Validate context
|
|
68
|
+
Kdep::ContextGuard.new(@config["context"]).validate!
|
|
69
|
+
|
|
70
|
+
@namespace = @config["namespace"]
|
|
71
|
+
|
|
72
|
+
# Dispatch sub-command
|
|
73
|
+
case sub
|
|
74
|
+
when "list" then list_secrets
|
|
75
|
+
when "create" then create_secret
|
|
76
|
+
when "check" then check_secrets
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
def list_secrets
|
|
83
|
+
data = Kdep::Kubectl.run_json(
|
|
84
|
+
"get", "secrets", "-n", @namespace,
|
|
85
|
+
"--field-selector", "type=kubernetes.io/dockerconfigjson"
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
items = data["items"]
|
|
89
|
+
if items.nil? || items.empty?
|
|
90
|
+
@ui.info("No image pull secrets in #{@namespace}")
|
|
91
|
+
return
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
items.each do |secret|
|
|
95
|
+
name = secret["metadata"]["name"]
|
|
96
|
+
created = secret["metadata"]["creationTimestamp"]
|
|
97
|
+
@ui.info("#{name} created: #{created}")
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def create_secret
|
|
102
|
+
server = @command_options[:server]
|
|
103
|
+
username = @command_options[:username]
|
|
104
|
+
password = @command_options[:password]
|
|
105
|
+
secret_name = @command_options[:name] || "registry-credentials"
|
|
106
|
+
|
|
107
|
+
# Default server from config registry if available
|
|
108
|
+
if server.nil? && @config["registry"]
|
|
109
|
+
# Extract hostname from registry (e.g., "ghcr.io/myorg" -> "ghcr.io")
|
|
110
|
+
registry = @config["registry"]
|
|
111
|
+
server = registry.include?("/") ? registry.split("/").first : registry
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Validate required flags
|
|
115
|
+
missing = []
|
|
116
|
+
missing << "--username" unless username
|
|
117
|
+
missing << "--password" unless password
|
|
118
|
+
missing << "--server" unless server
|
|
119
|
+
|
|
120
|
+
unless missing.empty?
|
|
121
|
+
@ui.error("Missing required flags: #{missing.join(', ')}")
|
|
122
|
+
@ui.info("Usage: kdep secrets create [deploy] --server=URL --username=NAME --password=PASS [--name=SECRET_NAME]")
|
|
123
|
+
exit 1
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
Kdep::Kubectl.run(
|
|
127
|
+
"create", "secret", "docker-registry", secret_name,
|
|
128
|
+
"--docker-server=#{server}",
|
|
129
|
+
"--docker-username=#{username}",
|
|
130
|
+
"--docker-password=#{password}",
|
|
131
|
+
"-n", @namespace
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
@ui.info("Created secret #{secret_name} in #{@namespace}")
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def check_secrets
|
|
138
|
+
secret_name = @command_options[:name] || @args.shift
|
|
139
|
+
|
|
140
|
+
unless secret_name
|
|
141
|
+
# No name given; list secrets and check all found
|
|
142
|
+
data = Kdep::Kubectl.run_json(
|
|
143
|
+
"get", "secrets", "-n", @namespace,
|
|
144
|
+
"--field-selector", "type=kubernetes.io/dockerconfigjson"
|
|
145
|
+
)
|
|
146
|
+
items = data["items"]
|
|
147
|
+
if items.nil? || items.empty?
|
|
148
|
+
@ui.info("No image pull secrets found in #{@namespace}")
|
|
149
|
+
return
|
|
150
|
+
end
|
|
151
|
+
items.each do |secret|
|
|
152
|
+
check_single_secret(secret["metadata"]["name"])
|
|
153
|
+
end
|
|
154
|
+
return
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
check_single_secret(secret_name)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def check_single_secret(secret_name)
|
|
161
|
+
data = Kdep::Kubectl.run_json("get", "secret", secret_name, "-n", @namespace)
|
|
162
|
+
|
|
163
|
+
raw = data["data"] && data["data"][".dockerconfigjson"]
|
|
164
|
+
unless raw
|
|
165
|
+
@ui.error("Secret #{secret_name} does not contain .dockerconfigjson")
|
|
166
|
+
return
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
begin
|
|
170
|
+
decoded = Base64.decode64(raw)
|
|
171
|
+
config = JSON.parse(decoded)
|
|
172
|
+
auths = config["auths"] || {}
|
|
173
|
+
|
|
174
|
+
if auths.empty?
|
|
175
|
+
@ui.info("Secret #{secret_name}: no auth entries found")
|
|
176
|
+
return
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
auths.each_key do |server|
|
|
180
|
+
@ui.info("Found credentials for #{server}")
|
|
181
|
+
end
|
|
182
|
+
rescue JSON::ParserError => e
|
|
183
|
+
@ui.error("Failed to parse dockerconfigjson from #{secret_name}: #{e.message}")
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def show_usage
|
|
188
|
+
@ui.info("Usage: kdep secrets <sub-command> [deploy]")
|
|
189
|
+
@ui.info("")
|
|
190
|
+
@ui.info("Sub-commands:")
|
|
191
|
+
@ui.info(" list List image pull secrets")
|
|
192
|
+
@ui.info(" create Create a docker-registry secret")
|
|
193
|
+
@ui.info(" check Decode and inspect an existing secret")
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def resolve_deploy_dir(kdep_dir, deploy_name, discovery)
|
|
197
|
+
if deploy_name
|
|
198
|
+
deploy_dir = File.join(kdep_dir, deploy_name)
|
|
199
|
+
unless File.directory?(deploy_dir)
|
|
200
|
+
@ui.error("Deploy target not found: #{deploy_name}")
|
|
201
|
+
deploys = discovery.find_deploys
|
|
202
|
+
if deploys.any?
|
|
203
|
+
@ui.info("Available deploys: #{deploys.join(', ')}")
|
|
204
|
+
end
|
|
205
|
+
return nil
|
|
206
|
+
end
|
|
207
|
+
deploy_dir
|
|
208
|
+
else
|
|
209
|
+
deploys = discovery.find_deploys
|
|
210
|
+
if deploys.length == 1
|
|
211
|
+
File.join(kdep_dir, deploys[0])
|
|
212
|
+
elsif deploys.length > 1
|
|
213
|
+
@ui.error("Multiple deploys found, specify one: #{deploys.join(', ')}")
|
|
214
|
+
nil
|
|
215
|
+
else
|
|
216
|
+
@ui.error("No deploy targets found in kdep/")
|
|
217
|
+
nil
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
require "optparse"
|
|
2
|
+
|
|
3
|
+
module Kdep
|
|
4
|
+
module Commands
|
|
5
|
+
class Sh
|
|
6
|
+
def self.option_parser
|
|
7
|
+
OptionParser.new do |opts|
|
|
8
|
+
opts.banner = "Usage: kdep sh [deploy] [container]"
|
|
9
|
+
opts.separator ""
|
|
10
|
+
opts.separator "Opens an interactive shell inside a running pod."
|
|
11
|
+
opts.separator ""
|
|
12
|
+
opts.on("-c", "--container=NAME", "Container name for multi-container pods")
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def initialize(global_options:, command_options:, args:)
|
|
17
|
+
@global_options = global_options
|
|
18
|
+
@command_options = command_options
|
|
19
|
+
@args = args
|
|
20
|
+
@ui = Kdep::UI.new(color: false)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def execute
|
|
24
|
+
deploy_name = @args[0]
|
|
25
|
+
|
|
26
|
+
# Discover kdep/ directory
|
|
27
|
+
discovery = Kdep::Discovery.new
|
|
28
|
+
kdep_dir = discovery.find_kdep_dir
|
|
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, nil).load
|
|
42
|
+
|
|
43
|
+
# Validate context
|
|
44
|
+
Kdep::ContextGuard.new(config["context"]).validate!
|
|
45
|
+
|
|
46
|
+
# Find running pod
|
|
47
|
+
namespace = config["namespace"]
|
|
48
|
+
app_name = config["name"]
|
|
49
|
+
pod_name = find_pod(namespace, app_name)
|
|
50
|
+
|
|
51
|
+
# Exec into pod
|
|
52
|
+
exec_shell(pod_name, namespace)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def find_pod(namespace, app_name)
|
|
58
|
+
data = Kdep::Kubectl.run_json(
|
|
59
|
+
"get", "pods",
|
|
60
|
+
"-n", namespace,
|
|
61
|
+
"-l", "app=#{app_name}",
|
|
62
|
+
"--field-selector", "status.phase=Running"
|
|
63
|
+
)
|
|
64
|
+
pods = data["items"]
|
|
65
|
+
|
|
66
|
+
if pods.nil? || pods.empty?
|
|
67
|
+
@ui.error("No running pods found for #{app_name} in #{namespace}")
|
|
68
|
+
exit 1
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
pods[0]["metadata"]["name"]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def exec_shell(pod_name, namespace)
|
|
75
|
+
container = @command_options[:container]
|
|
76
|
+
|
|
77
|
+
args = ["kubectl", "exec", "-it", pod_name, "-n", namespace]
|
|
78
|
+
|
|
79
|
+
if container
|
|
80
|
+
args << "-c"
|
|
81
|
+
args << container
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
args << "--"
|
|
85
|
+
args << "/bin/sh"
|
|
86
|
+
|
|
87
|
+
Kernel.exec(*args)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def resolve_deploy_dir(kdep_dir, deploy_name, discovery)
|
|
91
|
+
if deploy_name
|
|
92
|
+
deploy_dir = File.join(kdep_dir, deploy_name)
|
|
93
|
+
unless File.directory?(deploy_dir)
|
|
94
|
+
@ui.error("Deploy target not found: #{deploy_name}")
|
|
95
|
+
deploys = discovery.find_deploys
|
|
96
|
+
if deploys.any?
|
|
97
|
+
@ui.info("Available deploys: #{deploys.join(', ')}")
|
|
98
|
+
end
|
|
99
|
+
return nil
|
|
100
|
+
end
|
|
101
|
+
deploy_dir
|
|
102
|
+
else
|
|
103
|
+
deploys = discovery.find_deploys
|
|
104
|
+
if deploys.length == 1
|
|
105
|
+
File.join(kdep_dir, deploys[0])
|
|
106
|
+
elsif deploys.length > 1
|
|
107
|
+
@ui.error("Multiple deploys found, specify one: #{deploys.join(', ')}")
|
|
108
|
+
nil
|
|
109
|
+
else
|
|
110
|
+
@ui.error("No deploy targets found in kdep/")
|
|
111
|
+
nil
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|