sometimesfood/wright

View on GitHub
lib/wright/util/user.rb

Summary

Maintainability
A
35 mins
Test Coverage
require 'etc'

module Wright
  module Util
    # Various user utility functions.
    module User
      # Returns a user's uid.
      #
      # @param user [String, Integer] the user's name or uid
      #
      # @example
      #   Wright::Util::User.user_to_uid('root')
      #   # => 0
      #
      #   Wright::Util::User.user_to_uid(0)
      #   # => 0
      #
      # @return [Integer] the integer uid of the given user or nil if
      #   user was nil
      def self.user_to_uid(user)
        to_id(user, :user)
      end

      # Returns a group's gid.
      #
      # @param group [String, Integer] the group's name or gid
      #
      # @example
      #   Wright::Util::User.group_to_gid('root')
      #   # => 0
      #
      #   Wright::Util::User.group_to_gid(0)
      #   # => 0
      #
      # @return [Integer] the integer gid of the given group or nil if
      #   group was nil
      def self.group_to_gid(group)
        to_id(group, :group)
      end

      def self.to_id(object, type)
        raise ArgumentError unless [:group, :user].include?(type)
        return nil if object.nil?
        return object.to_i unless object.is_a?(String)
        type == :user ? Etc.getpwnam(object).uid : Etc.getgrnam(object).gid
      end
      private_class_method :to_id

      # Returns the next free uid in a range.
      #
      # @param uid_range [Range] the uid range
      #
      # @example
      #   Wright::Util::User.next_free_uid(1...500)
      #   # => 2
      #
      # @return [Integer] the next free uid
      # @raise [RuntimeError] if there are no free uids in the range
      def self.next_free_uid(uid_range)
        next_free_id(uid_range, :uid)
      end

      # Returns the next free gid in a range.
      #
      # @param gid_range [Range] the gid range
      #
      # @example
      #   Wright::Util::User.next_free_gid(1...500)
      #   # => 11
      #
      # @return [Integer] the next free gid
      # @raise [RuntimeError] if there are no free gids in the range
      def self.next_free_gid(gid_range)
        next_free_id(gid_range, :gid)
      end

      def self.next_free_id(id_range, id_type)
        iterator = id_iterator(id_type)
        used_ids = []
        iterator.call do |o|
          id = o.method(id_type).call
          used_ids << id if id_range.include?(id)
        end
        free_ids = id_range.to_a - used_ids
        raise "No free #{id_type} in range #{id_range}" if free_ids.empty?
        free_ids.min
      end
      private_class_method(:next_free_id)

      def self.id_iterator(id_type)
        raise ArgumentError unless [:uid, :gid].include?(id_type)
        id_type == :uid ? Etc.method(:passwd) : Etc.method(:group)
      end
      private_class_method :id_iterator

      # Returns a user entry.
      #
      # @param username [String] the name of the user
      #
      # @example
      #   Wright::Util::User.safe_getpwnam('this_user_does_not_exist')
      #   # => nil
      #
      # @return [Struct::Passwd, nil] the user entry or nil if the
      #   user does not exist
      def self.safe_getpwnam(username)
        Etc.getpwnam(username)
      rescue ArgumentError
        nil
      end

      # Returns a group entry.
      #
      # @param groupname [String] the name of the group
      #
      # @example
      #   Wright::Util::User.safe_getgrnam('this_group_does_not_exist')
      #   # => nil
      #
      # @return [Struct::Group, nil] the group entry or nil if the
      #   group does not exist
      def self.safe_getgrnam(groupname)
        Etc.getgrnam(groupname)
      rescue ArgumentError
        nil
      end
    end
  end
end