Skip to content

Commit 36e0323

Browse files
authored
Guard against inconsistently cased filesystems for @conda on macOS with @Batch (#377)
Conda fails to correctly set up environments for linux-64 packages on macOS which is needed to collect the necessary metadata for correctly setting up the conda environment on AWS Batch. This patch simply ignores the error-checks that conda throws while setting up the environments on macOS when the intended destination is AWS Batch.
1 parent 95da251 commit 36e0323

File tree

2 files changed

+56
-21
lines changed

2 files changed

+56
-21
lines changed

metaflow/plugins/conda/conda.py

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,22 @@ def __init__(self):
4141
'is required. Specify it with CONDA_CHANNELS '
4242
'environment variable.')
4343

44-
def create(self, step_name, env_id, deps, architecture=None, explicit=False):
44+
def create(self,
45+
step_name,
46+
env_id,
47+
deps,
48+
architecture=None,
49+
explicit=False,
50+
disable_safety_checks=False):
4551
# Create the conda environment
4652
try:
4753
with CondaLock(self._env_lock_file(env_id)):
4854
self._remove(env_id)
49-
self._create(env_id, deps, explicit, architecture)
55+
self._create(env_id,
56+
deps,
57+
explicit,
58+
architecture,
59+
disable_safety_checks)
5060
return self._deps(env_id)
5161
except CondaException as e:
5262
raise CondaStepException(e, step_name)
@@ -91,13 +101,20 @@ def package_info(self, env_id):
91101
def _info(self):
92102
return json.loads(self._call_conda(['info']))
93103

94-
def _create(self, env_id, deps, explicit=False, architecture=None):
104+
def _create(self,
105+
env_id,
106+
deps,
107+
explicit=False,
108+
architecture=None,
109+
disable_safety_checks=False):
95110
cmd = ['create', '--yes', '--no-default-packages',
96111
'--name', env_id, '--quiet']
97112
if explicit:
98113
cmd.append('--no-deps')
99114
cmd.extend(deps)
100-
self._call_conda(cmd, architecture)
115+
self._call_conda(cmd,
116+
architecture=architecture,
117+
disable_safety_checks=disable_safety_checks)
101118

102119
def _remove(self, env_id):
103120
self._call_conda(['env', 'remove', '--name',
@@ -143,19 +160,19 @@ def _env_path(self, env_id):
143160
def _env_lock_file(self, env_id):
144161
return os.path.join(self._info()['envs_dirs'][0], 'mf_env-creation.lock')
145162

146-
def _call_conda(self, args, architecture=None):
163+
def _call_conda(self, args, architecture=None, disable_safety_checks=False):
147164
try:
165+
env = {
166+
'CONDA_JSON': 'True',
167+
'CONDA_SUBDIR': (architecture if architecture else ''),
168+
'CONDA_USE_ONLY_TAR_BZ2': 'True'
169+
}
170+
if disable_safety_checks:
171+
env['CONDA_SAFETY_CHECKS'] = 'disabled'
148172
return subprocess.check_output(
149173
[self._bin] + args,
150174
stderr = open(os.devnull, 'wb'),
151-
env = dict(
152-
os.environ,
153-
**{
154-
'CONDA_JSON': 'True',
155-
'CONDA_SUBDIR': (architecture if architecture else ''),
156-
'CONDA_USE_ONLY_TAR_BZ2': 'True'
157-
})
158-
).strip()
175+
env = dict(os.environ, **env)).strip()
159176
except subprocess.CalledProcessError as e:
160177
try:
161178
output = json.loads(e.output)
@@ -165,7 +182,7 @@ def _call_conda(self, args, architecture=None):
165182
raise CondaException(err)
166183
except (TypeError, ValueError) as ve:
167184
pass
168-
raise RuntimeError(
185+
raise CondaException(
169186
'command \'{cmd}\' returned error ({code}): {output}'
170187
.format(cmd=e.cmd, code=e.returncode, output=e.output))
171188

metaflow/plugins/conda/conda_step_decorator.py

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -108,12 +108,17 @@ def _resolve_step_environment(self, ds_root, force=False):
108108
cached_deps = read_conda_manifest(ds_root, self.flow.name)
109109
if CondaStepDecorator.conda is None:
110110
CondaStepDecorator.conda = Conda()
111-
CondaStepDecorator.environments = CondaStepDecorator.conda.environments(self.flow.name)
111+
CondaStepDecorator.environments =\
112+
CondaStepDecorator.conda.environments(self.flow.name)
112113
if force or env_id not in cached_deps or 'cache_urls' not in cached_deps[env_id]:
113114
if force or env_id not in cached_deps:
114115
deps = self._step_deps()
115116
(exact_deps, urls, order) = \
116-
self.conda.create(self.step, env_id, deps, architecture=self.architecture)
117+
self.conda.create(self.step,
118+
env_id,
119+
deps,
120+
architecture=self.architecture,
121+
disable_safety_checks=self.disable_safety_checks)
117122
payload = {
118123
'explicit': exact_deps,
119124
'deps': [d.decode('ascii') for d in deps],
@@ -125,7 +130,8 @@ def _resolve_step_environment(self, ds_root, force=False):
125130
if self.datastore.TYPE == 's3' and 'cache_urls' not in payload:
126131
payload['cache_urls'] = self._cache_env()
127132
write_to_conda_manifest(ds_root, self.flow.name, env_id, payload)
128-
CondaStepDecorator.environments = CondaStepDecorator.conda.environments(self.flow.name)
133+
CondaStepDecorator.environments =\
134+
CondaStepDecorator.conda.environments(self.flow.name)
129135
return env_id
130136

131137
def _cache_env(self):
@@ -147,13 +153,13 @@ def _download(entry):
147153
package_info['fn'])
148154
tarball_path = package_info['package_tarball_full_path']
149155
if tarball_path.endswith('.conda'):
150-
#Conda doesn't set the metadata correctly for certain fields
156+
# Conda doesn't set the metadata correctly for certain fields
151157
# when the underlying OS is spoofed.
152158
tarball_path = tarball_path[:-6]
153159
if not tarball_path.endswith('.tar.bz2'):
154160
tarball_path = '%s.tar.bz2' % tarball_path
155161
if not os.path.isfile(tarball_path):
156-
#The tarball maybe missing when user invokes `conda clean`!
162+
# The tarball maybe missing when user invokes `conda clean`!
157163
to_download.append((package_info['url'], tarball_path))
158164
files.append((path, tarball_path))
159165
if to_download:
@@ -170,10 +176,21 @@ def _prepare_step_environment(self, step_name, ds_root):
170176
env_id,
171177
cached_deps[env_id]['urls'],
172178
architecture=self.architecture,
173-
explicit=True)
174-
CondaStepDecorator.environments = CondaStepDecorator.conda.environments(self.flow.name)
179+
explicit=True,
180+
disable_safety_checks=self.disable_safety_checks)
181+
CondaStepDecorator.environments =\
182+
CondaStepDecorator.conda.environments(self.flow.name)
175183
return env_id
176184

185+
def _disable_safety_checks(self, decos):
186+
# Disable conda safety checks when creating linux-64 environments on
187+
# a macOS. This is needed because of gotchas around inconsistently
188+
# case-(in)sensitive filesystems for macOS and linux.
189+
for deco in decos:
190+
if deco.name == 'batch' and platform.system() == 'Darwin':
191+
return True
192+
return False
193+
177194
def _architecture(self, decos):
178195
for deco in decos:
179196
if deco.name == 'batch':
@@ -205,6 +222,7 @@ def _logger(line, **kwargs):
205222
self.local_root = LocalDataStore.get_datastore_root_from_config(_logger)
206223
environment.set_local_root(self.local_root)
207224
self.architecture = self._architecture(decos)
225+
self.disable_safety_checks = self._disable_safety_checks(decos)
208226
self.step = step
209227
self.flow = flow
210228
self.datastore = datastore

0 commit comments

Comments
 (0)