def _preprocess_zones(self, zones, eligible_sources=None, sources=None):
        '''
        This may modify the passed in zone object, it should be ignored after
        the call and the zones returned from this function should be used
        instead.
        '''
        for name, config in list(zones.items()):
            if not name.startswith('*'):
                continue
            # we've found a dynamic config element

            # find its sources
            found_sources = sources or self._get_sources(
                name, config, eligible_sources
            )
            self.log.info('sync:   dynamic zone=%s, sources=%s', name, sources)
            for source in found_sources:
                if not hasattr(source, 'list_zones'):
                    raise ManagerException(
                        f'dynamic zone={name} includes a source, {source.id}, that does not support `list_zones`'
                    )
                for zone_name in source.list_zones():
                    if zone_name in zones:
                        self.log.info(
                            'sync:      zone=%s already in config, ignoring',
                            zone_name,
                        )
                        continue
                    self.log.info('sync:     adding dynamic zone=%s', zone_name)
                    zones[zone_name] = config

            # remove the dynamic config element so we don't try and populate it
            del zones[name]

        return zones

        This may modify the passed in zone object, it should be ignored after
        the call and the zones returned from this function should be used
        instead.
        
octodns/manager.py