Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/gems/kubeclient/lib')
-rw-r--r--vendor/gems/kubeclient/lib/kubeclient.rb35
-rw-r--r--vendor/gems/kubeclient/lib/kubeclient/aws_eks_credentials.rb46
-rw-r--r--vendor/gems/kubeclient/lib/kubeclient/common.rb661
-rw-r--r--vendor/gems/kubeclient/lib/kubeclient/config.rb202
-rw-r--r--vendor/gems/kubeclient/lib/kubeclient/entity_list.rb21
-rw-r--r--vendor/gems/kubeclient/lib/kubeclient/exec_credentials.rb89
-rw-r--r--vendor/gems/kubeclient/lib/kubeclient/gcp_auth_provider.rb19
-rw-r--r--vendor/gems/kubeclient/lib/kubeclient/gcp_command_credentials.rb31
-rw-r--r--vendor/gems/kubeclient/lib/kubeclient/google_application_default_credentials.rb31
-rw-r--r--vendor/gems/kubeclient/lib/kubeclient/http_error.rb25
-rw-r--r--vendor/gems/kubeclient/lib/kubeclient/missing_kind_compatibility.rb68
-rw-r--r--vendor/gems/kubeclient/lib/kubeclient/oidc_auth_provider.rb52
-rw-r--r--vendor/gems/kubeclient/lib/kubeclient/resource.rb11
-rw-r--r--vendor/gems/kubeclient/lib/kubeclient/resource_not_found_error.rb4
-rw-r--r--vendor/gems/kubeclient/lib/kubeclient/version.rb4
-rw-r--r--vendor/gems/kubeclient/lib/kubeclient/watch_stream.rb97
16 files changed, 1396 insertions, 0 deletions
diff --git a/vendor/gems/kubeclient/lib/kubeclient.rb b/vendor/gems/kubeclient/lib/kubeclient.rb
new file mode 100644
index 00000000000..eed4872834e
--- /dev/null
+++ b/vendor/gems/kubeclient/lib/kubeclient.rb
@@ -0,0 +1,35 @@
+require 'json'
+require 'rest-client'
+
+require 'kubeclient/aws_eks_credentials'
+require 'kubeclient/common'
+require 'kubeclient/config'
+require 'kubeclient/entity_list'
+require 'kubeclient/exec_credentials'
+require 'kubeclient/gcp_auth_provider'
+require 'kubeclient/http_error'
+require 'kubeclient/missing_kind_compatibility'
+require 'kubeclient/oidc_auth_provider'
+require 'kubeclient/resource'
+require 'kubeclient/resource_not_found_error'
+require 'kubeclient/version'
+require 'kubeclient/watch_stream'
+
+module Kubeclient
+ # Kubernetes Client
+ class Client
+ include ClientMixin
+ def initialize(
+ uri,
+ version = 'v1',
+ **options
+ )
+ initialize_client(
+ uri,
+ '/api',
+ version,
+ **options
+ )
+ end
+ end
+end
diff --git a/vendor/gems/kubeclient/lib/kubeclient/aws_eks_credentials.rb b/vendor/gems/kubeclient/lib/kubeclient/aws_eks_credentials.rb
new file mode 100644
index 00000000000..9b54b9e06cc
--- /dev/null
+++ b/vendor/gems/kubeclient/lib/kubeclient/aws_eks_credentials.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+module Kubeclient
+ # Get a bearer token to authenticate against aws eks.
+ class AmazonEksCredentials
+ class AmazonEksDependencyError < LoadError # rubocop:disable Lint/InheritException
+ end
+
+ class << self
+ def token(credentials, eks_cluster)
+ begin
+ require 'aws-sigv4'
+ require 'base64'
+ require 'cgi'
+ rescue LoadError => e
+ raise AmazonEksDependencyError,
+ 'Error requiring aws gems. Kubeclient itself does not include the following ' \
+ 'gems: [aws-sigv4]. To support auth-provider eks, you must ' \
+ "include it in your calling application. Failed with: #{e.message}"
+ end
+ # https://github.com/aws/aws-sdk-ruby/pull/1848
+ # Get a signer
+ # Note - sts only has ONE endpoint (not regional) so 'us-east-1' hardcoding should be OK
+ signer = Aws::Sigv4::Signer.new(
+ service: 'sts',
+ region: 'us-east-1',
+ credentials: credentials
+ )
+
+ # https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/Sigv4/Signer.html#presign_url-instance_method
+ presigned_url_string = signer.presign_url(
+ http_method: 'GET',
+ url: 'https://sts.amazonaws.com/?Action=GetCallerIdentity&Version=2011-06-15',
+ body: '',
+ credentials: credentials,
+ expires_in: 60,
+ headers: {
+ 'X-K8s-Aws-Id' => eks_cluster
+ }
+ )
+ kube_token = 'k8s-aws-v1.' + Base64.urlsafe_encode64(presigned_url_string.to_s).sub(/=*$/, '') # rubocop:disable Metrics/LineLength
+ kube_token
+ end
+ end
+ end
+end
diff --git a/vendor/gems/kubeclient/lib/kubeclient/common.rb b/vendor/gems/kubeclient/lib/kubeclient/common.rb
new file mode 100644
index 00000000000..51087fbe888
--- /dev/null
+++ b/vendor/gems/kubeclient/lib/kubeclient/common.rb
@@ -0,0 +1,661 @@
+require 'json'
+require 'rest-client'
+
+module Kubeclient
+ # Common methods
+ # this is mixed in by other gems
+ module ClientMixin
+ 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
+
+ attr_reader :api_endpoint
+ attr_reader :ssl_options
+ attr_reader :auth_options
+ attr_reader :http_proxy_uri
+ attr_reader :http_max_redirects
+ attr_reader :headers
+ attr_reader :discovered
+
+ 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
+ @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]
+ bearer_token(@auth_options[:bearer_token])
+ elsif auth_options[:bearer_token_file]
+ validate_bearer_token_file
+ bearer_token(File.read(@auth_options[:bearer_token_file]))
+ end
+ end
+
+ def method_missing(method_sym, *args, &block)
+ if discovery_needed?(method_sym)
+ discover
+ send(method_sym, *args, &block)
+ else
+ super
+ end
+ end
+
+ 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
+
+ def discovery_needed?(method_sym)
+ !@discovered && ENTITY_METHODS.any? { |x| method_sym.to_s.start_with?(x) }
+ end
+
+ 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
+
+ def discover
+ load_entities
+ define_entity_methods
+ @discovered = true
+ end
+
+ 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
+
+ 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
+
+ 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
+
+ def build_namespace_prefix(namespace)
+ namespace.to_s.empty? ? '' : "namespaces/#{namespace}/"
+ end
+
+ # rubocop:disable Metrics/BlockLength
+ 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
+ # rubocop:enable Metrics/BlockLength
+
+ def self.underscore_entity(entity_name)
+ entity_name.gsub(/([a-z])([A-Z])/, '\1_\2').downcase
+ end
+
+ 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
+
+ def rest_client
+ @rest_client ||= begin
+ create_rest_client("#{@api_endpoint.path}/#{@api_version}")
+ end
+ end
+
+ # 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
+ 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
+
+ # 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
+ 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(@headers))
+ end
+ format_response(options[:as] || @as, response.body, entity_type)
+ end
+
+ # 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
+ 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(@headers)
+ end
+ format_response(options[:as] || @as, response.body)
+ end
+
+ # delete_options are passed as a JSON payload in the delete request
+ 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(@headers),
+ payload: payload
+ )
+ )
+ end
+ format_response(@as, response.body)
+ end
+
+ 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(@headers))
+ end
+ format_response(@as, response.body)
+ end
+
+ 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(@headers))
+ end
+ format_response(@as, response.body)
+ end
+
+ 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(@headers)
+ )
+ end
+ format_response(@as, response.body)
+ end
+
+ 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(@headers)
+ )
+ end
+ format_response(@as, response.body)
+ end
+
+ 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
+
+ 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(@headers))
+ end
+ end
+
+ 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
+
+ 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
+
+ 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(@headers))
+ end
+ JSON.parse(response)
+ end
+
+ 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
+
+ def api
+ response = handle_exception { create_rest_client.get(@headers) }
+ JSON.parse(response)
+ end
+
+ private
+
+ IRREGULAR_NAMES = {
+ # In a few cases, the given kind itself is still plural.
+ # https://github.com/kubernetes/kubernetes/issues/8115
+ 'Endpoints' => %w[endpoint endpoints],
+ 'SecurityContextConstraints' => %w[security_context_constraint
+ security_context_constraints]
+ }.freeze
+
+ # Format datetime according to RFC3339
+ def format_datetime(value)
+ case value
+ when DateTime, Time
+ value.strftime('%FT%T.%9N%:z')
+ when String
+ value
+ else
+ raise ArgumentError, "unsupported type '#{value.class}' of time value '#{value}'"
+ end
+ end
+
+ def format_response(as, body, list_type = nil)
+ case as
+ when :raw
+ body
+ when :parsed
+ JSON.parse(body)
+ when :parsed_symbolized
+ JSON.parse(body, symbolize_names: true)
+ when :ros
+ result = JSON.parse(body)
+
+ if list_type
+ resource_version =
+ result.fetch('resourceVersion') do
+ result.fetch('metadata', {}).fetch('resourceVersion', nil)
+ end
+
+ # If 'limit' was passed save the continue token
+ # see https://kubernetes.io/docs/reference/using-api/api-concepts/#retrieving-large-results-sets-in-chunks
+ continue = result.fetch('metadata', {}).fetch('continue', nil)
+
+ # result['items'] might be nil due to https://github.com/kubernetes/kubernetes/issues/13096
+ collection = result['items'].to_a.map { |item| Kubeclient::Resource.new(item) }
+
+ Kubeclient::Common::EntityList.new(list_type, resource_version, collection, continue)
+ else
+ Kubeclient::Resource.new(result)
+ end
+ else
+ raise ArgumentError, "Unsupported format #{as.inspect}"
+ end
+ end
+
+ def load_entities
+ @entities = {}
+ fetch_entities['resources'].each do |resource|
+ next if resource['name'].include?('/')
+ # Not a regular entity, special functionality covered by `process_template`.
+ # https://github.com/openshift/origin/issues/21668
+ next if resource['kind'] == 'Template' && resource['name'] == 'processedtemplates'
+ resource['kind'] ||=
+ Kubeclient::Common::MissingKindCompatibility.resource_kind(resource['name'])
+ entity = ClientMixin.parse_definition(resource['kind'], resource['name'])
+ @entities[entity.method_names[0]] = entity if entity
+ end
+ end
+
+ def fetch_entities
+ JSON.parse(handle_exception { rest_client.get(@headers) })
+ end
+
+ def bearer_token(bearer_token)
+ @headers ||= {}
+ @headers[:Authorization] = "Bearer #{bearer_token}"
+ end
+
+ def validate_auth_options(opts)
+ # maintain backward compatibility:
+ opts[:username] = opts[:user] if opts[:user]
+
+ if %i[bearer_token bearer_token_file username].count { |key| opts[key] } > 1
+ raise(
+ ArgumentError,
+ 'Invalid auth options: specify only one of username/password,' \
+ ' bearer_token or bearer_token_file'
+ )
+ elsif %i[username password].count { |key| opts[key] } == 1
+ raise ArgumentError, 'Basic auth requires both username & password'
+ end
+ end
+
+ def validate_bearer_token_file
+ msg = "Token file #{@auth_options[:bearer_token_file]} does not exist"
+ raise ArgumentError, msg unless File.file?(@auth_options[:bearer_token_file])
+
+ msg = "Cannot read token file #{@auth_options[:bearer_token_file]}"
+ raise ArgumentError, msg unless File.readable?(@auth_options[:bearer_token_file])
+ end
+
+ def return_or_yield_to_watcher(watcher, &block)
+ return watcher unless block_given?
+
+ begin
+ watcher.each(&block)
+ ensure
+ watcher.finish
+ end
+ end
+
+ def http_options(uri)
+ options = {
+ basic_auth_user: @auth_options[:username],
+ basic_auth_password: @auth_options[:password],
+ headers: @headers,
+ http_proxy_uri: @http_proxy_uri,
+ http_max_redirects: http_max_redirects
+ }
+
+ if uri.scheme == 'https'
+ options[:ssl] = {
+ ca_file: @ssl_options[:ca_file],
+ cert: @ssl_options[:client_cert],
+ cert_store: @ssl_options[:cert_store],
+ key: @ssl_options[:client_key],
+ # ruby HTTP uses verify_mode instead of verify_ssl
+ # http://ruby-doc.org/stdlib-1.9.3/libdoc/openssl/rdoc/OpenSSL/SSL/SSLContext.html
+ verify_mode: @ssl_options[:verify_ssl]
+ }
+ end
+
+ options.merge(@socket_options)
+ end
+ end
+end
diff --git a/vendor/gems/kubeclient/lib/kubeclient/config.rb b/vendor/gems/kubeclient/lib/kubeclient/config.rb
new file mode 100644
index 00000000000..3598afe83fe
--- /dev/null
+++ b/vendor/gems/kubeclient/lib/kubeclient/config.rb
@@ -0,0 +1,202 @@
+require 'yaml'
+require 'base64'
+require 'pathname'
+
+module Kubeclient
+ # Kubernetes client configuration class
+ class Config
+ # Kubernetes client configuration context class
+ class Context
+ attr_reader :api_endpoint, :api_version, :ssl_options, :auth_options, :namespace
+
+ def initialize(api_endpoint, api_version, ssl_options, auth_options, namespace)
+ @api_endpoint = api_endpoint
+ @api_version = api_version
+ @ssl_options = ssl_options
+ @auth_options = auth_options
+ @namespace = namespace
+ end
+ end
+
+ # data (Hash) - Parsed kubeconfig data.
+ # kcfg_path (string) - Base directory for resolving relative references to external files.
+ # If set to nil, all external lookups & commands are disabled (even for absolute paths).
+ # See also the more convenient Config.read
+ def initialize(data, kcfg_path)
+ @kcfg = data
+ @kcfg_path = kcfg_path
+ raise 'Unknown kubeconfig version' if @kcfg['apiVersion'] != 'v1'
+ end
+
+ # Builds Config instance by parsing given file, with lookups relative to file's directory.
+ def self.read(filename)
+ parsed =
+ if RUBY_VERSION >= '2.6'
+ YAML.safe_load(File.read(filename), permitted_classes: [Date, Time])
+ else
+ YAML.safe_load(File.read(filename), [Date, Time])
+ end
+ Config.new(parsed, File.dirname(filename))
+ end
+
+ def contexts
+ @kcfg['contexts'].map { |x| x['name'] }
+ end
+
+ def context(context_name = nil)
+ cluster, user, namespace = fetch_context(context_name || @kcfg['current-context'])
+
+ if user.key?('exec')
+ exec_opts = expand_command_option(user['exec'], 'command')
+ user['exec_result'] = ExecCredentials.run(exec_opts)
+ end
+
+ client_cert_data = fetch_user_cert_data(user)
+ client_key_data = fetch_user_key_data(user)
+ auth_options = fetch_user_auth_options(user)
+
+ ssl_options = {}
+
+ ssl_options[:verify_ssl] = if cluster['insecure-skip-tls-verify'] == true
+ OpenSSL::SSL::VERIFY_NONE
+ else
+ OpenSSL::SSL::VERIFY_PEER
+ end
+
+ if cluster_ca_data?(cluster)
+ cert_store = OpenSSL::X509::Store.new
+ populate_cert_store_from_cluster_ca_data(cluster, cert_store)
+ ssl_options[:cert_store] = cert_store
+ end
+
+ unless client_cert_data.nil?
+ ssl_options[:client_cert] = OpenSSL::X509::Certificate.new(client_cert_data)
+ end
+
+ unless client_key_data.nil?
+ ssl_options[:client_key] = OpenSSL::PKey.read(client_key_data)
+ end
+
+ Context.new(cluster['server'], @kcfg['apiVersion'], ssl_options, auth_options, namespace)
+ end
+
+ private
+
+ def allow_external_lookups?
+ @kcfg_path != nil
+ end
+
+ def ext_file_path(path)
+ unless allow_external_lookups?
+ raise "Kubeclient::Config: external lookups disabled, can't load '#{path}'"
+ end
+ Pathname(path).absolute? ? path : File.join(@kcfg_path, path)
+ end
+
+ def ext_command_path(path)
+ unless allow_external_lookups?
+ raise "Kubeclient::Config: external lookups disabled, can't execute '#{path}'"
+ end
+ # Like go client https://github.com/kubernetes/kubernetes/pull/59495#discussion_r171138995,
+ # distinguish 3 cases:
+ # - absolute (e.g. /path/to/foo)
+ # - $PATH-based (e.g. curl)
+ # - relative to config file's dir (e.g. ./foo)
+ if Pathname(path).absolute?
+ path
+ elsif File.basename(path) == path
+ path
+ else
+ File.join(@kcfg_path, path)
+ end
+ end
+
+ def fetch_context(context_name)
+ context = @kcfg['contexts'].detect do |x|
+ break x['context'] if x['name'] == context_name
+ end
+
+ raise KeyError, "Unknown context #{context_name}" unless context
+
+ cluster = @kcfg['clusters'].detect do |x|
+ break x['cluster'] if x['name'] == context['cluster']
+ end
+
+ raise KeyError, "Unknown cluster #{context['cluster']}" unless cluster
+
+ user = @kcfg['users'].detect do |x|
+ break x['user'] if x['name'] == context['user']
+ end || {}
+
+ namespace = context['namespace']
+
+ [cluster, user, namespace]
+ end
+
+ def cluster_ca_data?(cluster)
+ cluster.key?('certificate-authority') || cluster.key?('certificate-authority-data')
+ end
+
+ def populate_cert_store_from_cluster_ca_data(cluster, cert_store)
+ if cluster.key?('certificate-authority')
+ cert_store.add_file(ext_file_path(cluster['certificate-authority']))
+ elsif cluster.key?('certificate-authority-data')
+ ca_cert_data = Base64.decode64(cluster['certificate-authority-data'])
+ cert_store.add_cert(OpenSSL::X509::Certificate.new(ca_cert_data))
+ end
+ end
+
+ def fetch_user_cert_data(user)
+ if user.key?('client-certificate')
+ File.read(ext_file_path(user['client-certificate']))
+ elsif user.key?('client-certificate-data')
+ Base64.decode64(user['client-certificate-data'])
+ elsif user.key?('exec_result') && user['exec_result'].key?('clientCertificateData')
+ user['exec_result']['clientCertificateData']
+ end
+ end
+
+ def fetch_user_key_data(user)
+ if user.key?('client-key')
+ File.read(ext_file_path(user['client-key']))
+ elsif user.key?('client-key-data')
+ Base64.decode64(user['client-key-data'])
+ elsif user.key?('exec_result') && user['exec_result'].key?('clientKeyData')
+ user['exec_result']['clientKeyData']
+ end
+ end
+
+ def fetch_user_auth_options(user)
+ options = {}
+ if user.key?('token')
+ options[:bearer_token] = user['token']
+ elsif user.key?('exec_result') && user['exec_result'].key?('token')
+ options[:bearer_token] = user['exec_result']['token']
+ elsif user.key?('auth-provider')
+ options[:bearer_token] = fetch_token_from_provider(user['auth-provider'])
+ else
+ %w[username password].each do |attr|
+ options[attr.to_sym] = user[attr] if user.key?(attr)
+ end
+ end
+ options
+ end
+
+ def fetch_token_from_provider(auth_provider)
+ case auth_provider['name']
+ when 'gcp'
+ config = expand_command_option(auth_provider['config'], 'cmd-path')
+ Kubeclient::GCPAuthProvider.token(config)
+ when 'oidc'
+ Kubeclient::OIDCAuthProvider.token(auth_provider['config'])
+ end
+ end
+
+ def expand_command_option(config, key)
+ config = config.dup
+ config[key] = ext_command_path(config[key]) if config[key]
+
+ config
+ end
+ end
+end
diff --git a/vendor/gems/kubeclient/lib/kubeclient/entity_list.rb b/vendor/gems/kubeclient/lib/kubeclient/entity_list.rb
new file mode 100644
index 00000000000..2f734560a4b
--- /dev/null
+++ b/vendor/gems/kubeclient/lib/kubeclient/entity_list.rb
@@ -0,0 +1,21 @@
+require 'delegate'
+module Kubeclient
+ module Common
+ # Kubernetes Entity List
+ class EntityList < DelegateClass(Array)
+ attr_reader :continue, :kind, :resourceVersion
+
+ def initialize(kind, resource_version, list, continue = nil)
+ @kind = kind
+ # rubocop:disable Style/VariableName
+ @resourceVersion = resource_version
+ @continue = continue
+ super(list)
+ end
+
+ def last?
+ continue.nil?
+ end
+ end
+ end
+end
diff --git a/vendor/gems/kubeclient/lib/kubeclient/exec_credentials.rb b/vendor/gems/kubeclient/lib/kubeclient/exec_credentials.rb
new file mode 100644
index 00000000000..016d48ae289
--- /dev/null
+++ b/vendor/gems/kubeclient/lib/kubeclient/exec_credentials.rb
@@ -0,0 +1,89 @@
+# frozen_string_literal: true
+
+module Kubeclient
+ # An exec-based client auth provide
+ # https://kubernetes.io/docs/reference/access-authn-authz/authentication/#configuration
+ # Inspired by https://github.com/kubernetes/client-go/blob/master/plugin/pkg/client/auth/exec/exec.go
+ class ExecCredentials
+ class << self
+ def run(opts)
+ require 'open3'
+ require 'json'
+
+ raise ArgumentError, 'exec options are required' if opts.nil?
+
+ cmd = opts['command']
+ args = opts['args']
+ env = map_env(opts['env'])
+
+ # Validate exec options
+ validate_opts(opts)
+
+ out, err, st = Open3.capture3(env, cmd, *args)
+
+ raise "exec command failed: #{err}" unless st.success?
+
+ creds = JSON.parse(out)
+ validate_credentials(opts, creds)
+ creds['status']
+ end
+
+ private
+
+ def validate_opts(opts)
+ raise KeyError, 'exec command is required' unless opts['command']
+ end
+
+ def validate_client_credentials_status(status)
+ has_client_cert_data = status.key?('clientCertificateData')
+ has_client_key_data = status.key?('clientKeyData')
+
+ if has_client_cert_data && !has_client_key_data
+ raise 'exec plugin didn\'t return client key data'
+ end
+
+ if !has_client_cert_data && has_client_key_data
+ raise 'exec plugin didn\'t return client certificate data'
+ end
+
+ has_client_cert_data && has_client_key_data
+ end
+
+ def validate_credentials_status(status)
+ raise 'exec plugin didn\'t return a status field' if status.nil?
+
+ has_client_credentials = validate_client_credentials_status(status)
+ has_token = status.key?('token')
+
+ if has_client_credentials && has_token
+ raise 'exec plugin returned both token and client data'
+ end
+
+ return if has_client_credentials || has_token
+
+ raise 'exec plugin didn\'t return a token or client data' unless has_token
+ end
+
+ def validate_credentials(opts, creds)
+ # out should have ExecCredential structure
+ raise 'invalid credentials' if creds.nil?
+
+ # Verify apiVersion?
+ api_version = opts['apiVersion']
+ if api_version && api_version != creds['apiVersion']
+ raise "exec plugin is configured to use API version #{api_version}, " \
+ "plugin returned version #{creds['apiVersion']}"
+ end
+
+ validate_credentials_status(creds['status'])
+ end
+
+ # Transform name/value pairs to hash
+ def map_env(env)
+ return {} unless env
+
+ Hash[env.map { |e| [e['name'], e['value']] }]
+ end
+ end
+ end
+end
diff --git a/vendor/gems/kubeclient/lib/kubeclient/gcp_auth_provider.rb b/vendor/gems/kubeclient/lib/kubeclient/gcp_auth_provider.rb
new file mode 100644
index 00000000000..b28e54bfd88
--- /dev/null
+++ b/vendor/gems/kubeclient/lib/kubeclient/gcp_auth_provider.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require 'kubeclient/google_application_default_credentials'
+require 'kubeclient/gcp_command_credentials'
+
+module Kubeclient
+ # Handle different ways to get a bearer token for Google Cloud Platform.
+ class GCPAuthProvider
+ class << self
+ def token(config)
+ if config.key?('cmd-path')
+ Kubeclient::GCPCommandCredentials.token(config)
+ else
+ Kubeclient::GoogleApplicationDefaultCredentials.token
+ end
+ end
+ end
+ end
+end
diff --git a/vendor/gems/kubeclient/lib/kubeclient/gcp_command_credentials.rb b/vendor/gems/kubeclient/lib/kubeclient/gcp_command_credentials.rb
new file mode 100644
index 00000000000..9c68c1a2847
--- /dev/null
+++ b/vendor/gems/kubeclient/lib/kubeclient/gcp_command_credentials.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module Kubeclient
+ # Generates a bearer token for Google Cloud Platform.
+ class GCPCommandCredentials
+ class << self
+ def token(config)
+ require 'open3'
+ require 'shellwords'
+ require 'json'
+ require 'jsonpath'
+
+ cmd = config['cmd-path']
+ args = config['cmd-args']
+ token_key = config['token-key']
+
+ out, err, st = Open3.capture3(cmd, *args.split(' '))
+
+ raise "exec command failed: #{err}" unless st.success?
+
+ extract_token(out, token_key)
+ end
+
+ private
+
+ def extract_token(output, token_key)
+ JsonPath.on(output, token_key.gsub(/^{|}$/, '')).first
+ end
+ end
+ end
+end
diff --git a/vendor/gems/kubeclient/lib/kubeclient/google_application_default_credentials.rb b/vendor/gems/kubeclient/lib/kubeclient/google_application_default_credentials.rb
new file mode 100644
index 00000000000..78f99ec9f32
--- /dev/null
+++ b/vendor/gems/kubeclient/lib/kubeclient/google_application_default_credentials.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module Kubeclient
+ # Get a bearer token from the Google's application default credentials.
+ class GoogleApplicationDefaultCredentials
+ class GoogleDependencyError < LoadError # rubocop:disable Lint/InheritException
+ end
+
+ class << self
+ def token
+ begin
+ require 'googleauth'
+ rescue LoadError => e
+ raise GoogleDependencyError,
+ 'Error requiring googleauth gem. Kubeclient itself does not include the ' \
+ 'googleauth gem. To support auth-provider gcp, you must include it in your ' \
+ "calling application. Failed with: #{e.message}"
+ end
+
+ scopes = [
+ 'https://www.googleapis.com/auth/cloud-platform',
+ 'https://www.googleapis.com/auth/userinfo.email'
+ ]
+
+ authorization = Google::Auth.get_application_default(scopes)
+ authorization.apply({})
+ authorization.access_token
+ end
+ end
+ end
+end
diff --git a/vendor/gems/kubeclient/lib/kubeclient/http_error.rb b/vendor/gems/kubeclient/lib/kubeclient/http_error.rb
new file mode 100644
index 00000000000..121368c2f17
--- /dev/null
+++ b/vendor/gems/kubeclient/lib/kubeclient/http_error.rb
@@ -0,0 +1,25 @@
+# TODO: remove this on next major version bump
+# Deprected http exception
+class KubeException < StandardError
+ attr_reader :error_code, :message, :response
+
+ def initialize(error_code, message, response)
+ @error_code = error_code
+ @message = message
+ @response = response
+ end
+
+ def to_s
+ string = "HTTP status code #{@error_code}, #{@message}"
+ if @response.is_a?(RestClient::Response) && @response.request
+ string << " for #{@response.request.method.upcase} #{@response.request.url}"
+ end
+ string
+ end
+end
+
+module Kubeclient
+ # Exception that is raised when a http request fails
+ class HttpError < KubeException
+ end
+end
diff --git a/vendor/gems/kubeclient/lib/kubeclient/missing_kind_compatibility.rb b/vendor/gems/kubeclient/lib/kubeclient/missing_kind_compatibility.rb
new file mode 100644
index 00000000000..ec88960a546
--- /dev/null
+++ b/vendor/gems/kubeclient/lib/kubeclient/missing_kind_compatibility.rb
@@ -0,0 +1,68 @@
+module Kubeclient
+ module Common
+ # Backward compatibility for old versions where kind is missing (e.g. OpenShift Enterprise 3.1)
+ class MissingKindCompatibility
+ MAPPING = {
+ 'bindings' => 'Binding',
+ 'componentstatuses' => 'ComponentStatus',
+ 'endpoints' => 'Endpoints',
+ 'events' => 'Event',
+ 'limitranges' => 'LimitRange',
+ 'namespaces' => 'Namespace',
+ 'nodes' => 'Node',
+ 'persistentvolumeclaims' => 'PersistentVolumeClaim',
+ 'persistentvolumes' => 'PersistentVolume',
+ 'pods' => 'Pod',
+ 'podtemplates' => 'PodTemplate',
+ 'replicationcontrollers' => 'ReplicationController',
+ 'resourcequotas' => 'ResourceQuota',
+ 'secrets' => 'Secret',
+ 'securitycontextconstraints' => 'SecurityContextConstraints',
+ 'serviceaccounts' => 'ServiceAccount',
+ 'services' => 'Service',
+ 'buildconfigs' => 'BuildConfig',
+ 'builds' => 'Build',
+ 'clusternetworks' => 'ClusterNetwork',
+ 'clusterpolicies' => 'ClusterPolicy',
+ 'clusterpolicybindings' => 'ClusterPolicyBinding',
+ 'clusterrolebindings' => 'ClusterRoleBinding',
+ 'clusterroles' => 'ClusterRole',
+ 'deploymentconfigrollbacks' => 'DeploymentConfigRollback',
+ 'deploymentconfigs' => 'DeploymentConfig',
+ 'generatedeploymentconfigs' => 'DeploymentConfig',
+ 'groups' => 'Group',
+ 'hostsubnets' => 'HostSubnet',
+ 'identities' => 'Identity',
+ 'images' => 'Image',
+ 'imagestreamimages' => 'ImageStreamImage',
+ 'imagestreammappings' => 'ImageStreamMapping',
+ 'imagestreams' => 'ImageStream',
+ 'imagestreamtags' => 'ImageStreamTag',
+ 'localresourceaccessreviews' => 'LocalResourceAccessReview',
+ 'localsubjectaccessreviews' => 'LocalSubjectAccessReview',
+ 'netnamespaces' => 'NetNamespace',
+ 'oauthaccesstokens' => 'OAuthAccessToken',
+ 'oauthauthorizetokens' => 'OAuthAuthorizeToken',
+ 'oauthclientauthorizations' => 'OAuthClientAuthorization',
+ 'oauthclients' => 'OAuthClient',
+ 'policies' => 'Policy',
+ 'policybindings' => 'PolicyBinding',
+ 'processedtemplates' => 'Template',
+ 'projectrequests' => 'ProjectRequest',
+ 'projects' => 'Project',
+ 'resourceaccessreviews' => 'ResourceAccessReview',
+ 'rolebindings' => 'RoleBinding',
+ 'roles' => 'Role',
+ 'routes' => 'Route',
+ 'subjectaccessreviews' => 'SubjectAccessReview',
+ 'templates' => 'Template',
+ 'useridentitymappings' => 'UserIdentityMapping',
+ 'users' => 'User'
+ }.freeze
+
+ def self.resource_kind(name)
+ MAPPING[name]
+ end
+ end
+ end
+end
diff --git a/vendor/gems/kubeclient/lib/kubeclient/oidc_auth_provider.rb b/vendor/gems/kubeclient/lib/kubeclient/oidc_auth_provider.rb
new file mode 100644
index 00000000000..ffdfd7e2a5d
--- /dev/null
+++ b/vendor/gems/kubeclient/lib/kubeclient/oidc_auth_provider.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+module Kubeclient
+ # Uses OIDC id-tokens and refreshes them if they are stale.
+ class OIDCAuthProvider
+ class OpenIDConnectDependencyError < LoadError # rubocop:disable Lint/InheritException
+ end
+
+ class << self
+ def token(provider_config)
+ begin
+ require 'openid_connect'
+ rescue LoadError => e
+ raise OpenIDConnectDependencyError,
+ 'Error requiring openid_connect gem. Kubeclient itself does not include the ' \
+ 'openid_connect gem. To support auth-provider oidc, you must include it in your ' \
+ "calling application. Failed with: #{e.message}"
+ end
+
+ issuer_url = provider_config['idp-issuer-url']
+ discovery = OpenIDConnect::Discovery::Provider::Config.discover! issuer_url
+
+ if provider_config.key? 'id-token'
+ return provider_config['id-token'] unless expired?(provider_config['id-token'], discovery)
+ end
+
+ client = OpenIDConnect::Client.new(
+ identifier: provider_config['client-id'],
+ secret: provider_config['client-secret'],
+ authorization_endpoint: discovery.authorization_endpoint,
+ token_endpoint: discovery.token_endpoint,
+ userinfo_endpoint: discovery.userinfo_endpoint
+ )
+ client.refresh_token = provider_config['refresh-token']
+ client.access_token!.id_token
+ end
+
+ def expired?(id_token, discovery)
+ decoded_token = OpenIDConnect::ResponseObject::IdToken.decode(
+ id_token,
+ discovery.jwks
+ )
+ # If token expired or expiring within 60 seconds
+ Time.now.to_i + 60 > decoded_token.exp.to_i
+ rescue JSON::JWK::Set::KidNotFound
+ # Token cannot be verified: the kid it was signed with is not available for discovery
+ # Consider it expired and fetch a new one.
+ true
+ end
+ end
+ end
+end
diff --git a/vendor/gems/kubeclient/lib/kubeclient/resource.rb b/vendor/gems/kubeclient/lib/kubeclient/resource.rb
new file mode 100644
index 00000000000..08a50c3fe4f
--- /dev/null
+++ b/vendor/gems/kubeclient/lib/kubeclient/resource.rb
@@ -0,0 +1,11 @@
+require 'recursive_open_struct'
+
+module Kubeclient
+ # Represents all the objects returned by Kubeclient
+ class Resource < RecursiveOpenStruct
+ def initialize(hash = nil, args = {})
+ args[:recurse_over_arrays] = true
+ super(hash, args)
+ end
+ end
+end
diff --git a/vendor/gems/kubeclient/lib/kubeclient/resource_not_found_error.rb b/vendor/gems/kubeclient/lib/kubeclient/resource_not_found_error.rb
new file mode 100644
index 00000000000..045a83642d7
--- /dev/null
+++ b/vendor/gems/kubeclient/lib/kubeclient/resource_not_found_error.rb
@@ -0,0 +1,4 @@
+module Kubeclient
+ class ResourceNotFoundError < HttpError
+ end
+end
diff --git a/vendor/gems/kubeclient/lib/kubeclient/version.rb b/vendor/gems/kubeclient/lib/kubeclient/version.rb
new file mode 100644
index 00000000000..bff50841794
--- /dev/null
+++ b/vendor/gems/kubeclient/lib/kubeclient/version.rb
@@ -0,0 +1,4 @@
+# Kubernetes REST-API Client
+module Kubeclient
+ VERSION = '4.9.4-gitlab1'.freeze
+end
diff --git a/vendor/gems/kubeclient/lib/kubeclient/watch_stream.rb b/vendor/gems/kubeclient/lib/kubeclient/watch_stream.rb
new file mode 100644
index 00000000000..ef676660d53
--- /dev/null
+++ b/vendor/gems/kubeclient/lib/kubeclient/watch_stream.rb
@@ -0,0 +1,97 @@
+require 'json'
+require 'http'
+module Kubeclient
+ module Common
+ # HTTP Stream used to watch changes on entities
+ class WatchStream
+ def initialize(uri, http_options, formatter:)
+ @uri = uri
+ @http_client = nil
+ @http_options = http_options
+ @http_options[:http_max_redirects] ||= Kubeclient::Client::DEFAULT_HTTP_MAX_REDIRECTS
+ @formatter = formatter
+ end
+
+ def each
+ @finished = false
+
+ @http_client = build_client
+ response = @http_client.request(:get, @uri, build_client_options)
+ unless response.code < 300
+ raise Kubeclient::HttpError.new(response.code, response.reason, response)
+ end
+
+ buffer = ''
+ response.body.each do |chunk|
+ buffer << chunk
+ while (line = buffer.slice!(/.+\n/))
+ yield @formatter.call(line.chomp)
+ end
+ end
+ rescue StandardError
+ raise unless @finished
+ end
+
+ def finish
+ @finished = true
+ @http_client.close unless @http_client.nil?
+ end
+
+ private
+
+ def max_hops
+ @http_options[:http_max_redirects] + 1
+ end
+
+ def follow_option
+ if max_hops > 1
+ { max_hops: max_hops }
+ else
+ # i.e. Do not follow redirects as we have set http_max_redirects to 0
+ # Setting `{ max_hops: 1 }` does not work FWIW
+ false
+ end
+ end
+
+ def build_client
+ client = HTTP::Client.new(follow: follow_option)
+
+ if @http_options[:basic_auth_user] && @http_options[:basic_auth_password]
+ client = client.basic_auth(
+ user: @http_options[:basic_auth_user],
+ pass: @http_options[:basic_auth_password]
+ )
+ end
+
+ client
+ end
+
+ def using_proxy
+ proxy = @http_options[:http_proxy_uri]
+ return nil unless proxy
+ p_uri = URI.parse(proxy)
+ {
+ proxy_address: p_uri.hostname,
+ proxy_port: p_uri.port,
+ proxy_username: p_uri.user,
+ proxy_password: p_uri.password
+ }
+ end
+
+ def build_client_options
+ client_options = {
+ headers: @http_options[:headers],
+ proxy: using_proxy
+ }
+ if @http_options[:ssl]
+ client_options[:ssl] = @http_options[:ssl]
+ socket_option = :ssl_socket_class
+ else
+ socket_option = :socket_class
+ end
+ client_options[socket_option] = @http_options[socket_option] if @http_options[socket_option]
+ client_options
+ end
+ end
+ end
+end