spiceweasel 1.2.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +201 -0
- data/README.md +141 -241
- data/bin/spiceweasel +2 -104
- data/lib/spiceweasel.rb +5 -11
- data/lib/spiceweasel/cli.rb +249 -83
- data/lib/spiceweasel/clusters.rb +53 -0
- data/lib/spiceweasel/config.rb +46 -0
- data/lib/spiceweasel/cookbooks.rb +105 -0
- data/lib/spiceweasel/data_bags.rb +89 -0
- data/lib/spiceweasel/environments.rb +103 -0
- data/lib/spiceweasel/execute.rb +42 -0
- data/lib/spiceweasel/extract_local.rb +130 -0
- data/lib/spiceweasel/log.rb +30 -0
- data/lib/spiceweasel/nodes.rb +126 -0
- data/lib/spiceweasel/roles.rb +131 -0
- data/lib/spiceweasel/version.rb +1 -1
- data/spec/bin/spiceweasel_spec.rb +44 -12
- metadata +106 -25
- data/lib/spiceweasel/cookbook_data.rb +0 -66
- data/lib/spiceweasel/cookbook_list.rb +0 -96
- data/lib/spiceweasel/data_bag_list.rb +0 -81
- data/lib/spiceweasel/directory_extractor.rb +0 -121
- data/lib/spiceweasel/environment_list.rb +0 -96
- data/lib/spiceweasel/node_list.rb +0 -98
- data/lib/spiceweasel/role_list.rb +0 -124
data/bin/spiceweasel
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
#
|
4
4
|
# Author:: Matt Ray (<matt@opscode.com>)
|
5
5
|
#
|
6
|
-
# Copyright:: 2011, Opscode, Inc <legal@opscode.com>
|
6
|
+
# Copyright:: 2011-2012, Opscode, Inc <legal@opscode.com>
|
7
7
|
#
|
8
8
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
9
9
|
# you may not use this file except in compliance with the License.
|
@@ -18,108 +18,6 @@
|
|
18
18
|
# limitations under the License.
|
19
19
|
#
|
20
20
|
|
21
|
-
require 'json'
|
22
|
-
require 'yaml'
|
23
|
-
|
24
21
|
require 'spiceweasel'
|
25
22
|
|
26
|
-
|
27
|
-
|
28
|
-
#process command line options
|
29
|
-
begin
|
30
|
-
ARGV << "-h" if ARGV.empty?
|
31
|
-
cli = Spiceweasel::CLI.new
|
32
|
-
cli.parse_options
|
33
|
-
Spiceweasel::DEBUG = cli.config[:debug]
|
34
|
-
Spiceweasel::PARALLEL = cli.config[:parallel]
|
35
|
-
Spiceweasel::SITEINSTALL = cli.config[:siteinstall]
|
36
|
-
Spiceweasel::NOVALIDATION = cli.config[:novalidation]
|
37
|
-
Spiceweasel::EXTRACTLOCAL = cli.config[:extractlocal]
|
38
|
-
Spiceweasel::EXTRACTYAML = cli.config[:extractyaml]
|
39
|
-
Spiceweasel::EXTRACTJSON = cli.config[:extractjson]
|
40
|
-
rescue OptionParser::InvalidOption => e
|
41
|
-
STDERR.puts e.message
|
42
|
-
puts cli.opt_parser.to_s
|
43
|
-
exit(-1)
|
44
|
-
end
|
45
|
-
|
46
|
-
if cli.config[:knifeconfig]
|
47
|
-
options['knife_options'] += " -c " + cli.config[:knifeconfig]
|
48
|
-
end
|
49
|
-
|
50
|
-
if cli.config[:serverurl]
|
51
|
-
options['knife_options'] += " --server-url " + cli.config[:serverurl]
|
52
|
-
end
|
53
|
-
|
54
|
-
if Spiceweasel::EXTRACTLOCAL || Spiceweasel::EXTRACTJSON || Spiceweasel::EXTRACTYAML
|
55
|
-
input = Spiceweasel::DirectoryExtractor.parse_objects
|
56
|
-
STDOUT.puts "DEBUG: extract input: #{input}" if Spiceweasel::DEBUG
|
57
|
-
else
|
58
|
-
begin
|
59
|
-
file = ARGV.last
|
60
|
-
STDOUT.puts "DEBUG: file: #{file}" if Spiceweasel::DEBUG
|
61
|
-
if (file.end_with?(".yml"))
|
62
|
-
input = YAML.load_file ARGV.last
|
63
|
-
elsif (file.end_with?(".json"))
|
64
|
-
input = JSON.parse(File.read(ARGV.last))
|
65
|
-
else
|
66
|
-
STDERR.puts "ERROR: Unknown file type, please use a file ending with either '.json' or '.yml'."
|
67
|
-
exit(-1)
|
68
|
-
end
|
69
|
-
rescue Psych::SyntaxError => e
|
70
|
-
STDERR.puts e.message
|
71
|
-
STDERR.puts "ERROR: Parsing error in #{file}."
|
72
|
-
exit(-1)
|
73
|
-
rescue JSON::ParserError => e
|
74
|
-
STDERR.puts e.message
|
75
|
-
STDERR.puts "ERROR: Parsing error in #{file}."
|
76
|
-
exit(-1)
|
77
|
-
rescue Exception
|
78
|
-
STDERR.puts "ERROR: No infrastructure .json or .yml file provided."
|
79
|
-
puts cli.opt_parser.to_s
|
80
|
-
exit(-1)
|
81
|
-
end
|
82
|
-
STDOUT.puts "DEBUG: file input: #{input}" if Spiceweasel::DEBUG
|
83
|
-
end
|
84
|
-
|
85
|
-
create = String.new()
|
86
|
-
delete = String.new()
|
87
|
-
|
88
|
-
cookbook_list = Spiceweasel::CookbookList.new(input['cookbooks'], options)
|
89
|
-
environment_list = Spiceweasel::EnvironmentList.new(input['environments'], cookbook_list, options)
|
90
|
-
role_list = Spiceweasel::RoleList.new(input['roles'], environment_list, cookbook_list, options)
|
91
|
-
data_bag_list = Spiceweasel::DataBagList.new(input['data bags'], options)
|
92
|
-
node_list = Spiceweasel::NodeList.new(input['nodes'], cookbook_list, environment_list, role_list, options)
|
93
|
-
|
94
|
-
create += cookbook_list.create
|
95
|
-
create += environment_list.create
|
96
|
-
create += role_list.create
|
97
|
-
create += data_bag_list.create
|
98
|
-
create += node_list.create
|
99
|
-
|
100
|
-
delete += cookbook_list.delete
|
101
|
-
delete += environment_list.delete
|
102
|
-
delete += role_list.delete
|
103
|
-
delete += data_bag_list.delete
|
104
|
-
delete += node_list.delete
|
105
|
-
|
106
|
-
#just print the knife commands, do not execute
|
107
|
-
#if cli.config[:dryrun]
|
108
|
-
if cli.config[:delete]
|
109
|
-
puts delete unless delete.empty?
|
110
|
-
elsif cli.config[:rebuild]
|
111
|
-
puts delete unless delete.empty?
|
112
|
-
puts create unless create.empty?
|
113
|
-
else
|
114
|
-
if Spiceweasel::EXTRACTJSON
|
115
|
-
puts JSON.pretty_generate(input)
|
116
|
-
elsif Spiceweasel::EXTRACTYAML
|
117
|
-
puts input.to_yaml
|
118
|
-
else
|
119
|
-
puts create unless create.empty?
|
120
|
-
end
|
121
|
-
end
|
122
|
-
#else
|
123
|
-
#eventually we will execute instead of printing knife commands
|
124
|
-
#puts "BAM!"
|
125
|
-
#end
|
23
|
+
Spiceweasel::CLI.new.run
|
data/lib/spiceweasel.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
#
|
2
2
|
# Author:: Matt Ray (<matt@opscode.com>)
|
3
3
|
#
|
4
|
-
# Copyright:: 2011, Opscode, Inc <legal@opscode.com>
|
4
|
+
# Copyright:: 2011-2012, Opscode, Inc <legal@opscode.com>
|
5
5
|
#
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
7
|
# you may not use this file except in compliance with the License.
|
@@ -16,13 +16,7 @@
|
|
16
16
|
# limitations under the License.
|
17
17
|
#
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
autoload :RoleList, 'spiceweasel/role_list'
|
24
|
-
autoload :DataBagList, 'spiceweasel/data_bag_list'
|
25
|
-
autoload :NodeList, 'spiceweasel/node_list'
|
26
|
-
autoload :DirectoryExtractor, 'spiceweasel/directory_extractor'
|
27
|
-
autoload :CookbookData, 'spiceweasel/cookbook_data'
|
28
|
-
end
|
19
|
+
require 'spiceweasel/version'
|
20
|
+
require 'spiceweasel/cli'
|
21
|
+
require 'spiceweasel/config'
|
22
|
+
require 'spiceweasel/log'
|
data/lib/spiceweasel/cli.rb
CHANGED
@@ -1,85 +1,251 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
:long => "--extractyaml",
|
19
|
-
:description => "Use contents of local chef repository directories to generate YAML spiceweasel manifest"
|
20
|
-
|
21
|
-
option :debug,
|
22
|
-
:long => "--debug",
|
23
|
-
:description => "Verbose debugging messages",
|
24
|
-
:boolean => true
|
25
|
-
|
26
|
-
option :delete,
|
27
|
-
:short => "-d",
|
28
|
-
:long => "--delete",
|
29
|
-
:description => "Print the knife commands to delete the infrastructure",
|
30
|
-
:boolean => true
|
31
|
-
|
32
|
-
option :dryrun,
|
33
|
-
:long => "--dryrun",
|
34
|
-
:description => "Print the knife commands to be executed to STDOUT",
|
35
|
-
:boolean => true
|
36
|
-
|
37
|
-
option :help,
|
38
|
-
:short => "-h",
|
39
|
-
:long => "--help",
|
40
|
-
:description => "Show this message",
|
41
|
-
:on => :tail,
|
42
|
-
:boolean => true,
|
43
|
-
:show_options => true,
|
44
|
-
:exit => 0
|
45
|
-
|
46
|
-
option :serverurl,
|
47
|
-
:short => "-s URL",
|
48
|
-
:long => "--server-url URL",
|
49
|
-
:description => "Specify the Chef Server URL"
|
50
|
-
|
51
|
-
option :knifeconfig,
|
52
|
-
:short => "-c CONFIG",
|
53
|
-
:long => "--knifeconfig CONFIG",
|
54
|
-
:description => "Specify the knife.rb configuration file"
|
55
|
-
|
56
|
-
option :novalidation,
|
57
|
-
:long => "--novalidation",
|
58
|
-
:description => "Disable validation",
|
59
|
-
:boolean => true
|
60
|
-
|
61
|
-
option :parallel,
|
62
|
-
:long => "--parallel",
|
63
|
-
:description => "Use the GNU 'parallel' command to parallelize 'knife VENDOR server create' commands that are not order-dependent",
|
64
|
-
:boolean => true
|
65
|
-
|
66
|
-
option :rebuild,
|
67
|
-
:short => "-r",
|
68
|
-
:long => "--rebuild",
|
69
|
-
:description => "Print the knife commands to be delete and recreate the infrastructure",
|
70
|
-
:boolean => true
|
71
|
-
|
72
|
-
option :siteinstall,
|
73
|
-
:long => "--siteinstall",
|
74
|
-
:description => "Use the 'install' command with 'knife cookbook site' instead of the default 'download'",
|
75
|
-
:boolean => true
|
76
|
-
|
77
|
-
option :version,
|
78
|
-
:short => "-v",
|
79
|
-
:long => "--version",
|
80
|
-
:description => "Show spiceweasel version",
|
81
|
-
:boolean => true,
|
82
|
-
:proc => lambda {|v| puts "Spiceweasel: #{Spiceweasel::VERSION}" },
|
83
|
-
:exit => 0
|
1
|
+
#
|
2
|
+
# Author:: Matt Ray (<matt@opscode.com>)
|
3
|
+
#
|
4
|
+
# Copyright:: 2011-2012, Opscode, Inc <legal@opscode.com>
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
84
18
|
|
19
|
+
require 'mixlib/cli'
|
20
|
+
require 'json'
|
21
|
+
require 'yaml'
|
22
|
+
|
23
|
+
require 'spiceweasel'
|
24
|
+
require 'spiceweasel/cookbooks'
|
25
|
+
require 'spiceweasel/environments'
|
26
|
+
require 'spiceweasel/roles'
|
27
|
+
require 'spiceweasel/data_bags'
|
28
|
+
require 'spiceweasel/nodes'
|
29
|
+
require 'spiceweasel/clusters'
|
30
|
+
require 'spiceweasel/extract_local'
|
31
|
+
require 'spiceweasel/execute'
|
32
|
+
|
33
|
+
module Spiceweasel
|
34
|
+
class CLI
|
35
|
+
include Mixlib::CLI
|
36
|
+
|
37
|
+
banner('Usage: spiceweasel [option] file
|
38
|
+
spiceweasel [option] --extractlocal')
|
39
|
+
|
40
|
+
option :clusterfile,
|
41
|
+
:long => '--cluster-file file',
|
42
|
+
:description => 'Specify an additional cluster manifest file, overriding any other node or cluster definitions'
|
43
|
+
|
44
|
+
option :debug,
|
45
|
+
:long => '--debug',
|
46
|
+
:description => 'Verbose debugging messages',
|
47
|
+
:boolean => true
|
48
|
+
|
49
|
+
option :delete,
|
50
|
+
:short => '-d',
|
51
|
+
:long => '--delete',
|
52
|
+
:description => 'Print the knife commands to delete the infrastructure',
|
53
|
+
:boolean => true
|
54
|
+
|
55
|
+
option :execute,
|
56
|
+
:short => '-e',
|
57
|
+
:long => '--execute',
|
58
|
+
:description => 'Execute the knife commands to create the infrastructure directly',
|
59
|
+
:boolean => true
|
60
|
+
|
61
|
+
option :extractlocal,
|
62
|
+
:long => '--extractlocal',
|
63
|
+
:description => 'Use contents of local chef repository directories to generate knife commands to build infrastructure'
|
64
|
+
|
65
|
+
option :extractjson,
|
66
|
+
:long => '--extractjson',
|
67
|
+
:description => 'Use contents of local chef repository directories to generate JSON spiceweasel manifest'
|
68
|
+
|
69
|
+
option :extractyaml,
|
70
|
+
:long => '--extractyaml',
|
71
|
+
:description => 'Use contents of local chef repository directories to generate YAML spiceweasel manifest'
|
72
|
+
|
73
|
+
option :help,
|
74
|
+
:short => '-h',
|
75
|
+
:long => '--help',
|
76
|
+
:description => 'Show this message',
|
77
|
+
:on => :tail,
|
78
|
+
:boolean => true,
|
79
|
+
:show_options => true,
|
80
|
+
:exit => 0
|
81
|
+
|
82
|
+
option :knifeconfig,
|
83
|
+
:short => '-c CONFIG',
|
84
|
+
:long => '--knifeconfig CONFIG',
|
85
|
+
:description => 'Specify the knife.rb configuration file'
|
86
|
+
|
87
|
+
option :log_level,
|
88
|
+
:short => "-l LEVEL",
|
89
|
+
:long => "--log_level LEVEL",
|
90
|
+
:description => "Set the log level (debug, info, warn, error, fatal)",
|
91
|
+
:proc => lambda { |l| l.to_sym }
|
92
|
+
|
93
|
+
option :log_location,
|
94
|
+
:short => "-L LOGLOCATION",
|
95
|
+
:long => "--logfile LOGLOCATION",
|
96
|
+
:description => "Set the log file location, defaults to STDOUT",
|
97
|
+
:proc => nil
|
98
|
+
|
99
|
+
option :novalidation,
|
100
|
+
:long => '--novalidation',
|
101
|
+
:description => 'Disable validation',
|
102
|
+
:boolean => true
|
103
|
+
|
104
|
+
option :parallel,
|
105
|
+
:long => '--parallel',
|
106
|
+
:description => "Use the GNU 'parallel' command to parallelize 'knife VENDOR server create' commands where applicable",
|
107
|
+
:boolean => true
|
108
|
+
|
109
|
+
option :rebuild,
|
110
|
+
:short => '-r',
|
111
|
+
:long => '--rebuild',
|
112
|
+
:description => 'Print the knife commands to delete and recreate the infrastructure',
|
113
|
+
:boolean => true
|
114
|
+
|
115
|
+
option :serverurl,
|
116
|
+
:short => '-s URL',
|
117
|
+
:long => '--server-url URL',
|
118
|
+
:description => 'Specify the Chef Server URL'
|
119
|
+
|
120
|
+
option :siteinstall,
|
121
|
+
:long => '--siteinstall',
|
122
|
+
:description => "Use the 'install' command with 'knife cookbook site' instead of the default 'download'",
|
123
|
+
:boolean => true
|
124
|
+
|
125
|
+
option :version,
|
126
|
+
:short => '-v',
|
127
|
+
:long => '--version',
|
128
|
+
:description => 'Show spiceweasel version',
|
129
|
+
:boolean => true,
|
130
|
+
:proc => lambda { |v| puts "Spiceweasel: #{::Spiceweasel::VERSION}" },
|
131
|
+
:exit => 0
|
132
|
+
|
133
|
+
def run
|
134
|
+
if Spiceweasel::Config[:extractlocal] || Spiceweasel::Config[:extractjson] || Spiceweasel::Config[:extractyaml]
|
135
|
+
manifest = Spiceweasel::ExtractLocal.parse_objects
|
136
|
+
else
|
137
|
+
manifest = parse_and_validate_input(ARGV.last)
|
138
|
+
if Spiceweasel::Config[:clusterfile]
|
139
|
+
# if we have a cluster file, override any nodes or clusters in the original manifest
|
140
|
+
manifest['nodes'] = manifest['clusters'] = {}
|
141
|
+
manifest.merge!(parse_and_validate_input(Spiceweasel::Config[:clusterfile]))
|
142
|
+
end
|
143
|
+
end
|
144
|
+
Spiceweasel::Log.debug("file manifest: #{manifest}")
|
145
|
+
|
146
|
+
cookbooks = Cookbooks.new(manifest['cookbooks'])
|
147
|
+
environments = Environments.new(manifest['environments'], cookbooks)
|
148
|
+
roles = Roles.new(manifest['roles'], environments, cookbooks)
|
149
|
+
data_bags = DataBags.new(manifest['data bags'])
|
150
|
+
nodes = Nodes.new(manifest['nodes'], cookbooks, environments, roles)
|
151
|
+
clusters = Clusters.new(manifest['clusters'], cookbooks, environments, roles)
|
152
|
+
|
153
|
+
create = cookbooks.create + environments.create + roles.create + data_bags.create + nodes.create + clusters.create
|
154
|
+
delete = cookbooks.delete + environments.delete + roles.delete + data_bags.delete + nodes.delete + clusters.delete
|
155
|
+
|
156
|
+
#trim trailing whitespace
|
157
|
+
create.each {|x| x.rstrip!}
|
158
|
+
delete.each {|x| x.rstrip!}
|
159
|
+
|
160
|
+
if Spiceweasel::Config[:extractjson]
|
161
|
+
puts JSON.pretty_generate(manifest)
|
162
|
+
elsif Spiceweasel::Config[:extractyaml]
|
163
|
+
puts manifest.to_yaml
|
164
|
+
elsif Spiceweasel::Config[:delete]
|
165
|
+
if Spiceweasel::Config[:execute]
|
166
|
+
Execute.new(delete)
|
167
|
+
else
|
168
|
+
puts delete unless delete.empty?
|
169
|
+
end
|
170
|
+
elsif Spiceweasel::Config[:rebuild]
|
171
|
+
if Spiceweasel::Config[:execute]
|
172
|
+
Execute.new(delete)
|
173
|
+
Execute.new(create)
|
174
|
+
else
|
175
|
+
puts delete unless delete.empty?
|
176
|
+
puts create unless create.empty?
|
177
|
+
end
|
178
|
+
else
|
179
|
+
if Spiceweasel::Config[:execute]
|
180
|
+
Execute.new(create)
|
181
|
+
else
|
182
|
+
puts create unless create.empty?
|
183
|
+
end
|
184
|
+
end
|
185
|
+
exit 0
|
186
|
+
end
|
187
|
+
|
188
|
+
def initialize(argv=[])
|
189
|
+
super()
|
190
|
+
parse_and_validate_options
|
191
|
+
Config.merge!(@config)
|
192
|
+
configure_logging
|
193
|
+
Spiceweasel::Log.debug("Validation of the manifest has been turned off.") if Spiceweasel::Config[:novalidation]
|
194
|
+
end
|
195
|
+
|
196
|
+
def parse_and_validate_options
|
197
|
+
ARGV << "-h" if ARGV.empty?
|
198
|
+
begin
|
199
|
+
parse_options
|
200
|
+
if Spiceweasel::Config[:knifeconfig]
|
201
|
+
Spiceweasel::Config[:knife_options] = "-c #{Spiceweasel::Config[:knifeconfig]} "
|
202
|
+
end
|
203
|
+
if Spiceweasel::Config[:serverurl]
|
204
|
+
Spiceweasel::Config[:knife_options] += "--server-url #{Spiceweasel::Config[:serverurl]} "
|
205
|
+
end
|
206
|
+
rescue OptionParser::InvalidOption => e
|
207
|
+
STDERR.puts e.message
|
208
|
+
puts opt_parser.to_s
|
209
|
+
exit(-1)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def configure_logging
|
214
|
+
Spiceweasel::Log.init(Spiceweasel::Config[:log_location])
|
215
|
+
Spiceweasel::Log.level = Spiceweasel::Config[:log_level]
|
216
|
+
Spiceweasel::Log.level = :debug if Spiceweasel::Config[:debug]
|
217
|
+
end
|
218
|
+
|
219
|
+
def parse_and_validate_input(file)
|
220
|
+
begin
|
221
|
+
Spiceweasel::Log.debug("file: #{file}")
|
222
|
+
if !File.file?(file)
|
223
|
+
STDERR.puts "ERROR: #{file} is an invalid manifest file, please check your path."
|
224
|
+
exit(-1)
|
225
|
+
end
|
226
|
+
if (file.end_with?(".yml"))
|
227
|
+
output = YAML.load_file(file)
|
228
|
+
elsif (file.end_with?(".json"))
|
229
|
+
output = JSON.parse(File.read(file))
|
230
|
+
else
|
231
|
+
STDERR.puts "ERROR: #{file} is an unknown file type, please use a file ending with either '.json' or '.yml'."
|
232
|
+
exit(-1)
|
233
|
+
end
|
234
|
+
rescue Psych::SyntaxError => e
|
235
|
+
STDERR.puts e.message
|
236
|
+
STDERR.puts "ERROR: Parsing error in #{file}."
|
237
|
+
exit(-1)
|
238
|
+
rescue JSON::ParserError => e
|
239
|
+
STDERR.puts e.message
|
240
|
+
STDERR.puts "ERROR: Parsing error in #{file}."
|
241
|
+
exit(-1)
|
242
|
+
rescue Exception
|
243
|
+
STDERR.puts "ERROR: No manifest .json or .yml file provided."
|
244
|
+
puts opt_parser.to_s
|
245
|
+
exit(-1)
|
246
|
+
end
|
247
|
+
output
|
248
|
+
end
|
249
|
+
|
250
|
+
end
|
85
251
|
end
|