vagrant-vmware-desktop 0.0.1 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/vagrant-vmware-desktop.rb +190 -0
- data/lib/vagrant-vmware-desktop/action.rb +442 -0
- data/lib/vagrant-vmware-desktop/action/base_mac_to_ip.rb +55 -0
- data/lib/vagrant-vmware-desktop/action/boot.rb +26 -0
- data/lib/vagrant-vmware-desktop/action/check_existing_network.rb +35 -0
- data/lib/vagrant-vmware-desktop/action/check_vmware.rb +28 -0
- data/lib/vagrant-vmware-desktop/action/checkpoint.rb +86 -0
- data/lib/vagrant-vmware-desktop/action/clear_shared_folders.rb +25 -0
- data/lib/vagrant-vmware-desktop/action/common.rb +16 -0
- data/lib/vagrant-vmware-desktop/action/compatibility.rb +36 -0
- data/lib/vagrant-vmware-desktop/action/created.rb +20 -0
- data/lib/vagrant-vmware-desktop/action/destroy.rb +32 -0
- data/lib/vagrant-vmware-desktop/action/discard_suspended_state.rb +32 -0
- data/lib/vagrant-vmware-desktop/action/export.rb +29 -0
- data/lib/vagrant-vmware-desktop/action/fix_old_machine_id.rb +29 -0
- data/lib/vagrant-vmware-desktop/action/forward_ports.rb +110 -0
- data/lib/vagrant-vmware-desktop/action/halt.rb +27 -0
- data/lib/vagrant-vmware-desktop/action/import.rb +138 -0
- data/lib/vagrant-vmware-desktop/action/machine_lock.rb +26 -0
- data/lib/vagrant-vmware-desktop/action/message_already_running.rb +18 -0
- data/lib/vagrant-vmware-desktop/action/message_not_created.rb +18 -0
- data/lib/vagrant-vmware-desktop/action/message_not_running.rb +18 -0
- data/lib/vagrant-vmware-desktop/action/network.rb +339 -0
- data/lib/vagrant-vmware-desktop/action/package_vagrantfile.rb +46 -0
- data/lib/vagrant-vmware-desktop/action/prepare_forwarded_port_collision_params.rb +28 -0
- data/lib/vagrant-vmware-desktop/action/prepare_nfs_settings.rb +43 -0
- data/lib/vagrant-vmware-desktop/action/prepare_synced_folder_cleanup.rb +19 -0
- data/lib/vagrant-vmware-desktop/action/prune_forwarded_ports.rb +30 -0
- data/lib/vagrant-vmware-desktop/action/prune_nfs_exports.rb +22 -0
- data/lib/vagrant-vmware-desktop/action/running.rb +20 -0
- data/lib/vagrant-vmware-desktop/action/set_display_name.rb +37 -0
- data/lib/vagrant-vmware-desktop/action/share_folders.rb +97 -0
- data/lib/vagrant-vmware-desktop/action/snapshot_delete.rb +26 -0
- data/lib/vagrant-vmware-desktop/action/snapshot_restore.rb +26 -0
- data/lib/vagrant-vmware-desktop/action/snapshot_save.rb +26 -0
- data/lib/vagrant-vmware-desktop/action/suspend.rb +26 -0
- data/lib/vagrant-vmware-desktop/action/suspended.rb +24 -0
- data/lib/vagrant-vmware-desktop/action/vmx_modify.rb +39 -0
- data/lib/vagrant-vmware-desktop/action/wait_for_address.rb +31 -0
- data/lib/vagrant-vmware-desktop/action/wait_for_communicator_compat.rb +32 -0
- data/lib/vagrant-vmware-desktop/action/wait_for_vmx_halt.rb +35 -0
- data/lib/vagrant-vmware-desktop/cap/disk.rb +287 -0
- data/lib/vagrant-vmware-desktop/cap/provider.rb +37 -0
- data/lib/vagrant-vmware-desktop/cap/snapshot.rb +41 -0
- data/lib/vagrant-vmware-desktop/checkpoint_client.rb +203 -0
- data/lib/vagrant-vmware-desktop/config.rb +377 -0
- data/lib/vagrant-vmware-desktop/constants.rb +16 -0
- data/lib/vagrant-vmware-desktop/driver.rb +15 -0
- data/lib/vagrant-vmware-desktop/driver/base.rb +1356 -0
- data/lib/vagrant-vmware-desktop/errors.rb +342 -0
- data/lib/vagrant-vmware-desktop/guest_cap/linux/mount_vmware_shared_folder.rb +158 -0
- data/lib/vagrant-vmware-desktop/guest_cap/linux/verify_vmware_hgfs.rb +27 -0
- data/lib/vagrant-vmware-desktop/helper/lock.rb +26 -0
- data/lib/vagrant-vmware-desktop/helper/routing_table.rb +182 -0
- data/lib/vagrant-vmware-desktop/helper/vagrant_utility.rb +185 -0
- data/lib/vagrant-vmware-desktop/plugin.rb +148 -0
- data/lib/vagrant-vmware-desktop/provider.rb +96 -0
- data/lib/vagrant-vmware-desktop/setup_plugin.rb +24 -0
- data/lib/vagrant-vmware-desktop/synced_folder.rb +93 -0
- data/locales/en.yml +634 -0
- metadata +71 -17
@@ -0,0 +1,55 @@
|
|
1
|
+
require "log4r"
|
2
|
+
require "ipaddr"
|
3
|
+
|
4
|
+
module HashiCorp
|
5
|
+
module VagrantVMwareDesktop
|
6
|
+
module Action
|
7
|
+
# This action maps an IP address to a MAC address in the DHCP
|
8
|
+
# server for the default NAT interface
|
9
|
+
class BaseMacToIp
|
10
|
+
include Common
|
11
|
+
|
12
|
+
def initialize(app, env)
|
13
|
+
@app = app
|
14
|
+
@logger = Log4r::Logger.new("hashicorp::provider::vmware::basemactoip")
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(env)
|
18
|
+
if env[:machine].provider_config.base_address
|
19
|
+
ip = env[:machine].provider_config.base_address
|
20
|
+
mac = env[:machine].provider_config.base_mac
|
21
|
+
|
22
|
+
validate_address!(env[:machine].provider.driver,
|
23
|
+
ip, env[:machine].provider_config.nat_device)
|
24
|
+
|
25
|
+
env[:machine].provider.driver.reserve_dhcp_address(ip, mac, env[:machine].provider_config.nat_device)
|
26
|
+
|
27
|
+
env[:ui].info(I18n.t("hashicorp.vagrant_vmware_desktop.mac_to_ip_mapping",
|
28
|
+
address: ip, mac: mac))
|
29
|
+
end
|
30
|
+
@app.call(env)
|
31
|
+
end
|
32
|
+
|
33
|
+
def validate_address!(driver, address, nat_device)
|
34
|
+
devices = driver.read_vmnet_devices
|
35
|
+
vmnet = devices.detect { |v| v[:name] == nat_device }
|
36
|
+
if !vmnet
|
37
|
+
raise Errors::MissingNATDevice
|
38
|
+
end
|
39
|
+
# NOTE: VMware dhcpd is configured as follows for addressing:
|
40
|
+
# .1 -> host machine
|
41
|
+
# .2 - .127 -> static address (valid here)
|
42
|
+
# .128 - .253 -> DHCP assigned
|
43
|
+
# .254 -> DHCP server
|
44
|
+
dev_subnet = "#{vmnet[:hostonly_subnet]}/25"
|
45
|
+
dev_addr = IPAddr.new(dev_subnet)
|
46
|
+
if address.end_with?(".1") || !dev_addr.include?(address)
|
47
|
+
raise Errors::BaseAddressRange,
|
48
|
+
range_start: dev_addr.to_range.first.succ.succ.to_s,
|
49
|
+
range_end: dev_addr.to_range.last.to_s
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "log4r"
|
2
|
+
|
3
|
+
module HashiCorp
|
4
|
+
module VagrantVMwareDesktop
|
5
|
+
module Action
|
6
|
+
# This class boots the actual VMware VM. It also waits
|
7
|
+
# for it to complete booting.
|
8
|
+
class Boot
|
9
|
+
include Common
|
10
|
+
|
11
|
+
def initialize(app, env)
|
12
|
+
@app = app
|
13
|
+
@logger = Log4r::Logger.new("hashicorp::provider::vmware::boot")
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(env)
|
17
|
+
env[:ui].info(I18n.t("hashicorp.vagrant_vmware_desktop.booting"))
|
18
|
+
env[:machine].provider.driver.start(
|
19
|
+
env[:machine].provider_config.gui)
|
20
|
+
|
21
|
+
@app.call(env)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require "log4r"
|
2
|
+
|
3
|
+
require "vagrant-vmware-desktop/helper/lock"
|
4
|
+
|
5
|
+
module HashiCorp
|
6
|
+
module VagrantVMwareDesktop
|
7
|
+
module Action
|
8
|
+
# This class checks that the existing network interfaces are all
|
9
|
+
# properly running and are happy, and shows an error otherwise.
|
10
|
+
class CheckExistingNetwork
|
11
|
+
include Common
|
12
|
+
|
13
|
+
def initialize(app, env)
|
14
|
+
@app = app
|
15
|
+
@logger = Log4r::Logger.new("hashicorp::provider::vmware::check_existing_network")
|
16
|
+
end
|
17
|
+
|
18
|
+
def call(env)
|
19
|
+
if env[:machine].provider_config.verify_vmnet
|
20
|
+
@logger.info("Checking if the vmnet devices are healthy...")
|
21
|
+
Helper::Lock.lock(env[:machine], "vmware-network") do
|
22
|
+
env[:ui].info(I18n.t("hashicorp.vagrant_vmware_desktop.verifying_vmnet"))
|
23
|
+
env[:machine].provider.driver.verify_vmnet!
|
24
|
+
end
|
25
|
+
else
|
26
|
+
@logger.info("Skipping vmnet device verificiation, vmnet_verify option is false.")
|
27
|
+
env[:ui].info(I18n.t("hashicorp.vagrant_vmware_desktop.skipping_vmnet_verify"))
|
28
|
+
end
|
29
|
+
|
30
|
+
@app.call(env)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module HashiCorp
|
2
|
+
module VagrantVMwareDesktop
|
3
|
+
module Action
|
4
|
+
# This class checks to verify that VMware is properly
|
5
|
+
# installed and configured in such a way that Vagrant can use it.
|
6
|
+
class CheckVMware
|
7
|
+
include Common
|
8
|
+
|
9
|
+
def initialize(app, env)
|
10
|
+
@app = app
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(env)
|
14
|
+
if !env[:check_vmware_complete]
|
15
|
+
# Tell the provider to verify itself.
|
16
|
+
env[:machine].provider.driver.verify!
|
17
|
+
|
18
|
+
# Mark that we completed it so we only do that once
|
19
|
+
env[:check_vmware_complete] = true
|
20
|
+
end
|
21
|
+
|
22
|
+
# Carry on
|
23
|
+
@app.call(env)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module HashiCorp
|
2
|
+
module VagrantVMwareDesktop
|
3
|
+
module Action
|
4
|
+
# This class checks if there is a new release of the Vagrant
|
5
|
+
# VMware desktop plugin or the Vagrant VMware utility available
|
6
|
+
# and notifies the user
|
7
|
+
class Checkpoint
|
8
|
+
include Common
|
9
|
+
|
10
|
+
def initialize(app, env)
|
11
|
+
@app = app
|
12
|
+
@logger = Log4r::Logger.new("hashicorp::provider::vmware::checkpoint")
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(env)
|
16
|
+
if !env[:checkpoint_complete]
|
17
|
+
if !CheckpointClient.instance.complete?
|
18
|
+
@logger.debug("waiting for checkpoint to complete...")
|
19
|
+
end
|
20
|
+
CheckpointClient.instance.result.each_pair do |name, result|
|
21
|
+
next if !result.is_a?(Hash)
|
22
|
+
next if result["cached"]
|
23
|
+
version_check(env, name, result)
|
24
|
+
alerts_check(env, name, result)
|
25
|
+
end
|
26
|
+
@logger.debug("checkpoint data processing complete")
|
27
|
+
env[:checkpoint_complete] = true
|
28
|
+
end
|
29
|
+
@app.call(env)
|
30
|
+
end
|
31
|
+
|
32
|
+
def alerts_check(env, name, result)
|
33
|
+
full_name = "vagrant-vmware-#{name}"
|
34
|
+
if result["alerts"] && !result["alerts"].empty?
|
35
|
+
result["alerts"].group_by{|a| a["level"]}.each_pair do |_, alerts|
|
36
|
+
alerts.each do |alert|
|
37
|
+
date = nil
|
38
|
+
begin
|
39
|
+
date = Time.at(alert["date"])
|
40
|
+
rescue
|
41
|
+
date = Time.now
|
42
|
+
end
|
43
|
+
output = I18n.t("vagrant.hashicorp.vagrant_vmware_desktop.alert",
|
44
|
+
message: alert["message"],
|
45
|
+
date: date,
|
46
|
+
url: alert["url"]
|
47
|
+
)
|
48
|
+
case alert["level"]
|
49
|
+
when "info"
|
50
|
+
alert_ui = Vagrant::UI::Prefixed.new(env.ui, full_name)
|
51
|
+
alert_ui.info(output)
|
52
|
+
when "warn"
|
53
|
+
alert_ui = Vagrant::UI::Prefixed.new(env.ui, "#{full_name}-warning")
|
54
|
+
alert_ui.warn(output)
|
55
|
+
when "critical"
|
56
|
+
alert_ui = Vagrant::UI::Prefixed.new(env.ui, "#{full_name}-alert")
|
57
|
+
alert_ui.error(output)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
env.ui.info("")
|
61
|
+
end
|
62
|
+
else
|
63
|
+
@logger.debug("no alert notifications to display")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def version_check(env, name, result)
|
68
|
+
name = name.to_s
|
69
|
+
latest_version = Gem::Version.new(result["current_version"])
|
70
|
+
installed_version = Gem::Version.new(result["installed_version"])
|
71
|
+
ui = Vagrant::UI::Prefixed.new(env.ui, "vagrant-vmware-#{name}")
|
72
|
+
if latest_version > installed_version
|
73
|
+
@logger.info("new version of Vagrant VMware #{name.capitalize} available - #{latest_version}")
|
74
|
+
ui.info(I18n.t("hashicorp.vagrant_vmware_desktop.version_upgrade_available",
|
75
|
+
name: name.capitalize,
|
76
|
+
download_url: download_url["current_download_url"],
|
77
|
+
latest_version: latest_version))
|
78
|
+
env.ui.info("")
|
79
|
+
else
|
80
|
+
@logger.debug("Vagrant VMware #{name.capitalize} is currently up to date")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "log4r"
|
2
|
+
|
3
|
+
module HashiCorp
|
4
|
+
module VagrantVMwareDesktop
|
5
|
+
module Action
|
6
|
+
# This class removes all shared folders from the VM.
|
7
|
+
class ClearSharedFolders
|
8
|
+
include Common
|
9
|
+
|
10
|
+
def initialize(app, env)
|
11
|
+
@app = app
|
12
|
+
@logger = Log4r::Logger.new("hashicorp::provider::vmware::clear_shared_folders")
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(env)
|
16
|
+
@logger.info("Clearing shared folders")
|
17
|
+
env[:machine].provider.driver.clear_shared_folders
|
18
|
+
|
19
|
+
# Carry on
|
20
|
+
@app.call(env)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module HashiCorp
|
2
|
+
module VagrantVMwareDesktop
|
3
|
+
module Action
|
4
|
+
module Common
|
5
|
+
# We don't want to expose the entire path to the middleware
|
6
|
+
# class, so we just give ourselves the class name.
|
7
|
+
#
|
8
|
+
# @return [String]
|
9
|
+
def to_s
|
10
|
+
class_name = self.class.to_s.split("::").last
|
11
|
+
"VMware Middleware: #{class_name}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module HashiCorp
|
2
|
+
module VagrantVMwareDesktop
|
3
|
+
module Action
|
4
|
+
# This modifies the env with some compatibility layers for
|
5
|
+
# prior versions of Vagrant.
|
6
|
+
class Compatibility
|
7
|
+
include Common
|
8
|
+
|
9
|
+
def initialize(app, env)
|
10
|
+
@app = app
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(env)
|
14
|
+
return @app.call(env) if env[:_vmware_compatibility]
|
15
|
+
|
16
|
+
if Vagrant::VERSION < "1.5.0"
|
17
|
+
if env[:ui]
|
18
|
+
# Create the new UI methods
|
19
|
+
ui = env[:ui]
|
20
|
+
def ui.detail(*args)
|
21
|
+
self.info(*args)
|
22
|
+
end
|
23
|
+
|
24
|
+
def ui.output(*args)
|
25
|
+
self.info(*args)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
env[:_vmware_compatibility] = true
|
31
|
+
@app.call(env)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module HashiCorp
|
2
|
+
module VagrantVMwareDesktop
|
3
|
+
module Action
|
4
|
+
# This can be used with "Call" built-in to check if the machine
|
5
|
+
# is created and branch in the middleware.
|
6
|
+
class Created
|
7
|
+
include Common
|
8
|
+
|
9
|
+
def initialize(app, env)
|
10
|
+
@app = app
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(env)
|
14
|
+
env[:result] = env[:machine].state.id != :not_created
|
15
|
+
@app.call(env)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require "log4r"
|
2
|
+
|
3
|
+
module HashiCorp
|
4
|
+
module VagrantVMwareDesktop
|
5
|
+
module Action
|
6
|
+
# This deletes the machine from the system.
|
7
|
+
class Destroy
|
8
|
+
include Common
|
9
|
+
|
10
|
+
def initialize(app, env)
|
11
|
+
@app = app
|
12
|
+
@logger = Log4r::Logger.new("hashicorp::provider::vmware::destroy")
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(env)
|
16
|
+
if env[:machine].provider.state.id == :running
|
17
|
+
raise Errors::DestroyInvalidState
|
18
|
+
end
|
19
|
+
|
20
|
+
# As long as the VM exists, we destroy it.
|
21
|
+
if env[:machine].provider.state.id != :not_created
|
22
|
+
env[:ui].info(I18n.t("hashicorp.vagrant_vmware_desktop.destroying"))
|
23
|
+
env[:machine].provider.driver.delete
|
24
|
+
env[:machine].id = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
@app.call(env)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require "log4r"
|
2
|
+
|
3
|
+
module HashiCorp
|
4
|
+
module VagrantVMwareDesktop
|
5
|
+
module Action
|
6
|
+
# This discards the suspended state of the machine.
|
7
|
+
class DiscardSuspendedState
|
8
|
+
include Common
|
9
|
+
|
10
|
+
def initialize(app, env)
|
11
|
+
@app = app
|
12
|
+
@logger = Log4r::Logger.new("hashicorp::provider::vmware::discard_suspended_state")
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(env)
|
16
|
+
machine_state = env[:machine].provider.state.id
|
17
|
+
if machine_state == :suspended
|
18
|
+
env[:ui].info(I18n.t("hashicorp.vagrant_vmware_desktop.discarding_suspended_state"))
|
19
|
+
end
|
20
|
+
|
21
|
+
if machine_state != :not_created
|
22
|
+
# We don't care if the machine is suspended or not, just always
|
23
|
+
# delete the suspended state if it is created, since that is our job
|
24
|
+
env[:machine].provider.driver.discard_suspended_state
|
25
|
+
end
|
26
|
+
|
27
|
+
@app.call(env)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module HashiCorp
|
2
|
+
module VagrantVMwareDesktop
|
3
|
+
module Action
|
4
|
+
class Export
|
5
|
+
def initialize(app, env)
|
6
|
+
@app = app
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(env)
|
10
|
+
@env = env
|
11
|
+
|
12
|
+
raise Vagrant::Errors::VMPowerOffToPackage if \
|
13
|
+
@env[:machine].state.id != :not_running
|
14
|
+
|
15
|
+
path = File.join(@env["export.temp_dir"], "box.vmx")
|
16
|
+
|
17
|
+
if Vagrant::Util::Platform.respond_to?(:wsl?) && Vagrant::Util::Platform.wsl?
|
18
|
+
path = Vagrant::Util::Platform.wsl_to_windows_path(path)
|
19
|
+
end
|
20
|
+
|
21
|
+
@env[:ui].info I18n.t("vagrant.actions.vm.export.exporting")
|
22
|
+
@env[:machine].provider.driver.export(path)
|
23
|
+
|
24
|
+
@app.call(env)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "log4r"
|
2
|
+
|
3
|
+
module HashiCorp
|
4
|
+
module VagrantVMwareDesktop
|
5
|
+
module Action
|
6
|
+
# This fixes old-style machine IDs that were set in early versions of
|
7
|
+
# the VMware providers.
|
8
|
+
class FixOldMachineID
|
9
|
+
include Common
|
10
|
+
|
11
|
+
def initialize(app, env)
|
12
|
+
@app = app
|
13
|
+
@logger = Log4r::Logger.new("hashicorp::provider::vmware::fix_old_machine_id")
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(env)
|
17
|
+
driver = env[:machine].provider.driver
|
18
|
+
machine_id = env[:machine].id
|
19
|
+
if machine_id && driver.vmx_path.to_s != machine_id
|
20
|
+
@logger.warn("Old-style ID found. Resetting to VMX path.")
|
21
|
+
env[:machine].id = driver.vmx_path.to_s
|
22
|
+
end
|
23
|
+
|
24
|
+
@app.call(env)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|