Implement the resolve_arguments function

fixes #10006
This commit is contained in:
Gunnar Beutner 2015-08-27 08:22:35 +02:00
parent 1a0dd27b3a
commit 4418688f34
7 changed files with 264 additions and 184 deletions

View File

@ -196,6 +196,29 @@ value of arbitrary macro expressions:
return "Some text"
}}
The `resolve_arguments` can be used to resolve a command and its arguments much in
the same fashion Icinga does this for the `command` and `arguments` attributes for
commands. The `by_ssh` command uses this functionality to let users specify a
command and arguments that should be executed via SSH:
arguments = {
"-C" = {{
var command = macro("$by_ssh_command$")
var arguments = macro("$by_ssh_arguments$")
if (typeof(command) == String && !arguments) {
return command
}
var escaped_args = []
for (arg in resolve_arguments(command, arguments)) {
escaped_args.add(escape_shell_arg(arg))
}
return escaped_args.join(" ")
}}
...
}
Acessing object attributes at runtime inside these functions is described in the
[advanced topics](5-advanced-topics.md#access-object-attributes-at-runtime) chapter.

View File

@ -96,7 +96,8 @@ Name | Description
----------------|--------------
by_ssh_address | **Optional.** The host's address. Defaults to "$address$" if the host's `address` attribute is set, "$address6$" otherwise.
by_ssh_port | **Optional.** The SSH port. Defaults to 22.
by_ssh_command | **Optional.** The command that should be executed.
by_ssh_command | **Required.** The command that should be executed. Can be an array if multiple arguments should be passed to `check_by_ssh`.
by_ssh_arguments| **Optional.** A dictionary with arguments for the command. This works exactly like the 'arguments' dictionary for ordinary CheckCommands.
by_ssh_logname | **Optional.** The SSH username.
by_ssh_identity | **Optional.** The SSH identity.
by_ssh_quiet | **Optional.** Whether to suppress SSH warnings. Defaults to false.

View File

@ -1277,7 +1277,20 @@ object CheckCommand "by_ssh" {
arguments = {
"-H" = "$by_ssh_address$"
"-p" = "$by_ssh_port$"
"-C" = "$by_ssh_command$"
"-C" = {{
var command = macro("$by_ssh_command$")
var arguments = macro("$by_ssh_arguments$")
if (typeof(command) == String && !arguments) {
return command
}
var escaped_args = []
for (arg in resolve_arguments(command, arguments)) {
escaped_args.add(escape_shell_arg(arg))
}
return escaped_args.join(" ")
}}
"-l" = "$by_ssh_logname$"
"-i" = "$by_ssh_identity$"
"-q" = {

View File

@ -26,6 +26,8 @@
#include "base/context.hpp"
#include "base/dynamicobject.hpp"
#include "base/scriptframe.hpp"
#include "base/convert.hpp"
#include "base/exception.hpp"
#include <boost/foreach.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/join.hpp>
@ -36,7 +38,7 @@ using namespace icinga;
Value MacroProcessor::ResolveMacros(const Value& str, const ResolverList& resolvers,
const CheckResult::Ptr& cr, String *missingMacro,
const MacroProcessor::EscapeCallback& escapeFn, const Dictionary::Ptr& resolvedMacros,
bool useResolvedMacros)
bool useResolvedMacros, int recursionLevel)
{
Value result;
@ -45,7 +47,7 @@ Value MacroProcessor::ResolveMacros(const Value& str, const ResolverList& resolv
if (str.IsScalar()) {
result = InternalResolveMacros(str, resolvers, cr, missingMacro, escapeFn,
resolvedMacros, useResolvedMacros);
resolvedMacros, useResolvedMacros, recursionLevel + 1);
} else if (str.IsObjectType<Array>()) {
Array::Ptr resultArr = new Array();
Array::Ptr arr = str;
@ -55,7 +57,7 @@ Value MacroProcessor::ResolveMacros(const Value& str, const ResolverList& resolv
BOOST_FOREACH(const Value& arg, arr) {
/* Note: don't escape macros here. */
Value value = InternalResolveMacros(arg, resolvers, cr, missingMacro,
EscapeCallback(), resolvedMacros, useResolvedMacros);
EscapeCallback(), resolvedMacros, useResolvedMacros, recursionLevel + 1);
if (value.IsObjectType<Array>())
resultArr->Add(Utility::Join(value, ';'));
@ -73,7 +75,7 @@ Value MacroProcessor::ResolveMacros(const Value& str, const ResolverList& resolv
BOOST_FOREACH(const Dictionary::Pair& kv, dict) {
/* Note: don't escape macros here. */
resultDict->Set(kv.first, InternalResolveMacros(kv.second, resolvers, cr, missingMacro,
EscapeCallback(), resolvedMacros, useResolvedMacros));
EscapeCallback(), resolvedMacros, useResolvedMacros, recursionLevel + 1));
}
result = resultDict;
@ -187,6 +189,17 @@ Value MacroProcessor::InternalResolveMacrosShim(const std::vector<Value>& args,
resolvedMacros, useResolvedMacros, recursionLevel);
}
Value MacroProcessor::InternalResolveArgumentsShim(const std::vector<Value>& args, const ResolverList& resolvers,
const CheckResult::Ptr& cr, const Dictionary::Ptr& resolvedMacros,
bool useResolvedMacros, int recursionLevel)
{
if (args.size() < 2)
BOOST_THROW_EXCEPTION(std::invalid_argument("Too few arguments for function"));
return MacroProcessor::ResolveArguments(args[0], args[1], resolvers, cr,
resolvedMacros, useResolvedMacros, recursionLevel);
}
Value MacroProcessor::EvaluateFunction(const Function::Ptr& func, const ResolverList& resolvers,
const CheckResult::Ptr& cr, const MacroProcessor::EscapeCallback& escapeFn,
const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros, int recursionLevel)
@ -200,6 +213,9 @@ Value MacroProcessor::EvaluateFunction(const Function::Ptr& func, const Resolver
resolvers_this->Set("macro", new Function(boost::bind(&MacroProcessor::InternalResolveMacrosShim,
_1, boost::cref(resolvers), cr, MacroProcessor::EscapeCallback(), resolvedMacros, useResolvedMacros,
recursionLevel + 1)));
resolvers_this->Set("resolve_arguments", new Function(boost::bind(&MacroProcessor::InternalResolveArgumentsShim,
_1, boost::cref(resolvers), cr, resolvedMacros, useResolvedMacros,
recursionLevel + 1)));
ScriptFrame frame(resolvers_this);
return func->Invoke();
@ -250,7 +266,7 @@ Value MacroProcessor::InternalResolveMacros(const String& str, const ResolverLis
if (resolved_macro.IsObjectType<Function>()) {
resolved_macro = EvaluateFunction(resolved_macro, resolvers, cr, escapeFn,
resolvedMacros, useResolvedMacros, recursionLevel);
resolvedMacros, useResolvedMacros, recursionLevel + 1);
}
if (!found) {
@ -334,3 +350,179 @@ bool MacroProcessor::ValidateMacroString(const String& macro)
return true;
}
void MacroProcessor::AddArgumentHelper(const Array::Ptr& args, const String& key, const String& value,
bool add_key, bool add_value)
{
if (add_key)
args->Add(key);
if (add_value)
args->Add(value);
}
Value MacroProcessor::EscapeMacroShellArg(const Value& value)
{
String result;
if (value.IsObjectType<Array>()) {
Array::Ptr arr = value;
ObjectLock olock(arr);
BOOST_FOREACH(const Value& arg, arr) {
if (result.GetLength() > 0)
result += " ";
result += Utility::EscapeShellArg(arg);
}
} else
result = Utility::EscapeShellArg(value);
return result;
}
struct CommandArgument
{
int Order;
bool SkipKey;
bool RepeatKey;
bool SkipValue;
String Key;
Value AValue;
CommandArgument(void)
: Order(0), SkipKey(false), RepeatKey(true), SkipValue(false)
{ }
bool operator<(const CommandArgument& rhs) const
{
return Order < rhs.Order;
}
};
Value MacroProcessor::ResolveArguments(const Value& command, const Dictionary::Ptr& arguments,
const MacroProcessor::ResolverList& resolvers, const CheckResult::Ptr& cr,
const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros, int recursionLevel)
{
Value resolvedCommand;
if (!arguments || command.IsObjectType<Array>() || command.IsObjectType<Function>())
resolvedCommand = MacroProcessor::ResolveMacros(command, resolvers, cr, NULL,
EscapeMacroShellArg, resolvedMacros, useResolvedMacros, recursionLevel + 1);
else {
Array::Ptr arr = new Array();
arr->Add(command);
resolvedCommand = arr;
}
if (arguments) {
std::vector<CommandArgument> args;
ObjectLock olock(arguments);
BOOST_FOREACH(const Dictionary::Pair& kv, arguments) {
const Value& arginfo = kv.second;
CommandArgument arg;
arg.Key = kv.first;
bool required = false;
Value argval;
if (arginfo.IsObjectType<Dictionary>()) {
Dictionary::Ptr argdict = arginfo;
if (argdict->Contains("key"))
arg.Key = argdict->Get("key");
argval = argdict->Get("value");
if (argdict->Contains("required"))
required = argdict->Get("required");
arg.SkipKey = argdict->Get("skip_key");
if (argdict->Contains("repeat_key"))
arg.RepeatKey = argdict->Get("repeat_key");
arg.Order = argdict->Get("order");
Value set_if = argdict->Get("set_if");
if (!set_if.IsEmpty()) {
String missingMacro;
Value set_if_resolved = MacroProcessor::ResolveMacros(set_if, resolvers,
cr, &missingMacro, MacroProcessor::EscapeCallback(), resolvedMacros,
useResolvedMacros, recursionLevel + 1);
if (!missingMacro.IsEmpty())
continue;
int value;
if (set_if_resolved == "true")
value = 1;
else if (set_if_resolved == "false")
value = 0;
else {
try {
value = Convert::ToLong(set_if_resolved);
} catch (const std::exception& ex) {
/* tried to convert a string */
Log(LogWarning, "PluginUtility")
<< "Error evaluating set_if value '" << set_if_resolved << "': " << ex.what();
continue;
}
}
if (!value)
continue;
}
}
else
argval = arginfo;
if (argval.IsEmpty())
arg.SkipValue = true;
String missingMacro;
arg.AValue = MacroProcessor::ResolveMacros(argval, resolvers,
cr, &missingMacro, MacroProcessor::EscapeCallback(), resolvedMacros,
useResolvedMacros, recursionLevel + 1);
if (!missingMacro.IsEmpty()) {
if (required) {
BOOST_THROW_EXCEPTION(ScriptError("Non-optional macro '" + missingMacro + "' used in argument '" +
arg.Key + "' is missing."));
}
continue;
}
args.push_back(arg);
}
std::sort(args.begin(), args.end());
Array::Ptr command_arr = resolvedCommand;
BOOST_FOREACH(const CommandArgument& arg, args) {
if (arg.AValue.IsObjectType<Dictionary>()) {
Log(LogWarning, "PluginUtility", "Tried to use dictionary in argument");
continue;
} else if (arg.AValue.IsObjectType<Array>()) {
bool first = true;
Array::Ptr arr = static_cast<Array::Ptr>(arg.AValue);
ObjectLock olock(arr);
BOOST_FOREACH(const Value& value, arr) {
bool add_key;
if (first) {
first = false;
add_key = !arg.SkipKey;
} else
add_key = !arg.SkipKey && arg.RepeatKey;
AddArgumentHelper(command_arr, arg.Key, value, add_key, !arg.SkipValue);
}
} else
AddArgumentHelper(command_arr, arg.Key, arg.AValue, !arg.SkipKey, !arg.SkipValue);
}
}
return resolvedCommand;
}

View File

@ -45,7 +45,11 @@ public:
const CheckResult::Ptr& cr = CheckResult::Ptr(), String *missingMacro = NULL,
const EscapeCallback& escapeFn = EscapeCallback(),
const Dictionary::Ptr& resolvedMacros = Dictionary::Ptr(),
bool useResolvedMacros = false);
bool useResolvedMacros = false, int recursionLevel = 0);
static Value ResolveArguments(const Value& command, const Dictionary::Ptr& arguments,
const MacroProcessor::ResolverList& resolvers, const CheckResult::Ptr& cr,
const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros, int recursionLevel = 0);
static bool ValidateMacroString(const String& macro);
@ -62,10 +66,17 @@ private:
static Value InternalResolveMacrosShim(const std::vector<Value>& args, const ResolverList& resolvers,
const CheckResult::Ptr& cr, const MacroProcessor::EscapeCallback& escapeFn,
const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros, int recursionLevel);
static Value InternalResolveArgumentsShim(const std::vector<Value>& args, const ResolverList& resolvers,
const CheckResult::Ptr& cr, const Dictionary::Ptr& resolvedMacros,
bool useResolvedMacros, int recursionLevel);
static Value EvaluateFunction(const Function::Ptr& func, const ResolverList& resolvers,
const CheckResult::Ptr& cr, const MacroProcessor::EscapeCallback& escapeFn,
const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros, int recursionLevel);
static void AddArgumentHelper(const Array::Ptr& args, const String& key, const String& value,
bool add_key, bool add_value);
static Value EscapeMacroShellArg(const Value& value);
};
}

View File

@ -25,6 +25,7 @@
#include "base/convert.hpp"
#include "base/process.hpp"
#include "base/objectlock.hpp"
#include "base/exception.hpp"
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/trim.hpp>
@ -32,54 +33,6 @@
using namespace icinga;
struct CommandArgument
{
int Order;
bool SkipKey;
bool RepeatKey;
bool SkipValue;
String Key;
Value AValue;
CommandArgument(void)
: Order(0), SkipKey(false), RepeatKey(true), SkipValue(false)
{ }
bool operator<(const CommandArgument& rhs) const
{
return Order < rhs.Order;
}
};
void PluginUtility::AddArgumentHelper(const Array::Ptr& args, const String& key, const String& value, bool add_key, bool add_value)
{
if (add_key)
args->Add(key);
if (add_value)
args->Add(value);
}
Value PluginUtility::EscapeMacroShellArg(const Value& value)
{
String result;
if (value.IsObjectType<Array>()) {
Array::Ptr arr = value;
ObjectLock olock(arr);
BOOST_FOREACH(const Value& arg, arr) {
if (result.GetLength() > 0)
result += " ";
result += Utility::EscapeShellArg(arg);
}
} else
result = Utility::EscapeShellArg(value);
return result;
}
void PluginUtility::ExecuteCommand(const Command::Ptr& commandObj, const Checkable::Ptr& checkable,
const CheckResult::Ptr& cr, const MacroProcessor::ResolverList& macroResolvers,
const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros,
@ -89,136 +42,26 @@ void PluginUtility::ExecuteCommand(const Command::Ptr& commandObj, const Checkab
Dictionary::Ptr raw_arguments = commandObj->GetArguments();
Value command;
if (!raw_arguments || raw_command.IsObjectType<Array>() || raw_command.IsObjectType<Function>())
command = MacroProcessor::ResolveMacros(raw_command, macroResolvers, cr, NULL,
PluginUtility::EscapeMacroShellArg, resolvedMacros, useResolvedMacros);
else {
Array::Ptr arr = new Array();
arr->Add(raw_command);
command = arr;
}
if (raw_arguments) {
std::vector<CommandArgument> args;
try {
command = MacroProcessor::ResolveArguments(raw_command, raw_arguments,
macroResolvers, cr, resolvedMacros, useResolvedMacros);
} catch (const std::exception& ex) {
String message = DiagnosticInformation(ex);
ObjectLock olock(raw_arguments);
BOOST_FOREACH(const Dictionary::Pair& kv, raw_arguments) {
const Value& arginfo = kv.second;
Log(LogWarning, "PluginUtility", message);
CommandArgument arg;
arg.Key = kv.first;
bool required = false;
Value argval;
if (arginfo.IsObjectType<Dictionary>()) {
Dictionary::Ptr argdict = arginfo;
if (argdict->Contains("key"))
arg.Key = argdict->Get("key");
argval = argdict->Get("value");
if (argdict->Contains("required"))
required = argdict->Get("required");
arg.SkipKey = argdict->Get("skip_key");
if (argdict->Contains("repeat_key"))
arg.RepeatKey = argdict->Get("repeat_key");
arg.Order = argdict->Get("order");
Value set_if = argdict->Get("set_if");
if (!set_if.IsEmpty()) {
String missingMacro;
Value set_if_resolved = MacroProcessor::ResolveMacros(set_if, macroResolvers,
cr, &missingMacro, MacroProcessor::EscapeCallback(), resolvedMacros,
useResolvedMacros);
if (!missingMacro.IsEmpty())
continue;
int value;
if (set_if_resolved == "true")
value = 1;
else if (set_if_resolved == "false")
value = 0;
else {
try {
value = Convert::ToLong(set_if_resolved);
} catch (const std::exception& ex) {
/* tried to convert a string */
Log(LogWarning, "PluginUtility")
<< "Error evaluating set_if value '" << set_if_resolved << "': " << ex.what();
continue;
}
}
if (!value)
continue;
}
}
else
argval = arginfo;
if (argval.IsEmpty())
arg.SkipValue = true;
String missingMacro;
arg.AValue = MacroProcessor::ResolveMacros(argval, macroResolvers,
cr, &missingMacro, MacroProcessor::EscapeCallback(), resolvedMacros,
useResolvedMacros);
if (!missingMacro.IsEmpty()) {
if (required) {
String message = "Non-optional macro '" + missingMacro + "' used in argument '" +
arg.Key + "' is missing while executing command '" + commandObj->GetName() +
"' for object '" + checkable->GetName() + "'";
Log(LogWarning, "PluginUtility", message);
if (callback) {
ProcessResult pr;
pr.PID = -1;
pr.ExecutionStart = Utility::GetTime();
pr.ExecutionEnd = pr.ExecutionStart;
pr.ExitStatus = 3; /* Unknown */
pr.Output = message;
callback(Empty, pr);
}
return;
}
continue;
}
args.push_back(arg);
if (callback) {
ProcessResult pr;
pr.PID = -1;
pr.ExecutionStart = Utility::GetTime();
pr.ExecutionEnd = pr.ExecutionStart;
pr.ExitStatus = 3; /* Unknown */
pr.Output = message;
callback(Empty, pr);
}
std::sort(args.begin(), args.end());
Array::Ptr command_arr = command;
BOOST_FOREACH(const CommandArgument& arg, args) {
if (arg.AValue.IsObjectType<Dictionary>()) {
Log(LogWarning, "PluginUtility", "Tried to use dictionary in argument");
continue;
} else if (arg.AValue.IsObjectType<Array>()) {
bool first = true;
Array::Ptr arr = static_cast<Array::Ptr>(arg.AValue);
ObjectLock olock(arr);
BOOST_FOREACH(const Value& value, arr) {
bool add_key;
if (first) {
first = false;
add_key = !arg.SkipKey;
} else
add_key = !arg.SkipKey && arg.RepeatKey;
AddArgumentHelper(command_arr, arg.Key, value, add_key, !arg.SkipValue);
}
} else
AddArgumentHelper(command_arr, arg.Key, arg.AValue, !arg.SkipKey, !arg.SkipValue);
}
return;
}
Dictionary::Ptr envMacros = new Dictionary();

View File

@ -52,9 +52,6 @@ public:
private:
PluginUtility(void);
static void AddArgumentHelper(const Array::Ptr& args, const String& key, const String& value, bool add_key, bool add_value);
static Value EscapeMacroShellArg(const Value& value);
};
}