2018-08-10 06:45:01 +00:00
# frozen_string_literal: true
2017-11-21 13:03:07 +00: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 16:36:26 +00:00
class KubernetesService < DeploymentService
2016-11-22 19:55:56 +00:00
include Gitlab :: Kubernetes
include ReactiveCaching
2017-02-21 23:33:53 +00:00
self . reactive_cache_key = - > ( service ) { [ service . class . model_name . singular , service . project_id ] }
2016-11-22 19:55:56 +00:00
2016-12-08 16:36:26 +00: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 11:43:53 +00:00
validates :api_url , public_url : true
2016-12-08 16:36:26 +00:00
validates :token
end
2017-08-22 06:12:27 +00:00
before_validation :enforce_namespace_to_lower_case
2018-01-04 09:33:51 +00:00
validate :deprecation_validation , unless : :template?
2017-04-04 07:40:00 +00: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 19:55:56 +00:00
after_save :clear_reactive_cache!
2016-12-08 16:36:26 +00:00
def initialize_properties
2017-04-04 07:40:00 +00:00
self . properties = { } if properties . nil?
2016-12-08 16:36:26 +00:00
end
def title
'Kubernetes'
end
def description
'Kubernetes / Openshift integration'
end
def help
2016-11-22 19:55:56 +00:00
'To enable terminal access to Kubernetes environments, label your ' \
'deployments with `app=$CI_ENVIRONMENT_SLUG`'
2016-12-08 16:36:26 +00:00
end
2016-12-27 12:44:24 +00:00
def self . to_param
2016-12-08 16:36:26 +00:00
'kubernetes'
end
def fields
[
{ type : 'text' ,
name : 'api_url' ,
title : 'API URL' ,
2017-02-22 17:44:44 +00:00
placeholder : 'Kubernetes API URL, like https://kube.example.com/' } ,
2016-12-08 16:36:26 +00:00
{ type : 'textarea' ,
name : 'ca_pem' ,
2017-07-19 10:56:11 +00: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 16:36:26 +00:00
]
end
2017-05-08 15:28:05 +00:00
def actual_namespace
if namespace . present?
namespace
else
default_namespace
end
end
2016-12-08 16:36:26 +00:00
# Check we can connect to the Kubernetes API
def test ( * args )
2018-09-06 10:03:38 +00:00
kubeclient = build_kube_client!
2016-12-08 16:36:26 +00:00
2018-09-06 10:03:38 +00:00
kubeclient . core_client . discover
{ success : kubeclient . core_client . discovered , result : " Checked API discovery endpoint " }
2016-12-08 16:36:26 +00:00
rescue = > err
{ success : false , result : err }
end
2016-12-16 12:24:03 +00:00
def predefined_variables
2017-06-16 14:15:40 +00:00
config = YAML . dump ( kubeconfig )
2018-03-14 10:15:18 +00:00
Gitlab :: Ci :: Variables :: Collection . new . tap do | variables |
variables
. append ( key : 'KUBE_URL' , value : api_url )
. append ( key : 'KUBE_TOKEN' , value : token , public : false )
. append ( key : 'KUBE_NAMESPACE' , value : actual_namespace )
. append ( key : 'KUBECONFIG' , value : config , public : false , file : true )
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 21:02:24 +00:00
end
2016-12-16 12:24:03 +00:00
end
2016-11-22 19:55:56 +00: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 |
2017-06-13 15:31:23 +00:00
pods = filter_by_label ( data [ :pods ] , app : environment . slug )
terminals = pods . flat_map { | pod | terminals_for_pod ( api_url , actual_namespace , pod ) }
terminals . each { | terminal | add_terminal_auth ( terminal , terminal_auth ) }
2016-11-22 19:55:56 +00:00
end
end
2016-12-08 16:36:26 +00:00
2017-06-13 15:31:23 +00:00
# Caches resources in the namespace so other calls don't need to block on
# network access
2016-11-22 19:55:56 +00:00
def calculate_reactive_cache
return unless active? && project && ! project . pending_delete?
2016-12-08 16:36:26 +00:00
2016-11-22 19:55:56 +00:00
# We may want to cache extra things in the future
2017-06-13 15:31:23 +00:00
{ pods : read_pods }
2016-11-22 19:55:56 +00:00
end
2017-11-02 14:10:46 +00:00
def kubeclient
2018-09-06 10:03:38 +00:00
@kubeclient || = build_kube_client! ( api_groups : [ 'api' , 'apis/rbac.authorization.k8s.io' ] )
2017-11-02 10:14:10 +00:00
end
2018-01-04 09:33:51 +00:00
def deprecated?
! active
end
def deprecation_message
2018-01-29 19:49:00 +00: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 09:33:51 +00:00
content . html_safe
end
2017-04-04 07:40:00 +00:00
TEMPLATE_PLACEHOLDER = 'Kubernetes namespace' . freeze
2016-11-22 19:55:56 +00:00
private
2017-06-16 14:15:40 +00:00
def kubeconfig
to_kubeconfig (
url : api_url ,
namespace : actual_namespace ,
token : token ,
ca_pem : ca_pem )
end
2017-04-04 07:40:00 +00:00
def namespace_placeholder
default_namespace || TEMPLATE_PLACEHOLDER
end
def default_namespace
2017-10-02 20:32:36 +00:00
return unless project
2017-10-26 14:38:10 +00:00
slug = " #{ project . path } - #{ project . id } " . downcase
slug . gsub ( / [^-a-z0-9] / , '-' ) . gsub ( / ^-+ / , '' )
2017-04-04 07:40:00 +00:00
end
2018-09-06 10:03:38 +00:00
def build_kube_client! ( api_groups : [ 'api' ] , api_version : 'v1' )
2017-05-08 15:28:05 +00:00
raise " Incomplete settings " unless api_url && actual_namespace && token
2016-12-08 16:36:26 +00:00
2018-09-06 10:03:38 +00:00
Gitlab :: Kubernetes :: KubeClient . new (
api_url ,
api_groups ,
2016-12-08 16:36:26 +00:00
api_version ,
auth_options : kubeclient_auth_options ,
2016-11-22 19:55:56 +00:00
ssl_options : kubeclient_ssl_options ,
2016-12-08 16:36:26 +00:00
http_proxy_uri : ENV [ 'http_proxy' ]
)
end
2017-06-13 15:31:23 +00:00
# Returns a hash of all pods in the namespace
def read_pods
2018-09-06 10:03:38 +00:00
kubeclient = build_kube_client!
2017-06-13 15:31:23 +00:00
kubeclient . get_pods ( namespace : actual_namespace ) . as_json
2018-03-15 07:14:13 +00:00
rescue Kubeclient :: HttpError = > err
2017-06-13 15:31:23 +00:00
raise err unless err . error_code == 404
2017-11-14 09:02:39 +00:00
2017-06-13 15:31:23 +00:00
[ ]
end
2016-12-08 16:36:26 +00: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 19:55:56 +00:00
2017-01-26 18:16:50 +00:00
def terminal_auth
{
token : token ,
ca_pem : ca_pem ,
2018-02-02 18:39:55 +00:00
max_session_time : Gitlab :: CurrentSettings . terminal_max_session_time
2017-01-26 18:16:50 +00:00
}
end
2017-08-22 06:12:27 +00:00
def enforce_namespace_to_lower_case
self . namespace = self . namespace & . downcase
end
2018-01-04 09:33:51 +00:00
def deprecation_validation
2018-07-05 09:41:29 +00:00
return if active_changed? ( from : true , to : false ) || ( new_record? && ! active? )
2018-01-04 09:33:51 +00:00
if deprecated?
errors [ :base ] << deprecation_message
end
end
def deprecated_message_content
if active?
2018-01-29 19:49:00 +00:00
_ ( " Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure " )
2018-01-04 09:33:51 +00:00
else
2018-01-29 19:49:00 +00:00
_ ( " Fields on this page are now uneditable, you can configure " )
2018-01-04 09:33:51 +00:00
end
end
2016-12-08 16:36:26 +00:00
end