kubernetes-cli 0.3.0 → 0.4.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 +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
|
+

|
4
|
+

|
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
|