diff --git a/Gemfile b/Gemfile index f4760c3..8b3af2e 100644 --- a/Gemfile +++ b/Gemfile @@ -4,7 +4,8 @@ source 'https://rubygems.org' gemspec group :development do + gem 'rake' + gem 'rspec', '~> 2.11' gem 'vagrant', github: 'mitchellh/vagrant' gem 'vagrant-lxc', github: 'fgrehm/vagrant-lxc' - gem 'rake' end diff --git a/Gemfile.lock b/Gemfile.lock index 630680f..789071d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -27,6 +27,7 @@ GEM specs: childprocess (0.3.9) ffi (~> 1.0, >= 1.0.11) + diff-lcs (1.2.4) erubis (2.7.0) ffi (1.8.1) i18n (0.6.4) @@ -36,12 +37,21 @@ GEM net-ssh (>= 2.6.5) net-ssh (2.6.7) rake (10.0.4) + rspec (2.13.0) + rspec-core (~> 2.13.0) + rspec-expectations (~> 2.13.0) + rspec-mocks (~> 2.13.0) + rspec-core (2.13.1) + rspec-expectations (2.13.0) + diff-lcs (>= 1.1.3, < 2.0) + rspec-mocks (2.13.1) PLATFORMS ruby DEPENDENCIES rake + rspec (~> 2.11) vagrant! vagrant-cachier! vagrant-lxc! diff --git a/Rakefile b/Rakefile index 09df0ec..3004b1d 100644 --- a/Rakefile +++ b/Rakefile @@ -1,5 +1,11 @@ -Dir['./tasks/**/*.rake'].each { |f| load f } - require 'bundler/gem_tasks' +require 'rspec/core/rake_task' -task :ci => ['spec:unit'] +task :default => [:spec] + +namespace :spec do + RSpec::Core::RakeTask.new('unit') do |t| + t.pattern = 'spec/unit/**/*_spec.rb' + end +end +task :spec => ['spec:unit'] diff --git a/lib/vagrant-cachier/action/configure_apt_proxy.rb b/lib/vagrant-cachier/action/configure_apt_proxy.rb new file mode 100644 index 0000000..4461566 --- /dev/null +++ b/lib/vagrant-cachier/action/configure_apt_proxy.rb @@ -0,0 +1,53 @@ +require 'tempfile' + +module VagrantPlugins + module Cachier + class Action + class ConfigureAptProxy + attr_reader :logger + + def initialize(app, env) + @app = app + @logger = Log4r::Logger.new("vagrant::cachier::action::configure_apt_proxy") + end + + def call(env) + @app.call env + + proxy_config = env[:machine].config.apt_proxy + if !proxy_config.enabled? + logger.debug "apt_proxy not enabled or configured" + elsif !proxy_conf_capability?(env[:machine]) + env[:ui].info "Skipping Apt proxy config as the machine does not support it" + else + env[:ui].info "Configuring proxy for Apt..." + write_apt_proxy_conf(env[:machine], proxy_config) + end + end + + def write_apt_proxy_conf(machine, config) + logger.debug "Configuration:\n#{config}" + + temp = Tempfile.new("vagrant") + temp.binmode + temp.write(config) + temp.close + + machine.communicate.tap do |comm| + comm.upload(temp.path, "/tmp/vagrant-apt-proxy-conf") + comm.sudo("cat /tmp/vagrant-apt-proxy-conf > #{proxy_conf_path(machine)}") + comm.sudo("rm /tmp/vagrant-apt-proxy-conf") + end + end + + def proxy_conf_capability?(machine) + machine.guest.capability?(:apt_proxy_conf) + end + + def proxy_conf_path(machine) + machine.guest.capability(:apt_proxy_conf) + end + end + end + end +end diff --git a/lib/vagrant-cachier/apt_proxy_config.rb b/lib/vagrant-cachier/apt_proxy_config.rb new file mode 100644 index 0000000..bdb77a0 --- /dev/null +++ b/lib/vagrant-cachier/apt_proxy_config.rb @@ -0,0 +1,88 @@ +require 'vagrant' + +module VagrantPlugins + module Cachier + class AptProxyConfig < Vagrant.plugin("2", :config) + # HTTP proxy for Apt + attr_accessor :http + + # HTTPS proxy for Apt + attr_accessor :https + + def initialize + @http = UNSET_VALUE + @https = UNSET_VALUE + end + + def finalize! + @http = override_from_env_var('http', @http) + @http = nil if @http == UNSET_VALUE + + @https = override_from_env_var('https', @https) + @https = nil if @https == UNSET_VALUE + end + + def enabled? + !http.nil? || !https.nil? + end + + # @return [String] the full configuration stanza + def to_s + "#{config_for('http')}#{config_for('https')}" + end + + private + + def override_from_env_var(proto, default) + ENV.fetch("APT_PROXY_#{proto.upcase}", default) + end + + def config_for(proto) + ConfigValue.new(proto, send(proto.to_sym)) + end + + class ConfigValue + + attr_reader :proto, :value + + # @param proto [String] the protocol ("http", "https") + # @param value [Object] the configuration value + def initialize(proto, value) + @proto = proto + @value = value + end + + # @return [String] the full Apt configuration line + def to_s + set? ? %Q{Acquire::#{proto}::Proxy "#{proxy_uri}";\n} : "" + end + + private + + def set? + value && !value.empty? + end + + def direct? + value.upcase == "DIRECT" + end + + def proxy_uri + direct? ? "DIRECT" : "#{prefix}#{value}#{suffix}" + end + + def prefix + "#{proto}://" if value !~ %r{^.*://} + end + + def suffix + ":#{default_port}" if value !~ %r{:\d+$} + end + + def default_port + 3142 + end + end + end + end +end diff --git a/lib/vagrant-cachier/cap/debian/apt_proxy_conf.rb b/lib/vagrant-cachier/cap/debian/apt_proxy_conf.rb new file mode 100644 index 0000000..0329a59 --- /dev/null +++ b/lib/vagrant-cachier/cap/debian/apt_proxy_conf.rb @@ -0,0 +1,13 @@ +module VagrantPlugins + module Cachier + module Cap + module Debian + module AptProxyConf + def self.apt_proxy_conf(machine) + '/etc/apt/apt.conf.d/01proxy' + end + end + end + end + end +end diff --git a/lib/vagrant-cachier/plugin.rb b/lib/vagrant-cachier/plugin.rb index 31a2870..e5d94c4 100644 --- a/lib/vagrant-cachier/plugin.rb +++ b/lib/vagrant-cachier/plugin.rb @@ -8,6 +8,11 @@ module VagrantPlugins Config end + config 'apt_proxy' do + require_relative 'apt_proxy_config' + AptProxyConfig + end + guest_capability 'linux', 'gemdir' do require_relative 'cap/linux/gemdir' Cap::Linux::Gemdir @@ -18,6 +23,11 @@ module VagrantPlugins Cap::Debian::AptCacheDir end + guest_capability 'debian', 'apt_proxy_conf' do + require_relative 'cap/debian/apt_proxy_conf' + Cap::Debian::AptProxyConf + end + guest_capability 'redhat', 'yum_cache_dir' do require_relative 'cap/redhat/yum_cache_dir' Cap::RedHat::YumCacheDir @@ -30,7 +40,9 @@ module VagrantPlugins install_action_hook = lambda do |hook| require_relative 'action' + require_relative 'action/configure_apt_proxy' hook.after Vagrant::Action::Builtin::Provision, VagrantPlugins::Cachier::Action::Install + hook.after Vagrant::Action::Builtin::Provision, VagrantPlugins::Cachier::Action::ConfigureAptProxy end action_hook 'set-shared-cache-on-machine-up', :machine_action_up, &install_action_hook action_hook 'set-shared-cache-on-machine-reload', :machine_action_reload, &install_action_hook diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..9a15a42 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,10 @@ +RSpec.configure do |config| + config.expect_with :rspec do |c| + c.syntax = :expect + end + config.color = true + config.tty = true +end + +require 'tempfile' +require 'vagrant-cachier' diff --git a/spec/unit/support/shared/apt_proxy_config.rb b/spec/unit/support/shared/apt_proxy_config.rb new file mode 100644 index 0000000..b5958a6 --- /dev/null +++ b/spec/unit/support/shared/apt_proxy_config.rb @@ -0,0 +1,103 @@ +def config_with(options) + instance.tap do |c| + options.each_pair { |k, v| c.send("#{k}=".to_sym, v) } + c.finalize! + end +end + +def conf_line(proto, name, port = 3142) + if name == :direct + %Q{Acquire::#{proto}::Proxy "DIRECT";\n} + else + %Q{Acquire::#{proto}::Proxy "#{proto}://#{name}:#{port}";\n} + end +end + +def conf_line_pattern(proto, name, port = 3142) + "^#{Regexp.escape(conf_line(proto, name, port))}" +end + +shared_examples "apt proxy config" do |proto| + context "#{proto} proxy" do + + context "with ip" do + subject { config_with(proto => "10.1.2.3") } + its(:enabled?) { should be_true } + its(:to_s) { should eq conf_line(proto, "10.1.2.3") } + end + + context "with name" do + subject { config_with(proto => "proxy.example.com") } + its(:enabled?) { should be_true } + its(:to_s) { should eq conf_line(proto, "proxy.example.com") } + end + + context "with name and port" do + subject { config_with(proto => "acng:8080") } + its(:enabled?) { should be_true } + its(:to_s) { should eq conf_line(proto, "acng", 8080) } + end + + context "with protocol and name" do + subject { config_with(proto => "#{proto}://proxy.foo.tld") } + its(:enabled?) { should be_true } + its(:to_s) { should eq conf_line(proto, "proxy.foo.tld") } + end + + context "with protocol and name and port" do + subject { config_with(proto => "#{proto}://prism.nsa.gov:666") } + its(:enabled?) { should be_true } + its(:to_s) { should eq conf_line(proto, "prism.nsa.gov", 666) } + end + + ["DIRECT", "direct"].each do |direct| + context "with #{direct.inspect}" do + subject { config_with(proto => direct) } + its(:enabled?) { should be_true } + its(:to_s) { should eq conf_line(proto, :direct) } + end + end + + [false, ""].each do |unset| + context "with #{unset.inspect}" do + subject { config_with(proto => unset) } + its(:enabled?) { should be_true } + its(:to_s) { should eq "" } + end + end + + context "with nil" do + subject { config_with(proto => nil) } + its(:enabled?) { should be_false } + its(:to_s) { should eq "" } + end + + end +end + +shared_examples "apt proxy env var" do |var, proto| + context var do + let(:conf) { config_with(http: "acng:8080", https: "ssl-proxy:8443") } + + it "sets #{proto} proxy" do + ENV[var] = "myproxy" + expect(conf.to_s).to match conf_line_pattern(proto, "myproxy") + end + + it "does not set other proxies" do + ENV[var] = "myproxy:2345" + conf = config_with({}) + expect(conf.to_s).to eq conf_line(proto, "myproxy", 2345) + end + + it "sets empty configuration" do + ENV[var] = "" + expect(conf.to_s).to_not match %r{#{proto}://} + end + + it "sets direct configuration" do + ENV[var] = "direct" + expect(conf.to_s).to match conf_line_pattern(proto, :direct) + end + end +end diff --git a/spec/unit/vagrant-cachier/apt_proxy_config_spec.rb b/spec/unit/vagrant-cachier/apt_proxy_config_spec.rb new file mode 100644 index 0000000..e740f17 --- /dev/null +++ b/spec/unit/vagrant-cachier/apt_proxy_config_spec.rb @@ -0,0 +1,34 @@ +require 'spec_helper' +require 'unit/support/shared/apt_proxy_config' +require 'vagrant-cachier/apt_proxy_config' + +describe VagrantPlugins::Cachier::AptProxyConfig do + let(:instance) { described_class.new } + + before :each do + # Ensure tests are not affected by environment variables + %w[APT_PROXY_HTTP APT_PROXY_HTTPS].each { |k| ENV.delete(k) } + end + + context "defaults" do + subject { config_with({}) } + its(:enabled?) { should be_false } + its(:to_s) { should eq "" } + end + + include_examples "apt proxy config", "http" + include_examples "apt proxy config", "https" + + context "with both http and https proxies" do + subject { config_with(http: "10.2.3.4", https: "ssl-proxy:8443") } + its(:enabled?) { should be_true } + its(:to_s) { should match conf_line_pattern("http", "10.2.3.4") } + its(:to_s) { should match conf_line_pattern("https", "ssl-proxy", 8443) } + end + + context "with env var" do + include_examples "apt proxy env var", "APT_PROXY_HTTP", "http" + include_examples "apt proxy env var", "APT_PROXY_HTTPS", "https" + end + +end