kubeclient 0.3.0 → 4.9.2

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.

Files changed (63) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/actions.yml +35 -0
  3. data/.gitignore +1 -0
  4. data/.rubocop.yml +29 -0
  5. data/CHANGELOG.md +208 -0
  6. data/Gemfile +3 -0
  7. data/README.md +706 -57
  8. data/RELEASING.md +69 -0
  9. data/Rakefile +3 -5
  10. data/kubeclient.gemspec +19 -11
  11. data/lib/kubeclient/aws_eks_credentials.rb +46 -0
  12. data/lib/kubeclient/common.rb +597 -161
  13. data/lib/kubeclient/config.rb +195 -0
  14. data/lib/kubeclient/entity_list.rb +7 -2
  15. data/lib/kubeclient/exec_credentials.rb +89 -0
  16. data/lib/kubeclient/gcp_auth_provider.rb +19 -0
  17. data/lib/kubeclient/gcp_command_credentials.rb +31 -0
  18. data/lib/kubeclient/google_application_default_credentials.rb +31 -0
  19. data/lib/kubeclient/http_error.rb +25 -0
  20. data/lib/kubeclient/missing_kind_compatibility.rb +68 -0
  21. data/lib/kubeclient/oidc_auth_provider.rb +52 -0
  22. data/lib/kubeclient/resource.rb +11 -0
  23. data/lib/kubeclient/resource_not_found_error.rb +4 -0
  24. data/lib/kubeclient/version.rb +1 -1
  25. data/lib/kubeclient/watch_stream.rb +71 -28
  26. data/lib/kubeclient.rb +25 -82
  27. metadata +140 -114
  28. data/.travis.yml +0 -6
  29. data/lib/kubeclient/kube_exception.rb +0 -13
  30. data/lib/kubeclient/watch_notice.rb +0 -7
  31. data/test/json/created_namespace_b3.json +0 -20
  32. data/test/json/created_secret.json +0 -16
  33. data/test/json/created_service_b3.json +0 -31
  34. data/test/json/empty_pod_list_b3.json +0 -9
  35. data/test/json/endpoint_list_b3.json +0 -48
  36. data/test/json/entity_list_b3.json +0 -56
  37. data/test/json/event_list_b3.json +0 -35
  38. data/test/json/namespace_b3.json +0 -13
  39. data/test/json/namespace_exception_b3.json +0 -8
  40. data/test/json/namespace_list_b3.json +0 -32
  41. data/test/json/node_b3.json +0 -29
  42. data/test/json/node_list_b3.json +0 -37
  43. data/test/json/pod_b3.json +0 -92
  44. data/test/json/pod_list_b3.json +0 -75
  45. data/test/json/replication_controller_b3.json +0 -57
  46. data/test/json/replication_controller_list_b3.json +0 -64
  47. data/test/json/secret_list_b3.json +0 -44
  48. data/test/json/service_b3.json +0 -33
  49. data/test/json/service_illegal_json_404.json +0 -1
  50. data/test/json/service_list_b3.json +0 -97
  51. data/test/json/service_update_b3.json +0 -22
  52. data/test/json/versions_list.json +0 -6
  53. data/test/json/watch_stream_b3.json +0 -3
  54. data/test/test_helper.rb +0 -4
  55. data/test/test_kubeclient.rb +0 -407
  56. data/test/test_namespace.rb +0 -53
  57. data/test/test_node.rb +0 -25
  58. data/test/test_pod.rb +0 -21
  59. data/test/test_replication_controller.rb +0 -24
  60. data/test/test_secret.rb +0 -58
  61. data/test/test_service.rb +0 -136
  62. data/test/test_watch.rb +0 -37
  63. data/test/valid_token_file +0 -1
data/README.md CHANGED
@@ -3,11 +3,11 @@
3
3
  [![Gem Version](https://badge.fury.io/rb/kubeclient.svg)](http://badge.fury.io/rb/kubeclient)
4
4
  [![Build Status](https://travis-ci.org/abonas/kubeclient.svg?branch=master)](https://travis-ci.org/abonas/kubeclient)
5
5
  [![Code Climate](http://img.shields.io/codeclimate/github/abonas/kubeclient.svg)](https://codeclimate.com/github/abonas/kubeclient)
6
- [![Dependency Status](https://gemnasium.com/abonas/kubeclient.svg)](https://gemnasium.com/abonas/kubeclient)
7
6
 
8
7
  A Ruby client for Kubernetes REST api.
9
- The client supports GET, POST, PUT, DELETE on nodes, pods, secrets, services, replication controllers, namespaces and endpoints.
10
- The client currently supports Kubernetes REST api version v1beta3.
8
+ The client supports GET, POST, PUT, DELETE on all the entities available in kubernetes in both the core and group apis.
9
+ The client currently supports Kubernetes REST api version v1.
10
+ To learn more about groups and versions in kubernetes refer to [k8s docs](https://kubernetes.io/docs/api/)
11
11
 
12
12
  ## Installation
13
13
 
@@ -19,32 +19,45 @@ gem 'kubeclient'
19
19
 
20
20
  And then execute:
21
21
 
22
- $ bundle
22
+ ```Bash
23
+ bundle
24
+ ```
23
25
 
24
26
  Or install it yourself as:
25
27
 
26
- $ gem install kubeclient
28
+ ```Bash
29
+ gem install kubeclient
30
+ ```
27
31
 
28
32
  ## Usage
29
33
 
30
34
  Initialize the client:
35
+
36
+ ```ruby
37
+ client = Kubeclient::Client.new('http://localhost:8080/api/', "v1")
38
+ ```
39
+
40
+ Or without specifying version (it will be set by default to "v1")
41
+
31
42
  ```ruby
32
- client = Kubeclient::Client.new 'http://localhost:8080/api/' , "v1beta3"
43
+ client = Kubeclient::Client.new('http://localhost:8080/api/')
33
44
  ```
34
45
 
35
- Or without specifying version (it will be set by default to "v1beta3"
46
+ For A Group Api:
36
47
 
37
48
  ```ruby
38
- client = Kubeclient::Client.new 'http://localhost:8080/api/'
49
+ client = Kubeclient::Client.new('http://localhost:8080/apis/batch', 'v1')
39
50
  ```
40
51
 
41
52
  Another option is to initialize the client with URI object:
42
53
 
43
54
  ```ruby
44
55
  uri = URI::HTTP.build(host: "somehostname", port: 8080)
45
- client = Kubeclient::Client.new uri
56
+ client = Kubeclient::Client.new(uri)
46
57
  ```
47
58
 
59
+ ### SSL
60
+
48
61
  It is also possible to use https and configure ssl with:
49
62
 
50
63
  ```ruby
@@ -54,29 +67,48 @@ ssl_options = {
54
67
  ca_file: '/path/to/ca.crt',
55
68
  verify_ssl: OpenSSL::SSL::VERIFY_PEER
56
69
  }
57
- client = Kubeclient::Client.new 'https://localhost:8443/api/' , "v1beta3",
58
- ssl_options: ssl_options
70
+ client = Kubeclient::Client.new(
71
+ 'https://localhost:8443/api/', "v1", ssl_options: ssl_options
72
+ )
73
+ ```
74
+
75
+ As an alternative to the `ca_file` it's possible to use the `cert_store`:
76
+
77
+ ```ruby
78
+ cert_store = OpenSSL::X509::Store.new
79
+ cert_store.add_cert(OpenSSL::X509::Certificate.new(ca_cert_data))
80
+ ssl_options = {
81
+ cert_store: cert_store,
82
+ verify_ssl: OpenSSL::SSL::VERIFY_PEER
83
+ }
84
+ client = Kubeclient::Client.new(
85
+ 'https://localhost:8443/api/', "v1", ssl_options: ssl_options
86
+ )
59
87
  ```
60
88
 
61
89
  For testing and development purpose you can disable the ssl check with:
62
90
 
63
91
  ```ruby
64
92
  ssl_options = { verify_ssl: OpenSSL::SSL::VERIFY_NONE }
65
- client = Kubeclient::Client.new 'https://localhost:8443/api/' , 'v1beta3',
66
- ssl_options: ssl_options
93
+ client = Kubeclient::Client.new(
94
+ 'https://localhost:8443/api/', 'v1', ssl_options: ssl_options
95
+ )
67
96
  ```
68
97
 
98
+ ### Authentication
99
+
69
100
  If you are using basic authentication or bearer tokens as described
70
101
  [here](https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/authentication.md) then you can specify one
71
102
  of the following:
72
103
 
73
104
  ```ruby
74
105
  auth_options = {
75
- user: 'username',
106
+ username: 'username',
76
107
  password: 'password'
77
108
  }
78
- client = Kubeclient::Client.new 'https://localhost:8443/api/' , 'v1beta3',
79
- auth_options: auth_options
109
+ client = Kubeclient::Client.new(
110
+ 'https://localhost:8443/api/', 'v1', auth_options: auth_options
111
+ )
80
112
  ```
81
113
 
82
114
  or
@@ -85,8 +117,9 @@ or
85
117
  auth_options = {
86
118
  bearer_token: 'MDExMWJkMjItOWY1Ny00OGM5LWJlNDEtMjBiMzgxODkxYzYz'
87
119
  }
88
- client = Kubeclient::Client.new 'https://localhost:8443/api/' , 'v1beta3',
89
- auth_options: auth_options
120
+ client = Kubeclient::Client.new(
121
+ 'https://localhost:8443/api/', 'v1', auth_options: auth_options
122
+ )
90
123
  ```
91
124
 
92
125
  or
@@ -95,46 +128,392 @@ or
95
128
  auth_options = {
96
129
  bearer_token_file: '/path/to/token_file'
97
130
  }
98
- client = Kubeclient::Client.new 'https://localhost:8443/api/' , 'v1beta3',
99
- auth_options: auth_options
131
+ client = Kubeclient::Client.new(
132
+ 'https://localhost:8443/api/', 'v1', auth_options: auth_options
133
+ )
100
134
  ```
101
135
 
102
- If you are running your app using kubeclient inside a Kubernetes cluster, then you can have a bearer token file
103
- mounted inside your pod by using a
104
- [Service Account](https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/design/service_accounts.md). This
105
- will mount a bearer token [secret](https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/design/secrets.md)
106
- a/ `/var/run/secrets/kubernetes.io/serviceaccount/token` (see [here](https://github.com/GoogleCloudPlatform/kubernetes/pull/7101)
107
- for more details). For example:
136
+ #### Inside a Kubernetes cluster
137
+
138
+ The [recommended way to locate the API server](https://kubernetes.io/docs/tasks/access-application-cluster/access-cluster/#accessing-the-api-from-a-pod) within the pod is with the `kubernetes.default.svc` DNS name, which resolves to a Service IP which in turn will be routed to an API server.
139
+
140
+ The recommended way to authenticate to the API server is with a [service account](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/). kube-system associates a pod with a service account and a bearer token for that service account is placed into the filesystem tree of each container in that pod at `/var/run/secrets/kubernetes.io/serviceaccount/token`.
141
+
142
+ If available, a certificate bundle is placed into the filesystem tree of each container at `/var/run/secrets/kubernetes.io/serviceaccount/ca.crt`, and should be used to verify the serving certificate of the API server.
143
+
144
+ For example:
108
145
 
109
146
  ```ruby
110
147
  auth_options = {
111
148
  bearer_token_file: '/var/run/secrets/kubernetes.io/serviceaccount/token'
149
+ }
150
+ ssl_options = {}
151
+ if File.exist?("/var/run/secrets/kubernetes.io/serviceaccount/ca.crt")
152
+ ssl_options[:ca_file] = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
153
+ end
154
+ client = Kubeclient::Client.new(
155
+ 'https://kubernetes.default.svc',
156
+ 'v1',
157
+ auth_options: auth_options,
158
+ ssl_options: ssl_options
159
+ )
160
+ ```
161
+
162
+ Finally, the default namespace to be used for namespaced API operations is placed in a file at `/var/run/secrets/kubernetes.io/serviceaccount/namespace` in each container. It is recommended that you use this namespace when issuing API commands below.
163
+
164
+ ```ruby
165
+ namespace = File.read('/var/run/secrets/kubernetes.io/serviceaccount/namespace')
166
+ ```
167
+ You can find information about tokens in [this guide](https://kubernetes.io/docs/tasks/access-application-cluster/access-cluster/#accessing-the-api-from-a-pod) and in [this reference](http://kubernetes.io/docs/admin/authentication/).
168
+
169
+ ### Non-blocking IO
112
170
 
171
+ You can also use kubeclient with non-blocking sockets such as Celluloid::IO, see [here](https://github.com/httprb/http/wiki/Parallel-requests-with-Celluloid%3A%3AIO)
172
+ for details. For example:
173
+
174
+ ```ruby
175
+ require 'celluloid/io'
176
+ socket_options = {
177
+ socket_class: Celluloid::IO::TCPSocket,
178
+ ssl_socket_class: Celluloid::IO::SSLSocket
113
179
  }
114
- client = Kubeclient::Client.new 'https://localhost:8443/api/' , 'v1beta3',
115
- auth_options: auth_options
180
+ client = Kubeclient::Client.new(
181
+ 'https://localhost:8443/api/', 'v1', socket_options: socket_options
182
+ )
116
183
  ```
117
184
 
118
- ## Examples:
185
+ This affects only `.watch_*` sockets, not one-off actions like `.get_*`, `.delete_*` etc.
119
186
 
120
- #### Get all instances of a specific entity type
121
- Such as: `get_pods`, `get_secrets`, `get_services`, `get_nodes`, `get_replication_controllers`
187
+ ### Proxies
188
+
189
+ You can also use kubeclient with an http proxy server such as tinyproxy. It can be entered as a string or a URI object.
190
+ For example:
191
+ ```ruby
192
+ proxy_uri = URI::HTTP.build(host: "myproxyhost", port: 8443)
193
+ client = Kubeclient::Client.new(
194
+ 'https://localhost:8443/api/', http_proxy_uri: proxy_uri
195
+ )
196
+ ```
197
+
198
+ ### Redirects
199
+
200
+ You can optionally not allow redirection with kubeclient. For example:
201
+
202
+ ```ruby
203
+ client = Kubeclient::Client.new(
204
+ 'https://localhost:8443/api/', http_max_redirects: 0
205
+ )
206
+ ```
207
+
208
+ ### Timeouts
209
+
210
+ Watching configures the socket to never time out (however, sooner or later all watches terminate).
211
+
212
+ One-off actions like `.get_*`, `.delete_*` have a configurable timeout:
213
+ ```ruby
214
+ timeouts = {
215
+ open: 10, # unit is seconds
216
+ read: nil # nil means never time out
217
+ }
218
+ client = Kubeclient::Client.new(
219
+ 'https://localhost:8443/api/', timeouts: timeouts
220
+ )
221
+ ```
222
+
223
+ Default timeouts match `Net::HTTP` and `RestClient`, which unfortunately depends on ruby version:
224
+ - open was infinite up to ruby 2.2, 60 seconds in 2.3+.
225
+ - read is 60 seconds.
226
+
227
+ If you want ruby-independent behavior, always specify `:open`.
228
+
229
+ ### Discovery
230
+
231
+ Discovery from the kube-apiserver is done lazily on method calls so it would not change behavior.
232
+
233
+ It can also be done explicitly:
234
+
235
+ ```ruby
236
+ client = Kubeclient::Client.new('http://localhost:8080/api', 'v1')
237
+ client.discover
238
+ ```
239
+
240
+ It is possible to check the status of discovery
241
+
242
+ ```ruby
243
+ unless client.discovered
244
+ client.discover
245
+ end
246
+ ```
247
+
248
+ ### Kubeclient::Config
249
+
250
+ If you've been using `kubectl` and have a `.kube/config` file (possibly referencing other files in fields such as `client-certificate`), you can auto-populate a config object using `Kubeclient::Config`:
251
+
252
+ ```ruby
253
+ # assuming $KUBECONFIG is one file, won't merge multiple like kubectl
254
+ config = Kubeclient::Config.read(ENV['KUBECONFIG'] || '/path/to/.kube/config')
255
+ ```
256
+
257
+ This will lookup external files; relative paths will be resolved relative to the file's directory, if config refers to them with relative path.
258
+ This includes external [`exec:` credential plugins][exec] to be executed.
259
+
260
+ [exec]: https://kubernetes.io/docs/reference/access-authn-authz/authentication/#client-go-credential-plugins
261
+
262
+ You can also construct `Config` directly from nested data. For example if you have JSON or YAML config data in a variable:
263
+
264
+ ```ruby
265
+ config = Kubeclient::Config.new(YAML.safe_load(yaml_text), nil)
266
+ # or
267
+ config = Kubeclient::Config.new(JSON.parse(json_text), nil)
268
+ ```
269
+
270
+ The 2nd argument is a base directory for finding external files, if config refers to them with relative path.
271
+ Setting it to `nil` disables file lookups, and `exec:` execution - such configs will raise an exception. (A config can be self-contained by using inline fields such as `client-certificate-data`.)
272
+
273
+ To create a client based on a Config object:
274
+
275
+ ```ruby
276
+ # default context according to `current-context` field:
277
+ context = config.context
278
+ # or to use a specific context, by name:
279
+ context = config.context('default/192-168-99-100:8443/system:admin')
280
+
281
+ Kubeclient::Client.new(
282
+ context.api_endpoint,
283
+ 'v1',
284
+ ssl_options: context.ssl_options,
285
+ auth_options: context.auth_options
286
+ )
287
+ ```
288
+
289
+
290
+ #### Amazon EKS Credentials
291
+
292
+ On Amazon EKS by default the authentication method is IAM. When running kubectl a temporary token is generated by shelling out to
293
+ the aws-iam-authenticator binary which is sent to authenticate the user.
294
+ See [aws-iam-authenticator](https://github.com/kubernetes-sigs/aws-iam-authenticator).
295
+ To replicate that functionality, the `Kubeclient::AmazonEksCredentials` class can accept a set of IAM credentials and
296
+ contains a helper method to generate the authentication token for you.
297
+
298
+ This requires a set of gems which are _not_ included in
299
+ `kubeclient` dependencies (`aws-sigv4`) so you should add them to your bundle.
300
+ You will also require either the `aws-sdk` v2 or `aws-sdk-core` v3 gems to generate the required `Aws:Credentials` object to pass to this method.
301
+
302
+ To obtain a token:
303
+
304
+ ```ruby
305
+ require 'aws-sdk-core'
306
+ # Use keys
307
+ credentials = Aws::Credentials.new(access_key, secret_key)
308
+ # Or a profile
309
+ credentials = Aws::SharedCredentials.new(profile_name: 'default').credentials
310
+
311
+ auth_options = {
312
+ bearer_token: Kubeclient::AmazonEksCredentials.token(credentials, eks_cluster_name)
313
+ }
314
+ client = Kubeclient::Client.new(
315
+ eks_cluster_https_endpoint, 'v1', auth_options: auth_options
316
+ )
317
+ ```
318
+
319
+ Note that this returns a token good for one minute. If your code requires authorization for longer than that, you should plan to
320
+ acquire a new one, see [How to manually renew](#how-to-manually-renew-expired-credentials) section.
321
+
322
+ #### Google GCP credential plugin
323
+
324
+ If kubeconfig file has `user: {auth-provider: {name: gcp, cmd-path: ..., cmd-args: ..., token-key: ...}}`, the command will be executed to obtain a token.
325
+ (Normally this would be a `gcloud config config-helper` command.)
326
+
327
+ Note that this returns an expiring token. If your code requires authorization for a long time, you should plan to acquire a new one, see [How to manually renew](#how-to-manually-renew-expired-credentials) section.
328
+
329
+ #### Google's Application Default Credentials
330
+
331
+ On Google Compute Engine, Google App Engine, or Google Cloud Functions, as well as `gcloud`-configured systems
332
+ with [application default credentials](https://developers.google.com/identity/protocols/application-default-credentials),
333
+ kubeclient can use `googleauth` gem to authorize.
334
+
335
+ This requires the [`googleauth` gem](https://github.com/google/google-auth-library-ruby) that is _not_ included in
336
+ `kubeclient` dependencies so you should add it to your bundle.
337
+
338
+ If you use `Config.context(...).auth_options` and the kubeconfig file has `user: {auth-provider: {name: gcp}}`, but does not contain `cmd-path` key, kubeclient will automatically try this (raising LoadError if you don't have `googleauth` in your bundle).
339
+
340
+ Or you can obtain a token manually:
341
+
342
+ ```ruby
343
+ require 'googleauth'
344
+
345
+ auth_options = {
346
+ bearer_token: Kubeclient::GoogleApplicationDefaultCredentials.token
347
+ }
348
+ client = Kubeclient::Client.new(
349
+ 'https://localhost:8443/api/', 'v1', auth_options: auth_options
350
+ )
351
+ ```
352
+
353
+ Note that this returns a token good for one hour. If your code requires authorization for longer than that, you should plan to
354
+ acquire a new one, see [How to manually renew](#how-to-manually-renew-expired-credentials) section.
355
+
356
+ #### OIDC Auth Provider
357
+
358
+ If the cluster you are using has OIDC authentication enabled you can use the `openid_connect` gem to obtain
359
+ id-tokens if the one in your kubeconfig has expired.
360
+
361
+ This requires the [`openid_connect` gem](https://github.com/nov/openid_connect) which is not included in
362
+ the `kubeclient` dependencies so should be added to your own applications bundle.
363
+
364
+ The OIDC Auth Provider will not perform the initial setup of your `$KUBECONFIG` file. You will need to use something
365
+ like [`dexter`](https://github.com/gini/dexter) in order to configure the auth-provider in your `$KUBECONFIG` file.
366
+
367
+ If you use `Config.context(...).auth_options` and the `$KUBECONFIG` file has user: `{auth-provider: {name: oidc}}`,
368
+ kubeclient will automatically obtain a token (or use `id-token` if still valid)
369
+
370
+ Tokens are typically short-lived (e.g. 1 hour) and the expiration time is determined by the OIDC Provider (e.g. Google).
371
+ If your code requires authentication for longer than that you should obtain a new token periodically, see [How to manually renew](#how-to-manually-renew-expired-credentials) section.
372
+
373
+ Note: id-tokens retrieved via this provider are not written back to the `$KUBECONFIG` file as they would be when
374
+ using `kubectl`.
375
+
376
+ #### How to manually renew expired credentials
377
+
378
+ Kubeclient [does not yet](https://github.com/abonas/kubeclient/issues/393) help with this.
379
+
380
+ The division of labor between `Config` and `Context` objects may change, for now please make no assumptions at which stage `exec:` and `auth-provider:` are handled and whether they're cached.
381
+ The currently guaranteed way to renew is create a new `Config` object.
382
+
383
+ The more painful part is that you'll then need to create new `Client` object(s) with the credentials from new config.
384
+ So repeat all of this:
385
+ ```ruby
386
+ config = Kubeclient::Config.read(ENV['KUBECONFIG'] || '/path/to/.kube/config')
387
+ context = config.context
388
+ ssl_options = context.ssl_options
389
+ auth_options = context.auth_options
390
+
391
+ client = Kubeclient::Client.new(
392
+ context.api_endpoint, 'v1',
393
+ ssl_options: ssl_options, auth_options: auth_options
394
+ )
395
+ # and additional Clients if needed...
396
+ ```
397
+
398
+ #### Security: Don't use config from untrusted sources
399
+
400
+ `Config.read` is catastrophically unsafe — it will execute arbitrary command lines specified by the config!
401
+
402
+ `Config.new(data, nil)` is better but Kubeclient was never reviewed for behaving safely with malicious / malformed config.
403
+ It might crash / misbehave in unexpected ways...
404
+
405
+ #### namespace
406
+
407
+ Additionally, the `config.context` object will contain a `namespace` attribute, if it was defined in the file.
408
+ It is recommended that you use this namespace when issuing API commands below.
409
+ This is the same behavior that is implemented by `kubectl` command.
410
+
411
+ You can read it as follows:
412
+
413
+ ```ruby
414
+ puts config.context.namespace
415
+ ```
416
+
417
+ ### Supported kubernetes versions
418
+
419
+ We try to support the last 3 minor versions, matching the [official support policy for Kubernetes](https://github.com/kubernetes/community/blob/master/contributors/design-proposals/release/versioning.md#supported-releases-and-component-skew).
420
+ Kubernetes 1.2 and below have known issues and are unsupported.
421
+ Kubernetes 1.3 presumed to still work although nobody is really testing on such old versions...
422
+
423
+ ## Supported actions & examples:
424
+
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
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`
122
459
 
123
460
  ```ruby
124
461
  pods = client.get_pods
125
462
  ```
126
463
 
464
+ Get all entities of a specific type in a namespace:
465
+
466
+ ```ruby
467
+ services = client.get_services(namespace: 'development')
468
+ ```
469
+
127
470
  You can get entities which have specific labels by specifying a parameter named `label_selector` (named `labelSelector` in Kubernetes server):
471
+
128
472
  ```ruby
129
473
  pods = client.get_pods(label_selector: 'name=redis-master')
130
474
  ```
475
+
131
476
  You can specify multiple labels (that option will return entities which have both labels:
477
+
132
478
  ```ruby
133
479
  pods = client.get_pods(label_selector: 'name=redis-master,app=redis')
134
480
  ```
135
481
 
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
498
+
499
+ ```ruby
500
+ continue = nil
501
+ loop do
502
+ entities = client.get_pods(limit: 1_000, continue: continue)
503
+ continue = entities.continue
504
+
505
+ break if entities.last?
506
+ end
507
+ ```
508
+
509
+ See https://kubernetes.io/docs/reference/using-api/api-concepts/#retrieving-large-results-sets-in-chunks for more information.
510
+
511
+ The continue tokens expire after a short amount of time, so similar to a watch if you don't request a subsequent page within aprox. 5 minutes of the previous page being returned the server will return a `410 Gone` error and the client must request the list from the start (i.e. omit the continue token for the next call).
512
+
513
+ Support for chunking was added in v1.9 so previous versions will ignore the option and return the full collection.
514
+
136
515
  #### Get a specific instance of an entity (by name)
137
- Such as: `get_service "service name"` , `get_pod "pod name"` , `get_replication_controller "rc name"`, `get_secret "secret name"`
516
+ Such as: `get_service "service name"` , `get_pod "pod name"` , `get_replication_controller "rc name"`, `get_secret "secret name"`, `get_resource_quota "resource quota name"`, `get_limit_range "limit range name"` , `get_persistent_volume "persistent volume name"` , `get_persistent_volume_claim "persistent volume claim name"`, `get_component_status "component name"`, `get_service_account "service account name"`
138
517
 
139
518
  The GET request should include the namespace name, except for nodes and namespaces entities.
140
519
 
@@ -149,69 +528,339 @@ service = client.get_service "guestbook", 'development'
149
528
  Note - Kubernetes doesn't work with the uid, but rather with the 'name' property.
150
529
  Querying with uid causes 404.
151
530
 
152
- #### Delete an entity (by name)
531
+ #### Getting raw responses
532
+
533
+ To avoid overhead from parsing and building `RecursiveOpenStruct` objects for each reply, pass the `as: :raw` option when initializing `Kubeclient::Client` or when calling `get_` / `watch_` methods.
534
+ The result can then be printed, or searched with a regex, or parsed via `JSON.parse(r)`.
535
+
536
+ ```ruby
537
+ client = Kubeclient::Client.new(as: :raw)
538
+ ```
539
+
540
+ or
541
+
542
+ ```ruby
543
+ pods = client.get_pods as: :raw
544
+ node = client.get_node "127.0.0.1", as: :raw
545
+ ```
546
+
547
+ Other formats are:
548
+ - `:ros` (default) for `RecursiveOpenStruct`
549
+ - `:parsed` for `JSON.parse`
550
+ - `:parsed_symbolized` for `JSON.parse(..., symbolize_names: true)`
551
+
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)
153
633
 
154
634
  For example: `delete_pod "pod name"` , `delete_replication_controller "rc name"`, `delete_node "node name"`, `delete_secret "secret name"`
155
635
 
156
636
  Input parameter - name (string) specifying service name, pod name, replication controller name.
637
+
157
638
  ```ruby
158
- client.delete_service "redis-service"
639
+ deleted = client.delete_service("redis-service")
159
640
  ```
160
641
 
161
- #### Create an entity
162
- For example: `create_pod pod_object`, `create_replication_controller rc_obj`, `create_secret secret_object`
642
+ If you want to cascade delete, for example a deployment, you can use the `delete_options` parameter.
643
+
644
+ ```ruby
645
+ deployment_name = 'redis-deployment'
646
+ namespace = 'default'
647
+ delete_options = Kubeclient::Resource.new(
648
+ apiVersion: 'meta/v1',
649
+ gracePeriodSeconds: 0,
650
+ kind: 'DeleteOptions',
651
+ propagationPolicy: 'Foreground' # Orphan, Foreground, or Background
652
+ )
653
+ client.delete_deployment(deployment_name, namespace, delete_options: delete_options)
654
+ ```
655
+
656
+ ### Create an entity
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`
163
658
 
164
659
  Input parameter - object of type `Service`, `Pod`, `ReplicationController`.
165
660
 
166
- The below example is for v1beta3
661
+ The below example is for v1
167
662
 
168
663
  ```ruby
169
- service = Service.new
664
+ service = Kubeclient::Resource.new
665
+ service.metadata = {}
170
666
  service.metadata.name = "redis-master"
171
- service.spec.port = 6379
172
- service.spec.containerPort = "redis-server"
667
+ service.metadata.namespace = 'staging'
668
+ service.spec = {}
669
+ service.spec.ports = [{
670
+ 'port' => 6379,
671
+ 'targetPort' => 'redis-server'
672
+ }]
173
673
  service.spec.selector = {}
174
674
  service.spec.selector.name = "redis"
175
675
  service.spec.selector.role = "master"
176
- client.create_service service`
676
+ service.metadata.labels = {}
677
+ service.metadata.labels.app = 'redis'
678
+ service.metadata.labels.role = 'slave'
679
+ client.create_service(service)
680
+ ```
681
+
682
+ ### Update an entity
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`
684
+
685
+ Input parameter - object of type `Pod`, `Service`, `ReplicationController` etc.
686
+
687
+ The below example is for v1
688
+
689
+ ```ruby
690
+ updated = client.update_service(service1)
177
691
  ```
178
692
 
179
- #### Update an entity
180
- For example: `update_pod`, `update_service`, `update_replication_controller`, `update_secret`
693
+ ### Patch an entity (by name)
694
+ For example: `patch_pod`, `patch_service`, `patch_secret`, `patch_resource_quota`, `patch_persistent_volume`
181
695
 
182
- Input parameter - object of type `Service`, `Pod`, `ReplicationController`
696
+ Input parameters - name (string) specifying the entity name, patch (hash) to be applied to the resource, optional: namespace name (string)
183
697
 
184
- The below example is for v1beta3
698
+ The PATCH request should include the namespace name, except for nodes and namespaces entities.
699
+
700
+ The below example is for v1
185
701
 
186
702
  ```ruby
187
- client.update_service service1
703
+ patched = client.patch_pod("docker-registry", {metadata: {annotations: {key: 'value'}}}, "default")
188
704
  ```
189
705
 
190
- #### Get all entities of all types : all_entities
191
- Returns a hash with 8 keys (node, secret, service, pod, replication_controller, namespace, endpoint and event). Each key points to an EntityList of same type.
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.
707
+
708
+ ### Apply an entity
709
+
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
711
+
712
+ For example: `apply_pod`
713
+
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.
192
715
 
193
- This method is a convenience method instead of calling each entity's get method separately.
716
+ Example:
717
+
718
+ ```ruby
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')
730
+ ```
731
+
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.
194
737
 
195
738
  ```ruby
196
739
  client.all_entities
197
740
  ```
198
741
 
199
- #### Receive entity updates
200
- It is possible to receive live update notices watching the relevant entities:
742
+ ### Get a proxy URL
743
+ You can get a complete URL for connecting a kubernetes entity via the proxy.
201
744
 
202
745
  ```ruby
203
- watcher = client.watch_pods
204
- watcher.each do |notice|
205
- # process notice data
746
+ client.proxy_url('service', 'srvname', 'srvportname', 'ns')
747
+ # => "https://localhost.localdomain:8443/api/v1/proxy/namespaces/ns/services/srvname:srvportname"
748
+ ```
749
+
750
+ Note the third parameter, port, is a port name for services and an integer for pods:
751
+
752
+ ```ruby
753
+ client.proxy_url('pod', 'podname', 5001, 'ns')
754
+ # => "https://localhost.localdomain:8443/api/v1/namespaces/ns/pods/podname:5001/proxy"
755
+ ```
756
+
757
+ ### Get the logs of a pod
758
+ You can get the logs of a running pod, specifying the name of the pod and the
759
+ namespace where the pod is running:
760
+
761
+ ```ruby
762
+ client.get_pod_log('pod-name', 'default')
763
+ # => "Running...\nRunning...\nRunning...\n"
764
+ ```
765
+
766
+ If that pod has more than one container, you must specify the container:
767
+
768
+ ```ruby
769
+ client.get_pod_log('pod-name', 'default', container: 'ruby')
770
+ # => "..."
771
+ ```
772
+
773
+ If a container in a pod terminates, a new container is started, and you want to
774
+ retrieve the logs of the dead container, you can pass in the `:previous` option:
775
+
776
+ ```ruby
777
+ client.get_pod_log('pod-name', 'default', previous: true)
778
+ # => "..."
779
+ ```
780
+
781
+ Kubernetes can add timestamps to every log line or filter by lines time:
782
+ ```ruby
783
+ client.get_pod_log('pod-name', 'default', timestamps: true, since_time: '2018-04-27T18:30:17.480321984Z')
784
+ # => "..."
785
+ ```
786
+ `since_time` can be a a `Time`, `DateTime` or `String` formatted according to RFC3339
787
+
788
+ Kubernetes can fetch a specific number of lines from the end of the logs:
789
+ ```ruby
790
+ client.get_pod_log('pod-name', 'default', tail_lines: 10)
791
+ # => "..."
792
+ ```
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
+
800
+ You can also watch the logs of a pod to get a stream of data:
801
+
802
+ ```ruby
803
+ client.watch_pod_log('pod-name', 'default', container: 'ruby') do |line|
804
+ puts line
206
805
  end
207
806
  ```
208
807
 
209
- It is possible to interrupt the watcher from another thread with:
808
+ ### OpenShift: Process a template
809
+ Returns a processed template containing a list of objects to create.
810
+ Input parameter - template (hash)
811
+ Besides its metadata, the template should include a list of objects to be processed and a list of parameters
812
+ to be substituted. Note that for a required parameter that does not provide a generated value, you must supply a value.
813
+
814
+ ##### Note: This functionality is not supported by K8s at this moment. See the following [issue](https://github.com/kubernetes/kubernetes/issues/23896)
210
815
 
211
816
  ```ruby
212
- watcher.finish
817
+ client.process_template template
213
818
  ```
214
819
 
820
+ ## Upgrading
821
+
822
+ Kubeclient release versioning follows [SemVer](https://semver.org/).
823
+ See [CHANGELOG.md](CHANGELOG.md) for full changelog.
824
+
825
+ #### past version 4.0
826
+
827
+ Old kubernetes versions < 1.3 no longer supported.
828
+
829
+ #### past version 3.0
830
+
831
+ Ruby versions < 2.2 are no longer supported
832
+
833
+ Specific entity classes mentioned in [past version 1.2.0](#past_version_1.2.0) have been dropped.
834
+ Return values and expected classes are always Kubeclient::Resource.
835
+ Checking the type of a resource can be done using:
836
+ ```
837
+ > pod.kind
838
+ => "Pod"
839
+ ```
840
+
841
+ update_* delete_* and patch_* now return a RecursiveOpenStruct like the get_* methods
842
+
843
+ The `Kubeclient::Client` class raises `Kubeclient::HttpError` or subclasses now. Catching `KubeException` still works but is deprecated.
844
+
845
+ `Kubeclient::Config#context` raises `KeyError` instead of `RuntimeError` for non-existent context name.
846
+
847
+ <a name="past_version_1.2.0">
848
+
849
+ #### past version 1.2.0
850
+ Replace Specific Entity class references:
851
+
852
+ ```ruby
853
+ Kubeclient::Service
854
+ ```
855
+
856
+ with the generic
857
+
858
+ ```ruby
859
+ Kubeclient::Resource.new
860
+ ```
861
+
862
+ Where ever possible.
863
+
215
864
  ## Contributing
216
865
 
217
866
  1. Fork it ( https://github.com/[my-github-username]/kubeclient/fork )
@@ -224,7 +873,7 @@ watcher.finish
224
873
 
225
874
  ## Tests
226
875
 
227
- This client is tested with Minitest.
876
+ This client is tested with Minitest and also uses VCR recordings in some tests.
228
877
  Please run all tests before submitting a Pull Request, and add new tests for new functionality.
229
878
 
230
879
  Running tests: