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

Scott Seago sseago at redhat.com
Wed Feb 15 18:51:38 UTC 2012


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
-- 
1.7.6.4




More information about the aeolus-devel mailing list