k8s-ruby2 0.10.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/k8s/client.rb ADDED
@@ -0,0 +1,283 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+ require 'base64'
5
+ require 'yajl'
6
+ require 'monitor'
7
+ require 'uri'
8
+
9
+ require 'k8s/util'
10
+
11
+ require 'k8s/api/metav1'
12
+ require 'k8s/api/version'
13
+ require 'k8s/config'
14
+ require 'k8s/logging'
15
+ require 'k8s/api_client'
16
+ require "k8s/error"
17
+ require 'k8s/resource'
18
+ require 'k8s/resource_client'
19
+ require 'k8s/stack'
20
+ require 'k8s/transport'
21
+
22
+ module K8s
23
+ # @param server [String] http/s URL
24
+ # @param options [Hash] @see Transport.new
25
+ # @return [K8s::Client]
26
+ def self.client(server, **options)
27
+ Client.new(Transport.new(server, **options))
28
+ end
29
+
30
+ # Top-level client wrapper.
31
+ # Uses a {Transport} instance to talk to the kube API.
32
+ # Offers access to {APIClient} and {ResourceClient} instances.
33
+ class Client
34
+ # @param config [Phraos::Kube::Config]
35
+ # @param namespace [String] @see #initialize
36
+ # @param options [Hash] @see Transport.config
37
+ # @return [K8s::Client]
38
+ def self.config(config, namespace: nil, **options)
39
+ new(
40
+ Transport.config(config, **options),
41
+ namespace: namespace
42
+ )
43
+ end
44
+
45
+ # An K8s::Client instance from in-cluster config within a kube pod, using the kubernetes service envs and serviceaccount secrets
46
+ # @see K8s::Transport#in_cluster_config
47
+ #
48
+ # @param namespace [String] default namespace for all operations
49
+ # @param options [Hash] options passed to transport, @see Transport#in_cluster_config
50
+ # @return [K8s::Client]
51
+ # @raise [K8s::Error::Config,Errno::ENOENT,Errno::EACCES]
52
+ def self.in_cluster_config(namespace: nil, **options)
53
+ new(Transport.in_cluster_config(**options), namespace: namespace)
54
+ end
55
+
56
+ # Attempts to create a K8s::Client instance automatically using environment variables, existing configuration
57
+ # files or in cluster configuration.
58
+ #
59
+ # Look-up order:
60
+ # - KUBE_TOKEN, KUBE_CA, KUBE_SERVER environment variables
61
+ # - KUBECONFIG environment variable
62
+ # - $HOME/.kube/config file
63
+ # - In cluster configuration
64
+ #
65
+ # Will raise when no means of configuration is available
66
+ #
67
+ # @param options [Hash] default namespace for all operations
68
+ # @raise [K8s::Error::Config,Errno::ENOENT,Errno::EACCES]
69
+ # @return [K8s::Client]
70
+ def self.autoconfig(namespace: nil, **options)
71
+ if ENV.values_at('KUBE_TOKEN', 'KUBE_CA', 'KUBE_SERVER').none? { |v| v.nil? || v.empty? }
72
+ unless Base64.decode64(ENV['KUBE_CA']).match?(/CERTIFICATE/)
73
+ raise ArgumentError, 'KUBE_CA does not seem to be base64 encoded'
74
+ end
75
+
76
+ begin
77
+ token = options[:auth_token] || Base64.strict_decode64(ENV['KUBE_TOKEN'])
78
+ rescue ArgumentError
79
+ raise ArgumentError, 'KUBE_TOKEN does not seem to be base64 encoded'
80
+ end
81
+
82
+ configuration = K8s::Config.build(server: ENV['KUBE_SERVER'], ca: ENV['KUBE_CA'], auth_token: token)
83
+ elsif !ENV['KUBECONFIG'].to_s.empty?
84
+ configuration = K8s::Config.from_kubeconfig_env(ENV['KUBECONFIG'])
85
+ else
86
+ found_config = [
87
+ File.join(Dir.home, '.kube', 'config'),
88
+ '/etc/kubernetes/admin.conf',
89
+ '/etc/kubernetes/kubelet.conf'
90
+ ].find { |f| File.exist?(f) && File.readable?(f) }
91
+
92
+ configuration = K8s::Config.load_file(found_config) if found_config
93
+ end
94
+
95
+ if configuration
96
+ config(configuration, namespace: namespace, **options)
97
+ else
98
+ in_cluster_config(namespace: namespace, **options)
99
+ end
100
+ end
101
+
102
+ include MonitorMixin
103
+
104
+ attr_reader :transport
105
+
106
+ # @param transport [K8s::Transport]
107
+ # @param namespace [String, nil] default namespace for all operations
108
+ def initialize(transport, namespace: nil)
109
+ @transport = transport
110
+ @namespace = namespace
111
+
112
+ @api_clients = {}
113
+ super()
114
+ end
115
+
116
+ # @raise [K8s::Error]
117
+ # @return [K8s::API::Version]
118
+ def version
119
+ @version ||= @transport.version
120
+ end
121
+
122
+ # @param api_version [String] "group/version" or "version" (core)
123
+ # @return [APIClient]
124
+ def api(api_version = 'v1')
125
+ @api_clients[api_version] ||= APIClient.new(@transport, api_version)
126
+ end
127
+
128
+ # Force-update /apis cache.
129
+ # Required if creating new CRDs/apiservices.
130
+ #
131
+ # @return [Array<String>]
132
+ def api_groups!
133
+ synchronize do
134
+ @api_groups = @transport.get(
135
+ '/apis',
136
+ response_class: K8s::API::MetaV1::APIGroupList
137
+ ).groups.flat_map{ |api_group| api_group.versions.map(&:groupVersion) }
138
+
139
+ @api_clients.clear
140
+ end
141
+
142
+ @api_groups
143
+ end
144
+
145
+ # Cached /apis preferred group apiVersions
146
+ # @return [Array<String>]
147
+ def api_groups
148
+ @api_groups || api_groups!
149
+ end
150
+
151
+ # @param api_versions [Array<String>] defaults to all APIs
152
+ # @param prefetch_resources [Boolean] prefetch any missing api_resources for each api_version
153
+ # @param skip_missing [Boolean] return APIClient without api_resources? if 404
154
+ # @return [Array<APIClient>]
155
+ def apis(api_versions = nil, prefetch_resources: false, skip_missing: false)
156
+ api_versions ||= ['v1'] + api_groups
157
+
158
+ if prefetch_resources
159
+ # api groups that are missing their api_resources
160
+ api_paths = api_versions
161
+ .uniq
162
+ .reject{ |api_version| api(api_version).api_resources? }
163
+ .map{ |api_version| APIClient.path(api_version) }
164
+
165
+ # load into APIClient.api_resources=
166
+ begin
167
+ @transport.gets(*api_paths, response_class: K8s::API::MetaV1::APIResourceList, skip_missing: skip_missing).each do |api_resource_list|
168
+ api(api_resource_list.groupVersion).api_resources = api_resource_list.resources if api_resource_list
169
+ end
170
+ rescue K8s::Error::NotFound, K8s::Error::ServiceUnavailable
171
+ # kubernetes api is in unstable state
172
+ # because this is only performance optimization, better to skip prefetch and move on
173
+ end
174
+ end
175
+
176
+ api_versions.map{ |api_version| api(api_version) }
177
+ end
178
+
179
+ # @param namespace [String, nil]
180
+ # @return [Array<K8s::ResourceClient>]
181
+ def resources(namespace: nil)
182
+ apis(prefetch_resources: true).map { |api|
183
+ begin
184
+ api.resources(namespace: namespace)
185
+ rescue K8s::Error::ServiceUnavailable, K8s::Error::NotFound
186
+ []
187
+ end
188
+ }.flatten
189
+ end
190
+
191
+ # Pipeline list requests for multiple resource types.
192
+ #
193
+ # Returns flattened array with mixed resource kinds.
194
+ #
195
+ # @param resources [Array<K8s::ResourceClient>] default is all listable resources for api
196
+ # @param options @see K8s::ResourceClient#list
197
+ # @return [Array<K8s::Resource>]
198
+ def list_resources(resources = nil, **options)
199
+ cached_clients = @api_clients.size.positive?
200
+ resources ||= self.resources.select(&:list?)
201
+
202
+ begin
203
+ ResourceClient.list(resources, @transport, **options)
204
+ rescue K8s::Error::NotFound
205
+ raise unless cached_clients
206
+
207
+ cached_clients = false
208
+ api_groups!
209
+ retry
210
+ end
211
+ end
212
+
213
+ # @param resource [K8s::Resource]
214
+ # @param namespace [String, nil] default if resource is missing namespace
215
+ # @raise [K8s::Error::NotFound] API Group does not exist
216
+ # @raise [K8s::Error::UndefinedResource]
217
+ # @return [K8s::ResourceClient]
218
+ def client_for_resource(resource, namespace: nil)
219
+ api(resource.apiVersion).client_for_resource(resource, namespace: namespace)
220
+ end
221
+
222
+ # @param resource [K8s::Resource]
223
+ # @return [K8s::Resource]
224
+ def create_resource(resource)
225
+ client_for_resource(resource).create_resource(resource)
226
+ end
227
+
228
+ # @param resource [K8s::Resource]
229
+ # @return [K8s::Resource]
230
+ def get_resource(resource)
231
+ client_for_resource(resource).get_resource(resource)
232
+ end
233
+
234
+ # Returns nils for any resources that do not exist.
235
+ # This includes custom resources that were not yet defined.
236
+ #
237
+ # @param resources [Array<K8s::Resource>]
238
+ # @return [Array<K8s::Resource, nil>] matching resources array 1:1
239
+ def get_resources(resources)
240
+ # prefetch api resources, skip missing APIs
241
+ resource_apis = apis(resources.map(&:apiVersion), prefetch_resources: true, skip_missing: true)
242
+
243
+ # map each resource to excon request options, or nil if resource is not (yet) defined
244
+ requests = resources.zip(resource_apis).map{ |resource, api_client|
245
+ next nil unless api_client.api_resources?
246
+
247
+ resource_client = api_client.client_for_resource(resource)
248
+
249
+ {
250
+ method: 'GET',
251
+ path: resource_client.path(resource.metadata.name, namespace: resource.metadata.namespace),
252
+ response_class: resource_client.resource_class
253
+ }
254
+ }
255
+
256
+ # map non-nil requests to response objects, or nil for nil request options
257
+ Util.compact_map(requests) { |reqs|
258
+ @transport.requests(*reqs, skip_missing: true)
259
+ }
260
+ end
261
+
262
+ # @param resource [K8s::Resource]
263
+ # @return [K8s::Resource]
264
+ def update_resource(resource)
265
+ client_for_resource(resource).update_resource(resource)
266
+ end
267
+
268
+ # @param resource [K8s::Resource]
269
+ # @param options [Hash]
270
+ # @see ResourceClient#delete for options
271
+ # @return [K8s::Resource]
272
+ def delete_resource(resource, **options)
273
+ client_for_resource(resource).delete_resource(resource, **options)
274
+ end
275
+
276
+ # @param resource [K8s::Resource]
277
+ # @param attrs [Hash]
278
+ # @return [K8s::Client]
279
+ def patch_resource(resource, attrs)
280
+ client_for_resource(resource).json_patch(resource.metadata.name, attrs)
281
+ end
282
+ end
283
+ end
data/lib/k8s/config.rb ADDED
@@ -0,0 +1,212 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry-struct'
4
+ require 'dry-types'
5
+ require 'base64'
6
+ require 'yaml'
7
+
8
+ module K8s
9
+ # Common struct type for kubeconfigs:
10
+ #
11
+ # * converts string keys to symbols
12
+ # * normalizes foo-bar to foo_bar
13
+ class ConfigStruct < Dry::Struct
14
+ transform_keys do |key|
15
+ case key
16
+ when String
17
+ key.tr('-', '_').to_sym
18
+ else
19
+ key
20
+ end
21
+ end
22
+ end
23
+
24
+ # @see https://godoc.org/k8s.io/client-go/tools/clientcmd/api/v1#Config
25
+ class Config < ConfigStruct
26
+ # Common dry-types for config
27
+ class Types
28
+ include Dry::Types()
29
+ end
30
+
31
+ # structured cluster
32
+ class Cluster < ConfigStruct
33
+ attribute :server, Types::String
34
+ attribute :insecure_skip_tls_verify,
35
+ Types::Bool.optional.default(nil, shared: true)
36
+ attribute :certificate_authority,
37
+ Types::String.optional.default(nil, shared: true)
38
+ attribute :certificate_authority_data,
39
+ Types::String.optional.default(nil, shared: true)
40
+ attribute :extensions,
41
+ Types::Strict::Array.optional.default(nil, shared: true)
42
+ end
43
+
44
+ # structured cluster with name
45
+ class NamedCluster < ConfigStruct
46
+ attribute :name, Types::String
47
+ attribute :cluster, Cluster
48
+ end
49
+
50
+ # structured user auth provider
51
+ class UserAuthProvider < ConfigStruct
52
+ attribute :name, Types::String
53
+ attribute :config, Types::Strict::Hash
54
+ end
55
+
56
+ # structured user exec
57
+ class UserExec < ConfigStruct
58
+ attribute :command, Types::String
59
+ attribute :apiVersion, Types::String
60
+ attribute :env, Types::Strict::Array.of(Types::Hash).optional.default(nil)
61
+ attribute :args, Types::Strict::Array.of(Types::String).optional.default(nil, shared: true)
62
+ end
63
+
64
+ # structured user
65
+ class User < ConfigStruct
66
+ attribute :client_certificate, Types::String.optional.default(nil, shared: true)
67
+ attribute :client_certificate_data, Types::String.optional.default(nil, shared: true)
68
+ attribute :client_key, Types::String.optional.default(nil, shared: true)
69
+ attribute :client_key_data, Types::String.optional.default(nil, shared: true)
70
+ attribute :token, Types::String.optional.default(nil, shared: true)
71
+ attribute :tokenFile, Types::String.optional.default(nil, shared: true)
72
+ attribute :as, Types::String.optional.default(nil, shared: true)
73
+ attribute :as_groups, Types::Array.of(Types::String).optional.default(nil, shared: true)
74
+ attribute :as_user_extra, Types::Hash.optional.default(nil, shared: true)
75
+ attribute :username, Types::String.optional.default(nil, shared: true)
76
+ attribute :password, Types::String.optional.default(nil, shared: true)
77
+ attribute :auth_provider, UserAuthProvider.optional.default(nil, shared: true)
78
+ attribute :exec, UserExec.optional.default(nil, shared: true)
79
+ attribute :extensions, Types::Strict::Array.optional.default(nil, shared: true)
80
+ end
81
+
82
+ # structured user with name
83
+ class NamedUser < ConfigStruct
84
+ attribute :name, Types::String
85
+ attribute :user, User
86
+ end
87
+
88
+ # structured context
89
+ #
90
+ # Referrs to other named User/cluster objects within the same config.
91
+ class Context < ConfigStruct
92
+ attribute :cluster, Types::Strict::String
93
+ attribute :user, Types::Strict::String
94
+ attribute :namespace, Types::Strict::String.optional.default(nil, shared: true)
95
+ attribute :extensions, Types::Strict::Array.optional.default(nil, shared: true)
96
+ end
97
+
98
+ # named context
99
+ class NamedContext < ConfigStruct
100
+ attribute :name, Types::String
101
+ attribute :context, Context
102
+ end
103
+
104
+ attribute :kind, Types::Strict::String.optional.default(nil, shared: true)
105
+ attribute :apiVersion, Types::Strict::String.optional.default(nil, shared: true)
106
+ attribute :preferences, Types::Strict::Hash.optional.default(proc { {} }, shared: true)
107
+ attribute :clusters, Types::Strict::Array.of(NamedCluster).optional.default(proc { [] }, shared: true)
108
+ attribute :users, Types::Strict::Array.of(NamedUser).optional.default(proc { [] }, shared: true)
109
+ attribute :contexts, Types::Strict::Array.of(NamedContext).optional.default(proc { [] }, shared: true)
110
+ attribute :current_context, Types::Strict::String.optional.default(nil, shared: true)
111
+ attribute :extensions, Types::Strict::Array.optional.default(proc { [] }, shared: true)
112
+
113
+ # Loads a configuration from a YAML file
114
+ #
115
+ # @param path [String]
116
+ # @return [K8s::Config]
117
+ def self.load_file(path)
118
+ new(YAML.safe_load(File.read(File.expand_path(path)), [Time, DateTime, Date], [], true))
119
+ end
120
+
121
+ # Loads configuration files listed in KUBE_CONFIG environment variable and
122
+ # merge using the configuration merge rules, @see K8s::Config.merge
123
+ #
124
+ # @param kubeconfig [String] by default read from ENV['KUBECONFIG']
125
+ def self.from_kubeconfig_env(kubeconfig = nil)
126
+ kubeconfig ||= ENV.fetch('KUBECONFIG', '')
127
+ raise ArgumentError, "KUBECONFIG not set" if kubeconfig.empty?
128
+
129
+ paths = kubeconfig.split(/(?!\\):/)
130
+
131
+ paths.inject(load_file(paths.shift)) do |memo, other_cfg|
132
+ memo.merge(load_file(other_cfg))
133
+ end
134
+ end
135
+
136
+ # Build a minimal configuration from at least a server address, server certificate authority data and an access token.
137
+ #
138
+ # @param server [String] kubernetes server address
139
+ # @param ca [String] server certificate authority data (base64 encoded)
140
+ # @param token [String] access token
141
+ # @param cluster_name [String] cluster name
142
+ # @param user [String] user name
143
+ # @param context [String] context name
144
+ # @param options [Hash] (see #initialize)
145
+ def self.build(server:, ca:, auth_token:, cluster_name: 'kubernetes', user: 'k8s-client', context: 'k8s-client', **options)
146
+ new(
147
+ {
148
+ clusters: [{ name: cluster_name, cluster: { server: server, certificate_authority_data: ca } }],
149
+ users: [{ name: user, user: { token: auth_token } }],
150
+ contexts: [{ name: context, context: { cluster: cluster_name, user: user } }],
151
+ current_context: context
152
+ }.merge(options)
153
+ )
154
+ end
155
+
156
+ # Merges configuration according to the rules specified in
157
+ # https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/#merging-kubeconfig-files
158
+ #
159
+ # @param other [Hash, K8s::Config]
160
+ # @return [K8s::Config]
161
+ def merge(other)
162
+ old_attributes = attributes
163
+ other_attributes = other.is_a?(Hash) ? other : other.attributes
164
+
165
+ old_attributes.merge!(other_attributes) do |key, old_value, new_value|
166
+ case key
167
+ when :clusters, :contexts, :users
168
+ old_value + new_value.reject do |new_mapping|
169
+ old_value.any? { |old_mapping| old_mapping[:name] == new_mapping[:name] }
170
+ end
171
+ else
172
+ case old_value
173
+ when Array
174
+ (old_value + new_value).uniq
175
+ when Hash
176
+ old_value.merge(new_value) do |_key, inner_old_value, inner_new_value|
177
+ inner_old_value.nil? ? inner_new_value : inner_old_value
178
+ end
179
+ when NilClass
180
+ new_value
181
+ else
182
+ old_value
183
+ end
184
+ end
185
+ end
186
+
187
+ self.class.new(old_attributes)
188
+ end
189
+
190
+ # @param name [String]
191
+ # @raise [K8s::Error::Configuration]
192
+ # @return [K8s::Config::Context]
193
+ def context(name = current_context)
194
+ found = contexts.find{ |context| context.name == name }
195
+ raise K8s::Error::Configuration, "context not found: #{name.inspect}" unless found
196
+
197
+ found.context
198
+ end
199
+
200
+ # @param name [String]
201
+ # @return [K8s::Config::Cluster]
202
+ def cluster(name = context.cluster)
203
+ clusters.find{ |cluster| cluster.name == name }.cluster
204
+ end
205
+
206
+ # @param name [String]
207
+ # @return [K8s::Config::User]
208
+ def user(name = context.user)
209
+ users.find{ |user| user.name == name }.user
210
+ end
211
+ end
212
+ end
data/lib/k8s/error.rb ADDED
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module K8s
6
+ # Top-level class for all errors raised by this gem.
7
+ class Error < StandardError
8
+ # Kube API error, related to a HTTP response with a non-2xx code
9
+ class API < Error
10
+ extend Forwardable
11
+
12
+ attr_reader :method, :path, :code, :reason, :status
13
+
14
+ # @param method [Integer] HTTP request method
15
+ # @param path [Integer] HTTP request path
16
+ # @param code [Integer] HTTP response code
17
+ # @param reason [String] HTTP response reason
18
+ # @param status [K8s::API::MetaV1::Status]
19
+ def initialize(method, path, code, reason, status = nil)
20
+ @method = method
21
+ @path = path
22
+ @code = code
23
+ @reason = reason
24
+ @status = status
25
+
26
+ if status
27
+ super("#{@method} #{@path} => HTTP #{@code} #{@reason}: #{@status.message}")
28
+ else
29
+ super("#{@method} #{@path} => HTTP #{@code} #{@reason}")
30
+ end
31
+ end
32
+ end
33
+
34
+ BadRequest = Class.new(API).freeze
35
+ Unauthorized = Class.new(API).freeze
36
+ Forbidden = Class.new(API).freeze
37
+ NotFound = Class.new(API).freeze
38
+ MethodNotAllowed = Class.new(API).freeze
39
+ Conflict = Class.new(API).freeze # XXX: also AlreadyExists?
40
+ Invalid = Class.new(API).freeze
41
+ Timeout = Class.new(API).freeze
42
+ InternalError = Class.new(API).freeze
43
+ ServiceUnavailable = Class.new(API).freeze
44
+ ServerTimeout = Class.new(API).freeze
45
+
46
+ HTTP_STATUS_ERRORS = {
47
+ 400 => BadRequest,
48
+ 401 => Unauthorized,
49
+ 403 => Forbidden,
50
+ 404 => NotFound,
51
+ 405 => MethodNotAllowed,
52
+ 409 => Conflict,
53
+ 422 => Invalid,
54
+ 429 => Timeout,
55
+ 500 => InternalError,
56
+ 503 => ServiceUnavailable,
57
+ 504 => ServerTimeout
58
+ }.freeze
59
+
60
+ # Attempt to create a ResourceClient for an unknown resource type.
61
+ # The client cannot construct the correct API URL without having the APIResource definition.
62
+ UndefinedResource = Class.new(Error)
63
+
64
+ Configuration = Class.new(Error)
65
+ end
66
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+
5
+ module K8s
6
+ # Logging utilities
7
+ #
8
+ # This provides a per-class Logger that uses the class name as a logging prefix.
9
+ # Instances can optionally also use logger! to define a per-instance Logger using a custom prefix.
10
+ module Logging
11
+ # Default log target
12
+ LOG_TARGET = $stderr
13
+
14
+ # Default log level: show warnings.
15
+ #
16
+ # Use K8s::Logging.quiet! to supress warnings.
17
+ # Note that the K8s::Transport defaults to quiet!
18
+ LOG_LEVEL = Logger::WARN
19
+
20
+ # methods defined on both the global K8s::Logging module, as well as class methods on each class including K8s::Logging
21
+ module ModuleMethods
22
+ # global log_level shared across all including classes
23
+ # @return Logger::*
24
+ def log_level
25
+ @log_level
26
+ end
27
+
28
+ # @param level Logger::*
29
+ def log_level=(level)
30
+ @log_level = level
31
+ end
32
+
33
+ # Set log_level to Logger::DEBUG
34
+ def debug!
35
+ self.log_level = Logger::DEBUG
36
+ end
37
+
38
+ # Set log_level to Logger::INFO
39
+ def verbose!
40
+ self.log_level = Logger::INFO
41
+ end
42
+
43
+ # Set log_level to Logger::ERROR, surpressing any warnings logged by default
44
+ def quiet!
45
+ self.log_level = Logger::ERROR
46
+ end
47
+ end
48
+
49
+ extend ModuleMethods # global @log_level
50
+
51
+ # methods defined on each class including K8s::Logging
52
+ module ClassMethods
53
+ # @return [Logger]
54
+ def logger(target: LOG_TARGET, level: nil)
55
+ @logger ||= Logger.new(target).tap do |logger|
56
+ logger.progname = name
57
+ logger.level = level || log_level || K8s::Logging.log_level || LOG_LEVEL
58
+ end
59
+ end
60
+ end
61
+
62
+ # extend class/intance methods for per-class logger
63
+ def self.included(base)
64
+ base.extend(ModuleMethods) # per-class @log_level
65
+ base.extend(ClassMethods)
66
+ end
67
+
68
+ # Use per-instance logger instead of the default per-class logger
69
+ #
70
+ # Sets the instance variable returned by #logger
71
+ #
72
+ # @return [Logger]
73
+ def logger!(progname: self.class.name, target: LOG_TARGET, level: nil, debug: false)
74
+ @logger = Logger.new(target).tap do |logger|
75
+ level = Logger::DEBUG if debug
76
+
77
+ logger.progname = "#{self.class.name}<#{progname}>"
78
+ logger.level = level || self.class.log_level || K8s::Logging.log_level || LOG_LEVEL
79
+ end
80
+ end
81
+
82
+ # @return [Logger]
83
+ def logger
84
+ @logger || self.class.logger
85
+ end
86
+ end
87
+ end