blob: ae9454177e4528f512c6679096e598f5e175822e [file] [log] [blame]
Ales Komarek166cc672016-07-27 14:17:22 +02001# -*- coding: utf-8 -*-
2'''
Ales Komareka4a9f572016-12-03 20:15:50 +01003Module for handling reclass metadata models.
Ales Komarek166cc672016-07-27 14:17:22 +02004
5'''
6
7from __future__ import absolute_import
8
Adam Tengler8a1cf402017-05-16 10:59:35 +00009import io
10import json
Ales Komarek166cc672016-07-27 14:17:22 +020011import logging
12import os
13import sys
Ales Komareka961df42016-11-21 21:50:24 +010014import six
Ales Komarek166cc672016-07-27 14:17:22 +020015import yaml
16
Ales Komareka4a9f572016-12-03 20:15:50 +010017from reclass import get_storage, output
18from reclass.core import Core
19from reclass.config import find_and_read_configfile
Ales Komarek166cc672016-07-27 14:17:22 +020020
Ales Komareka4a9f572016-12-03 20:15:50 +010021LOG = logging.getLogger(__name__)
Ales Komarek166cc672016-07-27 14:17:22 +020022
Ales Komareka961df42016-11-21 21:50:24 +010023
Ales Komarek166cc672016-07-27 14:17:22 +020024def __virtual__():
25 '''
26 Only load this module if reclass
27 is installed on this minion.
28 '''
29 return 'reclass'
30
31
Ales Komareka4a9f572016-12-03 20:15:50 +010032def _get_nodes_dir():
33 defaults = find_and_read_configfile()
34 return os.path.join(defaults.get('inventory_base_uri'), 'nodes')
35
36
37def _get_classes_dir():
38 defaults = find_and_read_configfile()
39 return os.path.join(defaults.get('inventory_base_uri'), 'classes')
Ales Komarek166cc672016-07-27 14:17:22 +020040
41
Adam Tengler8a1cf402017-05-16 10:59:35 +000042def _get_cluster_dir():
43 classes_dir = _get_classes_dir()
44 return os.path.join(classes_dir, 'cluster')
45
46
Adam Tengler805666d2017-05-15 16:01:13 +000047def _get_node_meta(name, cluster="default", environment="prd", classes=None, parameters=None):
48 host_name = name.split('.')[0]
49 domain_name = '.'.join(name.split('.')[1:])
50
51 if classes == None:
52 meta_classes = []
53 else:
54 if isinstance(classes, six.string_types):
55 meta_classes = json.loads(classes)
56 else:
57 meta_classes = classes
58
59 if parameters == None:
60 meta_parameters = {}
61 else:
62 if isinstance(parameters, six.string_types):
63 meta_parameters = json.loads(parameters)
64 else:
65 # generate dict from OrderedDict
66 meta_parameters = {k: v for (k, v) in parameters.items()}
67
68 node_meta = {
69 'classes': meta_classes,
70 'parameters': {
71 '_param': meta_parameters,
72 'linux': {
73 'system': {
74 'name': host_name,
75 'domain': domain_name,
76 'cluster': cluster,
77 'environment': environment,
78 }
79 }
80 }
81 }
82
83 return node_meta
84
85
Ales Komarek166cc672016-07-27 14:17:22 +020086def node_create(name, path=None, cluster="default", environment="prd", classes=None, parameters=None, **kwargs):
87 '''
88 Create a reclass node
89
90 :param name: new node FQDN
91 :param path: custom path in nodes for new node
92 :param classes: classes given to the new node
93 :param parameters: parameters given to the new node
94 :param environment: node's environment
95 :param cluster: node's cluster
96
97 CLI Examples:
98
99 .. code-block:: bash
100
101 salt '*' reclass.node_create server.domain.com classes=[system.neco1, system.neco2]
102 salt '*' reclass.node_create namespace/test enabled=False
103
104 '''
105 ret = {}
106
107 node = node_get(name=name)
108
109 if node and not "Error" in node:
110 LOG.debug("node {0} exists".format(name))
111 ret[name] = node
112 return ret
113
114 host_name = name.split('.')[0]
115 domain_name = '.'.join(name.split('.')[1:])
116
Adam Tengler805666d2017-05-15 16:01:13 +0000117 node_meta = _get_node_meta(name, cluster, environment, classes, parameters)
Ales Komarek166cc672016-07-27 14:17:22 +0200118 LOG.debug(node_meta)
119
120 if path == None:
Ales Komareka4a9f572016-12-03 20:15:50 +0100121 file_path = os.path.join(_get_nodes_dir(), name + '.yml')
Ales Komarek166cc672016-07-27 14:17:22 +0200122 else:
Ales Komareka4a9f572016-12-03 20:15:50 +0100123 file_path = os.path.join(_get_nodes_dir(), path, name + '.yml')
Ales Komarek166cc672016-07-27 14:17:22 +0200124
125 with open(file_path, 'w') as node_file:
Ales Komareka961df42016-11-21 21:50:24 +0100126 node_file.write(yaml.safe_dump(node_meta, default_flow_style=False))
Ales Komarek71f94b02016-07-27 14:48:57 +0200127
Ales Komarek166cc672016-07-27 14:17:22 +0200128 return node_get(name)
129
Ales Komareka4a9f572016-12-03 20:15:50 +0100130
Ales Komarek166cc672016-07-27 14:17:22 +0200131def node_delete(name, **kwargs):
132 '''
133 Delete a reclass node
134
135 :params node: Node name
136
137 CLI Examples:
138
139 .. code-block:: bash
140
141 salt '*' reclass.node_delete demo01.domain.com
142 salt '*' reclass.node_delete name=demo01.domain.com
143 '''
144
145 node = node_get(name=name)
146
147 if 'Error' in node:
148 return {'Error': 'Unable to retreive node'}
149
150 if node[name]['path'] == '':
Ales Komareka4a9f572016-12-03 20:15:50 +0100151 file_path = os.path.join(_get_nodes_dir(), name + '.yml')
Ales Komarek166cc672016-07-27 14:17:22 +0200152 else:
Ales Komareka4a9f572016-12-03 20:15:50 +0100153 file_path = os.path.join(_get_nodes_dir(), node[name]['path'], name + '.yml')
Ales Komarek166cc672016-07-27 14:17:22 +0200154
155 os.remove(file_path)
156
157 ret = 'Node {0} deleted'.format(name)
158
159 return ret
160
161
162def node_get(name, path=None, **kwargs):
163 '''
164 Return a specific node
165
166 CLI Examples:
167
168 .. code-block:: bash
169
170 salt '*' reclass.node_get host01.domain.com
171 salt '*' reclass.node_get name=host02.domain.com
172 '''
173 ret = {}
174 nodes = node_list(**kwargs)
175
176 if not name in nodes:
177 return {'Error': 'Error in retrieving node'}
178 ret[name] = nodes[name]
179 return ret
180
181
182def node_list(**connection_args):
183 '''
184 Return a list of available nodes
185
186 CLI Example:
187
188 .. code-block:: bash
189
190 salt '*' reclass.node_list
191 '''
192 ret = {}
193
Ales Komareka4a9f572016-12-03 20:15:50 +0100194 for root, sub_folders, files in os.walk(_get_nodes_dir()):
Adam Tengler805666d2017-05-15 16:01:13 +0000195 for fl in files:
196 file_path = os.path.join(root, fl)
197 with open(file_path, 'r') as file_handle:
198 file_read = yaml.load(file_handle.read())
199 file_data = file_read or {}
200 classes = file_data.get('classes', [])
201 parameters = file_data.get('parameters', {}).get('_param', [])
202 name = fl.replace('.yml', '')
Ales Komarek166cc672016-07-27 14:17:22 +0200203 host_name = name.split('.')[0]
204 domain_name = '.'.join(name.split('.')[1:])
Ales Komareka4a9f572016-12-03 20:15:50 +0100205 path = root.replace(_get_nodes_dir()+'/', '')
Ales Komarek166cc672016-07-27 14:17:22 +0200206 ret[name] = {
Ales Komareka4a9f572016-12-03 20:15:50 +0100207 'name': host_name,
208 'domain': domain_name,
209 'cluster': 'default',
210 'environment': 'prd',
211 'path': path,
212 'classes': classes,
213 'parameters': parameters
Ales Komarek166cc672016-07-27 14:17:22 +0200214 }
215
216 return ret
217
Ales Komareka4a9f572016-12-03 20:15:50 +0100218
Ales Komarek166cc672016-07-27 14:17:22 +0200219def node_update(name, classes=None, parameters=None, **connection_args):
220 '''
221 Update a node metadata information, classes and parameters.
222
223 CLI Examples:
224
225 .. code-block:: bash
226
227 salt '*' reclass.node_update name=nodename classes="[clas1, class2]"
228 '''
229 node = node_get(name=name)
230 if not node.has_key('Error'):
Ales Komarek71f94b02016-07-27 14:48:57 +0200231 node = node[name.split("/")[1]]
232 else:
233 return {'Error': 'Error in retrieving node'}
Ales Komareka4a9f572016-12-03 20:15:50 +0100234
235
236def inventory(**connection_args):
237 '''
238 Get all nodes in inventory and their associated services/roles classification.
239
240 CLI Examples:
241
242 .. code-block:: bash
243
244 salt '*' reclass.inventory
245 '''
246 defaults = find_and_read_configfile()
247 storage = get_storage(defaults['storage_type'], _get_nodes_dir(), _get_classes_dir())
248 reclass = Core(storage, None)
249 nodes = reclass.inventory()["nodes"]
250 output = {}
251
252 for node in nodes:
253 service_classification = []
254 role_classification = []
255 for service in nodes[node]['parameters']:
256 if service not in ['_param', 'private_keys', 'public_keys', 'known_hosts']:
257 service_classification.append(service)
258 for role in nodes[node]['parameters'][service]:
259 if role not in ['_support', '_orchestrate', 'common']:
260 role_classification.append('%s.%s' % (service, role))
261 output[node] = {
262 'roles': role_classification,
263 'services': service_classification,
264 }
265 return output
Adam Tengler8a1cf402017-05-16 10:59:35 +0000266
267
268def cluster_meta_list(file_name="overrides.yml", cluster="", **kwargs):
269 path = os.path.join(_get_cluster_dir(), cluster, file_name)
270 try:
271 with io.open(path, 'r') as file_handle:
272 meta_yaml = yaml.safe_load(file_handle.read())
273 meta = meta_yaml or {}
274 except Exception as e:
275 msg = "Unable to load cluster metadata YAML %s: %s" % (path, repr(e))
276 LOG.debug(msg)
277 meta = {'Error': msg}
278 return meta
279
280
281def cluster_meta_delete(name, file_name="overrides.yml", cluster="", **kwargs):
282 ret = {}
283 path = os.path.join(_get_cluster_dir(), cluster, file_name)
284 meta = __salt__['reclass.cluster_meta_list'](path, **kwargs)
285 if 'Error' not in meta:
286 metadata = meta.get('parameters', {}).get('_param', {})
287 if name not in metadata:
288 return ret
289 del metadata[name]
290 try:
291 with io.open(path, 'w') as file_handle:
292 file_handle.write(unicode(yaml.dump(meta, default_flow_style=False)))
293 except Exception as e:
294 msg = "Unable to save cluster metadata YAML: %s" % repr(e)
295 LOG.error(msg)
296 return {'Error': msg}
297 ret = 'Cluster metadata entry {0} deleted'.format(name)
298 return ret
299
300
301def cluster_meta_set(name, value, file_name="overrides.yml", cluster="", **kwargs):
302 path = os.path.join(_get_cluster_dir(), cluster, file_name)
303 meta = __salt__['reclass.cluster_meta_list'](path, **kwargs)
304 if 'Error' not in meta:
305 if not meta:
306 meta = {'parameters': {'_param': {}}}
307 metadata = meta.get('parameters', {}).get('_param', {})
308 if name in metadata and metadata[name] == value:
309 return {name: 'Cluster metadata entry %s already exists and is in correct state' % name}
310 metadata.update({name: value})
311 try:
312 with io.open(path, 'w') as file_handle:
313 file_handle.write(unicode(yaml.dump(meta, default_flow_style=False)))
314 except Exception as e:
315 msg = "Unable to save cluster metadata YAML %s: %s" % (path, repr(e))
316 LOG.error(msg)
317 return {'Error': msg}
318 return cluster_meta_get(name, path, **kwargs)
319 return meta
320
321
322def cluster_meta_get(name, file_name="overrides.yml", cluster="", **kwargs):
323 ret = {}
324 path = os.path.join(_get_cluster_dir(), cluster, file_name)
325 meta = __salt__['reclass.cluster_meta_list'](path, **kwargs)
326 metadata = meta.get('parameters', {}).get('_param', {})
327 if 'Error' in meta:
328 ret['Error'] = meta['Error']
329 elif name in metadata:
330 ret[name] = metadata.get(name)
331
332 return ret
333