wordmove 0.0.8 → 0.1.0.alpha

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 :skip_db, :aliases => "-d", :type => :boolean
16
- method_option :skip_uploads, :aliases => "-u", :type => :boolean
17
- method_option :skip_themes, :aliases => "-t", :type => :boolean
18
- method_option :skip_plugins, :aliases => "-p", :type => :boolean
19
- method_option :verbose, :aliases => "-v", :type => :boolean
20
- method_option :config, :aliases => "-c"
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.new(options)
23
- deployer.pull
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 :skip_db, :aliases => "-d", :type => :boolean
28
- method_option :skip_uploads, :aliases => "-u", :type => :boolean
29
- method_option :skip_themes, :aliases => "-t", :type => :boolean
30
- method_option :skip_plugins, :aliases => "-p", :type => :boolean
31
- method_option :verbose, :aliases => "-v", :type => :boolean
32
- method_option :config, :aliases => "-c"
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.new(options)
35
- deployer.push
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
@@ -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 log(l, message)
13
- colors = [ :red, :green, :cyan ]
14
- if l <= level
15
- puts " " * [l-1, 0].max + message.send(colors[l])
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 info(message)
20
- log INFO, message
21
- end
22
-
23
- def verbose(message)
24
- log VERBOSE, message
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
@@ -1,3 +1,3 @@
1
1
  module Wordmove
2
- VERSION = "0.0.8"
2
+ VERSION = "0.1.0.alpha"
3
3
  end
@@ -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 'colored'
19
- gem.add_dependency 'escape'
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", "~> 3.0.0"
21
+ gem.add_dependency "activesupport"
25
22
  gem.add_dependency "i18n"
26
- gem.add_dependency "hashie"
23
+ gem.add_dependency "photocopier"
27
24
 
28
25
  gem.add_development_dependency "rspec"
29
26
  gem.add_development_dependency "cucumber"