--- /dev/null
+begin
+ require 'puppet_x/bodeco/archive'
+ require 'puppet_x/bodeco/util'
+rescue LoadError
+ require 'pathname' # WORK_AROUND #14073 and #7788
+ archive = Puppet::Module.find('archive', Puppet[:environment].to_s)
+ raise(LoadError, "Unable to find archive module in modulepath #{Puppet[:basemodulepath] || Puppet[:modulepath]}") unless archive
+ require File.join archive.path, 'lib/puppet_x/bodeco/archive'
+ require File.join archive.path, 'lib/puppet_x/bodeco/util'
+end
+
+require 'securerandom'
+require 'tempfile'
+
+# This provider implements a simple state-machine. The following attempts to #
+# document it. In general, `def adjective?` implements a [state], while `def
+# verb` implements an {action}.
+# Some states are more complex, as they might depend on other states, or trigger
+# actions. Since this implements an ad-hoc state-machine, many actions or states
+# have to guard themselves against being called out of order.
+#
+# [exists?]
+# |
+# v
+# [extracted?] -> no -> [checksum?]
+# |
+# v
+# yes
+# |
+# v
+# [path.exists?] -> no -> {cleanup}
+# | | |
+# v v v
+# [checksum?] yes. [extracted?] && [cleanup?]
+# |
+# v
+# {destroy}
+#
+# Now, with [exists?] defined, we can define [ensure]
+# But that's just part of the standard puppet provider state-machine:
+#
+# [ensure] -> absent -> [exists?] -> no.
+# | |
+# v v
+# present yes
+# | |
+# v v
+# [exists?] {destroy}
+# |
+# v
+# {create}
+#
+# Here's how we would extend archive for an `ensure => latest`:
+#
+# [exists?] -> no -> {create}
+# |
+# v
+# yes
+# |
+# v
+# [ttl?] -> expired -> {destroy} -> {create}
+# |
+# v
+# valid.
+#
+
+Puppet::Type.type(:archive).provide(:ruby) do
+ optional_commands aws: 'aws'
+ defaultfor feature: :microsoft_windows
+ attr_reader :archive_checksum
+
+ def exists?
+ return checksum? unless extracted?
+ return checksum? if File.exist? archive_filepath
+ cleanup
+ true
+ end
+
+ def create
+ transfer_download(archive_filepath) unless checksum?
+ extract
+ cleanup
+ end
+
+ def destroy
+ FileUtils.rm_f(archive_filepath) if File.exist?(archive_filepath)
+ end
+
+ def archive_filepath
+ resource[:path]
+ end
+
+ def tempfile_name
+ if resource[:checksum] == 'none'
+ "#{resource[:filename]}_#{SecureRandom.base64}"
+ else
+ "#{resource[:filename]}_#{resource[:checksum]}"
+ end
+ end
+
+ def creates
+ if resource[:extract] == :true
+ extracted? ? resource[:creates] : 'archive not extracted'
+ else
+ resource[:creates]
+ end
+ end
+
+ def creates=(_value)
+ extract
+ end
+
+ def checksum
+ resource[:checksum] || (resource[:checksum] = remote_checksum if resource[:checksum_url])
+ end
+
+ def remote_checksum
+ PuppetX::Bodeco::Util.content(
+ resource[:checksum_url],
+ username: resource[:username],
+ password: resource[:password],
+ cookie: resource[:cookie],
+ proxy_server: resource[:proxy_server],
+ proxy_type: resource[:proxy_type],
+ insecure: resource[:allow_insecure]
+ )[%r{\b[\da-f]{32,128}\b}i]
+ end
+
+ # Private: See if local archive checksum matches.
+ # returns boolean
+ def checksum?(store_checksum = true)
+ return false unless File.exist? archive_filepath
+ return true if resource[:checksum_type] == :none
+
+ archive = PuppetX::Bodeco::Archive.new(archive_filepath)
+ archive_checksum = archive.checksum(resource[:checksum_type])
+ @archive_checksum = archive_checksum if store_checksum
+ checksum == archive_checksum
+ end
+
+ def cleanup
+ return unless extracted? && resource[:cleanup] == :true
+ Puppet.debug("Cleanup archive #{archive_filepath}")
+ destroy
+ end
+
+ def extract
+ return unless resource[:extract] == :true
+ raise(ArgumentError, 'missing archive extract_path') unless resource[:extract_path]
+ PuppetX::Bodeco::Archive.new(archive_filepath).extract(
+ resource[:extract_path],
+ custom_command: resource[:extract_command],
+ options: resource[:extract_flags],
+ uid: resource[:user],
+ gid: resource[:group]
+ )
+ end
+
+ def extracted?
+ resource[:creates] && File.exist?(resource[:creates])
+ end
+
+ def transfer_download(archive_filepath)
+ if resource[:temp_dir] && !File.directory?(resource[:temp_dir])
+ raise Puppet::Error, "Temporary directory #{resource[:temp_dir]} doesn't exist"
+ end
+ tempfile = Tempfile.new(tempfile_name, resource[:temp_dir])
+
+ temppath = tempfile.path
+ tempfile.close!
+
+ case resource[:source]
+ when %r{^(puppet)}
+ puppet_download(temppath)
+ when %r{^(http|ftp)}
+ download(temppath)
+ when %r{^file}
+ uri = URI(resource[:source])
+ FileUtils.copy(Puppet::Util.uri_to_path(uri), temppath)
+ when %r{^s3}
+ s3_download(temppath)
+ when nil
+ raise(Puppet::Error, 'Unable to fetch archive, the source parameter is nil.')
+ else
+ raise(Puppet::Error, "Source file: #{resource[:source]} does not exists.") unless File.exist?(resource[:source])
+ FileUtils.copy(resource[:source], temppath)
+ end
+
+ # conditionally verify checksum:
+ if resource[:checksum_verify] == :true && resource[:checksum_type] != :none
+ archive = PuppetX::Bodeco::Archive.new(temppath)
+ actual_checksum = archive.checksum(resource[:checksum_type])
+ if actual_checksum != checksum
+ raise(Puppet::Error, "Download file checksum mismatch (expected: #{checksum} actual: #{actual_checksum})")
+ end
+ end
+
+ move_file_in_place(temppath, archive_filepath)
+ end
+
+ def move_file_in_place(from, to)
+ # Ensure to directory exists.
+ FileUtils.mkdir_p(File.dirname(to))
+ FileUtils.mv(from, to)
+ end
+
+ def download(filepath)
+ PuppetX::Bodeco::Util.download(
+ resource[:source],
+ filepath,
+ username: resource[:username],
+ password: resource[:password],
+ cookie: resource[:cookie],
+ proxy_server: resource[:proxy_server],
+ proxy_type: resource[:proxy_type],
+ insecure: resource[:allow_insecure]
+ )
+ end
+
+ def puppet_download(filepath)
+ PuppetX::Bodeco::Util.puppet_download(
+ resource[:source],
+ filepath
+ )
+ end
+
+ def s3_download(path)
+ params = [
+ 's3',
+ 'cp',
+ resource[:source],
+ path
+ ]
+ params += resource[:download_options] if resource[:download_options]
+
+ aws(params)
+ end
+
+ def optional_switch(value, option)
+ if value
+ option.map { |flags| flags % value }
+ else
+ []
+ end
+ end
+end