mcollective-client 2.0.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of mcollective-client might be problematic. Click here for more details.

Files changed (140) hide show
  1. data/lib/mcollective.rb +32 -23
  2. data/lib/mcollective/agent.rb +5 -0
  3. data/lib/mcollective/agents.rb +5 -16
  4. data/lib/mcollective/aggregate.rb +61 -0
  5. data/lib/mcollective/aggregate/base.rb +40 -0
  6. data/lib/mcollective/aggregate/result.rb +9 -0
  7. data/lib/mcollective/aggregate/result/base.rb +25 -0
  8. data/lib/mcollective/aggregate/result/collection_result.rb +19 -0
  9. data/lib/mcollective/aggregate/result/numeric_result.rb +13 -0
  10. data/lib/mcollective/application.rb +7 -4
  11. data/lib/mcollective/applications.rb +3 -14
  12. data/lib/mcollective/cache.rb +145 -0
  13. data/lib/mcollective/client.rb +10 -87
  14. data/lib/mcollective/config.rb +22 -8
  15. data/lib/mcollective/data.rb +87 -0
  16. data/lib/mcollective/data/base.rb +67 -0
  17. data/lib/mcollective/data/result.rb +40 -0
  18. data/lib/mcollective/ddl.rb +113 -0
  19. data/lib/mcollective/ddl/agentddl.rb +185 -0
  20. data/lib/mcollective/ddl/base.rb +220 -0
  21. data/lib/mcollective/ddl/dataddl.rb +56 -0
  22. data/lib/mcollective/ddl/discoveryddl.rb +52 -0
  23. data/lib/mcollective/ddl/validatorddl.rb +6 -0
  24. data/lib/mcollective/discovery.rb +143 -0
  25. data/lib/mcollective/generators.rb +7 -0
  26. data/lib/mcollective/generators/agent_generator.rb +51 -0
  27. data/lib/mcollective/generators/base.rb +46 -0
  28. data/lib/mcollective/generators/data_generator.rb +51 -0
  29. data/lib/mcollective/generators/templates/action_snippet.erb +13 -0
  30. data/lib/mcollective/generators/templates/data_input_snippet.erb +7 -0
  31. data/lib/mcollective/generators/templates/ddl.erb +8 -0
  32. data/lib/mcollective/generators/templates/plugin.erb +7 -0
  33. data/lib/mcollective/logger/console_logger.rb +15 -15
  34. data/lib/mcollective/matcher.rb +167 -0
  35. data/lib/mcollective/matcher/parser.rb +60 -25
  36. data/lib/mcollective/matcher/scanner.rb +156 -78
  37. data/lib/mcollective/message.rb +47 -6
  38. data/lib/mcollective/monkey_patches.rb +17 -0
  39. data/lib/mcollective/optionparser.rb +18 -1
  40. data/lib/mcollective/pluginmanager.rb +3 -3
  41. data/lib/mcollective/pluginpackager.rb +10 -3
  42. data/lib/mcollective/pluginpackager/agent_definition.rb +28 -20
  43. data/lib/mcollective/pluginpackager/standard_definition.rb +11 -9
  44. data/lib/mcollective/registration/base.rb +3 -1
  45. data/lib/mcollective/rpc.rb +18 -24
  46. data/lib/mcollective/rpc/agent.rb +37 -113
  47. data/lib/mcollective/rpc/client.rb +186 -64
  48. data/lib/mcollective/rpc/helpers.rb +42 -80
  49. data/lib/mcollective/rpc/progress.rb +3 -3
  50. data/lib/mcollective/rpc/reply.rb +37 -13
  51. data/lib/mcollective/rpc/request.rb +17 -6
  52. data/lib/mcollective/rpc/result.rb +9 -5
  53. data/lib/mcollective/rpc/stats.rb +71 -24
  54. data/lib/mcollective/security/base.rb +41 -34
  55. data/lib/mcollective/shell.rb +1 -1
  56. data/lib/mcollective/ssl.rb +34 -0
  57. data/lib/mcollective/util.rb +194 -23
  58. data/lib/mcollective/validator.rb +80 -0
  59. data/spec/fixtures/util/1.in +10 -0
  60. data/spec/fixtures/util/1.out +10 -0
  61. data/spec/fixtures/util/2.in +1 -0
  62. data/spec/fixtures/util/2.out +1 -0
  63. data/spec/fixtures/util/3.in +1 -0
  64. data/spec/fixtures/util/3.out +2 -0
  65. data/spec/fixtures/util/4.in +5 -0
  66. data/spec/fixtures/util/4.out +9 -0
  67. data/spec/spec.opts +1 -1
  68. data/spec/spec_helper.rb +2 -0
  69. data/spec/unit/agents_spec.rb +34 -19
  70. data/spec/unit/aggregate/base_spec.rb +57 -0
  71. data/spec/unit/aggregate/result/base_spec.rb +28 -0
  72. data/spec/unit/aggregate/result/collection_result_spec.rb +18 -0
  73. data/spec/unit/aggregate/result/numeric_result_spec.rb +22 -0
  74. data/spec/unit/aggregate_spec.rb +110 -0
  75. data/spec/unit/application_spec.rb +8 -3
  76. data/spec/unit/applications_spec.rb +2 -2
  77. data/spec/unit/cache_spec.rb +115 -0
  78. data/spec/unit/client_spec.rb +78 -0
  79. data/spec/unit/config_spec.rb +32 -34
  80. data/spec/unit/data/base_spec.rb +90 -0
  81. data/spec/unit/data/result_spec.rb +64 -0
  82. data/spec/unit/data_spec.rb +158 -0
  83. data/spec/unit/ddl/agentddl_spec.rb +217 -0
  84. data/spec/unit/{rpc/ddl_spec.rb → ddl/base_spec.rb} +238 -224
  85. data/spec/unit/ddl/dataddl_spec.rb +65 -0
  86. data/spec/unit/ddl/discoveryddl_spec.rb +58 -0
  87. data/spec/unit/ddl_spec.rb +84 -0
  88. data/spec/unit/discovery_spec.rb +196 -0
  89. data/spec/unit/facts/base_spec.rb +1 -1
  90. data/spec/unit/generators/agent_generator_spec.rb +72 -0
  91. data/spec/unit/generators/base_spec.rb +83 -0
  92. data/spec/unit/generators/data_generator_spec.rb +37 -0
  93. data/spec/unit/generators/snippets/agent_ddl +19 -0
  94. data/spec/unit/generators/snippets/data_ddl +20 -0
  95. data/spec/unit/logger/console_logger_spec.rb +76 -0
  96. data/spec/unit/logger/syslog_logger_spec.rb +2 -2
  97. data/spec/unit/matcher/parser_spec.rb +27 -10
  98. data/spec/unit/matcher/scanner_spec.rb +108 -5
  99. data/spec/unit/matcher_spec.rb +260 -0
  100. data/spec/unit/message_spec.rb +35 -13
  101. data/spec/unit/optionparser_spec.rb +2 -2
  102. data/spec/unit/pluginpackager/agent_definition_spec.rb +59 -42
  103. data/spec/unit/pluginpackager/standard_definition_spec.rb +10 -8
  104. data/spec/unit/pluginpackager_spec.rb +131 -0
  105. data/spec/unit/plugins/mcollective/aggregate/average_spec.rb +45 -0
  106. data/spec/unit/plugins/mcollective/aggregate/sum_spec.rb +31 -0
  107. data/spec/unit/plugins/mcollective/aggregate/summary_spec.rb +45 -0
  108. data/spec/unit/plugins/mcollective/connector/activemq_spec.rb +1 -1
  109. data/spec/unit/plugins/mcollective/connector/rabbitmq_spec.rb +478 -0
  110. data/spec/unit/plugins/mcollective/connector/stomp_spec.rb +2 -0
  111. data/spec/unit/plugins/mcollective/data/agent_data_spec.rb +43 -0
  112. data/spec/unit/plugins/mcollective/data/fstat_data_spec.rb +135 -0
  113. data/spec/unit/plugins/mcollective/discovery/flatfile_spec.rb +48 -0
  114. data/spec/unit/plugins/mcollective/discovery/mc_spec.rb +40 -0
  115. data/spec/unit/plugins/mcollective/packagers/debpackage_packager_spec.rb +41 -15
  116. data/spec/unit/plugins/mcollective/packagers/ospackage_spec.rb +1 -1
  117. data/spec/unit/plugins/mcollective/packagers/rpmpackage_packager_spec.rb +22 -38
  118. data/spec/unit/plugins/mcollective/validator/array_validator_spec.rb +19 -0
  119. data/spec/unit/plugins/mcollective/validator/ipv4address_validator_spec.rb +19 -0
  120. data/spec/unit/plugins/mcollective/validator/ipv6address_validator_spec.rb +19 -0
  121. data/spec/unit/plugins/mcollective/validator/length_validator_spec.rb +19 -0
  122. data/spec/unit/plugins/mcollective/validator/regex_validator_spec.rb +19 -0
  123. data/spec/unit/plugins/mcollective/validator/shellsafe_validator_spec.rb +21 -0
  124. data/spec/unit/plugins/mcollective/validator/typecheck_validator_spec.rb +23 -0
  125. data/spec/unit/registration/base_spec.rb +1 -1
  126. data/spec/unit/rpc/actionrunner_spec.rb +2 -2
  127. data/spec/unit/rpc/agent_spec.rb +41 -65
  128. data/spec/unit/rpc/client_spec.rb +430 -134
  129. data/spec/unit/rpc/reply_spec.rb +31 -1
  130. data/spec/unit/rpc/request_spec.rb +33 -12
  131. data/spec/unit/rpc/result_spec.rb +7 -0
  132. data/spec/unit/rpc/stats_spec.rb +14 -14
  133. data/spec/unit/rpc_spec.rb +16 -0
  134. data/spec/unit/security/base_spec.rb +8 -8
  135. data/spec/unit/ssl_spec.rb +20 -2
  136. data/spec/unit/string_spec.rb +15 -0
  137. data/spec/unit/util_spec.rb +141 -21
  138. data/spec/unit/validator_spec.rb +67 -0
  139. metadata +145 -7
  140. data/lib/mcollective/rpc/ddl.rb +0 -258
@@ -1,7 +1,7 @@
1
1
  module MCollective
2
2
  # Helpers for writing clients that can talk to agents, do discovery and so forth
3
3
  class Client
4
- attr_accessor :options, :stats
4
+ attr_accessor :options, :stats, :discoverer
5
5
 
6
6
  def initialize(configfile)
7
7
  @config = Config.instance
@@ -14,6 +14,7 @@ module MCollective
14
14
  @options = nil
15
15
  @subscriptions = {}
16
16
 
17
+ @discoverer = Discovery.new(self)
17
18
  @connection.connect
18
19
  end
19
20
 
@@ -49,7 +50,7 @@ module MCollective
49
50
 
50
51
  Log.debug("Sending request #{request.requestid} to the #{request.agent} agent with ttl #{request.ttl} in collective #{request.collective}")
51
52
 
52
- subscribe(agent, :reply)
53
+ subscribe(agent, :reply) unless request.reply_to
53
54
 
54
55
  request.publish
55
56
 
@@ -112,30 +113,7 @@ module MCollective
112
113
  # of the discovery being cancelled soon as it reached the
113
114
  # requested limit of hosts
114
115
  def discover(filter, timeout, limit=0)
115
- raise "Limit has to be an integer" unless limit.is_a?(Fixnum)
116
-
117
- begin
118
- hosts = []
119
- Timeout.timeout(timeout) do
120
- reqid = sendreq("ping", "discovery", filter)
121
- Log.debug("Waiting #{timeout} seconds for discovery replies to request #{reqid}")
122
-
123
- loop do
124
- reply = receive(reqid)
125
- Log.debug("Got discovery reply from #{reply.payload[:senderid]}")
126
- hosts << reply.payload[:senderid]
127
-
128
- return hosts if limit > 0 && hosts.size == limit
129
- end
130
- end
131
- rescue Timeout::Error => e
132
- rescue Exception => e
133
- raise
134
- ensure
135
- unsubscribe("discovery", :reply)
136
- end
137
-
138
- hosts.sort
116
+ discovered = @discoverer.discover(filter, timeout, limit)
139
117
  end
140
118
 
141
119
  # Send a request, performs the passed block for each response
@@ -155,15 +133,16 @@ module MCollective
155
133
 
156
134
  stat = {:starttime => Time.now.to_f, :discoverytime => 0, :blocktime => 0, :totaltime => 0}
157
135
 
158
- options = @options unless options
136
+ timeout = @discoverer.discovery_timeout(@options[:timeout], @options[:filter])
159
137
 
160
138
  STDOUT.sync = true
161
139
 
162
140
  hosts_responded = 0
141
+ reqid = nil
163
142
 
164
143
  begin
165
- Timeout.timeout(options[:timeout]) do
166
- reqid = sendreq(body, agent, options[:filter])
144
+ Timeout.timeout(timeout) do
145
+ reqid = sendreq(body, agent, @options[:filter])
167
146
 
168
147
  loop do
169
148
  resp = receive(reqid)
@@ -185,70 +164,14 @@ module MCollective
185
164
  stat[:blocktime] = stat[:totaltime] - stat[:discoverytime]
186
165
  stat[:responses] = hosts_responded
187
166
  stat[:noresponsefrom] = []
167
+ stat[:requestid] = reqid
188
168
 
189
169
  @stats = stat
190
170
  return stat
191
171
  end
192
172
 
193
- # Performs a discovery and then send a request, performs the passed block for each response
194
- #
195
- # times = discovered_req("status", "mcollectived", options, client) {|resp|
196
- # pp resp
197
- # }
198
- #
199
- # It returns a hash of times and timeouts for discovery and total run is taken from the options
200
- # hash which in turn is generally built using MCollective::Optionparser
201
173
  def discovered_req(body, agent, options=false)
202
- stat = {:starttime => Time.now.to_f, :discoverytime => 0, :blocktime => 0, :totaltime => 0}
203
-
204
- options = @options unless options
205
-
206
- STDOUT.sync = true
207
-
208
- print("Determining the amount of hosts matching filter for #{options[:disctimeout]} seconds .... ")
209
-
210
- begin
211
- discovered_hosts = discover(options[:filter], options[:disctimeout])
212
- discovered = discovered_hosts.size
213
- hosts_responded = []
214
- hosts_not_responded = discovered_hosts
215
-
216
- stat[:discoverytime] = Time.now.to_f - stat[:starttime]
217
-
218
- puts("#{discovered}\n\n")
219
- rescue Interrupt
220
- puts("Discovery interrupted.")
221
- exit!
222
- end
223
-
224
- raise("No matching clients found") if discovered == 0
225
-
226
- begin
227
- Timeout.timeout(options[:timeout]) do
228
- reqid = sendreq(body, agent, options[:filter])
229
-
230
- (1..discovered).each do |c|
231
- resp = receive(reqid)
232
-
233
- hosts_responded << resp.payload[:senderid]
234
- hosts_not_responded.delete(resp.payload[:senderid]) if hosts_not_responded.include?(resp.payload[:senderid])
235
-
236
- yield(resp.payload)
237
- end
238
- end
239
- rescue Interrupt => e
240
- rescue Timeout::Error => e
241
- end
242
-
243
- stat[:totaltime] = Time.now.to_f - stat[:starttime]
244
- stat[:blocktime] = stat[:totaltime] - stat[:discoverytime]
245
- stat[:responses] = hosts_responded.size
246
- stat[:responsesfrom] = hosts_responded
247
- stat[:noresponsefrom] = hosts_not_responded
248
- stat[:discovered] = discovered
249
-
250
- @stats = stat
251
- return stat
174
+ raise "Client#discovered_req has been removed, please port your agent and client to the SimpleRPC framework"
252
175
  end
253
176
 
254
177
  # Prints out the stats returns from req and discovered_req in a nice way
@@ -3,14 +3,18 @@ module MCollective
3
3
  class Config
4
4
  include Singleton
5
5
 
6
- attr_reader :topicprefix, :daemonize, :pluginconf, :libdir, :configured,
7
- :logfile, :keeplogs, :max_log_size, :loglevel, :logfacility, :identity,
8
- :daemonize, :connector, :securityprovider, :factsource, :registration,
9
- :registerinterval, :topicsep, :classesfile, :rpcauditprovider, :rpcaudit,
10
- :configdir, :rpcauthprovider, :rpcauthorization, :color, :configfile,
11
- :rpchelptemplate, :rpclimitmethod, :logger_type, :fact_cache_time,
12
- :collectives, :main_collective, :ssl_cipher, :registration_collective,
13
- :direct_addressing, :direct_addressing_threshold, :queueprefix, :ttl
6
+ attr_accessor :mode
7
+
8
+ attr_reader :topicprefix, :daemonize, :pluginconf, :libdir, :configured
9
+ attr_reader :logfile, :keeplogs, :max_log_size, :loglevel, :logfacility
10
+ attr_reader :identity, :daemonize, :connector, :securityprovider, :factsource
11
+ attr_reader :registration, :registerinterval, :topicsep, :classesfile
12
+ attr_reader :rpcauditprovider, :rpcaudit, :configdir, :rpcauthprovider
13
+ attr_reader :rpcauthorization, :color, :configfile, :rpchelptemplate
14
+ attr_reader :rpclimitmethod, :logger_type, :fact_cache_time, :collectives
15
+ attr_reader :main_collective, :ssl_cipher, :registration_collective
16
+ attr_reader :direct_addressing, :direct_addressing_threshold, :ttl, :helptemplatedir
17
+ attr_reader :queueprefix, :default_discovery_method, :default_discovery_options
14
18
 
15
19
  def initialize
16
20
  @configured = false
@@ -105,6 +109,12 @@ module MCollective
105
109
  @ssl_cipher = val
106
110
  when "ttl"
107
111
  @ttl = val.to_i
112
+ when "helptemplatedir"
113
+ @helptemplatedir = val
114
+ when "default_discovery_options"
115
+ @default_discovery_options << val
116
+ when "default_discovery_method"
117
+ @default_discovery_method = val
108
118
  else
109
119
  raise("Unknown config parameter #{key}")
110
120
  end
@@ -170,13 +180,17 @@ module MCollective
170
180
  @ssl_cipher = "aes-256-cbc"
171
181
  @direct_addressing = false
172
182
  @direct_addressing_threshold = 10
183
+ @default_discovery_method = "mc"
184
+ @default_discovery_options = []
173
185
  @ttl = 60
186
+ @mode = :client
174
187
 
175
188
  # look in the config dir for the template so users can provide their own and windows
176
189
  # with odd paths will just work more often, but fall back to old behavior if it does
177
190
  # not exist
178
191
  @rpchelptemplate = File.join(File.dirname(configfile), "rpc-help.erb")
179
192
  @rpchelptemplate = "/etc/mcollective/rpc-help.erb" unless File.exists?(@rpchelptemplate)
193
+ @helptemplatedir = File.dirname(@rpchelptemplate)
180
194
  end
181
195
 
182
196
  def read_plugin_config_dir(dir)
@@ -0,0 +1,87 @@
1
+ module MCollective
2
+ module Data
3
+ autoload :Base, "mcollective/data/base"
4
+ autoload :Result, "mcollective/data/result"
5
+
6
+ def self.load_data_sources
7
+ PluginManager.find_and_load("data")
8
+
9
+ PluginManager.grep(/_data$/).each do |plugin|
10
+ begin
11
+ unless PluginManager[plugin].class.activate?
12
+ Log.debug("Disabling data plugin %s due to plugin activation policy" % plugin)
13
+ PluginManager.delete(plugin)
14
+ end
15
+ rescue Exception => e
16
+ Log.debug("Disabling data plugin %s due to exception #{e.class}: #{e}" % plugin)
17
+ PluginManager.delete(plugin)
18
+ end
19
+ end
20
+ end
21
+
22
+ def self.pluginname(plugin)
23
+ plugin.to_s =~ /_data$/i ? plugin.to_s.downcase : "%s_data" % plugin.to_s.downcase
24
+ end
25
+
26
+ def self.[](plugin)
27
+ PluginManager[pluginname(plugin)]
28
+ end
29
+
30
+ # Data.package("httpd").architecture
31
+ def self.method_missing(method, *args)
32
+ super unless PluginManager.include?(pluginname(method))
33
+
34
+ PluginManager[pluginname(method)].lookup(args.first)
35
+ end
36
+
37
+ def self.ddl(plugin)
38
+ DDL.new(pluginname(plugin), :data)
39
+ end
40
+
41
+ def self.ddl_validate(ddl, argument)
42
+ name = ddl.meta[:name]
43
+ query = ddl.entities[:data]
44
+
45
+ raise DDLValidationError, "No dataquery has been defined in the DDL for data plugin #{name}" unless query
46
+
47
+ input = query[:input]
48
+ output = query[:output]
49
+
50
+ raise DDLValidationError, "No :query input has been defined in the DDL for data plugin #{name}" unless input[:query]
51
+ raise DDLValidationError, "No output has been defined in the DDL for data plugin #{name}" if output.keys.empty?
52
+
53
+ return true if argument.nil? && input[:query][:optional]
54
+
55
+ ddl.validate_input_argument(input, :query, argument)
56
+ end
57
+
58
+ def self.ddl_has_output?(ddl, output)
59
+ ddl.entities[:data][:output].include?(output.to_sym) rescue false
60
+ end
61
+
62
+ # For an input where the DDL requests a boolean or some number
63
+ # this will convert the input to the right type where possible
64
+ # else just returns the origin input unedited
65
+ #
66
+ # if anything here goes wrong just return the input value
67
+ # this is not really the end of the world or anything since
68
+ # all that will happen is that DDL validation will fail and
69
+ # the user will get an error, no need to be too defensive here
70
+ def self.ddl_transform_input(ddl, input)
71
+ begin
72
+ type = ddl.entities[:data][:input][:query][:type]
73
+
74
+ case type
75
+ when :boolean
76
+ return DDL.string_to_boolean(input)
77
+
78
+ when :number, :integer, :float
79
+ return DDL.string_to_number(input)
80
+ end
81
+ rescue
82
+ end
83
+
84
+ return input
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,67 @@
1
+ module MCollective
2
+ module Data
3
+ class Base
4
+ attr_reader :name, :result, :ddl, :timeout
5
+
6
+ # Register plugins that inherits base
7
+ def self.inherited(klass)
8
+ type = klass.to_s.split("::").last.downcase
9
+
10
+ PluginManager << {:type => type, :class => klass.to_s, :single_instance => false}
11
+ end
12
+
13
+ def initialize
14
+ @name = self.class.to_s.split("::").last.downcase
15
+ @ddl = DDL.new(@name, :data)
16
+ @result = Result.new
17
+ @timeout = @ddl.meta[:timeout] || 1
18
+
19
+ startup_hook
20
+ end
21
+
22
+ def lookup(what)
23
+ ddl_validate(what)
24
+
25
+ Log.debug("Doing data query %s for '%s'" % [ @name, what ])
26
+
27
+ Timeout::timeout(@timeout) do
28
+ query_data(what)
29
+ end
30
+
31
+ @result
32
+ rescue Timeout::Error
33
+ # Timeout::Error is a inherited from Interrupt which seems a really
34
+ # strange choice, making it an equivelant of ^C and such. Catch it
35
+ # and raise something less critical that will not the runner to just
36
+ # give up the ghost
37
+ msg = "Data plugin %s timed out on query '%s'" % [@name, what]
38
+ Log.error(msg)
39
+ raise MsgTTLExpired, msg
40
+ end
41
+
42
+ def self.query(&block)
43
+ self.module_eval { define_method("query_data", &block) }
44
+ end
45
+
46
+ def ddl_validate(what)
47
+ Data.ddl_validate(@ddl, what)
48
+ end
49
+
50
+ # activate_when do
51
+ # file.exist?("/usr/bin/puppet")
52
+ # end
53
+ def self.activate_when(&block)
54
+ (class << self; self; end).instance_eval do
55
+ define_method("activate?", &block)
56
+ end
57
+ end
58
+
59
+ # Always be active unless a specific block is given with activate_when
60
+ def self.activate?
61
+ return true
62
+ end
63
+
64
+ def startup_hook;end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,40 @@
1
+ module MCollective
2
+ module Data
3
+ class Result
4
+ # remove some methods that might clash with commonly
5
+ # used return data to improve the effectiveness of the
6
+ # method_missing lookup strategy
7
+ undef :type if method_defined?(:type)
8
+
9
+ def initialize
10
+ @data = {}
11
+ end
12
+
13
+ def include?(key)
14
+ @data.include?(key.to_sym)
15
+ end
16
+
17
+ def [](key)
18
+ @data[key.to_sym]
19
+ end
20
+
21
+ def []=(key, val)
22
+ raise "Can only store String, Integer, Float or Boolean data but got #{val.class} for key #{key}" unless [String, Fixnum, Float, TrueClass, FalseClass].include?(val.class)
23
+
24
+ @data[key.to_sym] = val
25
+ end
26
+
27
+ def keys
28
+ @data.keys
29
+ end
30
+
31
+ def method_missing(method, *args)
32
+ key = method.to_sym
33
+
34
+ raise NameError, "undefined local variable or method `%s'" % key unless include?(key)
35
+
36
+ @data[key]
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,113 @@
1
+ module MCollective
2
+ # A set of classes that helps create data description language files
3
+ # for plugins. You can define meta data, actions, input and output
4
+ # describing the behavior of your agent or other plugins
5
+ #
6
+ # DDL files are used for input validation, constructing outputs,
7
+ # producing online help, informing the various display routines and
8
+ # so forth.
9
+ #
10
+ # A sample DDL for an agent be seen below, you'd put this in your agent
11
+ # dir as <agent name>.ddl
12
+ #
13
+ # metadata :name => "SimpleRPC Service Agent",
14
+ # :description => "Agent to manage services using the Puppet service provider",
15
+ # :author => "R.I.Pienaar",
16
+ # :license => "GPLv2",
17
+ # :version => "1.1",
18
+ # :url => "http://mcollective-plugins.googlecode.com/",
19
+ # :timeout => 60
20
+ #
21
+ # action "status", :description => "Gets the status of a service" do
22
+ # display :always
23
+ #
24
+ # input :service,
25
+ # :prompt => "Service Name",
26
+ # :description => "The service to get the status for",
27
+ # :type => :string,
28
+ # :validation => '^[a-zA-Z\-_\d]+$',
29
+ # :optional => true,
30
+ # :maxlength => 30
31
+ #
32
+ # output :status,
33
+ # :description => "The status of service",
34
+ # :display_as => "Service Status"
35
+ # end
36
+ #
37
+ # There are now many types of DDL and ultimately all pugins should have
38
+ # DDL files. The code is organized so that any plugin type will magically
39
+ # just work - they will be an instane of Base which has #metadata and a few
40
+ # common cases.
41
+ #
42
+ # For plugin types that require more specific behaviors they can just add a
43
+ # class here that inherits from Base and add their specific behavior.
44
+ #
45
+ # Base defines a specific behavior for input, output and metadata which we'd
46
+ # like to keep standard across plugin types so do not completely override the
47
+ # behavior of input. The methods are written that they will gladly store extra
48
+ # content though so you add, do not remove. See the AgentDDL class for an example
49
+ # where agents want a :required argument to be always set.
50
+ module DDL
51
+ autoload :Base, "mcollective/ddl/base"
52
+ autoload :AgentDDL, "mcollective/ddl/agentddl"
53
+ autoload :DataDDL, "mcollective/ddl/dataddl"
54
+ autoload :DiscoveryDDL, "mcollective/ddl/discoveryddl"
55
+
56
+ # There used to be only one big nasty DDL class with a bunch of mashed
57
+ # together behaviors. It's been around for ages and we would rather not
58
+ # ask all the users to change their DDL.new calls to some other factory
59
+ # method that would have this exact same behavior.
60
+ #
61
+ # So we override the behavior of #new which is a hugely sucky thing to do
62
+ # but ultimately it's what would be least disrupting to code out there
63
+ # today. We did though change DDL to a module to make it possibly a
64
+ # little less suprising, possibly.
65
+ def self.new(*args, &blk)
66
+ load_and_cache(*args)
67
+ end
68
+
69
+ def self.load_and_cache(*args)
70
+ Cache.setup(:ddl, 300)
71
+
72
+ plugin = args.first
73
+ args.size > 1 ? type = args[1].to_s : type = "agent"
74
+ path = "%s/%s" % [type, plugin]
75
+
76
+ begin
77
+ ddl = Cache.read(:ddl, path)
78
+ rescue
79
+ begin
80
+ klass = DDL.const_get("%sDDL" % type.capitalize)
81
+ rescue NameError
82
+ klass = Base
83
+ end
84
+
85
+ ddl = Cache.write(:ddl, path, klass.new(*args))
86
+ end
87
+
88
+ return ddl
89
+ end
90
+
91
+ # As we're taking arguments on the command line we need a
92
+ # way to input booleans, true on the cli is a string so this
93
+ # method will take the ddl, find all arguments that are supposed
94
+ # to be boolean and if they are the strings "true"/"yes" or "false"/"no"
95
+ # turn them into the matching boolean
96
+ def self.string_to_boolean(val)
97
+ return true if ["true", "t", "yes", "y", "1"].include?(val.downcase)
98
+ return false if ["false", "f", "no", "n", "0"].include?(val.downcase)
99
+
100
+ raise "#{val} does not look like a boolean argument"
101
+ end
102
+
103
+ # a generic string to number function, if a number looks like a float
104
+ # it turns it into a float else an int. This is naive but should be sufficient
105
+ # for numbers typed on the cli in most cases
106
+ def self.string_to_number(val)
107
+ return val.to_f if val =~ /^\d+\.\d+$/
108
+ return val.to_i if val =~ /^\d+$/
109
+
110
+ raise "#{val} does not look like a number"
111
+ end
112
+ end
113
+ end