summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Smith <tsmith@chef.io>2021-09-21 14:42:51 -0700
committerGitHub <noreply@github.com>2021-09-21 14:42:51 -0700
commit772863d1f16271f566302223961d96f45d093169 (patch)
tree6a563e5d63f4eea3f5f07154ad36b48f02bc9c62
parent77a27db0403c6fe482d8145d9a5e77a75356f495 (diff)
parent1f6613ed5704277707852fc02164a0cfb17bd55b (diff)
downloadchef-772863d1f16271f566302223961d96f45d093169.tar.gz
Merge pull request #11907 from jasonwbarnett/feature/add-component-strip
Add strip_components property to archive_file
-rw-r--r--.expeditor/verify.pipeline.yml14
-rw-r--r--lib/chef/resource/archive_file.rb31
-rw-r--r--spec/data/archive_file/test_archive.tar.gzbin0 -> 224 bytes
-rw-r--r--spec/functional/resource/archive_file_spec.rb87
-rw-r--r--spec/unit/resource/archive_file_spec.rb417
5 files changed, 529 insertions, 20 deletions
diff --git a/.expeditor/verify.pipeline.yml b/.expeditor/verify.pipeline.yml
index 5bbfb25e9c..99d2109d8f 100644
--- a/.expeditor/verify.pipeline.yml
+++ b/.expeditor/verify.pipeline.yml
@@ -59,7 +59,7 @@ steps:
commands:
- /workdir/.expeditor/scripts/bk_container_prep.sh
- apt-get update -y
- - apt-get install -y cron locales # needed for functional tests to pass
+ - apt-get install -y cron locales libarchive-dev # needed for functional tests to pass
- cd /workdir; bundle config set --local without omnibus_package
- bundle install --jobs=3 --retry=3 --path=vendor/bundle
- bundle exec rake spec:functional
@@ -72,6 +72,8 @@ steps:
- label: "Unit Ubuntu 18.04 :ruby: 3.0"
commands:
- /workdir/.expeditor/scripts/bk_container_prep.sh
+ - apt-get update -y
+ - apt-get install -y libarchive-dev
- bundle config set --local without omnibus_package
- bundle install --jobs=3 --retry=3 --path=vendor/bundle
- bundle exec rake spec:unit
@@ -97,7 +99,7 @@ steps:
commands:
- /workdir/.expeditor/scripts/bk_container_prep.sh
- apt-get update -y
- - apt-get install -y cron locales # needed for functional tests to pass
+ - apt-get install -y cron locales libarchive-dev # needed for functional tests to pass
- cd /workdir; bundle config set --local without omnibus_package
- bundle install --jobs=3 --retry=3 --path=vendor/bundle
- bundle exec rake spec:functional
@@ -110,6 +112,8 @@ steps:
- label: "Unit Ubuntu 20.04 :ruby: 3.0"
commands:
- /workdir/.expeditor/scripts/bk_container_prep.sh
+ - apt-get update -y
+ - apt-get install -y libarchive-dev
- bundle config set --local without omnibus_package
- bundle install --jobs=3 --retry=3 --path=vendor/bundle
- bundle exec rake spec:unit
@@ -135,6 +139,7 @@ steps:
commands:
- /workdir/.expeditor/scripts/bk_container_prep.sh
- yum install -y crontabs e2fsprogs
+ - yum install -y libarchive-devel
- cd /workdir; bundle config set --local without omnibus_package
- bundle install --jobs=3 --retry=3 --path=vendor/bundle
- bundle exec rake spec:functional
@@ -147,6 +152,7 @@ steps:
- label: "Unit CentOS 7 :ruby: 3.0"
commands:
- /workdir/.expeditor/scripts/bk_container_prep.sh
+ - yum install -y libarchive-devel
- bundle config set --local without omnibus_package
- bundle install --jobs=3 --retry=3 --path=vendor/bundle
- bundle exec rake spec:unit
@@ -173,6 +179,7 @@ steps:
commands:
- /workdir/.expeditor/scripts/bk_container_prep.sh
- zypper install -y cronie insserv-compat
+ - zypper install -y libarchive-devel
- cd /workdir; bundle config set --local without omnibus_package
- bundle install --jobs=3 --retry=3 --path=vendor/bundle
- bundle exec rake spec:functional
@@ -185,7 +192,7 @@ steps:
- label: "Unit openSUSE 15 :ruby: 3.0"
commands:
- /workdir/.expeditor/scripts/bk_container_prep.sh
- - zypper install -y cron insserv-compat
+ - zypper install -y cron insserv-compat libarchive-devel
- bundle config set --local without omnibus_package
- bundle install --jobs=3 --retry=3 --path=vendor/bundle
- bundle exec rake spec:unit
@@ -226,6 +233,7 @@ steps:
- label: "Unit Fedora :ruby: 3.0"
commands:
- /workdir/.expeditor/scripts/bk_container_prep.sh
+ - dnf install -y libarchive-devel
- bundle config set --local without omnibus_package
- bundle install --jobs=3 --retry=3 --path=vendor/bundle
- bundle exec rake spec:unit
diff --git a/lib/chef/resource/archive_file.rb b/lib/chef/resource/archive_file.rb
index 212835114b..e47d8036b8 100644
--- a/lib/chef/resource/archive_file.rb
+++ b/lib/chef/resource/archive_file.rb
@@ -81,6 +81,11 @@ class Chef
description: "Should the resource overwrite the destination file contents if they already exist? If set to `:auto` the date stamp of files within the archive will be compared to those on disk and disk contents will be overwritten if they differ. This may cause unintended consequences if disk date stamps are changed between runs, which will result in the files being overwritten during each client run. Make sure to properly test any change to this property.",
default: false
+ property :strip_components, Integer,
+ description: "Remove the specified number of leading path elements. Pathnames with fewer elements will be silently skipped. This behaves similarly to tar's --strip-components command line argument.",
+ introduced: "17.5",
+ default: 0
+
# backwards compatibility for the legacy cookbook names
alias_method :extract_options, :options
alias_method :extract_to, :destination
@@ -117,7 +122,7 @@ class Chef
if new_resource.owner || new_resource.group
converge_by("set owner of files extracted in #{new_resource.destination} to #{new_resource.owner}:#{new_resource.group}") do
- archive = Archive::Reader.open_filename(new_resource.path)
+ archive = Archive::Reader.open_filename(new_resource.path, nil, strip_components: new_resource.strip_components)
archive.each_entry do |e|
FileUtils.chown(new_resource.owner, new_resource.group, "#{new_resource.destination}/#{e.pathname}")
end
@@ -160,18 +165,16 @@ class Chef
# @return [Boolean]
def archive_differs_from_disk?(src, dest)
modified = false
- Dir.chdir(dest) do
- archive = Archive::Reader.open_filename(src)
- Chef::Log.trace("Beginning the comparison of file mtime between contents of #{src} and #{dest}")
- archive.each_entry do |e|
- pathname = ::File.expand_path(e.pathname)
- if ::File.exist?(pathname)
- Chef::Log.trace("#{pathname} mtime is #{::File.mtime(pathname)} and archive is #{e.mtime}")
- modified = true unless ::File.mtime(pathname) == e.mtime
- else
- Chef::Log.trace("#{pathname} doesn't exist on disk, but exists in the archive")
- modified = true
- end
+ archive = Archive::Reader.open_filename(src, nil, strip_components: new_resource.strip_components)
+ Chef::Log.trace("Beginning the comparison of file mtime between contents of #{src} and #{dest}")
+ archive.each_entry do |e|
+ pathname = ::File.expand_path(e.pathname, dest)
+ if ::File.exist?(pathname)
+ Chef::Log.trace("#{pathname} mtime is #{::File.mtime(pathname)} and archive is #{e.mtime}")
+ modified = true unless ::File.mtime(pathname) == e.mtime
+ else
+ Chef::Log.trace("#{pathname} doesn't exist on disk, but exists in the archive")
+ modified = true
end
end
modified
@@ -189,7 +192,7 @@ class Chef
flags = [options].flatten.map { |option| extract_option_map[option] }.compact.reduce(:|)
Dir.chdir(dest) do
- archive = Archive::Reader.open_filename(src)
+ archive = Archive::Reader.open_filename(src, nil, strip_components: new_resource.strip_components)
archive.each_entry do |e|
archive.extract(e, flags.to_i)
diff --git a/spec/data/archive_file/test_archive.tar.gz b/spec/data/archive_file/test_archive.tar.gz
new file mode 100644
index 0000000000..a75a3a6ec0
--- /dev/null
+++ b/spec/data/archive_file/test_archive.tar.gz
Binary files differ
diff --git a/spec/functional/resource/archive_file_spec.rb b/spec/functional/resource/archive_file_spec.rb
new file mode 100644
index 0000000000..11ef5fa5fc
--- /dev/null
+++ b/spec/functional/resource/archive_file_spec.rb
@@ -0,0 +1,87 @@
+#
+# Copyright:: Copyright (c) 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 "tmpdir"
+
+# Excluding this test on Windows until CI issues can be addressed.
+describe Chef::Resource::ArchiveFile, :not_supported_on_windows do
+ include RecipeDSLHelper
+
+ let(:tmp_path) { Dir.mktmpdir }
+ let(:extract_destination) { "#{tmp_path}/extract_here" }
+ let(:test_archive_path) { File.expand_path("archive_file/test_archive.tar.gz", CHEF_SPEC_DATA) }
+
+ after do
+ FileUtils.remove_entry_secure(extract_destination) if File.exist?(extract_destination)
+ end
+
+ context "when strip_components is 0" do
+ it "extracts archive to destination" do
+ af = archive_file test_archive_path do
+ destination extract_destination
+ end
+ af.should_be_updated
+
+ expect(af.strip_components).to eq(0) # Validate defaults haven't changed here
+ expect(Dir.glob("#{extract_destination}/**/*").length).to eq(4)
+ expect(Dir.exist?("#{extract_destination}/folder-1")).to eq(true)
+ expect(File.exist?("#{extract_destination}/folder-1/file-1.txt")).to eq(true)
+ expect(Dir.exist?("#{extract_destination}/folder-1/folder-2")).to eq(true)
+ expect(File.exist?("#{extract_destination}/folder-1/folder-2/file-2.txt")).to eq(true)
+ end
+ end
+
+ context "when strip_components is 1" do
+ it "extracts archive to destination, with 1 component stripped" do
+ archive_file test_archive_path do
+ destination extract_destination
+ strip_components 1
+ end.should_be_updated
+
+ expect(Dir.exist?("#{extract_destination}/folder-1")).to eq(false)
+ expect(File.exist?("#{extract_destination}/folder-1/file-1.txt")).to eq(false)
+ expect(Dir.exist?("#{extract_destination}/folder-1/folder-2")).to eq(false)
+ expect(File.exist?("#{extract_destination}/folder-1/folder-2/file-2.txt")).to eq(false)
+
+ expect(Dir.glob("#{extract_destination}/**/*").length).to eq(3)
+ expect(File.exist?("#{extract_destination}/file-1.txt")).to eq(true)
+ expect(Dir.exist?("#{extract_destination}/folder-2")).to eq(true)
+ expect(File.exist?("#{extract_destination}/folder-2/file-2.txt")).to eq(true)
+ end
+ end
+
+ context "when strip_components is 2" do
+ it "extracts archive to destination, with 2 components stripped" do
+ archive_file test_archive_path do
+ destination extract_destination
+ strip_components 2
+ end.should_be_updated
+
+ expect(Dir.exist?("#{extract_destination}/folder-1")).to eq(false)
+ expect(File.exist?("#{extract_destination}/folder-1/file-1.txt")).to eq(false)
+ expect(Dir.exist?("#{extract_destination}/folder-1/folder-2")).to eq(false)
+ expect(File.exist?("#{extract_destination}/folder-1/folder-2/file-2.txt")).to eq(false)
+ expect(File.exist?("#{extract_destination}/file-1.txt")).to eq(false)
+ expect(Dir.exist?("#{extract_destination}/folder-2")).to eq(false)
+ expect(File.exist?("#{extract_destination}/folder-2/file-2.txt")).to eq(false)
+
+ expect(Dir.glob("#{extract_destination}/**/*").length).to eq(1)
+ expect(File.exist?("#{extract_destination}/file-2.txt")).to eq(true)
+ end
+ end
+end
diff --git a/spec/unit/resource/archive_file_spec.rb b/spec/unit/resource/archive_file_spec.rb
index 6effe550db..63096cb6ee 100644
--- a/spec/unit/resource/archive_file_spec.rb
+++ b/spec/unit/resource/archive_file_spec.rb
@@ -17,19 +17,60 @@
require "spec_helper"
-describe Chef::Resource::ArchiveFile do
+begin
+ require 'ffi-libarchive'
+rescue LoadError
+ module Archive
+ class Reader
+ def close; end
+ def each_entry; end
+ def extract(entry, flags = 0, destination: nil); end
+ end
+ end
+end
+
+# Excluding this test on Windows until CI issues can be addressed.
+describe Chef::Resource::ArchiveFile, :not_supported_on_windows do
let(:node) { Chef::Node.new }
let(:events) { Chef::EventDispatch::Dispatcher.new }
let(:run_context) { Chef::RunContext.new(node, {}, events) }
- let(:resource) { Chef::Resource::ArchiveFile.new("foo", run_context) }
+ let(:destination) { Dir.mktmpdir }
+ let(:path) { File.expand_path("/tmp/foo.zip") }
+ let(:resource) do
+ r = Chef::Resource::ArchiveFile.new(path, run_context)
+ r.destination = destination
+ r
+ end
let(:provider) { resource.provider_for_action(:extract) }
+ let(:entry_time) { Time.new(2021, 5, 25, 2, 2, 0, "-05:00") }
+ let(:older_time) { entry_time - 100 }
+ let(:newer_time) { entry_time + 100 }
+
+ let(:archive_reader) { instance_double("Archive::Reader", close: nil) }
+ let(:archive_entry_1) { instance_double("Archive::Entry", pathname: "folder-1/", mtime: entry_time) }
+ let(:archive_entry_2) { instance_double("Archive::Entry", pathname: "folder-1/file-1.txt", mtime: entry_time) }
+ let(:archive_entry_3) { instance_double("Archive::Entry", pathname: "folder-1/folder-2/", mtime: entry_time) }
+ let(:archive_entry_4) { instance_double("Archive::Entry", pathname: "folder-1/folder-2/file-2.txt", mtime: entry_time) }
+
+ let(:archive_reader_with_strip_components_1) { instance_double("Archive::Reader", close: nil) }
+ let(:archive_entry_2_s1) { instance_double("Archive::Entry", pathname: "file-1.txt", mtime: entry_time) }
+ let(:archive_entry_3_s1) { instance_double("Archive::Entry", pathname: "folder-2/", mtime: entry_time) }
+ let(:archive_entry_4_s1) { instance_double("Archive::Entry", pathname: "folder-2/file-2.txt", mtime: entry_time) }
+
+ let(:archive_reader_with_strip_components_2) { instance_double("Archive::Reader", close: nil) }
+ let(:archive_entry_4_s2) { instance_double("Archive::Entry", pathname: "file-2.txt", mtime: entry_time) }
+
+ before do
+ allow(resource).to receive(:provider_for_action).with(:extract).and_return(provider)
+ end
it "has a resource name of :archive_file" do
expect(resource.resource_name).to eql(:archive_file)
end
it "has a name property of path" do
- expect(resource.path).to match(/.*foo$/)
+ r = Chef::Resource::ArchiveFile.new("my-name", run_context)
+ expect(r.path).to match("my-name")
end
it "sets the default action as :extract" do
@@ -44,6 +85,376 @@ describe Chef::Resource::ArchiveFile do
expect(resource.mode).to eql("755")
end
+ it "strip_components property defaults to 0" do
+ expect(resource.strip_components).to eql(0)
+ end
+
+ describe "#action_extract" do
+ before do
+ allow(FileUtils).to receive(:mkdir_p)
+ allow(File).to receive(:exist?).and_call_original
+ allow(File).to receive(:exist?).with(path).and_return(true)
+ allow(File).to receive(:exist?).with(destination).and_return(true)
+
+ allow(Archive::Reader).to receive(:open_filename).with(path, nil, strip_components: 0).and_return(archive_reader)
+ allow(archive_reader).to receive(:each_entry)
+ .and_yield(archive_entry_1)
+ .and_yield(archive_entry_2)
+ .and_yield(archive_entry_3)
+ .and_yield(archive_entry_4)
+ allow(archive_reader).to receive(:extract).with(archive_entry_1, any_args)
+ allow(archive_reader).to receive(:extract).with(archive_entry_2, any_args)
+ allow(archive_reader).to receive(:extract).with(archive_entry_3, any_args)
+ allow(archive_reader).to receive(:extract).with(archive_entry_4, any_args)
+
+ allow(Archive::Reader).to receive(:open_filename).with(path, nil, strip_components: 1).and_return(archive_reader_with_strip_components_1)
+ allow(archive_reader_with_strip_components_1).to receive(:each_entry)
+ .and_yield(archive_entry_2_s1)
+ .and_yield(archive_entry_3_s1)
+ .and_yield(archive_entry_4_s1)
+ allow(archive_reader_with_strip_components_1).to receive(:extract).with(archive_entry_2_s1, any_args)
+ allow(archive_reader_with_strip_components_1).to receive(:extract).with(archive_entry_3_s1, any_args)
+ allow(archive_reader_with_strip_components_1).to receive(:extract).with(archive_entry_4_s1, any_args)
+
+ allow(Archive::Reader).to receive(:open_filename).with(path, nil, strip_components: 2).and_return(archive_reader_with_strip_components_2)
+ allow(archive_reader_with_strip_components_2).to receive(:each_entry)
+ .and_yield(archive_entry_4_s2)
+ allow(archive_reader_with_strip_components_2).to receive(:extract).with(archive_entry_4_s2, any_args)
+
+ allow(File).to receive(:exist?).with("#{destination}/folder-1").and_return(true)
+ allow(File).to receive(:exist?).with("#{destination}/folder-1/file-1.txt").and_return(true)
+ allow(File).to receive(:exist?).with("#{destination}/folder-1/folder-2").and_return(true)
+ allow(File).to receive(:exist?).with("#{destination}/folder-1/folder-2/file-2.txt").and_return(true)
+
+ allow(File).to receive(:mtime).with("#{destination}/folder-1").and_return(entry_time)
+ allow(File).to receive(:mtime).with("#{destination}/folder-1/file-1.txt").and_return(entry_time)
+ allow(File).to receive(:mtime).with("#{destination}/folder-1/folder-2").and_return(entry_time)
+ allow(File).to receive(:mtime).with("#{destination}/folder-1/folder-2/file-2.txt").and_return(entry_time)
+
+ resource.overwrite(true) # Force it to converge
+ end
+
+ context "when destination directory does not exist" do
+ before do
+ allow(File).to receive(:exist?).with(destination).and_return(false)
+ end
+
+ it "creates destination directory" do
+ expect(FileUtils).to receive(:mkdir_p).with(destination, { mode: 493 })
+ resource.run_action(:extract)
+ end
+ end
+
+ context "when destination directory exists" do
+ before do
+ allow(File).to receive(:exist?).with(destination).and_return(true)
+ end
+
+ it "does not create destination directory" do
+ expect(FileUtils).not_to receive(:mkdir_p)
+ resource.run_action(:extract)
+ end
+
+ context "when overwrite is set to false" do
+ before do
+ resource.overwrite(false)
+ end
+
+ context "when files on disk have identical modified times than what is in the archive" do
+ before do
+ allow(File).to receive(:mtime).with("#{destination}/folder-1").and_return(entry_time)
+ allow(File).to receive(:mtime).with("#{destination}/folder-1/file-1.txt").and_return(entry_time)
+ allow(File).to receive(:mtime).with("#{destination}/folder-1/folder-2").and_return(entry_time)
+ allow(File).to receive(:mtime).with("#{destination}/folder-1/folder-2/file-2.txt").and_return(entry_time)
+ end
+
+ it "does not extract archive" do
+ expect(provider).not_to receive(:extract)
+ resource.run_action(:extract)
+ end
+ end
+
+ context "when files on disk have newer modified times than what is in the archive" do
+ before do
+ allow(File).to receive(:mtime).with("#{destination}/folder-1").and_return(newer_time)
+ allow(File).to receive(:mtime).with("#{destination}/folder-1/file-1.txt").and_return(newer_time)
+ allow(File).to receive(:mtime).with("#{destination}/folder-1/folder-2").and_return(newer_time)
+ allow(File).to receive(:mtime).with("#{destination}/folder-1/folder-2/file-2.txt").and_return(newer_time)
+ end
+
+ it "does not extract archive" do
+ expect(provider).not_to receive(:extract)
+ resource.run_action(:extract)
+ end
+ end
+
+ context "when files on disk have older modified times than what is in the archive" do
+ before do
+ allow(File).to receive(:mtime).with("#{destination}/folder-1").and_return(older_time)
+ allow(File).to receive(:mtime).with("#{destination}/folder-1/file-1.txt").and_return(older_time)
+ allow(File).to receive(:mtime).with("#{destination}/folder-1/folder-2").and_return(older_time)
+ allow(File).to receive(:mtime).with("#{destination}/folder-1/folder-2/file-2.txt").and_return(older_time)
+ end
+
+ it "does not extract archive" do
+ expect(provider).not_to receive(:extract)
+ resource.run_action(:extract)
+ end
+ end
+ end
+
+ context "when overwrite is set to true" do
+ before do
+ resource.overwrite(true)
+ end
+
+ context "when files on disk have identical modified times than what is in the archive" do
+ before do
+ allow(File).to receive(:mtime).with("#{destination}/folder-1").and_return(entry_time)
+ allow(File).to receive(:mtime).with("#{destination}/folder-1/file-1.txt").and_return(entry_time)
+ allow(File).to receive(:mtime).with("#{destination}/folder-1/folder-2").and_return(entry_time)
+ allow(File).to receive(:mtime).with("#{destination}/folder-1/folder-2/file-2.txt").and_return(entry_time)
+ end
+
+ it "extracts archive" do
+ expect(provider).to receive(:extract)
+ resource.run_action(:extract)
+ end
+ end
+
+ context "when files on disk have newer modified times than what is in the archive" do
+ before do
+ allow(File).to receive(:mtime).with("#{destination}/folder-1").and_return(newer_time)
+ allow(File).to receive(:mtime).with("#{destination}/folder-1/file-1.txt").and_return(newer_time)
+ allow(File).to receive(:mtime).with("#{destination}/folder-1/folder-2").and_return(newer_time)
+ allow(File).to receive(:mtime).with("#{destination}/folder-1/folder-2/file-2.txt").and_return(newer_time)
+ end
+
+ it "extracts archive" do
+ expect(provider).to receive(:extract)
+ resource.run_action(:extract)
+ end
+ end
+
+ context "when files on disk have older modified times than what is in the archive" do
+ before do
+ allow(File).to receive(:mtime).with("#{destination}/folder-1").and_return(older_time)
+ allow(File).to receive(:mtime).with("#{destination}/folder-1/file-1.txt").and_return(older_time)
+ allow(File).to receive(:mtime).with("#{destination}/folder-1/folder-2").and_return(older_time)
+ allow(File).to receive(:mtime).with("#{destination}/folder-1/folder-2/file-2.txt").and_return(older_time)
+ end
+
+ it "extracts archive" do
+ expect(provider).to receive(:extract)
+ resource.run_action(:extract)
+ end
+ end
+ end
+
+ context "when overwrite is set to :auto" do
+ before do
+ resource.overwrite(:auto)
+ end
+
+ context "when strip_components is set to 0" do
+ before do
+ resource.strip_components(0)
+ end
+
+ context "when files on disk have identical modified times than what is in the archive" do
+ before do
+ allow(File).to receive(:mtime).with("#{destination}/folder-1").and_return(entry_time)
+ allow(File).to receive(:mtime).with("#{destination}/folder-1/file-1.txt").and_return(entry_time)
+ allow(File).to receive(:mtime).with("#{destination}/folder-1/folder-2").and_return(entry_time)
+ allow(File).to receive(:mtime).with("#{destination}/folder-1/folder-2/file-2.txt").and_return(entry_time)
+ end
+
+ context "when there is at least one missing files on disk" do
+ before do
+ expect(File).to receive(:exist?).with("#{destination}/folder-1").and_return(false)
+ expect(File).to receive(:exist?).with("#{destination}/folder-1/file-1.txt").and_return(true)
+ expect(File).to receive(:exist?).with("#{destination}/folder-1/folder-2").and_return(true)
+ expect(File).to receive(:exist?).with("#{destination}/folder-1/folder-2/file-2.txt").and_return(true)
+ end
+
+ it "extracts archive" do
+ expect(provider).to receive(:extract)
+ resource.run_action(:extract)
+ end
+ end
+
+ context "when there are no missing files on disk" do
+ before do
+ expect(File).to receive(:exist?).with("#{destination}/folder-1").and_return(true)
+ expect(File).to receive(:exist?).with("#{destination}/folder-1/file-1.txt").and_return(true)
+ expect(File).to receive(:exist?).with("#{destination}/folder-1/folder-2").and_return(true)
+ expect(File).to receive(:exist?).with("#{destination}/folder-1/folder-2/file-2.txt").and_return(true)
+ end
+
+ it "does not extract archive" do
+ expect(provider).not_to receive(:extract)
+ resource.run_action(:extract)
+ end
+ end
+ end
+ end
+
+ context "when strip_components is set to 1" do
+ before do
+ resource.strip_components(1)
+ end
+
+ context "when files on disk have identical modified times than what is in the archive" do
+ before do
+ allow(File).to receive(:mtime).with("#{destination}/file-1.txt").and_return(entry_time)
+ allow(File).to receive(:mtime).with("#{destination}/folder-2").and_return(entry_time)
+ allow(File).to receive(:mtime).with("#{destination}/folder-2/file-2.txt").and_return(entry_time)
+ end
+
+ context "when there is at least one missing files on disk" do
+ before do
+ expect(File).not_to receive(:exist?).with("#{destination}/folder-1")
+ expect(File).not_to receive(:exist?).with("#{destination}/folder-1/file-1.txt")
+ expect(File).not_to receive(:exist?).with("#{destination}/folder-1/folder-2")
+ expect(File).not_to receive(:exist?).with("#{destination}/folder-1/folder-2/file-2.txt")
+ expect(File).to receive(:exist?).with("#{destination}/file-1.txt").and_return(false)
+ expect(File).to receive(:exist?).with("#{destination}/folder-2").and_return(true)
+ expect(File).to receive(:exist?).with("#{destination}/folder-2/file-2.txt").and_return(true)
+ end
+
+ it "extracts archive" do
+ expect(provider).to receive(:extract)
+ resource.run_action(:extract)
+ end
+ end
+
+ context "when there are no missing files on disk" do
+ before do
+ expect(File).not_to receive(:exist?).with("#{destination}/folder-1")
+ expect(File).not_to receive(:exist?).with("#{destination}/folder-1/file-1.txt")
+ expect(File).not_to receive(:exist?).with("#{destination}/folder-1/folder-2")
+ expect(File).not_to receive(:exist?).with("#{destination}/folder-1/folder-2/file-2.txt")
+ expect(File).to receive(:exist?).with("#{destination}/file-1.txt").and_return(true)
+ expect(File).to receive(:exist?).with("#{destination}/folder-2").and_return(true)
+ expect(File).to receive(:exist?).with("#{destination}/folder-2/file-2.txt").and_return(true)
+ end
+
+ it "does not extract archive" do
+ expect(provider).not_to receive(:extract)
+ resource.run_action(:extract)
+ end
+ end
+ end
+ end
+
+ context "when files on disk have newer modified times than what is in the archive" do
+ before do
+ allow(File).to receive(:mtime).with("#{destination}/folder-1").and_return(newer_time)
+ allow(File).to receive(:mtime).with("#{destination}/folder-1/file-1.txt").and_return(newer_time)
+ allow(File).to receive(:mtime).with("#{destination}/folder-1/folder-2").and_return(newer_time)
+ allow(File).to receive(:mtime).with("#{destination}/folder-1/folder-2/file-2.txt").and_return(newer_time)
+ end
+
+ it "extracts archive" do
+ expect(provider).to receive(:extract)
+ resource.run_action(:extract)
+ end
+ end
+
+ context "when files on disk have older modified times than what is in the archive" do
+ before do
+ allow(File).to receive(:mtime).with("#{destination}/folder-1").and_return(older_time)
+ allow(File).to receive(:mtime).with("#{destination}/folder-1/file-1.txt").and_return(older_time)
+ allow(File).to receive(:mtime).with("#{destination}/folder-1/folder-2").and_return(older_time)
+ allow(File).to receive(:mtime).with("#{destination}/folder-1/folder-2/file-2.txt").and_return(older_time)
+ end
+
+ it "extracts archive" do
+ expect(provider).to receive(:extract)
+ resource.run_action(:extract)
+ end
+ end
+ end
+ end
+
+ context "when strip_components is set to 0" do
+ before do
+ resource.strip_components(0)
+ end
+
+ it "does not strip any paths" do
+ expect(archive_reader).to receive(:extract).with(archive_entry_1, 4)
+ expect(archive_reader).to receive(:extract).with(archive_entry_2, 4)
+ expect(archive_reader).to receive(:extract).with(archive_entry_3, 4)
+ expect(archive_reader).to receive(:extract).with(archive_entry_4, 4)
+ resource.run_action(:extract)
+ end
+ end
+
+ context "when strip_components is set to 1" do
+ before do
+ resource.strip_components(1)
+ end
+
+ it "strips leading number of paths specified in strip_components" do
+ expect(archive_reader_with_strip_components_1).to receive(:extract).with(archive_entry_2_s1, 4)
+ expect(archive_reader_with_strip_components_1).to receive(:extract).with(archive_entry_3_s1, 4)
+ expect(archive_reader_with_strip_components_1).to receive(:extract).with(archive_entry_4_s1, 4)
+ resource.run_action(:extract)
+ end
+ end
+
+ context "when strip_components is set to 2" do
+ before do
+ resource.strip_components(2)
+ end
+
+ it "strips leading number of paths specified in strip_components" do
+ expect(archive_reader_with_strip_components_2).to receive(:extract).with(archive_entry_4_s2, 4)
+ resource.run_action(:extract)
+ end
+ end
+
+ context "when owner property is set" do
+ before { resource.owner "root" }
+
+ it "chowns all archive file/directory paths" do
+ expect(FileUtils).to receive(:chown).with("root", nil, "#{destination}/folder-1/")
+ expect(FileUtils).to receive(:chown).with("root", nil, "#{destination}/folder-1/file-1.txt")
+ expect(FileUtils).to receive(:chown).with("root", nil, "#{destination}/folder-1/folder-2/")
+ expect(FileUtils).to receive(:chown).with("root", nil, "#{destination}/folder-1/folder-2/file-2.txt")
+ resource.run_action(:extract)
+ end
+ end
+
+ context "when group property is set" do
+ before { resource.group "root" }
+
+ it "chowns all archive file/directory paths" do
+ expect(FileUtils).to receive(:chown).with(nil, "root", "#{destination}/folder-1/")
+ expect(FileUtils).to receive(:chown).with(nil, "root", "#{destination}/folder-1/file-1.txt")
+ expect(FileUtils).to receive(:chown).with(nil, "root", "#{destination}/folder-1/folder-2/")
+ expect(FileUtils).to receive(:chown).with(nil, "root", "#{destination}/folder-1/folder-2/file-2.txt")
+ resource.run_action(:extract)
+ end
+ end
+
+ context "when owner and group properties are set" do
+ before do
+ resource.owner "root"
+ resource.group "root"
+ end
+
+ it "chowns all archive file/directory paths" do
+ expect(FileUtils).to receive(:chown).with("root", "root", "#{destination}/folder-1/")
+ expect(FileUtils).to receive(:chown).with("root", "root", "#{destination}/folder-1/file-1.txt")
+ expect(FileUtils).to receive(:chown).with("root", "root", "#{destination}/folder-1/folder-2/")
+ expect(FileUtils).to receive(:chown).with("root", "root", "#{destination}/folder-1/folder-2/file-2.txt")
+ resource.run_action(:extract)
+ end
+ end
+ end
+
it "mode property throws a deprecation warning if Integers are passed" do
expect(Chef::Log).to receive(:deprecation)
resource.mode 755