razor-x/config_curator

View on GitHub
lib/config_curator/unit.rb

Summary

Maintainability
A
0 mins
Test Coverage
require 'logger'
require 'socket'

module ConfigCurator
  # A unit is the base class for a type of configuration
  # that should be installed.
  # All units must specify a {#source} and a {#destination}.
  class Unit
    include Utils

    # Error if the unit will fail to install.
    class InstallFailed < RuntimeError; end

    attr_accessor :logger, :source, :destination, :hosts, :packages

    # Default {#options}.
    DEFAULT_OPTIONS = {
      # Unit installed relative to this path.
      root: Dir.home,

      # Automatically uninstall units that would not be installed.
      uninstall: false,

      # Package tool to use. See #package_lookup.
      package_tool: nil
    }

    def initialize(options: {}, logger: nil)
      self.options options
      self.logger = logger unless logger.nil?
    end

    # Uses {DEFAULT_OPTIONS} as initial value.
    # @param options [Hash] merged with current options
    # @return [Hash] current options
    def options(options = {})
      @options ||= DEFAULT_OPTIONS
      @options = @options.merge options
    end

    # Logger instance to use.
    # @return [Logger] logger instance
    def logger
      @logger ||= Logger.new($stdout).tap do |log|
        log.progname = self.class.name
      end
    end

    # Full path to source.
    # @return [String] expanded path to source
    def source_path
      File.expand_path source unless source.nil?
    end

    # Full path to destination.
    # @return [String] expanded path to destination
    def destination_path
      File.expand_path File.join(options[:root], destination) unless destination.nil?
    end

    # Unit will be installed on these hosts.
    # If empty, installed on any host.
    # @return [Array] list of hostnames
    def hosts
      @hosts ||= []
    end

    # Unit installed only if listed packages are installed.
    # @return [Array] list of package names
    def packages
      @packages ||= []
    end

    # A {PackageLookup} object for this unit.
    def package_lookup
      @package_lookup ||= PackageLookup.new tool: options[:package_tool]
    end

    # Uninstalls the unit.
    # @return [Boolean] if the unit was uninstalled
    def uninstall(force: false)
      return true if uninstall? || force
      false
    end

    # Checks if the unit should be uninstalled.
    # @return [Boolean] if the unit should be uninstalled
    def uninstall?
      return true if !install? && options[:uninstall]
      false
    end

    # Installs the unit.
    # @return [Boolean] if the unit was installed
    def install
      return false unless install?
      true
    end

    # Checks if the unit should be installed.
    # @return [Boolean] if the unit should be installed
    def install?
      return false unless allowed_host?
      return false unless packages_installed?
      true
    end

    # Checks if the unit should be installed on this host.
    # @return [Boolean] if the hostname is in {#hosts}
    def allowed_host?
      return true if hosts.empty?
      hosts.include? hostname
    end

    # Checks if the packages required for this unit are installed.
    # @return [Boolean] if the packages in {#packages} are installed
    def packages_installed?
      packages.map(&method(:pkg_exists?)).delete_if { |e| e }.empty?
    end

    private

    # @return [String] the machine hostname
    def hostname
      Socket.gethostname
    end

    # @return [Boolean] if the package exists on the system
    def pkg_exists?(pkg)
      package_lookup.installed? pkg
    end
  end
end