diff options
author | Tim Smith <tsmith84@gmail.com> | 2020-03-11 10:52:53 -0700 |
---|---|---|
committer | Tim Smith <tsmith84@gmail.com> | 2020-03-12 11:58:59 -0700 |
commit | 10e8ae47e255fb1ced9f594b0ce94f5f332a414c (patch) | |
tree | 1876e427810019e030b5bcfdc4ab1075717b5798 | |
parent | e5e0f8f399e2a52ace04702225d327a56db5438c (diff) | |
download | chef-10e8ae47e255fb1ced9f594b0ce94f5f332a414c.tar.gz |
Add alternatives resource for Linux
This comes from https://github.com/vkhatri/chef-alternatives
Signed-off-by: Tim Smith <tsmith@chef.io>
-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 | 131 | ||||
-rw-r--r-- | lib/chef/resources.rb | 1 | ||||
-rw-r--r-- | spec/unit/resource/alternatives_spec.rb | 50 |
5 files changed, 234 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..75916f7269 --- /dev/null +++ b/lib/chef/resource/alternatives.rb @@ -0,0 +1,131 @@ +# +# 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, :set, :remove) do |a| + a.assertion do + !new_resource.path.nil? + end + + a.failure_message("Could not set alternatives for #{new_resource.link_name}, must provide :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 + raise "missing :priority" unless new_resource.priority + + 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 + def alternatives_cmd + if platform_family?("rhel", "amazon", "fedora") + "alternatives" + else + "update-alternatives" + end + end + + 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 + + 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.nil? ? nil : match[1] + end + + 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..7540e73a97 --- /dev/null +++ b/spec/unit/resource/alternatives_spec.rb @@ -0,0 +1,50 @@ +# +# 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) } + + 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 +end |