choria-mcorpc-support 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (133) hide show
  1. checksums.yaml +7 -0
  2. data/bin/mco +64 -0
  3. data/lib/mcollective.rb +63 -0
  4. data/lib/mcollective/agent.rb +5 -0
  5. data/lib/mcollective/agents.rb +149 -0
  6. data/lib/mcollective/aggregate.rb +85 -0
  7. data/lib/mcollective/aggregate/average.ddl +33 -0
  8. data/lib/mcollective/aggregate/average.rb +29 -0
  9. data/lib/mcollective/aggregate/base.rb +40 -0
  10. data/lib/mcollective/aggregate/result.rb +9 -0
  11. data/lib/mcollective/aggregate/result/base.rb +25 -0
  12. data/lib/mcollective/aggregate/result/collection_result.rb +19 -0
  13. data/lib/mcollective/aggregate/result/numeric_result.rb +13 -0
  14. data/lib/mcollective/aggregate/sum.ddl +33 -0
  15. data/lib/mcollective/aggregate/sum.rb +18 -0
  16. data/lib/mcollective/aggregate/summary.ddl +33 -0
  17. data/lib/mcollective/aggregate/summary.rb +53 -0
  18. data/lib/mcollective/application.rb +365 -0
  19. data/lib/mcollective/application/completion.rb +104 -0
  20. data/lib/mcollective/application/describe_filter.rb +87 -0
  21. data/lib/mcollective/application/facts.rb +62 -0
  22. data/lib/mcollective/application/find.rb +23 -0
  23. data/lib/mcollective/application/help.rb +28 -0
  24. data/lib/mcollective/application/inventory.rb +344 -0
  25. data/lib/mcollective/application/ping.rb +82 -0
  26. data/lib/mcollective/application/plugin.rb +369 -0
  27. data/lib/mcollective/application/rpc.rb +111 -0
  28. data/lib/mcollective/applications.rb +134 -0
  29. data/lib/mcollective/cache.rb +145 -0
  30. data/lib/mcollective/client.rb +353 -0
  31. data/lib/mcollective/config.rb +245 -0
  32. data/lib/mcollective/connector.rb +18 -0
  33. data/lib/mcollective/connector/base.rb +26 -0
  34. data/lib/mcollective/data.rb +91 -0
  35. data/lib/mcollective/data/agent_data.ddl +22 -0
  36. data/lib/mcollective/data/agent_data.rb +17 -0
  37. data/lib/mcollective/data/base.rb +67 -0
  38. data/lib/mcollective/data/collective_data.ddl +20 -0
  39. data/lib/mcollective/data/collective_data.rb +9 -0
  40. data/lib/mcollective/data/fact_data.ddl +28 -0
  41. data/lib/mcollective/data/fact_data.rb +55 -0
  42. data/lib/mcollective/data/fstat_data.ddl +89 -0
  43. data/lib/mcollective/data/fstat_data.rb +56 -0
  44. data/lib/mcollective/data/result.rb +45 -0
  45. data/lib/mcollective/ddl.rb +113 -0
  46. data/lib/mcollective/ddl/agentddl.rb +253 -0
  47. data/lib/mcollective/ddl/base.rb +217 -0
  48. data/lib/mcollective/ddl/dataddl.rb +56 -0
  49. data/lib/mcollective/ddl/discoveryddl.rb +52 -0
  50. data/lib/mcollective/ddl/validatorddl.rb +6 -0
  51. data/lib/mcollective/discovery.rb +143 -0
  52. data/lib/mcollective/discovery/flatfile.ddl +11 -0
  53. data/lib/mcollective/discovery/flatfile.rb +48 -0
  54. data/lib/mcollective/discovery/mc.ddl +11 -0
  55. data/lib/mcollective/discovery/mc.rb +30 -0
  56. data/lib/mcollective/discovery/stdin.ddl +11 -0
  57. data/lib/mcollective/discovery/stdin.rb +68 -0
  58. data/lib/mcollective/exceptions.rb +28 -0
  59. data/lib/mcollective/facts.rb +39 -0
  60. data/lib/mcollective/facts/base.rb +100 -0
  61. data/lib/mcollective/facts/yaml_facts.rb +65 -0
  62. data/lib/mcollective/generators.rb +7 -0
  63. data/lib/mcollective/generators/agent_generator.rb +51 -0
  64. data/lib/mcollective/generators/base.rb +46 -0
  65. data/lib/mcollective/generators/data_generator.rb +51 -0
  66. data/lib/mcollective/generators/templates/action_snippet.erb +13 -0
  67. data/lib/mcollective/generators/templates/data_input_snippet.erb +7 -0
  68. data/lib/mcollective/generators/templates/ddl.erb +8 -0
  69. data/lib/mcollective/generators/templates/plugin.erb +7 -0
  70. data/lib/mcollective/log.rb +118 -0
  71. data/lib/mcollective/logger.rb +5 -0
  72. data/lib/mcollective/logger/base.rb +77 -0
  73. data/lib/mcollective/logger/console_logger.rb +61 -0
  74. data/lib/mcollective/logger/file_logger.rb +53 -0
  75. data/lib/mcollective/logger/syslog_logger.rb +53 -0
  76. data/lib/mcollective/matcher.rb +224 -0
  77. data/lib/mcollective/matcher/parser.rb +128 -0
  78. data/lib/mcollective/matcher/scanner.rb +241 -0
  79. data/lib/mcollective/message.rb +248 -0
  80. data/lib/mcollective/monkey_patches.rb +152 -0
  81. data/lib/mcollective/optionparser.rb +197 -0
  82. data/lib/mcollective/pluginmanager.rb +180 -0
  83. data/lib/mcollective/pluginpackager.rb +98 -0
  84. data/lib/mcollective/pluginpackager/agent_definition.rb +94 -0
  85. data/lib/mcollective/pluginpackager/debpackage_packager.rb +237 -0
  86. data/lib/mcollective/pluginpackager/modulepackage_packager.rb +127 -0
  87. data/lib/mcollective/pluginpackager/ospackage_packager.rb +59 -0
  88. data/lib/mcollective/pluginpackager/rpmpackage_packager.rb +180 -0
  89. data/lib/mcollective/pluginpackager/standard_definition.rb +69 -0
  90. data/lib/mcollective/pluginpackager/templates/debian/Makefile.erb +7 -0
  91. data/lib/mcollective/pluginpackager/templates/debian/changelog.erb +5 -0
  92. data/lib/mcollective/pluginpackager/templates/debian/compat.erb +1 -0
  93. data/lib/mcollective/pluginpackager/templates/debian/control.erb +15 -0
  94. data/lib/mcollective/pluginpackager/templates/debian/copyright.erb +8 -0
  95. data/lib/mcollective/pluginpackager/templates/debian/rules.erb +6 -0
  96. data/lib/mcollective/pluginpackager/templates/module/Modulefile.erb +5 -0
  97. data/lib/mcollective/pluginpackager/templates/module/README.md.erb +37 -0
  98. data/lib/mcollective/pluginpackager/templates/module/_manifest.pp.erb +9 -0
  99. data/lib/mcollective/pluginpackager/templates/redhat/rpm_spec.erb +63 -0
  100. data/lib/mcollective/registration/base.rb +91 -0
  101. data/lib/mcollective/rpc.rb +182 -0
  102. data/lib/mcollective/rpc/actionrunner.rb +158 -0
  103. data/lib/mcollective/rpc/agent.rb +374 -0
  104. data/lib/mcollective/rpc/audit.rb +38 -0
  105. data/lib/mcollective/rpc/client.rb +1066 -0
  106. data/lib/mcollective/rpc/helpers.rb +321 -0
  107. data/lib/mcollective/rpc/progress.rb +63 -0
  108. data/lib/mcollective/rpc/reply.rb +87 -0
  109. data/lib/mcollective/rpc/request.rb +86 -0
  110. data/lib/mcollective/rpc/result.rb +90 -0
  111. data/lib/mcollective/rpc/stats.rb +294 -0
  112. data/lib/mcollective/runnerstats.rb +90 -0
  113. data/lib/mcollective/security.rb +26 -0
  114. data/lib/mcollective/security/base.rb +244 -0
  115. data/lib/mcollective/shell.rb +126 -0
  116. data/lib/mcollective/ssl.rb +285 -0
  117. data/lib/mcollective/util.rb +579 -0
  118. data/lib/mcollective/validator.rb +85 -0
  119. data/lib/mcollective/validator/array_validator.ddl +7 -0
  120. data/lib/mcollective/validator/array_validator.rb +9 -0
  121. data/lib/mcollective/validator/ipv4address_validator.ddl +7 -0
  122. data/lib/mcollective/validator/ipv4address_validator.rb +16 -0
  123. data/lib/mcollective/validator/ipv6address_validator.ddl +7 -0
  124. data/lib/mcollective/validator/ipv6address_validator.rb +16 -0
  125. data/lib/mcollective/validator/length_validator.ddl +7 -0
  126. data/lib/mcollective/validator/length_validator.rb +11 -0
  127. data/lib/mcollective/validator/regex_validator.ddl +7 -0
  128. data/lib/mcollective/validator/regex_validator.rb +9 -0
  129. data/lib/mcollective/validator/shellsafe_validator.ddl +7 -0
  130. data/lib/mcollective/validator/shellsafe_validator.rb +13 -0
  131. data/lib/mcollective/validator/typecheck_validator.ddl +7 -0
  132. data/lib/mcollective/validator/typecheck_validator.rb +28 -0
  133. metadata +215 -0
@@ -0,0 +1,248 @@
1
+ module MCollective
2
+ # container for a message, its headers, agent, collective and other meta data
3
+ class Message
4
+ attr_reader :message, :request, :validated, :msgtime, :payload, :type, :expected_msgid, :reply_to
5
+ attr_accessor :headers, :agent, :collective, :filter
6
+ attr_accessor :requestid, :discovered_hosts, :options, :ttl
7
+
8
+ VALIDTYPES = [:message, :request, :direct_request, :reply]
9
+
10
+ # payload - the message body without headers etc, just the text
11
+ # message - the original message received from the middleware
12
+ # options[:base64] - if the body base64 encoded?
13
+ # options[:agent] - the agent the message is for/from
14
+ # options[:collective] - the collective its for/from
15
+ # options[:headers] - the message headers
16
+ # options[:type] - an indicator about the type of message, :message, :request, :direct_request or :reply
17
+ # options[:request] - if this is a reply this should old the message we are replying to
18
+ # options[:filter] - for requests, the filter to encode into the message
19
+ # options[:options] - the normal client options hash
20
+ # options[:ttl] - the maximum amount of seconds this message can be valid for
21
+ # options[:expected_msgid] - in the case of replies this is the msgid it is expecting in the replies
22
+ # options[:requestid] - specific request id to use else one will be generated
23
+ def initialize(payload, message, options = {})
24
+ options = {:base64 => false,
25
+ :agent => nil,
26
+ :headers => {},
27
+ :type => :message,
28
+ :request => nil,
29
+ :filter => Util.empty_filter,
30
+ :options => {},
31
+ :ttl => 60,
32
+ :expected_msgid => nil,
33
+ :requestid => nil,
34
+ :collective => nil}.merge(options)
35
+
36
+ @payload = payload
37
+ @message = message
38
+ @requestid = options[:requestid]
39
+ @discovered_hosts = nil
40
+ @reply_to = nil
41
+
42
+ @type = options[:type]
43
+ @headers = options[:headers]
44
+ @base64 = options[:base64]
45
+ @filter = options[:filter]
46
+ @expected_msgid = options[:expected_msgid]
47
+ @options = options[:options]
48
+
49
+ @ttl = @options[:ttl] || Config.instance.ttl
50
+ @msgtime = 0
51
+
52
+ @validated = false
53
+
54
+ if options[:request]
55
+ @request = options[:request]
56
+ @agent = request.agent
57
+ @collective = request.collective
58
+ @type = :reply
59
+ else
60
+ @agent = options[:agent]
61
+ @collective = options[:collective]
62
+ end
63
+
64
+ base64_decode!
65
+ end
66
+
67
+ # Sets the message type to one of the known types. In the case of :direct_request
68
+ # the list of hosts to communicate with should have been set with #discovered_hosts
69
+ # else an exception will be raised. This is for extra security, we never accidentally
70
+ # want to send a direct request without a list of hosts or something weird like that
71
+ # as it might result in a filterless broadcast being sent.
72
+ #
73
+ # Additionally you simply cannot set :direct_request if direct_addressing was not enabled
74
+ # this is to force a workflow that doesnt not yield in a mistake when someone might assume
75
+ # direct_addressing is enabled when its not.
76
+ def type=(type)
77
+ raise "Unknown message type #{type}" unless VALIDTYPES.include?(type)
78
+
79
+ if type == :direct_request
80
+ raise "Direct requests is not enabled using the direct_addressing config option" unless Config.instance.direct_addressing
81
+
82
+ unless @discovered_hosts && !@discovered_hosts.empty?
83
+ raise "Can only set type to :direct_request if discovered_hosts have been set"
84
+ end
85
+
86
+ # clear out the filter, custom discovery sources might interpret the filters
87
+ # different than the remote mcollectived and in directed mode really the only
88
+ # filter that matters is the agent filter
89
+ @filter = Util.empty_filter
90
+ @filter["agent"] << @agent
91
+ end
92
+
93
+ @type = type
94
+ end
95
+
96
+ # Sets a custom reply-to target for requests. The connector plugin should inspect this
97
+ # when constructing requests and set this header ensuring replies will go to the custom target
98
+ # otherwise the connector should just do what it usually does
99
+ def reply_to=(target)
100
+ raise "Custom reply targets can only be set on requests" unless [:request, :direct_request].include?(@type)
101
+
102
+ @reply_to = target
103
+ end
104
+
105
+ # in the case of reply messages we are expecting replies to a previously
106
+ # created message. This stores a hint to that previously sent message id
107
+ # and can be used by other classes like the security plugins as a means
108
+ # of optimizing their behavior like by ignoring messages not directed
109
+ # at us.
110
+ def expected_msgid=(msgid)
111
+ raise "Can only store the expected msgid for reply messages" unless @type == :reply
112
+ @expected_msgid = msgid
113
+ end
114
+
115
+ def base64_decode!
116
+ return unless @base64
117
+
118
+ @payload = SSL.base64_decode(@payload)
119
+ @base64 = false
120
+ end
121
+
122
+ def base64_encode!
123
+ return if @base64
124
+
125
+ @payload = SSL.base64_encode(@payload)
126
+ @base64 = true
127
+ end
128
+
129
+ def base64?
130
+ @base64
131
+ end
132
+
133
+ def description
134
+ cid = ""
135
+ cid += payload[:callerid] + "@" if payload.include?(:callerid)
136
+ cid += payload[:senderid]
137
+
138
+ "#{requestid} for agent '#{agent}' in collective '#{collective}' from #{cid}"
139
+ end
140
+
141
+ def encode!
142
+ case type
143
+ when :reply
144
+ raise "Cannot encode a reply message if no request has been associated with it" unless request
145
+ raise 'callerid in original request is not valid, surpressing reply to potentially forged request' unless PluginManager["security_plugin"].valid_callerid?(request.payload[:callerid])
146
+
147
+ @requestid = request.payload[:requestid]
148
+ @payload = PluginManager["security_plugin"].encodereply(agent, payload, requestid, request.payload[:callerid])
149
+ when :request, :direct_request
150
+ validate_compound_filter(@filter["compound"]) unless @filter["compound"].empty?
151
+
152
+ @requestid = create_reqid unless @requestid
153
+ @payload = PluginManager["security_plugin"].encoderequest(Config.instance.identity, payload, requestid, filter, agent, collective, ttl)
154
+ else
155
+ raise "Cannot encode #{type} messages"
156
+ end
157
+ end
158
+
159
+ def validate_compound_filter(compound_filter)
160
+ compound_filter.each do |filter|
161
+ filter.each do |statement|
162
+ if statement["fstatement"]
163
+ functionname = statement["fstatement"]["name"]
164
+ pluginname = Data.pluginname(functionname)
165
+ value = statement["fstatement"]["value"]
166
+
167
+ ddl = DDL.new(pluginname, :data)
168
+
169
+ # parses numbers and booleans entered as strings into proper
170
+ # types of data so that DDL validation will pass
171
+ statement["fstatement"]["params"] = Data.ddl_transform_input(ddl, statement["fstatement"]["params"])
172
+
173
+ Data.ddl_validate(ddl, statement["fstatement"]["params"])
174
+
175
+ unless value && Data.ddl_has_output?(ddl, value)
176
+ DDL.validation_fail!(:PLMC41, "Data plugin '%{functionname}()' does not return a '%{value}' value", :error, {:functionname => functionname, :value => value})
177
+ end
178
+ end
179
+ end
180
+ end
181
+ end
182
+
183
+ def decode!
184
+ raise "Cannot decode message type #{type}" unless [:request, :reply].include?(type)
185
+
186
+ begin
187
+ @payload = PluginManager["security_plugin"].decodemsg(self)
188
+ rescue Exception => e
189
+ if type == :request
190
+ # If we're a server receiving a request, reraise
191
+ raise(e)
192
+ else
193
+ # We're in the client, log and carry on as best we can
194
+
195
+ # Note: mc_sender is unverified. The verified identity is in the
196
+ # payload we just failed to decode
197
+ Log.warn("Failed to decode a message from '#{headers["mc_sender"]}': #{e}")
198
+ return
199
+ end
200
+ end
201
+
202
+ if type == :request
203
+ raise 'callerid in request is not valid, surpressing reply to potentially forged request' unless PluginManager["security_plugin"].valid_callerid?(payload[:callerid])
204
+ end
205
+
206
+ [:collective, :agent, :filter, :requestid, :ttl, :msgtime].each do |prop|
207
+ instance_variable_set("@#{prop}", payload[prop]) if payload.include?(prop)
208
+ end
209
+ end
210
+
211
+ # Perform validation against the message by checking filters and ttl
212
+ def validate
213
+ raise "Can only validate request messages" unless type == :request
214
+
215
+ msg_age = Time.now.utc.to_i - msgtime
216
+
217
+ if msg_age > ttl
218
+ PluginManager["global_stats"].ttlexpired
219
+ raise(MsgTTLExpired, "Message #{description} created at #{msgtime} is #{msg_age} seconds old, TTL is #{ttl}. Rejecting message.")
220
+ end
221
+
222
+ raise(NotTargettedAtUs, "Message #{description} does not pass filters. Ignoring message.") unless PluginManager["security_plugin"].validate_filter?(payload[:filter])
223
+
224
+ @validated = true
225
+ end
226
+
227
+ # publish a reply message by creating a target name and sending it
228
+ def publish
229
+ # If we've been specificaly told about hosts that were discovered
230
+ # use that information to do P2P calls if appropriate else just
231
+ # send it as is.
232
+ config = Config.instance
233
+ if @discovered_hosts && config.direct_addressing && (@discovered_hosts.size <= config.direct_addressing_threshold)
234
+ self.type = :direct_request
235
+ Log.debug("Handling #{requestid} as a direct request")
236
+ end
237
+
238
+ PluginManager['connector_plugin'].publish(self)
239
+ end
240
+
241
+ def create_reqid
242
+ # we gsub out the -s so that the format of the id does not
243
+ # change from previous versions, these should just be more
244
+ # unique than previous ones
245
+ SSL.uuid.gsub("-", "")
246
+ end
247
+ end
248
+ end
@@ -0,0 +1,152 @@
1
+ # start_with? was introduced in 1.8.7, we need to support
2
+ # 1.8.5 and 1.8.6
3
+ class String
4
+ def start_with?(str)
5
+ return self[0..(str.length-1)] == str
6
+ end unless method_defined?("start_with?")
7
+ end
8
+
9
+ # Make arrays of Symbols sortable
10
+ class Symbol
11
+ include Comparable
12
+
13
+ def <=>(other)
14
+ self.to_s <=> other.to_s
15
+ end unless method_defined?("<=>")
16
+ end
17
+
18
+ # This provides an alias for RbConfig to Config for versions of Ruby older then
19
+ # # version 1.8.5. This allows us to use RbConfig in place of the older Config in
20
+ # # our code and still be compatible with at least Ruby 1.8.1.
21
+ # require 'rbconfig'
22
+ unless defined? ::RbConfig
23
+ ::RbConfig = ::Config
24
+ end
25
+
26
+ # a method # that walks an array in groups, pass a block to
27
+ # call the block on each sub array
28
+ class Array
29
+ def in_groups_of(chunk_size, padded_with=nil, &block)
30
+ arr = self.clone
31
+
32
+ # how many to add
33
+ padding = chunk_size - (arr.size % chunk_size)
34
+
35
+ # pad at the end
36
+ arr.concat([padded_with] * padding) unless padding == chunk_size
37
+
38
+ # how many chunks we'll make
39
+ count = arr.size / chunk_size
40
+
41
+ # make that many arrays
42
+ result = []
43
+ count.times {|s| result << arr[s * chunk_size, chunk_size]}
44
+
45
+ if block_given?
46
+ result.each_with_index do |a, i|
47
+ case block.arity
48
+ when 1
49
+ yield(a)
50
+ when 2
51
+ yield(a, (i == result.size - 1))
52
+ else
53
+ raise "Expected 1 or 2 arguments, got #{block.arity}"
54
+ end
55
+ end
56
+ else
57
+ result
58
+ end
59
+ end unless method_defined?(:in_groups_of)
60
+ end
61
+
62
+ class String
63
+ def bytes(&block)
64
+ # This should not be necessary, really ...
65
+ require 'enumerator'
66
+ return to_enum(:each_byte) unless block_given?
67
+ each_byte(&block)
68
+ end unless method_defined?(:bytes)
69
+ end
70
+
71
+ class Dir
72
+ def self.mktmpdir(prefix_suffix=nil, tmpdir=nil)
73
+ case prefix_suffix
74
+ when nil
75
+ prefix = "d"
76
+ suffix = ""
77
+ when String
78
+ prefix = prefix_suffix
79
+ suffix = ""
80
+ when Array
81
+ prefix = prefix_suffix[0]
82
+ suffix = prefix_suffix[1]
83
+ else
84
+ raise ArgumentError, "unexpected prefix_suffix: #{prefix_suffix.inspect}"
85
+ end
86
+ tmpdir ||= Dir.tmpdir
87
+ t = Time.now.strftime("%Y%m%d")
88
+ n = nil
89
+ begin
90
+ path = "#{tmpdir}/#{prefix}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}"
91
+ path << "-#{n}" if n
92
+ path << suffix
93
+ Dir.mkdir(path, 0700)
94
+ rescue Errno::EEXIST
95
+ n ||= 0
96
+ n += 1
97
+ retry
98
+ end
99
+
100
+ if block_given?
101
+ begin
102
+ yield path
103
+ ensure
104
+ FileUtils.remove_entry_secure path
105
+ end
106
+ else
107
+ path
108
+ end
109
+ end unless method_defined?(:mktmpdir)
110
+
111
+ def self.tmpdir
112
+ tmp = '.'
113
+ for dir in [ENV['TMPDIR'], ENV['TMP'], ENV['TEMP'], '/tmp']
114
+ if dir and stat = File.stat(dir) and stat.directory? and stat.writable?
115
+ tmp = dir
116
+ break
117
+ end rescue nil
118
+ end
119
+ File.expand_path(tmp)
120
+ end unless method_defined?(:tmpdir)
121
+ end
122
+
123
+ # Reject all SSLv2 ciphers and all SSLv2 or SSLv3 handshakes by default
124
+ require 'openssl'
125
+ class OpenSSL::SSL::SSLContext
126
+ if DEFAULT_PARAMS[:options]
127
+ DEFAULT_PARAMS[:options] |= OpenSSL::SSL::OP_NO_SSLv2 | OpenSSL::SSL::OP_NO_SSLv3
128
+ else
129
+ DEFAULT_PARAMS[:options] = OpenSSL::SSL::OP_NO_SSLv2 | OpenSSL::SSL::OP_NO_SSLv3
130
+ end
131
+
132
+ # ruby 1.8.5 doesn't define this constant, but has it on by default
133
+ if defined?(OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS)
134
+ DEFAULT_PARAMS[:options] |= OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS
135
+ end
136
+
137
+ if DEFAULT_PARAMS[:ciphers]
138
+ DEFAULT_PARAMS[:ciphers] << ':!SSLv2'
139
+ end
140
+
141
+ alias __mcollective_original_initialize initialize
142
+ private :__mcollective_original_initialize
143
+
144
+ def initialize(*args)
145
+ __mcollective_original_initialize(*args)
146
+ params = {
147
+ :options => DEFAULT_PARAMS[:options],
148
+ :ciphers => DEFAULT_PARAMS[:ciphers],
149
+ }
150
+ set_params(params)
151
+ end
152
+ end
@@ -0,0 +1,197 @@
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 configuration 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
+
173
+ @parser.on("--publish_timeout TIMEOUT", Integer, "Timeout for publishing requests to remote agents.") do |pt|
174
+ @options[:publish_timeout] = pt
175
+ end
176
+
177
+ @parser.on("--threaded", "Start publishing requests and receiving responses in threaded mode.") do |v|
178
+ @options[:threaded] = true
179
+ end
180
+
181
+ @parser.on("--sort", "Sort the output of an RPC call before processing.") do |v|
182
+ @options[:sort] = true
183
+ end
184
+
185
+ @parser.on("--connection-timeout TIMEOUT", Integer, "Set the timeout for establishing a connection to the middleware") do |v|
186
+ @options[:connection_timeout] = Integer(v)
187
+ end
188
+ end
189
+
190
+ private
191
+ # Parse a fact filter string like foo=bar into the tuple hash thats needed
192
+ def parse_fact(fact)
193
+ Util.parse_fact_string(fact)
194
+ end
195
+
196
+ end
197
+ end