sensu-plugins-http-boutetnico 1.0.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/CHANGELOG.md +1 -0
- data/LICENSE +22 -0
- data/README.md +211 -0
- data/bin/check-head-redirect.rb +151 -0
- data/bin/check-http-cors.rb +145 -0
- data/bin/check-http-json.rb +232 -0
- data/bin/check-http.rb +461 -0
- data/bin/check-https-cert.rb +122 -0
- data/bin/check-last-modified.rb +178 -0
- data/bin/metrics-curl.rb +86 -0
- data/bin/metrics-http-json-deep.rb +133 -0
- data/bin/metrics-http-json.rb +137 -0
- data/bin/metrics-libcurl.rb +167 -0
- data/lib/sensu-plugins-http.rb +5 -0
- data/lib/sensu-plugins-http/aws-v4.rb +40 -0
- data/lib/sensu-plugins-http/common.rb +35 -0
- data/lib/sensu-plugins-http/version.rb +11 -0
- metadata +290 -0
@@ -0,0 +1,122 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: false
|
3
|
+
|
4
|
+
#
|
5
|
+
# check-https-cert
|
6
|
+
#
|
7
|
+
# DESCRIPTION:
|
8
|
+
# Checks the expiration date of a URL's TLS/SSL Certificate
|
9
|
+
# and notifies if it is before the expiry parameter. Throws
|
10
|
+
# a critical if the date is at or past the expiry date.
|
11
|
+
#
|
12
|
+
# OUTPUT:
|
13
|
+
# plain text
|
14
|
+
#
|
15
|
+
# PLATFORMS:
|
16
|
+
# Linux
|
17
|
+
#
|
18
|
+
# DEPENDENCIES:
|
19
|
+
# gem: sensu-plugin
|
20
|
+
# gem: net-https
|
21
|
+
#
|
22
|
+
# USAGE:
|
23
|
+
# Check that will warn 1 week prior and critical 3 days prior
|
24
|
+
# ./check-https-cert.rb -u https://my.site.com -w 7 -c 3
|
25
|
+
#
|
26
|
+
# Check an insecure certificate that will warn 1 week prior and critical 3 days prior
|
27
|
+
# ./check-https-cert.rb -u https://my.site.com -k -w 7 -c 3
|
28
|
+
#
|
29
|
+
# Check that a certificate has successfully expired
|
30
|
+
# ./check-https-cert.rb -u https://my.site.com -k -e
|
31
|
+
#
|
32
|
+
# NOTES:
|
33
|
+
#
|
34
|
+
# LICENSE:
|
35
|
+
# Copyright 2014 Rhommel Lamas <roml@rhommell.com>
|
36
|
+
# Updated 2017 Phil Porada <philporada@gmail.com>
|
37
|
+
# - Provide more clear usage documentation and messages
|
38
|
+
# - Added check for successfully expired certificate
|
39
|
+
# - Added long flags
|
40
|
+
# Released under the same terms as Sensu (the MIT license); see LICENSE
|
41
|
+
# for details.
|
42
|
+
#
|
43
|
+
|
44
|
+
require 'sensu-plugin/check/cli'
|
45
|
+
require 'net/https'
|
46
|
+
|
47
|
+
#
|
48
|
+
# Check HTTP
|
49
|
+
#
|
50
|
+
class CheckHttpCert < Sensu::Plugin::Check::CLI
|
51
|
+
option :url,
|
52
|
+
short: '-u URL',
|
53
|
+
long: '--url URL',
|
54
|
+
proc: proc(&:to_s),
|
55
|
+
description: 'The URL to connect to'
|
56
|
+
|
57
|
+
option :warning,
|
58
|
+
short: '-w',
|
59
|
+
long: '--warning DAYS',
|
60
|
+
proc: proc(&:to_i),
|
61
|
+
default: 50,
|
62
|
+
description: 'Warn EXPIRE days before cert expires'
|
63
|
+
|
64
|
+
option :critical,
|
65
|
+
short: '-c',
|
66
|
+
long: '--critical DAYS',
|
67
|
+
proc: proc(&:to_i),
|
68
|
+
default: 25,
|
69
|
+
description: 'Critical EXPIRE days before cert expires'
|
70
|
+
|
71
|
+
option :expired,
|
72
|
+
short: '-e',
|
73
|
+
long: '--expired',
|
74
|
+
boolean: true,
|
75
|
+
description: 'Expect certificate to be expired',
|
76
|
+
default: false
|
77
|
+
|
78
|
+
option :insecure,
|
79
|
+
short: '-k',
|
80
|
+
long: '--insecure',
|
81
|
+
boolean: true,
|
82
|
+
description: 'Enabling insecure connections',
|
83
|
+
default: false
|
84
|
+
|
85
|
+
def run
|
86
|
+
uri = URI.parse(config[:url])
|
87
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
88
|
+
http.use_ssl = true
|
89
|
+
http.verify_mode = if config[:insecure]
|
90
|
+
OpenSSL::SSL::VERIFY_NONE
|
91
|
+
else
|
92
|
+
OpenSSL::SSL::VERIFY_PEER
|
93
|
+
end
|
94
|
+
|
95
|
+
http.start do |h|
|
96
|
+
@cert = h.peer_cert
|
97
|
+
end
|
98
|
+
days_until = ((@cert.not_after - Time.now) / (60 * 60 * 24)).to_i
|
99
|
+
|
100
|
+
if config[:expired]
|
101
|
+
if days_until >= 0
|
102
|
+
critical "TLS/SSL certificate expires on #{@cert.not_after} - #{days_until} days left."
|
103
|
+
else
|
104
|
+
ok "TLS/SSL certificate expired #{days_until.abs} days ago."
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
unless config[:expired]
|
109
|
+
if days_until <= 0
|
110
|
+
critical "TLS/SSL certificate expired #{days_until.abs} days ago."
|
111
|
+
elsif days_until < config[:critical].to_i
|
112
|
+
critical "TLS/SSL certificate expires on #{@cert.not_after} - #{days_until} days left."
|
113
|
+
elsif days_until < config[:warning].to_i
|
114
|
+
warning "TLS/SSL certificate expires on #{@cert.not_after} - #{days_until} days left."
|
115
|
+
else
|
116
|
+
ok "TLS/SSL certificate expires on #{@cert.not_after} - #{days_until} days left."
|
117
|
+
end
|
118
|
+
end
|
119
|
+
rescue StandardError
|
120
|
+
critical "Could not connect to #{config[:url]}"
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: false
|
3
|
+
|
4
|
+
#
|
5
|
+
# check-fleet-units
|
6
|
+
#
|
7
|
+
# DESCRIPTION:
|
8
|
+
#
|
9
|
+
# OUTPUT:
|
10
|
+
# plain text
|
11
|
+
#
|
12
|
+
# PLATFORMS:
|
13
|
+
# Linux
|
14
|
+
#
|
15
|
+
# DEPENDENCIES:
|
16
|
+
# gem: sensu-plugin
|
17
|
+
#
|
18
|
+
# USAGE:
|
19
|
+
#
|
20
|
+
# NOTES:
|
21
|
+
#
|
22
|
+
# LICENSE:
|
23
|
+
# Barry Martin <nyxcharon@gmail.com>
|
24
|
+
# Released under the same terms as Sensu (the MIT license); see LICENSE
|
25
|
+
# for details.
|
26
|
+
#
|
27
|
+
require 'sensu-plugin/check/cli'
|
28
|
+
require 'net/https'
|
29
|
+
require 'time'
|
30
|
+
require 'aws-sdk'
|
31
|
+
require 'json'
|
32
|
+
require 'sensu-plugins-http'
|
33
|
+
|
34
|
+
#
|
35
|
+
# Checks the last modified time of a file to verify it has been updated with a
|
36
|
+
# specified threshold.
|
37
|
+
#
|
38
|
+
class CheckLastModified < Sensu::Plugin::Check::CLI
|
39
|
+
include Common
|
40
|
+
option :aws_access_key_id,
|
41
|
+
short: '-a AWS_ACCESS_KEY_ID',
|
42
|
+
long: '--aws-access-key-id AWS_ACCESS_KEY_ID',
|
43
|
+
description: 'AWS Access Key. Either set ENV["AWS_ACCESS_KEY_ID"] or provide it as an option',
|
44
|
+
default: ENV['AWS_ACCESS_KEY_ID']
|
45
|
+
|
46
|
+
option :aws_secret_access_key,
|
47
|
+
short: '-k AWS_SECRET_KEY',
|
48
|
+
long: '--aws-secret-access-key AWS_SECRET_ACCESS_KEY',
|
49
|
+
description: 'AWS Secret Access Key. Either set ENV["AWS_SECRET_ACCESS_KEY"] or provide it as an option',
|
50
|
+
default: ENV['AWS_SECRET_ACCESS_KEY']
|
51
|
+
|
52
|
+
option :aws_region,
|
53
|
+
short: '-r AWS_REGION',
|
54
|
+
long: '--aws-region REGION',
|
55
|
+
description: 'AWS Region (defaults to us-east-1).',
|
56
|
+
default: 'us-east-1'
|
57
|
+
|
58
|
+
option :s3_config_bucket,
|
59
|
+
short: '-s S3_CONFIG_BUCKET',
|
60
|
+
long: '--s3-config-bucket S3_CONFIG_BUCKET',
|
61
|
+
description: 'S3 config bucket'
|
62
|
+
|
63
|
+
option :s3_config_key,
|
64
|
+
short: '-k S3_CONFIG_KEY',
|
65
|
+
long: '--s3-config-key S3_CONFIG_KEY',
|
66
|
+
description: 'S3 config key'
|
67
|
+
|
68
|
+
option :url,
|
69
|
+
short: '-u URL',
|
70
|
+
long: '--url URL',
|
71
|
+
description: 'The URL of the file to be checked'
|
72
|
+
|
73
|
+
option :user,
|
74
|
+
short: '-U USER',
|
75
|
+
long: '--username USER',
|
76
|
+
description: 'A username to connect as'
|
77
|
+
|
78
|
+
option :password,
|
79
|
+
short: '-a PASS',
|
80
|
+
long: '--password PASS',
|
81
|
+
description: 'A password to use for the username'
|
82
|
+
|
83
|
+
option :threshold,
|
84
|
+
short: '-t TIME',
|
85
|
+
long: '--time TIME',
|
86
|
+
description: 'The time in seconds the file should be updated by'
|
87
|
+
|
88
|
+
option :follow_redirects,
|
89
|
+
short: '-R FOLLOW_REDIRECTS',
|
90
|
+
long: '--redirect FOLLOW_REDIRECTS',
|
91
|
+
proc: proc(&:to_i),
|
92
|
+
default: 0,
|
93
|
+
description: 'Follow first <N> redirects'
|
94
|
+
|
95
|
+
option :follow_redirects_with_get,
|
96
|
+
short: '-g GET_REDIRECTS',
|
97
|
+
long: '--get-redirects GET_REDIRECTS',
|
98
|
+
proc: proc(&:to_i),
|
99
|
+
default: 0,
|
100
|
+
description: 'Follow first <N> redirects with GET requests'
|
101
|
+
|
102
|
+
option :auth_first_only,
|
103
|
+
short: '-A',
|
104
|
+
long: '--auth-first-only',
|
105
|
+
default: true,
|
106
|
+
description: 'Use basic auth on first request only'
|
107
|
+
|
108
|
+
def follow_uri(uri, total_redirects, get_redirects, auth_count)
|
109
|
+
location = URI(uri)
|
110
|
+
http = Net::HTTP.new(location.host, location.port)
|
111
|
+
|
112
|
+
if location.port == 443
|
113
|
+
http.use_ssl = true
|
114
|
+
end
|
115
|
+
|
116
|
+
request = if get_redirects > 0
|
117
|
+
Net::HTTP::Get.new(location.request_uri)
|
118
|
+
else
|
119
|
+
Net::HTTP::Head.new(location.request_uri)
|
120
|
+
end
|
121
|
+
|
122
|
+
if auth_count > 0 && config[:user] && config[:password] && total_redirects == config[:follow_redirects]
|
123
|
+
http.use_ssl = true
|
124
|
+
request.basic_auth(config[:user], config[:password])
|
125
|
+
auth_count -= 1
|
126
|
+
end
|
127
|
+
|
128
|
+
response = http.request(request)
|
129
|
+
|
130
|
+
if total_redirects > 0
|
131
|
+
case response
|
132
|
+
when Net::HTTPSuccess then response
|
133
|
+
when Net::HTTPRedirection then follow_uri(response['location'], total_redirects - 1, get_redirects - 1, auth_count)
|
134
|
+
else
|
135
|
+
critical 'Http Error'
|
136
|
+
end
|
137
|
+
else
|
138
|
+
case response
|
139
|
+
when Net::HTTPSuccess then response
|
140
|
+
else
|
141
|
+
critical 'Http Error'
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def run
|
147
|
+
merge_s3_config
|
148
|
+
|
149
|
+
url = config[:url]
|
150
|
+
threshold = config[:threshold]
|
151
|
+
|
152
|
+
# Validate arguments
|
153
|
+
unless url
|
154
|
+
unknown 'No URL specified'
|
155
|
+
end
|
156
|
+
|
157
|
+
unless threshold
|
158
|
+
unknown 'No threshold specified'
|
159
|
+
end
|
160
|
+
|
161
|
+
response = follow_uri(url, config[:follow_redirects], config[:follow_redirects_with_get], config[:auth_first_only] ? 1 : config[:follow_redirects])
|
162
|
+
|
163
|
+
# Build a request from user options and then request it
|
164
|
+
if response.header['last-modified'].nil?
|
165
|
+
critical 'Http Error'
|
166
|
+
end
|
167
|
+
|
168
|
+
# Get timestamp of file and local timestamp and compare (Both in UTC)
|
169
|
+
file_stamp = Time.parse(response.header['last-modified']).getgm
|
170
|
+
local_stamp = Time.now.getgm
|
171
|
+
|
172
|
+
if (local_stamp - file_stamp).to_i <= threshold.to_i
|
173
|
+
ok 'Last modified time OK'
|
174
|
+
else
|
175
|
+
critical 'Last modified time greater than threshold'
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
data/bin/metrics-curl.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: false
|
3
|
+
|
4
|
+
#
|
5
|
+
# metrics-curl
|
6
|
+
#
|
7
|
+
# DESCRIPTION:
|
8
|
+
# Simple wrapper around curl for getting timing stats from the various phases
|
9
|
+
# of connecting to an HTTP/HTTPS server.
|
10
|
+
#
|
11
|
+
# OUTPUT:
|
12
|
+
# metric data
|
13
|
+
#
|
14
|
+
# PLATFORMS:
|
15
|
+
# Linux
|
16
|
+
#
|
17
|
+
# DEPENDENCIES:
|
18
|
+
# gem: sensu-plugin
|
19
|
+
#
|
20
|
+
# USAGE:
|
21
|
+
# #YELLOW
|
22
|
+
#
|
23
|
+
# NOTES:
|
24
|
+
# Based on: http://dev.nuclearrooster.com/2009/12/07/quick-download-benchmarks-with-curl/
|
25
|
+
# by Nick Stielau.
|
26
|
+
#
|
27
|
+
# LICENSE:
|
28
|
+
# Copyright 2012 Joe Miller
|
29
|
+
# Released under the same terms as Sensu (the MIT license); see LICENSE
|
30
|
+
# for details.
|
31
|
+
#
|
32
|
+
require 'socket'
|
33
|
+
require 'English'
|
34
|
+
require 'sensu-plugin/metric/cli'
|
35
|
+
|
36
|
+
#
|
37
|
+
# Curl Metrics
|
38
|
+
#
|
39
|
+
class CurlMetrics < Sensu::Plugin::Metric::CLI::Graphite
|
40
|
+
option :url,
|
41
|
+
short: '-u URL',
|
42
|
+
long: '--url URL',
|
43
|
+
description: 'valid cUrl url to connect',
|
44
|
+
default: 'http://127.0.0.1:80/'
|
45
|
+
|
46
|
+
option :curl_args,
|
47
|
+
short: '-a "CURL ARGS"',
|
48
|
+
long: '--curl_args "CURL ARGS"',
|
49
|
+
description: 'Additional arguments to pass to curl',
|
50
|
+
default: ''
|
51
|
+
|
52
|
+
option :scheme,
|
53
|
+
description: 'Metric naming scheme, text to prepend to metric',
|
54
|
+
short: '-s SCHEME',
|
55
|
+
long: '--scheme SCHEME',
|
56
|
+
required: true,
|
57
|
+
default: "#{Socket.gethostname}.curl_timings"
|
58
|
+
|
59
|
+
def run
|
60
|
+
`which curl > /dev/null 2>&1`
|
61
|
+
unless $CHILD_STATUS == 0
|
62
|
+
critical 'CRITICAL: curl executable not found in PATH on this system.'\
|
63
|
+
' If curl cannot be installed, consider using metrics_libcurl.rb instead.'
|
64
|
+
end
|
65
|
+
|
66
|
+
cmd = "LC_NUMERIC=C curl --silent --output /dev/null #{config[:curl_args]} "
|
67
|
+
cmd += '-w "%{time_total},%{time_namelookup},%{time_connect},%{time_pretransfer},%{time_redirect},%{time_starttransfer},%{http_code}" '
|
68
|
+
cmd += config[:url]
|
69
|
+
output = `#{cmd}`
|
70
|
+
|
71
|
+
(time_total, time_namelookup, time_connect, time_pretransfer, time_redirect, time_starttransfer, http_code) = output.split(',')
|
72
|
+
output "#{config[:scheme]}.time_total", time_total
|
73
|
+
output "#{config[:scheme]}.time_namelookup", time_namelookup
|
74
|
+
output "#{config[:scheme]}.time_connect", time_connect
|
75
|
+
output "#{config[:scheme]}.time_pretransfer", time_pretransfer
|
76
|
+
output "#{config[:scheme]}.time_redirect", time_redirect
|
77
|
+
output "#{config[:scheme]}.time_starttransfer", time_starttransfer
|
78
|
+
output "#{config[:scheme]}.http_code", http_code
|
79
|
+
|
80
|
+
if $CHILD_STATUS.to_i == 0
|
81
|
+
ok
|
82
|
+
else
|
83
|
+
warning
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
# frozen_string_literal: false
|
3
|
+
|
4
|
+
#
|
5
|
+
# metrics-http-json-deep
|
6
|
+
#
|
7
|
+
# DESCRIPTION:
|
8
|
+
# Get metrics in json format via http/https
|
9
|
+
#
|
10
|
+
# OUTPUT:
|
11
|
+
# metric data
|
12
|
+
#
|
13
|
+
# PLATFORMS:
|
14
|
+
# Linux
|
15
|
+
#
|
16
|
+
# DEPENDENCIES:
|
17
|
+
# gem: sensu-plugin
|
18
|
+
# gem: uri
|
19
|
+
# gem: socket
|
20
|
+
# gem: oj
|
21
|
+
#
|
22
|
+
# USAGE:
|
23
|
+
#
|
24
|
+
# NOTES:
|
25
|
+
#
|
26
|
+
# LICENSE:
|
27
|
+
# Copyright 2016 Hayato Matsuura
|
28
|
+
# Released under the same terms as Sensu (the MIT license); see LICENSE
|
29
|
+
# for details.
|
30
|
+
#
|
31
|
+
|
32
|
+
require 'sensu-plugin/metric/cli'
|
33
|
+
require 'net/https'
|
34
|
+
require 'uri'
|
35
|
+
require 'socket'
|
36
|
+
require 'oj'
|
37
|
+
|
38
|
+
#
|
39
|
+
# JSON Metrics
|
40
|
+
#
|
41
|
+
class JsonDeepMetrics < Sensu::Plugin::Metric::CLI::Graphite
|
42
|
+
option :url,
|
43
|
+
short: '-u URL',
|
44
|
+
long: '--url URL',
|
45
|
+
description: 'Full URL to JSON, example: https://example.com/foo.json This ignores --hostname and --port options'
|
46
|
+
|
47
|
+
option :hostname,
|
48
|
+
short: '-h HOSTNAME',
|
49
|
+
long: '--host HOSTNAME',
|
50
|
+
description: 'App server hostname',
|
51
|
+
default: '127.0.0.1'
|
52
|
+
|
53
|
+
option :port,
|
54
|
+
short: '-P PORT',
|
55
|
+
long: '--port PORT',
|
56
|
+
description: 'App server port',
|
57
|
+
default: '80'
|
58
|
+
|
59
|
+
option :path,
|
60
|
+
short: '-p PATH',
|
61
|
+
long: '--path ROOTPATH',
|
62
|
+
description: 'Path for json',
|
63
|
+
default: 'status'
|
64
|
+
|
65
|
+
option :root,
|
66
|
+
short: '-r ROOTPATH',
|
67
|
+
long: '--rootpath ROOTPATH',
|
68
|
+
description: 'Root attribute for json',
|
69
|
+
default: 'value'
|
70
|
+
|
71
|
+
option :scheme,
|
72
|
+
description: 'Metric naming scheme, text to prepend to metric',
|
73
|
+
short: '-s SCHEME',
|
74
|
+
long: '--scheme SCHEME',
|
75
|
+
default: "#{Socket.gethostname}.json"
|
76
|
+
|
77
|
+
option :numonly,
|
78
|
+
description: 'Output numbers only',
|
79
|
+
short: '-n',
|
80
|
+
long: '--number'
|
81
|
+
|
82
|
+
option :decimal_places,
|
83
|
+
description: 'Number of decimal places to allow, to be used with --number',
|
84
|
+
short: '-f DECIMAL_PLACES',
|
85
|
+
long: '--floats DECIMAL_PLACES',
|
86
|
+
proc: proc(&:to_i),
|
87
|
+
default: 4
|
88
|
+
|
89
|
+
def deep_value(hash, scheme = '')
|
90
|
+
hash.each do |key, value|
|
91
|
+
ekey = key.gsub(/\s/, '_')
|
92
|
+
if value.is_a?(Hash)
|
93
|
+
deep_value(value, "#{scheme}.#{ekey}")
|
94
|
+
elsif config[:numonly]
|
95
|
+
output "#{scheme}.#{ekey}", value.round(config[:decimal_places]) if value.is_a?(Numeric)
|
96
|
+
else
|
97
|
+
output "#{scheme}.#{ekey}", value
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def run
|
103
|
+
found = false
|
104
|
+
attempts = 0
|
105
|
+
until found || attempts >= 10
|
106
|
+
attempts += 1
|
107
|
+
if config[:url]
|
108
|
+
uri = URI.parse(config[:url])
|
109
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
110
|
+
if uri.scheme == 'https'
|
111
|
+
http.use_ssl = true
|
112
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
113
|
+
end
|
114
|
+
request = Net::HTTP::Get.new(uri.request_uri)
|
115
|
+
response = http.request(request)
|
116
|
+
if response.code == '200'
|
117
|
+
found = true
|
118
|
+
elsif !response.header['location'].nil?
|
119
|
+
config[:url] = response.header['location']
|
120
|
+
end
|
121
|
+
else
|
122
|
+
response = Net::HTTP.start(config[:hostname], config[:port]) do |connection|
|
123
|
+
request = Net::HTTP::Get.new("/#{config[:path]}")
|
124
|
+
connection.request(request)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
metrics = Oj.load(response.body, mode: :compat)
|
130
|
+
deep_value(metrics[config[:root]], config[:scheme])
|
131
|
+
ok
|
132
|
+
end
|
133
|
+
end
|