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:
89
cookbooks/poise-languages/CHANGELOG.md
Normal file
89
cookbooks/poise-languages/CHANGELOG.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# Changelog
|
||||
|
||||
## v2.1.2
|
||||
|
||||
* Drop support for Chef that uses Ruby 2.1 (<= 12.13).
|
||||
* Fix handling of RPM epoch prefixes in the system package resource.
|
||||
|
||||
## v2.1.1
|
||||
|
||||
* Fix the SCL repository enable command for RHEL.
|
||||
* Internal refactoring of the system package installer.
|
||||
|
||||
## v2.1.0
|
||||
|
||||
* Allow customizing properties on the system package install resource via a block
|
||||
|
||||
## v2.0.5
|
||||
|
||||
* Fixes to work with the latest Chef (again).
|
||||
|
||||
## v2.0.4
|
||||
|
||||
* Fixes to work with the latest Chef.
|
||||
|
||||
## v2.0.3
|
||||
|
||||
* Correct the subscription repository name used for SCLs on RedHat.
|
||||
|
||||
## v2.0.2
|
||||
|
||||
* Don't try to use SCL providers on Amazon Linux.
|
||||
|
||||
## v2.0.1
|
||||
|
||||
* Don't error on `Chef::Decorator::Lazy` proxy objects for `candidate_version`.
|
||||
* Retry system and SCL package installs because transient network failures.
|
||||
|
||||
## v2.0.0
|
||||
|
||||
* Backwards-incompatible change to SCL management to comply with their new repo
|
||||
packages and layout. Uses `centos-release-scl-rh` repo package or the
|
||||
`rhel-variant-rhscl` RedHat subscription.
|
||||
|
||||
## v1.4.0
|
||||
|
||||
* Use `poise-archive` to unpack static binary archives. This should work better
|
||||
on AIX and Solaris, as well as making it easier to add more archive formats in
|
||||
the future.
|
||||
|
||||
## v1.3.3
|
||||
|
||||
* [#3](https://github.com/poise/poise-languages/pull/3) Fix `static` binary
|
||||
installation on AIX and Solaris.
|
||||
* Only run the candidate version check for `system` installs when we aren't
|
||||
passing in package_version.
|
||||
|
||||
## v1.3.2
|
||||
|
||||
* Handle static archive unpacking correctly when a single download is shared
|
||||
between two paths.
|
||||
|
||||
## v1.3.1
|
||||
|
||||
* Fix system package installs on OS X.
|
||||
|
||||
## v1.3.0
|
||||
|
||||
* `%{machine_label}` is available in URL template for static download.
|
||||
* Automatically retry `remote_file` downloads to handle transient HTTP failures.
|
||||
* All `*_shell_out` language command helpers use `poise_shell_out` to set `$HOME`
|
||||
and other environment variables by default.
|
||||
|
||||
## v1.2.0
|
||||
|
||||
* Support for installing development headers with SCL providers.
|
||||
* Add `PoiseLanguages::Utils.shelljoin` for encoding command arrays with some
|
||||
bash metadata characters allowed.
|
||||
* [#1](https://github.com/poise/poise-languages/pull/1) Fix typo in gemspec.
|
||||
|
||||
## v1.1.0
|
||||
|
||||
* Add helpers for installing from static archives.
|
||||
* Improve auto-selection rules for system and SCL providers.
|
||||
* Support SCL packages that depend on other SCL packages.
|
||||
* Support Ruby 2.0 again.
|
||||
|
||||
## v1.0.0
|
||||
|
||||
* Initial release!
|
||||
27
cookbooks/poise-languages/README.md
Normal file
27
cookbooks/poise-languages/README.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Poise-Languages Cookbook
|
||||
|
||||
[](https://travis-ci.org/poise/poise-languages)
|
||||
[](https://rubygems.org/gems/poise-languages)
|
||||
[](https://supermarket.chef.io/cookbooks/poise-languages)
|
||||
[](https://codecov.io/github/poise/poise-languages)
|
||||
[](https://gemnasium.com/poise/poise-languages)
|
||||
[](https://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
||||
Shared support code for Poise's language cookbooks like poise-ruby and
|
||||
poise-python.
|
||||
|
||||
## License
|
||||
|
||||
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.
|
||||
@@ -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
|
||||
18
cookbooks/poise-languages/libraries/default.rb
Normal file
18
cookbooks/poise-languages/libraries/default.rb
Normal file
@@ -0,0 +1,18 @@
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
raise 'Halite is not compatible with no_lazy_load false, please set no_lazy_load true in your Chef configuration file.' unless Chef::Config[:no_lazy_load]
|
||||
$LOAD_PATH << File.expand_path('../../files/halite_gem', __FILE__)
|
||||
1
cookbooks/poise-languages/metadata.json
Normal file
1
cookbooks/poise-languages/metadata.json
Normal file
@@ -0,0 +1 @@
|
||||
{"name":"poise-languages","version":"2.1.2","description":"A Chef cookbook to help writing language cookbooks.","long_description":"# Poise-Languages Cookbook\n\n[](https://travis-ci.org/poise/poise-languages)\n[](https://rubygems.org/gems/poise-languages)\n[](https://supermarket.chef.io/cookbooks/poise-languages)\n[](https://codecov.io/github/poise/poise-languages)\n[](https://gemnasium.com/poise/poise-languages)\n[](https://www.apache.org/licenses/LICENSE-2.0)\n\nShared support code for Poise's language cookbooks like poise-ruby and\npoise-python.\n\n## License\n\nCopyright 2015-2017, Noah Kantrowitz\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n","maintainer":"Noah Kantrowitz","maintainer_email":"noah@coderanger.net","license":"Apache-2.0","platforms":{"aix":">= 0.0.0","amazon":">= 0.0.0","arch":">= 0.0.0","centos":">= 0.0.0","chefspec":">= 0.0.0","debian":">= 0.0.0","dragonfly4":">= 0.0.0","fedora":">= 0.0.0","freebsd":">= 0.0.0","gentoo":">= 0.0.0","ios_xr":">= 0.0.0","mac_os_x":">= 0.0.0","nexus":">= 0.0.0","omnios":">= 0.0.0","openbsd":">= 0.0.0","opensuse":">= 0.0.0","oracle":">= 0.0.0","raspbian":">= 0.0.0","redhat":">= 0.0.0","slackware":">= 0.0.0","smartos":">= 0.0.0","solaris2":">= 0.0.0","suse":">= 0.0.0","ubuntu":">= 0.0.0","windows":">= 0.0.0"},"dependencies":{"poise":"~> 2.5","poise-archive":"~> 1.0"},"recommendations":{},"suggestions":{},"conflicting":{},"providing":{},"replacing":{},"attributes":{},"groupings":{},"recipes":{},"source_url":"https://github.com/poise/poise-languages","issues_url":"https://github.com/poise/poise-languages/issues","chef_version":[["< 15",">= 12.14"]],"ohai_version":[]}
|
||||
Reference in New Issue
Block a user