carat 1.9.9.pre1
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/.gitignore +16 -0
- data/.rspec +3 -0
- data/.travis.yml +24 -0
- data/CHANGELOG.md +2006 -0
- data/CODE_OF_CONDUCT.md +40 -0
- data/CONTRIBUTING.md +23 -0
- data/DEVELOPMENT.md +119 -0
- data/ISSUES.md +96 -0
- data/LICENSE.md +23 -0
- data/README.md +32 -0
- data/Rakefile +308 -0
- data/bin/carat +21 -0
- data/bin/carat_ruby +56 -0
- data/carat.gemspec +32 -0
- data/lib/carat.rb +446 -0
- data/lib/carat/anonymizable_uri.rb +32 -0
- data/lib/carat/capistrano.rb +16 -0
- data/lib/carat/cli.rb +407 -0
- data/lib/carat/cli/binstubs.rb +38 -0
- data/lib/carat/cli/cache.rb +35 -0
- data/lib/carat/cli/check.rb +35 -0
- data/lib/carat/cli/clean.rb +26 -0
- data/lib/carat/cli/common.rb +56 -0
- data/lib/carat/cli/config.rb +84 -0
- data/lib/carat/cli/console.rb +38 -0
- data/lib/carat/cli/exec.rb +44 -0
- data/lib/carat/cli/gem.rb +195 -0
- data/lib/carat/cli/init.rb +33 -0
- data/lib/carat/cli/inject.rb +33 -0
- data/lib/carat/cli/install.rb +156 -0
- data/lib/carat/cli/open.rb +23 -0
- data/lib/carat/cli/outdated.rb +80 -0
- data/lib/carat/cli/package.rb +45 -0
- data/lib/carat/cli/platform.rb +43 -0
- data/lib/carat/cli/show.rb +74 -0
- data/lib/carat/cli/update.rb +73 -0
- data/lib/carat/cli/viz.rb +27 -0
- data/lib/carat/constants.rb +5 -0
- data/lib/carat/current_ruby.rb +183 -0
- data/lib/carat/definition.rb +628 -0
- data/lib/carat/dep_proxy.rb +43 -0
- data/lib/carat/dependency.rb +110 -0
- data/lib/carat/deployment.rb +59 -0
- data/lib/carat/deprecate.rb +15 -0
- data/lib/carat/dsl.rb +331 -0
- data/lib/carat/endpoint_specification.rb +76 -0
- data/lib/carat/env.rb +75 -0
- data/lib/carat/environment.rb +42 -0
- data/lib/carat/fetcher.rb +423 -0
- data/lib/carat/friendly_errors.rb +85 -0
- data/lib/carat/gem_helper.rb +180 -0
- data/lib/carat/gem_helpers.rb +26 -0
- data/lib/carat/gem_installer.rb +9 -0
- data/lib/carat/gem_path_manipulation.rb +8 -0
- data/lib/carat/gem_tasks.rb +2 -0
- data/lib/carat/graph.rb +169 -0
- data/lib/carat/index.rb +197 -0
- data/lib/carat/injector.rb +64 -0
- data/lib/carat/installer.rb +339 -0
- data/lib/carat/lazy_specification.rb +83 -0
- data/lib/carat/lockfile_parser.rb +167 -0
- data/lib/carat/match_platform.rb +13 -0
- data/lib/carat/psyched_yaml.rb +26 -0
- data/lib/carat/remote_specification.rb +57 -0
- data/lib/carat/resolver.rb +334 -0
- data/lib/carat/retry.rb +60 -0
- data/lib/carat/ruby_dsl.rb +11 -0
- data/lib/carat/ruby_version.rb +117 -0
- data/lib/carat/rubygems_ext.rb +170 -0
- data/lib/carat/rubygems_integration.rb +619 -0
- data/lib/carat/runtime.rb +289 -0
- data/lib/carat/settings.rb +208 -0
- data/lib/carat/setup.rb +24 -0
- data/lib/carat/shared_helpers.rb +149 -0
- data/lib/carat/similarity_detector.rb +63 -0
- data/lib/carat/source.rb +46 -0
- data/lib/carat/source/git.rb +294 -0
- data/lib/carat/source/git/git_proxy.rb +162 -0
- data/lib/carat/source/path.rb +226 -0
- data/lib/carat/source/path/installer.rb +43 -0
- data/lib/carat/source/rubygems.rb +381 -0
- data/lib/carat/source_list.rb +101 -0
- data/lib/carat/spec_set.rb +154 -0
- data/lib/carat/ssl_certs/.document +1 -0
- data/lib/carat/ssl_certs/AddTrustExternalCARoot-2048.pem +25 -0
- data/lib/carat/ssl_certs/AddTrustExternalCARoot.pem +32 -0
- data/lib/carat/ssl_certs/Class3PublicPrimaryCertificationAuthority.pem +14 -0
- data/lib/carat/ssl_certs/DigiCertHighAssuranceEVRootCA.pem +23 -0
- data/lib/carat/ssl_certs/EntrustnetSecureServerCertificationAuthority.pem +28 -0
- data/lib/carat/ssl_certs/GeoTrustGlobalCA.pem +20 -0
- data/lib/carat/ssl_certs/certificate_manager.rb +66 -0
- data/lib/carat/ssl_certs/index.rubygems.org/GlobalSignRootCA.pem +21 -0
- data/lib/carat/ssl_certs/rubygems.global.ssl.fastly.net/DigiCertHighAssuranceEVRootCA.pem +23 -0
- data/lib/carat/ssl_certs/rubygems.org/AddTrustExternalCARoot.pem +25 -0
- data/lib/carat/templates/Executable +16 -0
- data/lib/carat/templates/Executable.standalone +12 -0
- data/lib/carat/templates/Gemfile +4 -0
- data/lib/carat/templates/newgem/.travis.yml.tt +3 -0
- data/lib/carat/templates/newgem/CODE_OF_CONDUCT.md.tt +13 -0
- data/lib/carat/templates/newgem/Gemfile.tt +4 -0
- data/lib/carat/templates/newgem/LICENSE.txt.tt +21 -0
- data/lib/carat/templates/newgem/README.md.tt +39 -0
- data/lib/carat/templates/newgem/Rakefile.tt +25 -0
- data/lib/carat/templates/newgem/bin/console.tt +14 -0
- data/lib/carat/templates/newgem/bin/setup.tt +7 -0
- data/lib/carat/templates/newgem/exe/newgem.tt +3 -0
- data/lib/carat/templates/newgem/ext/newgem/extconf.rb.tt +3 -0
- data/lib/carat/templates/newgem/ext/newgem/newgem.c.tt +9 -0
- data/lib/carat/templates/newgem/ext/newgem/newgem.h.tt +6 -0
- data/lib/carat/templates/newgem/gitignore.tt +16 -0
- data/lib/carat/templates/newgem/lib/newgem.rb.tt +12 -0
- data/lib/carat/templates/newgem/lib/newgem/version.rb.tt +7 -0
- data/lib/carat/templates/newgem/newgem.gemspec.tt +43 -0
- data/lib/carat/templates/newgem/rspec.tt +2 -0
- data/lib/carat/templates/newgem/spec/newgem_spec.rb.tt +11 -0
- data/lib/carat/templates/newgem/spec/spec_helper.rb.tt +2 -0
- data/lib/carat/templates/newgem/test/minitest_helper.rb.tt +4 -0
- data/lib/carat/templates/newgem/test/test_newgem.rb.tt +11 -0
- data/lib/carat/ui.rb +7 -0
- data/lib/carat/ui/rg_proxy.rb +21 -0
- data/lib/carat/ui/shell.rb +103 -0
- data/lib/carat/ui/silent.rb +44 -0
- data/lib/carat/vendor/molinillo/lib/molinillo.rb +5 -0
- data/lib/carat/vendor/molinillo/lib/molinillo/dependency_graph.rb +266 -0
- data/lib/carat/vendor/molinillo/lib/molinillo/errors.rb +69 -0
- data/lib/carat/vendor/molinillo/lib/molinillo/gem_metadata.rb +3 -0
- data/lib/carat/vendor/molinillo/lib/molinillo/modules/specification_provider.rb +90 -0
- data/lib/carat/vendor/molinillo/lib/molinillo/modules/ui.rb +63 -0
- data/lib/carat/vendor/molinillo/lib/molinillo/resolution.rb +415 -0
- data/lib/carat/vendor/molinillo/lib/molinillo/resolver.rb +43 -0
- data/lib/carat/vendor/molinillo/lib/molinillo/state.rb +43 -0
- data/lib/carat/vendor/net/http/faster.rb +26 -0
- data/lib/carat/vendor/net/http/persistent.rb +1230 -0
- data/lib/carat/vendor/net/http/persistent/ssl_reuse.rb +128 -0
- data/lib/carat/vendor/thor/lib/thor.rb +484 -0
- data/lib/carat/vendor/thor/lib/thor/actions.rb +319 -0
- data/lib/carat/vendor/thor/lib/thor/actions/create_file.rb +103 -0
- data/lib/carat/vendor/thor/lib/thor/actions/create_link.rb +59 -0
- data/lib/carat/vendor/thor/lib/thor/actions/directory.rb +118 -0
- data/lib/carat/vendor/thor/lib/thor/actions/empty_directory.rb +135 -0
- data/lib/carat/vendor/thor/lib/thor/actions/file_manipulation.rb +316 -0
- data/lib/carat/vendor/thor/lib/thor/actions/inject_into_file.rb +107 -0
- data/lib/carat/vendor/thor/lib/thor/base.rb +656 -0
- data/lib/carat/vendor/thor/lib/thor/command.rb +133 -0
- data/lib/carat/vendor/thor/lib/thor/core_ext/hash_with_indifferent_access.rb +77 -0
- data/lib/carat/vendor/thor/lib/thor/core_ext/io_binary_read.rb +10 -0
- data/lib/carat/vendor/thor/lib/thor/core_ext/ordered_hash.rb +98 -0
- data/lib/carat/vendor/thor/lib/thor/error.rb +32 -0
- data/lib/carat/vendor/thor/lib/thor/group.rb +281 -0
- data/lib/carat/vendor/thor/lib/thor/invocation.rb +178 -0
- data/lib/carat/vendor/thor/lib/thor/line_editor.rb +17 -0
- data/lib/carat/vendor/thor/lib/thor/line_editor/basic.rb +35 -0
- data/lib/carat/vendor/thor/lib/thor/line_editor/readline.rb +88 -0
- data/lib/carat/vendor/thor/lib/thor/parser.rb +4 -0
- data/lib/carat/vendor/thor/lib/thor/parser/argument.rb +73 -0
- data/lib/carat/vendor/thor/lib/thor/parser/arguments.rb +175 -0
- data/lib/carat/vendor/thor/lib/thor/parser/option.rb +125 -0
- data/lib/carat/vendor/thor/lib/thor/parser/options.rb +218 -0
- data/lib/carat/vendor/thor/lib/thor/rake_compat.rb +71 -0
- data/lib/carat/vendor/thor/lib/thor/runner.rb +322 -0
- data/lib/carat/vendor/thor/lib/thor/shell.rb +81 -0
- data/lib/carat/vendor/thor/lib/thor/shell/basic.rb +421 -0
- data/lib/carat/vendor/thor/lib/thor/shell/color.rb +149 -0
- data/lib/carat/vendor/thor/lib/thor/shell/html.rb +126 -0
- data/lib/carat/vendor/thor/lib/thor/util.rb +267 -0
- data/lib/carat/vendor/thor/lib/thor/version.rb +3 -0
- data/lib/carat/vendored_fileutils.rb +9 -0
- data/lib/carat/vendored_molinillo.rb +2 -0
- data/lib/carat/vendored_persistent.rb +11 -0
- data/lib/carat/vendored_thor.rb +3 -0
- data/lib/carat/version.rb +6 -0
- data/lib/carat/vlad.rb +11 -0
- data/lib/carat/worker.rb +73 -0
- data/man/carat-config.ronn +178 -0
- data/man/carat-exec.ronn +136 -0
- data/man/carat-install.ronn +383 -0
- data/man/carat-package.ronn +66 -0
- data/man/carat-platform.ronn +42 -0
- data/man/carat-update.ronn +188 -0
- data/man/carat.ronn +98 -0
- data/man/gemfile.5.ronn +473 -0
- data/man/index.txt +7 -0
- 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
|
data/lib/carat/env.rb
ADDED
|
@@ -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
|