k3_capistrano 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/Rakefile +1 -0
- data/Readme.md +86 -0
- data/k3_capistrano.gemspec +30 -0
- data/lib/capistrano_database_yml.rb +142 -0
- data/lib/k3/capistrano.rb +2 -0
- data/lib/k3_capistrano.rb +5 -0
- data/lib/k3_capistrano/asset_pipeline.rb +316 -0
- data/lib/k3_capistrano/capistrano.rb +132 -0
- data/lib/k3_capistrano/chef.rb +41 -0
- data/lib/k3_capistrano/db.rb +72 -0
- data/lib/k3_capistrano/db_backups.rb +207 -0
- data/lib/k3_capistrano/ensure_db_is_set_up.rb +34 -0
- data/lib/k3_capistrano/git.rb +37 -0
- data/lib/k3_capistrano/logs.rb +20 -0
- data/lib/k3_capistrano/ssh.rb +14 -0
- data/lib/k3_capistrano/test_request.rb +16 -0
- data/lib/k3_capistrano/test_results.rb +85 -0
- data/lib/k3_capistrano/unicorn.rb +30 -0
- data/lib/k3_capistrano/version.rb +5 -0
- data/lib/templates/database.yml.erb +8 -0
- data/lib/test_result_logging.rb +79 -0
- metadata +164 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/Readme.md
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
k3_capistrano
|
2
|
+
=============
|
3
|
+
|
4
|
+
The k3_capistrano gem sets some reasonable defaults so that your app's config/deploy* files can be
|
5
|
+
very minimal and ideally only need to include any app-specific configuration.
|
6
|
+
|
7
|
+
It also adds some additional deploy tasks such as deploy:test_request (see below for a list).
|
8
|
+
|
9
|
+
Getting started
|
10
|
+
===============
|
11
|
+
|
12
|
+
Add k3_capistrano to your Gemfile and add something like this to your config/deploy.rb:
|
13
|
+
|
14
|
+
set :application, 'app_name'
|
15
|
+
set :repository, "user@server:#{application}.git"
|
16
|
+
|
17
|
+
require 'k3_capistrano/capistrano'
|
18
|
+
|
19
|
+
Add your staging/production -server-specific config in config/deploy/staging.rb /
|
20
|
+
config/deploy/production.rb, respectively. For example:
|
21
|
+
|
22
|
+
set :domain, 'my.domain.org'
|
23
|
+
|
24
|
+
To deploy, you just do the usual:
|
25
|
+
|
26
|
+
cap deploy # defaults to staging
|
27
|
+
|
28
|
+
or to deploy to production:
|
29
|
+
|
30
|
+
cap production deploy
|
31
|
+
|
32
|
+
If your app needs to, it can override any of the defaults, for example these:
|
33
|
+
|
34
|
+
set :deploy_to, "/apps/#{application}"
|
35
|
+
set :branch, 'master' # The git branch to deploy. Could also be set in config/deploy/#{stage}.rb
|
36
|
+
|
37
|
+
Integrating with Chef
|
38
|
+
=====================
|
39
|
+
|
40
|
+
If you are using Chef and would like capistrano to simply use the configuration that you've already
|
41
|
+
defined for this app using Chef, you can follow this alternate instructions to avoid duplicating
|
42
|
+
that configuration data in two places...
|
43
|
+
|
44
|
+
Set the chef_home environment variable to point to your copy of K3's deployment/kitchen/.
|
45
|
+
|
46
|
+
Just add a line like this to your .bashrc and source your .bashrc:
|
47
|
+
|
48
|
+
export chef_home=/path/to/deployment/kitchen/
|
49
|
+
|
50
|
+
Add k3_capistrano to your Gemfile and add something like this to your config/deploy.rb:
|
51
|
+
|
52
|
+
set :application, 'app_name'
|
53
|
+
require 'k3_capistrano/chef'
|
54
|
+
require 'k3_capistrano/capistrano'
|
55
|
+
|
56
|
+
|
57
|
+
Tasks provided by k3_capistrano
|
58
|
+
===============================
|
59
|
+
|
60
|
+
cap backup:create # Back up the database
|
61
|
+
cap backup:download # Download the latest backup
|
62
|
+
cap backup:locate_last # Locate the latest backup
|
63
|
+
cap backup:restore # Restore a database backup file.
|
64
|
+
cap backup:upload_backup # Upload a database backup file to the ...
|
65
|
+
cap db:database_yml:setup # Creates the database.yml file under t...
|
66
|
+
cap db:pull # Pull data from remote database to you...
|
67
|
+
cap db:pull_to_staging # Pull data from production database to...
|
68
|
+
cap db:setup # Run the db:setup rake task.
|
69
|
+
cap db:taps_server # This uses your database config from y...
|
70
|
+
cap deploy:test_request # Does a test request to the web site s...
|
71
|
+
cap git:check_if_needs_push # Checks to see if your branch is ahead...
|
72
|
+
cap git:push # Pushes your local commits to the git ...
|
73
|
+
cap logs:tail # tail all 4 app log files to help you ...
|
74
|
+
cap logs:watch # tail -f the production.log file
|
75
|
+
|
76
|
+
Skipping steps
|
77
|
+
==============
|
78
|
+
|
79
|
+
This deploy script (and its before/after hooks) will take care of doing everything, including
|
80
|
+
running db:migrate and precompiling your assets.
|
81
|
+
|
82
|
+
Since some of those steps aren't necessary for every signle deploy and can make your deploy take
|
83
|
+
longer than it needs to, you can skip some of those steps by setting the skip environment variable
|
84
|
+
when you run cap deploy. For example, to skip the migrate and assets steps, just run this:
|
85
|
+
|
86
|
+
skip=assets,migrate cap production deploy
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "k3_capistrano/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "k3_capistrano"
|
7
|
+
s.version = K3Capistrano.version
|
8
|
+
s.authors = ["Tyler Rick"]
|
9
|
+
s.email = ["tyler@k3integrations.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{Common Capistrano tasks and defaults used by K3 Integrations}
|
12
|
+
s.description = s.summary
|
13
|
+
|
14
|
+
s.rubyforge_project = "k3_capistrano"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_runtime_dependency 'parseconfig'
|
22
|
+
s.add_runtime_dependency 'capistrano-ext'
|
23
|
+
s.add_runtime_dependency 'capistrano_colors'
|
24
|
+
s.add_runtime_dependency 'facets'
|
25
|
+
s.add_runtime_dependency 'rvm-capistrano'
|
26
|
+
s.add_runtime_dependency 'colored'
|
27
|
+
|
28
|
+
#s.add_runtime_dependency 'tylerrick-chef'
|
29
|
+
#s.add_runtime_dependency 'capistrano-chef'
|
30
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
#
|
2
|
+
# = Capistrano database.yml task
|
3
|
+
#
|
4
|
+
# Provides a couple of tasks for creating the database.yml
|
5
|
+
# configuration file dynamically when deploy:setup is run.
|
6
|
+
#
|
7
|
+
# Category:: Capistrano
|
8
|
+
# Package:: Database
|
9
|
+
# Author:: Simone Carletti <weppos@weppos.net>
|
10
|
+
# Copyright:: 2007-2010 The Authors
|
11
|
+
# License:: MIT License
|
12
|
+
# Link:: http://www.simonecarletti.com/
|
13
|
+
# Source:: http://gist.github.com/2769
|
14
|
+
#
|
15
|
+
#
|
16
|
+
# == Requirements
|
17
|
+
#
|
18
|
+
# This extension requires the original <tt>config/database.yml</tt> to be excluded
|
19
|
+
# from source code checkout. You can easily accomplish this by renaming
|
20
|
+
# the file (for example to database.example.yml) and appending <tt>database.yml</tt>
|
21
|
+
# value to your SCM ignore list.
|
22
|
+
#
|
23
|
+
# # Example for Subversion
|
24
|
+
#
|
25
|
+
# $ svn mv config/database.yml config/database.example.yml
|
26
|
+
# $ svn propset svn:ignore 'database.yml' config
|
27
|
+
#
|
28
|
+
# # Example for Git
|
29
|
+
#
|
30
|
+
# $ git mv config/database.yml config/database.example.yml
|
31
|
+
# $ echo 'config/database.yml' >> .gitignore
|
32
|
+
#
|
33
|
+
#
|
34
|
+
# == Usage
|
35
|
+
#
|
36
|
+
# Include this file in your <tt>deploy.rb</tt> configuration file.
|
37
|
+
# Assuming you saved this recipe as capistrano_database_yml.rb:
|
38
|
+
#
|
39
|
+
# require "capistrano_database_yml"
|
40
|
+
#
|
41
|
+
# Now, when <tt>deploy:setup</tt> is called, this script will automatically
|
42
|
+
# create the <tt>database.yml</tt> file in the shared folder.
|
43
|
+
# Each time you run a deploy, this script will also create a symlink
|
44
|
+
# from your application <tt>config/database.yml</tt> pointing to the shared configuration file.
|
45
|
+
#
|
46
|
+
# == Custom template
|
47
|
+
#
|
48
|
+
# By default, this script creates an exact copy of the default
|
49
|
+
# <tt>database.yml</tt> file shipped with a new Rails 2.x application.
|
50
|
+
# If you want to overwrite the default template, simply create a custom Erb template
|
51
|
+
# called <tt>database.yml.erb</tt> and save it into <tt>config/deploy</tt> folder.
|
52
|
+
#
|
53
|
+
# Although the name of the file can't be changed, you can customize the directory
|
54
|
+
# where it is stored defining a variable called <tt>:template_dir</tt>.
|
55
|
+
#
|
56
|
+
# # store your custom template at foo/bar/database.yml.erb
|
57
|
+
# set :template_dir, "foo/bar"
|
58
|
+
#
|
59
|
+
# # example of database template
|
60
|
+
#
|
61
|
+
# base: &base
|
62
|
+
# adapter: sqlite3
|
63
|
+
# timeout: 5000
|
64
|
+
# development:
|
65
|
+
# database: #{shared_path}/db/development.sqlite3
|
66
|
+
# <<: *base
|
67
|
+
# test:
|
68
|
+
# database: #{shared_path}/db/test.sqlite3
|
69
|
+
# <<: *base
|
70
|
+
# production:
|
71
|
+
# adapter: mysql
|
72
|
+
# database: #{application}_production
|
73
|
+
# username: #{user}
|
74
|
+
# password: #{Capistrano::CLI.ui.ask("Enter MySQL database password: ")}
|
75
|
+
# encoding: utf8
|
76
|
+
# timeout: 5000
|
77
|
+
#
|
78
|
+
# Because this is an Erb template, you can place variables and Ruby scripts
|
79
|
+
# within the file.
|
80
|
+
# For instance, the template above takes advantage of Capistrano CLI
|
81
|
+
# to ask for a MySQL database password instead of hard coding it into the template.
|
82
|
+
#
|
83
|
+
# === Password prompt
|
84
|
+
#
|
85
|
+
# For security reasons, in the example below the password is not
|
86
|
+
# hard coded (or stored in a variable) but asked on setup.
|
87
|
+
# I don't like to store passwords in files under version control
|
88
|
+
# because they will live forever in your history.
|
89
|
+
# This is why I use the Capistrano::CLI utility.
|
90
|
+
#
|
91
|
+
|
92
|
+
unless Capistrano::Configuration.respond_to?(:instance)
|
93
|
+
abort "This extension requires Capistrano 2"
|
94
|
+
end
|
95
|
+
|
96
|
+
require 'pathname'
|
97
|
+
|
98
|
+
Capistrano::Configuration.instance.load do
|
99
|
+
|
100
|
+
namespace :db do
|
101
|
+
namespace :database_yml do
|
102
|
+
|
103
|
+
desc <<-DESC
|
104
|
+
Creates the database.yml file under the shared dir on the server.
|
105
|
+
|
106
|
+
During a deploy, the db:database_yml:symlink task will symlink from config/database.yml to
|
107
|
+
the shared file.
|
108
|
+
|
109
|
+
By default, this task uses the default template found in lib/templates/database.yml.erb of
|
110
|
+
the k3_capistrano gem unless a template called database.yml.erb is found either in your
|
111
|
+
app's config/deploy directory.
|
112
|
+
DESC
|
113
|
+
task :setup, :except => { :no_release => true } do
|
114
|
+
upload_to = "#{shared_path}/config/database.yml"
|
115
|
+
|
116
|
+
default_template = Pathname.new(__FILE__).dirname + 'templates/database.yml.erb'
|
117
|
+
|
118
|
+
override_template = Pathname.new(fetch(:template_dir, "config/deploy")) + 'database.yml.erb'
|
119
|
+
template = override_template.file? ? override_template.read : default_template.read
|
120
|
+
|
121
|
+
config = ERB.new(template).result(binding)
|
122
|
+
puts "Installing this database.yml to #{upload_to}:\n#{config}"
|
123
|
+
|
124
|
+
run "mkdir -p #{shared_path}/db"
|
125
|
+
run "mkdir -p #{shared_path}/config"
|
126
|
+
put config, upload_to
|
127
|
+
end
|
128
|
+
|
129
|
+
desc <<-DESC
|
130
|
+
[internal] Updates the symlink for database.yml file to the just deployed release.
|
131
|
+
DESC
|
132
|
+
task :symlink, :except => { :no_release => true } do
|
133
|
+
run "ln -nfs #{shared_path}/config/database.yml #{release_path}/config/database.yml"
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
before 'db:database_yml:setup', 'db:read_remote_my_cnf' # lib/templates/database.yml.erb gets password from my_cnf
|
140
|
+
after "deploy:setup", "db:database_yml:setup" unless fetch(:skip_db_setup, false)
|
141
|
+
after "deploy:finalize_update", "db:database_yml:symlink"
|
142
|
+
end
|
@@ -0,0 +1,5 @@
|
|
1
|
+
# This file will be automatically required if the k3_capistrano gem is in your
|
2
|
+
# Gemfile, so we don't want to put anything Capistrano-dependent here in case
|
3
|
+
# they aren't loading/running cap.
|
4
|
+
#
|
5
|
+
# To actually load this into your config/deploy.rb, you need to explicitly require 'k3_capistrano/capistrano' instead.
|
@@ -0,0 +1,316 @@
|
|
1
|
+
Capistrano::Configuration.instance(:must_exist).load do
|
2
|
+
|
3
|
+
# From Rails 3.1 and later, we should assume they want to use the asset pipeline. Can be disabled by setting to false.
|
4
|
+
_cset :asset_pipeline, true
|
5
|
+
|
6
|
+
if asset_pipeline
|
7
|
+
#===================================================================================================
|
8
|
+
# Standard recipe
|
9
|
+
|
10
|
+
load 'deploy/assets'
|
11
|
+
# The assets recipes that are included with capistrano work like this:
|
12
|
+
# 1. deploy:assets:symlink creates a symlink from #{latest_release}/public/assets to #{shared_path}/assets
|
13
|
+
# 2. Runs rake assets:precompile on the server
|
14
|
+
#
|
15
|
+
# We will make use of #1, but override #2 as needed to also allow assets to precompiled locally on
|
16
|
+
# the developer's localhost.
|
17
|
+
|
18
|
+
#===================================================================================================
|
19
|
+
# More libraries and requires
|
20
|
+
|
21
|
+
require 'pp'
|
22
|
+
require 'facets/class/to_proc'
|
23
|
+
require 'facets/string/cleanlines'
|
24
|
+
require 'colored'
|
25
|
+
|
26
|
+
# We can just get the digests from the manifest.yml instead of recomputing them!
|
27
|
+
# Also, when I had it recomputing the digest of every file, it would give false positives for a
|
28
|
+
# bunch of .gz files. Apparently, gzipping the same file twice can produce a file with a different
|
29
|
+
# digest each time? Anyway, if the source file that the .gz file was produced to hasn't changed, we
|
30
|
+
# don't care if the .gz file itself is not identical.
|
31
|
+
#Pathname.class_eval do
|
32
|
+
# def digest
|
33
|
+
# Digest::MD5.file(@path).hexdigest
|
34
|
+
# end
|
35
|
+
#end
|
36
|
+
class K3Capistrano::AssetManifest
|
37
|
+
attr_reader :digests
|
38
|
+
def initialize(manifest_file)
|
39
|
+
@manifest_file = manifest_file
|
40
|
+
@digests = YAML.load_file(manifest_file)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Compare to digest_for actionpack-3.2.11/lib/sprockets/helpers/rails_helper.rb
|
44
|
+
# We could just reuse that, if we were okay with it loading the Rails application (in order to get
|
45
|
+
# Rails.application.config.assets)
|
46
|
+
def digest_for!(logical_path)
|
47
|
+
digest_for(logical_path) or (raise "#{logical_path} not found in #{@manifest_file}" if logical_path =~ /\.js$|\.css$/)
|
48
|
+
end
|
49
|
+
|
50
|
+
def digest_for(logical_path)
|
51
|
+
#puts %(@digests[#{logical_path}]=#{(@digests[logical_path]).inspect})
|
52
|
+
@digests[logical_path]
|
53
|
+
end
|
54
|
+
|
55
|
+
def to_s
|
56
|
+
@manifest_file.to_s
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
#===================================================================================================
|
61
|
+
# Variables
|
62
|
+
|
63
|
+
_cset :precompile_assets_locally, false
|
64
|
+
# The following options only apply if precompile_assets_locally is true:
|
65
|
+
(
|
66
|
+
# Set skip to include 'local_precompile' to skip running assets:precompile locally during this deploy.
|
67
|
+
# You may want to do this to speed up the deploy if you know that your local assets directory is
|
68
|
+
# already current -- for example, if you just deployed to staging (and did the precompile during
|
69
|
+
# that deploy) and now you are ready to deploy the exact same code to production.
|
70
|
+
|
71
|
+
# Can also set to :previous_local
|
72
|
+
#_cset :compare_to_manifest_from, :remote
|
73
|
+
|
74
|
+
# If true, keeps a local public/assets; if false, renames it immediately after running precompile
|
75
|
+
# It is recommended to set this to false, because if a public/assets dir exists in development mode,
|
76
|
+
# changes to your assets files will *not* take effect until you run precompile, whereas if there is
|
77
|
+
# *no* public/assets dir, changes will be compiled automatically as needed and take immediately
|
78
|
+
# after changing them.
|
79
|
+
set :keep_public_assets_dir, false
|
80
|
+
|
81
|
+
# TODO: How many backups of public/assets to keep
|
82
|
+
set :local_asset_dirs_to_keep, 5
|
83
|
+
|
84
|
+
# Set to false if you don't want it to show the list of changed assets during a deploy.
|
85
|
+
set :list_changed_assets_during_deploy, true
|
86
|
+
)
|
87
|
+
|
88
|
+
#===================================================================================================
|
89
|
+
namespace :deploy do
|
90
|
+
namespace :assets do
|
91
|
+
|
92
|
+
# If you already know that no changes have been made to your assets since your last deploy, you
|
93
|
+
# can speed up the deploy by several minutes by skipping the precompilation step and just reusing
|
94
|
+
# the assets in shared/assets that were used by the previous deploy.
|
95
|
+
#
|
96
|
+
# Usage: skip=assets cap deploy
|
97
|
+
#
|
98
|
+
if skip.include?('assets')
|
99
|
+
task :precompile, :roles => :app, :except => { :no_release => false } do
|
100
|
+
# Do nothing.
|
101
|
+
puts "Warning: Skipping the 'deploy:assets:precompile' task. Make sure the assets in shared/assets are current."
|
102
|
+
end
|
103
|
+
|
104
|
+
else
|
105
|
+
if precompile_assets_locally
|
106
|
+
# This strategy precompiles your assets locally and then copies the files to the remote server.
|
107
|
+
# This way you can deploy to multiple servers in a row without having to recompile the assets each time,
|
108
|
+
# and you can redeploy to the same server without recompiling as long as no assets have changed.
|
109
|
+
# You must set :precompile_assets_locally, true if you want to opt in to using this.
|
110
|
+
#
|
111
|
+
# Overview:
|
112
|
+
# * Downloads current manifest from shared/assets/manifest.yml on server.
|
113
|
+
# * For each file in local public/assets dir (or most recent timestamped dir):
|
114
|
+
# * If local file not present in currently deployed manifest, upload it
|
115
|
+
# * If local file has different digest, upload it
|
116
|
+
# Do incremental update instead of rsyncing all
|
117
|
+
|
118
|
+
# List all asset dirs (dirs matching public/assets.*), sorted by filename, except always returning
|
119
|
+
# public/assets last if it exists.
|
120
|
+
def local_asset_dirs
|
121
|
+
(
|
122
|
+
Dir["public/assets.*"].map(&Pathname).sort +
|
123
|
+
[Pathname.new('public/assets')]
|
124
|
+
).select(&:directory?).select(&:exist?).to_a
|
125
|
+
end
|
126
|
+
|
127
|
+
task :list_local_asset_dirs do
|
128
|
+
puts local_asset_dirs
|
129
|
+
end
|
130
|
+
|
131
|
+
def changed_from(previous_file_list, previous_manifest, verbose = false)
|
132
|
+
current_dir = local_asset_dirs.last or raise "no local public/assets* dirs exist"
|
133
|
+
current_manifest = K3Capistrano::AssetManifest.new(current_dir + 'manifest.yml')
|
134
|
+
|
135
|
+
if verbose
|
136
|
+
puts "Listing changes between '#{previous_manifest}' (previous) and '#{current_manifest}' (latest local precompile, at '#{current_dir}')..."
|
137
|
+
puts "Legend: " + "doesn't exist (in dir of '#{previous_manifest}')".cyan + ' | ' + "exists but has changed since '#{previous_manifest}'".yellow
|
138
|
+
end
|
139
|
+
|
140
|
+
descendants = []; current_dir.find {|_| descendants << _ if _.file? }
|
141
|
+
descendants.select do |file_in_current|
|
142
|
+
base_name = file_in_current.relative_path_from(current_dir).to_s.gsub(%r<^../>, '')
|
143
|
+
logical_path = base_name.gsub(/-\w{32}/, '').gsub(/\.gz$/, '')
|
144
|
+
#file_in_previous = previous_dir + base_name if previous_dir
|
145
|
+
|
146
|
+
# Only if the file *exists* in and is in the manifest can we skip it.
|
147
|
+
# This is a safeguard against the case where the manifest.yml file gets uploaded but the
|
148
|
+
# files themselves haven't yet.
|
149
|
+
|
150
|
+
file_in_previous = previous_file_list.include?(base_name) && previous_manifest.digest_for(logical_path)
|
151
|
+
changed =
|
152
|
+
if file_in_previous #&& file_in_previous.exist?
|
153
|
+
if previous_manifest.digest_for!(logical_path) == current_manifest.digest_for!(logical_path)
|
154
|
+
color = :green
|
155
|
+
false
|
156
|
+
else
|
157
|
+
color = :yellow
|
158
|
+
true
|
159
|
+
end
|
160
|
+
else
|
161
|
+
color = :cyan
|
162
|
+
true
|
163
|
+
end
|
164
|
+
puts file_in_current.to_s.send(color) if verbose && changed
|
165
|
+
changed
|
166
|
+
end.map do |_|
|
167
|
+
_.relative_path_from(current_dir).to_s.gsub(%r<^../>, '')
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def changed_since_previous_local_precompile(verbose = false)
|
172
|
+
# TODO: It is also possible for there to be no previous assets dir, in which case it should
|
173
|
+
# list *all* files in latest_dir as being new files.
|
174
|
+
previous_dir = local_asset_dirs[-2]
|
175
|
+
previous_file_list = []; previous_dir.find {|_| previous_file_list << _ if _.file? }
|
176
|
+
latest_dir = local_asset_dirs.last
|
177
|
+
puts "Listing changes between '#{previous_dir}' and latest local precompile, '#{latest_dir}'..." if verbose
|
178
|
+
previous_manifest = K3Capistrano::AssetManifest.new(previous_dir + 'manifest.yml')
|
179
|
+
changed_from(previous_file_list, previous_manifest, verbose)
|
180
|
+
end
|
181
|
+
|
182
|
+
def remote_file_list
|
183
|
+
capture("cd #{shared_path}/assets && find . -type f").
|
184
|
+
cleanlines.map {|_| _.gsub(%r<^\./>, '') }
|
185
|
+
end
|
186
|
+
|
187
|
+
def changed_since_last_deploy(verbose = false)
|
188
|
+
remote_manifest = 'tmp/manifest_from_last_deploy.yml'
|
189
|
+
get "#{shared_path}/assets/manifest.yml", remote_manifest, via: :scp
|
190
|
+
remote_manifest = K3Capistrano::AssetManifest.new(remote_manifest)
|
191
|
+
remote_file_list = remote_file_list()
|
192
|
+
Pathname('tmp/asset_file_from_last_deploy.txt').tap do |file|
|
193
|
+
puts %(Saving list of files from server at: #{file})
|
194
|
+
file.open('w') {|_| _.puts remote_file_list.join("\n" )}
|
195
|
+
end
|
196
|
+
puts "Listing changes between latest deploy and latest local precompile..." if verbose
|
197
|
+
changed_from(remote_file_list, remote_manifest, verbose)
|
198
|
+
end
|
199
|
+
|
200
|
+
def changed_assets(verbose = false)
|
201
|
+
#case compare_to_manifest_from
|
202
|
+
#when :remote
|
203
|
+
changed_since_last_deploy(verbose)
|
204
|
+
#when :previous_local
|
205
|
+
# changed_since_previous_local_precompile(verbose)
|
206
|
+
#else
|
207
|
+
# raise "unrecognized value for compare_to_manifest_from: #{compare_to_manifest_from.inspect}"
|
208
|
+
#end
|
209
|
+
end
|
210
|
+
|
211
|
+
task :list_changed_since_previous_local_precompile do
|
212
|
+
puts changed_since_previous_local_precompile(verbose = true)
|
213
|
+
end
|
214
|
+
|
215
|
+
task :list_changed_assets do
|
216
|
+
changed_assets(verbose = true)
|
217
|
+
end
|
218
|
+
|
219
|
+
task :list_changed_since_last_deploy do
|
220
|
+
changed_since_last_deploy(verbose = true)
|
221
|
+
end
|
222
|
+
|
223
|
+
task :push_changed_assets do
|
224
|
+
current_dir = local_asset_dirs.last or raise "no local public/assets* dirs exist"
|
225
|
+
changed_files = changed_assets(verbose = list_changed_assets_during_deploy)
|
226
|
+
|
227
|
+
#remote_host_and_path = "#{user}@#{domain}:#{release_or_current_path.gsub('~/', './')}/public/assets/"
|
228
|
+
remote_host_and_path = "#{user}@#{domain}:#{shared_path}/assets/"
|
229
|
+
|
230
|
+
changed_files_in_root, changed_files_in_subdir = changed_files.partition {|_|
|
231
|
+
Pathname.new(_).dirname.to_s == '.'
|
232
|
+
}
|
233
|
+
if changed_files_in_root.any?
|
234
|
+
# TODO: shell escape arguments
|
235
|
+
command = "time scp " +
|
236
|
+
"#{changed_files_in_root.map {|_| "#{current_dir}/#{_}" }.join(' ')} " +
|
237
|
+
"#{remote_host_and_path}"
|
238
|
+
#command = "rsync -a --itemize-changes --recursive --compress --times #{changed_files} #{remote_host_and_path}"
|
239
|
+
run_locally command
|
240
|
+
|
241
|
+
# See also this version from https://groups.google.com/forum/#!topic/capistrano/cuOeI-aNLfo
|
242
|
+
# servers = find_servers_for_task(current_task)
|
243
|
+
# port_option = port ? " -e 'ssh -p #{port}' " : ''
|
244
|
+
# servers.each do |server|
|
245
|
+
# run_locally("rsync --recursive --times --rsh=ssh --compress --human-readable #{port_option} --progress public/assets #{user} @#{server}:#{shared_path}")
|
246
|
+
# end
|
247
|
+
end
|
248
|
+
|
249
|
+
remote_dir_list = remote_file_list().map {|_| Pathname(_).dirname }.uniq
|
250
|
+
changed_files_in_subdir.each do |_|
|
251
|
+
# mkdir subdir if it doesn't exist on server. (Check in remote_file_list so we don't
|
252
|
+
# have to hit the server to check each file individually.)
|
253
|
+
dir = Pathname(_).dirname
|
254
|
+
unless remote_dir_list.include? dir
|
255
|
+
remote_dir_list << dir
|
256
|
+
run "mkdir -p #{shared_path}/assets/#{dir}"
|
257
|
+
end
|
258
|
+
|
259
|
+
#put "#{current_dir}/#{_}", "#{shared_path}/assets/#{_}", via: :scp
|
260
|
+
command = "scp #{"#{current_dir}/#{_}"} #{remote_host_and_path}#{_}"
|
261
|
+
run_locally command
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
def skip_local_precompile?
|
266
|
+
skip.include?('local_precompile')
|
267
|
+
end
|
268
|
+
|
269
|
+
def local_asset_current?
|
270
|
+
# TODO: Compare time of last commit to app/assets or vendor/assets with time of last local
|
271
|
+
# precompile. Return true if there have been any changes since the last precompile that
|
272
|
+
# wouldn't be included in the last precompile.
|
273
|
+
#
|
274
|
+
#local_asset_dirs.any?
|
275
|
+
skip_local_precompile?
|
276
|
+
end
|
277
|
+
|
278
|
+
task :precompile_locally do
|
279
|
+
run_locally "time bundle exec rake assets:precompile RAILS_ENV=production RAILS_GROUPS=assets"
|
280
|
+
after_local_precompile
|
281
|
+
end
|
282
|
+
|
283
|
+
task :after_local_precompile do
|
284
|
+
unless keep_public_assets_dir
|
285
|
+
latest_dir = "public/assets.#{Time.now.strftime("%Y-%m-%dT%H.%M.%S")}"
|
286
|
+
# Move them out of the way locally, so that in development it won't try to use (possibly stale) assets from public/assets instead of dynamically generating them
|
287
|
+
run_locally "mv public/assets #{latest_dir}"
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
task :precompile, :roles => :app, :except => { :no_release => false } do
|
292
|
+
if local_asset_current?
|
293
|
+
# We'll just reuse the assets that we've already previously precompiled locally.
|
294
|
+
#latest_dir = local_asset_dirs.last or raise "local_asset_dirs empty"
|
295
|
+
puts "Using existing local asset dir: #{local_asset_dirs.last}"
|
296
|
+
else
|
297
|
+
precompile_locally
|
298
|
+
end
|
299
|
+
#Pathname(latest_dir).exist? or raise "local assets dir #{latest_dir.inspect} does not exist"
|
300
|
+
|
301
|
+
push_changed_assets
|
302
|
+
|
303
|
+
# This is so that the deploy will be aborted if the previous steps failed to produce
|
304
|
+
# asset files in the target directory on the server.
|
305
|
+
run "ls #{release_or_current_path}/public/assets/*.js"
|
306
|
+
end
|
307
|
+
|
308
|
+
else
|
309
|
+
# Just use the standard recipe supplied with capistrano in 'deploy/assets'
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end # if asset_pipeline
|
316
|
+
end
|