Implement support for arrays in command arguments

fixes #6709
This commit is contained in:
Gunnar Beutner 2014-11-26 20:43:42 +01:00 committed by Gunnar Beutner
parent f1d37f6aa4
commit 9dfa3d22d4
19 changed files with 209 additions and 41 deletions

View 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$"

View 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());

View 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);

View File

@ -202,10 +202,12 @@
%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"
}

View 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();

View 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);

View 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;

View 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,

View 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;
};
}

View 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.SkipValue)
command_arr->Add(arg.Value);
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;
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);
}
}

View 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);
};
}

View 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());

View 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);

View 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);
}

View 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);
};

View 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);

View 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);

View 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

55
test/icinga-macros.cpp Normal file
View File

@ -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()