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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6ae1643132a522cc95d0ca2aa305795d0339a552
4
+ data.tar.gz: 3bb0890088ba7f874d81df9ea7dc494f2a9ab485
5
+ SHA512:
6
+ metadata.gz: d6b85252e78fe6d90b2c269ca47b06879a963e9108f16170061d05a5da18ac5ccc388a98cfd52a59e1c3360806174dfcbc46bb2409e3814a6c1efc503a9cb788
7
+ data.tar.gz: b09baf095839b1915238661d5bd4ea5430b780318cae53c77bea645a3a23c3b21d3d986da3e25d920d220366961931a23ba59d1fc781c492db2fcead4d2168f5
data/bin/mco ADDED
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # For security reasons, ensure that '.' is not on the load path
4
+ # This is primarily for 1.8.7 since 1.9.2+ doesn't put '.' on the load path
5
+ $LOAD_PATH.delete '.'
6
+
7
+ require 'mcollective'
8
+
9
+ # assume everything will all be fine
10
+ exitcode = 0
11
+
12
+ Version = MCollective.version
13
+ known_applications = MCollective::Applications.list
14
+
15
+ # links from mc-ping to mc will result in ping being run
16
+ if $0 =~ /mc\-([a-zA-Z\-_\.]+)$/
17
+ app_name = $1
18
+ else
19
+ app_name = ARGV.first
20
+ ARGV.delete_at(0)
21
+
22
+ # it may be a config option rather than an application name. In this
23
+ # case pretend we had no application name
24
+ if app_name =~ /^--/
25
+ app_name = nil
26
+ end
27
+ end
28
+
29
+ if known_applications.include?(app_name)
30
+ # make sure the various options classes shows the right help etc
31
+ $0 = app_name
32
+
33
+ MCollective::Applications.run(app_name)
34
+ else
35
+ puts "The Marionette Collective version #{MCollective.version}"
36
+ puts
37
+
38
+ if app_name
39
+ puts "Unknown command '#{app_name}', searched for applications in:"
40
+ puts
41
+ puts MCollective::Config.instance.libdir.map { |path| " #{path}\n" }
42
+ puts
43
+
44
+ # exit with an error as we didn't find a command
45
+ exitcode = 1
46
+ else
47
+ puts "usage: #{$0} command <options>"
48
+ puts
49
+ end
50
+
51
+ puts "Known commands:"
52
+ puts
53
+
54
+ known_applications.sort.uniq.in_groups_of(3) do |apps|
55
+ puts " %-20s %-20s %-20s" % [apps[0], apps[1], apps[2]]
56
+ end
57
+
58
+ puts
59
+ puts "Type '#{$0} help' for a detailed list of commands and '#{$0} help command'"
60
+ puts "to get detailed help for a command"
61
+ puts
62
+ end
63
+
64
+ exit exitcode
@@ -0,0 +1,63 @@
1
+ require 'rubygems'
2
+ require 'json'
3
+ require 'timeout'
4
+ require 'digest/md5'
5
+ require 'optparse'
6
+ require 'singleton'
7
+ require 'socket'
8
+ require 'erb'
9
+ require 'shellwords'
10
+ require 'stringio'
11
+ require 'rbconfig'
12
+ require 'tempfile'
13
+ require 'tmpdir'
14
+ require 'mcollective/monkey_patches'
15
+ require 'mcollective/cache'
16
+ require 'mcollective/exceptions'
17
+ require 'systemu'
18
+
19
+ # == The Marionette Collective
20
+ #
21
+ # Framework to build and run Systems Administration agents running on a
22
+ # publish/subscribe middleware system. The system allows you to treat your
23
+ # network as the only true source of the state of your platform via discovery agents
24
+ # and allow you to run agents matching discovery criteria.
25
+ #
26
+ # For an overview of the idea behind this and what it enables please see:
27
+ # http://www.devco.net/archives/2009/10/18/middleware_for_systems_administration.php
28
+ module MCollective
29
+
30
+ require "mcollective/agent"
31
+ require "mcollective/agents"
32
+ require "mcollective/aggregate"
33
+ require "mcollective/application"
34
+ require "mcollective/applications"
35
+ require "mcollective/client"
36
+ require "mcollective/config"
37
+ require "mcollective/connector"
38
+ require "mcollective/data"
39
+ require "mcollective/ddl"
40
+ require "mcollective/discovery"
41
+ require "mcollective/facts"
42
+ require "mcollective/logger"
43
+ require "mcollective/log"
44
+ require "mcollective/matcher"
45
+ require "mcollective/message"
46
+ require "mcollective/optionparser"
47
+ require "mcollective/generators"
48
+ require "mcollective/pluginmanager"
49
+ require "mcollective/pluginpackager"
50
+ require "mcollective/rpc"
51
+ require "mcollective/runnerstats"
52
+ require "mcollective/security"
53
+ require "mcollective/shell"
54
+ require "mcollective/ssl"
55
+ require "mcollective/util"
56
+ require "mcollective/validator"
57
+
58
+ VERSION="2.20.0"
59
+
60
+ def self.version
61
+ VERSION
62
+ end
63
+ end
@@ -0,0 +1,5 @@
1
+ module MCollective
2
+ module Agent
3
+
4
+ end
5
+ end
@@ -0,0 +1,149 @@
1
+ module MCollective
2
+ # A collection of agents, loads them, reloads them and dispatches messages to them.
3
+ # It uses the PluginManager to store, load and manage instances of plugins.
4
+ class Agents
5
+ def initialize(agents = {})
6
+ @config = Config.instance
7
+ raise ("Configuration has not been loaded, can't load agents") unless @config.configured
8
+
9
+ @@agents = agents
10
+
11
+ loadagents
12
+ end
13
+
14
+ # Deletes all agents
15
+ def clear!
16
+ @@agents.each_key do |agent|
17
+ PluginManager.delete "#{agent}_agent"
18
+ Util.unsubscribe(Util.make_subscriptions(agent, :broadcast))
19
+ end
20
+
21
+ @@agents = {}
22
+ end
23
+
24
+ # Loads all agents from disk
25
+ def loadagents
26
+ Log.debug("Reloading all agents from disk")
27
+
28
+ clear!
29
+
30
+ @config.libdir.each do |libdir|
31
+ agentdir = "#{libdir}/mcollective/agent"
32
+ next unless File.directory?(agentdir)
33
+
34
+ Dir.new(agentdir).grep(/\.rb$/).each do |agent|
35
+ agentname = File.basename(agent, ".rb")
36
+ loadagent(agentname) unless PluginManager.include?("#{agentname}_agent")
37
+ end
38
+ end
39
+ end
40
+
41
+ # Loads a specified agent from disk if available
42
+ def loadagent(agentname)
43
+ agentfile = findagentfile(agentname)
44
+ return false unless agentfile
45
+ classname = class_for_agent(agentname)
46
+
47
+ PluginManager.delete("#{agentname}_agent")
48
+
49
+ begin
50
+ single_instance = ["registration", "discovery"].include?(agentname)
51
+
52
+ PluginManager.loadclass(classname)
53
+
54
+ if activate_agent?(agentname)
55
+ PluginManager << {:type => "#{agentname}_agent", :class => classname, :single_instance => single_instance}
56
+
57
+ # Attempt to instantiate the agent once so any validation and hooks get run
58
+ # this does a basic sanity check on the agent as a whole, if this fails it
59
+ # will be removed from the plugin list
60
+ PluginManager["#{agentname}_agent"]
61
+
62
+ Util.subscribe(Util.make_subscriptions(agentname, :broadcast)) unless @@agents.include?(agentname)
63
+
64
+ @@agents[agentname] = {:file => agentfile}
65
+ return true
66
+ else
67
+ Log.debug("Not activating agent #{agentname} due to agent policy in activate? method")
68
+ return false
69
+ end
70
+ rescue Exception => e
71
+ Log.error("Loading agent #{agentname} failed: #{e}")
72
+ PluginManager.delete("#{agentname}_agent")
73
+ return false
74
+ end
75
+ end
76
+
77
+ # Builds a class name string given a Agent name
78
+ def class_for_agent(agent)
79
+ "MCollective::Agent::#{agent.capitalize}"
80
+ end
81
+
82
+ # Checks if a plugin should be activated by
83
+ # calling #activate? on it if it responds to
84
+ # that method else always activate it
85
+ def activate_agent?(agent)
86
+ klass = Kernel.const_get("MCollective").const_get("Agent").const_get(agent.capitalize)
87
+
88
+ if klass.respond_to?("activate?")
89
+ return klass.activate?
90
+ else
91
+ Log.debug("#{klass} does not have an activate? method, activating as default")
92
+ return true
93
+ end
94
+ rescue Exception => e
95
+ Log.warn("Agent activation check for #{agent} failed: #{e.class}: #{e}")
96
+ return false
97
+ end
98
+
99
+ # searches the libdirs for agents
100
+ def findagentfile(agentname)
101
+ @config.libdir.each do |libdir|
102
+ agentfile = File.join([libdir, "mcollective", "agent", "#{agentname}.rb"])
103
+ if File.exist?(agentfile)
104
+ Log.debug("Found #{agentname} at #{agentfile}")
105
+ return agentfile
106
+ end
107
+ end
108
+ return false
109
+ end
110
+
111
+ # Determines if we have an agent with a certain name
112
+ def include?(agentname)
113
+ PluginManager.include?("#{agentname}_agent")
114
+ end
115
+
116
+ # Dispatches a message to an agent, accepts a block that will get run if there are
117
+ # any replies to process from the agent
118
+ def dispatch(request, connection)
119
+ Log.debug("Dispatching a message to agent #{request.agent}")
120
+
121
+ Thread.new do
122
+ begin
123
+ agent = PluginManager["#{request.agent}_agent"]
124
+
125
+ Timeout::timeout(agent.timeout) do
126
+ replies = agent.handlemsg(request.payload, connection)
127
+
128
+ # Agents can decide if they wish to reply or not,
129
+ # returning nil will mean nothing goes back to the
130
+ # requestor
131
+ unless replies == nil
132
+ yield(replies)
133
+ end
134
+ end
135
+ rescue Timeout::Error => e
136
+ Log.warn("Timeout while handling message for #{request.agent}")
137
+ rescue Exception => e
138
+ Log.error("Execution of #{request.agent} failed: #{e}")
139
+ Log.error(e.backtrace.join("\n\t\t"))
140
+ end
141
+ end
142
+ end
143
+
144
+ # Get a list of agents that we have
145
+ def self.agentlist
146
+ @@agents.keys
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,85 @@
1
+ module MCollective
2
+ class Aggregate
3
+ require 'mcollective/aggregate/result'
4
+ require 'mcollective/aggregate/base'
5
+
6
+ attr_accessor :ddl, :functions, :action, :failed
7
+
8
+ def initialize(ddl)
9
+ @functions = []
10
+ @ddl = ddl
11
+ @action = ddl[:action]
12
+ @failed = []
13
+
14
+ create_functions
15
+ end
16
+
17
+ # Creates instances of the Aggregate functions and stores them in the function array.
18
+ # All aggregate call and summarize method calls operate on these function as a batch.
19
+ def create_functions
20
+ @ddl[:aggregate].each_with_index do |agg, i|
21
+ output = agg[:args][0]
22
+
23
+ if contains_output?(output)
24
+ arguments = agg[:args][1]
25
+ format = (arguments.delete(:format) if arguments) || nil
26
+ begin
27
+ @functions << load_function(agg[:function]).new(output, arguments, format, @action)
28
+ rescue Exception => e
29
+ Log.error("Cannot create aggregate function '#{output}'. #{e.to_s}")
30
+ @failed << {:name => output, :type => :startup}
31
+ end
32
+ else
33
+ Log.error("Cannot create aggregate function '#{output}'. '#{output}' has not been specified as a valid ddl output.")
34
+ @failed << {:name => output, :type => :create}
35
+ end
36
+ end
37
+ end
38
+
39
+ # Check if the function param is defined as an output for the action in the ddl
40
+ def contains_output?(output)
41
+ @ddl[:output].keys.include?(output)
42
+ end
43
+
44
+ # Call all the appropriate functions with the reply data received from RPC::Client
45
+ def call_functions(reply)
46
+ @functions.each do |function|
47
+ Log.debug("Calling aggregate function #{function} for result")
48
+ begin
49
+ function.process_result(reply[:data][function.output_name], reply)
50
+ rescue Exception => e
51
+ Log.error("Could not process aggregate function for '#{function.output_name}'. #{e.to_s}")
52
+ @failed << {:name => function.output_name, :type => :process_result}
53
+ @functions.delete(function)
54
+ end
55
+ end
56
+ end
57
+
58
+ # Finalizes the function returning a result object
59
+ def summarize
60
+ summary = @functions.map do |function|
61
+ begin
62
+ function.summarize
63
+ rescue Exception => e
64
+ Log.error("Could not summarize aggregate result for '#{function.output_name}'. #{e.to_s}")
65
+ @failed << {:name => function.output_name, :type => :summarize}
66
+ nil
67
+ end
68
+ end
69
+
70
+ summary.reject{|x| x.nil?}.sort do |x,y|
71
+ x.result[:output] <=> y.result[:output]
72
+ end
73
+ end
74
+
75
+ # Loads function from disk for use
76
+ def load_function(function_name)
77
+ function_name = function_name.to_s.capitalize
78
+
79
+ PluginManager.loadclass("MCollective::Aggregate::#{function_name}") unless Aggregate.const_defined?(function_name)
80
+ Aggregate.const_get(function_name)
81
+ rescue Exception
82
+ raise "Aggregate function file '#{function_name.downcase}.rb' cannot be loaded"
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,33 @@
1
+ metadata :name => "average",
2
+ :description => "Displays the average of a set of numeric values",
3
+ :author => "Pieter Loubser <pieter.loubser@puppetlabs.com>",
4
+ :license => "ASL 2.0",
5
+ :version => "1.0",
6
+ :url => "https://docs.puppetlabs.com/mcollective/plugin_directory/",
7
+ :timeout => 5
8
+
9
+ usage <<-USAGE
10
+
11
+ This aggregate plugin will determine the average of a set of numeric values.
12
+
13
+ DDL Example:
14
+
15
+ summarize do
16
+ aggregate average(:value)
17
+ end
18
+
19
+ Sample Output:
20
+
21
+ host1.example.com
22
+ Value: 10
23
+
24
+ host2.example.com
25
+ Value: 20
26
+
27
+
28
+ Summary of Value:
29
+
30
+ Average of Value: 15.000000
31
+
32
+ USAGE
33
+
@@ -0,0 +1,29 @@
1
+ module MCollective
2
+ class Aggregate
3
+ class Average<Base
4
+ # Before function is run processing
5
+ def startup_hook
6
+ @result[:value] = 0
7
+ @result[:type] = :numeric
8
+
9
+ @count = 0
10
+
11
+ # Set default aggregate_function if it is undefined
12
+ @aggregate_format = "Average of #{@result[:output]}: %f" unless @aggregate_format
13
+ end
14
+
15
+ # Determines the average of a set of numerical values
16
+ def process_result(value, reply)
17
+ @result[:value] += value
18
+ @count += 1
19
+ end
20
+
21
+ # Stops execution of the function and returns a ResultObject
22
+ def summarize
23
+ @result[:value] /= @count
24
+
25
+ result_class(:numeric).new(@result, @aggregate_format, @action)
26
+ end
27
+ end
28
+ end
29
+ end