]> granicus.if.org Git - icinga2/commitdiff
Implement the resolve_arguments function
authorGunnar Beutner <gunnar@beutner.name>
Thu, 27 Aug 2015 06:22:35 +0000 (08:22 +0200)
committerGunnar Beutner <gunnar@beutner.name>
Thu, 27 Aug 2015 07:02:21 +0000 (09:02 +0200)
fixes #10006

doc/3-monitoring-basics.md
doc/7-icinga-template-library.md
itl/command-plugins.conf
lib/icinga/macroprocessor.cpp
lib/icinga/macroprocessor.hpp
lib/icinga/pluginutility.cpp
lib/icinga/pluginutility.hpp

index e13a606202a6ad4a79d63b45ab27ad182b5660f7..3bdb6f6c397545bd09324de161e455eaca627375 100644 (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.
 
index e65f6dfa689d51dbd159c8f0791617b1c929424d..7cf0550b808dbf8fa03e324c97235a36b6e05978 100644 (file)
@@ -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.
index 59d6306f2d4377e3089bd371be98d80cc2371f7b..7d5fd2886e58529fecd72b044cd08c335cdaaea3 100644 (file)
@@ -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" = {
index 7b712960e28610421fdafeadd7a7622cb29cc09c..c33289a5ed06a34f2d70fab2dbea7f0d8693eb94 100644 (file)
@@ -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 <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;
+}
+
index 54a08243de008091b3284b4aea02d6a01e8cd947..60d579d23c743b02e77235e5a3519aae55c80073 100644 (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);
+
 };
 
 }
index d051e7c2d55659b88425fc90f5d54702d55bfef0..2e1e393f2b99ae7ce431c13fc8f196226861f8a4 100644 (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>
 
 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;
-
-               ObjectLock olock(raw_arguments);
-               BOOST_FOREACH(const Dictionary::Pair& kv, raw_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, 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);
+       try {
+               command = MacroProcessor::ResolveArguments(raw_command, raw_arguments,
+                   macroResolvers, cr, resolvedMacros, useResolvedMacros);
+       } catch (const std::exception& ex) {
+               String message = DiagnosticInformation(ex);
+
+               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);
                }
 
-               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();
index d8a9ebafc292654bc7d6eae047bf77228d2a0082..c987b0b094865e604a119ba7c1ebb78055527c4a 100644 (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);
 };
 
 }