Greg Karékinian a32f34b408 Vendor the external cookbooks
Knife-Zero doesn't include Berkshelf support, so vendoring everything in
the repo is convenient again
2019-10-13 19:17:42 +02:00

242 lines
8.9 KiB
Ruby

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