porter 0.1.4 → 0.1.5
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.
- 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
|