engineyard-migrate 1.0.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.
@@ -0,0 +1,37 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "engineyard-migrate/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "engineyard-migrate"
7
+ s.version = Engineyard::Migrate::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Dr Nic Williams", "Danish Khan"]
10
+ s.email = ["drnicwilliams@gmail.com"]
11
+ s.homepage = "https://github.com/engineyard/engineyard-migrate"
12
+ s.summary = %q{Migrate up to Engine Yard AppCloud from Heroku or similar.}
13
+ s.description = %q{Want to migrate your Ruby on Rails application from Heroku (or similar) up to Engine Yard AppCloud? This is the tool for you.}
14
+
15
+ s.rubyforge_project = "engineyard-migrate"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_dependency("engineyard", ["~> 1.3.16"])
23
+ s.add_dependency("heroku", ["~> 1.17.10"])
24
+ s.add_dependency("POpen4", ["~> 0.1.4"])
25
+ s.add_dependency("net-sftp", ["~> 2.0.5"])
26
+
27
+ s.add_development_dependency("awesome_print")
28
+ s.add_development_dependency("builder", ["2.1.2"]) # for test app
29
+ s.add_development_dependency("rails", ["3.0.3"]) # for test app
30
+ s.add_development_dependency("rake", ["~> 0.8.7"])
31
+ s.add_development_dependency("cucumber", ["~> 0.10.0"])
32
+ s.add_development_dependency("cucumber-rails", ["~> 0.3.2"])
33
+ s.add_development_dependency("rspec", ["~> 2.2.0"])
34
+ s.add_development_dependency("nokogiri", ["~> 1.4.0"])
35
+ s.add_development_dependency("ssh-config")
36
+ s.add_development_dependency("taps", ["~> 0.3.15"])
37
+ end
@@ -0,0 +1,36 @@
1
+ Feature: Migration from Heroku
2
+ In order to reduce cost of migrating from Heroku to AppCloud
3
+ As a developer
4
+ I want to migrate as much of my Heroku-hosted application to AppCloud
5
+
6
+ Scenario: Migrate a simple app
7
+ Given I have setup my SSH keys
8
+ And I clone the application "git@github.com:engineyard/heroku2ey-simple-app.git" as "simple-app"
9
+
10
+ And I have setup my Heroku credentials
11
+ And I have a Heroku application "heroku2ey-simple-app"
12
+ And it has production data
13
+ When I visit the application at "heroku2ey-simple-app.heroku.com"
14
+ Then I should see table
15
+ | People |
16
+ | Dr Nic |
17
+ | Danish |
18
+
19
+ Given I have setup my AppCloud credentials
20
+ And I reset the AppCloud "heroku2eysimpleapp_production" application "heroku2eysimpleapp" database
21
+ When I visit the application at "ec2-50-17-248-148.compute-1.amazonaws.com"
22
+ Then I should see table
23
+ | People |
24
+
25
+ When I run local executable "ey-migrate" with arguments "heroku . --account heroku2ey --environment heroku2eysimpleapp_production"
26
+ Then I should see "Migration complete!"
27
+ When I visit the application at "ec2-50-17-248-148.compute-1.amazonaws.com"
28
+ Then I should see table
29
+ | People |
30
+ | Dr Nic |
31
+ | Danish |
32
+
33
+
34
+
35
+
36
+
@@ -0,0 +1,107 @@
1
+ Feature: Migration errors
2
+ I want useful error messages and prompts
3
+
4
+ Scenario: Fail if application isn't on Heroku
5
+ Given I clone the application "git@github.com:engineyard/heroku2ey-simple-app.git" as "simple-app"
6
+ When I run local executable "ey-migrate" with arguments "heroku . --account heroku2ey --environment heroku2eysimpleapp_production"
7
+ Then I should see
8
+ """
9
+ Not a Salesforce Heroku application.
10
+ """
11
+
12
+ Scenario: Fail if Heroku credentials not available
13
+ Given I clone the application "git@github.com:engineyard/heroku2ey-simple-app.git" as "simple-app"
14
+ And I have a Heroku application "heroku2ey-simple-app"
15
+ When I run local executable "ey-migrate" with arguments "heroku . --account heroku2ey --environment heroku2eysimpleapp_production"
16
+ Then I should see
17
+ """
18
+ Please setup your Salesforce Heroku credentials first.
19
+ """
20
+
21
+ Scenario: Fail if no Git 'origin' repo URI
22
+ Given I clone the application "git@github.com:engineyard/heroku2ey-simple-app.git" as "simple-app"
23
+ And I have a Heroku application "heroku2ey-simple-app"
24
+ And I have setup my SSH keys
25
+ And I have setup my Heroku credentials
26
+ Given I run executable "git" with arguments "remote rm origin"
27
+ When I run local executable "ey-migrate" with arguments "heroku . --account heroku2ey --environment heroku2eysimpleapp_production"
28
+ Then I should see
29
+ """
30
+ Please host your Git repo externally and add as remote 'origin'.
31
+ """
32
+
33
+ Scenario: Fail if AppCloud credentials not available
34
+ Given I clone the application "git@github.com:engineyard/heroku2ey-simple-app.git" as "simple-app"
35
+ And I have a Heroku application "heroku2ey-simple-app"
36
+ And I have setup my SSH keys
37
+ And I have setup my Heroku credentials
38
+
39
+ When I run local executable "ey-migrate" with arguments "heroku . --account heroku2ey --environment heroku2eysimpleapp_production"
40
+ Then I should see
41
+ """
42
+ Please create, boot and deploy an AppCloud application for git@github.com:engineyard/heroku2ey-simple-app.git.
43
+ """
44
+
45
+ Scenario: Fail if no AppCloud environments/applications match this application
46
+ Given I clone the application "git@github.com:engineyard/heroku2ey-simple-app.git" as "simple-app"
47
+ And I have a Heroku application "heroku2ey-simple-app"
48
+ And I have setup my SSH keys
49
+ And I have setup my Heroku credentials
50
+
51
+ Given I have setup my AppCloud credentials
52
+ And I run executable "git" with arguments "remote rm origin"
53
+ And I run executable "git" with arguments "remote add origin git@github.com:engineyard/UNKNOWN.git"
54
+
55
+ When I run local executable "ey-migrate" with arguments "heroku . -e heroku2eysimpleapp_production"
56
+ Then I should see
57
+ """
58
+ Please create, boot and deploy an AppCloud application for git@github.com:engineyard/UNKNOWN.git.
59
+ """
60
+
61
+ Scenario: Fail if too many AppCloud environments match
62
+ Given I clone the application "git@github.com:engineyard/heroku2ey-simple-app.git" as "simple-app"
63
+ And I have a Heroku application "heroku2ey-simple-app"
64
+ And I have setup my SSH keys
65
+ And I have setup my Heroku credentials
66
+
67
+ Given I have setup my AppCloud credentials
68
+ When I run local executable "ey-migrate" with arguments "heroku . -V"
69
+ Then I should see "Multiple environments possible, please be more specific:"
70
+ Then I should see
71
+ """
72
+ ey-migrate heroku . --app='heroku2eysimpleapp' --account='heroku2ey' --environment='heroku2eysimpleapp_production'
73
+ ey-migrate heroku . --app='heroku2eysimpleapp' --account='heroku2ey' --environment='heroku2ey_noinstances'
74
+ """
75
+
76
+
77
+ Scenario: Fail if environment hasn't been booted yet
78
+ Given I have setup my SSH keys
79
+ And I clone the application "git@github.com:engineyard/heroku2ey-simple-app.git" as "simple-app"
80
+
81
+ And I have setup my Heroku credentials
82
+ And I have a Heroku application "heroku2ey-simple-app"
83
+
84
+ Given I have setup my AppCloud credentials
85
+
86
+ When I run local executable "ey-migrate" with arguments "heroku . -e heroku2ey_noinstances"
87
+ Then I should see
88
+ """
89
+ Please boot your AppCloud environment and then deploy your application.
90
+ """
91
+
92
+ Scenario: Fail if application hasn't been deployed yet
93
+ Given I have setup my SSH keys
94
+ And I clone the application "git@github.com:engineyard/heroku2ey-simple-app.git" as "simple-app"
95
+
96
+ And I have setup my Heroku credentials
97
+ And I have a Heroku application "heroku2ey-simple-app"
98
+
99
+ Given I have setup my AppCloud credentials
100
+ And I remove AppCloud "heroku2eysimpleapp_production" application "heroku2eysimpleapp" folder
101
+
102
+ When I run local executable "ey-migrate" with arguments "heroku . -e heroku2eysimpleapp_production"
103
+ Then I should see
104
+ """
105
+ Please deploy your AppCloud application before running migration.
106
+ """
107
+
@@ -0,0 +1,102 @@
1
+ Given /^I have setup my SSH keys$/ do
2
+ in_home_folder do
3
+ FileUtils.cp_r(File.join(@fixtures_path, "credentials/ssh"), ".ssh")
4
+ FileUtils.chmod(0700, ".ssh")
5
+ FileUtils.chmod(0600, ".ssh/id_rsa")
6
+ FileUtils.chmod(0600, ".ssh/id_rsa.pub")
7
+ end
8
+ end
9
+
10
+ Given /^I clone the application "([^"]*)" as "([^"]*)"$/ do |git_uri, app_name|
11
+ @git_uri = git_uri
12
+ @app_name = app_name
13
+ repo_folder = File.expand_path(File.join(@repos_path, app_name))
14
+ unless File.exists?(repo_folder)
15
+ @stdout = File.expand_path(File.join(@tmp_root, "git.out"))
16
+ @stderr = File.expand_path(File.join(@tmp_root, "git.err"))
17
+ FileUtils.chdir(@repos_path) do
18
+ system "git clone #{git_uri} #{app_name} > #{@stdout.inspect} 2> #{@stderr.inspect}"
19
+ end
20
+ end
21
+ in_home_folder do
22
+ FileUtils.rm_rf(app_name)
23
+ FileUtils.cp_r(repo_folder, app_name)
24
+ end
25
+ @active_project_folder = File.join(@home_path, app_name)
26
+ @project_name = app_name
27
+ @stdout = File.expand_path(File.join(@tmp_root, "bundle.out"))
28
+ @stderr = File.expand_path(File.join(@tmp_root, "bundle.err"))
29
+ in_project_folder do
30
+ system "bundle > #{@stdout.inspect} 2> #{@stderr.inspect}"
31
+ end
32
+ end
33
+
34
+ Given /^I have setup my Heroku credentials$/ do
35
+ in_home_folder do
36
+ FileUtils.cp_r(File.join(@fixtures_path, "credentials/heroku"), ".heroku")
37
+ FileUtils.chmod(0700, ".heroku")
38
+ end
39
+ end
40
+ # note, when setting up the heroku credentials for the first time:
41
+ # set the new $HOME
42
+ # cd $HOME
43
+ # mkdir .ssh
44
+ # chmod 700 .ssh
45
+ # heroku list # will go through process of setting up and creating ssh keys
46
+
47
+
48
+ Given /^I have a Heroku application "([^"]*)"$/ do |name|
49
+ @heroku_name = name
50
+ @heroku_host = "#{name}.heroku.com"
51
+ in_project_folder do
52
+ system "git remote rm heroku 2> /dev/null"
53
+ system "git remote add heroku git@heroku.com:#{name}.git"
54
+ end
55
+ end
56
+
57
+ Given /^it has production data$/ do
58
+ unless @production_data_installed
59
+ in_project_folder do
60
+ # TODO - currently hard coded into fixtures/data/APPNAME.sqlite3 as the commented code below isn't working
61
+
62
+ `rm -f db/development.sqlite3`
63
+ `bundle exec rake db:schema:load`
64
+ cmds = ['Dr Nic', 'Danish'].map do |name|
65
+ "Person.create(:name => '#{name}')"
66
+ end.join("; ")
67
+ `bundle exec rails runner "#{cmds}"`
68
+
69
+ data_file = File.expand_path(File.join(@fixtures_path, "data", "#{@app_name}.sqlite3"))
70
+ raise "Missing production data for '#{@app_name}' at #{data_file}; run 'rake db:seed' in fixtures/repos/#{app_name}" unless File.exists?(data_file)
71
+ FileUtils.cp_r(data_file, "db/development.sqlite3")
72
+ @stdout = File.expand_path(File.join(@tmp_root, "heroku.out"))
73
+ @stderr = File.expand_path(File.join(@tmp_root, "heroku.err"))
74
+ system "heroku db:push --confirm #{@heroku_name} > #{@stdout.inspect} 2> #{@stderr.inspect}"
75
+ @production_data_installed = true
76
+ end
77
+ end
78
+ end
79
+
80
+ Given /^I have setup my AppCloud credentials$/ do
81
+ in_home_folder do
82
+ FileUtils.cp_r(File.join(@fixtures_path, "credentials/eyrc"), ".eyrc")
83
+ FileUtils.chmod(0700, ".eyrc")
84
+ end
85
+ end
86
+
87
+ Given /^I reset the AppCloud "([^"]*)" application "([^"]*)" database$/ do |environment, app_name|
88
+ in_project_folder do
89
+ @stdout = File.expand_path(File.join(@tmp_root, "eyssh.out"))
90
+ @stderr = File.expand_path(File.join(@tmp_root, "eyssh.err"))
91
+ system "ey ssh 'cd /data/#{app_name}/current/; RAILS_ENV=production rake db:schema:load' -e #{environment} > #{@stdout.inspect} 2> #{@stderr.inspect}"
92
+ end
93
+ end
94
+
95
+ # Actually moves it to .../current.bak; which is restored after the scenario
96
+ Given /^I remove AppCloud "([^"]*)" application "([^"]*)" folder$/ do |environment, app_name|
97
+ in_project_folder do
98
+ remove_from_appcloud("/data/#{app_name}/current", environment)
99
+ end
100
+ end
101
+
102
+
@@ -0,0 +1,208 @@
1
+ Given /^this project is active project folder/ do
2
+ @active_project_folder = File.expand_path(File.dirname(__FILE__) + "/../..")
3
+ end
4
+
5
+ Given /^env variable \$([\w_]+) set to( project path|) "(.*)"/ do |env_var, path, value|
6
+ in_project_folder {
7
+ value = File.expand_path(value)
8
+ } unless path.empty?
9
+ ENV[env_var] = value
10
+ end
11
+
12
+ Given /"(.*)" folder is deleted/ do |folder|
13
+ in_project_folder { FileUtils.rm_rf folder }
14
+ end
15
+
16
+ Given /file "(.*)" is deleted/ do |file|
17
+ in_project_folder { FileUtils.rm_rf file }
18
+ end
19
+
20
+ When /^I invoke "(.*)" generator with arguments "(.*)"$/ do |generator, arguments|
21
+ @stdout = StringIO.new
22
+ in_project_folder do
23
+ if Object.const_defined?("APP_ROOT")
24
+ APP_ROOT.replace(FileUtils.pwd)
25
+ else
26
+ APP_ROOT = FileUtils.pwd
27
+ end
28
+ run_generator(generator, arguments.split(' '), SOURCES, :stdout => @stdout)
29
+ end
30
+ File.open(File.join(@tmp_root, "generator.out"), "w") do |f|
31
+ @stdout.rewind
32
+ f << @stdout.read
33
+ end
34
+ end
35
+
36
+ When /^I run executable "(.*)" with arguments "(.*)"/ do |executable, arguments|
37
+ @stdout = File.expand_path(File.join(@tmp_root, "executable.out"))
38
+ in_project_folder do
39
+ system "#{executable.inspect} #{arguments} > #{@stdout.inspect} 2> #{@stdout.inspect}"
40
+ end
41
+ end
42
+
43
+ When /^I run project executable "(.*)" with arguments "(.*)"/ do |executable, arguments|
44
+ @stdout = File.expand_path(File.join(@tmp_root, "executable.out"))
45
+ in_project_folder do
46
+ system "ruby -rubygems #{executable.inspect} #{arguments} > #{@stdout.inspect} 2> #{@stdout.inspect}"
47
+ end
48
+ end
49
+
50
+ When /^I run local executable "(.*)" with arguments "(.*)"/ do |executable, arguments|
51
+ if executable == "ey-migrate"
52
+ require 'engineyard-migrate'
53
+ require 'engineyard-migrate/cli'
54
+ in_project_folder do
55
+ stdout, stderr = capture_stdios do
56
+ begin
57
+ Engineyard::Migrate::CLI.start(arguments.split(/ /))
58
+ rescue SystemExit
59
+ end
60
+ end
61
+ @stdout = File.expand_path(File.join(@tmp_root, "executable.out"))
62
+ File.open(@stdout, "w") {|f| f << stdout; f << stderr}
63
+ end
64
+ else
65
+ @stdout = File.expand_path(File.join(@tmp_root, "executable.out"))
66
+ executable = File.expand_path(File.join(File.dirname(__FILE__), "/../../bin", executable))
67
+ in_project_folder do
68
+ system "ruby -rubygems #{executable.inspect} #{arguments} > #{@stdout.inspect} 2> #{@stdout.inspect}"
69
+ end
70
+ end
71
+ end
72
+
73
+ When /^I invoke task "rake (.*)"/ do |task|
74
+ @stdout = File.expand_path(File.join(@tmp_root, "tests.out"))
75
+ in_project_folder do
76
+ system "bundle exec rake #{task} --trace > #{@stdout.inspect} 2> #{@stdout.inspect}"
77
+ end
78
+ end
79
+
80
+ Then /^folder "(.*)" (is|is not) created/ do |folder, is|
81
+ in_project_folder do
82
+ File.exists?(folder).should(is == 'is' ? be_true : be_false)
83
+ end
84
+ end
85
+
86
+ Then /^file "(.*)" (is|is not) created/ do |file, is|
87
+ in_project_folder do
88
+ File.exists?(file).should(is == 'is' ? be_true : be_false)
89
+ end
90
+ end
91
+
92
+ Then /^file with name matching "(.*)" is created/ do |pattern|
93
+ in_project_folder do
94
+ Dir[pattern].should_not be_empty
95
+ end
96
+ end
97
+
98
+ Then /^file "(.*)" contents (does|does not) match \/(.*)\// do |file, does, regex|
99
+ in_project_folder do
100
+ actual_output = File.read(file)
101
+ (does == 'does') ?
102
+ actual_output.should(match(/#{regex}/)) :
103
+ actual_output.should_not(match(/#{regex}/))
104
+ end
105
+ end
106
+
107
+ Then /^file "([^"]*)" contains "([^"]*)"$/ do |file, text|
108
+ in_project_folder do
109
+ actual_output = File.read(file)
110
+ actual_output.should contain(text)
111
+ end
112
+ end
113
+
114
+
115
+ Then /gem file "(.*)" and generated file "(.*)" should be the same/ do |gem_file, project_file|
116
+ File.exists?(gem_file).should be_true
117
+ File.exists?(project_file).should be_true
118
+ gem_file_contents = File.read(File.dirname(__FILE__) + "/../../#{gem_file}")
119
+ project_file_contents = File.read(File.join(@active_project_folder, project_file))
120
+ project_file_contents.should == gem_file_contents
121
+ end
122
+
123
+ Then /^(does|does not) invoke generator "(.*)"$/ do |does_invoke, generator|
124
+ actual_output = get_command_output
125
+ does_invoke == "does" ?
126
+ actual_output.should(match(/dependency\s+#{generator}/)) :
127
+ actual_output.should_not(match(/dependency\s+#{generator}/))
128
+ end
129
+
130
+ Then /help options "(.*)" and "(.*)" are displayed/ do |opt1, opt2|
131
+ actual_output = get_command_output
132
+ actual_output.should match(/#{opt1}/)
133
+ actual_output.should match(/#{opt2}/)
134
+ end
135
+
136
+ Then /^I should see "([^\"]*)"$/ do |text|
137
+ actual_output = get_command_output
138
+ actual_output.should contain(text)
139
+ end
140
+
141
+ Then /^I should not see "([^\"]*)"$/ do |text|
142
+ actual_output =
143
+ actual_output.should_not contain(text)
144
+ end
145
+
146
+ Then /^I should see$/ do |text|
147
+ actual_output = get_command_output
148
+ actual_output.should contain(text)
149
+ end
150
+
151
+ Then /^I should not see$/ do |text|
152
+ actual_output = get_command_output
153
+ actual_output.should_not contain(text)
154
+ end
155
+
156
+ Then /^I should see exactly$/ do |text|
157
+ actual_output = get_command_output
158
+ actual_output.should == text
159
+ end
160
+
161
+ Then /^I should see all (\d+) tests pass/ do |expected_test_count|
162
+ expected = %r{^#{expected_test_count} tests, \d+ assertions, 0 failures, 0 errors}
163
+ actual_output = get_command_output
164
+ actual_output.should match(expected)
165
+ end
166
+
167
+ Then /^I should see all (\d+) examples pass/ do |expected_test_count|
168
+ expected = %r{^#{expected_test_count} examples?, 0 failures}
169
+ actual_output = get_command_output
170
+ actual_output.should match(expected)
171
+ end
172
+
173
+ Then /^yaml file "(.*)" contains (\{.*\})/ do |file, yaml|
174
+ in_project_folder do
175
+ yaml = eval yaml
176
+ YAML.load(File.read(file)).should == yaml
177
+ end
178
+ end
179
+
180
+ Then /^Rakefile can display tasks successfully/ do
181
+ @stdout = File.expand_path(File.join(@tmp_root, "rakefile.out"))
182
+ in_project_folder do
183
+ system "rake -T > #{@stdout.inspect} 2> #{@stdout.inspect}"
184
+ end
185
+ actual_output = get_command_output
186
+ actual_output.should match(/^rake\s+\w+\s+#\s.*/)
187
+ end
188
+
189
+ Then /^task "rake (.*)" is executed successfully/ do |task|
190
+ @stdout.should_not be_nil
191
+ actual_output = get_command_output
192
+ actual_output.should_not match(/^Don't know how to build task '#{task}'/)
193
+ actual_output.should_not match(/Error/i)
194
+ end
195
+
196
+ Then /^gem spec key "(.*)" contains \/(.*)\// do |key, regex|
197
+ in_project_folder do
198
+ gem_file = Dir["pkg/*.gem"].first
199
+ gem_spec = Gem::Specification.from_yaml(`gem spec #{gem_file}`)
200
+ spec_value = gem_spec.send(key.to_sym)
201
+ spec_value.to_s.should match(/#{regex}/)
202
+ end
203
+ end
204
+
205
+ Then /^the file "([^\"]*)" is a valid gemspec$/ do |filename|
206
+ spec = eval(File.read(filename))
207
+ spec.validate
208
+ end