travis 1.4.0 → 1.5.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.
- checksums.yaml +4 -4
- data/README.md +85 -4
- data/Rakefile +76 -2
- data/bin/travis +7 -0
- data/completion/extconf.rb +17 -0
- data/completion/travis.sh +1320 -0
- data/lib/travis/cli.rb +2 -0
- data/lib/travis/cli/accounts.rb +21 -0
- data/lib/travis/cli/api_command.rb +7 -0
- data/lib/travis/cli/command.rb +15 -0
- data/lib/travis/cli/encrypt.rb +1 -2
- data/lib/travis/cli/logs.rb +3 -7
- data/lib/travis/cli/monitor.rb +48 -0
- data/lib/travis/cli/restart.rb +1 -0
- data/lib/travis/cli/setup.rb +16 -2
- data/lib/travis/cli/token.rb +1 -1
- data/lib/travis/cli/whatsup.rb +1 -1
- data/lib/travis/client.rb +2 -0
- data/lib/travis/client/account.rb +5 -1
- data/lib/travis/client/artifact.rb +40 -4
- data/lib/travis/client/broadcast.rb +14 -0
- data/lib/travis/client/build.rb +4 -0
- data/lib/travis/client/entity.rb +2 -2
- data/lib/travis/client/job.rb +4 -0
- data/lib/travis/client/listener.rb +149 -0
- data/lib/travis/client/methods.rb +10 -0
- data/lib/travis/client/repository.rb +9 -1
- data/lib/travis/client/session.rb +5 -0
- data/lib/travis/client/user.rb +10 -1
- data/lib/travis/tools/safe_string.rb +20 -0
- data/lib/travis/version.rb +1 -1
- data/spec/cli/encrypt_spec.rb +5 -0
- data/spec/cli/restart_spec.rb +2 -2
- data/spec/client/broadcast_spec.rb +10 -0
- data/spec/support/fake_api.rb +7 -0
- data/travis.gemspec +27 -12
- metadata +61 -14
data/lib/travis/cli.rb
CHANGED
@@ -16,6 +16,7 @@ module Travis
|
|
16
16
|
module CLI
|
17
17
|
autoload :Token, 'travis/cli/token'
|
18
18
|
autoload :ApiCommand, 'travis/cli/api_command'
|
19
|
+
autoload :Accounts, 'travis/cli/accounts'
|
19
20
|
autoload :Branches, 'travis/cli/branches'
|
20
21
|
autoload :Command, 'travis/cli/command'
|
21
22
|
autoload :Console, 'travis/cli/console'
|
@@ -28,6 +29,7 @@ module Travis
|
|
28
29
|
autoload :Init, 'travis/cli/init'
|
29
30
|
autoload :Login, 'travis/cli/login'
|
30
31
|
autoload :Logs, 'travis/cli/logs'
|
32
|
+
autoload :Monitor, 'travis/cli/monitor'
|
31
33
|
autoload :Open, 'travis/cli/open'
|
32
34
|
autoload :Parser, 'travis/cli/parser'
|
33
35
|
autoload :Pubkey, 'travis/cli/pubkey'
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'travis/cli'
|
2
|
+
|
3
|
+
module Travis
|
4
|
+
module CLI
|
5
|
+
class Accounts < ApiCommand
|
6
|
+
def run
|
7
|
+
authenticate
|
8
|
+
accounts.each do |account|
|
9
|
+
color = account.subscribed? ? :green : :info
|
10
|
+
say [
|
11
|
+
color(account.login, [color, :bold]),
|
12
|
+
color("(#{account.name}):", color),
|
13
|
+
account.subscribed? ? "subscribed," : "not subscribed,",
|
14
|
+
account.repos_count == 1 ? "1 repository" : "#{account.repos_count} repositories"
|
15
|
+
].join(" ")
|
16
|
+
end
|
17
|
+
say session.config['host'], "To set up a subscription, please visit %s." unless accounts.all?(&:subscribed?)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/travis/cli/command.rb
CHANGED
@@ -58,6 +58,7 @@ module Travis
|
|
58
58
|
attr_reader :input, :output
|
59
59
|
|
60
60
|
def initialize(options = {})
|
61
|
+
@on_signal = []
|
61
62
|
@formatter = Travis::Tools::Formatter.new
|
62
63
|
self.output = $stdout
|
63
64
|
self.input = $stdin
|
@@ -124,6 +125,7 @@ module Travis
|
|
124
125
|
end
|
125
126
|
|
126
127
|
def execute
|
128
|
+
setup_trap
|
127
129
|
check_ruby
|
128
130
|
check_arity(method(:run), *arguments)
|
129
131
|
load_config
|
@@ -173,8 +175,21 @@ module Travis
|
|
173
175
|
end
|
174
176
|
end
|
175
177
|
|
178
|
+
def on_signal(&block)
|
179
|
+
@on_signal << block
|
180
|
+
end
|
181
|
+
|
176
182
|
private
|
177
183
|
|
184
|
+
def setup_trap
|
185
|
+
[:INT, :TERM].each do |signal|
|
186
|
+
trap signal do
|
187
|
+
@on_signal.each { |c| c.call }
|
188
|
+
exit 1
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
178
193
|
def format(data, format = nil, style = nil)
|
179
194
|
style ||= :important
|
180
195
|
data = format % color(data, style) if format and interactive?
|
data/lib/travis/cli/encrypt.rb
CHANGED
@@ -20,7 +20,7 @@ module Travis
|
|
20
20
|
error "--override without --add makes no sense" if override? and not add?
|
21
21
|
self.override |= !config_key.start_with?('env.') if add? and not append?
|
22
22
|
|
23
|
-
if args.first =~ %r{\w+/\w+}
|
23
|
+
if args.first =~ %r{\w+/\w+} && !args.first.include?("=")
|
24
24
|
warn "WARNING: The name of the repository is now passed to the command with the -r option:"
|
25
25
|
warn " #{command("encrypt [...] -r #{args.first}")}"
|
26
26
|
warn " If you tried to pass the name of the repository as the first argument, you"
|
@@ -100,4 +100,3 @@ Please add the following to your <[[ color('.travis.yml', :info) ]]> file:
|
|
100
100
|
%s
|
101
101
|
|
102
102
|
Pro Tip: You can add it automatically by running with <[[ color('--add', :info) ]]>.
|
103
|
-
|
data/lib/travis/cli/logs.rb
CHANGED
@@ -1,18 +1,14 @@
|
|
1
1
|
require 'travis/cli'
|
2
|
+
require 'travis/tools/safe_string'
|
2
3
|
|
3
4
|
module Travis
|
4
5
|
module CLI
|
5
6
|
class Logs < RepoCommand
|
7
|
+
include Tools::SafeString
|
6
8
|
def run(number = last_build.number)
|
7
9
|
error "##{number} is not a job, try #{number}.1" unless job = job(number)
|
8
|
-
|
10
|
+
job.log.body { |part| print interactive? ? encoded(part) : clean(part) }
|
9
11
|
end
|
10
|
-
|
11
|
-
private
|
12
|
-
|
13
|
-
def log(job)
|
14
|
-
interactive? ? job.log.colorized_body : job.log.clean_body
|
15
|
-
end
|
16
12
|
end
|
17
13
|
end
|
18
14
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'travis/cli'
|
2
|
+
|
3
|
+
module Travis
|
4
|
+
module CLI
|
5
|
+
class Monitor < ApiCommand
|
6
|
+
on('-m', '--my-repos', 'Only monitor my own repositories')
|
7
|
+
on('-r', '--repo SLUG', 'monitor given repository (can be used more than once)') do |c, slug|
|
8
|
+
c.repos << slug
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :repos
|
12
|
+
|
13
|
+
def initialize(*)
|
14
|
+
@repos = []
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
def setup
|
19
|
+
super
|
20
|
+
repos.map! { |r| repo(r) }
|
21
|
+
repos.concat(user.repositories) if my_repos?
|
22
|
+
end
|
23
|
+
|
24
|
+
def description
|
25
|
+
case repos.size
|
26
|
+
when 0 then session.config['host']
|
27
|
+
when 1 then repos.first.slug
|
28
|
+
else "#{repos.size} repositories"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def run
|
33
|
+
listen(*repos) do |listener|
|
34
|
+
listener.on_connect { say description, 'Monitoring %s:' }
|
35
|
+
listener.on 'build:started', 'job:started', 'build:finished', 'job:finished' do |event|
|
36
|
+
entity = event.job || event.build
|
37
|
+
time = entity.finished_at || entity.started_at
|
38
|
+
say [
|
39
|
+
color(formatter.time(time), entity.color),
|
40
|
+
color(entity.inspect_info, [entity.color, :bold]),
|
41
|
+
color(entity.state, entity.color)
|
42
|
+
].join(" ")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/travis/cli/restart.rb
CHANGED
data/lib/travis/cli/setup.rb
CHANGED
@@ -33,6 +33,20 @@ module Travis
|
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
+
def setup_rubygems
|
37
|
+
configure 'deploy', 'provider' => 'rubygems' do |config|
|
38
|
+
rubygems_file = File.expand_path('.rubygems/authorization', ENV['HOME'])
|
39
|
+
|
40
|
+
if File.exist? rubygems_file
|
41
|
+
config['api_key'] = File.read(rubygems_file)
|
42
|
+
end
|
43
|
+
|
44
|
+
config['api_key'] ||= ask("RubyGems API token: ") { |q| q.echo = "*" }.to_s
|
45
|
+
config['on'] = { 'repo' => repository.slug } if agree("Deploy only from #{repository.slug}? ") { |q| q.default = 'yes' }
|
46
|
+
encrypt(config, 'api_key') if agree("Encrypt API key? ") { |q| q.default = 'yes' }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
36
50
|
def setup_nodejitsu
|
37
51
|
configure 'deploy', 'provider' => 'nodejitsu' do |config|
|
38
52
|
jitsu_file = File.expand_path('.jitsuconf', ENV['HOME'])
|
@@ -59,8 +73,8 @@ module Travis
|
|
59
73
|
end
|
60
74
|
end
|
61
75
|
|
62
|
-
alias
|
63
|
-
alias setup_sauce
|
76
|
+
alias setup_sauce_labs setup_sauce_connect
|
77
|
+
alias setup_sauce setup_sauce_connect
|
64
78
|
|
65
79
|
private
|
66
80
|
|
data/lib/travis/cli/token.rb
CHANGED
data/lib/travis/cli/whatsup.rb
CHANGED
data/lib/travis/client.rb
CHANGED
@@ -3,12 +3,16 @@ require 'travis/client'
|
|
3
3
|
module Travis
|
4
4
|
module Client
|
5
5
|
class Account < Entity
|
6
|
-
attributes :name, :login, :type, :repos_count
|
6
|
+
attributes :name, :login, :type, :repos_count, :subscribed
|
7
7
|
|
8
8
|
one :account
|
9
9
|
many :accounts
|
10
10
|
|
11
11
|
inspect_info :login
|
12
|
+
|
13
|
+
def subscribed
|
14
|
+
attributes.fetch('subscribed') { true }
|
15
|
+
end
|
12
16
|
end
|
13
17
|
end
|
14
18
|
end
|
@@ -1,9 +1,12 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
require 'travis/client'
|
3
|
+
require 'travis/tools/safe_string'
|
3
4
|
|
4
5
|
module Travis
|
5
6
|
module Client
|
6
7
|
class Artifact < Entity
|
8
|
+
CHUNKED = "application/json; chunked=true; version=2, application/json; version=2"
|
9
|
+
|
7
10
|
# @!parse attr_reader :job_id, :type, :body
|
8
11
|
attributes :job_id, :type, :body
|
9
12
|
|
@@ -11,16 +14,49 @@ module Travis
|
|
11
14
|
has :job
|
12
15
|
|
13
16
|
def encoded_body
|
14
|
-
|
15
|
-
body.encode 'utf-8'
|
17
|
+
Tools::SafeString.encoded(body)
|
16
18
|
end
|
17
19
|
|
18
20
|
def colorized_body
|
19
|
-
attributes['colorized_body'] ||=
|
21
|
+
attributes['colorized_body'] ||= Tools::SafeString.colorized(body)
|
20
22
|
end
|
21
23
|
|
22
24
|
def clean_body
|
23
|
-
attributes['clean_body'] ||=
|
25
|
+
attributes['clean_body'] ||= Tools::SafeString.clean(body)
|
26
|
+
end
|
27
|
+
|
28
|
+
def body(stream = block_given?)
|
29
|
+
return load_attribute('body') unless block_given? or stream
|
30
|
+
return yield(load_attribute('body')) unless stream and job.pending?
|
31
|
+
number = 0
|
32
|
+
|
33
|
+
session.listen(self) do |listener|
|
34
|
+
listener.on 'job:log' do |event|
|
35
|
+
next unless event.payload['number'] > number
|
36
|
+
number = event.payload['number']
|
37
|
+
yield event.payload['_log']
|
38
|
+
listener.disconnect if event.payload['final']
|
39
|
+
end
|
40
|
+
|
41
|
+
listener.on 'job:finished' do |event|
|
42
|
+
listener.disconnect
|
43
|
+
end
|
44
|
+
|
45
|
+
listener.on_connect do
|
46
|
+
data = session.get_raw("/artifacts/#{id}", nil, "Accept" => CHUNKED)['log']
|
47
|
+
if data['parts']
|
48
|
+
data['parts'].each { |p| yield p['content'] }
|
49
|
+
number = data['parts'].last['number'] if data['parts'].any?
|
50
|
+
else
|
51
|
+
yield data['body']
|
52
|
+
listener.disconnect
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def pusher_entity
|
59
|
+
job
|
24
60
|
end
|
25
61
|
|
26
62
|
one :log
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'travis/client'
|
2
|
+
|
3
|
+
module Travis
|
4
|
+
module Client
|
5
|
+
class Broadcast < Entity
|
6
|
+
attributes :recipient_id, :recipient_type, :kind, :message, :expired, :created_at, :updated_at
|
7
|
+
|
8
|
+
one :broadcast
|
9
|
+
many :broadcasts
|
10
|
+
|
11
|
+
inspect_info :message
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/travis/client/build.rb
CHANGED
data/lib/travis/client/entity.rb
CHANGED
data/lib/travis/client/job.rb
CHANGED
@@ -0,0 +1,149 @@
|
|
1
|
+
require 'travis/client'
|
2
|
+
require 'forwardable'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
if require 'pusher-client'
|
6
|
+
# it's us that has been loading pusher-client
|
7
|
+
# so let's assume we can mess with it - yay for global state
|
8
|
+
PusherClient.logger.level = 2
|
9
|
+
end
|
10
|
+
|
11
|
+
module Travis
|
12
|
+
module Client
|
13
|
+
class Listener
|
14
|
+
class Socket < PusherClient::Socket
|
15
|
+
attr_accessor :session, :signatures
|
16
|
+
def initialize(application_key, options = {})
|
17
|
+
@session = options.fetch(:session)
|
18
|
+
@signatures = {}
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
22
|
+
def subscribe_all
|
23
|
+
# bulk auth on connect
|
24
|
+
fetch_auth(*channels.channels.keys)
|
25
|
+
super
|
26
|
+
end
|
27
|
+
|
28
|
+
def fetch_auth(*channels)
|
29
|
+
channels.select! { |c| signatures[c].nil? if c.start_with? 'private-' }
|
30
|
+
signatures.merge! session.post_raw('/pusher/auth', :channels => channels, :socket_id => socket_id)['channels'] if channels.any?
|
31
|
+
end
|
32
|
+
|
33
|
+
def get_private_auth(channel)
|
34
|
+
fetch_auth(channel.name)
|
35
|
+
signatures[channel.name]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
EVENTS = %w[
|
40
|
+
build:created build:started build:finished
|
41
|
+
job:created job:started job:log job:finished
|
42
|
+
]
|
43
|
+
|
44
|
+
Event = Struct.new(:type, :repository, :build, :job, :payload)
|
45
|
+
|
46
|
+
class EntityListener
|
47
|
+
attr_reader :listener, :entities
|
48
|
+
|
49
|
+
extend Forwardable
|
50
|
+
def_delegators :listener, :disconnect, :on_connect, :subscribe
|
51
|
+
|
52
|
+
def initialize(listener, entities)
|
53
|
+
@listener, @entities = listener, Array(entities)
|
54
|
+
end
|
55
|
+
|
56
|
+
def on(*events)
|
57
|
+
listener.on(*events) { |e| yield(e) if dispatch?(e) }
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def dispatch?(event)
|
63
|
+
entities.include? event.repository or
|
64
|
+
entities.include? event.build or
|
65
|
+
entities.include? event.job
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
attr_reader :session, :socket
|
70
|
+
|
71
|
+
def initialize(session)
|
72
|
+
@session = session
|
73
|
+
@socket = Socket.new(pusher_key, :encrypted => true, :session => session)
|
74
|
+
@channels = []
|
75
|
+
@callbacks = []
|
76
|
+
end
|
77
|
+
|
78
|
+
def subscribe(*entities)
|
79
|
+
entities = entities.map do |entity|
|
80
|
+
entity = entity.pusher_entity while entity.respond_to? :pusher_entity
|
81
|
+
@channels.concat(entity.pusher_channels)
|
82
|
+
entity
|
83
|
+
end
|
84
|
+
|
85
|
+
yield entities.any? ? EntityListener.new(self, entities) : self if block_given?
|
86
|
+
end
|
87
|
+
|
88
|
+
def on(*events, &block)
|
89
|
+
events = events.flat_map { |e| e.respond_to?(:to_str) ? e.to_str : EVENTS.grep(e) }.uniq
|
90
|
+
events.each { |e| @callbacks << [e, block] }
|
91
|
+
end
|
92
|
+
|
93
|
+
def on_connect
|
94
|
+
socket.bind('pusher:connection_established') { yield }
|
95
|
+
end
|
96
|
+
|
97
|
+
def listen
|
98
|
+
@channels = default_channels if @channels.empty?
|
99
|
+
@channels.map! { |c| c.start_with?('private-') ? c : "private-#{c}" } if session.private_channels?
|
100
|
+
@channels.uniq.each { |c| socket.subscribe(c) }
|
101
|
+
@callbacks.each { |e,b| socket.bind(e) { |d| dispatch(e, d, &b) } }
|
102
|
+
socket.connect
|
103
|
+
end
|
104
|
+
|
105
|
+
def disconnect
|
106
|
+
socket.disconnect
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
def dispatch(type, json)
|
112
|
+
payload = JSON.parse(json)
|
113
|
+
entities = session.load format_payload(type, payload)
|
114
|
+
yield Event.new(type, entities['repository'], entities['build'], entities['job'], payload)
|
115
|
+
end
|
116
|
+
|
117
|
+
def format_payload(type, payload)
|
118
|
+
case type
|
119
|
+
when "job:log" then format_log(payload)
|
120
|
+
when /job:/ then format_job(payload)
|
121
|
+
else payload
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def format_job(payload)
|
126
|
+
build = { "id" => payload["build_id"], "repository_id" => payload["repository_id"] }
|
127
|
+
repo = { "id" => payload["repository_id"], "slug" => payload["repository_slug"] }
|
128
|
+
build["number"] = payload["number"][/^[^\.]+/] if payload["number"]
|
129
|
+
{ "job" => payload, "build" => build, "repository" => repo }
|
130
|
+
end
|
131
|
+
|
132
|
+
def format_log(payload)
|
133
|
+
job = session.job(payload['id'])
|
134
|
+
{ "job" => { "id" => job.id }, "build" => { "id" => job.build.id }, "repository" => { "id" => job.repository.id } }
|
135
|
+
end
|
136
|
+
|
137
|
+
def default_channels
|
138
|
+
return ['common'] if session.access_token.nil?
|
139
|
+
session.user.channels
|
140
|
+
end
|
141
|
+
|
142
|
+
def pusher_key
|
143
|
+
session.config.fetch('pusher').fetch('key')
|
144
|
+
rescue IndexError
|
145
|
+
raise Travis::Client::Error, "#{session.api_endpoint} is missing pusher key"
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|