fdb02cc0af82d95ed3894f095315fdc7b8cb5ff6
[packages/precise/mcollective.git] / lib / mcollective / optionparser.rb
1 module MCollective
2   # A simple helper to build cli tools that supports a uniform command line
3   # layout.
4   class Optionparser
5     attr_reader :parser
6
7     # Creates a new instance of the parser, you can supply defaults and include named groups of options.
8     #
9     # Starts a parser that defaults to verbose and that includs the filter options:
10     #
11     #  oparser = MCollective::Optionparser.new({:verbose => true}, "filter")
12     #
13     # Stats a parser in non verbose mode that does support discovery
14     #
15     #  oparser = MCollective::Optionparser.new()
16     #
17     # Starts a parser in verbose mode that does not show the common options:
18     #
19     #  oparser = MCollective::Optionparser.new({:verbose => true}, "filter", "common")
20     def initialize(defaults = {}, include_sections = nil, exclude_sections = nil)
21       @parser = ::OptionParser.new
22
23       @include = [include_sections].flatten
24       @exclude = [exclude_sections].flatten
25
26       @options = Util.default_options
27
28       @options.merge!(defaults)
29     end
30
31     # Parse the options returning the options, you can pass a block that adds additional options
32     # to the Optionparser.
33     #
34     # The sample below starts a parser that also prompts for --arguments in addition to the defaults.
35     # It also sets the description and shows a usage message specific to this app.
36     #
37     #  options = oparser.parse{|parser, options|
38     #       parser.define_head "Control the mcollective controller daemon"
39     #       parser.banner = "Usage: sh-mcollective [options] command"
40     #
41     #       parser.on('--arg', '--argument ARGUMENT', 'Argument to pass to agent') do |v|
42     #           options[:argument] = v
43     #       end
44     #  }
45     #
46     # Users can set default options that get parsed in using the MCOLLECTIVE_EXTRA_OPTS environemnt
47     # variable
48     def parse(&block)
49       yield(@parser, @options) if block_given?
50
51       add_required_options
52
53       add_common_options unless @exclude.include?("common")
54
55       @include.each do |i|
56         next if @exclude.include?(i)
57
58         options_name = "add_#{i}_options"
59         send(options_name)  if respond_to?(options_name)
60       end
61
62       @parser.environment("MCOLLECTIVE_EXTRA_OPTS")
63
64       @parser.parse!
65
66       @options[:collective] = Config.instance.main_collective unless @options[:collective]
67
68       @options
69     end
70
71     # These options will be added if you pass 'filter' into the include list of the
72     # constructor.
73     def add_filter_options
74       @parser.separator ""
75       @parser.separator "Host Filters"
76
77       @parser.on('-W', '--with FILTER', 'Combined classes and facts filter') do |f|
78         f.split(" ").each do |filter|
79           begin
80             fact_parsed = parse_fact(filter)
81             @options[:filter]["fact"] << fact_parsed
82           rescue
83             @options[:filter]["cf_class"] << filter
84           end
85         end
86       end
87
88       @parser.on('-S', '--select FILTER', 'Compound filter combining facts and classes') do |f|
89         @options[:filter]["compound"] << Matcher.create_compound_callstack(f)
90       end
91
92       @parser.on('-F', '--wf', '--with-fact fact=val', 'Match hosts with a certain fact') do |f|
93         fact_parsed = parse_fact(f)
94
95         @options[:filter]["fact"] << fact_parsed if fact_parsed
96       end
97
98       @parser.on('-C', '--wc', '--with-class CLASS', 'Match hosts with a certain config management class') do |f|
99         @options[:filter]["cf_class"] << f
100       end
101
102       @parser.on('-A', '--wa', '--with-agent AGENT', 'Match hosts with a certain agent') do |a|
103         @options[:filter]["agent"] << a
104       end
105
106       @parser.on('-I', '--wi', '--with-identity IDENT', 'Match hosts with a certain configured identity') do |a|
107         @options[:filter]["identity"] << a
108       end
109     end
110
111     # These options should always be present
112     def add_required_options
113       @parser.on('-c', '--config FILE', 'Load configuratuion from file rather than default') do |f|
114         @options[:config] = f
115       end
116
117       @parser.on('-v', '--verbose', 'Be verbose') do |v|
118         @options[:verbose] = v
119       end
120
121       @parser.on('-h', '--help', 'Display this screen') do
122         puts @parser
123         exit! 1
124       end
125     end
126
127     # These options will be added to most cli tools
128     def add_common_options
129       @parser.separator ""
130       @parser.separator "Common Options"
131
132       @parser.on('-T', '--target COLLECTIVE', 'Target messages to a specific sub collective') do |f|
133         @options[:collective] = f
134       end
135
136       @parser.on('--dt', '--discovery-timeout SECONDS', Integer, 'Timeout for doing discovery') do |t|
137         @options[:disctimeout] = t
138       end
139
140       @parser.on('-t', '--timeout SECONDS', Integer, 'Timeout for calling remote agents') do |t|
141         @options[:timeout] = t
142       end
143
144       @parser.on('-q', '--quiet', 'Do not be verbose') do |v|
145         @options[:verbose] = false
146       end
147
148       @parser.on('--ttl TTL', 'Set the message validity period') do |v|
149         @options[:ttl] = v.to_i
150       end
151
152       @parser.on('--reply-to TARGET', 'Set a custom target for replies') do |v|
153         @options[:reply_to] = v
154       end
155
156       @parser.on('--dm', '--disc-method METHOD', 'Which discovery method to use') do |v|
157         raise "Discovery method is already set by a competing option" if @options[:discovery_method] && @options[:discovery_method] != v
158         @options[:discovery_method] = v
159       end
160
161       @parser.on('--do', '--disc-option OPTION', 'Options to pass to the discovery method') do |a|
162         @options[:discovery_options] << a
163       end
164
165       @parser.on("--nodes FILE", "List of nodes to address") do |v|
166         raise "Cannot mix --disc-method, --disc-option and --nodes" if @options[:discovery_method] || @options[:discovery_options].size > 0
167         raise "Cannot read the discovery file #{v}" unless File.readable?(v)
168
169         @options[:discovery_method] = "flatfile"
170         @options[:discovery_options] = v
171       end
172     end
173
174     private
175     # Parse a fact filter string like foo=bar into the tuple hash thats needed
176     def parse_fact(fact)
177       Util.parse_fact_string(fact)
178     end
179
180   end
181 end