travis 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|