mirror of
https://github.com/Stone-Red-Code/dvc.git
synced 2026-11-06 15:28:28 +01:00
154 lines
5.1 KiB
Python
154 lines
5.1 KiB
Python
import re
|
|
from multiprocessing.pool import ThreadPool
|
|
|
|
from dvc.logger import Logger
|
|
from dvc.exceptions import DvcException
|
|
from dvc.config import Config, ConfigError
|
|
from dvc.utils import map_progress
|
|
|
|
from dvc.cloud.aws import DataCloudAWS
|
|
from dvc.cloud.gcp import DataCloudGCP
|
|
from dvc.cloud.ssh import DataCloudSSH
|
|
from dvc.cloud.local import DataCloudLOCAL
|
|
from dvc.cloud.base import DataCloudBase, CloudSettings
|
|
from dvc.cloud.base import STATUS_MODIFIED, STATUS_NEW, STATUS_DELETED
|
|
|
|
|
|
class DataCloud(object):
|
|
""" Generic class to do initial config parsing and redirect to proper DataCloud methods """
|
|
|
|
CLOUD_MAP = {
|
|
'aws' : DataCloudAWS,
|
|
'gcp' : DataCloudGCP,
|
|
'ssh' : DataCloudSSH,
|
|
'local' : DataCloudLOCAL,
|
|
}
|
|
|
|
def __init__(self, cache=None, config=None):
|
|
self._cache = cache
|
|
self._config = config
|
|
|
|
remote = self._config[Config.SECTION_CORE].get(Config.SECTION_CORE_REMOTE, '')
|
|
if remote == '':
|
|
if config[Config.SECTION_CORE].get(Config.SECTION_CORE_CLOUD, None):
|
|
# backward compatibility
|
|
Logger.warn('Using obsoleted config format. Consider updating.')
|
|
self._cloud = self.__init__compat()
|
|
else:
|
|
self._cloud = None
|
|
return
|
|
|
|
self._cloud = self._init_remote(remote)
|
|
|
|
@staticmethod
|
|
def supported(url):
|
|
for cloud in DataCloud.CLOUD_MAP.values():
|
|
if cloud.supported(url):
|
|
return cloud
|
|
return None
|
|
|
|
def _init_remote(self, remote):
|
|
section = Config.SECTION_REMOTE_FMT.format(remote)
|
|
cloud_config = self._config.get(section, None)
|
|
if not cloud_config:
|
|
raise ConfigError("Can't find remote section '{}' in config".format(section))
|
|
|
|
url = cloud_config[Config.SECTION_REMOTE_URL]
|
|
cloud_type = self.supported(url)
|
|
if not cloud_type:
|
|
raise ConfigError("Unsupported url '{}'".format(url))
|
|
|
|
return self._init_cloud(cloud_config, cloud_type)
|
|
|
|
def __init__compat(self):
|
|
cloud_name = self._config[Config.SECTION_CORE].get(Config.SECTION_CORE_CLOUD, '').strip().lower()
|
|
if cloud_name == '':
|
|
self._cloud = None
|
|
return
|
|
|
|
cloud_type = self.CLOUD_MAP.get(cloud_name, None)
|
|
if not cloud_type:
|
|
raise ConfigError('Wrong cloud type %s specified' % cloud_name)
|
|
|
|
cloud_config = self._config.get(cloud_name, None)
|
|
if not cloud_config:
|
|
raise ConfigError('Can\'t find cloud section \'[%s]\' in config' % cloud_name)
|
|
|
|
return self._init_cloud(cloud_config, cloud_type)
|
|
|
|
def _init_cloud(self, cloud_config, cloud_type):
|
|
global_storage_path = self._config[Config.SECTION_CORE].get(Config.SECTION_CORE_STORAGEPATH, None)
|
|
if global_storage_path:
|
|
Logger.warn('Using obsoleted config format. Consider updating.')
|
|
|
|
cloud_settings = CloudSettings(cache=self._cache,
|
|
global_storage_path=global_storage_path,
|
|
cloud_config=cloud_config)
|
|
|
|
cloud = cloud_type(cloud_settings)
|
|
cloud.sanity_check()
|
|
return cloud
|
|
|
|
def _collect(self, cloud, targets, jobs, local):
|
|
collected = set()
|
|
pool = ThreadPool(processes=jobs)
|
|
args = zip(targets, [local]*len(targets))
|
|
ret = pool.map(cloud.collect, args)
|
|
|
|
for r in ret:
|
|
collected |= set(r)
|
|
|
|
return collected
|
|
|
|
def _get_cloud(self, remote):
|
|
if remote:
|
|
return self._init_remote(remote)
|
|
|
|
return self._cloud
|
|
|
|
def _filter(self, func, status, targets, jobs, remote):
|
|
cloud = self._get_cloud(remote)
|
|
if not cloud:
|
|
return []
|
|
|
|
with cloud:
|
|
filtered = []
|
|
for t, s in self._status(cloud, targets, jobs):
|
|
if s == STATUS_MODIFIED or s == status:
|
|
filtered.append(t)
|
|
|
|
return map_progress(getattr(cloud, func), filtered, jobs)
|
|
|
|
def push(self, targets, jobs=1, remote=None):
|
|
"""
|
|
Push data items in a cloud-agnostic way.
|
|
"""
|
|
return self._filter('push', STATUS_NEW, targets, jobs, remote)
|
|
|
|
def pull(self, targets, jobs=1, remote=None):
|
|
"""
|
|
Pull data items in a cloud-agnostic way.
|
|
"""
|
|
return self._filter('pull', STATUS_DELETED, targets, jobs, remote)
|
|
|
|
def _collect_targets(self, cloud, targets, jobs=1):
|
|
collected = set()
|
|
collected |= self._collect(cloud, targets, jobs, True)
|
|
collected |= self._collect(cloud, targets, jobs, False)
|
|
return list(collected)
|
|
|
|
def _status(self, cloud, targets, jobs=1):
|
|
collected = self._collect_targets(cloud, targets, jobs)
|
|
return map_progress(cloud.status, collected, jobs)
|
|
|
|
def status(self, targets, jobs=1, remote=None):
|
|
"""
|
|
Check status of data items in a cloud-agnostic way.
|
|
"""
|
|
cloud = self._get_cloud(remote)
|
|
if not cloud:
|
|
return []
|
|
|
|
with cloud:
|
|
return self._status(cloud, targets, jobs)
|