Add puppet/archive module
[mirror/dsa-puppet.git] / 3rdparty / modules / archive / lib / puppet_x / bodeco / archive.rb
1 require 'digest'
2 require 'puppet/util/execution'
3 require 'shellwords'
4
5 module PuppetX
6   module Bodeco
7     class Archive
8       def initialize(file)
9         @file = file
10         @file_path =  if Facter.value(:osfamily) == 'windows'
11                         '"' + file + '"'
12                       else
13                         Shellwords.shellescape file
14                       end
15       end
16
17       def checksum(type)
18         return nil if type == :none
19
20         digest = Digest.const_get(type.to_s.upcase)
21         digest.file(@file).hexdigest
22       rescue LoadError
23         raise $ERROR_INFO, "invalid checksum type #{type}. #{$ERROR_INFO}", $ERROR_INFO.backtrace
24       end
25
26       def root_dir
27         if Facter.value(:osfamily) == 'windows'
28           'C:\\'
29         else
30           '/'
31         end
32       end
33
34       def extract(path = root_dir, opts = {})
35         opts = {
36           custom_command: nil,
37           options: '',
38           uid: nil,
39           gid: nil
40         }.merge(opts)
41
42         custom_command = opts.fetch(:custom_command, nil)
43         options = opts.fetch(:options)
44         Dir.chdir(path) do
45           cmd = if custom_command && custom_command =~ %r{%s}
46                   custom_command % @file_path
47                 elsif custom_command
48                   "#{custom_command} #{options} #{@file_path}"
49                 else
50                   command(options)
51                 end
52
53           Puppet.debug("Archive extracting #{@file} in #{path}: #{cmd}")
54           File.chmod(0o644, @file) if opts[:uid] || opts[:gid]
55           Puppet::Util::Execution.execute(cmd, uid: opts[:uid], gid: opts[:gid], failonfail: true, squelch: false, combine: true)
56         end
57       end
58
59       private
60
61       def win_7zip
62         if ENV['path'].include?('7-Zip')
63           '7z.exe'
64         elsif File.directory?('C:\\Program Files\\7-Zip')
65           'C:\\Program Files\\7-Zip\\7z.exe'
66         elsif File.directory?('C:\\Program Files (x86)\\7-zip')
67           'C:\\Program Files (x86)\\7-Zip\\7z.exe'
68         elsif @file_path =~ %r{.zip"$}
69           # Fallback to powershell for zipfiles - this works with windows
70           # 2012+ if your powershell/.net is too old the script will fail
71           # on execution and ask user to install 7zip.
72           # We have to manually extract each entry in the zip file
73           # to ensure we extract fresh copy because `ExtractToDirectory`
74           # method does not support overwriting
75           ps = <<-END
76           try {
77               Add-Type -AssemblyName System.IO.Compression.FileSystem -erroraction "silentlycontinue"
78               $zipFile = [System.IO.Compression.ZipFile]::openread(#{@file_path})
79               foreach ($zipFileEntry in $zipFile.Entries) {
80                   $pwd = (Get-Item -Path ".\" -Verbose).FullName
81                   $outputFile = [io.path]::combine($pwd, $zipFileEntry.FullName)
82                   $dir = ([io.fileinfo]$outputFile).DirectoryName
83
84                   if (-not(Test-Path -type Container -path $dir)) {
85                       mkdir $dir
86                   }
87                   if ($zipFileEntry.Name -ne "") {
88                       write-host "[extract] $zipFileEntry.Name"
89                       [System.IO.Compression.ZipFileExtensions]::ExtractToFile($zipFileEntry, $outputFile, $true)
90                   }
91               }
92           } catch [System.invalidOperationException] {
93               write-error "Your OS does not support System.IO.Compression.FileSystem - please install 7zip"
94           }
95           END
96
97           "powershell -command #{ps.gsub(%r{"}, '\\"').gsub(%r{\n}, '; ')}"
98         else
99           raise Exception, '7z.exe not available'
100         end
101       end
102
103       def command(options)
104         if Facter.value(:osfamily) == 'windows'
105           opt = parse_flags('x -aoa', options, '7z')
106           cmd = win_7zip
107           cmd =~ %r{7z.exe} ? "#{cmd} #{opt} #{@file_path}" : cmd
108         else
109           case @file
110           when %r{\.tar$}
111             opt = parse_flags('xf', options, 'tar')
112             "tar #{opt} #{@file_path}"
113           when %r{(\.tgz|\.tar\.gz)$}
114             case Facter.value(:osfamily)
115             when 'Solaris', 'AIX'
116               gunzip_opt = parse_flags('-dc', options, 'gunzip')
117               tar_opt = parse_flags('xf', options, 'tar')
118               "gunzip #{gunzip_opt} #{@file_path} | tar #{tar_opt} -"
119             else
120               opt = parse_flags('xzf', options, 'tar')
121               "tar #{opt} #{@file_path}"
122             end
123           when %r{(\.tbz|\.tar\.bz2)$}
124             case Facter.value(:osfamily)
125             when 'Solaris', 'AIX'
126               bunzip_opt = parse_flags('-dc', options, 'bunzip')
127               tar_opt = parse_flags('xf', options, 'tar')
128               "bunzip2 #{bunzip_opt} #{@file_path} | tar #{tar_opt} -"
129             else
130               opt = parse_flags('xjf', options, 'tar')
131               "tar #{opt} #{@file_path}"
132             end
133           when %r{(\.txz|\.tar\.xz)$}
134             unxz_opt = parse_flags('-dc', options, 'unxz')
135             tar_opt = parse_flags('xf', options, 'tar')
136             "unxz #{unxz_opt} #{@file_path} | tar #{tar_opt} -"
137           when %r{\.gz$}
138             opt = parse_flags('-d', options, 'gunzip')
139             "gunzip #{opt} #{@file_path}"
140           when %r{(\.zip|\.war|\.jar)$}
141             opt = parse_flags('-o', options, 'zip')
142             "unzip #{opt} #{@file_path}"
143           else
144             raise NotImplementedError, "Unknown filetype: #{@file}"
145           end
146         end
147       end
148
149       def parse_flags(default, options, command = nil)
150         case options
151         when :undef
152           default
153         when ::String
154           options
155         when ::Hash
156           options[command]
157         else
158           raise ArgumentError, "Invalid options for command #{command}: #{options.inspect}"
159         end
160       end
161     end
162   end
163 end