/
select.py
257 lines (208 loc) · 7.94 KB
/
select.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# -*- coding: utf-8 -*-
# pylint: disable=too-few-public-methods,modernize-parse-error
"""
The list of minions related to a Salt target is often needed for operations.
"""
from __future__ import absolute_import
from __future__ import print_function
import logging
import os
import sys
import re
# pylint: disable=import-error,3rd-party-module-not-gated,redefined-builtin
import salt.client
import netaddr
log = logging.getLogger(__name__)
def help_():
"""
Usage
"""
usage = ('salt-run select.minions key=value [key=value...]:\n'
'salt-run select.minions host=True key=value [key=value...]:\n\n'
'salt-run select.minions host=True format="{}" key=value [key=value...]:\n\n'
' Return an array of minions based on the target criteria\n'
' possibly formatted with format string\n'
' Note that the format string must contain exactly one {}\n'
'\n\n'
'salt-run select.one_minion key=value [key=value...]:\n\n'
' Return a random single minion that meets the critieria\n'
'\n\n'
'salt-run select.public_addresses key=value [key=value...]:\n'
'salt-run select.public_addresses tuples=True key=value [key=value...]:\n'
'salt-run select.public_addresses tuples=True host=True key=value [key=value...]:\n\n'
' Returns an array of public addresses for the specified criteria\n'
'\n\n'
'salt-run select.attr attr=value key=value [key=value...]:\n'
'salt-run select.attr host=True attr=value key=value [key=value...]:\n\n'
' Returns an array of pillar values for the specified criteria\n'
'\n\n'
'salt-run select.from pillar=var role=default_role attr=value1,value2 :\n\n'
' Returns an array of grain values that matches the pillar variable.\n'
' Defaults to role if variable is not found.\n'
'\n\n')
print(usage)
return ""
def _grain_host(client, minion):
"""
Return the host grain for a given minion, for use a short hostname
"""
return list(client.cmd(minion, 'grains.item', ['host']).values())[0]['host']
def _grain_fqdn(client, minion):
"""
Return the fqdn grain for a given minion
"""
return list(client.cmd(minion, 'grains.item', ['fqdn']).values())[0]['fqdn']
def minions(host=False, format='{}', fqdn=False, **kwargs):
"""
Some targets needs to match all minions within a search criteria.
"""
if not isinstance(format, str):
raise TypeError("format argument is not a string")
criteria = []
for key in kwargs:
if key[0] == "_":
continue
values = kwargs[key]
if not isinstance(values, list):
values = [values]
for value in values:
criteria.append("I@{}:{}".format(key, value))
search = " and ".join(criteria)
# When search matches no minions, salt prints to stdout. Suppress stdout.
_stdout = sys.stdout
sys.stdout = open(os.devnull, 'w')
local = salt.client.LocalClient()
_minions = local.cmd(search, 'pillar.get', ['id'], tgt_type="compound")
sys.stdout = _stdout
if host:
if fqdn:
return sorted([format.format(_grain_fqdn(local, k)) for k in _minions.keys()])
return sorted([format.format(_grain_host(local, k)) for k in _minions.keys()])
return sorted([format.format(m) for m in _minions.keys()])
def one_minion(**kwargs):
"""
Some steps only need to be run once, but on any minion in a specific
search. Return the first matching key.
"""
ret = minions(**kwargs)
return ret[0] if ret else None
def first(**kwargs):
"""
Some steps only need to be run once, but on any minion in a specific
search. Return the first matching key.
"""
ret = sorted(minions(**kwargs))
if ret:
return ret[0]
return ""
def public_addresses(tuples=False, host=False, roles=None, roles_or=None, url=False, **kwargs):
"""
Returns an array of public addresses matching the search critieria.
Can also return an array of tuples with fqdn or short name.
"""
criteria = []
for key in kwargs:
if key[0] == "_":
continue
criteria.append("I@{}:{}".format(key, kwargs[key]))
if roles_or is not None:
if not isinstance(roles_or, list):
roles_or = [roles_or]
roles_target = ["I@roles:{}".format(role) for role in roles_or]
criteria.append("( {} )".format(" or ".join(roles_target)))
if roles is not None:
if not isinstance(roles, list):
roles = [roles]
criteria.extend(["I@roles:{}".format(role) for role in roles])
search = " and ".join(criteria)
# When search matches no minions, salt prints to stdout. Suppress stdout.
_stdout = sys.stdout
sys.stdout = open(os.devnull, 'w')
local = salt.client.LocalClient()
result = local.cmd(search, 'public.address', [], tgt_type="compound")
sys.stdout = _stdout
def format_addr(addr):
"""
Format IP address when used for URLs
"""
if url:
if netaddr.valid_ipv6(addr) is True:
return "[{}]".format(addr)
return addr
if tuples:
if host:
addresses = [[_grain_host(local, k), format_addr(v)] for k, v in result.items()]
else:
addresses = [[k, format_addr(v)] for k, v in result.items()]
else:
addresses = []
for entry in result:
addresses.append(format_addr(result[entry]))
return addresses
def attr(host=False, **kwargs):
"""
Return a paired list of minions and a given attribute
"""
criteria = []
attribute = None
for key in kwargs:
if key[0] == "_":
continue
if key == 'attr':
attribute = kwargs['attr']
continue
criteria.append("I@{}:{}".format(key, kwargs[key]))
search = " and ".join(criteria)
# When search matches no minions, salt prints to stdout. Suppress stdout.
_stdout = sys.stdout
sys.stdout = open(os.devnull, 'w')
local = salt.client.LocalClient()
_minions = local.cmd(search, 'pillar.get', [attribute], tgt_type="compound")
sys.stdout = _stdout
if host:
pairs = [[_grain_host(local, k), v] for k, v in _minions.items()]
else:
pairs = [[k, v] for k, v in _minions.items()]
return pairs
def from_(pillar, role, *args, **kwargs):
"""
Return a list of roles and corresponding grains for the provided pillar
argument or role argument.
salt-run select.from rgw_configurations rgw host fqdn
salt-run select.from pillar=data, role=rgw, attr="host, fqdn"
Note: Support the second form because Jinja hates us.
"""
if 'attr' in kwargs:
args = re.split(r',\s*', kwargs['attr'])
# When search matches no minions, salt prints to stdout. Suppress stdout.
_stdout = sys.stdout
sys.stdout = open(os.devnull, 'w')
local = salt.client.LocalClient()
search = "I@roles:master"
try:
roles = list(local.cmd(search, 'pillar.get', [pillar], tgt_type="compound").values())[0]
# pylint: disable=bare-except
except:
roles = []
sys.stdout = _stdout
if not roles:
# With no pillar variable, check for minions with assigned role
result = minions(roles=role)
if result:
roles = [role]
results = []
for _role in roles:
minion_list = minions(roles=_role)
for minion in minion_list:
grains_result = list(local.cmd(minion, 'grains.item', list(args)).values())[0]
small = [_role]
for arg in list(args):
small.append(grains_result[arg])
results.append(small)
if results:
return results
return [[None] * (1 + len(args))]
__func_alias__ = {
'from_': 'from',
'help_': 'help',
}