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.
Files changed (119) hide show
  1. data/CHANGELOG +24 -0
  2. data/Manifest +118 -0
  3. data/README +0 -0
  4. data/README.md +218 -0
  5. data/Rakefile +73 -0
  6. data/bin/stackfu +10 -0
  7. data/lib/stackfu/api_hooks.rb +17 -0
  8. data/lib/stackfu/app.rb +69 -0
  9. data/lib/stackfu/commands/command.rb +127 -0
  10. data/lib/stackfu/commands/config_command.rb +43 -0
  11. data/lib/stackfu/commands/deploy_command.rb +144 -0
  12. data/lib/stackfu/commands/dump_command.rb +101 -0
  13. data/lib/stackfu/commands/generate_command.rb +107 -0
  14. data/lib/stackfu/commands/help_command.rb +28 -0
  15. data/lib/stackfu/commands/list_command.rb +26 -0
  16. data/lib/stackfu/commands/publish_command.rb +108 -0
  17. data/lib/stackfu/commands/server_command.rb +124 -0
  18. data/lib/stackfu/helpers/providers_credentials.rb +82 -0
  19. data/lib/stackfu/helpers/rendering.rb +201 -0
  20. data/lib/stackfu/operating_systems.rb +30 -0
  21. data/lib/stackfu.rb +88 -0
  22. data/stackfu-installer/config/01-controls.yml +17 -0
  23. data/stackfu-installer/config/02-requirements.yml +3 -0
  24. data/stackfu-installer/config/03-scripts.yml +16 -0
  25. data/stackfu-installer/config/04-validations.yml +3 -0
  26. data/stackfu-installer/script/dotfiles_installation.sh.erb +22 -0
  27. data/stackfu-installer/script/github_credentials_setup.sh.erb +31 -0
  28. data/stackfu-installer/script/nginx_and_passenger.sh.erb +83 -0
  29. data/stackfu-installer/script/redis_installation.sh.erb +33 -0
  30. data/stackfu-installer/script/resque_installation.sh.erb +7 -0
  31. data/stackfu-installer/script/ruby_environment.sh.erb +20 -0
  32. data/stackfu-installer/script/stackfu.sh.erb +114 -0
  33. data/stackfu-installer/stack.yml +5 -0
  34. data/stackfu.gemspec +60 -0
  35. data/templates/01-controls.yml.erb +31 -0
  36. data/templates/02-requirements.yml.erb +26 -0
  37. data/templates/03-scripts.yml.erb +34 -0
  38. data/templates/04-validations.yml.erb +17 -0
  39. data/templates/script.sh.erb +7 -0
  40. data/templates/stack.yml.erb +17 -0
  41. data/test/fixtures/add_server_error +7 -0
  42. data/test/fixtures/deployment_add +7 -0
  43. data/test/fixtures/deployment_add_error +8 -0
  44. data/test/fixtures/deployments +7 -0
  45. data/test/fixtures/logs +7 -0
  46. data/test/fixtures/logs_partial +7 -0
  47. data/test/fixtures/plugin_add +6 -0
  48. data/test/fixtures/plugin_add_error +8 -0
  49. data/test/fixtures/plugin_deployment_add +7 -0
  50. data/test/fixtures/plugin_not_found +7 -0
  51. data/test/fixtures/plugin_unauthorized +8 -0
  52. data/test/fixtures/plugins +7 -0
  53. data/test/fixtures/plugins_by_name +7 -0
  54. data/test/fixtures/plugins_by_name_other +7 -0
  55. data/test/fixtures/plugins_empty +7 -0
  56. data/test/fixtures/plugins_multiple +7 -0
  57. data/test/fixtures/providers +7 -0
  58. data/test/fixtures/providers_servers +7 -0
  59. data/test/fixtures/server_add +6 -0
  60. data/test/fixtures/server_add_dupe +7 -0
  61. data/test/fixtures/server_add_error +7 -0
  62. data/test/fixtures/server_delete +7 -0
  63. data/test/fixtures/server_delete_error +7 -0
  64. data/test/fixtures/servers +7 -0
  65. data/test/fixtures/servers_by_name +7 -0
  66. data/test/fixtures/servers_empty +8 -0
  67. data/test/fixtures/servers_not_found +26 -0
  68. data/test/fixtures/servers_unauthorized +8 -0
  69. data/test/fixtures/servers_webbynode +7 -0
  70. data/test/fixtures/stack/stackfu-installer/config/01-controls.yml +22 -0
  71. data/test/fixtures/stack/stackfu-installer/config/02-requirements.yml +1 -0
  72. data/test/fixtures/stack/stackfu-installer/config/03-scripts.yml +23 -0
  73. data/test/fixtures/stack/stackfu-installer/config/04-validations.yml +1 -0
  74. data/test/fixtures/stack/stackfu-installer/script/dotfiles_installation.sh.erb +22 -0
  75. data/test/fixtures/stack/stackfu-installer/script/github_credentials_setup.sh.erb +31 -0
  76. data/test/fixtures/stack/stackfu-installer/script/nginx_and_passenger.sh.erb +83 -0
  77. data/test/fixtures/stack/stackfu-installer/script/redis_installation.sh.erb +33 -0
  78. data/test/fixtures/stack/stackfu-installer/script/resque_installation.sh.erb +7 -0
  79. data/test/fixtures/stack/stackfu-installer/script/ruby_environment.sh.erb +20 -0
  80. data/test/fixtures/stack/stackfu-installer/script/stackfu.sh.erb +76 -0
  81. data/test/fixtures/stack/stackfu-installer/stack.yml +5 -0
  82. data/test/fixtures/stack_add +6 -0
  83. data/test/fixtures/stack_add_error +8 -0
  84. data/test/fixtures/stack_add_error_dupe +8 -0
  85. data/test/fixtures/stack_adds_by_name +7 -0
  86. data/test/fixtures/stack_delete_not_found +7 -0
  87. data/test/fixtures/stacks +7 -0
  88. data/test/fixtures/stacks_by_name +7 -0
  89. data/test/fixtures/stacks_by_name_other +7 -0
  90. data/test/fixtures/stacks_empty +7 -0
  91. data/test/fixtures/stacks_multiple +7 -0
  92. data/test/fixtures/stacks_not_found +7 -0
  93. data/test/fixtures/stacks_realworld +7 -0
  94. data/test/fixtures/stacks_stackfu-installer +7 -0
  95. data/test/fixtures/stacks_unauthorized +8 -0
  96. data/test/fixtures/stacks_with_controls +0 -0
  97. data/test/fixtures/users +7 -0
  98. data/test/fixtures/users_no_credentials +7 -0
  99. data/test/fixtures/users_update +6 -0
  100. data/test/stack.yml +26 -0
  101. data/test/support/custom_matchers.rb +69 -0
  102. data/test/support/fixtures.rb +98 -0
  103. data/test/support/io_stub.rb +10 -0
  104. data/test/support/web_fixtures.rb +91 -0
  105. data/test/test_helper.rb +186 -0
  106. data/test/unit/commands/test_command.rb +112 -0
  107. data/test/unit/commands/test_config_command.rb +92 -0
  108. data/test/unit/commands/test_deploy_command.rb +303 -0
  109. data/test/unit/commands/test_dump_command.rb +155 -0
  110. data/test/unit/commands/test_generate_command.rb +112 -0
  111. data/test/unit/commands/test_help_command.rb +33 -0
  112. data/test/unit/commands/test_list_command.rb +63 -0
  113. data/test/unit/commands/test_publish_command.rb +265 -0
  114. data/test/unit/commands/test_server_command.rb +259 -0
  115. data/test/unit/helpers/test_rendering.rb +141 -0
  116. data/test/unit/test_array.rb +26 -0
  117. data/test/unit/test_provider.rb +14 -0
  118. data/test/unit/test_stackfu.rb +27 -0
  119. metadata +311 -0
@@ -0,0 +1,6 @@
1
+ HTTP/1.1 200 OK
2
+ Connection: close
3
+ Date: Sat, 14 Nov 2009 17:34:12 GMT
4
+ Content-Type: application/json;charset=utf-8
5
+ Content-Length: 0
6
+
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,10 @@
1
+ module IoStub
2
+ def stdout
3
+ $stdout.rewind
4
+ $stdout.read
5
+ end
6
+
7
+ def debug(s)
8
+ @orig_stdout.puts s
9
+ end
10
+ 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
@@ -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