cf-runtime 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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