carat 1.9.9.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (184) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +24 -0
  5. data/CHANGELOG.md +2006 -0
  6. data/CODE_OF_CONDUCT.md +40 -0
  7. data/CONTRIBUTING.md +23 -0
  8. data/DEVELOPMENT.md +119 -0
  9. data/ISSUES.md +96 -0
  10. data/LICENSE.md +23 -0
  11. data/README.md +32 -0
  12. data/Rakefile +308 -0
  13. data/bin/carat +21 -0
  14. data/bin/carat_ruby +56 -0
  15. data/carat.gemspec +32 -0
  16. data/lib/carat.rb +446 -0
  17. data/lib/carat/anonymizable_uri.rb +32 -0
  18. data/lib/carat/capistrano.rb +16 -0
  19. data/lib/carat/cli.rb +407 -0
  20. data/lib/carat/cli/binstubs.rb +38 -0
  21. data/lib/carat/cli/cache.rb +35 -0
  22. data/lib/carat/cli/check.rb +35 -0
  23. data/lib/carat/cli/clean.rb +26 -0
  24. data/lib/carat/cli/common.rb +56 -0
  25. data/lib/carat/cli/config.rb +84 -0
  26. data/lib/carat/cli/console.rb +38 -0
  27. data/lib/carat/cli/exec.rb +44 -0
  28. data/lib/carat/cli/gem.rb +195 -0
  29. data/lib/carat/cli/init.rb +33 -0
  30. data/lib/carat/cli/inject.rb +33 -0
  31. data/lib/carat/cli/install.rb +156 -0
  32. data/lib/carat/cli/open.rb +23 -0
  33. data/lib/carat/cli/outdated.rb +80 -0
  34. data/lib/carat/cli/package.rb +45 -0
  35. data/lib/carat/cli/platform.rb +43 -0
  36. data/lib/carat/cli/show.rb +74 -0
  37. data/lib/carat/cli/update.rb +73 -0
  38. data/lib/carat/cli/viz.rb +27 -0
  39. data/lib/carat/constants.rb +5 -0
  40. data/lib/carat/current_ruby.rb +183 -0
  41. data/lib/carat/definition.rb +628 -0
  42. data/lib/carat/dep_proxy.rb +43 -0
  43. data/lib/carat/dependency.rb +110 -0
  44. data/lib/carat/deployment.rb +59 -0
  45. data/lib/carat/deprecate.rb +15 -0
  46. data/lib/carat/dsl.rb +331 -0
  47. data/lib/carat/endpoint_specification.rb +76 -0
  48. data/lib/carat/env.rb +75 -0
  49. data/lib/carat/environment.rb +42 -0
  50. data/lib/carat/fetcher.rb +423 -0
  51. data/lib/carat/friendly_errors.rb +85 -0
  52. data/lib/carat/gem_helper.rb +180 -0
  53. data/lib/carat/gem_helpers.rb +26 -0
  54. data/lib/carat/gem_installer.rb +9 -0
  55. data/lib/carat/gem_path_manipulation.rb +8 -0
  56. data/lib/carat/gem_tasks.rb +2 -0
  57. data/lib/carat/graph.rb +169 -0
  58. data/lib/carat/index.rb +197 -0
  59. data/lib/carat/injector.rb +64 -0
  60. data/lib/carat/installer.rb +339 -0
  61. data/lib/carat/lazy_specification.rb +83 -0
  62. data/lib/carat/lockfile_parser.rb +167 -0
  63. data/lib/carat/match_platform.rb +13 -0
  64. data/lib/carat/psyched_yaml.rb +26 -0
  65. data/lib/carat/remote_specification.rb +57 -0
  66. data/lib/carat/resolver.rb +334 -0
  67. data/lib/carat/retry.rb +60 -0
  68. data/lib/carat/ruby_dsl.rb +11 -0
  69. data/lib/carat/ruby_version.rb +117 -0
  70. data/lib/carat/rubygems_ext.rb +170 -0
  71. data/lib/carat/rubygems_integration.rb +619 -0
  72. data/lib/carat/runtime.rb +289 -0
  73. data/lib/carat/settings.rb +208 -0
  74. data/lib/carat/setup.rb +24 -0
  75. data/lib/carat/shared_helpers.rb +149 -0
  76. data/lib/carat/similarity_detector.rb +63 -0
  77. data/lib/carat/source.rb +46 -0
  78. data/lib/carat/source/git.rb +294 -0
  79. data/lib/carat/source/git/git_proxy.rb +162 -0
  80. data/lib/carat/source/path.rb +226 -0
  81. data/lib/carat/source/path/installer.rb +43 -0
  82. data/lib/carat/source/rubygems.rb +381 -0
  83. data/lib/carat/source_list.rb +101 -0
  84. data/lib/carat/spec_set.rb +154 -0
  85. data/lib/carat/ssl_certs/.document +1 -0
  86. data/lib/carat/ssl_certs/AddTrustExternalCARoot-2048.pem +25 -0
  87. data/lib/carat/ssl_certs/AddTrustExternalCARoot.pem +32 -0
  88. data/lib/carat/ssl_certs/Class3PublicPrimaryCertificationAuthority.pem +14 -0
  89. data/lib/carat/ssl_certs/DigiCertHighAssuranceEVRootCA.pem +23 -0
  90. data/lib/carat/ssl_certs/EntrustnetSecureServerCertificationAuthority.pem +28 -0
  91. data/lib/carat/ssl_certs/GeoTrustGlobalCA.pem +20 -0
  92. data/lib/carat/ssl_certs/certificate_manager.rb +66 -0
  93. data/lib/carat/ssl_certs/index.rubygems.org/GlobalSignRootCA.pem +21 -0
  94. data/lib/carat/ssl_certs/rubygems.global.ssl.fastly.net/DigiCertHighAssuranceEVRootCA.pem +23 -0
  95. data/lib/carat/ssl_certs/rubygems.org/AddTrustExternalCARoot.pem +25 -0
  96. data/lib/carat/templates/Executable +16 -0
  97. data/lib/carat/templates/Executable.standalone +12 -0
  98. data/lib/carat/templates/Gemfile +4 -0
  99. data/lib/carat/templates/newgem/.travis.yml.tt +3 -0
  100. data/lib/carat/templates/newgem/CODE_OF_CONDUCT.md.tt +13 -0
  101. data/lib/carat/templates/newgem/Gemfile.tt +4 -0
  102. data/lib/carat/templates/newgem/LICENSE.txt.tt +21 -0
  103. data/lib/carat/templates/newgem/README.md.tt +39 -0
  104. data/lib/carat/templates/newgem/Rakefile.tt +25 -0
  105. data/lib/carat/templates/newgem/bin/console.tt +14 -0
  106. data/lib/carat/templates/newgem/bin/setup.tt +7 -0
  107. data/lib/carat/templates/newgem/exe/newgem.tt +3 -0
  108. data/lib/carat/templates/newgem/ext/newgem/extconf.rb.tt +3 -0
  109. data/lib/carat/templates/newgem/ext/newgem/newgem.c.tt +9 -0
  110. data/lib/carat/templates/newgem/ext/newgem/newgem.h.tt +6 -0
  111. data/lib/carat/templates/newgem/gitignore.tt +16 -0
  112. data/lib/carat/templates/newgem/lib/newgem.rb.tt +12 -0
  113. data/lib/carat/templates/newgem/lib/newgem/version.rb.tt +7 -0
  114. data/lib/carat/templates/newgem/newgem.gemspec.tt +43 -0
  115. data/lib/carat/templates/newgem/rspec.tt +2 -0
  116. data/lib/carat/templates/newgem/spec/newgem_spec.rb.tt +11 -0
  117. data/lib/carat/templates/newgem/spec/spec_helper.rb.tt +2 -0
  118. data/lib/carat/templates/newgem/test/minitest_helper.rb.tt +4 -0
  119. data/lib/carat/templates/newgem/test/test_newgem.rb.tt +11 -0
  120. data/lib/carat/ui.rb +7 -0
  121. data/lib/carat/ui/rg_proxy.rb +21 -0
  122. data/lib/carat/ui/shell.rb +103 -0
  123. data/lib/carat/ui/silent.rb +44 -0
  124. data/lib/carat/vendor/molinillo/lib/molinillo.rb +5 -0
  125. data/lib/carat/vendor/molinillo/lib/molinillo/dependency_graph.rb +266 -0
  126. data/lib/carat/vendor/molinillo/lib/molinillo/errors.rb +69 -0
  127. data/lib/carat/vendor/molinillo/lib/molinillo/gem_metadata.rb +3 -0
  128. data/lib/carat/vendor/molinillo/lib/molinillo/modules/specification_provider.rb +90 -0
  129. data/lib/carat/vendor/molinillo/lib/molinillo/modules/ui.rb +63 -0
  130. data/lib/carat/vendor/molinillo/lib/molinillo/resolution.rb +415 -0
  131. data/lib/carat/vendor/molinillo/lib/molinillo/resolver.rb +43 -0
  132. data/lib/carat/vendor/molinillo/lib/molinillo/state.rb +43 -0
  133. data/lib/carat/vendor/net/http/faster.rb +26 -0
  134. data/lib/carat/vendor/net/http/persistent.rb +1230 -0
  135. data/lib/carat/vendor/net/http/persistent/ssl_reuse.rb +128 -0
  136. data/lib/carat/vendor/thor/lib/thor.rb +484 -0
  137. data/lib/carat/vendor/thor/lib/thor/actions.rb +319 -0
  138. data/lib/carat/vendor/thor/lib/thor/actions/create_file.rb +103 -0
  139. data/lib/carat/vendor/thor/lib/thor/actions/create_link.rb +59 -0
  140. data/lib/carat/vendor/thor/lib/thor/actions/directory.rb +118 -0
  141. data/lib/carat/vendor/thor/lib/thor/actions/empty_directory.rb +135 -0
  142. data/lib/carat/vendor/thor/lib/thor/actions/file_manipulation.rb +316 -0
  143. data/lib/carat/vendor/thor/lib/thor/actions/inject_into_file.rb +107 -0
  144. data/lib/carat/vendor/thor/lib/thor/base.rb +656 -0
  145. data/lib/carat/vendor/thor/lib/thor/command.rb +133 -0
  146. data/lib/carat/vendor/thor/lib/thor/core_ext/hash_with_indifferent_access.rb +77 -0
  147. data/lib/carat/vendor/thor/lib/thor/core_ext/io_binary_read.rb +10 -0
  148. data/lib/carat/vendor/thor/lib/thor/core_ext/ordered_hash.rb +98 -0
  149. data/lib/carat/vendor/thor/lib/thor/error.rb +32 -0
  150. data/lib/carat/vendor/thor/lib/thor/group.rb +281 -0
  151. data/lib/carat/vendor/thor/lib/thor/invocation.rb +178 -0
  152. data/lib/carat/vendor/thor/lib/thor/line_editor.rb +17 -0
  153. data/lib/carat/vendor/thor/lib/thor/line_editor/basic.rb +35 -0
  154. data/lib/carat/vendor/thor/lib/thor/line_editor/readline.rb +88 -0
  155. data/lib/carat/vendor/thor/lib/thor/parser.rb +4 -0
  156. data/lib/carat/vendor/thor/lib/thor/parser/argument.rb +73 -0
  157. data/lib/carat/vendor/thor/lib/thor/parser/arguments.rb +175 -0
  158. data/lib/carat/vendor/thor/lib/thor/parser/option.rb +125 -0
  159. data/lib/carat/vendor/thor/lib/thor/parser/options.rb +218 -0
  160. data/lib/carat/vendor/thor/lib/thor/rake_compat.rb +71 -0
  161. data/lib/carat/vendor/thor/lib/thor/runner.rb +322 -0
  162. data/lib/carat/vendor/thor/lib/thor/shell.rb +81 -0
  163. data/lib/carat/vendor/thor/lib/thor/shell/basic.rb +421 -0
  164. data/lib/carat/vendor/thor/lib/thor/shell/color.rb +149 -0
  165. data/lib/carat/vendor/thor/lib/thor/shell/html.rb +126 -0
  166. data/lib/carat/vendor/thor/lib/thor/util.rb +267 -0
  167. data/lib/carat/vendor/thor/lib/thor/version.rb +3 -0
  168. data/lib/carat/vendored_fileutils.rb +9 -0
  169. data/lib/carat/vendored_molinillo.rb +2 -0
  170. data/lib/carat/vendored_persistent.rb +11 -0
  171. data/lib/carat/vendored_thor.rb +3 -0
  172. data/lib/carat/version.rb +6 -0
  173. data/lib/carat/vlad.rb +11 -0
  174. data/lib/carat/worker.rb +73 -0
  175. data/man/carat-config.ronn +178 -0
  176. data/man/carat-exec.ronn +136 -0
  177. data/man/carat-install.ronn +383 -0
  178. data/man/carat-package.ronn +66 -0
  179. data/man/carat-platform.ronn +42 -0
  180. data/man/carat-update.ronn +188 -0
  181. data/man/carat.ronn +98 -0
  182. data/man/gemfile.5.ronn +473 -0
  183. data/man/index.txt +7 -0
  184. metadata +321 -0
@@ -0,0 +1,76 @@
1
+ module Carat
2
+ # used for Creating Specifications from the Gemcutter Endpoint
3
+ class EndpointSpecification < Gem::Specification
4
+ include MatchPlatform
5
+
6
+ attr_reader :name, :version, :platform, :dependencies
7
+ attr_accessor :source, :source_uri
8
+
9
+ def initialize(name, version, platform, dependencies)
10
+ @name = name
11
+ @version = version
12
+ @platform = platform
13
+ @dependencies = dependencies
14
+ end
15
+
16
+ def fetch_platform
17
+ @platform
18
+ end
19
+
20
+ # needed for standalone, load required_paths from local gemspec
21
+ # after the gem is installed
22
+ def require_paths
23
+ if @remote_specification
24
+ @remote_specification.require_paths
25
+ elsif _local_specification
26
+ _local_specification.require_paths
27
+ else
28
+ super
29
+ end
30
+ end
31
+
32
+ # needed for binstubs
33
+ def executables
34
+ if @remote_specification
35
+ @remote_specification.executables
36
+ elsif _local_specification
37
+ _local_specification.executables
38
+ else
39
+ super
40
+ end
41
+ end
42
+
43
+ # needed for carat clean
44
+ def bindir
45
+ if @remote_specification
46
+ @remote_specification.bindir
47
+ elsif _local_specification
48
+ _local_specification.bindir
49
+ else
50
+ super
51
+ end
52
+ end
53
+
54
+ # needed for post_install_messages during install
55
+ def post_install_message
56
+ if @remote_specification
57
+ @remote_specification.post_install_message
58
+ elsif _local_specification
59
+ _local_specification.post_install_message
60
+ end
61
+ end
62
+
63
+ def _local_specification
64
+ eval(File.read(local_specification_path)) if @loaded_from && File.exist?(local_specification_path)
65
+ end
66
+
67
+ def __swap__(spec)
68
+ @remote_specification = spec
69
+ end
70
+
71
+ private
72
+ def local_specification_path
73
+ "#{base_dir}/specifications/#{full_name}.gemspec"
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,75 @@
1
+ require 'carat/rubygems_integration'
2
+ require 'carat/source/git/git_proxy'
3
+
4
+ module Carat
5
+ class Env
6
+
7
+ def write(io)
8
+ io.write report(:print_gemfile => true)
9
+ end
10
+
11
+ def report(options = {})
12
+ print_gemfile = options.delete(:print_gemfile)
13
+
14
+ out = "Environment\n\n"
15
+ out << " Carat #{Carat::VERSION}\n"
16
+ out << " Rubygems #{Gem::VERSION}\n"
17
+ out << " Ruby #{ruby_version}"
18
+ out << " GEM_HOME #{ENV['GEM_HOME']}\n" unless ENV['GEM_HOME'].nil? || ENV['GEM_HOME'].empty?
19
+ out << " GEM_PATH #{ENV['GEM_PATH']}\n" unless ENV['GEM_PATH'] == ENV['GEM_HOME']
20
+ out << " RVM #{ENV['rvm_version']}\n" if ENV['rvm_version']
21
+ out << " Git #{git_version}\n"
22
+ %w(rubygems-carat open_gem).each do |name|
23
+ specs = Carat.rubygems.find_name(name)
24
+ out << " #{name} (#{specs.map(&:version).join(',')})\n" unless specs.empty?
25
+ end
26
+
27
+ out << "\nCarat settings\n\n" unless Carat.settings.all.empty?
28
+ Carat.settings.all.each do |setting|
29
+ out << " " << setting << "\n"
30
+ Carat.settings.pretty_values_for(setting).each do |line|
31
+ out << " " << line << "\n"
32
+ end
33
+ end
34
+
35
+ if print_gemfile
36
+ out << "\nGemfile\n\n"
37
+ out << " " << read_file(Carat.default_gemfile).gsub(/\n/, "\n ") << "\n"
38
+
39
+ out << "\n" << "Gemfile.lock\n\n"
40
+ out << " " << read_file(Carat.default_lockfile).gsub(/\n/, "\n ") << "\n"
41
+ end
42
+
43
+ out
44
+ end
45
+
46
+ private
47
+
48
+ def read_file(filename)
49
+ File.read(filename.to_s).strip
50
+ rescue Errno::ENOENT
51
+ "<No #{filename} found>"
52
+ rescue => e
53
+ "#{e.class}: #{e.message}"
54
+ end
55
+
56
+ def ruby_version
57
+ str = "#{RUBY_VERSION}"
58
+ if RUBY_VERSION < '1.9'
59
+ str << " (#{RUBY_RELEASE_DATE}"
60
+ str << " patchlevel #{RUBY_PATCHLEVEL}" if defined? RUBY_PATCHLEVEL
61
+ str << ") [#{RUBY_PLATFORM}]\n"
62
+ else
63
+ str << "p#{RUBY_PATCHLEVEL}" if defined? RUBY_PATCHLEVEL
64
+ str << " (#{RUBY_RELEASE_DATE} revision #{RUBY_REVISION}) [#{RUBY_PLATFORM}]\n"
65
+ end
66
+ end
67
+
68
+ def git_version
69
+ Carat::Source::Git::GitProxy.new(nil, nil, nil).version
70
+ rescue Carat::Source::Git::GitNotInstalledError
71
+ "not installed"
72
+ end
73
+
74
+ end
75
+ end
@@ -0,0 +1,42 @@
1
+ module Carat
2
+ class Environment
3
+ attr_reader :root
4
+
5
+ def initialize(root, definition)
6
+ @root = root
7
+ @definition = definition
8
+
9
+ env_file = Carat.app_config_path.join('environment.rb')
10
+ env_file.rmtree if env_file.exist?
11
+ end
12
+
13
+ def inspect
14
+ @definition.to_lock.inspect
15
+ end
16
+
17
+ def requested_specs
18
+ @definition.requested_specs
19
+ end
20
+
21
+ def specs
22
+ @definition.specs
23
+ end
24
+
25
+ def dependencies
26
+ @definition.dependencies
27
+ end
28
+
29
+ def current_dependencies
30
+ @definition.current_dependencies
31
+ end
32
+
33
+ def lock
34
+ @definition.lock(Carat.default_lockfile)
35
+ end
36
+
37
+ def update(*gems)
38
+ # Nothing
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,423 @@
1
+ require 'carat/vendored_persistent'
2
+ require 'securerandom'
3
+ require 'cgi'
4
+
5
+ module Carat
6
+
7
+ # Handles all the fetching with the rubygems server
8
+ class Fetcher
9
+ # This error is raised when it looks like the network is down
10
+ class NetworkDownError < HTTPError; end
11
+ # This error is raised if the API returns a 413 (only printed in verbose)
12
+ class FallbackError < HTTPError; end
13
+ # This is the error raised if OpenSSL fails the cert verification
14
+ class CertificateFailureError < HTTPError
15
+ def initialize(remote_uri)
16
+ super "Could not verify the SSL certificate for #{remote_uri}.\nThere" \
17
+ " is a chance you are experiencing a man-in-the-middle attack, but" \
18
+ " most likely your system doesn't have the CA certificates needed" \
19
+ " for verification. For information about OpenSSL certificates, see" \
20
+ " bit.ly/ruby-ssl. To connect without using SSL, edit your Gemfile" \
21
+ " sources and change 'https' to 'http'."
22
+ end
23
+ end
24
+ # This is the error raised when a source is HTTPS and OpenSSL didn't load
25
+ class SSLError < HTTPError
26
+ def initialize(msg = nil)
27
+ super msg || "Could not load OpenSSL.\n" \
28
+ "You must recompile Ruby with OpenSSL support or change the sources in your " \
29
+ "Gemfile from 'https' to 'http'. Instructions for compiling with OpenSSL " \
30
+ "using RVM are available at rvm.io/packages/openssl."
31
+ end
32
+ end
33
+ # This error is raised if HTTP authentication is required, but not provided.
34
+ class AuthenticationRequiredError < HTTPError
35
+ def initialize(remote_uri)
36
+ super "Authentication is required for #{remote_uri}.\n" \
37
+ "Please supply credentials for this source. You can do this by running:\n" \
38
+ " carat config #{remote_uri} username:password"
39
+ end
40
+ end
41
+ # This error is raised if HTTP authentication is provided, but incorrect.
42
+ class BadAuthenticationError < HTTPError
43
+ def initialize(remote_uri)
44
+ super "Bad username or password for #{remote_uri}.\n" \
45
+ "Please double-check your credentials and correct them."
46
+ end
47
+ end
48
+
49
+ # Exceptions classes that should bypass retry attempts. If your password didn't work the
50
+ # first time, it's not going to the third time.
51
+ AUTH_ERRORS = [AuthenticationRequiredError, BadAuthenticationError]
52
+
53
+ class << self
54
+ attr_accessor :disable_endpoint, :api_timeout, :redirect_limit, :max_retries
55
+
56
+ def download_gem_from_uri(spec, uri)
57
+ spec.fetch_platform
58
+
59
+ download_path = Carat.requires_sudo? ? Carat.tmp(spec.full_name) : Carat.rubygems.gem_dir
60
+ gem_path = "#{Carat.rubygems.gem_dir}/cache/#{spec.full_name}.gem"
61
+
62
+ FileUtils.mkdir_p("#{download_path}/cache")
63
+ Carat.rubygems.download_gem(spec, uri, download_path)
64
+
65
+ if Carat.requires_sudo?
66
+ Carat.mkdir_p "#{Carat.rubygems.gem_dir}/cache"
67
+ Carat.sudo "mv #{download_path}/cache/#{spec.full_name}.gem #{gem_path}"
68
+ end
69
+
70
+ gem_path
71
+ ensure
72
+ Carat.rm_rf(download_path) if Carat.requires_sudo?
73
+ end
74
+
75
+ def user_agent
76
+ @user_agent ||= begin
77
+ ruby = Carat.ruby_version
78
+
79
+ agent = "carat/#{Carat::VERSION}"
80
+ agent << " rubygems/#{Gem::VERSION}"
81
+ agent << " ruby/#{ruby.version}"
82
+ agent << " (#{ruby.host})"
83
+ agent << " command/#{ARGV.first}"
84
+
85
+ if ruby.engine != "ruby"
86
+ # engine_version raises on unknown engines
87
+ engine_version = ruby.engine_version rescue "???"
88
+ agent << " #{ruby.engine}/#{engine_version}"
89
+ end
90
+
91
+ agent << " options/#{Carat.settings.all.join(",")}"
92
+
93
+ # add a random ID so we can consolidate runs server-side
94
+ agent << " " << SecureRandom.hex(8)
95
+
96
+ # add any user agent strings set in the config
97
+ extra_ua = Carat.settings[:user_agent]
98
+ agent << " " << extra_ua if extra_ua
99
+
100
+ agent
101
+ end
102
+ end
103
+
104
+ end
105
+
106
+ def initialize(remote_uri)
107
+ @redirect_limit = 5 # How many redirects to allow in one request
108
+ @api_timeout = 10 # How long to wait for each API call
109
+ @max_retries = 3 # How many retries for the API call
110
+
111
+ @anonymizable_uri = configured_uri_for(remote_uri)
112
+
113
+ Socket.do_not_reverse_lookup = true
114
+ connection # create persistent connection
115
+ end
116
+
117
+ def connection
118
+ @connection ||= begin
119
+ needs_ssl = remote_uri.scheme == "https" ||
120
+ Carat.settings[:ssl_verify_mode] ||
121
+ Carat.settings[:ssl_client_cert]
122
+ raise SSLError if needs_ssl && !defined?(OpenSSL::SSL)
123
+
124
+ con = Net::HTTP::Persistent.new 'carat', :ENV
125
+
126
+ if remote_uri.scheme == "https"
127
+ con.verify_mode = (Carat.settings[:ssl_verify_mode] ||
128
+ OpenSSL::SSL::VERIFY_PEER)
129
+ con.cert_store = carat_cert_store
130
+ end
131
+
132
+ if Carat.settings[:ssl_client_cert]
133
+ pem = File.read(Carat.settings[:ssl_client_cert])
134
+ con.cert = OpenSSL::X509::Certificate.new(pem)
135
+ con.key = OpenSSL::PKey::RSA.new(pem)
136
+ end
137
+
138
+ con.read_timeout = @api_timeout
139
+ con.override_headers["User-Agent"] = self.class.user_agent
140
+ con
141
+ end
142
+ end
143
+
144
+ def uri
145
+ @anonymizable_uri.without_credentials
146
+ end
147
+
148
+ # fetch a gem specification
149
+ def fetch_spec(spec)
150
+ spec = spec - [nil, 'ruby', '']
151
+ spec_file_name = "#{spec.join '-'}.gemspec"
152
+
153
+ uri = URI.parse("#{remote_uri}#{Gem::MARSHAL_SPEC_DIR}#{spec_file_name}.rz")
154
+ if uri.scheme == 'file'
155
+ Carat.load_marshal Gem.inflate(Gem.read_binary(uri.path))
156
+ elsif cached_spec_path = gemspec_cached_path(spec_file_name)
157
+ Carat.load_gemspec(cached_spec_path)
158
+ else
159
+ Carat.load_marshal Gem.inflate(fetch(uri))
160
+ end
161
+ rescue MarshalError
162
+ raise HTTPError, "Gemspec #{spec} contained invalid data.\n" \
163
+ "Your network or your gem server is probably having issues right now."
164
+ end
165
+
166
+ # cached gem specification path, if one exists
167
+ def gemspec_cached_path spec_file_name
168
+ paths = Carat.rubygems.spec_cache_dirs.map { |dir| File.join(dir, spec_file_name) }
169
+ paths = paths.select {|path| File.file? path }
170
+ paths.first
171
+ end
172
+
173
+ # return the specs in the carat format as an index
174
+ def specs(gem_names, source)
175
+ old = Carat.rubygems.sources
176
+ index = Index.new
177
+
178
+ if gem_names && use_api
179
+ specs = fetch_remote_specs(gem_names)
180
+ end
181
+
182
+ if specs.nil?
183
+ # API errors mean we should treat this as a non-API source
184
+ @use_api = false
185
+
186
+ specs = Carat::Retry.new("source fetch", AUTH_ERRORS).attempts do
187
+ fetch_all_remote_specs
188
+ end
189
+ end
190
+
191
+ specs[remote_uri].each do |name, version, platform, dependencies|
192
+ next if name == 'carat'
193
+ spec = nil
194
+ if dependencies
195
+ spec = EndpointSpecification.new(name, version, platform, dependencies)
196
+ else
197
+ spec = RemoteSpecification.new(name, version, platform, self)
198
+ end
199
+ spec.source = source
200
+ spec.source_uri = @anonymizable_uri
201
+ index << spec
202
+ end
203
+
204
+ index
205
+ rescue CertificateFailureError => e
206
+ Carat.ui.info "" if gem_names && use_api # newline after dots
207
+ raise e
208
+ ensure
209
+ Carat.rubygems.sources = old
210
+ end
211
+
212
+ # fetch index
213
+ def fetch_remote_specs(gem_names, full_dependency_list = [], last_spec_list = [])
214
+ query_list = gem_names - full_dependency_list
215
+
216
+ # only display the message on the first run
217
+ if Carat.ui.debug?
218
+ Carat.ui.debug "Query List: #{query_list.inspect}"
219
+ else
220
+ Carat.ui.info ".", false
221
+ end
222
+
223
+ return {remote_uri => last_spec_list} if query_list.empty?
224
+
225
+ remote_specs = Carat::Retry.new("dependency api", AUTH_ERRORS).attempts do
226
+ fetch_dependency_remote_specs(query_list)
227
+ end
228
+
229
+ spec_list, deps_list = remote_specs
230
+ returned_gems = spec_list.map {|spec| spec.first }.uniq
231
+ fetch_remote_specs(deps_list, full_dependency_list + returned_gems, spec_list + last_spec_list)
232
+ rescue HTTPError, MarshalError, GemspecError
233
+ Carat.ui.info "" unless Carat.ui.debug? # new line now that the dots are over
234
+ Carat.ui.debug "could not fetch from the dependency API, trying the full index"
235
+ @use_api = false
236
+ return nil
237
+ end
238
+
239
+ def use_api
240
+ return @use_api if defined?(@use_api)
241
+
242
+ if remote_uri.scheme == "file" || Carat::Fetcher.disable_endpoint
243
+ @use_api = false
244
+ elsif fetch(dependency_api_uri)
245
+ @use_api = true
246
+ end
247
+ rescue NetworkDownError => e
248
+ raise HTTPError, e.message
249
+ rescue AuthenticationRequiredError
250
+ # We got a 401 from the server. Don't fall back to the full index, just fail.
251
+ raise
252
+ rescue HTTPError
253
+ @use_api = false
254
+ end
255
+
256
+ def inspect
257
+ "#<#{self.class}:0x#{object_id} uri=#{uri}>"
258
+ end
259
+
260
+ private
261
+
262
+ HTTP_ERRORS = [
263
+ Timeout::Error, EOFError, SocketError, Errno::ENETDOWN,
264
+ Errno::EINVAL, Errno::ECONNRESET, Errno::ETIMEDOUT, Errno::EAGAIN,
265
+ Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError,
266
+ Net::HTTP::Persistent::Error
267
+ ]
268
+
269
+ def fetch(uri, counter = 0)
270
+ raise HTTPError, "Too many redirects" if counter >= @redirect_limit
271
+
272
+ response = request(uri)
273
+ Carat.ui.debug("HTTP #{response.code} #{response.message}")
274
+
275
+ case response
276
+ when Net::HTTPRedirection
277
+ new_uri = URI.parse(response["location"])
278
+ if new_uri.host == uri.host
279
+ new_uri.user = uri.user
280
+ new_uri.password = uri.password
281
+ end
282
+ fetch(new_uri, counter + 1)
283
+ when Net::HTTPSuccess
284
+ response.body
285
+ when Net::HTTPRequestEntityTooLarge
286
+ raise FallbackError, response.body
287
+ when Net::HTTPUnauthorized
288
+ raise AuthenticationRequiredError, remote_uri.host
289
+ else
290
+ raise HTTPError, "#{response.class}: #{response.body}"
291
+ end
292
+ end
293
+
294
+ def request(uri)
295
+ Carat.ui.debug "HTTP GET #{uri}"
296
+ req = Net::HTTP::Get.new uri.request_uri
297
+ if uri.user
298
+ user = CGI.unescape(uri.user)
299
+ password = uri.password ? CGI.unescape(uri.password) : nil
300
+ req.basic_auth(user, password)
301
+ end
302
+ connection.request(uri, req)
303
+ rescue OpenSSL::SSL::SSLError
304
+ raise CertificateFailureError.new(uri)
305
+ rescue *HTTP_ERRORS => e
306
+ Carat.ui.trace e
307
+ case e.message
308
+ when /host down:/, /getaddrinfo: nodename nor servname provided/
309
+ raise NetworkDownError, "Could not reach host #{uri.host}. Check your network " \
310
+ "connection and try again."
311
+ else
312
+ raise HTTPError, "Network error while fetching #{uri}"
313
+ end
314
+ end
315
+
316
+ def dependency_api_uri(gem_names = [])
317
+ uri = fetch_uri + "api/v1/dependencies"
318
+ uri.query = "gems=#{URI.encode(gem_names.join(","))}" if gem_names.any?
319
+ uri
320
+ end
321
+
322
+ # fetch from Gemcutter Dependency Endpoint API
323
+ def fetch_dependency_remote_specs(gem_names)
324
+ Carat.ui.debug "Query Gemcutter Dependency Endpoint API: #{gem_names.join(',')}"
325
+ gem_list = []
326
+ deps_list = []
327
+
328
+ gem_names.each_slice(Source::Rubygems::API_REQUEST_SIZE) do |names|
329
+ marshalled_deps = fetch dependency_api_uri(names)
330
+ gem_list += Carat.load_marshal(marshalled_deps)
331
+ end
332
+
333
+ spec_list = gem_list.map do |s|
334
+ dependencies = s[:dependencies].map do |name, requirement|
335
+ dep = well_formed_dependency(name, requirement.split(", "))
336
+ deps_list << dep.name
337
+ dep
338
+ end
339
+
340
+ [s[:name], Gem::Version.new(s[:number]), s[:platform], dependencies]
341
+ end
342
+
343
+ [spec_list, deps_list.uniq]
344
+ end
345
+
346
+ # fetch from modern index: specs.4.8.gz
347
+ def fetch_all_remote_specs
348
+ old_sources = Carat.rubygems.sources
349
+ Carat.rubygems.sources = [remote_uri.to_s]
350
+ Carat.rubygems.fetch_all_remote_specs
351
+ rescue Gem::RemoteFetcher::FetchError, OpenSSL::SSL::SSLError => e
352
+ case e.message
353
+ when /certificate verify failed/
354
+ raise CertificateFailureError.new(uri)
355
+ when /401/
356
+ raise AuthenticationRequiredError, remote_uri
357
+ when /403/
358
+ if remote_uri.userinfo
359
+ raise BadAuthenticationError, remote_uri
360
+ else
361
+ raise AuthenticationRequiredError, remote_uri
362
+ end
363
+ else
364
+ Carat.ui.trace e
365
+ raise HTTPError, "Could not fetch specs from #{uri}"
366
+ end
367
+ ensure
368
+ Carat.rubygems.sources = old_sources
369
+ end
370
+
371
+ def well_formed_dependency(name, *requirements)
372
+ Gem::Dependency.new(name, *requirements)
373
+ rescue ArgumentError => e
374
+ illformed = 'Ill-formed requirement ["#<YAML::Syck::DefaultKey'
375
+ raise e unless e.message.include?(illformed)
376
+ puts # we shouldn't print the error message on the "fetching info" status line
377
+ raise GemspecError,
378
+ "Unfortunately, the gem #{s[:name]} (#{s[:number]}) has an invalid " \
379
+ "gemspec. \nPlease ask the gem author to yank the bad version to fix " \
380
+ "this issue. For more information, see http://bit.ly/syck-defaultkey."
381
+ end
382
+
383
+ def carat_cert_store
384
+ store = OpenSSL::X509::Store.new
385
+ if Carat.settings[:ssl_ca_cert]
386
+ if File.directory? Carat.settings[:ssl_ca_cert]
387
+ store.add_path Carat.settings[:ssl_ca_cert]
388
+ else
389
+ store.add_file Carat.settings[:ssl_ca_cert]
390
+ end
391
+ else
392
+ store.set_default_paths
393
+ certs = File.expand_path("../ssl_certs/*.pem", __FILE__)
394
+ Dir.glob(certs).each { |c| store.add_file c }
395
+ end
396
+ store
397
+ end
398
+
399
+ private
400
+
401
+ def configured_uri_for(uri)
402
+ uri = Carat::Source.mirror_for(uri)
403
+ config_auth = Carat.settings[uri.to_s] || Carat.settings[uri.host]
404
+ AnonymizableURI.new(uri, config_auth)
405
+ end
406
+
407
+ def fetch_uri
408
+ @fetch_uri ||= begin
409
+ if remote_uri.host == "rubygems.org"
410
+ uri = remote_uri.dup
411
+ uri.host = "bundler.rubygems.org"
412
+ uri
413
+ else
414
+ remote_uri
415
+ end
416
+ end
417
+ end
418
+
419
+ def remote_uri
420
+ @anonymizable_uri.original_uri
421
+ end
422
+ end
423
+ end