Class: Bcome::Ssh::Driver

Inherits:
Object
  • Object
show all
Defined in:
lib/objects/ssh/driver.rb

Constant Summary collapse

DEFAULT_TIMEOUT_IN_SECONDS =
5
PROXY_CONNECT_PREFIX =
'-o StrictHostKeyChecking=no -W %h:%p'.freeze
PROXY_SSH_PREFIX =
'-o UserKnownHostsFile=/dev/null -o "ProxyCommand ssh -W %h:%p'.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config, context_node) ⇒ Driver

Returns a new instance of Driver.



9
10
11
12
13
14
# File 'lib/objects/ssh/driver.rb', line 9

def initialize(config, context_node)
  @config = config
  @context_node = context_node
  @proxy_data = @config[:proxy] ? ::Bcome::Ssh::ProxyData.new(@config[:proxy], @context_node) : nil
  @bootstrap_settings = @config[:bootstrap_settings] ? ::Bcome::Ssh::Bootstrap.new(@config[:bootstrap_settings]) : nil
end

Instance Attribute Details

#bootstrap_settingsObject (readonly)

Returns the value of attribute bootstrap_settings.



3
4
5
# File 'lib/objects/ssh/driver.rb', line 3

def bootstrap_settings
  @bootstrap_settings
end

#configObject (readonly)

Returns the value of attribute config.



3
4
5
# File 'lib/objects/ssh/driver.rb', line 3

def config
  @config
end

Instance Method Details

#bastion_host_userObject



74
75
76
# File 'lib/objects/ssh/driver.rb', line 74

def bastion_host_user
  bootstrap? && @bootstrap_settings.bastion_host_user ? @bootstrap_settings.bastion_host_user : @proxy_data.bastion_host_user ? @proxy_data.bastion_host_user : user
end

#bootstrap?Boolean

Returns:

  • (Boolean)


16
17
18
# File 'lib/objects/ssh/driver.rb', line 16

def bootstrap?
  @context_node.bootstrap? && has_bootstrap_settings?
end

#bootstrap_proxy_connection_stringObject



65
66
67
# File 'lib/objects/ssh/driver.rb', line 65

def bootstrap_proxy_connection_string
  "ssh -i #{@bootstrap_settings.ssh_key_path} -o StrictHostKeyChecking=no -W %h:%p #{@bootstrap_settings.bastion_host_user}@#{@proxy_data.host}"
end

#bootstrap_rsync_command(local_path, remote_path) ⇒ Object



191
192
193
194
195
196
197
# File 'lib/objects/ssh/driver.rb', line 191

def bootstrap_rsync_command(local_path, remote_path)
  if has_proxy?
    "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}"
  else
    "rsync -i #{@bootstrap_settings.ssh_key_path} -av #{local_path} #{user}@#{@context_node.public_ip_address}:#{remote_path}"
  end
end

#bootstrap_ssh_commandObject



121
122
123
124
125
126
127
# File 'lib/objects/ssh/driver.rb', line 121

def bootstrap_ssh_command
  if has_proxy?
    "ssh -i #{@bootstrap_settings.ssh_key_path} -t #{bastion_host_user}@#{@proxy_data.host} ssh -t #{user}@#{@context_node.internal_ip_address}"
  else
    "ssh -i #{@bootstrap_settings.ssh_key_path} #{user}@#{@context_node.public_ip_address}"
  end
end

#close_ssh_connectionObject



247
248
249
250
251
# File 'lib/objects/ssh/driver.rb', line 247

def close_ssh_connection
  return unless @connection
  @connection.close unless @connection.closed?
  @connection = nil
end

#do_sshObject



69
70
71
72
# File 'lib/objects/ssh/driver.rb', line 69

def do_ssh
  cmd = ssh_command
  @context_node.execute_local(cmd)
end

#fallback_local_userObject



151
152
153
# File 'lib/objects/ssh/driver.rb', line 151

def fallback_local_user
  @fallback_local_user ||= ::Bcome::System::Local.instance.local_user
end

#get(remote_path, local_path) ⇒ Object



234
235
236
237
238
239
240
241
242
243
244
245
# File 'lib/objects/ssh/driver.rb', line 234

def get(remote_path, local_path)
  raise Bcome::Exception::MissingParamsForScp, "'get' requires a local_path and a remote_path" if local_path.to_s.empty? || remote_path.to_s.empty?
  puts "\n(#{@context_node.namespace})\s".namespace + "Downloading #{remote_path} to #{local_path}\n".informational

  begin
    scp.download!(remote_path, local_path, recursive: true) do |_ch, name, sent, total|
      puts "#{name}: #{sent}/#{total}".progress
    end
  rescue Exception => e # scp just throws generic exceptions :-/
    puts e.message.error
  end
end

#get_overriden_local_userObject



147
148
149
# File 'lib/objects/ssh/driver.rb', line 147

def get_overriden_local_user
  ::Bcome::Node::Factory.instance.local_data[:ssh_user]       
end

#has_bootstrap_settings?Boolean

Returns:

  • (Boolean)


20
21
22
# File 'lib/objects/ssh/driver.rb', line 20

def has_bootstrap_settings?
  !@bootstrap_settings.nil?
end

#has_open_ssh_con?Boolean

Returns:

  • (Boolean)


257
258
259
# File 'lib/objects/ssh/driver.rb', line 257

def has_open_ssh_con?
  !@connection.nil? && !@connection.closed?
end

#has_proxy?Boolean

Returns:

  • (Boolean)


52
53
54
55
# File 'lib/objects/ssh/driver.rb', line 52

def has_proxy?
  return false if proxy_config_value && proxy_config_value == -1
  !@config[:proxy].nil?
end

#key_specified_at_node_level?Boolean

Returns:

  • (Boolean)


92
93
94
# File 'lib/objects/ssh/driver.rb', line 92

def key_specified_at_node_level?
  !node_level_ssh_key.nil?
end

#local_port_forward(start_port, end_port) ⇒ Object



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/objects/ssh/driver.rb', line 100

def local_port_forward(start_port, end_port)
  if has_proxy?
    
    if bootstrap?
      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}"
    else
      tunnel_command = "ssh -N -L #{start_port}:#{@context_node.internal_ip_address}:#{end_port} #{bastion_host_user}@#{@proxy_data.host}"
    end
  else
    if bootstrap?
      tunnel_command = "ssh -i #{@bootstrap_settings.ssh_key_path} -N -L #{start_port}:#{@context_node.public_ip_address}:#{end_port}"
    else
      tunnel_command = "ssh -N -L #{start_port}:#{@context_node.public_ip_address}:#{end_port}"
    end
  end

  tunnel = ::Bcome::Ssh::Tunnel::LocalPortForward.new(tunnel_command)
  tunnel.open!
  return tunnel
end

#net_ssh_params(verbose = false) ⇒ Object



159
160
161
162
163
164
165
166
# File 'lib/objects/ssh/driver.rb', line 159

def net_ssh_params(verbose = false)
  raise Bcome::Exception::InvalidSshConfig, "Missing ssh keys for #{@context_node.namespace}" unless ssh_keys
  params = { keys: ssh_keys, paranoid: false }
  params[:proxy] = proxy if has_proxy?
  params[:timeout] = timeout_in_seconds
  params[:verbose] = :debug if verbose
  params
end

#node_host_or_ipObject



155
156
157
# File 'lib/objects/ssh/driver.rb', line 155

def node_host_or_ip
  has_proxy? ? @context_node.internal_ip_address : @context_node.public_ip_address
end

#node_level_ssh_keyObject



96
97
98
# File 'lib/objects/ssh/driver.rb', line 96

def node_level_ssh_key
  return (@config[:ssh_keys]) ? @config[:ssh_keys].first : nil
end

#node_level_ssh_key_connection_stringObject



88
89
90
# File 'lib/objects/ssh/driver.rb', line 88

def node_level_ssh_key_connection_string
  key_specified_at_node_level? ? "-i #{node_level_ssh_key}\s" : ""
end

#overriden_local_userObject



143
144
145
# File 'lib/objects/ssh/driver.rb', line 143

def overriden_local_user
  @overriden_local_user ||= get_overriden_local_user
end

#pingObject



209
210
211
212
213
214
# File 'lib/objects/ssh/driver.rb', line 209

def ping
  ssh_connect!
  return { success: true }
rescue Exception => e
  return { success: false, error: e }
end

#pretty_config_detailsObject



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/objects/ssh/driver.rb', line 24

def pretty_config_details
  config = {
    user: user,
    ssh_keys: ssh_keys,
    timeout: timeout_in_seconds
  }
  if has_proxy?
    config[:host_or_ip] = @context_node.internal_ip_address
    config[:proxy] = {
      bastion_host:  @proxy_data.host,
      bastion_host_user: bastion_host_user
    }
  else
    config[:host_or_ip] = @context_node.public_ip_address
  end
  config
end

#proxyObject



46
47
48
49
50
# File 'lib/objects/ssh/driver.rb', line 46

def proxy
  return nil unless has_proxy?
  connection_string = bootstrap? ? bootstrap_proxy_connection_string : proxy_connection_string
  ::Net::SSH::Proxy::Command.new(connection_string)
end

#proxy_config_valueObject



57
58
59
# File 'lib/objects/ssh/driver.rb', line 57

def proxy_config_value
  @config[:proxy]
end

#proxy_connection_stringObject



61
62
63
# File 'lib/objects/ssh/driver.rb', line 61

def proxy_connection_string
  "ssh #{PROXY_CONNECT_PREFIX} #{bastion_host_user}@#{@proxy_data.host}"
end

#put(local_path, remote_path) ⇒ Object



220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/objects/ssh/driver.rb', line 220

def put(local_path, remote_path)
  raise Bcome::Exception::MissingParamsForScp, "'put' requires a local_path and a remote_path" if local_path.to_s.empty? || remote_path.to_s.empty?
  puts "\n(#{@context_node.namespace})\s".namespace + "Uploading #{local_path} to #{remote_path}\n".informational

  begin
    scp.upload!(local_path, remote_path, recursive: true) do |_ch, name, sent, total|
      puts "#{name}: #{sent}/#{total}".progress
    end
  rescue Exception => e # scp just throws generic exceptions :-/
    puts e.message.error
  end
  nil
end

#rsync(local_path, remote_path) ⇒ Object



176
177
178
179
180
# File 'lib/objects/ssh/driver.rb', line 176

def rsync(local_path, remote_path)
  raise Bcome::Exception::MissingParamsForRsync, "'rsync' requires a local_path and a remote_path" if local_path.to_s.empty? || remote_path.to_s.empty?
  command = rsync_command(local_path, remote_path)
  @context_node.execute_local(command)
end

#rsync_command(local_path, remote_path) ⇒ Object



182
183
184
185
186
187
188
189
# File 'lib/objects/ssh/driver.rb', line 182

def rsync_command(local_path, remote_path)
  return bootstrap_rsync_command(local_path, remote_path) if bootstrap? && @bootstrap_settings.ssh_key_path
  if has_proxy?
    "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}"
  else
    "rsync -av #{local_path} #{user}@#{@context_node.public_ip_address}:#{remote_path}"
  end
end

#scpObject



216
217
218
# File 'lib/objects/ssh/driver.rb', line 216

def scp
  ssh_connection.scp
end

#ssh_command(as_pseudo_tty = false) ⇒ Object



78
79
80
81
82
83
84
85
86
# File 'lib/objects/ssh/driver.rb', line 78

def ssh_command(as_pseudo_tty = false)
  return bootstrap_ssh_command if bootstrap? && @bootstrap_settings.ssh_key_path

  if has_proxy?
    "#{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}"
  else
    "#{as_pseudo_tty ? "ssh -t" : "ssh"} #{node_level_ssh_key_connection_string}#{user}@#{@context_node.public_ip_address}"
  end
end

#ssh_connect!(_verbose = false) ⇒ Object



199
200
201
202
203
204
205
206
207
# File 'lib/objects/ssh/driver.rb', line 199

def ssh_connect!(_verbose = false)
  @connection = nil
  begin
    @connection = ::Net::SSH.start(node_host_or_ip, user, net_ssh_params)
  rescue Net::SSH::Proxy::ConnectError, Net::SSH::ConnectionTimeout, Errno::EPIPE => e
    raise Bcome::Exception::CouldNotInitiateSshConnection, @context_node.namespace + "\s-\s#{e.message}"
  end
  @connection
end

#ssh_connection(_bootstrap = false) ⇒ Object



253
254
255
# File 'lib/objects/ssh/driver.rb', line 253

def ssh_connection(_bootstrap = false)
  has_open_ssh_con? ? @connection : ssh_connect!
end

#ssh_keysObject



168
169
170
171
172
173
174
# File 'lib/objects/ssh/driver.rb', line 168

def ssh_keys
  if bootstrap?
    [@bootstrap_settings.ssh_key_path]
  else
    @config[:ssh_keys]
  end
end

#timeout_in_secondsObject



42
43
44
# File 'lib/objects/ssh/driver.rb', line 42

def timeout_in_seconds
  @config[:timeout_in_seconds] ||= Bcome::Ssh::Driver::DEFAULT_TIMEOUT_IN_SECONDS
end

#userObject



129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/objects/ssh/driver.rb', line 129

def user
  # If we're in bootstrapping mode and have a bootstrap user set, return it
  return @bootstrap_settings.user if (bootstrap? && @bootstrap_settings.user)

  # If we have a user explcitly set in the config, then return it
  return @config[:user] if @config[:user]

  # If the local user has explicitly overriden their user, return that
  return overriden_local_user if overriden_local_user

  # Else fall back to whichever local user is using bcome
  fallback_local_user
end