choria-mcorpc-support 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. checksums.yaml +7 -0
  2. data/bin/mco +64 -0
  3. data/lib/mcollective.rb +63 -0
  4. data/lib/mcollective/agent.rb +5 -0
  5. data/lib/mcollective/agents.rb +149 -0
  6. data/lib/mcollective/aggregate.rb +85 -0
  7. data/lib/mcollective/aggregate/average.ddl +33 -0
  8. data/lib/mcollective/aggregate/average.rb +29 -0
  9. data/lib/mcollective/aggregate/base.rb +40 -0
  10. data/lib/mcollective/aggregate/result.rb +9 -0
  11. data/lib/mcollective/aggregate/result/base.rb +25 -0
  12. data/lib/mcollective/aggregate/result/collection_result.rb +19 -0
  13. data/lib/mcollective/aggregate/result/numeric_result.rb +13 -0
  14. data/lib/mcollective/aggregate/sum.ddl +33 -0
  15. data/lib/mcollective/aggregate/sum.rb +18 -0
  16. data/lib/mcollective/aggregate/summary.ddl +33 -0
  17. data/lib/mcollective/aggregate/summary.rb +53 -0
  18. data/lib/mcollective/application.rb +365 -0
  19. data/lib/mcollective/application/completion.rb +104 -0
  20. data/lib/mcollective/application/describe_filter.rb +87 -0
  21. data/lib/mcollective/application/facts.rb +62 -0
  22. data/lib/mcollective/application/find.rb +23 -0
  23. data/lib/mcollective/application/help.rb +28 -0
  24. data/lib/mcollective/application/inventory.rb +344 -0
  25. data/lib/mcollective/application/ping.rb +82 -0
  26. data/lib/mcollective/application/plugin.rb +369 -0
  27. data/lib/mcollective/application/rpc.rb +111 -0
  28. data/lib/mcollective/applications.rb +134 -0
  29. data/lib/mcollective/cache.rb +145 -0
  30. data/lib/mcollective/client.rb +353 -0
  31. data/lib/mcollective/config.rb +245 -0
  32. data/lib/mcollective/connector.rb +18 -0
  33. data/lib/mcollective/connector/base.rb +26 -0
  34. data/lib/mcollective/data.rb +91 -0
  35. data/lib/mcollective/data/agent_data.ddl +22 -0
  36. data/lib/mcollective/data/agent_data.rb +17 -0
  37. data/lib/mcollective/data/base.rb +67 -0
  38. data/lib/mcollective/data/collective_data.ddl +20 -0
  39. data/lib/mcollective/data/collective_data.rb +9 -0
  40. data/lib/mcollective/data/fact_data.ddl +28 -0
  41. data/lib/mcollective/data/fact_data.rb +55 -0
  42. data/lib/mcollective/data/fstat_data.ddl +89 -0
  43. data/lib/mcollective/data/fstat_data.rb +56 -0
  44. data/lib/mcollective/data/result.rb +45 -0
  45. data/lib/mcollective/ddl.rb +113 -0
  46. data/lib/mcollective/ddl/agentddl.rb +253 -0
  47. data/lib/mcollective/ddl/base.rb +217 -0
  48. data/lib/mcollective/ddl/dataddl.rb +56 -0
  49. data/lib/mcollective/ddl/discoveryddl.rb +52 -0
  50. data/lib/mcollective/ddl/validatorddl.rb +6 -0
  51. data/lib/mcollective/discovery.rb +143 -0
  52. data/lib/mcollective/discovery/flatfile.ddl +11 -0
  53. data/lib/mcollective/discovery/flatfile.rb +48 -0
  54. data/lib/mcollective/discovery/mc.ddl +11 -0
  55. data/lib/mcollective/discovery/mc.rb +30 -0
  56. data/lib/mcollective/discovery/stdin.ddl +11 -0
  57. data/lib/mcollective/discovery/stdin.rb +68 -0
  58. data/lib/mcollective/exceptions.rb +28 -0
  59. data/lib/mcollective/facts.rb +39 -0
  60. data/lib/mcollective/facts/base.rb +100 -0
  61. data/lib/mcollective/facts/yaml_facts.rb +65 -0
  62. data/lib/mcollective/generators.rb +7 -0
  63. data/lib/mcollective/generators/agent_generator.rb +51 -0
  64. data/lib/mcollective/generators/base.rb +46 -0
  65. data/lib/mcollective/generators/data_generator.rb +51 -0
  66. data/lib/mcollective/generators/templates/action_snippet.erb +13 -0
  67. data/lib/mcollective/generators/templates/data_input_snippet.erb +7 -0
  68. data/lib/mcollective/generators/templates/ddl.erb +8 -0
  69. data/lib/mcollective/generators/templates/plugin.erb +7 -0
  70. data/lib/mcollective/log.rb +118 -0
  71. data/lib/mcollective/logger.rb +5 -0
  72. data/lib/mcollective/logger/base.rb +77 -0
  73. data/lib/mcollective/logger/console_logger.rb +61 -0
  74. data/lib/mcollective/logger/file_logger.rb +53 -0
  75. data/lib/mcollective/logger/syslog_logger.rb +53 -0
  76. data/lib/mcollective/matcher.rb +224 -0
  77. data/lib/mcollective/matcher/parser.rb +128 -0
  78. data/lib/mcollective/matcher/scanner.rb +241 -0
  79. data/lib/mcollective/message.rb +248 -0
  80. data/lib/mcollective/monkey_patches.rb +152 -0
  81. data/lib/mcollective/optionparser.rb +197 -0
  82. data/lib/mcollective/pluginmanager.rb +180 -0
  83. data/lib/mcollective/pluginpackager.rb +98 -0
  84. data/lib/mcollective/pluginpackager/agent_definition.rb +94 -0
  85. data/lib/mcollective/pluginpackager/debpackage_packager.rb +237 -0
  86. data/lib/mcollective/pluginpackager/modulepackage_packager.rb +127 -0
  87. data/lib/mcollective/pluginpackager/ospackage_packager.rb +59 -0
  88. data/lib/mcollective/pluginpackager/rpmpackage_packager.rb +180 -0
  89. data/lib/mcollective/pluginpackager/standard_definition.rb +69 -0
  90. data/lib/mcollective/pluginpackager/templates/debian/Makefile.erb +7 -0
  91. data/lib/mcollective/pluginpackager/templates/debian/changelog.erb +5 -0
  92. data/lib/mcollective/pluginpackager/templates/debian/compat.erb +1 -0
  93. data/lib/mcollective/pluginpackager/templates/debian/control.erb +15 -0
  94. data/lib/mcollective/pluginpackager/templates/debian/copyright.erb +8 -0
  95. data/lib/mcollective/pluginpackager/templates/debian/rules.erb +6 -0
  96. data/lib/mcollective/pluginpackager/templates/module/Modulefile.erb +5 -0
  97. data/lib/mcollective/pluginpackager/templates/module/README.md.erb +37 -0
  98. data/lib/mcollective/pluginpackager/templates/module/_manifest.pp.erb +9 -0
  99. data/lib/mcollective/pluginpackager/templates/redhat/rpm_spec.erb +63 -0
  100. data/lib/mcollective/registration/base.rb +91 -0
  101. data/lib/mcollective/rpc.rb +182 -0
  102. data/lib/mcollective/rpc/actionrunner.rb +158 -0
  103. data/lib/mcollective/rpc/agent.rb +374 -0
  104. data/lib/mcollective/rpc/audit.rb +38 -0
  105. data/lib/mcollective/rpc/client.rb +1066 -0
  106. data/lib/mcollective/rpc/helpers.rb +321 -0
  107. data/lib/mcollective/rpc/progress.rb +63 -0
  108. data/lib/mcollective/rpc/reply.rb +87 -0
  109. data/lib/mcollective/rpc/request.rb +86 -0
  110. data/lib/mcollective/rpc/result.rb +90 -0
  111. data/lib/mcollective/rpc/stats.rb +294 -0
  112. data/lib/mcollective/runnerstats.rb +90 -0
  113. data/lib/mcollective/security.rb +26 -0
  114. data/lib/mcollective/security/base.rb +244 -0
  115. data/lib/mcollective/shell.rb +126 -0
  116. data/lib/mcollective/ssl.rb +285 -0
  117. data/lib/mcollective/util.rb +579 -0
  118. data/lib/mcollective/validator.rb +85 -0
  119. data/lib/mcollective/validator/array_validator.ddl +7 -0
  120. data/lib/mcollective/validator/array_validator.rb +9 -0
  121. data/lib/mcollective/validator/ipv4address_validator.ddl +7 -0
  122. data/lib/mcollective/validator/ipv4address_validator.rb +16 -0
  123. data/lib/mcollective/validator/ipv6address_validator.ddl +7 -0
  124. data/lib/mcollective/validator/ipv6address_validator.rb +16 -0
  125. data/lib/mcollective/validator/length_validator.ddl +7 -0
  126. data/lib/mcollective/validator/length_validator.rb +11 -0
  127. data/lib/mcollective/validator/regex_validator.ddl +7 -0
  128. data/lib/mcollective/validator/regex_validator.rb +9 -0
  129. data/lib/mcollective/validator/shellsafe_validator.ddl +7 -0
  130. data/lib/mcollective/validator/shellsafe_validator.rb +13 -0
  131. data/lib/mcollective/validator/typecheck_validator.ddl +7 -0
  132. data/lib/mcollective/validator/typecheck_validator.rb +28 -0
  133. metadata +215 -0
@@ -0,0 +1,321 @@
1
+ module MCollective
2
+ module RPC
3
+ # Various utilities for the RPC system
4
+ class Helpers
5
+ # Parse JSON output as produced by printrpc or puppet query
6
+ # and extract the "sender" / "certname" of each entry
7
+ #
8
+ # The simplist valid JSON based data would be:
9
+ #
10
+ # [
11
+ # {"sender" => "example.com"},
12
+ # {"sender" => "another.com"}
13
+ # ]
14
+ #
15
+ # or
16
+ #
17
+ # [
18
+ # {"certname" => "example.com"},
19
+ # {"certname" => "another.com"}
20
+ # ]
21
+ def self.extract_hosts_from_json(json)
22
+ hosts = JSON.parse(json)
23
+
24
+ raise "JSON hosts list is not an array" unless hosts.is_a?(Array)
25
+
26
+ hosts.map do |host|
27
+ raise "JSON host list is not an array of Hashes" unless host.is_a?(Hash)
28
+
29
+ unless host.include?("sender") || host.include?("certname")
30
+ raise "JSON host list does not have senders in it"
31
+ end
32
+
33
+ host["sender"] || host["certname"]
34
+ end.uniq
35
+ end
36
+
37
+ # Given an array of something, make sure each is a string
38
+ # chomp off any new lines and return just the array of hosts
39
+ def self.extract_hosts_from_array(hosts)
40
+ [hosts].flatten.map do |host|
41
+ raise "#{host} should be a string" unless host.is_a?(String)
42
+ host.chomp
43
+ end
44
+ end
45
+
46
+ # Returns a blob of text representing the results in a standard way
47
+ #
48
+ # It tries hard to do sane things so you often
49
+ # should not need to write your own display functions
50
+ #
51
+ # If the agent you are getting results for has a DDL
52
+ # it will use the hints in there to do the right thing specifically
53
+ # it will look at the values of display in the DDL to choose
54
+ # when to show results
55
+ #
56
+ # If you do not have a DDL you can pass these flags:
57
+ #
58
+ # printrpc exim.mailq, :flatten => true
59
+ # printrpc exim.mailq, :verbose => true
60
+ #
61
+ # If you've asked it to flatten the result it will not print sender
62
+ # hostnames, it will just print the result as if it's one huge result,
63
+ # handy for things like showing a combined mailq.
64
+ def self.rpcresults(result, flags = {})
65
+ flags = {:verbose => false, :flatten => false, :format => :console, :force_display_mode => false}.merge(flags)
66
+
67
+ result_text = ""
68
+ ddl = nil
69
+
70
+ # if running in verbose mode, just use the old style print
71
+ # no need for all the DDL helpers obfuscating the result
72
+ if flags[:format] == :json
73
+ if STDOUT.tty?
74
+ result_text = JSON.pretty_generate(result)
75
+ else
76
+ result_text = result.to_json
77
+ end
78
+ else
79
+ if flags[:verbose]
80
+ result_text = old_rpcresults(result, flags)
81
+ else
82
+ [result].flatten.each do |r|
83
+ begin
84
+ ddl ||= DDL.new(r.agent).action_interface(r.action.to_s)
85
+
86
+ sender = r[:sender]
87
+ status = r[:statuscode]
88
+ message = r[:statusmsg]
89
+ result = r[:data]
90
+
91
+ if flags[:force_display_mode]
92
+ display = flags[:force_display_mode]
93
+ else
94
+ display = ddl[:display]
95
+ end
96
+
97
+ # appand the results only according to what the DDL says
98
+ case display
99
+ when :ok
100
+ if status == 0
101
+ result_text << text_for_result(sender, status, message, result, ddl)
102
+ end
103
+
104
+ when :failed
105
+ if status > 0
106
+ result_text << text_for_result(sender, status, message, result, ddl)
107
+ end
108
+
109
+ when :always
110
+ result_text << text_for_result(sender, status, message, result, ddl)
111
+
112
+ when :flatten
113
+ Log.warn("The display option :flatten is being deprecated and will be removed in the next minor release")
114
+ result_text << text_for_flattened_result(status, result)
115
+
116
+ end
117
+ rescue Exception => e
118
+ # no DDL so just do the old style print unchanged for
119
+ # backward compat
120
+ result_text = old_rpcresults(result, flags)
121
+ end
122
+ end
123
+ end
124
+ end
125
+
126
+ result_text
127
+ end
128
+
129
+ # Return text representing a result
130
+ def self.text_for_result(sender, status, msg, result, ddl)
131
+ statusses = ["",
132
+ Util.colorize(:red, "Request Aborted"),
133
+ Util.colorize(:yellow, "Unknown Action"),
134
+ Util.colorize(:yellow, "Missing Request Data"),
135
+ Util.colorize(:yellow, "Invalid Request Data"),
136
+ Util.colorize(:red, "Unknown Request Status")]
137
+
138
+ result_text = "%-40s %s\n" % [sender, statusses[status]]
139
+ result_text << " %s\n" % [Util.colorize(:yellow, msg)] unless msg == "OK"
140
+
141
+ # only print good data, ignore data that results from failure
142
+ if status == 0
143
+ if result.is_a?(Hash)
144
+ # figure out the lengths of the display as strings, we'll use
145
+ # it later to correctly justify the output
146
+ lengths = result.keys.map do |k|
147
+ begin
148
+ ddl[:output][k][:display_as].size
149
+ rescue
150
+ k.to_s.size
151
+ end
152
+ end
153
+
154
+ result.keys.sort_by{|k| k}.each do |k|
155
+ # get all the output fields nicely lined up with a
156
+ # 3 space front padding
157
+ begin
158
+ display_as = ddl[:output][k][:display_as]
159
+ rescue
160
+ display_as = k.to_s
161
+ end
162
+
163
+ display_length = display_as.size
164
+ padding = lengths.max - display_length + 3
165
+ result_text << " " * padding
166
+
167
+ result_text << "#{display_as}:"
168
+
169
+ if [String, Numeric].include?(result[k].class)
170
+ lines = result[k].to_s.split("\n")
171
+
172
+ if lines.empty?
173
+ result_text << "\n"
174
+ else
175
+ lines.each_with_index do |line, i|
176
+ i == 0 ? padtxt = " " : padtxt = " " * (padding + display_length + 2)
177
+
178
+ result_text << "#{padtxt}#{line}\n"
179
+ end
180
+ end
181
+ else
182
+ padding = " " * (lengths.max + 5)
183
+ result_text << " " << result[k].pretty_inspect.split("\n").join("\n" << padding) << "\n"
184
+ end
185
+ end
186
+ elsif status == 1
187
+ # for status 1 we dont want to show half baked
188
+ # data by default since the DDL will supply all the defaults
189
+ # it just doesnt look right
190
+ else
191
+ result_text << "\n\t" + result.pretty_inspect.split("\n").join("\n\t")
192
+ end
193
+ end
194
+
195
+ result_text << "\n"
196
+ result_text
197
+ end
198
+
199
+ # Returns text representing a flattened result of only good data
200
+ def self.text_for_flattened_result(status, result)
201
+ result_text = ""
202
+
203
+ if status <= 1
204
+ unless result.is_a?(String)
205
+ result_text << result.pretty_inspect
206
+ else
207
+ result_text << result
208
+ end
209
+ end
210
+ end
211
+
212
+ # Backward compatible display block for results without a DDL
213
+ def self.old_rpcresults(result, flags = {})
214
+ result_text = ""
215
+
216
+ if flags[:flatten]
217
+ result.each do |r|
218
+ if r[:statuscode] <= 1
219
+ data = r[:data]
220
+
221
+ unless data.is_a?(String)
222
+ result_text << data.pretty_inspect
223
+ else
224
+ result_text << data
225
+ end
226
+ else
227
+ result_text << r.pretty_inspect
228
+ end
229
+ end
230
+
231
+ result_text << ""
232
+ else
233
+ [result].flatten.each do |r|
234
+ if flags[:verbose]
235
+ result_text << "%-40s: %s\n" % [r[:sender], r[:statusmsg]]
236
+
237
+ if r[:statuscode] <= 1
238
+ r[:data].pretty_inspect.split("\n").each {|m| result_text += " #{m}"}
239
+ result_text << "\n\n"
240
+ elsif r[:statuscode] == 2
241
+ # dont print anything, no useful data to display
242
+ # past what was already shown
243
+ elsif r[:statuscode] == 3
244
+ # dont print anything, no useful data to display
245
+ # past what was already shown
246
+ elsif r[:statuscode] == 4
247
+ # dont print anything, no useful data to display
248
+ # past what was already shown
249
+ else
250
+ result_text << " #{r[:statusmsg]}"
251
+ end
252
+ else
253
+ unless r[:statuscode] == 0
254
+ result_text << "%-40s %s\n" % [r[:sender], Util.colorize(:red, r[:statusmsg])]
255
+ end
256
+ end
257
+ end
258
+ end
259
+
260
+ result_text << ""
261
+ end
262
+
263
+ # Add SimpleRPC common options
264
+ def self.add_simplerpc_options(parser, options)
265
+ parser.separator ""
266
+ parser.separator "RPC Options"
267
+
268
+ # add SimpleRPC specific options to all clients that use our library
269
+ parser.on('--np', '--no-progress', 'Do not show the progress bar') do |v|
270
+ options[:progress_bar] = false
271
+ end
272
+
273
+ parser.on('--one', '-1', 'Send request to only one discovered nodes') do |v|
274
+ options[:mcollective_limit_targets] = 1
275
+ end
276
+
277
+ parser.on('--batch SIZE', 'Do requests in batches') do |v|
278
+ # validate batch string. Is it x% where x > 0 or is it an integer
279
+ if ((v =~ /^(\d+)%$/ && Integer($1) != 0) || v =~ /^(\d+)$/)
280
+ options[:batch_size] = v
281
+ else
282
+ raise(::OptionParser::InvalidArgument.new(v))
283
+ end
284
+ end
285
+
286
+ parser.on('--batch-sleep SECONDS', Float, 'Sleep time between batches') do |v|
287
+ options[:batch_sleep_time] = v
288
+ end
289
+
290
+ parser.on('--limit-seed NUMBER', Integer, 'Seed value for deterministic random batching') do |v|
291
+ options[:limit_seed] = v
292
+ end
293
+
294
+ parser.on('--limit-nodes COUNT', '--ln', '--limit', 'Send request to only a subset of nodes, can be a percentage') do |v|
295
+ raise "Invalid limit specified: #{v} valid limits are /^\d+%*$/" unless v =~ /^\d+%*$/
296
+
297
+ if v =~ /^\d+$/
298
+ options[:mcollective_limit_targets] = v.to_i
299
+ else
300
+ options[:mcollective_limit_targets] = v
301
+ end
302
+ end
303
+
304
+ parser.on('--json', '-j', 'Produce JSON output') do |v|
305
+ options[:progress_bar] = false
306
+ options[:output_format] = :json
307
+ end
308
+
309
+ parser.on('--display MODE', 'Influence how results are displayed. One of ok, all or failed') do |v|
310
+ if v == "all"
311
+ options[:force_display_mode] = :always
312
+ else
313
+ options[:force_display_mode] = v.intern
314
+ end
315
+
316
+ raise "--display has to be one of 'ok', 'all' or 'failed'" unless [:ok, :failed, :always].include?(options[:force_display_mode])
317
+ end
318
+ end
319
+ end
320
+ end
321
+ end
@@ -0,0 +1,63 @@
1
+ module MCollective
2
+ module RPC
3
+ # Class that shows a progress bar, currently only supports a twirling
4
+ # progress bar.
5
+ #
6
+ # You can specify a size for the progress bar if you want if you dont
7
+ # it will use the helper functions to figure out terminal dimensions
8
+ # and draw an appropriately sized bar
9
+ #
10
+ # p = Progress.new
11
+ # 100.times {|i| print p.twirl(i+1, 100) + "\r"};puts
12
+ #
13
+ # * [ ==================================================> ] 100 / 100
14
+ class Progress
15
+ def initialize(size=nil)
16
+ @twirl = ['|', '/', '-', "\\", '|', '/', '-', "\\"]
17
+ @twirldex = 0
18
+
19
+ if size
20
+ @size = size
21
+ else
22
+ cols = Util.terminal_dimensions[0] - 22
23
+
24
+ # Defaults back to old behavior if it
25
+ # couldn't figure out the size or if
26
+ # its more than 60 wide
27
+ if cols <= 0
28
+ @size = 0
29
+ elsif cols > 60
30
+ @size = 60
31
+ else
32
+ @size = cols
33
+ end
34
+ end
35
+ end
36
+
37
+ def twirl(current, total)
38
+ # if the size is negative there is just not enough
39
+ # space on the terminal, return a simpler version
40
+ return "\r#{current} / #{total}" if @size == 0
41
+
42
+ if current == total
43
+ txt = "\r %s [ " % Util.colorize(:green, "*")
44
+ else
45
+ txt = "\r %s [ " % Util.colorize(:red, @twirl[@twirldex])
46
+ end
47
+
48
+ dashes = ((current.to_f / total) * @size).round
49
+
50
+ dashes.times { txt << "=" }
51
+ txt << ">"
52
+
53
+ (@size - dashes).times { txt << " " }
54
+
55
+ txt << " ] #{current} / #{total}"
56
+
57
+ @twirldex == 7 ? @twirldex = 0 : @twirldex += 1
58
+
59
+ return txt
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,87 @@
1
+ module MCollective
2
+ module RPC
3
+ # Simple class to manage compliant replies to MCollective::RPC
4
+ class Reply
5
+ attr_accessor :statuscode, :statusmsg, :data
6
+
7
+ def initialize(action, ddl)
8
+ @data = {}
9
+ @statuscode = 0
10
+ @statusmsg = "OK"
11
+ @ddl = ddl
12
+ @action = action
13
+
14
+ begin
15
+ initialize_data
16
+ rescue Exception => e
17
+ Log.warn("Could not pre-populate reply data from the DDL: %s: %s" % [e.class, e.to_s ])
18
+ end
19
+ end
20
+
21
+ def initialize_data
22
+ unless @ddl.actions.include?(@action)
23
+ raise "No action '%s' defined for agent '%s' in the DDL" % [@action, @ddl.pluginname]
24
+ end
25
+
26
+ interface = @ddl.action_interface(@action)
27
+
28
+ interface[:output].keys.each do |output|
29
+ # must deep clone this data to avoid accidental updates of the DDL in cases where the
30
+ # default is for example a string and someone does << on it
31
+ @data[output] = Marshal.load(Marshal.dump(interface[:output][output].fetch(:default, nil)))
32
+ end
33
+ end
34
+
35
+ # Helper to fill in statusmsg and code on failure
36
+ def fail(msg, code=1)
37
+ @statusmsg = msg
38
+ @statuscode = code
39
+ end
40
+
41
+ # Helper that fills in statusmsg and code but also raises an appropriate error
42
+ def fail!(msg, code=1)
43
+ @statusmsg = msg
44
+ @statuscode = code
45
+
46
+ case code
47
+ when 1
48
+ raise RPCAborted, msg
49
+
50
+ when 2
51
+ raise UnknownRPCAction, msg
52
+
53
+ when 3
54
+ raise MissingRPCData, msg
55
+
56
+ when 4
57
+ raise InvalidRPCData, msg
58
+
59
+ else
60
+ raise UnknownRPCError, msg
61
+ end
62
+ end
63
+
64
+ # Write to the data hash
65
+ def []=(key, val)
66
+ @data[key] = val
67
+ end
68
+
69
+ # Read from the data hash
70
+ def [](key)
71
+ @data[key]
72
+ end
73
+
74
+ def fetch(key, default)
75
+ @data.fetch(key, default)
76
+ end
77
+
78
+ # Returns a compliant Hash of the reply that should be sent
79
+ # over the middleware
80
+ def to_hash
81
+ return {:statuscode => @statuscode,
82
+ :statusmsg => @statusmsg,
83
+ :data => @data}
84
+ end
85
+ end
86
+ end
87
+ end