Introduces a generic web puppet resource type to access various web
apis from puppet. Implements a provider of this type using curl to
issue the requests.
Then uses this provider to issue requests to the conductor api to
create providers and hardware profiles, analyzing the results for
errors
---
recipes/aeolus_recipe/aeolus_recipe.pp | 12 +--
.../aeolus_recipe/lib/puppet/provider/web/curl.rb | 114 ++++++++++++++++++++
recipes/aeolus_recipe/lib/puppet/type/web.rb | 49 +++++++++
recipes/aeolus_recipe/manifests/aeolus.pp | 4 +-
recipes/aeolus_recipe/manifests/conductor.pp | 98 +++++++----------
recipes/aeolus_recipe/manifests/defaults.pp | 13 +++
6 files changed, 219 insertions(+), 71 deletions(-)
create mode 100644 recipes/aeolus_recipe/lib/puppet/provider/web/curl.rb
create mode 100644 recipes/aeolus_recipe/lib/puppet/type/web.rb
diff --git a/recipes/aeolus_recipe/aeolus_recipe.pp
b/recipes/aeolus_recipe/aeolus_recipe.pp
index 879b314..8574584 100644
--- a/recipes/aeolus_recipe/aeolus_recipe.pp
+++ b/recipes/aeolus_recipe/aeolus_recipe.pp
@@ -33,33 +33,27 @@ include aeolus::iwhd
aeolus::create_bucket{"aeolus":}
-aeolus::site_admin{"admin":
+aeolus::site_admin{"$admin_user":
email => 'dcuser(a)aeolusproject.org',
- password => 'password',
+ password => "$admin_password",
first_name => 'aeolus',
last_name => 'user'}
aeolus::provider{"mock":
type => 'mock',
port => 3002,
- login_user => 'admin',
- login_password => 'password',
require => Aeolus::Site_admin["admin"] }
aeolus::provider{"ec2-us-east-1":
type => 'ec2',
endpoint => 'us-east-1',
port => 3003,
- login_user => 'admin',
- login_password => 'password',
require => Aeolus::Site_admin["admin"] }
aeolus::provider{"ec2-us-west-1":
type => 'ec2',
endpoint => 'us-west-1',
port => 3004,
- login_user => 'admin',
- login_password => 'password',
require => Aeolus::Site_admin["admin"] }
aeolus::conductor::hwp{"hwp1":
@@ -67,7 +61,5 @@ aeolus::conductor::hwp{"hwp1":
cpu => "1",
storage => "1",
architecture => "x86_64",
- login_user => 'admin',
- login_password => 'password',
require => Aeolus::Site_admin["admin"] }
diff --git a/recipes/aeolus_recipe/lib/puppet/provider/web/curl.rb
b/recipes/aeolus_recipe/lib/puppet/provider/web/curl.rb
new file mode 100644
index 0000000..b5f4468
--- /dev/null
+++ b/recipes/aeolus_recipe/lib/puppet/provider/web/curl.rb
@@ -0,0 +1,114 @@
+require 'curb'
+require 'uuid'
+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
+
+ curl.follow_location = (params.has_key?(:follow) && params[:follow])
+
+ 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
+
+ when 'post'
+ 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'
+ 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(", ")}"
+ 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
+
+# Puppet provider definition
+Puppet::Type.type(:web).provide :curl do
+ desc "Use curl to access web resources"
+
+ def http_method
+ @request_method
+ end
+
+ def http_method=(request_method)
+ begin
+ # Set request method and generate a unique session key
+ @request_method = request_method
+ session = "/tmp/#{UUID.new.generate}"
+
+ # Invoke a login request if necessary
+ if @resource[:login]
+ login_params = @resource[:login].reject { |k,v| ['http_method',
'uri'].include?(k) }
+ web_request(@resource[:login]['http_method'],
@resource[:login]['uri'],
+ login_params, :cookie => session, :follow =>
@resource[:follow]).close
+ end
+
+ # Check to see if we should actually run the request
+ skip_request = !@resource[:unless].nil?
+ if @resource[:unless]
+ result = web_request(@resource[:unless]['http_method'],
@resource[:unless]['uri'],
+ @resource[:unless]['parameters'],
+ :cookie => session, :follow => @resource[:follow])
+ begin
+ verify_result(result,
+ :returns => @resource[:unless]['returns'],
+ :body => @resource[:unless]['verify'])
+ rescue Puppet::Error => e
+ skip_request = false
+ end
+ result.close
+ end
+ return if skip_request
+
+ # Actually run the request and verify the result
+ uri = !@resource[:uri].nil? ? @resource[:uri] : @resource[:name]
+ result = web_request(request_method, uri, @resource[:parameters],
+ :cookie => session, :follow => @resource[:follow])
+ verify_result(result,
+ :returns => @resource[:returns],
+ :body => @resource[:verify])
+ result.close
+
+ # Invoke a logout request if necessary
+ if @resource[:logout]
+ logout_params = @resource[:login].reject { |k,v| ['http_method',
'uri'].include?(k) }
+ web_request(@resource[:logout]['http_method'],
@resource[:logout]['uri'],
+ logout_params, :cookie => session, :follow =>
@resource[:follow]).close
+ end
+
+ rescue Exception => e
+ raise Puppet::Error, "An exception was raised when invoking web request:
#{e}"
+
+ ensure
+ FileUtils.rm_f(session) if @resource[:logout]
+ end
+ end
+end
diff --git a/recipes/aeolus_recipe/lib/puppet/type/web.rb
b/recipes/aeolus_recipe/lib/puppet/type/web.rb
new file mode 100644
index 0000000..a9bf765
--- /dev/null
+++ b/recipes/aeolus_recipe/lib/puppet/type/web.rb
@@ -0,0 +1,49 @@
+Puppet::Type.newtype(:web) do
+ @doc = "Issue a request via the world wide web"
+
+ newparam :name
+
+ newparam(:uri) do
+ desc "URI to the web resource"
+ end
+
+ newproperty(:http_method) do
+ desc "HTTP method through which to issue the web request"
+ validate do |value|
+ unless ['post', 'get', 'put',
'delete'].include?(value)
+ raise ArgumentError, "#{value} is not a valid HTTP request method"
+ end
+ 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
+ end
+
+ newparam(:follow) do
+ desc "Boolean indicating if redirects should be followed"
+ newvalues(:true, :false)
+ end
+
+ newparam(:verify) do
+ desc "String to verify as being part of the result"
+ end
+
+ newparam(:login) do
+ desc "Login parameters to be used if a login is required before making the
request"
+ end
+
+ newparam(:logout) do
+ desc "Logout parameters to be used if a logout is requred after making the
request"
+ end
+
+ newparam(:unless) do
+ desc "Do not run request if the request specified here succeeds"
+ end
+end
diff --git a/recipes/aeolus_recipe/manifests/aeolus.pp
b/recipes/aeolus_recipe/manifests/aeolus.pp
index ca684bb..a399771 100644
--- a/recipes/aeolus_recipe/manifests/aeolus.pp
+++ b/recipes/aeolus_recipe/manifests/aeolus.pp
@@ -54,13 +54,11 @@ class aeolus {
}
# Create a new provider in aeolus
-define aeolus::provider($type, $port, $endpoint="", $login_user="",
$login_password=""){
+define aeolus::provider($type, $port, $endpoint=""){
aeolus::deltacloud{$name: provider_type => $type, endpoint => $endpoint, port
=> $port}
aeolus::conductor::provider{$name:
type => $type,
url =>
"http://localhost:${port}/api",
- login_user => $login_user,
- login_password => $login_password,
require => Aeolus::Deltacloud[$name] }
}
diff --git a/recipes/aeolus_recipe/manifests/conductor.pp
b/recipes/aeolus_recipe/manifests/conductor.pp
index fee834f..7312e3a 100644
--- a/recipes/aeolus_recipe/manifests/conductor.pp
+++ b/recipes/aeolus_recipe/manifests/conductor.pp
@@ -228,66 +228,48 @@ define aeolus::site_admin($email="",
$password="", $first_name="", $last_name=""
require => Exec[create_site_admin_user]}
}
-define aeolus::conductor::login($user,$password){
- exec{"conductor-login-for-${name}":
- command => "/usr/bin/curl -X POST
http://localhost/conductor/user_session \
- -d user_session[login]=${user} \
- -d user_session[password]=${password} \
- -d commit=submit \
- -c /tmp/aeolus-${user}.cookie \
- --location --post301 --post302 -k -f",
- onlyif => "/usr/bin/test ! -f /tmp/aeolus-${user}.cookie ||
\"\" == \"`curl -X GET
http://localhost/conductor -b
/tmp/aeolus-${user}.cookie -i --silent | grep 'HTTP/1.1 200'`\"",
- require => Service['aeolus-conductor', 'httpd']}
-}
-
-define aeolus::conductor::logout($user){
- exec{"conductor-logout-for-${name}":
- command => "/usr/bin/curl -X GET
http://localhost/conductor/logout -b
/tmp/aeolus-${user}.cookie --location -k -f",
- onlyif => "/usr/bin/test -f /tmp/aeolus-${user}.cookie" } # TODO
add condition ensuring cookie / session is valid
- exec{"conductor-logout-cookie-for-${name}":
- command => "/bin/rm /tmp/aeolus-${user}.cookie",
- onlyif => "/usr/bin/test -f /tmp/aeolus-${user}.cookie",
- require => Exec["conductor-logout-for-${name}"]}
-}
-
-
# Create a new provider via the conductor
-define
aeolus::conductor::provider($type="",$url="",$login_user="",$login_password=""){
- aeolus::conductor::login{"provider-${name}": user => $login_user, password
=> $login_password }
- exec{"add-conductor-provider-${name}":
- command => "/usr/bin/curl -X POST
http://localhost/conductor/admin/providers \
- -b /tmp/aeolus-${login_user}.cookie \
- -d provider[name]=${name} \
- -d provider[url]=${url} \
- -d provider[provider_type_codename]=${type} \
- --location --post301 --post302 -k -f",
- logoutput => true,
- require => [Aeolus::Conductor::Login["provider-$name"]] }
- aeolus::conductor::logout{"provider-${name}":
- user => $login_user,
- require => Exec["add-conductor-provider-${name}"] }
+define aeolus::conductor::provider($type="",$url=""){
+ web{ "provider-$name":
+ uri => "https://localhost/conductor/admin/providers",
+ http_method => 'post',
+ parameters => { 'provider[name]' => $name, 'provider[url]'
=> $url,
+ 'provider[provider_type_codename]' => $type },
+ returns => '200',
+ verify => '.*Provider added.*',
+ follow => true,
+ unless => { 'http_method' => 'get',
+ 'uri' =>
'https://localhost/conductor/admin/providers',
+ 'verify' => ".*$name.*" },
+ require => Service['aeolus-conductor']
+ }
}
-define aeolus::conductor::hwp($memory='', $cpu='', $storage='',
$architecture='', $login_user="",$login_password=""){
- aeolus::conductor::login{"hwp-${name}": user => $login_user, password
=> $login_password }
- exec{"add-conductor-hwp-${name}":
- command => "/usr/bin/curl -X POST
http://localhost/conductor/admin/hardware_profiles \
- -b /tmp/aeolus-${login_user}.cookie \
- -d hardware_profile[name]=${name} \
- -d hardware_profile[memory_attributes][value]=${memory} \
- -d hardware_profile[cpu_attributes][value]=${cpu} \
- -d hardware_profile[storage_attributes][value]=${storage} \
- -d
hardware_profile[architecture_attributes][value]=${architecture} \
- -d hardware_profile[memory_attributes][name]=memory -d
hardware_profile[memory_attributes][unit]=MB \
- -d hardware_profile[cpu_attributes][name]=cpu -d
hardware_profile[cpu_attributes][unit]=count \
- -d hardware_profile[storage_attributes][name]=storage -d
hardware_profile[storage_attributes][unit]=GB \
- -d hardware_profile[architecture_attributes][name]=architecture
-d hardware_profile[architecture_attributes][unit]=label \
- -d commit=Save \
- --location --post301 --post302 -k -f",
- logoutput => true,
- require => [Aeolus::Conductor::Login["hwp-$name"]] }
- aeolus::conductor::logout{"hwp-${name}":
- user => $login_user,
- require => Exec["add-conductor-hwp-${name}"] }
+define aeolus::conductor::hwp($memory='', $cpu='', $storage='',
$architecture=''){
+ web{ "hwp-$name":
+ uri => "https://localhost/conductor/admin/hardware_profiles",
+ http_method => 'post',
+ parameters => {'hardware_profile[name]' => $name,
+ 'hardware_profile[memory_attributes][value]' =>
$memory,
+ 'hardware_profile[cpu_attributes][value]' =>
$cpu,
+ 'hardware_profile[storage_attributes][value]' =>
$storage,
+ 'hardware_profile[architecture_attributes][value]' =>
$architecture,
+ 'hardware_profile[memory_attributes][name]' =>
'memory',
+ 'hardware_profile[memory_attributes][unit]' =>
'MB',
+ 'hardware_profile[cpu_attributes][name]' =>
'cpu',
+ 'hardware_profile[cpu_attributes][unit]' =>
'count',
+ 'hardware_profile[storage_attributes][name]' =>
'storage',
+ 'hardware_profile[storage_attributes][unit]' =>
'GB',
+ 'hardware_profile[architecture_attributes][name]' =>
'architecture',
+ 'hardware_profile[architecture_attributes][unit]' =>
'label',
+ 'commit' => 'Save'},
+ returns => '200',
+ #verify => '.*Hardware profile added.*',
+ follow => true,
+ unless => { 'http_method' => 'get',
+ 'uri' =>
'https://localhost/conductor/admin/hardware_profiles',
+ 'verify' => ".*$name.*" },
+ require => Service['aeolus-conductor']
+ }
}
diff --git a/recipes/aeolus_recipe/manifests/defaults.pp
b/recipes/aeolus_recipe/manifests/defaults.pp
index f5c74c3..57b79f6 100644
--- a/recipes/aeolus_recipe/manifests/defaults.pp
+++ b/recipes/aeolus_recipe/manifests/defaults.pp
@@ -8,3 +8,16 @@
Package {provider => 'rpm'}
+$admin_user='admin'
+$admin_password='password'
+
+# Setup the default login/logout targets for web requests
+Web{
+ 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' }
+}
--
1.7.2.3