diff options
| author | erkin <e.altunbas@vyos.io> | 2021-12-16 17:31:57 +0300 | 
|---|---|---|
| committer | erkin <e.altunbas@vyos.io> | 2021-12-16 17:31:57 +0300 | 
| commit | 95b91627a6065b720365c9ae7d124d85fc8e493d (patch) | |
| tree | 1d743211400347d3847623fb0e17dd39dbfb604c /src | |
| parent | dc8c230ad45a10be93fc2cd79c38ebb39fd0a148 (diff) | |
| parent | 5a871f0dac024c7c0c59f1a1709543c4cc5e4f96 (diff) | |
| download | vyos-1x-95b91627a6065b720365c9ae7d124d85fc8e493d.tar.gz vyos-1x-95b91627a6065b720365c9ae7d124d85fc8e493d.zip  | |
Merge branch 'current' of https://github.com/erkin/vyos-1x into current
Diffstat (limited to 'src')
35 files changed, 614 insertions, 154 deletions
diff --git a/src/conf_mode/containers.py b/src/conf_mode/containers.py index ab992e415..2e14e0b25 100755 --- a/src/conf_mode/containers.py +++ b/src/conf_mode/containers.py @@ -158,7 +158,7 @@ def verify(container):              v6_prefix = 0              # If ipv4-prefix not defined for user-defined network              if 'prefix' not in network_config: -                raise ConfigError(f'prefix for network "{net}" must be defined!') +                raise ConfigError(f'prefix for network "{network}" must be defined!')              for prefix in network_config['prefix']:                  if is_ipv4(prefix): v4_prefix += 1 diff --git a/src/conf_mode/http-api.py b/src/conf_mode/http-api.py index 7e4b117c8..4bfcbeb47 100755 --- a/src/conf_mode/http-api.py +++ b/src/conf_mode/http-api.py @@ -100,7 +100,7 @@ def apply(http_api):          call('systemctl stop vyos-http-api.service')      # Let uvicorn settle before restarting Nginx -    time.sleep(2) +    time.sleep(1)      cmd(f'{vyos_conf_scripts_dir}/https.py', raising=ConfigError) diff --git a/src/conf_mode/policy.py b/src/conf_mode/policy.py index 1a03d520b..e251396c7 100755 --- a/src/conf_mode/policy.py +++ b/src/conf_mode/policy.py @@ -171,9 +171,7 @@ def verify(policy):  def generate(policy):      if not policy: -        policy['new_frr_config'] = ''          return None -      policy['new_frr_config'] = render_to_string('frr/policy.frr.tmpl', policy)      return None @@ -190,8 +188,9 @@ def apply(policy):      frr_cfg.modify_section(r'^bgp community-list .*')      frr_cfg.modify_section(r'^bgp extcommunity-list .*')      frr_cfg.modify_section(r'^bgp large-community-list .*') -    frr_cfg.modify_section(r'^route-map .*') -    frr_cfg.add_before('^line vty', policy['new_frr_config']) +    frr_cfg.modify_section(r'^route-map .*', stop_pattern='^exit', remove_stop_mark=True) +    if 'new_frr_config' in policy: +        frr_cfg.add_before(frr.default_add_before, policy['new_frr_config'])      frr_cfg.commit_configuration(bgp_daemon)      # The route-map used for the FIB (zebra) is part of the zebra daemon @@ -200,19 +199,11 @@ def apply(policy):      frr_cfg.modify_section(r'^ipv6 access-list .*')      frr_cfg.modify_section(r'^ip prefix-list .*')      frr_cfg.modify_section(r'^ipv6 prefix-list .*') -    frr_cfg.modify_section(r'^route-map .*') -    frr_cfg.add_before('^line vty', policy['new_frr_config']) +    frr_cfg.modify_section(r'^route-map .*', stop_pattern='^exit', remove_stop_mark=True) +    if 'new_frr_config' in policy: +        frr_cfg.add_before(frr.default_add_before, policy['new_frr_config'])      frr_cfg.commit_configuration(zebra_daemon) -    # If FRR config is blank, rerun the blank commit x times due to frr-reload -    # behavior/bug not properly clearing out on one commit. -    if policy['new_frr_config'] == '': -        for a in range(5): -            frr_cfg.commit_configuration(zebra_daemon) - -    # Save configuration to /run/frr/config/frr.conf -    frr.save_configuration() -      return None  if __name__ == '__main__': diff --git a/src/conf_mode/protocols_bfd.py b/src/conf_mode/protocols_bfd.py index 539fd7b8e..94825ba10 100755 --- a/src/conf_mode/protocols_bfd.py +++ b/src/conf_mode/protocols_bfd.py @@ -16,8 +16,6 @@  import os -from sys import exit -  from vyos.config import Config  from vyos.configdict import dict_merge  from vyos.template import is_ipv6 @@ -36,7 +34,6 @@ def get_config(config=None):          conf = Config()      base = ['protocols', 'bfd']      bfd = conf.get_config_dict(base, get_first_key=True) -      # Bail out early if configuration tree does not exist      if not conf.exists(base):          return bfd @@ -89,18 +86,19 @@ def verify(bfd):  def generate(bfd):      if not bfd: -        bfd['new_frr_config'] = ''          return None -      bfd['new_frr_config'] = render_to_string('frr/bfdd.frr.tmpl', bfd)  def apply(bfd): +    bfd_daemon = 'bfdd' +      # Save original configuration prior to starting any commit actions      frr_cfg = frr.FRRConfig() -    frr_cfg.load_configuration() -    frr_cfg.modify_section('^bfd', '') -    frr_cfg.add_before(r'(ip prefix-list .*|route-map .*|line vty)', bfd['new_frr_config']) -    frr_cfg.commit_configuration() +    frr_cfg.load_configuration(bfd_daemon) +    frr_cfg.modify_section('^bfd', stop_pattern='^exit', remove_stop_mark=True) +    if 'new_frr_config' in bfd: +        frr_cfg.add_before(frr.default_add_before, bfd['new_frr_config']) +    frr_cfg.commit_configuration(bfd_daemon)      return None diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index 68284e0f9..b88f0c4ef 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -268,8 +268,6 @@ def verify(bgp):  def generate(bgp):      if not bgp or 'deleted' in bgp: -        bgp['frr_bgpd_config'] = '' -        bgp['frr_zebra_config'] = ''          return None      bgp['protocol'] = 'bgp' # required for frr/vrf.route-map.frr.tmpl @@ -287,8 +285,9 @@ def apply(bgp):      # The route-map used for the FIB (zebra) is part of the zebra daemon      frr_cfg.load_configuration(zebra_daemon) -    frr_cfg.modify_section(r'(\s+)?ip protocol bgp route-map [-a-zA-Z0-9.]+$', '', '(\s|!)') -    frr_cfg.add_before(r'(ip prefix-list .*|route-map .*|line vty)', bgp['frr_zebra_config']) +    frr_cfg.modify_section(r'(\s+)?ip protocol bgp route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)') +    if 'frr_zebra_config' in bgp: +        frr_cfg.add_before(frr.default_add_before, bgp['frr_zebra_config'])      frr_cfg.commit_configuration(zebra_daemon)      # Generate empty helper string which can be ammended to FRR commands, it @@ -298,13 +297,11 @@ def apply(bgp):          vrf = ' vrf ' + bgp['vrf']      frr_cfg.load_configuration(bgp_daemon) -    frr_cfg.modify_section(f'^router bgp \d+{vrf}$', '') -    frr_cfg.add_before(r'(ip prefix-list .*|route-map .*|line vty)', bgp['frr_bgpd_config']) +    frr_cfg.modify_section(f'^router bgp \d+{vrf}', stop_pattern='^exit', remove_stop_mark=True) +    if 'frr_bgpd_config' in bgp: +        frr_cfg.add_before(frr.default_add_before, bgp['frr_bgpd_config'])      frr_cfg.commit_configuration(bgp_daemon) -    # Save configuration to /run/frr/config/frr.conf -    frr.save_configuration() -      return None  if __name__ == '__main__': diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py index 4505e2496..9b4b215de 100755 --- a/src/conf_mode/protocols_isis.py +++ b/src/conf_mode/protocols_isis.py @@ -56,10 +56,10 @@ def get_config(config=None):      # instead of the VRF instance.      if vrf: isis['vrf'] = vrf -    # As we no re-use this Python handler for both VRF and non VRF instances for -    # IS-IS we need to find out if any interfaces changed so properly adjust -    # the FRR configuration and not by acctident change interfaces from a -    # different VRF. +    # FRR has VRF support for different routing daemons. As interfaces belong +    # to VRFs - or the global VRF, we need to check for changed interfaces so +    # that they will be properly rendered for the FRR config. Also this eases +    # removal of interfaces from the running configuration.      interfaces_removed = node_changed(conf, base + ['interface'])      if interfaces_removed:          isis['interface_removed'] = list(interfaces_removed) @@ -196,8 +196,6 @@ def verify(isis):  def generate(isis):      if not isis or 'deleted' in isis: -        isis['frr_isisd_config'] = '' -        isis['frr_zebra_config'] = ''          return None      isis['protocol'] = 'isis' # required for frr/vrf.route-map.frr.tmpl @@ -214,8 +212,9 @@ def apply(isis):      # The route-map used for the FIB (zebra) is part of the zebra daemon      frr_cfg.load_configuration(zebra_daemon) -    frr_cfg.modify_section(r'(\s+)?ip protocol isis route-map [-a-zA-Z0-9.]+$', '', '(\s|!)') -    frr_cfg.add_before(r'(ip prefix-list .*|route-map .*|line vty)', isis['frr_zebra_config']) +    frr_cfg.modify_section('(\s+)?ip protocol isis route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)') +    if 'frr_zebra_config' in isis: +        frr_cfg.add_before(frr.default_add_before, isis['frr_zebra_config'])      frr_cfg.commit_configuration(zebra_daemon)      # Generate empty helper string which can be ammended to FRR commands, it @@ -225,19 +224,18 @@ def apply(isis):          vrf = ' vrf ' + isis['vrf']      frr_cfg.load_configuration(isis_daemon) -    frr_cfg.modify_section(f'^router isis VyOS{vrf}$', '') +    frr_cfg.modify_section(f'^router isis VyOS{vrf}', stop_pattern='^exit', remove_stop_mark=True)      for key in ['interface', 'interface_removed']:          if key not in isis:              continue          for interface in isis[key]: -            frr_cfg.modify_section(f'^interface {interface}{vrf}$', '') +            frr_cfg.modify_section(f'^interface {interface}{vrf}', stop_pattern='^exit', remove_stop_mark=True) -    frr_cfg.add_before(r'(ip prefix-list .*|route-map .*|line vty)', isis['frr_isisd_config']) -    frr_cfg.commit_configuration(isis_daemon) +    if 'frr_isisd_config' in isis: +        frr_cfg.add_before(frr.default_add_before, isis['frr_isisd_config']) -    # Save configuration to /run/frr/config/frr.conf -    frr.save_configuration() +    frr_cfg.commit_configuration(isis_daemon)      return None diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py index 6ccda2e5a..4895cde6f 100755 --- a/src/conf_mode/protocols_ospf.py +++ b/src/conf_mode/protocols_ospf.py @@ -56,10 +56,10 @@ def get_config(config=None):      # instead of the VRF instance.      if vrf: ospf['vrf'] = vrf -    # As we no re-use this Python handler for both VRF and non VRF instances for -    # OSPF we need to find out if any interfaces changed so properly adjust -    # the FRR configuration and not by acctident change interfaces from a -    # different VRF. +    # FRR has VRF support for different routing daemons. As interfaces belong +    # to VRFs - or the global VRF, we need to check for changed interfaces so +    # that they will be properly rendered for the FRR config. Also this eases +    # removal of interfaces from the running configuration.      interfaces_removed = node_changed(conf, base + ['interface'])      if interfaces_removed:          ospf['interface_removed'] = list(interfaces_removed) @@ -177,11 +177,11 @@ def verify(ospf):                          raise ConfigError('Can not use OSPF interface area and area ' \                                            'network configuration at the same time!') -            if 'vrf' in ospf:              # If interface specific options are set, we must ensure that the              # interface is bound to our requesting VRF. Due to the VyOS              # priorities the interface is bound to the VRF after creation of              # the VRF itself, and before any routing protocol is configured. +            if 'vrf' in ospf:                  vrf = ospf['vrf']                  tmp = get_interface_config(interface)                  if 'master' not in tmp or tmp['master'] != vrf: @@ -191,8 +191,6 @@ def verify(ospf):  def generate(ospf):      if not ospf or 'deleted' in ospf: -        ospf['frr_ospfd_config'] = '' -        ospf['frr_zebra_config'] = ''          return None      ospf['protocol'] = 'ospf' # required for frr/vrf.route-map.frr.tmpl @@ -209,8 +207,9 @@ def apply(ospf):      # The route-map used for the FIB (zebra) is part of the zebra daemon      frr_cfg.load_configuration(zebra_daemon) -    frr_cfg.modify_section(r'(\s+)?ip protocol ospf route-map [-a-zA-Z0-9.]+$', '', '(\s|!)') -    frr_cfg.add_before(r'(ip prefix-list .*|route-map .*|line vty)', ospf['frr_zebra_config']) +    frr_cfg.modify_section('(\s+)?ip protocol ospf route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)') +    if 'frr_zebra_config' in ospf: +        frr_cfg.add_before(frr.default_add_before, ospf['frr_zebra_config'])      frr_cfg.commit_configuration(zebra_daemon)      # Generate empty helper string which can be ammended to FRR commands, it @@ -220,20 +219,18 @@ def apply(ospf):          vrf = ' vrf ' + ospf['vrf']      frr_cfg.load_configuration(ospf_daemon) -    frr_cfg.modify_section(f'^router ospf{vrf}$', '') +    frr_cfg.modify_section(f'^router ospf{vrf}', stop_pattern='^exit', remove_stop_mark=True)      for key in ['interface', 'interface_removed']:          if key not in ospf:              continue          for interface in ospf[key]: -            frr_cfg.modify_section(f'^interface {interface}{vrf}$', '') +            frr_cfg.modify_section(f'^interface {interface}{vrf}', stop_pattern='^exit', remove_stop_mark=True) -    frr_cfg.add_before(r'(ip prefix-list .*|route-map .*|line vty)', ospf['frr_ospfd_config']) +    if 'frr_ospfd_config' in ospf: +        frr_cfg.add_before(frr.default_add_before, ospf['frr_ospfd_config'])      frr_cfg.commit_configuration(ospf_daemon) -    # Save configuration to /run/frr/config/frr.conf -    frr.save_configuration() -      return None  if __name__ == '__main__': diff --git a/src/conf_mode/protocols_ospfv3.py b/src/conf_mode/protocols_ospfv3.py index 536ffa690..d0460b830 100755 --- a/src/conf_mode/protocols_ospfv3.py +++ b/src/conf_mode/protocols_ospfv3.py @@ -17,30 +17,53 @@  import os  from sys import exit +from sys import argv  from vyos.config import Config  from vyos.configdict import dict_merge +from vyos.configdict import node_changed  from vyos.configverify import verify_common_route_maps  from vyos.template import render_to_string  from vyos.ifconfig import Interface +from vyos.util import get_interface_config  from vyos.xml import defaults  from vyos import ConfigError  from vyos import frr  from vyos import airbag  airbag.enable() -frr_daemon = 'ospf6d' -  def get_config(config=None):      if config:          conf = config      else:          conf = Config() -    base = ['protocols', 'ospfv3'] + +    vrf = None +    if len(argv) > 1: +        vrf = argv[1] + +    base_path = ['protocols', 'ospfv3'] + +    # eqivalent of the C foo ? 'a' : 'b' statement +    base = vrf and ['vrf', 'name', vrf, 'protocols', 'ospfv3'] or base_path      ospfv3 = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) +    # Assign the name of our VRF context. This MUST be done before the return +    # statement below, else on deletion we will delete the default instance +    # instead of the VRF instance. +    if vrf: ospfv3['vrf'] = vrf + +    # FRR has VRF support for different routing daemons. As interfaces belong +    # to VRFs - or the global VRF, we need to check for changed interfaces so +    # that they will be properly rendered for the FRR config. Also this eases +    # removal of interfaces from the running configuration. +    interfaces_removed = node_changed(conf, base + ['interface']) +    if interfaces_removed: +        ospfv3['interface_removed'] = list(interfaces_removed) +      # Bail out early if configuration tree does not exist      if not conf.exists(base): +        ospfv3.update({'deleted' : ''})          return ospfv3      # We also need some additional information from the config, prefix-lists @@ -61,33 +84,56 @@ def verify(ospfv3):      verify_common_route_maps(ospfv3)      if 'interface' in ospfv3: -        for ifname, if_config in ospfv3['interface'].items(): -            if 'ifmtu' in if_config: -                mtu = Interface(ifname).get_mtu() -                if int(if_config['ifmtu']) > int(mtu): +        for interface, interface_config in ospfv3['interface'].items(): +            if 'ifmtu' in interface_config: +                mtu = Interface(interface).get_mtu() +                if int(interface_config['ifmtu']) > int(mtu):                      raise ConfigError(f'OSPFv3 ifmtu can not exceed physical MTU of "{mtu}"') +            # If interface specific options are set, we must ensure that the +            # interface is bound to our requesting VRF. Due to the VyOS +            # priorities the interface is bound to the VRF after creation of +            # the VRF itself, and before any routing protocol is configured. +            if 'vrf' in ospfv3: +                vrf = ospfv3['vrf'] +                tmp = get_interface_config(interface) +                if 'master' not in tmp or tmp['master'] != vrf: +                    raise ConfigError(f'Interface {interface} is not a member of VRF {vrf}!') +      return None  def generate(ospfv3): -    if not ospfv3: -        ospfv3['new_frr_config'] = '' +    if not ospfv3 or 'deleted' in ospfv3:          return None      ospfv3['new_frr_config'] = render_to_string('frr/ospf6d.frr.tmpl', ospfv3)      return None  def apply(ospfv3): +    ospf6_daemon = 'ospf6d' +      # Save original configuration prior to starting any commit actions      frr_cfg = frr.FRRConfig() -    frr_cfg.load_configuration(frr_daemon) -    frr_cfg.modify_section(r'^interface \S+', '') -    frr_cfg.modify_section('^router ospf6$', '') -    frr_cfg.add_before(r'(ip prefix-list .*|route-map .*|line vty)', ospfv3['new_frr_config']) -    frr_cfg.commit_configuration(frr_daemon) - -    # Save configuration to /run/frr/config/frr.conf -    frr.save_configuration() + +    # Generate empty helper string which can be ammended to FRR commands, it +    # will be either empty (default VRF) or contain the "vrf <name" statement +    vrf = '' +    if 'vrf' in ospfv3: +        vrf = ' vrf ' + ospfv3['vrf'] + +    frr_cfg.load_configuration(ospf6_daemon) +    frr_cfg.modify_section(f'^router ospf6{vrf}', stop_pattern='^exit', remove_stop_mark=True) + +    for key in ['interface', 'interface_removed']: +        if key not in ospfv3: +            continue +        for interface in ospfv3[key]: +            frr_cfg.modify_section(f'^interface {interface}{vrf}', stop_pattern='^exit', remove_stop_mark=True) + +    if 'new_frr_config' in ospfv3: +        frr_cfg.add_before(frr.default_add_before, ospfv3['new_frr_config']) + +    frr_cfg.commit_configuration(ospf6_daemon)      return None diff --git a/src/conf_mode/protocols_rip.py b/src/conf_mode/protocols_rip.py index 6b78f6f2d..300f56489 100755 --- a/src/conf_mode/protocols_rip.py +++ b/src/conf_mode/protocols_rip.py @@ -20,6 +20,7 @@ from sys import exit  from vyos.config import Config  from vyos.configdict import dict_merge +from vyos.configdict import node_changed  from vyos.configverify import verify_common_route_maps  from vyos.configverify import verify_access_list  from vyos.configverify import verify_prefix_list @@ -39,8 +40,17 @@ def get_config(config=None):      base = ['protocols', 'rip']      rip = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) +    # FRR has VRF support for different routing daemons. As interfaces belong +    # to VRFs - or the global VRF, we need to check for changed interfaces so +    # that they will be properly rendered for the FRR config. Also this eases +    # removal of interfaces from the running configuration. +    interfaces_removed = node_changed(conf, base + ['interface']) +    if interfaces_removed: +        rip['interface_removed'] = list(interfaces_removed) +      # Bail out early if configuration tree does not exist      if not conf.exists(base): +        rip.update({'deleted' : ''})          return rip      # We have gathered the dict representation of the CLI, but there are default @@ -89,12 +99,10 @@ def verify(rip):                                        f'with "split-horizon disable" for "{interface}"!')  def generate(rip): -    if not rip: -        rip['new_frr_config'] = '' +    if not rip or 'deleted' in rip:          return None      rip['new_frr_config'] = render_to_string('frr/ripd.frr.tmpl', rip) -      return None  def apply(rip): @@ -106,19 +114,22 @@ def apply(rip):      # The route-map used for the FIB (zebra) is part of the zebra daemon      frr_cfg.load_configuration(zebra_daemon) -    frr_cfg.modify_section(r'^ip protocol rip route-map [-a-zA-Z0-9.]+$', '') +    frr_cfg.modify_section('^ip protocol rip route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)')      frr_cfg.commit_configuration(zebra_daemon)      frr_cfg.load_configuration(rip_daemon) -    frr_cfg.modify_section(r'key chain \S+', '') -    frr_cfg.modify_section(r'interface \S+', '') -    frr_cfg.modify_section('^router rip$', '') +    frr_cfg.modify_section('^key chain \S+', stop_pattern='^exit', remove_stop_mark=True) +    frr_cfg.modify_section('^router rip', stop_pattern='^exit', remove_stop_mark=True) -    frr_cfg.add_before(r'(ip prefix-list .*|route-map .*|line vty)', rip['new_frr_config']) -    frr_cfg.commit_configuration(rip_daemon) +    for key in ['interface', 'interface_removed']: +        if key not in rip: +            continue +        for interface in rip[key]: +            frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True) -    # Save configuration to /run/frr/config/frr.conf -    frr.save_configuration() +    if 'new_frr_config' in rip: +        frr_cfg.add_before(frr.default_add_before, rip['new_frr_config']) +    frr_cfg.commit_configuration(rip_daemon)      return None diff --git a/src/conf_mode/protocols_ripng.py b/src/conf_mode/protocols_ripng.py index bc4954f63..d9b8c0b30 100755 --- a/src/conf_mode/protocols_ripng.py +++ b/src/conf_mode/protocols_ripng.py @@ -31,8 +31,6 @@ from vyos import frr  from vyos import airbag  airbag.enable() -frr_daemon = 'ripngd' -  def get_config(config=None):      if config:          conf = config @@ -99,17 +97,24 @@ def generate(ripng):      return None  def apply(ripng): +    ripng_daemon = 'ripngd' +    zebra_daemon = 'zebra' +      # Save original configuration prior to starting any commit actions      frr_cfg = frr.FRRConfig() -    frr_cfg.load_configuration(frr_daemon) -    frr_cfg.modify_section(r'key chain \S+', '') -    frr_cfg.modify_section(r'interface \S+', '') -    frr_cfg.modify_section('router ripng', '') -    frr_cfg.add_before(r'(ip prefix-list .*|route-map .*|line vty)', ripng['new_frr_config']) -    frr_cfg.commit_configuration(frr_daemon) - -    # Save configuration to /run/frr/config/frr.conf -    frr.save_configuration() + +    # The route-map used for the FIB (zebra) is part of the zebra daemon +    frr_cfg.load_configuration(zebra_daemon) +    frr_cfg.modify_section('^ipv6 protocol ripng route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)') +    frr_cfg.commit_configuration(zebra_daemon) + +    frr_cfg.load_configuration(ripng_daemon) +    frr_cfg.modify_section('key chain \S+', stop_pattern='^exit', remove_stop_mark=True) +    frr_cfg.modify_section('interface \S+', stop_pattern='^exit', remove_stop_mark=True) +    frr_cfg.modify_section('^router ripng', stop_pattern='^exit', remove_stop_mark=True) +    if 'new_frr_config' in ripng: +        frr_cfg.add_before(frr.default_add_before, ripng['new_frr_config']) +    frr_cfg.commit_configuration(ripng_daemon)      return None diff --git a/src/conf_mode/protocols_rpki.py b/src/conf_mode/protocols_rpki.py index 947c8ab7a..51ad0d315 100755 --- a/src/conf_mode/protocols_rpki.py +++ b/src/conf_mode/protocols_rpki.py @@ -28,8 +28,6 @@ from vyos import frr  from vyos import airbag  airbag.enable() -frr_daemon = 'bgpd' -  def get_config(config=None):      if config:          conf = config @@ -38,7 +36,9 @@ def get_config(config=None):      base = ['protocols', 'rpki']      rpki = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) +    # Bail out early if configuration tree does not exist      if not conf.exists(base): +        rpki.update({'deleted' : ''})          return rpki      # We have gathered the dict representation of the CLI, but there are default @@ -79,17 +79,22 @@ def verify(rpki):      return None  def generate(rpki): +    if not rpki: +        return      rpki['new_frr_config'] = render_to_string('frr/rpki.frr.tmpl', rpki)      return None  def apply(rpki): +    bgp_daemon = 'bgpd' +      # Save original configuration prior to starting any commit actions      frr_cfg = frr.FRRConfig() -    frr_cfg.load_configuration(frr_daemon) -    frr_cfg.modify_section('rpki', '') -    frr_cfg.add_before(r'(ip prefix-list .*|route-map .*|line vty)', rpki['new_frr_config']) -    frr_cfg.commit_configuration(frr_daemon) +    frr_cfg.load_configuration(bgp_daemon) +    frr_cfg.modify_section('^rpki', stop_pattern='^exit', remove_stop_mark=True) +    if 'new_frr_config' in rpki: +        frr_cfg.add_before(frr.default_add_before, rpki['new_frr_config']) +    frr_cfg.commit_configuration(bgp_daemon)      return None  if __name__ == '__main__': diff --git a/src/conf_mode/protocols_static.py b/src/conf_mode/protocols_static.py index f010141e9..c1e427b16 100755 --- a/src/conf_mode/protocols_static.py +++ b/src/conf_mode/protocols_static.py @@ -85,6 +85,8 @@ def verify(static):      return None  def generate(static): +    if not static: +        return None      static['new_frr_config'] = render_to_string('frr/staticd.frr.tmpl', static)      return None @@ -97,24 +99,21 @@ def apply(static):      # The route-map used for the FIB (zebra) is part of the zebra daemon      frr_cfg.load_configuration(zebra_daemon) -    frr_cfg.modify_section(r'^ip protocol static route-map [-a-zA-Z0-9.]+$', '') +    frr_cfg.modify_section(r'^ip protocol static route-map [-a-zA-Z0-9.]+', '')      frr_cfg.commit_configuration(zebra_daemon) -      frr_cfg.load_configuration(static_daemon)      if 'vrf' in static:          vrf = static['vrf'] -        frr_cfg.modify_section(f'^vrf {vrf}$', '') +        frr_cfg.modify_section(f'^vrf {vrf}', stop_pattern='^exit', remove_stop_mark=True)      else: -        frr_cfg.modify_section(r'^ip route .*', '') -        frr_cfg.modify_section(r'^ipv6 route .*', '') +        frr_cfg.modify_section(r'^ip route .*') +        frr_cfg.modify_section(r'^ipv6 route .*') -    frr_cfg.add_before(r'(interface .*|line vty)', static['new_frr_config']) +    if 'new_frr_config' in static: +        frr_cfg.add_before(frr.default_add_before, static['new_frr_config'])      frr_cfg.commit_configuration(static_daemon) -    # Save configuration to /run/frr/config/frr.conf -    frr.save_configuration() -      return None  if __name__ == '__main__': diff --git a/src/conf_mode/tftp_server.py b/src/conf_mode/tftp_server.py index 2409eec1f..ef726670c 100755 --- a/src/conf_mode/tftp_server.py +++ b/src/conf_mode/tftp_server.py @@ -24,6 +24,7 @@ from sys import exit  from vyos.config import Config  from vyos.configdict import dict_merge +from vyos.configverify import verify_vrf  from vyos.template import render  from vyos.template import is_ipv4  from vyos.util import call @@ -65,10 +66,11 @@ def verify(tftpd):      if 'listen_address' not in tftpd:          raise ConfigError('TFTP server listen address must be configured!') -    for address in tftpd['listen_address']: +    for address, address_config in tftpd['listen_address'].items():          if not is_addr_assigned(address):              print(f'WARNING: TFTP server listen address "{address}" not ' \                    'assigned to any interface!') +        verify_vrf(address_config)      return None @@ -83,7 +85,7 @@ def generate(tftpd):          return None      idx = 0 -    for address in tftpd['listen_address']: +    for address, address_config in tftpd['listen_address'].items():          config = deepcopy(tftpd)          port = tftpd['port']          if is_ipv4(address): @@ -91,6 +93,9 @@ def generate(tftpd):          else:              config['listen_address'] = f'[{address}]:{port} -6' +        if 'vrf' in address_config: +            config['vrf'] = address_config['vrf'] +          file = config_file + str(idx)          render(file, 'tftp-server/default.tmpl', config)          idx = idx + 1 diff --git a/src/conf_mode/vpn_openconnect.py b/src/conf_mode/vpn_openconnect.py index f6db196dc..51ea1f223 100755 --- a/src/conf_mode/vpn_openconnect.py +++ b/src/conf_mode/vpn_openconnect.py @@ -23,9 +23,11 @@ from vyos.pki import wrap_certificate  from vyos.pki import wrap_private_key  from vyos.template import render  from vyos.util import call +from vyos.util import is_systemd_service_running  from vyos.xml import defaults  from vyos import ConfigError  from crypt import crypt, mksalt, METHOD_SHA512 +from time import sleep  from vyos import airbag  airbag.enable() @@ -172,6 +174,16 @@ def apply(ocserv):                  os.unlink(file)      else:          call('systemctl restart ocserv.service') +        counter = 0 +        while True: +            # exit early when service runs +            if is_systemd_service_running("ocserv.service"): +                break +            sleep(0.250) +            if counter > 5: +                raise ConfigError('openconnect failed to start, check the logs for details') +                break +            counter += 1  if __name__ == '__main__': diff --git a/src/conf_mode/vrf_vni.py b/src/conf_mode/vrf_vni.py index 50d60f0dc..1a7bd1f09 100755 --- a/src/conf_mode/vrf_vni.py +++ b/src/conf_mode/vrf_vni.py @@ -47,13 +47,11 @@ def apply(vrf):      # add configuration to FRR      frr_cfg = frr.FRRConfig()      frr_cfg.load_configuration(frr_daemon) -    frr_cfg.modify_section(f'^vrf .+$', '') -    frr_cfg.add_before(r'(interface .*|line vty)', vrf['new_frr_config']) +    frr_cfg.modify_section(f'^vrf .+', stop_pattern='^exit-vrf', remove_stop_mark=True) +    if 'new_frr_config' in vrf: +        frr_cfg.add_before(frr.default_add_before, vrf['new_frr_config'])      frr_cfg.commit_configuration(frr_daemon) -    # Save configuration to /run/frr/config/frr.conf -    frr.save_configuration() -      return None  if __name__ == '__main__': diff --git a/src/migration-scripts/ospf/0-to-1 b/src/migration-scripts/ospf/0-to-1 new file mode 100755 index 000000000..678569d9e --- /dev/null +++ b/src/migration-scripts/ospf/0-to-1 @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +# T3753: upgrade to FRR8 and move CLI options to better fit with the new FRR CLI + +from sys import argv +from vyos.configtree import ConfigTree + +def ospf_passive_migration(config, ospf_base): +    if config.exists(ospf_base): +        if config.exists(ospf_base + ['passive-interface']): +            default = False +            for interface in config.return_values(ospf_base + ['passive-interface']): +                if interface == 'default': +                    default = True +                    continue +                config.set(ospf_base + ['interface', interface, 'passive']) + +            config.delete(ospf_base + ['passive-interface']) +            config.set(ospf_base + ['passive-interface'], value='default') + +        if config.exists(ospf_base + ['passive-interface-exclude']): +            for interface in config.return_values(ospf_base + ['passive-interface-exclude']): +                config.set(ospf_base + ['interface', interface, 'passive', 'disable']) +            config.delete(ospf_base + ['passive-interface-exclude']) + +if (len(argv) < 1): +    print("Must specify file name!") +    exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +config = ConfigTree(config_file) + +ospfv3_base = ['protocols', 'ospfv3'] +if config.exists(ospfv3_base): +    area_base = ospfv3_base + ['area'] +    if config.exists(area_base): +        for area in config.list_nodes(area_base): +            if not config.exists(area_base + [area, 'interface']): +                continue + +            for interface in config.return_values(area_base + [area, 'interface']): +                config.set(ospfv3_base + ['interface', interface, 'area'], value=area) +                config.set_tag(ospfv3_base + ['interface']) + +            config.delete(area_base + [area, 'interface']) + +# Migrate OSPF syntax in default VRF +ospf_base = ['protocols', 'ospf'] +ospf_passive_migration(config, ospf_base) + +vrf_base = ['vrf', 'name'] +if config.exists(vrf_base): +    for vrf in config.list_nodes(vrf_base): +        vrf_ospf_base = vrf_base + [vrf, 'protocols', 'ospf'] +        if config.exists(vrf_ospf_base): +            ospf_passive_migration(config, vrf_ospf_base) + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print(f'Failed to save the modified config: {e}') +    exit(1) diff --git a/src/op_mode/force_root-partition-auto-resize.sh b/src/op_mode/force_root-partition-auto-resize.sh index 4f13e3e03..b39e87560 100755 --- a/src/op_mode/force_root-partition-auto-resize.sh +++ b/src/op_mode/force_root-partition-auto-resize.sh @@ -44,7 +44,13 @@ fi  #  # Resize the partition and grow the filesystem.  # +# "print" and "Fix" directives were added to fix GPT table if it corrupted after virtual drive extension. +# If GPT table is corrupted we'll get Fix/Ignore dialogue after "print" command. +# "Fix" will be the answer for this dialogue.  +# If GPT table is fine and no auto-fix dialogue appeared the directive "Fix" simply will print parted utility help info.    parted -m ${ROOT_DEV} ---pretend-input-tty > /dev/null 2>&1 <<EOF  +print +Fix  resizepart  ${ROOT_PART_NUM}  Yes diff --git a/src/op_mode/lldp_op.py b/src/op_mode/lldp_op.py index 731e71891..b9ebc991a 100755 --- a/src/op_mode/lldp_op.py +++ b/src/op_mode/lldp_op.py @@ -55,6 +55,9 @@ def parse_data(data, interface):              if interface is not None and local_if != interface:                  continue              for chassis, c_value in values.get('chassis', {}).items(): +                # bail out early if no capabilities found +                if 'capability' not in c_value: +                    continue                  capabilities = c_value['capability']                  if isinstance(capabilities, dict):                      capabilities = [capabilities] diff --git a/src/op_mode/show_configuration_json.py b/src/op_mode/show_configuration_json.py new file mode 100755 index 000000000..fdece533b --- /dev/null +++ b/src/op_mode/show_configuration_json.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +import argparse +import json + +from vyos.configquery import ConfigTreeQuery + + +config = ConfigTreeQuery() +c = config.get_config_dict() + +parser = argparse.ArgumentParser() +parser.add_argument("-p", "--pretty", action="store_true", help="Show pretty configuration in JSON format") + + +if __name__ == '__main__': +    args = parser.parse_args() + +    if args.pretty: +        print(json.dumps(c, indent=4)) +    else: +        print(json.dumps(c)) diff --git a/src/services/api/graphql/README.graphql b/src/services/api/graphql/README.graphql index 29f58f709..a3c30b005 100644 --- a/src/services/api/graphql/README.graphql +++ b/src/services/api/graphql/README.graphql @@ -73,6 +73,20 @@ mutation {    }  } +Op-mode 'show' commands may be requested by path, e.g.: + +mutation { +  Show (data: {path: ["interfaces", "ethernet", "detail"]}) { +    success +    errors +    data { +      result +    } +  } +} + +N.B. to see the output the 'data' field 'result' must be present in the +request.  The GraphQL playground will be found at: @@ -97,15 +111,22 @@ services  │       │   └── schema  │       │       ├── config_file.graphql  │       │       ├── dhcp_server.graphql +│       │       ├── firewall_group.graphql  │       │       ├── interface_ethernet.graphql -│       │       └── schema.graphql +│       │       ├── schema.graphql +│       │       ├── show_config.graphql +│       │       └── show.graphql  │       ├── README.graphql  │       ├── recipes  │       │   ├── __init__.py +│       │   ├── remove_firewall_address_group_members.py  │       │   ├── session.py  │       │   └── templates  │       │       ├── create_dhcp_server.tmpl -│       │       └── create_interface_ethernet.tmpl +│       │       ├── create_firewall_address_group.tmpl +│       │       ├── create_interface_ethernet.tmpl +│       │       ├── remove_firewall_address_group_members.tmpl +│       │       └── update_firewall_address_group_members.tmpl  │       └── state.py  ├── vyos-configd  ├── vyos-hostsd diff --git a/src/services/api/graphql/graphql/directives.py b/src/services/api/graphql/graphql/directives.py index f5cd88acd..10bc522db 100644 --- a/src/services/api/graphql/graphql/directives.py +++ b/src/services/api/graphql/graphql/directives.py @@ -1,5 +1,5 @@  from ariadne import SchemaDirectiveVisitor, ObjectType -from . mutations import make_configure_resolver, make_config_file_resolver +from . mutations import *  def non(arg):      pass @@ -19,19 +19,45 @@ class VyosDirective(SchemaDirectiveVisitor):  class ConfigureDirective(VyosDirective):      """      Class providing implementation of 'configure' directive in schema. -      """      def visit_field_definition(self, field, object_type):          super().visit_field_definition(field, object_type,                                         make_resolver=make_configure_resolver) +class ShowConfigDirective(VyosDirective): +    """ +    Class providing implementation of 'show' directive in schema. +    """ +    def visit_field_definition(self, field, object_type): +        super().visit_field_definition(field, object_type, +                                       make_resolver=make_show_config_resolver) +  class ConfigFileDirective(VyosDirective):      """      Class providing implementation of 'configfile' directive in schema. -      """      def visit_field_definition(self, field, object_type):          super().visit_field_definition(field, object_type,                                         make_resolver=make_config_file_resolver) -directives_dict = {"configure": ConfigureDirective, "configfile": ConfigFileDirective} +class ShowDirective(VyosDirective): +    """ +    Class providing implementation of 'show' directive in schema. +    """ +    def visit_field_definition(self, field, object_type): +        super().visit_field_definition(field, object_type, +                                       make_resolver=make_show_resolver) + +class ImageDirective(VyosDirective): +    """ +    Class providing implementation of 'image' directive in schema. +    """ +    def visit_field_definition(self, field, object_type): +        super().visit_field_definition(field, object_type, +                                       make_resolver=make_image_resolver) + +directives_dict = {"configure": ConfigureDirective, +                   "showconfig": ShowConfigDirective, +                   "configfile": ConfigFileDirective, +                   "show": ShowDirective, +                   "image": ImageDirective} diff --git a/src/services/api/graphql/graphql/mutations.py b/src/services/api/graphql/graphql/mutations.py index 8a28b13d7..8e5aab56d 100644 --- a/src/services/api/graphql/graphql/mutations.py +++ b/src/services/api/graphql/graphql/mutations.py @@ -51,7 +51,8 @@ def make_resolver(mutation_name, class_name, session_func):                  klass = type(class_name, (Session,), {})              k = klass(session, data)              method = getattr(k, session_func) -            method() +            result = method() +            data['result'] = result              return {                  "success": True, @@ -69,6 +70,10 @@ def make_configure_resolver(mutation_name):      class_name = mutation_name      return make_resolver(mutation_name, class_name, 'configure') +def make_show_config_resolver(mutation_name): +    class_name = mutation_name +    return make_resolver(mutation_name, class_name, 'show_config') +  def make_config_file_resolver(mutation_name):      if 'Save' in mutation_name:          class_name = mutation_name.replace('Save', '', 1) @@ -78,3 +83,17 @@ def make_config_file_resolver(mutation_name):          return make_resolver(mutation_name, class_name, 'load')      else:          raise Exception + +def make_show_resolver(mutation_name): +    class_name = mutation_name +    return make_resolver(mutation_name, class_name, 'show') + +def make_image_resolver(mutation_name): +    if 'Add' in mutation_name: +        class_name = mutation_name.replace('Add', '', 1) +        return make_resolver(mutation_name, class_name, 'add') +    elif 'Delete' in mutation_name: +        class_name = mutation_name.replace('Delete', '', 1) +        return make_resolver(mutation_name, class_name, 'delete') +    else: +        raise Exception diff --git a/src/services/api/graphql/graphql/schema/firewall_group.graphql b/src/services/api/graphql/graphql/schema/firewall_group.graphql new file mode 100644 index 000000000..efe7de632 --- /dev/null +++ b/src/services/api/graphql/graphql/schema/firewall_group.graphql @@ -0,0 +1,47 @@ +input CreateFirewallAddressGroupInput { +    name: String! +    address: [String] +} + +type CreateFirewallAddressGroup { +    name: String! +    address: [String] +} + +type CreateFirewallAddressGroupResult { +    data: CreateFirewallAddressGroup +    success: Boolean! +    errors: [String] +} + +input UpdateFirewallAddressGroupMembersInput { +    name: String! +    address: [String!]! +} + +type UpdateFirewallAddressGroupMembers { +    name: String! +    address: [String!]! +} + +type UpdateFirewallAddressGroupMembersResult { +    data: UpdateFirewallAddressGroupMembers +    success: Boolean! +    errors: [String] +} + +input RemoveFirewallAddressGroupMembersInput { +    name: String! +    address: [String!]! +} + +type RemoveFirewallAddressGroupMembers { +    name: String! +    address: [String!]! +} + +type RemoveFirewallAddressGroupMembersResult { +    data: RemoveFirewallAddressGroupMembers +    success: Boolean! +    errors: [String] +} diff --git a/src/services/api/graphql/graphql/schema/image.graphql b/src/services/api/graphql/graphql/schema/image.graphql new file mode 100644 index 000000000..7d1b4f9d0 --- /dev/null +++ b/src/services/api/graphql/graphql/schema/image.graphql @@ -0,0 +1,29 @@ +input AddSystemImageInput { +    location: String! +} + +type AddSystemImage { +    location: String +    result: String +} + +type AddSystemImageResult { +    data: AddSystemImage +    success: Boolean! +    errors: [String] +} + +input DeleteSystemImageInput { +    name: String! +} + +type DeleteSystemImage { +    name: String +    result: String +} + +type DeleteSystemImageResult { +    data: DeleteSystemImage +    success: Boolean! +    errors: [String] +} diff --git a/src/services/api/graphql/graphql/schema/schema.graphql b/src/services/api/graphql/graphql/schema/schema.graphql index e34a4eadb..c6899bee6 100644 --- a/src/services/api/graphql/graphql/schema/schema.graphql +++ b/src/services/api/graphql/graphql/schema/schema.graphql @@ -9,10 +9,20 @@ type Query {  directive @configure on FIELD_DEFINITION  directive @configfile on FIELD_DEFINITION +directive @show on FIELD_DEFINITION +directive @showconfig on FIELD_DEFINITION +directive @image on FIELD_DEFINITION  type Mutation {      CreateDhcpServer(data: DhcpServerConfigInput) : CreateDhcpServerResult @configure      CreateInterfaceEthernet(data: InterfaceEthernetConfigInput) : CreateInterfaceEthernetResult @configure +    CreateFirewallAddressGroup(data: CreateFirewallAddressGroupInput) : CreateFirewallAddressGroupResult @configure +    UpdateFirewallAddressGroupMembers(data: UpdateFirewallAddressGroupMembersInput) : UpdateFirewallAddressGroupMembersResult @configure +    RemoveFirewallAddressGroupMembers(data: RemoveFirewallAddressGroupMembersInput) : RemoveFirewallAddressGroupMembersResult @configure      SaveConfigFile(data: SaveConfigFileInput) : SaveConfigFileResult @configfile      LoadConfigFile(data: LoadConfigFileInput) : LoadConfigFileResult @configfile +    Show(data: ShowInput) : ShowResult @show +    ShowConfig(data: ShowConfigInput) : ShowConfigResult @showconfig +    AddSystemImage(data: AddSystemImageInput) : AddSystemImageResult @image +    DeleteSystemImage(data: DeleteSystemImageInput) : DeleteSystemImageResult @image  } diff --git a/src/services/api/graphql/graphql/schema/show.graphql b/src/services/api/graphql/graphql/schema/show.graphql new file mode 100644 index 000000000..c7709e48b --- /dev/null +++ b/src/services/api/graphql/graphql/schema/show.graphql @@ -0,0 +1,14 @@ +input ShowInput { +    path: [String!]! +} + +type Show { +    path: [String] +    result: String +} + +type ShowResult { +    data: Show +    success: Boolean! +    errors: [String] +} diff --git a/src/services/api/graphql/graphql/schema/show_config.graphql b/src/services/api/graphql/graphql/schema/show_config.graphql new file mode 100644 index 000000000..34afd2aa9 --- /dev/null +++ b/src/services/api/graphql/graphql/schema/show_config.graphql @@ -0,0 +1,21 @@ +""" +Use 'scalar Generic' for show config output, to avoid attempts to +JSON-serialize in case of JSON output. +""" +scalar Generic + +input ShowConfigInput { +    path: [String!]! +    configFormat: String +} + +type ShowConfig { +    path: [String] +    result: Generic +} + +type ShowConfigResult { +    data: ShowConfig +    success: Boolean! +    errors: [String] +} diff --git a/src/services/api/graphql/recipes/remove_firewall_address_group_members.py b/src/services/api/graphql/recipes/remove_firewall_address_group_members.py new file mode 100644 index 000000000..cde30c27a --- /dev/null +++ b/src/services/api/graphql/recipes/remove_firewall_address_group_members.py @@ -0,0 +1,21 @@ + +from . session import Session + +class RemoveFirewallAddressGroupMembers(Session): +    def __init__(self, session, data): +        super().__init__(session, data) + +    # Define any custom processing of parameters here by overriding +    # configure: +    # +    # def configure(self): +    #     self._data = transform_data(self._data) +    #     super().configure() +    #     self.clean_up() + +    def configure(self): +        super().configure() + +        group_name = self._data['name'] +        path = ['firewall', 'group', 'address-group', group_name] +        self.delete_path_if_childless(path) diff --git a/src/services/api/graphql/recipes/session.py b/src/services/api/graphql/recipes/session.py index aa3932ab9..5ece78ee6 100644 --- a/src/services/api/graphql/recipes/session.py +++ b/src/services/api/graphql/recipes/session.py @@ -1,27 +1,26 @@ +import json +  from ariadne import convert_camel_case_to_snake +  import vyos.defaults +from vyos.config import Config +from vyos.configtree import ConfigTree  from vyos.template import render -class Session(object): +class Session: +    """ +    Wrapper for calling configsession functions based on GraphQL requests. +    Non-nullable fields in the respective schema allow avoiding a key check +    in 'data'. +    """      def __init__(self, session, data):          self._session = session -        self.data = data +        self._data = data          self._name = convert_camel_case_to_snake(type(self).__name__) -    @property -    def data(self): -        return self.__data - -    @data.setter -    def data(self, data): -        if isinstance(data, dict): -            self.__data = data -        else: -            raise ValueError("data must be of type dict") -      def configure(self):          session = self._session -        data = self.data +        data = self._data          func_base_name = self._name          tmpl_file = f'{func_base_name}.tmpl' @@ -46,9 +45,31 @@ class Session(object):          except Exception as error:              raise error +    def delete_path_if_childless(self, path): +        session = self._session +        config = Config(session.get_session_env()) +        if not config.list_nodes(path): +            session.delete(path) +            session.commit() + +    def show_config(self): +        session = self._session +        data = self._data +        out = '' + +        try: +            out = session.show_config(data['path']) +            if data.get('config_format', '') == 'json': +                config_tree = vyos.configtree.ConfigTree(out) +                out = json.loads(config_tree.to_json()) +        except Exception as error: +            raise error + +        return out +      def save(self):          session = self._session -        data = self.data +        data = self._data          if 'file_name' not in data or not data['file_name']:              data['file_name'] = '/config/config.boot' @@ -59,10 +80,44 @@ class Session(object):      def load(self):          session = self._session -        data = self.data +        data = self._data          try:              session.load_config(data['file_name'])              session.commit()          except Exception as error:              raise error + +    def show(self): +        session = self._session +        data = self._data +        out = '' + +        try: +            out = session.show(data['path']) +        except Exception as error: +            raise error + +        return out + +    def add(self): +        session = self._session +        data = self._data + +        try: +            res = session.install_image(data['location']) +        except Exception as error: +            raise error + +        return res + +    def delete(self): +        session = self._session +        data = self._data + +        try: +            res = session.remove_image(data['name']) +        except Exception as error: +            raise error + +        return res diff --git a/src/services/api/graphql/recipes/templates/create_firewall_address_group.tmpl b/src/services/api/graphql/recipes/templates/create_firewall_address_group.tmpl new file mode 100644 index 000000000..a890d0086 --- /dev/null +++ b/src/services/api/graphql/recipes/templates/create_firewall_address_group.tmpl @@ -0,0 +1,4 @@ +set firewall group address-group {{ name }} +{% for add in address %} +set firewall group address-group {{ name }} address {{ add }} +{% endfor %} diff --git a/src/services/api/graphql/recipes/templates/remove_firewall_address_group_members.tmpl b/src/services/api/graphql/recipes/templates/remove_firewall_address_group_members.tmpl new file mode 100644 index 000000000..458f3e5fc --- /dev/null +++ b/src/services/api/graphql/recipes/templates/remove_firewall_address_group_members.tmpl @@ -0,0 +1,3 @@ +{% for add in address %} +delete firewall group address-group {{ name }} address {{ add }} +{% endfor %} diff --git a/src/services/api/graphql/recipes/templates/update_firewall_address_group_members.tmpl b/src/services/api/graphql/recipes/templates/update_firewall_address_group_members.tmpl new file mode 100644 index 000000000..f56c61231 --- /dev/null +++ b/src/services/api/graphql/recipes/templates/update_firewall_address_group_members.tmpl @@ -0,0 +1,3 @@ +{% for add in address %} +set firewall group address-group {{ name }} address {{ add }} +{% endfor %} diff --git a/src/systemd/tftpd@.service b/src/systemd/tftpd@.service index 266bc0962..a674bf598 100644 --- a/src/systemd/tftpd@.service +++ b/src/systemd/tftpd@.service @@ -7,7 +7,7 @@ RequiresMountsFor=/run  Type=forking  #NotifyAccess=main  EnvironmentFile=-/etc/default/tftpd%I -ExecStart=/usr/sbin/in.tftpd "$DAEMON_ARGS" +ExecStart=/bin/sh -c "${VRF_ARGS} /usr/sbin/in.tftpd ${DAEMON_ARGS}"  Restart=on-failure  [Install] diff --git a/src/systemd/vyos-http-api.service b/src/systemd/vyos-http-api.service index ba5df5984..55370b356 100644 --- a/src/systemd/vyos-http-api.service +++ b/src/systemd/vyos-http-api.service @@ -1,10 +1,9 @@  [Unit]  Description=VyOS HTTP API service -After=auditd.service systemd-user-sessions.service time-sync.target vyos-router.service +After=vyos-router.service  Requires=vyos-router.service  [Service] -ExecStartPre=/usr/libexec/vyos/init/vyos-config  ExecStart=/usr/libexec/vyos/services/vyos-http-api-server  Type=idle @@ -18,6 +17,5 @@ User=root  Group=vyattacfg  [Install] -# Installing in a earlier target leaves ExecStartPre waiting -WantedBy=getty.target +WantedBy=vyos.target diff --git a/src/utils/vyos-hostsd-client b/src/utils/vyos-hostsd-client index d4d38315a..a0515951a 100755 --- a/src/utils/vyos-hostsd-client +++ b/src/utils/vyos-hostsd-client @@ -129,7 +129,8 @@ try:              params = h.split(",")              if len(params) < 2:                  raise ValueError("Malformed host entry") -            entry['address'] = params[1] +            # Address needs to be a list because of changes made in T2683 +            entry['address'] = [params[1]]              entry['aliases'] = params[2:]              data[params[0]] = entry          client.add_hosts({args.tag: data})  | 
