1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
|
require 'net/http'
require 'uri'
require 'puppet/network/http_pool'
require 'puppet/network/http/api/v1'
require 'puppet/network/http/compression'
# Access objects via REST
class Puppet::Indirector::REST < Puppet::Indirector::Terminus
include Puppet::Network::HTTP::API::V1
include Puppet::Network::HTTP::Compression.module
class << self
attr_reader :server_setting, :port_setting
end
# Specify the setting that we should use to get the server name.
def self.use_server_setting(setting)
@server_setting = setting
end
def self.server
Puppet.settings[server_setting || :server]
end
# Specify the setting that we should use to get the port.
def self.use_port_setting(setting)
@port_setting = setting
end
def self.port
Puppet.settings[port_setting || :masterport].to_i
end
# Figure out the content type, turn that into a format, and use the format
# to extract the body of the response.
def deserialize(response, multiple = false)
case response.code
when "404"
return nil
when /^2/
raise "No content type in http response; cannot parse" unless response['content-type']
content_type = response['content-type'].gsub(/\s*;.*$/,'') # strip any appended charset
body = uncompress_body(response)
# Convert the response to a deserialized object.
if multiple
model.convert_from_multiple(content_type, body)
else
model.convert_from(content_type, body)
end
else
# Raise the http error if we didn't get a 'success' of some kind.
raise convert_to_http_error(response)
end
end
def convert_to_http_error(response)
message = "Error #{response.code} on SERVER: #{(response.body||'').empty? ? response.message : uncompress_body(response)}"
Net::HTTPError.new(message, response)
end
# Provide appropriate headers.
def headers
add_accept_encoding({"Accept" => model.supported_formats.join(", ")})
end
def network(request)
Puppet::Network::HttpPool.http_instance(request.server || self.class.server, request.port || self.class.port)
end
[:get, :post, :head, :delete, :put].each do |method|
define_method "http_#{method}" do |request, *args|
http_request(method, request, *args)
end
end
def http_request(method, request, *args)
http_connection = network(request)
peer_certs = []
# We add the callback to collect the certificates for use in constructing
# the error message if the verification failed. This is necessary since we
# don't have direct access to the cert that we expected the connection to
# use otherwise.
#
http_connection.verify_callback = proc do |preverify_ok, ssl_context|
peer_certs << Puppet::SSL::Certificate.from_s(ssl_context.current_cert.to_pem)
preverify_ok
end
http_connection.send(method, *args)
rescue OpenSSL::SSL::SSLError => error
if error.message.include? "certificate verify failed"
raise Puppet::Error, "#{error.message}. This is often because the time is out of sync on the server or client"
elsif error.message.include? "hostname was not match"
raise unless cert = peer_certs.find { |c| c.name !~ /^puppet ca/i }
valid_certnames = [cert.name, *cert.alternate_names].uniq
msg = valid_certnames.length > 1 ? "one of #{valid_certnames.join(', ')}" : valid_certnames.first
raise Puppet::Error, "Server hostname '#{http_connection.address}' did not match server certificate; expected #{msg}"
else
raise
end
end
def find(request)
uri, body = request_to_uri_and_body(request)
uri_with_query_string = "#{uri}?#{body}"
# WEBrick in Ruby 1.9.1 only supports up to 1024 character lines in an HTTP request
# http://redmine.ruby-lang.org/issues/show/3991
response = if "GET #{uri_with_query_string} HTTP/1.1\r\n".length > 1024
http_post(request, uri, body, headers)
else
http_get(request, uri_with_query_string, headers)
end
result = deserialize response
result.name = request.key if result.respond_to?(:name=)
result
end
def head(request)
response = http_head(request, indirection2uri(request), headers)
case response.code
when "404"
return false
when /^2/
return true
else
# Raise the http error if we didn't get a 'success' of some kind.
raise convert_to_http_error(response)
end
end
def search(request)
unless result = deserialize(http_get(request, indirection2uri(request), headers), true)
return []
end
result
end
def destroy(request)
raise ArgumentError, "DELETE does not accept options" unless request.options.empty?
deserialize http_delete(request, indirection2uri(request), headers)
end
def save(request)
raise ArgumentError, "PUT does not accept options" unless request.options.empty?
deserialize http_put(request, indirection2uri(request), request.instance.render, headers.merge({ "Content-Type" => request.instance.mime }))
end
private
def environment
Puppet::Node::Environment.new
end
end
|