diff --git a/tripleoclient/constants.py b/tripleoclient/constants.py
index 44ebcfe3d..2f309e5ff 100644
--- a/tripleoclient/constants.py
+++ b/tripleoclient/constants.py
@@ -150,6 +150,7 @@ VALIDATION_GROUPS_INFO = (
         )
 
 # ctlplane network defaults
+CTLPLANE_NET_NAME = 'ctlplane'
 CTLPLANE_CIDR_DEFAULT = '192.168.24.0/24'
 CTLPLANE_DHCP_START_DEFAULT = ['192.168.24.5']
 CTLPLANE_DHCP_END_DEFAULT = ['192.168.24.24']
diff --git a/tripleoclient/tests/v1/overcloud_node/test_overcloud_node.py b/tripleoclient/tests/v1/overcloud_node/test_overcloud_node.py
index 9b71d607a..8565e08f2 100644
--- a/tripleoclient/tests/v1/overcloud_node/test_overcloud_node.py
+++ b/tripleoclient/tests/v1/overcloud_node/test_overcloud_node.py
@@ -760,18 +760,35 @@ class TestExtractProvisionedNode(test_utils.TestCommand):
         self.baremetal = mock.Mock()
         self.app.client_manager.baremetal = self.baremetal
 
+        self.network = mock.Mock()
+        self.app.client_manager.network = self.network
+
         self.cmd = overcloud_node.ExtractProvisionedNode(self.app, None)
 
         self.extract_file = tempfile.NamedTemporaryFile(
             mode='w', delete=False, suffix='.yaml')
         self.extract_file.close()
+
+        roles_data = [
+            {'name': 'Controller',
+             'default_route_networks': ['External'],
+             'networks_skip_config': ['Tenant']},
+            {'name': 'Compute'}
+        ]
+        self.roles_file = tempfile.NamedTemporaryFile(
+            mode='w', delete=False, suffix='.yaml')
+        self.roles_file.write(yaml.safe_dump(roles_data))
+        self.roles_file.close()
         self.addCleanup(os.unlink, self.extract_file.name)
+        self.addCleanup(os.unlink, self.roles_file.name)
 
     def test_extract(self):
         stack_dict = {
             'parameters': {
                 'ComputeHostnameFormat': '%stackname%-novacompute-%index%',
-                'ControllerHostnameFormat': '%stackname%-controller-%index%'
+                'ControllerHostnameFormat': '%stackname%-controller-%index%',
+                'ComputeNetworkConfigTemplate': 'templates/compute.j2',
+                'ControllerNetworkConfigTemplate': 'templates/controller.j2'
             },
             'outputs': [{
                 'output_key': 'AnsibleHostVarsMap',
@@ -785,6 +802,25 @@ class TestExtractProvisionedNode(test_utils.TestCommand):
                         'overcloud-controller-2'
                     ],
                 }
+            }, {
+                'output_key': 'RoleNetIpMap',
+                'output_value': {
+                    'Compute': {
+                        'ctlplane': ['192.168.26.11'],
+                        'internal_api': ['172.17.1.23'],
+                    },
+                    'Controller': {
+                        'ctlplane': ['192.168.25.21',
+                                     '192.168.25.25',
+                                     '192.168.25.28'],
+                        'external': ['10.0.0.199',
+                                     '10.0.0.197',
+                                     '10.0.0.191'],
+                        'internal_api': ['172.17.0.37',
+                                         '172.17.0.33',
+                                         '172.17.0.39'],
+                    }
+                }
             }]
         }
         stack = mock.Mock()
@@ -809,9 +845,78 @@ class TestExtractProvisionedNode(test_utils.TestCommand):
 
         self.baremetal.node.list.return_value = nodes
 
-        argslist = ['--output', self.extract_file.name, '--yes']
+        networks = [
+            mock.Mock(),  # ctlplane
+            mock.Mock(),  # external
+            mock.Mock(),  # internal_api
+        ]
+        ctlplane_net = networks[0]
+        external_net = networks[1]
+        internal_api_net = networks[2]
+
+        ctlplane_net.id = 'ctlplane_id'
+        ctlplane_net.name = 'ctlplane'
+        ctlplane_net.subnet_ids = ['ctlplane_a_id',
+                                   'ctlplane_b_id']
+        external_net.id = 'external_id'
+        external_net.name = 'external'
+        external_net.subnet_ids = ['external_a_id']
+        internal_api_net.id = 'internal_api_id'
+        internal_api_net.name = 'internal_api'
+        internal_api_net.subnet_ids = ['internal_api_a_id',
+                                       'internal_api_b_id']
+
+        subnets = [
+            mock.Mock(),  # ctlplane_a
+            mock.Mock(),  # ctlplane_b
+            mock.Mock(),  # external_a
+            mock.Mock(),  # internal_api_a
+            mock.Mock(),  # internal_api_b
+        ]
+        ctlplane_a = subnets[0]
+        ctlplane_b = subnets[1]
+        external_a = subnets[2]
+        int_api_a = subnets[3]
+        int_api_b = subnets[4]
+
+        ctlplane_a.id = 'ctlplane_a_id'
+        ctlplane_a.name = 'ctlplane_a'
+        ctlplane_a.cidr = '192.168.25.0/24'
+        ctlplane_b.id = 'ctlplane_b_id'
+        ctlplane_b.name = 'ctlplane_b'
+        ctlplane_b.cidr = '192.168.26.0/24'
+
+        external_a.id = 'external_a_id'
+        external_a.name = 'external_a'
+        external_a.cidr = '10.0.0.0/24'
+
+        int_api_a.id = 'internal_api_a_id'
+        int_api_a.name = 'internal_api_a'
+        int_api_a.cidr = '172.17.0.0/24'
+        int_api_b.id = 'internal_api_b_id'
+        int_api_b.name = 'internal_api_b'
+        int_api_b.cidr = '172.17.1.0/24'
+
+        self.network.find_network.side_effect = [
+            ctlplane_net, internal_api_net,  # compute-0
+            ctlplane_net, external_net, internal_api_net,  # controller-0
+            ctlplane_net, external_net, internal_api_net,  # controller-1
+            ctlplane_net, external_net, internal_api_net,  # controller-2
+        ]
+        self.network.get_subnet.side_effect = [
+            ctlplane_a, ctlplane_b, int_api_a, int_api_b,  # compute-0
+            ctlplane_a, external_a, int_api_a,  # controller-0,
+            ctlplane_a, external_a, int_api_a,  # controller-1,
+            ctlplane_a, external_a, int_api_a,  # controller-2,
+        ]
+
+        argslist = ['--roles-file', self.roles_file.name,
+                    '--output', self.extract_file.name,
+                    '--yes']
         self.app.command_options = argslist
-        verifylist = [('output', self.extract_file.name), ('yes', True)]
+        verifylist = [('roles_file', self.roles_file.name),
+                      ('output', self.extract_file.name),
+                      ('yes', True)]
 
         parsed_args = self.check_parser(self.cmd,
                                         argslist, verifylist)
@@ -822,6 +927,17 @@ class TestExtractProvisionedNode(test_utils.TestCommand):
             'name': 'Compute',
             'count': 1,
             'hostname_format': '%stackname%-novacompute-%index%',
+            'defaults': {
+                'network_config': {'network_deployment_actions': ['CREATE'],
+                                   'physical_bridge_name': 'br-ex',
+                                   'public_interface_name': 'nic1',
+                                   'template': 'templates/compute.j2'},
+                'networks': [{'network': 'ctlplane',
+                              'subnet': 'ctlplane_b',
+                              'vif': True},
+                             {'network': 'internal_api',
+                              'subnet': 'internal_api_b'}]
+            },
             'instances': [{
                 'hostname': 'overcloud-novacompute-0',
                 'name': 'bm-3'
@@ -830,6 +946,21 @@ class TestExtractProvisionedNode(test_utils.TestCommand):
             'name': 'Controller',
             'count': 3,
             'hostname_format': '%stackname%-controller-%index%',
+            'defaults': {
+                'network_config': {'default_route_network': ['External'],
+                                   'network_deployment_actions': ['CREATE'],
+                                   'networks_skip_config': ['Tenant'],
+                                   'physical_bridge_name': 'br-ex',
+                                   'public_interface_name': 'nic1',
+                                   'template': 'templates/controller.j2'},
+                'networks': [{'network': 'ctlplane',
+                              'subnet': 'ctlplane_a',
+                              'vif': True},
+                             {'network': 'external',
+                              'subnet': 'external_a'},
+                             {'network': 'internal_api',
+                              'subnet': 'internal_api_a'}]
+            },
             'instances': [{
                 'hostname': 'overcloud-controller-0',
                 'name': 'bm-0'
@@ -858,9 +989,9 @@ class TestExtractProvisionedNode(test_utils.TestCommand):
 
         self.baremetal.node.list.return_value = nodes
 
-        argslist = []
+        argslist = ['--roles-file', self.roles_file.name]
         self.app.command_options = argslist
-        verifylist = []
+        verifylist = [('roles_file', self.roles_file.name)]
 
         parsed_args = self.check_parser(self.cmd,
                                         argslist, verifylist)
diff --git a/tripleoclient/v1/overcloud_node.py b/tripleoclient/v1/overcloud_node.py
index d2502a43d..5e3c44bf4 100644
--- a/tripleoclient/v1/overcloud_node.py
+++ b/tripleoclient/v1/overcloud_node.py
@@ -15,12 +15,14 @@
 
 import collections
 import datetime
+import ipaddress
 import json
 import logging
 import os
 import sys
 
 from cliff.formatters import table
+from openstack import exceptions as openstack_exc
 from osc_lib import exceptions as oscexc
 from osc_lib.i18n import _
 from osc_lib import utils
@@ -458,6 +460,7 @@ class ExtractProvisionedNode(command.Command):
         self.clients = self.app.client_manager
         self.orchestration_client = self.clients.orchestration
         self.baremetal_client = self.clients.baremetal
+        self.network_client = self.clients.network
 
     def get_parser(self, prog_name):
         parser = super(ExtractProvisionedNode, self).get_parser(prog_name)
@@ -473,15 +476,51 @@ class ExtractProvisionedNode(command.Command):
         parser.add_argument('-y', '--yes', default=False, action='store_true',
                             help=_('Skip yes/no prompt for existing files '
                                    '(assume yes).'))
+        parser.add_argument('--roles-file', '-r', dest='roles_file',
+                            required=True,
+                            help=_('Role data definition file'))
         return parser
 
+    def _get_subnet_from_net_name_and_ip(self, net_name, ip_addr):
+        try:
+            network = self.network_client.find_network(net_name)
+        except openstack_exc.DuplicateResource:
+            raise oscexc.CommandError(
+                "Unable to extract role networks. Duplicate network resources "
+                "with name %s detected." % net_name)
+
+        if network is None:
+            raise oscexc.CommandError("Unable to extract role networks. "
+                                      "Network %s not found." % net_name)
+
+        for subnet_id in network.subnet_ids:
+            subnet = self.network_client.get_subnet(subnet_id)
+            if (ipaddress.ip_address(ip_addr)
+                    in ipaddress.ip_network(subnet.cidr)):
+                subnet_name = subnet.name
+                return subnet_name
+
+        raise oscexc.CommandError("Unable to extract role networks. Could not "
+                                  "find subnet for IP address %(ip)s on "
+                                  "network %(net)s." % {'ip': ip_addr,
+                                                        'net': net_name})
+
     def take_action(self, parsed_args):
         self.log.debug("take_action(%s)" % parsed_args)
+
+        roles_file = os.path.abspath(parsed_args.roles_file)
+        with open(roles_file, 'r') as fd:
+            role_data = yaml.safe_load(fd.read())
+        # Convert role_data to a dict
+        role_data = {x['name']: x for x in role_data}
+
         self._setup_clients()
         stack = oooutils.get_stack(self.orchestration_client,
                                    parsed_args.stack)
         host_vars = oooutils.get_stack_output_item(
             stack, 'AnsibleHostVarsMap') or {}
+        role_net_ip_map = oooutils.get_stack_output_item(
+            stack, 'RoleNetIpMap') or {}
         parameters = stack.to_dict().get('parameters', {})
 
         # list all baremetal nodes and map hostname to node name
@@ -492,32 +531,102 @@ class ExtractProvisionedNode(command.Command):
             if hostname and node.name:
                 hostname_node_map[hostname] = node.name
 
-        role_data = six.StringIO()
-        role_data.write('# Generated with the following on %s\n#\n' %
-                        datetime.datetime.now().isoformat())
-        role_data.write('#   openstack %s\n#\n\n' %
-                        ' '.join(self.app.command_options))
-        for role, entries in host_vars.items():
+        data = []
+        for role_name, entries in host_vars.items():
             role_count = len(entries)
 
             # skip zero count roles
             if not role_count:
                 continue
 
-            role_data.write('- name: %s\n' % role)
-            role_data.write('  count: %s\n' % role_count)
+            if role_name not in role_data:
+                raise oscexc.CommandError(
+                    "Unable to extract. Invalid role file. Role {} is not "
+                    "defined in roles file {}".format(role_name, roles_file))
 
-            hostname_format = parameters.get('%sHostnameFormat' % role)
+            role = collections.OrderedDict()
+            role['name'] = role_name
+            role['count'] = role_count
+
+            hostname_format = parameters.get('%sHostnameFormat' % role_name)
             if hostname_format:
-                role_data.write('  hostname_format: "%s"\n' % hostname_format)
+                role['hostname_format'] = hostname_format
 
-            role_data.write('  instances:\n')
+            defaults = role['defaults'] = {}
 
+            # Add networks to the role default section
+            role_networks = defaults['networks'] = []
+            for net_name, ips in role_net_ip_map[role_name].items():
+                subnet_name = self._get_subnet_from_net_name_and_ip(net_name,
+                                                                    ips[0])
+                if net_name == constants.CTLPLANE_NET_NAME:
+                    role_networks.append({'network': net_name,
+                                          'subnet': subnet_name,
+                                          'vif': True})
+                else:
+                    role_networks.append({'network': net_name,
+                                          'subnet': subnet_name})
+
+            # Add network config to role defaults section
+            net_conf = defaults['network_config'] = {}
+            net_conf['template'] = parameters.get(
+                role_name + 'NetworkConfigTemplate')
+
+            if parameters.get(role_name + 'NetworkDeploymentActions'):
+                net_conf['network_deployment_actions'] = parameters.get(
+                    role_name + 'NetworkDeploymentActions')
+            else:
+                net_conf['network_deployment_actions'] = parameters.get(
+                    'NetworkDeploymentActions', ['CREATE'])
+
+            if isinstance(net_conf['network_deployment_actions'], str):
+                net_conf['network_deployment_actions'] = net_conf[
+                    'network_deployment_actions'].split(',')
+
+            # The NetConfigDataLookup parameter is of type: json, but when
+            # not set it returns as string '{}'
+            ncdl = parameters.get('NetConfigDataLookup')
+            if isinstance(ncdl, str):
+                ncdl = json.loads(ncdl)
+            if ncdl:
+                net_conf['net_config_data_lookup'] = ncdl
+
+            if parameters.get('DnsSearchDomains'):
+                net_conf['dns_search_domains'] = parameters.get(
+                    'DnsSearchDomains')
+
+            net_conf['physical_bridge_name'] = parameters.get(
+                'NeutronPhysicalBridge', 'br-ex')
+            net_conf['public_interface_name'] = parameters.get(
+                'NeutronPublicInterface', 'nic1')
+
+            if role_data[role_name].get('default_route_networks'):
+                net_conf['default_route_network'] = role_data[role_name].get(
+                    'default_route_networks')
+            if role_data[role_name].get('networks_skip_config'):
+                net_conf['networks_skip_config'] = role_data[role_name].get(
+                    'networks_skip_config')
+
+            # Add individual instances
+            instances = role['instances'] = []
             for entry in sorted(entries):
-                role_data.write('  - hostname: %s\n' % entry)
+                instance = {'hostname': entry}
                 if entry in hostname_node_map:
-                    role_data.write('    name: %s\n' %
-                                    hostname_node_map[entry])
+                    instance['name'] = hostname_node_map[entry]
+                instances.append(instance)
+
+            data.append(role)
+
+        # Write the file header
+        file_data = six.StringIO()
+        file_data.write('# Generated with the following on %s\n#\n' %
+                        datetime.datetime.now().isoformat())
+        file_data.write('#   openstack %s\n#\n\n' %
+                        ' '.join(self.app.command_options))
+        # Write the data
+        if data:
+            yaml.dump(data, file_data, RoleDataDumper, width=120,
+                      default_flow_style=False)
 
         if parsed_args.output:
             if (os.path.exists(parsed_args.output)
@@ -530,5 +639,14 @@ class ExtractProvisionedNode(command.Command):
                         "Will not overwrite existing file:"
                         " %s" % parsed_args.output)
             with open(parsed_args.output, 'w+') as fp:
-                fp.write(role_data.getvalue())
-        self.app.stdout.write(role_data.getvalue())
+                fp.write(file_data.getvalue())
+        self.app.stdout.write(file_data.getvalue())
+
+
+class RoleDataDumper(yaml.SafeDumper):
+    def represent_ordered_dict(self, data):
+        return self.represent_dict(data.items())
+
+
+RoleDataDumper.add_representer(collections.OrderedDict,
+                               RoleDataDumper.represent_ordered_dict)