]> granicus.if.org Git - icinga2/commitdiff
Implement support for arrays in command arguments
authorGunnar Beutner <gunnar@beutner.name>
Wed, 26 Nov 2014 19:43:42 +0000 (20:43 +0100)
committerGunnar Beutner <gunnar.beutner@netways.de>
Thu, 27 Nov 2014 12:24:07 +0000 (13:24 +0100)
fixes #6709

19 files changed:
itl/command-plugins.conf
lib/icinga/host.cpp
lib/icinga/host.hpp
lib/icinga/icinga-type.conf
lib/icinga/icingaapplication.cpp
lib/icinga/icingaapplication.hpp
lib/icinga/macroprocessor.cpp
lib/icinga/macroprocessor.hpp
lib/icinga/macroresolver.hpp
lib/icinga/pluginutility.cpp
lib/icinga/pluginutility.hpp
lib/icinga/service.cpp
lib/icinga/service.hpp
lib/perfdata/graphitewriter.cpp
lib/perfdata/graphitewriter.hpp
lib/perfdata/perfdatawriter.cpp
lib/perfdata/perfdatawriter.hpp
test/CMakeLists.txt
test/icinga-macros.cpp [new file with mode: 0644]

index 7da0f64a4f345009dbbeb5593a6ccb7324fafacf..af8112c9b462f1b4cd9fa02e731288321b88dc2d 100644 (file)
@@ -736,6 +736,11 @@ object CheckCommand "nrpe" {
                        description = "Make socket timeouts return an UNKNOWN state instead of CRITICAL"
                }
                "-t" = "$nrpe_timeout$"
+               "-a" = {
+                       value = "$nrpe_arguments$"
+                       repeat_key = false
+                       order = 1
+               }
        }
 
        vars.nrpe_address = "$address$"
index 72c42575d838d114a8ffd06ea4a7efb80c1ec07a..1a7fe818d931e9e83a4673cce87a7bd85db17013 100644 (file)
@@ -212,7 +212,7 @@ String Host::StateTypeToString(StateType type)
                return "HARD";
 }
 
-bool Host::ResolveMacro(const String& macro, const CheckResult::Ptr&, String *result) const
+bool Host::ResolveMacro(const String& macro, const CheckResult::Ptr&, Value *result) const
 {
        if (macro == "state") {
                *result = StateToString(GetState());
index 3d8f1a1c626a3c71e666a62760a6d56ab16f3361..91e4c6ea2bc6499d9a38fa47c51c9c49ca8963e5 100644 (file)
@@ -63,7 +63,7 @@ public:
        static StateType StateTypeFromString(const String& state);
        static String StateTypeToString(StateType state);
 
-       virtual bool ResolveMacro(const String& macro, const CheckResult::Ptr& cr, String *result) const;
+       virtual bool ResolveMacro(const String& macro, const CheckResult::Ptr& cr, Value *result) const;
 
 protected:
        virtual void Stop(void);
index 4c8d0677211658fb5f99e7284dfd2cce955df967..12e5945f208702ffba5a633cea55a7743c7ea325 100644 (file)
        %attribute %dictionary "arguments" {
                %attribute %string "*",
                %attribute %dictionary "*" {
+                       %attribute %string "key"
                        %attribute %string "value"
                        %attribute %string "description"
                        %attribute %number "required"
                        %attribute %number "skip_key"
+                       %attribute %number "repeat_key"
                        %attribute %string "set_if"
                        %attribute %number "order"
                }
index b52404d92474ad0520f7a7b2fe5a97261c81ccf3..c957dff1e93ffc815940d89552804abda24cf2a4 100644 (file)
@@ -147,7 +147,7 @@ String IcingaApplication::GetNodeName(void) const
        return ScriptVariable::Get("NodeName");
 }
 
-bool IcingaApplication::ResolveMacro(const String& macro, const CheckResult::Ptr&, String *result) const
+bool IcingaApplication::ResolveMacro(const String& macro, const CheckResult::Ptr&, Value *result) const
 {
        double now = Utility::GetTime();
 
index a644ce87144ed952b8be2e0a03f4ecde47060fa0..ef0077191d2f7b5a0b0eede8526a3210ef00ad86 100644 (file)
@@ -50,7 +50,7 @@ public:
        Dictionary::Ptr GetVars(void) const;
        String GetNodeName(void) const;
 
-       virtual bool ResolveMacro(const String& macro, const CheckResult::Ptr& cr, String *result) const;
+       virtual bool ResolveMacro(const String& macro, const CheckResult::Ptr& cr, Value *result) const;
 
        bool GetEnableNotifications(void) const;
        void SetEnableNotifications(bool enabled);
index d47cec010e2085cc57347a0f4916661d4bf4fb52..8071dbe3b74de700d3e04f2f5238cbae61ffa3f6 100644 (file)
@@ -66,7 +66,7 @@ Value MacroProcessor::ResolveMacros(const Value& str, const ResolverList& resolv
 }
 
 bool MacroProcessor::ResolveMacro(const String& macro, const ResolverList& resolvers,
-    const CheckResult::Ptr& cr, String *result, bool *recursive_macro)
+    const CheckResult::Ptr& cr, Value *result, bool *recursive_macro)
 {
        CONTEXT("Resolving macro '" + macro + "'");
 
@@ -92,12 +92,7 @@ bool MacroProcessor::ResolveMacro(const String& macro, const ResolverList& resol
                                Dictionary::Ptr vars = dobj->GetVars();
 
                                if (vars && vars->Contains(macro)) {
-                                       Value value = vars->Get(macro);
-
-                                       if (value.IsObjectType<Array>())
-                                               value = Utility::Join(value, ';');
-
-                                       *result = value;
+                                       *result = vars->Get(macro);
                                        *recursive_macro = true;
                                        return true;
                                }
@@ -150,9 +145,6 @@ bool MacroProcessor::ResolveMacro(const String& macro, const ResolverList& resol
                            tokens[0] == "notes")
                                *recursive_macro = true;
 
-                       if (ref.IsObjectType<Array>())
-                               ref = Utility::Join(ref, ';');
-
                        *result = ref;
                        return true;
                }
@@ -161,7 +153,7 @@ bool MacroProcessor::ResolveMacro(const String& macro, const ResolverList& resol
        return false;
 }
 
-String MacroProcessor::InternalResolveMacros(const String& str, const ResolverList& resolvers,
+Value MacroProcessor::InternalResolveMacros(const String& str, const ResolverList& resolvers,
     const CheckResult::Ptr& cr, String *missingMacro,
     const MacroProcessor::EscapeCallback& escapeFn, const Dictionary::Ptr& resolvedMacros,
     bool useResolvedMacros, int recursionLevel)
@@ -183,7 +175,7 @@ String MacroProcessor::InternalResolveMacros(const String& str, const ResolverLi
 
                String name = result.SubStr(pos_first + 1, pos_second - pos_first - 1);
 
-               String resolved_macro;
+               Value resolved_macro;
                bool recursive_macro;
                bool found;
 
@@ -211,10 +203,24 @@ String MacroProcessor::InternalResolveMacros(const String& str, const ResolverLi
                }
 
                /* recursively resolve macros in the macro if it was a user macro */
-               if (recursive_macro)
-                       resolved_macro = InternalResolveMacros(resolved_macro,
-                           resolvers, cr, missingMacro, EscapeCallback(), Dictionary::Ptr(),
-                           false, recursionLevel + 1);
+               if (recursive_macro) {
+                       if (resolved_macro.IsObjectType<Array>()) {
+                               Array::Ptr arr = resolved_macro;
+                               Array::Ptr result = new Array();
+
+                               ObjectLock olock(arr);
+                               BOOST_FOREACH(Value& value, arr) {
+                                       result->Add(InternalResolveMacros(value,
+                                               resolvers, cr, missingMacro, EscapeCallback(), Dictionary::Ptr(),
+                                               false, recursionLevel + 1));
+                               }
+
+                               resolved_macro = result;
+                       } else
+                               resolved_macro = InternalResolveMacros(resolved_macro,
+                                       resolvers, cr, missingMacro, EscapeCallback(), Dictionary::Ptr(),
+                                       false, recursionLevel + 1);
+               }
 
                if (!useResolvedMacros && found && resolvedMacros)
                        resolvedMacros->Set(name, resolved_macro);
@@ -222,8 +228,19 @@ String MacroProcessor::InternalResolveMacros(const String& str, const ResolverLi
                if (escapeFn)
                        resolved_macro = escapeFn(resolved_macro);
 
+               /* we're done if the value is an array */
+               if (resolved_macro.IsObjectType<Array>()) {
+                       /* don't allow mixing strings and arrays in macro strings */
+                       if (pos_first != 0 || pos_second != str.GetLength() - 1)
+                               BOOST_THROW_EXCEPTION(std::invalid_argument("Mixing both strings and non-strings in macros is not allowed."));
+
+                       return resolved_macro;
+               }
+
+               String resolved_macro_str = resolved_macro;
+
                result.Replace(pos_first, pos_second - pos_first + 1, resolved_macro);
-               offset = pos_first + resolved_macro.GetLength() + 1;
+               offset = pos_first + resolved_macro_str.GetLength() + 1;
        }
 
        return result;
index a7e0b0f9c835e5228502ef482dfc2a2baf066180..00afbda0f2efcbfd8c2eddd7ead7a59ee139fa84 100644 (file)
@@ -37,7 +37,7 @@ namespace icinga
 class I2_ICINGA_API MacroProcessor
 {
 public:
-       typedef boost::function<String (const String&)> EscapeCallback;
+       typedef boost::function<Value (const Value&)> EscapeCallback;
        typedef std::pair<String, Object::Ptr> ResolverSpec;
        typedef std::vector<ResolverSpec> ResolverList;
 
@@ -51,8 +51,8 @@ private:
        MacroProcessor(void);
 
        static bool ResolveMacro(const String& macro, const ResolverList& resolvers,
-               const CheckResult::Ptr& cr, String *result, bool *recursive_macro);
-       static String InternalResolveMacros(const String& str,
+               const CheckResult::Ptr& cr, Value *result, bool *recursive_macro);
+       static Value InternalResolveMacros(const String& str,
            const ResolverList& resolvers, const CheckResult::Ptr& cr,
            String *missingMacro, const EscapeCallback& escapeFn,
            const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros,
index 300145f2b4e2fb99c92c4b108837a6370512b66a..c2702d6afc6552db58ed82567ca4e7f0f7e20c6b 100644 (file)
@@ -38,7 +38,7 @@ class I2_ICINGA_API MacroResolver
 public:
        DECLARE_PTR_TYPEDEFS(MacroResolver);
 
-       virtual bool ResolveMacro(const String& macro, const CheckResult::Ptr& cr, String *result) const = 0;
+       virtual bool ResolveMacro(const String& macro, const CheckResult::Ptr& cr, Value *result) const = 0;
 };
 
 }
index 736d7f465589e128b614c612d57d4f4011268d5f..783431b9a494443b4c5d7945d4e093206f7167d9 100644 (file)
@@ -36,12 +36,13 @@ struct CommandArgument
 {
        int Order;
        bool SkipKey;
+       bool RepeatKey;
        bool SkipValue;
        String Key;
-       String Value;
+       Value AValue;
 
        CommandArgument(void)
-               : Order(0), SkipKey(false), SkipValue(false)
+               : Order(0), SkipKey(false), RepeatKey(true), SkipValue(false)
        { }
 
        bool operator<(const CommandArgument& rhs) const
@@ -50,6 +51,35 @@ struct CommandArgument
        }
 };
 
+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,
@@ -61,7 +91,7 @@ void PluginUtility::ExecuteCommand(const Command::Ptr& commandObj, const Checkab
        Value command;
        if (!raw_arguments || raw_command.IsObjectType<Array>())
                command = MacroProcessor::ResolveMacros(raw_command, macroResolvers, cr, NULL,
-                   Utility::EscapeShellArg, resolvedMacros, useResolvedMacros);
+                   PluginUtility::EscapeMacroShellArg, resolvedMacros, useResolvedMacros);
        else {
                Array::Ptr arr = new Array();
                arr->Add(raw_command);
@@ -83,10 +113,14 @@ void PluginUtility::ExecuteCommand(const Command::Ptr& commandObj, const Checkab
 
                        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");
 
                                String set_if = argdict->Get("set_if");
@@ -118,7 +152,7 @@ void PluginUtility::ExecuteCommand(const Command::Ptr& commandObj, const Checkab
                                arg.SkipValue = true;
 
                        String missingMacro;
-                       arg.Value = MacroProcessor::ResolveMacros(argval, macroResolvers,
+                       arg.AValue = MacroProcessor::ResolveMacros(argval, macroResolvers,
                            cr, &missingMacro, MacroProcessor::EscapeCallback(), resolvedMacros,
                            useResolvedMacros);
 
@@ -152,11 +186,32 @@ void PluginUtility::ExecuteCommand(const Command::Ptr& commandObj, const Checkab
 
                Array::Ptr command_arr = command;
                BOOST_FOREACH(const CommandArgument& arg, args) {
-                       if (!arg.SkipKey)
-                               command_arr->Add(arg.Key);
+                       Array::Ptr arr;
+
+                       if (arg.AValue.IsString())
+                               AddArgumentHelper(command_arr, arg.Key, arg.AValue, !arg.SkipKey, !arg.SkipValue);
+                       else if (arg.AValue.IsObjectType<Array>())
+                               arr = static_cast<Array::Ptr>(arg.AValue);
+                       else
+                               continue;
+
+                       if (arr) {
+                               bool first = true;
 
-                       if (!arg.SkipValue)
-                               command_arr->Add(arg.Value);
+                               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);
+                               }
+                       }
                }
        }
 
@@ -173,6 +228,9 @@ void PluginUtility::ExecuteCommand(const Command::Ptr& commandObj, const Checkab
                            NULL, MacroProcessor::EscapeCallback(), resolvedMacros,
                            useResolvedMacros);
 
+                       if (value.IsObjectType<Array>())
+                               value = Utility::Join(value, ';');
+
                        envMacros->Set(kv.first, value);
                }
        }
index 2039f1237efb934ad90520e4a8cfd32b32067256..d9c6c0dfcf5d2f95b9660fbe90b17d1a4d923e2c 100644 (file)
@@ -41,7 +41,7 @@ class I2_ICINGA_API PluginUtility
 public:
        static void ExecuteCommand(const Command::Ptr& commandObj, const Checkable::Ptr& checkable,
            const CheckResult::Ptr& cr, const MacroProcessor::ResolverList& macroResolvers,
-            const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros,
+           const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros,
            const boost::function<void(const Value& commandLine, const ProcessResult&)>& callback = boost::function<void(const Value& commandLine, const ProcessResult&)>());
 
        static ServiceState ExitStatusToState(int exitStatus);
@@ -52,6 +52,9 @@ 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);
 };
 
 }
index e0b914c12fb34b419de3a3d7bb8cb82832f3ee67..5168a5f2fbea3d94c17f16627723f9881eaa6424 100644 (file)
@@ -134,7 +134,7 @@ String Service::StateTypeToString(StateType type)
                return "HARD";
 }
 
-bool Service::ResolveMacro(const String& macro, const CheckResult::Ptr& cr, String *result) const
+bool Service::ResolveMacro(const String& macro, const CheckResult::Ptr& cr, Value *result) const
 {
        if (macro == "state") {
                *result = StateToString(GetState());
index 1fe1a532e00a3ab4b23f18d4e7f2b93fd099240f..2c0ee86a63d5ad1988d5f77c58ed114da6af5e5e 100644 (file)
@@ -43,7 +43,7 @@ public:
 
        Host::Ptr GetHost(void) const;
 
-       virtual bool ResolveMacro(const String& macro, const CheckResult::Ptr& cr, String *result) const;
+       virtual bool ResolveMacro(const String& macro, const CheckResult::Ptr& cr, Value *result) const;
 
        static ServiceState StateFromString(const String& state);
        static String StateToString(ServiceState state);
index 7498f3f6cb17acf40fc619ba28f718204b095e42..036ac90b575dc745fabd796b9de3b901d68d7b2a 100644 (file)
@@ -117,11 +117,11 @@ void GraphiteWriter::CheckResultHandler(const Checkable::Ptr& checkable, const C
        String prefix;
 
        if (service) {
-               prefix = MacroProcessor::ResolveMacros(GetServiceNameTemplate(), resolvers, cr, NULL, &GraphiteWriter::EscapeMetric);
+               prefix = MacroProcessor::ResolveMacros(GetServiceNameTemplate(), resolvers, cr, NULL, &GraphiteWriter::EscapeMacroMetric);
 
                SendMetric(prefix, "state", service->GetState());
        } else {
-               prefix = MacroProcessor::ResolveMacros(GetHostNameTemplate(), resolvers, cr, NULL, &GraphiteWriter::EscapeMetric);
+               prefix = MacroProcessor::ResolveMacros(GetHostNameTemplate(), resolvers, cr, NULL, &GraphiteWriter::EscapeMacroMetric);
 
                SendMetric(prefix, "state", host->GetState());
        }
@@ -214,3 +214,19 @@ String GraphiteWriter::EscapeMetric(const String& str)
 
        return result;
 }
+
+Value GraphiteWriter::EscapeMacroMetric(const Value& value)
+{
+       if (value.IsObjectType<Array>()) {
+               Array::Ptr arr = value;
+               Array::Ptr result = new Array();
+
+               ObjectLock olock(arr);
+               BOOST_FOREACH(const Value& arg, arr) {
+                       result->Add(EscapeMetric(arg));
+               }
+
+               return Utility::Join(result, '.');
+       } else
+               return EscapeMetric(value);
+}
index f0128266a716f68678dbd3dd552b73c67e75087f..fa34fbecb976f08e5a319d252ba4c3e62e4b03c7 100644 (file)
@@ -55,6 +55,7 @@ private:
        void SendMetric(const String& prefix, const String& name, double value);
        void SendPerfdata(const String& prefix, const CheckResult::Ptr& cr);
        static String EscapeMetric(const String& str);
+       static Value EscapeMacroMetric(const Value& value);
 
        void ReconnectTimerHandler(void);
 };
index 40575eac095541579c2c3485c9a7011eda191c25..288f70cc5581ba69754b39cbfb03c917671c3174 100644 (file)
@@ -64,6 +64,14 @@ void PerfdataWriter::Start(void)
        RotateFile(m_HostOutputFile, GetHostTempPath(), GetHostPerfdataPath());
 }
 
+Value PerfdataWriter::EscapeMacroMetric(const Value& value)
+{
+       if (value.IsObjectType<Array>())
+               return Utility::Join(value, ';');
+       else
+               return value;
+}
+
 void PerfdataWriter::CheckResultHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr)
 {
        CONTEXT("Writing performance data for object '" + checkable->GetName() + "'");
@@ -86,7 +94,7 @@ void PerfdataWriter::CheckResultHandler(const Checkable::Ptr& checkable, const C
        resolvers.push_back(std::make_pair("icinga", IcingaApplication::GetInstance()));
 
        if (service) {
-               String line = MacroProcessor::ResolveMacros(GetServiceFormatTemplate(), resolvers, cr);
+               String line = MacroProcessor::ResolveMacros(GetServiceFormatTemplate(), resolvers, cr, NULL, &PerfdataWriter::EscapeMacroMetric);
 
                {
                        ObjectLock olock(this);
@@ -96,7 +104,7 @@ void PerfdataWriter::CheckResultHandler(const Checkable::Ptr& checkable, const C
                        m_ServiceOutputFile << line << "\n";
                }
        } else {
-               String line = MacroProcessor::ResolveMacros(GetHostFormatTemplate(), resolvers, cr);
+               String line = MacroProcessor::ResolveMacros(GetHostFormatTemplate(), resolvers, cr, NULL, &PerfdataWriter::EscapeMacroMetric);
 
                {
                        ObjectLock olock(this);
index 0e3caf0ab83d062af39e917edf57a7b9ed7d3464..f297f706eb9c01c62d3076380a32ced92bd45615 100644 (file)
@@ -47,6 +47,7 @@ protected:
 
 private:
        void CheckResultHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr);
+       static Value EscapeMacroMetric(const Value& value);
 
        Timer::Ptr m_RotationTimer;
        void RotationTimerHandler(void);
index fb0aa3b6c9b6c44749e428fe911e7f79a7513b1b..1a884688f58c32f00cf298f48900304c2120d57f 100644 (file)
@@ -22,7 +22,8 @@ set(base_test_SOURCES
   base-json.cpp base-match.cpp base-netstring.cpp base-object.cpp
   base-serialize.cpp base-shellescape.cpp base-stacktrace.cpp
   base-stream.cpp base-string.cpp base-timer.cpp base-type.cpp
-  base-value.cpp config-ops.cpp icinga-perfdata.cpp test.cpp
+  base-value.cpp config-ops.cpp icinga-macros.cpp icinga-perfdata.cpp
+  test.cpp
 )
 
 set_property(SOURCE test.cpp PROPERTY EXCLUDE_UNITY_BUILD TRUE)
@@ -90,6 +91,7 @@ add_boost_test(base
         base_value/format
        config_ops/simple
        config_ops/advanced
+       icinga_macros/simple
        icinga_perfdata/empty
        icinga_perfdata/simple
        icinga_perfdata/quotes
diff --git a/test/icinga-macros.cpp b/test/icinga-macros.cpp
new file mode 100644 (file)
index 0000000..e4b6160
--- /dev/null
@@ -0,0 +1,55 @@
+/******************************************************************************
+ * Icinga 2                                                                   *
+ * Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org)    *
+ *                                                                            *
+ * This program is free software; you can redistribute it and/or              *
+ * modify it under the terms of the GNU General Public License                *
+ * as published by the Free Software Foundation; either version 2             *
+ * of the License, or (at your option) any later version.                     *
+ *                                                                            *
+ * This program is distributed in the hope that it will be useful,            *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of             *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the              *
+ * GNU General Public License for more details.                               *
+ *                                                                            *
+ * You should have received a copy of the GNU General Public License          *
+ * along with this program; if not, write to the Free Software Foundation     *
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.             *
+ ******************************************************************************/
+
+#include "icinga/macroprocessor.hpp"
+#include <boost/test/unit_test.hpp>
+
+using namespace icinga;
+
+BOOST_AUTO_TEST_SUITE(icinga_macros)
+
+BOOST_AUTO_TEST_CASE(simple)
+{
+       Dictionary::Ptr macrosA = new Dictionary();
+       macrosA->Set("testA", 7);
+       macrosA->Set("testB", "hello");
+
+       Dictionary::Ptr macrosB = new Dictionary();
+       macrosB->Set("testA", 3);
+       macrosB->Set("testC", "world");
+
+       Array::Ptr testD = new Array();
+       testD->Add(3);
+       testD->Add("test");
+
+       macrosB->Set("testD", testD);
+
+       MacroProcessor::ResolverList resolvers;
+       resolvers.push_back(std::make_pair("macrosA", macrosA));
+       resolvers.push_back(std::make_pair("macrosB", macrosB));
+
+       BOOST_CHECK(MacroProcessor::ResolveMacros("$macrosA.testB$ $macrosB.testC$", resolvers) == "hello world");
+       BOOST_CHECK(MacroProcessor::ResolveMacros("$testA$", resolvers) == "7");
+
+       Array::Ptr result = MacroProcessor::ResolveMacros("$testD$", resolvers);
+       BOOST_CHECK(result->GetLength() == 2);
+
+}
+
+BOOST_AUTO_TEST_SUITE_END()