2018-08-10 02:45:01 -04:00
# frozen_string_literal: true
2017-11-21 08:03:07 -05:00
##
# NOTE:
# We'll move this class to Clusters::Platforms::Kubernetes, which contains exactly the same logic.
# After we've migrated data, we'll remove KubernetesService. This would happen in a few months.
# If you're modyfiyng this class, please note that you should update the same change in Clusters::Platforms::Kubernetes.
2016-12-08 11:36:26 -05:00
class KubernetesService < DeploymentService
2016-11-22 14:55:56 -05:00
include Gitlab :: Kubernetes
include ReactiveCaching
2017-02-21 18:33:53 -05:00
self . reactive_cache_key = - > ( service ) { [ service . class . model_name . singular , service . project_id ] }
2016-11-22 14:55:56 -05:00
2016-12-08 11:36:26 -05:00
# Namespace defaults to the project path, but can be overridden in case that
# is an invalid or inappropriate name
prop_accessor :namespace
# Access to kubernetes is directly through the API
prop_accessor :api_url
# Bearer authentication
# TODO: user/password auth, client certificates
prop_accessor :token
# Provide a custom CA bundle for self-signed deployments
prop_accessor :ca_pem
with_options presence : true , if : :activated? do
2018-06-01 07:43:53 -04:00
validates :api_url , public_url : true
2016-12-08 11:36:26 -05:00
validates :token
end
2017-08-22 02:12:27 -04:00
before_validation :enforce_namespace_to_lower_case
2018-01-04 04:33:51 -05:00
validate :deprecation_validation , unless : :template?
2017-04-04 03:40:00 -04:00
validates :namespace ,
allow_blank : true ,
length : 1 .. 63 ,
if : :activated? ,
format : {
with : Gitlab :: Regex . kubernetes_namespace_regex ,
message : Gitlab :: Regex . kubernetes_namespace_regex_message
}
2016-11-22 14:55:56 -05:00
after_save :clear_reactive_cache!
2016-12-08 11:36:26 -05:00
def initialize_properties
2017-04-04 03:40:00 -04:00
self . properties = { } if properties . nil?
2016-12-08 11:36:26 -05:00
end
def title
'Kubernetes'
end
def description
2019-01-02 05:03:02 -05:00
'Kubernetes / OpenShift integration'
2016-12-08 11:36:26 -05:00
end
def help
2016-11-22 14:55:56 -05:00
'To enable terminal access to Kubernetes environments, label your ' \
'deployments with `app=$CI_ENVIRONMENT_SLUG`'
2016-12-08 11:36:26 -05:00
end
2016-12-27 07:44:24 -05:00
def self . to_param
2016-12-08 11:36:26 -05:00
'kubernetes'
end
def fields
[
{ type : 'text' ,
name : 'api_url' ,
title : 'API URL' ,
2017-02-22 12:44:44 -05:00
placeholder : 'Kubernetes API URL, like https://kube.example.com/' } ,
2016-12-08 11:36:26 -05:00
{ type : 'textarea' ,
name : 'ca_pem' ,
2017-07-19 06:56:11 -04:00
title : 'CA Certificate' ,
placeholder : 'Certificate Authority bundle (PEM format)' } ,
{ type : 'text' ,
name : 'namespace' ,
title : 'Project namespace (optional/unique)' ,
placeholder : namespace_placeholder } ,
{ type : 'text' ,
name : 'token' ,
title : 'Token' ,
placeholder : 'Service token' }
2016-12-08 11:36:26 -05:00
]
end
2019-05-16 18:49:12 -04:00
def kubernetes_namespace_for ( project )
2017-05-08 11:28:05 -04:00
if namespace . present?
namespace
else
default_namespace
end
end
2016-12-08 11:36:26 -05:00
# Check we can connect to the Kubernetes API
def test ( * args )
2018-09-06 06:03:38 -04:00
kubeclient = build_kube_client!
2016-12-08 11:36:26 -05:00
2018-09-06 06:03:38 -04:00
kubeclient . core_client . discover
{ success : kubeclient . core_client . discovered , result : " Checked API discovery endpoint " }
2016-12-08 11:36:26 -05:00
rescue = > err
{ success : false , result : err }
end
2018-11-02 11:46:15 -04:00
# Project param was added on
# https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22011,
# as a way to keep this service compatible with
# Clusters::Platforms::Kubernetes, it won't be used on this method
# as it's only needed for Clusters::Cluster.
def predefined_variables ( project : )
2018-03-14 06:15:18 -04:00
Gitlab :: Ci :: Variables :: Collection . new . tap do | variables |
variables
. append ( key : 'KUBE_URL' , value : api_url )
2019-03-07 12:08:17 -05:00
. append ( key : 'KUBE_TOKEN' , value : token , public : false , masked : true )
2019-05-16 18:49:12 -04:00
. append ( key : 'KUBE_NAMESPACE' , value : kubernetes_namespace_for ( project ) )
2018-11-28 07:29:01 -05:00
. append ( key : 'KUBECONFIG' , value : kubeconfig , public : false , file : true )
2018-03-14 06:15:18 -04:00
if ca_pem . present?
variables
. append ( key : 'KUBE_CA_PEM' , value : ca_pem )
. append ( key : 'KUBE_CA_PEM_FILE' , value : ca_pem , file : true )
end
2017-02-20 16:02:24 -05:00
end
2016-12-16 07:24:03 -05:00
end
2016-11-22 14:55:56 -05:00
# Constructs a list of terminals from the reactive cache
#
# Returns nil if the cache is empty, in which case you should try again a
# short time later
def terminals ( environment )
with_reactive_cache do | data |
2019-05-16 18:49:12 -04:00
project = environment . project
2019-03-21 04:06:47 -04:00
pods = filter_by_project_environment ( data [ :pods ] , project . full_path_slug , environment . slug )
2019-05-16 18:49:12 -04:00
terminals = pods . flat_map { | pod | terminals_for_pod ( api_url , kubernetes_namespace_for ( project ) , pod ) } . compact
2017-06-13 11:31:23 -04:00
terminals . each { | terminal | add_terminal_auth ( terminal , terminal_auth ) }
2016-11-22 14:55:56 -05:00
end
end
2016-12-08 11:36:26 -05:00
2017-06-13 11:31:23 -04:00
# Caches resources in the namespace so other calls don't need to block on
# network access
2016-11-22 14:55:56 -05:00
def calculate_reactive_cache
return unless active? && project && ! project . pending_delete?
2016-12-08 11:36:26 -05:00
2016-11-22 14:55:56 -05:00
# We may want to cache extra things in the future
2017-06-13 11:31:23 -04:00
{ pods : read_pods }
2016-11-22 14:55:56 -05:00
end
2017-11-02 10:10:46 -04:00
def kubeclient
2018-10-03 00:04:15 -04:00
@kubeclient || = build_kube_client!
2017-11-02 06:14:10 -04:00
end
2018-01-04 04:33:51 -05:00
def deprecated?
! active
end
def deprecation_message
2018-01-29 14:49:00 -05:00
content = _ ( " Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href= \" %{url} \" />Kubernetes Clusters</a> page " ) % {
deprecated_message_content : deprecated_message_content ,
url : Gitlab :: Routing . url_helpers . project_clusters_path ( project )
}
2018-01-04 04:33:51 -05:00
content . html_safe
end
2017-04-04 03:40:00 -04:00
TEMPLATE_PLACEHOLDER = 'Kubernetes namespace' . freeze
2016-11-22 14:55:56 -05:00
private
2017-06-16 10:15:40 -04:00
def kubeconfig
to_kubeconfig (
url : api_url ,
2019-05-16 18:49:12 -04:00
namespace : kubernetes_namespace_for ( project ) ,
2017-06-16 10:15:40 -04:00
token : token ,
ca_pem : ca_pem )
end
2017-04-04 03:40:00 -04:00
def namespace_placeholder
default_namespace || TEMPLATE_PLACEHOLDER
end
def default_namespace
2017-10-02 16:32:36 -04:00
return unless project
2017-10-26 10:38:10 -04:00
slug = " #{ project . path } - #{ project . id } " . downcase
slug . gsub ( / [^-a-z0-9] / , '-' ) . gsub ( / ^-+ / , '' )
2017-04-04 03:40:00 -04:00
end
2018-10-23 06:52:34 -04:00
def build_kube_client!
2019-05-16 18:49:12 -04:00
raise " Incomplete settings " unless api_url && kubernetes_namespace_for ( project ) && token
2016-12-08 11:36:26 -05:00
2018-09-06 06:03:38 -04:00
Gitlab :: Kubernetes :: KubeClient . new (
api_url ,
2016-12-08 11:36:26 -05:00
auth_options : kubeclient_auth_options ,
2016-11-22 14:55:56 -05:00
ssl_options : kubeclient_ssl_options ,
2016-12-08 11:36:26 -05:00
http_proxy_uri : ENV [ 'http_proxy' ]
)
end
2017-06-13 11:31:23 -04:00
# Returns a hash of all pods in the namespace
def read_pods
2018-09-06 06:03:38 -04:00
kubeclient = build_kube_client!
2017-06-13 11:31:23 -04:00
2019-05-16 18:49:12 -04:00
kubeclient . get_pods ( namespace : kubernetes_namespace_for ( project ) ) . as_json
2018-11-13 07:46:01 -05:00
rescue Kubeclient :: ResourceNotFoundError
2017-06-13 11:31:23 -04:00
[ ]
end
2016-12-08 11:36:26 -05:00
def kubeclient_ssl_options
opts = { verify_ssl : OpenSSL :: SSL :: VERIFY_PEER }
if ca_pem . present?
opts [ :cert_store ] = OpenSSL :: X509 :: Store . new
opts [ :cert_store ] . add_cert ( OpenSSL :: X509 :: Certificate . new ( ca_pem ) )
end
opts
end
def kubeclient_auth_options
{ bearer_token : token }
end
2016-11-22 14:55:56 -05:00
2017-01-26 13:16:50 -05:00
def terminal_auth
{
token : token ,
ca_pem : ca_pem ,
2018-02-02 13:39:55 -05:00
max_session_time : Gitlab :: CurrentSettings . terminal_max_session_time
2017-01-26 13:16:50 -05:00
}
end
2017-08-22 02:12:27 -04:00
def enforce_namespace_to_lower_case
self . namespace = self . namespace & . downcase
end
2018-01-04 04:33:51 -05:00
def deprecation_validation
2018-07-05 05:41:29 -04:00
return if active_changed? ( from : true , to : false ) || ( new_record? && ! active? )
2018-01-04 04:33:51 -05:00
if deprecated?
errors [ :base ] << deprecation_message
end
end
def deprecated_message_content
if active?
2018-01-29 14:49:00 -05:00
_ ( " Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure " )
2018-01-04 04:33:51 -05:00
else
2018-01-29 14:49:00 -05:00
_ ( " Fields on this page are now uneditable, you can configure " )
2018-01-04 04:33:51 -05:00
end
end
2016-12-08 11:36:26 -05:00
end