bcome 1.3.3 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/bin/bcome +13 -8
- data/lib/bcome.rb +14 -11
- data/lib/objects/bcome/version.rb +19 -1
- data/lib/objects/bootup.rb +16 -7
- data/lib/objects/command/local.rb +2 -0
- data/lib/objects/config_factory.rb +3 -0
- data/lib/objects/driver/base.rb +52 -6
- data/lib/objects/driver/bucket.rb +6 -4
- data/lib/objects/driver/ec2.rb +45 -5
- data/lib/objects/driver/gcp.rb +168 -0
- data/lib/objects/driver/gcp/authentication/api_key.rb +6 -0
- data/lib/objects/driver/gcp/authentication/base.rb +36 -0
- data/lib/objects/driver/gcp/authentication/oauth.rb +96 -0
- data/lib/objects/driver/gcp/authentication/oauth_client_config.rb +22 -0
- data/lib/objects/driver/gcp/authentication/oauth_session_store.rb +22 -0
- data/lib/objects/driver/gcp/authentication/service_account.rb +62 -0
- data/lib/objects/driver/gcp/authentication/signet/service_account.rb +27 -0
- data/lib/objects/driver/gcp/authentication/utilities.rb +42 -0
- data/lib/objects/encryptor.rb +109 -24
- data/lib/objects/exception/argument_error_invoking_method_from_command_line.rb +8 -4
- data/lib/objects/exception/base.rb +21 -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_authorization_keys.rb +11 -0
- data/lib/objects/exception/ec2_driver_missing_provisioning_region.rb +8 -4
- data/lib/objects/exception/empty_namespace_tree.rb +11 -0
- data/lib/objects/exception/failed_to_run_local_command.rb +8 -4
- data/lib/objects/exception/gcp_auth_service_account_missing_credentials.rb +11 -0
- 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_account_credentials_filename.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/exception/user_orchestration_error.rb +11 -0
- data/lib/objects/initialization/factory.rb +36 -0
- data/lib/objects/initialization/structure.rb +18 -0
- data/lib/objects/initialization/utils.rb +20 -0
- 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 +65 -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/draw.rb +49 -0
- data/lib/objects/modules/registry_management.rb +16 -10
- data/lib/objects/modules/tree.rb +157 -0
- data/lib/objects/modules/ui_output.rb +10 -6
- data/lib/objects/modules/workspace_commands.rb +131 -157
- data/lib/objects/modules/workspace_menu.rb +193 -123
- data/lib/objects/node/attributes.rb +17 -19
- data/lib/objects/node/base.rb +136 -74
- data/lib/objects/node/cache_handler.rb +3 -1
- data/lib/objects/node/collection.rb +10 -9
- data/lib/objects/node/factory.rb +47 -36
- data/lib/objects/node/inventory/base.rb +106 -100
- data/lib/objects/node/inventory/defined.rb +113 -89
- data/lib/objects/node/inventory/merge.rb +51 -0
- data/lib/objects/node/inventory/subselect.rb +66 -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 +4 -2
- data/lib/objects/node/meta_data_loader.rb +28 -29
- data/lib/objects/node/resources/base.rb +5 -1
- data/lib/objects/node/resources/inventory.rb +26 -5
- data/lib/objects/node/resources/merged.rb +47 -0
- data/lib/objects/node/resources/sub_inventory.rb +12 -8
- data/lib/objects/node/server/base.rb +106 -71
- data/lib/objects/node/server/dynamic/base.rb +23 -0
- data/lib/objects/node/server/{dynamic.rb → dynamic/ec2.rb} +13 -13
- data/lib/objects/node/server/dynamic/gcp.rb +46 -0
- data/lib/objects/node/server/static.rb +34 -10
- data/lib/objects/orchestration/base.rb +17 -1
- data/lib/objects/orchestration/interactive_terraform.rb +59 -30
- 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 +9 -3
- data/lib/objects/registry/command/group.rb +11 -4
- 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 +13 -10
- data/lib/objects/ssh/bootstrap.rb +3 -1
- data/lib/objects/ssh/command.rb +9 -8
- data/lib/objects/ssh/command_exec.rb +16 -10
- data/lib/objects/ssh/connection_wrangler.rb +122 -0
- data/lib/objects/ssh/connector.rb +108 -0
- data/lib/objects/ssh/driver.rb +28 -242
- data/lib/objects/ssh/driver_concerns/command_strings.rb +17 -0
- data/lib/objects/ssh/driver_concerns/connection.rb +70 -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_chain.rb +19 -0
- data/lib/objects/ssh/proxy_chain_link.rb +26 -0
- data/lib/objects/ssh/proxy_hop.rb +130 -0
- data/lib/objects/ssh/script_exec.rb +12 -11
- 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 +58 -0
- data/lib/objects/system/local.rb +3 -0
- data/lib/objects/terraform/output.rb +45 -0
- data/lib/objects/workspace.rb +13 -14
- data/patches/irb.rb +63 -6
- data/patches/string-encrypt.rb +20 -23
- data/patches/string.rb +18 -1
- data/patches/string_stylesheet.rb +2 -0
- metadata +157 -33
- data/lib/objects/driver/static.rb +0 -4
- data/lib/objects/progress_bar.rb +0 -30
- data/lib/objects/ssh/connection_handler.rb +0 -101
- data/lib/objects/ssh/proxy_data.rb +0 -56
- data/lib/objects/terraform/parser.rb +0 -23
- data/lib/objects/terraform/state.rb +0 -40
@@ -1,9 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Bcome::Registry::Command
|
2
4
|
class Base
|
3
5
|
class << self
|
4
6
|
def new_from_raw_command(data)
|
5
7
|
raise Bcome::Exception::InvalidContextCommand, "#{data.inspect} is missing key type" unless data[:type]
|
6
8
|
raise Bcome::Exception::InvalidContextCommand, "#{data.inspect} has invalid type '#{data[:type]}'" unless is_valid_type?(data[:type])
|
9
|
+
|
7
10
|
valid_types[data[:type].to_sym].new(data)
|
8
11
|
end
|
9
12
|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Bcome::Registry::Command
|
2
4
|
class External < Base
|
3
5
|
# In which the bcome context is passed to an external call
|
@@ -28,15 +30,19 @@ module Bcome::Registry::Command
|
|
28
30
|
error_message_suffix = "- missing '#{substitution}' from command '#{local_command}'"
|
29
31
|
raise Bcome::Exception::MissingArgumentForRegistryCommand, error_message_suffix
|
30
32
|
end
|
31
|
-
|
32
|
-
substitute_with = [TrueClass, FalseClass].include?(substitute_with.class)
|
33
|
+
|
34
|
+
substitute_with = if [TrueClass, FalseClass].include?(substitute_with.class)
|
35
|
+
substitute_with ? 'true' : 'false'
|
36
|
+
else
|
37
|
+
substitute_with
|
38
|
+
end
|
33
39
|
substituted_command.gsub!("%#{substitution}%", substitute_with)
|
34
40
|
end
|
35
41
|
substituted_command
|
36
42
|
end
|
37
43
|
|
38
44
|
def namespace_command(node, command)
|
39
|
-
"
|
45
|
+
"bcome_context=\"#{node.keyed_namespace}\" #{command}"
|
40
46
|
end
|
41
47
|
|
42
48
|
def local_command_substitutions
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Bcome::Registry::Command
|
2
4
|
class Group
|
3
5
|
def initialize(node)
|
@@ -32,7 +34,8 @@ module Bcome::Registry::Command
|
|
32
34
|
end
|
33
35
|
|
34
36
|
def item_spacing(item)
|
35
|
-
raise ::Bcome::Exception::InvalidRegistryCommandNameLength
|
37
|
+
raise ::Bcome::Exception::InvalidRegistryCommandNameLength, "command '#{item}' exceeds length limit of #{menu_item_spacing_length}" if item.length > menu_item_spacing_length
|
38
|
+
|
36
39
|
"\s" * (menu_item_spacing_length - item.length)
|
37
40
|
end
|
38
41
|
|
@@ -48,7 +51,7 @@ module Bcome::Registry::Command
|
|
48
51
|
::Bcome::System::Local.instance.in_console_session?
|
49
52
|
end
|
50
53
|
|
51
|
-
def pretty_print
|
54
|
+
def pretty_print
|
52
55
|
puts "\nRegistry commands".title + "\sfor #{@node.class} #{@node.keyed_namespace}".resource_value + "\n\n"
|
53
56
|
all_commands.sort.each do |group_name, commands|
|
54
57
|
puts tab_spacing + group_name.title + "\n\n"
|
@@ -59,11 +62,15 @@ module Bcome::Registry::Command
|
|
59
62
|
|
60
63
|
puts tab_spacing + command_key.resource_key + item_spacing(command_key) + description.resource_value
|
61
64
|
|
62
|
-
usage_string = in_console_session?
|
65
|
+
usage_string = if in_console_session?
|
66
|
+
command_key.to_s
|
67
|
+
else
|
68
|
+
"bcome #{@node.keyed_namespace.empty? ? '' : "#{@node.keyed_namespace}:"}#{command_key}"
|
69
|
+
end
|
63
70
|
puts tab_spacing + ("\s" * menu_item_spacing_length) + 'usage: '.instructional + usage_string
|
64
71
|
|
65
72
|
if defaults.keys.any?
|
66
|
-
defaults_usage = in_console_session? ? "\s
|
73
|
+
defaults_usage = in_console_session? ? "\s#{defaults.collect { |key, _value| "\"#{key}=your-value\"" }.join(",\s")}" : "\s" + defaults.collect { |key, _value| "#{key}=your-value" }.join("\s")
|
67
74
|
puts tab_spacing + ("\s" * menu_item_spacing_length) + "defaults:\s".instructional + defaults.collect { |k, v| "#{k}=#{v}" }.join(', ')
|
68
75
|
puts tab_spacing + ("\s" * menu_item_spacing_length) + "override:\s".instructional + usage_string + defaults_usage
|
69
76
|
end
|
@@ -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,20 @@ module Bcome::Registry
|
|
17
19
|
|
18
20
|
data.each do |key, commands|
|
19
21
|
begin
|
20
|
-
if /^#{key
|
21
|
-
|
22
|
+
if /^#{key}$/.match(node.keyed_namespace)
|
23
|
+
|
24
|
+
next if commands.nil?
|
22
25
|
|
26
|
+
commands.each do |c|
|
23
27
|
unless c[:console_command]
|
24
28
|
error_message = "Registry method is missing key 'console_command'."
|
25
29
|
error_message += "\n\n#{c.inspect}"
|
26
|
-
raise Bcome::Exception::InvalidRegistryDataConfig
|
30
|
+
raise Bcome::Exception::InvalidRegistryDataConfig, error_message
|
27
31
|
end
|
28
32
|
|
29
33
|
# 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
|
34
|
+
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])
|
35
|
+
|
33
36
|
command_group << ::Bcome::Registry::Command::Base.new_from_raw_command(c) unless restrict_config?(node, c)
|
34
37
|
::Bcome::Registry::CommandList.instance.register(node, c[:console_command].to_sym)
|
35
38
|
end
|
@@ -47,11 +50,10 @@ module Bcome::Registry
|
|
47
50
|
|
48
51
|
def restrict_config?(node, command_config)
|
49
52
|
return false unless command_config.key?(:restrict_to_node)
|
53
|
+
|
50
54
|
node_klass_mapping = restriction_to_node_klass_mappings[command_config[:restrict_to_node].to_sym]
|
51
55
|
|
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
|
56
|
+
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
57
|
|
56
58
|
!node.is_a?(node_klass_mapping)
|
57
59
|
end
|
@@ -66,6 +68,7 @@ module Bcome::Registry
|
|
66
68
|
|
67
69
|
def do_load
|
68
70
|
return {} unless File.exist?(FILE_PATH)
|
71
|
+
|
69
72
|
begin
|
70
73
|
file_data = YAML.load_file(FILE_PATH).deep_symbolize_keys
|
71
74
|
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]
|
@@ -14,15 +16,14 @@ module ::Bcome::Ssh
|
|
14
16
|
@node = nil
|
15
17
|
end
|
16
18
|
|
17
|
-
def
|
18
|
-
|
19
|
-
end
|
19
|
+
def output
|
20
|
+
cmd_output = @stdout
|
20
21
|
|
21
|
-
|
22
|
+
cmd_output += "\nExit code:" + "\s#{@exit_code}"
|
22
23
|
|
23
|
-
|
24
|
-
|
25
|
-
"\n#{
|
24
|
+
cmd_output += "\nSTDERR: #{@stderr}" if exit_code == 1 && !@stderr.empty?
|
25
|
+
|
26
|
+
"\n#{cmd_output}"
|
26
27
|
end
|
27
28
|
|
28
29
|
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,17 @@ 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"
|
21
|
+
rescue StandardError => e
|
22
|
+
puts "Could not print #{@output_string.inspect}"
|
15
23
|
end
|
16
24
|
|
17
25
|
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
26
|
@commands.each do |command|
|
22
27
|
node = command.node
|
23
28
|
ssh = node.ssh_driver.ssh_connection
|
@@ -25,24 +30,25 @@ module ::Bcome::Ssh
|
|
25
30
|
begin
|
26
31
|
ssh_exec!(ssh, command)
|
27
32
|
rescue IOError # Typically occurs after a timeout if the session has been left idle
|
28
|
-
node.
|
33
|
+
node.reopen_ssh_connection
|
34
|
+
ssh = node.ssh_driver.ssh_connection
|
29
35
|
ssh_exec!(ssh, command) # retry, once
|
30
36
|
end
|
31
37
|
|
32
|
-
output_append("\n(#{node.namespace})$".terminal_prompt + ">\s#{command.raw}
|
38
|
+
output_append("\n(#{node.namespace})$".terminal_prompt + ">\s#{command.raw}\n")
|
33
39
|
output_append(command.output.to_s)
|
34
40
|
end
|
35
|
-
|
41
|
+
|
42
|
+
print_output unless ::Bcome::Orchestrator.instance.command_output_silenced? || ::Bcome::Orchestrator.instance.tail_all_command_output?
|
36
43
|
end
|
37
44
|
|
38
45
|
def ssh_exec!(ssh, command) # NON PTY (i.e no pseudo-terminal)
|
39
46
|
ssh.open_channel do |channel|
|
40
47
|
channel.exec(command.raw) do |_cha, success|
|
41
|
-
unless success
|
42
|
-
abort "FAILED: couldn't execute command (ssh.channel.exec)"
|
43
|
-
end
|
48
|
+
abort "FAILED: couldn't execute command (ssh.channel.exec)" unless success
|
44
49
|
|
45
50
|
channel.on_data do |_ch, data|
|
51
|
+
log_window.add(command.node, data) if ::Bcome::Orchestrator.instance.tail_all_command_output?
|
46
52
|
command.stdout += data
|
47
53
|
end
|
48
54
|
|
@@ -55,7 +61,7 @@ module ::Bcome::Ssh
|
|
55
61
|
end
|
56
62
|
|
57
63
|
channel.on_request('exit-signal') do |_ch, data|
|
58
|
-
command.
|
64
|
+
command.exit_code = data.read_long
|
59
65
|
end
|
60
66
|
end
|
61
67
|
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'net/ssh/proxy/jump'
|
4
|
+
|
5
|
+
module Bcome::Ssh
|
6
|
+
class ConnectionWrangler
|
7
|
+
attr_accessor :proxy_details
|
8
|
+
|
9
|
+
def initialize(ssh_driver)
|
10
|
+
@ssh_driver = ssh_driver
|
11
|
+
@config = ssh_driver.config[:proxy]
|
12
|
+
@context_node = ssh_driver.context_node
|
13
|
+
@user = ssh_driver.user
|
14
|
+
set_proxy_details
|
15
|
+
end
|
16
|
+
|
17
|
+
## Accessors --
|
18
|
+
def first_hop
|
19
|
+
hops.reverse.first
|
20
|
+
end
|
21
|
+
|
22
|
+
def has_hop?
|
23
|
+
hops.any?
|
24
|
+
end
|
25
|
+
|
26
|
+
def single_hop?
|
27
|
+
has_hop? && hops.size == 1
|
28
|
+
end
|
29
|
+
|
30
|
+
def proxy
|
31
|
+
@proxy ||= create_proxy
|
32
|
+
end
|
33
|
+
|
34
|
+
def create_proxy
|
35
|
+
proxy = Net::SSH::Proxy::Jump.new(hops.reverse.collect(&:get_ssh_string).join(','))
|
36
|
+
proxy
|
37
|
+
end
|
38
|
+
|
39
|
+
def get_ssh_command(config = {}, _proxy_only = false)
|
40
|
+
cmd = has_hop? ? 'ssh -J' : 'ssh'
|
41
|
+
cmd += "\s" + hops.collect(&:get_ssh_string).join(',') if has_hop?
|
42
|
+
cmd += "\s#{@ssh_driver.user}@#{target_machine_ingress_ip}"
|
43
|
+
|
44
|
+
config[:as_pseudo_tty] ? "#{cmd} -t" : cmd
|
45
|
+
end
|
46
|
+
|
47
|
+
def get_rsync_command(local_path, remote_path)
|
48
|
+
cmd = 'rsync -azv'
|
49
|
+
cmd += "\s-e 'ssh\s-A -J\s" + hops.collect(&:get_ssh_string).join(',') + "'" if has_hop?
|
50
|
+
cmd += "\s#{local_path}\s#{@ssh_driver.user}@#{target_machine_ingress_ip}:#{remote_path}"
|
51
|
+
cmd
|
52
|
+
end
|
53
|
+
|
54
|
+
def get_local_port_forward_command(start_port, end_port)
|
55
|
+
# TODO: - below check is not actually true... you might still want to proxy over SSH...
|
56
|
+
raise ::Bcome::Exception::InvalidPortForwardRequest, 'Connections to this node are not via a proxy. Rather than port forward, try connecting directly.' unless has_hop?
|
57
|
+
|
58
|
+
cmd = "ssh -N -L #{start_port}:localhost:#{end_port} -J"
|
59
|
+
cmd += "\s" + hops.collect(&:get_ssh_string).join(',') if has_hop?
|
60
|
+
cmd += "\s#{@ssh_driver.user}@#{target_machine_ingress_ip}"
|
61
|
+
|
62
|
+
cmd
|
63
|
+
end
|
64
|
+
|
65
|
+
def hops
|
66
|
+
@hops ||= set_hops
|
67
|
+
end
|
68
|
+
|
69
|
+
protected
|
70
|
+
|
71
|
+
def set_proxy_details
|
72
|
+
@proxy_details ||= hops.compact.collect(&:proxy_details)
|
73
|
+
end
|
74
|
+
|
75
|
+
def target_machine_ingress_ip
|
76
|
+
return @context_node.internal_ip_address if @context_node.local_network?
|
77
|
+
|
78
|
+
unless has_hop?
|
79
|
+
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
|
80
|
+
end
|
81
|
+
|
82
|
+
has_hop? ? @context_node.internal_ip_address : @context_node.public_ip_address
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def set_hops
|
88
|
+
hop_collection = []
|
89
|
+
|
90
|
+
parent = nil
|
91
|
+
iterable_configs.each do |config|
|
92
|
+
hop = get_proxy_hop(config, parent)
|
93
|
+
|
94
|
+
if @context_node.is_same_machine?(hop.bcome_proxy_node)
|
95
|
+
# We don't hop through ourselves. If we're reached ourselves in the proxy chain,
|
96
|
+
# then we'll break the chain at that point.
|
97
|
+
break
|
98
|
+
end
|
99
|
+
|
100
|
+
# Set proxy hop
|
101
|
+
hop_collection << hop
|
102
|
+
parent = hop
|
103
|
+
end
|
104
|
+
|
105
|
+
hop_collection.compact
|
106
|
+
end
|
107
|
+
|
108
|
+
def get_proxy_hop(config, parent)
|
109
|
+
config[:fallback_bastion_host_user] = @ssh_driver.fallback_bastion_host_user
|
110
|
+
h = ::Bcome::Ssh::ProxyHop.new(config, @context_node, parent)
|
111
|
+
return h
|
112
|
+
end
|
113
|
+
|
114
|
+
def iterable_configs
|
115
|
+
@iterable ||= if @config
|
116
|
+
@config.is_a?(Hash) ? [@config] : @config
|
117
|
+
else
|
118
|
+
[]
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,108 @@
|
|
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
|
+
print "\n"
|
36
|
+
wrap_indicator type: :progress, size: @servers_to_connect.size, title: 'Opening connections' do
|
37
|
+
open_connections
|
38
|
+
end
|
39
|
+
else
|
40
|
+
open_connections
|
41
|
+
end
|
42
|
+
|
43
|
+
report_connection_outcome
|
44
|
+
end
|
45
|
+
|
46
|
+
def report_connection_outcome
|
47
|
+
print "\n"
|
48
|
+
|
49
|
+
if ping?
|
50
|
+
@connected_machines.pmap do |machine|
|
51
|
+
puts machine.print_ping_result
|
52
|
+
end
|
53
|
+
|
54
|
+
# If any machines remain, then we couldn't connect to them
|
55
|
+
@servers_to_connect.each do |machine|
|
56
|
+
ping_result = {
|
57
|
+
success: false,
|
58
|
+
error: @connection_exceptions[machine]
|
59
|
+
}
|
60
|
+
puts machine.print_ping_result(ping_result)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
puts "Failed to connect to #{@servers_to_connect.size} node#{@servers_to_connect.size > 1 ? 's' : ''}".error if @servers_to_connect.any?
|
65
|
+
end
|
66
|
+
|
67
|
+
def open_connections
|
68
|
+
@servers_to_connect.pmap do |machine|
|
69
|
+
begin
|
70
|
+
machine.open_ssh_connection(ping?)
|
71
|
+
|
72
|
+
if machine.has_ssh_connection?
|
73
|
+
@servers_to_connect -= [machine]
|
74
|
+
@connected_machines << machine
|
75
|
+
signal_success if show_progress?
|
76
|
+
else
|
77
|
+
signal_failure if show_progress?
|
78
|
+
end
|
79
|
+
rescue Errno::EPIPE, Bcome::Exception::CouldNotInitiateSshConnection, ::Bcome::Exception::InvalidProxyConfig => e
|
80
|
+
signal_failure if show_progress?
|
81
|
+
@connection_exceptions[machine] = e
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def set_servers
|
89
|
+
@servers_to_connect = machines.dup
|
90
|
+
|
91
|
+
# Ensure that all connections are loaded. A machine might need to proxy through another that
|
92
|
+
# has not yet been loaded. Here we ensure that we've traversed the tree for all required nodes.
|
93
|
+
@servers_to_connect.each do |server|
|
94
|
+
server.ssh_driver.set_connection_wrangler
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def number_unconnected_machines
|
99
|
+
@servers_to_connect.reject(&:has_ssh_connection?).size
|
100
|
+
end
|
101
|
+
|
102
|
+
def machines
|
103
|
+
skip_for_hidden = true # Skip servers with hidden namespaces
|
104
|
+
@node.server? ? [@node] : @node.machines(skip_for_hidden)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|