class Hetzner::K3s::CLI
Attributes
configuration[R]
errors[RW]
hetzner_client[R]
k3s_version[R]
used_server_types[RW]
Public Class Methods
exit_on_failure?()
click to toggle source
# File lib/hetzner/k3s/cli.rb, line 13 def self.exit_on_failure? true end
Public Instance Methods
create_cluster()
click to toggle source
# File lib/hetzner/k3s/cli.rb, line 25 def create_cluster validate_config_file :create Cluster.new(hetzner_client: hetzner_client, hetzner_token: find_hetzner_token).create configuration: configuration end
delete_cluster()
click to toggle source
# File lib/hetzner/k3s/cli.rb, line 34 def delete_cluster validate_config_file :delete Cluster.new(hetzner_client: hetzner_client, hetzner_token: find_hetzner_token).delete configuration: configuration end
releases()
click to toggle source
# File lib/hetzner/k3s/cli.rb, line 50 def releases find_available_releases.each do |release| puts release end end
upgrade_cluster()
click to toggle source
# File lib/hetzner/k3s/cli.rb, line 44 def upgrade_cluster validate_config_file :upgrade Cluster.new(hetzner_client: hetzner_client, hetzner_token: find_hetzner_token).upgrade configuration: configuration, new_k3s_version: options[:new_k3s_version], config_file: options[:config_file] end
version()
click to toggle source
# File lib/hetzner/k3s/cli.rb, line 18 def version puts Hetzner::K3s::VERSION end
Private Instance Methods
find_available_releases()
click to toggle source
# File lib/hetzner/k3s/cli.rb, line 189 def find_available_releases @available_releases ||= begin response = HTTP.get("https://api.github.com/repos/k3s-io/k3s/tags").body JSON.parse(response).map { |hash| hash["name"] } end rescue errors << "Cannot fetch the releases with Hetzner API, please try again later" end
find_hetzner_token()
click to toggle source
# File lib/hetzner/k3s/cli.rb, line 328 def find_hetzner_token @token = ENV["HCLOUD_TOKEN"] return @token if @token @token = configuration.dig("hetzner_token") end
kubernetes_client()
click to toggle source
# File lib/hetzner/k3s/cli.rb, line 313 def kubernetes_client return @kubernetes_client if @kubernetes_client config_hash = YAML.load_file(File.expand_path(configuration["kubeconfig_path"])) config_hash['current-context'] = configuration["cluster_name"] @kubernetes_client = K8s::Client.config(K8s::Config.new(config_hash)) errors << "Cannot connect to the Kubernetes cluster" false end
locations()
click to toggle source
# File lib/hetzner/k3s/cli.rb, line 176 def locations return [] unless valid_token? @locations ||= hetzner_client.get("/locations")["locations"].map{ |location| location["name"] } rescue @errors << "Cannot fetch locations with Hetzner API, please try again later" [] end
server_types()
click to toggle source
# File lib/hetzner/k3s/cli.rb, line 168 def server_types return [] unless valid_token? @server_types ||= hetzner_client.get("/server_types")["server_types"].map{ |server_type| server_type["name"] } rescue @errors << "Cannot fetch server types with Hetzner API, please try again later" false end
valid_token?()
click to toggle source
# File lib/hetzner/k3s/cli.rb, line 113 def valid_token? return @valid unless @valid.nil? begin token = find_hetzner_token @hetzner_client = Hetzner::Client.new(token: token) response = hetzner_client.get("/locations") error_code = response.dig("error", "code") @valid = if error_code and error_code.size > 0 false else true end rescue @valid = false end end
validate_cluster_name()
click to toggle source
# File lib/hetzner/k3s/cli.rb, line 135 def validate_cluster_name errors << "Cluster name is an invalid format (only lowercase letters, digits and dashes are allowed)" unless configuration["cluster_name"] =~ /\A[a-z\d-]+\z/ errors << "Ensure that the cluster name starts with a normal letter" unless configuration["cluster_name"] =~ /\A[a-z]+.*\z/ end
validate_config_file(action)
click to toggle source
# File lib/hetzner/k3s/cli.rb, line 61 def validate_config_file(action) config_file_path = options[:config_file] if File.exists?(config_file_path) begin @configuration = YAML.load_file(options[:config_file]) raise "invalid" unless configuration.is_a? Hash rescue puts "Please ensure that the config file is a correct YAML manifest." return end else puts "Please specify a correct path for the config file." return end @errors = [] @used_server_types = [] validate_token validate_cluster_name validate_kubeconfig_path case action when :create validate_ssh_key validate_ssh_allowed_networks validate_location validate_k3s_version validate_masters validate_worker_node_pools validate_verify_host_key when :delete validate_kubeconfig_path_must_exist when :upgrade validate_kubeconfig_path_must_exist validate_new_k3s_version validate_new_k3s_version_must_be_more_recent end errors.flatten! unless errors.empty? puts "Some information in the configuration file requires your attention:" errors.each do |error| puts " - #{error}" end exit 1 end end
validate_instance_group(instance_group, workers: true)
click to toggle source
# File lib/hetzner/k3s/cli.rb, line 281 def validate_instance_group(instance_group, workers: true) instance_group_errors = [] instance_group_type = workers ? "Worker mode pool #{instance_group["name"]}" : "Masters pool" unless !workers || instance_group["name"] =~ /\A([A-Za-z0-9\-\_]+)\Z/ instance_group_errors << "#{instance_group_type} has an invalid name" end unless instance_group.is_a? Hash instance_group_errors << "#{instance_group_type} is in an invalid format" end unless !valid_token? or server_types.include?(instance_group["instance_type"]) instance_group_errors << "#{instance_group_type} has an invalid instance type" end if instance_group["instance_count"].is_a? Integer if instance_group["instance_count"] < 1 instance_group_errors << "#{instance_group_type} must have at least one node" elsif !workers instance_group_errors << "Masters count must equal to 1 for non-HA clusters or an odd number (recommended 3) for an HA cluster" unless instance_group["instance_count"].odd? end else instance_group_errors << "#{instance_group_type} has an invalid instance count" end used_server_types << instance_group["instance_type"] errors << instance_group_errors end
validate_k3s_version()
click to toggle source
# File lib/hetzner/k3s/cli.rb, line 198 def validate_k3s_version k3s_version = configuration.dig("k3s_version") available_releases = find_available_releases errors << "Invalid k3s version" unless available_releases.include? k3s_version end
validate_kubeconfig_path()
click to toggle source
# File lib/hetzner/k3s/cli.rb, line 140 def validate_kubeconfig_path path = File.expand_path(configuration.dig("kubeconfig_path")) errors << "kubeconfig path cannot be a directory" and return if File.directory? path directory = File.dirname(path) errors << "Directory #{directory} doesn't exist" unless File.exists? directory rescue errors << "Invalid path for the kubeconfig" end
validate_kubeconfig_path_must_exist()
click to toggle source
# File lib/hetzner/k3s/cli.rb, line 160 def validate_kubeconfig_path_must_exist path = File.expand_path configuration.dig("kubeconfig_path") errors << "kubeconfig path is invalid" and return unless File.exists? path errors << "kubeconfig path cannot be a directory" if File.directory? path rescue errors << "Invalid kubeconfig path" end
validate_location()
click to toggle source
# File lib/hetzner/k3s/cli.rb, line 184 def validate_location return if locations.empty? && !valid_token? errors << "Invalid location - available locations: nbg1 (Nuremberg, Germany), fsn1 (Falkenstein, Germany), hel1 (Helsinki, Finland)" unless locations.include? configuration.dig("location") end
validate_masters()
click to toggle source
# File lib/hetzner/k3s/cli.rb, line 210 def validate_masters masters_pool = nil begin masters_pool = configuration.dig("masters") rescue errors << "Invalid masters configuration" return end if masters_pool.nil? errors << "Invalid masters configuration" return end validate_instance_group masters_pool, workers: false end
validate_new_k3s_version()
click to toggle source
# File lib/hetzner/k3s/cli.rb, line 204 def validate_new_k3s_version new_k3s_version = options[:new_k3s_version] available_releases = find_available_releases errors << "The new k3s version is invalid" unless available_releases.include? new_k3s_version end
validate_new_k3s_version_must_be_more_recent()
click to toggle source
# File lib/hetzner/k3s/cli.rb, line 251 def validate_new_k3s_version_must_be_more_recent return if options[:force] == "true" return unless kubernetes_client begin Timeout::timeout(5) do servers = kubernetes_client.api("v1").resource("nodes").list if servers.size == 0 errors << "The cluster seems to have no nodes, nothing to upgrade" else available_releases = find_available_releases current_k3s_version = servers.first.dig(:status, :nodeInfo, :kubeletVersion) current_k3s_version_index = available_releases.index(current_k3s_version) || 1000 new_k3s_version = options[:new_k3s_version] new_k3s_version_index = available_releases.index(new_k3s_version) || 1000 unless new_k3s_version_index < current_k3s_version_index errors << "The new k3s version must be more recent than the current one" end end end rescue Timeout::Error puts "Cannot upgrade: Unable to fetch nodes from Kubernetes API. Is the cluster online?" end end
validate_ssh_allowed_networks()
click to toggle source
# File lib/hetzner/k3s/cli.rb, line 334 def validate_ssh_allowed_networks networks ||= configuration.dig("ssh_allowed_networks") if networks.nil? or networks.empty? errors << "At least one network/IP range must be specified for SSH access" return end invalid_networks = networks.reject do |network| IPAddr.new(network) rescue false end unless invalid_networks.empty? invalid_networks.each do |network| errors << "The network #{network} is an invalid range" end end invalid_ranges = networks.reject do |network| network.include? "/" end unless invalid_ranges.empty? invalid_ranges.each do |network| errors << "Please use the CIDR notation for the networks to avoid ambiguity" end end return unless invalid_networks.empty? current_ip = URI.open('http://whatismyip.akamai.com').read current_ip_networks = networks.detect do |network| IPAddr.new(network).include?(current_ip) rescue false end unless current_ip_networks errors << "Your current IP #{current_ip} is not included into any of the networks you've specified, so we won't be able to SSH into the nodes" end end
validate_ssh_key()
click to toggle source
# File lib/hetzner/k3s/cli.rb, line 150 def validate_ssh_key path = File.expand_path(configuration.dig("ssh_key_path")) errors << "Invalid Public SSH key path" and return unless File.exists? path key = File.read(path) errors << "Public SSH key is invalid" unless ::SSHKey.valid_ssh_public_key? key rescue errors << "Invalid Public SSH key path" end
validate_token()
click to toggle source
# File lib/hetzner/k3s/cli.rb, line 131 def validate_token errors << "Invalid Hetzner Cloud token" unless valid_token? end
validate_verify_host_key()
click to toggle source
# File lib/hetzner/k3s/cli.rb, line 323 def validate_verify_host_key return unless [true, false].include?(configuration.fetch("ssh_key_path", false)) errors << "Please set the verify_host_key option to either true or false" end
validate_worker_node_pools()
click to toggle source
# File lib/hetzner/k3s/cli.rb, line 228 def validate_worker_node_pools worker_node_pools = nil begin worker_node_pools = configuration.dig("worker_node_pools") rescue errors << "Invalid node pools configuration" return end if !worker_node_pools.is_a? Array errors << "Invalid node pools configuration" elsif worker_node_pools.size == 0 errors << "At least one node pool is required in order to schedule workloads" elsif worker_node_pools.map{ |worker_node_pool| worker_node_pool["name"]}.uniq.size != worker_node_pools.size errors << "Each node pool must have an unique name" elsif server_types worker_node_pools.each do |worker_node_pool| validate_instance_group worker_node_pool end end end