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
'Kubernetes / Openshift integration'
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
2017-05-08 11:28:05 -04:00
def actual_namespace
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 )
2016-11-22 14:55:56 -05:00
kubeclient = build_kubeclient!
2016-12-08 11:36:26 -05:00
2016-11-22 14:55:56 -05:00
kubeclient . discover
2016-12-08 11:36:26 -05:00
{ success : kubeclient . discovered , result : " Checked API discovery endpoint " }
rescue = > err
{ success : false , result : err }
end
2016-12-16 07:24:03 -05:00
def predefined_variables
2017-06-16 10:15:40 -04:00
config = YAML . dump ( kubeconfig )
2018-03-14 06:15:18 -04: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 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 |
2017-06-13 11:31:23 -04: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 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
@kubeclient || = build_kubeclient!
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 ,
namespace : actual_namespace ,
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
2016-11-22 14:55:56 -05:00
def build_kubeclient! ( api_path : 'api' , api_version : 'v1' )
2017-05-08 11:28:05 -04:00
raise " Incomplete settings " unless api_url && actual_namespace && token
2016-12-08 11:36:26 -05:00
:: Kubeclient :: Client . new (
2016-11-22 14:55:56 -05:00
join_api_url ( api_path ) ,
2016-12-08 11:36:26 -05:00
api_version ,
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
kubeclient = build_kubeclient!
kubeclient . get_pods ( namespace : actual_namespace ) . as_json
2018-03-15 03:14:13 -04:00
rescue Kubeclient :: HttpError = > err
2017-06-13 11:31:23 -04:00
raise err unless err . error_code == 404
2017-11-14 04:02:39 -05:00
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-06-13 11:31:23 -04:00
def join_api_url ( api_path )
2016-11-22 14:55:56 -05:00
url = URI . parse ( api_url )
prefix = url . path . sub ( %r{ /+ \ z } , '' )
2017-06-13 11:31:23 -04:00
url . path = [ prefix , api_path ] . join ( " / " )
2016-11-22 14:55:56 -05:00
url . to_s
end
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