summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJay Mundrawala <jdmundrawala@gmail.com>2015-05-15 12:09:51 -0500
committerJay Mundrawala <jdmundrawala@gmail.com>2015-05-15 12:09:51 -0500
commitc5fad0e662da7c3ee39cb505723d0b1ee4e65764 (patch)
tree782aaa4945537d0a5ef872236e96f5f91d29b410
parentec4e3f70a71de7de5bc6eb8d81a5ef112f3b6ac6 (diff)
parent4c73a841e018057302b7c0cb10f8f880c282d849 (diff)
downloadchef-c5fad0e662da7c3ee39cb505723d0b1ee4e65764.tar.gz
Merge pull request #3318 from chef/jdm/msi-uri-source
Modify windows package provider to allow url
-rw-r--r--lib/chef/mixin/uris.rb33
-rw-r--r--lib/chef/provider/package/windows.rb96
-rw-r--r--lib/chef/resource/windows_package.rb26
-rw-r--r--spec/unit/mixin/uris_spec.rb45
-rw-r--r--spec/unit/provider/package/windows_spec.rb129
-rw-r--r--spec/unit/resource/windows_package_spec.rb18
6 files changed, 312 insertions, 35 deletions
diff --git a/lib/chef/mixin/uris.rb b/lib/chef/mixin/uris.rb
new file mode 100644
index 0000000000..6c1ae793e2
--- /dev/null
+++ b/lib/chef/mixin/uris.rb
@@ -0,0 +1,33 @@
+#
+# Author:: Jay Mundrawala (<jdm@chef.io>)
+# 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 'uri'
+
+class Chef
+ module Mixin
+ module Uris
+ # uri_scheme? returns true if the string starts with
+ # scheme://
+ # For example, it will match http://foo.bar.com
+ def uri_scheme?(source)
+ # From open-uri
+ !!(%r{\A[A-Za-z][A-Za-z0-9+\-\.]*://} =~ source)
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/windows.rb b/lib/chef/provider/package/windows.rb
index 143d82f111..7ff0b71807 100644
--- a/lib/chef/provider/package/windows.rb
+++ b/lib/chef/provider/package/windows.rb
@@ -16,14 +16,18 @@
# limitations under the License.
#
+require 'chef/mixin/uris'
require 'chef/resource/windows_package'
require 'chef/provider/package'
require 'chef/util/path_helper'
+require 'chef/mixin/checksum'
class Chef
class Provider
class Package
class Windows < Chef::Provider::Package
+ include Chef::Mixin::Uris
+ include Chef::Mixin::Checksum
provides :package, os: "windows"
provides :windows_package, os: "windows"
@@ -36,19 +40,23 @@ class Chef
# load_current_resource is run in Chef::Provider#run_action when not in whyrun_mode?
def load_current_resource
- @new_resource.source(Chef::Util::PathHelper.validate_path(@new_resource.source))
-
@current_resource = Chef::Resource::WindowsPackage.new(@new_resource.name)
- @current_resource.version(package_provider.installed_version)
- @new_resource.version(package_provider.package_version)
- @current_resource
+ if downloadable_file_missing?
+ Chef::Log.debug("We do not know the version of #{new_resource.source} because the file is not downloaded")
+ current_resource.version(:unknown.to_s)
+ else
+ current_resource.version(package_provider.installed_version)
+ new_resource.version(package_provider.package_version)
+ end
+
+ current_resource
end
def package_provider
@package_provider ||= begin
case installer_type
when :msi
- Chef::Provider::Package::Windows::MSI.new(@new_resource)
+ Chef::Provider::Package::Windows::MSI.new(resource_for_provider)
else
raise "Unable to find a Chef::Provider::Package::Windows provider for installer_type '#{installer_type}'"
end
@@ -71,6 +79,17 @@ class Chef
end
end
+ def action_install
+ if uri_scheme?(new_resource.source)
+ download_source_file
+ load_current_resource
+ else
+ validate_content!
+ end
+
+ super
+ end
+
# Chef::Provider::Package action_install + action_remove call install_package + remove_package
# Pass those calls to the correct sub-provider
def install_package(name, version)
@@ -80,6 +99,71 @@ class Chef
def remove_package(name, version)
package_provider.remove_package(name, version)
end
+
+ # @return [Array] new_version(s) as an array
+ def new_version_array
+ # Because the one in the parent caches things
+ [new_resource.version]
+ end
+
+ private
+
+ def downloadable_file_missing?
+ uri_scheme?(new_resource.source) && !::File.exists?(source_location)
+ end
+
+ def resource_for_provider
+ @resource_for_provider = Chef::Resource::WindowsPackage.new(new_resource.name).tap do |r|
+ r.source(Chef::Util::PathHelper.validate_path(source_location))
+ r.timeout(new_resource.timeout)
+ r.returns(new_resource.returns)
+ r.options(new_resource.options)
+ end
+ end
+
+ def download_source_file
+ source_resource.run_action(:create)
+ Chef::Log.debug("#{@new_resource} fetched source file to #{source_resource.path}")
+ end
+
+ def source_resource
+ @source_resource ||= Chef::Resource::RemoteFile.new(default_download_cache_path, run_context).tap do |r|
+ r.source(new_resource.source)
+ r.checksum(new_resource.checksum)
+ r.backup(false)
+
+ if new_resource.remote_file_attributes
+ new_resource.remote_file_attributes.each do |(k,v)|
+ r.send(k.to_sym, v)
+ end
+ end
+ end
+ end
+
+ def default_download_cache_path
+ uri = ::URI.parse(new_resource.source)
+ filename = ::File.basename(::URI.unescape(uri.path))
+ file_cache_dir = Chef::FileCache.create_cache_path("package/")
+ Chef::Util::PathHelper.cleanpath("#{file_cache_dir}/#{filename}")
+ end
+
+ def source_location
+ if uri_scheme?(new_resource.source)
+ source_resource.path
+ else
+ Chef::Util::PathHelper.cleanpath(new_resource.source)
+ end
+ end
+
+ def validate_content!
+ if new_resource.checksum
+ source_checksum = checksum(source_location)
+ if new_resource.checksum != source_checksum
+ raise Chef::Exceptions::ChecksumMismatch.new(short_cksum(new_resource.checksum), short_cksum(source_checksum))
+ end
+ end
+ end
+
end
end
end
diff --git a/lib/chef/resource/windows_package.rb b/lib/chef/resource/windows_package.rb
index 16cfcf865e..d4f8ae0603 100644
--- a/lib/chef/resource/windows_package.rb
+++ b/lib/chef/resource/windows_package.rb
@@ -16,6 +16,7 @@
# limitations under the License.
#
+require 'chef/mixin/uris'
require 'chef/resource/package'
require 'chef/provider/package/windows'
require 'chef/win32/error' if RUBY_PLATFORM =~ /mswin|mingw|windows/
@@ -23,6 +24,7 @@ require 'chef/win32/error' if RUBY_PLATFORM =~ /mswin|mingw|windows/
class Chef
class Resource
class WindowsPackage < Chef::Resource::Package
+ include Chef::Mixin::Uris
provides :package, os: "windows"
provides :windows_package, os: "windows"
@@ -69,10 +71,30 @@ class Chef
@source
else
raise ArgumentError, "Bad type for WindowsPackage resource, use a String" unless arg.is_a?(String)
- Chef::Log.debug("#{package_name}: sanitizing source path '#{arg}'")
- @source = ::File.absolute_path(arg).gsub(::File::SEPARATOR, ::File::ALT_SEPARATOR)
+ if uri_scheme?(arg)
+ @source = arg
+ else
+ @source = Chef::Util::PathHelper.canonical_path(arg, false)
+ end
end
end
+
+ def checksum(arg=nil)
+ set_or_return(
+ :checksum,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
+
+ def remote_file_attributes(arg=nil)
+ set_or_return(
+ :remote_file_attributes,
+ arg,
+ :kind_of => [ Hash ]
+ )
+ end
+
end
end
end
diff --git a/spec/unit/mixin/uris_spec.rb b/spec/unit/mixin/uris_spec.rb
new file mode 100644
index 0000000000..07e4f34508
--- /dev/null
+++ b/spec/unit/mixin/uris_spec.rb
@@ -0,0 +1,45 @@
+#
+# Author:: Jay Mundrawala (<jdm@chef.io>)
+# 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 'spec_helper'
+require 'chef/mixin/uris'
+
+class Chef::UrisTest
+ include Chef::Mixin::Uris
+end
+
+describe Chef::Mixin::Uris do
+ let (:uris) { Chef::UrisTest.new }
+
+ it "matches 'scheme://foo.com'" do
+ expect(uris.uri_scheme?('scheme://foo.com')).to eq(true)
+ end
+
+ it "does not match 'c:/foo.com'" do
+ expect(uris.uri_scheme?('c:/foo.com')).to eq(false)
+ end
+
+ it "does not match '/usr/bin/foo.com'" do
+ expect(uris.uri_scheme?('/usr/bin/foo.com')).to eq(false)
+ end
+
+ it "does not match 'c:/foo.com://bar.com'" do
+ expect(uris.uri_scheme?('c:/foo.com://bar.com')).to eq(false)
+ end
+
+end
diff --git a/spec/unit/provider/package/windows_spec.rb b/spec/unit/provider/package/windows_spec.rb
index d402113d72..e5acc87694 100644
--- a/spec/unit/provider/package/windows_spec.rb
+++ b/spec/unit/provider/package/windows_spec.rb
@@ -19,50 +19,129 @@
require 'spec_helper'
describe Chef::Provider::Package::Windows, :windows_only do
+ before(:each) do
+ allow(Chef::Util::PathHelper).to receive(:windows?).and_return(true)
+ allow(Chef::FileCache).to receive(:create_cache_path).with("package/").and_return(cache_path)
+ end
+
let(:node) { double('Chef::Node') }
let(:events) { double('Chef::Events').as_null_object } # mock all the methods
let(:run_context) { double('Chef::RunContext', :node => node, :events => events) }
- let(:new_resource) { Chef::Resource::WindowsPackage.new("calculator.msi") }
+ let(:resource_source) { 'calculator.msi' }
+ let(:new_resource) { Chef::Resource::WindowsPackage.new(resource_source) }
let(:provider) { Chef::Provider::Package::Windows.new(new_resource, run_context) }
+ let(:cache_path) { 'c:\\cache\\' }
describe "load_current_resource" do
- before(:each) do
- allow(Chef::Util::PathHelper).to receive(:validate_path)
- allow(provider).to receive(:package_provider).and_return(double('package_provider',
+ shared_examples "a local file" do
+ before(:each) do
+ allow(Chef::Util::PathHelper).to receive(:validate_path)
+ allow(provider).to receive(:package_provider).and_return(double('package_provider',
:installed_version => "1.0", :package_version => "2.0"))
- end
+ end
- it "creates a current resource with the name of the new resource" do
- provider.load_current_resource
- expect(provider.current_resource).to be_a(Chef::Resource::WindowsPackage)
- expect(provider.current_resource.name).to eql("calculator.msi")
- end
+ it "creates a current resource with the name of the new resource" do
+ provider.load_current_resource
+ expect(provider.current_resource).to be_a(Chef::Resource::WindowsPackage)
+ expect(provider.current_resource.name).to eql(resource_source)
+ end
+
+ it "sets the current version if the package is installed" do
+ provider.load_current_resource
+ expect(provider.current_resource.version).to eql("1.0")
+ end
- it "sets the current version if the package is installed" do
- provider.load_current_resource
- expect(provider.current_resource.version).to eql("1.0")
+ it "sets the version to be installed" do
+ provider.load_current_resource
+ expect(provider.new_resource.version).to eql("2.0")
+ end
end
- it "sets the version to be installed" do
- provider.load_current_resource
- expect(provider.new_resource.version).to eql("2.0")
+ context "when the source is a uri" do
+ let(:resource_source) { 'https://foo.bar/calculator.msi' }
+
+ context "when the source has not been downloaded" do
+ before(:each) do
+ allow(provider).to receive(:downloadable_file_missing?).and_return(true)
+ end
+ it "sets the current version to unknown" do
+ provider.load_current_resource
+ expect(provider.current_resource.version).to eql("unknown")
+ end
+ end
+
+ context "when the source has been downloaded" do
+ before(:each) do
+ allow(provider).to receive(:downloadable_file_missing?).and_return(false)
+ end
+ it_behaves_like "a local file"
+ end
+
+ context "when remote_file_attributes are provided" do
+ let (:remote_file_attributes) { {:path => 'C:\\foobar.msi'} }
+ before(:each) do
+ new_resource.remote_file_attributes(remote_file_attributes)
+ end
+
+ it 'should override the attributes of the remote file resource used' do
+ expect(::File).to receive(:exists?).with(remote_file_attributes[:path])
+ provider.load_current_resource
+ end
+
+ end
end
- it "checks that the source path is valid" do
- expect(Chef::Util::PathHelper).to receive(:validate_path)
- provider.load_current_resource
+ context "when source is a local file" do
+ it_behaves_like "a local file"
end
end
describe "package_provider" do
- it "sets the package provider to MSI if the the installer type is :msi" do
- allow(provider).to receive(:installer_type).and_return(:msi)
- expect(provider.package_provider).to be_a(Chef::Provider::Package::Windows::MSI)
+ shared_examples "a local file" do
+ it "checks that the source path is valid" do
+ expect(Chef::Util::PathHelper).to receive(:validate_path)
+ provider.package_provider
+ end
+
+ it "sets the package provider to MSI if the the installer type is :msi" do
+ allow(provider).to receive(:installer_type).and_return(:msi)
+ expect(provider.package_provider).to be_a(Chef::Provider::Package::Windows::MSI)
+ end
+
+ it "raises an error if the installer_type is unknown" do
+ allow(provider).to receive(:installer_type).and_return(:apt_for_windows)
+ expect { provider.package_provider }.to raise_error
+ end
+ end
+
+ context "when the source is a uri" do
+ let(:resource_source) { 'https://foo.bar/calculator.msi' }
+
+ context "when the source has not been downloaded" do
+ before(:each) do
+ allow(provider).to receive(:should_download?).and_return(true)
+ end
+
+ it "should create a package provider with source pointing at the local file" do
+ expect(Chef::Provider::Package::Windows::MSI).to receive(:new) do |r|
+ expect(r.source).to eq("#{cache_path}#{::File.basename(resource_source)}")
+ end
+ provider.package_provider
+ end
+
+ it_behaves_like "a local file"
+ end
+
+ context "when the source has been downloaded" do
+ before(:each) do
+ allow(provider).to receive(:should_download?).and_return(false)
+ end
+ it_behaves_like "a local file"
+ end
end
- it "raises an error if the installer_type is unknown" do
- allow(provider).to receive(:installer_type).and_return(:apt_for_windows)
- expect { provider.package_provider }.to raise_error
+ context "when source is a local file" do
+ it_behaves_like "a local file"
end
end
diff --git a/spec/unit/resource/windows_package_spec.rb b/spec/unit/resource/windows_package_spec.rb
index 1e02f2449b..6aa5d357ea 100644
--- a/spec/unit/resource/windows_package_spec.rb
+++ b/spec/unit/resource/windows_package_spec.rb
@@ -63,9 +63,9 @@ describe Chef::Resource::WindowsPackage, "initialize" do
end
it "coverts a source to an absolute path" do
- allow(::File).to receive(:absolute_path).and_return("c:\\Files\\frost.msi")
+ allow(::File).to receive(:absolute_path).and_return("c:\\files\\frost.msi")
resource.source("frost.msi")
- expect(resource.source).to eql "c:\\Files\\frost.msi"
+ expect(resource.source).to eql "c:\\files\\frost.msi"
end
it "converts slashes to backslashes in the source path" do
@@ -78,4 +78,18 @@ describe Chef::Resource::WindowsPackage, "initialize" do
# it's a little late to stub out File.absolute_path
expect(resource.source).to include("solitaire.msi")
end
+
+ it "supports the checksum attribute" do
+ resource.checksum('somechecksum')
+ expect(resource.checksum).to eq('somechecksum')
+ end
+
+ context 'when a URL is used' do
+ let(:resource_source) { 'https://foo.bar/solitare.msi' }
+ let(:resource) { Chef::Resource::WindowsPackage.new(resource_source) }
+
+ it "should return the source unmodified" do
+ expect(resource.source).to eq(resource_source)
+ end
+ end
end