leap_cli 1.7.4 → 1.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/leap +6 -13
- data/lib/leap/platform.rb +2 -0
- data/lib/leap_cli.rb +2 -1
- data/lib/leap_cli/bootstrap.rb +197 -0
- data/lib/leap_cli/commands/common.rb +61 -0
- data/lib/leap_cli/commands/new.rb +5 -1
- data/lib/leap_cli/commands/pre.rb +1 -66
- data/lib/leap_cli/config/environment.rb +180 -0
- data/lib/leap_cli/config/manager.rb +100 -197
- data/lib/leap_cli/config/node.rb +2 -2
- data/lib/leap_cli/config/object.rb +56 -43
- data/lib/leap_cli/config/object_list.rb +6 -3
- data/lib/leap_cli/config/provider.rb +11 -0
- data/lib/leap_cli/config/secrets.rb +14 -1
- data/lib/leap_cli/config/tag.rb +2 -2
- data/lib/leap_cli/leapfile.rb +1 -0
- data/lib/leap_cli/log.rb +1 -0
- data/lib/leap_cli/logger.rb +16 -12
- data/lib/leap_cli/markdown_document_listener.rb +3 -1
- data/lib/leap_cli/path.rb +12 -0
- data/lib/leap_cli/remote/leap_plugin.rb +9 -34
- data/lib/leap_cli/remote/puppet_plugin.rb +0 -40
- data/lib/leap_cli/remote/tasks.rb +9 -34
- data/lib/leap_cli/ssh_key.rb +5 -2
- data/lib/leap_cli/version.rb +2 -2
- metadata +5 -18
- data/lib/leap_cli/commands/ca.rb +0 -518
- data/lib/leap_cli/commands/clean.rb +0 -16
- data/lib/leap_cli/commands/compile.rb +0 -340
- data/lib/leap_cli/commands/db.rb +0 -65
- data/lib/leap_cli/commands/deploy.rb +0 -368
- data/lib/leap_cli/commands/env.rb +0 -76
- data/lib/leap_cli/commands/facts.rb +0 -100
- data/lib/leap_cli/commands/inspect.rb +0 -144
- data/lib/leap_cli/commands/list.rb +0 -132
- data/lib/leap_cli/commands/node.rb +0 -165
- data/lib/leap_cli/commands/node_init.rb +0 -169
- data/lib/leap_cli/commands/ssh.rb +0 -220
- data/lib/leap_cli/commands/test.rb +0 -74
- data/lib/leap_cli/commands/user.rb +0 -136
- data/lib/leap_cli/commands/util.rb +0 -50
- data/lib/leap_cli/commands/vagrant.rb +0 -197
data/lib/leap_cli/config/node.rb
CHANGED
@@ -11,33 +11,6 @@ require 'ya2yaml' # pure ruby yaml
|
|
11
11
|
module LeapCli
|
12
12
|
module Config
|
13
13
|
|
14
|
-
#
|
15
|
-
# A proxy for Manager that binds to a particular object
|
16
|
-
# (so that we can bind to a particular environment)
|
17
|
-
#
|
18
|
-
class ManagerBinding
|
19
|
-
def initialize(manager, object)
|
20
|
-
@manager = manager
|
21
|
-
@object = object
|
22
|
-
end
|
23
|
-
|
24
|
-
def services
|
25
|
-
@manager.env(@object.environment).services
|
26
|
-
end
|
27
|
-
|
28
|
-
def tags
|
29
|
-
@manager.env(@object.environment).tags
|
30
|
-
end
|
31
|
-
|
32
|
-
def provider
|
33
|
-
@manager.env(@object.environment).provider
|
34
|
-
end
|
35
|
-
|
36
|
-
def method_missing(*args)
|
37
|
-
@manager.send(*args)
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
14
|
#
|
42
15
|
# This class represents the configuration for a single node, service, or tag.
|
43
16
|
# Also, all the nested hashes are also of this type.
|
@@ -46,21 +19,27 @@ module LeapCli
|
|
46
19
|
#
|
47
20
|
class Object < Hash
|
48
21
|
|
22
|
+
attr_reader :env
|
49
23
|
attr_reader :node
|
50
24
|
|
51
|
-
def initialize(
|
52
|
-
|
53
|
-
|
54
|
-
@
|
55
|
-
|
56
|
-
# an object that is a node as @node equal to self, otherwise all the child objects point back to the top level node.
|
25
|
+
def initialize(environment=nil, node=nil)
|
26
|
+
raise ArgumentError unless environment.nil? || environment.is_a?(Config::Environment)
|
27
|
+
@env = environment
|
28
|
+
# an object that is a node as @node equal to self, otherwise all the
|
29
|
+
# child objects point back to the top level node.
|
57
30
|
@node = node || self
|
58
31
|
end
|
59
32
|
|
60
33
|
def manager
|
61
|
-
|
34
|
+
@env.manager
|
35
|
+
end
|
36
|
+
|
37
|
+
#
|
38
|
+
# TODO: deprecate node.global()
|
39
|
+
#
|
40
|
+
def global
|
41
|
+
@env
|
62
42
|
end
|
63
|
-
alias :global :manager
|
64
43
|
|
65
44
|
def environment=(e)
|
66
45
|
self.store('environment', e)
|
@@ -70,6 +49,11 @@ module LeapCli
|
|
70
49
|
self['environment']
|
71
50
|
end
|
72
51
|
|
52
|
+
def duplicate(env)
|
53
|
+
new_object = self.deep_dup
|
54
|
+
new_object.set_environment(env, new_object)
|
55
|
+
end
|
56
|
+
|
73
57
|
#
|
74
58
|
# export YAML
|
75
59
|
#
|
@@ -85,12 +69,23 @@ module LeapCli
|
|
85
69
|
#
|
86
70
|
# export JSON
|
87
71
|
#
|
88
|
-
def dump_json(
|
72
|
+
def dump_json(options={})
|
89
73
|
evaluate(@node)
|
90
|
-
if options
|
91
|
-
self.to_json
|
74
|
+
if options[:format] == :compact
|
75
|
+
return self.to_json
|
92
76
|
else
|
93
|
-
|
77
|
+
excluded = {}
|
78
|
+
if options[:exclude]
|
79
|
+
options[:exclude].each do |key|
|
80
|
+
excluded[key] = self[key]
|
81
|
+
self.delete(key)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
json_str = JSON.sorted_generate(self)
|
85
|
+
if excluded.any?
|
86
|
+
self.merge!(excluded)
|
87
|
+
end
|
88
|
+
return json_str
|
94
89
|
end
|
95
90
|
end
|
96
91
|
|
@@ -186,6 +181,10 @@ module LeapCli
|
|
186
181
|
mode = :subtract
|
187
182
|
old_value = self.fetch '-'+key, nil
|
188
183
|
self.delete('-'+key)
|
184
|
+
elsif self.has_key?('!'+key)
|
185
|
+
mode = :replace
|
186
|
+
old_value = self.fetch '!'+key, nil
|
187
|
+
self.delete('!'+key)
|
189
188
|
else
|
190
189
|
mode = :normal
|
191
190
|
old_value = self.fetch key, nil
|
@@ -197,9 +196,13 @@ module LeapCli
|
|
197
196
|
old_value = true if old_value == "true"
|
198
197
|
old_value = false if old_value == "false"
|
199
198
|
|
199
|
+
# force replace?
|
200
|
+
if mode == :replace && prefer_self
|
201
|
+
value = old_value
|
202
|
+
|
200
203
|
# merge hashes
|
201
|
-
|
202
|
-
value = Config::Object.new(@
|
204
|
+
elsif old_value.is_a?(Hash) || new_value.is_a?(Hash)
|
205
|
+
value = Config::Object.new(@env, @node)
|
203
206
|
old_value.is_a?(Hash) ? value.deep_merge!(old_value) : (value[key] = old_value if !old_value.nil?)
|
204
207
|
new_value.is_a?(Hash) ? value.deep_merge!(new_value, prefer_self) : (value[key] = new_value if !new_value.nil?)
|
205
208
|
|
@@ -250,6 +253,16 @@ module LeapCli
|
|
250
253
|
self
|
251
254
|
end
|
252
255
|
|
256
|
+
def set_environment(env, node)
|
257
|
+
@env = env
|
258
|
+
@node = node
|
259
|
+
self.each do |key, value|
|
260
|
+
if value.is_a?(Config::Object)
|
261
|
+
value.set_environment(env, node)
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
253
266
|
#
|
254
267
|
# like a reverse deep merge
|
255
268
|
# (self takes precedence)
|
@@ -286,7 +299,7 @@ module LeapCli
|
|
286
299
|
keys.each do |key|
|
287
300
|
obj = fetch_value(key, context)
|
288
301
|
if is_required_value_not_set?(obj)
|
289
|
-
Util::log 0, :warning, "required
|
302
|
+
Util::log 0, :warning, "required property \"#{key}\" is not set in node \"#{node.name}\"."
|
290
303
|
elsif obj.is_a? Config::Object
|
291
304
|
obj.evaluate_everything(context)
|
292
305
|
end
|
@@ -301,7 +314,7 @@ module LeapCli
|
|
301
314
|
@late_eval_list.each do |key, value|
|
302
315
|
self[key] = context.evaluate_ruby(key, value)
|
303
316
|
if is_required_value_not_set?(self[key])
|
304
|
-
Util::log 0, :warning, "required
|
317
|
+
Util::log 0, :warning, "required property \"#{key}\" is not set in node \"#{node.name}\"."
|
305
318
|
end
|
306
319
|
end
|
307
320
|
end
|
@@ -167,14 +167,17 @@ module LeapCli
|
|
167
167
|
end
|
168
168
|
|
169
169
|
#
|
170
|
-
#
|
170
|
+
# Applies inherit_from! to all objects.
|
171
171
|
#
|
172
|
-
|
172
|
+
# 'env' specifies what environment should be for
|
173
|
+
# each object in the list.
|
174
|
+
#
|
175
|
+
def inherit_from!(object_list, env)
|
173
176
|
object_list.each do |name, object|
|
174
177
|
if self[name]
|
175
178
|
self[name].inherit_from!(object)
|
176
179
|
else
|
177
|
-
self[name] = object.
|
180
|
+
self[name] = object.duplicate(env)
|
178
181
|
end
|
179
182
|
end
|
180
183
|
end
|
@@ -4,8 +4,19 @@
|
|
4
4
|
|
5
5
|
module LeapCli; module Config
|
6
6
|
class Provider < Object
|
7
|
+
attr_reader :environment
|
8
|
+
def set_env(e)
|
9
|
+
if e == 'default'
|
10
|
+
@environment = nil
|
11
|
+
else
|
12
|
+
@environment = e
|
13
|
+
end
|
14
|
+
end
|
7
15
|
def provider
|
8
16
|
self
|
9
17
|
end
|
18
|
+
def validate!
|
19
|
+
# nothing here yet :(
|
20
|
+
end
|
10
21
|
end
|
11
22
|
end; end
|
@@ -27,6 +27,15 @@ module LeapCli; module Config
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
+
# searches over all keys matching the regexp, checking to see if the value
|
31
|
+
# has been already used by any of them.
|
32
|
+
def taken?(regexp, value, environment)
|
33
|
+
self.keys.grep(regexp).each do |key|
|
34
|
+
return true if self.retrieve(key, environment) == value
|
35
|
+
end
|
36
|
+
return false
|
37
|
+
end
|
38
|
+
|
30
39
|
def set_without_block(key, value, environment)
|
31
40
|
set_with_block(key, environment) {value}
|
32
41
|
end
|
@@ -56,7 +65,11 @@ module LeapCli; module Config
|
|
56
65
|
if clean
|
57
66
|
self.each_key do |environment|
|
58
67
|
if pinned_env.nil? || pinned_env == environment
|
59
|
-
self[environment]
|
68
|
+
env = self[environment]
|
69
|
+
if env.nil?
|
70
|
+
raise StandardError.new("secrets.json file seems corrupted. No such environment '#{environment}'")
|
71
|
+
end
|
72
|
+
env.each_key do |key|
|
60
73
|
unless @discovered_keys[environment] && @discovered_keys[environment][key]
|
61
74
|
self[environment].delete(key)
|
62
75
|
end
|
data/lib/leap_cli/config/tag.rb
CHANGED
data/lib/leap_cli/leapfile.rb
CHANGED
data/lib/leap_cli/log.rb
CHANGED
@@ -83,6 +83,7 @@ module LeapCli
|
|
83
83
|
when :fatal_error then ['fatal error:', :red, :bold]
|
84
84
|
when :warning then ['warning:', :yellow, :bold]
|
85
85
|
when :info then ['info', :cyan, :bold]
|
86
|
+
when :note then ['NOTE:', :cyan, :bold]
|
86
87
|
when :updated then ['updated', :cyan, :bold]
|
87
88
|
when :updating then ['updating', :cyan, :bold]
|
88
89
|
when :created then ['created', :green, :bold]
|
data/lib/leap_cli/logger.rb
CHANGED
@@ -113,7 +113,7 @@ module LeapCli
|
|
113
113
|
{ :match => /sh: .+: command not found/, :color => :magenta, :match_level => 1, :priority => -30 },
|
114
114
|
|
115
115
|
# IMPORTANT
|
116
|
-
{ :match => /^
|
116
|
+
{ :match => /^(E|e)rr ::/, :color => :red, :match_level => 0, :priority => -10, :exit => 1},
|
117
117
|
{ :match => /^ERROR:/, :color => :red, :priority => -10, :exit => 1},
|
118
118
|
{ :match => /.*/, :color => :blue, :match_level => 0, :priority => -20 },
|
119
119
|
|
@@ -129,19 +129,23 @@ module LeapCli
|
|
129
129
|
{ :match => /WARNING: The following packages cannot be authenticated!/, :color => :red, :level => 0, :priority => -10},
|
130
130
|
|
131
131
|
# PUPPET
|
132
|
-
{ :match => /^
|
133
|
-
{ :match => /^
|
134
|
-
{ :match => /^
|
135
|
-
{ :match => /^
|
136
|
-
{ :match => /^
|
137
|
-
{ :match => /^
|
138
|
-
{ :match =>
|
132
|
+
{ :match => /^(W|w)arning: Not collecting exported resources without storeconfigs/, :level => 2, :color => :yellow, :priority => -10},
|
133
|
+
{ :match => /^(W|w)arning: Found multiple default providers for vcsrepo:/, :level => 2, :color => :yellow, :priority => -10},
|
134
|
+
{ :match => /^(W|w)arning: .*is deprecated.*$/, :level => 2, :color => :yellow, :priority => -10},
|
135
|
+
{ :match => /^(W|w)arning: Scope.*$/, :level => 2, :color => :yellow, :priority => -10},
|
136
|
+
{ :match => /^(N|n)otice:/, :level => 1, :color => :cyan, :priority => -20},
|
137
|
+
{ :match => /^(N|n)otice:.*executed successfully$/, :level => 2, :color => :cyan, :priority => -15},
|
138
|
+
{ :match => /^(W|w)arning:/, :level => 0, :color => :yellow, :priority => -20},
|
139
|
+
{ :match => /^Duplicate declaration:/, :level => 0, :color => :red, :priority => -20},
|
140
|
+
{ :match => /Finished catalog run/, :level => 0, :color => :green, :priority => -10},
|
139
141
|
{ :match => /^APPLY COMPLETE \(changes made\)/, :level => 0, :color => :green, :priority => -10},
|
140
|
-
{ :match => /^APPLY COMPLETE \(no changes\)/,
|
142
|
+
{ :match => /^APPLY COMPLETE \(no changes\)/, :level => 0, :color => :green, :priority => -10},
|
141
143
|
|
142
144
|
# PUPPET FATAL ERRORS
|
143
|
-
{ :match => /^
|
145
|
+
{ :match => /^(E|e)rr(or|):/, :level => 0, :color => :red, :priority => -1, :exit => 1},
|
146
|
+
{ :match => /^Wrapped exception:/, :level => 0, :color => :red, :priority => -1, :exit => 1},
|
144
147
|
{ :match => /^Failed to parse template/, :level => 0, :color => :red, :priority => -1, :exit => 1},
|
148
|
+
{ :match => /^Execution of.*returned/, :level => 0, :color => :red, :priority => -1, :exit => 1},
|
145
149
|
{ :match => /^Parameter matches failed:/, :level => 0, :color => :red, :priority => -1, :exit => 1},
|
146
150
|
{ :match => /^Syntax error/, :level => 0, :color => :red, :priority => -1, :exit => 1},
|
147
151
|
{ :match => /^Cannot reassign variable/, :level => 0, :color => :red, :priority => -1, :exit => 1},
|
@@ -155,8 +159,8 @@ module LeapCli
|
|
155
159
|
{ :match => /\d+ tests: \d+ passes, \d+ skips, 0 warnings, 0 failures, 0 errors/, :color => :blue, :priority => -20},
|
156
160
|
|
157
161
|
# LOG SUPPRESSION
|
158
|
-
{ :match => /^
|
159
|
-
{ :match => /^
|
162
|
+
{ :match => /^(W|w)arning: You cannot collect without storeconfigs being set/, :level => 2, :priority => 10},
|
163
|
+
{ :match => /^(W|w)arning: You cannot collect exported resources without storeconfigs being set/, :level => 2, :priority => 10}
|
160
164
|
]
|
161
165
|
|
162
166
|
def self.sorted_formatters
|
@@ -29,6 +29,8 @@ module LeapCli
|
|
29
29
|
# Gives you the program description
|
30
30
|
def program_desc(desc)
|
31
31
|
@io.puts "@title = 'Command Line Reference'"
|
32
|
+
@io.puts "@summary = 'A copy of leap --help'"
|
33
|
+
|
32
34
|
#@io.puts "# #{File.basename($0)} - #{desc}"
|
33
35
|
@io.puts
|
34
36
|
end
|
@@ -98,7 +100,7 @@ module LeapCli
|
|
98
100
|
@commands.push(name)
|
99
101
|
#@io.puts "#{@nest}## Command: <tt>#{([name] + aliases).join('|')} #{@arg_name_formatter.format(arg_name,arg_options)}</tt>"
|
100
102
|
@io.puts
|
101
|
-
@io.puts "#{@nest}# #{@commands.join ' '} #{@arg_name_formatter.format(arg_name,arg_options)}"
|
103
|
+
@io.puts "#{@nest}# #{@commands.join ' '} #{@arg_name_formatter.format(arg_name, arg_options, [])}"
|
102
104
|
@io.puts
|
103
105
|
@io.puts String(desc).strip
|
104
106
|
@io.puts
|
data/lib/leap_cli/path.rb
CHANGED
@@ -99,4 +99,16 @@ module LeapCli; module Path
|
|
99
99
|
end
|
100
100
|
end
|
101
101
|
|
102
|
+
def self.vagrant_ssh_priv_key_file
|
103
|
+
File.join(LEAP_CLI_BASE_DIR, 'vendor', 'vagrant_ssh_keys', 'vagrant.key')
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.vagrant_ssh_pub_key_file
|
107
|
+
File.join(LEAP_CLI_BASE_DIR, 'vendor', 'vagrant_ssh_keys', 'vagrant.pub')
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.node_init_script
|
111
|
+
File.join(@platform, 'bin', 'node_init')
|
112
|
+
end
|
113
|
+
|
102
114
|
end; end
|
@@ -9,10 +9,6 @@ module LeapCli; module Remote; module LeapPlugin
|
|
9
9
|
"puppet rsync lsb-release locales"
|
10
10
|
end
|
11
11
|
|
12
|
-
def required_wheezy_packages
|
13
|
-
"puppet ruby-hiera-puppet rsync lsb-release locales"
|
14
|
-
end
|
15
|
-
|
16
12
|
def log(*args, &block)
|
17
13
|
LeapCli::Util::log(*args, &block)
|
18
14
|
end
|
@@ -61,15 +57,19 @@ module LeapCli; module Remote; module LeapPlugin
|
|
61
57
|
end
|
62
58
|
end
|
63
59
|
|
64
|
-
|
65
|
-
|
60
|
+
#
|
61
|
+
# dumps debugging information
|
62
|
+
# #
|
63
|
+
def debug
|
64
|
+
run "#{Leap::Platform.leap_dir}/bin/debug.sh"
|
66
65
|
end
|
67
66
|
|
68
67
|
#
|
69
68
|
# dumps the recent deploy history to the console
|
70
69
|
#
|
71
|
-
def history
|
72
|
-
|
70
|
+
def history(lines)
|
71
|
+
command = "(test -s /var/log/leap/deploy-summary.log && tail -n #{lines} /var/log/leap/deploy-summary.log) || (test -s /var/log/leap/deploy-summary.log.1 && tail -n #{lines} /var/log/leap/deploy-summary.log.1) || (echo 'no history')"
|
72
|
+
run command
|
73
73
|
end
|
74
74
|
|
75
75
|
#
|
@@ -189,29 +189,4 @@ module LeapCli; module Remote; module LeapPlugin
|
|
189
189
|
STDOUT.flush
|
190
190
|
end
|
191
191
|
|
192
|
-
|
193
|
-
# run "mkdir -p #{dir}"
|
194
|
-
#end
|
195
|
-
|
196
|
-
#def chown_root(dir)
|
197
|
-
# run "chown root -R #{dir} && chmod -R ag-rwx,u+rwX #{dir}"
|
198
|
-
#end
|
199
|
-
|
200
|
-
#def logrun(cmd)
|
201
|
-
# @streamer ||= LeapCli::Remote::LogStreamer.new
|
202
|
-
# run cmd do |channel, stream, data|
|
203
|
-
# @streamer.collect_output(channel[:host], data)
|
204
|
-
# end
|
205
|
-
#end
|
206
|
-
|
207
|
-
# return_code = nil
|
208
|
-
# run "something; echo return code: $?" do |channel, stream, data|
|
209
|
-
# if data =~ /return code: (\d+)/
|
210
|
-
# return_code = $1.to_i
|
211
|
-
# else
|
212
|
-
# Capistrano::Configuration.default_io_proc.call(channel, stream, data)
|
213
|
-
# end
|
214
|
-
# end
|
215
|
-
# puts "finished with return code: #{return_code}"
|
216
|
-
|
217
|
-
end; end; end
|
192
|
+
end; end; end
|
@@ -24,43 +24,3 @@ module LeapCli; module Remote; module PuppetPlugin
|
|
24
24
|
end
|
25
25
|
|
26
26
|
end; end; end
|
27
|
-
|
28
|
-
|
29
|
-
# def puppet(command = :noop)
|
30
|
-
# #puppet_cmd = "cd #{puppet_destination} && #{sudo_cmd} #{puppet_command} --modulepath=#{puppet_lib} #{puppet_parameters}"
|
31
|
-
# puppet_cmd = "cd #{puppet_destination} && #{sudo_cmd} #{puppet_command} #{puppet_parameters}"
|
32
|
-
# flag = command == :noop ? '--noop' : ''
|
33
|
-
|
34
|
-
# writer = if puppet_stream_output
|
35
|
-
# SupplyDrop::Writer::Streaming.new(logger)
|
36
|
-
# else
|
37
|
-
# SupplyDrop::Writer::Batched.new(logger)
|
38
|
-
# end
|
39
|
-
|
40
|
-
# writer = SupplyDrop::Writer::File.new(writer, puppet_write_to_file) unless puppet_write_to_file.nil?
|
41
|
-
|
42
|
-
# begin
|
43
|
-
# exitcode = nil
|
44
|
-
# run "#{puppet_cmd} #{flag}; echo exitcode:$?" do |channel, stream, data|
|
45
|
-
# if data =~ /exitcode:(\d+)/
|
46
|
-
# exitcode = $1
|
47
|
-
# writer.collect_output(channel[:host], "Puppet #{command} complete (#{exitcode_description(exitcode)}).\n")
|
48
|
-
# else
|
49
|
-
# writer.collect_output(channel[:host], data)
|
50
|
-
# end
|
51
|
-
# end
|
52
|
-
# ensure
|
53
|
-
# writer.all_output_collected
|
54
|
-
# end
|
55
|
-
# end
|
56
|
-
|
57
|
-
# def exitcode_description(code)
|
58
|
-
# case code
|
59
|
-
# when "0" then "no changes"
|
60
|
-
# when "2" then "changes made"
|
61
|
-
# when "4" then "failed"
|
62
|
-
# when "6" then "changes and failures"
|
63
|
-
# else code
|
64
|
-
# end
|
65
|
-
# end
|
66
|
-
|