bcome 1.3.6 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/bcome +13 -8
- data/lib/bcome.rb +7 -11
- data/lib/objects/bcome/version.rb +19 -1
- data/lib/objects/bootup.rb +13 -5
- data/lib/objects/command/local.rb +2 -0
- data/lib/objects/config_factory.rb +3 -0
- data/lib/objects/driver/base.rb +36 -4
- data/lib/objects/driver/bucket.rb +6 -4
- data/lib/objects/driver/ec2.rb +35 -4
- data/lib/objects/driver/gcp.rb +124 -0
- data/lib/objects/driver/gcp/authentication/api_key.rb +6 -0
- data/lib/objects/driver/gcp/authentication/oauth.rb +101 -0
- data/lib/objects/driver/gcp/authentication/service_account.rb +7 -0
- data/lib/objects/driver/static.rb +2 -0
- data/lib/objects/encryptor.rb +26 -24
- data/lib/objects/exception/argument_error_invoking_method_from_command_line.rb +8 -4
- data/lib/objects/exception/base.rb +14 -10
- data/lib/objects/exception/can_only_subselect_on_inventory.rb +8 -4
- data/lib/objects/exception/cannot_authenticate_to_gcp.rb +11 -0
- data/lib/objects/exception/cannot_find_internal_registry_klass.rb +8 -4
- data/lib/objects/exception/cannot_find_inventory.rb +11 -0
- data/lib/objects/exception/cannot_find_subselection_parent.rb +8 -4
- data/lib/objects/exception/cant_find_key_in_cloud_tags.rb +8 -4
- data/lib/objects/exception/cant_find_key_in_metadata.rb +8 -4
- data/lib/objects/exception/cant_find_proxy_host_by_identifier.rb +8 -4
- data/lib/objects/exception/cant_find_proxy_host_by_namespace.rb +8 -4
- data/lib/objects/exception/could_not_initiate_ssh_connection.rb +8 -4
- data/lib/objects/exception/could_not_initiate_ssh_connection_through_backend_proxy.rb +8 -4
- data/lib/objects/exception/could_not_retrieve_terraform_output.rb +11 -0
- data/lib/objects/exception/deprecation_warning.rb +9 -7
- data/lib/objects/exception/duplicate_command_line_argument_key.rb +8 -4
- data/lib/objects/exception/ec2_driver_missing_provisioning_region.rb +8 -4
- data/lib/objects/exception/failed_to_run_local_command.rb +8 -4
- data/lib/objects/exception/generic.rb +11 -0
- data/lib/objects/exception/interactive_session_halt.rb +6 -2
- data/lib/objects/exception/invalid_bcome_breadcrumb.rb +8 -4
- data/lib/objects/exception/invalid_breadcrumb.rb +8 -4
- data/lib/objects/exception/invalid_context_command.rb +8 -4
- data/lib/objects/exception/invalid_gcp_authentication_scheme.rb +11 -0
- data/lib/objects/exception/invalid_identifier.rb +8 -4
- data/lib/objects/exception/invalid_machines_cache_config.rb +8 -4
- data/lib/objects/exception/invalid_matcher_query.rb +8 -4
- data/lib/objects/exception/invalid_meta_data_config.rb +8 -4
- data/lib/objects/exception/invalid_metadata_encryption_key.rb +8 -4
- data/lib/objects/exception/invalid_network_config.rb +8 -4
- data/lib/objects/exception/invalid_network_driver_type.rb +8 -4
- data/lib/objects/exception/invalid_port_forward_request.rb +11 -0
- data/lib/objects/exception/invalid_proxy_config.rb +8 -4
- data/lib/objects/exception/invalid_regexp_matcher_in_registry.rb +8 -4
- data/lib/objects/exception/invalid_registry_arguments_type.rb +8 -4
- data/lib/objects/exception/invalid_registry_command_name_length.rb +8 -4
- data/lib/objects/exception/invalid_registry_data_config.rb +8 -4
- data/lib/objects/exception/invalid_restriction_key_in_registry.rb +8 -4
- data/lib/objects/exception/invalid_ssh_config.rb +8 -4
- data/lib/objects/exception/inventories_cannot_have_subviews.rb +8 -4
- data/lib/objects/exception/malformed_command_line_arguments.rb +8 -4
- data/lib/objects/exception/method_invocation_requires_parameter.rb +8 -4
- data/lib/objects/exception/method_name_conflict_in_registry.rb +8 -4
- data/lib/objects/exception/missing_argument_for_registry_command.rb +8 -4
- data/lib/objects/exception/missing_description_on_view.rb +8 -4
- data/lib/objects/exception/missing_execute_on_registry_object.rb +8 -4
- data/lib/objects/exception/missing_gcp_authentication_scheme.rb +11 -0
- data/lib/objects/exception/missing_gcp_service_scopes.rb +11 -0
- data/lib/objects/exception/missing_identifier_on_view.rb +8 -4
- data/lib/objects/exception/missing_inventory_contributors.rb +11 -0
- data/lib/objects/exception/missing_ip_address_on_server.rb +8 -4
- data/lib/objects/exception/missing_network_config.rb +8 -4
- data/lib/objects/exception/missing_or_invalid_client_secrets.rb +11 -0
- data/lib/objects/exception/missing_params_for_rsync.rb +8 -4
- data/lib/objects/exception/missing_params_for_scp.rb +8 -4
- data/lib/objects/exception/missing_subselection_key.rb +8 -4
- data/lib/objects/exception/missing_type_on_view.rb +8 -4
- data/lib/objects/exception/no_node_found_for_breadcrumb.rb +8 -4
- data/lib/objects/exception/no_node_named_by_identifier.rb +8 -4
- data/lib/objects/exception/node_identifiers_must_be_unique.rb +8 -4
- data/lib/objects/exception/orchestration_script_does_not_exist.rb +8 -4
- data/lib/objects/exception/proxy_host_node_does_not_have_public_ip_address.rb +8 -4
- data/lib/objects/exception/unknown_dynamic_server_type.rb +11 -0
- data/lib/objects/exception/unknown_method_for_namespace.rb +8 -4
- data/lib/objects/interactive/session.rb +4 -1
- data/lib/objects/interactive/session_item/base.rb +2 -0
- data/lib/objects/interactive/session_item/capture_input.rb +2 -0
- data/lib/objects/interactive/session_item/transparent_ssh.rb +29 -23
- data/lib/objects/loading_bar/handler.rb +80 -0
- data/lib/objects/loading_bar/indicator/base.rb +64 -0
- data/lib/objects/loading_bar/indicator/basic.rb +34 -0
- data/lib/objects/loading_bar/indicator/progress.rb +26 -0
- data/lib/objects/loading_bar/pid_bucket.rb +27 -0
- data/lib/objects/modules/context.rb +13 -9
- data/lib/objects/modules/registry_management.rb +16 -10
- data/lib/objects/modules/ui_output.rb +10 -6
- data/lib/objects/modules/workspace_commands.rb +159 -155
- data/lib/objects/modules/workspace_menu.rb +129 -130
- data/lib/objects/node/attributes.rb +13 -21
- data/lib/objects/node/base.rb +113 -71
- data/lib/objects/node/cache_handler.rb +2 -0
- data/lib/objects/node/collection.rb +10 -9
- data/lib/objects/node/factory.rb +35 -28
- data/lib/objects/node/inventory/base.rb +100 -100
- data/lib/objects/node/inventory/defined.rb +110 -89
- data/lib/objects/node/inventory/merge.rb +43 -0
- data/lib/objects/node/inventory/subselect.rb +64 -46
- data/lib/objects/node/kube/base.rb +51 -0
- data/lib/objects/node/kube/container.rb +9 -0
- data/lib/objects/node/kube/estate.rb +19 -0
- data/lib/objects/node/kube/namespace.rb +24 -0
- data/lib/objects/node/kube/pod.rb +24 -0
- data/lib/objects/node/kube_wrap.rb +26 -0
- data/lib/objects/node/meta/base.rb +8 -1
- data/lib/objects/node/meta/cloud.rb +2 -0
- data/lib/objects/node/meta/local.rb +2 -0
- data/lib/objects/node/meta_data_factory.rb +3 -1
- data/lib/objects/node/meta_data_loader.rb +27 -28
- data/lib/objects/node/resources/base.rb +5 -1
- data/lib/objects/node/resources/inventory.rb +7 -5
- data/lib/objects/node/resources/merged.rb +38 -0
- data/lib/objects/node/resources/sub_inventory.rb +7 -4
- data/lib/objects/node/server/base.rb +88 -66
- data/lib/objects/node/server/dynamic/base.rb +23 -0
- data/lib/objects/node/server/{dynamic.rb → dynamic/ec2.rb} +14 -13
- data/lib/objects/node/server/dynamic/gcp.rb +47 -0
- data/lib/objects/node/server/static.rb +13 -2
- data/lib/objects/orchestration/base.rb +10 -0
- data/lib/objects/orchestration/interactive_terraform.rb +62 -27
- data/lib/objects/orchestrator.rb +22 -0
- data/lib/objects/parser/bread_crumb.rb +3 -1
- data/lib/objects/registry/arguments/base.rb +3 -1
- data/lib/objects/registry/arguments/command_line.rb +6 -1
- data/lib/objects/registry/arguments/console.rb +4 -1
- data/lib/objects/registry/command/base.rb +3 -0
- data/lib/objects/registry/command/external.rb +4 -2
- data/lib/objects/registry/command/group.rb +6 -3
- data/lib/objects/registry/command/internal.rb +3 -1
- data/lib/objects/registry/command/shortcut.rb +17 -9
- data/lib/objects/registry/command_list.rb +2 -0
- data/lib/objects/registry/loader.rb +10 -10
- data/lib/objects/ssh/bootstrap.rb +3 -1
- data/lib/objects/ssh/command.rb +10 -5
- data/lib/objects/ssh/command_exec.rb +13 -9
- data/lib/objects/ssh/connection_wrangler.rb +105 -0
- data/lib/objects/ssh/connector.rb +100 -0
- data/lib/objects/ssh/driver.rb +27 -230
- data/lib/objects/ssh/driver_concerns/command_strings.rb +17 -0
- data/lib/objects/ssh/driver_concerns/connection.rb +78 -0
- data/lib/objects/ssh/driver_concerns/functions.rb +89 -0
- data/lib/objects/ssh/driver_concerns/user.rb +32 -0
- data/lib/objects/ssh/{proxy_data.rb → proxy_hop.rb} +52 -7
- data/lib/objects/ssh/script_exec.rb +4 -1
- data/lib/objects/ssh/tunnel/local_port_forward.rb +5 -6
- data/lib/objects/ssh/tunnel_keeper.rb +21 -0
- data/lib/objects/ssh/window.rb +31 -0
- data/lib/objects/startup.rb +52 -0
- data/lib/objects/system/local.rb +3 -0
- data/lib/objects/terraform/output.rb +41 -0
- data/lib/objects/workspace.rb +3 -14
- data/patches/irb.rb +27 -3
- data/patches/string-encrypt.rb +20 -23
- data/patches/string.rb +5 -1
- data/patches/string_stylesheet.rb +2 -0
- metadata +95 -18
- data/lib/objects/progress_bar.rb +0 -30
- data/lib/objects/ssh/connection_handler.rb +0 -101
- data/lib/objects/terraform/parser.rb +0 -23
- data/lib/objects/terraform/state.rb +0 -40
@@ -1,6 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Bcome::Registry::Command
|
2
4
|
class Internal < Base
|
3
|
-
# In which the bcome context is an
|
5
|
+
# In which the bcome context is an internal (extended framework) call
|
4
6
|
|
5
7
|
def execute(node, arguments)
|
6
8
|
merged_arguments = process_arguments(arguments)
|
@@ -1,24 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Bcome::Registry::Command
|
2
4
|
class Shortcut < Base
|
3
5
|
# In which the bcome context is a shortcut to a more complex command
|
4
6
|
|
5
|
-
def execute(node,
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
def execute(node, _arguments) ## We'll add in arguments later
|
8
|
+
begin
|
9
|
+
if run_as_pseudo_tty?
|
10
|
+
node.pseudo_tty command
|
11
|
+
else
|
12
|
+
puts "\n(#{node.namespace})$".terminal_prompt + ">\s#{command}"
|
13
|
+
::Bcome::Orchestrator.instance.tail_all_command_output!(node)
|
14
|
+
node.run command
|
15
|
+
::Bcome::Orchestrator.instance.reset!
|
16
|
+
end
|
17
|
+
rescue Interrupt
|
18
|
+
::Bcome::Orchestrator.instance.reset!
|
19
|
+
puts "\nExiting gracefully from interrupt\n".warning
|
10
20
|
end
|
11
|
-
|
12
|
-
puts "\nExiting gracefully from interrupt\n".warning
|
21
|
+
nil
|
13
22
|
end
|
14
23
|
|
15
24
|
def command
|
16
25
|
@data[:shortcut_command]
|
17
26
|
end
|
18
|
-
|
27
|
+
|
19
28
|
def run_as_pseudo_tty?
|
20
29
|
@data[:run_as_pseudo_tty]
|
21
30
|
end
|
22
|
-
|
23
31
|
end
|
24
32
|
end
|
@@ -1,8 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Bcome::Registry
|
2
4
|
class Loader
|
3
5
|
include ::Singleton
|
4
6
|
|
5
|
-
FILE_PATH = 'bcome/registry.yml'
|
7
|
+
FILE_PATH = 'bcome/registry.yml'
|
6
8
|
|
7
9
|
def data
|
8
10
|
@data ||= do_load
|
@@ -17,19 +19,17 @@ module Bcome::Registry
|
|
17
19
|
|
18
20
|
data.each do |key, commands|
|
19
21
|
begin
|
20
|
-
if /^#{key
|
22
|
+
if /^#{key}$/.match(node.keyed_namespace)
|
21
23
|
commands.each do |c|
|
22
|
-
|
23
24
|
unless c[:console_command]
|
24
25
|
error_message = "Registry method is missing key 'console_command'."
|
25
26
|
error_message += "\n\n#{c.inspect}"
|
26
|
-
raise Bcome::Exception::InvalidRegistryDataConfig
|
27
|
+
raise Bcome::Exception::InvalidRegistryDataConfig, error_message
|
27
28
|
end
|
28
29
|
|
29
30
|
# Verify that the proposed user registered method does not conflict with either an existing method name, instance var, or other registry command name for this node
|
30
|
-
if node.is_node_level_method?(c[:console_command]) || command_group.console_method_name_exists?(c[:console_command])
|
31
|
-
|
32
|
-
end
|
31
|
+
raise Bcome::Exception::MethodNameConflictInRegistry, "'#{c[:console_command]}'" if node.is_node_level_method?(c[:console_command]) || command_group.console_method_name_exists?(c[:console_command])
|
32
|
+
|
33
33
|
command_group << ::Bcome::Registry::Command::Base.new_from_raw_command(c) unless restrict_config?(node, c)
|
34
34
|
::Bcome::Registry::CommandList.instance.register(node, c[:console_command].to_sym)
|
35
35
|
end
|
@@ -47,11 +47,10 @@ module Bcome::Registry
|
|
47
47
|
|
48
48
|
def restrict_config?(node, command_config)
|
49
49
|
return false unless command_config.key?(:restrict_to_node)
|
50
|
+
|
50
51
|
node_klass_mapping = restriction_to_node_klass_mappings[command_config[:restrict_to_node].to_sym]
|
51
52
|
|
52
|
-
unless node_klass_mapping
|
53
|
-
raise Bcome::Exception::InvalidRestrictionKeyInRegistry, "'#{command_config[:restrict_to_node]}' is invalid. Valid keys: #{restriction_to_node_klass_mappings.keys.join(', ')}"
|
54
|
-
end
|
53
|
+
raise Bcome::Exception::InvalidRestrictionKeyInRegistry, "'#{command_config[:restrict_to_node]}' is invalid. Valid keys: #{restriction_to_node_klass_mappings.keys.join(', ')}" unless node_klass_mapping
|
55
54
|
|
56
55
|
!node.is_a?(node_klass_mapping)
|
57
56
|
end
|
@@ -66,6 +65,7 @@ module Bcome::Registry
|
|
66
65
|
|
67
66
|
def do_load
|
68
67
|
return {} unless File.exist?(FILE_PATH)
|
68
|
+
|
69
69
|
begin
|
70
70
|
file_data = YAML.load_file(FILE_PATH).deep_symbolize_keys
|
71
71
|
rescue Psych::SyntaxError => e
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Bcome::Ssh
|
2
4
|
class Bootstrap
|
3
5
|
def initialize(config)
|
@@ -7,7 +9,7 @@ module Bcome::Ssh
|
|
7
9
|
def ssh_key_path
|
8
10
|
key_path = `eval directory=#{@config[:ssh_key_path]}; echo $directory`
|
9
11
|
key_path =~ /(.+)\n/
|
10
|
-
|
12
|
+
Regexp.last_match(1)
|
11
13
|
end
|
12
14
|
|
13
15
|
def user
|
data/lib/objects/ssh/command.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ::Bcome::Ssh
|
2
4
|
class Command
|
3
|
-
attr_reader :raw, :stdout, :stderr, :exit_code, :node
|
5
|
+
attr_reader :raw, :stdout, :stderr, :exit_code, :node
|
4
6
|
|
5
7
|
def initialize(params)
|
6
8
|
@raw = params[:raw]
|
@@ -18,11 +20,14 @@ module ::Bcome::Ssh
|
|
18
20
|
is_success? ? 'success'.success : 'failure'.error
|
19
21
|
end
|
20
22
|
|
21
|
-
attr_writer :bootstrap
|
22
|
-
|
23
23
|
def output
|
24
|
-
|
25
|
-
|
24
|
+
cmd_output = @stdout
|
25
|
+
|
26
|
+
unless is_success?
|
27
|
+
cmd_output += "\nExit code: #{@exit_code}"
|
28
|
+
cmd_output += "\nSTDERR: #{@stderr}" unless @stderr.empty?
|
29
|
+
end
|
30
|
+
"\n#{cmd_output}"
|
26
31
|
end
|
27
32
|
|
28
33
|
def is_success?
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ::Bcome::Ssh
|
2
4
|
class CommandExec
|
3
5
|
attr_reader :commands
|
@@ -10,14 +12,15 @@ module ::Bcome::Ssh
|
|
10
12
|
@output_string = "#{@output_string}#{output_string}"
|
11
13
|
end
|
12
14
|
|
15
|
+
def log_window
|
16
|
+
::Bcome::Ssh::Window.instance
|
17
|
+
end
|
18
|
+
|
13
19
|
def print_output
|
14
20
|
print "#{@output_string}\n\n"
|
15
21
|
end
|
16
22
|
|
17
23
|
def execute!
|
18
|
-
# TODO: - catch IOError: closed stream here and re-connect gracefully
|
19
|
-
# to reproduce: open connections, and let them timeout then re-enter command (interactive mode)
|
20
|
-
|
21
24
|
@commands.each do |command|
|
22
25
|
node = command.node
|
23
26
|
ssh = node.ssh_driver.ssh_connection
|
@@ -25,24 +28,25 @@ module ::Bcome::Ssh
|
|
25
28
|
begin
|
26
29
|
ssh_exec!(ssh, command)
|
27
30
|
rescue IOError # Typically occurs after a timeout if the session has been left idle
|
28
|
-
node.
|
31
|
+
node.reopen_ssh_connection
|
32
|
+
ssh = node.ssh_driver.ssh_connection
|
29
33
|
ssh_exec!(ssh, command) # retry, once
|
30
34
|
end
|
31
35
|
|
32
36
|
output_append("\n(#{node.namespace})$".terminal_prompt + ">\s#{command.raw} (#{command.pretty_result})\n")
|
33
37
|
output_append(command.output.to_s)
|
34
38
|
end
|
35
|
-
|
39
|
+
|
40
|
+
print_output unless ::Bcome::Orchestrator.instance.command_output_silenced? || ::Bcome::Orchestrator.instance.tail_all_command_output?
|
36
41
|
end
|
37
42
|
|
38
43
|
def ssh_exec!(ssh, command) # NON PTY (i.e no pseudo-terminal)
|
39
44
|
ssh.open_channel do |channel|
|
40
45
|
channel.exec(command.raw) do |_cha, success|
|
41
|
-
unless success
|
42
|
-
abort "FAILED: couldn't execute command (ssh.channel.exec)"
|
43
|
-
end
|
46
|
+
abort "FAILED: couldn't execute command (ssh.channel.exec)" unless success
|
44
47
|
|
45
48
|
channel.on_data do |_ch, data|
|
49
|
+
log_window.add(command.node, data) if ::Bcome::Orchestrator.instance.tail_all_command_output?
|
46
50
|
command.stdout += data
|
47
51
|
end
|
48
52
|
|
@@ -55,7 +59,7 @@ module ::Bcome::Ssh
|
|
55
59
|
end
|
56
60
|
|
57
61
|
channel.on_request('exit-signal') do |_ch, data|
|
58
|
-
command.
|
62
|
+
command.exit_code = data.read_long
|
59
63
|
end
|
60
64
|
end
|
61
65
|
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'net/ssh/proxy/jump'
|
4
|
+
|
5
|
+
module Bcome::Ssh
|
6
|
+
class ConnectionWrangler
|
7
|
+
def initialize(ssh_driver)
|
8
|
+
@ssh_driver = ssh_driver
|
9
|
+
@config = ssh_driver.config[:proxy]
|
10
|
+
@context_node = ssh_driver.context_node
|
11
|
+
@user = ssh_driver.user
|
12
|
+
end
|
13
|
+
|
14
|
+
## Accessors --
|
15
|
+
|
16
|
+
def proxy_details
|
17
|
+
hops.reverse.collect(&:proxy_details)
|
18
|
+
end
|
19
|
+
|
20
|
+
def first_hop
|
21
|
+
hops.reverse.first
|
22
|
+
end
|
23
|
+
|
24
|
+
def has_hop?
|
25
|
+
hops.any?
|
26
|
+
end
|
27
|
+
|
28
|
+
def single_hop?
|
29
|
+
has_hop? && hops.size == 1
|
30
|
+
end
|
31
|
+
|
32
|
+
def proxy
|
33
|
+
@proxy ||= create_proxy
|
34
|
+
end
|
35
|
+
|
36
|
+
def create_proxy
|
37
|
+
proxy = Net::SSH::Proxy::Jump.new(hops.reverse.collect(&:get_ssh_string).join(','))
|
38
|
+
proxy
|
39
|
+
end
|
40
|
+
|
41
|
+
def get_ssh_command(config = {}, _proxy_only = false)
|
42
|
+
cmd = has_hop? ? 'ssh -J' : 'ssh'
|
43
|
+
cmd += "\s" + hops.collect(&:get_ssh_string).join(',') if has_hop?
|
44
|
+
cmd += "\s#{@ssh_driver.node_level_ssh_key_connection_string}\s#{@ssh_driver.user}@#{target_machine_ingress_ip}"
|
45
|
+
|
46
|
+
config[:as_pseudo_tty] ? "#{cmd} -t" : cmd
|
47
|
+
end
|
48
|
+
|
49
|
+
def get_rsync_command(local_path, remote_path)
|
50
|
+
cmd = 'rsync -azv'
|
51
|
+
cmd += "\s-e 'ssh\s-A -J\s" + hops.collect(&:get_ssh_string).join(',') + "'" if has_hop?
|
52
|
+
cmd += "\s#{local_path}\s#{@ssh_driver.user}@#{target_machine_ingress_ip}:#{remote_path}"
|
53
|
+
cmd
|
54
|
+
end
|
55
|
+
|
56
|
+
def get_local_port_forward_command(start_port, end_port)
|
57
|
+
# TODO: - below check is not actaully true... you might still want to proxy over SSH...
|
58
|
+
raise ::Bcome::Exception::InvalidPortForwardRequest, 'Connections to this node are not via a proxy. Rather than port forward, try connecting directly.' unless has_hop?
|
59
|
+
|
60
|
+
cmd = "ssh -N -L #{start_port}:localhost:#{end_port} -J"
|
61
|
+
cmd += "\s" + hops.collect(&:get_ssh_string).join(',') if has_hop?
|
62
|
+
cmd += "\s#{@ssh_driver.node_level_ssh_key_connection_string}\s#{@ssh_driver.user}@#{target_machine_ingress_ip}"
|
63
|
+
|
64
|
+
cmd
|
65
|
+
end
|
66
|
+
|
67
|
+
protected
|
68
|
+
|
69
|
+
def target_machine_ingress_ip
|
70
|
+
unless has_hop?
|
71
|
+
raise ::Bcome::Exception::InvalidProxyConfig, "missing target ip address for #{@context_node.identifier}. Perhaps you meant to configure a proxy?" unless @context_node.public_ip_address
|
72
|
+
end
|
73
|
+
|
74
|
+
has_hop? ? @context_node.internal_ip_address : @context_node.public_ip_address
|
75
|
+
end
|
76
|
+
|
77
|
+
def hops
|
78
|
+
@hops ||= set_hops
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def set_hops
|
84
|
+
hop_collection = []
|
85
|
+
|
86
|
+
parent = nil
|
87
|
+
iterable_configs.each do |config|
|
88
|
+
hop = set_proxy_hop(config, parent)
|
89
|
+
hop_collection << hop
|
90
|
+
parent = hop
|
91
|
+
end
|
92
|
+
|
93
|
+
hop_collection
|
94
|
+
end
|
95
|
+
|
96
|
+
def set_proxy_hop(config, parent)
|
97
|
+
config[:fallback_bastion_host_user] = @ssh_driver.fallback_bastion_host_user
|
98
|
+
::Bcome::Ssh::ProxyHop.new(config, @context_node, parent)
|
99
|
+
end
|
100
|
+
|
101
|
+
def iterable_configs
|
102
|
+
@iterable ||= @config ? (@config.is_a?(Hash) ? [@config] : @config) : []
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bcome
|
4
|
+
module Ssh
|
5
|
+
class Connector
|
6
|
+
include ::Bcome::LoadingBar::Handler
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def connect(node, config = {})
|
10
|
+
handler = new(node, config)
|
11
|
+
handler.connect
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(node, config)
|
16
|
+
@node = node
|
17
|
+
@config = config
|
18
|
+
set_servers
|
19
|
+
@connected_machines = []
|
20
|
+
@connection_exceptions = {}
|
21
|
+
end
|
22
|
+
|
23
|
+
def show_progress?
|
24
|
+
@config[:show_progress] ? true : false
|
25
|
+
end
|
26
|
+
|
27
|
+
def ping?
|
28
|
+
@config[:is_ping] ? true : false
|
29
|
+
end
|
30
|
+
|
31
|
+
def connect
|
32
|
+
return if number_unconnected_machines == 0 && !ping?
|
33
|
+
|
34
|
+
if show_progress?
|
35
|
+
wrap_indicator type: :progress, size: @servers_to_connect.size, title: 'Opening connections' do
|
36
|
+
open_connections
|
37
|
+
end
|
38
|
+
else
|
39
|
+
open_connections
|
40
|
+
end
|
41
|
+
|
42
|
+
report_connection_outcome
|
43
|
+
end
|
44
|
+
|
45
|
+
def report_connection_outcome
|
46
|
+
print "\n"
|
47
|
+
|
48
|
+
if ping?
|
49
|
+
@connected_machines.pmap do |machine|
|
50
|
+
puts machine.print_ping_result
|
51
|
+
end
|
52
|
+
|
53
|
+
# If any machines remain, then we couldn't connect to them
|
54
|
+
@servers_to_connect.each do |machine|
|
55
|
+
ping_result = {
|
56
|
+
success: false,
|
57
|
+
error: @connection_exceptions[machine]
|
58
|
+
}
|
59
|
+
puts machine.print_ping_result(ping_result)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
puts "Failed to connect to #{@servers_to_connect.size} node#{@servers_to_connect.size > 1 ? 's' : ''}".error if @servers_to_connect.any?
|
64
|
+
end
|
65
|
+
|
66
|
+
def open_connections
|
67
|
+
@servers_to_connect.pmap do |machine|
|
68
|
+
begin
|
69
|
+
machine.open_ssh_connection(ping?)
|
70
|
+
if machine.has_ssh_connection?
|
71
|
+
@servers_to_connect -= [machine]
|
72
|
+
@connected_machines << machine
|
73
|
+
signal_success if show_progress?
|
74
|
+
else
|
75
|
+
signal_failure if show_progress?
|
76
|
+
end
|
77
|
+
rescue Errno::EPIPE, Bcome::Exception::CouldNotInitiateSshConnection, ::Bcome::Exception::InvalidProxyConfig => e
|
78
|
+
signal_failure if show_progress?
|
79
|
+
@connection_exceptions[machine] = e
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def set_servers
|
87
|
+
@servers_to_connect = machines.dup
|
88
|
+
end
|
89
|
+
|
90
|
+
def number_unconnected_machines
|
91
|
+
@servers_to_connect.reject(&:has_ssh_connection?).size
|
92
|
+
end
|
93
|
+
|
94
|
+
def machines
|
95
|
+
skip_for_hidden = true # Skip servers with hidden namespaces
|
96
|
+
@node.server? ? [@node] : @node.machines(skip_for_hidden)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
data/lib/objects/ssh/driver.rb
CHANGED
@@ -1,276 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'net/scp'
|
4
|
+
|
1
5
|
module Bcome::Ssh
|
2
6
|
class Driver
|
3
|
-
attr_reader :config, :
|
7
|
+
attr_reader :config, :context_node
|
4
8
|
|
5
|
-
|
6
|
-
|
7
|
-
|
9
|
+
include Bcome::Ssh::DriverConnection
|
10
|
+
include Bcome::Ssh::DriverFunctions
|
11
|
+
include Bcome::Ssh::DriverUser
|
12
|
+
include Bcome::Ssh::DriverCommandStrings
|
8
13
|
|
9
14
|
def initialize(config, context_node)
|
10
15
|
@config = config
|
11
16
|
@context_node = context_node
|
12
|
-
@proxy_data = @config[:proxy] ? ::Bcome::Ssh::ProxyData.new(@config[:proxy], @context_node) : nil
|
13
|
-
@bootstrap_settings = @config[:bootstrap_settings] ? ::Bcome::Ssh::Bootstrap.new(@config[:bootstrap_settings]) : nil
|
14
17
|
end
|
15
18
|
|
16
|
-
def
|
17
|
-
@
|
19
|
+
def connection_wrangler
|
20
|
+
@connection_wrangler ||= set_connection_wrangler
|
18
21
|
end
|
19
22
|
|
20
|
-
def
|
21
|
-
|
23
|
+
def set_connection_wrangler
|
24
|
+
@set_connection_wrangler ||= ::Bcome::Ssh::ConnectionWrangler.new(self)
|
22
25
|
end
|
23
26
|
|
24
|
-
def
|
27
|
+
def pretty_ssh_config
|
25
28
|
config = {
|
26
29
|
user: user,
|
27
30
|
ssh_keys: ssh_keys,
|
28
31
|
timeout: timeout_in_seconds
|
29
32
|
}
|
33
|
+
|
30
34
|
if has_proxy?
|
31
|
-
config[:
|
32
|
-
config[:proxy] = {
|
33
|
-
bastion_host: @proxy_data.host,
|
34
|
-
bastion_host_user: bastion_host_user
|
35
|
-
}
|
35
|
+
config[:proxy] = connection_wrangler.proxy_details
|
36
36
|
else
|
37
37
|
config[:host_or_ip] = @context_node.public_ip_address
|
38
38
|
end
|
39
|
-
config
|
40
|
-
end
|
41
|
-
|
42
|
-
def timeout_in_seconds
|
43
|
-
@config[:timeout_in_seconds] ||= Bcome::Ssh::Driver::DEFAULT_TIMEOUT_IN_SECONDS
|
44
|
-
end
|
45
|
-
|
46
|
-
def proxy
|
47
|
-
return nil unless has_proxy?
|
48
|
-
connection_string = bootstrap? ? bootstrap_proxy_connection_string : proxy_connection_string
|
49
|
-
::Net::SSH::Proxy::Command.new(connection_string)
|
50
|
-
end
|
51
39
|
|
52
|
-
|
53
|
-
return false if proxy_config_value && proxy_config_value == -1
|
54
|
-
!@config[:proxy].nil?
|
40
|
+
config
|
55
41
|
end
|
56
42
|
|
57
43
|
def proxy_config_value
|
58
44
|
@config[:proxy]
|
59
45
|
end
|
60
46
|
|
61
|
-
def
|
62
|
-
|
63
|
-
end
|
64
|
-
|
65
|
-
def bootstrap_proxy_connection_string
|
66
|
-
"ssh -i #{@bootstrap_settings.ssh_key_path} -o StrictHostKeyChecking=no -W %h:%p #{@bootstrap_settings.bastion_host_user}@#{@proxy_data.host}"
|
67
|
-
end
|
68
|
-
|
69
|
-
def do_ssh
|
70
|
-
cmd = ssh_command
|
71
|
-
@context_node.execute_local(cmd)
|
72
|
-
end
|
73
|
-
|
74
|
-
def bastion_host_user
|
75
|
-
bootstrap? && @bootstrap_settings.bastion_host_user ? @bootstrap_settings.bastion_host_user : @proxy_data.bastion_host_user ? @proxy_data.bastion_host_user : user
|
76
|
-
end
|
77
|
-
|
78
|
-
def ssh_command(as_pseudo_tty = false)
|
79
|
-
return bootstrap_ssh_command if bootstrap? && @bootstrap_settings.ssh_key_path
|
80
|
-
|
81
|
-
if has_proxy?
|
82
|
-
"#{as_pseudo_tty ? "ssh -t" : "ssh"} #{PROXY_SSH_PREFIX} #{bastion_host_user}@#{@proxy_data.host}\" #{node_level_ssh_key_connection_string}#{user}@#{@context_node.internal_ip_address}"
|
83
|
-
else
|
84
|
-
"#{as_pseudo_tty ? "ssh -t" : "ssh"} #{node_level_ssh_key_connection_string}#{user}@#{@context_node.public_ip_address}"
|
85
|
-
end
|
47
|
+
def multi_hop_proxy_config
|
48
|
+
@config[:multi_hop_proxy]
|
86
49
|
end
|
87
50
|
|
88
51
|
def node_level_ssh_key_connection_string
|
89
|
-
key_specified_at_node_level? ? "-i #{node_level_ssh_key}\s" :
|
52
|
+
key_specified_at_node_level? ? "-i #{node_level_ssh_key}\s" : ''
|
90
53
|
end
|
91
54
|
|
92
55
|
def key_specified_at_node_level?
|
93
56
|
!node_level_ssh_key.nil?
|
94
|
-
end
|
95
|
-
|
96
|
-
def node_level_ssh_key
|
97
|
-
return (@config[:ssh_keys]) ? @config[:ssh_keys].first : nil
|
98
|
-
end
|
99
|
-
|
100
|
-
def local_port_forward(start_port, end_port)
|
101
|
-
if has_proxy?
|
102
|
-
|
103
|
-
if bootstrap?
|
104
|
-
tunnel_command = "ssh -N -L #{start_port}:#{@context_node.internal_ip_address}:#{end_port} -i #{@bootstrap_settings.ssh_key_path} #{bastion_host_user}@#{@proxy_data.host}"
|
105
|
-
else
|
106
|
-
tunnel_command = "ssh -N -L #{start_port}:#{@context_node.internal_ip_address}:#{end_port} #{bastion_host_user}@#{@proxy_data.host}"
|
107
|
-
end
|
108
|
-
else
|
109
|
-
if bootstrap?
|
110
|
-
tunnel_command = "ssh -i #{@bootstrap_settings.ssh_key_path} -N -L #{start_port}:#{@context_node.public_ip_address}:#{end_port}"
|
111
|
-
else
|
112
|
-
tunnel_command = "ssh -N -L #{start_port}:#{@context_node.public_ip_address}:#{end_port}"
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
tunnel = ::Bcome::Ssh::Tunnel::LocalPortForward.new(tunnel_command)
|
117
|
-
tunnel.open!
|
118
|
-
return tunnel
|
119
|
-
end
|
120
|
-
|
121
|
-
def bootstrap_ssh_command
|
122
|
-
if has_proxy?
|
123
|
-
"ssh -i #{@bootstrap_settings.ssh_key_path} -t #{bastion_host_user}@#{@proxy_data.host} ssh -t #{user}@#{@context_node.internal_ip_address}"
|
124
|
-
else
|
125
|
-
"ssh -i #{@bootstrap_settings.ssh_key_path} #{user}@#{@context_node.public_ip_address}"
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
|
-
def user
|
130
|
-
# If we're in bootstrapping mode and have a bootstrap user set, return it
|
131
|
-
return @bootstrap_settings.user if (bootstrap? && @bootstrap_settings.user)
|
132
|
-
|
133
|
-
# If we have a user explcitly set in the config, then return it
|
134
|
-
return @config[:user] if @config[:user]
|
135
|
-
|
136
|
-
# If the local user has explicitly overriden their user, return that
|
137
|
-
return overriden_local_user if overriden_local_user
|
138
|
-
|
139
|
-
# Else fall back to whichever local user is using bcome
|
140
|
-
fallback_local_user
|
141
|
-
end
|
142
|
-
|
143
|
-
def overriden_local_user
|
144
|
-
@overriden_local_user ||= get_overriden_local_user
|
145
|
-
end
|
146
|
-
|
147
|
-
def get_overriden_local_user
|
148
|
-
::Bcome::Node::Factory.instance.local_data[:ssh_user]
|
149
|
-
end
|
150
|
-
|
151
|
-
def fallback_local_user
|
152
|
-
@fallback_local_user ||= ::Bcome::System::Local.instance.local_user
|
153
|
-
end
|
154
|
-
|
155
|
-
def node_host_or_ip
|
156
|
-
has_proxy? ? @context_node.internal_ip_address : @context_node.public_ip_address
|
157
|
-
end
|
158
|
-
|
159
|
-
def net_ssh_params(verbose = false)
|
160
|
-
raise Bcome::Exception::InvalidSshConfig, "Missing ssh keys for #{@context_node.namespace}" unless ssh_keys
|
161
|
-
params = { keys: ssh_keys, paranoid: false }
|
162
|
-
params[:proxy] = proxy if has_proxy?
|
163
|
-
params[:timeout] = timeout_in_seconds
|
164
|
-
params[:verbose] = :debug if verbose
|
165
|
-
params
|
166
|
-
end
|
167
|
-
|
168
|
-
def ssh_keys
|
169
|
-
if bootstrap?
|
170
|
-
[@bootstrap_settings.ssh_key_path]
|
171
|
-
else
|
172
|
-
@config[:ssh_keys]
|
173
|
-
end
|
174
|
-
end
|
175
|
-
|
176
|
-
def rsync(local_path, remote_path)
|
177
|
-
raise Bcome::Exception::MissingParamsForRsync, "'rsync' requires a local_path and a remote_path" if local_path.to_s.empty? || remote_path.to_s.empty?
|
178
|
-
command = rsync_command(local_path, remote_path)
|
179
|
-
@context_node.execute_local(command)
|
180
|
-
end
|
181
|
-
|
182
|
-
def rsync_command(local_path, remote_path)
|
183
|
-
return bootstrap_rsync_command(local_path, remote_path) if bootstrap? && @bootstrap_settings.ssh_key_path
|
184
|
-
if has_proxy?
|
185
|
-
"rsync -av -e \"ssh -A #{bastion_host_user}@#{@proxy_data.host} ssh -o StrictHostKeyChecking=no\" #{local_path} #{user}@#{@context_node.internal_ip_address}:#{remote_path}"
|
186
|
-
else
|
187
|
-
"rsync -av #{local_path} #{user}@#{@context_node.public_ip_address}:#{remote_path}"
|
188
|
-
end
|
189
|
-
end
|
190
|
-
|
191
|
-
def bootstrap_rsync_command(local_path, remote_path)
|
192
|
-
if has_proxy?
|
193
|
-
"rsync -av -e \"ssh -i #{@bootstrap_settings.ssh_key_path} -A #{bastion_host_user}@#{@proxy_data.host} ssh -o StrictHostKeyChecking=no\" #{local_path} #{user}@#{@context_node.internal_ip_address}:#{remote_path}"
|
194
|
-
else
|
195
|
-
"rsync -i #{@bootstrap_settings.ssh_key_path} -av #{local_path} #{user}@#{@context_node.public_ip_address}:#{remote_path}"
|
196
|
-
end
|
197
57
|
end
|
198
58
|
|
199
|
-
def
|
200
|
-
@
|
201
|
-
begin
|
202
|
-
@connection = ::Net::SSH.start(node_host_or_ip, user, net_ssh_params)
|
203
|
-
rescue Net::SSH::Proxy::ConnectError, Net::SSH::ConnectionTimeout, Errno::EPIPE => e
|
204
|
-
raise Bcome::Exception::CouldNotInitiateSshConnection, @context_node.namespace + "\s-\s#{e.message}"
|
205
|
-
end
|
206
|
-
@connection
|
207
|
-
end
|
208
|
-
|
209
|
-
def ping
|
210
|
-
ssh_connect!
|
211
|
-
return { success: true }
|
212
|
-
rescue Exception => e
|
213
|
-
return { success: false, error: e }
|
214
|
-
end
|
215
|
-
|
216
|
-
def scp
|
217
|
-
ssh_connection.scp
|
218
|
-
end
|
219
|
-
|
220
|
-
def put(local_path, remote_path)
|
221
|
-
raise Bcome::Exception::MissingParamsForScp, "'put' requires a local_path and a remote_path" if local_path.to_s.empty? || remote_path.to_s.empty?
|
222
|
-
puts "\n(#{@context_node.namespace})\s".namespace + "Uploading #{local_path} to #{remote_path}\n".informational
|
223
|
-
|
224
|
-
begin
|
225
|
-
scp.upload!(local_path, remote_path, recursive: true) do |_ch, name, sent, total|
|
226
|
-
puts "#{name}: #{sent}/#{total}".progress
|
227
|
-
end
|
228
|
-
rescue Exception => e # scp just throws generic exceptions :-/
|
229
|
-
puts e.message.error
|
230
|
-
end
|
231
|
-
nil
|
232
|
-
end
|
233
|
-
|
234
|
-
def put_str(string, remote_path)
|
235
|
-
raise Bcome::Exception::MissingParamsForScp, "'put' requires a string and a remote_path" if string.to_s.empty? || remote_path.to_s.empty?
|
236
|
-
puts "\n(#{@context_node.namespace})\s".namespace + "Uploading from string to #{remote_path}\n".informational
|
237
|
-
|
238
|
-
begin
|
239
|
-
scp.upload!(StringIO.new(string), remote_path) do |_ch, name, sent, total|
|
240
|
-
puts "#{name}: #{sent}/#{total}".progress
|
241
|
-
end
|
242
|
-
rescue Exception => e # scp just throws generic exceptions :-/
|
243
|
-
puts e.message.error
|
244
|
-
end
|
245
|
-
nil
|
246
|
-
end
|
247
|
-
|
248
|
-
|
249
|
-
def get(remote_path, local_path)
|
250
|
-
raise Bcome::Exception::MissingParamsForScp, "'get' requires a local_path and a remote_path" if local_path.to_s.empty? || remote_path.to_s.empty?
|
251
|
-
puts "\n(#{@context_node.namespace})\s".namespace + "Downloading #{remote_path} to #{local_path}\n".informational
|
252
|
-
|
253
|
-
begin
|
254
|
-
scp.download!(remote_path, local_path, recursive: true) do |_ch, name, sent, total|
|
255
|
-
puts "#{name}: #{sent}/#{total}".progress
|
256
|
-
end
|
257
|
-
rescue Exception => e # scp just throws generic exceptions :-/
|
258
|
-
puts e.message.error
|
259
|
-
end
|
59
|
+
def node_level_ssh_key
|
60
|
+
@config[:ssh_keys] ? @config[:ssh_keys].first : nil
|
260
61
|
end
|
261
62
|
|
262
|
-
def
|
263
|
-
|
264
|
-
@connection.close unless @connection.closed?
|
265
|
-
@connection = nil
|
63
|
+
def has_multi_hop_proxy?
|
64
|
+
!multi_hop_proxy_config.nil?
|
266
65
|
end
|
267
66
|
|
268
|
-
def
|
269
|
-
|
270
|
-
end
|
67
|
+
def has_proxy?
|
68
|
+
return false if proxy_config_value && proxy_config_value == -1
|
271
69
|
|
272
|
-
|
273
|
-
!@connection.nil? && !@connection.closed?
|
70
|
+
!@config[:proxy].nil?
|
274
71
|
end
|
275
72
|
end
|
276
73
|
end
|