I have a Sinatra application that uses sinatra/config_file to parse a YAML configuration file. The file has entries for various environments such as development, test, production.
class MyApp < Sinatra::Base
register Sinatra::ConfigFile
config_file 'config.yml'
##client = Mysql2::EM::Client.new(
:host => settings.host,
:username => settings.username,
:password => settings.password,
:database => settings.database
)
Currently when I run the application, the settings are retrieved from the development section. And the only way that I found to make it use a different one was to set
ENV['RACK_ENV'] = 'production'
in my myapp.rb file.
I do not like editing source, how do I set the environment the proper way?
According to the Sinatra documentation, you can run your app in a different environment by using an argument at the command line, as such:
ruby my_app.rb -e [ENVIRONMENT].
Related
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
I have a sinatra app in which i have a yml file to set environment variables, i call them using this method
module MyConfig
def config
environment = ENV["RACK_ENV"] || "development"
YAML.load_file("./config/config.yml")[environment]
end
end
so when i want to use a variable i do this for example
aws_access_key_id = config['aws_access_key']
I have a .gitignore file that ignores config.yml when pushing to github for example.So when I push to heroku these environment variables will not be accessible?
So this leaves me with using the heroku way of setting them like so
heroku config:add aws_access_key= myapikey
but heroku accesses these like
aws_access_key_id = ENV['aws_access_key']
How can i set my dev environment to use method config and heroku use ENV, am i looking at this the wrong way? or does my config method do this for me?
Any help appreciated
RAKEFILE
require 'active_support/core_ext'
require './config/config.rb'
require 'bundler/setup'
Bundler.require(:default)
include MyConfig
AssetSync.configure do |con|
con.fog_provider = 'AWS'
con.fog_region = 'eu-west-1'
con.fog_directory = config['fog_directory']
con.aws_access_key_id = config['aws_access_key']
con.aws_secret_access_key = config['aws_secret_key']
con.prefix = "assets"
con.public_path = Pathname("./public")
end
namespace :assets do
desc "Precompile assets"
task :precompile do
AssetSync.sync
end
end
Update:
I now use the dotenv gem instead of the example below. So instead of ignoring the env.rb file, I now ignore the .env file with Git.
Original post:
Try this,
# /env.rb
ENV['aws_bucket'] = 'my_bucket'
ENV['aws_access_key'] = 'my_access_key'
ENV['aws_access_secret'] = 'my_access_secret'
This file sets the same ENV values as heroku config would do.
# /config.rb
require './env' if File.exists?('env.rb')
The env.rb will only get required if it exists.
# /.gitignore
/env.rb
The env.rb has been added to the .gitignore file so it isn't kept in Git.
You would then access the values using ENV['key'] instead of config['key'].
You might need to change the path to the env.rb if it's not in the same directory as the config.rb file.
EDIT:
From looking at your Rakefile in the previous question, you need to change it to this:
# Rakefile
require 'bundler/setup'
Bundler.require(:default)
require './env' if File.exists?('env.rb')
AssetSync.configure do |con|
con.fog_provider = 'AWS'
con.fog_region = 'eu-west-1'
con.fog_directory = ENV['aws_bucket']
con.aws_access_key_id = ENV['aws_access_key']
con.aws_secret_access_key = ENV['aws_access_secret']
con.prefix = "assets"
con.public_path = Pathname("./public")
end
namespace :assets do
desc "Precompile assets"
task :precompile do
AssetSync.sync
end
end
I've assumed that the only method in /config/config.rb was the config method so I've removed the,
require './config/config.rb'
include MyConfig
And swapped the config[key] for the ENV[key] values defined in env.rb. You may need to change the key names to match up.
You could delete the yaml, and describe the environment variables in a .env file then start your app with foreman start. See https://devcenter.heroku.com/articles/config-vars#local-setup
Or keep your hybrid system, where you load a yaml in dev, and use environment variables on heroku.
I do something similar to Sam's suggestion, but a little bit different. I have a YAML config file too, but I wrap the reading of it in a Rake task, which then runs the app.
# in the Rakefile
require 'yaml'
def set_connstring
s = %Q!postgres://#{ENV["DB_APP"]}#localhost/#{ENV["DB_APP"]}!
ENV['DATABASE_URL'] ||= ENV["RACK_ENV"] == "test" ? "#{s}.test" : s
end
def basic_environment
warn " Setting up environment..."
file = File.expand_path( File.join File.dirname(__FILE__), "./config.yml" )
if File.exist? file
YAML.load_file(file).each do |k,v|
warn "-> #{k}"
ENV[k.upcase] = v
end
end
set_connstring()
end
namespace :app do
desc "Set up the environment locally"
task :environment do
basic_environment()
end
desc "Run the app locally"
task :run_local => "app:environment" do
exec "bin/rackup config.ru -p #{ENV['RUN_LOCAL_PORT']}"
end
end
It means I can run it locally without any code inside the app to deal with this.
Edit: a quick aside, I notice you have Bundler.require(:default) in your Rakefile. If you use bundle install --binstubs then Bundler installs all executables into a dir named "bin/" within the project. Then, if you run any of those executables they automatically use the libraries installed by Bundler, no need to require via Bundler. See http://gembundler.com/v1.2/man/bundle-exec.1.html.
Exporting directly from heroku admin:
Settings -> Reveal Config Vars
Then open browser js console, paste this and type enter...
k=[];
$(".config-var-list input").map(function(y, x){k.push($(x).val())});
v=[];
$(".config-var-list textarea").map(function(y, x){v.push($(x).val())});
ret="";
k.map(function(x, i){ret+=k[i]+"\t"+v[2*i]+"\n"});
console.info(ret);
tl;dr How can I get a single Sinatra app to start up very differently on different servers via customizations to config.ru?
Background
I have a single web application written using Sinatra that's run on different servers. Currently the codebase for these servers is forked because there are some non-trivial differences in the way (discrete) parts of them work. For example:
one server authenticates users via an intranet LDAP server, while another server uses a simpler local database table lookup.
one server uses an external cron job to periodically update some statistics, while another (Windows-based) server uses an internal sleepy Thread.
one server stores certain metadata in a local table, while another server pulls the metadata from an external Wiki via screen scraping (!).
…and so on.
I'd like to get these code bases completely shared (single Git repo). I envision that each server would have one slightly-differing configuration file that causes the app to be started up differently.
Abandoned Solutions
I could change the behavior of the app based on environment variables. As there are a not-tiny number of variations in behavior, I'd rather not hide the settings in environment variables.
I could create my own "server-settings.rb" file that is unique to each machine, require it in my app.rb, and then change the configuration there. However, this seems to possibly be re-inventing the wheel. I already have a file named config.ru for each server. Shouldn't I be using this?
The Current Code
My config.ru for the app currently is simply:
require ::File.join( ::File.dirname(__FILE__), 'app' )
run MyApp.new
And the app.rb that it requires is, in essence:
require 'sinatra'
require_relative 'helpers/login' # customized for LDAP lookup on this server
class MyApp < Sinatra::Application
use Rack::Session::Cookie, key:'foo.bar', path:'/', secret:'ohnoes'
set :protection, except: [:path_traversal, :session_hijacking]
configure :production do
# run various code that depends on server settings, e.g.
Snapshotter.start # there is no cron on this machine, so we do it ourselves
end
configure :development do
# run various code that depends on server settings
end
end
The Question
I'd like to make config.ru live up to its name, and have it look something like this:
require ::File.join( ::File.dirname(__FILE__), 'app' )
run MyApp.new( auth: :ldap, snapshot:false, metadata: :remote_wiki, … )
How can I modify my application to change its configuration behavior based on settings supplied via config.ru? Or is this an abuse of config.ru, trying to use it for totally the wrong thing?
As soon as I started reading the question the first answer to pop into my head was "environment variable" but you scotched that straight away :)
I'll go with a mixture of one of your coulds and the desired outcome code, as it's how I structure things…
Because I want to be able to test my applications more easily, I take most of the Ruby out of the config.ru and into a separate config.rb file and leave config.ru to be a bootstrap file. So my standard skel is:
config.ru
# encoding: UTF-8
require 'rubygems'
require 'bundler'
Bundler.setup
root = File.expand_path File.dirname(__FILE__)
require File.join( root , "./app/config.rb" )
# everything was moved into a separate module/file to make it easier to set up tests
map "/" do
run APP_NAME.app
end
app/config.rb
# encoding: utf-8
require_relative File.expand_path(File.join File.dirname(__FILE__), "../lib/ext/warn.rb")
require_relative "./init.rb" # config
require_relative "./main.rb" # routes and helpers
require 'encrypted_cookie'
# standard cookie settings
COOKIE_SETTINGS = {
:key => 'usr',
:path => "/",
:expire_after => 86400, # In seconds, 1 day
:secret => ENV["LLAVE"],
:httponly => true
}
module APP_NAME # overall name of the app
require 'rack/ssl' # force SSL
require 'rack/csrf'
if ENV["RACK_ENV"] == "development"
require 'pry'
require 'pry-nav'
end
# from http://devcenter.heroku.com/articles/ruby#logging
$stdout.sync = true
ONE_MONTH = 60 * 60 * 24 * 30
def self.app
Rack::Builder.app do
cookie_settings = COOKIE_SETTINGS
# more security if in production
cookie_settings.merge!( :secure => true ) if ENV["RACK_ENV"] == "production"
# AES encryption of cookies
use Rack::Session::EncryptedCookie, cookie_settings
if ENV["RACK_ENV"] == "production"
use Rack::SSL, :hsts => {:expires => ONE_MONTH}
end
# to stop XSS
use Rack::Csrf, :raise => true unless ENV["RACK_ENV"] == "test"
run App # the main Sinatra app
end
end # self.app
end # APP_NAME
The initial reason I did this was making it easy to run the app in specs:
shared_context "All routes" do
include Rack::Test::Methods
let(:app){ APP_NAME.app }
end
but it makes sense to me to keep this code with the rest of the application code, so to speak, as I can bundle things together, run other apps etc. I've used this to conditionally load different examples into the specs in a few projects (it helps cut down on duplicated effort and check the examples really work), so I don't see why you couldn't use it to conditionally load configurations.
This way you get to choose to use a conditional in the config.ru as to which config.rb file you would use, or use an env var in the config.rb as to which definiton of self.app to use , or pass in an options hash to self.app…
With your set up I'd rename the APP_NAME module to MyApp, and the Sinatra class to App (because quite often I'll have an website that runs a front end and an API, so the Sinatra classes get named by their function (App, API etc) and wrapped in a module named after the site) and end up with:
config.ru
map "/" do
run MyApp.app( auth: :ldap, snapshot:false, metadata: :remote_wiki )
end
config.rb
def self.app( opts={} )
opts = DEFAULT_OPTIONS.merge opts
# …
run App
end
It'll be interesting to see how other people tackle this.
I have the following configuration for both my production and development environments.
config.action_mailer.smtp_settings = {
:address => "smtp.mandrillapp.com",
:port => 587,
:user_name => ENV["MANDRILL_USERNAME"],
:password => ENV["MANDRILL_PASSWORD"]
}
I googled this for a while but I didn't find an answer that made me understand. How do I set the ENV variables both for development and for production for my rails project?
For development, you'll want to use a configuration file that is ignored in version control. It could be YAML, JSON, ruby, bash... it's a trivial choice. You'll then use an initializer file to make sure these variables are loaded when the app boots. For production, you can copy the config file (it may even have different settings) into the right place as part of your deploy process; or if you're using heroku, you can set these from command line using heroku config:add YADA=yada.
Here's a basic example with settings coming from a YAML file:
# config/settings.yml
development:
MANDRILL_USERNAME=secret_username
MANDRILL_PASSWORD=secret_password
ANOTHER_SECRET_SETTING=the_list_goes_on_and_on
production:
MANDRILL_USERNAME=different_username
MANDRILL_PASSWORD=another_password
ANOTHER_SECRET_SETTING=get_the_idea?
# .gitigore
# ...
config/settings.yml
# config/initializers/environment_settings.rb
environment_settings = YAML.load_file('./config/settings.yml')[Rails.env]
environment_settings.each do |key, value|
ENV[key] ||= value
end
They're environment variables; you don't set them for development and production.
You can set application config variables in a variety of ways, or you can set them in the shell before running, or you can use environment-specific names, or...
This is a non-rails app, just a simple ruby script that uses rake etc. to automate some things.
My folder layout is this:
/scripts/Rakefile
/scripts/config/config.yml
/scripts/tasks/*.rake (various rake files with namespaces to organize them)
/scripts/lib/settings.rb
Now I want to create a Settings class that will load the config yaml file, and then expose properties/methods for the contents of the yaml file.
The yaml file has separate sections for development and production.
development:
scripts_path: '/dev/mygit/app1/scripts/'
production:
scripts_path: '/var/lib/app1/scripts/'
My rakefile so far looks like:
$LOAD_PATH.unshift File.expand_path('..', __FILE__)
#imports
require 'fileutils'
require 'rubygems'
require 'active_record'
require 'yaml'
require 'logger'
require 'ar/models'
require 'lib/app1'
env = ENV['ENV'] || 'development'
config = YAML::load(File.open('config/config.yml'))[env]
Dir.glob('tasks/*.rake').each { |r| import r }
I need help with the Settings.rb file, is this right?
module App1
class Settings
def initialize(config_path, env)
config = YAML.load(File.open(config_path))
end
def scripts_path
end
end
end
How can I pass in the env, and then read the correct value from the config for each method like scripts_path etc?
Now suppose each *.rake file needs to reference my Settings.rb file somehow (to get the config related information). How should I do this? Since my settings needs the path of the config.yml file, do I have to do this in each rake file?
Update
Sorry, this isn't a Rails app, just some ruby scripts.
I would do it quite simple.
You don't need a complex solution.
require 'ostruct'
require 'yaml'
MY_ENV = ENV['ENV'] || 'development'
CONFIG = OpenStruct.new(YAML.load_file("config/config.yml")[MY_ENV])
Stick this at the top of your rakefile
and CONFIG will be available in all rake tasks.
Just call CONFIG.scripts_path
Inside my applications I do something of this sort.
# config/application.yml
development:
some_variable: a string
production:
some_variable: a different string
Then in application.rb I load it up.
# config/application.rb
module MyApp
def self.config
#config ||= OpenStruct.new(YAML.load_file("config/application.yml")[Rails.env.to_s])
end
class Application < Rails::Application
...
In this case, anywhere the environment is loaded I can say
MyApp.config.some_variable
To get access to this inside a rake task, I just need to include environment
task :something => :environment do
MyApp.config.some_variable
# do something with it
end