summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Smith <tsmith@chef.io>2019-04-15 12:50:56 -0700
committerGitHub <noreply@github.com>2019-04-15 12:50:56 -0700
commit094e9bb5a5c374369d387d688194d2ace659bc3b (patch)
treec92125c4ebe3a9e1a3bc96aabbb0e0280769e5ea
parent5205661286a29a13a520f2bde49e82a864ccf603 (diff)
parent02be687aedc00cbb9d353fdf237895c01d8e730b (diff)
downloadchef-094e9bb5a5c374369d387d688194d2ace659bc3b.tar.gz
Merge pull request #8028 from chef/archive_file
Add a new archive_file resource from the libarchive cookbook
-rw-r--r--Gemfile.lock4
-rw-r--r--chef.gemspec3
-rw-r--r--kitchen-tests/cookbooks/end_to_end/files/tourism.tar.gzbin0 -> 2253 bytes
-rw-r--r--kitchen-tests/cookbooks/end_to_end/files/tourism.tar.xzbin0 -> 2128 bytes
-rw-r--r--kitchen-tests/cookbooks/end_to_end/files/tourism.zipbin0 -> 2680 bytes
-rw-r--r--kitchen-tests/cookbooks/end_to_end/recipes/default.rb12
-rw-r--r--lib/chef/resource/archive_file.rb172
-rw-r--r--lib/chef/resources.rb1
-rw-r--r--spec/unit/resource/archive_file_spec.rb47
9 files changed, 237 insertions, 2 deletions
diff --git a/Gemfile.lock b/Gemfile.lock
index 9a72f0b45b..f01b89a943 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -35,6 +35,7 @@ PATH
diff-lcs (~> 1.2, >= 1.2.4)
erubis (~> 2.7)
ffi (~> 1.9, >= 1.9.25)
+ ffi-libarchive
ffi-yajl (~> 2.2)
highline (~> 1.6, >= 1.6.9)
iniparse (~> 1.4)
@@ -59,6 +60,7 @@ PATH
diff-lcs (~> 1.2, >= 1.2.4)
erubis (~> 2.7)
ffi (~> 1.9, >= 1.9.25)
+ ffi-libarchive
ffi-yajl (~> 2.2)
highline (~> 1.6, >= 1.6.9)
iniparse (~> 1.4)
@@ -136,6 +138,8 @@ GEM
ffi (1.10.0)
ffi (1.10.0-x64-mingw32)
ffi (1.10.0-x86-mingw32)
+ ffi-libarchive (0.4.6)
+ ffi (~> 1.0)
ffi-win32-extensions (1.0.3)
ffi
ffi-yajl (2.3.1)
diff --git a/chef.gemspec b/chef.gemspec
index bb0c1f290e..f6d8e93ab7 100644
--- a/chef.gemspec
+++ b/chef.gemspec
@@ -32,9 +32,8 @@ Gem::Specification.new do |s|
s.add_dependency "highline", "~> 1.6", ">= 1.6.9"
s.add_dependency "erubis", "~> 2.7"
s.add_dependency "diff-lcs", "~> 1.2", ">= 1.2.4"
-
+ s.add_dependency "ffi-libarchive"
s.add_dependency "chef-zero", ">= 14.0.11"
-
s.add_dependency "plist", "~> 3.2"
s.add_dependency "iniparse", "~> 1.4"
s.add_dependency "addressable"
diff --git a/kitchen-tests/cookbooks/end_to_end/files/tourism.tar.gz b/kitchen-tests/cookbooks/end_to_end/files/tourism.tar.gz
new file mode 100644
index 0000000000..a03da304d3
--- /dev/null
+++ b/kitchen-tests/cookbooks/end_to_end/files/tourism.tar.gz
Binary files differ
diff --git a/kitchen-tests/cookbooks/end_to_end/files/tourism.tar.xz b/kitchen-tests/cookbooks/end_to_end/files/tourism.tar.xz
new file mode 100644
index 0000000000..de37cea21e
--- /dev/null
+++ b/kitchen-tests/cookbooks/end_to_end/files/tourism.tar.xz
Binary files differ
diff --git a/kitchen-tests/cookbooks/end_to_end/files/tourism.zip b/kitchen-tests/cookbooks/end_to_end/files/tourism.zip
new file mode 100644
index 0000000000..8c578e691d
--- /dev/null
+++ b/kitchen-tests/cookbooks/end_to_end/files/tourism.zip
Binary files differ
diff --git a/kitchen-tests/cookbooks/end_to_end/recipes/default.rb b/kitchen-tests/cookbooks/end_to_end/recipes/default.rb
index 2779ed92fc..7c9856d909 100644
--- a/kitchen-tests/cookbooks/end_to_end/recipes/default.rb
+++ b/kitchen-tests/cookbooks/end_to_end/recipes/default.rb
@@ -95,4 +95,16 @@ openssl_rsa_public_key "/etc/ssl/rsakey_aes128cbc.pub" do
action :create
end
+# test various archive formats in the archive_file resource
+%w{tourism.tar.gz tourism.tar.xz tourism.zip}.each do |archive|
+ cookbook_file File.join(Chef::Config[:file_cache_path], archive) do
+ source archive
+ end
+
+ archive_file archive do
+ path File.join(Chef::Config[:file_cache_path], archive)
+ extract_to File.join(Chef::Config[:file_cache_path], archive.tr(".", "_"))
+ end
+end
+
include_recipe "::tests"
diff --git a/lib/chef/resource/archive_file.rb b/lib/chef/resource/archive_file.rb
new file mode 100644
index 0000000000..bf057727b5
--- /dev/null
+++ b/lib/chef/resource/archive_file.rb
@@ -0,0 +1,172 @@
+#
+# Copyright:: Copyright 2017-2018, Chef Software Inc.
+# Author:: Jamie Winsor (<jamie@vialstudios.com>)
+# Author:: Tim Smith (<tsmith@chef.io>)
+#
+# 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"
+
+class Chef
+ class Resource
+ class ArchiveFile < Chef::Resource
+
+ resource_name :archive_file
+ provides :archive_file
+ provides :libarchive_file # legacy cookbook name
+
+ introduced "15.0"
+ description "Use the archive_file resource to extract archive files to disk. This resource uses the libarchive library to extract multiple archive formats including tar, gzip, bzip, and zip formats."
+
+ property :path, String,
+ name_property: true,
+ coerce: proc { |f| ::File.expand_path(f) },
+ description: "An optional property to set the file path to the archive to extract if it differs from the resource block's name."
+
+ property :owner, String,
+ description: "The owner of the extracted files."
+
+ property :group, String,
+ description: "The group of the extracted files."
+
+ property :mode, [String, Integer],
+ description: "The mode of the extracted files.",
+ default: "755"
+
+ property :destination, String,
+ description: "The file path to extract the archive file to.",
+ required: true
+
+ property :options, [Array, Symbol],
+ description: "An array of symbols representing extraction flags. Example: :no_overwrite to prevent overwriting files on disk. By default this properly sets :time which preserves the modification timestamps of files in the archive when writing them to disk.",
+ default: lazy { [:time] }
+
+ property :overwrite, [TrueClass, FalseClass, :auto],
+ 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 unintented consequences if on 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
+
+ # backwards compatibility for the legacy cookbook names
+ alias_method :extract_options, :options
+ alias_method :extract_to, :destination
+
+ require "fileutils"
+
+ action :extract do
+ description "Extract and archive file."
+
+ unless ::File.exist?(new_resource.path)
+ raise Errno::ENOENT, "No archive found at #{new_resource.path}! Cannot continue."
+ end
+
+ if !::File.exist?(new_resource.destination)
+ Chef::Log.trace("File or directory does not exist at destination path: #{new_resource.destination}")
+
+ converge_by("create directory #{new_resource.destination}") do
+ FileUtils.mkdir_p(new_resource.destination, mode: new_resource.mode.to_i)
+ end
+
+ extract(new_resource.path, new_resource.destination, Array(new_resource.options))
+ else
+ Chef::Log.trace("File or directory exists at destination path: #{new_resource.destination}.")
+
+ if new_resource.overwrite == true ||
+ (new_resource.overwrite == :auto && archive_differs_from_disk?(new_resource.path, new_resource.destination))
+ Chef::Log.debug("Overwriting existing content at #{new_resource.destination} due to resource's overwrite property settings.")
+
+ extract(new_resource.path, new_resource.destination, Array(new_resource.options))
+ else
+ Chef::Log.debug("Not extracting archive as #{new_resource.destination} exists and resource not set to overwrite.")
+ end
+ end
+
+ if new_resource.owner || new_resource.group
+ converge_by("set owner of #{new_resource.destination} to #{new_resource.owner}:#{new_resource.group}") do
+ FileUtils.chown_R(new_resource.owner, new_resource.group, new_resource.destination)
+ end
+ end
+ end
+
+ action_class do
+ # This can't be a constant since we might not have required 'ffi-libarchive' yet.
+ def extract_option_map
+ {
+ owner: Archive::EXTRACT_OWNER,
+ permissions: Archive::EXTRACT_PERM,
+ time: Archive::EXTRACT_TIME,
+ no_overwrite: Archive::EXTRACT_NO_OVERWRITE,
+ acl: Archive::EXTRACT_ACL,
+ fflags: Archive::EXTRACT_FFLAGS,
+ extended_information: Archive::EXTRACT_XATTR,
+ xattr: Archive::EXTRACT_XATTR,
+ no_overwrite_newer: Archive::EXTRACT_NO_OVERWRITE_NEWER,
+ }
+ end
+
+ # try to determine if the resource has updated or not by checking for files that are in the
+ # archive, but not on disk or files with a non-matching mtime
+ #
+ # @param [String] src
+ # @param [String] dest
+ #
+ # @return [Boolean]
+ def archive_differs_from_disk?(src, dest)
+ require "ffi-libarchive"
+
+ 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
+ end
+ end
+ modified
+ end
+
+ # extract the archive
+ #
+ # @param [String] src
+ # @param [String] dest
+ # @param [Array] options
+ #
+ # @return [void]
+ def extract(src, dest, options = [])
+ require "ffi-libarchive"
+
+ converge_by("extract #{src} to #{dest}") do
+ flags = [options].flatten.map { |option| extract_option_map[option] }.compact.reduce(:|)
+
+ Dir.chdir(dest) do
+ archive = Archive::Reader.open_filename(src)
+
+ archive.each_entry do |e|
+ archive.extract(e, flags.to_i)
+ end
+ archive.close
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resources.rb b/lib/chef/resources.rb
index 0a74fcea23..59cae3558a 100644
--- a/lib/chef/resources.rb
+++ b/lib/chef/resources.rb
@@ -20,6 +20,7 @@ require "chef/resource/apt_package"
require "chef/resource/apt_preference"
require "chef/resource/apt_repository"
require "chef/resource/apt_update"
+require "chef/resource/archive_file"
require "chef/resource/bash"
require "chef/resource/batch"
require "chef/resource/breakpoint"
diff --git a/spec/unit/resource/archive_file_spec.rb b/spec/unit/resource/archive_file_spec.rb
new file mode 100644
index 0000000000..d9f40c0d65
--- /dev/null
+++ b/spec/unit/resource/archive_file_spec.rb
@@ -0,0 +1,47 @@
+#
+# Copyright:: Copyright 2018, 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::ArchiveFile do
+
+ let(:resource) { Chef::Resource::ArchiveFile.new("foo") }
+
+ 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$/)
+ end
+
+ it "sets the default action as :extract" do
+ expect(resource.action).to eql([:extract])
+ end
+
+ it "supports :extract action" do
+ expect { resource.action :extract }.not_to raise_error
+ end
+
+ it "mode property defaults to '755'" do
+ expect(resource.mode).to eql("755")
+ end
+
+ it "options property defaults to [:time]" do
+ expect(resource.options).to eql([:time])
+ end
+end