Skip to content

Commit

Permalink
Merge pull request #151 from opscode-cookbooks/jdm/chef-12
Browse files Browse the repository at this point in the history
Modify windows_package to run on Chef 11 and Chef 12
  • Loading branch information
jaym committed Dec 11, 2014
2 parents d0ff7e6 + 74e98bc commit 8a01480
Show file tree
Hide file tree
Showing 10 changed files with 357 additions and 290 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.kitchen/
.kitchen.local.yml
Berksfile.lock
Gemfile.lock
.bundle
35 changes: 35 additions & 0 deletions .kitchen.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
driver:
forward_agent: yes
name: vagrant
vm_hostname: false # do not bother trying to change the hostname (sometimes fails)
customize:
cpus: 2
memory: 4096

transport:
name: winrm

provisioner:
name: chef_zero

platforms:
- name: windows-8.1-chef11
driver_config:
box: windows-8.1
box_url: http://cdn.box-cutter.com/windows/virtualbox4.3.20/eval-win81x64-enterprise-nocm-1.0.2.box
provisioner:
require_chef_omnibus: 11.16.4
- name: windows-8.1-chef12
driver_config:
box: windows-8.1
box_url: http://cdn.box-cutter.com/windows/virtualbox4.3.20/eval-win81x64-enterprise-nocm-1.0.2.box
provisioner:
require_chef_omnibus: 12.0.0

suites:
- name: default
run_list:
- recipe[windows::default]
- recipe[minimal::default]

7 changes: 7 additions & 0 deletions Berksfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
source "https://supermarket.getchef.com"

metadata

group :integration do
cookbook 'minimal', :path => './test/cookbooks/minimal'
end
21 changes: 21 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
source "https://rubygems.org"

group :development do
# The current official branch is the 'windows-guest-support' branch, but it isn't working for me right now.
# gem "test-kitchen", :git => 'https://github.com/test-kitchen/test-kitchen.git', :branch => 'windows-guest-support'
gem 'test-kitchen', git: 'https://github.com/jdmundrawala/test-kitchen.git', :branch => 'Transport'

# afiune/Transport supports copying files from Windows -> Windows
# gem 'kitchen-vagrant', git: 'https://github.com/jdmundrawala/kitchen-vagrant.git', :branch => 'Transport'
gem 'kitchen-vagrant', git: 'https://github.com/btm/kitchen-vagrant.git', :branch => 'afiune/Transport'

gem "berkshelf"

# Adds windows support to vagrant-wrapper gem
gem "vagrant-wrapper", git: 'https://github.com/btm/gem-vagrant-wrapper.git', :branch => 'windows'

# We should be able to remove those once vagrant 1.7.0+ is fixed (https://github.com/mitchellh/vagrant/issues/4924)
# vagrant (= 1.6.5) x86-mingw32 depends on
# bundler (< 1.7.0, >= 1.5.2) x86-mingw32
gem "bundler", "< 1.7.0"
end
2 changes: 2 additions & 0 deletions chefignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.git
.kitchen
266 changes: 266 additions & 0 deletions libraries/windows_package.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
require 'chef/resource/lwrp_base'
require 'chef/provider/lwrp_base'

if RUBY_PLATFORM =~ /mswin|mingw32|windows/
require 'win32/registry'
end

require 'chef/mixin/shell_out'
require 'chef/mixin/language'
class Chef
class Provider
class WindowsCookbookPackage < Chef::Provider::LWRPBase
include Chef::Mixin::ShellOut
include Windows::Helper

# the logic in all action methods mirror that of
# the Chef::Provider::Package which will make
# refactoring into core chef easy

action :install do
# If we specified a version, and it's not the current version, move to the specified version
if @new_resource.version != nil && @new_resource.version != @current_resource.version
install_version = @new_resource.version
# If it's not installed at all, install it
elsif @current_resource.version == nil
install_version = candidate_version
end

if install_version
Chef::Log.info("Installing #{@new_resource} version #{install_version}")
status = install_package(@new_resource.package_name, install_version)
if status
new_resource.updated_by_last_action(true)
end
end
end

action :upgrade do
if @current_resource.version != candidate_version
orig_version = @current_resource.version || "uninstalled"
Chef::Log.info("Upgrading #{@new_resource} version from #{orig_version} to #{candidate_version}")
status = upgrade_package(@new_resource.package_name, candidate_version)
if status
new_resource.updated_by_last_action(true)
end
end
end

action :remove do
if removing_package?
Chef::Log.info("Removing #{@new_resource}")
remove_package(@current_resource.package_name, @new_resource.version)
new_resource.updated_by_last_action(true)
else
end
end

def removing_package?
if @current_resource.version.nil?
false # nothing to remove
elsif @new_resource.version.nil?
true # remove any version of a package
elsif @new_resource.version == @current_resource.version
true # remove the version we have
else
false # we don't have the version we want to remove
end
end

def expand_options(options)
options ? " #{options}" : ""
end

# these methods are the required overrides of
# a provider that extends from Chef::Provider::Package
# so refactoring into core Chef should be easy

def load_current_resource
@current_resource = Chef::Resource::WindowsPackage.new(@new_resource.name)
@current_resource.package_name(@new_resource.package_name)
@current_resource.version(nil)

unless current_installed_version.nil?
@current_resource.version(current_installed_version)
end

@current_resource
end

def current_installed_version
@current_installed_version ||= begin
if installed_packages.include?(@new_resource.package_name)
installed_packages[@new_resource.package_name][:version]
end
end
end

def candidate_version
@candidate_version ||= begin
@new_resource.version || 'latest'
end
end

def install_package(name,version)
Chef::Log.debug("Processing #{@new_resource} as a #{installer_type} installer.")
install_args = [cached_file(@new_resource.source, @new_resource.checksum), expand_options(unattended_installation_flags), expand_options(@new_resource.options)]
Chef::Log.info("Starting installation...this could take awhile.")
Chef::Log.debug "Install command: #{ sprintf(install_command_template, *install_args) }"
shell_out!(sprintf(install_command_template, *install_args), {:timeout => @new_resource.timeout, :returns => @new_resource.success_codes})
end

def remove_package(name, version)
uninstall_string = installed_packages[@new_resource.package_name][:uninstall_string]
Chef::Log.info("Registry provided uninstall string for #{@new_resource} is '#{uninstall_string}'")
uninstall_command = begin
if uninstall_string =~ /msiexec/i
"#{uninstall_string} /qn"
else
uninstall_string.gsub!('"','')
"start \"\" /wait /d\"#{::File.dirname(uninstall_string)}\" #{::File.basename(uninstall_string)}#{expand_options(@new_resource.options)} /S & exit %%%%ERRORLEVEL%%%%"
end
end
Chef::Log.info("Removing #{@new_resource} with uninstall command '#{uninstall_command}'")
shell_out!(uninstall_command, {:returns => @new_resource.success_codes})
end

private

def install_command_template
case installer_type
when :msi
"msiexec%2$s \"%1$s\"%3$s"
else
"start \"\" /wait \"%1$s\"%2$s%3$s & exit %%%%ERRORLEVEL%%%%"
end
end


# http://unattended.sourceforge.net/installers.php
def unattended_installation_flags
case installer_type
when :msi
# this is no-ui
"/qn /i"
when :installshield
"/s /sms"
when :nsis
"/S /NCRC"
when :inno
#"/sp- /silent /norestart"
"/verysilent /norestart"
when :wise
"/s"
else
end
end

def installed_packages
@installed_packages || begin
installed_packages = {}
# Computer\HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall
installed_packages.merge!(extract_installed_packages_from_key(::Win32::Registry::HKEY_LOCAL_MACHINE)) #rescue nil
# 64-bit registry view
# Computer\HKEY_LOCAL_MACHINE\Software\Wow6464Node\Microsoft\Windows\CurrentVersion\Uninstall
installed_packages.merge!(extract_installed_packages_from_key(::Win32::Registry::HKEY_LOCAL_MACHINE, (::Win32::Registry::Constants::KEY_READ | 0x0100))) #rescue nil
# 32-bit registry view
# Computer\HKEY_LOCAL_MACHINE\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall
installed_packages.merge!(extract_installed_packages_from_key(::Win32::Registry::HKEY_LOCAL_MACHINE, (::Win32::Registry::Constants::KEY_READ | 0x0200))) #rescue nil
# Computer\HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Uninstall
installed_packages.merge!(extract_installed_packages_from_key(::Win32::Registry::HKEY_CURRENT_USER)) #rescue nil
installed_packages
end
end

def extract_installed_packages_from_key(hkey = ::Win32::Registry::HKEY_LOCAL_MACHINE, desired = ::Win32::Registry::Constants::KEY_READ)
uninstall_subkey = 'Software\Microsoft\Windows\CurrentVersion\Uninstall'
packages = {}
begin
::Win32::Registry.open(hkey, uninstall_subkey, desired) do |reg|
reg.each_key do |key, wtime|
begin
k = reg.open(key, desired)
display_name = k["DisplayName"] rescue nil
version = k["DisplayVersion"] rescue "NO VERSION"
uninstall_string = k["UninstallString"] rescue nil
if display_name
packages[display_name] = {:name => display_name,
:version => version,
:uninstall_string => uninstall_string}
end
rescue ::Win32::Registry::Error
end
end
end
rescue ::Win32::Registry::Error
end
packages
end

def installer_type
@installer_type || begin
if @new_resource.installer_type
@new_resource.installer_type
else
basename = ::File.basename(cached_file(@new_resource.source, @new_resource.checksum))
if basename.split(".").last.downcase == "msi" # Microsoft MSI
:msi
else
# search the binary file for installer type
contents = ::Kernel.open(::File.expand_path(cached_file(@new_resource.source)), "rb") {|io| io.read } # TODO limit data read in
case contents
when /inno/i # Inno Setup
:inno
when /wise/i # Wise InstallMaster
:wise
when /nsis/i # Nullsoft Scriptable Install System
:nsis
else
# if file is named 'setup.exe' assume installshield
if basename == "setup.exe"
:installshield
else
raise Chef::Exceptions::AttributeNotFound, "installer_type could not be determined, please set manually"
end
end
end
end
end
end
end
end
end


class Chef
class Resource
class WindowsCookbookPackage < Chef::Resource::LWRPBase
if Gem::Version.new(Chef::VERSION) >= Gem::Version.new('12')
provides :windows_package, os: "windows"
end
actions :install, :remove

default_action :install

attribute :package_name, :kind_of => String, :name_attribute => true
attribute :source, :kind_of => String, :required => true
attribute :version, :kind_of => String
attribute :options, :kind_of => String
attribute :installer_type, :kind_of => Symbol, :default => nil, :equal_to => [:msi, :inno, :nsis, :wise, :installshield, :custom]
attribute :checksum, :kind_of => String
attribute :timeout, :kind_of => Integer, :default => 600
attribute :success_codes, :kind_of => Array, :default => [0, 42, 127]

self.resource_name = 'windows_package'
def initialize(*args)
super
@provider = Chef::Provider::WindowsCookbookPackage
end
end
end
end

if Gem::Version.new(Chef::VERSION) < Gem::Version.new('12')
Chef::Resource.send(:remove_const, :WindowsPackage)
Chef::Resource.const_set("WindowsPackage", Chef::Resource::WindowsCookbookPackage)
end

0 comments on commit 8a01480

Please sign in to comment.