vagrant-vmware-appcatalyst 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +38 -0
- data/.rubocop.yml +34 -0
- data/Gemfile +7 -0
- data/LICENSE +177 -0
- data/README.md +72 -0
- data/lib/vagrant-vmware-appcatalyst.rb +30 -0
- data/lib/vagrant-vmware-appcatalyst/action.rb +249 -0
- data/lib/vagrant-vmware-appcatalyst/action/connect_appcatalyst.rb +45 -0
- data/lib/vagrant-vmware-appcatalyst/action/destroy_vm.rb +33 -0
- data/lib/vagrant-vmware-appcatalyst/action/import.rb +101 -0
- data/lib/vagrant-vmware-appcatalyst/action/is_created.rb +34 -0
- data/lib/vagrant-vmware-appcatalyst/action/is_paused.rb +33 -0
- data/lib/vagrant-vmware-appcatalyst/action/is_running.rb +32 -0
- data/lib/vagrant-vmware-appcatalyst/action/message_already_running.rb +29 -0
- data/lib/vagrant-vmware-appcatalyst/action/message_not_created.rb +29 -0
- data/lib/vagrant-vmware-appcatalyst/action/message_not_running.rb +29 -0
- data/lib/vagrant-vmware-appcatalyst/action/message_not_supported.rb +29 -0
- data/lib/vagrant-vmware-appcatalyst/action/message_will_not_destroy.rb +30 -0
- data/lib/vagrant-vmware-appcatalyst/action/power_off.rb +40 -0
- data/lib/vagrant-vmware-appcatalyst/action/power_on.rb +34 -0
- data/lib/vagrant-vmware-appcatalyst/action/read_ssh_info.rb +56 -0
- data/lib/vagrant-vmware-appcatalyst/action/read_state.rb +72 -0
- data/lib/vagrant-vmware-appcatalyst/action/resume.rb +31 -0
- data/lib/vagrant-vmware-appcatalyst/action/suspend.rb +31 -0
- data/lib/vagrant-vmware-appcatalyst/cap/mount_appcatalyst_shared_folder.rb +100 -0
- data/lib/vagrant-vmware-appcatalyst/cap/public_address.rb +31 -0
- data/lib/vagrant-vmware-appcatalyst/config.rb +40 -0
- data/lib/vagrant-vmware-appcatalyst/driver/base.rb +131 -0
- data/lib/vagrant-vmware-appcatalyst/driver/meta.rb +106 -0
- data/lib/vagrant-vmware-appcatalyst/driver/version_1_0.rb +223 -0
- data/lib/vagrant-vmware-appcatalyst/errors.rb +44 -0
- data/lib/vagrant-vmware-appcatalyst/plugin.rb +111 -0
- data/lib/vagrant-vmware-appcatalyst/provider.rb +60 -0
- data/lib/vagrant-vmware-appcatalyst/synced_folder.rb +123 -0
- data/lib/vagrant-vmware-appcatalyst/version.rb +18 -0
- data/locales/en.yml +29 -0
- data/vagrant-vmware-appcatalyst.gemspec +40 -0
- metadata +165 -0
@@ -0,0 +1,106 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# Copyright (c) 2015 VMware, Inc. All Rights Reserved.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
5
|
+
# use this file except in compliance with the License. You may obtain a copy of
|
6
|
+
# the License at http://www.apache.org/licenses/LICENSE-2.0
|
7
|
+
#
|
8
|
+
# Unless required by applicable law or agreed to in writing, software
|
9
|
+
# distributed under the License is distributed on an "AS IS" BASIS, without
|
10
|
+
# warranties or conditions of any kind, EITHER EXPRESS OR IMPLIED. See the
|
11
|
+
# License for the specific language governing permissions and limitations under
|
12
|
+
# the License.
|
13
|
+
|
14
|
+
require 'forwardable'
|
15
|
+
require 'log4r'
|
16
|
+
require 'httpclient'
|
17
|
+
require 'json'
|
18
|
+
|
19
|
+
require File.expand_path('../base', __FILE__)
|
20
|
+
|
21
|
+
module VagrantPlugins
|
22
|
+
module AppCatalyst
|
23
|
+
module Driver
|
24
|
+
class Meta < Base
|
25
|
+
extend Forwardable
|
26
|
+
attr_reader :endpoint
|
27
|
+
attr_reader :version
|
28
|
+
|
29
|
+
def initialize(endpoint)
|
30
|
+
super()
|
31
|
+
|
32
|
+
@logger = Log4r::Logger.new('vagrant::provider::appcatalyst::meta')
|
33
|
+
|
34
|
+
@endpoint = endpoint
|
35
|
+
|
36
|
+
# Read and assign the version of AppCatalyst we know which
|
37
|
+
# specific driver to instantiate.
|
38
|
+
@logger.debug("Asking API Version with host_url: #{@endpoint}")
|
39
|
+
@version = get_api_version(@endpoint) || ''
|
40
|
+
|
41
|
+
# Instantiate the proper version driver for AppCatalyst
|
42
|
+
@logger.debug("Finding driver for AppCatalyst version: #{@version}")
|
43
|
+
driver_map = {
|
44
|
+
'1.0.0' => Version_1_0
|
45
|
+
}
|
46
|
+
|
47
|
+
driver_klass = nil
|
48
|
+
driver_map.each do |key, klass|
|
49
|
+
if @version.start_with?(key)
|
50
|
+
driver_klass = klass
|
51
|
+
break
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
unless driver_klass
|
56
|
+
supported_versions = driver_map.keys.sort.join(', ')
|
57
|
+
fail Errors::AppCatalystInvalidVersion,
|
58
|
+
:supported_versions => supported_versions
|
59
|
+
end
|
60
|
+
|
61
|
+
@logger.info("Using AppCatalyst driver: #{driver_klass}")
|
62
|
+
@driver = driver_klass.new(@endpoint)
|
63
|
+
end
|
64
|
+
|
65
|
+
def_delegators :@driver,
|
66
|
+
:get_vm,
|
67
|
+
:get_vm_power,
|
68
|
+
:get_vm_ipaddress,
|
69
|
+
:set_vm_power,
|
70
|
+
:delete_vm,
|
71
|
+
:import_vm,
|
72
|
+
:list_vm,
|
73
|
+
:get_vm_shared_folders,
|
74
|
+
:set_vm_shared_folders,
|
75
|
+
:add_vm_shared_folder,
|
76
|
+
:get_vm_shared_folder,
|
77
|
+
:delete_vm_shared_folder,
|
78
|
+
:clone_vm_in_directory,
|
79
|
+
:set_vmx_value
|
80
|
+
protected
|
81
|
+
|
82
|
+
def get_api_version(endpoint)
|
83
|
+
# Create a new HTTP client
|
84
|
+
clnt = HTTPClient.new
|
85
|
+
uri = URI(endpoint)
|
86
|
+
url = "#{uri.scheme}://#{uri.host}:#{uri.port}/json/swagger.json"
|
87
|
+
|
88
|
+
begin
|
89
|
+
response = clnt.request('GET', url, nil, nil, nil)
|
90
|
+
unless response.ok?
|
91
|
+
fail Errors::UnattendedCodeError,
|
92
|
+
:message => response.status + ' ' + response.reason
|
93
|
+
end
|
94
|
+
|
95
|
+
api_definition = JSON.parse(response.body)
|
96
|
+
|
97
|
+
api_definition['info']['version']
|
98
|
+
|
99
|
+
rescue SocketError, Errno::EADDRNOTAVAIL, Errno::ETIMEDOUT, Errno::ECONNREFUSED
|
100
|
+
raise Errors::EndpointUnavailable
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,223 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# Copyright (c) 2015 VMware, Inc. All Rights Reserved.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
5
|
+
# use this file except in compliance with the License. You may obtain a copy of
|
6
|
+
# the License at http://www.apache.org/licenses/LICENSE-2.0
|
7
|
+
#
|
8
|
+
# Unless required by applicable law or agreed to in writing, software
|
9
|
+
# distributed under the License is distributed on an "AS IS" BASIS, without
|
10
|
+
# warranties or conditions of any kind, EITHER EXPRESS OR IMPLIED. See the
|
11
|
+
# License for the specific language governing permissions and limitations under
|
12
|
+
# the License.
|
13
|
+
|
14
|
+
require 'uri'
|
15
|
+
require 'vagrant/util/platform'
|
16
|
+
require 'log4r'
|
17
|
+
|
18
|
+
module VagrantPlugins
|
19
|
+
module AppCatalyst
|
20
|
+
module Driver
|
21
|
+
# Main class to access AppCatalyst rest APIs
|
22
|
+
class Version_1_0 < Base
|
23
|
+
|
24
|
+
##
|
25
|
+
# Init the driver with the Vagrantfile information
|
26
|
+
def initialize(endpoint)
|
27
|
+
super()
|
28
|
+
|
29
|
+
@logger = Log4r::Logger.new('vagrant::provider::appcatalyst::driver_1_0')
|
30
|
+
@logger.debug("AppCatalyst Driver 1.0 loaded")
|
31
|
+
@endpoint = endpoint
|
32
|
+
end
|
33
|
+
|
34
|
+
##
|
35
|
+
# Fetch details about a given VM
|
36
|
+
def get_vm(vm_id)
|
37
|
+
params = {
|
38
|
+
'method' => :get,
|
39
|
+
'command' => "/api/vms/#{vm_id}"
|
40
|
+
}
|
41
|
+
|
42
|
+
response, _headers = send_request(params)
|
43
|
+
|
44
|
+
response
|
45
|
+
end
|
46
|
+
|
47
|
+
def get_vm_power(vm_id)
|
48
|
+
params = {
|
49
|
+
'method' => :get,
|
50
|
+
'command' => "/api/vms/power/#{vm_id}"
|
51
|
+
}
|
52
|
+
|
53
|
+
response, _headers = send_request(params)
|
54
|
+
|
55
|
+
response
|
56
|
+
end
|
57
|
+
|
58
|
+
def get_vm_ipaddress(vm_id)
|
59
|
+
params = {
|
60
|
+
'method' => :get,
|
61
|
+
'command' => "/api/vms/#{vm_id}/ipaddress"
|
62
|
+
}
|
63
|
+
|
64
|
+
response, _headers = send_request(params)
|
65
|
+
|
66
|
+
response
|
67
|
+
end
|
68
|
+
|
69
|
+
def set_vm_power(vm_id, operation)
|
70
|
+
params = {
|
71
|
+
'method' => :patch,
|
72
|
+
'command' => "/api/vms/power/#{vm_id}"
|
73
|
+
}
|
74
|
+
|
75
|
+
response, _headers = send_request(
|
76
|
+
params,
|
77
|
+
operation,
|
78
|
+
'application/json'
|
79
|
+
)
|
80
|
+
|
81
|
+
response
|
82
|
+
end
|
83
|
+
|
84
|
+
def delete_vm(vm_id)
|
85
|
+
params = {
|
86
|
+
'method' => :delete,
|
87
|
+
'command' => "/api/vms/#{vm_id}"
|
88
|
+
}
|
89
|
+
|
90
|
+
_response, _headers = send_request(params)
|
91
|
+
end
|
92
|
+
|
93
|
+
def import_vm(vm_id, name, source_reference, tag)
|
94
|
+
@logger.debug("Importing #{name}")
|
95
|
+
vm_to_import = {
|
96
|
+
'id' => vm_id,
|
97
|
+
'name' => name,
|
98
|
+
'sourceReference' => source_reference,
|
99
|
+
'tag' => tag
|
100
|
+
}
|
101
|
+
|
102
|
+
params = {
|
103
|
+
'method' => :post,
|
104
|
+
'command' => '/api/vms'
|
105
|
+
}
|
106
|
+
|
107
|
+
@logger.debug("JSON stuff #{JSON.generate(vm_to_import)}")
|
108
|
+
response, _headers = send_request(
|
109
|
+
params,
|
110
|
+
JSON.generate(vm_to_import),
|
111
|
+
'application/json'
|
112
|
+
)
|
113
|
+
|
114
|
+
response
|
115
|
+
end
|
116
|
+
|
117
|
+
def list_vms
|
118
|
+
params = {
|
119
|
+
'method' => :get,
|
120
|
+
'command' => '/api/vms'
|
121
|
+
}
|
122
|
+
|
123
|
+
response, _headers = send_request(params)
|
124
|
+
|
125
|
+
response
|
126
|
+
end
|
127
|
+
|
128
|
+
def get_vm_shared_folders(vm_id)
|
129
|
+
params = {
|
130
|
+
'method' => :get,
|
131
|
+
'command' => "/api/vms/#{vm_id}/folders"
|
132
|
+
}
|
133
|
+
|
134
|
+
response, _headers = send_request(params)
|
135
|
+
|
136
|
+
response
|
137
|
+
end
|
138
|
+
|
139
|
+
def set_vm_shared_folders(vm_id, operation)
|
140
|
+
params = {
|
141
|
+
'method' => :patch,
|
142
|
+
'command' => "/api/vms/#{vm_id}/folders"
|
143
|
+
}
|
144
|
+
|
145
|
+
response, _headers = send_request(
|
146
|
+
params,
|
147
|
+
operation,
|
148
|
+
'application/json'
|
149
|
+
)
|
150
|
+
|
151
|
+
response
|
152
|
+
end
|
153
|
+
|
154
|
+
def add_vm_shared_folder(vm_id, guest_path, host_path, flags)
|
155
|
+
@logger.debug("Adding Shared Folder #{guest_path} to #{vm_id}")
|
156
|
+
shared_folder_to_add = {
|
157
|
+
'guestPath' => guest_path,
|
158
|
+
'hostPath' => host_path,
|
159
|
+
'flags' => flags
|
160
|
+
}
|
161
|
+
|
162
|
+
params = {
|
163
|
+
'method' => :post,
|
164
|
+
'command' => "/api/vms/#{vm_id}/folders"
|
165
|
+
}
|
166
|
+
|
167
|
+
@logger.debug("JSON stuff #{JSON.generate(shared_folder_to_add)}")
|
168
|
+
response, _headers = send_request(
|
169
|
+
params,
|
170
|
+
JSON.generate(shared_folder_to_add),
|
171
|
+
'application/json'
|
172
|
+
)
|
173
|
+
|
174
|
+
response
|
175
|
+
end
|
176
|
+
|
177
|
+
def get_vm_shared_folder(vm_id, shared_folder_id)
|
178
|
+
params = {
|
179
|
+
'method' => :get,
|
180
|
+
'command' => "/api/vms/#{vm_id}/folders/#{shared_folder_id}"
|
181
|
+
}
|
182
|
+
|
183
|
+
response, _headers = send_request(params)
|
184
|
+
|
185
|
+
response
|
186
|
+
end
|
187
|
+
|
188
|
+
def delete_vm_shared_folder(vm_id, shared_folder_id)
|
189
|
+
params = {
|
190
|
+
'method' => :delete,
|
191
|
+
'command' => "/api/vms/#{vm_id}/folders/#{shared_folder_id}"
|
192
|
+
}
|
193
|
+
|
194
|
+
_response, _headers = send_request(params)
|
195
|
+
end
|
196
|
+
|
197
|
+
def clone_vm_in_directory(src, dest)
|
198
|
+
@logger.debug("Cloning VM from #{src} to #{dest}")
|
199
|
+
FileUtils.cp_r("#{src}/.", dest)
|
200
|
+
end
|
201
|
+
|
202
|
+
def set_vmx_value(vmx_file, key, value)
|
203
|
+
# read VMX in a hash
|
204
|
+
temp_vmx = Hash[File.read(vmx_file).scan(/^(.+?)\s*=\s*"(.*?)"\s*$/)]
|
205
|
+
vmx = Hash[temp_vmx.map { |k, v| v.class == Array ? [k, v.map { |r| f r }.to_a] : [k.downcase, v]}]
|
206
|
+
@logger.debug("Setting #{key} = #{value} in VMX")
|
207
|
+
# Set the key/value
|
208
|
+
vmx[key.downcase] = value
|
209
|
+
|
210
|
+
# Open file for writing
|
211
|
+
f = File.open(vmx_file, 'w')
|
212
|
+
|
213
|
+
# Write file in order
|
214
|
+
vmx.sort.map do |k, v|
|
215
|
+
f.write("#{k} = \"#{v}\"\n")
|
216
|
+
end
|
217
|
+
|
218
|
+
f.close
|
219
|
+
end
|
220
|
+
end # Class Version 5.1
|
221
|
+
end # Module Driver
|
222
|
+
end # module AppCatalyst
|
223
|
+
end # Module VagrantPlugins
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# Copyright (c) 2015 VMware, Inc. All Rights Reserved.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
5
|
+
# use this file except in compliance with the License. You may obtain a copy of
|
6
|
+
# the License at http://www.apache.org/licenses/LICENSE-2.0
|
7
|
+
#
|
8
|
+
# Unless required by applicable law or agreed to in writing, software
|
9
|
+
# distributed under the License is distributed on an "AS IS" BASIS, without
|
10
|
+
# warranties or conditions of any kind, EITHER EXPRESS OR IMPLIED. See the
|
11
|
+
# License for the specific language governing permissions and limitations under
|
12
|
+
# the License.
|
13
|
+
|
14
|
+
require 'vagrant'
|
15
|
+
|
16
|
+
module VagrantPlugins
|
17
|
+
module AppCatalyst
|
18
|
+
module Errors
|
19
|
+
# Generic Errors during Vagrant execution
|
20
|
+
class AppCatalystGenericError < Vagrant::Errors::VagrantError
|
21
|
+
error_namespace('vagrant_appcatalyst.errors')
|
22
|
+
end
|
23
|
+
class AppCatalystOldVersion < AppCatalystGenericError
|
24
|
+
error_key(:appcatalyst_old_version)
|
25
|
+
end
|
26
|
+
# Errors in the REST API communication
|
27
|
+
class AppCatalystRestError < Vagrant::Errors::VagrantError
|
28
|
+
error_namespace('vagrant_appcatalyst.errors.rest_errors')
|
29
|
+
end
|
30
|
+
class UnattendedCodeError < AppCatalystRestError
|
31
|
+
error_key(:unattended_code_error)
|
32
|
+
end
|
33
|
+
class EndpointUnavailable < AppCatalystRestError
|
34
|
+
error_key(:endpoint_unavailable)
|
35
|
+
end
|
36
|
+
class AppCatalystViolationError < Vagrant::Errors::VagrantError
|
37
|
+
error_namespace('vagrant_appcatalyst.errors.violations')
|
38
|
+
end
|
39
|
+
class PowerOnNotAllowed < AppCatalystViolationError
|
40
|
+
error_key(:poweron_not_allowed)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# Copyright (c) 2015 VMware, Inc. All Rights Reserved.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
5
|
+
# use this file except in compliance with the License. You may obtain a copy of
|
6
|
+
# the License at http://www.apache.org/licenses/LICENSE-2.0
|
7
|
+
#
|
8
|
+
# Unless required by applicable law or agreed to in writing, software
|
9
|
+
# distributed under the License is distributed on an "AS IS" BASIS, without
|
10
|
+
# warranties or conditions of any kind, EITHER EXPRESS OR IMPLIED. See the
|
11
|
+
# License for the specific language governing permissions and limitations under
|
12
|
+
# the License.
|
13
|
+
|
14
|
+
begin
|
15
|
+
require 'vagrant'
|
16
|
+
rescue LoadError
|
17
|
+
raise 'The Vagrant AppCatalyst plugin must be run within Vagrant.'
|
18
|
+
end
|
19
|
+
|
20
|
+
if Vagrant::VERSION < '1.6.3'
|
21
|
+
fail 'The Vagrant AppCatalyst plugin is only compatible with Vagrant 1.6.3+'
|
22
|
+
end
|
23
|
+
|
24
|
+
module VagrantPlugins
|
25
|
+
module AppCatalyst
|
26
|
+
class Plugin < Vagrant.plugin('2')
|
27
|
+
name 'VMware AppCatalyst Provider'
|
28
|
+
description 'Allows Vagrant to manage machines with VMware AppCatalyst®'
|
29
|
+
|
30
|
+
config(:vmware_appcatalyst, :provider) do
|
31
|
+
require_relative 'config'
|
32
|
+
Config
|
33
|
+
end
|
34
|
+
|
35
|
+
# We provide support for multiple box formats.
|
36
|
+
provider(
|
37
|
+
:vmware_appcatalyst,
|
38
|
+
box_format: %w(vmware_desktop vmware_fusion vmware_workstation),
|
39
|
+
parallel: true
|
40
|
+
) do
|
41
|
+
setup_logging
|
42
|
+
setup_i18n
|
43
|
+
|
44
|
+
# Return the provider
|
45
|
+
require_relative 'provider'
|
46
|
+
Provider
|
47
|
+
end
|
48
|
+
|
49
|
+
# Add vagrant share support
|
50
|
+
provider_capability('vmware_appcatalyst', 'public_address') do
|
51
|
+
require_relative 'cap/public_address'
|
52
|
+
Cap::PublicAddress
|
53
|
+
end
|
54
|
+
|
55
|
+
synced_folder(:vmware_appcatalyst) do
|
56
|
+
require File.expand_path('../synced_folder', __FILE__)
|
57
|
+
SyncedFolder
|
58
|
+
end
|
59
|
+
|
60
|
+
# Add vmware shared folders mount capability to linux
|
61
|
+
guest_capability('linux', 'mount_appcatalyst_shared_folder') do
|
62
|
+
require_relative 'cap/mount_appcatalyst_shared_folder'
|
63
|
+
Cap::MountAppCatalystSharedFolder
|
64
|
+
end
|
65
|
+
|
66
|
+
guest_capability('linux', 'unmount_appcatalyst_shared_folder') do
|
67
|
+
require_relative 'cap/mount_appcatalyst_shared_folder'
|
68
|
+
Cap::MountAppCatalystSharedFolder
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.setup_i18n
|
72
|
+
I18n.load_path << File.expand_path('locales/en.yml', AppCatalyst.source_root)
|
73
|
+
I18n.reload!
|
74
|
+
end
|
75
|
+
|
76
|
+
# This sets up our log level to be whatever VAGRANT_LOG is.
|
77
|
+
def self.setup_logging
|
78
|
+
require 'log4r'
|
79
|
+
|
80
|
+
level = nil
|
81
|
+
begin
|
82
|
+
level = Log4r.const_get(ENV['VAGRANT_LOG'].upcase)
|
83
|
+
rescue NameError
|
84
|
+
# This means that the logging constant wasn't found,
|
85
|
+
# which is fine. We just keep `level` as `nil`. But
|
86
|
+
# we tell the user.
|
87
|
+
level = nil
|
88
|
+
end
|
89
|
+
|
90
|
+
# Some constants, such as 'true' resolve to booleans, so the
|
91
|
+
# above error checking doesn't catch it. This will check to make
|
92
|
+
# sure that the log level is an integer, as Log4r requires.
|
93
|
+
level = nil unless level.is_a?(Integer)
|
94
|
+
|
95
|
+
# Set the logging level on all 'vagrant' namespaced
|
96
|
+
# logs as long as we have a valid level.
|
97
|
+
if level
|
98
|
+
logger = Log4r::Logger.new('vagrant_appcatalyst')
|
99
|
+
logger.outputters = Log4r::Outputter.stderr
|
100
|
+
logger.level = level
|
101
|
+
# logger = nil
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
module Driver
|
107
|
+
autoload :Meta, File.expand_path('../driver/meta', __FILE__)
|
108
|
+
autoload :Version_1_0, File.expand_path('../driver/version_1_0', __FILE__)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|