vagrant-openstack-plugin-tom 0.12.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.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/.travis.yml +11 -0
- data/Authors.txt +11 -0
- data/CHANGELOG.md +185 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +23 -0
- data/README.md +278 -0
- data/Rakefile +21 -0
- data/dummy.box +0 -0
- data/example_box/README.md +13 -0
- data/example_box/metadata.json +3 -0
- data/example_vagrant_file +24 -0
- data/lib/vagrant-openstack-plugin.rb +53 -0
- data/lib/vagrant-openstack-plugin/action.rb +268 -0
- data/lib/vagrant-openstack-plugin/action/connect_openstack.rb +90 -0
- data/lib/vagrant-openstack-plugin/action/create_network_interfaces.rb +52 -0
- data/lib/vagrant-openstack-plugin/action/create_orchestration_stack.rb +97 -0
- data/lib/vagrant-openstack-plugin/action/create_server.rb +263 -0
- data/lib/vagrant-openstack-plugin/action/delete_orchestration_stack.rb +78 -0
- data/lib/vagrant-openstack-plugin/action/delete_server.rb +84 -0
- data/lib/vagrant-openstack-plugin/action/hard_reboot_server.rb +27 -0
- data/lib/vagrant-openstack-plugin/action/is_created.rb +16 -0
- data/lib/vagrant-openstack-plugin/action/is_paused.rb +16 -0
- data/lib/vagrant-openstack-plugin/action/is_snapshoting.rb +24 -0
- data/lib/vagrant-openstack-plugin/action/is_suspended.rb +16 -0
- data/lib/vagrant-openstack-plugin/action/message_already_created.rb +16 -0
- data/lib/vagrant-openstack-plugin/action/message_already_paused.rb +16 -0
- data/lib/vagrant-openstack-plugin/action/message_already_suspended.rb +16 -0
- data/lib/vagrant-openstack-plugin/action/message_not_created.rb +16 -0
- data/lib/vagrant-openstack-plugin/action/message_server_running.rb +16 -0
- data/lib/vagrant-openstack-plugin/action/message_snapshot_done.rb +16 -0
- data/lib/vagrant-openstack-plugin/action/message_snapshot_in_progress.rb +16 -0
- data/lib/vagrant-openstack-plugin/action/message_will_not_destroy.rb +16 -0
- data/lib/vagrant-openstack-plugin/action/pause_server.rb +27 -0
- data/lib/vagrant-openstack-plugin/action/read_ssh_info.rb +103 -0
- data/lib/vagrant-openstack-plugin/action/read_state.rb +39 -0
- data/lib/vagrant-openstack-plugin/action/reboot_server.rb +27 -0
- data/lib/vagrant-openstack-plugin/action/resume_server.rb +31 -0
- data/lib/vagrant-openstack-plugin/action/suspend_server.rb +27 -0
- data/lib/vagrant-openstack-plugin/action/sync_folders.rb +104 -0
- data/lib/vagrant-openstack-plugin/action/take_snapshot.rb +26 -0
- data/lib/vagrant-openstack-plugin/action/wait_for_state.rb +39 -0
- data/lib/vagrant-openstack-plugin/action/wait_for_task.rb +44 -0
- data/lib/vagrant-openstack-plugin/action/warn_networks.rb +19 -0
- data/lib/vagrant-openstack-plugin/command.rb +70 -0
- data/lib/vagrant-openstack-plugin/command/command_snapshot.rb +43 -0
- data/lib/vagrant-openstack-plugin/config.rb +246 -0
- data/lib/vagrant-openstack-plugin/errors.rb +71 -0
- data/lib/vagrant-openstack-plugin/plugin.rb +45 -0
- data/lib/vagrant-openstack-plugin/provider.rb +50 -0
- data/lib/vagrant-openstack-plugin/version.rb +5 -0
- data/locales/en.yml +154 -0
- data/spec/vagrant-openstack-plugin/config_spec.rb +152 -0
- data/vagrant-openstack-plugin.gemspec +24 -0
- metadata +142 -0
@@ -0,0 +1,39 @@
|
|
1
|
+
require "log4r"
|
2
|
+
|
3
|
+
module VagrantPlugins
|
4
|
+
module OpenStack
|
5
|
+
module Action
|
6
|
+
# This action reads the state of the machine and puts it in the
|
7
|
+
# `:machine_state_id` key in the environment.
|
8
|
+
class ReadState
|
9
|
+
NOT_CREATED_STATES = [:deleted, :soft_deleted, :building, :error].freeze
|
10
|
+
|
11
|
+
def initialize(app, env)
|
12
|
+
@app = app
|
13
|
+
@logger = Log4r::Logger.new("vagrant_openstack::action::read_state")
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(env)
|
17
|
+
env[:machine_state_id] = read_state(env[:openstack_compute], env[:machine])
|
18
|
+
@app.call(env)
|
19
|
+
end
|
20
|
+
|
21
|
+
def read_state(openstack, machine)
|
22
|
+
id = machine.id || openstack.servers.all( :name => machine.name ).first.id rescue nil
|
23
|
+
return :not_created if id.nil?
|
24
|
+
|
25
|
+
# Find the machine using the OpenStack API.
|
26
|
+
server = openstack.servers.get(machine.id)
|
27
|
+
if server.nil? || NOT_CREATED_STATES.include?(server.state.downcase.to_sym)
|
28
|
+
@logger.info(I18n.t("vagrant_openstack.not_created"))
|
29
|
+
machine.id = nil
|
30
|
+
return :not_created
|
31
|
+
end
|
32
|
+
|
33
|
+
server.state.downcase.to_sym
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "log4r"
|
2
|
+
|
3
|
+
module VagrantPlugins
|
4
|
+
module OpenStack
|
5
|
+
module Action
|
6
|
+
# This reboots a running server, if there is one.
|
7
|
+
class RebootServer
|
8
|
+
def initialize(app, env)
|
9
|
+
@app = app
|
10
|
+
@logger = Log4r::Logger.new("vagrant_openstack::action::reboot_server")
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(env)
|
14
|
+
if env[:machine].id
|
15
|
+
env[:ui].info(I18n.t("vagrant_openstack.rebooting_server"))
|
16
|
+
|
17
|
+
# TODO: Validate the fact that we get a server back from the API.
|
18
|
+
server = env[:openstack_compute].servers.get(env[:machine].id)
|
19
|
+
server.reboot('SOFT')
|
20
|
+
end
|
21
|
+
|
22
|
+
@app.call(env)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "log4r"
|
2
|
+
|
3
|
+
module VagrantPlugins
|
4
|
+
module OpenStack
|
5
|
+
module Action
|
6
|
+
# This starts a suspended server, if there is one.
|
7
|
+
class ResumeServer
|
8
|
+
def initialize(app, env)
|
9
|
+
@app = app
|
10
|
+
@logger = Log4r::Logger.new("vagrant_openstack::action::resume_server")
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(env)
|
14
|
+
if env[:machine].id
|
15
|
+
env[:ui].info(I18n.t("vagrant_openstack.resuming_server"))
|
16
|
+
|
17
|
+
# TODO: Validate the fact that we get a server back from the API.
|
18
|
+
server = env[:openstack_compute].servers.get(env[:machine].id)
|
19
|
+
if server.state == 'PAUSED'
|
20
|
+
env[:openstack_compute].unpause_server(server.id)
|
21
|
+
elsif server.state == 'SUSPENDED'
|
22
|
+
env[:openstack_compute].resume_server(server.id)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
@app.call(env)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "log4r"
|
2
|
+
|
3
|
+
module VagrantPlugins
|
4
|
+
module OpenStack
|
5
|
+
module Action
|
6
|
+
# This deletes the running server, if there is one.
|
7
|
+
class SuspendServer
|
8
|
+
def initialize(app, env)
|
9
|
+
@app = app
|
10
|
+
@logger = Log4r::Logger.new("vagrant_openstack::action::suspend_server")
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(env)
|
14
|
+
if env[:machine].id
|
15
|
+
env[:ui].info(I18n.t("vagrant_openstack.suspending_server"))
|
16
|
+
|
17
|
+
# TODO: Validate the fact that we get a server back from the API.
|
18
|
+
server = env[:openstack_compute].servers.get(env[:machine].id)
|
19
|
+
env[:openstack_compute].suspend_server(server.id)
|
20
|
+
end
|
21
|
+
|
22
|
+
@app.call(env)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require "log4r"
|
2
|
+
|
3
|
+
require "vagrant/util/subprocess"
|
4
|
+
require "vagrant/util/which"
|
5
|
+
|
6
|
+
module VagrantPlugins
|
7
|
+
module OpenStack
|
8
|
+
module Action
|
9
|
+
# This middleware uses `rsync` to sync the folders over to the
|
10
|
+
# remote instance.
|
11
|
+
class SyncFolders
|
12
|
+
def initialize(app, env)
|
13
|
+
@app = app
|
14
|
+
@logger = Log4r::Logger.new("vagrant_openstack::action::sync_folders")
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(env)
|
18
|
+
@app.call(env)
|
19
|
+
|
20
|
+
ssh_info = env[:machine].ssh_info
|
21
|
+
|
22
|
+
env[:machine].config.vm.synced_folders.each do |id, data|
|
23
|
+
# ignore disabled shared folders
|
24
|
+
if data[:disabled]
|
25
|
+
@logger.info "Not syncing disabled folder: #{data[:hostpath]} => #{data[:guestpath]}"
|
26
|
+
next
|
27
|
+
end
|
28
|
+
|
29
|
+
unless Vagrant::Util::Which.which('rsync')
|
30
|
+
@logger.info "please install rsync first"
|
31
|
+
break
|
32
|
+
end
|
33
|
+
|
34
|
+
hostpath = File.expand_path(data[:hostpath], env[:root_path])
|
35
|
+
#rsync interprets paths with colons as remote locations.
|
36
|
+
# "cygdrive" path for cygwin on windows.
|
37
|
+
if Vagrant::Util::Platform.windows?
|
38
|
+
hostpath = Vagrant::Util::Subprocess.execute("cygpath", "-u", "-a", hostpath).stdout.chomp
|
39
|
+
end
|
40
|
+
guestpath = data[:guestpath]
|
41
|
+
|
42
|
+
# Make sure there is a trailing slash on the host path to
|
43
|
+
# avoid creating an additional directory with rsync
|
44
|
+
hostpath = "#{hostpath}/" if hostpath !~ /\/$/
|
45
|
+
|
46
|
+
env[:ui].info(I18n.t("vagrant_openstack.rsync_folder",
|
47
|
+
:hostpath => hostpath,
|
48
|
+
:guestpath => guestpath))
|
49
|
+
|
50
|
+
# Create the guest path
|
51
|
+
# Use sudo only when it is necessary
|
52
|
+
cmd_mkdir = "mkdir -p '#{guestpath}'"
|
53
|
+
cmd_chown = "chown #{ssh_info[:username]} '#{guestpath}'"
|
54
|
+
if env[:machine].communicate.execute(cmd_mkdir, :error_check => false) != 0 then
|
55
|
+
env[:machine].communicate.sudo(cmd_mkdir)
|
56
|
+
end
|
57
|
+
if env[:machine].communicate.execute(cmd_chown, :error_check => false) != 0 then
|
58
|
+
env[:machine].communicate.sudo(cmd_chown)
|
59
|
+
end
|
60
|
+
|
61
|
+
#collect rsync excludes specified :rsync_excludes=>['path1',...] in synced_folder options
|
62
|
+
excludes = ['.vagrant/', 'Vagrantfile', *Array(data[:rsync_excludes])].uniq
|
63
|
+
|
64
|
+
# Rsync over to the guest path using the SSH info
|
65
|
+
if env[:machine].config.ssh.proxy_command
|
66
|
+
proxy_cmd = "-o ProxyCommand='#{env[:machine].config.ssh.proxy_command}'"
|
67
|
+
else
|
68
|
+
proxy_cmd = ''
|
69
|
+
end
|
70
|
+
|
71
|
+
# poor workaround for poor ipv6 handling of rsync
|
72
|
+
if ssh_info[:host].include? ':'
|
73
|
+
user_at_host = "[#{ssh_info[:username]}@#{ssh_info[:host]}]"
|
74
|
+
else
|
75
|
+
user_at_host = ssh_info[:username] + "@" + ssh_info[:host]
|
76
|
+
end
|
77
|
+
|
78
|
+
command = [
|
79
|
+
'rsync', '--verbose', '--archive', '-z', '--delete',
|
80
|
+
*excludes.map{|e|['--exclude', e]}.flatten,
|
81
|
+
'-e', "ssh -p #{ssh_info[:port]} -o StrictHostKeyChecking=no #{proxy_cmd} #{ssh_key_options(ssh_info)}",
|
82
|
+
hostpath,
|
83
|
+
user_at_host + ":" + guestpath]
|
84
|
+
|
85
|
+
r = Vagrant::Util::Subprocess.execute(*command)
|
86
|
+
if r.exit_code != 0
|
87
|
+
raise Errors::RsyncError,
|
88
|
+
:guestpath => guestpath,
|
89
|
+
:hostpath => hostpath,
|
90
|
+
:stderr => r.stderr
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def ssh_key_options(ssh_info)
|
98
|
+
# Ensure that `private_key_path` is an Array (for Vagrant < 1.4)
|
99
|
+
Array(ssh_info[:private_key_path]).map { |path| "-i '#{path}' " }.join
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "log4r"
|
2
|
+
|
3
|
+
module VagrantPlugins
|
4
|
+
module OpenStack
|
5
|
+
module Action
|
6
|
+
# This reboots a running server, if there is one.
|
7
|
+
class TakeSnapshot
|
8
|
+
def initialize(app, env)
|
9
|
+
@app = app
|
10
|
+
@logger = Log4r::Logger.new("vagrant_openstack::action::take_snapshot")
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(env)
|
14
|
+
if env[:machine].id
|
15
|
+
env[:ui].info(I18n.t("vagrant_openstack.snapshoting_server"))
|
16
|
+
infos = env[:openstack_compute].get_server_details(env[:machine].id)
|
17
|
+
env[:openstack_compute].create_image(env[:machine].id,env[:openstack_snapshot_name] || 'snapshot')
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
@app.call(env)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'log4r'
|
3
|
+
require 'timeout'
|
4
|
+
|
5
|
+
module VagrantPlugins
|
6
|
+
module OpenStack
|
7
|
+
module Action
|
8
|
+
# This action will wait for a machine to reach a specific state or quit by timeout.
|
9
|
+
class WaitForState
|
10
|
+
def initialize(app, env, state, timeout)
|
11
|
+
@app = app
|
12
|
+
@logger = Log4r::Logger.new('vagrant_openstack::action::wait_for_state')
|
13
|
+
@state = Array.new(state).flatten
|
14
|
+
@timeout = timeout
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(env)
|
18
|
+
env[:result] = true
|
19
|
+
state = env[:machine].state.id.to_sym
|
20
|
+
|
21
|
+
if @state.include?(state)
|
22
|
+
@logger.info("Machine already at status #{ state.to_s }")
|
23
|
+
else
|
24
|
+
@logger.info("Waiting for machine to reach state...")
|
25
|
+
begin
|
26
|
+
Timeout.timeout(@timeout) do
|
27
|
+
sleep 2 until @state.include?(env[:machine].state.id)
|
28
|
+
end
|
29
|
+
rescue Timeout::Error
|
30
|
+
env[:result] = false
|
31
|
+
end
|
32
|
+
|
33
|
+
@app.call(env)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'log4r'
|
3
|
+
require 'timeout'
|
4
|
+
|
5
|
+
module VagrantPlugins
|
6
|
+
module OpenStack
|
7
|
+
module Action
|
8
|
+
# This action will wait for a machine to reach a specific state or quit by timeout.
|
9
|
+
class WaitForTask
|
10
|
+
def initialize(app, env, task, timeout)
|
11
|
+
@app = app
|
12
|
+
@logger = Log4r::Logger.new('vagrant_openstack::action::wait_for_task')
|
13
|
+
@task = Array.new(task).flatten
|
14
|
+
@timeout = timeout
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(env)
|
18
|
+
env[:result] = true
|
19
|
+
task = get_task(env)
|
20
|
+
|
21
|
+
if @task.include?(task)
|
22
|
+
@logger.info("Machine already at task #{ task.to_s }")
|
23
|
+
else
|
24
|
+
@logger.info("Waiting for machine to reach task...")
|
25
|
+
begin
|
26
|
+
Timeout.timeout(@timeout) do
|
27
|
+
sleep 5 until @task.include?(get_task(env))
|
28
|
+
end
|
29
|
+
rescue Timeout::Error
|
30
|
+
env[:result] = false
|
31
|
+
end
|
32
|
+
|
33
|
+
@app.call(env)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def get_task(env)
|
38
|
+
infos = env[:openstack_compute].get_server_details(env[:machine].id)
|
39
|
+
infos.body['server']['OS-EXT-STS:task_state']
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module VagrantPlugins
|
2
|
+
module OpenStack
|
3
|
+
module Action
|
4
|
+
class WarnNetworks
|
5
|
+
def initialize(app, env)
|
6
|
+
@app = app
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(env)
|
10
|
+
if env[:machine].config.vm.networks.length > 0
|
11
|
+
env[:ui].warn(I18n.t("vagrant_openstack.warn_networks"))
|
12
|
+
end
|
13
|
+
|
14
|
+
@app.call(env)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require "vagrant"
|
2
|
+
require "vagrant/action/builder"
|
3
|
+
require "pathname"
|
4
|
+
|
5
|
+
module VagrantPlugins
|
6
|
+
module OpenStack
|
7
|
+
module Action
|
8
|
+
|
9
|
+
class Command < Vagrant.plugin(2, :command)
|
10
|
+
|
11
|
+
include Vagrant::Action::Builtin
|
12
|
+
# include VagrantPlugins::OpenStack::Action
|
13
|
+
|
14
|
+
def initialize(argv, env)
|
15
|
+
super
|
16
|
+
|
17
|
+
@main_args, @sub_command, @sub_args = split_main_and_subcommand(argv)
|
18
|
+
|
19
|
+
@subcommands = Vagrant::Registry.new
|
20
|
+
@subcommands.register(:snapshot) do
|
21
|
+
require_relative 'command/command_snapshot'
|
22
|
+
CommandTakeSnapshot
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
|
28
|
+
def execute
|
29
|
+
if @main_args.include?("-h") || @main_args.include?("--help")
|
30
|
+
# Print the help for all the sub-commands.
|
31
|
+
return help
|
32
|
+
end
|
33
|
+
|
34
|
+
# If we reached this far then we must have a subcommand. If not,
|
35
|
+
# then we also just print the help and exit.
|
36
|
+
command_class = @subcommands.get(@sub_command.to_sym) if @sub_command
|
37
|
+
return help if !command_class || !@sub_command
|
38
|
+
@logger.debug("Invoking command class: #{command_class} #{@sub_args.inspect}")
|
39
|
+
|
40
|
+
# Initialize and execute the command class
|
41
|
+
command_class.new(@sub_args, @env).execute
|
42
|
+
end
|
43
|
+
|
44
|
+
# Prints the help out for this command
|
45
|
+
def help
|
46
|
+
opts = OptionParser.new do |o|
|
47
|
+
o.banner = "Usage: vagrant openstack <command> [<args>]"
|
48
|
+
o.separator ""
|
49
|
+
o.separator "Available subcommands:"
|
50
|
+
|
51
|
+
# Add the available subcommands as separators in order to print them
|
52
|
+
# out as well.
|
53
|
+
keys = []
|
54
|
+
@subcommands.each { |key, value| keys << key.to_s }
|
55
|
+
|
56
|
+
keys.sort.each do |key|
|
57
|
+
o.separator " #{key}"
|
58
|
+
end
|
59
|
+
|
60
|
+
o.separator ""
|
61
|
+
o.separator "For help on any individual command run `vagrant openstack <command> -h`"
|
62
|
+
end
|
63
|
+
|
64
|
+
@env.ui.info(opts.help, :prefix => false)
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require "vagrant"
|
2
|
+
require "vagrant/action/builder"
|
3
|
+
require "pathname"
|
4
|
+
require "vagrant-openstack-plugin/action"
|
5
|
+
|
6
|
+
|
7
|
+
module VagrantPlugins
|
8
|
+
module OpenStack
|
9
|
+
module Action
|
10
|
+
class CommandTakeSnapshot < Vagrant.plugin("2", :command)
|
11
|
+
include Vagrant::Action::Builtin
|
12
|
+
|
13
|
+
def execute
|
14
|
+
options = {:openstack_snapshot_name => 'snapshot'}
|
15
|
+
opts = OptionParser.new do |opts|
|
16
|
+
opts.banner = "Enters openstack"
|
17
|
+
opts.separator ""
|
18
|
+
opts.separator "Usage: vagrant openstack snapshot <vmname> -n <snapshotname>"
|
19
|
+
|
20
|
+
|
21
|
+
opts.on( '-n', '--name NAME', 'snapshotname' ) do |name|
|
22
|
+
options[:openstack_snapshot_name] = name
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
# Parse the options
|
28
|
+
argv = parse_options(opts)
|
29
|
+
|
30
|
+
return if !argv
|
31
|
+
|
32
|
+
|
33
|
+
with_target_vms(argv, :reverse => true) do |vm|
|
34
|
+
if vm.provider.to_s == VagrantPlugins::OpenStack::Provider.new(nil).to_s
|
35
|
+
vm.action(:take_snapshot,options)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|