Class: OpsWalrus::ScopedMappingInteractionHandler

Inherits:
Object
  • Object
show all
Defined in:
lib/opswalrus/interaction_handlers.rb

Constant Summary collapse

STANDARD_SUDO_PASSWORD_PROMPT =
/\[sudo\] password for .*?:/
STANDARD_SSH_PASSWORD_PROMPT =
/.*?@.*?'s password:/

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(mappings, lookback_window_chars = DefaultLookbackWindowCharCount) ⇒ ScopedMappingInteractionHandler

Returns a new instance of ScopedMappingInteractionHandler.



12
13
14
15
# File 'lib/opswalrus/interaction_handlers.rb', line 12

def initialize(mappings, lookback_window_chars = DefaultLookbackWindowCharCount)
  @input_mappings = mappings
  @online_matcher = Kleene::NaiveOnlineRegex.new(mappings.keys, lookback_window_chars)
end

Instance Attribute Details

#input_mappingsObject

Hash[ String | Regex => (String | Proc) ]



10
11
12
# File 'lib/opswalrus/interaction_handlers.rb', line 10

def input_mappings
  @input_mappings
end

Class Method Details

.mapping_for_ops_sudo_prompt(sudo_password) ⇒ Object

sudo_password : String | Nil



34
35
36
37
38
39
# File 'lib/opswalrus/interaction_handlers.rb', line 34

def self.mapping_for_ops_sudo_prompt(sudo_password)
  password_response = sudo_password && ::SSHKit::InteractionHandler::Password.new("#{sudo_password}\n")
  {
    App::LOCAL_SUDO_PASSWORD_PROMPT => password_response,
  }
end

.mapping_for_ssh_password_prompt(ssh_password) ⇒ Object

sudo_password : String | Nil



18
19
20
21
22
23
# File 'lib/opswalrus/interaction_handlers.rb', line 18

def self.mapping_for_ssh_password_prompt(ssh_password)
  password_response = ssh_password && ::SSHKit::InteractionHandler::Password.new("#{ssh_password}\n")
  {
    STANDARD_SSH_PASSWORD_PROMPT => password_response,
  }
end

.mapping_for_sudo_password(sudo_password) ⇒ Object

sudo_password : String | Nil



26
27
28
29
30
31
# File 'lib/opswalrus/interaction_handlers.rb', line 26

def self.mapping_for_sudo_password(sudo_password)
  password_response = sudo_password && ::SSHKit::InteractionHandler::Password.new("#{sudo_password}\n")
  {
    STANDARD_SUDO_PASSWORD_PROMPT => password_response,
  }
end

Instance Method Details

#on_data(_command, stream_name, data, response_channel) ⇒ Object

cmd, :stdout, data, stdin the return value from on_data is returned to Command#call_interaction_handler which is then returned verbatim to Command#on_stdout, which is then returned verbatim to the backend that called #on_stdout, and in my case that is LocalPty#handle_data_for_stdout. So, LocalPty#handle_data_for_stdout -> Command#on_stdout -> Command#call_interaction_handler -> ScopedMappingInteractionHandler#on_data which means that if I return that a password was emitted from this method, then back in LocalPty#handle_data_for_stdout I can discard the subsequent line that I read from stdout in order to read and immediately discard the password that this interaction handler emits.

This method returns the data that is emitted to the response channel as a result of having processed the output from a command that the interaction handler was expecting.



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/opswalrus/interaction_handlers.rb', line 95

def on_data(_command, stream_name, data, response_channel)
  # trace(Style.yellow("regexen=#{@online_matcher.instance_exec { @regexen } }"))
  # trace(Style.yellow("data=`#{data}`"))
  # trace(Style.yellow("buffer=#{@online_matcher.instance_exec { @buffer } }"))
  new_matches = @online_matcher.ingest(data)
  # debug(Style.yellow("new_matches=`#{new_matches}`"))
  response_data = new_matches.find_map do |online_match|
    mapped_output_value = @input_mappings[online_match.regex]
    case mapped_output_value
    when Proc, Method
      mapped_output_value.call(online_match.match)
    when String
      mapped_output_value
    end
  end
  # debug(Style.yellow("response_data=`#{response_data.inspect}`"))

  # response_data = @input_mappings.find_map do |pattern, mapped_output_value|
  #   pattern = pattern.is_a?(String) ? Regexp.new(Regexp.escape(pattern)) : pattern
  #   if pattern_match = data.match(pattern)   # pattern_match : MatchData | Nil
  #     case mapped_output_value
  #     when Proc, Method
  #       mapped_output_value.call(pattern_match)
  #     when String
  #       mapped_output_value
  #     end
  #   end
  # end

  if response_data.nil?
    trace(Style.red("No interaction handler mapping for #{stream_name}: `#{data}` so no response was sent"))
  else
    debug(Style.yellow("Handling #{stream_name} message |>#{data}<|"))
    debug(Style.yellow("Sending response |>#{response_data}<|"))
    if response_channel.respond_to?(:send_data)  # Net SSH Channel
      App.instance.trace "writing: #{response_data.to_s} to Net SSH Channel"
      response_channel.send_data(response_data.to_s)
    elsif response_channel.respond_to?(:write)   # Local IO (stdin)
      App.instance.trace "writing: #{response_data.to_s} to pty stdin"
      response_channel.write(response_data.to_s)
    else
      raise "Unable to write response data to channel #{channel.inspect} - does not support '#send_data' or '#write'"
    end
  end

  response_data
end

#with_mapping(mapping = nil, sudo_password: nil, ops_sudo_password: nil, inherit_existing_mappings: true, lookback_window_chars: DefaultLookbackWindowCharCount) ⇒ Object

temporarily adds the specified input mapping to the interaction handler while the given block is being evaluated when the given block returns, then the temporary mapping is removed from the interaction handler

mapping : Hash[ String | Regex => (String | Proc) ] | Nil



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/opswalrus/interaction_handlers.rb', line 45

def with_mapping(mapping = nil, sudo_password: nil, ops_sudo_password: nil, inherit_existing_mappings: true, lookback_window_chars: DefaultLookbackWindowCharCount)
  new_mapping = inherit_existing_mappings ? @input_mappings.clone : {}

  if mapping
    raise ArgumentError.new("mapping must be a Hash") unless mapping.is_a?(Hash)
    new_mapping.merge!(mapping)
  end

  # ops_sudo_password takes precedence over sudo_password
  password_mappings = if ops_sudo_password
    ScopedMappingInteractionHandler.mapping_for_ops_sudo_prompt(ops_sudo_password).
      merge(ScopedMappingInteractionHandler.mapping_for_sudo_password(nil))
  elsif sudo_password
    ScopedMappingInteractionHandler.mapping_for_sudo_password(sudo_password).
      merge(ScopedMappingInteractionHandler.mapping_for_ops_sudo_prompt(nil))
  end
  new_mapping.merge!(password_mappings) if password_mappings

  # debug(Style.green("mapping: #{mapping}"))
  # debug(Style.green("new_mapping: #{new_mapping}"))
  # debug(Style.green("new_mapping.empty?: #{new_mapping.empty?}"))
  # debug(Style.green("new_mapping == @input_mappings: #{new_mapping == @input_mappings}"))
  if new_mapping.empty? || new_mapping == @input_mappings
    # debug(Style.red("with_mapping -> reset"))
    @online_matcher.reset
    yield self
  else
    # debug(Style.red("with_mapping -> new mapping"))
    yield ScopedMappingInteractionHandler.new(new_mapping, lookback_window_chars)
  end
end