staticd 0.0.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.
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