k8s-client 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e41fdd25707bd063607b40f12a7f5ce09030fea59d54c35934c9cae0392df1e5
4
- data.tar.gz: 6fe3f509d0a9f0e47a1c42498e1aa329d18205c5d5b09d6dde15716a3e3d72f4
3
+ metadata.gz: 780d34c7890954d2e54e0b158f94a8d90e9b5bdaaab11e65cf2db86e695f640a
4
+ data.tar.gz: 40f4843ff19e0b403f906e2f17f9ff7859c5f47413f0354e7ae1758fc5d24377
5
5
  SHA512:
6
- metadata.gz: dc80198faad81e54d58e2f5c70db0baf05c5096a236b937b2d10883d91dbe3447674f64e64cf7ef7f92bb0dd14de1cf2be1407cd8c62a2cbfdd9598968a68eef
7
- data.tar.gz: a4da4e0d39328b33d44c672851f4dbcf7fb970a1907c1fa7150b75a035f6a2ff40b651c3cff8365d4dc495d2ad5c2c49e52d6b7240c6b0820ca5a6e002a582ed
6
+ metadata.gz: 8a5014cf50bd530964fd31c686587288c76bd86aff746bdd5c09b73ec265a82baf4e46d10d297150d48e8984f7d7bc3f0eee25c33511611b7d71e73397675cc0
7
+ data.tar.gz: fda08120279153fd294f8b7a820acdd052f61d6a96d589733800e90ef3673863e3fe5ef600ae5b46f421c4a32b2994681840da356424c0da5aea650faf96dedf
data/README.md CHANGED
@@ -1,8 +1,18 @@
1
1
  # K8s::Client
2
2
 
3
3
  [![Build Status](https://travis-ci.com/kontena/k8s-client.svg?branch=master)](https://travis-ci.com/kontena/k8s-client)
4
+ [![Gem Version](https://badge.fury.io/rb/k8s-client.svg)](https://badge.fury.io/rb/k8s-client)
5
+ [![Yard Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/github/kontena/k8s-client/master)
4
6
 
5
- Ruby client library for the Kubernetes (1.10) API
7
+
8
+ Ruby client library for the Kubernetes (1.11) API
9
+
10
+ ## Highlights
11
+
12
+ * Clean API for dynamic Kubernetes API Groups / Resources
13
+ * Fast API requests using HTTP connection keepalive
14
+ * Fast API discovery and resource listings using pipelined HTTP requests
15
+ * Typed errors with useful debugging information
6
16
 
7
17
  ## Installation
8
18
 
@@ -20,9 +30,156 @@ Or install it yourself as:
20
30
 
21
31
  $ gem install k8s-client
22
32
 
33
+ And then load the code using:
34
+
35
+ ```ruby
36
+ require 'k8s-client'
37
+ ```
38
+
23
39
  ## Usage
24
40
 
25
- WIP: See [`bin/k8s-client`](bin/k8s-client) for example code.
41
+ ### Overview
42
+ The top-level `K8s::Client` provides access to separate `APIClient` instances for each Kubernetes API Group (`v1`, `apps/v1`, etc.), which in turns provides access to separate `ResourceClient` instances for each API resource type (`nodes`, `pods`, `deployments`, etc.).
43
+
44
+ Individual resources are returned as `K8s::Resource` instances, which are `RecursiveOpenStruct` instances providing attribute access (`resource.metadata.name`). The resource instances are returned by methods such as `client.api('v1').resource('nodes').get('foo')`, and passed as arguments for `client.api('v1').resource('nodes').create_resource(res)`. Resources can also be loaded from disk using `K8s::Resource.from_files(path)`, and passed to the top-level methods such as `client.create_resource(res)`, which lookup the correct API/Resource client from the resource `apiVersion` and `kind`.
45
+
46
+ The different `K8s::Error::API` subclasses represent different HTTP response codes, such as `K8s::Error::NotFound` or `K8s::Error::Conflict`.
47
+
48
+ See [`bin/k8s-client`](bin/k8s-client) for example code.
49
+
50
+ ### Creating a client
51
+
52
+ #### Unauthenticated client
53
+
54
+ ```ruby
55
+ client = K8s.client('https://localhost:6443', ssl_verify_peer: false)
56
+ ```
57
+
58
+ The keyword options are [Excon](https://github.com/excon/excon/) options.
59
+
60
+ #### Client from kubeconfig
61
+
62
+ ```ruby
63
+ client = K8s::Client.config(K8s::Config.load_file('~/.kube/config'))
64
+ ```
65
+
66
+ #### In-cluster client from pod envs/secrets
67
+
68
+ ```ruby
69
+ client = K8s::Client.in_cluster_config
70
+ ```
71
+
72
+ ### Logging
73
+
74
+ #### Quiet
75
+
76
+ To supress any warning messages:
77
+
78
+ ```ruby
79
+ K8s::Logging.quiet!
80
+ K8s::Transport.quiet!
81
+ ```
82
+
83
+ The `K8s::Transport` is quiet by default, but other components may log warnings in the future.
84
+
85
+ #### Debugging
86
+
87
+ Log all API requests
88
+
89
+ ```ruby
90
+ K8s::Logging.debug!
91
+ K8s::Transport.verbose!
92
+ ```
93
+
94
+ ```
95
+ I, [2018-08-09T14:19:50.404739 #1] INFO -- K8s::Transport: Using config with server=https://167.99.39.233:6443
96
+ I, [2018-08-09T14:19:50.629521 #1] INFO -- K8s::Transport<https://167.99.39.233:6443>: GET /version => HTTP 200: <K8s::API::Version> in 0.224s
97
+ I, [2018-08-09T14:19:50.681367 #1] INFO -- K8s::Transport<https://167.99.39.233:6443>: GET /api/v1 => HTTP 200: <K8s::API::MetaV1::APIResourceList> in 0.046s
98
+ I, [2018-08-09T14:19:51.018740 #1] INFO -- K8s::Transport<https://167.99.39.233:6443>: GET /api/v1/pods => HTTP 200: <K8s::API::MetaV1::List> in 0.316s
99
+ ```
100
+
101
+ Using `K8s::Transport.debug!` will also log request/response bodies. The `EXCON_DEBUG=true` env will log all request/response attributes, including headers.
102
+
103
+ ### Prefetching API resources
104
+
105
+ Operations like mapping a resource `kind` to an API resource URL require knowledge of the API resource lists for the API group. Mapping resources for multiple API groups would require fetching the API resource lists for each API group in turn, leading to additional request latency. This can be optimized using resource prefetching:
106
+
107
+ ```ruby
108
+ client.apis(prefetch_resources: true)
109
+ ```
110
+
111
+ This will fetch the API resource lists for all API groups in a single pipelined request.
112
+
113
+ ### Listing resources
114
+
115
+ ```ruby
116
+ client.api('v1').resource('pods', namespace: 'default').list(labelSelector: {'role' => 'test'}).each do |pod|
117
+ puts "namespace=#{pod.metadata.namespace} pod: #{pod.metadata.name} node=#{pod.spec.nodeName}"
118
+ end
119
+ ```
120
+
121
+ ### Updating resources
122
+
123
+ ```ruby
124
+ node = client.api('v1').resource('nodes').get('test-node')
125
+
126
+ node[:spec][:unschedulable] = true
127
+
128
+ client.api('v1').resource('nodes').update_resource(node)
129
+ ```
130
+
131
+ ### Deleting resources
132
+
133
+ ```ruby
134
+ pod = client.api('v1').resource('pods', namespace: 'default').delete('test-pod')
135
+ ```
136
+
137
+ ```ruby
138
+ pods = client.api('v1').resource('pods', namespace: 'default').delete_collection(labelSelector: {'role' => 'test'})
139
+ ```
140
+
141
+ ### Creating resources
142
+
143
+ #### Programmatically defined resources
144
+ ```ruby
145
+ service = K8s::Resource.new(
146
+ apiVersion: 'v1',
147
+ kind: 'Service',
148
+ metadata: {
149
+ namespace: 'default',
150
+ name: 'test',
151
+ },
152
+ spec: {
153
+ type: 'ClusterIP',
154
+ ports: [
155
+ { port: 80 },
156
+ ],
157
+ selector: {'app' => 'test'},
158
+ },
159
+ )
160
+
161
+ logger.info "Create service=#{service.metadata.name} in namespace=#{service.metadata.namespace}"
162
+
163
+ service = client.api('v1').resource('services').create_resource(service)
164
+ ```
165
+
166
+ #### From file(s)
167
+
168
+ ```ruby
169
+ resources = K8s::Resource.from_files('./test.yaml')
170
+
171
+ for resource in resources
172
+ resource = client.create_resource(resource)
173
+ end
174
+ ```
175
+
176
+ ### Patching resources
177
+
178
+ ```ruby
179
+ client.api('apps/v1').resource('deployments', namespace: 'default').merge_patch('test', {
180
+ spec: { replicas: 3 },
181
+ })
182
+ ```
26
183
 
27
184
  ## Contributing
28
185
 
@@ -1,5 +1,5 @@
1
1
  module K8s
2
2
  class Client
3
- VERSION = "0.2.1"
3
+ VERSION = "0.3.0"
4
4
  end
5
5
  end
data/lib/k8s/error.rb CHANGED
@@ -42,6 +42,7 @@ module K8s
42
42
  define_status_error 422, :Invalid
43
43
  define_status_error 429, :Timeout
44
44
  define_status_error 500, :InternalError
45
+ define_status_error 503, :ServiceUnavailable
45
46
  define_status_error 504, :ServerTimeout
46
47
  end
47
48
  end
data/lib/k8s/transport.rb CHANGED
@@ -167,6 +167,8 @@ module K8s
167
167
  status = K8s::API::MetaV1::Status.new(response_data)
168
168
 
169
169
  raise error_class.new(method, path, response.status, response.reason_phrase, status)
170
+ elsif response_data
171
+ raise error_class.new(method, path, response.status, "#{response.reason_phrase}: #{response_data}")
170
172
  else
171
173
  raise error_class.new(method, path, response.status, response.reason_phrase)
172
174
  end
@@ -196,9 +198,10 @@ module K8s
196
198
  end
197
199
 
198
200
  # @param options [Array<Hash>]
199
- # @param skip_missing [Boolean] return nil for 404
201
+ # @param skip_missing [Boolean] return nil for HTTP 404 responses
202
+ # @param retry_errors [Boolean] retry with non-pipelined request for HTTP 503 responses
200
203
  # @return [Array<response_class, Hash, nil>]
201
- def requests(*options, response_class: nil, skip_missing: false)
204
+ def requests(*options, response_class: nil, skip_missing: false, retry_errors: true)
202
205
  return [] if options.empty? # excon chokes
203
206
 
204
207
  start = Time.now
@@ -218,6 +221,15 @@ module K8s
218
221
  else
219
222
  raise
220
223
  end
224
+ rescue K8s::Error::ServiceUnavailable => exc
225
+ if retry_errors
226
+ logger.warn { "Retry #{format_request(request_options)} => HTTP #{exc.code} #{exc.reason} in #{'%.3f' % t}s" }
227
+
228
+ # only retry the failed request, not the entire pipeline
229
+ request(response_class: response_class, **request_options)
230
+ else
231
+ raise
232
+ end
221
233
  end
222
234
  }
223
235
  rescue K8s::Error => exc
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: k8s-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kontena, Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-08-08 00:00:00.000000000 Z
11
+ date: 2018-08-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: excon