debian-mirror-gitlab/lib/gitlab/kubernetes/kube_client.rb

198 lines
5.9 KiB
Ruby
Raw Normal View History

2018-11-20 20:47:30 +05:30
# frozen_string_literal: true
require 'uri'
module Gitlab
module Kubernetes
# Wrapper around Kubeclient::Client to dispatch
# the right message to the client that can respond to the message.
# We must have a kubeclient for each ApiGroup as there is no
# other way to use the Kubeclient gem.
#
# See https://github.com/abonas/kubeclient/issues/348.
class KubeClient
include Gitlab::Utils::StrongMemoize
2018-12-13 13:39:08 +05:30
SUPPORTED_API_GROUPS = {
core: { group: 'api', version: 'v1' },
rbac: { group: 'apis/rbac.authorization.k8s.io', version: 'v1' },
2020-03-13 15:44:24 +05:30
apps: { group: 'apis/apps', version: 'v1' },
2019-02-15 15:39:39 +05:30
extensions: { group: 'apis/extensions', version: 'v1beta1' },
2020-03-13 15:44:24 +05:30
istio: { group: 'apis/networking.istio.io', version: 'v1alpha3' },
2020-05-24 23:13:21 +05:30
knative: { group: 'apis/serving.knative.dev', version: 'v1alpha1' },
metrics: { group: 'apis/metrics.k8s.io', version: 'v1beta1' },
2020-10-24 23:57:45 +05:30
networking: { group: 'apis/networking.k8s.io', version: 'v1' },
cilium_networking: { group: 'apis/cilium.io', version: 'v2' }
2018-12-13 13:39:08 +05:30
}.freeze
SUPPORTED_API_GROUPS.each do |name, params|
client_method_name = "#{name}_client".to_sym
define_method(client_method_name) do
strong_memoize(client_method_name) do
build_kubeclient(params[:group], params[:version])
end
end
end
2018-11-20 20:47:30 +05:30
# Core API methods delegates to the core api group client
2020-05-24 23:13:21 +05:30
delegate :get_nodes,
:get_pods,
2018-11-20 20:47:30 +05:30
:get_secrets,
:get_config_map,
:get_namespace,
:get_pod,
2018-12-05 23:21:45 +05:30
:get_secret,
2018-11-20 20:47:30 +05:30
:get_service,
:get_service_account,
2019-12-21 20:55:43 +05:30
:delete_namespace,
2018-11-20 20:47:30 +05:30
:delete_pod,
2019-12-21 20:55:43 +05:30
:delete_service_account,
2018-11-20 20:47:30 +05:30
:create_config_map,
:create_namespace,
:create_pod,
2018-12-05 23:21:45 +05:30
:create_secret,
2018-11-20 20:47:30 +05:30
:create_service_account,
:update_config_map,
2019-02-15 15:39:39 +05:30
:update_secret,
2018-11-20 20:47:30 +05:30
:update_service_account,
to: :core_client
# RBAC methods delegates to the apis/rbac.authorization.k8s.io api
# group client
2020-05-24 23:13:21 +05:30
delegate :update_cluster_role_binding,
2021-01-29 00:20:46 +05:30
:create_role,
:get_role,
:update_role,
:delete_role_binding,
:update_role_binding,
2018-12-13 13:39:08 +05:30
to: :rbac_client
2018-11-20 20:47:30 +05:30
# non-entity methods that can only work with the core client
# as it uses the pods/log resource
delegate :get_pod_log,
:watch_pod_log,
to: :core_client
2020-03-13 15:44:24 +05:30
# Gateway methods delegate to the apis/networking.istio.io api
# group client
delegate :create_gateway,
:get_gateway,
:update_gateway,
to: :istio_client
2022-07-23 23:45:48 +05:30
delegate :get_ingresses, :patch_ingress, to: :networking_client
delegate :get_deployments, to: :apps_client
2018-12-13 13:39:08 +05:30
attr_reader :api_prefix, :kubeclient_options
2018-11-20 20:47:30 +05:30
2020-03-13 15:44:24 +05:30
DEFAULT_KUBECLIENT_OPTIONS = {
timeouts: {
open: 10,
read: 30
}
}.freeze
2020-05-24 23:13:21 +05:30
def self.graceful_request(cluster_id)
{ status: :connected, response: yield }
rescue *Gitlab::Kubernetes::Errors::CONNECTION
2020-10-24 23:57:45 +05:30
{ status: :unreachable, connection_error: :connection_error }
2020-05-24 23:13:21 +05:30
rescue *Gitlab::Kubernetes::Errors::AUTHENTICATION
2020-10-24 23:57:45 +05:30
{ status: :authentication_failure, connection_error: :authentication_error }
2020-05-24 23:13:21 +05:30
rescue Kubeclient::HttpError => e
2020-10-24 23:57:45 +05:30
{ status: kubeclient_error_status(e.message), connection_error: :http_error }
2021-06-08 01:23:25 +05:30
rescue StandardError => e
2020-05-24 23:13:21 +05:30
Gitlab::ErrorTracking.track_exception(e, cluster_id: cluster_id)
2020-10-24 23:57:45 +05:30
{ status: :unknown_failure, connection_error: :unknown_error }
2020-05-24 23:13:21 +05:30
end
# KubeClient uses the same error class
# For connection errors (eg. timeout) and
# for Kubernetes errors.
def self.kubeclient_error_status(message)
if message&.match?(/timed out|timeout/i)
:unreachable
else
:authentication_failure
end
end
2019-03-02 22:35:43 +05:30
# We disable redirects through 'http_max_redirects: 0',
# so that KubeClient does not follow redirects and
# expose internal services.
2018-12-13 13:39:08 +05:30
def initialize(api_prefix, **kubeclient_options)
2018-11-20 20:47:30 +05:30
@api_prefix = api_prefix
2020-03-13 15:44:24 +05:30
@kubeclient_options = DEFAULT_KUBECLIENT_OPTIONS
.deep_merge(kubeclient_options)
.merge(http_max_redirects: 0)
2019-03-13 22:55:13 +05:30
validate_url!
2018-11-20 20:47:30 +05:30
end
2019-02-15 15:39:39 +05:30
def create_or_update_cluster_role_binding(resource)
2020-05-24 23:13:21 +05:30
update_cluster_role_binding(resource)
2019-02-15 15:39:39 +05:30
end
2021-01-29 00:20:46 +05:30
# Note that we cannot update roleRef as that is immutable
2019-02-15 15:39:39 +05:30
def create_or_update_role_binding(resource)
2020-05-24 23:13:21 +05:30
update_role_binding(resource)
2019-02-15 15:39:39 +05:30
end
def create_or_update_service_account(resource)
if service_account_exists?(resource)
update_service_account(resource)
else
create_service_account(resource)
end
end
def create_or_update_secret(resource)
if secret_exists?(resource)
update_secret(resource)
else
create_secret(resource)
end
end
2018-11-20 20:47:30 +05:30
private
2019-03-13 22:55:13 +05:30
def validate_url!
2019-10-12 21:52:04 +05:30
return if Gitlab::CurrentSettings.allow_local_requests_from_web_hooks_and_services?
2019-03-13 22:55:13 +05:30
2023-03-04 22:38:38 +05:30
Gitlab::UrlBlocker.validate!(api_prefix, allow_local_network: false, schemes: %w[http https])
2019-03-13 22:55:13 +05:30
end
2019-02-15 15:39:39 +05:30
def service_account_exists?(resource)
get_service_account(resource.metadata.name, resource.metadata.namespace)
rescue ::Kubeclient::ResourceNotFoundError
false
end
def secret_exists?(resource)
get_secret(resource.metadata.name, resource.metadata.namespace)
rescue ::Kubeclient::ResourceNotFoundError
false
end
2018-12-13 13:39:08 +05:30
def build_kubeclient(api_group, api_version)
::Kubeclient::Client.new(
join_api_url(api_prefix, api_group),
api_version,
**kubeclient_options
)
2018-11-20 20:47:30 +05:30
end
def join_api_url(api_prefix, api_path)
url = URI.parse(api_prefix)
prefix = url.path.sub(%r{/+\z}, '')
url.path = [prefix, api_path].join("/")
url.to_s
end
end
end
end