travis-async-listener 1.8.3
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 +7 -0
- data/LICENSE +22 -0
- data/README.md +2486 -0
- data/Rakefile +63 -0
- data/assets/cacert.pem +69 -0
- data/assets/init/c.yml +4 -0
- data/assets/init/clojure.yml +1 -0
- data/assets/init/cpp.yml +4 -0
- data/assets/init/erlang.yml +3 -0
- data/assets/init/go.yml +4 -0
- data/assets/init/groovy.yml +1 -0
- data/assets/init/haskell.yml +1 -0
- data/assets/init/java.yml +4 -0
- data/assets/init/node_js.yml +4 -0
- data/assets/init/objective-c.yml +1 -0
- data/assets/init/perl.yml +4 -0
- data/assets/init/php.yml +4 -0
- data/assets/init/python.yml +5 -0
- data/assets/init/ruby.yml +6 -0
- data/assets/init/scala.yml +4 -0
- data/assets/notifications/Travis CI.app/Contents/Info.plist +52 -0
- data/assets/notifications/Travis CI.app/Contents/MacOS/Travis CI +0 -0
- data/assets/notifications/Travis CI.app/Contents/PkgInfo +1 -0
- data/assets/notifications/Travis CI.app/Contents/Resources/Travis CI.icns +0 -0
- data/assets/notifications/Travis CI.app/Contents/Resources/en.lproj/Credits.rtf +29 -0
- data/assets/notifications/Travis CI.app/Contents/Resources/en.lproj/InfoPlist.strings +0 -0
- data/assets/notifications/Travis CI.app/Contents/Resources/en.lproj/MainMenu.nib +0 -0
- data/assets/notifications/Travis CI.app/Contents/_CodeSignature/CodeResources +173 -0
- data/assets/notifications/Travis CI.app/Contents/embedded.provisionprofile +0 -0
- data/assets/notifications/icon.png +0 -0
- data/assets/travis.sh +163 -0
- data/assets/travis.sh.erb +64 -0
- data/bin/travis +18 -0
- data/examples/org_overview.rb +3 -0
- data/examples/pro_auth.rb +23 -0
- data/examples/stream.rb +6 -0
- data/lib/travis.rb +8 -0
- data/lib/travis/auto_login.rb +3 -0
- data/lib/travis/cli.rb +126 -0
- data/lib/travis/cli/accounts.rb +31 -0
- data/lib/travis/cli/api_command.rb +180 -0
- data/lib/travis/cli/branches.rb +25 -0
- data/lib/travis/cli/cache.rb +76 -0
- data/lib/travis/cli/cancel.rb +18 -0
- data/lib/travis/cli/command.rb +422 -0
- data/lib/travis/cli/console.rb +31 -0
- data/lib/travis/cli/disable.rb +15 -0
- data/lib/travis/cli/enable.rb +31 -0
- data/lib/travis/cli/encrypt.rb +108 -0
- data/lib/travis/cli/encrypt_file.rb +140 -0
- data/lib/travis/cli/endpoint.rb +35 -0
- data/lib/travis/cli/env.rb +66 -0
- data/lib/travis/cli/help.rb +23 -0
- data/lib/travis/cli/history.rb +49 -0
- data/lib/travis/cli/init.rb +82 -0
- data/lib/travis/cli/lint.rb +49 -0
- data/lib/travis/cli/login.rb +76 -0
- data/lib/travis/cli/logout.rb +14 -0
- data/lib/travis/cli/logs.rb +65 -0
- data/lib/travis/cli/monitor.rb +110 -0
- data/lib/travis/cli/open.rb +39 -0
- data/lib/travis/cli/parser.rb +43 -0
- data/lib/travis/cli/pubkey.rb +30 -0
- data/lib/travis/cli/raw.rb +20 -0
- data/lib/travis/cli/repo_command.rb +154 -0
- data/lib/travis/cli/report.rb +101 -0
- data/lib/travis/cli/repos.rb +53 -0
- data/lib/travis/cli/requests.rb +47 -0
- data/lib/travis/cli/restart.rb +18 -0
- data/lib/travis/cli/settings.rb +77 -0
- data/lib/travis/cli/setup.rb +66 -0
- data/lib/travis/cli/setup/anynines.rb +21 -0
- data/lib/travis/cli/setup/appfog.rb +19 -0
- data/lib/travis/cli/setup/artifacts.rb +23 -0
- data/lib/travis/cli/setup/biicode.rb +19 -0
- data/lib/travis/cli/setup/cloud_66.rb +20 -0
- data/lib/travis/cli/setup/cloud_control.rb +21 -0
- data/lib/travis/cli/setup/cloud_files.rb +20 -0
- data/lib/travis/cli/setup/cloud_foundry.rb +23 -0
- data/lib/travis/cli/setup/code_deploy.rb +55 -0
- data/lib/travis/cli/setup/deis.rb +20 -0
- data/lib/travis/cli/setup/divshot.rb +18 -0
- data/lib/travis/cli/setup/elastic_beanstalk.rb +23 -0
- data/lib/travis/cli/setup/engine_yard.rb +24 -0
- data/lib/travis/cli/setup/gcs.rb +22 -0
- data/lib/travis/cli/setup/hackage.rb +18 -0
- data/lib/travis/cli/setup/heroku.rb +20 -0
- data/lib/travis/cli/setup/modulus.rb +18 -0
- data/lib/travis/cli/setup/ninefold.rb +20 -0
- data/lib/travis/cli/setup/nodejitsu.rb +27 -0
- data/lib/travis/cli/setup/npm.rb +20 -0
- data/lib/travis/cli/setup/open_shift.rb +20 -0
- data/lib/travis/cli/setup/opsworks.rb +22 -0
- data/lib/travis/cli/setup/pypi.rb +22 -0
- data/lib/travis/cli/setup/releases.rb +35 -0
- data/lib/travis/cli/setup/ruby_gems.rb +25 -0
- data/lib/travis/cli/setup/s3.rb +25 -0
- data/lib/travis/cli/setup/sauce_connect.rb +21 -0
- data/lib/travis/cli/setup/service.rb +73 -0
- data/lib/travis/cli/show.rb +69 -0
- data/lib/travis/cli/sshkey.rb +118 -0
- data/lib/travis/cli/status.rb +19 -0
- data/lib/travis/cli/sync.rb +30 -0
- data/lib/travis/cli/token.rb +14 -0
- data/lib/travis/cli/version.rb +17 -0
- data/lib/travis/cli/whatsup.rb +30 -0
- data/lib/travis/cli/whoami.rb +15 -0
- data/lib/travis/client.rb +39 -0
- data/lib/travis/client/account.rb +56 -0
- data/lib/travis/client/annotation.rb +21 -0
- data/lib/travis/client/artifact.rb +88 -0
- data/lib/travis/client/auto_login.rb +45 -0
- data/lib/travis/client/broadcast.rb +14 -0
- data/lib/travis/client/build.rb +47 -0
- data/lib/travis/client/cache.rb +25 -0
- data/lib/travis/client/commit.rb +28 -0
- data/lib/travis/client/entity.rb +238 -0
- data/lib/travis/client/env_var.rb +102 -0
- data/lib/travis/client/error.rb +38 -0
- data/lib/travis/client/has_uuid.rb +13 -0
- data/lib/travis/client/job.rb +61 -0
- data/lib/travis/client/lint_result.rb +25 -0
- data/lib/travis/client/listener.rb +184 -0
- data/lib/travis/client/methods.rb +104 -0
- data/lib/travis/client/namespace.rb +85 -0
- data/lib/travis/client/not_loadable.rb +13 -0
- data/lib/travis/client/repository.rb +224 -0
- data/lib/travis/client/request.rb +36 -0
- data/lib/travis/client/restartable.rb +23 -0
- data/lib/travis/client/session.rb +339 -0
- data/lib/travis/client/settings.rb +25 -0
- data/lib/travis/client/singleton_setting.rb +36 -0
- data/lib/travis/client/ssh_key.rb +11 -0
- data/lib/travis/client/states.rb +98 -0
- data/lib/travis/client/user.rb +67 -0
- data/lib/travis/client/weak_entity.rb +26 -0
- data/lib/travis/pro.rb +5 -0
- data/lib/travis/pro/auto_login.rb +3 -0
- data/lib/travis/tools/assets.rb +21 -0
- data/lib/travis/tools/completion.rb +54 -0
- data/lib/travis/tools/formatter.rb +50 -0
- data/lib/travis/tools/github.rb +279 -0
- data/lib/travis/tools/notification.rb +69 -0
- data/lib/travis/tools/safe_string.rb +22 -0
- data/lib/travis/tools/ssl_key.rb +48 -0
- data/lib/travis/tools/system.rb +88 -0
- data/lib/travis/version.rb +3 -0
- data/spec/cli/cancel_spec.rb +15 -0
- data/spec/cli/encrypt_spec.rb +43 -0
- data/spec/cli/endpoint_spec.rb +34 -0
- data/spec/cli/help_spec.rb +33 -0
- data/spec/cli/history_spec.rb +38 -0
- data/spec/cli/init_spec.rb +226 -0
- data/spec/cli/login_spec.rb +13 -0
- data/spec/cli/logs_spec.rb +8 -0
- data/spec/cli/open_spec.rb +33 -0
- data/spec/cli/repo_command_spec.rb +25 -0
- data/spec/cli/restart_spec.rb +15 -0
- data/spec/cli/setup_spec.rb +5 -0
- data/spec/cli/show_spec.rb +9 -0
- data/spec/cli/status_spec.rb +28 -0
- data/spec/cli/token_spec.rb +22 -0
- data/spec/cli/version_spec.rb +18 -0
- data/spec/cli/whoami_spec.rb +34 -0
- data/spec/client/account_spec.rb +32 -0
- data/spec/client/annotation_spec.rb +14 -0
- data/spec/client/broadcast_spec.rb +10 -0
- data/spec/client/build_spec.rb +31 -0
- data/spec/client/commit_spec.rb +22 -0
- data/spec/client/job_spec.rb +30 -0
- data/spec/client/methods_spec.rb +15 -0
- data/spec/client/namespace_spec.rb +19 -0
- data/spec/client/repository_spec.rb +39 -0
- data/spec/client/session_spec.rb +165 -0
- data/spec/client/user_spec.rb +16 -0
- data/spec/client_spec.rb +17 -0
- data/spec/pro_spec.rb +10 -0
- data/spec/spec_helper.rb +29 -0
- data/spec/support/fake_api.rb +738 -0
- data/spec/support/fake_github.rb +24 -0
- data/spec/support/helpers.rb +45 -0
- data/spec/travis_spec.rb +10 -0
- data/travis.gemspec +323 -0
- metadata +489 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require 'travis/client/weak_entity'
|
|
2
|
+
require 'json'
|
|
3
|
+
|
|
4
|
+
module Travis
|
|
5
|
+
module Client
|
|
6
|
+
class Settings < WeakEntity
|
|
7
|
+
attr_accessor :repository
|
|
8
|
+
# @!parse attr_reader :builds_only_with_travis_yml, :build_pushes, :build_pull_requests, :maximum_number_of_builds
|
|
9
|
+
attributes :builds_only_with_travis_yml, :build_pushes, :build_pull_requests, :maximum_number_of_builds
|
|
10
|
+
one :settings
|
|
11
|
+
many :settings
|
|
12
|
+
|
|
13
|
+
def save
|
|
14
|
+
raise "repository unknown" unless repository
|
|
15
|
+
result = session.patch("/repos/#{repository.id}/settings", JSON.dump("settings" => attributes))
|
|
16
|
+
attributes.replace(result['settings'].attributes)
|
|
17
|
+
self
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def inspect_info
|
|
21
|
+
repository ? repository.slug : repository
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
|
|
3
|
+
module Travis
|
|
4
|
+
module Client
|
|
5
|
+
class SingletonSetting < Entity
|
|
6
|
+
def self.base_path
|
|
7
|
+
"settings/#{one}"
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
has :repository
|
|
11
|
+
|
|
12
|
+
def repository_id
|
|
13
|
+
id
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def path
|
|
17
|
+
"#{self.class.base_path}/#{id}"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def update(values = {})
|
|
21
|
+
values = { 'value' => values } unless values.is_a? Hash
|
|
22
|
+
values.each { |key, value| attributes[key.to_s] = value.to_s }
|
|
23
|
+
session.patch_raw(path, JSON.dump(self.class.one => attributes))
|
|
24
|
+
reload
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def delete
|
|
28
|
+
session.delete_raw(path)
|
|
29
|
+
reload
|
|
30
|
+
true
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
alias save update
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
require 'travis/client'
|
|
2
|
+
|
|
3
|
+
module Travis
|
|
4
|
+
module Client
|
|
5
|
+
module States
|
|
6
|
+
STATES = %w[created queued received started passed failed errored canceled ready]
|
|
7
|
+
|
|
8
|
+
def ready?
|
|
9
|
+
state == 'ready'
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def pending?
|
|
13
|
+
check_state
|
|
14
|
+
%w[created started queued received ].include? state
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def started?
|
|
18
|
+
check_state
|
|
19
|
+
state != 'created' and state != 'received' and state != 'queued'
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def received?
|
|
23
|
+
check_state
|
|
24
|
+
state != 'created' and state != 'queued'
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def queued?
|
|
28
|
+
check_state
|
|
29
|
+
state != 'created'
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def finished?
|
|
33
|
+
not pending?
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def passed?
|
|
37
|
+
check_state
|
|
38
|
+
state == 'passed'
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def errored?
|
|
42
|
+
check_state
|
|
43
|
+
state == 'errored'
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def failed?
|
|
47
|
+
check_state
|
|
48
|
+
state == 'failed'
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def canceled?
|
|
52
|
+
check_state
|
|
53
|
+
state == 'canceled'
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def unsuccessful?
|
|
57
|
+
errored? or failed? or canceled?
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def created?
|
|
61
|
+
check_state
|
|
62
|
+
!!state
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def color
|
|
66
|
+
case state
|
|
67
|
+
when 'created', 'queued', 'received', 'started' then 'yellow'
|
|
68
|
+
when 'passed', 'ready' then 'green'
|
|
69
|
+
when 'errored', 'canceled', 'failed' then 'red'
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def yellow?
|
|
74
|
+
color == 'yellow'
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def green?
|
|
78
|
+
color == 'green'
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def red?
|
|
82
|
+
color == 'red'
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def running?
|
|
86
|
+
state == 'started'
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
alias successful? passed?
|
|
90
|
+
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
def check_state
|
|
94
|
+
raise Error, "unknown state %p for %p" % [state, self] unless STATES.include? state
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
require 'travis/client'
|
|
2
|
+
|
|
3
|
+
module Travis
|
|
4
|
+
module Client
|
|
5
|
+
class User < Entity
|
|
6
|
+
# @!parse attr_reader :login, :name, :email, :gravatar_id, :locale, :is_syncing, :synced_at, :correct_scopes
|
|
7
|
+
attributes :login, :name, :email, :gravatar_id, :locale, :is_syncing, :synced_at, :correct_scopes, :channels
|
|
8
|
+
inspect_info :login
|
|
9
|
+
|
|
10
|
+
one :user
|
|
11
|
+
many :users
|
|
12
|
+
|
|
13
|
+
def synced_at=(time)
|
|
14
|
+
set_attribute(:synced_at, time(time))
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def sync
|
|
18
|
+
session.post_raw('/users/sync')
|
|
19
|
+
reload
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def channels
|
|
23
|
+
load_attribute(:is_syncing) # dummy to trigger load, as channels might not be included
|
|
24
|
+
attributes['channels'] ||= ['common']
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def permissions
|
|
28
|
+
attributes['permissions'] ||= begin
|
|
29
|
+
repos = session.get('/users/permissions')
|
|
30
|
+
repos.each_value { |r| r.compact! }
|
|
31
|
+
repos
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def repositories
|
|
36
|
+
permissions['permissions']
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def push_access
|
|
40
|
+
permissions['push']
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def pull_access
|
|
44
|
+
permissions['pull']
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def admin_access
|
|
48
|
+
permissions['admin']
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def push_access?(repo)
|
|
52
|
+
push_access.include? repo
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def pull_access?(repo)
|
|
56
|
+
pull_access.include? repo
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def admin_access?(repo)
|
|
60
|
+
admin_access.include? repo
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
alias syncing? is_syncing
|
|
64
|
+
alias correct_scopes? correct_scopes
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
require 'travis/client'
|
|
2
|
+
|
|
3
|
+
module Travis
|
|
4
|
+
module Client
|
|
5
|
+
class WeakEntity < Entity
|
|
6
|
+
include NotLoadable
|
|
7
|
+
|
|
8
|
+
def self.weak?
|
|
9
|
+
true
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.id_field
|
|
13
|
+
raise "weak entities do not have id fields"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.id?(object)
|
|
17
|
+
object.nil?
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.cast_id(object)
|
|
21
|
+
return object if id? object
|
|
22
|
+
raise "weak entities do not have id fields"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
data/lib/travis/pro.rb
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Travis
|
|
2
|
+
module Tools
|
|
3
|
+
module Assets
|
|
4
|
+
BASE = File.expand_path('../../../../assets', __FILE__)
|
|
5
|
+
extend self
|
|
6
|
+
|
|
7
|
+
def asset_path(file)
|
|
8
|
+
File.expand_path(file, BASE)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def asset(file)
|
|
12
|
+
File.read(asset_path(file))
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class << self
|
|
16
|
+
alias [] asset_path
|
|
17
|
+
alias read asset
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
require 'travis/tools/assets'
|
|
2
|
+
require 'travis/cli'
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'erb'
|
|
5
|
+
|
|
6
|
+
module Travis
|
|
7
|
+
module Tools
|
|
8
|
+
module Completion
|
|
9
|
+
RCS = ['.zshrc', '.bashrc'].map { |f| File.expand_path(f, ENV['HOME']) }
|
|
10
|
+
include FileUtils
|
|
11
|
+
extend self
|
|
12
|
+
|
|
13
|
+
def config_path
|
|
14
|
+
ENV.fetch('TRAVIS_CONFIG_PATH') { File.expand_path('.travis', ENV['HOME']) }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def cmp_file
|
|
18
|
+
File.expand_path('travis.sh', config_path)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def install_completion
|
|
22
|
+
update_completion
|
|
23
|
+
source = "source " << cmp_file
|
|
24
|
+
|
|
25
|
+
RCS.each do |file|
|
|
26
|
+
next unless File.exist? file and File.writable? file
|
|
27
|
+
next if File.read(file).include? source
|
|
28
|
+
File.open(file, "a") { |f| f.puts("", "# added by travis gem", "[ -f #{cmp_file} ] && #{source}") }
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def update_completion
|
|
33
|
+
mkdir_p(config_path)
|
|
34
|
+
cp(Assets['travis.sh'], cmp_file)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def completion_installed?
|
|
38
|
+
source = "source " << config_path
|
|
39
|
+
RCS.each do |file|
|
|
40
|
+
next unless File.exist? file and File.writable? file
|
|
41
|
+
return false unless File.read(file).include? source
|
|
42
|
+
end
|
|
43
|
+
true
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def compile
|
|
47
|
+
commands = Travis::CLI.commands.sort_by { |c| c.command_name }
|
|
48
|
+
template = Assets.read('travis.sh.erb')
|
|
49
|
+
source = ERB.new(template).result(binding).gsub(/^ +\n/, '')
|
|
50
|
+
File.write(Assets['travis.sh'], source)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
require 'time'
|
|
2
|
+
|
|
3
|
+
module Travis
|
|
4
|
+
module Tools
|
|
5
|
+
class Formatter
|
|
6
|
+
DAY = 24 * 60 * 60
|
|
7
|
+
TIME_FORMAT = "%Y-%m-%d %H:%M:%S"
|
|
8
|
+
CONFIG_KEYS = ['rvm', 'gemfile', 'env', 'jdk', 'otp_release', 'php', 'node_js', 'perl', 'python', 'scala', 'compiler', 'os']
|
|
9
|
+
|
|
10
|
+
def duration(seconds, suffix = nil)
|
|
11
|
+
return "none" if seconds.nil?
|
|
12
|
+
seconds = (Time.now - seconds).to_i if seconds.is_a? Time
|
|
13
|
+
output = []
|
|
14
|
+
minutes, seconds = seconds.divmod(60)
|
|
15
|
+
hours, minutes = minutes.divmod(60)
|
|
16
|
+
output << "#{hours } hrs" if hours > 0
|
|
17
|
+
output << "#{minutes} min" if minutes > 0
|
|
18
|
+
output << "#{seconds} sec" if seconds > 0 or output.empty?
|
|
19
|
+
output << suffix if suffix
|
|
20
|
+
output.join(" ")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def file_size(input, human = true)
|
|
24
|
+
return "#{input} B" unless human
|
|
25
|
+
format = "B"
|
|
26
|
+
iec = %w[KiB MiB GiB TiB PiB EiB ZiB YiB]
|
|
27
|
+
while human and input > 512 and iec.any?
|
|
28
|
+
input /= 1024.0
|
|
29
|
+
format = iec.shift
|
|
30
|
+
end
|
|
31
|
+
input = input.round(2) if input.is_a? Float
|
|
32
|
+
"#{input} #{format}"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def time(time)
|
|
36
|
+
return "not yet" if time.nil? # or time > Time.now
|
|
37
|
+
#return duration(time, "ago") if Time.now - time < DAY
|
|
38
|
+
time.localtime.strftime(TIME_FORMAT)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def job_config(config)
|
|
42
|
+
output = []
|
|
43
|
+
config.each_pair do |key, value|
|
|
44
|
+
output << "#{key}: #{value}" if CONFIG_KEYS.include? key
|
|
45
|
+
end
|
|
46
|
+
output.join(", ")
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
require 'travis/tools/system'
|
|
2
|
+
require 'yaml'
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module Travis
|
|
6
|
+
module Tools
|
|
7
|
+
class Github
|
|
8
|
+
TOKEN_SIZE = 40
|
|
9
|
+
GITHUB_API = 'api.github.com'
|
|
10
|
+
GITHUB_HOST = 'github.com'
|
|
11
|
+
|
|
12
|
+
attr_accessor :api_url, :scopes, :github_token, :github_login, :drop_token, :callback, :explode, :after_tokens,
|
|
13
|
+
:ask_login, :ask_password, :ask_otp, :login_header, :auto_token, :auto_password, :manual_login, :note,
|
|
14
|
+
:netrc_path, :hub_path, :oauth_paths, :composer_path, :git_config_keys, :debug, :no_token, :check_token
|
|
15
|
+
|
|
16
|
+
def initialize(options = nil)
|
|
17
|
+
@check_token = true
|
|
18
|
+
@manual_login = true
|
|
19
|
+
@ask_login = proc { raise "ask_login callback not set" }
|
|
20
|
+
@after_tokens = proc { }
|
|
21
|
+
@ask_password = proc { |_| raise "ask_password callback not set" }
|
|
22
|
+
@ask_otp = proc { |_| raise "ask_otp callback not set" }
|
|
23
|
+
@debug = proc { |_| }
|
|
24
|
+
@netrc_path = '~/.netrc'
|
|
25
|
+
@hub_path = ENV['HUB_CONFIG'] || '~/.config/hub'
|
|
26
|
+
@oauth_paths = ['~/.github-oauth-token']
|
|
27
|
+
@composer_path = "~/.composer/config.json"
|
|
28
|
+
@note = 'temporary token'
|
|
29
|
+
@git_config_keys = %w[github.token github.oauth-token]
|
|
30
|
+
@scopes = ['user', 'user:email', 'repo'] # overridden by value from /config
|
|
31
|
+
options.each_pair { |k,v| send("#{k}=", v) if respond_to? "#{k}=" } if options
|
|
32
|
+
yield self if block_given?
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def with_token
|
|
36
|
+
each_token { |t| break yield(t) }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def with_basic_auth(&block)
|
|
40
|
+
user, password = ask_credentials
|
|
41
|
+
basic_auth(user, password, true) do |gh|
|
|
42
|
+
gh['user'] # so otp kicks in
|
|
43
|
+
yield gh
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def each_token
|
|
48
|
+
require 'gh' unless defined? GH
|
|
49
|
+
possible_tokens { |t| yield(t) if acceptable?(t) }
|
|
50
|
+
ensure
|
|
51
|
+
callback, self.callback = self.callback, nil
|
|
52
|
+
callback.call if callback
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def with_session(&block)
|
|
56
|
+
with_token { |t| GH.with(:token => t) { yield(t) } }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def possible_tokens(&block)
|
|
60
|
+
return block[github_token] if github_token
|
|
61
|
+
|
|
62
|
+
if auto_token
|
|
63
|
+
netrc_tokens(&block)
|
|
64
|
+
git_tokens(&block)
|
|
65
|
+
hub_tokens(&block)
|
|
66
|
+
oauth_file_tokens(&block)
|
|
67
|
+
github_for_mac_token(&block)
|
|
68
|
+
issuepost_token(&block)
|
|
69
|
+
composer_token(&block)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
if auto_password
|
|
73
|
+
possible_logins do |user, password|
|
|
74
|
+
yield login(user, password, false)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
if manual_login
|
|
79
|
+
user, password = ask_credentials
|
|
80
|
+
yield login(user, password, true)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
after_tokens.call
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def ask_credentials
|
|
87
|
+
login_header.call if login_header
|
|
88
|
+
user = github_login || ask_login.call
|
|
89
|
+
password = ask_password.arity == 0 ? ask_password.call : ask_password.call(user)
|
|
90
|
+
[user, password]
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def possible_logins(&block)
|
|
94
|
+
netrc_logins(&block)
|
|
95
|
+
hub_logins(&block)
|
|
96
|
+
keychain_login(&block)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def netrc_tokens
|
|
100
|
+
netrc.each do |entry|
|
|
101
|
+
next unless entry["machine"] == api_host or entry["machine"] == host
|
|
102
|
+
entry.values_at("token", "login", "password").each do |entry|
|
|
103
|
+
next if entry.to_s.size != TOKEN_SIZE
|
|
104
|
+
debug "found oauth token in netrc"
|
|
105
|
+
yield entry
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def git_tokens
|
|
111
|
+
return unless System.has? 'git'
|
|
112
|
+
git_config_keys.each do |key|
|
|
113
|
+
`git config --get-all #{key}`.each_line do |line|
|
|
114
|
+
token = line.strip
|
|
115
|
+
yield token unless token.empty?
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def composer_token
|
|
121
|
+
file(composer_path) do |content|
|
|
122
|
+
token = JSON.parse(content)['config'].fetch('github-oauth', {})[host]
|
|
123
|
+
yield token if token
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def hub_tokens
|
|
128
|
+
hub.fetch(host, []).each do |entry|
|
|
129
|
+
next if github_login and github_login != entry["user"]
|
|
130
|
+
yield entry["oauth_token"] if entry["oauth_token"]
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def oauth_file_tokens(&block)
|
|
135
|
+
oauth_paths.each do |path|
|
|
136
|
+
file(path) do |content|
|
|
137
|
+
token = content.strip
|
|
138
|
+
yield token unless token.empty?
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def netrc_logins
|
|
144
|
+
netrc.each do |entry|
|
|
145
|
+
next unless entry["machine"] == api_host or entry["machine"] == host
|
|
146
|
+
next if github_login and github_login != entry["login"]
|
|
147
|
+
yield entry["login"], entry["password"] if entry["login"] and entry["password"]
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def hub_logins
|
|
152
|
+
hub.fetch(host, []).each do |entry|
|
|
153
|
+
next if github_login and github_login != entry["user"]
|
|
154
|
+
yield entry["user"], entry["password"] if entry["user"] and entry["password"]
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def keychain_login
|
|
159
|
+
if github_login
|
|
160
|
+
security(:internet, :w, "-s #{host} -a #{github_login}", "#{host} password for #{github_login}") do |password|
|
|
161
|
+
yield github_login, password if password and not password.empty?
|
|
162
|
+
end
|
|
163
|
+
else
|
|
164
|
+
security(:internet, :g, "-s #{host}", "#{host} login and password") do |data|
|
|
165
|
+
username = data[/^\s+"acct"<blob>="(.*)"$/, 1].to_s
|
|
166
|
+
password = data[/^password: "(.*)"$/, 1].to_s
|
|
167
|
+
yield username, password unless username.empty? or password.empty?
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def netrc
|
|
173
|
+
file(netrc_path, []) do |contents|
|
|
174
|
+
contents.scan(/^\s*(\S+)\s+(\S+)\s*$/).inject([]) do |mapping, (key, value)|
|
|
175
|
+
mapping << {} if key == "machine"
|
|
176
|
+
mapping.last[key] = value if mapping.last
|
|
177
|
+
mapping
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def hub
|
|
183
|
+
file(hub_path, {}) do |contents|
|
|
184
|
+
YAML.load(contents)
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def issuepost_token(&block)
|
|
189
|
+
security(:generic, :w, "-l issuepost.github.access_token", "issuepost token", &block) if host == 'github.com'
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def github_for_mac_token(&block)
|
|
193
|
+
command = '-s "github.com/mac"'
|
|
194
|
+
command << " -a #{github_login}" if github_login
|
|
195
|
+
security(:internet, :w, command, "GitHub for Mac token", &block) if host == 'github.com'
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def host
|
|
199
|
+
api_host == GITHUB_API ? GITHUB_HOST : api_host
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def api_host
|
|
203
|
+
return GITHUB_API unless api_url
|
|
204
|
+
api_url[%r{^(?:https?://)?([^/]+)}, 1]
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def basic_auth(user, password, die = true, otp = nil, &block)
|
|
208
|
+
opt = { :username => user, :password => password }
|
|
209
|
+
opt[:headers] = { "X-GitHub-OTP" => otp } if otp
|
|
210
|
+
yield GH.with(opt)
|
|
211
|
+
rescue GH::Error => error
|
|
212
|
+
if error.info[:response_status] == 401 and error.info[:response_headers]['x-github-otp'].to_s =~ /required/
|
|
213
|
+
otp = ask_otp.arity == 0 ? ask_otp.call : ask_otp.call(user)
|
|
214
|
+
basic_auth(user, password, die, otp, &block)
|
|
215
|
+
elsif die
|
|
216
|
+
raise gh_error(error)
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def login(user, password, die = true, otp = nil)
|
|
221
|
+
basic_auth(user, password, die, otp) do |gh|
|
|
222
|
+
reply = gh.post('/authorizations', :scopes => scopes, :note => note)
|
|
223
|
+
self.callback = proc { gh.delete reply['_links']['self']['href'] } if drop_token
|
|
224
|
+
reply['token']
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def acceptable?(token)
|
|
229
|
+
return true unless check_token
|
|
230
|
+
gh = GH.with(:token => token)
|
|
231
|
+
user = gh['user']
|
|
232
|
+
|
|
233
|
+
if github_login and github_login != user['login']
|
|
234
|
+
debug "token is not acceptable: identifies %p instead of %p" % [user['login'], github_login]
|
|
235
|
+
false
|
|
236
|
+
else
|
|
237
|
+
true
|
|
238
|
+
end
|
|
239
|
+
rescue GH::Error => error
|
|
240
|
+
debug "token is not acceptable: #{gh_error(error)}"
|
|
241
|
+
false
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
private
|
|
245
|
+
|
|
246
|
+
def gh_error(error)
|
|
247
|
+
raise error if explode
|
|
248
|
+
JSON.parse(error.info[:response_body])["message"].to_s
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def debug(line)
|
|
252
|
+
return unless @debug
|
|
253
|
+
@debug.call "Tools::Github: #{line}"
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def security(type, key, arg, name)
|
|
257
|
+
return false unless System.has? 'security'
|
|
258
|
+
return false unless system "security find-#{type}-password #{arg} 2>/dev/null >/dev/null"
|
|
259
|
+
debug "requesting to load #{name} from keychain"
|
|
260
|
+
result = %x[security find-#{type}-password #{arg} -#{key} 2>&1].chomp
|
|
261
|
+
$?.success? ? yield(result) : debug("request denied")
|
|
262
|
+
rescue => e
|
|
263
|
+
raise e if explode
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def file(path, default = nil)
|
|
267
|
+
path &&= File.expand_path(path)
|
|
268
|
+
@file ||= {}
|
|
269
|
+
@file[path] ||= if path and File.readable?(path)
|
|
270
|
+
debug "reading #{path}"
|
|
271
|
+
yield File.read(path)
|
|
272
|
+
end
|
|
273
|
+
@file[path] || default
|
|
274
|
+
rescue => e
|
|
275
|
+
raise e if explode
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
end
|