staticd 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/staticd +7 -0
- data/lib/rack/auth/hmac.rb +81 -0
- data/lib/rack/request_time.rb +19 -0
- data/lib/staticd.rb +7 -0
- data/lib/staticd/api.rb +356 -0
- data/lib/staticd/app.rb +130 -0
- data/lib/staticd/cache_engine.rb +45 -0
- data/lib/staticd/cli.rb +70 -0
- data/lib/staticd/config.rb +115 -0
- data/lib/staticd/database.rb +41 -0
- data/lib/staticd/datastore.rb +64 -0
- data/lib/staticd/datastores/local.rb +48 -0
- data/lib/staticd/datastores/s3.rb +63 -0
- data/lib/staticd/domain_generator.rb +42 -0
- data/lib/staticd/http_cache.rb +65 -0
- data/lib/staticd/http_server.rb +197 -0
- data/lib/staticd/json_request.rb +15 -0
- data/lib/staticd/json_response.rb +29 -0
- data/lib/staticd/models/base.rb +43 -0
- data/lib/staticd/models/domain_name.rb +17 -0
- data/lib/staticd/models/release.rb +20 -0
- data/lib/staticd/models/resource.rb +19 -0
- data/lib/staticd/models/route.rb +19 -0
- data/lib/staticd/models/site.rb +36 -0
- data/lib/staticd/models/staticd_config.rb +71 -0
- data/lib/staticd/public/jquery-1.11.1.min.js +4 -0
- data/lib/staticd/public/main.css +61 -0
- data/lib/staticd/public/main.js +15 -0
- data/lib/staticd/version.rb +3 -0
- data/lib/staticd/views/main.haml +9 -0
- data/lib/staticd/views/setup.haml +47 -0
- data/lib/staticd/views/welcome.haml +92 -0
- data/lib/staticd_utils/archive.rb +80 -0
- data/lib/staticd_utils/file_size.rb +26 -0
- data/lib/staticd_utils/gli_object.rb +14 -0
- data/lib/staticd_utils/memory_file.rb +36 -0
- data/lib/staticd_utils/sitemap.rb +100 -0
- data/lib/staticd_utils/tar.rb +43 -0
- metadata +300 -0
data/lib/staticd/app.rb
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
require "rack"
|
2
|
+
require "rack/request_time"
|
3
|
+
|
4
|
+
require "staticd/config"
|
5
|
+
require "staticd/database"
|
6
|
+
require "staticd/datastore"
|
7
|
+
require "staticd/api"
|
8
|
+
require "staticd/http_cache"
|
9
|
+
require "staticd/http_server"
|
10
|
+
|
11
|
+
module Staticd
|
12
|
+
|
13
|
+
# Staticd App.
|
14
|
+
#
|
15
|
+
# This class manage the app initialization and runtime.
|
16
|
+
#
|
17
|
+
# Example:
|
18
|
+
# app = Staticd::App.new(config)
|
19
|
+
# app.run
|
20
|
+
class App
|
21
|
+
|
22
|
+
# Initialize the Staticd app.
|
23
|
+
#
|
24
|
+
# General configuration:
|
25
|
+
# * environment: the app environment (test, development or production)
|
26
|
+
# * domain: base to generate per app sub-domain
|
27
|
+
# * public_port: port used to generate application and endpoint url,
|
28
|
+
# (default to 80)
|
29
|
+
# * database: database url to store resources metadata
|
30
|
+
# * datastore: datastore url to store resources
|
31
|
+
# * host: host to listen to
|
32
|
+
# * port: port to listen to
|
33
|
+
#
|
34
|
+
# API service configuration:
|
35
|
+
# * api: enable the API service
|
36
|
+
# * access_id: HMAC authentication access ID for the API service
|
37
|
+
# * secret_key: HMAC authentication secret key for the API service
|
38
|
+
#
|
39
|
+
# HTTP service configuration:
|
40
|
+
# * http: enable the HTTP service
|
41
|
+
# * http_cache: folder where resources are cached
|
42
|
+
def initialize(config)
|
43
|
+
@config = config
|
44
|
+
@config[:public_port] ||= "80"
|
45
|
+
require_settings(:environment, :domain, :database, :datastore)
|
46
|
+
|
47
|
+
env = @config[:environment]
|
48
|
+
puts "Starting Staticd in #{env} environment." unless env == "test"
|
49
|
+
display_current_config if env == "development"
|
50
|
+
|
51
|
+
init_database
|
52
|
+
init_datastore
|
53
|
+
end
|
54
|
+
|
55
|
+
# Start the application.
|
56
|
+
def run
|
57
|
+
require_settings(:host, :port)
|
58
|
+
require_settings(:http_cache) if @config[:http]
|
59
|
+
require_settings(:access_id, :secret_key) if @config[:api]
|
60
|
+
|
61
|
+
routes = {}
|
62
|
+
routes["/"] = build_http_service if @config[:http]
|
63
|
+
routes["/api/#{Staticd::API::VERSION}"] = build_api_service if @config[:api]
|
64
|
+
router = Rack::URLMap.new(routes)
|
65
|
+
|
66
|
+
Rack::Server.start(
|
67
|
+
Host: @config[:host],
|
68
|
+
Port: @config[:port],
|
69
|
+
app: router,
|
70
|
+
environment: @config[:environment]
|
71
|
+
)
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def require_settings(*settings)
|
77
|
+
settings.each do |setting|
|
78
|
+
unless @config.key?(setting) && !@config[setting].nil?
|
79
|
+
raise "Missing '#{setting}' setting"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def display_current_config
|
85
|
+
puts "Configuration:"
|
86
|
+
puts "* Database: #{@config[:database]}"
|
87
|
+
puts "* Datastore: #{@config[:datastore]}"
|
88
|
+
|
89
|
+
if Staticd::Config[:api]
|
90
|
+
puts "* Host: #{@config[:host]}"
|
91
|
+
puts "* Port: #{@config[:port]}"
|
92
|
+
puts "* Domain: #{@config[:domain]}"
|
93
|
+
puts "* Public Port: #{@config[:public_port]}"
|
94
|
+
puts "* Access ID: #{@config[:access_id]}"
|
95
|
+
puts "* Secret Key: #{@config[:secret_key]}"
|
96
|
+
end
|
97
|
+
|
98
|
+
if Staticd::Config[:http]
|
99
|
+
puts "* HTTP cache: #{@config[:http_cache]}"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def init_database
|
104
|
+
Staticd::Database.setup(@config[:environment], @config[:database])
|
105
|
+
end
|
106
|
+
|
107
|
+
def init_datastore
|
108
|
+
Staticd::Datastore.setup(@config[:datastore])
|
109
|
+
end
|
110
|
+
|
111
|
+
def build_api_service
|
112
|
+
api_service = Staticd::API.new(@config)
|
113
|
+
|
114
|
+
# Bind the API service with the HMAC middleware.
|
115
|
+
raise "No access ID provided" unless @config[:access_id]
|
116
|
+
raise "No secret_key provided" unless @config[:secret_key]
|
117
|
+
Rack::Auth::HMAC.new(
|
118
|
+
api_service, except: Staticd::API::PUBLIC_URI
|
119
|
+
) do |access_id|
|
120
|
+
@config[:secret_key] if access_id == @config[:access_id].to_s
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def build_http_service
|
125
|
+
http_service = Staticd::HTTPServer.new(@config[:http_cache])
|
126
|
+
cache_middleware = Staticd::HTTPCache.new(@config[:http_cache], http_service)
|
127
|
+
Rack::RequestTime.new(cache_middleware)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require "open-uri"
|
2
|
+
require "sendfile"
|
3
|
+
|
4
|
+
module Staticd
|
5
|
+
|
6
|
+
# Class to manage HTTP resources caching.
|
7
|
+
#
|
8
|
+
# Example:
|
9
|
+
# cache_engine = CacheEngine.new("/tmp/cache")
|
10
|
+
# unless cache.cached?("/index.html")
|
11
|
+
# cache_engine.cache("/index.html", "http://storage.tld/0000000")
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# TODO: add a purge method based on file's atime attribute
|
15
|
+
class CacheEngine
|
16
|
+
|
17
|
+
def initialize(http_root)
|
18
|
+
@http_root = http_root
|
19
|
+
check_cache_directory
|
20
|
+
end
|
21
|
+
|
22
|
+
def cache(resource_path, resource_url)
|
23
|
+
open(resource_url, "rb") do |resource|
|
24
|
+
FileUtils.mkdir_p(File.dirname(local_path(resource_path)))
|
25
|
+
File.open(local_path(resource_path), "w+") do |file|
|
26
|
+
resource.each { |chunk| file.write(chunk) }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def cached?(resource_path)
|
32
|
+
File.exist?(local_path(resource_path))
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def local_path(resource_path)
|
38
|
+
@http_root + resource_path
|
39
|
+
end
|
40
|
+
|
41
|
+
def check_cache_directory
|
42
|
+
FileUtils.mkdir_p(@http_root) unless File.directory?(@http_root)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/staticd/cli.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
require "staticd_utils/gli_object"
|
2
|
+
require "staticd"
|
3
|
+
|
4
|
+
module Staticd
|
5
|
+
class CLI
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@gli = GLIObject.new
|
9
|
+
@gli.program_desc("Staticd HTTP and API server")
|
10
|
+
@gli.version(Staticd::VERSION)
|
11
|
+
@gli.on_error { |exception| raise exception }
|
12
|
+
build_commands
|
13
|
+
end
|
14
|
+
|
15
|
+
def run(*args)
|
16
|
+
@gli.run(*args)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def build_commands
|
22
|
+
build_command_server
|
23
|
+
end
|
24
|
+
|
25
|
+
def build_command_server
|
26
|
+
@gli.desc("Start the staticd API and HTTP services")
|
27
|
+
@gli.command(:server) do |c|
|
28
|
+
c.switch([:api], desc: "enable the API service", default_value: true)
|
29
|
+
c.switch([:http], desc: "enable the HTTP service", default_value: true)
|
30
|
+
c.flag(
|
31
|
+
[:environment],
|
32
|
+
desc: "application environment",
|
33
|
+
default_value: :development
|
34
|
+
)
|
35
|
+
c.flag(
|
36
|
+
[:domain],
|
37
|
+
desc: "base to generate per app sub-domain"
|
38
|
+
)
|
39
|
+
c.flag(
|
40
|
+
[:public_port],
|
41
|
+
desc: "port used to generate application and endpoint url"
|
42
|
+
)
|
43
|
+
c.flag([:access_id], desc: "HMAC auth access id for the API service")
|
44
|
+
c.flag([:secret_key], desc: "HMAC auth secret key for the API service")
|
45
|
+
c.flag([:database], desc: "URL for the database")
|
46
|
+
c.flag([:datastore], desc: "URL for the datastore")
|
47
|
+
c.flag(
|
48
|
+
[:http_cache],
|
49
|
+
desc: "directory path where HTTP resources are cached",
|
50
|
+
default_value: "/var/cache/staticd"
|
51
|
+
)
|
52
|
+
c.flag([:host], desc: "address to listen to", default_value: "0.0.0.0")
|
53
|
+
c.flag([:port], desc: "port to listen to", default_value: 80)
|
54
|
+
c.flag([:config], desc: "load a config file")
|
55
|
+
c.action do |global_options, options,args|
|
56
|
+
|
57
|
+
# Load configuration from command line options, environment variables
|
58
|
+
# options and config file.
|
59
|
+
Staticd::Config << options
|
60
|
+
Staticd::Config.load_env
|
61
|
+
Staticd::Config.load_file(options[:config]) if options[:config]
|
62
|
+
|
63
|
+
# Initialize and start the Staticd app.
|
64
|
+
app = Staticd::App.new(Staticd::Config)
|
65
|
+
app.run
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require "singleton"
|
2
|
+
|
3
|
+
module Staticd
|
4
|
+
|
5
|
+
# Manage Staticd Configuration.
|
6
|
+
#
|
7
|
+
# Can load configuration from a hash, from environment variables with the
|
8
|
+
# STATICD_ prefix or from a config file in yaml format.
|
9
|
+
#
|
10
|
+
# Once loaded,configuration is available from anywhere in the app using
|
11
|
+
# Staticd::Config[:setting].
|
12
|
+
class Config
|
13
|
+
include Singleton
|
14
|
+
|
15
|
+
# Load configuration from environment variables.
|
16
|
+
#
|
17
|
+
# Example:
|
18
|
+
# ENV["STATICD_FOO"] = "bar"
|
19
|
+
# Staticd::Config.load_env
|
20
|
+
# Staticd::Config[:foo]
|
21
|
+
# # => "bar"
|
22
|
+
def self.load_env
|
23
|
+
settings = {}
|
24
|
+
env = ENV.select { |name, value| name =~ /^STATICD_/ }
|
25
|
+
env.each do |name, value|
|
26
|
+
setting = name[/^STATICD_(.*)/, 1].downcase.to_sym
|
27
|
+
settings[setting] = value
|
28
|
+
end
|
29
|
+
instance << settings
|
30
|
+
end
|
31
|
+
|
32
|
+
# Load configuration from a YAML file.
|
33
|
+
#
|
34
|
+
# The configuration file can contain ERB code.
|
35
|
+
#
|
36
|
+
# Example (config file)
|
37
|
+
# ---
|
38
|
+
# foo: bar
|
39
|
+
#
|
40
|
+
# Example:
|
41
|
+
# Staticd::Config.load_file("/etc/staticd/staticd.yml")
|
42
|
+
# Staticd::Config[:foo]
|
43
|
+
# # => "bar"
|
44
|
+
def self.load_file(config_file)
|
45
|
+
content = File.read(config_file)
|
46
|
+
erb = ERB.new(content)
|
47
|
+
settings = YAML.load(erb.result)
|
48
|
+
instance << settings
|
49
|
+
end
|
50
|
+
|
51
|
+
# Push settings into Staticd global configuration.
|
52
|
+
#
|
53
|
+
# String setting keys are converted to symbols.
|
54
|
+
#
|
55
|
+
# Example:
|
56
|
+
# Staticd::Config << {"foo" => bar}
|
57
|
+
# Staticd::Config[:foo]
|
58
|
+
# # => "bar"
|
59
|
+
def self.<<(settings)
|
60
|
+
instance << settings
|
61
|
+
end
|
62
|
+
|
63
|
+
# Same as << method.
|
64
|
+
def self.[]=(setting, value)
|
65
|
+
instance << {setting => value}
|
66
|
+
end
|
67
|
+
|
68
|
+
# Get a setting value from the Staticd global configuration.
|
69
|
+
def self.[](setting)
|
70
|
+
instance[setting]
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.key?(setting_name)
|
74
|
+
instance.key?(setting_name)
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.to_s
|
78
|
+
instance.to_s
|
79
|
+
end
|
80
|
+
|
81
|
+
def initialize
|
82
|
+
@settings = {}
|
83
|
+
end
|
84
|
+
|
85
|
+
def <<(settings)
|
86
|
+
settings = hash_symbolize_keys(settings)
|
87
|
+
mutex.synchronize { @settings.merge!(settings) }
|
88
|
+
end
|
89
|
+
|
90
|
+
def [](setting)
|
91
|
+
mutex.synchronize { @settings.key?(setting) ? @settings[setting] : nil }
|
92
|
+
end
|
93
|
+
|
94
|
+
def key?(setting_name)
|
95
|
+
mutex.synchronize { @settings.key?(setting_name) }
|
96
|
+
end
|
97
|
+
|
98
|
+
def to_s
|
99
|
+
mutex.synchronize { @settings.to_s }
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
def hash_symbolize_keys(hash)
|
105
|
+
hash.keys.each do |key|
|
106
|
+
hash[(key.to_sym rescue key) || key] = hash.delete(key)
|
107
|
+
end
|
108
|
+
hash
|
109
|
+
end
|
110
|
+
|
111
|
+
def mutex
|
112
|
+
@mutex ||= Mutex.new
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require "data_mapper"
|
2
|
+
|
3
|
+
# Load models.
|
4
|
+
Dir["#{File.dirname(__FILE__)}/models/*.rb"].each do |model_library|
|
5
|
+
require model_library
|
6
|
+
end
|
7
|
+
|
8
|
+
module Staticd
|
9
|
+
module Database
|
10
|
+
|
11
|
+
# Initialize the database.
|
12
|
+
#
|
13
|
+
# It support the test, development and production environment.
|
14
|
+
# Database logger is silent in test environment, verbose in development
|
15
|
+
# environment, and only displaying errors in production.
|
16
|
+
def self.setup(environment, database_url)
|
17
|
+
raise "No environment given for the database" unless environment
|
18
|
+
raise "No database_url given" unless database_url
|
19
|
+
|
20
|
+
environment = environment.to_sym
|
21
|
+
|
22
|
+
log_enabled, destination, level =
|
23
|
+
case environment
|
24
|
+
when :development
|
25
|
+
[true, '$stdout', :debug]
|
26
|
+
when :production
|
27
|
+
[true, '$stderr', :error]
|
28
|
+
else
|
29
|
+
[false]
|
30
|
+
end
|
31
|
+
|
32
|
+
if log_enabled
|
33
|
+
DataMapper::Logger.new(eval(destination), level)
|
34
|
+
end
|
35
|
+
|
36
|
+
DataMapper.setup(:default, database_url)
|
37
|
+
DataMapper.finalize
|
38
|
+
environment == :test ? DataMapper.auto_migrate! : DataMapper.auto_upgrade!
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require "singleton"
|
2
|
+
require "uri"
|
3
|
+
|
4
|
+
# Load datastores libraries.
|
5
|
+
Dir["#{File.dirname(__FILE__)}/datastores/*.rb"].each do |datastore_library|
|
6
|
+
require datastore_library
|
7
|
+
end
|
8
|
+
|
9
|
+
module Staticd
|
10
|
+
|
11
|
+
# Load the corresponding datastore driver from an URL.
|
12
|
+
#
|
13
|
+
# This class use an URL to choose wich datastore library to use.
|
14
|
+
# It create a datastore instance with the correct driver and proxies its
|
15
|
+
# calls to it.
|
16
|
+
# It use the URL scheme to guess wich datastore library to use.
|
17
|
+
#
|
18
|
+
# Example:
|
19
|
+
# Staticd::Datastore.setup("s3://[...]") # Staticd::Datastores::S3
|
20
|
+
# Staticd::Datastore.setup("local:/[...]") # Staticd::Datastores::Local
|
21
|
+
# Staticd::Datastore.put(file_path)
|
22
|
+
class Datastore
|
23
|
+
include Singleton
|
24
|
+
|
25
|
+
def self.setup(url)
|
26
|
+
instance.setup(url)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.put(file_path)
|
30
|
+
instance.put(file_path)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.exist?(file_path)
|
34
|
+
instance.exist?(file_path)
|
35
|
+
end
|
36
|
+
|
37
|
+
def setup(url)
|
38
|
+
@uri = URI(url)
|
39
|
+
end
|
40
|
+
|
41
|
+
def put(file_path)
|
42
|
+
datastore.put(file_path)
|
43
|
+
end
|
44
|
+
|
45
|
+
def exist?(file_path)
|
46
|
+
datastore.exist?(file_path)
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def datastoreClass
|
52
|
+
@datastoreClass ||= Datastores.const_get(@uri.scheme.capitalize)
|
53
|
+
end
|
54
|
+
|
55
|
+
def datastore
|
56
|
+
@datastore ||= datastoreClass.new(
|
57
|
+
host: @uri.host,
|
58
|
+
path: @uri.path,
|
59
|
+
username: @uri.user,
|
60
|
+
password: @uri.password
|
61
|
+
)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|