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
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