From e1f1d7f516fb1b2f4d7758b6e1ae5bfc263e58b7 Mon Sep 17 00:00:00 2001 From: Gael Chamoulaud Date: Wed, 10 Dec 2014 12:21:04 +0100 Subject: [PATCH] Aviator Automatic update (Master) This module update commit was generated by Bade. For more info please check https://github.com/paramite/bade This commit is setting modules to following state: aviator - old commit: 5f1429fecf755692d6056fce3f3af08d1d97e763 - new commit: d308a263318399f0de7fd760826f8b98b2b59aba Fixes: rhbz#1171352 Signed-off-by: Gael Chamoulaud --- Puppetfile | 2 +- aviator/Modulefile | 2 +- .../puppet/feature/aviator/core/session.rb | 264 ++++++++++++++++-- .../feature/aviator/openstack/provider.rb | 63 +++++ aviator/lib/puppet/feature/aviator/version.rb | 2 +- aviator/lib/puppet/feature/composite_io.rb | 108 +++++++ 6 files changed, 408 insertions(+), 33 deletions(-) create mode 100644 aviator/lib/puppet/feature/composite_io.rb diff --git a/Puppetfile b/Puppetfile index fdc6b4872..edb7a7d5e 100644 --- a/Puppetfile +++ b/Puppetfile @@ -3,7 +3,7 @@ mod 'apache', :git => 'https://github.com/puppetlabs/puppetlabs-apache.git' mod 'aviator', - :commit => '5f1429fecf755692d6056fce3f3af08d1d97e763', + :commit => 'd308a263318399f0de7fd760826f8b98b2b59aba', :git => 'https://github.com/aimonb/puppet_aviator.git' mod 'ceilometer', diff --git a/aviator/Modulefile b/aviator/Modulefile index 98b4bfb9e..cd152bd8a 100644 --- a/aviator/Modulefile +++ b/aviator/Modulefile @@ -1,5 +1,5 @@ name 'aimonb-aviator' -version '0.4.4' +version '0.5.0' source 'https://github.com/aimonb/puppet_aviator' author 'aimonb' license 'MIT License' diff --git a/aviator/lib/puppet/feature/aviator/core/session.rb b/aviator/lib/puppet/feature/aviator/core/session.rb index 43a8690ed..f7ec05fab 100644 --- a/aviator/lib/puppet/feature/aviator/core/session.rb +++ b/aviator/lib/puppet/feature/aviator/core/session.rb @@ -1,5 +1,13 @@ module Aviator + # + # Manages a provider (e.g. OpenStack) session. + # + # Author:: Mark Maglana (mmaglana@gmail.com) + # Copyright:: Copyright (c) 2014 Mark Maglana + # License:: Distributed under the MIT license + # Homepage:: http://aviator.github.io/www/ + # class Session class AuthenticationError < StandardError @@ -42,7 +50,91 @@ def initialize end end - + # + # Create a new Session instance with options provided in opts which can + # have many forms discussed below. + # + # Initialize with a config file + # + # Aviator::Session.new(:config_file => 'path/to/aviator.yml', :environment => :production) + # + # In the above example, the config file must have the following form: + # + # production: + # provider: openstack + # auth_service: + # name: identity + # host_uri: 'http://my.openstackenv.org:5000' + # request: create_token + # validator: list_tenants + # api_version: v2 + # auth_credentials: + # username: myusername + # password: mypassword + # tenant_name: myproject + # + # Once the session has been instantiated, you may authenticate against the + # provider as follows: + # + # session.authenticate + # + # Note that the required items under auth_credentials in the config + # file depends on the required parameters of the request class declared under + # auth_service. If writing the auth_credentials in the config + # file is not acceptable, you may omit it and just supply the credentials at + # runtime. For instance, assume that the auth_credentials section in the + # config file above is missing. You would then authenticate as follows: + # + # session.authenticate do |params| + # params.username = ARGV[0] + # params.password = ARGV[1] + # params.tenant_name = ARGV[2] + # end + # + # Please see Session#authenticate for more info. + # + # Note that while the example config file above only has one environment (production), + # you can declare an arbitrary number of environments in your config file. Shifting + # between environments is as simple as changing the :environment to refer to that. + # + # + # Initialize with an in-memory hash + # + # You can create an in-memory hash which is similar in structure to the config file except + # that you don't need to specify an environment name. For example: + # + # configuration = { + # :provider => 'openstack', + # :auth_service => { + # :name => 'identity', + # :host_uri => 'http://devstack:5000/v2.0', + # :request => 'create_token', + # :validator => 'list_tenants' + # } + # } + # + # Supply this to the initializer using the :config option. For example: + # + # Aviator::Session.new(:config => configuration) + # + # + # Initialize with a session dump + # + # You can create a new Session instance using a dump from another instance. For example: + # + # session_dump = session1.dump + # session2 = Aviator::Session.new(:session_dump => session_dump) + # + # However, Session.load is cleaner and recommended over this method. + # + # + # Optionally supply a log file + # + # In all forms above, you may optionally add a :log_file option to make + # Aviator write all HTTP calls to the given path. For example: + # + # Aviator::Session.new(:config_file => 'path/to/aviator.yml', :environment => :production, :log_file => 'path/to/log') + # def initialize(opts={}) if opts.has_key? :session_dump initialize_with_dump(opts[:session_dump]) @@ -57,15 +149,47 @@ def initialize(opts={}) @log_file = opts[:log_file] end - + # + # Authenticates against the auth_service request class declared in the session's + # configuration during initialization. Please see Session.new for more information + # on declaring the request class to use for authentication. + # + # If the auth_service request class accepts a parameter block, you may also supply that + # when calling this method and it will be directly passed to the request. For example: + # + # session = Aviator::Session.new(:config => config) + # session.authenticate do |params| + # params.username = username + # params.password = password + # params.tenant_name = project + # end + # + # Expects an HTTP status 200 or 201. Any other status is treated as a failure. + # + # Note that you can also treat the block's argument like a hash with the attribute + # names as the keys. For example, we can rewrite the above as: + # + # session = Aviator::Session.new(:config => config) + # session.authenticate do |params| + # params[:username] = username + # params[:password] = password + # params[:tenant_name] = project + # end + # + # Keys can be symbols or strings. + # def authenticate(&block) block ||= lambda do |params| - environment[:auth_credentials].each do |key, value| - params[key] = value + config[:auth_credentials].each do |key, value| + begin + params[key] = value + rescue NameError => e + raise NameError.new("Unknown param name '#{key}'") + end end end - response = auth_service.request environment[:auth_service][:request].to_sym, &block + response = auth_service.request config[:auth_service][:request].to_sym, &block if [200, 201].include? response.status @auth_response = Hashish.new({ @@ -79,20 +203,43 @@ def authenticate(&block) self end - + # + # Returns true if the session has been authenticated. + # def authenticated? !auth_response.nil? end + # + # Returns its configuration. + # + def config + @config + end + # + # Returns a JSON string of its configuration and auth_data. This string can be streamed + # or stored and later re-loaded in another Session instance. For example: + # + # session = Aviator::Session.new(:config => configuration) + # str = session.dump + # + # # time passes... + # + # session = Aviator::Session.load(str) + # def dump JSON.generate({ - :environment => environment, + :config => config, :auth_response => auth_response }) end + # + # Same as Session::load but re-uses the Session instance this method is + # called on instead of creating a new one. + # def load(session_dump) initialize_with_dump(session_dump) update_services_session_data @@ -111,6 +258,14 @@ def method_missing(name, *args, &block) end + # + # Creates a new Session object from a previous session's dump. See Session#dump for + # more information. + # + # If you want the newly deserialized session to log its output, make sure to indicate it on load + # + # Aviator::Session.load(session_dump_str, :log_file => 'path/to/aviator.log') + # def self.load(session_dump, opts={}) opts[:session_dump] = session_dump @@ -118,13 +273,72 @@ def self.load(session_dump, opts={}) end + # + # Returns the log file path. May be nil if none was provided during initialization. + # + def log_file + @log_file + end + + + # + # Calls the given request of the given service. An example call might look like: + # + # session.request :compute_service, :create_server do |p| + # p.name = "My Server" + # p.image_ref = "7cae8c8e-fb01-4a88-bba3-ae0fcb1dbe29" + # p.flavor_ref = "fa283da1-59a5-4245-8569-b6eadf69f10b" + # end + # + # Note that you can also treat the block's argument like a hash with the attribute + # names as the keys. For example, we can rewrite the above as: + # + # session.request :compute_service, :create_server do |p| + # p[:name] = "My Server" + # p[:image_ref] = "7cae8c8e-fb01-4a88-bba3-ae0fcb1dbe29" + # p[:flavor_ref] = "fa283da1-59a5-4245-8569-b6eadf69f10b" + # end + # + # Keys can be symbols or strings. + # + # --- + # + # Request Options + # + # You can further customize how the request is fulfilled by providing one or more + # options to the method call. For example, the following ensures that the request + # will call the :create_server request for the v1 API. + # + # session.request :compute_service, :create_server, :api_version => v1 + # + # The available options vary depending on the provider. See the documentation + # on the provider's Provider class for more information (e.g. Aviator::OpenStack::Provider) + # + def request(service_name, request_name, opts={}, ¶ms) + service = send("#{service_name.to_s}_service") + service.request(request_name, opts, ¶ms) + end + + + # + # Returns true if the session is still valid in the underlying provider. This method does this + # by calling the validator request class declared declared under auth_service in the + # configuration. The validator can be any request class as long as: + # + # * The request class exists! + # * Does not require any parameters + # * It returns an HTTP status 200 or 203 to indicate auth info validity. + # * It returns any other HTTP status to indicate that the auth info is invalid. + # + # See Session::new for an example on how to specify the request class to use for session validation. + # def validate raise NotAuthenticatedError.new unless authenticated? - raise ValidatorNotDefinedError.new unless environment[:auth_service][:validator] + raise ValidatorNotDefinedError.new unless config[:auth_service][:validator] - auth_with_bootstrap = auth_response.merge({ :auth_service => environment[:auth_service] }) + auth_with_bootstrap = auth_response.merge({ :auth_service => config[:auth_service] }) - response = auth_service.request environment[:auth_service][:validator].to_sym, :session_data => auth_with_bootstrap + response = auth_service.request config[:auth_service][:validator].to_sym, :session_data => auth_with_bootstrap response.status == 200 || response.status == 203 end @@ -139,27 +353,22 @@ def auth_response def auth_service @auth_service ||= Service.new( - :provider => environment[:provider], - :service => environment[:auth_service][:name], - :default_session_data => { :auth_service => environment[:auth_service] }, + :provider => config[:provider], + :service => config[:auth_service][:name], + :default_session_data => { :auth_service => config[:auth_service] }, :log_file => log_file ) end - def environment - @environment - end - - def get_service_obj(service_name) @services ||= {} if @services[service_name].nil? - default_options = environment["#{ service_name }_service"] + default_options = config["#{ service_name }_service"] @services[service_name] = Service.new( - :provider => environment[:provider], + :provider => config[:provider], :service => service_name, :default_session_data => auth_response, :default_options => default_options, @@ -174,28 +383,23 @@ def get_service_obj(service_name) def initialize_with_config(config_path, environment) raise InvalidConfigFilePathError.new(config_path) unless Pathname.new(config_path).file? - config = Hashish.new(YAML.load_file(config_path)) + all_config = Hashish.new(YAML.load_file(config_path)) - raise EnvironmentNotDefinedError.new(config_path, environment) unless config[environment] + raise EnvironmentNotDefinedError.new(config_path, environment) unless all_config[environment] - @environment = config[environment] + @config = all_config[environment] end def initialize_with_dump(session_dump) session_info = Hashish.new(JSON.parse(session_dump)) - @environment = session_info[:environment] + @config = session_info[:config] @auth_response = session_info[:auth_response] end def initialize_with_hash(hash_obj) - @environment = Hashish.new(hash_obj) - end - - - def log_file - @log_file + @config = Hashish.new(hash_obj) end diff --git a/aviator/lib/puppet/feature/aviator/openstack/provider.rb b/aviator/lib/puppet/feature/aviator/openstack/provider.rb index 5f1b6c701..17fd0cbf7 100644 --- a/aviator/lib/puppet/feature/aviator/openstack/provider.rb +++ b/aviator/lib/puppet/feature/aviator/openstack/provider.rb @@ -1,6 +1,68 @@ module Aviator module Openstack + # + # Manages a provider (e.g. OpenStack) session. + # + # Author:: Mark Maglana (mmaglana@gmail.com) + # Copyright:: Copyright (c) 2014 Mark Maglana + # License:: Distributed under the MIT license + # Homepage:: http://aviator.github.io/www/ + # + # Request Options + # + # The following options may be used in combination with each other when calling + # an OpenStack request class + # + # :api_version => :v2:: + # Forces Aviator to use the request class for the v2 API. For any other + # version, replace Note that this may throw an error if no such request + # class exists. If you want to globally specify the API version to use for + # a specific service, declare it in your config file under the correct + # environment. For example: + # + # production: + # provider: openstack + # ... + # compute_service: + # api_version: v2 + # + # Note that the :api_version option overrides whatever is declared in the + # configuration. + # + # :endpoint_type => (:public|:admin):: + # This allows you to be specific about the endpoint type in cases where two + # request classes under admin and public endpoints of the same service share + # the same name. This is true, for example, for the :list_tenants request of + # the identity service's v2 API. Its public endpoint will return only the tenants + # the user is a member of whereas the admin endpoint will return all tenants + # in the system. + # + # :session_data => Hash:: + # Under normal situations, you wouldn't need to use this as it is automatically populated + # by the Session object provided it is authenticated. The specific use case when you'd + # need to set thsi optin is when you want to use Aviator to seed your OpenStack installation. + # In such a scenario, you would need to use a service token since no usernames and tenants + # would exist yet in the environment. To use a service token with Aviator, you will need to + # write something similar to the following example: + # + # openstack = Aviator::Session.new(:config => { :provider => 'openstack'}) + # + # session_data = {:base_url => 'http://example.com', + # :service_token => 'service-token-created-at-openstack-install-time'} + # + # openstack.request :identity, :create_tenant, :api_version => :v2, :session_data => session_data) do |params| + # params.name = 'Tenant A' + # params.description = 'First Tenant!' + # params.enabled = true + # end + # + # Notice how the above code skips authentication. This is because the service token is + # pre-validated and ready for use with any request. Also note how we're providing a :base_url + # member in our session data. This is necessary since we normally get the service endpoints from + # Keystone when we authenticate. Now since we are not authenticating against Keystone, we don't have + # that catalogue to begin with. Thus the need to hardcode it in the request. + # module Provider class MultipleServiceApisError < StandardError @@ -35,6 +97,7 @@ def initialize(service, entries, request_name) class << self + #:nodoc: def find_request(service, name, session_data, options) service = service.to_s endpoint_type = options[:endpoint_type] diff --git a/aviator/lib/puppet/feature/aviator/version.rb b/aviator/lib/puppet/feature/aviator/version.rb index 89b5225a3..231ac0afa 100644 --- a/aviator/lib/puppet/feature/aviator/version.rb +++ b/aviator/lib/puppet/feature/aviator/version.rb @@ -1,3 +1,3 @@ module Aviator - VERSION = "0.0.9" + VERSION = "0.1.0" end diff --git a/aviator/lib/puppet/feature/composite_io.rb b/aviator/lib/puppet/feature/composite_io.rb new file mode 100644 index 000000000..4ba7cf5ff --- /dev/null +++ b/aviator/lib/puppet/feature/composite_io.rb @@ -0,0 +1,108 @@ +#-- +# Copyright (c) 2007-2012 Nick Sieger. +# See the file README.txt included with the distribution for +# software license details. +#++ + +# Concatenate together multiple IO objects into a single, composite IO object +# for purposes of reading as a single stream. +# +# Usage: +# +# crio = CompositeReadIO.new(StringIO.new('one'), StringIO.new('two'), StringIO.new('three')) +# puts crio.read # => "onetwothree" +# +class CompositeReadIO + # Create a new composite-read IO from the arguments, all of which should + # respond to #read in a manner consistent with IO. + def initialize(*ios) + @ios = ios.flatten + @index = 0 + end + + # Read from IOs in order until `length` bytes have been received. + def read(length = nil, outbuf = nil) + got_result = false + outbuf = outbuf ? outbuf.replace("") : "" + + while io = current_io + if result = io.read(length) + got_result ||= !result.nil? + result.force_encoding("BINARY") if result.respond_to?(:force_encoding) + outbuf << result + length -= result.length if length + break if length == 0 + end + advance_io + end + (!got_result && length) ? nil : outbuf + end + + def rewind + @ios.each { |io| io.rewind } + @index = 0 + end + + private + + def current_io + @ios[@index] + end + + def advance_io + @index += 1 + end +end + +# Convenience methods for dealing with files and IO that are to be uploaded. +class UploadIO + # Create an upload IO suitable for including in the params hash of a + # Net::HTTP::Post::Multipart. + # + # Can take two forms. The first accepts a filename and content type, and + # opens the file for reading (to be closed by finalizer). + # + # The second accepts an already-open IO, but also requires a third argument, + # the filename from which it was opened (particularly useful/recommended if + # uploading directly from a form in a framework, which often save the file to + # an arbitrarily named RackMultipart file in /tmp). + # + # Usage: + # + # UploadIO.new("file.txt", "text/plain") + # UploadIO.new(file_io, "text/plain", "file.txt") + # + attr_reader :content_type, :original_filename, :local_path, :io, :opts + + def initialize(filename_or_io, content_type, filename = nil, opts = {}) + io = filename_or_io + local_path = "" + if io.respond_to? :read + # in Ruby 1.9.2, StringIOs no longer respond to path + # (since they respond to :length, so we don't need their local path, see parts.rb:41) + local_path = filename_or_io.respond_to?(:path) ? filename_or_io.path : "local.path" + else + io = File.open(filename_or_io) + local_path = filename_or_io + end + filename ||= local_path + + @content_type = content_type + @original_filename = File.basename(filename) + @local_path = local_path + @io = io + @opts = opts + end + + def self.convert!(io, content_type, original_filename, local_path) + raise ArgumentError, "convert! has been removed. You must now wrap IOs using:\nUploadIO.new(filename_or_io, content_type, filename=nil)\nPlease update your code." + end + + def method_missing(*args) + @io.send(*args) + end + + def respond_to?(meth, include_all = false) + @io.respond_to?(meth, include_all) || super(meth, include_all) + end +end