Merge branch 'master' into rkh-metrics

Conflicts:
	Gemfile.lock
This commit is contained in:
Mathias Meyer 2013-05-09 13:37:28 +02:00
commit 0cd89de38a
63 changed files with 1912 additions and 381 deletions

View File

@ -2,6 +2,7 @@ language: ruby
rvm:
- 1.9.3
- rbx-19mode
- jruby-19mode
before_script:
- 'RAILS_ENV=test rake db:create db:schema:load --trace'
notifications:
@ -9,3 +10,4 @@ notifications:
matrix:
allow_failures:
- rvm: rbx-19mode
- rvm: jruby-19mode

15
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,15 @@
# Contributing to Travis-CI
Issues for any Travis-CI repo should be submitted to https://github.com/travis-ci/travis-ci/issues
## Security Issues
***Any security issues should be submitted directly to [security@travis-ci.org](mailto:security@travis-ci.org)***
## Reporting Issues
- Explain what you expected to happen vs the actual results
- Include a screenshot if it helps illustrate the issue. https://github.com/blog/1347-issue-attachments
- What steps are required to reproduce the issue
- An example build that shows the issue
## Submitting a PR to Travis-API
See testing and setup notes in the base [README](https://github.com/travis-ci/travis-api)

17
Gemfile
View File

@ -1,18 +1,18 @@
ruby '1.9.3' rescue nil
source :rubygems
source 'https://rubygems.org'
gemspec
gem 'travis-support', github: 'travis-ci/travis-support'
gem 'travis-core', github: 'travis-ci/travis-core'
gem 'travis-support', github: 'travis-ci/travis-support'
gem 'travis-sidekiqs', github: 'travis-ci/travis-sidekiqs', require: nil, ref: 'cde9741'
gem 'sinatra', github: 'sinatra/sinatra'
gem 'sinatra-contrib', github: 'sinatra/sinatra-contrib', require: nil
gem 'sinatra' #github: 'sinatra/sinatra'
gem 'sinatra-contrib', require: nil #github: 'sinatra/sinatra-contrib', require: nil
# TODO need to release the gem as soon i'm certain this change makes sense
gem 'simple_states', github: 'svenfuchs/simple_states', branch: 'sf-set-state-early'
gem 'unicorn'
gem 'puma', '1.6.3'
gem "sentry-raven", github: 'getsentry/raven-ruby'
gem 'yard-sinatra', github: 'rkh/yard-sinatra'
gem 'rack-contrib', github: 'rack/rack-contrib'
@ -21,7 +21,9 @@ gem 'gh', github: 'rkh/gh'
gem 'bunny'
gem 'dalli'
gem 'pry'
gem 'metriks', '0.9.9.2'
gem 'metriks', '0.9.9.2'
gem 'ar-octopus', github: 'travis-ci/octopus', require: nil
group :test do
gem 'rspec', '~> 2.11'
@ -34,9 +36,10 @@ group :development do
gem 'foreman'
gem 'rerun'
# gem 'debugger'
gem 'rb-fsevent', '~> 0.9.1'
end
group :development, :test do
gem 'rake', '~> 0.9.2'
gem 'micro_migrations', git: 'http://gist.github.com/4269321.git'
gem 'micro_migrations', git: 'https://gist.github.com/4269321.git'
end

View File

@ -1,10 +1,10 @@
GIT
remote: git://github.com/getsentry/raven-ruby.git
revision: a0f59d3974034b38891fb1d4a70ff45fa996e7c9
revision: 267b33417a3ed43f552911cf353561a641ba9fb2
specs:
sentry-raven (0.4.0)
sentry-raven (0.4.6)
faraday (>= 0.7.6)
hashie
hashie (>= 1.1.0)
multi_json (~> 1.0)
uuidtools
@ -17,11 +17,11 @@ GIT
GIT
remote: git://github.com/rkh/gh.git
revision: 1dece05c588c63e714520aae686589de1b3bcbd5
revision: ff93c759591a66c9d5250cada5234d2adde95dd3
specs:
gh (0.9.1)
gh (0.11.1)
addressable
backports (~> 2.3)
backports
faraday (~> 0.8)
multi_json (~> 1.0)
net-http-persistent (>= 2.7)
@ -29,32 +29,11 @@ GIT
GIT
remote: git://github.com/rkh/yard-sinatra.git
revision: 3b1064eef407d2d288a5b96d258178a1e67b3b80
revision: e61831bca0431b35eaa62fdd18acbc65f81322af
specs:
yard-sinatra (1.0.0)
yard (~> 0.7)
GIT
remote: git://github.com/sinatra/sinatra-contrib.git
revision: 86c85007860bbaf596092547e7902ff5e0a07698
specs:
sinatra-contrib (1.4.0)
backports (>= 2.0)
eventmachine
rack-protection
rack-test
sinatra (~> 1.4.0)
tilt (~> 1.3)
GIT
remote: git://github.com/sinatra/sinatra.git
revision: 459369eb66224836f72e21bbece58c007f3422fa
specs:
sinatra (1.4.0)
rack (~> 1.4)
rack-protection (~> 1.3)
tilt (~> 1.3, >= 1.3.3)
GIT
remote: git://github.com/svenfuchs/simple_states.git
revision: b1d45144e6a758220d7b21f83b08dc92de0d3196
@ -64,23 +43,30 @@ GIT
activesupport
hashr (~> 0.0.10)
GIT
remote: git://github.com/travis-ci/octopus.git
revision: 2d4cca475479516f47c3144971205f50c335ad35
specs:
ar-octopus (0.5.0beta)
activerecord (>= 2.3.0)
activesupport (>= 2.3.0)
GIT
remote: git://github.com/travis-ci/travis-core.git
revision: 259e48ffc68a67eff32848334025ef17ab58a3b3
revision: 9ecc95058bd63f9f70799c7f9f8ad41cbd39b16c
specs:
travis-core (0.0.1)
actionmailer (~> 3.2.11)
activerecord (~> 3.2.11)
actionmailer (~> 3.2.12)
activerecord (~> 3.2.12)
coder (~> 0.3.0)
data_migrations (~> 0.0.1)
gh
hashr (~> 0.0.19)
metriks (~> 0.9.7)
multi_json
postmark-rails (~> 0.4.1)
pusher (~> 0.11.0)
railties (~> 3.2.11)
rake (~> 0.9.2.2)
railties (~> 3.2.12)
rake
redis (~> 3.0)
rollout (~> 1.1.0)
simple_states (~> 0.1.1)
@ -96,12 +82,12 @@ GIT
GIT
remote: git://github.com/travis-ci/travis-support.git
revision: cf916e10949db43ce6f2b6f86082b367f04acfcd
revision: 5463f10e58563e79b950da8c6c392d1e80ec3013
specs:
travis-support (0.0.1)
GIT
remote: http://gist.github.com/4269321.git
remote: https://gist.github.com/4269321.git
revision: 8e2d21b924a69dd48191df6a18e51769f5a88614
specs:
micro_migrations (0.0.1)
@ -112,10 +98,10 @@ PATH
travis-api (0.0.1)
backports (~> 2.5)
hubble (~> 0.1)
newrelic_rpm (~> 3.5.0)
newrelic_rpm (~> 3.6.1.88)
pg (~> 0.13.2)
rack-contrib (~> 1.1)
rack-ssl (~> 1.3)
rack-ssl (~> 1.3, >= 1.3.3)
redcarpet (~> 2.1)
sinatra (~> 1.3)
sinatra-contrib (~> 1.3)
@ -124,73 +110,78 @@ PATH
travis-support
GEM
remote: http://rubygems.org/
remote: https://rubygems.org/
specs:
actionmailer (3.2.11)
actionpack (= 3.2.11)
mail (~> 2.4.4)
actionpack (3.2.11)
activemodel (= 3.2.11)
activesupport (= 3.2.11)
actionmailer (3.2.13)
actionpack (= 3.2.13)
mail (~> 2.5.3)
actionpack (3.2.13)
activemodel (= 3.2.13)
activesupport (= 3.2.13)
builder (~> 3.0.0)
erubis (~> 2.7.0)
journey (~> 1.0.4)
rack (~> 1.4.0)
rack (~> 1.4.5)
rack-cache (~> 1.2)
rack-test (~> 0.6.1)
sprockets (~> 2.2.1)
activemodel (3.2.11)
activesupport (= 3.2.11)
activemodel (3.2.13)
activesupport (= 3.2.13)
builder (~> 3.0.0)
activerecord (3.2.11)
activemodel (= 3.2.11)
activesupport (= 3.2.11)
activerecord (3.2.13)
activemodel (= 3.2.13)
activesupport (= 3.2.13)
arel (~> 3.0.2)
tzinfo (~> 0.3.29)
activesupport (3.2.11)
i18n (~> 0.6)
activesupport (3.2.13)
i18n (= 0.6.1)
multi_json (~> 1.0)
addressable (2.3.2)
addressable (2.3.4)
arel (3.0.2)
atomic (1.0.1)
atomic (1.1.9)
avl_tree (1.1.3)
backports (2.7.1)
backports (2.8.2)
builder (3.0.4)
bunny (0.8.0)
celluloid (0.12.4)
facter (>= 1.6.12)
timers (>= 1.0.0)
coder (0.3.0)
coderay (1.0.8)
coderay (1.0.9)
connection_pool (0.9.3)
daemons (1.1.9)
dalli (2.6.0)
dalli (2.6.3)
data_migrations (0.0.1)
activerecord
rake
database_cleaner (0.8.0)
diff-lcs (1.1.3)
diff-lcs (1.2.4)
dotenv (0.7.0)
erubis (2.7.0)
eventmachine (1.0.0)
facter (1.6.17)
eventmachine (1.0.3)
facter (1.7.0)
factory_girl (2.4.2)
activesupport
faraday (0.8.4)
faraday (0.8.7)
multipart-post (~> 1.1)
foreman (0.61.0)
ffi (1.8.1)
foreman (0.63.0)
dotenv (>= 0.7)
thor (>= 0.13.6)
hashie (1.2.0)
hashie (2.0.4)
hashr (0.0.22)
hike (1.2.1)
hitimes (1.1.1)
hike (1.2.2)
hitimes (1.2.1)
hubble (0.1.2)
yajl-ruby (~> 1.1)
i18n (0.6.1)
journey (1.0.4)
json (1.7.6)
kgio (2.8.0)
listen (0.7.2)
mail (2.4.4)
json (1.7.7)
listen (1.0.3)
rb-fsevent (>= 0.9.3)
rb-inotify (>= 0.9)
rb-kqueue (>= 0.2)
mail (2.5.3)
i18n (>= 0.4.0)
mime-types (~> 1.16)
treetop (~> 1.4.8)
@ -200,101 +191,108 @@ GEM
atomic (~> 1.0)
avl_tree (~> 1.1.2)
hitimes (~> 1.1)
mime-types (1.19)
mocha (0.13.1)
mime-types (1.23)
mocha (0.13.3)
metaclass (~> 0.0.1)
multi_json (1.5.0)
multipart-post (1.1.5)
multi_json (1.7.3)
multipart-post (1.2.0)
net-http-persistent (2.8)
net-http-pipeline (1.0.1)
newrelic_rpm (3.5.5.38)
newrelic_rpm (3.6.1.88)
pg (0.13.2)
polyglot (0.3.3)
postmark (0.9.18)
json
rake
postmark-rails (0.4.1)
actionmailer
postmark (>= 0.9.0)
rake
pry (0.9.11.4)
pry (0.9.12.1)
coderay (~> 1.0.5)
method_source (~> 0.8)
slop (~> 3.4)
pusher (0.11.2)
puma (1.6.3)
rack (~> 1.2)
pusher (0.11.3)
multi_json (~> 1.0)
signature (~> 0.1.6)
rack (1.4.4)
rack (1.4.5)
rack-cache (1.2)
rack (>= 0.4)
rack-protection (1.3.2)
rack-protection (1.5.0)
rack
rack-ssl (1.3.2)
rack-ssl (1.3.3)
rack
rack-test (0.6.2)
rack (>= 1.0)
railties (3.2.11)
actionpack (= 3.2.11)
activesupport (= 3.2.11)
railties (3.2.13)
actionpack (= 3.2.13)
activesupport (= 3.2.13)
rack-ssl (~> 1.3.2)
rake (>= 0.8.7)
rdoc (~> 3.4)
thor (>= 0.14.6, < 2.0)
raindrops (0.10.0)
rake (0.9.2.2)
rdoc (3.12)
rake (0.9.6)
rb-fsevent (0.9.3)
rb-inotify (0.9.0)
ffi (>= 0.5.0)
rb-kqueue (0.2.0)
ffi (>= 0.5.0)
rdoc (3.12.2)
json (~> 1.4)
redcarpet (2.2.2)
redis (3.0.2)
redis-namespace (1.2.1)
redis (3.0.4)
redis-namespace (1.3.0)
redis (~> 3.0.0)
rerun (0.7.1)
listen
rerun (0.8.1)
listen (>= 1.0.3)
rollout (1.1.0)
rspec (2.12.0)
rspec-core (~> 2.12.0)
rspec-expectations (~> 2.12.0)
rspec-mocks (~> 2.12.0)
rspec-core (2.12.2)
rspec-expectations (2.12.1)
diff-lcs (~> 1.1.3)
rspec-mocks (2.12.1)
rspec (2.13.0)
rspec-core (~> 2.13.0)
rspec-expectations (~> 2.13.0)
rspec-mocks (~> 2.13.0)
rspec-core (2.13.1)
rspec-expectations (2.13.0)
diff-lcs (>= 1.1.3, < 2.0)
rspec-mocks (2.13.1)
sidekiq (2.5.4)
celluloid (~> 0.12.0)
connection_pool (~> 0.9.2)
multi_json (~> 1)
redis (~> 3)
redis-namespace
signature (0.1.6)
slop (3.4.3)
signature (0.1.7)
sinatra (1.3.6)
rack (~> 1.4)
rack-protection (~> 1.3)
tilt (~> 1.3, >= 1.3.3)
sinatra-contrib (1.3.2)
backports (>= 2.0)
eventmachine
rack-protection
rack-test
sinatra (~> 1.3.0)
tilt (~> 1.3)
slop (3.4.4)
sprockets (2.2.2)
hike (~> 1.2)
multi_json (~> 1.0)
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
thin (1.5.0)
thin (1.5.1)
daemons (>= 1.0.9)
eventmachine (>= 0.12.6)
rack (>= 1.0.0)
thor (0.14.6)
tilt (1.3.3)
tilt (1.4.1)
timers (1.1.0)
treetop (1.4.12)
polyglot
polyglot (>= 0.3.1)
tzinfo (0.3.35)
unicorn (4.5.0)
kgio (~> 2.6)
rack
raindrops (~> 0.7)
uuidtools (2.1.3)
tzinfo (0.3.37)
uuidtools (2.1.4)
yajl-ruby (1.1.0)
yard (0.8.3)
yard (0.8.6.1)
PLATFORMS
ruby
DEPENDENCIES
ar-octopus!
bunny
dalli
database_cleaner (~> 0.8.0)
@ -305,18 +303,19 @@ DEPENDENCIES
micro_migrations!
mocha (~> 0.12)
pry
puma (= 1.6.3)
rack-cache (~> 1.2)
rack-contrib!
rake (~> 0.9.2)
rb-fsevent (~> 0.9.1)
rerun
rspec (~> 2.11)
sentry-raven!
simple_states!
sinatra!
sinatra-contrib!
sinatra
sinatra-contrib
travis-api!
travis-core!
travis-sidekiqs!
travis-support!
unicorn
yard-sinatra!

View File

@ -1,7 +1,11 @@
require 'bundler/setup'
ENV['SCHEMA'] = "#{Gem.loaded_specs['travis-core'].full_gem_path}/db/schema.rb"
require 'micro_migrations'
begin
require 'micro_migrations'
rescue LoadError
# we can't load micro migrations on production
end
require 'travis'
begin
@ -33,3 +37,6 @@ task 'travis-api.gemspec' do
end
task default: 'travis-api.gemspec'
tasks_path = File.expand_path('../lib/tasks/*.rake', __FILE__)
Dir.glob(tasks_path).each { |r| import r }

View File

@ -7,10 +7,12 @@ $stdout.sync = true
require 'travis/api/app'
require 'core_ext/module/load_constants'
# models = Travis::Model.constants.map(&:to_s)
# only = [/^(ActiveRecord|ActiveModel|Travis|GH|#{models.join('|')})/]
# [Travis::Api, Travis, GH].each do |target|
# target.load_constants! :only => only, :skip => ['Travis::Memory', 'GH::ResponseWrapper'], :debug => false
# end
models = Travis::Model.constants.map(&:to_s)
only = [/^(ActiveRecord|ActiveModel|Travis|GH|#{models.join('|')})/]
skip = ['Travis::Memory', 'GH::ResponseWrapper', 'Travis::NewRelic']
[Travis::Api, Travis, GH].each do |target|
target.load_constants! :only => only, :skip => skip, :debug => false
end
run Travis::Api::App.new

View File

@ -0,0 +1,23 @@
namespace :build do
namespace :migrate do
task :branch do
require 'travis'
Travis::Database.connect
Build.select(['id', 'commit_id']).pushes.includes(:commit).find_in_batches do |builds|
branches = Hash.new { |h, k| h[k] = [] }
builds.each do |build|
#next if build.branch
branches[build.commit.branch] << build.id
end
branches.each do |branch, ids|
Build.where(id: ids).update_all(branch: branch)
end
end; nil
end
end
end

View File

@ -0,0 +1,22 @@
namespace :build do
namespace :migrate do
task :pull_request_data do
require 'travis'
Travis::Database.connect
Build.pull_requests.includes(:request).order('id DESC').find_in_batches do |builds|
Build.transaction do
builds.each do |build|
next if build.pull_request_number && build.pull_request_title
attrs = {
:pull_request_number => build.request.pull_request_number,
:pull_request_title => build.request.pull_request_title
}
Build.where(id: build.id).update_all(attrs)
end
end
end
end
end
end

View File

@ -52,7 +52,11 @@ module Travis::Api
end
def self.deploy_sha
@deploy_sha ||= File.exist?('.deploy_sha') ? File.read('.deploy-sha')[0..7] : 'deploy-sha'
@deploy_sha ||= File.exist?(deploy_sha_path) ? File.read(deploy_sha_path)[0..7] : 'deploy-sha'
end
def self.deploy_sha_path
File.expand_path('../../../../.deploy-sha', __FILE__)
end
attr_accessor :app
@ -70,8 +74,8 @@ module Travis::Api
if Travis::Features.feature_active?(:use_rack_cache) && memcache_server
use Rack::Cache,
verbose: true,
metastore: "memcached://#{memcache_servers}/#{self.class.deploy_sha}",
entitystore: "memcached://#{memcache_servers}/#{self.class.deploy_sha}"
metastore: "memcached://#{memcache_servers}/#{Travis::Api::App.deploy_sha}",
entitystore: "memcached://#{memcache_servers}/#{Travis::Api::App.deploy_sha}"
end
use Rack::Deflater
@ -100,6 +104,10 @@ module Travis::Api
private
def self.console?
defined? Travis::Console
end
def self.setup!
setup_travis
load_endpoints
@ -109,18 +117,50 @@ module Travis::Api
def self.setup_travis
Travis::Amqp.config = Travis.config.amqp
Travis::Database.connect
setup_database_connections
Travis::Features.start
Sidekiq.configure_client do |config|
config.redis = Travis.config.redis.merge(size: 1, namespace: Travis.config.sidekiq.namespace)
if Travis.env == 'production' || Travis.env == 'staging'
Sidekiq.configure_client do |config|
config.redis = Travis.config.redis.merge(size: 1, namespace: Travis.config.sidekiq.namespace)
end
end
Raven.configure do |config|
config.dsn = Travis.config.sentry.dsn
end if Travis.config.sentry
if Travis.env == 'production' and not console?
Raven.configure do |config|
config.dsn = Travis.config.sentry.dsn
end if Travis.config.sentry
Travis::LogSubscriber::ActiveRecordMetrics.attach
$metriks_reporter = Metriks::Reporter::Logger.new
Travis::LogSubscriber::ActiveRecordMetrics.attach
Travis::Notification.setup
end
end
def self.setup_database_connections
Travis::Database.connect
return unless Travis.config.use_database_follower?
require 'octopus'
if Travis.env == 'production' || Travis.env == 'staging'
puts "Setting up the DB follower as a read slave"
# Octopus checks for Rails.env, just hardcode enabled?
Octopus.instance_eval do
def enabled?
true
end
end
ActiveRecord::Base.custom_octopus_connection = false
::Octopus.setup do |config|
config.shards = { :follower => Travis.config.database_follower }
config.environments = ['production', 'staging']
end
end
end
def self.load_endpoints

View File

@ -4,7 +4,7 @@ require 'securerandom'
class Travis::Api::App
class AccessToken
DEFAULT_SCOPES = [:public, :private]
attr_reader :token, :scopes, :user_id, :app_id
attr_reader :token, :scopes, :user_id, :app_id, :expires_in, :extra
def self.create(options = {})
new(options).tap(&:save)
@ -18,25 +18,40 @@ class Travis::Api::App
def self.find_by_token(token)
return token if token.is_a? self
user_id, app_id, *scopes = redis.lrange(key(token), 0, -1)
new(token: token, scopes: scopes, user_id: user_id, app_id: app_id) if user_id
extra = decode_json(scopes.pop) if scopes.last && scopes.last =~ /^json:/
new(token: token, scopes: scopes, user_id: user_id, app_id: app_id, extra: extra) if user_id
end
def initialize(options = {})
raise ArgumentError, 'must supply either user_id or user' unless options.key?(:user) ^ options.key?(:user_id)
raise ArgumentError, 'must supply app_id' unless options.key?(:app_id)
begin
@expires_in = Integer(options[:expires_in]) if options[:expires_in]
rescue ArgumentError
raise ArgumentError, 'expires_in must be of integer type'
end
@app_id = Integer(options[:app_id])
@scopes = Array(options[:scopes] || options[:scope] || DEFAULT_SCOPES).map(&:to_sym)
@user = options[:user]
@user_id = Integer(options[:user_id] || @user.id)
@token = options[:token] || reuse_token || SecureRandom.urlsafe_base64(16)
@extra = options[:extra]
end
def save
key = key(token)
redis.del(key)
redis.rpush(key, [user_id, app_id, *scopes].map(&:to_s))
data = [user_id, app_id, *scopes]
data << encode_json(extra) if extra
redis.rpush(key, data.map(&:to_s))
redis.set(reuse_key, token)
if expires_in
redis.expire(reuse_key, expires_in)
redis.expire(key, expires_in)
end
end
def user
@ -60,6 +75,14 @@ class Travis::Api::App
def key(token)
"t:#{token}"
end
def encode_json(hash)
'json:' + Base64.encode64(hash.to_json)
end
def decode_json(json)
JSON.parse(Base64.decode64(json.gsub(/^json:/, '')))
end
end
include Helpers
@ -68,7 +91,7 @@ class Travis::Api::App
private
def reuse_token
redis.get(reuse_key)
redis.get(reuse_key) unless expires_in
end
def reuse_key

View File

@ -1,6 +1,5 @@
require 'travis/api/app'
require 'sinatra/base'
require 'new_relic/agent/instrumentation/rack'
class Travis::Api::App
# Superclass for any endpoint and middleware.
@ -18,6 +17,17 @@ class Travis::Api::App
"This feature has not yet been implemented. Sorry :(\n\nPull Requests welcome!"
end
# hotfix??
def route_missing
@app ? forward : halt(404)
end
def call(env)
super
rescue Sinatra::NotFound
[404, {'Content-Type' => 'text/plain'}, ['Tell Konstantin to fix this!']]
end
configure do
# We pull in certain protection middleware in App.
# Being token based makes us invulnerable to common

View File

@ -10,7 +10,7 @@ class Travis::Api::App
set(:prefix) { "/" << name[/[^:]+$/].underscore }
set disable_root_endpoint: false
register :scoping
helpers :current_user, :flash
helpers :current_user, :flash, :db_follower
# TODO hmmm?
before { flash.clear }

View File

@ -4,10 +4,12 @@ class Travis::Api::App
class Endpoint
# Artifacts are generated by builds. Currently we only expose logs as
# artifacts
#
# **DEPRECATED** will be removed as soon as the client uses /logs/:id
class Artifacts < Endpoint
# Fetches an artifact by it's *id*.
get '/:id' do |id|
respond_with service(:find_artifact, params)
respond_with service(:find_log, params)
end
end
end

View File

@ -218,7 +218,7 @@ class Travis::Api::App
end
def user_for_github_token(token, drop_token = false)
data = GH.with(token: token.to_s) { GH['user'] }
data = GH.with(token: token.to_s, client_id: nil) { GH['user'] }
scopes = parse_scopes data.headers['x-oauth-scopes']
halt 403, 'insufficient access: %p' unless acceptable? scopes

View File

@ -4,7 +4,8 @@ class Travis::Api::App
class Endpoint
class Builds < Endpoint
get '/' do
respond_with service(:find_builds, params)
name = params[:branches] ? :find_branches : :find_builds
respond_with service(name, params)
end
get '/:id' do

View File

@ -1,4 +1,5 @@
require 'travis/api/app'
require 'travis/api/app/endpoint/documentation/resources'
class Travis::Api::App
class Endpoint
@ -44,6 +45,7 @@ class Travis::Api::App
def with_code_highlighting(str)
str.
gsub(/json\(:([^)]+)\)/) { "<pre>" + Resources::Helpers.json($1) + "</pre>" }.
gsub('<pre', '<pre class="prettyprint linenums pre-scrollable"').
gsub(/<\/?code>/, '').
gsub(/TODO:?/, '<span class="label label-warning">TODO</span>')
@ -81,154 +83,116 @@ __END__
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta charset="utf-8">
<title>Travis API documentation</title>
<!-- we might wanna change this -->
<link href="<%= url('/css/bootstrap.css') %>" rel="stylesheet" />
<link href="<%= url('/css/prettify.css') %>" rel="stylesheet" />
<script src="<%= url('/js/jquery.js') %>"></script>
<script src="<%= url('/js/prettify.js') %>"></script>
<script src="<%= url('/js/bootstrap.min.js') %>"></script>
<style type="text/css">
header {
position: relative;
text-align: center;
margin-top: 36px;
}
header h1 {
text-shadow: 2px 2px 5px #000;
margin-bottom: 9px;
font-size: 81px;
font-weight: bold;
letter-spacing: -1px;
line-height: 1;
}
header p {
margin-bottom: 18px;
font-weight: 300;
font-size: 18px;
}
.page-header {
margin-top: 90px;
}
.route {
margin-bottom: 36px;
}
.page-header a {
color: black;
}
.nav-list a {
color: inherit !important;
}
</style>
<!-- <link href="<%= url('/css/bootstrap.css') %>" rel="stylesheet" /> -->
<!-- <link href="<%= url('/css/prettify.css') %>" rel="stylesheet" /> -->
<link href="<%= url('/css/style.css') %>" rel="stylesheet" />
<!-- <script src="<%= url('/js/jquery.js') %>"></script> -->
<!-- <script src="<%= url('/js/prettify.js') %>"></script> -->
<!-- <script src="<%= url('/js/bootstrap.min.js') %>"></script> -->
</head>
<body onload="prettyPrint()">
<a href="https://github.com/travis-ci/travis-api">
<img style="position: absolute; top: 0; right: 0; border: 0;"
src="https://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png"
alt="Fork me on GitHub">
</a>
<div class="container">
<div class="row">
<header class="span12">
<h1>The Travis API</h1>
<p>All the routes, just waiting for you to build something awesome.</p>
</header>
<div id="navigation">
<div class="wrapper">
<a href="http://travis-ci.org" id="logo">travis-ci<span>.org</span></a>
<ul>
<li><a href="http://about.travis-ci.org/blog/">Blog</a></li>
<li><a href="http://about.travis-ci.org/docs/">User Documentation</a></li>
</ul>
</div>
</div>
<div class="row">
<div id="header">
<div class="wrapper">
<h1 class="riddle"><a href="/docs" title="Travis API">The Travis API</a></h1>
<p>All the routes, just waiting for you to build something awesome.</p>
</div>
</div>
<aside class="span3">
<div class="page-header">
<h1>Navigation</h1>
<div id="content">
<div class="wrapper">
<div class="pad">
<div id="main">
<% general_docs.each do |doc| %>
<%= erb :entry, locals: doc %>
<% end %>
<% endpoints.each do |endpoint| %>
<%= erb :entry, {},
id: endpoint['name'],
title: endpoint['name'],
content: erb(:endpoint_content, {}, endpoint: endpoint) %>
<% end %>
</div>
<div class="well" style="padding: 8px 0;">
<ul class="nav nav-list">
<% general_docs.each do |doc| %>
<li class="nav-header"><a href="#<%= doc[:id] %>"><%= doc[:title] %></a></li>
<% doc[:subheaders].each do |sub| %>
<li><a href="#<%= sub %>"><%= sub %></a></li>
<% end %>
<div id="sidebar">
<% general_docs.each do |doc| %>
<h2><a href="#<%= doc[:id] %>"><%= doc[:title] %></a></h2>
<ul>
<% doc[:subheaders].each do |sub| %>
<li><a href="#<%= sub %>"><%= sub %></a></li>
<% end %>
<li class="divider"></li>
<% endpoints.each do |endpoint| %>
<li class="nav-header"><a href="#<%= endpoint['name'] %>"><%= endpoint['name'] %></a></li>
<% endpoint['routes'].each do |route| %>
<li>
<a href="#<%= slug_for(route) %>">
<i class="icon-<%= icon_for route['verb'] %>"></i>
<tt><%= route['uri'] %></tt>
</a>
</li>
<% end %>
</ul>
<% end %>
<% endpoints.each do |endpoint| %>
<h2><a href="#<%= endpoint['name'] %>"><%= endpoint['name'] %></a></h2>
<ul>
<% endpoint['routes'].each do |route| %>
<li>
<a href="#<%= slug_for(route) %>">
<i class="icon-<%= icon_for route['verb'] %>"></i>
<tt><%= route['uri'] %></tt>
</a>
</li>
<% end %>
<li class="divider"></li>
<li class="nav-header">
External Links
</li>
<li>
<a href="https://travis-ci.org">
<i class="icon-globe"></i>
Travis CI
</a>
</li>
<li>
<a href="https://github.com/travis-ci/travis-api">
<i class="icon-cog"></i>
Source Code
</a>
</li>
<li>
<a href="https://github.com/travis-ci/travis-api/issues">
<i class="icon-list-alt"></i>
API issues
</a>
</li>
<li>
<a href="https://github.com/travis-ci/travis-ember">
<i class="icon-play-circle"></i>
Example Client
</a>
</li>
</ul>
<% end %>
<h2>External Links</h2>
<ul>
<li><a href="https://travis-ci.org">Travis CI</a></li>
<li><a href="https://github.com/travis-ci/travis-api">Source Code</a></li>
<li><a href="https://github.com/travis-ci/travis-api/issues">API issues</a></li>
<li><a href="https://github.com/travis-ci/travis-web">Example Client</a></li>
</ul>
</div>
</aside>
<section class="span9">
<% general_docs.each do |doc| %>
<%= erb :entry, locals: doc %>
<% end %>
<% endpoints.each do |endpoint| %>
<%= erb :entry, {},
id: endpoint['name'],
title: endpoint['name'],
content: erb(:endpoint_content, {}, endpoint: endpoint) %>
<% end %>
</section>
</div>
</div>
</div>
<div id="footer">
<div class="wrapper">
<div class="box">
<p>This site is maintained by the <a href="http://github.com/travis-ci">Travis CI community</a>. Feel free to <a href="http://github.com/travis-ci/travis-api">contribute</a>!</p>
</div>
<div class="box">
<p>This design was kindly provided by the talented Ben Webster of <a href="http://www.plus2.com.au">Plus2</a>.</p>
</div>
<div class="box last">
<ul>
<li><a href="https://github.com/travis-ci" title="">Travis CI on GitHub</a></li>
<li><a href="https://twitter.com/travisci" title="">Travis CI on Twitter</a></li>
</ul>
</div>
</div>
</div>
</body>
</html>
@@ endpoint_content
<% unless endpoint['doc'].to_s.empty? %>
<%= docs_for endpoint %>
<hr>
<% end %>
<% endpoint['routes'].each do |route| %>
<div class="route" id="<%= slug_for(route) %>">
<pre><h3><%= route['verb'] %> <%= route['uri'] %></h3></pre>
<h3><%= route['verb'] %> <%= route['uri'] %></h3>
<% if route['scope'] %>
<p>
<h5>Required autorization scope: <span class="label"><%= route['scope'] %></span></h5>
<h5>Required authorization scope: <span class="label"><%= route['scope'] %></span></h5>
</p>
<% end %>
<%= docs_for route %>
@ -237,11 +201,7 @@ __END__
@@ entry
<div id="<%= id %>">
<div class="page-header">
<h1>
<a href="#<%= id %>"><%= title %></a>
</h1>
</div>
<h2><%= title %> <a class="toc-anchor" href="#<%= id %>">#</a></h2>
<%= content %>
</div>

View File

@ -0,0 +1,594 @@
/* ---( = begin global reset thanks to eric meyer elements )------------------------------- */
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, font, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td {
margin: 0;
padding: 0;
border: 0;
outline: 0;
font-weight: inherit;
font-style: inherit;
font-size: 100%;
font-family: inherit;
vertical-align: baseline;
}
/* remember to define focus styles! */
:focus {
outline: 0;
}
body {
line-height: 1;
color: black;
background: white;
}
ol, ul {
list-style: none;
}
/* tables still need 'cellspacing="0"' in the markup */
table {
border-collapse: separate;
border-spacing: 0;
}
caption, th, td {
text-align: left;
font-weight: normal;
}
blockquote:before, blockquote:after, q:before, q:after {
content: "";
}
blockquote, q {
quotes: "" "";
}
/* travis-ci styles */
body {
margin: 0 0 1em 0;
font-size: 14px;
line-height: 1.4286;
color: #555;
background: #fff;
font-family: "Helvetica Neue", Arial, Verdana, sans-serif;
}
a {
color: #3366cc;
outline: none;
text-decoration: underline;
}
a:visited {
color: #666;
}
a:hover {
color: #66cc33;
text-decoration: none;
}
p, ul, blockquote, pre, td, th, label {
margin: 1.4286em 0;
font-size: 1em;
line-height: 1.4286;
}
blockquote {
font-style: italic;
margin-left: 1em;
}
blockquote small.author {
font-style: normal;
margin-top: 10px;
text-align: right;
}
blockquote small.author:after {
content: ":";
}
p small.author {
margin: 0;
}
ul, ol {
margin: 1.4286em 0;
text-align: left;
}
li {
line-height: 1.4286;
}
table {
border-collapse: collapse;
margin-bottom: 1.5em;
}
strong {
font-family: Helvetica, Arial;
color: #8e7a2b;
font-weight: bold;
}
em {
font-style: italic;
}
span.help {
font-style: italic;
background-color: #ffff99;
font-family: Georgia, Times, Serif;
}
pre {
margin-top: 1em;
padding: 1em 1.5em;
line-height: 1.5em;
border: 1px solid #ddd;
background: #fafafa;
border-bottom-left-radius: 8px 8px;
border-bottom-right-radius: 8px 8px;
border-top-left-radius: 8px 8px;
border-top-right-radius: 8px 8px;
font-family: monospace;
font-size: 13px;
overflow-x: scroll;
}
p > code, div > code, li > code {
background-color: #fafafa;
border: 1px solid #e0e0e0;
color: #333;
padding: 0px 0.2em;
font-family: monospace;
font-size: 13.3px;
}
.wrapper {
width: 960px;
margin: 0 auto;
text-align: left;
overflow: hidden;
position: relative;
}
div#navigation {
clear: both;
margin: 0 0 0 0;
padding: .8em 0 .6em 0;
overflow: hidden;
background: #efefef;
color: #888;
border-bottom: 1px solid #ccc;
}
div#header {
clear: both;
margin: 1em auto 0 auto;
padding-top: 10px;
text-align: center;
overflow: hidden;
}
div#navigation .wrapper {
width: 940px;
}
div#navigation .wrapper a#logo {
color: #828282;
display:block;
float: left;
font-weight: bold;
font-size: 1.1em;
line-height: 1.2em;
margin: 0.15em 1em 0 0;
padding: 0 1em 0 0;
text-decoration: none;
}
div#navigation .wrapper a#logo span {
color: #bbbaba;
font-size: .85em;
}
div#navigation ul {
margin:0;
padding:0;
}
div#navigation ul li {
margin: 0;
padding: 0;
float: left;
border-left: 1px solid #ccc;
}
div#navigation ul li.right {
float: right;
border-left: none;
font-size: .83em;
}
div#navigation ul li a,
div#navigation ul li a:visited {
color: #999;
display:block;
padding: 0em 1em 0 1em ;
text-decoration:none;
}
div#navigation ul li a:hover,
div#navigation ul li a:visited {
text-decoration:underline;
}
div#navigation ul li.selected a,
div#navigation ul li.selected a:visited {
text-decoration: none;
color: #6c3;
}
div#navigation ul li.active a,
div#navigation ul li.selected a:visited {
text-decoration: none;
color: #444;
}
div#content {
clear: both;
margin: 0 auto 3em auto;
padding: 0 0 0;
text-align:center;
overflow: hidden;
}
div#content div.pad {
margin-left: 20px;
margin-right: 20px;
}
div#main {
float:left;
width: 580px;
overflow: hidden;
padding-top: 2em;
}
#main ul,
#main ul li {
list-style-type: disc;
}
#main ol,
#main ol li {
list-style-type: decimal;
}
#main li {
margin-left: 1.4286em;
}
.clear {
clear: both;
}
#main figure {
clear: both !important;
width: 578px;
border: 1px solid #efefef;
text-align: center;
margin: 0px;
border-radius: 4px;
}
#main figure figcaption {
background-color: #efefef;
text-align: left;
font-size: 80%;
padding: 5px 10px;
}
#main figure img {
max-width: 570px;
margin: 4px;
}
#main figure.left {
margin-bottom: 1.4286em;
margin-right: 1.4286em;
float: left;
}
#main figure.right {
margin-bottom: 1.4286em;
margin-left: 1.4286em;
float: right;
}
#main figure.smallest {
width: 138px;
}
#main figure.smallest img {
width: 130px;
}
#main figure.smaller {
width: 208px;
}
#main figure.smaller img {
width: 200px;
}
#main figure.small {
width: 258px;
}
#main figure.small img {
max-width: 250px;
}
div#sidebar {
float:right;
width: 325px;
overflow: hidden;
padding-top: 0;
}
#sidebar ul {
margin:0 0 1.5em 0;
padding:0;
}
#sidebar p {
margin: 0;
padding: 0;
}
#sidebar p,
#sidebar ul li p,
#sidebar ul li a,
#sidebar ul li a:visited {
display:block;
text-decoration:none;
padding:4px 0 3px 20px;
border-top: 1px solid #efefef;
}
#sidebar ul li a:hover,
#sidebar ul li a:visited:hover {
text-decoration:underline;
}
#sidebar ul li.selected a,
#sidebar ul li.selected a:visited {
color: #6c3;
background: #efefef;
margin-left: 0;
padding-left: 20px;
}
#sidebar ul li ul {
border-bottom: 0;
list-style-type: disc;
margin-bottom: 0.5em;
}
#sidebar ul li ul li {
border-bottom: 0;
list-style-type: disc;
margin-left: 1.5em;
}
#sidebar ul li ul li a,
#sidebar ul li ul li a:visited,
#sidebar ul li.selected ul li a,
#sidebar ul li.selected ul li a:visited {
display:block;
text-decoration:none;
padding:0;
border-top: 0;
font-weight: normal;
color: #195190;
}
#sidebar ul li ul li.selected a,
#sidebar ul li ul li.selected a:visited {
color: #444;
font-weight: normal;
}
div#footer {
clear: both;
margin: 0 auto;
padding-top: 10px;
text-align: center;
overflow: hidden;
background: #efefef;
border-top: 1px solid #ddd;
border-bottom: 1px solid #ddd
}
div#footer .wrapper {
background: transparent;
width: 920px;
}
div#footer div.box {
float: left;
width: 280px;
margin-right: 30px;
color: #888;
}
div#footer div.last {
margin-right: 0;
margin-left: 15px;
}
div#footer div.box ul li a,
div#footer div.box ul li a:visited {
color: #7d8997;
}
h1, h2, h3, h4, h5, h6 {
margin: 0;
padding: 0;
font-weight: bold;
color: #40454F;
}
h1 {
font-size: 3em;
line-height: 1em;
margin-bottom: 0.5em;
clear: both;
}
h2 {
font-size: 1.6em;
margin-bottom: 1em;
line-height: 1em;
letter-spacing: -0.5px;
clear: both;
}
.meta {
margin-top: -1.7em;
font-size: 80%;
}
h3 {
font-size: 1.15em;
line-height: 1em;
margin-bottom: 0.5em;
}
h4 {
font-size: 0.95em;
line-height: 1.25;
margin-bottom: 1.25em;
height: 1.25em;
}
h1 img, h2 img, h3 img, h4 img, h5 img, h6 img {
margin: 0;
}
h1 a,
h1 a:visited,
h2 a,
h2 a:visited,
h3 a,
h3 a:visited,
h4 a,
h4 a:visited,
h5 a,
h5 a:visited {
color: #464755;
}
div#header h1 {
float: left;
width: 350px;
text-indent: -20000em;
background: transparent;
font-size:1px;
margin: 20px 0 0 20px;
}
div#header h1 a,
div#header h1 a:visited,
div#header h1 a:hover,
div#header h1 a:visited:hover {
display: block;
width: 350px;
height: 75px;
}
div#header h1.riddle {
float: none;
width: 100%;
text-indent: 0;
background: #fff;
color: #000;
font-size:4em;
margin: 20px 0 0 0;
padding:0;
}
div#header h1.riddle a,
div#header h1.riddle a:visited,
div#header h1.riddle a:hover,
div#header h1.riddle a:visited:hover {
display: block;
width: 100%;
height: auto;
color: #000;
text-decoration: none;
}
div#header p {
color: #777;
display: block;
font: italic 1.25em Georgia, Times, Serif;
line-height: 1.67em;
margin: 0.935em 0 1.87em 0;
padding: 0 0 1.25em 0;
border-bottom: 1px solid #ccc;
}
div#sidebar h2 {
font-size: .9em;
letter-spacing: .5px;
text-transform:uppercase;
margin: 40px 20px 5px 20px;
}
.highlight pre {
font-family: 'Bitstream Vera Sans Mono', 'Courier', monospace;
padding: 0 1.5em;
}
.highlight br {
display: none;
}
.highlight {
background-color: #f8f8f8;
border: 1px solid silver;
font-family: 'Courier New', 'Terminal', monospace;
color: #100;
margin: 1.5em 0;
}
div#content div.pad {
margin-left: 0;
margin-right: 0;
}
div#navigation ul li.lang {
float: right;
border-left: none;
font-size: .83em;
}
#dsq-global-toolbar ul li {
list-style-type: none;
}

View File

@ -0,0 +1,141 @@
require 'json'
class Travis::Api::App::Endpoint
module Resources
module Helpers
def self.json(key)
JSON.pretty_generate(Resources.const_get(key.to_s.upcase))
end
end
REPOSITORY_KEY = {
'public_key' => '-----BEGIN RSA PUBLIC KEY-----\nMIGJAoGBAOcx131amMqIzm5+FbZz+DhIgSDbFzjKKpzaN5UWVCrLSc57z64xxTV6\nkaOTZmjCWz6WpaPkFZY+czfL7lmuZ/Y6UNm0vupvdZ6t27SytFFGd1/RJlAe89tu\nGcIrC1vtEvQu2frMLvHqFylnGd5Gy64qkQT4KRhMsfZctX4z5VzTAgMBAAE=\n-----END RSA PUBLIC KEY-----\n',
}
REPOSITORY = {
'repo' => {
'id' => 119756,
'slug' => 'travis-ci/travis-api',
'description' => 'The public Travis API',
'last_build_id' => 6347735,
'last_build_number' => '468',
'last_build_state' => 'started',
'last_build_duration' => nil,
'last_build_language' => nil,
'last_build_started_at' => '2013-04-15T09:45:29Z',
'last_build_finished_at' => nil,
}
}
REPOSITORIES = { 'repos' => [ REPOSITORY['repo'] ] }
SHORT_BUILD = {
'id' => 6347735,
'repository_id' => 119756,
'commit_id' => 1873023,
'number' => '468',
'pull_request' => false,
'pull_request_title' => nil,
'pull_request_number' => nil,
'config' => {
'language' => 'ruby',
'rvm' => [
'1.9.3',
'rbx-19mode',
'jruby-19mode',
],
'before_script' => [
'RAILS_ENV=test rake db:create db:schema:load --trace',
],
'notifications' => {
'irc' => 'irc.freenode.org#travis',
},
'matrix' => {
'allow_failures' => [
{
'rvm' => 'rbx-19mode',
},
{
'rvm' => 'jruby-19mode',
},
],
},
'.result' => 'configured',
},
'state' => 'passed',
'started_at' => '2013-04-15T09:45:29Z',
'finished_at' => '2013-04-15T09:49:42Z',
'duration' => 489,
'job_ids' => [
6347736,
6347737,
6347738,
],
}
COMMIT = {
'id' => 1873023,
'sha' => 'a18f211f6f921affd1ecd8c18691b40d9948aae5',
'branch' => 'master',
'message' => "Merge pull request #25 from henrikhodne/add-responses-to-documentation\n\nAdd responses to documentation",
'committed_at' => '2013-04-15T09:44:31Z',
'author_name' => 'Henrik Hodne',
'author_email' => 'me@henrikhodne.com',
'committer_name' => 'Henrik Hodne',
'committer_email' => 'me@henrikhodne.com',
'compare_url' => 'https://github.com/travis-ci/travis-api/compare/0f31ff4fb6aa...a18f211f6f92',
'pull_request_number' => nil,
}
BUILDS = {
'builds' => [
SHORT_BUILD
],
'commits' => [
COMMIT
]
}
JOB = {
'id' => 6347736,
'repository_id' => 119756,
'build_id' => 6347735,
'commit_id' => 1873023,
'log_id' => 1219815,
'state' => 'passed',
'number' => '468.1',
'config' => {
'language' => 'ruby',
'rvm' => '1.9.3',
'before_script' => [
'RAILS_ENV=test rake db:create db:schema:load --trace',
],
'notifications' => {
'irc' => 'irc.freenode.org#travis',
},
'matrix' => {
'allow_failures' => [
{
'rvm' => 'rbx-19mode',
},
{
'rvm' => 'jruby-19mode',
}
]
},
'.result' => 'configured'
},
'started_at' => '2013-04-15T09:45:29Z',
'finished_at' => '2013-04-15T09:48:14Z',
'queue' => 'builds.linux',
'allow_failure' => false,
'tags' => '',
}
BUILD = {
'build' => SHORT_BUILD,
'commit' => COMMIT,
'jobs' => [ JOB ]
}
end
end

View File

@ -10,6 +10,31 @@ class Travis::Api::App
get '/:id' do
respond_with service(:find_job, params)
end
get '/:job_id/log' do
resource = service(:find_log, params).run
if !resource || resource.archived?
archived_log_path = archive_url("/jobs/#{params[:job_id]}/log.txt")
if params[:cors_hax]
status 204
headers['Access-Control-Expose-Headers'] = 'Location'
headers['Location'] = archived_log_path
else
redirect archived_log_path, 307
end
else
respond_with resource
end
end
def archive_url(path)
"https://s3.amazonaws.com/#{hostname('archive')}#{path}"
end
def hostname(name)
"#{name}#{'-staging' if Travis.env == 'staging'}.#{Travis.config.host.split('.')[-2, 2].join('.')}"
end
end
end
end

View File

@ -0,0 +1,21 @@
require 'travis/api/app'
class Travis::Api::App
class Endpoint
# Logs are generated by builds.
class Logs < Endpoint
# Fetches a log by it's *id*.
get '/:id' do |id|
respond_with service(:find_log, params)
end
put '/:id' do |id|
# TODO @rkh ... rather lost in the auth/scopes code.
token = env['HTTP_TOKEN']
halt 403, 'no token' unless token
halt 403, 'internal' unless token == Travis.config.tokens.internal
respond_with service(:update_log, params)
end
end
end
end

View File

@ -8,10 +8,19 @@ class Travis::Api::App
# You can filter the repositories by adding parameters to the request. For example, you can get all repositories
# owned by johndoe by adding `owner_name=johndoe`, or all repositories that johndoe has access to by adding
# `member=johndoe`. The parameter names correspond to the keys of the response hash.
#
# ### Response
#
# json(:repositories)
get '/' do
respond_with service(:find_repos, params)
end
# Gets the repository with the given id.
#
# ### Response
#
# json(:repository)
get '/:id' do
respond_with service(:find_repo, params)
end
@ -20,6 +29,15 @@ class Travis::Api::App
respond_with service(:find_repo, params.merge(schema: 'cc'))
end
# Get the public key for the repository with the given id.
#
# This can be used to encrypt secure variables in the build configuration. See
# [the encryption keys](http://about.travis-ci.org/docs/user/encryption-keys/) documentation page for more
# information.
#
# ### Response
#
# json(:repository_key)
get '/:id/key' do
respond_with service(:find_repo_key, params), version: :v2
end
@ -28,14 +46,31 @@ class Travis::Api::App
respond_with service(:regenerate_repo_key, params), version: :v2
end
# Gets the repository with the given name.
#
# ### Response
#
# json(:repository)
get '/:owner_name/:name' do
respond_with service(:find_repo, params)
prefer_follower do
respond_with service(:find_repo, params)
end
end
# Gets the builds for the repository with the given name.
#
# ### Response
#
# json(:builds)
get '/:owner_name/:name/builds' do
respond_with service(:find_builds, params)
end
# Get a build with the given id in the repository with the given name.
#
# ### Response
#
# json(:build)
get '/:owner_name/:name/builds/:id' do
respond_with service(:find_build, params)
end
@ -44,6 +79,15 @@ class Travis::Api::App
respond_with service(:find_repo, params.merge(schema: 'cc'))
end
# Get the public key for a given repository.
#
# This can be used to encrypt secure variables in the build configuration. See
# [the encryption keys](http://about.travis-ci.org/docs/user/encryption-keys/) documentation page for more
# information.
#
# ### Response
#
# json(:repository_key)
get '/:owner_name/:name/key' do
respond_with service(:find_repo_key, params), version: :v2
end

View File

@ -4,11 +4,11 @@ class Travis::Api::App
class Endpoint
class Workers < Endpoint
get '/' do
respond_with service(:find_workers, params)
respond_with service(:find_workers, params), type: :workers
end
get '/:id' do
respond_with service(:find_worker, params)
respond_with service(:find_worker, params), type: :worker
end
end
end

View File

@ -11,6 +11,16 @@ class Travis::Api::App
def public?
scope == :public
end
def required_params_match?
return true unless token = env['travis.access_token']
if token.extra && (required_params = token.extra['required_params'])
required_params.all? { |name, value| params[name] == value }
else
true
end
end
end
def self.registered(app)
@ -18,23 +28,35 @@ class Travis::Api::App
app.helpers(Helpers)
end
def scope(name)
def scope(*names)
condition do
name = settings.default_scope if name == :default
names = [settings.default_scope] if names == [:default]
scopes = env['travis.access_token'].try(:scopes) || settings.anonymous_scopes
headers['X-OAuth-Scopes'] = scopes.map(&:to_s).join(',')
headers['X-Accepted-OAuth-Scopes'] = name.to_s
if scopes.include? name
env['travis.scope'] = name
headers['Vary'] = 'Accept'
headers['Vary'] << ', Authorization' unless public?
true
elsif env['travis.access_token']
pass { halt 403, "insufficient access" }
else
pass { halt 401, "no access token supplied" }
result = names.any? do |name|
if scopes.include?(name) && required_params_match?
headers['X-OAuth-Scopes'] = scopes.map(&:to_s).join(',')
headers['X-Accepted-OAuth-Scopes'] = name.to_s
env['travis.scope'] = name
headers['Vary'] = 'Accept'
headers['Vary'] << ', Authorization' unless public?
true
end
end
if !result
headers['X-OAuth-Scopes'] = scopes.map(&:to_s).join(',')
headers['X-Accepted-OAuth-Scopes'] = names.first.to_s
if env['travis.access_token']
pass { halt 403, "insufficient access" }
else
pass { halt 401, "no access token supplied" }
end
end
result
end
end

View File

@ -7,6 +7,76 @@ class Travis::Api::App
DEFAULT_VERSION = 'v1'
DEFAULT_FORMAT = 'json'
class Entry
SEPARATORS = Regexp.escape("()<>@,;:\/[]?={}\t ")
TOKEN = /[^#{SEPARATORS}]+/
attr_reader :type, :subtype, :quality, :version, :params
def initialize(accept_string)
@type, @subtype, @quality, @version, @params = parse(accept_string)
end
def <=>(other)
[1 - quality, mime_type.count('*'), 1 - params.size] <=>
[1 - other.quality, other.mime_type.count('*'), 1 - other.params.size]
end
def mime_type
"#{type}/#{subtype}"
end
def version
version = @version || params['version']
version ? "v#{version}" : nil
end
def accepts?(mime_type)
return true if self.mime_type == '*/*'
type, subtype = mime_type.scan(%r{(#{TOKEN})/(#{TOKEN})}).flatten
type == self.type && (self.subtype == '*' || subtype == self.subtype)
end
def to_s
str = "#{mime_type}; q=#{quality}"
str << "; #{params.map { |k,v| "#{k}=#{v}" }.join('; ')}" if params.length > 0
str
end
private
def parse(str)
# this handles only subset of what Accept header can
# contain, only the simplest cases, no quoted strings etc.
type, subtype, params = str.scan(%r{(#{TOKEN})/(#{TOKEN})(.*)}).flatten
quality = 1
version = nil
if params
params = Hash[*params.split(';').map { |p| p.scan /(#{TOKEN})=(#{TOKEN})/ }.flatten]
quality = params.delete('q').to_f if params['q']
end
if subtype =~ HEADER_FORMAT
subtype = $2
version = $1
end
[type, subtype, quality, version, params]
end
end
def accept_entries
entries = env['HTTP_ACCEPT'].to_s.delete(' ').to_s.split(',').map { |e| Entry.new(e) }
entries.empty? ? [Entry.new('*/*')] : entries.sort
end
def acceptable_formats
if format = env['travis.format_from_path']
[Entry.new(Rack::Mime.mime_type(".#{format}"))]
else
accept_entries
end
end
def accept_version
@accept_version ||= request.accept.join =~ HEADER_FORMAT && "v#{$1}" || DEFAULT_VERSION
end

View File

@ -0,0 +1,17 @@
require 'travis/api/app'
class Travis::Api::App
module Helpers
module DbFollower
def prefer_follower
if Travis.config.use_database_follower?
Octopus.using(:follower) do
yield
end
else
yield
end
end
end
end
end

View File

@ -6,11 +6,12 @@ class Travis::Api::App
# convert (in addition to the return values supported by Sinatra, of
# course). These values will be encoded in JSON.
module RespondWith
include Accept
def respond_with(resource, options = {})
options[:format] ||= env['travis.format']
result = respond(resource, options)
result = result ? result.to_json : 404
halt result
result = result.to_json if result && response.content_type =~ /application\/json/
halt result || 404
end
def body(value = nil, options = {}, &block)
@ -21,15 +22,34 @@ class Travis::Api::App
private
def respond(resource, options)
responders(resource, options).each do |const|
responder = const.new(self, resource, options)
resource = responder.apply if responder.apply?
resource = apply_service_responder(resource, options)
response = nil
acceptable_formats.find do |accept|
responders(resource, options).find do |const|
responder = const.new(self, resource, options.dup.merge(accept: accept))
response = responder.apply if responder.apply?
end
end
if responders = options[:responders]
responders.each do |klass|
responder = klass.new(self, response, options)
response = responder.apply if responder.apply?
end
end
response || (resource ? error(406) : error(404))
end
def apply_service_responder(resource, options)
responder = Responders::Service.new(self, resource, options)
resource = responder.apply if responder.apply?
resource
end
def responders(resource, options)
[:Service, :Json, :Image, :Xml, :Plain].map do |name|
[:Json, :Image, :Xml, :Plain].map do |name|
Responders.const_get(name)
end
end

View File

@ -24,6 +24,7 @@ class Travis::Api::App
def extract_format
env['PATH_INFO'].sub!(FORMAT, '')
env['travis.format_from_path'] = $1
env['travis.format'] = $1 || accept_format
end
@ -40,6 +41,7 @@ class Travis::Api::App
end
def force_redirect(path)
path += "?#{request.query_string}" unless request.query_string.empty?
response.body = ''
response['Content-Length'] = '0'
response['Content-Type'] = ''

View File

@ -31,5 +31,23 @@ module Travis::Api::App::Responders
def headers
endpoint.headers
end
def apply
endpoint.content_type content_type
end
def apply?
resource && acceptable_format?
end
def format
self.class.name.split('::').last.downcase
end
def acceptable_format?
if accept = options[:accept]
accept.accepts?(Rack::Mime.mime_type(".#{format}"))
end
end
end
end

View File

@ -1,18 +1,22 @@
module Travis::Api::App::Responders
class Image < Base
def apply?
options[:format] == 'png'
def format
'png'
end
def apply
headers['Pragma'] = "no-cache"
headers['Expires'] = Time.now.utc.httpdate
headers['Content-Disposition'] = %(inline; filename="#{File.basename(filename)}")
halt send_file(filename, type: :png)
send_file(filename, type: :png, last_modified: last_modified)
end
private
def content_type
'image/png'
end
def filename
"#{root}/public/images/result/#{result}.png"
end
@ -24,5 +28,10 @@ module Travis::Api::App::Responders
def root
File.expand_path('.') # TODO wat.
end
def last_modified
resource ? resource.last_build_finished_at : nil
end
end
end

View File

@ -4,21 +4,54 @@ class Travis::Api::App
include Helpers::Accept
def apply?
options[:format] == 'json' && !resource.is_a?(String) && !resource.nil?
super && !resource.is_a?(String) && !resource.nil? && accepts_log?
end
def apply
halt result.to_json
super
result
end
private
def content_type
'application/json;charset=utf-8'
end
def accepts_log?
return true unless resource.is_a?(Log)
chunked = accept_params[:chunked]
chunked ? !resource.aggregated_at : true
end
def result
builder ? builder.new(resource, request.params).data : resource
builder ? builder.new(resource, params).data : basic_type_resource
end
def builder
@builder ||= Travis::Api.builder(resource, { :version => accept_version }.merge(options))
if defined?(@builder)
@builder
else
@builder = Travis::Api.builder(resource, { :version => version }.merge(options))
end
end
def accept_params
(options[:accept].params || {}).symbolize_keys
end
def version
options[:accept].version || Travis::Api::App::Helpers::Accept::DEFAULT_VERSION
end
def params
(request.params || {}).merge(accept_params)
end
def basic_type_resource
resource if resource.is_a?(Hash)
end
end
end

View File

@ -1,26 +1,35 @@
module Travis::Api::App::Responders
class Plain < Base
def format
'txt'
end
def apply?
# make sure that we don't leak anything by processing only Artifact::Log
# make sure that we don't leak anything by processing only Log
# instances here. I don't want to create entire new API builder just
# for log's content for now.
#
# TODO: think how to handle other formats correctly
options[:format] == 'txt' && resource.is_a?(Artifact::Log)
super && resource.is_a?(Log)
end
def apply
super
filename = resource.id
disposition = params[:attachment] ? 'attachment' : 'inline'
headers['Content-Disposition'] = %(#{disposition}; filename="#{filename}")
endpoint.content_type 'text/plain'
halt(params[:deansi] ? clear_ansi(resource.content) : resource.content)
params[:deansi] ? clear_ansi(resource.content) : resource.content
end
private
def content_type
'text/plain'
end
def clear_ansi(content)
content.gsub(/\r\r/, "\r")
.gsub(/^.*\r(?!$)/, '')

View File

@ -16,19 +16,25 @@ module Travis::Api::App::Responders
}
def apply?
options[:format] == 'xml'
super && resource.is_a?(Repository)
end
def apply
halt TEMPLATE % data
super
TEMPLATE % data
end
private
def content_type
'application/xml;charset=utf-8'
end
def data
{
name: resource.slug,
url: [Travis.config.domain, resource.slug].join('/'),
url: File.join("https://", Travis.config.client_domain, resource.slug),
activity: activity,
label: last_build.try(:number),
status: status,

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 932 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -3,6 +3,6 @@ cd "$(dirname "$0")/.."
[ $PORT ] || PORT=3000
[ $RACK_ENV ] || RACK_ENV=development
cmd="ruby -I lib -S bundle exec ruby -I lib -S unicorn config.ru -c config/unicorn.rb -p $PORT -E $RACK_ENV"
[[ $RACK_ENV == "development" ]] && exec rerun "$cmd -l 127.0.0.1:$PORT"
cmd="ruby -I lib -S bundle exec ruby -I lib -S puma config.ru -p $PORT -e $RACK_ENV --threads 0:16"
[[ $RACK_ENV == "development" ]] && exec rerun "$cmd -b tcp://127.0.0.1:$PORT"
exec $cmd

View File

@ -0,0 +1,32 @@
require 'spec_helper'
describe 'App' do
before do
FactoryGirl.create(:test, :number => '3.1', :queue => 'builds.common')
add_endpoint '/foo' do
get '/' do
respond_with(Log.first)
end
get '/hash' do
respond_with foo: 'bar'
end
end
end
it 'gives priority to format given the url' do
response = get '/foo.txt', {}, 'HTTP_ACCEPT' => 'application/json'
response.content_type.should =~ /^text\/plain/
end
it 'responds with first available type' do
response = get '/foo', {}, 'HTTP_ACCEPT' => 'image/jpeg, application/json'
response.content_type.should =~ /^application\/json/
end
it 'responds with 406 if server can\'t use any mime type' do
response = get '/foo/hash', {}, 'HTTP_ACCEPT' => 'text/plain, image/jpeg'
response.status.should == 406
end
end

View File

@ -0,0 +1,30 @@
require 'spec_helper'
describe 'App' do
before do
FactoryGirl.create(:test, :number => '3.1', :queue => 'builds.common')
responder = Class.new(Travis::Api::App::Responders::Base) do
def apply?
true
end
def apply
resource[:extra] = 'moar!'
resource
end
end
add_endpoint '/foo' do
get '/hash' do
respond_with({ foo: 'bar' }, responders: [responder])
end
end
end
it 'runs responder when rendering the response with respond_with' do
response = get '/foo/hash', {}, 'HTTP_ACCEPT' => 'application/json'
JSON.parse(response.body).should == { 'foo' => 'bar', 'extra' => 'moar!' }
end
end

View File

@ -0,0 +1,49 @@
require 'spec_helper'
describe 'App' do
before do
FactoryGirl.create(:test, :number => '3.1', :queue => 'builds.common')
add_endpoint '/foo' do
get '/:id/bar', scope: [:foo, :bar] do
respond_with foo: 'bar'
end
get '/:job_id/log' do
respond_with job_id: params[:job_id]
end
end
end
it 'checks if token has one of the required scopes' do
token = Travis::Api::App::AccessToken.new(app_id: 1, user_id: 2, scopes: [:foo]).tap(&:save)
response = get '/foo/1/bar', {}, 'HTTP_ACCEPT' => 'application/json; version=2', 'HTTP_AUTHORIZATION' => "token #{token.token}"
response.should be_successful
response.headers['X-Accepted-OAuth-Scopes'].should == 'foo'
token = Travis::Api::App::AccessToken.new(app_id: 1, user_id: 2, scopes: [:bar]).tap(&:save)
response = get '/foo/1/bar', {}, 'HTTP_ACCEPT' => 'application/json; version=2', 'HTTP_AUTHORIZATION' => "token #{token.token}"
response.should be_successful
response.headers['X-Accepted-OAuth-Scopes'].should == 'bar'
token = Travis::Api::App::AccessToken.new(app_id: 1, user_id: 2, scopes: [:baz]).tap(&:save)
response = get '/foo/1/bar', {}, 'HTTP_ACCEPT' => 'application/json; version=2', 'HTTP_AUTHORIZATION' => "token #{token.token}"
response.status.should == 404
end
it 'checks if required_params match the from the request' do
extra = {
required_params: { job_id: '10' }
}
token = Travis::Api::App::AccessToken.new(app_id: 1, user_id: 2, extra: extra).tap(&:save)
response = get '/foo/10/log', {}, 'HTTP_ACCEPT' => 'application/json', 'HTTP_AUTHORIZATION' => "token #{token.token}"
response.should be_successful
response = get '/foo/11/log', {}, 'HTTP_ACCEPT' => 'application/json', 'HTTP_AUTHORIZATION' => "token #{token.token}"
response.status.should == 403
end
end

View File

@ -1,8 +1,6 @@
require 'spec_helper'
describe 'Branches' do
before { Scenario.default }
let(:repo) { Repository.by_slug('svenfuchs/minimal').first }
let(:headers) { { 'HTTP_ACCEPT' => 'application/vnd.travis-ci.1+json' } }

View File

@ -1,8 +1,6 @@
require 'spec_helper'
describe 'Builds' do
before { Scenario.default }
let(:repo) { Repository.by_slug('svenfuchs/minimal').first }
let(:build) { repo.builds.first }
let(:headers) { { 'HTTP_ACCEPT' => 'application/vnd.travis-ci.1+json' } }

View File

@ -2,7 +2,6 @@ require 'spec_helper'
describe 'Hooks' do
before(:each) do
Scenario.default
user.permissions.create repository: repo, admin: true
end

View File

@ -1,8 +1,6 @@
require 'spec_helper'
describe 'v1 repos' do
before(:each) { Scenario.default }
let(:repo) { Repository.by_slug('svenfuchs/minimal').first }
let(:headers) { { 'HTTP_ACCEPT' => 'application/vnd.travis-ci.1+json' } }
@ -42,10 +40,6 @@ describe 'v1 repos' do
end
describe 'GET /svenfuchs/minimal.png' do
it '"unknown" when the repository does not exist' do
get('/svenfuchs/does-not-exist.png').should deliver_result_image_for('unknown')
end
it '"unknown" when it only has one build that is not finished' do
Build.delete_all
Factory(:build, repository: repo, state: :created, result: nil)
@ -74,10 +68,6 @@ describe 'v1 repos' do
let(:on_foo) { Factory(:commit, branch: 'foo') }
let(:on_bar) { Factory(:commit, branch: 'bar') }
it '"unknown" when the repository does not exist' do
get('/svenfuchs/does-not-exist.png?branch=foo,bar').should deliver_result_image_for('unknown')
end
it '"unknown" when it only has unfinished builds on the relevant branches' do
Build.delete_all
Factory(:build, repository: repo, state: :started, commit: on_foo)

View File

@ -1,21 +1,12 @@
require 'spec_helper'
describe 'Workers' do
before(:each) do
Time.stubs(:now).returns(Time.utc(2011, 11, 11, 11, 11, 11))
@workers = [
Factory(:worker, :name => 'worker-1', :state => :working),
Factory(:worker, :name => 'worker-2', :state => :errored)
]
end
let(:headers) { { 'HTTP_ACCEPT' => 'application/vnd.travis-ci.1+json' } }
attr_reader :workers
let!(:workers) { [Worker.create(full_name: 'one'), Worker.create(full_name: 'two')] }
let(:headers) { { 'HTTP_ACCEPT' => 'application/vnd.travis-ci.1+json' } }
it 'GET /workers' do
response = get '/workers', {}, headers
response.should deliver_json_for(Worker.order(:host, :name), version: 'v1')
response.should deliver_json_for(Worker.all, version: 'v1', type: 'workers')
end
end

View File

@ -1,8 +1,6 @@
require 'spec_helper'
describe 'Branches' do
before { Scenario.default }
let(:repo) { Repository.by_slug('svenfuchs/minimal').first }
let(:headers) { { 'HTTP_ACCEPT' => 'application/vnd.travis-ci.2+json' } }

View File

@ -1,8 +1,6 @@
require 'spec_helper'
describe 'Builds' do
before { Scenario.default }
let(:repo) { Repository.by_slug('svenfuchs/minimal').first }
let(:build) { repo.builds.first }
let(:headers) { { 'HTTP_ACCEPT' => 'application/vnd.travis-ci.2+json' } }
@ -31,4 +29,9 @@ describe 'Builds' do
response = get "/repos/svenfuchs/minimal/builds/#{build.id}", {}, headers
response.should deliver_json_for(build, version: 'v2')
end
it 'GET /builds/1?repository_id=1&branches=true' do
response = get "/builds?repository_id=#{repo.id}&branches=true", {}, headers
response.should deliver_json_for(repo.last_finished_builds_by_branches, version: 'v2')
end
end

View File

@ -3,7 +3,6 @@ require 'travis/testing/payloads'
describe 'Hooks' do
before(:each) do
Scenario.default
user.permissions.create repository: repo, admin: true
end

View File

@ -13,8 +13,66 @@ describe 'Jobs' do
response.should deliver_json_for(Job.queued('builds.common'), version: 'v2')
end
it '/jobs/:job_id' do
it '/jobs/:id' do
response = get "/jobs/#{job.id}", {}, headers
response.should deliver_json_for(job, version: 'v2')
end
context 'GET /jobs/:job_id/log.txt' do
it 'returns log for a job' do
job.log.update_attributes!(content: 'the log')
response = get "/jobs/#{job.id}/log.txt", {}, headers
response.should deliver_as_txt('the log', version: 'v2')
end
context 'when log is archived' do
it 'redirects to archive' do
job.log.update_attributes!(content: 'the log', archived_at: Time.now, archive_verified: true)
response = get "/jobs/#{job.id}/log.txt", {}, headers
response.should redirect_to("https://s3.amazonaws.com/archive.travis-ci.org/jobs/#{job.id}/log.txt")
end
end
context 'when log is missing' do
it 'redirects to archive' do
job.log.destroy
response = get "/jobs/#{job.id}/log.txt", {}, headers
response.should redirect_to("https://s3.amazonaws.com/archive.travis-ci.org/jobs/#{job.id}/log.txt")
end
end
context 'with cors_hax param' do
it 'renders No Content response with location of the archived log' do
job.log.destroy
response = get "/jobs/#{job.id}/log.txt?cors_hax=true", {}, headers
response.status.should == 204
response.headers['Location'].should == "https://s3.amazonaws.com/archive.travis-ci.org/jobs/#{job.id}/log.txt"
end
end
context 'with chunked log requested' do
it 'responds with 406 when log is already aggregated' do
job.log.update_attributes(aggregated_at: Time.now)
headers = { 'HTTP_ACCEPT' => 'application/vnd.travis-ci.2+json; chunked=true' }
response = get "/jobs/#{job.id}/log", {}, headers
response.status.should == 406
end
it 'responds with chunks instead of full log' do
job.log.parts << Log::Part.new(content: 'foo', number: 1, final: false)
job.log.parts << Log::Part.new(content: 'bar', number: 2, final: true)
headers = { 'HTTP_ACCEPT' => 'application/vnd.travis-ci.2+json; chunked=true' }
response = get "/jobs/#{job.id}/log", {}, headers
response.should deliver_json_for(job.log, version: 'v2', params: { chunked: true})
end
it 'responds with full log if chunks are not available and full log is accepted' do
job.log.update_attributes(aggregated_at: Time.now)
headers = { 'HTTP_ACCEPT' => 'application/vnd.travis-ci.2+json; chunked=true, application/vnd.travis-ci.2+json' }
response = get "/jobs/#{job.id}/log", {}, headers
response.should deliver_json_for(job.log, version: 'v2')
end
end
end
end

View File

@ -1,8 +1,6 @@
require 'spec_helper'
describe 'Repos' do
before(:each) { Scenario.default }
let(:repo) { Repository.by_slug('svenfuchs/minimal').first }
let(:headers) { { 'HTTP_ACCEPT' => 'application/vnd.travis-ci.2+json' } }
@ -76,6 +74,7 @@ describe 'Repos' do
it 'GET /repos/1/cc.xml' do
response = get "repos/#{repo.id}/cc.xml"
response.should deliver_cc_xml_for(Repository.by_slug('svenfuchs/minimal').first)
response.content_type.should eq('application/xml;charset=utf-8')
end
it 'GET /repos/svenfuchs/minimal' do
@ -88,15 +87,26 @@ describe 'Repos' do
response.should deliver_cc_xml_for(Repository.by_slug('svenfuchs/minimal').first)
end
it 'does not respond with cc.xml for /repos list' do
response = get '/repos', {}, 'HTTP_ACCEPT' => 'application/xml; version=2'
response.status.should == 406
end
it 'responds with 404 when repo can\'t be found and format is png' do
result = get('/repos/foo/bar.png', {}, 'HTTP_ACCEPT' => 'image/png; version=2')
result.status.should == 404
end
it 'responds with 404 when repo can\'t be found and format is other than png' do
result = get('/repos/foo/bar', {}, 'HTTP_ACCEPT' => 'application/json; version=2')
result.status.should == 404
JSON.parse(result.body).should == { 'file' => 'not found' }
end
describe 'GET /repos/svenfuchs/minimal.png?branch=foo,bar' do
let(:on_foo) { Factory(:commit, branch: 'foo') }
let(:on_bar) { Factory(:commit, branch: 'bar') }
it '"unknown" when the repository does not exist' do
result = get('/repos/svenfuchs/does-not-exist.png?branch=foo,bar', {}, headers)
result.should deliver_result_image_for('unknown')
end
it '"unknown" when it only has unfinished builds on the relevant branches' do
Build.delete_all
Factory(:build, repository: repo, state: :started, commit: on_foo)
@ -117,6 +127,7 @@ describe 'Repos' do
Factory(:build, repository: repo, state: :passed, commit: on_bar)
result = get('/repos/svenfuchs/minimal.png?branch=foo,bar', {}, headers)
result.should deliver_result_image_for('passing')
result.headers['Last-Modified'].should == repo.last_build_finished_at.httpdate
end
it '"passing" when there is a running build but the previous one has passed' do

View File

@ -1,7 +1,7 @@
require 'spec_helper'
describe 'Users' do
let(:user) { Factory(:user, locale: 'en') }
let(:user) { User.first }
let(:token) { Travis::Api::App::AccessToken.create(user: user, app_id: -1) }
let(:headers) { { 'HTTP_ACCEPT' => 'application/vnd.travis-ci.2+json', 'HTTP_AUTHORIZATION' => "token #{token}" } }
@ -17,6 +17,7 @@ describe 'Users' do
context 'POST /users/sync' do
it 'syncs current_user repos' do
user.update_attribute :is_syncing, false
response = post "/users/sync", {}, headers
response.should be_successful
end

View File

@ -1,21 +1,12 @@
require 'spec_helper'
describe 'Workers' do
before(:each) do
Time.stubs(:now).returns(Time.utc(2011, 11, 11, 11, 11, 11))
@workers = [
Factory(:worker, :name => 'worker-1', :state => :working),
Factory(:worker, :name => 'worker-2', :state => :errored)
]
end
let(:headers) { { 'HTTP_ACCEPT' => 'application/vnd.travis-ci.2+json' } }
attr_reader :workers
let!(:workers) { [Worker.create(full_name: 'one'), Worker.create(full_name: 'two')] }
let(:headers) { { 'HTTP_ACCEPT' => 'application/vnd.travis-ci.2+json' } }
it 'GET /workers' do
response = get '/workers', {}, headers
response.should deliver_json_for(Worker.order(:host, :name), version: 'v2')
response.should deliver_json_for(Worker.all, version: 'v2', type: 'workers')
end
end

View File

@ -0,0 +1,26 @@
require 'spec_helper'
describe 'App' do
before do
add_endpoint '/foo' do
get '/' do
respond_with foo: 'bar'
end
end
end
it 'uses version from current accept header' do
Travis::Api.expects(:builder).with { |r, options| options[:version] == 'v1' }
Travis::Api::App::Responders::Json.any_instance.stubs(:apply?).
returns(false).then.returns(true)
response = get '/foo', {}, 'HTTP_ACCEPT' => 'application/json; version=2, application/json; version=1'
response.content_type.should == 'application/json;charset=utf-8'
end
it 'uses v1 by default' do
Travis::Api.expects(:builder).with { |r, options| options[:version] == 'v1' }
get '/foo', {}, 'HTTP_ACCEPT' => 'application/json'
end
end

View File

@ -14,6 +14,7 @@ require 'support/matchers'
Travis.logger = Logger.new(StringIO.new)
Travis::Api::App.setup
Travis.config.client_domain = "www.example.com"
module TestHelpers
include Sinatra::TestHelpers
@ -41,7 +42,8 @@ RSpec.configure do |c|
c.before :suite do
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with :transaction
DatabaseCleaner.clean_with :truncation
Scenario.default
end
c.before :each do

View File

@ -24,6 +24,26 @@ RSpec::Matchers.define :deliver_json_for do |resource, options = {}|
end
end
RSpec::Matchers.define :deliver_as_txt do |expected, options = {}|
match do |response|
if response.status == 200
failure_message_for_should do
"expected\n\n#{actual}\n\nto equal\n\n#{expected}"
end
response.body.to_s == expected
else
failure_message_for_should do
"expected the request to be successful (200) but was #{response.status}"
end
false
end
end
def parse(body)
MultiJson.decode(body)
end
end
RSpec::Matchers.define :deliver_result_image_for do |name|
match do |response|
header = response.headers['content-disposition']
@ -42,7 +62,7 @@ RSpec::Matchers.define :deliver_cc_xml_for do |repo|
"expected #{body} to be a valid cc.xml"
end
body.include?('<Projects>') && body.include?(%(name="#{repo.slug}"))
body.include?('<Projects>') && body.include?(%(name="#{repo.slug}")) && body.include?("https://www.example.com/#{repo.slug}")
end
end

View File

@ -0,0 +1,49 @@
require 'spec_helper'
describe Travis::Api::App::AccessToken do
it 'errors out on wrong type of :expires_in argument' do
expect {
described_class.new(app_id: 1, user_id: 2, expires_in: 'foo')
}.to raise_error(ArgumentError, 'expires_in must be of integer type')
end
it 'allows to skip expires_in' do
expect {
described_class.new(app_id: 1, user_id: 2, expires_in: nil)
}.to_not raise_error(ArgumentError)
end
it 'does not reuse token if expires_in is set' do
token = described_class.new(app_id: 1, user_id: 2).tap(&:save)
new_token = described_class.new(app_id: 1, user_id: 2, expires_in: 10)
token.token.should_not == new_token.token
end
it 'expires the token after given period of time' do
token = described_class.new(app_id: 1, user_id: 2, expires_in: 1).tap(&:save)
described_class.find_by_token(token.token).should_not be_nil
sleep 2
described_class.find_by_token(token.token).should be_nil
end
it 'allows to save extra information' do
attrs = {
app_id: 1,
user_id: 3,
expires_in: 1,
extra: {
required_params: { job_id: '1' }
}
}
token = described_class.new(attrs).tap(&:save)
token.extra.should == attrs[:extra]
token = described_class.find_by_token(token.token)
token.extra.should == { 'required_params' => { 'job_id' => '1' } }
end
end

View File

@ -18,7 +18,7 @@ describe Travis::Api::App::Endpoint::Accounts do
'login' => user.login,
'name' => user.name,
'type' => 'user',
'repos_count' => nil
'repos_count' => 1
}]
end
end

View File

@ -26,10 +26,10 @@ describe Travis::Api::App::Endpoint::Authorization do
describe 'POST /auth/github' do
before do
data = { 'id' => user.github_id, 'name' => user.name, 'login' => user.login, 'gravatar_id' => user.gravatar_id }
GH.stubs(:with).with(token: 'private repos').returns stub(:[] => user.login, :headers => {'x-oauth-scopes' => 'repo'}, :to_hash => data)
GH.stubs(:with).with(token: 'public repos').returns stub(:[] => user.login, :headers => {'x-oauth-scopes' => 'public_repo'}, :to_hash => data)
GH.stubs(:with).with(token: 'no repos').returns stub(:[] => user.login, :headers => {'x-oauth-scopes' => 'user'}, :to_hash => data)
GH.stubs(:with).with(token: 'invalid token').raises(Faraday::Error::ClientError, 'CLIENT ERROR!')
GH.stubs(:with).with(token: 'private repos', client_id: nil).returns stub(:[] => user.login, :headers => {'x-oauth-scopes' => 'repo'}, :to_hash => data)
GH.stubs(:with).with(token: 'public repos', client_id: nil).returns stub(:[] => user.login, :headers => {'x-oauth-scopes' => 'public_repo'}, :to_hash => data)
GH.stubs(:with).with(token: 'no repos', client_id: nil).returns stub(:[] => user.login, :headers => {'x-oauth-scopes' => 'user'}, :to_hash => data)
GH.stubs(:with).with(token: 'invalid token', client_id: nil).raises(Faraday::Error::ClientError, 'CLIENT ERROR!')
end
def get_token(github_token)

View File

@ -0,0 +1,71 @@
require 'spec_helper'
module Travis::Api::App::Helpers
describe Accept do
class FakeApp < Struct.new(:env)
include Accept
end
it 'returns accept entries sorted properly' do
accept = "text/html; q=0.2; level=1, application/vnd.travis-ci.2+json, text/*, text/html;level=2; q=0.5"
FakeApp.new('HTTP_ACCEPT' => accept).accept_entries.map(&:to_s).should ==
["application/json; q=1", "text/*; q=1", "text/html; q=0.5; level=2", "text/html; q=0.2; level=1"]
end
it 'properly parses params, quality and version' do
accept = "application/vnd.travis-ci.2+json; q=0.2; level=1; foo=bar"
accept_entry = FakeApp.new('HTTP_ACCEPT' => accept).accept_entries.first
accept_entry.quality.should == 0.2
accept_entry.params.should == { 'level' => '1', 'foo' => 'bar' }
accept_entry.mime_type.should == 'application/json'
accept_entry.version.should == 'v2'
end
it 'returns */* for empty accept header' do
accept_entry = FakeApp.new({}).accept_entries.first
accept_entry.mime_type.should == '*/*'
end
describe Accept::Entry do
describe 'version' do
it 'can be passed as a vendor extension' do
entry = Accept::Entry.new('application/vnd.travis-ci.2+json')
entry.version.should == 'v2'
end
it 'can be passed as a param' do
entry = Accept::Entry.new('application/json; version=2')
entry.version.should == 'v2'
end
it 'has a higher priority when in vendor extension' do
entry = Accept::Entry.new('application/vnd.travis-ci.1+json; version=2')
entry.version.should == 'v1'
end
end
describe 'accepts?' do
it 'accepts everything with */* type' do
entry = Accept::Entry.new('*/*')
entry.accepts?('application/json').should be_true
entry.accepts?('foo/bar').should be_true
end
it 'accepts every subtype with application/* type' do
entry = Accept::Entry.new('application/*')
entry.accepts?('application/foo').should be_true
entry.accepts?('application/bar').should be_true
entry.accepts?('text/plain').should be_false
end
it 'accepts when type and subtype match' do
entry = Accept::Entry.new('application/json')
entry.accepts?('application/json').should be_true
entry.accepts?('application/xml').should be_false
end
end
end
end
end

View File

@ -0,0 +1,52 @@
require 'spec_helper'
module Travis::Api::App::Responders
describe Json do
class MyJson < Json
end
let(:request) { stub 'request', params: {} }
let(:endpoint) { stub 'endpoint', request: request, content_type: nil }
let(:resource) { stub 'resource' }
let(:accept) { stub 'accept entry', version: '2', params: {} }
let(:options) { { :accept => accept} }
let(:json) { MyJson.new(endpoint, resource, options) }
context 'with resource not associated with Api data class' do
it 'returns nil result' do
json.apply.should be_false
end
end
context 'with resource being' do
context 'a Hash instance' do
let(:resource) { { foo: 'bar' } }
it 'returns resource converted to_json' do
json.apply.should == { foo: 'bar' }
end
end
context 'nil' do
let(:resource) { nil }
it 'responds with 404' do
json.apply?.should be_false
json.apply.should be_false
end
end
end
context 'with resource associated with Api data class' do
let(:builder) { stub 'builder', data: { foo: 'bar' } }
let(:builder_class) { stub 'builder class', new: builder }
before do
json.stubs :builder => builder_class
end
it 'returns proper data converted to json' do
json.apply.should == { foo: 'bar' }
end
end
end
end

View File

@ -12,9 +12,14 @@ Gem::Specification.new do |s|
"Sven Fuchs",
"Konstantin Haase",
"Piotr Sarnacki",
"Henrik Hodne",
"Mathias Meyer",
"Josh Kalderimis",
"Andre Arko",
"Erik Michaels-Ober",
"Steve Richert",
"Brian Ford",
"Henrik Hodne"
"Nick Schonning"
]
s.email = [
@ -22,12 +27,18 @@ Gem::Specification.new do |s|
"konstantin.mailinglists@googlemail.com",
"drogus@gmail.com",
"meyer@paperplanes.de",
"me@henrikhodne.com",
"svenfuchs@artweb-design.de",
"josh.kalderimis@gmail.com",
"andre@arko.net",
"sferik@gmail.com",
"steve.richert@gmail.com",
"bford@engineyard.com",
"me@henrikhodne.com"
"nschonni@gmail.com"
]
s.files = [
"CONTRIBUTING.md",
"Procfile",
"README.md",
"Rakefile",
@ -37,6 +48,7 @@ Gem::Specification.new do |s|
"config/unicorn.rb",
"docs/00_overview.md",
"docs/01_cross_origin.md",
"lib/tasks/build_update_pull_request_data.rake",
"lib/travis/api/app.rb",
"lib/travis/api/app/access_token.rb",
"lib/travis/api/app/base.rb",
@ -54,6 +66,7 @@ Gem::Specification.new do |s|
"lib/travis/api/app/endpoint/documentation/css/bootstrap.css",
"lib/travis/api/app/endpoint/documentation/css/bootstrap.min.css",
"lib/travis/api/app/endpoint/documentation/css/prettify.css",
"lib/travis/api/app/endpoint/documentation/css/style.css",
"lib/travis/api/app/endpoint/documentation/img/glyphicons-halflings-white.png",
"lib/travis/api/app/endpoint/documentation/img/glyphicons-halflings.png",
"lib/travis/api/app/endpoint/documentation/img/grid-18px-masked.png",
@ -79,11 +92,13 @@ Gem::Specification.new do |s|
"lib/travis/api/app/endpoint/documentation/js/lang-xq.js",
"lib/travis/api/app/endpoint/documentation/js/lang-yaml.js",
"lib/travis/api/app/endpoint/documentation/js/prettify.js",
"lib/travis/api/app/endpoint/documentation/resources.rb",
"lib/travis/api/app/endpoint/endpoints.rb",
"lib/travis/api/app/endpoint/events.rb",
"lib/travis/api/app/endpoint/home.rb",
"lib/travis/api/app/endpoint/hooks.rb",
"lib/travis/api/app/endpoint/jobs.rb",
"lib/travis/api/app/endpoint/logs.rb",
"lib/travis/api/app/endpoint/repos.rb",
"lib/travis/api/app/endpoint/requests.rb",
"lib/travis/api/app/endpoint/stats.rb",
@ -107,13 +122,18 @@ Gem::Specification.new do |s|
"lib/travis/api/app/responders/base.rb",
"lib/travis/api/app/responders/image.rb",
"lib/travis/api/app/responders/json.rb",
"lib/travis/api/app/responders/plain.rb",
"lib/travis/api/app/responders/service.rb",
"lib/travis/api/app/responders/xml.rb",
"public/favicon.ico",
"public/images/result/error.png",
"public/images/result/failing.png",
"public/images/result/passing.png",
"public/images/result/pending.png",
"public/images/result/unknown.png",
"script/console",
"script/server",
"spec/integration/formats_handling_spec.rb",
"spec/integration/routes.backup.rb",
"spec/integration/v1/branches_spec.rb",
"spec/integration/v1/builds_spec.rb",
@ -130,6 +150,7 @@ Gem::Specification.new do |s|
"spec/integration/v2/users_spec.rb",
"spec/integration/v2/workers_spec.rb",
"spec/integration/v2_spec.backup.rb",
"spec/integration/version_spec.rb",
"spec/spec_helper.rb",
"spec/support/matchers.rb",
"spec/unit/app_spec.rb",
@ -152,10 +173,12 @@ Gem::Specification.new do |s|
"spec/unit/extensions/scoping_spec.rb",
"spec/unit/extensions/smart_constants_spec.rb",
"spec/unit/extensions/subclass_tracker_spec.rb",
"spec/unit/helpers/accept_spec.rb",
"spec/unit/helpers/json_renderer_spec.rb",
"spec/unit/middleware/logging_spec.rb",
"spec/unit/middleware/scope_check_spec.rb",
"spec/unit/middleware_spec.rb",
"spec/unit/responders/json_spec.rb",
"spec/unit/responders/service_spec.rb",
"travis-api.gemspec"
]
@ -166,12 +189,12 @@ Gem::Specification.new do |s|
s.add_dependency 'hubble', '~> 0.1'
s.add_dependency 'backports', '~> 2.5'
s.add_dependency 'pg', '~> 0.13.2'
s.add_dependency 'newrelic_rpm', '~> 3.5.0'
s.add_dependency 'newrelic_rpm', '~> 3.6.1.88'
s.add_dependency 'thin', '~> 1.4'
s.add_dependency 'sinatra', '~> 1.3'
s.add_dependency 'sinatra-contrib', '~> 1.3'
s.add_dependency 'redcarpet', '~> 2.1'
s.add_dependency 'rack-ssl', '~> 1.3'
s.add_dependency 'rack-ssl', '~> 1.3', '>= 1.3.3'
s.add_dependency 'rack-contrib', '~> 1.1'
end