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.
30 class DeprecatedDict(dict):
34 class DeprecatedList(list):
38 def append_deprecation_warning(check_function
):
39 '''A wrapper to WARN if legacy code is used
41 If the parse result is of one of the legacy Types the decorated
42 check function will yield an additional WARNING state.
44 These legacy parse results correspond to agents/plugins from version
48 @functools.wraps(check_function
)
49 def wrapper(item
, params
, parsed
):
51 is_deprecated
= isinstance(parsed
, (DeprecatedDict
, DeprecatedList
))
52 catch_these
= Exception if is_deprecated
else ()
55 results
= check_function(item
, params
, parsed
)
56 if isinstance(results
, tuple):
59 for result
in results
:
62 yield 3, "Could not handle data"
65 yield 1, ("Deprecated plugin/agent (see long output)(!)\n"
66 "You are using legacy code, which may lead to crashes and/or"
67 " incomplete information. Please upgrade the monitored host to"
68 " use the plugin 'mk_docker.py'.")
73 def _legacy_docker_get_bytes(string
):
74 '''get number of bytes from string
77 "123GB (42%)" -> 123000000000
83 string
= string
.split('(')[0].strip()
84 tmp
= re
.split('([a-zA-Z]+)', string
)
85 value_string
= tmp
[0].strip()
86 unit_string
= tmp
[1].strip() if len(tmp
) > 1 else 'B'
97 return int(float(value_string
) * factor
)
98 except (ValueError, TypeError):
102 def _legacy_docker_trunk_id(hash_string
):
103 '''normalize to short ID
105 Some docker commands use shortened, some long IDs:
106 Convert long ones to short ones, e.g.
107 "sha256:8b15606a9e3e430cb7ba739fde2fbb3734a19f8a59a825ffa877f9be49059817"
111 long_id
= hash_string
.split(':', 1)[-1]
115 def _get_repo_tag(string
):
117 return tuple(string
.rsplit(":", 1))
118 return string
, "latest"
121 def parse_legacy_docker_node_info(info
):
122 '''parse output of "docker info"'''
123 parsed
= DeprecatedDict()
127 # parse legacy json output (verisons 1.5.0 - 1.5.0p6)
128 joined
= " ".join(info
[0])
129 if joined
.endswith("permission denied"):
132 # this may contain a certificate containing newlines.
133 return json
.loads(joined
.replace("\n", "\\n"))
141 # remove '|', it was protecting leading whitespace
145 # ignore misssing keys / pad lines that are not of "key: value" type
149 value
= ':'.join(row
[1:]).strip()
150 # indented keys are prefixed by the last not indented key
151 if len(row0
) - len(key
) == 0:
155 parsed
[prefix
+ key
] = value
157 ## some modifications to match json output:
158 for key
in ("Images", "Containers", "ContainersRunning", "ContainersStopped",
161 parsed
[key
] = int(parsed
[key
])
162 except (KeyError, ValueError):
164 # reconstruct labels (they where not in "k: v" format)
165 parsed
["Labels"] = []
166 for k
in sorted(parsed
.keys()): # pylint: disable=consider-iterating-dictionary
167 if k
.startswith("Labels") and k
!= "Labels":
168 parsed
["Labels"].append(k
[6:] + parsed
.pop(k
))
169 # reconstruct swarm info:
170 if "Swarm" in parsed
:
171 swarm
= {"LocalNodeState": parsed
["Swarm"]}
172 if "SwarmNodeID" in parsed
:
173 swarm
["NodeID"] = parsed
.pop("SwarmNodeID")
174 if "SwarmManagers" in parsed
:
175 swarm
["RemoteManagers"] = parsed
.pop("SwarmManagers")
176 parsed
["Swarm"] = swarm
178 if "Server Version" in parsed
:
179 parsed
["ServerVersion"] = parsed
.pop("Server Version")
180 if "Registry" in parsed
:
181 parsed
["IndexServerAddress"] = parsed
.pop("Registry")
186 def _legacy_docker_parse_table(rows
, fields
, keys
):
187 '''docker provides us with space separated tables with field containing spaces
191 TYPE TOTAL ACTIVE SIZE RECLAIMABLE
192 Images 7 6 2.076 GB 936.9 MB (45%)
193 Containers 22 0 2.298 GB 2.298 GB (100%)
194 Local Volumes 5 5 304 B 0 B (0%)
196 if not rows
or not rows
[0]:
201 rex
= regex(field
+ r
'\ *')
202 m
= rex
.search(rows
[0][0])
204 start
, end
= m
.start(), m
.end()
205 if end
- start
== len(field
):
207 indices
.append((start
, end
))
209 indices
.append((0, 0))
216 line
= {k
: row
[0][i
:j
].strip() for k
, (i
, j
) in zip(keys
, indices
)}
224 def parse_legacy_docker_system_df(info
):
226 return int(s
.strip() or 0)
229 ('TYPE', 'TOTAL', 'ACTIVE', 'SIZE', 'RECLAIMABLE'),
230 ('Type', 'TotalCount', 'Active', 'Size', 'Reclaimable'),
231 (str, int_or_zero
, int_or_zero
, _legacy_docker_get_bytes
, _legacy_docker_get_bytes
),
234 try: # parse legacy json output: from 1.5.0 - 1.5.0p6
235 table
= [json
.loads(",".join(row
)) for row
in info
if row
]
237 table
= _legacy_docker_parse_table(info
, field_map
[0], field_map
[1])
239 parsed
= DeprecatedDict()
241 for key
, type_
in zip(field_map
[1], field_map
[2]):
244 line
[key
] = type_(val
)
245 parsed
[line
.get("Type").lower()] = line
250 def _get_json_list(info
):
256 json_list
.append(json
.loads(' '.join(row
)))
259 # some buggy docker commands produce empty output
260 return [element
for element
in json_list
if element
]
263 def parse_legacy_docker_subsection_images(info
):
265 table
= _get_json_list(info
)
267 parsed
= DeprecatedDict()
269 val
= item
.get("VirtualSize")
271 item
["VirtualSize"] = _legacy_docker_get_bytes(val
)
272 parsed
[item
.get("ID")] = item
277 def parse_legacy_docker_subsection_image_labels(info
):
279 table
= _get_json_list(info
)
281 parsed
= DeprecatedDict()
282 for long_id
, data
in table
:
284 parsed
[_legacy_docker_trunk_id(long_id
)] = data
288 def parse_legacy_docker_subsection_image_inspect(info
):
289 parsed
= DeprecatedDict()
291 table
= json
.loads(' '.join(' '.join(row
) for row
in info
if row
))
295 parsed
[_legacy_docker_trunk_id(image
["Id"])] = image
299 def parse_legacy_docker_subsection_containers(info
):
301 table
= _get_json_list(info
)
303 parsed
= DeprecatedDict()
305 image_name
= item
.get("Image", "")
306 item
["Repository"], item
["Tag"] = _get_repo_tag(image_name
)
307 parsed
[item
.get("ID")] = item
312 def _split_subsections(info
):
318 if row
[0].startswith('[[[') and row
[0].endswith(']]]'):
319 subname
= row
[0].strip('[]')
321 subsections
.setdefault(subname
, []).append(row
)
325 def parse_legacy_docker_messed_up_labels(string
):
326 '''yield key value pairs
328 'string' is in the format "key1=value1,key2=value2,...", but there
329 may be unescaped commas in the values.
332 def toggle_key_value():
333 for chunk
in string
.split('='):
334 for item
in chunk
.rsplit(',', 1):
337 toggler
= toggle_key_value()
338 return dict(zip(toggler
, toggler
))
341 def parse_legacy_docker_node_images(info
):
342 subsections
= _split_subsections(info
)
343 images
= parse_legacy_docker_subsection_images(subsections
.get("images", []))
344 image_labels
= parse_legacy_docker_subsection_image_labels(subsections
.get("image_labels", []))
345 image_inspect
= parse_legacy_docker_subsection_image_inspect(
346 subsections
.get("image_inspect", []))
347 containers
= parse_legacy_docker_subsection_containers(subsections
.get("containers", []))
349 for image_id
, pref_info
in image_inspect
.iteritems():
350 image
= images
.setdefault(image_id
, {})
351 image
["ID"] = image_id
352 labels
= pref_info
.get("Config", {}).get("Labels")
353 if labels
is not None:
354 image
.setdefault("__labels__", {}).update(labels
)
355 image
["CreatedAt"] = pref_info
["Created"]
356 image
["VirtualSize"] = pref_info
["VirtualSize"]
357 repotags
= pref_info
.get("RepoTags")
359 image
["Repository"], image
["Tag"] = _get_repo_tag(repotags
[-1])
361 repo
= pref_info
.get("RepoDigest", "").split('@', 1)[0]
362 image
["Repository"], image
["Tag"] = _get_repo_tag(repo
)
364 for image
in images
.itervalues():
365 image
["amount_containers"] = 0
366 image
.setdefault("__labels__", {})
368 for image_id
, labels
in image_labels
.iteritems():
369 image
= images
.get(_legacy_docker_trunk_id(image_id
))
370 if image
is not None and labels
is not None:
371 image
["__labels__"].update(labels
)
373 mapping
= {(i
['Repository'], i
['Tag']): i
['ID'] for i
in images
.itervalues()}
375 for cont
in containers
.itervalues():
376 image_id
= mapping
.get((cont
["Repository"], cont
["Tag"]))
377 image
= images
.get(image_id
)
378 if image
is not None:
379 image
["amount_containers"] += 1
381 labels
= cont
.get("Labels")
382 if isinstance(labels
, (str, unicode)):
383 cont
["Labels"] = parse_legacy_docker_messed_up_labels(labels
)
385 return DeprecatedDict((("images", images
), ("containers", containers
)))
388 def parse_legacy_docker_network_inspect(info
):
390 raw
= json
.loads(''.join(row
[0] for row
in info
if row
))
393 return DeprecatedList(raw
)