wordmove 0.0.8 → 0.1.0.alpha
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/bin/wordmove +2 -0
- data/lib/wordmove/assets/dump.php.erb +182 -0
- data/lib/wordmove/assets/import.php.erb +1076 -0
- data/lib/wordmove/cli.rb +27 -18
- data/lib/wordmove/deployer/base.rb +135 -0
- data/lib/wordmove/deployer/ftp.rb +117 -0
- data/lib/wordmove/deployer/ssh.rb +73 -0
- data/lib/wordmove/logger.rb +14 -22
- data/lib/wordmove/version.rb +1 -1
- data/wordmove.gemspec +4 -7
- metadata +13 -57
- data/lib/wordmove/deployer.rb +0 -149
data/lib/wordmove/cli.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
require 'thor'
|
2
2
|
require 'wordmove/generators/movefile'
|
3
|
-
require 'wordmove/deployer'
|
4
|
-
|
3
|
+
require 'wordmove/deployer/base'
|
5
4
|
|
6
5
|
module Wordmove
|
7
6
|
class CLI < Thor
|
@@ -12,27 +11,37 @@ module Wordmove
|
|
12
11
|
end
|
13
12
|
|
14
13
|
desc "pull", "Pulls WP data from remote host to the local machine"
|
15
|
-
method_option :
|
16
|
-
method_option :
|
17
|
-
method_option :
|
18
|
-
method_option :
|
19
|
-
method_option :verbose,
|
20
|
-
method_option :
|
14
|
+
method_option :db, :aliases => "-d", :type => :boolean
|
15
|
+
method_option :uploads, :aliases => "-u", :type => :boolean
|
16
|
+
method_option :themes, :aliases => "-t", :type => :boolean
|
17
|
+
method_option :plugins, :aliases => "-p", :type => :boolean
|
18
|
+
method_option :verbose, :aliases => "-v", :type => :boolean
|
19
|
+
method_option :simulate, :aliases => "-s", :type => :boolean
|
20
|
+
method_option :config, :aliases => "-c"
|
21
21
|
def pull
|
22
|
-
deployer = Wordmove::Deployer.
|
23
|
-
|
22
|
+
deployer = Wordmove::Deployer::Base.deployer_for(options)
|
23
|
+
%w(db uploads themes plugins).map(&:to_sym).each do |task|
|
24
|
+
if options[task]
|
25
|
+
deployer.send("pull_#{task}")
|
26
|
+
end
|
27
|
+
end
|
24
28
|
end
|
25
29
|
|
26
30
|
desc "push", "Pushes WP data from local machine to remote host"
|
27
|
-
method_option :
|
28
|
-
method_option :
|
29
|
-
method_option :
|
30
|
-
method_option :
|
31
|
-
method_option :verbose,
|
32
|
-
method_option :
|
31
|
+
method_option :db, :aliases => "-d", :type => :boolean
|
32
|
+
method_option :uploads, :aliases => "-u", :type => :boolean
|
33
|
+
method_option :themes, :aliases => "-t", :type => :boolean
|
34
|
+
method_option :plugins, :aliases => "-p", :type => :boolean
|
35
|
+
method_option :verbose, :aliases => "-v", :type => :boolean
|
36
|
+
method_option :simulate, :aliases => "-s", :type => :boolean
|
37
|
+
method_option :config, :aliases => "-c"
|
33
38
|
def push
|
34
|
-
deployer = Wordmove::Deployer.
|
35
|
-
|
39
|
+
deployer = Wordmove::Deployer::Base.deployer_for(options)
|
40
|
+
%w(db uploads themes plugins).map(&:to_sym).each do |task|
|
41
|
+
if options[task]
|
42
|
+
deployer.send("push_#{task}")
|
43
|
+
end
|
44
|
+
end
|
36
45
|
end
|
37
46
|
|
38
47
|
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'active_support/core_ext'
|
2
|
+
require 'wordmove/logger'
|
3
|
+
require 'escape'
|
4
|
+
|
5
|
+
module Wordmove
|
6
|
+
module Deployer
|
7
|
+
|
8
|
+
class Base
|
9
|
+
attr_reader :options
|
10
|
+
attr_reader :logger
|
11
|
+
|
12
|
+
def self.deployer_for(options)
|
13
|
+
options = fetch_movefile(options[:config]).merge(options)
|
14
|
+
recursive_symbolize_keys!(options)
|
15
|
+
if options[:remote][:ftp]
|
16
|
+
require 'wordmove/deployer/ftp'
|
17
|
+
FTP.new(options)
|
18
|
+
elsif options[:remote][:ssh]
|
19
|
+
require 'wordmove/deployer/ssh'
|
20
|
+
SSH.new(options)
|
21
|
+
else
|
22
|
+
raise Thor::Error, "No valid adapter found."
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.fetch_movefile(path)
|
27
|
+
path ||= "Movefile"
|
28
|
+
unless File.exists?(path)
|
29
|
+
raise Thor::Error, "Could not find a valid Movefile"
|
30
|
+
end
|
31
|
+
YAML::load(File.open(path))
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize(options = {})
|
35
|
+
@options = options
|
36
|
+
@logger = Logger.new(STDOUT)
|
37
|
+
@logger.level = Logger::DEBUG
|
38
|
+
end
|
39
|
+
|
40
|
+
def push_db;
|
41
|
+
logger.task "Pushing Database"
|
42
|
+
end
|
43
|
+
|
44
|
+
def pull_db
|
45
|
+
logger.task "Pulling Database"
|
46
|
+
end
|
47
|
+
|
48
|
+
def remote_get_directory(directory); end
|
49
|
+
def remote_put_directory(directory); end
|
50
|
+
|
51
|
+
%w(uploads themes plugins).each do |task|
|
52
|
+
define_method "push_#{task}" do
|
53
|
+
logger.task "Pushing #{task.titleize}"
|
54
|
+
remote_put_directory(local_wpcontent_path(task), remote_wpcontent_path(task))
|
55
|
+
end
|
56
|
+
|
57
|
+
define_method "pull_#{task}" do
|
58
|
+
logger.task "Pulling #{task.titleize}"
|
59
|
+
remote_get_directory(remote_wpcontent_path(task), local_wpcontent_path(task))
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
protected
|
64
|
+
|
65
|
+
def run(command)
|
66
|
+
logger.task_step true, command
|
67
|
+
unless simulate?
|
68
|
+
system(command)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def download(url, local_path)
|
73
|
+
logger.task_step true, "download #{url}"
|
74
|
+
unless simulate?
|
75
|
+
open(local_path, 'w') do |file|
|
76
|
+
file << open(url).read
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def simulate?
|
82
|
+
options[:simulate]
|
83
|
+
end
|
84
|
+
|
85
|
+
def local_wpcontent_path(*args)
|
86
|
+
File.join(options[:local][:wordpress_path], "wp-content", *args)
|
87
|
+
end
|
88
|
+
|
89
|
+
def remote_wpcontent_path(*args)
|
90
|
+
File.join(options[:remote][:wordpress_path], "wp-content", *args)
|
91
|
+
end
|
92
|
+
|
93
|
+
def remote_wpcontent_url(*args)
|
94
|
+
options[:remote][:vhost] + File.join("/wp-content", *args)
|
95
|
+
end
|
96
|
+
|
97
|
+
def adapt_sql(save_to_path, local, remote)
|
98
|
+
logger.task_step true, "adapt dump"
|
99
|
+
unless simulate?
|
100
|
+
File.open(save_to_path, 'a') do |file|
|
101
|
+
file.write "UPDATE wp_options SET option_value=\"#{remote[:vhost]}\" WHERE option_name=\"siteurl\" OR option_name=\"home\";\n"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def mysql_dump_command(options, save_to_path)
|
107
|
+
arguments = [ "mysqldump" ]
|
108
|
+
arguments << "--host=#{options[:host]}" if options[:host].present?
|
109
|
+
arguments << "--user=#{options[:user]}" if options[:user].present?
|
110
|
+
arguments << "--password=#{options[:password]}" if options[:password].present?
|
111
|
+
arguments << "--default-character-set=#{options[:charset]}" if options[:charset].present?
|
112
|
+
arguments << options[:name]
|
113
|
+
Escape.shell_command(arguments) + " > #{save_to_path}"
|
114
|
+
end
|
115
|
+
|
116
|
+
def mysql_import_command(dump_path, options)
|
117
|
+
arguments = [ "mysql" ]
|
118
|
+
arguments << "--host=#{options[:host]}" if options[:host].present?
|
119
|
+
arguments << "--user=#{options[:user]}" if options[:user].present?
|
120
|
+
arguments << "--password=#{options[:password]}" if options[:password].present?
|
121
|
+
arguments << "--database=#{options[:name]}"
|
122
|
+
Escape.shell_command(arguments) + " < #{dump_path}"
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
def self.recursive_symbolize_keys! hash
|
128
|
+
hash.symbolize_keys!
|
129
|
+
hash.values.select{|v| v.is_a? Hash}.each{|h| recursive_symbolize_keys!(h)}
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'wordmove/deployer/base'
|
2
|
+
require 'photocopier/ftp'
|
3
|
+
require 'erb'
|
4
|
+
require 'open-uri'
|
5
|
+
|
6
|
+
module Wordmove
|
7
|
+
module Deployer
|
8
|
+
class FTP < Base
|
9
|
+
|
10
|
+
def initialize(options)
|
11
|
+
super
|
12
|
+
ftp_options = options[:remote][:ftp]
|
13
|
+
@copier = Photocopier::FTP.new(ftp_options.merge(logger: logger))
|
14
|
+
end
|
15
|
+
|
16
|
+
def push_db
|
17
|
+
super
|
18
|
+
|
19
|
+
remote_import_script_path = remote_wpcontent_path("import.php")
|
20
|
+
local_dump_path = local_wpcontent_path("dump.sql")
|
21
|
+
remote_dump_path = remote_wpcontent_path("dump.sql")
|
22
|
+
|
23
|
+
# dump local mysql into file
|
24
|
+
run mysql_dump_command(options[:local][:database], local_dump_path)
|
25
|
+
# gsub sql
|
26
|
+
adapt_sql(local_dump_path, options[:local], options[:remote])
|
27
|
+
# upload it
|
28
|
+
remote_put(local_dump_path, remote_dump_path)
|
29
|
+
|
30
|
+
# generate a secure one-time password
|
31
|
+
one_time_password = SecureRandom.hex(40)
|
32
|
+
# generate import script
|
33
|
+
import_script = generate_import_script(options[:remote][:database], one_time_password)
|
34
|
+
# upload import script
|
35
|
+
remote_put(import_script, remote_import_script_path)
|
36
|
+
# run import script
|
37
|
+
import_url = "#{remote_wpcontent_url("import.php")}?shared_key=#{one_time_password}&start=1&foffset=0&totalqueries=0&fn=dump.sql"
|
38
|
+
download(import_url, local_dump_path + "_")
|
39
|
+
|
40
|
+
# remove script remotely
|
41
|
+
remote_delete(remote_import_script_path)
|
42
|
+
# remove dump remotely
|
43
|
+
remote_delete(remote_dump_path)
|
44
|
+
# and locally
|
45
|
+
run "rm #{local_dump_path}"
|
46
|
+
end
|
47
|
+
|
48
|
+
def pull_db
|
49
|
+
super
|
50
|
+
|
51
|
+
remote_dump_script = remote_wpcontent_path("dump.php")
|
52
|
+
local_dump_path = local_wpcontent_path("dump.sql")
|
53
|
+
|
54
|
+
# generate a secure one-time password
|
55
|
+
one_time_password = SecureRandom.hex(40)
|
56
|
+
|
57
|
+
# generate dump script
|
58
|
+
dump_script = generate_dump_script(options[:remote][:database], one_time_password)
|
59
|
+
# upload the dump script
|
60
|
+
remote_put(dump_script, remote_dump_script)
|
61
|
+
# download the resulting dump (using the password)
|
62
|
+
dump_url = "#{remote_wpcontent_url("dump.php")}?shared_key=#{one_time_password}"
|
63
|
+
download(dump_url, local_dump_path)
|
64
|
+
|
65
|
+
# gsub sql
|
66
|
+
adapt_sql(local_dump_path, options[:remote], options[:local])
|
67
|
+
# import locally
|
68
|
+
run mysql_import_command(local_dump_path, options[:local][:database])
|
69
|
+
|
70
|
+
# remove it remotely
|
71
|
+
remote_delete(remote_dump_script)
|
72
|
+
# and locally
|
73
|
+
run "rm #{local_dump_path}"
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
%w(get get_directory put_directory delete).each do |command|
|
79
|
+
define_method "remote_#{command}" do |*args|
|
80
|
+
logger.task_step false, "#{command}: #{args.join(" ")}"
|
81
|
+
unless simulate?
|
82
|
+
@copier.send(command, *args)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def remote_put(thing, path)
|
88
|
+
if File.exists?(thing)
|
89
|
+
logger.task_step false, "copying #{thing} to #{path}"
|
90
|
+
else
|
91
|
+
logger.task_step false, "write #{path}"
|
92
|
+
end
|
93
|
+
unless simulate?
|
94
|
+
@copier.put(thing, path)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def escape_php(string)
|
99
|
+
# replaces \ with \\
|
100
|
+
# replaces ' with \'
|
101
|
+
string.gsub('\\','\\\\\\').gsub(/[']/, '\\\\\'')
|
102
|
+
end
|
103
|
+
|
104
|
+
def generate_dump_script(db, password)
|
105
|
+
template = ERB.new File.read(File.join(File.dirname(__FILE__), "../assets/dump.php.erb"))
|
106
|
+
template.result(binding)
|
107
|
+
end
|
108
|
+
|
109
|
+
def generate_import_script(db, password)
|
110
|
+
template = ERB.new File.read(File.join(File.dirname(__FILE__), "../assets/import.php.erb"))
|
111
|
+
template.result(binding)
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'wordmove/deployer/base'
|
2
|
+
require 'photocopier/ssh'
|
3
|
+
|
4
|
+
module Wordmove
|
5
|
+
module Deployer
|
6
|
+
class SSH < Base
|
7
|
+
def initialize(options)
|
8
|
+
super
|
9
|
+
ssh_options = options[:remote][:ssh]
|
10
|
+
@copier = Photocopier::SSH.new(ssh_options.merge(logger: logger))
|
11
|
+
end
|
12
|
+
|
13
|
+
def push_db
|
14
|
+
super
|
15
|
+
|
16
|
+
local_dump_path = local_wpcontent_path("dump.sql")
|
17
|
+
remote_dump_path = remote_wpcontent_path("dump.sql")
|
18
|
+
|
19
|
+
# dump local mysql into file
|
20
|
+
run mysql_dump_command(options[:local][:database], local_dump_path)
|
21
|
+
# gsub sql
|
22
|
+
adapt_sql(local_dump_path, options[:local], options[:remote])
|
23
|
+
# upload it
|
24
|
+
remote_put(local_dump_path, remote_dump_path)
|
25
|
+
# import it remotely
|
26
|
+
remote_run mysql_import_command(remote_dump_path, options[:remote][:database])
|
27
|
+
# remove it remotely
|
28
|
+
remote_delete(remote_dump_path)
|
29
|
+
# and locally
|
30
|
+
run "rm #{local_dump_path}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def pull_db
|
34
|
+
super
|
35
|
+
|
36
|
+
local_dump_path = local_wpcontent_path("dump.sql")
|
37
|
+
remote_dump_path = remote_wpcontent_path("dump.sql")
|
38
|
+
|
39
|
+
# dump remote db into file
|
40
|
+
remote_run mysql_dump_command(options[:remote][:database], remote_dump_path)
|
41
|
+
# download remote dump
|
42
|
+
remote_get(remote_dump_path, local_dump_path)
|
43
|
+
# gsub sql
|
44
|
+
adapt_sql(local_dump_path, options[:remote], options[:local])
|
45
|
+
# import locally
|
46
|
+
run mysql_import_command(local_dump_path, options[:local][:database])
|
47
|
+
# remove it remotely
|
48
|
+
remote_delete(remote_dump_path)
|
49
|
+
# and locally
|
50
|
+
run "rm #{local_dump_path}"
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
%w(get put get_directory put_directory delete).each do |command|
|
56
|
+
define_method "remote_#{command}" do |*args|
|
57
|
+
logger.task_step false, "#{command}: #{args.join(" ")}"
|
58
|
+
unless simulate?
|
59
|
+
@copier.send(command, *args)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def remote_run(command)
|
65
|
+
logger.task_step false, command
|
66
|
+
unless simulate?
|
67
|
+
@copier.session.exec! command
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/lib/wordmove/logger.rb
CHANGED
@@ -1,31 +1,23 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
1
3
|
require 'colored'
|
4
|
+
require 'logger'
|
2
5
|
|
3
6
|
module Wordmove
|
4
|
-
class Logger
|
5
|
-
|
6
|
-
ERROR = 0
|
7
|
-
INFO = 1
|
8
|
-
VERBOSE = 2
|
9
|
-
|
10
|
-
attr_accessor :level
|
7
|
+
class Logger < ::Logger
|
11
8
|
|
12
|
-
def
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
end
|
9
|
+
def task(title)
|
10
|
+
puts ""
|
11
|
+
title = " ✓ #{title} "
|
12
|
+
puts "▬" * 2 + title.green + "▬" * (70 - title.length)
|
17
13
|
end
|
18
14
|
|
19
|
-
def
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
end
|
26
|
-
|
27
|
-
def error(message)
|
28
|
-
log ERROR, message
|
15
|
+
def task_step(local_step, title)
|
16
|
+
if local_step
|
17
|
+
puts " local".cyan + " | ".black + title
|
18
|
+
else
|
19
|
+
puts " remote".yellow + " | ".black + title
|
20
|
+
end
|
29
21
|
end
|
30
22
|
|
31
23
|
end
|
data/lib/wordmove/version.rb
CHANGED
data/wordmove.gemspec
CHANGED
@@ -15,15 +15,12 @@ Gem::Specification.new do |gem|
|
|
15
15
|
gem.require_paths = ["lib"]
|
16
16
|
gem.version = Wordmove::VERSION
|
17
17
|
|
18
|
-
gem.add_dependency
|
19
|
-
gem.add_dependency
|
20
|
-
gem.add_dependency 'rake'
|
21
|
-
gem.add_dependency 'net-ssh'
|
22
|
-
gem.add_dependency 'net-scp'
|
18
|
+
gem.add_dependency "colored"
|
19
|
+
gem.add_dependency "rake"
|
23
20
|
gem.add_dependency "thor"
|
24
|
-
gem.add_dependency "activesupport"
|
21
|
+
gem.add_dependency "activesupport"
|
25
22
|
gem.add_dependency "i18n"
|
26
|
-
gem.add_dependency "
|
23
|
+
gem.add_dependency "photocopier"
|
27
24
|
|
28
25
|
gem.add_development_dependency "rspec"
|
29
26
|
gem.add_development_dependency "cucumber"
|