beaker 1.13.1 → 1.14.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -7
- data/lib/beaker/answers.rb +3 -1
- data/lib/beaker/answers/version34.rb +12 -0
- data/lib/beaker/cli.rb +1 -0
- data/lib/beaker/dsl.rb +2 -1
- data/lib/beaker/dsl/ezbake_utils.rb +220 -0
- data/lib/beaker/dsl/helpers.rb +47 -38
- data/lib/beaker/dsl/install_utils.rb +229 -9
- data/lib/beaker/host/unix.rb +2 -2
- data/lib/beaker/host/unix/pkg.rb +8 -2
- data/lib/beaker/host/windows.rb +0 -1
- data/lib/beaker/host_prebuilt_steps.rb +1 -43
- data/lib/beaker/hypervisor.rb +0 -3
- data/lib/beaker/hypervisor/docker.rb +6 -1
- data/lib/beaker/logger.rb +22 -3
- data/lib/beaker/options/presets.rb +3 -1
- data/lib/beaker/platform.rb +38 -26
- data/lib/beaker/result.rb +3 -2
- data/lib/beaker/tasks/rake_task.rb +2 -8
- data/lib/beaker/version.rb +1 -1
- data/spec/beaker/answers_spec.rb +23 -0
- data/spec/beaker/dsl/ezbake_utils_spec.rb +239 -0
- data/spec/beaker/dsl/helpers_spec.rb +46 -36
- data/spec/beaker/dsl/install_utils_spec.rb +180 -1
- data/spec/beaker/host_prebuilt_steps_spec.rb +0 -43
- data/spec/beaker/hypervisor/docker_spec.rb +19 -4
- data/spec/beaker/platform_spec.rb +19 -1
- data/spec/helpers.rb +14 -14
- metadata +272 -151
checksums.yaml
CHANGED
@@ -1,7 +1,15 @@
|
|
1
|
-
---
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
YTAxN2IzZDYzNjNjM2E0NjhjZDVjNmE1OWQ2YmZkNWY2MDE5MGNiMg==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
OGRjYjFhOWYxNjI5MTc2NDc3NTNlZjEyNGFkMTZmYmUxZmRkZjVhZg==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
YWUwMDIzZTc4MDM5MjlhY2M4ZmVkZGE3ZjIzYWVjNzk2YmEyZjZjMjRiZjdm
|
10
|
+
YTQzMTU4Njg3ZDQzNDMxMDk1NDY3NDdjZWY1NTZjMjA4ZDA2OTk4ZWE5Yzk5
|
11
|
+
MjY0Y2JjNjcxMWZlNDZhZmZlNGVkM2FiMDBhMTIzYjY2OGZmOTE=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
NjA0MzQwMjE2YmZiZDQ1YTZhMTUwZWU5Yjk1MjFlMWNjN2EzMzg3MzJlZmQ2
|
14
|
+
OTZkMjBkYTEwM2E3MTFjMTRkMDJiMjUwMGU0ZTAxZWVhZTcwNWVjMDUwNDNh
|
15
|
+
MDE2YzNlNzQ2ZWIzMzJkMjY3MTVlYzczZWE0MjEzYTYzZjNlMTU=
|
data/lib/beaker/answers.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
[ 'version32', 'version30', 'version28', 'version20' ].each do |lib|
|
1
|
+
[ 'version34', 'version32', 'version30', 'version28', 'version20' ].each do |lib|
|
2
2
|
require "beaker/answers/#{lib}"
|
3
3
|
end
|
4
4
|
|
@@ -21,6 +21,8 @@ module Beaker
|
|
21
21
|
def self.answers(version, hosts, master_certname, options)
|
22
22
|
|
23
23
|
case version
|
24
|
+
when /\A3\.4/
|
25
|
+
Version34.answers(hosts, master_certname, options)
|
24
26
|
when /\A3\.[2-3]/
|
25
27
|
Version32.answers(hosts, master_certname, options)
|
26
28
|
when /\A3\.1/
|
data/lib/beaker/cli.rb
CHANGED
data/lib/beaker/dsl.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
[ 'install_utils', 'roles', 'outcomes', 'assertions',
|
2
|
-
'structure', 'helpers', 'wrappers' ].each do |lib|
|
2
|
+
'structure', 'helpers', 'ezbake_utils', 'wrappers' ].each do |lib|
|
3
3
|
require "beaker/dsl/#{lib}"
|
4
4
|
end
|
5
5
|
|
@@ -76,6 +76,7 @@ module Beaker
|
|
76
76
|
include Beaker::DSL::Assertions
|
77
77
|
include Beaker::DSL::Wrappers
|
78
78
|
include Beaker::DSL::Helpers
|
79
|
+
include Beaker::DSL::EZBakeUtils
|
79
80
|
include Beaker::DSL::InstallUtils
|
80
81
|
end
|
81
82
|
end
|
@@ -0,0 +1,220 @@
|
|
1
|
+
require 'beaker/dsl/install_utils'
|
2
|
+
|
3
|
+
module Beaker
|
4
|
+
module DSL
|
5
|
+
#
|
6
|
+
# This module contains methods to assist in installing projects from source
|
7
|
+
# that use ezbake for packaging.
|
8
|
+
#
|
9
|
+
# @api dsl
|
10
|
+
module EZBakeUtils
|
11
|
+
|
12
|
+
REMOTE_PACKAGES_REQUIRED = ['make']
|
13
|
+
LOCAL_COMMANDS_REQUIRED = [
|
14
|
+
['leiningen', 'lein --version', nil],
|
15
|
+
['lein-pprint', 'lein with-profile ci pprint :version',
|
16
|
+
'Must have lein-pprint installed under the :ci profile.'],
|
17
|
+
['java', 'java -version', nil],
|
18
|
+
['git', 'git --version', nil],
|
19
|
+
['rake', 'rake --version', nil],
|
20
|
+
]
|
21
|
+
class << self
|
22
|
+
attr_accessor :config
|
23
|
+
end
|
24
|
+
|
25
|
+
# Return the ezbake config.
|
26
|
+
#
|
27
|
+
def ezbake_config
|
28
|
+
EZBakeUtils.config
|
29
|
+
end
|
30
|
+
|
31
|
+
# Checks given host for the tools necessary to perform
|
32
|
+
# install_from_ezbake. If no host is given then check the local machine
|
33
|
+
# for necessary available tools. If a tool is not found, then raise
|
34
|
+
# RuntimeError.
|
35
|
+
#
|
36
|
+
def ezbake_tools_available? host = nil
|
37
|
+
if host
|
38
|
+
REMOTE_PACKAGES_REQUIRED.each do |package_name|
|
39
|
+
if not check_for_package host, package_name
|
40
|
+
raise "Required package, #{package_name}, not installed on #{host}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
else
|
44
|
+
LOCAL_COMMANDS_REQUIRED.each do |software_name, command, additional_error_message|
|
45
|
+
if not system command
|
46
|
+
error_message = "Must have #{software_name} installed on development system.\n"
|
47
|
+
if additional_error_message
|
48
|
+
error_message += additional_error_message
|
49
|
+
end
|
50
|
+
raise error_message
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Prepares a staging directory for the specified project.
|
57
|
+
#
|
58
|
+
# @param [String] project_name The name of the ezbake project being worked
|
59
|
+
# on.
|
60
|
+
# @param [String] project_version The desired version of the primary
|
61
|
+
# subproject being worked.
|
62
|
+
# @param [String] ezbake_dir The local directory where the ezbake project
|
63
|
+
# resides or should reside if it doesn't exist
|
64
|
+
# already.
|
65
|
+
#
|
66
|
+
def ezbake_stage project_name, project_version, ezbake_dir="tmp/ezbake"
|
67
|
+
ezbake_tools_available?
|
68
|
+
conditionally_clone "git@github.com:puppetlabs/ezbake.git", ezbake_dir
|
69
|
+
|
70
|
+
package_version = ''
|
71
|
+
Dir.chdir(ezbake_dir) do
|
72
|
+
`lein run -- stage #{project_name} #{project_name}-version=#{project_version}`
|
73
|
+
end
|
74
|
+
|
75
|
+
staging_dir = File.join(ezbake_dir, 'target/staging')
|
76
|
+
Dir.chdir(staging_dir) do
|
77
|
+
output = `rake package:bootstrap`
|
78
|
+
load 'ezbake.rb'
|
79
|
+
ezbake = EZBake::Config
|
80
|
+
ezbake[:package_version] = `echo -n $(rake pl:print_build_param[ref] | tail -n 1)`
|
81
|
+
EZBakeUtils.config = ezbake
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Installs ezbake dependencies on given host.
|
86
|
+
#
|
87
|
+
# @param [Host] host A single remote host on which to install the
|
88
|
+
# packaging dependencies of the ezbake project configuration currently in
|
89
|
+
# Beaker::DSL::EZBakeUtils.config
|
90
|
+
#
|
91
|
+
def install_ezbake_deps host
|
92
|
+
ezbake_tools_available? host
|
93
|
+
|
94
|
+
if not ezbake_config
|
95
|
+
ezbake_stage project_name, project_version
|
96
|
+
end
|
97
|
+
|
98
|
+
variant, version, arch, codename = host['platform'].to_array
|
99
|
+
ezbake = ezbake_config
|
100
|
+
|
101
|
+
case variant
|
102
|
+
when /^(fedora|el|centos)$/
|
103
|
+
dependency_list = ezbake[:redhat][:additional_dependencies]
|
104
|
+
dependency_list.each do |dependency|
|
105
|
+
package_name, _, package_version = dependency.split
|
106
|
+
install_package host, package_name, package_version
|
107
|
+
end
|
108
|
+
|
109
|
+
when /^(debian|ubuntu)$/
|
110
|
+
dependency_list = ezbake[:debian][:additional_dependencies]
|
111
|
+
dependency_list.each do |dependency|
|
112
|
+
package_name, _, package_version = dependency.split
|
113
|
+
if package_version
|
114
|
+
package_version = package_version.chop
|
115
|
+
end
|
116
|
+
install_package host, package_name, package_version
|
117
|
+
end
|
118
|
+
|
119
|
+
else
|
120
|
+
raise "No repository installation step for #{variant} yet..."
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
|
125
|
+
# Installs leiningen project with given name and version on remote host.
|
126
|
+
#
|
127
|
+
# @param [Host] host A single remote host on which to install the
|
128
|
+
# specified leiningen project.
|
129
|
+
# @param [String] project_name The name of the project. In ezbake context
|
130
|
+
# this is the name of both a subdirectory of the ezbake_dir/configs dir
|
131
|
+
# and the name of the .clj file in that directory which contains the
|
132
|
+
# project map used by ezbake to create the staging directory.
|
133
|
+
# @param [String] project_version The version of the project specified by
|
134
|
+
# project_name which is to be built and installed on the remote host.
|
135
|
+
# @param [String] ezbake_dir The directory to which ezbake should be
|
136
|
+
# cloned; alternatively, if ezbake is already at that directory, it will
|
137
|
+
# be updated from its github master before any ezbake operations are
|
138
|
+
# performed.
|
139
|
+
#
|
140
|
+
def install_from_ezbake host, project_name, project_version, env_args={}, ezbake_dir='tmp/ezbake'
|
141
|
+
ezbake_tools_available? host
|
142
|
+
|
143
|
+
if not ezbake_config
|
144
|
+
ezbake_stage project_name, project_version
|
145
|
+
end
|
146
|
+
|
147
|
+
variant, _, _, _ = host['platform'].to_array
|
148
|
+
|
149
|
+
case variant
|
150
|
+
when /^(osx|windows|solaris|aix)$/
|
151
|
+
raise "Beaker::DSL::EZBakeUtils unsupported platform: #{variant}"
|
152
|
+
end
|
153
|
+
|
154
|
+
ezbake = ezbake_config
|
155
|
+
project_package_version = ezbake[:package_version]
|
156
|
+
project_name = ezbake[:project]
|
157
|
+
|
158
|
+
ezbake_staging_dir = File.join(ezbake_dir, "target/staging")
|
159
|
+
|
160
|
+
remote_tarball = ""
|
161
|
+
local_tarball = ""
|
162
|
+
dir_name = ""
|
163
|
+
|
164
|
+
Dir.chdir(ezbake_staging_dir) do
|
165
|
+
output = `rake package:tar`
|
166
|
+
|
167
|
+
pattern = "%s-%s"
|
168
|
+
dir_name = pattern % [
|
169
|
+
project_name,
|
170
|
+
project_package_version
|
171
|
+
]
|
172
|
+
local_tarball = "./pkg/" + dir_name + ".tar.gz"
|
173
|
+
remote_tarball = "/root/" + dir_name + ".tar.gz"
|
174
|
+
|
175
|
+
scp_to host, local_tarball, remote_tarball
|
176
|
+
end
|
177
|
+
|
178
|
+
# untar tarball on host
|
179
|
+
on host, "tar -xzf " + remote_tarball
|
180
|
+
|
181
|
+
# "make" on target
|
182
|
+
cd_to_package_dir = "cd /root/" + dir_name + "; "
|
183
|
+
env = ""
|
184
|
+
if not env_args.empty?
|
185
|
+
env = "env " + env_args.map {|k, v| "#{k}=#{v} "}.join(' ')
|
186
|
+
end
|
187
|
+
on host, cd_to_package_dir + env + "make -e install-" + project_name
|
188
|
+
|
189
|
+
# install init scripts and default settings, perform additional preinst
|
190
|
+
# TODO: figure out a better way to install init scripts and defaults
|
191
|
+
case variant
|
192
|
+
when /^(fedora|el|centos)$/
|
193
|
+
env += "defaultsdir=/etc/sysconfig "
|
194
|
+
on host, cd_to_package_dir + env + "make -e install-rpm-sysv-init"
|
195
|
+
when /^(debian|ubuntu)$/
|
196
|
+
env += "defaultsdir=/etc/default "
|
197
|
+
on host, cd_to_package_dir + env + "make -e install-deb-sysv-init"
|
198
|
+
else
|
199
|
+
raise "No ezbake installation step for #{variant} yet..."
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
# Only clone from given git URI if there is no existing git clone at the
|
204
|
+
# given local_path location.
|
205
|
+
#
|
206
|
+
# @!visibility private
|
207
|
+
def conditionally_clone(upstream_uri, local_path)
|
208
|
+
ezbake_tools_available?
|
209
|
+
if system "git --work-tree=#{local_path} --git-dir=#{local_path}/.git status"
|
210
|
+
system "git --work-tree=#{local_path} --git-dir=#{local_path}/.git pull"
|
211
|
+
else
|
212
|
+
parent_dir = File.dirname(local_path)
|
213
|
+
FileUtils.mkdir_p(parent_dir)
|
214
|
+
system "git clone #{upstream_uri} #{local_path}"
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
data/lib/beaker/dsl/helpers.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
1
2
|
require 'resolv'
|
2
3
|
require 'inifile'
|
3
4
|
require 'timeout'
|
@@ -23,6 +24,9 @@ module Beaker
|
|
23
24
|
#
|
24
25
|
# @api dsl
|
25
26
|
module Helpers
|
27
|
+
|
28
|
+
PUPPET_MODULE_INSTALL_IGNORE = ['.git', '.idea', '.vagrant', '.vendor', 'acceptance', 'spec', 'tests', 'log']
|
29
|
+
|
26
30
|
# @!macro common_opts
|
27
31
|
# @param [Hash{Symbol=>String}] opts Options to alter execution.
|
28
32
|
# @option opts [Boolean] :silent (false) Do not produce log output
|
@@ -221,8 +225,8 @@ module Beaker
|
|
221
225
|
# @param [String] package_name Name of the package to install
|
222
226
|
#
|
223
227
|
# @return [Result] An object representing the outcome of *install command*.
|
224
|
-
def install_package host, package_name
|
225
|
-
host.install_package package_name
|
228
|
+
def install_package host, package_name, package_version = nil
|
229
|
+
host.install_package package_name, '', package_version
|
226
230
|
end
|
227
231
|
|
228
232
|
# Upgrade a package on a host. The package must already be installed
|
@@ -345,8 +349,8 @@ module Beaker
|
|
345
349
|
# @option opts [String] :source The location on the test runners box where the files are found
|
346
350
|
# @option opts [String] :module_name The name of the module to be copied over
|
347
351
|
def puppet_module_install_on(host, opts = {})
|
348
|
-
Array(host).each do |
|
349
|
-
|
352
|
+
Array(host).each do |h|
|
353
|
+
on h, puppet("module install #{opts[:module_name]}")
|
350
354
|
end
|
351
355
|
end
|
352
356
|
|
@@ -522,12 +526,15 @@ module Beaker
|
|
522
526
|
cmdline_args = conf_opts[:__commandline_args__]
|
523
527
|
conf_opts = conf_opts.reject { |k,v| k == :__commandline_args__ }
|
524
528
|
|
529
|
+
curl_retries = host['master-start-curl-retries'] || options['master-start-curl-retries']
|
530
|
+
logger.debug "Setting curl retries to #{curl_retries}"
|
531
|
+
|
525
532
|
begin
|
526
533
|
backup_file = backup_the_file(host, host['puppetpath'], testdir, 'puppet.conf')
|
527
534
|
lay_down_new_puppet_conf host, conf_opts, testdir
|
528
535
|
|
529
536
|
if host['puppetservice']
|
530
|
-
bounce_service( host, host['puppetservice'] )
|
537
|
+
bounce_service( host, host['puppetservice'], curl_retries )
|
531
538
|
else
|
532
539
|
puppet_master_started = start_puppet_from_source_on!( host, cmdline_args )
|
533
540
|
end
|
@@ -543,7 +550,7 @@ module Beaker
|
|
543
550
|
restore_puppet_conf_from_backup( host, backup_file )
|
544
551
|
|
545
552
|
if host['puppetservice']
|
546
|
-
bounce_service( host, host['puppetservice'] )
|
553
|
+
bounce_service( host, host['puppetservice'], curl_retries )
|
547
554
|
else
|
548
555
|
if puppet_master_started
|
549
556
|
stop_puppet_from_source_on( host )
|
@@ -681,14 +688,12 @@ module Beaker
|
|
681
688
|
end
|
682
689
|
|
683
690
|
# @!visibility private
|
684
|
-
def bounce_service host, service
|
691
|
+
def bounce_service host, service, curl_retries = 120
|
685
692
|
# Any reason to not
|
686
693
|
# host.exec puppet_resource( 'service', service, 'ensure=stopped' )
|
687
694
|
# host.exec puppet_resource( 'service', service, 'ensure=running' )
|
688
695
|
host.exec( Command.new( "#{host['service-prefix']}#{service} restart" ) )
|
689
|
-
|
690
|
-
curl_with_retries(" #{service} ", host, "http://localhost:8140", [0, 52], 120)
|
691
|
-
end
|
696
|
+
curl_with_retries(" #{service} ", host, "https://localhost:8140", [35, 60], curl_retries)
|
692
697
|
end
|
693
698
|
|
694
699
|
# Blocks until the port is open on the host specified, returns false
|
@@ -1135,7 +1140,7 @@ module Beaker
|
|
1135
1140
|
end
|
1136
1141
|
|
1137
1142
|
|
1138
|
-
#Install local module for acceptance testing
|
1143
|
+
# Install local module for acceptance testing
|
1139
1144
|
# should be used as a presuite to ensure local module is copied to the hosts you want, particularly masters
|
1140
1145
|
# @api dsl
|
1141
1146
|
# @param [Host, Array<Host>, String, Symbol] host
|
@@ -1148,38 +1153,26 @@ module Beaker
|
|
1148
1153
|
# Name which the module should be installed under, please do not include author,
|
1149
1154
|
# if none is provided it will attempt to parse the metadata.json and then the Modulefile to determine
|
1150
1155
|
# the name of the module
|
1151
|
-
# @option opts [String] :target_module_path (host['
|
1156
|
+
# @option opts [String] :target_module_path (host['distmoduledir']/modules)
|
1152
1157
|
# Location where the module should be installed, will default
|
1153
|
-
# to host['
|
1158
|
+
# to host['distmoduledir']/modules
|
1159
|
+
# @option opts [Array] :ignore_list
|
1154
1160
|
# @raise [ArgumentError] if not host is provided or module_name is not provided and can not be found in Modulefile
|
1155
1161
|
#
|
1156
|
-
def
|
1157
|
-
|
1158
|
-
|
1159
|
-
|
1160
|
-
|
1161
|
-
|
1162
|
-
|
1163
|
-
|
1164
|
-
|
1165
|
-
|
1166
|
-
raise(ArgumentError, "Unable to determine the module name, please update your call of puppet_module_install")
|
1167
|
-
end
|
1168
|
-
|
1169
|
-
module_dir = File.join(target_module_path, module_name)
|
1170
|
-
on host, "mkdir -p #{target_module_path}"
|
1171
|
-
['manifests', 'lib', 'templates', 'metadata.json', 'Modulefile', 'files', 'Gemfile'].each do |item|
|
1172
|
-
item_source = File.join(source, item)
|
1173
|
-
if File.exists? item_source
|
1174
|
-
options = {}
|
1175
|
-
if File.directory? item_source
|
1176
|
-
on host, "mkdir -p #{File.join(module_dir, item)}"
|
1177
|
-
options = { :mkdir => true }
|
1178
|
-
end
|
1179
|
-
host.do_scp_to(item_source, module_dir, options)
|
1180
|
-
end
|
1162
|
+
def copy_module_to(host, opts = {})
|
1163
|
+
opts = {:source => './',
|
1164
|
+
:target_module_path => host['distmoduledir'],
|
1165
|
+
:ignore_list => PUPPET_MODULE_INSTALL_IGNORE}.merge(opts)
|
1166
|
+
ignore_list = build_ignore_list(opts)
|
1167
|
+
target_module_dir = opts[:target_module_path]
|
1168
|
+
if opts.has_key?(:module_name)
|
1169
|
+
module_name = opts[:module_name]
|
1170
|
+
else
|
1171
|
+
module_name = parse_for_modulename(opts[:source])
|
1181
1172
|
end
|
1173
|
+
scp_to host, File.join(opts[:source]), File.join(target_module_dir, module_name), {:ignore => ignore_list}
|
1182
1174
|
end
|
1175
|
+
alias :copy_root_module_to :copy_module_to
|
1183
1176
|
|
1184
1177
|
|
1185
1178
|
#Recursive method for finding the module root
|
@@ -1253,6 +1246,22 @@ module Beaker
|
|
1253
1246
|
end
|
1254
1247
|
end
|
1255
1248
|
|
1249
|
+
# Build an array list of files/directories to ignore when pushing to remote host
|
1250
|
+
# Automatically adds '..' and '.' to array. If not opts of :ignore list is provided
|
1251
|
+
# it will use the static variable PUPPET_MODULE_INSTALL_IGNORE
|
1252
|
+
#
|
1253
|
+
# @param opts [Hash]
|
1254
|
+
# @option opts [Array] :ignore_list A list of files/directories to ignore
|
1255
|
+
def build_ignore_list(opts = {})
|
1256
|
+
ignore_list = opts[:ignore_list] || PUPPET_MODULE_INSTALL_IGNORE
|
1257
|
+
if !ignore_list.kind_of?(Array) || ignore_list.nil?
|
1258
|
+
raise ArgumentError "Ignore list must be an Array"
|
1259
|
+
end
|
1260
|
+
ignore_list << '.' unless ignore_list.include? '.'
|
1261
|
+
ignore_list << '..' unless ignore_list.include? '..'
|
1262
|
+
ignore_list
|
1263
|
+
end
|
1264
|
+
|
1256
1265
|
end
|
1257
1266
|
end
|
1258
1267
|
end
|