summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Smith <tsmith@chef.io>2020-03-12 18:31:40 -0700
committerGitHub <noreply@github.com>2020-03-12 18:31:40 -0700
commit3f3b33c2db198b20d84c7d4f422a2982591e9cb0 (patch)
tree61747c3b7a6fc2eaa2dc1cef0f57cc7b7529b216
parent24ab5dbf7f34daee7500899924744d9a0484b085 (diff)
parent04bf2a950cb46dc0f48585992cfa7ce9c911b721 (diff)
downloadchef-3f3b33c2db198b20d84c7d4f422a2982591e9cb0.tar.gz
Merge pull request #9481 from chef/alternatives
Add alternatives resource for Linux
-rw-r--r--kitchen-tests/cookbooks/end_to_end/recipes/alternatives.rb51
-rw-r--r--kitchen-tests/cookbooks/end_to_end/recipes/default.rb2
-rw-r--r--lib/chef/resource/alternatives.rb149
-rw-r--r--lib/chef/resources.rb1
-rw-r--r--spec/unit/resource/alternatives_spec.rb120
5 files changed, 322 insertions, 1 deletions
diff --git a/kitchen-tests/cookbooks/end_to_end/recipes/alternatives.rb b/kitchen-tests/cookbooks/end_to_end/recipes/alternatives.rb
new file mode 100644
index 0000000000..8e0a0bb178
--- /dev/null
+++ b/kitchen-tests/cookbooks/end_to_end/recipes/alternatives.rb
@@ -0,0 +1,51 @@
+#
+# Cookbook:: end_to_end
+# Recipe:: alternatives
+#
+
+file "/usr/local/sample-binary-1" do
+ content '#!/bin/bash
+ echo sample-binary-v1
+ '
+ mode "500"
+end
+
+file "/usr/local/sample-binary-2" do
+ content '#!/bin/bash
+ echo sample-binary-v2
+ '
+ mode "550"
+end
+
+alternatives "sample-binary v1" do
+ link_name "sample-binary"
+ path "/usr/local/sample-binary-1"
+ priority 100
+ action :install
+end
+
+alternatives "sample-binary v2" do
+ link_name "sample-binary"
+ path "/usr/local/sample-binary-2"
+ priority 101
+ action :install
+end
+
+alternatives "set sample-binary v1" do
+ link_name "sample-binary"
+ path "/usr/local/sample-binary-1"
+ action :set
+end
+
+alternatives "sample-binary-test v1" do
+ link_name "sample-binary-test"
+ path "/usr/local/sample-binary-1"
+ priority 100
+ action :install
+end
+
+alternatives "sample-binary-test v1" do
+ link_name "sample-binary-test"
+ path "/usr/local/sample-binary-1"
+ action :remove
+end
diff --git a/kitchen-tests/cookbooks/end_to_end/recipes/default.rb b/kitchen-tests/cookbooks/end_to_end/recipes/default.rb
index dc8b44cbf4..eeccb9ffd7 100644
--- a/kitchen-tests/cookbooks/end_to_end/recipes/default.rb
+++ b/kitchen-tests/cookbooks/end_to_end/recipes/default.rb
@@ -118,5 +118,5 @@ end
end
include_recipe "::chef-vault" unless includes_recipe?("end_to_end::chef-vault")
-
+include_recipe "::alternatives"
include_recipe "::tests"
diff --git a/lib/chef/resource/alternatives.rb b/lib/chef/resource/alternatives.rb
new file mode 100644
index 0000000000..a2f08ad864
--- /dev/null
+++ b/lib/chef/resource/alternatives.rb
@@ -0,0 +1,149 @@
+#
+# Copyright:: 2020, Chef Software Inc.
+# Copyright:: 2016-2020, Virender Khatri
+#
+# 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.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require_relative "../resource"
+
+class Chef
+ class Resource
+ class Alternatives < Chef::Resource
+ unified_mode true
+
+ provides(:alternatives) { true }
+
+ description "The alternatives resource allows for configuration of command alternatives in Linux using the alternatives or update-alternatives packages."
+ introduced "16.0"
+
+ property :link_name, String, name_property: true
+ property :link, String, default: lazy { |n| "/usr/bin/#{n.link_name}" }
+ property :path, String
+ property :priority, [String, Integer], coerce: proc { |n| n.to_i }
+
+ def define_resource_requirements
+ requirements.assert(:install) do |a|
+ a.assertion do
+ !new_resource.priority.nil?
+ end
+
+ a.failure_message("Could not set alternatives for #{new_resource.link_name}, you must provide the :priority property")
+ end
+
+ requirements.assert(:install, :set, :remove) do |a|
+ a.assertion do
+ !new_resource.path.nil?
+ end
+
+ a.failure_message("Could not set alternatives for #{new_resource.link_name}, you must provide the :path property")
+ end
+
+ requirements.assert(:install, :set, :remove) do |a|
+ a.assertion do
+ ::File.exist?(new_resource.path)
+ end
+
+ a.whyrun("Assuming file #{new_resource.path} already exists or was created already")
+ a.failure_message("Could not set alternatives for #{new_resource.link_name}, missing #{new_resource.path}")
+ end
+ end
+
+ action :install do
+ if path_priority != new_resource.priority
+ converge_by("adding alternative #{new_resource.link} #{new_resource.link_name} #{new_resource.path} #{new_resource.priority}") do
+ output = shell_out(alternatives_cmd, "--install", new_resource.link, new_resource.link_name, new_resource.path, new_resource.priority)
+ unless output.exitstatus == 0
+ raise "failed to add alternative #{new_resource.link} #{new_resource.link_name} #{new_resource.path} #{new_resource.priority}"
+ end
+ end
+ end
+ end
+
+ action :set do
+ if current_path != new_resource.path
+ converge_by("setting alternative #{new_resource.link_name} #{new_resource.path}") do
+ output = shell_out(alternatives_cmd, "--set", new_resource.link_name, new_resource.path)
+ unless output.exitstatus == 0
+ raise "failed to set alternative #{new_resource.link_name} #{new_resource.path} \n #{output.stdout.strip}"
+ end
+ end
+ end
+ end
+
+ action :remove do
+ if path_exists?
+ converge_by("removing alternative #{new_resource.link_name} #{new_resource.path}") do
+ shell_out(alternatives_cmd, "--remove", new_resource.link_name, new_resource.path)
+ end
+ end
+ end
+
+ action :auto do
+ converge_by("setting auto alternative #{new_resource.link_name}") do
+ shell_out(alternatives_cmd, "--auto", new_resource.link_name)
+ end
+ end
+
+ action :refresh do
+ converge_by("refreshing alternative #{new_resource.link_name}") do
+ shell_out(alternatives_cmd, "--refresh", new_resource.link_name)
+ end
+ end
+
+ action_class do
+ #
+ # @return [String] The appropriate alternatives command based on the platform
+ #
+ def alternatives_cmd
+ if debian?
+ "update-alternatives"
+ else
+ "alternatives"
+ end
+ end
+
+ #
+ # @return [Integer] The current path priority for the link_name alternative
+ #
+ def path_priority
+ # https://rubular.com/r/IcUlEU0mSNaMm3
+ escaped_path = Regexp.new(Regexp.escape("#{new_resource.path} - priority ") + "(.*)")
+ match = shell_out(alternatives_cmd, "--display", new_resource.link_name).stdout.match(escaped_path)
+
+ match.nil? ? nil : match[1].to_i
+ end
+
+ #
+ # @return [String] The current path for the link_name alternative
+ #
+ def current_path
+ # https://rubular.com/r/ylsuvzUtquRPqc
+ match = shell_out(alternatives_cmd, "--display", new_resource.link_name).stdout.match(/link currently points to (.*)/)
+ match[1]
+ end
+
+ #
+ # @return [Boolean] does the path exist for the link_name alternative
+ #
+ def path_exists?
+ # https://rubular.com/r/ogvDdq8h2IKRff
+ escaped_path = Regexp.new(Regexp.escape("#{new_resource.path} - priority"))
+ shell_out(alternatives_cmd, "--display", new_resource.link_name).stdout.match?(escaped_path)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resources.rb b/lib/chef/resources.rb
index 7d9a24c830..f0736e2429 100644
--- a/lib/chef/resources.rb
+++ b/lib/chef/resources.rb
@@ -16,6 +16,7 @@
# limitations under the License.
#
+require_relative "resource/alternatives"
require_relative "resource/apt_package"
require_relative "resource/apt_preference"
require_relative "resource/apt_repository"
diff --git a/spec/unit/resource/alternatives_spec.rb b/spec/unit/resource/alternatives_spec.rb
new file mode 100644
index 0000000000..d8e7977125
--- /dev/null
+++ b/spec/unit/resource/alternatives_spec.rb
@@ -0,0 +1,120 @@
+#
+# Author:: Tim Smith (<tsmith@chef.io>)
+# Copyright:: 2020, 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.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "spec_helper"
+
+describe Chef::Resource::Alternatives do
+ let(:node) { Chef::Node.new }
+ let(:events) { Chef::EventDispatch::Dispatcher.new }
+ let(:run_context) { Chef::RunContext.new(node, {}, events) }
+ let(:resource) { Chef::Resource::Alternatives.new("fakey_fakerton", run_context) }
+ let(:provider) { resource.provider_for_action(:install) }
+
+ let(:alternatives_display_exists) do
+ double("shellout", stdout: <<-STDOUT)
+ java - auto mode
+ link best version is /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java
+ link currently points to /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java
+ link java is /usr/bin/java
+ slave java.1.gz is /usr/share/man/man1/java.1.gz
+/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java - priority 1081
+ slave java.1.gz: /usr/lib/jvm/java-8-openjdk-amd64/jre/man/man1/java.1.gz
+ STDOUT
+ end
+
+ let(:alternatives_display_does_not_exist) do
+ double("shellout", stdout: "update-alternatives: error: no alternatives for fakey_fakerton")
+ end
+
+ it "the link_name property is the name_property" do
+ expect(resource.link_name).to eql("fakey_fakerton")
+ end
+
+ it "sets the default action as :install" do
+ expect(resource.action).to eql([:install])
+ end
+
+ it "coerces priority value to an Integer" do
+ resource.priority("1")
+ expect(resource.priority).to eql(1)
+ end
+
+ it "builds a default value for link based on link_name value" do
+ expect(resource.link).to eql("/usr/bin/fakey_fakerton")
+ end
+
+ it "supports :install, :auto, :refresh, and :remove actions" do
+ expect { resource.action :install }.not_to raise_error
+ expect { resource.action :auto }.not_to raise_error
+ expect { resource.action :refresh }.not_to raise_error
+ expect { resource.action :remove }.not_to raise_error
+ end
+
+ describe "#path_exists?" do
+ it "returns true if the path exists according to alternatives --display" do
+ allow(provider).to receive(:shell_out).with("alternatives", "--display", "fakey_fakerton").and_return(alternatives_display_exists)
+ expect(provider.path_exists?).to eql(true)
+ end
+
+ it "returns false if alternatives --display does not find a path" do
+ allow(provider).to receive(:shell_out).with("alternatives", "--display", "fakey_fakerton").and_return(alternatives_display_does_not_exist)
+ expect(provider.path_exists?).to eql(false)
+ end
+ end
+
+ describe "#current_path" do
+ it "extracts the current path by running alternatives --display" do
+ allow(provider).to receive(:shell_out).with("alternatives", "--display", "fakey_fakerton").and_return(alternatives_display_exists)
+ expect(provider.current_path).to eql("/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java")
+ end
+ end
+
+ describe "#path_priority" do
+ it "extracts the path priority by running alternatives --display" do
+ allow(provider).to receive(:shell_out).with("alternatives", "--display", "fakey_fakerton").and_return(alternatives_display_exists)
+ expect(provider.path_priority).to eql(1081)
+ end
+ end
+
+ describe "#alternatives_cmd" do
+ it "returns alternatives on fedora" do
+ node.automatic_attrs[:platform_family] = "fedora"
+ expect(provider.alternatives_cmd).to eql("alternatives")
+ end
+
+ it "returns alternatives on amazon" do
+ node.automatic_attrs[:platform_family] = "amazon"
+ expect(provider.alternatives_cmd).to eql("alternatives")
+ end
+
+ it "returns alternatives on suse" do
+ node.automatic_attrs[:platform_family] = "suse"
+ expect(provider.alternatives_cmd).to eql("alternatives")
+ end
+
+ it "returns alternatives on redhat" do
+ node.automatic_attrs[:platform_family] = "rhel"
+ expect(provider.alternatives_cmd).to eql("alternatives")
+ end
+
+ it "returns update-alternatives on debian" do
+ node.automatic_attrs[:platform_family] = "debian"
+ expect(provider.alternatives_cmd).to eql("update-alternatives")
+ end
+ end
+end