bcome 1.3.2 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- 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 -124
- data/lib/objects/node/attributes.rb +13 -21
- data/lib/objects/node/base.rb +116 -67
- 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 +29 -23
- 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 +91 -65
- 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 -215
- 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 +29 -6
- data/patches/string-encrypt.rb +20 -23
- data/patches/string.rb +5 -1
- data/patches/string_stylesheet.rb +2 -0
- metadata +101 -23
- 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 -34
@@ -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,261 +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
39
|
|
42
|
-
|
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
|
-
|
52
|
-
def has_proxy?
|
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
57
|
end
|
120
58
|
|
121
|
-
def
|
122
|
-
|
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
|
-
end
|
198
|
-
|
199
|
-
def ssh_connect!(_verbose = false)
|
200
|
-
@connection = nil
|
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 get(remote_path, local_path)
|
235
|
-
raise Bcome::Exception::MissingParamsForScp, "'get' requires a local_path and a remote_path" if local_path.to_s.empty? || remote_path.to_s.empty?
|
236
|
-
puts "\n(#{@context_node.namespace})\s".namespace + "Downloading #{remote_path} to #{local_path}\n".informational
|
237
|
-
|
238
|
-
begin
|
239
|
-
scp.download!(remote_path, local_path, recursive: true) 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
|
59
|
+
def node_level_ssh_key
|
60
|
+
@config[:ssh_keys] ? @config[:ssh_keys].first : nil
|
245
61
|
end
|
246
62
|
|
247
|
-
def
|
248
|
-
|
249
|
-
@connection.close unless @connection.closed?
|
250
|
-
@connection = nil
|
63
|
+
def has_multi_hop_proxy?
|
64
|
+
!multi_hop_proxy_config.nil?
|
251
65
|
end
|
252
66
|
|
253
|
-
def
|
254
|
-
|
255
|
-
end
|
67
|
+
def has_proxy?
|
68
|
+
return false if proxy_config_value && proxy_config_value == -1
|
256
69
|
|
257
|
-
|
258
|
-
!@connection.nil? && !@connection.closed?
|
70
|
+
!@config[:proxy].nil?
|
259
71
|
end
|
260
72
|
end
|
261
73
|
end
|