crazy-yard 3.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +19 -0
  3. data/README.md +438 -0
  4. data/bin/ey +9 -0
  5. data/lib/engineyard.rb +9 -0
  6. data/lib/engineyard/cli.rb +816 -0
  7. data/lib/engineyard/cli/api.rb +98 -0
  8. data/lib/engineyard/cli/recipes.rb +129 -0
  9. data/lib/engineyard/cli/ui.rb +275 -0
  10. data/lib/engineyard/cli/web.rb +85 -0
  11. data/lib/engineyard/config.rb +158 -0
  12. data/lib/engineyard/deploy_config.rb +65 -0
  13. data/lib/engineyard/deploy_config/ref.rb +56 -0
  14. data/lib/engineyard/error.rb +82 -0
  15. data/lib/engineyard/eyrc.rb +59 -0
  16. data/lib/engineyard/repo.rb +105 -0
  17. data/lib/engineyard/serverside_runner.rb +159 -0
  18. data/lib/engineyard/templates.rb +6 -0
  19. data/lib/engineyard/templates/ey.yml.erb +196 -0
  20. data/lib/engineyard/templates/ey_yml.rb +119 -0
  21. data/lib/engineyard/thor.rb +215 -0
  22. data/lib/engineyard/version.rb +4 -0
  23. data/lib/vendor/thor/Gemfile +15 -0
  24. data/lib/vendor/thor/LICENSE.md +20 -0
  25. data/lib/vendor/thor/README.md +35 -0
  26. data/lib/vendor/thor/lib/thor.rb +473 -0
  27. data/lib/vendor/thor/lib/thor/actions.rb +318 -0
  28. data/lib/vendor/thor/lib/thor/actions/create_file.rb +105 -0
  29. data/lib/vendor/thor/lib/thor/actions/create_link.rb +60 -0
  30. data/lib/vendor/thor/lib/thor/actions/directory.rb +119 -0
  31. data/lib/vendor/thor/lib/thor/actions/empty_directory.rb +137 -0
  32. data/lib/vendor/thor/lib/thor/actions/file_manipulation.rb +314 -0
  33. data/lib/vendor/thor/lib/thor/actions/inject_into_file.rb +109 -0
  34. data/lib/vendor/thor/lib/thor/base.rb +652 -0
  35. data/lib/vendor/thor/lib/thor/command.rb +136 -0
  36. data/lib/vendor/thor/lib/thor/core_ext/hash_with_indifferent_access.rb +80 -0
  37. data/lib/vendor/thor/lib/thor/core_ext/io_binary_read.rb +12 -0
  38. data/lib/vendor/thor/lib/thor/core_ext/ordered_hash.rb +100 -0
  39. data/lib/vendor/thor/lib/thor/error.rb +28 -0
  40. data/lib/vendor/thor/lib/thor/group.rb +282 -0
  41. data/lib/vendor/thor/lib/thor/invocation.rb +172 -0
  42. data/lib/vendor/thor/lib/thor/parser.rb +4 -0
  43. data/lib/vendor/thor/lib/thor/parser/argument.rb +74 -0
  44. data/lib/vendor/thor/lib/thor/parser/arguments.rb +171 -0
  45. data/lib/vendor/thor/lib/thor/parser/option.rb +121 -0
  46. data/lib/vendor/thor/lib/thor/parser/options.rb +218 -0
  47. data/lib/vendor/thor/lib/thor/rake_compat.rb +72 -0
  48. data/lib/vendor/thor/lib/thor/runner.rb +322 -0
  49. data/lib/vendor/thor/lib/thor/shell.rb +88 -0
  50. data/lib/vendor/thor/lib/thor/shell/basic.rb +393 -0
  51. data/lib/vendor/thor/lib/thor/shell/color.rb +148 -0
  52. data/lib/vendor/thor/lib/thor/shell/html.rb +127 -0
  53. data/lib/vendor/thor/lib/thor/util.rb +270 -0
  54. data/lib/vendor/thor/lib/thor/version.rb +3 -0
  55. data/lib/vendor/thor/thor.gemspec +24 -0
  56. data/spec/engineyard/cli/api_spec.rb +50 -0
  57. data/spec/engineyard/cli_spec.rb +28 -0
  58. data/spec/engineyard/config_spec.rb +61 -0
  59. data/spec/engineyard/deploy_config_spec.rb +194 -0
  60. data/spec/engineyard/eyrc_spec.rb +76 -0
  61. data/spec/engineyard/repo_spec.rb +83 -0
  62. data/spec/engineyard_spec.rb +7 -0
  63. data/spec/ey/console_spec.rb +57 -0
  64. data/spec/ey/deploy_spec.rb +435 -0
  65. data/spec/ey/ey_spec.rb +23 -0
  66. data/spec/ey/init_spec.rb +123 -0
  67. data/spec/ey/list_environments_spec.rb +120 -0
  68. data/spec/ey/login_spec.rb +33 -0
  69. data/spec/ey/logout_spec.rb +24 -0
  70. data/spec/ey/logs_spec.rb +36 -0
  71. data/spec/ey/rebuild_spec.rb +18 -0
  72. data/spec/ey/recipes/apply_spec.rb +29 -0
  73. data/spec/ey/recipes/download_spec.rb +43 -0
  74. data/spec/ey/recipes/upload_spec.rb +99 -0
  75. data/spec/ey/rollback_spec.rb +73 -0
  76. data/spec/ey/scp_spec.rb +176 -0
  77. data/spec/ey/servers_spec.rb +209 -0
  78. data/spec/ey/ssh_spec.rb +273 -0
  79. data/spec/ey/status_spec.rb +45 -0
  80. data/spec/ey/timeout_deploy_spec.rb +18 -0
  81. data/spec/ey/web/disable_spec.rb +21 -0
  82. data/spec/ey/web/enable_spec.rb +26 -0
  83. data/spec/ey/web/restart_spec.rb +21 -0
  84. data/spec/ey/whoami_spec.rb +30 -0
  85. data/spec/spec_helper.rb +84 -0
  86. data/spec/support/bundled_ey +7 -0
  87. data/spec/support/fixture_recipes.tgz +0 -0
  88. data/spec/support/git_repos.rb +115 -0
  89. data/spec/support/helpers.rb +330 -0
  90. data/spec/support/matchers.rb +16 -0
  91. data/spec/support/ruby_ext.rb +13 -0
  92. data/spec/support/shared_behavior.rb +278 -0
  93. metadata +411 -0
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'bundler/setup'
5
+
6
+ $LOAD_PATH.unshift File.expand_path("../../../lib", __FILE__)
7
+ load File.expand_path('../../../bin/ey', __FILE__)
@@ -0,0 +1,115 @@
1
+ module EY
2
+ class << self
3
+ def define_git_repo(name, &setup)
4
+ git_repo_setup[name] ||= setup
5
+ end
6
+
7
+ def refresh_git_repo(name)
8
+ git_repo_dir_cache.delete name
9
+ end
10
+
11
+ def git_repo_dir(name)
12
+ return git_repo_dir_cache[name] if git_repo_dir_cache.has_key?(name)
13
+ raise ArgumentError, "No definition for git repo #{name}" unless git_repo_setup[name]
14
+
15
+ git_dir = TMPDIR.join("engineyard_test_repo_#{Time.now.tv_sec}_#{Time.now.tv_usec}_#{$$}")
16
+ git_dir.mkpath
17
+ Dir.chdir(git_dir) do
18
+ system("git init -q")
19
+ system('git config user.email ey@spec.test')
20
+ system('git config user.name "EY Specs"')
21
+ system("git remote add testremote user@git.host:path/to/repo.git")
22
+ git_repo_setup[name].call(git_dir)
23
+ end
24
+ git_repo_dir_cache[name] = git_dir
25
+ end
26
+
27
+ def chdir_to_repo(repo_name)
28
+ @_original_wd ||= []
29
+ @_original_wd << Dir.getwd
30
+ Dir.chdir(git_repo_dir(repo_name))
31
+ end
32
+
33
+ def chdir_return
34
+ Dir.chdir(@_original_wd.pop) if @_original_wd && @_original_wd.any?
35
+ end
36
+
37
+ def fixture_recipes_tgz
38
+ File.expand_path('../fixture_recipes.tgz', __FILE__)
39
+ end
40
+
41
+ def link_recipes_tgz(git_dir)
42
+ system("ln -s #{fixture_recipes_tgz} #{git_dir.join('recipes.tgz')}")
43
+ end
44
+
45
+ protected
46
+
47
+ def git_repo_setup
48
+ @git_repo_setup ||= {}
49
+ end
50
+
51
+ def git_repo_dir_cache
52
+ @git_repo_dir_cache ||= {}
53
+ end
54
+ end
55
+
56
+ define_git_repo("default") do |git_dir|
57
+ system("echo 'source :gemcutter' > Gemfile")
58
+ system("git add Gemfile")
59
+ system("git commit -m 'initial commit' >/dev/null 2>&1")
60
+ end
61
+
62
+ define_git_repo('deploy test') do
63
+ # we'll have one commit on master
64
+ system("echo 'source :gemcutter' > Gemfile")
65
+ system("git add Gemfile")
66
+ system("git commit -m 'initial commit' >/dev/null 2>&1")
67
+
68
+ # and a tag
69
+ system("git tag -a -m 'version one' v1")
70
+
71
+ # and we need a non-master branch
72
+ system("git checkout -b current-branch >/dev/null 2>&1")
73
+ end
74
+
75
+ define_git_repo('+cookbooks') do |git_dir|
76
+ git_dir.join("cookbooks").mkdir
77
+ git_dir.join("cookbooks/file").open("w") {|f| f << "boo" }
78
+ end
79
+
80
+ define_git_repo('+recipes') do |git_dir|
81
+ link_recipes_tgz(git_dir)
82
+ end
83
+
84
+ define_git_repo "only cookbooks, no remotes" do |git_dir|
85
+ `git --git-dir "#{git_dir}/.git" remote`.split("\n").each do |remote|
86
+ `git --git-dir "#{git_dir}/.git" remote rm #{remote}`
87
+ end
88
+
89
+ git_dir.join("cookbooks").mkdir
90
+ File.open(git_dir.join("cookbooks/file"), "w"){|f| f << "stuff" }
91
+ end
92
+
93
+ define_git_repo "only cookbooks, unrelated remotes" do |git_dir|
94
+ `git --git-dir "#{git_dir}/.git" remote`.split("\n").each do |remote|
95
+ `git --git-dir "#{git_dir}/.git" remote rm #{remote}`
96
+ end
97
+
98
+ `git remote add origin polly@pirate.example.com:wanna/cracker.git`
99
+
100
+ git_dir.join("cookbooks").mkdir
101
+ File.open(git_dir.join("cookbooks/file"), "w"){|f| f << "rawk" }
102
+ end
103
+
104
+ define_git_repo('dup test') do
105
+ system("git remote add dup git://github.com/engineyard/dup.git")
106
+ end
107
+
108
+ define_git_repo("not actually a git repo") do |git_dir|
109
+ # in case we screw up and are not in a freshly-generated test
110
+ # git repository, don't blow away the thing we're developing
111
+ system("rm -rf .git") if `git remote -v`.include?("path/to/repo.git")
112
+ git_dir.join("cookbooks").mkdir
113
+ link_recipes_tgz(git_dir)
114
+ end
115
+ end
@@ -0,0 +1,330 @@
1
+ require 'engineyard/cli'
2
+
3
+ require 'realweb'
4
+ require 'open4'
5
+ require 'stringio'
6
+
7
+ module SpecHelpers
8
+ module Given
9
+ def given(name)
10
+ include_examples name
11
+ end
12
+ end
13
+
14
+ module IntegrationHelpers
15
+ def run_ey(command_options, ey_options={})
16
+
17
+ if respond_to?(:extra_ey_options) # needed for ssh tests
18
+ ey_options.merge!(extra_ey_options)
19
+ return ey(command_to_run(command_options), ey_options)
20
+ end
21
+
22
+ if ey_options[:expect_failure]
23
+ fast_failing_ey(command_to_run(command_options))
24
+ else
25
+ fast_ey(command_to_run(command_options))
26
+ end
27
+ end
28
+
29
+ def make_scenario(opts)
30
+ # since nil will silently turn to empty string when interpolated,
31
+ # and there's a lot of string matching involved in integration
32
+ # testing, it would be nice to have early notification of typos.
33
+ scenario = Hash.new { |h,k| raise "Tried to get key #{k.inspect}, but it's missing!" }
34
+ scenario.merge!(opts)
35
+ end
36
+ end
37
+
38
+ module GitRepoHelpers
39
+ def define_git_repo(name, &setup)
40
+ # EY's ivars don't get cleared between examples, so we can keep
41
+ # a git repo around longer (and thus make our tests faster)
42
+ EY.define_git_repo(name, &setup)
43
+ end
44
+
45
+ def use_git_repo(repo_name)
46
+ before(:all) do
47
+ EY.chdir_to_repo(repo_name)
48
+ end
49
+
50
+ after(:all) do
51
+ EY.chdir_return
52
+ end
53
+ end
54
+ end
55
+
56
+ class UnexpectedExit < StandardError
57
+ def initialize(stdout, stderr)
58
+ super "Exited with an unexpected exit code\n---STDOUT---\n#{stdout}\n---STDERR---\n#{stderr}\n"
59
+ end
60
+ end
61
+ NonzeroExitStatus = Class.new(UnexpectedExit)
62
+ ZeroExitStatus = Class.new(UnexpectedExit)
63
+
64
+ def ey_api
65
+ @api ||= EY::CloudClient.new(:token => 'asdf')
66
+ end
67
+
68
+ def ensure_eyrc
69
+ begin
70
+ unless (data = read_eyrc) and data['api_token']
71
+ raise ".eyrc has no token, specs will stall waiting for stdin authentication input"
72
+ end
73
+ rescue Errno::ENOENT => e
74
+ raise ".eyrc must be written before calling run_ey or specs will stall waiting for stdin authentication input"
75
+ end
76
+ end
77
+
78
+ def fast_ey(args, options = {})
79
+
80
+ ensure_eyrc
81
+
82
+ begin
83
+ debug = options[:debug] ? 'true' : nil
84
+ err, out = StringIO.new, StringIO.new
85
+ capture_stderr_into(err) do
86
+ capture_stdout_into(out) do
87
+ with_env('DEBUG' => debug, 'PRINT_CMD' => 'true') do
88
+ EY::CLI.start(args)
89
+ end
90
+ end
91
+ end
92
+ ensure
93
+ @err, @out = err.string, out.string
94
+ @raw_ssh_commands, @ssh_commands = extract_ssh_commands(@out)
95
+ end
96
+ end
97
+
98
+ def fast_failing_ey(*args)
99
+ begin
100
+ fast_ey(*args)
101
+ raise ZeroExitStatus.new(@out, @err)
102
+ rescue SystemExit => exit_status
103
+ # SystemExit typically indicates a bogus command, which we
104
+ # here in expected-to-fail land are entirely happy with.
105
+ nil
106
+ rescue Thor::Error, EY::Error, EY::CloudClient::Error => e
107
+ nil
108
+ end
109
+ end
110
+
111
+ def capture_stderr_into(stream)
112
+ $stderr = stream
113
+ yield
114
+ ensure
115
+ $stderr = STDERR
116
+ end
117
+
118
+ def capture_stdout_into(stream)
119
+ $stdout = stream
120
+ yield
121
+ ensure
122
+ $stdout = STDOUT
123
+ end
124
+
125
+ def ey(args = [], options = {}, &block)
126
+ if respond_to?(:extra_ey_options) # needed for ssh tests
127
+ options.merge!(extra_ey_options)
128
+ end
129
+
130
+ hide_err = options.has_key?(:hide_err) ? options[:hide_err] : options[:expect_failure]
131
+
132
+ path_prepends = options[:prepend_to_path]
133
+
134
+ ey_env = {
135
+ 'DEBUG' => ENV['DEBUG'],
136
+ 'PRINT_CMD' => 'true',
137
+ 'EYRC' => ENV['EYRC'],
138
+ 'CLOUD_URL' => ENV['CLOUD_URL'],
139
+ }
140
+
141
+ if options.has_key?(:debug)
142
+ ey_env['DEBUG'] = options[:debug] ? "true" : nil
143
+ end
144
+
145
+ if path_prepends
146
+ tempdir = TMPDIR.join("ey_test_cmds_#{Time.now.tv_sec}#{Time.now.tv_usec}_#{$$}")
147
+ tempdir.mkpath
148
+ path_prepends.each do |name, contents|
149
+ tempdir.join(name).open('w') do |f|
150
+ f.write(contents)
151
+ f.chmod(0755)
152
+ end
153
+ end
154
+
155
+ ey_env['PATH'] = "#{tempdir}:#{ENV['PATH']}"
156
+ end
157
+
158
+ eybin = File.expand_path('../bundled_ey', __FILE__)
159
+
160
+ with_env(ey_env) do
161
+ exit_status = Open4::open4("#{eybin} #{Escape.shell_command(args)}") do |pid, stdin, stdout, stderr|
162
+ block.call(stdin) if block
163
+ stdin.close
164
+ @out = stdout.read
165
+ @err = stderr.read
166
+ end
167
+
168
+ if !exit_status.success? && !options[:expect_failure]
169
+ raise NonzeroExitStatus.new(@out, @err)
170
+ elsif exit_status.success? && options[:expect_failure]
171
+ raise ZeroExitStatus.new(@out, @err)
172
+ end
173
+ end
174
+
175
+ @raw_ssh_commands, @ssh_commands = extract_ssh_commands(@out)
176
+
177
+ puts @err unless @err.empty? || hide_err
178
+ @out
179
+ end
180
+
181
+ def extract_ssh_commands(output)
182
+ raw_ssh_commands = [@out,@err].join("\n").split(/\n/).find_all do |line|
183
+ line =~ /^bash -lc/ || line =~ /^ssh/ || line =~ /^scp/
184
+ end
185
+
186
+ ssh_commands = raw_ssh_commands.map do |cmd|
187
+ # Strip off everything up to and including user@host, leaving
188
+ # just the command that the remote system would run
189
+ #
190
+ # XXX: this is a really icky icky.
191
+ # engineyard gem was written as if shelling out to run serverside
192
+ # and running an ssh command will always be the same. This is a nasty
193
+ # hack to get it working with Net::SSH for now so we can repair 1.9.2.
194
+ ssh_prefix_removed = cmd.gsub(/^bash -lc /, '').gsub(/^ssh .*?\w+@\S*\s*/, '')
195
+
196
+ # Its arguments have been double-escaped: one layer is to get
197
+ # them through our local shell and into ssh, and the other
198
+ # layer is to get them through the remote system's shell.
199
+ #
200
+ # Strip off one layer by running it through the shell.
201
+ just_the_remote_command = ssh_prefix_removed.gsub(/>\s*\/dev\/null.*$/, '')
202
+ `echo #{just_the_remote_command}`.strip
203
+ end
204
+
205
+ [raw_ssh_commands, ssh_commands]
206
+ end
207
+
208
+ DEPRECATED_SCENARIOS = {
209
+ "empty" => "User Name",
210
+ "one app without environment" => "App Without Env",
211
+ "one app, one environment, not linked" => "Unlinked App",
212
+ "two apps" => "Two Apps",
213
+ "one app, one environment" => "Linked App",
214
+ "Stuck Deployment" => "Stuck Deployment",
215
+ "two accounts, two apps, two environments, ambiguous" => "Multiple Ambiguous Accounts",
216
+ "one app, one environment, no instances" => "Linked App Not Running",
217
+ "one app, one environment, app master red" => "Linked App Red Master",
218
+ "one app, many environments" => "One App Many Envs",
219
+ "one app, many similarly-named environments" => "One App Similarly Named Envs",
220
+ "two apps, same git uri" => "Two Apps Same Git URI",
221
+ }
222
+
223
+ def api_scenario(old_name)
224
+ clean_eyrc # switching scenarios, always clean up
225
+ name = DEPRECATED_SCENARIOS[old_name]
226
+ @scenario = EY::CloudClient::Test::Scenario[name]
227
+ @scenario_email = @scenario.email
228
+ @scenario_password = @scenario.password
229
+ @scenario_api_token = @scenario.api_token
230
+ @scenario
231
+ end
232
+
233
+ def login_scenario(scenario_name)
234
+ scen = api_scenario(scenario_name)
235
+ write_eyrc('api_token' => scenario_api_token)
236
+ scen
237
+ end
238
+
239
+ def scenario_email
240
+ @scenario_email
241
+ end
242
+
243
+ def scenario_password
244
+ @scenario_password
245
+ end
246
+
247
+ def scenario_api_token
248
+ @scenario_api_token
249
+ end
250
+
251
+ def clean_tmpdir
252
+ TMPDIR.rmtree if TMPDIR.exist?
253
+ end
254
+
255
+ def read_yaml(file)
256
+ contents = File.read(File.expand_path(file))
257
+ YAML.load(contents)
258
+ rescue Exception => e
259
+ raise "#{e}\n#{contents}"
260
+ end
261
+
262
+ def write_yaml(data, file)
263
+ File.open(file, "w"){|f| YAML.dump(data, f) }
264
+ end
265
+
266
+ def clean_eyrc
267
+ ENV['EYRC'] = File.join('/tmp','eyrc')
268
+ if ENV['EYRC'] && File.exist?(ENV['EYRC'])
269
+ File.unlink(ENV['EYRC'])
270
+ end
271
+ end
272
+
273
+ def read_eyrc
274
+ read_yaml(ENV['EYRC'])
275
+ end
276
+
277
+ def write_eyrc(data)
278
+ write_yaml(data, ENV['EYRC'])
279
+ end
280
+
281
+ def ey_yml
282
+ EY::Config.pathname
283
+ end
284
+
285
+ def clean_ey_yml
286
+ ey_yml.unlink if ey_yml && ey_yml.exist?
287
+ FileUtils.rm_r 'config' if FileTest.exist?('config')
288
+ end
289
+
290
+ def read_ey_yml
291
+ read_yaml(EY::Config.pathname)
292
+ end
293
+
294
+ def write_ey_yml(data)
295
+ write_yaml(data, EY::Config.pathname_for_write)
296
+ end
297
+
298
+ def expect_config(*keys)
299
+ root = keys.unshift('defaults') unless %w[defaults environments].include?(keys.first)
300
+ config = read_ey_yml
301
+ value = keys.inject(config) { |conf, key| conf[key] }
302
+ expect(value)
303
+ end
304
+
305
+ def exist
306
+ be_exist
307
+ end
308
+
309
+ def with_env(new_env_vars)
310
+ raise ArgumentError, "with_env takes a block" unless block_given?
311
+ old_env_vars = {}
312
+ new_env_vars.each do |k, v|
313
+ if ENV.has_key?(k)
314
+ old_env_vars[k] = ENV[k]
315
+ end
316
+ ENV[k] = v if v
317
+ end
318
+
319
+ retval = yield
320
+
321
+ new_env_vars.keys.each do |k|
322
+ if old_env_vars.has_key?(k)
323
+ ENV[k] = old_env_vars[k]
324
+ else
325
+ ENV.delete(k)
326
+ end
327
+ end
328
+ retval
329
+ end
330
+ end