stackfu 0.1.0
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.
- data/CHANGELOG +24 -0
- data/Manifest +118 -0
- data/README +0 -0
- data/README.md +218 -0
- data/Rakefile +73 -0
- data/bin/stackfu +10 -0
- data/lib/stackfu/api_hooks.rb +17 -0
- data/lib/stackfu/app.rb +69 -0
- data/lib/stackfu/commands/command.rb +127 -0
- data/lib/stackfu/commands/config_command.rb +43 -0
- data/lib/stackfu/commands/deploy_command.rb +144 -0
- data/lib/stackfu/commands/dump_command.rb +101 -0
- data/lib/stackfu/commands/generate_command.rb +107 -0
- data/lib/stackfu/commands/help_command.rb +28 -0
- data/lib/stackfu/commands/list_command.rb +26 -0
- data/lib/stackfu/commands/publish_command.rb +108 -0
- data/lib/stackfu/commands/server_command.rb +124 -0
- data/lib/stackfu/helpers/providers_credentials.rb +82 -0
- data/lib/stackfu/helpers/rendering.rb +201 -0
- data/lib/stackfu/operating_systems.rb +30 -0
- data/lib/stackfu.rb +88 -0
- data/stackfu-installer/config/01-controls.yml +17 -0
- data/stackfu-installer/config/02-requirements.yml +3 -0
- data/stackfu-installer/config/03-scripts.yml +16 -0
- data/stackfu-installer/config/04-validations.yml +3 -0
- data/stackfu-installer/script/dotfiles_installation.sh.erb +22 -0
- data/stackfu-installer/script/github_credentials_setup.sh.erb +31 -0
- data/stackfu-installer/script/nginx_and_passenger.sh.erb +83 -0
- data/stackfu-installer/script/redis_installation.sh.erb +33 -0
- data/stackfu-installer/script/resque_installation.sh.erb +7 -0
- data/stackfu-installer/script/ruby_environment.sh.erb +20 -0
- data/stackfu-installer/script/stackfu.sh.erb +114 -0
- data/stackfu-installer/stack.yml +5 -0
- data/stackfu.gemspec +60 -0
- data/templates/01-controls.yml.erb +31 -0
- data/templates/02-requirements.yml.erb +26 -0
- data/templates/03-scripts.yml.erb +34 -0
- data/templates/04-validations.yml.erb +17 -0
- data/templates/script.sh.erb +7 -0
- data/templates/stack.yml.erb +17 -0
- data/test/fixtures/add_server_error +7 -0
- data/test/fixtures/deployment_add +7 -0
- data/test/fixtures/deployment_add_error +8 -0
- data/test/fixtures/deployments +7 -0
- data/test/fixtures/logs +7 -0
- data/test/fixtures/logs_partial +7 -0
- data/test/fixtures/plugin_add +6 -0
- data/test/fixtures/plugin_add_error +8 -0
- data/test/fixtures/plugin_deployment_add +7 -0
- data/test/fixtures/plugin_not_found +7 -0
- data/test/fixtures/plugin_unauthorized +8 -0
- data/test/fixtures/plugins +7 -0
- data/test/fixtures/plugins_by_name +7 -0
- data/test/fixtures/plugins_by_name_other +7 -0
- data/test/fixtures/plugins_empty +7 -0
- data/test/fixtures/plugins_multiple +7 -0
- data/test/fixtures/providers +7 -0
- data/test/fixtures/providers_servers +7 -0
- data/test/fixtures/server_add +6 -0
- data/test/fixtures/server_add_dupe +7 -0
- data/test/fixtures/server_add_error +7 -0
- data/test/fixtures/server_delete +7 -0
- data/test/fixtures/server_delete_error +7 -0
- data/test/fixtures/servers +7 -0
- data/test/fixtures/servers_by_name +7 -0
- data/test/fixtures/servers_empty +8 -0
- data/test/fixtures/servers_not_found +26 -0
- data/test/fixtures/servers_unauthorized +8 -0
- data/test/fixtures/servers_webbynode +7 -0
- data/test/fixtures/stack/stackfu-installer/config/01-controls.yml +22 -0
- data/test/fixtures/stack/stackfu-installer/config/02-requirements.yml +1 -0
- data/test/fixtures/stack/stackfu-installer/config/03-scripts.yml +23 -0
- data/test/fixtures/stack/stackfu-installer/config/04-validations.yml +1 -0
- data/test/fixtures/stack/stackfu-installer/script/dotfiles_installation.sh.erb +22 -0
- data/test/fixtures/stack/stackfu-installer/script/github_credentials_setup.sh.erb +31 -0
- data/test/fixtures/stack/stackfu-installer/script/nginx_and_passenger.sh.erb +83 -0
- data/test/fixtures/stack/stackfu-installer/script/redis_installation.sh.erb +33 -0
- data/test/fixtures/stack/stackfu-installer/script/resque_installation.sh.erb +7 -0
- data/test/fixtures/stack/stackfu-installer/script/ruby_environment.sh.erb +20 -0
- data/test/fixtures/stack/stackfu-installer/script/stackfu.sh.erb +76 -0
- data/test/fixtures/stack/stackfu-installer/stack.yml +5 -0
- data/test/fixtures/stack_add +6 -0
- data/test/fixtures/stack_add_error +8 -0
- data/test/fixtures/stack_add_error_dupe +8 -0
- data/test/fixtures/stack_adds_by_name +7 -0
- data/test/fixtures/stack_delete_not_found +7 -0
- data/test/fixtures/stacks +7 -0
- data/test/fixtures/stacks_by_name +7 -0
- data/test/fixtures/stacks_by_name_other +7 -0
- data/test/fixtures/stacks_empty +7 -0
- data/test/fixtures/stacks_multiple +7 -0
- data/test/fixtures/stacks_not_found +7 -0
- data/test/fixtures/stacks_realworld +7 -0
- data/test/fixtures/stacks_stackfu-installer +7 -0
- data/test/fixtures/stacks_unauthorized +8 -0
- data/test/fixtures/stacks_with_controls +0 -0
- data/test/fixtures/users +7 -0
- data/test/fixtures/users_no_credentials +7 -0
- data/test/fixtures/users_update +6 -0
- data/test/stack.yml +26 -0
- data/test/support/custom_matchers.rb +69 -0
- data/test/support/fixtures.rb +98 -0
- data/test/support/io_stub.rb +10 -0
- data/test/support/web_fixtures.rb +91 -0
- data/test/test_helper.rb +186 -0
- data/test/unit/commands/test_command.rb +112 -0
- data/test/unit/commands/test_config_command.rb +92 -0
- data/test/unit/commands/test_deploy_command.rb +303 -0
- data/test/unit/commands/test_dump_command.rb +155 -0
- data/test/unit/commands/test_generate_command.rb +112 -0
- data/test/unit/commands/test_help_command.rb +33 -0
- data/test/unit/commands/test_list_command.rb +63 -0
- data/test/unit/commands/test_publish_command.rb +265 -0
- data/test/unit/commands/test_server_command.rb +259 -0
- data/test/unit/helpers/test_rendering.rb +141 -0
- data/test/unit/test_array.rb +26 -0
- data/test/unit/test_provider.rb +14 -0
- data/test/unit/test_stackfu.rb +27 -0
- metadata +311 -0
data/test/stack.yml
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
type:
|
|
3
|
+
name: "test"
|
|
4
|
+
#
|
|
5
|
+
# TODO: Before submitting this stack, you have to specify
|
|
6
|
+
# the attributes below.
|
|
7
|
+
#
|
|
8
|
+
# Available operating systems
|
|
9
|
+
#
|
|
10
|
+
# For OS Use
|
|
11
|
+
# -------------------- --------------------
|
|
12
|
+
# Debian 5.0 debian_50
|
|
13
|
+
# Gentoo 2008 gentoo_2008
|
|
14
|
+
# Ubuntu 8.10 ubuntu_810
|
|
15
|
+
# Fedora 10 fedora_10
|
|
16
|
+
# Arch 2009 arch_2009
|
|
17
|
+
# Ubuntu 9.04 ubuntu_904
|
|
18
|
+
# Centos 5.2 centos_52
|
|
19
|
+
# ArchLinux 2009 arch_2009
|
|
20
|
+
# Ubuntu 8.04 ubuntu_804
|
|
21
|
+
# Centos 5.3 centos_53
|
|
22
|
+
#
|
|
23
|
+
# operating_system:
|
|
24
|
+
# description: "Enter a description for this stack here"
|
|
25
|
+
# tags: [add, some, tags]
|
|
26
|
+
#
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
module CustomMatchers
|
|
2
|
+
custom_matcher :be_nil do |receiver, matcher, args|
|
|
3
|
+
matcher.positive_failure_message = "Expected #{receiver} to be nil but it wasn't"
|
|
4
|
+
matcher.negative_failure_message = "Expected #{receiver} not to be nil but it was"
|
|
5
|
+
receiver.nil?
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
custom_matcher :be_blank do |receiver, matcher, args|
|
|
9
|
+
matcher.positive_failure_message = "Expected #{receiver} to be blank but it wasn't"
|
|
10
|
+
matcher.negative_failure_message = "Expected #{receiver} not to be blank but it was"
|
|
11
|
+
receiver.blank?
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
custom_matcher :be_true do |receiver, matcher, args|
|
|
15
|
+
matcher.positive_failure_message = "Expected #{receiver} to be true but it wasn't"
|
|
16
|
+
matcher.negative_failure_message = "Expected #{receiver} not to be true but it was"
|
|
17
|
+
receiver.eql?(true)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
custom_matcher :be_false do |receiver, matcher, args|
|
|
21
|
+
matcher.positive_failure_message = "Expected #{receiver} to be false but it wasn't"
|
|
22
|
+
matcher.negative_failure_message = "Expected #{receiver} not to be false but it was"
|
|
23
|
+
receiver.eql?(false)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
custom_matcher :be_valid do |receiver, matcher, args|
|
|
27
|
+
matcher.positive_failure_message = "Expected to be valid but it was invalid #{receiver.errors.inspect}"
|
|
28
|
+
matcher.negative_failure_message = "Expected to be invalid but it was valid #{receiver.errors.inspect}"
|
|
29
|
+
receiver.valid?
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
custom_matcher :be_of_type do |receiver, matcher, args|
|
|
33
|
+
klass = args[0]
|
|
34
|
+
matcher.positive_failure_message = "Expected #{receiver} to be of type #{klass} but it wasn't"
|
|
35
|
+
matcher.negative_failure_message = "Expected #{receiver} to not be of type #{klass} but it was"
|
|
36
|
+
receiver.kind_of?(klass)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
custom_matcher :start_with do |receiver, matcher, args|
|
|
40
|
+
s = args[0]
|
|
41
|
+
matcher.positive_failure_message = "Expected #{receiver} to start with '#{s}' but it didn't"
|
|
42
|
+
matcher.negative_failure_message = "Expected #{receiver} not to start with '#{s}' but it did"
|
|
43
|
+
true if receiver =~ /^#{s}/
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
custom_matcher :have_error_on do |receiver, matcher, args|
|
|
47
|
+
receiver.valid?
|
|
48
|
+
attribute = args[0]
|
|
49
|
+
expected_message = args[1]
|
|
50
|
+
|
|
51
|
+
if expected_message.nil?
|
|
52
|
+
matcher.positive_failure_message = "#{receiver} had no errors on #{attribute}"
|
|
53
|
+
matcher.negative_failure_message = "#{receiver} had errors on #{attribute} #{receiver.errors.inspect}"
|
|
54
|
+
!receiver.errors.on(attribute).blank?
|
|
55
|
+
else
|
|
56
|
+
actual = receiver.errors.on(attribute)
|
|
57
|
+
matcher.positive_failure_message = %Q(Expected error on #{attribute} to be "#{expected_message}" but was "#{actual}")
|
|
58
|
+
matcher.negative_failure_message = %Q(Expected error on #{attribute} not to be "#{expected_message}" but was "#{actual}")
|
|
59
|
+
actual == expected_message
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
custom_matcher :have_index do |receiver, matcher, args|
|
|
64
|
+
index_name = args[0]
|
|
65
|
+
matcher.positive_failure_message = "#{receiver} does not have index named #{index_name}, but should"
|
|
66
|
+
matcher.negative_failure_message = "#{receiver} does have index named #{index_name}, but should not"
|
|
67
|
+
!receiver.collection.index_information.detect { |index| index[0] == index_name }.nil?
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
module Fixtures
|
|
2
|
+
ApiUrlPrefix = StackFu::API.gsub(/api/, "flipper:abc123@api")
|
|
3
|
+
|
|
4
|
+
def with_logs(id, kind=nil)
|
|
5
|
+
register :get, :path => "deployments/#{id}/logs", :kind => kind
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def with_providers(kind=nil)
|
|
9
|
+
register :get, :path => "providers", :kind => kind
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def with_provider(id, kind=nil)
|
|
13
|
+
register :get, :path => "providers/#{id}/#{kind}", :fixture => "providers", :kind => kind
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def with_stacks(kind=nil, params=nil)
|
|
17
|
+
register :get, :path => "stacks", :kind => kind, :params => params
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def with_stack_add(kind=nil)
|
|
21
|
+
register :post, :path => "stacks", :fixture => "stack_add", :kind => kind
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def with_stack_delete(id=nil, fixture="stack_add", kind=nil)
|
|
25
|
+
register :delete, :path => "stacks/#{id}", :fixture => fixture, :kind => kind
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def with_plugins(kind=nil, params=nil)
|
|
29
|
+
register :get, :path => "plugins", :kind => kind, :params => params
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def with_plugin_add(kind=nil)
|
|
33
|
+
register :post, :path => "plugins", :fixture => "plugin_add", :kind => kind
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def with_plugin_delete(id=nil, fixture="plugin_add", kind=nil)
|
|
37
|
+
register :delete, :path => "plugins/#{id}", :fixture => fixture, :kind => kind
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def with_server_list(kind=nil, params=nil)
|
|
41
|
+
register :get, :path => "servers", :kind => kind, :params => params
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def with_server_add(kind=nil)
|
|
45
|
+
register :post, :path => "servers", :fixture => "server_add", :kind => kind
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def with_server_delete(kind=nil)
|
|
49
|
+
register :delete, :path => "servers/4afe06b9e1054e1e00000002", :fixture => "server_add", :kind => kind
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def with_user(id, kind=nil)
|
|
53
|
+
register :get, :path => "users/#{id}/#{kind}", :fixture => "users", :kind => kind
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def with_users(kind=nil)
|
|
57
|
+
register :get, :path => "users", :kind => kind
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def with_users_add(kind=nil)
|
|
61
|
+
register :post, :path => "users", :fixture => "users_update", :kind => kind
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def with_users_update(id=nil, kind=nil)
|
|
65
|
+
register :put, :path => "users#{id ? "/#{id}" : ""}", :fixture => "users_update", :kind => kind
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def with_new_deployment(kind=nil)
|
|
69
|
+
register :post, :path => "deployments", :fixture => "deployment_add", :kind => kind
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def with_new_plugin_deployment(kind=nil)
|
|
73
|
+
register :post, :path => "deployments", :fixture => "plugin_deployment_add", :kind => kind
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
def register(method, options) # path, kind, fixture=path)
|
|
79
|
+
kind = options[:kind] ? "_#{options[:kind]}" : ""
|
|
80
|
+
path = options[:path] or raise "path is mandatory"
|
|
81
|
+
fixture = options[:fixture] || options[:path]
|
|
82
|
+
params = options[:params] ? "?#{options[:params]}" : ""
|
|
83
|
+
|
|
84
|
+
# d "Registering: #{method} #{ApiUrlPrefix}/#{path}.json#{params} => #{fixture}#{kind}"
|
|
85
|
+
FakeWeb.register_uri(method, "#{ApiUrlPrefix}/#{path}.json#{params}",
|
|
86
|
+
:response => fixture("#{fixture}#{kind}"))
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def fixture(*path)
|
|
90
|
+
file_name = File.join(File.dirname(__FILE__), "../fixtures", path)
|
|
91
|
+
raise "Fixture #{file_name} was not found" unless File.exists?(file_name)
|
|
92
|
+
file_name
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def fixture_contents(*path)
|
|
96
|
+
File.open(File.join(File.dirname(__FILE__), "../fixtures", path), "r").readlines.join("")
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
module WebFixtures
|
|
2
|
+
def stub_file(*path)
|
|
3
|
+
File.join(File.dirname(__FILE__), "../stubs", path)
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def stub_file_contents(*path)
|
|
7
|
+
File.open(File.join(File.dirname(__FILE__), "../stubs", path), "r").readlines.join("")
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def fake_webby_os(os)
|
|
11
|
+
yaml_api = "https://manager.webbynode.com/api/yaml"
|
|
12
|
+
|
|
13
|
+
response = stub_file_contents("webbynode", "fcoury@me.com", "webbies")
|
|
14
|
+
response.gsub! ":os_template: 32", ":os_template: #{os}"
|
|
15
|
+
|
|
16
|
+
FakeWeb.clean_registry
|
|
17
|
+
FakeWeb.register_uri(:post, "#{yaml_api}/webbies", :response => response)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def fake_webbynode
|
|
21
|
+
FakeWeb.clean_registry
|
|
22
|
+
yaml_api = "https://manager.webbynode.com/api/yaml"
|
|
23
|
+
|
|
24
|
+
users = {
|
|
25
|
+
"fcoury@me.com" => "1da75b299084548ccd84990e463d4266e96abc5a",
|
|
26
|
+
"ctab@me.com" => "2da127cbffd4266e96abc5a8bbc671e1c2bb3bac",
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
fakes = {
|
|
30
|
+
"webbies" => "webbies",
|
|
31
|
+
"client" => "client"
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
users.each_pair do |email, token|
|
|
35
|
+
fakes.each_pair do |key, value|
|
|
36
|
+
FakeWeb.register_uri(:post, "#{yaml_api}/#{key}", :email => email, :response => stub_file("webbynode", email, value))
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
SlicehostUsers = {
|
|
42
|
+
"felipe.coury@gmail.com" => "6ed05e7521dfb6a19c98a84f2d0a28fdffc1bf00e050fe7114a66d3049e9715e"
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
def fake_slicehost
|
|
46
|
+
FakeWeb.clean_registry
|
|
47
|
+
|
|
48
|
+
SlicehostUsers.each_pair do |email, token|
|
|
49
|
+
slicehost_register(:get, "/slices.xml", "slices")
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def fake_slicehost_no_slice
|
|
54
|
+
FakeWeb.clean_registry
|
|
55
|
+
|
|
56
|
+
SlicehostUsers.each_pair do |email, token|
|
|
57
|
+
slicehost_register(:get, "/slices.xml", "slices_none")
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def fake_slicehost_os(os)
|
|
62
|
+
user = SlicehostUsers.keys.first
|
|
63
|
+
token = SlicehostUsers.values.first
|
|
64
|
+
|
|
65
|
+
xml_api = "https://#{token}@api.slicehost.com"
|
|
66
|
+
|
|
67
|
+
response = stub_file_contents("slicehost", user, "slices")
|
|
68
|
+
response.gsub! "Content-Length: 554", "Content-Length: 553" if os.to_s.size < 2
|
|
69
|
+
response.gsub! '<image-id type="integer">10</image-id>', "<image-id type=\"integer\">#{os}</image-id>"
|
|
70
|
+
|
|
71
|
+
FakeWeb.clean_registry
|
|
72
|
+
FakeWeb.register_uri(:get, "#{xml_api}/slices.xml", :response => response)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def fake_slice_creation(success=true)
|
|
76
|
+
slicehost_register :post, "/slices.xml", "create_slice"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def fake_slice_deletion(slice_id, error="")
|
|
80
|
+
slicehost_register :delete, "/slices/#{slice_id}.xml", "delete_slice#{error.present? ? "_#{error}" : ""}"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def slicehost_register(method, uri, file, user=SlicehostUsers.keys.first)
|
|
84
|
+
token = SlicehostUsers[user]
|
|
85
|
+
|
|
86
|
+
file = stub_file("slicehost", user, file)
|
|
87
|
+
prefix = "https://#{token}@api.slicehost.com"
|
|
88
|
+
|
|
89
|
+
FakeWeb.register_uri(method, "#{prefix}#{uri}", :response => file)
|
|
90
|
+
end
|
|
91
|
+
end
|
data/test/test_helper.rb
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../lib/stackfu')
|
|
2
|
+
|
|
3
|
+
gem 'fcoury-matchy', '0.4.0'
|
|
4
|
+
gem 'shoulda', '2.10.2'
|
|
5
|
+
gem 'timecop', '0.3.1'
|
|
6
|
+
gem 'mocha', '>=0.9.4'
|
|
7
|
+
gem 'fakeweb', '>=1.2.7'
|
|
8
|
+
gem 'phocus', '>=1.1'
|
|
9
|
+
gem 'uuid', '>=2.0.2'
|
|
10
|
+
|
|
11
|
+
require 'matchy'
|
|
12
|
+
require 'shoulda'
|
|
13
|
+
require 'fakeweb'
|
|
14
|
+
require 'phocus'
|
|
15
|
+
require 'mocha'
|
|
16
|
+
require 'uuid'
|
|
17
|
+
require 'pp'
|
|
18
|
+
|
|
19
|
+
require 'support/custom_matchers'
|
|
20
|
+
require 'support/io_stub'
|
|
21
|
+
require 'support/fixtures'
|
|
22
|
+
|
|
23
|
+
HighLine.track_eof = false
|
|
24
|
+
FakeWeb.allow_net_connect = false
|
|
25
|
+
$testing = true
|
|
26
|
+
|
|
27
|
+
class Test::Unit::TestCase
|
|
28
|
+
include CustomMatchers
|
|
29
|
+
include IoStub
|
|
30
|
+
include Fixtures
|
|
31
|
+
include StackFu
|
|
32
|
+
|
|
33
|
+
def command(cmd, settings_present=true)
|
|
34
|
+
cmd ||= ""
|
|
35
|
+
App.expects(:settings?).returns(settings_present)
|
|
36
|
+
if settings_present
|
|
37
|
+
App.any_instance.expects(:load_config).returns({ :login => "flipper", :token => "abc123" })
|
|
38
|
+
end
|
|
39
|
+
App.new(cmd.split(" ")).start
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def setup
|
|
43
|
+
FakeWeb.clean_registry
|
|
44
|
+
@orig_stdout = $stdout
|
|
45
|
+
$stdout = StringIO.new
|
|
46
|
+
$actions = []
|
|
47
|
+
$asked = []
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def when_asked_to_choose(what, options)
|
|
51
|
+
$actions << { :type => "choose", :action => options.merge(:prompt => what) }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def when_asked(what, options)
|
|
55
|
+
$actions << { :type => "ask", :action => [what, options[:answer]] }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def agree_with(what)
|
|
59
|
+
$actions << { :type => "agree", :action => what }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def disagree_of(what)
|
|
63
|
+
$actions << { :type => "disagree", :action => what }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def teardown
|
|
67
|
+
$stdout = @orig_stdout
|
|
68
|
+
asked = $asked.map { |s| " - #{s}" }.join("\n")
|
|
69
|
+
unless $actions.empty?
|
|
70
|
+
questions = $actions.map do |q|
|
|
71
|
+
info = case q[:type]
|
|
72
|
+
when "ask"
|
|
73
|
+
q[:action].first
|
|
74
|
+
|
|
75
|
+
when "choose"
|
|
76
|
+
txt = "'#{q[:action][:prompt]}'"
|
|
77
|
+
txt << " with options '#{q[:action][:with_options].join(", ")}'" if q[:action][:with_options]
|
|
78
|
+
else
|
|
79
|
+
q[:action]
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
" - #{q[:type]} #{info}"
|
|
83
|
+
end.join("\n")
|
|
84
|
+
fail "Expected questions not asked:\n#{questions}\nQuestions responded:\n#{asked}\n"
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
module StackFu
|
|
90
|
+
class ConfigCommand
|
|
91
|
+
def save_config(login, token)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
class Array
|
|
97
|
+
def delete_first(value=nil)
|
|
98
|
+
result = nil
|
|
99
|
+
self.each_index do |i|
|
|
100
|
+
if block_given? and yield(self[i])
|
|
101
|
+
result = self.delete_at(i)
|
|
102
|
+
break
|
|
103
|
+
end
|
|
104
|
+
if value and self[i] == value
|
|
105
|
+
result = self.delete_at(i)
|
|
106
|
+
break
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
return result unless result and result.empty?
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
module Kernel
|
|
115
|
+
def choose(*items, &details)
|
|
116
|
+
if (action = $actions.first)
|
|
117
|
+
if action[:type] == "choose"
|
|
118
|
+
item = OpenStruct.new
|
|
119
|
+
class << item
|
|
120
|
+
def choice(*args)
|
|
121
|
+
(self.choices||=[]) << args
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
details.call item
|
|
126
|
+
|
|
127
|
+
exp_action = $actions.shift[:action]
|
|
128
|
+
item.prompt.should == exp_action[:prompt]
|
|
129
|
+
|
|
130
|
+
exp_action[:with_options].try(:each) do |opt|
|
|
131
|
+
item.choices.flatten.include?(opt).should == true
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
if (answer = exp_action[:answer]).is_a?(Numeric)
|
|
135
|
+
item.choices[answer][0]
|
|
136
|
+
else
|
|
137
|
+
answer
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
else
|
|
141
|
+
fail "Unexpected choose"
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def ask(question, answer_type = String, &details)
|
|
146
|
+
if (action = $actions.first)
|
|
147
|
+
matches = if (expected = action[:action].first).is_a?(Regexp)
|
|
148
|
+
question =~ expected
|
|
149
|
+
else
|
|
150
|
+
question == expected
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
if action[:type] == "ask" and matches
|
|
154
|
+
$asked << "responded to ask #{action[:action].first} with #{action[:action].last}"
|
|
155
|
+
exp_action = $actions.shift
|
|
156
|
+
result = exp_action[:action].last
|
|
157
|
+
return result
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
fail "Expected to #{action[:type]} #{action[:action].inspect} but asked #{question.inspect}"
|
|
161
|
+
else
|
|
162
|
+
fail "Unexpected ask #{question.inspect}"
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def agree(yes_or_no_question, character = nil)
|
|
167
|
+
action = $actions.first
|
|
168
|
+
if action and action[:action] == yes_or_no_question
|
|
169
|
+
if action[:type] == "agree" or action[:type] == "disagree"
|
|
170
|
+
$asked << "#{action[:type]}d to #{action[:action]}"
|
|
171
|
+
result = $actions.shift[:type] == "agree"
|
|
172
|
+
# d "#{action[:type]} #{yes_or_no_question} => #{result}"
|
|
173
|
+
return result
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
if action
|
|
178
|
+
fail "Expected to #{action[:type]} #{action[:action].inspect} but asked to agree with #{yes_or_no_question.inspect}"
|
|
179
|
+
else
|
|
180
|
+
fail "Unexpected agreement #{yes_or_no_question.inspect}"
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def d(x); $stderr.puts x; end
|
|
186
|
+
def ppd(x); $stderr.puts x.pretty_inspect; end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
require File.dirname(__FILE__) + '/../../test_helper.rb'
|
|
2
|
+
|
|
3
|
+
class SampleCmd < StackFu::Command
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
class TestCommand < Test::Unit::TestCase
|
|
7
|
+
should "return true to params? if params given" do
|
|
8
|
+
SampleCmd.new(["hello", "world"]).params?.should be_true
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
should "return false to params? if params given" do
|
|
12
|
+
SampleCmd.new(["--hello", "--world"]).params?.should be_false
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
should "allow command with no subcommand" do
|
|
16
|
+
cmd = SampleCmd.new(["--force"])
|
|
17
|
+
cmd.subcommand.should == :default
|
|
18
|
+
cmd.parameters.should == []
|
|
19
|
+
cmd.options.should == { :force => true }
|
|
20
|
+
|
|
21
|
+
cmd = SampleCmd.new(["mine", "yellow", "--azul=b"])
|
|
22
|
+
cmd.subcommand.should == :mine
|
|
23
|
+
cmd.parameters.should == ["yellow"]
|
|
24
|
+
cmd.options.should == { :azul => "b" }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
should "parse as parameter if = is present" do
|
|
28
|
+
cmd = SampleCmd.new(["--force", "--ssh_passphrase="])
|
|
29
|
+
cmd.options.should == { :force => true, :ssh_passphrase => "" }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
should "parse subcommand and options" do
|
|
33
|
+
cmd = SampleCmd.new(["copy", "--force", "/var/opt/music/mine.mp3", "/home/fcoury"])
|
|
34
|
+
cmd.subcommand.should == :copy
|
|
35
|
+
cmd.parameters.should == ["/var/opt/music/mine.mp3", "/home/fcoury"]
|
|
36
|
+
cmd.options.should == { :force => true }
|
|
37
|
+
|
|
38
|
+
cmd = SampleCmd.new(["move", "/var/log/1.log", "--mode=FAILSAFE", "/var/log/one.log"])
|
|
39
|
+
cmd.subcommand.should == :move
|
|
40
|
+
cmd.parameters.should == ["/var/log/1.log", "/var/log/one.log"]
|
|
41
|
+
cmd.options.should == { :mode => "FAILSAFE" }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
should "allow commands to have aliases" do
|
|
45
|
+
SampleCmd.aliases "smp", "sampoo"
|
|
46
|
+
Command.create("smp").class.should == SampleCmd
|
|
47
|
+
Command.create("sampoo").class.should == SampleCmd
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
should "be valid if only a default subcommand exists" do
|
|
51
|
+
SampleCmd.new.should be_valid
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
should "allow named parameters" do
|
|
55
|
+
class NewCmd < Command
|
|
56
|
+
subcommand :move, :required_parameters => [:provider, :server_name]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
lambda {
|
|
60
|
+
cmd = NewCmd.new(["move"])
|
|
61
|
+
}.should raise_error(Exceptions::InvalidCommand, /requires 2 parameters/)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
should "inflex the requirement error" do
|
|
65
|
+
class NewCmd < Command
|
|
66
|
+
subcommand :move, :required_parameters => [:server_name]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
lambda {
|
|
70
|
+
cmd = NewCmd.new(["move"])
|
|
71
|
+
}.should raise_error(Exceptions::InvalidCommand, /requires 1 parameter\./)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
should "allow subcommand aliases" do
|
|
75
|
+
class Cmd < Command
|
|
76
|
+
alias_subcommand :list => :default
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
cmd = Cmd.new(["list"])
|
|
80
|
+
cmd.subcommand.should == :default
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
should "throw a specific exception when subcommand doesn't exist" do
|
|
84
|
+
class ListCommand < Command
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
lambda {
|
|
88
|
+
ListCommand.new(["find"]).run
|
|
89
|
+
}.should raise_error(Exceptions::InvalidCommand, /Command list doesn't have a subcommand "find". Try "stackfu list help" for more information./)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
should "allow the error message for inexistent subcommand to be customized" do
|
|
93
|
+
class ExampleCommand < Command
|
|
94
|
+
error_messages :missing_subcommand => "There's no such thing as %s"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
lambda {
|
|
98
|
+
ExampleCommand.new(["test"]).run
|
|
99
|
+
}.should raise_error(Exceptions::InvalidCommand, /There's no such thing as test/)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
should "allow the error message for missing params to be customized" do
|
|
103
|
+
class ExampleCommand < Command
|
|
104
|
+
subcommand :test, :required_parameters => [:sex, :age]
|
|
105
|
+
error_messages :missing_params => "You have to provide sex and age, dude!"
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
lambda {
|
|
109
|
+
ExampleCommand.new(["test"]).run
|
|
110
|
+
}.should raise_error(Exceptions::InvalidCommand, /You have to provide sex and age, dude!/)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
require File.dirname(__FILE__) + '/../../test_helper.rb'
|
|
2
|
+
|
|
3
|
+
class TestConfigCommand < Test::Unit::TestCase
|
|
4
|
+
context "provider command" do
|
|
5
|
+
should "recognize Webbynode" do
|
|
6
|
+
with_users
|
|
7
|
+
with_users_update("4afe06b8e1054e1e00000001")
|
|
8
|
+
|
|
9
|
+
when_asked "Webbynode Login:", :answer => "fcoury@me.com"
|
|
10
|
+
when_asked "Webbynode Token:", :answer => "abc123"
|
|
11
|
+
|
|
12
|
+
command "config webbynode"
|
|
13
|
+
stdout.should =~ /Webbynode credentials saved./
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
should "recognize Webbynode" do
|
|
17
|
+
with_users
|
|
18
|
+
with_users_update("4afe06b8e1054e1e00000001")
|
|
19
|
+
|
|
20
|
+
when_asked "Webbynode Login:", :answer => "fcoury@me.com"
|
|
21
|
+
when_asked "Webbynode Token:", :answer => "abc123"
|
|
22
|
+
|
|
23
|
+
command "config webbynode"
|
|
24
|
+
stdout.should =~ /Webbynode credentials saved./
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
should "recognize Slicehost" do
|
|
28
|
+
with_users
|
|
29
|
+
with_users_update("4afe06b8e1054e1e00000001")
|
|
30
|
+
|
|
31
|
+
when_asked "", :answer => "6ed05e7521dfb6a19c98a84f2d0a28fdffc1bf00e050fe7114a66d3049e9715e"
|
|
32
|
+
|
|
33
|
+
command "config slicehost"
|
|
34
|
+
stdout.should =~ /Slicehost credentials saved./
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
context "default command" do
|
|
39
|
+
should "skip asking login, token and confirmation if --login and --token provided" do
|
|
40
|
+
ConfigCommand.any_instance.expects(:save_config).with("flipper", "abc123")
|
|
41
|
+
command "config --login=flipper --token=abc123"
|
|
42
|
+
stdout.should =~ /Configuration saved/
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
should "skip asking login if --login provided" do
|
|
46
|
+
when_asked "StackFu Token: ", :answer => "abc123"
|
|
47
|
+
|
|
48
|
+
agree_with "Is this information correct? "
|
|
49
|
+
|
|
50
|
+
ConfigCommand.any_instance.expects(:save_config).with("flipper", "abc123")
|
|
51
|
+
command "config --login=flipper"
|
|
52
|
+
stdout.should =~ /Configuration saved/
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
should "skip asking token if --token provided" do
|
|
56
|
+
when_asked "StackFu Login: ", :answer => "flipper"
|
|
57
|
+
|
|
58
|
+
agree_with "Is this information correct? "
|
|
59
|
+
|
|
60
|
+
ConfigCommand.any_instance.expects(:save_config).with("flipper", "abc123")
|
|
61
|
+
command "config --token=abc123"
|
|
62
|
+
stdout.should =~ /Configuration saved/
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
should "Ask the login, token and offer to add a new server" do
|
|
66
|
+
when_asked "StackFu Login: ", :answer => "flipper"
|
|
67
|
+
when_asked "StackFu Token: ", :answer => "abc123"
|
|
68
|
+
|
|
69
|
+
agree_with "Is this information correct? "
|
|
70
|
+
|
|
71
|
+
ConfigCommand.any_instance.expects(:save_config).with("flipper", "abc123")
|
|
72
|
+
command "config"
|
|
73
|
+
stdout.should =~ /Configuration saved/
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
should "Ask the login, token again if information not correct" do
|
|
77
|
+
when_asked "StackFu Login: ", :answer => "flipper"
|
|
78
|
+
when_asked "StackFu Token: ", :answer => "abc123"
|
|
79
|
+
|
|
80
|
+
disagree_of "Is this information correct? "
|
|
81
|
+
|
|
82
|
+
when_asked "StackFu Login: ", :answer => "flipper"
|
|
83
|
+
when_asked "StackFu Token: ", :answer => "abc123"
|
|
84
|
+
|
|
85
|
+
agree_with "Is this information correct? "
|
|
86
|
+
|
|
87
|
+
ConfigCommand.any_instance.expects(:save_config).with("flipper", "abc123")
|
|
88
|
+
command "config"
|
|
89
|
+
stdout.should =~ /Configuration saved/
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|