wordmove 0.1.0.alpha → 0.1.0.beta
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.mdown +18 -20
- data/lib/wordmove/assets/import.php.erb +2 -2
- data/lib/wordmove/deployer/base.rb +10 -7
- data/lib/wordmove/deployer/ftp.rb +2 -1
- data/lib/wordmove/deployer/ssh.rb +2 -1
- data/lib/wordmove/generators/Movefile +6 -11
- data/lib/wordmove/sql_mover.rb +59 -0
- data/lib/wordmove/version.rb +1 -1
- data/spec/fixtures/Movefile +3 -8
- metadata +3 -4
- data/lib/wordmove/hosts/local_host.rb +0 -41
- data/lib/wordmove/hosts/remote_host.rb +0 -135
data/README.mdown
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# Wordmove
|
2
2
|
|
3
|
-
Wordmove is a nice little gem that lets you automatically mirror local Wordpress
|
3
|
+
Wordmove is a nice little gem that lets you automatically mirror local Wordpress
|
4
|
+
installations and DB data back and forth from your local development machine to
|
5
|
+
the remote staging server. SSH and FTP connections are both supported.
|
4
6
|
|
5
7
|
Think of it like Capistrano for Wordpress, complete with push/pull capabilities.
|
6
8
|
|
@@ -9,7 +11,7 @@ Think of it like Capistrano for Wordpress, complete with push/pull capabilities.
|
|
9
11
|
That's easy:
|
10
12
|
|
11
13
|
```
|
12
|
-
gem install wordmove
|
14
|
+
gem install wordmove --pre
|
13
15
|
```
|
14
16
|
|
15
17
|
## Usage
|
@@ -33,7 +35,7 @@ local:
|
|
33
35
|
wordpress_path: "~/dev/sites/your_site"
|
34
36
|
database:
|
35
37
|
name: "database_name"
|
36
|
-
|
38
|
+
user: "user"
|
37
39
|
password: "password"
|
38
40
|
host: "host"
|
39
41
|
remote:
|
@@ -46,34 +48,30 @@ remote:
|
|
46
48
|
- Movefile
|
47
49
|
database:
|
48
50
|
name: "database_name"
|
49
|
-
|
51
|
+
user: "user"
|
50
52
|
password: "password"
|
51
53
|
host: "host"
|
52
54
|
ssh:
|
53
|
-
|
55
|
+
user: "user"
|
54
56
|
password: "password" # optional, will use SSH public key if available
|
55
57
|
host: "host"
|
56
58
|
port: 2202 # optional
|
57
59
|
```
|
58
|
-
## About SSH authentication
|
59
|
-
|
60
60
|
### If you have your local SSH public key already installed on the remote machine.. (recommended)
|
61
|
-
Just
|
62
|
-
|
63
|
-
### Else..
|
64
|
-
You need to have [`sshpass`](http://sourceforge.net/projects/sshpass/) on your machine. On Linux, install it with your standard package manager. On Mac, you can have it via [`brew`](https://github.com/mxcl/homebrew):
|
61
|
+
Just not use the `remote.ssh.password` field on your `Movefile`. Easy peasy.
|
65
62
|
|
66
|
-
|
67
|
-
|
68
|
-
```
|
63
|
+
### If you want to specify SSH password on the Movefile
|
64
|
+
Please take a look at the various gotchas of the underlying [`photocopier` gem](https://github.com/stefanoverna/photocopier#password-gotchas).
|
69
65
|
|
70
|
-
|
66
|
+
### How the heck you are able to sync the DB via FTP?
|
67
|
+
Thanks for asking. We basically upload via FTP a PHP script that performs the various
|
68
|
+
import/export operations. This script then gets executed via HTTP. Don't worry
|
69
|
+
too much about security though: the script is deleted just after the usage,
|
70
|
+
and can only be executed by `wordmove`, as each time it requires a pre-shared
|
71
|
+
one-time-password to be run.
|
71
72
|
|
72
|
-
|
73
|
-
*
|
74
|
-
* Wordmove requires `mysqldump` and `mysql` to be present in the remote host and executable by the SSH user;
|
75
|
-
* Wordmove requires `rsync` to be present and executable in your machine;
|
76
|
-
* As said above, you may also need `sshpass` on your machine;
|
73
|
+
* The dump script is the [`MYSQL-dump` PHP package](https://github.com/dg/MySQL-dump) by David Grudl;
|
74
|
+
* The import script used is the [BigDump](http://www.ozerov.de/bigdump/) library;
|
77
75
|
|
78
76
|
## License
|
79
77
|
|
@@ -10,7 +10,7 @@ if ($_GET['shared_key'] != $shared_key) {
|
|
10
10
|
// Database configuration
|
11
11
|
|
12
12
|
$db_server = '<%= escape_php db[:host] %>';
|
13
|
-
$db_username = '<%= escape_php db[:
|
13
|
+
$db_username = '<%= escape_php db[:username] %>';
|
14
14
|
$db_password = '<%= escape_php db[:password] %>';
|
15
15
|
$db_name = '<%= escape_php db[:name] %>';
|
16
16
|
|
@@ -48,7 +48,7 @@ $comment[]='/*!'; // Or add your own string to leave out oth
|
|
48
48
|
// See http://dev.mysql.com/doc/refman/5.0/en/charset-charsets.html for the full list
|
49
49
|
// Change this if you have problems with non-latin letters
|
50
50
|
|
51
|
-
$db_connection_charset = '<%= db[:charset] || utf8';
|
51
|
+
$db_connection_charset = '<%= db[:charset] || 'utf8' %>';
|
52
52
|
|
53
53
|
// Default query delimiter: this character at the line end tells Bigdump where a SQL statement ends
|
54
54
|
// Can be changed by DELIMITER statement in the dump file (normally used when defining procedures/functions)
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'active_support/core_ext'
|
2
2
|
require 'wordmove/logger'
|
3
|
+
require 'wordmove/sql_mover'
|
3
4
|
require 'escape'
|
4
5
|
|
5
6
|
module Wordmove
|
@@ -51,17 +52,21 @@ module Wordmove
|
|
51
52
|
%w(uploads themes plugins).each do |task|
|
52
53
|
define_method "push_#{task}" do
|
53
54
|
logger.task "Pushing #{task.titleize}"
|
54
|
-
remote_put_directory(local_wpcontent_path(task), remote_wpcontent_path(task))
|
55
|
+
remote_put_directory(local_wpcontent_path(task), remote_wpcontent_path(task), paths_to_exclude)
|
55
56
|
end
|
56
57
|
|
57
58
|
define_method "pull_#{task}" do
|
58
59
|
logger.task "Pulling #{task.titleize}"
|
59
|
-
remote_get_directory(remote_wpcontent_path(task), local_wpcontent_path(task))
|
60
|
+
remote_get_directory(remote_wpcontent_path(task), local_wpcontent_path(task), paths_to_exclude)
|
60
61
|
end
|
61
62
|
end
|
62
63
|
|
63
64
|
protected
|
64
65
|
|
66
|
+
def paths_to_exclude
|
67
|
+
options[:remote][:exclude] || Array.new
|
68
|
+
end
|
69
|
+
|
65
70
|
def run(command)
|
66
71
|
logger.task_step true, command
|
67
72
|
unless simulate?
|
@@ -97,16 +102,14 @@ module Wordmove
|
|
97
102
|
def adapt_sql(save_to_path, local, remote)
|
98
103
|
logger.task_step true, "adapt dump"
|
99
104
|
unless simulate?
|
100
|
-
|
101
|
-
file.write "UPDATE wp_options SET option_value=\"#{remote[:vhost]}\" WHERE option_name=\"siteurl\" OR option_name=\"home\";\n"
|
102
|
-
end
|
105
|
+
SqlMover.new(save_to_path, local, remote).move!
|
103
106
|
end
|
104
107
|
end
|
105
108
|
|
106
109
|
def mysql_dump_command(options, save_to_path)
|
107
110
|
arguments = [ "mysqldump" ]
|
108
111
|
arguments << "--host=#{options[:host]}" if options[:host].present?
|
109
|
-
arguments << "--user=#{options[:
|
112
|
+
arguments << "--user=#{options[:username]}" if options[:username].present?
|
110
113
|
arguments << "--password=#{options[:password]}" if options[:password].present?
|
111
114
|
arguments << "--default-character-set=#{options[:charset]}" if options[:charset].present?
|
112
115
|
arguments << options[:name]
|
@@ -116,7 +119,7 @@ module Wordmove
|
|
116
119
|
def mysql_import_command(dump_path, options)
|
117
120
|
arguments = [ "mysql" ]
|
118
121
|
arguments << "--host=#{options[:host]}" if options[:host].present?
|
119
|
-
arguments << "--user=#{options[:
|
122
|
+
arguments << "--user=#{options[:username]}" if options[:username].present?
|
120
123
|
arguments << "--password=#{options[:password]}" if options[:password].present?
|
121
124
|
arguments << "--database=#{options[:name]}"
|
122
125
|
Escape.shell_command(arguments) + " < #{dump_path}"
|
@@ -10,7 +10,8 @@ module Wordmove
|
|
10
10
|
def initialize(options)
|
11
11
|
super
|
12
12
|
ftp_options = options[:remote][:ftp]
|
13
|
-
@copier = Photocopier::FTP.new(ftp_options
|
13
|
+
@copier = Photocopier::FTP.new(ftp_options)
|
14
|
+
@copier.logger = logger
|
14
15
|
end
|
15
16
|
|
16
17
|
def push_db
|
@@ -3,30 +3,25 @@ local:
|
|
3
3
|
wordpress_path: "~/dev/sites/your_site"
|
4
4
|
database:
|
5
5
|
name: "database_name"
|
6
|
-
username: "
|
6
|
+
username: "user"
|
7
7
|
password: "password"
|
8
|
-
host: "
|
8
|
+
host: "127.0.0.1"
|
9
9
|
remote:
|
10
10
|
vhost: "http://remote.com"
|
11
11
|
wordpress_path: "/var/www/your_site"
|
12
|
-
exclude:
|
13
|
-
- .git
|
14
|
-
- .DS_Store
|
15
|
-
- .sass-cache
|
16
|
-
- Movefile
|
17
12
|
database:
|
18
13
|
name: "database_name"
|
19
|
-
username: "
|
14
|
+
username: "user"
|
20
15
|
password: "password"
|
21
16
|
host: "host"
|
22
|
-
ssh:
|
17
|
+
ssh: # also ftp is allowed
|
23
18
|
host: "host"
|
24
|
-
|
19
|
+
user: "user"
|
25
20
|
password: "password" # password is optional, will use public keys if available.
|
26
21
|
# port: 22 # Port is optional
|
27
22
|
#
|
28
23
|
# gateway:
|
29
24
|
# host: "host"
|
30
|
-
#
|
25
|
+
# user: "user"
|
31
26
|
# password: "password" # password is optional, will use public keys if available.
|
32
27
|
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Wordmove
|
2
|
+
|
3
|
+
class SqlMover
|
4
|
+
|
5
|
+
attr_accessor :sql_content
|
6
|
+
attr_reader :sql_path, :source_config, :dest_config
|
7
|
+
|
8
|
+
def initialize(sql_path, source_config, dest_config)
|
9
|
+
@sql_path = sql_path
|
10
|
+
@source_config = source_config
|
11
|
+
@dest_config = dest_config
|
12
|
+
end
|
13
|
+
|
14
|
+
def sql_content
|
15
|
+
@sql_content ||= File.open(sql_path).read
|
16
|
+
end
|
17
|
+
|
18
|
+
def move!
|
19
|
+
replace_vhost!
|
20
|
+
replace_wordpress_path!
|
21
|
+
write_sql!
|
22
|
+
end
|
23
|
+
|
24
|
+
def replace_vhost!
|
25
|
+
replace_field!(:vhost)
|
26
|
+
end
|
27
|
+
|
28
|
+
def replace_wordpress_path!
|
29
|
+
replace_field!(:wordpress_path)
|
30
|
+
end
|
31
|
+
|
32
|
+
def replace_field!(field_sym)
|
33
|
+
source_field = source_config[field_sym]
|
34
|
+
dest_field = dest_config[field_sym]
|
35
|
+
if source_field && dest_field
|
36
|
+
serialized_replace!(source_field, dest_field)
|
37
|
+
simple_replace!(source_field, dest_field)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def serialized_replace!(source_field, dest_field)
|
42
|
+
length_delta = source_field.length - dest_field.length
|
43
|
+
|
44
|
+
sql_content.gsub!(/s:(\d+):"#{Regexp.escape(source_field)}/) do |match|
|
45
|
+
source_length = $1.to_i
|
46
|
+
dest_length = source_length - length_delta
|
47
|
+
"s:#{dest_length}:\"#{dest_field}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def simple_replace!(source_field, dest_field)
|
52
|
+
sql_content.gsub!(source_field, dest_field)
|
53
|
+
end
|
54
|
+
|
55
|
+
def write_sql!
|
56
|
+
File.open(sql_path, 'w') {|f| f.write(sql_content) }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/wordmove/version.rb
CHANGED
data/spec/fixtures/Movefile
CHANGED
@@ -3,24 +3,19 @@ local:
|
|
3
3
|
wordpress_path: "~/dev/sites/your_site"
|
4
4
|
database:
|
5
5
|
name: "database_name"
|
6
|
-
|
6
|
+
user: "user"
|
7
7
|
password: "password"
|
8
8
|
host: "host"
|
9
9
|
remote:
|
10
10
|
vhost: "http://remote.com"
|
11
11
|
wordpress_path: "/var/www/your_site"
|
12
|
-
exclude:
|
13
|
-
- .git
|
14
|
-
- .DS_Store
|
15
|
-
- .sass-cache
|
16
|
-
- Movefile
|
17
12
|
database:
|
18
13
|
name: "database_name"
|
19
|
-
|
14
|
+
user: "user"
|
20
15
|
password: "password"
|
21
16
|
host: "host"
|
22
17
|
ssh:
|
23
|
-
|
18
|
+
user: "user"
|
24
19
|
password: "password"
|
25
20
|
host: "host"
|
26
21
|
port: 30000
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: wordmove
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.0.
|
4
|
+
version: 0.1.0.beta
|
5
5
|
prerelease: 6
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-11-17 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: colored
|
@@ -183,9 +183,8 @@ files:
|
|
183
183
|
- lib/wordmove/deployer/ssh.rb
|
184
184
|
- lib/wordmove/generators/Movefile
|
185
185
|
- lib/wordmove/generators/movefile.rb
|
186
|
-
- lib/wordmove/hosts/local_host.rb
|
187
|
-
- lib/wordmove/hosts/remote_host.rb
|
188
186
|
- lib/wordmove/logger.rb
|
187
|
+
- lib/wordmove/sql_mover.rb
|
189
188
|
- lib/wordmove/version.rb
|
190
189
|
- pkg/wordmove-0.0.1.gem
|
191
190
|
- pkg/wordmove-0.0.2.gem
|
@@ -1,41 +0,0 @@
|
|
1
|
-
require 'escape'
|
2
|
-
|
3
|
-
module Wordmove
|
4
|
-
class LocalHost
|
5
|
-
|
6
|
-
attr_reader :options
|
7
|
-
attr_reader :logger
|
8
|
-
attr_reader :ssh_extras
|
9
|
-
|
10
|
-
def initialize(options = {})
|
11
|
-
@options = Hashie::Mash.new(options)
|
12
|
-
@logger = @options[:logger]
|
13
|
-
end
|
14
|
-
|
15
|
-
def run(*args)
|
16
|
-
command = shell_command(*args)
|
17
|
-
logger.verbose "Executing locally #{command}"
|
18
|
-
unless system(command)
|
19
|
-
raise Thor::Error, "Error executing \"#{command}\""
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
def close
|
24
|
-
end
|
25
|
-
|
26
|
-
protected
|
27
|
-
|
28
|
-
def shell_command(*args)
|
29
|
-
options = args.extract_options!
|
30
|
-
command = Escape.shell_command(args)
|
31
|
-
if options[:stdin]
|
32
|
-
command += " < #{options[:stdin]}"
|
33
|
-
end
|
34
|
-
if options[:stdout]
|
35
|
-
command += " > #{options[:stdout]}"
|
36
|
-
end
|
37
|
-
command
|
38
|
-
end
|
39
|
-
|
40
|
-
end
|
41
|
-
end
|
@@ -1,135 +0,0 @@
|
|
1
|
-
require 'net/ssh'
|
2
|
-
require 'net/scp'
|
3
|
-
require 'net/ssh/gateway'
|
4
|
-
|
5
|
-
module Wordmove
|
6
|
-
class RemoteHost < LocalHost
|
7
|
-
|
8
|
-
alias :locally_run :run
|
9
|
-
|
10
|
-
attr_reader :session
|
11
|
-
|
12
|
-
def initialize(options = {})
|
13
|
-
super
|
14
|
-
end
|
15
|
-
|
16
|
-
def session
|
17
|
-
if options.ssh.nil?
|
18
|
-
raise Thor::Error, "No SSH credentials provided on Movefile!"
|
19
|
-
end
|
20
|
-
|
21
|
-
ssh_extras = {}
|
22
|
-
[ :port, :password ].each do |p|
|
23
|
-
ssh_extras.merge!( { p => options.ssh[p] } ) if options.ssh[p]
|
24
|
-
end
|
25
|
-
|
26
|
-
if options.ssh.gateway.nil?
|
27
|
-
logger.verbose "Connecting to #{options.ssh.host}..." unless @session.present?
|
28
|
-
@session ||= Net::SSH.start(options.ssh.host, options.ssh.username, ssh_extras)
|
29
|
-
else
|
30
|
-
logger.verbose "Connecting to #{options.ssh.host} through the gateway..." unless @session.present?
|
31
|
-
@session ||= gateway.ssh(options.ssh.host, options.ssh.username, ssh_extras)
|
32
|
-
end
|
33
|
-
|
34
|
-
@session
|
35
|
-
end
|
36
|
-
|
37
|
-
def gateway
|
38
|
-
if options.ssh.gateway.nil?
|
39
|
-
raise Thor::Error, "No SSH credentials provided on Movefile!"
|
40
|
-
end
|
41
|
-
|
42
|
-
ssh_extras = {}
|
43
|
-
[ :port, :password ].each do |p|
|
44
|
-
ssh_extras.merge!( { p => options.ssh.gateway[p] } ) if options.ssh.gateway[p]
|
45
|
-
end
|
46
|
-
|
47
|
-
logger.verbose "Connecting to #{options.ssh.gateway.host}..." unless @gateway.present?
|
48
|
-
@gateway ||= Net::SSH::Gateway.new(options.ssh.gateway.host, options.ssh.gateway.username, ssh_extras )
|
49
|
-
|
50
|
-
@gateway
|
51
|
-
end
|
52
|
-
|
53
|
-
def close
|
54
|
-
session.close
|
55
|
-
if options.ssh.gateway.present?
|
56
|
-
gateway.close(session.transport.port)
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
def upload_file(source_file, destination_file)
|
61
|
-
logger.verbose "Copying remote #{source_file} to #{destination_file}..."
|
62
|
-
session.scp.download! source_file, destination_file
|
63
|
-
end
|
64
|
-
|
65
|
-
def download_file(source_file, destination_file)
|
66
|
-
logger.verbose "Copying local #{source_file} to #{destination_file}..."
|
67
|
-
session.scp.upload! source_file, destination_file
|
68
|
-
end
|
69
|
-
|
70
|
-
def download_dir(source_dir, destination_dir)
|
71
|
-
destination_dir = ":#{destination_dir}"
|
72
|
-
destination_dir = "#{options.ssh.username}@#{destination_dir}" if options.ssh.username
|
73
|
-
rsync "#{source_dir}/", destination_dir
|
74
|
-
end
|
75
|
-
|
76
|
-
def upload_dir(source_dir, destination_dir)
|
77
|
-
source_dir = ":#{source_dir}/"
|
78
|
-
rsync source_dir, destination_dir
|
79
|
-
end
|
80
|
-
|
81
|
-
def run(*args)
|
82
|
-
command = shell_command(*args)
|
83
|
-
logger.verbose "Executing remotely #{command}"
|
84
|
-
session.exec!(command)
|
85
|
-
end
|
86
|
-
|
87
|
-
private
|
88
|
-
|
89
|
-
def rsync(source_dir, destination_dir)
|
90
|
-
|
91
|
-
exclude_file = Tempfile.new('exclude')
|
92
|
-
exclude_file.write(options.exclude.join("\n"))
|
93
|
-
exclude_file.close
|
94
|
-
|
95
|
-
arguments = [ "-azLKO" ]
|
96
|
-
|
97
|
-
if options.ssh && (options.ssh.port || options.ssh.password || options.ssh.gateway)
|
98
|
-
|
99
|
-
remote_shell_arguments = []
|
100
|
-
|
101
|
-
if options.ssh.gateway
|
102
|
-
host = options.ssh.gateway.host
|
103
|
-
host = "#{options.ssh.gateway.username}@#{host}" if options.ssh.gateway.username
|
104
|
-
remote_shell_arguments << [ "ssh", host ]
|
105
|
-
if options.ssh.gateway.port
|
106
|
-
remote_shell_arguments << [ "-p", options.ssh.gateway.port ]
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
remote_shell_arguments << [ "ssh" ]
|
111
|
-
|
112
|
-
if options.ssh.port
|
113
|
-
remote_shell_arguments << [ "-p", options.ssh.port ]
|
114
|
-
end
|
115
|
-
|
116
|
-
if options.ssh.password
|
117
|
-
remote_shell_arguments = [ "sshpass", "-p", options.ssh.password ] + remote_shell_arguments
|
118
|
-
end
|
119
|
-
|
120
|
-
host = options.ssh.host
|
121
|
-
host = "#{options.ssh.username}@#{host}" if options.ssh.username
|
122
|
-
remote_shell_arguments << host
|
123
|
-
|
124
|
-
arguments << [ "-e", remote_shell_arguments.join(" ") ]
|
125
|
-
end
|
126
|
-
|
127
|
-
arguments << [ "--exclude-from=#{exclude_file.path}", "--delete", source_dir, destination_dir ]
|
128
|
-
arguments.flatten!
|
129
|
-
locally_run "rsync", *arguments
|
130
|
-
|
131
|
-
exclude_file.unlink
|
132
|
-
end
|
133
|
-
|
134
|
-
end
|
135
|
-
end
|