]> granicus.if.org Git - icinga2/blob - lib/icinga/pluginutility.cpp
Implement support for arrays in command arguments
[icinga2] / lib / icinga / pluginutility.cpp
1 /******************************************************************************
2  * Icinga 2                                                                   *
3  * Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org)    *
4  *                                                                            *
5  * This program is free software; you can redistribute it and/or              *
6  * modify it under the terms of the GNU General Public License                *
7  * as published by the Free Software Foundation; either version 2             *
8  * of the License, or (at your option) any later version.                     *
9  *                                                                            *
10  * This program is distributed in the hope that it will be useful,            *
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of             *
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the              *
13  * GNU General Public License for more details.                               *
14  *                                                                            *
15  * You should have received a copy of the GNU General Public License          *
16  * along with this program; if not, write to the Free Software Foundation     *
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.             *
18  ******************************************************************************/
19
20 #include "icinga/pluginutility.hpp"
21 #include "icinga/macroprocessor.hpp"
22 #include "icinga/perfdatavalue.hpp"
23 #include "base/logger.hpp"
24 #include "base/utility.hpp"
25 #include "base/convert.hpp"
26 #include "base/process.hpp"
27 #include "base/objectlock.hpp"
28 #include <boost/algorithm/string/classification.hpp>
29 #include <boost/algorithm/string/split.hpp>
30 #include <boost/algorithm/string/trim.hpp>
31 #include <boost/foreach.hpp>
32
33 using namespace icinga;
34
35 struct CommandArgument
36 {
37         int Order;
38         bool SkipKey;
39         bool RepeatKey;
40         bool SkipValue;
41         String Key;
42         Value AValue;
43
44         CommandArgument(void)
45                 : Order(0), SkipKey(false), RepeatKey(true), SkipValue(false)
46         { }
47
48         bool operator<(const CommandArgument& rhs) const
49         {
50                 return Order < rhs.Order;
51         }
52 };
53
54 void PluginUtility::AddArgumentHelper(const Array::Ptr& args, const String& key, const String& value, bool add_key, bool add_value)
55 {
56         if (add_key)
57                 args->Add(key);
58
59         if (add_value)
60                 args->Add(value);
61 }
62
63 Value PluginUtility::EscapeMacroShellArg(const Value& value)
64 {
65         String result;
66
67         if (value.IsObjectType<Array>()) {
68                 Array::Ptr arr = value;
69
70                 ObjectLock olock(arr);
71                 BOOST_FOREACH(const Value& arg, arr) {
72                         if (result.GetLength() > 0)
73                                 result += " ";
74
75                         result += Utility::EscapeShellArg(arg);
76                 }       
77         } else
78                 result = Utility::EscapeShellArg(value);
79
80         return result;
81 }
82
83 void PluginUtility::ExecuteCommand(const Command::Ptr& commandObj, const Checkable::Ptr& checkable,
84     const CheckResult::Ptr& cr, const MacroProcessor::ResolverList& macroResolvers,
85     const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros,
86     const boost::function<void(const Value& commandLine, const ProcessResult&)>& callback)
87 {
88         Value raw_command = commandObj->GetCommandLine();
89         Dictionary::Ptr raw_arguments = commandObj->GetArguments();
90
91         Value command;
92         if (!raw_arguments || raw_command.IsObjectType<Array>())
93                 command = MacroProcessor::ResolveMacros(raw_command, macroResolvers, cr, NULL,
94                     PluginUtility::EscapeMacroShellArg, resolvedMacros, useResolvedMacros);
95         else {
96                 Array::Ptr arr = new Array();
97                 arr->Add(raw_command);
98                 command = arr;
99         }
100
101         if (raw_arguments) {
102                 std::vector<CommandArgument> args;
103
104                 ObjectLock olock(raw_arguments);
105                 BOOST_FOREACH(const Dictionary::Pair& kv, raw_arguments) {
106                         const Value& arginfo = kv.second;
107
108                         CommandArgument arg;
109                         arg.Key = kv.first;
110
111                         bool required = false;
112                         String argval;
113
114                         if (arginfo.IsObjectType<Dictionary>()) {
115                                 Dictionary::Ptr argdict = arginfo;
116                                 if (argdict->Contains("key"))
117                                         arg.Key = argdict->Get("key");
118                                 argval = argdict->Get("value");
119                                 if (argdict->Contains("required"))
120                                         required = argdict->Get("required");
121                                 arg.SkipKey = argdict->Get("skip_key");
122                                 if (argdict->Contains("repeat_key"))
123                                         arg.RepeatKey = argdict->Get("repeat_key");
124                                 arg.Order = argdict->Get("order");
125
126                                 String set_if = argdict->Get("set_if");
127
128                                 if (!set_if.IsEmpty()) {
129                                         String missingMacro;
130                                         String set_if_resolved = MacroProcessor::ResolveMacros(set_if, macroResolvers,
131                                             cr, &missingMacro, MacroProcessor::EscapeCallback(), resolvedMacros,
132                                             useResolvedMacros);
133
134                                         if (!missingMacro.IsEmpty())
135                                                 continue;
136
137                                         try {
138                                                 if (!Convert::ToLong(set_if_resolved))
139                                                         continue;
140                                         } catch (const std::exception& ex) {
141                                                 /* tried to convert a string */
142                                                 Log(LogWarning, "PluginUtility")
143                                                     << "Error evaluating set_if value '" << set_if_resolved << "': " << ex.what();
144                                                 continue;
145                                         }
146                                 }
147                         }
148                         else
149                                 argval = arginfo;
150
151                         if (argval.IsEmpty())
152                                 arg.SkipValue = true;
153
154                         String missingMacro;
155                         arg.AValue = MacroProcessor::ResolveMacros(argval, macroResolvers,
156                             cr, &missingMacro, MacroProcessor::EscapeCallback(), resolvedMacros,
157                             useResolvedMacros);
158
159                         if (!missingMacro.IsEmpty()) {
160                                 if (required) {
161                                         String message = "Non-optional macro '" + missingMacro + "' used in argument '" +
162                                             arg.Key + "' is missing while executing command '" + commandObj->GetName() +
163                                             "' for object '" + checkable->GetName() + "'";
164                                         Log(LogWarning, "PluginUtility", message);
165
166                                         if (callback) {
167                                                 ProcessResult pr;
168                                                 pr.PID = -1;
169                                                 pr.ExecutionStart = Utility::GetTime();
170                                                 pr.ExecutionStart = pr.ExecutionStart;
171                                                 pr.ExitStatus = 3; /* Unknown */
172                                                 pr.Output = message;
173                                                 callback(Empty, pr);
174                                         }
175
176                                         return;
177                                 }
178
179                                 continue;
180                         }
181
182                         args.push_back(arg);
183                 }
184
185                 std::sort(args.begin(), args.end());
186
187                 Array::Ptr command_arr = command;
188                 BOOST_FOREACH(const CommandArgument& arg, args) {
189                         Array::Ptr arr;
190
191                         if (arg.AValue.IsString())
192                                 AddArgumentHelper(command_arr, arg.Key, arg.AValue, !arg.SkipKey, !arg.SkipValue);
193                         else if (arg.AValue.IsObjectType<Array>())
194                                 arr = static_cast<Array::Ptr>(arg.AValue);
195                         else
196                                 continue;
197
198                         if (arr) {
199                                 bool first = true;
200
201                                 ObjectLock olock(arr);
202                                 BOOST_FOREACH(const Value& value, arr) {
203                                         bool add_key;
204
205                                         if (first) {
206                                                 first = false;
207                                                 add_key = !arg.SkipKey;
208                                         } else
209                                                 add_key = !arg.SkipKey && arg.RepeatKey;
210                                                 
211
212                                         AddArgumentHelper(command_arr, arg.Key, value, add_key, !arg.SkipValue);
213                                 }
214                         }
215                 }
216         }
217
218         Dictionary::Ptr envMacros = new Dictionary();
219
220         Dictionary::Ptr env = commandObj->GetEnv();
221
222         if (env) {
223                 ObjectLock olock(env);
224                 BOOST_FOREACH(const Dictionary::Pair& kv, env) {
225                         String name = kv.second;
226
227                         Value value = MacroProcessor::ResolveMacros(name, macroResolvers, cr,
228                             NULL, MacroProcessor::EscapeCallback(), resolvedMacros,
229                             useResolvedMacros);
230
231                         if (value.IsObjectType<Array>())
232                                 value = Utility::Join(value, ';');
233
234                         envMacros->Set(kv.first, value);
235                 }
236         }
237
238         if (resolvedMacros && !useResolvedMacros)
239                 return;
240
241         Process::Ptr process = new Process(Process::PrepareCommand(command), envMacros);
242         process->SetTimeout(commandObj->GetTimeout());
243         process->Run(boost::bind(callback, command, _1));
244 }
245
246 ServiceState PluginUtility::ExitStatusToState(int exitStatus)
247 {
248         switch (exitStatus) {
249                 case 0:
250                         return ServiceOK;
251                 case 1:
252                         return ServiceWarning;
253                 case 2:
254                         return ServiceCritical;
255                 default:
256                         return ServiceUnknown;
257         }
258 }
259
260 std::pair<String, String> PluginUtility::ParseCheckOutput(const String& output)
261 {
262         String text;
263         String perfdata;
264
265         std::vector<String> lines;
266         boost::algorithm::split(lines, output, boost::is_any_of("\r\n"));
267
268         BOOST_FOREACH (const String& line, lines) {
269                 size_t delim = line.FindFirstOf("|");
270
271                 if (!text.IsEmpty())
272                         text += "\n";
273
274                 if (delim != String::NPos) {
275                         text += line.SubStr(0, delim);
276
277                         if (!perfdata.IsEmpty())
278                                 perfdata += " ";
279
280                         perfdata += line.SubStr(delim + 1, line.GetLength());
281                 } else {
282                         text += line;
283                 }
284         }
285
286         boost::algorithm::trim(perfdata);
287
288         return std::make_pair(text, perfdata);
289 }
290
291 Array::Ptr PluginUtility::SplitPerfdata(const String& perfdata)
292 {
293         Array::Ptr result = new Array();
294
295         size_t begin = 0;
296         String multi_prefix;
297
298         for (;;) {
299                 size_t eqp = perfdata.FindFirstOf('=', begin);
300
301                 if (eqp == String::NPos)
302                         break;
303
304                 String label = perfdata.SubStr(begin, eqp - begin);
305
306                 if (label.GetLength() > 2 && label[0] == '\'' && label[label.GetLength() - 1] == '\'')
307                         label = label.SubStr(1, label.GetLength() - 2);
308
309                 size_t multi_index = label.RFind("::");
310
311                 if (multi_index != String::NPos)
312                         multi_prefix = "";
313
314                 size_t spq = perfdata.FindFirstOf(' ', eqp);
315
316                 if (spq == String::NPos)
317                         spq = perfdata.GetLength();
318
319                 String value = perfdata.SubStr(eqp + 1, spq - eqp - 1);
320
321                 if (!multi_prefix.IsEmpty())
322                         label = multi_prefix + "::" + label;
323
324                 String pdv;
325                 if (label.FindFirstOf(" ") != String::NPos)
326                         pdv = "'" + label + "'=" + value;
327                 else
328                         pdv = label + "=" + value;
329
330                 result->Add(pdv);
331
332                 if (multi_index != String::NPos)
333                         multi_prefix = label.SubStr(0, multi_index);
334
335                 begin = spq + 1;
336         }
337
338         return result;
339 }
340
341 String PluginUtility::FormatPerfdata(const Array::Ptr& perfdata)
342 {
343         if (!perfdata)
344                 return "";
345
346         std::ostringstream result;
347
348         ObjectLock olock(perfdata);
349
350         bool first = true;
351         BOOST_FOREACH(const Value& pdv, perfdata) {
352                 if (!first)
353                         result << " ";
354                 else
355                         first = false;
356
357                 if (pdv.IsObjectType<PerfdataValue>())
358                         result << static_cast<PerfdataValue::Ptr>(pdv)->Format();
359                 else
360                         result << pdv;
361         }
362
363         return result.str();
364 }