openshift 0.60.3
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/Error codes.txt +15 -0
- data/bin/os +42 -0
- data/bin/os-add-cartridge +190 -0
- data/bin/os-clone-application +175 -0
- data/bin/os-create-application +306 -0
- data/bin/os-create-environment +306 -0
- data/bin/os-delete-application +141 -0
- data/bin/os-delete-environment +140 -0
- data/bin/os-deregister-cloud +124 -0
- data/bin/os-help +108 -0
- data/bin/os-inspect-application +222 -0
- data/bin/os-list-applications +139 -0
- data/bin/os-list-cartridges +182 -0
- data/bin/os-list-clouds +137 -0
- data/bin/os-list-environments +188 -0
- data/bin/os-list-servers +122 -0
- data/bin/os-open-console +162 -0
- data/bin/os-register-cloud +181 -0
- data/bin/os-remove-cartridge +170 -0
- data/bin/os-restart-application +147 -0
- data/bin/os-start-application +147 -0
- data/bin/os-start-environment +144 -0
- data/bin/os-stop-application +147 -0
- data/bin/os-stop-environment +144 -0
- data/bin/os-tail-logs +159 -0
- data/conf/openshift.conf +5 -0
- data/lib/openshift.rb +666 -0
- metadata +192 -0
@@ -0,0 +1,124 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Copyright 2010 Red Hat, Inc.
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person
|
5
|
+
# obtaining a copy of this software and associated documentation files
|
6
|
+
# (the "Software"), to deal in the Software without restriction,
|
7
|
+
# including without limitation the rights to use, copy, modify, merge,
|
8
|
+
# publish, distribute, sublicense, and/or sell copies of the Software,
|
9
|
+
# and to permit persons to whom the Software is furnished to do so,
|
10
|
+
# subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
19
|
+
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
20
|
+
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
21
|
+
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
22
|
+
# SOFTWARE.
|
23
|
+
|
24
|
+
require 'openshift'
|
25
|
+
|
26
|
+
def usage
|
27
|
+
puts <<USAGE
|
28
|
+
== Synopsis
|
29
|
+
|
30
|
+
os-deregister-cloud: Deregister a cloud account.
|
31
|
+
|
32
|
+
== Usage
|
33
|
+
|
34
|
+
os deregister-cloud [options] [NAME]
|
35
|
+
|
36
|
+
-u|--username
|
37
|
+
Redhat Login (RHN or OpenShift login with OpenShift Express access)
|
38
|
+
|
39
|
+
-p|--password
|
40
|
+
Redhat Password
|
41
|
+
|
42
|
+
-t|--target flex|express
|
43
|
+
The cloud platform for the cloud account.
|
44
|
+
|
45
|
+
-h|--help
|
46
|
+
Prints this message
|
47
|
+
|
48
|
+
NAME: The name or ID of the cloud to deregister.
|
49
|
+
USAGE
|
50
|
+
end
|
51
|
+
|
52
|
+
begin
|
53
|
+
opts = GetoptLong.new(
|
54
|
+
["--username", "-u", GetoptLong::REQUIRED_ARGUMENT],
|
55
|
+
["--password", "-p", GetoptLong::REQUIRED_ARGUMENT],
|
56
|
+
["--help", "-h", GetoptLong::NO_ARGUMENT],
|
57
|
+
["--debug", GetoptLong::NO_ARGUMENT],
|
58
|
+
["--sso", GetoptLong::REQUIRED_ARGUMENT],
|
59
|
+
["--porcelin", GetoptLong::NO_ARGUMENT]
|
60
|
+
)
|
61
|
+
rescue Exception => e
|
62
|
+
puts e.message
|
63
|
+
end
|
64
|
+
|
65
|
+
args = {}
|
66
|
+
begin
|
67
|
+
opts.each{ |k,v| args[k]=v }
|
68
|
+
rescue GetoptLong::Error => e
|
69
|
+
usage
|
70
|
+
exit -100
|
71
|
+
end
|
72
|
+
|
73
|
+
@debug = true if args['--debug']
|
74
|
+
args['--target'] = conf('default_target') || 'flex' if args['--target'].nil? or args['--target']==""
|
75
|
+
debug "Target platform #{args['--target']}"
|
76
|
+
|
77
|
+
if args['--help']
|
78
|
+
usage
|
79
|
+
exit
|
80
|
+
end
|
81
|
+
|
82
|
+
if args['--target'] == 'flex'
|
83
|
+
flex_server = conf('flex_server')
|
84
|
+
cloud_name = ARGV.shift
|
85
|
+
if not cloud_name
|
86
|
+
csay("No cloud name or id was provided.", :error)
|
87
|
+
exit
|
88
|
+
end
|
89
|
+
|
90
|
+
cookie = args['--sso']
|
91
|
+
if !cookie
|
92
|
+
username = args['--username'] || conf("username") || Openshift::IO.prompt("Redhat username",[],Openshift::Validation.method(:check_login))
|
93
|
+
password = args['--password'] || Openshift::IO.prompt("Redhat password",nil,nil,true,false)
|
94
|
+
csay("Logging into Openshift Flex as #{username}\n",:message)
|
95
|
+
cookie=Openshift.login(@http,username,password)
|
96
|
+
end
|
97
|
+
|
98
|
+
csay("Retrieving list of cloud accounts...")
|
99
|
+
clouds = JSON.parse(`os-list-clouds --sso "#{cookie}" --porcelin`)
|
100
|
+
|
101
|
+
clouds.each{ |cloud|
|
102
|
+
if cloud["name"].strip == cloud_name.strip
|
103
|
+
csay("Deregistering cloud ")
|
104
|
+
csay("#{cloud["name"]} ",:emphasis)
|
105
|
+
csay("... ")
|
106
|
+
delete_link = cloud["links"]["delete"]
|
107
|
+
uri = URI.parse("#{flex_server}/rest/#{delete_link["href"]}")
|
108
|
+
response = Openshift::Rest.doHttp(@http, delete_link["method"], uri, nil, cookie, nil)
|
109
|
+
case response
|
110
|
+
when Net::HTTPSuccess
|
111
|
+
csay("[OK]",:conf)
|
112
|
+
else
|
113
|
+
debug "HTTP code: #{response.code}"
|
114
|
+
debug response.body
|
115
|
+
csay("[ERROR]",:error)
|
116
|
+
csay("Unable to deregister cloud account.",:error)
|
117
|
+
exit -301
|
118
|
+
end
|
119
|
+
break
|
120
|
+
end
|
121
|
+
}
|
122
|
+
else
|
123
|
+
csay("This feature is currently not implemented for Openshift Express applications.\n",:red)
|
124
|
+
end
|
data/bin/os-help
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Copyright 2010 Red Hat, Inc.
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person
|
5
|
+
# obtaining a copy of this software and associated documentation files
|
6
|
+
# (the "Software"), to deal in the Software without restriction,
|
7
|
+
# including without limitation the rights to use, copy, modify, merge,
|
8
|
+
# publish, distribute, sublicense, and/or sell copies of the Software,
|
9
|
+
# and to permit persons to whom the Software is furnished to do so,
|
10
|
+
# subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
19
|
+
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
20
|
+
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
21
|
+
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
22
|
+
# SOFTWARE.
|
23
|
+
|
24
|
+
require 'openshift'
|
25
|
+
|
26
|
+
def usage
|
27
|
+
puts <<USAGE
|
28
|
+
== Usage
|
29
|
+
|
30
|
+
os COMMAND [ARGS]
|
31
|
+
|
32
|
+
COMMAND:
|
33
|
+
- register-cloud
|
34
|
+
Registers a new cloud account with Openshift Flex.
|
35
|
+
|
36
|
+
- list-clouds
|
37
|
+
Lists registered Openshift Flex clouds.
|
38
|
+
|
39
|
+
- deregister-clouds
|
40
|
+
Deregisters a previously created Openshift Flex cloud.
|
41
|
+
|
42
|
+
- create-environment
|
43
|
+
Creates a new Openshift Flex or Express environment on which you can deploy applicatons.
|
44
|
+
|
45
|
+
- list-environment
|
46
|
+
Lists all Openshift Flex and Express environments.
|
47
|
+
|
48
|
+
- stop-environments
|
49
|
+
Stops an Openshift Flex environment.
|
50
|
+
|
51
|
+
- start-environments
|
52
|
+
Starts a previously stopped Openshift Flex environment.
|
53
|
+
|
54
|
+
- destroy-environment
|
55
|
+
Destroys an Openshift Flex or Express environemnt.
|
56
|
+
|
57
|
+
- open-console
|
58
|
+
Opens a console login to the Openshift Flex environemnt.
|
59
|
+
|
60
|
+
- list-servers
|
61
|
+
Lists all server nodes that are part of an Openshift Flex environemnt.
|
62
|
+
|
63
|
+
- add-server
|
64
|
+
Adds a new server node to an existing Openshift Flex environemnt.
|
65
|
+
|
66
|
+
- remove-server
|
67
|
+
Removes a server node from an existing Openshift Flex environemnt.
|
68
|
+
|
69
|
+
- create-applicaton
|
70
|
+
Creates a new application on an exisitng environemnt.
|
71
|
+
|
72
|
+
- clone-application
|
73
|
+
Clones an exisitng application into a new directory
|
74
|
+
|
75
|
+
- list-application
|
76
|
+
Lists all applications.
|
77
|
+
|
78
|
+
- inspect-applications
|
79
|
+
Show application details.
|
80
|
+
|
81
|
+
- start-application
|
82
|
+
Starts a stopped application.
|
83
|
+
|
84
|
+
- stop-application
|
85
|
+
Stops a started application.
|
86
|
+
|
87
|
+
- restart-application
|
88
|
+
Restarts an application if it was running or starts it if it was stopped.
|
89
|
+
|
90
|
+
- list-cartridges
|
91
|
+
Lists available and installed application dependencies.
|
92
|
+
|
93
|
+
- add-cartridges
|
94
|
+
Installs application dependency cartridges.
|
95
|
+
|
96
|
+
- remove-cartridges
|
97
|
+
Uninstalls application dependency cartridges
|
98
|
+
|
99
|
+
See 'os help COMMAND' for more information on a sepcific command.
|
100
|
+
USAGE
|
101
|
+
end
|
102
|
+
|
103
|
+
command = ARGV.shift
|
104
|
+
if command.nil? or command.strip == ''
|
105
|
+
usage
|
106
|
+
else
|
107
|
+
system("os #{command} --help")
|
108
|
+
end
|
@@ -0,0 +1,222 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Copyright 2010 Red Hat, Inc.
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person
|
5
|
+
# obtaining a copy of this software and associated documentation files
|
6
|
+
# (the "Software"), to deal in the Software without restriction,
|
7
|
+
# including without limitation the rights to use, copy, modify, merge,
|
8
|
+
# publish, distribute, sublicense, and/or sell copies of the Software,
|
9
|
+
# and to permit persons to whom the Software is furnished to do so,
|
10
|
+
# subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
19
|
+
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
20
|
+
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
21
|
+
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
22
|
+
# SOFTWARE.
|
23
|
+
|
24
|
+
require 'openshift'
|
25
|
+
|
26
|
+
def usage
|
27
|
+
puts <<USAGE
|
28
|
+
== Synopsis
|
29
|
+
|
30
|
+
os-inspect-application: Inspect application details
|
31
|
+
|
32
|
+
== Usage
|
33
|
+
|
34
|
+
os inspect-application [options] [NAME]
|
35
|
+
|
36
|
+
-u|--username
|
37
|
+
Redhat Login (RHN or OpenShift login with OpenShift Express access)
|
38
|
+
|
39
|
+
-p|--password
|
40
|
+
Redhat Password
|
41
|
+
|
42
|
+
-t|--target flex|express
|
43
|
+
The cloud platform the application is hosted on.
|
44
|
+
|
45
|
+
-e|--environment ENVIRONMENT_ID:
|
46
|
+
The name or ID of the environment that hosts the application.
|
47
|
+
|
48
|
+
-v|--verbose
|
49
|
+
Get application deployment history.
|
50
|
+
|
51
|
+
-h|--help
|
52
|
+
Prints this message
|
53
|
+
|
54
|
+
NAME: The name or GUID of the application.
|
55
|
+
USAGE
|
56
|
+
end
|
57
|
+
|
58
|
+
opts = GetoptLong.new(
|
59
|
+
["--username", "-u", GetoptLong::REQUIRED_ARGUMENT],
|
60
|
+
["--password", "-p", GetoptLong::REQUIRED_ARGUMENT],
|
61
|
+
["--environment", "-e", GetoptLong::REQUIRED_ARGUMENT],
|
62
|
+
["--verbose", "-v", GetoptLong::NO_ARGUMENT],
|
63
|
+
["--help", "-h", GetoptLong::NO_ARGUMENT],
|
64
|
+
["--debug", GetoptLong::NO_ARGUMENT],
|
65
|
+
["--sso", GetoptLong::REQUIRED_ARGUMENT],
|
66
|
+
["--porcelin", GetoptLong::NO_ARGUMENT]
|
67
|
+
)
|
68
|
+
|
69
|
+
args = {}
|
70
|
+
begin
|
71
|
+
opts.each{ |k,v| args[k]=v }
|
72
|
+
rescue GetoptLong::Error => e
|
73
|
+
usage
|
74
|
+
exit -100
|
75
|
+
end
|
76
|
+
|
77
|
+
if args['--help']
|
78
|
+
usage
|
79
|
+
exit
|
80
|
+
end
|
81
|
+
|
82
|
+
app_name = ARGV.shift
|
83
|
+
clone_dir = ARGV.shift
|
84
|
+
@debug = true if args['--debug']
|
85
|
+
debug "Application name: #{app_name}"
|
86
|
+
|
87
|
+
@porcelin = true if args['--porcelin']
|
88
|
+
|
89
|
+
args['--target'] = conf('default_target') || 'flex' if args['--target'].nil? or args['--target']==""
|
90
|
+
debug "Target platform #{args['--target']}"
|
91
|
+
|
92
|
+
if args['--target'] == 'flex'
|
93
|
+
flex_server = conf('flex_server')
|
94
|
+
cookie = args['--sso']
|
95
|
+
if !cookie
|
96
|
+
username = args['--username'] || conf("username") || Openshift::IO.prompt("Redhat username",[],Openshift::Validation.method(:check_login))
|
97
|
+
password = args['--password'] || Openshift::IO.prompt("Redhat password",nil,nil,true,false)
|
98
|
+
csay("Logging into Openshift Flex as #{username}\n",:message)
|
99
|
+
cookie=Openshift.login(@http,username,password)
|
100
|
+
end
|
101
|
+
|
102
|
+
candidates=[]
|
103
|
+
environment_id = args["--environment"]
|
104
|
+
if environment_id
|
105
|
+
csay("Retrieving environment details for environment #{environment_id}... ") if not @porcelin
|
106
|
+
uri = URI.parse("#{flex_server}/rest/api")
|
107
|
+
response = Openshift::Rest.get(@http, uri, nil, cookie, nil)
|
108
|
+
case response
|
109
|
+
when Net::HTTPSuccess
|
110
|
+
csay("[OK]",:conf) if not @porcelin
|
111
|
+
else
|
112
|
+
debug "HTTP code: #{response.code}"
|
113
|
+
debug response.body
|
114
|
+
csay("[ERROR]",:error) if not @porcelin
|
115
|
+
csay("Unable to contact Flex server",:error)
|
116
|
+
exit -301
|
117
|
+
end
|
118
|
+
data = JSON.parse(response.body)
|
119
|
+
list_env_url = data['links']['list-clusters']
|
120
|
+
|
121
|
+
uri = URI.parse("#{flex_server}/rest/#{list_env_url['href']}/#{environment_id}")
|
122
|
+
response = Openshift::Rest.doHttp(@http, list_env_url['method'], uri, nil, cookie, nil)
|
123
|
+
case response
|
124
|
+
when Net::HTTPSuccess
|
125
|
+
csay("[OK]",:conf) if not @porcelin
|
126
|
+
else
|
127
|
+
debug "HTTP code: #{response.code}"
|
128
|
+
debug response.body
|
129
|
+
csay("[ERROR]",:error) if not @porcelin
|
130
|
+
csay("Unable to retrieve environment details.",:error)
|
131
|
+
exit -301
|
132
|
+
end
|
133
|
+
data = [JSON.parse(response.body)['cluster']]
|
134
|
+
else
|
135
|
+
csay("Retrieving environment details... ") if not @porcelin
|
136
|
+
data = JSON.parse(`os-list-environments --sso "#{cookie}" --porcelin`)
|
137
|
+
end
|
138
|
+
|
139
|
+
environments = data
|
140
|
+
environments.each{ |environment|
|
141
|
+
debug environment['name']
|
142
|
+
if environment['cluster-status'] == 'STARTED'
|
143
|
+
csay("Retrieving application list for environment #{environment["name"]}... ") if not @porcelin
|
144
|
+
uri = URI.parse("https://#{environment['dns']}:4242/api")
|
145
|
+
response = Openshift::Rest.get(@http, uri, nil, nil, {'user' => environment['username'], 'password' => environment['password']})
|
146
|
+
api = JSON.parse(response.body)
|
147
|
+
list_app_link = api['links']['list-applications']
|
148
|
+
|
149
|
+
uri = URI.parse("https://#{environment['dns']}:4242/#{list_app_link['href']}")
|
150
|
+
response = Openshift::Rest.doHttp(@http, list_app_link['method'], uri, nil, nil, {'user' => environment['username'], 'password' => environment['password']})
|
151
|
+
case response
|
152
|
+
when Net::HTTPSuccess
|
153
|
+
csay("[OK]",:conf) if not @porcelin
|
154
|
+
else
|
155
|
+
debug "HTTP code: #{response.code}"
|
156
|
+
debug response.body
|
157
|
+
csay("[ERROR]",:error) if not @porcelin
|
158
|
+
csay("Unable to retrieve application list.",:error)
|
159
|
+
exit -301
|
160
|
+
end
|
161
|
+
|
162
|
+
apps = JSON.parse(response.body)["applications"]
|
163
|
+
apps.each{ |app|
|
164
|
+
if args['--verbose']
|
165
|
+
csay("Retrieving application details... ") if not @porcelin
|
166
|
+
uri = URI.parse("https://#{environment['dns']}:4242/applications/#{app['guid']}/revisions")
|
167
|
+
response = Openshift::Rest.get(@http, uri, nil, nil, {'user' => environment['username'], 'password' => environment['password']})
|
168
|
+
case response
|
169
|
+
when Net::HTTPSuccess
|
170
|
+
csay("[OK]",:conf) if not @porcelin
|
171
|
+
else
|
172
|
+
debug "HTTP code: #{response.code}"
|
173
|
+
debug response.body
|
174
|
+
csay("[ERROR]",:error) if not @porcelin
|
175
|
+
csay("Unable to retrieve application details",:error)
|
176
|
+
exit -301
|
177
|
+
end
|
178
|
+
|
179
|
+
data = JSON.parse(response.body)
|
180
|
+
app["revisions"] = data["revisions"]
|
181
|
+
app["revisions"].each{|rev| rev['datetime'] = Time.at(rev['timestamp'].to_f)}
|
182
|
+
end
|
183
|
+
|
184
|
+
if not app_name or (app["name"] == app_name or app["guid"] == app_name)
|
185
|
+
candidates += [{"environment"=>environment, "application"=>app}]
|
186
|
+
end
|
187
|
+
}
|
188
|
+
else
|
189
|
+
csay("Environment #{environment["name"]} is not currently started, skipping.",:warn) if not @porcelin
|
190
|
+
end
|
191
|
+
}
|
192
|
+
|
193
|
+
if args["--porcelin"]
|
194
|
+
print JSON.generate(candidates)
|
195
|
+
else
|
196
|
+
candidates.each{ |candidate|
|
197
|
+
environment = candidate["environment"]
|
198
|
+
app = candidate["application"]
|
199
|
+
|
200
|
+
print "Environment information:\n"
|
201
|
+
Openshift::Formatter.table(["Environment Id","Name","Cloud", "DNS", "Load balanced", "Location", "State"],
|
202
|
+
['id','name','cloud-account-name', 'dns', 'loadbalanced','location','cluster-status'],
|
203
|
+
[16,15,20,40,20,15,10],
|
204
|
+
[environment])
|
205
|
+
print "Application information:\n"
|
206
|
+
Openshift::Formatter.table(["Application GUID","Application name", "Version", "State"],
|
207
|
+
['guid','name','version','status'],
|
208
|
+
[39,20,7,20],
|
209
|
+
[app],1)
|
210
|
+
if app['revisions']
|
211
|
+
print " Application deployments:\n"
|
212
|
+
Openshift::Formatter.table(["Deployment #","Branch name", "Comments", "Timestamp", "Revision"],
|
213
|
+
['number','name','comments','datetime','revision'],
|
214
|
+
[12,40,50,30,45],
|
215
|
+
app['revisions'],2)
|
216
|
+
end
|
217
|
+
print "\n"
|
218
|
+
}
|
219
|
+
end
|
220
|
+
else
|
221
|
+
csay("This feature is currently not implemented for Openshift Express applications.\n",:red)
|
222
|
+
end
|