harbor 0.16.1
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/Rakefile +76 -0
- data/bin/harbor +7 -0
- data/lib/harbor.rb +17 -0
- data/lib/harbor/accessor_injector.rb +30 -0
- data/lib/harbor/application.rb +172 -0
- data/lib/harbor/auth/basic.rb +51 -0
- data/lib/harbor/block_io.rb +63 -0
- data/lib/harbor/cache.rb +90 -0
- data/lib/harbor/cache/disk.rb +99 -0
- data/lib/harbor/cache/item.rb +48 -0
- data/lib/harbor/cache/memory.rb +35 -0
- data/lib/harbor/cascade.rb +75 -0
- data/lib/harbor/console.rb +34 -0
- data/lib/harbor/container.rb +134 -0
- data/lib/harbor/contrib/debug.rb +236 -0
- data/lib/harbor/contrib/session/data_mapper.rb +74 -0
- data/lib/harbor/daemon.rb +105 -0
- data/lib/harbor/errors.rb +49 -0
- data/lib/harbor/events.rb +45 -0
- data/lib/harbor/exception_notifier.rb +59 -0
- data/lib/harbor/file.rb +66 -0
- data/lib/harbor/file_store.rb +69 -0
- data/lib/harbor/file_store/file.rb +100 -0
- data/lib/harbor/file_store/local.rb +71 -0
- data/lib/harbor/file_store/mosso.rb +154 -0
- data/lib/harbor/file_store/mosso/private.rb +8 -0
- data/lib/harbor/generator.rb +56 -0
- data/lib/harbor/generator/help.rb +34 -0
- data/lib/harbor/generator/setup.rb +82 -0
- data/lib/harbor/generator/skeletons/basic/config.ru.skel +21 -0
- data/lib/harbor/generator/skeletons/basic/lib/appname.rb.skel +49 -0
- data/lib/harbor/generator/skeletons/basic/lib/appname/controllers/home.rb +9 -0
- data/lib/harbor/generator/skeletons/basic/lib/appname/views/home/index.html.erb.skel +23 -0
- data/lib/harbor/generator/skeletons/basic/lib/appname/views/layouts/application.html.erb.skel +48 -0
- data/lib/harbor/generator/skeletons/basic/lib/appname/views/layouts/exception.html.erb.skel +13 -0
- data/lib/harbor/generator/skeletons/basic/lib/appname/views/layouts/login.html.erb.skel +11 -0
- data/lib/harbor/generator/skeletons/basic/log/development.log +0 -0
- data/lib/harbor/hooks.rb +105 -0
- data/lib/harbor/json_cookies.rb +37 -0
- data/lib/harbor/layouts.rb +61 -0
- data/lib/harbor/locale.rb +50 -0
- data/lib/harbor/locales.txt +22 -0
- data/lib/harbor/logging.rb +39 -0
- data/lib/harbor/logging/appenders/email.rb +84 -0
- data/lib/harbor/logging/request_logger.rb +34 -0
- data/lib/harbor/mail_servers/abstract.rb +12 -0
- data/lib/harbor/mail_servers/sendmail.rb +19 -0
- data/lib/harbor/mail_servers/smtp.rb +25 -0
- data/lib/harbor/mail_servers/test.rb +17 -0
- data/lib/harbor/mailer.rb +96 -0
- data/lib/harbor/messages.rb +17 -0
- data/lib/harbor/mime.rb +206 -0
- data/lib/harbor/plugin.rb +52 -0
- data/lib/harbor/plugin_list.rb +38 -0
- data/lib/harbor/request.rb +138 -0
- data/lib/harbor/response.rb +281 -0
- data/lib/harbor/router.rb +112 -0
- data/lib/harbor/script.rb +155 -0
- data/lib/harbor/session.rb +75 -0
- data/lib/harbor/session/abstract.rb +27 -0
- data/lib/harbor/session/cookie.rb +17 -0
- data/lib/harbor/shellwords.rb +26 -0
- data/lib/harbor/test/mailer.rb +10 -0
- data/lib/harbor/test/request.rb +28 -0
- data/lib/harbor/test/response.rb +17 -0
- data/lib/harbor/test/session.rb +11 -0
- data/lib/harbor/test/test.rb +22 -0
- data/lib/harbor/version.rb +3 -0
- data/lib/harbor/view.rb +89 -0
- data/lib/harbor/view_context.rb +134 -0
- data/lib/harbor/view_context/helpers.rb +7 -0
- data/lib/harbor/view_context/helpers/cache.rb +77 -0
- data/lib/harbor/view_context/helpers/form.rb +34 -0
- data/lib/harbor/view_context/helpers/html.rb +26 -0
- data/lib/harbor/view_context/helpers/text.rb +120 -0
- data/lib/harbor/view_context/helpers/url.rb +11 -0
- data/lib/harbor/xml_view.rb +57 -0
- data/lib/harbor/zipped_io.rb +203 -0
- data/lib/vendor/cloudfiles-1.3.0/cloudfiles.rb +77 -0
- data/lib/vendor/cloudfiles-1.3.0/cloudfiles/authentication.rb +46 -0
- data/lib/vendor/cloudfiles-1.3.0/cloudfiles/connection.rb +280 -0
- data/lib/vendor/cloudfiles-1.3.0/cloudfiles/container.rb +260 -0
- data/lib/vendor/cloudfiles-1.3.0/cloudfiles/storage_object.rb +253 -0
- metadata +155 -0
@@ -0,0 +1,74 @@
|
|
1
|
+
require "dm-core"
|
2
|
+
require "dm-timestamps"
|
3
|
+
|
4
|
+
module Harbor
|
5
|
+
module Contrib
|
6
|
+
class Session
|
7
|
+
##
|
8
|
+
# This is a database backed session handle for DataMapper. You can use it
|
9
|
+
# instead of the builtin Harbor::Session::Cookie by doing:
|
10
|
+
#
|
11
|
+
# Harbor::Session.configure do |session|
|
12
|
+
# session.store = Harbor::Contrib::Session::DataMapper
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# A basic Session resource is defined for you.
|
16
|
+
##
|
17
|
+
class DataMapper < Harbor::Session::Abstract
|
18
|
+
|
19
|
+
class SessionHash < Hash
|
20
|
+
def initialize(instance)
|
21
|
+
super()
|
22
|
+
@instance = instance
|
23
|
+
merge!(@instance.data)
|
24
|
+
end
|
25
|
+
|
26
|
+
def [](key)
|
27
|
+
key == :session_id ? @instance.id : super
|
28
|
+
end
|
29
|
+
|
30
|
+
def []=(key, value)
|
31
|
+
raise ArgumentError.new("You cannot manually set the session_id for a session.") if key == :session_id
|
32
|
+
|
33
|
+
super
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_hash
|
37
|
+
{}.merge(reject { |key,| key == :session_id })
|
38
|
+
end
|
39
|
+
|
40
|
+
def instance
|
41
|
+
@instance
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.load_session(cookie)
|
46
|
+
session = if expire_after = Harbor::Session.options[:expire_after]
|
47
|
+
::Session.first(:id => cookie, :created_at.gte => Time.now - expire_after)
|
48
|
+
else
|
49
|
+
::Session.get(cookie)
|
50
|
+
end
|
51
|
+
|
52
|
+
session ||= ::Session.create
|
53
|
+
|
54
|
+
SessionHash.new(session)
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.commit_session(data)
|
58
|
+
record = data.instance
|
59
|
+
record.update_attributes(:data => data.to_hash)
|
60
|
+
record.id
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class ::Session #:nodoc:
|
66
|
+
include DataMapper::Resource
|
67
|
+
|
68
|
+
property :id, String, :key => true, :default => lambda { `uuidgen`.chomp }
|
69
|
+
property :data, Object, :default => {}
|
70
|
+
property :created_at, DateTime
|
71
|
+
property :updated_at, DateTime
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module Harbor
|
2
|
+
|
3
|
+
class Daemon
|
4
|
+
|
5
|
+
START_CONTEXT = {
|
6
|
+
:argv => ARGV.map { |arg| arg.dup },
|
7
|
+
:cwd => `/bin/sh -c pwd`.chomp("\n"),
|
8
|
+
:cmd => $0.dup
|
9
|
+
}
|
10
|
+
|
11
|
+
DEFAULT_SLEEP_SECONDS = 60
|
12
|
+
|
13
|
+
attr_accessor :logger
|
14
|
+
attr_reader :worker, :options
|
15
|
+
|
16
|
+
def initialize(worker, log_file, seconds_between_runs = DEFAULT_SLEEP_SECONDS)
|
17
|
+
@worker = worker
|
18
|
+
|
19
|
+
# The template may include the tag %PID to be replaced with
|
20
|
+
# the PID of the forked worker process. Don't use @log_file
|
21
|
+
# directly, use self.log_file
|
22
|
+
@log_file_template = log_file
|
23
|
+
|
24
|
+
@seconds_between_runs = seconds_between_runs
|
25
|
+
end
|
26
|
+
|
27
|
+
def log_file
|
28
|
+
@log_file ||= @log_file_template.gsub('%PID', Process.pid.to_s)
|
29
|
+
end
|
30
|
+
|
31
|
+
def detach
|
32
|
+
srand
|
33
|
+
fork and exit
|
34
|
+
Process.setsid # detach -- we want to be able to close our shell!
|
35
|
+
|
36
|
+
log_directory = ::File.dirname(self.log_file)
|
37
|
+
::File.mkdir_p(log_directory) unless ::File.directory?(log_directory)
|
38
|
+
|
39
|
+
redirect_io(@log_file)
|
40
|
+
end
|
41
|
+
|
42
|
+
def run
|
43
|
+
@restart = false
|
44
|
+
@alive = true
|
45
|
+
|
46
|
+
# graceful restart
|
47
|
+
trap(:HUP) do
|
48
|
+
logger.info "Restarting gracefully." if logger
|
49
|
+
@restart = true
|
50
|
+
@alive = nil
|
51
|
+
@worker.alive = nil
|
52
|
+
end
|
53
|
+
|
54
|
+
# graceful shutdown
|
55
|
+
trap(:QUIT) do
|
56
|
+
logger.info "Shutting down gracefully." if logger
|
57
|
+
@alive = nil
|
58
|
+
@worker.alive = nil
|
59
|
+
end
|
60
|
+
|
61
|
+
# quick exit
|
62
|
+
[:TERM, :INT].each do |sig|
|
63
|
+
trap(sig) do
|
64
|
+
logger.info "Shutting down NOW" if logger
|
65
|
+
cleanup!
|
66
|
+
|
67
|
+
exit!(0)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
begin
|
72
|
+
@worker.run
|
73
|
+
sleep(@seconds_between_runs) if @alive
|
74
|
+
end while @alive
|
75
|
+
|
76
|
+
if @restart
|
77
|
+
logger.info "Starting new daemon..."
|
78
|
+
fork do
|
79
|
+
Dir.chdir(START_CONTEXT[:cwd])
|
80
|
+
exec(START_CONTEXT[:cmd], *START_CONTEXT[:argv])
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
logger.info "Shutting down..." if logger
|
85
|
+
cleanup!
|
86
|
+
end
|
87
|
+
|
88
|
+
def cleanup!
|
89
|
+
worker.cleanup! if worker.respond_to?(:cleanup!)
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def redirect_io(file = nil)
|
95
|
+
STDIN.reopen "/dev/null"
|
96
|
+
STDOUT.reopen file || "/dev/null"
|
97
|
+
STDOUT.sync = true
|
98
|
+
|
99
|
+
STDERR.reopen STDOUT
|
100
|
+
STDERR.sync = true
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Harbor
|
2
|
+
|
3
|
+
class Errors
|
4
|
+
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
def initialize(errors = [])
|
8
|
+
@errors = errors
|
9
|
+
end
|
10
|
+
|
11
|
+
def [](index)
|
12
|
+
errors[index]
|
13
|
+
end
|
14
|
+
|
15
|
+
def <<(message)
|
16
|
+
if message.is_a?(String)
|
17
|
+
errors << message
|
18
|
+
elsif message.is_a?(Enumerable)
|
19
|
+
message.each do |error_message|
|
20
|
+
errors << error_message
|
21
|
+
end
|
22
|
+
else
|
23
|
+
errors << message
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def each
|
28
|
+
errors.each do |error|
|
29
|
+
yield error
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def size
|
34
|
+
errors.size
|
35
|
+
end
|
36
|
+
|
37
|
+
def +(other)
|
38
|
+
Errors.new((errors + other.errors).uniq)
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
def errors
|
44
|
+
@errors
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Harbor
|
2
|
+
module Events
|
3
|
+
|
4
|
+
def self.included(target)
|
5
|
+
target.extend(ClassMethods)
|
6
|
+
end
|
7
|
+
|
8
|
+
def raise_event(name, *args)
|
9
|
+
if self.class.events[name].nil?
|
10
|
+
return false
|
11
|
+
else
|
12
|
+
self.class.events[name].each do |event|
|
13
|
+
event.call(*args)
|
14
|
+
end
|
15
|
+
return true
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module ClassMethods
|
20
|
+
|
21
|
+
def events
|
22
|
+
class_variable_defined?(:@@events) ? class_variable_get(:@@events) : class_variable_set(:@@events, {})
|
23
|
+
end
|
24
|
+
|
25
|
+
def events=(hash)
|
26
|
+
events.replace(hash)
|
27
|
+
end
|
28
|
+
|
29
|
+
def register_event(name, &block)
|
30
|
+
events[name] ||= []
|
31
|
+
events[name] << block
|
32
|
+
end
|
33
|
+
|
34
|
+
def clear_events!(name=nil)
|
35
|
+
if name
|
36
|
+
events[name] = nil
|
37
|
+
else
|
38
|
+
events = {}
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Harbor
|
2
|
+
##
|
3
|
+
# Utility class for receiving email notifications of exceptions in
|
4
|
+
# non-development environments.
|
5
|
+
#
|
6
|
+
# services.register("mailer", Harbor::Mailer)
|
7
|
+
# services.register("mail_server", Harbor::SendmailServer)
|
8
|
+
#
|
9
|
+
# require 'harbor/exception_notifier'
|
10
|
+
#
|
11
|
+
# You will then receive email alerts for all 500 errors in the format of:
|
12
|
+
#
|
13
|
+
# From: errors@request_host
|
14
|
+
# Subject: [ERROR] [request_host] [environment] Exception description
|
15
|
+
# Body: stack trace found in log, with request details.
|
16
|
+
##
|
17
|
+
class ExceptionNotifier
|
18
|
+
|
19
|
+
def self.notification_address=(address)
|
20
|
+
@@notification_address = address
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.notification_address
|
24
|
+
@@notification_address
|
25
|
+
rescue NameError
|
26
|
+
raise "Harbor::ExceptionMailer.notification_address not set."
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.notification_address?
|
30
|
+
defined?(@@notification_address)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.notify(exception, request, response, trace)
|
34
|
+
return if request.environment == "development"
|
35
|
+
|
36
|
+
mailer = request.application.services.get("mailer")
|
37
|
+
mailer.to = notification_address
|
38
|
+
|
39
|
+
host = request.env["HTTP_X_FORWARDED_HOST"] || request.host
|
40
|
+
mailer.from = "errors@#{host}"
|
41
|
+
|
42
|
+
subject = exception.to_s
|
43
|
+
|
44
|
+
# We can't have multi-line subjects, so we chop off extra lines
|
45
|
+
if subject[$/]
|
46
|
+
subject = subject.split($/, 2)[0] + "..."
|
47
|
+
end
|
48
|
+
|
49
|
+
mailer.subject = "[ERROR] [#{request.host}] [#{request.environment}] #{subject}"
|
50
|
+
mailer.text = trace
|
51
|
+
mailer.set_header("X-Priority", 1)
|
52
|
+
mailer.set_header("X-MSMail-Priority", "High")
|
53
|
+
mailer.send!
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
Harbor::Application.register_event(:exception) { |*args| Harbor::ExceptionNotifier.notify(*args) }
|
data/lib/harbor/file.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module Harbor
|
4
|
+
|
5
|
+
class File
|
6
|
+
|
7
|
+
attr_accessor :path, :name
|
8
|
+
|
9
|
+
def initialize(path, name = nil)
|
10
|
+
@path = path
|
11
|
+
@name = name || ::File.basename(@path)
|
12
|
+
end
|
13
|
+
|
14
|
+
def read(block_size)
|
15
|
+
@io ||= File.open(@path, "rb")
|
16
|
+
yield @io.read(block_size)
|
17
|
+
end
|
18
|
+
|
19
|
+
def rewind
|
20
|
+
@io ||= File.open(@path, "rb")
|
21
|
+
@io.rewind
|
22
|
+
end
|
23
|
+
|
24
|
+
def size
|
25
|
+
::File.size(@path)
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
# The file is first copied, and then the provided block is run.
|
30
|
+
# If no errors occur, the source file is deleted. If an error
|
31
|
+
# occurs, the copied file is removed and the directory cleaned.
|
32
|
+
##
|
33
|
+
def self.move_safely(from, to, mode = 0666 - ::File.umask)
|
34
|
+
raise ArgumentError.new("no block given") unless block_given?
|
35
|
+
|
36
|
+
FileUtils.mkdir_p(::File.dirname(to))
|
37
|
+
FileUtils.cp(from, to)
|
38
|
+
FileUtils.chmod(mode, to)
|
39
|
+
begin
|
40
|
+
yield
|
41
|
+
FileUtils.rm(from)
|
42
|
+
rescue
|
43
|
+
FileUtils.rm(to)
|
44
|
+
rmdir_p(::File.dirname(to))
|
45
|
+
raise $!
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# Recursively delete empty directories as mkdir -p recursively
|
51
|
+
# creates directories.
|
52
|
+
##
|
53
|
+
def self.rmdir_p(directory)
|
54
|
+
`rmdir -p #{Shellwords.escape(directory)} &> /dev/null`
|
55
|
+
end
|
56
|
+
|
57
|
+
##
|
58
|
+
# Moves a file and gives it the default file permissions minus the
|
59
|
+
# declared umask unless another mode is specified.
|
60
|
+
##
|
61
|
+
def self.move(from, to, mode = 0666 - ::File.umask)
|
62
|
+
FileUtils.mv(from, to)
|
63
|
+
FileUtils.chmod(mode, to)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Harbor
|
2
|
+
class FileStore
|
3
|
+
|
4
|
+
require Pathname(__FILE__).dirname + "file_store/file"
|
5
|
+
require Pathname(__FILE__).dirname + "file_store/local"
|
6
|
+
|
7
|
+
@@registrations = []
|
8
|
+
|
9
|
+
def self.register(name, store)
|
10
|
+
@@registrations.push name
|
11
|
+
file_stores[name] = store
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.file_stores
|
15
|
+
@@file_stores ||= {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.[](name)
|
19
|
+
file_stores[name]
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.exists?(path)
|
23
|
+
@@registrations.each do |registration|
|
24
|
+
if file_stores[registration].exists?(path) == true
|
25
|
+
return true
|
26
|
+
end
|
27
|
+
end
|
28
|
+
false
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.get(path)
|
32
|
+
@@registrations.each do |registration|
|
33
|
+
if file_stores[registration].exists?(path) == true
|
34
|
+
return file_stores[registration].get(path)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
|
40
|
+
def put(path, file)
|
41
|
+
raise NotImplementedError.new("You must define your own implementation of FileStore#put")
|
42
|
+
end
|
43
|
+
|
44
|
+
def delete(path)
|
45
|
+
raise NotImplementedError.new("You must define your own implementation of FileStore#delete")
|
46
|
+
end
|
47
|
+
|
48
|
+
def exists?(path)
|
49
|
+
raise NotImplementedError.new("You must define your own implementation of FileStore#exists?")
|
50
|
+
end
|
51
|
+
|
52
|
+
def open(path)
|
53
|
+
raise NotImplementedError.new("You must define your own implementation of FileStore#open")
|
54
|
+
end
|
55
|
+
|
56
|
+
def size(path)
|
57
|
+
raise NotImplementedError.new("You must define your own implementation of FileStore#size")
|
58
|
+
end
|
59
|
+
|
60
|
+
##
|
61
|
+
# Defines whether a file store is local, and thus could be directly copied rather
|
62
|
+
# than read and then written.
|
63
|
+
##
|
64
|
+
def local?
|
65
|
+
raise NotImplementedError.new("You must define your own implementation of FileStore#local?")
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|