wordpress-deploy 1.0.0.alpha1 → 1.0.0.alpha2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/.gitignore +26 -1
  2. data/.rspec +1 -0
  3. data/.rvmrc +48 -0
  4. data/.travis.yml +4 -0
  5. data/Gemfile +2 -5
  6. data/Gemfile.lock +40 -28
  7. data/Guardfile +24 -0
  8. data/README.md +22 -7
  9. data/bin/wp-deploy +1 -4
  10. data/lib/wordpress_deploy.rb +22 -48
  11. data/lib/wordpress_deploy/cli/helpers.rb +27 -14
  12. data/lib/wordpress_deploy/cli/utility.rb +86 -37
  13. data/lib/wordpress_deploy/database/mysql.rb +22 -136
  14. data/lib/wordpress_deploy/environment.rb +68 -0
  15. data/lib/wordpress_deploy/errors.rb +54 -0
  16. data/lib/wordpress_deploy/logger.rb +28 -50
  17. data/lib/wordpress_deploy/transfer_protocols/ftp.rb +305 -0
  18. data/lib/wordpress_deploy/version.rb +1 -1
  19. data/lib/wordpress_deploy/wordpress/configuration.rb +196 -0
  20. data/spec/data/ftp.yml +4 -0
  21. data/spec/data/wp-config-sample.php +90 -0
  22. data/spec/data/wp-config.yml +128 -0
  23. data/spec/database/mysql_spec.rb +93 -0
  24. data/spec/environment_spec.rb +35 -0
  25. data/spec/spec_helper.rb +36 -1
  26. data/spec/transfer_protocols/ftp_spec.rb +193 -0
  27. data/spec/wordpress/configuration_spec.rb +202 -0
  28. data/wordpress_deploy.gemspec +13 -10
  29. metadata +63 -47
  30. data/lib/wordpress_deploy/config.rb +0 -68
  31. data/lib/wordpress_deploy/database/base.rb +0 -53
  32. data/lib/wordpress_deploy/pipeline.rb +0 -110
  33. data/lib/wordpress_deploy/storage/base.rb +0 -99
  34. data/lib/wordpress_deploy/storage/ftp.rb +0 -133
  35. data/lib/wordpress_deploy/storage/local.rb +0 -82
  36. data/lib/wordpress_deploy/storage/scp.rb +0 -99
  37. data/lib/wordpress_deploy/storage/sftp.rb +0 -108
  38. data/spec/config_spec.rb +0 -16
@@ -1,40 +1,46 @@
1
1
  # encoding: utf-8
2
+ require 'colorize'
2
3
 
3
4
  module WordpressDeploy
4
5
  module Logger
5
6
  class << self
6
7
 
7
- attr_accessor :quiet
8
+ attr_accessor :verbose
8
9
 
9
- ##
10
- # Outputs a messages to the console and writes it to the backup.log
11
- def message(string)
12
- to_console loggify(string, :message, :green)
13
- to_file loggify(string, :message)
10
+ # Outputs the data as if it were a regular 'puts' command,
11
+ # but also logs it to the backup.log
12
+ def normal(string, color = nil)
13
+ string = string.colorize(color) unless color.nil?
14
+ to_console loggify(string)
15
+ to_file loggify(string)
14
16
  end
15
17
 
16
18
  ##
17
- # Outputs an error to the console and writes it to the backup.log
18
- # Called when an Exception has caused the backup process to abort.
19
- def error(string)
20
- to_console loggify(string, :error, :red), true
21
- to_file loggify(string, :error)
19
+ # Outputs a debug message to the console and writes it to the
20
+ # backup.log
21
+ def debug(string, color = nil)
22
+ string = string.colorize(color) unless color.nil?
23
+ to_console loggify(string, :debug, :blue) if verbose
24
+ to_file loggify(string, :debug)
22
25
  end
23
26
 
24
27
  ##
25
28
  # Outputs a notice to the console and writes it to the backup.log
26
29
  # Sets #has_warnings? true so :on_warning notifications will be sent
27
- def warn(string)
30
+ def warn(string, color = nil)
28
31
  @has_warnings = true
32
+ string = string.colorize(color) unless color.nil?
29
33
  to_console loggify(string, :warning, :yellow), true
30
34
  to_file loggify(string, :warning)
31
35
  end
32
36
 
33
- # Outputs the data as if it were a regular 'puts' command,
34
- # but also logs it to the backup.log
35
- def normal(string)
36
- to_console loggify(string)
37
- to_file loggify(string)
37
+ ##
38
+ # Outputs an error to the console and writes it to the backup.log
39
+ # Called when an Exception has caused the backup process to abort.
40
+ def error(string, color = nil)
41
+ string = string.colorize(color) unless color.nil?
42
+ to_console loggify(string, :error, :red), true
43
+ to_file loggify(string, :error)
38
44
  end
39
45
 
40
46
  ##
@@ -61,7 +67,7 @@ module WordpressDeploy
61
67
  end
62
68
 
63
69
  def truncate!(max_bytes = 500_000)
64
- log_file = File.join(Config.log_path, 'backup.log')
70
+ log_file = Environment.log_file
65
71
  return unless File.exist?(log_file)
66
72
 
67
73
  if File.stat(log_file).size > max_bytes
@@ -95,7 +101,7 @@ module WordpressDeploy
95
101
  def loggify(string, type = false, color = false)
96
102
  lines = string.to_s.split("\n")
97
103
  if type
98
- type = send(color, type) if color
104
+ type = type.to_s.colorize(color) if color
99
105
  time_now = time
100
106
  lines.map {|line| "[#{time_now}][#{type}] #{line}" }
101
107
  else
@@ -106,47 +112,19 @@ module WordpressDeploy
106
112
  ##
107
113
  # Receives an Array of Strings to be written to the console.
108
114
  def to_console(lines, stderr = false)
109
- return if quiet
110
115
  lines.each {|line| stderr ? Kernel.warn(line) : puts(line) }
111
116
  end
112
117
 
113
118
  ##
114
119
  # Receives an Array of Strings to be written to the log file.
115
120
  def to_file(lines)
116
- File.open(File.join(Config.log_path, 'backup.log'), 'a') do |file|
121
+ dir_name = File.dirname(Environment.log_file)
122
+ File.open(Environment.log_file, 'a') do |file|
117
123
  lines.each {|line| file.puts line }
118
- end
124
+ end if File.writable? dir_name
119
125
  messages.push(*lines)
120
126
  end
121
127
 
122
- ##
123
- # Invokes the #colorize method with the provided string
124
- # and the color code "32" (for green)
125
- def green(string)
126
- colorize(string, 32)
127
- end
128
-
129
- ##
130
- # Invokes the #colorize method with the provided string
131
- # and the color code "33" (for yellow)
132
- def yellow(string)
133
- colorize(string, 33)
134
- end
135
-
136
- ##
137
- # Invokes the #colorize method the with provided string
138
- # and the color code "31" (for red)
139
- def red(string)
140
- colorize(string, 31)
141
- end
142
-
143
- ##
144
- # Wraps the provided string in colorizing tags to provide
145
- # easier to view output to the client
146
- def colorize(string, code)
147
- "\e[#{code}m#{string}\e[0m"
148
- end
149
-
150
128
  end
151
129
  end
152
130
  end
@@ -0,0 +1,305 @@
1
+ ##
2
+ # Only load the Net::FTP library/gem when the
3
+ # WordpressDeploy::TransferProtocols::Ftp class is loaded
4
+ require "net/ftp"
5
+ require "pathname"
6
+ require "action_view"
7
+
8
+ module WordpressDeploy
9
+ module TransferProtocols
10
+ class Ftp
11
+ include ActionView::Helpers::NumberHelper
12
+
13
+ attr_reader :configuration
14
+ attr_accessor :available_names
15
+ alias :names :available_names
16
+
17
+ ##
18
+ # Create a new instance of the Ftp object
19
+ def initialize(config_name = nil)
20
+ # Set the configuration
21
+ @yaml = YAML.load_file(File.join(Environment.config_dir, "wp-config.yml"))
22
+ @available_names = @yaml.map { |key, val| key }
23
+
24
+ self.name = config_name unless config_name.nil?
25
+
26
+ # Actually open the connection
27
+ connect
28
+ end
29
+
30
+ def configuration=(new_config)
31
+ @configuration = new_config if new_config.instance_of? WordpressDeploy::Wordpress::Configuration
32
+ end
33
+
34
+ ##
35
+ # If the connection is open, close it
36
+ # Returns true if the connection is closed; false otherwise
37
+ def close
38
+ unless ftp.closed?
39
+ ftp.close
40
+ return true
41
+ end
42
+ return false
43
+ end
44
+
45
+ def transmit
46
+ raise NotImplementedError
47
+ end
48
+
49
+ ##
50
+ #
51
+ def transmit!
52
+ files = Dir.glob(File.join(Environment.wp_dir, "**/*")).sort
53
+ files.each do |file|
54
+ put_file file
55
+ end
56
+ end
57
+
58
+ def receive
59
+ raise NotImplementedError
60
+ end
61
+
62
+ def receive!(path=remote_path)
63
+ # Where did it start from
64
+ pwd = ftp.pwd
65
+
66
+ # Change to the remote path
67
+ chdir(path)
68
+
69
+ # Get a list of all the files
70
+ # and directories in the current pwd
71
+ files = ftp.nlst ftp.pwd
72
+
73
+ # Remove the 'dot' directories
74
+ files.delete(".")
75
+ files.delete("..")
76
+
77
+ # Create a relative pathname
78
+ rel_remote_path = Pathname.new(ftp.pwd).relative_path_from Pathname.new(remote_path)
79
+
80
+ # Loop through each file and directory
81
+ # found in the current pwd
82
+ files.each do |file|
83
+
84
+ # Build the file name to save it to
85
+ local_file = Pathname.new(File.join(Environment.wp_dir, rel_remote_path, File.basename(file))).cleanpath.to_s
86
+ if directory? file
87
+ Logger.debug "[mkdir] #{local_file}"
88
+ FileUtils.mkdir_p local_file
89
+ receive! file
90
+ else
91
+ str = "[cp] #{file} (#{self.number_to_human_size(ftp.size(file), precision: 2)})"
92
+ ftp.getbinaryfile(file, local_file)
93
+ Logger.debug str
94
+ end
95
+ end
96
+
97
+ # Return from whence we came
98
+ chdir(pwd)
99
+
100
+ end
101
+
102
+ ##
103
+ # Return the configuration's name.
104
+ #
105
+ # Defaults to the first configuration name.
106
+ def name
107
+ @name ||= available_names.first
108
+ @name
109
+ end
110
+
111
+ ##
112
+ # Set the configuration's name.
113
+ #
114
+ # Only performs the assignment if the proposed name is
115
+ # an available name.
116
+ #
117
+ # Returns the Configuration's name.
118
+ def name=(new_name)
119
+ @name = new_name if name? new_name
120
+ @name
121
+ end
122
+
123
+ ##
124
+ # Test if the name passed in is an available configuration
125
+ # name.
126
+ def name?(name_to_check)
127
+ available_names.include? name_to_check
128
+ end
129
+
130
+ def local_path
131
+ Environment.wp_dir
132
+ end
133
+
134
+ def remote_path
135
+ ftp_dir = send(:FTP_DIR)
136
+ return "/" if ftp_dir.nil?
137
+ ftp_dir
138
+ end
139
+
140
+ def username
141
+ send(:FTP_USER) || ""
142
+ end
143
+
144
+ def password
145
+ send(:FTP_PASSWORD) || ""
146
+ end
147
+
148
+ def port
149
+ port = 21
150
+ match = /:(?<port>\d+)$/.match(send(:FTP_HOST))
151
+ port = match[:port].to_i unless match.nil?
152
+ port
153
+ end
154
+
155
+ ##
156
+ # Does FTP_HOST contain a port number?
157
+ def port?
158
+ !(send(:FTP_HOST) =~ /:(?<port>\d+)$/).nil?
159
+ end
160
+
161
+ ##
162
+ # Get just the hostname from DB_HOST. Only different from
163
+ # FTP_HOST if FTP_HOST has a port number in it.
164
+ def host
165
+ host = "localhost"
166
+ match = /(?<host>.*?)(?=:|$)/.match(send(:FTP_HOST))
167
+ host = match[:host].to_s unless match.nil?
168
+ host
169
+ end
170
+
171
+ FTP_CONFIGURATION_ATTRIBUTES = [:FTP_DIR, :FTP_USER, :FTP_PASSWORD,
172
+ :FTP_HOST]
173
+
174
+ ##
175
+ # Define the behaviours of the default parameters quickly
176
+ def method_missing(meth, *args, &block)
177
+ # Convert the method to a symbol
178
+ method_symbol = meth.to_sym
179
+
180
+ if FTP_CONFIGURATION_ATTRIBUTES.include? method_symbol
181
+ config = @yaml[name]
182
+ return config[meth.to_s] if config.include? meth.to_s
183
+ else
184
+ # You *must* call super if you don't handle the method, otherwise
185
+ # you will mess up Ruby's method lookup.
186
+ super
187
+ end
188
+ end
189
+
190
+ ##
191
+ # Define respond_to?
192
+ def respond_to?(method)
193
+ return true if FTP_CONFIGURATION_ATTRIBUTES.include? method.to_sym
194
+ super
195
+ end
196
+
197
+ private
198
+
199
+ def ftp
200
+ @ftp ||= Net::FTP.new
201
+ @ftp
202
+ end
203
+
204
+ ##
205
+ # Establish a connection to the remote server
206
+ def connect
207
+ ftp.connect(host, port)
208
+ ftp.login(username, password)
209
+ ftp.passive = true
210
+ # ftp.debug_mode = true
211
+ #chdir(remote_path)
212
+ end
213
+
214
+ ##
215
+ # Put file on remote machine
216
+ def put_file(real_path)
217
+ pn = Pathname.new(real_path)
218
+ relative = pn.relative_path_from Pathname.new(Environment.wp_dir)
219
+
220
+ # Only try to send files; no directories
221
+ unless pn.directory?
222
+ local_directory, local_file_name = relative.split
223
+ remote_directory = Pathname.new("#{remote_path}/#{local_directory}").cleanpath.to_s
224
+
225
+ begin
226
+ # Make sure to be in the right directory
227
+ chdir remote_directory
228
+
229
+ # Now send the file (overwriting if it exists)
230
+ write_file(real_path, local_file_name.to_s, true)
231
+ rescue Net::FTPPermError; end
232
+ end
233
+ end
234
+
235
+ def write_file(local_file, remote_file_name, overwrite)
236
+ begin
237
+ str = "[cp] #{remote_file_name} (#{self.number_to_human_size(File.size(local_file), precision: 2)})"
238
+ ftp.putbinaryfile(local_file, remote_file_name)
239
+ Logger.debug str
240
+ rescue Net::FTPPermError => e
241
+ if ftp.last_response_code.to_i === 550 and overwrite
242
+ ftp.delete(remote_file_name)
243
+ ftp.putbinaryfile(real_path, remote_file_name)
244
+ Logger.debug "#{str} - OVERWRITING"
245
+ end
246
+ end
247
+ end
248
+
249
+ ##
250
+ # Check that directory path exists on the FTP site
251
+ def directory?(directory)
252
+ pwd = ftp.pwd
253
+ begin
254
+ ftp.chdir(directory)
255
+ return true
256
+ rescue Net::FTPPermError
257
+ return false
258
+ end
259
+ ensure
260
+ ftp.chdir pwd
261
+ end
262
+
263
+ ##
264
+ # Change the current working directory
265
+ def chdir(directory)
266
+ # If the current working directory does not
267
+ # match the current working directory then
268
+ # the directory must be changed
269
+ if ftp.pwd != directory
270
+ Logger.debug "[cd] #{directory}"
271
+ # Make the requested directory if it does not exist
272
+ mkdir(directory) unless directory?(directory)
273
+ # Change into the requested directory
274
+ ftp.chdir(directory)
275
+ end
276
+ end
277
+
278
+ ##
279
+ # Similar to mkdir -p.
280
+ # It will make all the full directory path specified on the FTP site
281
+ def mkdir(directory)
282
+ pwd = ftp.pwd
283
+ dirs = directory.split("/")
284
+ dirs.each do |dir|
285
+ begin
286
+ ftp.chdir(dir)
287
+ rescue Net::FTPPermError => e
288
+ if ftp.last_response_code.to_i === 550
289
+ Logger.debug "[mkdir] #{File.join(ftp.pwd, dir)}"
290
+ ftp.mkdir(dir)
291
+ ftp.chdir(dir)
292
+ end
293
+ end
294
+ end
295
+ ensure
296
+ # Make sure to return to the directory
297
+ # that was currently in use before the mkdir command
298
+ # was issued
299
+ ftp.chdir(pwd)
300
+ end
301
+
302
+ end
303
+ end
304
+ end
305
+
@@ -1,3 +1,3 @@
1
1
  module WordpressDeploy
2
- VERSION = "1.0.0.alpha1"
2
+ VERSION = "1.0.0.alpha2"
3
3
  end
@@ -0,0 +1,196 @@
1
+ module WordpressDeploy
2
+ module Wordpress
3
+
4
+ class Configuration
5
+
6
+ attr_accessor :available_names
7
+ alias :names :available_names
8
+
9
+ def initialize(config_name=nil)
10
+ @template = File.join(Environment.wp_dir, "wp-config-sample.php")
11
+ @output = File.join(Environment.wp_dir, "wp-config.php")
12
+
13
+ @yaml = YAML.load_file(File.join(Environment.config_dir, "wp-config.yml"))
14
+ @available_names = @yaml.map { |key, val| key }
15
+
16
+ self.name = config_name unless config_name.nil?
17
+ end
18
+
19
+ ##
20
+ # Return the configuration's name.
21
+ #
22
+ # Defaults to the first configuration name.
23
+ def name
24
+ @name ||= available_names.first
25
+ @name
26
+ end
27
+
28
+ ##
29
+ # Set the configuration's name.
30
+ #
31
+ # Only performs the assignment if the proposed name is
32
+ # an available name.
33
+ #
34
+ # Returns the Configuration's name.
35
+ def name=(new_name)
36
+ @name = new_name if name? new_name
37
+ @name
38
+ end
39
+
40
+ ##
41
+ # Test if the name passed in is an available configuration
42
+ # name.
43
+ def name?(name_to_check)
44
+ available_names.include? name_to_check
45
+ end
46
+
47
+ ##
48
+ # Returns 64 psuedo-random characters as a string
49
+ # Characters can be a-zA-z or !@#$%^&*()-~+=|/{}:;,.?<>[]
50
+ def self.salt
51
+ salt_array.sample(64).join("")
52
+ end
53
+
54
+ ##
55
+ # The file that contains the template for the output file
56
+ def template
57
+ @template
58
+ end
59
+
60
+ ##
61
+ # The file that will be output
62
+ def output
63
+ @output
64
+ end
65
+
66
+ ##
67
+ # Extract just the port number from the DB_HOST
68
+ # configuration file. Or return the default port of 3306.
69
+ def port
70
+ port = 3306
71
+ match = /:(?<port>\d+)$/.match(send(:DB_HOST))
72
+ port = match[:port].to_i unless match.nil?
73
+ port
74
+ end
75
+
76
+ ##
77
+ # Does DB_HOST contain a port number?
78
+ def port?
79
+ !(send(:DB_HOST) =~ /:(?<port>\d+)$/).nil?
80
+ end
81
+
82
+ ##
83
+ # Get just the hostname from DB_HOST. Only different from
84
+ # DB_HOST if DB_HOST has a socket or a port number in it.
85
+ def host
86
+ host = "localhost"
87
+ match = /(?<host>.*?)(?=:|$)/.match(send(:DB_HOST))
88
+ host = match[:host].to_s unless match.nil?
89
+ host
90
+ end
91
+
92
+ ##
93
+ # Extract just the socket part from the DB_HOST
94
+ # configuration file. Or return an empty string if none.
95
+ def socket
96
+ socket = ""
97
+ match = /:(?<socket>\/.*)$/.match(send(:DB_HOST))
98
+ socket = match[:socket].to_s unless match.nil?
99
+ socket
100
+ end
101
+
102
+ ##
103
+ # Does DB_HOST contain a socket path?
104
+ def socket?
105
+ !(send(:DB_HOST) =~ /:(?<socket>\/.*)$/).nil?
106
+ end
107
+
108
+ ##
109
+ # Write the file the filesystem
110
+ def save!
111
+ # Remove the output file if is already there
112
+ FileUtils.rm @output if File.exists? @output
113
+
114
+ # Open the output file
115
+ output = File.open(@output, 'w')
116
+
117
+ # Start parsing the template file
118
+ File.open(@template, 'r') do |template|
119
+ template.each_line do |line|
120
+ match = /^define\(['"](?<parameter>\w*)['"]/.match(line)
121
+ unless match.nil?
122
+ # Get named capture group from Regular Expression
123
+ param = match[:parameter]
124
+
125
+ # Get the value for the specified parameter
126
+ value = send(param)
127
+
128
+ # Set the definition of the line
129
+ line = define(param, value)
130
+ end
131
+ output.puts(line)
132
+ end
133
+ end
134
+
135
+ ensure
136
+ # Close the output file if it is open
137
+ # even if an exception occurs
138
+ output.close if output
139
+ end
140
+
141
+ WP_CONFIGURATION_ATTRIBUTES = [:DB_NAME, :DB_USER, :DB_PASSWORD, :DB_HOST,
142
+ :DB_CHARSET, :DB_COLLATE, :WPLANG,
143
+ :WP_DEBUG]
144
+
145
+ WP_CONFIGURATION_SALTS = [:AUTH_KEY, :SECURE_AUTH_KEY,
146
+ :LOGGED_IN_KEY, :NONCE_KEY, :AUTH_SALT,
147
+ :SECURE_AUTH_SALT, :LOGGED_IN_SALT,
148
+ :NONCE_SALT]
149
+
150
+ WP_CONFIGURATION_ALL = WP_CONFIGURATION_ATTRIBUTES +
151
+ WP_CONFIGURATION_SALTS
152
+
153
+ ##
154
+ # Define the behaviours of the default parameters quickly
155
+ def method_missing(meth, *args, &block)
156
+ # Convert the method to a symbol
157
+ method_symbol = meth.to_sym
158
+
159
+ if WP_CONFIGURATION_ATTRIBUTES.include? method_symbol
160
+ config = @yaml[name]
161
+ return config[meth.to_s] if config.include? meth.to_s
162
+ ""
163
+ elsif WP_CONFIGURATION_SALTS.include? method_symbol
164
+ # Return salt if the method is a salting method
165
+ Configuration.salt
166
+ else
167
+ # You *must* call super if you don't handle the method, otherwise
168
+ # you will mess up Ruby's method lookup.
169
+ super
170
+ end
171
+ end
172
+
173
+ ##
174
+ # Define respond_to?
175
+ def respond_to?(method)
176
+ return true if WP_CONFIGURATION_ALL.include? method.to_sym
177
+ super
178
+ end
179
+
180
+ private
181
+
182
+ ##
183
+ # The Salting array
184
+ # Provides the array of available characters that can bet used as salt
185
+ def self.salt_array
186
+ @salt_array ||= %w{0 1 2 3 4 5 6 7 8 9 a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z ! @ # $ % ^ & * ( ) - ~ + = | / { } : ; , . ? < > [ ]}
187
+ end
188
+
189
+ def define(key, value)
190
+ "define('#{key}', '#{value}');"
191
+ end
192
+
193
+ end
194
+
195
+ end
196
+ end