kubeclient 4.5.0 → 4.9.1
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 +4 -4
- data/.travis.yml +6 -1
- data/CHANGELOG.md +39 -1
- data/README.md +163 -37
- data/RELEASING.md +3 -1
- data/kubeclient.gemspec +2 -2
- data/lib/kubeclient.rb +1 -1
- data/lib/kubeclient/aws_eks_credentials.rb +1 -1
- data/lib/kubeclient/common.rb +60 -13
- data/lib/kubeclient/config.rb +11 -3
- data/lib/kubeclient/exec_credentials.rb +33 -4
- data/lib/kubeclient/google_application_default_credentials.rb +6 -1
- data/lib/kubeclient/version.rb +1 -1
- data/test/test_common_url_handling.rb +160 -0
- data/test/test_exec_credentials.rb +119 -19
- data/test/test_pod_log.rb +37 -3
- data/test/test_service.rb +27 -0
- data/test/test_watch.rb +13 -0
- metadata +10 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1ea191875dc5c9e99d49152f6615caa50478bc8604a4de9c6fdda0d46da75ea5
|
4
|
+
data.tar.gz: 84214e5b1f2f3116aeb646828e0a5cf6456173493abf1fb384fe6132a723271d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 549974ed0fbec82aa99df19db4829af4da21eec3ab037037c9a6880ab2b7f0bbf9a854ee9e1713a9eedccf6e7fafbc593301a8e67b745e54d88f9da158f6b596
|
7
|
+
data.tar.gz: 2f44720eca3c585e69c562b218f6e17c1ed0fb13dd5e26177fef6fa3ec610a6c0b1f056102ac3d3e3d9cf21801fe13d8560c509423838304cac0a40b66eedae8
|
data/.travis.yml
CHANGED
@@ -11,13 +11,18 @@ rvm:
|
|
11
11
|
- "2.3.0"
|
12
12
|
- "2.4.0"
|
13
13
|
- "2.5.0"
|
14
|
-
- "2.6.
|
14
|
+
- "2.6.0"
|
15
|
+
- "2.7.0"
|
15
16
|
env:
|
16
17
|
- TASK=test
|
17
18
|
matrix:
|
18
19
|
exclude:
|
19
20
|
- os: osx
|
20
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.
|
21
26
|
include:
|
22
27
|
- os: linux
|
23
28
|
rvm: "2.5.0"
|
data/CHANGELOG.md
CHANGED
@@ -4,10 +4,48 @@ 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.
|
7
|
+
## 4.9.1 — 2020-08-31
|
8
|
+
### Fixed
|
9
|
+
- Now should work with apiserver deployed not at root of domain but a sub-path,
|
10
|
+
which is standard with Rancher.
|
11
|
+
Notably, `create_...` methods were sending bad apiVersion and getting 400 error.
|
12
|
+
(#457, hopefully fixes #318, #418 and https://gitlab.com/gitlab-org/gitlab/-/issues/22043)
|
13
|
+
|
14
|
+
## 4.9.0 - 2020-08-03
|
15
|
+
### Added
|
16
|
+
- Support for `user: exec` credential plugins using TLS client auth (#453)
|
17
|
+
|
18
|
+
## 4.8.0 — 2020-07-03
|
8
19
|
|
9
20
|
### Added
|
21
|
+
- Support for server-side apply (#448).
|
10
22
|
|
23
|
+
### Fixed
|
24
|
+
- Declared forgotten dependency on jsonpath, needed for `gcp` provider with `cmd-path` (#450).
|
25
|
+
|
26
|
+
## 4.7.0 — 2020-06-14
|
27
|
+
|
28
|
+
### Fixed
|
29
|
+
- Ruby 2.7 compatibility: bumped minimum recursive-open-struct to one that works on 2.7 (#439).
|
30
|
+
- Ruby 2.7 warnings (#433, #438).
|
31
|
+
- Improved watch documentation, including behavior planned to change in 5.0.0 (#436).
|
32
|
+
|
33
|
+
### Added
|
34
|
+
- Google Application Default Credentials: Added `userinfo.email` to requested scopes, which is necessary for RBAC policies (#441).
|
35
|
+
|
36
|
+
## 4.6.0 — 2019-12-30
|
37
|
+
|
38
|
+
### Fixed
|
39
|
+
- AmazonEksCredentials was sometimes leaving base64 padding that IAM auth of the EKS cluster rejects. Now padding is always stripped. (#424, #423)
|
40
|
+
|
41
|
+
### Added
|
42
|
+
- Allow calling `watch_foos` methods with a block, simpler to use and guarantees closing the connection. (#425)
|
43
|
+
|
44
|
+
- Support `limitBytes` query parameter for `get_pod_log`. (#426)
|
45
|
+
|
46
|
+
## 4.5.0 — 2019-09-27
|
47
|
+
|
48
|
+
### Added
|
11
49
|
- Support `:resourceVersion` parameter in `get_foos` methods (similar to existing support in `watch_foos` methods). (#420)
|
12
50
|
|
13
51
|
- Relax dependency on `http` gem to allow both 3.x and 4.x. (#413)
|
data/README.md
CHANGED
@@ -207,7 +207,7 @@ client = Kubeclient::Client.new(
|
|
207
207
|
|
208
208
|
### Timeouts
|
209
209
|
|
210
|
-
Watching never
|
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
|
-
##
|
423
|
+
## Supported actions & examples:
|
424
424
|
|
425
|
-
|
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
|
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,13 +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
|
-
|
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
|
+
```
|
451
486
|
|
487
|
+
You can ask for entities at a specific version by specifying a parameter named `resource_version`:
|
452
488
|
```ruby
|
453
489
|
pods = client.get_pods(resource_version: '0')
|
454
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.
|
455
494
|
|
456
|
-
|
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
|
457
498
|
|
458
499
|
```ruby
|
459
500
|
continue = nil
|
@@ -508,7 +549,87 @@ Other formats are:
|
|
508
549
|
- `:parsed` for `JSON.parse`
|
509
550
|
- `:parsed_symbolized` for `JSON.parse(..., symbolize_names: true)`
|
510
551
|
|
511
|
-
|
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)
|
512
633
|
|
513
634
|
For example: `delete_pod "pod name"` , `delete_replication_controller "rc name"`, `delete_node "node name"`, `delete_secret "secret name"`
|
514
635
|
|
@@ -532,7 +653,7 @@ delete_options = Kubeclient::Resource.new(
|
|
532
653
|
client.delete_deployment(deployment_name, namespace, delete_options: delete_options)
|
533
654
|
```
|
534
655
|
|
535
|
-
|
656
|
+
### Create an entity
|
536
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`
|
537
658
|
|
538
659
|
Input parameter - object of type `Service`, `Pod`, `ReplicationController`.
|
@@ -558,7 +679,7 @@ service.metadata.labels.role = 'slave'
|
|
558
679
|
client.create_service(service)
|
559
680
|
```
|
560
681
|
|
561
|
-
|
682
|
+
### Update an entity
|
562
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`
|
563
684
|
|
564
685
|
Input parameter - object of type `Pod`, `Service`, `ReplicationController` etc.
|
@@ -569,7 +690,7 @@ The below example is for v1
|
|
569
690
|
updated = client.update_service(service1)
|
570
691
|
```
|
571
692
|
|
572
|
-
|
693
|
+
### Patch an entity (by name)
|
573
694
|
For example: `patch_pod`, `patch_service`, `patch_secret`, `patch_resource_quota`, `patch_persistent_volume`
|
574
695
|
|
575
696
|
Input parameters - name (string) specifying the entity name, patch (hash) to be applied to the resource, optional: namespace name (string)
|
@@ -584,41 +705,41 @@ patched = client.patch_pod("docker-registry", {metadata: {annotations: {key: 'va
|
|
584
705
|
|
585
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.
|
586
707
|
|
587
|
-
|
588
|
-
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.
|
589
|
-
This method is a convenience method instead of calling each entity's get method separately.
|
708
|
+
### Apply an entity
|
590
709
|
|
591
|
-
|
592
|
-
client.all_entities
|
593
|
-
```
|
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
|
594
711
|
|
595
|
-
|
596
|
-
It is possible to receive live update notices watching the relevant entities:
|
712
|
+
For example: `apply_pod`
|
597
713
|
|
598
|
-
|
599
|
-
watcher = client.watch_pods
|
600
|
-
watcher.each do |notice|
|
601
|
-
# process notice data
|
602
|
-
end
|
603
|
-
```
|
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.
|
604
715
|
|
605
|
-
|
716
|
+
Example:
|
606
717
|
|
607
718
|
```ruby
|
608
|
-
|
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')
|
609
730
|
```
|
610
731
|
|
611
|
-
|
612
|
-
|
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.
|
613
737
|
|
614
738
|
```ruby
|
615
|
-
|
616
|
-
watcher.each do |notice|
|
617
|
-
# process notice date
|
618
|
-
end
|
739
|
+
client.all_entities
|
619
740
|
```
|
620
741
|
|
621
|
-
|
742
|
+
### Get a proxy URL
|
622
743
|
You can get a complete URL for connecting a kubernetes entity via the proxy.
|
623
744
|
|
624
745
|
```ruby
|
@@ -633,7 +754,7 @@ client.proxy_url('pod', 'podname', 5001, 'ns')
|
|
633
754
|
# => "https://localhost.localdomain:8443/api/v1/namespaces/ns/pods/podname:5001/proxy"
|
634
755
|
```
|
635
756
|
|
636
|
-
|
757
|
+
### Get the logs of a pod
|
637
758
|
You can get the logs of a running pod, specifying the name of the pod and the
|
638
759
|
namespace where the pod is running:
|
639
760
|
|
@@ -670,16 +791,21 @@ client.get_pod_log('pod-name', 'default', tail_lines: 10)
|
|
670
791
|
# => "..."
|
671
792
|
```
|
672
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
|
+
|
673
800
|
You can also watch the logs of a pod to get a stream of data:
|
674
801
|
|
675
802
|
```ruby
|
676
|
-
|
677
|
-
watcher.each do |line|
|
803
|
+
client.watch_pod_log('pod-name', 'default', container: 'ruby') do |line|
|
678
804
|
puts line
|
679
805
|
end
|
680
806
|
```
|
681
807
|
|
682
|
-
|
808
|
+
### OpenShift: Process a template
|
683
809
|
Returns a processed template containing a list of objects to create.
|
684
810
|
Input parameter - template (hash)
|
685
811
|
Besides its metadata, the template should include a list of objects to be processed and a list of parameters
|
data/RELEASING.md
CHANGED
@@ -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
|
-
|
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.
|
data/kubeclient.gemspec
CHANGED
@@ -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.
|
36
|
+
spec.add_dependency 'recursive-open-struct', '~> 1.1', '>= 1.1.1'
|
37
37
|
spec.add_dependency 'http', '>= 3.0', '< 5.0'
|
38
38
|
end
|
data/lib/kubeclient.rb
CHANGED
@@ -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).
|
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
|
data/lib/kubeclient/common.rb
CHANGED
@@ -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,
|
@@ -194,10 +194,22 @@ module Kubeclient
|
|
194
194
|
def handle_uri(uri, path)
|
195
195
|
raise ArgumentError, 'Missing uri' unless uri
|
196
196
|
@api_endpoint = (uri.is_a?(URI) ? uri : URI.parse(uri))
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
197
|
+
|
198
|
+
# This regex will anchor at the last `/api`, `/oapi` or`/apis/:group`) part of the URL
|
199
|
+
# The whole path will be matched and if existing, the api_group will be extracted.
|
200
|
+
re = /^(?<path>.*\/o?api(?:s\/(?<apigroup>[^\/]+))?)$/mi
|
201
|
+
match = re.match(@api_endpoint.path.chomp('/'))
|
202
|
+
|
203
|
+
if match
|
204
|
+
# Since `re` captures 2 groups, match will always have 3 elements
|
205
|
+
# If thus we have a non-nil value in match 2, this is our api_group.
|
206
|
+
@api_group = match[:apigroup].nil? ? '' : match[:apigroup] + '/'
|
207
|
+
@api_endpoint.path = match[:path]
|
208
|
+
else
|
209
|
+
# This is a fallback, for when `/api` was not provided as part of the uri
|
210
|
+
@api_group = ''
|
211
|
+
@api_endpoint.path = @api_endpoint.path.chomp('/') + path
|
212
|
+
end
|
201
213
|
end
|
202
214
|
|
203
215
|
def build_namespace_prefix(namespace)
|
@@ -213,12 +225,12 @@ module Kubeclient
|
|
213
225
|
end
|
214
226
|
|
215
227
|
# watch all entities of a type e.g. watch_nodes, watch_pods, etc.
|
216
|
-
define_singleton_method("watch_#{entity.method_names[1]}") do |options = {}|
|
228
|
+
define_singleton_method("watch_#{entity.method_names[1]}") do |options = {}, &block|
|
217
229
|
# This method used to take resource_version as a param, so
|
218
230
|
# this conversion is to keep backwards compatibility
|
219
231
|
options = { resource_version: options } unless options.is_a?(Hash)
|
220
232
|
|
221
|
-
watch_entities(entity.resource_name, options)
|
233
|
+
watch_entities(entity.resource_name, options, &block)
|
222
234
|
end
|
223
235
|
|
224
236
|
# get a single entity of a specific type by name
|
@@ -229,7 +241,7 @@ module Kubeclient
|
|
229
241
|
|
230
242
|
define_singleton_method("delete_#{entity.method_names[0]}") \
|
231
243
|
do |name, namespace = nil, opts = {}|
|
232
|
-
delete_entity(entity.resource_name, name, namespace, opts)
|
244
|
+
delete_entity(entity.resource_name, name, namespace, **opts)
|
233
245
|
end
|
234
246
|
|
235
247
|
define_singleton_method("create_#{entity.method_names[0]}") do |entity_config|
|
@@ -254,6 +266,10 @@ module Kubeclient
|
|
254
266
|
do |name, patch, namespace = nil|
|
255
267
|
patch_entity(entity.resource_name, name, patch, 'merge-patch', namespace)
|
256
268
|
end
|
269
|
+
|
270
|
+
define_singleton_method("apply_#{entity.method_names[0]}") do |*args|
|
271
|
+
apply_entity(entity.resource_name, *args)
|
272
|
+
end
|
257
273
|
end
|
258
274
|
end
|
259
275
|
# rubocop:enable Metrics/BlockLength
|
@@ -295,7 +311,9 @@ module Kubeclient
|
|
295
311
|
# :as (:raw|:ros) - defaults to :ros
|
296
312
|
# :raw - return the raw response body as a string
|
297
313
|
# :ros - return a collection of RecursiveOpenStruct objects
|
298
|
-
|
314
|
+
# Accepts an optional block, that will be called with each entity,
|
315
|
+
# otherwise returns a WatchStream
|
316
|
+
def watch_entities(resource_name, options = {}, &block)
|
299
317
|
ns = build_namespace_prefix(options[:namespace])
|
300
318
|
|
301
319
|
path = "watch/#{ns}#{resource_name}"
|
@@ -306,11 +324,13 @@ module Kubeclient
|
|
306
324
|
WATCH_ARGUMENTS.each { |k, v| params[k] = options[v] if options[v] }
|
307
325
|
uri.query = URI.encode_www_form(params) if params.any?
|
308
326
|
|
309
|
-
Kubeclient::Common::WatchStream.new(
|
327
|
+
watcher = Kubeclient::Common::WatchStream.new(
|
310
328
|
uri,
|
311
329
|
http_options(uri),
|
312
330
|
formatter: ->(value) { format_response(options[:as] || @as, value) }
|
313
331
|
)
|
332
|
+
|
333
|
+
return_or_yield_to_watcher(watcher, &block)
|
314
334
|
end
|
315
335
|
|
316
336
|
# Accepts the following options:
|
@@ -407,6 +427,19 @@ module Kubeclient
|
|
407
427
|
format_response(@as, response.body)
|
408
428
|
end
|
409
429
|
|
430
|
+
def apply_entity(resource_name, resource, field_manager:, force: true)
|
431
|
+
name = "#{resource[:metadata][:name]}?fieldManager=#{field_manager}&force=#{force}"
|
432
|
+
ns_prefix = build_namespace_prefix(resource[:metadata][:namespace])
|
433
|
+
response = handle_exception do
|
434
|
+
rest_client[ns_prefix + resource_name + "/#{name}"]
|
435
|
+
.patch(
|
436
|
+
resource.to_json,
|
437
|
+
{ 'Content-Type' => 'application/apply-patch+yaml' }.merge(@headers)
|
438
|
+
)
|
439
|
+
end
|
440
|
+
format_response(@as, response.body)
|
441
|
+
end
|
442
|
+
|
410
443
|
def all_entities(options = {})
|
411
444
|
discover unless @discovered
|
412
445
|
@entities.values.each_with_object({}) do |entity, result_hash|
|
@@ -423,13 +456,14 @@ module Kubeclient
|
|
423
456
|
|
424
457
|
def get_pod_log(pod_name, namespace,
|
425
458
|
container: nil, previous: false,
|
426
|
-
timestamps: false, since_time: nil, tail_lines: nil)
|
459
|
+
timestamps: false, since_time: nil, tail_lines: nil, limit_bytes: nil)
|
427
460
|
params = {}
|
428
461
|
params[:previous] = true if previous
|
429
462
|
params[:container] = container if container
|
430
463
|
params[:timestamps] = timestamps if timestamps
|
431
464
|
params[:sinceTime] = format_datetime(since_time) if since_time
|
432
465
|
params[:tailLines] = tail_lines if tail_lines
|
466
|
+
params[:limitBytes] = limit_bytes if limit_bytes
|
433
467
|
|
434
468
|
ns = build_namespace_prefix(namespace)
|
435
469
|
handle_exception do
|
@@ -438,7 +472,7 @@ module Kubeclient
|
|
438
472
|
end
|
439
473
|
end
|
440
474
|
|
441
|
-
def watch_pod_log(pod_name, namespace, container: nil)
|
475
|
+
def watch_pod_log(pod_name, namespace, container: nil, &block)
|
442
476
|
# Adding the "follow=true" query param tells the Kubernetes API to keep
|
443
477
|
# the connection open and stream updates to the log.
|
444
478
|
params = { follow: true }
|
@@ -450,7 +484,10 @@ module Kubeclient
|
|
450
484
|
uri.path += "/#{@api_version}/#{ns}pods/#{pod_name}/log"
|
451
485
|
uri.query = URI.encode_www_form(params)
|
452
486
|
|
453
|
-
Kubeclient::Common::WatchStream.new(
|
487
|
+
watcher = Kubeclient::Common::WatchStream.new(
|
488
|
+
uri, http_options(uri), formatter: ->(value) { value }
|
489
|
+
)
|
490
|
+
return_or_yield_to_watcher(watcher, &block)
|
454
491
|
end
|
455
492
|
|
456
493
|
def proxy_url(kind, name, port, namespace = '')
|
@@ -587,6 +624,16 @@ module Kubeclient
|
|
587
624
|
raise ArgumentError, msg unless File.readable?(@auth_options[:bearer_token_file])
|
588
625
|
end
|
589
626
|
|
627
|
+
def return_or_yield_to_watcher(watcher, &block)
|
628
|
+
return watcher unless block_given?
|
629
|
+
|
630
|
+
begin
|
631
|
+
watcher.each(&block)
|
632
|
+
ensure
|
633
|
+
watcher.finish
|
634
|
+
end
|
635
|
+
end
|
636
|
+
|
590
637
|
def http_options(uri)
|
591
638
|
options = {
|
592
639
|
basic_auth_user: @auth_options[:username],
|
data/lib/kubeclient/config.rb
CHANGED
@@ -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?('
|
153
|
-
|
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
|
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']
|
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
|
-
|
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
|
-
|
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
|
data/lib/kubeclient/version.rb
CHANGED
@@ -0,0 +1,160 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
# URLHandling tests
|
4
|
+
class TestCommonUrlHandling < MiniTest::Test
|
5
|
+
def test_no_path_in_uri
|
6
|
+
client = Kubeclient::Client.new('http://localhost:8080', 'v1')
|
7
|
+
rest_client = client.rest_client
|
8
|
+
assert_equal('v1', client.instance_variable_get(:@api_version))
|
9
|
+
assert_equal('', client.instance_variable_get(:@api_group))
|
10
|
+
assert_equal('http://localhost:8080/api/v1', rest_client.url.to_s)
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_with_api_path_in_uri
|
14
|
+
client = Kubeclient::Client.new('http://localhost:8080/api', 'v1')
|
15
|
+
rest_client = client.rest_client
|
16
|
+
assert_equal('v1', client.instance_variable_get(:@api_version))
|
17
|
+
assert_equal('', client.instance_variable_get(:@api_group))
|
18
|
+
assert_equal('http://localhost:8080/api/v1', rest_client.url.to_s)
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_with_api_path_in_uri_other_version
|
22
|
+
client = Kubeclient::Client.new('http://localhost:8080/api', 'v2')
|
23
|
+
rest_client = client.rest_client
|
24
|
+
assert_equal('v2', client.instance_variable_get(:@api_version))
|
25
|
+
assert_equal('', client.instance_variable_get(:@api_group))
|
26
|
+
assert_equal('http://localhost:8080/api/v2', rest_client.url.to_s)
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_with_api_group_path_in_uri
|
30
|
+
client = Kubeclient::Client.new('http://localhost:8080/apis/this_is_the_group', 'v1')
|
31
|
+
rest_client = client.rest_client
|
32
|
+
assert_equal('v1', client.instance_variable_get(:@api_version))
|
33
|
+
assert_equal('this_is_the_group/', client.instance_variable_get(:@api_group))
|
34
|
+
assert_equal('http://localhost:8080/apis/this_is_the_group/v1', rest_client.url.to_s)
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_with_api_group_path_in_uri_other_version
|
38
|
+
client = Kubeclient::Client.new('http://localhost:8080/apis/this_is_the_group', 'v2')
|
39
|
+
rest_client = client.rest_client
|
40
|
+
assert_equal('v2', client.instance_variable_get(:@api_version))
|
41
|
+
assert_equal('this_is_the_group/', client.instance_variable_get(:@api_group))
|
42
|
+
assert_equal('http://localhost:8080/apis/this_is_the_group/v2', rest_client.url.to_s)
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_with_api_path_in_uri_trailing_slash
|
46
|
+
client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1')
|
47
|
+
rest_client = client.rest_client
|
48
|
+
assert_equal('v1', client.instance_variable_get(:@api_version))
|
49
|
+
assert_equal('', client.instance_variable_get(:@api_group))
|
50
|
+
assert_equal('http://localhost:8080/api/v1', rest_client.url.to_s)
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_with_api_path_in_api
|
54
|
+
client = Kubeclient::Client.new('http://localhost:8080/api/but/I/want/a/hidden/k8s/api', 'v1')
|
55
|
+
rest_client = client.rest_client
|
56
|
+
assert_equal('v1', client.instance_variable_get(:@api_version))
|
57
|
+
assert_equal('', client.instance_variable_get(:@api_group))
|
58
|
+
assert_equal('http://localhost:8080/api/but/I/want/a/hidden/k8s/api/v1', rest_client.url.to_s)
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_with_api_group_path_in_api
|
62
|
+
client = Kubeclient::Client.new(
|
63
|
+
'http://localhost:8080/api/but/I/want/a/hidden/k8s/apis/this_is_the_group',
|
64
|
+
'v1'
|
65
|
+
)
|
66
|
+
rest_client = client.rest_client
|
67
|
+
assert_equal('v1', client.instance_variable_get(:@api_version))
|
68
|
+
assert_equal('this_is_the_group/', client.instance_variable_get(:@api_group))
|
69
|
+
assert_equal(
|
70
|
+
'http://localhost:8080/api/but/I/want/a/hidden/k8s/apis/this_is_the_group/v1',
|
71
|
+
rest_client.url.to_s
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_rancher_with_api_path_in_uri
|
76
|
+
client = Kubeclient::Client.new('http://localhost:8080/k8s/clusters/c-somerancherID/api', 'v1')
|
77
|
+
rest_client = client.rest_client
|
78
|
+
assert_equal('v1', client.instance_variable_get(:@api_version))
|
79
|
+
assert_equal('', client.instance_variable_get(:@api_group))
|
80
|
+
assert_equal('http://localhost:8080/k8s/clusters/c-somerancherID/api/v1', rest_client.url.to_s)
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_rancher_no_api_path_in_uri
|
84
|
+
client = Kubeclient::Client.new('http://localhost:8080/k8s/clusters/c-somerancherID', 'v1')
|
85
|
+
rest_client = client.rest_client
|
86
|
+
assert_equal('v1', client.instance_variable_get(:@api_version))
|
87
|
+
assert_equal('', client.instance_variable_get(:@api_group))
|
88
|
+
assert_equal('http://localhost:8080/k8s/clusters/c-somerancherID/api/v1', rest_client.url.to_s)
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_rancher_no_api_path_in_uri_trailing_slash
|
92
|
+
client = Kubeclient::Client.new('http://localhost:8080/k8s/clusters/c-somerancherID/', 'v1')
|
93
|
+
rest_client = client.rest_client
|
94
|
+
assert_equal('v1', client.instance_variable_get(:@api_version))
|
95
|
+
assert_equal('', client.instance_variable_get(:@api_group))
|
96
|
+
assert_equal('http://localhost:8080/k8s/clusters/c-somerancherID/api/v1', rest_client.url.to_s)
|
97
|
+
end
|
98
|
+
|
99
|
+
def test_rancher_with_api_path_in_uri_trailing_slash
|
100
|
+
client = Kubeclient::Client.new('http://localhost:8080/k8s/clusters/c-somerancherID/api/', 'v1')
|
101
|
+
rest_client = client.rest_client
|
102
|
+
assert_equal('v1', client.instance_variable_get(:@api_version))
|
103
|
+
assert_equal('', client.instance_variable_get(:@api_group))
|
104
|
+
assert_equal('http://localhost:8080/k8s/clusters/c-somerancherID/api/v1', rest_client.url.to_s)
|
105
|
+
end
|
106
|
+
|
107
|
+
def test_rancher_with_api_group_in_uri_trailing_slash
|
108
|
+
client = Kubeclient::Client.new(
|
109
|
+
'http://localhost:8080/k8s/clusters/c-somerancherID/apis/this_is_the_group',
|
110
|
+
'v1'
|
111
|
+
)
|
112
|
+
rest_client = client.rest_client
|
113
|
+
assert_equal('v1', client.instance_variable_get(:@api_version))
|
114
|
+
assert_equal('this_is_the_group/', client.instance_variable_get(:@api_group))
|
115
|
+
assert_equal(
|
116
|
+
'http://localhost:8080/k8s/clusters/c-somerancherID/apis/this_is_the_group/v1',
|
117
|
+
rest_client.url.to_s
|
118
|
+
)
|
119
|
+
end
|
120
|
+
|
121
|
+
def test_with_openshift_api_path_in_uri
|
122
|
+
client = Kubeclient::Client.new('http://localhost:8080/oapi', 'v1')
|
123
|
+
rest_client = client.rest_client
|
124
|
+
assert_equal('v1', client.instance_variable_get(:@api_version))
|
125
|
+
assert_equal('', client.instance_variable_get(:@api_group))
|
126
|
+
assert_equal('http://localhost:8080/oapi/v1', rest_client.url.to_s)
|
127
|
+
end
|
128
|
+
|
129
|
+
def test_arbitrary_path_with_openshift_api_path_in_uri
|
130
|
+
client = Kubeclient::Client.new('http://localhost:8080/foobarbaz/oapi', 'v1')
|
131
|
+
rest_client = client.rest_client
|
132
|
+
assert_equal('v1', client.instance_variable_get(:@api_version))
|
133
|
+
assert_equal('', client.instance_variable_get(:@api_group))
|
134
|
+
assert_equal('http://localhost:8080/foobarbaz/oapi/v1', rest_client.url.to_s)
|
135
|
+
end
|
136
|
+
|
137
|
+
def test_with_openshift_api_path_in_uri_trailing_slash
|
138
|
+
client = Kubeclient::Client.new('http://localhost:8080/oapi/', 'v1')
|
139
|
+
rest_client = client.rest_client
|
140
|
+
assert_equal('v1', client.instance_variable_get(:@api_version))
|
141
|
+
assert_equal('', client.instance_variable_get(:@api_group))
|
142
|
+
assert_equal('http://localhost:8080/oapi/v1', rest_client.url.to_s)
|
143
|
+
end
|
144
|
+
|
145
|
+
def test_with_arbitrary_path_in_uri
|
146
|
+
client = Kubeclient::Client.new('http://localhost:8080/foobarbaz', 'v1')
|
147
|
+
rest_client = client.rest_client
|
148
|
+
assert_equal('v1', client.instance_variable_get(:@api_version))
|
149
|
+
assert_equal('', client.instance_variable_get(:@api_group))
|
150
|
+
assert_equal('http://localhost:8080/foobarbaz/api/v1', rest_client.url.to_s)
|
151
|
+
end
|
152
|
+
|
153
|
+
def test_with_arbitrary_and_api_path_in_uri
|
154
|
+
client = Kubeclient::Client.new('http://localhost:8080/foobarbaz/api', 'v1')
|
155
|
+
rest_client = client.rest_client
|
156
|
+
assert_equal('v1', client.instance_variable_get(:@api_version))
|
157
|
+
assert_equal('', client.instance_variable_get(:@api_group))
|
158
|
+
assert_equal('http://localhost:8080/foobarbaz/api/v1', rest_client.url.to_s)
|
159
|
+
end
|
160
|
+
end
|
@@ -1,13 +1,13 @@
|
|
1
1
|
require_relative 'test_helper'
|
2
2
|
require 'open3'
|
3
3
|
|
4
|
-
# Unit tests for the ExecCredentials
|
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.
|
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.
|
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.
|
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
|
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'
|
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
|
-
|
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'
|
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.
|
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
|
178
|
+
def test_credentials_missing
|
79
179
|
opts = { 'command' => 'dummy' }
|
80
180
|
|
81
181
|
creds = JSON.dump(
|
82
|
-
'apiVersion'
|
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.
|
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'
|
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.
|
220
|
+
Kubeclient::ExecCredentials.run(opts)
|
121
221
|
end
|
122
222
|
assert_equal(expected_msg, exception.message)
|
123
223
|
end
|
data/test/test_pod_log.rb
CHANGED
@@ -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
|
-
|
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:
|
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'
|
data/test/test_service.rb
CHANGED
@@ -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'
|
data/test/test_watch.rb
CHANGED
@@ -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,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kubeclient
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
4
|
+
version: 4.9.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alissa Bonas
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-08-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -157,7 +157,7 @@ dependencies:
|
|
157
157
|
- - "~>"
|
158
158
|
- !ruby/object:Gem::Version
|
159
159
|
version: '1.0'
|
160
|
-
type: :
|
160
|
+
type: :runtime
|
161
161
|
prerelease: false
|
162
162
|
version_requirements: !ruby/object:Gem::Requirement
|
163
163
|
requirements:
|
@@ -184,20 +184,20 @@ dependencies:
|
|
184
184
|
requirements:
|
185
185
|
- - "~>"
|
186
186
|
- !ruby/object:Gem::Version
|
187
|
-
version: '1.
|
187
|
+
version: '1.1'
|
188
188
|
- - ">="
|
189
189
|
- !ruby/object:Gem::Version
|
190
|
-
version: 1.
|
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.
|
197
|
+
version: '1.1'
|
198
198
|
- - ">="
|
199
199
|
- !ruby/object:Gem::Version
|
200
|
-
version: 1.
|
200
|
+
version: 1.1.1
|
201
201
|
- !ruby/object:Gem::Dependency
|
202
202
|
name: http
|
203
203
|
requirement: !ruby/object:Gem::Requirement
|
@@ -324,6 +324,7 @@ files:
|
|
324
324
|
- test/json/versions_list.json
|
325
325
|
- test/json/watch_stream.json
|
326
326
|
- test/test_common.rb
|
327
|
+
- test/test_common_url_handling.rb
|
327
328
|
- test/test_component_status.rb
|
328
329
|
- test/test_config.rb
|
329
330
|
- test/test_endpoint.rb
|
@@ -372,8 +373,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
372
373
|
- !ruby/object:Gem::Version
|
373
374
|
version: '0'
|
374
375
|
requirements: []
|
375
|
-
|
376
|
-
rubygems_version: 2.7.6.2
|
376
|
+
rubygems_version: 3.1.2
|
377
377
|
signing_key:
|
378
378
|
specification_version: 4
|
379
379
|
summary: A client for Kubernetes REST api
|
@@ -451,6 +451,7 @@ test_files:
|
|
451
451
|
- test/json/versions_list.json
|
452
452
|
- test/json/watch_stream.json
|
453
453
|
- test/test_common.rb
|
454
|
+
- test/test_common_url_handling.rb
|
454
455
|
- test/test_component_status.rb
|
455
456
|
- test/test_config.rb
|
456
457
|
- test/test_endpoint.rb
|