Refactoring: Changed remaining check parameters starting with an 's' to the new rules...
[check_mk.git] / cmk / special_agents / agent_kubernetes.py
blob071c9b3aa6c0ba669e5d41b4936eba6648ef11c0
1 #!/usr/bin/env python
2 # -*- encoding: utf-8; py-indent-offset: 4 -*-
3 # +------------------------------------------------------------------+
4 # | ____ _ _ __ __ _ __ |
5 # | / ___| |__ ___ ___| | __ | \/ | |/ / |
6 # | | | | '_ \ / _ \/ __| |/ / | |\/| | ' / |
7 # | | |___| | | | __/ (__| < | | | | . \ |
8 # | \____|_| |_|\___|\___|_|\_\___|_| |_|_|\_\ |
9 # | |
10 # | Copyright Mathias Kettner 2019 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.
26 """
27 Special agent for monitoring Kubernetes clusters.
28 """
30 from __future__ import (
31 absolute_import,
32 division,
33 print_function,
36 import argparse
37 from collections import OrderedDict, Sequence
38 import functools
39 import itertools
40 import json
41 import logging
42 import operator
43 import os
44 import sys
45 import time
46 from typing import ( # pylint: disable=unused-import
47 Any, Dict, Generic, List, Mapping, Optional, TypeVar, Union,
50 from kubernetes import client
51 from kubernetes.client.rest import ApiException
53 import cmk.utils.profile
54 import cmk.utils.password_store
57 class PathPrefixAction(argparse.Action):
58 def __call__(self, parser, namespace, values, option_string=None):
59 if not values:
60 return ''
61 path_prefix = '/' + values.strip('/')
62 setattr(namespace, self.dest, path_prefix)
65 def parse(args):
66 # type: (List[str]) -> argparse.Namespace
67 p = argparse.ArgumentParser(description=__doc__)
68 p.add_argument('--debug', action='store_true', help='Debug mode: raise Python exceptions')
69 p.add_argument(
70 '-v',
71 '--verbose',
72 action='count',
73 default=0,
74 help='Verbose mode (for even more output use -vvv)')
75 p.add_argument('host', metavar='HOST', help='Kubernetes host to connect to')
76 p.add_argument('--port', type=int, default=443, help='Port to connect to')
77 p.add_argument('--token', required=True, help='Token for that user')
78 p.add_argument('--url-prefix', help='Custom URL prefix for Kubernetes API calls')
79 p.add_argument(
80 '--path-prefix',
81 default='',
82 action=PathPrefixAction,
83 help='Optional URL path prefix to prepend to Kubernetes API calls')
84 p.add_argument('--no-cert-check', action='store_true', help='Disable certificate verification')
85 p.add_argument(
86 '--profile',
87 metavar='FILE',
88 help='Profile the performance of the agent and write the output to a file')
90 arguments = p.parse_args(args)
91 return arguments
94 def setup_logging(verbosity):
95 # type: (int) -> None
96 if verbosity >= 3:
97 fmt = '%(levelname)s: %(name)s: %(filename)s: %(lineno)s: %(message)s'
98 lvl = logging.DEBUG
99 elif verbosity == 2:
100 fmt = '%(levelname)s: %(filename)s: %(lineno)s: %(message)s'
101 lvl = logging.DEBUG
102 elif verbosity == 1:
103 fmt = '%(levelname)s: %(funcName)s: %(message)s'
104 lvl = logging.INFO
105 else:
106 fmt = '%(levelname)s: %(message)s'
107 lvl = logging.WARNING
108 logging.basicConfig(level=lvl, format=fmt)
111 def parse_frac_prefix(value):
112 # type: (str) -> float
113 if value.endswith('m'):
114 return 0.001 * float(value[:-1])
115 return float(value)
118 def parse_memory(value):
119 # type: (str) -> float
120 if value.endswith('Ki'):
121 return 1024**1 * float(value[:-2])
122 if value.endswith('Mi'):
123 return 1024**2 * float(value[:-2])
124 if value.endswith('Gi'):
125 return 1024**3 * float(value[:-2])
126 if value.endswith('Ti'):
127 return 1024**4 * float(value[:-2])
128 if value.endswith('Pi'):
129 return 1024**5 * float(value[:-2])
130 if value.endswith('Ei'):
131 return 1024**6 * float(value[:-2])
133 if value.endswith('K'):
134 return 1e3 * float(value[:-1])
135 if value.endswith('M'):
136 return 1e6 * float(value[:-1])
137 if value.endswith('G'):
138 return 1e9 * float(value[:-1])
139 if value.endswith('T'):
140 return 1e12 * float(value[:-1])
141 if value.endswith('P'):
142 return 1e15 * float(value[:-1])
143 if value.endswith('E'):
144 return 1e18 * float(value[:-1])
146 return float(value)
149 def left_join_dicts(initial, new, operation):
150 d = {}
151 for key, value in initial.iteritems():
152 if isinstance(value, dict):
153 d[key] = left_join_dicts(value, new.get(key, {}), operation)
154 else:
155 if key in new:
156 d[key] = operation(value, new[key])
157 else:
158 d[key] = value
159 return d
162 class Metadata(object):
163 def __init__(self, metadata):
164 # type: (Optional[client.V1ObjectMeta]) -> None
165 if metadata:
166 self.name = metadata.name
167 self.namespace = metadata.namespace
168 self.creation_timestamp = (time.mktime(metadata.creation_timestamp.timetuple())
169 if metadata.creation_timestamp else None)
170 else:
171 self.name = None
172 self.namespace = None
173 self.creation_timestamp = None
176 class Node(Metadata):
177 def __init__(self, node, stats):
178 # type: (client.V1Node, str) -> None
179 super(Node, self).__init__(node.metadata)
180 self._status = node.status
181 # kubelet replies statistics for the last 2 minutes with 10s
182 # intervals. We only need the latest state.
183 self.stats = eval(stats)['stats'][-1]
185 @property
186 def conditions(self):
187 # type: () -> Optional[Dict[str, str]]
188 if not self._status:
189 return None
190 conditions = self._status.conditions
191 if not conditions:
192 return None
193 return {c.type: c.status for c in conditions}
195 @staticmethod
196 def zero_resources():
197 return {
198 'capacity': {
199 'cpu': 0.0,
200 'memory': 0.0,
201 'pods': 0,
203 'allocatable': {
204 'cpu': 0.0,
205 'memory': 0.0,
206 'pods': 0,
210 @property
211 def resources(self):
212 # type: () -> Dict[str, Dict[str, float]]
213 view = self.zero_resources()
214 if not self._status:
215 return view
216 capacity, allocatable = self._status.capacity, self._status.allocatable
217 if capacity:
218 view['capacity']['cpu'] += parse_frac_prefix(capacity.get('cpu', '0.0'))
219 view['capacity']['memory'] += parse_memory(capacity.get('memory', '0.0'))
220 view['capacity']['pods'] += int(capacity.get('pods', '0'))
221 if allocatable:
222 view['allocatable']['cpu'] += parse_frac_prefix(allocatable.get('cpu', '0.0'))
223 view['allocatable']['memory'] += parse_memory(allocatable.get('memory', '0.0'))
224 view['allocatable']['pods'] += int(allocatable.get('pods', '0'))
225 return view
228 class ComponentStatus(Metadata):
229 def __init__(self, status):
230 # type: (client.V1ComponentStatus) -> None
231 super(ComponentStatus, self).__init__(status.metadata)
232 self._conditions = status.conditions
234 @property
235 def conditions(self):
236 # type: () -> List[Dict[str, str]]
237 if not self._conditions:
238 return []
239 return [{'type': c.type, 'status': c.status} for c in self._conditions]
242 class Pod(Metadata):
243 def __init__(self, pod):
244 # type: (client.V1Pod) -> None
245 super(Pod, self).__init__(pod.metadata)
246 spec = pod.spec
247 self.node = spec.node_name if spec else None
248 self.containers = spec.containers if spec else []
250 @staticmethod
251 def zero_resources():
252 return {
253 'limits': {
254 'cpu': 0.0,
255 'memory': 0.0,
257 'requests': {
258 'cpu': 0.0,
259 'memory': 0.0,
263 @property
264 def resources(self):
265 view = self.zero_resources()
266 for container in self.containers:
267 resources = container.resources
268 if not resources:
269 continue
270 limits = resources.limits
271 if limits:
272 view['limits']['cpu'] += parse_frac_prefix(limits.get('cpu', 'inf'))
273 view['limits']['memory'] += parse_memory(limits.get('memory', 'inf'))
274 else:
275 view['limits']['cpu'] += float('inf')
276 view['limits']['memory'] += float('inf')
277 requests = resources.requests
278 if requests:
279 view['requests']['cpu'] += parse_frac_prefix(requests.get('cpu', '0.0'))
280 view['requests']['memory'] += parse_memory(requests.get('memory', '0.0'))
281 return view
284 class Namespace(Metadata):
285 # TODO: namespaces may have resource quotas and limits
286 # https://kubernetes.io/docs/tasks/administer-cluster/namespaces/
287 def __init__(self, namespace):
288 # type: (client.V1Namespace) -> None
289 super(Namespace, self).__init__(namespace.metadata)
290 self._status = namespace.status
292 @property
293 def phase(self):
294 # type: () -> Optional[str]
295 if self._status:
296 return self._status.phase
297 return None
300 class PersistentVolume(Metadata):
301 def __init__(self, pv):
302 # type: (client.V1PersistentVolume) -> None
303 super(PersistentVolume, self).__init__(pv.metadata)
304 self._status = pv.status
305 self._spec = pv.spec
307 @property
308 def access_modes(self):
309 # type: () -> Optional[List[str]]
310 if self._spec:
311 return self._spec.access_modes
312 return None
314 @property
315 def capacity(self):
316 # type: () -> Optional[float]
317 if not self._spec or not self._spec.capacity:
318 return None
319 storage = self._spec.capacity.get('storage')
320 if storage:
321 return parse_memory(storage)
322 return None
324 @property
325 def phase(self):
326 # type: () -> Optional[str]
327 if self._status:
328 return self._status.phase
329 return None
332 class PersistentVolumeClaim(Metadata):
333 def __init__(self, pvc):
334 # type: (client.V1PersistentVolumeClaim) -> None
335 super(PersistentVolumeClaim, self).__init__(pvc.metadata)
336 self._status = pvc.status
337 self._spec = pvc.spec
339 @property
340 def conditions(self):
341 # type: () -> Optional[client.V1PersistentVolumeClaimCondition]
342 # TODO: don't return client specific object
343 if self._status:
344 return self._status.conditions
345 return None
347 @property
348 def phase(self):
349 # type: () -> Optional[str]
350 if self._status:
351 return self._status.phase
352 return None
354 @property
355 def volume_name(self):
356 # type: () -> Optional[str]
357 if self._spec:
358 return self._spec.volume_name
359 return None
362 class StorageClass(Metadata):
363 def __init__(self, storage_class):
364 # type: (client.V1StorageClass) -> None
365 super(StorageClass, self).__init__(storage_class.metadata)
366 self.provisioner = storage_class.provisioner
367 self.reclaim_policy = storage_class.reclaim_policy
370 class Role(Metadata):
371 def __init__(self, role):
372 # type: (Union[client.V1Role, client.V1ClusterRole]) -> None
373 super(Role, self).__init__(role.metadata)
376 ListElem = TypeVar('ListElem')
379 class ListLike(Generic[ListElem], Sequence):
380 def __init__(self, elements):
381 # type: (List[ListElem]) -> None
382 super(ListLike, self).__init__()
383 self.elements = elements
385 def __getitem__(self, index):
386 return self.elements[index]
388 def __len__(self):
389 # type: () -> int
390 return len(self.elements)
393 class NodeList(ListLike[Node]):
394 def list_nodes(self):
395 # type: () -> Dict[str, List[str]]
396 return {'nodes': [node.name for node in self if node.name]}
398 def conditions(self):
399 # type: () -> Dict[str, Dict[str, str]]
400 return {node.name: node.conditions for node in self if node.name and node.conditions}
402 def resources(self):
403 # type: () -> Dict[str, Dict[str, Dict[str, Optional[float]]]]
404 return {node.name: node.resources for node in self if node.name}
406 def stats(self):
407 return {node.name: node.stats for node in self if node.name}
409 def cluster_resources(self):
410 merge = functools.partial(left_join_dicts, operation=operator.add)
411 return reduce(merge, self.resources().itervalues())
413 def cluster_stats(self):
414 merge = functools.partial(left_join_dicts, operation=operator.add)
415 return reduce(merge, self.stats().itervalues())
418 class ComponentStatusList(ListLike[ComponentStatus]):
419 def list_statuses(self):
420 # type: () -> Dict[str, List[Dict[str, str]]]
421 return {status.name: status.conditions for status in self if status.name}
424 class PodList(ListLike[Pod]):
425 def pods_per_node(self):
426 # type: () -> Dict[str, Dict[str, Dict[str, int]]]
427 pods_sorted = sorted(self, key=lambda pod: pod.node)
428 by_node = itertools.groupby(pods_sorted, lambda pod: pod.node)
429 return {node: {'requests': {'pods': len(list(pods))}} for node, pods in by_node}
431 def pods_in_cluster(self):
432 return {'requests': {'pods': len(self)}}
434 def resources_per_node(self):
435 # type: () -> Dict[str, Dict[str, Dict[str, float]]]
437 Returns the limits and requests of all containers grouped by node. If at least
438 one container does not specify a limit, infinity is returned as the container
439 may consume any amount of resources.
442 pods_sorted = sorted(self, key=lambda pod: pod.node)
443 by_node = itertools.groupby(pods_sorted, lambda pod: pod.node)
444 merge = functools.partial(left_join_dicts, operation=operator.add)
445 return {
446 node: reduce(merge, [p.resources for p in pods
447 ], Pod.zero_resources()) for node, pods in by_node
450 def cluster_resources(self):
451 merge = functools.partial(left_join_dicts, operation=operator.add)
452 return reduce(merge, [p.resources for p in self], Pod.zero_resources())
455 class NamespaceList(ListLike[Namespace]):
456 def list_namespaces(self):
457 # type: () -> Dict[str, Dict[str, Dict[str, Optional[str]]]]
458 return {
459 namespace.name: {
460 'status': {
461 'phase': namespace.phase,
463 } for namespace in self if namespace.name
467 class PersistentVolumeList(ListLike[PersistentVolume]):
468 def list_volumes(self):
469 # type: () -> Dict[str, Dict[str, Union[Optional[List[str]], Optional[float], Dict[str, Optional[str]]]]]
470 # TODO: Output details of the different types of volumes
471 return {
472 pv.name: {
473 'access': pv.access_modes,
474 'capacity': pv.capacity,
475 'status': {
476 'phase': pv.phase,
478 } for pv in self if pv.name
482 class PersistentVolumeClaimList(ListLike[PersistentVolumeClaim]):
483 def list_volume_claims(self):
484 # type: () -> Dict[str, Dict[str, Any]]
485 # TODO: Fix "Any"
486 return {
487 pvc.name: {
488 'namespace': pvc.namespace,
489 'condition': pvc.conditions,
490 'phase': pvc.phase,
491 'volume': pvc.volume_name,
492 } for pvc in self if pvc.name
496 class StorageClassList(ListLike[StorageClass]):
497 def list_storage_classes(self):
498 # type: () -> Dict[Any, Dict[str, Any]]
499 # TODO: should be Dict[str, Dict[str, Optional[str]]]
500 return {
501 storage_class.name: {
502 'provisioner': storage_class.provisioner,
503 'reclaim_policy': storage_class.reclaim_policy
504 } for storage_class in self if storage_class.name
508 class RoleList(ListLike[Role]):
509 def list_roles(self):
510 return [{
511 'name': role.name,
512 'namespace': role.namespace,
513 'creation_timestamp': role.creation_timestamp
514 } for role in self if role.name]
517 class Metric(object):
518 def __init__(self, metric):
519 self.from_object = metric['describedObject']
520 self.metrics = {metric['metricName']: metric.get('value')}
522 def __add__(self, other):
523 assert self.from_object == other.from_object
524 self.metrics.update(other.metrics)
525 return self
527 def __str__(self):
528 return str(self.__dict__)
530 def __repr__(self):
531 return str(self.__dict__)
534 class MetricList(ListLike[Metric]):
535 def __add__(self, other):
536 return MetricList([a + b for a, b in zip(self, other)])
538 def list_metrics(self):
539 return [item.__dict__ for item in self]
542 class Group(object):
544 A group of elements where an element is e.g. a piggyback host.
547 def __init__(self):
548 # type: () -> None
549 super(Group, self).__init__()
550 self.elements = OrderedDict() # type: OrderedDict[str, Element]
552 def get(self, element_name):
553 # type: (str) -> Element
554 if element_name not in self.elements:
555 self.elements[element_name] = Element()
556 return self.elements[element_name]
558 def join(self, section_name, pairs):
559 # type: (str, Mapping[str, Dict[str, Any]]) -> Group
560 for element_name, data in pairs.iteritems():
561 section = self.get(element_name).get(section_name)
562 section.insert(data)
563 return self
565 def output(self):
566 # type: () -> List[str]
567 data = []
568 for name, element in self.elements.iteritems():
569 data.append('<<<<%s>>>>' % name)
570 data.extend(element.output())
571 data.append('<<<<>>>>')
572 return data
575 class Element(object):
577 An element that bundles a collection of sections.
580 def __init__(self):
581 # type: () -> None
582 super(Element, self).__init__()
583 self.sections = OrderedDict() # type: OrderedDict[str, Section]
585 def get(self, section_name):
586 # type: (str) -> Section
587 if section_name not in self.sections:
588 self.sections[section_name] = Section()
589 return self.sections[section_name]
591 def output(self):
592 # type: () -> List[str]
593 data = []
594 for name, section in self.sections.iteritems():
595 data.append('<<<%s:sep(0)>>>' % name)
596 data.append(section.output())
597 return data
600 class Section(object):
602 An agent section.
605 def __init__(self):
606 # type: () -> None
607 super(Section, self).__init__()
608 self.content = OrderedDict() # type: OrderedDict[str, Dict[str, Any]]
610 def insert(self, data):
611 # type: (Dict[str, Any]) -> None
612 for key, value in data.iteritems():
613 if key not in self.content:
614 self.content[key] = value
615 else:
616 if isinstance(value, dict):
617 self.content[key].update(value)
618 else:
619 raise ValueError('Key %s is already present and cannot be merged' % key)
621 def output(self):
622 # type: () -> str
623 return json.dumps(self.content)
626 class ApiData(object):
628 Contains the collected API data.
631 def __init__(self, api_client):
632 # type: (client.ApiClient) -> None
633 super(ApiData, self).__init__()
634 logging.info('Collecting API data')
636 logging.debug('Constructing API client wrappers')
637 core_api = client.CoreV1Api(api_client)
638 storage_api = client.StorageV1Api(api_client)
639 rbac_authorization_api = client.RbacAuthorizationV1Api(api_client)
641 self.custom_api = client.CustomObjectsApi(api_client)
643 logging.debug('Retrieving data')
644 storage_classes = storage_api.list_storage_class()
645 namespaces = core_api.list_namespace()
646 roles = rbac_authorization_api.list_role_for_all_namespaces()
647 cluster_roles = rbac_authorization_api.list_cluster_role()
648 component_statuses = core_api.list_component_status()
649 nodes = core_api.list_node()
650 # Try to make it a post, when client api support sending post data
651 # include {"num_stats": 1} to get the latest only and use less bandwidth
652 nodes_stats = [
653 core_api.connect_get_node_proxy_with_path(node.metadata.name, "stats")
654 for node in nodes.items
656 pvs = core_api.list_persistent_volume()
657 pvcs = core_api.list_persistent_volume_claim_for_all_namespaces()
658 pods = core_api.list_pod_for_all_namespaces()
660 logging.debug('Assigning collected data')
661 self.storage_classes = StorageClassList(map(StorageClass, storage_classes.items))
662 self.namespaces = NamespaceList(map(Namespace, namespaces.items))
663 self.roles = RoleList(map(Role, roles.items))
664 self.cluster_roles = RoleList(map(Role, cluster_roles.items))
665 self.component_statuses = ComponentStatusList(
666 map(ComponentStatus, component_statuses.items))
667 self.nodes = NodeList(map(Node, nodes.items, nodes_stats))
668 self.persistent_volumes = PersistentVolumeList(map(PersistentVolume, pvs.items))
669 self.persistent_volume_claims = PersistentVolumeClaimList(
670 map(PersistentVolumeClaim, pvcs.items))
671 self.pods = PodList(map(Pod, pods.items))
673 pods_custom_metrics = {
674 "memory": ['memory_rss', 'memory_swap', 'memory_usage_bytes', 'memory_max_usage_bytes'],
675 "fs": ['fs_inodes', 'fs_reads', 'fs_writes', 'fs_limit_bytes', 'fs_usage_bytes'],
676 "cpu": ['cpu_system', 'cpu_user', 'cpu_usage']
679 self.pods_Metrics = dict() # type: Dict[str, Dict[str, List]]
680 for metric_group, metrics in pods_custom_metrics.items():
681 self.pods_Metrics[metric_group] = self.get_namespaced_group_metric(metrics)
683 def get_namespaced_group_metric(self, metrics):
684 # type: (List[str]) -> Dict[str, List]
685 queries = [self.get_namespaced_custom_pod_metric(metric) for metric in metrics]
687 grouped_metrics = {} # type: Dict[str, List]
688 for response in queries:
689 for namespace in response:
690 grouped_metrics.setdefault(namespace, []).append(response[namespace])
692 for namespace in grouped_metrics:
693 grouped_metrics[namespace] = reduce(operator.add,
694 grouped_metrics[namespace]).list_metrics()
696 return grouped_metrics
698 def get_namespaced_custom_pod_metric(self, metric):
699 # type: (str) -> Dict
701 logging.debug('Query Custom Metrics Endpoint: %s', metric)
702 custom_metric = {}
703 for namespace in self.namespaces:
704 try:
705 data = map(
706 Metric,
707 self.custom_api.get_namespaced_custom_object(
708 'custom.metrics.k8s.io',
709 'v1beta1',
710 namespace.name,
711 'pods/*',
712 metric,
713 )['items'])
714 custom_metric[namespace.name] = MetricList(data)
715 except ApiException as err:
716 if err.status == 404:
717 logging.info('Data unavailable. No pods in namespace %s', namespace.name)
718 elif err.status == 500:
719 logging.info('Data unavailable. %s', err)
720 else:
721 raise err
723 return custom_metric
725 def cluster_sections(self):
726 # type: () -> str
727 logging.info('Output cluster sections')
728 e = Element()
729 e.get('k8s_nodes').insert(self.nodes.list_nodes())
730 e.get('k8s_namespaces').insert(self.namespaces.list_namespaces())
731 e.get('k8s_persistent_volumes').insert(self.persistent_volumes.list_volumes())
732 e.get('k8s_component_statuses').insert(self.component_statuses.list_statuses())
733 e.get('k8s_persistent_volume_claims').insert(
734 self.persistent_volume_claims.list_volume_claims())
735 e.get('k8s_storage_classes').insert(self.storage_classes.list_storage_classes())
736 e.get('k8s_roles').insert({'roles': self.roles.list_roles()})
737 e.get('k8s_roles').insert({'cluster_roles': self.cluster_roles.list_roles()})
738 e.get('k8s_resources').insert(self.nodes.cluster_resources())
739 e.get('k8s_resources').insert(self.pods.cluster_resources())
740 e.get('k8s_resources').insert(self.pods.pods_in_cluster())
741 e.get('k8s_stats').insert(self.nodes.cluster_stats())
742 return '\n'.join(e.output())
744 def node_sections(self):
745 # type: () -> str
746 logging.info('Output node sections')
747 g = Group()
748 g.join('k8s_resources', self.nodes.resources())
749 g.join('k8s_resources', self.pods.resources_per_node())
750 g.join('k8s_resources', self.pods.pods_per_node())
751 g.join('k8s_stats', self.nodes.stats())
752 g.join('k8s_conditions', self.nodes.conditions())
753 return '\n'.join(g.output())
755 def custom_metrics_section(self):
756 # type: () -> str
757 logging.info('Output pods custom metrics')
758 e = Element()
759 for c_metric in self.pods_Metrics:
760 e.get('k8s_pods_%s' % c_metric).insert(self.pods_Metrics[c_metric])
761 return '\n'.join(e.output())
764 def get_api_client(arguments):
765 # type: (argparse.Namespace) -> client.ApiClient
766 logging.info('Constructing API client')
768 config = client.Configuration()
769 if arguments.url_prefix:
770 config.host = '%s:%s%s' % (arguments.url_prefix, arguments.port, arguments.path_prefix)
771 else:
772 config.host = 'https://%s:%s%s' % (arguments.host, arguments.port, arguments.path_prefix)
774 config.api_key_prefix['authorization'] = 'Bearer'
775 config.api_key['authorization'] = arguments.token
777 if arguments.no_cert_check:
778 logging.warn('Disabling SSL certificate verification')
779 config.verify_ssl = False
780 else:
781 config.ssl_ca_cert = os.environ.get('REQUESTS_CA_BUNDLE')
783 return client.ApiClient(config)
786 def main(args=None):
787 # type: (Optional[List[str]]) -> int
788 if args is None:
789 cmk.utils.password_store.replace_passwords()
790 args = sys.argv[1:]
791 arguments = parse(args)
793 try:
794 setup_logging(arguments.verbose)
795 logging.debug('parsed arguments: %s\n', arguments)
797 with cmk.utils.profile.Profile(
798 enabled=bool(arguments.profile), profile_file=arguments.profile):
799 api_client = get_api_client(arguments)
800 api_data = ApiData(api_client)
801 print(api_data.cluster_sections())
802 print(api_data.node_sections())
803 print(api_data.custom_metrics_section())
804 except Exception as e:
805 if arguments.debug:
806 raise
807 print("%s" % e, file=sys.stderr)
808 return 1
809 return 0