foreman_bootdisk 13.0.0 → 14.0.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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +4 -0
  3. data/README.md +3 -2
  4. data/app/controllers/foreman_bootdisk/api/v2/disks_controller.rb +11 -9
  5. data/app/controllers/foreman_bootdisk/api/v2/subnet_disks_controller.rb +37 -31
  6. data/app/controllers/foreman_bootdisk/disks_controller.rb +20 -19
  7. data/app/helpers/concerns/foreman_bootdisk/hosts_helper_ext.rb +86 -45
  8. data/app/lib/foreman_bootdisk/scope/bootdisk.rb +2 -0
  9. data/app/lib/foreman_bootdisk/scope/full_host_bootdisk.rb +15 -0
  10. data/app/models/concerns/foreman_bootdisk/compute_resources/vmware.rb +4 -2
  11. data/app/models/concerns/foreman_bootdisk/host_ext.rb +43 -31
  12. data/app/models/concerns/foreman_bootdisk/orchestration/compute.rb +14 -10
  13. data/app/models/setting/bootdisk.rb +28 -23
  14. data/app/services/foreman_bootdisk/iso_generator.rb +127 -104
  15. data/app/services/foreman_bootdisk/renderer.rb +16 -13
  16. data/config/routes.rb +15 -13
  17. data/config/routes/mount_engine.rb +3 -1
  18. data/db/migrate/20130914211030_create_host_bootdisk_template.rb +4 -4
  19. data/db/migrate/20130915104500_edit_host_bootdisk_template_multinic.rb +4 -4
  20. data/db/migrate/20130915133321_create_kickstart_bootdisk_template.rb +4 -4
  21. data/db/migrate/20130915201457_create_generic_host_bootdisk_template.rb +4 -4
  22. data/db/migrate/20131021095100_edit_host_bootdisk_template_dns_secondary.rb +4 -4
  23. data/db/migrate/20140522185700_change_templatekind_to_bootdisk.rb +16 -14
  24. data/db/migrate/20171009225200_remove_duplicate_bootdisk_templates.rb +4 -2
  25. data/db/seeds.d/50-bootdisk_templates.rb +24 -22
  26. data/lib/foreman_bootdisk.rb +2 -0
  27. data/lib/foreman_bootdisk/engine.rb +22 -33
  28. data/lib/foreman_bootdisk/version.rb +3 -1
  29. data/lib/tasks/bootdisk.rake +34 -17
  30. data/locale/gemspec.rb +3 -1
  31. data/test/functional/foreman_bootdisk/api/v2/disks_controller_test.rb +35 -33
  32. data/test/functional/foreman_bootdisk/api/v2/subnet_disks_controller_test.rb +9 -7
  33. data/test/functional/foreman_bootdisk/disks_controller_test.rb +27 -25
  34. data/test/models/host/managed_test.rb +17 -13
  35. data/test/test_plugin_helper.rb +9 -7
  36. data/test/unit/access_permissions_test.rb +2 -0
  37. data/test/unit/concerns/compute_resources/vmware_test.rb +67 -63
  38. data/test/unit/concerns/host_test.rb +54 -54
  39. data/test/unit/concerns/orchestration/compute_test.rb +41 -39
  40. data/test/unit/foreman_bootdisk/renderer_test.rb +3 -1
  41. data/test/unit/foreman_bootdisk/scope/bootdisk_test.rb +3 -1
  42. data/test/unit/foreman_bootdisk/scope/full_host_bootdisk_test.rb +30 -0
  43. data/test/unit/iso_generator_test.rb +57 -40
  44. metadata +19 -3
@@ -1,2 +1,4 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Matches foreman_bootdisk.gemspec
2
- _("Plugin for Foreman that creates iPXE-based boot disks to provision hosts without the need for PXE infrastructure.")
4
+ _('Plugin for Foreman that creates iPXE-based boot disks to provision hosts without the need for PXE infrastructure.')
@@ -1,61 +1,63 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'test_plugin_helper'
2
4
 
3
5
  class ForemanBootdisk::Api::V2::DisksControllerTest < ActionController::TestCase
4
6
  include ForemanBootdiskTestHelper
5
7
  setup :setup_bootdisk
6
8
 
7
- test "should generate generic image" do
8
- ForemanBootdisk::ISOGenerator.expects(:generate).with(has_entry(:ipxe => regexp_matches(/disk generic host template/))).yields("temp ISO")
9
- @controller.expects(:read_file).with("temp ISO").returns("ISO image")
9
+ test 'should generate generic image' do
10
+ ForemanBootdisk::ISOGenerator.expects(:generate).with(has_entry(ipxe: regexp_matches(/disk generic host template/))).yields('temp ISO')
11
+ @controller.expects(:read_file).with('temp ISO').returns('ISO image')
10
12
  get :generic
11
13
  assert_response :success
12
- assert_equal "ISO image", @response.body
14
+ assert_equal 'ISO image', @response.body
13
15
  end
14
16
 
15
- describe "#host" do
17
+ describe '#host' do
16
18
  setup :setup_referer
17
19
  setup :setup_host_env
18
20
 
19
- test "should generate host image" do
20
- ForemanBootdisk::ISOGenerator.expects(:generate).with(has_entry(:ipxe => regexp_matches(/disk host template/))).yields("temp ISO")
21
- @controller.expects(:read_file).with("temp ISO").returns("ISO image")
22
- get :host, params: {:id => @host.name}
21
+ test 'should generate host image' do
22
+ ForemanBootdisk::ISOGenerator.expects(:generate).with(has_entry(ipxe: regexp_matches(/disk host template/))).yields('temp ISO')
23
+ @controller.expects(:read_file).with('temp ISO').returns('ISO image')
24
+ get :host, params: { id: @host.name }
23
25
  assert_response :success
24
- assert_equal "ISO image", @response.body
26
+ assert_equal 'ISO image', @response.body
25
27
  end
26
28
 
27
- test "should generate full host image" do
28
- ForemanBootdisk::ISOGenerator.expects(:generate_full_host).with(@host).yields("temp ISO")
29
- @controller.expects(:read_file).with("temp ISO").returns("ISO image")
30
- get :host, params: {:id => @host.name, :full => true}
29
+ test 'should generate full host image' do
30
+ ForemanBootdisk::ISOGenerator.expects(:generate_full_host).with(@host).yields('temp ISO')
31
+ @controller.expects(:read_file).with('temp ISO').returns('ISO image')
32
+ get :host, params: { id: @host.name, full: true }
31
33
  assert_response :success
32
- assert_equal "ISO image", @response.body
34
+ assert_equal 'ISO image', @response.body
33
35
  end
34
36
  end
35
37
 
36
- describe "default API version 2" do
38
+ describe 'default API version 2' do
37
39
  setup :setup_host_env
38
40
 
39
- test "path - /api/hosts/:host_id routes to #host" do
40
- if Rails::VERSION::MAJOR >= 5
41
- expected_path = "/api/v2/hosts/#{@host.id}"
42
- else
43
- expected_path = "/api/hosts/#{@host.id}"
44
- end
41
+ test 'path - /api/hosts/:host_id routes to #host' do
42
+ expected_path = if Rails::VERSION::MAJOR >= 5
43
+ "/api/v2/hosts/#{@host.id}"
44
+ else
45
+ "/api/hosts/#{@host.id}"
46
+ end
45
47
  assert_routing expected_path,
46
- :format => "json",
47
- :apiv => "v2",
48
- :controller => "foreman_bootdisk/api/v2/disks",
49
- :action => "host",
50
- :id => @host.id.to_s
48
+ format: 'json',
49
+ apiv: 'v2',
50
+ controller: 'foreman_bootdisk/api/v2/disks',
51
+ action: 'host',
52
+ id: @host.id.to_s
51
53
  end
52
54
 
53
- test "path - /api/generic routes to #generic" do
54
- assert_routing "/api/generic",
55
- :format => "json",
56
- :apiv => "v2",
57
- :controller => "foreman_bootdisk/api/v2/disks",
58
- :action => "generic"
55
+ test 'path - /api/generic routes to #generic' do
56
+ assert_routing '/api/generic',
57
+ format: 'json',
58
+ apiv: 'v2',
59
+ controller: 'foreman_bootdisk/api/v2/disks',
60
+ action: 'generic'
59
61
  end
60
62
  end
61
63
  end
@@ -1,22 +1,24 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'test_plugin_helper'
2
4
 
3
5
  class ForemanBootdisk::Api::V2::SubnetDisksControllerTest < ActionController::TestCase
4
6
  include ForemanBootdiskTestHelper
5
7
  setup :setup_bootdisk
6
8
 
7
- describe "#subnet_host" do
9
+ describe '#subnet_host' do
8
10
  setup :setup_referer
9
11
  setup :setup_org_loc
10
12
  setup :setup_subnet
11
13
  setup :setup_host
12
14
 
13
- test "should generate subnet generic host image" do
14
- ForemanBootdisk::ISOGenerator.expects(:generate).with(has_entry(:ipxe => regexp_matches(/disk generic host template/))).yields("temp ISO")
15
- ForemanBootdisk::Renderer.any_instance.stubs(:bootdisk_chain_url).yields("http://smart-proxy.lan")
16
- File.expects(:read).with("temp ISO").returns("ISO image")
17
- get :subnet, params: {:id => @host.subnet.id}
15
+ test 'should generate subnet generic host image' do
16
+ ForemanBootdisk::ISOGenerator.expects(:generate).with(has_entry(ipxe: regexp_matches(/disk generic host template/))).yields('temp ISO')
17
+ ForemanBootdisk::Renderer.any_instance.stubs(:bootdisk_chain_url).yields('http://smart-proxy.lan')
18
+ File.expects(:read).with('temp ISO').returns('ISO image')
19
+ get :subnet, params: { id: @host.subnet.id }
18
20
  assert_response :success
19
- assert_equal "ISO image", @response.body
21
+ assert_equal 'ISO image', @response.body
20
22
  end
21
23
  end
22
24
  end
@@ -1,64 +1,66 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'test_plugin_helper'
2
4
 
3
5
  class ForemanBootdisk::DisksControllerTest < ActionController::TestCase
4
6
  include ForemanBootdiskTestHelper
5
7
  setup :setup_bootdisk
6
8
 
7
- test "should generate generic image" do
8
- ForemanBootdisk::ISOGenerator.expects(:generate).with(has_entry(:ipxe => regexp_matches(/disk generic host template/))).yields("temp ISO")
9
- @controller.expects(:read_file).with("temp ISO").returns("ISO image")
9
+ test 'should generate generic image' do
10
+ ForemanBootdisk::ISOGenerator.expects(:generate).with(has_entry(ipxe: regexp_matches(/disk generic host template/))).yields('temp ISO')
11
+ @controller.expects(:read_file).with('temp ISO').returns('ISO image')
10
12
  get :generic, session: set_session_user
11
13
  assert_response :success
12
- assert_equal "ISO image", @response.body
14
+ assert_equal 'ISO image', @response.body
13
15
  end
14
16
 
15
- describe "#host" do
17
+ describe '#host' do
16
18
  setup :setup_referer
17
19
  setup :setup_org_loc
18
20
  setup :setup_subnet
19
21
  setup :setup_host
20
22
 
21
- test "should generate host image" do
22
- ForemanBootdisk::ISOGenerator.expects(:generate).with(has_entry(:ipxe => regexp_matches(/disk host template/))).yields("temp ISO")
23
- @controller.expects(:read_file).with("temp ISO").returns("ISO image")
24
- get :host, params: {:id => @host.name}, session: set_session_user
23
+ test 'should generate host image' do
24
+ ForemanBootdisk::ISOGenerator.expects(:generate).with(has_entry(ipxe: regexp_matches(/disk host template/))).yields('temp ISO')
25
+ @controller.expects(:read_file).with('temp ISO').returns('ISO image')
26
+ get :host, params: { id: @host.name }, session: set_session_user
25
27
  assert_response :success
26
- assert_equal "ISO image", @response.body
28
+ assert_equal 'ISO image', @response.body
27
29
  end
28
30
 
29
- test "should generate full host image" do
30
- ForemanBootdisk::ISOGenerator.expects(:generate_full_host).with(@host).yields("temp ISO")
31
- @controller.expects(:read_file).with("temp ISO").returns("ISO image")
32
- get :full_host, params: {:id => @host.name}, session: set_session_user
31
+ test 'should generate full host image' do
32
+ ForemanBootdisk::ISOGenerator.expects(:generate_full_host).with(@host).yields('temp ISO')
33
+ @controller.expects(:read_file).with('temp ISO').returns('ISO image')
34
+ get :full_host, params: { id: @host.name }, session: set_session_user
33
35
  assert_response :success
34
- assert_equal "ISO image", @response.body
36
+ assert_equal 'ISO image', @response.body
35
37
  end
36
38
 
37
- test "should generate subnet image" do
38
- ForemanBootdisk::ISOGenerator.expects(:generate).with(has_entry(:ipxe => regexp_matches(/disk generic host template/))).yields("temp ISO")
39
- ForemanBootdisk::Renderer.any_instance.stubs(:bootdisk_chain_url).yields("http://smart-proxy.lan")
40
- @controller.expects(:read_file).with("temp ISO").returns("ISO image")
41
- get :subnet, params: {:id => @host.name}, session: set_session_user
39
+ test 'should generate subnet image' do
40
+ ForemanBootdisk::ISOGenerator.expects(:generate).with(has_entry(ipxe: regexp_matches(/disk generic host template/))).yields('temp ISO')
41
+ ForemanBootdisk::Renderer.any_instance.stubs(:bootdisk_chain_url).yields('http://smart-proxy.lan')
42
+ @controller.expects(:read_file).with('temp ISO').returns('ISO image')
43
+ get :subnet, params: { id: @host.name }, session: set_session_user
42
44
  assert_empty flash[:error]
43
45
  assert_response :success
44
- assert_equal "ISO image", @response.body
46
+ assert_equal 'ISO image', @response.body
45
47
  end
46
48
  end
47
49
 
48
- describe "#host without tftp" do
50
+ describe '#host without tftp' do
49
51
  setup :setup_referer
50
52
  setup :setup_org_loc
51
53
  setup :setup_subnet_no_tftp
52
54
  setup :setup_host
53
55
 
54
- test "should not generate subnet image" do
55
- get :subnet, params: {:id => @host.name}, session: set_session_user
56
+ test 'should not generate subnet image' do
57
+ get :subnet, params: { id: @host.name }, session: set_session_user
56
58
  assert_match(/Failed.*: TFTP feature not enabled/, flash[:error])
57
59
  assert_response :redirect
58
60
  end
59
61
  end
60
62
 
61
- test "should render help" do
63
+ test 'should render help' do
62
64
  get :help, session: set_session_user
63
65
  assert_response :success
64
66
  end
@@ -1,22 +1,26 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'test_plugin_helper'
2
4
 
3
- class Host::ManagedTest < ActiveSupport::TestCase
4
- include ForemanBootdiskTestHelper
5
+ module Host
6
+ class ManagedTest < ActiveSupport::TestCase
7
+ include ForemanBootdiskTestHelper
5
8
 
6
- setup do
7
- User.current = users(:admin)
8
- setup_bootdisk
9
- end
9
+ setup do
10
+ User.current = users(:admin)
11
+ setup_bootdisk
12
+ end
10
13
 
11
- context 'with host' do
12
- let(:host) { FactoryBot.create(:host, :managed, :with_subnet, build: true) }
14
+ context 'with host' do
15
+ let(:host) { FactoryBot.create(:host, :managed, :with_subnet, build: true) }
13
16
 
14
- test 'finds the bootdisk_template specified in settings' do
15
- assert_kind_of ProvisioningTemplate, host.bootdisk_template
16
- end
17
+ test 'finds the bootdisk_template specified in settings' do
18
+ assert_kind_of ProvisioningTemplate, host.bootdisk_template
19
+ end
17
20
 
18
- test 'renders the host bootdisk template' do
19
- assert_includes host.bootdisk_template_render, 'loop_success'
21
+ test 'renders the host bootdisk template' do
22
+ assert_includes host.bootdisk_template_render, 'loop_success'
23
+ end
20
24
  end
21
25
  end
22
26
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'test_helper'
2
4
 
3
5
  module ForemanBootdiskTestHelper
@@ -26,25 +28,25 @@ module ForemanBootdiskTestHelper
26
28
  end
27
29
 
28
30
  def setup_referer
29
- request.env["HTTP_REFERER"] = "/history"
31
+ request.env['HTTP_REFERER'] = '/history'
30
32
  end
31
33
 
32
34
  def setup_org_loc
33
35
  disable_orchestration
34
- @org, @loc = FactoryBot.create(:organization), FactoryBot.create(:location)
36
+ @org = FactoryBot.create(:organization)
37
+ @loc = FactoryBot.create(:location)
35
38
  end
36
39
 
37
40
  def setup_subnet
38
- tftp_proxy = FactoryBot.create(:smart_proxy, :features => [FactoryBot.create(:tftp_feature)])
39
- setup_subnet_no_tftp.update! :tftp => tftp_proxy
41
+ tftp_proxy = FactoryBot.create(:smart_proxy, features: [FactoryBot.create(:tftp_feature)])
42
+ setup_subnet_no_tftp.update! tftp: tftp_proxy
40
43
  end
41
44
 
42
45
  def setup_subnet_no_tftp
43
- @subnet = FactoryBot.create(:subnet_ipv4, :gateway => '10.0.1.254', :dns_primary => '8.8.8.8', :organizations => [@org], :locations => [@loc])
46
+ @subnet = FactoryBot.create(:subnet_ipv4, gateway: '10.0.1.254', dns_primary: '8.8.8.8', organizations: [@org], locations: [@loc])
44
47
  end
45
48
 
46
49
  def setup_host
47
- @host = FactoryBot.create(:host, :managed, :subnet => @subnet, :ip => @subnet.network.sub(/0$/, '4'), :organization => @org, :location => @loc)
50
+ @host = FactoryBot.create(:host, :managed, subnet: @subnet, ip: @subnet.network.sub(/0$/, '4'), organization: @org, location: @loc)
48
51
  end
49
-
50
52
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'test_helper'
2
4
  require 'unit/shared/access_permissions_test_base'
3
5
 
@@ -1,79 +1,83 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'test_plugin_helper'
2
4
 
3
- class ForemanBootdisk::VmwareTest < ActiveSupport::TestCase
4
- describe "#create_vm" do
5
- setup do
6
- @cr = FactoryBot.build(:vmware_cr)
7
- @cr.stubs(:test_connection)
8
- end
5
+ module ForemanBootdisk
6
+ class VmwareTest < ActiveSupport::TestCase
7
+ describe '#create_vm' do
8
+ setup do
9
+ @cr = FactoryBot.build(:vmware_cr)
10
+ @cr.stubs(:test_connection)
11
+ end
9
12
 
10
- test "does not call clone_vm when bootdisk provisioning" do
11
- args = { "provision_method" => "bootdisk" }
12
- mock_vm = mock('vm')
13
- mock_vm.stubs(:firmware)
14
- mock_vm.expects(:save).returns(mock_vm)
15
- @cr.stubs(:parse_networks).returns(args)
16
- @cr.expects(:clone_vm).times(0)
17
- @cr.expects(:new_vm).returns(mock_vm)
18
- @cr.create_vm(args)
13
+ test 'does not call clone_vm when bootdisk provisioning' do
14
+ args = { 'provision_method' => 'bootdisk' }
15
+ mock_vm = mock('vm')
16
+ mock_vm.stubs(:firmware)
17
+ mock_vm.expects(:save).returns(mock_vm)
18
+ @cr.stubs(:parse_networks).returns(args)
19
+ @cr.expects(:clone_vm).times(0)
20
+ @cr.expects(:new_vm).returns(mock_vm)
21
+ @cr.create_vm(args)
22
+ end
19
23
  end
20
- end
21
24
 
22
- describe "#new_vm" do
23
- setup do
24
- @cr = FactoryBot.build(:vmware_cr)
25
- end
25
+ describe '#new_vm' do
26
+ setup do
27
+ @cr = FactoryBot.build(:vmware_cr)
28
+ end
26
29
 
27
- test "calls client with cdrom drive and correct boot order when bootdisk provisioning" do
28
- args = { "provision_method" => "bootdisk" }
29
- mock_client = mock('client')
30
- mock_servers = mock('servers')
31
- mock_cdrom = mock('cdrom')
32
- mock_client.expects(:servers).returns(mock_servers)
33
- mock_servers.expects(:new).with() do |args|
34
- assert_equal args[:boot_order], ['cdrom', 'disk']
35
- assert_equal args[:boot_retry], 10
36
- assert_includes args[:cdroms], mock_cdrom
30
+ test 'calls client with cdrom drive and correct boot order when bootdisk provisioning' do
31
+ args = { 'provision_method' => 'bootdisk' }
32
+ mock_client = mock('client')
33
+ mock_servers = mock('servers')
34
+ mock_cdrom = mock('cdrom')
35
+ mock_client.expects(:servers).returns(mock_servers)
36
+ mock_servers.expects(:new).with do |opts|
37
+ assert_equal opts[:boot_order], %w[cdrom disk]
38
+ assert_equal opts[:boot_retry], 10
39
+ assert_includes opts[:cdroms], mock_cdrom
40
+ end
41
+ @cr.expects(:new_cdrom).returns(mock_cdrom)
42
+ @cr.expects(:new_interface)
43
+ @cr.expects(:new_volume)
44
+ @cr.expects(:datacenter)
45
+ @cr.expects(:client).returns(mock_client)
46
+ @cr.new_vm(args)
37
47
  end
38
- @cr.expects(:new_cdrom).returns(mock_cdrom)
39
- @cr.expects(:new_interface)
40
- @cr.expects(:new_volume)
41
- @cr.expects(:datacenter)
42
- @cr.expects(:client).returns(mock_client)
43
- @cr.new_vm(args)
44
48
  end
45
- end
46
49
 
47
- describe "#parse_args" do
48
- setup do
49
- @cr = FactoryBot.build(:vmware_cr)
50
- end
50
+ describe '#parse_args' do
51
+ setup do
52
+ @cr = FactoryBot.build(:vmware_cr)
53
+ end
51
54
 
52
- test "should add a cdrom drive while keeping other parameters when provision_method is bootdisk" do
53
- mock_cdrom = mock('cdrom')
54
- @cr.expects(:new_cdrom).returns(mock_cdrom)
55
- attrs_in = HashWithIndifferentAccess.new(
56
- 'cpus' => '1',
57
- :provision_method => 'bootdisk',
58
- )
59
- attrs_out = {
60
- :cpus => '1',
61
- :provision_method => 'bootdisk',
62
- :cdroms => [mock_cdrom],
63
- :boot_order => ['cdrom', 'disk'],
64
- :boot_retry => 10
65
- }
66
- assert_equal attrs_out, @cr.parse_args(attrs_in)
55
+ test 'should add a cdrom drive while keeping other parameters when provision_method is bootdisk' do
56
+ mock_cdrom = mock('cdrom')
57
+ @cr.expects(:new_cdrom).returns(mock_cdrom)
58
+ attrs_in = HashWithIndifferentAccess.new(
59
+ 'cpus' => '1',
60
+ :provision_method => 'bootdisk'
61
+ )
62
+ attrs_out = {
63
+ cpus: '1',
64
+ provision_method: 'bootdisk',
65
+ cdroms: [mock_cdrom],
66
+ boot_order: %w[cdrom disk],
67
+ boot_retry: 10
68
+ }
69
+ assert_equal attrs_out, @cr.parse_args(attrs_in)
70
+ end
67
71
  end
68
- end
69
72
 
70
- describe "#capabilities" do
71
- setup do
72
- @cr = FactoryBot.build(:vmware_cr)
73
- end
73
+ describe '#capabilities' do
74
+ setup do
75
+ @cr = FactoryBot.build(:vmware_cr)
76
+ end
74
77
 
75
- test "should include bootdisk" do
76
- assert_includes @cr.capabilities, :bootdisk
78
+ test 'should include bootdisk' do
79
+ assert_includes @cr.capabilities, :bootdisk
80
+ end
77
81
  end
78
82
  end
79
83
  end