mirror of
https://github.com/fog/fog.git
synced 2022-11-09 13:51:43 -05:00
first pass at read only terremark (vCloud Express) support
This commit is contained in:
parent
2762947a70
commit
082276e051
11 changed files with 493 additions and 7 deletions
39
bin/fog
39
bin/fog
|
@ -17,7 +17,7 @@ module AWS
|
||||||
credential = (ARGV.first && :"#{ARGV.first}") || :default
|
credential = (ARGV.first && :"#{ARGV.first}") || :default
|
||||||
if Fog.credentials[:aws_access_key_id] && Fog.credentials[:aws_secret_access_key]
|
if Fog.credentials[:aws_access_key_id] && Fog.credentials[:aws_secret_access_key]
|
||||||
|
|
||||||
def ready?
|
def initialized?
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -74,7 +74,7 @@ module AWS
|
||||||
|
|
||||||
else
|
else
|
||||||
|
|
||||||
def ready?
|
def initialized?
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -86,7 +86,7 @@ module Rackspace
|
||||||
class << self
|
class << self
|
||||||
if Fog.credentials[:rackspace_api_key] && Fog.credentials[:rackspace_username]
|
if Fog.credentials[:rackspace_api_key] && Fog.credentials[:rackspace_username]
|
||||||
|
|
||||||
def ready?
|
def initialized?
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -119,7 +119,7 @@ module Rackspace
|
||||||
|
|
||||||
else
|
else
|
||||||
|
|
||||||
def ready?
|
def initialized?
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -131,7 +131,7 @@ module Slicehost
|
||||||
class << self
|
class << self
|
||||||
if Fog.credentials[:slicehost_password]
|
if Fog.credentials[:slicehost_password]
|
||||||
|
|
||||||
def ready?
|
def initialized?
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -162,10 +162,35 @@ module Slicehost
|
||||||
|
|
||||||
else
|
else
|
||||||
|
|
||||||
def ready?
|
def initialized?
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module Terremark
|
||||||
|
class << self
|
||||||
|
if Fog.credentials[:terremark_password] && Fog.credentials[:terremark_username]
|
||||||
|
|
||||||
|
def initialized?
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def [](service)
|
||||||
|
@@connections ||= Hash.new do |hash, key|
|
||||||
|
credentials = Fog.credentials.reject do |k,v|
|
||||||
|
![:terremark_username, :terremark_password].include?(k)
|
||||||
|
end
|
||||||
|
hash[key] = case key
|
||||||
|
when :vcloud
|
||||||
|
Fog::Terremark.new(credentials)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
@@connections[service]
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -176,7 +201,7 @@ module Fog
|
||||||
def services
|
def services
|
||||||
services = []
|
services = []
|
||||||
[::AWS, ::Rackspace, ::Slicehost].each do |service|
|
[::AWS, ::Rackspace, ::Slicehost].each do |service|
|
||||||
if service.ready?
|
if service.initialized?
|
||||||
services << service
|
services << service
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -38,6 +38,7 @@ module Fog
|
||||||
load "fog/aws.rb"
|
load "fog/aws.rb"
|
||||||
load "fog/rackspace.rb"
|
load "fog/rackspace.rb"
|
||||||
load "fog/slicehost.rb"
|
load "fog/slicehost.rb"
|
||||||
|
load "fog/terremark.rb"
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.credential=(new_credential)
|
def self.credential=(new_credential)
|
||||||
|
@ -69,6 +70,8 @@ module Fog
|
||||||
:rackspace_api_key: INTENTIONALLY_LEFT_BLANK
|
:rackspace_api_key: INTENTIONALLY_LEFT_BLANK
|
||||||
:rackspace_username: INTENTIONALLY_LEFT_BLANK
|
:rackspace_username: INTENTIONALLY_LEFT_BLANK
|
||||||
:slicehost_password: INTENTIONALLY_LEFT_BLANK
|
:slicehost_password: INTENTIONALLY_LEFT_BLANK
|
||||||
|
:terremark_username: INTENTIONALLY_LEFT_BLANK
|
||||||
|
:terremark_password: INTENTIONALLY_LEFT_BLANK
|
||||||
|
|
||||||
YML
|
YML
|
||||||
print(yml)
|
print(yml)
|
||||||
|
|
61
lib/fog/terremark.rb
Normal file
61
lib/fog/terremark.rb
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
module Fog
|
||||||
|
class Terremark
|
||||||
|
|
||||||
|
def self.reload
|
||||||
|
load 'fog/terremark/parsers/get_catalog.rb'
|
||||||
|
load 'fog/terremark/parsers/get_organization.rb'
|
||||||
|
load 'fog/terremark/parsers/get_organizations.rb'
|
||||||
|
load 'fog/terremark/parsers/get_vdc.rb'
|
||||||
|
|
||||||
|
load 'fog/terremark/requests/get_catalog.rb'
|
||||||
|
load 'fog/terremark/requests/get_organization.rb'
|
||||||
|
load 'fog/terremark/requests/get_organizations.rb'
|
||||||
|
load 'fog/terremark/requests/get_vdc.rb'
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(options={})
|
||||||
|
unless @terremark_password = options[:terremark_password]
|
||||||
|
raise ArgumentError.new('terremark_password is required to access terremark')
|
||||||
|
end
|
||||||
|
unless @terremark_username = options[:terremark_username]
|
||||||
|
raise ArgumentError.new('terremark_username is required to access terremark')
|
||||||
|
end
|
||||||
|
@host = options[:host] || "services.vcloudexpress.terremark.com"
|
||||||
|
@path = options[:path] || "/api/v0.8"
|
||||||
|
@port = options[:port] || 443
|
||||||
|
@scheme = options[:scheme] || 'https'
|
||||||
|
|
||||||
|
@cookie = get_organizations.headers['Set-Cookie']
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def request(params)
|
||||||
|
@connection = Fog::Connection.new("#{@scheme}://#{@host}:#{@port}")
|
||||||
|
headers = {}
|
||||||
|
if @cookie
|
||||||
|
headers.merge!('Cookie' => @cookie)
|
||||||
|
end
|
||||||
|
response = @connection.request({
|
||||||
|
:body => params[:body],
|
||||||
|
:expects => params[:expects],
|
||||||
|
:headers => headers.merge!(params[:headers] || {}),
|
||||||
|
:host => @host,
|
||||||
|
:method => params[:method],
|
||||||
|
:parser => params[:parser],
|
||||||
|
:path => "#{@path}/#{params[:path]}"
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
if Fog.mocking?
|
||||||
|
|
||||||
|
srand(Time.now.to_i)
|
||||||
|
|
||||||
|
class Mock
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
Fog::Terremark.reload
|
43
lib/fog/terremark/parsers/get_catalog.rb
Normal file
43
lib/fog/terremark/parsers/get_catalog.rb
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
module Fog
|
||||||
|
module Parsers
|
||||||
|
module Terremark
|
||||||
|
|
||||||
|
class GetCatalog < Fog::Parsers::Base
|
||||||
|
|
||||||
|
def reset
|
||||||
|
@response = { 'CatalogItems' => [] }
|
||||||
|
end
|
||||||
|
|
||||||
|
def start_element(name, attributes)
|
||||||
|
@value = ''
|
||||||
|
case name
|
||||||
|
when 'CatalogItem'
|
||||||
|
catalog_item = {}
|
||||||
|
until attributes.empty?
|
||||||
|
catalog_item[attributes.shift] = attributes.shift
|
||||||
|
end
|
||||||
|
@response['CatalogItems'] << catalog_item
|
||||||
|
when 'Catalog'
|
||||||
|
catalog = {}
|
||||||
|
until attributes.empty?
|
||||||
|
if attributes.first.is_a?(Array)
|
||||||
|
catalog[attributes.first.first] = attributes.shift.last
|
||||||
|
else
|
||||||
|
catalog[attributes.shift] = attributes.shift
|
||||||
|
end
|
||||||
|
end
|
||||||
|
@response['name'] = catalog['name']
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def end_element(name)
|
||||||
|
if name == 'Description'
|
||||||
|
@response[name] = @value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
44
lib/fog/terremark/parsers/get_organization.rb
Normal file
44
lib/fog/terremark/parsers/get_organization.rb
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
module Fog
|
||||||
|
module Parsers
|
||||||
|
module Terremark
|
||||||
|
|
||||||
|
class GetOrganization < Fog::Parsers::Base
|
||||||
|
|
||||||
|
def reset
|
||||||
|
@response = { 'links' => [] }
|
||||||
|
end
|
||||||
|
|
||||||
|
def start_element(name, attributes)
|
||||||
|
@value = ''
|
||||||
|
case name
|
||||||
|
when 'Link'
|
||||||
|
link = {}
|
||||||
|
until attributes.empty?
|
||||||
|
link[attributes.shift] = attributes.shift
|
||||||
|
end
|
||||||
|
@response['links'] << link
|
||||||
|
when 'Org'
|
||||||
|
org = {}
|
||||||
|
until attributes.empty?
|
||||||
|
if attributes.first.is_a?(Array)
|
||||||
|
org[attributes.first.first] = attributes.shift.last
|
||||||
|
else
|
||||||
|
org[attributes.shift] = attributes.shift
|
||||||
|
end
|
||||||
|
end
|
||||||
|
@response['href'] = org['href']
|
||||||
|
@response['name'] = org['name']
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def end_element(name)
|
||||||
|
if name == 'Description'
|
||||||
|
@response[name] = @value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
26
lib/fog/terremark/parsers/get_organizations.rb
Normal file
26
lib/fog/terremark/parsers/get_organizations.rb
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
module Fog
|
||||||
|
module Parsers
|
||||||
|
module Terremark
|
||||||
|
|
||||||
|
class GetOrganizations < Fog::Parsers::Base
|
||||||
|
|
||||||
|
def reset
|
||||||
|
@response = { 'OrgList' => [] }
|
||||||
|
end
|
||||||
|
|
||||||
|
def start_element(name, attributes)
|
||||||
|
@value = ''
|
||||||
|
if name == 'Org'
|
||||||
|
organization = {}
|
||||||
|
until attributes.empty?
|
||||||
|
organization[attributes.shift] = attributes.shift
|
||||||
|
end
|
||||||
|
@response['OrgList'] << organization
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
105
lib/fog/terremark/parsers/get_vdc.rb
Normal file
105
lib/fog/terremark/parsers/get_vdc.rb
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
module Fog
|
||||||
|
module Parsers
|
||||||
|
module Terremark
|
||||||
|
|
||||||
|
class GetVdc < Fog::Parsers::Base
|
||||||
|
|
||||||
|
def reset
|
||||||
|
@in_storage_capacity = false
|
||||||
|
@in_cpu = false
|
||||||
|
@in_memory = false
|
||||||
|
@in_instantiated_vms_quota = false
|
||||||
|
@in_deployed_vms_quota = false
|
||||||
|
@response = {
|
||||||
|
'links' => [],
|
||||||
|
'AvailableNetworks' => [],
|
||||||
|
'ComputeCapacity' => {
|
||||||
|
'Cpu' => {},
|
||||||
|
'DeployedVmsQuota' => {},
|
||||||
|
'InstantiatedVmsQuota' => {},
|
||||||
|
'Memory' => {}
|
||||||
|
},
|
||||||
|
'StorageCapacity' => {},
|
||||||
|
'ResourceEntities' => {}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def start_element(name, attributes)
|
||||||
|
@value = ''
|
||||||
|
case name
|
||||||
|
when 'Cpu'
|
||||||
|
@in_cpu = true
|
||||||
|
when 'DeployedVmsQuota'
|
||||||
|
@in_deployed_vms_quota = true
|
||||||
|
when 'InstantiatedVmsQuota'
|
||||||
|
@in_instantiated_vms_quota = true
|
||||||
|
when 'Link'
|
||||||
|
link = {}
|
||||||
|
until attributes.empty?
|
||||||
|
link[attributes.shift] = attributes.shift
|
||||||
|
end
|
||||||
|
@response['links'] << link
|
||||||
|
when 'Memory'
|
||||||
|
@in_memory = true
|
||||||
|
when 'Network'
|
||||||
|
network = {}
|
||||||
|
until attributes.empty?
|
||||||
|
network[attributes.shift] = attributes.shift
|
||||||
|
end
|
||||||
|
@response['AvailableNetworks'] << network
|
||||||
|
when 'ResourceEntity'
|
||||||
|
resource_entity = {}
|
||||||
|
until attributes.empty?
|
||||||
|
resource_entity[attributes.shift] = attributes.shift
|
||||||
|
end
|
||||||
|
@response['ResourceEntity'] << resource_entity
|
||||||
|
when 'StorageCapacity'
|
||||||
|
@in_storage_capacity = true
|
||||||
|
when 'Vdc'
|
||||||
|
vdc = {}
|
||||||
|
until attributes.empty?
|
||||||
|
if attributes.first.is_a?(Array)
|
||||||
|
vdc[attributes.first.first] = attributes.shift.last
|
||||||
|
else
|
||||||
|
vdc[attributes.shift] = attributes.shift
|
||||||
|
end
|
||||||
|
end
|
||||||
|
@response['href'] = vdc['href']
|
||||||
|
@response['name'] = vdc['name']
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def end_element(name)
|
||||||
|
case name
|
||||||
|
when 'Allocated', 'Limit', 'Units', 'Used'
|
||||||
|
if @in_cpu
|
||||||
|
@response['Compute Capacity']['Cpu'][name] = @value
|
||||||
|
elsif @in_deployed_vms_quota
|
||||||
|
@response['Compute Capacity']['DeployedVmsQuota'][name] = @value
|
||||||
|
elsif @in_instantiated_vms_quota
|
||||||
|
@response['Compute Capacity']['InstantiatedVmsQuota'][name] = @value
|
||||||
|
elsif @in_memory
|
||||||
|
@response['Compute Capacity']['Memory'][name] = @value
|
||||||
|
elsif @in_storage_capacity
|
||||||
|
@response['StorageCapacity'][name] = @value
|
||||||
|
end
|
||||||
|
when 'Cpu'
|
||||||
|
@in_cpu = false
|
||||||
|
when 'DeployedVmsQuota'
|
||||||
|
@in_deployed_vms_quota = false
|
||||||
|
when 'InstantiatedVmsQuota'
|
||||||
|
@in_instantiated_vms_quota = false
|
||||||
|
when 'Memory'
|
||||||
|
@in_memory = false
|
||||||
|
when 'StorageCapacity'
|
||||||
|
@in_storage_capacity = false
|
||||||
|
when 'Type'
|
||||||
|
@response[name] = @value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
44
lib/fog/terremark/requests/get_catalog.rb
Normal file
44
lib/fog/terremark/requests/get_catalog.rb
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
unless Fog.mocking?
|
||||||
|
|
||||||
|
module Fog
|
||||||
|
class Terremark
|
||||||
|
|
||||||
|
# Get details of a catalog
|
||||||
|
#
|
||||||
|
# ==== Parameters
|
||||||
|
# * vdc_id<~Integer> - Id of vdc to view catalog for
|
||||||
|
#
|
||||||
|
# ==== Returns
|
||||||
|
# * response<~Excon::Response>:
|
||||||
|
# * body<~Hash>:
|
||||||
|
# * 'CatalogItems'<~Array>
|
||||||
|
# * 'href'<~String> - linke to item
|
||||||
|
# * 'name'<~String> - name of item
|
||||||
|
# * 'type'<~String> - type of item
|
||||||
|
# * 'description'<~String> - Description of catalog
|
||||||
|
# * 'name'<~String> - Name of catalog
|
||||||
|
def get_catalog(vdc_id)
|
||||||
|
request(
|
||||||
|
:expects => 200,
|
||||||
|
:method => 'GET',
|
||||||
|
:parser => Fog::Parsers::Terremark::GetCatalog.new,
|
||||||
|
:path => "vdc/#{vdc_id}/catalog"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
module Fog
|
||||||
|
class Terremark
|
||||||
|
|
||||||
|
def get_catalog(vdc_id)
|
||||||
|
raise MockNotImplemented.new("Contributions welcome!")
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
46
lib/fog/terremark/requests/get_organization.rb
Normal file
46
lib/fog/terremark/requests/get_organization.rb
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
unless Fog.mocking?
|
||||||
|
|
||||||
|
module Fog
|
||||||
|
class Terremark
|
||||||
|
|
||||||
|
# Get details of an organization
|
||||||
|
#
|
||||||
|
# ==== Parameters
|
||||||
|
# * organization_id<~Integer> - Id of organization to lookup
|
||||||
|
#
|
||||||
|
# ==== Returns
|
||||||
|
# * response<~Excon::Response>:
|
||||||
|
# * body<~Hash>:
|
||||||
|
# * 'description'<~String> - Description of organization
|
||||||
|
# * 'links'<~Array> - An array of links to entities in the organization
|
||||||
|
# * 'href'<~String> - location of link
|
||||||
|
# * 'name'<~String> - name of link
|
||||||
|
# * 'rel'<~String> - action to perform
|
||||||
|
# * 'type'<~String> - type of link
|
||||||
|
# * 'name'<~String> - Name of organization
|
||||||
|
def get_organization(organization_id)
|
||||||
|
response = request(
|
||||||
|
:expects => 200,
|
||||||
|
:method => 'GET',
|
||||||
|
:parser => Fog::Parsers::Terremark::GetOrganization.new,
|
||||||
|
:path => "org/#{organization_id}"
|
||||||
|
)
|
||||||
|
response
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
module Fog
|
||||||
|
class Terremark
|
||||||
|
|
||||||
|
def get_organization(organization_id)
|
||||||
|
raise MockNotImplemented.new("Contributions welcome!")
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
42
lib/fog/terremark/requests/get_organizations.rb
Normal file
42
lib/fog/terremark/requests/get_organizations.rb
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
unless Fog.mocking?
|
||||||
|
|
||||||
|
module Fog
|
||||||
|
class Terremark
|
||||||
|
|
||||||
|
# Get list of organizations
|
||||||
|
#
|
||||||
|
# ==== Returns
|
||||||
|
# * response<~Excon::Response>:
|
||||||
|
# * body<~Array>:
|
||||||
|
# * 'description'<~String> - Description of organization
|
||||||
|
# * 'links'<~Array> - An array of links to entities in the organization
|
||||||
|
# * 'name'<~String> - Name of organization
|
||||||
|
def get_organizations
|
||||||
|
request({
|
||||||
|
:body => '',
|
||||||
|
:expects => 200,
|
||||||
|
:headers => {
|
||||||
|
'Authorization' => "Basic #{Base64.encode64("#{@terremark_username}:#{@terremark_password}").chomp!}"
|
||||||
|
},
|
||||||
|
:method => 'POST',
|
||||||
|
:parser => Fog::Parsers::Terremark::GetOrganizations.new,
|
||||||
|
:path => 'login'
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
module Fog
|
||||||
|
class Terremark
|
||||||
|
|
||||||
|
def get_organizations
|
||||||
|
raise MockNotImplemented.new("Contributions welcome!")
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
47
lib/fog/terremark/requests/get_vdc.rb
Normal file
47
lib/fog/terremark/requests/get_vdc.rb
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
unless Fog.mocking?
|
||||||
|
|
||||||
|
module Fog
|
||||||
|
class Terremark
|
||||||
|
|
||||||
|
# Get details of a vdc
|
||||||
|
#
|
||||||
|
# ==== Parameters
|
||||||
|
# * vdc_id<~Integer> - Id of vdc to lookup
|
||||||
|
#
|
||||||
|
# ==== Returns
|
||||||
|
# * response<~Excon::Response>:
|
||||||
|
# * body<~Hash>:
|
||||||
|
|
||||||
|
# FIXME
|
||||||
|
|
||||||
|
# * 'CatalogItems'<~Array>
|
||||||
|
# * 'href'<~String> - linke to item
|
||||||
|
# * 'name'<~String> - name of item
|
||||||
|
# * 'type'<~String> - type of item
|
||||||
|
# * 'description'<~String> - Description of catalog
|
||||||
|
# * 'name'<~String> - Name of catalog
|
||||||
|
def get_vdc(vdc_id)
|
||||||
|
request(
|
||||||
|
:expects => 200,
|
||||||
|
:method => 'GET',
|
||||||
|
:parser => Fog::Parsers::Terremark::GetVdc.new,
|
||||||
|
:path => "vdc/#{vdc_id}"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
module Fog
|
||||||
|
class Terremark
|
||||||
|
|
||||||
|
def get_vdc(vdc_id)
|
||||||
|
raise MockNotImplemented.new("Contributions welcome!")
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
Loading…
Add table
Reference in a new issue