k8s-client 0.8.3 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.travis.yml +2 -1
- data/bin/k8s-client +2 -2
- data/k8s-client.gemspec +2 -1
- data/lib/k8s/api.rb +1 -1
- data/lib/k8s/api/metav1/api_resource.rb +2 -2
- data/lib/k8s/client.rb +11 -1
- data/lib/k8s/client/version.rb +1 -1
- data/lib/k8s/config.rb +21 -17
- data/lib/k8s/stack.rb +2 -2
- data/lib/k8s/transport.rb +59 -17
- metadata +27 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f0648161976a34f538f314a5f5ac7509779c1df4
|
4
|
+
data.tar.gz: e771af2d29bf02f3abaad33cb4ac6be17854bf07
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1ca57fc419c30157b69392a63df5ca8ef5dd64600124ad2c292f2e58ae4b08a0ab89219e5fb732dd5472cb1d70ebbe22309a168051d88b8cb1e676da807db537
|
7
|
+
data.tar.gz: 3591b8d176c5423cb5f2ed78f24feb571e2bf9f33e0b295296097edfb41ba8cd4a4942f2603d917afc995c464f115fb3dae690b59ed39bbe3937fe84e83c6362
|
data/.travis.yml
CHANGED
data/bin/k8s-client
CHANGED
@@ -313,8 +313,8 @@ options.delete_resources.each do |resource|
|
|
313
313
|
resource = client.delete_resource(resource)
|
314
314
|
|
315
315
|
logger.info "Deleted #{resource.apiVersion} resource #{resource.kind} #{resource.metadata.name} in namespace #{resource.metadata.namespace}:\n#{JSON.pretty_generate(resource)}"
|
316
|
-
rescue K8s::Error::NotFound =>
|
317
|
-
logger.info "Skip #{resource.apiVersion} resource #{resource.kind} #{resource.metadata.name} in namespace #{resource.metadata.namespace}: #{
|
316
|
+
rescue K8s::Error::NotFound => e
|
317
|
+
logger.info "Skip #{resource.apiVersion} resource #{resource.kind} #{resource.metadata.name} in namespace #{resource.metadata.namespace}: #{e}"
|
318
318
|
end
|
319
319
|
end
|
320
320
|
|
data/k8s-client.gemspec
CHANGED
@@ -25,13 +25,14 @@ Gem::Specification.new do |spec|
|
|
25
25
|
|
26
26
|
spec.add_runtime_dependency "excon", "~> 0.62.0"
|
27
27
|
spec.add_runtime_dependency "dry-struct", "~> 0.5.0"
|
28
|
+
spec.add_runtime_dependency "dry-types", "~> 0.13.0"
|
28
29
|
spec.add_runtime_dependency "recursive-open-struct", "~> 1.1.0"
|
29
30
|
spec.add_runtime_dependency 'hashdiff', '~> 0.3.7'
|
30
31
|
spec.add_runtime_dependency 'jsonpath', '~> 0.9.5'
|
31
32
|
spec.add_runtime_dependency 'yajl-ruby', '~> 1.4.0'
|
32
33
|
spec.add_runtime_dependency "yaml-safe_load_stream", "~> 0.1"
|
33
34
|
|
34
|
-
spec.add_development_dependency "bundler", "
|
35
|
+
spec.add_development_dependency "bundler", ">= 1.17", "< 3.0"
|
35
36
|
spec.add_development_dependency "rake", "~> 10.0"
|
36
37
|
spec.add_development_dependency "rspec", "~> 3.7"
|
37
38
|
spec.add_development_dependency "webmock", "~> 3.4.2"
|
data/lib/k8s/api.rb
CHANGED
@@ -12,8 +12,8 @@ module K8s
|
|
12
12
|
attribute :version, Types::Strict::String.optional.default(nil)
|
13
13
|
attribute :kind, Types::Strict::String
|
14
14
|
attribute :verbs, Types::Strict::Array.of(Types::Strict::String)
|
15
|
-
attribute :shortNames, Types::Strict::Array.of(Types::Strict::String).optional.default([])
|
16
|
-
attribute :categories, Types::Strict::Array.of(Types::Strict::String).optional.default([])
|
15
|
+
attribute :shortNames, Types::Strict::Array.of(Types::Strict::String).optional.default(proc { [] })
|
16
|
+
attribute :categories, Types::Strict::Array.of(Types::Strict::String).optional.default(proc { [] })
|
17
17
|
end
|
18
18
|
|
19
19
|
# @see https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1#APIResourceList
|
data/lib/k8s/client.rb
CHANGED
@@ -68,7 +68,17 @@ module K8s
|
|
68
68
|
# @return [K8s::Client]
|
69
69
|
def self.autoconfig(namespace: nil, **options)
|
70
70
|
if ENV.values_at('KUBE_TOKEN', 'KUBE_CA', 'KUBE_SERVER').none? { |v| v.nil? || v.empty? }
|
71
|
-
|
71
|
+
unless Base64.decode64(ENV['KUBE_CA']).match?(/CERTIFICATE/)
|
72
|
+
raise ArgumentError, 'KUBE_CA does not seem to be base64 encoded'
|
73
|
+
end
|
74
|
+
|
75
|
+
begin
|
76
|
+
token = options[:auth_token] || Base64.strict_decode64(ENV['KUBE_TOKEN'])
|
77
|
+
rescue ArgumentError
|
78
|
+
raise ArgumentError, 'KUBE_TOKEN does not seem to be base64 encoded'
|
79
|
+
end
|
80
|
+
|
81
|
+
configuration = K8s::Config.build(server: ENV['KUBE_SERVER'], ca: ENV['KUBE_CA'], auth_token: token)
|
72
82
|
elsif !ENV['KUBECONFIG'].to_s.empty?
|
73
83
|
configuration = K8s::Config.from_kubeconfig_env(ENV['KUBECONFIG'])
|
74
84
|
elsif File.exist?(File.join(Dir.home, '.kube', 'config'))
|
data/lib/k8s/client/version.rb
CHANGED
data/lib/k8s/config.rb
CHANGED
@@ -49,6 +49,14 @@ module K8s
|
|
49
49
|
attribute :config, Types::Strict::Hash
|
50
50
|
end
|
51
51
|
|
52
|
+
# structured user exec
|
53
|
+
class UserExec < ConfigStruct
|
54
|
+
attribute :command, Types::String
|
55
|
+
attribute :apiVersion, Types::String
|
56
|
+
attribute :env, Types::Strict::Array.of(Types::Hash).optional.default(nil)
|
57
|
+
attribute :args, Types::Strict::Array.of(Types::String).optional.default(nil)
|
58
|
+
end
|
59
|
+
|
52
60
|
# structured user
|
53
61
|
class User < ConfigStruct
|
54
62
|
attribute :client_certificate, Types::String.optional.default(nil)
|
@@ -63,7 +71,7 @@ module K8s
|
|
63
71
|
attribute :username, Types::String.optional.default(nil)
|
64
72
|
attribute :password, Types::String.optional.default(nil)
|
65
73
|
attribute :auth_provider, UserAuthProvider.optional.default(nil)
|
66
|
-
attribute :exec,
|
74
|
+
attribute :exec, UserExec.optional.default(nil)
|
67
75
|
attribute :extensions, Types::Strict::Array.optional.default(nil)
|
68
76
|
end
|
69
77
|
|
@@ -103,19 +111,18 @@ module K8s
|
|
103
111
|
# @param path [String]
|
104
112
|
# @return [K8s::Config]
|
105
113
|
def self.load_file(path)
|
106
|
-
new(YAML.safe_load(File.read(path), [Time, DateTime, Date]))
|
114
|
+
new(YAML.safe_load(File.read(File.expand_path(path)), [Time, DateTime, Date], [], true))
|
107
115
|
end
|
108
116
|
|
109
117
|
# Loads configuration files listed in KUBE_CONFIG environment variable and
|
110
|
-
#
|
118
|
+
# merge using the configuration merge rules, @see K8s::Config.merge
|
111
119
|
#
|
112
120
|
# @param kubeconfig [String] by default read from ENV['KUBECONFIG']
|
113
121
|
def self.from_kubeconfig_env(kubeconfig = nil)
|
114
122
|
kubeconfig ||= ENV.fetch('KUBECONFIG', '')
|
115
|
-
|
123
|
+
raise ArgumentError, "KUBECONFIG not set" if kubeconfig.empty?
|
116
124
|
|
117
125
|
paths = kubeconfig.split(/(?!\\):/)
|
118
|
-
return load_file(paths.first) if paths.size == 1
|
119
126
|
|
120
127
|
paths.inject(load_file(paths.shift)) do |memo, other_cfg|
|
121
128
|
memo.merge(load_file(other_cfg))
|
@@ -125,23 +132,17 @@ module K8s
|
|
125
132
|
# Build a minimal configuration from at least a server address, server certificate authority data and an access token.
|
126
133
|
#
|
127
134
|
# @param server [String] kubernetes server address
|
128
|
-
# @param ca [String] server certificate authority data
|
129
|
-
# @param token [String] access token
|
135
|
+
# @param ca [String] server certificate authority data (base64 encoded)
|
136
|
+
# @param token [String] access token
|
130
137
|
# @param cluster_name [String] cluster name
|
131
138
|
# @param user [String] user name
|
132
139
|
# @param context [String] context name
|
133
140
|
# @param options [Hash] (see #initialize)
|
134
141
|
def self.build(server:, ca:, auth_token:, cluster_name: 'kubernetes', user: 'k8s-client', context: 'k8s-client', **options)
|
135
|
-
begin
|
136
|
-
decoded_token = Base64.strict_decode64(auth_token)
|
137
|
-
rescue ArgumentError
|
138
|
-
decoded_token = nil
|
139
|
-
end
|
140
|
-
|
141
142
|
new(
|
142
143
|
{
|
143
144
|
clusters: [{ name: cluster_name, cluster: { server: server, certificate_authority_data: ca } }],
|
144
|
-
users: [{ name: user, user: { token:
|
145
|
+
users: [{ name: user, user: { token: auth_token } }],
|
145
146
|
contexts: [{ name: context, context: { cluster: cluster_name, user: user } }],
|
146
147
|
current_context: context
|
147
148
|
}.merge(options)
|
@@ -174,7 +175,7 @@ module K8s
|
|
174
175
|
when NilClass
|
175
176
|
new_value
|
176
177
|
else
|
177
|
-
|
178
|
+
warn "key is #{key} old val is #{old_value.inspect} and new val is #{new_value.inspect}"
|
178
179
|
old_value
|
179
180
|
end
|
180
181
|
end
|
@@ -184,10 +185,13 @@ module K8s
|
|
184
185
|
end
|
185
186
|
|
186
187
|
# @param name [String]
|
187
|
-
#
|
188
|
+
# @raise [K8s::Error::Configuration]
|
188
189
|
# @return [K8s::Config::Context]
|
189
190
|
def context(name = current_context)
|
190
|
-
contexts.find{ |context| context.name == name }
|
191
|
+
found = contexts.find{ |context| context.name == name }
|
192
|
+
raise K8s::Error::Configuration, "context not found: #{name.inspect}" unless found
|
193
|
+
|
194
|
+
found.context
|
191
195
|
end
|
192
196
|
|
193
197
|
# @param name [String]
|
data/lib/k8s/stack.rb
CHANGED
@@ -163,10 +163,10 @@ module K8s
|
|
163
163
|
logger.info "Delete resource #{resource.apiVersion}:#{resource.kind}/#{resource.metadata.name} in namespace #{resource.metadata.namespace}"
|
164
164
|
begin
|
165
165
|
client.delete_resource(resource, propagationPolicy: 'Background')
|
166
|
-
rescue K8s::Error::NotFound =>
|
166
|
+
rescue K8s::Error::NotFound => e
|
167
167
|
# assume aliased objects in multiple API groups, like for Deployments
|
168
168
|
# alternatively, a custom resource whose definition was already deleted earlier
|
169
|
-
logger.debug { "Ignoring #{
|
169
|
+
logger.debug { "Ignoring #{e} : #{e.message}" }
|
170
170
|
end
|
171
171
|
end
|
172
172
|
end
|
data/lib/k8s/transport.rb
CHANGED
@@ -75,14 +75,15 @@ module K8s
|
|
75
75
|
options[:auth_token] = token
|
76
76
|
elsif config.user.auth_provider && auth_provider = config.user.auth_provider.config
|
77
77
|
logger.debug "Using config with .user.auth-provider.name=#{config.user.auth_provider.name}"
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
78
|
+
options[:auth_token] = token_from_auth_provider(auth_provider)
|
79
|
+
elsif exec_conf = config.user.exec
|
80
|
+
logger.debug "Using config with .user.exec.command=#{exec_conf.command}"
|
81
|
+
options[:auth_token] = token_from_exec(exec_conf)
|
82
|
+
elsif config.user.username && config.user.password
|
83
|
+
logger.debug "Using config with .user.password=..."
|
84
|
+
|
85
|
+
options[:auth_username] = config.user.username
|
86
|
+
options[:auth_password] = config.user.password
|
86
87
|
end
|
87
88
|
|
88
89
|
logger.info "Using config with server=#{server}"
|
@@ -90,6 +91,35 @@ module K8s
|
|
90
91
|
new(server, **options, **overrides)
|
91
92
|
end
|
92
93
|
|
94
|
+
# @param auth_provider [K8s::Config::UserAuthProvider]
|
95
|
+
# @return [String]
|
96
|
+
def self.token_from_auth_provider(auth_provider)
|
97
|
+
auth_data = `#{auth_provider['cmd-path']} #{auth_provider['cmd-args']}`.strip
|
98
|
+
if auth_provider['token-key']
|
99
|
+
json_path = JsonPath.new(auth_provider['token-key'][1...-1])
|
100
|
+
json_path.first(auth_data)
|
101
|
+
else
|
102
|
+
auth_data
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# @param exec_conf [K8s::Config::UserExec]
|
107
|
+
# @return [String]
|
108
|
+
def self.token_from_exec(exec_conf)
|
109
|
+
cmd = [exec_conf.command]
|
110
|
+
cmd += exec_conf.args if exec_conf.args
|
111
|
+
orig_env = ENV.to_h
|
112
|
+
if envs = exec_conf.env
|
113
|
+
envs.each do |env|
|
114
|
+
ENV[env['name']] = env['value']
|
115
|
+
end
|
116
|
+
end
|
117
|
+
auth_json = `#{cmd.join(' ')}`.strip
|
118
|
+
ENV.replace(orig_env)
|
119
|
+
|
120
|
+
JSON.parse(auth_json).dig('status', 'token')
|
121
|
+
end
|
122
|
+
|
93
123
|
# In-cluster config within a kube pod, using the kubernetes service envs and serviceaccount secrets
|
94
124
|
#
|
95
125
|
# @param options [Hash] see #new
|
@@ -116,10 +146,14 @@ module K8s
|
|
116
146
|
|
117
147
|
# @param server [String] URL with protocol://host:port - any /path is ignored
|
118
148
|
# @param auth_token [String] optional Authorization: Bearer token
|
149
|
+
# @param auth_username [String] optional Basic authentication username
|
150
|
+
# @param auth_password [String] optional Basic authentication password
|
119
151
|
# @param options [Hash] @see Excon.new
|
120
|
-
def initialize(server, auth_token: nil, **options)
|
152
|
+
def initialize(server, auth_token: nil, auth_username: nil, auth_password: nil, **options)
|
121
153
|
@server = server
|
122
154
|
@auth_token = auth_token
|
155
|
+
@auth_username = auth_username
|
156
|
+
@auth_password = auth_password
|
123
157
|
@options = options
|
124
158
|
|
125
159
|
logger! progname: @server
|
@@ -127,7 +161,12 @@ module K8s
|
|
127
161
|
|
128
162
|
# @return [Excon::Connection]
|
129
163
|
def excon
|
130
|
-
@excon ||=
|
164
|
+
@excon ||= build_excon
|
165
|
+
end
|
166
|
+
|
167
|
+
# @return [Excon::Connection]
|
168
|
+
def build_excon
|
169
|
+
Excon.new(
|
131
170
|
@server,
|
132
171
|
persistent: true,
|
133
172
|
middlewares: EXCON_MIDDLEWARES,
|
@@ -151,6 +190,8 @@ module K8s
|
|
151
190
|
|
152
191
|
if @auth_token
|
153
192
|
options[:headers]['Authorization'] = "Bearer #{@auth_token}"
|
193
|
+
elsif @auth_username && @auth_password
|
194
|
+
options[:headers]['Authorization'] = "Basic #{Base64.strict_encode64("#{@auth_username}:#{@auth_password}")}"
|
154
195
|
end
|
155
196
|
|
156
197
|
if request_object
|
@@ -236,12 +277,13 @@ module K8s
|
|
236
277
|
excon_options = request_options(**options)
|
237
278
|
|
238
279
|
start = Time.now
|
239
|
-
|
280
|
+
excon_client = options[:response_block] ? build_excon : excon
|
281
|
+
response = excon_client.request(**excon_options)
|
240
282
|
t = Time.now - start
|
241
283
|
|
242
284
|
obj = options[:response_block] ? {} : parse_response(response, options, response_class: response_class)
|
243
|
-
rescue K8s::Error::API =>
|
244
|
-
logger.warn { "#{format_request(options)} => HTTP #{
|
285
|
+
rescue K8s::Error::API => e
|
286
|
+
logger.warn { "#{format_request(options)} => HTTP #{e.code} #{e.reason} in #{'%.3f' % t}s" }
|
245
287
|
logger.debug { "Request: #{excon_options[:body]}" } if excon_options[:body]
|
246
288
|
logger.debug { "Response: #{response.body}" }
|
247
289
|
raise
|
@@ -281,17 +323,17 @@ module K8s
|
|
281
323
|
raise unless skip_forbidden
|
282
324
|
|
283
325
|
nil
|
284
|
-
rescue K8s::Error::ServiceUnavailable =>
|
326
|
+
rescue K8s::Error::ServiceUnavailable => e
|
285
327
|
raise unless retry_errors
|
286
328
|
|
287
|
-
logger.warn { "Retry #{format_request(request_options)} => HTTP #{
|
329
|
+
logger.warn { "Retry #{format_request(request_options)} => HTTP #{e.code} #{e.reason} in #{'%.3f' % t}s" }
|
288
330
|
|
289
331
|
# only retry the failed request, not the entire pipeline
|
290
332
|
request(response_class: response_class, **common_options.merge(request_options))
|
291
333
|
end
|
292
334
|
}
|
293
|
-
rescue K8s::Error =>
|
294
|
-
logger.warn { "[#{options.map{ |o| format_request(o) }.join ', '}] => HTTP #{
|
335
|
+
rescue K8s::Error => e
|
336
|
+
logger.warn { "[#{options.map{ |o| format_request(o) }.join ', '}] => HTTP #{e.code} #{e.reason} in #{'%.3f' % t}s" }
|
295
337
|
raise
|
296
338
|
else
|
297
339
|
logger.info { "[#{options.map{ |o| format_request(o) }.join ', '}] => HTTP [#{responses.map(&:status).join ', '}] in #{'%.3f' % t}s" }
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: k8s-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kontena, Inc.
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-02
|
11
|
+
date: 2019-05-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: excon
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: 0.5.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: dry-types
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.13.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.13.0
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: recursive-open-struct
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -112,16 +126,22 @@ dependencies:
|
|
112
126
|
name: bundler
|
113
127
|
requirement: !ruby/object:Gem::Requirement
|
114
128
|
requirements:
|
115
|
-
- - "
|
129
|
+
- - ">="
|
116
130
|
- !ruby/object:Gem::Version
|
117
|
-
version: '1.
|
131
|
+
version: '1.17'
|
132
|
+
- - "<"
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: '3.0'
|
118
135
|
type: :development
|
119
136
|
prerelease: false
|
120
137
|
version_requirements: !ruby/object:Gem::Requirement
|
121
138
|
requirements:
|
122
|
-
- - "
|
139
|
+
- - ">="
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '1.17'
|
142
|
+
- - "<"
|
123
143
|
- !ruby/object:Gem::Version
|
124
|
-
version: '
|
144
|
+
version: '3.0'
|
125
145
|
- !ruby/object:Gem::Dependency
|
126
146
|
name: rake
|
127
147
|
requirement: !ruby/object:Gem::Requirement
|
@@ -239,7 +259,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
239
259
|
version: '0'
|
240
260
|
requirements: []
|
241
261
|
rubyforge_project:
|
242
|
-
rubygems_version: 2.
|
262
|
+
rubygems_version: 2.6.14.4
|
243
263
|
signing_key:
|
244
264
|
specification_version: 4
|
245
265
|
summary: Kubernetes client library
|