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.
Files changed (184) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +22 -0
  3. data/README.md +2486 -0
  4. data/Rakefile +63 -0
  5. data/assets/cacert.pem +69 -0
  6. data/assets/init/c.yml +4 -0
  7. data/assets/init/clojure.yml +1 -0
  8. data/assets/init/cpp.yml +4 -0
  9. data/assets/init/erlang.yml +3 -0
  10. data/assets/init/go.yml +4 -0
  11. data/assets/init/groovy.yml +1 -0
  12. data/assets/init/haskell.yml +1 -0
  13. data/assets/init/java.yml +4 -0
  14. data/assets/init/node_js.yml +4 -0
  15. data/assets/init/objective-c.yml +1 -0
  16. data/assets/init/perl.yml +4 -0
  17. data/assets/init/php.yml +4 -0
  18. data/assets/init/python.yml +5 -0
  19. data/assets/init/ruby.yml +6 -0
  20. data/assets/init/scala.yml +4 -0
  21. data/assets/notifications/Travis CI.app/Contents/Info.plist +52 -0
  22. data/assets/notifications/Travis CI.app/Contents/MacOS/Travis CI +0 -0
  23. data/assets/notifications/Travis CI.app/Contents/PkgInfo +1 -0
  24. data/assets/notifications/Travis CI.app/Contents/Resources/Travis CI.icns +0 -0
  25. data/assets/notifications/Travis CI.app/Contents/Resources/en.lproj/Credits.rtf +29 -0
  26. data/assets/notifications/Travis CI.app/Contents/Resources/en.lproj/InfoPlist.strings +0 -0
  27. data/assets/notifications/Travis CI.app/Contents/Resources/en.lproj/MainMenu.nib +0 -0
  28. data/assets/notifications/Travis CI.app/Contents/_CodeSignature/CodeResources +173 -0
  29. data/assets/notifications/Travis CI.app/Contents/embedded.provisionprofile +0 -0
  30. data/assets/notifications/icon.png +0 -0
  31. data/assets/travis.sh +163 -0
  32. data/assets/travis.sh.erb +64 -0
  33. data/bin/travis +18 -0
  34. data/examples/org_overview.rb +3 -0
  35. data/examples/pro_auth.rb +23 -0
  36. data/examples/stream.rb +6 -0
  37. data/lib/travis.rb +8 -0
  38. data/lib/travis/auto_login.rb +3 -0
  39. data/lib/travis/cli.rb +126 -0
  40. data/lib/travis/cli/accounts.rb +31 -0
  41. data/lib/travis/cli/api_command.rb +180 -0
  42. data/lib/travis/cli/branches.rb +25 -0
  43. data/lib/travis/cli/cache.rb +76 -0
  44. data/lib/travis/cli/cancel.rb +18 -0
  45. data/lib/travis/cli/command.rb +422 -0
  46. data/lib/travis/cli/console.rb +31 -0
  47. data/lib/travis/cli/disable.rb +15 -0
  48. data/lib/travis/cli/enable.rb +31 -0
  49. data/lib/travis/cli/encrypt.rb +108 -0
  50. data/lib/travis/cli/encrypt_file.rb +140 -0
  51. data/lib/travis/cli/endpoint.rb +35 -0
  52. data/lib/travis/cli/env.rb +66 -0
  53. data/lib/travis/cli/help.rb +23 -0
  54. data/lib/travis/cli/history.rb +49 -0
  55. data/lib/travis/cli/init.rb +82 -0
  56. data/lib/travis/cli/lint.rb +49 -0
  57. data/lib/travis/cli/login.rb +76 -0
  58. data/lib/travis/cli/logout.rb +14 -0
  59. data/lib/travis/cli/logs.rb +65 -0
  60. data/lib/travis/cli/monitor.rb +110 -0
  61. data/lib/travis/cli/open.rb +39 -0
  62. data/lib/travis/cli/parser.rb +43 -0
  63. data/lib/travis/cli/pubkey.rb +30 -0
  64. data/lib/travis/cli/raw.rb +20 -0
  65. data/lib/travis/cli/repo_command.rb +154 -0
  66. data/lib/travis/cli/report.rb +101 -0
  67. data/lib/travis/cli/repos.rb +53 -0
  68. data/lib/travis/cli/requests.rb +47 -0
  69. data/lib/travis/cli/restart.rb +18 -0
  70. data/lib/travis/cli/settings.rb +77 -0
  71. data/lib/travis/cli/setup.rb +66 -0
  72. data/lib/travis/cli/setup/anynines.rb +21 -0
  73. data/lib/travis/cli/setup/appfog.rb +19 -0
  74. data/lib/travis/cli/setup/artifacts.rb +23 -0
  75. data/lib/travis/cli/setup/biicode.rb +19 -0
  76. data/lib/travis/cli/setup/cloud_66.rb +20 -0
  77. data/lib/travis/cli/setup/cloud_control.rb +21 -0
  78. data/lib/travis/cli/setup/cloud_files.rb +20 -0
  79. data/lib/travis/cli/setup/cloud_foundry.rb +23 -0
  80. data/lib/travis/cli/setup/code_deploy.rb +55 -0
  81. data/lib/travis/cli/setup/deis.rb +20 -0
  82. data/lib/travis/cli/setup/divshot.rb +18 -0
  83. data/lib/travis/cli/setup/elastic_beanstalk.rb +23 -0
  84. data/lib/travis/cli/setup/engine_yard.rb +24 -0
  85. data/lib/travis/cli/setup/gcs.rb +22 -0
  86. data/lib/travis/cli/setup/hackage.rb +18 -0
  87. data/lib/travis/cli/setup/heroku.rb +20 -0
  88. data/lib/travis/cli/setup/modulus.rb +18 -0
  89. data/lib/travis/cli/setup/ninefold.rb +20 -0
  90. data/lib/travis/cli/setup/nodejitsu.rb +27 -0
  91. data/lib/travis/cli/setup/npm.rb +20 -0
  92. data/lib/travis/cli/setup/open_shift.rb +20 -0
  93. data/lib/travis/cli/setup/opsworks.rb +22 -0
  94. data/lib/travis/cli/setup/pypi.rb +22 -0
  95. data/lib/travis/cli/setup/releases.rb +35 -0
  96. data/lib/travis/cli/setup/ruby_gems.rb +25 -0
  97. data/lib/travis/cli/setup/s3.rb +25 -0
  98. data/lib/travis/cli/setup/sauce_connect.rb +21 -0
  99. data/lib/travis/cli/setup/service.rb +73 -0
  100. data/lib/travis/cli/show.rb +69 -0
  101. data/lib/travis/cli/sshkey.rb +118 -0
  102. data/lib/travis/cli/status.rb +19 -0
  103. data/lib/travis/cli/sync.rb +30 -0
  104. data/lib/travis/cli/token.rb +14 -0
  105. data/lib/travis/cli/version.rb +17 -0
  106. data/lib/travis/cli/whatsup.rb +30 -0
  107. data/lib/travis/cli/whoami.rb +15 -0
  108. data/lib/travis/client.rb +39 -0
  109. data/lib/travis/client/account.rb +56 -0
  110. data/lib/travis/client/annotation.rb +21 -0
  111. data/lib/travis/client/artifact.rb +88 -0
  112. data/lib/travis/client/auto_login.rb +45 -0
  113. data/lib/travis/client/broadcast.rb +14 -0
  114. data/lib/travis/client/build.rb +47 -0
  115. data/lib/travis/client/cache.rb +25 -0
  116. data/lib/travis/client/commit.rb +28 -0
  117. data/lib/travis/client/entity.rb +238 -0
  118. data/lib/travis/client/env_var.rb +102 -0
  119. data/lib/travis/client/error.rb +38 -0
  120. data/lib/travis/client/has_uuid.rb +13 -0
  121. data/lib/travis/client/job.rb +61 -0
  122. data/lib/travis/client/lint_result.rb +25 -0
  123. data/lib/travis/client/listener.rb +184 -0
  124. data/lib/travis/client/methods.rb +104 -0
  125. data/lib/travis/client/namespace.rb +85 -0
  126. data/lib/travis/client/not_loadable.rb +13 -0
  127. data/lib/travis/client/repository.rb +224 -0
  128. data/lib/travis/client/request.rb +36 -0
  129. data/lib/travis/client/restartable.rb +23 -0
  130. data/lib/travis/client/session.rb +339 -0
  131. data/lib/travis/client/settings.rb +25 -0
  132. data/lib/travis/client/singleton_setting.rb +36 -0
  133. data/lib/travis/client/ssh_key.rb +11 -0
  134. data/lib/travis/client/states.rb +98 -0
  135. data/lib/travis/client/user.rb +67 -0
  136. data/lib/travis/client/weak_entity.rb +26 -0
  137. data/lib/travis/pro.rb +5 -0
  138. data/lib/travis/pro/auto_login.rb +3 -0
  139. data/lib/travis/tools/assets.rb +21 -0
  140. data/lib/travis/tools/completion.rb +54 -0
  141. data/lib/travis/tools/formatter.rb +50 -0
  142. data/lib/travis/tools/github.rb +279 -0
  143. data/lib/travis/tools/notification.rb +69 -0
  144. data/lib/travis/tools/safe_string.rb +22 -0
  145. data/lib/travis/tools/ssl_key.rb +48 -0
  146. data/lib/travis/tools/system.rb +88 -0
  147. data/lib/travis/version.rb +3 -0
  148. data/spec/cli/cancel_spec.rb +15 -0
  149. data/spec/cli/encrypt_spec.rb +43 -0
  150. data/spec/cli/endpoint_spec.rb +34 -0
  151. data/spec/cli/help_spec.rb +33 -0
  152. data/spec/cli/history_spec.rb +38 -0
  153. data/spec/cli/init_spec.rb +226 -0
  154. data/spec/cli/login_spec.rb +13 -0
  155. data/spec/cli/logs_spec.rb +8 -0
  156. data/spec/cli/open_spec.rb +33 -0
  157. data/spec/cli/repo_command_spec.rb +25 -0
  158. data/spec/cli/restart_spec.rb +15 -0
  159. data/spec/cli/setup_spec.rb +5 -0
  160. data/spec/cli/show_spec.rb +9 -0
  161. data/spec/cli/status_spec.rb +28 -0
  162. data/spec/cli/token_spec.rb +22 -0
  163. data/spec/cli/version_spec.rb +18 -0
  164. data/spec/cli/whoami_spec.rb +34 -0
  165. data/spec/client/account_spec.rb +32 -0
  166. data/spec/client/annotation_spec.rb +14 -0
  167. data/spec/client/broadcast_spec.rb +10 -0
  168. data/spec/client/build_spec.rb +31 -0
  169. data/spec/client/commit_spec.rb +22 -0
  170. data/spec/client/job_spec.rb +30 -0
  171. data/spec/client/methods_spec.rb +15 -0
  172. data/spec/client/namespace_spec.rb +19 -0
  173. data/spec/client/repository_spec.rb +39 -0
  174. data/spec/client/session_spec.rb +165 -0
  175. data/spec/client/user_spec.rb +16 -0
  176. data/spec/client_spec.rb +17 -0
  177. data/spec/pro_spec.rb +10 -0
  178. data/spec/spec_helper.rb +29 -0
  179. data/spec/support/fake_api.rb +738 -0
  180. data/spec/support/fake_github.rb +24 -0
  181. data/spec/support/helpers.rb +45 -0
  182. data/spec/travis_spec.rb +10 -0
  183. data/travis.gemspec +323 -0
  184. 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,11 @@
1
+ require 'json'
2
+
3
+ module Travis
4
+ module Client
5
+ class SshKey < SingletonSetting
6
+ attributes :description, :fingerprint
7
+ one :ssh_key
8
+ many :ssh_keys
9
+ end
10
+ end
11
+ 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
@@ -0,0 +1,5 @@
1
+ require 'travis/client'
2
+
3
+ module Travis
4
+ Pro = Client::Namespace.new(Client::PRO_URI)
5
+ end
@@ -0,0 +1,3 @@
1
+ require 'travis/pro'
2
+ require 'travis/client/auto_login'
3
+ Travis::Client::AutoLogin.new(Travis::Pro).authenticate
@@ -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