diff options
author | Tim Smith <tsmith@chef.io> | 2021-09-21 14:42:51 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-09-21 14:42:51 -0700 |
commit | 772863d1f16271f566302223961d96f45d093169 (patch) | |
tree | 6a563e5d63f4eea3f5f07154ad36b48f02bc9c62 | |
parent | 77a27db0403c6fe482d8145d9a5e77a75356f495 (diff) | |
parent | 1f6613ed5704277707852fc02164a0cfb17bd55b (diff) | |
download | chef-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.yml | 14 | ||||
-rw-r--r-- | lib/chef/resource/archive_file.rb | 31 | ||||
-rw-r--r-- | spec/data/archive_file/test_archive.tar.gz | bin | 0 -> 224 bytes | |||
-rw-r--r-- | spec/functional/resource/archive_file_spec.rb | 87 | ||||
-rw-r--r-- | spec/unit/resource/archive_file_spec.rb | 417 |
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 Binary files differnew file mode 100644 index 0000000000..a75a3a6ec0 --- /dev/null +++ b/spec/data/archive_file/test_archive.tar.gz 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 |