[PATCH conductor] bug 786220 - Scope images to Pool Family

Imre Farkas ifarkas at redhat.com
Thu Feb 16 10:37:27 UTC 2012


On 02/15/2012 07:51 PM, Scott Seago wrote:
> https://bugzilla.redhat.com/show_bug.cgi?id=786220
> ---
>   src/app/controllers/api/environments_controller.rb |   39 ++++++++
>   src/app/controllers/api/images_controller.rb       |   58 ++++++++++--
>   .../controllers/api/provider_images_controller.rb  |    4 +
>   src/app/controllers/deployables_controller.rb      |   30 ++----
>   src/app/controllers/deployments_controller.rb      |   86 ++++++++++--------
>   src/app/controllers/images_controller.rb           |   35 ++++---
>   src/app/controllers/pool_families_controller.rb    |    2 +-
>   src/app/models/deployable.rb                       |   61 +++++++------
>   src/app/models/pool_family.rb                      |    7 ++
>   src/app/models/provider_account.rb                 |   10 ++-
>   .../views/api/environments/_environment.xml.haml   |    9 ++
>   src/app/views/api/environments/index.xml.haml      |    4 +
>   src/app/views/api/environments/show.xml.haml       |    2 +
>   src/app/views/api/images/_image.xml.haml           |    1 +
>   src/app/views/deployables/_image_uuids.html.haml   |    8 +-
>   src/app/views/deployables/show.html.haml           |    8 +-
>   .../deployments/launch_from_catalog.html.haml      |    2 +-
>   src/app/views/images/_list.html.haml               |   47 ++++++----
>   src/app/views/images/import.html.haml              |    3 +
>   src/app/views/images/show.html.haml                |   23 +++--
>   src/config/locales/en.yml                          |    8 ++
>   src/config/routes.rb                               |    4 +
>   src/features/deployables.feature                   |    1 +
>   src/features/deployment.feature                    |    3 +-
>   src/lib/image.rb                                   |    6 +-
>   src/spec/controllers/api/images_controller_spec.rb |   26 +++++-
>   .../api/provider_images_controller_spec.rb         |    5 +-
>   .../controllers/deployables_controller_spec.rb     |   12 ++--
>   .../controllers/deployments_controller_spec.rb     |    5 +-
>   src/spec/vcr/cassettes/iwhd_connection.yml         |   96 ++++++++++++++++++++
>   30 files changed, 439 insertions(+), 166 deletions(-)
>   create mode 100644 src/app/controllers/api/environments_controller.rb
>   create mode 100644 src/app/views/api/environments/_environment.xml.haml
>   create mode 100644 src/app/views/api/environments/index.xml.haml
>   create mode 100644 src/app/views/api/environments/show.xml.haml
>
> diff --git a/src/app/controllers/api/environments_controller.rb b/src/app/controllers/api/environments_controller.rb
> new file mode 100644
> index 0000000..9260caa
> --- /dev/null
> +++ b/src/app/controllers/api/environments_controller.rb
> @@ -0,0 +1,39 @@
> +#
> +#   Copyright 2012 Red Hat, Inc.
> +#
> +#   Licensed under the Apache License, Version 2.0 (the "License");
> +#   you may not use this file except in compliance with the License.
> +#   You may obtain a copy of the License at
> +#
> +#       http://www.apache.org/licenses/LICENSE-2.0
> +#
> +#   Unless required by applicable law or agreed to in writing, software
> +#   distributed under the License is distributed on an "AS IS" BASIS,
> +#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> +#   See the License for the specific language governing permissions and
> +#   limitations under the License.
> +#
> +
> +# Filters added to this controller apply to all controllers in the application.
> +# Likewise, all the methods added will be available for all controllers.
> +
> +module Api
> +  class EnvironmentsController<  ApplicationController
> +    before_filter :require_user
> +
> +    respond_to :xml
> +    layout :false
> +
> +    def index
> +      @environments = PoolFamily.all
> +    end
> +
> +    def show
> +      begin
> +        @environment = PoolFamily.find(params[:id])
> +      rescue ActiveRecord::RecordNotFound
> +        @environment = PoolFamily.find_by_name(params[:id])
> +      end
> +    end
> +  end
> +end
> diff --git a/src/app/controllers/api/images_controller.rb b/src/app/controllers/api/images_controller.rb
> index 18870f2..465a20b 100644
> --- a/src/app/controllers/api/images_controller.rb
> +++ b/src/app/controllers/api/images_controller.rb
> @@ -20,13 +20,16 @@
>   module Api
>     class ImagesController<  ApplicationController
>       before_filter :require_user_api
> -    before_filter :check_permissions, :only =>  [:create, :destroy]
>
>       respond_to :xml
>       layout :false
>
>       def index
> -      @images = Aeolus::Image::Warehouse::Image.all
> +      if (@environment = params[:environment_id])
> +        @images = Aeolus::Image::Warehouse::Image.by_environment(@environment)
> +      else
> +        @images = Aeolus::Image::Warehouse::Image.all
> +      end
>         respond_with(@images)
>       end
>
> @@ -47,9 +50,16 @@ module Api
>         begin
>           if req[:type] == :failed
>             raise(Aeolus::Conductor::API::InsufficientParametersSupplied.new(400, t("api.error_messages.specify_a_type_build_or_import")))
> +        elsif req[:type] == :failed_env
> +          raise(Aeolus::Conductor::API::InsufficientParametersSupplied.new(400, t("api.error_messages.specify_environment")))
>           elsif req[:type] == :build
>             errors = TemplateXML.validate(req[:params][:template])
>             raise Aeolus::Conductor::API::ParameterDataIncorrect.new(400, t("api.error_messages.invalid_template", :errors =>  errors.join(", "))) if errors.any?
> +          raise Aeolus::Conductor::API::InsufficientParametersSupplied.new(400, t("api.error_messages.environment_required")) if req[:params][:environment].nil?
> +          @pool_family = PoolFamily.find_by_name(req[:params][:environment])
> +          raise Aeolus::Conductor::API::ParameterDataIncorrect.new(400, t("api.error_messages.environment_not_found", :environment =>  req[:params][:environment])) if @pool_family.nil?
> +          check_permissions
> +          environment_targets = @pool_family.build_targets
>
>             @targetnotfound=false
>             @badtarget=""
> @@ -58,20 +68,46 @@ module Api
>               if !target
>                 @targetnotfound=true
>                 @badtarget=t
> +            elsif !environment_targets.include?(t)
> +              raise(Aeolus::Conductor::API::ParameterDataIncorrect.new(400, t("api.error_messages.target_not_found_in_environment", :target =>  t, :targets =>  environment_targets.join(", "))))
>               end
>             end
>             if @targetnotfound
>               raise(Aeolus::Conductor::API::TargetNotFound.new(404, t("api.error_messages.target_not_found", :target =>  @badtarget)))
>             end
> -          @image = Aeolus::Image::Factory::Image.new(req[:params])
> +
> +          uuid = UUIDTools::UUID.timestamp_create.to_s
> +          @tpl = Aeolus::Image::Warehouse::Template.create!(uuid, req[:params][:template], {
> +            :object_type =>  'template',
> +            :uuid =>  uuid
> +          })
> +          uuid = UUIDTools::UUID.timestamp_create.to_s
> +          body = "<image><name>#{@tpl.name}</name></image>"
> +          iwhd_image = Aeolus::Image::Warehouse::Image.create!(uuid, body, {
> +            :uuid =>  uuid,
> +            :object_type =>  'image',
> +            :template =>  @tpl.uuid,
> +            :environment =>  @pool_family.name
> +          })
> +          @image = Aeolus::Image::Factory::Image.new(:id =>  iwhd_image.id)
> +          @image.targets = req[:params][:targets]
> +          @image.template = req[:params][:template]
>             @image.save!
>             respond_with(@image)
>           elsif req[:type] == :import
>             account = ProviderAccount.find_by_label(req[:params][:provider_account_name])
>             raise(Aeolus::Conductor::API::ProviderAccountNotFound.new(404, t("api.error_messages.provider_account_not_found",
>               :name =>  req[:params][:provider_account_name]))) unless account.present?
> +          raise Aeolus::Conductor::API::InsufficientParametersSupplied.new(400, t("api.error_messages.environment_required")) if req[:params][:environment].nil?
> +          @pool_family = PoolFamily.find_by_name(req[:params][:environment])
> +          raise Aeolus::Conductor::API::ParameterDataIncorrect.new(400, t("api.error_messages.environment_not_found", :environment =>  req[:params][:environment])) if @pool_family.nil?
> +          check_permissions
> +          environment_accounts = @pool_family.provider_accounts
> +          if !environment_accounts.include?(account)
> +            raise(Aeolus::Conductor::API::ParameterDataIncorrect.new(400, t("api.error_messages.account_not_found_in_environment", :account =>  account.label, :accounts =>  environment_accounts.collect{|a|a.label}.join(", "))))
> +          end
>             begin
> -            @image = Image.import(account, req[:params][:target_identifier], req[:params][:image_descriptor])
> +            @image = Image.import(account, req[:params][:target_identifier], @pool_family, req[:params][:image_descriptor])
>             rescue Aeolus::Conductor::Base::ImageNotFound
>               raise(Aeolus::Conductor::API::ImageNotFound.new(404, t("api.error_messages.image_not_found_on_provider",
>                 :image =>  req[:params][:target_identifier])))
> @@ -86,6 +122,8 @@ module Api
>       def destroy
>         begin
>           if @image = Aeolus::Image::Warehouse::Image.find(params[:id])
> +          @pool_family = PoolFamily.find_by_name(@image.environment)
> +          check_permissions
>             @provider_images = @image.provider_images
>             if @image.delete!
>               respond_with(@image, @provider_images)
> @@ -103,17 +141,21 @@ module Api
>       private
>       def process_post(body)
>         doc = Nokogiri::XML CGI.unescapeHTML(body)
> -      if !doc.xpath("/image/targets").empty?&&  !doc.xpath("/image/tdl/template").empty?
> +      if !doc.xpath("/image/targets").empty?&&  !doc.xpath("/image/tdl/template").empty?&&  !doc.xpath("/image/environment").empty?
>           { :type =>  :build, :params =>  { :template =>  doc.xpath("/image/tdl/template").to_s,
> -                                        :targets =>  doc.xpath("/image/targets").text }
> +                                        :targets =>  doc.xpath("/image/targets").text,
> +                                        :environment =>  doc.xpath("/image/environment").text}
>           }
>         elsif !doc.xpath("/image/provider_account_name").empty?&&  !doc.xpath("/image/target_identifier").empty?&&
> -                 !doc.xpath("/image/image_descriptor").empty?
> +                 !doc.xpath("/image/image_descriptor").empty?&&  !doc.xpath("/image/environment").empty?
>
>           { :type =>  :import, :params =>  { :target_identifier =>  doc.xpath("/image/target_identifier").text,
>                                            :image_descriptor =>  doc.xpath("/image/image_descriptor").children.first.to_s,
> -                                         :provider_account_name =>  doc.xpath("/image/provider_account_name").text }
> +                                         :provider_account_name =>  doc.xpath("/image/provider_account_name").text,
> +                                        :environment =>  doc.xpath("/image/environment").text }
>           }
> +      elsif !doc.xpath("/image").empty?&&  doc.xpath("/image/environment").empty?
> +        { :type =>  :failed_env }
>         else
>           { :type =>  :failed }
>         end
> diff --git a/src/app/controllers/api/provider_images_controller.rb b/src/app/controllers/api/provider_images_controller.rb
> index 7055cb4..ec98813 100644
> --- a/src/app/controllers/api/provider_images_controller.rb
> +++ b/src/app/controllers/api/provider_images_controller.rb
> @@ -64,12 +64,16 @@ module Api
>           return
>         end
>
> +      @pool_family = PoolFamily.find_by_name(target_images.first.build.image.environment)
> +      environment_accounts = @pool_family.provider_accounts
>         @provider_images = []
>         @errors = []
>         doc.xpath("/provider_image/provider_account").text.split(",").each do |account_name|
>           account = ProviderAccount.find_by_label(account_name)
>           if !account
>             raise(Aeolus::Conductor::API::ProviderAccountNotFound.new(404, t("api.error_messages.provider_account_not_found", :name =>  account_name)))
> +        elsif !environment_accounts.include?(account)
> +          raise(Aeolus::Conductor::API::ParameterDataIncorrect.new(400, t("api.error_messages.account_not_found_in_environment", :account =>  account.label, :accounts =>  environment_accounts.collect{|a|a.label}.join(", "))))
>           end
>
>           target_image = find_target_image_for_account(target_images, account)
> diff --git a/src/app/controllers/deployables_controller.rb b/src/app/controllers/deployables_controller.rb
> index 4e4404f..e37bc4a 100644
> --- a/src/app/controllers/deployables_controller.rb
> +++ b/src/app/controllers/deployables_controller.rb
> @@ -63,7 +63,10 @@ class DeployablesController<  ApplicationController
>       require_privilege(Privilege::VIEW, @deployable)
>       save_breadcrumb(polymorphic_path([@catalog, @deployable]), @deployable.name)
>       @providers = Provider.all
> -    @catalogs_options = Catalog.list_for_user(current_user, Privilege::VIEW).select {|c| !@deployable.catalogs.include?(c)}
> +    @catalogs_options = Catalog.list_for_user(current_user, Privilege::VIEW).select do |c|
> +      !@deployable.catalogs.include?(c) and
> +        @deployable.catalogs.first.pool.pool_family == c.pool.pool_family
> +    end
>
>       if @catalog.present?
>         add_permissions_inline(@deployable, '', {:catalog_id =>  @catalog.id})
> @@ -71,28 +74,14 @@ class DeployablesController<  ApplicationController
>         add_permissions_inline(@deployable)
>       end
>
> -    @images_details = @deployable.get_image_details
> -    images = @deployable.fetch_images
> -    uuids = @deployable.fetch_image_uuids
> -    @missing_images = images.zip(uuids).select{|p| p.first.nil?}.map{|p| p.second}
> -    @images_hash_details = images != [nil] ? @deployable.get_uuids_hash(images) : nil
> -
> -    @images_details.each do |assembly|
> -      assembly.keys.each do |key|
> -        @deployable_errors ||= []
> -        @deployable_errors<<  "#{assembly[:name]}: #{assembly[key]}" if key.to_s =~ /^error\w+/
> -      end
> -      if @missing_images.include?(assembly[:image_uuid])
> -        @deployable_errors<<  "#{assembly[:name]}: Image (UUID: #{assembly[:image_uuid]}) doesn't exist."
> -      end
> -      flash.now[:error] = @deployable_errors unless @deployable_errors.empty?
> -    end
> +    @images_details, images, @missing_images, @deployable_errors = @deployable.get_image_details
> +    flash.now[:error] = @deployable_errors unless @deployable_errors.empty?
>
>       return unless @missing_images.empty?
>
>       @build_results = {}
>       @pushed_count = 0
> -    ProviderAccount.includes(:provider).where('providers.enabled' =>  true).each do |account|
> +    ProviderAccount.includes(:provider, :pool_families).where('providers.enabled' =>  true, 'pool_families.id' =>  @deployable.catalogs.first.pool.pool_family.id).each do |account|
>         type = account.provider.provider_type.deltacloud_driver
>         @build_results[type] ||= []
>         status = @deployable.build_status(images, account)
> @@ -163,8 +152,8 @@ class DeployablesController<  ApplicationController
>       rescue =>  e
>         flash.now[:warning]= t('deployables.flash.warning.failed', :message =>  e.message) if @deployable.errors.empty?
>         if params[:create_from_image].present?
> -        load_catalogs
>           @image = Aeolus::Image::Warehouse::Image.find(params[:create_from_image])
> +        load_catalogs
>           @hw_profiles = HardwareProfile.frontend.list_for_user(current_user, Privilege::VIEW)
>         else
>           @catalog = @selected_catalogs.first
> @@ -267,7 +256,8 @@ class DeployablesController<  ApplicationController
>     end
>
>     def load_catalogs
> -    @catalogs = Catalog.list_for_user(current_user, Privilege::MODIFY)
> +    @pool_family = PoolFamily.where(:name =>  @image.environment).first
> +    @catalogs = Catalog.includes(:pool).list_for_user(current_user, Privilege::MODIFY).where('pools.pool_family_id' =>  @pool_family.id)
>     end
>
>     def import_xml_from_url(url)
> diff --git a/src/app/controllers/deployments_controller.rb b/src/app/controllers/deployments_controller.rb
> index b858723..e36c483 100644
> --- a/src/app/controllers/deployments_controller.rb
> +++ b/src/app/controllers/deployments_controller.rb
> @@ -61,14 +61,15 @@ class DeploymentsController<  ApplicationController
>         redirect_to launch_new_deployments_path(:pool_id =>  params[:deployment][:pool_id]) and return
>       end
>
> -    @deployable = Deployable.find(params[:deployable_id])
>       @deployment = Deployment.new(params[:deployment])
>       @pool = @deployment.pool
> +    init_new_deployment_attrs
>       require_privilege(Privilege::CREATE, Deployment, @pool)
>       require_privilege(Privilege::USE, @deployable)
> -    init_new_deployment_attrs
> +    img, img2, missing, d_errors = @deployable.get_image_details
> +    flash[:error] = d_errors unless d_errors.empty?
>
> -    unless @deployable_xml&&  @deployment.valid_deployable_xml?(@deployable_xml)
> +    unless @deployable&&  @deployable.xml&&  @deployment.valid_deployable_xml?(@deployable.xml)&&  d_errors.empty?
>         render 'launch_new' and return
>       end
>
> @@ -84,16 +85,17 @@ class DeploymentsController<  ApplicationController
>     end
>
>     def overview
> -    @deployable = Deployable.find(params[:deployable_id])
>       @deployment = Deployment.new(params[:deployment])
>       @pool = @deployment.pool
> +    init_new_deployment_attrs
>       require_privilege(Privilege::CREATE, Deployment, @pool)
>       require_privilege(Privilege::USE, @deployable)
> -    init_new_deployment_attrs
>       @launch_parameters_encoded = Base64.encode64(ActiveSupport::JSON.encode(@deployment.launch_parameters))
> +    img, img2, missing, d_errors = @deployable.get_image_details
> +    flash[:error] = d_errors unless d_errors.empty?
>
>       respond_to do |format|
> -      if @deployable_xml&&  @deployment.valid_deployable_xml?(@deployable_xml)
> +      if @deployable.xml&&  @deployment.valid_deployable_xml?(@deployable.xml)&&  d_errors.empty?
>           @errors = @deployment.check_assemblies_matches(current_user)
>           set_errors_flash(@errors)
>           @additional_quota = count_additional_quota(@deployment)
> @@ -119,44 +121,54 @@ class DeploymentsController<  ApplicationController
>       @pool = @deployment.pool
>       require_privilege(Privilege::CREATE, Deployment, @pool)
>       init_new_deployment_attrs
> -    @deployment.deployable_xml = @deployable_xml if @deployable_xml
> +    @deployment.deployable_xml = @deployable.xml if @deployable&&  @deployable.xml
>       if params.has_key?(:deployable_id)
> -      @deployable = Deployable.find(params[:deployable_id])
>         require_privilege(Privilege::USE, @deployable)
>       end
> -    @deployment.owner = current_user
> -    load_assemblies_services
> -    if params.delete(:commit) == 'back'
> -      view = launch_parameters_encoded.blank? ? 'launch_new' : 'launch_time_params'
> -      render view and return
> -    end
> -
> +    img, img2, missing, d_errors = @deployable.get_image_details
>       respond_to do |format|
> -      if @deployment.save
> -        status = @deployment.launch(current_user)
> -        if status[:errors].empty?
> -          flash[:notice] = t "deployments.flash.notice.launched"
> -        else
> -          flash[:error] = {
> -              :summary  =>  t("deployments.flash.error.failed_to_launch_assemblies"),
> -              :failures =>  status[:errors],
> -              :successes =>  status[:successes]
> -          }
> -        end
> -        format.html { redirect_to deployment_path(@deployment) }
> -        format.js do
> -          @deployment_properties = @deployment.properties
> -          render :partial =>  'properties'
> -        end
> -        format.json { render :json =>  @deployment, :status =>  :created }
> -      else
> -        # We need @pool to re-display the form
> +      unless d_errors.empty?
>           @pool = @deployment.pool
>           flash.now[:warning] = t "deployments.flash.warning.failed_to_launch"
> -        init_new_deployment_attrs
> +        flash[:error] = d_errors
>           format.html { render :action =>  'overview' }
>           format.js { launch_new }
> -        format.json { render :json =>  @deployment.errors, :status =>  :unprocessable_entity }
> +        format.json { render :json =>  d_errors, :status =>  :unprocessable_entity }
> +      else
> +
> +        @deployment.owner = current_user
> +        load_assemblies_services
> +        if params.delete(:commit) == 'back'
> +          view = launch_parameters_encoded.blank? ? 'launch_new' : 'launch_time_params'
> +          render view and return
> +        end
> +
> +        if @deployment.save
> +          status = @deployment.launch(current_user)
> +          if status[:errors].empty?
> +            flash[:notice] = t "deployments.flash.notice.launched"
> +          else
> +            flash[:error] = {
> +              :summary  =>  t("deployments.flash.error.failed_to_launch_assemblies"),
> +              :failures =>  status[:errors],
> +              :successes =>  status[:successes]
> +            }
> +          end
> +          format.html { redirect_to deployment_path(@deployment) }
> +          format.js do
> +            @deployment_properties = @deployment.properties
> +            render :partial =>  'properties'
> +          end
> +          format.json { render :json =>  @deployment, :status =>  :created }
> +        else
> +          # We need @pool to re-display the form
> +          @pool = @deployment.pool
> +          flash.now[:warning] = t "deployments.flash.warning.failed_to_launch"
> +          init_new_deployment_attrs
> +          format.html { render :action =>  'overview' }
> +          format.js { launch_new }
> +          format.json { render :json =>  @deployment.errors, :status =>  :unprocessable_entity }
> +        end
>         end
>       end
>     end
> @@ -372,7 +384,7 @@ class DeploymentsController<  ApplicationController
>     def init_new_deployment_attrs
>       @deployables = Deployable.list_for_user(current_user, Privilege::USE).select{|d| d.catalogs.collect {|c| c.pool}.include?(@pool)}
>       @pools = Pool.list_for_user(current_user, Privilege::CREATE, Deployment)
> -    @deployable_xml = params[:deployable_id] ? Deployable.find(params[:deployable_id]).xml : nil
> +    @deployable = params[:deployable_id] ? Deployable.find(params[:deployable_id]) : nil
>       @realms = FrontendRealm.all
>       @hardware_profiles = HardwareProfile.all(
>           :include =>  :architecture,
> diff --git a/src/app/controllers/images_controller.rb b/src/app/controllers/images_controller.rb
> index 41108c5..cc2231e 100644
> --- a/src/app/controllers/images_controller.rb
> +++ b/src/app/controllers/images_controller.rb
> @@ -21,8 +21,8 @@ class ImagesController<  ApplicationController
>     def index
>       set_admin_environments_tabs 'images'
>       @header = [
> -      { :name =>  'checkbox', :class =>  'checkbox', :sortable =>  false },
>         { :name =>  t('images.index.name'), :sort_attr =>  :name },
> +      { :name =>  t('images.environment_header'), :sort_attr =>  :name },
>         { :name =>  t('images.index.os'), :sort_attr =>  :name },
>         { :name =>  t('images.index.os_version'), :sort_attr =>  :name },
>         { :name =>  t('images.index.architecture'), :sort_attr =>  :name },
> @@ -37,6 +37,7 @@ class ImagesController<  ApplicationController
>
>     def show
>       @image = Aeolus::Image::Warehouse::Image.find(params[:id])
> +    @environment = PoolFamily.where('name' =>  @image.environment).first
>       if @image.imported?
>         begin
>           # For an imported image, we only want to show the actual provider account
> @@ -46,12 +47,12 @@ class ImagesController<  ApplicationController
>           type = provider.provider_type
>           acct = ProviderAccount.enabled.find_by_provider_name_and_login(provider.name, pimg.provider_account_identifier)
>           raise unless acct
> -        @account_groups = {type.deltacloud_driver =>  {:type =>  type, :accounts =>  [acct]}}
> +        @account_groups = {type.deltacloud_driver =>  {:type =>  type, :accounts =>  [[acct, at environment.provider_accounts.include?(acct)]]}}
>         rescue Exception =>  e
> -        @account_groups = ProviderAccount.enabled.group_by_type(current_user)
> +        @account_groups = ProviderAccount.enabled.group_by_type(@environment)
>         end
>       else
> -      @account_groups = ProviderAccount.enabled.group_by_type(current_user)
> +      @account_groups = ProviderAccount.enabled.group_by_type(@environment)
>       end
>       # according to imagefactory Builder.first shouldn't be implemented yet
>       # but it does what we need - returns builder object which contains
> @@ -74,8 +75,8 @@ class ImagesController<  ApplicationController
>           active_pushes = @account_groups.inject({})  do |result, (driver, group)|
>             timg = @target_images_by_target[driver]
>             group[:accounts].each do |account|
> -            result[account.id] = @builder.find_active_push(timg.id, account.provider.name, account.credentials_hash['username'])
> -            result[account.id].attributes['status'].capitalize! if result[account.id]
> +            result[account[0].id] = @builder.find_active_push(timg.id, account[0].provider.name, account.credentials_hash['username'])
> +            result[account[0].id].attributes['status'].capitalize! if result[account[0].id]
>             end if timg.present?
>
>             result
> @@ -84,7 +85,7 @@ class ImagesController<  ApplicationController
>           provider_images = @account_groups.inject({})  do |result, (driver, group)|
>             timg = @target_images_by_target[driver]
>             group[:accounts].each do |account|
> -            result[account.id] = timg.find_provider_image_by_provider_and_account(account.provider.name, account.credentials_hash['username']).first
> +            result[account[0].id] = timg.find_provider_image_by_provider_and_account(account[0].provider.name, account[0].credentials_hash['username']).first
>             end if timg.present?
>
>             result
> @@ -98,7 +99,7 @@ class ImagesController<  ApplicationController
>           failed_push_counts = @account_groups.inject({})  do |result, (driver, group)|
>             timg = @target_images_by_target[driver]
>             group[:accounts].each do |account|
> -            result[account.id] = @builder.failed_push_count(timg.id, account.provider.name, account.credentials_hash['username'])
> +            result[account[0].id] = @builder.failed_push_count(timg.id, account[0].provider.name, account[0].credentials_hash['username'])
>             end if timg.present?
>
>             result
> @@ -120,7 +121,8 @@ class ImagesController<  ApplicationController
>
>     def rebuild_all
>       @image = Aeolus::Image::Warehouse::Image.find(params[:id])
> -    targets = Provider.enabled.list_for_user(current_user, Privilege::VIEW).map {|p| p.provider_type.deltacloud_driver}.uniq
> +    @environment = PoolFamily.where('name' =>  @image.environment).first
> +    targets = @environment.build_targets
>       unless targets.empty?
>         factory_image = Aeolus::Image::Factory::Image.new(:id =>  @image.id)
>         factory_image.targets = targets.join(',')
> @@ -132,13 +134,14 @@ class ImagesController<  ApplicationController
>
>     def push_all
>       @image = Aeolus::Image::Warehouse::Image.find(params[:id])
> +    @environment = PoolFamily.where('name' =>  @image.environment).first
>       @build = Aeolus::Image::Warehouse::ImageBuild.find(params[:build_id])
>       # only latest builds can be pushed
>       unless latest_build?(@build)
>         redirect_to image_path(@image.id)
>         return
>       end
> -    accounts = ProviderAccount.list_for_user(current_user, Privilege::VIEW)
> +    accounts = @environment.provider_accounts
>       target_images = @build.target_images
>       accounts.each do |account|
>         if account.image_status(@image) == :not_pushed
> @@ -169,20 +172,21 @@ class ImagesController<  ApplicationController
>     end
>
>     def new
> +    @environment = PoolFamily.find(params[:environment])
>       if 'import' == params[:tab]
> -      @accounts = ProviderAccount.enabled.list_for_user(current_user, Privilege::USE)
> +      @accounts = @environment.provider_accounts.enabled.list_for_user(current_user, Privilege::USE)
>         render :import and return
> -    else
> -      @environment = PoolFamily.find(params[:environment])
>       end
>
>     end
>
>     def import
>       account = ProviderAccount.find(params[:provider_account])
> +    @environment = PoolFamily.find(params[:environment])
> +
>       xml = "<image><name>#{params[:name]}</name></image>" unless params[:name].blank?
>       begin
> -      image = Image.import(account, params[:image_id], xml)
> +      image = Image.import(account, params[:image_id], @environment, xml)
>         flash[:success] = t("images.import.image_imported")
>         redirect_to image_url(image.id) and return
>       rescue Exception =>  e
> @@ -283,7 +287,8 @@ class ImagesController<  ApplicationController
>       @image = Aeolus::Image::Warehouse::Image.create!(uuid, body, {
>         :uuid =>  uuid,
>         :object_type =>  'image',
> -      :template =>  @tpl.uuid
> +      :template =>  @tpl.uuid,
> +      :environment =>  @environment.name
>       })
>       flash.now[:error] = t('images.flash.notice.created')
>       redirect_to image_path(@image.id)
> diff --git a/src/app/controllers/pool_families_controller.rb b/src/app/controllers/pool_families_controller.rb
> index 98322eb..d9398ed 100644
> --- a/src/app/controllers/pool_families_controller.rb
> +++ b/src/app/controllers/pool_families_controller.rb
> @@ -74,7 +74,7 @@ class PoolFamiliesController<  ApplicationController
>       @pool_family = PoolFamily.find(params[:id])
>       save_breadcrumb(pool_family_path(@pool_family), @pool_family.name)
>       require_privilege(Privilege::VIEW, @pool_family)
> -    @images = Aeolus::Image::Warehouse::Image.all
> +    @images = Aeolus::Image::Warehouse::Image.by_environment(@pool_family.name)
>       load_pool_family_tabs
>
>       respond_to do |format|
> diff --git a/src/app/models/deployable.rb b/src/app/models/deployable.rb
> index c9df4c5..6d33eef 100644
> --- a/src/app/models/deployable.rb
> +++ b/src/app/models/deployable.rb
> @@ -117,28 +117,49 @@ class Deployable<  ActiveRecord::Base
>     #get details of image for deployable#show
>     def get_image_details
>       deployable_xml = DeployableXML.new(xml)
> +    uuids = deployable_xml.image_uuids
> +    images = []
> +    missing_images = []
>       assemblies_array ||= []
> +    deployable_errors ||= []
>       deployable_xml.assemblies.each do |assembly|
> -      assembly_hash ||= {:name =>  assembly.name}
> +      assembly_hash = {}
> +      image = Aeolus::Image::Warehouse::Image.find(assembly.image_id)
> +      if image.nil?
> +        missing_images<<  assembly.image_id
> +        deployable_errors<<  I18n.t("deployables.flash.error.missing_image",
> +                                    :assembly =>  assembly.name,
> +                                    :uuid =>  assembly.image_id)
> +      else
> +        if image.environment != catalogs.first.pool.pool_family.name
> +          deployable_errors<<  I18n.t("deployables.flash.error.wrong_environment",
> +                                      :assembly =>  assembly.name,
> +                                      :uuid =>  assembly.image_id,
> +                                      :wrong_env =>  image.environment,
> +                                      :environment =>  catalogs.first.pool.pool_family.name)
> +        end
> +        images<<  image
> +        assembly_hash[:build_and_target_uuids] = get_build_and_target_uuids(image)
> +      end
> +      assembly_hash[:name] = assembly.name
>         assembly_hash[:image_uuid] = assembly.image_id
>         assembly_hash[:images_count] = assembly.images_count
>         if assembly.hwp
> -        hwp_name = assembly.hwp
> -        hwp = HardwareProfile.find_by_name(hwp_name)
> +        hwp = HardwareProfile.find_by_name(assembly.hwp)
>           if hwp
>             assembly_hash[:hwp_name] = hwp.name
>             assembly_hash[:hwp_hdd] = hwp.storage.value
>             assembly_hash[:hwp_ram] = hwp.memory.value
>             assembly_hash[:hwp_arch] = hwp.architecture.value
>           else
> -          assembly_hash[:error_hwp] = I18n.t('deployables.error.hwp_not_exists', :name =>  hwp_name)
> +          deployable_errors<<  "#{assembly_hash[:name]}: " + I18n.t('deployables.error.hwp_not_exists', :name =>  assembly.hwp)
>           end
>         else
> -        assembly_hash[:error_hwp] = I18n.t('deployables.error.attribute_not_exist')
> +        deployable_errors<<  "#{assembly_hash[:name]}: " + I18n.t('deployables.error.attribute_not_exist')
>         end
>         assemblies_array<<  assembly_hash
>       end
> -    assemblies_array
> +    [assemblies_array, images, missing_images, deployable_errors]
>     end
>
>     def build_status(images, account)
> @@ -156,29 +177,11 @@ class Deployable<  ActiveRecord::Base
>       [catalog, self]
>     end
>
> -  def get_uuids_hash(images)
> -    image_uuids = []
> -    target_image_uuids = []
> -    latest_build_uuid = []
> -
> -    images.each do |i|
> -      image_uuids<<  (i.respond_to?(:uuid) ? i.uuid : nil)
> -      latest_build = i.latest_pushed_build if i.respond_to?(:latest_pushed_build)
> -      latest_build_uuid<<  (latest_build ? latest_build.uuid : nil)
> -      if latest_build
> -        target_images = latest_build.target_images
> -        target_image_uuids_for_build = []
> -        target_images.each do |ti|
> -          target_image_uuids_for_build<<  ti.uuid
> -        end
> -        target_image_uuids<<  target_image_uuids_for_build
> -      else
> -        target_image_uuids<<  nil
> -      end
> -    end
> -
> -  #return array [[image_uuid, latest_build_uuid, [target_image_uuids]], [..]]
> -  image_uuids.zip(latest_build_uuid, target_image_uuids)
> +  def get_build_and_target_uuids(image)
> +    latest_build = image.respond_to?(:latest_pushed_build) ? image.latest_pushed_build : nil
> +    [(image.respond_to?(:uuid) ? image.uuid : nil),
> +     (latest_build ? latest_build.uuid : nil),
> +     (latest_build ? latest_build.target_images.collect { |ti| ti.uuid} : nil)]
>     end
>
>     private
> diff --git a/src/app/models/pool_family.rb b/src/app/models/pool_family.rb
> index 9bb54b7..3e834a0 100644
> --- a/src/app/models/pool_family.rb
> +++ b/src/app/models/pool_family.rb
> @@ -83,4 +83,11 @@ class PoolFamily<  ActiveRecord::Base
>         :available_quota =>  avail,
>       }
>     end
> +  def build_targets
> +    targets = []
> +    ProviderAccount.enabled.group_by_type(self).each do |driver, group|
> +      targets<<  driver if group[:included]
> +    end
> +    targets
> +  end
>   end
> diff --git a/src/app/models/provider_account.rb b/src/app/models/provider_account.rb
> index 251a1e8..aa450eb 100644
> --- a/src/app/models/provider_account.rb
> +++ b/src/app/models/provider_account.rb
> @@ -316,12 +316,16 @@ class ProviderAccount<  ActiveRecord::Base
>
>     PRESET_FILTERS_OPTIONS = []
>
> -  def self.group_by_type(user)
> +  def self.group_by_type(pool_family)
>       res = {}
> -    ProviderAccount.list_for_user(user, Privilege::VIEW).each do |account|
> +    family_accounts = pool_family.nil? ? [] : pool_family.provider_accounts
> +    ProviderAccount.enabled.each do |account|
>         ptype = account.provider.provider_type
>         res[ptype.deltacloud_driver] ||= {:type =>  ptype, :accounts =>  []}
> -      res[ptype.deltacloud_driver][:accounts]<<  account
> +      res[ptype.deltacloud_driver][:accounts]<<  [account, family_accounts.include?(account)]
> +    end
> +    res.each do |driver, group|
> +      group[:included] = (group[:accounts].count{|a| a[1]}>  0)
>       end
>       res
>     end
> diff --git a/src/app/views/api/environments/_environment.xml.haml b/src/app/views/api/environments/_environment.xml.haml
> new file mode 100644
> index 0000000..5fd425f
> --- /dev/null
> +++ b/src/app/views/api/environments/_environment.xml.haml
> @@ -0,0 +1,9 @@
> +!!!XML
> +%environment{:id =>  environment.id, :href =>  api_image_url(environment.id) }
> +  %name= environment.name
> +  %description= environment.description
> +  %targets{:type =>  'xs:list'}
> +    - ProviderAccount.enabled.group_by_type(environment).select{|driver,group| group[:included]}.each do |driver, group|
> +      %target{:name =>  driver}
> +        - group[:accounts].select{|account| account[1]==true}.each do |account|
> +          %account{:name =>  account[0].name}
> diff --git a/src/app/views/api/environments/index.xml.haml b/src/app/views/api/environments/index.xml.haml
> new file mode 100644
> index 0000000..a3ff564
> --- /dev/null
> +++ b/src/app/views/api/environments/index.xml.haml
> @@ -0,0 +1,4 @@
> +!!!XML
> +%environments
> +  - @environments.each do |env|
> +    = render :partial =>  'environment', :locals =>  {:environment =>  env}
> diff --git a/src/app/views/api/environments/show.xml.haml b/src/app/views/api/environments/show.xml.haml
> new file mode 100644
> index 0000000..21e5fac
> --- /dev/null
> +++ b/src/app/views/api/environments/show.xml.haml
> @@ -0,0 +1,2 @@
> +!!! XML
> += render :partial =>  "environment", :locals =>  {:environment =>  @environment}
> diff --git a/src/app/views/api/images/_image.xml.haml b/src/app/views/api/images/_image.xml.haml
> index f19b1e6..bfabbde 100644
> --- a/src/app/views/api/images/_image.xml.haml
> +++ b/src/app/views/api/images/_image.xml.haml
> @@ -1,6 +1,7 @@
>   !!!XML
>   %image{:id =>  image.id, :href =>  api_image_url(image.id) }
>     %name= image.name
> +  %environment= image.environment
>     %description= image.description
>     %os= image.os.name
>     %os_version= image.os.version
> diff --git a/src/app/views/deployables/_image_uuids.html.haml b/src/app/views/deployables/_image_uuids.html.haml
> index 91902a2..9445641 100644
> --- a/src/app/views/deployables/_image_uuids.html.haml
> +++ b/src/app/views/deployables/_image_uuids.html.haml
> @@ -4,20 +4,20 @@
>         %td
>           %strong="#{t("deployables.show.image_uuid")}"
>         %td
> -        ="#{(@images_hash_details[index][0].nil? ? t('deployables.show.n_a') : @images_hash_details[index][0])}"
> +        ="#{(uuid_arr[0].nil? ? t('deployables.show.n_a') : uuid_arr[0])}"
>       %tr
>         %td
>           %strong="#{t("deployables.show.latest_build_uuid")}"
>         %td
> -        ="#{(@images_hash_details[index][1].nil? ? t("deployables.show.n_a") : @images_hash_details[index][1])}"
> +        ="#{(uuid_arr[1].nil? ? t("deployables.show.n_a") : uuid_arr[1])}"
>       %tr
>         %td
>           %strong="#{t("deployables.show.target_images_uuids")}"
>         %td
> -        - if @images_hash_details[index][2].nil?
> +        - if uuid_arr[2].nil?
>             =t'deployables.show.n_a'
>           - else
>             %ul
> -            - @images_hash_details[index][2].each do |image_uuid|
> +            - uuid_arr[2].each do |image_uuid|
>                 %li
>                   =image_uuid
> diff --git a/src/app/views/deployables/show.html.haml b/src/app/views/deployables/show.html.haml
> index 772536f..504016e 100644
> --- a/src/app/views/deployables/show.html.haml
> +++ b/src/app/views/deployables/show.html.haml
> @@ -31,7 +31,7 @@
>             %th.align-center=t'.arch'
>             %th=t(".uuids")
>         %tbody
> -        - @images_details.each_with_index do |assembly, index|
> +        - @images_details.each do |assembly|
>             %tr
>               %td.status= image_valid?(assembly)
>               %td
> @@ -42,16 +42,16 @@
>               %td.align-center=assembly[:hwp_ram]
>               %td.align-center=assembly[:hwp_arch]
>               %td.align-center
> -              - if @images_hash_details.nil?
> +              - if assembly[:build_and_target_uuids].nil?
>                   = t(".n_a")
>                 - else
>                   %a.control{:href =>  '#'}=t(".show_hide_uuids")
> -          - unless @images_hash_details.nil?
> +          - unless assembly[:build_and_target_uuids].nil?
>               %tr.collapsed
>                 %td
>                 %td{:colspan =>  7}
>                   %ul
> -                  = render :partial =>  "image_uuids", :locals =>  {:index =>  index}
> +                  = render :partial =>  "image_uuids", :locals =>  {:uuid_arr =>  assembly[:build_and_target_uuids]}
>
>   %section.admin-content-section
>     - if @missing_images.empty?
> diff --git a/src/app/views/deployments/launch_from_catalog.html.haml b/src/app/views/deployments/launch_from_catalog.html.haml
> index 8b37b1b..923ba64 100644
> --- a/src/app/views/deployments/launch_from_catalog.html.haml
> +++ b/src/app/views/deployments/launch_from_catalog.html.haml
> @@ -30,7 +30,7 @@
>                   %th=t'.arch'
>                   %th
>                     %strong=t'.deployable_xml'
> -              -deployable.get_image_details.each do |assembly|
> +              -deployable.get_image_details.first.each do |assembly|
>                   %tr
>                     %td
>                       %strong= assembly[:image_uuid]
> diff --git a/src/app/views/images/_list.html.haml b/src/app/views/images/_list.html.haml
> index d75ea59..635297f 100644
> --- a/src/app/views/images/_list.html.haml
> +++ b/src/app/views/images/_list.html.haml
> @@ -1,22 +1,33 @@
>   - content_for :form_header do
> -  - if check_privilege(Privilege::USE, PoolFamily)
> +  - if @pool_family and check_privilege(Privilege::USE, @pool_family)
>       %li= restful_submit_tag t("delete"), "destroy", multi_destroy_images_path, 'DELETE', :id =>  'delete_button', :class =>  'button danger'
> -    %li= link_to t('images.import.import_image'), new_image_path(:tab =>  'import'), { :class =>  'button primary', :id =>  'import_image_button'}
> -    - if @pool_family
> -      %li= link_to t('images.index.new'), new_image_path(:environment =>  @pool_family), { :class =>  'button primary', :id =>  'import_image_button'}
> +    %li= link_to t('images.import.import_image'), new_image_path(:tab =>  'import', :environment =>  @pool_family), { :class =>  'button primary', :id =>  'import_image_button'}
> +    %li= link_to t('images.index.new'), new_image_path(:environment =>  @pool_family), { :class =>  'button primary', :id =>  'import_image_button'}
>
>   = filter_table(@header, @images) do |image|
> -  %tr{:class =>  cycle('nostripe','stripe')}
> -    %td{:class =>  'checkbox'}
> -      - selected = params[:select] == 'all'
> -      = check_box_tag "images_selected[]", image.id, selected, :id =>  "image_checkbox_#{image.id}"
> -    %td
> -      = link_to (image.imported? ? image.name + " (Imported)" : image.name), image_path(image.id)
> -    %td
> -      = image.os.name.empty? ? "N/A" : image.os.name
> -    %td
> -      = image.os.version.empty? ? "N/A" : image.os.version
> -    %td
> -      = image.os.arch.empty? ? "N/A" : image.os.arch
> -    %td
> -      = Time.at(image.latest_pushed_or_unpushed_build.timestamp.to_f) rescue ''
> +  - if @pool_family.nil? and image.environment
> +    - environment = PoolFamily.find_by_name(image.environment)
> +  - else
> +    - environment = nil
> +  - if environment.nil? or check_privilege(Privilege::USE, PoolFamily, environment)
> +    %tr{:class =>  cycle('nostripe','stripe')}
> +      - if @pool_family
> +        %td{:class =>  'checkbox'}
> +          - selected = params[:select] == 'all'
> +          = check_box_tag "images_selected[]", image.id, selected, :id =>  "image_checkbox_#{image.id}"
> +      %td
> +        = link_to (image.imported? ? image.name + " (Imported)" : image.name), image_path(image.id)
> +      - unless @pool_family
> +        %td
> +          - if environment
> +            = link_to environment.name, pool_family_path(environment, :details_tab =>  'images')
> +          - else
> +            N/A
> +      %td
> +        = image.os.name.empty? ? "N/A" : image.os.name
> +      %td
> +        = image.os.version.empty? ? "N/A" : image.os.version
> +      %td
> +        = image.os.arch.empty? ? "N/A" : image.os.arch
> +      %td
> +        = Time.at(image.latest_pushed_or_unpushed_build.timestamp.to_f) rescue ''
> diff --git a/src/app/views/images/import.html.haml b/src/app/views/images/import.html.haml
> index e1c759b..f725b74 100644
> --- a/src/app/views/images/import.html.haml
> +++ b/src/app/views/images/import.html.haml
> @@ -2,11 +2,14 @@
>   %header.admin-page-header
>     %h1.images= t('.import_image')
>     #obj_actions.button-container
> +    = t :return_to
> +    = link_to t('images.environment', :environment =>  @environment.name), pool_family_path(@environment), :class =>  'rounded-link'
>
>   %section.admin-content-section.image-import
>
>     %section#image-file-form
>       = form_tag(import_images_path, { :multipart =>  true, :method =>  :post, :class =>  :generic }) do
> +      = hidden_field_tag :environment, @environment.id
>         %fieldset
>           %p
>             = label_tag :provider_account, t('.provider_account')
> diff --git a/src/app/views/images/show.html.haml b/src/app/views/images/show.html.haml
> index e26a720..5abb90e 100644
> --- a/src/app/views/images/show.html.haml
> +++ b/src/app/views/images/show.html.haml
> @@ -6,20 +6,23 @@
>       = link_to t('images.index.images'), images_path, :class =>  'rounded-link'
>       - if check_privilege(Privilege::USE, PoolFamily)
>         .button-group
> -        = link_to t('.new_deployable_from_image'), new_deployable_path(:create_from_image =>  @image.id), :class =>  'button pill'
> +        - if @environment
> +          = link_to t('.new_deployable_from_image'), new_deployable_path(:create_from_image =>  @image.id), :class =>  'button pill'
>           - unless @image.imported?
>             = link_to t('.template_xml'), template_image_path(@image.uuid), :class =>  'button'
>           = button_to t("delete"), image_path(@image.id), :method =>  'delete', :confirm =>  "Are you sure you want to delete?", :class =>  'button pill danger', :id =>  'delete'
>
>   %section.admin-content-section
>     %header
> +    %h2=t'properties'
> +    %h3= t('images.environment', :environment =>  @image.environment)
>       %h2= t('.provider_images')
>       .section-controls
>         = t'.view_build'
>         = form_tag image_path(@image.id), :method =>  :get do
>           = select_tag :build, options_for_build_select(@builds, @build, @latest_build)
>           = submit_tag t('.select_build'), :id =>  'seletect_build_button'
> -      - if check_privilege(Privilege::USE, PoolFamily)
> +      - if check_privilege(Privilege::USE, PoolFamily) and @environment
>           - if @image.imported?
>             = t('.can_not_build_imported_image')
>           -else
> @@ -28,7 +31,7 @@
>               = button_to t('.push_all'), push_all_image_path(@image.id, :build_id =>  @build.id), :class =>  'button pill'
>     .content
>       %ul.image_builds
> -      - @account_groups.each do |driver, group|
> +      - @account_groups.select{|driver,group| group[:included] || @target_images_by_target[driver] || (@build and @builder.find_active_build(@build.id, driver))}.each do |driver, group|
>           - timg = @target_images_by_target[driver]
>           %li
>             %dl
> @@ -62,23 +65,23 @@
>                       = t'.image_uri'
>                     %th.image_controls
>                   %tbody
> -                  - group[:accounts].each do |account|
> -                    - pimg = timg ? timg.find_provider_image_by_provider_and_account(account.provider.name, account.credentials_hash['username']).first : nil
> +                  - group[:accounts].select{|account| account[1]==true || (timg and !timg.find_provider_image_by_provider_and_account(account[0].provider.name, account[0].credentials_hash['username']).empty?)}.each do |account|
> +                    - pimg = timg ? timg.find_provider_image_by_provider_and_account(account[0].provider.name, account[0].credentials_hash['username']).first : nil
>                       %tr
>                         %td
> -                        %strong= account.name
> +                        %strong= account[0].name
>                         %td.light
> -                        = account.provider.name
> +                        = account[0].provider.name
>                         %td.light
>                           = pimg ? pimg.target_identifier : ''
>                         %td.light
>                           = pimg ? pimg.id : ''
>                         %td.image_controls.light
> -                        - if timg and b = @builder.find_active_push(timg.id, account.provider.name, account.credentials_hash['username'])
> +                        - if timg and b = @builder.find_active_push(timg.id, account[0].provider.name, account[0].credentials_hash['username'])
>                             = label_tag b.status
>                           - elsif timg and not pimg and @build and @build.id == @latest_build
> -                          = button_to t('.upload'), image_provider_images_path(@image.id, :build_id =>  @build.id, :target_image_id =>  timg.id, :account_id =>  account.id), :method =>  :post, :class =>  'upload_image'
> -                          - failed_push_count = @builder.failed_push_count(timg.id, account.provider.name, account.credentials_hash['username'])
> +                          = button_to t('.upload'), image_provider_images_path(@image.id, :build_id =>  @build.id, :target_image_id =>  timg.id, :account_id =>  account[0].id), :method =>  :post, :class =>  'upload_image'
> +                          - failed_push_count = @builder.failed_push_count(timg.id, account[0].provider.name, account[0].credentials_hash['username'])
>                             - if failed_push_count>  0
>                               = t('images.show.failed_push_attempts', :count =>  failed_push_count )
>                           - elsif pimg
> diff --git a/src/config/locales/en.yml b/src/config/locales/en.yml
> index 505be04..117861f 100644
> --- a/src/config/locales/en.yml
> +++ b/src/config/locales/en.yml
> @@ -716,6 +716,7 @@ en:
>           not_found: Target Image not found
>     images:
>       environment: %{environment} Environment
> +    environment_header: Environment
>       import:
>         import_image: Import Image
>         provider_account: Provider Account
> @@ -903,6 +904,8 @@ en:
>           not_selected: "No deployable was not selected!"
>           no_catalog_exists: "No catalog exists! Please create one."
>           no_hwp_exists: "No hardware profile exists! Please create one."
> +        missing_image: "%{assembly}: Image (UUID: %{uuid}) doesn't exist"
> +        wrong_environment: "%{assembly}: Image (UUID: %{uuid}) belongs to the wrong environment (%{wrong_env}) instead of %{environment}"
>         warning:
>           failed: "Deployable was not created: %{message}"
>         notice:
> @@ -1283,7 +1286,10 @@ en:
>         create_new: Create a new Realm Mapping
>     api:
>       error_messages:
> +      account_not_found_in_environment: "Provider Account %{account} is not in this environment. Valid accounts include %{accounts}."
>         build_not_found: Could not find Build %{build}
> +      environment_required: "Environment parameter is required."
> +      environment_not_found: "Environment %{environment} was not found."
>         image_not_found: Could not find Image %{image}
>         image_not_found_on_provider: "Could not find Image %{image} on provider"
>         in_grabbing_target_images: "In grabbing list of target images: %{error}"
> @@ -1298,10 +1304,12 @@ en:
>         provider_image_status_not_found: Could not find status for ProviderImage %{providerimage}
>         push_error: "Could not push TargetImage %{targetimage} to %{account} and error %{error}"
>         specify_a_type_build_or_import: Please specify a type, build or import
> +      specify_environment: Please specify an environment
>         target_image_status_not_found: Could not find status for TargetImage %{targetimage}
>         target_image_not_found: Could not find TargetImage %{targetimage}
>         target_image_not_found_for_account: Could not find an appropriate target image for account %{account}
>         target_not_found: Could not find Target %{target}
> +      target_not_found_in_environment: Target %{target} has no provider accounts in this environment. Valid targets include %{targets}."
>     support:
>       array:
>         words_connector: ', '
> diff --git a/src/config/routes.rb b/src/config/routes.rb
> index 3d440a8..d24f039 100644
> --- a/src/config/routes.rb
> +++ b/src/config/routes.rb
> @@ -262,6 +262,10 @@ Conductor::Application.routes.draw do
>      # :except =>  [:new, :edit]
>
>       resources :hooks
> +
> +    resources :environments do
> +      resources :images
> +    end
>     end
>
>     scope "/api" do
> diff --git a/src/features/deployables.feature b/src/features/deployables.feature
> index a3290cf..f63670c 100644
> --- a/src/features/deployables.feature
> +++ b/src/features/deployables.feature
> @@ -7,6 +7,7 @@ Feature: Manage Catalog Entries
>       Given I am an authorised user
>       And I am logged in
>       And there is mock provider account "mock_account"
> +    And there is a provider account "mock_account" related to pool family "default"
>
>     Scenario: Create new catalog entry
>       Given there is a "default" catalog
> diff --git a/src/features/deployment.feature b/src/features/deployment.feature
> index 0ef9119..a4e80e8 100644
> --- a/src/features/deployment.feature
> +++ b/src/features/deployment.feature
> @@ -185,9 +185,8 @@ Feature: Manage Deployments
>       When I select "test_catalog_entry" from "deployable_id"
>       When I fill in "deployment_name" with "mynewdeployment"
>       When I press "next_button"
> -    Then I should see "Are you sure you wish to deploy"
>       And I should see an error message
> -    And I should see "front_hwp2 not found."
> +    And I should see "Hardware profile front_hwp2 specified in XML doesn't exist."
>
>     Scenario: Verify that the launch parameters are displayed
>       Given a pool "mockpool" exists
> diff --git a/src/lib/image.rb b/src/lib/image.rb
> index 02bfdba..b9ce9b7 100644
> --- a/src/lib/image.rb
> +++ b/src/lib/image.rb
> @@ -17,7 +17,7 @@
>   class Image
>     # Given a ProviderAccount and an AMI/image ID on a provider, plus an optional XML string, use aeolus-image
>     # to import the image. Returns an Aeolus::Image::Factory::Image or allows exceptions to bubble up
> -  def self.import(provider_account, image_id, xml=nil)
> +  def self.import(provider_account, image_id, environment, xml=nil)
>       raise Aeolus::Conductor::Base::BlankImageId unless image_id.present?
>       # Verify that the image exists prior to import
>       conn = provider_account.connect
> @@ -27,6 +27,8 @@ class Image
>       xml ||= "<image><name>#{img.name}</name></image>" if img.name.present?
>       provider = provider_account.provider
>       account_id = provider_account.credentials_hash['username']
> -    Aeolus::Image.import(provider.name, provider.provider_type.deltacloud_driver, image_id, account_id, xml)
> +    image = Aeolus::Image.import(provider.name, provider.provider_type.deltacloud_driver, image_id, account_id, xml)
> +    image.set_attr("environment", environment.name)
> +    image
>     end
>   end
> diff --git a/src/spec/controllers/api/images_controller_spec.rb b/src/spec/controllers/api/images_controller_spec.rb
> index ccce985..718ae9f 100644
> --- a/src/spec/controllers/api/images_controller_spec.rb
> +++ b/src/spec/controllers/api/images_controller_spec.rb
> @@ -33,6 +33,7 @@ describe Api::ImagesController do
>                       :provider =>  "provider")
>         @image = mock(Aeolus::Image::Warehouse::Image,
>                       :id =>  '5',
> +                    :environment =>  'default',
>                       :os =>  @os,
>                       :name =>  'test',
>                       :description =>  'test image',
> @@ -40,15 +41,24 @@ describe Api::ImagesController do
>                       :build =>  @build,
>                       :provider_images =>  [@provider_image, @provider_image.clone]
>                       )
> -      @provider_account = mock(ProviderAccount,
> -                    :label =>  'mock',
> -                    :credentials_hash =>  {'username' =>  'foo', 'password' =>  'bar'})
>         @provider_type = mock(ProviderType,
>                       :name =>  'mock',
>                       :deltacloud_driver =>  'mock')
>         @provider = mock(Provider,
>                       :name =>  'mock',
> +                    :enabled =>  true,
>                       :provider_type =>  @provider_type)
> +      @provider_account = mock(ProviderAccount,
> +                    :label =>  'mock',
> +                    :provider =>  @provider,
> +                    :credentials_hash =>  {'username' =>  'foo', 'password' =>  'bar'})
> +      @environment = mock(PoolFamily, :name =>  'default')
> +      ProviderAccount.any_instance.stub(:pool_families).and_return([@environment])
> +      ProviderAccount.stub(:group_by_type).and_return(
> +                                 "mock" =>  {:type =>  @provider_type,
> +                                            :included =>  true,
> +                                            :accounts =>  [@provider_account, true]})
> +      PoolFamily.any_instance.stub(:provider_accounts).and_return([@provider_account])
>         Aeolus::Image::Warehouse::ImageBuild.stub(:where).and_return([@build])
>       end
>
> @@ -82,6 +92,7 @@ describe Api::ImagesController do
>               @image_collection.each_with_index do |image, index|
>                 resp['images']['image'][index]['name'].should == image.name
>                 resp['images']['image'][index]['id'].should == image.id
> +              resp['images']['image'][index]['environment'].should == image.environment
>                 resp['images']['image'][index]['os'].should == image.os.name
>                 resp['images']['image'][index]['arch'].should == image.os.arch
>                 resp['images']['image'][index]['os_version'].should == image.os.version
> @@ -105,6 +116,7 @@ describe Api::ImagesController do
>               resp = Hash.from_xml(response.body)
>               resp['images']['image']['name'].should == @image.name
>               resp['images']['image']['id'].should == @image.id
> +            resp['images']['image']['environment'].should == @image.environment
>               resp['images']['image']['os'].should == @image.os.name
>               resp['images']['image']['arch'].should == @image.os.arch
>               resp['images']['image']['os_version'].should == @image.os.version
> @@ -146,6 +158,7 @@ describe Api::ImagesController do
>               resp = Hash.from_xml(response.body)
>               resp['image']['name'].should == @image.name
>               resp['image']['id'].should == @image.id
> +            resp['image']['environment'].should == @image.environment
>               resp['image']['os'].should == @image.os.name
>               resp['image']['arch'].should == @image.os.arch
>               resp['image']['os_version'].should == @image.os.version
> @@ -231,9 +244,12 @@ describe Api::ImagesController do
>                     parent.add_child(Nokogiri::XML(tpl).root)
>                     target "mock"
>                   }
> +                environment "default"
>                 }
>               end
>               Aeolus::Image::Factory::Image.stub(:new).and_return(@image)
> +            @image.stub(:targets=).and_return(@image)
> +            @image.stub(:template=).and_return(@image)
>               @image.stub(:save!)
>               Aeolus::Image::Factory::TargetImage.stub(:status).and_return(nil)
>
> @@ -261,12 +277,14 @@ describe Api::ImagesController do
>                     child "c2"
>                   }
>                   provider_account_name "mock"
> +                environment "default"
>                 }
>               end
>               Aeolus::Image::Factory::Image.stub(:new).and_return(@image)
>               ProviderAccount.stub(:find_by_label).and_return(@provider_account)
>               @provider_account.stub(:provider).and_return(@provider)
>               @image.stub(:save!)
> +            @image.stub(:set_attr)
>               # We previously stubbed this out to return nil... That's inappropriate here:
>               Aeolus::Image::Warehouse::Image.stub(:find).and_return(@image)
>               @provider_image.stub(:set_attr)
> @@ -300,12 +318,14 @@ describe Api::ImagesController do
>                     child "c4"
>                   }
>                   provider_account_name "mock2"
> +                environment "default"
>                 }
>               end
>               Aeolus::Image::Factory::Image.stub(:new).and_return(@image)
>               ProviderAccount.stub(:find_by_label).and_return(@provider_account)
>               @provider_account.stub(:provider).and_return(@provider)
>               @image.stub(:save!)
> +            @image.stub(:set_attr)
>               # We previously stubbed this out to return nil... That's inappropriate here:
>               Aeolus::Image::Warehouse::Image.stub(:find).and_return(@image)
>               @provider_image.stub(:set_attr)
> diff --git a/src/spec/controllers/api/provider_images_controller_spec.rb b/src/spec/controllers/api/provider_images_controller_spec.rb
> index 162f9ee..51a2e9a 100644
> --- a/src/spec/controllers/api/provider_images_controller_spec.rb
> +++ b/src/spec/controllers/api/provider_images_controller_spec.rb
> @@ -56,7 +56,7 @@ describe Api::ProviderImagesController do
>                                                                       :build =>  @build)
>           @build1 = mock(Aeolus::Image::Warehouse::ImageBuild, :target_images =>  [@target_image, @target_image])
>           @build2 = mock(Aeolus::Image::Warehouse::ImageBuild, :target_images =>  [@target_image, @target_image])
> -        @image = mock(Aeolus::Image::Warehouse::Image, :image_builds =>  [@build1, @build2])
> +        @image = mock(Aeolus::Image::Warehouse::Image, :image_builds =>  [@build1, @build2], :environment =>  'default')
>         end
>
>         it "should raise bad request when no provider account is given" do
> @@ -347,6 +347,7 @@ describe Api::ProviderImagesController do
>           context "when trying to build image" do
>             before(:each) do
>               @provider_account = FactoryGirl.create :mock_provider_account
> +            @provider_account.pool_families<<  PoolFamily.find_by_name('default')
>               xml = Nokogiri::XML::Builder.new do |x|
>                 x.provider_image {
>                   x.image_id "17"
> @@ -362,7 +363,7 @@ describe Api::ProviderImagesController do
>                                                                           :target =>  'mock')
>               @build1 = mock(Aeolus::Image::Warehouse::ImageBuild, :id=>'3', :target_images =>  [@target_image, @target_image])
>               @build2 = mock(Aeolus::Image::Warehouse::ImageBuild, :id=>'2', :target_images =>  [@target_image, @target_image])
> -            @image = mock(Aeolus::Image::Warehouse::Image, :id=>'1', :image_builds =>  [@build1, @build2])
> +            @image = mock(Aeolus::Image::Warehouse::Image, :id=>'1', :image_builds =>  [@build1, @build2], :environment =>  'default')
>
>               @build1.stub(:image).and_return(@image)
>               @build2.stub(:image).and_return(@image)
> diff --git a/src/spec/controllers/deployables_controller_spec.rb b/src/spec/controllers/deployables_controller_spec.rb
> index 65583cb..72d89f4 100644
> --- a/src/spec/controllers/deployables_controller_spec.rb
> +++ b/src/spec/controllers/deployables_controller_spec.rb
> @@ -23,13 +23,14 @@ describe DeployablesController do
>       @admin_permission = FactoryGirl.create :admin_permission
>       @admin = @admin_permission.user
>       mock_warden(@admin)
> +    @catalog = FactoryGirl.create(:catalog)
>     end
>
>     describe "#new" do
>       context "with params[:create_from_image]" do
>         before do
>           @deployable = stub_model(Deployable, :name =>  "test_new", :id =>  1)
> -        @image = mock(Aeolus::Image::Warehouse::Image, :id =>  '3c58e0d6-d11a-4e68-8b12-233783e56d35', :name =>  'image1', :uuid =>  '3c58e0d6-d11a-4e68-8b12-233783e56d35')
> +        @image = mock(Aeolus::Image::Warehouse::Image, :id =>  '3c58e0d6-d11a-4e68-8b12-233783e56d35', :name =>  'image1', :uuid =>  '3c58e0d6-d11a-4e68-8b12-233783e56d35', :environment =>  "default")
>           Aeolus::Image::Warehouse::Image.stub(:find).and_return(@image)
>         end
>
> @@ -39,13 +40,13 @@ describe DeployablesController do
>         end
>
>         it "returns flash[:error] when no catalog and hardware profile exists" do
> -        Catalog.stub(:list_for_user).and_return([])
> +        Catalog.stub(:list_for_user).and_return(Catalog.includes(:pool).where("1=0"))
>           get :new, :create_from_image =>  @image.id
>           flash[:error].should_not be_empty
>         end
>
>         it "returns flash[:error] when no catalog exists" do
> -        Catalog.stub(:list_for_user).and_return([])
> +        Catalog.stub(:list_for_user).and_return(Catalog.includes(:pool).where("1=0"))
>           HardwareProfile.stub(:list_for_user).and_return([mock(HardwareProfile)])
>           get :new, :create_from_image =>  @image.id
>           flash[:error].should eql(["No catalog exists! Please create one."])
> @@ -55,7 +56,7 @@ describe DeployablesController do
>
>     describe "#create" do
>       before(:each) do
> -      @image = mock(Aeolus::Image::Warehouse::Image, :id =>  '3c58e0d6-d11a-4e68-8b12-233783e56d35', :name =>  'image1', :uuid =>  '3c58e0d6-d11a-4e68-8b12-233783e56d35')
> +      @image = mock(Aeolus::Image::Warehouse::Image, :id =>  '3c58e0d6-d11a-4e68-8b12-233783e56d35', :name =>  'image1', :uuid =>  '3c58e0d6-d11a-4e68-8b12-233783e56d35', :environment =>  "default")
>         Aeolus::Image::Warehouse::Image.stub(:find).and_return(@image)
>       end
>
> @@ -67,8 +68,7 @@ describe DeployablesController do
>
>       it "creates new deployable from image via POST" do
>         hw_profile = FactoryGirl.create(:front_hwp1)
> -      catalog = FactoryGirl.create(:catalog)
> -      post(:create, :create_from_image =>  @image.id, :deployable =>  {:name =>  @image.name}, :hardware_profile =>  hw_profile.id, :catalog_id =>  catalog.id)
> +      post(:create, :create_from_image =>  @image.id, :deployable =>  {:name =>  @image.name}, :hardware_profile =>  hw_profile.id, :catalog_id =>  @catalog.id)
>         response.should be_redirect
>       end
>     end
> diff --git a/src/spec/controllers/deployments_controller_spec.rb b/src/spec/controllers/deployments_controller_spec.rb
> index 278cb9d..6986ef8 100644
> --- a/src/spec/controllers/deployments_controller_spec.rb
> +++ b/src/spec/controllers/deployments_controller_spec.rb
> @@ -62,8 +62,11 @@ describe DeploymentsController do
>           Factory.create(:front_hwp1)
>           Factory.create(:front_hwp2)
>           @deployment = Factory.build(:deployment)
> +        catalog_entry = Factory.create(:catalog_entry)
> +        @deployable = catalog_entry.deployable
> +        @catalog = catalog_entry.catalog
>           Deployment.stub!(:new).and_return(@deployment)
> -        post :create
> +        post :create, :deployable_id =>  @deployable.id
>         end
>
>         it { response.should be_success }
> diff --git a/src/spec/vcr/cassettes/iwhd_connection.yml b/src/spec/vcr/cassettes/iwhd_connection.yml
> index fc26a1a..0f2e6b7 100644
> --- a/src/spec/vcr/cassettes/iwhd_connection.yml
> +++ b/src/spec/vcr/cassettes/iwhd_connection.yml
> @@ -75,6 +75,7 @@
>         	<object_attr name="object_type" path="http://localhost:9090/images/53d2a281-448b-4872-b1b0-680edaad5922/object_type"/>
>         	<object_attr name="uuid" path="http://localhost:9090/images/53d2a281-448b-4872-b1b0-680edaad5922/uuid"/>
>         	<object_attr name="template" path="http://localhost:9090/images/53d2a281-448b-4872-b1b0-680edaad5922/template"/>
> +      	<object_attr name="environment" path="http://localhost:9090/images/53d2a281-448b-4872-b1b0-680edaad5922/environment"/>
>         </object>
>
>       http_version: "1.1"
> @@ -104,6 +105,29 @@
>   - !ruby/struct:VCR::HTTPInteraction
>     request: !ruby/struct:VCR::Request
>       method: :get
> +    uri: http://localhost:9090/images/53d2a281-448b-4872-b1b0-680edaad5922/environment
> +    body:
> +    headers:
> +      accept:
> +      - "*/*; q=0.5, application/xml"
> +      accept-encoding:
> +      - gzip, deflate
> +      content-length:
> +      - "0"
> +  response: !ruby/struct:VCR::Response
> +    status: !ruby/struct:VCR::ResponseStatus
> +      code: 200
> +      message: OK
> +    headers:
> +      date:
> +      - Mon, 13 Jun 2011 14:11:30 GMT
> +      content-length:
> +      - "7"
> +    body: default
> +    http_version: "1.1"
> +- !ruby/struct:VCR::HTTPInteraction
> +  request: !ruby/struct:VCR::Request
> +    method: :get
>       uri: http://localhost:9090/images/53d2a281-448b-4872-b1b0-680edaad5922/latest_unpushed
>       body:
>       headers:
> @@ -384,6 +408,7 @@
>         	<object_attr name="target" path="http://localhost:9090/images/34c87aa0-3405-42f8-820e-309054029295/target"/>
>         	<object_attr name="target_parameters" path="http://localhost:9090/images/34c87aa0-3405-42f8-820e-309054029295/target_parameters"/>
>         	<object_attr name="template" path="http://localhost:9090/images/34c87aa0-3405-42f8-820e-309054029295/template"/>
> +      	<object_attr name="environment" path="http://localhost:9090/images/34c87aa0-3405-42f8-820e-309054029295/environment"/>
>         	<object_attr name="uuid" path="http://localhost:9090/images/34c87aa0-3405-42f8-820e-309054029295/uuid"/>
>         </object>
>
> @@ -506,6 +531,29 @@
>   - !ruby/struct:VCR::HTTPInteraction
>     request: !ruby/struct:VCR::Request
>       method: :get
> +    uri: http://localhost:9090/images/34c87aa0-3405-42f8-820e-309054029295/environment
> +    body:
> +    headers:
> +      accept:
> +      - "*/*; q=0.5, application/xml"
> +      accept-encoding:
> +      - gzip, deflate
> +      content-length:
> +      - "0"
> +  response: !ruby/struct:VCR::Response
> +    status: !ruby/struct:VCR::ResponseStatus
> +      code: 200
> +      message: OK
> +    headers:
> +      date:
> +      - Mon, 13 Jun 2011 14:11:46 GMT
> +      content-length:
> +      - "7"
> +    body: default
> +    http_version: "1.1"
> +- !ruby/struct:VCR::HTTPInteraction
> +  request: !ruby/struct:VCR::Request
> +    method: :get
>       uri: http://localhost:9090/images/34c87aa0-3405-42f8-820e-309054029295/uuid
>       body:
>       headers:
> @@ -557,6 +605,7 @@
>         	<object_attr name="target_parameters" path="http://localhost:9090/images/ef6fd2bb-50d4-4f53-ae92-a68b01f82cf1/target_parameters"/>
>         	<object_attr name="template" path="http://localhost:9090/images/ef6fd2bb-50d4-4f53-ae92-a68b01f82cf1/template"/>
>         	<object_attr name="uuid" path="http://localhost:9090/images/ef6fd2bb-50d4-4f53-ae92-a68b01f82cf1/uuid"/>
> +      	<object_attr name="environment" path="http://localhost:9090/images/ef6fd2bb-50d4-4f53-ae92-a68b01f82cf1/environment"/>
>         </object>
>
>       http_version: "1.1"
> @@ -678,6 +727,29 @@
>   - !ruby/struct:VCR::HTTPInteraction
>     request: !ruby/struct:VCR::Request
>       method: :get
> +    uri: http://localhost:9090/images/ef6fd2bb-50d4-4f53-ae92-a68b01f82cf1/environment
> +    body:
> +    headers:
> +      accept:
> +      - "*/*; q=0.5, application/xml"
> +      accept-encoding:
> +      - gzip, deflate
> +      content-length:
> +      - "0"
> +  response: !ruby/struct:VCR::Response
> +    status: !ruby/struct:VCR::ResponseStatus
> +      code: 200
> +      message: OK
> +    headers:
> +      date:
> +      - Mon, 13 Jun 2011 14:11:48 GMT
> +      content-length:
> +      - "7"
> +    body: default
> +    http_version: "1.1"
> +- !ruby/struct:VCR::HTTPInteraction
> +  request: !ruby/struct:VCR::Request
> +    method: :get
>       uri: http://localhost:9090/images/ef6fd2bb-50d4-4f53-ae92-a68b01f82cf1/uuid
>       body:
>       headers:
> @@ -727,6 +799,7 @@
>         	<object_attr name="latest_unpushed" path="http://localhost:9090/images/85653387-88b2-46b0-b7b2-c779d2af06c7/latest_unpushed"/>
>         	<object_attr name="object_type" path="http://localhost:9090/images/85653387-88b2-46b0-b7b2-c779d2af06c7/object_type"/>
>         	<object_attr name="uuid" path="http://localhost:9090/images/85653387-88b2-46b0-b7b2-c779d2af06c7/uuid"/>
> +      	<object_attr name="environment" path="http://localhost:9090/images/85653387-88b2-46b0-b7b2-c779d2af06c7/environment"/>
>         </object>
>
>       http_version: "1.1"
> @@ -824,6 +897,29 @@
>       http_version: "1.1"
>   - !ruby/struct:VCR::HTTPInteraction
>     request: !ruby/struct:VCR::Request
> +    method: :get
> +    uri: http://localhost:9090/images/85653387-88b2-46b0-b7b2-c779d2af06c7/environment
> +    body:
> +    headers:
> +      accept:
> +      - "*/*; q=0.5, application/xml"
> +      accept-encoding:
> +      - gzip, deflate
> +      content-length:
> +      - "0"
> +  response: !ruby/struct:VCR::Response
> +    status: !ruby/struct:VCR::ResponseStatus
> +      code: 200
> +      message: OK
> +    headers:
> +      date:
> +      - Mon, 13 Jun 2011 14:11:51 GMT
> +      content-length:
> +      - "7"
> +    body: default
> +    http_version: "1.1"
> +- !ruby/struct:VCR::HTTPInteraction
> +  request: !ruby/struct:VCR::Request
>       method: :post
>       uri: http://localhost:9090/images/images_testing_uuid
>       body: op=parts

This patch introduces some inconsistency between the haml views and 
jquery templates in images/show.html.haml. Could you please fix this?



More information about the aeolus-devel mailing list