jefferies_tube 0.1.0

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 28837049e119307ca0d578437fe643b85b0392ca
4
+ data.tar.gz: 8aa5841a30c82576ca60f8ffdb3b3b9be669b69a
5
+ SHA512:
6
+ metadata.gz: 3041d592626f0a1e060b89b67b56122a303e755eb9a7f5bb356ddeb59bc7c207778ea7b285dc86cf06f99f58e0e16b12a69493f22e7b27f6b89609c525ebef11
7
+ data.tar.gz: 9c5f762135566c6e513e594ee0c0624f6538dfdfd6fee992982169c4fe2a5b54dc572fbd9b0914706c73c7eedf82d583335bfa45919c1c588d1238661c788acf
data/.gitignore ADDED
@@ -0,0 +1,20 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .DS_Store
6
+ .rspec
7
+ .yardoc
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ Gemfile.lock
12
+ InstalledFiles
13
+ lib/bundler/man
14
+ pkg
15
+ rdoc
16
+ spec/examples.txt
17
+ spec/reports
18
+ test/tmp
19
+ test/version_tmp
20
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ jefferies_tube
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.2.3
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in jeffries_tube.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Brian Samson
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,148 @@
1
+ # JefferiesTube
2
+
3
+ A collection of useful tools used at Ten Forward Consulting
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'jefferies_tube'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install jefferies_tube
18
+
19
+ ## Usage
20
+
21
+ ### Error Handling
22
+
23
+ #### 404 Handling
24
+
25
+ JefferiesTube by default installs a catchall route that will render 404 for you and supress the rollbar error. This also allows you to create super easy custom error pages.
26
+
27
+ Simple put a template in the parent app in `app/views/errors/404.haml` (or html or erb, etc) and it will be rendered instead of the default Jefferies tube error.
28
+
29
+ #### 500 handling
30
+
31
+ In progress -- not sure if this is super useful.
32
+
33
+ ### Rake
34
+
35
+ * `rake db:backup`
36
+
37
+ Capture a database backup
38
+
39
+ * `rake db:restore`
40
+
41
+ Load most recent database backup. Can specify location of backup with `FILE`.
42
+
43
+ ### Capistrano
44
+
45
+ Add this line *last* in your Capfile (it depends on rails/migrations and cap/deploy)
46
+ ```ruby
47
+ require 'jefferies_tube/capistrano'
48
+ ```
49
+
50
+ #### Tasks
51
+
52
+ * `cap beta ssh`
53
+
54
+ Open ssh session in `current` directory.
55
+
56
+ * `cap beta rails:console`
57
+
58
+ Open rails console.
59
+
60
+ * `cap beta rails:dbconsole`
61
+
62
+ Open database console.
63
+
64
+ * `cap beta rails:log`
65
+
66
+ Open log file. Can specify log file like so: `LOG=foobar cap beta rails:log`
67
+
68
+ * `cap beta db:backup`
69
+
70
+ Make a database backup.
71
+
72
+ * `cap beta db:fetch`
73
+
74
+ Fetches the latest database backup. Useful for getting production data locally.
75
+
76
+ * `cap beta db:restore FILE=path/to/backup.dump`
77
+
78
+ Nuke the server's database with one you give it. Don't do this on production for obvious reasons. Useful for putting a backup fetched from production onto a dev server.
79
+
80
+ * `cap beta deploy:ensure_tag`
81
+
82
+ Yells at you if there is not a tag for your code.
83
+
84
+ * `cap beta deploy:create_tag`
85
+
86
+ Creates a tag for your code and pushes it.
87
+
88
+ #### Tagging
89
+
90
+ To enforce that you tagged the code before deploying, inside `config/deploy/<stage>.rb`:
91
+ ```ruby
92
+ before 'deploy', 'deploy:ensure_tag'
93
+ ```
94
+
95
+ To automatically tag the code that is about to be released (lazy programmer solution), inside `config/deploy/<stage>.rb`:
96
+ ```ruby
97
+ before 'deploy', 'deploy:create_tag'
98
+ ```
99
+
100
+ ### Enable/Disable Maintence Mode
101
+
102
+ ```
103
+ cap production maintenance:enable MESSAGE="Site is down for maintenance, should be back shortly."
104
+ cap production maintenance:disable
105
+ ```
106
+
107
+ ### Whenever
108
+
109
+ JefferiesTube has backup functionality. To use it, add something like this to your `schedule.rb`:
110
+
111
+ ```ruby
112
+ every 1.day, at: '12am' do
113
+ rake 'db:backup'
114
+ end
115
+ ```
116
+
117
+ For hourly backups:
118
+
119
+ ```ruby
120
+ every :hour do
121
+ rake 'db:backup:hourly'
122
+ end
123
+ ```
124
+
125
+ Or for daily backups:
126
+
127
+ ```ruby
128
+ every :day do
129
+ rake 'db:backup:daily'
130
+ end
131
+ ```
132
+
133
+ ### Sass
134
+
135
+ To get compass reset and box-sizing border-box to all elements:
136
+ ```sass
137
+ # app/assets/stylesheets/application.sass
138
+
139
+ @import jefferies_tube
140
+ ```
141
+
142
+ ## Contributing
143
+
144
+ 1. Fork it ( http://github.com/<my-github-username>/jefferies_tube/fork )
145
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
146
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
147
+ 4. Push to the branch (`git push origin my-new-feature`)
148
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,7 @@
1
+ @import compass/reset
2
+
3
+ html
4
+ box-sizing: border-box
5
+
6
+ *, *:before, *:after
7
+ box-sizing: inherit
@@ -0,0 +1,64 @@
1
+ class JefferiesTube::ErrorsController < ApplicationController
2
+ if Rails.version.start_with? "3"
3
+ before_filter :disable_pundit
4
+ skip_before_filter :verify_authenticity_token
5
+ else
6
+ before_action :disable_pundit
7
+ skip_before_action :verify_authenticity_token
8
+ end
9
+
10
+ def render_404
11
+ log_404
12
+ render_error_page 404
13
+ end
14
+
15
+ def additional_information
16
+ # TODO not implemented yet
17
+ render text: "Thanks!", layout: has_app_layout?
18
+ end
19
+
20
+ private
21
+ def render_error_page(code)
22
+ request.format = :html unless [:html, :json, :xml].include? request.format.to_sym
23
+ respond_to do |format|
24
+ format.any do
25
+ begin
26
+ render template: "/errors/#{code}", layout: has_app_layout?, status: code
27
+ rescue #ActionView::MissingTemplate
28
+ # Failsafe
29
+ render template: "/errors/#{code}", layout: false, status: code
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ def log_404
36
+ if defined?(Rollbar) && request.referrer.present?
37
+ Rollbar.warn("Got 404 with referrer", referrer: request.referrer, current_path: request.path)
38
+ end
39
+ end
40
+
41
+ def disable_pundit
42
+ if defined?(Pundit)
43
+ skip_authorization
44
+ end
45
+ end
46
+
47
+ def has_app_layout?
48
+ if Gem::Version.new(Rails.version) >= Gem::Version.new("5")
49
+ !!self.send(:_layout, [request.format.to_sym])
50
+ else
51
+ # boolean based on if there is a default layout for the current mime type
52
+ !!self.send(:_layout)
53
+ end
54
+ end
55
+
56
+ def html_layout
57
+ if Gem::Version.new(Rails.version) >= Gem::Version.new("5")
58
+ self.send(:_layout, ["html"]).virtual_path
59
+ else
60
+ # boolean based on if there is a default layout for the current mime type
61
+ "application"
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,7 @@
1
+ :css
2
+ #jt-error { margin: 32px auto; padding: 32px; }
3
+
4
+ #jt-error
5
+ %h1 Page not found (Error 404)
6
+
7
+ %p Sorry! We couldn't find the page you were looking for :(
@@ -0,0 +1,2 @@
1
+ json.error "Page not Found"
2
+ json.status 404
@@ -0,0 +1,4 @@
1
+ <error>
2
+ <status>404</status>
3
+ <message>Page Not Found</message>
4
+ </error>
@@ -0,0 +1,22 @@
1
+ :css
2
+ #jt-error { margin: 32px auto; padding: 32px; }
3
+ #jt-error label { display: block; }
4
+ #jt-error input, textarea { font-size: 16px; margin-bottom: 16px; width: 300px; display: block; }
5
+
6
+ #jt-error
7
+ %h1 Server Error
8
+
9
+ %p Sorry! Something went wrong with our server. Our development team has been notified.
10
+ %p Error Code: 500 (#{@exception.class.name})
11
+
12
+ / = form_tag "/jefferies_tube_add_error_information", method: "post" do
13
+ / %p Want to help us fix this? Tell us what you were doing and we'll try to fix it asap
14
+ / %label
15
+ / Comments:
16
+ / = text_area_tag :comments, nil, rows: 3
17
+ / %label
18
+ / Your email address (optional):
19
+ / = text_field_tag :email
20
+ / = submit_tag "Submit"
21
+
22
+
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'jefferies_tube/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "jefferies_tube"
8
+ spec.version = JefferiesTube::VERSION
9
+ spec.authors = ["Brian Samson"]
10
+ spec.email = ["brian@tenforwardconsulting.com"]
11
+ spec.summary = %q{Ten Forward Consulting useful tools.}
12
+ spec.description = "Useful tools for Rails."
13
+ spec.homepage = "https://github.com/tenforwardconsulting/jefferies_tube/"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "awesome_print"
22
+ spec.add_development_dependency "bundler"
23
+ spec.add_development_dependency "pry"
24
+ spec.add_development_dependency "rake"
25
+ spec.add_development_dependency "rspec", '~> 3.0'
26
+
27
+ spec.add_dependency "bundler-audit", '~>0.5.0'
28
+ spec.add_dependency 'compass-rails'
29
+ end
@@ -0,0 +1,6 @@
1
+ require 'jefferies_tube/version'
2
+ require 'jefferies_tube/engine'
3
+
4
+ module JefferiesTube
5
+ require 'jefferies_tube/railtie' if defined?(Rails)
6
+ end
@@ -0,0 +1,17 @@
1
+ class JefferiesTube::AccessToken
2
+ def self.create(hash)
3
+ verifier.generate(hash.to_yaml)
4
+ end
5
+
6
+ def self.read(token)
7
+ YAML.load(verifier.verify token)
8
+ rescue ActiveSupport::MessageVerifier::InvalidSignature
9
+ {}
10
+ end
11
+
12
+ private
13
+
14
+ def self.verifier
15
+ ActiveSupport::MessageVerifier.new(Rails.configuration.secret_token)
16
+ end
17
+ end
@@ -0,0 +1,11 @@
1
+ if !defined?(Capistrano::VERSION)
2
+ raise "Capistrano is not present."
3
+ elsif !Capistrano::VERSION.start_with?("3")
4
+ raise "Capistrano support is limited to version 3"
5
+ end
6
+
7
+ require 'jefferies_tube/capistrano/db'
8
+ require 'jefferies_tube/capistrano/deploy'
9
+ require 'jefferies_tube/capistrano/maintenance'
10
+ require 'jefferies_tube/capistrano/rails'
11
+ require 'jefferies_tube/capistrano/ssh'
@@ -0,0 +1,32 @@
1
+ namespace :db do
2
+ desc "Capture a database snapshot"
3
+ task :backup do
4
+ on roles(:db), primary: true do |host|
5
+ unless fetch(:linked_dirs).include?("db/backups")
6
+ warn "'db/backups' is not in your capistrano linked_dirs; you should add it yo"
7
+ end
8
+ within release_path do
9
+ execute :rake, "db:backup", "RAILS_ENV=#{fetch(:rails_env)}"
10
+ end
11
+ end
12
+ end
13
+
14
+ desc "Fetch the latest database backup"
15
+ task :fetch do
16
+ on roles(:db), primary: true do |host|
17
+ FileUtils.mkdir_p 'db/backups'
18
+ download! "#{deploy_to}/shared/db/backups/latest.dump", "db/backups/latest-#{fetch(:stage)}.dump"
19
+ end
20
+ end
21
+
22
+ desc "Restore the database from a local file FILE=./local/file_path"
23
+ task :restore do
24
+ on roles(:app), primary: true do |host|
25
+ within release_path do
26
+ remote_path = "#{release_path}/db/backups/#{File.basename(ENV["FILE"])}"
27
+ upload! ENV["FILE"], remote_path
28
+ execute :rake, "db:restore", "RAILS_ENV=#{fetch(:rails_env)}", "FILE=#{remote_path}"
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,53 @@
1
+ namespace :deploy do
2
+ task :ensure_tag do
3
+ tagname = `git describe --exact-match HEAD`.chomp
4
+ if $? == 0
5
+ puts "Code is tagged as `#{tagname}`, proceeding with deployment"
6
+ else
7
+ abort "You need to tag the source before you can deploy production"
8
+ end
9
+ end
10
+
11
+ task :create_tag do
12
+ now = Time.now
13
+ tagname = "#{fetch(:stage)}-#{now.strftime('%Y-%m-%d-%H%M')}"
14
+ me = `whoami`.chomp
15
+ %x(git tag -a #{tagname} -m "Automated deploy tag by #{me}" && git push origin #{tagname})
16
+ end
17
+
18
+ task :backup_database do
19
+ if fetch(:skip_deploy_backups)
20
+ puts "Skipping database backup because :skip_deploy_backups is set"
21
+ else
22
+ invoke "db:backup"
23
+ end
24
+ end
25
+
26
+ task :scan_gems do
27
+ require 'bundler/audit/scanner'
28
+ require 'bundler/audit/database'
29
+
30
+ Bundler::Audit::Database.update!
31
+ scanner = Bundler::Audit::Scanner.new
32
+ vulnerable = false
33
+ scanner.scan do |result|
34
+ vulnerable = true
35
+ case result
36
+ when Bundler::Audit::Scanner::InsecureSource
37
+ print_warning "Insecure Source URI found: #{result.source}"
38
+ when Bundler::Audit::Scanner::UnpatchedGem
39
+ puts "#{result.gem} is not secure!"
40
+ end
41
+ end
42
+ if vulnerable && ENV["I_KNOW_GEMS_ARE_INSECURE"].blank?
43
+ abort """
44
+ Your Gemfile.lock contains unpatched gems -- refusing to deploy
45
+ Run `bundle-audit check --update` for full information
46
+ You can set 'I_KNOW_GEMS_ARE_INSECURE' if you really want to do this anyway
47
+ """
48
+ end
49
+ end
50
+ end
51
+
52
+ before 'deploy:migrate', 'deploy:backup_database'
53
+ before 'deploy', 'deploy:scan_gems'
@@ -0,0 +1,22 @@
1
+ namespace :maintenance do
2
+ desc "Enable maintenance: set MESSAGE='We should be back in approximately 2 hours'"
3
+ task :enable do
4
+ on roles(:web) do |host, user|
5
+ within current_path do
6
+ message = ENV["MESSAGE"] || ""
7
+ upload! StringIO.new(message), "#{current_path}/tmp/maintenance.txt"
8
+ invoke 'deploy:restart'
9
+ end
10
+ end
11
+ end
12
+
13
+ desc "Disable maintenance mode"
14
+ task :disable do
15
+ on roles(:web) do |host, user|
16
+ within current_path do
17
+ execute :rm, "#{current_path}/tmp/maintenance.txt"
18
+ invoke 'deploy:restart'
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,31 @@
1
+ namespace :rails do
2
+ desc "Open the rails console on each of the remote servers"
3
+ task :console do
4
+ on roles(:app), primary: true do |host, user|
5
+ rails_env = fetch(:rails_env)
6
+ run_interactively "RAILS_ENV=#{rails_env} bundle exec rails console"
7
+ end
8
+ end
9
+
10
+ desc "Open the rails dbconsole on each of the remote servers"
11
+ task :dbconsole do
12
+ on roles(:db), primary: true do |host|
13
+ rails_env = fetch(:rails_env)
14
+ run_interactively "RAILS_ENV=#{rails_env} bundle exec rails dbconsole"
15
+ end
16
+ end
17
+
18
+ desc "Open the rails log"
19
+ task :log do
20
+ on roles(:app), primary: true do |host, user|
21
+ rails_env = ENV['LOG'] || fetch(:rails_env)
22
+ run_interactively "tail -f log/#{rails_env}.log"
23
+ end
24
+ end
25
+
26
+ def run_interactively(command)
27
+ port = host.port || 22
28
+ puts "ssh #{host.user}@#{host} -p #{port} -t 'cd #{deploy_to}/current; #{command}'"
29
+ exec "ssh #{host.user}@#{host} -p #{port} -t 'cd #{deploy_to}/current; #{command}'"
30
+ end
31
+ end
@@ -0,0 +1,8 @@
1
+ desc "Open an ssh session"
2
+ task :ssh do
3
+ on roles(:app), primary: true do |host|
4
+ port = host.port || 22
5
+ puts "ssh #{host.user}@#{host} -p #{port}"
6
+ exec "ssh #{host.user}@#{host} -p #{port}"
7
+ end
8
+ end
@@ -0,0 +1,130 @@
1
+ class DatabaseBackup
2
+ BACKUP_DIR = 'db/backups'
3
+
4
+ module Frequency
5
+ DAILY = :daily
6
+ HOURLY = :hourly
7
+ end
8
+
9
+ attr_accessor :max_num_of_backups
10
+
11
+ def initialize(database_backup_adapter, max_num_of_backups: 10)
12
+ @database_backup_adapter = database_backup_adapter
13
+ @max_num_of_backups = max_num_of_backups
14
+ @rotate_frequency = nil
15
+ end
16
+
17
+ def create
18
+ FileUtils.mkdir_p backup_path
19
+ @latest_backup_file = create_backup
20
+ remove_symlink_to_old_backup
21
+ create_symlink_to_new_backup
22
+ delete_oldest_backup
23
+ compress_old_backups
24
+ @latest_backup_file
25
+ end
26
+
27
+ def create_rotated(frequency)
28
+ @rotate_frequency = frequency
29
+ create
30
+ cleanup
31
+ end
32
+
33
+ def restore(path)
34
+ @database_backup_adapter.restore path
35
+ end
36
+
37
+ def restore_most_recent
38
+ @database_backup_adapter.restore symlink_file
39
+ end
40
+
41
+ def symlink_file
42
+ File.join backup_path, 'latest.dump'
43
+ end
44
+
45
+ def backups
46
+ Dir.glob(File.join(backup_path, '*'))
47
+ end
48
+
49
+ def cleanup
50
+ # hourly - keep for 24 hours
51
+ sh "find #{storage_path}/backup.hourly/ -mmin +1440 -exec rm -rv {} \\;"
52
+ # daily - keep for 14 days
53
+ sh "find #{storage_path}/backup.daily/ -mtime +14 -exec rm -rv {} \\;"
54
+ # weekly - keep for 60 days
55
+ sh "find #{storage_path}/backup.weekly/ -mtime +60 -exec rm -rv {} \\;"
56
+ # monthly - keep for 300 days
57
+ sh "find #{storage_path}/backup.monthly/ -mtime +300 -exec rm -rv {} \\;"
58
+ end
59
+
60
+ private
61
+
62
+ # Procedural Methods
63
+ def create_backup
64
+ backup_filename = "#{Time.now.strftime('%Y%m%d%H%M%S')}.dump"
65
+ latest_backup_file = File.join backup_path, backup_filename
66
+ @database_backup_adapter.create_backup latest_backup_file
67
+ latest_backup_file
68
+ end
69
+
70
+ def remove_symlink_to_old_backup
71
+ File.delete(symlink_file) if File.exist?(symlink_file)
72
+ end
73
+
74
+ def create_symlink_to_new_backup
75
+ sh "ln -sf #{@latest_backup_file} #{symlink_file}"
76
+ end
77
+
78
+ def delete_oldest_backup
79
+ File.delete(old_backups.first) if old_backups.count >= max_num_of_backups
80
+ end
81
+
82
+ def compress_old_backups
83
+ old_backups.each do |backup_filename|
84
+ next if backup_filename =~ /.dump.gz/
85
+ sh "gzip #{backup_filename}"
86
+ end
87
+ end
88
+
89
+ # Helper Methods
90
+ def root_dir
91
+ Rails.root
92
+ end
93
+
94
+ def backup_path
95
+ if @rotate_frequency
96
+ rotated_backup_path(@rotate_frequency)
97
+ else
98
+ storage_path
99
+ end
100
+ end
101
+
102
+ def storage_path
103
+ File.join(root_dir, BACKUP_DIR)
104
+ end
105
+
106
+ def rotated_backup_path(frequency = Frequency::DAILY)
107
+ storage = File.join(root_dir, BACKUP_DIR)
108
+ now = Time.now
109
+ if now.day == 1
110
+ storage = File.join(storage, 'backup.monthly')
111
+ elsif now.wday == 0
112
+ storage = File.join(storage, 'backup.weekly')
113
+ elsif frequency == Frequency::DAILY || now.hour == 0
114
+ storage = File.join(storage, 'backup.daily')
115
+ elsif frequency == Frequency::HOURLY
116
+ storage = File.join(storage, 'backup.hourly')
117
+ end
118
+ storage
119
+ end
120
+
121
+ def old_backups
122
+ backups.sort.reject { |r|
123
+ r == @latest_backup_file || r == symlink_file
124
+ }
125
+ end
126
+
127
+ def sh(cmd)
128
+ `#{cmd}`
129
+ end
130
+ end
@@ -0,0 +1,9 @@
1
+ class DatabaseBackupAdapter
2
+ def create_backup(file)
3
+ raise NotImplementedError
4
+ end
5
+
6
+ def restore(file)
7
+ raise NotImplementedError
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module JefferiesTube
2
+ module Rails
3
+ class Engine < ::Rails::Engine
4
+ initializer 'jefferies_tube.engine', :group => :all do |app|
5
+ app.config.assets.paths << root.join('assets', 'stylesheets')
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,52 @@
1
+ require_relative 'database_backup_adapter'
2
+
3
+ class PostgresqlBackupAdapter < DatabaseBackupAdapter
4
+ def create_backup(file)
5
+ `#{password_option} pg_dump --verbose -Fc \
6
+ #{host_option} #{username_option} --file #{file} \
7
+ #{database}
8
+ `
9
+ end
10
+
11
+ def restore(file)
12
+ `#{password_option} pg_restore --verbose --clean --no-acl --no-owner \
13
+ #{host_option} #{username_option} -d #{database} \
14
+ #{file}`
15
+ end
16
+
17
+ private
18
+
19
+ def db_option(name)
20
+ value = db_config(name)
21
+ if (value)
22
+ "--#{name}=#{value}"
23
+ else
24
+ ""
25
+ end
26
+ end
27
+
28
+ def username_option
29
+ db_option('username')
30
+ end
31
+
32
+ def password_option
33
+ password = db_config('password')
34
+ if password
35
+ password_option = "PGPASSWORD=\"#{password}\""
36
+ else
37
+ password_option = ""
38
+ end
39
+ end
40
+
41
+ def host_option
42
+ db_option('host')
43
+ end
44
+
45
+ def database
46
+ db_config "database"
47
+ end
48
+
49
+ def db_config(key)
50
+ Rails.configuration.database_configuration[Rails.env][key]
51
+ end
52
+ end
@@ -0,0 +1,20 @@
1
+ require 'rack'
2
+
3
+ module JefferiesTube
4
+ module Rack
5
+ class Maintenance
6
+ attr_reader :app, :options
7
+
8
+ def initialize(app, options={})
9
+ @app = app
10
+ @options = options
11
+ end
12
+
13
+ def call(env)
14
+ message = File.read('./tmp/maintenance.txt')
15
+ message = "Sorry, this site is down for maintenance." if message.empty?
16
+ [ 503, { "Content-Type" => "text/plain", "Content-Length" => message.bytesize.to_s }, [message] ]
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,39 @@
1
+ require 'jefferies_tube'
2
+ require 'rails'
3
+
4
+ module JefferiesTube
5
+ class Railtie < ::Rails::Railtie
6
+ railtie_name :jefferies_tube
7
+
8
+ console do
9
+ ActiveRecord::Base.connection
10
+ end
11
+
12
+ config.after_initialize do |args|
13
+ begin
14
+ # if this route exists, it means the app already defined its own catchall route
15
+ # if not, this will raise an exception and we will install our catchall instead
16
+ ::Rails.application.routes.recognize_path("/jefferies_tube_404_test_route_test_supertest")
17
+ rescue ActionController::RoutingError
18
+ ::Rails.application.routes.append do
19
+ match "*a" => "jefferies_tube/errors#render_404", via: [:get, :post, :put, :options]
20
+ end
21
+ end
22
+ end
23
+
24
+ initializer "jefferies_tube.add_maintenance_middleware" do |config|
25
+ if File.exists? "tmp/maintenance.txt"
26
+ require 'jefferies_tube/rack/maintenance'
27
+ config.middleware.use 'JefferiesTube::Rack::Maintenance'
28
+ end
29
+ end
30
+
31
+
32
+ initializer "fix spring + figaro" do |config|
33
+ if defined?(Spring) && File.exists?("config/application.yml")
34
+ require 'spring/watcher'
35
+ Spring.watch "config/application.yml"
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,10 @@
1
+ require_relative 'database_backup_adapter'
2
+
3
+ class TestBackupAdapter < DatabaseBackupAdapter
4
+ def create_backup(file)
5
+ FileUtils.touch file
6
+ end
7
+
8
+ def restore(file)
9
+ end
10
+ end
@@ -0,0 +1,3 @@
1
+ module JefferiesTube
2
+ VERSION = "0.1.0"
3
+ end
data/lib/tasks/db.rake ADDED
@@ -0,0 +1,27 @@
1
+ require_relative '../jefferies_tube/database_backup'
2
+ require_relative '../jefferies_tube/postgresql_backup_adapter'
3
+
4
+ namespace :db do
5
+ desc 'restore a backup. Defaults to "db/backups/latest.dump". options: FILE=path/to/backup.dump'
6
+ task :restore do
7
+ # Only supports Postgresql for now
8
+ file = ENV['FILE'] || "db/backups/latest.dump"
9
+ DatabaseBackup.new(PostgresqlBackupAdapter.new).restore(file)
10
+ end
11
+
12
+ desc 'Capture a database backup'
13
+ task :backup do
14
+ # Only supports Postgresql for now
15
+ DatabaseBackup.new(PostgresqlBackupAdapter.new).create
16
+ end
17
+
18
+ namespace :backup do
19
+ task :daily do
20
+ DatabaseBackup.new(PostgresqlBackupAdapter.new).create_rotated(DatabaseBackup::Frequency::DAILY)
21
+ end
22
+
23
+ task :hourly do
24
+ DatabaseBackup.new(PostgresqlBackupAdapter.new).create_rotated(DatabaseBackup::Frequency::HOURLY)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,73 @@
1
+ require 'tmpdir'
2
+ require_relative '../lib/jefferies_tube/database_backup'
3
+ require_relative '../lib/jefferies_tube/test_backup_adapter'
4
+
5
+ RSpec.describe DatabaseBackup do
6
+ let(:database_backup_adapter) { TestBackupAdapter.new }
7
+ let(:database_backup) { DatabaseBackup.new database_backup_adapter, max_num_of_backups: 2 }
8
+ let(:root_dir) { Dir.mktmpdir }
9
+
10
+ before :each do
11
+ allow(database_backup).to receive(:root_dir).and_return root_dir
12
+ end
13
+
14
+ after :each do
15
+ FileUtils.remove_entry root_dir
16
+ end
17
+
18
+ describe '#create' do
19
+ it 'creates the backup directory' do
20
+ backup_dir = File.join(root_dir, DatabaseBackup::BACKUP_DIR)
21
+ expect {
22
+ database_backup.create
23
+ }.to change { Dir.exist? backup_dir }.from(false).to true
24
+ end
25
+
26
+ it 'creates a backup' do
27
+ latest_backup_file = database_backup.create
28
+ expect(File.exist? latest_backup_file).to eq true
29
+ end
30
+
31
+ it 'creates a symlink to the latest backup' do
32
+ expect {
33
+ database_backup.create
34
+ }.to change { File.symlink? database_backup.symlink_file }.from(false).to true
35
+ end
36
+
37
+ it 'compresses old backups' do
38
+ will_be_old_backup = database_backup.create
39
+ sleep 1 # To change the timestamp
40
+ database_backup.create
41
+ expect(File.exist? will_be_old_backup).to eq false
42
+ expect(File.exist? "#{will_be_old_backup}.gz").to eq true
43
+ end
44
+
45
+ it 'deletes oldest backups' do
46
+ will_be_deleted_backup = database_backup.create
47
+ sleep 1 # To change the timestamp
48
+ second_backup = database_backup.create
49
+ third_backup = database_backup.create
50
+ expect(File.exist? will_be_deleted_backup).to eq false
51
+ expect(File.exist? second_backup).to eq true
52
+ expect(File.exist? third_backup).to eq true
53
+ expect(database_backup.backups.size).to eq 3
54
+ end
55
+ end
56
+
57
+ describe '#created_rotated' do
58
+ end
59
+
60
+ describe '#restore' do
61
+ it 'restores using the database adapter' do
62
+ expect(database_backup_adapter).to receive(:restore).with 'db/backups/foobar.dump'
63
+ database_backup.restore 'db/backups/foobar.dump'
64
+ end
65
+ end
66
+
67
+ describe '#restore_most_recent' do
68
+ it 'restores latest.dump' do
69
+ expect(database_backup_adapter).to receive(:restore).with File.join(root_dir, 'db/backups/latest.dump')
70
+ database_backup.restore_most_recent
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,92 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # The generated `.rspec` file contains `--require spec_helper` which will cause
4
+ # this file to always be loaded, without a need to explicitly require it in any
5
+ # files.
6
+ #
7
+ # Given that it is always loaded, you are encouraged to keep this file as
8
+ # light-weight as possible. Requiring heavyweight dependencies from this file
9
+ # will add to the boot time of your test suite on EVERY test run, even for an
10
+ # individual file that may not need all of that loaded. Instead, consider making
11
+ # a separate helper file that requires the additional dependencies and performs
12
+ # the additional setup, and require it from the spec files that actually need
13
+ # it.
14
+ #
15
+ # The `.rspec` file also contains a few flags that are not defaults but that
16
+ # users commonly want.
17
+ #
18
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
19
+ RSpec.configure do |config|
20
+ # rspec-expectations config goes here. You can use an alternate
21
+ # assertion/expectation library such as wrong or the stdlib/minitest
22
+ # assertions if you prefer.
23
+ config.expect_with :rspec do |expectations|
24
+ # This option will default to `true` in RSpec 4. It makes the `description`
25
+ # and `failure_message` of custom matchers include text for helper methods
26
+ # defined using `chain`, e.g.:
27
+ # be_bigger_than(2).and_smaller_than(4).description
28
+ # # => "be bigger than 2 and smaller than 4"
29
+ # ...rather than:
30
+ # # => "be bigger than 2"
31
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
32
+ end
33
+
34
+ # rspec-mocks config goes here. You can use an alternate test double
35
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
36
+ config.mock_with :rspec do |mocks|
37
+ # Prevents you from mocking or stubbing a method that does not exist on
38
+ # a real object. This is generally recommended, and will default to
39
+ # `true` in RSpec 4.
40
+ mocks.verify_partial_doubles = true
41
+ end
42
+
43
+ # These two settings work together to allow you to limit a spec run
44
+ # to individual examples or groups you care about by tagging them with
45
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
46
+ # get run.
47
+ config.filter_run :focus
48
+ config.run_all_when_everything_filtered = true
49
+
50
+ # Allows RSpec to persist some state between runs in order to support
51
+ # the `--only-failures` and `--next-failure` CLI options. We recommend
52
+ # you configure your source control system to ignore this file.
53
+ config.example_status_persistence_file_path = "spec/examples.txt"
54
+
55
+ # Limits the available syntax to the non-monkey patched syntax that is
56
+ # recommended. For more details, see:
57
+ # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
58
+ # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
59
+ # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
60
+ config.disable_monkey_patching!
61
+
62
+ # This setting enables warnings. It's recommended, but in some cases may
63
+ # be too noisy due to issues in dependencies.
64
+ config.warnings = true
65
+
66
+ # Many RSpec users commonly either run the entire suite or an individual
67
+ # file, and it's useful to allow more verbose output when running an
68
+ # individual spec file.
69
+ if config.files_to_run.one?
70
+ # Use the documentation formatter for detailed output,
71
+ # unless a formatter has already been configured
72
+ # (e.g. via a command-line flag).
73
+ config.default_formatter = 'doc'
74
+ end
75
+
76
+ # Print the 10 slowest examples and example groups at the
77
+ # end of the spec run, to help surface which specs are running
78
+ # particularly slow.
79
+ #config.profile_examples = 10
80
+
81
+ # Run specs in random order to surface order dependencies. If you find an
82
+ # order dependency and want to debug it, you can fix the order by providing
83
+ # the seed, which is printed after each run.
84
+ # --seed 1234
85
+ config.order = :random
86
+
87
+ # Seed global randomization in this process using the `--seed` CLI option.
88
+ # Setting this allows you to use `--seed` to deterministically reproduce
89
+ # test failures related to randomization by passing the same `--seed` value
90
+ # as the one that triggered the failure.
91
+ Kernel.srand config.seed
92
+ end
metadata ADDED
@@ -0,0 +1,178 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jefferies_tube
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Brian Samson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-03-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: awesome_print
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: bundler-audit
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.5.0
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.5.0
97
+ - !ruby/object:Gem::Dependency
98
+ name: compass-rails
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: Useful tools for Rails.
112
+ email:
113
+ - brian@tenforwardconsulting.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - ".gitignore"
119
+ - ".rspec"
120
+ - ".ruby-gemset"
121
+ - ".ruby-version"
122
+ - Gemfile
123
+ - LICENSE.txt
124
+ - README.md
125
+ - Rakefile
126
+ - app/assets/stylesheets/jefferies_tube.sass
127
+ - app/controllers/jefferies_tube/errors_controller.rb
128
+ - app/views/errors/404.html.haml
129
+ - app/views/errors/404.json.jbuilder
130
+ - app/views/errors/404.xml
131
+ - app/views/errors/500.haml
132
+ - jefferies_tube.gemspec
133
+ - lib/jefferies_tube.rb
134
+ - lib/jefferies_tube/access_token.rb
135
+ - lib/jefferies_tube/capistrano.rb
136
+ - lib/jefferies_tube/capistrano/db.rb
137
+ - lib/jefferies_tube/capistrano/deploy.rb
138
+ - lib/jefferies_tube/capistrano/maintenance.rb
139
+ - lib/jefferies_tube/capistrano/rails.rb
140
+ - lib/jefferies_tube/capistrano/ssh.rb
141
+ - lib/jefferies_tube/database_backup.rb
142
+ - lib/jefferies_tube/database_backup_adapter.rb
143
+ - lib/jefferies_tube/engine.rb
144
+ - lib/jefferies_tube/postgresql_backup_adapter.rb
145
+ - lib/jefferies_tube/rack/maintenance.rb
146
+ - lib/jefferies_tube/railtie.rb
147
+ - lib/jefferies_tube/test_backup_adapter.rb
148
+ - lib/jefferies_tube/version.rb
149
+ - lib/tasks/db.rake
150
+ - spec/database_backup_spec.rb
151
+ - spec/spec_helper.rb
152
+ homepage: https://github.com/tenforwardconsulting/jefferies_tube/
153
+ licenses:
154
+ - MIT
155
+ metadata: {}
156
+ post_install_message:
157
+ rdoc_options: []
158
+ require_paths:
159
+ - lib
160
+ required_ruby_version: !ruby/object:Gem::Requirement
161
+ requirements:
162
+ - - ">="
163
+ - !ruby/object:Gem::Version
164
+ version: '0'
165
+ required_rubygems_version: !ruby/object:Gem::Requirement
166
+ requirements:
167
+ - - ">="
168
+ - !ruby/object:Gem::Version
169
+ version: '0'
170
+ requirements: []
171
+ rubyforge_project:
172
+ rubygems_version: 2.4.5.1
173
+ signing_key:
174
+ specification_version: 4
175
+ summary: Ten Forward Consulting useful tools.
176
+ test_files:
177
+ - spec/database_backup_spec.rb
178
+ - spec/spec_helper.rb