janky 0.9.0 → 0.9.9
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +30 -0
- data/README.md +59 -21
- data/Rakefile +7 -0
- data/janky.gemspec +6 -4
- data/lib/janky.rb +81 -22
- data/lib/janky/app.rb +1 -1
- data/lib/janky/build.rb +9 -1
- data/lib/janky/builder/http.rb +8 -0
- data/lib/janky/builder/receiver.rb +1 -1
- data/lib/janky/{campfire.rb → chat_service.rb} +28 -55
- data/lib/janky/chat_service/campfire.rb +33 -0
- data/lib/janky/chat_service/hipchat.rb +29 -0
- data/lib/janky/chat_service/mock.rb +26 -0
- data/lib/janky/database/migrate/1312115512_init.rb +1 -1
- data/lib/janky/exception.rb +23 -0
- data/lib/janky/github.rb +82 -23
- data/lib/janky/github/api.rb +17 -8
- data/lib/janky/github/payload_parser.rb +1 -1
- data/lib/janky/hubot.rb +15 -14
- data/lib/janky/job_creator.rb +10 -2
- data/lib/janky/notifier/{campfire.rb → chat_service.rb} +4 -3
- data/lib/janky/notifier/mock.rb +2 -2
- data/lib/janky/public/css/base.css +33 -10
- data/lib/janky/repository.rb +4 -4
- data/lib/janky/templates/index.mustache +10 -5
- data/lib/janky/version.rb +1 -1
- data/lib/janky/views/index.rb +10 -3
- data/lib/janky/views/layout.rb +1 -0
- data/test/janky_test.rb +14 -2
- data/test/test_helper.rb +5 -4
- metadata +13 -10
- data/lib/janky/campfire/mock.rb +0 -0
data/CHANGES
CHANGED
@@ -1,3 +1,33 @@
|
|
1
|
+
= 0.9.9 / Not yet released
|
2
|
+
|
3
|
+
* HipChat support [Justin Smestad, Seth Chisamore, Simon Rozet]
|
4
|
+
|
5
|
+
* Support for GitHub Enterprise. [Dusty Burwell, Simon Rozet]
|
6
|
+
|
7
|
+
* Support for GitHub logins containing dashes. [Thom May]
|
8
|
+
|
9
|
+
* Support for branches containing slashes in their name. [Andres Torres]
|
10
|
+
|
11
|
+
* Respond with proper status code to invalid Jenkins notification
|
12
|
+
requests. [Chris Mytton]
|
13
|
+
|
14
|
+
* Support for Jenkins servers running behind SSL. [Vivek Pandey,
|
15
|
+
Gavin Heavyside, Thom May]
|
16
|
+
|
17
|
+
* Support for Jenkins servers running under a path [Piet Jaspers]
|
18
|
+
|
19
|
+
* Deprecate JANKY_CAMPFIRE_ACCOUNT. Please use JANKY_CHAT_CAMPFIRE_ACCOUNT
|
20
|
+
instead. [Simon Rozet]
|
21
|
+
|
22
|
+
* Deprecate JANKY_CAMPFIRE_TOKEN. Please use JANKY_CHAT_CAMPFIRE_TOKEN
|
23
|
+
instead. [Simon Rozet]
|
24
|
+
|
25
|
+
* Deprecate JANKY_CAMPFIRE_DEFAULT_ROOM. Please use
|
26
|
+
JANKY_CHAT_DEFAULT_ROOM instead. [Simon Rozet]
|
27
|
+
|
28
|
+
* Both JANKY_BASE_URL and JANKY_DEFAULT_BUILDER now require a trailing
|
29
|
+
slash. [Simon Rozet]
|
30
|
+
|
1
31
|
= 0.9 / 2011-12-19
|
2
32
|
|
3
33
|
* Initial public release.
|
data/README.md
CHANGED
@@ -5,7 +5,7 @@ This is Janky, a continuous integration server built on top of
|
|
5
5
|
[Jenkins][], controlled by [Hubot][], and designed for [GitHub][].
|
6
6
|
|
7
7
|
* **Built on top of Jenkins.** The power, vast amount of plugins and large
|
8
|
-
|
8
|
+
community of the popular CI server all wrapped up in a great experience.
|
9
9
|
|
10
10
|
* **Controlled by Hubot.** Day to day operations are exposed as simple
|
11
11
|
Hubot commands that the whole team can use.
|
@@ -52,7 +52,7 @@ available rooms:
|
|
52
52
|
|
53
53
|
Then pick one:
|
54
54
|
|
55
|
-
hubot ci set janky
|
55
|
+
hubot ci set room janky The Serious Room
|
56
56
|
|
57
57
|
Get the status of a build:
|
58
58
|
|
@@ -86,21 +86,24 @@ instructions and install the [Notification Plugin][np] version 1.4.
|
|
86
86
|
|
87
87
|
Janky is designed to be deployed to [Heroku](https://heroku.com).
|
88
88
|
|
89
|
-
Grab all the necessary files from [
|
89
|
+
Grab all the necessary files from [the gist][gist]:
|
90
90
|
|
91
|
-
$ git clone
|
91
|
+
$ git clone git://gist.github.com/1497335 janky
|
92
92
|
|
93
93
|
Then push up it to a new Heroku app:
|
94
94
|
|
95
95
|
$ cd janky
|
96
96
|
$ heroku create --stack cedar
|
97
|
+
$ bundle install
|
98
|
+
$ git add Gemfile.lock
|
99
|
+
$ git commit Gemfile.lock -m "lock bundle"
|
97
100
|
$ git push heroku master
|
98
101
|
|
99
|
-
After
|
102
|
+
After configuring the app (see below), create the database:
|
100
103
|
|
101
104
|
$ heroku run rake db:migrate
|
102
105
|
|
103
|
-
[gist]: https://gist.github.com/
|
106
|
+
[gist]: https://gist.github.com/1497335
|
104
107
|
|
105
108
|
### Configuring
|
106
109
|
|
@@ -111,10 +114,11 @@ command:
|
|
111
114
|
|
112
115
|
Required settings:
|
113
116
|
|
114
|
-
* `JANKY_BASE_URL`: The application URL with a trailing slash. Example:
|
117
|
+
* `JANKY_BASE_URL`: The application URL **with** a trailing slash. Example:
|
115
118
|
`http://mf-doom-42.heroku.com/`.
|
116
|
-
* `JANKY_BUILDER_DEFAULT`: The Jenkins server URL with a trailing slash.
|
117
|
-
Example: `http://jenkins.example.com/`.
|
119
|
+
* `JANKY_BUILDER_DEFAULT`: The Jenkins server URL **with** a trailing slash.
|
120
|
+
Example: `http://jenkins.example.com/`. For basic auth, include the
|
121
|
+
credentials in the URL: `http://user:pass@jenkins.example.com/`.
|
118
122
|
* `JANKY_CONFIG_DIR`: Directory where build config templates are stored.
|
119
123
|
Typically set to `/app/config` on Heroku.
|
120
124
|
* `JANKY_HUBOT_USER`: Login used to protect the Hubot API.
|
@@ -124,11 +128,45 @@ Required settings:
|
|
124
128
|
* `JANKY_GITHUB_PASSWORD`: The password for the GitHub user.
|
125
129
|
* `JANKY_GITHUB_HOOK_SECRET`: Secret used to sign hook requests from
|
126
130
|
GitHub.
|
127
|
-
* `
|
128
|
-
|
131
|
+
* `JANKY_CHAT_DEFAULT_ROOM`: Chat room where notifications are sent by default.
|
132
|
+
|
133
|
+
#### GitHub Enterprise
|
134
|
+
|
135
|
+
Using Janky with [GitHub Enterprise][ghe] requires one extra setting:
|
136
|
+
|
137
|
+
* `JANKY_GITHUB_API_URL`: Full API URL of the instance, *with* a trailing
|
138
|
+
slash. Example: `https://github.example.com/api/v3/`.
|
139
|
+
|
140
|
+
[ghe]: https://enterprise.github.com
|
141
|
+
|
142
|
+
### Chat Notification
|
143
|
+
|
144
|
+
#### Campfire
|
145
|
+
Janky notifies [Campfire][] chat rooms by default. Required settings:
|
146
|
+
|
147
|
+
* `JANKY_CHAT_CAMPFIRE_ACCOUNT`: account name.
|
148
|
+
* `JANKY_CHAT_CAMPFIRE_TOKEN`: authentication token for the user sending
|
129
149
|
build notifications.
|
130
|
-
|
131
|
-
|
150
|
+
|
151
|
+
[Campfire]: http://campfirenow.com/
|
152
|
+
|
153
|
+
#### HipChat
|
154
|
+
|
155
|
+
Required settings:
|
156
|
+
|
157
|
+
* `JANKY_CHAT=hipchat`
|
158
|
+
* `JANKY_CHAT_HIPCHAT_TOKEN`: authentication token
|
159
|
+
* `JANKY_CHAT_HIPCHAT_FROM`: name that messages will appear be sent from.
|
160
|
+
Defaults to `CI`.
|
161
|
+
|
162
|
+
Installation:
|
163
|
+
|
164
|
+
* Add `require "janky/chat_service/hipchat"` to the `config.ru` file.
|
165
|
+
* `echo 'gem "hipchat", "~>0.4" >> Gemfile'`
|
166
|
+
* `bundle`
|
167
|
+
* `git commit -am "install hipchat"`
|
168
|
+
|
169
|
+
### Authentication
|
132
170
|
|
133
171
|
To restrict access to members of a GitHub organization, [register a new
|
134
172
|
OAuth application on GitHub](https://github.com/account/applications)
|
@@ -143,10 +181,10 @@ a few extra settings:
|
|
143
181
|
|
144
182
|
### Hubot
|
145
183
|
|
146
|
-
Install the [
|
184
|
+
Install the [janky script](http://git.io/hubot-janky) in your Hubot
|
147
185
|
then set the `HUBOT_JANKY_URL` environment variable. Example:
|
148
186
|
`http://user:secret@janky.example.com/_hubot/`, with user and password
|
149
|
-
replaced by `JANKY_HUBOT_USER` and `JANKY_HUBOT_PASSWORD`
|
187
|
+
replaced by `JANKY_HUBOT_USER` and `JANKY_HUBOT_PASSWORD` respectively.
|
150
188
|
|
151
189
|
### Custom Build Configuration
|
152
190
|
|
@@ -168,6 +206,10 @@ server.
|
|
168
206
|
Hacking
|
169
207
|
-------
|
170
208
|
|
209
|
+
Get your environment up and running:
|
210
|
+
|
211
|
+
$ script/bootstrap
|
212
|
+
|
171
213
|
Create the databases:
|
172
214
|
|
173
215
|
$ mysqladmin -uroot create janky_development
|
@@ -178,10 +220,6 @@ Create the tables:
|
|
178
220
|
$ RACK_ENV=development bin/rake db:migrate
|
179
221
|
$ RACK_ENV=test bin/rake db:migrate
|
180
222
|
|
181
|
-
Get your environment up and running:
|
182
|
-
|
183
|
-
$ script/boostrap
|
184
|
-
|
185
223
|
Seed some data into the development database:
|
186
224
|
|
187
225
|
$ bin/rake db:seed
|
@@ -196,7 +234,7 @@ Open the app:
|
|
196
234
|
|
197
235
|
Run the test suite:
|
198
236
|
|
199
|
-
$
|
237
|
+
$ bin/rake
|
200
238
|
|
201
239
|
Contributing
|
202
240
|
------------
|
@@ -207,5 +245,5 @@ send a Pull Request.
|
|
207
245
|
Copying
|
208
246
|
-------
|
209
247
|
|
210
|
-
Copyright © 2011, GitHub, Inc. See the `COPYING` file for license
|
248
|
+
Copyright © 2011-2012, GitHub, Inc. See the `COPYING` file for license
|
211
249
|
rights and limitations (MIT).
|
data/Rakefile
CHANGED
data/janky.gemspec
CHANGED
@@ -13,7 +13,7 @@ Gem::Specification.new "janky", Janky::VERSION do |s|
|
|
13
13
|
s.add_dependency "sinatra", "~>1.3"
|
14
14
|
s.add_dependency "sinatra_auth_github", "~>0.1.5"
|
15
15
|
s.add_dependency "mustache", "~>0.11"
|
16
|
-
s.add_dependency "yajl-ruby", "~>0
|
16
|
+
s.add_dependency "yajl-ruby", "~>1.1.0"
|
17
17
|
s.add_dependency "activerecord", "~>3.1.0"
|
18
18
|
s.add_dependency "broach", "~>0.2"
|
19
19
|
s.add_dependency "replicate", "~>1.4"
|
@@ -46,8 +46,10 @@ lib/janky/builder/mock.rb
|
|
46
46
|
lib/janky/builder/payload.rb
|
47
47
|
lib/janky/builder/receiver.rb
|
48
48
|
lib/janky/builder/runner.rb
|
49
|
-
lib/janky/
|
50
|
-
lib/janky/campfire
|
49
|
+
lib/janky/chat_service.rb
|
50
|
+
lib/janky/chat_service/campfire.rb
|
51
|
+
lib/janky/chat_service/hipchat.rb
|
52
|
+
lib/janky/chat_service/mock.rb
|
51
53
|
lib/janky/commit.rb
|
52
54
|
lib/janky/database/migrate/1312115512_init.rb
|
53
55
|
lib/janky/database/migrate/1312117285_non_unique_repo_uri.rb
|
@@ -72,7 +74,7 @@ lib/janky/helpers.rb
|
|
72
74
|
lib/janky/hubot.rb
|
73
75
|
lib/janky/job_creator.rb
|
74
76
|
lib/janky/notifier.rb
|
75
|
-
lib/janky/notifier/
|
77
|
+
lib/janky/notifier/chat_service.rb
|
76
78
|
lib/janky/notifier/mock.rb
|
77
79
|
lib/janky/notifier/multi.rb
|
78
80
|
lib/janky/public/css/base.css
|
data/lib/janky.rb
CHANGED
@@ -33,12 +33,14 @@ require "janky/builder/http"
|
|
33
33
|
require "janky/builder/mock"
|
34
34
|
require "janky/builder/payload"
|
35
35
|
require "janky/builder/receiver"
|
36
|
-
require "janky/
|
36
|
+
require "janky/chat_service"
|
37
|
+
require "janky/chat_service/campfire"
|
38
|
+
require "janky/chat_service/mock"
|
37
39
|
require "janky/exception"
|
38
40
|
require "janky/notifier"
|
41
|
+
require "janky/notifier/chat_service"
|
39
42
|
require "janky/notifier/mock"
|
40
43
|
require "janky/notifier/multi"
|
41
|
-
require "janky/notifier/campfire"
|
42
44
|
require "janky/app"
|
43
45
|
require "janky/views/layout"
|
44
46
|
require "janky/views/index"
|
@@ -53,9 +55,9 @@ module Janky
|
|
53
55
|
|
54
56
|
# Setup the application, including the database and Jenkins connections.
|
55
57
|
#
|
56
|
-
# settings - Hash of app settings. Typically ENV but any object
|
57
|
-
# to #[] is valid. See required_settings for
|
58
|
-
# The RACK_ENV
|
58
|
+
# settings - Hash of app settings. Typically ENV but any object that responds
|
59
|
+
# to #[], #[]= and #each is valid. See required_settings for
|
60
|
+
# required keys. The RACK_ENV key is always required.
|
59
61
|
#
|
60
62
|
# Raises an Error when required settings are missing.
|
61
63
|
# Returns nothing.
|
@@ -75,13 +77,20 @@ module Janky
|
|
75
77
|
|
76
78
|
if env != "production"
|
77
79
|
settings["DATABASE_URL"] ||= "mysql2://root@localhost/janky_#{env}"
|
78
|
-
settings["JANKY_BASE_URL"] ||= "http://localhost:9393"
|
80
|
+
settings["JANKY_BASE_URL"] ||= "http://localhost:9393/"
|
79
81
|
settings["JANKY_BUILDER_DEFAULT"] ||= "http://localhost:8080/"
|
80
82
|
settings["JANKY_CONFIG_DIR"] ||= File.dirname(__FILE__)
|
83
|
+
settings["JANKY_CHAT"] = "campfire"
|
84
|
+
settings["JANKY_CHAT_CAMPFIRE_ACCOUNT"] = "account"
|
85
|
+
settings["JANKY_CHAT_CAMPFIRE_TOKEN"] = "token"
|
81
86
|
end
|
82
87
|
|
83
88
|
database = URI(settings["DATABASE_URL"])
|
84
89
|
adapter = database.scheme == "postgres" ? "postgresql" : database.scheme
|
90
|
+
if settings["JANKY_BASE_URL"][-1] != ?/
|
91
|
+
warn "JANKY_BASE_URL must have a trailing slash"
|
92
|
+
settings["JANKY_BASE_URL"] = settings["JANKY_BASE_URL"] + "/"
|
93
|
+
end
|
85
94
|
base_url = URI(settings["JANKY_BASE_URL"]).to_s
|
86
95
|
|
87
96
|
ActiveRecord::Base.establish_connection(
|
@@ -99,16 +108,32 @@ module Janky
|
|
99
108
|
end
|
100
109
|
|
101
110
|
# Setup the callback URL of this Janky host.
|
102
|
-
Janky::Builder.setup(base_url + "
|
111
|
+
Janky::Builder.setup(base_url + "_builder")
|
103
112
|
|
104
113
|
# Setup the default Jenkins build host
|
114
|
+
if settings["JANKY_BUILDER_DEFAULT"][-1] != ?/
|
115
|
+
raise Error, "JANKY_BUILDER_DEFAULT must have a trailing slash"
|
116
|
+
end
|
105
117
|
Janky::Builder[:default] = settings["JANKY_BUILDER_DEFAULT"]
|
106
118
|
|
119
|
+
if settings.key?("JANKY_GITHUB_API_URL")
|
120
|
+
api_url = settings["JANKY_GITHUB_API_URL"]
|
121
|
+
git_host = URI(api_url).host
|
122
|
+
else
|
123
|
+
api_url = "https://api.github.com/"
|
124
|
+
git_host = "github.com"
|
125
|
+
end
|
126
|
+
if api_url[-1] != ?/
|
127
|
+
raise Error, "JANKY_GITHUB_API_URL must have a trailing slash"
|
128
|
+
end
|
129
|
+
hook_url = base_url + "_github"
|
107
130
|
Janky::GitHub.setup(
|
108
131
|
settings["JANKY_GITHUB_USER"],
|
109
132
|
settings["JANKY_GITHUB_PASSWORD"],
|
110
133
|
settings["JANKY_GITHUB_HOOK_SECRET"],
|
111
|
-
|
134
|
+
hook_url,
|
135
|
+
api_url,
|
136
|
+
git_host
|
112
137
|
)
|
113
138
|
|
114
139
|
if settings.key?("JANKY_SESSION_SECRET")
|
@@ -132,15 +157,38 @@ module Janky
|
|
132
157
|
:password => settings["JANKY_HUBOT_PASSWORD"]
|
133
158
|
)
|
134
159
|
|
135
|
-
Janky::
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
160
|
+
Janky::Exception.setup(Janky::Exception::Logger.new($stderr))
|
161
|
+
|
162
|
+
if campfire_account = settings["JANKY_CAMPFIRE_ACCOUNT"]
|
163
|
+
warn "JANKY_CAMPFIRE_ACCOUNT is deprecated. Please use " \
|
164
|
+
"JANKY_CHAT_CAMPFIRE_ACCOUNT instead."
|
165
|
+
settings["JANKY_CHAT_CAMPFIRE_ACCOUNT"] ||=
|
166
|
+
settings["JANKY_CAMPFIRE_ACCOUNT"]
|
167
|
+
end
|
168
|
+
|
169
|
+
if campfire_token = settings["JANKY_CAMPFIRE_TOKEN"]
|
170
|
+
warn "JANKY_CAMPFIRE_TOKEN is deprecated. Please use " \
|
171
|
+
"JANKY_CHAT_CAMPFIRE_TOKEN instead."
|
172
|
+
settings["JANKY_CHAT_CAMPFIRE_TOKEN"] ||=
|
173
|
+
settings["JANKY_CAMPFIRE_ACCOUNT"]
|
174
|
+
end
|
140
175
|
|
141
|
-
|
176
|
+
chat_name = settings["JANKY_CHAT"] || "campfire"
|
177
|
+
chat_settings = {}
|
178
|
+
settings.each do |key, value|
|
179
|
+
if key =~ /^JANKY_CHAT_#{chat_name.upcase}_/
|
180
|
+
chat_settings[key] = value
|
181
|
+
end
|
182
|
+
end
|
183
|
+
chat_room = settings["JANKY_CHAT_DEFAULT_ROOM"] ||
|
184
|
+
settings["JANKY_CAMPFIRE_DEFAULT_ROOM"]
|
185
|
+
if settings["JANKY_CAMPFIRE_DEFAULT_ROOM"]
|
186
|
+
warn "JANKY_CAMPFIRE_DEFAULT_ROOM is deprecated. Please use " \
|
187
|
+
"JANKY_CHAT_DEFAULT_ROOM instead."
|
188
|
+
end
|
189
|
+
ChatService.setup(chat_name, chat_settings, chat_room)
|
142
190
|
|
143
|
-
Notifier.setup(Notifier::
|
191
|
+
Notifier.setup(Notifier::ChatService)
|
144
192
|
end
|
145
193
|
|
146
194
|
# List of settings required in production.
|
@@ -152,8 +200,7 @@ module Janky
|
|
152
200
|
JANKY_BUILDER_DEFAULT
|
153
201
|
JANKY_CONFIG_DIR
|
154
202
|
JANKY_GITHUB_USER JANKY_GITHUB_PASSWORD JANKY_GITHUB_HOOK_SECRET
|
155
|
-
JANKY_HUBOT_USER JANKY_HUBOT_PASSWORD
|
156
|
-
JANKY_CAMPFIRE_ACCOUNT JANKY_CAMPFIRE_TOKEN JANKY_CAMPFIRE_DEFAULT_ROOM]
|
203
|
+
JANKY_HUBOT_USER JANKY_HUBOT_PASSWORD]
|
157
204
|
end
|
158
205
|
|
159
206
|
# Directory where Jenkins job configuration templates are located.
|
@@ -171,7 +218,7 @@ module Janky
|
|
171
218
|
Janky::Builder.enable_mock!
|
172
219
|
Janky::GitHub.enable_mock!
|
173
220
|
Janky::Notifier.enable_mock!
|
174
|
-
Janky::
|
221
|
+
Janky::ChatService.enable_mock!
|
175
222
|
Janky::App.disable :github_team_id
|
176
223
|
end
|
177
224
|
|
@@ -183,10 +230,10 @@ module Janky
|
|
183
230
|
Janky::Builder.reset!
|
184
231
|
end
|
185
232
|
|
186
|
-
# The Janky Rack application, assembled from four apps. Exceptions
|
187
|
-
#
|
188
|
-
#
|
189
|
-
#
|
233
|
+
# The Janky Rack application, assembled from four apps. Exceptions raised
|
234
|
+
# during the request cycle are caught by the Exception middleware which
|
235
|
+
# typically reports them to an external service before re-raising the
|
236
|
+
# exception.
|
190
237
|
#
|
191
238
|
# Returns a memoized Rack application.
|
192
239
|
def self.app
|
@@ -221,4 +268,16 @@ module Janky
|
|
221
268
|
end
|
222
269
|
}
|
223
270
|
end
|
271
|
+
|
272
|
+
# Register a Chat service implementation.
|
273
|
+
#
|
274
|
+
# name - Service name as a String, e.g. "irc".
|
275
|
+
# service - Constant for the implementation.
|
276
|
+
#
|
277
|
+
# Returns nothing.
|
278
|
+
def self.register_chat_service(name, service)
|
279
|
+
Janky::ChatService.adapters[name] = service
|
280
|
+
end
|
281
|
+
|
282
|
+
register_chat_service "campfire", ChatService::Campfire
|
224
283
|
end
|
data/lib/janky/app.rb
CHANGED
data/lib/janky/build.rb
CHANGED
@@ -180,7 +180,7 @@ module Janky
|
|
180
180
|
# Returns the String room name.
|
181
181
|
def room_name
|
182
182
|
if room_id && room_id > 0
|
183
|
-
|
183
|
+
ChatService.room_name(room_id)
|
184
184
|
end
|
185
185
|
end
|
186
186
|
|
@@ -212,6 +212,14 @@ module Janky
|
|
212
212
|
commit.url
|
213
213
|
end
|
214
214
|
|
215
|
+
def commit_message
|
216
|
+
commit.message
|
217
|
+
end
|
218
|
+
|
219
|
+
def commit_author
|
220
|
+
commit.author
|
221
|
+
end
|
222
|
+
|
215
223
|
def number
|
216
224
|
id.to_s
|
217
225
|
end
|