Skip to content
This repository was archived by the owner on Feb 7, 2024. It is now read-only.

Commit 9f96040

Browse files
beechtomglennsarti
authored andcommitted
Add support for HTTP proxies
This adds a few new configuration options to the searchers - `git.proxy`: URL for the proxy to Git based requests - `forge.proxy`: URL for the proxy to Forge requests This allows users to use HTTP proxies when making requests to GitHub and the Forge. Note that the HTTPS_PROXY and HTTP_PROXY environment variables are still viable options to set a HTTTP/S proxy.
1 parent 241a07f commit 9f96040

File tree

8 files changed

+160
-15
lines changed

8 files changed

+160
-15
lines changed

Diff for: lib/puppetfile-resolver/spec_searchers/forge.rb

+10-12
Original file line numberDiff line numberDiff line change
@@ -44,19 +44,17 @@ def self.fetch_all_module_releases(owner, name, resolver_ui, config, &block)
4444
loops = 0
4545
loop do
4646
resolver_ui.debug { "Querying the forge for a module with #{uri}" }
47-
48-
http_options = { :use_ssl => uri.class == URI::HTTPS }
49-
# Because on Windows Ruby doesn't use the Windows certificate store which has up-to date
50-
# CA certs, we can't depend on someone setting the environment variable correctly. So use our
51-
# static CA PEM file if SSL_CERT_FILE is not set.
52-
http_options[:ca_file] = PuppetfileResolver::Util.static_ca_cert_file if ENV['SSL_CERT_FILE'].nil?
53-
47+
err_msg = "Unable to find module #{owner}-#{name} on #{config.forge_api}"
48+
err_msg += config.proxy ? " with proxy #{config.proxy}: " : ': '
5449
response = nil
55-
Net::HTTP.start(uri.host, uri.port, http_options) do |http|
56-
request = Net::HTTP::Get.new uri
57-
response = http.request request
50+
51+
begin
52+
response = ::PuppetfileResolver::Util.net_http_get(uri, config.proxy)
53+
rescue ::StandardError => e
54+
raise err_msg + e.message
5855
end
59-
raise "Expected HTTP Code 200, but received #{response.code} for URI #{uri}: #{response.inspect}" unless response.code == '200'
56+
57+
raise err_msg + "Expected HTTP Code 200, but received #{response.code}" unless response.code == '200'
6058

6159
reply = ::JSON.parse(response.body)
6260
yield reply['results']
@@ -66,7 +64,7 @@ def self.fetch_all_module_releases(owner, name, resolver_ui, config, &block)
6664

6765
# Circuit breaker in case the worst happens (max 1000 module releases)
6866
loops += 1
69-
raise "Too many Forge API requests #{loops * 50}" if loops > 20
67+
raise err_msg + "Too many Forge API requests #{loops}" if loops > 20
7068
end
7169
end
7270
private_class_method :fetch_all_module_releases

Diff for: lib/puppetfile-resolver/spec_searchers/forge_configuration.rb

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ def forge_api
1010
end
1111

1212
attr_writer :forge_api
13+
14+
attr_accessor :proxy
1315
end
1416
end
1517
end

Diff for: lib/puppetfile-resolver/spec_searchers/git.rb

+14-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
# frozen_string_literal: true
22

3+
require 'puppetfile-resolver/util'
34
require 'puppetfile-resolver/spec_searchers/common'
45
require 'puppetfile-resolver/spec_searchers/git_configuration'
56

67
module PuppetfileResolver
78
module SpecSearchers
89
module Git
9-
def self.find_all(puppetfile_module, dependency, cache, resolver_ui, _config)
10+
def self.find_all(puppetfile_module, dependency, cache, resolver_ui, config)
1011
dep_id = ::PuppetfileResolver::SpecSearchers::Common.dependency_cache_id(self, dependency)
1112
# Has the information been cached?
1213
return cache.load(dep_id) if cache.exist?(dep_id)
@@ -40,9 +41,19 @@ def self.find_all(puppetfile_module, dependency, cache, resolver_ui, _config)
4041
require 'net/http'
4142
require 'uri'
4243

43-
resolver_ui.debug { "Querying the Github with #{metadata_url}" }
44-
response = Net::HTTP.get_response(URI.parse(metadata_url))
44+
resolver_ui.debug { "Querying GitHub with #{metadata_url}" }
45+
err_msg = "Unable to find module at #{puppetfile_module.remote}"
46+
err_msg += config.proxy ? " with proxy #{config.proxy}: " : ': '
47+
response = nil
48+
49+
begin
50+
response = ::PuppetfileResolver::Util.net_http_get(metadata_url, config.proxy)
51+
rescue ::StandardError => e
52+
raise err_msg + e.message
53+
end
54+
4555
if response.code != '200'
56+
resolver_ui.debug(err_msg + "Expected HTTP Code 200, but received #{response.code}")
4657
cache.save(dep_id, [])
4758
return []
4859
end

Diff for: lib/puppetfile-resolver/spec_searchers/git_configuration.rb

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
module PuppetfileResolver
44
module SpecSearchers
55
class GitConfiguration
6+
attr_accessor :proxy
67
end
78
end
89
end

Diff for: lib/puppetfile-resolver/util.rb

+24
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,29 @@ def self.symbolise_object(object)
1919
def self.static_ca_cert_file
2020
@static_ca_cert_file ||= File.expand_path(File.join(__dir__, 'data', 'ruby_ca_certs.pem'))
2121
end
22+
23+
# Execute a HTTP/S GET query and return the response
24+
# @param [String, URI] uri The URI to request
25+
# @param [nil, String, URI] proxy The URI of the proxy server to use. Defaults to nil (No proxy server)
26+
# @return [Net::HTTPResponse] the response of the request
27+
def self.net_http_get(uri, proxy = nil)
28+
uri = URI.parse(uri) unless uri.is_a?(URI)
29+
30+
http_options = { :use_ssl => uri.class == URI::HTTPS }
31+
# Because on Windows Ruby doesn't use the Windows certificate store which has up-to date
32+
# CA certs, we can't depend on someone setting the environment variable correctly. So use our
33+
# static CA PEM file if SSL_CERT_FILE is not set.
34+
http_options[:ca_file] = PuppetfileResolver::Util.static_ca_cert_file if ENV['SSL_CERT_FILE'].nil?
35+
36+
start_args = [uri.host, uri.port]
37+
38+
unless proxy.nil?
39+
proxy = URI.parse(proxy) unless proxy.is_a?(URI)
40+
start_args.concat([proxy.host, proxy.port, proxy.user, proxy.password])
41+
end
42+
43+
Net::HTTP.start(*start_args, http_options) { |http| return http.request(Net::HTTP::Get.new(uri)) }
44+
nil
45+
end
2246
end
2347
end

Diff for: puppetfile-cli.rb

+9
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ def self.parse(options)
1818
cache_dir: nil,
1919
module_paths: [],
2020
path: nil,
21+
proxy: nil,
2122
puppet_version: nil,
2223
strict: false
2324
}
@@ -41,6 +42,10 @@ def self.parse(options)
4142
args[:debug] = true
4243
end
4344

45+
opts.on('--proxy=PROXY_URL', 'HTTP/S Proxy server to use. For example http://localhost:8888') do |proxy|
46+
args[:proxy] = proxy
47+
end
48+
4449
opts.on('-s', '--strict', 'Do not allow missing dependencies. Default false which marks dependencies as missing and does not raise an error.') do
4550
args[:strict] = true
4651
end
@@ -91,6 +96,10 @@ def self.parse(options)
9196

9297
config = PuppetfileResolver::SpecSearchers::Configuration.new
9398
config.local.puppet_module_paths = options[:module_paths]
99+
unless options[:proxy].nil?
100+
config.git.proxy = options[:proxy]
101+
config.forge.proxy = options[:proxy]
102+
end
94103

95104
opts = { cache: cache, ui: ui, spec_searcher_configuration: config, allow_missing_modules: !options[:strict] }
96105

Diff for: spec/fixtures/proxy.rb

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
require 'webrick'
2+
require 'webrick/httpproxy'
3+
4+
proxy = WEBrick::HTTPProxyServer.new Port: (ARGV[0].nil? ? 32768 : ARGV[0])
5+
6+
trap 'INT' do proxy.shutdown end
7+
trap 'TERM' do proxy.shutdown end
8+
9+
proxy.start

Diff for: spec/integration/proxy_spec.rb

+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
require 'spec_helper'
2+
require 'open3'
3+
require 'puppetfile-resolver/resolver'
4+
require 'puppetfile-resolver/puppetfile'
5+
6+
describe 'Proxy Tests' do
7+
let(:content) do <<-PUPFILE
8+
forge 'https://forge.puppet.com'
9+
10+
mod 'powershell',
11+
:git => 'https://github.com/puppetlabs/puppetlabs-powershell',
12+
:tag => 'v4.0.0'
13+
14+
mod 'puppetlabs/stdlib', '6.3.0'
15+
PUPFILE
16+
end
17+
let(:puppetfile) { ::PuppetfileResolver::Puppetfile::Parser::R10KEval.parse(content) }
18+
let(:resolver_config) do
19+
PuppetfileResolver::SpecSearchers::Configuration.new.tap do |obj|
20+
obj.git.proxy = 'http://localhost:32768'
21+
obj.forge.proxy = 'http://localhost:32768'
22+
end
23+
end
24+
25+
context 'with an invalid proxy server' do
26+
it 'should not resolve a complete Puppetfile' do
27+
resolver = PuppetfileResolver::Resolver.new(puppetfile)
28+
result = resolver.resolve({
29+
allow_missing_modules: true,
30+
spec_searcher_configuration: resolver_config,
31+
})
32+
33+
expect(result.specifications).to include('powershell')
34+
expect(result.specifications['powershell']).to be_a(PuppetfileResolver::Models::MissingModuleSpecification)
35+
36+
expect(result.specifications).to include('stdlib')
37+
expect(result.specifications['stdlib']).to be_a(PuppetfileResolver::Models::MissingModuleSpecification)
38+
end
39+
end
40+
41+
context 'with a valid proxy server' do
42+
def start_proxy_server(start_options = ['--timeout=5'])
43+
cmd = "ruby \"#{File.join(FIXTURES_DIR, 'proxy.rb')}\" 32768"
44+
45+
stdin, stdout, stderr, wait_thr = Open3.popen3(cmd)
46+
# Wait for the Proxy server to indicate it started
47+
line = nil
48+
begin
49+
line = stderr.readline
50+
end until line =~ /#start/
51+
stdout.close
52+
stdin.close
53+
[wait_thr, stderr]
54+
end
55+
56+
before(:each) do
57+
@server_thr, @server_pipe = start_proxy_server
58+
end
59+
60+
after(:each) do
61+
begin
62+
Process.kill("KILL", @server_thr[:pid])
63+
Process.wait(@server_thr[:pid])
64+
rescue
65+
# The server process may not exist and checking in a cross platform way in ruby is difficult
66+
# Instead just swallow any errors
67+
end
68+
69+
begin
70+
@server_pipe.close
71+
rescue
72+
# The server process may not exist and checking in a cross platform way in ruby is difficult
73+
# Instead just swallow any errors
74+
end
75+
end
76+
77+
it 'should resolve a complete Puppetfile' do
78+
resolver = PuppetfileResolver::Resolver.new(puppetfile)
79+
result = resolver.resolve({
80+
allow_missing_modules: false,
81+
spec_searcher_configuration: resolver_config,
82+
})
83+
84+
expect(result.specifications).to include('powershell')
85+
expect(result.specifications['powershell'].version.to_s).to eq('4.0.0')
86+
87+
expect(result.specifications).to include('stdlib')
88+
expect(result.specifications['stdlib'].version.to_s).to eq('6.3.0')
89+
end
90+
end
91+
end

0 commit comments

Comments
 (0)