minestrone 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.
- checksums.yaml +7 -0
- data/.github/workflows/main.yml +32 -0
- data/.gitignore +5 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +22 -0
- data/README.md +35 -0
- data/Rakefile +10 -0
- data/bin/capify +89 -0
- data/bin/min +5 -0
- data/docs/lib-codebase-map.md +162 -0
- data/docs/lib-dependency-graph.svg +129 -0
- data/lib/minestrone/callback.rb +45 -0
- data/lib/minestrone/cli/help.rb +131 -0
- data/lib/minestrone/cli/help.txt +72 -0
- data/lib/minestrone/cli/options.rb +232 -0
- data/lib/minestrone/cli.rb +159 -0
- data/lib/minestrone/command.rb +177 -0
- data/lib/minestrone/configuration/actions/file_transfer.rb +53 -0
- data/lib/minestrone/configuration/actions/inspect.rb +46 -0
- data/lib/minestrone/configuration/actions/invocation.rb +202 -0
- data/lib/minestrone/configuration/alias_task.rb +29 -0
- data/lib/minestrone/configuration/callbacks.rb +129 -0
- data/lib/minestrone/configuration/connections.rb +66 -0
- data/lib/minestrone/configuration/execution.rb +139 -0
- data/lib/minestrone/configuration/loading.rb +207 -0
- data/lib/minestrone/configuration/log_formatters.rb +75 -0
- data/lib/minestrone/configuration/namespaces.rb +225 -0
- data/lib/minestrone/configuration/servers.rb +70 -0
- data/lib/minestrone/configuration/variables.rb +115 -0
- data/lib/minestrone/configuration.rb +69 -0
- data/lib/minestrone/errors.rb +17 -0
- data/lib/minestrone/ext/string.rb +7 -0
- data/lib/minestrone/extensions.rb +56 -0
- data/lib/minestrone/logger.rb +171 -0
- data/lib/minestrone/processable.rb +50 -0
- data/lib/minestrone/recipes/deploy/assets.rb +194 -0
- data/lib/minestrone/recipes/deploy/bundler.rb +81 -0
- data/lib/minestrone/recipes/deploy/dependencies.rb +44 -0
- data/lib/minestrone/recipes/deploy/local_dependency.rb +45 -0
- data/lib/minestrone/recipes/deploy/remote_dependency.rb +119 -0
- data/lib/minestrone/recipes/deploy/scm/base.rb +204 -0
- data/lib/minestrone/recipes/deploy/scm/git.rb +284 -0
- data/lib/minestrone/recipes/deploy/scm/none.rb +54 -0
- data/lib/minestrone/recipes/deploy/scm.rb +22 -0
- data/lib/minestrone/recipes/deploy/strategy/base.rb +87 -0
- data/lib/minestrone/recipes/deploy/strategy/copy.rb +353 -0
- data/lib/minestrone/recipes/deploy/strategy/remote_cache.rb +80 -0
- data/lib/minestrone/recipes/deploy/strategy.rb +22 -0
- data/lib/minestrone/recipes/deploy.rb +639 -0
- data/lib/minestrone/recipes/standard.rb +23 -0
- data/lib/minestrone/recipes/templates/maintenance.rhtml +53 -0
- data/lib/minestrone/server_definition.rb +56 -0
- data/lib/minestrone/ssh.rb +81 -0
- data/lib/minestrone/task_definition.rb +82 -0
- data/lib/minestrone/transfer.rb +205 -0
- data/lib/minestrone/version.rb +11 -0
- data/lib/minestrone.rb +3 -0
- data/minestrone.gemspec +32 -0
- data/test/cli/execute_test.rb +130 -0
- data/test/cli/help_test.rb +178 -0
- data/test/cli/options_test.rb +315 -0
- data/test/cli/ui_test.rb +26 -0
- data/test/cli_test.rb +17 -0
- data/test/command_test.rb +305 -0
- data/test/configuration/actions/file_transfer_test.rb +61 -0
- data/test/configuration/actions/inspect_test.rb +76 -0
- data/test/configuration/actions/invocation_test.rb +258 -0
- data/test/configuration/alias_task_test.rb +110 -0
- data/test/configuration/callbacks_test.rb +201 -0
- data/test/configuration/connections_test.rb +192 -0
- data/test/configuration/execution_test.rb +176 -0
- data/test/configuration/loading_test.rb +149 -0
- data/test/configuration/namespace_dsl_test.rb +325 -0
- data/test/configuration/servers_test.rb +100 -0
- data/test/configuration/variables_test.rb +191 -0
- data/test/configuration_test.rb +77 -0
- data/test/deploy/local_dependency_test.rb +61 -0
- data/test/deploy/remote_dependency_test.rb +146 -0
- data/test/deploy/scm/base_test.rb +55 -0
- data/test/deploy/scm/git_test.rb +260 -0
- data/test/deploy/scm/none_test.rb +26 -0
- data/test/deploy/strategy/copy_test.rb +360 -0
- data/test/extensions_test.rb +69 -0
- data/test/fixtures/cli_integration.rb +5 -0
- data/test/fixtures/config.rb +4 -0
- data/test/fixtures/custom.rb +3 -0
- data/test/logger_formatting_test.rb +149 -0
- data/test/logger_test.rb +134 -0
- data/test/recipes_test.rb +26 -0
- data/test/server_definition_test.rb +121 -0
- data/test/ssh_test.rb +99 -0
- data/test/task_definition_test.rb +117 -0
- data/test/transfer_test.rb +172 -0
- data/test/utils.rb +28 -0
- data/test/version_test.rb +11 -0
- metadata +258 -0
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
require "utils"
|
|
2
|
+
require 'minestrone/command'
|
|
3
|
+
require 'minestrone/configuration'
|
|
4
|
+
|
|
5
|
+
class CommandTest < Test::Unit::TestCase
|
|
6
|
+
class FakeChannel < Hash
|
|
7
|
+
def exec(*); end
|
|
8
|
+
def send_data(*); end
|
|
9
|
+
def eof!; end
|
|
10
|
+
def close; end
|
|
11
|
+
def request_pty(*); end
|
|
12
|
+
def on_data(*); end
|
|
13
|
+
def on_extended_data(*); end
|
|
14
|
+
def on_request(*); end
|
|
15
|
+
def on_close(*); end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def test_command_should_keep_session
|
|
19
|
+
session = mock_session
|
|
20
|
+
assert_equal session, Minestrone::Command.new("ls", session).session
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def test_command_with_newlines_should_be_properly_escaped
|
|
24
|
+
cmd = Minestrone::Command.new("ls\necho", mock_session)
|
|
25
|
+
assert_equal "ls\\\necho", cmd.command
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def test_command_with_crlf_newlines_should_be_properly_escaped
|
|
29
|
+
cmd = Minestrone::Command.new("ls\r\necho", mock_session)
|
|
30
|
+
assert_equal "ls\\\necho", cmd.command
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def test_command_with_pty_should_request_pty_and_register_success_callback
|
|
34
|
+
session = setup_for_extracting_channel_action(:request_pty, true) do |ch|
|
|
35
|
+
ch.expects(:exec).with(%(sh -c 'ls'))
|
|
36
|
+
end
|
|
37
|
+
open_test_channel("ls", session, :pty => true)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def test_command_with_env_key_should_have_environment_constructed_and_prepended
|
|
41
|
+
session = setup_for_extracting_channel_action do |ch|
|
|
42
|
+
ch.expects(:request_pty).never
|
|
43
|
+
ch.expects(:exec).with(%(env FOO=bar sh -c 'ls'))
|
|
44
|
+
end
|
|
45
|
+
open_test_channel("ls", session, :env => { "FOO" => "bar" })
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def test_env_with_symbolic_key_should_be_accepted_as_a_string
|
|
49
|
+
session = setup_for_extracting_channel_action do |ch|
|
|
50
|
+
ch.expects(:exec).with(%(env FOO=bar sh -c 'ls'))
|
|
51
|
+
end
|
|
52
|
+
open_test_channel("ls", session, :env => { :FOO => "bar" })
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def test_env_as_string_should_be_substituted_in_directly
|
|
56
|
+
session = setup_for_extracting_channel_action do |ch|
|
|
57
|
+
ch.expects(:exec).with(%(env HOWDY=there sh -c 'ls'))
|
|
58
|
+
end
|
|
59
|
+
open_test_channel("ls", session, :env => "HOWDY=there")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def test_env_with_symbolic_value_should_be_accepted_as_string
|
|
63
|
+
session = setup_for_extracting_channel_action do |ch|
|
|
64
|
+
ch.expects(:exec).with(%(env FOO=bar sh -c 'ls'))
|
|
65
|
+
end
|
|
66
|
+
open_test_channel("ls", session, :env => { "FOO" => :bar })
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def test_env_value_should_be_escaped
|
|
70
|
+
session = setup_for_extracting_channel_action do |ch|
|
|
71
|
+
ch.expects(:exec).with(%(env FOO=(\\ \\\"bar\\\"\\ ) sh -c 'ls'))
|
|
72
|
+
end
|
|
73
|
+
open_test_channel("ls", session, :env => { "FOO" => '( "bar" )' })
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def test_env_with_multiple_keys_should_chain_the_entries_together
|
|
77
|
+
session = setup_for_extracting_channel_action do |ch|
|
|
78
|
+
ch.expects(:exec).with do |command|
|
|
79
|
+
command =~ /^env / &&
|
|
80
|
+
command =~ /\ba=b\b/ &&
|
|
81
|
+
command =~ /\bc=d\b/ &&
|
|
82
|
+
command =~ /\be=f\b/ &&
|
|
83
|
+
command =~ / sh -c 'ls'$/
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
open_test_channel("ls", session, :env => { :a => :b, :c => :d, :e => :f })
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def test_open_channel_should_set_host_key_on_channel
|
|
90
|
+
channel = nil
|
|
91
|
+
session = setup_for_extracting_channel_action { |ch| channel = ch }
|
|
92
|
+
open_test_channel("ls", session)
|
|
93
|
+
assert_equal "minestrone", channel[:host]
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def test_open_channel_should_set_options_key_on_channel
|
|
97
|
+
channel = nil
|
|
98
|
+
session = setup_for_extracting_channel_action { |ch| channel = ch }
|
|
99
|
+
open_test_channel("ls", session, :data => "here we go")
|
|
100
|
+
assert_equal({ :data => 'here we go' }, channel[:options])
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def test_successful_channel_should_send_command
|
|
104
|
+
session = setup_for_extracting_channel_action do |ch|
|
|
105
|
+
ch.expects(:exec).with(%(sh -c 'ls'))
|
|
106
|
+
end
|
|
107
|
+
open_test_channel("ls", session)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def test_successful_channel_with_shell_option_should_send_command_via_specified_shell
|
|
111
|
+
session = setup_for_extracting_channel_action do |ch|
|
|
112
|
+
ch.expects(:exec).with(%(/bin/bash -c 'ls'))
|
|
113
|
+
end
|
|
114
|
+
open_test_channel("ls", session, :shell => "/bin/bash")
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def test_successful_channel_with_shell_false_should_send_command_without_shell
|
|
118
|
+
session = setup_for_extracting_channel_action do |ch|
|
|
119
|
+
ch.expects(:exec).with(%(echo `hostname`))
|
|
120
|
+
end
|
|
121
|
+
open_test_channel("echo `hostname`", session, :shell => false)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def test_successful_channel_should_send_data_if_data_key_is_present
|
|
125
|
+
session = setup_for_extracting_channel_action do |ch|
|
|
126
|
+
ch.expects(:exec).with(%(sh -c 'ls'))
|
|
127
|
+
ch.expects(:send_data).with("here we go")
|
|
128
|
+
end
|
|
129
|
+
open_test_channel("ls", session, :data => "here we go")
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def test_unsuccessful_pty_request_should_close_channel
|
|
133
|
+
session = setup_for_extracting_channel_action(:request_pty, false) do |ch|
|
|
134
|
+
ch.expects(:close)
|
|
135
|
+
end
|
|
136
|
+
open_test_channel("ls", session, :pty => true)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def test_on_data_should_invoke_callback_as_stdout
|
|
140
|
+
session = setup_for_extracting_channel_action(:on_data, "hello")
|
|
141
|
+
called = false
|
|
142
|
+
open_test_channel("ls", session) do |ch, stream, data|
|
|
143
|
+
called = true
|
|
144
|
+
assert_equal :out, stream
|
|
145
|
+
assert_equal "hello", data
|
|
146
|
+
end
|
|
147
|
+
assert called
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def test_on_extended_data_should_invoke_callback_as_stderr
|
|
151
|
+
session = setup_for_extracting_channel_action(:on_extended_data, 2, "hello")
|
|
152
|
+
called = false
|
|
153
|
+
open_test_channel("ls", session) do |ch, stream, data|
|
|
154
|
+
called = true
|
|
155
|
+
assert_equal :err, stream
|
|
156
|
+
assert_equal "hello", data
|
|
157
|
+
end
|
|
158
|
+
assert called
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def test_on_request_should_record_exit_status
|
|
162
|
+
data = mock(:read_long => 5)
|
|
163
|
+
channel = nil
|
|
164
|
+
session = setup_for_extracting_channel_action([:on_request, "exit-status"], data) { |ch| channel = ch }
|
|
165
|
+
open_test_channel("ls", session)
|
|
166
|
+
assert_equal 5, channel[:status]
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def test_on_request_should_log_exit_signal_if_logger_present
|
|
170
|
+
data = mock(:read_string => "TERM")
|
|
171
|
+
logger = stub_everything
|
|
172
|
+
|
|
173
|
+
session = setup_for_extracting_channel_action([:on_request, "exit-signal"], data)
|
|
174
|
+
logger.expects(:important).with("command received signal TERM", server("minestrone"))
|
|
175
|
+
|
|
176
|
+
open_test_channel("puppet", session, :logger => logger)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def test_on_close_should_set_channel_closed
|
|
180
|
+
channel = nil
|
|
181
|
+
session = setup_for_extracting_channel_action(:on_close) { |ch| channel = ch }
|
|
182
|
+
open_test_channel("ls", session)
|
|
183
|
+
assert channel[:closed]
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def test_stop_should_close_open_channel
|
|
187
|
+
cmd = Minestrone::Command.new("ls", mock_session)
|
|
188
|
+
cmd.send(:open_channel, mock_session(new_channel(false)))
|
|
189
|
+
cmd.stop!
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def test_process_should_return_cleanly_if_channel_has_zero_exit_status
|
|
193
|
+
cmd = Minestrone::Command.new("ls", mock_session(new_channel(true, 0)))
|
|
194
|
+
assert_nothing_raised { cmd.process! }
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def test_process_should_raise_error_if_channel_has_non_zero_exit_status
|
|
198
|
+
cmd = Minestrone::Command.new("ls", mock_session(new_channel(true, 1)))
|
|
199
|
+
assert_raises(Minestrone::CommandError) { cmd.process! }
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def test_command_error_should_include_accessor_with_host
|
|
203
|
+
cmd = Minestrone::Command.new("ls", mock_session(new_channel(true, 1)))
|
|
204
|
+
|
|
205
|
+
begin
|
|
206
|
+
cmd.process!
|
|
207
|
+
flunk "expected an exception to be raised"
|
|
208
|
+
rescue Minestrone::CommandError => e
|
|
209
|
+
assert e.respond_to?(:host)
|
|
210
|
+
assert_equal "minestrone", e.host.to_s
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def test_process_should_loop_until_channel_is_closed
|
|
215
|
+
ch = mock("channel")
|
|
216
|
+
ch.stubs(:to_ary)
|
|
217
|
+
ch.stubs(:[]).with(:closed).returns(false, false, false, true)
|
|
218
|
+
ch.expects(:[]).with(:status).returns(0)
|
|
219
|
+
cmd = Minestrone::Command.new("ls", mock_session(ch))
|
|
220
|
+
assert_nothing_raised do
|
|
221
|
+
cmd.process!
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def test_process_should_instantiate_command_and_process!
|
|
226
|
+
cmd = mock("command", :process! => nil)
|
|
227
|
+
session = mock_session
|
|
228
|
+
Minestrone::Command.expects(:new).with("ls -l", session, {:foo => "bar"}).returns(cmd)
|
|
229
|
+
Minestrone::Command.process("ls -l", session, :foo => "bar")
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def test_process_with_host_placeholder_should_substitute_host_placeholder_with_each_host
|
|
233
|
+
session = setup_for_extracting_channel_action do |ch|
|
|
234
|
+
ch.expects(:exec).with(%(sh -c 'echo minestrone'))
|
|
235
|
+
end
|
|
236
|
+
open_test_channel("echo $CAPISTRANO:HOST$", session)
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def test_process_with_unknown_placeholder_should_not_replace_placeholder
|
|
240
|
+
session = setup_for_extracting_channel_action do |ch|
|
|
241
|
+
ch.expects(:exec).with(%(sh -c 'echo $CAPISTRANO:OTHER$'))
|
|
242
|
+
end
|
|
243
|
+
open_test_channel("echo $CAPISTRANO:OTHER$", session)
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def test_input_stream_closed_when_eof_option_is_true
|
|
247
|
+
channel = nil
|
|
248
|
+
session = setup_for_extracting_channel_action { |ch| channel = ch }
|
|
249
|
+
channel.expects(:eof!)
|
|
250
|
+
open_test_channel("cat", session, :data => "here we go", :eof => true)
|
|
251
|
+
assert_equal({ :data => 'here we go', :eof => true }, channel[:options])
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
private
|
|
255
|
+
|
|
256
|
+
def mock_session(channel = nil)
|
|
257
|
+
stub('session',
|
|
258
|
+
:open_channel => channel,
|
|
259
|
+
:preprocess => true,
|
|
260
|
+
:postprocess => true,
|
|
261
|
+
:listeners => {},
|
|
262
|
+
:xserver => server("minestrone"))
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
class MockChannel < Hash
|
|
266
|
+
def close
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def new_channel(closed, status = nil)
|
|
271
|
+
ch = MockChannel.new
|
|
272
|
+
ch.update({ :closed => closed, :host => "minestrone", :server => server("minestrone") })
|
|
273
|
+
ch[:status] = status if status
|
|
274
|
+
ch.expects(:close) unless closed
|
|
275
|
+
ch
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def setup_for_extracting_channel_action(action = nil, *args)
|
|
279
|
+
s = server("minestrone")
|
|
280
|
+
session = mock("session", :xserver => s)
|
|
281
|
+
|
|
282
|
+
channel = FakeChannel.new
|
|
283
|
+
session.expects(:open_channel).yields(channel)
|
|
284
|
+
|
|
285
|
+
channel.stubs(:on_data)
|
|
286
|
+
channel.stubs(:on_extended_data)
|
|
287
|
+
channel.stubs(:on_request)
|
|
288
|
+
channel.stubs(:on_close)
|
|
289
|
+
|
|
290
|
+
if action
|
|
291
|
+
action = Array(action)
|
|
292
|
+
channel.expects(action.first).with(*action[1..-1]).yields(channel, *args)
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
yield channel if block_given?
|
|
296
|
+
|
|
297
|
+
session
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def open_test_channel(command, session, options = {}, &block)
|
|
301
|
+
cmd = Minestrone::Command.new(command, session, options, &block)
|
|
302
|
+
cmd.send(:open_channel, session)
|
|
303
|
+
cmd
|
|
304
|
+
end
|
|
305
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
require "utils"
|
|
2
|
+
require 'minestrone/configuration/actions/file_transfer'
|
|
3
|
+
|
|
4
|
+
class ConfigurationActionsFileTransferTest < Test::Unit::TestCase
|
|
5
|
+
class MockConfig
|
|
6
|
+
include Minestrone::Configuration::Actions::FileTransfer
|
|
7
|
+
attr_accessor :session, :dry_run
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def setup
|
|
11
|
+
@config = MockConfig.new
|
|
12
|
+
@config.stubs(:logger).returns(stub_everything)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def test_put_should_delegate_to_upload
|
|
16
|
+
@config.expects(:upload).with { |from, to, opts|
|
|
17
|
+
from.string == "some data" && to == "test.txt" && opts == { :mode => 0777 } }
|
|
18
|
+
@config.expects(:run).never
|
|
19
|
+
@config.put("some data", "test.txt", :mode => 0777)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def test_get_should_delegate_to_download
|
|
23
|
+
@config.expects(:download).with("testr.txt", "testl.txt", { :foo => "bar" })
|
|
24
|
+
@config.get("testr.txt", "testl.txt", :foo => "bar")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def test_upload_should_delegate_to_transfer
|
|
28
|
+
@config.expects(:transfer).with(:up, "testl.txt", "testr.txt", { :foo => "bar" })
|
|
29
|
+
@config.upload("testl.txt", "testr.txt", :foo => "bar")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def test_upload_without_mode_should_not_try_to_chmod
|
|
33
|
+
@config.expects(:transfer).with(:up, "testl.txt", "testr.txt", { :foo => "bar" })
|
|
34
|
+
@config.expects(:run).never
|
|
35
|
+
@config.upload("testl.txt", "testr.txt", :foo => "bar")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def test_upload_with_mode_should_try_to_chmod
|
|
39
|
+
@config.expects(:transfer).with(:up, "testl.txt", "testr.txt", { :foo => "bar" })
|
|
40
|
+
@config.expects(:run).with("chmod 775 testr.txt", {:foo => "bar"})
|
|
41
|
+
@config.upload("testl.txt", "testr.txt", :mode => 0775, :foo => "bar")
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def test_upload_with_symbolic_mode_should_try_to_chmod
|
|
45
|
+
@config.expects(:transfer).with(:up, "testl.txt", "testr.txt", { :foo => "bar" })
|
|
46
|
+
@config.expects(:run).with("chmod g+w testr.txt", {:foo => "bar"})
|
|
47
|
+
@config.upload("testl.txt", "testr.txt", :mode => "g+w", :foo => "bar")
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def test_download_should_delegate_to_transfer
|
|
51
|
+
@config.expects(:transfer).with(:down, "testr.txt", "testl.txt", { :foo => "bar" })
|
|
52
|
+
@config.download("testr.txt", "testl.txt", :foo => "bar")
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def test_transfer_should_invoke_transfer_on_the_configured_server
|
|
56
|
+
@config.session = 1
|
|
57
|
+
@config.expects(:execute_on_server).yields
|
|
58
|
+
Minestrone::Transfer.expects(:process).with(:up, "testl.txt", "testr.txt", 1, {:foo => "bar", :logger => @config.logger})
|
|
59
|
+
@config.transfer(:up, "testl.txt", "testr.txt", :foo => "bar")
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
require "utils"
|
|
2
|
+
require 'minestrone/configuration/actions/inspect'
|
|
3
|
+
|
|
4
|
+
class ConfigurationActionsInspectTest < Test::Unit::TestCase
|
|
5
|
+
class MockConfig
|
|
6
|
+
include Minestrone::Configuration::Actions::Inspect
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def setup
|
|
10
|
+
@config = MockConfig.new
|
|
11
|
+
@config.stubs(:logger).returns(stub_everything)
|
|
12
|
+
@config.stubs(:sudo).returns('sudo')
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def test_stream_should_pass_options_through_to_run
|
|
16
|
+
@config.expects(:invoke_command).with("tail -f foo.log", { :once => true, :eof => true })
|
|
17
|
+
@config.stream("tail -f foo.log", :once => true)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def test_stream_with_sudo_should_avoid_closing_stdin
|
|
21
|
+
@config.expects(:invoke_command).with("sudo tail -f foo.log", { :once => true, :eof => false })
|
|
22
|
+
@config.stream("sudo tail -f foo.log", :once => true)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def test_stream_should_emit_stdout_via_puts
|
|
26
|
+
@config.expects(:invoke_command).yields(mock("channel"), :out, "something streamed")
|
|
27
|
+
@config.expects(:puts).with("something streamed")
|
|
28
|
+
@config.expects(:warn).never
|
|
29
|
+
@config.stream("tail -f foo.log")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def test_stream_should_emit_stderr_via_warn
|
|
33
|
+
ch = mock("channel")
|
|
34
|
+
ch.expects(:[]).with(:server).returns(server("minestrone"))
|
|
35
|
+
@config.expects(:invoke_command).yields(ch, :err, "something streamed")
|
|
36
|
+
@config.expects(:puts).never
|
|
37
|
+
@config.expects(:warn).with("[err :: minestrone] something streamed")
|
|
38
|
+
@config.stream("tail -f foo.log")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def test_capture_should_pass_options_merged_with_once_to_run
|
|
42
|
+
@config.expects(:invoke_command).with("hostname", { :foo => "bar", :once => true, :eof => true })
|
|
43
|
+
@config.capture("hostname", :foo => "bar")
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def test_capture_with_sudo_should_avoid_closing_stdin
|
|
47
|
+
@config.expects(:invoke_command).with("sudo hostname", { :foo => "bar", :once => true, :eof => false })
|
|
48
|
+
@config.capture("sudo hostname", :foo => "bar")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def test_capture_with_stderr_should_emit_stderr_via_warn
|
|
52
|
+
ch = mock("channel")
|
|
53
|
+
ch.expects(:[]).with(:server).returns(server("minestrone"))
|
|
54
|
+
@config.expects(:invoke_command).yields(ch, :err, "boom")
|
|
55
|
+
@config.expects(:warn).with("[err :: minestrone] boom")
|
|
56
|
+
@config.capture("hostname")
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def test_capture_with_stdout_should_aggregate_and_return_stdout
|
|
60
|
+
config_expects_invoke_command_to_loop_with(mock("channel"), "foo", "bar", "baz")
|
|
61
|
+
assert_equal "foobarbaz", @config.capture("hostname")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def config_expects_invoke_command_to_loop_with(channel, *output)
|
|
67
|
+
class <<@config
|
|
68
|
+
attr_accessor :script, :channel
|
|
69
|
+
def invoke_command(*args)
|
|
70
|
+
script.each { |item| yield channel, :out, item }
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
@config.channel = channel
|
|
74
|
+
@config.script = output
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
require "utils"
|
|
2
|
+
require 'minestrone/configuration/actions/invocation'
|
|
3
|
+
require 'minestrone/configuration/actions/file_transfer'
|
|
4
|
+
|
|
5
|
+
class ConfigurationActionsInvocationTest < Test::Unit::TestCase
|
|
6
|
+
class MockConfig
|
|
7
|
+
attr_reader :options
|
|
8
|
+
attr_accessor :debug
|
|
9
|
+
attr_accessor :dry_run
|
|
10
|
+
attr_accessor :server
|
|
11
|
+
attr_accessor :session
|
|
12
|
+
|
|
13
|
+
def initialize
|
|
14
|
+
@options = {}
|
|
15
|
+
@server = nil
|
|
16
|
+
initialize_invocation
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def [](*args)
|
|
20
|
+
@options[*args]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def set(name, value)
|
|
24
|
+
@options[name] = value
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def fetch(*args)
|
|
28
|
+
@options.fetch(*args)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def active_server
|
|
32
|
+
@server
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def execute_on_server
|
|
36
|
+
yield
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
include Minestrone::Configuration::Actions::Invocation
|
|
40
|
+
include Minestrone::Configuration::Actions::FileTransfer
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def setup
|
|
44
|
+
@config = make_config
|
|
45
|
+
@original_io_proc = MockConfig.default_io_proc
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def teardown
|
|
49
|
+
MockConfig.default_io_proc = @original_io_proc
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def test_run_options_should_be_passed_to_command_process
|
|
53
|
+
::Minestrone::Command.expects(:process).with("ls", @config.session, has_entries(:foo => "bar", :eof => true, :logger => @config.logger, :configuration => @config))
|
|
54
|
+
@config.run "ls", :foo => "bar"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def test_run_will_return_if_dry_run
|
|
58
|
+
@config.expects(:dry_run).returns(true)
|
|
59
|
+
@config.expects(:execute_on_server).never
|
|
60
|
+
@config.run "ls", :foo => "bar"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def test_put_wont_transfer_if_dry_run
|
|
64
|
+
config = make_config
|
|
65
|
+
config.dry_run = true
|
|
66
|
+
config.server = "foo"
|
|
67
|
+
config.expects(:execute_on_server).never
|
|
68
|
+
::Minestrone::Transfer.expects(:process).never
|
|
69
|
+
config.put "foo", "bar", :mode => 0644
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def test_add_default_command_options_should_return_bare_options_if_there_is_no_env_or_shell_specified
|
|
73
|
+
assert_equal({:foo => "bar"}, @config.add_default_command_options(:foo => "bar"))
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def test_add_default_command_options_should_merge_default_environment_as_env
|
|
77
|
+
@config[:default_environment][:bang] = "baz"
|
|
78
|
+
assert_equal({:foo => "bar", :env => { :bang => "baz" }}, @config.add_default_command_options(:foo => "bar"))
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def test_add_default_command_options_should_merge_env_with_default_environment
|
|
82
|
+
@config[:default_environment][:bang] = "baz"
|
|
83
|
+
@config[:default_environment][:bacon] = "crunchy"
|
|
84
|
+
assert_equal({:foo => "bar", :env => { :bang => "baz", :bacon => "chunky", :flip => "flop" }}, @config.add_default_command_options(:foo => "bar", :env => {:bacon => "chunky", :flip => "flop"}))
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def test_add_default_command_options_should_use_default_shell_if_present
|
|
88
|
+
@config.set :default_shell, "/bin/bash"
|
|
89
|
+
assert_equal({:foo => "bar", :shell => "/bin/bash"}, @config.add_default_command_options(:foo => "bar"))
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def test_add_default_command_options_should_use_default_shell_of_false_if_present
|
|
93
|
+
@config.set :default_shell, false
|
|
94
|
+
assert_equal({:foo => "bar", :shell => false}, @config.add_default_command_options(:foo => "bar"))
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def test_add_default_command_options_should_use_shell_in_preference_of_default_shell
|
|
98
|
+
@config.set :default_shell, "/bin/bash"
|
|
99
|
+
assert_equal({:foo => "bar", :shell => "/bin/sh"}, @config.add_default_command_options(:foo => "bar", :shell => "/bin/sh"))
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def test_default_io_proc_should_log_stdout_arguments_as_info
|
|
103
|
+
ch = { :host => "minestrone",
|
|
104
|
+
:server => server("minestrone"),
|
|
105
|
+
:options => { :logger => mock("logger") } }
|
|
106
|
+
ch[:options][:logger].expects(:info).with("data stuff", "out :: minestrone")
|
|
107
|
+
MockConfig.default_io_proc[ch, :out, "data stuff"]
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def test_default_io_proc_should_log_stderr_arguments_as_important
|
|
111
|
+
ch = { :host => "minestrone",
|
|
112
|
+
:server => server("minestrone"),
|
|
113
|
+
:options => { :logger => mock("logger") } }
|
|
114
|
+
ch[:options][:logger].expects(:important).with("data stuff", "err :: minestrone")
|
|
115
|
+
MockConfig.default_io_proc[ch, :err, "data stuff"]
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def test_sudo_should_default_to_sudo
|
|
119
|
+
@config.expects(:run).with("sudo -p 'sudo password: ' ls", {})
|
|
120
|
+
@config.sudo "ls"
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def test_sudo_should_keep_input_stream_open
|
|
124
|
+
@config.expects(:execute_on_server)
|
|
125
|
+
@config.sudo "ls", :foo => "bar"
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def test_sudo_should_use_sudo_variable_definition
|
|
129
|
+
@config.expects(:run).with("/opt/local/bin/sudo -p 'sudo password: ' ls", {})
|
|
130
|
+
@config.options[:sudo] = "/opt/local/bin/sudo"
|
|
131
|
+
@config.sudo "ls"
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def test_sudo_should_interpret_as_option_as_user
|
|
135
|
+
@config.expects(:run).with("sudo -p 'sudo password: ' -u app ls", {})
|
|
136
|
+
@config.sudo "ls", :as => "app"
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def test_sudo_should_pass_options_through_to_run
|
|
140
|
+
@config.expects(:run).with("sudo -p 'sudo password: ' ls", { :foo => "bar" })
|
|
141
|
+
@config.sudo "ls", :foo => "bar"
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def test_sudo_should_avoid_minus_p_when_sudo_prompt_is_empty
|
|
145
|
+
@config.set :sudo_prompt, ""
|
|
146
|
+
@config.expects(:run).with("sudo ls", {})
|
|
147
|
+
@config.sudo "ls"
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def test_sudo_should_interpret_sudo_prompt_variable_as_custom_prompt
|
|
151
|
+
@config.set :sudo_prompt, "give it to me: "
|
|
152
|
+
@config.expects(:run).with("sudo -p 'give it to me: ' ls", {})
|
|
153
|
+
@config.sudo "ls"
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def test_sudo_behavior_callback_should_send_password_when_prompted_with_default_sudo_prompt
|
|
157
|
+
ch = mock("channel")
|
|
158
|
+
ch.expects(:send_data).with("g00b3r\n")
|
|
159
|
+
@config.options[:password] = "g00b3r"
|
|
160
|
+
@config.sudo_behavior_callback(nil)[ch, nil, "sudo password: "]
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def test_sudo_behavior_callback_should_send_password_when_prompted_with_custom_sudo_prompt
|
|
164
|
+
ch = mock("channel")
|
|
165
|
+
ch.expects(:send_data).with("g00b3r\n")
|
|
166
|
+
@config.set :sudo_prompt, "give it to me: "
|
|
167
|
+
@config.options[:password] = "g00b3r"
|
|
168
|
+
@config.sudo_behavior_callback(nil)[ch, nil, "give it to me: "]
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def test_sudo_behavior_callback_with_incorrect_password_on_first_prompt
|
|
172
|
+
ch = mock("channel")
|
|
173
|
+
ch.stubs(:[]).with(:host).returns("minestrone")
|
|
174
|
+
ch.stubs(:[]).with(:server).returns(server("minestrone"))
|
|
175
|
+
@config.expects(:reset!).with(:password)
|
|
176
|
+
@config.sudo_behavior_callback(nil)[ch, nil, "Sorry, try again."]
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def test_sudo_behavior_callback_with_incorrect_password_on_subsequent_prompts
|
|
180
|
+
callback = @config.sudo_behavior_callback(nil)
|
|
181
|
+
|
|
182
|
+
ch = mock("channel")
|
|
183
|
+
ch.stubs(:[]).with(:host).returns("minestrone")
|
|
184
|
+
ch.stubs(:[]).with(:server).returns(server("minestrone"))
|
|
185
|
+
ch2 = mock("channel")
|
|
186
|
+
ch2.stubs(:[]).with(:host).returns("cap2")
|
|
187
|
+
ch2.stubs(:[]).with(:server).returns(server("cap2"))
|
|
188
|
+
|
|
189
|
+
@config.expects(:reset!).with(:password).times(2)
|
|
190
|
+
|
|
191
|
+
callback[ch, nil, "Sorry, try again."]
|
|
192
|
+
callback[ch2, nil, "Sorry, try again."] # shouldn't call reset!
|
|
193
|
+
callback[ch, nil, "Sorry, try again."]
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def test_sudo_behavior_callback_should_reset_password_and_prompt_again_if_output_includes_both_cues
|
|
197
|
+
ch = mock("channel")
|
|
198
|
+
ch.stubs(:[]).with(:host).returns("minestrone")
|
|
199
|
+
ch.stubs(:[]).with(:server).returns(server("minestrone"))
|
|
200
|
+
ch.expects(:send_data, "password!\n").times(2)
|
|
201
|
+
|
|
202
|
+
@config.set(:password, "password!")
|
|
203
|
+
@config.expects(:reset!).with(:password)
|
|
204
|
+
|
|
205
|
+
callback = @config.sudo_behavior_callback(nil)
|
|
206
|
+
callback[ch, :out, "sudo password: "]
|
|
207
|
+
callback[ch, :out, "Sorry, try again.\nsudo password: "]
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def test_sudo_behavior_callback_should_defer_to_fallback_for_other_output
|
|
211
|
+
callback = @config.sudo_behavior_callback(inspectable_proc)
|
|
212
|
+
|
|
213
|
+
a = mock("channel", :called => true)
|
|
214
|
+
b = mock("stream", :called => true)
|
|
215
|
+
c = mock("data", :called => true)
|
|
216
|
+
|
|
217
|
+
callback[a, b, c]
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def test_invoke_command_should_default_to_run
|
|
221
|
+
@config.expects(:run).with("ls", { :once => true })
|
|
222
|
+
@config.invoke_command("ls", :once => true)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def test_invoke_command_should_delegate_to_method_identified_by_via
|
|
226
|
+
@config.expects(:foobar).with("ls", { :once => true })
|
|
227
|
+
@config.invoke_command("ls", :once => true, :via => :foobar)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def test_run_only_logs_once
|
|
231
|
+
@config.server = :app
|
|
232
|
+
|
|
233
|
+
logger = mock('logger')
|
|
234
|
+
logger.stubs(:debug).with("executing \"ls\"")
|
|
235
|
+
@config.stubs(:logger).returns(logger)
|
|
236
|
+
|
|
237
|
+
@config.expects(:execute_on_server)
|
|
238
|
+
|
|
239
|
+
@config.run("ls")
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
private
|
|
243
|
+
|
|
244
|
+
def make_config
|
|
245
|
+
config = MockConfig.new
|
|
246
|
+
config.stubs(:logger).returns(stub_everything)
|
|
247
|
+
config
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def inspectable_proc
|
|
251
|
+
Proc.new do |ch, stream, data|
|
|
252
|
+
ch.called
|
|
253
|
+
stream.called
|
|
254
|
+
data.called
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
end
|