--- .../aeolus/lib/puppet/provider/web_request/curl.rb | 260 +++++++++++++------- recipes/aeolus/lib/puppet/type/web_request.rb | 108 +++++++-- recipes/aeolus/manifests/defaults.pp | 14 - 3 files changed, 264 insertions(+), 118 deletions(-)
diff --git a/recipes/aeolus/lib/puppet/provider/web_request/curl.rb b/recipes/aeolus/lib/puppet/provider/web_request/curl.rb index 724f70f..14da60d 100644 --- a/recipes/aeolus/lib/puppet/provider/web_request/curl.rb +++ b/recipes/aeolus/lib/puppet/provider/web_request/curl.rb @@ -1,107 +1,86 @@ -require 'curb' -require 'uuidtools' require 'fileutils'
-# Helper to invoke the web request w/ curl -def web_request(method, uri, request_params, params = {}) - raise Puppet::Error, "Must specify http method and uri" if method.nil? || uri.nil? - - curl = Curl::Easy.new - - if params.has_key?(:cookie) - curl.enable_cookies = true - curl.cookiefile = params[:cookie] - curl.cookiejar = params[:cookie] - end +# Provides an interface to curl using the curb gem for puppet +require 'curb'
- curl.follow_location = (params.has_key?(:follow) && params[:follow]) +# uses nokogiri to verify responses w/ xpath +require 'nokogiri'
- case(method) - when 'get' - url = uri - url += ";" + request_params.collect { |k,v| "#{k}=#{v}" }.join("&") unless request_params.nil? - curl.url = url - curl.http_get - return curl +class Curl::Easy
- when 'post' + # Format request parameters for the specified request method + def self.format_params(method, params, file_params) + if([:get, :delete].include?(method)) + return params.collect { |k,v| "#{k}=#{v}" }.join("&") unless params.nil? + return "" + end + # post, put: cparams = [] - request_params.each_pair { |k,v| cparams << Curl::PostField.content(k,v) } unless request_params.nil? - curl.url = uri - curl.http_post(cparams) - return curl - - #when 'put' - #when 'delete' + params.each_pair { |k,v| cparams << Curl::PostField.content(k,v) } unless params.nil? + file_params.each_pair { |k,v| cparams << Curl::PostField.file(k,v) } unless file_params.nil? + return cparams end -end
-# Helper to verify the response -def verify_result(result, verify = {}) - returns = (verify.has_key?(:returns) && !verify[:returns].nil?) ? verify[:returns] : "200" - returns = [returns] unless returns.is_a? Array - unless returns.include?(result.response_code.to_s) - raise Puppet::Error, "Invalid HTTP Return Code: #{result.response_code}, - was expecting one of #{returns.join(", ")}" + # Format a url for the specified request method, base uri, and parameters + def self.format_url(method, uri, params) + if([:get, :delete].include?(method)) + url = uri + url += ";" + format_params(method, params) + return url + end + # post, put: + return uri end
- if verify.has_key?(:body) && !verify[:body].nil? && !(result.body_str =~ Regexp.new(verify[:body])) - raise Puppet::Error, "Expecting #{verify[:body]} in the result" - end -end + # Invoke a new curl request and return result + def self.web_request(method, uri, params = {}) + raise Puppet::Error, "Must specify http method (#{method}) and uri (#{uri})" if method.nil? || uri.nil?
-# Helper to process/parse web parameters -def process_params(request_method, params, uri) - begin - # Set request method and generate a unique session key - session = "/tmp/#{UUIDTools::UUID.timestamp_create.to_s}" - - # Invoke a login request if necessary - if params[:login] - login_params = params[:login].reject { |k,v| ['http_method', 'uri'].include?(k) } - web_request(params[:login]['http_method'], params[:login]['uri'], - login_params, :cookie => session, :follow => params[:follow]).close - end + curl = self.new
- # Check to see if we should actually run the request - skip_request = !params[:unless].nil? - if params[:unless] - result = web_request(params[:unless]['http_method'], params[:unless]['uri'], - params[:unless]['parameters'], - :cookie => session, :follow => params[:follow]) - begin - verify_result(result, - :returns => params[:unless]['returns'], - :body => params[:unless]['verify']) - rescue Puppet::Error => e - skip_request = false - end - result.close + if params.has_key?(:cookie) && !params[:cookie].nil? + curl.enable_cookies = true + curl.cookiefile = params[:cookie] + curl.cookiejar = params[:cookie] end - return if skip_request - - # Actually run the request and verify the result - uri = params[:name] if uri.nil? - result = web_request(request_method, uri, params[:parameters], - :cookie => session, :follow => params[:follow]) - verify_result(result, - :returns => params[:returns], - :body => params[:verify]) - result.close - - # Invoke a logout request if necessary - if params[:logout] - logout_params = params[:login].reject { |k,v| ['http_method', 'uri'].include?(k) } - web_request(params[:logout]['http_method'], params[:logout]['uri'], - logout_params, :cookie => session, :follow => params[:follow]).close + + curl.follow_location = (params.has_key?(:follow) && params[:follow]) + request_params = params[:parameters] + file_params = params[:file_parameters] + + case(method) + when 'get' + curl.url = format_url(method, uri, request_params) + curl.http_get + return curl + + when 'post' + curl.url = format_url(method, uri, request_params) + curl.multipart_form_post = true if !file_params.nil? && file_params.size > 0 + curl.http_post(*format_params(method, request_params, file_params)) + return curl + + when 'put' + curl.url = format_url(method, uri, request_params) + curl.multipart_form_post = true if !file_params.nil? && file_params.size > 0 + curl.http_put(*format_params(method, request_params, file_params)) + return curl + + when 'delete' + curl.url = format_url(method, uri, request_params) + curl.http_delete + return curl end + end
- rescue Exception => e - raise Puppet::Error, "An exception was raised when invoking web request: #{e}" + def valid_status_code?(valid_values=[]) + valid_values.include?(response_code.to_s) + end
- ensure - FileUtils.rm_f(session) if params[:logout] + def valid_xpath?(xpath="/") + !Nokogiri::HTML(body_str.to_s).xpath(xpath.to_s).empty? end + end
# Puppet provider definition @@ -116,6 +95,14 @@ Puppet::Type.type(:web_request).provide :curl do @uri end
+ def delete + @uri + end + + def put + @uri + end + def get=(uri) @uri = uri process_params('get', @resource, uri) @@ -125,4 +112,103 @@ Puppet::Type.type(:web_request).provide :curl do @uri = uri process_params('post', @resource, uri) end + + def delete=(uri) + @uri = uri + process_params('delete', @resource, uri) + end + + def put=(uri) + @uri = uri + process_params('put', @resource, uri) + end + + private + + # Helper to process/parse web parameters + def process_params(request_method, params, uri) + begin + cookies = nil + if params[:store_cookies_at] + FileUtils.touch(params[:store_cookies_at]) if !File.exist?(params[:store_cookies_at]) + cookies = params[:store_cookies_at] + elsif params[:use_cookies_at] + cookies = params[:use_cookies_at] + end + + # verify that we should actually run the request + return if skip_request?(params, cookies) + + # Actually run the request and verify the result + result = Curl::Easy::web_request(request_method, uri, + :parameters => params[:parameters], + :file_parameters => params[:file_parameters], + :cookie => cookies, + :follow => params[:follow]) + verify_result(result, + :returns => params[:returns], + :does_not_return => params[:does_not_return], + :contains => params[:contains], + :does_not_contain => params[:does_not_contain] ) + result.close + + rescue Exception => e + raise Puppet::Error, "An exception was raised when invoking web request: #{e}" + + ensure + FileUtils.rm_f(cookies) if params[:remove_cookies] + end + end + + # Helper to determine if we should skip the request + def skip_request?(params, cookie = nil) + [:if, :unless].each { |c| + condition = params[c] + unless condition.nil? + method = (condition.keys & ['get', 'post', 'delete', 'put']).first + result = Curl::Easy::web_request(method, condition[method], + :parameters => condition['parameters'], + :file_parameters => condition['file_parameters'], + :cookie => cookie, :follow => condition[:follow]) + result_succeeded = true + begin + verify_result(result, condition) + rescue Puppet::Error + result_succeeded = false + end + return true if (c == :if && !result_succeeded) || (c == :unless && result_succeeded) + end + } + return false + end + + # Helper to verify the response + def verify_result(result, verify = {}) + verify[:returns] = verify['returns'] if verify[:returns].nil? && !verify['returns'].nil? + verify[:does_not_return] = verify['does_not_return'] if verify[:does_not_return].nil? && !verify['does_not_return'].nil? + verify[:contains] = verify['contains'] if verify[:contains].nil? && !verify['contains'].nil? + verify[:does_not_contain] = verify['does_not_contain'] if verify[:does_not_contain].nil? && !verify['does_not_contain'].nil? + + if !verify[:returns].nil? && + !result.valid_status_code?(verify[:returns]) + raise Puppet::Error, "Invalid HTTP Return Code: #{result.response_code}, + was expecting one of #{verify[:returns].join(", ")}" + end + + if !verify[:does_not_return].nil? && + result.valid_status_code?(verify[:does_not_return]) + raise Puppet::Error, "Invalid HTTP Return Code: #{result.response_code}, + was not expecting one of #{verify[:does_not_return].join(", ")}" + end + + if !verify[:contains].nil? && + !result.valid_xpath?(verify[:contains]) + raise Puppet::Error, "Expecting #{verify[:contains]} in the result" + end + + if !verify[:does_not_contain].nil? && + result.valid_xpath?(verify[:does_not_contain]) + raise Puppet::Error, "Not expecting #{verify[:does_not_contain]} in the result" + end + end end diff --git a/recipes/aeolus/lib/puppet/type/web_request.rb b/recipes/aeolus/lib/puppet/type/web_request.rb index 5225633..407b2fe 100644 --- a/recipes/aeolus/lib/puppet/type/web_request.rb +++ b/recipes/aeolus/lib/puppet/type/web_request.rb @@ -1,29 +1,74 @@ +require 'uri' + +# A puppet resource type used to access resources on the World Wide Web Puppet::Type.newtype(:web_request) do - @doc = "Issue a request via the world wide web" + @doc = "Issue a request to a resource on the world wide web" + + private + + # Validates uris passed in + def self.validate_uri(url) + begin + uri = URI.parse(url) + raise ArgumentError, "Specified uri #{url} is not valid" if ![URI::HTTP, URI::HTTPS].include?(uri.class) + rescue URI::InvalidURIError + raise ArgumentError, "Specified uri #{url} is not valid" + end + end + + # Validates http statuses passed in + def self.validate_http_status(status) + status = [status] unless status.is_a?(Array) + status.each { |stat| + stat = stat.to_s + unless ['100', '101', '102', '122', + '200', '201', '202', '203', '204', '205', '206', '207', '226', + '300', '301', '302', '303', '304', '305', '306', '307', + '400', '401', '402', '403', '404', '405', '406', '407', '408', '409', + '410', '411', '412', '413', '414', '415', '416', '417', '418', + '422', '423', '424', '425', '426', '444', '449', '450', '499', + '500', '501', '502', '503', '504', '505', '506', '507', '508', ' 509', '510' + ].include?(stat) + raise ArgumentError, "Invalid http status code #{stat} specified" + end + } + end + + # Convert singular params into arrays of strings + def self.munge_array_params(value) + value = [value] unless value.is_a?(Array) + value = value.collect { |val| val.to_s } + value + end
newparam :name
newproperty(:get) do desc "Issue get request to the specified uri" - # TODO valid value to be a uri + validate do |value| Puppet::Type::Web_request.validate_uri(value) end end
newproperty(:post) do - desc "Issue get request to the specified uri" - # TODO valid value to be a uri + desc "Issue post request to the specified uri" + validate do |value| Puppet::Type::Web_request.validate_uri(value) end end
- #newproperty(:delete) - #newproperty(:put) + newproperty(:delete) do + desc "Issue delete request to the specified uri" + validate do |value| Puppet::Type::Web_request.validate_uri(value) end + end + + newproperty(:put) do + desc "Issue put request to the specified uri" + validate do |value| Puppet::Type::Web_request.validate_uri(value) end + end
newparam(:parameters) do desc "Hash of parameters to include in the web request" end
- newparam(:returns) do - desc "Expected http return codes of the request" - defaultto "200" - # TODO validate value(s) is among possible valid http return codes + newparam(:file_parameters) do + desc "Hash of file parameters to include in the web request" end
newparam(:follow) do @@ -31,19 +76,48 @@ Puppet::Type.newtype(:web_request) do newvalues(:true, :false) end
- newparam(:verify) do - desc "String to verify as being part of the result" + newparam(:store_cookies_at) do + desc "String indicating where session cookies should be stored" end
- newparam(:login) do - desc "Login parameters to be used if a login is required before making the request" + newparam(:use_cookies_at) do + desc "String indicating where session cookies should be read from" end
- newparam(:logout) do - desc "Logout parameters to be used if a logout is requred after making the request" + newparam(:remove_cookies) do + desc "Boolean indicating if cookies should be removed after using them" + newvalues(:true, :false) + end + + newparam(:returns) do + desc "Expected http return codes of the request" + defaultto ["200"] + validate do |value| Puppet::Type::Web_request.validate_http_status(value) end + munge do |value| Puppet::Type::Web_request.munge_array_params(value) end + end + + newparam(:does_not_return) do + desc "Unexecpected http return codes of the request" + validate do |value| Puppet::Type::Web_request.validate_http_status(value) end + munge do |value| Puppet::Type::Web_request.munge_array_params(value) end + end + + newparam(:contains) do + desc "XPath to verify as part of the result" + munge do |value| Puppet::Type::Web_request.munge_array_params(value) end + end + + newparam(:does_not_contain) do + desc "XPath to verify as not being part of the result" + munge do |value| Puppet::Type::Web_request.munge_array_params(value) end + end + + newparam(:if) do + desc "Invoke request only if the specified request returns true" end
newparam(:unless) do - desc "Do not run request if the request specified here succeeds" + desc "Invoke request unless the specified request returns true" end + end diff --git a/recipes/aeolus/manifests/defaults.pp b/recipes/aeolus/manifests/defaults.pp index f2fc891..f43fad3 100644 --- a/recipes/aeolus/manifests/defaults.pp +++ b/recipes/aeolus/manifests/defaults.pp @@ -7,17 +7,3 @@ #switching back to yum
Package {provider => 'rpm'} - -$admin_user='admin' -$admin_password='password' - -# Setup the default login/logout targets for web requests -Web_request{ - login => { 'http_method' => 'post', - 'uri' => 'https://localhost/conductor/user_session', - 'user_session[login]' => "$admin_user", - 'user_session[password]' => "$admin_password", - 'commit' => 'submit' }, - logout => { 'http_method' => 'post', - 'uri' => 'https://localhost/conductor/logout' } -}