diff options
author | Christian Hofstaedtler <hofstaedtler@inqnet.at> | 2009-04-28 12:23:58 +0000 |
---|---|---|
committer | James Turnbull <james@lovedthanlost.net> | 2009-05-02 09:13:29 +1000 |
commit | 6e01e7ab403d090f29f13c938ca5b19930c4b408 (patch) | |
tree | 413dd59275768143ace1161a43dccc640744ccfc /lib/puppet | |
parent | cc09c1af21e218917cc02c0ad9d8c44300803f49 (diff) | |
download | puppet-6e01e7ab403d090f29f13c938ca5b19930c4b408.tar.gz puppet-6e01e7ab403d090f29f13c938ca5b19930c4b408.tar.xz puppet-6e01e7ab403d090f29f13c938ca5b19930c4b408.zip |
Puppet as a Rack application
This lays the ground: a wrapper for the REST handler, and an application
confirming to the Rack standard. Also includes a base class for Rack
handlers, as RackREST will not stay the only one, and there needs to be
a central place where client authentication data can be checked.
Diffstat (limited to 'lib/puppet')
-rw-r--r-- | lib/puppet/feature/base.rb | 3 | ||||
-rw-r--r-- | lib/puppet/network/http/rack.rb | 45 | ||||
-rw-r--r-- | lib/puppet/network/http/rack/httphandler.rb | 16 | ||||
-rw-r--r-- | lib/puppet/network/http/rack/rest.rb | 74 |
4 files changed, 138 insertions, 0 deletions
diff --git a/lib/puppet/feature/base.rb b/lib/puppet/feature/base.rb index c3fb9a2f3..7c0f241c1 100644 --- a/lib/puppet/feature/base.rb +++ b/lib/puppet/feature/base.rb @@ -28,3 +28,6 @@ Puppet.features.add(:augeas, :libs => ["augeas"]) # We have RRD available Puppet.features.add(:rrd, :libs => ["RRDtool"]) + +# We have rack available, an HTTP Application Stack +Puppet.features.add(:rack, :libs => ["rack"]) diff --git a/lib/puppet/network/http/rack.rb b/lib/puppet/network/http/rack.rb new file mode 100644 index 000000000..58f49416b --- /dev/null +++ b/lib/puppet/network/http/rack.rb @@ -0,0 +1,45 @@ + +require 'rack' +require 'puppet/network/http' +require 'puppet/network/http/rack/rest' + +# An rack application, for running the Puppet HTTP Server. +class Puppet::Network::HTTP::Rack + + def initialize(args) + raise ArgumentError, ":protocols must be specified." if !args[:protocols] or args[:protocols].empty? + protocols = args[:protocols] + + # Always prepare a REST handler + @rest_http_handler = Puppet::Network::HTTP::RackREST.new() + protocols.delete :rest + + raise ArgumentError, "there were unknown :protocols specified." if !protocols.empty? + end + + # The real rack application (which needs to respond to call). + # The work we need to do, roughly is: + # * Read request (from env) and prepare a response + # * Route the request to the correct handler + # * Return the response (in rack-format) to our caller. + def call(env) + request = Rack::Request.new(env) + response = Rack::Response.new() + Puppet.debug 'Handling request: %s %s' % [request.request_method, request.fullpath] + + begin + @rest_http_handler.process(request, response) + rescue => detail + # Send a Status 500 Error on unhandled exceptions. + response.status = 500 + response['Content-Type'] = 'text/plain' + response.write 'Internal Server Error: "%s"' % detail.message + # log what happened + Puppet.err "Puppet Server (Rack): Internal Server Error: Unhandled Exception: \"%s\"" % detail.message + Puppet.err "Backtrace:" + detail.backtrace.each { |line| Puppet.err " > %s" % line } + end + response.finish() + end +end + diff --git a/lib/puppet/network/http/rack/httphandler.rb b/lib/puppet/network/http/rack/httphandler.rb new file mode 100644 index 000000000..e14206850 --- /dev/null +++ b/lib/puppet/network/http/rack/httphandler.rb @@ -0,0 +1,16 @@ +require 'openssl' +require 'puppet/ssl/certificate' + +class Puppet::Network::HTTP::RackHttpHandler + + def initialize() + end + + # do something useful with request (a Rack::Request) and use + # response to fill your Rack::Response + def process(request, response) + raise NotImplementedError, "Your RackHttpHandler subclass is supposed to override service(request)" + end + +end + diff --git a/lib/puppet/network/http/rack/rest.rb b/lib/puppet/network/http/rack/rest.rb new file mode 100644 index 000000000..e98bffc1e --- /dev/null +++ b/lib/puppet/network/http/rack/rest.rb @@ -0,0 +1,74 @@ +require 'puppet/network/http/handler' +require 'puppet/network/http/rack/httphandler' + +class Puppet::Network::HTTP::RackREST < Puppet::Network::HTTP::RackHttpHandler + + include Puppet::Network::HTTP::Handler + + HEADER_ACCEPT = 'HTTP_ACCEPT'.freeze + ContentType = 'Content-Type'.freeze + + def initialize(args={}) + super() + initialize_for_puppet(args) + end + + def set_content_type(response, format) + response[ContentType] = format + end + + # produce the body of the response + def set_response(response, result, status = 200) + response.status = status + response.write result + end + + # Retrieve the accept header from the http request. + def accept_header(request) + request.env[HEADER_ACCEPT] + end + + # Return which HTTP verb was used in this request. + def http_method(request) + request.request_method + end + + # Return the query params for this request. + def params(request) + result = decode_params(request.params) + result.merge(extract_client_info(request)) + end + + # what path was requested? (this is, without any query parameters) + def path(request) + request.path + end + + # return the request body + # request.body has some limitiations, so we need to concat it back + # into a regular string, which is something puppet can use. + def body(request) + body = '' + request.body.each { |part| body += part } + body + end + + def extract_client_info(request) + result = {} + result[:ip] = request.ip + + # if we find SSL info in the headers, use them to get a hostname. + # try this with :ssl_client_header, which defaults should work for + # Apache with StdEnvVars. + if dn = request.env[Puppet[:ssl_client_header]] and dn_matchdata = dn.match(/^.*?CN\s*=\s*(.*)/) + result[:node] = dn_matchdata[1].to_str + result[:authenticated] = (request.env[Puppet[:ssl_client_verify_header]] == 'SUCCESS') + else + result[:node] = resolve_node(result) + result[:authenticated] = false + end + + result + end + +end |