2018-09-06 06:03:38 -04:00
|
|
|
# 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-10-16 23:43:53 -04:00
|
|
|
SUPPORTED_API_GROUPS = {
|
2018-10-23 06:52:34 -04:00
|
|
|
core: { group: 'api', version: 'v1' },
|
|
|
|
rbac: { group: 'apis/rbac.authorization.k8s.io', version: 'v1' },
|
2020-01-23 07:08:38 -05:00
|
|
|
apps: { group: 'apis/apps', version: 'v1' },
|
2018-11-15 06:39:43 -05:00
|
|
|
extensions: { group: 'apis/extensions', version: 'v1beta1' },
|
2019-12-23 19:07:31 -05:00
|
|
|
istio: { group: 'apis/networking.istio.io', version: 'v1alpha3' },
|
2020-04-28 11:09:29 -04:00
|
|
|
knative: { group: 'apis/serving.knative.dev', version: 'v1alpha1' },
|
2020-05-14 17:07:52 -04:00
|
|
|
metrics: { group: 'apis/metrics.k8s.io', version: 'v1beta1' },
|
2020-07-23 20:09:34 -04:00
|
|
|
networking: { group: 'apis/networking.k8s.io', version: 'v1' },
|
|
|
|
cilium_networking: { group: 'apis/cilium.io', version: 'v2' }
|
2018-10-16 23:43:53 -04:00
|
|
|
}.freeze
|
2018-09-06 06:03:38 -04:00
|
|
|
|
2018-10-23 06:52:34 -04:00
|
|
|
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-09-06 06:03:38 -04:00
|
|
|
|
|
|
|
# Core API methods delegates to the core api group client
|
2020-05-14 17:07:52 -04:00
|
|
|
delegate :get_nodes,
|
|
|
|
:get_pods,
|
2018-09-06 06:03:38 -04:00
|
|
|
:get_secrets,
|
|
|
|
:get_config_map,
|
|
|
|
:get_namespace,
|
|
|
|
:get_pod,
|
2018-09-07 07:48:06 -04:00
|
|
|
:get_secret,
|
2018-09-06 06:03:38 -04:00
|
|
|
:get_service,
|
|
|
|
:get_service_account,
|
2019-09-23 20:06:02 -04:00
|
|
|
:delete_namespace,
|
2018-09-06 06:03:38 -04:00
|
|
|
:delete_pod,
|
2019-10-06 08:05:58 -04:00
|
|
|
:delete_service_account,
|
2018-09-06 06:03:38 -04:00
|
|
|
:create_config_map,
|
|
|
|
:create_namespace,
|
|
|
|
:create_pod,
|
2018-09-07 06:24:39 -04:00
|
|
|
:create_secret,
|
2018-09-06 06:03:38 -04:00
|
|
|
:create_service_account,
|
|
|
|
:update_config_map,
|
2018-11-28 05:31:28 -05:00
|
|
|
:update_secret,
|
2018-09-06 06:03:38 -04:00
|
|
|
:update_service_account,
|
|
|
|
to: :core_client
|
|
|
|
|
|
|
|
# RBAC methods delegates to the apis/rbac.authorization.k8s.io api
|
|
|
|
# group client
|
2020-05-06 08:09:36 -04:00
|
|
|
delegate :update_cluster_role_binding,
|
2020-10-27 08:08:33 -04:00
|
|
|
:create_role,
|
|
|
|
:get_role,
|
|
|
|
:update_role,
|
|
|
|
:delete_role_binding,
|
|
|
|
:update_role_binding,
|
2018-10-22 17:47:54 -04:00
|
|
|
to: :rbac_client
|
|
|
|
|
2018-09-06 06:03:38 -04:00
|
|
|
# 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
|
|
|
|
|
2019-12-23 19:07:31 -05:00
|
|
|
# Gateway methods delegate to the apis/networking.istio.io api
|
|
|
|
# group client
|
|
|
|
delegate :create_gateway,
|
|
|
|
:get_gateway,
|
|
|
|
:update_gateway,
|
|
|
|
to: :istio_client
|
|
|
|
|
2022-06-22 17:08:26 -04:00
|
|
|
delegate :get_ingresses, :patch_ingress, to: :networking_client
|
|
|
|
|
|
|
|
delegate :get_deployments, to: :apps_client
|
|
|
|
|
2018-10-23 06:52:34 -04:00
|
|
|
attr_reader :api_prefix, :kubeclient_options
|
2018-09-06 06:03:38 -04:00
|
|
|
|
2020-02-09 07:08:54 -05:00
|
|
|
DEFAULT_KUBECLIENT_OPTIONS = {
|
|
|
|
timeouts: {
|
|
|
|
open: 10,
|
|
|
|
read: 30
|
|
|
|
}
|
|
|
|
}.freeze
|
|
|
|
|
2020-05-14 17:07:52 -04:00
|
|
|
def self.graceful_request(cluster_id)
|
|
|
|
{ status: :connected, response: yield }
|
|
|
|
rescue *Gitlab::Kubernetes::Errors::CONNECTION
|
2020-08-05 17:09:40 -04:00
|
|
|
{ status: :unreachable, connection_error: :connection_error }
|
2020-05-14 17:07:52 -04:00
|
|
|
rescue *Gitlab::Kubernetes::Errors::AUTHENTICATION
|
2020-08-05 17:09:40 -04:00
|
|
|
{ status: :authentication_failure, connection_error: :authentication_error }
|
2020-05-14 17:07:52 -04:00
|
|
|
rescue Kubeclient::HttpError => e
|
2020-08-05 17:09:40 -04:00
|
|
|
{ status: kubeclient_error_status(e.message), connection_error: :http_error }
|
2021-04-26 08:09:44 -04:00
|
|
|
rescue StandardError => e
|
2020-05-14 17:07:52 -04:00
|
|
|
Gitlab::ErrorTracking.track_exception(e, cluster_id: cluster_id)
|
|
|
|
|
2020-08-05 17:09:40 -04:00
|
|
|
{ status: :unknown_failure, connection_error: :unknown_error }
|
2020-05-14 17:07:52 -04:00
|
|
|
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-01-09 18:26:40 -05:00
|
|
|
# We disable redirects through 'http_max_redirects: 0',
|
|
|
|
# so that KubeClient does not follow redirects and
|
|
|
|
# expose internal services.
|
2018-10-23 06:52:34 -04:00
|
|
|
def initialize(api_prefix, **kubeclient_options)
|
2018-09-06 06:03:38 -04:00
|
|
|
@api_prefix = api_prefix
|
2020-02-09 07:08:54 -05:00
|
|
|
@kubeclient_options = DEFAULT_KUBECLIENT_OPTIONS
|
|
|
|
.deep_merge(kubeclient_options)
|
|
|
|
.merge(http_max_redirects: 0)
|
2019-02-12 15:46:59 -05:00
|
|
|
|
|
|
|
validate_url!
|
2018-09-06 06:03:38 -04:00
|
|
|
end
|
|
|
|
|
2018-11-28 05:31:28 -05:00
|
|
|
def create_or_update_cluster_role_binding(resource)
|
2020-05-06 08:09:36 -04:00
|
|
|
update_cluster_role_binding(resource)
|
2018-11-28 05:31:28 -05:00
|
|
|
end
|
|
|
|
|
2020-10-27 08:08:33 -04:00
|
|
|
# Note that we cannot update roleRef as that is immutable
|
2018-11-28 05:31:28 -05:00
|
|
|
def create_or_update_role_binding(resource)
|
2020-05-06 08:09:36 -04:00
|
|
|
update_role_binding(resource)
|
2018-11-28 05:31:28 -05:00
|
|
|
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-09-06 06:03:38 -04:00
|
|
|
private
|
|
|
|
|
2019-02-12 15:46:59 -05:00
|
|
|
def validate_url!
|
2019-07-26 06:21:52 -04:00
|
|
|
return if Gitlab::CurrentSettings.allow_local_requests_from_web_hooks_and_services?
|
2019-02-12 15:46:59 -05:00
|
|
|
|
|
|
|
Gitlab::UrlBlocker.validate!(api_prefix, allow_local_network: false)
|
|
|
|
end
|
|
|
|
|
2018-11-28 05:31:28 -05:00
|
|
|
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-10-08 17:18:16 -04:00
|
|
|
def build_kubeclient(api_group, api_version)
|
|
|
|
::Kubeclient::Client.new(
|
|
|
|
join_api_url(api_prefix, api_group),
|
|
|
|
api_version,
|
|
|
|
**kubeclient_options
|
|
|
|
)
|
2018-09-06 06:03:38 -04:00
|
|
|
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
|