syncwrap 2.6.2 → 2.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/History.rdoc +44 -0
- data/Manifest.txt +2 -0
- data/README.rdoc +3 -3
- data/bin/syncwrap +1 -1
- data/lib/syncwrap/amazon_ec2.rb +1 -1
- data/lib/syncwrap/amazon_ws.rb +51 -17
- data/lib/syncwrap/base.rb +2 -2
- data/lib/syncwrap/cli.rb +1 -1
- data/lib/syncwrap/component.rb +6 -1
- data/lib/syncwrap/components/amazon_linux.rb +1 -1
- data/lib/syncwrap/components/arch.rb +48 -5
- data/lib/syncwrap/components/bundle.rb +1 -1
- data/lib/syncwrap/components/bundled_iyyov_daemon.rb +1 -1
- data/lib/syncwrap/components/bundler_gem.rb +1 -1
- data/lib/syncwrap/components/centos.rb +1 -1
- data/lib/syncwrap/components/commercial_jdk.rb +2 -1
- data/lib/syncwrap/components/cruby_vm.rb +15 -11
- data/lib/syncwrap/components/debian.rb +46 -10
- data/lib/syncwrap/components/etc_hosts.rb +1 -1
- data/lib/syncwrap/components/geminabox.rb +1 -1
- data/lib/syncwrap/components/hashdot.rb +2 -2
- data/lib/syncwrap/components/iyyov.rb +1 -1
- data/lib/syncwrap/components/iyyov_daemon.rb +1 -1
- data/lib/syncwrap/components/jruby_vm.rb +13 -7
- data/lib/syncwrap/components/lvm_cache.rb +1 -1
- data/lib/syncwrap/components/mdraid.rb +1 -1
- data/lib/syncwrap/components/network.rb +1 -1
- data/lib/syncwrap/components/open_jdk.rb +1 -1
- data/lib/syncwrap/components/postgresql.rb +24 -4
- data/lib/syncwrap/components/puma.rb +1 -1
- data/lib/syncwrap/components/qpid.rb +1 -1
- data/lib/syncwrap/components/rake_gem.rb +1 -1
- data/lib/syncwrap/components/rhel.rb +38 -17
- data/lib/syncwrap/components/run_user.rb +1 -1
- data/lib/syncwrap/components/source_tree.rb +1 -1
- data/lib/syncwrap/components/tarpit_gem.rb +1 -1
- data/lib/syncwrap/components/ubuntu.rb +1 -1
- data/lib/syncwrap/components/users.rb +10 -1
- data/lib/syncwrap/context.rb +11 -1
- data/lib/syncwrap/distro.rb +10 -5
- data/lib/syncwrap/formatter.rb +1 -1
- data/lib/syncwrap/git_help.rb +1 -1
- data/lib/syncwrap/hash_support.rb +1 -1
- data/lib/syncwrap/host.rb +1 -1
- data/lib/syncwrap/main.rb +1 -1
- data/lib/syncwrap/path_util.rb +1 -1
- data/lib/syncwrap/rsync.rb +8 -17
- data/lib/syncwrap/ruby_support.rb +1 -1
- data/lib/syncwrap/shell.rb +1 -1
- data/lib/syncwrap/systemd.rb +1 -1
- data/lib/syncwrap/user_data.rb +1 -1
- data/lib/syncwrap/version_support.rb +1 -1
- data/lib/syncwrap/zone_balancer.rb +65 -0
- data/lib/syncwrap.rb +19 -6
- data/sync/postgresql/postgresql.conf.erb +27 -5
- data/test/setup.rb +1 -1
- data/test/test_components.rb +3 -1
- data/test/test_context.rb +1 -1
- data/test/test_context_rput.rb +1 -1
- data/test/test_rsync.rb +1 -1
- data/test/test_shell.rb +1 -1
- data/test/test_space.rb +1 -1
- data/test/test_space_main.rb +9 -2
- data/test/test_version_support.rb +1 -1
- data/test/test_zone_balancer.rb +48 -0
- metadata +4 -2
@@ -1,5 +1,5 @@
|
|
1
1
|
#--
|
2
|
-
# Copyright (c) 2011-
|
2
|
+
# Copyright (c) 2011-2016 David Kellum
|
3
3
|
#
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License"); you
|
5
5
|
# may not use this file except in compliance with the License. You may
|
@@ -40,6 +40,7 @@ module SyncWrap
|
|
40
40
|
# * RHEL, CentOS 7: 9.2
|
41
41
|
# * AmazonLinux 2013.03: 8.4 9.2
|
42
42
|
# * AmazonLinux 2014.09: 8.4 9.2 9.3
|
43
|
+
# * AmazonLinux 2015.09: 9.2 9.3 9.4
|
43
44
|
# * Debian 7: 9.1
|
44
45
|
# * Debian 8: 9.4
|
45
46
|
# * Ubuntu 14: 9.3
|
@@ -150,8 +151,25 @@ module SyncWrap
|
|
150
151
|
# increased risk. (PG Default: 0 -> none)
|
151
152
|
attr_accessor :commit_delay
|
152
153
|
|
153
|
-
# WAL log segments (16MB each)
|
154
|
-
|
154
|
+
# WAL log segments (16MB each)
|
155
|
+
# Deprecated with PostgreSQL 9.5: Use min/max_wal_size instead
|
156
|
+
attr_writer :checkpoint_segments
|
157
|
+
|
158
|
+
def checkpoint_segments
|
159
|
+
@checkpoint_segments || ( version_lt?(pg_version, [9,5]) ? 3 : 5 )
|
160
|
+
end
|
161
|
+
|
162
|
+
# Minimum WAL size as string with units
|
163
|
+
# Default: PG <9.5: "48MB"; PG 9.5+: "80MB"
|
164
|
+
attr_writer :min_wal_size
|
165
|
+
|
166
|
+
def min_wal_size
|
167
|
+
@min_wal_size || "#{ checkpoint_segments * 16 }MB"
|
168
|
+
end
|
169
|
+
|
170
|
+
# Maximum WAL size as string with units.
|
171
|
+
# (Default: unset, PG Default: '1GB')
|
172
|
+
attr_accessor :max_wal_size
|
155
173
|
|
156
174
|
# Shared buffers (Default: '256MB' vs PG: '128MB')
|
157
175
|
attr_accessor :shared_buffers
|
@@ -231,7 +249,9 @@ module SyncWrap
|
|
231
249
|
@service_name = 'postgresql'
|
232
250
|
@synchronous_commit = :on
|
233
251
|
@commit_delay = 0
|
234
|
-
@checkpoint_segments =
|
252
|
+
@checkpoint_segments = nil
|
253
|
+
@min_wal_size = nil
|
254
|
+
@max_wal_size = nil
|
235
255
|
@shared_buffers = '256MB'
|
236
256
|
@work_mem = '128MB'
|
237
257
|
@maintenance_work_mem = '128MB'
|
@@ -1,5 +1,5 @@
|
|
1
1
|
#--
|
2
|
-
# Copyright (c) 2011-
|
2
|
+
# Copyright (c) 2011-2016 David Kellum
|
3
3
|
#
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License"); you
|
5
5
|
# may not use this file except in compliance with the License. You may
|
@@ -45,17 +45,21 @@ module SyncWrap
|
|
45
45
|
# interpreted as options, see below.
|
46
46
|
#
|
47
47
|
# ==== Options
|
48
|
-
# :succeed:: Always succeed (useful for local rpm files which
|
49
|
-
# might already be installed.)
|
50
48
|
#
|
51
|
-
#
|
49
|
+
# :check_install:: Short-circuit if all packages already
|
50
|
+
# installed. Thus no upgrades will be performed.
|
51
|
+
#
|
52
|
+
# :succeed:: Deprecated, use check_install instead
|
53
|
+
#
|
54
|
+
# Additional options are passed to the sudo calls.
|
52
55
|
def dist_install( *pkgs )
|
53
|
-
opts = pkgs.last.is_a?( Hash ) && pkgs.pop || {}
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
56
|
+
opts = pkgs.last.is_a?( Hash ) && pkgs.pop.dup || {}
|
57
|
+
opts.delete( :minimal )
|
58
|
+
pkgs.flatten!
|
59
|
+
chk = opts.delete( :check_install ) || opts.delete( :succeed )
|
60
|
+
chk = check_install? if chk.nil?
|
61
|
+
dist_if_not_installed?( pkgs, chk, opts ) do
|
62
|
+
sudo( "yum install -q -y #{pkgs.join( ' ' )}", opts )
|
59
63
|
end
|
60
64
|
end
|
61
65
|
|
@@ -63,19 +67,36 @@ module SyncWrap
|
|
63
67
|
# interpreted as options, see below.
|
64
68
|
#
|
65
69
|
# ==== Options
|
66
|
-
# :succeed:: Succeed even if no such packages are installed
|
67
70
|
#
|
68
|
-
#
|
71
|
+
# :succeed:: Succeed even if none of the packages are
|
72
|
+
# installed. (Deprecated, Default: true)
|
73
|
+
#
|
74
|
+
# Additional options are passed to the sudo calls.
|
69
75
|
def dist_uninstall( *pkgs )
|
70
|
-
opts = pkgs.last.is_a?( Hash ) && pkgs.pop || {}
|
71
|
-
|
72
|
-
|
73
|
-
|
76
|
+
opts = pkgs.last.is_a?( Hash ) && pkgs.pop.dup || {}
|
77
|
+
pkgs.flatten!
|
78
|
+
if opts.delete( :succeed ) != false
|
79
|
+
sudo( <<-SH, opts )
|
80
|
+
if yum list -C -q installed #{pkgs.join( ' ' )} >/dev/null 2>&1; then
|
74
81
|
yum remove -q -y #{pkgs.join( ' ' )}
|
75
82
|
fi
|
76
83
|
SH
|
77
84
|
else
|
78
|
-
sudo "yum remove -q -y #{pkgs.join( ' ' )}"
|
85
|
+
sudo( "yum remove -q -y #{pkgs.join( ' ' )}", opts )
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# If chk is true, then wrap block in a sudo bash conditional
|
90
|
+
# testing if any specified pkgs are not installed. Otherwise just
|
91
|
+
# yield to block.
|
92
|
+
def dist_if_not_installed?( pkgs, chk, opts, &block )
|
93
|
+
if chk
|
94
|
+
qry = "yum list -C -q installed #{pkgs.join ' '}"
|
95
|
+
cnt = qry + " | tail -n +2 | wc -l"
|
96
|
+
cond = %Q{if [ "$(#{cnt})" != "#{pkgs.count}" ]; then}
|
97
|
+
sudo( cond, opts.merge( close: 'fi' ), &block )
|
98
|
+
else
|
99
|
+
block.call
|
79
100
|
end
|
80
101
|
end
|
81
102
|
|
@@ -1,5 +1,5 @@
|
|
1
1
|
#--
|
2
|
-
# Copyright (c) 2011-
|
2
|
+
# Copyright (c) 2011-2016 David Kellum
|
3
3
|
#
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License"); you
|
5
5
|
# may not use this file except in compliance with the License. You may
|
@@ -99,6 +99,15 @@ module SyncWrap
|
|
99
99
|
set_sudoers( u )
|
100
100
|
end
|
101
101
|
|
102
|
+
# Some distro's, like Debian, don't come with rsync installed so
|
103
|
+
# need to install it here. For backward compatibly, only do
|
104
|
+
# this if dist_install is defined (i.e. Distro component before
|
105
|
+
# self.)
|
106
|
+
if !users.empty? && respond_to?( :dist_install )
|
107
|
+
dist_install( 'rsync',
|
108
|
+
ssh_flags.merge( minimal: true, check_install: true ) )
|
109
|
+
end
|
110
|
+
|
102
111
|
users.each do |u|
|
103
112
|
sync_home_files( u )
|
104
113
|
end
|
data/lib/syncwrap/context.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
#--
|
2
|
-
# Copyright (c) 2011-
|
2
|
+
# Copyright (c) 2011-2016 David Kellum
|
3
3
|
#
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License"); you
|
5
5
|
# may not use this file except in compliance with the License. You
|
@@ -82,6 +82,12 @@ module SyncWrap
|
|
82
82
|
@default_options[ :verbose ]
|
83
83
|
end
|
84
84
|
|
85
|
+
# Return any value of :check_install set in constructed default
|
86
|
+
# options.
|
87
|
+
def check_install?
|
88
|
+
@default_options[ :check_install ]
|
89
|
+
end
|
90
|
+
|
85
91
|
# See Component#sh for interface details
|
86
92
|
def sh( command, opts = {} )
|
87
93
|
opts = @default_options.merge( opts )
|
@@ -150,6 +156,10 @@ module SyncWrap
|
|
150
156
|
if maybes.empty?
|
151
157
|
changes = rsync( plains, target, opts ) unless plains.empty?
|
152
158
|
else
|
159
|
+
if ssh_host_name == 'localhost' && opts[ :user ]
|
160
|
+
# tmpdir needs to be visable to alt. opts[ :user ]
|
161
|
+
opts[ :tmpdir_mode ] = 0755
|
162
|
+
end
|
153
163
|
process_templates( maybes, opts ) do |processed|
|
154
164
|
unless processed.empty? || plains.empty?
|
155
165
|
opts = opts.dup
|
data/lib/syncwrap/distro.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
#--
|
2
|
-
# Copyright (c) 2011-
|
2
|
+
# Copyright (c) 2011-2016 David Kellum
|
3
3
|
#
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License"); you
|
5
5
|
# may not use this file except in compliance with the License. You may
|
@@ -41,15 +41,20 @@ module SyncWrap
|
|
41
41
|
#
|
42
42
|
# ==== Options
|
43
43
|
#
|
44
|
-
# :
|
45
|
-
#
|
44
|
+
# :check_install:: Short-circuit if all packages already
|
45
|
+
# installed. Thus no upgrades will be performed.
|
46
46
|
#
|
47
|
-
# :
|
47
|
+
# :succeed:: Deprecated, use check_install instead
|
48
|
+
#
|
49
|
+
# :minimal:: Avoid additional "optional" packages when possible
|
50
|
+
#
|
51
|
+
# Additional options are passed to the sudo calls.
|
48
52
|
def dist_install( *pkgs )
|
49
53
|
raise "Include a distro-specific component, e.g. Debian, RHEL"
|
50
54
|
end
|
51
55
|
|
52
|
-
# Uninstall the specified package names.
|
56
|
+
# Uninstall the specified package names. A trailing hash is
|
57
|
+
# interpreted as options, passed to the sudo calls.
|
53
58
|
def dist_uninstall( *pkgs )
|
54
59
|
raise "Include a distro-specific component, e.g. Debian, RHEL"
|
55
60
|
end
|
data/lib/syncwrap/formatter.rb
CHANGED
data/lib/syncwrap/git_help.rb
CHANGED
data/lib/syncwrap/host.rb
CHANGED
data/lib/syncwrap/main.rb
CHANGED
data/lib/syncwrap/path_util.rb
CHANGED
data/lib/syncwrap/rsync.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
#--
|
2
|
-
# Copyright (c) 2011-
|
2
|
+
# Copyright (c) 2011-2016 David Kellum
|
3
3
|
#
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License"); you
|
5
5
|
# may not use this file except in compliance with the License. You may
|
@@ -169,7 +169,7 @@ module SyncWrap
|
|
169
169
|
def process_templates( srcs, opts ) # :doc:
|
170
170
|
bnd = opts[ :erb_binding ] or raise "required :erb_binding param missing"
|
171
171
|
erb_mode = opts[ :erb_mode ] || '<>' #Trim new line on "<% ... %>\n"
|
172
|
-
mktmpdir(
|
172
|
+
mktmpdir( opts ) do |tmp_dir|
|
173
173
|
processed_sources = []
|
174
174
|
out_dir = File.join( tmp_dir, 'd' ) #for default perms
|
175
175
|
srcs.each do |src|
|
@@ -196,23 +196,14 @@ module SyncWrap
|
|
196
196
|
end
|
197
197
|
end
|
198
198
|
|
199
|
-
#
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
newdir = nil
|
204
|
-
if defined?( JRUBY_VERSION ) && JRUBY_VERSION =~ /^1.6/
|
205
|
-
old_env_tmpdir = ENV['TMPDIR']
|
206
|
-
newdir = "/tmp/syncwrap.#{ENV['USER']}"
|
207
|
-
FileUtils.mkdir_p( newdir, mode: 0700 )
|
208
|
-
ENV['TMPDIR'] = newdir
|
209
|
-
end
|
210
|
-
Dir.mktmpdir( prefix ) do |tmp_dir|
|
211
|
-
yield tmp_dir
|
199
|
+
# Like Dir.mktmpdir but with option to specify :tmpdir_mode.
|
200
|
+
def mktmpdir( opts ) # :doc:
|
201
|
+
path = Dir::Tmpname.create( 'syncwrap-' ) do |n|
|
202
|
+
Dir.mkdir( n, opts[ :tmpdir_mode ] || 0700 )
|
212
203
|
end
|
204
|
+
yield path
|
213
205
|
ensure
|
214
|
-
FileUtils.
|
215
|
-
ENV['TMPDIR'] = old_env_tmpdir if old_env_tmpdir
|
206
|
+
FileUtils.remove_entry( path ) if path
|
216
207
|
end
|
217
208
|
|
218
209
|
def find_source_erbs( sources ) # :doc:
|
data/lib/syncwrap/shell.rb
CHANGED
data/lib/syncwrap/systemd.rb
CHANGED
data/lib/syncwrap/user_data.rb
CHANGED
@@ -0,0 +1,65 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2011-2016 David Kellum
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you
|
5
|
+
# may not use this file except in compliance with the License. You may
|
6
|
+
# obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
13
|
+
# implied. See the License for the specific language governing
|
14
|
+
# permissions and limitations under the License.
|
15
|
+
#++
|
16
|
+
|
17
|
+
module SyncWrap
|
18
|
+
|
19
|
+
# Utility for balancing new hosts accross multiple (AWS)
|
20
|
+
# availability zones for fault tolarance.
|
21
|
+
module ZoneBalancer
|
22
|
+
|
23
|
+
# Returns a ruby Proc which when called will return the best
|
24
|
+
# pick of availability zone, via ::next_zone. This variant
|
25
|
+
# uses Space.current within a Space#with block.
|
26
|
+
def self.zone( zones, roles )
|
27
|
+
space = Space.current
|
28
|
+
lambda do
|
29
|
+
next_zone( space, zones, roles )
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Return the next best zone from zones Array<String>, preferring
|
34
|
+
# the least frequent :availability_zone of existing hosts in the
|
35
|
+
# specified space and roles (Array<Symbol>, if empty all hosts).
|
36
|
+
def self.next_zone( space, zones, roles = [] )
|
37
|
+
if zones
|
38
|
+
hosts = filter_hosts( space.hosts, roles )
|
39
|
+
zfreqs = {}
|
40
|
+
zones.each { |z| zfreqs[z] = 0 }
|
41
|
+
czones = hosts.map { |h| h[:availability_zone] }.compact
|
42
|
+
czones.each { |z| zfreqs[z] += 1 if zfreqs.has_key?( z ) }
|
43
|
+
|
44
|
+
# Sort by ascending frequency (lowest first). Keep order stable
|
45
|
+
# from original zones, when frequency tied.
|
46
|
+
# Return the first (least frequent, zones stable) zone.
|
47
|
+
n = 0
|
48
|
+
zfreqs.sort_by { |_,f| [ f, (n+=1) ] }.first[0]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def self.filter_hosts( hosts, roles )
|
55
|
+
unless roles.empty?
|
56
|
+
hosts = hosts.select do |h|
|
57
|
+
h.roles.any? { |r| roles.include?( r ) }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
hosts
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
data/lib/syncwrap.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
#--
|
2
|
-
# Copyright (c) 2011-
|
2
|
+
# Copyright (c) 2011-2016 David Kellum
|
3
3
|
#
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License"); you
|
5
5
|
# may not use this file except in compliance with the License. You may
|
@@ -22,6 +22,7 @@ require 'syncwrap/context'
|
|
22
22
|
require 'syncwrap/host'
|
23
23
|
require 'syncwrap/formatter'
|
24
24
|
require 'syncwrap/path_util'
|
25
|
+
require 'syncwrap/version_support.rb'
|
25
26
|
|
26
27
|
module SyncWrap
|
27
28
|
|
@@ -51,6 +52,7 @@ module SyncWrap
|
|
51
52
|
# level #execute.
|
52
53
|
class Space
|
53
54
|
include PathUtil
|
55
|
+
include VersionSupport
|
54
56
|
|
55
57
|
# Return the current space, as setup within a Space#with block, or
|
56
58
|
# raise something fierce.
|
@@ -92,11 +94,11 @@ module SyncWrap
|
|
92
94
|
def load_sync_file( filename )
|
93
95
|
require 'syncwrap/main'
|
94
96
|
with do
|
95
|
-
load( filename,
|
96
|
-
#
|
97
|
-
#
|
98
|
-
# binding scheme of components. If not done,
|
99
|
-
# methods/vars in sync.rb would have precidents over
|
97
|
+
load( filename, wrap_sync_load? )
|
98
|
+
# Should wrap to avoid pollution of sync namespace, but there
|
99
|
+
# are jruby bugs to workaround. This is particularly important
|
100
|
+
# given the dynamic binding scheme of components. If not done,
|
101
|
+
# top-level methods/vars in sync.rb would have precidents over
|
100
102
|
# component methods.
|
101
103
|
end
|
102
104
|
end
|
@@ -291,6 +293,16 @@ module SyncWrap
|
|
291
293
|
|
292
294
|
private
|
293
295
|
|
296
|
+
# Return true if wrapped load should be attempted on this ruby
|
297
|
+
# Avoid this only on JRuby 9.0.0-9.0.4, which will fail hard.
|
298
|
+
#
|
299
|
+
# See https://github.com/jruby/jruby/issues/3180
|
300
|
+
def wrap_sync_load?
|
301
|
+
( !defined?( JRUBY_VERSION ) ||
|
302
|
+
version_lt?( JRUBY_VERSION, [9] ) ||
|
303
|
+
version_gte?( JRUBY_VERSION, [9,0,5] ) )
|
304
|
+
end
|
305
|
+
|
294
306
|
def execute_host( host, component_plan = [], opts = {} )
|
295
307
|
comp_roles = opts[ :comp_roles ]
|
296
308
|
comps = if comp_roles && !comp_roles.empty?
|
@@ -413,5 +425,6 @@ module SyncWrap
|
|
413
425
|
autoload :AmazonEC2, 'syncwrap/amazon_ec2'
|
414
426
|
autoload :GitHelp, 'syncwrap/git_help'
|
415
427
|
autoload :UserData, 'syncwrap/user_data'
|
428
|
+
autoload :ZoneBalancer, 'syncwrap/zone_balancer'
|
416
429
|
|
417
430
|
end
|