k8s-ruby2 0.10.6

Sign up to get free protection for your applications and to get access to all the features.
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]
@@ -0,0 +1,10 @@
1
+ rspec:
2
+ build: .
3
+ volumes:
4
+ - .:/app
5
+ entrypoint: bundle exec rspec
6
+ k8s-ruby:
7
+ build: .
8
+ volumes:
9
+ - .:/app
10
+ entrypoint: bundle exec bin/k8s-client
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