kubernetes-cli 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/Gemfile +14 -3
- data/README.md +49 -0
- data/Rakefile +3 -1
- data/kubernetes-cli.gemspec +2 -2
- data/lib/kubernetes-cli/version.rb +3 -1
- data/lib/kubernetes-cli.rb +302 -31
- data/rbi/kubernetes-cli.rbi +219 -0
- data/spec/cli_spec.rb +597 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/support/matchers.rb +122 -0
- data/spec/support/test_cli.rb +37 -0
- data/spec/support/test_config_map.yaml +7 -0
- data/spec/support/test_config_map_bad.yaml +8 -0
- data/spec/support/test_resource.rb +22 -0
- metadata +14 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b93bf8e3f620c17f2781d2eb5f2748f970982a854d0aa622d3313b1d74104d3b
|
4
|
+
data.tar.gz: 9c7d170e2ab873b66cfbf6a12532260e3442781330c9e96363e472d354ff997a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0156747413340bab08b571bf13db212d0a8397ceb0b733fbc152c7835080f203400f6f1554829c74dab4fe1b743b3f3eda3d2e11950896f01da6b9bedf5bc3ca
|
7
|
+
data.tar.gz: c21786be60e403d60d2c39fd7be371a7043539412000e9cff2ea197e37092fe38a2bc621a6413266aabd0b8633656375056ce46745ac65726eb08c3e31cd14e3
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
## 0.4.0
|
2
|
+
* Add `#version` method to get k8s client/server version info.
|
3
|
+
* Add Sorbet type definitions.
|
4
|
+
* Add tests.
|
5
|
+
|
6
|
+
## 0.3.2
|
7
|
+
* Add missing require statement.
|
8
|
+
|
9
|
+
## 0.3.1
|
10
|
+
* Fix issue restricting Docker CLI output.
|
11
|
+
|
1
12
|
## 0.3.0
|
2
13
|
* Add ability to redirect kubectl's stdout and stderr streams.
|
3
14
|
|
data/Gemfile
CHANGED
@@ -6,10 +6,21 @@ gemspec
|
|
6
6
|
# See: https://github.com/rubygems/rubygems/issues/3646
|
7
7
|
gem 'kubectl-rb'
|
8
8
|
|
9
|
+
group :test do
|
10
|
+
gem 'rspec'
|
11
|
+
gem 'kind-rb', '~> 0.1'
|
12
|
+
gem 'kube-dsl', '~> 0.6'
|
13
|
+
end
|
14
|
+
|
9
15
|
group :development do
|
10
|
-
gem '
|
16
|
+
gem 'curdle', '~> 1.0'
|
17
|
+
|
18
|
+
# lock to same version as kuby-core
|
19
|
+
gem 'sorbet', '= 0.5.6433'
|
20
|
+
gem 'parlour', '~> 6.0'
|
11
21
|
end
|
12
22
|
|
13
|
-
group :test do
|
14
|
-
gem '
|
23
|
+
group :development, :test do
|
24
|
+
gem 'pry-byebug'
|
25
|
+
gem 'rake'
|
15
26
|
end
|
data/README.md
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
## kubernetes-cli
|
2
|
+
|
3
|
+
![Unit Tests](https://github.com/getkuby/kuby-core/actions/workflows/unit_tests.yml/badge.svg?branch=master)
|
4
|
+
![Integration Tests](https://github.com/getkuby/kuby-core/actions/workflows/integration_tests.yml/badge.svg?branch=master)
|
5
|
+
|
6
|
+
A Ruby wrapper around the Kubernetes CLI.
|
7
|
+
|
8
|
+
### Usage
|
9
|
+
|
10
|
+
Create a new instance by passing the path to your Kube config (usually ~/.kube/config) and optionally the path to the kubectl executable (by default, the executable path comes from the [kubectl-rb gem](https://github.com/getkuby/kubectl-rb)).
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
cli = KubernetesCLI(File.join(ENV['HOME'], '.kube', 'config'))
|
14
|
+
```
|
15
|
+
|
16
|
+
### Available Methods
|
17
|
+
|
18
|
+
- `annotate`
|
19
|
+
- `api_resources`
|
20
|
+
- `apply`
|
21
|
+
- `apply_uri`
|
22
|
+
- `current_context`
|
23
|
+
- `delete_object`
|
24
|
+
- `delete_objects`
|
25
|
+
- `exec_cmd`
|
26
|
+
- `executable`
|
27
|
+
- `get_object`
|
28
|
+
- `get_objects`
|
29
|
+
- `kubeconfig_path`
|
30
|
+
- `last_status`
|
31
|
+
- `logtail`
|
32
|
+
- `patch_object`
|
33
|
+
- `restart_deployment`
|
34
|
+
- `run_cmd`
|
35
|
+
- `system_cmd`
|
36
|
+
|
37
|
+
Please see the source code for available options.
|
38
|
+
|
39
|
+
## Running Tests
|
40
|
+
|
41
|
+
`bundle exec rspec` should do the trick. Requires that you have Docker installed.
|
42
|
+
|
43
|
+
## License
|
44
|
+
|
45
|
+
Licensed under the MIT license. See LICENSE for details.
|
46
|
+
|
47
|
+
## Authors
|
48
|
+
|
49
|
+
* Cameron C. Dutro: http://github.com/camertron
|
data/Rakefile
CHANGED
data/kubernetes-cli.gemspec
CHANGED
@@ -11,8 +11,8 @@ Gem::Specification.new do |s|
|
|
11
11
|
|
12
12
|
s.description = s.summary = 'Ruby wrapper around the Kubernetes CLI.'
|
13
13
|
|
14
|
-
s.add_dependency 'kubectl-rb', '~> 0.
|
14
|
+
s.add_dependency 'kubectl-rb', '~> 0.2'
|
15
15
|
|
16
16
|
s.require_path = 'lib'
|
17
|
-
s.files = Dir['{lib,spec,
|
17
|
+
s.files = Dir['{lib,spec,rbi}/**/*', 'Gemfile', 'LICENSE', 'CHANGELOG.md', 'README.md', 'Rakefile', 'kubernetes-cli.gemspec']
|
18
18
|
end
|
data/lib/kubernetes-cli.rb
CHANGED
@@ -1,64 +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
8
|
|
5
9
|
class KubernetesCLI
|
10
|
+
# extend T::Sig
|
11
|
+
|
6
12
|
class KubernetesError < StandardError; end
|
7
13
|
|
8
14
|
class InvalidResourceError < KubernetesError
|
9
|
-
|
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
|
10
28
|
end
|
11
29
|
|
12
30
|
class InvalidResourceUriError < KubernetesError
|
13
|
-
|
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
|
14
44
|
end
|
15
45
|
|
16
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
|
17
51
|
|
18
52
|
STATUS_KEY = :kubernetes_cli_last_status
|
19
53
|
STDOUT_KEY = :kubernetes_cli_stdout
|
20
54
|
STDERR_KEY = :kubernetes_cli_stderr
|
21
55
|
|
22
|
-
|
56
|
+
# T::Sig::WithoutRuntime.sig { returns(String) }
|
57
|
+
attr_reader :kubeconfig_path
|
58
|
+
|
59
|
+
# T::Sig::WithoutRuntime.sig { returns(String) }
|
60
|
+
attr_reader :executable
|
23
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
|
66
|
+
|
67
|
+
# T::Sig::WithoutRuntime.sig { params(kubeconfig_path: String, executable: String).void }
|
24
68
|
def initialize(kubeconfig_path, executable = KubectlRb.executable)
|
25
69
|
@kubeconfig_path = kubeconfig_path
|
26
70
|
@executable = executable
|
27
71
|
@before_execute = []
|
28
72
|
@after_execute = []
|
73
|
+
# @env = T.let(@env, T.nilable(T::Hash[String, String]))
|
29
74
|
end
|
30
75
|
|
76
|
+
# T::Sig::WithoutRuntime.sig { params(block: BeforeCallback).void }
|
31
77
|
def before_execute(&block)
|
32
78
|
@before_execute << block
|
33
79
|
end
|
34
80
|
|
81
|
+
# T::Sig::WithoutRuntime.sig { params(block: AfterCallback).void }
|
35
82
|
def after_execute(&block)
|
36
83
|
@after_execute << block
|
37
84
|
end
|
38
85
|
|
86
|
+
# T::Sig::WithoutRuntime.sig { returns(T.nilable(Process::Status)) }
|
39
87
|
def last_status
|
40
88
|
Thread.current[STATUS_KEY]
|
41
89
|
end
|
42
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 }
|
43
117
|
def run_cmd(cmd)
|
44
118
|
cmd = [executable, '--kubeconfig', kubeconfig_path, *Array(cmd)]
|
45
119
|
execc(cmd)
|
46
120
|
end
|
47
121
|
|
48
|
-
|
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)
|
49
133
|
cmd = [executable, '--kubeconfig', kubeconfig_path, '-n', namespace, 'exec']
|
50
134
|
cmd += ['-it'] if tty
|
135
|
+
cmd += ['-c', container] if container
|
51
136
|
cmd += [pod, '--', *Array(container_cmd)]
|
137
|
+
cmd += ['>', out_file] if out_file
|
52
138
|
execc(cmd)
|
53
139
|
end
|
54
140
|
|
55
|
-
|
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)
|
56
151
|
cmd = [executable, '--kubeconfig', kubeconfig_path, '-n', namespace, 'exec']
|
57
152
|
cmd += ['-it'] if tty
|
153
|
+
cmd += ['-c', container] if container
|
58
154
|
cmd += [pod, '--', *Array(container_cmd)]
|
59
155
|
systemm(cmd)
|
60
156
|
end
|
61
157
|
|
158
|
+
# T::Sig::WithoutRuntime.sig { params(res: ::KubeDSL::DSLObject, dry_run: T::Boolean).void }
|
62
159
|
def apply(res, dry_run: false)
|
63
160
|
cmd = [executable, '--kubeconfig', kubeconfig_path, 'apply', '--validate']
|
64
161
|
cmd << '--dry-run=client' if dry_run
|
@@ -68,8 +165,8 @@ class KubernetesCLI
|
|
68
165
|
stdin.puts(res.to_resource.to_yaml)
|
69
166
|
end
|
70
167
|
|
71
|
-
|
72
|
-
err = InvalidResourceError.new("Could not apply #{res.kind_sym
|
168
|
+
on_last_status_failure do |last_status|
|
169
|
+
err = InvalidResourceError.new("Could not apply #{res.kind_sym} "\
|
73
170
|
"'#{res.metadata.name}': kubectl exited with status code #{last_status.exitstatus}"
|
74
171
|
)
|
75
172
|
|
@@ -78,13 +175,14 @@ class KubernetesCLI
|
|
78
175
|
end
|
79
176
|
end
|
80
177
|
|
178
|
+
# T::Sig::WithoutRuntime.sig { params(uri: String, dry_run: T::Boolean).void }
|
81
179
|
def apply_uri(uri, dry_run: false)
|
82
180
|
cmd = [executable, '--kubeconfig', kubeconfig_path, 'apply', '--validate']
|
83
181
|
cmd << '--dry-run=client' if dry_run
|
84
182
|
cmd += ['-f', uri]
|
85
183
|
systemm(cmd)
|
86
184
|
|
87
|
-
|
185
|
+
on_last_status_failure do |last_status|
|
88
186
|
err = InvalidResourceUriError.new("Could not apply #{uri}: "\
|
89
187
|
"kubectl exited with status code #{last_status.exitstatus}"
|
90
188
|
)
|
@@ -94,29 +192,48 @@ class KubernetesCLI
|
|
94
192
|
end
|
95
193
|
end
|
96
194
|
|
97
|
-
|
98
|
-
|
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
|
99
207
|
cmd += ['get', type, name]
|
100
|
-
|
101
|
-
unless match_labels.empty?
|
102
|
-
cmd += ['--selector', match_labels.map { |key, value| "#{key}=#{value}" }.join(',')]
|
103
|
-
end
|
104
|
-
|
105
208
|
cmd += ['-o', 'json']
|
106
209
|
|
107
210
|
result = backticks(cmd)
|
108
211
|
|
109
|
-
|
110
|
-
raise GetResourceError, "couldn't get
|
212
|
+
on_last_status_failure do |last_status|
|
213
|
+
raise GetResourceError, "couldn't get resource of type '#{type}' named '#{name}' "\
|
111
214
|
"in namespace #{namespace}: kubectl exited with status code #{last_status.exitstatus}"
|
112
215
|
end
|
113
216
|
|
114
217
|
JSON.parse(result)
|
115
218
|
end
|
116
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
|
+
# }
|
117
229
|
def get_objects(type, namespace, match_labels = {})
|
118
|
-
cmd = [executable, '--kubeconfig', kubeconfig_path, '
|
119
|
-
|
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
|
120
237
|
|
121
238
|
unless match_labels.empty?
|
122
239
|
cmd += ['--selector', match_labels.map { |key, value| "#{key}=#{value}" }.join(',')]
|
@@ -126,7 +243,7 @@ class KubernetesCLI
|
|
126
243
|
|
127
244
|
result = backticks(cmd)
|
128
245
|
|
129
|
-
|
246
|
+
on_last_status_failure do |last_status|
|
130
247
|
raise GetResourceError, "couldn't get resources of type '#{type}' "\
|
131
248
|
"in namespace #{namespace}: kubectl exited with status code #{last_status.exitstatus}"
|
132
249
|
end
|
@@ -134,6 +251,89 @@ class KubernetesCLI
|
|
134
251
|
JSON.parse(result)['items']
|
135
252
|
end
|
136
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
|
+
# }
|
137
337
|
def annotate(type, namespace, name, annotations, overwrite: true)
|
138
338
|
cmd = [
|
139
339
|
executable,
|
@@ -151,12 +351,19 @@ class KubernetesCLI
|
|
151
351
|
|
152
352
|
systemm(cmd)
|
153
353
|
|
154
|
-
|
155
|
-
raise
|
354
|
+
on_last_status_failure do |last_status|
|
355
|
+
raise AnnotateResourceError, "could not annotate resource '#{name}': kubectl "\
|
156
356
|
"exited with status code #{last_status.exitstatus}"
|
157
357
|
end
|
158
358
|
end
|
159
359
|
|
360
|
+
# T::Sig::WithoutRuntime.sig {
|
361
|
+
# params(
|
362
|
+
# namespace: String,
|
363
|
+
# selector: T::Hash[String, String],
|
364
|
+
# follow: T::Boolean
|
365
|
+
# ).void
|
366
|
+
# }
|
160
367
|
def logtail(namespace, selector, follow: true)
|
161
368
|
cmd = [executable, '--kubeconfig', kubeconfig_path, '-n', namespace, 'logs']
|
162
369
|
cmd << '-f' if follow
|
@@ -165,16 +372,18 @@ class KubernetesCLI
|
|
165
372
|
execc(cmd)
|
166
373
|
end
|
167
374
|
|
375
|
+
# T::Sig::WithoutRuntime.sig { returns(String) }
|
168
376
|
def current_context
|
169
377
|
cmd = [executable, '--kubeconfig', kubeconfig_path, 'config', 'current-context']
|
170
378
|
backticks(cmd).strip
|
171
379
|
end
|
172
380
|
|
381
|
+
# T::Sig::WithoutRuntime.sig { returns(String) }
|
173
382
|
def api_resources
|
174
383
|
cmd = [executable, '--kubeconfig', kubeconfig_path, 'api-resources']
|
175
384
|
result = backticks(cmd)
|
176
385
|
|
177
|
-
|
386
|
+
on_last_status_failure do |last_status|
|
178
387
|
raise KubernetesError, 'could not fetch API resources: kubectl exited with '\
|
179
388
|
"status code #{last_status.exitstatus}. #{result}"
|
180
389
|
end
|
@@ -182,6 +391,7 @@ class KubernetesCLI
|
|
182
391
|
result
|
183
392
|
end
|
184
393
|
|
394
|
+
# T::Sig::WithoutRuntime.sig { params(namespace: String, deployment: String).void }
|
185
395
|
def restart_deployment(namespace, deployment)
|
186
396
|
cmd = [
|
187
397
|
executable,
|
@@ -192,13 +402,14 @@ class KubernetesCLI
|
|
192
402
|
|
193
403
|
systemm(cmd)
|
194
404
|
|
195
|
-
|
405
|
+
on_last_status_failure do |last_status|
|
196
406
|
raise KubernetesError, 'could not restart deployment: kubectl exited with '\
|
197
407
|
"status code #{last_status.exitstatus}"
|
198
408
|
end
|
199
409
|
end
|
200
410
|
|
201
|
-
|
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)
|
202
413
|
previous_stdout = self.stdout
|
203
414
|
previous_stderr = self.stderr
|
204
415
|
self.stdout = out
|
@@ -209,39 +420,66 @@ class KubernetesCLI
|
|
209
420
|
self.stderr = previous_stderr
|
210
421
|
end
|
211
422
|
|
423
|
+
# T::Sig::WithoutRuntime.sig { returns(T.any(StringIO, IO)) }
|
212
424
|
def stdout
|
213
425
|
Thread.current[STDOUT_KEY] || STDOUT
|
214
426
|
end
|
215
427
|
|
428
|
+
# T::Sig::WithoutRuntime.sig { params(new_stdout: T.nilable(T.any(StringIO, IO))).void }
|
216
429
|
def stdout=(new_stdout)
|
217
430
|
Thread.current[STDOUT_KEY] = new_stdout
|
218
431
|
end
|
219
432
|
|
433
|
+
# T::Sig::WithoutRuntime.sig { returns(T.any(StringIO, IO)) }
|
220
434
|
def stderr
|
221
435
|
Thread.current[STDERR_KEY] || STDERR
|
222
436
|
end
|
223
437
|
|
438
|
+
# T::Sig::WithoutRuntime.sig { params(new_stderr: T.nilable(T.any(StringIO, IO))).void }
|
224
439
|
def stderr=(new_stderr)
|
225
440
|
Thread.current[STDERR_KEY] = new_stderr
|
226
441
|
end
|
227
442
|
|
228
443
|
private
|
229
444
|
|
445
|
+
# T::Sig::WithoutRuntime.sig { returns(T::Hash[String, String]) }
|
230
446
|
def env
|
231
447
|
@env ||= {}
|
232
448
|
end
|
233
449
|
|
450
|
+
# T::Sig::WithoutRuntime.sig { returns(T::Array[String]) }
|
234
451
|
def base_cmd
|
235
452
|
[executable, '--kubeconfig', kubeconfig_path]
|
236
453
|
end
|
237
454
|
|
455
|
+
# T::Sig::WithoutRuntime.sig { params(cmd: T::Array[String]).void }
|
238
456
|
def execc(cmd)
|
239
457
|
run_before_callbacks(cmd)
|
240
458
|
cmd_s = cmd.join(' ')
|
241
459
|
exec(cmd_s)
|
242
460
|
end
|
243
461
|
|
462
|
+
# T::Sig::WithoutRuntime.sig { params(cmd: T::Array[String]).void }
|
244
463
|
def systemm(cmd)
|
464
|
+
if stdout == STDOUT && stderr == STDERR
|
465
|
+
systemm_default(cmd)
|
466
|
+
else
|
467
|
+
systemm_open3(cmd)
|
468
|
+
end
|
469
|
+
end
|
470
|
+
|
471
|
+
# T::Sig::WithoutRuntime.sig { params(cmd: T::Array[String]).void }
|
472
|
+
def systemm_default(cmd)
|
473
|
+
run_before_callbacks(cmd)
|
474
|
+
cmd_s = cmd.join(' ')
|
475
|
+
system(cmd_s).tap do
|
476
|
+
self.last_status = $?
|
477
|
+
run_after_callbacks(cmd)
|
478
|
+
end
|
479
|
+
end
|
480
|
+
|
481
|
+
# T::Sig::WithoutRuntime.sig { params(cmd: T::Array[String]).void }
|
482
|
+
def systemm_open3(cmd)
|
245
483
|
run_before_callbacks(cmd)
|
246
484
|
cmd_s = cmd.join(' ')
|
247
485
|
|
@@ -267,7 +505,27 @@ class KubernetesCLI
|
|
267
505
|
end
|
268
506
|
end
|
269
507
|
|
508
|
+
# T::Sig::WithoutRuntime.sig { params(cmd: T::Array[String]).returns(String) }
|
270
509
|
def backticks(cmd)
|
510
|
+
if stdout == STDOUT && stderr == STDERR
|
511
|
+
backticks_default(cmd)
|
512
|
+
else
|
513
|
+
backticks_open3(cmd)
|
514
|
+
end
|
515
|
+
end
|
516
|
+
|
517
|
+
# T::Sig::WithoutRuntime.sig { params(cmd: T::Array[String]).returns(String) }
|
518
|
+
def backticks_default(cmd)
|
519
|
+
run_before_callbacks(cmd)
|
520
|
+
cmd_s = cmd.join(' ')
|
521
|
+
`#{cmd_s}`.tap do
|
522
|
+
self.last_status = $?
|
523
|
+
run_after_callbacks(cmd)
|
524
|
+
end
|
525
|
+
end
|
526
|
+
|
527
|
+
# T::Sig::WithoutRuntime.sig { params(cmd: T::Array[String]).returns(String) }
|
528
|
+
def backticks_open3(cmd)
|
271
529
|
run_before_callbacks(cmd)
|
272
530
|
cmd_s = cmd.join(' ')
|
273
531
|
result = StringIO.new
|
@@ -296,10 +554,20 @@ class KubernetesCLI
|
|
296
554
|
result.string
|
297
555
|
end
|
298
556
|
|
557
|
+
# T::Sig::WithoutRuntime.sig {
|
558
|
+
# params(
|
559
|
+
# env: T::Hash[String, String],
|
560
|
+
# cmd: T::Array[String],
|
561
|
+
# opts: T::Hash[Symbol, T.untyped],
|
562
|
+
# block: T.proc.params(p_stdin: IO).void
|
563
|
+
# ).void
|
564
|
+
# }
|
299
565
|
def open3_w(env, cmd, opts = {}, &block)
|
300
566
|
run_before_callbacks(cmd)
|
301
567
|
cmd_s = cmd.join(' ')
|
302
568
|
|
569
|
+
# unsafes here b/c popen3 takes an optional first argument hash
|
570
|
+
# with environment variables, which confuses the sh*t out of sorbet
|
303
571
|
Open3.popen3(env, cmd_s, opts) do |p_stdin, p_stdout, p_stderr, wait_thread|
|
304
572
|
Thread.new(stdout) do |t_stdout|
|
305
573
|
begin
|
@@ -315,23 +583,26 @@ class KubernetesCLI
|
|
315
583
|
end
|
316
584
|
end
|
317
585
|
|
318
|
-
yield(p_stdin)
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
586
|
+
yield(p_stdin)
|
587
|
+
|
588
|
+
p_stdin.close
|
589
|
+
self.last_status = wait_thread.value
|
590
|
+
run_after_callbacks(cmd)
|
591
|
+
wait_thread.join
|
324
592
|
end
|
325
593
|
end
|
326
594
|
|
595
|
+
# T::Sig::WithoutRuntime.sig { params(cmd: T::Array[String]).void }
|
327
596
|
def run_before_callbacks(cmd)
|
328
597
|
@before_execute.each { |cb| cb.call(cmd) }
|
329
598
|
end
|
330
599
|
|
600
|
+
# T::Sig::WithoutRuntime.sig { params(cmd: T::Array[String]).void }
|
331
601
|
def run_after_callbacks(cmd)
|
332
602
|
@after_execute.each { |cb| cb.call(cmd, last_status) }
|
333
603
|
end
|
334
604
|
|
605
|
+
# T::Sig::WithoutRuntime.sig { params(status: Process::Status).void }
|
335
606
|
def last_status=(status)
|
336
607
|
Thread.current[STATUS_KEY] = status
|
337
608
|
end
|