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 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