# # Copyright:: Copyright (c) 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 provides(:alternatives, target_mode: true) { true } description "Use the **alternatives** resource to configure command alternatives in Linux using the alternatives or update-alternatives packages." introduced "16.0" examples <<~DOC **Install an alternative**: ```ruby alternatives 'python install 2' do link_name 'python' path '/usr/bin/python2.7' priority 100 action :install end ``` **Set an alternative**: ```ruby alternatives 'python set version 3' do link_name 'python' path '/usr/bin/python3' action :set end ``` **Set the automatic alternative state**: ```ruby alternatives 'python auto' do link_name 'python' action :auto end ``` **Refresh an alternative**: ```ruby alternatives 'python refresh' do link_name 'python' action :refresh end ``` **Remove an alternative**: ```ruby alternatives 'python remove' do link_name 'python' path '/usr/bin/python3' action :remove end ``` DOC property :link_name, String, name_property: true, description: "The name of the link to create. This will be the command you type on the command line such as `ruby` or `gcc`." property :link, String, default: lazy { |n| "/usr/bin/#{n.link_name}" }, default_description: "/usr/bin/LINK_NAME", description: "The path to the alternatives link." property :path, String, description: "The absolute path to the original application binary such as `/usr/bin/ruby27`." property :priority, [String, Integer], coerce: proc { |n| n.to_i }, description: "The priority of the alternative." 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 ::TargetIO::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, description: "Install an alternative on the system including symlinks." 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, description: "Set the symlink for an alternative." 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, description: "Remove an alternative and all associated links." 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, description: "Set an alternative up in automatic mode with the highest priority automatically selected." do converge_by("setting auto alternative #{new_resource.link_name}") do shell_out(alternatives_cmd, "--auto", new_resource.link_name) end end action :refresh, description: "Refresh alternatives." 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