1"""
2Spacewalk Runner
3================
4
5.. versionadded:: 2016.3.0
6
7Runner to interact with Spacewalk using Spacewalk API
8
9:codeauthor: Nitin Madhok <nmadhok@g.clemson.edu>, Joachim Werner <joe@suse.com>, Benedikt Werner <1benediktwerner@gmail.com>
10:maintainer: Benedikt Werner <1benediktwerner@gmail.com>
11
12To use this runner, set up the Spacewalk URL, username and password in the
13master configuration at ``/etc/salt/master`` or ``/etc/salt/master.d/spacewalk.conf``:
14
15.. code-block:: yaml
16
17    spacewalk:
18      spacewalk01.domain.com:
19        username: 'testuser'
20        password: 'verybadpass'
21      spacewalk02.domain.com:
22        username: 'testuser'
23        password: 'verybadpass'
24
25.. note::
26
27    Optionally, ``protocol`` can be specified if the spacewalk server is
28    not using the defaults. Default is ``protocol: https``.
29
30"""
31
32import atexit
33import logging
34import xmlrpc.client
35
36log = logging.getLogger(__name__)
37
38_sessions = {}
39
40
41def __virtual__():
42    """
43    Check for spacewalk configuration in master config file
44    or directory and load runner only if it is specified
45    """
46    if not _get_spacewalk_configuration():
47        return False, "No spacewalk configuration found"
48    return True
49
50
51def _get_spacewalk_configuration(spacewalk_url=""):
52    """
53    Return the configuration read from the master configuration
54    file or directory
55    """
56    spacewalk_config = __opts__["spacewalk"] if "spacewalk" in __opts__ else None
57
58    if spacewalk_config:
59        try:
60            for spacewalk_server, service_config in spacewalk_config.items():
61                username = service_config.get("username", None)
62                password = service_config.get("password", None)
63                protocol = service_config.get("protocol", "https")
64
65                if not username or not password:
66                    log.error(
67                        "Username or Password has not been specified in the master "
68                        "configuration for %s",
69                        spacewalk_server,
70                    )
71                    return False
72
73                ret = {
74                    "api_url": "{}://{}/rpc/api".format(protocol, spacewalk_server),
75                    "username": username,
76                    "password": password,
77                }
78
79                if (not spacewalk_url) or (spacewalk_url == spacewalk_server):
80                    return ret
81        except Exception as exc:  # pylint: disable=broad-except
82            log.error("Exception encountered: %s", exc)
83            return False
84
85        if spacewalk_url:
86            log.error(
87                "Configuration for %s has not been specified in the master "
88                "configuration",
89                spacewalk_url,
90            )
91            return False
92
93    return False
94
95
96def _get_client_and_key(url, user, password, verbose=0):
97    """
98    Return the client object and session key for the client
99    """
100    session = {}
101    session["client"] = xmlrpc.client.Server(url, verbose=verbose, use_datetime=True)
102    session["key"] = session["client"].auth.login(user, password)
103
104    return session
105
106
107def _disconnect_session(session):
108    """
109    Disconnect API connection
110    """
111    session["client"].auth.logout(session["key"])
112
113
114def _get_session(server):
115    """
116    Get session and key
117    """
118    if server in _sessions:
119        return _sessions[server]
120
121    config = _get_spacewalk_configuration(server)
122    if not config:
123        raise Exception("No config for '{}' found on master".format(server))
124
125    session = _get_client_and_key(
126        config["api_url"], config["username"], config["password"]
127    )
128    atexit.register(_disconnect_session, session)
129
130    client = session["client"]
131    key = session["key"]
132    _sessions[server] = (client, key)
133
134    return client, key
135
136
137def api(server, command, *args, **kwargs):
138    """
139    Call the Spacewalk xmlrpc api.
140
141    CLI Example:
142
143    .. code-block:: bash
144
145        salt-run spacewalk.api spacewalk01.domain.com systemgroup.create MyGroup Description
146        salt-run spacewalk.api spacewalk01.domain.com systemgroup.create arguments='["MyGroup", "Description"]'
147
148    State Example:
149
150    .. code-block:: yaml
151
152        create_group:
153          salt.runner:
154            - name: spacewalk.api
155            - server: spacewalk01.domain.com
156            - command: systemgroup.create
157            - arguments:
158              - MyGroup
159              - Description
160    """
161    if "arguments" in kwargs:
162        arguments = kwargs["arguments"]
163    else:
164        arguments = args
165
166    call = "{} {}".format(command, arguments)
167    try:
168        client, key = _get_session(server)
169    except Exception as exc:  # pylint: disable=broad-except
170        err_msg = (
171            "Exception raised when connecting to spacewalk server ({}): {}".format(
172                server, exc
173            )
174        )
175        log.error(err_msg)
176        return {call: err_msg}
177
178    namespace, _, method = command.rpartition(".")
179    if not namespace:
180        return {
181            call: "Error: command must use the following format: 'namespace.method'"
182        }
183    endpoint = getattr(getattr(client, namespace), method)
184
185    try:
186        output = endpoint(key, *arguments)
187    except Exception as e:  # pylint: disable=broad-except
188        output = "API call failed: {}".format(e)
189
190    return {call: output}
191
192
193def addGroupsToKey(server, activation_key, groups):
194    """
195    Add server groups to a activation key
196
197    CLI Example:
198
199    .. code-block:: bash
200
201        salt-run spacewalk.addGroupsToKey spacewalk01.domain.com 1-my-key '[group1, group2]'
202    """
203
204    try:
205        client, key = _get_session(server)
206    except Exception as exc:  # pylint: disable=broad-except
207        err_msg = (
208            "Exception raised when connecting to spacewalk server ({}): {}".format(
209                server, exc
210            )
211        )
212        log.error(err_msg)
213        return {"Error": err_msg}
214
215    all_groups = client.systemgroup.listAllGroups(key)
216    groupIds = []
217    for group in all_groups:
218        if group["name"] in groups:
219            groupIds.append(group["id"])
220
221    if client.activationkey.addServerGroups(key, activation_key, groupIds) == 1:
222        return {activation_key: groups}
223    else:
224        return {activation_key: "Failed to add groups to activation key"}
225
226
227def deleteAllGroups(server):
228    """
229    Delete all server groups from Spacewalk
230    """
231
232    try:
233        client, key = _get_session(server)
234    except Exception as exc:  # pylint: disable=broad-except
235        err_msg = (
236            "Exception raised when connecting to spacewalk server ({}): {}".format(
237                server, exc
238            )
239        )
240        log.error(err_msg)
241        return {"Error": err_msg}
242
243    groups = client.systemgroup.listAllGroups(key)
244
245    deleted_groups = []
246    failed_groups = []
247
248    for group in groups:
249        if client.systemgroup.delete(key, group["name"]) == 1:
250            deleted_groups.append(group["name"])
251        else:
252            failed_groups.append(group["name"])
253
254    ret = {"deleted": deleted_groups}
255    if failed_groups:
256        ret["failed"] = failed_groups
257    return ret
258
259
260def deleteAllSystems(server):
261    """
262    Delete all systems from Spacewalk
263
264    CLI Example:
265
266    .. code-block:: bash
267
268        salt-run spacewalk.deleteAllSystems spacewalk01.domain.com
269    """
270
271    try:
272        client, key = _get_session(server)
273    except Exception as exc:  # pylint: disable=broad-except
274        err_msg = (
275            "Exception raised when connecting to spacewalk server ({}): {}".format(
276                server, exc
277            )
278        )
279        log.error(err_msg)
280        return {"Error": err_msg}
281
282    systems = client.system.listSystems(key)
283
284    ids = []
285    names = []
286    for system in systems:
287        ids.append(system["id"])
288        names.append(system["name"])
289
290    if client.system.deleteSystems(key, ids) == 1:
291        return {"deleted": names}
292    else:
293        return {"Error": "Failed to delete all systems"}
294
295
296def deleteAllActivationKeys(server):
297    """
298    Delete all activation keys from Spacewalk
299
300    CLI Example:
301
302    .. code-block:: bash
303
304        salt-run spacewalk.deleteAllActivationKeys spacewalk01.domain.com
305    """
306
307    try:
308        client, key = _get_session(server)
309    except Exception as exc:  # pylint: disable=broad-except
310        err_msg = (
311            "Exception raised when connecting to spacewalk server ({}): {}".format(
312                server, exc
313            )
314        )
315        log.error(err_msg)
316        return {"Error": err_msg}
317
318    activation_keys = client.activationkey.listActivationKeys(key)
319
320    deleted_keys = []
321    failed_keys = []
322
323    for aKey in activation_keys:
324        if client.activationkey.delete(key, aKey["key"]) == 1:
325            deleted_keys.append(aKey["key"])
326        else:
327            failed_keys.append(aKey["key"])
328
329    ret = {"deleted": deleted_keys}
330    if failed_keys:
331        ret["failed"] = failed_keys
332    return ret
333
334
335def unregister(name, server_url):
336    """
337    Unregister specified server from Spacewalk
338
339    CLI Example:
340
341    .. code-block:: bash
342
343        salt-run spacewalk.unregister my-test-vm spacewalk01.domain.com
344    """
345
346    try:
347        client, key = _get_session(server_url)
348    except Exception as exc:  # pylint: disable=broad-except
349        err_msg = (
350            "Exception raised when connecting to spacewalk server ({}): {}".format(
351                server_url, exc
352            )
353        )
354        log.error(err_msg)
355        return {name: err_msg}
356
357    systems_list = client.system.getId(key, name)
358
359    if systems_list:
360        for system in systems_list:
361            out = client.system.deleteSystem(key, system["id"])
362            if out == 1:
363                return {name: "Successfully unregistered from {}".format(server_url)}
364            else:
365                return {name: "Failed to unregister from {}".format(server_url)}
366    else:
367        return {
368            name: "System does not exist in spacewalk server ({})".format(server_url)
369        }
370