Module: ScoutAgent::Dispatcher

Defined in:
lib/scout_agent/dispatcher.rb

Overview

This is the namespace for the methods that turn a command-line invocation into a command executed by the agent.

Class Method Summary collapse

Class Method Details

.abort_with_ambiguous_assignment(assignment, matches) ⇒ Object

Abort with an error message to the user that says we matched assignment to multiple matches and we need clarification.



195
196
197
198
199
200
201
# File 'lib/scout_agent/dispatcher.rb', line 195

def abort_with_ambiguous_assignment(assignment, matches)
  choices         = matches.map { |m| "'#{File.basename(m, '.rb')}'" }
  choices[-2..-1] = choices[-2..-1].join(", or ")
  abort <<-END_AMBIGUOUS.trim
  Ambiguous command '#{assignment}'.  Did you mean #{choices.join(', ')}?
  END_AMBIGUOUS
end

.abort_with_missing_code(class_name) ⇒ Object

Abort with an error message to the user that warns of a broken command in our system.



215
216
217
# File 'lib/scout_agent/dispatcher.rb', line 215

def abort_with_missing_code(class_name)
  abort "Failed to load '#{class_name}'."
end

.abort_with_unknown_assignment(assignment) ⇒ Object

Abort with an error message to the user that says we were unable to match assignment to a known command.



207
208
209
# File 'lib/scout_agent/dispatcher.rb', line 207

def abort_with_unknown_assignment(assignment)
  abort "Unknown command '#{assignment}'."
end

.dispatch(args = ARGV) ⇒ Object

This method handles the command invocation process. The passed args are parsed for switches and command, then the selected code is loaded and run.



18
19
20
21
22
23
# File 'lib/scout_agent/dispatcher.rb', line 18

def dispatch(args = ARGV)
  switches   = parse_switches(args)
  assignment = parse_assignment(args)
  code       = load_assignment(assignment)
  execute_assignment(assignment, code, switches, args)
end

.execute_assignment(assignment, code, switches, other_args) ⇒ Object

Requires code, loads the Class indicated by assignment, build an instance passing switches and other_args, then executes the command. This method exits with an error if the code cannot be loaded.



180
181
182
183
184
185
186
187
188
189
# File 'lib/scout_agent/dispatcher.rb', line 180

def execute_assignment(assignment, code, switches, other_args)
  require code
  class_name = code.basename(".rb").to_s.CamelCase
  begin
    loaded = Assignment.const_get(class_name)
  rescue NameError  # can't load module
    abort_with_missing_code(class_name)
  end
  loaded.new(switches, other_args).prepare_and_execute
end

.load_assignment(assignment) ⇒ Object

Loads the code matching the assignment command or dies with an error message if the name cannot be matched. Returns the loaded code file.



162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/scout_agent/dispatcher.rb', line 162

def load_assignment(assignment)
  dir     = LIB_DIR + "assignment"
  matches = dir.entries.map { |path| path.to_s }.
                        grep(/#{Regexp.escape(assignment)}\w*\.rb\z/)
  if matches.size > 1
    abort_with_ambiguous_assignment(assignment, matches)
  elsif matches.first and (code = dir + matches.first).exist?
    return code
  else
    abort_with_unknown_assignment(assignment)
  end
end

.parse_assignment(args) ⇒ Object

Locates the command in args. This process aborts with an error message if the given command is malformed. Otherwise the provided name is returned.

If a command is not given, the agent will default "identify" if not configured, "start" if not running, or "status".



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

def parse_assignment(args)
  assignment = args.shift.to_s.downcase
  if assignment.empty?
    if Plan.present?
      if IDCard.new(:lifeline).pid_file.exist?
        return "status"
      else
        return "start"
      end
    else
      return "identify"
    end
  end
  unless assignment =~ /\A\w+\z/
    abort_with_unknown_assignment(assignment)
  end
  assignment
end

.parse_switches(args) ⇒ Object

Removes all supported command-line switches from args. Each switch sets the corresponding value in the Plan and a Hash of all switches are returned from this method.



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
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
# File 'lib/scout_agent/dispatcher.rb', line 30

def parse_switches(args)
  switches = { }

  args.options do |opts|
    opts.banner = <<-END_USAGE.trim
    Usage:
    
      [sudo] #{ScoutAgent.agent_name} [OPTIONS] COMMAND
    
    Use the commands identify, start, and stop to prepare, launch, and
    shutdown the agent respectively.  Those require super user privileges.
    You can also use the status command to check in on a running agent.
    
    END_USAGE

    opts.separator "Basic Options:"
    opts.on( "-s", "--server URL", String,
             "The URL for the server to report to." ) do |url|
      switches[:server_url] = url
    end
    opts.on( "-p", "--proxy URL", String,
             "A proxy URL to pass HTTP requests through." ) do |url|
      switches[:proxy_url] = url
    end
    opts.on( "-d", "--[no-]daemon",
             "Run in the background as a daemon." ) do |boolean|
      switches[:run_as_daemon] = boolean
    end
    opts.on( "-l", "--logging-level LEVEL", %w[DEBUG INFO WARN ERROR FATAL],
             "The minimum level of log message to record." ) do |level|
      switches[:logging_level] = level
    end
    opts.on( "-x", "--[no-]xmpp",
             "Allow the server to send XMPP commands." ) do |boolean|
      switches[:enable_xmpp] = boolean
    end
    opts.on( "-t", "--[no-]test-mode",
             "Used in agent development." ) do |boolean|
      if switches[:test_mode]    = boolean
        switches[:server_url]    = "http://localhost:4567"
        switches[:run_as_daemon] = false
      end
    end

    opts.separator "Expert Options:"
    opts.on( "--[no-]periodic-snapshots",
             "Take regular system snapshots." ) do |boolean|
      switches[:periodic_snapshots] = boolean
    end
    opts.on( "--trusted USER1,USER2,...", Array,
             "A list of trusted XMPP users." ) do |users|
      switches[:xmpp_trusted] = users
    end
    opts.on( "--users NAME1,NAME2,...", Array,
             "A list of users to try switching to." ) do |users|
      switches[:user_choices] = users
    end
    opts.on( "--groups NAME1,NAME2,...", Array,
             "A list of groups to try switching to." ) do |groups|
      switches[:group_choices] = groups
    end
    opts.on( "--prefix PATH", String,
             "A prefix path prepended to all other paths." ) do |path|
      switches[:prefix_path] = path
    end
    [ %w[os_config_path configuration],
      %w[os_db_path     databases],
      %w[os_pid_path    PID\ files],
      %w[os_log_path    log\ files] ].each do |name, used_for|
      opts.on( "--#{name.tr('_', '-')} PATH", String,
               "The path your OS uses for #{used_for}." ) do |path|
        switches[name.to_sym] = path
      end
    end

    opts.separator "Application Options:"
    opts.on( "-h", "--help",
             "Show this message." ) do
      puts opts  # show usage
      exit
    end
    opts.on( "-v", "--version",
             "Display the current version." ) do
      puts "#{ScoutAgent.proper_agent_name} v#{ScoutAgent::VERSION}"
      exit
    end

    begin
      opts.parse!
    rescue OptionParser::ParseError  # failed to parse options
      puts opts  # show usage
      exit
    end
  end
  
  # apply switches so paths will be set correctly for load checks
  Plan.update_from_switches(switches)

  switches
end