Permalink
Browse files

Creates a mysql user when binding.

[#54137341]
  • Loading branch information...
1 parent 6ae70bc commit 853054cfd49510709c17ea9214401c9970d9e635 Cornelia Davis, Jeff Schnitzer and alex choi committed Sep 24, 2013
View
@@ -3,6 +3,8 @@ source 'https://rubygems.org'
gem 'rails'
gem 'rails-api'
gem 'settingslogic'
+gem 'sqlite3'
+gem 'mysql2'
group :production do
gem 'unicorn'
View
@@ -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)
@@ -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)
@@ -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)
@@ -100,8 +102,10 @@ PLATFORMS
ruby
DEPENDENCIES
+ mysql2
rails
rails-api
rspec-rails
settingslogic
+ sqlite3
unicorn
@@ -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",
View
@@ -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
@@ -0,0 +1,7 @@
+---
+database:
+ admin_user: root
+ admin_password: password
+ singleton_database: development
+ ip: localhost
+ port: 3306
@@ -0,0 +1,7 @@
+---
+database:
+ admin_user: root
+ admin_password: password
+ singleton_database: test
+ ip: localhost
+ port: 3306
View
@@ -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'
@@ -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
+
View
@@ -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
View
@@ -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
View
@@ -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
+
@@ -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(
@@ -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
@@ -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.