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