Skip to content

Commit

Permalink
Integration for Compose for PostgreSQL service
Browse files Browse the repository at this point in the history
  • Loading branch information
jgawor committed Dec 2, 2016
1 parent 11ee4dd commit 65a9a7f
Show file tree
Hide file tree
Showing 11 changed files with 399 additions and 121 deletions.
3 changes: 2 additions & 1 deletion lib/liberty_buildpack/container/liberty.rb
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,8 @@ def download_and_install_liberty

# read opt-out of service bindings information from env (manifest.yml), and initialise
# services manager, which will be used to list dependencies for any bound services.
@services_manager = ServicesManager.new(@vcap_services, runtime_vars_dir(root), @environment['services_autoconfig_excludes'])
context = { app_dir: @app_dir, java_home: @java_home }
@services_manager = ServicesManager.new(@vcap_services, runtime_vars_dir(root), @environment['services_autoconfig_excludes'], context)

# if the liberty feature manager and repository are not being used to install server
# features, download the required files from the various configured locations. If the
Expand Down
9 changes: 7 additions & 2 deletions lib/liberty_buildpack/container/services_manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,11 @@ module LibertyBuildpack::Container
# The class that encapsulate access to services and services information.
class ServicesManager

def initialize(vcap_services, server_dir, opt_out_string)
def initialize(vcap_services, server_dir, opt_out_string, context = nil)
@logger = LibertyBuildpack::Diagnostics::LoggerFactory.get_logger
@logger.debug("init: server dir is #{server_dir}, vcap_services is #{LibertyBuildpack::Util.safe_vcap_services(vcap_services)} and opt_out is #{opt_out_string}")
@opt_out = parse_opt_out(opt_out_string)
@context = context
FileUtils.mkdir_p(server_dir)
# The collection of service instances that require full autoconfig
@services_full_autoconfig = []
Expand Down Expand Up @@ -411,7 +412,11 @@ def create_instance(element, type, config, instance_data)
# require the file
filename = File.join(File.expand_path('..', File.dirname(__FILE__)), 'services', file)
require filename
instance = class_name.constantize.new(type, config)
if class_name.constantize.instance_method(:initialize).parameters.map(&:last).map(&:to_s).include? 'context'
instance = class_name.constantize.new(type, config, @context)
else
instance = class_name.constantize.new(type, config)
end
instance.parse_vcap_services(element, instance_data)
instance
end
Expand Down
96 changes: 96 additions & 0 deletions lib/liberty_buildpack/services/compose_postgresql.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Encoding: utf-8
# IBM WebSphere Application Server Liberty Buildpack
# Copyright 2014 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

require 'base64'
require 'rexml/document'
require 'liberty_buildpack/services/client_jar_utils'
require 'liberty_buildpack/services/postgresql'

module LibertyBuildpack::Services

#------------------------------------------------------------------------------------
# The ComposePostgreSQL class is the class for Compose for PostgreSQL relational database resources.
#------------------------------------------------------------------------------------
class ComposePostgreSQL < PostgreSQL

#------------------------------------------------------------------------------------
# Initialize
#
# @param type - the vcap_services type
# @param config - a hash containing the configuration data from the yml file.
#------------------------------------------------------------------------------------
def initialize(type, config, context)
super(type, config)
@config_type = 'compose-postgresql'
@app_dir = context[:app_dir]
end

#-----------------------------------------------------------------------------------------
# parse the vcap services and create cloud properties
#
# @param element - the root element of the REXML document for runtime-vars.xml
# @param instance - the hash containing the vcap_services data for this instance
#------------------------------------------------------------------------------------------
def parse_vcap_services(element, instance)
super

credentials = instance['credentials'] || {}

# fix the database name as the relational_db uses 'name' from vcap_services
# which does not match the actual database name in compose
service_uri = credentials['uri']
uri = URI.parse(service_uri)
db_var_name = "cloud.services.#{@service_name}.connection.db"
new_element = REXML::Element.new('variable', element)
new_element.add_attribute('name', db_var_name)
new_element.add_attribute('value', uri.path[1..-1])
@db_name = "${#{db_var_name}}"

@service_cert = credentials['ca_certificate_base64']
raise "Resource #{@service_name} does not contain a #{conn_prefix}uri property" if @service_cert.nil?
end

protected

#------------------------------------------------------------------------------------
# Method to customize properties - called on create or update.
#
# @param properties_element - the properties element
#------------------------------------------------------------------------------------
def modify_properties(properties_element)
save_cert

properties_element.add_attribute('ssl', 'true')
properties_element.add_attribute('sslMode', 'verify-ca')
properties_element.add_attribute('sslRootCert', "/home/vcap/app/#{CRT_DIRECTORY}/#{CRT_FILE}")
end

private

CRT_DIRECTORY = '.compose_postgresql'.freeze

CRT_FILE = 'cacert.pem'.freeze

def save_cert
cert_dir = File.join(@app_dir, CRT_DIRECTORY)
FileUtils.mkdir_p(cert_dir)
cert_file = File.join(cert_dir, CRT_FILE)
File.open(cert_file, 'w+') { |f| f.write(Base64.decode64(@service_cert)) }
end

end

end
34 changes: 34 additions & 0 deletions lib/liberty_buildpack/services/config/compose_postgresql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# IBM WebSphere Application Server Liberty Buildpack
# Copyright 2014 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Service configuration
---
# attributes required by all service plugins
class_name : LibertyBuildpack::Services::ComposePostgreSQL
class_file : compose_postgresql.rb
server_xml_stanza : dataSource
service_filter : 'compose-for-postgresql'

# plugin specific attributes
features:
if: ['jdbc-4.0']
then: []
else: ['jdbc-4.1']

client_jars : 'postgresql-.*.jar'

driver:
version: 9.4.+
repository_root: "https://download.run.pivotal.io/postgresql-jdbc"
2 changes: 1 addition & 1 deletion lib/liberty_buildpack/services/config/postgresql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
class_name : LibertyBuildpack::Services::PostgreSQL
class_file : postgresql.rb
server_xml_stanza : dataSource
service_filter : 'postgresql|elephantsql'
service_filter : '(?<!compose-for-)postgresql|elephantsql'

# plugin specific attributes
features:
Expand Down
60 changes: 11 additions & 49 deletions lib/liberty_buildpack/services/mysql.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,63 +51,25 @@ def create_connection_manager(ds)
cm.add_attribute('maxPoolSize', cp_size)
end

protected

#------------------------------------------------------------------------------------
# Method to create a jdbc driver.
# This method will also create the library associated with the jdbc driver.
# Method to customize jdbcDriver - called on create or update.
#
# @param doc - the root element of the REXML::Document for server.xml
# @param lib_dir - the directory where client driver jars are located
# @raise if an internal inconsistency was found.
# @param jdbc_driver - an array containing all jdbcDriver elements with a given id.
#------------------------------------------------------------------------------------
def create_jdbcdriver(doc, jdbc_driver_id, lib_id, fileset_id, lib_dir)
# We assume a consistent server.xml. If the datasource did not exist, then this must be a pure "push app" use case. If it is a "push server.xml"
# case, then failure to find the datasource indicates server.xml is not consistent.
# TODO: surprisingly, the following will find the driver even if it is imbedded in another datasource.
# We could probably use XPATH /server/jdbcDriver to limit the search to global.
drivers = doc.elements.to_a("//jdbcDriver[@id='#{jdbc_driver_id}']")
# if we find an existing jdbc driver, then one of two things has occurred
# 1) case of pushing server.xml, but datasource not found (user error)
# 2) case of pushing a web app and multiple instances of a given resource type (db2) were bound. The JDBC driver was already created when
# we created the datasource for a previously processed instance. All instances of a resource type share the same JDBCDriver and library.
if drivers.empty?
# Not found, create it. The JDBC Driver is created as a global element and not nested underneath the datasource.
# puts "jdbcDriver #{jdbc_driver_id} not found, creating it"
# create the jdbcDriver
driver = REXML::Element.new('jdbcDriver', doc.root)
driver.add_attribute('id', jdbc_driver_id)
driver.add_attribute('javax.sql.XADataSource', 'org.mariadb.jdbc.MySQLDataSource')
driver.add_attribute('javax.sql.ConnectionPoolDataSource', 'org.mariadb.jdbc.MySQLDataSource')
driver.add_attribute('libraryRef', lib_id)
# create the shared library. It should not exist.
ClientJarUtils.create_global_library(doc, lib_id, fileset_id, lib_dir, @client_jars_string)
end
def modify_jdbc_driver(jdbcdrivers)
Utils.find_and_update_attribute(jdbcdrivers, 'javax.sql.XADataSource', 'org.mariadb.jdbc.MySQLDataSource')
Utils.find_and_update_attribute(jdbcdrivers, 'javax.sql.ConnectionPoolDataSource', 'org.mariadb.jdbc.MySQLDataSource')
end

#------------------------------------------------------------------------------------
# Method to create/update a datasource stanza (and all related sub-artifacts such as the JDBCDriver) in server.xml.
# Method to customize dataSource - called on create or update.
#
# @param doc - the root element of the REXML::Document for server.xml
# @param server_dir - the server directory which is the location for bootstrap.properties and jvm.options
# @param driver_dir - the symbolic name of the directory where client jars are installed
# @param available_jars - an array containing the names of all installed client driver jars.
# @param number_instances - the number of service instances that update the same service-specific server.xml stanzas
# @raise if a problem was discovered (incoherent or inconsistent existing configuration, for example)
# @param datasources - an array containing all dataSource stanzas with a given id.
#------------------------------------------------------------------------------------
def update(doc, server_dir, driver_dir, available_jars, number_instances)
super
# Find the datasource config for this service instance.
datasource = find_datasource(doc, number_instances)
unless datasource.empty?
# Make sure the correct type is added if the datasource already exists.
Utils.find_and_update_attribute(datasource, 'type', 'javax.sql.ConnectionPoolDataSource')

# Update the javax.sql.ConnectionPoolDataSource to use the mysql implementation.
jdbc_attribute = Utils.find_attribute(datasource, 'jdbcDriverRef')
unless jdbc_attribute.nil?
driver = doc.elements.to_a("//jdbcDriver[@id='#{jdbc_attribute}']")
Utils.find_and_update_attribute(driver, 'javax.sql.ConnectionPoolDataSource', 'org.mariadb.jdbc.MySQLDataSource')
end
end
def modify_datasource(datasources)
Utils.find_and_update_attribute(datasources, 'type', 'javax.sql.ConnectionPoolDataSource')
end

end
Expand Down
60 changes: 11 additions & 49 deletions lib/liberty_buildpack/services/postgresql.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,63 +37,25 @@ def initialize(type, config)
@properties_type = 'properties'
end

protected

#------------------------------------------------------------------------------------
# Method to create a jdbc driver.
# This method will also create the library associated with the jdbc driver.
# Method to customize jdbcDriver - called on create or update.
#
# @param doc - the root element of the REXML::Document for server.xml
# @param lib_dir - the directory where client driver jars are located
# @raise if an internal inconsistency was found.
# @param jdbc_driver - an array containing all jdbcDriver elements with a given id.
#------------------------------------------------------------------------------------
def create_jdbcdriver(doc, jdbc_driver_id, lib_id, fileset_id, lib_dir)
# We assume a consistent server.xml. If the datasource did not exist, then this must be a pure "push app" use case. If it is a "push server.xml"
# case, then failure to find the datasource indicates server.xml is not consistent.
# TODO: surprisingly, the following will find the driver even if it is imbedded in another datasource.
# We could probably use XPATH /server/jdbcDriver to limit the search to global.
drivers = doc.elements.to_a("//jdbcDriver[@id='#{jdbc_driver_id}']")
# if we find an existing jdbc driver, then one of two things has occurred
# 1) case of pushing server.xml, but datasource not found (user error)
# 2) case of pushing a web app and multiple instances of a given resource type (db2) were bound. The JDBC driver was already created when
# we created the datasource for a previously processed instance. All instances of a resource type share the same JDBCDriver and library.
if drivers.empty?
# Not found, create it. The JDBC Driver is created as a global element and not nested underneath the datasource.
# puts "jdbcDriver #{jdbc_driver_id} not found, creating it"
# create the jdbcDriver
driver = REXML::Element.new('jdbcDriver', doc.root)
driver.add_attribute('id', jdbc_driver_id)
driver.add_attribute('javax.sql.XADataSource', 'org.postgresql.xa.PGXADataSource')
driver.add_attribute('javax.sql.ConnectionPoolDataSource', 'org.postgresql.ds.PGConnectionPoolDataSource')
driver.add_attribute('libraryRef', lib_id)
# create the shared library. It should not exist.
ClientJarUtils.create_global_library(doc, lib_id, fileset_id, lib_dir, @client_jars_string)
end
def modify_jdbc_driver(jdbcdrivers)
Utils.find_and_update_attribute(jdbcdrivers, 'javax.sql.XADataSource', 'org.postgresql.xa.PGXADataSource')
Utils.find_and_update_attribute(jdbcdrivers, 'javax.sql.ConnectionPoolDataSource', 'org.postgresql.ds.PGConnectionPoolDataSource')
end

#------------------------------------------------------------------------------------
# Method to create/update a datasource stanza (and all related sub-artifacts such as the JDBCDriver) in server.xml.
# Method to customize dataSource - called on create or update.
#
# @param doc - the root element of the REXML::Document for server.xml
# @param server_dir - the server directory which is the location for bootstrap.properties and jvm.options
# @param driver_dir - the symbolic name of the directory where client jars are installed
# @param available_jars - an array containing the names of all installed client driver jars.
# @param number_instances - the number of service instances that update the same service-specific server.xml stanzas
# @raise if a problem was discovered (incoherent or inconsistent existing configuration, for example)
# @param datasources - an array containing all dataSource stanzas with a given id.
#------------------------------------------------------------------------------------
def update(doc, server_dir, driver_dir, available_jars, number_instances)
super
# Find the datasource config for this service instance.
datasource = find_datasource(doc, number_instances)
unless datasource.empty?
# Make sure the correct type is added if the datasource already exists.
Utils.find_and_update_attribute(datasource, 'type', 'javax.sql.ConnectionPoolDataSource')

# Update the javax.sql.ConnectionPoolDataSource to use the postgresql implementation.
jdbc_attribute = Utils.find_attribute(datasource, 'jdbcDriverRef')
unless jdbc_attribute.nil?
driver = doc.elements.to_a("//jdbcDriver[@id='#{jdbc_attribute}']")
Utils.find_and_update_attribute(driver, 'javax.sql.ConnectionPoolDataSource', 'org.postgresql.ds.PGConnectionPoolDataSource')
end
end
def modify_datasource(datasources)
Utils.find_and_update_attribute(datasources, 'type', 'javax.sql.ConnectionPoolDataSource')
end

end
Expand Down
Loading

0 comments on commit 65a9a7f

Please sign in to comment.