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 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
- Capistrano::Configuration.instance.load do
2
- def changed_in_git(filename)
3
- `git diff --name-only`.grep(/^#{filename}$/).length > 0 ? true : false
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
- def commit_of_rev(branch)
7
- x=`git rev-parse --revs-only #{branch}`.chomp
8
- return nil if x.empty?
9
- return x
10
- end
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
- def commit_in_remote_branch?(commit,branch)
13
- return false if commit.nil?
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
- def valid_commit?(commit)
23
- return false if commit.nil?
24
- if branch_stages.include? stage.to_s
25
- return false unless commit_in_remote_branch?(commit,fetch(:deploy_branch, stage.to_s))
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
- # returns the actual branch name, if it exists. nil if it does not
31
- def remote_branch_name(branch)
32
- return nil if branch.nil?
33
- rem_branch = `git branch -r`.grep(/^\s*origin\/(v|)#{branch}$/)[0]
34
- return nil if rem_branch.nil?
35
- return rem_branch.sub(/^\s*origin\//, '').chomp
36
- end
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
- release = nil;
39
- set :release_number do
40
- commit=nil;
41
- release=ENV[ 'DEPLOY_VERSION' ] if ENV[ 'DEPLOY_VERSION' ]
42
- release=ENV['BUILD_VCS_NUMBER'][0,12] if ENV['BUILD_VCS_NUMBER']
43
- if release.nil? && !branch_stages.include?(stage.to_s)
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
- commit = commit_of_rev(release)
47
- while not valid_commit?(commit) do
48
- release=Capistrano::CLI.ui.ask( 'Enter release number to deploy: ' )
49
- commit=commit_of_rev(release)
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
- set :branch do
55
- release_number
56
- end
61
+ set :branch do
62
+ revision
63
+ end
57
64
 
58
- set :release_commit do
59
- branch
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
- send_to_campfire("#{Etc.getpwnam(Etc.getlogin)['gecos'].split.first} tried to deploy branch #{branch} to #{stage}. It succeeded#{time_msg}! :D")
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 :rollback_notification, :on_error => :continue do
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
- send_to_campfire("#{Etc.getpwnam(Etc.getlogin)['gecos'].split.first} tried to deploy branch #{branch} to #{stage}. It failed#{time_msg}. :.(")
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
- namespace :rake do
31
- desc "Allow running of remote rake tasks"
32
- task :invoke do
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 Branch: #{release_number}</p>
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
- before "deploy:update_code", "deploy:cleanup_vendor"
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"
@@ -27,3 +27,6 @@ namespace :time do
27
27
  end
28
28
  end
29
29
  end
30
+
31
+ after 'multistage:ensure', 'time:begin'
32
+ after 'deploy', 'time:finish'
@@ -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
- put result, "/tmp/disable_cap", :mode => 0644
36
- run "if [ -d #{dir} -a \! -e #{file} ]; then mv /tmp/disable_cap #{file}; fi"
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__)+'/projectdx'
4
- def to_minutes(seconds)
5
- m = (seconds/60).floor
6
- s = (seconds - (m * 60)).round
7
- # add leading zero to one-digit minute
8
- if m < 10
9
- m = "0#{m}"
10
- end
11
- # add leading zero to one-digit second
12
- if s < 10
13
- s = "0#{s}"
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
@@ -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: 17
4
+ hash: 7
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 0
9
- - 7
10
- version: 0.0.7
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-05-06 00:00:00 -07:00
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
- - lib/capistrano/ext/projectdx/all.rb
38
+ - bin/deploy.sh
33
39
  - lib/capistrano/ext/projectdx/cache.rb
34
- - lib/capistrano/ext/projectdx/campfire.rb
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