mcollective-client 1.3.3

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 (103) hide show
  1. data/bin/mc-call-agent +54 -0
  2. data/bin/mco +27 -0
  3. data/lib/mcollective.rb +70 -0
  4. data/lib/mcollective/agents.rb +160 -0
  5. data/lib/mcollective/application.rb +354 -0
  6. data/lib/mcollective/applications.rb +145 -0
  7. data/lib/mcollective/client.rb +292 -0
  8. data/lib/mcollective/config.rb +202 -0
  9. data/lib/mcollective/connector.rb +18 -0
  10. data/lib/mcollective/connector/base.rb +24 -0
  11. data/lib/mcollective/facts.rb +39 -0
  12. data/lib/mcollective/facts/base.rb +86 -0
  13. data/lib/mcollective/log.rb +103 -0
  14. data/lib/mcollective/logger.rb +5 -0
  15. data/lib/mcollective/logger/base.rb +73 -0
  16. data/lib/mcollective/logger/console_logger.rb +61 -0
  17. data/lib/mcollective/logger/file_logger.rb +46 -0
  18. data/lib/mcollective/logger/syslog_logger.rb +53 -0
  19. data/lib/mcollective/matcher.rb +16 -0
  20. data/lib/mcollective/matcher/parser.rb +93 -0
  21. data/lib/mcollective/matcher/scanner.rb +123 -0
  22. data/lib/mcollective/message.rb +201 -0
  23. data/lib/mcollective/monkey_patches.rb +104 -0
  24. data/lib/mcollective/optionparser.rb +164 -0
  25. data/lib/mcollective/pluginmanager.rb +180 -0
  26. data/lib/mcollective/pluginpackager.rb +26 -0
  27. data/lib/mcollective/pluginpackager/agent_definition.rb +79 -0
  28. data/lib/mcollective/pluginpackager/standard_definition.rb +59 -0
  29. data/lib/mcollective/registration.rb +16 -0
  30. data/lib/mcollective/registration/base.rb +75 -0
  31. data/lib/mcollective/rpc.rb +188 -0
  32. data/lib/mcollective/rpc/actionrunner.rb +142 -0
  33. data/lib/mcollective/rpc/agent.rb +441 -0
  34. data/lib/mcollective/rpc/audit.rb +38 -0
  35. data/lib/mcollective/rpc/client.rb +793 -0
  36. data/lib/mcollective/rpc/ddl.rb +258 -0
  37. data/lib/mcollective/rpc/helpers.rb +339 -0
  38. data/lib/mcollective/rpc/progress.rb +63 -0
  39. data/lib/mcollective/rpc/reply.rb +61 -0
  40. data/lib/mcollective/rpc/request.rb +51 -0
  41. data/lib/mcollective/rpc/result.rb +41 -0
  42. data/lib/mcollective/rpc/stats.rb +185 -0
  43. data/lib/mcollective/runnerstats.rb +90 -0
  44. data/lib/mcollective/security.rb +26 -0
  45. data/lib/mcollective/security/base.rb +237 -0
  46. data/lib/mcollective/shell.rb +87 -0
  47. data/lib/mcollective/ssl.rb +246 -0
  48. data/lib/mcollective/unix_daemon.rb +37 -0
  49. data/lib/mcollective/util.rb +274 -0
  50. data/lib/mcollective/vendor.rb +41 -0
  51. data/lib/mcollective/vendor/require_vendored.rb +2 -0
  52. data/lib/mcollective/windows_daemon.rb +25 -0
  53. data/spec/Rakefile +16 -0
  54. data/spec/fixtures/application/test.rb +7 -0
  55. data/spec/fixtures/test-cert.pem +15 -0
  56. data/spec/fixtures/test-private.pem +15 -0
  57. data/spec/fixtures/test-public.pem +6 -0
  58. data/spec/monkey_patches/instance_variable_defined.rb +7 -0
  59. data/spec/spec.opts +1 -0
  60. data/spec/spec_helper.rb +25 -0
  61. data/spec/unit/agents_spec.rb +280 -0
  62. data/spec/unit/application_spec.rb +636 -0
  63. data/spec/unit/applications_spec.rb +155 -0
  64. data/spec/unit/array.rb +30 -0
  65. data/spec/unit/config_spec.rb +148 -0
  66. data/spec/unit/facts/base_spec.rb +118 -0
  67. data/spec/unit/facts_spec.rb +39 -0
  68. data/spec/unit/log_spec.rb +71 -0
  69. data/spec/unit/logger/base_spec.rb +110 -0
  70. data/spec/unit/logger/syslog_logger_spec.rb +86 -0
  71. data/spec/unit/matcher/parser_spec.rb +106 -0
  72. data/spec/unit/matcher/scanner_spec.rb +71 -0
  73. data/spec/unit/message_spec.rb +401 -0
  74. data/spec/unit/optionparser_spec.rb +113 -0
  75. data/spec/unit/pluginmanager_spec.rb +173 -0
  76. data/spec/unit/pluginpackager/agent_definition_spec.rb +130 -0
  77. data/spec/unit/pluginpackager/standard_definition_spec.rb +75 -0
  78. data/spec/unit/plugins/mcollective/connector/activemq_spec.rb +533 -0
  79. data/spec/unit/plugins/mcollective/connector/stomp/eventlogger_spec.rb +34 -0
  80. data/spec/unit/plugins/mcollective/connector/stomp_spec.rb +417 -0
  81. data/spec/unit/plugins/mcollective/packagers/ospackage_spec.rb +229 -0
  82. data/spec/unit/plugins/mcollective/security/psk_spec.rb +156 -0
  83. data/spec/unit/registration/base_spec.rb +77 -0
  84. data/spec/unit/rpc/actionrunner_spec.rb +213 -0
  85. data/spec/unit/rpc/agent_spec.rb +155 -0
  86. data/spec/unit/rpc/client_spec.rb +523 -0
  87. data/spec/unit/rpc/ddl_spec.rb +388 -0
  88. data/spec/unit/rpc/helpers_spec.rb +55 -0
  89. data/spec/unit/rpc/reply_spec.rb +143 -0
  90. data/spec/unit/rpc/request_spec.rb +115 -0
  91. data/spec/unit/rpc/result_spec.rb +66 -0
  92. data/spec/unit/rpc/stats_spec.rb +288 -0
  93. data/spec/unit/runnerstats_spec.rb +40 -0
  94. data/spec/unit/security/base_spec.rb +279 -0
  95. data/spec/unit/shell_spec.rb +144 -0
  96. data/spec/unit/ssl_spec.rb +244 -0
  97. data/spec/unit/symbol.rb +11 -0
  98. data/spec/unit/unix_daemon.rb +41 -0
  99. data/spec/unit/util_spec.rb +342 -0
  100. data/spec/unit/vendor_spec.rb +34 -0
  101. data/spec/unit/windows_daemon.rb +43 -0
  102. data/spec/windows_spec.opts +1 -0
  103. metadata +242 -0
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env rspec
2
+
3
+ require 'spec_helper'
4
+
5
+ module MCollective
6
+ describe RunnerStats do
7
+ before do
8
+ Agents.stubs(:agentlist).returns("agents")
9
+ Time.stubs(:now).returns(Time.at(0))
10
+
11
+ @stats = RunnerStats.new
12
+
13
+ logger = mock
14
+ logger.stubs(:log)
15
+ logger.stubs(:start)
16
+ Log.configure(logger)
17
+ end
18
+
19
+ describe "#to_hash" do
20
+ it "should return the correct data" do
21
+ @stats.to_hash.keys.sort.should == [:stats, :threads, :pid, :times, :agents].sort
22
+
23
+ @stats.to_hash[:stats].should == {:validated => 0, :unvalidated => 0, :passed => 0, :filtered => 0,
24
+ :starttime => 0, :total => 0, :ttlexpired => 0, :replies => 0}
25
+
26
+ @stats.to_hash[:agents].should == "agents"
27
+ end
28
+ end
29
+
30
+ [[:ttlexpired, :ttlexpired], [:passed, :passed], [:filtered, :filtered],
31
+ [:validated, :validated], [:received, :total], [:sent, :replies]].each do |tst|
32
+ describe "##{tst.first}" do
33
+ it "should increment #{tst.first}" do
34
+ @stats.send(tst.first)
35
+ @stats.to_hash[:stats][tst.last].should == 1
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,279 @@
1
+ #!/usr/bin/env rspec
2
+
3
+ require 'spec_helper'
4
+
5
+ module MCollective
6
+ module Security
7
+ describe Base do
8
+ before do
9
+ @config = mock("config")
10
+ @config.stubs(:identity).returns("test")
11
+ @config.stubs(:configured).returns(true)
12
+ @config.stubs(:topicsep).returns(".")
13
+ @config.stubs(:topicprefix).returns("/topic/")
14
+
15
+ @stats = mock("stats")
16
+
17
+ @time = Time.now
18
+ ::Time.stubs(:now).returns(@time)
19
+
20
+ MCollective::Log.stubs(:debug).returns(true)
21
+
22
+ MCollective::PluginManager << {:type => "global_stats", :class => @stats}
23
+ MCollective::Config.stubs("instance").returns(@config)
24
+ MCollective::Util.stubs("empty_filter?").returns(false)
25
+
26
+ @plugin = Base.new
27
+ end
28
+
29
+ describe "#should_process_msg?" do
30
+ it "should correctly validate messages" do
31
+ m = mock
32
+ m.stubs(:expected_msgid).returns("rspec")
33
+
34
+ @plugin.should_process_msg?(m, "rspec").should == true
35
+
36
+ expect {
37
+ @plugin.should_process_msg?(m, "fail").should == true
38
+ }.to raise_error MsgDoesNotMatchRequestID
39
+ end
40
+
41
+ it "should not test messages without expected_msgid" do
42
+ m = mock
43
+ m.stubs(:expected_msgid).returns(nil)
44
+
45
+ @plugin.should_process_msg?(m, "rspec").should == true
46
+ end
47
+ end
48
+
49
+ describe "#validate_filter?" do
50
+ it "should pass on empty filter" do
51
+ MCollective::Util.stubs("empty_filter?").returns(true)
52
+
53
+ @stats.stubs(:passed).once
54
+ @stats.stubs(:filtered).never
55
+ @stats.stubs(:passed).never
56
+
57
+ MCollective::Log.expects(:debug).with("Message passed the filter checks").once
58
+
59
+ @plugin.validate_filter?({}).should == true
60
+ end
61
+
62
+ it "should pass for known classes" do
63
+ MCollective::Util.stubs("has_cf_class?").with("foo").returns(true)
64
+
65
+ @stats.stubs(:passed).once
66
+ @stats.stubs(:filtered).never
67
+
68
+ MCollective::Log.expects(:debug).with("Message passed the filter checks").once
69
+ MCollective::Log.expects(:debug).with("Passing based on configuration management class foo").once
70
+
71
+ @plugin.validate_filter?({"cf_class" => ["foo"]}).should == true
72
+ end
73
+
74
+ it "should fail for unknown classes" do
75
+ MCollective::Util.stubs("has_cf_class?").with("foo").returns(false)
76
+
77
+ @stats.stubs(:filtered).once
78
+ @stats.stubs(:passed).never
79
+
80
+ MCollective::Log.expects(:debug).with("Message failed the filter checks").once
81
+ MCollective::Log.expects(:debug).with("Failing based on configuration management class foo").once
82
+
83
+ @plugin.validate_filter?({"cf_class" => ["foo"]}).should == false
84
+ end
85
+
86
+ it "should pass for known agents" do
87
+ MCollective::Util.stubs("has_agent?").with("foo").returns(true)
88
+
89
+ @stats.stubs(:passed).once
90
+ @stats.stubs(:filtered).never
91
+
92
+ MCollective::Log.expects(:debug).with("Message passed the filter checks").once
93
+ MCollective::Log.expects(:debug).with("Passing based on agent foo").once
94
+
95
+ @plugin.validate_filter?({"agent" => ["foo"]}).should == true
96
+ end
97
+
98
+ it "should fail for unknown agents" do
99
+ MCollective::Util.stubs("has_agent?").with("foo").returns(false)
100
+
101
+ @stats.stubs(:filtered).once
102
+ @stats.stubs(:passed).never
103
+
104
+ MCollective::Log.expects(:debug).with("Message failed the filter checks").once
105
+ MCollective::Log.expects(:debug).with("Failing based on agent foo").once
106
+
107
+ @plugin.validate_filter?({"agent" => ["foo"]}).should == false
108
+ end
109
+
110
+ it "should pass for known facts" do
111
+ MCollective::Util.stubs("has_fact?").with("fact", "value", "operator").returns(true)
112
+
113
+ @stats.stubs(:passed).once
114
+ @stats.stubs(:filtered).never
115
+
116
+ MCollective::Log.expects(:debug).with("Message passed the filter checks").once
117
+ MCollective::Log.expects(:debug).with("Passing based on fact fact operator value").once
118
+
119
+ @plugin.validate_filter?({"fact" => [{:fact => "fact", :operator => "operator", :value => "value"}]}).should == true
120
+ end
121
+
122
+ it "should fail for unknown facts" do
123
+ MCollective::Util.stubs("has_fact?").with("fact", "value", "operator").returns(false)
124
+
125
+ @stats.stubs(:filtered).once
126
+ @stats.stubs(:passed).never
127
+
128
+ MCollective::Log.expects(:debug).with("Message failed the filter checks").once
129
+ MCollective::Log.expects(:debug).with("Failing based on fact fact operator value").once
130
+
131
+ @plugin.validate_filter?({"fact" => [{:fact => "fact", :operator => "operator", :value => "value"}]}).should == false
132
+ end
133
+
134
+ it "should pass for known identity" do
135
+ MCollective::Util.stubs("has_identity?").with("test").returns(true)
136
+
137
+ @stats.stubs(:passed).once
138
+ @stats.stubs(:filtered).never
139
+
140
+ MCollective::Log.expects(:debug).with("Message passed the filter checks").once
141
+ MCollective::Log.expects(:debug).with("Passing based on identity").once
142
+
143
+ @plugin.validate_filter?({"identity" => ["test"]}).should == true
144
+ end
145
+
146
+ it "should fail for known identity" do
147
+ MCollective::Util.stubs("has_identity?").with("test").returns(false)
148
+
149
+ @stats.stubs(:passed).never
150
+ @stats.stubs(:filtered).once
151
+
152
+ MCollective::Log.expects(:debug).with("Message failed the filter checks").once
153
+ MCollective::Log.expects(:debug).with("Failed based on identity").once
154
+
155
+ @plugin.validate_filter?({"identity" => ["test"]}).should == false
156
+ end
157
+
158
+ it "should treat multiple identity filters correctly" do
159
+ MCollective::Util.stubs("has_identity?").with("foo").returns(false)
160
+ MCollective::Util.stubs("has_identity?").with("bar").returns(true)
161
+
162
+ @stats.stubs(:passed).once
163
+ @stats.stubs(:filtered).never
164
+
165
+ MCollective::Log.expects(:debug).with("Message passed the filter checks").once
166
+ MCollective::Log.expects(:debug).with("Passing based on identity").once
167
+
168
+ @plugin.validate_filter?({"identity" => ["foo", "bar"]}).should == true
169
+ end
170
+
171
+ it "should fail if no identity matches are found" do
172
+ MCollective::Util.stubs("has_identity?").with("foo").returns(false)
173
+ MCollective::Util.stubs("has_identity?").with("bar").returns(false)
174
+
175
+ @stats.stubs(:passed).never
176
+ @stats.stubs(:filtered).once
177
+
178
+ MCollective::Log.expects(:debug).with("Message failed the filter checks").once
179
+ MCollective::Log.expects(:debug).with("Failed based on identity").once
180
+
181
+ @plugin.validate_filter?({"identity" => ["foo", "bar"]}).should == false
182
+ end
183
+ end
184
+
185
+ describe "#create_reply" do
186
+ it "should return correct data" do
187
+ expected = {:senderid => "test",
188
+ :requestid => "reqid",
189
+ :senderagent => "agent",
190
+ :msgtime => @time.to_i,
191
+ :body => "body"}
192
+
193
+ @plugin.create_reply("reqid", "agent", "body").should == expected
194
+ end
195
+ end
196
+
197
+ describe "#create_request" do
198
+ it "should return correct data" do
199
+ expected = {:body => "body",
200
+ :senderid => "test",
201
+ :requestid => "reqid",
202
+ :callerid => "uid=#{Process.uid}",
203
+ :agent => "discovery",
204
+ :collective => "mcollective",
205
+ :filter => "filter",
206
+ :ttl => 20,
207
+ :msgtime => @time.to_i}
208
+
209
+ @plugin.create_request("reqid", "filter", "body", :server, "discovery", "mcollective", 20).should == expected
210
+ end
211
+
212
+ it "should set the callerid when appropriate" do
213
+ expected = {:body => "body",
214
+ :senderid => "test",
215
+ :requestid => "reqid",
216
+ :agent => "discovery",
217
+ :collective => "mcollective",
218
+ :filter => "filter",
219
+ :callerid => "callerid",
220
+ :ttl => 60,
221
+ :msgtime => @time.to_i}
222
+
223
+ @plugin.stubs(:callerid).returns("callerid")
224
+ @plugin.create_request("reqid", "filter", "body", :client, "discovery", "mcollective").should == expected
225
+ end
226
+ end
227
+
228
+ describe "#valid_callerid?" do
229
+ it "should not pass invalid callerids" do
230
+ @plugin.valid_callerid?("foo-bar").should == false
231
+ @plugin.valid_callerid?("foo=bar=baz").should == false
232
+ @plugin.valid_callerid?('foo=bar\baz').should == false
233
+ @plugin.valid_callerid?("foo=bar/baz").should == false
234
+ @plugin.valid_callerid?("foo=bar|baz").should == false
235
+ end
236
+
237
+ it "should pass valid callerids" do
238
+ @plugin.valid_callerid?("cert=foo-bar").should == true
239
+ @plugin.valid_callerid?("uid=foo.bar").should == true
240
+ @plugin.valid_callerid?("uid=foo.bar.123").should == true
241
+ end
242
+ end
243
+
244
+ describe "#callerid" do
245
+ it "should return a unix UID based callerid" do
246
+ @plugin.callerid.should == "uid=#{Process.uid}"
247
+ end
248
+ end
249
+
250
+ describe "#validrequest?" do
251
+ it "should log an error when not implimented" do
252
+ MCollective::Log.expects(:error).with("validrequest? is not implimented in MCollective::Security::Base")
253
+ @plugin.validrequest?(nil)
254
+ end
255
+ end
256
+
257
+ describe "#encoderequest" do
258
+ it "should log an error when not implimented" do
259
+ MCollective::Log.expects(:error).with("encoderequest is not implimented in MCollective::Security::Base")
260
+ @plugin.encoderequest(nil, nil, nil)
261
+ end
262
+ end
263
+
264
+ describe "#encodereply" do
265
+ it "should log an error when not implimented" do
266
+ MCollective::Log.expects(:error).with("encodereply is not implimented in MCollective::Security::Base")
267
+ @plugin.encodereply(nil, nil, nil)
268
+ end
269
+ end
270
+
271
+ describe "#decodemsg" do
272
+ it "should log an error when not implimented" do
273
+ MCollective::Log.expects(:error).with("decodemsg is not implimented in MCollective::Security::Base")
274
+ @plugin.decodemsg(nil)
275
+ end
276
+ end
277
+ end
278
+ end
279
+ end
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env rspec
2
+
3
+ require 'spec_helper'
4
+
5
+ module MCollective
6
+ describe Shell do
7
+ describe "#initialize" do
8
+ it "should set locale by default" do
9
+ s = Shell.new("date")
10
+ s.environment.should == {"LC_ALL" => "C"}
11
+ end
12
+
13
+ it "should merge environment and keep locale" do
14
+ s = Shell.new("date", :environment => {"foo" => "bar"})
15
+ s.environment.should == {"LC_ALL" => "C", "foo" => "bar"}
16
+ end
17
+
18
+ it "should allow locale to be overridden" do
19
+ s = Shell.new("date", :environment => {"LC_ALL" => "TEST", "foo" => "bar"})
20
+ s.environment.should == {"LC_ALL" => "TEST", "foo" => "bar"}
21
+ end
22
+
23
+ it "should set no environment when given nil" do
24
+ s = Shell.new("date", :environment => nil)
25
+ s.environment.should == {}
26
+ end
27
+
28
+ it "should save the command" do
29
+ s = Shell.new("date")
30
+ s.command.should == "date"
31
+ end
32
+
33
+ it "should check the cwd exist" do
34
+ expect {
35
+ s = Shell.new("date", :cwd => "/nonexistant")
36
+ }.to raise_error("Directory /nonexistant does not exist")
37
+ end
38
+
39
+ it "should warn of illegal stdin" do
40
+ expect {
41
+ s = Shell.new("date", :stdin => nil)
42
+ }.to raise_error("stdin should be a String")
43
+ end
44
+
45
+ it "should warn of illegal stdout" do
46
+ expect {
47
+ s = Shell.new("date", :stdout => nil)
48
+ }.to raise_error("stdout should support <<")
49
+ end
50
+
51
+ it "should warn of illegal stderr" do
52
+ expect {
53
+ s = Shell.new("date", :stderr => nil)
54
+ }.to raise_error("stderr should support <<")
55
+ end
56
+
57
+ it "should set stdout" do
58
+ s = Shell.new("date", :stdout => "stdout")
59
+ s.stdout.should == "stdout"
60
+ end
61
+
62
+ it "should set stderr" do
63
+ s = Shell.new("date", :stderr => "stderr")
64
+ s.stderr.should == "stderr"
65
+ end
66
+
67
+ it "should set stdin" do
68
+ s = Shell.new("date", :stdin => "hello world")
69
+ s.stdin.should == "hello world"
70
+ end
71
+ end
72
+
73
+ describe "#runcommand" do
74
+ it "should run the command" do
75
+ Shell.any_instance.stubs("systemu").returns(true).once.with("date", "stdout" => '', "stderr" => '', "env" => {"LC_ALL" => "C"}, 'cwd' => Dir.tmpdir)
76
+ s = Shell.new("date")
77
+ s.runcommand
78
+ end
79
+
80
+ it "should set stdin, stdout and status" do
81
+ s = Shell.new('ruby -e "STDERR.puts \"stderr\"; STDOUT.puts \"stdout\""')
82
+ s.runcommand
83
+ s.stdout.should == "stdout\n"
84
+ s.stderr.should == "stderr\n"
85
+ s.status.exitstatus.should == 0
86
+ end
87
+
88
+ it "should report correct exitcode" do
89
+ s = Shell.new('ruby -e "exit 1"')
90
+ s.runcommand
91
+
92
+ s.status.exitstatus.should == 1
93
+ end
94
+
95
+ it "shold have correct environment" do
96
+ s = Shell.new('ruby -e "puts ENV[\'LC_ALL\'];puts ENV[\'foo\'];"', :environment => {"foo" => "bar"})
97
+ s.runcommand
98
+ s.stdout.should == "C\nbar\n"
99
+ end
100
+
101
+ it "should save stdout in custom stdout variable" do
102
+ out = "STDOUT"
103
+
104
+ s = Shell.new('echo foo', :stdout => out)
105
+ s.runcommand
106
+
107
+ s.stdout.should == "STDOUTfoo\n"
108
+ out.should == "STDOUTfoo\n"
109
+ end
110
+
111
+ it "should save stderr in custom stderr variable" do
112
+ out = "STDERR"
113
+
114
+ s = Shell.new('ruby -e "STDERR.puts \"foo\""', :stderr => out)
115
+ s.runcommand
116
+
117
+ s.stderr.should == "STDERRfoo\n"
118
+ out.should == "STDERRfoo\n"
119
+ end
120
+
121
+ it "should run in the correct cwd" do
122
+ s = Shell.new('ruby -e "puts Dir.pwd"', :cwd => Dir.tmpdir)
123
+
124
+ s.runcommand
125
+
126
+ s.stdout.should == "#{Dir.tmpdir}\n"
127
+ end
128
+
129
+ it "should send the stdin" do
130
+ s = Shell.new('ruby -e "puts STDIN.gets"', :stdin => "hello world")
131
+ s.runcommand
132
+
133
+ s.stdout.should == "hello world\n"
134
+ end
135
+
136
+ it "should support multiple lines of stdin" do
137
+ s = Shell.new('ruby -e "puts STDIN.gets;puts;puts STDIN.gets"', :stdin => "first line\n2nd line")
138
+ s.runcommand
139
+
140
+ s.stdout.should == "first line\n\n2nd line\n"
141
+ end
142
+ end
143
+ end
144
+ end