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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
|
require 'cgi'
require 'uri'
require 'puppet/indirector'
require 'puppet/util/pson'
# This class encapsulates all of the information you need to make an
# Indirection call, and as a a result also handles REST calls. It's somewhat
# analogous to an HTTP Request object, except tuned for our Indirector.
class Puppet::Indirector::Request
attr_accessor :key, :method, :options, :instance, :node, :ip, :authenticated, :ignore_cache, :ignore_terminus
attr_accessor :server, :port, :uri, :protocol
attr_reader :indirection_name
OPTION_ATTRIBUTES = [:ip, :node, :authenticated, :ignore_terminus, :ignore_cache, :instance, :environment]
# Load json before trying to register.
Puppet.features.pson? and ::PSON.register_document_type('IndirectorRequest',self)
def self.from_pson(json)
raise ArgumentError, "No indirection name provided in json data" unless indirection_name = json['type']
raise ArgumentError, "No method name provided in json data" unless method = json['method']
raise ArgumentError, "No key provided in json data" unless key = json['key']
request = new(indirection_name, method, key, json['attributes'])
if instance = json['instance']
klass = Puppet::Indirector::Indirection.instance(request.indirection_name).model
if instance.is_a?(klass)
request.instance = instance
else
request.instance = klass.from_pson(instance)
end
end
request
end
def to_pson(*args)
result = {
'document_type' => 'IndirectorRequest',
'data' => {
'type' => indirection_name,
'method' => method,
'key' => key
}
}
data = result['data']
attributes = {}
OPTION_ATTRIBUTES.each do |key|
next unless value = send(key)
attributes[key] = value
end
options.each do |opt, value|
attributes[opt] = value
end
data['attributes'] = attributes unless attributes.empty?
data['instance'] = instance if instance
result.to_pson(*args)
end
# Is this an authenticated request?
def authenticated?
# Double negative, so we just get true or false
! ! authenticated
end
def environment
@environment ||= Puppet::Node::Environment.new
end
def environment=(env)
@environment = if env.is_a?(Puppet::Node::Environment)
env
else
Puppet::Node::Environment.new(env)
end
end
def escaped_key
URI.escape(key)
end
# LAK:NOTE This is a messy interface to the cache, and it's only
# used by the Configurer class. I decided it was better to implement
# it now and refactor later, when we have a better design, than
# to spend another month coming up with a design now that might
# not be any better.
def ignore_cache?
ignore_cache
end
def ignore_terminus?
ignore_terminus
end
def initialize(indirection_name, method, key_or_instance, options_or_instance = {})
if options_or_instance.is_a? Hash
options = options_or_instance
@instance = nil
else
options = {}
@instance = options_or_instance
end
self.indirection_name = indirection_name
self.method = method
options = options.inject({}) { |hash, ary| hash[ary[0].to_sym] = ary[1]; hash }
set_attributes(options)
@options = options
if key_or_instance.is_a?(String) || key_or_instance.is_a?(Symbol)
key = key_or_instance
else
@instance ||= key_or_instance
end
if key
# If the request key is a URI, then we need to treat it specially,
# because it rewrites the key. We could otherwise strip server/port/etc
# info out in the REST class, but it seemed bad design for the REST
# class to rewrite the key.
if key.to_s =~ /^[a-z]:[\/\\]/i # It's an absolute path for Windows.
@key = key
elsif key.to_s =~ /^\w+:\/\// # it's a URI
set_uri_key(key)
else
@key = key
end
end
@key = @instance.name if ! @key and @instance
end
# Look up the indirection based on the name provided.
def indirection
Puppet::Indirector::Indirection.instance(indirection_name)
end
def indirection_name=(name)
@indirection_name = name.to_sym
end
def model
raise ArgumentError, "Could not find indirection '#{indirection_name}'" unless i = indirection
i.model
end
# Should we allow use of the cached object?
def use_cache?
if defined?(@use_cache)
! ! use_cache
else
true
end
end
# Are we trying to interact with multiple resources, or just one?
def plural?
method == :search
end
# Create the query string, if options are present.
def query_string
return "" unless options and ! options.empty?
"?" + options.collect do |key, value|
case value
when nil; next
when true, false; value = value.to_s
when Fixnum, Bignum, Float; value = value # nothing
when String; value = CGI.escape(value)
when Symbol; value = CGI.escape(value.to_s)
when Array; value = CGI.escape(YAML.dump(value))
else
raise ArgumentError, "HTTP REST queries cannot handle values of type '#{value.class}'"
end
"#{key}=#{value}"
end.join("&")
end
def to_hash
result = options.dup
OPTION_ATTRIBUTES.each do |attribute|
if value = send(attribute)
result[attribute] = value
end
end
result
end
def to_s
return(uri ? uri : "/#{indirection_name}/#{key}")
end
private
def set_attributes(options)
OPTION_ATTRIBUTES.each do |attribute|
if options.include?(attribute.to_sym)
send(attribute.to_s + "=", options[attribute])
options.delete(attribute)
end
end
end
# Parse the key as a URI, setting attributes appropriately.
def set_uri_key(key)
@uri = key
begin
uri = URI.parse(URI.escape(key))
rescue => detail
raise ArgumentError, "Could not understand URL #{key}: #{detail}"
end
# Just short-circuit these to full paths
if uri.scheme == "file"
@key = URI.unescape(uri.path)
return
end
@server = uri.host if uri.host
# If the URI class can look up the scheme, it will provide a port,
# otherwise it will default to '0'.
if uri.port.to_i == 0 and uri.scheme == "puppet"
@port = Puppet.settings[:masterport].to_i
else
@port = uri.port.to_i
end
@protocol = uri.scheme
if uri.scheme == 'puppet'
@key = URI.unescape(uri.path.sub(/^\//, ''))
return
end
env, indirector, @key = URI.unescape(uri.path.sub(/^\//, '')).split('/',3)
@key ||= ''
self.environment = env unless env == ''
end
end
|