rhc 0.93.19 → 0.94.8
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.
- data/bin/rhc +53 -43
- data/bin/rhc-app +17 -14
- data/bin/rhc-chk +116 -48
- data/bin/rhc-create-app +1 -1
- data/bin/rhc-create-domain +0 -15
- data/lib/rhc-common.rb +47 -64
- data/lib/rhc-rest.rb +4 -8
- data/lib/rhc-rest/client.rb +1 -0
- data/lib/rhc-rest/exceptions/exceptions.rb +2 -2
- data/lib/rhc/client.rb +2 -0
- data/lib/rhc/config.rb +18 -9
- data/lib/rhc/json.rb +0 -1
- data/lib/rhc/targz.rb +1 -2
- data/lib/rhc/vendor/parseconfig.rb +178 -0
- data/lib/rhc/wizard.rb +12 -4
- data/spec/rhc/wizard_spec.rb +366 -145
- metadata +219 -213
data/lib/rhc-common.rb
CHANGED
@@ -1,11 +1,10 @@
|
|
1
|
-
require 'rubygems'
|
1
|
+
require 'rubygems' # Will eventually be removed when this file is deprecated
|
2
2
|
require 'fileutils'
|
3
3
|
require 'getoptlong'
|
4
4
|
require 'net/http'
|
5
5
|
require 'net/https'
|
6
6
|
require 'net/ssh'
|
7
7
|
require 'rhc/vendor/sshkey'
|
8
|
-
require 'parseconfig'
|
9
8
|
require 'resolv'
|
10
9
|
require 'uri'
|
11
10
|
require 'highline/import'
|
@@ -531,14 +530,8 @@ end
|
|
531
530
|
rescue Rhc::Rest::ResourceAccessException => e
|
532
531
|
print_response_err(Struct::FakeResponse.new(e.message,e.code))
|
533
532
|
rescue Rhc::Rest::ValidationException => e
|
534
|
-
|
535
|
-
|
536
|
-
if e.message =~ /^Failed to create application .* due to:Scalable app cannot be of type/
|
537
|
-
puts "Can not create a scaling app of type #{app_type}, either disable scaling or choose another app type"
|
538
|
-
exit 1
|
539
|
-
else
|
540
|
-
raise e
|
541
|
-
end
|
533
|
+
validation_error_code = (e.code.nil?) ? 406 : e.code
|
534
|
+
print_response_err(Struct::FakeResponse.new(e.message, validation_error_code))
|
542
535
|
end
|
543
536
|
else
|
544
537
|
json_data = generate_json(data)
|
@@ -610,7 +603,7 @@ end
|
|
610
603
|
puts <<WARNING
|
611
604
|
|
612
605
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
613
|
-
WARNING: We
|
606
|
+
WARNING: We were unable to lookup your hostname (#{fqdn})
|
614
607
|
in a reasonable amount of time. This can happen periodically and will just
|
615
608
|
take an extra minute or two to propagate depending on where you are in the
|
616
609
|
world. Once you are able to access your application in a browser, you can then
|
@@ -631,7 +624,7 @@ also try destroying and recreating the application as well using:
|
|
631
624
|
If this doesn't work for you, let us know in the forums or in IRC and we'll
|
632
625
|
make sure to get you up and running.
|
633
626
|
|
634
|
-
Forums: https://
|
627
|
+
Forums: https://openshift.redhat.com/community/forums/openshift
|
635
628
|
|
636
629
|
IRC: #openshift (on Freenode)
|
637
630
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
@@ -651,9 +644,45 @@ WARNING
|
|
651
644
|
quiet = (@mydebug ? ' ' : '--quiet ')
|
652
645
|
git_clone = %x<git clone #{quiet} #{git_url} #{repo_dir}>
|
653
646
|
if $?.exitstatus != 0
|
654
|
-
|
655
|
-
|
656
|
-
|
647
|
+
|
648
|
+
if RHC::Helpers.windows?
|
649
|
+
|
650
|
+
`nslookup #{app_name}-#{namespace}.#{rhc_domain}`
|
651
|
+
windows_nslookup = $?.exitstatus == 0
|
652
|
+
`ping #{app_name}-#{namespace}.#{rhc_domain} -n 2`
|
653
|
+
windows_ping = $?.exitstatus == 0
|
654
|
+
|
655
|
+
if windows_nslookup and !windows_ping # this is related to BZ #826769
|
656
|
+
puts <<WINSOCKISSUE
|
657
|
+
|
658
|
+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
659
|
+
WARNING: We were unable to lookup your hostname (#{fqdn})
|
660
|
+
in a reasonable amount of time. This can happen periodically and will just
|
661
|
+
take up to 10 extra minutes to propagate depending on where you are in the
|
662
|
+
world. This may also be related to an issue with Winsock on Windows [1][2].
|
663
|
+
We recommend you wait a few minutes then clone your git repository manually.
|
664
|
+
|
665
|
+
Git Clone command:
|
666
|
+
git clone #{git_url} #{repo_dir}
|
667
|
+
|
668
|
+
[1] http://support.microsoft.com/kb/299357
|
669
|
+
[2] http://support.microsoft.com/kb/811259
|
670
|
+
|
671
|
+
If this doesn't work for you, let us know in the forums or in IRC and we'll
|
672
|
+
make sure to get you up and running.
|
673
|
+
|
674
|
+
Forums: https://openshift.redhat.com/community/forums/openshift
|
675
|
+
|
676
|
+
IRC: #openshift (on Freenode)
|
677
|
+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
678
|
+
|
679
|
+
WINSOCKISSUE
|
680
|
+
exit 0
|
681
|
+
end
|
682
|
+
end
|
683
|
+
puts "Error in git clone"
|
684
|
+
puts git_clone
|
685
|
+
exit 216
|
657
686
|
end
|
658
687
|
else
|
659
688
|
if is_embedded_jenkins
|
@@ -939,15 +968,6 @@ _gem_cfg = File.join(File.expand_path(File.dirname(__FILE__) + "/../conf"), @con
|
|
939
968
|
|
940
969
|
local_config_path = File.expand_path(@local_config_path)
|
941
970
|
|
942
|
-
begin
|
943
|
-
@global_config = ParseConfig.new(@config_path)
|
944
|
-
@local_config = ParseConfig.new(File.expand_path(@local_config_path)) if \
|
945
|
-
File.exists?(@local_config_path)
|
946
|
-
rescue Errno::EACCES => e
|
947
|
-
puts "Could not open config file: #{e.message}"
|
948
|
-
exit 253
|
949
|
-
end
|
950
|
-
|
951
971
|
#
|
952
972
|
# Check for proxy environment
|
953
973
|
#
|
@@ -962,26 +982,6 @@ else
|
|
962
982
|
end
|
963
983
|
|
964
984
|
|
965
|
-
#
|
966
|
-
# Support funcs
|
967
|
-
#
|
968
|
-
def check_cpath(opts)
|
969
|
-
if !opts["config"].nil?
|
970
|
-
@opts_config_path = opts["config"]
|
971
|
-
if !File.readable?(File.expand_path(@opts_config_path))
|
972
|
-
puts "Could not open config file: #{@opts_config_path}"
|
973
|
-
exit 253
|
974
|
-
else
|
975
|
-
begin
|
976
|
-
@opts_config = ParseConfig.new(File.expand_path(@opts_config_path))
|
977
|
-
rescue Errno::EACCES => e
|
978
|
-
puts "Could not open config file (#{@opts_config_path}): #{e.message}"
|
979
|
-
exit 253
|
980
|
-
end
|
981
|
-
end
|
982
|
-
end
|
983
|
-
end
|
984
|
-
|
985
985
|
def config_path
|
986
986
|
return @opts_config_path ? @opts_config_path : @local_config_path
|
987
987
|
end
|
@@ -990,23 +990,6 @@ def config
|
|
990
990
|
return @opts_config ? @opts_config : @local_config
|
991
991
|
end
|
992
992
|
|
993
|
-
#
|
994
|
-
# Check for local var in
|
995
|
-
# 0) --config path file
|
996
|
-
# 1) ~/.openshift/express.conf
|
997
|
-
# 2) /etc/openshift/express.conf
|
998
|
-
# 3) $GEM/../conf/express.conf
|
999
|
-
#
|
1000
|
-
def get_var(var)
|
1001
|
-
v = nil
|
1002
|
-
if !@opts_config.nil? && @opts_config.get_value(var)
|
1003
|
-
v = @opts_config.get_value(var)
|
1004
|
-
else
|
1005
|
-
v = @local_config.get_value(var) ? @local_config.get_value(var) : @global_config.get_value(var)
|
1006
|
-
end
|
1007
|
-
v
|
1008
|
-
end
|
1009
|
-
|
1010
993
|
def ask_password
|
1011
994
|
return ask("Password: ") { |q| q.echo = '*' }
|
1012
995
|
end
|
@@ -1064,7 +1047,7 @@ end
|
|
1064
1047
|
def self.add_rhlogin_config(rhlogin, uuid)
|
1065
1048
|
config_path = RHC::Config.local_config_path
|
1066
1049
|
f = open(File.expand_path(config_path), 'a')
|
1067
|
-
unless RHC::Config
|
1050
|
+
unless RHC::Config['default_rhlogin']
|
1068
1051
|
f.puts("# Default rhlogin to use if none is specified")
|
1069
1052
|
f.puts("default_rhlogin=#{rhlogin}")
|
1070
1053
|
f.puts("")
|
@@ -1171,7 +1154,7 @@ def add_or_update_key(command, identifier, pub_key_file_path, rhlogin, password)
|
|
1171
1154
|
data[:action] = 'update-key'
|
1172
1155
|
end
|
1173
1156
|
|
1174
|
-
url = URI.parse("https://#{RHC::Config
|
1157
|
+
url = URI.parse("https://#{RHC::Config['libra_server']}/broker/ssh_keys")
|
1175
1158
|
handle_key_mgmt_response(url, data, password)
|
1176
1159
|
end
|
1177
1160
|
|
@@ -1241,7 +1224,7 @@ end
|
|
1241
1224
|
|
1242
1225
|
# Public: legacy convinience function for getting config keys
|
1243
1226
|
def get_var(key)
|
1244
|
-
RHC::Config
|
1227
|
+
RHC::Config[key]
|
1245
1228
|
end
|
1246
1229
|
|
1247
1230
|
# Public: convinience function for running the wizard
|
data/lib/rhc-rest.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'rubygems'
|
2
1
|
require 'rest-client'
|
3
2
|
require 'logger'
|
4
3
|
require 'rhc-rest/exceptions/exceptions'
|
@@ -8,7 +7,6 @@ require 'rhc-rest/client'
|
|
8
7
|
require 'rhc-rest/domain'
|
9
8
|
require 'rhc-rest/key'
|
10
9
|
require 'rhc-rest/user'
|
11
|
-
require 'rhc/helpers'
|
12
10
|
|
13
11
|
@@end_point = ""
|
14
12
|
@@headers = {:accept => :json}
|
@@ -123,12 +121,10 @@ module Rhc
|
|
123
121
|
#puts response
|
124
122
|
e = nil
|
125
123
|
messages.each do |message|
|
126
|
-
if message["field"]
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
e = ValidationException.new(message["text"], message["field"])
|
131
|
-
end
|
124
|
+
if e and e.field == message["field"]
|
125
|
+
e.message << " #{message["text"]}"
|
126
|
+
else
|
127
|
+
e = ValidationException.new(message["text"], message["field"], message["code"])
|
132
128
|
end
|
133
129
|
end
|
134
130
|
raise e
|
data/lib/rhc-rest/client.rb
CHANGED
@@ -43,8 +43,8 @@ module Rhc
|
|
43
43
|
#Exceptions thrown in case of an HTTP 422 is received.
|
44
44
|
class ValidationException < Rhc::Rest::ClientErrorException
|
45
45
|
attr_reader :field
|
46
|
-
def initialize(message, field=nil)
|
47
|
-
super(message)
|
46
|
+
def initialize(message, field=nil, error_code=nil)
|
47
|
+
super(message, error_code)
|
48
48
|
@field = field
|
49
49
|
end
|
50
50
|
end
|
data/lib/rhc/client.rb
CHANGED
data/lib/rhc/config.rb
CHANGED
@@ -1,14 +1,15 @@
|
|
1
|
-
require 'parseconfig'
|
1
|
+
require 'rhc/vendor/parseconfig'
|
2
|
+
require 'rhc/core_ext'
|
2
3
|
|
3
4
|
module RHC
|
4
5
|
module Config
|
5
6
|
def self.initialize
|
6
|
-
@@defaults = ParseConfig.new()
|
7
|
+
@@defaults = RHC::Vendor::ParseConfig.new()
|
7
8
|
@@global_config = nil
|
8
9
|
@@local_config = nil
|
9
10
|
@@opts_config = nil
|
10
11
|
@@default_proxy = nil
|
11
|
-
@@env_config = ParseConfig.new()
|
12
|
+
@@env_config = RHC::Vendor::ParseConfig.new()
|
12
13
|
|
13
14
|
@@defaults.add('libra_server', 'openshift.redhat.com')
|
14
15
|
@@env_config.add('libra_server', ENV['LIBRA_SERVER']) if ENV['LIBRA_SERVER']
|
@@ -27,8 +28,8 @@ module RHC
|
|
27
28
|
@@local_config_path = File.join(@@home_conf_path, @@conf_name)
|
28
29
|
|
29
30
|
begin
|
30
|
-
@@global_config = ParseConfig.new(config_path)
|
31
|
-
@@local_config = ParseConfig.new(File.expand_path(@@local_config_path)) if File.exists?(@@local_config_path)
|
31
|
+
@@global_config = RHC::Vendor::ParseConfig.new(config_path)
|
32
|
+
@@local_config = RHC::Vendor::ParseConfig.new(File.expand_path(@@local_config_path)) if File.exists?(@@local_config_path)
|
32
33
|
rescue Errno::EACCES => e
|
33
34
|
puts "Could not open config file: #{e.message}"
|
34
35
|
exit 253
|
@@ -44,18 +45,22 @@ module RHC
|
|
44
45
|
@@local_config_path = File.join(@@home_conf_path, @@conf_name)
|
45
46
|
end
|
46
47
|
|
47
|
-
def self.
|
48
|
+
def self.[](key)
|
48
49
|
# evaluate in cascading order
|
49
50
|
configs = [@@opts_config, @@env_config, @@local_config, @@global_config, @@defaults]
|
50
51
|
result = nil
|
51
52
|
configs.each do |conf|
|
52
|
-
result = conf
|
53
|
+
result = conf[key] if !conf.nil?
|
53
54
|
break if !result.nil?
|
54
55
|
end
|
55
56
|
|
56
57
|
result
|
57
58
|
end
|
58
59
|
|
60
|
+
def self.get_value(key)
|
61
|
+
self[key]
|
62
|
+
end
|
63
|
+
|
59
64
|
# Public: configures the default user for this session
|
60
65
|
def self.config_user(username)
|
61
66
|
@@defaults.add('default_rhlogin', username)
|
@@ -63,7 +68,7 @@ module RHC
|
|
63
68
|
|
64
69
|
def self.set_local_config(confpath)
|
65
70
|
begin
|
66
|
-
@@local_config = ParseConfig.new(File.expand_path(confpath))
|
71
|
+
@@local_config = RHC::Vendor::ParseConfig.new(File.expand_path(confpath))
|
67
72
|
rescue Errno::EACCES => e
|
68
73
|
puts "Could not open config file: #{e.message}"
|
69
74
|
exit 253
|
@@ -72,7 +77,7 @@ module RHC
|
|
72
77
|
|
73
78
|
def self.set_opts_config(confpath)
|
74
79
|
begin
|
75
|
-
@@opts_config = ParseConfig.new(File.expand_path(confpath))
|
80
|
+
@@opts_config = RHC::Vendor::ParseConfig.new(File.expand_path(confpath))
|
76
81
|
rescue Errno::EACCES => e
|
77
82
|
puts "Could not open config file: #{e.message}"
|
78
83
|
exit 253
|
@@ -120,6 +125,10 @@ module RHC
|
|
120
125
|
@@home_dir
|
121
126
|
end
|
122
127
|
|
128
|
+
def self.default_rhlogin
|
129
|
+
get_value('default_rhlogin')
|
130
|
+
end
|
131
|
+
|
123
132
|
def self.default_proxy
|
124
133
|
#
|
125
134
|
# Check for proxy environment
|
data/lib/rhc/json.rb
CHANGED
data/lib/rhc/targz.rb
CHANGED
@@ -0,0 +1,178 @@
|
|
1
|
+
# modified version of parseconfig rubygem module
|
2
|
+
#
|
3
|
+
# Author:: BJ Dierkes <derks@bjdierkes.com>
|
4
|
+
# Copyright:: Copyright (c) 2006,2012 BJ Dierkes
|
5
|
+
# License:: MIT
|
6
|
+
# URL:: https://github.com/derks/ruby-parseconfig
|
7
|
+
#
|
8
|
+
|
9
|
+
# This class was written to simplify the parsing of configuration
|
10
|
+
# files in the format of "param = value". Please review the
|
11
|
+
# demo files included with this package.
|
12
|
+
#
|
13
|
+
# For further information please refer to the './doc' directory
|
14
|
+
# as well as the ChangeLog and README files included.
|
15
|
+
#
|
16
|
+
|
17
|
+
# Note: A group is a set of parameters defined for a subpart of a
|
18
|
+
# config file
|
19
|
+
|
20
|
+
module RHC
|
21
|
+
module Vendor
|
22
|
+
class ParseConfig
|
23
|
+
|
24
|
+
Version = '1.0.2'
|
25
|
+
|
26
|
+
attr_accessor :config_file, :params, :groups
|
27
|
+
|
28
|
+
# Initialize the class with the path to the 'config_file'
|
29
|
+
# The class objects are dynamically generated by the
|
30
|
+
# name of the 'param' in the config file. Therefore, if
|
31
|
+
# the config file is 'param = value' then the itializer
|
32
|
+
# will eval "@param = value"
|
33
|
+
#
|
34
|
+
def initialize(config_file=nil)
|
35
|
+
@config_file = config_file
|
36
|
+
@params = {}
|
37
|
+
@groups = []
|
38
|
+
|
39
|
+
if(self.config_file)
|
40
|
+
self.validate_config()
|
41
|
+
self.import_config()
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Validate the config file, and contents
|
46
|
+
def validate_config()
|
47
|
+
if !File.readable?(self.config_file)
|
48
|
+
raise Errno::EACCES, "#{self.config_file} is not readable"
|
49
|
+
end
|
50
|
+
|
51
|
+
# FIX ME: need to validate contents/structure?
|
52
|
+
end
|
53
|
+
|
54
|
+
# Import data from the config to our config object.
|
55
|
+
def import_config()
|
56
|
+
# The config is top down.. anything after a [group] gets added as part
|
57
|
+
# of that group until a new [group] is found.
|
58
|
+
group = nil
|
59
|
+
File.open(self.config_file) { |f| f.each do |line|
|
60
|
+
line.strip!
|
61
|
+
unless (/^\#/.match(line))
|
62
|
+
if(/\s*=\s*/.match(line))
|
63
|
+
param, value = line.split(/\s*=\s*/, 2)
|
64
|
+
var_name = "#{param}".chomp.strip
|
65
|
+
value = value.chomp.strip
|
66
|
+
new_value = ''
|
67
|
+
if (value)
|
68
|
+
if value =~ /^['"](.*)['"]$/
|
69
|
+
new_value = $1
|
70
|
+
else
|
71
|
+
new_value = value
|
72
|
+
end
|
73
|
+
else
|
74
|
+
new_value = ''
|
75
|
+
end
|
76
|
+
|
77
|
+
if group
|
78
|
+
self.add_to_group(group, var_name, new_value)
|
79
|
+
else
|
80
|
+
self.add(var_name, new_value)
|
81
|
+
end
|
82
|
+
|
83
|
+
elsif(/^\[(.+)\]$/.match(line).to_a != [])
|
84
|
+
group = /^\[(.+)\]$/.match(line).to_a[1]
|
85
|
+
self.add(group, {})
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end }
|
90
|
+
end
|
91
|
+
|
92
|
+
# This method will provide the value held by the object "@param"
|
93
|
+
# where "@param" is actually the name of the param in the config
|
94
|
+
# file.
|
95
|
+
#
|
96
|
+
# DEPRECATED - will be removed in future versions
|
97
|
+
#
|
98
|
+
def get_value(param)
|
99
|
+
puts "ParseConfig Deprecation Warning: get_value() is deprecated. Use " + \
|
100
|
+
"config['param'] or config['group']['param'] instead."
|
101
|
+
return self.params[param]
|
102
|
+
end
|
103
|
+
|
104
|
+
# This method is a shortcut to accessing the @params variable
|
105
|
+
def [](param)
|
106
|
+
return self.params[param]
|
107
|
+
end
|
108
|
+
|
109
|
+
# This method returns all parameters/groups defined in a config file.
|
110
|
+
def get_params()
|
111
|
+
return self.params.keys
|
112
|
+
end
|
113
|
+
|
114
|
+
# List available sub-groups of the config.
|
115
|
+
def get_groups()
|
116
|
+
return self.groups
|
117
|
+
end
|
118
|
+
|
119
|
+
# This method adds an element to the config object (not the config file)
|
120
|
+
# By adding a Hash, you create a new group
|
121
|
+
def add(param_name, value)
|
122
|
+
if value.class == Hash
|
123
|
+
if self.params.has_key?(param_name)
|
124
|
+
if self.params[param_name].class == Hash
|
125
|
+
self.params[param_name].merge!(value)
|
126
|
+
elsif self.params.has_key?(param_name)
|
127
|
+
if self.params[param_name].class != value.class
|
128
|
+
raise ArgumentError, "#{param_name} already exists, and is of different type!"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
else
|
132
|
+
self.params[param_name] = value
|
133
|
+
end
|
134
|
+
if ! self.groups.include?(param_name)
|
135
|
+
self.groups.push(param_name)
|
136
|
+
end
|
137
|
+
else
|
138
|
+
self.params[param_name] = value
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Add parameters to a group. Note that parameters with the same name
|
143
|
+
# could be placed in different groups
|
144
|
+
def add_to_group(group, param_name, value)
|
145
|
+
if ! self.groups.include?(group)
|
146
|
+
self.add(group, {})
|
147
|
+
end
|
148
|
+
self.params[group][param_name] = value
|
149
|
+
end
|
150
|
+
|
151
|
+
# Writes out the config file to output_stream
|
152
|
+
def write(output_stream=STDOUT)
|
153
|
+
self.params.each do |name,value|
|
154
|
+
if value.class.to_s != 'Hash'
|
155
|
+
if value.scan(/\w+/).length > 1
|
156
|
+
output_stream.puts "#{name} = \"#{value}\""
|
157
|
+
else
|
158
|
+
output_stream.puts "#{name} = #{value}"
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
output_stream.puts "\n"
|
163
|
+
|
164
|
+
self.groups.each do |group|
|
165
|
+
output_stream.puts "[#{group}]"
|
166
|
+
self.params[group].each do |param, value|
|
167
|
+
if value.scan(/\w+/).length > 1
|
168
|
+
output_stream.puts "#{param} = \"#{value}\""
|
169
|
+
else
|
170
|
+
output_stream.puts "#{param} = #{value}"
|
171
|
+
end
|
172
|
+
end
|
173
|
+
output_stream.puts "\n"
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|