├── .gitignore
├── ChangeLog.md
├── Gemfile
├── MIT-LICENSE
├── README.md
├── Rakefile
├── bin
└── ey-migrate
├── docs
└── migration-options.graffle
│ ├── QuickLook
│ ├── Preview.pdf
│ └── Thumbnail.tiff
│ ├── data.plist
│ ├── image1.tiff
│ └── image2.tiff
├── engineyard-migrate.gemspec
├── features
├── heroku.feature
├── migration_errors.feature
├── step_definitions
│ ├── application_setup_steps.rb
│ ├── common_steps.rb
│ └── web_steps.rb
└── support
│ ├── appcloud_restore_folders.rb
│ ├── common.rb
│ ├── env.rb
│ ├── matchers.rb
│ └── web.rb
├── fixtures
└── data
│ └── simple-app.sqlite3
├── heroku-todo.md
├── lib
├── engineyard-migrate.rb
└── engineyard-migrate
│ ├── cli.rb
│ └── version.rb
└── spec
└── spec_helper.rb
/.gitignore:
--------------------------------------------------------------------------------
1 | *.gem
2 | .bundle
3 | Gemfile.lock
4 | pkg/*
5 | tmp
6 | fixtures/repos
7 | fixtures/credentials
--------------------------------------------------------------------------------
/ChangeLog.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | ## 1.1.0 2012-04-30
4 |
5 | Heroku
6 |
7 | * Credentials now at ~/.netrc or ~/_netrc
8 | * Taps needs sqlite3 installed
9 |
10 | General:
11 |
12 | * A log of changes. But prettier.
13 |
14 | ## 1.0.1 2011-03-11
15 |
16 | Remove version dependencies on engineyard + heroku gems [fixes #4]
17 |
18 | ## 1.0.0 2011-03-11
19 |
20 | Initial release!
21 |
22 | * Migrate database from Heroku application to a running AppCloud application
23 | * Expansive integration test suite
24 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "http://rubygems.org"
2 |
3 | # Specify your gem's dependencies in engineyard-migrate.gemspec
4 | gemspec
5 |
--------------------------------------------------------------------------------
/MIT-LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2011 Engine Yard
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOa AND
17 | NONINFRINGEMENT. IN NO EVENT SaALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Migrate your Rails application to Engine Yard AppCloud
2 |
3 | Want to migrate your Ruby on Rails application from Heroku (or similar) up to Engine Yard AppCloud? This is the tool for you.
4 |
5 | Currently supported: **Migrate Heroku to AppCloud**.
6 |
7 |
8 |
9 | ## Installation
10 |
11 | Currently, it is only installable from source.
12 |
13 | bundle
14 | rake install
15 |
16 | ## Usage
17 |
18 | The tool is simple to use. If you need to do something, it will tell you.
19 |
20 | ey-migrate heroku path/to/heroku/app
21 |
22 | ## Migration from Salesforce Heroku
23 |
24 | The migration tool assumes you have:
25 |
26 | * A running Heroku application with your data in its SQL database
27 | * A Gemfile, rather than Heroku's deprecated .gems format
28 | * Added `mysql2` to your Gemfile
29 | * This upgraded application running on AppCloud without any of your data
30 |
31 | ### Database
32 |
33 | Your SQL database is automatically migrated to your AppCloud application via `ey-migrate heroku`.
34 |
35 | A MySQL database is created automatically for you for each AppCloud application. On a 1 instance environment it runs on the same instances as your web application. For dedicated databases, use a 2+ instance environment with a dedicated database instance.
36 |
37 | ### Workers
38 |
39 | Automated support for setting up delayed_job workers is coming.
40 |
41 | ### Other add-ons
42 |
43 | If you have specific Heroku Add-Ons you'd like to be automatically migrated to AppCloud, please leave a [note/request](https://github.com/engineyard/engineyard-migrate).
44 |
45 | ## Development of project
46 |
47 | ### Running tests
48 |
49 | Then to run tests:
50 |
51 | bundle
52 | rake
53 |
54 | This will install `.ssh/config` required for your SSH credentials to run the test suite.
55 |
56 | ### Credentials
57 |
58 | To run the integration tests, you either need access to the [credentials repository](https://github.com/engineyard/ey-migrate-test-credentials)
59 |
60 | Please send a Github message to `drnic` requesting access to the credentials. You'll then be able to run the test suite.
61 |
62 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'bundler'
2 | Bundler::GemHelper.install_tasks
3 |
4 | require 'rspec/core/rake_task'
5 |
6 | desc "Run all examples"
7 | RSpec::Core::RakeTask.new
8 |
9 | namespace :cucumber do
10 | require 'cucumber/rake/task'
11 | Cucumber::Rake::Task.new(:wip, 'Run features that are being worked on') do |t|
12 | t.cucumber_opts = "--tags @wip"
13 | end
14 | Cucumber::Rake::Task.new(:ok, 'Run features that should be working') do |t|
15 | t.cucumber_opts = "--tags ~@wip"
16 | end
17 | task :all => [:ok, :wip]
18 |
19 | desc "Download credentials"
20 | task :download_credentials do
21 | credentials = File.expand_path('../fixtures/credentials', __FILE__)
22 | unless File.exists?(credentials)
23 | sh "git clone git@github.com:engineyard/ey-migrate-test-credentials.git #{credentials}"
24 | end
25 | end
26 |
27 | desc "Setup IdentityFile for SSH keys for running tests"
28 | task :ssh_config do
29 | puts "Installing SSH credentials for running integration tests..."
30 | config_file = File.expand_path("~#{ENV['USER']}/.ssh/config")
31 | identity_file = File.expand_path("../tmp/home/.ssh/id_rsa", __FILE__)
32 | if File.exist? config_file
33 | sh "ssh-config set ec2-50-17-248-148.compute-1.amazonaws.com IdentityFile #{identity_file}"
34 | else
35 | File.open(config_file, 'w')
36 | sh "ssh-config set ec2-50-17-248-148.compute-1.amazonaws.com IdentityFile #{identity_file}"
37 | File.delete(config_file + '~')
38 | end
39 | end
40 | end
41 |
42 | desc 'Alias for cucumber:ok'
43 | task :cucumber => ['cucumber:download_credentials', 'cucumber:ssh_config', 'cucumber:ok']
44 |
45 | desc "Start test server; Run cucumber:ok; Kill Test Server;"
46 | task :default => ["spec", "cucumber"]
47 |
48 | desc "Clean out cached git app repos"
49 | task :clean_app_repos do
50 | repos_path = File.dirname(__FILE__) + "/fixtures/repos"
51 | FileUtils.rm_rf(repos_path)
52 | puts "Removed #{repos_path}..."
53 | end
54 |
55 |
--------------------------------------------------------------------------------
/bin/ey-migrate:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require 'rubygems'
4 | $:.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib"))
5 |
6 | require 'engineyard-migrate'
7 | require 'engineyard-migrate/cli'
8 |
9 | $stdout.sync = true
10 |
11 | Engineyard::Migrate::CLI.start
12 |
--------------------------------------------------------------------------------
/docs/migration-options.graffle/QuickLook/Preview.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/engineyard/engineyard-migrate/77f7a6fb695f88eb8325cee51248f865474b2a0c/docs/migration-options.graffle/QuickLook/Preview.pdf
--------------------------------------------------------------------------------
/docs/migration-options.graffle/QuickLook/Thumbnail.tiff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/engineyard/engineyard-migrate/77f7a6fb695f88eb8325cee51248f865474b2a0c/docs/migration-options.graffle/QuickLook/Thumbnail.tiff
--------------------------------------------------------------------------------
/docs/migration-options.graffle/data.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ActiveLayerIndex
6 | 0
7 | ApplicationVersion
8 |
9 | com.omnigroup.OmniGrafflePro
10 | 138.17.0.133677
11 |
12 | AutoAdjust
13 |
14 | BackgroundGraphic
15 |
16 | Bounds
17 | {{0, 0}, {576, 733}}
18 | Class
19 | SolidGraphic
20 | ID
21 | 2
22 | Style
23 |
24 | shadow
25 |
26 | Draws
27 | NO
28 |
29 | stroke
30 |
31 | Draws
32 | NO
33 |
34 |
35 |
36 | CanvasOrigin
37 | {0, 0}
38 | ColumnAlign
39 | 1
40 | ColumnSpacing
41 | 36
42 | CreationDate
43 | 2011-03-11 13:52:41 -0800
44 | Creator
45 | Dr Nic Williams
46 | DisplayScale
47 | 1 0/72 in = 1 0/72 in
48 | GraphDocumentVersion
49 | 6
50 | GraphicsList
51 |
52 |
53 | Bounds
54 | {{221.5, 62.5}, {74, 40}}
55 | Class
56 | ShapedGraphic
57 | ID
58 | 29
59 | Rotation
60 | 5.9071908253827132e-06
61 | Shape
62 | AdjustableArrow
63 | ShapeData
64 |
65 | ratio
66 | 0.50000017881393433
67 | width
68 | 20.000001907348633
69 |
70 | Style
71 |
72 | fill
73 |
74 | Color
75 |
76 | b
77 | 0.696626
78 | g
79 | 0.354368
80 | r
81 | 0.112333
82 |
83 | FillType
84 | 2
85 | GradientAngle
86 | 355
87 | GradientColor
88 |
89 | b
90 | 0.829482
91 | g
92 | 0.541172
93 | r
94 | 0.272927
95 |
96 | MiddleFraction
97 | 0.4523809552192688
98 |
99 | shadow
100 |
101 | Color
102 |
103 | a
104 | 0.4
105 | b
106 | 0
107 | g
108 | 0
109 | r
110 | 0
111 |
112 | ShadowVector
113 | {0, 2}
114 |
115 | stroke
116 |
117 | Color
118 |
119 | b
120 | 0.794995
121 | g
122 | 0.459724
123 | r
124 | 0
125 |
126 |
127 |
128 | TextRelativeArea
129 | {{0.125, 0.25}, {0.75, 0.5}}
130 | isConnectedShape
131 |
132 |
133 |
134 | Bounds
135 | {{311, 29}, {83, 107}}
136 | Class
137 | ShapedGraphic
138 | ID
139 | 6
140 | ImageID
141 | 2
142 | Shape
143 | Rectangle
144 | Style
145 |
146 | fill
147 |
148 | Draws
149 | NO
150 |
151 | shadow
152 |
153 | Draws
154 | NO
155 |
156 | stroke
157 |
158 | Draws
159 | NO
160 |
161 |
162 |
163 |
164 | Bounds
165 | {{41, 55}, {165, 55}}
166 | Class
167 | ShapedGraphic
168 | ID
169 | 4
170 | ImageID
171 | 1
172 | Shape
173 | Rectangle
174 | Style
175 |
176 | fill
177 |
178 | Draws
179 | NO
180 |
181 | shadow
182 |
183 | Draws
184 | NO
185 |
186 | stroke
187 |
188 | Draws
189 | NO
190 |
191 |
192 |
193 |
194 | GridInfo
195 |
196 | GuidesLocked
197 | NO
198 | GuidesVisible
199 | YES
200 | HPages
201 | 1
202 | ImageCounter
203 | 3
204 | ImageLinkBack
205 |
206 |
207 |
208 |
209 | ImageList
210 |
211 | image2.tiff
212 | image1.tiff
213 |
214 | KeepToScale
215 |
216 | Layers
217 |
218 |
219 | Lock
220 | NO
221 | Name
222 | Layer 1
223 | Print
224 | YES
225 | View
226 | YES
227 |
228 |
229 | LayoutInfo
230 |
231 | Animate
232 | NO
233 | circoMinDist
234 | 18
235 | circoSeparation
236 | 0.0
237 | layoutEngine
238 | dot
239 | neatoSeparation
240 | 0.0
241 | twopiSeparation
242 | 0.0
243 |
244 | LinksVisible
245 | NO
246 | MagnetsVisible
247 | NO
248 | MasterSheets
249 |
250 | ModificationDate
251 | 2011-03-11 13:57:10 -0800
252 | Modifier
253 | Dr Nic Williams
254 | NotesVisible
255 | NO
256 | Orientation
257 | 2
258 | OriginVisible
259 | NO
260 | PageBreaks
261 | YES
262 | PrintInfo
263 |
264 | NSBottomMargin
265 |
266 | float
267 | 41
268 |
269 | NSLeftMargin
270 |
271 | float
272 | 18
273 |
274 | NSPaperSize
275 |
276 | size
277 | {612, 792}
278 |
279 | NSRightMargin
280 |
281 | float
282 | 18
283 |
284 | NSTopMargin
285 |
286 | float
287 | 18
288 |
289 |
290 | PrintOnePage
291 |
292 | ReadOnly
293 | NO
294 | RowAlign
295 | 1
296 | RowSpacing
297 | 36
298 | SheetTitle
299 | Canvas 1
300 | SmartAlignmentGuidesActive
301 | YES
302 | SmartDistanceGuidesActive
303 | YES
304 | UniqueID
305 | 1
306 | UseEntirePage
307 |
308 | VPages
309 | 1
310 | WindowInfo
311 |
312 | CurrentSheet
313 | 0
314 | ExpandedCanvases
315 |
316 |
317 | name
318 | Canvas 1
319 |
320 |
321 | Frame
322 | {{43, 185}, {916, 591}}
323 | ListView
324 |
325 | OutlineWidth
326 | 142
327 | RightSidebar
328 |
329 | ShowRuler
330 |
331 | Sidebar
332 |
333 | SidebarWidth
334 | 120
335 | VisibleRegion
336 | {{-95, 0}, {767, 437}}
337 | Zoom
338 | 1
339 | ZoomValues
340 |
341 |
342 | Canvas 1
343 | 1
344 | 1
345 |
346 |
347 |
348 | saveQuickLookFiles
349 | YES
350 |
351 |
352 |
--------------------------------------------------------------------------------
/docs/migration-options.graffle/image1.tiff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/engineyard/engineyard-migrate/77f7a6fb695f88eb8325cee51248f865474b2a0c/docs/migration-options.graffle/image1.tiff
--------------------------------------------------------------------------------
/docs/migration-options.graffle/image2.tiff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/engineyard/engineyard-migrate/77f7a6fb695f88eb8325cee51248f865474b2a0c/docs/migration-options.graffle/image2.tiff
--------------------------------------------------------------------------------
/engineyard-migrate.gemspec:
--------------------------------------------------------------------------------
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.4.11")
23 | s.add_dependency("heroku")
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
38 |
--------------------------------------------------------------------------------
/features/heroku.feature:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/features/migration_errors.feature:
--------------------------------------------------------------------------------
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 no Git 'origin' repo URI
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 | And I have setup my SSH keys
16 | And I have setup my Heroku credentials
17 | Given I run executable "git" with arguments "remote rm origin"
18 | When I run local executable "ey-migrate" with arguments "heroku . --account heroku2ey --environment heroku2eysimpleapp_production"
19 | Then I should see
20 | """
21 | Please host your Git repo externally and add as remote 'origin'.
22 | """
23 |
24 | Scenario: Fail if AppCloud credentials not available
25 | Given I clone the application "git@github.com:engineyard/heroku2ey-simple-app.git" as "simple-app"
26 | And I have a Heroku application "heroku2ey-simple-app"
27 | And I have setup my SSH keys
28 | And I have setup my Heroku credentials
29 |
30 | When I run local executable "ey-migrate" with arguments "heroku . --account heroku2ey --environment heroku2eysimpleapp_production"
31 | Then I should see
32 | """
33 | Please create, boot and deploy an AppCloud application for git@github.com:engineyard/heroku2ey-simple-app.git.
34 | """
35 |
36 | Scenario: Fail if no AppCloud environments/applications match this application
37 | Given I clone the application "git@github.com:engineyard/heroku2ey-simple-app.git" as "simple-app"
38 | And I have a Heroku application "heroku2ey-simple-app"
39 | And I have setup my SSH keys
40 | And I have setup my Heroku credentials
41 |
42 | Given I have setup my AppCloud credentials
43 | And I run executable "git" with arguments "remote rm origin"
44 | And I run executable "git" with arguments "remote add origin git@github.com:engineyard/UNKNOWN.git"
45 |
46 | When I run local executable "ey-migrate" with arguments "heroku . -e heroku2eysimpleapp_production"
47 | Then I should see
48 | """
49 | Please create, boot and deploy an AppCloud application for git@github.com:engineyard/UNKNOWN.git.
50 | """
51 |
52 | Scenario: Fail if too many AppCloud environments match
53 | Given I clone the application "git@github.com:engineyard/heroku2ey-simple-app.git" as "simple-app"
54 | And I have a Heroku application "heroku2ey-simple-app"
55 | And I have setup my SSH keys
56 | And I have setup my Heroku credentials
57 |
58 | Given I have setup my AppCloud credentials
59 | When I run local executable "ey-migrate" with arguments "heroku . -V"
60 | Then I should see "Multiple environments possible, please be more specific:"
61 | Then I should see
62 | """
63 | ey-migrate heroku . --app='heroku2eysimpleapp' --account='heroku2ey' --environment='heroku2eysimpleapp_production'
64 | ey-migrate heroku . --app='heroku2eysimpleapp' --account='heroku2ey' --environment='heroku2ey_noinstances'
65 | """
66 |
67 |
68 | Scenario: Fail if environment hasn't been booted yet
69 | Given I have setup my SSH keys
70 | And I clone the application "git@github.com:engineyard/heroku2ey-simple-app.git" as "simple-app"
71 |
72 | And I have setup my Heroku credentials
73 | And I have a Heroku application "heroku2ey-simple-app"
74 |
75 | Given I have setup my AppCloud credentials
76 |
77 | When I run local executable "ey-migrate" with arguments "heroku . -e heroku2ey_noinstances"
78 | Then I should see
79 | """
80 | Please boot your AppCloud environment and then deploy your application.
81 | """
82 |
83 | Scenario: Fail if application hasn't been deployed yet
84 | Given I have setup my SSH keys
85 | And I clone the application "git@github.com:engineyard/heroku2ey-simple-app.git" as "simple-app"
86 |
87 | And I have setup my Heroku credentials
88 | And I have a Heroku application "heroku2ey-simple-app"
89 |
90 | Given I have setup my AppCloud credentials
91 | And I remove AppCloud "heroku2eysimpleapp_production" application "heroku2eysimpleapp" folder
92 |
93 | When I run local executable "ey-migrate" with arguments "heroku . -e heroku2eysimpleapp_production"
94 | Then I should see
95 | """
96 | Please deploy your AppCloud application before running migration.
97 | """
98 |
99 |
--------------------------------------------------------------------------------
/features/step_definitions/application_setup_steps.rb:
--------------------------------------------------------------------------------
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 |
103 |
--------------------------------------------------------------------------------
/features/step_definitions/common_steps.rb:
--------------------------------------------------------------------------------
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
209 |
--------------------------------------------------------------------------------
/features/step_definitions/web_steps.rb:
--------------------------------------------------------------------------------
1 | When /^I visit the application at "([^"]*)"$/ do |host|
2 | Net::HTTP.start(host) do |http|
3 | req = http.get("/")
4 | @response_body = req.body
5 | end
6 | end
7 |
8 | Then /^I should see table$/ do |table|
9 | doc_table = tableish('table#people tr', 'td,th')
10 | doc_table.should == table.raw
11 | end
12 |
13 | Then /^port "([^"]*)" on "([^"]*)" should be closed$/ do |port, host|
14 | pending
15 | end
16 |
--------------------------------------------------------------------------------
/features/support/appcloud_restore_folders.rb:
--------------------------------------------------------------------------------
1 | module AppcloudRestoreFolder
2 | # In scenarios like 'I remove AppCloud application "my_app_name" folder'
3 | # a folder is removed from AppCloud; but it is required for all other scenarios
4 | # unless explicitly deleted
5 | # This helper ensures that the folder is restored
6 | def remove_from_appcloud(path, environment)
7 | @stdout = File.expand_path(File.join(@tmp_root, "eyssh.remove.out"))
8 | @stderr = File.expand_path(File.join(@tmp_root, "eyssh.remove.err"))
9 | path = path.gsub(%r{/$}, '')
10 | path_tmp = "#{path}.tmp"
11 | @restore_paths ||= []
12 | @restore_paths << [path_tmp, path, environment]
13 | cmd = "mv #{Escape.shell_command(path)} #{Escape.shell_command(path_tmp)}"
14 | system "ey ssh #{Escape.shell_command(cmd)} -e #{environment} > #{@stdout.inspect} 2> #{@stderr.inspect}"
15 | end
16 | end
17 | World(AppcloudRestoreFolder)
18 |
19 | After do
20 | if @restore_paths
21 | @restore_paths.each do |path_tmp, path, environment|
22 | cmd = "mv #{Escape.shell_command(path_tmp)} #{Escape.shell_command(path)}"
23 | system "ey ssh #{Escape.shell_command(cmd)} -e #{environment} > #{@stdout.inspect} 2> #{@stderr.inspect}"
24 | end
25 | end
26 | end
--------------------------------------------------------------------------------
/features/support/common.rb:
--------------------------------------------------------------------------------
1 | module CommonHelpers
2 | def get_command_output
3 | strip_color_codes(File.read(@stdout)).chomp
4 | end
5 |
6 | def strip_color_codes(text)
7 | text.gsub(/\e\[\d+m/, '')
8 | end
9 |
10 | def in_tmp_folder(&block)
11 | FileUtils.chdir(@tmp_root, &block)
12 | end
13 |
14 | def in_project_folder(&block)
15 | project_folder = @active_project_folder || @tmp_root
16 | FileUtils.chdir(project_folder, &block)
17 | end
18 |
19 | def in_mock_world_path(&block)
20 | FileUtils.chdir(@mock_world_path, &block)
21 | end
22 |
23 | def in_home_folder(&block)
24 | FileUtils.chdir(@home_path, &block)
25 | end
26 |
27 | def force_local_lib_override(project_name = @project_name)
28 | rakefile = File.read(File.join(project_name, 'Rakefile'))
29 | File.open(File.join(project_name, 'Rakefile'), "w+") do |f|
30 | f << "$:.unshift('#{@lib_path}')\n"
31 | f << rakefile
32 | end
33 | end
34 |
35 | def setup_active_project_folder project_name
36 | @active_project_folder = File.join(@tmp_root, project_name)
37 | @project_name = project_name
38 | end
39 |
40 | # capture both [stdout, stderr] as well as stdin
41 | def capture_stdios(input = nil, &block)
42 | require 'stringio'
43 | org_stdin, $stdin = $stdin, StringIO.new(input) if input
44 | org_stdout, $stdout = $stdout, StringIO.new
45 | org_stderr, $stderr = $stdout, StringIO.new
46 | yield
47 | return [$stdout.string, $stderr.string]
48 | ensure
49 | $stderr = org_stderr
50 | $stdout = org_stdout
51 | $stdin = org_stdin
52 | end
53 | end
54 |
55 | World(CommonHelpers)
--------------------------------------------------------------------------------
/features/support/env.rb:
--------------------------------------------------------------------------------
1 | $:.unshift(File.expand_path(File.dirname(__FILE__) + '/../../lib'))
2 | require 'bundler/setup'
3 | require 'net/http'
4 | require 'escape'
5 |
6 | require 'cucumber/web/tableish'
7 |
8 | [$stdout, $stderr].each { |pipe| pipe.sync = true }
9 |
10 | Before do
11 | @tmp_root = File.dirname(__FILE__) + "/../../tmp"
12 | @active_project_folder = @tmp_root
13 | @home_path = File.expand_path(File.join(@tmp_root, "home"))
14 | @lib_path = File.expand_path(File.dirname(__FILE__) + "/../../lib")
15 | @fixtures_path = File.expand_path(File.dirname(__FILE__) + "/../../fixtures")
16 | @mock_world_path = File.expand_path(File.dirname(__FILE__) + "/../../fixtures/mock_world")
17 |
18 | @repos_path = File.expand_path(File.dirname(__FILE__) + "/../../fixtures/repos")
19 | FileUtils.mkdir_p @repos_path
20 |
21 | FileUtils.rm_rf @tmp_root
22 | FileUtils.mkdir_p @home_path
23 | ENV['HOME'] = @home_path
24 | end
25 |
--------------------------------------------------------------------------------
/features/support/matchers.rb:
--------------------------------------------------------------------------------
1 |
2 | module Matchers
3 | RSpec::Matchers.define :contain do |expected_text|
4 | match do |text|
5 | text.index expected_text
6 | end
7 | end
8 | end
9 |
10 | World(Matchers)
11 |
--------------------------------------------------------------------------------
/features/support/web.rb:
--------------------------------------------------------------------------------
1 | module Web
2 | def response_body
3 | @response_body
4 | end
5 | end
6 | World(Web)
--------------------------------------------------------------------------------
/fixtures/data/simple-app.sqlite3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/engineyard/engineyard-migrate/77f7a6fb695f88eb8325cee51248f865474b2a0c/fixtures/data/simple-app.sqlite3
--------------------------------------------------------------------------------
/heroku-todo.md:
--------------------------------------------------------------------------------
1 | ### Custom domains
2 |
3 | Example:
4 |
5 | custom_domains:basic, wildcard
6 |
7 | There are no restrictions on domains associated with your AppCloud account.
8 |
9 | ### Cron [todo]
10 |
11 | Examples:
12 |
13 | heroku addons:add cron:daily
14 | heroku addons:add cron:hourly
15 |
16 | Heroku's `cron` addon ran your `rake cron` task, either daily or hourly.
17 |
18 | A corresponding cron job will be created for you on AppCloud:
19 |
20 | cd /data/appname/current && RAILS_ENV=production rake cron
21 |
22 | ### Logging
23 |
24 | Example:
25 |
26 | logging:advanced, basic, expanded
27 |
28 | AppCloud implements its own logging system.
29 |
30 | ### Memcached
31 |
32 | Example:
33 |
34 | memcache:100mb, 10gb, 1gb, 250mb, 50gb...
35 |
36 | AppCloud applications automatically have memcached enabled.
37 |
38 | ### New Relic
39 |
40 | Example:
41 |
42 | newrelic:bronze, gold, silver
43 |
44 | You can enable New Relic for your AppCloud account through the https://cloud.engineyard.com dashboard.
45 |
46 | ### Release management
47 |
48 | Example:
49 |
50 | releases:basic, advanced
51 |
52 | AppCloud implements its release management system.
53 |
54 | ### SSL
55 |
56 | Example:
57 |
58 | ssl:hostname, ip, piggyback, sni
59 |
60 | There is no cost for installing SSL for your AppCloud application through the https://cloud.engineyard.com dashboard.
61 |
62 | ### Other addons
63 |
64 | The remaining known Heroku addons are:
65 |
66 | amazon_rds
67 | apigee:basic
68 | apigee_facebook:basic
69 | bundles:single, unlimited
70 | cloudant:argon, helium, krypton...
71 | cloudmailin:test
72 | custom_error_pages
73 | deployhooks:basecamp, campfire...
74 | exceptional:basic, premium
75 | heroku-postgresql:baku, fugu, ika...
76 | hoptoad:basic, plus
77 | indextank:plus, premium, pro...
78 | mongohq:free, large, micro, small
79 | moonshadosms:basic, free, max, plus...
80 | pandastream:duo, quad, sandbox, solo
81 | pgbackups:basic, plus
82 | pusher:test
83 | redistogo:large, medium, mini, nano...
84 | sendgrid:free, premium, pro
85 | websolr:gold, platinum, silver...
86 | zencoder:100k, 10k, 1k, 20k, 2k, 40k, 4k...
87 | zerigo_dns:basic, tier1, tier2
88 |
89 | --- beta ---
90 | chargify:test
91 | docraptor:test
92 | heroku-postgresql:...
93 | jasondb:test
94 | memcached:basic
95 | pgbackups:daily, hourly
96 | recurly:test
97 | releases:advanced
98 | ticketly:test
99 |
100 |
101 |
--------------------------------------------------------------------------------
/lib/engineyard-migrate.rb:
--------------------------------------------------------------------------------
1 | module Engineyard
2 | module Migrate
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/lib/engineyard-migrate/cli.rb:
--------------------------------------------------------------------------------
1 | require 'thor'
2 | require 'uri'
3 | require 'net/http'
4 | require 'net/sftp'
5 | require 'POpen4'
6 | require 'engineyard/thor'
7 | require "engineyard/cli"
8 | require "engineyard/cli/ui"
9 | require "engineyard/error"
10 |
11 | module Engineyard::Migrate
12 | class CLI < Thor
13 | include EY::UtilityMethods
14 | attr_reader :verbose
15 |
16 | desc "heroku PATH", "Migrate this Heroku app to Engine Yard AppCloud"
17 | method_option :verbose, :aliases => ["-V"], :desc => "Display more output"
18 | method_option :environment, :aliases => ["-e"], :desc => "Environment in which to deploy this application", :type => :string
19 | method_option :account, :aliases => ["-c"], :desc => "Name of the account you want to deploy in"
20 | def heroku(path)
21 | @verbose = options[:verbose]
22 | error "Path '#{path}' does not exist" unless File.exists? path
23 | FileUtils.chdir(path) do
24 | begin
25 | heroku_repo = `git config remote.heroku.url`.strip
26 | if heroku_repo.empty?
27 | error "Not a Salesforce Heroku application."
28 | end
29 | heroku_repo =~ /git@heroku\.com:(.*)\.git/
30 | heroku_app_name = $1
31 |
32 | say "Requesting Heroku account information..."; $stdout.flush
33 | say "Heroku app: "; say heroku_app_name, :green
34 |
35 | say `heroku info`
36 | say ""
37 |
38 | repo = `git config remote.origin.url`.strip
39 | if repo.empty?
40 | error "Please host your Git repo externally and add as remote 'origin'.", <<-SUGGESTION.gsub(/^\s{12}/, '')
41 | You can create a GitHub repository using 'github' gem:
42 | $ gem install github
43 | $ gh create-from-local --private
44 | SUGGESTION
45 | end
46 | unless EY::API.new.token
47 | error "Please create, boot and deploy an AppCloud application for #{repo}."
48 | end
49 |
50 | say "Requesting AppCloud account information..."; $stdout.flush
51 | @app, @environment = fetch_app_and_environment(options[:app], options[:environment], options[:account])
52 |
53 | unless @app.repository_uri == repo
54 | error "Please create, boot and deploy an AppCloud application for #{repo}."
55 | end
56 | unless @environment.app_master
57 | error "Please boot your AppCloud environment and then deploy your application."
58 | end
59 |
60 | @app.name = @app.name
61 | app_master_host = @environment.app_master.public_hostname
62 | app_master_user = @environment.username
63 |
64 | say "Application: "; say "#{@app.name}", :green
65 | say "Account: "; say "#{@environment.account.name}", :green
66 | say "Environment: "; say "#{@environment.name}", :green
67 | say "Cluster size: "; say "#{@environment.instances_count}"
68 | say "Hostname: "; say "#{app_master_host}"
69 | debug "$RACK_ENV: "; debug "#{@environment.framework_env}"
70 | say ""
71 |
72 | # TODO - what if no application deployed yet?
73 | # bash: line 0: cd: /data/heroku2eysimpleapp/current: No such file or directory
74 |
75 | # TODO - to test for cron setup:
76 | # dna_env["cron"] - list of:
77 | # [0] {
78 | # "minute" => "0",
79 | # "name" => "rake cron",
80 | # "command" => "cd /data/heroku2eysimpleapp/current && RAILS_ENV=production rake cron",
81 | # "month" => "*",
82 | # "hour" => "1",
83 | # "day" => "*/1",
84 | # "user" => "deploy",
85 | # "weekday" => "*"
86 | # }
87 |
88 | say "Testing AppCloud application status..."
89 |
90 | deploy_path_found = ssh_appcloud "test -d #{@app.name}/current && echo 'found'",
91 | :path => '/data', :return_output => true
92 | error "Please deploy your AppCloud application before running migration." unless deploy_path_found =~ /found/
93 |
94 | say "Setting up Heroku on AppCloud..."
95 |
96 | ssh_appcloud "sudo gem install heroku taps sqlite3 mysql2 pg --no-ri --no-rdoc -q"
97 | ssh_appcloud "git remote rm heroku 2> /dev/null; git remote add heroku #{heroku_repo} 2> /dev/null"
98 |
99 | say "Uploading Heroku credential file..."
100 | home_path = ssh_appcloud("pwd", :path => "~", :return_output => true)
101 | debug "AppCloud $HOME: "; debug home_path, :yellow
102 |
103 | require 'heroku/auth'
104 | netrc_file = File.basename(Heroku::Auth.netrc_path)
105 | remote_netrc = File.join(home_path, ".netrc")
106 | Net::SFTP.start(app_master_host, app_master_user) do |sftp|
107 | sftp.upload!(Heroku::Auth.netrc_path, remote_netrc)
108 | end
109 | ssh_appcloud("chmod 0600 #{remote_netrc}")
110 | say ""
111 |
112 | say "Migrating data from Heroku '#{heroku_app_name}' to AppCloud '#{@app.name}'..."
113 | env_vars = %w[RAILS_ENV RACK_ENV MERB_ENV].map {|var| "#{var}=#{@environment.framework_env}" }.join(" ")
114 | ssh_appcloud "#{env_vars} heroku db:pull --confirm #{heroku_app_name} 2>&1"
115 | say ""
116 |
117 | say "Migration complete!", :green
118 | rescue SystemExit
119 | rescue EY::MultipleMatchesError => e
120 | envs = []
121 | e.message.split(/\n/).map do |line|
122 | env = {}
123 | line.scan(/--([^=]+)='([^']+)'/) do
124 | env[$1] = $2
125 | end
126 | envs << env unless env.empty?
127 | end
128 | too_many_environments_discovered 'heroku', envs, path
129 | rescue Net::SSH::AuthenticationFailed => e
130 | error "Please setup your SSH credentials for AppCloud."
131 | rescue Net::SFTP::StatusException => e
132 | error e.description + ": " + e.text
133 | rescue Exception => e
134 | say "Migration failed", :red
135 | puts e.inspect
136 | puts e.backtrace
137 | end
138 | end
139 | end
140 |
141 | map "-v" => :version, "--version" => :version, "-h" => :help, "--help" => :help
142 |
143 | private
144 | def ssh_appcloud(cmd, options = {})
145 | path = options[:path] || "/data/#{@app.name}/current/"
146 | flags = " #{options[:flags]}" || "" if options[:flags] # app master by default
147 | full_cmd = "cd #{path}; #{cmd}"
148 | ssh_cmd = "ey ssh #{Escape.shell_command([full_cmd])}#{flags} -e #{@environment.name} -c #{@environment.account.name}"
149 | debug options[:return_output] ? "Capturing: " : "Running: "
150 | debug ssh_cmd, :yellow; $stdout.flush
151 | out = ""
152 | status =
153 | POpen4::popen4(ssh_cmd) do |stdout, stderr, stdin, pid|
154 | if options[:return_output]
155 | out += stdout.read.strip
156 | err = stderr.read.strip; say err unless err.empty?
157 | else
158 | while line = stdout.gets("\n") || stderr.gets("\n")
159 | say line
160 | end
161 | end
162 | end
163 |
164 | puts "exitstatus : #{ status.exitstatus }" unless status.exitstatus == 0
165 | out if options[:return_output]
166 | end
167 |
168 | def say(msg, color = nil)
169 | color ? shell.say(msg, color) : shell.say(msg)
170 | end
171 |
172 | def debug(msg, color = nil)
173 | say(msg, color) if verbose
174 | end
175 |
176 | def display(text)
177 | shell.say text
178 | exit
179 | end
180 |
181 | def error(text, suggestion = nil)
182 | shell.say "ERROR: #{text}", :red
183 | if suggestion
184 | shell.say ""
185 | shell.say suggestion
186 | end
187 | exit
188 | end
189 |
190 | # TODO - not being used yet
191 | def no_environments_discovered
192 | say "No AppCloud environments found for this application.", :red
193 | say "Either:"
194 | say " * Create an AppCloud environment for this application/git URL"
195 | say " * Use --environment/--account flags to select an AppCloud environment"
196 | end
197 |
198 | def too_many_environments_discovered(task, environments, *args)
199 | return no_environments_discovered if environments.empty?
200 | say "Multiple environments possible, please be more specific:", :red
201 | say ""
202 | environments.each do |env|
203 | flags = env.map { |key, value| "--#{key}='#{value}'"}.join(" ")
204 | say " ey-migrate #{task} #{args.join(' ')} #{flags}"
205 | end
206 | exit 1
207 | end
208 |
209 | end
210 | end
211 |
--------------------------------------------------------------------------------
/lib/engineyard-migrate/version.rb:
--------------------------------------------------------------------------------
1 | module Engineyard
2 | module Migrate
3 | VERSION = "1.1.0"
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | $:.unshift(File.expand_path(File.dirname(__FILE__) + '/../lib'))
2 | require 'bundler/setup'
3 | require 'engineyard-migrate'
4 | require 'rspec'
--------------------------------------------------------------------------------