Vendor the external cookbooks
Knife-Zero doesn't include Berkshelf support, so vendoring everything in the repo is convenient again
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
#
|
||||
# Copyright 2015-2017, Noah Kantrowitz
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
|
||||
module PoiseLanguages
|
||||
autoload :Command, 'poise_languages/command'
|
||||
autoload :Error, 'poise_languages/error'
|
||||
autoload :Scl, 'poise_languages/scl'
|
||||
autoload :Static, 'poise_languages/static'
|
||||
autoload :System, 'poise_languages/system'
|
||||
autoload :Utils, 'poise_languages/utils'
|
||||
autoload :VERSION, 'poise_languages/version'
|
||||
end
|
||||
@@ -0,0 +1,25 @@
|
||||
#
|
||||
# Copyright 2015-2017, Noah Kantrowitz
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
|
||||
module PoiseLanguages
|
||||
# A namespace for language-command-related stuff.
|
||||
#
|
||||
# @since 1.0.0
|
||||
module Command
|
||||
autoload :Mixin, 'poise_languages/command/mixin'
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,241 @@
|
||||
#
|
||||
# Copyright 2015-2017, Noah Kantrowitz
|
||||
#
|
||||
# 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 'shellwords'
|
||||
|
||||
require 'poise'
|
||||
|
||||
require 'poise_languages/error'
|
||||
require 'poise_languages/utils'
|
||||
|
||||
|
||||
module PoiseLanguages
|
||||
module Command
|
||||
# A mixin for resources and providers that run language commands.
|
||||
#
|
||||
# @since 1.0.0
|
||||
module Mixin
|
||||
include Poise::Utils::ResourceProviderMixin
|
||||
|
||||
# A mixin for resources that run language commands. Also available as a
|
||||
# parameterized mixin via `include PoiseLanguages::Command::Mixin::Resource(name)`.
|
||||
#
|
||||
# @example
|
||||
# class MyLangThing
|
||||
# include PoiseLanguages::Command::Mixin::Resource(:mylang)
|
||||
# # ...
|
||||
# end
|
||||
module Resource
|
||||
include Poise::Resource
|
||||
poise_subresource(true)
|
||||
|
||||
private
|
||||
|
||||
# Implementation of the $name accessor.
|
||||
#
|
||||
# @api private
|
||||
# @param name [Symbol] Language name.
|
||||
# @param runtime [Symbol] Language runtime resource name.
|
||||
# @param val [String, Chef::Resource, Poise::NOT_PASSED, nil] Accessor value.
|
||||
# @return [String]
|
||||
def language_command_runtime(name, runtime, default_binary, val=Poise::NOT_PASSED)
|
||||
unless val == Poise::NOT_PASSED
|
||||
path_arg = parent_arg = nil
|
||||
# Figure out which property we are setting.
|
||||
if val.is_a?(String)
|
||||
# Check if it is a runtime resource.
|
||||
begin
|
||||
parent_arg = run_context.resource_collection.find("#{runtime}[#{val}]")
|
||||
rescue Chef::Exceptions::ResourceNotFound
|
||||
# Check if something looks like a path, defined as containing
|
||||
# either / or \. While a single word could be a path, I think the
|
||||
# UX win of better error messages should take priority.
|
||||
might_be_path = val =~ %r{/|\\}
|
||||
if might_be_path
|
||||
Chef::Log.debug("[#{self}] #{runtime}[#{val}] not found, treating it as a path")
|
||||
path_arg = val
|
||||
else
|
||||
# Surface the error up to the user.
|
||||
raise
|
||||
end
|
||||
end
|
||||
else
|
||||
parent_arg = val
|
||||
end
|
||||
# Set both attributes.
|
||||
send(:"parent_#{name}", parent_arg)
|
||||
set_or_return(name, path_arg, kind_of: [String, NilClass])
|
||||
else
|
||||
# Getter behavior. Using the ivar directly is kind of gross but oh well.
|
||||
instance_variable_get(:"@#{name}") || default_language_command_runtime(name, default_binary)
|
||||
end
|
||||
end
|
||||
|
||||
# Compute the path to the default runtime binary.
|
||||
#
|
||||
# @api private
|
||||
# @param name [Symbol] Language name.
|
||||
# @return [String]
|
||||
def default_language_command_runtime(name, default_binary)
|
||||
parent = send(:"parent_#{name}")
|
||||
if parent
|
||||
parent.send(:"#{name}_binary")
|
||||
else
|
||||
PoiseLanguages::Utils.which(default_binary || name.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
# Inherit language parent from another resource.
|
||||
#
|
||||
# @api private
|
||||
# @param name [Symbol] Language name.
|
||||
# @param resource [Chef::Resource] Resource to inherit from.
|
||||
# @return [void]
|
||||
def language_command_runtime_from_parent(name, resource)
|
||||
parent = resource.send(:"parent_#{name}")
|
||||
if parent
|
||||
send(:"parent_#{name}", parent)
|
||||
else
|
||||
path = resource.send(name)
|
||||
if path
|
||||
send(name, path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Configure this module or class for a specific language.
|
||||
#
|
||||
# @param name [Symbol] Language name.
|
||||
# @param runtime [Symbol] Language runtime resource name.
|
||||
# @param timeout [Boolean] Enable the timeout attribute.
|
||||
# @param default_binary [String] Name of the default language binary.
|
||||
# @return [void]
|
||||
def language_command_mixin(name, runtime: :"#{name}_runtime", timeout: true, default_binary: nil)
|
||||
# Create the parent attribute.
|
||||
parent_attribute(name, type: runtime, optional: true)
|
||||
|
||||
# Timeout attribute for the shell_out wrappers in the provider.
|
||||
attribute(:timeout, kind_of: Integer, default: 900) if timeout
|
||||
|
||||
# Create the main accessor for the parent/path.
|
||||
define_method(name) do |val=Poise::NOT_PASSED|
|
||||
language_command_runtime(name, runtime, default_binary, val)
|
||||
end
|
||||
|
||||
# Create the method to inherit settings from another resource.
|
||||
define_method(:"#{name}_from_parent") do |resource|
|
||||
language_command_runtime_from_parent(name, resource)
|
||||
end
|
||||
private :"#{name}_from_parent"
|
||||
end
|
||||
|
||||
def language_command_default_binary(val=Poise::NOT_PASSED)
|
||||
@language_command_default_binary = val if val != Poise::NOT_PASSED
|
||||
@language_command_default_binary
|
||||
end
|
||||
|
||||
# @api private
|
||||
def included(klass)
|
||||
super
|
||||
klass.extend(ClassMethods)
|
||||
end
|
||||
end
|
||||
|
||||
extend ClassMethods
|
||||
Poise::Utils.parameterized_module(self) {|*args| language_command_mixin(*args) }
|
||||
end # /module Resource
|
||||
|
||||
# A mixin for providers that run language commands.
|
||||
module Provider
|
||||
include Poise::Utils::ShellOut
|
||||
|
||||
private
|
||||
|
||||
# Run a command using the configured language via `shell_out`.
|
||||
#
|
||||
# @api private
|
||||
# @param name [Symbol] Language name.
|
||||
# @param command_args [Array] Arguments to `shell_out`.
|
||||
# @return [Mixlib::ShellOut]
|
||||
def language_command_shell_out(name, *command_args, **options)
|
||||
# Inject our environment variables if needed.
|
||||
options[:environment] ||= {}
|
||||
parent = new_resource.send(:"parent_#{name}")
|
||||
if parent
|
||||
options[:environment].update(parent.send(:"#{name}_environment"))
|
||||
end
|
||||
# Inject other options.
|
||||
options[:timeout] ||= new_resource.timeout
|
||||
# Find the actual binary to use. Raise an exception if we see false
|
||||
# which happens if no parent resource is found, no explicit default
|
||||
# binary was given, and which() fails to find a thing.
|
||||
binary = new_resource.send(name)
|
||||
raise Error.new("Unable to find a #{name} binary for command: #{command_args.is_a?(Array) ? Shellwords.shelljoin(command_args) : command_args}") unless binary
|
||||
command = if command_args.length == 1 && command_args.first.is_a?(String)
|
||||
# String mode, sigh.
|
||||
"#{Shellwords.escape(binary)} #{command_args.first}"
|
||||
else
|
||||
# Array mode. Handle both ('one', 'two') and (['one', 'two']).
|
||||
[binary] + command_args.flatten
|
||||
end
|
||||
Chef::Log.debug("[#{new_resource}] Running #{name} command: #{command.is_a?(Array) ? Shellwords.shelljoin(command) : command}")
|
||||
# Run the command
|
||||
poise_shell_out(command, options)
|
||||
end
|
||||
|
||||
# Run a command using the configured language via `shell_out!`.
|
||||
#
|
||||
# @api private
|
||||
# @param name [Symbol] Language name.
|
||||
# @param command_args [Array] Arguments to `shell_out!`.
|
||||
# @return [Mixlib::ShellOut]
|
||||
def language_command_shell_out!(name, *command_args)
|
||||
send(:"#{name}_shell_out", *command_args).tap(&:error!)
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Configure this module or class for a specific language.
|
||||
#
|
||||
# @param name [Symbol] Language name.
|
||||
# @return [void]
|
||||
def language_command_mixin(name)
|
||||
define_method(:"#{name}_shell_out") do |*command_args|
|
||||
language_command_shell_out(name, *command_args)
|
||||
end
|
||||
private :"#{name}_shell_out"
|
||||
|
||||
define_method(:"#{name}_shell_out!") do |*command_args|
|
||||
language_command_shell_out!(name, *command_args)
|
||||
end
|
||||
private :"#{name}_shell_out!"
|
||||
end
|
||||
|
||||
# @api private
|
||||
def included(klass)
|
||||
super
|
||||
klass.extend(ClassMethods)
|
||||
end
|
||||
end
|
||||
|
||||
extend ClassMethods
|
||||
Poise::Utils.parameterized_module(self) {|*args| language_command_mixin(*args) }
|
||||
end # /module Provider
|
||||
|
||||
Poise::Utils.parameterized_module(self) {|*args| language_command_mixin(*args) }
|
||||
end # /module Mixin
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,21 @@
|
||||
#
|
||||
# Copyright 2015-2017, Noah Kantrowitz
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
|
||||
module PoiseLanguages
|
||||
class Error < ::Exception
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,24 @@
|
||||
#
|
||||
# Copyright 2015-2017, Noah Kantrowitz
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
|
||||
module PoiseLanguages
|
||||
module Scl
|
||||
autoload :Mixin, 'poise_languages/scl/mixin'
|
||||
autoload :Resource, 'poise_languages/scl/resource'
|
||||
autoload :Provider, 'poise_languages/scl/resource'
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,134 @@
|
||||
#
|
||||
# Copyright 2015-2017, Noah Kantrowitz
|
||||
#
|
||||
# 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 'poise_languages/scl/resource'
|
||||
|
||||
|
||||
module PoiseLanguages
|
||||
module Scl
|
||||
module Mixin
|
||||
private
|
||||
|
||||
def install_scl_package
|
||||
pkg = scl_package
|
||||
poise_languages_scl options[:package_name] || pkg[:name] do
|
||||
action :upgrade if options[:package_upgrade]
|
||||
dev_package options[:dev_package] == true ? pkg[:devel_name] : options[:dev_package]
|
||||
parent new_resource
|
||||
version options[:package_version]
|
||||
end
|
||||
end
|
||||
|
||||
def uninstall_scl_package
|
||||
install_scl_package.tap do |r|
|
||||
r.action(:uninstall)
|
||||
end
|
||||
end
|
||||
|
||||
def scl_package
|
||||
@scl_package ||= self.class.find_scl_package(node, options['version']).tap do |p|
|
||||
raise PoiseLanguages::Error.new("No SCL repoistory package for #{node['platform']} #{node['platform_version']}") unless p
|
||||
end
|
||||
end
|
||||
|
||||
def scl_folder
|
||||
::File.join('', 'opt', 'rh', scl_package[:name])
|
||||
end
|
||||
|
||||
def scl_environment
|
||||
parse_enable_file(::File.join(scl_folder, 'enable'))
|
||||
end
|
||||
|
||||
# Parse an SCL enable file to extract the environment variables set in it.
|
||||
#
|
||||
# @param path [String] Path to the enable file.
|
||||
# @return [Hash<String, String>]
|
||||
def parse_enable_file(path, env={})
|
||||
# Doesn't exist yet, so running Python will fail anyway. Just make sure
|
||||
# it fails in the expected way.
|
||||
return {} unless File.exist?(path)
|
||||
# Yes, this is a bash parser in regex. Feel free to be mad at me.
|
||||
IO.readlines(path).inject(env) do |memo, line|
|
||||
if match = line.match(/^export (\w+)=(.*)$/)
|
||||
memo[match[1]] = match[2].gsub(/\$(?:\{(\w+)(:\+:\$\{\w+\})?\}|(\w+))/) do
|
||||
key = $1 || $3
|
||||
value = (memo[key] || ENV[key]).to_s
|
||||
value = ":#{value}" if $2 && !value.empty?
|
||||
value
|
||||
end
|
||||
elsif match = line.match(/^\. scl_source enable (\w+)$/)
|
||||
# Parse another file.
|
||||
memo.update(parse_enable_file(::File.join('', 'opt', 'rh', match[1], 'enable'), memo))
|
||||
end
|
||||
memo
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def provides_auto?(node, resource)
|
||||
# They don't build 32-bit versions for these and only for RHEL/CentOS.
|
||||
# TODO: What do I do about Fedora and/or Amazon?
|
||||
return false unless node['kernel']['machine'] == 'x86_64' && node.platform?('redhat', 'centos')
|
||||
version = inversion_options(node, resource)['version']
|
||||
!!find_scl_package(node, version)
|
||||
end
|
||||
|
||||
# Set some default inversion provider options. Package name can't get
|
||||
# a default value here because that would complicate the handling of
|
||||
# {system_package_candidates}.
|
||||
#
|
||||
# @api private
|
||||
def default_inversion_options(node, resource)
|
||||
super.merge({
|
||||
# Install dev headers?
|
||||
dev_package: true,
|
||||
# Manual overrides for package name and/or version.
|
||||
package_name: nil,
|
||||
package_version: nil,
|
||||
# Set to true to use action :upgrade on system packages.
|
||||
package_upgrade: false,
|
||||
})
|
||||
end
|
||||
|
||||
def find_scl_package(node, version)
|
||||
platform_version = ::Gem::Version.create(node['platform_version'])
|
||||
# Filter out anything that doesn't match this EL version.
|
||||
candidate_packages = scl_packages.select {|p| p[:platform_version].satisfied_by?(platform_version) }
|
||||
# Find something with a prefix match on the Python version.
|
||||
candidate_packages.find {|p| p[:version].start_with?(version) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def scl_packages
|
||||
@scl_packages ||= []
|
||||
end
|
||||
|
||||
def scl_package(version, name, devel_name=nil, platform_version='>= 6.0')
|
||||
scl_packages << {version: version, name: name, devel_name: devel_name, platform_version: ::Gem::Requirement.create(platform_version)}
|
||||
end
|
||||
|
||||
def included(klass)
|
||||
super
|
||||
klass.extend(ClassMethods)
|
||||
end
|
||||
end
|
||||
|
||||
extend ClassMethods
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,159 @@
|
||||
#
|
||||
# Copyright 2015-2017, Noah Kantrowitz
|
||||
#
|
||||
# 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 'chef/resource'
|
||||
require 'chef/provider'
|
||||
require 'poise'
|
||||
|
||||
|
||||
module PoiseLanguages
|
||||
module Scl
|
||||
# A `poise_language_scl` resource to manage installing a language from
|
||||
# SCL packages. This is an internal implementation detail of
|
||||
# poise-languages.
|
||||
#
|
||||
# @api private
|
||||
# @since 1.0
|
||||
# @provides poise_languages_scl
|
||||
# @action install
|
||||
# @action uninstall
|
||||
class Resource < Chef::Resource
|
||||
include Poise
|
||||
provides(:poise_languages_scl)
|
||||
actions(:install, :upgrade, :uninstall)
|
||||
|
||||
# @!attribute package_name
|
||||
# Name of the SCL package for the language.
|
||||
# @return [String]
|
||||
attribute(:package_name, kind_of: String, name_attribute: true)
|
||||
# @!attribute dev_package
|
||||
# Name of the -devel package with headers and whatnot.
|
||||
# @return [String, nil]
|
||||
attribute(:dev_package, kind_of: [String, NilClass])
|
||||
# @!attribute version
|
||||
# Version of the SCL package(s) to install. If unset, follows the same
|
||||
# semantics as the core `package` resource.
|
||||
# @return [String, nil]
|
||||
attribute(:version, kind_of: [String, NilClass])
|
||||
# @!attribute parent
|
||||
# Resource for the language runtime. Used only for messages.
|
||||
# @return [Chef::Resource]
|
||||
attribute(:parent, kind_of: Chef::Resource, required: true)
|
||||
end
|
||||
|
||||
# The default provider for `poise_languages_scl`.
|
||||
#
|
||||
# @api private
|
||||
# @since 1.0
|
||||
# @see Resource
|
||||
# @provides poise_languages_scl
|
||||
class Provider < Chef::Provider
|
||||
include Poise
|
||||
provides(:poise_languages_scl)
|
||||
|
||||
# The `install` action for the `poise_languages_scl` resource.
|
||||
#
|
||||
# @return [void]
|
||||
def action_install
|
||||
notifying_block do
|
||||
install_scl_repo
|
||||
flush_yum_cache
|
||||
install_scl_package(:install)
|
||||
install_scl_devel_package(:install) if new_resource.dev_package
|
||||
end
|
||||
end
|
||||
|
||||
# The `upgrade` action for the `poise_languages_scl` resource.
|
||||
#
|
||||
# @return [void]
|
||||
def action_upgrade
|
||||
notifying_block do
|
||||
install_scl_repo
|
||||
flush_yum_cache
|
||||
install_scl_package(:upgrade)
|
||||
install_scl_devel_package(:upgrade) if new_resource.dev_package
|
||||
end
|
||||
end
|
||||
|
||||
# The `uninstall` action for the `poise_languages_scl` resource.
|
||||
#
|
||||
# @return [void]
|
||||
def action_uninstall
|
||||
notifying_block do
|
||||
uninstall_scl_devel_package if new_resource.dev_package
|
||||
uninstall_scl_package
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def install_scl_repo
|
||||
if node.platform?('redhat')
|
||||
# Set up the real RHSCL subscription.
|
||||
# NOTE: THIS IS NOT TESTED BECAUSE REDHAT DOESN'T OFFER ANY WAY TO DO
|
||||
# AUTOMATED TESTING. IF YOU USE REDHAT AND THIS BREAKS, PLEASE LET ME
|
||||
# KNOW BY FILING A GITHUB ISSUE AT http://github.com/poise/poise-languages/issues/new.
|
||||
repo_name = "rhel-server-rhscl-#{node['platform_version'][0]}-rpms"
|
||||
execute "subscription-manager repos --enable #{repo_name}" do
|
||||
not_if { shell_out!('subscription-manager repos --list-enabled').stdout.include?(repo_name) }
|
||||
end
|
||||
else
|
||||
package 'centos-release-scl-rh' do
|
||||
# Using upgrade here because changes very very rare and always
|
||||
# important when they happen. If this breaks your prod infra, I'm
|
||||
# sorry :-(
|
||||
action :upgrade
|
||||
retries 5
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def flush_yum_cache
|
||||
ruby_block 'flush_yum_cache' do
|
||||
block do
|
||||
# Equivalent to flush_cache after: true
|
||||
Chef::Provider::Package::Yum::YumCache.instance.reload
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def install_scl_package(action)
|
||||
package new_resource.package_name do
|
||||
action action
|
||||
retries 5
|
||||
version new_resource.version
|
||||
end
|
||||
end
|
||||
|
||||
def install_scl_devel_package(action)
|
||||
package new_resource.dev_package do
|
||||
action action
|
||||
retries 5
|
||||
version new_resource.version
|
||||
end
|
||||
end
|
||||
|
||||
def uninstall_scl_package
|
||||
install_scl_package(:remove)
|
||||
end
|
||||
|
||||
def uninstall_scl_devel_package
|
||||
install_scl_devel_package(:remove)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,34 @@
|
||||
#
|
||||
# Copyright 2015-2017, Noah Kantrowitz
|
||||
#
|
||||
# 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 'poise/utils'
|
||||
|
||||
|
||||
module PoiseLanguages
|
||||
# Helpers for installing languages from static archives.
|
||||
#
|
||||
# @since 1.1.0
|
||||
module Static
|
||||
autoload :Mixin, 'poise_languages/static/mixin'
|
||||
autoload :Resource, 'poise_languages/static/resource'
|
||||
autoload :Provider, 'poise_languages/static/resource'
|
||||
|
||||
Poise::Utils.parameterized_module(self) do |opts|
|
||||
require 'poise_languages/static/mixin'
|
||||
include PoiseLanguages::Static::Mixin(opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,144 @@
|
||||
#
|
||||
# Copyright 2015-2017, Noah Kantrowitz
|
||||
#
|
||||
# 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 'poise_languages/static/resource'
|
||||
|
||||
|
||||
module PoiseLanguages
|
||||
module Static
|
||||
# Mixin for language providers to install from static archives.
|
||||
#
|
||||
# @since 1.1.0
|
||||
module Mixin
|
||||
private
|
||||
|
||||
def install_static
|
||||
url = static_url
|
||||
poise_languages_static static_folder do
|
||||
source url
|
||||
strip_components options['strip_components']
|
||||
end
|
||||
end
|
||||
|
||||
def uninstall_static
|
||||
install_static.tap do |r|
|
||||
r.action(:uninstall)
|
||||
end
|
||||
end
|
||||
|
||||
def static_folder
|
||||
options['path'] || ::File.join('', 'opt', "#{self.class.static_name}-#{options['static_version']}")
|
||||
end
|
||||
|
||||
def static_url
|
||||
options['url'] % static_url_variables
|
||||
end
|
||||
|
||||
def static_url_variables
|
||||
{
|
||||
version: options['static_version'],
|
||||
kernel: node['kernel']['name'].downcase,
|
||||
machine: node['kernel']['machine'],
|
||||
machine_label: self.class.static_machine_label_wrapper(node, new_resource),
|
||||
}
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
attr_accessor :static_name
|
||||
attr_accessor :static_versions
|
||||
attr_accessor :static_machines
|
||||
attr_accessor :static_url
|
||||
attr_accessor :static_strip_components
|
||||
attr_accessor :static_retries
|
||||
|
||||
def provides_auto?(node, resource)
|
||||
# Check that the version starts with our project name and the machine
|
||||
# we are on is supported.
|
||||
resource.version.to_s =~ /^#{static_name}(-|$)/ && static_machines.include?(static_machine_label_wrapper(node, resource))
|
||||
end
|
||||
|
||||
# Set some default inversion provider options. Package name can't get
|
||||
# a default value here because that would complicate the handling of
|
||||
# {system_package_candidates}.
|
||||
#
|
||||
# @api private
|
||||
def default_inversion_options(node, resource)
|
||||
super.merge({
|
||||
# Path to install the package. Defaults to /opt/name-version.
|
||||
path: nil,
|
||||
# Number of times to retry failed downloads.
|
||||
retries: static_retries,
|
||||
# Full version number for use in interpolation.
|
||||
static_version: static_version(node, resource),
|
||||
# Value to pass to tar --strip-components.
|
||||
strip_components: static_strip_components,
|
||||
# URL template to download from.
|
||||
url: static_url,
|
||||
})
|
||||
end
|
||||
|
||||
def static_options(name: nil, versions: [], machines: %w{linux-i686 linux-x86_64}, url: nil, strip_components: 1, retries: 5)
|
||||
raise PoiseLanguages::Error.new("Static archive URL is required, on #{self}") unless url
|
||||
self.static_name = name || provides.to_s
|
||||
self.static_versions = versions
|
||||
self.static_machines = Set.new(machines)
|
||||
self.static_url = url
|
||||
self.static_strip_components = strip_components
|
||||
self.static_retries = retries
|
||||
end
|
||||
|
||||
def static_version(node, resource)
|
||||
raw_version = resource.version.to_s.gsub(/^#{static_name}(-|$)/, '')
|
||||
if static_versions.include?(raw_version)
|
||||
raw_version
|
||||
else
|
||||
# Prefix match or just use the given version number if not found.
|
||||
# This allow mild future proofing in some cases.
|
||||
static_versions.find {|v| v.start_with?(raw_version) } || raw_version
|
||||
end
|
||||
end
|
||||
|
||||
def static_machine_label(node, _resource=nil)
|
||||
"#{node['kernel']['name'].downcase}-#{node['kernel']['machine']}"
|
||||
end
|
||||
|
||||
# Wrapper for {#static_machine_label} because I need to add an argument.
|
||||
# This preserves backwards compat.
|
||||
#
|
||||
# @api private
|
||||
def static_machine_label_wrapper(node, resource)
|
||||
args = [node]
|
||||
arity = method(:static_machine_label).arity
|
||||
args << resource if arity > 1 || arity < 0
|
||||
static_machine_label(*args)
|
||||
end
|
||||
|
||||
def included(klass)
|
||||
super
|
||||
klass.extend ClassMethods
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
extend ClassMethods
|
||||
|
||||
Poise::Utils.parameterized_module(self) do |opts|
|
||||
static_options(opts)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,139 @@
|
||||
#
|
||||
# Copyright 2015-2017, Noah Kantrowitz
|
||||
#
|
||||
# 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 'chef/resource'
|
||||
require 'chef/provider'
|
||||
require 'poise'
|
||||
|
||||
|
||||
module PoiseLanguages
|
||||
module Static
|
||||
# A `poise_languages_static` resource to manage installing a language from
|
||||
# static binary archives. This is an internal implementation detail of
|
||||
# poise-languages.
|
||||
#
|
||||
# @api private
|
||||
# @since 1.1.0
|
||||
# @provides poise_languages_static
|
||||
# @action install
|
||||
# @action uninstall
|
||||
class Resource < Chef::Resource
|
||||
include Poise
|
||||
provides(:poise_languages_static)
|
||||
actions(:install, :uninstall)
|
||||
|
||||
# @!attribute path
|
||||
# Directory to install to.
|
||||
# @return [String]
|
||||
attribute(:path, kind_of: String, name_attribute: true)
|
||||
# @!attribute download_retries
|
||||
# Number of times to retry failed downloads. Defaults to 5.
|
||||
# @return [Integer]
|
||||
attribute(:download_retries, kind_of: Integer, default: 5)
|
||||
# @!attribute source
|
||||
# URL to download from.
|
||||
# @return [String]
|
||||
attribute(:source, kind_of: String, required: true)
|
||||
# @!attribute strip_components
|
||||
# Value to pass to tar --strip-components.
|
||||
# @return [String, Integer, nil]
|
||||
attribute(:strip_components, kind_of: [String, Integer, NilClass], default: 1)
|
||||
|
||||
def cache_path
|
||||
@cache_path ||= ::File.join(Chef::Config[:file_cache_path], source.split(/\//).last)
|
||||
end
|
||||
end
|
||||
|
||||
# The default provider for `poise_languages_static`.
|
||||
#
|
||||
# @api private
|
||||
# @since 1.0
|
||||
# @see Resource
|
||||
# @provides poise_languages_static
|
||||
class Provider < Chef::Provider
|
||||
include Poise
|
||||
provides(:poise_languages_static)
|
||||
|
||||
# The `install` action for the `poise_languages_static` resource.
|
||||
#
|
||||
# @return [void]
|
||||
def action_install
|
||||
notifying_block do
|
||||
download_archive
|
||||
create_directory
|
||||
# Unpack is handled as a notification from download_archive.
|
||||
end
|
||||
end
|
||||
|
||||
# The `uninstall` action for the `poise_languages_static` resource.
|
||||
#
|
||||
# @return [void]
|
||||
def action_uninstall
|
||||
notifying_block do
|
||||
delete_archive
|
||||
delete_directory
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_directory
|
||||
unpack_resource = unpack_archive
|
||||
directory new_resource.path do
|
||||
user 0
|
||||
group 0
|
||||
mode '755'
|
||||
notifies :unpack, unpack_resource, :immediately
|
||||
end
|
||||
end
|
||||
|
||||
def download_archive
|
||||
unpack_resource = unpack_archive
|
||||
remote_file new_resource.cache_path do
|
||||
source new_resource.source
|
||||
owner 0
|
||||
group 0
|
||||
mode '644'
|
||||
notifies :unpack, unpack_resource, :immediately if ::File.exist?(new_resource.path)
|
||||
retries new_resource.download_retries
|
||||
end
|
||||
end
|
||||
|
||||
def unpack_archive
|
||||
@unpack_archive ||= poise_archive new_resource.cache_path do
|
||||
# Run via notification from #download_archive and #create_directory.
|
||||
action :nothing
|
||||
destination new_resource.path
|
||||
strip_components new_resource.strip_components
|
||||
end
|
||||
end
|
||||
|
||||
def delete_archive
|
||||
file new_resource.cache_path do
|
||||
action :delete
|
||||
end
|
||||
end
|
||||
|
||||
def delete_directory
|
||||
directory new_resource.path do
|
||||
action :delete
|
||||
recursive true
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,24 @@
|
||||
#
|
||||
# Copyright 2015-2017, Noah Kantrowitz
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
|
||||
module PoiseLanguages
|
||||
module System
|
||||
autoload :Mixin, 'poise_languages/system/mixin'
|
||||
autoload :Resource, 'poise_languages/system/resource'
|
||||
autoload :Provider, 'poise_languages/system/resource'
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,170 @@
|
||||
#
|
||||
# Copyright 2015-2017, Noah Kantrowitz
|
||||
#
|
||||
# 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 'poise_languages/system/resource'
|
||||
|
||||
|
||||
module PoiseLanguages
|
||||
module System
|
||||
module Mixin
|
||||
|
||||
private
|
||||
|
||||
# Install a language using system packages.
|
||||
#
|
||||
# @api public
|
||||
# @return [PoiseLanguages::System::Resource]
|
||||
def install_system_packages(&block)
|
||||
dev_package_overrides = system_dev_package_overrides
|
||||
poise_languages_system system_package_name do
|
||||
# Otherwise use the default install action.
|
||||
action(:upgrade) if options['package_upgrade']
|
||||
parent new_resource
|
||||
# Don't pass true because we want the default computed behavior for that.
|
||||
dev_package options['dev_package'] unless options['dev_package'] == true
|
||||
dev_package_overrides dev_package_overrides
|
||||
package_version options['package_version'] if options['package_version']
|
||||
version options['version']
|
||||
instance_exec(&block) if block
|
||||
end
|
||||
end
|
||||
|
||||
# Uninstall a language using system packages.
|
||||
#
|
||||
# @api public
|
||||
# @return [PoiseLanguages::System::Resource]
|
||||
def uninstall_system_packages(&block)
|
||||
install_system_packages.tap do |r|
|
||||
r.action(:uninstall)
|
||||
r.instance_exec(&block) if block
|
||||
end
|
||||
end
|
||||
|
||||
# Compute all possible package names for a given language version. Must be
|
||||
# implemented by mixin users. Versions are expressed as prefixes so ''
|
||||
# matches all versions, '2' matches 2.x.
|
||||
#
|
||||
# @abstract
|
||||
# @api public
|
||||
# @param version [String] Language version prefix.
|
||||
# @return [Array<String>]
|
||||
def system_package_candidates(version)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
# Compute the default package name for the base package for this language.
|
||||
#
|
||||
# @api public
|
||||
# @return [String]
|
||||
def system_package_name
|
||||
# If we have an override, just use that.
|
||||
return options['package_name'] if options['package_name']
|
||||
# Look up all packages for this language on this platform.
|
||||
system_packages = self.class.packages && node.value_for_platform(self.class.packages)
|
||||
if !system_packages && self.class.default_package
|
||||
Chef::Log.debug("[#{new_resource}] No known packages for #{node['platform']} #{node['platform_version']}, defaulting to '#{self.class.default_package}'.") if self.class.packages
|
||||
system_packages = Array(self.class.default_package)
|
||||
end
|
||||
|
||||
# Find the first value on system_package_candidates that is in system_packages.
|
||||
system_package_candidates(options['version'].to_s).each do |name|
|
||||
return name if system_packages.include?(name)
|
||||
end
|
||||
# No valid candidate. Sad trombone.
|
||||
raise PoiseLanguages::Error.new("Unable to find a candidate package for version #{options['version'].to_s.inspect}. Please set package_name provider option for #{new_resource}.")
|
||||
end
|
||||
|
||||
# A hash mapping package names to their override dev package name.
|
||||
#
|
||||
# @api public
|
||||
# @return [Hash<String, String>]
|
||||
def system_dev_package_overrides
|
||||
{}
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Install this as a default provider if nothing else matched. Might not
|
||||
# work, but worth a try at least for unknown platforms. Windows is a
|
||||
# whole different story, and OS X might work sometimes so at least try.
|
||||
#
|
||||
# @api private
|
||||
def provides_auto?(node, resource)
|
||||
!node.platform_family?('windows')
|
||||
end
|
||||
|
||||
# Set some default inversion provider options. Package name can't get
|
||||
# a default value here because that would complicate the handling of
|
||||
# {system_package_candidates}.
|
||||
#
|
||||
# @api private
|
||||
def default_inversion_options(node, resource)
|
||||
super.merge({
|
||||
# Install dev headers?
|
||||
dev_package: true,
|
||||
# Manual overrides for package name and/or version.
|
||||
package_name: nil,
|
||||
package_version: nil,
|
||||
# Set to true to use action :upgrade on system packages.
|
||||
package_upgrade: false,
|
||||
})
|
||||
end
|
||||
|
||||
# @overload packages()
|
||||
# Return a hash formatted for value_for_platform returning an Array
|
||||
# of package names.
|
||||
# @return [Hash]
|
||||
# @overload packages(default_package, packages)
|
||||
# Define what system packages are available for this language on each
|
||||
# platform.
|
||||
# @param default_package [String] Default package name for platforms
|
||||
# not otherwise defined.
|
||||
# @param [Hash] Hash formatted for value_for_platform returning an
|
||||
# Array of package names.
|
||||
# @return [Hash]
|
||||
def packages(default_package=nil, packages=nil)
|
||||
self.default_package(default_package) if default_package
|
||||
if packages
|
||||
@packages = packages
|
||||
end
|
||||
@packages
|
||||
end
|
||||
|
||||
# @overload default_package()
|
||||
# Return the default package name for platforms not otherwise defined.
|
||||
# @return [String]
|
||||
# @overload default_package(name)
|
||||
# Set the default package name for platforms not defined in {packages}.
|
||||
# @param name [String] Package name.
|
||||
# @return [String]
|
||||
def default_package(name=nil)
|
||||
if name
|
||||
@default_package = name
|
||||
end
|
||||
@default_package
|
||||
end
|
||||
|
||||
# @api private
|
||||
def included(klass)
|
||||
super
|
||||
klass.extend(ClassMethods)
|
||||
end
|
||||
end
|
||||
|
||||
extend ClassMethods
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,254 @@
|
||||
#
|
||||
# Copyright 2015-2017, Noah Kantrowitz
|
||||
#
|
||||
# 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 'chef/resource'
|
||||
require 'chef/provider'
|
||||
require 'poise'
|
||||
|
||||
|
||||
module PoiseLanguages
|
||||
module System
|
||||
# A `poise_language_system` resource to manage installing a language from
|
||||
# system packages. This is an internal implementation detail of
|
||||
# poise-languages.
|
||||
#
|
||||
# @api private
|
||||
# @since 1.0
|
||||
# @provides poise_languages_system
|
||||
# @action install
|
||||
# @action upgrade
|
||||
# @action uninstall
|
||||
class Resource < Chef::Resource
|
||||
include Poise
|
||||
provides(:poise_languages_system)
|
||||
actions(:install, :upgrade, :uninstall)
|
||||
|
||||
# @!attribute package_name
|
||||
# Name of the main package for the language.
|
||||
# @return [String]
|
||||
attribute(:package_name, kind_of: String, name_attribute: true)
|
||||
# @!attribute dev_package
|
||||
# Name of the development headers package, or false to disable
|
||||
# installing headers. By default computed from {package_name}.
|
||||
# @return [String, false]
|
||||
attribute(:dev_package, kind_of: [String, FalseClass], default: lazy { default_dev_package })
|
||||
# @!attribute dev_package_overrides
|
||||
# A hash of override names for dev packages that don't match the normal
|
||||
# naming scheme.
|
||||
# @return [Hash<String, String>]
|
||||
attribute(:dev_package_overrides, kind_of: Hash, default: lazy { {} })
|
||||
# @!attribute package_version
|
||||
# Version of the package(s) to install. This is distinct from {version},
|
||||
# and is the specific version package version, not the language version.
|
||||
# By default this is unset meaning the latest version will be used.
|
||||
# @return [String, nil]
|
||||
attribute(:package_version, kind_of: [String, NilClass])
|
||||
# @!attribute parent
|
||||
# Resource for the language runtime. Used only for messages.
|
||||
# @return [Chef::Resource]
|
||||
attribute(:parent, kind_of: Chef::Resource, required: true)
|
||||
# @!attributes version
|
||||
# Language version prefix. This prefix determines which version of the
|
||||
# language to install, following prefix matching rules.
|
||||
# @return [String]
|
||||
attribute(:version, kind_of: String, default: '')
|
||||
|
||||
# Compute the default package name for the development headers.
|
||||
#
|
||||
# @return [String]
|
||||
def default_dev_package
|
||||
# Check for an override.
|
||||
return dev_package_overrides[package_name] if dev_package_overrides.include?(package_name)
|
||||
suffix = node.value_for_platform_family(debian: '-dev', rhel: '-devel', fedora: '-devel')
|
||||
# Platforms like Arch and Gentoo don't need this anyway. I've got no
|
||||
# clue how Amazon Linux does this.
|
||||
if suffix
|
||||
package_name + suffix
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# The default provider for `poise_languages_system`.
|
||||
#
|
||||
# @api private
|
||||
# @since 1.0
|
||||
# @see Resource
|
||||
# @provides poise_languages_system
|
||||
class Provider < Chef::Provider
|
||||
include Poise
|
||||
provides(:poise_languages_system)
|
||||
|
||||
# The `install` action for the `poise_languages_system` resource.
|
||||
#
|
||||
# @return [void]
|
||||
def action_install
|
||||
notifying_block do
|
||||
install_packages
|
||||
run_action_hack
|
||||
end
|
||||
end
|
||||
|
||||
# The `upgrade` action for the `poise_languages_system` resource.
|
||||
#
|
||||
# @return [void]
|
||||
def action_upgrade
|
||||
notifying_block do
|
||||
upgrade_packages
|
||||
run_action_hack
|
||||
end
|
||||
end
|
||||
|
||||
# The `uninstall` action for the `poise_languages_system` resource.
|
||||
#
|
||||
# @return [void]
|
||||
def action_uninstall
|
||||
notifying_block do
|
||||
uninstall_packages
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Install the needed language packages.
|
||||
#
|
||||
# @api private
|
||||
# @return [Array<Chef::Resource>]
|
||||
def install_packages
|
||||
packages = {new_resource.package_name => new_resource.package_version}
|
||||
# If we are supposed to install the dev package, grab it using the same
|
||||
# version as the main package.
|
||||
if new_resource.dev_package
|
||||
packages[new_resource.dev_package] = new_resource.package_version
|
||||
end
|
||||
Chef::Log.debug("[#{new_resource.parent}] Building package resource using #{packages.inspect}.")
|
||||
|
||||
# Check for multi-package support.
|
||||
package_resource_class = Chef::Resource.resource_for_node(:package, node)
|
||||
package_provider_class = package_resource_class.new('multipackage_check', run_context).provider_for_action(:install)
|
||||
package_resources = if package_provider_class.respond_to?(:use_multipackage_api?) && package_provider_class.use_multipackage_api?
|
||||
package packages.keys do
|
||||
version packages.values
|
||||
end
|
||||
else
|
||||
# Fallback for non-multipackage.
|
||||
packages.map do |pkg_name, pkg_version|
|
||||
package pkg_name do
|
||||
version pkg_version
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Apply some settings to all of the resources.
|
||||
Array(package_resources).each do |res|
|
||||
res.retries(5)
|
||||
res.define_singleton_method(:apply_action_hack?) { true }
|
||||
end
|
||||
end
|
||||
|
||||
# Upgrade the needed language packages.
|
||||
#
|
||||
# @api private
|
||||
# @return [Array<Chef::Resource>]
|
||||
def upgrade_packages
|
||||
install_packages.each do |res|
|
||||
res.action(:upgrade)
|
||||
end
|
||||
end
|
||||
|
||||
# Uninstall the needed language packages.
|
||||
#
|
||||
# @api private
|
||||
# @return [Array<Chef::Resource>]
|
||||
def uninstall_packages
|
||||
install_packages.each do |res|
|
||||
res.action(node.platform_family?('debian') ? :purge : :remove)
|
||||
end
|
||||
end
|
||||
|
||||
# Run the requested action for all package resources. This exists because
|
||||
# we inject our version check in to the provider directly and I want to
|
||||
# only run the provider action once for performance. It is otherwise
|
||||
# mostly a stripped down version of Chef::Resource#run_action.
|
||||
#
|
||||
# @param action [Symbol] Action to run on all package resources.
|
||||
# @return [void]
|
||||
def run_action_hack
|
||||
# If new_resource.package_version is set, skip this madness.
|
||||
return if new_resource.package_version
|
||||
|
||||
# Process every resource in the current collection, which is bounded
|
||||
# by notifying_block.
|
||||
run_context.resource_collection.each do |resource|
|
||||
# Only apply to things we tagged above.
|
||||
next unless resource.respond_to?(:apply_action_hack?) && resource.apply_action_hack?
|
||||
|
||||
Array(resource.action).each do |action|
|
||||
# Reset it so we have a clean baseline.
|
||||
resource.updated_by_last_action(false)
|
||||
# Grab the provider.
|
||||
provider = resource.provider_for_action(action)
|
||||
provider.action = action
|
||||
# Inject our check for the candidate version. This will actually
|
||||
# get run during run_action below.
|
||||
patch_load_current_resource!(provider, new_resource.version)
|
||||
# Run our action.
|
||||
Chef::Log.debug("[#{new_resource.parent}] Running #{provider} with #{action}")
|
||||
provider.run_action(action)
|
||||
# Check updated flag.
|
||||
new_resource.updated_by_last_action(true) if resource.updated_by_last_action?
|
||||
end
|
||||
|
||||
# Make sure the resource doesn't run again when notifying_block ends.
|
||||
resource.action(:nothing)
|
||||
end
|
||||
end
|
||||
|
||||
# Hack a provider object to run our verification code.
|
||||
#
|
||||
# @param provider [Chef::Provider] Provider object to patch.
|
||||
# @param version [String] Language version prefix to check for.
|
||||
# @return [void]
|
||||
def patch_load_current_resource!(provider, version)
|
||||
# Create a closure module and inject it.
|
||||
provider.extend Module.new {
|
||||
# Patch load_current_resource to run our verification logic after
|
||||
# the normal code.
|
||||
define_method(:load_current_resource) do
|
||||
super().tap do |_|
|
||||
each_package do |package_name, new_version, current_version, candidate_version|
|
||||
# In Chef 12.14+, candidate_version is a Chef::Decorator::Lazy object
|
||||
# so we need the nil? check to see if the object being proxied is
|
||||
# nil (i.e. there is no version). The `\d+:` is for RPM epoch prefixes.
|
||||
unless candidate_version && (!candidate_version.nil?) && (!candidate_version.empty?) && candidate_version =~ /^(\d+:)?#{Regexp.escape(version)}/
|
||||
# Don't display a wonky error message if there is no candidate.
|
||||
candidate_label = if candidate_version && (!candidate_version.nil?) && (!candidate_version.empty?)
|
||||
candidate_version
|
||||
else
|
||||
candidate_version.inspect
|
||||
end
|
||||
raise PoiseLanguages::Error.new("Package #{package_name} would install #{candidate_label}, which does not match #{version.empty? ? version.inspect : version}. Please set the package_name or package_version provider options.")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,68 @@
|
||||
#
|
||||
# Copyright 2015-2017, Noah Kantrowitz
|
||||
#
|
||||
# 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 'shellwords'
|
||||
|
||||
require 'poise_languages/utils/which'
|
||||
|
||||
|
||||
module PoiseLanguages
|
||||
module Utils
|
||||
include Which
|
||||
extend self
|
||||
|
||||
# Default whitelist for {#shelljoin}.
|
||||
SHELLJOIN_WHITELIST = [/^2?[><]/]
|
||||
|
||||
# An improved version of Shellwords.shelljoin that doesn't escape a few
|
||||
# things.
|
||||
#
|
||||
# @param cmd [Array<String>] Command array to join.
|
||||
# @param whitelist [Array<Regexp>] Array of patterns to whitelist.
|
||||
# @return [String]
|
||||
def shelljoin(cmd, whitelist: SHELLJOIN_WHITELIST)
|
||||
cmd.map do |str|
|
||||
if whitelist.any? {|pat| str =~ pat }
|
||||
str
|
||||
else
|
||||
Shellwords.shellescape(str)
|
||||
end
|
||||
end.join(' ')
|
||||
end
|
||||
|
||||
# Convert the executable in a string or array command to an absolute path.
|
||||
#
|
||||
# @param cmd [String, Array<String>] Command to fix up.
|
||||
# @param path [String, nil] Replacement $PATH for executable lookup.
|
||||
# @return [String, Array<String>]
|
||||
def absolute_command(cmd, path: nil)
|
||||
was_array = cmd.is_a?(Array)
|
||||
cmd = if was_array
|
||||
cmd.dup
|
||||
else
|
||||
Shellwords.split(cmd)
|
||||
end
|
||||
# Don't try to touch anything if the first value looks like a flag or a path.
|
||||
if cmd.first && !cmd.first.start_with?('-') && !cmd.first.include?(::File::SEPARATOR)
|
||||
# If which returns false, just leave it I guess.
|
||||
cmd[0] = which(cmd.first, path: path) || cmd.first
|
||||
end
|
||||
cmd = shelljoin(cmd) unless was_array
|
||||
cmd
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,51 @@
|
||||
#
|
||||
# Copyright 2015-2017, Noah Kantrowitz
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
|
||||
module PoiseLanguages
|
||||
module Utils
|
||||
# Replacement module for Chef::Mixin::Which with a slight improvement.
|
||||
#
|
||||
# @since 1.0.0
|
||||
# @see Which#which
|
||||
module Which
|
||||
extend self
|
||||
|
||||
# A replacement for Chef::Mixin::Which#which that allows using something
|
||||
# other than an environment variable if needed.
|
||||
#
|
||||
# @param cmd [String] Executable to search for.
|
||||
# @param extra_path [Array<String>] Extra directories to always search.
|
||||
# @param path [String, nil] Replacement $PATH value.
|
||||
# @return [String, false]
|
||||
def which(cmd, extra_path: %w{/bin /usr/bin /sbin /usr/sbin}, path: nil)
|
||||
# If it was already absolute, just return that.
|
||||
return cmd if cmd =~ /^(\/|([a-z]:)?\\)/i
|
||||
# Allow passing something other than the real env var.
|
||||
path ||= ENV['PATH']
|
||||
# Based on Chef::Mixin::Which#which
|
||||
# Copyright 2010-2017, Chef Softare, Inc.
|
||||
paths = path.split(File::PATH_SEPARATOR) + extra_path
|
||||
paths.each do |candidate_path|
|
||||
filename = ::File.join(candidate_path, cmd)
|
||||
return filename if ::File.executable?(filename)
|
||||
end
|
||||
false
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,20 @@
|
||||
#
|
||||
# Copyright 2015-2017, Noah Kantrowitz
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
|
||||
module PoiseLanguages
|
||||
VERSION = '2.1.2'
|
||||
end
|
||||
Reference in New Issue
Block a user