wordpress-deploy 1.0.0.alpha1 → 1.0.0.alpha2

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.
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