# frozen_string_literal: true require 'uffizzi/services/cluster_service' require 'uffizzi/services/dev_service' require 'uffizzi/services/kubeconfig_service' require 'uffizzi/auth_helper' module Uffizzi class Cli::Dev < Thor include ApiClient desc 'start [CONFIG]', 'Start dev environment' method_option :quiet, type: :boolean, aliases: :q method_option :'default-repo', type: :string method_option :kubeconfig, type: :string method_option :'k8s-version', required: false, type: :string def start(config_path = 'skaffold.yaml') Uffizzi::AuthHelper.check_login(options[:project]) DevService.check_skaffold_existence DevService.check_running_daemon if options[:quiet] DevService.check_skaffold_config_existence(config_path) cluster_id, cluster_name = start_create_cluster kubeconfig = wait_cluster_creation(cluster_name) if options[:quiet] launch_demonise_skaffold(config_path) else DevService.start_basic_skaffold(config_path, options) end ensure if defined?(cluster_name).present? && defined?(cluster_id).present? kubeconfig = defined?(kubeconfig).present? ? kubeconfig : nil handle_delete_cluster(cluster_id, cluster_name, kubeconfig) end end desc 'stop', 'Stop dev environment' def stop return Uffizzi.ui.say('Uffizzi dev is not running') unless File.exist?(DevService.pid_path) pid = File.read(DevService.pid_path).to_i File.delete(DevService.pid_path) Uffizzi.process.kill('QUIT', pid) Uffizzi.ui.say('Uffizzi dev was stopped') rescue Errno::ESRCH Uffizzi.ui.say('Uffizzi dev is not running') File.delete(DevService.pid_path) end private def start_create_cluster params = cluster_creation_params( name: ClusterService.generate_name, creation_source: ClusterService::MANUAL_CREATION_SOURCE, k8s_version: options[:"k8s-version"], ) Uffizzi.ui.say('Start creating a cluster') response = create_cluster(ConfigFile.read_option(:server), project_slug, params) return ResponseHelper.handle_failed_response(response) unless ResponseHelper.created?(response) cluster_id = response.dig(:body, :cluster, :id) cluster_name = response.dig(:body, :cluster, :name) [cluster_id, cluster_name] end def wait_cluster_creation(cluster_name) Uffizzi.ui.say('Checking the cluster status...') cluster_data = ClusterService.wait_cluster_deploy(project_slug, cluster_name, ConfigFile.read_option(:oidc_token)) if ClusterService.failed?(cluster_data[:state]) Uffizzi.ui.say_error_and_exit("Cluster with name: #{cluster_name} failed to be created.") end handle_succeed_cluster_creation(cluster_data) parse_kubeconfig(cluster_data[:kubeconfig]) end def handle_succeed_cluster_creation(cluster_data) kubeconfig_path = options[:kubeconfig] || KubeconfigService.default_path parsed_kubeconfig = parse_kubeconfig(cluster_data[:kubeconfig]) cluster_name = cluster_data[:name] Uffizzi.ui.say("Cluster with name: #{cluster_name} was created.") save_kubeconfig(parsed_kubeconfig, kubeconfig_path) update_clusters_config(cluster_data[:id], name: cluster_name, kubeconfig_path: kubeconfig_path) end def save_kubeconfig(kubeconfig, kubeconfig_path) KubeconfigService.save_to_filepath(kubeconfig_path, kubeconfig) do |kubeconfig_by_path| merged_kubeconfig = KubeconfigService.merge(kubeconfig_by_path, kubeconfig) new_current_context = KubeconfigService.get_current_context(kubeconfig) new_kubeconfig = KubeconfigService.update_current_context(merged_kubeconfig, new_current_context) next new_kubeconfig if kubeconfig_by_path.nil? previous_current_context = KubeconfigService.get_current_context(kubeconfig_by_path) save_previous_current_context(kubeconfig_path, previous_current_context) new_kubeconfig end end def update_clusters_config(id, params) clusters_config = Uffizzi::ConfigHelper.update_clusters_config_by_id(id, params) ConfigFile.write_option(:clusters, clusters_config) end def cluster_creation_params(name:, creation_source:, k8s_version:) oidc_token = Uffizzi::ConfigFile.read_option(:oidc_token) { cluster: { name: name, manifest: nil, creation_source: creation_source, k8s_version: k8s_version, }, token: oidc_token, } end def handle_delete_cluster(cluster_id, cluster_name, kubeconfig) return if cluster_id.nil? || cluster_name.nil? exclude_kubeconfig(cluster_id, kubeconfig) if kubeconfig.present? params = { cluster_name: cluster_name, oidc_token: ConfigFile.read_option(:oidc_token), } response = delete_cluster(ConfigFile.read_option(:server), project_slug, params) if ResponseHelper.no_content?(response) Uffizzi.ui.say("Cluster #{cluster_name} deleted") else ResponseHelper.handle_failed_response(response) end end def exclude_kubeconfig(cluster_id, kubeconfig) cluster_config = Uffizzi::ConfigHelper.cluster_config_by_id(cluster_id) return if cluster_config.nil? kubeconfig_path = cluster_config[:kubeconfig_path] ConfigFile.write_option(:clusters, Uffizzi::ConfigHelper.clusters_config_without(cluster_id)) KubeconfigService.save_to_filepath(kubeconfig_path, kubeconfig) do |kubeconfig_by_path| return if kubeconfig_by_path.nil? new_kubeconfig = KubeconfigService.exclude(kubeconfig_by_path, kubeconfig) new_current_context = find_previous_current_context(new_kubeconfig, kubeconfig_path) KubeconfigService.update_current_context(new_kubeconfig, new_current_context) end end def find_previous_current_context(kubeconfig, kubeconfig_path) prev_current_context = Uffizzi::ConfigHelper.previous_current_context_by_path(kubeconfig_path)&.fetch(:current_context, nil) if KubeconfigService.find_cluster_contexts_by_name(kubeconfig, prev_current_context).present? prev_current_context end end def save_previous_current_context(kubeconfig_path, current_context) previous_current_contexts = Uffizzi::ConfigHelper.set_previous_current_context_by_path(kubeconfig_path, current_context) ConfigFile.write_option(:previous_current_contexts, previous_current_contexts) end def parse_kubeconfig(kubeconfig) return if kubeconfig.nil? Psych.safe_load(Base64.decode64(kubeconfig)) end def launch_demonise_skaffold(config_path) Uffizzi.process.daemon(true) at_exit do File.delete(DevService.pid_path) if File.exist?(DevService.pid_path) end File.delete(DevService.logs_path) if File.exist?(DevService.logs_path) File.write(DevService.pid_path, Uffizzi.process.pid) DevService.start_check_pid_file_existence DevService.start_demonised_skaffold(config_path, options) rescue StandardError => e File.open(DevService.logs_path, 'a') { |f| f.puts(e.message) } end def project_slug @project_slug ||= ConfigFile.read_option(:project) end end end