Dhanu Gurung
Dhanu Gurung

Reputation: 8840

Set database.yml using environment variables in Sinatra

I have a very weird requirement in a project I am working on.

I have file stored in some location /etc/config/config.json which contains database information like Host, Port, Username or Passwordor roughly looks as below:

{ 
      "mysql-db": {
            "host": "172.17.0.27",
            "port": 3306,
            "password": "root",
            "username": "root"
        }
}

I am building my small web-app based on Sinatra and using sinatra-activerecord for working with MySql database.

My database.yml file looks like this:

development:
  adapter: mysql2
  database: toopaste
  host: <%= ENV["MYSQL_DB_HOST"] %>
  port: <%= ENV["MYSQL_DB_PORT"] %>
  username: <%= ENV["MYSQL_DB_USERNAME"] %>
  password: <%= ENV["MYSQL_DB_PASSWORD"] %>

What I was trying?

I created a executable file like setup.rb as:

#! /usr/bin/env ruby

require 'json'

FILE_PATH = "/etc/atlantis/config/konfig.json"

data = JSON.parse(File.read(FILE_PATH))

system("export MYSQL_DB_HOST=#{data['mysql-db']['host']}")
system("export MYSQL_DB_PORT=#{data['mysql-db']['port']}")
system("export MYSQL_DB_USERNAME=#{data['mysql-db']['username']}")
system("export MYSQL_DB_PASSWORD=#{data['mysql-db']['password']}")

This doesn't set env variables MYSQL_DB_HOST or others variables to be used by config/database.yml file.

Any idea how to accomplish such job?

One way I could think is "Dynamically generate whole database.yml file after reading config.json params. But would like to know if there is better solution available.

Upvotes: 1

Views: 4587

Answers (3)

matt
matt

Reputation: 79743

The system method creates a new subshell and executes the command in it. When the command sets an environment variable then that environment variable is set in that subshell, the change does not propagate back to the parent process. This means that those environment variables are not being set.

To set environment variables in the current process, you can simply access ENV directly. So instead of system("export MYSQL_DB_HOST=#{data['mysql-db']['host']}") do this:

ENV['MYSQL_DB_HOST'] = data['mysql-db']['host']

(and similarly for the other settings).


This should fix your problem, but since you are reading the settings yourself directly from the json config file there arguably isn’t much point using environment variables. It would be more direct to access the settings more directly from the Erb/Yaml file. Ideally you’d want to pass the settings to the Erb evaluation in some way, but Sinatra-ActiveRecord doesn’t appear to allow you to set the binding or context of the Erb evaluation in any way. An alternative could be to use a global variable for the data hash:

# When reading the json, use a global instead,
# and pick out the 'mysql-db' key
$data = JSON.parse(File.read(FILE_PATH))['mysql-db']

The in the database.yml:

development:
  adapter: mysql2
  database: toopaste
  host: <%= $data['host'] %>
  port: <%= $data['port'] %>
  username: <%= $data['username'] %>
  password: <%= $data['password'] %>

Upvotes: 0

Phobos
Phobos

Reputation: 737

I assume this configuration file is outside of your application's reach otherwise you could just read the json file directly. Otherwise you could have a parser of the JSON and transform that into YAML. Something like this would work:

require 'json'
require 'yaml'

json = JSON.parse(File.read('./test.json'))
database = Hash.new
database[:development] = json
File.open("./test.yaml","w"){|h| h.write database.to_yaml }

Your YAML should look like this.

$ cat test.yaml 
---
:development:
  mysql-db:
    host: 172.17.0.27
    port: 3306
    password: root
    username: root

I assume you can add the additional parameters to the hash to get something like the bellow configurations.

config/production.yaml

database:
   adapter: mysql2
   host: localhost
   port: 3306
   database: myappdb
   username: myprodusername
   password: myprodpassword

config/development.yaml

database:
   adapter: mysql2
   host: localhost
   port: 3306
   database: myappdb_dev
   username: mydevuser
   password: mydevpassword

And then load them into your app like this.

config.ru

require 'sinatra'
require 'yaml'

configure :production do
    @config = YAML.load_file("config/#{ENV["RACK_ENV"]}.yaml")
    #some other things that you do in prod
end
configure :development do
    @config = YAML.load_file("config/#{ENV["RACK_ENV"]}.yaml")
    #some other things that you only do in dev
end

Starting the app

$ RACK_ENV=development puma (or whatever other server you use like thin)

or for prod

$ RACK_ENV=production puma (or whatever other server you use like thin)

Upvotes: 0

xmjw
xmjw

Reputation: 3434

I have a feeling that Sinatra doesn't process the environment variables into the database.yml file. Rails does... You can do this, but I think it's a bit of a faff. I think you have to put the YML file through as an ERB template or something.

Some other options:

Dynamically write the entire database.yml file from your setup.rb - although I wouldn't do this. The load it form there as normal.

Or, use the Sinatra config to set your Database connections details form your preferred file. Example in the sinatra-activerecord read me.

set :database, {adapter: 'mysql', database: ENV['MY_SQL_DB_HOST']}

This would seem cleaner to me. In fact, I would go a little further and use Sinatra config to do the whole thing (load the file, and get the parameters from there). This way the code becomes more explicit, and easier to change in the future. i.e., and this is just off the top of my head, so you may need to adjust:

configure do
  FILE_PATH = "/etc/atlantis/config/konfig.json"
  data = JSON.parse(File.read(FILE_PATH))
  set :DB_PASSWORD = data['mysql-db']['password']
  # ETC...
  set :database, {  ... }
end

Hope this helps.

Upvotes: 1

Related Questions