Sinatra configuring environments on the fly - ruby

I have successfull written a little Sinatra application and already successfully deployed it on heroku.
However I want to run that application in development mode on my local computer and I want to have it production mode on heroku once I push it to the remote repository.
Currently I can achieve either one of thos options. When I change my config.ru to the following values:
require 'rubygems'
require 'sinatra'
require 'sinatra/reloader'
require "./calc.rb"
enable :logging
set :environment, :development
set :port, 4567
I'm able to run it locally (on port 4567) via ruby config.ru. When I change the config.ru to this:
require 'rubygems'
require 'sinatra'
require 'sinatra/reloader'
require "./calc.rb"
enable :logging
set :environment, :production
set :port, 4567
run Sinatra::Application
I'm able to get it to run on Heroku (on port 80).
But I can not use the same configuration for both development and production.
I would like to have something like:
ruby config.ru dev for development and ruby config.ru for production.
Additional information:
When I try to run the production config.ru on my local machine I get:
$ ruby config.ru
(eval):2:in `method_missing': undefined method `run' for main:Object (NoMethodError)
from (eval):4:in `__send__'
from (eval):4:in `method_missing'
from config.ru:10

C:\>type tmp.ru
require 'sinatra'
configure(:production){ p "I'm production" }
configure(:development){ p "I'mma dev mode" }
configure(:sassycustom){ p "I'mma own mode" }
exit!
C:\>rackup tmp.ru
"I'mma dev mode"
C:\>rackup -E development tmp.ru
"I'mma dev mode"
C:\>rackup -E production tmp.ru
"I'm production"
C:\>rackup -E sassycustom tmp.ru
"I'mma own mode"
C:\>rackup -E notdefined tmp.ru
If you don't specify an environment, development is used by default. You can specify any environment name you want, though 'production' is very common. If you specify an environment that you don't configure, no configuration block will match. (It might be a mistake on your part, but it's not an error caught by the code.)
Note that the Sinatra documentation says that setting RACK_ENV environment variable will be used if available. This used to not work, but some time in the last few years it has been fixed!
If, for example, you can set an environment variable for your service, you can control the mode.

You can also grab ENV['RACK_ENV'] in your config.ru and use that configure your app differently. On Heroku it should run in production by default and if you rackup to fire up your server it will be development by default. Here's some sample code from one of my apps that runs in both environments with the same config file:
#\ -p 4567
require 'bundler' # gem requires
Bundler.require(:default, ENV['RACK_ENV'].to_sym) # only loads environment specific gems
if ENV['RACK_ENV'] == 'production' # production config / requires
require './lib/middleware/exceptionmailer'
use Rack::ExceptionMailer,
:to => ['me#example.com'],
:from => 'service#example.com',
:subject => 'Error Occurred on Rack Application'
else # development or testing only
use Rack::ShowExceptions
end
This way, Thin or Passenger or whatever will pick it up and the right modules will get loaded in production but you can do other configuration for development.

Look at the Heroku documentation:
http://devcenter.heroku.com/articles/rack#frameworks
That's basically what I use for my app, when I start it locally it runs on port 4567.

Related

sinatra will not load config data when started via systemd

i have a sinatra app. when loaded locally via bundler, everything is fine. when i load the app via systemd the app got started, but the config file seems to be not loading. It says that in the logs that its loaded but the values are not available inside the app
The unit file:
[Unit]
Description=PNL: Puppet Node Lister
After=network.target
[Service]
Type=simple
ExecStart=/bin/bundle exec ruby puppetdb_node_lister.rb
KillMode=process
Environment=RACK_ENV="production"
Restart=always
User=pnl
Group=pnl
WorkingDirectory=/opt/puppetdb_node_lister
[Install]
WantedBy=multi-user.target
my sinatra app:
require 'rubygems'
require 'sinatra'
require 'sinatra/config_file'
require 'json'
require 'rest-client'
require 'date'
require 'time'
require 'active_support/time_with_zone'
require 'tilt/erb'
### local methods
require_relative 'lib/methods.rb'
config_file 'config.yaml'
### request area
get '/' do
"#{settings.methods(false).inspect} <br/><br/> #{$:} <br/><br/> #{File.expand_path(File.dirname(__FILE__))}"
end
my yaml:
development:
username: 'test'
password: 'test'
puppetdb: 'puppet.example.com'
puppetsrv: 'puppet.example.com'
production:
username: 'superadmin'
password: 'somes3cret'
puppetdb: 'puppet.example.com'
puppetsrv: 'puppet.example.com'
local bundle output of /
[:app_file=, :app_file, :app_file?, :logging=, :logging, :logging?, :method_override=, :method_override, :method_override?, :run=, :run, :run?, :session_secret=, :session_secret, :session_secret?, :register, :environments=, :environments, :environments?, :username=, :username, :username?, :password=, :password, :password?, :puppetdb=, :puppetdb, :puppetdb?, :puppetsrv=, :puppetsrv, :puppetsrv?, :traps=, :traps, :traps?, :running_server=, :running_server, :running_server?, :handler_name=, :handler_name, :handler_name?]
["/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/sinatra-contrib-1.4.7/lib", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/sinatra-1.4.7/lib", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/tilt-2.0.4/lib", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/rubygems-update-2.6.4/hide_lib_for_update", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/rest-client-1.8.0/lib", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/rack-test-0.6.3/lib", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/rack-protection-1.5.3/lib", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/rack-1.6.4/lib", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/netrc-0.11.0/lib", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/multi_json-1.12.1/lib", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/mime-types-2.99.2/lib", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/http-cookie-1.0.2/lib", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/domain_name-0.5.20160310/lib", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/unf-0.1.4/lib", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/unf_ext-0.0.7.2/lib", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/unf_ext-0.0.7.2/lib", "/usr/share/gems/gems/bundler-1.7.8/lib/gems/bundler-1.7.8/lib", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/backports-3.6.8/lib", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/activesupport-4.2.6/lib", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/tzinfo-1.2.2/lib", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/thread_safe-0.3.5/lib", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/minitest-5.9.0/lib", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/json-1.8.3/lib", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/json-1.8.3/lib", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/i18n-0.7.0/lib", "/usr/share/gems/gems/bundler-1.7.8/lib", "/usr/local/share/ruby/site_ruby", "/usr/local/lib64/ruby/site_ruby", "/usr/share/ruby/vendor_ruby", "/usr/lib64/ruby/vendor_ruby", "/usr/share/rubygems", "/usr/share/ruby", "/usr/lib64/ruby/"]
/opt/puppetdb_node_lister
systemd output of /
[:app_file=, :app_file, :app_file?, :logging=, :logging, :logging?, :method_override=, :method_override, :method_override?, :run=, :run, :run?, :session_secret=, :session_secret, :session_secret?, :register, :environments=, :environments, :environments?, :traps=, :traps, :traps?, :running_server=, :running_server, :running_server?, :handler_name=, :handler_name, :handler_name?]
["/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/sinatra-contrib-1.4.7/lib", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/sinatra-1.4.7/lib", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/tilt-2.0.4/lib", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/rubygems-update-2.6.4/hide_lib_for_update", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/rest-client-1.8.0/lib", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/rack-test-0.6.3/lib", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/rack-protection-1.5.3/lib", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/rack-1.6.4/lib", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/netrc-0.11.0/lib", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/multi_json-1.12.1/lib", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/mime-types-2.99.2/lib", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/http-cookie-1.0.2/lib", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/domain_name-0.5.20160310/lib", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/unf-0.1.4/lib", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/unf_ext-0.0.7.2/lib", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/unf_ext-0.0.7.2/lib", "/usr/share/gems/gems/bundler-1.7.8/lib/gems/bundler-1.7.8/lib", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/backports-3.6.8/lib", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/activesupport-4.2.6/lib", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/tzinfo-1.2.2/lib", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/thread_safe-0.3.5/lib", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/minitest-5.9.0/lib", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/json-1.8.3/lib", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/json-1.8.3/lib", "/opt/puppetdb_node_lister/vendor/bundle/ruby/gems/i18n-0.7.0/lib", "/usr/share/gems/gems/bundler-1.7.8/lib", "/usr/local/share/ruby/site_ruby", "/usr/local/lib64/ruby/site_ruby", "/usr/share/ruby/vendor_ruby", "/usr/lib64/ruby/vendor_ruby", "/usr/share/rubygems", "/usr/share/ruby", "/usr/lib64/ruby/"]
/opt/puppetdb_node_lister
one can see, that if loaded via systemd the values in settings (which would come from the yaml) are missing.
can anybody help me with that? am i doing this right, or is there i'm missing something?
after all it was
Environment=RACK_ENV="production"
which should be more like
Environment=RACK_ENV=production
after this change, everything worked as expected!
Your systemd configuration looks good to me. You set both the environment variables and the WorkingDirectory correctly.
Replace your app that with something that just dumps out the environment and the current directory. Run this test app through bundler, and also via systemd.
You should find there's still some difference between the environment that is being set up by systemd and your alternate method. Resolve the differences. :)

How to use get programatically current host from heroku and use it in rspec test?

I'm writting a simple web app (sinatra) thats does github authentication.
I have to make a create a link with a callback param, somethig like: https://github.com/login/oauth/authorize?client_id=b5b1a33df1c7b5acccac&redirect_uri=http://localhost:4567/step2callback&scope=public_repo,user,gist,admin:repo_hook,gist, so that when the uses click on it it will authenticate, authorize my application, then send the user back to my web app.
Since i'm using rspec, I would like to make a code that pass on tests and on production, so I would like to get the current host+port to use it on the code I generate the link, something like:
HOST = get_current_host # this is the problem, how to get it?
#authorize_url = #client.authorize_url(#client.client_id,
{
:redirect_uri => "#{HOST}/step2",
:scope => 'public_repo,user,gist,admin:repo_hook,gist'
})
So, my question is how do I get my current running host on heroku, so my code would work on test and production?
You can configure different values for different environments
A simple example given below:
# File: test.rb
require 'sinatra'
# Set the values of various host names based on deployment environment
configure(:production) { set :host, "production-host" }
configure(:development) { set :host, "development-host" }
configure(:test) { set :host, "test-host" }
get '/' do
"Hi #{settings.host}"
end
Set RACK_ENV environment variable to environment value - production, development, or test
>set RACK_ENV=test
Run the Sinatra App
>ruby test.rb
[2015-12-17 20:20:46] INFO WEBrick 1.3.1
[2015-12-17 20:20:46] INFO ruby 2.1.7 (2015-08-18) [x64-mingw32]
== Sinatra (v1.4.6) has taken the stage on 4567 for test with backup from WEBrick
[2015-12-17 20:20:46] INFO WEBrick::HTTPServer#start: pid=11040 port=4567
Access the URL:
>curl http://localhost:4567
Hi test-host
If you are using Rack to run your Sinatra app, you could have following as config.ru
# config.ru
require 'sinatra'
require './test.rb'
configure(:production) { set :host, "production-host" }
configure(:development) { set :host, "development-host" }
configure(:test) { set :host, "test-host" }
run Sinatra::Application
In this case, test.rb will be simplified like below:
# test.rb
require 'sinatra'
get '/' do
"Hi #{settings.host}"
end
You can specify the environment on command line as shown below:
> rackup -E test config.ru
This answer is inspired from Sinatra configuring environments on the fly

Sidekiq web panel shows forbidden

I have mounted the Sidekiq panel in my Sinatra app like this:
require 'rubygems'
require 'bundler'
require 'sidekiq/web'
env = ENV['RACK_ENV'].to_sym || :development
Bundler.require(:default, :sinatra, env)
disable :run
Encoding.default_external = Encoding::UTF_8
set :environment, env
use Rack::ShowExceptions
use Rack::Session::Pool
use Rack::MethodOverride
Sidekiq::Web.use Rack::Session::Pool
require File.expand_path '../app/my_app.rb', __FILE__
run Rack::URLMap.new("/" => MyApp.new, "/sidekiq" => Sidekiq::Web.new)
This means my app is accessible through / and the sidekiq web panel through /sidekiq.
Now when I try to delete a job, I always get Forbidden. I read here https://github.com/mperham/sidekiq/issues/1289 and here https://github.com/mperham/sidekiq/issues/2487 but wether upgrading to rack-protection > 1.5.1 nor setting a session for Sidekiq::Web has solved the problem so far.
I'm starting my server with rackup using WEBrick, so I think this shouldn't be a server problem.
I'm using sinatra 1.4.2 with sidekiq 3.5.1. Any ideas on how to solve this?
I've found a solution. First I updated from sidekiq 3.4.2 to 4.0.1 and from sinatra 1.4.2 to 1.4.6. No problems so far.
The problem with the Forbidden message was a missing authenticity token for sidekiq's web panel. By adding the following lines, it worked:
require 'rubygems'
require 'bundler'
require 'sidekiq/web'
#####################################
# added a require for rack/protection
require 'rack/protection'
#####################################
env = ENV['RACK_ENV'].to_sym || :development
Bundler.require(:default, :sinatra, env)
disable :run
Encoding.default_external = Encoding::UTF_8
set :environment, env
use Rack::ShowExceptions
use Rack::Session::Pool
use Rack::MethodOverride
#####################################
# tell sinatra to use rack's protection methods
use Rack::Protection
#####################################
require File.expand_path '../app/my_app.rb', __FILE__
run Rack::URLMap.new("/" => MyApp.new, "/sidekiq" => Sidekiq::Web.new)
Also have a look at https://github.com/sinatra/rack-protection where all the protection methods are listed.
You may need to add this to application.rb (or an initializer like config/initializers/sidekiq.rb):
Sidekiq::Web.instance_variable_get(:#middleware).delete_if do |middleware|
middleware.first == Rack::Protection
end
That comes from a recent commit, but it's only applied to production and staging environments.
Detailed explanation about this problem.

Sinatra is using Thin instead of Puma

I'm building a Sinatra application that needs to be threadable, as I'm using sucker-punch for jobs, and I want to use the Puma server to do it though I've never used it before.
For some reason, when I start my application it runs Thin.
I uninstalled Thin and it uses Puma, which is good, but how do I stop it from starting with Thin in the future in the case this happens again?
I start my application with rackup and I have in my main app.rb file:
class App < ::Sinatra::Base
configure do
set :show_exceptions, true
set :root, Info[:root]
set :threaded, true
set :server, :puma
Tilt.register Tilt::ERBTemplate, 'html.erb'
enable :logging
use Rack::CommonLogger, Log.file
if ENV['APP_ENVIRONMENT'] == 'PROD'
set :environment, :production
set :bind, '0.0.0.0', HOST
set :show_exceptions, false
end
end
end
You need to set your server in the config.ru rackup file. In this file you can set
Rack::Handler.get('puma').run App.new
Documentation is available in "Module: Rack::Handler".
However an even better way is to just run Puma explicitly:
bundle exec puma config.ru
OR as suggested by #matt:
rackup -s puma
Just run it with bundle exec. That ensures the gems available are only the ones specified on your Gemfile.
So, even if you have thin installed, but you have puma on your Gemfile, it will pick puma.

How to get the current Rack environment in Rake?

Is there a way to get information about the current Rack environment in Rake? For example, how can I tell whether Rack is running in development or production mode?
I understand that Rake is not Rack-aware. I'm trying to avoid replicating code in nearly-identical Rake tasks between production and dev environments.
Question is old but never fetched the best practice answer or a satisfying answer at all.
The real question is: How to go sure which environment is used in a Rake task in order to load the correct configuration / hitting into correct if-conditions.
Note: As Rake doesn't give much about Rack (Rake is not using HTTP) to
rely on the RACK_ENV is basically wrong but common and handy if a Rake
task loads your main Sinatra application (the RACK_ENV is required to let
Sinatras development? / test? / production? being set correctly).
The answer: Set the environment with each Rake task call.
Command line call:
/usr/bin/rake namespace:task_name RACK_ENV=production
Cronjob call (in crontab):
cd /into/your/app/root && /usr/bin/rake namespace:task_name RACK_ENV=production --silent
Note: To specify the path of the Rake bin is not necessary if you have it in your global system variables. Your path might differs from mine used in the examples, check on Unix systems with: whereis rake
You can check the RACK_ENV in your tasks via:
puts ENV["RACK_ENV"]
As other environment variable, you can retrieve it using:
ENV['RACK_ENV']
Considering it's a Sinatra application, and that you've set the environment into config/environment.rb, you can add the following to your Rakefile:
task :environment do
require File.expand_path('config/environment', File.dirname(__FILE__))
end
task :your_task => :environment do
# task
end
Then, you can retrieve the environment (depending how you set it up in your environment.rb) with ENV['RACK_ENV'] or Sinatra::Application.environment.
Considering there isn't a config/environment.rb config file, only the application file, for instance hello_world.rb, the following works:
hello_world.rb:
require 'sinatra'
set :environment, :production
get '/' do
'Hello World'
end
Rakefile:
task :environment do
require File.expand_path('hello_world', File.dirname(__FILE__)) # your Sinatra app
end
task :your_task => :environment do
puts Sinatra::Application.environment
end
When doing rake your_task you should obtain:
> rake your_task
production
After 2.5 years, I want to share what I've found to be the best solution.
Create a .env file in the root folder of the application, and add a flag specifying the application environment:
ENVIRONMENT=development
Then use Brandon Keepers' dotenv gem to load all environment variables from this file. Now you can use any environment variables specified in .env within Rake tasks.
Rake will rely on the explicit value set in .env, so you must create separate .env files for each environment you plan on using (e.g. dev, test, staging, production, etc).
Sample Rakefile:
require 'dotenv/tasks'
task :default => :help
desc 'Show this help menu'
task :help do
puts "Available rake tasks:"
system('rake --tasks')
end
# Will run in any environment
desc 'Demo task'
task :demo_task => :dotenv do
puts "Running demo task in '#{ENV['ENVIRONMENT']}' mode"
end
# Will only run if ENVIRONMENT value in .env file is set to 'production'
desc 'Production-only task'
task :production_task => :dotenv do
if ENV['ENVIRONMENT'] == 'production'
puts "Running 'Production-only' task"
else
puts "Won't run, because the environment is not set to PRODUCTION!"
end
end
# Will only run if ENVIRONMENT value in .env file is set to 'development'
desc 'Development-only task'
task :dev_task => :dotenv do
if ENV['ENVIRONMENT'] == 'development'
puts "Running 'Development-only' task"
else
puts "Won't run, because the environment is not set to DEVELOPMENT!"
end
end
If you want to use the environment variables within your Rack or Sinatra app (which you probably do), add the following to the application's config or bootstrap block:
require 'dotenv'
Dotenv.load

Resources