kubeclient 4.2.2 → 4.3.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of kubeclient might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9e11771dcb8f222f6c3a3bb4fb1c8b3db8cc745a9d2dbe8b2ff3edfe02382f3d
4
- data.tar.gz: a5edd9efc28428fd2e5d232a1bdce0a1f5d0bd56fad06cff4d10e82e03bdc3f6
3
+ metadata.gz: f53d064748148a041be5853562e7789f8cc792735e4916c1859c63bf93f1f8bc
4
+ data.tar.gz: 15a4ba1b298c4bfb3469765a92ad58e8dd806f470870bbadaaf81a0c7ac3dbf4
5
5
  SHA512:
6
- metadata.gz: 17cdfd31bf3df9292a2edf052071e99b30e62ee6ebfd05bce7b710623cc7c0ae57acb1c469787cee49bd472dc1661d687908acbb364cc8d36723f7ee4b07d273
7
- data.tar.gz: f739a8fc768a688853e2f6e55232087bdfbdc31b26a86ca6ffe6ecc87996b72906a2d60d674c5aae3d5023d578b5cc9d20611a349f1cfc0d2ee1d2511e4f4c7c
6
+ metadata.gz: 39b85eead8370920f1dbf6f30720506de756894ae4d34b55a5e589b9ff1c8bce0a6e93c5517d4afbc7bb039b729e760250c7b4ead17b4fced76f52941b6bcf44
7
+ data.tar.gz: 6c95743f7680fac757b90804050b81488d084fe083749890f069ca8ed1a97158c789d3a912fcccc72f64d089695a9fe0c2085b45dffa492773c82d2ae5b3626d
@@ -14,12 +14,16 @@ Metrics/ParameterLists:
14
14
  CountKeywordArgs: false
15
15
  Metrics/CyclomaticComplexity:
16
16
  Max: 8
17
+ Metrics/PerceivedComplexity:
18
+ Max: 8
17
19
  Metrics/ModuleLength:
18
20
  Enabled: false
19
21
  Style/MethodCallWithArgsParentheses:
20
22
  Enabled: true
21
23
  IgnoredMethods: [require, raise, include, attr_reader, refute, assert]
22
24
  Exclude: [Gemfile, Rakefile, kubeclient.gemspec, Gemfile.dev.rb]
25
+ Metrics/BlockLength:
26
+ Exclude: [kubeclient.gemspec]
23
27
  Security/MarshalLoad:
24
28
  Exclude: [test/**/*]
25
29
  Style/FileName:
@@ -4,6 +4,16 @@ Notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
5
5
  Kubeclient release versioning follows [SemVer](https://semver.org/).
6
6
 
7
+ ## 4.3.0 — 2019-03-03
8
+
9
+ ### Changed
10
+ - `GoogleApplicationDefaultCredentials` will automatically be used in `fetch_user_auth_options` if the `user[auth-provider][name] == 'gcp'` in the provided context. Note that `user[exec]` is checked first in anticipation of this functionality being added to GCP sometime in the future. Kubeclient _does not_ import the required `googleauth` gem, so you will need to import it in your calling application. (#394)
11
+
12
+ ### Added
13
+ - OpenID Connect credentials will automatically be used if the `user[auth-provider][name] == 'oidc'` in the provided context. Note that `user[exec]` is checked first. Kubeclient _does not_ import the required `openid_connect` gem, so you will need to import it in your calling application. (#396)
14
+
15
+ - Support for `json_patch_#{entity}` and `merge_patch_#{entity}`. `patch_#{entity}` will continue to use strategic merge patch. (#390)
16
+
7
17
  ## 4.2.2 — 2019-01-09
8
18
 
9
19
  ### Added
data/README.md CHANGED
@@ -166,29 +166,6 @@ namespace = File.read('/var/run/secrets/kubernetes.io/serviceaccount/namespace')
166
166
  ```
167
167
  You can find information about tokens in [this guide](https://kubernetes.io/docs/tasks/access-application-cluster/access-cluster/#accessing-the-api-from-a-pod) and in [this reference](http://kubernetes.io/docs/admin/authentication/).
168
168
 
169
- #### Google's Application Default Credentials
170
-
171
- On Google Compute Engine, Google App Engine, or Google Cloud Functions, as well as `gcloud`-configured systems
172
- with [application default credentials](https://developers.google.com/identity/protocols/application-default-credentials),
173
- you can use the token provider to authorize `kubeclient`.
174
-
175
- This requires the [`googleauth` gem](https://github.com/google/google-auth-library-ruby) that is _not_ included in
176
- `kubeclient` dependencies so you should add it to your bundle.
177
-
178
- ```ruby
179
- require 'googleauth'
180
-
181
- auth_options = {
182
- bearer_token: Kubeclient::GoogleApplicationDefaultCredentials.token
183
- }
184
- client = Kubeclient::Client.new(
185
- 'https://localhost:8443/api/', 'v1', auth_options: auth_options
186
- )
187
- ```
188
-
189
- Note that this token is good for one hour. If your code runs for longer than that, you should plan to
190
- acquire a new one.
191
-
192
169
  ### Non-blocking IO
193
170
 
194
171
  You can also use kubeclient with non-blocking sockets such as Celluloid::IO, see [here](https://github.com/httprb/http/wiki/Parallel-requests-with-Celluloid%3A%3AIO)
@@ -303,12 +280,82 @@ context = config.context('default/192-168-99-100:8443/system:admin')
303
280
 
304
281
  Kubeclient::Client.new(
305
282
  context.api_endpoint,
306
- context.api_version,
283
+ 'v1',
307
284
  ssl_options: context.ssl_options,
308
285
  auth_options: context.auth_options
309
286
  )
310
287
  ```
311
288
 
289
+
290
+ #### Google's Application Default Credentials
291
+
292
+ On Google Compute Engine, Google App Engine, or Google Cloud Functions, as well as `gcloud`-configured systems
293
+ with [application default credentials](https://developers.google.com/identity/protocols/application-default-credentials),
294
+ kubeclient can use `googleauth` gem to authorize.
295
+
296
+ This requires the [`googleauth` gem](https://github.com/google/google-auth-library-ruby) that is _not_ included in
297
+ `kubeclient` dependencies so you should add it to your bundle.
298
+
299
+ If you use `Config.context(...).auth_options` and the kubeconfig file has `user: {auth-provider: {name: gcp}}`, kubeclient will automatically try this (raising LoadError if you don't have `googleauth` in your bundle).
300
+
301
+ Or you can obtain a token manually:
302
+
303
+ ```ruby
304
+ require 'googleauth'
305
+
306
+ auth_options = {
307
+ bearer_token: Kubeclient::GoogleApplicationDefaultCredentials.token
308
+ }
309
+ client = Kubeclient::Client.new(
310
+ 'https://localhost:8443/api/', 'v1', auth_options: auth_options
311
+ )
312
+ ```
313
+
314
+ Note that this returns a token good for one hour. If your code requires authorization for longer than that, you should plan to
315
+ acquire a new one, see [How to manually renew](#how-to-manually-renew-expired-credentials) section.
316
+
317
+ #### OIDC Auth Provider
318
+
319
+ If the cluster you are using has OIDC authentication enabled you can use the `openid_connect` gem to obtain
320
+ id-tokens if the one in your kubeconfig has expired.
321
+
322
+ This requires the [`openid_connect` gem](https://github.com/nov/openid_connect) which is not included in
323
+ the `kubeclient` dependencies so should be added to your own applications bundle.
324
+
325
+ The OIDC Auth Provider will not perform the initial setup of your `$KUBECONFIG` file. You will need to use something
326
+ like [`dexter`](https://github.com/gini/dexter) in order to configure the auth-provider in your `$KUBECONFIG` file.
327
+
328
+ If you use `Config.context(...).auth_options` and the `$KUBECONFIG` file has user: `{auth-provider: {name: oidc}}`,
329
+ kubeclient will automatically obtain a token (or use `id-token` if still valid)
330
+
331
+ Tokens are typically short-lived (e.g. 1 hour) and the expiration time is determined by the OIDC Provider (e.g. Google).
332
+ If your code requires authentication for longer than that you should obtain a new token periodically, see [How to manually renew](#how-to-manually-renew-expired-credentials) section.
333
+
334
+ Note: id-tokens retrieved via this provider are not written back to the `$KUBECONFIG` file as they would be when
335
+ using `kubectl`.
336
+
337
+ #### How to manually renew expired credentials
338
+
339
+ Kubeclient [does not yet](https://github.com/abonas/kubeclient/issues/393) help with this.
340
+
341
+ The division of labor between `Config` and `Context` objects may change, for now please make no assumptions at which stage `exec:` and `auth-provider:` are handled and whether they're cached.
342
+ The currently guaranteed way to renew is create a new `Config` object.
343
+
344
+ The more painful part is that you'll then need to create new `Client` object(s) with the credentials from new config.
345
+ So repeat all of this:
346
+ ```ruby
347
+ config = Kubeclient::Config.read(ENV['KUBECONFIG'] || '/path/to/.kube/config')
348
+ context = config.context
349
+ ssl_options = context.ssl_options
350
+ auth_options = context.auth_options
351
+
352
+ client = Kubeclient::Client.new(
353
+ context.api_endpoint, 'v1',
354
+ ssl_options: ssl_options, auth_options: auth_options
355
+ )
356
+ # and additional Clients if needed...
357
+ ```
358
+
312
359
  #### Security: Don't use config from untrusted sources
313
360
 
314
361
  `Config.read` is catastrophically unsafe — it will execute arbitrary command lines specified by the config!
@@ -490,6 +537,8 @@ The below example is for v1
490
537
  patched = client.patch_pod("docker-registry", {metadata: {annotations: {key: 'value'}}}, "default")
491
538
  ```
492
539
 
540
+ `patch_#{entity}` is called using a [strategic merge patch](https://kubernetes.io/docs/tasks/run-application/update-api-object-kubectl-patch/#notes-on-the-strategic-merge-patch). `json_patch_#{entity}` and `merge_patch_#{entity}` are also available that use JSON patch and JSON merge patch, respectively. These strategies are useful for resources that do not support strategic merge patch, such as Custom Resources. Consult the [Kubernetes docs](https://kubernetes.io/docs/tasks/run-application/update-api-object-kubectl-patch/#use-a-json-merge-patch-to-update-a-deployment) for more information about the different patch strategies.
541
+
493
542
  #### Get all entities of all types : all_entities
494
543
  Returns a hash with the following keys (node, secret, service, pod, replication_controller, namespace, resource_quota, limit_range, endpoint, event, persistent_volume, persistent_volume_claim, component_status and service_account). Each key points to an EntityList of same type.
495
544
  This method is a convenience method instead of calling each entity's get method separately.
@@ -28,6 +28,8 @@ Gem::Specification.new do |spec|
28
28
  spec.add_development_dependency 'vcr'
29
29
  spec.add_development_dependency 'rubocop', '= 0.49.1'
30
30
  spec.add_development_dependency 'googleauth', '~> 0.5.1'
31
+ spec.add_development_dependency('mocha', '~> 1.5')
32
+ spec.add_development_dependency 'openid_connect', '~> 1.1'
31
33
 
32
34
  spec.add_dependency 'rest-client', '~> 2.0'
33
35
  spec.add_dependency 'recursive-open-struct', '~> 1.0', '>= 1.0.4'
@@ -8,6 +8,7 @@ require 'kubeclient/google_application_default_credentials'
8
8
  require 'kubeclient/exec_credentials'
9
9
  require 'kubeclient/http_error'
10
10
  require 'kubeclient/missing_kind_compatibility'
11
+ require 'kubeclient/oidc_auth_provider'
11
12
  require 'kubeclient/resource'
12
13
  require 'kubeclient/resource_not_found_error'
13
14
  require 'kubeclient/version'
@@ -5,7 +5,7 @@ module Kubeclient
5
5
  # Common methods
6
6
  # this is mixed in by other gems
7
7
  module ClientMixin
8
- ENTITY_METHODS = %w[get watch delete create update patch].freeze
8
+ ENTITY_METHODS = %w[get watch delete create update patch json_patch merge_patch].freeze
9
9
 
10
10
  DEFAULT_SSL_OPTIONS = {
11
11
  client_cert: nil,
@@ -203,6 +203,7 @@ module Kubeclient
203
203
  namespace.to_s.empty? ? '' : "namespaces/#{namespace}/"
204
204
  end
205
205
 
206
+ # rubocop:disable Metrics/BlockLength
206
207
  def define_entity_methods
207
208
  @entities.values.each do |entity|
208
209
  # get all entities of a type e.g. get_nodes, get_pods, etc.
@@ -238,11 +239,23 @@ module Kubeclient
238
239
  update_entity(entity.resource_name, entity_config)
239
240
  end
240
241
 
241
- define_singleton_method("patch_#{entity.method_names[0]}") do |name, patch, namespace = nil|
242
- patch_entity(entity.resource_name, name, patch, namespace)
242
+ define_singleton_method("patch_#{entity.method_names[0]}") \
243
+ do |name, patch, namespace = nil|
244
+ patch_entity(entity.resource_name, name, patch, 'strategic-merge-patch', namespace)
245
+ end
246
+
247
+ define_singleton_method("json_patch_#{entity.method_names[0]}") \
248
+ do |name, patch, namespace = nil|
249
+ patch_entity(entity.resource_name, name, patch, 'json-patch', namespace)
250
+ end
251
+
252
+ define_singleton_method("merge_patch_#{entity.method_names[0]}") \
253
+ do |name, patch, namespace = nil|
254
+ patch_entity(entity.resource_name, name, patch, 'merge-patch', namespace)
243
255
  end
244
256
  end
245
257
  end
258
+ # rubocop:enable Metrics/BlockLength
246
259
 
247
260
  def self.underscore_entity(entity_name)
248
261
  entity_name.gsub(/([a-z])([A-Z])/, '\1_\2').downcase
@@ -381,13 +394,13 @@ module Kubeclient
381
394
  format_response(@as, response.body)
382
395
  end
383
396
 
384
- def patch_entity(resource_name, name, patch, namespace = nil)
397
+ def patch_entity(resource_name, name, patch, strategy, namespace)
385
398
  ns_prefix = build_namespace_prefix(namespace)
386
399
  response = handle_exception do
387
400
  rest_client[ns_prefix + resource_name + "/#{name}"]
388
401
  .patch(
389
402
  patch.to_json,
390
- { 'Content-Type' => 'application/strategic-merge-patch+json' }.merge(@headers)
403
+ { 'Content-Type' => "application/#{strategy}+json" }.merge(@headers)
391
404
  )
392
405
  end
393
406
  format_response(@as, response.body)
@@ -153,6 +153,14 @@ module Kubeclient
153
153
  exec_opts = user['exec'].dup
154
154
  exec_opts['command'] = ext_command_path(exec_opts['command']) if exec_opts['command']
155
155
  options[:bearer_token] = Kubeclient::ExecCredentials.token(exec_opts)
156
+ elsif user.key?('auth-provider')
157
+ auth_provider = user['auth-provider']
158
+ options[:bearer_token] = case auth_provider['name']
159
+ when 'gcp'
160
+ then Kubeclient::GoogleApplicationDefaultCredentials.token
161
+ when 'oidc'
162
+ then Kubeclient::OIDCAuthProvider.token(auth_provider['config'])
163
+ end
156
164
  else
157
165
  %w[username password].each do |attr|
158
166
  options[attr.to_sym] = user[attr] if user.key?(attr)
@@ -3,9 +3,19 @@
3
3
  module Kubeclient
4
4
  # Get a bearer token from the Google's application default credentials.
5
5
  class GoogleApplicationDefaultCredentials
6
+ class GoogleDependencyError < LoadError # rubocop:disable Lint/InheritException
7
+ end
8
+
6
9
  class << self
7
10
  def token
8
- require 'googleauth'
11
+ begin
12
+ require 'googleauth'
13
+ rescue LoadError => e
14
+ raise GoogleDependencyError,
15
+ 'Error requiring googleauth gem. Kubeclient itself does not include the ' \
16
+ 'googleauth gem. To support auth-provider gcp, you must include it in your ' \
17
+ "calling application. Failed with: #{e.message}"
18
+ end
9
19
  scopes = ['https://www.googleapis.com/auth/cloud-platform']
10
20
  authorization = Google::Auth.get_application_default(scopes)
11
21
  authorization.apply({})
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kubeclient
4
+ # Uses OIDC id-tokens and refreshes them if they are stale.
5
+ class OIDCAuthProvider
6
+ class OpenIDConnectDependencyError < LoadError # rubocop:disable Lint/InheritException
7
+ end
8
+
9
+ class << self
10
+ def token(provider_config)
11
+ begin
12
+ require 'openid_connect'
13
+ rescue LoadError => e
14
+ raise OpenIDConnectDependencyError,
15
+ 'Error requiring openid_connect gem. Kubeclient itself does not include the ' \
16
+ 'openid_connect gem. To support auth-provider oidc, you must include it in your ' \
17
+ "calling application. Failed with: #{e.message}"
18
+ end
19
+
20
+ issuer_url = provider_config['idp-issuer-url']
21
+ discovery = OpenIDConnect::Discovery::Provider::Config.discover! issuer_url
22
+
23
+ if provider_config.key? 'id-token'
24
+ id_token = OpenIDConnect::ResponseObject::IdToken.decode provider_config['id-token'],
25
+ discovery.jwks
26
+ return provider_config['id-token'] unless expired?(id_token)
27
+ end
28
+
29
+ client = OpenIDConnect::Client.new(
30
+ identifier: provider_config['client-id'],
31
+ secret: provider_config['client-secret'],
32
+ authorization_endpoint: discovery.authorization_endpoint,
33
+ token_endpoint: discovery.token_endpoint,
34
+ userinfo_endpoint: discovery.userinfo_endpoint
35
+ )
36
+ client.refresh_token = provider_config['refresh-token']
37
+ client.access_token!.id_token
38
+ end
39
+
40
+ def expired?(id_token)
41
+ # If token expired or expiring within 60 seconds
42
+ Time.now.to_i + 60 > id_token.exp.to_i
43
+ end
44
+ end
45
+ end
46
+ end
@@ -1,4 +1,4 @@
1
1
  # Kubernetes REST-API Client
2
2
  module Kubeclient
3
- VERSION = '4.2.2'.freeze
3
+ VERSION = '4.3.0'.freeze
4
4
  end
@@ -0,0 +1,22 @@
1
+ apiVersion: v1
2
+ clusters:
3
+ - cluster:
4
+ server: https://localhost:8443
5
+ insecure-skip-tls-verify: true
6
+ name: localhost:8443
7
+ contexts:
8
+ - context:
9
+ cluster: localhost:8443
10
+ namespace: default
11
+ user: application-default-credentials
12
+ name: localhost/application-default-credentials
13
+ kind: Config
14
+ preferences: {}
15
+ users:
16
+ - name: application-default-credentials
17
+ user:
18
+ auth-provider:
19
+ config:
20
+ access-token: <fake_token>
21
+ expiry: 2019-02-19T11:07:29.827352-05:00
22
+ name: gcp
@@ -0,0 +1,25 @@
1
+ apiVersion: v1
2
+ clusters:
3
+ - cluster:
4
+ server: https://localhost:8443
5
+ insecure-skip-tls-verify: true
6
+ name: localhost:8443
7
+ contexts:
8
+ - context:
9
+ cluster: localhost:8443
10
+ namespace: default
11
+ user: oidc-auth-provider
12
+ name: localhost/oidc-auth-provider
13
+ kind: Config
14
+ preferences: {}
15
+ users:
16
+ - name: oidc-auth-provider
17
+ user:
18
+ auth-provider:
19
+ config:
20
+ client-id: fake-client-id
21
+ client-secret: fake-client-secret
22
+ id-token: fake-id-token
23
+ idp-issuer-url: https://accounts.google.com
24
+ refresh-token: fake-refresh-token
25
+ name: oidc
@@ -0,0 +1,26 @@
1
+ {
2
+ "status" : {},
3
+ "kind" : "Service",
4
+ "apiVersion" : "v1",
5
+ "spec" : {
6
+ "ports" : [
7
+ {
8
+ "targetPort" : 80,
9
+ "nodePort" : 0,
10
+ "port" : 80,
11
+ "protocol" : "TCP"
12
+ }
13
+ ],
14
+ "clusterIP" : "1.2.3.4",
15
+ "type": "LoadBalancer"
16
+ },
17
+ "metadata" : {
18
+ "name" : "my-service",
19
+ "creationTimestamp" : null,
20
+ "namespace" : "development",
21
+ "resourceVersion" : "2",
22
+ "annotations" : {
23
+ "key" : "value"
24
+ }
25
+ }
26
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "status" : {},
3
+ "kind" : "Service",
4
+ "apiVersion" : "v1",
5
+ "spec" : {
6
+ "ports" : [
7
+ {
8
+ "targetPort" : 80,
9
+ "nodePort" : 0,
10
+ "port" : 80,
11
+ "protocol" : "TCP"
12
+ }
13
+ ],
14
+ "clusterIP" : "1.2.3.4",
15
+ "type": "NodePort"
16
+ },
17
+ "metadata" : {
18
+ "name" : "my-service",
19
+ "creationTimestamp" : null,
20
+ "namespace" : "development",
21
+ "resourceVersion" : "2",
22
+ "annotations" : {
23
+ "key" : "value"
24
+ }
25
+ }
26
+ }
@@ -123,6 +123,43 @@ class KubeclientConfigTest < MiniTest::Test
123
123
  end
124
124
  end
125
125
 
126
+ def test_gcp_default_auth
127
+ Kubeclient::GoogleApplicationDefaultCredentials.expects(:token).returns('token1').once
128
+ parsed = YAML.safe_load(File.read(config_file('gcpauth.kubeconfig')), [Date, Time])
129
+ config = Kubeclient::Config.new(parsed, nil)
130
+ config.context(config.contexts.first)
131
+ end
132
+
133
+ # Each call to .context() obtains a new token, calling .auth_options doesn't change anything.
134
+ # NOTE: this is not a guarantee, may change, just testing current behavior.
135
+ def test_gcp_default_auth_renew
136
+ Kubeclient::GoogleApplicationDefaultCredentials.expects(:token).returns('token1').once
137
+ parsed = YAML.safe_load(File.read(config_file('gcpauth.kubeconfig')), [Date, Time])
138
+ config = Kubeclient::Config.new(parsed, nil)
139
+ context = config.context(config.contexts.first)
140
+ assert_equal({ bearer_token: 'token1' }, context.auth_options)
141
+ assert_equal({ bearer_token: 'token1' }, context.auth_options)
142
+
143
+ Kubeclient::GoogleApplicationDefaultCredentials.expects(:token).returns('token2').once
144
+ context2 = config.context(config.contexts.first)
145
+ assert_equal({ bearer_token: 'token2' }, context2.auth_options)
146
+ assert_equal({ bearer_token: 'token1' }, context.auth_options)
147
+ end
148
+
149
+ def test_oidc_auth_provider
150
+ Kubeclient::OIDCAuthProvider.expects(:token)
151
+ .with('client-id' => 'fake-client-id',
152
+ 'client-secret' => 'fake-client-secret',
153
+ 'id-token' => 'fake-id-token',
154
+ 'idp-issuer-url' => 'https://accounts.google.com',
155
+ 'refresh-token' => 'fake-refresh-token')
156
+ .returns('token1')
157
+ .once
158
+ parsed = YAML.safe_load(File.read(config_file('oidcauth.kubeconfig')))
159
+ config = Kubeclient::Config.new(parsed, nil)
160
+ config.context(config.contexts.first)
161
+ end
162
+
126
163
  private
127
164
 
128
165
  def check_context(context, ssl: true)
@@ -2,6 +2,7 @@ require 'bundler/setup'
2
2
  require 'minitest/autorun'
3
3
  require 'minitest/rg'
4
4
  require 'webmock/minitest'
5
+ require 'mocha/minitest'
5
6
  require 'json'
6
7
  require 'kubeclient'
7
8
 
@@ -0,0 +1,84 @@
1
+ require_relative 'test_helper'
2
+ require 'openid_connect'
3
+
4
+ class OIDCAuthProviderTest < MiniTest::Test
5
+ def setup
6
+ @client_id = 'client_id'
7
+ @client_secret = 'client_secret'
8
+ @idp_issuer_url = 'idp_issuer_url'
9
+ @refresh_token = 'refresh_token'
10
+ @id_token = 'id_token'
11
+ @new_id_token = 'new_id_token'
12
+ end
13
+
14
+ def test_expired_token
15
+ OpenIDConnect::Discovery::Provider::Config.stub(:discover!, discovery_mock) do
16
+ OpenIDConnect::ResponseObject::IdToken.stub(:decode, id_token_mock(Time.now.to_i - 7200)) do
17
+ OpenIDConnect::Client.stub(:new, openid_client_mock) do
18
+ retrieved_id_token = Kubeclient::OIDCAuthProvider.token(
19
+ 'client-id' => @client_id,
20
+ 'client-secret' => @client_secret,
21
+ 'id-token' => @id_token,
22
+ 'idp-issuer-url' => @idp_issuer_url,
23
+ 'refresh-token' => @refresh_token
24
+ )
25
+ assert_equal(@new_id_token, retrieved_id_token)
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ def test_valid_token
32
+ OpenIDConnect::Discovery::Provider::Config.stub(:discover!, discovery_mock) do
33
+ OpenIDConnect::ResponseObject::IdToken.stub(:decode, id_token_mock(Time.now.to_i + 7200)) do
34
+ retrieved_id_token = Kubeclient::OIDCAuthProvider.token(
35
+ 'client-id' => @client_id,
36
+ 'client-secret' => @client_secret,
37
+ 'id-token' => @id_token,
38
+ 'idp-issuer-url' => @idp_issuer_url,
39
+ 'refresh-token' => @refresh_token
40
+ )
41
+ assert_equal(@id_token, retrieved_id_token)
42
+ end
43
+ end
44
+ end
45
+
46
+ def test_missing_id_token
47
+ OpenIDConnect::Discovery::Provider::Config.stub(:discover!, discovery_mock) do
48
+ OpenIDConnect::Client.stub(:new, openid_client_mock) do
49
+ retrieved_id_token = Kubeclient::OIDCAuthProvider.token(
50
+ 'client-id' => @client_id,
51
+ 'client-secret' => @client_secret,
52
+ 'idp-issuer-url' => @idp_issuer_url,
53
+ 'refresh-token' => @refresh_token
54
+ )
55
+ assert_equal(@new_id_token, retrieved_id_token)
56
+ end
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def openid_client_mock
63
+ access_token = Minitest::Mock.new
64
+ access_token.expect(@id_token, @new_id_token)
65
+
66
+ openid_client = Minitest::Mock.new
67
+ openid_client.expect(:refresh_token=, nil, [@refresh_token])
68
+ openid_client.expect(:access_token!, access_token)
69
+ end
70
+
71
+ def id_token_mock(expiry)
72
+ id_token_mock = Minitest::Mock.new
73
+ id_token_mock.expect(:exp, expiry)
74
+ end
75
+
76
+ def discovery_mock
77
+ discovery = Minitest::Mock.new
78
+ discovery.expect(:jwks, 'jwks')
79
+ discovery.expect(:authorization_endpoint, 'authz_endpoint')
80
+ discovery.expect(:token_endpoint, 'token_endpoint')
81
+ discovery.expect(:userinfo_endpoint, 'userinfo_endpoint')
82
+ discovery
83
+ end
84
+ end
@@ -273,4 +273,58 @@ class TestService < MiniTest::Test
273
273
  data['metadata']['annotations']['key'] == 'value'
274
274
  end
275
275
  end
276
+
277
+ def test_json_patch_service
278
+ service = Kubeclient::Resource.new
279
+ name = 'my-service'
280
+
281
+ service.metadata = {}
282
+ service.metadata.name = name
283
+ service.metadata.namespace = 'development'
284
+
285
+ stub_core_api_list
286
+ expected_url = "http://localhost:8080/api/v1/namespaces/development/services/#{name}"
287
+ stub_request(:patch, expected_url)
288
+ .to_return(body: open_test_file('service_json_patch.json'), status: 200)
289
+
290
+ patch = [
291
+ { 'op' => 'add', 'path' => '/spec/type', 'value' => 'LoadBalancer' }
292
+ ]
293
+
294
+ client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1')
295
+ service = client.json_patch_service(name, patch, 'development')
296
+ assert_kind_of(RecursiveOpenStruct, service)
297
+
298
+ assert_requested(:patch, expected_url, times: 1) do |req|
299
+ data = JSON.parse(req.body)
300
+ req.headers['Content-Type'] == 'application/json-patch+json' &&
301
+ data == patch
302
+ end
303
+ end
304
+
305
+ def test_merge_patch_service
306
+ service = Kubeclient::Resource.new
307
+ name = 'my-service'
308
+
309
+ service.metadata = {}
310
+ service.metadata.name = name
311
+ service.metadata.namespace = 'development'
312
+
313
+ stub_core_api_list
314
+ expected_url = "http://localhost:8080/api/v1/namespaces/development/services/#{name}"
315
+ stub_request(:patch, expected_url)
316
+ .to_return(body: open_test_file('service_merge_patch.json'), status: 200)
317
+
318
+ patch = { spec: { type: 'NodePort' } }
319
+
320
+ client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1')
321
+ service = client.merge_patch_service(name, patch, 'development')
322
+ assert_kind_of(RecursiveOpenStruct, service)
323
+
324
+ assert_requested(:patch, expected_url, times: 1) do |req|
325
+ data = JSON.parse(req.body)
326
+ req.headers['Content-Type'] == 'application/merge-patch+json' &&
327
+ data['spec']['type'] == 'NodePort'
328
+ end
329
+ end
276
330
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kubeclient
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.2.2
4
+ version: 4.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alissa Bonas
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-01-09 00:00:00.000000000 Z
11
+ date: 2019-03-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -122,6 +122,34 @@ dependencies:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
124
  version: 0.5.1
125
+ - !ruby/object:Gem::Dependency
126
+ name: mocha
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '1.5'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '1.5'
139
+ - !ruby/object:Gem::Dependency
140
+ name: openid_connect
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '1.1'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '1.1'
125
153
  - !ruby/object:Gem::Dependency
126
154
  name: rest-client
127
155
  requirement: !ruby/object:Gem::Requirement
@@ -195,6 +223,7 @@ files:
195
223
  - lib/kubeclient/google_application_default_credentials.rb
196
224
  - lib/kubeclient/http_error.rb
197
225
  - lib/kubeclient/missing_kind_compatibility.rb
226
+ - lib/kubeclient/oidc_auth_provider.rb
198
227
  - lib/kubeclient/resource.rb
199
228
  - lib/kubeclient/resource_not_found_error.rb
200
229
  - lib/kubeclient/version.rb
@@ -206,7 +235,9 @@ files:
206
235
  - test/config/external-cert.pem
207
236
  - test/config/external-key.rsa
208
237
  - test/config/external.kubeconfig
238
+ - test/config/gcpauth.kubeconfig
209
239
  - test/config/nouser.kubeconfig
240
+ - test/config/oidcauth.kubeconfig
210
241
  - test/config/timestamps.kubeconfig
211
242
  - test/config/userauth.kubeconfig
212
243
  - test/json/bindings_list.json
@@ -258,7 +289,9 @@ files:
258
289
  - test/json/service_account.json
259
290
  - test/json/service_account_list.json
260
291
  - test/json/service_illegal_json_404.json
292
+ - test/json/service_json_patch.json
261
293
  - test/json/service_list.json
294
+ - test/json/service_merge_patch.json
262
295
  - test/json/service_patch.json
263
296
  - test/json/service_update.json
264
297
  - test/json/template.json
@@ -279,6 +312,7 @@ files:
279
312
  - test/test_missing_methods.rb
280
313
  - test/test_namespace.rb
281
314
  - test/test_node.rb
315
+ - test/test_oidc_auth_provider.rb
282
316
  - test/test_persistent_volume.rb
283
317
  - test/test_persistent_volume_claim.rb
284
318
  - test/test_pod.rb
@@ -326,7 +360,9 @@ test_files:
326
360
  - test/config/external-cert.pem
327
361
  - test/config/external-key.rsa
328
362
  - test/config/external.kubeconfig
363
+ - test/config/gcpauth.kubeconfig
329
364
  - test/config/nouser.kubeconfig
365
+ - test/config/oidcauth.kubeconfig
330
366
  - test/config/timestamps.kubeconfig
331
367
  - test/config/userauth.kubeconfig
332
368
  - test/json/bindings_list.json
@@ -378,7 +414,9 @@ test_files:
378
414
  - test/json/service_account.json
379
415
  - test/json/service_account_list.json
380
416
  - test/json/service_illegal_json_404.json
417
+ - test/json/service_json_patch.json
381
418
  - test/json/service_list.json
419
+ - test/json/service_merge_patch.json
382
420
  - test/json/service_patch.json
383
421
  - test/json/service_update.json
384
422
  - test/json/template.json
@@ -399,6 +437,7 @@ test_files:
399
437
  - test/test_missing_methods.rb
400
438
  - test/test_namespace.rb
401
439
  - test/test_node.rb
440
+ - test/test_oidc_auth_provider.rb
402
441
  - test/test_persistent_volume.rb
403
442
  - test/test_persistent_volume_claim.rb
404
443
  - test/test_pod.rb