travis 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +22 -0
- data/README.md +154 -0
- data/Rakefile +38 -0
- data/bin/travis +4 -0
- data/lib/travis.rb +8 -0
- data/lib/travis/cacert.pem +3895 -0
- data/lib/travis/cli.rb +83 -0
- data/lib/travis/cli/api_command.rb +65 -0
- data/lib/travis/cli/command.rb +212 -0
- data/lib/travis/cli/encrypt.rb +57 -0
- data/lib/travis/cli/endpoint.rb +13 -0
- data/lib/travis/cli/help.rb +21 -0
- data/lib/travis/cli/login.rb +57 -0
- data/lib/travis/cli/parser.rb +43 -0
- data/lib/travis/cli/raw.rb +16 -0
- data/lib/travis/cli/repo_command.rb +54 -0
- data/lib/travis/cli/version.rb +12 -0
- data/lib/travis/cli/whoami.rb +12 -0
- data/lib/travis/client.rb +20 -0
- data/lib/travis/client/entity.rb +139 -0
- data/lib/travis/client/error.rb +11 -0
- data/lib/travis/client/methods.rb +45 -0
- data/lib/travis/client/namespace.rb +78 -0
- data/lib/travis/client/repository.rb +66 -0
- data/lib/travis/client/session.rb +191 -0
- data/lib/travis/client/user.rb +20 -0
- data/lib/travis/pro.rb +5 -0
- data/lib/travis/tools/token_finder.rb +51 -0
- data/lib/travis/version.rb +3 -0
- data/spec/cli/encrypt_spec.rb +18 -0
- data/spec/cli/endpoint_spec.rb +23 -0
- data/spec/cli/help_spec.rb +33 -0
- data/spec/cli/login_spec.rb +13 -0
- data/spec/cli/version_spec.rb +18 -0
- data/spec/cli/whoami_spec.rb +27 -0
- data/spec/client/methods_spec.rb +15 -0
- data/spec/client/namespace_spec.rb +19 -0
- data/spec/client/repository_spec.rb +15 -0
- data/spec/client/session_spec.rb +145 -0
- data/spec/client/user_spec.rb +16 -0
- data/spec/client_spec.rb +5 -0
- data/spec/pro_spec.rb +10 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/support/fake_api.rb +89 -0
- data/spec/support/fake_github.rb +20 -0
- data/spec/support/helpers.rb +43 -0
- data/spec/travis_spec.rb +9 -0
- data/travis.gemspec +84 -0
- metadata +240 -0
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'travis/cli'
|
2
|
+
require 'travis/tools/token_finder'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Travis
|
6
|
+
module CLI
|
7
|
+
class Login < ApiCommand
|
8
|
+
skip :authenticate
|
9
|
+
attr_accessor :github_login, :github_password, :github_token, :callback
|
10
|
+
|
11
|
+
on('--github-token TOKEN', 'identify by GitHub token')
|
12
|
+
|
13
|
+
on('--auto', 'try to figure out who you are automatically (might send another apps token to Travis, token will not be stored)') do |c|
|
14
|
+
c.github_token ||= Travis::Tools::TokenFinder.find(:explode => c.explode?)
|
15
|
+
end
|
16
|
+
|
17
|
+
def run
|
18
|
+
generate_github_token unless github_token
|
19
|
+
endpoint_config['access_token'] = github_auth(github_token)
|
20
|
+
success("Successfully logged in!")
|
21
|
+
ensure
|
22
|
+
callback.call if callback
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def generate_github_token
|
28
|
+
ask_info
|
29
|
+
|
30
|
+
gh = GH.with(:username => github_login, :password => github_password)
|
31
|
+
reply = gh.post('/authorizations', :scopes => github_scopes, :note => "temporary token to identify on #{api_endpoint}")
|
32
|
+
|
33
|
+
self.github_token = reply['token']
|
34
|
+
self.callback = proc { gh.delete reply['_links']['self']['href'] }
|
35
|
+
rescue GH::Error => e
|
36
|
+
raise e if explode?
|
37
|
+
error JSON.parse(e.info[:response_body])["message"]
|
38
|
+
end
|
39
|
+
|
40
|
+
def github_scopes
|
41
|
+
['user:email', org? ? 'public_repo' : 'repo']
|
42
|
+
end
|
43
|
+
|
44
|
+
def ask_info
|
45
|
+
say "We need your #{color("GitHub login", :important)} to identify you."
|
46
|
+
say "This information will #{color("not be sent to Travis CI", :important)}, only to GitHub."
|
47
|
+
say "The password will not be displayed."
|
48
|
+
empty_line
|
49
|
+
say "Try running with #{color("--github-token", :info)} or #{color("--auto", :info)} if you don't want to enter your password anyways."
|
50
|
+
empty_line
|
51
|
+
self.github_login = ask("Username: ")
|
52
|
+
self.github_password = ask("Password: ") { |q| q.echo = "*" }
|
53
|
+
empty_line
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'travis/cli'
|
2
|
+
require 'optparse'
|
3
|
+
|
4
|
+
module Travis
|
5
|
+
module CLI
|
6
|
+
module Parser
|
7
|
+
def on_initialize(&block)
|
8
|
+
@on_initialize ||= []
|
9
|
+
@on_initialize << block if block
|
10
|
+
if superclass.respond_to? :on_initialize
|
11
|
+
superclass.on_initialize + @on_initialize
|
12
|
+
else
|
13
|
+
@on_initialize
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def on(*args, &block)
|
18
|
+
block ||= begin
|
19
|
+
full_arg = args.detect { |a| a.start_with? '--' }
|
20
|
+
name = full_arg.gsub(/^--(\[no-\])?(\S+).*$/, '\2').gsub('-', '_')
|
21
|
+
attr_reader(name) unless method_defined? name
|
22
|
+
attr_writer(name) unless method_defined? "#{name}="
|
23
|
+
alias_method("#{name}?", name) if full_arg.start_with? '--[no-]' and not method_defined? "#{name}?"
|
24
|
+
proc { |instance, value| instance.public_send("#{name}=", value) }
|
25
|
+
end
|
26
|
+
|
27
|
+
on_initialize do |instance|
|
28
|
+
instance.parser.on(*args) do |value|
|
29
|
+
block.call(instance, value)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def new(*)
|
35
|
+
attr_accessor :parser unless method_defined? :parser
|
36
|
+
result = super
|
37
|
+
result.parser = OptionParser.new
|
38
|
+
on_initialize.each { |b| b[result] }
|
39
|
+
result
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'travis/cli'
|
2
|
+
require 'pp'
|
3
|
+
|
4
|
+
module Travis
|
5
|
+
module CLI
|
6
|
+
class Raw < ApiCommand
|
7
|
+
skip :authenticate
|
8
|
+
on('--[no-]json', 'display as json')
|
9
|
+
|
10
|
+
def run(resource)
|
11
|
+
reply = session.get_raw(resource)
|
12
|
+
json? ? say(reply.to_json) : pp(reply)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'travis/cli'
|
2
|
+
|
3
|
+
module Travis
|
4
|
+
module CLI
|
5
|
+
class RepoCommand < ApiCommand
|
6
|
+
GIT_REGEX = %r{Fetch URL: (?:https://|git://|git@)github\.com[:/](.*/.+?)(\.git)?$}
|
7
|
+
on('-r', '--repo SLUG') { |c| c.slug = slug }
|
8
|
+
|
9
|
+
attr_accessor :slug
|
10
|
+
abstract
|
11
|
+
|
12
|
+
def setup
|
13
|
+
error "Can't figure out GitHub repo name. Are you in the right directory?" unless self.slug ||= find_slug
|
14
|
+
self.api_endpoint = detect_api_endpoint
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
def repository
|
19
|
+
repo(slug)
|
20
|
+
rescue Travis::Client::NotFound
|
21
|
+
error "repository not known to travis: #{color(slug, :important)}"
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def detected_endpoint?
|
27
|
+
!explicit_api_endpoint?
|
28
|
+
end
|
29
|
+
|
30
|
+
def find_slug
|
31
|
+
git_info = `git remote show origin 2>&1`
|
32
|
+
$1 if git_info =~ GIT_REGEX
|
33
|
+
end
|
34
|
+
|
35
|
+
def repo_config
|
36
|
+
config['repos'] ||= {}
|
37
|
+
config['repos'][slug] ||= {}
|
38
|
+
end
|
39
|
+
|
40
|
+
def detect_api_endpoint
|
41
|
+
if explicit_api_endpoint?
|
42
|
+
repo_config['endpoint'] = api_endpoint
|
43
|
+
else
|
44
|
+
repo_config['endpoint'] ||= begin
|
45
|
+
GH.head("/repos/#{slug}")
|
46
|
+
Travis::Client::ORG_URI
|
47
|
+
rescue GH::Error
|
48
|
+
Travis::Client::PRO_URI
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'backports/1.9.3' if RUBY_VERSION < '1.9.3'
|
2
|
+
require 'travis/client/error'
|
3
|
+
require 'travis/client/methods'
|
4
|
+
require 'travis/client/session'
|
5
|
+
require 'travis/client/entity'
|
6
|
+
require 'travis/client/user'
|
7
|
+
require 'travis/client/repository'
|
8
|
+
require 'travis/client/namespace'
|
9
|
+
|
10
|
+
module Travis
|
11
|
+
module Client
|
12
|
+
ORG_URI = 'https://api.travis-ci.org/'
|
13
|
+
PRO_URI = 'https://api.travis-ci.com/'
|
14
|
+
|
15
|
+
def self.new(options = {})
|
16
|
+
options['uri'] ||= ORG_URI if options.is_a? Hash
|
17
|
+
Session.new(options)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'travis/client'
|
2
|
+
require 'time'
|
3
|
+
|
4
|
+
module Travis
|
5
|
+
module Client
|
6
|
+
class Entity
|
7
|
+
attr_reader :attributes, :id, :session
|
8
|
+
attr_accessor :curry
|
9
|
+
|
10
|
+
MAP = {}
|
11
|
+
|
12
|
+
def self.subclasses
|
13
|
+
MAP.values.uniq
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.subclass_for(key)
|
17
|
+
MAP.fetch(key)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.one(key = nil)
|
21
|
+
MAP[key.to_s] = self if key
|
22
|
+
@one ||= key.to_s
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.many(key = nil)
|
26
|
+
MAP[key.to_s] = self if key
|
27
|
+
@many ||= key.to_s
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.attributes(*list)
|
31
|
+
@attributes ||= []
|
32
|
+
list.each do |name|
|
33
|
+
name = name.to_s
|
34
|
+
@attributes << name
|
35
|
+
define_method(name) { load_attribute(name) }
|
36
|
+
define_method("#{name}=") { |value| set_attribute(name, value) }
|
37
|
+
define_method("#{name}?") { !!send(name) }
|
38
|
+
end
|
39
|
+
@attributes
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.inspect_info(name)
|
43
|
+
alias_method(:inspect_info, name)
|
44
|
+
private(:inspect_info)
|
45
|
+
end
|
46
|
+
|
47
|
+
def initialize(session, id)
|
48
|
+
@attributes = {}
|
49
|
+
@session = session
|
50
|
+
@id = Integer(id)
|
51
|
+
end
|
52
|
+
|
53
|
+
def update_attributes(data)
|
54
|
+
data.each_pair do |key, value|
|
55
|
+
self[key] = value
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def attribute_names
|
60
|
+
self.class.attributes
|
61
|
+
end
|
62
|
+
|
63
|
+
def [](key)
|
64
|
+
send(key) if include? key
|
65
|
+
end
|
66
|
+
|
67
|
+
def []=(key, value)
|
68
|
+
send("#{key}=", value) if include? key
|
69
|
+
end
|
70
|
+
|
71
|
+
def include?(key)
|
72
|
+
attributes.include? key or attribute_names.include? key.to_s
|
73
|
+
end
|
74
|
+
|
75
|
+
def reload
|
76
|
+
session.reload(self)
|
77
|
+
end
|
78
|
+
|
79
|
+
def load
|
80
|
+
reload unless complete?
|
81
|
+
end
|
82
|
+
|
83
|
+
def missing?(key)
|
84
|
+
return false unless include? key
|
85
|
+
!attributes.include?(key.to_s)
|
86
|
+
end
|
87
|
+
|
88
|
+
def complete?
|
89
|
+
attribute_names.all? { |key| attributes.include? key }
|
90
|
+
end
|
91
|
+
|
92
|
+
def inspect
|
93
|
+
klass = self.class
|
94
|
+
klass = curry if curry and curry.name and curry.to_s.size < klass.to_s.size
|
95
|
+
"#<#{klass}: #{inspect_info}>"
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def inspect_info
|
101
|
+
id
|
102
|
+
end
|
103
|
+
|
104
|
+
def set_attribute(name, value)
|
105
|
+
attributes[name.to_s] = value
|
106
|
+
end
|
107
|
+
|
108
|
+
def load_attribute(name)
|
109
|
+
reload if missing? name
|
110
|
+
attributes[name.to_s]
|
111
|
+
end
|
112
|
+
|
113
|
+
# shamelessly stolen from sinatra
|
114
|
+
def time(value)
|
115
|
+
if value.respond_to? :to_time
|
116
|
+
value.to_time
|
117
|
+
elsif value.is_a? Time
|
118
|
+
value
|
119
|
+
elsif value.respond_to? :new_offset
|
120
|
+
d = value.new_offset 0
|
121
|
+
t = Time.utc d.year, d.mon, d.mday, d.hour, d.min, d.sec + d.sec_fraction
|
122
|
+
t.getlocal
|
123
|
+
elsif value.respond_to? :mday
|
124
|
+
Time.local(value.year, value.mon, value.mday)
|
125
|
+
elsif value.is_a? Numeric
|
126
|
+
Time.at value
|
127
|
+
elsif value.nil? or value.empty?
|
128
|
+
nil
|
129
|
+
else
|
130
|
+
Time.parse value.to_s
|
131
|
+
end
|
132
|
+
rescue ArgumentError => boom
|
133
|
+
raise boom
|
134
|
+
rescue Exception
|
135
|
+
raise ArgumentError, "unable to convert #{value.inspect} to a Time object"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'travis/client'
|
2
|
+
|
3
|
+
module Travis
|
4
|
+
module Client
|
5
|
+
module Methods
|
6
|
+
def access_token
|
7
|
+
session.access_token
|
8
|
+
end
|
9
|
+
|
10
|
+
def access_token=(token)
|
11
|
+
session.access_token = token
|
12
|
+
end
|
13
|
+
|
14
|
+
def api_endpoint
|
15
|
+
session.uri
|
16
|
+
end
|
17
|
+
|
18
|
+
def github_auth(github_token)
|
19
|
+
reply = session.post_raw("/auth/github?github_token=#{github_token}")
|
20
|
+
session.access_token = reply["access_token"]
|
21
|
+
end
|
22
|
+
|
23
|
+
def explicit_api_endpoint?
|
24
|
+
@explicit_api_endpoint ||= false
|
25
|
+
end
|
26
|
+
|
27
|
+
def api_endpoint=(uri)
|
28
|
+
@explicit_api_endpoint = true
|
29
|
+
session.uri = uri
|
30
|
+
end
|
31
|
+
|
32
|
+
def repos(params = {})
|
33
|
+
session.find_many(Repository, params)
|
34
|
+
end
|
35
|
+
|
36
|
+
def repo(id_or_slug)
|
37
|
+
session.find_one(Repository, id_or_slug)
|
38
|
+
end
|
39
|
+
|
40
|
+
def user
|
41
|
+
session.find_one(User)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'travis/client'
|
2
|
+
|
3
|
+
module Travis
|
4
|
+
module Client
|
5
|
+
class Namespace < Module
|
6
|
+
class Curry < Module
|
7
|
+
attr_accessor :namespace, :type
|
8
|
+
|
9
|
+
def initialize(namespace, type)
|
10
|
+
@namespace, @type = namespace, type
|
11
|
+
end
|
12
|
+
|
13
|
+
def find_one(id = nil)
|
14
|
+
result = session.find_one(type, id)
|
15
|
+
result.curry = self
|
16
|
+
result
|
17
|
+
end
|
18
|
+
|
19
|
+
def current
|
20
|
+
result = session.find_one_or_many(type)
|
21
|
+
Array(result).each { |e| e.curry = self }
|
22
|
+
result
|
23
|
+
end
|
24
|
+
|
25
|
+
def find_many(params = {})
|
26
|
+
session.find_many(type, params).each do |entity|
|
27
|
+
entity.curry = self
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
alias find find_one
|
32
|
+
alias find_all find_many
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def session
|
37
|
+
namespace.session
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
include Methods
|
43
|
+
attr_accessor :session
|
44
|
+
|
45
|
+
def initialize(session = nil)
|
46
|
+
session = Travis::Client.new(session || {}) unless session.is_a? Session
|
47
|
+
@session = session
|
48
|
+
|
49
|
+
Entity.subclasses.each do |subclass|
|
50
|
+
name = subclass.name[/[^:]+$/]
|
51
|
+
const_set(name, Curry.new(self, subclass))
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def included(klass)
|
56
|
+
fix_names(klass)
|
57
|
+
delegate_session(klass)
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def fix_names(klass)
|
63
|
+
constants.each do |name|
|
64
|
+
const = klass.const_get(name)
|
65
|
+
klass.const_set(name, const) if const == const_get(name)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def delegate_session(klass)
|
70
|
+
return if klass == Object or klass == Kernel
|
71
|
+
klass.extend(Methods)
|
72
|
+
namespace = self
|
73
|
+
klass.define_singleton_method(:session) { namespace.session }
|
74
|
+
klass.define_singleton_method(:session=) { |value| namespace.session = value }
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|