Merge branch 'master' into ps-ember-update
Conflicts: Gemfile assets/scripts/travis.coffee
This commit is contained in:
commit
554c8621e3
3
Gemfile
3
Gemfile
|
@ -1,6 +1,5 @@
|
|||
ruby '1.9.3' rescue nil
|
||||
|
||||
source 'https://rubygems.org'
|
||||
ruby '1.9.3'
|
||||
|
||||
gem 'puma'
|
||||
gem 'rack-ssl', '~> 1.3'
|
||||
|
|
|
@ -15,6 +15,9 @@
|
|||
<li>
|
||||
<a href="http://about.travis-ci.org/docs">{{t layouts.top.docs}}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://status.travis-ci.com">{{t layouts.top.status}}</a>
|
||||
</li>
|
||||
<li {{bindAttr class="view.classProfile"}}>
|
||||
<p class="handle">
|
||||
<a class="signed-out" href="#" {{action signIn target="Travis"}}>{{t layouts.top.github_login}}</a>
|
||||
|
|
|
@ -4,6 +4,18 @@ require 'ext/ember/namespace'
|
|||
window.ENV ||= {}
|
||||
window.ENV.RAISE_ON_DEPRECATION = true
|
||||
|
||||
if window.history.state == undefined
|
||||
window.history.state = {}
|
||||
oldPushState = window.history.pushState
|
||||
window.history.pushState = (state, title, href) ->
|
||||
window.history.state = state
|
||||
oldPushState.apply this, arguments
|
||||
|
||||
oldReplaceState = window.history.replaceState
|
||||
window.history.replaceState = (state, title, href) ->
|
||||
window.history.state = state
|
||||
oldReplaceState.apply this, arguments
|
||||
|
||||
# TODO: how can I put it in Travis namespace and use immediately?
|
||||
Storage = Em.Object.extend
|
||||
init: ->
|
||||
|
@ -99,7 +111,6 @@ $.extend Travis,
|
|||
CONFIG_KEYS: ['rvm', 'gemfile', 'env', 'jdk', 'otp_release', 'php', 'node_js', 'perl', 'python', 'scala', 'compiler']
|
||||
|
||||
QUEUES: [
|
||||
{ name: 'common', display: 'Common' }
|
||||
{ name: 'linux', display: 'Linux' }
|
||||
{ name: 'mac_osx', display: 'Mac and OSX' }
|
||||
]
|
||||
|
|
|
@ -25,7 +25,7 @@ use Travis::Web::ApiRedirect do |app|
|
|||
app.settings.api_endpoint = ENV['API_ENDPOINT'] if ENV['API_ENDPOINT']
|
||||
end
|
||||
|
||||
run Travis::Web::App.new(
|
||||
run Travis::Web::App.build(
|
||||
environment: ENV['RACK_ENV'] || 'development',
|
||||
api_endpoint: ENV['API_ENDPOINT'],
|
||||
pusher_key: ENV['PUSHER_KEY'],
|
||||
|
|
|
@ -6,8 +6,11 @@ require 'delegate'
|
|||
require 'time'
|
||||
|
||||
class Travis::Web::App
|
||||
autoload :AltVersions, 'travis/web/app/alt_versions'
|
||||
autoload :MobileRedirect, 'travis/web/app/mobile_redirect'
|
||||
|
||||
S3_URL = 'https://s3.amazonaws.com/travis-web-production/assets'
|
||||
|
||||
# Simple Rack router that behaves like a hash.
|
||||
# Key is the path, value the response.
|
||||
class Router < DelegateClass(Hash)
|
||||
|
@ -18,103 +21,83 @@ class Travis::Web::App
|
|||
end
|
||||
|
||||
def call(env)
|
||||
if main_app.custom_branch?(env)
|
||||
main_app.response_for_custom_branch(env)
|
||||
else
|
||||
self[env['PATH_INFO']]
|
||||
end
|
||||
self[env['PATH_INFO']]
|
||||
end
|
||||
end
|
||||
|
||||
def self.new(options = {})
|
||||
return super unless options[:environment] == 'development'
|
||||
proc { |e| super.call(e) } # poor man's reloader
|
||||
class << self
|
||||
def new(options = {})
|
||||
return super unless options[:environment] == 'development'
|
||||
proc { |e| super.call(e) } # poor man's reloader
|
||||
end
|
||||
|
||||
def build(options = {})
|
||||
builder = Rack::Builder.new
|
||||
if options[:environment] == 'production'
|
||||
builder.use Rack::SSL
|
||||
# builder.use Rack::Cache
|
||||
end
|
||||
builder.use Rack::Deflater
|
||||
builder.use Rack::Head
|
||||
builder.use Rack::Protection::XSSHeader
|
||||
builder.use Rack::Protection::FrameOptions
|
||||
builder.use Rack::Protection::PathTraversal
|
||||
builder.use Rack::ConditionalGet
|
||||
builder.use Travis::Web::App::AltVersions
|
||||
builder.run new(options)
|
||||
builder.to_app
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :app, :router, :environment, :version, :last_modified, :age, :options, :root
|
||||
attr_reader :routers, :version, :last_modified, :age, :options, :root
|
||||
|
||||
def initialize(options = {})
|
||||
@options = options
|
||||
@environment = options.fetch(:environment)
|
||||
@root = options.fetch(:root)
|
||||
@router = Router.new(self)
|
||||
@app = builder.to_app
|
||||
@version = File.read File.expand_path('version', root)
|
||||
@last_modified = Time.now
|
||||
@age = 60 * 60 * 24 * 365
|
||||
load_routes
|
||||
@routers = { default: create_router }
|
||||
end
|
||||
|
||||
def call(env)
|
||||
app.call(env)
|
||||
end
|
||||
|
||||
def response_for_custom_branch(env)
|
||||
status, headers, body = response_for File.join(root, 'index.html'), custom_branch: custom_branch(env)
|
||||
response = Rack::Response.new body, status, headers
|
||||
|
||||
if disable_custom_branch?(env)
|
||||
response.delete_cookie 'custom_branch'
|
||||
elsif custom_branch_from_params(env)
|
||||
response.set_cookie 'custom_branch', value: custom_branch_from_params(env), expires: Time.now + 31536000
|
||||
end
|
||||
|
||||
response.finish
|
||||
end
|
||||
|
||||
def custom_branch?(env)
|
||||
custom_branch(env) || disable_custom_branch?(env)
|
||||
name = env['travis.alt'] || :default
|
||||
routers[name] ||= create_router(alt: name)
|
||||
routers[name].call(env)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def disable_custom_branch?(env)
|
||||
env['QUERY_STRING'] =~ /disable[_-]custom[_-]branch/
|
||||
def create_router(options = {})
|
||||
router = Router.new(self)
|
||||
load_routes(router, options)
|
||||
router
|
||||
end
|
||||
|
||||
def custom_branch_from_params(env)
|
||||
branch = custom_branch_from_string env['QUERY_STRING']
|
||||
end
|
||||
|
||||
def custom_branch_from_cookie(env)
|
||||
custom_branch_from_string env['HTTP_COOKIE']
|
||||
end
|
||||
|
||||
def custom_branch_from_string(string)
|
||||
$1 if string =~ /(?<!disable.)custom[_-]branch=([^&]+)/
|
||||
end
|
||||
|
||||
def custom_branch(env)
|
||||
custom_branch_from_params(env) || custom_branch_from_cookie(env)
|
||||
end
|
||||
|
||||
def load_routes
|
||||
each_file { |f| router[route_for(f)] = response_for(f) }
|
||||
def load_routes(router, options = {})
|
||||
each_file { |file| router[path_for(file)] = response_for(file, options) }
|
||||
router.default = router['/']
|
||||
end
|
||||
|
||||
def response_for(file, options = {})
|
||||
content = File.read(file)
|
||||
set_config(content, options) if config_needed? file
|
||||
|
||||
set_config(content, options) if config_needed?(file)
|
||||
headers = {
|
||||
'Content-Length' => content.bytesize.to_s,
|
||||
'Content-Location' => route_for(file),
|
||||
'Content-Location' => path_for(file),
|
||||
'Cache-Control' => cache_control(file),
|
||||
'Content-Location' => route_for(file),
|
||||
'Content-Location' => path_for(file),
|
||||
'Content-Type' => mime_type(file),
|
||||
'ETag' => version,
|
||||
'Last-Modified' => last_modified.httpdate,
|
||||
'Expires' => (last_modified + age).httpdate,
|
||||
'Vary' => vary_for(file)
|
||||
}
|
||||
|
||||
[ 200, headers, [ content ] ]
|
||||
[ 200, headers, [content] ]
|
||||
end
|
||||
|
||||
def each_file
|
||||
pattern = File.join(root, '**/*')
|
||||
Dir.glob(pattern) { |f| yield f if File.file? f }
|
||||
Dir.glob(File.join(root, '**/*')) { |file| yield file if File.file?(file) }
|
||||
end
|
||||
|
||||
def prefix?(file)
|
||||
|
@ -126,18 +109,11 @@ class Travis::Web::App
|
|||
end
|
||||
|
||||
def index?(file)
|
||||
file.end_with? 'index.html'
|
||||
end
|
||||
|
||||
def route_for(file)
|
||||
file = file.sub("#{root}/", '')
|
||||
file = File.join(version, file) if prefix? file
|
||||
file = "" if index? file
|
||||
"/#{file}"
|
||||
file.end_with?('index.html')
|
||||
end
|
||||
|
||||
def cache_control(file)
|
||||
case route_for(file)
|
||||
case path_for(file)
|
||||
when '/' then "public, must-revalidate"
|
||||
when '/version' then "no-cache"
|
||||
else "public, max-age=#{age}"
|
||||
|
@ -145,13 +121,20 @@ class Travis::Web::App
|
|||
end
|
||||
|
||||
def vary_for(file)
|
||||
case route_for(file)
|
||||
case path_for(file)
|
||||
when '/' then 'Accept'
|
||||
when '/version' then '*'
|
||||
else ''
|
||||
end
|
||||
end
|
||||
|
||||
def path_for(file)
|
||||
file = file.sub("#{root}/", '')
|
||||
file = File.join(version, file) if prefix?(file)
|
||||
file = "" if index?(file)
|
||||
"/#{file}"
|
||||
end
|
||||
|
||||
def mime_type(file)
|
||||
Rack::Mime.mime_type File.extname(file)
|
||||
end
|
||||
|
@ -162,28 +145,7 @@ class Travis::Web::App
|
|||
end
|
||||
|
||||
string.gsub! %r{(src|href)="(?:\/?)((styles|scripts)\/[^"]*)"} do
|
||||
if opts[:custom_branch]
|
||||
url = "https://s3.amazonaws.com/travis-web-production/assets/#{opts[:custom_branch]}/#{$2}"
|
||||
%(#$1="#{url}")
|
||||
else
|
||||
%(#$1="/#{version}/#$2")
|
||||
end
|
||||
%(#{$1}=#{opts[:alt] ? "#{S3_URL}/#{opts[:alt]}/#{$2}" : "/#{version}/#{$2}"})
|
||||
end
|
||||
end
|
||||
|
||||
def builder
|
||||
builder = Rack::Builder.new
|
||||
if environment == 'production'
|
||||
builder.use Rack::SSL
|
||||
builder.use Rack::Cache
|
||||
end
|
||||
builder.use Rack::Deflater
|
||||
builder.use Rack::Head
|
||||
builder.use Rack::Protection::XSSHeader
|
||||
builder.use Rack::Protection::FrameOptions
|
||||
builder.use Rack::Protection::PathTraversal
|
||||
builder.use Rack::ConditionalGet
|
||||
builder.run router
|
||||
builder
|
||||
end
|
||||
end
|
||||
|
|
29
lib/travis/web/app/alt_versions.rb
Normal file
29
lib/travis/web/app/alt_versions.rb
Normal file
|
@ -0,0 +1,29 @@
|
|||
class Travis::Web::App::AltVersions
|
||||
attr_reader :app
|
||||
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
|
||||
def call(env)
|
||||
alt = alt_from_params(env) || alt_from_cookie(env)
|
||||
env['travis.alt'] = alt if alt && alt != 'default'
|
||||
status, headers, body = app.call(env)
|
||||
headers['Set-Cookie'] = cookie(alt) if alt
|
||||
[status, headers, body]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def cookie(alt)
|
||||
"alt=#{alt == 'default' ? '' : alt}; path=/; max-age=#{alt == 'default' ? 0 : 86400}"
|
||||
end
|
||||
|
||||
def alt_from_params(env)
|
||||
$1 if env['QUERY_STRING'] =~ /alt=([^&]+)/
|
||||
end
|
||||
|
||||
def alt_from_cookie(env)
|
||||
$1 if env['HTTP_COOKIE'] =~ /alt=([^;]+)/
|
||||
end
|
||||
end
|
|
@ -96,6 +96,7 @@ en:
|
|||
sign_out: Sign Out
|
||||
signing_in: Signing In
|
||||
stats: Stats
|
||||
status: Status
|
||||
locales:
|
||||
ca:
|
||||
de: Deutsch
|
||||
|
|
|
@ -6,7 +6,7 @@ describe Travis::Web::App do
|
|||
end
|
||||
|
||||
describe 'catch all' do
|
||||
before { get('/foo/bar') }
|
||||
before { get('/foo/bar') }
|
||||
example { last_response.should be_ok }
|
||||
example { headers['Content-Location'].should be == '/' }
|
||||
example { headers['Cache-Control'].should include('must-revalidate') }
|
||||
|
@ -15,7 +15,7 @@ describe Travis::Web::App do
|
|||
end
|
||||
|
||||
describe 'assets' do
|
||||
before { get('/favicon.ico') }
|
||||
before { get('/favicon.ico') }
|
||||
example { last_response.should be_ok }
|
||||
example { headers['Content-Location'].should be == '/favicon.ico' }
|
||||
example { headers['Cache-Control'].should_not include('must-revalidate') }
|
||||
|
@ -24,30 +24,31 @@ describe Travis::Web::App do
|
|||
end
|
||||
|
||||
describe 'version' do
|
||||
before { get('/version') }
|
||||
before { get('/version') }
|
||||
example { last_response.should be_ok }
|
||||
example { headers['Content-Location'].should be == '/version' }
|
||||
example { headers['Cache-Control'].should be == 'no-cache' }
|
||||
example { headers['Vary'].split(',').should_not include('Accept') }
|
||||
end
|
||||
|
||||
describe 'custom branch' do
|
||||
context 'when passing custom branch as a param' do
|
||||
before { get('/?custom-branch=foo') }
|
||||
describe 'alternate asset versions' do
|
||||
context 'not passing an alt param' do
|
||||
before { get('/') }
|
||||
example { headers['Set-Cookie'].should be_nil }
|
||||
end
|
||||
|
||||
context 'passing an alt param' do
|
||||
before { get('/?alt=foo') }
|
||||
example { last_response.should be_ok }
|
||||
example { last_response.body.should include('/assets/foo/styles/app.css') }
|
||||
example { last_response.body.should include('/assets/foo/scripts/app.js') }
|
||||
example { headers['Set-Cookie'].should include('custom_branch=foo') }
|
||||
example { headers['Set-Cookie'].should == 'alt=foo; path=/; max-age=86400' }
|
||||
end
|
||||
|
||||
context 'disabling custom branch' do
|
||||
before { get('/?disable-custom-branch=true') }
|
||||
example { last_response.should be_ok }
|
||||
example { last_response.body.should =~ %r{src="/[^\/]+/scripts/app.js} }
|
||||
example { last_response.body.should_not include('/assets/true/styles/app.css') }
|
||||
example { last_response.body.should_not include('/assets/foo/styles/app.css') }
|
||||
example { last_response.body.should_not include('/assets/foo/scripts/app.js') }
|
||||
example { headers['Set-Cookie'].should include('custom_branch=;') }
|
||||
context 'passing default as an alt param' do
|
||||
before { get('/?alt=default') }
|
||||
example { last_response.body.should_not =~ /\/assets\/[^\/]+\/scripts\/app.js/ }
|
||||
example { headers['Set-Cookie'].should == 'alt=; path=/; max-age=0' }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue
Block a user