53b065f383f4a230557aba4542f2df9fa3b566f0
[mirror/dsa-puppet.git] / 3rdparty / modules / archive / lib / puppet / provider / archive / ruby.rb
1 begin
2   require 'puppet_x/bodeco/archive'
3   require 'puppet_x/bodeco/util'
4 rescue LoadError
5   require 'pathname' # WORK_AROUND #14073 and #7788
6   archive = Puppet::Module.find('archive', Puppet[:environment].to_s)
7   raise(LoadError, "Unable to find archive module in modulepath #{Puppet[:basemodulepath] || Puppet[:modulepath]}") unless archive
8   require File.join archive.path, 'lib/puppet_x/bodeco/archive'
9   require File.join archive.path, 'lib/puppet_x/bodeco/util'
10 end
11
12 require 'securerandom'
13 require 'tempfile'
14
15 # This provider implements a simple state-machine. The following attempts to #
16 # document it. In general, `def adjective?` implements a [state], while `def
17 # verb` implements an {action}.
18 # Some states are more complex, as they might depend on other states, or trigger
19 # actions. Since this implements an ad-hoc state-machine, many actions or states
20 # have to guard themselves against being called out of order.
21 #
22 # [exists?]
23 #   |
24 #   v
25 # [extracted?] -> no -> [checksum?]
26 #    |
27 #    v
28 #   yes
29 #    |
30 #    v
31 # [path.exists?] -> no -> {cleanup}
32 #    |                    |    |
33 #    v                    v    v
34 # [checksum?]            yes. [extracted?] && [cleanup?]
35 #                              |
36 #                              v
37 #                            {destroy}
38 #
39 # Now, with [exists?] defined, we can define [ensure]
40 # But that's just part of the standard puppet provider state-machine:
41 #
42 # [ensure] -> absent -> [exists?] -> no.
43 #   |                     |
44 #   v                     v
45 #  present               yes
46 #   |                     |
47 #   v                     v
48 # [exists?]            {destroy}
49 #   |
50 #   v
51 # {create}
52 #
53 # Here's how we would extend archive for an `ensure => latest`:
54 #
55 #  [exists?] -> no -> {create}
56 #    |
57 #    v
58 #   yes
59 #    |
60 #    v
61 #  [ttl?] -> expired -> {destroy} -> {create}
62 #    |
63 #    v
64 #  valid.
65 #
66
67 Puppet::Type.type(:archive).provide(:ruby) do
68   optional_commands aws: 'aws'
69   defaultfor feature: :microsoft_windows
70   attr_reader :archive_checksum
71
72   def exists?
73     return checksum? unless extracted?
74     return checksum? if File.exist? archive_filepath
75     cleanup
76     true
77   end
78
79   def create
80     transfer_download(archive_filepath) unless checksum?
81     extract
82     cleanup
83   end
84
85   def destroy
86     FileUtils.rm_f(archive_filepath) if File.exist?(archive_filepath)
87   end
88
89   def archive_filepath
90     resource[:path]
91   end
92
93   def tempfile_name
94     if resource[:checksum] == 'none'
95       "#{resource[:filename]}_#{SecureRandom.base64}"
96     else
97       "#{resource[:filename]}_#{resource[:checksum]}"
98     end
99   end
100
101   def creates
102     if resource[:extract] == :true
103       extracted? ? resource[:creates] : 'archive not extracted'
104     else
105       resource[:creates]
106     end
107   end
108
109   def creates=(_value)
110     extract
111   end
112
113   def checksum
114     resource[:checksum] || (resource[:checksum] = remote_checksum if resource[:checksum_url])
115   end
116
117   def remote_checksum
118     PuppetX::Bodeco::Util.content(
119       resource[:checksum_url],
120       username: resource[:username],
121       password: resource[:password],
122       cookie: resource[:cookie],
123       proxy_server: resource[:proxy_server],
124       proxy_type: resource[:proxy_type],
125       insecure: resource[:allow_insecure]
126     )[%r{\b[\da-f]{32,128}\b}i]
127   end
128
129   # Private: See if local archive checksum matches.
130   # returns boolean
131   def checksum?(store_checksum = true)
132     return false unless File.exist? archive_filepath
133     return true  if resource[:checksum_type] == :none
134
135     archive = PuppetX::Bodeco::Archive.new(archive_filepath)
136     archive_checksum = archive.checksum(resource[:checksum_type])
137     @archive_checksum = archive_checksum if store_checksum
138     checksum == archive_checksum
139   end
140
141   def cleanup
142     return unless extracted? && resource[:cleanup] == :true
143     Puppet.debug("Cleanup archive #{archive_filepath}")
144     destroy
145   end
146
147   def extract
148     return unless resource[:extract] == :true
149     raise(ArgumentError, 'missing archive extract_path') unless resource[:extract_path]
150     PuppetX::Bodeco::Archive.new(archive_filepath).extract(
151       resource[:extract_path],
152       custom_command: resource[:extract_command],
153       options: resource[:extract_flags],
154       uid: resource[:user],
155       gid: resource[:group]
156     )
157   end
158
159   def extracted?
160     resource[:creates] && File.exist?(resource[:creates])
161   end
162
163   def transfer_download(archive_filepath)
164     if resource[:temp_dir] && !File.directory?(resource[:temp_dir])
165       raise Puppet::Error, "Temporary directory #{resource[:temp_dir]} doesn't exist"
166     end
167     tempfile = Tempfile.new(tempfile_name, resource[:temp_dir])
168
169     temppath = tempfile.path
170     tempfile.close!
171
172     case resource[:source]
173     when %r{^(puppet)}
174       puppet_download(temppath)
175     when %r{^(http|ftp)}
176       download(temppath)
177     when %r{^file}
178       uri = URI(resource[:source])
179       FileUtils.copy(Puppet::Util.uri_to_path(uri), temppath)
180     when %r{^s3}
181       s3_download(temppath)
182     when nil
183       raise(Puppet::Error, 'Unable to fetch archive, the source parameter is nil.')
184     else
185       raise(Puppet::Error, "Source file: #{resource[:source]} does not exists.") unless File.exist?(resource[:source])
186       FileUtils.copy(resource[:source], temppath)
187     end
188
189     # conditionally verify checksum:
190     if resource[:checksum_verify] == :true && resource[:checksum_type] != :none
191       archive = PuppetX::Bodeco::Archive.new(temppath)
192       actual_checksum = archive.checksum(resource[:checksum_type])
193       if actual_checksum != checksum
194         raise(Puppet::Error, "Download file checksum mismatch (expected: #{checksum} actual: #{actual_checksum})")
195       end
196     end
197
198     move_file_in_place(temppath, archive_filepath)
199   end
200
201   def move_file_in_place(from, to)
202     # Ensure to directory exists.
203     FileUtils.mkdir_p(File.dirname(to))
204     FileUtils.mv(from, to)
205   end
206
207   def download(filepath)
208     PuppetX::Bodeco::Util.download(
209       resource[:source],
210       filepath,
211       username: resource[:username],
212       password: resource[:password],
213       cookie: resource[:cookie],
214       proxy_server: resource[:proxy_server],
215       proxy_type: resource[:proxy_type],
216       insecure: resource[:allow_insecure]
217     )
218   end
219
220   def puppet_download(filepath)
221     PuppetX::Bodeco::Util.puppet_download(
222       resource[:source],
223       filepath
224     )
225   end
226
227   def s3_download(path)
228     params = [
229       's3',
230       'cp',
231       resource[:source],
232       path
233     ]
234     params += resource[:download_options] if resource[:download_options]
235
236     aws(params)
237   end
238
239   def optional_switch(value, option)
240     if value
241       option.map { |flags| flags % value }
242     else
243       []
244     end
245   end
246 end