mybot 0.0.4 → 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/Botfile +7 -61
- data/CHANGELOG.md +2 -25
- data/README.md +72 -189
- data/lib/mybot.rb +20 -21
- data/lib/mybot/base.rb +6 -2
- data/lib/mybot/cli.rb +45 -5
- data/lib/mybot/command.rb +37 -28
- data/lib/mybot/fmt.rb +143 -52
- data/lib/mybot/helpers.rb +24 -0
- data/lib/mybot/installer.rb +31 -0
- data/lib/mybot/lib.rb +20 -59
- data/lib/mybot/libs.rb +31 -28
- data/lib/mybot/loader.rb +25 -0
- data/lib/mybot/node.rb +100 -47
- data/lib/mybot/nodes.rb +16 -0
- data/lib/mybot/tasks.rb +47 -11
- data/lib/mybot/template.rb +8 -8
- data/lib/mybot/templates.rb +15 -0
- data/lib/mybot/version.rb +1 -1
- data/vendor/lib/{core/.gitkeep → .gitkeep} +0 -0
- data/vendor/{recipes → plugins}/.gitkeep +0 -0
- data/vendor/{tpl/core → tasks}/.gitkeep +0 -0
- data/vendor/tpl/.gitkeep +0 -0
- metadata +11 -34
- data/lib/mybot/aws.rb +0 -54
- data/lib/mybot/commands.rb +0 -108
- data/lib/mybot/core-ext/array.rb +0 -9
- data/lib/mybot/core-ext/class.rb +0 -53
- data/lib/mybot/core-ext/kernel.rb +0 -21
- data/lib/mybot/messages.rb +0 -30
- data/lib/mybot/recipes.rb +0 -25
- data/lib/mybot/result.rb +0 -26
- data/lib/mybot/utils.rb +0 -7
- data/vendor/lib/core/fs.rb +0 -191
- data/vendor/lib/core/osx/git.rb +0 -31
- data/vendor/lib/core/osx/rails.rb +0 -31
- data/vendor/lib/core/osx/static.rb +0 -31
- data/vendor/lib/core/ubuntu/apache.rb +0 -42
- data/vendor/lib/core/ubuntu/apt.rb +0 -57
- data/vendor/lib/core/ubuntu/git.rb +0 -48
- data/vendor/lib/core/ubuntu/mysql.rb +0 -214
- data/vendor/lib/core/ubuntu/nginx.rb +0 -43
- data/vendor/lib/core/ubuntu/passenger.rb +0 -55
- data/vendor/lib/core/ubuntu/php.rb +0 -76
- data/vendor/lib/core/ubuntu/rails.rb +0 -105
- data/vendor/lib/core/ubuntu/ruby.rb +0 -166
- data/vendor/lib/core/ubuntu/service.rb +0 -30
- data/vendor/lib/core/ubuntu/static.rb +0 -50
- data/vendor/lib/core/ubuntu/users.rb +0 -76
- data/vendor/lib/core/ubuntu/vim.rb +0 -31
- data/vendor/tpl/core/ubuntu/apache/apache2.conf.erb +0 -80
- data/vendor/tpl/core/ubuntu/nginx/nginx.conf.erb +0 -72
- data/vendor/tpl/core/ubuntu/php/php.ini.erb +0 -1818
@@ -1,214 +0,0 @@
|
|
1
|
-
module Core
|
2
|
-
module Ubuntu
|
3
|
-
class Mysql < Mybot::Lib
|
4
|
-
PROMPT = "mysql -u %s --password=%s -e \"%s\""
|
5
|
-
|
6
|
-
def initialize(node)
|
7
|
-
@node = node
|
8
|
-
@node.use [
|
9
|
-
"core/fs",
|
10
|
-
"core/ubuntu/apt",
|
11
|
-
"core/ubuntu/service"
|
12
|
-
]
|
13
|
-
end
|
14
|
-
|
15
|
-
def installed?(options = {})
|
16
|
-
options[:file] = "/etc/init.d/mysql"
|
17
|
-
@node.fs.exists?(options)
|
18
|
-
end
|
19
|
-
|
20
|
-
def install(options = {})
|
21
|
-
error "root_password is required" unless options[:root_password]
|
22
|
-
|
23
|
-
options[:pkg] = "mysql-server"
|
24
|
-
@node.apt.install(options)
|
25
|
-
|
26
|
-
options[:service] = "mysql"
|
27
|
-
@node.service.restart(options)
|
28
|
-
|
29
|
-
options[:cmd] = "mysql_secure_installation"
|
30
|
-
@node.run(options) do |cmd|
|
31
|
-
cmd.on "Enter current password for root (enter for none):" do
|
32
|
-
cmd.write ""
|
33
|
-
end
|
34
|
-
|
35
|
-
cmd.on "Set root password? [Y/n]" do
|
36
|
-
cmd.write "Y"
|
37
|
-
end
|
38
|
-
|
39
|
-
cmd.on "Change the root password? [Y/n]" do
|
40
|
-
cmd.write "Y"
|
41
|
-
end
|
42
|
-
|
43
|
-
cmd.on "New password:" do
|
44
|
-
cmd.write options[:root_password]
|
45
|
-
end
|
46
|
-
|
47
|
-
cmd.on "Re-enter new password:" do
|
48
|
-
cmd.write options[:root_password]
|
49
|
-
end
|
50
|
-
|
51
|
-
cmd.on "Remove anonymous users? [Y/n]" do
|
52
|
-
cmd.write "Y"
|
53
|
-
end
|
54
|
-
|
55
|
-
cmd.on "Disallow root login remotely? [Y/n]" do
|
56
|
-
cmd.write "Y"
|
57
|
-
end
|
58
|
-
|
59
|
-
cmd.on "Remove test database and access to it? [Y/n]" do
|
60
|
-
cmd.write "Y"
|
61
|
-
end
|
62
|
-
|
63
|
-
cmd.on "Reload privilege tables now? [Y/n]" do
|
64
|
-
cmd.write "Y"
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
def remove(options = {})
|
70
|
-
options[:service] = "mysql"
|
71
|
-
@node.service.stop(options)
|
72
|
-
|
73
|
-
options[:pkg] = "mysql-server"
|
74
|
-
@node.apt.remove(options)
|
75
|
-
|
76
|
-
options[:dir] = "/etc/mysql"
|
77
|
-
@node.fs.remove(options)
|
78
|
-
|
79
|
-
options[:dir] = "/var/lib/mysql"
|
80
|
-
@node.fs.remove(options)
|
81
|
-
end
|
82
|
-
|
83
|
-
def database_list(options = {})
|
84
|
-
error "root_password is required" unless options[:root_password]
|
85
|
-
|
86
|
-
query = "SHOW DATABASES;"
|
87
|
-
options[:cmd] = sprintf(PROMPT, "root", options[:root_password], query)
|
88
|
-
|
89
|
-
databases = @node.run(options).stdout.split("\r\n")
|
90
|
-
databases[3..-2].map do |db|
|
91
|
-
db.gsub("|", "").strip
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
def database_exists?(options = {})
|
96
|
-
error "database is required" unless options[:database]
|
97
|
-
|
98
|
-
database_list(options).include?(options[:database])
|
99
|
-
end
|
100
|
-
|
101
|
-
def database_create(options = {})
|
102
|
-
error "root_password is required" unless options[:root_password]
|
103
|
-
error "database is required" unless options[:database]
|
104
|
-
|
105
|
-
options[:character_set] ||= "utf8"
|
106
|
-
options[:collate] ||= "utf8_general_ci"
|
107
|
-
|
108
|
-
query = sprintf("CREATE DATABASE %s CHARACTER SET %s COLLATE %s;",
|
109
|
-
options[:database], options[:character_set], options[:collate])
|
110
|
-
|
111
|
-
options[:cmd] = sprintf(PROMPT, "root", options[:root_password], query)
|
112
|
-
@node.run(options)
|
113
|
-
end
|
114
|
-
|
115
|
-
def database_remove(options = {})
|
116
|
-
error "root_password is required" unless options[:root_password]
|
117
|
-
error "database is required" unless options[:database]
|
118
|
-
|
119
|
-
query = sprintf("DROP DATABASE %s;", options[:database])
|
120
|
-
|
121
|
-
options[:cmd] = sprintf(PROMPT, "root", options[:root_password], query)
|
122
|
-
@node.run(options)
|
123
|
-
end
|
124
|
-
|
125
|
-
def user_list(options = {})
|
126
|
-
error "root_password is required" unless options[:root_password]
|
127
|
-
|
128
|
-
query = "USE mysql; SELECT User from user;"
|
129
|
-
options[:cmd] = sprintf(PROMPT, "root", options[:root_password], query)
|
130
|
-
users = @node.run(options).stdout.split("\r\n")
|
131
|
-
users[3..-2].map do |u|
|
132
|
-
u.gsub("|", "").strip
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
def user_exists?(options = {})
|
137
|
-
user_list(options).include?(options[:username])
|
138
|
-
end
|
139
|
-
|
140
|
-
def user_create(options = {})
|
141
|
-
error "root_password is required" unless options[:root_password]
|
142
|
-
error "username is required" unless options[:username]
|
143
|
-
error "password is required" unless options[:password]
|
144
|
-
|
145
|
-
options[:host] ||= "localhost"
|
146
|
-
|
147
|
-
query = "CREATE USER '#{options[:username]}'@"
|
148
|
-
query += "'#{options[:host]}' IDENTIFIED BY '#{options[:password]}';"
|
149
|
-
|
150
|
-
options[:cmd] = sprintf(PROMPT, "root", options[:root_password], query)
|
151
|
-
@node.run(options)
|
152
|
-
end
|
153
|
-
|
154
|
-
def user_remove(options = {})
|
155
|
-
error "root_password is required" unless options[:root_password]
|
156
|
-
error "username is required" unless options[:username]
|
157
|
-
|
158
|
-
options[:host] ||= "localhost"
|
159
|
-
|
160
|
-
query = "DROP USER '#{options[:username]}'@'#{options[:host]}';"
|
161
|
-
|
162
|
-
options[:cmd] = sprintf(PROMPT, "root", options[:root_password], query)
|
163
|
-
@node.run(options)
|
164
|
-
end
|
165
|
-
|
166
|
-
def grant_privileges(options = {})
|
167
|
-
error "root_password is required" unless options[:root_password]
|
168
|
-
error "username is required" unless options[:username]
|
169
|
-
|
170
|
-
options[:host] ||= "localhost"
|
171
|
-
options[:database] ||= "*"
|
172
|
-
options[:table] ||= "*"
|
173
|
-
|
174
|
-
query = sprintf("GRANT ALL PRIVILEGES ON %s.%s TO '%s'@'%s';",
|
175
|
-
options[:database], options[:table], options[:username],
|
176
|
-
options[:host])
|
177
|
-
|
178
|
-
options[:cmd] = sprintf(PROMPT, "root", options[:root_password], query)
|
179
|
-
@node.run(options)
|
180
|
-
end
|
181
|
-
|
182
|
-
def flush_privileges(options = {})
|
183
|
-
error "root_password is required" unless options[:root_password]
|
184
|
-
|
185
|
-
query = "FLUSH PRIVILEGES;"
|
186
|
-
|
187
|
-
options[:cmd] = sprintf(PROMPT, "root", options[:root_password], query)
|
188
|
-
@node.run(options)
|
189
|
-
end
|
190
|
-
|
191
|
-
def dump(options = {})
|
192
|
-
error "root_password is required" unless options[:root_password]
|
193
|
-
error "database is required" unless options[:database]
|
194
|
-
|
195
|
-
tmp = @node.fs.mktemp({ :pattern => "/tmp/dump.XXXX" })
|
196
|
-
|
197
|
-
options[:cmd] = sprintf("mysqldump -u %s --password=%s %s > %s",
|
198
|
-
"root", options[:root_password], options[:database], tmp)
|
199
|
-
@node.run(options)
|
200
|
-
tmp
|
201
|
-
end
|
202
|
-
|
203
|
-
def load(options = {})
|
204
|
-
error "root_password is required" unless options[:root_password]
|
205
|
-
error "database is required" unless options[:database]
|
206
|
-
error "file is required" unless options[:file]
|
207
|
-
|
208
|
-
options[:cmd] = sprintf("mysql -u %s --password=%s %s < %s",
|
209
|
-
"root", options[:root_password], options[:database], options[:file])
|
210
|
-
@node.run(options)
|
211
|
-
end
|
212
|
-
end
|
213
|
-
end
|
214
|
-
end
|
@@ -1,43 +0,0 @@
|
|
1
|
-
module Core
|
2
|
-
module Ubuntu
|
3
|
-
class Nginx < Mybot::Lib
|
4
|
-
def initialize(node)
|
5
|
-
@node = node
|
6
|
-
@node.use [
|
7
|
-
"core/fs",
|
8
|
-
"core/ubuntu/apt",
|
9
|
-
"core/ubuntu/service"
|
10
|
-
]
|
11
|
-
|
12
|
-
@nginxconf_tpl = tpl_file "core/ubuntu/nginx/nginx.conf.erb"
|
13
|
-
end
|
14
|
-
|
15
|
-
def installed?(options = {})
|
16
|
-
@node.fs.exists?({ :file => "/usr/sbin/nginx"} )
|
17
|
-
end
|
18
|
-
|
19
|
-
def install(options = {})
|
20
|
-
options[:pkg] = "nginx"
|
21
|
-
@node.apt.install(options)
|
22
|
-
end
|
23
|
-
|
24
|
-
def remove(options = {})
|
25
|
-
options[:pkg] = "nginx"
|
26
|
-
@node.apt.remove(options)
|
27
|
-
end
|
28
|
-
|
29
|
-
def reconfigure(options = {})
|
30
|
-
error "config is required" unless options[:config]
|
31
|
-
|
32
|
-
options[:file] = "/etc/nginx/nginx.conf"
|
33
|
-
@node.fs.remove(options)
|
34
|
-
|
35
|
-
options[:content] = @nginxconf_tpl.render(options[:config])
|
36
|
-
@node.fs.create(options)
|
37
|
-
|
38
|
-
options[:service] = "nginx"
|
39
|
-
@node.service.restart(options)
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
@@ -1,55 +0,0 @@
|
|
1
|
-
module Core
|
2
|
-
module Ubuntu
|
3
|
-
class Passenger < Mybot::Lib
|
4
|
-
def initialize(node)
|
5
|
-
@node = node
|
6
|
-
@node.use [
|
7
|
-
"core/ubuntu/ruby",
|
8
|
-
"core/ubuntu/apache"
|
9
|
-
]
|
10
|
-
end
|
11
|
-
|
12
|
-
def installed?(options = {})
|
13
|
-
error "ruby_version is required" unless options[:ruby_version]
|
14
|
-
|
15
|
-
options[:gem_name] = "passenger"
|
16
|
-
@node.ruby.rbenv_gem_installed?(options)
|
17
|
-
end
|
18
|
-
|
19
|
-
def install(options = {})
|
20
|
-
error "server is required" unless options[:server]
|
21
|
-
unless options[:server] == "apache" || options[:server] == "nginx"
|
22
|
-
error "invalid server, allowed are: 'apache' and 'nginx'"
|
23
|
-
end
|
24
|
-
|
25
|
-
options[:pkg] = "libcurl4-openssl-dev"
|
26
|
-
@node.apt.install(options)
|
27
|
-
|
28
|
-
options[:pkg] = "apache2-prefork-dev"
|
29
|
-
@node.apt.install(options)
|
30
|
-
|
31
|
-
options[:pkg] = "libapr1-dev"
|
32
|
-
@node.apt.install(options)
|
33
|
-
|
34
|
-
options[:pkg] = "libaprutil1-dev"
|
35
|
-
@node.apt.install(options)
|
36
|
-
|
37
|
-
options[:gem_name] = "passenger"
|
38
|
-
@node.ruby.rbenv_gem_install(options)
|
39
|
-
|
40
|
-
if options[:server] == "apache"
|
41
|
-
options[:cmd] = "passenger-install-apache2-module -a"
|
42
|
-
else
|
43
|
-
options[:cmd] = "passenger-install-nginx-module -a"
|
44
|
-
end
|
45
|
-
|
46
|
-
@node.ruby.rbenv_exec(options)
|
47
|
-
end
|
48
|
-
|
49
|
-
def remove(options = {})
|
50
|
-
options[:gem_name] = "passenger"
|
51
|
-
@node.ruby.rbenv_gem_remove(options)
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
@@ -1,76 +0,0 @@
|
|
1
|
-
module Core
|
2
|
-
module Ubuntu
|
3
|
-
class Php < Mybot::Lib
|
4
|
-
def initialize(node)
|
5
|
-
@node = node
|
6
|
-
@node.use [
|
7
|
-
"core/fs",
|
8
|
-
"core/ubuntu/apt",
|
9
|
-
"core/ubuntu/service"
|
10
|
-
]
|
11
|
-
@phpini_tpl = tpl_file "core/ubuntu/php/php.ini.erb"
|
12
|
-
end
|
13
|
-
|
14
|
-
def installed?(options = {})
|
15
|
-
options[:file] = "/usr/bin/php"
|
16
|
-
@node.fs.exists?(options)
|
17
|
-
end
|
18
|
-
|
19
|
-
def install(options = {})
|
20
|
-
options[:pkg] = "php5-common php5-cli php5-fpm"
|
21
|
-
@node.apt.install(options)
|
22
|
-
|
23
|
-
options[:service] = "php5-fpm"
|
24
|
-
@node.service.restart(options)
|
25
|
-
end
|
26
|
-
|
27
|
-
def remove(options = {})
|
28
|
-
options[:service] = "php5-fpm"
|
29
|
-
@node.service.stop(options)
|
30
|
-
|
31
|
-
options[:pkg] = "php5-common php5-cli php5-fpm"
|
32
|
-
@node.apt.remove(options)
|
33
|
-
end
|
34
|
-
|
35
|
-
def ext_installed?(options = {})
|
36
|
-
error "ext is required" unless options[:ext]
|
37
|
-
|
38
|
-
options[:file] = "/usr/lib/php5/20100525/#{options[:ext]}.so"
|
39
|
-
@node.fs.exists?(options)
|
40
|
-
end
|
41
|
-
|
42
|
-
def ext_install(options = {})
|
43
|
-
error "ext is required" unless options[:ext]
|
44
|
-
|
45
|
-
options[:pkg] = "php5-#{options[:ext]}"
|
46
|
-
@node.apt.install(options)
|
47
|
-
|
48
|
-
options[:service] = "php5-fpm"
|
49
|
-
@node.service.restart(options)
|
50
|
-
end
|
51
|
-
|
52
|
-
def ext_remove(options = {})
|
53
|
-
error "ext is required" unless options[:ext]
|
54
|
-
|
55
|
-
options[:pkg] = "php5-#{options[:ext]}"
|
56
|
-
@node.apt.remove(options)
|
57
|
-
|
58
|
-
options[:service] = "php5-fpm"
|
59
|
-
@node.service.restart(options)
|
60
|
-
end
|
61
|
-
|
62
|
-
def reconfigure(options = {})
|
63
|
-
error "config is required" unless options[:config]
|
64
|
-
|
65
|
-
options[:file] = "/etc/php5/fpm/php.ini"
|
66
|
-
@node.fs.remove(options)
|
67
|
-
|
68
|
-
options[:content] = @phpini_tpl.render(options[:config])
|
69
|
-
@node.fs.create(options)
|
70
|
-
|
71
|
-
options[:service] = "php5-fpm"
|
72
|
-
@node.service.restart(options)
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
@@ -1,105 +0,0 @@
|
|
1
|
-
module Core
|
2
|
-
module Ubuntu
|
3
|
-
class Rails < Mybot::Lib
|
4
|
-
def initialize(node)
|
5
|
-
@node = node
|
6
|
-
@node.use [
|
7
|
-
"core/fs",
|
8
|
-
"core/ubuntu/ruby"
|
9
|
-
]
|
10
|
-
end
|
11
|
-
|
12
|
-
def setup_deployment(options = {})
|
13
|
-
error "app_name is required" unless options[:app_name]
|
14
|
-
|
15
|
-
dirs = [
|
16
|
-
"/home/#{options[:app_name]}/releases",
|
17
|
-
"/home/#{options[:app_name]}/shared",
|
18
|
-
"/home/#{options[:app_name]}/shared/bundle",
|
19
|
-
"/home/#{options[:app_name]}/shared/log",
|
20
|
-
"/home/#{options[:app_name]}/shared/pids",
|
21
|
-
"/home/#{options[:app_name]}/shared/system",
|
22
|
-
]
|
23
|
-
|
24
|
-
dirs.each do |d|
|
25
|
-
options[:dir] = d
|
26
|
-
@node.fs.require_created(options)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def deploy(options = {})
|
31
|
-
error "app_name is required" unless options[:app_name]
|
32
|
-
error "release is required" unless options[:release]
|
33
|
-
|
34
|
-
releases_path = "/home/#{options[:app_name]}/releases"
|
35
|
-
release_path = "#{releases_path}/#{options[:release]}"
|
36
|
-
shared_path = "/home/#{options[:app_name]}/shared"
|
37
|
-
current_path = "/home/#{options[:app_name]}/current"
|
38
|
-
|
39
|
-
# upload release archive
|
40
|
-
options[:from] = options[:to] = "/tmp/#{options[:release]}.tar.gz"
|
41
|
-
@node.fs.upload(options)
|
42
|
-
|
43
|
-
# extract archive
|
44
|
-
options[:to] = releases_path
|
45
|
-
@node.fs.extract(options)
|
46
|
-
|
47
|
-
# install bundle
|
48
|
-
options[:ruby_version] = "1.9.3-p194"
|
49
|
-
options[:cmd] = "bundle install " +
|
50
|
-
"--gemfile #{release_path}/Gemfile " +
|
51
|
-
"--path /home/#{options[:app_name]}/shared/bundle " +
|
52
|
-
"--deployment --without development test"
|
53
|
-
options[:cwd] = release_path
|
54
|
-
@node.ruby.rbenv_exec(options)
|
55
|
-
|
56
|
-
# link public/system
|
57
|
-
options[:dir] = "#{release_path}/public/system"
|
58
|
-
@node.fs.remove(options)
|
59
|
-
|
60
|
-
options[:dir] = "#{release_path}/public"
|
61
|
-
@node.fs.require_created(options)
|
62
|
-
|
63
|
-
options[:from] = "#{shared_path}/public/system"
|
64
|
-
options[:to] = "#{release_path}/public/system"
|
65
|
-
@node.fs.link(options)
|
66
|
-
|
67
|
-
# link log
|
68
|
-
options[:dir] = "#{release_path}/log"
|
69
|
-
@node.fs.remove(options)
|
70
|
-
|
71
|
-
options[:from] = "#{shared_path}/log"
|
72
|
-
options[:to] = "#{release_path}/log"
|
73
|
-
@node.fs.link(options)
|
74
|
-
|
75
|
-
# link tmp/pids
|
76
|
-
options[:dir] = "#{release_path}/tmp/pids"
|
77
|
-
@node.fs.remove(options)
|
78
|
-
|
79
|
-
options[:dir] = "#{release_path}/tmp"
|
80
|
-
@node.fs.require_created(options)
|
81
|
-
|
82
|
-
options[:from] = "#{shared_path}/pids"
|
83
|
-
options[:to] = "#{release_path}/tmp/pids"
|
84
|
-
@node.fs.link(options)
|
85
|
-
|
86
|
-
# link current
|
87
|
-
options[:dir] = current_path
|
88
|
-
@node.fs.remove(options)
|
89
|
-
|
90
|
-
options[:from] = release_path
|
91
|
-
options[:to] = current_path
|
92
|
-
@node.fs.link(options)
|
93
|
-
|
94
|
-
# restart passenger
|
95
|
-
options[:file] = "#{release_path}/tmp/restart.txt"
|
96
|
-
|
97
|
-
# repair permissions
|
98
|
-
options[:dir] = "/home/#{options[:app_name]}"
|
99
|
-
options[:user] = options[:app_name]
|
100
|
-
options[:group] = options[:app_name]
|
101
|
-
@node.fs.chown(options)
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|