blob: fe34f60d6c94d3cb908023cc875dff67ebed06eb [file] [log] [blame]
savex4448e132018-04-25 15:51:14 +02001import json
2import os
3import sys
4
5from copy import deepcopy
6
Alex Savatieievd48994d2018-12-13 12:13:00 +01007import reporter
Alex Savatieiev5118de02019-02-20 15:50:42 -06008
9from cfg_checker.common import utils, const
10from cfg_checker.common import config, logger, logger_cli, pkg_dir
11from cfg_checker.common import salt_utils
savex4448e132018-04-25 15:51:14 +020012
13node_tmpl = {
14 'role': '',
15 'node_group': '',
16 'status': const.NODE_DOWN,
17 'pillars': {},
18 'grains': {}
19}
20
21
22class CloudPackageChecker(object):
savex4448e132018-04-25 15:51:14 +020023 def __init__(self):
Alex Savatieiev799bee32019-02-20 17:19:26 -060024 logger_cli.info("### Collecting nodes for package check")
savex4448e132018-04-25 15:51:14 +020025 # simple salt rest client
26 self.salt = salt_utils.SaltRemote()
27
28 # Keys for all nodes
29 # this is not working in scope of 2016.8.3, will overide with list
30 # cls.node_keys = cls.salt.list_keys()
31
Alex Savatieiev799bee32019-02-20 17:19:26 -060032 logger_cli.info("### Collecting node names existing in the cloud")
savex4448e132018-04-25 15:51:14 +020033 self.node_keys = {
Alex Savatieieve47f7f42019-02-20 16:41:23 -060034 'minions': config.all_nodes
savex4448e132018-04-25 15:51:14 +020035 }
36
37 # all that answer ping
38 _active = self.salt.get_active_nodes()
Alex Savatieiev799bee32019-02-20 17:19:26 -060039 logger_cli.debug("-> Nodes responded: {}".format(_active))
savex4448e132018-04-25 15:51:14 +020040 # just inventory for faster interaction
41 # iterate through all accepted nodes and create a dict for it
42 self.nodes = {}
43 for _name in self.node_keys['minions']:
44 _nc = utils.get_node_code(_name)
45 _rmap = const.all_roles_map
46 _role = _rmap[_nc] if _nc in _rmap else 'unknown'
47 _status = const.NODE_UP if _name in _active else const.NODE_DOWN
48
49 self.nodes[_name] = deepcopy(node_tmpl)
50 self.nodes[_name]['node_group'] = _nc
51 self.nodes[_name]['role'] = _role
52 self.nodes[_name]['status'] = _status
53
Alex Savatieiev799bee32019-02-20 17:19:26 -060054 logger_cli.info("-> {} nodes collected".format(len(self.nodes)))
savex4448e132018-04-25 15:51:14 +020055
56 def collect_installed_packages(self):
57 """
58 Collect installed packages on each node
59 sets 'installed' dict property in the class
60
61 :return: none
62 """
Alex Savatieiev799bee32019-02-20 17:19:26 -060063 logger_cli.info("### Collecting installed packages")
savex4448e132018-04-25 15:51:14 +020064 # form an all nodes compound string to use in salt
65 _active_nodes_string = self.salt.compound_string_from_list(
66 filter(
67 lambda nd: self.nodes[nd]['status'] == const.NODE_UP,
68 self.nodes
69 )
70 )
71 # Prepare script
72 _script_filename = "pkg_versions.py"
Alex Savatieieve47f7f42019-02-20 16:41:23 -060073 _p = os.path.join(pkg_dir, 'scripts', _script_filename)
savex4448e132018-04-25 15:51:14 +020074 with open(_p, 'rt') as fd:
75 _script = fd.read().splitlines()
savex4448e132018-04-25 15:51:14 +020076 _storage_path = os.path.join(
Alex Savatieieve47f7f42019-02-20 16:41:23 -060077 config.salt_file_root, config.salt_scripts_folder
savex4448e132018-04-25 15:51:14 +020078 )
Alex Savatieiev799bee32019-02-20 17:19:26 -060079 logger_cli.debug(
80 "# Uploading script {} to master's file cache folder: '{}'".format(
savexce010ba2018-04-27 09:49:23 +020081 _script_filename,
82 _storage_path
savex4448e132018-04-25 15:51:14 +020083 )
84 )
savexce010ba2018-04-27 09:49:23 +020085 _result = self.salt.mkdir("cfg01*", _storage_path)
savex4448e132018-04-25 15:51:14 +020086 # Form cache, source and target path
87 _cache_path = os.path.join(_storage_path, _script_filename)
88 _source_path = os.path.join(
89 'salt://',
Alex Savatieieve47f7f42019-02-20 16:41:23 -060090 config.salt_scripts_folder,
savex4448e132018-04-25 15:51:14 +020091 _script_filename
92 )
93 _target_path = os.path.join(
94 '/root',
Alex Savatieieve47f7f42019-02-20 16:41:23 -060095 config.salt_scripts_folder,
savex4448e132018-04-25 15:51:14 +020096 _script_filename
97 )
98
Alex Savatieiev799bee32019-02-20 17:19:26 -060099 logger_cli.debug("# Creating file in cache '{}'".format(_cache_path))
savex4448e132018-04-25 15:51:14 +0200100 _result = self.salt.f_touch_master(_cache_path)
101 _result = self.salt.f_append_master(_cache_path, _script)
102 # command salt to copy file to minions
Alex Savatieiev799bee32019-02-20 17:19:26 -0600103 logger_cli.debug("# Creating script target folder '{}'".format(_cache_path))
savex4448e132018-04-25 15:51:14 +0200104 _result = self.salt.mkdir(
105 _active_nodes_string,
106 os.path.join(
107 '/root',
Alex Savatieieve47f7f42019-02-20 16:41:23 -0600108 config.salt_scripts_folder
savex4448e132018-04-25 15:51:14 +0200109 ),
110 tgt_type="compound"
111 )
Alex Savatieiev799bee32019-02-20 17:19:26 -0600112 logger_cli.info("-> Running script to all active nodes")
savex4448e132018-04-25 15:51:14 +0200113 _result = self.salt.get_file(
114 _active_nodes_string,
115 _source_path,
116 _target_path,
117 tgt_type="compound"
118 )
119 # execute pkg collecting script
120 logger.debug("Running script to all nodes")
121 # handle results for each node
122 _result = self.salt.cmd(
123 _active_nodes_string,
124 'cmd.run',
125 param='python {}'.format(_target_path),
126 expr_form="compound"
127 )
128 for key in self.nodes.keys():
129 # due to much data to be passed from salt, it is happening in order
130 if key in _result:
131 _text = _result[key]
132 _dict = json.loads(_text[_text.find('{'):])
133 self.nodes[key]['packages'] = _dict
134 else:
135 self.nodes[key]['packages'] = {}
Alex Savatieiev799bee32019-02-20 17:19:26 -0600136 logger_cli.debug("# {} has {} packages installed".format(
savex4448e132018-04-25 15:51:14 +0200137 key,
138 len(self.nodes[key]['packages'].keys())
139 ))
Alex Savatieiev799bee32019-02-20 17:19:26 -0600140 logger_cli.info("-> Done")
savex4448e132018-04-25 15:51:14 +0200141
142 def collect_packages(self):
143 """
144 Check package versions in repos vs installed
145
146 :return: no return values, all date put to dict in place
147 """
148 _all_packages = {}
149 for node_name, node_value in self.nodes.iteritems():
150 for package_name in node_value['packages']:
151 if package_name not in _all_packages:
152 _all_packages[package_name] = {}
153 _all_packages[package_name][node_name] = node_value
154
155 # TODO: process data for per-package basis
156
157 self.all_packages = _all_packages
158
159 def create_html_report(self, filename):
160 """
161 Create static html showing packages diff per node
162
163 :return: buff with html
164 """
Alex Savatieiev799bee32019-02-20 17:19:26 -0600165 logger_cli.info("### Generating report to '{}'".format(filename))
Alex Savatieievd48994d2018-12-13 12:13:00 +0100166 _report = reporter.ReportToFile(
167 reporter.HTMLPackageCandidates(),
savex4448e132018-04-25 15:51:14 +0200168 filename
169 )
Alex Savatieiev36b938d2019-01-21 11:01:18 +0100170 _report({
171 "nodes": self.nodes,
172 "diffs": {}
173 })
Alex Savatieiev799bee32019-02-20 17:19:26 -0600174 logger_cli.info("-> Done")
savex4448e132018-04-25 15:51:14 +0200175
176
Alex Savatieiev5118de02019-02-20 15:50:42 -0600177if __name__ == '__main__':
178 # init connection to salt and collect minion data
179 cl = CloudPackageChecker()
savex4448e132018-04-25 15:51:14 +0200180
Alex Savatieiev5118de02019-02-20 15:50:42 -0600181 # collect data on installed packages
182 cl.collect_installed_packages()
savex4448e132018-04-25 15:51:14 +0200183
Alex Savatieiev5118de02019-02-20 15:50:42 -0600184 # diff installed and candidates
185 # cl.collect_packages()
savex4448e132018-04-25 15:51:14 +0200186
Alex Savatieiev5118de02019-02-20 15:50:42 -0600187 # report it
188 cl.create_html_report("./pkg_versions.html")
savex4448e132018-04-25 15:51:14 +0200189
Alex Savatieiev5118de02019-02-20 15:50:42 -0600190 sys.exit(0)