kubeclient 4.4.0 → 4.9.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: 688623a12e9f3e68f6573b39d0d8e58ddee8848154d72670848d05096453a827
4
- data.tar.gz: fa3432fed8c342a2686a9f0f1ba599f7e87fa85ac42aa410f39b61674754e6ca
3
+ metadata.gz: 3c8f79b61300006829c55c6b13715e6073a1cf9517ad8334959ff64706bee4ca
4
+ data.tar.gz: 04ae220b4f78422992e34538e5b47a0dbf0a832aa2eb9f1ac86d3f1c221cd088
5
5
  SHA512:
6
- metadata.gz: c6543f0f8494b64d3d56342dab4dc7ff70eeeb97f6a18cfa2915885c56c5e9051e0cd8df6eba330129330b8ba3e3e4088838f8b06cc10093f40a280e52e870b9
7
- data.tar.gz: 7df2f38d1b116a137446935c4c166236c7ce04027cd17bc038047bdfee817c4b4a734d6a5775dafc1e1d02ed1ad3ec15843d5fcb59d0ca62c459dfe0de1848cb
6
+ metadata.gz: b7c9ab202ff3e30f5884fd2f80987fe87a3a65a86fa1a3ecdba16383cbb958fdb98f02dd48a00a5ac50fac828b335a33b33f634f7e2cbe7913883e5aa9a81fa4
7
+ data.tar.gz: 52078faf6ec204c2b08658d83604fb84c7e68ad409d754ada7ae9d0d989fbce01ae53b2c33993dc71a6bed5851904fe22b6cd3688f67fa1c4bf9cd7704c43f71
@@ -1,13 +1,29 @@
1
1
  language: ruby
2
+ sudo: false
3
+ cache: bundler
4
+ script: bundle exec rake $TASK
5
+
6
+ os:
7
+ - linux
8
+ - osx
2
9
  rvm:
3
- - "2.2"
10
+ - "2.2.0"
4
11
  - "2.3.0"
5
12
  - "2.4.0"
6
13
  - "2.5.0"
7
- - "2.6.0-preview1"
8
- sudo: false
9
- cache: bundler
10
- script: bundle exec rake $TASK
14
+ - "2.6.0"
15
+ - "2.7.0"
11
16
  env:
12
17
  - TASK=test
13
- - TASK=rubocop
18
+ matrix:
19
+ exclude:
20
+ - os: osx
21
+ rvm: "2.2.0"
22
+ - os: osx
23
+ rvm: "2.3.0"
24
+ # No point running Rubocop on different rubies, results will be same.
25
+ # The rubies it expects the code to target are controlled in .rubocop.yml.
26
+ include:
27
+ - os: linux
28
+ rvm: "2.5.0"
29
+ env: TASK=rubocop
@@ -4,7 +4,44 @@ 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
- ## Unreleased
7
+ ## 4.9.0 - 2020-08-03
8
+ ### Added
9
+ - Support for `user: exec` credential plugins using TLS client auth (#453)
10
+
11
+ ## 4.8.0 — 2020-07-03
12
+
13
+ ### Added
14
+ - Support for server-side apply (#448).
15
+
16
+ ### Fixed
17
+ - Declared forgotten dependency on jsonpath, needed for `gcp` provider with `cmd-path` (#450).
18
+
19
+ ## 4.7.0 — 2020-06-14
20
+
21
+ ### Fixed
22
+ - Ruby 2.7 compatibility: bumped minimum recursive-open-struct to one that works on 2.7 (#439).
23
+ - Ruby 2.7 warnings (#433, #438).
24
+ - Improved watch documentation, including behavior planned to change in 5.0.0 (#436).
25
+
26
+ ### Added
27
+ - Google Application Default Credentials: Added `userinfo.email` to requested scopes, which is necessary for RBAC policies (#441).
28
+
29
+ ## 4.6.0 — 2019-12-30
30
+
31
+ ### Fixed
32
+ - AmazonEksCredentials was sometimes leaving base64 padding that IAM auth of the EKS cluster rejects. Now padding is always stripped. (#424, #423)
33
+
34
+ ### Added
35
+ - Allow calling `watch_foos` methods with a block, simpler to use and guarantees closing the connection. (#425)
36
+
37
+ - Support `limitBytes` query parameter for `get_pod_log`. (#426)
38
+
39
+ ## 4.5.0 — 2019-09-27
40
+
41
+ ### Added
42
+ - Support `:resourceVersion` parameter in `get_foos` methods (similar to existing support in `watch_foos` methods). (#420)
43
+
44
+ - Relax dependency on `http` gem to allow both 3.x and 4.x. (#413)
8
45
 
9
46
  ## 4.4.0 — 2019-05-03
10
47
 
data/README.md CHANGED
@@ -207,7 +207,7 @@ client = Kubeclient::Client.new(
207
207
 
208
208
  ### Timeouts
209
209
 
210
- Watching never times out.
210
+ Watching configures the socket to never time out (however, sooner or later all watches terminate).
211
211
 
212
212
  One-off actions like `.get_*`, `.delete_*` have a configurable timeout:
213
213
  ```ruby
@@ -420,16 +420,48 @@ We try to support the last 3 minor versions, matching the [official support poli
420
420
  Kubernetes 1.2 and below have known issues and are unsupported.
421
421
  Kubernetes 1.3 presumed to still work although nobody is really testing on such old versions...
422
422
 
423
- ## Examples:
423
+ ## Supported actions & examples:
424
424
 
425
- #### Get all instances of a specific entity type
425
+ Summary of main CRUD actions:
426
+
427
+ ```
428
+ get_foos(namespace: 'namespace', **opts) # namespaced collection
429
+ get_foos(**opts) # all namespaces or global collection
430
+
431
+ get_foo('name', 'namespace', opts) # namespaced
432
+ get_foo('name', nil, opts) # global
433
+
434
+ watch_foos(namespace: ns, **opts) # namespaced collection
435
+ watch_foos(**opts) # all namespaces or global collection
436
+ watch_foos(namespace: ns, name: 'name', **opts) # namespaced single object
437
+ watch_foos(name: 'name', **opts) # global single object
438
+
439
+ delete_foo('name', 'namespace', opts) # namespaced
440
+ delete_foo('name', nil, opts) # global
441
+
442
+ create_foo(Kubeclient::Resource.new({metadata: {name: 'name', namespace: 'namespace', ...}, ...}))
443
+ create_foo(Kubeclient::Resource.new({metadata: {name: 'name', ...}, ...})) # global
444
+
445
+ update_foo(Kubeclient::Resource.new({metadata: {name: 'name', namespace: 'namespace', ...}, ...}))
446
+ update_foo(Kubeclient::Resource.new({metadata: {name: 'name', ...}, ...})) # global
447
+
448
+ patch_foo('name', patch, 'namespace') # namespaced
449
+ patch_foo('name', patch) # global
450
+
451
+ apply_foo(Kubeclient::Resource.new({metadata: {name: 'name', namespace: 'namespace', ...}, ...}), field_manager: 'myapp', **opts)
452
+ apply_foo(Kubeclient::Resource.new({metadata: {name: 'name', ...}, ...}), field_manager: 'myapp', **opts) # global
453
+ ```
454
+
455
+ These grew to be quite inconsistent :confounded:, see https://github.com/abonas/kubeclient/issues/312 and https://github.com/abonas/kubeclient/issues/332 for improvement plans.
456
+
457
+ ### Get all instances of a specific entity type
426
458
  Such as: `get_pods`, `get_secrets`, `get_services`, `get_nodes`, `get_replication_controllers`, `get_resource_quotas`, `get_limit_ranges`, `get_persistent_volumes`, `get_persistent_volume_claims`, `get_component_statuses`, `get_service_accounts`
427
459
 
428
460
  ```ruby
429
461
  pods = client.get_pods
430
462
  ```
431
463
 
432
- Get all entities of a specific type in a namespace:<br>
464
+ Get all entities of a specific type in a namespace:
433
465
 
434
466
  ```ruby
435
467
  services = client.get_services(namespace: 'development')
@@ -447,7 +479,22 @@ You can specify multiple labels (that option will return entities which have bot
447
479
  pods = client.get_pods(label_selector: 'name=redis-master,app=redis')
448
480
  ```
449
481
 
450
- Get all entities of a specific type in chunks:
482
+ There is also [a limited ability to filter by *some* fields](https://kubernetes.io/docs/concepts/overview/working-with-objects/field-selectors/). Which fields are supported is not documented, you can try and see if you get an error...
483
+ ```ruby
484
+ client.get_pods(field_selector: 'spec.nodeName=master-0')
485
+ ```
486
+
487
+ You can ask for entities at a specific version by specifying a parameter named `resource_version`:
488
+ ```ruby
489
+ pods = client.get_pods(resource_version: '0')
490
+ ```
491
+ but it's not guaranteed you'll get it. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions to understand the semantics.
492
+
493
+ With default (`as: :ros`) return format, the returned object acts like an array of the individual pods, but also supports a `.resourceVersion` method.
494
+
495
+ With `:parsed` and `:parsed_symbolized` formats, the returned data structure matches kubernetes list structure: it's a hash containing `metadata` and `items` keys, the latter containing the individual pods.
496
+
497
+ #### Get all entities of a specific type in chunks
451
498
 
452
499
  ```ruby
453
500
  continue = nil
@@ -502,7 +549,87 @@ Other formats are:
502
549
  - `:parsed` for `JSON.parse`
503
550
  - `:parsed_symbolized` for `JSON.parse(..., symbolize_names: true)`
504
551
 
505
- #### Delete an entity (by name)
552
+ ### Watch Receive entities updates
553
+
554
+ See https://kubernetes.io/docs/reference/using-api/api-concepts/#efficient-detection-of-changes for an overview.
555
+
556
+ It is possible to receive live update notices watching the relevant entities:
557
+
558
+ ```ruby
559
+ client.watch_pods do |notice|
560
+ # process notice data
561
+ end
562
+ ```
563
+
564
+ The notices have `.type` field which may be `'ADDED'`, `'MODIFIED'`, `'DELETED'`, or currently `'ERROR'`, and an `.object` field containing the object. **UPCOMING CHANGE**: In next major version, we plan to raise exceptions instead of passing on ERROR into the block.
565
+
566
+ For namespaced entities, the default watches across all namespaces, and you can specify `client.watch_secrets(namespace: 'foo')` to only watch in a single namespace.
567
+
568
+ You can narrow down using `label_selector:` and `field_selector:` params, like with `get_pods` methods.
569
+
570
+ You can also watch a single object by specifying `name:` e.g. `client.watch_nodes(name: 'gandalf')` (not namespaced so a name is enough) or `client.watch_pods(namespace: 'foo', name: 'bar')` (namespaced, need both params).
571
+ Note the method name is still plural! There is no `watch_pod`, only `watch_pods`. The yielded "type" remains the same — watch notices, it's just they'll always refer to the same object.
572
+
573
+ You can use `as:` param to control the format of the yielded notices.
574
+
575
+ #### All watches come to an end!
576
+
577
+ While nominally the watch block *looks* like an infinite loop, that's unrealistic. Network connections eventually get severed, and kubernetes apiserver is known to terminate watches.
578
+
579
+ Unfortunately, this sometimes raises an exception and sometimes the loop just exits. **UPCOMING CHANGE**: In next major version, non-deliberate termination will always raise an exception; the block will only exit silenty if stopped deliberately.
580
+
581
+ #### Deliberately stopping a watch
582
+
583
+ You can use `break` or `return` inside the watch block.
584
+
585
+ It is possible to interrupt the watcher from another thread with:
586
+
587
+ ```ruby
588
+ watcher = client.watch_pods
589
+
590
+ watcher.each do |notice|
591
+ # process notice data
592
+ end
593
+ # <- control will pass here after .finish is called
594
+
595
+ ### In another thread ###
596
+ watcher.finish
597
+ ```
598
+
599
+ #### Starting watch version
600
+
601
+ You can specify version to start from, commonly used in "List+Watch" pattern:
602
+ ```
603
+ list = client.get_pods
604
+ collection_version = list.resourceVersion
605
+ # or with other return formats:
606
+ list = client.get_pods(as: :parsed)
607
+ collection_version = list['metadata']['resourceVersion']
608
+
609
+ # note spelling resource_version vs resourceVersion.
610
+ client.watch_pods(resource_version: collection_version) do |notice|
611
+ # process notice data
612
+ end
613
+ ```
614
+ It's important to understand [the effects of unset/0/specific resource_version](https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions) as it modifies the behavior of the watch — in some modes you'll first see a burst of synthetic 'ADDED' notices for all existing objects.
615
+
616
+ If you re-try a terminated watch again without specific resourceVersion, you might see previously seen notices again, and might miss some events.
617
+
618
+ To attempt resuming a watch from same point, you can try using last resourceVersion observed during the watch. Or do list+watch again.
619
+
620
+ Whenever you ask for a specific version, you must be prepared for an 410 "Gone" error if the server no longer recognizes it.
621
+
622
+ #### Watch events about a particular object
623
+ Events are [entities in their own right](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#event-v1-core).
624
+ You can use the `field_selector` option as part of the watch methods.
625
+
626
+ ```ruby
627
+ client.watch_events(namespace: 'development', field_selector: 'involvedObject.name=redis-master') do |notice|
628
+ # process notice date
629
+ end
630
+ ```
631
+
632
+ ### Delete an entity (by name)
506
633
 
507
634
  For example: `delete_pod "pod name"` , `delete_replication_controller "rc name"`, `delete_node "node name"`, `delete_secret "secret name"`
508
635
 
@@ -526,7 +653,7 @@ delete_options = Kubeclient::Resource.new(
526
653
  client.delete_deployment(deployment_name, namespace, delete_options: delete_options)
527
654
  ```
528
655
 
529
- #### Create an entity
656
+ ### Create an entity
530
657
  For example: `create_pod pod_object`, `create_replication_controller rc_obj`, `create_secret secret_object`, `create_resource_quota resource_quota_object`, `create_limit_range limit_range_object`, `create_persistent_volume persistent_volume_object`, `create_persistent_volume_claim persistent_volume_claim_object`, `create_service_account service_account_object`
531
658
 
532
659
  Input parameter - object of type `Service`, `Pod`, `ReplicationController`.
@@ -552,7 +679,7 @@ service.metadata.labels.role = 'slave'
552
679
  client.create_service(service)
553
680
  ```
554
681
 
555
- #### Update an entity
682
+ ### Update an entity
556
683
  For example: `update_pod`, `update_service`, `update_replication_controller`, `update_secret`, `update_resource_quota`, `update_limit_range`, `update_persistent_volume`, `update_persistent_volume_claim`, `update_service_account`
557
684
 
558
685
  Input parameter - object of type `Pod`, `Service`, `ReplicationController` etc.
@@ -563,7 +690,7 @@ The below example is for v1
563
690
  updated = client.update_service(service1)
564
691
  ```
565
692
 
566
- #### Patch an entity (by name)
693
+ ### Patch an entity (by name)
567
694
  For example: `patch_pod`, `patch_service`, `patch_secret`, `patch_resource_quota`, `patch_persistent_volume`
568
695
 
569
696
  Input parameters - name (string) specifying the entity name, patch (hash) to be applied to the resource, optional: namespace name (string)
@@ -578,41 +705,41 @@ patched = client.patch_pod("docker-registry", {metadata: {annotations: {key: 'va
578
705
 
579
706
  `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.
580
707
 
581
- #### Get all entities of all types : all_entities
582
- 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.
583
- This method is a convenience method instead of calling each entity's get method separately.
708
+ ### Apply an entity
584
709
 
585
- ```ruby
586
- client.all_entities
587
- ```
710
+ This is similar to `kubectl apply --server-side` (kubeclient doesn't implement logic for client-side apply). See https://kubernetes.io/docs/reference/using-api/api-concepts/#server-side-apply
588
711
 
589
- #### Receive entity updates
590
- It is possible to receive live update notices watching the relevant entities:
712
+ For example: `apply_pod`
591
713
 
592
- ```ruby
593
- watcher = client.watch_pods
594
- watcher.each do |notice|
595
- # process notice data
596
- end
597
- ```
714
+ Input parameters - resource (Kubeclient::Resource) representing the desired state of the resource, field_manager (String) to identify the system managing the state of the resource, force (Boolean) whether or not to override a field managed by someone else.
598
715
 
599
- It is possible to interrupt the watcher from another thread with:
716
+ Example:
600
717
 
601
718
  ```ruby
602
- watcher.finish
719
+ service = Kubeclient::Resource.new(
720
+ metadata: {
721
+ name: 'redis-master',
722
+ namespace: 'staging',
723
+ },
724
+ spec: {
725
+ ...
726
+ }
727
+ )
728
+
729
+ client.apply_service(service, field_manager: 'myapp')
603
730
  ```
604
731
 
605
- #### Watch events for a particular object
606
- You can use the `field_selector` option as part of the watch methods.
732
+ ### Get all entities of all types : all_entities
733
+
734
+ Makes requests for all entities of each discovered kind (in this client's API group). This method is a convenience method instead of calling each entity's get method separately.
735
+
736
+ Returns a hash with keys being the *singular* entity kind, in lowercase underscore style. For example for core API group may return keys `"node'`, `"secret"`, `"service"`, `"pod"`, `"replication_controller"`, `"namespace"`, `"resource_quota"`, `"limit_range"`, `"endpoint"`, `"event"`, `"persistent_volume"`, `"persistent_volume_claim"`, `"component_status"`, `"service_account"`. Each key points to an EntityList of same type.
607
737
 
608
738
  ```ruby
609
- watcher = client.watch_events(namespace: 'development', field_selector: 'involvedObject.name=redis-master')
610
- watcher.each do |notice|
611
- # process notice date
612
- end
739
+ client.all_entities
613
740
  ```
614
741
 
615
- #### Get a proxy URL
742
+ ### Get a proxy URL
616
743
  You can get a complete URL for connecting a kubernetes entity via the proxy.
617
744
 
618
745
  ```ruby
@@ -627,7 +754,7 @@ client.proxy_url('pod', 'podname', 5001, 'ns')
627
754
  # => "https://localhost.localdomain:8443/api/v1/namespaces/ns/pods/podname:5001/proxy"
628
755
  ```
629
756
 
630
- #### Get the logs of a pod
757
+ ### Get the logs of a pod
631
758
  You can get the logs of a running pod, specifying the name of the pod and the
632
759
  namespace where the pod is running:
633
760
 
@@ -664,16 +791,21 @@ client.get_pod_log('pod-name', 'default', tail_lines: 10)
664
791
  # => "..."
665
792
  ```
666
793
 
794
+ Kubernetes can fetch a specific number of bytes from the log, but the exact size is not guaranteed and last line may not be terminated:
795
+ ```ruby
796
+ client.get_pod_log('pod-name', 'default', limit_bytes: 10)
797
+ # => "..."
798
+ ```
799
+
667
800
  You can also watch the logs of a pod to get a stream of data:
668
801
 
669
802
  ```ruby
670
- watcher = client.watch_pod_log('pod-name', 'default', container: 'ruby')
671
- watcher.each do |line|
803
+ client.watch_pod_log('pod-name', 'default', container: 'ruby') do |line|
672
804
  puts line
673
805
  end
674
806
  ```
675
807
 
676
- #### Process a template
808
+ ### OpenShift: Process a template
677
809
  Returns a processed template containing a list of objects to create.
678
810
  Input parameter - template (hash)
679
811
  Besides its metadata, the template should include a list of objects to be processed and a list of parameters
@@ -20,7 +20,9 @@ Edit `CHANGELOG.md` as necessary. Even if all included changes remembered to up
20
20
 
21
21
  Bump `lib/kubeclient/version.rb` manually, or by using:
22
22
  ```bash
23
- git checkout -b release-$RELEASE_VERSION
23
+ RELEASE_VERSION=x.y.z
24
+
25
+ git checkout -b "release-$RELEASE_VERSION" $RELEASE_BRANCH
24
26
  # Won't work with uncommitted changes, you have to commit the changelog first.
25
27
  gem bump --version $RELEASE_VERSION
26
28
  git show # View version bump change.
@@ -20,7 +20,7 @@ Gem::Specification.new do |spec|
20
20
  spec.require_paths = ['lib']
21
21
  spec.required_ruby_version = '>= 2.2.0'
22
22
 
23
- spec.add_development_dependency 'bundler', '~> 1.6'
23
+ spec.add_development_dependency 'bundler', '>= 1.6'
24
24
  spec.add_development_dependency 'rake', '~> 12.0'
25
25
  spec.add_development_dependency 'minitest'
26
26
  spec.add_development_dependency 'minitest-rg'
@@ -30,9 +30,9 @@ Gem::Specification.new do |spec|
30
30
  spec.add_development_dependency 'googleauth', '~> 0.5.1'
31
31
  spec.add_development_dependency('mocha', '~> 1.5')
32
32
  spec.add_development_dependency 'openid_connect', '~> 1.1'
33
- spec.add_development_dependency 'jsonpath', '~> 1.0'
34
33
 
34
+ spec.add_dependency 'jsonpath', '~> 1.0'
35
35
  spec.add_dependency 'rest-client', '~> 2.0'
36
- spec.add_dependency 'recursive-open-struct', '~> 1.0', '>= 1.0.4'
37
- spec.add_dependency 'http', '~> 3.0'
36
+ spec.add_dependency 'recursive-open-struct', '~> 1.1', '>= 1.1.1'
37
+ spec.add_dependency 'http', '>= 3.0', '< 5.0'
38
38
  end
@@ -28,7 +28,7 @@ module Kubeclient
28
28
  uri,
29
29
  '/api',
30
30
  version,
31
- options
31
+ **options
32
32
  )
33
33
  end
34
34
  end
@@ -38,7 +38,7 @@ module Kubeclient
38
38
  'X-K8s-Aws-Id' => eks_cluster
39
39
  }
40
40
  )
41
- kube_token = 'k8s-aws-v1.' + Base64.urlsafe_encode64(presigned_url_string.to_s).chomp('==')
41
+ kube_token = 'k8s-aws-v1.' + Base64.urlsafe_encode64(presigned_url_string.to_s).sub(/=*$/, '') # rubocop:disable Metrics/LineLength
42
42
  kube_token
43
43
  end
44
44
  end
@@ -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 json_patch merge_patch].freeze
8
+ ENTITY_METHODS = %w[get watch delete create update patch json_patch merge_patch apply].freeze
9
9
 
10
10
  DEFAULT_SSL_OPTIONS = {
11
11
  client_cert: nil,
@@ -37,10 +37,11 @@ module Kubeclient
37
37
  DEFAULT_HTTP_MAX_REDIRECTS = 10
38
38
 
39
39
  SEARCH_ARGUMENTS = {
40
- 'labelSelector' => :label_selector,
41
- 'fieldSelector' => :field_selector,
42
- 'limit' => :limit,
43
- 'continue' => :continue
40
+ 'labelSelector' => :label_selector,
41
+ 'fieldSelector' => :field_selector,
42
+ 'resourceVersion' => :resource_version,
43
+ 'limit' => :limit,
44
+ 'continue' => :continue
44
45
  }.freeze
45
46
 
46
47
  WATCH_ARGUMENTS = {
@@ -212,12 +213,12 @@ module Kubeclient
212
213
  end
213
214
 
214
215
  # watch all entities of a type e.g. watch_nodes, watch_pods, etc.
215
- define_singleton_method("watch_#{entity.method_names[1]}") do |options = {}|
216
+ define_singleton_method("watch_#{entity.method_names[1]}") do |options = {}, &block|
216
217
  # This method used to take resource_version as a param, so
217
218
  # this conversion is to keep backwards compatibility
218
219
  options = { resource_version: options } unless options.is_a?(Hash)
219
220
 
220
- watch_entities(entity.resource_name, options)
221
+ watch_entities(entity.resource_name, options, &block)
221
222
  end
222
223
 
223
224
  # get a single entity of a specific type by name
@@ -228,7 +229,7 @@ module Kubeclient
228
229
 
229
230
  define_singleton_method("delete_#{entity.method_names[0]}") \
230
231
  do |name, namespace = nil, opts = {}|
231
- delete_entity(entity.resource_name, name, namespace, opts)
232
+ delete_entity(entity.resource_name, name, namespace, **opts)
232
233
  end
233
234
 
234
235
  define_singleton_method("create_#{entity.method_names[0]}") do |entity_config|
@@ -253,6 +254,10 @@ module Kubeclient
253
254
  do |name, patch, namespace = nil|
254
255
  patch_entity(entity.resource_name, name, patch, 'merge-patch', namespace)
255
256
  end
257
+
258
+ define_singleton_method("apply_#{entity.method_names[0]}") do |*args|
259
+ apply_entity(entity.resource_name, *args)
260
+ end
256
261
  end
257
262
  end
258
263
  # rubocop:enable Metrics/BlockLength
@@ -294,7 +299,9 @@ module Kubeclient
294
299
  # :as (:raw|:ros) - defaults to :ros
295
300
  # :raw - return the raw response body as a string
296
301
  # :ros - return a collection of RecursiveOpenStruct objects
297
- def watch_entities(resource_name, options = {})
302
+ # Accepts an optional block, that will be called with each entity,
303
+ # otherwise returns a WatchStream
304
+ def watch_entities(resource_name, options = {}, &block)
298
305
  ns = build_namespace_prefix(options[:namespace])
299
306
 
300
307
  path = "watch/#{ns}#{resource_name}"
@@ -305,11 +312,13 @@ module Kubeclient
305
312
  WATCH_ARGUMENTS.each { |k, v| params[k] = options[v] if options[v] }
306
313
  uri.query = URI.encode_www_form(params) if params.any?
307
314
 
308
- Kubeclient::Common::WatchStream.new(
315
+ watcher = Kubeclient::Common::WatchStream.new(
309
316
  uri,
310
317
  http_options(uri),
311
318
  formatter: ->(value) { format_response(options[:as] || @as, value) }
312
319
  )
320
+
321
+ return_or_yield_to_watcher(watcher, &block)
313
322
  end
314
323
 
315
324
  # Accepts the following options:
@@ -406,6 +415,19 @@ module Kubeclient
406
415
  format_response(@as, response.body)
407
416
  end
408
417
 
418
+ def apply_entity(resource_name, resource, field_manager:, force: true)
419
+ name = "#{resource[:metadata][:name]}?fieldManager=#{field_manager}&force=#{force}"
420
+ ns_prefix = build_namespace_prefix(resource[:metadata][:namespace])
421
+ response = handle_exception do
422
+ rest_client[ns_prefix + resource_name + "/#{name}"]
423
+ .patch(
424
+ resource.to_json,
425
+ { 'Content-Type' => 'application/apply-patch+yaml' }.merge(@headers)
426
+ )
427
+ end
428
+ format_response(@as, response.body)
429
+ end
430
+
409
431
  def all_entities(options = {})
410
432
  discover unless @discovered
411
433
  @entities.values.each_with_object({}) do |entity, result_hash|
@@ -422,13 +444,14 @@ module Kubeclient
422
444
 
423
445
  def get_pod_log(pod_name, namespace,
424
446
  container: nil, previous: false,
425
- timestamps: false, since_time: nil, tail_lines: nil)
447
+ timestamps: false, since_time: nil, tail_lines: nil, limit_bytes: nil)
426
448
  params = {}
427
449
  params[:previous] = true if previous
428
450
  params[:container] = container if container
429
451
  params[:timestamps] = timestamps if timestamps
430
452
  params[:sinceTime] = format_datetime(since_time) if since_time
431
453
  params[:tailLines] = tail_lines if tail_lines
454
+ params[:limitBytes] = limit_bytes if limit_bytes
432
455
 
433
456
  ns = build_namespace_prefix(namespace)
434
457
  handle_exception do
@@ -437,7 +460,7 @@ module Kubeclient
437
460
  end
438
461
  end
439
462
 
440
- def watch_pod_log(pod_name, namespace, container: nil)
463
+ def watch_pod_log(pod_name, namespace, container: nil, &block)
441
464
  # Adding the "follow=true" query param tells the Kubernetes API to keep
442
465
  # the connection open and stream updates to the log.
443
466
  params = { follow: true }
@@ -449,7 +472,10 @@ module Kubeclient
449
472
  uri.path += "/#{@api_version}/#{ns}pods/#{pod_name}/log"
450
473
  uri.query = URI.encode_www_form(params)
451
474
 
452
- Kubeclient::Common::WatchStream.new(uri, http_options(uri), formatter: ->(value) { value })
475
+ watcher = Kubeclient::Common::WatchStream.new(
476
+ uri, http_options(uri), formatter: ->(value) { value }
477
+ )
478
+ return_or_yield_to_watcher(watcher, &block)
453
479
  end
454
480
 
455
481
  def proxy_url(kind, name, port, namespace = '')
@@ -586,6 +612,16 @@ module Kubeclient
586
612
  raise ArgumentError, msg unless File.readable?(@auth_options[:bearer_token_file])
587
613
  end
588
614
 
615
+ def return_or_yield_to_watcher(watcher, &block)
616
+ return watcher unless block_given?
617
+
618
+ begin
619
+ watcher.each(&block)
620
+ ensure
621
+ watcher.finish
622
+ end
623
+ end
624
+
589
625
  def http_options(uri)
590
626
  options = {
591
627
  basic_auth_user: @auth_options[:username],
@@ -41,6 +41,11 @@ module Kubeclient
41
41
  def context(context_name = nil)
42
42
  cluster, user, namespace = fetch_context(context_name || @kcfg['current-context'])
43
43
 
44
+ if user.key?('exec')
45
+ exec_opts = expand_command_option(user['exec'], 'command')
46
+ user['exec_result'] = ExecCredentials.run(exec_opts)
47
+ end
48
+
44
49
  ca_cert_data = fetch_cluster_ca_data(cluster)
45
50
  client_cert_data = fetch_user_cert_data(user)
46
51
  client_key_data = fetch_user_key_data(user)
@@ -134,6 +139,8 @@ module Kubeclient
134
139
  File.read(ext_file_path(user['client-certificate']))
135
140
  elsif user.key?('client-certificate-data')
136
141
  Base64.decode64(user['client-certificate-data'])
142
+ elsif user.key?('exec_result') && user['exec_result'].key?('clientCertificateData')
143
+ user['exec_result']['clientCertificateData']
137
144
  end
138
145
  end
139
146
 
@@ -142,6 +149,8 @@ module Kubeclient
142
149
  File.read(ext_file_path(user['client-key']))
143
150
  elsif user.key?('client-key-data')
144
151
  Base64.decode64(user['client-key-data'])
152
+ elsif user.key?('exec_result') && user['exec_result'].key?('clientKeyData')
153
+ user['exec_result']['clientKeyData']
145
154
  end
146
155
  end
147
156
 
@@ -149,9 +158,8 @@ module Kubeclient
149
158
  options = {}
150
159
  if user.key?('token')
151
160
  options[:bearer_token] = user['token']
152
- elsif user.key?('exec')
153
- exec_opts = expand_command_option(user['exec'], 'command')
154
- options[:bearer_token] = Kubeclient::ExecCredentials.token(exec_opts)
161
+ elsif user.key?('exec_result') && user['exec_result'].key?('token')
162
+ options[:bearer_token] = user['exec_result']['token']
155
163
  elsif user.key?('auth-provider')
156
164
  options[:bearer_token] = fetch_token_from_provider(user['auth-provider'])
157
165
  else
@@ -6,7 +6,7 @@ module Kubeclient
6
6
  # Inspired by https://github.com/kubernetes/client-go/blob/master/plugin/pkg/client/auth/exec/exec.go
7
7
  class ExecCredentials
8
8
  class << self
9
- def token(opts)
9
+ def run(opts)
10
10
  require 'open3'
11
11
  require 'json'
12
12
 
@@ -25,7 +25,7 @@ module Kubeclient
25
25
 
26
26
  creds = JSON.parse(out)
27
27
  validate_credentials(opts, creds)
28
- creds['status']['token']
28
+ creds['status']
29
29
  end
30
30
 
31
31
  private
@@ -34,6 +34,36 @@ module Kubeclient
34
34
  raise KeyError, 'exec command is required' unless opts['command']
35
35
  end
36
36
 
37
+ def validate_client_credentials_status(status)
38
+ has_client_cert_data = status.key?('clientCertificateData')
39
+ has_client_key_data = status.key?('clientKeyData')
40
+
41
+ if has_client_cert_data && !has_client_key_data
42
+ raise 'exec plugin didn\'t return client key data'
43
+ end
44
+
45
+ if !has_client_cert_data && has_client_key_data
46
+ raise 'exec plugin didn\'t return client certificate data'
47
+ end
48
+
49
+ has_client_cert_data && has_client_key_data
50
+ end
51
+
52
+ def validate_credentials_status(status)
53
+ raise 'exec plugin didn\'t return a status field' if status.nil?
54
+
55
+ has_client_credentials = validate_client_credentials_status(status)
56
+ has_token = status.key?('token')
57
+
58
+ if has_client_credentials && has_token
59
+ raise 'exec plugin returned both token and client data'
60
+ end
61
+
62
+ return if has_client_credentials || has_token
63
+
64
+ raise 'exec plugin didn\'t return a token or client data' unless has_token
65
+ end
66
+
37
67
  def validate_credentials(opts, creds)
38
68
  # out should have ExecCredential structure
39
69
  raise 'invalid credentials' if creds.nil?
@@ -45,8 +75,7 @@ module Kubeclient
45
75
  "plugin returned version #{creds['apiVersion']}"
46
76
  end
47
77
 
48
- raise 'exec plugin didn\'t return a status field' if creds['status'].nil?
49
- raise 'exec plugin didn\'t return a token' if creds['status']['token'].nil?
78
+ validate_credentials_status(creds['status'])
50
79
  end
51
80
 
52
81
  # Transform name/value pairs to hash
@@ -16,7 +16,12 @@ module Kubeclient
16
16
  'googleauth gem. To support auth-provider gcp, you must include it in your ' \
17
17
  "calling application. Failed with: #{e.message}"
18
18
  end
19
- scopes = ['https://www.googleapis.com/auth/cloud-platform']
19
+
20
+ scopes = [
21
+ 'https://www.googleapis.com/auth/cloud-platform',
22
+ 'https://www.googleapis.com/auth/userinfo.email'
23
+ ]
24
+
20
25
  authorization = Google::Auth.get_application_default(scopes)
21
26
  authorization.apply({})
22
27
  authorization.access_token
@@ -1,4 +1,4 @@
1
1
  # Kubernetes REST-API Client
2
2
  module Kubeclient
3
- VERSION = '4.4.0'.freeze
3
+ VERSION = '4.9.0'.freeze
4
4
  end
@@ -1,13 +1,13 @@
1
1
  require_relative 'test_helper'
2
2
  require 'open3'
3
3
 
4
- # Unit tests for the ExecCredentials token provider
4
+ # Unit tests for the ExecCredentials provider
5
5
  class ExecCredentialsTest < MiniTest::Test
6
6
  def test_exec_opts_missing
7
7
  expected_msg =
8
8
  'exec options are required'
9
9
  exception = assert_raises(ArgumentError) do
10
- Kubeclient::ExecCredentials.token(nil)
10
+ Kubeclient::ExecCredentials.run(nil)
11
11
  end
12
12
  assert_equal(expected_msg, exception.message)
13
13
  end
@@ -16,7 +16,7 @@ class ExecCredentialsTest < MiniTest::Test
16
16
  expected_msg =
17
17
  'exec command is required'
18
18
  exception = assert_raises(KeyError) do
19
- Kubeclient::ExecCredentials.token({})
19
+ Kubeclient::ExecCredentials.run({})
20
20
  end
21
21
  assert_equal(expected_msg, exception.message)
22
22
  end
@@ -33,34 +33,134 @@ class ExecCredentialsTest < MiniTest::Test
33
33
 
34
34
  Open3.stub(:capture3, [nil, err, st]) do
35
35
  exception = assert_raises(RuntimeError) do
36
- Kubeclient::ExecCredentials.token(opts)
36
+ Kubeclient::ExecCredentials.run(opts)
37
37
  end
38
38
  assert_equal(expected_msg, exception.message)
39
39
  end
40
40
  end
41
41
 
42
- def test_token
42
+ def test_run_with_token_credentials
43
43
  opts = { 'command' => 'dummy' }
44
44
 
45
+ credentials = {
46
+ 'token' => '0123456789ABCDEF0123456789ABCDEF'
47
+ }
48
+
49
+ creds = JSON.dump(
50
+ 'apiVersion' => 'client.authentication.k8s.io/v1alpha1',
51
+ 'status' => credentials
52
+ )
53
+
54
+ st = Minitest::Mock.new
55
+ st.expect(:success?, true)
56
+
57
+ Open3.stub(:capture3, [creds, nil, st]) do
58
+ assert_equal(credentials, Kubeclient::ExecCredentials.run(opts))
59
+ end
60
+ end
61
+
62
+ def test_run_with_client_credentials
63
+ opts = { 'command' => 'dummy' }
64
+
65
+ credentials = {
66
+ 'clientCertificateData' => '0123456789ABCDEF0123456789ABCDEF',
67
+ 'clientKeyData' => '0123456789ABCDEF0123456789ABCDEF'
68
+ }
69
+
70
+ creds = JSON.dump(
71
+ 'apiVersion' => 'client.authentication.k8s.io/v1alpha1',
72
+ 'status' => credentials
73
+ )
74
+
75
+ st = Minitest::Mock.new
76
+ st.expect(:success?, true)
77
+
78
+ Open3.stub(:capture3, [creds, nil, st]) do
79
+ assert_equal(credentials, Kubeclient::ExecCredentials.run(opts))
80
+ end
81
+ end
82
+
83
+ def test_run_with_missing_client_certificate_data
84
+ opts = { 'command' => 'dummy' }
85
+
86
+ credentials = {
87
+ 'clientKeyData' => '0123456789ABCDEF0123456789ABCDEF'
88
+ }
89
+
90
+ creds = JSON.dump(
91
+ 'apiVersion' => 'client.authentication.k8s.io/v1alpha1',
92
+ 'status' => credentials
93
+ )
94
+
95
+ st = Minitest::Mock.new
96
+ st.expect(:success?, true)
97
+
98
+ expected_msg = 'exec plugin didn\'t return client certificate data'
99
+
100
+ Open3.stub(:capture3, [creds, nil, st]) do
101
+ exception = assert_raises(RuntimeError) do
102
+ Kubeclient::ExecCredentials.run(opts)
103
+ end
104
+ assert_equal(expected_msg, exception.message)
105
+ end
106
+ end
107
+
108
+ def test_run_with_missing_client_key_data
109
+ opts = { 'command' => 'dummy' }
110
+
111
+ credentials = {
112
+ 'clientCertificateData' => '0123456789ABCDEF0123456789ABCDEF'
113
+ }
114
+
45
115
  creds = JSON.dump(
46
- 'apiVersion': 'client.authentication.k8s.io/v1alpha1',
47
- 'status': {
48
- 'token': '0123456789ABCDEF0123456789ABCDEF'
49
- }
116
+ 'apiVersion' => 'client.authentication.k8s.io/v1alpha1',
117
+ 'status' => credentials
50
118
  )
51
119
 
52
120
  st = Minitest::Mock.new
53
121
  st.expect(:success?, true)
54
122
 
123
+ expected_msg = 'exec plugin didn\'t return client key data'
124
+
55
125
  Open3.stub(:capture3, [creds, nil, st]) do
56
- assert_equal('0123456789ABCDEF0123456789ABCDEF', Kubeclient::ExecCredentials.token(opts))
126
+ exception = assert_raises(RuntimeError) do
127
+ Kubeclient::ExecCredentials.run(opts)
128
+ end
129
+ assert_equal(expected_msg, exception.message)
130
+ end
131
+ end
132
+
133
+ def test_run_with_client_data_and_token
134
+ opts = { 'command' => 'dummy' }
135
+
136
+ credentials = {
137
+ 'clientCertificateData' => '0123456789ABCDEF0123456789ABCDEF',
138
+ 'clientKeyData' => '0123456789ABCDEF0123456789ABCDEF',
139
+ 'token' => '0123456789ABCDEF0123456789ABCDEF'
140
+ }
141
+
142
+ creds = JSON.dump(
143
+ 'apiVersion' => 'client.authentication.k8s.io/v1alpha1',
144
+ 'status' => credentials
145
+ )
146
+
147
+ st = Minitest::Mock.new
148
+ st.expect(:success?, true)
149
+
150
+ expected_msg = 'exec plugin returned both token and client data'
151
+
152
+ Open3.stub(:capture3, [creds, nil, st]) do
153
+ exception = assert_raises(RuntimeError) do
154
+ Kubeclient::ExecCredentials.run(opts)
155
+ end
156
+ assert_equal(expected_msg, exception.message)
57
157
  end
58
158
  end
59
159
 
60
160
  def test_status_missing
61
161
  opts = { 'command' => 'dummy' }
62
162
 
63
- creds = JSON.dump('apiVersion': 'client.authentication.k8s.io/v1alpha1')
163
+ creds = JSON.dump('apiVersion' => 'client.authentication.k8s.io/v1alpha1')
64
164
 
65
165
  st = Minitest::Mock.new
66
166
  st.expect(:success?, true)
@@ -69,28 +169,28 @@ class ExecCredentialsTest < MiniTest::Test
69
169
 
70
170
  Open3.stub(:capture3, [creds, nil, st]) do
71
171
  exception = assert_raises(RuntimeError) do
72
- Kubeclient::ExecCredentials.token(opts)
172
+ Kubeclient::ExecCredentials.run(opts)
73
173
  end
74
174
  assert_equal(expected_msg, exception.message)
75
175
  end
76
176
  end
77
177
 
78
- def test_token_missing
178
+ def test_credentials_missing
79
179
  opts = { 'command' => 'dummy' }
80
180
 
81
181
  creds = JSON.dump(
82
- 'apiVersion': 'client.authentication.k8s.io/v1alpha1',
83
- 'status': {}
182
+ 'apiVersion' => 'client.authentication.k8s.io/v1alpha1',
183
+ 'status' => {}
84
184
  )
85
185
 
86
186
  st = Minitest::Mock.new
87
187
  st.expect(:success?, true)
88
188
 
89
- expected_msg = 'exec plugin didn\'t return a token'
189
+ expected_msg = 'exec plugin didn\'t return a token or client data'
90
190
 
91
191
  Open3.stub(:capture3, [creds, nil, st]) do
92
192
  exception = assert_raises(RuntimeError) do
93
- Kubeclient::ExecCredentials.token(opts)
193
+ Kubeclient::ExecCredentials.run(opts)
94
194
  end
95
195
  assert_equal(expected_msg, exception.message)
96
196
  end
@@ -106,7 +206,7 @@ class ExecCredentialsTest < MiniTest::Test
106
206
  }
107
207
 
108
208
  creds = JSON.dump(
109
- 'apiVersion': api_version
209
+ 'apiVersion' => api_version
110
210
  )
111
211
 
112
212
  st = Minitest::Mock.new
@@ -117,7 +217,7 @@ class ExecCredentialsTest < MiniTest::Test
117
217
 
118
218
  Open3.stub(:capture3, [creds, nil, st]) do
119
219
  exception = assert_raises(RuntimeError) do
120
- Kubeclient::ExecCredentials.token(opts)
220
+ Kubeclient::ExecCredentials.run(opts)
121
221
  end
122
222
  assert_equal(expected_msg, exception.message)
123
223
  end
@@ -296,6 +296,22 @@ class KubeclientTest < MiniTest::Test
296
296
  )
297
297
  end
298
298
 
299
+ def test_entities_with_resource_version
300
+ version = '329'
301
+
302
+ stub_core_api_list
303
+ stub_get_services
304
+
305
+ services = client.get_services(resource_version: version)
306
+
307
+ assert_instance_of(Kubeclient::Common::EntityList, services)
308
+ assert_requested(
309
+ :get,
310
+ "http://localhost:8080/api/v1/services?resourceVersion=#{version}",
311
+ times: 1
312
+ )
313
+ end
314
+
299
315
  def test_entities_with_field_selector
300
316
  selector = 'involvedObject.name=redis-master'
301
317
 
@@ -69,12 +69,31 @@ class TestPodLog < MiniTest::Test
69
69
  times: 1)
70
70
  end
71
71
 
72
+ def test_get_pod_limit_bytes
73
+ selected_bytes = open_test_file('pod_log.txt').read(10)
74
+
75
+ stub_request(:get, %r{/namespaces/default/pods/[a-z0-9-]+/log})
76
+ .to_return(body: selected_bytes,
77
+ status: 200)
78
+
79
+ client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1')
80
+ retrieved_log = client.get_pod_log('redis-master-pod',
81
+ 'default',
82
+ limit_bytes: 10)
83
+
84
+ assert_equal(selected_bytes, retrieved_log)
85
+
86
+ assert_requested(:get,
87
+ 'http://localhost:8080/api/v1/namespaces/default/pods/redis-master-pod/log?limitBytes=10',
88
+ times: 1)
89
+ end
90
+
72
91
  def test_watch_pod_log
73
- expected_lines = open_test_file('pod_log.txt').read.split("\n")
92
+ file = open_test_file('pod_log.txt')
93
+ expected_lines = file.read.split("\n")
74
94
 
75
95
  stub_request(:get, %r{/namespaces/default/pods/[a-z0-9-]+/log\?.*follow})
76
- .to_return(body: open_test_file('pod_log.txt'),
77
- status: 200)
96
+ .to_return(body: file, status: 200)
78
97
 
79
98
  client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1')
80
99
 
@@ -85,6 +104,21 @@ class TestPodLog < MiniTest::Test
85
104
  end
86
105
  end
87
106
 
107
+ def test_watch_pod_log_with_block
108
+ file = open_test_file('pod_log.txt')
109
+ first = file.readlines.first.chomp
110
+
111
+ stub_request(:get, %r{/namespaces/default/pods/[a-z0-9-]+/log\?.*follow})
112
+ .to_return(body: file, status: 200)
113
+
114
+ client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1')
115
+
116
+ client.watch_pod_log('redis-master-pod', 'default') do |line|
117
+ assert_equal first, line
118
+ break
119
+ end
120
+ end
121
+
88
122
  def test_watch_pod_log_follow_redirect
89
123
  expected_lines = open_test_file('pod_log.txt').read.split("\n")
90
124
  redirect = 'http://localhost:1234/api/namespaces/default/pods/redis-master-pod/log'
@@ -274,6 +274,33 @@ class TestService < MiniTest::Test
274
274
  end
275
275
  end
276
276
 
277
+ def test_apply_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
+ service.metadata.annotations = {}
285
+ service.metadata.annotations['key'] = 'value'
286
+
287
+ stub_core_api_list
288
+ resource_name = "#{name}?fieldManager=myapp&force=true"
289
+ expected_url = "http://localhost:8080/api/v1/namespaces/development/services/#{resource_name}"
290
+ stub_request(:patch, expected_url)
291
+ .to_return(body: open_test_file('service_patch.json'), status: 200)
292
+
293
+ client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1')
294
+ service = client.apply_service(service, field_manager: 'myapp')
295
+ assert_kind_of(RecursiveOpenStruct, service)
296
+
297
+ assert_requested(:patch, expected_url, times: 1) do |req|
298
+ data = JSON.parse(req.body)
299
+ req.headers['Content-Type'] == 'application/apply-patch+yaml' &&
300
+ data['metadata']['annotations']['key'] == 'value'
301
+ end
302
+ end
303
+
277
304
  def test_json_patch_service
278
305
  service = Kubeclient::Resource.new
279
306
  name = 'my-service'
@@ -27,6 +27,19 @@ class TestWatch < MiniTest::Test
27
27
  end
28
28
  end
29
29
 
30
+ def test_watch_pod_block
31
+ stub_core_api_list
32
+ stub_request(:get, %r{/watch/pods})
33
+ .to_return(body: open_test_file('watch_stream.json'),
34
+ status: 200)
35
+
36
+ client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1')
37
+ yielded = []
38
+ client.watch_pods { |notice| yielded << notice.type }
39
+
40
+ assert_equal %w[ADDED MODIFIED DELETED], yielded
41
+ end
42
+
30
43
  def test_watch_pod_raw
31
44
  stub_core_api_list
32
45
 
metadata CHANGED
@@ -1,27 +1,27 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kubeclient
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.4.0
4
+ version: 4.9.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-05-03 00:00:00.000000000 Z
11
+ date: 2020-08-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '1.6'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.6'
27
27
  - !ruby/object:Gem::Dependency
@@ -157,7 +157,7 @@ dependencies:
157
157
  - - "~>"
158
158
  - !ruby/object:Gem::Version
159
159
  version: '1.0'
160
- type: :development
160
+ type: :runtime
161
161
  prerelease: false
162
162
  version_requirements: !ruby/object:Gem::Requirement
163
163
  requirements:
@@ -184,34 +184,40 @@ dependencies:
184
184
  requirements:
185
185
  - - "~>"
186
186
  - !ruby/object:Gem::Version
187
- version: '1.0'
187
+ version: '1.1'
188
188
  - - ">="
189
189
  - !ruby/object:Gem::Version
190
- version: 1.0.4
190
+ version: 1.1.1
191
191
  type: :runtime
192
192
  prerelease: false
193
193
  version_requirements: !ruby/object:Gem::Requirement
194
194
  requirements:
195
195
  - - "~>"
196
196
  - !ruby/object:Gem::Version
197
- version: '1.0'
197
+ version: '1.1'
198
198
  - - ">="
199
199
  - !ruby/object:Gem::Version
200
- version: 1.0.4
200
+ version: 1.1.1
201
201
  - !ruby/object:Gem::Dependency
202
202
  name: http
203
203
  requirement: !ruby/object:Gem::Requirement
204
204
  requirements:
205
- - - "~>"
205
+ - - ">="
206
206
  - !ruby/object:Gem::Version
207
207
  version: '3.0'
208
+ - - "<"
209
+ - !ruby/object:Gem::Version
210
+ version: '5.0'
208
211
  type: :runtime
209
212
  prerelease: false
210
213
  version_requirements: !ruby/object:Gem::Requirement
211
214
  requirements:
212
- - - "~>"
215
+ - - ">="
213
216
  - !ruby/object:Gem::Version
214
217
  version: '3.0'
218
+ - - "<"
219
+ - !ruby/object:Gem::Version
220
+ version: '5.0'
215
221
  description: A client for Kubernetes REST api
216
222
  email:
217
223
  - abonas@redhat.com
@@ -366,8 +372,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
366
372
  - !ruby/object:Gem::Version
367
373
  version: '0'
368
374
  requirements: []
369
- rubyforge_project:
370
- rubygems_version: 2.7.6
375
+ rubygems_version: 3.1.2
371
376
  signing_key:
372
377
  specification_version: 4
373
378
  summary: A client for Kubernetes REST api