class SSHKit::Backend::SudoNetssh

Values that are static, like the filtered logging patterns, or the name of the env variables, can be implemented, if needed, as Configuration attributes.

Constants

PASSWORD_PROMPT_REGEX
SKIP_STDOUT_LOGGING_PATTERNS

Public Class Methods

config() click to toggle source

It's not possible to send a custom configuration class, so we need to create a custom one. The :owner could be an instance variable, but it's a bit ugly to use a different setting strategy.

# File lib/sshkit/sudo/sudo_netssh.rb, line 31
def config
  @config ||= Class.new(Netssh::Configuration) do
    attr_accessor :owner, :servers_password, :commands_log
  end.new
end
new(*args, &block) click to toggle source
Calls superclass method
# File lib/sshkit/sudo/sudo_netssh.rb, line 38
def initialize(*args, &block)
  super

  @interaction_handler = SSHKit::Sudo::PasswordSendingInteractionHandler.new(self.class.config.servers_password + "\n")
end
pool() click to toggle source

`pool` is a class-level instance variable, so we can't use the superclass' attr_accessor. The attribute is only read though, so we don't need to handle assignments.

# File lib/sshkit/sudo/sudo_netssh.rb, line 23
def pool
  self.superclass.pool
end

Public Instance Methods

capture(*args) click to toggle source
Calls superclass method
# File lib/sshkit/sudo/sudo_netssh.rb, line 44
def capture(*args)
  # To ensure that we clean out the sudo part when the results are returned,
  # otherwise the commands will be corrupt.
  #
  super.gsub(PASSWORD_PROMPT_REGEX, '')
end
upload!(local, remote, options = {}) click to toggle source

Required because the uploaded file is owned by the SSH user, not the owner.

Calls superclass method
# File lib/sshkit/sudo/sudo_netssh.rb, line 53
def upload!(local, remote, options = {})
  super

  # We can't check the user inside the :as block, because @user is root.
  #
  target_user = @user || self.class.config.owner

  as :root do
    execute :chown, target_user, remote
  end
end

Private Instance Methods

command(args, options) click to toggle source
# File lib/sshkit/sudo/sudo_netssh.rb, line 67
def command(args, options)
  options[:interaction_handler] ||= @interaction_handler

  env = (@env || {})

  user = @user || self.class.config.owner

  # As general Linux practice, switching user is not enough - some variables need to be updated
  # as well.
  # For an explanation, see https://saveriomiroddi.github.io/Chef-properly-run-a-resource-as-alternate-user.
  #
  user_home = user == 'root' ? '/root' : "/home/#{user}"
  env = env.merge(user: user, home: user_home)

  # Sshkit::Command#user runs commands in a non-login shell, so that variables are not inherited.
  # We can workaround this by setting them in the env, which is `export`ed, however, only the
  # specified ones are. Since `RAILS_ENV` is common, we pass it.
  #
  env = env.merge(rails_env: fetch(:rails_env))

  SSHKit::Command.new(*args, options.merge(
    {
      in: pwd_path,
      env: env,
      host: @host,
      user: user,
      group: @group,
    }
  ))
end
execute_command(cmd) click to toggle source
# File lib/sshkit/sudo/sudo_netssh.rb, line 98
def execute_command(cmd)
  output.log_command_start(cmd)
  cmd.started = true
  exit_status = nil
  with_ssh do |ssh|
    ssh.open_channel do |chan|
      chan.request_pty
      prepared_command = cmd.to_command

      if self.class.config.commands_log
        IO.write(self.class.config.commands_log, prepared_command + "\n", mode: 'a')
      end

      chan.exec prepared_command do |_ch, _success|
        chan.on_data do |ch, data|
          cmd.on_stdout(ch, data)
          skip_stdout_logging = SKIP_STDOUT_LOGGING_PATTERNS.any? { |pattern| data =~ pattern }
          output.log_command_data(cmd, :stdout, data) unless skip_stdout_logging
        end
        chan.on_extended_data do |ch, _type, data|
          cmd.on_stderr(ch, data)
          output.log_command_data(cmd, :stderr, data)
        end
        chan.on_request("exit-status") do |_ch, data|
          exit_status = data.read_long
        end
        #chan.on_request("exit-signal") do |ch, data|
        #  # TODO: This gets called if the program is killed by a signal
        #  # might also be a worthwhile thing to report
        #  exit_signal = data.read_string.to_i
        #  warn ">>> " + exit_signal.inspect
        #  output.log_command_killed(cmd, exit_signal)
        #end
        chan.on_open_failed do |_ch|
          # TODO: What do do here?
          # I think we should raise something
        end
        chan.on_process do |_ch|
          # TODO: I don't know if this is useful
        end
        chan.on_eof do |_ch|
          # TODO: chan sends EOF before the exit status has been
          # writtend
        end
      end
      chan.wait
    end
    ssh.loop
  end
  # Set exit_status and log the result upon completion
  if exit_status
    cmd.exit_status = exit_status
    output.log_command_exit(cmd)
  end
end