bosh-bootstrap 0.8.2 → 0.9.0
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/.gitignore +0 -1
- data/.travis.yml +24 -3
- data/ChangeLog.md +8 -0
- data/Gemfile +5 -3
- data/Guardfile +3 -3
- data/Rakefile +21 -5
- data/lib/bosh/providers/aws.rb +67 -27
- data/lib/bosh/providers/base_provider.rb +27 -0
- data/lib/bosh/providers/openstack.rb +16 -6
- data/lib/bosh-bootstrap/cli.rb +166 -61
- data/lib/bosh-bootstrap/commander/remote_script_command.rb +7 -4
- data/lib/bosh-bootstrap/commander/remote_server.rb +12 -10
- data/lib/bosh-bootstrap/stages/stage_micro_bosh_deploy/bosh_micro_deploy +5 -1
- data/lib/bosh-bootstrap/stages/stage_micro_bosh_deploy/download_micro_bosh_stemcell +12 -6
- data/lib/bosh-bootstrap/stages/stage_micro_bosh_deploy.rb +4 -1
- data/lib/bosh-bootstrap/stages/stage_prepare_inception_vm/configure_git +4 -9
- data/lib/bosh-bootstrap/stages/stage_prepare_inception_vm/create_vcap_user +1 -1
- data/lib/bosh-bootstrap/stages/stage_prepare_inception_vm/install_base_packages +12 -2
- data/lib/bosh-bootstrap/stages/stage_prepare_inception_vm/install_bosh +3 -45
- data/lib/bosh-bootstrap/stages/stage_prepare_inception_vm/install_bosh_by_gem_install +54 -0
- data/lib/bosh-bootstrap/stages/stage_prepare_inception_vm/install_hub +26 -0
- data/lib/bosh-bootstrap/stages/stage_prepare_inception_vm/install_ruby +10 -15
- data/lib/bosh-bootstrap/stages/stage_prepare_inception_vm/install_useful_gems +1 -1
- data/lib/bosh-bootstrap/stages/stage_prepare_inception_vm/validate_bosh_deployer +0 -5
- data/lib/bosh-bootstrap/stages/stage_prepare_inception_vm.rb +21 -15
- data/lib/bosh-bootstrap/stages/{stage_prepare_inception_vm → stage_salted_password}/convert_salted_password +0 -0
- data/lib/bosh-bootstrap/stages/stage_salted_password.rb +51 -0
- data/lib/bosh-bootstrap/stages/stage_setup_new_bosh.rb +0 -1
- data/lib/bosh-bootstrap/stages/stage_validate_inception_vm.rb +1 -1
- data/lib/bosh-bootstrap/stages.rb +1 -0
- data/lib/bosh-bootstrap/version.rb +1 -1
- data/spec/{functional → integration}/.gitkeep +0 -0
- data/spec/integration/aws/aws_basic_spec.rb +39 -0
- data/spec/integration/aws/aws_edge_prebuilt_ami_spec.rb +46 -0
- data/spec/integration/aws/aws_edge_prebuilt_spec.rb +46 -0
- data/spec/integration/aws/aws_edge_spec.rb +45 -0
- data/spec/integration/aws/aws_helpers.rb +79 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/unit/aws_spec.rb +28 -6
- data/spec/unit/bosh/providers/aws_spec.rb +14 -0
- data/spec/unit/cli_spec.rb +10 -8
- data/vendor/cache/POpen4-0.1.4.gem +0 -0
- data/vendor/cache/Platform-0.4.0.gem +0 -0
- data/vendor/cache/activesupport-3.2.8.gem +0 -0
- data/vendor/cache/awesome_print-1.1.0.gem +0 -0
- data/vendor/cache/aws-s3-0.6.3.gem +0 -0
- data/vendor/cache/blobstore_client-0.4.0.gem +0 -0
- data/vendor/cache/bosh_cli-1.0.3.gem +0 -0
- data/vendor/cache/bosh_common-0.5.4.gem +0 -0
- data/vendor/cache/builder-3.1.4.gem +0 -0
- data/vendor/cache/coderay-1.0.8.gem +0 -0
- data/vendor/cache/diff-lcs-1.1.3.gem +0 -0
- data/vendor/cache/escape-0.0.4.gem +0 -0
- data/vendor/cache/excon-0.17.0.gem +0 -0
- data/vendor/cache/fog-1.8.0.gem +0 -0
- data/vendor/cache/formatador-0.2.4.gem +0 -0
- data/vendor/cache/guard-1.6.2.gem +0 -0
- data/vendor/cache/guard-rspec-2.4.0.gem +0 -0
- data/vendor/cache/highline-1.6.15.gem +0 -0
- data/vendor/cache/httpclient-2.2.4.gem +0 -0
- data/vendor/cache/i18n-0.6.1.gem +0 -0
- data/vendor/cache/json_pure-1.6.8.gem +0 -0
- data/vendor/cache/listen-0.7.2.gem +0 -0
- data/vendor/cache/log4r-1.1.10.gem +0 -0
- data/vendor/cache/lumberjack-1.0.2.gem +0 -0
- data/vendor/cache/method_source-0.8.1.gem +0 -0
- data/vendor/cache/mime-types-1.21.gem +0 -0
- data/vendor/cache/multi_json-1.1.0.gem +0 -0
- data/vendor/cache/net-scp-1.0.4.gem +0 -0
- data/vendor/cache/net-ssh-2.2.2.gem +0 -0
- data/vendor/cache/net-ssh-gateway-1.1.0.gem +0 -0
- data/vendor/cache/netaddr-1.5.0.gem +0 -0
- data/vendor/cache/nokogiri-1.5.6-java.gem +0 -0
- data/vendor/cache/nokogiri-1.5.6.gem +0 -0
- data/vendor/cache/open4-1.3.0.gem +0 -0
- data/vendor/cache/progressbar-0.9.2.gem +0 -0
- data/vendor/cache/pry-0.9.11.4-java.gem +0 -0
- data/vendor/cache/pry-0.9.11.4.gem +0 -0
- data/vendor/cache/rake-10.0.3.gem +0 -0
- data/vendor/cache/rb-fsevent-0.9.3.gem +0 -0
- data/vendor/cache/redcard-1.0.0.gem +0 -0
- data/vendor/cache/rspec-2.12.0.gem +0 -0
- data/vendor/cache/rspec-core-2.12.2.gem +0 -0
- data/vendor/cache/rspec-expectations-2.12.1.gem +0 -0
- data/vendor/cache/rspec-mocks-2.12.2.gem +0 -0
- data/vendor/cache/ruby-atmos-pure-1.0.5.gem +0 -0
- data/vendor/cache/ruby-hmac-0.4.0.gem +0 -0
- data/vendor/cache/settingslogic-2.0.9.gem +0 -0
- data/vendor/cache/slop-3.4.3.gem +0 -0
- data/vendor/cache/spoon-0.0.1.gem +0 -0
- data/vendor/cache/terminal-table-1.4.5.gem +0 -0
- data/vendor/cache/thor-0.17.0.gem +0 -0
- data/vendor/cache/uuidtools-2.1.3.gem +0 -0
- data/vendor/cache/xml-simple-1.1.2.gem +0 -0
- metadata +72 -7
- data/lib/bosh-bootstrap/stages/stage_setup_new_bosh/cleanup_permissions +0 -14
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
language: ruby
|
|
2
|
+
script: bundle exec rake spec:$SUITE
|
|
3
|
+
bundler_args: "--local"
|
|
2
4
|
rvm:
|
|
3
|
-
- 1.9.3
|
|
4
|
-
- rbx-19mode
|
|
5
|
-
# - ruby-
|
|
5
|
+
- ruby-1.9.3
|
|
6
|
+
# - rbx-19mode
|
|
7
|
+
# - ruby-2.0.0 - generates "Cannot find Syck parser for YAML"
|
|
8
|
+
notifications:
|
|
9
|
+
email:
|
|
10
|
+
recipients:
|
|
11
|
+
- drnicwilliams@gmail.com
|
|
12
|
+
on_success: change
|
|
13
|
+
on_failure: always
|
|
14
|
+
env:
|
|
15
|
+
matrix:
|
|
16
|
+
- SUITE=unit
|
|
17
|
+
# - SUITE=integration:aws:basic
|
|
18
|
+
- SUITE=integration:aws:edge
|
|
19
|
+
# - SUITE=integration:aws:edge_prebuilt
|
|
20
|
+
# - SUITE=integration:aws:edge_prebuilt_ami
|
|
21
|
+
global:
|
|
22
|
+
- secure: "OS3pnXOAVRP0QPDl/Nn/0iBPZbrnn9irFYwEbMJ2fGPsV00u4IYDwoIN5Gzb\nX+cXyiPZB0jdQlbiCMaQEYjYig3fgnDhiLj/MnxkVYtILm+0uPSrX2Zf7jvR\nmxCT5jfTSZoPXQeNi9h8rEqvaqKv4z930xp/Z0QNoZLPWdwY5wk="
|
|
23
|
+
matrix:
|
|
24
|
+
allow_failures:
|
|
25
|
+
- env: SUITE=integration:aws:basic
|
|
26
|
+
|
data/ChangeLog.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
`bosh-bootstrap` is a command line tool that you can run on your laptop and automatically get a microbosh (and an inception VM) deployed on either AWS or OpenStack.
|
|
4
4
|
|
|
5
|
+
## v0.9
|
|
6
|
+
|
|
7
|
+
* v0.8 wasn't working for many people; and neither will v0.9; but its a move in the right direction.
|
|
8
|
+
* Moving towards new 1.5.0 version of bosh that hasn't come out yet formally.
|
|
9
|
+
* AWS us-east-1 will use a pre-created AMI. It saves about 10-15 minutes!
|
|
10
|
+
* AWS other regions will use a pre-created stemcell. I haven't tested this well yet.
|
|
11
|
+
* OpenStack support is still broken because you need to create your own stemcells and for that you need a 12.10 inception VM and that work isn't quite done yet.
|
|
12
|
+
|
|
5
13
|
## v0.8 (& v0.8.1)
|
|
6
14
|
|
|
7
15
|
* SSH keys used to access inception VM are now generated and stored within the `~/.bosh_bootstrap/ssh` folder. This fixes many issues that many people were having (their keys had passphrases, their fog_default keypair was old). It also allows a manifest file to be shared between people as it contains the private key contents, and the private key file will be recreated if it is missing.
|
data/Gemfile
CHANGED
|
@@ -3,6 +3,8 @@ source 'https://rubygems.org'
|
|
|
3
3
|
# Specify your gem's dependencies in bosh-bootstrap.gemspec
|
|
4
4
|
gemspec
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
gem "
|
|
8
|
-
gem "
|
|
6
|
+
group :development do
|
|
7
|
+
gem "awesome_print"
|
|
8
|
+
gem "rb-fsevent", "~> 0.9.1"
|
|
9
|
+
gem "guard-rspec"
|
|
10
|
+
end
|
data/Guardfile
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
guard 'rspec' do
|
|
2
|
-
watch(%r{^spec
|
|
1
|
+
guard 'rspec', spec_paths: ["spec/unit"] do
|
|
2
|
+
watch(%r{^spec/unit/(.+_spec)\.rb$})
|
|
3
3
|
watch(%r{^lib/bosh-bootstrap/(.+)\.rb$}) { |m| "spec/unit/#{m[1]}_spec.rb" }
|
|
4
|
-
watch('spec/spec_helper.rb') { "spec" }
|
|
4
|
+
watch('spec/spec_helper.rb') { "spec/unit" }
|
|
5
5
|
end
|
|
6
6
|
|
data/Rakefile
CHANGED
|
@@ -19,15 +19,31 @@ if defined?(RSpec)
|
|
|
19
19
|
t.rspec_opts = %w(--format progress --color)
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
namespace :integration do
|
|
23
|
+
namespace :aws do
|
|
24
|
+
jobs = Dir["spec/integration/aws/*_spec.rb"].map {|f| File.basename(f).gsub(/aws_(.*)_spec.rb/, '\1')}
|
|
25
|
+
jobs.each do |job|
|
|
26
|
+
desc "Run AWS '#{job}' Integration Test"
|
|
27
|
+
RSpec::Core::RakeTask.new(job.to_sym) do |t|
|
|
28
|
+
t.pattern = "spec/integration/aws/aws_#{job}_spec.rb"
|
|
29
|
+
t.rspec_opts = %w(--format progress --color)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
desc "Run AWS Integration Tests"
|
|
35
|
+
RSpec::Core::RakeTask.new(:aws) do |t|
|
|
36
|
+
t.pattern = "spec/integration/aws/*_spec.rb"
|
|
37
|
+
t.rspec_opts = %w(--format progress --color)
|
|
38
|
+
end
|
|
26
39
|
end
|
|
40
|
+
|
|
41
|
+
desc "Run all Integration Tests"
|
|
42
|
+
task :integration => %w[spec:integration:aws]
|
|
27
43
|
end
|
|
28
44
|
|
|
29
45
|
desc "Install dependencies and run tests"
|
|
30
|
-
task :spec => %w(spec:unit spec:
|
|
46
|
+
task :spec => %w(spec:unit spec:integration)
|
|
31
47
|
end
|
|
32
48
|
|
|
33
49
|
task :default => :spec
|
data/lib/bosh/providers/aws.rb
CHANGED
|
@@ -123,33 +123,6 @@ class Bosh::Providers::AWS < Bosh::Providers::BaseProvider
|
|
|
123
123
|
true
|
|
124
124
|
end
|
|
125
125
|
|
|
126
|
-
# Any of the following +port_defn+ can be used:
|
|
127
|
-
# {
|
|
128
|
-
# ssh: 22,
|
|
129
|
-
# http: { ports: (80..82) },
|
|
130
|
-
# mosh: { protocol: "udp", ports: (60000..60050) }
|
|
131
|
-
# mosh: { protocol: "rdp", ports: (3398..3398), ip_range: "196.212.12.34/32" }
|
|
132
|
-
# }
|
|
133
|
-
# In this example,
|
|
134
|
-
# * TCP 22 will be opened for ssh from any ip_range,
|
|
135
|
-
# * TCP ports 80, 81, 82 for http from any ip_range,
|
|
136
|
-
# * UDP 60000 -> 60050 for mosh from any ip_range and
|
|
137
|
-
# * TCP 3398 for RDP from ip range: 96.212.12.34/32
|
|
138
|
-
def extract_port_definition(port_defn)
|
|
139
|
-
protocol = "tcp"
|
|
140
|
-
ip_range = "0.0.0.0/0"
|
|
141
|
-
if port_defn.is_a? Integer
|
|
142
|
-
port_range = (port_defn..port_defn)
|
|
143
|
-
elsif port_defn.is_a? Range
|
|
144
|
-
port_range = port_defn
|
|
145
|
-
elsif port_defn.is_a? Hash
|
|
146
|
-
protocol = port_defn[:protocol] if port_defn[:protocol]
|
|
147
|
-
port_range = port_defn[:ports] if port_defn[:ports]
|
|
148
|
-
ip_range = port_defn[:ip_range] if port_defn[:ip_range]
|
|
149
|
-
end
|
|
150
|
-
[protocol, port_range, ip_range]
|
|
151
|
-
end
|
|
152
|
-
|
|
153
126
|
def port_open?(ip_permissions, port_range, protocol, ip_range)
|
|
154
127
|
ip_permissions && ip_permissions.find do |ip|
|
|
155
128
|
ip["ipProtocol"] == protocol \
|
|
@@ -181,7 +154,34 @@ class Bosh::Providers::AWS < Bosh::Providers::BaseProvider
|
|
|
181
154
|
volume.server = server
|
|
182
155
|
end
|
|
183
156
|
|
|
157
|
+
# Ubuntu 12.10 64bit (EBS) - Quantal
|
|
158
|
+
def quantal_image_id(region)
|
|
159
|
+
# http://cloud-images.ubuntu.com/quantal/current/
|
|
160
|
+
image_id = case region.to_s
|
|
161
|
+
when 'ap-northeast-1'
|
|
162
|
+
'ami-ccf270cd'
|
|
163
|
+
when 'ap-southeast-1'
|
|
164
|
+
'ami-16e8a444'
|
|
165
|
+
when 'ap-southeast-2'
|
|
166
|
+
'ami-5af36360'
|
|
167
|
+
when 'eu-west-1'
|
|
168
|
+
'ami-789c890c'
|
|
169
|
+
when 'sa-east-1'
|
|
170
|
+
'ami-35b36928'
|
|
171
|
+
when 'us-east-1'
|
|
172
|
+
'ami-1c80e475'
|
|
173
|
+
when 'us-west-1'
|
|
174
|
+
'ami-28567a6d'
|
|
175
|
+
when 'us-west-2'
|
|
176
|
+
'ami-5822b668'
|
|
177
|
+
end
|
|
178
|
+
image_id || raise("Please add Ubuntu 12.10 64bit (EBS) AMI image id to aws.rb#image_id method for region '#{region}'")
|
|
179
|
+
end
|
|
180
|
+
|
|
184
181
|
def bootstrap(new_attributes = {})
|
|
182
|
+
if new_attributes.delete(:quantal)
|
|
183
|
+
new_attributes[:image_id] ||= quantal_image_id(fog_compute.region)
|
|
184
|
+
end
|
|
185
185
|
vpc = new_attributes[:subnet_id]
|
|
186
186
|
|
|
187
187
|
server = fog_compute.servers.new(new_attributes)
|
|
@@ -215,4 +215,44 @@ class Bosh::Providers::AWS < Bosh::Providers::BaseProvider
|
|
|
215
215
|
server
|
|
216
216
|
end
|
|
217
217
|
|
|
218
|
+
def servers_with_sg(sg_name)
|
|
219
|
+
inception_sg = fog_compute.security_groups.find {|sg| sg.name == sg_name }
|
|
220
|
+
if inception_sg
|
|
221
|
+
fog_compute.servers.select {|s| s.security_group_ids.include? inception_sg.group_id }
|
|
222
|
+
else
|
|
223
|
+
$stderr.puts "no security group #{sg_name} was found"
|
|
224
|
+
[]
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def delete_security_group_and_servers(sg_name)
|
|
229
|
+
sg = fog_compute.security_groups.find {|sg| sg.name == sg_name }
|
|
230
|
+
if sg
|
|
231
|
+
fog_compute.servers.select {|s| s.security_group_ids.include? sg.group_id }.each do |server|
|
|
232
|
+
puts "Destroying server #{server.id}..."
|
|
233
|
+
server.destroy
|
|
234
|
+
end
|
|
235
|
+
begin
|
|
236
|
+
puts "Destroying security group #{sg.name}..."
|
|
237
|
+
sg.destroy
|
|
238
|
+
rescue Fog::Compute::AWS::Error => e
|
|
239
|
+
$stderr.puts e
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def delete_key_pair(kp_name)
|
|
245
|
+
if kp = fog_compute.key_pairs.find {|kp| kp.name == kp_name}
|
|
246
|
+
puts "Deleting key pair #{kp.name}..."
|
|
247
|
+
kp.destroy
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# Destroy all IP addresses that aren't bound to a server
|
|
252
|
+
def cleanup_unused_ip_addresses
|
|
253
|
+
fog_compute.addresses.each do |a|
|
|
254
|
+
puts "Deleting IP address #{a.public_ip}..."
|
|
255
|
+
a.destroy unless a.server
|
|
256
|
+
end
|
|
257
|
+
end
|
|
218
258
|
end
|
|
@@ -18,4 +18,31 @@ class Bosh::Providers::BaseProvider
|
|
|
18
18
|
fog_key_pair.destroy
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
|
+
|
|
22
|
+
# Any of the following +port_defn+ can be used:
|
|
23
|
+
# {
|
|
24
|
+
# ssh: 22,
|
|
25
|
+
# http: { ports: (80..82) },
|
|
26
|
+
# mosh: { protocol: "udp", ports: (60000..60050) }
|
|
27
|
+
# mosh: { protocol: "rdp", ports: (3398..3398), ip_range: "196.212.12.34/32" }
|
|
28
|
+
# }
|
|
29
|
+
# In this example,
|
|
30
|
+
# * TCP 22 will be opened for ssh from any ip_range,
|
|
31
|
+
# * TCP ports 80, 81, 82 for http from any ip_range,
|
|
32
|
+
# * UDP 60000 -> 60050 for mosh from any ip_range and
|
|
33
|
+
# * TCP 3398 for RDP from ip range: 96.212.12.34/32
|
|
34
|
+
def extract_port_definition(port_defn)
|
|
35
|
+
protocol = "tcp"
|
|
36
|
+
ip_range = "0.0.0.0/0"
|
|
37
|
+
if port_defn.is_a? Integer
|
|
38
|
+
port_range = (port_defn..port_defn)
|
|
39
|
+
elsif port_defn.is_a? Range
|
|
40
|
+
port_range = port_defn
|
|
41
|
+
elsif port_defn.is_a? Hash
|
|
42
|
+
protocol = port_defn[:protocol] if port_defn[:protocol]
|
|
43
|
+
port_range = port_defn[:ports] if port_defn[:ports]
|
|
44
|
+
ip_range = port_defn[:ip_range] if port_defn[:ip_range]
|
|
45
|
+
end
|
|
46
|
+
[protocol, port_range, ip_range]
|
|
47
|
+
end
|
|
21
48
|
end
|
|
@@ -37,10 +37,11 @@ class Bosh::Providers::OpenStack < Bosh::Providers::BaseProvider
|
|
|
37
37
|
end
|
|
38
38
|
ip_permissions = sg.rules
|
|
39
39
|
ports_opened = 0
|
|
40
|
-
ports.each do |name,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
ports.each do |name, port_defn|
|
|
41
|
+
(protocol, port_range, ip_range) = extract_port_definition(port_defn)
|
|
42
|
+
unless port_open?(ip_permissions, port_range, protocol, ip_range)
|
|
43
|
+
sg.create_security_group_rule(port_range.min, port_range.max, protocol, ip_range)
|
|
44
|
+
puts " -> opened #{name} ports #{protocol.upcase} #{port_range.min}..#{port_range.max} from IP range #{ip_range}"
|
|
44
45
|
ports_opened += 1
|
|
45
46
|
end
|
|
46
47
|
end
|
|
@@ -48,8 +49,13 @@ class Bosh::Providers::OpenStack < Bosh::Providers::BaseProvider
|
|
|
48
49
|
true
|
|
49
50
|
end
|
|
50
51
|
|
|
51
|
-
def port_open?(ip_permissions,
|
|
52
|
-
ip_permissions && ip_permissions.find
|
|
52
|
+
def port_open?(ip_permissions, port_range, protocol, ip_range)
|
|
53
|
+
ip_permissions && ip_permissions.find do |ip|
|
|
54
|
+
ip["ip_protocol"] == protocol \
|
|
55
|
+
&& ip["ip_range"].detect { |range| range["cidr"] == ip_range } \
|
|
56
|
+
&& ip["from_port"] <= port_range.min \
|
|
57
|
+
&& ip["to_port"] >= port_range.max
|
|
58
|
+
end
|
|
53
59
|
end
|
|
54
60
|
|
|
55
61
|
def find_server_device(server, device)
|
|
@@ -66,4 +72,8 @@ class Bosh::Providers::OpenStack < Bosh::Providers::BaseProvider
|
|
|
66
72
|
volume.attach(server.id, device)
|
|
67
73
|
volume.wait_for { volume.status == 'in-use' }
|
|
68
74
|
end
|
|
75
|
+
|
|
76
|
+
def delete_security_group_and_servers(sg_name)
|
|
77
|
+
raise "not implemented yet"
|
|
78
|
+
end
|
|
69
79
|
end
|