← Back to team overview

cloud-init-dev team mailing list archive

[Merge] ~raharper/cloud-init:feature/distro-net-renderer into cloud-init:master

 

Ryan Harper has proposed merging ~raharper/cloud-init:feature/distro-net-renderer into cloud-init:master.

Commit message:
sysconfig: refactor sysconfig to accept distro specific templates paths
    
Multiple distros use sysconfig format but have different content
and paths to certain files.  Update distros to specify these
template paths in their renderer_configs dictionary.

Requested reviews:
  cloud-init commiters (cloud-init-dev)

For more details, see:
https://code.launchpad.net/~raharper/cloud-init/+git/cloud-init/+merge/353740
-- 
Your team cloud-init commiters is requested to review the proposed merge of ~raharper/cloud-init:feature/distro-net-renderer into cloud-init:master.
diff --git a/cloudinit/cmd/devel/net_convert.py b/cloudinit/cmd/devel/net_convert.py
index 271dc5e..a0f58a0 100755
--- a/cloudinit/cmd/devel/net_convert.py
+++ b/cloudinit/cmd/devel/net_convert.py
@@ -10,6 +10,7 @@ import yaml
 from cloudinit.sources.helpers import openstack
 from cloudinit.sources import DataSourceAzure as azure
 
+from cloudinit import distros
 from cloudinit.net import eni, netplan, network_state, sysconfig
 from cloudinit import log
 
@@ -36,6 +37,11 @@ def get_parser(parser=None):
                         metavar="PATH",
                         help="directory to place output in",
                         required=True)
+    parser.add_argument("-D", "--distro",
+                        choices=[item for sublist in
+                                 distros.OSFAMILIES.values()
+                                 for item in sublist],
+                        required=True)
     parser.add_argument("-m", "--mac",
                         metavar="name,mac",
                         action='append',
@@ -96,14 +102,20 @@ def handle_args(name, args):
         sys.stderr.write('\n'.join([
             "", "Internal State",
             yaml.dump(ns, default_flow_style=False, indent=4), ""]))
+    distro_cls = distros.fetch(args.distro)
+    distro = distro_cls(args.distro, {}, None)
+    config = {}
     if args.output_kind == "eni":
         r_cls = eni.Renderer
+        config = distro.renderer_configs.get('eni')
     elif args.output_kind == "netplan":
         r_cls = netplan.Renderer
+        config = distro.renderer_configs.get('netplan')
     else:
         r_cls = sysconfig.Renderer
+        config = distro.renderer_configs.get('sysconfig')
 
-    r = r_cls()
+    r = r_cls(config=config)
     sys.stderr.write(''.join([
         "Read input format '%s' from '%s'.\n" % (
             args.kind, args.network_data.name),
diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py
index ab0b077..f0de906 100755
--- a/cloudinit/distros/__init__.py
+++ b/cloudinit/distros/__init__.py
@@ -91,7 +91,7 @@ class Distro(object):
         LOG.debug("Selected renderer '%s' from priority list: %s",
                   name, priority)
         renderer = render_cls(config=self.renderer_configs.get(name))
-        renderer.render_network_config(network_config=network_config)
+        renderer.render_network_config(network_config)
         return []
 
     def _find_tz_file(self, tz):
diff --git a/cloudinit/distros/opensuse.py b/cloudinit/distros/opensuse.py
index 9f90e95..1fe896a 100644
--- a/cloudinit/distros/opensuse.py
+++ b/cloudinit/distros/opensuse.py
@@ -28,13 +28,23 @@ class Distro(distros.Distro):
     hostname_conf_fn = '/etc/HOSTNAME'
     init_cmd = ['service']
     locale_conf_fn = '/etc/sysconfig/language'
-    network_conf_fn = '/etc/sysconfig/network'
+    network_conf_fn = '/etc/sysconfig/network/config'
     network_script_tpl = '/etc/sysconfig/network/ifcfg-%s'
     resolve_conf_fn = '/etc/resolv.conf'
     route_conf_tpl = '/etc/sysconfig/network/ifroute-%s'
     systemd_hostname_conf_fn = '/etc/hostname'
     systemd_locale_conf_fn = '/etc/locale.conf'
     tz_local_fn = '/etc/localtime'
+    renderer_configs = {
+        'sysconfig': {
+            'control': 'etc/sysconfig/network/config',
+            'iface_templates': '%(base)s/network/ifcfg-%(name)s',
+            'route_templates': {
+                'ipv4': '%(base)s/network/ifroute-%(name)s',
+                'ipv6': '%(base)s/network/ifroute-%(name)s',
+            }
+        }
+    }
 
     def __init__(self, name, cfg, paths):
         distros.Distro.__init__(self, name, cfg, paths)
@@ -208,6 +218,9 @@ class Distro(distros.Distro):
                                             nameservers, searchservers)
         return dev_names
 
+    def _write_network_config(self, netconfig):
+        return self._supported_write_network_config(netconfig)
+
     @property
     def preferred_ntp_clients(self):
         """The preferred ntp client is dependent on the version."""
diff --git a/cloudinit/distros/rhel.py b/cloudinit/distros/rhel.py
index 1fecb61..ff51343 100644
--- a/cloudinit/distros/rhel.py
+++ b/cloudinit/distros/rhel.py
@@ -39,6 +39,16 @@ class Distro(distros.Distro):
     resolve_conf_fn = "/etc/resolv.conf"
     tz_local_fn = "/etc/localtime"
     usr_lib_exec = "/usr/libexec"
+    renderer_configs = {
+        'sysconfig': {
+            'control': 'etc/sysconfig/network',
+            'iface_templates': '%(base)s/network-scripts/ifcfg-%(name)s',
+            'route_templates': {
+                'ipv4': '%(base)s/network-scripts/route-%(name)s',
+                'ipv6': '%(base)s/network-scripts/route6-%(name)s'
+            }
+        }
+    }
 
     def __init__(self, name, cfg, paths):
         distros.Distro.__init__(self, name, cfg, paths)
diff --git a/cloudinit/net/eni.py b/cloudinit/net/eni.py
index 80be242..c6f631a 100644
--- a/cloudinit/net/eni.py
+++ b/cloudinit/net/eni.py
@@ -480,7 +480,7 @@ class Renderer(renderer.Renderer):
 
         return '\n\n'.join(['\n'.join(s) for s in sections]) + "\n"
 
-    def render_network_state(self, network_state, target=None):
+    def render_network_state(self, network_state, templates=None, target=None):
         fpeni = util.target_path(target, self.eni_path)
         util.ensure_dir(os.path.dirname(fpeni))
         header = self.eni_header if self.eni_header else ""
diff --git a/cloudinit/net/netplan.py b/cloudinit/net/netplan.py
index 6352e78..bc1087f 100644
--- a/cloudinit/net/netplan.py
+++ b/cloudinit/net/netplan.py
@@ -189,7 +189,7 @@ class Renderer(renderer.Renderer):
         self._postcmds = config.get('postcmds', False)
         self.clean_default = config.get('clean_default', True)
 
-    def render_network_state(self, network_state, target):
+    def render_network_state(self, network_state, templates=None, target=None):
         # check network state for version
         # if v2, then extract network_state.config
         # else render_v2_from_state
diff --git a/cloudinit/net/renderer.py b/cloudinit/net/renderer.py
index 57652e2..5f32e90 100644
--- a/cloudinit/net/renderer.py
+++ b/cloudinit/net/renderer.py
@@ -45,11 +45,14 @@ class Renderer(object):
         return content.getvalue()
 
     @abc.abstractmethod
-    def render_network_state(self, network_state, target=None):
+    def render_network_state(self, network_state, templates=None,
+                             target=None):
         """Render network state."""
 
-    def render_network_config(self, network_config, target=None):
+    def render_network_config(self, network_config, templates=None,
+                              target=None):
         return self.render_network_state(
-            network_state=parse_net_config_data(network_config), target=target)
+            network_state=parse_net_config_data(network_config),
+            templates=templates, target=target)
 
 # vi: ts=4 expandtab
diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py
index 3d71923..e35f44e 100644
--- a/cloudinit/net/sysconfig.py
+++ b/cloudinit/net/sysconfig.py
@@ -91,19 +91,20 @@ class ConfigMap(object):
 class Route(ConfigMap):
     """Represents a route configuration."""
 
-    route_fn_tpl_ipv4 = '%(base)s/network-scripts/route-%(name)s'
-    route_fn_tpl_ipv6 = '%(base)s/network-scripts/route6-%(name)s'
-
-    def __init__(self, route_name, base_sysconf_dir):
+    def __init__(self, route_name, base_sysconf_dir,
+                 ipv4_tpl, ipv6_tpl):
         super(Route, self).__init__()
         self.last_idx = 1
         self.has_set_default_ipv4 = False
         self.has_set_default_ipv6 = False
         self._route_name = route_name
         self._base_sysconf_dir = base_sysconf_dir
+        self.route_fn_tpl_ipv4 = ipv4_tpl
+        self.route_fn_tpl_ipv6 = ipv6_tpl
 
     def copy(self):
-        r = Route(self._route_name, self._base_sysconf_dir)
+        r = Route(self._route_name, self._base_sysconf_dir,
+                  self.route_fn_tpl_ipv4, self.route_fn_tpl_ipv6)
         r._conf = self._conf.copy()
         r.last_idx = self.last_idx
         r.has_set_default_ipv4 = self.has_set_default_ipv4
@@ -169,18 +170,22 @@ class Route(ConfigMap):
 class NetInterface(ConfigMap):
     """Represents a sysconfig/networking-script (and its config + children)."""
 
-    iface_fn_tpl = '%(base)s/network-scripts/ifcfg-%(name)s'
-
     iface_types = {
         'ethernet': 'Ethernet',
         'bond': 'Bond',
         'bridge': 'Bridge',
     }
 
-    def __init__(self, iface_name, base_sysconf_dir, kind='ethernet'):
+    def __init__(self, iface_name, base_sysconf_dir, templates,
+                 kind='ethernet'):
         super(NetInterface, self).__init__()
         self.children = []
-        self.routes = Route(iface_name, base_sysconf_dir)
+        self.templates = templates
+        route_tpl = self.templates.get('route_templates')
+        self.routes = Route(iface_name, base_sysconf_dir,
+                            ipv4_tpl=route_tpl.get('ipv4'),
+                            ipv6_tpl=route_tpl.get('ipv6'))
+        self.iface_fn_tpl = self.templates.get('iface_templates')
         self.kind = kind
 
         self._iface_name = iface_name
@@ -213,7 +218,8 @@ class NetInterface(ConfigMap):
                                      'name': self.name})
 
     def copy(self, copy_children=False, copy_routes=False):
-        c = NetInterface(self.name, self._base_sysconf_dir, kind=self._kind)
+        c = NetInterface(self.name, self._base_sysconf_dir,
+                         self.templates, kind=self._kind)
         c._conf = self._conf.copy()
         if copy_children:
             c.children = list(self.children)
@@ -251,6 +257,8 @@ class Renderer(renderer.Renderer):
         ('bridge_bridgeprio', 'PRIO'),
     ])
 
+    templates = {}
+
     def __init__(self, config=None):
         if not config:
             config = {}
@@ -261,6 +269,11 @@ class Renderer(renderer.Renderer):
         nm_conf_path = 'etc/NetworkManager/conf.d/99-cloud-init.conf'
         self.networkmanager_conf_path = config.get('networkmanager_conf_path',
                                                    nm_conf_path)
+        self.templates = {
+            'control': config.get('control'),
+            'iface_templates': config.get('iface_templates'),
+            'route_templates': config.get('route_templates'),
+        }
 
     @classmethod
     def _render_iface_shared(cls, iface, iface_cfg):
@@ -512,7 +525,7 @@ class Renderer(renderer.Renderer):
         return content_str
 
     @staticmethod
-    def _render_networkmanager_conf(network_state):
+    def _render_networkmanager_conf(network_state, templates=None):
         content = networkmanager_conf.NetworkManagerConf("")
 
         # If DNS server information is provided, configure
@@ -556,14 +569,17 @@ class Renderer(renderer.Renderer):
             cls._render_subnet_routes(iface_cfg, route_cfg, iface_subnets)
 
     @classmethod
-    def _render_sysconfig(cls, base_sysconf_dir, network_state):
+    def _render_sysconfig(cls, base_sysconf_dir, network_state,
+                          templates=None):
         '''Given state, return /etc/sysconfig files + contents'''
+        if not templates:
+            templates = cls.templates
         iface_contents = {}
         for iface in network_state.iter_interfaces():
             if iface['type'] == "loopback":
                 continue
             iface_name = iface['name']
-            iface_cfg = NetInterface(iface_name, base_sysconf_dir)
+            iface_cfg = NetInterface(iface_name, base_sysconf_dir, templates)
             cls._render_iface_shared(iface, iface_cfg)
             iface_contents[iface_name] = iface_cfg
         cls._render_physical_interfaces(network_state, iface_contents)
@@ -584,11 +600,14 @@ class Renderer(renderer.Renderer):
                     iface_cfg.routes.to_string("ipv6")
         return contents
 
-    def render_network_state(self, network_state, target=None):
+    def render_network_state(self, network_state, templates=None, target=None):
+        if not templates:
+            templates = self.templates
         file_mode = 0o644
         base_sysconf_dir = util.target_path(target, self.sysconf_dir)
         for path, data in self._render_sysconfig(base_sysconf_dir,
-                                                 network_state).items():
+                                                 network_state,
+                                                 templates=templates).items():
             util.write_file(path, data, file_mode)
         if self.dns_path:
             dns_path = util.target_path(target, self.dns_path)
@@ -598,7 +617,8 @@ class Renderer(renderer.Renderer):
         if self.networkmanager_conf_path:
             nm_conf_path = util.target_path(target,
                                             self.networkmanager_conf_path)
-            nm_conf_content = self._render_networkmanager_conf(network_state)
+            nm_conf_content = self._render_networkmanager_conf(network_state,
+                                                               templates)
             if nm_conf_content:
                 util.write_file(nm_conf_path, nm_conf_content, file_mode)
         if self.netrules_path:
@@ -606,13 +626,15 @@ class Renderer(renderer.Renderer):
             netrules_path = util.target_path(target, self.netrules_path)
             util.write_file(netrules_path, netrules_content, file_mode)
 
-        # always write /etc/sysconfig/network configuration
-        sysconfig_path = util.target_path(target, "etc/sysconfig/network")
-        netcfg = [_make_header(), 'NETWORKING=yes']
-        if network_state.use_ipv6:
-            netcfg.append('NETWORKING_IPV6=yes')
-            netcfg.append('IPV6_AUTOCONF=no')
-        util.write_file(sysconfig_path, "\n".join(netcfg) + "\n", file_mode)
+        sysconfig_path = util.target_path(target, templates.get('control'))
+        if sysconfig_path.endswith('network'):
+            util.ensure_dir(os.path.dirname(sysconfig_path))
+            netcfg = [_make_header(), 'NETWORKING=yes']
+            if network_state.use_ipv6:
+                netcfg.append('NETWORKING_IPV6=yes')
+                netcfg.append('IPV6_AUTOCONF=no')
+            util.write_file(sysconfig_path,
+                            "\n".join(netcfg) + "\n", file_mode)
 
 
 def available(target=None):
diff --git a/tests/unittests/test_datasource/test_configdrive.py b/tests/unittests/test_datasource/test_configdrive.py
index 68400f2..7e6fcbb 100644
--- a/tests/unittests/test_datasource/test_configdrive.py
+++ b/tests/unittests/test_datasource/test_configdrive.py
@@ -642,7 +642,7 @@ class TestConvertNetworkData(CiTestCase):
             routes)
         eni_renderer = eni.Renderer()
         eni_renderer.render_network_state(
-            network_state.parse_net_config_data(ncfg), self.tmp)
+            network_state.parse_net_config_data(ncfg), target=self.tmp)
         with open(os.path.join(self.tmp, "etc",
                                "network", "interfaces"), 'r') as f:
             eni_rendering = f.read()
@@ -664,7 +664,7 @@ class TestConvertNetworkData(CiTestCase):
         eni_renderer = eni.Renderer()
 
         eni_renderer.render_network_state(
-            network_state.parse_net_config_data(ncfg), self.tmp)
+            network_state.parse_net_config_data(ncfg), target=self.tmp)
         with open(os.path.join(self.tmp, "etc",
                                "network", "interfaces"), 'r') as f:
             eni_rendering = f.read()
@@ -695,7 +695,7 @@ class TestConvertNetworkData(CiTestCase):
                                           known_macs=KNOWN_MACS)
         eni_renderer = eni.Renderer()
         eni_renderer.render_network_state(
-            network_state.parse_net_config_data(ncfg), self.tmp)
+            network_state.parse_net_config_data(ncfg), target=self.tmp)
         with open(os.path.join(self.tmp, "etc",
                                "network", "interfaces"), 'r') as f:
             eni_rendering = f.read()
diff --git a/tests/unittests/test_distros/test_netconfig.py b/tests/unittests/test_distros/test_netconfig.py
index 7765e40..d1813ec 100644
--- a/tests/unittests/test_distros/test_netconfig.py
+++ b/tests/unittests/test_distros/test_netconfig.py
@@ -467,6 +467,8 @@ NETWORKING=yes
                 mock.patch.object(util, 'load_file', return_value=''))
             mocks.enter_context(
                 mock.patch.object(os.path, 'isfile', return_value=True))
+            mocks.enter_context(
+                mock.patch.object(util, 'ensure_dir', return_value=True))
 
             rh_distro.apply_network_config(V1_NET_CFG, False)
 
@@ -619,6 +621,8 @@ IPV6_AUTOCONF=no
                 mock.patch.object(util, 'load_file', return_value=''))
             mocks.enter_context(
                 mock.patch.object(os.path, 'isfile', return_value=True))
+            mocks.enter_context(
+                mock.patch.object(util, 'ensure_dir', return_value=True))
 
             rh_distro.apply_network_config(V1_NET_CFG_IPV6, False)
 
diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
index 58e5ea1..cb5c861 100644
--- a/tests/unittests/test_net.py
+++ b/tests/unittests/test_net.py
@@ -1,6 +1,7 @@
 # This file is part of cloud-init. See LICENSE file for license information.
 
 from cloudinit import net
+from cloudinit import distros
 from cloudinit.net import cmdline
 from cloudinit.net import (
     eni, interface_has_own_mac, natural_sort_key, netplan, network_state,
@@ -1527,7 +1528,7 @@ class TestGenerateFallbackConfig(CiTestCase):
         # don't set rulepath so eni writes them
         renderer = eni.Renderer(
             {'eni_path': 'interfaces', 'netrules_path': 'netrules'})
-        renderer.render_network_state(ns, render_dir)
+        renderer.render_network_state(ns, target=render_dir)
 
         self.assertTrue(os.path.exists(os.path.join(render_dir,
                                                     'interfaces')))
@@ -1591,7 +1592,7 @@ iface eth0 inet dhcp
         # don't set rulepath so eni writes them
         renderer = eni.Renderer(
             {'eni_path': 'interfaces', 'netrules_path': 'netrules'})
-        renderer.render_network_state(ns, render_dir)
+        renderer.render_network_state(ns, target=render_dir)
 
         self.assertTrue(os.path.exists(os.path.join(render_dir,
                                                     'interfaces')))
@@ -1682,7 +1683,7 @@ iface eth1 inet dhcp
         self.assertEqual(0, mock_settle.call_count)
 
 
-class TestSysConfigRendering(CiTestCase):
+class TestRhelSysConfigRendering(CiTestCase):
 
     with_logs = True
 
@@ -1690,6 +1691,11 @@ class TestSysConfigRendering(CiTestCase):
     header = ('# Created by cloud-init on instance boot automatically, '
               'do not edit.\n#\n')
 
+    def _get_renderer(self):
+        distro_cls = distros.fetch('rhel')
+        return sysconfig.Renderer(
+            config=distro_cls.renderer_configs.get('sysconfig'))
+
     def _render_and_read(self, network_config=None, state=None, dir=None):
         if dir is None:
             dir = self.tmp_dir()
@@ -1701,8 +1707,8 @@ class TestSysConfigRendering(CiTestCase):
         else:
             raise ValueError("Expected data or state, got neither")
 
-        renderer = sysconfig.Renderer()
-        renderer.render_network_state(ns, dir)
+        renderer = self._get_renderer()
+        renderer.render_network_state(ns, target=dir)
         return dir2dict(dir)
 
     def _compare_files_to_expected(self, expected, found):
@@ -1745,8 +1751,8 @@ class TestSysConfigRendering(CiTestCase):
         render_dir = os.path.join(tmp_dir, "render")
         os.makedirs(render_dir)
 
-        renderer = sysconfig.Renderer()
-        renderer.render_network_state(ns, render_dir)
+        renderer = self._get_renderer()
+        renderer.render_network_state(ns, target=render_dir)
 
         render_file = 'etc/sysconfig/network-scripts/ifcfg-eth1000'
         with open(os.path.join(render_dir, render_file)) as fh:
@@ -1797,9 +1803,9 @@ USERCTL=no
         network_cfg = openstack.convert_net_json(net_json, known_macs=macs)
         ns = network_state.parse_net_config_data(network_cfg,
                                                  skip_broken=False)
-        renderer = sysconfig.Renderer()
+        renderer = self._get_renderer()
         with self.assertRaises(ValueError):
-            renderer.render_network_state(ns, render_dir)
+            renderer.render_network_state(ns, target=render_dir)
         self.assertEqual([], os.listdir(render_dir))
 
     def test_multiple_ipv6_default_gateways(self):
@@ -1835,9 +1841,9 @@ USERCTL=no
         network_cfg = openstack.convert_net_json(net_json, known_macs=macs)
         ns = network_state.parse_net_config_data(network_cfg,
                                                  skip_broken=False)
-        renderer = sysconfig.Renderer()
+        renderer = self._get_renderer()
         with self.assertRaises(ValueError):
-            renderer.render_network_state(ns, render_dir)
+            renderer.render_network_state(ns, target=render_dir)
         self.assertEqual([], os.listdir(render_dir))
 
     def test_openstack_rendering_samples(self):
@@ -1849,11 +1855,11 @@ USERCTL=no
                 ex_input, known_macs=ex_mac_addrs)
             ns = network_state.parse_net_config_data(network_cfg,
                                                      skip_broken=False)
-            renderer = sysconfig.Renderer()
+            renderer = self._get_renderer()
             # render a multiple times to simulate reboots
-            renderer.render_network_state(ns, render_dir)
-            renderer.render_network_state(ns, render_dir)
-            renderer.render_network_state(ns, render_dir)
+            renderer.render_network_state(ns, target=render_dir)
+            renderer.render_network_state(ns, target=render_dir)
+            renderer.render_network_state(ns, target=render_dir)
             for fn, expected_content in os_sample.get('out_sysconfig', []):
                 with open(os.path.join(render_dir, fn)) as fh:
                     self.assertEqual(expected_content, fh.read())
@@ -1862,8 +1868,8 @@ USERCTL=no
         ns = network_state.parse_net_config_data(CONFIG_V1_SIMPLE_SUBNET)
         render_dir = self.tmp_path("render")
         os.makedirs(render_dir)
-        renderer = sysconfig.Renderer()
-        renderer.render_network_state(ns, render_dir)
+        renderer = self._get_renderer()
+        renderer.render_network_state(ns, target=render_dir)
         found = dir2dict(render_dir)
         nspath = '/etc/sysconfig/network-scripts/'
         self.assertNotIn(nspath + 'ifcfg-lo', found.keys())
@@ -1888,8 +1894,8 @@ USERCTL=no
         ns = network_state.parse_net_config_data(CONFIG_V1_EXPLICIT_LOOPBACK)
         render_dir = self.tmp_path("render")
         os.makedirs(render_dir)
-        renderer = sysconfig.Renderer()
-        renderer.render_network_state(ns, render_dir)
+        renderer = self._get_renderer()
+        renderer.render_network_state(ns, target=render_dir)
         found = dir2dict(render_dir)
         nspath = '/etc/sysconfig/network-scripts/'
         self.assertNotIn(nspath + 'ifcfg-lo', found.keys())
@@ -1982,7 +1988,7 @@ class TestEniNetRendering(CiTestCase):
 
         renderer = eni.Renderer(
             {'eni_path': 'interfaces', 'netrules_path': None})
-        renderer.render_network_state(ns, render_dir)
+        renderer.render_network_state(ns, target=render_dir)
 
         self.assertTrue(os.path.exists(os.path.join(render_dir,
                                                     'interfaces')))
@@ -2002,7 +2008,7 @@ iface eth1000 inet dhcp
         tmp_dir = self.tmp_dir()
         ns = network_state.parse_net_config_data(CONFIG_V1_EXPLICIT_LOOPBACK)
         renderer = eni.Renderer()
-        renderer.render_network_state(ns, tmp_dir)
+        renderer.render_network_state(ns, target=tmp_dir)
         expected = """\
 auto lo
 iface lo inet loopback
@@ -2038,7 +2044,7 @@ class TestNetplanNetRendering(CiTestCase):
         render_target = 'netplan.yaml'
         renderer = netplan.Renderer(
             {'netplan_path': render_target, 'postcmds': False})
-        renderer.render_network_state(ns, render_dir)
+        renderer.render_network_state(ns, target=render_dir)
 
         self.assertTrue(os.path.exists(os.path.join(render_dir,
                                                     render_target)))
@@ -2143,7 +2149,7 @@ class TestNetplanPostcommands(CiTestCase):
         render_target = 'netplan.yaml'
         renderer = netplan.Renderer(
             {'netplan_path': render_target, 'postcmds': True})
-        renderer.render_network_state(ns, render_dir)
+        renderer.render_network_state(ns, target=render_dir)
 
         mock_netplan_generate.assert_called_with(run=True)
         mock_net_setup_link.assert_called_with(run=True)
@@ -2168,7 +2174,7 @@ class TestNetplanPostcommands(CiTestCase):
                        '/sys/class/net/lo'], capture=True),
         ]
         with mock.patch.object(os.path, 'islink', return_value=True):
-            renderer.render_network_state(ns, render_dir)
+            renderer.render_network_state(ns, target=render_dir)
             mock_subp.assert_has_calls(expected)
 
 
@@ -2363,7 +2369,7 @@ class TestNetplanRoundTrip(CiTestCase):
         renderer = netplan.Renderer(
             config={'netplan_path': netplan_path})
 
-        renderer.render_network_state(ns, target)
+        renderer.render_network_state(ns, target=target)
         return dir2dict(target)
 
     def testsimple_render_bond_netplan(self):
@@ -2453,7 +2459,7 @@ class TestEniRoundTrip(CiTestCase):
         renderer = eni.Renderer(
             config={'eni_path': eni_path, 'netrules_path': netrules_path})
 
-        renderer.render_network_state(ns, dir)
+        renderer.render_network_state(ns, target=dir)
         return dir2dict(dir)
 
     def testsimple_convert_and_render(self):

Follow ups