diff options
author | Tim Smith <tsmith@chef.io> | 2020-03-12 18:31:40 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-03-12 18:31:40 -0700 |
commit | 3f3b33c2db198b20d84c7d4f422a2982591e9cb0 (patch) | |
tree | 61747c3b7a6fc2eaa2dc1cef0f57cc7b7529b216 | |
parent | 24ab5dbf7f34daee7500899924744d9a0484b085 (diff) | |
parent | 04bf2a950cb46dc0f48585992cfa7ce9c911b721 (diff) | |
download | chef-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.rb | 51 | ||||
-rw-r--r-- | kitchen-tests/cookbooks/end_to_end/recipes/default.rb | 2 | ||||
-rw-r--r-- | lib/chef/resource/alternatives.rb | 149 | ||||
-rw-r--r-- | lib/chef/resources.rb | 1 | ||||
-rw-r--r-- | spec/unit/resource/alternatives_spec.rb | 120 |
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 |