class Chef::Provider::Package::Homebrew

Public Instance Methods

available_version(i) click to toggle source

Packages (formula) available to install should have a “stable” version, per the Homebrew project's acceptable formula documentation, so we will rely on that being the case. Older implementations of this provider in the homebrew cookbook would fall back to +brew_info+, but the schema has changed, and homebrew is a constantly rolling forward project.

github.com/Homebrew/homebrew/wiki/Acceptable-Formulae#stable-versions

@param [String] package name

@returns [String] package version

# File lib/chef/provider/package/homebrew.rb, line 172
def available_version(i)
  p_data = package_info(i)

  # nothing is available
  return nil if p_data.empty?

  p_data["versions"]["stable"]
end
brew_cmd_output(*command, **options) click to toggle source
# File lib/chef/provider/package/homebrew.rb, line 181
def brew_cmd_output(*command, **options)
  homebrew_uid = find_homebrew_uid(new_resource.respond_to?(:homebrew_user) && new_resource.homebrew_user)
  homebrew_user = Etc.getpwuid(homebrew_uid)

  logger.trace "Executing 'brew #{command.join(" ")}' as user '#{homebrew_user.name}'"

  # allow the calling method to decide if the cmd should raise or not
  # brew_info uses this when querying out available package info since a bad
  # package name will raise and we want to surface a nil available package so that
  # the package provider can magically handle that
  shell_out_cmd = options[:allow_failure] ? :shell_out : :shell_out!

  # FIXME: this 1800 second default timeout should be deprecated
  output = send(shell_out_cmd, "brew", *command, timeout: 1800, user: homebrew_uid, environment: { "HOME" => homebrew_user.dir, "RUBYOPT" => nil, "TMPDIR" => nil })
  output.stdout.chomp
end
brew_info() click to toggle source

We implement a querying method that returns the JSON-as-Hash data for a formula per the Homebrew documentation. Previous implementations of this provider in the homebrew cookbook performed a bit of magic with the load path to get this information, but that is not any more robust than using the command-line interface that returns the same thing.

docs.brew.sh/Querying-Brew

@returns [Hash] a hash of package information where the key is the package name

# File lib/chef/provider/package/homebrew.rb, line 93
def brew_info
  @brew_info ||= begin
    command_array = ["info", "--json=v1"].concat package_name_array
    # convert the array of hashes into a hash where the key is the package name

    cmd_output = brew_cmd_output(command_array, allow_failure: true)

    if cmd_output.empty?
      # we had some kind of failure so we need to iterate through each package to find them
      package_name_array.each_with_object({}) do |package_name, hsh|
        cmd_output = brew_cmd_output("info", "--json=v1", package_name, allow_failure: true)
        if cmd_output.empty?
          hsh[package_name] = {}
        else
          json = Chef::JSONCompat.from_json(cmd_output).first
          hsh[json["name"]] = json
        end
      end
    else
      Hash[Chef::JSONCompat.from_json(cmd_output).collect { |pkg| [pkg["name"], pkg] }]
    end
  end
end
candidate_version() click to toggle source
# File lib/chef/provider/package/homebrew.rb, line 44
def candidate_version
  package_name_array.map do |package_name|
    available_version(package_name)
  end
end
get_current_versions() click to toggle source
# File lib/chef/provider/package/homebrew.rb, line 50
def get_current_versions
  package_name_array.map do |package_name|
    installed_version(package_name)
  end
end
install_package(names, versions) click to toggle source
# File lib/chef/provider/package/homebrew.rb, line 56
def install_package(names, versions)
  brew_cmd_output("install", options, names.compact)
end
installed_version(i) click to toggle source

Some packages (formula) are “keg only” and aren't linked, because multiple versions installed can cause conflicts. We handle this by using the last installed version as the “current” (as in latest). Otherwise, we will use the version that brew thinks is linked as the current version.

@param [String] package name

@returns [String] package version

# File lib/chef/provider/package/homebrew.rb, line 145
def installed_version(i)
  p_data = package_info(i)

  if p_data["keg_only"]
    if p_data["installed"].empty?
      nil
    else
      p_data["installed"].last["version"]
    end
  else
    p_data["linked_keg"]
  end
end
load_current_resource() click to toggle source
# File lib/chef/provider/package/homebrew.rb, line 35
def load_current_resource
  @current_resource = Chef::Resource::HomebrewPackage.new(new_resource.name)
  current_resource.package_name(new_resource.package_name)
  current_resource.version(get_current_versions)
  logger.trace("#{new_resource} current package version(s): #{current_resource.version}") if current_resource.version

  current_resource
end
package_info(package_name) click to toggle source

Return the package information given a package name or package alias

@param [String] name_or_alias The name of the package or its alias

@return [Hash] Package information

# File lib/chef/provider/package/homebrew.rb, line 124
def package_info(package_name)
  # return the package hash if it's in the brew info hash
  return brew_info[package_name] if brew_info[package_name]

  # check each item in the hash to see if we were passed an alias
  brew_info.each_value do |p|
    return p if p["full_name"] == package_name || p["aliases"].include?(package_name)
  end

  {}
end
purge_package(names, versions) click to toggle source

Homebrew doesn't really have a notion of purging, do a “force remove”

# File lib/chef/provider/package/homebrew.rb, line 79
def purge_package(names, versions)
  brew_cmd_output("uninstall", "--force", options, names.compact)
end
remove_package(names, versions) click to toggle source
# File lib/chef/provider/package/homebrew.rb, line 74
def remove_package(names, versions)
  brew_cmd_output("uninstall", options, names.compact)
end
upgrade_package(names, versions) click to toggle source

upgrades are a bit harder in homebrew than other package formats. If you try to brew upgrade a package that isn't installed it will fail so if a user specifies the action of upgrade we need to figure out which packages need to be installed and which packages can be upgrades. We do this by checking if brew_info has an entry via the installed_version helper.

# File lib/chef/provider/package/homebrew.rb, line 65
def upgrade_package(names, versions)
  # @todo when we no longer support Ruby 2.6 this can be simplified to be a .filter_map
  upgrade_pkgs = names.select { |x| x if installed_version(x) }.compact
  install_pkgs = names.select { |x| x unless installed_version(x) }.compact

  brew_cmd_output("upgrade", options, upgrade_pkgs) unless upgrade_pkgs.empty?
  brew_cmd_output("install", options, install_pkgs) unless install_pkgs.empty?
end