Add puppet/archive module, required for newer puppet/rabbitmq
[mirror/dsa-puppet.git] / 3rdparty / modules / archive / lib / puppet_x / bodeco / util.rb
1 module PuppetX
2   module Bodeco
3     module Util
4       def self.download(url, filepath, options = {})
5         uri = URI(url)
6         @connection = PuppetX::Bodeco.const_get(uri.scheme.upcase).new("#{uri.scheme}://#{uri.host}:#{uri.port}", options)
7         @connection.download(uri, filepath)
8       end
9
10       def self.content(url, options = {})
11         uri = URI(url)
12         @connection = PuppetX::Bodeco.const_get(uri.scheme.upcase).new("#{uri.scheme}://#{uri.host}:#{uri.port}", options)
13         @connection.content(uri)
14       end
15
16       #
17       # This allows you to use a puppet syntax for a file and return it's content.
18       #
19       # @example
20       #  puppet_download 'puppet:///modules/my_module_name/my_file.dat
21       #
22       # @param [String] url this is the puppet url of the file to be fetched
23       # @param [String] filepath this is path of the file to create
24       #
25       # @raise [ArgumentError] when the file doesn't exist
26       #
27       def self.puppet_download(url, filepath)
28         # Somehow there is no consistent way to determine what terminus to use. So we switch to a
29         # trial and error method. First we start withe the default. And if it doesn't work, we try the
30         # other ones
31         status = load_file_with_any_terminus(url)
32         raise ArgumentError, "Could not retrieve information from environment #{Puppet['environment']} source(s) #{url}'" unless status
33         File.open(filepath, 'w') { |file| file.write(status.content) }
34       end
35
36       # @private
37       # rubocop:disable HandleExceptions
38       def self.load_file_with_any_terminus(url)
39         termini_to_try = [:file_server, :rest]
40         termini_to_try.each do |terminus|
41           with_terminus(terminus) do
42             begin
43               content = Puppet::FileServing::Content.indirection.find(url)
44             rescue SocketError, Timeout::Error, Errno::ECONNREFUSED, Errno::EHOSTDOWN, Errno::EHOSTUNREACH, Errno::ETIMEDOUT
45               # rescue any network error
46             end
47             return content if content
48           end
49         end
50         nil
51       end
52       # rubocop:enable HandleExceptions
53
54       def self.with_terminus(terminus)
55         old_terminus = Puppet[:default_file_terminus]
56         Puppet[:default_file_terminus] = terminus
57         value = yield
58         Puppet[:default_file_terminus] = old_terminus
59         value
60       end
61     end
62     class HTTP
63       require 'net/http'
64
65       FOLLOW_LIMIT = 5
66       URI_UNSAFE = %r{[^\-_.!~*'()a-zA-Z\d;\/?:@&=+$,\[\]%]}
67
68       def initialize(_url, options)
69         @username = options[:username]
70         @password = options[:password]
71         @cookie = options[:cookie]
72         @insecure = options[:insecure]
73
74         if options[:proxy_server]
75           uri = URI(options[:proxy_server])
76           unless uri.scheme
77             uri = URI("#{options[:proxy_type]}://#{options[:proxy_server]}")
78           end
79           @proxy_addr = uri.hostname
80           @proxy_port = uri.port
81         end
82
83         ENV['SSL_CERT_FILE'] = File.expand_path(File.join(__FILE__, '..', 'cacert.pem')) if Facter.value(:osfamily) == 'windows' && !ENV.key?('SSL_CERT_FILE')
84       end
85
86       def generate_request(uri)
87         header = @cookie && { 'Cookie' => @cookie }
88
89         request = Net::HTTP::Get.new(uri.request_uri, header)
90         request.basic_auth(@username, @password) if @username && @password
91         request
92       end
93
94       def follow_redirect(uri, option = { limit: FOLLOW_LIMIT }, &block)
95         http_opts = if uri.scheme == 'https'
96                       { use_ssl: true,
97                         verify_mode: (@insecure ? OpenSSL::SSL::VERIFY_NONE : OpenSSL::SSL::VERIFY_PEER) }
98                     else
99                       { use_ssl: false }
100                     end
101         Net::HTTP.start(uri.host, uri.port, @proxy_addr, @proxy_port, http_opts) do |http|
102           http.request(generate_request(uri)) do |response|
103             case response
104             when Net::HTTPSuccess
105               yield response
106             when Net::HTTPRedirection
107               limit = option[:limit] - 1
108               raise Puppet::Error, "Redirect limit exceeded, last url: #{uri}" if limit < 0
109               location = safe_escape(response['location'])
110               new_uri = URI(location)
111               new_uri = URI(uri.to_s + location) if new_uri.relative?
112               follow_redirect(new_uri, limit: limit, &block)
113             else
114               raise Puppet::Error, "HTTP Error Code #{response.code}\nURL: #{uri}\nContent:\n#{response.body}"
115             end
116           end
117         end
118       end
119
120       def download(uri, file_path, option = { limit: FOLLOW_LIMIT })
121         follow_redirect(uri, option) do |response|
122           File.open file_path, 'wb' do |io|
123             response.read_body do |chunk|
124               io.write chunk
125             end
126           end
127         end
128       end
129
130       def content(uri, option = { limit: FOLLOW_LIMIT })
131         follow_redirect(uri, option) do |response|
132           return response.body
133         end
134       end
135
136       def safe_escape(uri)
137         uri.to_s.gsub(URI_UNSAFE) do |match|
138           '%' + match.unpack('H2' * match.bytesize).join('%').upcase
139         end
140       end
141     end
142
143     class HTTPS < HTTP
144     end
145
146     class FTP
147       require 'net/ftp'
148
149       def initialize(url, options)
150         uri = URI(url)
151         username = options[:username]
152         password = options[:password]
153         proxy_server = options[:proxy_server]
154         proxy_type = options[:proxy_type]
155
156         ENV["#{proxy_type}_proxy"] = proxy_server
157
158         @ftp = Net::FTP.new
159         @ftp.connect(uri.host, uri.port)
160         if username
161           @ftp.login(username, password)
162         else
163           @ftp.login
164         end
165       end
166
167       def download(uri, file_path)
168         @ftp.getbinaryfile(uri.path, file_path)
169       end
170     end
171
172     class FILE
173       def initialize(_url, _options) end
174
175       def download(uri, file_path)
176         FileUtils.copy(uri.path, file_path)
177       end
178     end
179   end
180 end