choria-mcorpc-support 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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