gitlab-org--gitlab-foss/tooling/lib/tooling/kubernetes_client.rb

172 lines
4.8 KiB
Ruby

# frozen_string_literal: true
require 'json'
require 'time'
require_relative '../../../lib/gitlab/popen' unless defined?(Gitlab::Popen)
module Tooling
class KubernetesClient
RESOURCE_LIST = 'ingress,svc,pdb,hpa,deploy,statefulset,job,pod,secret,configmap,pvc,secret,clusterrole,clusterrolebinding,role,rolebinding,sa,crd'
CommandFailedError = Class.new(StandardError)
attr_reader :namespace
def initialize(namespace:)
@namespace = namespace
end
def cleanup_by_release(release_name:, wait: true)
delete_by_selector(release_name: release_name, wait: wait)
delete_by_matching_name(release_name: release_name)
end
def cleanup_by_created_at(resource_type:, created_before:, wait: true)
resource_names = resource_names_created_before(resource_type: resource_type, created_before: created_before)
return if resource_names.empty?
delete_by_exact_names(resource_type: resource_type, resource_names: resource_names, wait: wait)
end
def cleanup_review_app_namespaces(created_before:, wait: true)
namespaces = review_app_namespaces_created_before(created_before: created_before)
return if namespaces.empty?
delete_namespaces_by_exact_names(resource_names: namespaces, wait: wait)
end
private
def delete_by_selector(release_name:, wait:)
selector = case release_name
when String
%(-l release="#{release_name}")
when Array
%(-l 'release in (#{release_name.join(', ')})')
else
raise ArgumentError, 'release_name must be a string or an array'
end
command = [
'delete',
RESOURCE_LIST,
%(--namespace "#{namespace}"),
'--now',
'--ignore-not-found',
%(--wait=#{wait}),
selector
]
run_command(command)
end
def delete_by_exact_names(resource_names:, wait:, resource_type: nil)
command = [
'delete',
resource_type,
%(--namespace "#{namespace}"),
'--now',
'--ignore-not-found',
%(--wait=#{wait}),
resource_names.join(' ')
]
run_command(command)
end
def delete_namespaces_by_exact_names(resource_names:, wait:)
command = [
'delete',
'namespace',
'--now',
'--ignore-not-found',
%(--wait=#{wait}),
resource_names.join(' ')
]
run_command(command)
end
def delete_by_matching_name(release_name:)
resource_names = raw_resource_names
command = [
'delete',
%(--namespace "#{namespace}"),
'--ignore-not-found'
]
Array(release_name).each do |release|
resource_names
.select { |resource_name| resource_name.include?(release) }
.each { |matching_resource| run_command(command + [matching_resource]) }
end
end
def raw_resource_names
command = [
'get',
RESOURCE_LIST,
%(--namespace "#{namespace}"),
'-o name'
]
run_command(command).lines.map(&:strip)
end
def resource_names_created_before(resource_type:, created_before:)
command = [
'get',
resource_type,
%(--namespace "#{namespace}"),
"--sort-by='{.metadata.creationTimestamp}'",
'-o json'
]
response = run_command(command)
resources_created_before_date(response, created_before)
end
def review_app_namespaces_created_before(created_before:)
command = [
'get',
'namespace',
"-l tls=review-apps-tls", # Get only namespaces used for review-apps
"--sort-by='{.metadata.creationTimestamp}'",
'-o json'
]
response = run_command(command)
resources_created_before_date(response, created_before)
end
def resources_created_before_date(response, date)
items = JSON.parse(response)['items'] # rubocop:disable Gitlab/Json
items.each_with_object([]) do |item, result|
item_created_at = Time.parse(item.dig('metadata', 'creationTimestamp'))
if item_created_at < date
resource_name = item.dig('metadata', 'name')
result << resource_name
end
end
rescue ::JSON::ParserError => ex
puts "Ignoring this JSON parsing error: #{ex}\n\nResponse was:\n#{response}" # rubocop:disable Rails/Output
[]
end
def run_command(command)
final_command = ['kubectl', *command.compact].join(' ')
puts "Running command: `#{final_command}`" # rubocop:disable Rails/Output
result = Gitlab::Popen.popen_with_detail([final_command])
if result.status.success?
result.stdout.chomp.freeze
else
raise CommandFailedError, "The `#{final_command}` command failed (status: #{result.status}) with the following error:\n#{result.stderr}"
end
end
end
end