summaryrefslogtreecommitdiffstats
path: root/lib/puppet
diff options
context:
space:
mode:
authorChristian Hofstaedtler <hofstaedtler@inqnet.at>2009-04-28 12:23:58 +0000
committerJames Turnbull <james@lovedthanlost.net>2009-05-02 09:13:29 +1000
commit6e01e7ab403d090f29f13c938ca5b19930c4b408 (patch)
tree413dd59275768143ace1161a43dccc640744ccfc /lib/puppet
parentcc09c1af21e218917cc02c0ad9d8c44300803f49 (diff)
downloadpuppet-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.rb3
-rw-r--r--lib/puppet/network/http/rack.rb45
-rw-r--r--lib/puppet/network/http/rack/httphandler.rb16
-rw-r--r--lib/puppet/network/http/rack/rest.rb74
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