cf-runtime 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,28 @@
1
+ #cf-runtime
2
+
3
+ A library for interacting with Cloud Foundry services. Provides methods for obtaining pre-configured connection objects and connection properties.
4
+
5
+ _Copyright (c) 2011-2012 VMware, Inc. Please see the LICENSE file._
6
+
7
+ require 'cfruntime'
8
+
9
+ #Connect to mysql service named 'mysql-test'
10
+ client = CFRuntime::Mysql2Client.create_from_svc 'mysql-test'
11
+
12
+ #Connect to a single service of type MongoDB
13
+ connection = CFRuntime::MongoClient.create
14
+ db = connection.db
15
+
16
+ #Obtain connection properties for 'myservice'
17
+ if CFRuntime::CloudApp.running_in_cloud?
18
+ service_props = CFRuntime::CloudApp.service_props 'myservice'
19
+ end
20
+
21
+ #Obtain connection properties for single service of type MySQL
22
+ service_props = CFRuntime::CloudApp.service_props 'mysql'
23
+
24
+ #Other handy methods
25
+ CFRuntime::CloudApp.host
26
+ CFRuntime::CloudApp.port
27
+ CFRuntime::CloudApp.service_names
28
+ CFRuntime::CloudApp.service_names_of_type 'mysql'
@@ -0,0 +1,26 @@
1
+ begin
2
+ require 'cfruntime/amqp'
3
+ rescue LoadError
4
+ end
5
+ begin
6
+ require 'cfruntime/carrot'
7
+ rescue LoadError
8
+ end
9
+ begin
10
+ require 'cfruntime/mongodb'
11
+ rescue LoadError
12
+ end
13
+ begin
14
+ require 'cfruntime/mysql'
15
+ rescue LoadError
16
+ end
17
+ begin
18
+ require 'cfruntime/postgres'
19
+ rescue LoadError
20
+ end
21
+ begin
22
+ require 'cfruntime/redis'
23
+ rescue LoadError
24
+ end
25
+ require 'cfruntime/properties'
26
+ require 'cfruntime/version'
@@ -0,0 +1,43 @@
1
+ require 'amqp'
2
+ module CFRuntime
3
+ class AMQPClient
4
+
5
+ # Creates and returns an +AMQP+ connection to a single rabbitmq service.
6
+ # Passes optional other_options and block arguments to +AMQP.connect+.
7
+ # Raises +ArgumentError+ If zero or multiple rabbitmq services are found.
8
+ def self.create(other_options = {}, &block)
9
+ service_names = CloudApp.service_names_of_type('rabbitmq')
10
+ if service_names.length != 1
11
+ raise ArgumentError.new("Expected 1 service of rabbitmq type, " +
12
+ "but found #{service_names.length}. " +
13
+ "Consider using create_from_svc(service_name) instead.")
14
+ end
15
+ create_from_svc(service_names[0],other_options,&block)
16
+ end
17
+
18
+ # Creates and returns an +AMQP+ connection to a rabbitmq service with the
19
+ # specified name.
20
+ # Passes optional other_options and block arguments to +AMQP.connect+.
21
+ # Raises +ArgumentError+ If specified rabbitmq service is not found.
22
+ def self.create_from_svc(service_name, other_options = {}, &block)
23
+ AMQP.connect(options_for_svc(service_name), other_options, &block)
24
+ end
25
+
26
+ # Merges provided options with connection options for specified rabbitmq service.
27
+ # Returns merged Hash containing (:user, :pass, :vhost, :host, :port).
28
+ # Raises +ArgumentError+ If specified rabbitmq service is not found.
29
+ def self.options_for_svc(service_name,options={})
30
+ service_props = CFRuntime::CloudApp.service_props(service_name)
31
+ if service_props.nil?
32
+ raise ArgumentError.new("Service with name #{service_name} not found")
33
+ end
34
+ cfoptions = options
35
+ cfoptions[:host] = service_props[:host]
36
+ cfoptions[:port] = service_props[:port]
37
+ cfoptions[:user] = service_props[:username]
38
+ cfoptions[:pass] = service_props[:password]
39
+ cfoptions[:vhost] = service_props[:vhost]
40
+ cfoptions
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,44 @@
1
+ require 'carrot'
2
+ require 'cfruntime/properties'
3
+ module CFRuntime
4
+ class CarrotClient
5
+
6
+ # Creates and returns a +Carrot+ instance connected to a single rabbitmq service.
7
+ # Passes optional Hash of non-connection-related options to +Carrot.new+.
8
+ # Raises +ArgumentError+ If zero or multiple rabbitmq services are found.
9
+ def self.create(options={})
10
+ service_names = CloudApp.service_names_of_type('rabbitmq')
11
+ if service_names.length != 1
12
+ raise ArgumentError.new("Expected 1 service of rabbitmq type, " +
13
+ "but found #{service_names.length}. " +
14
+ "Consider using create_from_svc(service_name) instead.")
15
+ end
16
+ create_from_svc(service_names[0],options)
17
+ end
18
+
19
+ # Creates and returns a +Carrot+ instance connected to a rabbitmq service with the
20
+ # specified name.
21
+ # Passes optional Hash of non-connection-related options to +Carrot.new+.
22
+ # Raises +ArgumentError+ If specified rabbitmq service is not found.
23
+ def self.create_from_svc(service_name, options={})
24
+ Carrot.new(options_for_svc(service_name,options))
25
+ end
26
+
27
+ # Merges provided options with connection options for specified rabbitmq service.
28
+ # Returns merged Hash containing (:user, :pass, :vhost, :host, :port).
29
+ # Raises +ArgumentError+ If specified rabbitmq service is not found.
30
+ def self.options_for_svc(service_name,options={})
31
+ service_props = CFRuntime::CloudApp.service_props(service_name)
32
+ if service_props.nil?
33
+ raise ArgumentError.new("Service with name #{service_name} not found")
34
+ end
35
+ cfoptions = options
36
+ cfoptions[:host] = service_props[:host]
37
+ cfoptions[:port] = service_props[:port]
38
+ cfoptions[:user] = service_props[:username]
39
+ cfoptions[:pass] = service_props[:password]
40
+ cfoptions[:vhost] = service_props[:vhost]
41
+ cfoptions
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,86 @@
1
+ require 'mongo'
2
+ require 'cfruntime/properties'
3
+ module CFRuntime
4
+ class MongoClient
5
+
6
+ # Creates and returns a Mongo +Connection+ to a single mongodb service.
7
+ # Passes optional Hash of non-connection-related options to +Mongo::Connection.new+.
8
+ # The connection is wrapped in a proxy that adds a no-argument db method to gain access
9
+ # to the database created by CloudFoundry without having to specify the name.
10
+ # Raises +ArgumentError+ If zero or multiple mongodb services are found.
11
+ def self.create(options={})
12
+ service_names = CloudApp.service_names_of_type('mongodb')
13
+ if service_names.length != 1
14
+ raise ArgumentError.new("Expected 1 service of mongodb type, " +
15
+ "but found #{service_names.length}. " +
16
+ "Consider using create_from_svc(service_name) instead.")
17
+ end
18
+ create_from_svc(service_names[0],options)
19
+ end
20
+
21
+ # Creates and returns a Mongo +Connection+ to a mongodb service with the
22
+ # specified name.
23
+ # Passes optional Hash of non-connection-related options to +Mongo::Connection.new+.
24
+ # The connection is wrapped in a proxy that adds a no-argument db method to gain access
25
+ # to the database created by CloudFoundry without having to specify the name.
26
+ # Raises +ArgumentError+ If specified mongodb service is not found.
27
+ def self.create_from_svc(service_name,options={})
28
+ service_props = CFRuntime::CloudApp.service_props(service_name)
29
+ if service_props.nil?
30
+ raise ArgumentError.new("Service with name #{service_name} not found")
31
+ end
32
+ uri = "mongodb://#{service_props[:username]}:#{service_props[:password]}@#{service_props[:host]}:#{service_props[:port]}/#{service_props[:db]}"
33
+ conn = Mongo::Connection.from_uri(uri, options)
34
+ MongoConnection.new(conn, service_props[:db])
35
+ end
36
+
37
+ # Returns the db_name for a single mongodb service.
38
+ # Raises +ArgumentError+ If zero or multiple mongodb services are found.
39
+ def self.db_name()
40
+ service_names = CloudApp.service_names_of_type('mongodb')
41
+ if service_names.length != 1
42
+ raise ArgumentError.new("Expected 1 service of mongodb type, " +
43
+ "but found #{service_names.length}. " +
44
+ "Consider using db_name_from_svc(service_name) instead.")
45
+ end
46
+ db_name_from_svc(service_names[0])
47
+ end
48
+
49
+ # Returns the db_name for the mongodb service with the specified name.
50
+ # Raises +ArgumentError+ If specified mongodb service is not found.
51
+ def self.db_name_from_svc(service_name)
52
+ service_props = CFRuntime::CloudApp.service_props(service_name)
53
+ if service_props.nil?
54
+ raise ArgumentError.new("Service with name #{service_name} not found")
55
+ end
56
+ service_props[:db]
57
+ end
58
+
59
+ end
60
+
61
+ class MongoConnection
62
+
63
+ instance_methods.each { |m| undef_method m unless m =~ /^__|instance_eval|object_id/ }
64
+
65
+ def initialize(connection, db_name)
66
+ @target = connection
67
+ @dbname = db_name
68
+ end
69
+
70
+ def db(db_name=@dbname, opts={})
71
+ @target.send('db', db_name, opts)
72
+ end
73
+
74
+ def target()
75
+ @target
76
+ end
77
+
78
+ protected
79
+
80
+ def method_missing(method, *args, &block)
81
+ @target.send(method, *args, &block)
82
+ end
83
+
84
+ end
85
+
86
+ end
@@ -0,0 +1,44 @@
1
+ require 'mysql2'
2
+ require 'cfruntime/properties'
3
+ module CFRuntime
4
+ class Mysql2Client
5
+
6
+ # Creates and returns a Mysql2 +Client+ instance connected to a single mysql service.
7
+ # Passes optional Hash of non-connection-related options to +Mysql2::Client.new+.
8
+ # Raises +ArgumentError+ If zero or multiple mysql services are found.
9
+ def self.create(options={})
10
+ service_names = CloudApp.service_names_of_type('mysql')
11
+ if service_names.length != 1
12
+ raise ArgumentError.new("Expected 1 service of mysql type, " +
13
+ "but found #{service_names.length}. " +
14
+ "Consider using create_from_svc(service_name) instead.")
15
+ end
16
+ create_from_svc(service_names[0],options)
17
+ end
18
+
19
+ # Creates and returns a Mysql2 +Client+ instance connected to a mysql service with the
20
+ # specified name.
21
+ # Passes optional Hash of non-connection-related options to +Mysql2::Client.new+.
22
+ # Raises +ArgumentError+ If specified mysql service is not found.
23
+ def self.create_from_svc(service_name, options={})
24
+ Mysql2::Client.new(options_for_svc(service_name,options))
25
+ end
26
+
27
+ # Merges provided options with connection options for specified mysql service.
28
+ # Returns merged Hash containing (:username, :password, :database, :host, :port).
29
+ # Raises +ArgumentError+ If specified mysql service is not found.
30
+ def self.options_for_svc(service_name,options={})
31
+ service_props = CFRuntime::CloudApp.service_props(service_name)
32
+ if service_props.nil?
33
+ raise ArgumentError.new("Service with name #{service_name} not found")
34
+ end
35
+ cfoptions = options
36
+ cfoptions[:host] = service_props[:host]
37
+ cfoptions[:port] = service_props[:port]
38
+ cfoptions[:username] = service_props[:username]
39
+ cfoptions[:password] = service_props[:password]
40
+ cfoptions[:database] = service_props[:database]
41
+ cfoptions
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,50 @@
1
+ require 'pg'
2
+ require 'cfruntime/properties'
3
+ module CFRuntime
4
+ class PGClient
5
+
6
+ # Creates and returns a +PGconn+ connecting to a single postgresql service.
7
+ # Passes optional Hash of non-connection-related options to +PGconn.open+.
8
+ # Raises +ArgumentError+ If zero or multiple postgresql services are found.
9
+ def self.create(options={})
10
+ service_names = CloudApp.service_names_of_type('postgresql')
11
+ if service_names.length != 1
12
+ raise ArgumentError.new("Expected 1 service of postgresql type, " +
13
+ "but found #{service_names.length}. " +
14
+ "Consider using create_from_svc(service_name) instead.")
15
+ end
16
+ cfoptions = options_for_svc(service_names[0],options)
17
+ #Pass back the options for verification
18
+ yield cfoptions if block_given?
19
+ PGconn.open(cfoptions)
20
+ end
21
+
22
+ # Creates and returns a +PGconn+ connecting to a postgresql service with the
23
+ # specified name.
24
+ # Passes optional Hash of non-connection-related options to +PGconn.open+.
25
+ # Raises +ArgumentError+ If specified postgresql service is not found.
26
+ def self.create_from_svc(service_name,options={})
27
+ cfoptions = options_for_svc(service_name,options)
28
+ #Pass back the options for verification
29
+ yield cfoptions if block_given?
30
+ PGconn.open(cfoptions)
31
+ end
32
+
33
+ # Merges provided options with connection options for specified postgresql service.
34
+ # Returns merged Hash containing (:user, :password, :dbname, :host, :port).
35
+ # Raises +ArgumentError+ If specified postgresql service is not found.
36
+ def self.options_for_svc(service_name,options={})
37
+ service_props = CFRuntime::CloudApp.service_props(service_name)
38
+ if service_props.nil?
39
+ raise ArgumentError.new("Service with name #{service_name} not found")
40
+ end
41
+ cfoptions = options
42
+ cfoptions[:host] = service_props[:host]
43
+ cfoptions[:port] = service_props[:port]
44
+ cfoptions[:user] = service_props[:username]
45
+ cfoptions[:password] = service_props[:password]
46
+ cfoptions[:dbname] = service_props[:database]
47
+ cfoptions
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,124 @@
1
+ module CFRuntime
2
+
3
+ require 'crack/json'
4
+ require 'uri'
5
+
6
+ class CloudApp
7
+
8
+ class << self
9
+ # Returns true if this code is running on Cloud Foundry
10
+ def running_in_cloud?()
11
+ !ENV['VCAP_APPLICATION'].nil?
12
+ end
13
+
14
+ # Returns the application host name
15
+ def host
16
+ ENV['VCAP_APP_HOST']
17
+ end
18
+
19
+ # Returns the port bound to the application
20
+ def port
21
+ ENV['VCAP_APP_PORT']
22
+ end
23
+
24
+ # Parses the VCAP_SERVICES environment variable and returns a Hash of properties
25
+ # for the specified service name. If only one service of a particular type is bound
26
+ # to the application, service_props(type) will also work.
27
+ # Example: service_props('mysql').
28
+ # Returns nil if service with specified name is not found or if zero or multiple services
29
+ # of a specified type are found.
30
+ def service_props(service_name)
31
+ registered_svcs = {}
32
+ if ENV['VCAP_SERVICES']
33
+ svcs = Crack::JSON.parse(ENV['VCAP_SERVICES'])
34
+ else
35
+ svcs = {}
36
+ end
37
+ svcs.each do |key,list|
38
+ label, version = key.split('-')
39
+ list.each do |svc|
40
+ name = svc["name"]
41
+ serviceopts = {}
42
+ serviceopts[:label] = label
43
+ serviceopts[:version] = version
44
+ serviceopts[:name] = name
45
+ cred = svc["credentials"]
46
+ if label =~ /rabbitmq/
47
+ if cred['url']
48
+ #The RabbitMQ default vhost
49
+ vhost = '/'
50
+ # The new "srs" credentials format
51
+ uri=URI.parse(cred['url'])
52
+ user=URI.unescape(uri.user) if uri.user
53
+ passwd=URI.unescape(uri.password) if uri.password
54
+ host=uri.host
55
+ port=uri.port
56
+ if uri.path =~ %r{^/(.*)}
57
+ raise ArgumentError.new("multiple segments in path of amqp URI: #{uri}") if $1.index('/')
58
+ vhost = URI.unescape($1)
59
+ end
60
+ serviceopts[:url] = cred['url']
61
+ else
62
+ # The "old" credentials format
63
+ user,passwd,host,port,vhost = %w(user pass hostname port vhost).map {|key|
64
+ cred[key]}
65
+ end
66
+ serviceopts[:vhost] = vhost
67
+ else
68
+ user,passwd,host,port,dbname,db = %w(username password hostname port name db).map {|key|
69
+ cred[key]}
70
+ if label == "mongodb"
71
+ serviceopts[:db] = db
72
+ else
73
+ serviceopts[:database] = dbname
74
+ end
75
+ end
76
+ serviceopts[:username] = user
77
+ serviceopts[:password] = passwd
78
+ serviceopts[:host] = host
79
+ serviceopts[:port] = port
80
+ registered_svcs[name] = serviceopts
81
+ if list.count == 1
82
+ registered_svcs[label] = serviceopts
83
+ end
84
+ end
85
+ end
86
+ registered_svcs[service_name]
87
+ end
88
+
89
+ # Parses the VCAP_SERVICES environment variable and returns an array of Service
90
+ # names bound to the current application.
91
+ def service_names
92
+ service_names = []
93
+ if ENV['VCAP_SERVICES']
94
+ Crack::JSON.parse(ENV['VCAP_SERVICES']).each do |key,list|
95
+ list.each do |svc|
96
+ service_names << svc["name"]
97
+ end
98
+ end
99
+ end
100
+ service_names
101
+ end
102
+
103
+ # Parses the VCAP_SERVICES environment variable and returns an array of Service
104
+ # names of the specified type bound to the current application.
105
+ # Example: service_names_of_type('mysql')
106
+ def service_names_of_type(type)
107
+ service_names = []
108
+ if ENV['VCAP_SERVICES']
109
+ Crack::JSON.parse(ENV['VCAP_SERVICES']).each do |key,list|
110
+ label, version = key.split('-')
111
+ list.each do |svc|
112
+ if label == type
113
+ service_names << svc["name"]
114
+ end
115
+ end
116
+ end
117
+ end
118
+ service_names
119
+ end
120
+ end
121
+
122
+ end
123
+
124
+ end