summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLamont Granquist <lamont@scriptkiddie.org>2016-01-08 12:14:20 -0800
committerLamont Granquist <lamont@scriptkiddie.org>2016-01-08 12:14:20 -0800
commit02bbb1aeef2dcf720967ac476002877344e88f80 (patch)
tree9cd1141fa87e22694f67204deb579c164ace7d96
parentc9d7e017de225db3e8b8610dc7e2f41242dbe730 (diff)
downloadchef-02bbb1aeef2dcf720967ac476002877344e88f80.tar.gz
chocolatey multipackage providerlcg/chocolatey-package
-rw-r--r--lib/chef/provider/package/chocolatey.rb230
-rw-r--r--lib/chef/providers.rb3
-rw-r--r--lib/chef/resource/chocolatey_package.rb37
-rw-r--r--lib/chef/resources.rb3
-rw-r--r--spec/unit/provider/package/chocolatey_spec.rb422
-rw-r--r--spec/unit/provider/package/zypper_spec.rb2
-rw-r--r--spec/unit/resource/chocolatey_package_spec.rb67
7 files changed, 761 insertions, 3 deletions
diff --git a/lib/chef/provider/package/chocolatey.rb b/lib/chef/provider/package/chocolatey.rb
new file mode 100644
index 0000000000..3f52370939
--- /dev/null
+++ b/lib/chef/provider/package/chocolatey.rb
@@ -0,0 +1,230 @@
+#
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/provider/package'
+require 'chef/resource/chocolatey_package'
+require 'chef/mixin/powershell_out'
+
+class Chef
+ class Provider
+ class Package
+ class Chocolatey < Chef::Provider::Package
+ include Chef::Mixin::PowershellOut
+
+ provides :chocolatey_package, os: "windows"
+
+ # Declare that our arguments should be arrays
+ use_multipackage_api
+
+ # Responsible for building the current_resource.
+ #
+ # @return [Chef::Resource::ChocolateyPackage] the current_resource
+ def load_current_resource
+ @current_resource = Chef::Resource::ChocolateyPackage.new(new_resource.name)
+ current_resource.package_name(new_resource.package_name)
+ current_resource.version(build_current_versions)
+ current_resource
+ end
+
+ # Lazy initializer for candidate_version. A nil value means that there is no candidate
+ # version and the package is not installable (generally an error).
+ #
+ # @return [Array] list of candidate_versions indexed same as new_resource.package_name/version
+ def candidate_version
+ @candidate_version ||= build_candidate_versions
+ end
+
+ # Install multiple packages via choco.exe
+ #
+ # @param names [Array<String>] array of package names to install
+ # @param versions [Array<String>] array of versions to install
+ def install_package(names, versions)
+ name_versions_to_install = desired_name_versions.select { |n, v| names.include?(n) }
+
+ name_nil_versions = name_versions_to_install.select { |n,v| v.nil? }
+ name_has_versions = name_versions_to_install.reject { |n,v| v.nil? }
+
+ # choco does not support installing multiple packages with version pins
+ name_has_versions.each do |name, version|
+ choco_command("install -y -version", version, cmd_args, name)
+ end
+
+ # but we can do all the ones without version pins at once
+ unless name_nil_versions.empty?
+ cmd_names = name_nil_versions.keys
+ choco_command("install -y", cmd_args, *cmd_names)
+ end
+ end
+
+ # Upgrade multiple packages via choco.exe
+ #
+ # @param names [Array<String>] array of package names to install
+ # @param versions [Array<String>] array of versions to install
+ def upgrade_package(names, versions)
+ name_versions_to_install = desired_name_versions.select { |n, v| names.include?(n) }
+
+ name_nil_versions = name_versions_to_install.select { |n,v| v.nil? }
+ name_has_versions = name_versions_to_install.reject { |n,v| v.nil? }
+
+ # choco does not support installing multiple packages with version pins
+ name_has_versions.each do |name, version|
+ choco_command("upgrade -y -version", version, cmd_args, name)
+ end
+
+ # but we can do all the ones without version pins at once
+ unless name_nil_versions.empty?
+ cmd_names = name_nil_versions.keys
+ choco_command("upgrade -y", cmd_args, *cmd_names)
+ end
+ end
+
+ # Remove multiple packages via choco.exe
+ #
+ # @param names [Array<String>] array of package names to install
+ # @param versions [Array<String>] array of versions to install
+ def remove_package(names, versions)
+ choco_command("uninstall -y", cmd_args, *names)
+ end
+
+ # Support :uninstall as an action in order for users to easily convert
+ # from the `chocolatey` provider in the cookbook. It is, however,
+ # already deprecated.
+ def action_uninstall
+ Chef::Log.deprecation "The use of action :uninstall on the chocolatey_package provider is deprecated, please use :remove"
+ action_remove
+ end
+
+ # Choco does not have dpkg's distinction between purge and remove
+ alias_method :purge_package, :remove_package
+
+ # Override the superclass check. The semantics for our new_resource.source is not files to
+ # install from, but like the rubygem provider's sources which are more like repos.
+ def check_resource_semantics!
+ end
+
+ private
+
+ # Magic to find where chocolatey is installed in the system, and to
+ # return the full path of choco.exe
+ #
+ # @return [String] full path of choco.exe
+ def choco_exe
+ @choco_exe ||=
+ ::File.join(
+ powershell_out!(
+ "[System.Environment]::GetEnvironmentVariable('ChocolateyInstall', 'MACHINE')"
+ ).stdout.chomp,
+ 'bin',
+ 'choco.exe'
+ )
+ end
+
+ # Helper to dispatch a choco command through shell_out using the timeout
+ # set on the new resource, with nice command formatting.
+ #
+ # @param args [String] variable number of string arguments
+ # @return [Mixlib::ShellOut] object returned from shell_out!
+ def choco_command(*args)
+ shell_out_with_timeout!(args_to_string(choco_exe, *args))
+ end
+
+ # Use the available_packages Hash helper to create an array suitable for
+ # using in candidate_version
+ #
+ # @return [Array] list of candidate_version, same index as new_resource.package_name/version
+ def build_candidate_versions
+ new_resource.package_name.map do |package_name|
+ available_packages[package_name.downcase]
+ end
+ end
+
+ # Use the installed_packages Hash helper to create an array suitable for
+ # using in current_resource.version
+ #
+ # @return [Array] list of candidate_version, same index as new_resource.package_name/version
+ def build_current_versions
+ new_resource.package_name.map do |package_name|
+ installed_packages[package_name.downcase]
+ end
+ end
+
+ # Helper to construct Hash of names-to-versions, requested on the new_resource.
+ # If new_resource.version is nil, then all values will be nil.
+ #
+ # @return [Hash] Mapping of requested names to versions
+ def desired_name_versions
+ desired_versions = new_resource.version || new_resource.package_name.map { nil }
+ Hash[*new_resource.package_name.zip(desired_versions).flatten]
+ end
+
+ # Helper to construct optional args out of new_resource
+ #
+ # @return [String] options from new_resource or empty string
+ def cmd_args
+ cmd_args = [ new_resource.options ]
+ cmd_args.push( "-source #{new_resource.source}" ) if new_resource.source
+ args_to_string(*cmd_args)
+ end
+
+ # Helper to nicely convert variable string args into a single command line. It
+ # will compact nulls or empty strings and join arguments with single spaces, without
+ # introducing any double-spaces for missing args.
+ #
+ # @param args [String] variable number of string arguments
+ # @return [String] nicely concatenated string or empty string
+ def args_to_string(*args)
+ args.reject {|i| i.nil? || i == "" }.join(" ")
+ end
+
+ # Available packages in chocolatey as a Hash of names mapped to versions
+ # (names are downcased for case-insensitive matching)
+ #
+ # @return [Hash] name-to-version mapping of available packages
+ def available_packages
+ @available_packages ||=
+ begin
+ cmd = [ "list -r #{package_name_array.join ' '}" ]
+ cmd.push( "-source #{new_resource.source}" ) if new_resource.source
+ parse_list_output(*cmd)
+ end
+ end
+
+ # Installed packages in chocolatey as a Hash of names mapped to versions
+ # (names are downcased for case-insensitive matching)
+ #
+ # @return [Hash] name-to-version mapping of installed packages
+ def installed_packages
+ @installed_packages ||= parse_list_output("list -l -r")
+ end
+
+ # Helper to convert choco.exe list output to a Hash
+ # (names are downcased for case-insenstive matching)
+ #
+ # @param cmd [String] command to run
+ # @return [String] list output converted to ruby Hash
+ def parse_list_output(*args)
+ hash = {}
+ choco_command(*args).stdout.each_line do |line|
+ name, version = line.split('|')
+ hash[name.downcase] = version.chomp
+ end
+ hash
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/providers.rb b/lib/chef/providers.rb
index f5e7a0f989..b4eeabd48d 100644
--- a/lib/chef/providers.rb
+++ b/lib/chef/providers.rb
@@ -1,6 +1,6 @@
#
# Author:: Daniel DeLeo (<dan@opscode.com>)
-# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# Copyright:: Copyright (c) 2010-2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -57,6 +57,7 @@ require 'chef/provider/whyrun_safe_ruby_block'
require 'chef/provider/env/windows'
require 'chef/provider/package/apt'
+require 'chef/provider/package/chocolatey'
require 'chef/provider/package/dpkg'
require 'chef/provider/package/easy_install'
require 'chef/provider/package/freebsd/port'
diff --git a/lib/chef/resource/chocolatey_package.rb b/lib/chef/resource/chocolatey_package.rb
new file mode 100644
index 0000000000..2e29c1722e
--- /dev/null
+++ b/lib/chef/resource/chocolatey_package.rb
@@ -0,0 +1,37 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/resource/package'
+
+class Chef
+ class Resource
+ class ChocolateyPackage < Chef::Resource::Package
+
+ provides :chocolatey_package, os: "windows"
+
+ def initialize(name, run_context=nil)
+ super
+ @resource_name = :chocolatey_package
+ end
+
+ property :package_name, [String, Array], coerce: proc { |x| [x].flatten }
+
+ property :version, [String, Array], coerce: proc { |x| [x].flatten }
+ end
+ end
+end
diff --git a/lib/chef/resources.rb b/lib/chef/resources.rb
index f699d95ace..cc5b9be8c6 100644
--- a/lib/chef/resources.rb
+++ b/lib/chef/resources.rb
@@ -1,6 +1,6 @@
#
# Author:: Daniel DeLeo (<dan@opscode.com>)
-# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# Copyright:: Copyright (c) 2010-2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -22,6 +22,7 @@ require 'chef/resource/batch'
require 'chef/resource/breakpoint'
require 'chef/resource/cookbook_file'
require 'chef/resource/chef_gem'
+require 'chef/resource/chocolatey_package'
require 'chef/resource/cron'
require 'chef/resource/csh'
require 'chef/resource/deploy'
diff --git a/spec/unit/provider/package/chocolatey_spec.rb b/spec/unit/provider/package/chocolatey_spec.rb
new file mode 100644
index 0000000000..d2d6fabb19
--- /dev/null
+++ b/spec/unit/provider/package/chocolatey_spec.rb
@@ -0,0 +1,422 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Provider::Package::Chocolatey do
+ let(:timeout) { 900 }
+
+ let(:new_resource) { Chef::Resource::ChocolateyPackage.new("git") }
+
+ let(:provider) do
+ node = Chef::Node.new
+ events = Chef::EventDispatch::Dispatcher.new
+ run_context = Chef::RunContext.new(node, {}, events)
+ Chef::Provider::Package::Chocolatey.new(new_resource, run_context)
+ end
+
+ let(:choco_exe) { 'C:\ProgramData\chocolatey\bin\choco.exe' }
+
+ # installed packages (ConEmu is upgradable)
+ let(:local_list_stdout) do
+ <<-EOF
+chocolatey|0.9.9.11
+ConEmu|15.10.25.0
+ EOF
+ end
+
+ before do
+ allow(provider).to receive(:choco_exe).and_return(choco_exe)
+ local_list_obj = double(:stdout => local_list_stdout)
+ allow(provider).to receive(:shell_out!).with("#{choco_exe} list -l -r", {:timeout => timeout}).and_return(local_list_obj)
+ end
+
+ def allow_remote_list(package_names, args = nil)
+ remote_list_stdout = <<-EOF
+chocolatey|0.9.9.11
+ConEmu|15.10.25.1
+git|2.6.2
+munin-node|1.6.1.20130823
+ EOF
+ remote_list_obj = double(stdout: remote_list_stdout)
+ allow(provider).to receive(:shell_out!).with("#{choco_exe} list -r #{package_names.join ' '}#{args}", {timeout: timeout}).and_return(remote_list_obj)
+ end
+
+ describe "#initialize" do
+ it "should return the correct class" do
+ expect(provider).to be_kind_of(Chef::Provider::Package::Chocolatey)
+ end
+
+ it "should support arrays" do
+ expect(provider.use_multipackage_api?).to be true
+ end
+ end
+
+ describe "#candidate_version" do
+ it "should set the candidate_version correctly" do
+ allow_remote_list(["git"])
+ expect(provider.candidate_version).to eql(["2.6.2"])
+ end
+
+ it "should set the candidate_version to nil if there is no candidate" do
+ allow_remote_list(["vim"])
+ new_resource.package_name("vim")
+ expect(provider.candidate_version).to eql([nil])
+ end
+
+ it "should set the candidate_version correctly when there are two packages to install" do
+ allow_remote_list(["ConEmu", "chocolatey"])
+ new_resource.package_name(["ConEmu", "chocolatey"])
+ expect(provider.candidate_version).to eql(["15.10.25.1", "0.9.9.11"])
+ end
+
+ it "should set the candidate_version correctly when only the first is installable" do
+ allow_remote_list(["ConEmu", "vim"])
+ new_resource.package_name(["ConEmu", "vim"])
+ expect(provider.candidate_version).to eql(["15.10.25.1", nil])
+ end
+
+ it "should set the candidate_version correctly when only the last is installable" do
+ allow_remote_list(["vim", "chocolatey"])
+ new_resource.package_name(["vim", "chocolatey"])
+ expect(provider.candidate_version).to eql([nil, "0.9.9.11"])
+ end
+
+ it "should set the candidate_version correctly when neither are is installable" do
+ allow_remote_list(["vim", "ruby"])
+ new_resource.package_name(["vim", "ruby"])
+ expect(provider.candidate_version).to eql([nil, nil])
+ end
+ end
+
+ describe "#load_current_resource" do
+ it "should return a current_resource" do
+ expect(provider.load_current_resource).to be_kind_of(Chef::Resource::ChocolateyPackage)
+ end
+
+ it "should set the current_resource#package_name" do
+ provider.load_current_resource
+ expect(provider.current_resource.package_name).to eql(["git"])
+ end
+
+ it "should load and downcase names in the installed_packages hash" do
+ provider.load_current_resource
+ expect(provider.send(:installed_packages)).to eql(
+ {"chocolatey"=>"0.9.9.11", "conemu"=>"15.10.25.0"}
+ )
+ end
+
+ it "should load and downcase names in the available_packages hash" do
+ allow_remote_list(["git"])
+ provider.load_current_resource
+ expect(provider.send(:available_packages)).to eql(
+ {"chocolatey"=>"0.9.9.11", "conemu"=>"15.10.25.1", "git"=>"2.6.2", "munin-node" => "1.6.1.20130823"}
+ )
+ end
+
+ it "should set the current_resource.version to nil when the package is not installed" do
+ provider.load_current_resource
+ expect(provider.current_resource.version).to eql([nil])
+ end
+
+ it "should set the current_resource.version to the installed version when the package is installed" do
+ new_resource.package_name("ConEmu")
+ provider.load_current_resource
+ expect(provider.current_resource.version).to eql(["15.10.25.0"])
+ end
+
+ it "should set the current_resource.version when there are two packages that are installed" do
+ new_resource.package_name(["ConEmu", "chocolatey"])
+ provider.load_current_resource
+ expect(provider.current_resource.version).to eql(["15.10.25.0", "0.9.9.11"])
+ end
+
+ it "should set the current_resource.version correctly when only the first is installed" do
+ new_resource.package_name(["ConEmu", "git"])
+ provider.load_current_resource
+ expect(provider.current_resource.version).to eql(["15.10.25.0", nil])
+ end
+
+ it "should set the current_resource.version correctly when only the last is installed" do
+ new_resource.package_name(["git", "chocolatey"])
+ provider.load_current_resource
+ expect(provider.current_resource.version).to eql([nil, "0.9.9.11"])
+ end
+
+ it "should set the current_resource.version correctly when none are installed" do
+ new_resource.package_name(["git", "vim"])
+ provider.load_current_resource
+ expect(provider.current_resource.version).to eql([nil, nil])
+ end
+ end
+
+ describe "#action_install" do
+ it "should install a single package" do
+ allow_remote_list(["git"])
+ provider.load_current_resource
+ expect(provider).to receive(:shell_out!).with("#{choco_exe} install -y git", {:timeout=>timeout}).and_return(double)
+ provider.run_action(:install)
+ expect(new_resource).to be_updated_by_last_action
+ end
+
+ context "when changing the timeout to 3600" do
+ let(:timeout) { 3600 }
+ it "sets the timeout on shell_out commands" do
+ allow_remote_list(["git"])
+ new_resource.timeout(timeout)
+ provider.load_current_resource
+ expect(provider).to receive(:shell_out!).with("#{choco_exe} install -y git", {:timeout=>timeout}).and_return(double)
+ provider.run_action(:install)
+ expect(new_resource).to be_updated_by_last_action
+ end
+ end
+
+ it "should not install packages that are up-to-date" do
+ allow_remote_list(["chocolatey"])
+ new_resource.package_name("chocolatey")
+ provider.load_current_resource
+ expect(provider).not_to receive(:install_package)
+ provider.run_action(:install)
+ expect(new_resource).not_to be_updated_by_last_action
+ end
+
+ it "should not upgrade packages" do
+ allow_remote_list(["ConEmu"])
+ new_resource.package_name("ConEmu")
+ provider.load_current_resource
+ expect(provider).not_to receive(:install_package)
+ provider.run_action(:install)
+ expect(new_resource).not_to be_updated_by_last_action
+ end
+
+ it "should upgrade packages when given a version pin" do
+ allow_remote_list(["ConEmu"])
+ new_resource.package_name("ConEmu")
+ new_resource.version("15.10.25.1")
+ provider.load_current_resource
+ expect(provider).to receive(:shell_out!).with("#{choco_exe} install -y -version 15.10.25.1 ConEmu", {:timeout=>timeout}).and_return(double)
+ provider.run_action(:install)
+ expect(new_resource).to be_updated_by_last_action
+ end
+
+ it "should handle complicated cases when the name/version array is pruned" do
+ # chocolatey will be pruned by the superclass out of the args to install_package and we
+ # implicitly test that we correctly pick up new_resource.version[1] instead of
+ # new_version.resource[0]
+ allow_remote_list(["chocolatey", "ConEmu"])
+ new_resource.package_name(["chocolatey", "ConEmu"])
+ new_resource.version([nil, "15.10.25.1"])
+ provider.load_current_resource
+ expect(provider).to receive(:shell_out!).with("#{choco_exe} install -y -version 15.10.25.1 ConEmu", {:timeout=>timeout}).and_return(double)
+ provider.run_action(:install)
+ expect(new_resource).to be_updated_by_last_action
+ end
+
+ it "should be case-insensitive" do
+ allow_remote_list(["conemu"])
+ new_resource.package_name("conemu")
+ new_resource.version("15.10.25.1")
+ provider.load_current_resource
+ expect(provider).to receive(:shell_out!).with("#{choco_exe} install -y -version 15.10.25.1 conemu", {:timeout=>timeout}).and_return(double)
+ provider.run_action(:install)
+ expect(new_resource).to be_updated_by_last_action
+ end
+
+ it "should split up commands when given two packages, one with a version pin" do
+ allow_remote_list(["ConEmu", "git"])
+ new_resource.package_name(["ConEmu", "git"])
+ new_resource.version(["15.10.25.1", nil])
+ provider.load_current_resource
+ expect(provider).to receive(:shell_out!).with("#{choco_exe} install -y -version 15.10.25.1 ConEmu", {:timeout=>timeout}).and_return(double)
+ expect(provider).to receive(:shell_out!).with("#{choco_exe} install -y git", {:timeout=>timeout}).and_return(double)
+ provider.run_action(:install)
+ expect(new_resource).to be_updated_by_last_action
+ end
+
+ it "should do multipackage installs when given two packages without constraints" do
+ allow_remote_list(["git", "munin-node"])
+ new_resource.package_name(["git", "munin-node"])
+ provider.load_current_resource
+ expect(provider).to receive(:shell_out!).with("#{choco_exe} install -y git munin-node", {:timeout=>timeout}).and_return(double)
+ provider.run_action(:install)
+ expect(new_resource).to be_updated_by_last_action
+ end
+
+ context "when passing a source argument" do
+ it "should pass options into the install command" do
+ allow_remote_list(["git"], " -source localpackages")
+ new_resource.source("localpackages")
+ provider.load_current_resource
+ expect(provider).to receive(:shell_out!).with("#{choco_exe} install -y -source localpackages git", {:timeout=>timeout}).and_return(double)
+ provider.run_action(:install)
+ expect(new_resource).to be_updated_by_last_action
+ end
+ end
+
+ it "should pass options into the install command" do
+ allow_remote_list(["git"])
+ new_resource.options("-force")
+ provider.load_current_resource
+ expect(provider).to receive(:shell_out!).with("#{choco_exe} install -y -force git", {:timeout=>timeout}).and_return(double)
+ provider.run_action(:install)
+ expect(new_resource).to be_updated_by_last_action
+ end
+
+ it "installing a package that does not exist throws an error" do
+ allow_remote_list(["package-does-not-exist"])
+ new_resource.package_name("package-does-not-exist")
+ provider.load_current_resource
+ expect { provider.run_action(:install) }.to raise_error(Chef::Exceptions::Package)
+ end
+
+ it "installing multiple packages with a package that does not exist throws an error" do
+ allow_remote_list(["git", "package-does-not-exist"])
+ new_resource.package_name(["git", "package-does-not-exist"])
+ provider.load_current_resource
+ expect { provider.run_action(:install) }.to raise_error(Chef::Exceptions::Package)
+ end
+ end
+
+ describe "#action_upgrade" do
+ it "should install a package that is not installed" do
+ allow_remote_list(["git"])
+ provider.load_current_resource
+ expect(provider).to receive(:shell_out!).with("#{choco_exe} upgrade -y git", {:timeout=>timeout}).and_return(double)
+ provider.run_action(:upgrade)
+ expect(new_resource).to be_updated_by_last_action
+ end
+
+ it "should upgrade a package that is installed but upgradable" do
+ allow_remote_list(["ConEmu"])
+ new_resource.package_name("ConEmu")
+ provider.load_current_resource
+ expect(provider).to receive(:shell_out!).with("#{choco_exe} upgrade -y ConEmu", {:timeout=>timeout}).and_return(double)
+ provider.run_action(:upgrade)
+ expect(new_resource).to be_updated_by_last_action
+ end
+
+ it "should be case insensitive" do
+ allow_remote_list(["conemu"])
+ new_resource.package_name("conemu")
+ provider.load_current_resource
+ expect(provider).to receive(:shell_out!).with("#{choco_exe} upgrade -y conemu", {:timeout=>timeout}).and_return(double)
+ provider.run_action(:upgrade)
+ expect(new_resource).to be_updated_by_last_action
+ end
+
+ it "should not install a package that is up-to-date" do
+ allow_remote_list(["chocolatey"])
+ new_resource.package_name("chocolatey")
+ provider.load_current_resource
+ expect(provider).not_to receive(:shell_out!).with("#{choco_exe} upgrade -y chocolatey", {:timeout=>timeout})
+ provider.run_action(:upgrade)
+ expect(new_resource).not_to be_updated_by_last_action
+ end
+
+ it "version pins work as well" do
+ allow_remote_list(["git"])
+ new_resource.version("99.99.99.99")
+ provider.load_current_resource
+ expect(provider).to receive(:shell_out!).with("#{choco_exe} upgrade -y -version 99.99.99.99 git", {:timeout=>timeout})
+ provider.run_action(:upgrade)
+ expect(new_resource).to be_updated_by_last_action
+ end
+
+ it "upgrading multiple packages uses a single command" do
+ allow_remote_list(["conemu", "git"])
+ new_resource.package_name(["conemu", "git"])
+ expect(provider).to receive(:shell_out!).with("#{choco_exe} upgrade -y conemu git", {:timeout=>timeout}).and_return(double)
+ provider.run_action(:upgrade)
+ expect(new_resource).to be_updated_by_last_action
+ end
+
+ it "upgrading a package that does not exist throws an error" do
+ allow_remote_list(["package-does-not-exist"])
+ new_resource.package_name("package-does-not-exist")
+ provider.load_current_resource
+ expect { provider.run_action(:upgrade) }.to raise_error(Chef::Exceptions::Package)
+ end
+
+ it "upgrading multiple packages with a package that does not exist throws an error" do
+ allow_remote_list(["git", "package-does-not-exist"])
+ new_resource.package_name(["git", "package-does-not-exist"])
+ provider.load_current_resource
+ expect { provider.run_action(:upgrade) }.to raise_error(Chef::Exceptions::Package)
+ end
+ end
+
+ describe "#action_remove" do
+ it "does nothing when the package is already removed" do
+ allow_remote_list(["git"])
+ provider.load_current_resource
+ expect(provider).not_to receive(:remove_package)
+ provider.run_action(:remove)
+ expect(new_resource).not_to be_updated_by_last_action
+ end
+
+ it "does nothing when all the packages are already removed" do
+ allow_remote_list(["git", "package-does-not-exist"])
+ new_resource.package_name(["git", "package-does-not-exist"])
+ provider.load_current_resource
+ expect(provider).not_to receive(:remove_package)
+ provider.run_action(:remove)
+ expect(new_resource).not_to be_updated_by_last_action
+ end
+
+ it "removes a package" do
+ allow_remote_list(["ConEmu"])
+ new_resource.package_name("ConEmu")
+ provider.load_current_resource
+ expect(provider).to receive(:shell_out!).with("#{choco_exe} uninstall -y ConEmu", {:timeout=>timeout}).and_return(double)
+ provider.run_action(:remove)
+ expect(new_resource).to be_updated_by_last_action
+ end
+
+ it "is case-insensitive" do
+ allow_remote_list(["conemu"])
+ new_resource.package_name("conemu")
+ provider.load_current_resource
+ expect(provider).to receive(:shell_out!).with("#{choco_exe} uninstall -y conemu", {:timeout=>timeout}).and_return(double)
+ provider.run_action(:remove)
+ expect(new_resource).to be_updated_by_last_action
+ end
+
+ it "removes a single package when its the only one installed" do
+ pending "this is a bug in the superclass"
+ allow_remote_list(["git", "conemu"])
+ new_resource.package_name(["git", "conemu"])
+ provider.load_current_resource
+ expect(provider).to receive(:shell_out!).with("#{choco_exe} uninstall -y conemu", {:timeout=>timeout}).and_return(double)
+ provider.run_action(:remove)
+ expect(new_resource).to be_updated_by_last_action
+ end
+ end
+
+ describe "#action_uninstall" do
+ it "should call :remove with a deprecation warning" do
+ Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ expect(Chef::Log).to receive(:deprecation).with(/please use :remove/)
+ allow_remote_list(["ConEmu"])
+ new_resource.package_name("ConEmu")
+ provider.load_current_resource
+ expect(provider).to receive(:remove_package)
+ provider.run_action(:uninstall)
+ expect(new_resource).to be_updated_by_last_action
+ end
+ end
+end
diff --git a/spec/unit/provider/package/zypper_spec.rb b/spec/unit/provider/package/zypper_spec.rb
index 661bca4c1b..5fb3abe875 100644
--- a/spec/unit/provider/package/zypper_spec.rb
+++ b/spec/unit/provider/package/zypper_spec.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/spec/unit/resource/chocolatey_package_spec.rb b/spec/unit/resource/chocolatey_package_spec.rb
new file mode 100644
index 0000000000..38f1d2905c
--- /dev/null
+++ b/spec/unit/resource/chocolatey_package_spec.rb
@@ -0,0 +1,67 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Resource::ChocolateyPackage do
+
+ let(:resource) { Chef::Resource::ChocolateyPackage.new("fakey_fakerton") }
+
+ it "should create a new Chef::Resource::ChocolateyPackage" do
+ expect(resource).to be_a_kind_of(Chef::Resource)
+ expect(resource).to be_a_kind_of(Chef::Resource::Package)
+ expect(resource).to be_a_instance_of(Chef::Resource::ChocolateyPackage)
+ end
+
+ it "should have a resource name of :python" do
+ expect(resource.resource_name).to eql(:chocolatey_package)
+ end
+
+ it "should coerce its name to a package_name array" do
+ expect(resource.package_name).to eql(["fakey_fakerton"])
+ end
+
+ it "the package_name setter should coerce to arrays" do
+ resource.package_name("git")
+ expect(resource.package_name).to eql(["git"])
+ end
+
+ it "the package_name setter should accept arrays" do
+ resource.package_name(["git", "unzip"])
+ expect(resource.package_name).to eql(["git", "unzip"])
+ end
+
+ it "the name should accept arrays" do
+ resource = Chef::Resource::ChocolateyPackage.new(["git", "unzip"])
+ expect(resource.package_name).to eql(["git", "unzip"])
+ end
+
+ it "the default version should be nil" do
+ expect(resource.version).to eql(nil)
+ end
+
+ it "the version setter should coerce to arrays" do
+ resource.version("1.2.3")
+ expect(resource.version).to eql(["1.2.3"])
+ end
+
+ it "the version setter should accept arrays" do
+ resource.version(["1.2.3", "4.5.6"])
+ expect(resource.version).to eql(["1.2.3", "4.5.6"])
+ end
+end