summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ipalib/cli.py44
-rw-r--r--ipalib/plugins/baseldap.py42
-rw-r--r--ipalib/plugins/dns.py159
3 files changed, 225 insertions, 20 deletions
diff --git a/ipalib/cli.py b/ipalib/cli.py
index 99f236bb4..5e1365dc3 100644
--- a/ipalib/cli.py
+++ b/ipalib/cli.py
@@ -527,6 +527,47 @@ class textui(backend.Backend):
return None
return self.decode(data)
+ def prompt_yesno(self, label, default=None):
+ """
+ Prompt user for yes/no input. This method returns True/False according
+ to user response.
+
+ Parameter "default" should be True, False or None
+
+ If Default parameter is not None, user can enter an empty input instead
+ of Yes/No answer. Value passed to Default is returned in that case.
+
+ If Default parameter is None, user is asked for Yes/No answer until
+ a correct answer is provided. Answer is then returned.
+
+ In case of an error, a None value may returned
+ """
+
+ default_prompt = None
+ if default is not None:
+ if default:
+ default_prompt = "Yes"
+ else:
+ default_prompt = "No"
+
+ if default_prompt:
+ prompt = u'%s Yes/No (default %s): ' % (label, default_prompt)
+ else:
+ prompt = u'%s Yes/No: ' % label
+
+ while True:
+ try:
+ data = raw_input(self.encode(prompt)).lower()
+ except EOFError:
+ return None
+
+ if data in (u'yes', u'y'):
+ return True
+ elif data in ( u'n', u'no'):
+ return False
+ elif default is not None and data == u'':
+ return default
+
def prompt_password(self, label):
"""
Prompt user for a password or read it in via stdin depending
@@ -1032,6 +1073,9 @@ class cli(backend.Executioner):
param.label
)
+ for callback in getattr(cmd, 'INTERACTIVE_PROMPT_CALLBACKS', []):
+ callback(kw)
+
def load_files(self, cmd, kw):
"""
Load files from File parameters.
diff --git a/ipalib/plugins/baseldap.py b/ipalib/plugins/baseldap.py
index 3908dfe3e..7d4552576 100644
--- a/ipalib/plugins/baseldap.py
+++ b/ipalib/plugins/baseldap.py
@@ -482,12 +482,17 @@ class CallbackInterface(Method):
self.__class__.POST_CALLBACKS = []
if not hasattr(self.__class__, 'EXC_CALLBACKS'):
self.__class__.EXC_CALLBACKS = []
+ if not hasattr(self.__class__, 'INTERACTIVE_PROMPT_CALLBACKS'):
+ self.__class__.INTERACTIVE_PROMPT_CALLBACKS = []
if hasattr(self, 'pre_callback'):
self.register_pre_callback(self.pre_callback, True)
if hasattr(self, 'post_callback'):
self.register_post_callback(self.post_callback, True)
if hasattr(self, 'exc_callback'):
self.register_exc_callback(self.exc_callback, True)
+ if hasattr(self, 'interactive_prompt_callback'):
+ self.register_interactive_prompt_callback(
+ self.interactive_prompt_callback, True) #pylint: disable=E1101
super(Method, self).__init__()
@classmethod
@@ -520,6 +525,16 @@ class CallbackInterface(Method):
else:
klass.EXC_CALLBACKS.append(callback)
+ @classmethod
+ def register_interactive_prompt_callback(klass, callback, first=False):
+ assert callable(callback)
+ if not hasattr(klass, 'INTERACTIVE_PROMPT_CALLBACKS'):
+ klass.INTERACTIVE_PROMPT_CALLBACKS = []
+ if first:
+ klass.INTERACTIVE_PROMPT_CALLBACKS.insert(0, callback)
+ else:
+ klass.INTERACTIVE_PROMPT_CALLBACKS.append(callback)
+
def _call_exc_callbacks(self, args, options, exc, call_func, *call_args, **call_kwargs):
rv = None
for i in xrange(len(getattr(self, 'EXC_CALLBACKS', []))):
@@ -670,6 +685,9 @@ class LDAPCreate(CallbackInterface, crud.Create):
def exc_callback(self, keys, options, exc, call_func, *call_args, **call_kwargs):
raise exc
+ def interactive_prompt_callback(self, kw):
+ return
+
# list of attributes we want exported to JSON
json_friendly_attributes = (
'takes_args', 'takes_options',
@@ -795,6 +813,9 @@ class LDAPRetrieve(LDAPQuery):
def exc_callback(self, keys, options, exc, call_func, *call_args, **call_kwargs):
raise exc
+ def interactive_prompt_callback(self, kw):
+ return
+
class LDAPUpdate(LDAPQuery, crud.Update):
"""
@@ -959,6 +980,9 @@ class LDAPUpdate(LDAPQuery, crud.Update):
def exc_callback(self, keys, options, exc, call_func, *call_args, **call_kwargs):
raise exc
+ def interactive_prompt_callback(self, kw):
+ return
+
class LDAPDelete(LDAPMultiQuery):
"""
@@ -1046,6 +1070,9 @@ class LDAPDelete(LDAPMultiQuery):
def exc_callback(self, keys, options, exc, call_func, *call_args, **call_kwargs):
raise exc
+ def interactive_prompt_callback(self, kw):
+ return
+
class LDAPModMember(LDAPQuery):
"""
@@ -1191,6 +1218,9 @@ class LDAPAddMember(LDAPModMember):
def exc_callback(self, keys, options, exc, call_func, *call_args, **call_kwargs):
raise exc
+ def interactive_prompt_callback(self, kw):
+ return
+
class LDAPRemoveMember(LDAPModMember):
"""
@@ -1297,6 +1327,9 @@ class LDAPRemoveMember(LDAPModMember):
def exc_callback(self, keys, options, exc, call_func, *call_args, **call_kwargs):
raise exc
+ def interactive_prompt_callback(self, kw):
+ return
+
class LDAPSearch(CallbackInterface, crud.Search):
"""
@@ -1501,6 +1534,9 @@ class LDAPSearch(CallbackInterface, crud.Search):
def exc_callback(self, args, options, exc, call_func, *call_args, **call_kwargs):
raise exc
+ def interactive_prompt_callback(self, kw):
+ return
+
# list of attributes we want exported to JSON
json_friendly_attributes = (
'takes_options',
@@ -1644,6 +1680,9 @@ class LDAPAddReverseMember(LDAPModReverseMember):
def exc_callback(self, keys, options, exc, call_func, *call_args, **call_kwargs):
raise exc
+ def interactive_prompt_callback(self, kw):
+ return
+
class LDAPRemoveReverseMember(LDAPModReverseMember):
"""
Remove other LDAP entries from members in reverse.
@@ -1753,3 +1792,6 @@ class LDAPRemoveReverseMember(LDAPModReverseMember):
def exc_callback(self, keys, options, exc, call_func, *call_args, **call_kwargs):
raise exc
+
+ def interactive_prompt_callback(self, kw):
+ return
diff --git a/ipalib/plugins/dns.py b/ipalib/plugins/dns.py
index 3f8753d11..42ca498c9 100644
--- a/ipalib/plugins/dns.py
+++ b/ipalib/plugins/dns.py
@@ -1,5 +1,6 @@
# Authors:
# Pavel Zuna <pzuna@redhat.com>
+# Martin Kosek <mkosek@redhat.com>
#
# Copyright (C) 2010 Red Hat
# see file 'COPYING' for use and warranty information
@@ -49,6 +50,28 @@ EXAMPLES:
ipa dnsrecord-add example.com _ldap._tcp --srv-rec="0 1 389 slow.example.com"
ipa dnsrecord-add example.com _ldap._tcp --srv-rec="1 1 389 backup.example.com"
+ When dnsrecord-add command is executed with no option to add a specific record
+ an interactive mode is started. The mode interactively prompts for the most
+ typical record types for the respective zone:
+ ipa dnsrecord-add example.com www
+ [A record]: 1.2.3.4,11.22.33.44 (2 interactively entered random IPs)
+ [AAAA record]: (no AAAA address entered)
+ Record name: www
+ A record: 1.2.3.4, 11.22.33.44
+
+ The interactive mode can also be used for deleting the DNS records:
+ ipa dnsrecord-del example.com www
+ No option to delete specific record provided.
+ Delete all? Yes/No (default No): (do not delete all records)
+ Current DNS record contents:
+
+ A record: 1.2.3.4, 11.22.33.44
+
+ Delete A record '1.2.3.4'? Yes/No (default No):
+ Delete A record '11.22.33.44'? Yes/No (default No): y
+ Record name: www
+ A record: 1.2.3.4 (A record 11.22.33.44 has been deleted)
+
Show zone example.com:
ipa dnszone-show example.com
@@ -71,7 +94,6 @@ EXAMPLES:
if one is not included):
ipa dns-resolve www.example.com
ipa dns-resolve www
-
"""
import netaddr
@@ -93,6 +115,14 @@ _record_types = (
u'TSIG', u'TXT',
)
+# DNS zone record identificator
+_dns_zone_record = u'@'
+
+# most used record types, always ask for those in interactive prompt
+_top_record_types = ('A', 'AAAA', )
+_rev_top_record_types = ('PTR', )
+_zone_top_record_types = ('NS', 'MX', 'LOC', )
+
# attributes derived from record types
_record_attributes = [str('%srecord' % t.lower()) for t in _record_types]
@@ -195,6 +225,14 @@ _valid_reverse_zones = {
'.ip6.arpa.' : 32,
}
+def zone_is_reverse(zone_name):
+ for rev_zone_name in _valid_reverse_zones.keys():
+ if zone_name.endswith(rev_zone_name):
+ return True
+
+ return False
+
+
def has_cli_options(entry, no_option_msg):
entry = dict((t, entry.get(t, [])) for t in _record_attributes)
numattr = reduce(lambda x,y: x+y,
@@ -505,7 +543,7 @@ class dnsrecord(LDAPObject):
def is_pkey_zone_record(self, *keys):
idnsname = keys[-1]
- if idnsname == '@' or idnsname == ('%s.' % keys[-2]):
+ if idnsname == str(_dns_zone_record) or idnsname == ('%s.' % keys[-2]):
return True
return False
@@ -533,25 +571,40 @@ class dnsrecord_cmd_w_record_options(Command):
def get_record_options(self):
for t in _record_types:
t = t.encode('utf-8')
- doc = self.record_param_doc % t
- validator = _record_validators.get(t)
- if validator:
- yield List(
- '%srecord?' % t.lower(), validator,
- cli_name='%s_rec' % t.lower(), doc=doc,
- label='%s record' % t, attribute=True
- )
- else:
- yield List(
- '%srecord?' % t.lower(), cli_name='%s_rec' % t.lower(),
- doc=doc, label='%s record' % t, attribute=True
- )
+ yield self.get_record_option(t)
def record_options_2_entry(self, **options):
entries = dict((t, options.get(t, [])) for t in _record_attributes)
entries.update(dict((k, []) for (k,v) in entries.iteritems() if v == None ))
return entries
+ def get_record_option(self, rec_type):
+ doc = self.record_param_doc % rec_type
+ validator = _record_validators.get(rec_type)
+ if validator:
+ return List(
+ '%srecord?' % rec_type.lower(), validator,
+ cli_name='%s_rec' % rec_type.lower(), doc=doc,
+ label='%s record' % rec_type, attribute=True
+ )
+ else:
+ return List(
+ '%srecord?' % rec_type.lower(), cli_name='%s_rec' % rec_type.lower(),
+ doc=doc, label='%s record' % rec_type, attribute=True
+ )
+
+ def prompt_record_options(self, rec_type_list):
+ user_options = {}
+ # ask for all usual record types
+ for rec_type in rec_type_list:
+ rec_option = self.get_record_option(rec_type)
+ raw = self.Backend.textui.prompt(rec_option.label,optional=True)
+ rec_value = rec_option(raw)
+ if rec_value is not None:
+ user_options[rec_option.name] = rec_value
+
+ return user_options
+
class dnsrecord_mod_record(LDAPQuery, dnsrecord_cmd_w_record_options):
"""
@@ -599,7 +652,7 @@ class dnsrecord_mod_record(LDAPQuery, dnsrecord_cmd_w_record_options):
self.obj.handle_not_found(*keys)
if self.obj.is_pkey_zone_record(*keys):
- entry_attrs[self.obj.primary_key.name] = [u'@']
+ entry_attrs[self.obj.primary_key.name] = [_dns_zone_record]
retval = self.post_callback(keys, entry_attrs)
if retval:
@@ -637,7 +690,8 @@ class dnsrecord_add(LDAPCreate, dnsrecord_cmd_w_record_options):
"""
Add new DNS resource record.
"""
- no_option_msg = 'No options to add a specific record provided.'
+ no_option_msg = 'No options to add a specific record provided.\n' \
+ "Command help may be consulted for all supported record types."
takes_options = LDAPCreate.takes_options + (
Flag('force',
label=_('Force'),
@@ -693,6 +747,24 @@ class dnsrecord_add(LDAPCreate, dnsrecord_cmd_w_record_options):
return dn
+ def interactive_prompt_callback(self, kw):
+ for param in kw.keys():
+ if param in _record_attributes:
+ # some record type entered, skip this helper
+ return
+
+ # check zone type
+ if kw['idnsname'] == _dns_zone_record:
+ top_record_types = _zone_top_record_types
+ elif zone_is_reverse(kw['dnszoneidnsname']):
+ top_record_types = _rev_top_record_types
+ else:
+ top_record_types = _top_record_types
+
+ # ask for all usual record types
+ user_options = self.prompt_record_options(top_record_types)
+ kw.update(user_options)
+
def pre_callback(self, ldap, dn, entry_attrs, *keys, **options):
for rtype in options:
rtype_cb = '_%s_pre_callback' % rtype
@@ -727,7 +799,8 @@ class dnsrecord_del(dnsrecord_mod_record):
"""
Delete DNS resource record.
"""
- no_option_msg = _('Neither --del-all nor options to delete a specific record provided.')
+ no_option_msg = _('Neither --del-all nor options to delete a specific record provided.\n'\
+ "Command help may be consulted for all supported record types.")
takes_options = (
Flag('del_all',
default=False,
@@ -745,6 +818,52 @@ class dnsrecord_del(dnsrecord_mod_record):
entry = super(dnsrecord_del, self).record_options_2_entry(**options)
return has_cli_options(entry, self.no_option_msg)
+ def interactive_prompt_callback(self, kw):
+ if kw.get('del_all', False):
+ return
+ for param in kw.keys():
+ if param in _record_attributes:
+ # we have something to delete, skip this helper
+ return
+
+ # get DNS record first so that the NotFound exception is raised
+ # before the helper would start
+ dns_record = api.Command['dnsrecord_show'](kw['dnszoneidnsname'], kw['idnsname'])['result']
+ rec_types = [rec_type for rec_type in dns_record if rec_type in _record_attributes]
+
+ self.Backend.textui.print_plain(_("No option to delete specific record provided."))
+ user_del_all = self.Backend.textui.prompt_yesno(_("Delete all?"), default=False)
+
+ if user_del_all is True:
+ kw['del_all'] = True
+ return
+
+ # ask user for records to be removed
+ dns_record = api.Command['dnsrecord_show'](kw['dnszoneidnsname'], kw['idnsname'])['result']
+ rec_types = [rec_type for rec_type in dns_record if rec_type in _record_attributes]
+
+ self.Backend.textui.print_plain(_(u'Current DNS record contents:\n'))
+ present_params = []
+ for param in self.params():
+ if param.name in _record_attributes and param.name in dns_record:
+ present_params.append(param)
+ rec_type_content = u', '.join(dns_record[param.name])
+ self.Backend.textui.print_plain(u'%s: %s' % (param.label, rec_type_content))
+ self.Backend.textui.print_plain(u'')
+
+ # ask what records to remove
+ for param in present_params:
+ deleted_values = []
+ for rec_value in dns_record[param.name]:
+ user_del_value = self.Backend.textui.prompt_yesno(
+ _(u"Delete %s '%s'?"
+ % (param.label, rec_value)), default=False)
+ if user_del_value is True:
+ deleted_values.append(rec_value)
+ if deleted_values:
+ deleted_list = u','.join(deleted_values)
+ kw[param.name] = param(deleted_list)
+
def update_old_entry_callback(self, entry_attrs, old_entry_attrs):
for (a, v) in entry_attrs.iteritems():
if not isinstance(v, (list, tuple)):
@@ -776,7 +895,7 @@ class dnsrecord_show(LDAPRetrieve, dnsrecord_cmd_w_record_options):
def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
if self.obj.is_pkey_zone_record(*keys):
- entry_attrs[self.obj.primary_key.name] = [u'@']
+ entry_attrs[self.obj.primary_key.name] = [_dns_zone_record]
return dn
api.register(dnsrecord_show)
@@ -805,7 +924,7 @@ class dnsrecord_find(LDAPSearch, dnsrecord_cmd_w_record_options):
zone_obj = self.api.Object[self.obj.parent_object]
zone_dn = zone_obj.get_dn(args[0])
if entries[0][0] == zone_dn:
- entries[0][1][zone_obj.primary_key.name] = [u'@']
+ entries[0][1][zone_obj.primary_key.name] = [_dns_zone_record]
api.register(dnsrecord_find)