Class: EC2::Platform::Linux::Image
- Inherits:
-
Object
- Object
- EC2::Platform::Linux::Image
- Defined in:
- lib/ec2/platform/linux/image.rb
Overview
This class encapsulate functionality to create an file loopback image from a volume. The image is created using dd. Sub-directories of the volume, including mounts of local filesystems, are copied to the image. Symbolic links are preserved.
Constant Summary collapse
- IMG_MNT =
'/mnt/img-mnt'
- EXCLUDES =
['/dev', '/media', '/mnt', '/proc', '/sys']
- DEFAULT_FSTAB =
EC2::Platform::Linux::Fstab::DEFAULT
- LEGACY_FSTAB =
EC2::Platform::Linux::Fstab::LEGACY
- BASE_UTILS =
[ 'modprobe', 'mount', 'umount', 'dd' ]
- PART_UTILS =
[ 'dmsetup', 'kpartx', 'losetup' ]
- CHROOT_UTILS =
[ 'grub' ]
- ROOT_DEVICE_REGEX =
———————————————————————# Assign an appropriate partition type. The current implementation will fail to bundle volumes that reside on devices whose partition schemes deviate from what is commonly available in EC2, namely a partitioned disk with the root file system residing on the first partition.
/^(\/dev\/(?:xvd|sd)(?:[a-z]|[a-c][a-z]|d[a-x]))[1]?$/
Instance Method Summary collapse
- #check_deps(part_type) ⇒ Object
- #check_kernel_parameters(conf) ⇒ Object
- #compare_hashes(a, b) ⇒ Object
-
#initialize(volume, image_filename, mb_image_size, exclude, includes, filter = true, fstab = nil, part_type = nil, arch = nil, script = nil, debug = false, grub_config = nil) ⇒ Image
constructor
Initialize the instance with the required parameters.
-
#is_disk_image? ⇒ Boolean
———————————————————————# Returns true if we are trying to build a valid disk image.
-
#make ⇒ Object
Create the loopback image file and copy volume to it.
-
#make_hash(array) ⇒ Object
——————————————————————–#.
- #remove_kernel(tokens) ⇒ Object
- #set_partition_type(input) ⇒ Object
-
#settle ⇒ Object
———————————————————————#.
- #tokenize(string) ⇒ Object
-
#verify_runtime(utils, chroot = nil) ⇒ Object
———————————————————————# Ensure we have the specified commonly-needed utils in the PATH.
Constructor Details
#initialize(volume, image_filename, mb_image_size, exclude, includes, filter = true, fstab = nil, part_type = nil, arch = nil, script = nil, debug = false, grub_config = nil) ⇒ Image
Initialize the instance with the required parameters.
-
volume The path to the volume to create the image file from.
-
image_filename The name of the image file to create.
-
mb_image_size The image file size in MB.
-
exclude List of directories to exclude.
-
debug Make extra noise.
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/ec2/platform/linux/image.rb', line 46 def initialize( volume, image_filename, mb_image_size, exclude, includes, filter = true, fstab = nil, part_type = nil, arch = nil, script = nil, debug = false, grub_config = nil ) @volume = volume @image_filename = image_filename @mb_image_size = mb_image_size @exclude = exclude @includes = includes @filter = filter @arch = arch || EC2::Platform::Linux::Uname.platform @script = script @fstab = nil @conf = grub_config @warnings = Array.new self.verify_runtime(BASE_UTILS) self.set_partition_type(part_type) # Cunning plan or horrible hack? # If :legacy is passed in as the fstab, we use the old v3 manifest's # device naming and fstab. if [:legacy, :default].include? fstab @fstab = fstab elsif not fstab.nil? @fstab = File.open(fstab).read() end @debug = debug # Exclude the temporary image mount point if it is under the volume # being bundled. if IMG_MNT.index( volume ) == 0 @exclude << IMG_MNT end end |
Instance Method Details
#check_deps(part_type) ⇒ Object
201 202 203 204 205 206 207 208 209 |
# File 'lib/ec2/platform/linux/image.rb', line 201 def check_deps(part_type) if part_type == EC2::Platform::PartitionType::MBR self.verify_runtime([ 'parted' ]) self.verify_runtime(CHROOT_UTILS, @volume) elsif part_type == EC2::Platform::PartitionType::GPT self.verify_runtime([ 'sgdisk' ]) self.verify_runtime(CHROOT_UTILS, @volume) end end |
#check_kernel_parameters(conf) ⇒ Object
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 |
# File 'lib/ec2/platform/linux/image.rb', line 126 def check_kernel_parameters(conf) cmdline = File.read('/proc/cmdline') cmdline_hash = make_hash(tokenize(cmdline)) default = nil File.readlines(conf).each do |line| match = line.match(/^default.*([\d+])/) if match default = match.captures[0] end end if not default STDERR.puts "Couldn't find default kernel designation in grub config. The resulting image may not boot." return end default = default.to_i kernels = File.readlines(conf).grep(/^\s*kernel/) kernel_line = kernels[default] kernel_line = remove_kernel(tokenize(kernel_line)) kernel_hash = make_hash(kernel_line) compare_hashes(cmdline_hash, kernel_hash) if not @warnings.empty? $stdout.puts "Found the following differences between your kernel " + "commandline and the grub configuration on the volume:" @warnings.each do |warning| $stdout.puts warning end $stdout.puts "Please verify that the kernel command line in " + "#{File.(conf)} is correct for your new AMI." end end |
#compare_hashes(a, b) ⇒ Object
102 103 104 105 106 107 108 109 110 |
# File 'lib/ec2/platform/linux/image.rb', line 102 def compare_hashes(a, b) a.each do |key,value| if not b.has_key?(key) @warnings.push "\t* Missing key '#{key}' with value '#{value}'" elsif b[key] != value @warnings.push "\t* Key '#{key}' value '#{b[key]}' differs from /proc/cmdline value '#{value}'" end end end |
#is_disk_image? ⇒ Boolean
———————————————————————# Returns true if we are trying to build a valid disk image
324 325 326 |
# File 'lib/ec2/platform/linux/image.rb', line 324 def is_disk_image? EC2::Platform::PartitionType.valid?(@part_type) end |
#make ⇒ Object
Create the loopback image file and copy volume to it.
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
# File 'lib/ec2/platform/linux/image.rb', line 165 def make begin puts( "Copying #{@volume} into the image file #{@image_filename}...") puts( 'Excluding: ' ) @exclude.each { |x| puts( "\t #{x}" ) } create_image_file format_image execute( 'sync' ) # Flush so newly formatted filesystem is ready to mount. mount_image copy_rec( @volume, IMG_MNT) update_fstab customize_image finalize_image ensure cleanup end end |
#make_hash(array) ⇒ Object
——————————————————————–#
92 93 94 95 96 97 98 99 100 |
# File 'lib/ec2/platform/linux/image.rb', line 92 def make_hash(array) hash = Hash.new array.each do |entry| # Split on the first '=' key, value = entry.split('=', 2) hash[key] = value || '' end hash end |
#remove_kernel(tokens) ⇒ Object
112 113 114 115 116 117 118 119 120 |
# File 'lib/ec2/platform/linux/image.rb', line 112 def remove_kernel(tokens) if tokens.include?('kernel') tokens.delete('kernel') # The kernel can receive optional arguments, drop those along with the kernel kernel_index = tokens.index{ |token| !token.include?('--') } tokens = tokens.drop(kernel_index + 1) end tokens end |
#set_partition_type(input) ⇒ Object
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 |
# File 'lib/ec2/platform/linux/image.rb', line 218 def set_partition_type(input) input ||= EC2::Platform::PartitionType::NONE if input == EC2::Platform::PartitionType::NONE # We are not doing anything interesting. Return early. puts('Not partitioning boot device.') @part_type = EC2::Platform::PartitionType::NONE return end # Verify that general partitioning utilities are present self.verify_runtime(PART_UTILS) value = input puts('Setting partition type to bundle "%s" with...' % [@volume]) if File.directory?(@volume) mtab = EC2::Platform::Linux::Mtab.load entry = mtab.entries[@volume] if entry # Volume is a mounted file system: # * Determine the mounted device # * Ensure device partition scheme is one that we understand # * Ensure device and it's container(if applicable) are block devices # * Determine the current partition type using parted if appropriate. device = entry.device root = nil if (match = ROOT_DEVICE_REGEX.match(device)) root = match[1] root = device unless File.exists?(root) # classic AMI with no partition table # Be paranoid. Bail unless the device and parent are block devices [device, root].each do |dev| unless File.blockdev?(dev) raise FatalError.new('Not a block device: %s' % [dev]) end end else raise FatalError.new('Non-standard volume device "%s"' % [device]) end if input == :auto self.verify_runtime([ 'parted' ]) puts('Auto-detecting partition type for "%s"' % [@volume]) cmd = "parted -s %s print|awk -F: '/^Partition Table/{print $2}'" % [root] value = evaluate(cmd).strip raise FatalError.new('Cannot determine partition type') if value.empty? puts('Partition label detected using parted: "%s"' % value) end else # Volume specified is possibly a file system root: # * Proceed cautiously using sane defaults if no partition type # has been provided. puts('Volume "%s" is not a mount point.' % [@volume]) value = EC2::Platform::PartitionType::GPT if input == :auto puts('Treating it as a file system root and using "%s"...' % [value]) end elsif File.blockdev?(@volume) # Volume specified is a block device: # * Not sure how we got here. # * We only support bundling of file system roots and not block # devices, so throw an exception. raise FatalError('Volume cannot be a block device "%s".' % [@volume]) else # Volume specified is not a file system (mounted or otherwise): # * Bail! raise FatalError.new('Cannot determine partition type of "%s"' % [@volume]) end if EC2::Platform::PartitionType.valid?(value) @part_type = value elsif value == 'msdos' # This is the parted label value reported for MBR partition tables. @part_type = EC2::Platform::PartitionType::MBR elsif value == 'loop' # This typically indicates that we have a bare partition that is not # part of a partition table. This is typically the case for pv amis. @part_type = EC2::Platform::PartitionType::NONE elsif value == 'gpt' @part_type = EC2::Platform::PartitionType::GPT elsif value if input == :auto # Somehow we failed to determine a partition type that we support raise FatalError.new('Could not determine a suitable partition type') else # User specified a format we currently do not support. Bail. raise FatalError.new('Unsupported partition table type %s' % input) end else raise FatalError.ne('Cannot determine partition type for %s' % [@volume]) end puts('Using partition type "%s"' % @part_type) self.check_deps(@part_type) end |
#settle ⇒ Object
———————————————————————#
312 313 314 315 316 317 318 319 320 |
# File 'lib/ec2/platform/linux/image.rb', line 312 def settle # Run sync and udevadm settle to quiet device. execute('sync||:') if File.executable?('/usr/sbin/udevsettle') execute('/usr/sbin/udevsettle||:') elsif File.executable?('/sbin/udevadm') execute('/sbin/udevadm settle||:') end end |
#tokenize(string) ⇒ Object
122 123 124 |
# File 'lib/ec2/platform/linux/image.rb', line 122 def tokenize(string) string.strip.split(/\s+/) end |
#verify_runtime(utils, chroot = nil) ⇒ Object
———————————————————————# Ensure we have the specified commonly-needed utils in the PATH.
186 187 188 189 190 191 192 193 194 195 196 197 198 199 |
# File 'lib/ec2/platform/linux/image.rb', line 186 def verify_runtime(utils, chroot = nil) unless ENV['PATH'] raise FatalError.new('PATH not set, cannot find needed utilities') end paths = ENV['PATH'].split(File::PATH_SEPARATOR) paths.map! { |path| File.join(chroot, path) } if chroot utils.each do |util| unless paths.any? { |dir| File.executable?(File.join(dir, util)) } raise FatalError.new("Required utility '%s' not found in PATH - is it installed?" % util) end end end |