choria-mcorpc-support 2.22.1 → 2.23.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (145) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mcollective.rb +1 -1
  3. data/lib/mcollective/agent/bolt_tasks.ddl +235 -0
  4. data/lib/mcollective/agent/bolt_tasks.json +347 -0
  5. data/lib/mcollective/agent/bolt_tasks.rb +176 -0
  6. data/lib/mcollective/agent/choria_util.ddl +152 -0
  7. data/lib/mcollective/agent/choria_util.json +244 -0
  8. data/lib/mcollective/agent/rpcutil.ddl +7 -3
  9. data/lib/mcollective/agent/rpcutil.json +333 -0
  10. data/lib/mcollective/agent/scout.ddl +169 -0
  11. data/lib/mcollective/agent/scout.json +224 -0
  12. data/lib/mcollective/agents.rb +7 -6
  13. data/lib/mcollective/aggregate.rb +4 -4
  14. data/lib/mcollective/aggregate/average.rb +2 -2
  15. data/lib/mcollective/aggregate/base.rb +2 -2
  16. data/lib/mcollective/aggregate/result.rb +3 -3
  17. data/lib/mcollective/aggregate/result/collection_result.rb +2 -2
  18. data/lib/mcollective/aggregate/result/numeric_result.rb +2 -2
  19. data/lib/mcollective/aggregate/sum.rb +2 -2
  20. data/lib/mcollective/aggregate/summary.rb +3 -4
  21. data/lib/mcollective/application.rb +57 -21
  22. data/lib/mcollective/application/choria.rb +249 -0
  23. data/lib/mcollective/application/completion.rb +6 -6
  24. data/lib/mcollective/application/describe_filter.rb +20 -20
  25. data/lib/mcollective/application/facts.rb +11 -11
  26. data/lib/mcollective/application/federation.rb +239 -0
  27. data/lib/mcollective/application/find.rb +4 -4
  28. data/lib/mcollective/application/help.rb +3 -3
  29. data/lib/mcollective/application/inventory.rb +3 -341
  30. data/lib/mcollective/application/ping.rb +3 -77
  31. data/lib/mcollective/application/playbook.rb +207 -0
  32. data/lib/mcollective/application/plugin.rb +106 -106
  33. data/lib/mcollective/application/rpc.rb +3 -108
  34. data/lib/mcollective/application/tasks.rb +416 -0
  35. data/lib/mcollective/applications.rb +11 -10
  36. data/lib/mcollective/audit/choria.rb +33 -0
  37. data/lib/mcollective/cache.rb +2 -4
  38. data/lib/mcollective/client.rb +11 -10
  39. data/lib/mcollective/config.rb +21 -26
  40. data/lib/mcollective/connector/base.rb +2 -1
  41. data/lib/mcollective/connector/nats.ddl +9 -0
  42. data/lib/mcollective/connector/nats.rb +450 -0
  43. data/lib/mcollective/data.rb +8 -3
  44. data/lib/mcollective/data/agent_data.rb +1 -1
  45. data/lib/mcollective/data/base.rb +6 -5
  46. data/lib/mcollective/data/bolt_task_data.ddl +90 -0
  47. data/lib/mcollective/data/bolt_task_data.rb +32 -0
  48. data/lib/mcollective/data/collective_data.rb +1 -1
  49. data/lib/mcollective/data/fact_data.rb +6 -6
  50. data/lib/mcollective/data/fstat_data.rb +2 -4
  51. data/lib/mcollective/data/result.rb +7 -2
  52. data/lib/mcollective/ddl/agentddl.rb +5 -17
  53. data/lib/mcollective/ddl/base.rb +10 -13
  54. data/lib/mcollective/discovery.rb +12 -26
  55. data/lib/mcollective/discovery/choria.ddl +11 -0
  56. data/lib/mcollective/discovery/choria.rb +223 -0
  57. data/lib/mcollective/discovery/flatfile.rb +7 -8
  58. data/lib/mcollective/discovery/mc.rb +2 -2
  59. data/lib/mcollective/discovery/stdin.rb +17 -18
  60. data/lib/mcollective/exceptions.rb +13 -0
  61. data/lib/mcollective/facts/base.rb +9 -9
  62. data/lib/mcollective/facts/yaml_facts.rb +12 -12
  63. data/lib/mcollective/generators.rb +3 -3
  64. data/lib/mcollective/generators/agent_generator.rb +3 -4
  65. data/lib/mcollective/generators/base.rb +14 -15
  66. data/lib/mcollective/generators/data_generator.rb +5 -6
  67. data/lib/mcollective/log.rb +2 -2
  68. data/lib/mcollective/logger/base.rb +3 -2
  69. data/lib/mcollective/logger/console_logger.rb +10 -10
  70. data/lib/mcollective/logger/file_logger.rb +7 -7
  71. data/lib/mcollective/logger/syslog_logger.rb +11 -15
  72. data/lib/mcollective/matcher.rb +14 -14
  73. data/lib/mcollective/matcher/parser.rb +31 -41
  74. data/lib/mcollective/matcher/scanner.rb +69 -74
  75. data/lib/mcollective/message.rb +10 -17
  76. data/lib/mcollective/monkey_patches.rb +2 -4
  77. data/lib/mcollective/optionparser.rb +1 -0
  78. data/lib/mcollective/pluginmanager.rb +3 -5
  79. data/lib/mcollective/pluginpackager.rb +1 -3
  80. data/lib/mcollective/pluginpackager/agent_definition.rb +3 -8
  81. data/lib/mcollective/pluginpackager/forge_packager.rb +7 -9
  82. data/lib/mcollective/pluginpackager/standard_definition.rb +1 -2
  83. data/lib/mcollective/registration/base.rb +18 -16
  84. data/lib/mcollective/rpc.rb +2 -4
  85. data/lib/mcollective/rpc/actionrunner.rb +16 -18
  86. data/lib/mcollective/rpc/agent.rb +26 -43
  87. data/lib/mcollective/rpc/audit.rb +1 -0
  88. data/lib/mcollective/rpc/client.rb +67 -85
  89. data/lib/mcollective/rpc/helpers.rb +55 -62
  90. data/lib/mcollective/rpc/progress.rb +2 -2
  91. data/lib/mcollective/rpc/reply.rb +17 -19
  92. data/lib/mcollective/rpc/request.rb +7 -5
  93. data/lib/mcollective/rpc/result.rb +6 -8
  94. data/lib/mcollective/rpc/stats.rb +49 -58
  95. data/lib/mcollective/security/base.rb +29 -36
  96. data/lib/mcollective/security/choria.rb +765 -0
  97. data/lib/mcollective/shell.rb +9 -4
  98. data/lib/mcollective/signer/base.rb +28 -0
  99. data/lib/mcollective/signer/choria.rb +185 -0
  100. data/lib/mcollective/ssl.rb +8 -6
  101. data/lib/mcollective/util.rb +52 -53
  102. data/lib/mcollective/util/bolt_support.rb +176 -0
  103. data/lib/mcollective/util/bolt_support/plan_runner.rb +167 -0
  104. data/lib/mcollective/util/bolt_support/task_result.rb +94 -0
  105. data/lib/mcollective/util/bolt_support/task_results.rb +128 -0
  106. data/lib/mcollective/util/choria.rb +1103 -0
  107. data/lib/mcollective/util/indifferent_hash.rb +12 -0
  108. data/lib/mcollective/util/natswrapper.rb +242 -0
  109. data/lib/mcollective/util/playbook.rb +435 -0
  110. data/lib/mcollective/util/playbook/data_stores.rb +201 -0
  111. data/lib/mcollective/util/playbook/data_stores/base.rb +99 -0
  112. data/lib/mcollective/util/playbook/data_stores/consul_data_store.rb +88 -0
  113. data/lib/mcollective/util/playbook/data_stores/environment_data_store.rb +33 -0
  114. data/lib/mcollective/util/playbook/data_stores/etcd_data_store.rb +42 -0
  115. data/lib/mcollective/util/playbook/data_stores/file_data_store.rb +106 -0
  116. data/lib/mcollective/util/playbook/data_stores/shell_data_store.rb +103 -0
  117. data/lib/mcollective/util/playbook/inputs.rb +265 -0
  118. data/lib/mcollective/util/playbook/nodes.rb +207 -0
  119. data/lib/mcollective/util/playbook/nodes/mcollective_nodes.rb +86 -0
  120. data/lib/mcollective/util/playbook/nodes/pql_nodes.rb +40 -0
  121. data/lib/mcollective/util/playbook/nodes/shell_nodes.rb +55 -0
  122. data/lib/mcollective/util/playbook/nodes/terraform_nodes.rb +65 -0
  123. data/lib/mcollective/util/playbook/nodes/yaml_nodes.rb +47 -0
  124. data/lib/mcollective/util/playbook/playbook_logger.rb +47 -0
  125. data/lib/mcollective/util/playbook/puppet_logger.rb +51 -0
  126. data/lib/mcollective/util/playbook/report.rb +152 -0
  127. data/lib/mcollective/util/playbook/task_result.rb +55 -0
  128. data/lib/mcollective/util/playbook/tasks.rb +196 -0
  129. data/lib/mcollective/util/playbook/tasks/base.rb +45 -0
  130. data/lib/mcollective/util/playbook/tasks/graphite_event_task.rb +64 -0
  131. data/lib/mcollective/util/playbook/tasks/mcollective_task.rb +356 -0
  132. data/lib/mcollective/util/playbook/tasks/shell_task.rb +93 -0
  133. data/lib/mcollective/util/playbook/tasks/slack_task.rb +105 -0
  134. data/lib/mcollective/util/playbook/tasks/webhook_task.rb +136 -0
  135. data/lib/mcollective/util/playbook/template_util.rb +98 -0
  136. data/lib/mcollective/util/playbook/uses.rb +169 -0
  137. data/lib/mcollective/util/tasks_support.rb +733 -0
  138. data/lib/mcollective/util/tasks_support/cli.rb +260 -0
  139. data/lib/mcollective/util/tasks_support/default_formatter.rb +138 -0
  140. data/lib/mcollective/util/tasks_support/json_formatter.rb +108 -0
  141. data/lib/mcollective/validator.rb +6 -1
  142. data/lib/mcollective/validator/bolt_task_name_validator.ddl +7 -0
  143. data/lib/mcollective/validator/bolt_task_name_validator.rb +11 -0
  144. data/lib/mcollective/validator/length_validator.rb +1 -3
  145. metadata +67 -4
@@ -0,0 +1,207 @@
1
+ require_relative "nodes/mcollective_nodes"
2
+ require_relative "nodes/pql_nodes"
3
+ require_relative "nodes/yaml_nodes"
4
+ require_relative "nodes/shell_nodes"
5
+ require_relative "nodes/terraform_nodes"
6
+
7
+ module MCollective
8
+ module Util
9
+ class Playbook
10
+ class Nodes
11
+ attr_reader :nodes
12
+
13
+ def initialize(playbook)
14
+ @playbook = playbook
15
+ @nodes = {}
16
+ end
17
+
18
+ # List of known node set names
19
+ #
20
+ # @return [Array<String>]
21
+ def keys
22
+ @nodes.keys
23
+ end
24
+
25
+ # Nodes belonging to a specific node set
26
+ #
27
+ # @param nodeset [String] node set name
28
+ # @return [Array<String>]
29
+ # @raise [StandardError] when node set is unknown
30
+ def [](nodeset)
31
+ if include?(nodeset)
32
+ @nodes[nodeset][:discovered]
33
+ else
34
+ raise("Unknown node set %s" % nodeset)
35
+ end
36
+ end
37
+
38
+ # Properties for a certain node set
39
+ #
40
+ # @param nodes [String] node set name
41
+ # @return [Hash]
42
+ # @raise [StandardError] when node set is unknown
43
+ def properties(nodes)
44
+ if include?(nodes)
45
+ @nodes[nodes][:properties]
46
+ else
47
+ raise("Unknown node set %s" % nodes)
48
+ end
49
+ end
50
+
51
+ # Determines if a node set is known
52
+ #
53
+ # @param nodes [String] node set name
54
+ # @return [Boolean]
55
+ def include?(nodes)
56
+ @nodes.include?(nodes)
57
+ end
58
+
59
+ def prepare
60
+ @nodes.each do |node_set, dets|
61
+ @playbook.in_context(node_set) do
62
+ Log.debug("Preparing nodeset %s" % node_set)
63
+
64
+ resolve_nodes(node_set)
65
+ check_empty(node_set)
66
+ limit_nodes(node_set)
67
+ validate_nodes(node_set)
68
+
69
+ Log.info("Discovered %d node(s) in node set %s" % [dets[:discovered].size, node_set])
70
+ end
71
+ end
72
+
73
+ @playbook.in_context("conn.test") { test_nodes }
74
+ @playbook.in_context("ddl.test") { check_uses }
75
+ end
76
+
77
+ # Resolve the node list using the resolver class
78
+ #
79
+ # @param node_set [String] node set name
80
+ def resolve_nodes(node_set)
81
+ node_props = @nodes[node_set]
82
+ node_props[:resolver].prepare
83
+ node_props[:discovered] = node_props[:resolver].discover.uniq
84
+ end
85
+
86
+ # Checks if the agents on the nodes matches the desired versions
87
+ #
88
+ # @raise [StandardError] on error
89
+ def check_uses
90
+ agent_nodes = {}
91
+
92
+ @nodes.map do |_, dets|
93
+ dets[:properties].fetch("uses", []).each do |agent|
94
+ agent_nodes[agent] ||= []
95
+ agent_nodes[agent].concat(dets[:discovered])
96
+ end
97
+ end
98
+
99
+ @playbook.validate_agents(agent_nodes) unless agent_nodes.empty?
100
+ end
101
+
102
+ # Determines if a nodeset needs connectivity test
103
+ #
104
+ # @param nodes [String] node set name
105
+ # @return [Boolean]
106
+ # @raise [StandardError] for unknown node sets
107
+ def should_test?(nodes)
108
+ !!properties(nodes)["test"]
109
+ end
110
+
111
+ def mcollective_task
112
+ Tasks::McollectiveTask.new(@playbook)
113
+ end
114
+
115
+ # Tests a RPC ping to the discovered nodes
116
+ #
117
+ # @todo is this really needed?
118
+ # @raise [StandardError] on error
119
+ def test_nodes
120
+ nodes_to_test = @nodes.map do |nodes, _|
121
+ self[nodes] if should_test?(nodes)
122
+ end.flatten.compact
123
+
124
+ return if nodes_to_test.empty?
125
+
126
+ Log.info("Checking connectivity for %d nodes" % nodes_to_test.size)
127
+
128
+ rpc = mcollective_task
129
+ rpc.from_hash(
130
+ "nodes" => nodes_to_test,
131
+ "action" => "rpcutil.ping",
132
+ "silent" => true
133
+ )
134
+ success, msg, _ = rpc.run
135
+
136
+ raise("Connectivity test failed for some nodes: %s" % [msg]) unless success
137
+ end
138
+
139
+ # Checks that discovered nodes matches stated expectations
140
+ #
141
+ # @param nodes [String] node set name
142
+ # @raise [StandardError] on error
143
+ def validate_nodes(nodes)
144
+ return if properties(nodes)["empty_ok"]
145
+
146
+ raise("Node set %s needs at least %d nodes, got %d" % [nodes, properties(nodes)["at_least"], self[nodes].size]) unless self[nodes].size >= properties(nodes)["at_least"]
147
+ end
148
+
149
+ # Handles an empty discovered list
150
+ #
151
+ # @param nodes [String] node set name
152
+ # @raise [StandardError] when empty
153
+ def check_empty(nodes)
154
+ raise(properties(nodes)["when_empty"] || "Did not discover any nodes for nodeset %s" % nodes) if self[nodes].empty? && !properties(nodes)["empty_ok"]
155
+ end
156
+
157
+ # Limits the discovered list for a node set based on the playbook limits
158
+ #
159
+ # @todo more intelegent limiting with weighted randoms like mco rpc client
160
+ # @param nodes [String] node set name
161
+ def limit_nodes(nodes)
162
+ return if self[nodes].empty?
163
+
164
+ if limit = properties(nodes)["limit"]
165
+ Log.debug("Limiting node set %s to %d nodes from %d" % [nodes, limit, @nodes[nodes][:discovered].size])
166
+ @nodes[nodes][:discovered] = @nodes[nodes][:discovered][0..(limit - 1)]
167
+ end
168
+ end
169
+
170
+ # Retrieves a new instance of the resolver for a certain type of discovery
171
+ #
172
+ # @param type [String] finds classes called *Nodes::TypeNodes* based on this type
173
+ def resolver_for(type)
174
+ klass_name = "%sNodes" % type.capitalize
175
+
176
+ Nodes.const_get(klass_name).new
177
+ rescue NameError
178
+ raise("Cannot find a handler for Node Set type %s" % type)
179
+ end
180
+
181
+ def from_hash(data)
182
+ data.each do |nodes, props|
183
+ resolver = resolver_for(props["type"])
184
+ resolver.from_hash(props)
185
+ resolver.validate_configuration!
186
+
187
+ node_props = {
188
+ "at_least" => 1,
189
+ "empty_ok" => false,
190
+ "when_empty" => "Did not discover any nodes for nodeset %s" % nodes
191
+ }.merge(props)
192
+
193
+ node_props["at_least"] = 0 if node_props["empty_ok"]
194
+
195
+ @nodes[nodes] = {
196
+ :resolver => resolver,
197
+ :discovered => [],
198
+ :properties => node_props
199
+ }
200
+ end
201
+
202
+ self
203
+ end
204
+ end
205
+ end
206
+ end
207
+ end
@@ -0,0 +1,86 @@
1
+ module MCollective
2
+ module Util
3
+ class Playbook
4
+ class Nodes
5
+ class McollectiveNodes
6
+ def initialize
7
+ @discovery_method = "mc"
8
+ @agents = []
9
+ @facts = []
10
+ @classes = []
11
+ @identity = []
12
+ @compound = nil
13
+ end
14
+
15
+ def prepare; end
16
+
17
+ # @todo
18
+ def validate_configuration!; end
19
+
20
+ # Creates and cache an RPC::Client for the configured agent
21
+ #
22
+ # @param from_cache [Boolean] when false a new instance is always returned
23
+ # @return [RPC::Client]
24
+ def client(from_cache: true)
25
+ if from_cache
26
+ @_rpc_client ||= create_and_configure_client
27
+ else
28
+ create_and_configure_client
29
+ end
30
+ end
31
+
32
+ # Creates a new RPC::Client and configures it with the configured settings
33
+ #
34
+ # @todo discovery
35
+ # @return [RPC::Client]
36
+ def create_and_configure_client
37
+ client = RPC::Client.new(@agents[0], :configfile => Util.config_file_for_user, :options => Util.default_options)
38
+ client.progress = false
39
+ client.discovery_method = @discovery_method
40
+
41
+ @classes.each do |filter|
42
+ client.class_filter(filter)
43
+ end
44
+
45
+ @facts.each do |filter|
46
+ client.fact_filter(filter)
47
+ end
48
+
49
+ @agents.each do |filter|
50
+ client.agent_filter(filter)
51
+ end
52
+
53
+ @identity.each do |filter|
54
+ client.identity_filter(filter)
55
+ end
56
+
57
+ client.compound_filter(@compound) if @compound
58
+
59
+ client
60
+ end
61
+
62
+ # Initialize the nodes source from a hash
63
+ #
64
+ # @param data [Hash] input data matching nodes.json schema
65
+ # @return [McollectiveNodes]
66
+ def from_hash(data)
67
+ @discovery_method = data.fetch("discovery_method", "mc")
68
+ @agents = data.fetch("agents", ["rpcutil"])
69
+ @facts = data.fetch("facts", [])
70
+ @classes = data.fetch("classes", [])
71
+ @identity = data.fetch("identities", [])
72
+ @compound = data["compound"]
73
+
74
+ @_rpc_client = nil
75
+
76
+ self
77
+ end
78
+
79
+ def discover
80
+ client.discover
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,40 @@
1
+ require "mcollective/util/choria"
2
+
3
+ module MCollective
4
+ module Util
5
+ class Playbook
6
+ class Nodes
7
+ class PqlNodes
8
+ def initialize
9
+ @query = nil
10
+ end
11
+
12
+ def prepare; end
13
+
14
+ def validate_configuration!
15
+ raise("No PQL query specified") unless @query
16
+ end
17
+
18
+ def choria
19
+ @_choria ||= Util::Choria.new(false)
20
+ end
21
+
22
+ # Initialize the nodes source from a hash
23
+ #
24
+ # @param data [Hash] input data matching nodes.json schema
25
+ # @return [PqlNodes]
26
+ def from_hash(data)
27
+ @query = data["query"]
28
+
29
+ self
30
+ end
31
+
32
+ # Performs the PQL query and extracts certnames
33
+ def discover
34
+ choria.pql_query(@query, true)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,55 @@
1
+ module MCollective
2
+ module Util
3
+ class Playbook
4
+ class Nodes
5
+ class ShellNodes
6
+ def initialize
7
+ @script = nil
8
+ end
9
+
10
+ def prepare; end
11
+
12
+ def validate_configuration!
13
+ raise("No node source script specified") unless @script
14
+ raise("Node source script is not executable") unless File.executable?(@script)
15
+ raise("Node source script produced no results") if data.empty?
16
+ end
17
+
18
+ def from_hash(data)
19
+ @script = data["script"]
20
+ @_data = nil
21
+
22
+ self
23
+ end
24
+
25
+ def valid_hostname?(host)
26
+ host =~ /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/
27
+ end
28
+
29
+ def data
30
+ return @_data if @_data
31
+
32
+ shell = Shell.new(@script, "timeout" => 10)
33
+ shell.runcommand
34
+
35
+ exitcode = shell.status.exitstatus
36
+
37
+ raise("Could not discover nodes via shell method, command exited with code %d" % [exitcode]) unless exitcode == 0
38
+
39
+ @_data = shell.stdout.lines.map do |line|
40
+ line.chomp!
41
+
42
+ raise("%s is not a valid hostname" % line) unless valid_hostname?(line)
43
+
44
+ line
45
+ end
46
+ end
47
+
48
+ def discover
49
+ data
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,65 @@
1
+ module MCollective
2
+ module Util
3
+ class Playbook
4
+ class Nodes
5
+ class TerraformNodes
6
+ def prepare; end
7
+
8
+ def validate_configuration!
9
+ raise("The supplied terraform path %s is not executable" % @terraform) if @terraform && !File.executable?(@terraform)
10
+ raise("A terraform state file is needed") unless @state
11
+ raise("The terraform statefile %s is not readable" % @state) unless File.readable?(@state)
12
+ raise("An output name is needed") unless @output
13
+
14
+ Validator.validate(@terraform, :shellsafe)
15
+ Validator.validate(@state, :shellsafe)
16
+ Validator.validate(@output, :shellsafe)
17
+ end
18
+
19
+ def from_hash(data)
20
+ @state = data["statefile"]
21
+ @output = data["output"]
22
+ @terraform = data.fetch("terraform", choria.which("terraform"))
23
+
24
+ self
25
+ end
26
+
27
+ def valid_hostname?(host)
28
+ host =~ /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/
29
+ end
30
+
31
+ def tf_output
32
+ shell = Shell.new("%s output -state %s -json %s 2>&1" % [@terraform, @state, @output])
33
+ shell.runcommand
34
+
35
+ raise("Terraform exited with code %d: %s" % [shell.status.exitstatus, shell.stdout]) unless shell.status.exitstatus == 0
36
+
37
+ shell.stdout
38
+ end
39
+
40
+ def output_data
41
+ return @_data if @_data
42
+
43
+ data = JSON.parse(tf_output)
44
+
45
+ raise("Only terraform outputs of type list is supported") unless data["type"] == "list"
46
+
47
+ data["value"].each do |result|
48
+ raise("%s is not a valid hostname" % result) unless valid_hostname?(result)
49
+ end
50
+
51
+ @_data = data["value"]
52
+ end
53
+
54
+ def choria
55
+ @_choria ||= Util::Choria.new(false)
56
+ end
57
+
58
+ def discover
59
+ output_data
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end