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.
- checksums.yaml +7 -0
- data/bin/mco +64 -0
- data/lib/mcollective.rb +63 -0
- data/lib/mcollective/agent.rb +5 -0
- data/lib/mcollective/agents.rb +149 -0
- data/lib/mcollective/aggregate.rb +85 -0
- data/lib/mcollective/aggregate/average.ddl +33 -0
- data/lib/mcollective/aggregate/average.rb +29 -0
- data/lib/mcollective/aggregate/base.rb +40 -0
- data/lib/mcollective/aggregate/result.rb +9 -0
- data/lib/mcollective/aggregate/result/base.rb +25 -0
- data/lib/mcollective/aggregate/result/collection_result.rb +19 -0
- data/lib/mcollective/aggregate/result/numeric_result.rb +13 -0
- data/lib/mcollective/aggregate/sum.ddl +33 -0
- data/lib/mcollective/aggregate/sum.rb +18 -0
- data/lib/mcollective/aggregate/summary.ddl +33 -0
- data/lib/mcollective/aggregate/summary.rb +53 -0
- data/lib/mcollective/application.rb +365 -0
- data/lib/mcollective/application/completion.rb +104 -0
- data/lib/mcollective/application/describe_filter.rb +87 -0
- data/lib/mcollective/application/facts.rb +62 -0
- data/lib/mcollective/application/find.rb +23 -0
- data/lib/mcollective/application/help.rb +28 -0
- data/lib/mcollective/application/inventory.rb +344 -0
- data/lib/mcollective/application/ping.rb +82 -0
- data/lib/mcollective/application/plugin.rb +369 -0
- data/lib/mcollective/application/rpc.rb +111 -0
- data/lib/mcollective/applications.rb +134 -0
- data/lib/mcollective/cache.rb +145 -0
- data/lib/mcollective/client.rb +353 -0
- data/lib/mcollective/config.rb +245 -0
- data/lib/mcollective/connector.rb +18 -0
- data/lib/mcollective/connector/base.rb +26 -0
- data/lib/mcollective/data.rb +91 -0
- data/lib/mcollective/data/agent_data.ddl +22 -0
- data/lib/mcollective/data/agent_data.rb +17 -0
- data/lib/mcollective/data/base.rb +67 -0
- data/lib/mcollective/data/collective_data.ddl +20 -0
- data/lib/mcollective/data/collective_data.rb +9 -0
- data/lib/mcollective/data/fact_data.ddl +28 -0
- data/lib/mcollective/data/fact_data.rb +55 -0
- data/lib/mcollective/data/fstat_data.ddl +89 -0
- data/lib/mcollective/data/fstat_data.rb +56 -0
- data/lib/mcollective/data/result.rb +45 -0
- data/lib/mcollective/ddl.rb +113 -0
- data/lib/mcollective/ddl/agentddl.rb +253 -0
- data/lib/mcollective/ddl/base.rb +217 -0
- data/lib/mcollective/ddl/dataddl.rb +56 -0
- data/lib/mcollective/ddl/discoveryddl.rb +52 -0
- data/lib/mcollective/ddl/validatorddl.rb +6 -0
- data/lib/mcollective/discovery.rb +143 -0
- data/lib/mcollective/discovery/flatfile.ddl +11 -0
- data/lib/mcollective/discovery/flatfile.rb +48 -0
- data/lib/mcollective/discovery/mc.ddl +11 -0
- data/lib/mcollective/discovery/mc.rb +30 -0
- data/lib/mcollective/discovery/stdin.ddl +11 -0
- data/lib/mcollective/discovery/stdin.rb +68 -0
- data/lib/mcollective/exceptions.rb +28 -0
- data/lib/mcollective/facts.rb +39 -0
- data/lib/mcollective/facts/base.rb +100 -0
- data/lib/mcollective/facts/yaml_facts.rb +65 -0
- data/lib/mcollective/generators.rb +7 -0
- data/lib/mcollective/generators/agent_generator.rb +51 -0
- data/lib/mcollective/generators/base.rb +46 -0
- data/lib/mcollective/generators/data_generator.rb +51 -0
- data/lib/mcollective/generators/templates/action_snippet.erb +13 -0
- data/lib/mcollective/generators/templates/data_input_snippet.erb +7 -0
- data/lib/mcollective/generators/templates/ddl.erb +8 -0
- data/lib/mcollective/generators/templates/plugin.erb +7 -0
- data/lib/mcollective/log.rb +118 -0
- data/lib/mcollective/logger.rb +5 -0
- data/lib/mcollective/logger/base.rb +77 -0
- data/lib/mcollective/logger/console_logger.rb +61 -0
- data/lib/mcollective/logger/file_logger.rb +53 -0
- data/lib/mcollective/logger/syslog_logger.rb +53 -0
- data/lib/mcollective/matcher.rb +224 -0
- data/lib/mcollective/matcher/parser.rb +128 -0
- data/lib/mcollective/matcher/scanner.rb +241 -0
- data/lib/mcollective/message.rb +248 -0
- data/lib/mcollective/monkey_patches.rb +152 -0
- data/lib/mcollective/optionparser.rb +197 -0
- data/lib/mcollective/pluginmanager.rb +180 -0
- data/lib/mcollective/pluginpackager.rb +98 -0
- data/lib/mcollective/pluginpackager/agent_definition.rb +94 -0
- data/lib/mcollective/pluginpackager/debpackage_packager.rb +237 -0
- data/lib/mcollective/pluginpackager/modulepackage_packager.rb +127 -0
- data/lib/mcollective/pluginpackager/ospackage_packager.rb +59 -0
- data/lib/mcollective/pluginpackager/rpmpackage_packager.rb +180 -0
- data/lib/mcollective/pluginpackager/standard_definition.rb +69 -0
- data/lib/mcollective/pluginpackager/templates/debian/Makefile.erb +7 -0
- data/lib/mcollective/pluginpackager/templates/debian/changelog.erb +5 -0
- data/lib/mcollective/pluginpackager/templates/debian/compat.erb +1 -0
- data/lib/mcollective/pluginpackager/templates/debian/control.erb +15 -0
- data/lib/mcollective/pluginpackager/templates/debian/copyright.erb +8 -0
- data/lib/mcollective/pluginpackager/templates/debian/rules.erb +6 -0
- data/lib/mcollective/pluginpackager/templates/module/Modulefile.erb +5 -0
- data/lib/mcollective/pluginpackager/templates/module/README.md.erb +37 -0
- data/lib/mcollective/pluginpackager/templates/module/_manifest.pp.erb +9 -0
- data/lib/mcollective/pluginpackager/templates/redhat/rpm_spec.erb +63 -0
- data/lib/mcollective/registration/base.rb +91 -0
- data/lib/mcollective/rpc.rb +182 -0
- data/lib/mcollective/rpc/actionrunner.rb +158 -0
- data/lib/mcollective/rpc/agent.rb +374 -0
- data/lib/mcollective/rpc/audit.rb +38 -0
- data/lib/mcollective/rpc/client.rb +1066 -0
- data/lib/mcollective/rpc/helpers.rb +321 -0
- data/lib/mcollective/rpc/progress.rb +63 -0
- data/lib/mcollective/rpc/reply.rb +87 -0
- data/lib/mcollective/rpc/request.rb +86 -0
- data/lib/mcollective/rpc/result.rb +90 -0
- data/lib/mcollective/rpc/stats.rb +294 -0
- data/lib/mcollective/runnerstats.rb +90 -0
- data/lib/mcollective/security.rb +26 -0
- data/lib/mcollective/security/base.rb +244 -0
- data/lib/mcollective/shell.rb +126 -0
- data/lib/mcollective/ssl.rb +285 -0
- data/lib/mcollective/util.rb +579 -0
- data/lib/mcollective/validator.rb +85 -0
- data/lib/mcollective/validator/array_validator.ddl +7 -0
- data/lib/mcollective/validator/array_validator.rb +9 -0
- data/lib/mcollective/validator/ipv4address_validator.ddl +7 -0
- data/lib/mcollective/validator/ipv4address_validator.rb +16 -0
- data/lib/mcollective/validator/ipv6address_validator.ddl +7 -0
- data/lib/mcollective/validator/ipv6address_validator.rb +16 -0
- data/lib/mcollective/validator/length_validator.ddl +7 -0
- data/lib/mcollective/validator/length_validator.rb +11 -0
- data/lib/mcollective/validator/regex_validator.ddl +7 -0
- data/lib/mcollective/validator/regex_validator.rb +9 -0
- data/lib/mcollective/validator/shellsafe_validator.ddl +7 -0
- data/lib/mcollective/validator/shellsafe_validator.rb +13 -0
- data/lib/mcollective/validator/typecheck_validator.ddl +7 -0
- data/lib/mcollective/validator/typecheck_validator.rb +28 -0
- 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
|