capistrano-ext-projectdx 0.0.7 → 0.1.14
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/deploy.sh +40 -0
- data/lib/capistrano/ext/projectdx/db.rb +68 -0
- data/lib/capistrano/ext/projectdx/git_branch.rb +57 -47
- data/lib/capistrano/ext/projectdx/misc.rb +19 -8
- data/lib/capistrano/ext/projectdx/projectdx.rb +25 -17
- data/lib/capistrano/ext/projectdx/time.rb +3 -0
- data/lib/capistrano/ext/projectdx/web.rb +4 -2
- data/lib/capistrano/ext/projectdx.rb +11 -15
- data/lib/capistrano-ext-projectdx.rb +1 -0
- data/lib/database_operations.rb +68 -0
- data/lib/projectdx/tasks.rb +87 -0
- data/lib/rf_filtering.rb +64 -0
- data/lib/sql_obfuscator.rb +74 -0
- metadata +18 -7
- data/lib/capistrano/ext/projectdx/all.rb +0 -14
- data/lib/capistrano/ext/projectdx/campfire.rb +0 -62
data/bin/deploy.sh
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
#Must fetch to retrieve most recent tags
|
3
|
+
|
4
|
+
if [ ! -d ${APP} ]; then
|
5
|
+
git clone "git@github.com:projectdx/${APP}.git"
|
6
|
+
fi
|
7
|
+
|
8
|
+
cd "${APP}"
|
9
|
+
git fetch origin
|
10
|
+
git checkout origin/accepted-work
|
11
|
+
|
12
|
+
if [ -z "${DEPLOY_VERSION}" ]; then
|
13
|
+
bundle check || bundle install --deployment --without development
|
14
|
+
export DEPLOY_VERSION=`bundle exec cap -q "${STAGE}" deploy:show_revision | egrep '^[a-f0-9]{40}.?$' | sed 's/[^a-f0-9]*//g'`
|
15
|
+
fi
|
16
|
+
|
17
|
+
if [ -z "${DEPLOY_VERSION}" ]; then
|
18
|
+
export DEPLOY_VERSION=`git tag -l "${STAGE}/*" | egrep "^${STAGE}/[0-9]{4}-[0-9][0-9]-[0-9][0-9](\.[0-9]+)?" | sort -r | head -1`
|
19
|
+
fi
|
20
|
+
|
21
|
+
if [ -z "${DEPLOY_VERSION}" ]; then
|
22
|
+
echo "No deployable version found!"
|
23
|
+
EXIT=1
|
24
|
+
else
|
25
|
+
echo "Deploying ${DEPLOY_VERSION} to ${STAGE}"
|
26
|
+
git checkout "${DEPLOY_VERSION}"
|
27
|
+
|
28
|
+
bundle check || bundle install --deployment --without development
|
29
|
+
|
30
|
+
ssh-agent > .agent
|
31
|
+
. .agent
|
32
|
+
ssh-add
|
33
|
+
|
34
|
+
bundle exec cap ${STAGE} deploy
|
35
|
+
EXIT=$?
|
36
|
+
|
37
|
+
ssh-agent -k
|
38
|
+
fi
|
39
|
+
|
40
|
+
exit $EXIT
|
@@ -0,0 +1,68 @@
|
|
1
|
+
namespace :deploy do
|
2
|
+
def with_tempfile_on_remote_server(&block)
|
3
|
+
tempfile_name = capture('mktemp -t preparedb.XXXXXXXX').strip
|
4
|
+
|
5
|
+
yield tempfile_name
|
6
|
+
ensure
|
7
|
+
run "rm #{tempfile_name}"
|
8
|
+
end
|
9
|
+
|
10
|
+
def db_connection
|
11
|
+
fetch(:db_connection, {})
|
12
|
+
end
|
13
|
+
|
14
|
+
def dbinfo
|
15
|
+
@dbinfo ||= db_defaults.merge(db_connection)
|
16
|
+
@dbinfo['database'] ||= '%s-%s' % [db_name, stage]
|
17
|
+
@dbinfo
|
18
|
+
end
|
19
|
+
|
20
|
+
def run_with_password(cmd, pwd)
|
21
|
+
once = false
|
22
|
+
run cmd do |ch, stream, data|
|
23
|
+
$stdout.write(data)
|
24
|
+
unless once
|
25
|
+
ch.send_data pwd + "\n"
|
26
|
+
$stdout.write('[not shown]')
|
27
|
+
once = true
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
namespace :db do
|
33
|
+
desc "Regenerate deployment database.yml"
|
34
|
+
task :copy_database_yml do
|
35
|
+
put({ rails_env => dbinfo }.to_yaml, "#{release_path}/config/database.yml")
|
36
|
+
end
|
37
|
+
|
38
|
+
desc "Drop and recreate stage DB"
|
39
|
+
task :create do
|
40
|
+
raise "Don't do this on production" if stage == 'production'
|
41
|
+
raise "Your database name #{dbinfo['database']} looks wrong" unless dbinfo['database'] =~ /^[\w-]+-#{stage}$/
|
42
|
+
|
43
|
+
with_tempfile_on_remote_server do |tf|
|
44
|
+
referencedb = "%s-reference" % db_name
|
45
|
+
|
46
|
+
popt = [['host', 'h'], ['username', 'U'], ['port', 'p']].
|
47
|
+
select { |key, flag| dbinfo[key] }.
|
48
|
+
map { |key, flag| '-%s "%s"' % [flag, dbinfo[key]] } * ' ' +
|
49
|
+
' -W'
|
50
|
+
|
51
|
+
sql = <<-SQL % [referencedb, dbinfo['database'], dbinfo['database']]
|
52
|
+
select killusers('%s','%s');
|
53
|
+
drop database "%s";
|
54
|
+
SQL
|
55
|
+
put sql, tf
|
56
|
+
|
57
|
+
run_with_password %{psql postgres #{popt} -a -f #{tf} 2>&1}, dbinfo['password']
|
58
|
+
run_with_password %{createdb "#{dbinfo['database']}" #{popt} -T "#{referencedb}" 2>&1}, dbinfo['password']
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
task :create_hook do
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
after 'deploy:update_code', 'deploy:db:copy_database_yml'
|
68
|
+
before 'deploy:migrate', 'deploy:db:create_hook'
|
@@ -1,61 +1,71 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
namespace :deploy do
|
2
|
+
desc "Return SHA-1 commit ID of deployed branch"
|
3
|
+
task :show_revision do
|
4
|
+
puts capture("cat #{deploy_to}/current/REVISION")
|
4
5
|
end
|
5
6
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
desc "Determine commit to use (may default to HEAD)."
|
8
|
+
task :get_revision do
|
9
|
+
def stage
|
10
|
+
variables[:stage] && variables[:stage].to_s
|
11
|
+
end
|
11
12
|
|
12
|
-
|
13
|
-
|
14
|
-
if %x{git branch -r --contains #{commit}}.grep(/^\s*origin\/#{branch}/).empty?
|
15
|
-
puts ""
|
16
|
-
puts "no rev matches #{commit} in the remote #{branch} branch(es)"
|
17
|
-
return false
|
13
|
+
def changed_in_git(filename)
|
14
|
+
`git diff --name-only`.grep(/^#{filename}$/).length > 0 ? true : false
|
18
15
|
end
|
19
|
-
true
|
20
|
-
end
|
21
16
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
return
|
17
|
+
def commit_of_rev(branch)
|
18
|
+
x=`git rev-parse --revs-only #{branch}`.chomp
|
19
|
+
return nil if x.empty?
|
20
|
+
return x
|
26
21
|
end
|
27
|
-
return true
|
28
|
-
end
|
29
22
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
23
|
+
def commit_in_remote_branch?(commit,branch)
|
24
|
+
return false if commit.nil?
|
25
|
+
if %x{git branch -r --contains #{commit}}.grep(/^\s*origin\/#{branch}/).empty?
|
26
|
+
puts ""
|
27
|
+
puts "no rev matches #{commit} in the remote #{branch} branch(es)"
|
28
|
+
return false
|
29
|
+
end
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
33
|
+
def valid_commit?(commit)
|
34
|
+
return false if commit.nil?
|
35
|
+
if branch_stages.include? stage.to_s
|
36
|
+
return false unless commit_in_remote_branch?(commit,fetch(:deploy_branch, stage.to_s))
|
37
|
+
end
|
38
|
+
return true
|
39
|
+
end
|
37
40
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
release='master'
|
41
|
+
# returns the actual branch name, if it exists. nil if it does not
|
42
|
+
def remote_branch_name(branch)
|
43
|
+
return nil if branch.nil?
|
44
|
+
rem_branch = `git branch -r`.grep(/^\s*origin\/(v|)#{branch}$/)[0]
|
45
|
+
return nil if rem_branch.nil?
|
46
|
+
return rem_branch.sub(/^\s*origin\//, '').chomp
|
45
47
|
end
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
48
|
+
|
49
|
+
set :revision, begin
|
50
|
+
deploy_version = ENV[ 'DEPLOY_VERSION' ]
|
51
|
+
deploy_version ||= 'HEAD' unless branch_stages.include?(stage.to_s)
|
52
|
+
|
53
|
+
commit = commit_of_rev(deploy_version)
|
54
|
+
until valid_commit?(commit) do
|
55
|
+
deploy_version=Capistrano::CLI.ui.ask( 'Enter release number to deploy: ' )
|
56
|
+
commit = commit_of_rev(deploy_version)
|
57
|
+
end
|
58
|
+
commit
|
50
59
|
end
|
51
|
-
commit
|
52
|
-
end
|
53
60
|
|
54
|
-
|
55
|
-
|
56
|
-
|
61
|
+
set :branch do
|
62
|
+
revision
|
63
|
+
end
|
57
64
|
|
58
|
-
|
59
|
-
|
65
|
+
set :release_commit do
|
66
|
+
branch
|
67
|
+
end
|
60
68
|
end
|
61
69
|
end
|
70
|
+
|
71
|
+
before 'deploy:update', 'deploy:get_revision'
|
@@ -3,15 +3,24 @@ namespace :misc do
|
|
3
3
|
task :send_success, :on_error => :continue do
|
4
4
|
t=end_time
|
5
5
|
time_msg=t.nil? ? '' : " in #{t} minutes"
|
6
|
-
|
6
|
+
puts "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
|
7
|
+
puts ""
|
8
|
+
puts "%s deployed branch #{branch} to #{stage}. It succeeded#{time_msg}! :D" % Etc.getpwnam(Etc.getlogin)['gecos'].split.first
|
9
|
+
puts ""
|
10
|
+
puts "SUCCESS SUCCESS SUCCESS SUCCESS SUCCESS"
|
7
11
|
end
|
8
12
|
|
9
13
|
desc "[internal] send notifications in case the deploy fails"
|
10
|
-
task :
|
14
|
+
task :setup_rollback_notification, :on_error => :continue do
|
11
15
|
on_rollback {
|
12
16
|
t=end_time
|
13
17
|
time_msg=t.nil? ? '' : " after #{t} minutes"
|
14
|
-
|
18
|
+
|
19
|
+
puts "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
|
20
|
+
puts ""
|
21
|
+
puts "%s tried to deploy branch #{branch} to #{stage}. It failed#{time_msg}! 8{" % Etc.getpwnam(Etc.getlogin)['gecos'].split.first
|
22
|
+
puts ""
|
23
|
+
puts "FAIL FAIL FAIL FAIL FAIL FAIL FAIL FAIL"
|
15
24
|
}
|
16
25
|
end
|
17
26
|
end
|
@@ -27,9 +36,11 @@ end
|
|
27
36
|
before "deploy:migrate", "deploy:tables"
|
28
37
|
end
|
29
38
|
|
30
|
-
|
31
|
-
|
32
|
-
task
|
33
|
-
run "cd #{deploy_to}/current && rake #{ENV['task']} RAILS_ENV=#{rails_env}"
|
34
|
-
end
|
39
|
+
desc "Allow running of remote rake tasks"
|
40
|
+
task :rake_invoke do
|
41
|
+
run "cd #{deploy_to}/current && rake #{ENV['task']} RAILS_ENV=#{rails_env}"
|
35
42
|
end
|
43
|
+
|
44
|
+
|
45
|
+
before 'deploy', 'misc:setup_rollback_notification'
|
46
|
+
after 'deploy', 'misc:send_success'
|
@@ -1,5 +1,28 @@
|
|
1
1
|
Capistrano::Configuration.instance.load do
|
2
|
+
def to_minutes(seconds)
|
3
|
+
m = (seconds/60).floor
|
4
|
+
s = (seconds - (m * 60)).round
|
5
|
+
# add leading zero to one-digit minute
|
6
|
+
if m < 10
|
7
|
+
m = "0#{m}"
|
8
|
+
end
|
9
|
+
# add leading zero to one-digit second
|
10
|
+
if s < 10
|
11
|
+
s = "0#{s}"
|
12
|
+
end
|
13
|
+
# return formatted time
|
14
|
+
return "#{m}:#{s}"
|
15
|
+
end
|
16
|
+
|
2
17
|
namespace :deploy do
|
18
|
+
desc "install database.yml, and other things "
|
19
|
+
task :local_config do
|
20
|
+
run "mkdir -p #{shared_path}/cache"
|
21
|
+
run "/bin/ln -nsf #{release_path}/../../shared/cache #{release_path}/public/cache"
|
22
|
+
end
|
23
|
+
|
24
|
+
after "deploy:symlink", "deploy:local_config"
|
25
|
+
|
3
26
|
desc "update selinux context"
|
4
27
|
task :selinux do
|
5
28
|
run %Q{if [ -d #{release_path} ]; then
|
@@ -15,28 +38,13 @@ Capistrano::Configuration.instance.load do
|
|
15
38
|
}
|
16
39
|
end
|
17
40
|
|
18
|
-
desc "install database.yml, and other things "
|
19
|
-
task :local_config do
|
20
|
-
run "cp #{release_path}/config/database.deploy.yml #{release_path}/config/database.yml"
|
21
|
-
case stage
|
22
|
-
when :alpha
|
23
|
-
run "rsync -a #{release_path}/features/ #{release_path}/public/features/"
|
24
|
-
run %Q{ /bin/ls -1 #{release_path}/public/features/ | /bin/awk -v dq='"' 'BEGIN{print "<!DOCTYPE HTML PUBLIC " dq "-//W3C//DTD HTML 4.01//EN" dq "\n" dq "http://www.w3.org/TR/html4/strict.dtd"dq">\n<body><table>"} /feature$/ {print "<tr><td><a href="dq $1 dq ">" $1 "</a></td></tr>"} END{print "</table></body>"}' > #{release_path}/public/features/index.html}
|
25
|
-
end
|
26
|
-
run "mkdir -p #{shared_path}/cache"
|
27
|
-
run "/bin/ln -nsf #{release_path}/../../shared/cache #{release_path}/public/cache"
|
28
|
-
#run "/bin/ln -nsf #{release_path} #{release_path}/../latest"
|
29
|
-
#run "/bin/ln -nsf #{shared_path}/config/database.yml #{release_path}/config/database.yml"
|
30
|
-
end
|
31
|
-
|
32
41
|
desc "Write Version file on server"
|
33
42
|
task :write_version_file, :roles => :web do
|
34
43
|
output=%Q{
|
35
44
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
|
36
45
|
"http://www.w3.org/TR/html4/strict.dtd">
|
37
46
|
<body>
|
38
|
-
<p>Release
|
39
|
-
<p>Release Commit: #{release_commit}</p>
|
47
|
+
<p>Release Commit: #{revision}</p>
|
40
48
|
<p>Deployed on: #{Time.now.strftime('%m/%d/%Y at %H:%M %Z')}</p>
|
41
49
|
</body>
|
42
50
|
}
|
@@ -57,7 +65,7 @@ Capistrano::Configuration.instance.load do
|
|
57
65
|
run "cd #{deploy_to}/shared/cached-copy && git clean -dxf vendor"
|
58
66
|
end
|
59
67
|
|
60
|
-
|
68
|
+
after "deploy:update_code", "deploy:cleanup_vendor"
|
61
69
|
before "deploy:rollback:revision", "deploy:rollback_migrations"
|
62
70
|
|
63
71
|
desc "Rolls back database to migration level of the previously deployed release"
|
@@ -32,8 +32,10 @@ namespace :deploy do
|
|
32
32
|
result = ERB.new(template).result(binding)
|
33
33
|
maint_files.each do |file|
|
34
34
|
dir=File.dirname(file)
|
35
|
-
|
36
|
-
|
35
|
+
tmpfile=%x{mktemp -u /tmp/capXXXXXXXX} # this seems like the quickest way to just get a name.
|
36
|
+
tmpfile.chomp!
|
37
|
+
put result, tmpfile, :mode => 0644
|
38
|
+
run "if [ -d #{dir} -a \! -e #{file} ]; then mv #{tmpfile} #{file}; fi"
|
37
39
|
run "chcon -t httpd_sys_content_t #{file}; true" # we ignore this since some hosts have home on nfs
|
38
40
|
end
|
39
41
|
end
|
@@ -1,18 +1,14 @@
|
|
1
1
|
require 'capistrano'
|
2
2
|
Capistrano::Configuration.instance.load do
|
3
|
-
load_paths << File.dirname(__FILE__)
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
end
|
15
|
-
# return formatted time
|
16
|
-
return "#{m}:#{s}"
|
17
|
-
end
|
3
|
+
load_paths << File.expand_path(File.dirname(__FILE__))
|
4
|
+
|
5
|
+
load 'projectdx/projectdx.rb'
|
6
|
+
load 'projectdx/git_branch.rb'
|
7
|
+
load 'projectdx/misc.rb'
|
8
|
+
load 'projectdx/time.rb'
|
9
|
+
load 'projectdx/web.rb'
|
10
|
+
load 'projectdx/passenger.rb'
|
11
|
+
load 'projectdx/docs.rb'
|
12
|
+
load 'projectdx/cache.rb'
|
13
|
+
load 'projectdx/db.rb'
|
18
14
|
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/database_operations'
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'open3'
|
2
|
+
require 'tempfile'
|
3
|
+
class DatabaseOperations
|
4
|
+
def self.pg(cfg, cmd)
|
5
|
+
ENV['PGPASSWORD'] = cfg["password"]
|
6
|
+
|
7
|
+
args = []
|
8
|
+
args << %{-h "#{cfg["host"]}"} if cfg["host"]
|
9
|
+
args << %{-U "#{cfg["username"]}"} if cfg["username"]
|
10
|
+
args << %{-p "#{cfg["port"]}"} if cfg["port"]
|
11
|
+
args << %{-w}
|
12
|
+
|
13
|
+
%x{#{cmd} #{args.join(' ')} "#{cfg['database']}"}
|
14
|
+
ensure
|
15
|
+
ENV.delete('PGPASSWORD')
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.load_database_schema!(cfg, file)
|
19
|
+
pg(cfg, 'createdb') unless pg(cfg, "psql -l") =~ /^ #{cfg['database']}\s*\|/m
|
20
|
+
|
21
|
+
Tempfile.open('initdb') do |f|
|
22
|
+
f.puts "set client_min_messages=error;"
|
23
|
+
f.puts "drop schema if exists public cascade;"
|
24
|
+
f.puts "create schema public;"
|
25
|
+
f.puts "drop schema if exists tiger cascade;"
|
26
|
+
|
27
|
+
f.puts "drop language if exists plpgsql cascade;"
|
28
|
+
f.puts "create language plpgsql;"
|
29
|
+
f.flush
|
30
|
+
pg(cfg, "psql -f #{f.path}")
|
31
|
+
end
|
32
|
+
|
33
|
+
pg cfg, %{psql -f "#{file}"}
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.dump_database_schema!(cfg, file)
|
37
|
+
search_path = cfg["schema_search_path"]
|
38
|
+
search_path = search_path.split(',').map{|x| "--schema=#{x}"}.join(' ') if search_path
|
39
|
+
|
40
|
+
File.open(Rails.root.join(file), "w") { |f|
|
41
|
+
f.puts "begin;"
|
42
|
+
f.write pg(cfg, %{pg_dump -n tiger}).gsub('CREATE FUNCTION', 'CREATE OR REPLACE FUNCTION').lines.reject { |l| l =~ /soundex/ } * ""
|
43
|
+
f.write pg(cfg, %{pg_dump -s -n public}).gsub('CREATE FUNCTION', 'CREATE OR REPLACE FUNCTION')
|
44
|
+
f.write pg(cfg, %{pg_dump -a -t spatial_ref_sys})
|
45
|
+
f.write pg(cfg, %{pg_dump -a -t schema_migrations})
|
46
|
+
f.puts "commit;"
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.load_views_and_triggers!
|
51
|
+
cfg = ActiveRecord::Base.configurations[Rails.env]
|
52
|
+
unless pg(cfg, "createlang -l") =~ /plpgsql/
|
53
|
+
pg(cfg, "createlang plpgsql")
|
54
|
+
end
|
55
|
+
|
56
|
+
output = nil
|
57
|
+
|
58
|
+
Tempfile.open('load_views') do |temp|
|
59
|
+
|
60
|
+
Dir.glob(Rails.root.join('lib', 'sql_erb', '[0-9]*.sql.erb')).sort_by { |f| ('0.%s' % File.split(f).last.gsub(/\D.*/,'')).to_f }.each do |fpath|
|
61
|
+
temp.puts File.open(fpath){|io| ERB.new(io.read).result }
|
62
|
+
end
|
63
|
+
temp.flush
|
64
|
+
output = pg cfg, %{psql --single-transaction -f #{temp.path} }
|
65
|
+
end
|
66
|
+
output
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
def copy(src,dst)
|
2
|
+
%x{if [ -e #{dst} ]; then mv #{dst} #{dst}.bak; fi; cp #{src} #{dst}}
|
3
|
+
end
|
4
|
+
|
5
|
+
require File.dirname(__FILE__) + '/../database_operations'
|
6
|
+
|
7
|
+
namespace :ci do
|
8
|
+
task :setup => 'setup:default'
|
9
|
+
|
10
|
+
namespace :setup do
|
11
|
+
task :default => [:copy_config, :clone_structure]
|
12
|
+
|
13
|
+
task :cucumber => [:copy_config, :clean_logdir, :clean_cache, :default]
|
14
|
+
|
15
|
+
task :clone_structure do
|
16
|
+
abcs = YAML.load_file('config/database.yml')
|
17
|
+
DatabaseOperations.dump_database_schema!(abcs['reference'], 'db/ci_structure.sql')
|
18
|
+
DatabaseOperations.load_database_schema!(abcs['test'], 'db/ci_structure.sql')
|
19
|
+
puts %x{env RAILS_ENV=test rake db:migrate}
|
20
|
+
DatabaseOperations.dump_database_schema!(abcs['test'], 'db/ci_structure.sql')
|
21
|
+
end
|
22
|
+
|
23
|
+
task :copy_config do
|
24
|
+
ci = YAML.load_file('config/database.ci.yml')
|
25
|
+
ci['login']['database'] = '%s-ci-%s' % [ci['login']['application'], %x{hostname}.strip.split('.').first]
|
26
|
+
login = ci.delete('login')
|
27
|
+
ref = '%s-reference' % login['application']
|
28
|
+
environments = login.delete('environments')
|
29
|
+
|
30
|
+
ci['reference'] = login.clone
|
31
|
+
ci['reference']['database'] = ref
|
32
|
+
ci['development'] = login.clone
|
33
|
+
|
34
|
+
environments.each do |e|
|
35
|
+
ci[e] = login.clone
|
36
|
+
end
|
37
|
+
|
38
|
+
File.rename("config/database.yml", "config/database.yml.bak") if File.exists?("config/database.yml")
|
39
|
+
|
40
|
+
File.open('config/database.yml', 'w') do |f|
|
41
|
+
f.write(ci.to_yaml);
|
42
|
+
end
|
43
|
+
|
44
|
+
if File.exists?("config/cms_config.deploy.yml")
|
45
|
+
File.rename("config/cms_config.yml", "config/cms_config.yml.bak") if File.exists?("config/cms_config.yml")
|
46
|
+
copy 'config/cms_config.deploy.yml', 'config/cms_config.yml'
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
task :clean_cache do
|
51
|
+
`rm -f public/javascripts/cache_*.js`
|
52
|
+
end
|
53
|
+
|
54
|
+
task :clean_logdir do
|
55
|
+
`for f in log/* tmp/*; do if [ -f $f ]; then rm $f ; fi; done`
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
namespace :db do
|
61
|
+
Rake::Task['db:structure:dump'].clear_actions()
|
62
|
+
namespace :structure do
|
63
|
+
desc "Dump the database structure to a SQL file"
|
64
|
+
task :dump => :environment do
|
65
|
+
if File.exists?('db/ci_structure.sql')
|
66
|
+
STDERR.puts "CI environment detected: not dumping schema"
|
67
|
+
else
|
68
|
+
abcs = ActiveRecord::Base.configurations
|
69
|
+
DatabaseOperations.dump_database_schema!(abcs['development'], 'db/development_structure.sql')
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
Rake::Task['db:test:clone_structure'].clear_actions()
|
75
|
+
namespace :test do
|
76
|
+
desc "Recreate the test databases from the development structure"
|
77
|
+
task :clone_structure => [ "db:structure:dump", "db:test:purge"] do
|
78
|
+
abcs = ActiveRecord::Base.configurations
|
79
|
+
if File.exists?('db/ci_structure.sql')
|
80
|
+
STDERR.puts "CI environment detected"
|
81
|
+
DatabaseOperations.load_database_schema!(abcs['test'], 'db/ci_structure.sql')
|
82
|
+
else
|
83
|
+
DatabaseOperations.load_database_schema!(abcs['test'], 'db/development_structure.sql')
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
data/lib/rf_filtering.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
# Include this from application.rb and call rf_param_filtering() with a
|
2
|
+
# whitelist and a blacklist set of params, as below. Blacklisted parameters
|
3
|
+
# are filtered first by Rails, and any other parameters not in the whitelist
|
4
|
+
# are filtered by this code.
|
5
|
+
#
|
6
|
+
## application.rb:
|
7
|
+
#
|
8
|
+
# require 'rf_filtering'
|
9
|
+
# ...
|
10
|
+
# class Application < Rails::Application
|
11
|
+
# ...
|
12
|
+
# include RFFiltering
|
13
|
+
# RFFiltering.UnfilteredEnvironments << 'unfiltered_environment'
|
14
|
+
# rf_param_filtering(
|
15
|
+
# :blacklist => %w[password card_number card_number],
|
16
|
+
# :whitelist => %w[brochure_nickname id])
|
17
|
+
#
|
18
|
+
|
19
|
+
module RFFiltering
|
20
|
+
extend ActiveSupport::Concern
|
21
|
+
|
22
|
+
UnfilteredEnvironments = ['development']
|
23
|
+
FilteredReplacement = '[+++]'
|
24
|
+
|
25
|
+
included do
|
26
|
+
# this space intentionally left blank
|
27
|
+
end
|
28
|
+
|
29
|
+
module ClassMethods
|
30
|
+
attr_reader :rf_filtering_blacklist, :rf_filtering_whitelist
|
31
|
+
|
32
|
+
# Leaving this exposed for mockability
|
33
|
+
def should_filter_params?
|
34
|
+
# there are not a few non-positives in this code.
|
35
|
+
!UnfilteredEnvironments.include?(Rails.env)
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
def rf_param_filtering(options = {})
|
40
|
+
@rf_filtering_blacklist = (options[:blacklist] || []).map(&:to_s)
|
41
|
+
@rf_filtering_whitelist = (options[:whitelist] || []).map(&:to_s).to_set
|
42
|
+
|
43
|
+
config.filter_parameters += rf_filtering_blacklist
|
44
|
+
config.filter_parameters << rf_whitelist_filter_proc
|
45
|
+
end
|
46
|
+
|
47
|
+
def rf_whitelist_filter_proc
|
48
|
+
lambda do |k,v|
|
49
|
+
next unless should_filter_params?
|
50
|
+
next unless v.present?
|
51
|
+
next unless v.respond_to?(:to_str) && v.respond_to?(:replace)
|
52
|
+
next if param_filter_white_list.include?(k.to_s)
|
53
|
+
v.replace FilteredReplacement
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
module InstanceMethods
|
59
|
+
# Leaving this exposed for mockability
|
60
|
+
def param_filter_white_list
|
61
|
+
self.class.rf_filtering_whitelist
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# Include this with an initializer similar to the one below.
|
2
|
+
#
|
3
|
+
# whitelisted_fields = %w[
|
4
|
+
# cached_cms_responses.filename
|
5
|
+
# cached_cms_responses.site
|
6
|
+
# customers.hostname
|
7
|
+
# sessions.session_id
|
8
|
+
# ]
|
9
|
+
# whitelisted_fields.each { |field| SqlObfuscator.whitelist(field) }
|
10
|
+
#
|
11
|
+
# SqlObfuscator.dont_obfuscate_table_and_field do |table, field|
|
12
|
+
# %w[created_at updated_at nickname].include?(field)
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# NewRelic::Agent.set_sql_obfuscator(:replace) do |sql|
|
16
|
+
# SqlObfuscator.obfuscate(sql)
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
|
20
|
+
|
21
|
+
module SqlObfuscator
|
22
|
+
module_function
|
23
|
+
|
24
|
+
# Provide a facility for whitelisting certain fields
|
25
|
+
WhitelistHash = Hash.new(false)
|
26
|
+
def whitelist(field_expression)
|
27
|
+
WhitelistHash[field_expression] = true
|
28
|
+
end
|
29
|
+
def whitelisted?(field_expression)
|
30
|
+
WhitelistHash[field_expression]
|
31
|
+
end
|
32
|
+
|
33
|
+
DontObfuscateProcs = []
|
34
|
+
# Provides a facility for *not* obfuscating if the field meets some arbitrary criteria.
|
35
|
+
# Takes blocks, calls them on expressions for which a table and field can be found.
|
36
|
+
# Blocks should take two arguments, table and field, and return TRUE if the value should NOT be obfuscated.
|
37
|
+
def dont_obfuscate_table_and_field(&proc)
|
38
|
+
DontObfuscateProcs << proc
|
39
|
+
end
|
40
|
+
|
41
|
+
# Should match:
|
42
|
+
# f_table_name = 'sensitive data'
|
43
|
+
# "some_table"."some_field" = 'sensitive data'
|
44
|
+
# "some_table"."some_field" somehow_matches 'sensitive data'
|
45
|
+
# ...but see also test/unit/lib/sql_obfuscator_unit_test.rb
|
46
|
+
ComparisonExpression = /(?:("\w+"\."\w+")([^"']+)?)?'([^'\\]*((?:\\.|'')[^'\\]+)*)'/
|
47
|
+
|
48
|
+
# Actually do the obfuscation
|
49
|
+
def obfuscate(sql)
|
50
|
+
sql.gsub(ComparisonExpression) do |match|
|
51
|
+
field_expression, comparison, sensitive_data = $1, $2, $3
|
52
|
+
|
53
|
+
# Obfuscate by default
|
54
|
+
obfuscated_sql = [field_expression, comparison, obfuscate_value(sensitive_data)].compact.join
|
55
|
+
|
56
|
+
# Don't obfuscate if we have a field expression that's on the whitelist or is on a 'nickname' field
|
57
|
+
if field_expression.present?
|
58
|
+
table_name, field_name = field_expression.to_s.gsub('"', '').split('.')
|
59
|
+
case
|
60
|
+
when whitelisted?('%s.%s' % [table_name, field_name])
|
61
|
+
obfuscated_sql = match
|
62
|
+
when DontObfuscateProcs.any? { |proc| proc.call(table_name, field_name) }
|
63
|
+
obfuscated_sql = match
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
obfuscated_sql
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def obfuscate_value(value)
|
72
|
+
"'%s'" % Digest::MD5.hexdigest("%d:%s" % [value.to_s.length, value.to_s])
|
73
|
+
end
|
74
|
+
end
|
metadata
CHANGED
@@ -1,27 +1,33 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: capistrano-ext-projectdx
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 7
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 0.
|
8
|
+
- 1
|
9
|
+
- 14
|
10
|
+
version: 0.1.14
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Darrell Fuhriman
|
14
|
+
- Joel Hoffman
|
15
|
+
- Sam Livingston-Gray
|
16
|
+
- Laurie Kemmerer
|
14
17
|
autorequire:
|
15
18
|
bindir: bin
|
16
19
|
cert_chain: []
|
17
20
|
|
18
|
-
date: 2011-
|
21
|
+
date: 2011-12-09 00:00:00 -08:00
|
19
22
|
default_executable:
|
20
23
|
dependencies: []
|
21
24
|
|
22
25
|
description: Local extensions to capistrano, which others may find useful
|
23
26
|
email:
|
24
27
|
- darrell@renewfund.com
|
28
|
+
- joel@renewfund.com
|
29
|
+
- sam@renewfund.com
|
30
|
+
- laurie@renewfund.com
|
25
31
|
executables: []
|
26
32
|
|
27
33
|
extensions: []
|
@@ -29,9 +35,9 @@ extensions: []
|
|
29
35
|
extra_rdoc_files: []
|
30
36
|
|
31
37
|
files:
|
32
|
-
-
|
38
|
+
- bin/deploy.sh
|
33
39
|
- lib/capistrano/ext/projectdx/cache.rb
|
34
|
-
- lib/capistrano/ext/projectdx/
|
40
|
+
- lib/capistrano/ext/projectdx/db.rb
|
35
41
|
- lib/capistrano/ext/projectdx/docs.rb
|
36
42
|
- lib/capistrano/ext/projectdx/git_branch.rb
|
37
43
|
- lib/capistrano/ext/projectdx/misc.rb
|
@@ -40,6 +46,11 @@ files:
|
|
40
46
|
- lib/capistrano/ext/projectdx/time.rb
|
41
47
|
- lib/capistrano/ext/projectdx/web.rb
|
42
48
|
- lib/capistrano/ext/projectdx.rb
|
49
|
+
- lib/capistrano-ext-projectdx.rb
|
50
|
+
- lib/database_operations.rb
|
51
|
+
- lib/projectdx/tasks.rb
|
52
|
+
- lib/rf_filtering.rb
|
53
|
+
- lib/sql_obfuscator.rb
|
43
54
|
has_rdoc: true
|
44
55
|
homepage: http://github.com/projectdx/capistrano-ext-projectdx
|
45
56
|
licenses: []
|
@@ -1,14 +0,0 @@
|
|
1
|
-
# we have to call this something else so
|
2
|
-
# we do not keep loading this file over and over
|
3
|
-
Capistrano::Configuration.instance.load do
|
4
|
-
load_paths << File.dirname(__FILE__)
|
5
|
-
load('projectdx.rb')
|
6
|
-
load('campfire.rb')
|
7
|
-
load('git_branch.rb')
|
8
|
-
load('misc.rb')
|
9
|
-
load('time.rb')
|
10
|
-
load('web.rb')
|
11
|
-
load('passenger.rb')
|
12
|
-
load('docs.rb')
|
13
|
-
load('cache.rb')
|
14
|
-
end
|
@@ -1,62 +0,0 @@
|
|
1
|
-
namespace :misc do
|
2
|
-
|
3
|
-
def send_to_campfire(message)
|
4
|
-
campfire_host=fetch(:campfire_host,'')
|
5
|
-
return nil if campfire_host.empty?
|
6
|
-
begin
|
7
|
-
gem 'tinder'
|
8
|
-
rescue Gem::LoadError
|
9
|
-
puts "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
|
10
|
-
puts ""
|
11
|
-
puts "not notifying because tinder not installed. Message is: '#{message}'"
|
12
|
-
puts ""
|
13
|
-
puts "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
|
14
|
-
return
|
15
|
-
end
|
16
|
-
require 'tinder'
|
17
|
-
# for some reason, ssl isn't working.
|
18
|
-
begin
|
19
|
-
campfire = Tinder::Campfire.new(campfire_host, :ssl => true)
|
20
|
-
campfire.login(campfire_login, campfire_password)
|
21
|
-
rescue => e
|
22
|
-
puts "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
|
23
|
-
puts ""
|
24
|
-
puts "Unable to login to campfire. Error is: '#{e}'"
|
25
|
-
puts "login: #{campfire_host}, #{campfire_login}, #{campfire_password}"
|
26
|
-
puts ""
|
27
|
-
puts "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
|
28
|
-
return
|
29
|
-
end
|
30
|
-
begin
|
31
|
-
room = campfire.find_room_by_name(campfire_room)
|
32
|
-
rescue => e
|
33
|
-
logger.error("Trouble initalizing campfire room #{campfire_room}")
|
34
|
-
return
|
35
|
-
end
|
36
|
-
begin
|
37
|
-
room.speak(message)
|
38
|
-
rescue => e
|
39
|
-
puts "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
|
40
|
-
puts ""
|
41
|
-
puts "Unable to send message to campfire. Message is: '#{message}'"
|
42
|
-
puts ""
|
43
|
-
puts "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
|
44
|
-
return
|
45
|
-
end
|
46
|
-
end
|
47
|
-
desc "[internal] Send campfire notifications on success"
|
48
|
-
task :send_success, :on_error => :continue do
|
49
|
-
t=end_time
|
50
|
-
time_msg=t.nil? ? '' : " in #{t} minutes"
|
51
|
-
send_to_campfire("#{Etc.getpwnam(Etc.getlogin)['gecos'].split.first} tried to deploy branch #{branch} to #{stage}. It succeeded#{time_msg}! :D")
|
52
|
-
end
|
53
|
-
|
54
|
-
desc "[internal] send notifications in case the deploy fails"
|
55
|
-
task :rollback_notification, :on_error => :continue do
|
56
|
-
on_rollback {
|
57
|
-
t=end_time
|
58
|
-
time_msg=t.nil? ? '' : " after #{t} minutes"
|
59
|
-
send_to_campfire("#{Etc.getpwnam(Etc.getlogin)['gecos'].split.first} tried to deploy branch #{branch} to #{stage}. It failed#{time_msg}. :.(")
|
60
|
-
}
|
61
|
-
end
|
62
|
-
end
|