Skip to content

Instantly share code, notes, and snippets.

@jcpowermac
Created July 2, 2014 20:11
Show Gist options
  • Select an option

  • Save jcpowermac/a69eb795c7ee01bdca38 to your computer and use it in GitHub Desktop.

Select an option

Save jcpowermac/a69eb795c7ee01bdca38 to your computer and use it in GitHub Desktop.
require "fileutils"
require "digest/sha2"
module ProjectHanlon
module ImageService
# Base image abstract
class Base < ProjectHanlon::Object
MOUNT_COMMAND = (Process::uid == 0 ? "mount" : "sudo -n mount")
UMOUNT_COMMAND = (Process::uid == 0 ? "umount" : "sudo -n umount")
ARCHIVE_COMMAND = "fuseiso"
ARCHIVE_UMOUNT_COMMAND = "fusermount"
attr_accessor :filename
attr_accessor :description
attr_accessor :size
attr_accessor :verification_hash
attr_accessor :path_prefix
attr_accessor :hidden
def initialize(hash)
super()
@path_prefix = "base"
@_namespace = :images
@noun = "image"
@description = "Image Base"
@hidden = true
from_hash(hash) unless hash == nil
end
def set_lcl_image_path(lcl_image_path)
@_lcl_image_path = lcl_image_path + "/" + @path_prefix
end
# Used to add an image to the service
# Within each child class the methods are overridden for that child template
def add(src_image_path, lcl_image_path, extra)
set_lcl_image_path(lcl_image_path)
begin
create_imagepath_success = false
create_mount_success = false
# Get full path
fullpath = File.expand_path(src_image_path)
# Get filename
@filename = File.basename(fullpath)
logger.debug "fullpath: #{fullpath}"
logger.debug "filename: #@filename"
logger.debug "mount path: #{mount_path}"
#handler = archive_handler?
# Make sure file exists
return cleanup_on_failure(create_mount_success, create_imagepath_success, "File '#{fullpath}' does not exist") unless File.exist?(fullpath)
# Make sure it has an .iso extension
return cleanup_on_failure(create_mount_success, create_imagepath_success, "File '#{fullpath}' is not an ISO") if @filename[-4..-1] != ".iso"
# Determine if there is an existing image path for iso
if is_image_path?
## Remove if there is
remove_dir_completely(image_path)
end
## Create image path
unless create_image_path
logger.error "Cannot create image path: '#{image_path}'"
return cleanup_on_failure(create_mount_success, create_imagepath_success, "Cannot create image path: '#{image_path}'")
end
## if we reach here the creating of the image path is true
create_imagepath_success = true
create_mount_success = mount(fullpath)
# unless create_mount_success
# logger.error "Could not mount '#{fullpath}' on '#{mount_path}'"
# return cleanup_on_failure(create_mount_success, create_imagepath_success, "Could not mount '#{fullpath}' on '#{mount_path}'",handler)
# end
# Copying mounted iso image to image_path
FileUtils.cp_r(mount_path + "/.", image_path)
unless verification
return cleanup_on_failure(create_mount_success, create_imagepath_success,"Image copy failed verification: #{@verification_hash}")
end
rescue => e
logger.error e.message
return cleanup_on_failure(create_mount_success, create_imagepath_success, e.message)
end
umount
[true, '']
end
# Used to remove an image to the service
# Within each child class the methods are overridden for that child template
def remove(lcl_image_path)
set_lcl_image_path(lcl_image_path) unless @_lcl_image_path != nil
remove_dir_completely(image_path)
!File.directory?(image_path)
end
# Verify diff between mount / image paths
# For speed/flexibility reasons we just verify all files exists and not their contents
def verification
@verification_hash = get_dir_hash(image_path)
src_hash = get_dir_hash(mount_path)
unless src_hash == @verification_hash
logger.error "Image copy failed verification: #{@verification_hash} <> #{src_hash}"
return false
end
return true
end
# if a non-root method exists use it to extract the ISO,
# if not fall back to mount and/or sudo mount
# sudo -n
def archive_handler?
if !(cmd_path = `which #{ARCHIVE_COMMAND}`.strip).empty? && File.executable?(cmd_path)
true
elsif `#{MOUNT_COMMAND}` && $? == 0
false
else
raise "Neither #{ARCHIVE_COMMAND} or #{MOUNT_COMMAND} was available for extracting the ISO."
end
end
def mount(src_image_path)
FileUtils.mkpath(mount_path) unless File.directory?(mount_path)
`#{ARCHIVE_COMMAND} #{src_image_path} #{mount_path} 2> /dev/null`
if $? != 0
`#{MOUNT_COMMAND} -o loop #{src_image_path} #{mount_path} 2> /dev/null`
if $? != 0
cleanup_on_failure(false, true, "Could not mount '#{fullpath}' on '#{mount_path}'")
raise "Neither #{ARCHIVE_COMMAND} or #{MOUNT_COMMAND} was available for extracting the ISO."
end
end
true
# if handler
# `#{ARCHIVE_COMMAND} #{src_image_path} #{mount_path} 2> /dev/null`
# else
# `#{MOUNT_COMMAND} -o loop #{src_image_path} #{mount_path} 2> /dev/null`
# end
#
# if $? == 0
# logger.debug "mounted: #{src_image_path} on #{mount_path}"
# true
# else
# logger.debug "could not mount: #{src_image_path} on #{mount_path}"
# remove_dir_completely(mount_path)
# false
# end
end
# Used to verify an image within the filesystem (local/remote/possible Glance)
# Within each child class the methods are overridden for that child emplate
def verify(lcl_image_path)
set_lcl_image_path(lcl_image_path) unless @_lcl_image_path != nil
get_dir_hash(image_path) == @verification_hash
end
def image_path
@_lcl_image_path + "/" + @uuid
end
def is_mounted?(src_image_path)
mounts.each do
|mount|
return true if mount[0] == src_image_path && mount[1] == mount_path
end
false
end
def umount
`#{ARCHIVE_UMOUNT_COMMAND} -u #{mount_path} 2> /dev/null`
if $? != 0
`#{UMOUNT_COMMAND} #{mount_path} 2> /dev/null`
if $? != 0
cleanup_on_failure(true, true, "Could not mount '#{fullpath}' on '#{mount_path}'")
raise "Neither #{ARCHIVE_UMOUNT_COMMAND} or #{UMOUNT_COMMAND} was available for unmounting the ISO."
else
return :mount
end
end
:fuseiso
# if handler
# `#{ARCHIVE_UMOUNT_COMMAND} -u #{mount_path} 2> /dev/null`
# else
# `#{UMOUNT_COMMAND} #{mount_path} 2> /dev/null`
# end
#
# if $? == 0
# logger.debug "unmounted: #{mount_path}"
# remove_dir_completely(mount_path)
# true
# else
# logger.debug "could not unmount: #{mount_path}"
# false
# end
end
def mounts
`#{MOUNT_COMMAND}`.split("\n").map! {|x| x.split("on")}.map! {|x| [x[0],x[1].split(" ")[0]]}
end
## cleanup_on_failure method, based on arguments will unmount the iso,
## delete the mount directory and/or remove the image directory.
## If the image directory is removed a exception will be raised to inform
## the client of the error.
def cleanup_on_failure(do_unmount,do_image_delete,errormsg)
# unmount, if mounted
# remove directory if created
logger.error "Error: #{errormsg}"
if do_unmount
umount
end
if do_image_delete
remove_dir_completely(image_path)
raise "Deleted Image Directory, Image Path: #{image_path}, errormsg: #{errormsg}"
end
return [false,errormsg]
end
def mount_path
"#{$temp_path}/#{@uuid}"
end
def is_image_path?
File.directory?(image_path)
end
def create_image_path
FileUtils.mkpath(image_path)
end
def remove_dir_completely(path)
if File.directory?(path)
FileUtils.rm_r(path, :force => true)
else
true
end
end
def get_dir_hash(dir)
logger.debug "Generating hash for path: #{dir}"
files_string = Dir.glob("#{dir}/**/*").map {|x| x.sub("#{dir}/","")}.sort.join("\n")
Digest::SHA2.hexdigest(files_string)
end
def print_header
return "UUID", "Type", "ISO Filename", "Path", "Status"
end
def print_items
set_lcl_image_path(ProjectHanlon.config.image_path)
success, message = verify(@_lcl_image_path)
return @uuid, @description, @filename, image_path.to_s, "#{success ? "Valid".green : "Broken/Missing".red}"
end
def print_item_header
return "UUID", "Type", "ISO Filename", "Path", "Status"
end
def print_item
set_lcl_image_path(ProjectHanlon.config.image_path)
success, message = verify(@_lcl_image_path)
return @uuid, @description, @filename, image_path.to_s, "#{success ? "Valid".green : "Broken/Missing".red}"
end
def line_color
:white_on_black
end
def header_color
:red_on_black
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment