staticd 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/bin/staticd +7 -0
  3. data/lib/rack/auth/hmac.rb +81 -0
  4. data/lib/rack/request_time.rb +19 -0
  5. data/lib/staticd.rb +7 -0
  6. data/lib/staticd/api.rb +356 -0
  7. data/lib/staticd/app.rb +130 -0
  8. data/lib/staticd/cache_engine.rb +45 -0
  9. data/lib/staticd/cli.rb +70 -0
  10. data/lib/staticd/config.rb +115 -0
  11. data/lib/staticd/database.rb +41 -0
  12. data/lib/staticd/datastore.rb +64 -0
  13. data/lib/staticd/datastores/local.rb +48 -0
  14. data/lib/staticd/datastores/s3.rb +63 -0
  15. data/lib/staticd/domain_generator.rb +42 -0
  16. data/lib/staticd/http_cache.rb +65 -0
  17. data/lib/staticd/http_server.rb +197 -0
  18. data/lib/staticd/json_request.rb +15 -0
  19. data/lib/staticd/json_response.rb +29 -0
  20. data/lib/staticd/models/base.rb +43 -0
  21. data/lib/staticd/models/domain_name.rb +17 -0
  22. data/lib/staticd/models/release.rb +20 -0
  23. data/lib/staticd/models/resource.rb +19 -0
  24. data/lib/staticd/models/route.rb +19 -0
  25. data/lib/staticd/models/site.rb +36 -0
  26. data/lib/staticd/models/staticd_config.rb +71 -0
  27. data/lib/staticd/public/jquery-1.11.1.min.js +4 -0
  28. data/lib/staticd/public/main.css +61 -0
  29. data/lib/staticd/public/main.js +15 -0
  30. data/lib/staticd/version.rb +3 -0
  31. data/lib/staticd/views/main.haml +9 -0
  32. data/lib/staticd/views/setup.haml +47 -0
  33. data/lib/staticd/views/welcome.haml +92 -0
  34. data/lib/staticd_utils/archive.rb +80 -0
  35. data/lib/staticd_utils/file_size.rb +26 -0
  36. data/lib/staticd_utils/gli_object.rb +14 -0
  37. data/lib/staticd_utils/memory_file.rb +36 -0
  38. data/lib/staticd_utils/sitemap.rb +100 -0
  39. data/lib/staticd_utils/tar.rb +43 -0
  40. metadata +300 -0
@@ -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
@@ -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