master
py 1,294 lines 38.8 KB
Raw
1 #!/usr/bin/env python3
2
3 import json
4 import re
5 import sys
6 from copy import deepcopy
7
8 from jsonschema import ValidationError
9
10 from _common import (
11 AGENT_REPO,
12 INTEGRATIONS_PATH,
13 METADATA_PATTERN,
14 REPO_PATH,
15 debug,
16 fail_on_warnings,
17 load_collectors,
18 load_yaml,
19 make_id,
20 make_validator,
21 warn,
22 )
23
24 TEMPLATE_PATH = INTEGRATIONS_PATH / 'templates'
25 OUTPUT_PATH = INTEGRATIONS_PATH / 'integrations.js'
26 JSON_PATH = INTEGRATIONS_PATH / 'integrations.json'
27 CATEGORIES_FILE = INTEGRATIONS_PATH / 'categories.yaml'
28 DISTROS_FILE = REPO_PATH / '.github' / 'data' / 'distros.yml'
29
30 FLOWS_SOURCES = [
31 (AGENT_REPO, REPO_PATH / 'src' / 'crates' / 'netflow-plugin' / 'metadata.yaml', False),
32 ]
33
34 DEPLOY_SOURCES = [
35 (AGENT_REPO, INTEGRATIONS_PATH / 'deploy.yaml', False),
36 ]
37
38 EXPORTER_SOURCES = [
39 (AGENT_REPO, REPO_PATH / 'src' / 'exporting', True),
40 ]
41
42 AGENT_NOTIFICATION_SOURCES = [
43 (AGENT_REPO, REPO_PATH / 'src' / 'health' / 'notifications', True),
44 ]
45
46 CLOUD_NOTIFICATION_SOURCES = [
47 (AGENT_REPO, INTEGRATIONS_PATH / 'cloud-notifications' / 'metadata.yaml', False),
48 ]
49
50 LOGS_SOURCES = [
51 (AGENT_REPO, INTEGRATIONS_PATH / 'logs' / 'metadata.yaml', False),
52 ]
53
54 AUTHENTICATION_SOURCES = [
55 (AGENT_REPO, INTEGRATIONS_PATH / 'cloud-authentication' / 'metadata.yaml', False),
56 ]
57
58 SECRETSTORE_SOURCES = [
59 (AGENT_REPO, REPO_PATH / 'src' / 'go' / 'plugin' / 'agent' / 'secrets' / 'secretstore' / 'backends', True),
60 ]
61
62 SERVICE_DISCOVERY_SOURCES = [
63 (AGENT_REPO, REPO_PATH / 'src' / 'go' / 'plugin' / 'go.d' / 'discovery' / 'sdext' / 'discoverer', True),
64 ]
65
66 COLLECTOR_RENDER_KEYS = [
67 'alerts',
68 'metrics',
69 'functions',
70 'overview',
71 'related_resources',
72 'setup',
73 'troubleshooting',
74 ]
75
76 FLOWS_RENDER_KEYS = [
77 'alerts',
78 'metrics',
79 'functions',
80 'overview',
81 'related_resources',
82 'setup',
83 'troubleshooting',
84 ]
85
86 EXPORTER_RENDER_KEYS = [
87 'overview',
88 'setup',
89 'troubleshooting',
90 ]
91
92 AGENT_NOTIFICATION_RENDER_KEYS = [
93 'overview',
94 'setup',
95 'troubleshooting',
96 ]
97
98 CLOUD_NOTIFICATION_RENDER_KEYS = [
99 'setup',
100 'troubleshooting',
101 ]
102
103 LOGS_RENDER_KEYS = [
104 'overview',
105 'setup',
106 ]
107
108 AUTHENTICATION_RENDER_KEYS = [
109 'overview',
110 'setup',
111 'troubleshooting',
112 ]
113
114 SECRETSTORE_RENDER_KEYS = [
115 'overview',
116 'setup',
117 'collector_configs',
118 'troubleshooting',
119 ]
120
121 SERVICE_DISCOVERY_RENDER_KEYS = [
122 'overview',
123 'setup',
124 'services',
125 'verify',
126 'troubleshooting',
127 ]
128
129 CUSTOM_TAG_PATTERN = re.compile('\\{% if .*?%\\}.*?\\{% /if %\\}|\\{%.*?%\\}', flags=re.DOTALL)
130 FIXUP_BLANK_PATTERN = re.compile('\\\\\\n *\\n')
131
132 CATEGORY_VALIDATOR = make_validator('./categories.json#')
133 DEPLOY_VALIDATOR = make_validator('./deploy.json#')
134 EXPORTER_VALIDATOR = make_validator('./exporter.json#')
135 AGENT_NOTIFICATION_VALIDATOR = make_validator('./agent_notification.json#')
136 CLOUD_NOTIFICATION_VALIDATOR = make_validator('./cloud_notification.json#')
137 LOGS_VALIDATOR = make_validator('./logs.json#')
138 AUTHENTICATION_VALIDATOR = make_validator('./authentication.json#')
139 FLOWS_VALIDATOR = make_validator('./flows.json#')
140 SECRETSTORE_VALIDATOR = make_validator('./secretstore.json#')
141 SERVICE_DISCOVERY_VALIDATOR = make_validator('./service_discovery.json#')
142
143 _jinja_env = False
144
145
146 def get_jinja_env():
147 global _jinja_env
148
149 if not _jinja_env:
150 from jinja2 import Environment, FileSystemLoader, select_autoescape
151
152 _jinja_env = Environment(
153 loader=FileSystemLoader(TEMPLATE_PATH),
154 autoescape=select_autoescape(),
155 block_start_string='[%',
156 block_end_string='%]',
157 variable_start_string='[[',
158 variable_end_string=']]',
159 comment_start_string='[#',
160 comment_end_string='#]',
161 trim_blocks=True,
162 lstrip_blocks=True,
163 )
164
165 _jinja_env.globals.update(strfy=strfy, anchorfy=anchorfy)
166
167 return _jinja_env
168
169
170 def strfy(value):
171 if isinstance(value, bool):
172 return "yes" if value else "no"
173 if isinstance(value, str):
174 return ' '.join([v.strip() for v in value.strip().split("\n") if v]).replace('|', '/')
175 return value
176
177
178 def anchorfy(value):
179 if value is None:
180 return ''
181
182 anchor = str(value).strip().lower()
183 anchor = re.sub(r'[^a-z0-9]+', '-', anchor)
184 anchor = re.sub(r'-{2,}', '-', anchor).strip('-')
185
186 return anchor
187
188
189 def get_section_template_name(item, key):
190 integration_type = item.get('integration_type')
191
192 if key == 'setup':
193 if integration_type == 'secretstore':
194 return 'setup-secretstore.md'
195 if integration_type == 'service_discovery':
196 return 'setup-service_discovery.md'
197 if integration_type == 'logs':
198 return 'setup-logs.md'
199 return 'setup-generic.md'
200
201 if integration_type == 'service_discovery':
202 if key == 'services':
203 return 'sd-services.md'
204 if key == 'verify':
205 return 'sd-verify.md'
206
207 return f'{key}.md'
208
209
210 def get_category_sets(categories):
211 default = set()
212 valid = set()
213
214 for c in categories:
215 if 'id' in c:
216 valid.add(c['id'])
217
218 if c.get('collector_default', False):
219 default.add(c['id'])
220
221 if 'children' in c and c['children']:
222 d, v = get_category_sets(c['children'])
223 default |= d
224 valid |= v
225
226 return (default, valid)
227
228
229 def load_categories():
230 categories = load_yaml(CATEGORIES_FILE)
231
232 if not categories:
233 sys.exit(1)
234
235 try:
236 CATEGORY_VALIDATOR.validate(categories)
237 except ValidationError as e:
238 warn(
239 f'Failed to validate {CATEGORIES_FILE} against the schema: {e.message} (path: {"/".join(str(p) for p in e.absolute_path)})',
240 CATEGORIES_FILE)
241 sys.exit(1)
242
243 return categories
244
245
246 def load_flows():
247 ret = []
248
249 for repo, path, match in FLOWS_SOURCES:
250 if match and path.exists() and path.is_dir():
251 files = list(path.glob(METADATA_PATTERN))
252 elif not match and path.exists() and path.is_file():
253 files = [path]
254 else:
255 files = []
256
257 for file in files:
258 debug(f'Loading {file}.')
259 data = load_yaml(file)
260
261 if not data:
262 continue
263
264 try:
265 FLOWS_VALIDATOR.validate(data)
266 except ValidationError as e:
267 warn(
268 f'Failed to validate {file} against the schema: {e.message} (path: {"/".join(str(p) for p in e.absolute_path)})',
269 file)
270 continue
271
272 for idx, item in enumerate(data['modules']):
273 item['meta']['plugin_name'] = data['plugin_name']
274 item['integration_type'] = 'flows'
275 item['_src_path'] = file
276 item['_repo'] = repo
277 item['_index'] = idx
278 ret.append(item)
279
280 return ret
281
282
283 def _load_deploy_file(file, repo):
284 ret = []
285 debug(f'Loading {file}.')
286 data = load_yaml(file)
287
288 if not data:
289 return []
290
291 try:
292 DEPLOY_VALIDATOR.validate(data)
293 except ValidationError as e:
294 warn(
295 f'Failed to validate {file} against the schema: {e.message} (path: {"/".join(str(p) for p in e.absolute_path)})',
296 file)
297 return []
298
299 for idx, item in enumerate(data):
300 item['integration_type'] = 'deploy'
301 item['_src_path'] = file
302 item['_repo'] = repo
303 item['_index'] = idx
304 ret.append(item)
305
306 return ret
307
308
309 def load_deploy():
310 ret = []
311
312 for repo, path, match in DEPLOY_SOURCES:
313 if match and path.exists() and path.is_dir():
314 for file in path.glob(METADATA_PATTERN):
315 ret.extend(_load_deploy_file(file, repo))
316 elif not match and path.exists() and path.is_file():
317 ret.extend(_load_deploy_file(path, repo))
318
319 return ret
320
321
322 def _load_exporter_file(file, repo):
323 debug(f'Loading {file}.')
324 data = load_yaml(file)
325
326 if not data:
327 return []
328
329 try:
330 EXPORTER_VALIDATOR.validate(data)
331 except ValidationError as e:
332 warn(
333 f'Failed to validate {file} against the schema: {e.message} (path: {"/".join(str(p) for p in e.absolute_path)})',
334 file)
335 return []
336
337 if 'id' in data:
338 data['integration_type'] = 'exporter'
339 data['_src_path'] = file
340 data['_repo'] = repo
341 data['_index'] = 0
342
343 return [data]
344 else:
345 ret = []
346
347 for idx, item in enumerate(data):
348 item['integration_type'] = 'exporter'
349 item['_src_path'] = file
350 item['_repo'] = repo
351 item['_index'] = idx
352 ret.append(item)
353
354 return ret
355
356
357 def load_exporters():
358 ret = []
359
360 for repo, path, match in EXPORTER_SOURCES:
361 if match and path.exists() and path.is_dir():
362 for file in path.glob(METADATA_PATTERN):
363 ret.extend(_load_exporter_file(file, repo))
364 elif not match and path.exists() and path.is_file():
365 ret.extend(_load_exporter_file(path, repo))
366
367 return ret
368
369
370 def _load_agent_notification_file(file, repo):
371 debug(f'Loading {file}.')
372 data = load_yaml(file)
373
374 if not data:
375 return []
376
377 try:
378 AGENT_NOTIFICATION_VALIDATOR.validate(data)
379 except ValidationError as e:
380 warn(
381 f'Failed to validate {file} against the schema: {e.message} (path: {"/".join(str(p) for p in e.absolute_path)})',
382 file)
383 return []
384
385 if 'id' in data:
386 data['integration_type'] = 'agent_notification'
387 data['_src_path'] = file
388 data['_repo'] = repo
389 data['_index'] = 0
390
391 return [data]
392 else:
393 ret = []
394
395 for idx, item in enumerate(data):
396 item['integration_type'] = 'agent_notification'
397 item['_src_path'] = file
398 item['_repo'] = repo
399 item['_index'] = idx
400 ret.append(item)
401
402 return ret
403
404
405 def load_agent_notifications():
406 ret = []
407
408 for repo, path, match in AGENT_NOTIFICATION_SOURCES:
409 if match and path.exists() and path.is_dir():
410 for file in path.glob(METADATA_PATTERN):
411 ret.extend(_load_agent_notification_file(file, repo))
412 elif not match and path.exists() and path.is_file():
413 ret.extend(_load_agent_notification_file(path, repo))
414
415 return ret
416
417
418 def _load_cloud_notification_file(file, repo):
419 debug(f'Loading {file}.')
420 data = load_yaml(file)
421
422 if not data:
423 return []
424
425 try:
426 CLOUD_NOTIFICATION_VALIDATOR.validate(data)
427 except ValidationError as e:
428 warn(
429 f'Failed to validate {file} against the schema: {e.message} (path: {"/".join(str(p) for p in e.absolute_path)})',
430 file)
431 return []
432
433 if 'id' in data:
434 data['integration_type'] = 'cloud_notification'
435 data['_src_path'] = file
436 data['_repo'] = repo
437 data['_index'] = 0
438
439 return [data]
440 else:
441 ret = []
442
443 for idx, item in enumerate(data):
444 item['integration_type'] = 'cloud_notification'
445 item['_src_path'] = file
446 item['_repo'] = repo
447 item['_index'] = idx
448 ret.append(item)
449
450 return ret
451
452
453 def load_cloud_notifications():
454 ret = []
455
456 for repo, path, match in CLOUD_NOTIFICATION_SOURCES:
457 if match and path.exists() and path.is_dir():
458 for file in path.glob(METADATA_PATTERN):
459 ret.extend(_load_cloud_notification_file(file, repo))
460 elif not match and path.exists() and path.is_file():
461 ret.extend(_load_cloud_notification_file(path, repo))
462
463 return ret
464
465
466 def _load_logs_file(file, repo):
467 debug(f'Loading {file}.')
468 data = load_yaml(file)
469
470 if not data:
471 return []
472
473 try:
474 LOGS_VALIDATOR.validate(data)
475 except ValidationError as e:
476 warn(
477 f'Failed to validate {file} against the schema: {e.message} (path: {"/".join(str(p) for p in e.absolute_path)})',
478 file)
479 return []
480
481 if 'id' in data:
482 data['integration_type'] = 'logs'
483 data['_src_path'] = file
484 data['_repo'] = repo
485 data['_index'] = 0
486
487 return [data]
488 else:
489 ret = []
490
491 for idx, item in enumerate(data):
492 item['integration_type'] = 'logs'
493 item['_src_path'] = file
494 item['_repo'] = repo
495 item['_index'] = idx
496 ret.append(item)
497
498 return ret
499
500
501 def load_logs():
502 ret = []
503
504 for repo, path, match in LOGS_SOURCES:
505 if match and path.exists() and path.is_dir():
506 for file in path.glob(METADATA_PATTERN):
507 ret.extend(_load_logs_file(file, repo))
508 elif not match and path.exists() and path.is_file():
509 ret.extend(_load_logs_file(path, repo))
510
511 return ret
512
513
514 def _load_authentication_file(file, repo):
515 debug(f'Loading {file}.')
516 data = load_yaml(file)
517
518 if not data:
519 return []
520
521 try:
522 AUTHENTICATION_VALIDATOR.validate(data)
523 except ValidationError as e:
524 warn(
525 f'Failed to validate {file} against the schema: {e.message} (path: {"/".join(str(p) for p in e.absolute_path)})',
526 file)
527 return []
528
529 if 'id' in data:
530 data['integration_type'] = 'authentication'
531 data['_src_path'] = file
532 data['_repo'] = repo
533 data['_index'] = 0
534
535 return [data]
536 else:
537 ret = []
538
539 for idx, item in enumerate(data):
540 item['integration_type'] = 'authentication'
541 item['_src_path'] = file
542 item['_repo'] = repo
543 item['_index'] = idx
544 ret.append(item)
545
546 return ret
547
548
549 def load_authentications():
550 ret = []
551
552 for repo, path, match in AUTHENTICATION_SOURCES:
553 if match and path.exists() and path.is_dir():
554 for file in path.glob(METADATA_PATTERN):
555 ret.extend(_load_authentication_file(file, repo))
556 elif not match and path.exists() and path.is_file():
557 ret.extend(_load_authentication_file(path, repo))
558
559 return ret
560
561
562 def _load_secretstore_file(file, repo):
563 debug(f'Loading {file}.')
564 data = load_yaml(file)
565
566 if not data:
567 return []
568
569 try:
570 SECRETSTORE_VALIDATOR.validate(data)
571 except ValidationError as e:
572 warn(
573 f'Failed to validate {file} against the schema: {e.message} (path: {"/".join(str(p) for p in e.absolute_path)})',
574 file)
575 return []
576
577 if 'id' in data:
578 data['integration_type'] = 'secretstore'
579 data['_src_path'] = file
580 data['_repo'] = repo
581 data['_index'] = 0
582
583 return [data]
584 else:
585 ret = []
586
587 for idx, item in enumerate(data):
588 item['integration_type'] = 'secretstore'
589 item['_src_path'] = file
590 item['_repo'] = repo
591 item['_index'] = idx
592 ret.append(item)
593
594 return ret
595
596
597 def load_secretstores():
598 ret = []
599
600 for repo, path, match in SECRETSTORE_SOURCES:
601 if match and path.exists() and path.is_dir():
602 for file in path.glob(METADATA_PATTERN):
603 ret.extend(_load_secretstore_file(file, repo))
604 elif not match and path.exists() and path.is_file():
605 ret.extend(_load_secretstore_file(path, repo))
606
607 return ret
608
609
610 def _load_service_discovery_file(file, repo):
611 debug(f'Loading {file}.')
612 data = load_yaml(file)
613
614 if not data:
615 return []
616
617 try:
618 SERVICE_DISCOVERY_VALIDATOR.validate(data)
619 except ValidationError as e:
620 warn(
621 f'Failed to validate {file} against the schema: {e.message} (path: {"/".join(str(p) for p in e.absolute_path)})',
622 file)
623 return []
624
625 if 'id' in data:
626 data['integration_type'] = 'service_discovery'
627 data['_src_path'] = file
628 data['_repo'] = repo
629 data['_index'] = 0
630
631 return [data]
632 else:
633 ret = []
634
635 for idx, item in enumerate(data):
636 item['integration_type'] = 'service_discovery'
637 item['_src_path'] = file
638 item['_repo'] = repo
639 item['_index'] = idx
640 ret.append(item)
641
642 return ret
643
644
645 def load_service_discoveries():
646 ret = []
647
648 for repo, path, match in SERVICE_DISCOVERY_SOURCES:
649 if match and path.exists() and path.is_dir():
650 for file in path.glob(METADATA_PATTERN):
651 ret.extend(_load_service_discovery_file(file, repo))
652 elif not match and path.exists() and path.is_file():
653 ret.extend(_load_service_discovery_file(path, repo))
654
655 return ret
656
657
658 def make_edit_link(item):
659 item_path = item['_src_path'].relative_to(REPO_PATH)
660
661 return f'https://github.com/{item["_repo"]}/blob/master/{item_path}'
662
663
664 def sort_integrations(integrations):
665 integrations.sort(key=lambda i: i['_index'])
666 integrations.sort(key=lambda i: i['_src_path'])
667 integrations.sort(key=lambda i: i['id'])
668
669
670 def dedupe_integrations(integrations, ids):
671 tmp_integrations = []
672
673 for i in integrations:
674 if ids.get(i['id'], False):
675 first_path, first_index = ids[i['id']]
676 warn(
677 f'Duplicate integration ID found at {i["_src_path"]} index {i["_index"]} (original definition at {first_path} index {first_index}), ignoring that integration.',
678 i['_src_path'])
679 else:
680 tmp_integrations.append(i)
681 ids[i['id']] = (i['_src_path'], i['_index'])
682
683 return tmp_integrations, ids
684
685
686 def render_collectors(categories, collectors, ids):
687 debug('Computing default categories.')
688
689 default_cats, valid_cats = get_category_sets(categories)
690
691 debug('Generating collector IDs.')
692
693 for item in collectors:
694 item['id'] = make_id(item['meta'])
695
696 debug('Sorting collectors.')
697
698 sort_integrations(collectors)
699
700 debug('Removing duplicate collectors.')
701
702 collectors, ids = dedupe_integrations(collectors, ids)
703 clean_collectors = []
704
705 # Build hierarchical indexes for cascading related_resources lookup:
706 # Level 1: plugin_name + module_name + monitored_instance_name (exact)
707 # Level 2: plugin_name + module_name (all instances of that module)
708 # Level 3: plugin_name (all modules of that plugin)
709 by_pm_instance = {} # (plugin, module, instance) -> [items]
710 by_pm = {} # (plugin, module) -> [items]
711 by_plugin = {} # plugin -> [items]
712
713 for i in collectors:
714 m = i['meta']
715 pn = m['plugin_name']
716 mn = m['module_name']
717 inst = m['monitored_instance']['name']
718
719 by_pm_instance.setdefault((pn, mn, inst), []).append(i)
720 by_pm.setdefault((pn, mn), []).append(i)
721 by_plugin.setdefault(pn, []).append(i)
722
723 def find_related(res):
724 """Cascading lookup: try most specific first, relax until a match is found."""
725 pn = res['plugin_name']
726 mn = res.get('module_name')
727 inst = res.get('monitored_instance_name')
728
729 # Level 1: all three specified
730 if mn and inst:
731 matches = by_pm_instance.get((pn, mn, inst))
732 if matches:
733 return matches
734 debug(f'No exact related_resources match for plugin={pn!r}, '
735 f'module={mn!r}, monitored_instance_name={inst!r}; '
736 f'falling back to all instances of that module.')
737
738 # Level 2: plugin + module
739 if mn:
740 # When module_name is explicitly specified, don't fall back to
741 # plugin-only — that would mask typos in the reference.
742 return by_pm.get((pn, mn), [])
743
744 # Level 3: plugin only (no module specified)
745 return by_plugin.get(pn, [])
746
747 for item in collectors:
748 debug(f'Processing {item["id"]}.')
749
750 item['edit_link'] = make_edit_link(item)
751
752 clean_item = deepcopy(item)
753
754 related = []
755 seen_ids = set()
756
757 for res in item['meta']['related_resources']['integrations']['list']:
758 matches = find_related(res)
759
760 if not matches:
761 warn(f'Could not find related integration for {res}, ignoring it.', item['_src_path'])
762 continue
763
764 for match in matches:
765 mid = match['id']
766
767 # skip self-references and duplicates
768 if mid == item['id'] or mid in seen_ids:
769 continue
770
771 seen_ids.add(mid)
772 related.append({
773 'plugin_name': match['meta']['plugin_name'],
774 'module_name': match['meta']['module_name'],
775 'id': mid,
776 'name': match['meta']['monitored_instance']['name'],
777 'info': match['meta']['info_provided_to_referring_integrations'],
778 })
779
780 item_cats = set(item['meta']['monitored_instance']['categories'])
781 bogus_cats = item_cats - valid_cats
782 actual_cats = item_cats & valid_cats
783
784 if bogus_cats:
785 warn(f'Ignoring invalid categories: {", ".join(bogus_cats)}', item["_src_path"])
786
787 if not item_cats:
788 item['meta']['monitored_instance']['categories'] = list(default_cats)
789 warn(f'{item["id"]} does not list any caregories, adding it to: {default_cats}', item["_src_path"])
790 else:
791 item['meta']['monitored_instance']['categories'] = [x for x in
792 item['meta']['monitored_instance']['categories'] if
793 x in list(actual_cats)]
794
795 for scope in item['metrics']['scopes']:
796 if scope['name'] == 'global':
797 scope['name'] = f'{item["meta"]["monitored_instance"]["name"]} instance'
798
799 for cfg_example in item['setup']['configuration']['examples']['list']:
800 if 'folding' not in cfg_example:
801 cfg_example['folding'] = {
802 'enabled': item['setup']['configuration']['examples']['folding']['enabled']
803 }
804
805 for key in COLLECTOR_RENDER_KEYS:
806 if key in item.keys():
807 template = get_jinja_env().get_template(get_section_template_name(item, key))
808 data = template.render(entry=item, related=related, clean=False)
809 clean_data = template.render(entry=item, related=related, clean=True)
810
811 if 'variables' in item['meta']['monitored_instance']:
812 template = get_jinja_env().from_string(data)
813 data = template.render(variables=item['meta']['monitored_instance']['variables'])
814 template = get_jinja_env().from_string(clean_data)
815 clean_data = template.render(variables=item['meta']['monitored_instance']['variables'])
816 else:
817 data = ''
818 clean_data = ''
819
820 item[key] = data
821 clean_item[key] = clean_data
822
823 for k in ['_src_path', '_repo', '_index']:
824 del item[k], clean_item[k]
825
826 clean_collectors.append(clean_item)
827
828 return collectors, clean_collectors, ids
829
830
831 def render_deploy(distros, categories, deploy, ids):
832 debug('Sorting deployments.')
833
834 sort_integrations(deploy)
835
836 debug('Checking deployment ids.')
837
838 deploy, ids = dedupe_integrations(deploy, ids)
839 clean_deploy = []
840
841 template = get_jinja_env().get_template('platform_info.md')
842
843 for item in deploy:
844 debug(f'Processing {item["id"]}.')
845 item['edit_link'] = make_edit_link(item)
846 clean_item = deepcopy(item)
847
848 if item['platform_info']['group']:
849 entries = [
850 {
851 'version': i['version'],
852 'support': i['support_type'],
853 'arches': i.get('packages', {'arches': []})['arches'],
854 'notes': i['notes'],
855 } for i in distros[item['platform_info']['group']] if i['distro'] == item['platform_info']['distro']
856 ]
857 else:
858 entries = []
859
860 data = template.render(entries=entries, clean=False)
861 clean_data = template.render(entries=entries, clean=True)
862
863 for method in clean_item['methods']:
864 for command in method['commands']:
865 command['command'] = CUSTOM_TAG_PATTERN.sub('', command['command'])
866 command['command'] = FIXUP_BLANK_PATTERN.sub('', command['command'])
867
868 item['platform_info'] = data
869 clean_item['platform_info'] = clean_data
870
871 if 'clean_additional_info' in item:
872 clean_item['additional_info'] = item['clean_additional_info']
873 del item['clean_additional_info'], clean_item['clean_additional_info']
874
875 for k in ['_src_path', '_repo', '_index']:
876 del item[k], clean_item[k]
877
878 clean_deploy.append(clean_item)
879
880 return deploy, clean_deploy, ids
881
882
883 def render_exporters(categories, exporters, ids):
884 debug('Sorting exporters.')
885
886 sort_integrations(exporters)
887
888 debug('Checking exporter ids.')
889
890 exporters, ids = dedupe_integrations(exporters, ids)
891
892 clean_exporters = []
893
894 for item in exporters:
895 item['edit_link'] = make_edit_link(item)
896
897 clean_item = deepcopy(item)
898
899 for key in EXPORTER_RENDER_KEYS:
900 if key in item.keys():
901 template = get_jinja_env().get_template(get_section_template_name(item, key))
902 data = template.render(entry=item, clean=False)
903 clean_data = template.render(entry=item, clean=True)
904
905 if 'variables' in item['meta']:
906 template = get_jinja_env().from_string(data)
907 data = template.render(variables=item['meta']['variables'], clean=False)
908 template = get_jinja_env().from_string(clean_data)
909 clean_data = template.render(variables=item['meta']['variables'], clean=True)
910 else:
911 data = ''
912 clean_data = ''
913
914 item[key] = data
915 clean_item[key] = clean_data
916
917 for k in ['_src_path', '_repo', '_index']:
918 del item[k], clean_item[k]
919
920 clean_exporters.append(clean_item)
921
922 return exporters, clean_exporters, ids
923
924
925 def render_agent_notifications(categories, notifications, ids):
926 debug('Sorting notifications.')
927
928 sort_integrations(notifications)
929
930 debug('Checking notification ids.')
931
932 notifications, ids = dedupe_integrations(notifications, ids)
933
934 clean_notifications = []
935
936 for item in notifications:
937 item['edit_link'] = make_edit_link(item)
938
939 clean_item = deepcopy(item)
940
941 for key in AGENT_NOTIFICATION_RENDER_KEYS:
942 if key in item.keys():
943 template = get_jinja_env().get_template(get_section_template_name(item, key))
944 data = template.render(entry=item, clean=False)
945
946 clean_data = template.render(entry=item, clean=True)
947
948 if 'variables' in item['meta']:
949 template = get_jinja_env().from_string(data)
950 data = template.render(variables=item['meta']['variables'], clean=False)
951 template = get_jinja_env().from_string(clean_data)
952 clean_data = template.render(variables=item['meta']['variables'], clean=True)
953 else:
954 data = ''
955 clean_data = ''
956
957 item[key] = data
958 clean_item[key] = clean_data
959
960 for k in ['_src_path', '_repo', '_index']:
961 del item[k], clean_item[k]
962
963 clean_notifications.append(clean_item)
964
965 return notifications, clean_notifications, ids
966
967
968 def render_cloud_notifications(categories, notifications, ids):
969 debug('Sorting notifications.')
970
971 sort_integrations(notifications)
972
973 debug('Checking notification ids.')
974
975 notifications, ids = dedupe_integrations(notifications, ids)
976
977 clean_notifications = []
978
979 for item in notifications:
980 item['edit_link'] = make_edit_link(item)
981
982 clean_item = deepcopy(item)
983
984 for key in CLOUD_NOTIFICATION_RENDER_KEYS:
985 if key in item.keys():
986 template = get_jinja_env().get_template(get_section_template_name(item, key))
987 data = template.render(entry=item, clean=False)
988 clean_data = template.render(entry=item, clean=True)
989
990 if 'variables' in item['meta']:
991 template = get_jinja_env().from_string(data)
992 data = template.render(variables=item['meta']['variables'], clean=False)
993 template = get_jinja_env().from_string(clean_data)
994 clean_data = template.render(variables=item['meta']['variables'], clean=True)
995 else:
996 data = ''
997 clean_data = ''
998
999 item[key] = data
1000 clean_item[key] = clean_data
1001
1002 for k in ['_src_path', '_repo', '_index']:
1003 del item[k], clean_item[k]
1004
1005 clean_notifications.append(clean_item)
1006
1007 return notifications, clean_notifications, ids
1008
1009
1010 def render_flows(categories, flows, ids):
1011 debug('Generating flow IDs.')
1012
1013 for item in flows:
1014 item['id'] = make_id(item['meta'])
1015
1016 debug('Sorting flows.')
1017
1018 sort_integrations(flows)
1019
1020 debug('Checking flow ids.')
1021
1022 flows, ids = dedupe_integrations(flows, ids)
1023
1024 clean_flows = []
1025
1026 for item in flows:
1027 item['edit_link'] = make_edit_link(item)
1028
1029 clean_item = deepcopy(item)
1030
1031 for key in FLOWS_RENDER_KEYS:
1032 if key in item.keys():
1033 template = get_jinja_env().get_template(get_section_template_name(item, key))
1034 data = template.render(entry=item, clean=False)
1035 clean_data = template.render(entry=item, clean=True)
1036
1037 if 'variables' in item['meta']:
1038 template = get_jinja_env().from_string(data)
1039 data = template.render(variables=item['meta']['variables'], clean=False)
1040 template = get_jinja_env().from_string(clean_data)
1041 clean_data = template.render(variables=item['meta']['variables'], clean=True)
1042 else:
1043 data = ''
1044 clean_data = ''
1045
1046 item[key] = data
1047 clean_item[key] = clean_data
1048
1049 for k in ['_src_path', '_repo', '_index']:
1050 del item[k], clean_item[k]
1051
1052 clean_flows.append(clean_item)
1053
1054 return flows, clean_flows, ids
1055
1056
1057 def render_logs(categories, logs, ids):
1058 debug('Sorting logs.')
1059
1060 sort_integrations(logs)
1061
1062 debug('Checking log ids.')
1063
1064 logs, ids = dedupe_integrations(logs, ids)
1065
1066 clean_logs = []
1067
1068 for item in logs:
1069 item['edit_link'] = make_edit_link(item)
1070
1071 clean_item = deepcopy(item)
1072
1073 for key in LOGS_RENDER_KEYS:
1074 if key in item.keys():
1075 template = get_jinja_env().get_template(get_section_template_name(item, key))
1076 data = template.render(entry=item, clean=False)
1077 clean_data = template.render(entry=item, clean=True)
1078
1079 if 'variables' in item['meta']:
1080 template = get_jinja_env().from_string(data)
1081 data = template.render(variables=item['meta']['variables'], clean=False)
1082 template = get_jinja_env().from_string(clean_data)
1083 clean_data = template.render(variables=item['meta']['variables'], clean=True)
1084 else:
1085 data = ''
1086 clean_data = ''
1087
1088 item[key] = data
1089 clean_item[key] = clean_data
1090
1091 for k in ['_src_path', '_repo', '_index']:
1092 del item[k], clean_item[k]
1093
1094 clean_logs.append(clean_item)
1095
1096 return logs, clean_logs, ids
1097
1098
1099 def render_authentications(categories, authentications, ids):
1100 debug('Sorting authentications.')
1101
1102 sort_integrations(authentications)
1103
1104 debug('Checking authentication ids.')
1105
1106 authentications, ids = dedupe_integrations(authentications, ids)
1107
1108 clean_authentications = []
1109
1110 for item in authentications:
1111 item['edit_link'] = make_edit_link(item)
1112
1113 clean_item = deepcopy(item)
1114
1115 for key in AUTHENTICATION_RENDER_KEYS:
1116
1117 if key in item.keys():
1118 template = get_jinja_env().get_template(get_section_template_name(item, key))
1119 data = template.render(entry=item, clean=False)
1120 clean_data = template.render(entry=item, clean=True)
1121
1122 if 'variables' in item['meta']:
1123 template = get_jinja_env().from_string(data)
1124 data = template.render(variables=item['meta']['variables'], clean=False)
1125 template = get_jinja_env().from_string(clean_data)
1126 clean_data = template.render(variables=item['meta']['variables'], clean=True)
1127 else:
1128 data = ''
1129 clean_data = ''
1130
1131 item[key] = data
1132 clean_item[key] = clean_data
1133
1134 for k in ['_src_path', '_repo', '_index']:
1135 del item[k], clean_item[k]
1136
1137 clean_authentications.append(clean_item)
1138
1139 return authentications, clean_authentications, ids
1140
1141
1142 def render_secretstores(categories, secretstores, ids):
1143 debug('Sorting secretstores.')
1144
1145 sort_integrations(secretstores)
1146
1147 debug('Checking secretstore ids.')
1148
1149 secretstores, ids = dedupe_integrations(secretstores, ids)
1150
1151 clean_secretstores = []
1152
1153 for item in secretstores:
1154 item['edit_link'] = make_edit_link(item)
1155
1156 clean_item = deepcopy(item)
1157 collector_configs = item.get('collector_configs', {})
1158 collector_configs_summary = {}
1159 if isinstance(collector_configs, dict):
1160 summary = collector_configs.get('summary', {})
1161 if isinstance(summary, dict):
1162 collector_configs_summary = deepcopy(summary)
1163
1164 item['collector_configs_summary'] = deepcopy(collector_configs_summary)
1165 clean_item['collector_configs_summary'] = deepcopy(collector_configs_summary)
1166
1167 for key in SECRETSTORE_RENDER_KEYS:
1168 if key in item.keys():
1169 template = get_jinja_env().get_template(get_section_template_name(item, key))
1170 data = template.render(entry=item, clean=False)
1171 clean_data = template.render(entry=item, clean=True)
1172
1173 if 'variables' in item['meta']:
1174 template = get_jinja_env().from_string(data)
1175 data = template.render(variables=item['meta']['variables'], clean=False)
1176 template = get_jinja_env().from_string(clean_data)
1177 clean_data = template.render(variables=item['meta']['variables'], clean=True)
1178 else:
1179 data = ''
1180 clean_data = ''
1181
1182 item[key] = data
1183 clean_item[key] = clean_data
1184
1185 for k in ['_src_path', '_repo', '_index']:
1186 del item[k], clean_item[k]
1187
1188 clean_secretstores.append(clean_item)
1189
1190 return secretstores, clean_secretstores, ids
1191
1192
1193 def render_service_discoveries(categories, service_discoveries, ids):
1194 debug('Sorting service discoveries.')
1195
1196 sort_integrations(service_discoveries)
1197
1198 debug('Checking service discovery ids.')
1199
1200 service_discoveries, ids = dedupe_integrations(service_discoveries, ids)
1201
1202 clean_service_discoveries = []
1203
1204 for item in service_discoveries:
1205 item['edit_link'] = make_edit_link(item)
1206
1207 clean_item = deepcopy(item)
1208
1209 for key in SERVICE_DISCOVERY_RENDER_KEYS:
1210 if key in item.keys():
1211 template = get_jinja_env().get_template(get_section_template_name(item, key))
1212 data = template.render(entry=item, clean=False)
1213 clean_data = template.render(entry=item, clean=True)
1214
1215 if 'variables' in item['meta']:
1216 template = get_jinja_env().from_string(data)
1217 data = template.render(variables=item['meta']['variables'], clean=False)
1218 template = get_jinja_env().from_string(clean_data)
1219 clean_data = template.render(variables=item['meta']['variables'], clean=True)
1220 else:
1221 data = ''
1222 clean_data = ''
1223
1224 item[key] = data
1225 clean_item[key] = clean_data
1226
1227 for k in ['_src_path', '_repo', '_index']:
1228 del item[k], clean_item[k]
1229
1230 clean_service_discoveries.append(clean_item)
1231
1232 return service_discoveries, clean_service_discoveries, ids
1233
1234
1235 def convert_local_links(text, prefix):
1236 return text.replace("](/", f"]({prefix}/")
1237
1238
1239 def render_integrations(categories, integrations):
1240 template = get_jinja_env().get_template('integrations.js')
1241 data = template.render(
1242 categories=json.dumps(categories, indent=4),
1243 integrations=json.dumps(integrations, indent=4),
1244 )
1245 data = convert_local_links(data, "https://github.com/netdata/netdata/blob/master")
1246 OUTPUT_PATH.write_text(data)
1247
1248
1249 def render_json(categories, integrations):
1250 JSON_PATH.write_text(json.dumps({
1251 'categories': categories,
1252 'integrations': integrations,
1253 }, indent=4))
1254
1255
1256 def main():
1257 categories = load_categories()
1258 distros = load_yaml(DISTROS_FILE)
1259 collectors = load_collectors()
1260 deploy = load_deploy()
1261 exporters = load_exporters()
1262 agent_notifications = load_agent_notifications()
1263 cloud_notifications = load_cloud_notifications()
1264 logs = load_logs()
1265 flows = load_flows()
1266 authentications = load_authentications()
1267 secretstores = load_secretstores()
1268 service_discoveries = load_service_discoveries()
1269
1270 collectors, clean_collectors, ids = render_collectors(categories, collectors, dict())
1271 deploy, clean_deploy, ids = render_deploy(distros, categories, deploy, ids)
1272 exporters, clean_exporters, ids = render_exporters(categories, exporters, ids)
1273 agent_notifications, clean_agent_notifications, ids = render_agent_notifications(categories, agent_notifications,
1274 ids)
1275 cloud_notifications, clean_cloud_notifications, ids = render_cloud_notifications(categories, cloud_notifications,
1276 ids)
1277 logs, clean_logs, ids = render_logs(categories, logs, ids)
1278 flows, clean_flows, ids = render_flows(categories, flows, ids)
1279 authentications, clean_authentications, ids = render_authentications(categories, authentications, ids)
1280 secretstores, clean_secretstores, ids = render_secretstores(categories, secretstores, ids)
1281 service_discoveries, clean_service_discoveries, ids = render_service_discoveries(categories, service_discoveries,
1282 ids)
1283
1284 integrations = collectors + deploy + exporters + agent_notifications + cloud_notifications + logs + flows + authentications + secretstores + service_discoveries
1285 render_integrations(categories, integrations)
1286
1287 clean_integrations = clean_collectors + clean_deploy + clean_exporters + clean_agent_notifications + clean_cloud_notifications + clean_logs + clean_flows + clean_authentications + clean_secretstores + clean_service_discoveries
1288 render_json(categories, clean_integrations)
1289
1290 return fail_on_warnings()
1291
1292
1293 if __name__ == '__main__':
1294 sys.exit(main())