summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Smith <tsmith@chef.io>2020-04-17 17:54:29 -0700
committerGitHub <noreply@github.com>2020-04-17 17:54:29 -0700
commit6fcc359ce1c000a31ec9af61b48091060328617f (patch)
tree802b48b9a76f4ffd48ca27f75b8603e9ade73754
parent272bafca5a132aa79c804a160f04a091529b0396 (diff)
parentc25eb20b2b1e4676118d7583b7f17394c9deec41 (diff)
downloadchef-6fcc359ce1c000a31ec9af61b48091060328617f.tar.gz
Merge pull request #7982 from chef/multi_package
Add multipackage support to homebrew
-rw-r--r--lib/chef/provider/package/homebrew.rb148
-rw-r--r--lib/chef/resource/homebrew_package.rb2
-rw-r--r--spec/unit/provider/package/homebrew_spec.rb454
3 files changed, 387 insertions, 217 deletions
diff --git a/lib/chef/provider/package/homebrew.rb b/lib/chef/provider/package/homebrew.rb
index b9021ecb38..012bcdbb13 100644
--- a/lib/chef/provider/package/homebrew.rb
+++ b/lib/chef/provider/package/homebrew.rb
@@ -24,6 +24,8 @@ class Chef
class Provider
class Package
class Homebrew < Chef::Provider::Package
+ allow_nils
+ use_multipackage_api
provides :package, os: "darwin", override: true
provides :homebrew_package
@@ -31,49 +33,51 @@ class Chef
include Chef::Mixin::HomebrewUser
def load_current_resource
- self.current_resource = Chef::Resource::HomebrewPackage.new(new_resource.name)
+ @current_resource = Chef::Resource::HomebrewPackage.new(new_resource.name)
current_resource.package_name(new_resource.package_name)
- current_resource.version(current_installed_version)
- logger.trace("#{new_resource} current version is #{current_resource.version}") if current_resource.version
-
- @candidate_version = candidate_version
-
- logger.trace("#{new_resource} candidate version is #{@candidate_version}") if @candidate_version
+ current_resource.version(get_current_versions)
+ logger.trace("#{new_resource} current package version(s): #{current_resource.version}") if current_resource.version
current_resource
end
- def install_package(name, version)
- unless current_resource.version == version
- brew("install", options, name)
+ def candidate_version
+ package_name_array.map do |package_name|
+ available_version(package_name)
end
end
- def upgrade_package(name, version)
- current_version = current_resource.version
-
- if current_version.nil? || current_version.empty?
- install_package(name, version)
- elsif current_version != version
- brew("upgrade", options, name)
+ def get_current_versions
+ package_name_array.map do |package_name|
+ installed_version(package_name)
end
end
- def remove_package(name, version)
- if current_resource.version
- brew("uninstall", options, name)
- end
+ def install_package(names, versions)
+ brew_cmd_output("install", options, names.compact)
end
- # Homebrew doesn't really have a notion of purging, do a "force remove"
- def purge_package(name, version)
- if current_resource.version
- brew("uninstall", "--force", options, name)
- end
+ # 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.
+ 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
- def brew(*args)
- get_response_from_command("brew", *args)
+ def remove_package(names, versions)
+ brew_cmd_output("uninstall", options, names.compact)
+ end
+
+ # Homebrew doesn't really have a notion of purging, do a "force remove"
+ def purge_package(names, versions)
+ brew_cmd_output("uninstall", "--force", options, names.compact)
end
# We implement a querying method that returns the JSON-as-Hash
@@ -83,9 +87,50 @@ class Chef
# information, but that is not any more robust than using the
# command-line interface that returns the same thing.
#
- # https://github.com/Homebrew/homebrew/wiki/Querying-Brew
+ # https://docs.brew.sh/Querying-Brew
+ #
+ # @returns [Hash] a hash of package information where the key is the package name
def brew_info
- @brew_info ||= Chef::JSONCompat.from_json(brew("info", "--json=v1", new_resource.package_name)).first
+ @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
+
+ #
+ # 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
+ #
+ 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["aliases"].include?(package_name)
+ end
+
+ {}
end
# Some packages (formula) are "keg only" and aren't linked,
@@ -94,15 +139,20 @@ class Chef
# "current" (as in latest). Otherwise, we will use the version
# that brew thinks is linked as the current version.
#
- def current_installed_version
- if brew_info["keg_only"]
- if brew_info["installed"].empty?
+ # @param [String] package name
+ #
+ # @returns [String] package version
+ def installed_version(i)
+ p_data = package_info(i)
+
+ if p_data["keg_only"]
+ if p_data["installed"].empty?
nil
else
- brew_info["installed"].last["version"]
+ p_data["installed"].last["version"]
end
else
- brew_info["linked_keg"]
+ p_data["linked_keg"]
end
end
@@ -115,19 +165,33 @@ class Chef
# forward project.
#
# https://github.com/Homebrew/homebrew/wiki/Acceptable-Formulae#stable-versions
- def candidate_version
- brew_info["versions"]["stable"]
- end
+ #
+ # @param [String] package name
+ #
+ # @returns [String] package version
+ def available_version(i)
+ p_data = package_info(i)
- private
+ # nothing is available
+ return nil if p_data.empty?
- def get_response_from_command(*command)
+ p_data["versions"]["stable"]
+ end
+
+ 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 '#{command.join(" ")}' as user '#{homebrew_user.name}'"
+ 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 = shell_out!(*command, timeout: 1800, user: homebrew_uid, environment: { "HOME" => homebrew_user.dir, "RUBYOPT" => nil, "TMPDIR" => nil })
+ 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
diff --git a/lib/chef/resource/homebrew_package.rb b/lib/chef/resource/homebrew_package.rb
index 4ec7a20272..5bcba3ed56 100644
--- a/lib/chef/resource/homebrew_package.rb
+++ b/lib/chef/resource/homebrew_package.rb
@@ -33,7 +33,7 @@ class Chef
introduced "12.0"
property :homebrew_user, [ String, Integer ],
- description: "The name of the Homebrew owner to be used by #{Chef::Dist::PRODUCT} when executing a command."
+ description: "The name or uid of the Homebrew owner to be used by #{Chef::Dist::PRODUCT} when executing a command."
end
end
diff --git a/spec/unit/provider/package/homebrew_spec.rb b/spec/unit/provider/package/homebrew_spec.rb
index 101110ccc1..9975e72b02 100644
--- a/spec/unit/provider/package/homebrew_spec.rb
+++ b/spec/unit/provider/package/homebrew_spec.rb
@@ -1,6 +1,7 @@
#
# Author:: Joshua Timberman (<joshua@chef.io>)
# Copyright:: Copyright (c) Chef Software Inc.
+# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -19,171 +20,289 @@ require "spec_helper"
describe Chef::Provider::Package::Homebrew do
let(:node) { Chef::Node.new }
- let(:events) { double("Chef::Events").as_null_object }
- let(:logger) { double("Mixlib::Log::Child").as_null_object }
- let(:run_context) { double("Chef::RunContext", node: node, events: events, logger: logger) }
- let(:new_resource) { Chef::Resource::HomebrewPackage.new("emacs") }
- let(:current_resource) { Chef::Resource::HomebrewPackage.new("emacs") }
-
+ let(:new_resource) { Chef::Resource::HomebrewPackage.new(%w{emacs vim}) }
+ let(:current_resource) { Chef::Resource::HomebrewPackage.new("emacs, vim") }
let(:provider) do
+ node = Chef::Node.new
+ events = Chef::EventDispatch::Dispatcher.new
+ run_context = Chef::RunContext.new(node, {}, events)
Chef::Provider::Package::Homebrew.new(new_resource, run_context)
end
let(:homebrew_uid) { 1001 }
- let(:uninstalled_brew_info) do
- {
- "name" => "emacs",
- "homepage" => "http://www.gnu.org/software/emacs",
- "versions" => {
- "stable" => "24.3",
- "bottle" => false,
- "devel" => nil,
- "head" => nil,
- },
- "revision" => 0,
- "installed" => [],
- "linked_keg" => nil,
- "keg_only" => nil,
- "dependencies" => [],
- "conflicts_with" => [],
- "caveats" => nil,
- "options" => [],
- }
- end
-
- let(:installed_brew_info) do
- {
- "name" => "emacs",
- "homepage" => "http://www.gnu.org/software/emacs/",
- "versions" => {
- "stable" => "24.3",
- "bottle" => false,
- "devel" => nil,
- "head" => "HEAD",
- },
- "revision" => 0,
- "installed" => [{ "version" => "24.3" }],
- "linked_keg" => "24.3",
- "keg_only" => nil,
- "dependencies" => [],
- "conflicts_with" => [],
- "caveats" => "",
- "options" => [],
- }
- end
-
- let(:keg_only_brew_info) do
- {
- "name" => "emacs-kegger",
- "homepage" => "http://www.gnu.org/software/emacs/",
- "versions" => {
- "stable" => "24.3-keggy",
- "bottle" => false,
- "devel" => nil,
- "head" => "HEAD",
- },
- "revision" => 0,
- "installed" => [{ "version" => "24.3-keggy" }],
- "linked_keg" => nil,
- "keg_only" => true,
- "dependencies" => [],
- "conflicts_with" => [],
- "caveats" => "",
- "options" => [],
- }
- end
-
- let(:keg_only_uninstalled_brew_info) do
- {
- "name" => "emacs-kegger",
- "homepage" => "http://www.gnu.org/software/emacs/",
- "versions" => {
- "stable" => "24.3-keggy",
- "bottle" => false,
- "devel" => nil,
- "head" => "HEAD",
- },
- "revision" => 0,
- "installed" => [],
- "linked_keg" => nil,
- "keg_only" => true,
- "dependencies" => [],
- "conflicts_with" => [],
- "caveats" => "",
- "options" => [],
- }
+ let(:brew_cmd_output_data) { '[{"name":"emacs","full_name":"emacs","oldname":null,"aliases":[],"versioned_formulae":[],"desc":"GNU Emacs text editor","homepage":"https://www.gnu.org/software/emacs/","versions":{"stable":"26.3","devel":null,"head":"HEAD","bottle":true},"urls":{"stable":{"url":"https://ftp.gnu.org/gnu/emacs/emacs-26.3.tar.xz","tag":null,"revision":null}},"revision":0,"version_scheme":0,"bottle":{"stable":{"rebuild":0,"cellar":"/usr/local/Cellar","prefix":"/usr/local","root_url":"https://homebrew.bintray.com/bottles","files":{"catalina":{"url":"https://homebrew.bintray.com/bottles/emacs-26.3.catalina.bottle.tar.gz","sha256":"9ab33f4386ca5f7326a8c28da1324556ec990f682a7ca88641203da0b42dbdae"},"mojave":{"url":"https://homebrew.bintray.com/bottles/emacs-26.3.mojave.bottle.tar.gz","sha256":"8162a26246de7db44c53ea0d0ef0a806140318d19c69e8e5e33aa88ce7e823a8"},"high_sierra":{"url":"https://homebrew.bintray.com/bottles/emacs-26.3.high_sierra.bottle.tar.gz","sha256":"6a2629b6deddf99f81abb1990ecd6c87f0242a0eecbb6b6c2e4c3540e421d4c4"},"sierra":{"url":"https://homebrew.bintray.com/bottles/emacs-26.3.sierra.bottle.tar.gz","sha256":"2a47477e71766d7dd6b16c29ad5ba71817ed80d06212e3261ef3c776e7e9f5a2"}}}},"keg_only":false,"bottle_disabled":false,"options":[],"build_dependencies":["pkg-config"],"dependencies":["gnutls"],"recommended_dependencies":[],"optional_dependencies":[],"uses_from_macos":["libxml2","ncurses"],"requirements":[],"conflicts_with":[],"caveats":null,"installed":[],"linked_keg":null,"pinned":false,"outdated":false},{"name":"vim","full_name":"vim","oldname":null,"aliases":[],"versioned_formulae":[],"desc":"Vi \'workalike\' with many additional features","homepage":"https://www.vim.org/","versions":{"stable":"8.2.0550","devel":null,"head":"HEAD","bottle":true},"urls":{"stable":{"url":"https://github.com/vim/vim/archive/v8.2.0550.tar.gz","tag":null,"revision":null}},"revision":0,"version_scheme":0,"bottle":{"stable":{"rebuild":0,"cellar":"/usr/local/Cellar","prefix":"/usr/local","root_url":"https://homebrew.bintray.com/bottles","files":{"catalina":{"url":"https://homebrew.bintray.com/bottles/vim-8.2.0550.catalina.bottle.tar.gz","sha256":"8f9252500775aa85d8f826af30ca9e1118a56145fc2f961c37abed48bf78cf6b"},"mojave":{"url":"https://homebrew.bintray.com/bottles/vim-8.2.0550.mojave.bottle.tar.gz","sha256":"7566c83b770f3e8c4d4b462a39e5eb26609b37a8f8db6690a2560a3e22ded6b6"},"high_sierra":{"url":"https://homebrew.bintray.com/bottles/vim-8.2.0550.high_sierra.bottle.tar.gz","sha256":"a76e517fc69bf67b6903cb82295bc085c5eb4b46b4659f034c694dd97d2ee2d9"}}}},"keg_only":false,"bottle_disabled":false,"options":[],"build_dependencies":[],"dependencies":["gettext","lua","perl","python","ruby"],"recommended_dependencies":[],"optional_dependencies":[],"uses_from_macos":["ncurses"],"requirements":[],"conflicts_with":["ex-vi","macvim"],"caveats":null,"installed":[{"version":"8.2.0550","used_options":[],"built_as_bottle":true,"poured_from_bottle":true,"runtime_dependencies":[{"full_name":"gettext","version":"0.20.1"},{"full_name":"lua","version":"5.3.5"},{"full_name":"perl","version":"5.30.2"},{"full_name":"gdbm","version":"1.18.1"},{"full_name":"openssl@1.1","version":"1.1.1f"},{"full_name":"readline","version":"8.0.4"},{"full_name":"sqlite","version":"3.31.1"},{"full_name":"xz","version":"5.2.5"},{"full_name":"python","version":"3.7.7"},{"full_name":"libyaml","version":"0.2.2"},{"full_name":"ruby","version":"2.7.1"}],"installed_as_dependency":false,"installed_on_request":true}],"linked_keg":"8.2.0550","pinned":false,"outdated":false}]' }
+
+ let(:brew_info_data) do
+ { "openssl@1.1" =>
+ { "name" => "openssl@1.1",
+ "full_name" => "openssl@1.1",
+ "oldname" => nil,
+ "aliases" => ["openssl"],
+ "versioned_formulae" => [],
+ "desc" => "Cryptography and SSL/TLS Toolkit",
+ "homepage" => "https://openssl.org/",
+ "versions" => { "stable" => "1.1.1f", "devel" => nil, "head" => nil, "bottle" => true },
+ "urls" => { "stable" => { "url" => "https://www.openssl.org/source/openssl-1.1.1f.tar.gz", "tag" => nil, "revision" => nil } },
+ "revision" => 0,
+ "version_scheme" => 1,
+ "bottle" =>
+ { "stable" =>
+ { "rebuild" => 0,
+ "cellar" => "/usr/local/Cellar",
+ "prefix" => "/usr/local",
+ "root_url" => "https://homebrew.bintray.com/bottles",
+ "files" =>
+ { "catalina" => { "url" => "https://homebrew.bintray.com/bottles/openssl@1.1-1.1.1f.catalina.bottle.tar.gz", "sha256" => "724cd97c269952cdc28e24798e350fcf520a32c5985aeb26053ce006a09d8179" },
+ "mojave" => { "url" => "https://homebrew.bintray.com/bottles/openssl@1.1-1.1.1f.mojave.bottle.tar.gz", "sha256" => "25ab844d2f14fc85c7f52958b4b89bdd2965bbd9c557445829eff6473f238744" },
+ "high_sierra" => { "url" => "https://homebrew.bintray.com/bottles/openssl@1.1-1.1.1f.high_sierra.bottle.tar.gz", "sha256" => "27f26e2442222ac0565193fe0b86d8719559d776bcdd070d6113c16bb13accf6" } } } },
+ "keg_only" => true,
+ "bottle_disabled" => false,
+ "options" => [],
+ "build_dependencies" => [],
+ "dependencies" => [],
+ "recommended_dependencies" => [],
+ "optional_dependencies" => [],
+ "uses_from_macos" => [],
+ "requirements" => [],
+ "conflicts_with" => [],
+ "caveats" =>
+ "A CA file has been bootstrapped using certificates from the system\nkeychain. To add additional certificates, place .pem files in\n $(brew --prefix)/etc/openssl@1.1/certs\n\nand run\n $(brew --prefix)/opt/openssl@1.1/bin/c_rehash\n",
+ "installed" => [{ "version" => "1.1.1a", "used_options" => [], "built_as_bottle" => true, "poured_from_bottle" => true, "runtime_dependencies" => [], "installed_as_dependency" => true, "installed_on_request" => false }],
+ "linked_keg" => nil,
+ "pinned" => false,
+ "outdated" => false },
+ "kubernetes-cli" =>
+ { "name" => "kubernetes-cli",
+ "full_name" => "kubernetes-cli",
+ "oldname" => nil,
+ "aliases" => ["kubectl"],
+ "versioned_formulae" => [],
+ "desc" => "Kubernetes command-line interface",
+ "homepage" => "https://kubernetes.io/",
+ "versions" => { "stable" => "1.18.1", "devel" => nil, "head" => "HEAD", "bottle" => true },
+ "urls" => { "stable" => { "url" => "https://github.com/kubernetes/kubernetes.git", "tag" => "v1.18.1", "revision" => "7879fc12a63337efff607952a323df90cdc7a335" } },
+ "revision" => 0,
+ "version_scheme" => 0,
+ "bottle" =>
+ { "stable" =>
+ { "rebuild" => 0,
+ "cellar" => ":any_skip_relocation",
+ "prefix" => "/usr/local",
+ "root_url" => "https://homebrew.bintray.com/bottles",
+ "files" =>
+ { "catalina" => { "url" => "https://homebrew.bintray.com/bottles/kubernetes-cli-1.18.1.catalina.bottle.tar.gz", "sha256" => "0b3d688ee458b70b914a37a4ba867e202c6e71190d0c40a27f84628aec744749" },
+ "mojave" => { "url" => "https://homebrew.bintray.com/bottles/kubernetes-cli-1.18.1.mojave.bottle.tar.gz", "sha256" => "21fddfc86ec6d3e4f7ea787310b0fafd845d368de37524569bbe45938b18ba09" },
+ "high_sierra" => { "url" => "https://homebrew.bintray.com/bottles/kubernetes-cli-1.18.1.high_sierra.bottle.tar.gz", "sha256" => "1e20dcd177fd16b862b2432950984807b048cca5879c27bec59e85590f40eece" } } } },
+ "keg_only" => false,
+ "bottle_disabled" => false,
+ "options" => [],
+ "build_dependencies" => ["go"],
+ "dependencies" => [],
+ "recommended_dependencies" => [],
+ "optional_dependencies" => [],
+ "uses_from_macos" => [],
+ "requirements" => [],
+ "conflicts_with" => [],
+ "caveats" => nil,
+ "installed" => [],
+ "linked_keg" => nil,
+ "pinned" => false,
+ "outdated" => false },
+ "vim" =>
+ { "name" => "vim",
+ "full_name" => "vim",
+ "oldname" => nil,
+ "aliases" => [],
+ "versioned_formulae" => [],
+ "desc" => "Vi 'workalike' with many additional features",
+ "homepage" => "https://www.vim.org/",
+ "versions" => { "stable" => "8.2.0550", "devel" => nil, "head" => "HEAD", "bottle" => true },
+ "urls" => { "stable" => { "url" => "https://github.com/vim/vim/archive/v8.2.0550.tar.gz", "tag" => nil, "revision" => nil } },
+ "revision" => 0,
+ "version_scheme" => 0,
+ "bottle" =>
+ { "stable" =>
+ { "rebuild" => 0,
+ "cellar" => "/usr/local/Cellar",
+ "prefix" => "/usr/local",
+ "root_url" => "https://homebrew.bintray.com/bottles",
+ "files" =>
+ { "catalina" => { "url" => "https://homebrew.bintray.com/bottles/vim-8.2.0550.catalina.bottle.tar.gz", "sha256" => "8f9252500775aa85d8f826af30ca9e1118a56145fc2f961c37abed48bf78cf6b" },
+ "mojave" => { "url" => "https://homebrew.bintray.com/bottles/vim-8.2.0550.mojave.bottle.tar.gz", "sha256" => "7566c83b770f3e8c4d4b462a39e5eb26609b37a8f8db6690a2560a3e22ded6b6" },
+ "high_sierra" => { "url" => "https://homebrew.bintray.com/bottles/vim-8.2.0550.high_sierra.bottle.tar.gz", "sha256" => "a76e517fc69bf67b6903cb82295bc085c5eb4b46b4659f034c694dd97d2ee2d9" } } } },
+ "keg_only" => false,
+ "bottle_disabled" => false,
+ "options" => [],
+ "build_dependencies" => [],
+ "dependencies" => %w{gettext lua perl python ruby},
+ "recommended_dependencies" => [],
+ "optional_dependencies" => [],
+ "uses_from_macos" => ["ncurses"],
+ "requirements" => [],
+ "conflicts_with" => %w{ex-vi macvim},
+ "caveats" => nil,
+ "installed" =>
+ [{ "version" => "8.2.0550",
+ "used_options" => [],
+ "built_as_bottle" => true,
+ "poured_from_bottle" => true,
+ "runtime_dependencies" =>
+ [{ "full_name" => "gettext", "version" => "0.20.1" },
+ { "full_name" => "lua", "version" => "5.3.5" },
+ { "full_name" => "perl", "version" => "5.30.2" },
+ { "full_name" => "gdbm", "version" => "1.18.1" },
+ { "full_name" => "openssl@1.1", "version" => "1.1.1f" },
+ { "full_name" => "readline", "version" => "8.0.4" },
+ { "full_name" => "sqlite", "version" => "3.31.1" },
+ { "full_name" => "xz", "version" => "5.2.5" },
+ { "full_name" => "python", "version" => "3.7.7" },
+ { "full_name" => "libyaml", "version" => "0.2.2" },
+ { "full_name" => "ruby", "version" => "2.7.1" }],
+ "installed_as_dependency" => false,
+ "installed_on_request" => true }],
+ "linked_keg" => "8.2.0550",
+ "pinned" => false,
+ "outdated" => false },
+ "curl" =>
+ { "name" => "curl",
+ "full_name" => "curl",
+ "oldname" => nil,
+ "aliases" => [],
+ "versioned_formulae" => [],
+ "desc" => "Get a file from an HTTP, HTTPS or FTP server",
+ "homepage" => "https://curl.haxx.se/",
+ "versions" => { "stable" => "7.69.1", "devel" => nil, "head" => "HEAD", "bottle" => true },
+ "urls" => { "stable" => { "url" => "https://curl.haxx.se/download/curl-7.69.1.tar.bz2", "tag" => nil, "revision" => nil } },
+ "revision" => 0,
+ "version_scheme" => 0,
+ "bottle" =>
+ { "stable" =>
+ { "rebuild" => 0,
+ "cellar" => ":any",
+ "prefix" => "/usr/local",
+ "root_url" => "https://homebrew.bintray.com/bottles",
+ "files" =>
+ { "catalina" => { "url" => "https://homebrew.bintray.com/bottles/curl-7.69.1.catalina.bottle.tar.gz", "sha256" => "400500fede02f9335bd38c16786b2bbf5e601e358dfac8c21e363d2a8fdd8fac" },
+ "mojave" => { "url" => "https://homebrew.bintray.com/bottles/curl-7.69.1.mojave.bottle.tar.gz", "sha256" => "f082c275f9af1e8e93be12b63a1aff659ba6efa48c8528a97e26c9858a6f95b6" },
+ "high_sierra" => { "url" => "https://homebrew.bintray.com/bottles/curl-7.69.1.high_sierra.bottle.tar.gz", "sha256" => "ad023093c252799a4c60646a149bfe14ffa6984817cf463a6f0e98f6551057fe" } } } },
+ "keg_only" => true,
+ "bottle_disabled" => false,
+ "options" => [],
+ "build_dependencies" => ["pkg-config"],
+ "dependencies" => [],
+ "recommended_dependencies" => [],
+ "optional_dependencies" => [],
+ "uses_from_macos" => ["openssl@1.1", "zlib"],
+ "requirements" => [],
+ "conflicts_with" => [],
+ "caveats" => nil,
+ "installed" => [],
+ "linked_keg" => nil,
+ "pinned" => false,
+ "outdated" => false } }
end
- before(:each) do
-
- end
-
- describe "load_current_resource" do
+ describe "#load_current_resource" do
before(:each) do
- allow(provider).to receive(:current_installed_version).and_return(nil)
- allow(provider).to receive(:candidate_version).and_return("24.3")
+ allow(provider).to receive(:installed_version).and_return(nil)
+ allow(provider).to receive(:available_version).and_return("1.0")
end
it "creates a current resource with the name of the new resource" do
provider.load_current_resource
expect(provider.current_resource).to be_a(Chef::Resource::Package)
- expect(provider.current_resource.name).to eql("emacs")
+ expect(provider.current_resource.name).to eql("emacs, vim")
end
it "creates a current resource with the version if the package is installed" do
- expect(provider).to receive(:current_installed_version).and_return("24.3")
+ expect(provider).to receive(:get_current_versions).and_return(["1.0", "2.0"])
provider.load_current_resource
- expect(provider.current_resource.version).to eql("24.3")
+ expect(provider.current_resource.version).to eql(["1.0", "2.0"])
end
it "creates a current resource with a nil version if the package is not installed" do
provider.load_current_resource
- expect(provider.current_resource.version).to be_nil
+ expect(provider.current_resource.version).to eq([nil, nil])
end
it "sets a candidate version if one exists" do
provider.load_current_resource
- expect(provider.candidate_version).to eql("24.3")
+ expect(provider.candidate_version).to eql(["1.0", "1.0"])
+ end
+ end
+
+ describe "#brew_info" do
+ it "returns a hash of data per package" do
+ allow(provider).to receive(:brew_cmd_output).and_return(brew_cmd_output_data)
+ expect(provider.brew_info).to have_key("vim")
+ end
+
+ it "returns empty hash for packages if they lack data" do
+ new_resource.package_name %w{bogus}
+ allow(provider).to receive(:brew_cmd_output).and_return("")
+ expect(provider.brew_info).to eq("bogus" => {})
end
end
- describe "current_installed_version" do
+ describe "#installed_version" do
it "returns the latest version from brew info if the package is keg only" do
- allow(provider).to receive(:brew_info).and_return(keg_only_brew_info)
- expect(provider.current_installed_version).to eql("24.3-keggy")
+ allow(provider).to receive(:brew_info).and_return(brew_info_data)
+ expect(provider.installed_version("openssl@1.1")).to eql("1.1.1a")
end
it "returns the linked keg version if the package is not keg only" do
- allow(provider).to receive(:brew_info).and_return(installed_brew_info)
- expect(provider.current_installed_version).to eql("24.3")
+ allow(provider).to receive(:brew_info).and_return(brew_info_data)
+ expect(provider.installed_version("vim")).to eql("8.2.0550")
end
it "returns nil if the package is not installed" do
- allow(provider).to receive(:brew_info).and_return(uninstalled_brew_info)
- expect(provider.current_installed_version).to be_nil
+ allow(provider).to receive(:brew_info).and_return(brew_info_data)
+ expect(provider.installed_version("kubernetes-cli")).to be_nil
end
it "returns nil if the package is keg only and not installed" do
- allow(provider).to receive(:brew_info).and_return(keg_only_uninstalled_brew_info)
- expect(provider.current_installed_version).to be_nil
+ allow(provider).to receive(:brew_info).and_return(brew_info_data)
+ expect(provider.installed_version("curl")).to be_nil
+ end
+
+ it "returns the version if a package alias is given" do
+ allow(provider).to receive(:brew_info).and_return(brew_info_data)
+ expect(provider.installed_version("openssl")).to eql("1.1.1a")
+ end
+ end
+
+ describe "#available_version" do
+ it "returns version of package when exact name given" do
+ allow(provider).to receive(:brew_info).and_return(brew_info_data)
+ expect(provider.available_version("openssl@1.1")).to eql("1.1.1f")
+ end
+
+ it "returns version of package when alias is given" do
+ allow(provider).to receive(:brew_info).and_return(brew_info_data)
+ expect(provider.available_version("openssl")).to eql("1.1.1f")
+ end
+
+ it "returns nil if the package is not installed" do
+ allow(provider).to receive(:brew_info).and_return(brew_info_data)
+ expect(provider.available_version("bogus")).to be_nil
end
end
- describe "brew" do
+ describe "#brew_cmd_output" do
before do
expect(provider).to receive(:find_homebrew_uid).and_return(homebrew_uid)
expect(Etc).to receive(:getpwuid).with(homebrew_uid).and_return(OpenStruct.new(name: "name", dir: "/"))
end
- it "passes a single to the brew command and return stdout" do
+ it "passes a single pkg to the brew command and return stdout" do
allow(provider).to receive(:shell_out!).and_return(OpenStruct.new(stdout: "zombo"))
- expect(provider.brew).to eql("zombo")
+ expect(provider.brew_cmd_output).to eql("zombo")
end
it "takes multiple arguments as an array" do
allow(provider).to receive(:shell_out!).and_return(OpenStruct.new(stdout: "homestarrunner"))
- expect(provider.brew("info", "opts", "bananas")).to eql("homestarrunner")
+ expect(provider.brew_cmd_output("info", "opts", "bananas")).to eql("homestarrunner")
end
context "when new_resource is Package" do
@@ -191,102 +310,89 @@ describe Chef::Provider::Package::Homebrew do
it "does not try to read homebrew_user from Package, which does not have it" do
allow(provider).to receive(:shell_out!).and_return(OpenStruct.new(stdout: "zombo"))
- expect(provider.brew).to eql("zombo")
+ expect(provider.brew_cmd_output).to eql("zombo")
end
end
end
- context "when testing actions" do
+ describe "resource actions" do
before(:each) do
provider.current_resource = current_resource
+ allow(provider).to receive(:brew_info).and_return(brew_info_data)
end
- describe "install_package" do
- before(:each) do
- allow(provider).to receive(:candidate_version).and_return("24.3")
+ describe "install" do
+ it "calls brew_cmd_output to install only the necessary packages" do
+ new_resource.package_name %w{curl openssl}
+ expect(provider).to receive(:brew_cmd_output).with("install", nil, ["curl"])
+ provider.run_action(:install)
end
- it "installs the named package with brew install" do
- allow(provider.new_resource).to receive(:version).and_return("24.3")
- allow(provider.current_resource).to receive(:version).and_return(nil)
- allow(provider).to receive(:brew_info).and_return(uninstalled_brew_info)
- expect(provider).to receive(:get_response_from_command).with("brew", "install", nil, "emacs")
- provider.install_package("emacs", "24.3")
- end
-
- it "does not do anything if the package is installed" do
- allow(provider.current_resource).to receive(:version).and_return("24.3")
- allow(provider).to receive(:brew_info).and_return(installed_brew_info)
- expect(provider).not_to receive(:get_response_from_command)
- provider.install_package("emacs", "24.3")
+ it "does not do anything if all the packages are already installed" do
+ new_resource.package_name %w{vim openssl}
+ expect(provider).not_to receive(:brew_cmd_output)
+ provider.run_action(:install)
end
it "uses options to the brew command if specified" do
+ new_resource.package_name "curl"
new_resource.options "--cocoa"
- allow(provider.current_resource).to receive(:version).and_return("24.3")
- allow(provider).to receive(:get_response_from_command).with("brew", "install", "--cocoa", "emacs")
- provider.install_package("emacs", "24.3")
+ expect(provider).to receive(:brew_cmd_output).with("install", ["--cocoa"], ["curl"])
+ provider.run_action(:install)
end
end
- describe "upgrade_package" do
- it "uses brew upgrade to upgrade the package if it is installed" do
- allow(provider.current_resource).to receive(:version).and_return("24")
- allow(provider).to receive(:brew_info).and_return(installed_brew_info)
- expect(provider).to receive(:get_response_from_command).with("brew", "upgrade", nil, "emacs")
- provider.upgrade_package("emacs", "24.3")
- end
-
- it "does not do anything if the package version is already installed" do
- allow(provider.current_resource).to receive(:version).and_return("24.3")
- allow(provider).to receive(:brew_info).and_return(installed_brew_info)
- expect(provider).not_to receive(:get_response_from_command)
- provider.install_package("emacs", "24.3")
+ describe "upgrade" do
+ it "calls #brew_cmd_output to upgrade the packages" do
+ new_resource.package_name %w{openssl}
+ allow(provider.current_resource).to receive(:version).and_return(["1.0.1a"])
+ expect(provider).to receive(:brew_cmd_output).with("upgrade", nil, ["openssl"])
+ provider.run_action(:upgrade)
end
- it "uses brew install to install the package if it is not installed" do
- allow(provider.current_resource).to receive(:version).and_return(nil)
- allow(provider).to receive(:brew_info).and_return(uninstalled_brew_info)
- expect(provider).to receive(:get_response_from_command).with("brew", "install", nil, "emacs")
- provider.upgrade_package("emacs", "24.3")
+ it "calls #brew_cmd_output to both upgrade and install the packages as necessary" do
+ new_resource.package_name %w{openssl kubernetes-cli}
+ allow(provider.current_resource).to receive(:version).and_return(["1.0.1a", nil])
+ expect(provider).to receive(:brew_cmd_output).with("upgrade", nil, ["openssl"])
+ expect(provider).to receive(:brew_cmd_output).with("install", nil, ["kubernetes-cli"])
+ provider.run_action(:upgrade)
end
it "uses options to the brew command if specified" do
- allow(provider.current_resource).to receive(:version).and_return("24")
- allow(provider).to receive(:brew_info).and_return(installed_brew_info)
+ new_resource.package_name %w{openssl}
+ allow(provider.current_resource).to receive(:version).and_return(["1.0.1a"])
+ allow(provider).to receive(:brew_info).and_return(brew_info_data)
new_resource.options "--cocoa"
- expect(provider).to receive(:get_response_from_command).with("brew", "upgrade", [ "--cocoa" ], "emacs")
- provider.upgrade_package("emacs", "24.3")
+ expect(provider).to receive(:brew_cmd_output).with("upgrade", [ "--cocoa" ], ["openssl"])
+ provider.run_action(:upgrade)
end
end
- describe "remove_package" do
- it "uninstalls the package with brew uninstall" do
- allow(provider.current_resource).to receive(:version).and_return("24.3")
- allow(provider).to receive(:brew_info).and_return(installed_brew_info)
- expect(provider).to receive(:get_response_from_command).with("brew", "uninstall", nil, "emacs")
- provider.remove_package("emacs", "24.3")
+ describe "remove" do
+ it "calls #brew_cmd_output to uninstall the packages" do
+ new_resource.package_name %w{curl openssl}
+ expect(provider).to receive(:brew_cmd_output).with("uninstall", nil, %w{curl openssl})
+ provider.run_action(:remove)
end
it "does not do anything if the package is not installed" do
- allow(provider).to receive(:brew_info).and_return(uninstalled_brew_info)
- expect(provider).not_to receive(:get_response_from_command)
- provider.remove_package("emacs", "24.3")
+ new_resource.package_name %w{kubernetes-cli}
+ expect(provider).not_to receive(:brew_cmd_output)
+ provider.run_action(:remove)
end
end
- describe "purge_package" do
- it "uninstalls the package with brew uninstall --force" do
- allow(provider.current_resource).to receive(:version).and_return("24.3")
- allow(provider).to receive(:brew_info).and_return(installed_brew_info)
- expect(provider).to receive(:get_response_from_command).with("brew", "uninstall", "--force", nil, "emacs")
- provider.purge_package("emacs", "24.3")
+ describe "purge" do
+ it "call #brew_cmd_output to uninstall --force the packages" do
+ new_resource.package_name %w{curl openssl}
+ expect(provider).to receive(:brew_cmd_output).with("uninstall", "--force", nil, %w{curl openssl})
+ provider.run_action(:purge)
end
it "does not do anything if the package is not installed" do
- allow(provider).to receive(:brew_info).and_return(uninstalled_brew_info)
- expect(provider).not_to receive(:get_response_from_command)
- provider.purge_package("emacs", "24.3")
+ new_resource.package_name %w{kubernetes-cli}
+ expect(provider).not_to receive(:brew_cmd_output)
+ provider.run_action(:purge)
end
end
end