Module: Kubeclient::ClientMixin

Included in:
Client
Defined in:
lib/kubeclient/common.rb

Overview

Common methods this is mixed in by other gems

Constant Summary collapse

ENTITY_METHODS =
%w[get watch delete create update patch json_patch merge_patch apply].freeze
DEFAULT_SSL_OPTIONS =
{
  client_cert: nil,
  client_key:  nil,
  ca_file:     nil,
  cert_store:  nil,
  verify_ssl:  OpenSSL::SSL::VERIFY_PEER
}.freeze
DEFAULT_AUTH_OPTIONS =
{
  username:          nil,
  password:          nil,
  bearer_token:      nil,
  bearer_token_file: nil
}.freeze
DEFAULT_SOCKET_OPTIONS =
{
  socket_class:     nil,
  ssl_socket_class: nil
}.freeze
DEFAULT_TIMEOUTS =
{
  # These do NOT affect watch, watching never times out.
  open: Net::HTTP.new('127.0.0.1').open_timeout, # depends on ruby version
  read: Net::HTTP.new('127.0.0.1').read_timeout
}.freeze
DEFAULT_HTTP_PROXY_URI =
nil
DEFAULT_HTTP_MAX_REDIRECTS =
10
SEARCH_ARGUMENTS =
{
  'labelSelector'   => :label_selector,
  'fieldSelector'   => :field_selector,
  'resourceVersion' => :resource_version,
  'limit'           => :limit,
  'continue'        => :continue
}.freeze
WATCH_ARGUMENTS =
{
  'labelSelector'   => :label_selector,
  'fieldSelector'   => :field_selector,
  'resourceVersion' => :resource_version
}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_sym, *args, &block) ⇒ Object



98
99
100
101
102
103
104
105
# File 'lib/kubeclient/common.rb', line 98

def method_missing(method_sym, *args, &block)
  if discovery_needed?(method_sym)
    discover
    send(method_sym, *args, &block)
  else
    super
  end
end

Instance Attribute Details

#api_endpointObject (readonly)

Returns the value of attribute api_endpoint.



53
54
55
# File 'lib/kubeclient/common.rb', line 53

def api_endpoint
  @api_endpoint
end

#auth_optionsObject (readonly)

Returns the value of attribute auth_options.



55
56
57
# File 'lib/kubeclient/common.rb', line 55

def auth_options
  @auth_options
end

#discoveredObject (readonly)

Returns the value of attribute discovered.



59
60
61
# File 'lib/kubeclient/common.rb', line 59

def discovered
  @discovered
end

#headersObject (readonly)

Returns the value of attribute headers.



58
59
60
# File 'lib/kubeclient/common.rb', line 58

def headers
  @headers
end

#http_max_redirectsObject (readonly)

Returns the value of attribute http_max_redirects.



57
58
59
# File 'lib/kubeclient/common.rb', line 57

def http_max_redirects
  @http_max_redirects
end

#http_proxy_uriObject (readonly)

Returns the value of attribute http_proxy_uri.



56
57
58
# File 'lib/kubeclient/common.rb', line 56

def http_proxy_uri
  @http_proxy_uri
end

#ssl_optionsObject (readonly)

Returns the value of attribute ssl_options.



54
55
56
# File 'lib/kubeclient/common.rb', line 54

def ssl_options
  @ssl_options
end

Class Method Details

.parse_definition(kind, name) ⇒ Object



144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/kubeclient/common.rb', line 144

def self.parse_definition(kind, name)
  # Kubernetes gives us 3 inputs:
  #   kind: "ComponentStatus", "NetworkPolicy", "Endpoints"
  #   name: "componentstatuses", "networkpolicies", "endpoints"
  #   singularName: "componentstatus" etc (usually omitted, defaults to kind.downcase)
  # and want to derive singular and plural method names, with underscores:
  #   "network_policy"
  #   "network_policies"
  # kind's CamelCase word boundaries determine our placement of underscores.

  if IRREGULAR_NAMES[kind]
    # In a few cases, the given kind / singularName itself is still plural.
    # We require a distinct singular method name, so force it.
    method_names = IRREGULAR_NAMES[kind]
  else
    # TODO: respect singularName from discovery?
    # But how?  If it differs from kind.downcase, kind's word boundaries don't apply.
    singular_name = kind.downcase

    if !(/[A-Z]/ =~ kind)
      # Some custom resources have a fully lowercase kind - can't infer underscores.
      method_names = [singular_name, name]
    else
      # Some plurals are not exact suffixes, e.g. NetworkPolicy -> networkpolicies.
      # So don't expect full last word to match.
      /^(?<prefix>(.*[A-Z]))(?<singular_suffix>[^A-Z]*)$/ =~ kind  # "NetworkP", "olicy"
      if name.start_with?(prefix.downcase)
        plural_suffix = name[prefix.length..-1]                    # "olicies"
        prefix_underscores = ClientMixin.underscore_entity(prefix) # "network_p"
        method_names = [prefix_underscores + singular_suffix,      # "network_policy"
                        prefix_underscores + plural_suffix]        # "network_policies"
      else
        method_names = resolve_unconventional_method_names(name, kind, singular_name)
      end
    end
  end

  OpenStruct.new(
    entity_type:   kind,
    resource_name: name,
    method_names:  method_names
  )
end

.resolve_unconventional_method_names(name, kind, singular_name) ⇒ Object



188
189
190
191
192
193
194
195
196
197
# File 'lib/kubeclient/common.rb', line 188

def self.resolve_unconventional_method_names(name, kind, singular_name)
  underscored_name = name.tr('-', '_')
  singular_underscores = ClientMixin.underscore_entity(kind)
  if underscored_name.start_with?(singular_underscores)
    [singular_underscores, underscored_name]
  else
    # fallback to lowercase, no separators for both names
    [singular_name, underscored_name.tr('_', '')]
  end
end

.underscore_entity(entity_name) ⇒ Object

rubocop:enable Metrics/BlockLength



282
283
284
# File 'lib/kubeclient/common.rb', line 282

def self.underscore_entity(entity_name)
  entity_name.gsub(/([a-z])([A-Z])/, '\1_\2').downcase
end

Instance Method Details

#all_entities(options = {}) ⇒ Object



448
449
450
451
452
453
454
455
456
457
458
459
460
# File 'lib/kubeclient/common.rb', line 448

def all_entities(options = {})
  discover unless @discovered
  @entities.values.each_with_object({}) do |entity, result_hash|
    # method call for get each entities
    # build hash of entity name to array of the entities
    method_name = "get_#{entity.method_names[1]}"
    begin
      result_hash[entity.method_names[0]] = send(method_name, options)
    rescue Kubeclient::HttpError
      next # do not fail due to resources not supporting get
    end
  end
end

#apiObject



526
527
528
529
# File 'lib/kubeclient/common.rb', line 526

def api
  response = handle_exception { create_rest_client.get(get_headers) }
  JSON.parse(response)
end

#api_valid?Boolean

Returns:

  • (Boolean)


519
520
521
522
523
524
# File 'lib/kubeclient/common.rb', line 519

def api_valid?
  result = api
  result.is_a?(Hash) && (result['versions'] || []).any? do |group|
    @api_group.empty? ? group.include?(@api_version) : group['version'] == @api_version
  end
end

#apply_entity(resource_name, resource, field_manager:, force: true) ⇒ Object



435
436
437
438
439
440
441
442
443
444
445
446
# File 'lib/kubeclient/common.rb', line 435

def apply_entity(resource_name, resource, field_manager:, force: true)
  name = "#{resource[:metadata][:name]}?fieldManager=#{field_manager}&force=#{force}"
  ns_prefix = build_namespace_prefix(resource[:metadata][:namespace])
  response = handle_exception do
    rest_client[ns_prefix + resource_name + "/#{name}"]
      .patch(
        resource.to_json,
        { 'Content-Type' => 'application/apply-patch+yaml' }.merge(get_headers)
      )
  end
  format_response(@as, response.body)
end

#build_namespace_prefix(namespace) ⇒ Object



220
221
222
# File 'lib/kubeclient/common.rb', line 220

def build_namespace_prefix(namespace)
  namespace.to_s.empty? ? '' : "namespaces/#{namespace}/"
end

#create_entity(entity_type, resource_name, entity_config) ⇒ Object



394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
# File 'lib/kubeclient/common.rb', line 394

def create_entity(entity_type, resource_name, entity_config)
  # Duplicate the entity_config to a hash so that when we assign
  # kind and apiVersion, this does not mutate original entity_config obj.
  hash = entity_config.to_hash

  ns_prefix = build_namespace_prefix(hash[:metadata][:namespace])

  # TODO: temporary solution to add "kind" and apiVersion to request
  # until this issue is solved
  # https://github.com/GoogleCloudPlatform/kubernetes/issues/6439
  hash[:kind] = entity_type
  hash[:apiVersion] = @api_group + @api_version
  response = handle_exception do
    rest_client[ns_prefix + resource_name]
      .post(hash.to_json, { 'Content-Type' => 'application/json' }.merge(get_headers))
  end
  format_response(@as, response.body)
end

#create_rest_client(path = nil) ⇒ Object



286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
# File 'lib/kubeclient/common.rb', line 286

def create_rest_client(path = nil)
  path ||= @api_endpoint.path
  options = {
    ssl_ca_file: @ssl_options[:ca_file],
    ssl_cert_store: @ssl_options[:cert_store],
    verify_ssl: @ssl_options[:verify_ssl],
    ssl_client_cert: @ssl_options[:client_cert],
    ssl_client_key: @ssl_options[:client_key],
    proxy: @http_proxy_uri,
    max_redirects: @http_max_redirects,
    user: @auth_options[:username],
    password: @auth_options[:password],
    open_timeout: @timeouts[:open],
    read_timeout: @timeouts[:read]
  }
  RestClient::Resource.new(@api_endpoint.merge(path).to_s, options)
end

#define_entity_methodsObject

rubocop:disable Metrics/BlockLength



225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
# File 'lib/kubeclient/common.rb', line 225

def define_entity_methods
  @entities.values.each do |entity|
    # get all entities of a type e.g. get_nodes, get_pods, etc.
    define_singleton_method("get_#{entity.method_names[1]}") do |options = {}|
      get_entities(entity.entity_type, entity.resource_name, options)
    end

    # watch all entities of a type e.g. watch_nodes, watch_pods, etc.
    define_singleton_method("watch_#{entity.method_names[1]}") do |options = {}, &block|
      # This method used to take resource_version as a param, so
      # this conversion is to keep backwards compatibility
      options = { resource_version: options } unless options.is_a?(Hash)

      watch_entities(entity.resource_name, options, &block)
    end

    # get a single entity of a specific type by name
    define_singleton_method("get_#{entity.method_names[0]}") \
    do |name, namespace = nil, opts = {}|
      get_entity(entity.resource_name, name, namespace, opts)
    end

    define_singleton_method("delete_#{entity.method_names[0]}") \
    do |name, namespace = nil, opts = {}|
      delete_entity(entity.resource_name, name, namespace, **opts)
    end

    define_singleton_method("create_#{entity.method_names[0]}") do |entity_config|
      create_entity(entity.entity_type, entity.resource_name, entity_config)
    end

    define_singleton_method("update_#{entity.method_names[0]}") do |entity_config|
      update_entity(entity.resource_name, entity_config)
    end

    define_singleton_method("patch_#{entity.method_names[0]}") \
    do |name, patch, namespace = nil|
      patch_entity(entity.resource_name, name, patch, 'strategic-merge-patch', namespace)
    end

    define_singleton_method("json_patch_#{entity.method_names[0]}") \
    do |name, patch, namespace = nil|
      patch_entity(entity.resource_name, name, patch, 'json-patch', namespace)
    end

    define_singleton_method("merge_patch_#{entity.method_names[0]}") \
    do |name, patch, namespace = nil|
      patch_entity(entity.resource_name, name, patch, 'merge-patch', namespace)
    end

    define_singleton_method("apply_#{entity.method_names[0]}") do |resource, opts = {}|
      apply_entity(entity.resource_name, resource, **opts)
    end
  end
end

#delete_entity(resource_name, name, namespace = nil, delete_options: {}) ⇒ Object

delete_options are passed as a JSON payload in the delete request



376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
# File 'lib/kubeclient/common.rb', line 376

def delete_entity(resource_name, name, namespace = nil, delete_options: {})
  delete_options_hash = delete_options.to_hash
  ns_prefix = build_namespace_prefix(namespace)
  payload = delete_options_hash.to_json unless delete_options_hash.empty?
  response = handle_exception do
    rs = rest_client[ns_prefix + resource_name + "/#{name}"]
    RestClient::Request.execute(
      rs.options.merge(
        method: :delete,
        url: rs.url,
        headers: { 'Content-Type' => 'application/json' }.merge(get_headers),
        payload: payload
      )
    )
  end
  format_response(@as, response.body)
end

#discoverObject



133
134
135
136
137
# File 'lib/kubeclient/common.rb', line 133

def discover
  load_entities
  define_entity_methods
  @discovered = true
end

#discovery_needed?(method_sym) ⇒ Boolean

Returns:

  • (Boolean)


116
117
118
# File 'lib/kubeclient/common.rb', line 116

def discovery_needed?(method_sym)
  !@discovered && ENTITY_METHODS.any? { |x| method_sym.to_s.start_with?(x) }
end

#get_entities(entity_type, resource_name, options = {}) ⇒ Object

Accepts the following options:

:namespace (string) - the namespace of the entity.
:label_selector (string) - a selector to restrict the list of returned objects by labels.
:field_selector (string) - a selector to restrict the list of returned objects by fields.
:limit (integer) - a maximum number of items to return in each response
:continue (string) - a token used to retrieve the next chunk of entities
:as (:raw|:ros) - defaults to :ros
  :raw - return the raw response body as a string
  :ros - return a collection of RecursiveOpenStruct objects


350
351
352
353
354
355
356
357
358
359
360
# File 'lib/kubeclient/common.rb', line 350

def get_entities(entity_type, resource_name, options = {})
  params = {}
  SEARCH_ARGUMENTS.each { |k, v| params[k] = options[v] if options[v] }

  ns_prefix = build_namespace_prefix(options[:namespace])
  response = handle_exception do
    rest_client[ns_prefix + resource_name]
      .get({ 'params' => params }.merge(get_headers))
  end
  format_response(options[:as] || @as, response.body, entity_type)
end

#get_entity(resource_name, name, namespace = nil, options = {}) ⇒ Object

Accepts the following options:

:as (:raw|:ros) - defaults to :ros
  :raw - return the raw response body as a string
  :ros - return a collection of RecursiveOpenStruct objects


366
367
368
369
370
371
372
373
# File 'lib/kubeclient/common.rb', line 366

def get_entity(resource_name, name, namespace = nil, options = {})
  ns_prefix = build_namespace_prefix(namespace)
  response = handle_exception do
    rest_client[ns_prefix + resource_name + "/#{name}"]
      .get(get_headers)
  end
  format_response(options[:as] || @as, response.body)
end

#get_headersObject



139
140
141
142
# File 'lib/kubeclient/common.rb', line 139

def get_headers
  bearer_token(File.read(@auth_options[:bearer_token_file])) if @auth_options[:bearer_token_file]
  @headers
end

#get_pod_log(pod_name, namespace, container: nil, previous: false, timestamps: false, since_time: nil, tail_lines: nil, limit_bytes: nil) ⇒ Object



462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
# File 'lib/kubeclient/common.rb', line 462

def get_pod_log(pod_name, namespace,
                container: nil, previous: false,
                timestamps: false, since_time: nil, tail_lines: nil, limit_bytes: nil)
  params = {}
  params[:previous] = true if previous
  params[:container] = container if container
  params[:timestamps] = timestamps if timestamps
  params[:sinceTime] = format_datetime(since_time) if since_time
  params[:tailLines] = tail_lines if tail_lines
  params[:limitBytes] = limit_bytes if limit_bytes

  ns = build_namespace_prefix(namespace)
  handle_exception do
    rest_client[ns + "pods/#{pod_name}/log"]
      .get({ 'params' => params }.merge(get_headers))
  end
end

#handle_exceptionObject



120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/kubeclient/common.rb', line 120

def handle_exception
  yield
rescue RestClient::Exception => e
  json_error_msg = begin
    JSON.parse(e.response || '') || {}
  rescue JSON::ParserError
    {}
  end
  err_message = json_error_msg['message'] || e.message
  error_klass = e.http_code == 404 ? ResourceNotFoundError : HttpError
  raise error_klass.new(e.http_code, err_message, e.response)
end

#handle_uri(uri, path) ⇒ Object

Raises:

  • (ArgumentError)


199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/kubeclient/common.rb', line 199

def handle_uri(uri, path)
  raise ArgumentError, 'Missing uri' unless uri
  @api_endpoint = (uri.is_a?(URI) ? uri : URI.parse(uri))

  # This regex will anchor at the last `/api`, `/oapi` or`/apis/:group`) part of the URL
  # The whole path will be matched and if existing, the api_group will be extracted.
  re = /^(?<path>.*\/o?api(?:s\/(?<apigroup>[^\/]+))?)$/mi
  match = re.match(@api_endpoint.path.chomp('/'))

  if match
    # Since `re` captures 2 groups, match will always have 3 elements
    # If thus we have a non-nil value in match 2, this is our api_group.
    @api_group = match[:apigroup].nil? ? '' : match[:apigroup] + '/'
    @api_endpoint.path = match[:path]
  else
    # This is a fallback, for when `/api` was not provided as part of the uri
    @api_group = ''
    @api_endpoint.path = @api_endpoint.path.chomp('/') + path
  end
end

#initialize_client(uri, path, version, ssl_options: DEFAULT_SSL_OPTIONS, auth_options: DEFAULT_AUTH_OPTIONS, socket_options: DEFAULT_SOCKET_OPTIONS, timeouts: DEFAULT_TIMEOUTS, http_proxy_uri: DEFAULT_HTTP_PROXY_URI, http_max_redirects: DEFAULT_HTTP_MAX_REDIRECTS, as: :ros) ⇒ Object



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/kubeclient/common.rb', line 61

def initialize_client(
  uri,
  path,
  version,
  ssl_options: DEFAULT_SSL_OPTIONS,
  auth_options: DEFAULT_AUTH_OPTIONS,
  socket_options: DEFAULT_SOCKET_OPTIONS,
  timeouts: DEFAULT_TIMEOUTS,
  http_proxy_uri: DEFAULT_HTTP_PROXY_URI,
  http_max_redirects: DEFAULT_HTTP_MAX_REDIRECTS,
  as: :ros
)
  validate_auth_options(auth_options)
  handle_uri(uri, path)

  @entities = {}
  @discovered = false
  @api_version = version
  @headers = {}
  @ssl_options = ssl_options
  @auth_options = auth_options.dup
  @socket_options = socket_options
  # Allow passing partial timeouts hash, without unspecified
  # @timeouts[:foo] == nil resulting in infinite timeout.
  @timeouts = DEFAULT_TIMEOUTS.merge(timeouts)
  @http_proxy_uri = http_proxy_uri ? http_proxy_uri.to_s : nil
  @http_max_redirects = http_max_redirects
  @as = as

  if auth_options[:bearer_token_file]
    validate_bearer_token_file
    bearer_token(File.read(@auth_options[:bearer_token_file]))
  elsif auth_options[:bearer_token]
    bearer_token(@auth_options[:bearer_token])
  end
end

#patch_entity(resource_name, name, patch, strategy, namespace) ⇒ Object



423
424
425
426
427
428
429
430
431
432
433
# File 'lib/kubeclient/common.rb', line 423

def patch_entity(resource_name, name, patch, strategy, namespace)
  ns_prefix = build_namespace_prefix(namespace)
  response = handle_exception do
    rest_client[ns_prefix + resource_name + "/#{name}"]
      .patch(
        patch.to_json,
        { 'Content-Type' => "application/#{strategy}+json" }.merge(get_headers)
      )
  end
  format_response(@as, response.body)
end

#process_template(template) ⇒ Object



510
511
512
513
514
515
516
517
# File 'lib/kubeclient/common.rb', line 510

def process_template(template)
  ns_prefix = build_namespace_prefix(template[:metadata][:namespace])
  response = handle_exception do
    rest_client[ns_prefix + 'processedtemplates']
      .post(template.to_h.to_json, { 'Content-Type' => 'application/json' }.merge(get_headers))
  end
  JSON.parse(response)
end

#proxy_url(kind, name, port, namespace = '') ⇒ Object



498
499
500
501
502
503
504
505
506
507
508
# File 'lib/kubeclient/common.rb', line 498

def proxy_url(kind, name, port, namespace = '')
  discover unless @discovered
  entity_name_plural =
    if %w[services pods nodes].include?(kind.to_s)
      kind.to_s
    else
      @entities[kind.to_s].resource_name
    end
  ns_prefix = build_namespace_prefix(namespace)
  rest_client["#{ns_prefix}#{entity_name_plural}/#{name}:#{port}/proxy"].url
end

#respond_to_missing?(method_sym, include_private = false) ⇒ Boolean

Returns:

  • (Boolean)


107
108
109
110
111
112
113
114
# File 'lib/kubeclient/common.rb', line 107

def respond_to_missing?(method_sym, include_private = false)
  if discovery_needed?(method_sym)
    discover
    respond_to?(method_sym, include_private)
  else
    super
  end
end

#rest_clientObject



304
305
306
307
308
# File 'lib/kubeclient/common.rb', line 304

def rest_client
  @rest_client ||= begin
    create_rest_client("#{@api_endpoint.path}/#{@api_version}")
  end
end

#update_entity(resource_name, entity_config) ⇒ Object



413
414
415
416
417
418
419
420
421
# File 'lib/kubeclient/common.rb', line 413

def update_entity(resource_name, entity_config)
  name      = entity_config[:metadata][:name]
  ns_prefix = build_namespace_prefix(entity_config[:metadata][:namespace])
  response = handle_exception do
    rest_client[ns_prefix + resource_name + "/#{name}"]
      .put(entity_config.to_h.to_json, { 'Content-Type' => 'application/json' }.merge(get_headers))
  end
  format_response(@as, response.body)
end

#watch_entities(resource_name, options = {}, &block) ⇒ Object

Accepts the following options:

:namespace (string) - the namespace of the entity.
:name (string) - the name of the entity to watch.
:label_selector (string) - a selector to restrict the list of returned objects by labels.
:field_selector (string) - a selector to restrict the list of returned objects by fields.
:resource_version (string) - shows changes that occur after passed version of a resource.
:as (:raw|:ros) - defaults to :ros
  :raw - return the raw response body as a string
  :ros - return a collection of RecursiveOpenStruct objects

Accepts an optional block, that will be called with each entity, otherwise returns a WatchStream



321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
# File 'lib/kubeclient/common.rb', line 321

def watch_entities(resource_name, options = {}, &block)
  ns = build_namespace_prefix(options[:namespace])

  path = "watch/#{ns}#{resource_name}"
  path += "/#{options[:name]}" if options[:name]
  uri = @api_endpoint.merge("#{@api_endpoint.path}/#{@api_version}/#{path}")

  params = {}
  WATCH_ARGUMENTS.each { |k, v| params[k] = options[v] if options[v] }
  uri.query = URI.encode_www_form(params) if params.any?

  watcher = Kubeclient::Common::WatchStream.new(
    uri,
    http_options(uri),
    formatter: ->(value) { format_response(options[:as] || @as, value) }
  )

  return_or_yield_to_watcher(watcher, &block)
end

#watch_pod_log(pod_name, namespace, container: nil, &block) ⇒ Object



480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
# File 'lib/kubeclient/common.rb', line 480

def watch_pod_log(pod_name, namespace, container: nil, &block)
  # Adding the "follow=true" query param tells the Kubernetes API to keep
  # the connection open and stream updates to the log.
  params = { follow: true }
  params[:container] = container if container

  ns = build_namespace_prefix(namespace)

  uri = @api_endpoint.dup
  uri.path += "/#{@api_version}/#{ns}pods/#{pod_name}/log"
  uri.query = URI.encode_www_form(params)

  watcher = Kubeclient::Common::WatchStream.new(
    uri, http_options(uri), formatter: ->(value) { value }
  )
  return_or_yield_to_watcher(watcher, &block)
end