capricorn 0.2.25 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (139) hide show
  1. data/LICENSE +19 -0
  2. data/README.md +1 -0
  3. data/bin/capricorn-app-scaffolder +27 -0
  4. data/bin/capricorn-gem-spec +33 -0
  5. data/bin/capricornctl +12 -0
  6. data/bin/capricornd +31 -0
  7. data/erlang/lib/bert/doc/overview.edoc +4 -0
  8. data/erlang/lib/bert/ebin/bert.app +9 -0
  9. data/erlang/lib/bert/rebar.config +3 -0
  10. data/erlang/lib/bert/src/bert.erl +69 -0
  11. data/erlang/lib/bertio/doc/overview.edoc +4 -0
  12. data/erlang/lib/bertio/ebin/bertio.app +9 -0
  13. data/erlang/lib/bertio/rebar.config +3 -0
  14. data/erlang/lib/bertio/src/bertio.erl +28 -0
  15. data/erlang/lib/bertrpc/doc/overview.edoc +4 -0
  16. data/erlang/lib/bertrpc/ebin/bertrpc.app +12 -0
  17. data/erlang/lib/bertrpc/include/bertrpc.hrl +1 -0
  18. data/erlang/lib/bertrpc/rebar.config +5 -0
  19. data/erlang/lib/bertrpc/src/bertrpc.erl +471 -0
  20. data/erlang/lib/bertrpc/src/bertrpc_hello_world.erl +148 -0
  21. data/erlang/lib/bertrpc/src/fd_tcp.erl +376 -0
  22. data/erlang/lib/capricorn/doc/overview.edoc +4 -0
  23. data/erlang/lib/capricorn/ebin/capricorn.app +56 -0
  24. data/erlang/lib/capricorn/include/capricorn.hrl +76 -0
  25. data/erlang/lib/capricorn/rebar.config +3 -0
  26. data/erlang/lib/capricorn/src/cap_application.erl +170 -0
  27. data/erlang/lib/capricorn/src/cap_cluster.erl +121 -0
  28. data/erlang/lib/capricorn/src/cap_cluster_gems.erl +422 -0
  29. data/erlang/lib/capricorn/src/cap_config.erl +25 -0
  30. data/erlang/lib/capricorn/src/cap_dets_updater.erl +26 -0
  31. data/erlang/lib/capricorn/src/cap_event_sup.erl +35 -0
  32. data/erlang/lib/capricorn/src/cap_events.erl +24 -0
  33. data/erlang/lib/capricorn/src/cap_external_api.erl +87 -0
  34. data/erlang/lib/capricorn/src/cap_external_apps_api.erl +125 -0
  35. data/erlang/lib/capricorn/src/cap_external_gems_api.erl +85 -0
  36. data/erlang/lib/capricorn/src/cap_external_machines_api.erl +18 -0
  37. data/erlang/lib/capricorn/src/cap_gem_utils.erl +111 -0
  38. data/erlang/lib/capricorn/src/cap_internal_api.erl +69 -0
  39. data/erlang/lib/capricorn/src/cap_internal_apps_api.erl +38 -0
  40. data/erlang/lib/capricorn/src/cap_log.erl +113 -0
  41. data/erlang/lib/capricorn/src/cap_machine.erl +338 -0
  42. data/erlang/lib/capricorn/src/cap_machine_apps.erl +410 -0
  43. data/erlang/lib/capricorn/src/cap_machine_apps_sup.erl +57 -0
  44. data/erlang/lib/capricorn/src/cap_sup.erl +200 -0
  45. data/erlang/lib/capricorn/src/cap_util.erl +278 -0
  46. data/erlang/lib/capricorn/src/capricorn.erl +45 -0
  47. data/erlang/lib/capricorn/src/capricorn_app.erl +36 -0
  48. data/erlang/lib/emq/ebin/emq.app +17 -0
  49. data/erlang/lib/emq/src/emq.erl +261 -0
  50. data/erlang/lib/emq/src/emq_app.erl +11 -0
  51. data/erlang/lib/emq/src/emq_pool.erl +178 -0
  52. data/erlang/lib/emq/src/emq_queue.erl +332 -0
  53. data/erlang/lib/emq/src/emq_status.erl +148 -0
  54. data/erlang/lib/emq/src/emq_sup.erl +132 -0
  55. data/erlang/lib/gcd/ebin/gcd.app +18 -0
  56. data/erlang/lib/gcd/include/gcd.hrl +5 -0
  57. data/erlang/lib/gcd/rebar.config +3 -0
  58. data/erlang/lib/gcd/src/gcd.erl +51 -0
  59. data/erlang/lib/gcd/src/gcd_app.erl +13 -0
  60. data/erlang/lib/gcd/src/gcd_event.erl +39 -0
  61. data/erlang/lib/gcd/src/gcd_server.erl +30 -0
  62. data/erlang/lib/gcd/src/gcd_srv.erl +186 -0
  63. data/erlang/lib/gcd/src/gcd_sup.erl +18 -0
  64. data/erlang/rebar +0 -0
  65. data/erlang/rebar.config +9 -0
  66. data/erlang/rel/overlay/bin/capricornd +146 -0
  67. data/erlang/rel/overlay/erts-vsn/bin/erl +34 -0
  68. data/erlang/rel/overlay/erts-vsn/bin/nodetool +80 -0
  69. data/erlang/rel/overlay/etc/capricorn/app.config +59 -0
  70. data/erlang/rel/overlay/etc/capricorn/cluster-vm.args +21 -0
  71. data/erlang/rel/overlay/etc/capricorn/machine-vm.args +21 -0
  72. data/erlang/rel/reltool.config +43 -0
  73. data/ext/Makefile +2 -0
  74. data/ext/extconf.rb +1 -0
  75. data/lib/capricorn-client.rb +86 -0
  76. data/lib/capricorn-client/cli/applications.rb +256 -0
  77. data/lib/capricorn-client/cli/gems.rb +57 -0
  78. data/lib/capricorn-client/cli/machines.rb +9 -0
  79. data/lib/capricorn-client/helpers.rb +62 -0
  80. data/lib/capricorn.rb +5 -99
  81. data/lib/capricorn/driver.rb +86 -0
  82. data/lib/capricorn/recipes/apache-debian.rb +112 -0
  83. data/lib/capricorn/recipes/centos-plesk.rb +162 -0
  84. data/lib/capricorn/recipes/macports.rb +54 -0
  85. data/lib/capricorn/system_context.rb +49 -0
  86. data/lib/capricorn/version.rb +5 -0
  87. metadata +233 -74
  88. data/app_generators/engine/engine_generator.rb +0 -40
  89. data/app_generators/engine/templates/Gmfile +0 -20
  90. data/app_generators/engine/templates/MIT-LICENSE.txt +0 -20
  91. data/app_generators/engine/templates/README.rdoc +0 -7
  92. data/app_generators/engine/templates/config/initializers/rails_init.rb +0 -1
  93. data/app_generators/engine/templates/config/routes.rb +0 -2
  94. data/app_generators/engine/templates/gitignore +0 -9
  95. data/app_generators/engine/templates/init.rb +0 -1
  96. data/app_generators/engine/templates/lib/engine.rb +0 -4
  97. data/app_generators/engine/templates/rails/init.rb +0 -1
  98. data/app_generators/engine/templates/tasks/engine_tasks.rake +0 -4
  99. data/bin/capricorn +0 -20
  100. data/lib/capricorn/actor.rb +0 -23
  101. data/lib/capricorn/actor/actions.rb +0 -76
  102. data/lib/capricorn/actors/apache_actor.rb +0 -56
  103. data/lib/capricorn/actors/base_actor.rb +0 -335
  104. data/lib/capricorn/actors/host_file_actor.rb +0 -77
  105. data/lib/capricorn/actors/mysql_actor.rb +0 -20
  106. data/lib/capricorn/actors/passenger_actor.rb +0 -28
  107. data/lib/capricorn/actors/plesk_actor.rb +0 -228
  108. data/lib/capricorn/actors/sqlite3_actor.rb +0 -44
  109. data/lib/capricorn/app_runner.rb +0 -108
  110. data/lib/capricorn/apps/dev.rb +0 -15
  111. data/lib/capricorn/apps/engines.rb +0 -33
  112. data/lib/capricorn/apps/jobs.rb +0 -35
  113. data/lib/capricorn/apps/satellite.rb +0 -68
  114. data/lib/capricorn/apps/server.rb +0 -73
  115. data/lib/capricorn/client.rb +0 -48
  116. data/lib/capricorn/client/auth_token.rb +0 -98
  117. data/lib/capricorn/daemon.rb +0 -81
  118. data/lib/capricorn/exception_handler.rb +0 -79
  119. data/lib/capricorn/extentions/rubygems_plugin.rb +0 -27
  120. data/lib/capricorn/extentions/thor_extentions.rb +0 -32
  121. data/lib/capricorn/job_queue.rb +0 -203
  122. data/lib/capricorn/satellite.rb +0 -52
  123. data/lib/capricorn/satellite/actions.rb +0 -55
  124. data/lib/capricorn/satellite/dependency_loader.rb +0 -82
  125. data/lib/capricorn/satellite/persistence.rb +0 -50
  126. data/lib/capricorn/server.rb +0 -144
  127. data/lib/capricorn/server/daemon.rb +0 -83
  128. data/lib/capricorn/server/proxy.rb +0 -25
  129. data/lib/capricorn/server/security.rb +0 -120
  130. data/lib/capricorn/system.rb +0 -218
  131. data/lib/capricorn/system/config.rb +0 -49
  132. data/lib/capricorn/system/helper.rb +0 -21
  133. data/lib/capricorn/system/options.rb +0 -79
  134. data/lib/capricorn/system/process_user.rb +0 -73
  135. data/lib/capricorn/system/satellites.rb +0 -44
  136. data/lib/capricorn/system/shell.rb +0 -80
  137. data/lib/rubygems_plugin.rb +0 -1
  138. data/spec/actor/actions_spec.rb +0 -13
  139. data/spec/spec_helper.rb +0 -1
@@ -1,15 +0,0 @@
1
- Capricorn.runtime_gem('thor', Capricorn::THOR_VERSION)
2
-
3
- module Capricorn
4
- module Apps # :nodoc:
5
-
6
- class Dev < Thor
7
- desc "activate DOMAIN NAME", "turn an existing satelite into a development satelite"
8
- def activate(domain, name)
9
- Capricorn.runtime_gem('rubigen', Capricorn::RUBIGEN_VERSION)
10
- Capricorn.client.make_development_satellite(domain, name)
11
- end
12
- end
13
-
14
- end
15
- end
@@ -1,33 +0,0 @@
1
- Capricorn.runtime_gem('thor', Capricorn::THOR_VERSION)
2
-
3
- module Capricorn
4
- module Apps # :nodoc:
5
-
6
- class Engines < Thor
7
- desc 'install DOMAIN NAME', 'install an engine'
8
- method_options :version => :required, :lib => :optional, :source => :optional, :token => :optional, :immediate => :boolean
9
- def install(domain, name)
10
- desc = { :version => options[:version] }
11
- desc[:lib] = options[:lib] if options[:lib]
12
- desc[:source] = options[:source] if options[:source]
13
- Capricorn.client(options[:token]).install_engine(domain, name, desc, options[:immediate])
14
- end
15
-
16
- desc 'update DOMAIN NAME', 'update an engine'
17
- method_options :version => :required, :lib => :optional, :source => :optional, :token => :optional, :immediate => :boolean
18
- def update(domain, name)
19
- desc = { :version => options[:version] }
20
- desc[:lib] = options[:lib] if options[:lib]
21
- desc[:source] = options[:source] if options[:source]
22
- Capricorn.client(options[:token]).update_engine(domain, name, desc, options[:immediate])
23
- end
24
-
25
- desc 'uninstall DOMAIN NAME', 'uninstall an engine'
26
- method_options :token => :optional, :immediate => :boolean
27
- def uninstall(domain, name)
28
- Capricorn.client(options[:token]).uninstall_engine(domain, name, options[:immediate])
29
- end
30
- end
31
-
32
- end
33
- end
@@ -1,35 +0,0 @@
1
- Capricorn.runtime_gem('thor', Capricorn::THOR_VERSION)
2
-
3
- module Capricorn
4
- module Apps
5
- class Jobs < Thor
6
-
7
- desc "list", 'list the jobs in the queue'
8
- method_options :token => :optional
9
- def list
10
- queued_jobs = Capricorn.client(options[:token]).queued_jobs
11
- queued_jobs.each do |id, name, canceled, immediated, running, waiting, delay|
12
- status = []
13
- status.push canceled ? 'c' : ' '
14
- status.push immediated ? 'i' : ' '
15
- status.push running ? 'r' : ' '
16
- status.push waiting ? 'w' : ' '
17
- puts("% 8d % 8d % 8s %s" % [id, delay.to_i, status.join, name])
18
- end
19
- end
20
-
21
- desc "cancel ID", 'cancel the job with ID'
22
- method_options :token => :optional
23
- def cancel(id)
24
- Capricorn.client(options[:token]).cancel_job(id.to_i)
25
- end
26
-
27
- desc "immediate ID", 'immediately run the job with ID'
28
- method_options :token => :optional
29
- def immediate(id)
30
- Capricorn.client(options[:token]).immediate_job(id.to_i)
31
- end
32
-
33
- end
34
- end
35
- end
@@ -1,68 +0,0 @@
1
- Capricorn.runtime_gem('thor', Capricorn::THOR_VERSION)
2
-
3
- module Capricorn
4
- module Apps # :nodoc:
5
-
6
- class Satellite < Thor
7
- desc 'list [DOMAIN]', 'show info for managed satellites'
8
- method_options :token => :optional
9
- def list(domain=nil)
10
- satellites = Capricorn.client(options[:token]).satellites.dup
11
-
12
- names_by_index = []
13
- satellites.size.times do |i|
14
- sat = satellites[i]
15
- names_by_index.push([sat.domain, i])
16
- end
17
- names_by_index.sort! { |a,b| a.first <=> b.first }
18
-
19
- names_by_index.each do |(domain_, i)|
20
- sat = satellites[i]
21
- if domain.nil? or sat.domain.include?(domain)
22
- puts [
23
- sat.domain,
24
- (sat.development ? ['development:', sat.module_name] : [])
25
- ].flatten.join(' ')
26
- sat.engines.each do |name, options|
27
- puts "- #{name} #{options.inspect}"
28
- end
29
- end
30
- end
31
- rescue => e
32
- p e
33
- puts e.backtrace
34
- end
35
-
36
- desc 'install DOMAIN', 'install a satellite'
37
- method_options :token => :optional, :immediate => :boolean
38
- def install(domain)
39
- Capricorn.client(options[:token]).install_satellite(domain, options[:immediate])
40
- end
41
-
42
- desc 'uninstall DOMAIN', 'uninstall a satellite'
43
- method_options :token => :optional, :immediate => :boolean
44
- def uninstall(domain)
45
- Capricorn.client(options[:token]).uninstall_satellite(domain, options[:immediate])
46
- end
47
-
48
- desc 'relink DOMAIN', 'relink a satellite'
49
- method_options :token => :optional, :immediate => :boolean
50
- def relink(domain)
51
- Capricorn.client(options[:token]).relink_satellite(domain, options[:immediate])
52
- end
53
-
54
- desc 'update DOMAIN', 'update the installed engines of a satellite'
55
- method_options :token => :optional, :immediate => :boolean
56
- def update(domain)
57
- Capricorn.client(options[:token]).update_satellite(domain, options[:immediate])
58
- end
59
-
60
- # desc 'upgrade DOMAIN', 'upgrade/rebuild the rails app of a satellite'
61
- # method_options :token => :optional, :immediate => :boolean
62
- # def upgrade(domain)
63
- # Capricorn.client(options[:token]).upgrade_satellite(domain)
64
- # end
65
- end
66
-
67
- end
68
- end
@@ -1,73 +0,0 @@
1
- Capricorn.runtime_gem('thor', Capricorn::THOR_VERSION)
2
-
3
- module Capricorn
4
- module Apps
5
-
6
- class Server < Thor
7
-
8
- desc "start", 'start the server'
9
- method_options :foreground => :boolean, :config => :optional
10
- def start
11
- Capricorn.server? true
12
- Capricorn::System.load!(options[:root_path])
13
-
14
- begin
15
- FileUtils.mkdir_p(Capricorn.system.root)
16
- rescue Errno::EACCES
17
- Capricorn.logger.out.fatal "must be executed as root"
18
- exit(1)
19
- end
20
-
21
- unless Capricorn.system.is_user('root')
22
- Capricorn.logger.out.fatal "must be executed as root"
23
- exit(1)
24
- end
25
-
26
- if options[:foreground]
27
- Capricorn::Server.start
28
- else
29
- Capricorn::Server.daemonize
30
- end
31
- end
32
-
33
- desc "stop", 'stop the server'
34
- method_options :token => :optional
35
- def stop
36
- Capricorn.client(options[:token]).stop_server
37
- end
38
-
39
- desc "restart", 'restart the server'
40
- method_options :token => :optional
41
- def restart
42
- Capricorn.client(options[:token]).restart_server
43
- end
44
-
45
- desc "reload", 'reload the server'
46
- method_options :token => :optional
47
- def reload
48
- Capricorn.client(options[:token]).reload_server
49
- end
50
-
51
- desc "update", 'update the capricorn'
52
- method_options :token => :optional
53
- def update
54
- Capricorn.client(options[:token]).update_server
55
- end
56
-
57
- desc "gupdate", 'update the gems'
58
- method_options :token => :optional
59
- def gupdate
60
- Capricorn.client(options[:token]).update_gems
61
- end
62
-
63
- desc "version", 'version of the server'
64
- method_options :token => :optional
65
- def version
66
- puts "Client: #{Capricorn.version}"
67
- puts "Server: #{Capricorn.client(options[:token]).server_version}"
68
- end
69
-
70
- end
71
-
72
- end
73
- end
@@ -1,48 +0,0 @@
1
- require 'drb'
2
- require 'drb/ssl'
3
- require 'uri'
4
-
5
- module Capricorn
6
- class Client
7
-
8
- autoload :AuthToken, File.dirname(__FILE__)+'/client/auth_token'
9
-
10
- # return a DRb uri for the given Capricorn uri.
11
- def self.parse_uri(uri)
12
- uri = URI.parse(uri)
13
- use_ssl = (uri.scheme == 'ssl+capricorn')
14
- uri.scheme = 'druby'
15
- return use_ssl, uri.to_s
16
- end
17
-
18
- # return an potentialy initialize the client to the given token.
19
- def self.current(token=nil)
20
- @client = connect(token) unless @client
21
- @client
22
- end
23
-
24
- # connect to the server referenced by the given token.
25
- def self.connect(token=nil)
26
- token ||= 'core.token'
27
-
28
- [Capricorn::DEFAULT_ROOT_SYSTEM_DIR,
29
- Capricorn::DEFAULT_USER_SYSTEM_DIR,
30
- File.join(Capricorn::DEFAULT_USER_SYSTEM_DIR, 'tokens'),
31
- '.'].each do |path|
32
- path = File.expand_path(File.join(path, token))
33
- if File.file? path
34
- token = path
35
- break
36
- end
37
- end
38
-
39
- unless File.file? token
40
- raise "Unable to read the token at: #{token}"
41
- end
42
-
43
- token = Capricorn::Client::AuthToken.load_file(token) if String === token
44
- token.connect if token
45
- end
46
-
47
- end
48
- end
@@ -1,98 +0,0 @@
1
- require 'yaml'
2
-
3
- module Capricorn
4
- class Client
5
- class AuthToken
6
-
7
- # load a token from the passed IO.
8
- def self.load(io)
9
- self.new YAML.load(io)
10
- end
11
-
12
- # load a token from a file referenced by the given +path+.
13
- def self.load_file(path)
14
- self.new YAML.load_file(path)
15
- end
16
-
17
- # the uri at which the capricorn server can be accessed.
18
- attr_reader :target_uri
19
- # the SSL verification mode used by the capricorn server
20
- attr_reader :verify_mode
21
- # the optional CA certificate used by the capricorn server
22
- attr_reader :ca_certificate_data
23
- # the private key used by the client
24
- attr_reader :private_key_data
25
- # the certificate used by the client
26
- attr_reader :certificate_data
27
-
28
- # create a new token from the given options
29
- #
30
- # +:target_uri+, +:verify_mode+, +:ca_certificate_data+, +:private_key_data+, +:certificate_data+
31
- def initialize(options={})
32
- @target_uri = options[:target_uri]
33
- @verify_mode = options[:verify_mode]
34
- @ca_certificate_data = options[:ca_certificate_data]
35
- @private_key_data = options[:private_key_data]
36
- @certificate_data = options[:certificate_data]
37
- end
38
-
39
- # get the parsed and initialized OpenSSL::X509::Certificate
40
- def ca_certificate
41
- @ca_certificate ||= OpenSSL::X509::Certificate.new(@ca_certificate_data)
42
- end
43
-
44
- # get the parsed and initialized OpenSSL::X509::Certificate
45
- def certificate
46
- @certificate ||= OpenSSL::X509::Certificate.new(@certificate_data)
47
- end
48
-
49
- # get the parsed and initialized OpenSSL::PKey::RSA
50
- def private_key
51
- @private_key ||= OpenSSL::PKey::RSA.new(@private_key_data)
52
- end
53
-
54
- # connect to the server and return the server handle.
55
- def connect
56
- use_ssl, uri = Capricorn::Client.parse_uri(self.target_uri)
57
- if use_ssl
58
- DRb.start_service nil, nil, self.options_for_drb
59
- else
60
- DRb.start_service
61
- end
62
- DRbObject.new nil, uri
63
- end
64
-
65
- # return options for use with DRb
66
- def options_for_drb
67
- @options_for_drb ||= {
68
- :SSLVerifyMode => self.verify_mode,
69
- :SSLCACertificate => self.ca_certificate,
70
- :SSLPrivateKey => self.private_key,
71
- :SSLCertificate => self.certificate
72
- }
73
- end
74
-
75
- # dump this token to the given IO or return the content as a String
76
- def dump(io=nil)
77
- data = {
78
- :target_uri => self.target_uri,
79
- :verify_mode => self.verify_mode,
80
- :ca_certificate_data => self.ca_certificate_data,
81
- :private_key_data => self.private_key_data,
82
- :certificate_data => self.certificate_data
83
- }
84
- if io
85
- io.write YAML.dump(data)
86
- else
87
- YAML.dump(data)
88
- end
89
- end
90
-
91
- # dump this token to a file at the given +path+.
92
- def dump_file(path)
93
- File.open(path, 'w+') { |f| dump(f) }
94
- end
95
-
96
- end
97
- end
98
- end
@@ -1,81 +0,0 @@
1
-
2
- module Capricorn
3
- module Daemon
4
- class Base
5
-
6
- def self.daemonize(&block)
7
- Capricorn::Daemon::Controller.start(self,&block)
8
- end
9
-
10
- end
11
-
12
- module PidFile
13
- def self.store(pid)
14
- File.open(self.pid_file, 'w') {|f| f << pid}
15
- end
16
-
17
- def self.recall
18
- IO.read(self.pid_file).to_i rescue nil
19
- end
20
-
21
- def self.destroy
22
- FileUtils.rm(self.pid_file) if self.exist?
23
- end
24
-
25
- def self.pid_file
26
- Capricorn.system.path("Server.pid")
27
- end
28
-
29
- def self.exist?
30
- File.file?(self.pid_file)
31
- end
32
- end
33
-
34
- module Controller
35
-
36
- def self.start(daemon, &block)
37
- fork do
38
- Process.setsid
39
- exit if fork
40
- if PidFile.exist?
41
- puts "Pid file #{PidFile.pid_file} already exists. Not starting."
42
- exit 1
43
- end
44
-
45
- setup_child
46
- regitser_handlers(daemon, &block)
47
-
48
- daemon.start
49
- end
50
- puts "Daemon started."
51
- end
52
-
53
- def self.stop
54
- if !Capricorn::Daemon::PidFile.exist?
55
- puts "Pid file not found. Is the daemon started?"
56
- exit
57
- end
58
- pid = Capricorn::Daemon::PidFile.recall
59
- pid && Process.kill("TERM", pid)
60
- rescue Errno::ESRCH
61
- puts "Pid file found, but process was not running. The daemon may have died."
62
- end
63
-
64
- private
65
-
66
- def self.setup_child
67
- Capricorn::Daemon::PidFile.store(Process.pid)
68
- Dir.chdir Capricorn.system.root
69
- File.umask 0000
70
- Capricorn::ExceptionHandler.redirect_std
71
- end
72
-
73
- def self.regitser_handlers(daemon, &block)
74
- trap("TERM") { daemon.stop; exit }
75
- at_exit { Capricorn::Daemon::PidFile.destroy if $master }
76
- at_exit(&block) if block
77
- end
78
-
79
- end
80
- end
81
- end