2 # -*- encoding: utf-8; py-indent-offset: 4 -*-
3 # +------------------------------------------------------------------+
4 # | ____ _ _ __ __ _ __ |
5 # | / ___| |__ ___ ___| | __ | \/ | |/ / |
6 # | | | | '_ \ / _ \/ __| |/ / | |\/| | ' / |
7 # | | |___| | | | __/ (__| < | | | | . \ |
8 # | \____|_| |_|\___|\___|_|\_\___|_| |_|_|\_\ |
10 # | Copyright Mathias Kettner 2018 mk@mathias-kettner.de |
11 # +------------------------------------------------------------------+
13 # This file is part of Check_MK.
14 # The official homepage is at http://mathias-kettner.de/check_mk.
16 # check_mk is free software; you can redistribute it and/or modify it
17 # under the terms of the GNU General Public License as published by
18 # the Free Software Foundation in version 2. check_mk is distributed
19 # in the hope that it will be useful, but WITHOUT ANY WARRANTY; with-
20 # out even the implied warranty of MERCHANTABILITY or FITNESS FOR A
21 # PARTICULAR PURPOSE. See the GNU General Public License for more de-
22 # tails. You should have received a copy of the GNU General Public
23 # License along with GNU Make; see the file COPYING. If not, write
24 # to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
25 # Boston, MA 02110-1301 USA.
31 class DeprecatedDict(dict):
35 class DeprecatedList(list):
39 def append_deprecation_warning(check_function
):
40 '''A wrapper to WARN if legacy code is used
42 If the parse result is of one of the legacy Types the decorated
43 check function will yield an additional WARNING state.
45 These legacy parse results correspond to agents/plugins from version
49 @functools.wraps(check_function
)
50 def wrapper(item
, params
, parsed
):
52 is_deprecated
= isinstance(parsed
, (DeprecatedDict
, DeprecatedList
))
53 catch_these
= Exception if is_deprecated
else ()
56 results
= check_function(item
, params
, parsed
)
57 if isinstance(results
, tuple):
60 for result
in results
:
63 yield 3, "Could not handle data"
66 yield 1, ("Deprecated plugin/agent (see long output)(!)\n"
67 "You are using legacy code, which may lead to crashes and/or"
68 " incomplete information. Please upgrade the monitored host to"
69 " use the plugin 'mk_docker.py'.")
74 def _legacy_docker_get_bytes(string
):
75 '''get number of bytes from string
78 "123GB (42%)" -> 123000000000
84 string
= string
.split('(')[0].strip()
85 tmp
= re
.split('([a-zA-Z]+)', string
)
86 value_string
= tmp
[0].strip()
87 unit_string
= tmp
[1].strip() if len(tmp
) > 1 else 'B'
98 return int(float(value_string
) * factor
)
99 except (ValueError, TypeError):
103 def _legacy_docker_trunk_id(hash_string
):
104 '''normalize to short ID
106 Some docker commands use shortened, some long IDs:
107 Convert long ones to short ones, e.g.
108 "sha256:8b15606a9e3e430cb7ba739fde2fbb3734a19f8a59a825ffa877f9be49059817"
112 long_id
= hash_string
.split(':', 1)[-1]
116 def parse_legacy_docker_node_info(info
): # pylint: disable=too-many-branches
117 '''parse output of "docker info"'''
118 parsed
= DeprecatedDict()
122 # parse legacy json output (verisons 1.5.0 - 1.5.0p6)
123 joined
= " ".join(info
[0])
124 if joined
.endswith("permission denied"):
127 # this may contain a certificate containing newlines.
128 return json
.loads(joined
.replace("\n", "\\n"))
136 # remove '|', it was protecting leading whitespace
140 # ignore misssing keys / pad lines that are not of "key: value" type
144 value
= ':'.join(row
[1:]).strip()
145 # indented keys are prefixed by the last not indented key
146 if len(row0
) - len(key
) == 0:
150 parsed
[prefix
+ key
] = value
152 ## some modifications to match json output:
153 for key
in ("Images", "Containers", "ContainersRunning", "ContainersStopped",
156 parsed
[key
] = int(parsed
[key
])
157 except (KeyError, ValueError):
159 # reconstruct labels (they where not in "k: v" format)
160 parsed
["Labels"] = []
161 for k
in sorted(parsed
.keys()): # pylint: disable=consider-iterating-dictionary
162 if k
.startswith("Labels") and k
!= "Labels":
163 parsed
["Labels"].append(k
[6:] + parsed
.pop(k
))
164 # reconstruct swarm info:
165 if "Swarm" in parsed
:
166 swarm
= {"LocalNodeState": parsed
["Swarm"]}
167 if "SwarmNodeID" in parsed
:
168 swarm
["NodeID"] = parsed
.pop("SwarmNodeID")
169 if "SwarmManagers" in parsed
:
170 swarm
["RemoteManagers"] = parsed
.pop("SwarmManagers")
171 parsed
["Swarm"] = swarm
173 if "Server Version" in parsed
:
174 parsed
["ServerVersion"] = parsed
.pop("Server Version")
175 if "Registry" in parsed
:
176 parsed
["IndexServerAddress"] = parsed
.pop("Registry")
181 def _legacy_docker_parse_table(rows
, keys
):
182 '''docker provides us with space separated tables with field containing spaces
186 TYPE TOTAL ACTIVE SIZE RECLAIMABLE
187 Images 7 6 2.076 GB 936.9 MB (45%)
188 Containers 22 0 2.298 GB 2.298 GB (100%)
189 Local Volumes 5 5 304 B 0 B (0%)
191 if not rows
or not rows
[0]:
197 rex
= regex(field
+ r
'\ *')
198 match
= rex
.search(rows
[0][0])
199 if match
is not None:
200 start
, end
= match
.start(), match
.end()
201 if end
- start
== len(field
):
203 indices
.append((start
, end
))
205 indices
.append((0, 0))
212 line
= {k
: row
[0][i
:j
].strip() for k
, (i
, j
) in zip(keys
, indices
)}
220 def _legacy_map_keys(dictionary
, map_keys
):
221 for old
, new
in map_keys
:
222 if old
in dictionary
:
223 dictionary
[new
] = dictionary
.pop(old
)
226 def parse_legacy_docker_system_df(info
):
227 def int_or_zero(string
):
228 return int(string
.strip() or 0)
231 ('type', 'total', 'active', 'size', 'reclaimable'),
232 (str, int_or_zero
, int_or_zero
, _legacy_docker_get_bytes
, _legacy_docker_get_bytes
),
235 try: # parse legacy json output: from 1.5.0 - 1.5.0p6
236 table
= [json
.loads(",".join(row
)) for row
in info
if row
]
238 table
= _legacy_docker_parse_table(info
, type_map
[0])
240 parsed
= DeprecatedDict()
242 sane_line
= {k
.lower(): v
for k
, v
in line
.items()}
243 _legacy_map_keys(sane_line
, (('totalcount', 'total'),))
244 for key
, type_
in zip(type_map
[0], type_map
[1]):
245 val
= sane_line
.get(key
)
247 sane_line
[key
] = type_(val
)
248 _legacy_map_keys(sane_line
, (('total', 'count'),))
249 parsed
[sane_line
.get("type").lower()] = sane_line
254 def _get_json_list(info
):
260 json_list
.append(json
.loads(' '.join(row
)))
263 # some buggy docker commands produce empty output
264 return [element
for element
in json_list
if element
]
267 def parse_legacy_docker_subsection_images(info
):
269 table
= _get_json_list(info
)
271 map_keys
= (("ID", "Id"), ("CreatedAt", "Created"))
273 parsed
= DeprecatedDict()
275 _legacy_map_keys(item
, map_keys
)
277 val
= item
.get("VirtualSize")
279 item
["VirtualSize"] = _legacy_docker_get_bytes(val
)
281 repotags
= item
.setdefault("RepoTags", [])
282 if not repotags
and item
.get("Repository"):
283 repotags
.append('%s:%s' % (item
["Repository"], item
.get("Tag", "latest")))
285 parsed
[item
.get("Id")] = item
290 def parse_legacy_docker_subsection_image_labels(info
):
292 table
= _get_json_list(info
)
294 parsed
= DeprecatedDict()
295 for long_id
, data
in table
:
297 parsed
[_legacy_docker_trunk_id(long_id
)] = data
301 def parse_legacy_docker_subsection_image_inspect(info
):
302 parsed
= DeprecatedDict()
304 table
= json
.loads(' '.join(' '.join(row
) for row
in info
if row
))
308 parsed
[_legacy_docker_trunk_id(image
["Id"])] = image
312 def parse_legacy_docker_subsection_containers(info
):
314 table
= _get_json_list(info
)
316 map_keys
= (("ID", "Id"), ("CreatedAt", "Created"), ("Names", "Name"))
318 parsed
= DeprecatedDict()
320 _legacy_map_keys(item
, map_keys
)
322 item
["State"] = {"Status": item
["Status"]}
324 parsed
[item
.get("Id")] = item
329 def parse_legacy_docker_messed_up_labels(string
):
330 '''yield key value pairs
332 'string' is in the format "key1=value1,key2=value2,...", but there
333 may be unescaped commas in the values.
336 def toggle_key_value():
337 for chunk
in string
.split('='):
338 for item
in chunk
.rsplit(',', 1):
341 toggler
= toggle_key_value()
342 return dict(zip(toggler
, toggler
))
345 def parse_legacy_docker_node_images(subsections
):
346 images
= parse_legacy_docker_subsection_images(subsections
.get("images", []))
347 image_labels
= parse_legacy_docker_subsection_image_labels(subsections
.get("image_labels", []))
348 image_inspect
= parse_legacy_docker_subsection_image_inspect(
349 subsections
.get("image_inspect", []))
350 containers
= parse_legacy_docker_subsection_containers(subsections
.get("containers", []))
352 for image_id
, pref_info
in image_inspect
.iteritems():
353 image
= images
.setdefault(image_id
, {})
354 image
["Id"] = image_id
355 labels
= pref_info
.get("Config", {}).get("Labels") or {}
356 image
.setdefault("Labels", {}).update(labels
)
357 image
["Created"] = pref_info
["Created"]
358 image
["VirtualSize"] = pref_info
["VirtualSize"]
360 repotags
= pref_info
.get("RepoTags")
362 image
["RepoTags"] = repotags
364 repodigests
= pref_info
.get("RepoDigests") or []
365 if 'RepoDigest' in pref_info
:
366 # Singular? I think this was a bug, and never existed.
367 # But better safe than sorry.
368 repodigests
.append(pref_info
['RepoDigest'])
369 image
["RepoDigests"] = repodigests
372 for image_id
, image
in images
.iteritems():
373 image
["amount_containers"] = 0
374 image
.setdefault("Labels", {})
375 for reta
in image
.get("RepoTags", []):
376 images_lookup
[reta
] = image
377 images_lookup
[_legacy_docker_trunk_id(image_id
) + ':latest'] = image
379 for image_id
, labels
in image_labels
.iteritems():
380 image
= images
.get(_legacy_docker_trunk_id(image_id
))
381 if image
is not None and labels
is not None:
382 image
["Labels"].update(labels
)
384 for cont
in containers
.itervalues():
386 image_repotag
= cont
["Image"]
387 if ':' not in image_repotag
:
388 image_repotag
+= ':latest'
389 image
= images_lookup
.get(image_repotag
)
390 if image
is not None:
391 image
["amount_containers"] += 1
393 labels
= cont
.get("Labels")
394 if isinstance(labels
, (str, unicode)):
395 cont
["Labels"] = parse_legacy_docker_messed_up_labels(labels
)
397 return DeprecatedDict((("images", images
), ("containers", containers
)))
400 def parse_legacy_docker_network_inspect(info
):
402 raw
= json
.loads(''.join(row
[0] for row
in info
if row
))
405 return DeprecatedList(raw
)
408 def parse_legacy_docker_container_node_name(info
):