stackfu 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|