Skip to content
This repository has been archived by the owner on Jan 21, 2022. It is now read-only.

Commit

Permalink
Creates a mysql user when binding.
Browse files Browse the repository at this point in the history
[#54137341]
  • Loading branch information
Cornelia Davis, Jeff Schnitzer and alex choi committed Sep 24, 2013
1 parent 6ae70bc commit 853054c
Show file tree
Hide file tree
Showing 12 changed files with 188 additions and 32 deletions.
2 changes: 2 additions & 0 deletions Gemfile
Expand Up @@ -3,6 +3,8 @@ source 'https://rubygems.org'
gem 'rails'
gem 'rails-api'
gem 'settingslogic'
gem 'sqlite3'
gem 'mysql2'

group :production do
gem 'unicorn'
Expand Down
26 changes: 15 additions & 11 deletions Gemfile.lock
Expand Up @@ -26,19 +26,20 @@ GEM
thread_safe (~> 0.1)
tzinfo (~> 0.3.37)
arel (4.0.0)
atomic (1.1.12)
atomic (1.1.14)
builder (3.1.4)
diff-lcs (1.2.4)
erubis (2.7.0)
hike (1.2.3)
i18n (0.6.4)
kgio (2.8.0)
i18n (0.6.5)
kgio (2.8.1)
mail (2.5.4)
mime-types (~> 1.16)
treetop (~> 1.4.8)
mime-types (1.23)
mime-types (1.25)
minitest (4.7.5)
multi_json (1.7.8)
multi_json (1.8.0)
mysql2 (0.3.13)
polyglot (0.3.3)
rack (1.5.2)
rack-test (0.6.2)
Expand All @@ -60,12 +61,12 @@ GEM
activesupport (= 4.0.0)
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
raindrops (0.11.0)
raindrops (0.12.0)
rake (10.1.0)
rspec-core (2.14.4)
rspec-expectations (2.14.0)
rspec-core (2.14.5)
rspec-expectations (2.14.3)
diff-lcs (>= 1.1.3, < 2.0)
rspec-mocks (2.14.2)
rspec-mocks (2.14.3)
rspec-rails (2.14.0)
actionpack (>= 3.0)
activesupport (>= 3.0)
Expand All @@ -83,11 +84,12 @@ GEM
actionpack (>= 3.0)
activesupport (>= 3.0)
sprockets (~> 2.8)
sqlite3 (1.3.8)
thor (0.18.1)
thread_safe (0.1.2)
thread_safe (0.1.3)
atomic
tilt (1.4.1)
treetop (1.4.14)
treetop (1.4.15)
polyglot
polyglot (>= 0.3.1)
tzinfo (0.3.37)
Expand All @@ -100,8 +102,10 @@ PLATFORMS
ruby

DEPENDENCIES
mysql2
rails
rails-api
rspec-rails
settingslogic
sqlite3
unicorn
16 changes: 11 additions & 5 deletions app/controllers/v2/service_bindings_controller.rb
@@ -1,21 +1,27 @@

class V2::ServiceBindingsController < V2::BaseController

def update
database_settings = AppSettings.database
database_ip = database_settings.ip
database_name = database_settings.singleton_database
database_user = database_settings.admin_user
database_password = database_settings.admin_password
database_port = database_settings.port

base_database_url = "mysql://#{database_user}:#{database_password}@#{database_ip}:#{database_port}/#{database_name}"
binding_guid = params.fetch(:id)
creds = UserCreds.new(binding_guid)

base_database_url = "mysql://#{creds.username}:#{creds.password}@#{database_ip}:#{database_port}/#{database_name}"

ActiveRecord::Base.connection.execute("CREATE USER '#{creds.username}' IDENTIFIED BY '#{creds.password}';")
ActiveRecord::Base.connection.execute("GRANT ALL PRIVILEGES ON *.* TO '#{creds.username}';")
ActiveRecord::Base.connection.execute("FLUSH PRIVILEGES;")

render status: 201, :json => {
'credentials' => {
'hostname' => database_ip,
'name' => database_name,
'username' => database_user,
'password' => database_password,
'username' => creds.username,
'password' => creds.password,
'port' => database_port,
'jdbcUrl' => "jdbc:#{base_database_url}",
'uri' => "#{base_database_url}?reconnect=true",
Expand Down
13 changes: 8 additions & 5 deletions app/models/app_settings.rb
@@ -1,7 +1,10 @@
class AppSettings < Settingslogic
# This points at /var/vcap/packages, not /var/vcap/jobs which is where
# BOSH renders the config templates.
#source Rails.root.join("config/app_settings.yml")

source '/var/vcap/jobs/cf-mysql-broker/config/app_settings.yml'
case ENV['RAILS_ENV']
when 'production'
source Rails.root.join("config/app_settings_production.yml")
when 'test'
source Rails.root.join("config/app_settings_test.yml")
else
source Rails.root.join("config/app_settings_development.yml")
end
end
7 changes: 7 additions & 0 deletions config/app_settings_development.yml
@@ -0,0 +1,7 @@
---
database:
admin_user: root
admin_password: password
singleton_database: development
ip: localhost
port: 3306
7 changes: 7 additions & 0 deletions config/app_settings_test.yml
@@ -0,0 +1,7 @@
---
database:
admin_user: root
admin_password: password
singleton_database: test
ip: localhost
port: 3306
6 changes: 5 additions & 1 deletion config/application.rb
@@ -1,7 +1,7 @@
require File.expand_path('../boot', __FILE__)

# Pick the frameworks you want:
# require 'active_record/railtie'
require 'active_record/railtie'
require 'action_controller/railtie'
# require 'action_mailer/railtie'
# require 'sprockets/railtie'
Expand All @@ -27,5 +27,9 @@ class Application < Rails::Application

# Disable the asset pipeline.
config.assets.enabled = false

config.autoload_paths += %W(#{config.root}/lib)
config.autoload_paths += Dir["#{config.root}/lib/**/"]
end
end

20 changes: 20 additions & 0 deletions config/database.yml
@@ -0,0 +1,20 @@

development:
adapter: mysql2
encoding: utf8
database: development
pool: 5
username: root
password: password


# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
adapter: mysql2
encoding: utf8
database: test
pool: 5
username: root
password: password
16 changes: 16 additions & 0 deletions db/schema.rb
@@ -0,0 +1,16 @@
# encoding: UTF-8
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# Note that this schema.rb definition is the authoritative source for your
# database schema. If you need to create the application database on another
# system, you should be using db:schema:load, not running all the migrations
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
# you'll amass, the slower it'll run and the greater likelihood for issues).
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 0) do

end
13 changes: 13 additions & 0 deletions lib/user_creds.rb
@@ -0,0 +1,13 @@
require 'securerandom'
require 'digest/md5'

class UserCreds
attr_reader :username, :password

def initialize(guid)
# mysql usernames are limited to 16 chars
@username = Digest::MD5.base64digest(guid)[0...16]
@password = SecureRandom.hex(8)
end
end

38 changes: 28 additions & 10 deletions spec/controllers/v2/service_bindings_controller_spec.rb
Expand Up @@ -3,13 +3,18 @@
describe V2::ServiceBindingsController do
before { authenticate }

describe "#update" do
describe '#update' do
let(:admin_user) { 'root' }
let(:admin_password) { 'admin_password' }
let(:singleton_database_name) { 'mydb' }
let(:database_ip) { '10.10.1.1' }
let(:database_port) { '3306' }

let(:generated_username) { 'generated_user' }
let(:generated_password) { 'generated_pw' }

let(:creds) { double('user generator', username: generated_username, password: generated_password) }

before do
authenticate
allow(AppSettings).to receive(:database).and_return(
Expand All @@ -21,23 +26,36 @@
:port => database_port
)
)

UserCreds.stub(:new).with('123').and_return(creds)

ActiveRecord::Base.connection.
should_receive(:execute).
with("CREATE USER '#{generated_username}' IDENTIFIED BY '#{generated_password}';")

ActiveRecord::Base.connection.
should_receive(:execute).
with("GRANT ALL PRIVILEGES ON *.* TO '#{generated_username}';")

ActiveRecord::Base.connection.
should_receive(:execute).
with("FLUSH PRIVILEGES;")
end

it "responds with the root credentials from the configuration" do
it 'responds with generated credentials' do
put :update, id: 123

expect(response.status).to eq(201)
binding = JSON.parse(response.body)

expect(binding['credentials']).to eq(
"hostname" => database_ip,
"name" => singleton_database_name,
"username" => admin_user,
"password" => admin_password,
"port" => database_port,

"jdbcUrl" => "jdbc:mysql://#{admin_user}:#{admin_password}@#{database_ip}:#{database_port}/#{singleton_database_name}",
"uri" => "mysql://#{admin_user}:#{admin_password}@#{database_ip}:#{database_port}/#{singleton_database_name}?reconnect=true",
'hostname' => database_ip,
'name' => singleton_database_name,
'username' => generated_username,
'password' => generated_password,
'port' => database_port,
'jdbcUrl' => "jdbc:mysql://#{generated_username}:#{generated_password}@#{database_ip}:#{database_port}/#{singleton_database_name}",
'uri' => "mysql://#{generated_username}:#{generated_password}@#{database_ip}:#{database_port}/#{singleton_database_name}?reconnect=true",
)
end
end
Expand Down
56 changes: 56 additions & 0 deletions spec/requests/service_bindings_spec.rb
@@ -0,0 +1,56 @@
require 'spec_helper'


def cleanup_mysql_user(username)
ActiveRecord::Base.connection.execute("DROP USER #{username}")
rescue
end


describe 'POST /v2/service_bindings' do
let(:instance_guid) { 'INSTANCE-1' }
let(:binding_guid) { 'BINDING-1' }
let(:password) { 'somepassword' }
let(:username) { UserCreds.new(binding_guid).username }

before do
SecureRandom.stub(:hex).with(8).and_return(password)

cleanup_mysql_user(username)
end

after do
cleanup_mysql_user(username)
end

it 'creates a user and returns the new service binding' do
put "/v2/service_instances/#{instance_guid}", {service_plan_id: 'PLAN-1'}, env
put "/v2/service_bindings/#{binding_guid}", {service_instance_id: instance_guid}, env

expect(response.status).to eq(201)
instance = JSON.parse(response.body)

expect(instance.fetch('credentials')).to eq({
'hostname' => 'localhost',
'name' => 'test',
'username' => username,
'password' => password,
'port' => 3306,
'jdbcUrl' => "jdbc:mysql://#{username}:#{password}@localhost:3306/test",
'uri' => "mysql://#{username}:#{password}@localhost:3306/test?reconnect=true",
})

client = Mysql2::Client.new(
:host => AppSettings.database.ip,
:port => AppSettings.database.port,
:database => AppSettings.database.singleton_database,
:username => username,
:password => password,
)

client.query("CREATE TABLE IF NOT EXISTS data_values ( id VARCHAR(20), data_value VARCHAR(20));")
client.query("INSERT INTO data_values VALUES('123', '456');")
found = client.query("SELECT id, data_value FROM data_values;").first
expect(found.fetch('data_value')).to eq('456')
end
end

0 comments on commit 853054c

Please sign in to comment.