k8s-client 0.8.3 → 0.9.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 +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
|