From 2f48e7c38754e73cd51cdcbdd9419a4139a08dab Mon Sep 17 00:00:00 2001 From: Joe LeVeque Date: Mon, 8 Jun 2020 13:50:10 -0700 Subject: [PATCH] [201911][config] Support abbreviation (#933) Backport #893 to the 201911 branch to support abbreviation of `config` subcommands --- config/main.py | 120 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 79 insertions(+), 41 deletions(-) diff --git a/config/main.py b/config/main.py index 4edbc4ec01..ad843e2082 100755 --- a/config/main.py +++ b/config/main.py @@ -62,6 +62,44 @@ def log_error(msg): syslog.syslog(syslog.LOG_ERR, msg) syslog.closelog() +class AbbreviationGroup(click.Group): + """This subclass of click.Group supports abbreviated subgroup/subcommand names + """ + + def get_command(self, ctx, cmd_name): + # Try to get builtin commands as normal + rv = click.Group.get_command(self, ctx, cmd_name) + if rv is not None: + return rv + + # Allow automatic abbreviation of the command. "status" for + # instance will match "st". We only allow that however if + # there is only one command. + # If there are multiple matches and the shortest one is the common prefix of all the matches, return + # the shortest one + matches = [] + shortest = None + for x in self.list_commands(ctx): + if x.lower().startswith(cmd_name.lower()): + matches.append(x) + if not shortest: + shortest = x + elif len(shortest) > len(x): + shortest = x + + if not matches: + return None + elif len(matches) == 1: + return click.Group.get_command(self, ctx, matches[0]) + else: + for x in matches: + if not x.startswith(shortest): + break + else: + return click.Group.get_command(self, ctx, shortest) + + ctx.fail('Too many matches: %s' % ', '.join(sorted(matches))) + # # Load asic_type for further use # @@ -528,7 +566,7 @@ def is_ipaddress(val): # This is our main entrypoint - the main 'config' command -@click.group(context_settings=CONTEXT_SETTINGS) +@click.group(cls=AbbreviationGroup, context_settings=CONTEXT_SETTINGS) def config(): """SONiC command line - 'config' command""" @@ -903,7 +941,7 @@ def hostname(new_hostname): # # 'portchannel' group ('config portchannel ...') # -@config.group() +@config.group(cls=AbbreviationGroup) @click.pass_context def portchannel(ctx): config_db = ConfigDBConnector() @@ -935,7 +973,7 @@ def remove_portchannel(ctx, portchannel_name): db = ctx.obj['db'] db.set_entry('PORTCHANNEL', portchannel_name, None) -@portchannel.group('member') +@portchannel.group(cls=AbbreviationGroup, name='member') @click.pass_context def portchannel_member(ctx): pass @@ -964,7 +1002,7 @@ def del_portchannel_member(ctx, portchannel_name, port_name): # # 'mirror_session' group ('config mirror_session ...') # -@config.group('mirror_session') +@config.group(cls=AbbreviationGroup, name='mirror_session') def mirror_session(): pass @@ -1036,7 +1074,7 @@ def remove(session_name): # # 'pfcwd' group ('config pfcwd ...') # -@config.group() +@config.group(cls=AbbreviationGroup) def pfcwd(): """Configure pfc watchdog """ pass @@ -1122,7 +1160,7 @@ def start_default(verbose): # # 'qos' group ('config qos ...') # -@config.group() +@config.group(cls=AbbreviationGroup) @click.pass_context def qos(ctx): pass @@ -1160,7 +1198,7 @@ def reload(): # # 'warm_restart' group ('config warm_restart ...') # -@config.group('warm_restart') +@config.group(cls=AbbreviationGroup, name='warm_restart') @click.pass_context @click.option('-s', '--redis-unix-socket-path', help='unix socket path for redis connection') def warm_restart(ctx, redis_unix_socket_path): @@ -1236,7 +1274,7 @@ def warm_restart_bgp_eoiu(ctx, enable): # # 'vlan' group ('config vlan ...') # -@config.group() +@config.group(cls=AbbreviationGroup) @click.pass_context @click.option('-s', '--redis-unix-socket-path', help='unix socket path for redis connection') def vlan(ctx, redis_unix_socket_path): @@ -1276,7 +1314,7 @@ def del_vlan(ctx, vid): # # 'member' group ('config vlan member ...') # -@vlan.group('member') +@vlan.group(cls=AbbreviationGroup, name='member') @click.pass_context def vlan_member(ctx): pass @@ -1391,7 +1429,7 @@ def vrf_delete_management_vrf(config_db): config_db.mod_entry('MGMT_VRF_CONFIG',"vrf_global",{"mgmtVrfEnabled": "false"}) mvrf_restart_services() -@config.group() +@config.group(cls=AbbreviationGroup) @click.pass_context def snmpagentaddress(ctx): """SNMP agent listening IP address, port, vrf configuration""" @@ -1441,7 +1479,7 @@ def del_snmp_agent_address(ctx, agentip, port, vrf): cmd="systemctl restart snmp" os.system (cmd) -@config.group() +@config.group(cls=AbbreviationGroup) @click.pass_context def snmptrap(ctx): """SNMP Trap server configuration to send traps""" @@ -1489,7 +1527,7 @@ def delete_snmptrap_server(ctx, ver): cmd="systemctl restart snmp" os.system (cmd) -@vlan.group('dhcp_relay') +@vlan.group(cls=AbbreviationGroup, name='dhcp_relay') @click.pass_context def vlan_dhcp_relay(ctx): pass @@ -1558,7 +1596,7 @@ def del_vlan_dhcp_relay_destination(ctx, vid, dhcp_relay_destination_ip): # 'bgp' group ('config bgp ...') # -@config.group() +@config.group(cls=AbbreviationGroup) def bgp(): """BGP-related configuration tasks""" pass @@ -1567,7 +1605,7 @@ def bgp(): # 'shutdown' subgroup ('config bgp shutdown ...') # -@bgp.group() +@bgp.group(cls=AbbreviationGroup) def shutdown(): """Shut down BGP session(s)""" pass @@ -1624,7 +1662,7 @@ def neighbor(ipaddr_or_hostname, verbose): if not found_neighbor: click.get_current_context().fail("Could not locate neighbor '{}'".format(ipaddr_or_hostname)) -@bgp.group() +@bgp.group(cls=AbbreviationGroup) def startup(): """Start up BGP session(s)""" pass @@ -1685,7 +1723,7 @@ def neighbor(ipaddr_or_hostname, verbose): # 'remove' subgroup ('config bgp remove ...') # -@bgp.group() +@bgp.group(cls=AbbreviationGroup) def remove(): "Remove BGP neighbor configuration from the device" pass @@ -1718,7 +1756,7 @@ def remove_neighbor(neighbor_ip_or_hostname): # 'interface' group ('config interface ...') # -@config.group() +@config.group(cls=AbbreviationGroup) @click.pass_context def interface(ctx): """Interface-related configuration tasks""" @@ -1831,7 +1869,7 @@ def mgmt_ip_restart_services(): # 'ip' subgroup ('config interface ip ...') # -@interface.group() +@interface.group(cls=AbbreviationGroup) @click.pass_context def ip(ctx): """Add or remove IP address""" @@ -1937,7 +1975,7 @@ def remove(ctx, interface_name, ip_addr): # -@interface.group() +@interface.group(cls=AbbreviationGroup) @click.pass_context def vrf(ctx): """Bind or unbind VRF""" @@ -2008,7 +2046,7 @@ def unbind(ctx, interface_name): # 'vrf' group ('config vrf ...') # -@config.group('vrf') +@config.group(cls=AbbreviationGroup, name='vrf') @click.pass_context def vrf(ctx): """VRF-related configuration tasks""" @@ -2054,7 +2092,7 @@ def del_vrf(ctx, vrf_name): # 'route' group ('config route ...') # -@config.group() +@config.group(cls=AbbreviationGroup) @click.pass_context def route(ctx): """route-related configuration tasks""" @@ -2168,7 +2206,7 @@ def del_route(ctx, command_str): # 'acl' group ('config acl ...') # -@config.group() +@config.group(cls=AbbreviationGroup) def acl(): """ACL-related configuration tasks""" pass @@ -2177,7 +2215,7 @@ def acl(): # 'add' subgroup ('config acl add ...') # -@acl.group() +@acl.group(cls=AbbreviationGroup) def add(): """ Add ACL configuration. @@ -2241,7 +2279,7 @@ def table(table_name, table_type, description, ports, stage): # 'remove' subgroup ('config acl remove ...') # -@acl.group() +@acl.group(cls=AbbreviationGroup) def remove(): """ Remove ACL configuration. @@ -2267,7 +2305,7 @@ def table(table_name): # 'acl update' group # -@acl.group() +@acl.group(cls=AbbreviationGroup) def update(): """ACL-related configuration tasks""" pass @@ -2301,7 +2339,7 @@ def incremental(file_name): # 'dropcounters' group ('config dropcounters ...') # -@config.group() +@config.group(cls=AbbreviationGroup) def dropcounters(): """Drop counter related configuration tasks""" pass @@ -2398,7 +2436,7 @@ def ecn(profile, rmax, rmin, ymax, ymin, gmax, gmin, verbose): # 'pfc' group ('config pfc ...') # -@interface.group() +@interface.group(cls=AbbreviationGroup) @click.pass_context def pfc(ctx): """Set PFC configuration.""" @@ -2426,7 +2464,7 @@ def asymmetric(ctx, interface_name, status): # 'platform' group ('config platform ...') # -@config.group() +@config.group(cls=AbbreviationGroup) def platform(): """Platform-related configuration tasks""" @@ -2434,7 +2472,7 @@ def platform(): platform.add_command(mlnx.mlnx) # 'firmware' subgroup ("config platform firmware ...") -@platform.group() +@platform.group(cls=AbbreviationGroup) def firmware(): """Firmware configuration tasks""" pass @@ -2479,12 +2517,12 @@ def update(args): # 'watermark' group ("show watermark telemetry interval") # -@config.group() +@config.group(cls=AbbreviationGroup) def watermark(): """Configure watermark """ pass -@watermark.group() +@watermark.group(cls=AbbreviationGroup) def telemetry(): """Configure watermark telemetry""" pass @@ -2501,7 +2539,7 @@ def interval(interval): # 'interface_naming_mode' subgroup ('config interface_naming_mode ...') # -@config.group('interface_naming_mode') +@config.group(cls=AbbreviationGroup, name='interface_naming_mode') def interface_naming_mode(): """Modify interface naming mode for interacting with SONiC CLI""" pass @@ -2516,7 +2554,7 @@ def naming_mode_alias(): """Set CLI interface naming mode to ALIAS (Vendor port alias)""" set_interface_naming_mode('alias') -@config.group() +@config.group(cls=AbbreviationGroup) def ztp(): """ Configure Zero Touch Provisioning """ if os.path.isfile('/usr/bin/ztp') is False: @@ -2554,7 +2592,7 @@ def enable(enable): # # 'syslog' group ('config syslog ...') # -@config.group('syslog') +@config.group(cls=AbbreviationGroup, name='syslog') @click.pass_context def syslog_group(ctx): """Syslog server configuration tasks""" @@ -2607,7 +2645,7 @@ def del_syslog_server(ctx, syslog_ip_address): # # 'ntp' group ('config ntp ...') # -@config.group() +@config.group(cls=AbbreviationGroup) @click.pass_context def ntp(ctx): """NTP server configuration tasks""" @@ -2660,7 +2698,7 @@ def del_ntp_server(ctx, ntp_ip_address): # # 'sflow' group ('config sflow ...') # -@config.group() +@config.group(cls=AbbreviationGroup) @click.pass_context def sflow(ctx): """sFlow-related configuration tasks""" @@ -2742,7 +2780,7 @@ def is_valid_sample_rate(rate): # # 'sflow interface' group # -@sflow.group() +@sflow.group(cls=AbbreviationGroup) @click.pass_context def interface(ctx): """Configure sFlow settings for an interface""" @@ -2817,7 +2855,7 @@ def sample_rate(ctx, ifname, rate): # # 'sflow collector' group # -@sflow.group() +@sflow.group(cls=AbbreviationGroup) @click.pass_context def collector(ctx): """Add/Delete a sFlow collector""" @@ -2885,7 +2923,7 @@ def del_collector(ctx, name): # # 'sflow agent-id' group # -@sflow.group('agent-id') +@sflow.group(cls=AbbreviationGroup, name='agent-id') @click.pass_context def agent_id(ctx): """Add/Delete a sFlow agent""" @@ -2957,7 +2995,7 @@ def feature_status(name, state): # # 'container' group ('config container ...') # -@config.group(name='container', invoke_without_command=False) +@config.group(cls=AbbreviationGroup, name='container', invoke_without_command=False) def container(): """Modify configuration of containers""" pass @@ -2965,7 +3003,7 @@ def container(): # # 'feature' group ('config container feature ...') # -@container.group(name='feature', invoke_without_command=False) +@container.group(cls=AbbreviationGroup, name='feature', invoke_without_command=False) def feature(): """Modify configuration of container features""" pass