capistrano-ext-projectdx 0.0.7 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/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
|