class Puppet::Network::HTTP::API::IndirectedRoutes
Constants
- IndirectionType
- METHOD_MAP
How we map http methods and the indirection name in the URI to an indirection method.
Public Class Methods
routes()
click to toggle source
# File lib/puppet/network/http/api/indirected_routes.rb 29 def self.routes 30 Puppet::Network::HTTP::Route.path(/.*/).any(new) 31 end
Private Class Methods
pluralize(indirection)
click to toggle source
# File lib/puppet/network/http/api/indirected_routes.rb 258 def self.pluralize(indirection) 259 return(indirection == "status" ? "statuses" : indirection + "s") 260 end
Public Instance Methods
call(request, response)
click to toggle source
Handle an HTTP request. The request has already been authenticated prior to calling this method.
# File lib/puppet/network/http/api/indirected_routes.rb 35 def call(request, response) 36 indirection, method, key, params = uri2indirection(request.method, request.path, request.params) 37 certificate = request.client_cert 38 39 if !indirection.allow_remote_requests? 40 # TODO: should we tell the user we found an indirection but it doesn't 41 # allow remote requests, or just pretend there's no handler at all? what 42 # are the security implications for the former? 43 raise Puppet::Network::HTTP::Error::HTTPNotFoundError.new(_("No handler for %{indirection}") % { indirection: indirection.name }, :NO_INDIRECTION_REMOTE_REQUESTS) 44 end 45 46 overrides = { 47 trusted_information: Puppet::Context::TrustedInformation.remote(params[:authenticated], params[:node], certificate), 48 } 49 if params[:environment] 50 overrides[:current_environment] = params[:environment] 51 end 52 53 Puppet.override(overrides) do 54 send("do_#{method}", indirection, key, params, request, response) 55 end 56 end
uri2indirection(http_method, uri, params)
click to toggle source
# File lib/puppet/network/http/api/indirected_routes.rb 58 def uri2indirection(http_method, uri, params) 59 # the first field is always nil because of the leading slash, 60 indirection_type, version, indirection_name, key = uri.split("/", 5)[1..-1] 61 url_prefix = "/#{indirection_type}/#{version}" 62 environment = params.delete(:environment) 63 64 if indirection_name !~ /^\w+$/ 65 raise Puppet::Network::HTTP::Error::HTTPBadRequestError.new( 66 _("The indirection name must be purely alphanumeric, not '%{indirection_name}'") % { indirection_name: indirection_name }) 67 end 68 69 # this also depluralizes the indirection_name if it is a search 70 method = indirection_method(http_method, indirection_name) 71 72 # check whether this indirection matches the prefix and version in the 73 # request 74 if url_prefix != IndirectionType.url_prefix_for(indirection_name) 75 raise Puppet::Network::HTTP::Error::HTTPBadRequestError.new( 76 _("Indirection '%{indirection_name}' does not match url prefix '%{url_prefix}'") % { indirection_name: indirection_name, url_prefix: url_prefix }) 77 end 78 79 indirection = Puppet::Indirector::Indirection.instance(indirection_name.to_sym) 80 if !indirection 81 raise Puppet::Network::HTTP::Error::HTTPNotFoundError.new( 82 _("Could not find indirection '%{indirection_name}'") % { indirection_name: indirection_name }, 83 Puppet::Network::HTTP::Issues::HANDLER_NOT_FOUND) 84 end 85 86 if !environment 87 raise Puppet::Network::HTTP::Error::HTTPBadRequestError.new( 88 _("An environment parameter must be specified")) 89 end 90 91 if ! Puppet::Node::Environment.valid_name?(environment) 92 raise Puppet::Network::HTTP::Error::HTTPBadRequestError.new( 93 _("The environment must be purely alphanumeric, not '%{environment}'") % { environment: environment }) 94 end 95 96 configured_environment = Puppet.lookup(:environments).get(environment) 97 unless configured_environment.nil? 98 configured_environment = configured_environment.override_from_commandline(Puppet.settings) 99 params[:environment] = configured_environment 100 end 101 102 if configured_environment.nil? && indirection.terminus.require_environment? 103 raise Puppet::Network::HTTP::Error::HTTPNotFoundError.new( 104 _("Could not find environment '%{environment}'") % { environment: environment }) 105 end 106 107 params.delete(:bucket_path) 108 109 if key == "" or key.nil? 110 raise Puppet::Network::HTTP::Error::HTTPBadRequestError.new( 111 _("No request key specified in %{uri}") % { uri: uri }) 112 end 113 114 [indirection, method, key, params] 115 end
Private Instance Methods
accepted_response_formatter_or_json_for(model_class, request)
click to toggle source
Return the first response formatter that the client accepts and the server supports, or default to 'application/json'.
# File lib/puppet/network/http/api/indirected_routes.rb 222 def accepted_response_formatter_or_json_for(model_class, request) 223 request.response_formatters_for(model_class.supported_formats, "application/json").first 224 end
accepted_response_formatters_for(model_class, request)
click to toggle source
Return an array of response formatters that the client accepts and the server supports.
# File lib/puppet/network/http/api/indirected_routes.rb 216 def accepted_response_formatters_for(model_class, request) 217 request.response_formatters_for(model_class.supported_formats) 218 end
do_destroy(indirection, key, params, request, response)
click to toggle source
Execute our destroy.
# File lib/puppet/network/http/api/indirected_routes.rb 169 def do_destroy(indirection, key, params, request, response) 170 formatter = accepted_response_formatter_or_json_for(indirection.model, request) 171 172 result = indirection.destroy(key, params) 173 174 response.respond_with(200, formatter, formatter.render(result)) 175 end
do_find(indirection, key, params, request, response)
click to toggle source
Execute our find.
# File lib/puppet/network/http/api/indirected_routes.rb 120 def do_find(indirection, key, params, request, response) 121 result = indirection.find(key, params) 122 unless result 123 raise Puppet::Network::HTTP::Error::HTTPNotFoundError.new(_("Could not find %{value0} %{key}") % { value0: indirection.name, key: key }, Puppet::Network::HTTP::Issues::RESOURCE_NOT_FOUND) 124 end 125 126 rendered_result = result 127 128 rendered_format = first_response_formatter_for(indirection.model, request, key) do |format| 129 if result.respond_to?(:render) 130 Puppet::Util::Profiler.profile(_("Rendered result in %{format}") % { format: format }, [:http, :v3_render, format]) do 131 rendered_result = result.render(format) 132 end 133 end 134 end 135 136 Puppet::Util::Profiler.profile(_("Sent response"), [:http, :v3_response]) do 137 response.respond_with(200, rendered_format, rendered_result) 138 end 139 end
do_head(indirection, key, params, request, response)
click to toggle source
Execute our head.
# File lib/puppet/network/http/api/indirected_routes.rb 142 def do_head(indirection, key, params, request, response) 143 unless indirection.head(key, params) 144 raise Puppet::Network::HTTP::Error::HTTPNotFoundError.new(_("Could not find %{indirection} %{key}") % { indirection: indirection.name, key: key }, Puppet::Network::HTTP::Issues::RESOURCE_NOT_FOUND) 145 end 146 147 # No need to set a response because no response is expected from a 148 # HEAD request. All we need to do is not die. 149 end
do_save(indirection, key, params, request, response)
click to toggle source
Execute our save.
# File lib/puppet/network/http/api/indirected_routes.rb 178 def do_save(indirection, key, params, request, response) 179 formatter = accepted_response_formatter_or_json_for(indirection.model, request) 180 sent_object = read_body_into_model(indirection.model, request) 181 182 result = indirection.save(sent_object, key) 183 184 response.respond_with(200, formatter, formatter.render(result)) 185 end
do_search(indirection, key, params, request, response)
click to toggle source
Execute our search.
# File lib/puppet/network/http/api/indirected_routes.rb 152 def do_search(indirection, key, params, request, response) 153 result = indirection.search(key, params) 154 155 if result.nil? 156 raise Puppet::Network::HTTP::Error::HTTPNotFoundError.new(_("Could not find instances in %{indirection} with '%{key}'") % { indirection: indirection.name, key: key }, Puppet::Network::HTTP::Issues::RESOURCE_NOT_FOUND) 157 end 158 159 rendered_result = nil 160 161 rendered_format = first_response_formatter_for(indirection.model, request, key) do |format| 162 rendered_result = indirection.model.render_multiple(format, result) 163 end 164 165 response.respond_with(200, rendered_format, rendered_result) 166 end
first_response_formatter_for(model, request, key) { |format| ... }
click to toggle source
Return the first response formatter that didn't cause the yielded block to raise a FormatError.
# File lib/puppet/network/http/api/indirected_routes.rb 189 def first_response_formatter_for(model, request, key, &block) 190 formats = accepted_response_formatters_for(model, request) 191 formatter = formats.find do |format| 192 begin 193 yield format 194 true 195 rescue Puppet::Network::FormatHandler::FormatError => err 196 msg = _("Failed to serialize %{model} for '%{key}': %{detail}") % 197 {model: model, key: key, detail: err} 198 if Puppet[:allow_pson_serialization] 199 Puppet.warning(msg) 200 else 201 raise Puppet::Network::FormatHandler::FormatError.new(msg) 202 end 203 false 204 end 205 end 206 207 return formatter if formatter 208 209 raise Puppet::Network::HTTP::Error::HTTPNotAcceptableError.new( 210 _("No supported formats are acceptable (Accept: %{accepted_formats})") % { accepted_formats: formats.map(&:mime).join(', ') }, 211 Puppet::Network::HTTP::Issues::UNSUPPORTED_FORMAT) 212 end
indirection_method(http_method, indirection)
click to toggle source
# File lib/puppet/network/http/api/indirected_routes.rb 245 def indirection_method(http_method, indirection) 246 raise Puppet::Network::HTTP::Error::HTTPMethodNotAllowedError.new( 247 _("No support for http method %{http_method}") % { http_method: http_method }) unless METHOD_MAP[http_method] 248 249 method = METHOD_MAP[http_method][plurality(indirection)] 250 unless method 251 raise Puppet::Network::HTTP::Error::HTTPBadRequestError.new( 252 _("No support for plurality %{indirection} for %{http_method} operations") % { indirection: plurality(indirection), http_method: http_method }) 253 end 254 255 method 256 end
plurality(indirection)
click to toggle source
# File lib/puppet/network/http/api/indirected_routes.rb 263 def plurality(indirection) 264 # NOTE These specific hooks for paths are ridiculous, but it's a *many*-line 265 # fix to not need this, and our goal is to move away from the complication 266 # that leads to the fix being too long. 267 return :singular if indirection == "facts" 268 return :singular if indirection == "status" 269 return :singular if indirection == "certificate_status" 270 271 result = (indirection =~ /s$|_search$/) ? :plural : :singular 272 273 indirection.sub!(/s$|_search$/, '') 274 indirection.sub!(/statuse$/, 'status') 275 276 result 277 end
read_body_into_model(model_class, request)
click to toggle source
# File lib/puppet/network/http/api/indirected_routes.rb 226 def read_body_into_model(model_class, request) 227 data = request.body.to_s 228 formatter = request.formatter 229 230 if formatter.supported?(model_class) 231 begin 232 return model_class.convert_from(formatter.name.to_s, data) 233 rescue => e 234 raise Puppet::Network::HTTP::Error::HTTPBadRequestError.new( 235 _("The request body is invalid: %{message}") % { message: e.message }) 236 end 237 end 238 239 #TRANSLATORS "mime-type" is a keyword and should not be translated 240 raise Puppet::Network::HTTP::Error::HTTPUnsupportedMediaTypeError.new( 241 _("Client sent a mime-type (%{header}) that doesn't correspond to a format we support") % { header: request.headers['content-type'] }, 242 Puppet::Network::HTTP::Issues::UNSUPPORTED_MEDIA_TYPE) 243 end