Skip to content

Commit

Permalink
Run the Windows installer in Powershell
Browse files Browse the repository at this point in the history
  • Loading branch information
hartmantis committed Jan 2, 2015
1 parent c99470c commit 3810c84
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 29 deletions.
58 changes: 43 additions & 15 deletions libraries/provider_private_internet_access_windows.rb
Expand Up @@ -21,6 +21,8 @@
require 'net/http'
require 'chef/resource/cookbook_file'
require 'chef/resource/execute'
require 'chef/resource/powershell_script'
require 'chef/resource/ruby'
require_relative 'provider_private_internet_access'

class Chef
Expand All @@ -30,36 +32,62 @@ class PrivateInternetAccess < Provider
#
# @author Jonathan Hartman <j@p4nt5.com>
class Windows < PrivateInternetAccess
WATCH_FILE ||= '/Program Files/pia_manager/pia_manager.exe'

#
# Override the install action to trust the OpenVPN TAP driver cert
# Override the install action to...
# 1) trust the OpenVPN TAP driver cert
# 2) Install PIA from a Powershell script instead of package resource
#
def action_install
remote_file.run_action(:create)
cert_file.run_action(:create)
trust_cert.run_action(:run)
run_installer.run_action(:run)
start_installer.run_action(:run)
wait_for_installer.run_action(:run)
new_resource.installed = true
end

private

#
# Use a Ruby block to spawn a new process for the installer, otherwise
# Chef hangs indefinitely when the installer starts up PIA processes
# and never exits
# Because the installer process is started in the background, make
# Chef wait until it's finished before proceeding. Just sleep as needed
# and let Chef handle any timeouts.
#
# @return [Chef::Resource::Ruby]
#
# @return [Chef::Resource::RubyBlock]
def wait_for_installer
unless @wait_for_installer
@wait_for_installer = Resource::Ruby.new('pia_installer_wait',
run_context)
@wait_for_installer.code(
"sleep 1 while !::File.exist?('#{WATCH_FILE}')"
)
@wait_for_installer.creates(WATCH_FILE)
end
@wait_for_installer
end

#
def run_installer
unless @run_installer
@run_installer = Resource::Ruby.new('pia_install', run_context)
chk = '/Program Files/pia_manager/pia_manager.exe'
# Process.spawn needs backslashes for Windows
@run_installer.code("spawn('#{package.source.gsub('/', '\\')}') " \
"&& (sleep 1 while !::File.exist?('#{chk}'))")
@run_installer.creates(chk)
# The PIA installer does something funky where it spawns the daemons
# in its foreground and never exits. This causes Chef to hang until
# it times out when using a package resource, so use Powershell to
# start the installer in the background.
#
# @return [Chef::Resource::PowershellScript]
#
def start_installer
unless @start_installer
@start_installer = Resource::PowershellScript.new('pia_installer',
run_context)
@start_installer.code(
# Start-Process fails if given forward slashes
"Start-Process #{package.source.gsub('/', '\\')}"
)
@start_installer.creates(WATCH_FILE)
end
@run_installer
@start_installer
end

#
Expand Down
55 changes: 41 additions & 14 deletions spec/libraries/provider_private_internet_access_windows_spec.rb
Expand Up @@ -15,14 +15,24 @@

describe '#action_install' do
[
:remote_file, :package, :cert_file, :trust_cert, :run_installer
:remote_file,
:package,
:cert_file,
:trust_cert,
:start_installer,
:wait_for_installer
].each do |r|
let(r) { double(run_action: true) }
end

before(:each) do
[
:remote_file, :package, :cert_file, :trust_cert, :run_installer
:remote_file,
:package,
:cert_file,
:trust_cert,
:start_installer,
:wait_for_installer
].each do |r|
allow_any_instance_of(described_class).to receive(r).and_return(send(r))
end
Expand All @@ -32,7 +42,8 @@
remote_file: :create,
cert_file: :create,
trust_cert: :run,
run_installer: :run
start_installer: :run,
wait_for_installer: :run
}.each do |k, v|
it "sends a #{v} action to #{k}" do
expect(send(k)).to receive(:run_action).with(v)
Expand All @@ -46,34 +57,50 @@
end

it 'takes no action on the package resource' do
expect(new_resource).not_to receive(:run_action)
expect(package).not_to receive(:run_action)
provider.action_install
end
end

describe '#run_installer' do
describe '#wait_for_installer' do
it 'returns a ruby resource' do
expected = Chef::Resource::Ruby
expect(provider.send(:wait_for_installer)).to be_an_instance_of(expected)
end

it 'sets the correct code' do
expected = 'sleep 1 while !::File.exist?(\'/Program Files/pia_manager/' \
'pia_manager.exe\')'
expect(provider.send(:wait_for_installer).code).to eq(expected)
end

it 'sets the correct watch file' do
expected = '/Program Files/pia_manager/pia_manager.exe'
expect(provider.send(:wait_for_installer).creates).to eq(expected)
end
end

describe '#start_installer' do
let(:package) { double(source: '/somewhere/out/there/win.exe') }

before(:each) do
allow_any_instance_of(described_class).to receive(:package)
.and_return(package)
end

it 'returns a ruby resource' do
expected = Chef::Resource::Ruby
expect(provider.send(:run_installer)).to be_an_instance_of(expected)
it 'returns a powershell_script resource' do
expected = Chef::Resource::PowershellScript
expect(provider.send(:start_installer)).to be_an_instance_of(expected)
end

it 'uses the correct code' do
expected = 'spawn(\'\\somewhere\\out\\there\\win.exe\') && ' \
'(sleep 1 while !::File.exist?(\'/Program Files/pia_manager' \
'/pia_manager.exe\'))'
expect(provider.send(:run_installer).code).to eq(expected)
expected = 'Start-Process \\somewhere\\out\\there\\win.exe'
expect(provider.send(:start_installer).code).to eq(expected)
end

it 'uses the correct file creation check' do
it 'uses the correct watch file' do
expected = '/Program Files/pia_manager/pia_manager.exe'
expect(provider.send(:run_installer).creates).to eq(expected)
expect(provider.send(:start_installer).creates).to eq(expected)
end
end

Expand Down

0 comments on commit 3810c84

Please sign in to comment.