puppetlabs/facter-ng

View on GitHub
lib/resolvers/partitions.rb

Summary

Maintainability
A
25 mins
Test Coverage
# frozen_string_literal: true

module Facter
  module Resolvers
    class Partitions < BaseResolver
      @semaphore = Mutex.new
      @fact_list ||= {}
      BLOCK_PATH = '/sys/block'
      BLOCK_SIZE = 512

      class << self
        private

        def post_resolve(fact_name)
          @fact_list.fetch(fact_name) { read_partitions(fact_name) }
        end

        def read_partitions(fact_name)
          @fact_list[:partitions] = {}
          return {} unless File.readable?(BLOCK_PATH)

          block_devices = Dir.entries(BLOCK_PATH).reject { |dir| dir =~ /^\.+/ }
          block_devices.each do |block_device|
            block_path = "#{BLOCK_PATH}/#{block_device}"
            if File.directory?("#{block_path}/device")
              extract_from_device(block_path)
            elsif File.directory?("#{block_path}/dm")
              extract_from_dm(block_path)
            elsif File.directory?("#{block_path}/loop")
              extract_from_loop(block_path)
            end
          end
          @fact_list[fact_name]
        end

        def extract_from_device(block_path)
          subdirs = browse_subdirectories(block_path)
          subdirs.each do |subdir|
            name = "/dev/#{subdir.split('/').last}"
            populate_partitions(name, subdir)
          end
        end

        def extract_from_dm(block_path)
          map_name = Util::FileHelper.safe_read("#{block_path}/dm/name").chomp
          if map_name.empty?
            populate_partitions("/dev#{block_path}", block_path)
          else
            populate_partitions("/dev/mapper/#{map_name}", block_path)
          end
        end

        def extract_from_loop(block_path)
          backing_file = Util::FileHelper.safe_read("#{block_path}/loop/backing_file").chomp
          if backing_file.empty?
            populate_partitions("/dev#{block_path}", block_path)
          else
            populate_partitions("/dev#{block_path}", block_path, backing_file)
          end
        end

        def populate_partitions(partition_name, block_path, backing_file = nil)
          @fact_list[:partitions][partition_name] = {}
          size_bytes = Util::FileHelper.safe_read("#{block_path}/size", '0')
                                       .chomp.to_i * BLOCK_SIZE
          info_hash = { size_bytes: size_bytes,
                        size: Facter::FactsUtils::UnitConverter.bytes_to_human_readable(size_bytes),
                        backing_file: backing_file }
          info_hash.merge!(populate_from_blkid(partition_name))
          @fact_list[:partitions][partition_name] = info_hash.reject { |_key, value| value.nil? }
        end

        def populate_from_blkid(partition_name)
          return {} unless blkid_command?

          @blkid_content ||= execute_and_extract_blkid_info
          return {} unless @blkid_content[partition_name]

          filesys = @blkid_content[partition_name]['TYPE']
          uuid = @blkid_content[partition_name]['UUID']
          label = @blkid_content[partition_name]['LABEL']
          part_uuid = @blkid_content[partition_name]['PARTUUID']
          part_label = @blkid_content[partition_name]['PARTLABEL']
          { filesystem: filesys, uuid: uuid, label: label, partuuid: part_uuid, partlabel: part_label }
        end

        def blkid_command?
          return @blkid_exists unless @blkid_exists.nil?

          output = Facter::Core::Execution.execute('which blkid', logger: log)

          @blkid_exists = !output.empty?
        end

        def execute_and_extract_blkid_info
          stdout = Facter::Core::Execution.execute('blkid', logger: log)
          output_hash = Hash[*stdout.split(/^([^:]+):/)[1..-1]]
          output_hash.each do |key, value|
            output_hash[key] = Hash[*value.delete('"').chomp.rstrip.split(/ ([^= ]+)=/)[1..-1]]
          end
        end

        def browse_subdirectories(path)
          dirs = Dir[File.join(path, '**', '*')].select { |p| File.directory? p }
          dirs.select { |subdir| subdir.split('/').last.include?(path.split('/').last) }.reject(&:nil?)
        end
      end
    end
  end
end