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,110 @@
|
|
|
1
|
+
require 'utils'
|
|
2
|
+
require 'minestrone/configuration/alias_task'
|
|
3
|
+
require 'minestrone/configuration/execution'
|
|
4
|
+
require 'minestrone/configuration/namespaces'
|
|
5
|
+
require 'minestrone/task_definition'
|
|
6
|
+
|
|
7
|
+
class AliasTaskTest < Test::Unit::TestCase
|
|
8
|
+
class MockConfig
|
|
9
|
+
attr_reader :options
|
|
10
|
+
attr_accessor :logger
|
|
11
|
+
|
|
12
|
+
def initialize(options = {})
|
|
13
|
+
@options = {}
|
|
14
|
+
@logger = options.delete(:logger)
|
|
15
|
+
initialize_execution
|
|
16
|
+
initialize_namespaces
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
include Minestrone::Configuration::AliasTask
|
|
20
|
+
include Minestrone::Configuration::Execution
|
|
21
|
+
include Minestrone::Configuration::Namespaces
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def setup
|
|
25
|
+
@config = MockConfig.new( :logger => stub(:debug => nil, :info => nil, :important => nil) )
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def test_makes_a_copy_of_the_task
|
|
29
|
+
@config.task(:foo) { 42 }
|
|
30
|
+
@config.alias_task 'new_foo', 'foo'
|
|
31
|
+
|
|
32
|
+
assert @config.tasks.key?(:new_foo)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def test_original_task_remain_with_same_name
|
|
36
|
+
@config.task(:foo) { 42 }
|
|
37
|
+
@config.alias_task 'new_foo', 'foo'
|
|
38
|
+
|
|
39
|
+
assert_equal :foo, @config.tasks[:foo].name
|
|
40
|
+
assert_equal :new_foo, @config.tasks[:new_foo].name
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def test_aliased_task_do_the_same
|
|
44
|
+
@config.task(:foo) { 42 }
|
|
45
|
+
@config.alias_task 'new_foo', 'foo'
|
|
46
|
+
|
|
47
|
+
assert_equal 42, @config.find_and_execute_task('new_foo')
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def test_aliased_task_should_preserve_description
|
|
51
|
+
@config.task(:foo, :desc => "the Ultimate Question of Life, the Universe, and Everything" ) { 42 }
|
|
52
|
+
@config.alias_task 'new_foo', 'foo'
|
|
53
|
+
|
|
54
|
+
task = @config.find_task('foo')
|
|
55
|
+
new_task = @config.find_task('new_foo')
|
|
56
|
+
|
|
57
|
+
assert_equal task.description, new_task.description
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def test_aliased_task_should_preserve_on_error
|
|
61
|
+
@config.task(:foo, :on_error => :continue) { 42 }
|
|
62
|
+
@config.alias_task 'new_foo', 'foo'
|
|
63
|
+
|
|
64
|
+
task = @config.find_task('foo')
|
|
65
|
+
new_task = @config.find_task('new_foo')
|
|
66
|
+
|
|
67
|
+
assert_equal task.on_error, new_task.on_error
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def test_raise_exception_when_task_doesnt_exist
|
|
71
|
+
assert_raises(Minestrone::NoSuchTaskError) { @config.alias_task 'non_existant_task', 'fail_miserably' }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def test_convert_task_names_using_to_str
|
|
75
|
+
@config.task(:foo) { 42 }
|
|
76
|
+
|
|
77
|
+
@config.alias_task 'one', 'foo'
|
|
78
|
+
@config.alias_task :two, 'foo'
|
|
79
|
+
@config.alias_task 'three', :foo
|
|
80
|
+
@config.alias_task :four, :foo
|
|
81
|
+
|
|
82
|
+
assert @config.tasks.key?(:one)
|
|
83
|
+
assert @config.tasks.key?(:two)
|
|
84
|
+
assert @config.tasks.key?(:three)
|
|
85
|
+
assert @config.tasks.key?(:four)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def test_raise_an_exception_when_task_names_can_not_be_converted
|
|
89
|
+
@config.task(:foo) { 42 }
|
|
90
|
+
|
|
91
|
+
assert_raises(ArgumentError) { @config.alias_task mock('x'), :foo }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def test_should_include_namespace
|
|
95
|
+
@config.namespace(:outer) do
|
|
96
|
+
task(:foo) { 42 }
|
|
97
|
+
alias_task 'new_foo', 'foo'
|
|
98
|
+
|
|
99
|
+
namespace(:inner) do
|
|
100
|
+
task(:foo) { 43 }
|
|
101
|
+
alias_task 'new_foo', 'foo'
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
assert_equal 42, @config.find_and_execute_task('outer:new_foo')
|
|
106
|
+
assert_equal 42, @config.find_and_execute_task('outer:foo')
|
|
107
|
+
assert_equal 43, @config.find_and_execute_task('outer:inner:new_foo')
|
|
108
|
+
assert_equal 43, @config.find_and_execute_task('outer:inner:foo')
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
require "utils"
|
|
2
|
+
require 'minestrone/configuration/callbacks'
|
|
3
|
+
|
|
4
|
+
class ConfigurationCallbacksTest < Test::Unit::TestCase
|
|
5
|
+
class MockConfig
|
|
6
|
+
attr_reader :original_initialize_called
|
|
7
|
+
attr_reader :called
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
@original_initialize_called = true
|
|
11
|
+
@called = []
|
|
12
|
+
initialize_callbacks
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def execute_task(task)
|
|
16
|
+
invoke_task_directly(task)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
protected
|
|
20
|
+
|
|
21
|
+
def invoke_task_directly(task)
|
|
22
|
+
@called << task
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
include Minestrone::Configuration::Callbacks
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def setup
|
|
29
|
+
@config = MockConfig.new
|
|
30
|
+
@config.stubs(:logger).returns(stub_everything("logger"))
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def test_initialize_should_initialize_callbacks_collection
|
|
34
|
+
assert @config.original_initialize_called
|
|
35
|
+
assert @config.callbacks.empty?
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def test_before_should_delegate_to_on
|
|
39
|
+
@config.expects(:on).with(:before, :foo, "bing:blang", {:only => :bar, :zip => :zing})
|
|
40
|
+
@config.before :bar, :foo, "bing:blang", :zip => :zing
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def test_before_should_keep_task_names_as_given
|
|
44
|
+
@config.before "deploy:create_symlink", "bing:blang", "deploy:create_symlink"
|
|
45
|
+
assert_equal "bing:blang", @config.callbacks[:before][0].source
|
|
46
|
+
assert_equal "deploy:create_symlink", @config.callbacks[:before][1].source
|
|
47
|
+
assert_equal ["deploy:create_symlink"], @config.callbacks[:before][1].only
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def test_before_should_keep_task_name_arrays_as_given
|
|
51
|
+
@config.before ["deploy:create_symlink", "bingo:blast"], "bing:blang"
|
|
52
|
+
assert_equal ["deploy:create_symlink", "bingo:blast"], @config.callbacks[:before].last.only
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def test_after_should_delegate_to_on
|
|
56
|
+
@config.expects(:on).with(:after, :foo, "bing:blang", {:only => :bar, :zip => :zing})
|
|
57
|
+
@config.after :bar, :foo, "bing:blang", :zip => :zing
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def test_after_should_keep_task_names_as_given
|
|
61
|
+
@config.after "deploy:create_symlink", "bing:blang", "deploy:create_symlink"
|
|
62
|
+
assert_equal "bing:blang", @config.callbacks[:after][0].source
|
|
63
|
+
assert_equal "deploy:create_symlink", @config.callbacks[:after][1].source
|
|
64
|
+
assert_equal ["deploy:create_symlink"], @config.callbacks[:after][1].only
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def test_after_should_keep_task_name_arrays_as_given
|
|
68
|
+
@config.after ["deploy:create_symlink", "bingo:blast"], "bing:blang"
|
|
69
|
+
assert_equal ["deploy:create_symlink", "bingo:blast"], @config.callbacks[:after].last.only
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def test_on_with_single_reference_should_add_task_callback
|
|
73
|
+
@config.on :before, :a_test
|
|
74
|
+
assert_equal 1, @config.callbacks[:before].length
|
|
75
|
+
assert_equal :a_test, @config.callbacks[:before][0].source
|
|
76
|
+
@config.expects(:find_and_execute_task).with(:a_test)
|
|
77
|
+
@config.callbacks[:before][0].call
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def test_on_with_multi_reference_should_add_all_as_task_callback
|
|
81
|
+
@config.on :before, :first, :second, :third
|
|
82
|
+
assert_equal 3, @config.callbacks[:before].length
|
|
83
|
+
assert_equal %w(first second third), @config.callbacks[:before].map { |c| c.source.to_s }
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def test_on_with_block_should_add_block_as_proc_callback
|
|
87
|
+
called = false
|
|
88
|
+
@config.on(:before) { called = true }
|
|
89
|
+
assert_equal 1, @config.callbacks[:before].length
|
|
90
|
+
assert_instance_of Proc, @config.callbacks[:before][0].source
|
|
91
|
+
@config.callbacks[:before][0].call
|
|
92
|
+
assert called
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def test_on_with_single_only_should_set_only_as_string_array_on_all_references
|
|
96
|
+
@config.on :before, :first, "second:third", :only => :primary
|
|
97
|
+
assert_equal 2, @config.callbacks[:before].length
|
|
98
|
+
assert @config.callbacks[:before].all? { |c| c.only == %w(primary) }
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def test_on_with_multi_only_should_set_only_as_string_array_on_all_references
|
|
102
|
+
@config.on :before, :first, "second:third", :only => [:primary, "other:one"]
|
|
103
|
+
assert_equal 2, @config.callbacks[:before].length
|
|
104
|
+
assert @config.callbacks[:before].all? { |c| c.only == %w(primary other:one) }
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def test_on_with_single_except_should_set_except_as_string_array_on_all_references
|
|
108
|
+
@config.on :before, :first, "second:third", :except => :primary
|
|
109
|
+
assert_equal 2, @config.callbacks[:before].length
|
|
110
|
+
assert @config.callbacks[:before].all? { |c| c.except == %w(primary) }
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def test_on_with_multi_except_should_set_except_as_string_array_on_all_references
|
|
114
|
+
@config.on :before, :first, "second:third", :except => [:primary, "other:one"]
|
|
115
|
+
assert_equal 2, @config.callbacks[:before].length
|
|
116
|
+
assert @config.callbacks[:before].all? { |c| c.except == %w(primary other:one) }
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def test_on_with_only_and_block_should_set_only_as_string_array
|
|
120
|
+
@config.on(:before, :only => :primary) { blah }
|
|
121
|
+
assert_equal 1, @config.callbacks[:before].length
|
|
122
|
+
assert_equal %w(primary), @config.callbacks[:before].first.only
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def test_on_with_except_and_block_should_set_except_as_string_array
|
|
126
|
+
@config.on(:before, :except => :primary) { blah }
|
|
127
|
+
assert_equal 1, @config.callbacks[:before].length
|
|
128
|
+
assert_equal %w(primary), @config.callbacks[:before].first.except
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def test_on_without_tasks_or_block_should_raise_error
|
|
132
|
+
assert_raises(ArgumentError) { @config.on(:before) }
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def test_on_with_both_tasks_and_block_should_raise_error
|
|
136
|
+
assert_raises(ArgumentError) { @config.on(:before, :first) { blah } }
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def test_trigger_without_constraints_should_invoke_all_callbacks
|
|
140
|
+
task = stub(:fully_qualified_name => "any:old:thing")
|
|
141
|
+
@config.on(:before, :first, "second:third")
|
|
142
|
+
@config.on(:after, :another, "and:another")
|
|
143
|
+
@config.expects(:find_and_execute_task).with(:first)
|
|
144
|
+
@config.expects(:find_and_execute_task).with("second:third")
|
|
145
|
+
@config.expects(:find_and_execute_task).with(:another).never
|
|
146
|
+
@config.expects(:find_and_execute_task).with("and:another").never
|
|
147
|
+
@config.trigger(:before, task)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def test_trigger_with_only_constraint_should_invoke_only_matching_callbacks
|
|
151
|
+
task = stub(:fully_qualified_name => "any:old:thing")
|
|
152
|
+
@config.on(:before, :first)
|
|
153
|
+
@config.on(:before, "second:third", :only => "any:old:thing")
|
|
154
|
+
@config.on(:before, "this:too", :only => "any:other:thing")
|
|
155
|
+
@config.on(:after, :another, "and:another")
|
|
156
|
+
@config.expects(:find_and_execute_task).with(:first)
|
|
157
|
+
@config.expects(:find_and_execute_task).with("second:third")
|
|
158
|
+
@config.expects(:find_and_execute_task).with("this:too").never
|
|
159
|
+
@config.expects(:find_and_execute_task).with(:another).never
|
|
160
|
+
@config.expects(:find_and_execute_task).with("and:another").never
|
|
161
|
+
@config.trigger(:before, task)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def test_trigger_with_except_constraint_should_invoke_anything_but_matching_callbacks
|
|
165
|
+
task = stub(:fully_qualified_name => "any:old:thing")
|
|
166
|
+
@config.on(:before, :first)
|
|
167
|
+
@config.on(:before, "second:third", :except => "any:old:thing")
|
|
168
|
+
@config.on(:before, "this:too", :except => "any:other:thing")
|
|
169
|
+
@config.on(:after, :another, "and:another")
|
|
170
|
+
@config.expects(:find_and_execute_task).with(:first)
|
|
171
|
+
@config.expects(:find_and_execute_task).with("second:third").never
|
|
172
|
+
@config.expects(:find_and_execute_task).with("this:too")
|
|
173
|
+
@config.expects(:find_and_execute_task).with(:another).never
|
|
174
|
+
@config.expects(:find_and_execute_task).with("and:another").never
|
|
175
|
+
@config.trigger(:before, task)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def test_trigger_without_task_should_invoke_all_callbacks_for_that_event
|
|
179
|
+
@config.on(:before, :first)
|
|
180
|
+
@config.on(:before, "second:third", :except => "any:old:thing")
|
|
181
|
+
@config.on(:before, "this:too", :except => "any:other:thing")
|
|
182
|
+
@config.on(:after, :another, "and:another")
|
|
183
|
+
@config.expects(:find_and_execute_task).with(:first)
|
|
184
|
+
@config.expects(:find_and_execute_task).with("second:third")
|
|
185
|
+
@config.expects(:find_and_execute_task).with("this:too")
|
|
186
|
+
@config.expects(:find_and_execute_task).with(:another).never
|
|
187
|
+
@config.expects(:find_and_execute_task).with("and:another").never
|
|
188
|
+
@config.trigger(:before)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def test_execute_task_without_named_hooks_should_just_call_task
|
|
192
|
+
ns = stub("namespace", :default_task => nil, :name => "old", :fully_qualified_name => "any:old")
|
|
193
|
+
task = stub(:fully_qualified_name => "any:old:thing", :name => "thing", :namespace => ns)
|
|
194
|
+
|
|
195
|
+
ns.stubs(:search_task).returns(nil)
|
|
196
|
+
|
|
197
|
+
@config.execute_task(task)
|
|
198
|
+
assert_equal [task], @config.called
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
end
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
require "utils"
|
|
2
|
+
require 'minestrone/configuration/connections'
|
|
3
|
+
|
|
4
|
+
class ConfigurationConnectionsTest < Test::Unit::TestCase
|
|
5
|
+
class MockConfig
|
|
6
|
+
attr_reader :original_initialize_called
|
|
7
|
+
attr_reader :values
|
|
8
|
+
attr_reader :dry_run
|
|
9
|
+
attr_accessor :current_task
|
|
10
|
+
attr_accessor :server
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
@original_initialize_called = true
|
|
14
|
+
@values = {}
|
|
15
|
+
initialize_connections
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def fetch(*args)
|
|
19
|
+
@values.fetch(*args)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def [](key)
|
|
23
|
+
@values[key]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def exists?(key)
|
|
27
|
+
@values.key?(key)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def resolved_server
|
|
31
|
+
@server
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
include Minestrone::Configuration::Connections
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def setup
|
|
38
|
+
@config = MockConfig.new
|
|
39
|
+
@config.stubs(:logger).returns(stub_everything)
|
|
40
|
+
Net::SSH.stubs(:configuration_for).returns({})
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def test_initialize_should_initialize_session_and_call_original_initialize
|
|
44
|
+
assert @config.original_initialize_called
|
|
45
|
+
assert_nil @config.session
|
|
46
|
+
assert_false @config.instance_variable_get('@failed')
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def test_establish_connection_to_server_should_use_the_resolved_server
|
|
50
|
+
Minestrone::SSH.expects(:connect).with { |s,c| s.host == "minestrone" && c == @config }.returns(:success)
|
|
51
|
+
assert_nil @config.session
|
|
52
|
+
@config.server = server("minestrone")
|
|
53
|
+
@config.establish_connection_to_server
|
|
54
|
+
assert_equal :success, @config.session
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def test_establish_connection_to_server_should_not_attempt_to_reestablish_existing_connection
|
|
58
|
+
Minestrone::SSH.expects(:connect).never
|
|
59
|
+
@config.expects(:resolved_server).never
|
|
60
|
+
@config.session = :ok
|
|
61
|
+
@config.server = server("cap1")
|
|
62
|
+
@config.establish_connection_to_server
|
|
63
|
+
assert_equal :ok, @config.session
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def test_establish_connection_to_server_should_raise_connection_error_on_failure
|
|
67
|
+
Minestrone::SSH.expects(:connect).raises(Exception)
|
|
68
|
+
@config.server = server("cap1")
|
|
69
|
+
assert_raises(Minestrone::ConnectionError) {
|
|
70
|
+
@config.establish_connection_to_server
|
|
71
|
+
}
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def test_connection_error_should_include_failed_host
|
|
75
|
+
Minestrone::SSH.expects(:connect).raises(Exception)
|
|
76
|
+
@config.server = server("cap1")
|
|
77
|
+
begin
|
|
78
|
+
@config.establish_connection_to_server
|
|
79
|
+
flunk "expected an exception to be raised"
|
|
80
|
+
rescue Minestrone::ConnectionError => e
|
|
81
|
+
assert e.respond_to?(:host)
|
|
82
|
+
assert_equal "cap1", e.host.to_s
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def test_execute_on_server_should_require_a_block
|
|
87
|
+
assert_raises(ArgumentError) { @config.execute_on_server }
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def test_execute_on_server_without_current_task_should_use_configured_server
|
|
91
|
+
host = server("first")
|
|
92
|
+
@config.server = host
|
|
93
|
+
@config.expects(:establish_connection_to_server).returns(:done)
|
|
94
|
+
block_called = false
|
|
95
|
+
@config.execute_on_server do
|
|
96
|
+
block_called = true
|
|
97
|
+
end
|
|
98
|
+
assert block_called
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def test_execute_on_server_should_determine_server_from_configured_server
|
|
102
|
+
assert_nil @config.session
|
|
103
|
+
@config.server = server("cap1")
|
|
104
|
+
@config.current_task = mock_task
|
|
105
|
+
Minestrone::SSH.expects(:connect).returns(:success)
|
|
106
|
+
@config.execute_on_server {}
|
|
107
|
+
assert_equal :success, @config.session
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def test_execute_on_server_should_yield_to_block
|
|
111
|
+
assert_nil @config.session
|
|
112
|
+
host = server("cap1")
|
|
113
|
+
@config.server = host
|
|
114
|
+
@config.current_task = mock_task
|
|
115
|
+
Minestrone::SSH.expects(:connect).returns(:success)
|
|
116
|
+
block_called = false
|
|
117
|
+
@config.execute_on_server do
|
|
118
|
+
block_called = true
|
|
119
|
+
assert @config.session
|
|
120
|
+
end
|
|
121
|
+
assert block_called
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def test_execute_servers_should_raise_connection_error_on_failure_by_default
|
|
125
|
+
@config.current_task = mock_task
|
|
126
|
+
@config.server = server("cap1")
|
|
127
|
+
Minestrone::SSH.expects(:connect).raises(Exception)
|
|
128
|
+
assert_raises(Minestrone::ConnectionError) do
|
|
129
|
+
@config.execute_on_server do
|
|
130
|
+
flunk "expected an exception to be raised"
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def test_execute_servers_should_not_raise_connection_error_on_failure_with_on_errors_continue
|
|
136
|
+
host = server("cap1")
|
|
137
|
+
@config.current_task = mock_task(:on_error => :continue)
|
|
138
|
+
@config.server = host
|
|
139
|
+
Minestrone::SSH.expects(:connect).raises(Exception)
|
|
140
|
+
assert_nothing_raised {
|
|
141
|
+
@config.execute_on_server do
|
|
142
|
+
flunk "should not yield after connection failure"
|
|
143
|
+
end
|
|
144
|
+
}
|
|
145
|
+
assert @config.instance_variable_get('@failed')
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def test_execute_on_server_should_skip_failed_host_with_on_errors_continue
|
|
149
|
+
@config.current_task = mock_task(:on_error => :continue)
|
|
150
|
+
@config.server = server("cap1")
|
|
151
|
+
@config.instance_variable_set('@failed', true)
|
|
152
|
+
|
|
153
|
+
Minestrone::SSH.expects(:connect).never
|
|
154
|
+
|
|
155
|
+
@config.execute_on_server do
|
|
156
|
+
flunk "should not yield for failed host"
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def test_execute_on_server_should_record_command_errors_with_on_errors_continue
|
|
161
|
+
host = server("cap1")
|
|
162
|
+
@config.current_task = mock_task(:on_error => :continue)
|
|
163
|
+
@config.server = host
|
|
164
|
+
Minestrone::SSH.expects(:connect).returns(:success)
|
|
165
|
+
@config.execute_on_server do
|
|
166
|
+
error = Minestrone::CommandError.new
|
|
167
|
+
error.host = host
|
|
168
|
+
raise error
|
|
169
|
+
end
|
|
170
|
+
assert @config.instance_variable_get('@failed')
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def test_connect_should_establish_connection_to_server_in_scope
|
|
174
|
+
assert_nil @config.session
|
|
175
|
+
@config.current_task = mock_task
|
|
176
|
+
@config.server = server("cap1")
|
|
177
|
+
Minestrone::SSH.expects(:connect).returns(:success)
|
|
178
|
+
@config.connect!
|
|
179
|
+
assert_equal :success, @config.session
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
private
|
|
183
|
+
|
|
184
|
+
def mock_task(options = {})
|
|
185
|
+
continue_on_error = options[:on_error] == :continue
|
|
186
|
+
stub("task",
|
|
187
|
+
:fully_qualified_name => "name",
|
|
188
|
+
:options => options,
|
|
189
|
+
:continue_on_error? => continue_on_error
|
|
190
|
+
)
|
|
191
|
+
end
|
|
192
|
+
end
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
require "utils"
|
|
2
|
+
require 'minestrone/configuration/execution'
|
|
3
|
+
require 'minestrone/task_definition'
|
|
4
|
+
|
|
5
|
+
class ConfigurationExecutionTest < Test::Unit::TestCase
|
|
6
|
+
class MockConfig
|
|
7
|
+
attr_reader :tasks, :namespaces, :fully_qualified_name, :parent
|
|
8
|
+
attr_reader :state, :original_initialize_called
|
|
9
|
+
attr_accessor :logger, :default_task
|
|
10
|
+
|
|
11
|
+
def initialize(options = {})
|
|
12
|
+
@original_initialize_called = true
|
|
13
|
+
@tasks = {}
|
|
14
|
+
@namespaces = {}
|
|
15
|
+
@state = {}
|
|
16
|
+
@fully_qualified_name = options[:fqn]
|
|
17
|
+
@parent = options[:parent]
|
|
18
|
+
@logger = options.delete(:logger)
|
|
19
|
+
initialize_execution
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
include Minestrone::Configuration::Execution
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def setup
|
|
26
|
+
@config = MockConfig.new(:logger => stub(:debug => nil, :info => nil, :important => nil))
|
|
27
|
+
@config.stubs(:search_task).returns(nil)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def test_initialize_should_initialize_collections
|
|
31
|
+
assert_nil @config.rollback_requests
|
|
32
|
+
assert @config.original_initialize_called
|
|
33
|
+
assert @config.task_call_frames.empty?
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def test_execute_task_should_populate_call_stack
|
|
37
|
+
task = new_task @config, :testing
|
|
38
|
+
assert_nothing_raised { @config.execute_task(task) }
|
|
39
|
+
assert_equal %w(testing), @config.state[:testing][:stack]
|
|
40
|
+
assert_nil @config.state[:testing][:history]
|
|
41
|
+
assert @config.task_call_frames.empty?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def test_nested_execute_task_should_add_to_call_stack
|
|
45
|
+
testing = new_task @config, :testing
|
|
46
|
+
outer = new_task(@config, :outer) { execute_task(testing) }
|
|
47
|
+
|
|
48
|
+
assert_nothing_raised { @config.execute_task(outer) }
|
|
49
|
+
assert_equal %w(outer testing), @config.state[:testing][:stack]
|
|
50
|
+
assert_nil @config.state[:testing][:history]
|
|
51
|
+
assert @config.task_call_frames.empty?
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def test_execute_task_should_execute_in_scope_of_tasks_parent
|
|
55
|
+
ns = stub("namespace", :tasks => {}, :default_task => nil, :fully_qualified_name => "ns")
|
|
56
|
+
ns.expects(:instance_eval)
|
|
57
|
+
testing = new_task ns, :testing
|
|
58
|
+
@config.execute_task(testing)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def test_transaction_outside_of_task_should_raise_exception
|
|
62
|
+
assert_raises(ScriptError) { @config.transaction {} }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def test_transaction_without_block_should_raise_argument_error
|
|
66
|
+
testing = new_task(@config, :testing) { transaction }
|
|
67
|
+
assert_raises(ArgumentError) { @config.execute_task(testing) }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def test_transaction_should_initialize_transaction_history
|
|
71
|
+
@config.state[:inspector] = stack_inspector
|
|
72
|
+
testing = new_task(@config, :testing) { transaction { instance_eval(&state[:inspector]) } }
|
|
73
|
+
@config.execute_task(testing)
|
|
74
|
+
assert_equal [], @config.state[:testing][:history]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def test_transaction_from_within_transaction_should_not_start_new_transaction
|
|
78
|
+
third = new_task(@config, :third, &stack_inspector)
|
|
79
|
+
second = new_task(@config, :second) { transaction { execute_task(third) } }
|
|
80
|
+
first = new_task(@config, :first) { transaction { execute_task(second) } }
|
|
81
|
+
# kind of fragile...not sure how else to check that transaction was only
|
|
82
|
+
# really run twice...but if the transaction was REALLY run, logger.info
|
|
83
|
+
# will be called once when it starts, and once when it finishes.
|
|
84
|
+
@config.logger = mock()
|
|
85
|
+
@config.logger.stubs(:debug)
|
|
86
|
+
@config.logger.expects(:info).times(2)
|
|
87
|
+
@config.execute_task(first)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def test_on_rollback_should_have_no_effect_outside_of_transaction
|
|
91
|
+
aaa = new_task(@config, :aaa) { on_rollback { state[:rollback] = true }; raise "boom" }
|
|
92
|
+
assert_raises(RuntimeError) { @config.execute_task(aaa) }
|
|
93
|
+
assert_nil @config.state[:rollback]
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def test_exception_raised_in_transaction_should_call_all_registered_rollback_handlers_in_reverse_order
|
|
97
|
+
aaa = new_task(@config, :aaa) { on_rollback { (state[:rollback] ||= []) << :aaa } }
|
|
98
|
+
bbb = new_task(@config, :bbb) { on_rollback { (state[:rollback] ||= []) << :bbb } }
|
|
99
|
+
ccc = new_task(@config, :ccc) {}
|
|
100
|
+
ddd = new_task(@config, :ddd) { on_rollback { (state[:rollback] ||= []) << :ddd }; execute_task(bbb); execute_task(ccc) }
|
|
101
|
+
eee = new_task(@config, :eee) { transaction { execute_task(ddd); execute_task(aaa); raise "boom" } }
|
|
102
|
+
assert_raises(RuntimeError) do
|
|
103
|
+
@config.execute_task(eee)
|
|
104
|
+
end
|
|
105
|
+
assert_equal [:aaa, :bbb, :ddd], @config.state[:rollback]
|
|
106
|
+
assert_nil @config.rollback_requests
|
|
107
|
+
assert @config.task_call_frames.empty?
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def test_exception_during_rollback_should_simply_be_logged_and_ignored
|
|
111
|
+
aaa = new_task(@config, :aaa) { on_rollback { state[:aaa] = true; raise LoadError, "ouch" }; execute_task(bbb) }
|
|
112
|
+
bbb = new_task(@config, :bbb) { raise MadError, "boom" }
|
|
113
|
+
ccc = new_task(@config, :ccc) { transaction { execute_task(aaa) } }
|
|
114
|
+
assert_raises(NameError) do
|
|
115
|
+
@config.execute_task(ccc)
|
|
116
|
+
end
|
|
117
|
+
assert @config.state[:aaa]
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def test_on_rollback_called_twice_should_result_in_last_rollback_block_being_effective
|
|
121
|
+
aaa = new_task(@config, :aaa) do
|
|
122
|
+
transaction do
|
|
123
|
+
on_rollback { (state[:rollback] ||= []) << :first }
|
|
124
|
+
on_rollback { (state[:rollback] ||= []) << :second }
|
|
125
|
+
raise "boom"
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
assert_raises(RuntimeError) do
|
|
130
|
+
@config.execute_task(aaa)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
assert_equal [:second], @config.state[:rollback]
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def test_find_and_execute_task_should_raise_error_when_task_cannot_be_found
|
|
137
|
+
@config.expects(:find_task).with("path:to:task").returns(nil)
|
|
138
|
+
assert_raises(Minestrone::NoSuchTaskError) { @config.find_and_execute_task("path:to:task") }
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def test_find_and_execute_task_should_execute_task_when_task_is_found
|
|
142
|
+
@config.expects(:find_task).with("path:to:task").returns(:found)
|
|
143
|
+
@config.expects(:execute_task).with(:found)
|
|
144
|
+
assert_nothing_raised { @config.find_and_execute_task("path:to:task") }
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def test_find_and_execute_task_with_before_option_should_trigger_callback
|
|
148
|
+
@config.expects(:find_task).with("path:to:task").returns(:found)
|
|
149
|
+
@config.expects(:trigger).with(:incoming, :found)
|
|
150
|
+
@config.expects(:execute_task).with(:found)
|
|
151
|
+
@config.find_and_execute_task("path:to:task", :before => :incoming)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def test_find_and_execute_task_with_after_option_should_trigger_callback
|
|
155
|
+
@config.expects(:find_task).with("path:to:task").returns(:found)
|
|
156
|
+
@config.expects(:trigger).with(:outgoing, :found)
|
|
157
|
+
@config.expects(:execute_task).with(:found)
|
|
158
|
+
@config.find_and_execute_task("path:to:task", :after => :outgoing)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
private
|
|
162
|
+
|
|
163
|
+
def stack_inspector
|
|
164
|
+
Proc.new do
|
|
165
|
+
(state[:trail] ||= []) << current_task.fully_qualified_name
|
|
166
|
+
data = state[current_task.name] = {}
|
|
167
|
+
data[:stack] = task_call_frames.map { |frame| frame.task.fully_qualified_name }
|
|
168
|
+
data[:history] = rollback_requests && rollback_requests.map { |frame| frame.task.fully_qualified_name }
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def new_task(namespace, name, options = {}, &block)
|
|
173
|
+
block ||= stack_inspector
|
|
174
|
+
namespace.tasks[name] = Minestrone::TaskDefinition.new(name, namespace, &block)
|
|
175
|
+
end
|
|
176
|
+
end
|