cf-autoconfig 0.1.0
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.
- data/LICENSE +12737 -0
- data/lib/cfautoconfig.rb +3 -0
- data/lib/cfautoconfig/blob/aws_s3.rb +23 -0
- data/lib/cfautoconfig/blob/aws_s3_configurer.rb +41 -0
- data/lib/cfautoconfig/configuration_helper.rb +19 -0
- data/lib/cfautoconfig/configurer.rb +44 -0
- data/lib/cfautoconfig/document/mongodb.rb +56 -0
- data/lib/cfautoconfig/document/mongodb_configurer.rb +44 -0
- data/lib/cfautoconfig/keyvalue/redis.rb +24 -0
- data/lib/cfautoconfig/keyvalue/redis_configurer.rb +33 -0
- data/lib/cfautoconfig/messaging/amqp.rb +30 -0
- data/lib/cfautoconfig/messaging/amqp_configurer.rb +33 -0
- data/lib/cfautoconfig/messaging/carrot.rb +24 -0
- data/lib/cfautoconfig/messaging/carrot_configurer.rb +33 -0
- data/lib/cfautoconfig/relational/mysql.rb +27 -0
- data/lib/cfautoconfig/relational/mysql_configurer.rb +38 -0
- data/lib/cfautoconfig/relational/postgres.rb +80 -0
- data/lib/cfautoconfig/relational/postgres_configurer.rb +42 -0
- data/lib/cfautoconfig/version.rb +3 -0
- metadata +275 -0
data/lib/cfautoconfig.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'cfruntime/properties'
|
2
|
+
require 'cfruntime/aws_s3'
|
3
|
+
|
4
|
+
module AutoReconfiguration
|
5
|
+
SUPPORTED_AWS_S3_VERSION = '0.6.3'
|
6
|
+
module AwsS3
|
7
|
+
def self.included( base )
|
8
|
+
base.send(:alias_method, :original_connect, :connect)
|
9
|
+
base.send(:alias_method, :connect, :connect_with_cf )
|
10
|
+
end
|
11
|
+
|
12
|
+
def connect_with_cf(options = {})
|
13
|
+
service_names = CFRuntime::CloudApp.service_names_of_type('blob')
|
14
|
+
if service_names.length == 1
|
15
|
+
puts "Auto-reconfiguring AWS-S3"
|
16
|
+
options = CFRuntime::AWSS3Client.options_for_svc(service_names[0],options)
|
17
|
+
else
|
18
|
+
puts "Found #{service_names.length} blob services. Skipping auto-reconfiguration."
|
19
|
+
end
|
20
|
+
original_connect options
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'cfautoconfig/configuration_helper'
|
2
|
+
begin
|
3
|
+
require 'aws/s3'
|
4
|
+
require File.join(File.dirname(__FILE__), 'aws_s3')
|
5
|
+
aws_s3_version = Gem.loaded_specs['aws-s3'].version
|
6
|
+
if aws_s3_version >= Gem::Version.new(AutoReconfiguration::SUPPORTED_AWS_S3_VERSION)
|
7
|
+
if AutoReconfiguration::ConfigurationHelper.disabled? :blob
|
8
|
+
puts "Blob auto-reconfiguration disabled."
|
9
|
+
module AWS
|
10
|
+
module S3
|
11
|
+
class << Connection
|
12
|
+
#Remove introduced aliases and methods.
|
13
|
+
#This is mostly for testing, as we don't expect this script
|
14
|
+
#to run twice during a single app startup
|
15
|
+
if method_defined?(:connect_with_cf)
|
16
|
+
undef_method :connect_with_cf
|
17
|
+
alias :connect :original_connect
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
elsif AWS::S3::Connection.public_methods.index :connect_with_cf
|
23
|
+
#Guard against introducing a method that may already exist
|
24
|
+
puts "AWS-S3 auto-reconfiguration already included."
|
25
|
+
else
|
26
|
+
#Introduce around alias into Connection class
|
27
|
+
module AWS
|
28
|
+
module S3
|
29
|
+
class << Connection
|
30
|
+
include AutoReconfiguration::AwsS3
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
else
|
36
|
+
puts "Auto-reconfiguration not supported for this AWS-S3 version. " +
|
37
|
+
"Found: #{aws_s3_version}. Required: #{AutoReconfiguration::SUPPORTED_AWS_S3_VERSION} or higher."
|
38
|
+
end
|
39
|
+
rescue LoadError=>e
|
40
|
+
puts "No AWS-S3 Library Found. Skipping auto-reconfiguration. #{e}"
|
41
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module AutoReconfiguration
|
2
|
+
class ConfigurationHelper
|
3
|
+
def self.disabled? (client)
|
4
|
+
disable_auto_config = []
|
5
|
+
if ENV['DISABLE_AUTO_CONFIG']
|
6
|
+
disable_auto_config = ENV['DISABLE_AUTO_CONFIG'].split(/\s*\:\s*/)
|
7
|
+
disable_auto_config.collect! { |element|
|
8
|
+
element.upcase
|
9
|
+
}
|
10
|
+
end
|
11
|
+
if disable_auto_config.include? 'ALL'
|
12
|
+
true
|
13
|
+
else
|
14
|
+
disable_auto_config.include? client.to_s.upcase
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'cfruntime/properties'
|
2
|
+
|
3
|
+
if CFRuntime::CloudApp.service_props('redis')
|
4
|
+
puts "Loading Redis auto-reconfiguration."
|
5
|
+
require 'cfautoconfig/keyvalue/redis_configurer'
|
6
|
+
else
|
7
|
+
puts "No Redis service bound to app. Skipping auto-reconfiguration."
|
8
|
+
end
|
9
|
+
|
10
|
+
if CFRuntime::CloudApp.service_props('mongodb')
|
11
|
+
puts "Loading MongoDB auto-reconfiguration."
|
12
|
+
require 'cfautoconfig/document/mongodb_configurer'
|
13
|
+
else
|
14
|
+
puts "No Mongo service bound to app. Skipping auto-reconfiguration."
|
15
|
+
end
|
16
|
+
|
17
|
+
if CFRuntime::CloudApp.service_props('mysql')
|
18
|
+
puts "Loading MySQL auto-reconfiguration."
|
19
|
+
require 'cfautoconfig/relational/mysql_configurer'
|
20
|
+
else
|
21
|
+
puts "No MySQL service bound to app. Skipping auto-reconfiguration."
|
22
|
+
end
|
23
|
+
|
24
|
+
if CFRuntime::CloudApp.service_props('postgresql')
|
25
|
+
puts "Loading PostgreSQL auto-reconfiguration."
|
26
|
+
require 'cfautoconfig/relational/postgres_configurer'
|
27
|
+
else
|
28
|
+
puts "No PostgreSQL service bound to app. Skipping auto-reconfiguration."
|
29
|
+
end
|
30
|
+
|
31
|
+
if CFRuntime::CloudApp.service_props('rabbitmq')
|
32
|
+
puts "Loading RabbitMQ auto-reconfiguration."
|
33
|
+
require 'cfautoconfig/messaging/amqp_configurer'
|
34
|
+
require 'cfautoconfig/messaging/carrot_configurer'
|
35
|
+
else
|
36
|
+
puts "No RabbitMQ service bound to app. Skipping auto-reconfiguration."
|
37
|
+
end
|
38
|
+
|
39
|
+
if CFRuntime::CloudApp.service_props('blob')
|
40
|
+
puts "Loading Blob auto-reconfiguration."
|
41
|
+
require 'cfautoconfig/blob/aws_s3_configurer'
|
42
|
+
else
|
43
|
+
puts "No Blob service bound to app. Skipping auto-reconfiguration."
|
44
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'cfruntime/properties'
|
2
|
+
|
3
|
+
module AutoReconfiguration
|
4
|
+
SUPPORTED_MONGO_VERSION = '1.2.0'
|
5
|
+
module Mongo
|
6
|
+
|
7
|
+
def self.included( base )
|
8
|
+
base.send( :alias_method, :original_initialize, :initialize)
|
9
|
+
base.send( :alias_method, :initialize, :initialize_with_cf )
|
10
|
+
base.send( :alias_method, :original_apply_saved_authentication, :apply_saved_authentication)
|
11
|
+
base.send( :alias_method, :apply_saved_authentication, :apply_saved_authentication_with_cf )
|
12
|
+
base.send( :alias_method, :original_db, :db)
|
13
|
+
base.send( :alias_method, :db, :db_with_cf )
|
14
|
+
base.send( :alias_method, :original_shortcut, :[])
|
15
|
+
base.send( :alias_method, :[], :shortcut_with_cf )
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize_with_cf(host = nil, port = nil, opts = {})
|
19
|
+
service_names = CFRuntime::CloudApp.service_names_of_type('mongodb')
|
20
|
+
if service_names.length == 1
|
21
|
+
@service_props = CFRuntime::CloudApp.service_props('mongodb')
|
22
|
+
puts "Auto-reconfiguring MongoDB"
|
23
|
+
@auto_config = true
|
24
|
+
original_initialize @service_props[:host], @service_props[:port], opts
|
25
|
+
add_auth(@service_props[:db], @service_props[:username], @service_props[:password])
|
26
|
+
else
|
27
|
+
puts "Found #{service_names.length} mongo services. Skipping auto-reconfiguration."
|
28
|
+
@auto_config = false
|
29
|
+
original_initialize host, port, opts
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def apply_saved_authentication_with_cf(opts = {})
|
34
|
+
add_auth(@service_props[:db], @service_props[:username], @service_props[:password])
|
35
|
+
original_apply_saved_authentication opts
|
36
|
+
end
|
37
|
+
|
38
|
+
def db_with_cf(db_name, opts = {})
|
39
|
+
if @auto_config && db_name != 'admin'
|
40
|
+
add_auth(@service_props[:db], @service_props[:username], @service_props[:password])
|
41
|
+
db = original_db @service_props[:db], opts
|
42
|
+
else
|
43
|
+
original_db db_name, opts
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def shortcut_with_cf(db_name)
|
48
|
+
if @auto_config && db_name != 'admin'
|
49
|
+
add_auth(@service_props[:db], @service_props[:username], @service_props[:password])
|
50
|
+
db = original_shortcut(@service_props[:db])
|
51
|
+
else
|
52
|
+
db = original_shortcut(db_name)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'cfautoconfig/configuration_helper'
|
2
|
+
begin
|
3
|
+
#Require mongo here is mandatory for configurer to ensure class is loaded before applying OpenClass
|
4
|
+
require "mongo"
|
5
|
+
require File.join(File.dirname(__FILE__), 'mongodb')
|
6
|
+
mongo_version = Gem.loaded_specs['mongo'].version
|
7
|
+
if mongo_version >= Gem::Version.new(AutoReconfiguration::SUPPORTED_MONGO_VERSION)
|
8
|
+
if AutoReconfiguration::ConfigurationHelper.disabled? :mongodb
|
9
|
+
puts "MongoDB auto-reconfiguration disabled."
|
10
|
+
module Mongo
|
11
|
+
class Connection
|
12
|
+
#Remove introduced aliases and methods.
|
13
|
+
#This is mostly for testing, as we don't expect this script
|
14
|
+
#to run twice during a single app startup
|
15
|
+
if method_defined?(:initialize_with_cf)
|
16
|
+
undef_method :initialize_with_cf
|
17
|
+
alias :initialize :original_initialize
|
18
|
+
undef_method :apply_saved_authentication_with_cf
|
19
|
+
alias :apply_saved_authentication :original_apply_saved_authentication
|
20
|
+
undef_method :db_with_cf
|
21
|
+
alias :db :original_db
|
22
|
+
undef_method :shortcut_with_cf
|
23
|
+
alias :[] :original_shortcut
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
elsif Mongo::Connection.public_instance_methods.index :initialize_with_cf
|
28
|
+
puts "MongoDB AutoReconfiguration already included."
|
29
|
+
else
|
30
|
+
module Mongo
|
31
|
+
#Introduce around alias into Mongo Connection class
|
32
|
+
class Connection
|
33
|
+
include AutoReconfiguration::Mongo
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
else
|
38
|
+
puts "Auto-reconfiguration not supported for this Mongo version. " +
|
39
|
+
"Found: #{mongo_version}. Required: #{AutoReconfiguration::SUPPORTED_MONGO_VERSION} or higher."
|
40
|
+
end
|
41
|
+
rescue LoadError
|
42
|
+
puts "No MongoDB Library Found. Skipping auto-reconfiguration."
|
43
|
+
end
|
44
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'cfruntime/properties'
|
2
|
+
require 'cfruntime/redis'
|
3
|
+
|
4
|
+
module AutoReconfiguration
|
5
|
+
SUPPORTED_REDIS_VERSION = '2.0'
|
6
|
+
module Redis
|
7
|
+
def self.included( base )
|
8
|
+
base.send( :alias_method, :original_initialize, :initialize)
|
9
|
+
base.send( :alias_method, :initialize, :initialize_with_cf )
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize_with_cf(options = {})
|
13
|
+
service_names = CFRuntime::CloudApp.service_names_of_type('redis')
|
14
|
+
if service_names.length == 1
|
15
|
+
puts "Auto-reconfiguring Redis."
|
16
|
+
cfoptions = CFRuntime::RedisClient.options_for_svc(service_names[0],options)
|
17
|
+
original_initialize cfoptions
|
18
|
+
else
|
19
|
+
puts "Found #{service_names.length} redis services. Skipping auto-reconfiguration."
|
20
|
+
original_initialize options
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'cfautoconfig/configuration_helper'
|
2
|
+
begin
|
3
|
+
require 'redis'
|
4
|
+
require File.join(File.dirname(__FILE__), 'redis')
|
5
|
+
redis_version = Gem.loaded_specs['redis'].version
|
6
|
+
if redis_version >= Gem::Version.new(AutoReconfiguration::SUPPORTED_REDIS_VERSION)
|
7
|
+
if AutoReconfiguration::ConfigurationHelper.disabled? :redis
|
8
|
+
puts "Redis auto-reconfiguration disabled."
|
9
|
+
class Redis
|
10
|
+
#Remove introduced aliases and methods.
|
11
|
+
#This is mostly for testing, as we don't expect this script
|
12
|
+
#to run twice during a single app startup
|
13
|
+
if method_defined?(:initialize_with_cf)
|
14
|
+
undef_method :initialize_with_cf
|
15
|
+
alias :initialize :original_initialize
|
16
|
+
end
|
17
|
+
end
|
18
|
+
elsif Redis.public_instance_methods.index :initialize_with_cf
|
19
|
+
#Guard against introducing a method that may already exist
|
20
|
+
puts "Redis auto-reconfiguration already included."
|
21
|
+
else
|
22
|
+
#Introduce around alias into Redis class
|
23
|
+
class Redis
|
24
|
+
include AutoReconfiguration::Redis
|
25
|
+
end
|
26
|
+
end
|
27
|
+
else
|
28
|
+
puts "Auto-reconfiguration not supported for this Redis version. " +
|
29
|
+
"Found: #{redis_version}. Required: #{AutoReconfiguration::SUPPORTED_REDIS_VERSION} or higher."
|
30
|
+
end
|
31
|
+
rescue LoadError
|
32
|
+
puts "No Redis Library Found. Skipping auto-reconfiguration."
|
33
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'cfruntime/properties'
|
2
|
+
require 'cfruntime/amqp'
|
3
|
+
|
4
|
+
module AutoReconfiguration
|
5
|
+
SUPPORTED_AMQP_VERSION = '0.8'
|
6
|
+
module AMQP
|
7
|
+
def self.included( base )
|
8
|
+
base.send( :alias_method, :original_connect, :connect)
|
9
|
+
base.send( :alias_method, :connect, :connect_with_cf )
|
10
|
+
end
|
11
|
+
|
12
|
+
def connect_with_cf(connection_options_or_string = {}, other_options = {}, &block)
|
13
|
+
service_names = CFRuntime::CloudApp.service_names_of_type('rabbitmq')
|
14
|
+
if service_names.length == 1
|
15
|
+
puts "Auto-reconfiguring AMQP."
|
16
|
+
case connection_options_or_string
|
17
|
+
when String then
|
18
|
+
cfoptions = {}
|
19
|
+
else
|
20
|
+
cfoptions = connection_options_or_string
|
21
|
+
end
|
22
|
+
original_connect(CFRuntime::AMQPClient.options_for_svc(service_names[0],cfoptions),
|
23
|
+
other_options, &block)
|
24
|
+
else
|
25
|
+
puts "Found #{service_names.length} rabbitmq services. Skipping auto-reconfiguration."
|
26
|
+
original_connect(connection_options_or_string, other_options, &block)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'cfautoconfig/configuration_helper'
|
2
|
+
begin
|
3
|
+
#Require amqp here is mandatory for configurer to ensure class is loaded before applying OpenClass
|
4
|
+
require "amqp"
|
5
|
+
require File.join(File.dirname(__FILE__), 'amqp')
|
6
|
+
amqp_version = Gem.loaded_specs['amqp'].version
|
7
|
+
if amqp_version >= Gem::Version.new(AutoReconfiguration::SUPPORTED_AMQP_VERSION)
|
8
|
+
if AutoReconfiguration::ConfigurationHelper.disabled? :rabbitmq
|
9
|
+
puts "RabbitMQ auto-reconfiguration disabled."
|
10
|
+
class << AMQP
|
11
|
+
#Remove introduced aliases and methods.
|
12
|
+
#This is mostly for testing, as we don't expect this script
|
13
|
+
#to run twice during a single app startup
|
14
|
+
if method_defined?(:connect_with_cf)
|
15
|
+
undef_method :connect_with_cf
|
16
|
+
alias :connect :original_connect
|
17
|
+
end
|
18
|
+
end
|
19
|
+
elsif AMQP.public_methods.index :connect_with_cf
|
20
|
+
puts "AMQP AutoReconfiguration already included."
|
21
|
+
else
|
22
|
+
#Introduce around alias into AMQP class
|
23
|
+
class << AMQP
|
24
|
+
include AutoReconfiguration::AMQP
|
25
|
+
end
|
26
|
+
end
|
27
|
+
else
|
28
|
+
puts "Auto-reconfiguration not supported for this AMQP version. " +
|
29
|
+
"Found: #{amqp_version}. Required: #{AutoReconfiguration::SUPPORTED_AMQP_VERSION} or higher."
|
30
|
+
end
|
31
|
+
rescue LoadError
|
32
|
+
puts "No AMQP Library Found. Skipping auto-reconfiguration."
|
33
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'cfruntime/properties'
|
2
|
+
require 'cfruntime/carrot'
|
3
|
+
|
4
|
+
module AutoReconfiguration
|
5
|
+
SUPPORTED_CARROT_VERSION = '1.0'
|
6
|
+
module Carrot
|
7
|
+
def self.included( base )
|
8
|
+
base.send( :alias_method, :original_initialize, :initialize)
|
9
|
+
base.send( :alias_method, :initialize, :initialize_with_cf )
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize_with_cf(opts = {})
|
13
|
+
service_names = CFRuntime::CloudApp.service_names_of_type('rabbitmq')
|
14
|
+
if service_names.length == 1
|
15
|
+
puts "Auto-reconfiguring Carrot."
|
16
|
+
cfopts = CFRuntime::CarrotClient.options_for_svc(service_names[0],opts)
|
17
|
+
original_initialize cfopts
|
18
|
+
else
|
19
|
+
puts "Found #{service_names.length} rabbitmq services. Skipping auto-reconfiguration."
|
20
|
+
original_initialize opts
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'cfautoconfig/configuration_helper'
|
2
|
+
begin
|
3
|
+
#Require carrot here is mandatory for configurer to ensure class is loaded before applying OpenClass
|
4
|
+
require "carrot"
|
5
|
+
require File.join(File.dirname(__FILE__), 'carrot')
|
6
|
+
carrot_version = Gem.loaded_specs['carrot'].version
|
7
|
+
if carrot_version >= Gem::Version.new(AutoReconfiguration::SUPPORTED_CARROT_VERSION)
|
8
|
+
if AutoReconfiguration::ConfigurationHelper.disabled? :rabbitmq
|
9
|
+
puts "RabbitMQ auto-reconfiguration disabled."
|
10
|
+
class Carrot
|
11
|
+
#Remove introduced aliases and methods.
|
12
|
+
#This is mostly for testing, as we don't expect this script
|
13
|
+
#to run twice during a single app startup
|
14
|
+
if method_defined?(:initialize_with_cf)
|
15
|
+
undef_method :initialize_with_cf
|
16
|
+
alias :initialize :original_initialize
|
17
|
+
end
|
18
|
+
end
|
19
|
+
elsif Carrot.public_instance_methods.index :initialize_with_cf
|
20
|
+
puts "Carrot AutoReconfiguration already included."
|
21
|
+
else
|
22
|
+
#Introduce around alias into Carrot class
|
23
|
+
class Carrot
|
24
|
+
include AutoReconfiguration::Carrot
|
25
|
+
end
|
26
|
+
end
|
27
|
+
else
|
28
|
+
puts "Auto-reconfiguration not supported for this Carrot version. " +
|
29
|
+
"Found: #{carrot_version}. Required: #{AutoReconfiguration::SUPPORTED_CARROT_VERSION} or higher."
|
30
|
+
end
|
31
|
+
rescue LoadError
|
32
|
+
puts "No Carrot Library Found. Skipping auto-reconfiguration."
|
33
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'cfruntime/properties'
|
2
|
+
require 'cfruntime/mysql'
|
3
|
+
|
4
|
+
module AutoReconfiguration
|
5
|
+
SUPPORTED_MYSQL2_VERSION = '0.2.7'
|
6
|
+
module Mysql
|
7
|
+
|
8
|
+
def self.included( base )
|
9
|
+
base.send( :alias_method, :original_initialize, :initialize)
|
10
|
+
base.send( :alias_method, :initialize, :initialize_with_cf )
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize_with_cf(opts = {})
|
14
|
+
service_names = CFRuntime::CloudApp.service_names_of_type('mysql')
|
15
|
+
if service_names.length == 1
|
16
|
+
puts "Auto-reconfiguring MySQL"
|
17
|
+
original_initialize(CFRuntime::Mysql2Client.options_for_svc(service_names[0],opts))
|
18
|
+
else
|
19
|
+
puts "Found #{service_names.length} mysql services. Skipping auto-reconfiguration."
|
20
|
+
original_initialize opts
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
|