porter 0.1.4 → 0.1.5
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +7 -5
- data/generators/porter/porter_generator.rb +4 -3
- data/generators/porter/templates/porter.rb +4 -0
- data/generators/porter/templates/porter_config.yml +16 -2
- data/lib/porter/recipes/porter.rb +19 -9
- data/lib/porter.rb +1 -1
- data/lib/tasks/porter.rake +91 -0
- data/porter.gemspec +4 -3
- metadata +6 -4
- data/generators/porter/templates/porter.rake +0 -76
data/README.rdoc
CHANGED
@@ -4,11 +4,11 @@ The Porter gem is comprised of Capistrano and Rake tasks that make cloning your
|
|
4
4
|
|
5
5
|
== Overview
|
6
6
|
|
7
|
-
* A mysqldump command is remotely issued (via Capistrano) to the remote production
|
7
|
+
* A mysqldump command is remotely issued (via Capistrano) to the remote server (production or staging environment), saving the result as a compressed (gz) file
|
8
8
|
* The database backup file from the server is retrieved (via scp) and decompressed
|
9
9
|
* The development database is dropped, recreated, and restored from the backup
|
10
10
|
* Assets stored in shared/public are rysnc'd down to your local public directory (exclusions are accepted!)
|
11
|
-
* Separate rake tasks are included for restoring the db and re-syncing the assets without re-dumping the
|
11
|
+
* Separate rake tasks are included for restoring the db and re-syncing the assets without re-dumping the remote db
|
12
12
|
|
13
13
|
== Dependencies
|
14
14
|
|
@@ -26,9 +26,11 @@ The Porter gem is comprised of Capistrano and Rake tasks that make cloning your
|
|
26
26
|
|
27
27
|
== Usage
|
28
28
|
|
29
|
-
* cap porter:
|
30
|
-
* rake porter:
|
31
|
-
* rake porter:
|
29
|
+
* cap porter:[stage] (creates the remote db backup file then calls the two rake tasks below)
|
30
|
+
* rake porter:[stage]:db (if you've already got a db backup on the remote server and want to restore from it again)
|
31
|
+
* rake porter:[stage]:assets (rsync the assets down from the remote server again)
|
32
|
+
|
33
|
+
Note: [stage] is most likely going to be 'production' unless your app has a staging or demo environment and you want to pull from there instead.
|
32
34
|
|
33
35
|
== Copyright
|
34
36
|
|
@@ -9,9 +9,10 @@ class PorterGenerator < Rails::Generator::Base
|
|
9
9
|
@domain = @app + (@app.include?('.') ? '' : '.com')
|
10
10
|
|
11
11
|
record do |m|
|
12
|
-
m.template
|
13
|
-
m.template
|
14
|
-
m.append_to 'config/deploy.rb',
|
12
|
+
m.template 'porter_config.yml', File.join('config', 'porter_config.yml')
|
13
|
+
m.template 'porter.rb', File.join('lib', 'tasks', 'porter.rb')
|
14
|
+
m.append_to 'config/deploy.rb', "require 'porter'"
|
15
|
+
m.append_to 'Rakefile', "require 'tasks/porter'"
|
15
16
|
end
|
16
17
|
end
|
17
18
|
|
@@ -5,6 +5,20 @@ server:
|
|
5
5
|
domain: <%= domain %>
|
6
6
|
dir: /opt/apps/<%= app %>
|
7
7
|
|
8
|
+
# If you want to be able to pull from different environments, and the domains,
|
9
|
+
# usernames or directories differ, you can delete the catch-all server option
|
10
|
+
# above and enable a config for each environment's details like this:
|
11
|
+
#
|
12
|
+
# production:
|
13
|
+
# user: deploy
|
14
|
+
# domain: <%= domain %>
|
15
|
+
# dir: /opt/apps/<%= app %>
|
16
|
+
#
|
17
|
+
# staging:
|
18
|
+
# user: deploy
|
19
|
+
# domain: <%= domain %>
|
20
|
+
# dir: /opt/apps/<%= app %>
|
21
|
+
|
8
22
|
assets:
|
9
23
|
entire_dirs: public/attachments
|
10
24
|
excludable_dir:
|
@@ -16,9 +30,9 @@ assets:
|
|
16
30
|
# Example:
|
17
31
|
#
|
18
32
|
# assets:
|
19
|
-
# entire_dirs:
|
33
|
+
# entire_dirs: public/campaign public/user
|
20
34
|
# excludable_dir: public/attachments
|
21
35
|
# excludable_model: Attachment
|
22
36
|
# excludable_column: attachable_type
|
23
|
-
# excludable_matches:
|
37
|
+
# excludable_matches: Document Product
|
24
38
|
# rsync_options: --verbose --progress --stats --recursive --times --compress
|
@@ -1,15 +1,25 @@
|
|
1
1
|
if defined?(Capistrano)
|
2
2
|
Capistrano::Configuration.instance.load do
|
3
3
|
namespace :porter do
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
4
|
+
CONFIG = YAML::load_file('config/porter_config.yml')
|
5
|
+
DATABASES = YAML::load_file('config/database.yml')
|
6
|
+
STAGES = DATABASES.keys - %w(development test) # you don't need data out of these
|
7
|
+
STAGES.each do |stage|
|
8
|
+
# task names for each of your other stages: production, staging, etc.
|
9
|
+
# cap porter:production, cap porter:staging, etc.
|
10
|
+
task stage do
|
11
|
+
src_db = DATABASES[stage]
|
12
|
+
db = src_db['database']
|
13
|
+
user = src_db['username']
|
14
|
+
pass = src_db['password']
|
15
|
+
|
16
|
+
domain = CONFIG[stage].nil? ? CONFIG['server']['domain'] : CONFIG[stage]['domain']
|
17
|
+
server domain, :porter
|
18
|
+
|
19
|
+
run "mysqldump --user=#{user} --password=#{pass} #{db} | gzip > ~/#{db}.sql.gz", :roles => :porter
|
20
|
+
system "rake porter:#{stage}:db"
|
21
|
+
system "rake porter:#{stage}:assets"
|
22
|
+
end
|
13
23
|
end
|
14
24
|
end
|
15
25
|
end
|
data/lib/porter.rb
CHANGED
@@ -1 +1 @@
|
|
1
|
-
require "#{File.dirname(__FILE__)}/porter/recipes/porter"
|
1
|
+
require "#{File.dirname(__FILE__)}/porter/recipes/porter"
|
@@ -0,0 +1,91 @@
|
|
1
|
+
CONFIG = YAML::load_file(File.join(RAILS_ROOT, 'config', 'porter_config.yml'))
|
2
|
+
DATABASES = YAML::load_file(File.join(RAILS_ROOT, 'config', 'database.yml'))
|
3
|
+
STAGES = DATABASES.keys - %w(development test) # you don't need data out of these
|
4
|
+
|
5
|
+
namespace :porter do
|
6
|
+
STAGES.each do |stage|
|
7
|
+
namespace stage do
|
8
|
+
task :db => :environment do
|
9
|
+
# Optional: You can setup specific username and host
|
10
|
+
# combos for each stage in porter_config.yml or use
|
11
|
+
# the default 'server' key to apply to all stages
|
12
|
+
if CONFIG[stage]
|
13
|
+
src_user = CONFIG[stage]['user']
|
14
|
+
src_domain = CONFIG[stage]['domain']
|
15
|
+
else
|
16
|
+
src_user = CONFIG['server']['user']
|
17
|
+
src_domain = CONFIG['server']['domain']
|
18
|
+
end
|
19
|
+
|
20
|
+
src_db = ActiveRecord::Base.configurations[stage]
|
21
|
+
dest_db = ActiveRecord::Base.configurations[RAILS_ENV]
|
22
|
+
dest_root = RAILS_ROOT
|
23
|
+
|
24
|
+
app = src_db['database'].split('_').first
|
25
|
+
root = RAILS_ROOT
|
26
|
+
|
27
|
+
puts "Retrieving latest compressed database backup from #{stage} server..."
|
28
|
+
system "scp #{src_user}@#{src_domain}:~/#{src_db['database']}.sql.gz #{root}"
|
29
|
+
|
30
|
+
puts "Decompressing database backup..."
|
31
|
+
system "gunzip #{root}/#{src_db['database']}.sql.gz"
|
32
|
+
|
33
|
+
# Drop the database if it exists
|
34
|
+
begin
|
35
|
+
ActiveRecord::Base.establish_connection(dest_db)
|
36
|
+
ActiveRecord::Base.connection # Should raise Mysql::Error if db doesn't exist
|
37
|
+
puts "Dropping database: " + dest_db['database']
|
38
|
+
Rake::Task['db:drop'].execute
|
39
|
+
rescue Mysql::Error => e
|
40
|
+
raise e unless e.message =~ /Unknown database/
|
41
|
+
end
|
42
|
+
|
43
|
+
puts "Creating database: " + dest_db['database']
|
44
|
+
Rake::Task['db:create'].execute
|
45
|
+
|
46
|
+
puts "Restoring database from backup..."
|
47
|
+
mysql_version = `which mysql`.empty? ? 'mysql5' : 'mysql'
|
48
|
+
system "#{mysql_version} -u root #{dest_db['database']} < #{root}/#{src_db['database']}.sql"
|
49
|
+
|
50
|
+
puts "Removing database backup file..."
|
51
|
+
system "rm #{root}/#{src_db['database']}.sql"
|
52
|
+
|
53
|
+
puts "Production data reload complete"
|
54
|
+
end
|
55
|
+
|
56
|
+
task :assets => :environment do
|
57
|
+
root = RAILS_ROOT
|
58
|
+
user = CONFIG[stage].nil? ? CONFIG['server']['user'] : CONFIG[stage]['user']
|
59
|
+
domain = CONFIG[stage].nil? ? CONFIG['server']['domain'] : CONFIG[stage]['domain']
|
60
|
+
dir = CONFIG[stage].nil? ? CONFIG['server']['dir'] : CONFIG[stage]['dir']
|
61
|
+
entire_dirs = CONFIG['assets']['entire_dirs'].blank? ? '' : CONFIG['assets']['entire_dirs'].gsub(/,/,'').split(' ').map { |i| i.strip }
|
62
|
+
excludable_dir = CONFIG['assets']['excludable_dir']
|
63
|
+
model = CONFIG['assets']['excludable_model'].constantize unless CONFIG['assets']['excludable_model'].blank?
|
64
|
+
column = CONFIG['assets']['excludable_column']
|
65
|
+
exclusions = CONFIG['assets']['excludable_matches'].blank? ? '' : CONFIG['assets']['excludable_matches'].gsub(/,/,'').split(' ').map { |i| i.strip }
|
66
|
+
rsync_options = CONFIG['assets']['rsync_options']
|
67
|
+
|
68
|
+
if exclusions.blank?
|
69
|
+
entire_dirs << excludable_dir unless excludable_dir.blank?
|
70
|
+
else
|
71
|
+
puts "Building a list of excludable assets (excluding: #{exclusions.join(', ')}) to rsync down..."
|
72
|
+
rsync_file_list = File.new('rsync_file_list.txt', "w")
|
73
|
+
attachments = model.find(:all, :conditions => ["#{column} NOT IN (?)", exclusions])
|
74
|
+
attachments.each do |a|
|
75
|
+
rsync_file_list.send((a == attachments.last ? :print : :puts), a.partitioned_path.join('/'))
|
76
|
+
end
|
77
|
+
rsync_file_list.close
|
78
|
+
system "rsync --files-from=#{root}/rsync_file_list.txt #{rsync_options} #{user}@#{domain}:#{dir}/shared/#{excludable_dir}/ #{excludable_dir}"
|
79
|
+
system "rm #{root}/rsync_file_list.txt" if File.exists?("#{root}/rsync_file_list.txt")
|
80
|
+
end
|
81
|
+
|
82
|
+
entire_dirs.each do |d|
|
83
|
+
puts "Synchronizing assets in #{d}..."
|
84
|
+
system "rsync #{rsync_options} #{user}@#{domain}:#{dir}/shared/#{d}/ #{d}"
|
85
|
+
end
|
86
|
+
|
87
|
+
puts "Production asset synchronization complete"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/porter.gemspec
CHANGED
@@ -5,10 +5,10 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{porter}
|
8
|
-
s.version = "0.1.
|
8
|
+
s.version = "0.1.5"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
-
s.authors = ["Kenny Johnston"]
|
11
|
+
s.authors = ["Kenny Johnston", "Robert Bousquet"]
|
12
12
|
s.date = %q{2010-07-02}
|
13
13
|
s.description = %q{Capistrano and Rake tasks for cloning production database and assets to development.}
|
14
14
|
s.email = %q{info@appcreations.com}
|
@@ -24,10 +24,11 @@ Gem::Specification.new do |s|
|
|
24
24
|
"VERSION",
|
25
25
|
"generators/porter/lib/insert_commands.rb",
|
26
26
|
"generators/porter/porter_generator.rb",
|
27
|
-
"generators/porter/templates/porter.
|
27
|
+
"generators/porter/templates/porter.rb",
|
28
28
|
"generators/porter/templates/porter_config.yml",
|
29
29
|
"lib/porter.rb",
|
30
30
|
"lib/porter/recipes/porter.rb",
|
31
|
+
"lib/tasks/porter.rake",
|
31
32
|
"porter.gemspec",
|
32
33
|
"test/helper.rb"
|
33
34
|
]
|
metadata
CHANGED
@@ -1,16 +1,17 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: porter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 17
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 0.1.
|
9
|
+
- 5
|
10
|
+
version: 0.1.5
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Kenny Johnston
|
14
|
+
- Robert Bousquet
|
14
15
|
autorequire:
|
15
16
|
bindir: bin
|
16
17
|
cert_chain: []
|
@@ -63,10 +64,11 @@ files:
|
|
63
64
|
- VERSION
|
64
65
|
- generators/porter/lib/insert_commands.rb
|
65
66
|
- generators/porter/porter_generator.rb
|
66
|
-
- generators/porter/templates/porter.
|
67
|
+
- generators/porter/templates/porter.rb
|
67
68
|
- generators/porter/templates/porter_config.yml
|
68
69
|
- lib/porter.rb
|
69
70
|
- lib/porter/recipes/porter.rb
|
71
|
+
- lib/tasks/porter.rake
|
70
72
|
- porter.gemspec
|
71
73
|
- test/helper.rb
|
72
74
|
has_rdoc: true
|
@@ -1,76 +0,0 @@
|
|
1
|
-
namespace :porter do
|
2
|
-
namespace :production do
|
3
|
-
task :db => :environment do
|
4
|
-
root = RAILS_ROOT
|
5
|
-
config = YAML.load_file(File.join(RAILS_ROOT, 'config', 'porter_config.yml'))
|
6
|
-
user = config['server']['user']
|
7
|
-
domain = config['server']['domain']
|
8
|
-
dbconfig = ActiveRecord::Base.configurations[RAILS_ENV]
|
9
|
-
app = dbconfig['database'].gsub('_dev', '')
|
10
|
-
|
11
|
-
puts "Retrieving latest compressed database backup from production server..."
|
12
|
-
system "scp #{user}@#{domain}:~/#{app}.sql.gz #{root}"
|
13
|
-
|
14
|
-
puts "Decompressing database backup..."
|
15
|
-
system "gunzip #{root}/#{app}.sql.gz"
|
16
|
-
|
17
|
-
# Drop the database if it exists
|
18
|
-
begin
|
19
|
-
ActiveRecord::Base.establish_connection(dbconfig)
|
20
|
-
ActiveRecord::Base.connection # Should raise Mysql::Error if db doesn't exist
|
21
|
-
puts "Dropping database: " + dbconfig['database']
|
22
|
-
Rake::Task['db:drop'].execute
|
23
|
-
rescue Mysql::Error => e
|
24
|
-
raise e unless e.message =~ /Unknown database/
|
25
|
-
end
|
26
|
-
|
27
|
-
puts "Creating database: " + dbconfig['database']
|
28
|
-
Rake::Task['db:create'].execute
|
29
|
-
|
30
|
-
puts "Restoring database from backup..."
|
31
|
-
mysql_version = `which mysql`.empty? ? 'mysql5' : 'mysql'
|
32
|
-
system "#{mysql_version} -u root #{dbconfig['database']} < #{root}/#{app}.sql"
|
33
|
-
|
34
|
-
puts "Removing database backup file..."
|
35
|
-
system "rm #{root}/#{app}.sql"
|
36
|
-
|
37
|
-
puts "Production data reload complete"
|
38
|
-
end
|
39
|
-
|
40
|
-
task :assets => :environment do
|
41
|
-
require 'yaml'
|
42
|
-
root = RAILS_ROOT
|
43
|
-
config = YAML.load_file(File.join(RAILS_ROOT, 'config', 'porter_config.yml'))
|
44
|
-
user = config['server']['user']
|
45
|
-
domain = config['server']['domain']
|
46
|
-
dir = config['server']['dir']
|
47
|
-
entire_dirs = config['assets']['entire_dirs'].blank? ? '' : config['assets']['entire_dirs'].split(',').map { |i| i.strip }
|
48
|
-
excludable_dir = config['assets']['excludable_dir']
|
49
|
-
model = config['assets']['excludable_model'].constantize unless config['assets']['excludable_model'].blank?
|
50
|
-
column = config['assets']['excludable_column']
|
51
|
-
exclusions = config['assets']['excludable_matches'].blank? ? '' : config['assets']['excludable_matches'].split(',').map { |i| i.strip }
|
52
|
-
rsync_options = config['assets']['rsync_options']
|
53
|
-
|
54
|
-
if exclusions.blank?
|
55
|
-
entire_dirs << excludable_dir unless excludable_dir.blank?
|
56
|
-
else
|
57
|
-
puts "Building a list of excludable assets (excluding: #{exclusions.join(', ')}) to rsync down..."
|
58
|
-
rsync_file_list = File.new('rsync_file_list.txt', "w")
|
59
|
-
attachments = model.find(:all, :conditions => ["#{column} NOT IN (?)", exclusions])
|
60
|
-
attachments.each do |a|
|
61
|
-
rsync_file_list.send((a == attachments.last ? :print : :puts), a.partitioned_path.join('/'))
|
62
|
-
end
|
63
|
-
rsync_file_list.close
|
64
|
-
system "rsync --files-from=#{root}/rsync_file_list.txt #{rsync_options} #{user}@#{domain}:#{dir}/shared/#{excludable_dir}/ #{excludable_dir}"
|
65
|
-
system "rm #{root}/rsync_file_list.txt" if File.exists?("#{root}/rsync_file_list.txt")
|
66
|
-
end
|
67
|
-
|
68
|
-
entire_dirs.each do |d|
|
69
|
-
puts "Synchronizing assets in #{d}..."
|
70
|
-
system "rsync #{rsync_options} #{user}@#{domain}:#{dir}/shared/#{d}/ #{d}"
|
71
|
-
end
|
72
|
-
|
73
|
-
puts "Production asset synchronization complete"
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|