From ca27cd7064fcc8c94549d83b001dcaed5bbe03e4 Mon Sep 17 00:00:00 2001 From: Kimmo Lehto Date: Thu, 25 Apr 2019 16:03:05 +0300 Subject: [PATCH 1/4] Pharos init command for cluster.yml bootstrapping --- lib/pharos/init_command.rb | 129 ++++++++++++++++++++ lib/pharos/phases/configure_cluster_name.rb | 6 +- lib/pharos/root_command.rb | 2 + 3 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 lib/pharos/init_command.rb diff --git a/lib/pharos/init_command.rb b/lib/pharos/init_command.rb new file mode 100644 index 000000000..d2428a992 --- /dev/null +++ b/lib/pharos/init_command.rb @@ -0,0 +1,129 @@ +# frozen_string_literal: true + +module Pharos + class InitCommand < Pharos::Command + using Pharos::CoreExt::DeepTransformKeys + + option %w(-c --config), 'FILE', 'output filename', default: 'cluster.yml', attribute_name: :config_file + option '--defaults', :flag, 'include all configuration default values' + + option %w(-m --master), 'HOST', 'master host [user@]address[:port]' do |master| + @hosts ||= [] + @hosts << parse_host(master).merge(role: 'master') + end + + option %w(-w --worker), 'HOST', 'worker host [user@]address[:port]' do |worker| + @hosts ||= [] + @hosts << parse_host(worker).merge(role: 'worker') + end + + option %w(-b --bastion), 'HOST', 'bastion (ssh proxy) host [user@]address[:port]' do |bastion| + parse_host(bastion) + end + + option %w(-n --name), 'NAME', 'cluster name' + + def parse_host(host_str) + user = host_str[/(.+?)@/, 1] + address = host_str[/(?:.+?@)?([^:]+)/, 1] + ssh_port = host_str[/:(\d+)/, 1]&.to_i + { address: address, user: user, ssh_port: ssh_port } + end + + PRESET_CFG = <<~CONFIG_TPL + # For full configuration reference, see https://pharos.sh/docs/configuration/ + --- + name: <%= name %> + + host_defaults: &host_defaults + user: <%= host_defaults[:user] %> + ssh_key_path: <%= host_defaults[:ssh_key_path] %> + ssh_port: 22 + # environment: + # HTTP_PROXY: 192.168.0.1 + <%- if bastion -%> + bastion: + address: <%= bastion[:address] %> + <%- if bastion[:user] -%> + user: <%= bastion[:user] %> + <%- end -%> + <%- if bastion[:ssh_port] -%> + ssh_port: <%= bastion[:ssh_port] %> + <%- end -%> + # ssh_key_path: ~/.ssh/id_rsa + <%- else -%> + # bastion: + # address: 192.168.0.1 + # user: bastion + # ssh_key_path: ~/.ssh/id_rsa + <%- end -%> + + hosts: + <%- hosts.each do |host| -%> + - <<: *host_defaults + address: <%= host[:address] %> + <%- if host[:user] != host_defaults[:user] -%> + user: <%= host[:user] %> + <%- end -%> + <%- if host[:private_address] -%> + private_address: <%= host[:private_address] %> + <%- end -%> + <%- if host[:ssh_port] != host_defaults[:ssh_port] -%> + ssh_port: <%= host[:ssh_port] %> + <%- end -%> + role: <%= host[:role] %> + <%- end -%> + + network: + provider: weave + + addons: + ingress-nginx: + enabled: true + CONFIG_TPL + + def default_name + Pharos::Phases::ConfigureClusterName.new('').random_name + end + + def host_defaults + {}.tap do |defaults| + defaults[:ssh_key_path] = '~/.ssh/id_rsa' + defaults[:bastion] = bastion if bastion + defaults[:user] = hosts.find { |h| h[:user] }&.fetch(:user) || ENV['USER'] || 'username' + end + end + + def hosts + @hosts ||= [ + { address: '10.0.0.1', private_address: '172.16.0.1', role: 'master', ssh_key_path: '~/.ssh/id_rsa' }, + { address: '10.0.0.2', private_address: '172.16.0.2', role: 'worker', ssh_key_path: '~/.ssh/id_rsa' } + ] + end + + def config_content + require 'pharos/phases/configure_cluster_name' + + if defaults? + hosts.each { |h| h.replace(host_defaults.merge(h)) } + { + name: name, + hosts: hosts.map { |h| h.merge(host_defaults) } + }.merge(Pharos::Config.new.to_h).deep_stringify_keys.to_yaml + else + Pharos::YamlFile.new(StringIO.new(PRESET_CFG)).erb_result( + hosts: hosts, + bastion: bastion, + host_defaults: host_defaults, + name: name + ) + end + end + + def execute + signal_error "configuration file #{config_file} already exists" if File.exist?(config_file) + File.write(config_file, config_content) + end + end +end + diff --git a/lib/pharos/phases/configure_cluster_name.rb b/lib/pharos/phases/configure_cluster_name.rb index cf3716947..de374d9a5 100644 --- a/lib/pharos/phases/configure_cluster_name.rb +++ b/lib/pharos/phases/configure_cluster_name.rb @@ -71,10 +71,14 @@ def generate_new_name return false if @config.name return nil if cluster_context['no-generate-name'] - new_name = "#{ADJECTIVES.sample}-#{NOUNS.sample}-#{'%04d' % rand(9999)}" + new_name = random_name logger.info "Using generated random name #{new_name.magenta} as cluster name" new_name end + + def random_name + "#{ADJECTIVES.sample}-#{NOUNS.sample}-#{'%04d' % rand(9999)}" + end end end end diff --git a/lib/pharos/root_command.rb b/lib/pharos/root_command.rb index c9b624f4b..2e6d6d4bc 100644 --- a/lib/pharos/root_command.rb +++ b/lib/pharos/root_command.rb @@ -6,6 +6,7 @@ require_relative 'kubeconfig_command' require_relative 'exec_command' require_relative 'terraform_command' +require_relative 'init_command' module Pharos class RootCommand < Pharos::Command @@ -16,6 +17,7 @@ class RootCommand < Pharos::Command subcommand "reset", "reset cluster", ResetCommand subcommand %w(exec ssh), "run a command or an interactive session on a host", ExecCommand subcommand %w(tf terraform), "terraform specific commands", TerraformCommand + subcommand "init", "create an initial cluster configuration file", InitCommand subcommand "version", "show version information", VersionCommand end end From bed24fcd53c8c8358ab2a90a0b1599c57db405d4 Mon Sep 17 00:00:00 2001 From: Kimmo Lehto Date: Thu, 25 Apr 2019 16:27:20 +0300 Subject: [PATCH 2/4] Add -e and -i --- lib/pharos/init_command.rb | 41 +++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/lib/pharos/init_command.rb b/lib/pharos/init_command.rb index d2428a992..fd82fcbc6 100644 --- a/lib/pharos/init_command.rb +++ b/lib/pharos/init_command.rb @@ -23,6 +23,12 @@ class InitCommand < Pharos::Command option %w(-n --name), 'NAME', 'cluster name' + option %w(-e --env), 'KEY=VALUE', 'host environment variables (can be given multiple times)', multivalued: true do |kv_pair| + Hash[*kv_pair.split('=', 2)] + end + + option %w(-i --ssh-key-path), 'PATH', 'ssh key path' + def parse_host(host_str) user = host_str[/(.+?)@/, 1] address = host_str[/(?:.+?@)?([^:]+)/, 1] @@ -37,20 +43,35 @@ def parse_host(host_str) host_defaults: &host_defaults user: <%= host_defaults[:user] %> + <%- if host_defaults[:ssh_key_path] -%> ssh_key_path: <%= host_defaults[:ssh_key_path] %> + <%- else -%> + # ssh_key_path: ~/.ssh/id_rsa + <%- end -%> ssh_port: 22 + <%- if host_defaults[:environment] -%> + environment: + <%- host_defaults[:environment].each do |key, value| -%> + <%= key %>: <%= value.inspect %> + <%- end -%> + <%- else -%> # environment: # HTTP_PROXY: 192.168.0.1 - <%- if bastion -%> + <%- end -%> + <%- if host_defaults[:bastion] -%> bastion: - address: <%= bastion[:address] %> - <%- if bastion[:user] -%> - user: <%= bastion[:user] %> + address: <%= host_defaults[:bastion][:address] %> + <%- if host_defaults[:bastion][:user] -%> + user: <%= host_defaults[:bastion][:user] %> <%- end -%> - <%- if bastion[:ssh_port] -%> - ssh_port: <%= bastion[:ssh_port] %> + <%- if host_defaults[:bastion][:ssh_port] -%> + ssh_port: <%= host_defaults[:bastion][:ssh_port] %> <%- end -%> + <%- if host_defaults[:ssh_key_path] -%> + ssh_key_path: <%= host_defaults[:ssh_key_path] %> + <%- else -%> # ssh_key_path: ~/.ssh/id_rsa + <%- end -%> <%- else -%> # bastion: # address: 192.168.0.1 @@ -67,6 +88,8 @@ def parse_host(host_str) <%- end -%> <%- if host[:private_address] -%> private_address: <%= host[:private_address] %> + <%- else -%> + # private_address: 10.0.0.1 <%- end -%> <%- if host[:ssh_port] != host_defaults[:ssh_port] -%> ssh_port: <%= host[:ssh_port] %> @@ -88,14 +111,16 @@ def default_name def host_defaults {}.tap do |defaults| - defaults[:ssh_key_path] = '~/.ssh/id_rsa' defaults[:bastion] = bastion if bastion defaults[:user] = hosts.find { |h| h[:user] }&.fetch(:user) || ENV['USER'] || 'username' + defaults[:environment] = env_list.inject(:merge) unless env_list.empty? + defaults[:ssh_key_path] = ssh_key_path if ssh_key_path end end def hosts @hosts ||= [ + # dummy example hosts { address: '10.0.0.1', private_address: '172.16.0.1', role: 'master', ssh_key_path: '~/.ssh/id_rsa' }, { address: '10.0.0.2', private_address: '172.16.0.2', role: 'worker', ssh_key_path: '~/.ssh/id_rsa' } ] @@ -113,7 +138,6 @@ def config_content else Pharos::YamlFile.new(StringIO.new(PRESET_CFG)).erb_result( hosts: hosts, - bastion: bastion, host_defaults: host_defaults, name: name ) @@ -126,4 +150,3 @@ def execute end end end - From 0688f3259238fb34d7f43e7136aef12d55eaa963 Mon Sep 17 00:00:00 2001 From: Kimmo Lehto Date: Thu, 25 Apr 2019 16:33:15 +0300 Subject: [PATCH 3/4] More multivalues --- lib/pharos/init_command.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/pharos/init_command.rb b/lib/pharos/init_command.rb index fd82fcbc6..4e4a7094a 100644 --- a/lib/pharos/init_command.rb +++ b/lib/pharos/init_command.rb @@ -4,15 +4,17 @@ module Pharos class InitCommand < Pharos::Command using Pharos::CoreExt::DeepTransformKeys + banner 'Create a Pharos cluster configuration' + option %w(-c --config), 'FILE', 'output filename', default: 'cluster.yml', attribute_name: :config_file option '--defaults', :flag, 'include all configuration default values' - option %w(-m --master), 'HOST', 'master host [user@]address[:port]' do |master| + option %w(-m --master), 'HOST', 'master host [user@]address[:port] (can be given multiple times)' do |master| @hosts ||= [] @hosts << parse_host(master).merge(role: 'master') end - option %w(-w --worker), 'HOST', 'worker host [user@]address[:port]' do |worker| + option %w(-w --worker), 'HOST', 'worker host [user@]address[:port] (can be given multiple times)' do |worker| @hosts ||= [] @hosts << parse_host(worker).merge(role: 'worker') end From 26fd526e9ee9916896420936b263fde26e547d06 Mon Sep 17 00:00:00 2001 From: Kimmo Lehto Date: Fri, 26 Apr 2019 08:53:37 +0300 Subject: [PATCH 4/4] Fix empty user --- lib/pharos/init_command.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pharos/init_command.rb b/lib/pharos/init_command.rb index 4e4a7094a..1a06db99e 100644 --- a/lib/pharos/init_command.rb +++ b/lib/pharos/init_command.rb @@ -85,7 +85,7 @@ def parse_host(host_str) <%- hosts.each do |host| -%> - <<: *host_defaults address: <%= host[:address] %> - <%- if host[:user] != host_defaults[:user] -%> + <%- if host[:user] && host[:user] != host_defaults[:user] -%> user: <%= host[:user] %> <%- end -%> <%- if host[:private_address] -%>