Skip to content

Instantly share code, notes, and snippets.

@jcpowermac
Created July 2, 2014 21:18
Show Gist options
  • Select an option

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

Select an option

Save jcpowermac/0989986ddc5a92a98975 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?
logger.error "archive_handler: #{handler}"
# Make sure file exists
return cleanup_on_failure(create_mount_success, create_imagepath_success, "File '#{fullpath}' does not exist",handler) 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",handler) 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}'",handler)
end
## if we reach here the creating of the image path is true
create_imagepath_success = true
logger.error "before is_mounted?"
unless is_mounted?
create_mount_success = mount(handler, fullpath)
logger.error "after mount: #{create_mount_success}"
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
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}",handler)
end
rescue => e
logger.error e.message
return cleanup_on_failure(create_mount_success, create_imagepath_success, e.message,handler)
end
umount(handler)
[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?
cmd_path = `which #{ARCHIVE_COMMAND}`
test = `/usr/bin/fuseiso -h`
logger.error "fullpath test: #{test}"
logger.error "cmd_path: #{cmd_path}"
exec = File.executable?(cmd_path)
logger.error "File.executable?:#{exec} "
cmd_path_test = !(cmd_path = `which #{ARCHIVE_COMMAND}`.strip).empty?
logger.error "cmd_path_test: #{cmd_path_test}"
if !(cmd_path = `which #{ARCHIVE_COMMAND}`.strip).empty?
true
elsif `#{MOUNT_COMMAND}` && $? == 0
false
else
raise "Neither #{ARCHIVE_COMMAND} or #{MOUNT_COMMAND} was available for extracting the ISO."
end
end
def mount(handler, src_image_path)
FileUtils.mkpath(mount_path) unless File.directory?(mount_path)
logger.error "in mount - handler: #{handler}"
if handler
output = `#{ARCHIVE_COMMAND} #{src_image_path} #{mount_path} -d`
logger.error "fuseiso_mount_output: #{output}"
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.error "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?
mounts.each do
|mount|
return true if mount[1] == mount_path
end
false
end
def umount(handler)
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,handler)
# unmount, if mounted
# remove directory if created
logger.error "Error: #{errormsg}"
if do_unmount
umount(handler)
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
"/tmp/#{@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