k8s-ruby2 0.10.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/.rspec +3 -0
- data/.rubocop.relaxed.yml +197 -0
- data/.rubocop.yml +57 -0
- data/Dockerfile +11 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +109 -0
- data/LICENSE +201 -0
- data/README.md +227 -0
- data/Rakefile +16 -0
- data/docker-compose.yaml +10 -0
- data/k8s-ruby2.gemspec +40 -0
- data/lib/k8s/api/metav1/api_group.rb +26 -0
- data/lib/k8s/api/metav1/api_resource.rb +26 -0
- data/lib/k8s/api/metav1/list.rb +20 -0
- data/lib/k8s/api/metav1/object.rb +53 -0
- data/lib/k8s/api/metav1/status.rb +34 -0
- data/lib/k8s/api/metav1/watch_event.rb +20 -0
- data/lib/k8s/api/metav1.rb +26 -0
- data/lib/k8s/api/version.rb +17 -0
- data/lib/k8s/api.rb +31 -0
- data/lib/k8s/api_client.rb +116 -0
- data/lib/k8s/client.rb +283 -0
- data/lib/k8s/config.rb +212 -0
- data/lib/k8s/error.rb +66 -0
- data/lib/k8s/logging.rb +87 -0
- data/lib/k8s/resource.rb +115 -0
- data/lib/k8s/resource_client.rb +349 -0
- data/lib/k8s/ruby/version.rb +8 -0
- data/lib/k8s/stack.rb +187 -0
- data/lib/k8s/transport.rb +382 -0
- data/lib/k8s/util.rb +139 -0
- data/lib/k8s-ruby.rb +4 -0
- metadata +279 -0
data/README.md
ADDED
@@ -0,0 +1,227 @@
|
|
1
|
+
# K8s::Client provided by k8s-ruby
|
2
|
+
|
3
|
+
Ruby client library for the Kubernetes (1.9+) API.
|
4
|
+
|
5
|
+
The `k8s-ruby` project is a fork of
|
6
|
+
[kontena/k8s-client](https://github.com/kontena/k8s-client).
|
7
|
+
|
8
|
+
Unfortunately the company
|
9
|
+
[Kontena.io went bankcrupt](https://blog.kontena.io/farewell/) at
|
10
|
+
the end of the year 2019. They had created many wonderful Ruby projects which we
|
11
|
+
are grateful.
|
12
|
+
|
13
|
+
The `k8s-ruby` library is a community effort to keep `k8s-client`
|
14
|
+
maintained without any dependencies to the former Kontena.io organization.
|
15
|
+
The library was renamed in order to publish it to Rubygems without conflicting
|
16
|
+
with `k8s-client`.
|
17
|
+
|
18
|
+
## Highlights
|
19
|
+
|
20
|
+
* Clean API for dynamic Kubernetes API Groups / Resources
|
21
|
+
* Fast API requests using HTTP connection keepalive
|
22
|
+
* Fast API discovery and resource listings using pipelined HTTP requests
|
23
|
+
* Typed errors with useful debugging information
|
24
|
+
|
25
|
+
## Installation
|
26
|
+
|
27
|
+
Add this line to your application's Gemfile:
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
gem 'k8s-ruby'
|
31
|
+
```
|
32
|
+
|
33
|
+
And then execute:
|
34
|
+
|
35
|
+
$ bundle
|
36
|
+
|
37
|
+
Or install it yourself as:
|
38
|
+
|
39
|
+
$ gem install k8s-ruby
|
40
|
+
|
41
|
+
And then load the code using:
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
require 'k8s-ruby'
|
45
|
+
```
|
46
|
+
|
47
|
+
## Usage
|
48
|
+
|
49
|
+
### Overview
|
50
|
+
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.).
|
51
|
+
|
52
|
+
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`.
|
53
|
+
|
54
|
+
The different `K8s::Error::API` subclasses represent different HTTP response codes, such as `K8s::Error::NotFound` or `K8s::Error::Conflict`.
|
55
|
+
|
56
|
+
### Creating a client
|
57
|
+
|
58
|
+
#### Unauthenticated client
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
client = K8s.client('https://localhost:6443', ssl_verify_peer: false)
|
62
|
+
```
|
63
|
+
|
64
|
+
The keyword options are [Excon](https://github.com/excon/excon/) options.
|
65
|
+
|
66
|
+
#### Client from kubeconfig
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
client = K8s::Client.config(
|
70
|
+
K8s::Config.load_file(
|
71
|
+
File.expand_path '~/.kube/config'
|
72
|
+
)
|
73
|
+
)
|
74
|
+
```
|
75
|
+
|
76
|
+
#### Supported kubeconfig options
|
77
|
+
|
78
|
+
Not all kubeconfig options are supported, only the following kubeconfig options work:
|
79
|
+
|
80
|
+
* `current-context`
|
81
|
+
* `context.cluster`
|
82
|
+
* `context.user`
|
83
|
+
* `cluster.server`
|
84
|
+
* `cluster.insecure_skip_tls_verify`
|
85
|
+
* `cluster.certificate_authority`
|
86
|
+
* `cluster.certificate_authority_data`
|
87
|
+
* `user.client_certificate` + `user.client_key`
|
88
|
+
* `user.client_certificate_data` + `user.client_key_data`
|
89
|
+
* `user.token`
|
90
|
+
|
91
|
+
##### With overrides
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
client = K8s::Client.config(K8s::Config.load_file('~/.kube/config'),
|
95
|
+
server: 'http://localhost:8001',
|
96
|
+
)
|
97
|
+
```
|
98
|
+
|
99
|
+
#### In-cluster client from pod envs/secrets
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
client = K8s::Client.in_cluster_config
|
103
|
+
```
|
104
|
+
|
105
|
+
### Logging
|
106
|
+
|
107
|
+
#### Quiet
|
108
|
+
|
109
|
+
To supress any warning messages:
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
K8s::Logging.quiet!
|
113
|
+
K8s::Transport.quiet!
|
114
|
+
```
|
115
|
+
|
116
|
+
The `K8s::Transport` is quiet by default, but other components may log warnings in the future.
|
117
|
+
|
118
|
+
#### Debugging
|
119
|
+
|
120
|
+
Log all API requests
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
K8s::Logging.debug!
|
124
|
+
K8s::Transport.verbose!
|
125
|
+
```
|
126
|
+
|
127
|
+
```
|
128
|
+
I, [2018-08-09T14:19:50.404739 #1] INFO -- K8s::Transport: Using config with server=https://167.99.39.233:6443
|
129
|
+
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
|
130
|
+
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
|
131
|
+
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
|
132
|
+
```
|
133
|
+
|
134
|
+
Using `K8s::Transport.debug!` will also log request/response bodies. The `EXCON_DEBUG=true` env will log all request/response attributes, including headers.
|
135
|
+
|
136
|
+
### Prefetching API resources
|
137
|
+
|
138
|
+
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:
|
139
|
+
|
140
|
+
```ruby
|
141
|
+
client.apis(prefetch_resources: true)
|
142
|
+
```
|
143
|
+
|
144
|
+
This will fetch the API resource lists for all API groups in a single pipelined request.
|
145
|
+
|
146
|
+
### Listing resources
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
client.api('v1').resource('pods', namespace: 'default').list(labelSelector: {'role' => 'test'}).each do |pod|
|
150
|
+
puts "namespace=#{pod.metadata.namespace} pod: #{pod.metadata.name} node=#{pod.spec.nodeName}"
|
151
|
+
end
|
152
|
+
```
|
153
|
+
|
154
|
+
### Updating resources
|
155
|
+
|
156
|
+
```ruby
|
157
|
+
node = client.api('v1').resource('nodes').get('test-node')
|
158
|
+
|
159
|
+
node[:spec][:unschedulable] = true
|
160
|
+
|
161
|
+
client.api('v1').resource('nodes').update_resource(node)
|
162
|
+
```
|
163
|
+
|
164
|
+
### Deleting resources
|
165
|
+
|
166
|
+
```ruby
|
167
|
+
pod = client.api('v1').resource('pods', namespace: 'default').delete('test-pod')
|
168
|
+
```
|
169
|
+
|
170
|
+
```ruby
|
171
|
+
pods = client.api('v1').resource('pods', namespace: 'default').delete_collection(labelSelector: {'role' => 'test'})
|
172
|
+
```
|
173
|
+
|
174
|
+
### Creating resources
|
175
|
+
|
176
|
+
#### Programmatically defined resources
|
177
|
+
```ruby
|
178
|
+
service = K8s::Resource.new(
|
179
|
+
apiVersion: 'v1',
|
180
|
+
kind: 'Service',
|
181
|
+
metadata: {
|
182
|
+
namespace: 'default',
|
183
|
+
name: 'test',
|
184
|
+
},
|
185
|
+
spec: {
|
186
|
+
type: 'ClusterIP',
|
187
|
+
ports: [
|
188
|
+
{ port: 80 },
|
189
|
+
],
|
190
|
+
selector: {'app' => 'test'},
|
191
|
+
},
|
192
|
+
)
|
193
|
+
|
194
|
+
logger.info "Create service=#{service.metadata.name} in namespace=#{service.metadata.namespace}"
|
195
|
+
|
196
|
+
service = client.api('v1').resource('services').create_resource(service)
|
197
|
+
```
|
198
|
+
|
199
|
+
#### From file(s)
|
200
|
+
|
201
|
+
```ruby
|
202
|
+
resources = K8s::Resource.from_files('./test.yaml')
|
203
|
+
|
204
|
+
for resource in resources
|
205
|
+
resource = client.create_resource(resource)
|
206
|
+
end
|
207
|
+
```
|
208
|
+
|
209
|
+
### Patching resources
|
210
|
+
|
211
|
+
```ruby
|
212
|
+
client.api('apps/v1').resource('deployments', namespace: 'default').merge_patch('test', {
|
213
|
+
spec: { replicas: 3 },
|
214
|
+
})
|
215
|
+
```
|
216
|
+
|
217
|
+
### Watching resources
|
218
|
+
|
219
|
+
```ruby
|
220
|
+
client.api('v1').resource('pods', namespace: 'default').watch(labelSelector: {'role' => 'test'}) do |watch_event|
|
221
|
+
puts "type=#{watch_event.type} pod=#{watch_event.resource.metadata.name}"
|
222
|
+
end
|
223
|
+
```
|
224
|
+
|
225
|
+
## Contributing
|
226
|
+
|
227
|
+
Bug reports and pull requests are welcome on GitHub at [k8s-ruby/k8s-ruby](https://github.com/k8s-ruby/k8s-ruby).
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
require "rspec/core/rake_task"
|
5
|
+
require 'rubocop/rake_task'
|
6
|
+
|
7
|
+
RSpec::Core::RakeTask.new(:spec)
|
8
|
+
|
9
|
+
RuboCop::RakeTask.new(:rubocop).tap do |task|
|
10
|
+
task.options = %w(--fail-level A -S --format c)
|
11
|
+
if RUBY_ENGINE == 'ruby' && RbConfig::CONFIG['host_os'] !~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/
|
12
|
+
task.options << '--parallel'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
task default: [:spec, :rubocop]
|
data/docker-compose.yaml
ADDED
data/k8s-ruby2.gemspec
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
lib = File.expand_path('lib', __dir__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require "k8s/ruby/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "k8s-ruby2"
|
7
|
+
spec.version = K8s::Ruby::VERSION
|
8
|
+
spec.authors = ["rdx.net", "Kontena, Inc."]
|
9
|
+
spec.email = ["firstname.lastname@rdx.net"]
|
10
|
+
spec.license = "Apache-2.0"
|
11
|
+
|
12
|
+
spec.summary = "Kubernetes client library for Ruby"
|
13
|
+
spec.homepage = "https://github.com/tsutsu/k8s-ruby"
|
14
|
+
|
15
|
+
# Specify which files should be added to the gem when it is released.
|
16
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
17
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
18
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
19
|
+
end
|
20
|
+
spec.bindir = "bin"
|
21
|
+
spec.executables = []
|
22
|
+
spec.require_paths = ["lib"]
|
23
|
+
spec.required_ruby_version = '>= 2.4'
|
24
|
+
|
25
|
+
spec.add_runtime_dependency "dry-struct", "~> 1.4.0"
|
26
|
+
spec.add_runtime_dependency "dry-types", "~> 1.5.0"
|
27
|
+
spec.add_runtime_dependency "excon", "~> 0.78"
|
28
|
+
spec.add_runtime_dependency "recursive-open-struct", "~> 1.1.3"
|
29
|
+
spec.add_runtime_dependency "yaml-safe_load_stream2", "~> 0.1.1"
|
30
|
+
spec.add_runtime_dependency 'hashdiff', '~> 1.0.1'
|
31
|
+
spec.add_runtime_dependency 'jsonpath', '~> 1.1.0'
|
32
|
+
spec.add_runtime_dependency 'yajl-ruby', '~> 1.4.1'
|
33
|
+
|
34
|
+
spec.add_development_dependency "bundler", ">= 1.17", "< 3.0"
|
35
|
+
spec.add_development_dependency "rake", ">= 12.3.3"
|
36
|
+
spec.add_development_dependency "rspec", "~> 3.10"
|
37
|
+
spec.add_development_dependency "webmock", "~> 3.11.0"
|
38
|
+
spec.add_development_dependency "rubocop", "~> 1.8.0"
|
39
|
+
spec.add_development_dependency "byebug", "~> 11.1.3"
|
40
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module K8s
|
4
|
+
module API
|
5
|
+
module MetaV1
|
6
|
+
# structured list of available APIGroup versions
|
7
|
+
# groupVersion provided for convenience
|
8
|
+
class APIGroupVersion < Struct
|
9
|
+
attribute :groupVersion, Types::Strict::String
|
10
|
+
attribute :version, Types::Strict::String
|
11
|
+
end
|
12
|
+
|
13
|
+
# @see https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1#APIGroup
|
14
|
+
class APIGroup < Struct
|
15
|
+
attribute :name, Types::Strict::String
|
16
|
+
attribute :versions, Types::Strict::Array.of(APIGroupVersion)
|
17
|
+
attribute :preferredVersion, APIGroupVersion
|
18
|
+
end
|
19
|
+
|
20
|
+
# @see https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1#APIGroupList
|
21
|
+
class APIGroupList < Resource
|
22
|
+
attribute :groups, Types::Strict::Array.of(APIGroup)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module K8s
|
4
|
+
module API
|
5
|
+
module MetaV1
|
6
|
+
# @see https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1#APIResource
|
7
|
+
class APIResource < Struct
|
8
|
+
attribute :name, Types::Strict::String
|
9
|
+
attribute :singularName, Types::Strict::String
|
10
|
+
attribute :namespaced, Types::Strict::Bool
|
11
|
+
attribute :group, Types::Strict::String.optional.default(nil, shared: true)
|
12
|
+
attribute :version, Types::Strict::String.optional.default(nil, shared: true)
|
13
|
+
attribute :kind, Types::Strict::String
|
14
|
+
attribute :verbs, Types::Strict::Array.of(Types::Strict::String)
|
15
|
+
attribute :shortNames, Types::Strict::Array.of(Types::Strict::String).optional.default(proc { [] }, shared: true)
|
16
|
+
attribute :categories, Types::Strict::Array.of(Types::Strict::String).optional.default(proc { [] }, shared: true)
|
17
|
+
end
|
18
|
+
|
19
|
+
# @see https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1#APIResourceList
|
20
|
+
class APIResourceList < Resource
|
21
|
+
attribute :groupVersion, Types::Strict::String
|
22
|
+
attribute :resources, Types::Strict::Array.of(APIResource)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module K8s
|
4
|
+
module API
|
5
|
+
module MetaV1
|
6
|
+
# @see https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1#ListMeta
|
7
|
+
class ListMeta < Struct
|
8
|
+
attribute :selfLink, Types::Strict::String.optional.default(nil)
|
9
|
+
attribute :resourceVersion, Types::Strict::String.optional.default(nil)
|
10
|
+
attribute :continue, Types::Strict::String.optional.default(nil)
|
11
|
+
end
|
12
|
+
|
13
|
+
# @see https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1#List
|
14
|
+
class List < Resource
|
15
|
+
attribute :metadata, ListMeta
|
16
|
+
attribute :items, Types::Strict::Array # untyped
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'k8s/api/metav1/status'
|
4
|
+
|
5
|
+
module K8s
|
6
|
+
module API
|
7
|
+
module MetaV1
|
8
|
+
# @see https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1#OwnerReference
|
9
|
+
class OwnerReference < Resource
|
10
|
+
attribute :name, Types::Strict::String
|
11
|
+
attribute :uid, Types::Strict::String
|
12
|
+
attribute :controller, Types::Strict::Bool.optional.default(nil, shared: true)
|
13
|
+
attribute :blockOwnerDeletion, Types::Strict::Bool.optional.default(nil, shared: true)
|
14
|
+
end
|
15
|
+
|
16
|
+
# @see https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1#Initializer
|
17
|
+
class Initializer < Struct
|
18
|
+
attribute :name, Types::Strict::String
|
19
|
+
end
|
20
|
+
|
21
|
+
# @see https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1#Initializers
|
22
|
+
class Initializers < Struct
|
23
|
+
attribute :pending, Initializer
|
24
|
+
attribute :result, Status.optional.default(nil, shared: true)
|
25
|
+
end
|
26
|
+
|
27
|
+
# @see https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1#ObjectMeta
|
28
|
+
class ObjectMeta < Resource
|
29
|
+
attribute :name, Types::Strict::String.optional.default(nil, shared: true)
|
30
|
+
attribute :generateName, Types::Strict::String.optional.default(nil, shared: true)
|
31
|
+
attribute :namespace, Types::Strict::String.optional.default(nil, shared: true)
|
32
|
+
attribute :selfLink, Types::Strict::String.optional.default(nil, shared: true)
|
33
|
+
attribute :uid, Types::Strict::String.optional.default(nil, shared: true)
|
34
|
+
attribute :resourceVersion, Types::Strict::String.optional.default(nil, shared: true)
|
35
|
+
attribute :generation, Types::Strict::Integer.optional.default(nil, shared: true)
|
36
|
+
attribute :creationTimestamp, Types::DateTime.optional.default(nil, shared: true)
|
37
|
+
attribute :deletionTimestamp, Types::DateTime.optional.default(nil, shared: true)
|
38
|
+
attribute :deletionGracePeriodSeconds, Types::Strict::Integer.optional.default(nil, shared: true)
|
39
|
+
attribute :labels, Types::Strict::Hash.map(Types::Strict::String, Types::Strict::String).optional.default(nil, shared: true)
|
40
|
+
attribute :annotations, Types::Strict::Hash.map(Types::Strict::String, Types::Strict::String).optional.default(nil, shared: true)
|
41
|
+
attribute :ownerReferences, Types::Strict::Array.of(OwnerReference).optional.default([].freeze)
|
42
|
+
attribute :initializers, Initializers.optional.default(nil, shared: true)
|
43
|
+
attribute :finalizers, Types::Strict::Array.of(Types::Strict::String).optional.default([].freeze)
|
44
|
+
attribute :clusterName, Types::Strict::String.optional.default(nil, shared: true)
|
45
|
+
end
|
46
|
+
|
47
|
+
# common attributes shared by all object types
|
48
|
+
class ObjectCommon < Resource
|
49
|
+
attribute :metadata, ObjectMeta.optional.default(nil, shared: true)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module K8s
|
4
|
+
module API
|
5
|
+
module MetaV1
|
6
|
+
# @see https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1#Status
|
7
|
+
class Status < Resource
|
8
|
+
# structured cause
|
9
|
+
class Cause < Struct
|
10
|
+
attribute :reason, Types::Strict::String.optional.default(nil)
|
11
|
+
attribute :message, Types::Strict::String.optional.default(nil) # human-readable
|
12
|
+
attribute :field, Types::Strict::String.optional.default(nil) # human-readable
|
13
|
+
end
|
14
|
+
|
15
|
+
# structured details
|
16
|
+
class Details < Struct
|
17
|
+
attribute :name, Types::Strict::String.optional.default(nil)
|
18
|
+
attribute :group, Types::Strict::String.optional.default(nil)
|
19
|
+
attribute :kind, Types::Strict::String.optional.default(nil)
|
20
|
+
attribute :uid, Types::Strict::String.optional.default(nil)
|
21
|
+
attribute :causes, Types::Strict::Array.of(Cause).optional.default(nil)
|
22
|
+
attribute :retryAfterSeconds, Types::Strict::Integer.optional.default(nil)
|
23
|
+
end
|
24
|
+
|
25
|
+
attribute :metadata, ListMeta
|
26
|
+
attribute :status, Types::Strict::String.enum('Success', 'Failure').optional.default(nil)
|
27
|
+
attribute :message, Types::Strict::String.optional.default(nil) # human-readable
|
28
|
+
attribute :reason, Types::Strict::String.optional.default(nil) # machine-readable
|
29
|
+
attribute :details, Details.optional.default(nil)
|
30
|
+
attribute :code, Types::Strict::Integer.optional.default(nil)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../resource'
|
4
|
+
|
5
|
+
module K8s
|
6
|
+
module API
|
7
|
+
module MetaV1
|
8
|
+
# @see https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1#WatchEvent
|
9
|
+
class WatchEvent < Resource
|
10
|
+
attribute :type, Types::Strict::String
|
11
|
+
attribute :object, Types::Strict::Hash
|
12
|
+
|
13
|
+
# @return [K8s::Resource]
|
14
|
+
def resource
|
15
|
+
@resource ||= K8s::Resource.new(object)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'k8s/api'
|
4
|
+
|
5
|
+
module K8s
|
6
|
+
module API
|
7
|
+
# API types common to all apigroups/versions
|
8
|
+
#
|
9
|
+
# @see https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1
|
10
|
+
module MetaV1
|
11
|
+
# @see https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1#TypeMeta
|
12
|
+
class Resource < Struct
|
13
|
+
# XXX: making these optional seems dangerous, but some APIs (GET /api/v1) are missing these
|
14
|
+
attribute :kind, Types::Strict::String.optional.default(nil)
|
15
|
+
attribute :apiVersion, Types::Strict::String.optional.default(nil)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'k8s/api/metav1/api_group'
|
22
|
+
require 'k8s/api/metav1/api_resource'
|
23
|
+
require 'k8s/api/metav1/list'
|
24
|
+
require 'k8s/api/metav1/object'
|
25
|
+
require 'k8s/api/metav1/status'
|
26
|
+
require 'k8s/api/metav1/watch_event'
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module K8s
|
4
|
+
module API
|
5
|
+
# GET /version
|
6
|
+
class Version < Struct
|
7
|
+
attribute :buildDate, Types::Strict::String # TODO: parse datetime?
|
8
|
+
attribute :compiler, Types::Strict::String
|
9
|
+
attribute :gitCommit, Types::Strict::String
|
10
|
+
attribute :gitTreeState, Types::Strict::String
|
11
|
+
attribute :gitVersion, Types::Strict::String
|
12
|
+
attribute :major, Types::Strict::String
|
13
|
+
attribute :minor, Types::Strict::String
|
14
|
+
attribute :platform, Types::Strict::String
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/k8s/api.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dry-types'
|
4
|
+
require 'dry-struct'
|
5
|
+
|
6
|
+
module K8s
|
7
|
+
# Kube API definitions
|
8
|
+
module API
|
9
|
+
# Common Dry::Types used in the API
|
10
|
+
module Types
|
11
|
+
include Dry::Types()
|
12
|
+
end
|
13
|
+
|
14
|
+
# Common API struct type, handling JSON transforms with symbol keys
|
15
|
+
class Struct < Dry::Struct
|
16
|
+
# input from JSON with string keys
|
17
|
+
transform_keys(&:to_sym)
|
18
|
+
|
19
|
+
# @param data [Hash]
|
20
|
+
# @return [self]
|
21
|
+
def self.from_json(data)
|
22
|
+
new(Yajl::Parser.parse(data))
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [String]
|
26
|
+
def to_json(*_args)
|
27
|
+
to_hash.to_json
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module K8s
|
4
|
+
# Per-APIGroup/version client.
|
5
|
+
#
|
6
|
+
# Offers access to {ResourceClient} instances for the APIResource types defined in this apigroup/version
|
7
|
+
class APIClient
|
8
|
+
# @param api_version [String] either core version (v1) or apigroup/apiversion (apps/v1)
|
9
|
+
# @return [String]
|
10
|
+
def self.path(api_version)
|
11
|
+
if api_version.include? '/'
|
12
|
+
File.join('/apis', api_version)
|
13
|
+
else
|
14
|
+
File.join('/api', api_version)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# @param transport [K8s::Transport]
|
19
|
+
# @param api_version [String] "group/version" or "version" (core)
|
20
|
+
def initialize(transport, api_version)
|
21
|
+
@transport = transport
|
22
|
+
@api_version = api_version
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [String]
|
26
|
+
attr_reader :api_version
|
27
|
+
|
28
|
+
# @param path [Array<String>] join path from parts
|
29
|
+
# @return [String]
|
30
|
+
def path(*path)
|
31
|
+
@transport.path(self.class.path(@api_version), *path)
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return [Bool] loaded yet?
|
35
|
+
def api_resources?
|
36
|
+
!!@api_resources
|
37
|
+
end
|
38
|
+
|
39
|
+
# @param api_resources [Array<K8s::API::MetaV1::APIResource>]
|
40
|
+
attr_writer :api_resources
|
41
|
+
|
42
|
+
# Force-update APIResources
|
43
|
+
#
|
44
|
+
# @return [Array<K8s::API::MetaV1::APIResource>]
|
45
|
+
def api_resources!
|
46
|
+
@api_resources = @transport.get(path,
|
47
|
+
response_class: K8s::API::MetaV1::APIResourceList).resources
|
48
|
+
end
|
49
|
+
|
50
|
+
# Cached APIResources
|
51
|
+
#
|
52
|
+
# @return [Array<K8s::API::MetaV1::APIResource>]
|
53
|
+
def api_resources
|
54
|
+
@api_resources || api_resources!
|
55
|
+
end
|
56
|
+
|
57
|
+
# @param resource_name [String]
|
58
|
+
# @raise [K8s::Error::UndefinedResource]
|
59
|
+
# @return [K8s::API::MetaV1::APIResource]
|
60
|
+
def find_api_resource(resource_name)
|
61
|
+
found_resource = api_resources.find{ |api_resource| api_resource.name == resource_name }
|
62
|
+
found_resource ||= api_resources!.find{ |api_resource| api_resource.name == resource_name }
|
63
|
+
raise K8s::Error::UndefinedResource, "Unknown resource #{resource_name} for #{@api_version}" unless found_resource
|
64
|
+
|
65
|
+
found_resource
|
66
|
+
end
|
67
|
+
|
68
|
+
# @param resource_name [String]
|
69
|
+
# @param namespace [String, nil]
|
70
|
+
# @raise [K8s::Error::UndefinedResource]
|
71
|
+
# @return [K8s::ResourceClient]
|
72
|
+
def resource(resource_name, namespace: nil)
|
73
|
+
ResourceClient.new(@transport, self, find_api_resource(resource_name), namespace: namespace)
|
74
|
+
end
|
75
|
+
|
76
|
+
# @param resource [K8s::Resource]
|
77
|
+
# @param namespace [String, nil] default if resource is missing namespace
|
78
|
+
# @raise [K8s::Error::UndefinedResource]
|
79
|
+
# @return [K8s::ResourceClient]
|
80
|
+
def client_for_resource(resource, namespace: nil)
|
81
|
+
unless @api_version == resource.apiVersion
|
82
|
+
raise K8s::Error::UndefinedResource, "Invalid apiVersion=#{resource.apiVersion} for #{@api_version} client"
|
83
|
+
end
|
84
|
+
|
85
|
+
found_resource = api_resources.find{ |api_resource| api_resource.kind == resource.kind }
|
86
|
+
found_resource ||= api_resources!.find{ |api_resource| api_resource.kind == resource.kind }
|
87
|
+
raise K8s::Error::UndefinedResource, "Unknown resource kind=#{resource.kind} for #{@api_version}" unless found_resource
|
88
|
+
|
89
|
+
ResourceClient.new(@transport, self, found_resource, namespace: resource.metadata.namespace || namespace)
|
90
|
+
end
|
91
|
+
|
92
|
+
# TODO: skip non-namespaced resources if namespace is given, or ignore namespace?
|
93
|
+
#
|
94
|
+
# @param namespace [String, nil]
|
95
|
+
# @return [Array<K8s::ResourceClient>]
|
96
|
+
def resources(namespace: nil)
|
97
|
+
api_resources.map{ |api_resource|
|
98
|
+
ResourceClient.new(@transport, self, api_resource,
|
99
|
+
namespace: namespace)
|
100
|
+
}
|
101
|
+
end
|
102
|
+
|
103
|
+
# Pipeline list requests for multiple resource types.
|
104
|
+
#
|
105
|
+
# Returns flattened array with mixed resource kinds.
|
106
|
+
#
|
107
|
+
# @param resources [Array<K8s::ResourceClient>] default is all listable resources for api
|
108
|
+
# @param options @see [K8s::ResourceClient#list]
|
109
|
+
# @return [Array<K8s::Resource>]
|
110
|
+
def list_resources(resources = nil, **options)
|
111
|
+
resources ||= self.resources.select(&:list?)
|
112
|
+
|
113
|
+
ResourceClient.list(resources, @transport, **options)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|