kubernetes-cli 0.3.2 → 0.5.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.
@@ -1,65 +1,161 @@
1
+ # typed: strict
2
+
3
+ require 'json'
1
4
  require 'kubectl-rb'
2
5
  require 'open3'
6
+ require 'shellwords'
3
7
  require 'stringio'
4
- require 'json'
5
8
 
6
9
  class KubernetesCLI
10
+ # extend T::Sig
11
+
7
12
  class KubernetesError < StandardError; end
8
13
 
9
14
  class InvalidResourceError < KubernetesError
10
- attr_accessor :resource
15
+ # extend T::Sig
16
+
17
+ # T::Sig::WithoutRuntime.sig { returns(T.nilable(::KubeDSL::DSLObject)) }
18
+ attr_reader :resource
19
+
20
+ # T::Sig::WithoutRuntime.sig { params(resource: ::KubeDSL::DSLObject).returns(::KubeDSL::DSLObject) }
21
+ attr_writer :resource
22
+
23
+ # T::Sig::WithoutRuntime.sig { params(args: T.untyped).void }
24
+ def initialize(*args)
25
+ # @resource = T.let(@resource, T.nilable(::KubeDSL::DSLObject))
26
+ super
27
+ end
11
28
  end
12
29
 
13
30
  class InvalidResourceUriError < KubernetesError
14
- attr_accessor :resource_uri
31
+ # extend T::Sig
32
+
33
+ # T::Sig::WithoutRuntime.sig { returns(T.nilable(String)) }
34
+ attr_reader :resource_uri
35
+
36
+ # T::Sig::WithoutRuntime.sig { params(resource_uri: String).returns(String) }
37
+ attr_writer :resource_uri
38
+
39
+ # T::Sig::WithoutRuntime.sig { params(args: T.untyped).void }
40
+ def initialize(*args)
41
+ # @resource_uri = T.let(@resource_uri, T.nilable(String))
42
+ super
43
+ end
15
44
  end
16
45
 
17
46
  class GetResourceError < KubernetesError; end
47
+ class DeleteResourceError < KubernetesError; end
48
+ class PatchResourceError < KubernetesError; end
49
+ class AnnotateResourceError < KubernetesError; end
50
+ class GetVersionError < KubernetesError; end
18
51
 
19
52
  STATUS_KEY = :kubernetes_cli_last_status
20
53
  STDOUT_KEY = :kubernetes_cli_stdout
21
54
  STDERR_KEY = :kubernetes_cli_stderr
22
55
 
23
- attr_reader :kubeconfig_path, :executable
56
+ # T::Sig::WithoutRuntime.sig { returns(String) }
57
+ attr_reader :kubeconfig_path
58
+
59
+ # T::Sig::WithoutRuntime.sig { returns(String) }
60
+ attr_reader :executable
61
+
62
+ # BeforeCallback = T.type_alias { T.proc.params(cmd: T::Array[String]).void }
63
+ # AfterCallback = T.type_alias do
64
+ # T.proc.params(cmd: T::Array[String], last_status: Process::Status).void
65
+ # end
24
66
 
67
+ # T::Sig::WithoutRuntime.sig { params(kubeconfig_path: String, executable: String).void }
25
68
  def initialize(kubeconfig_path, executable = KubectlRb.executable)
26
69
  @kubeconfig_path = kubeconfig_path
27
70
  @executable = executable
28
71
  @before_execute = []
29
72
  @after_execute = []
73
+ # @env = T.let(@env, T.nilable(T::Hash[String, String]))
30
74
  end
31
75
 
76
+ # T::Sig::WithoutRuntime.sig { params(block: BeforeCallback).void }
32
77
  def before_execute(&block)
33
78
  @before_execute << block
34
79
  end
35
80
 
81
+ # T::Sig::WithoutRuntime.sig { params(block: AfterCallback).void }
36
82
  def after_execute(&block)
37
83
  @after_execute << block
38
84
  end
39
85
 
86
+ # T::Sig::WithoutRuntime.sig { returns(T.nilable(Process::Status)) }
40
87
  def last_status
41
88
  Thread.current[STATUS_KEY]
42
89
  end
43
90
 
91
+ # T::Sig::WithoutRuntime.sig { params(block: T.proc.params(last_status: Process::Status).void).void }
92
+ def with_last_status(&block)
93
+ block.call(last_status)
94
+ end
95
+
96
+ # T::Sig::WithoutRuntime.sig { params(block: T.proc.params(last_status: Process::Status).void).void }
97
+ def on_last_status_failure(&block)
98
+ with_last_status do |ls|
99
+ block.call(ls) unless ls.success?
100
+ end
101
+ end
102
+
103
+ # T::Sig::WithoutRuntime.sig { returns(T::Hash[T.untyped, T.untyped]) }
104
+ def version
105
+ cmd = [executable, '--kubeconfig', kubeconfig_path, 'version', '-o', 'json']
106
+ result = backticks(cmd)
107
+
108
+ on_last_status_failure do |last_status|
109
+ raise GetVersionError, "couldn't get version info: "\
110
+ "kubectl exited with status code #{last_status.exitstatus}"
111
+ end
112
+
113
+ JSON.parse(result)
114
+ end
115
+
116
+ # T::Sig::WithoutRuntime.sig { params(cmd: T.any(String, T::Array[String])).void }
44
117
  def run_cmd(cmd)
45
118
  cmd = [executable, '--kubeconfig', kubeconfig_path, *Array(cmd)]
46
119
  execc(cmd)
47
120
  end
48
121
 
49
- def exec_cmd(container_cmd, namespace, pod, tty = true)
122
+ # T::Sig::WithoutRuntime.sig {
123
+ # params(
124
+ # container_cmd: T.any(String, T::Array[String]),
125
+ # namespace: String,
126
+ # pod: String,
127
+ # tty: T::Boolean,
128
+ # container: T.nilable(String),
129
+ # out_file: T.nilable(String)
130
+ # ).void
131
+ # }
132
+ def exec_cmd(container_cmd, namespace, pod, tty = true, container = nil, out_file = nil)
50
133
  cmd = [executable, '--kubeconfig', kubeconfig_path, '-n', namespace, 'exec']
51
134
  cmd += ['-it'] if tty
135
+ cmd += ['-c', container] if container
52
136
  cmd += [pod, '--', *Array(container_cmd)]
137
+ cmd += ['>', out_file] if out_file
53
138
  execc(cmd)
54
139
  end
55
140
 
56
- def system_cmd(container_cmd, namespace, pod, tty = true)
141
+ # T::Sig::WithoutRuntime.sig {
142
+ # params(
143
+ # container_cmd: T.any(String, T::Array[String]),
144
+ # namespace: String,
145
+ # pod: String,
146
+ # tty: T::Boolean,
147
+ # container: T.nilable(String)
148
+ # ).void
149
+ # }
150
+ def system_cmd(container_cmd, namespace, pod, tty = true, container = nil)
57
151
  cmd = [executable, '--kubeconfig', kubeconfig_path, '-n', namespace, 'exec']
58
152
  cmd += ['-it'] if tty
153
+ cmd += ['-c', container] if container
59
154
  cmd += [pod, '--', *Array(container_cmd)]
60
155
  systemm(cmd)
61
156
  end
62
157
 
158
+ # T::Sig::WithoutRuntime.sig { params(res: ::KubeDSL::DSLObject, dry_run: T::Boolean).void }
63
159
  def apply(res, dry_run: false)
64
160
  cmd = [executable, '--kubeconfig', kubeconfig_path, 'apply', '--validate']
65
161
  cmd << '--dry-run=client' if dry_run
@@ -69,8 +165,8 @@ class KubernetesCLI
69
165
  stdin.puts(res.to_resource.to_yaml)
70
166
  end
71
167
 
72
- unless last_status.success?
73
- err = InvalidResourceError.new("Could not apply #{res.kind_sym.to_s.humanize.downcase} "\
168
+ on_last_status_failure do |last_status|
169
+ err = InvalidResourceError.new("Could not apply #{res.kind_sym} "\
74
170
  "'#{res.metadata.name}': kubectl exited with status code #{last_status.exitstatus}"
75
171
  )
76
172
 
@@ -79,13 +175,14 @@ class KubernetesCLI
79
175
  end
80
176
  end
81
177
 
178
+ # T::Sig::WithoutRuntime.sig { params(uri: String, dry_run: T::Boolean).void }
82
179
  def apply_uri(uri, dry_run: false)
83
180
  cmd = [executable, '--kubeconfig', kubeconfig_path, 'apply', '--validate']
84
181
  cmd << '--dry-run=client' if dry_run
85
182
  cmd += ['-f', uri]
86
183
  systemm(cmd)
87
184
 
88
- unless last_status.success?
185
+ on_last_status_failure do |last_status|
89
186
  err = InvalidResourceUriError.new("Could not apply #{uri}: "\
90
187
  "kubectl exited with status code #{last_status.exitstatus}"
91
188
  )
@@ -95,29 +192,48 @@ class KubernetesCLI
95
192
  end
96
193
  end
97
194
 
98
- def get_object(type, namespace, name = nil, match_labels = {})
99
- cmd = [executable, '--kubeconfig', kubeconfig_path, '-n', namespace]
195
+ # T::Sig::WithoutRuntime.sig {
196
+ # params(
197
+ # type: String,
198
+ # namespace: String,
199
+ # name: String
200
+ # ).returns(
201
+ # T::Hash[String, T.untyped]
202
+ # )
203
+ # }
204
+ def get_object(type, namespace, name)
205
+ cmd = [executable, '--kubeconfig', kubeconfig_path]
206
+ cmd += ['-n', namespace] if namespace
100
207
  cmd += ['get', type, name]
101
-
102
- unless match_labels.empty?
103
- cmd += ['--selector', match_labels.map { |key, value| "#{key}=#{value}" }.join(',')]
104
- end
105
-
106
208
  cmd += ['-o', 'json']
107
209
 
108
210
  result = backticks(cmd)
109
211
 
110
- unless last_status.success?
111
- raise GetResourceError, "couldn't get resources of type '#{type}' "\
212
+ on_last_status_failure do |last_status|
213
+ raise GetResourceError, "couldn't get resource of type '#{type}' named '#{name}' "\
112
214
  "in namespace #{namespace}: kubectl exited with status code #{last_status.exitstatus}"
113
215
  end
114
216
 
115
217
  JSON.parse(result)
116
218
  end
117
219
 
220
+ # T::Sig::WithoutRuntime.sig {
221
+ # params(
222
+ # type: String,
223
+ # namespace: T.any(String, Symbol),
224
+ # match_labels: T::Hash[String, String]
225
+ # ).returns(
226
+ # T::Array[T.untyped]
227
+ # )
228
+ # }
118
229
  def get_objects(type, namespace, match_labels = {})
119
- cmd = [executable, '--kubeconfig', kubeconfig_path, '-n', namespace]
120
- cmd += ['get', type]
230
+ cmd = [executable, '--kubeconfig', kubeconfig_path, 'get', type]
231
+
232
+ if namespace == :all
233
+ cmd << '--all-namespaces'
234
+ elsif namespace
235
+ cmd += ['-n', namespace.to_s]
236
+ end
121
237
 
122
238
  unless match_labels.empty?
123
239
  cmd += ['--selector', match_labels.map { |key, value| "#{key}=#{value}" }.join(',')]
@@ -127,7 +243,7 @@ class KubernetesCLI
127
243
 
128
244
  result = backticks(cmd)
129
245
 
130
- unless last_status.success?
246
+ on_last_status_failure do |last_status|
131
247
  raise GetResourceError, "couldn't get resources of type '#{type}' "\
132
248
  "in namespace #{namespace}: kubectl exited with status code #{last_status.exitstatus}"
133
249
  end
@@ -135,6 +251,89 @@ class KubernetesCLI
135
251
  JSON.parse(result)['items']
136
252
  end
137
253
 
254
+ # T::Sig::WithoutRuntime.sig {
255
+ # params(
256
+ # type: String,
257
+ # namespace: String,
258
+ # name: String
259
+ # ).void
260
+ # }
261
+ def delete_object(type, namespace, name)
262
+ cmd = [executable, '--kubeconfig', kubeconfig_path]
263
+ cmd += ['-n', namespace] if namespace
264
+ cmd += ['delete', type, name]
265
+
266
+ systemm(cmd)
267
+
268
+ on_last_status_failure do |last_status|
269
+ raise DeleteResourceError, "couldn't delete resource of type '#{type}' named '#{name}' "\
270
+ "in namespace #{namespace}: kubectl exited with status code #{last_status.exitstatus}"
271
+ end
272
+ end
273
+
274
+ # T::Sig::WithoutRuntime.sig {
275
+ # params(
276
+ # type: String,
277
+ # namespace: T.any(String, Symbol),
278
+ # match_labels: T::Hash[String, String]
279
+ # ).void
280
+ # }
281
+ def delete_objects(type, namespace, match_labels = {})
282
+ cmd = [executable, '--kubeconfig', kubeconfig_path]
283
+
284
+ if namespace == :all
285
+ cmd << '--all-namespaces'
286
+ elsif namespace
287
+ cmd += ['-n', namespace.to_s]
288
+ end
289
+
290
+ cmd += ['delete', type]
291
+
292
+ unless match_labels.empty?
293
+ cmd += ['--selector', match_labels.map { |key, value| "#{key}=#{value}" }.join(',')]
294
+ end
295
+
296
+ systemm(cmd)
297
+
298
+ on_last_status_failure do |last_status|
299
+ raise DeleteResourceError, "couldn't delete resources of type '#{type}' "\
300
+ "in namespace #{namespace}: kubectl exited with status code #{last_status.exitstatus}"
301
+ end
302
+ end
303
+
304
+ # T::Sig::WithoutRuntime.sig {
305
+ # params(
306
+ # type: String,
307
+ # namespace: String,
308
+ # name: String,
309
+ # patch_data: String,
310
+ # patch_type: String
311
+ # ).void
312
+ # }
313
+ def patch_object(type, namespace, name, patch_data, patch_type = 'merge')
314
+ cmd = [executable, '--kubeconfig', kubeconfig_path]
315
+ cmd += ['-n', namespace] if namespace
316
+ cmd += ['patch', type, name]
317
+ cmd += ['-p', Shellwords.shellescape(patch_data)]
318
+ cmd += ['--type', patch_type]
319
+
320
+ systemm(cmd)
321
+
322
+ on_last_status_failure do |last_status|
323
+ raise PatchResourceError, "couldn't patch resource of type '#{type}' named '#{name}' "\
324
+ "in namespace #{namespace}: kubectl exited with status code #{last_status.exitstatus}"
325
+ end
326
+ end
327
+
328
+ # T::Sig::WithoutRuntime.sig {
329
+ # params(
330
+ # type: String,
331
+ # namespace: String,
332
+ # name: String,
333
+ # annotations: T::Hash[String, String],
334
+ # overwrite: T::Boolean
335
+ # ).void
336
+ # }
138
337
  def annotate(type, namespace, name, annotations, overwrite: true)
139
338
  cmd = [
140
339
  executable,
@@ -152,12 +351,19 @@ class KubernetesCLI
152
351
 
153
352
  systemm(cmd)
154
353
 
155
- unless last_status.success?
156
- raise KubernetesError, "could not annotate resource '#{name}': kubectl "\
354
+ on_last_status_failure do |last_status|
355
+ raise AnnotateResourceError, "could not annotate resource '#{name}': kubectl "\
157
356
  "exited with status code #{last_status.exitstatus}"
158
357
  end
159
358
  end
160
359
 
360
+ # T::Sig::WithoutRuntime.sig {
361
+ # params(
362
+ # namespace: String,
363
+ # selector: T::Hash[String, String],
364
+ # follow: T::Boolean
365
+ # ).void
366
+ # }
161
367
  def logtail(namespace, selector, follow: true)
162
368
  cmd = [executable, '--kubeconfig', kubeconfig_path, '-n', namespace, 'logs']
163
369
  cmd << '-f' if follow
@@ -166,16 +372,18 @@ class KubernetesCLI
166
372
  execc(cmd)
167
373
  end
168
374
 
375
+ # T::Sig::WithoutRuntime.sig { returns(String) }
169
376
  def current_context
170
377
  cmd = [executable, '--kubeconfig', kubeconfig_path, 'config', 'current-context']
171
378
  backticks(cmd).strip
172
379
  end
173
380
 
381
+ # T::Sig::WithoutRuntime.sig { returns(String) }
174
382
  def api_resources
175
383
  cmd = [executable, '--kubeconfig', kubeconfig_path, 'api-resources']
176
384
  result = backticks(cmd)
177
385
 
178
- unless last_status.success?
386
+ on_last_status_failure do |last_status|
179
387
  raise KubernetesError, 'could not fetch API resources: kubectl exited with '\
180
388
  "status code #{last_status.exitstatus}. #{result}"
181
389
  end
@@ -183,6 +391,7 @@ class KubernetesCLI
183
391
  result
184
392
  end
185
393
 
394
+ # T::Sig::WithoutRuntime.sig { params(namespace: String, deployment: String).void }
186
395
  def restart_deployment(namespace, deployment)
187
396
  cmd = [
188
397
  executable,
@@ -193,13 +402,14 @@ class KubernetesCLI
193
402
 
194
403
  systemm(cmd)
195
404
 
196
- unless last_status.success?
405
+ on_last_status_failure do |last_status|
197
406
  raise KubernetesError, 'could not restart deployment: kubectl exited with '\
198
407
  "status code #{last_status.exitstatus}"
199
408
  end
200
409
  end
201
410
 
202
- def with_pipes(out = STDOUT, err = STDERR)
411
+ # T::Sig::WithoutRuntime.sig { params(out: T.any(StringIO, IO), err: T.any(StringIO, IO), block: T.proc.void).void }
412
+ def with_pipes(out = STDOUT, err = STDERR, &block)
203
413
  previous_stdout = self.stdout
204
414
  previous_stderr = self.stderr
205
415
  self.stdout = out
@@ -210,38 +420,46 @@ class KubernetesCLI
210
420
  self.stderr = previous_stderr
211
421
  end
212
422
 
423
+ # T::Sig::WithoutRuntime.sig { returns(T.any(StringIO, IO)) }
213
424
  def stdout
214
425
  Thread.current[STDOUT_KEY] || STDOUT
215
426
  end
216
427
 
428
+ # T::Sig::WithoutRuntime.sig { params(new_stdout: T.nilable(T.any(StringIO, IO))).void }
217
429
  def stdout=(new_stdout)
218
430
  Thread.current[STDOUT_KEY] = new_stdout
219
431
  end
220
432
 
433
+ # T::Sig::WithoutRuntime.sig { returns(T.any(StringIO, IO)) }
221
434
  def stderr
222
435
  Thread.current[STDERR_KEY] || STDERR
223
436
  end
224
437
 
438
+ # T::Sig::WithoutRuntime.sig { params(new_stderr: T.nilable(T.any(StringIO, IO))).void }
225
439
  def stderr=(new_stderr)
226
440
  Thread.current[STDERR_KEY] = new_stderr
227
441
  end
228
442
 
229
- private
230
-
443
+ # T::Sig::WithoutRuntime.sig { returns(T::Hash[String, String]) }
231
444
  def env
232
445
  @env ||= {}
233
446
  end
234
447
 
448
+ private
449
+
450
+ # T::Sig::WithoutRuntime.sig { returns(T::Array[String]) }
235
451
  def base_cmd
236
452
  [executable, '--kubeconfig', kubeconfig_path]
237
453
  end
238
454
 
455
+ # T::Sig::WithoutRuntime.sig { params(cmd: T::Array[String]).void }
239
456
  def execc(cmd)
240
457
  run_before_callbacks(cmd)
241
458
  cmd_s = cmd.join(' ')
242
- exec(cmd_s)
459
+ exec(env, cmd_s)
243
460
  end
244
461
 
462
+ # T::Sig::WithoutRuntime.sig { params(cmd: T::Array[String]).void }
245
463
  def systemm(cmd)
246
464
  if stdout == STDOUT && stderr == STDERR
247
465
  systemm_default(cmd)
@@ -250,20 +468,22 @@ class KubernetesCLI
250
468
  end
251
469
  end
252
470
 
471
+ # T::Sig::WithoutRuntime.sig { params(cmd: T::Array[String]).void }
253
472
  def systemm_default(cmd)
254
473
  run_before_callbacks(cmd)
255
474
  cmd_s = cmd.join(' ')
256
- system(cmd_s).tap do
475
+ system(env, cmd_s).tap do
257
476
  self.last_status = $?
258
477
  run_after_callbacks(cmd)
259
478
  end
260
479
  end
261
480
 
481
+ # T::Sig::WithoutRuntime.sig { params(cmd: T::Array[String]).void }
262
482
  def systemm_open3(cmd)
263
483
  run_before_callbacks(cmd)
264
484
  cmd_s = cmd.join(' ')
265
485
 
266
- Open3.popen3(cmd_s) do |p_stdin, p_stdout, p_stderr, wait_thread|
486
+ Open3.popen3(env, cmd_s) do |p_stdin, p_stdout, p_stderr, wait_thread|
267
487
  Thread.new(stdout) do |t_stdout|
268
488
  begin
269
489
  p_stdout.each { |line| t_stdout.puts(line) }
@@ -285,29 +505,18 @@ class KubernetesCLI
285
505
  end
286
506
  end
287
507
 
508
+ # T::Sig::WithoutRuntime.sig { params(cmd: T::Array[String]).returns(String) }
288
509
  def backticks(cmd)
289
- if stdout == STDOUT && stderr == STDERR
290
- backticks_default(cmd)
291
- else
292
- backticks_open3(cmd)
293
- end
294
- end
295
-
296
- def backticks_default(cmd)
297
- run_before_callbacks(cmd)
298
- cmd_s = cmd.join(' ')
299
- `#{cmd_s}`.tap do
300
- self.last_status = $?
301
- run_after_callbacks(cmd)
302
- end
510
+ backticks_open3(cmd)
303
511
  end
304
512
 
513
+ # T::Sig::WithoutRuntime.sig { params(cmd: T::Array[String]).returns(String) }
305
514
  def backticks_open3(cmd)
306
515
  run_before_callbacks(cmd)
307
516
  cmd_s = cmd.join(' ')
308
517
  result = StringIO.new
309
518
 
310
- Open3.popen3(cmd_s) do |p_stdin, p_stdout, p_stderr, wait_thread|
519
+ Open3.popen3(env, cmd_s) do |p_stdin, p_stdout, p_stderr, wait_thread|
311
520
  Thread.new do
312
521
  begin
313
522
  p_stdout.each { |line| result.puts(line) }
@@ -331,10 +540,20 @@ class KubernetesCLI
331
540
  result.string
332
541
  end
333
542
 
543
+ # T::Sig::WithoutRuntime.sig {
544
+ # params(
545
+ # env: T::Hash[String, String],
546
+ # cmd: T::Array[String],
547
+ # opts: T::Hash[Symbol, T.untyped],
548
+ # block: T.proc.params(p_stdin: IO).void
549
+ # ).void
550
+ # }
334
551
  def open3_w(env, cmd, opts = {}, &block)
335
552
  run_before_callbacks(cmd)
336
553
  cmd_s = cmd.join(' ')
337
554
 
555
+ # unsafes here b/c popen3 takes an optional first argument hash
556
+ # with environment variables, which confuses the sh*t out of sorbet
338
557
  Open3.popen3(env, cmd_s, opts) do |p_stdin, p_stdout, p_stderr, wait_thread|
339
558
  Thread.new(stdout) do |t_stdout|
340
559
  begin
@@ -350,23 +569,26 @@ class KubernetesCLI
350
569
  end
351
570
  end
352
571
 
353
- yield(p_stdin).tap do
354
- p_stdin.close
355
- self.last_status = wait_thread.value
356
- run_after_callbacks(cmd)
357
- wait_thread.join
358
- end
572
+ yield(p_stdin)
573
+
574
+ p_stdin.close
575
+ self.last_status = wait_thread.value
576
+ run_after_callbacks(cmd)
577
+ wait_thread.join
359
578
  end
360
579
  end
361
580
 
581
+ # T::Sig::WithoutRuntime.sig { params(cmd: T::Array[String]).void }
362
582
  def run_before_callbacks(cmd)
363
583
  @before_execute.each { |cb| cb.call(cmd) }
364
584
  end
365
585
 
586
+ # T::Sig::WithoutRuntime.sig { params(cmd: T::Array[String]).void }
366
587
  def run_after_callbacks(cmd)
367
588
  @after_execute.each { |cb| cb.call(cmd, last_status) }
368
589
  end
369
590
 
591
+ # T::Sig::WithoutRuntime.sig { params(status: Process::Status).void }
370
592
  def last_status=(status)
371
593
  Thread.current[STATUS_KEY] = status
372
594
  end