capistrano-wp 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,85 @@
1
+ require 'tempfile'
2
+
3
+ # These methods are used by recap tasks to run commands and detect when files have changed
4
+ # as part of a deployments
5
+
6
+ module CrowdFavorite::Support::CapistranoExtensions
7
+ # Run a command as the given user
8
+ def as_user(user, command, pwd = deploy_to)
9
+ sudo "su - #{user} -c 'cd #{pwd} && #{command}'"
10
+ end
11
+
12
+ # Run a command as root
13
+ def as_root(command, pwd = deploy_to)
14
+ as_user 'root', command, pwd
15
+ end
16
+
17
+ # Run a command as the application user
18
+ def as_app(command, pwd = deploy_to)
19
+ as_user application_user, command, pwd
20
+ end
21
+
22
+ # Put a string into a file as the application user
23
+ def put_as_app(string, path)
24
+ put string, "/tmp/cf-put-as-app"
25
+ as_app "cp /tmp/cf-put-as-app #{path} && chmod g+rw #{path}", "/"
26
+ ensure
27
+ run "rm /tmp/cf-put-as-app"
28
+ end
29
+
30
+ def editor
31
+ ENV['DEPLOY_EDITOR'] || ENV['EDITOR']
32
+ end
33
+
34
+ # Edit a file on the remote server, using a local editor
35
+ def edit_file(path)
36
+ if editor
37
+ as_app "touch #{path} && chmod g+rw #{path}"
38
+ local_path = Tempfile.new('deploy-edit').path
39
+ get(path, local_path)
40
+ CrowdFavorite::Support::ShellCommand.execute_interactive("#{editor} #{local_path}")
41
+ File.read(local_path)
42
+ else
43
+ abort "To edit a remote file, either the EDITOR or DEPLOY_EDITOR environment variables must be set"
44
+ end
45
+ end
46
+
47
+ # Run a git command in the `deploy_to` directory
48
+ def git(command)
49
+ run "cd #{deploy_to} && umask 002 && sg #{application_group} -c \"git #{command}\""
50
+ end
51
+
52
+ # Capture the result of a git command run within the `deploy_to` directory
53
+ def capture_git(command)
54
+ capture "cd #{deploy_to} && umask 002 && sg #{application_group} -c 'git #{command}'"
55
+ end
56
+
57
+ def exit_code(command)
58
+ capture("#{command} > /dev/null 2>&1; echo $?").strip
59
+ end
60
+
61
+ def exit_code_as_app(command, pwd = deploy_to)
62
+ capture(%|sudo -p 'sudo password: ' su - #{application_user} -c 'cd #{pwd} && #{command} > /dev/null 2>&1'; echo $?|).strip
63
+ end
64
+
65
+ # Find the latest tag from the repository. As `git tag` returns tags in order, and our release
66
+ # tags are timestamps, the latest tag will always be the last in the list.
67
+ def latest_tag_from_repository
68
+ result = capture_git("tag | tail -n1").strip
69
+ result.empty? ? nil : result
70
+ end
71
+
72
+ # Does the given file exist within the deployment directory?
73
+ def deployed_file_exists?(path, root_path = deploy_to)
74
+ exit_code("cd #{root_path} && [ -f #{path} ]") == "0"
75
+ end
76
+
77
+ # Has the given path been created or changed since the previous deployment? During the first
78
+ # successful deployment this will always return true.
79
+ def deployed_file_changed?(path)
80
+ return true unless latest_tag
81
+ exit_code("cd #{deploy_to} && git diff --exit-code #{latest_tag} origin/#{branch} #{path}") == "1"
82
+ end
83
+
84
+ Capistrano::Configuration.send :include, self
85
+ end
@@ -0,0 +1,47 @@
1
+ require 'capistrano'
2
+ require 'crowdfavorite/support/capistrano_extensions'
3
+
4
+ # This module is used to capture the definition of capistrano tasks, which makes it
5
+ # easier to test the behaviour of specific tasks without loading everything. If you
6
+ # are writing tests for a collection of tasks, you should put those tasks in a module
7
+ # and extend that module with `CrowdFavorite::Support::Namespace.
8
+ #
9
+ # You can look at some of the existing tasks (such as [env](../tasks/env.html)) and
10
+ # its corresponding specs for an example of this in practice.
11
+ #
12
+ # You should not need to use this module directly when using recap to deploy.
13
+
14
+ module CrowdFavorite::Support::Namespace
15
+ def self.default_config
16
+ @default_config
17
+ end
18
+
19
+ def self.default_config=(config)
20
+ @default_config = config
21
+ end
22
+
23
+ if Capistrano::Configuration.instance
24
+ self.default_config = Capistrano::Configuration.instance(:must_exist)
25
+ end
26
+
27
+ def capistrano_definitions
28
+ @capistrano_definitions ||= []
29
+ end
30
+
31
+ def namespace(name, &block)
32
+ capistrano_definitions << Proc.new do
33
+ namespace name do
34
+ instance_eval(&block)
35
+ end
36
+ end
37
+
38
+ load_into(CrowdFavorite::Support::Namespace.default_config) if CrowdFavorite::Support::Namespace.default_config
39
+ end
40
+
41
+ def load_into(configuration)
42
+ configuration.extend(self)
43
+ capistrano_definitions.each do |definition|
44
+ configuration.load(&definition)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,230 @@
1
+ require 'crowdfavorite/tasks'
2
+
3
+ module CrowdFavorite::Tasks::LocalChanges
4
+ extend CrowdFavorite::Support::Namespace
5
+
6
+ namespace :cf do
7
+ def _cset(name, *args, &block)
8
+ unless exists?(name)
9
+ set(name, *args, &block)
10
+ end
11
+ end
12
+
13
+ _cset(:comparison_target) { current_release rescue File.dirname(release_path) }
14
+ _cset(:hash_creation) {
15
+ # Find out what hashing mechanism we can use - shasum, sha1sum, openssl, or just an ls command.
16
+ # Unfortunate double-call of which handles some systems which output an error message on stdout
17
+ # when the program cannot be found.
18
+ creation = capture('((which shasum >/dev/null 2>&1 && which shasum) || (which sha1sum >/dev/null 2>&1 && which sha1sum) || (which openssl >/dev/null 2>&1 && which openssl) || echo "ls -ld")').to_s.strip
19
+ if creation.match(/openssl$/)
20
+ "#{creation} sha1 -r"
21
+ end
22
+ creation
23
+ }
24
+
25
+ _cset(:hash_directory) { shared_path }
26
+ _cset(:hash_suffix) { "_hash" }
27
+ _cset(:hash_compare_suffix) { "compare" }
28
+ _cset(:hashes) { capture("ls -xt #{File.join(hash_directory, '*' + hash_suffix)}").split.reverse }
29
+
30
+
31
+ def _snapshot_exists(path)
32
+ retcode = (capture("test -f " + Shellwords::escape(path) + "; echo $?").to_s.strip.to_i)
33
+ return retcode == 0 ? true : false
34
+ end
35
+
36
+ def _hash_path(release, extra = "")
37
+ File.join(hash_directory, release + hash_suffix + extra)
38
+ end
39
+
40
+ namespace :localchanges do
41
+ task :snapshot_deploy, :except => { :no_release => true } do
42
+ set(:snapshot_target, latest_release)
43
+ snapshot
44
+ unset(:snapshot_target)
45
+ end
46
+
47
+ desc "Snapshot the current release for later change detection."
48
+ task :snapshot, :except => { :no_release => true } do
49
+ target_release = File.basename(fetch(:snapshot_target, comparison_target))
50
+
51
+ target_path = File.join(releases_path, target_release)
52
+ default_hash_path = _hash_path(target_release) # File.join(shared_path, target_release + "_hash")
53
+ hash_path = fetch(:snapshot_hash_path, default_hash_path)
54
+
55
+ snapshot_exists = _snapshot_exists(hash_path)
56
+
57
+ if snapshot_exists and !fetch(:snapshot_force, false)
58
+ logger.info "A snapshot for release #{target_release} already exists."
59
+ next
60
+ end
61
+
62
+ run("find " + Shellwords::escape(target_path) + " -name .git -prune -o -name .svn -prune -o -type f -print0 | xargs -0 #{hash_creation} > " + Shellwords::escape(hash_path))
63
+
64
+ end
65
+
66
+ desc "Call this before a deploy to continue despite local changes made on the server."
67
+ task :allow_differences do
68
+ set(:snapshot_allow_differences, true)
69
+ end
70
+
71
+ task :forbid_differences do
72
+ set(:snapshot_allow_differences, false)
73
+ end
74
+
75
+ def _do_snapshot_compare()
76
+ if releases.length == 0
77
+ logger.info "no current release"
78
+ return false
79
+ end
80
+ release_name = File.basename(current_release)
81
+ set(:snapshot_target, current_release)
82
+ default_hash_path = _hash_path(release_name) # File.join(shared_path, release_name + "_hash")
83
+ snapshot_exists = _snapshot_exists(default_hash_path)
84
+ if !snapshot_exists
85
+ logger.info "no previous snapshot to compare against"
86
+ return false
87
+ end
88
+ set(:snapshot_hash_path, _hash_path(release_name, hash_compare_suffix)) # File.join(shared_path, release_name + "_hash_compare"))
89
+ set(:snapshot_force, true)
90
+ snapshot
91
+
92
+ # Hand-tooled diff-parsing - handles either shasum-style or ls -ld output
93
+ # Hashes store filename => [host, host, host]
94
+ left = {}
95
+ right = {}
96
+ changed = {}
97
+ run("diff " + default_hash_path + " " + snapshot_hash_path + " || true") do |channel, stream, data|
98
+ data.each_line do |line|
99
+ line.strip!
100
+ if line.match(/^\s*[<>]/)
101
+ parts = line.split(/\s+/)
102
+ if hash_creation.match(/ls -ld/)
103
+ # > -rw-rw-r-- 1 example example 41 Sep 19 14:58 index.php
104
+ parts.slice!(0, 9)
105
+ else
106
+ # < 198ed94e9f1e5c69e159e8ba6d4420bb9c039715 index.php
107
+ parts.slice!(0,2)
108
+ end
109
+
110
+ bucket = line.match(/^\s*</) ? left : right
111
+ filename = parts.join('')
112
+
113
+ bucket[filename] ||= []
114
+ bucket[filename].push(channel[:host])
115
+ end
116
+ end
117
+ end
118
+ if !(left.empty? && right.empty?)
119
+ left.each do |filename, servers|
120
+ if right.has_key?(filename)
121
+ servers.each do |host|
122
+ if right[filename].delete(host)
123
+ changed[filename] ||= []
124
+ changed[filename].push(host)
125
+ left[filename].delete(host)
126
+ end
127
+ end
128
+
129
+ left.delete(filename) if left[filename].empty?
130
+ right.delete(filename) if right[filename].empty?
131
+ end
132
+ end
133
+ end
134
+
135
+ unset(:snapshot_target)
136
+ unset(:snapshot_hash_path)
137
+ unset(:snapshot_force)
138
+ return {:left => left, :right => right, :changed => changed}
139
+ end
140
+
141
+ def _do_snapshot_diff(results, format = :full)
142
+ if !results
143
+ return false
144
+ end
145
+ if results[:left].empty? && results[:right].empty? && results[:changed].empty?
146
+ return false
147
+ end
148
+
149
+ if format == :basic || !(fetch(:strategy).class <= Capistrano::Deploy::Strategy.new(:remote).class)
150
+ logger.important "deleted: " + results[:left].inspect
151
+ logger.important "created: " + results[:right].inspect
152
+ logger.important "changed: " + results[:changed].inspect
153
+ return true
154
+ end
155
+
156
+ ## TODO: improve diff handling for remote_cache with .git copy_excluded
157
+ ## TODO: improve diff handling for remote_cache with .git not copy_excluded
158
+ ## TODO: improve diff handling for remote_cache with .svn copy_excluded
159
+ ## TODO: improve diff handling for remote_cache with .svn not copy_excluded
160
+ logger.important "deleted: " + results[:left].inspect
161
+ logger.important "created: " + results[:right].inspect
162
+ logger.important "changed: " + results[:changed].inspect
163
+ return true
164
+
165
+ todostuff = <<-'EOTODO'
166
+ File.join(shared_path, configuration[:repository_cache] || "cached-copy")
167
+ if fetch(:scm) == :git
168
+ if fetch(:copy_exclude, []).include?(".git")
169
+ run("echo 'gitdir: #{}
170
+
171
+ end
172
+
173
+
174
+ run("diff " + default_hash_path + " " + snapshot_hash_path + " || true") do |channel, stream, data|
175
+ data.each_line do |line|
176
+ line.strip!
177
+ if line.match(/^\s*[<>]/)
178
+ parts = line.split(/\s+/)
179
+ if hash_creation.match(/ls -ld/)
180
+ # > -rw-rw-r-- 1 example example 41 Sep 19 14:58 index.php
181
+ parts.slice!(0, 9)
182
+ else
183
+ # < 198ed94e9f1e5c69e159e8ba6d4420bb9c039715 index.php
184
+ parts.slice!(0,2)
185
+ end
186
+
187
+ bucket = line.match(/^\s*</) ? left : right
188
+ filename = parts.join('')
189
+
190
+ bucket[filename] ||= []
191
+ bucket[filename].push(channel[:host])
192
+ end
193
+ end
194
+ end
195
+ logger.important "oh hey, fully featured"
196
+ return true
197
+ EOTODO
198
+ end
199
+
200
+ desc "Check the current release for changes made on the server; abort if changes are detected."
201
+ task :compare, :except => { :no_release => true } do
202
+ results = _do_snapshot_compare()
203
+ if _do_snapshot_diff(results, :basic)
204
+ abort("Aborting: local changes detected in current release") unless fetch(:snapshot_allow_differences, false)
205
+ logger.important "Continuing deploy despite differences!"
206
+ end
207
+ end
208
+
209
+ desc "Check the current release for changes made on the server (and return detailed changes, if using a remote-cached git repo). Does not abort on changes."
210
+ task :diff, :except => { :no_release => true } do
211
+ results = _do_snapshot_compare()
212
+ _do_snapshot_diff(results, :full)
213
+ end
214
+
215
+ task :cleanup, :except => { :no_release => true } do
216
+ count = fetch(:keep_releases, 5).to_i
217
+ if count >= hashes.length
218
+ logger.info "no old hashes to clean up"
219
+ else
220
+ logger.info "keeping #{count} of #{hashes.length} release hashes"
221
+ hashpaths = (hashes - hashes.last(count)).map{ |thehash|
222
+ File.join(hash_directory, thehash) + " " + File.join(hash_directory, thehash + hash_compare_suffix)
223
+ }.join(" ")
224
+ try_sudo "rm -f #{hashpaths}"
225
+ end
226
+ end
227
+ end
228
+ end
229
+ end
230
+
@@ -0,0 +1,247 @@
1
+ require 'crowdfavorite/tasks'
2
+ require 'Shellwords'
3
+
4
+ module CrowdFavorite::Tasks::WordPress
5
+ extend CrowdFavorite::Support::Namespace
6
+ def _combine_filehash base, update
7
+ if update.respond_to? :has_key?
8
+ update = [update]
9
+ end
10
+ new = {}
11
+ new.merge!(base)
12
+ update.each do |update_hash|
13
+ new.merge!(update_hash)
14
+ end
15
+ new
16
+ end
17
+ namespace :cf do
18
+ def _cset(name, *args, &block)
19
+ unless exists?(name)
20
+ set(name, *args, &block)
21
+ end
22
+ end
23
+
24
+ _cset :copy_exclude, [
25
+ ".git", ".gitignore", ".gitmodules",
26
+ ".DS_Store", ".svn",
27
+ "Capfile", "/config",
28
+ "capinfo.json",
29
+ ]
30
+
31
+ # wp_symlinks are symlinked from the shared directory into the release.
32
+ # Key is the shared file; value is the target location.
33
+ # :wp_symlinks overwrites :base_wp_symlinks; an empty target location
34
+ # means to skip linking the file.
35
+ _cset :base_wp_symlinks, {
36
+ "cache" => "wp-content/cache",
37
+ "uploads" => "wp-content/uploads",
38
+ "blogs.dir" => "wp-content/blogs.dir",
39
+ }
40
+
41
+ _cset :wp_symlinks, {}
42
+
43
+ # wp_configs are copied from the shared directory into the release.
44
+ # Key is the shared file; value is the target location.
45
+ # :wp_configs overwrites :base_wp_configs; an empty target location
46
+ # means to skip copying the file.
47
+ _cset :base_wp_configs, {
48
+ "db-config.php" => "wp-content/",
49
+ "advanced-cache.php" => "wp-content/",
50
+ "object-cache.php" => "wp-content/",
51
+ "*.html" => "/",
52
+ }
53
+
54
+ _cset :wp_configs, {}
55
+
56
+ # stage_specific_overrides are uploaded from the repo's config
57
+ # directory, if they exist for that stage. Files are named
58
+ # 'STAGE-filename.txt' locally and copied to 'filename.txt'
59
+ # on deploy for that stage.
60
+ # Key is the local file; value is the target location.
61
+ # :stage_specific_overrides overwrites :base_stage_specific_overrides;
62
+ # an empty target location means to skip uploading the file.
63
+ _cset :base_stage_specific_overrides, {
64
+ "local-config.php" => "local-config.php",
65
+ ".htaccess" => ".htaccess"
66
+ }
67
+
68
+ _cset :stage_specific_overrides, {}
69
+
70
+ before "deploy:finalize_update", "cf:wordpress:generate_config"
71
+ after "deploy:finalize_update", "cf:wordpress:touch_release"
72
+ after "cf:wordpress:generate_config", "cf:wordpress:link_symlinks"
73
+ after "cf:wordpress:link_symlinks", "cf:wordpress:copy_configs"
74
+ after "cf:wordpress:copy_configs", "cf:wordpress:install"
75
+ after "cf:wordpress:install", "cf:wordpress:do_stage_specific_overrides"
76
+ namespace :wordpress do
77
+
78
+ namespace :install do
79
+
80
+ desc <<-DESC
81
+ [internal] Installs WordPress with a remote svn cache
82
+ DESC
83
+ task :with_remote_cache, :except => { :no_release => true } do
84
+ wp = fetch(:wordpress_version, "trunk")
85
+ wp_target = fetch(:wp_path, release_path)
86
+ wp_stage = File.join(shared_path, "wordpress", wp)
87
+ # check out cache of wordpress code
88
+ run Shellwords::shelljoin(["test", "-e", wp_stage]) +
89
+ " || " + Shellwords::shelljoin(["svn", "co", "-q", "http://core.svn.wordpress.org/" + wp, wp_stage])
90
+ # update branches or trunk (no need to update tags)
91
+ run Shellwords::shelljoin(["svn", "up", "--force", "-q", wp_stage]) unless wp.start_with?("tags/")
92
+ # ensure a clean copy
93
+ run Shellwords::shelljoin(["svn", "revert", "-R", "-q", wp_stage])
94
+ # trailingslashit for rsync
95
+ wp_stage << '/' unless wp_stage[-1..-1] == '/'
96
+ # push wordpress into the right place (release_path by default, could be #{release_path}/wp)
97
+ run Shellwords::shelljoin(["rsync", "--exclude=.svn", "--ignore-existing", "-a", wp_stage, wp_target])
98
+ end
99
+
100
+ desc <<-DESC
101
+ [internal] Installs WordPress with a local svn cache/copy, compressing and uploading a snapshot
102
+ DESC
103
+ task :with_copy, :except => { :no_release => true } do
104
+ wp = fetch(:wordpress_version, "trunk")
105
+ wp_target = fetch(:wp_path, release_path)
106
+ Dir.mktmpdir do |tmp_dir|
107
+ tmpdir = fetch(:cf_database_store, tmp_dir)
108
+ wp = fetch(:wordpress_version, "trunk")
109
+ Dir.chdir(tmpdir) do
110
+ if !(wp.start_with?("tags/") || wp.start_with?("branches/") || wp == "trunk")
111
+ wp = "branches/#{wp}"
112
+ end
113
+ wp_stage = File.join(tmpdir, "wordpress", wp)
114
+ ["branches", "tags"].each do |wpsvntype|
115
+ system Shellwords::shelljoin(["mkdir", "-p", File.join(tmpdir, "wordpress", wpsvntype)])
116
+ end
117
+
118
+ puts "Getting WordPress #{wp} to #{wp_stage}"
119
+ system Shellwords::shelljoin(["test", "-e", wp_stage]) +
120
+ " || " + Shellwords::shelljoin(["svn", "co", "-q", "http://core.svn.wordpress.org/" + wp, wp_stage])
121
+ system Shellwords::shelljoin(["svn", "up", "--force", "-q", wp_stage]) unless wp.start_with?("tags/")
122
+ system Shellwords::shelljoin(["svn", "revert", "-R", "-q", wp_stage])
123
+ wp_stage << '/' unless wp_stage[-1..-1] == '/'
124
+ Dir.mktmpdir do |copy_dir|
125
+ comp = Struct.new(:extension, :compress_command, :decompress_command)
126
+ remote_tar = fetch(:copy_remote_tar, 'tar')
127
+ local_tar = fetch(:copy_local_tar, 'tar')
128
+ type = fetch(:copy_compression, :gzip)
129
+ compress = case type
130
+ when :gzip, :gz then comp.new("tar.gz", [local_tar, '-c -z --exclude .svn -f'], [remote_tar, '-x -k -z -f'])
131
+ when :bzip2, :bz2 then comp.new("tar.bz2", [local_tar, '-c -j --exclude .svn -f'], [remote_tar, '-x -k -j -f'])
132
+ when :zip then comp.new("zip", %w(zip -qyr), %w(unzip -q))
133
+ else raise ArgumentError, "invalid compression type #{type.inspect}"
134
+ end
135
+ compressed_filename = "wp-" + File.basename(fetch(:release_path)) + "." + compress.extension
136
+ local_file = File.join(copy_dir, compressed_filename)
137
+ puts "Compressing #{wp_stage} to #{local_file}"
138
+ Dir.chdir(wp_stage) do
139
+ system([compress.compress_command, local_file, '.'].join(' '))
140
+ end
141
+ remote_file = File.join(fetch(:copy_remote_dir, '/tmp'), File.basename(local_file))
142
+ puts "Pushing #{local_file} to #{remote_file} to deploy"
143
+ upload(local_file, remote_file)
144
+ wp_target = fetch(:wp_path, fetch(:release_path))
145
+ run("mkdir -p #{wp_target} && cd #{wp_target} && (#{compress.decompress_command.join(' ')} #{remote_file} || echo 'tar errors for normal conditions') && rm #{remote_file}")
146
+ end
147
+
148
+ end
149
+ end
150
+ end
151
+
152
+ desc <<-DESC
153
+ [internal] Installs WordPress to the application deploy point
154
+ DESC
155
+ task :default, :except => { :no_release => true } do
156
+ if fetch(:strategy).class <= Capistrano::Deploy::Strategy.new(:remote).class
157
+ with_remote_cache
158
+ else
159
+ with_copy
160
+ end
161
+ end
162
+ end
163
+
164
+ desc <<-DESC
165
+ [internal] (currently unused) Generate config files if appropriate
166
+ DESC
167
+ task :generate_config, :except => { :no_release => true } do
168
+ # live config lives in wp-config.php; dev config loaded with local-config.php
169
+ # this method does nothing for now
170
+ end
171
+
172
+ desc <<-DESC
173
+ [internal] Symlinks specified files (usually uploads/blogs.dir/cache directories)
174
+ DESC
175
+ task :link_symlinks, :except => { :no_release => true } do
176
+ symlinks = _combine_filehash(fetch(:base_wp_symlinks), fetch(:wp_symlinks))
177
+ symlinks.each do |src, targ|
178
+ next if targ.nil? || targ == false || targ.empty?
179
+ src = File.join(shared_path, src) unless src.include?(shared_path)
180
+ targ = File.join(release_path, targ) unless targ.include?(release_path)
181
+ run [
182
+ Shellwords::shelljoin(["test", "-e", src]),
183
+ Shellwords::shelljoin(["test", "-d", targ]),
184
+ Shellwords::shelljoin(["rm", "-rf", targ])
185
+ ].join(' && ') + " || true"
186
+ run Shellwords::shelljoin(["test", "-e", src]) + " && " + Shellwords::shelljoin(["ln", "-nsf", src, targ]) + " || true"
187
+ end
188
+ end
189
+
190
+ desc <<-DESC
191
+ [internal] Copies specified files (usually advanced-cache, object-cache, db-config)
192
+ DESC
193
+ task :copy_configs, :except => { :no_release => true } do
194
+ configs = _combine_filehash(fetch(:base_wp_configs), fetch(:wp_configs))
195
+ configs.each do |src, targ|
196
+ next if targ.nil? || targ == false || targ.empty?
197
+ src = File.join(shared_path, src) unless src.include?(shared_path)
198
+ targ = File.join(release_path, targ) unless targ.include?(release_path)
199
+ run "ls -d #{src} >/dev/null 2>&1 && cp -urp #{src} #{targ} || true"
200
+ #run Shellwords::shelljoin(["test", "-e", src]) + " && " + Shellwords::shelljoin(["cp", "-rp", src, targ]) + " || true"
201
+ end
202
+ end
203
+
204
+ desc <<-DESC
205
+ [internal] Pushes up local-config.php, .htaccess, others if they exist for that stage
206
+ DESC
207
+ task :do_stage_specific_overrides, :except => { :no_release => true } do
208
+ next unless fetch(:stage, false)
209
+ overrides = _combine_filehash(fetch(:base_stage_specific_overrides), fetch(:stage_specific_overrides))
210
+ overrides.each do |src, targ|
211
+ next if targ.nil? || targ == false || targ.empty?
212
+ src = File.join("config", "#{stage}-#{src}")
213
+ targ = File.join(release_path, targ) unless targ.include?(release_path)
214
+ if File.exist?(src)
215
+ upload(src, targ)
216
+ end
217
+ end
218
+ end
219
+
220
+ desc <<-DESC
221
+ [internal] Ensure the release path has an updated modified time for deploy:cleanup
222
+ DESC
223
+ task :touch_release, :except => { :no_release => true } do
224
+ run "touch '#{release_path}'"
225
+ end
226
+ end
227
+
228
+ #===========================================================================
229
+ # util / debugging code
230
+
231
+ namespace :debugging do
232
+
233
+ namespace :release_info do
234
+ desc <<-DESC
235
+ [internal] Debugging info about releases.
236
+ DESC
237
+
238
+ task :default do
239
+ %w{releases_path shared_path current_path release_path releases previous_release current_revision latest_revision previous_revision latest_release}.each do |var|
240
+ puts "#{var}: #{eval(var)}"
241
+ end
242
+ end
243
+ end
244
+ end
245
+ end
246
+ end
247
+
@@ -0,0 +1,4 @@
1
+ require 'crowdfavorite'
2
+ module CrowdFavorite::Tasks
3
+ end
4
+
@@ -0,0 +1,3 @@
1
+ module CrowdFavorite
2
+ VERSION = '0.2.0'
3
+ end
@@ -0,0 +1,13 @@
1
+ # Default set of tasks for wordpress deploys, including all the before/after bindings
2
+
3
+ require 'crowdfavorite/tasks/wordpress'
4
+ require 'crowdfavorite/tasks/localchanges'
5
+
6
+ module CrowdFavorite::WordPress
7
+ extend CrowdFavorite::Support::Namespace
8
+ namespace :cf do
9
+ after "deploy:finalize_update", "cf:localchanges:snapshot_deploy"
10
+ before "deploy", "cf:localchanges:compare"
11
+ end
12
+ end
13
+
@@ -0,0 +1,9 @@
1
+ require 'rubygems'
2
+
3
+ module CrowdFavorite
4
+ module Support
5
+ autoload :Namespace, 'crowdfavorite/support/namespace'
6
+ end
7
+ autoload :Version, 'crowdfavorite/version'
8
+ end
9
+
data/spec/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color