Class: Chef::Provider::Package::Homebrew

Inherits:
Chef::Provider::Package show all
Includes:
Mixin::HomebrewUser
Defined in:
lib/chef/provider/package/homebrew.rb

Instance Attribute Summary

Attributes inherited from Chef::Provider

#action, #after_resource, #current_resource, #logger, #new_resource, #recipe_name, #run_context

Instance Method Summary collapse

Methods included from Mixin::HomebrewUser

#find_homebrew_uid, #find_homebrew_username

Methods inherited from Chef::Provider::Package

#as_array, #check_resource_semantics!, #define_resource_requirements, #expand_options, #have_any_matching_version?, #initialize, #lock_package, #multipackage_api_adapter, #options, #package_locked, #prepare_for_installation, #preseed_package, #reconfig_package, #removing_package?, #target_version_already_installed?, #unlock_package, #version_compare, #version_equals?, #version_requirement_satisfied?

Methods included from Mixin::SubclassDirective

#subclass_directive

Methods inherited from Chef::Provider

action, #action_nothing, #check_resource_semantics!, #cleanup_after_converge, #compile_and_converge_action, #converge_by, #converge_if_changed, #cookbook_name, #define_resource_requirements, #description, #events, include_resource_dsl?, include_resource_dsl_module, #initialize, #introduced, #load_after_resource, #node, #process_resource_requirements, provides, provides?, #requirements, #resource_collection, #resource_updated?, #run_action, #set_updated_status, supports?, use, use_inline_resources, #validate_required_properties!, #whyrun_mode?, #whyrun_supported?

Methods included from Mixin::LazyModuleInclude

#descendants, #include, #included

Methods included from Mixin::PowershellOut

#powershell_out, #powershell_out!

Methods included from Mixin::WindowsArchitectureHelper

#assert_valid_windows_architecture!, #disable_wow64_file_redirection, #forced_32bit_override_required?, #is_i386_process_on_x86_64_windows?, #node_supports_windows_architecture?, #node_windows_architecture, #restore_wow64_file_redirection, #valid_windows_architecture?, #with_os_architecture, #wow64_architecture_override_required?, #wow64_directory

Methods included from Mixin::PowershellExec

#powershell_exec, #powershell_exec!

Methods included from DSL::Powershell

#ps_credential

Methods included from DSL::RegistryHelper

#registry_data_exists?, #registry_get_subkeys, #registry_get_values, #registry_has_subkeys?, #registry_key_exists?, #registry_value_exists?

Methods included from DSL::ChefVault

#chef_vault, #chef_vault_item, #chef_vault_item_for_environment

Methods included from DSL::DataQuery

#data_bag, #data_bag_item, #search, #tagged?

Methods included from EncryptedDataBagItem::CheckEncrypted

#encrypted?

Methods included from DSL::PlatformIntrospection

#older_than_win_2012_or_8?, #platform?, #platform_family?, #value_for_platform, #value_for_platform_family

Methods included from Mixin::Provides

#provided_as, #provides, #provides?

Methods included from Mixin::DescendantsTracker

#descendants, descendants, #direct_descendants, direct_descendants, #find_descendants_by_name, find_descendants_by_name, #inherited, store_inherited

Methods included from DSL::Recipe

#exec, #have_resource_class_for?, #resource_class_for

Methods included from DSL::Definitions

add_definition, #evaluate_resource_definition, #has_resource_definition?

Methods included from DSL::Resources

add_resource_dsl, remove_resource_dsl

Methods included from DSL::Cheffish

load_cheffish

Methods included from DSL::RebootPending

#reboot_pending?

Methods included from DSL::IncludeRecipe

#include_recipe, #load_recipe

Methods included from Mixin::NotifyingBlock

#notifying_block, #subcontext_block

Methods included from DSL::DeclareResource

#build_resource, #declare_resource, #delete_resource, #delete_resource!, #edit_resource, #edit_resource!, #find_resource, #find_resource!, #resources, #with_run_context

Constructor Details

This class inherits a constructor from Chef::Provider::Package

Instance Method Details

#available_version(i) ⇒ Object

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['version']+, but the schema has changed, and homebrew is a constantly rolling forward project.

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

Parameters:


172
173
174
175
176
177
178
179
# 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) ⇒ Object


181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# 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_infoObject

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.

https://docs.brew.sh/Querying-Brew


93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# 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_versionObject


44
45
46
47
48
# 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_versionsObject


50
51
52
53
54
# 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) ⇒ Object


56
57
58
# 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) ⇒ Object

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.

Parameters:


145
146
147
148
149
150
151
152
153
154
155
156
157
# 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_resourceObject


35
36
37
38
39
40
41
42
# 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) ⇒ Hash

Return the package information given a package name or package alias

Parameters:

  • name_or_alias (String)

    The name of the package or its alias

Returns:

  • (Hash)

    Package information


124
125
126
127
128
129
130
131
132
133
134
# 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) ⇒ Object

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


79
80
81
# 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) ⇒ Object


74
75
76
# 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) ⇒ Object

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.


65
66
67
68
69
70
71
72
# 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