1"""
2Apache Libcloud DNS Management
3==============================
4
5Connection module for Apache Libcloud DNS management
6
7.. versionadded:: 2016.11.0
8
9:configuration:
10    This module uses a configuration profile for one or multiple DNS providers
11
12    .. code-block:: yaml
13
14        libcloud_dns:
15            profile_test1:
16              driver: cloudflare
17              key: 12345
18              secret: mysecret
19            profile_test2:
20              driver: godaddy
21              key: 12345
22              secret: mysecret
23              shopper_id: 12345
24
25:depends: apache-libcloud
26"""
27# keep lint from choking on _get_conn and _cache_id
28# pylint: disable=E0602
29
30
31import logging
32
33from salt.utils.versions import LooseVersion as _LooseVersion
34
35log = logging.getLogger(__name__)
36
37REQUIRED_LIBCLOUD_VERSION = "2.0.0"
38try:
39    # pylint: disable=unused-import
40    import libcloud
41    from libcloud.dns.providers import get_driver
42    from libcloud.dns.types import RecordType
43
44    # pylint: enable=unused-import
45    if hasattr(libcloud, "__version__") and _LooseVersion(
46        libcloud.__version__
47    ) < _LooseVersion(REQUIRED_LIBCLOUD_VERSION):
48        raise ImportError()
49    logging.getLogger("libcloud").setLevel(logging.CRITICAL)
50    HAS_LIBCLOUD = True
51except ImportError:
52    HAS_LIBCLOUD = False
53
54
55def __virtual__():
56    """
57    Only load if libcloud libraries exist.
58    """
59    if not HAS_LIBCLOUD:
60        return (
61            False,
62            "A apache-libcloud library with version at least {} was not found".format(
63                REQUIRED_LIBCLOUD_VERSION
64            ),
65        )
66    return True
67
68
69def _get_driver(profile):
70    config = __salt__["config.option"]("libcloud_dns")[profile]
71    cls = get_driver(config["driver"])
72    args = config.copy()
73    del args["driver"]
74    args["key"] = config.get("key")
75    args["secret"] = config.get("secret", None)
76    args["secure"] = config.get("secure", True)
77    args["host"] = config.get("host", None)
78    args["port"] = config.get("port", None)
79    return cls(**args)
80
81
82def list_record_types(profile):
83    """
84    List available record types for the given profile, e.g. A, AAAA
85
86    :param profile: The profile key
87    :type  profile: ``str``
88
89    CLI Example:
90
91    .. code-block:: bash
92
93        salt myminion libcloud_dns.list_record_types profile1
94    """
95    conn = _get_driver(profile=profile)
96    return conn.list_record_types()
97
98
99def list_zones(profile):
100    """
101    List zones for the given profile
102
103    :param profile: The profile key
104    :type  profile: ``str``
105
106    CLI Example:
107
108    .. code-block:: bash
109
110        salt myminion libcloud_dns.list_zones profile1
111    """
112    conn = _get_driver(profile=profile)
113    return [_simple_zone(zone) for zone in conn.list_zones()]
114
115
116def list_records(zone_id, profile, type=None):
117    """
118    List records for the given zone_id on the given profile
119
120    :param zone_id: Zone to export.
121    :type  zone_id: ``str``
122
123    :param profile: The profile key
124    :type  profile: ``str``
125
126    :param type: The record type, e.g. A, NS
127    :type  type: ``str``
128
129    CLI Example:
130
131    .. code-block:: bash
132
133        salt myminion libcloud_dns.list_records google.com profile1
134    """
135    conn = _get_driver(profile=profile)
136    zone = conn.get_zone(zone_id)
137    if type is not None:
138        return [
139            _simple_record(record)
140            for record in conn.list_records(zone)
141            if record.type == type
142        ]
143    else:
144        return [_simple_record(record) for record in conn.list_records(zone)]
145
146
147def get_zone(zone_id, profile):
148    """
149    Get zone information for the given zone_id on the given profile
150
151    :param zone_id: Zone to export.
152    :type  zone_id: ``str``
153
154    :param profile: The profile key
155    :type  profile: ``str``
156
157    CLI Example:
158
159    .. code-block:: bash
160
161        salt myminion libcloud_dns.get_zone google.com profile1
162    """
163    conn = _get_driver(profile=profile)
164    return _simple_zone(conn.get_zone(zone_id))
165
166
167def get_record(zone_id, record_id, profile):
168    """
169    Get record information for the given zone_id on the given profile
170
171    :param zone_id: Zone to export.
172    :type  zone_id: ``str``
173
174    :param record_id: Record to delete.
175    :type  record_id: ``str``
176
177    :param profile: The profile key
178    :type  profile: ``str``
179
180    CLI Example:
181
182    .. code-block:: bash
183
184        salt myminion libcloud_dns.get_record google.com www profile1
185    """
186    conn = _get_driver(profile=profile)
187    return _simple_record(conn.get_record(zone_id, record_id))
188
189
190def create_zone(domain, profile, type="master", ttl=None):
191    """
192    Create a new zone.
193
194    :param domain: Zone domain name (e.g. example.com)
195    :type domain: ``str``
196
197    :param profile: The profile key
198    :type  profile: ``str``
199
200    :param type: Zone type (master / slave).
201    :type  type: ``str``
202
203    :param ttl: TTL for new records. (optional)
204    :type  ttl: ``int``
205
206    CLI Example:
207
208    .. code-block:: bash
209
210        salt myminion libcloud_dns.create_zone google.com profile1
211    """
212    conn = _get_driver(profile=profile)
213    zone = conn.create_record(domain, type=type, ttl=ttl)
214    return _simple_zone(zone)
215
216
217def update_zone(zone_id, domain, profile, type="master", ttl=None):
218    """
219    Update an existing zone.
220
221    :param zone_id: Zone ID to update.
222    :type  zone_id: ``str``
223
224    :param domain: Zone domain name (e.g. example.com)
225    :type  domain: ``str``
226
227    :param profile: The profile key
228    :type  profile: ``str``
229
230    :param type: Zone type (master / slave).
231    :type  type: ``str``
232
233    :param ttl: TTL for new records. (optional)
234    :type  ttl: ``int``
235
236    CLI Example:
237
238    .. code-block:: bash
239
240        salt myminion libcloud_dns.update_zone google.com google.com profile1 type=slave
241    """
242    conn = _get_driver(profile=profile)
243    zone = conn.get_zone(zone_id)
244    return _simple_zone(conn.update_zone(zone=zone, domain=domain, type=type, ttl=ttl))
245
246
247def create_record(name, zone_id, type, data, profile):
248    """
249    Create a new record.
250
251    :param name: Record name without the domain name (e.g. www).
252                 Note: If you want to create a record for a base domain
253                 name, you should specify empty string ('') for this
254                 argument.
255    :type  name: ``str``
256
257    :param zone_id: Zone where the requested record is created.
258    :type  zone_id: ``str``
259
260    :param type: DNS record type (A, AAAA, ...).
261    :type  type: ``str``
262
263    :param data: Data for the record (depends on the record type).
264    :type  data: ``str``
265
266    :param profile: The profile key
267    :type  profile: ``str``
268
269    CLI Example:
270
271    .. code-block:: bash
272
273        salt myminion libcloud_dns.create_record www google.com A 12.32.12.2 profile1
274    """
275    conn = _get_driver(profile=profile)
276    record_type = _string_to_record_type(type)
277    zone = conn.get_zone(zone_id)
278    return _simple_record(conn.create_record(name, zone, record_type, data))
279
280
281def delete_zone(zone_id, profile):
282    """
283    Delete a zone.
284
285    :param zone_id: Zone to delete.
286    :type  zone_id: ``str``
287
288    :param profile: The profile key
289    :type  profile: ``str``
290
291    :rtype: ``bool``
292
293    CLI Example:
294
295    .. code-block:: bash
296
297        salt myminion libcloud_dns.delete_zone google.com profile1
298    """
299    conn = _get_driver(profile=profile)
300    zone = conn.get_zone(zone_id=zone_id)
301    return conn.delete_zone(zone)
302
303
304def delete_record(zone_id, record_id, profile):
305    """
306    Delete a record.
307
308    :param zone_id: Zone to delete.
309    :type  zone_id: ``str``
310
311    :param record_id: Record to delete.
312    :type  record_id: ``str``
313
314    :param profile: The profile key
315    :type  profile: ``str``
316
317    :rtype: ``bool``
318
319    CLI Example:
320
321    .. code-block:: bash
322
323        salt myminion libcloud_dns.delete_record google.com www profile1
324    """
325    conn = _get_driver(profile=profile)
326    record = conn.get_record(zone_id=zone_id, record_id=record_id)
327    return conn.delete_record(record)
328
329
330def get_bind_data(zone_id, profile):
331    """
332    Export Zone to the BIND compatible format.
333
334    :param zone_id: Zone to export.
335    :type  zone_id: ``str``
336
337    :param profile: The profile key
338    :type  profile: ``str``
339
340    :return: Zone data in BIND compatible format.
341    :rtype: ``str``
342
343    CLI Example:
344
345    .. code-block:: bash
346
347        salt myminion libcloud_dns.get_bind_data google.com profile1
348    """
349    conn = _get_driver(profile=profile)
350    zone = conn.get_zone(zone_id)
351    return conn.export_zone_to_bind_format(zone)
352
353
354def extra(method, profile, **libcloud_kwargs):
355    """
356    Call an extended method on the driver
357
358    :param method: Driver's method name
359    :type  method: ``str``
360
361    :param profile: The profile key
362    :type  profile: ``str``
363
364    :param libcloud_kwargs: Extra arguments for the driver's delete_container method
365    :type  libcloud_kwargs: ``dict``
366
367    CLI Example:
368
369    .. code-block:: bash
370
371        salt myminion libcloud_dns.extra ex_get_permissions google container_name=my_container object_name=me.jpg --out=yaml
372    """
373    _sanitize_kwargs(libcloud_kwargs)
374    conn = _get_driver(profile=profile)
375    connection_method = getattr(conn, method)
376    return connection_method(**libcloud_kwargs)
377
378
379def _string_to_record_type(string):
380    """
381    Return a string representation of a DNS record type to a
382    libcloud RecordType ENUM.
383
384    :param string: A record type, e.g. A, TXT, NS
385    :type  string: ``str``
386
387    :rtype: :class:`RecordType`
388    """
389    string = string.upper()
390    record_type = getattr(RecordType, string)
391    return record_type
392
393
394def _simple_zone(zone):
395    return {
396        "id": zone.id,
397        "domain": zone.domain,
398        "type": zone.type,
399        "ttl": zone.ttl,
400        "extra": zone.extra,
401    }
402
403
404def _simple_record(record):
405    return {
406        "id": record.id,
407        "name": record.name,
408        "type": record.type,
409        "data": record.data,
410        "zone": _simple_zone(record.zone),
411        "ttl": record.ttl,
412        "extra": record.extra,
413    }
414