From 63a1ff77c32ab94351d9253c54e80070e3146af5 Mon Sep 17 00:00:00 2001 From: Gunnar Beutner Date: Thu, 27 Aug 2015 08:22:35 +0200 Subject: [PATCH] Implement the resolve_arguments function fixes #10006 --- doc/3-monitoring-basics.md | 23 ++++ doc/7-icinga-template-library.md | 3 +- itl/command-plugins.conf | 15 ++- lib/icinga/macroprocessor.cpp | 202 ++++++++++++++++++++++++++++++- lib/icinga/macroprocessor.hpp | 13 +- lib/icinga/pluginutility.cpp | 189 +++-------------------------- lib/icinga/pluginutility.hpp | 3 - 7 files changed, 264 insertions(+), 184 deletions(-) diff --git a/doc/3-monitoring-basics.md b/doc/3-monitoring-basics.md index e13a60620..3bdb6f6c3 100644 --- a/doc/3-monitoring-basics.md +++ b/doc/3-monitoring-basics.md @@ -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. diff --git a/doc/7-icinga-template-library.md b/doc/7-icinga-template-library.md index e65f6dfa6..7cf0550b8 100644 --- a/doc/7-icinga-template-library.md +++ b/doc/7-icinga-template-library.md @@ -107,7 +107,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. diff --git a/itl/command-plugins.conf b/itl/command-plugins.conf index 59d6306f2..7d5fd2886 100644 --- a/itl/command-plugins.conf +++ b/itl/command-plugins.conf @@ -1265,7 +1265,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" = { diff --git a/lib/icinga/macroprocessor.cpp b/lib/icinga/macroprocessor.cpp index 7b712960e..c33289a5e 100644 --- a/lib/icinga/macroprocessor.cpp +++ b/lib/icinga/macroprocessor.cpp @@ -26,6 +26,8 @@ #include "base/context.hpp" #include "base/configobject.hpp" #include "base/scriptframe.hpp" +#include "base/convert.hpp" +#include "base/exception.hpp" #include #include #include @@ -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::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()) 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& args, resolvedMacros, useResolvedMacros, recursionLevel); } +Value MacroProcessor::InternalResolveArgumentsShim(const std::vector& 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()) { 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::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() || command.IsObjectType()) + 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 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::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()) { + Log(LogWarning, "PluginUtility", "Tried to use dictionary in argument"); + continue; + } else if (arg.AValue.IsObjectType()) { + bool first = true; + Array::Ptr arr = static_cast(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; +} + diff --git a/lib/icinga/macroprocessor.hpp b/lib/icinga/macroprocessor.hpp index 54a08243d..60d579d23 100644 --- a/lib/icinga/macroprocessor.hpp +++ b/lib/icinga/macroprocessor.hpp @@ -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& 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& 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); + }; } diff --git a/lib/icinga/pluginutility.cpp b/lib/icinga/pluginutility.cpp index d051e7c2d..2e1e393f2 100644 --- a/lib/icinga/pluginutility.cpp +++ b/lib/icinga/pluginutility.cpp @@ -25,6 +25,7 @@ #include "base/convert.hpp" #include "base/process.hpp" #include "base/objectlock.hpp" +#include "base/exception.hpp" #include #include #include @@ -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::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() || raw_command.IsObjectType()) - 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 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::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()) { - Log(LogWarning, "PluginUtility", "Tried to use dictionary in argument"); - continue; - } else if (arg.AValue.IsObjectType()) { - bool first = true; - Array::Ptr arr = static_cast(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(); diff --git a/lib/icinga/pluginutility.hpp b/lib/icinga/pluginutility.hpp index d8a9ebafc..c987b0b09 100644 --- a/lib/icinga/pluginutility.hpp +++ b/lib/icinga/pluginutility.hpp @@ -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); }; }