mirror of
https://github.com/Stone-Red-Code/dvc.git
synced 2026-11-07 23:09:21 +01:00
config: empty config by default + format verification
Signed-off-by: Ruslan Kuprieiev <[email protected]>
This commit is contained in:
+3
-2
@@ -12,6 +12,7 @@ except ImportError:
|
||||
sys.modules['httplib'] = httplib
|
||||
from boto.s3.resumable_download_handler import ResumableDownloadHandler
|
||||
|
||||
from dvc.config import Config
|
||||
from dvc.logger import Logger
|
||||
from dvc.progress import progress
|
||||
from dvc.cloud.credentials_aws import AWSCredentials
|
||||
@@ -57,7 +58,7 @@ class DataCloudAWS(DataCloudBase):
|
||||
See notes http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region
|
||||
"""
|
||||
|
||||
region = self._cloud_settings.cloud_config.get('Region', None)
|
||||
region = self._cloud_settings.cloud_config.get(Config.SECTION_AWS_REGION, None)
|
||||
if region is None or region == '':
|
||||
return 's3.amazonaws.com'
|
||||
if region == 'us-east-1':
|
||||
@@ -69,7 +70,7 @@ class DataCloudAWS(DataCloudBase):
|
||||
Try obtaining path to aws credentials from config file.
|
||||
"""
|
||||
paths = []
|
||||
credpath = self._cloud_settings.cloud_config.get('CredentialPath', None)
|
||||
credpath = self._cloud_settings.cloud_config.get(Config.SECTION_AWS_CREDENTIALPATH, None)
|
||||
if credpath is not None and len(credpath) > 0:
|
||||
credpath = os.path.expanduser(credpath)
|
||||
if os.path.isfile(credpath):
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@ from checksumdir import dirhash
|
||||
|
||||
from dvc.logger import Logger
|
||||
from dvc.exceptions import DvcException
|
||||
from dvc.config import ConfigError
|
||||
from dvc.config import Config, ConfigError
|
||||
from dvc.stage import Output
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ class DataCloudBase(object):
|
||||
if self._cloud_settings.global_storage_path:
|
||||
return self._cloud_settings.global_storage_path
|
||||
|
||||
path = self._cloud_settings.cloud_config.get('StoragePath', None)
|
||||
path = self._cloud_settings.cloud_config.get(Config.SECTION_CORE_STORAGEPATH, None)
|
||||
if path is None:
|
||||
raise ConfigError('invalid StoragePath: not set for Data or cloud specific')
|
||||
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import os
|
||||
import configparser
|
||||
|
||||
from dvc.config import Config
|
||||
from dvc.utils import cached_property
|
||||
from dvc.logger import Logger
|
||||
|
||||
|
||||
class AWSCredentials(object):
|
||||
def __init__(self, cloud_config):
|
||||
self._conf_credpath = cloud_config.get('CredentialPath', None)
|
||||
self._conf_credsect = cloud_config.get('Profile', 'default')
|
||||
self._conf_credpath = cloud_config.get(Config.SECTION_AWS_CREDENTIALPATH, None)
|
||||
self._conf_credsect = cloud_config.get(Config.SECTION_AWS_PROFILE, 'default')
|
||||
|
||||
@property
|
||||
def access_key_id(self):
|
||||
|
||||
+19
-34
@@ -10,27 +10,34 @@ from dvc.config import Config
|
||||
from dvc.cloud.aws import DataCloudAWS
|
||||
from dvc.cloud.gcp import DataCloudGCP
|
||||
from dvc.cloud.local import DataCloudLOCAL
|
||||
from dvc.cloud.base import DataCloudBase
|
||||
|
||||
|
||||
class DataCloud(object):
|
||||
""" Generic class to do initial config parsing and redirect to proper DataCloud methods """
|
||||
|
||||
CLOUD_MAP = {
|
||||
'AWS' : DataCloudAWS,
|
||||
'GCP' : DataCloudGCP,
|
||||
'LOCAL' : DataCloudLOCAL,
|
||||
'aws' : DataCloudAWS,
|
||||
'gcp' : DataCloudGCP,
|
||||
'local' : DataCloudLOCAL,
|
||||
}
|
||||
|
||||
SCHEME_MAP = {
|
||||
's3' : 'AWS',
|
||||
'gs' : 'GCP',
|
||||
'' : 'LOCAL',
|
||||
's3' : 'aws',
|
||||
'gs' : 'gcp',
|
||||
'' : 'local',
|
||||
}
|
||||
|
||||
def __init__(self, cache, config):
|
||||
self._config = config
|
||||
|
||||
cloud_type = self._config[Config.SECTION_CORE].get('Cloud', '').strip().upper()
|
||||
cloud_type = self._config[Config.SECTION_CORE].get(Config.SECTION_CORE_CLOUD, '').strip().lower()
|
||||
|
||||
if cloud_type == '':
|
||||
self.typ = None
|
||||
self._cloud = None
|
||||
return
|
||||
|
||||
if cloud_type not in self.CLOUD_MAP.keys():
|
||||
raise ConfigError('Wrong cloud type %s specified' % cloud_type)
|
||||
|
||||
@@ -43,8 +50,7 @@ class DataCloud(object):
|
||||
|
||||
self.typ = cloud_type
|
||||
self._cloud = self.CLOUD_MAP[cloud_type](cloud_settings)
|
||||
|
||||
self.sanity_check()
|
||||
self._cloud.sanity_check()
|
||||
|
||||
@staticmethod
|
||||
def get_cloud_settings(cache, config, cloud_type):
|
||||
@@ -55,34 +61,10 @@ class DataCloud(object):
|
||||
cloud_config = None
|
||||
else:
|
||||
cloud_config = config[cloud_type]
|
||||
global_storage_path = config[Config.SECTION_CORE].get('StoragePath', None)
|
||||
global_storage_path = config[Config.SECTION_CORE].get(Config.SECTION_CORE_STORAGEPATH, None)
|
||||
cloud_settings = CloudSettings(cache, global_storage_path, cloud_config)
|
||||
return cloud_settings
|
||||
|
||||
def sanity_check(self):
|
||||
""" sanity check a config
|
||||
|
||||
check that we have a cloud and storagePath
|
||||
if aws, check can read credentials
|
||||
if google, check ProjectName
|
||||
|
||||
Returns:
|
||||
(T,) if good
|
||||
(F, issues) if bad
|
||||
"""
|
||||
key = 'Cloud'
|
||||
core = self._config[Config.SECTION_CORE]
|
||||
if key.lower() not in [k.lower() for k in core.keys()] or len(core[key]) < 1:
|
||||
raise ConfigError('Please set {} in section {} in config file'.format(key, Config.SECTION_CORE))
|
||||
|
||||
# now that a cloud is chosen, can check StoragePath
|
||||
storage_path = self._cloud.storage_path
|
||||
if storage_path is None or len(storage_path) == 0:
|
||||
raise ConfigError('Please set StoragePath = bucket/{optional path} '
|
||||
'in config file in a cloud specific section')
|
||||
|
||||
self._cloud.sanity_check()
|
||||
|
||||
def _collect(self, targets, jobs, local):
|
||||
collected = set()
|
||||
pool = ThreadPool(processes=jobs)
|
||||
@@ -98,6 +80,9 @@ class DataCloud(object):
|
||||
"""
|
||||
Process targets as data items in parallel.
|
||||
"""
|
||||
if not self._cloud:
|
||||
return
|
||||
|
||||
self._cloud.connect()
|
||||
|
||||
collected = set()
|
||||
|
||||
+3
-3
@@ -4,7 +4,7 @@ import os
|
||||
from google.cloud import storage as gc
|
||||
|
||||
from dvc.logger import Logger
|
||||
from dvc.config import ConfigError
|
||||
from dvc.config import Config, ConfigError
|
||||
from dvc.cloud.base import DataCloudError, DataCloudBase
|
||||
from dvc.utils import file_md5
|
||||
from dvc.progress import progress
|
||||
@@ -17,13 +17,13 @@ class DataCloudGCP(DataCloudBase):
|
||||
"""
|
||||
Get project name from config.
|
||||
"""
|
||||
return self._cloud_settings.cloud_config.get('ProjectName', None)
|
||||
return self._cloud_settings.cloud_config.get(Config.SECTION_GCP_PROJECTNAME, None)
|
||||
|
||||
def sanity_check(self):
|
||||
project = self.gc_project_name
|
||||
if project is None or len(project) < 1:
|
||||
raise ConfigError('can\'t read google cloud project name. '
|
||||
'Please set ProjectName in section GC.')
|
||||
'Please set \'{}\' in section GCP.'.format(Config.SECTION_GCP_PROJECTNAME))
|
||||
|
||||
def connect(self):
|
||||
client = gc.Client(project=self.gc_project_name)
|
||||
|
||||
+53
-18
@@ -2,6 +2,7 @@
|
||||
DVC config objects.
|
||||
"""
|
||||
import os
|
||||
import schema
|
||||
import configobj
|
||||
|
||||
from dvc.exceptions import DvcException
|
||||
@@ -15,26 +16,51 @@ class ConfigError(DvcException):
|
||||
|
||||
class Config(object):
|
||||
CONFIG = 'config'
|
||||
SECTION_CORE = 'Core'
|
||||
CONFIG_TEMPLATE = '''
|
||||
[Core]
|
||||
# Supported clouds: AWS, GCP
|
||||
Cloud = AWS
|
||||
|
||||
# Log levels: Debug, Info, Warning and Error
|
||||
LogLevel = Info
|
||||
SECTION_CORE = 'core'
|
||||
SECTION_CORE_LOGLEVEL = 'loglevel'
|
||||
SECTION_CORE_LOGLEVEL_SCHEMA = schema.And(schema.Use(str.lower), lambda l: l in ('info', 'debug', 'warning', 'error'))
|
||||
SECTION_CORE_CLOUD = 'cloud'
|
||||
SECTION_CORE_CLOUD_SCHEMA = schema.And(schema.Use(str.lower), lambda c: c in ('aws', 'gcp', 'local', ''))
|
||||
SECTION_CORE_STORAGEPATH = 'storagepath' # backward compatibility
|
||||
SECTION_CORE_SCHEMA = {
|
||||
schema.Optional(SECTION_CORE_LOGLEVEL, default='info'): SECTION_CORE_LOGLEVEL_SCHEMA,
|
||||
schema.Optional(SECTION_CORE_CLOUD, default=''): SECTION_CORE_CLOUD_SCHEMA,
|
||||
schema.Optional(SECTION_CORE_STORAGEPATH, default=''): str,
|
||||
}
|
||||
|
||||
[AWS]
|
||||
CredentialPath = ~/.aws/credentials
|
||||
Profile = default
|
||||
SECTION_AWS = 'aws'
|
||||
SECTION_AWS_STORAGEPATH = 'storagepath'
|
||||
SECTION_AWS_CREDENTIALPATH = 'credentialpath'
|
||||
SECTION_AWS_REGION = 'region'
|
||||
SECTION_AWS_PROFILE = 'profile'
|
||||
SECTION_AWS_SCHEMA = {
|
||||
SECTION_AWS_STORAGEPATH: str,
|
||||
schema.Optional(SECTION_AWS_REGION): str,
|
||||
schema.Optional(SECTION_AWS_PROFILE, default='default'): str,
|
||||
schema.Optional(SECTION_AWS_CREDENTIALPATH, default = ''): str,
|
||||
}
|
||||
|
||||
StoragePath = dvc/tutorial
|
||||
SECTION_GCP = 'gcp'
|
||||
SECTION_GCP_STORAGEPATH = SECTION_AWS_STORAGEPATH
|
||||
SECTION_GCP_PROJECTNAME = 'projectname'
|
||||
SECTION_GCP_SCHEMA = {
|
||||
SECTION_GCP_STORAGEPATH: str,
|
||||
SECTION_GCP_PROJECTNAME: str,
|
||||
}
|
||||
|
||||
[GCP]
|
||||
StoragePath =
|
||||
ProjectName =
|
||||
SECTION_LOCAL = 'local'
|
||||
SECTION_LOCAL_STORAGEPATH = SECTION_AWS_STORAGEPATH
|
||||
SECTION_LOCAL_SCHEMA = {
|
||||
SECTION_LOCAL_STORAGEPATH: str,
|
||||
}
|
||||
|
||||
'''
|
||||
SCHEMA = {
|
||||
schema.Optional(SECTION_CORE, default={}): SECTION_CORE_SCHEMA,
|
||||
schema.Optional(SECTION_AWS, default={}): SECTION_AWS_SCHEMA,
|
||||
schema.Optional(SECTION_GCP, default={}): SECTION_GCP_SCHEMA,
|
||||
schema.Optional(SECTION_LOCAL, default={}): SECTION_LOCAL_SCHEMA,
|
||||
}
|
||||
|
||||
def __init__(self, dvc_dir):
|
||||
self.dvc_dir = os.path.abspath(os.path.realpath(dvc_dir))
|
||||
@@ -42,14 +68,23 @@ ProjectName =
|
||||
|
||||
try:
|
||||
self._config = configobj.ConfigObj(self.config_file, write_empty_values=True)
|
||||
self._config = self._lower(self._config)
|
||||
self._config = schema.Schema(self.SCHEMA).validate(self._config)
|
||||
except Exception as ex:
|
||||
raise ConfigError(ex.message)
|
||||
|
||||
if self.SECTION_CORE not in self._config.keys():
|
||||
raise ConfigError(u'section \'{}\' was not found'.format(self.SECTION_CORE))
|
||||
@staticmethod
|
||||
def _lower(config):
|
||||
new_config = {}
|
||||
for s_key, s_value in config.items():
|
||||
new_s = {}
|
||||
for key, value in s_value.items():
|
||||
new_s[key.lower()] = value
|
||||
new_config[s_key.lower()] = new_s
|
||||
return new_config
|
||||
|
||||
@staticmethod
|
||||
def init(dvc_dir):
|
||||
config_file = os.path.join(dvc_dir, Config.CONFIG)
|
||||
open(config_file, 'w').write(Config.CONFIG_TEMPLATE)
|
||||
open(config_file, 'w+').close()
|
||||
return Config(dvc_dir)
|
||||
|
||||
+33
-47
@@ -1,5 +1,4 @@
|
||||
import os
|
||||
import configparser
|
||||
import configobj
|
||||
from unittest import TestCase
|
||||
import mock
|
||||
@@ -26,49 +25,42 @@ class TestConfigTest(TestCase):
|
||||
pass
|
||||
|
||||
def _conf(self, s):
|
||||
c = configparser.SafeConfigParser()
|
||||
c.readfp(s)
|
||||
c = configobj.ConfigObj(s)
|
||||
return c
|
||||
|
||||
def storage_path_hierarchy_test(self):
|
||||
""" StoragePath should be read first from global and then from cloud section """
|
||||
|
||||
c = ("[Core]",
|
||||
"LogLevel =",
|
||||
"DataDir = ",
|
||||
"CacheDir = ",
|
||||
"StateDir = ",
|
||||
"Cloud = aws",
|
||||
"StoragePath = globalsb/global_storage_path",
|
||||
"[AWS]",
|
||||
"StoragePath = awssb/aws_storage_path",
|
||||
"CredentialPath =",
|
||||
"[GCP]",
|
||||
"StoragePath = googlesb/google_storage_path"
|
||||
c = ("[core]",
|
||||
"loglevel =",
|
||||
"cloud = aws",
|
||||
"storagepath = globalsb/global_storage_path",
|
||||
"[aws]",
|
||||
"storagepath = awssb/aws_storage_path",
|
||||
"credentialpath =",
|
||||
"[gcp]",
|
||||
"storagepath = googlesb/google_storage_path"
|
||||
)
|
||||
s = StringIO('\n'.join(c))
|
||||
conf = self._conf(s)
|
||||
cloud = DataCloud(None, conf)
|
||||
self.assertEqual(cloud.typ, 'AWS')
|
||||
self.assertEqual(cloud.typ, 'aws')
|
||||
self.assertEqual(cloud._cloud.storage_bucket, 'globalsb')
|
||||
self.assertEqual(cloud._cloud.storage_prefix, 'global_storage_path')
|
||||
|
||||
c = ("[Core]",
|
||||
"LogLevel =",
|
||||
"DataDir = ",
|
||||
"CacheDir = ",
|
||||
"StateDir = ",
|
||||
"Cloud = Aws",
|
||||
"[AWS]",
|
||||
"StoragePath = awssb/aws_storage_path",
|
||||
"CredentialPath =",
|
||||
"[GCP]",
|
||||
"StoragePath = googlesb/google_storage_path"
|
||||
c = ("[core]",
|
||||
"loglevel =",
|
||||
"cloud = Aws",
|
||||
"[aws]",
|
||||
"storagepath = awssb/aws_storage_path",
|
||||
"credentialpath =",
|
||||
"[gcp]",
|
||||
"storagepath = googlesb/google_storage_path"
|
||||
)
|
||||
s = StringIO('\n'.join(c))
|
||||
conf = self._conf(s)
|
||||
cloud = DataCloud(None, conf)
|
||||
self.assertEqual(cloud.typ, 'AWS')
|
||||
self.assertEqual(cloud.typ, 'aws')
|
||||
self.assertEqual(cloud._cloud.storage_bucket, 'awssb')
|
||||
self.assertEqual(cloud._cloud.storage_prefix, 'aws_storage_path')
|
||||
|
||||
@@ -90,15 +82,12 @@ class TestConfigTest(TestCase):
|
||||
""" in absence of [AWS] -> CredentialPath, aws creds should be read from ~/.aws/credentials """
|
||||
|
||||
default_path = os.path.expanduser('~/.aws/credentials')
|
||||
c = ("[Core]",
|
||||
"LogLevel =",
|
||||
"DataDir = ",
|
||||
"CacheDir = ",
|
||||
"StateDir = ",
|
||||
"Cloud = aws",
|
||||
"[AWS]",
|
||||
"StoragePath = awssb/aws_storage_path",
|
||||
"CredentialPath =",
|
||||
c = ("[core]",
|
||||
"loglevel =",
|
||||
"cloud = aws",
|
||||
"[aws]",
|
||||
"storagepath = awssb/aws_storage_path",
|
||||
"credentialpath =",
|
||||
)
|
||||
s = StringIO('\n'.join(c))
|
||||
|
||||
@@ -121,15 +110,12 @@ class TestConfigTest(TestCase):
|
||||
def aws_credentials_specified_tes_t_DISABLED(self, isfile_function):
|
||||
""" in presence of [AWS] -> CredentialPath, use those credentials """
|
||||
|
||||
c = ("[Core]",
|
||||
"LogLevel =",
|
||||
"DataDir = ",
|
||||
"CacheDir = ",
|
||||
"StateDir = ",
|
||||
"Cloud = aws",
|
||||
"[AWS]",
|
||||
"StoragePath = awssb/aws_storage_path",
|
||||
"CredentialPath = some_credential_path",
|
||||
c = ("[core]",
|
||||
"loglevel =",
|
||||
"cloud = aws",
|
||||
"[aws]",
|
||||
"storagePath = awssb/aws_storage_path",
|
||||
"credentialpath = some_credential_path",
|
||||
)
|
||||
s = StringIO('\n'.join(c))
|
||||
|
||||
@@ -160,7 +146,7 @@ class TestConfigCLI(TestDvc):
|
||||
return True
|
||||
|
||||
def test(self):
|
||||
section = 'SetSection'
|
||||
section = 'setsection'
|
||||
field = 'setfield'
|
||||
section_field = '{}.{}'.format(section, field)
|
||||
value = 'setvalue'
|
||||
|
||||
+11
-11
@@ -15,14 +15,14 @@ from tests.basic_env import TestDvc
|
||||
class TestDataCloud(TestDvc):
|
||||
def test(self):
|
||||
for k,v in DataCloud.CLOUD_MAP.items():
|
||||
config = {Config.SECTION_CORE: {'Cloud': k},
|
||||
k: {'StoragePath': 'a/b',
|
||||
'ProjectName': 'name'}}
|
||||
config = {Config.SECTION_CORE: {'cloud': k},
|
||||
k: {'storagepath': 'a/b',
|
||||
'projectname': 'name'}}
|
||||
cloud = DataCloud(self.dvc.cache, config)
|
||||
self.assertIsInstance(cloud._cloud, v)
|
||||
|
||||
with self.assertRaises(ConfigError) as cx:
|
||||
config = {Config.SECTION_CORE: {'Cloud': 'not_supported_type'}}
|
||||
config = {Config.SECTION_CORE: {'cloud': 'not_supported_type'}}
|
||||
DataCloud(self.dvc.cache, config)
|
||||
|
||||
|
||||
@@ -117,8 +117,8 @@ class TestDataCloudAWS(TestDataCloudBase):
|
||||
repo = self.TEST_REPO_BUCKET + '/' + str(uuid.uuid4())
|
||||
|
||||
# Setup cloud
|
||||
config = {'StoragePath': repo,
|
||||
'Region': self.TEST_REPO_REGION}
|
||||
config = {'storagepath': repo,
|
||||
'region': self.TEST_REPO_REGION}
|
||||
cloud_settings = CloudSettings(self.dvc.cache, None, config)
|
||||
self.cloud = DataCloudAWS(cloud_settings)
|
||||
|
||||
@@ -150,8 +150,8 @@ class TestDataCloudGCP(TestDataCloudBase):
|
||||
repo = self.TEST_REPO_GCP_BUCKET + '/' + str(uuid.uuid4())
|
||||
|
||||
# Setup cloud
|
||||
config = {'StoragePath': repo,
|
||||
'Region': self.TEST_REPO_GCP_PROJECT}
|
||||
config = {'storagepath': repo,
|
||||
'region': self.TEST_REPO_GCP_PROJECT}
|
||||
cloud_settings = CloudSettings(self.dvc.cache, None, config)
|
||||
self.cloud = DataCloudGCP(cloud_settings)
|
||||
|
||||
@@ -167,7 +167,7 @@ class TestDataCloudLOCAL(TestDataCloudBase):
|
||||
def _setup_cloud(self):
|
||||
self.dname = 'cloud'
|
||||
os.mkdir(self.dname)
|
||||
config = {'StoragePath': self.dname}
|
||||
config = {'storagepath': self.dname}
|
||||
cloud_settings = CloudSettings(self.dvc.cache, None, config)
|
||||
self.cloud = DataCloudLOCAL(cloud_settings)
|
||||
|
||||
@@ -186,8 +186,8 @@ class TestDataCloudLocalCli(TestDvc):
|
||||
dname = 'cloud'
|
||||
os.mkdir(dname)
|
||||
|
||||
self.main(['config', 'Core.cloud', 'LOCAL'])
|
||||
self.main(['config', 'LOCAL.StoragePath', dname])
|
||||
self.main(['config', 'core.cloud', 'local'])
|
||||
self.main(['config', 'local.storagepath', dname])
|
||||
|
||||
stage = self.dvc.add(self.FOO)
|
||||
cache = stage.outs[0].cache
|
||||
|
||||
Reference in New Issue
Block a user