/fail_fast

fail fast if some pre-conditions are not met

Primary LanguageRubyMIT LicenseMIT

fail_fast : don't start your application if some preconditions are not met.

How to install :

gem install fail_fast

How to use :

Example 0 : don't start the application on Sunday or if '_why' cannot be found (on the PATH).

Insert this code early in your program starting sequence :

require 'fail_fast'
FailFast().check do
  is_on_path '_why'
  fail "I don't work on Sunday" if 0 == Time.now.wday
end

If _why can't be found :

  • the application will exit immediately and

  • you will see this report :

    +------------------------------------------------------------------------------------------ | FAIL_FAST error : precondition(s) not met. +------------------------------------------------------------------------------------------ | _why could not be found on the path +------------------------------------------------------------------------------------------

Example 1 : don't start the application if the DB cannot be reached.

Early in your project boot sequence insert this code :

require 'fail_fast'
FailFast("path/to/database.yml").check do
  has_active_record_db_for  'production', :message => 'The main DB cannot be reached :'
end

remark : the :message part is always optional.

If the DB connection fails,

  • the application will exit immediately and

  • you will see this report :

    +------------------------------------------------------------------------------------------

    FAIL_FAST error : precondition(s) not met in
    file : "path/to/database.yml"
    +------------------------------------------------------------------------------------------
    error key value
    +------------------------------------------------------------------------------------------
    The main DB cannot be reached :
    * active_record_db_connection_error production Unknown database 'a_db'
    +------------------------------------------------------------------------------------------

Remark : check will call exit(1) at the end of the first block with an error.
If you want to collect and report all the errors before exiting, use check_now.but_fail_later (see Example 2 below).

Example 2 : collect errors in multiple blocks.

require 'fail_fast'
FailFast('database.yml').check_now.but_fail_later do
  has_active_record_db_for  'production'
end

FailFast('database.mongo.yml').check_now.but_fail_later do
  has_mongoDB_for   Rails.env
end

FailFast('path_to/config.yml', prefix=Rails.env).check_now.but_fail_later do
  has_values_for    'author/fname', 'author/lname'
  has_email_for     'newsletter/to_address'

  only_if Rails.env.production? do
    has_url_for       'bug_tracker/url', :reachable => true
  end

  directory_exists_for  '/tmp', :message => 'the log '
  file_exists_for       'public/nda.pdf'

  skip_if Rails.env.development? do
    fail "I don't work on Sunday" if 0 == Time.now.wday
   end
end

FailFast.fail_now   # exit it an error was detected in any of the 3 blocks above.

If it fails, you'll get a report like this :

+------------------------------------------------------------------------------------------
|   FAIL_FAST error : precondition(s) not met in
|  -----------------
|     file         :  "path/to/database.yml"
|     keys prefix  :  (none)
+------------------------------------------------------------------------------------------
|      error                                   key                                value
+------------------------------------------------------------------------------------------
|  * active_record_db_connection_error      production                          Unknown database 'a_db'
+------------------------------------------------------------------------------------------

+------------------------------------------------------------------------------------------
|   FAIL_FAST error : precondition(s) not met in
|  -----------------
|     file         :  "./spec/_/fixtures/simple.yml"
|     keys prefix  :  none
+------------------------------------------------------------------------------------------
|      error                                   key                                value
+------------------------------------------------------------------------------------------
|  * missing_value                          first_keyNOT                                                       
|  * missing_value                          last_keyNOT                                                        
|  * missing_value                          number_sixNOT                                                      
|  * missing_value                          testNOT/mongoDB/database                                           
|  * value_does_not_match                   last_key                            dernier                        
|  * not_an_email                           test/host                           localhost                      
|  * not_a_url                              test/host                           localhost                      
|  * url_not_reachable                      test/url_not_reachable              http://xxx.zzz                 
|  * directory_not_found                                                        /foobarbaz                     
|  * directory_not_found                    test/a_file                         ./spec/_/fixtures/simple.yml
|  * file_not_found                                                             /tmp/foo/bar/??nOTaFile        
|  * file_not_found                         test/a_directory                    ./spec/_/fixtures
|  * mongoDB_server_not_found                                                   10.0.0.123                     
|  * mongoDB_server_not_found               test/mongoDB                        localhost                      
|  * mongoDB_db_not_found                                                       not_a_known_db                 
|  * mongoDB_db_not_found                   test/unknown_mongoDB_db             unknown_mongoDB_db             
|  * active_record_db_connection_error                                          Unknown database 'some-db'     
|  * active_record_db_connection_error      db_connection                       Unknown database 'a_db'
|  * fail                                                                       a custom failure message
+------------------------------------------------------------------------------------------

Example 3 : capture - and handle - individual errors

FailFast().check_now.but_fail_later do
  directory_exists_for  '/cache'                # default error handling/message

  unless directory_exists_for  '/log'           # custom error handling
    puts "The /log directory is missing"        #  ...
    puts "create it with $ mkdir /log"          #  ...
  end                                           #  ...
end

FailFast.fail_now   # exit it an error was detected in any of the 3 blocks above.

Example 4 : print an additional custom message if errors were detected

...	# code like in the cases above.

if FailFast.failed?
    puts "Cannot start the application due to the problems mentioned above."
	puts "You can skip those test with the SKIP_FAIL_FAST environment variable"
	FailFast.fail_now	unless 'true'==ENV['SKIP_FAIL_FAST']
end

Example 5a : send errors report to Hoptoad (http://hoptoadapp.com)

FailFast().check do
    ...                 # errors only reported to stdout
end
...
require 'hoptoad_notifier'
FailFast.report_to :hoptoad => '<your-api-token>'       # <- add this in your FF script
...
FailFast().check do
    ...                 # errors are (also) reported to your Hoptoad account
end

Example 5b : send errors report to Exceptional (http://getexceptional.com)

FailFast().check do
    ...                 # errors only reported to stdout
end
...
require 'exceptional'                                       # <- add this in your FF script
FailFast.report_to :exceptional => '<your-api-token>'       # <- add this in your FF script
...
FailFast().check do
    ...                 # errors are (also) reported to your Hoptoad account
end

Remark : Error reporters

  • :stdout is the default (cannot be disabled)
  • you can combine the error reporters (ex: stdout + :hoptoad + :exceptional)

Info :

This gem DSL lets you write preconditions-scripts that you run early in the boot sequence of an application. An exception is raised if one or more tests fail, and you get a detailled report of all the problems encountered.

Some rules are based on the contents of configuration files (database.yml, config.yml, etc...) :

  • can a database connnection be established?
  • is the mongoDB server active?
  • is there an :application_name value in config.yml, and does it match a regexp pattern?
  • is the value of :info_email_ a valid email?
  • is the value of :sponspor_link a valid url, and is the site up?

You can also add custom rules, not related to any config files :

  • is there a /tmp, or an public/upload directory on the server?
  • can the server access http://google.com?
  • is there a public/nda_pdf file?
  • etc..

Features :

free/direct commands (not linked to the yaml file contents) :

fail "I don't work on Sunday" if (0 == Time.now.wday)

directory_exists      '/tmp'
file_exists           '/Users/me/.bash_profile'
has_mongoDB           'localhost', 'db_app_1'
has_active_record_db  :host => 'dbserv', :adapter => 'mysql', :database => 'db'

keyed commands (linked to a value found in a yaml file) :

####Test values linked to a key :

presence :

has_value_for   :application_name
has_values_for  'author/fname', 'author/lname'

contents <-> a regexp or pattern :

has_value_for   'level',  /(alpha|beta|production)/   
has_url_for     'bug_tracker/url'   
has_email_for   'newsletter/to_address'   
  • customize it

    nda_file = value_of(:nda_file) fail 'NDA is too old' if (Time.now - File.mtime(nda_file)) > (24 * 60 * 60) * 365

Test the file system and .. :

directory_exists    '/home/ci/uploads'
file_exists         '/home/ci/config/secret.key'

# the keyed version :
directory_exists_for  'assets-upload_dir'
file_exists_for       'assets-nda_pdf_file'

is_on_path      'jruby'         # fail if jruby cannot be found 
is_on_path_for  :ruby_engine

Test external services :

# is a webserver up ?
has_url_for     'bug_tracker/url', :reachable => true
has_url_for     'bug_tracker/url', :reachable => true, :may_add_trailing_slash => true

# can we connect to a mongoDB db/server :
has_mongoDB_for   'test/mongoDB'
has_mongoDB_for   'test/unknown_mongoDB_db', :check_database => false

# can we connect to a SQL db :
has_active_record_db_for 'production/db_connection'

Misc :

fail "I don't work on Sunday" if 0 == Time.now.wday

Control commands :

skip_if <condition> do .. end
only_if <condition> do .. end