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 +4 -4
- data/README.md +159 -2
- data/lib/k8s/client/version.rb +1 -1
- data/lib/k8s/error.rb +1 -0
- data/lib/k8s/transport.rb +14 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 780d34c7890954d2e54e0b158f94a8d90e9b5bdaaab11e65cf2db86e695f640a
|
4
|
+
data.tar.gz: 40f4843ff19e0b403f906e2f17f9ff7859c5f47413f0354e7ae1758fc5d24377
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
-
|
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
|
|
data/lib/k8s/client/version.rb
CHANGED
data/lib/k8s/error.rb
CHANGED
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.
|
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-
|
11
|
+
date: 2018-08-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: excon
|