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,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
|
||||
Reference in New Issue
Block a user