gitlab-org--gitlab-foss/lib/gitlab/kubernetes.rb

160 lines
4.5 KiB
Ruby

# frozen_string_literal: true
module Gitlab
# Helper methods to do with Kubernetes network services & resources
module Kubernetes
def self.build_header_hash
Hash.new { |h, k| h[k] = [] }
end
# This is the comand that is run to start a terminal session. Kubernetes
# expects `command=foo&command=bar, not `command[]=foo&command[]=bar`
EXEC_COMMAND = URI.encode_www_form(
['sh', '-c', 'bash || sh'].map { |value| ['command', value] }
)
# Filters an array of pods (as returned by the kubernetes API) by their labels
def filter_by_label(items, labels = {})
items.select do |item|
metadata = item.fetch("metadata", {})
item_labels = metadata.fetch("labels", nil)
next unless item_labels
labels.all? { |k, v| item_labels[k.to_s] == v }
end
end
# Filters an array of pods (as returned by the kubernetes API) by their annotations
def filter_by_annotation(items, annotations = {})
items.select do |item|
metadata = item.fetch("metadata", {})
item_annotations = metadata.fetch("annotations", nil)
next unless item_annotations
annotations.all? { |k, v| item_annotations[k.to_s] == v }
end
end
# Filters an array of pods (as returned by the kubernetes API) by their project and environment
def filter_by_project_environment(items, app, env)
filter_by_annotation(items, {
'app.gitlab.com/app' => app,
'app.gitlab.com/env' => env
})
end
def filter_by_legacy_label(items, app, env)
legacy_items = filter_by_label(items, { app: env })
non_legacy_items = filter_by_project_environment(legacy_items, app, env)
legacy_items - non_legacy_items
end
# Converts a pod (as returned by the kubernetes API) into a terminal
def terminals_for_pod(api_url, namespace, pod)
metadata = pod.fetch("metadata", {})
status = pod.fetch("status", {})
spec = pod.fetch("spec", {})
containers = spec["containers"]
pod_name = metadata["name"]
phase = status["phase"]
return unless containers.present? && pod_name.present? && phase == "Running"
created_at = begin
DateTime.parse(metadata["creationTimestamp"])
rescue StandardError
nil
end
containers.map do |container|
{
selectors: { pod: pod_name, container: container["name"] },
url: container_exec_url(api_url, namespace, pod_name, container["name"]),
subprotocols: ['channel.k8s.io'],
headers: ::Gitlab::Kubernetes.build_header_hash,
created_at: created_at
}
end
end
def add_terminal_auth(terminal, token:, max_session_time:, ca_pem: nil)
terminal[:headers] ||= ::Gitlab::Kubernetes.build_header_hash
terminal[:headers]['Authorization'] << "Bearer #{token}"
terminal[:max_session_time] = max_session_time
terminal[:ca_pem] = ca_pem if ca_pem.present?
end
def container_exec_url(api_url, namespace, pod_name, container_name)
url = URI.parse(api_url)
url.path = [
url.path.sub(%r{/+\z}, ''),
'api', 'v1',
'namespaces', ERB::Util.url_encode(namespace),
'pods', ERB::Util.url_encode(pod_name),
'exec'
].join('/')
url.query = {
container: container_name,
tty: true,
stdin: true,
stdout: true,
stderr: true
}.to_query + '&' + EXEC_COMMAND
case url.scheme
when 'http'
url.scheme = 'ws'
when 'https'
url.scheme = 'wss'
end
url.to_s
end
def to_kubeconfig(url:, namespace:, token:, ca_pem: nil)
return unless token.present?
config = {
apiVersion: 'v1',
clusters: [
name: 'gitlab-deploy',
cluster: {
server: url
}
],
contexts: [
name: 'gitlab-deploy',
context: {
cluster: 'gitlab-deploy',
namespace: namespace,
user: 'gitlab-deploy'
}
],
'current-context': 'gitlab-deploy',
kind: 'Config',
users: [
{
name: 'gitlab-deploy',
user: { token: token }
}
]
}
kubeconfig_embed_ca_pem(config, ca_pem) if ca_pem
YAML.dump(config.deep_stringify_keys)
end
private
def kubeconfig_embed_ca_pem(config, ca_pem)
cluster = config.dig(:clusters, 0, :cluster)
cluster[:'certificate-authority-data'] = Base64.strict_encode64(ca_pem)
end
end
end