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:
Greg Karékinian
2019-10-13 18:32:56 +02:00
parent aa66743166
commit 049d5dd006
1245 changed files with 100630 additions and 0 deletions

View 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!

View File

@@ -0,0 +1,27 @@
# Poise-Languages Cookbook
[![Build Status](https://img.shields.io/travis/poise/poise-languages.svg)](https://travis-ci.org/poise/poise-languages)
[![Gem Version](https://img.shields.io/gem/v/poise-languages.svg)](https://rubygems.org/gems/poise-languages)
[![Cookbook Version](https://img.shields.io/cookbook/v/poise-languages.svg)](https://supermarket.chef.io/cookbooks/poise-languages)
[![Coverage](https://img.shields.io/codecov/c/github/poise/poise-languages.svg)](https://codecov.io/github/poise/poise-languages)
[![Gemnasium](https://img.shields.io/gemnasium/poise/poise-languages.svg)](https://gemnasium.com/poise/poise-languages)
[![License](https://img.shields.io/badge/license-Apache_2-blue.svg)](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.

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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__)

View 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[![Build Status](https://img.shields.io/travis/poise/poise-languages.svg)](https://travis-ci.org/poise/poise-languages)\n[![Gem Version](https://img.shields.io/gem/v/poise-languages.svg)](https://rubygems.org/gems/poise-languages)\n[![Cookbook Version](https://img.shields.io/cookbook/v/poise-languages.svg)](https://supermarket.chef.io/cookbooks/poise-languages)\n[![Coverage](https://img.shields.io/codecov/c/github/poise/poise-languages.svg)](https://codecov.io/github/poise/poise-languages)\n[![Gemnasium](https://img.shields.io/gemnasium/poise/poise-languages.svg)](https://gemnasium.com/poise/poise-languages)\n[![License](https://img.shields.io/badge/license-Apache_2-blue.svg)](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":[]}