avm-tools 0.68.0 → 0.69.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 (90) hide show
  1. checksums.yaml +4 -4
  2. data/lib/avm/eac_rails_base0/apache_host.rb +17 -0
  3. data/lib/avm/eac_rails_base0/deploy.rb +50 -0
  4. data/lib/avm/eac_rails_base0/instance.rb +14 -0
  5. data/lib/avm/eac_redmine_base0/deploy.rb +2 -2
  6. data/lib/avm/eac_redmine_base0/instance.rb +2 -2
  7. data/lib/avm/eac_webapp_base0/apache_host.rb +103 -0
  8. data/lib/avm/eac_webapp_base0/deploy.rb +102 -0
  9. data/lib/avm/eac_webapp_base0/deploy/appended_directories.rb +25 -0
  10. data/lib/avm/eac_webapp_base0/deploy/file_unit.rb +41 -0
  11. data/lib/avm/eac_webapp_base0/deploy/git_info.rb +49 -0
  12. data/lib/avm/eac_webapp_base0/deploy/version.rb +20 -0
  13. data/lib/avm/eac_webapp_base0/instance.rb +50 -0
  14. data/lib/avm/eac_webapp_base0/runner/apache_host.rb +39 -0
  15. data/lib/avm/eac_webapp_base0/runner/data.rb +25 -0
  16. data/lib/avm/eac_webapp_base0/runner/data/dump.rb +69 -0
  17. data/lib/avm/eac_webapp_base0/runner/data/load.rb +66 -0
  18. data/lib/avm/eac_webapp_base0/runner/deploy.rb +59 -0
  19. data/lib/avm/stereotypes/eac_wordpress_base0/apache_host.rb +2 -2
  20. data/lib/avm/stereotypes/eac_wordpress_base0/deploy.rb +2 -2
  21. data/lib/avm/stereotypes/eac_wordpress_base0/instance.rb +2 -2
  22. data/lib/avm/tools/runner/eac_rails_base0.rb +2 -2
  23. data/lib/avm/tools/runner/eac_rails_base0/apache_host.rb +3 -26
  24. data/lib/avm/tools/runner/eac_rails_base0/data.rb +2 -2
  25. data/lib/avm/tools/runner/eac_rails_base0/deploy.rb +3 -3
  26. data/lib/avm/tools/runner/eac_redmine_base0/data.rb +1 -1
  27. data/lib/avm/tools/runner/eac_redmine_base0/data/dump.rb +2 -2
  28. data/lib/avm/tools/runner/eac_redmine_base0/deploy.rb +2 -2
  29. data/lib/avm/tools/runner/eac_redmine_base0/project_rename.rb +1 -0
  30. data/lib/avm/tools/runner/eac_wordpress_base0/apache_host.rb +3 -25
  31. data/lib/avm/tools/runner/eac_wordpress_base0/data.rb +2 -2
  32. data/lib/avm/tools/runner/eac_wordpress_base0/deploy.rb +2 -2
  33. data/lib/avm/tools/version.rb +1 -1
  34. data/template/avm/{stereotypes/eac_rails_base0 → eac_rails_base0}/deploy/config/database.yml.template +0 -0
  35. data/template/avm/{stereotypes/eac_webapp_base0 → eac_webapp_base0}/apache_host/no_ssl.conf +0 -0
  36. data/vendor/eac_cli/Gemfile +5 -0
  37. data/vendor/eac_cli/eac_cli.gemspec +18 -0
  38. data/vendor/eac_cli/lib/eac_cli.rb +7 -0
  39. data/vendor/eac_cli/lib/eac_cli/default_runner.rb +22 -0
  40. data/vendor/eac_cli/lib/eac_cli/definition.rb +72 -0
  41. data/vendor/eac_cli/lib/eac_cli/definition/argument_option.rb +13 -0
  42. data/vendor/eac_cli/lib/eac_cli/definition/base_option.rb +26 -0
  43. data/vendor/eac_cli/lib/eac_cli/definition/boolean_option.rb +13 -0
  44. data/vendor/eac_cli/lib/eac_cli/definition/positional_argument.rb +27 -0
  45. data/vendor/eac_cli/lib/eac_cli/docopt/doc_builder.rb +77 -0
  46. data/vendor/eac_cli/lib/eac_cli/docopt/runner_extension.rb +45 -0
  47. data/vendor/eac_cli/lib/eac_cli/parser.rb +14 -0
  48. data/vendor/eac_cli/lib/eac_cli/parser/collector.rb +56 -0
  49. data/vendor/eac_cli/lib/eac_cli/parser/error.rb +15 -0
  50. data/vendor/eac_cli/lib/eac_cli/parser/options_collection.rb +105 -0
  51. data/vendor/eac_cli/lib/eac_cli/parser/parse_result.rb +21 -0
  52. data/vendor/eac_cli/lib/eac_cli/parser/positional_collection.rb +49 -0
  53. data/vendor/eac_cli/lib/eac_cli/runner.rb +87 -0
  54. data/vendor/eac_cli/lib/eac_cli/runner/context.rb +18 -0
  55. data/vendor/eac_cli/lib/eac_cli/version.rb +5 -0
  56. data/vendor/eac_cli/spec/lib/eac_cli/runner_spec.rb +70 -0
  57. data/vendor/eac_cli/spec/rubocop_spec.rb +7 -0
  58. data/vendor/eac_cli/spec/spec_helper.rb +100 -0
  59. data/vendor/eac_ruby_gems_utils/lib/eac_ruby_gems_utils/gem.rb +10 -0
  60. data/vendor/eac_ruby_gems_utils/lib/eac_ruby_gems_utils/tests/multiple.rb +5 -34
  61. data/vendor/eac_ruby_gems_utils/lib/eac_ruby_gems_utils/tests/multiple/decorated_gem.rb +42 -0
  62. data/vendor/eac_ruby_gems_utils/lib/eac_ruby_gems_utils/tests/multiple/result.rb +25 -0
  63. data/vendor/eac_ruby_gems_utils/lib/eac_ruby_gems_utils/version.rb +1 -1
  64. data/vendor/eac_ruby_utils/lib/eac_ruby_utils/common_concern.rb +4 -4
  65. data/vendor/eac_ruby_utils/lib/eac_ruby_utils/console/docopt_runner.rb +6 -0
  66. data/vendor/eac_ruby_utils/lib/eac_ruby_utils/console/docopt_runner/_class_methods.rb +1 -1
  67. data/vendor/eac_ruby_utils/lib/eac_ruby_utils/console/docopt_runner/_subcommands.rb +8 -2
  68. data/vendor/eac_ruby_utils/lib/eac_ruby_utils/inflector.rb +18 -0
  69. data/vendor/eac_ruby_utils/lib/eac_ruby_utils/listable/value.rb +3 -2
  70. data/vendor/eac_ruby_utils/lib/eac_ruby_utils/patches/string.rb +4 -0
  71. data/vendor/eac_ruby_utils/lib/eac_ruby_utils/patches/string/inflector.rb +9 -0
  72. data/vendor/eac_ruby_utils/lib/eac_ruby_utils/struct.rb +47 -0
  73. data/vendor/eac_ruby_utils/lib/eac_ruby_utils/version.rb +1 -1
  74. data/vendor/eac_ruby_utils/spec/lib/eac_ruby_utils/inflector_spec.rb +15 -0
  75. data/vendor/eac_ruby_utils/spec/lib/eac_ruby_utils/struct_spec.rb +46 -0
  76. metadata +50 -18
  77. data/lib/avm/stereotypes/eac_rails_base0/apache_host.rb +0 -19
  78. data/lib/avm/stereotypes/eac_rails_base0/deploy.rb +0 -52
  79. data/lib/avm/stereotypes/eac_rails_base0/instance.rb +0 -16
  80. data/lib/avm/stereotypes/eac_webapp_base0/apache_host.rb +0 -97
  81. data/lib/avm/stereotypes/eac_webapp_base0/deploy.rb +0 -104
  82. data/lib/avm/stereotypes/eac_webapp_base0/deploy/appended_directories.rb +0 -27
  83. data/lib/avm/stereotypes/eac_webapp_base0/deploy/file_unit.rb +0 -43
  84. data/lib/avm/stereotypes/eac_webapp_base0/deploy/git_info.rb +0 -51
  85. data/lib/avm/stereotypes/eac_webapp_base0/deploy/version.rb +0 -22
  86. data/lib/avm/stereotypes/eac_webapp_base0/instance.rb +0 -52
  87. data/lib/avm/stereotypes/eac_webapp_base0/runner/data.rb +0 -27
  88. data/lib/avm/stereotypes/eac_webapp_base0/runner/data/dump.rb +0 -71
  89. data/lib/avm/stereotypes/eac_webapp_base0/runner/data/load.rb +0 -68
  90. data/lib/avm/stereotypes/eac_webapp_base0/runner/deploy.rb +0 -61
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 804e613bfa303949fb0ed952f999d5815b6de62aea34e9c03c0e87ecf65f35b4
4
- data.tar.gz: a86f89745be17ae8bc039beb9d0e28680481e30069975c809a9083e42ef17589
3
+ metadata.gz: 057f67989ce7342dfc11871a686093edee245979cded73a5a1d34b4d1c6f74d1
4
+ data.tar.gz: '019d0f6e63a1008bc0d3565a30f19651ea89211c46a8cc5ed4cad9a22e0c67d1'
5
5
  SHA512:
6
- metadata.gz: fcd4890d0c29dc11ddd5c1cbce9c5d767900e5e9be10d763afcb91fd73cfdd715c0f6ae217c64fc82f5e87128dbad619500e2ee3a33c4500a29c49739b5a93dc
7
- data.tar.gz: 89a8e507755008a2f7a18c0ee84ed2572588aff5ab12e0e9d5ec7e897a7c397f4cc6e1d2e7d83595d46fd3b835fcd3104dbb5cfebb38ae837113f25091169a85
6
+ metadata.gz: 8911e882460a527138f7bc2f77bc35fae5e029c3735d061e4c58d23aaebbd18e782907c4ec6a3e8d2be76da62e57c9ae98fec0732ab3048ad1362dd1af47ce29
7
+ data.tar.gz: feca924ad65af5bea21245a0764a26cbf869edca515e5a35af1141c62e74eab2828c7b194911460eac8c33f22c519056b68692f48e8761a17d64774c838f3b71
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'avm/eac_webapp_base0/apache_host'
4
+
5
+ module Avm
6
+ module EacRailsBase0
7
+ class ApacheHost < ::Avm::EacWebappBase0::ApacheHost
8
+ def document_root
9
+ "#{instance.read_entry(:fs_path)}/public"
10
+ end
11
+
12
+ def extra_content
13
+ 'PassengerEnabled On'
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'avm/eac_webapp_base0/deploy'
4
+
5
+ module Avm
6
+ module EacRailsBase0
7
+ class Deploy < ::Avm::EacWebappBase0::Deploy
8
+ set_callback :assert_instance_branch, :after do
9
+ bundle_install
10
+ assert_database
11
+ database_migrate
12
+ compile_assets
13
+ restart_tasks_scheduler
14
+ touch_restart_file
15
+ end
16
+
17
+ def assert_database
18
+ infom 'Asserting database...'
19
+ instance.rake('db:create').system!
20
+ end
21
+
22
+ def bundle_install
23
+ infom 'Running "bundle install"...'
24
+ instance.bundle('install').system!
25
+ end
26
+
27
+ def compile_assets
28
+ infom 'Compiling assets...'
29
+ instance.rake('assets:clean', 'assets:precompile').system!
30
+ end
31
+
32
+ def database_migrate
33
+ infom 'Running database migrations...'
34
+ instance.rake('db:migrate').system!
35
+ end
36
+
37
+ def restart_tasks_scheduler
38
+ infom 'Restarting Tasks Scheduler\'s daemon...'
39
+ instance.bundle('exec', 'tasks_scheduler', 'restart').system!
40
+ end
41
+
42
+ def touch_restart_file
43
+ infom 'Touching restart file...'
44
+ instance.host_env.command(
45
+ 'touch', ::File.join(instance.read_entry(:fs_path), 'tmp', 'restart.txt')
46
+ ).system!
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'avm/eac_webapp_base0/instance'
4
+ require 'avm/stereotypes/rails/instance'
5
+
6
+ module Avm
7
+ module EacRailsBase0
8
+ class Instance < ::Avm::EacWebappBase0::Instance
9
+ include ::Avm::Stereotypes::Rails::Instance
10
+
11
+ FILES_UNITS = { uploads: 'public/uploads' }.freeze
12
+ end
13
+ end
14
+ end
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'avm/stereotypes/eac_webapp_base0/deploy'
3
+ require 'avm/eac_webapp_base0/deploy'
4
4
  require 'eac_ruby_utils/ruby'
5
5
 
6
6
  module Avm
7
7
  module EacRedmineBase0
8
- class Deploy < ::Avm::Stereotypes::EacWebappBase0::Deploy
8
+ class Deploy < ::Avm::EacWebappBase0::Deploy
9
9
  set_callback :assert_instance_branch, :after, :run_installer
10
10
 
11
11
  def run_installer
@@ -2,12 +2,12 @@
2
2
 
3
3
  require 'avm/eac_redmine_base0/data_unit'
4
4
  require 'avm/stereotypes/eac_ubuntu_base0/docker_image'
5
- require 'avm/stereotypes/eac_webapp_base0/instance'
5
+ require 'avm/eac_webapp_base0/instance'
6
6
  require 'avm/stereotypes/rails/instance'
7
7
 
8
8
  module Avm
9
9
  module EacRedmineBase0
10
- class Instance < ::Avm::Stereotypes::EacWebappBase0::Instance
10
+ class Instance < ::Avm::EacWebappBase0::Instance
11
11
  include ::Avm::Stereotypes::Rails::Instance
12
12
 
13
13
  FILES_UNITS = { files: 'files' }.freeze
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'eac_ruby_utils/core_ext'
4
+ require 'avm/patches/object/template'
5
+ require 'avm/stereotypes/eac_ubuntu_base0/apache'
6
+ require 'avm/patches/object/template'
7
+
8
+ module Avm
9
+ module EacWebappBase0
10
+ class ApacheHost
11
+ enable_console_speaker
12
+ enable_simple_cache
13
+ common_constructor :instance, :options, default: [{}]
14
+
15
+ def run
16
+ write_available_no_ssl_site
17
+ enable_no_ssl_site
18
+ remove_ssl_site
19
+ reload_apache
20
+ run_certbot
21
+ enable_ssl_site
22
+ reload_apache
23
+ ::Avm::Result.success('Done')
24
+ end
25
+
26
+ def no_ssl_site_content
27
+ ::Avm::EacWebappBase0::ApacheHost.template.child('no_ssl.conf')
28
+ .apply(EntriesReader.new(self, instance))
29
+ end
30
+
31
+ def ssl?
32
+ options[:certbot]
33
+ end
34
+
35
+ private
36
+
37
+ def apache_uncached
38
+ ::Avm::Stereotypes::EacUbuntuBase0::Apache.new(instance.host_env)
39
+ end
40
+
41
+ def enable_no_ssl_site
42
+ infom 'Enabling no SSL site...'
43
+ no_ssl_site.enable
44
+ end
45
+
46
+ def enable_ssl_site
47
+ return unless ssl?
48
+
49
+ infom 'Enabling SSL site...'
50
+ ssl_site.enable
51
+ end
52
+
53
+ def no_ssl_site_uncached
54
+ apache.site(instance.id)
55
+ end
56
+
57
+ def reload_apache
58
+ infom 'Reloading Apache...'
59
+ apache.service('reload')
60
+ end
61
+
62
+ def remove_ssl_site
63
+ infom 'Removing SSL site...'
64
+ ssl_site.remove
65
+ end
66
+
67
+ def run_certbot
68
+ return unless ssl?
69
+
70
+ infom 'Running Certbot...'
71
+ instance.host_env.command(
72
+ 'sudo', 'certbot', '--apache', '--domain', instance.read_entry('web.hostname'),
73
+ '--redirect', '--non-interactive', '--agree-tos',
74
+ '--email', instance.read_entry('admin.email')
75
+ ).system!
76
+ end
77
+
78
+ def ssl_site_uncached
79
+ apache.site(no_ssl_site.name + '-le-ssl')
80
+ end
81
+
82
+ def write_available_no_ssl_site
83
+ infom 'Writing no SSL site conf...'
84
+ no_ssl_site.write(no_ssl_site_content)
85
+ end
86
+
87
+ class EntriesReader
88
+ common_constructor :job, :instance
89
+
90
+ def read_entry(path, options = {})
91
+ entry_from_job(path) || instance.read_entry(path, options)
92
+ end
93
+
94
+ private
95
+
96
+ def entry_from_job(path)
97
+ method = path.gsub('.', '_').underscore
98
+ return job.send(method) if job.respond_to?(method, true)
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/callbacks'
4
+ require 'delegate'
5
+ require 'eac_ruby_utils/core_ext'
6
+ require 'eac_launcher/git/base'
7
+ require 'avm/git'
8
+ require 'avm/patches/object/template'
9
+ require 'net/http'
10
+
11
+ module Avm
12
+ module EacWebappBase0
13
+ class Deploy
14
+ require_sub __FILE__, include_modules: true
15
+ include ::ActiveSupport::Callbacks
16
+
17
+ DEFAULT_REFERENCE = 'HEAD'
18
+
19
+ enable_console_speaker
20
+ enable_simple_cache
21
+
22
+ JOBS = %w[git_deploy setup_files_units assert_instance_branch request_test].freeze
23
+ define_callbacks(*JOBS)
24
+
25
+ attr_reader :instance, :options
26
+
27
+ def initialize(instance, options = {})
28
+ @instance = instance
29
+ @options = options
30
+ end
31
+
32
+ def build_git_commit
33
+ ::Avm::Git::Commit.new(git, commit_sha1).deploy_to_env_path(
34
+ instance.host_env,
35
+ instance.read_entry(:fs_path)
36
+ ).variables_source_set(instance)
37
+ end
38
+
39
+ def run
40
+ start_banner
41
+ JOBS.each do |job|
42
+ run_callbacks job do
43
+ send(job)
44
+ end
45
+ end
46
+ ::Avm::Result.success('Deployed')
47
+ rescue ::Avm::Result::Error => e
48
+ e.to_result
49
+ end
50
+
51
+ def start_banner
52
+ infov 'Instance', instance
53
+ infov 'Git reference (User)', git_reference.if_present('- BLANK -')
54
+ infov 'Git remote name', git_remote_name
55
+ infov 'Git reference (Found)', git_reference_found
56
+ infov 'Git commit SHA1', commit_sha1
57
+ infov 'Appended directories', appended_directories
58
+ end
59
+
60
+ def git_deploy
61
+ infom 'Deploying source code and appended content...'
62
+ build_git_commit
63
+ .append_directory(template.path)
64
+ .append_directories(appended_directories)
65
+ .append_file_content(VERSION_TARGET_PATH, version)
66
+ .run
67
+ end
68
+
69
+ def setup_files_units
70
+ instance.class.const_get('FILES_UNITS').each do |data_key, fs_path_subpath|
71
+ FileUnit.new(self, data_key, fs_path_subpath).run
72
+ end
73
+ end
74
+
75
+ def assert_instance_branch
76
+ infom 'Setting instance branch...'
77
+ git.execute!('push', git_remote_name, "#{commit_sha1}:refs/heads/#{instance.id}", '-f')
78
+ end
79
+
80
+ def request_test
81
+ infom 'Requesting web interface...'
82
+ uri = URI(instance.read_entry('web.url'))
83
+ response = ::Net::HTTP.get_response(uri)
84
+ infov 'Response status', response.code
85
+ fatal_error "Request to #{uri} failed" unless response.code.to_i == 200
86
+ end
87
+
88
+ def git_uncached
89
+ ::EacLauncher::Git::Base.new(git_repository_path)
90
+ end
91
+
92
+ def git_fetch_uncached
93
+ infom "Fetching remote \"#{git_remote_name}\" from \"#{git_repository_path}\"..."
94
+ git.fetch(git_remote_name)
95
+ end
96
+
97
+ def git_repository_path
98
+ instance.source_instance.read_entry(:fs_path)
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'avm/path_string'
4
+
5
+ module Avm
6
+ module EacWebappBase0
7
+ class Deploy
8
+ module AppendedDirectories
9
+ APPENDED_DIRECTORIES_ENTRY_KEY = 'deploy.appended_directories'
10
+
11
+ def appended_directories
12
+ appended_directories_from_instance_entry + appended_directories_from_options
13
+ end
14
+
15
+ def appended_directories_from_instance_entry
16
+ ::Avm::PathString.paths(instance.read_entry_optional(APPENDED_DIRECTORIES_ENTRY_KEY))
17
+ end
18
+
19
+ def appended_directories_from_options
20
+ options[:appended_directories] || []
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Avm
4
+ module EacWebappBase0
5
+ class Deploy
6
+ class FileUnit < ::SimpleDelegator
7
+ attr_reader :data_key, :fs_path_subpath
8
+
9
+ def initialize(deploy, data_key, fs_path_subpath)
10
+ super(deploy)
11
+ @data_key = data_key
12
+ @fs_path_subpath = fs_path_subpath
13
+ end
14
+
15
+ def run
16
+ assert_source_directory
17
+ link_source_target
18
+ end
19
+
20
+ def assert_source_directory
21
+ infom "Asserting \"#{data_key}\" source directory..."
22
+ instance.host_env.command('mkdir', '-p', source_path).execute!
23
+ end
24
+
25
+ def source_path
26
+ ::File.join(instance.read_entry(:data_fs_path), data_key.to_s)
27
+ end
28
+
29
+ def target_path
30
+ ::File.join(instance.read_entry(:fs_path), fs_path_subpath.to_s)
31
+ end
32
+
33
+ def link_source_target
34
+ infom "Linking \"#{data_key}\" directory..."
35
+ instance.host_env.command('rm', '-rf', target_path).execute!
36
+ instance.host_env.command('ln', '-s', source_path, target_path).execute!
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Avm
4
+ module EacWebappBase0
5
+ class Deploy
6
+ module GitInfo
7
+ def commit_sha1_uncached
8
+ git_fetch
9
+ r = git.rev_parse(git_reference_found)
10
+ return r if r
11
+
12
+ raise ::Avm::Result::Error, "No commit SHA1 found for \"#{git_reference_found}\""
13
+ end
14
+
15
+ def git_reference
16
+ options[:reference] || DEFAULT_REFERENCE
17
+ end
18
+
19
+ def git_reference_found_uncached
20
+ %w[git_reference instance_branch master_branch].map { |b| send(b) }.find(&:present?) ||
21
+ raise(
22
+ ::Avm::Result::Error,
23
+ 'No git reference found (Searched for option, instance and master)'
24
+ )
25
+ end
26
+
27
+ def git_remote_hashs_uncached
28
+ git.remote_hashs(git_remote_name)
29
+ end
30
+
31
+ def git_remote_name
32
+ ::Avm::Git::DEFAULT_REMOTE_NAME
33
+ end
34
+
35
+ def instance_branch
36
+ remote_branch(instance.id)
37
+ end
38
+
39
+ def remote_branch(name)
40
+ git_remote_hashs.key?("refs/heads/#{name}") ? "#{git_remote_name}/#{name}" : nil
41
+ end
42
+
43
+ def master_branch
44
+ remote_branch('master')
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end