Class: Kitchen::Transport::Ssh::Connection

Inherits:
Base::Connection show all
Defined in:
lib/kitchen/transport/ssh.rb

Overview

A Connection instance can be generated and re-generated, given new connection details such as connection port, hostname, credentials, etc. This object is responsible for carrying out the actions on the remote host such as executing commands, transferring files, etc.

Author:

Instance Method Summary collapse

Methods inherited from Base::Connection

#execute_with_retry, #retry?

Methods included from Logging

#banner, #debug, #error, #fatal, #info, #warn

Constructor Details

#initialize(config = {}) {|self| ... } ⇒ Object

Create a new Connection instance.

Parameters:

  • options (Hash)

    connection options

Yields:

  • (self)

    yields itself for block-style invocation



122
123
124
125
# File 'lib/kitchen/transport/ssh.rb', line 122

def initialize(config = {})
  super(config)
  @session = nil
end

Instance Method Details

#closeObject

Closes the session connection, if it is still active.



128
129
130
131
132
133
134
135
136
137
# File 'lib/kitchen/transport/ssh.rb', line 128

def close
  return if @session.nil?

  string_to_mask = "[SSH] closing connection to #{self}"
  masked_string = Util.mask_values(string_to_mask, %w{password ssh_http_proxy_password})
  logger.debug(masked_string)
  session.close
ensure
  @session = nil
end

#download(remotes, local) ⇒ Object

Download remote files or directories to local host.

Parameters:

  • remotes (Array<String>)

    paths to remote files or directories

  • local (String)

    path to local destination. If ‘local` is an existing directory, `remote` will be downloaded into the directory using its original name

Raises:

  • (TransportFailed)

    if the files could not all be downloaded successfully, which may vary by implementation



202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/kitchen/transport/ssh.rb', line 202

def download(remotes, local)
  # ensure the parent dir of the local target exists
  FileUtils.mkdir_p(File.dirname(local))

  Array(remotes).each do |file|
    logger.debug("Attempting to download '#{file}' as file")
    session.scp.download!(file, local)
  rescue Net::SCP::Error
    begin
      logger.debug("Attempting to download '#{file}' as directory")
      session.scp.download!(file, local, recursive: true)
    rescue Net::SCP::Error
      logger.warn(
        "SCP download failed for file or directory '#{file}', perhaps it does not exist?"
      )
    end
  end
rescue Net::SSH::Exception => ex
  raise SshFailed, "SCP download failed (#{ex.message})"
end

#execute(command) ⇒ Object

Execute a command on the remote host.

Parameters:

  • command (String)

    command string to execute

Raises:

  • (TransportFailed)

    if the command does not exit successfully, which may vary by implementation



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/kitchen/transport/ssh.rb', line 140

def execute(command)
  return if command.nil?

  string_to_mask = "[SSH] #{self} (#{command})"
  masked_string = Util.mask_values(string_to_mask, %w{password ssh_http_proxy_password})
  logger.debug(masked_string)
  exit_code = execute_with_exit_code(command)

  if exit_code != 0
    raise Transport::SshFailed.new(
      "SSH exited (#{exit_code}) for command: [#{command}]",
      exit_code
    )
  end
rescue Net::SSH::Exception => ex
  raise SshFailed, "SSH command failed (#{ex.message})"
end

#login_commandLoginCommand

Builds a LoginCommand which can be used to open an interactive session on the remote host.

Returns:

  • (LoginCommand)

    an object containing the array of command line tokens and exec options to be used in a fork/exec

Raises:



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/kitchen/transport/ssh.rb', line 159

def 
  args  = %w{ -o UserKnownHostsFile=/dev/null }
  args += %w{ -o StrictHostKeyChecking=no }
  args += %w{ -o IdentitiesOnly=yes } if options[:keys]
  args += %W{ -o LogLevel=#{logger.debug? ? "VERBOSE" : "ERROR"} }
  if options.key?(:forward_agent)
    args += %W{ -o ForwardAgent=#{options[:forward_agent] ? "yes" : "no"} }
  end
  if ssh_gateway
    gateway_command = "ssh -q #{ssh_gateway_username}@#{ssh_gateway} nc #{hostname} #{port}"
    args += %W{ -o ProxyCommand=#{gateway_command} -p #{ssh_gateway_port} }
  end
  Array(options[:keys]).each { |ssh_key| args += %W{ -i #{ssh_key} } }
  args += %W{ -p #{port} }
  args += %W{ #{username}@#{hostname} }

  LoginCommand.new("ssh", args)
end

#upload(locals, remote) ⇒ Object

Uploads local files or directories to remote host.

Parameters:

  • locals (Array<String>)

    paths to local files or directories

  • remote (String)

    path to remote destination

Raises:

  • (TransportFailed)

    if the files could not all be uploaded successfully, which may vary by implementation



179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/kitchen/transport/ssh.rb', line 179

def upload(locals, remote)
  logger.debug("TIMING: scp async upload (Kitchen::Transport::Ssh)")
  elapsed = Benchmark.measure do
    waits = []
    Array(locals).map do |local|
      opts = File.directory?(local) ? { recursive: true } : {}

      waits.push(
        session.scp.upload(local, remote, opts) do |_ch, name, sent, total|
          logger.debug("Async Uploaded #{name} (#{total} bytes)") if sent == total
        end
      )
      waits.shift.wait while waits.length >= max_ssh_sessions
    end
    waits.each(&:wait)
  end
  delta = Util.duration(elapsed.real)
  logger.debug("TIMING: scp async upload (Kitchen::Transport::Ssh) took #{delta}")
rescue Net::SSH::Exception => ex
  raise SshFailed, "SCP upload failed (#{ex.message})"
end

#wait_until_readyObject

Block and return only when the remote host is prepared and ready to execute command and upload files. The semantics and details will vary by implementation, but a round trip through the hosted service is preferred to simply waiting on a socket to become available.



224
225
226
227
228
229
230
231
232
233
# File 'lib/kitchen/transport/ssh.rb', line 224

def wait_until_ready
  delay = 3
  session(
    retries: max_wait_until_ready / delay,
    delay:,
    message: "Waiting for SSH service on #{hostname}:#{port}, " \
      "retrying in #{delay} seconds"
  )
  execute(PING_COMMAND.dup)
end