Skip to content

Commit 6a25bf4

Browse files
author
Mike Place
authored
Merge pull request #42349 from coredumb/saltclass
New pillar/master_tops saltclass module
2 parents e4be436 + 139e065 commit 6a25bf4

File tree

17 files changed

+806
-0
lines changed

17 files changed

+806
-0
lines changed

doc/topics/releases/oxygen.rst

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,194 @@ file. For example:
110110
111111
These commands will run in sequence **before** the bootstrap script is executed.
112112

113+
New pillar/master_tops module called saltclass
114+
----------------------------------------------
115+
116+
This module clones the behaviour of reclass (http://reclass.pantsfullofunix.net/), without the need of an external app, and add several features to improve flexibility.
117+
Saltclass lets you define your nodes from simple ``yaml`` files (``.yml``) through hierarchical class inheritance with the possibility to override pillars down the tree.
118+
119+
**Features**
120+
121+
- Define your nodes through hierarchical class inheritance
122+
- Reuse your reclass datas with minimal modifications
123+
- applications => states
124+
- parameters => pillars
125+
- Use Jinja templating in your yaml definitions
126+
- Access to the following Salt objects in Jinja
127+
- ``__opts__``
128+
- ``__salt__``
129+
- ``__grains__``
130+
- ``__pillars__``
131+
- ``minion_id``
132+
- Chose how to merge or override your lists using ^ character (see examples)
133+
- Expand variables ${} with possibility to escape them if needed \${} (see examples)
134+
- Ignores missing node/class and will simply return empty without breaking the pillar module completely - will be logged
135+
136+
An example subset of datas is available here: http://git.mauras.ch/salt/saltclass/src/master/examples
137+
138+
========================== ===========
139+
Terms usable in yaml files Description
140+
========================== ===========
141+
classes A list of classes that will be processed in order
142+
states A list of states that will be returned by master_tops function
143+
pillars A yaml dictionnary that will be returned by the ext_pillar function
144+
environment Node saltenv that will be used by master_tops
145+
========================== ===========
146+
147+
A class consists of:
148+
149+
- zero or more parent classes
150+
- zero or more states
151+
- any number of pillars
152+
153+
A child class can override pillars from a parent class.
154+
A node definition is a class in itself with an added ``environment`` parameter for ``saltenv`` definition.
155+
156+
**class names**
157+
158+
Class names mimic salt way of defining states and pillar files.
159+
This means that ``default.users`` class name will correspond to one of these:
160+
161+
- ``<saltclass_path>/classes/default/users.yml``
162+
- ``<saltclass_path>/classes/default/users/init.yml``
163+
164+
**Saltclass tree**
165+
166+
A saltclass tree would look like this:
167+
168+
.. code-block:: text
169+
170+
<saltclass_path>
171+
├── classes
172+
│ ├── app
173+
│ │ ├── borgbackup.yml
174+
│ │ └── ssh
175+
│ │ └── server.yml
176+
│ ├── default
177+
│ │ ├── init.yml
178+
│ │ ├── motd.yml
179+
│ │ └── users.yml
180+
│ ├── roles
181+
│ │ ├── app.yml
182+
│ │ └── nginx
183+
│ │ ├── init.yml
184+
│ │ └── server.yml
185+
│ └── subsidiaries
186+
│ ├── gnv.yml
187+
│ ├── qls.yml
188+
│ └── zrh.yml
189+
└── nodes
190+
├── geneva
191+
│ └── gnv.node1.yml
192+
├── lausanne
193+
│ ├── qls.node1.yml
194+
│ └── qls.node2.yml
195+
├── node127.yml
196+
└── zurich
197+
├── zrh.node1.yml
198+
├── zrh.node2.yml
199+
└── zrh.node3.yml
200+
201+
**Examples**
202+
203+
``<saltclass_path>/nodes/lausanne/qls.node1.yml``
204+
205+
.. code-block:: yaml
206+
207+
environment: base
208+
209+
classes:
210+
{% for class in ['default'] %}
211+
- {{ class }}
212+
{% endfor %}
213+
- subsidiaries.{{ __grains__['id'].split('.')[0] }}
214+
215+
``<saltclass_path>/classes/default/init.yml``
216+
217+
.. code-block:: yaml
218+
219+
classes:
220+
- default.users
221+
- default.motd
222+
223+
states:
224+
- openssh
225+
226+
pillars:
227+
default:
228+
network:
229+
dns:
230+
srv1: 192.168.0.1
231+
srv2: 192.168.0.2
232+
domain: example.com
233+
ntp:
234+
srv1: 192.168.10.10
235+
srv2: 192.168.10.20
236+
237+
``<saltclass_path>/classes/subsidiaries/gnv.yml``
238+
239+
.. code-block:: yaml
240+
241+
pillars:
242+
default:
243+
network:
244+
sub: Geneva
245+
dns:
246+
srv1: 10.20.0.1
247+
srv2: 10.20.0.2
248+
srv3: 192.168.1.1
249+
domain: gnv.example.com
250+
users:
251+
adm1:
252+
uid: 1210
253+
gid: 1210
254+
gecos: 'Super user admin1'
255+
homedir: /srv/app/adm1
256+
adm3:
257+
uid: 1203
258+
gid: 1203
259+
gecos: 'Super user adm
260+
261+
Variable expansions:
262+
263+
Escaped variables are rendered as is - ``${test}``
264+
265+
Missing variables are rendered as is - ``${net:dns:srv2}``
266+
267+
.. code-block:: yaml
268+
269+
pillars:
270+
app:
271+
config:
272+
dns:
273+
srv1: ${default:network:dns:srv1}
274+
srv2: ${net:dns:srv2}
275+
uri: https://application.domain/call?\${test}
276+
prod_parameters:
277+
- p1
278+
- p2
279+
- p3
280+
pkg:
281+
- app-core
282+
- app-backend
283+
284+
List override:
285+
286+
Not using ``^`` as the first entry will simply merge the lists
287+
288+
.. code-block:: yaml
289+
290+
pillars:
291+
app:
292+
pkg:
293+
- ^
294+
- app-frontend
295+
296+
297+
**Known limitation**
298+
299+
Currently you can't have both a variable and an escaped variable in the same string as the escaped one will not be correctly rendered - '\${xx}' will stay as is instead of being rendered as '${xx}'
300+
113301
Newer PyWinRM Versions
114302
----------------------
115303

salt/pillar/saltclass.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# -*- coding: utf-8 -*-
2+
'''
3+
SaltClass Pillar Module
4+
5+
.. code-block:: yaml
6+
7+
ext_pillar:
8+
- saltclass:
9+
- path: /srv/saltclass
10+
11+
'''
12+
13+
# import python libs
14+
from __future__ import absolute_import
15+
import salt.utils.saltclass as sc
16+
import logging
17+
18+
log = logging.getLogger(__name__)
19+
20+
21+
def __virtual__():
22+
'''
23+
This module has no external dependencies
24+
'''
25+
return True
26+
27+
28+
def ext_pillar(minion_id, pillar, *args, **kwargs):
29+
'''
30+
Node definitions path will be retrieved from args - or set to default -
31+
then added to 'salt_data' dict that is passed to the 'get_pillars' function.
32+
'salt_data' dict is a convenient way to pass all the required datas to the function
33+
It contains:
34+
- __opts__
35+
- __salt__
36+
- __grains__
37+
- __pillar__
38+
- minion_id
39+
- path
40+
41+
If successfull the function will return a pillar dict for minion_id
42+
'''
43+
# If path has not been set, make a default
44+
for i in args:
45+
if 'path' not in i:
46+
path = '/srv/saltclass'
47+
args[i]['path'] = path
48+
log.warning('path variable unset, using default: {0}'.format(path))
49+
else:
50+
path = i['path']
51+
52+
# Create a dict that will contain our salt dicts to pass it to reclass
53+
salt_data = {
54+
'__opts__': __opts__,
55+
'__salt__': __salt__,
56+
'__grains__': __grains__,
57+
'__pillar__': pillar,
58+
'minion_id': minion_id,
59+
'path': path
60+
}
61+
62+
return sc.get_pillars(minion_id, salt_data)

salt/tops/saltclass.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# -*- coding: utf-8 -*-
2+
'''
3+
SaltClass master_tops Module
4+
5+
.. code-block:: yaml
6+
master_tops:
7+
saltclass:
8+
path: /srv/saltclass
9+
'''
10+
11+
# import python libs
12+
from __future__ import absolute_import
13+
import logging
14+
15+
import salt.utils.saltclass as sc
16+
17+
log = logging.getLogger(__name__)
18+
19+
20+
def __virtual__():
21+
'''
22+
Only run if properly configured
23+
'''
24+
if __opts__['master_tops'].get('saltclass'):
25+
return True
26+
return False
27+
28+
29+
def top(**kwargs):
30+
'''
31+
Node definitions path will be retrieved from __opts__ - or set to default -
32+
then added to 'salt_data' dict that is passed to the 'get_tops' function.
33+
'salt_data' dict is a convenient way to pass all the required datas to the function
34+
It contains:
35+
- __opts__
36+
- empty __salt__
37+
- __grains__
38+
- empty __pillar__
39+
- minion_id
40+
- path
41+
42+
If successfull the function will return a top dict for minion_id
43+
'''
44+
# If path has not been set, make a default
45+
_opts = __opts__['master_tops']['saltclass']
46+
if 'path' not in _opts:
47+
path = '/srv/saltclass'
48+
log.warning('path variable unset, using default: {0}'.format(path))
49+
else:
50+
path = _opts['path']
51+
52+
# Create a dict that will contain our salt objects
53+
# to send to get_tops function
54+
if 'id' not in kwargs['opts']:
55+
log.warning('Minion id not found - Returning empty dict')
56+
return {}
57+
else:
58+
minion_id = kwargs['opts']['id']
59+
60+
salt_data = {
61+
'__opts__': kwargs['opts'],
62+
'__salt__': {},
63+
'__grains__': kwargs['grains'],
64+
'__pillar__': {},
65+
'minion_id': minion_id,
66+
'path': path
67+
}
68+
69+
return sc.get_tops(minion_id, salt_data)

0 commit comments

Comments
 (0)