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.
- data/.gitignore +26 -1
- data/.rspec +1 -0
- data/.rvmrc +48 -0
- data/.travis.yml +4 -0
- data/Gemfile +2 -5
- data/Gemfile.lock +40 -28
- data/Guardfile +24 -0
- data/README.md +22 -7
- data/bin/wp-deploy +1 -4
- data/lib/wordpress_deploy.rb +22 -48
- data/lib/wordpress_deploy/cli/helpers.rb +27 -14
- data/lib/wordpress_deploy/cli/utility.rb +86 -37
- data/lib/wordpress_deploy/database/mysql.rb +22 -136
- data/lib/wordpress_deploy/environment.rb +68 -0
- data/lib/wordpress_deploy/errors.rb +54 -0
- data/lib/wordpress_deploy/logger.rb +28 -50
- data/lib/wordpress_deploy/transfer_protocols/ftp.rb +305 -0
- data/lib/wordpress_deploy/version.rb +1 -1
- data/lib/wordpress_deploy/wordpress/configuration.rb +196 -0
- data/spec/data/ftp.yml +4 -0
- data/spec/data/wp-config-sample.php +90 -0
- data/spec/data/wp-config.yml +128 -0
- data/spec/database/mysql_spec.rb +93 -0
- data/spec/environment_spec.rb +35 -0
- data/spec/spec_helper.rb +36 -1
- data/spec/transfer_protocols/ftp_spec.rb +193 -0
- data/spec/wordpress/configuration_spec.rb +202 -0
- data/wordpress_deploy.gemspec +13 -10
- metadata +63 -47
- data/lib/wordpress_deploy/config.rb +0 -68
- data/lib/wordpress_deploy/database/base.rb +0 -53
- data/lib/wordpress_deploy/pipeline.rb +0 -110
- data/lib/wordpress_deploy/storage/base.rb +0 -99
- data/lib/wordpress_deploy/storage/ftp.rb +0 -133
- data/lib/wordpress_deploy/storage/local.rb +0 -82
- data/lib/wordpress_deploy/storage/scp.rb +0 -99
- data/lib/wordpress_deploy/storage/sftp.rb +0 -108
- data/spec/config_spec.rb +0 -16
@@ -1,68 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
module WordpressDeploy
|
4
|
-
module Config
|
5
|
-
DEFAULTS = {
|
6
|
-
:config_file => 'config.rb',
|
7
|
-
:data_path => 'data',
|
8
|
-
:log_path => 'log',
|
9
|
-
:cache_path => '.cache',
|
10
|
-
:tmp_path => '.tmp'
|
11
|
-
}
|
12
|
-
|
13
|
-
class << self
|
14
|
-
# These paths are the basis for all scripts
|
15
|
-
attr_reader :base_dir
|
16
|
-
def base_dir
|
17
|
-
@base_dir ||= Dir.pwd
|
18
|
-
end
|
19
|
-
attr_reader :config_dir
|
20
|
-
def config_dir
|
21
|
-
@config_dir ||= File.join(base_dir, "config")
|
22
|
-
end
|
23
|
-
attr_reader :sites_dir
|
24
|
-
def sites_dir
|
25
|
-
@sites_dir ||= File.join(base_dir, "site")
|
26
|
-
end
|
27
|
-
|
28
|
-
attr_reader :ftp_config
|
29
|
-
def ftp_config
|
30
|
-
@ftp_config ||= YAML.load_file(File.join(config_dir, "ftp.yml"))
|
31
|
-
end
|
32
|
-
|
33
|
-
attr_reader :wp_config
|
34
|
-
def wp_config
|
35
|
-
@wp_config ||= YAML.load_file(File.join(config_dir, "wp-config.yml"))
|
36
|
-
end
|
37
|
-
|
38
|
-
attr_reader :wp_config_sample
|
39
|
-
def wp_config_sample
|
40
|
-
@wp_config_sample ||= File.join(sites_dir, "wp-config-sample.php")
|
41
|
-
end
|
42
|
-
attr_reader :wp_config_output
|
43
|
-
def wp_config_output
|
44
|
-
@wp_config_output ||= File.join(sites_dir, "wp-config.php")
|
45
|
-
end
|
46
|
-
|
47
|
-
attr_reader :environment
|
48
|
-
def environment
|
49
|
-
@environment ||= "production"
|
50
|
-
end
|
51
|
-
alias :env :environment
|
52
|
-
|
53
|
-
def environment=(new_env)
|
54
|
-
@environment = new_env.downcase
|
55
|
-
end
|
56
|
-
|
57
|
-
# The Salting array
|
58
|
-
def salt_array
|
59
|
-
@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 ! @ # $ % ^ & * ( ) - ~ + = | / { } : ; , . ? < > [ ]}
|
60
|
-
end
|
61
|
-
|
62
|
-
def salt_keys
|
63
|
-
@salt_keys ||= %w{AUTH_KEY SECURE_AUTH_KEY LOGGED_IN_KEY NONCE_KEY AUTH_SALT SECURE_AUTH_SALT LOGGED_IN_SALT NONCE_SALT}
|
64
|
-
end
|
65
|
-
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
@@ -1,53 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
module WordpressDeploy
|
4
|
-
module Database
|
5
|
-
class Base
|
6
|
-
include WordpressDeploy::CLI::Helpers
|
7
|
-
include WordpressDeploy::Configuration::Helpers
|
8
|
-
|
9
|
-
##
|
10
|
-
# Creates a new instance of the MongoDB database object
|
11
|
-
# * Called using super(model) from subclasses *
|
12
|
-
def initialize(model)
|
13
|
-
@model = model
|
14
|
-
load_defaults!
|
15
|
-
end
|
16
|
-
|
17
|
-
##
|
18
|
-
# Super method for all child (database) objects. Every database object's #perform!
|
19
|
-
# method should call #super before anything else to prepare
|
20
|
-
def perform!
|
21
|
-
prepare!
|
22
|
-
log!
|
23
|
-
end
|
24
|
-
|
25
|
-
private
|
26
|
-
|
27
|
-
##
|
28
|
-
# Defines the @dump_path and ensures it exists by creating it
|
29
|
-
def prepare!
|
30
|
-
@dump_path = File.join(
|
31
|
-
Config.tmp_path,
|
32
|
-
@model.trigger,
|
33
|
-
'databases',
|
34
|
-
self.class.name.split('::').last
|
35
|
-
)
|
36
|
-
FileUtils.mkdir_p(@dump_path)
|
37
|
-
end
|
38
|
-
|
39
|
-
##
|
40
|
-
# Return the database name, with WordpressDeploy namespace removed
|
41
|
-
def database_name
|
42
|
-
self.class.to_s.sub('WordpressDeploy::', '')
|
43
|
-
end
|
44
|
-
|
45
|
-
##
|
46
|
-
# Logs a message to the console and log file to inform
|
47
|
-
# the client that WordpressDeploy is dumping the database
|
48
|
-
def log!
|
49
|
-
Logger.message "#{ database_name } started dumping and archiving '#{ name }'."
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
@@ -1,110 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
module WordpressDeploy
|
4
|
-
class Pipeline
|
5
|
-
include WordpressDeploy::CLI::Helpers
|
6
|
-
|
7
|
-
attr_reader :stderr, :errors
|
8
|
-
|
9
|
-
def initialize
|
10
|
-
@commands = []
|
11
|
-
@errors = []
|
12
|
-
@stderr = ''
|
13
|
-
end
|
14
|
-
|
15
|
-
##
|
16
|
-
# Adds a command to be executed in the pipeline.
|
17
|
-
# Each command will be run in the order in which it was added,
|
18
|
-
# with it's output being piped to the next command.
|
19
|
-
def <<(command)
|
20
|
-
@commands << command
|
21
|
-
end
|
22
|
-
|
23
|
-
##
|
24
|
-
# Runs the command line from `#pipeline` and collects STDOUT/STDERR.
|
25
|
-
# STDOUT is then parsed to determine the exit status of each command.
|
26
|
-
# For each command with a non-zero exit status, a SystemCallError is
|
27
|
-
# created and added to @errors. All STDERR output is set in @stderr.
|
28
|
-
#
|
29
|
-
# Note that there is no accumulated STDOUT from the commands themselves.
|
30
|
-
# Also, the last command should not attempt to write to STDOUT.
|
31
|
-
# Any output on STDOUT from the final command will be sent to STDERR.
|
32
|
-
# This in itself will not cause #run to fail, but will log warnings
|
33
|
-
# when all commands exit with non-zero status.
|
34
|
-
#
|
35
|
-
# Use `#success?` to determine if all commands in the pipeline succeeded.
|
36
|
-
# If `#success?` returns `false`, use `#error_messages` to get an error report.
|
37
|
-
def run
|
38
|
-
Open4.popen4(pipeline) do |pid, stdin, stdout, stderr|
|
39
|
-
pipestatus = stdout.read.gsub("\n", '').split(':').sort
|
40
|
-
pipestatus.each do |status|
|
41
|
-
index, exitstatus = status.split('|').map(&:to_i)
|
42
|
-
if exitstatus > 0
|
43
|
-
command = command_name(@commands[index])
|
44
|
-
@errors << SystemCallError.new(
|
45
|
-
"'#{ command }' returned exit code: #{ exitstatus }", exitstatus
|
46
|
-
)
|
47
|
-
end
|
48
|
-
end
|
49
|
-
@stderr = stderr.read.strip
|
50
|
-
end
|
51
|
-
Logger.warn(stderr_messages) if success? && stderr_messages
|
52
|
-
rescue Exception => e
|
53
|
-
raise Errors::Pipeline::ExecutionError.wrap(e)
|
54
|
-
end
|
55
|
-
|
56
|
-
def success?
|
57
|
-
@errors.empty?
|
58
|
-
end
|
59
|
-
|
60
|
-
##
|
61
|
-
# Returns a multi-line String, reporting all STDERR messages received
|
62
|
-
# from the commands in the pipeline (if any), along with the SystemCallError
|
63
|
-
# (Errno) message for each command which had a non-zero exit status.
|
64
|
-
#
|
65
|
-
# Each error is wrapped by WordpressDeploy::Errors to provide formatting.
|
66
|
-
def error_messages
|
67
|
-
@error_messages ||= (stderr_messages || '') +
|
68
|
-
"The following system errors were returned:\n" +
|
69
|
-
@errors.map {|err| Errors::Error.wrap(err).message }.join("\n")
|
70
|
-
end
|
71
|
-
|
72
|
-
private
|
73
|
-
|
74
|
-
##
|
75
|
-
# Each command is added as part of the pipeline, grouped with an `echo`
|
76
|
-
# command to pass along the command's index in @commands and it's exit status.
|
77
|
-
# The command's STDERR is redirected to FD#4, and the `echo` command to
|
78
|
-
# report the "index|exit status" is redirected to FD#3.
|
79
|
-
# Each command's STDOUT will be connected to the STDIN of the next subshell.
|
80
|
-
# The entire pipeline is run within a container group, which redirects
|
81
|
-
# FD#3 to STDOUT and FD#4 to STDERR so these can be collected.
|
82
|
-
# FD#1 is redirected to STDERR so that any output from the final command
|
83
|
-
# on STDOUT will generate warnings, since the final command should not
|
84
|
-
# attempt to write to STDOUT, as this would interfere with collecting
|
85
|
-
# the exit statuses.
|
86
|
-
#
|
87
|
-
# There is no guarantee as to the order of this output, which is why the
|
88
|
-
# command's index in @commands is passed along with it's exit status.
|
89
|
-
# And, if multiple commands output messages on STDERR, those messages
|
90
|
-
# may be interleaved. Interleaving of the "index|exit status" outputs
|
91
|
-
# should not be an issue, given the small byte size of the data being written.
|
92
|
-
def pipeline
|
93
|
-
parts = []
|
94
|
-
@commands.each_with_index do |command, index|
|
95
|
-
parts << %Q[{ #{ command } 2>&4 ; echo "#{ index }|$?:" >&3 ; }]
|
96
|
-
end
|
97
|
-
%Q[{ #{ parts.join(' | ') } } 3>&1 1>&2 4>&2]
|
98
|
-
end
|
99
|
-
|
100
|
-
def stderr_messages
|
101
|
-
@stderr_messages ||= @stderr.empty? ? false : <<-EOS.gsub(/^ +/, ' ')
|
102
|
-
Pipeline STDERR Messages:
|
103
|
-
(Note: may be interleaved if multiple commands returned error messages)
|
104
|
-
|
105
|
-
#{ @stderr }
|
106
|
-
EOS
|
107
|
-
end
|
108
|
-
|
109
|
-
end
|
110
|
-
end
|
@@ -1,99 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
module WordpressDeploy
|
4
|
-
module Storage
|
5
|
-
class Base
|
6
|
-
include WordpressDeploy::Configuration::Helpers
|
7
|
-
|
8
|
-
##
|
9
|
-
# Sets the limit to how many backups to keep in the remote location.
|
10
|
-
# If exceeded, the oldest will be removed to make room for the newest
|
11
|
-
attr_accessor :keep
|
12
|
-
|
13
|
-
##
|
14
|
-
# (Optional)
|
15
|
-
# User-defined string used to uniquely identify multiple storages of the
|
16
|
-
# same type. This will be appended to the YAML storage file used for
|
17
|
-
# cycling backups.
|
18
|
-
attr_accessor :storage_id
|
19
|
-
|
20
|
-
##
|
21
|
-
# Creates a new instance of the storage object
|
22
|
-
# * Called with super(model, storage_id) from each subclass
|
23
|
-
def initialize(model, storage_id = nil)
|
24
|
-
load_defaults!
|
25
|
-
@model = model
|
26
|
-
@storage_id = storage_id
|
27
|
-
end
|
28
|
-
|
29
|
-
##
|
30
|
-
# Performs the backup transfer
|
31
|
-
def perform!
|
32
|
-
@package = @model.package
|
33
|
-
transfer!
|
34
|
-
cycle!
|
35
|
-
end
|
36
|
-
|
37
|
-
private
|
38
|
-
|
39
|
-
##
|
40
|
-
# Provider defaults to false. Overridden when using a service-based
|
41
|
-
# storage such as Amazon S3, Rackspace Cloud Files or Dropbox
|
42
|
-
def provider
|
43
|
-
false
|
44
|
-
end
|
45
|
-
|
46
|
-
##
|
47
|
-
# Each subclass must define a +path+ where remote files will be stored
|
48
|
-
def path; end
|
49
|
-
|
50
|
-
##
|
51
|
-
# Return the storage name, with optional storage_id
|
52
|
-
def storage_name
|
53
|
-
self.class.to_s.sub('WordpressDeploy::', '') +
|
54
|
-
(storage_id ? " (#{storage_id})" : '')
|
55
|
-
end
|
56
|
-
|
57
|
-
##
|
58
|
-
# Returns the local path
|
59
|
-
# This is where any Package to be transferred is located.
|
60
|
-
def local_path
|
61
|
-
Config.tmp_path
|
62
|
-
end
|
63
|
-
|
64
|
-
##
|
65
|
-
# Returns the remote path for the given Package
|
66
|
-
# This is where the Package will be stored, or was previously stored.
|
67
|
-
def remote_path_for(package)
|
68
|
-
File.join(path, package.trigger, package.time)
|
69
|
-
end
|
70
|
-
|
71
|
-
##
|
72
|
-
# Yields two arguments to the given block: "local_file, remote_file"
|
73
|
-
# The local_file is the full file name:
|
74
|
-
# e.g. "2011.08.30.11.00.02.backup.tar.enc"
|
75
|
-
# The remote_file is the full file name, minus the timestamp:
|
76
|
-
# e.g. "backup.tar.enc"
|
77
|
-
def files_to_transfer_for(package)
|
78
|
-
package.filenames.each do |filename|
|
79
|
-
yield filename, filename[20..-1]
|
80
|
-
end
|
81
|
-
end
|
82
|
-
alias :transferred_files_for :files_to_transfer_for
|
83
|
-
|
84
|
-
##
|
85
|
-
# Adds the current package being stored to the YAML cycle data file
|
86
|
-
# and will remove any old Package file(s) when the storage limit
|
87
|
-
# set by #keep is exceeded. Any errors raised while attempting to
|
88
|
-
# remove older packages will be rescued and a warning will be logged
|
89
|
-
# containing the original error message.
|
90
|
-
def cycle!
|
91
|
-
return unless keep.to_i > 0
|
92
|
-
Logger.message "#{ storage_name }: Cycling Started..."
|
93
|
-
Cycler.cycle!(self, @package)
|
94
|
-
Logger.message "#{ storage_name }: Cycling Complete!"
|
95
|
-
end
|
96
|
-
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|
@@ -1,133 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
##
|
4
|
-
# Only load the Net::FTP library/gem when the WordpressDeploy::Storage::FTP class is loaded
|
5
|
-
require 'net/ftp'
|
6
|
-
require 'pathname'
|
7
|
-
require 'action_view'
|
8
|
-
|
9
|
-
module WordpressDeploy
|
10
|
-
module Storage
|
11
|
-
class FTP < Base
|
12
|
-
include ActionView::Helpers::NumberHelper
|
13
|
-
|
14
|
-
##
|
15
|
-
# Server credentials
|
16
|
-
attr_accessor :username, :password
|
17
|
-
|
18
|
-
##
|
19
|
-
# Server IP Address and FTP port
|
20
|
-
attr_accessor :ip, :port
|
21
|
-
alias :hostname :ip
|
22
|
-
|
23
|
-
##
|
24
|
-
# Path to store backups to
|
25
|
-
attr_accessor :path
|
26
|
-
|
27
|
-
##
|
28
|
-
# use passive mode?
|
29
|
-
attr_accessor :passive_mode
|
30
|
-
|
31
|
-
##
|
32
|
-
# the remote path
|
33
|
-
attr_accessor :remote_path
|
34
|
-
|
35
|
-
##
|
36
|
-
# Creates a new instance of the storage object
|
37
|
-
def initialize(model, storage_id = nil, &block)
|
38
|
-
super(model, storage_id)
|
39
|
-
|
40
|
-
@hostname = hostname
|
41
|
-
@username = username
|
42
|
-
@password = password
|
43
|
-
@remote_path = remote_path
|
44
|
-
@remote_directores = []
|
45
|
-
|
46
|
-
@port ||= 21
|
47
|
-
@path ||= 'backups'
|
48
|
-
@passive_mode ||= false
|
49
|
-
|
50
|
-
instance_eval(&block) if block_given?
|
51
|
-
|
52
|
-
@path = path.sub(/^\~\//, '')
|
53
|
-
end
|
54
|
-
|
55
|
-
private
|
56
|
-
|
57
|
-
##
|
58
|
-
# Establishes a connection to the remote server
|
59
|
-
#
|
60
|
-
# Note:
|
61
|
-
# Since the FTP port is defined as a constant in the Net::FTP class, and
|
62
|
-
# might be required to change by the user, we dynamically remove and
|
63
|
-
# re-add the constant with the provided port value
|
64
|
-
def connection
|
65
|
-
if Net::FTP.const_defined?(:FTP_PORT)
|
66
|
-
Net::FTP.send(:remove_const, :FTP_PORT)
|
67
|
-
end; Net::FTP.send(:const_set, :FTP_PORT, port)
|
68
|
-
|
69
|
-
Net::FTP.open(ip, username, password) do |ftp|
|
70
|
-
ftp.passive = true if passive_mode
|
71
|
-
yield ftp
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
##
|
76
|
-
# Transfers the archived file to the specified remote server
|
77
|
-
def transfer!
|
78
|
-
remote_path = remote_path_for(@package)
|
79
|
-
|
80
|
-
connection do |ftp|
|
81
|
-
create_remote_path(remote_path, ftp)
|
82
|
-
|
83
|
-
files_to_transfer_for(@package) do |local_file, remote_file|
|
84
|
-
Logger.message "#{storage_name} started transferring " +
|
85
|
-
"'#{ local_file }' to '#{ ip }'."
|
86
|
-
ftp.put(
|
87
|
-
File.join(local_path, local_file),
|
88
|
-
File.join(remote_path, remote_file)
|
89
|
-
)
|
90
|
-
end
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
##
|
95
|
-
# Removes the transferred archive file(s) from the storage location.
|
96
|
-
# Any error raised will be rescued during Cycling
|
97
|
-
# and a warning will be logged, containing the error message.
|
98
|
-
def remove!(package)
|
99
|
-
remote_path = remote_path_for(package)
|
100
|
-
|
101
|
-
connection do |ftp|
|
102
|
-
transferred_files_for(package) do |local_file, remote_file|
|
103
|
-
Logger.message "#{storage_name} started removing " +
|
104
|
-
"'#{ local_file }' from '#{ ip }'."
|
105
|
-
|
106
|
-
ftp.delete(File.join(remote_path, remote_file))
|
107
|
-
end
|
108
|
-
|
109
|
-
ftp.rmdir(remote_path)
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
##
|
114
|
-
# Creates (if they don't exist yet) all the directories on the remote
|
115
|
-
# server in order to upload the backup file. Net::FTP does not support
|
116
|
-
# paths to directories that don't yet exist when creating new
|
117
|
-
# directories. Instead, we split the parts up in to an array (for each
|
118
|
-
# '/') and loop through that to create the directories one by one.
|
119
|
-
# Net::FTP raises an exception when the directory it's trying to create
|
120
|
-
# already exists, so we have rescue it
|
121
|
-
def create_remote_path(remote_path, ftp)
|
122
|
-
path_parts = Array.new
|
123
|
-
remote_path.split('/').each do |path_part|
|
124
|
-
path_parts << path_part
|
125
|
-
begin
|
126
|
-
ftp.mkdir(path_parts.join('/'))
|
127
|
-
rescue Net::FTPPermError; end
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
|
-
end
|
132
|
-
end
|
133
|
-
end
|