blob: 10fcdc33fa968b830f8c9344ecf2f68f7535aa34 [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
9import logging
10import os
11import sys
Ales Komareka961df42016-11-21 21:50:24 +010012import six
Ales Komarek166cc672016-07-27 14:17:22 +020013import yaml
Ales Komareka961df42016-11-21 21:50:24 +010014import json
Ales Komarek166cc672016-07-27 14:17:22 +020015
Ales Komareka4a9f572016-12-03 20:15:50 +010016from reclass import get_storage, output
17from reclass.core import Core
18from reclass.config import find_and_read_configfile
Ales Komarek166cc672016-07-27 14:17:22 +020019
Ales Komareka4a9f572016-12-03 20:15:50 +010020LOG = logging.getLogger(__name__)
Ales Komarek166cc672016-07-27 14:17:22 +020021
Ales Komareka961df42016-11-21 21:50:24 +010022
Ales Komarek166cc672016-07-27 14:17:22 +020023def __virtual__():
24 '''
25 Only load this module if reclass
26 is installed on this minion.
27 '''
28 return 'reclass'
29
30
Ales Komareka4a9f572016-12-03 20:15:50 +010031def _get_nodes_dir():
32 defaults = find_and_read_configfile()
33 return os.path.join(defaults.get('inventory_base_uri'), 'nodes')
34
35
36def _get_classes_dir():
37 defaults = find_and_read_configfile()
38 return os.path.join(defaults.get('inventory_base_uri'), 'classes')
Ales Komarek166cc672016-07-27 14:17:22 +020039
40
Adam Tengler805666d2017-05-15 16:01:13 +000041def _get_node_meta(name, cluster="default", environment="prd", classes=None, parameters=None):
42 host_name = name.split('.')[0]
43 domain_name = '.'.join(name.split('.')[1:])
44
45 if classes == None:
46 meta_classes = []
47 else:
48 if isinstance(classes, six.string_types):
49 meta_classes = json.loads(classes)
50 else:
51 meta_classes = classes
52
53 if parameters == None:
54 meta_parameters = {}
55 else:
56 if isinstance(parameters, six.string_types):
57 meta_parameters = json.loads(parameters)
58 else:
59 # generate dict from OrderedDict
60 meta_parameters = {k: v for (k, v) in parameters.items()}
61
62 node_meta = {
63 'classes': meta_classes,
64 'parameters': {
65 '_param': meta_parameters,
66 'linux': {
67 'system': {
68 'name': host_name,
69 'domain': domain_name,
70 'cluster': cluster,
71 'environment': environment,
72 }
73 }
74 }
75 }
76
77 return node_meta
78
79
Ales Komarek166cc672016-07-27 14:17:22 +020080def node_create(name, path=None, cluster="default", environment="prd", classes=None, parameters=None, **kwargs):
81 '''
82 Create a reclass node
83
84 :param name: new node FQDN
85 :param path: custom path in nodes for new node
86 :param classes: classes given to the new node
87 :param parameters: parameters given to the new node
88 :param environment: node's environment
89 :param cluster: node's cluster
90
91 CLI Examples:
92
93 .. code-block:: bash
94
95 salt '*' reclass.node_create server.domain.com classes=[system.neco1, system.neco2]
96 salt '*' reclass.node_create namespace/test enabled=False
97
98 '''
99 ret = {}
100
101 node = node_get(name=name)
102
103 if node and not "Error" in node:
104 LOG.debug("node {0} exists".format(name))
105 ret[name] = node
106 return ret
107
108 host_name = name.split('.')[0]
109 domain_name = '.'.join(name.split('.')[1:])
110
Adam Tengler805666d2017-05-15 16:01:13 +0000111 node_meta = _get_node_meta(name, cluster, environment, classes, parameters)
Ales Komarek166cc672016-07-27 14:17:22 +0200112 LOG.debug(node_meta)
113
114 if path == None:
Ales Komareka4a9f572016-12-03 20:15:50 +0100115 file_path = os.path.join(_get_nodes_dir(), name + '.yml')
Ales Komarek166cc672016-07-27 14:17:22 +0200116 else:
Ales Komareka4a9f572016-12-03 20:15:50 +0100117 file_path = os.path.join(_get_nodes_dir(), path, name + '.yml')
Ales Komarek166cc672016-07-27 14:17:22 +0200118
119 with open(file_path, 'w') as node_file:
Ales Komareka961df42016-11-21 21:50:24 +0100120 node_file.write(yaml.safe_dump(node_meta, default_flow_style=False))
Ales Komarek71f94b02016-07-27 14:48:57 +0200121
Ales Komarek166cc672016-07-27 14:17:22 +0200122 return node_get(name)
123
Ales Komareka4a9f572016-12-03 20:15:50 +0100124
Ales Komarek166cc672016-07-27 14:17:22 +0200125def node_delete(name, **kwargs):
126 '''
127 Delete a reclass node
128
129 :params node: Node name
130
131 CLI Examples:
132
133 .. code-block:: bash
134
135 salt '*' reclass.node_delete demo01.domain.com
136 salt '*' reclass.node_delete name=demo01.domain.com
137 '''
138
139 node = node_get(name=name)
140
141 if 'Error' in node:
142 return {'Error': 'Unable to retreive node'}
143
144 if node[name]['path'] == '':
Ales Komareka4a9f572016-12-03 20:15:50 +0100145 file_path = os.path.join(_get_nodes_dir(), name + '.yml')
Ales Komarek166cc672016-07-27 14:17:22 +0200146 else:
Ales Komareka4a9f572016-12-03 20:15:50 +0100147 file_path = os.path.join(_get_nodes_dir(), node[name]['path'], name + '.yml')
Ales Komarek166cc672016-07-27 14:17:22 +0200148
149 os.remove(file_path)
150
151 ret = 'Node {0} deleted'.format(name)
152
153 return ret
154
155
156def node_get(name, path=None, **kwargs):
157 '''
158 Return a specific node
159
160 CLI Examples:
161
162 .. code-block:: bash
163
164 salt '*' reclass.node_get host01.domain.com
165 salt '*' reclass.node_get name=host02.domain.com
166 '''
167 ret = {}
168 nodes = node_list(**kwargs)
169
170 if not name in nodes:
171 return {'Error': 'Error in retrieving node'}
172 ret[name] = nodes[name]
173 return ret
174
175
176def node_list(**connection_args):
177 '''
178 Return a list of available nodes
179
180 CLI Example:
181
182 .. code-block:: bash
183
184 salt '*' reclass.node_list
185 '''
186 ret = {}
187
Ales Komareka4a9f572016-12-03 20:15:50 +0100188 for root, sub_folders, files in os.walk(_get_nodes_dir()):
Adam Tengler805666d2017-05-15 16:01:13 +0000189 for fl in files:
190 file_path = os.path.join(root, fl)
191 with open(file_path, 'r') as file_handle:
192 file_read = yaml.load(file_handle.read())
193 file_data = file_read or {}
194 classes = file_data.get('classes', [])
195 parameters = file_data.get('parameters', {}).get('_param', [])
196 name = fl.replace('.yml', '')
Ales Komarek166cc672016-07-27 14:17:22 +0200197 host_name = name.split('.')[0]
198 domain_name = '.'.join(name.split('.')[1:])
Ales Komareka4a9f572016-12-03 20:15:50 +0100199 path = root.replace(_get_nodes_dir()+'/', '')
Ales Komarek166cc672016-07-27 14:17:22 +0200200 ret[name] = {
Ales Komareka4a9f572016-12-03 20:15:50 +0100201 'name': host_name,
202 'domain': domain_name,
203 'cluster': 'default',
204 'environment': 'prd',
205 'path': path,
206 'classes': classes,
207 'parameters': parameters
Ales Komarek166cc672016-07-27 14:17:22 +0200208 }
209
210 return ret
211
Ales Komareka4a9f572016-12-03 20:15:50 +0100212
Ales Komarek166cc672016-07-27 14:17:22 +0200213def node_update(name, classes=None, parameters=None, **connection_args):
214 '''
215 Update a node metadata information, classes and parameters.
216
217 CLI Examples:
218
219 .. code-block:: bash
220
221 salt '*' reclass.node_update name=nodename classes="[clas1, class2]"
222 '''
223 node = node_get(name=name)
224 if not node.has_key('Error'):
Ales Komarek71f94b02016-07-27 14:48:57 +0200225 node = node[name.split("/")[1]]
226 else:
227 return {'Error': 'Error in retrieving node'}
Ales Komareka4a9f572016-12-03 20:15:50 +0100228
229
230def inventory(**connection_args):
231 '''
232 Get all nodes in inventory and their associated services/roles classification.
233
234 CLI Examples:
235
236 .. code-block:: bash
237
238 salt '*' reclass.inventory
239 '''
240 defaults = find_and_read_configfile()
241 storage = get_storage(defaults['storage_type'], _get_nodes_dir(), _get_classes_dir())
242 reclass = Core(storage, None)
243 nodes = reclass.inventory()["nodes"]
244 output = {}
245
246 for node in nodes:
247 service_classification = []
248 role_classification = []
249 for service in nodes[node]['parameters']:
250 if service not in ['_param', 'private_keys', 'public_keys', 'known_hosts']:
251 service_classification.append(service)
252 for role in nodes[node]['parameters'][service]:
253 if role not in ['_support', '_orchestrate', 'common']:
254 role_classification.append('%s.%s' % (service, role))
255 output[node] = {
256 'roles': role_classification,
257 'services': service_classification,
258 }
259 return output