commit fba2bf5f41fb2ead4e4fbaa69bb61faba880daa1
Author: Greg Sutcliffe <gsutclif@redhat.com>
Date:   Sun Dec 22 00:21:27 2013 +0000

    Fixes #3927 - Allow VMs to provision via user-data

diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index cd45925cf..b63e332d6 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -161,13 +161,11 @@ function toggleRowGroup(el) {
 }
 
 function template_info(div, url) {
-  os_id = $("#host_operatingsystem_id :selected").attr("value");
-  env_id = $("#host_environment_id :selected").attr("value");
-  hostgroup_id = $("#host_hostgroup_id :selected").attr("value");
+  form = $("form").serialize();
   build = $('input:radio[name$="[provision_method]"]:checked').val();
 
   $(div).html(spinner_placeholder());
-  $(div).load(url + "?operatingsystem_id=" + os_id + "&hostgroup_id=" + hostgroup_id + "&environment_id=" + env_id+"&provisioning="+build,
+  $(div).load(url + "?provisioning=" + build + "&" + form,
               function(response, status, xhr) {
                 if (status == "error") {
                   $(div).html('<div class="alert alert-warning alert-dismissable">' +
diff --git a/app/controllers/hosts_controller.rb b/app/controllers/hosts_controller.rb
index cce39ffe1..2e0341377 100644
--- a/app/controllers/hosts_controller.rb
+++ b/app/controllers/hosts_controller.rb
@@ -474,12 +474,28 @@ class HostsController < ApplicationController
     render :partial => 'form'
   end
 
-
   def template_used
-    kinds = params[:provisioning] == 'image' ? [TemplateKind.find_by_name('finish')] : TemplateKind.all
+    host  = params[:host]
+    kinds = if params[:provisioning] == 'image'
+              cr     = ComputeResource.find_by_id(host[:compute_resource_id])
+              images = cr.try(:images)
+              if images.nil?
+                [TemplateKind.find_by_name('finish')]
+              else
+                uuid       = host[:compute_attributes][cr.image_param_name]
+                image_kind = images.find_by_uuid(uuid).try(:user_data) ? 'user_data' : 'finish'
+                [TemplateKind.find_by_name(image_kind)]
+              end
+            else
+              TemplateKind.all
+            end
+
     templates = kinds.map do |kind|
-      ConfigTemplate.find_template({:kind => kind.name, :operatingsystem_id => params[:operatingsystem_id],
-                                   :hostgroup_id => params[:hostgroup_id], :environment_id => params[:environment_id]})
+      ConfigTemplate.find_template({:kind               => kind.name,
+                                    :operatingsystem_id => host[:operatingsystem_id],
+                                    :hostgroup_id       => host[:hostgroup_id],
+                                    :environment_id     => host[:environment_id]
+      })
     end.compact
     return not_found if templates.empty?
     render :partial => "provisioning", :locals => {:templates => templates}
diff --git a/app/models/compute_resource.rb b/app/models/compute_resource.rb
index c0174835b..19f57efa2 100644
--- a/app/models/compute_resource.rb
+++ b/app/models/compute_resource.rb
@@ -96,6 +96,10 @@ class ComputeResource < ActiveRecord::Base
     list[PROVIDERS.index(provider)] rescue ""
   end
 
+  def image_param_name
+    :image_id
+  end
+
   # returns a new fog server instance
   def new_vm attr={}
     test_connection
diff --git a/app/models/compute_resources/foreman/model/openstack.rb b/app/models/compute_resources/foreman/model/openstack.rb
index 51dad0e8e..9ccc9868f 100644
--- a/app/models/compute_resources/foreman/model/openstack.rb
+++ b/app/models/compute_resources/foreman/model/openstack.rb
@@ -18,6 +18,10 @@ module Foreman::Model
       ComputeResource.model_name
     end
 
+    def image_param_name
+      :image_ref
+    end
+
     def capabilities
       [:image]
     end
diff --git a/app/models/concerns/orchestration/compute.rb b/app/models/concerns/orchestration/compute.rb
index 82b578147..438cc43f8 100644
--- a/app/models/concerns/orchestration/compute.rb
+++ b/app/models/concerns/orchestration/compute.rb
@@ -8,7 +8,7 @@ module Orchestration::Compute
   end
 
   def compute?
-    compute_resource_id.present? and compute_attributes.present?
+    compute_resource_id.present? && ( compute_attributes.present? || uuid.present? )
   end
 
   def compute_object
@@ -29,11 +29,13 @@ module Orchestration::Compute
   end
 
   def queue_compute_create
-    queue.create(:name   => _("Set up compute instance %s") % self, :priority => 1,
+    queue.create(:name   => _("Render user data template for %s") % self, :priority => 1,
+                 :action => [self, :setUserData]) if find_image.try(:user_data)
+    queue.create(:name   => _("Set up compute instance %s") % self, :priority => 2,
                  :action => [self, :setCompute])
-    queue.create(:name   => _("Acquire IP address for %s") % self, :priority => 2,
+    queue.create(:name   => _("Acquire IP address for %s") % self, :priority => 3,
                  :action => [self, :setComputeIP]) if compute_resource.provided_attributes.keys.include?(:ip)
-    queue.create(:name   => _("Query instance details for %s") % self, :priority => 3,
+    queue.create(:name   => _("Query instance details for %s") % self, :priority => 4,
                  :action => [self, :setComputeDetails])
     queue.create(:name   => _("Power up compute instance %s") % self, :priority => 1000,
                  :action => [self, :setComputePowerUp]) if compute_attributes[:start] == '1'
@@ -59,6 +61,29 @@ module Orchestration::Compute
     failure _("Failed to create a compute %{compute_resource} instance %{name}: %{message}\n ") % { :compute_resource => compute_resource, :name => name, :message => e.message }, e.backtrace
   end
 
+  def setUserData
+    logger.info "Rendering UserData template for #{name}"
+    template   = configTemplate(:kind => "user_data")
+    @host      = self
+    # For some reason this renders as 'built' in spoof view but 'provision' when
+    # actually used. For now, use foreman_url('built') in the template
+    self.compute_attributes[:user_data] = unattended_render(template.template)
+    self.handle_ca
+    return false if errors.any?
+    logger.info "Revoked old certificates and enabled autosign for UserData"
+  end
+
+  def delUserData
+    # Mostly copied from SSHProvision, should probably refactor to have both use a common set of PuppetCA actions
+    compute_attributes.merge!(:user_data => nil) # Unset any badly formatted data
+    # since we enable certificates/autosign via here, we also need to make sure we clean it up in case of an error
+    if puppetca?
+      respond_to?(:initialize_puppetca,true) && initialize_puppetca && delCertificate && delAutosign
+    end
+  rescue => e
+    failure _("Failed to remove certificates for %{name}: %{e}") % { :name => name, :e => e }, e.backtrace
+  end
+
   def setComputeDetails
     if vm
       attrs = compute_resource.provided_attributes
@@ -139,11 +164,16 @@ module Orchestration::Compute
     compute_resource.update_required?(old.compute_attributes, compute_attributes.symbolize_keys)
   end
 
-  def validate_compute_provisioning
-    return true if compute_attributes.nil?
+  def find_image
+    return nil if compute_attributes.nil?
     image_uuid = compute_attributes[:image_id] || compute_attributes[:image_ref]
-    return true if image_uuid.blank?
-    img = Image.where(:uuid => image_uuid, :compute_resource_id => compute_resource_id).first
+    return nil if image_uuid.blank?
+    Image.where(:uuid => image_uuid, :compute_resource_id => compute_resource_id).first
+  end
+
+  def validate_compute_provisioning
+    return true if ( compute_attributes.nil? or (compute_attributes[:image_id] || compute_attributes[:image_ref]).blank? )
+    img = find_image
     if img
       self.image = img
     else
diff --git a/app/models/concerns/orchestration/ssh_provision.rb b/app/models/concerns/orchestration/ssh_provision.rb
index 09dbfb911..b05465a2a 100644
--- a/app/models/concerns/orchestration/ssh_provision.rb
+++ b/app/models/concerns/orchestration/ssh_provision.rb
@@ -7,7 +7,7 @@ module Orchestration::SSHProvision
   end
 
   def ssh_provision?
-    compute_attributes.present? && capabilities.include?(:image)
+    compute_attributes.present? && capabilities.include?(:image) && !image.try(:user_data)
   end
 
   protected
diff --git a/app/models/image.rb b/app/models/image.rb
index 012833540..cdee841c5 100644
--- a/app/models/image.rb
+++ b/app/models/image.rb
@@ -14,5 +14,6 @@ class Image < ActiveRecord::Base
   scoped_search :in => :compute_resources, :on => :name, :complete_value => :true, :rename => "compute_resource"
   scoped_search :in => :architecture, :on => :id, :rename => "architecture"
   scoped_search :in => :operatingsystem, :on => :id, :rename => "operatingsystem"
+  scoped_search :on => :user_data, :complete_value => {:true => true, :false => false}
 
 end
diff --git a/app/views/images/_form.html.erb b/app/views/images/_form.html.erb
index 91ac5d426..79eecd0a5 100644
--- a/app/views/images/_form.html.erb
+++ b/app/views/images/_form.html.erb
@@ -6,6 +6,7 @@
   <%= select_f f, :architecture_id, Architecture.all, :id, :to_label %>
   <%= text_f f, :username, :value => @image.username || "root", :help_inline => _("The user that is used to ssh into the instance, normally cloud-user, ec2-user, ubuntu, root etc") %>
   <%= image_field(f) %>
+  <%= checkbox_f f, :user_data, :help_inline => _("Does this image support user data input (e.g. via cloud-init)?") %>
   <% if @compute_resource.provider == 'EC2' %>
     <%# TODO - Get IAM roles from AWS and display in select drop %>
     <%= text_f f, :iam_role, :help_inline => _("(optional) IAM Role for Fog to use when creating this image.") %>
diff --git a/app/views/images/index.html.erb b/app/views/images/index.html.erb
index 5441a7016..8d24389ec 100644
--- a/app/views/images/index.html.erb
+++ b/app/views/images/index.html.erb
@@ -5,10 +5,11 @@
 <table class="table table-bordered table-striped" data-table='inline'>
   <thead>
   <tr>
-    <th><%= _("Image|Name") %></th>
-    <th><%= _("Operating System") %></th>
-    <th><%= _("Image|Username") %></th>
-    <th><%= _("Image|Uuid") %></th>
+    <th><%= s_("Image|Name") %></th>
+    <th><%= s_("Operating System") %></th>
+    <th><%= s_("Image|Username") %></th>
+    <th><%= s_("Image|Uuid") %></th>
+    <th><%= s_("Image|User data") %></th>
     <th></th>
   </tr>
   </thead>
@@ -18,6 +19,7 @@
     <td><%= image.operatingsystem %></td>
     <td><%= image.username %></td>
     <td><%= image.uuid %></td>
+    <td><%= image.user_data? ? _("Enabled") : _("Disabled") %></td>
     <td><%= action_buttons(link_to(_('Edit'), edit_compute_resource_image_path(@compute_resource, image)),
      link_to(_('Destroy'), [@compute_resource, image], :confirm => _('Are you sure?'), :method => :delete)) %></td>
   </tr>
diff --git a/db/migrate/20131223120945_add_userdata_flag_to_images.rb b/db/migrate/20131223120945_add_userdata_flag_to_images.rb
new file mode 100644
index 000000000..147e6b127
--- /dev/null
+++ b/db/migrate/20131223120945_add_userdata_flag_to_images.rb
@@ -0,0 +1,5 @@
+class AddUserdataFlagToImages < ActiveRecord::Migration
+  def change
+    add_column :images, :user_data, :boolean, :default => false
+  end
+end
diff --git a/db/seeds.rb b/db/seeds.rb
index 364facd65..cf32540b4 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -28,7 +28,7 @@ end
 
 # Template kinds
 kinds = {}
-[:PXELinux, :PXEGrub, :iPXE, :provision, :finish, :script].each do |type|
+[:PXELinux, :PXEGrub, :iPXE, :provision, :finish, :script, :user_data].each do |type|
   kinds[type] = TemplateKind.find_by_name(type)
   kinds[type] ||= TemplateKind.create :name => type
   raise "Unable to create template kind: #{format_errors kinds[type]}" if kinds[type].nil? || kinds[type].errors.any?
