1"""
2:depends:  kazoo
3:configuration: See :py:mod:`salt.modules.zookeeper` for setup instructions.
4
5ACLS
6~~~~
7
8For more information about acls, please checkout the kazoo documentation.
9
10http://kazoo.readthedocs.io/en/latest/api/security.html#kazoo.security.make_digest_acl
11
12The following options can be included in the acl dictionary:
13
14    :param username: Username to use for the ACL.
15    :param password: A plain-text password to hash.
16    :param write: Write permission.
17    :type write: bool
18    :param create: Create permission.
19    :type create: bool
20    :param delete: Delete permission.
21    :type delete: bool
22    :param admin: Admin permission.
23    :type admin: bool
24    :param all: All permissions.
25    :type all: bool
26"""
27
28
29__virtualname__ = "zookeeper"
30
31
32def __virtual__():
33    if "zookeeper.create" in __salt__:
34        return __virtualname__
35    return (False, "zookeeper module could not be loaded")
36
37
38def _check_acls(left, right):
39    first = not bool(set(left) - set(right))
40    second = not bool(set(right) - set(left))
41    return first and second
42
43
44def present(
45    name,
46    value,
47    acls=None,
48    ephemeral=False,
49    sequence=False,
50    makepath=False,
51    version=-1,
52    profile=None,
53    hosts=None,
54    scheme=None,
55    username=None,
56    password=None,
57    default_acl=None,
58):
59    """
60    Make sure znode is present in the correct state with the correct acls
61
62    name
63        path to znode
64
65    value
66        value znode should be set to
67
68    acls
69        list of acl dictionaries to set on znode (make sure the ones salt is connected with are included)
70        Default: None
71
72    ephemeral
73        Boolean to indicate if ephemeral znode should be created
74        Default: False
75
76    sequence
77        Boolean to indicate if znode path is suffixed with a unique index
78        Default: False
79
80    makepath
81        Boolean to indicate if the parent paths should be created
82        Default: False
83
84    version
85        For updating, specify the version which should be updated
86        Default: -1 (always match)
87
88    profile
89        Configured Zookeeper profile to authenticate with (Default: None)
90
91    hosts
92        Lists of Zookeeper Hosts (Default: '127.0.0.1:2181)
93
94    scheme
95        Scheme to authenticate with (Default: 'digest')
96
97    username
98        Username to authenticate (Default: None)
99
100    password
101        Password to authenticate (Default: None)
102
103    default_acl
104        Default acls to assign if a node is created in this connection (Default: None)
105
106    .. code-block:: yaml
107
108        add znode:
109          zookeeper.present:
110            - name: /test/name
111            - value: gtmanfred
112            - makepath: True
113
114        update znode:
115          zookeeper.present:
116            - name: /test/name
117            - value: daniel
118            - acls:
119              - username: daniel
120                password: test
121                read: true
122              - username: gtmanfred
123                password: test
124                read: true
125                write: true
126                create: true
127                delete: true
128                admin: true
129            - makepath: True
130    """
131
132    ret = {
133        "name": name,
134        "result": False,
135        "comment": "Failed to setup znode {}".format(name),
136        "changes": {},
137    }
138    connkwargs = {
139        "profile": profile,
140        "hosts": hosts,
141        "scheme": scheme,
142        "username": username,
143        "password": password,
144        "default_acl": default_acl,
145    }
146    if acls is None:
147        chk_acls = []
148    else:
149        chk_acls = [__salt__["zookeeper.make_digest_acl"](**acl) for acl in acls]
150    if __salt__["zookeeper.exists"](name, **connkwargs):
151        cur_value = __salt__["zookeeper.get"](name, **connkwargs)
152        cur_acls = __salt__["zookeeper.get_acls"](name, **connkwargs)
153        if cur_value == value and _check_acls(cur_acls, chk_acls):
154            ret["result"] = True
155            ret[
156                "comment"
157            ] = "Znode {} is already set to the correct value with the correct acls".format(
158                name
159            )
160            return ret
161        elif __opts__["test"] is True:
162            ret["result"] = None
163            ret["comment"] = "Znode {} is will be updated".format(name)
164            ret["changes"]["old"] = {}
165            ret["changes"]["new"] = {}
166            if value != cur_value:
167                ret["changes"]["old"]["value"] = cur_value
168                ret["changes"]["new"]["value"] = value
169            if not _check_acls(chk_acls, cur_acls):
170                ret["changes"]["old"]["acls"] = cur_acls
171                ret["changes"]["new"]["acls"] = chk_acls
172            return ret
173        else:
174            value_result, acl_result = True, True
175            changes = {}
176            if value != cur_value:
177                __salt__["zookeeper.set"](name, value, version, **connkwargs)
178                new_value = __salt__["zookeeper.get"](name, **connkwargs)
179                value_result = new_value == value
180                changes.setdefault("new", {}).setdefault("value", new_value)
181                changes.setdefault("old", {}).setdefault("value", cur_value)
182            if chk_acls and not _check_acls(chk_acls, cur_acls):
183                __salt__["zookeeper.set_acls"](name, acls, version, **connkwargs)
184                new_acls = __salt__["zookeeper.get_acls"](name, **connkwargs)
185                acl_result = _check_acls(new_acls, chk_acls)
186                changes.setdefault("new", {}).setdefault("acls", new_acls)
187                changes.setdefault("old", {}).setdefault("value", cur_acls)
188            ret["changes"] = changes
189            if value_result and acl_result:
190                ret["result"] = True
191                ret["comment"] = "Znode {} successfully updated".format(name)
192            return ret
193
194    if __opts__["test"] is True:
195        ret["result"] = None
196        ret["comment"] = "{} is will be created".format(name)
197        ret["changes"]["old"] = {}
198        ret["changes"]["new"] = {}
199        ret["changes"]["new"]["acls"] = chk_acls
200        ret["changes"]["new"]["value"] = value
201        return ret
202
203    __salt__["zookeeper.create"](
204        name, value, acls, ephemeral, sequence, makepath, **connkwargs
205    )
206
207    value_result, acl_result = True, True
208    changes = {"old": {}}
209
210    new_value = __salt__["zookeeper.get"](name, **connkwargs)
211    value_result = new_value == value
212    changes.setdefault("new", {}).setdefault("value", new_value)
213
214    new_acls = __salt__["zookeeper.get_acls"](name, **connkwargs)
215    acl_result = acls is None or _check_acls(new_acls, chk_acls)
216    changes.setdefault("new", {}).setdefault("acls", new_acls)
217
218    ret["changes"] = changes
219    if value_result and acl_result:
220        ret["result"] = True
221        ret["comment"] = "Znode {} successfully created".format(name)
222
223    return ret
224
225
226def absent(
227    name,
228    version=-1,
229    recursive=False,
230    profile=None,
231    hosts=None,
232    scheme=None,
233    username=None,
234    password=None,
235    default_acl=None,
236):
237    """
238    Make sure znode is absent
239
240    name
241        path to znode
242
243    version
244        Specify the version which should be deleted
245        Default: -1 (always match)
246
247    recursive
248        Boolean to indicate if children should be recursively deleted
249        Default: False
250
251    profile
252        Configured Zookeeper profile to authenticate with (Default: None)
253
254    hosts
255        Lists of Zookeeper Hosts (Default: '127.0.0.1:2181)
256
257    scheme
258        Scheme to authenticate with (Default: 'digest')
259
260    username
261        Username to authenticate (Default: None)
262
263    password
264        Password to authenticate (Default: None)
265
266    default_acl
267        Default acls to assign if a node is created in this connection (Default: None)
268
269    .. code-block:: yaml
270
271        delete znode:
272          zookeeper.absent:
273            - name: /test
274            - recursive: True
275    """
276    ret = {
277        "name": name,
278        "result": False,
279        "comment": "Failed to delete znode {}".format(name),
280        "changes": {},
281    }
282    connkwargs = {
283        "profile": profile,
284        "hosts": hosts,
285        "scheme": scheme,
286        "username": username,
287        "password": password,
288        "default_acl": default_acl,
289    }
290
291    if __salt__["zookeeper.exists"](name, **connkwargs) is False:
292        ret["result"] = True
293        ret["comment"] = "Znode {} does not exist".format(name)
294        return ret
295
296    changes = {}
297    changes["value"] = __salt__["zookeeper.get"](name, **connkwargs)
298    changes["acls"] = __salt__["zookeeper.get_acls"](name, **connkwargs)
299    if recursive is True:
300        changes["children"] = __salt__["zookeeper.get_children"](name, **connkwargs)
301
302    if __opts__["test"] is True:
303        ret["result"] = None
304        ret["comment"] = "Znode {} will be removed".format(name)
305        ret["changes"]["old"] = changes
306        return ret
307
308    __salt__["zookeeper.delete"](name, version, recursive, **connkwargs)
309
310    if __salt__["zookeeper.exists"](name, **connkwargs) is False:
311        ret["result"] = True
312        ret["comment"] = "Znode {} has been removed".format(name)
313        ret["changes"]["old"] = changes
314
315    return ret
316
317
318def acls(
319    name,
320    acls,
321    version=-1,
322    profile=None,
323    hosts=None,
324    scheme=None,
325    username=None,
326    password=None,
327    default_acl=None,
328):
329    """
330    Update acls on a znode
331
332    name
333        path to znode
334
335    acls
336        list of acl dictionaries to set on znode
337
338    version
339        Specify the version which should be deleted
340        Default: -1 (always match)
341
342    profile
343        Configured Zookeeper profile to authenticate with (Default: None)
344
345    hosts
346        Lists of Zookeeper Hosts (Default: '127.0.0.1:2181)
347
348    scheme
349        Scheme to authenticate with (Default: 'digest')
350
351    username
352        Username to authenticate (Default: None)
353
354    password
355        Password to authenticate (Default: None)
356
357    default_acl
358        Default acls to assign if a node is created in this connection (Default: None)
359
360    .. code-block:: yaml
361
362        update acls:
363          zookeeper.acls:
364            - name: /test/name
365            - acls:
366              - username: daniel
367                password: test
368                all: True
369              - username: gtmanfred
370                password: test
371                all: True
372    """
373    ret = {
374        "name": name,
375        "result": False,
376        "comment": "Failed to set acls on znode {}".format(name),
377        "changes": {},
378    }
379    connkwargs = {
380        "profile": profile,
381        "hosts": hosts,
382        "scheme": scheme,
383        "username": username,
384        "password": password,
385        "default_acl": default_acl,
386    }
387    if isinstance(acls, dict):
388        acls = [acls]
389    chk_acls = [__salt__["zookeeper.make_digest_acl"](**acl) for acl in acls]
390
391    if not __salt__["zookeeper.exists"](name, **connkwargs):
392        ret["comment"] += ": Znode does not exist"
393        return ret
394
395    cur_acls = __salt__["zookeeper.get_acls"](name, **connkwargs)
396    if _check_acls(cur_acls, chk_acls):
397        ret["result"] = True
398        ret["comment"] = "Znode {} acls already set".format(name)
399        return ret
400
401    if __opts__["test"] is True:
402        ret["result"] = None
403        ret["comment"] = "Znode {} acls will be updated".format(name)
404        ret["changes"]["old"] = cur_acls
405        ret["changes"]["new"] = chk_acls
406        return ret
407
408    __salt__["zookeeper.set_acls"](name, acls, version, **connkwargs)
409
410    new_acls = __salt__["zookeeper.get_acls"](name, **connkwargs)
411    ret["changes"] = {"old": cur_acls, "new": new_acls}
412    if _check_acls(new_acls, chk_acls):
413        ret["result"] = True
414        ret["comment"] = "Znode {} acls updated".format(name)
415        return ret
416    ret["comment"] = "Znode {} acls failed to update".format(name)
417    return ret
418