The following patchset adds support for aeolus configuration profiles, or collections of components and seed data which the user can install and configure independently of other bits.
Additionally an interactive installer (which can be invoked via 'aeolus-configure -i') is included, prompting the user for the components to create, and the seed data to initialize (providers to setup, account credentials, repos and software to use to setup and deploy images, etc).
The interactive installer is a completely optional feature, the node yml and puppet recipe (which is the output of the installer anyways) can still be used as is for automated installations
The patchset most likely will need a little more work to shore things up, but the main functionality is in place
--- .../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' } -}
--- recipes/aeolus/manifests/conductor.pp | 86 ++++++++++++++++++++++++++-- recipes/aeolus/manifests/image-factory.pp | 41 ++++++++++++++ 2 files changed, 120 insertions(+), 7 deletions(-)
diff --git a/recipes/aeolus/manifests/conductor.pp b/recipes/aeolus/manifests/conductor.pp index bdc0064..985b883 100644 --- a/recipes/aeolus/manifests/conductor.pp +++ b/recipes/aeolus/manifests/conductor.pp @@ -203,6 +203,32 @@ define aeolus::site_admin($email="", $password="", $first_name="", $last_name="" require => Exec[create_site_admin_user]} }
+# login to the aeolus conductor +define aeolus::conductor::login($password){ + web_request{ "$name-conductor-login": + post => 'https://localhost/conductor/user_session', + parameters => { 'user_session[login]' => "$name", 'user_session[password]' => "$password", + 'commit' => 'submit' }, + returns => '200', + follow => true, + store_cookies_at => "/tmp/aeolus-$name", + require => Service['aeolus-conductor'] + } +} + +# log out of the aeolus conductor +define aeolus::conductor::logout(){ + web_request{ "$name-conductor-logout": + post => 'https://localhost/conductor/logout', + parameters => { 'user_session[login]' => "admin", 'user_session[password]' => "password", + 'commit' => 'submit' }, + returns => '200', + follow => true, + use_cookies_at => "/tmp/aeolus-$name", + remove_cookies => true + } +} + # Create a new provider via the conductor define aeolus::conductor::provider($type="",$url=""){ web_request{ "provider-$name": @@ -210,15 +236,60 @@ define aeolus::conductor::provider($type="",$url=""){ parameters => { 'provider[name]' => $name, 'provider[url]' => $url, 'provider[provider_type_codename]' => $type }, returns => '200', - verify => '.*Provider added.*', + contains => "//html/body//li[text() = 'Provider added.']", follow => true, - unless => { 'http_method' => 'get', - 'uri' => 'https://localhost/conductor/providers', - 'verify' => ".*$name.*" }, + use_cookies_at => '/tmp/aeolus-admin', + unless => { 'get' => 'https://localhost/conductor/providers', + 'contains' => "//html/body//a[text() = '$name']" }, require => [Service['aeolus-conductor'], Exec['grant_site_admin_privs']] } }
+# Create a new provider account via the conductor +define aeolus::conductor::provider::account($provider="", $type="", $username="",$password="", $account_id="",$x509private="", $x509public=""){ + if $type == "mock" { + web_request{ "provider-account-$name": + post => "https://localhost/conductor/provider_accounts", + parameters => { 'provider_account[label]' => $name, + 'provider_account[provider]' => $provider, + 'provider_account[credentials_hash[username]]' => $username, + 'provider_account[credentials_hash[password]]' => $password, + 'quota[max_running_instances]' => 'unlimited', + 'commit' => 'Save' }, + + returns => '200', + #contains => "//table/thead/tr/th[text() = 'Properties for $name']", + follow => true, + use_cookies_at => '/tmp/aeolus-admin', + unless => { 'get' => 'https://localhost/conductor/provider_accounts', + 'contains' => "//html/body//a[text() = '$name']" }, + require => Service['aeolus-conductor']} + + } elsif $type == "ec2" { + web_request{ "provider-account-$name": + post => "https://localhost/conductor/provider_accounts", + parameters => { 'provider_account[label]' => $name, + 'provider_account[provider]' => $provider, + 'provider_account[credentials_hash[username]]' => $username, + 'provider_account[credentials_hash[password]]' => $password, + 'provider_account[credentials_hash[account_id]]' => $account_id, + 'quota[max_running_instances]' => 'unlimited', + 'commit' => 'Save' }, + file_parameters => { 'provider_account[credentials_hash[x509private]]'=> $x509private, + 'provider_account[credentials_hash[x509public]]' => $x509public }, + + returns => '200', + #contains => "//table/thead/tr/th[text() = 'Properties for $name']", + follow => true, + use_cookies_at => '/tmp/aeolus-admin', + unless => { 'get' => 'https://localhost/conductor/provider_accounts', + 'contains' => "//html/body//a[text() = '$name']" }, + require => Service['aeolus-conductor'] + } + } +} + + define aeolus::conductor::hwp($memory='', $cpu='', $storage='', $architecture=''){ web_request{ "hwp-$name": post => "https://localhost/conductor/hardware_profiles", @@ -239,9 +310,10 @@ define aeolus::conductor::hwp($memory='', $cpu='', $storage='', $architecture='' returns => '200', #verify => '.*Hardware profile added.*', follow => true, - unless => { 'http_method' => 'get', - 'uri' => 'https://localhost/conductor/hardware_profiles', - 'verify' => ".*$name.*" }, + use_cookies_at => '/tmp/aeolus-admin', + #unless => { 'http_method' => 'get', + # 'uri' => 'https://localhost/conductor/hardware_profiles', + # 'verify' => ".*$name.*" }, require => [Service['aeolus-conductor'], Exec['grant_site_admin_privs']] } } diff --git a/recipes/aeolus/manifests/image-factory.pp b/recipes/aeolus/manifests/image-factory.pp index 0bb9e72..ede106d 100644 --- a/recipes/aeolus/manifests/image-factory.pp +++ b/recipes/aeolus/manifests/image-factory.pp @@ -2,6 +2,13 @@
class aeolus::image-factory inherits aeolus {
+ # image factory client + package { 'rubygem-aeolus-image': ensure => 'installed' } + file{"/root/.aeolus-cli": + source => "/usr/lib/ruby/gems/1.8/gems/aeolus-image-0.0.1/examples/aeolus-cli", + require => Package['rubygem-aeolus-image'] } + + # image factory services package { 'libvirt': ensure=> 'installed' } @@ -61,3 +68,37 @@ class aeolus::image-factory::disabled { exec{"remove_aeolus_templates": command => "/bin/rm -rf /templates"} }
+define aeolus::image($template, $provider='', $target=''){ + exec{"build-$name-image": logoutput => true, timeout => 0, + command => "/usr/sbin/aeolus-configure-image $name $target $template $provider", + require => Service['aeolus-conductor']} + + web_request{ "deployment-$name": + post => "https://localhost/conductor/deployments", + parameters => { 'deployable_url' => "http://localhost/deployables/$name.xml", + 'deployment[name]' => $name, + 'deployment[pool_id]' => '1', + 'deployment[frontend_realm_id]' => '' , + 'commit' => 'Next', + 'suggested_deployable_id' => "other"}, + returns => '200', + #contains => "//html/body//li[text() = 'Provider added.']", + follow => true, + use_cookies_at => '/tmp/aeolus-admin', + #unless => { 'get' => 'https://localhost/conductor/providers', + # 'contains' => "//html/body//a[text() = '$name']" }, + require => Exec["build-$name-image"] + } + + #web_request{ "launch-deployment-$name": + # post => "https://localhost/conductor/deployments/new", + # parameters => { 'deployable_name' => $name }, + # returns => '200', + # #contains => "//html/body//li[text() = 'Provider added.']", + # follow => true, + # use_cookies_at => '/tmp/aeolus-admin', + # #unless => { 'get' => 'https://localhost/conductor/providers', + # # 'contains' => "//html/body//a[text() = '$name']" }, + # require => Web_request["deployment-$name"] + #} +}
--- bin/aeolus-configure-image | 47 ++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 47 insertions(+), 0 deletions(-) create mode 100755 bin/aeolus-configure-image
diff --git a/bin/aeolus-configure-image b/bin/aeolus-configure-image new file mode 100755 index 0000000..c2ff846 --- /dev/null +++ b/bin/aeolus-configure-image @@ -0,0 +1,47 @@ +#!/usr/bin/ruby +# helper script to generate aeolus images + +image_name = ARGV[0] +target = ARGV[1] +template = ARGV[2] +provider = ARGV[3] + +Dir.chdir '/usr/lib/ruby/gems/1.8/gems/aeolus-image-0.0.1' + +cmd = "/usr/bin/ruby -rrubygems \ + /usr/lib/ruby/gems/1.8/gems/aeolus-image-0.0.1/bin/aeolus-image build \ + --target #{target} --template #{template}" +puts "Building image for #{target} using #{template}" +#puts " Running build command #{cmd}" +out = `#{cmd}` +puts "Image build returned w/ exit code #{$?}" +puts "Image build output: #{out}" + +if out =~ /^\s*Image:\s*([0-9a-zA-Z-]*).*/ + image = $1 + cmd = "/usr/bin/ruby -rrubygems \ + /usr/lib/ruby/gems/1.8/gems/aeolus-image-0.0.1/bin/aeolus-image push \ + --provider #{provider} --id #{image}" + + puts "Image #{image} built, pushing to #{provider}" + #puts "Running push command #{cmd}" + out = `#{cmd}` + + puts "Image push returned w/ exit code #{$?}" + puts "Image push output: #{out}" + + deployables_dir = '/var/www/html/deployables' + FileUtils.mkdir deployables_dir unless File.exist? deployables_dir + File.open("/var/www/html/deployables/#{image_name}.xml", "w") { |f| + f.write "<deployable name='#{image_name}'>\n" + + " <assemblies name = '#{image_name}'>\n" + + " <assembly name='#{image_name}' hwp='hwp1' >\n" + + " <image id='#{image}'></image>\n" + + " </assembly>\n" + + " </assemblies>\n" + + "</deployable>\n" + } + + puts "Deployment definition written" + puts "Image building complete" +end
After this patch we now have the ability to setup different profiles of aeolus components to install and seed data to initialize.
As these profiles are based on the node yml and puppet recipes, they can be fully automated as well as manually generated with an interactive installer (see the next patch) --- bin/aeolus-cleanup | 6 ++- bin/aeolus-configure | 26 +++++++++++--- conf/default_cleanup | 2 - conf/default_configure | 13 +------ conf/default_custom | 7 ++++ recipes/aeolus/manifests/conductor.pp | 35 ------------------ recipes/aeolus/manifests/init.pp | 3 ++ recipes/aeolus/manifests/profiles/custom.pp | 25 +++++++++++++ recipes/aeolus/manifests/profiles/default.pp | 51 ++++++++++++++++++++++++++ 9 files changed, 112 insertions(+), 56 deletions(-) create mode 100644 conf/default_custom create mode 100644 recipes/aeolus/manifests/profiles/custom.pp create mode 100644 recipes/aeolus/manifests/profiles/default.pp
diff --git a/bin/aeolus-cleanup b/bin/aeolus-cleanup index bdbbacd..8e3abcd 100644 --- a/bin/aeolus-cleanup +++ b/bin/aeolus-cleanup @@ -31,11 +31,13 @@ while true ; do esac done
+PUPPET_NODE='cleanup' + export FACTER_AEOLUS_ENABLE_HTTPS=true export FACTER_AEOLUS_ENABLE_SECURITY=false -puppet /usr/share/aeolus-configure/modules/aeolus/manifests/defaults.pp \ +puppet /usr/share/aeolus-configure/aeolus.pp \ --modulepath=/usr/share/aeolus-configure/modules/ \ - --external_nodes '/usr/sbin/aeolus-node cleanup' --node_terminus exec \ + --external_nodes "/usr/sbin/aeolus-node $PUPPET_NODE" --node_terminus exec \ --logdest=/var/log/aeolus-configure/aeolus-cleanup.log \ --logdest=console \ $LOGLEVEL diff --git a/bin/aeolus-configure b/bin/aeolus-configure index 310b76e..2798c7b 100644 --- a/bin/aeolus-configure +++ b/bin/aeolus-configure @@ -4,39 +4,55 @@ usage() cat << EOF
USAGE: -aeolus-configure [-d|--debug] [-h|--help] [-v|--verbose] +aeolus-configure [-d|--debug] [-h|--help] [-v|--verbose] [-i|--interactive]
OPTIONS: -h | --help Show this message. -d | --debug Debug logging mode. -v | --verbose Verbose logging mode. + -i | --interactive Interactive installer. EOF }
-args=`getopt -o :hdv --long help,debug,verbose -- "$@"` +args=`getopt -o :hdvi --long help,debug,verbose,interactive -- "$@"` if test $? != 0 then usage exit 1 fi
+INTERACTIVE=1 + eval set -- $args while true ; do case "$1" in -h|--help) usage ; exit 1 ; shift ;; -d|--debug) LOGLEVEL="--debug" ; shift ;; -v|--verbose) LOGLEVEL="--verbose" ; shift ;; + -i|--interactive) INTERACTIVE=0 ; shift ;; --) shift ; break ;; *) usage ; exit 1 ;; esac done
+PUPPET_NODE='configure' + +if [[ $INTERACTIVE -eq 0 ]] ; then +PUPPET_NODE='custom' +/usr/bin/ruby /usr/share/aeolus-configure/cli.rb +if [ $? == 1 ] ; then +echo +exit +fi +fi + +echo "Launching aeolus configuration recipe..." + export FACTER_AEOLUS_ENABLE_HTTPS=true export FACTER_AEOLUS_ENABLE_SECURITY=false -puppet /usr/share/aeolus-configure/modules/aeolus/manifests/defaults.pp \ +puppet /usr/share/aeolus-configure/aeolus.pp \ --modulepath=/usr/share/aeolus-configure/modules/ \ - --external_nodes '/usr/sbin/aeolus-node configure' --node_terminus exec \ + --external_nodes "/usr/sbin/aeolus-node $PUPPET_NODE" --node_terminus exec \ --logdest=/var/log/aeolus-configure/aeolus-configure.log \ --logdest=console \ $LOGLEVEL - diff --git a/conf/default_cleanup b/conf/default_cleanup index d43e906..5a5ce55 100644 --- a/conf/default_cleanup +++ b/conf/default_cleanup @@ -20,5 +20,3 @@ classes: - aeolus::conductor::remove_seed_data # Uncomment this to clean up RHEV details #- aeolus::rhevm::disabled -# Uncomment to remove vmware -#- aeolus::vmware::disabled diff --git a/conf/default_configure b/conf/default_configure index 33ac7e1..a047b5d 100644 --- a/conf/default_configure +++ b/conf/default_configure @@ -25,21 +25,10 @@ parameters: # rhevm_deltacloud_username: username@fqdn # rhevm_deltacloud_password: password # rhevm_deltacloud_powershell_url: https://rhevm.server.com:8543/rhevm-api-powershell -# -# Uncomment this section and provide appropriate values to configure vmware -# vmware_api_endpoint: vsphere.server.com -# vmware_username: username -# vmware_password: password -# vmware_datastore: datastore -# vmware_network_name: network_name -# vmware_deltacloud_port: 3006 classes: - aeolus::conductor - aeolus::image-factory - aeolus::iwhd -- aeolus::conductor::seed_data +- aeolus::profiles::default # Uncomment this section to include rhev setup #- aeolus::rhevm -# -# Uncomment this section to include vmware setup -#- aeolus::vmware diff --git a/conf/default_custom b/conf/default_custom new file mode 100644 index 0000000..1e7f7aa --- /dev/null +++ b/conf/default_custom @@ -0,0 +1,7 @@ +--- +parameters: + enable_https: true + enable_security: false +classes: +CUSTOM_CLASSES +- aeolus::profiles::custom diff --git a/recipes/aeolus/manifests/conductor.pp b/recipes/aeolus/manifests/conductor.pp index 985b883..5df47e9 100644 --- a/recipes/aeolus/manifests/conductor.pp +++ b/recipes/aeolus/manifests/conductor.pp @@ -112,41 +112,6 @@ class aeolus::conductor inherits aeolus { enable => 'true' } }
-class aeolus::conductor::seed_data { - aeolus::create_bucket{"aeolus":} - - aeolus::site_admin{"$admin_user": - email => 'dcuser@aeolusproject.org', - password => "$admin_password", - first_name => 'aeolus', - last_name => 'user'} - - aeolus::provider{"mock": - type => 'mock', - port => 3002, - require => Aeolus::Site_admin["admin"] } - - aeolus::provider{"ec2-us-east-1": - type => 'ec2', - endpoint => 'us-east-1', - port => 3003, - require => Aeolus::Site_admin["admin"] } - - aeolus::provider{"ec2-us-west-1": - type => 'ec2', - endpoint => 'us-west-1', - port => 3004, - require => Aeolus::Site_admin["admin"] } - - aeolus::conductor::hwp{"hwp1": - memory => "512", - cpu => "1", - storage => "", - architecture => "x86_64", - require => Aeolus::Site_admin["admin"] } - -} - class aeolus::conductor::remove_seed_data { aeolus::deltacloud::disabled{"mock": } aeolus::deltacloud::disabled{"ec2-us-east-1": } diff --git a/recipes/aeolus/manifests/init.pp b/recipes/aeolus/manifests/init.pp index 7768733..f7f7bca 100644 --- a/recipes/aeolus/manifests/init.pp +++ b/recipes/aeolus/manifests/init.pp @@ -13,6 +13,9 @@ import "image-factory" import "rhevm" import "vmware"
+import "defaults" +import "profiles/*" + if $aeolus_enable_https == "true" or $aeolus_enable_https == "1" { import "openssl" $enable_https = true diff --git a/recipes/aeolus/manifests/profiles/custom.pp b/recipes/aeolus/manifests/profiles/custom.pp new file mode 100644 index 0000000..2e1134e --- /dev/null +++ b/recipes/aeolus/manifests/profiles/custom.pp @@ -0,0 +1,25 @@ +class aeolus::profiles::custom { + +aeolus::create_bucket{"aeolus":} + +aeolus::conductor::site_admin{"admin": + email => 'dcuser@aeolusproject.org', + password => "password", + first_name => 'aeolus', + last_name => 'user'} + +aeolus::conductor::login{"admin": password => "password", + require => Aeolus::Conductor::Site_admin['admin']} + +AEOLUS_SEED_DATA + +aeolus::conductor::hwp{"hwp1": + memory => "1", + cpu => "1", + storage => "1", + architecture => "x86_64", + require => Aeolus::Conductor::Login["admin"] } + +aeolus::conductor::logout{"admin": + require => [AEOLUS_SEED_DATA_REQUIRESAeolus::Conductor::Hwp["hwp1"]] } +} diff --git a/recipes/aeolus/manifests/profiles/default.pp b/recipes/aeolus/manifests/profiles/default.pp new file mode 100644 index 0000000..c464f2b --- /dev/null +++ b/recipes/aeolus/manifests/profiles/default.pp @@ -0,0 +1,51 @@ +class aeolus::profiles::default { + + aeolus::create_bucket{"aeolus":} + + aeolus::conductor::site_admin{"admin": + email => 'dcuser@aeolusproject.org', + password => "password", + first_name => 'aeolus', + last_name => 'user'} + + aeolus::conductor::login{"admin": password => "password", + require => Aeolus::Conductor::Site_admin['admin']} + + aeolus::provider{"mock": + type => 'mock', + port => 3002, + require => Aeolus::Conductor::Login["admin"] } + aeolus::conductor::provider::account{"mockuser": + provider => 'mock', + type => 'mock', + username => 'mockuser', + password => 'mockpassword', + require => Aeolus::Provider["mock"] } + + aeolus::provider{"ec2-us-east-1": + type => 'ec2', + endpoint => 'us-east-1', + port => 3003, + require => Aeolus::Conductor::Login["admin"] } + + aeolus::provider{"ec2-us-west-1": + type => 'ec2', + endpoint => 'us-west-1', + port => 3004, + require => Aeolus::Conductor::Login["admin"] } + + aeolus::conductor::hwp{"hwp1": + memory => "1", + cpu => "1", + storage => "1", + architecture => "x86_64", + require => Aeolus::Conductor::Login["admin"] } + + aeolus::conductor::logout{"admin": + require => [Aeolus::Provider['mock'], + Aeolus::Conductor::Provider::Account['mockuser'], + Aeolus::Provider['ec2-us-east-1'], + Aeolus::Provider['ec2-us-west-1'], + Aeolus::Conductor::Hwp['hwp1']] } + +}
this patch adds an interactive installer to aeolus such that the user will be prompted as to which aeolus components to install and seed data to initialize
the goal is to provide a very simple interface to configure only the bits the user wants, the components that are to be setup locally, the providers to be created, and parameters create core aeolus data --- recipes/aeolus/cli.rb | 145 ++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 145 insertions(+), 0 deletions(-) create mode 100644 recipes/aeolus/aeolus.pp create mode 100644 recipes/aeolus/cli.rb
diff --git a/recipes/aeolus/aeolus.pp b/recipes/aeolus/aeolus.pp new file mode 100644 index 0000000..e69de29 diff --git a/recipes/aeolus/cli.rb b/recipes/aeolus/cli.rb new file mode 100644 index 0000000..2064d5e --- /dev/null +++ b/recipes/aeolus/cli.rb @@ -0,0 +1,145 @@ +# Interactive aeolus configure installation utility. +# Prompt the user for provider account and instance values and write them +# to a new puppet config files + +require 'rubygems' +require 'highline/import' + +puts "Press ^C at any time to terminate" +Signal.trap("INT") do + exit 1 +end + +NODE_YAML='/etc/aeolus-configure/nodes/default_custom' +PROFILE_RECIPE='/usr/share/aeolus-configure/modules/aeolus/manifests/profiles/custom.pp' + + +say "Select Aeolus Components to Install" +installed_component = nil +install_components = [] +while ![:None, :All].include?(installed_component) + installed_component = + choose do |menu| + menu.prompt = "Install Aeolus Component: " + menu.choice :All + menu.choice :None + menu.choice :"Image Factory" + menu.choice :"Image Warehouse" + menu.choice :"Conductor" + end + if installed_component == :"Image Factory" + install_components << "- aeolus::image-factory" + elsif installed_component == :"Image Warehouse" + install_components << "- aeolus::iwhd" + elsif installed_component == :"Conductor" + install_components << "- aeolus::conductor" + elsif installed_component == :All + install_components << "- aeolus::conductor" << + "- aeolus::image-factory" << + "- aeolus::iwhd" + end +end + +providers = [] +if install_components.include? "- aeolus::conductor" + provider_port = 3001 + profile='' + profile_requires = [] + while agree("Add provider (y/n)? ") + name = ask("Cloud provider label: ") + type = choose do |menu| + menu.prompt = "Cloud provider type: " + menu.choice :mock + menu.choice :ec2 + menu.choice :rackspace + menu.choice :rhevm + menu.choice :vsphere + end + providers << [name,type] + + if type == :mock + profile += "aeolus::provider{#{name}:\n" + + " type => 'mock',\n" + + " port => '#{provider_port += 1}',\n" + + " require => Aeolus::Conductor::Login['admin'] }\n\n" + + "aeolus::conductor::provider::account{#{name}:\n" + + " provider => 'mock',\n" + + " type => 'mock',\n" + + " username => 'mockuser',\n" + + " password => 'mockpassword',\n" + + " require => Aeolus::Provider['#{name}'] }\n\n" + profile_requires << "Aeolus::Provider['#{name}']" << + "Aeolus::Conductor::Provider::Account['#{name}']" + + elsif type == :ec2 + endpoint = ask("EC2 Endpoint: ") + access_key = ask("EC2 Access Key: ") + secret_access_key = ask("EC2 Secret Access Key: "){ |q| q.echo = false } + account_id = ask("EC2 Account ID: ") + public_cert = ask("EC2 Public Cert: ") + private_key = ask("EC2 Private Key: ") + profile += "aeolus::provider{#{name}:\n" + + " type => 'ec2',\n" + + " endpoint => '#{endpoint}',\n" + + " port => '#{provider_port += 1}',\n" + + " require => Aeolus::Conductor::Login['admin'] }\n\n" + + "aeolus::conductor::provider::account{#{name}:\n" + + " provider => '#{name}',\n" + + " type => 'ec2',\n" + + " username => '#{access_key}',\n" + + " password => '#{secret_access_key}',\n" + + " account_id => '#{account_id}',\n" + + " x509private => '#{private_key}',\n" + + " x509public => '#{public_cert}',\n" + + " require => Aeolus::Provider['#{name}'] }\n\n" + profile_requires << "Aeolus::Provider['#{name}']" << + "Aeolus::Conductor::Provider::Account['#{name}']" + + # else if type == :rackspace ... + end + end + + # TODO change to create image / deploy to providers (which to select) + while agree("Deploy instance to providers (y/n)? ") + name = ask("Instance name: ") + providers.each { |provider| + pname,ptype = *provider + profile += "aeolus::image{#{pname}-#{name}:\n" + + " target => '#{ptype}',\n" + + " template => 'examples/custom_repo.tdl',\n" + + " provider => '#{pname}',\n" + + " require => [Aeolus::Conductor::Provider::Account['#{pname}'], Aeolus::Conductor::Hwp['hwp1']] }\n\n" + profile_requires << "Aeolus::Image['#{pname}-#{name}']" + } + + # while agree("Add yum repo? ") + # yum_repo = ask("URI ") + # end + # + # while agree("Add package? ") do + # package_name = ask("Package Name ") + # end + # + # while agree("Add file? ") do + # src_location = ask("File Source ") + # dst_location = ask("File Destination ") + # end + # + end +end + + +# create the profile +text = File.read PROFILE_RECIPE +File.open(PROFILE_RECIPE, 'w+'){|f| + requires = profile_requires.join(',') + requires += ", " unless requires == "" + f << text.gsub(/AEOLUS_SEED_DATA_REQUIRES/, requires). + gsub(/AEOLUS_SEED_DATA/, profile) +} + +# create the node yaml +text = File.read NODE_YAML +File.open(NODE_YAML, 'w+'){|f| + f << text.gsub(/CUSTOM_CLASSES/, install_components.join("\n")) +}
--- .../controllers/provider_accounts_controller.rb | 4 ++++ 1 files changed, 4 insertions(+), 0 deletions(-)
diff --git a/src/app/controllers/provider_accounts_controller.rb b/src/app/controllers/provider_accounts_controller.rb index 59616a0..bf5716c 100644 --- a/src/app/controllers/provider_accounts_controller.rb +++ b/src/app/controllers/provider_accounts_controller.rb @@ -55,6 +55,10 @@ class ProviderAccountsController < ApplicationController end
def create + unless params[:provider_account][:provider].nil? + provider = params[:provider_account].delete(:provider) + params[:provider_account][:provider_id] = Provider.find_by_name(provider).id + end @selected_provider = @provider = Provider.find(params[:provider_account][:provider_id]) require_privilege(Privilege::CREATE, ProviderAccount, @provider)
aeolus-devel@lists.fedorahosted.org