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