bcome 1.4.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/lib/bcome.rb +7 -0
  3. data/lib/objects/bcome/version.rb +3 -3
  4. data/lib/objects/bootup.rb +4 -3
  5. data/lib/objects/driver/base.rb +16 -2
  6. data/lib/objects/driver/ec2.rb +11 -2
  7. data/lib/objects/driver/gcp.rb +49 -5
  8. data/lib/objects/driver/gcp/authentication/base.rb +36 -0
  9. data/lib/objects/driver/gcp/authentication/oauth.rb +24 -29
  10. data/lib/objects/driver/gcp/authentication/oauth_client_config.rb +22 -0
  11. data/lib/objects/driver/gcp/authentication/oauth_session_store.rb +22 -0
  12. data/lib/objects/driver/gcp/authentication/service_account.rb +57 -2
  13. data/lib/objects/driver/gcp/authentication/signet/service_account.rb +27 -0
  14. data/lib/objects/driver/gcp/authentication/utilities.rb +42 -0
  15. data/lib/objects/encryptor.rb +83 -0
  16. data/lib/objects/exception/base.rb +10 -3
  17. data/lib/objects/exception/ec2_driver_missing_authorization_keys.rb +11 -0
  18. data/lib/objects/exception/empty_namespace_tree.rb +11 -0
  19. data/lib/objects/exception/gcp_auth_service_account_missing_credentials.rb +11 -0
  20. data/lib/objects/exception/invalid_metadata_encryption_key.rb +1 -1
  21. data/lib/objects/exception/missing_gcp_service_account_credentials_filename.rb +11 -0
  22. data/lib/objects/exception/user_orchestration_error.rb +11 -0
  23. data/lib/objects/initialization/factory.rb +36 -0
  24. data/lib/objects/initialization/structure.rb +18 -0
  25. data/lib/objects/initialization/utils.rb +20 -0
  26. data/lib/objects/loading_bar/handler.rb +1 -1
  27. data/lib/objects/loading_bar/indicator/base.rb +1 -0
  28. data/lib/objects/modules/draw.rb +49 -0
  29. data/lib/objects/modules/tree.rb +157 -0
  30. data/lib/objects/modules/workspace_commands.rb +2 -32
  31. data/lib/objects/modules/workspace_menu.rb +113 -48
  32. data/lib/objects/node/attributes.rb +6 -0
  33. data/lib/objects/node/base.rb +27 -7
  34. data/lib/objects/node/cache_handler.rb +1 -1
  35. data/lib/objects/node/factory.rb +15 -11
  36. data/lib/objects/node/inventory/base.rb +9 -3
  37. data/lib/objects/node/inventory/defined.rb +18 -15
  38. data/lib/objects/node/inventory/merge.rb +9 -1
  39. data/lib/objects/node/inventory/subselect.rb +6 -4
  40. data/lib/objects/node/meta_data_factory.rb +1 -1
  41. data/lib/objects/node/meta_data_loader.rb +2 -2
  42. data/lib/objects/node/resources/inventory.rb +19 -0
  43. data/lib/objects/node/resources/merged.rb +23 -14
  44. data/lib/objects/node/resources/sub_inventory.rb +6 -5
  45. data/lib/objects/node/server/base.rb +35 -22
  46. data/lib/objects/node/server/dynamic/ec2.rb +0 -1
  47. data/lib/objects/node/server/dynamic/gcp.rb +0 -1
  48. data/lib/objects/node/server/static.rb +22 -9
  49. data/lib/objects/orchestration/base.rb +7 -1
  50. data/lib/objects/orchestration/interactive_terraform.rb +10 -16
  51. data/lib/objects/registry/command/external.rb +6 -2
  52. data/lib/objects/registry/command/group.rb +5 -1
  53. data/lib/objects/registry/loader.rb +3 -0
  54. data/lib/objects/ssh/command.rb +4 -8
  55. data/lib/objects/ssh/command_exec.rb +3 -1
  56. data/lib/objects/ssh/connection_wrangler.rb +34 -17
  57. data/lib/objects/ssh/connector.rb +17 -9
  58. data/lib/objects/ssh/driver.rb +7 -18
  59. data/lib/objects/ssh/driver_concerns/connection.rb +3 -11
  60. data/lib/objects/ssh/driver_concerns/functions.rb +7 -7
  61. data/lib/objects/ssh/proxy_chain.rb +19 -0
  62. data/lib/objects/ssh/proxy_chain_link.rb +26 -0
  63. data/lib/objects/ssh/proxy_hop.rb +47 -18
  64. data/lib/objects/ssh/script_exec.rb +9 -11
  65. data/lib/objects/startup.rb +7 -1
  66. data/lib/objects/terraform/output.rb +5 -1
  67. data/lib/objects/workspace.rb +10 -0
  68. data/patches/irb.rb +35 -1
  69. data/patches/string.rb +13 -0
  70. metadata +71 -25
  71. data/lib/objects/driver/static.rb +0 -6
@@ -7,13 +7,17 @@ module Bcome::Node::Server
7
7
  end
8
8
 
9
9
  def initialize(params)
10
- config = params[:views]
11
- @identifier = config[:identifier]
12
- @public_ip_address = config[:public_ip_address]
13
- @internal_ip_address = config[:internal_ip_address]
14
- @cloud_tags = config[:cloud_tags]
15
- @description = config[:description]
16
- verify_we_have_at_least_one_interface(config)
10
+ @view_config = params[:views]
11
+
12
+ set_cloud_tags
13
+
14
+ @identifier = @view_config[:identifier]
15
+ @public_ip_address = @view_config[:public_ip_address]
16
+ @internal_ip_address = @view_config[:internal_ip_address]
17
+ @cloud_tags = @view_config[:cloud_tags]
18
+ @description = @view_config[:description]
19
+ verify_we_have_at_least_one_interface
20
+ verify_identifier_and_description
17
21
  super
18
22
  end
19
23
 
@@ -27,8 +31,17 @@ module Bcome::Node::Server
27
31
 
28
32
  attr_reader :description
29
33
 
30
- def verify_we_have_at_least_one_interface(config)
31
- raise Bcome::Exception::MissingIpaddressOnServer, config unless has_at_least_one_interface?
34
+ def set_cloud_tags
35
+ @view_config[:cloud_tags] = ::Bcome::Node::Meta::Cloud.new(@view_config[:cloud_tags]) unless @view_config[:cloud_tags].is_a?(::Bcome::Node::Meta::Cloud)
36
+ end
37
+
38
+ def verify_we_have_at_least_one_interface
39
+ raise Bcome::Exception::MissingIpaddressOnServer, @view_config unless has_at_least_one_interface?
40
+ end
41
+
42
+ def verify_identifier_and_description
43
+ raise Bcome::Exception::Generic, "Your static server defined by #{@view_config} is missing a description" unless @description
44
+ raise Bcome::Exception::Generic, "Your static server defined by #{@view_config} is missing an identifier" unless @identifier
32
45
  end
33
46
 
34
47
  def has_at_least_one_interface?
@@ -10,7 +10,13 @@ module Bcome::Orchestration
10
10
  def do_execute
11
11
  raise Bcome::Exception::MissingExecuteOnRegistryObject, self.class.to_s unless respond_to?(:execute)
12
12
 
13
- execute
13
+ begin
14
+ execute
15
+ rescue ::Bcome::Exception::Base => bcome_exception
16
+ show_backtrace = true unless bcome_exception.is_a?(::Bcome::Exception::InvalidMetaDataEncryptionKey)
17
+ bcome_exception.pretty_display(show_backtrace)
18
+ raise ::Bcome::Exception::UserOrchestrationError, self.class.to_s
19
+ end
14
20
  end
15
21
 
16
22
  def method_missing(method_sym, *_arguments)
@@ -11,14 +11,19 @@ module Bcome::Orchestration
11
11
  QUIT = '\\q'
12
12
  COMMAND_PROMPT = "enter command or '#{QUIT}' to quit: " + 'terraform'.informational + "\s"
13
13
 
14
+ def initialize(*params)
15
+ super
16
+ raise ::Bcome::Exception::Generic, "Missing terraform configuration directory #{path_to_env_config}" unless File.exist?(path_to_env_config)
17
+ end
18
+
14
19
  def execute
15
20
  show_intro_text
16
21
  wait_for_command_input
17
22
  end
18
23
 
19
24
  def show_intro_text
20
- puts "\n"
21
- puts "INTERACTIVE TERRAFORM\n".underline
25
+ puts "\n\n"
26
+ puts "Interactive Terraform\n".underline
22
27
  puts "Namespace:\s" + @node.namespace.to_s.informational
23
28
  puts "Configuration Path:\s" + "#{path_to_env_config}/*".informational
24
29
  puts "\nConfigured metadata:\s" + terraform_metadata.inspect.informational
@@ -29,13 +34,6 @@ module Bcome::Orchestration
29
34
  # PROCESSING INTERACTIVE COMMANDS
30
35
  #
31
36
  def process_command(raw_command)
32
- if raw_command =~ /destroy/
33
- are_you_sure_message = "Are you SURE you want to 'destroy'? Make sure you know what will be destroyed before you continue. (y/n):".warning
34
- response = wait_for_input(are_you_sure_message)
35
- response = wait_for_input(are_you_sure_message) until %w[y n].include?(response)
36
- return if response == 'n'
37
- end
38
-
39
37
  full_command = command(raw_command)
40
38
  @node.execute_local(full_command)
41
39
  wait_for_command_input
@@ -78,8 +76,6 @@ module Bcome::Orchestration
78
76
  end
79
77
 
80
78
  all_vars[:ssh_user] = @node.ssh_driver.user
81
- all_vars[:ssh_key_path] = @node.ssh_driver.ssh_keys.first
82
-
83
79
  all_vars.collect { |key, value| "-var #{key}=\"#{value}\"" }.join("\s")
84
80
  end
85
81
 
@@ -105,11 +101,9 @@ module Bcome::Orchestration
105
101
 
106
102
  # Formulate a terraform command
107
103
  def command(raw_command)
108
- # if raw_command == "init"
109
- # "cd #{path_to_env_config} ; terraform #{raw_command} #{backend_config_parameter_string}"
110
- # else
111
- "cd #{path_to_env_config} ; terraform #{raw_command} #{var_string}"
112
- # end
104
+ cmd = "cd #{path_to_env_config} ; terraform #{raw_command}"
105
+ cmd = "#{cmd} #{var_string}" if raw_command =~ Regexp.new(/^apply$|plan|destroy|refresh/)
106
+ cmd
113
107
  end
114
108
  end
115
109
  end
@@ -31,14 +31,18 @@ module Bcome::Registry::Command
31
31
  raise Bcome::Exception::MissingArgumentForRegistryCommand, error_message_suffix
32
32
  end
33
33
 
34
- substitute_with = [TrueClass, FalseClass].include?(substitute_with.class) ? (substitute_with ? 'true' : 'false') : substitute_with
34
+ substitute_with = if [TrueClass, FalseClass].include?(substitute_with.class)
35
+ substitute_with ? 'true' : 'false'
36
+ else
37
+ substitute_with
38
+ end
35
39
  substituted_command.gsub!("%#{substitution}%", substitute_with)
36
40
  end
37
41
  substituted_command
38
42
  end
39
43
 
40
44
  def namespace_command(node, command)
41
- "#{command} bcome_context=\"#{node.keyed_namespace}\""
45
+ "bcome_context=\"#{node.keyed_namespace}\" #{command}"
42
46
  end
43
47
 
44
48
  def local_command_substitutions
@@ -62,7 +62,11 @@ module Bcome::Registry::Command
62
62
 
63
63
  puts tab_spacing + command_key.resource_key + item_spacing(command_key) + description.resource_value
64
64
 
65
- usage_string = in_console_session? ? command_key.to_s : "bcome #{@node.keyed_namespace.empty? ? '' : "#{@node.keyed_namespace}:"}#{command_key}"
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
66
70
  puts tab_spacing + ("\s" * menu_item_spacing_length) + 'usage: '.instructional + usage_string
67
71
 
68
72
  if defaults.keys.any?
@@ -20,6 +20,9 @@ module Bcome::Registry
20
20
  data.each do |key, commands|
21
21
  begin
22
22
  if /^#{key}$/.match(node.keyed_namespace)
23
+
24
+ next if commands.nil?
25
+
23
26
  commands.each do |c|
24
27
  unless c[:console_command]
25
28
  error_message = "Registry method is missing key 'console_command'."
@@ -16,17 +16,13 @@ module ::Bcome::Ssh
16
16
  @node = nil
17
17
  end
18
18
 
19
- def pretty_result
20
- is_success? ? 'success'.success : 'failure'.error
21
- end
22
-
23
19
  def output
24
20
  cmd_output = @stdout
25
21
 
26
- unless is_success?
27
- cmd_output += "\nExit code: #{@exit_code}"
28
- cmd_output += "\nSTDERR: #{@stderr}" unless @stderr.empty?
29
- end
22
+ cmd_output += "\nExit code:" + "\s#{@exit_code}"
23
+
24
+ cmd_output += "\nSTDERR: #{@stderr}" if exit_code == 1 && !@stderr.empty?
25
+
30
26
  "\n#{cmd_output}"
31
27
  end
32
28
 
@@ -18,6 +18,8 @@ module ::Bcome::Ssh
18
18
 
19
19
  def print_output
20
20
  print "#{@output_string}\n\n"
21
+ rescue StandardError => e
22
+ puts "Could not print #{@output_string.inspect}"
21
23
  end
22
24
 
23
25
  def execute!
@@ -33,7 +35,7 @@ module ::Bcome::Ssh
33
35
  ssh_exec!(ssh, command) # retry, once
34
36
  end
35
37
 
36
- output_append("\n(#{node.namespace})$".terminal_prompt + ">\s#{command.raw} (#{command.pretty_result})\n")
38
+ output_append("\n(#{node.namespace})$".terminal_prompt + ">\s#{command.raw}\n")
37
39
  output_append(command.output.to_s)
38
40
  end
39
41
 
@@ -4,19 +4,17 @@ require 'net/ssh/proxy/jump'
4
4
 
5
5
  module Bcome::Ssh
6
6
  class ConnectionWrangler
7
+ attr_accessor :proxy_details
8
+
7
9
  def initialize(ssh_driver)
8
10
  @ssh_driver = ssh_driver
9
11
  @config = ssh_driver.config[:proxy]
10
12
  @context_node = ssh_driver.context_node
11
13
  @user = ssh_driver.user
14
+ set_proxy_details
12
15
  end
13
16
 
14
17
  ## Accessors --
15
-
16
- def proxy_details
17
- hops.reverse.collect(&:proxy_details)
18
- end
19
-
20
18
  def first_hop
21
19
  hops.reverse.first
22
20
  end
@@ -41,7 +39,7 @@ module Bcome::Ssh
41
39
  def get_ssh_command(config = {}, _proxy_only = false)
42
40
  cmd = has_hop? ? 'ssh -J' : 'ssh'
43
41
  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}"
42
+ cmd += "\s#{@ssh_driver.user}@#{target_machine_ingress_ip}"
45
43
 
46
44
  config[:as_pseudo_tty] ? "#{cmd} -t" : cmd
47
45
  end
@@ -54,19 +52,29 @@ module Bcome::Ssh
54
52
  end
55
53
 
56
54
  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...
55
+ # TODO: - below check is not actually true... you might still want to proxy over SSH...
58
56
  raise ::Bcome::Exception::InvalidPortForwardRequest, 'Connections to this node are not via a proxy. Rather than port forward, try connecting directly.' unless has_hop?
59
57
 
60
58
  cmd = "ssh -N -L #{start_port}:localhost:#{end_port} -J"
61
59
  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}"
60
+ cmd += "\s#{@ssh_driver.user}@#{target_machine_ingress_ip}"
63
61
 
64
62
  cmd
65
63
  end
66
64
 
65
+ def hops
66
+ @hops ||= set_hops
67
+ end
68
+
67
69
  protected
68
70
 
71
+ def set_proxy_details
72
+ @proxy_details ||= hops.compact.collect(&:proxy_details)
73
+ end
74
+
69
75
  def target_machine_ingress_ip
76
+ return @context_node.internal_ip_address if @context_node.local_network?
77
+
70
78
  unless has_hop?
71
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
72
80
  end
@@ -74,10 +82,6 @@ module Bcome::Ssh
74
82
  has_hop? ? @context_node.internal_ip_address : @context_node.public_ip_address
75
83
  end
76
84
 
77
- def hops
78
- @hops ||= set_hops
79
- end
80
-
81
85
  private
82
86
 
83
87
  def set_hops
@@ -85,21 +89,34 @@ module Bcome::Ssh
85
89
 
86
90
  parent = nil
87
91
  iterable_configs.each do |config|
88
- hop = set_proxy_hop(config, parent)
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
89
101
  hop_collection << hop
90
102
  parent = hop
91
103
  end
92
104
 
93
- hop_collection
105
+ hop_collection.compact
94
106
  end
95
107
 
96
- def set_proxy_hop(config, parent)
108
+ def get_proxy_hop(config, parent)
97
109
  config[:fallback_bastion_host_user] = @ssh_driver.fallback_bastion_host_user
98
- ::Bcome::Ssh::ProxyHop.new(config, @context_node, parent)
110
+ h = ::Bcome::Ssh::ProxyHop.new(config, @context_node, parent)
111
+ return h
99
112
  end
100
113
 
101
114
  def iterable_configs
102
- @iterable ||= @config ? (@config.is_a?(Hash) ? [@config] : @config) : []
115
+ @iterable ||= if @config
116
+ @config.is_a?(Hash) ? [@config] : @config
117
+ else
118
+ []
119
+ end
103
120
  end
104
121
  end
105
122
  end
@@ -32,6 +32,7 @@ module Bcome
32
32
  return if number_unconnected_machines == 0 && !ping?
33
33
 
34
34
  if show_progress?
35
+ print "\n"
35
36
  wrap_indicator type: :progress, size: @servers_to_connect.size, title: 'Opening connections' do
36
37
  open_connections
37
38
  end
@@ -66,18 +67,19 @@ module Bcome
66
67
  def open_connections
67
68
  @servers_to_connect.pmap do |machine|
68
69
  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
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
77
79
  rescue Errno::EPIPE, Bcome::Exception::CouldNotInitiateSshConnection, ::Bcome::Exception::InvalidProxyConfig => e
78
80
  signal_failure if show_progress?
79
81
  @connection_exceptions[machine] = e
80
- end
82
+ end
81
83
  end
82
84
  end
83
85
 
@@ -85,6 +87,12 @@ module Bcome
85
87
 
86
88
  def set_servers
87
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
88
96
  end
89
97
 
90
98
  def number_unconnected_machines
@@ -20,21 +20,24 @@ module Bcome::Ssh
20
20
  @connection_wrangler ||= set_connection_wrangler
21
21
  end
22
22
 
23
+ def proxy_chain
24
+ @proxy_chain ||= ::Bcome::Ssh::ProxyChain.new(connection_wrangler)
25
+ end
26
+
23
27
  def set_connection_wrangler
24
- @set_connection_wrangler ||= ::Bcome::Ssh::ConnectionWrangler.new(self)
28
+ @connection_wrangler = ::Bcome::Ssh::ConnectionWrangler.new(self)
25
29
  end
26
30
 
27
31
  def pretty_ssh_config
28
32
  config = {
29
33
  user: user,
30
- ssh_keys: ssh_keys,
31
34
  timeout: timeout_in_seconds
32
35
  }
33
36
 
34
37
  if has_proxy?
35
38
  config[:proxy] = connection_wrangler.proxy_details
36
39
  else
37
- config[:host_or_ip] = @context_node.public_ip_address
40
+ config[:host_or_ip] = node_host_or_ip
38
41
  end
39
42
 
40
43
  config
@@ -48,26 +51,12 @@ module Bcome::Ssh
48
51
  @config[:multi_hop_proxy]
49
52
  end
50
53
 
51
- def node_level_ssh_key_connection_string
52
- key_specified_at_node_level? ? "-i #{node_level_ssh_key}\s" : ''
53
- end
54
-
55
- def key_specified_at_node_level?
56
- !node_level_ssh_key.nil?
57
- end
58
-
59
- def node_level_ssh_key
60
- @config[:ssh_keys] ? @config[:ssh_keys].first : nil
61
- end
62
-
63
54
  def has_multi_hop_proxy?
64
55
  !multi_hop_proxy_config.nil?
65
56
  end
66
57
 
67
58
  def has_proxy?
68
- return false if proxy_config_value && proxy_config_value == -1
69
-
70
- !@config[:proxy].nil?
59
+ return connection_wrangler.has_hop?
71
60
  end
72
61
  end
73
62
  end
@@ -12,9 +12,8 @@ module ::Bcome::Ssh
12
12
  @connection = nil
13
13
  begin
14
14
  raise ::Bcome::Exception::InvalidProxyConfig, "missing target ip address for #{@context_node.identifier}. Perhaps you meant to configure a proxy?" unless node_host_or_ip
15
-
16
15
  @connection = ::Net::SSH.start(node_host_or_ip, user, net_ssh_params)
17
- rescue Net::SSH::Proxy::ConnectError, Net::SSH::ConnectionTimeout => e
16
+ rescue Net::SSH::AuthenticationFailed, Net::SSH::Proxy::ConnectError, Net::SSH::ConnectionTimeout => e
18
17
  raise Bcome::Exception::CouldNotInitiateSshConnection, @context_node.namespace + "\s-\s#{e.message}"
19
18
  end
20
19
  @connection
@@ -45,13 +44,12 @@ module ::Bcome::Ssh
45
44
  end
46
45
 
47
46
  def node_host_or_ip
47
+ return @context_node.internal_ip_address if @context_node.local_network?
48
48
  has_proxy? ? @context_node.internal_ip_address : @context_node.public_ip_address
49
49
  end
50
50
 
51
51
  def net_ssh_params
52
- raise Bcome::Exception::InvalidSshConfig, "Missing ssh keys for #{@context_node.namespace}" unless ssh_keys
53
-
54
- params = { keys: ssh_keys, paranoid: false }
52
+ params = { paranoid: false }
55
53
  params[:proxy] = proxy if has_proxy?
56
54
  params[:timeout] = timeout_in_seconds
57
55
  params[:verbose] = :fatal # All but silent
@@ -63,12 +61,6 @@ module ::Bcome::Ssh
63
61
  @config[:timeout_in_seconds] ||= DEFAULT_TIMEOUT_IN_SECONDS
64
62
  end
65
63
 
66
- ## SSH KEYS
67
-
68
- def ssh_keys
69
- @config[:ssh_keys]
70
- end
71
-
72
64
  ## PROXYING --
73
65
 
74
66
  def proxy