From fcb017117540e0e9d9c2363fd67b5e7dcfd5b468 Mon Sep 17 00:00:00 2001
From: Ohad Levy <ohadlevy@gmail.com>
Date: Sun, 17 Oct 2010 10:10:34 +0200
Subject: [PATCH] Fixes #178 - Automate populating the TFTP directory

This implementation is only for the Red Hat family, other OS's should follow
It implements a way to query foreman and gets the required boot files (i.e. kernel and initrd) files
from a predefine media over JSON.
it also provides a syslinux pxe configuration file based on those values and potentially other settings define via foreman.
---
 app/controllers/hosts_controller.rb            |    6 ++++-
 app/controllers/operatingsystems_controller.rb |   12 ++++++++++
 app/controllers/unattended_controller.rb       |   14 +++++++++--
 app/models/operatingsystem.rb                  |   27 ++++++++++++++++++++---
 app/models/redhat.rb                           |   16 ++++++++++++++
 app/views/unattended/pxe_kickstart_config.erb  |    4 +++
 config/routes.rb                               |    4 +-
 7 files changed, 73 insertions(+), 10 deletions(-)
 create mode 100644 app/views/unattended/pxe_kickstart_config.erb

diff --git a/app/controllers/hosts_controller.rb b/app/controllers/hosts_controller.rb
index e15f82a..c09e81e 100644
--- a/app/controllers/hosts_controller.rb
+++ b/app/controllers/hosts_controller.rb
@@ -7,7 +7,7 @@ class HostsController < ApplicationController
     :select_multiple_hostgroup, :select_multiple_environment, :multiple_parameters, :multiple_destroy,
     :multiple_enable, :multiple_disable, :submit_multiple_disable, :submit_multiple_enable]
   before_filter :find_host, :only => %w[show edit update destroy puppetrun setBuild cancelBuild report
-    reports facts storeconfig_klasses clone externalNodes]
+    reports facts storeconfig_klasses clone externalNodes pxe_config]
 
   helper :hosts, :reports
 
@@ -228,6 +228,10 @@ class HostsController < ApplicationController
     end
   end
 
+  def pxe_config
+    redirect_to(:controller => "unattended", :action => "pxe_#{@host.operatingsystem.pxe_type}_config", :host_id => @host) if @host
+  end
+
   def storeconfig_klasses
   end
 
diff --git a/app/controllers/operatingsystems_controller.rb b/app/controllers/operatingsystems_controller.rb
index 7fb3d1a..ea220a3 100644
--- a/app/controllers/operatingsystems_controller.rb
+++ b/app/controllers/operatingsystems_controller.rb
@@ -54,6 +54,18 @@ class OperatingsystemsController < ApplicationController
     redirect_to operatingsystems_url
   end
 
+  def bootfiles
+    media = Media.find_by_name(params[:media])
+    arch =  Architecture.find_by_name(params[:architecture])
+    respond_to do |format|
+      format.json { render :json => @operatingsystem.pxe_files(media, arch)}
+    end
+  rescue => e
+    respond_to do |format|
+      format.json { render :json => e.to_s, :status => :unprocessable_entity }
+    end
+  end
+
   private
   def find_os
     @operatingsystem = Operatingsystem.find(params[:id])
diff --git a/app/controllers/unattended_controller.rb b/app/controllers/unattended_controller.rb
index 977c839..70175f9 100644
--- a/app/controllers/unattended_controller.rb
+++ b/app/controllers/unattended_controller.rb
@@ -1,10 +1,11 @@
 class UnattendedController < ApplicationController
   layout nil
   helper :all
-  before_filter :get_host_details, :allowed_to_install?
-  before_filter :handle_ca, :except => [:jumpstart_finish, :preseed_finish]
+  before_filter :get_host_details, :allowed_to_install?, :except => :pxe_kickstart_config
+  before_filter :handle_ca, :except => [:jumpstart_finish, :preseed_finish, :pxe_kickstart_config]
   skip_before_filter :require_ssl, :require_login
-  after_filter :set_content_type, :only => [:kickstart, :preseed, :preseed_finish, :jumpstart_profile, :jumpstart_finish]
+  after_filter :set_content_type, :only => [:kickstart, :preseed, :preseed_finish,
+    :jumpstart_profile, :jumpstart_finish, :pxe_kickstart_config]
 
   def kickstart
     logger.info "#{controller_name}: Kickstart host #{@host.name}"
@@ -43,6 +44,13 @@ class UnattendedController < ApplicationController
     head(:created) and return
   end
 
+  def pxe_kickstart_config
+    @host = Host.find_by_name params[:host_id]
+    prefix = @host.operatingsystem.pxe_prefix(@host.arch)
+    @kernel = "#{prefix}-#{Redhat::PXEFILES[:kernel]}"
+    @initrd = "#{prefix}-#{Redhat::PXEFILES[:initrd]}"
+  end
+
   private
   # lookup for a host based on the ip address and if possible by a mac address(as sent by anaconda)
   # if the host was found than its record will be in @host
diff --git a/app/models/operatingsystem.rb b/app/models/operatingsystem.rb
index 1a7355d..3d74050 100644
--- a/app/models/operatingsystem.rb
+++ b/app/models/operatingsystem.rb
@@ -43,10 +43,14 @@ class Operatingsystem < ActiveRecord::Base
 
   def media_uri host, url = nil
     url ||= host.media.path
-    URI.parse(url.gsub('$arch',    host.architecture.name).
-                  gsub('$major',   host.os.major).
-                  gsub('$minor',   host.os.minor).
-                  gsub('$version', [ host.os.major, host.os.minor ].compact.join('.'))
+    media_vars_to_uri(url, host.architecture.name, host.os)
+  end
+
+  def media_vars_to_uri (url, arch, os)
+    URI.parse(url.gsub('$arch',  arch).
+              gsub('$major',  os.major).
+              gsub('$minor',  os.minor).
+              gsub('$version', [os.major, os.minor ].compact.join('.'))
              ).normalize
   end
 
@@ -63,6 +67,17 @@ class Operatingsystem < ActiveRecord::Base
     to_label
   end
 
+  # sets the prefix for the tfp files based on the os / arch combination
+  def pxe_prefix(arch)
+    "boot/#{to_s}-#{arch}".gsub(" ","-")
+  end
+
+  def pxe_files(media, arch)
+    boot_files_uri(media, arch).collect do |img|
+      { pxe_prefix(arch).to_sym => img.to_s}
+    end
+  end
+
   def as_json(options={})
     {:operatingsystem => {:name => to_s, :id => id}}
   end
@@ -84,4 +99,8 @@ class Operatingsystem < ActiveRecord::Base
     self.release_name.downcase! unless defined?(Rake) or release_name.nil? or release_name.empty?
   end
 
+  def boot_files_uri(media = nil , architecture = nil)
+    "Abstract"
+  end
+
 end
diff --git a/app/models/redhat.rb b/app/models/redhat.rb
index ce9f3e2..234dbf8 100644
--- a/app/models/redhat.rb
+++ b/app/models/redhat.rb
@@ -1,4 +1,8 @@
 class Redhat < Operatingsystem
+
+  PXEDIR   = "images/pxeboot"
+  PXEFILES = {:kernel => "vmlinuz", :initrd => "initrd.img"}
+
   # outputs kickstart installation media based on the media type (NFS or URL)
   # it also convert the $arch string to the current host architecture
   def mediapath host
@@ -43,4 +47,16 @@ class Redhat < Operatingsystem
     Operatingsystem
   end
 
+  def boot_files_uri(media, architecture)
+    raise "invalid media for #{to_s}" unless medias.include?(media)
+    raise "invalid architecture for #{to_s}" unless architectures.include?(architecture)
+    PXEFILES.values.collect do |img|
+      URI.parse("#{media_vars_to_uri(media.path, architecture.name, self)}/#{PXEDIR}/#{img}").normalize
+    end
+  end
+
+  def pxe_type
+    "kickstart"
+  end
+
 end
diff --git a/app/views/unattended/pxe_kickstart_config.erb b/app/views/unattended/pxe_kickstart_config.erb
new file mode 100644
index 0000000..8b0cf96
--- /dev/null
+++ b/app/views/unattended/pxe_kickstart_config.erb
@@ -0,0 +1,4 @@
+default linux
+label linux
+kernel <%= @kernel %>
+append initrd=<%= @initrd %> ks=<%= foreman_url("kickstart")%> ksdevice=bootif network kssendmac
diff --git a/config/routes.rb b/config/routes.rb
index e2c2d90..7498fe8 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -9,7 +9,7 @@ ActionController::Routing::Routes.draw do |map|
     :requirements => {:id => /[^\/]+/},
     :member => { :report => :get, :reports => :get, :clone => :get,
       :environment_selected => :post, :architecture_selected => :post, :os_selected => :post,
-      :storeconfig_klasses => :get, :externalNodes => :get, :setBuild => :get, :cancelBuild => :get, :puppetrun => :get, :facts => :get },
+      :storeconfig_klasses => :get, :externalNodes => :get, :setBuild => :get, :cancelBuild => :get, :puppetrun => :get, :facts => :get, :pxe_config => :get },
     :collection => { :show_search => :get, :multiple_actions => :get, :multiple_parameters => :get,
       :update_multiple_parameters => :post, :save_checkbox => :post, :select_multiple_hostgroup => :get,
       :update_multiple_hostgroup => :post, :select_multiple_environment => :get, :update_multiple_environment => :post,
@@ -24,7 +24,7 @@ ActionController::Routing::Routes.draw do |map|
   map.resources :lookup_keys
   map.connect   "/lookup", :controller => "lookup_keys", :action => "q"
   map.resources :domains, :requirements => {:id => /[^\/]+/}
-  map.resources :operatingsystems
+  map.resources :operatingsystems, :member => {:bootfiles => :get}
   map.resources :medias
   map.resources :models
   map.resources :architectures
-- 
1.7.2.3

