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

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

Instance Attribute Summary

Attributes inherited from Chef::Provider

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

Instance Method Summary collapse

Methods included from Mixin::Homebrew

#find_homebrew_uid, #find_homebrew_username, #homebrew_bin_path

Methods inherited from Chef::Provider::Package

#as_array, #check_resource_semantics!, #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_description, action_descriptions, #action_nothing, #check_resource_semantics!, #cleanup_after_converge, #compile_and_converge_action, #converge_by, #converge_if_changed, #cookbook_name, #description, #events, include_resource_dsl?, include_resource_dsl_module, #initialize, #introduced, #load_after_resource, #node, #process_resource_requirements, provides, provides?, #recipe_name, #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::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 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 DSL::Secret

#default_secret_config, #default_secret_service, #secret, #with_secret_config, #with_secret_service

Methods included from DSL::RenderHelpers

#render_json, #render_toml, #render_yaml

Methods included from DSL::ReaderHelpers

#parse_file, #parse_json, #parse_toml, #parse_yaml

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 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

Methods included from DSL::Compliance

#include_input, #include_profile, #include_waiver

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

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

Parameters:



180
181
182
183
184
185
186
187
# File 'lib/chef/provider/package/homebrew.rb', line 180

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



189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/chef/provider/package/homebrew.rb', line 189

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 '#{homebrew_bin_path} #{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!

  output = send(shell_out_cmd, homebrew_bin_path, *command, user: homebrew_uid, login: true, 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.

docs.brew.sh/Querying-Brew



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/chef/provider/package/homebrew.rb', line 101

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



53
54
55
56
57
# File 'lib/chef/provider/package/homebrew.rb', line 53

def candidate_version
  package_name_array.map do |package_name|
    available_version(package_name)
  end
end

#define_resource_requirementsObject



44
45
46
47
48
49
50
51
# File 'lib/chef/provider/package/homebrew.rb', line 44

def define_resource_requirements
  super

  requirements.assert(:all_actions) do |a|
    a.assertion { !new_resource.environment }
    a.failure_message Chef::Exceptions::Package, "The environment property is not supported for package resources on this platform"
  end
end

#get_current_versionsObject



59
60
61
62
63
# File 'lib/chef/provider/package/homebrew.rb', line 59

def get_current_versions
  package_name_array.map do |package_name|
    installed_version(package_name)
  end
end

#install_package(names, versions) ⇒ Object



65
66
67
# File 'lib/chef/provider/package/homebrew.rb', line 65

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:



153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/chef/provider/package/homebrew.rb', line 153

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



132
133
134
135
136
137
138
139
140
141
142
# File 'lib/chef/provider/package/homebrew.rb', line 132

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”



87
88
89
# File 'lib/chef/provider/package/homebrew.rb', line 87

def purge_package(names, versions)
  brew_cmd_output("uninstall", "--force", options, names.compact)
end

#remove_package(names, versions) ⇒ Object



82
83
84
# File 'lib/chef/provider/package/homebrew.rb', line 82

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.



74
75
76
77
78
79
80
# File 'lib/chef/provider/package/homebrew.rb', line 74

def upgrade_package(names, versions)
  upgrade_pkgs = names.filter_map { |x| x if installed_version(x) }
  install_pkgs = names.filter_map { |x| x unless installed_version(x) }

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