1# Copyright 2019 Google LLC 2# 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6 7import posixpath 8from recipe_engine import recipe_api 9 10 11MOUNT_SRC = '/SRC' 12MOUNT_OUT = '/OUT' 13 14 15class DockerApi(recipe_api.RecipeApi): 16 def _chmod(self, filepath, mode, recursive=False): 17 cmd = ['chmod'] 18 if recursive: 19 cmd.append('-R') 20 cmd.extend([mode, filepath]) 21 name = ' '.join([str(elem) for elem in cmd]) 22 self.m.step(name, cmd=cmd, infra_step=True) 23 24 def mount_src(self): 25 return MOUNT_SRC 26 27 def mount_out(self): 28 return MOUNT_OUT 29 30 # Unless match_directory_structure ==True, src_dir must be 31 # self.m.path['start_dir'] for the script to be located correctly. 32 def run(self, name, docker_image, src_dir, out_dir, script, args=None, docker_args=None, copies=None, recursive_read=None, attempts=1, match_directory_structure=False): 33 # Setup. Docker runs as a different user, so we need to give it access to 34 # read, write, and execute certain files. 35 with self.m.step.nest('Docker setup'): 36 step_stdout = self.m.python.inline( 37 name='Get uid and gid', 38 program='''import os 39print('%d:%d' % (os.getuid(), os.getgid())) 40''', 41 stdout=self.m.raw_io.output(), 42 step_test_data=( 43 lambda: self.m.raw_io.test_api.stream_output('13:17')) 44 ).stdout.decode('utf-8') 45 uid_gid_pair = step_stdout.rstrip() if step_stdout else '' 46 # Make sure out_dir exists, otherwise mounting will fail. 47 # (Note that the docker --mount option, unlike the --volume option, does 48 # not create this dir as root if it doesn't exist.) 49 self.m.file.ensure_directory('mkdirs out_dir', out_dir, mode=0o777) 50 # ensure_directory won't change the permissions if the dir already exists, 51 # so we need to do that explicitly. 52 self._chmod(out_dir, '777') 53 54 # chmod the src_dir, but not recursively; Swarming writes some files which 55 # we can't access, so "chmod -R" will fail if this is the root workdir. 56 self._chmod(src_dir, '755') 57 58 # Need to make the script executable, or Docker can't run it. 59 self._chmod(script, '0755') 60 61 # Copy any requested files. 62 if copies: 63 for copy in copies: 64 src = copy['src'] 65 dest = copy['dst'] 66 dirname = self.m.path.dirname(dest) 67 self.m.file.ensure_directory( 68 'mkdirs %s' % dirname, dirname, mode=0o777) 69 self.m.file.copy('cp %s %s' % (src, dest), src, dest) 70 self._chmod(dest, '644') 71 72 # Recursive chmod any requested directories. 73 if recursive_read: 74 for elem in recursive_read: 75 self._chmod(elem, 'a+r', recursive=True) 76 77 # Run. 78 cmd = [ 79 'docker', 'run', '--shm-size=2gb', '--rm', '--user', uid_gid_pair, 80 '--mount', 'type=bind,source=%s,target=%s' % 81 (src_dir, src_dir if match_directory_structure else MOUNT_SRC), 82 '--mount', 'type=bind,source=%s,target=%s' % 83 (out_dir, out_dir if match_directory_structure else MOUNT_OUT), 84 ] 85 if docker_args: 86 cmd.extend(docker_args) 87 if not match_directory_structure: 88 # This only works when src_dir == self.m.path['start_dir'] but that's our 89 # only use case for now. 90 script = MOUNT_SRC + '/' + posixpath.relpath(str(script), str(self.m.path['start_dir'])) 91 cmd.extend([docker_image, script]) 92 if args: 93 cmd.extend(args) 94 95 env = {'DOCKER_CONFIG': '/home/chrome-bot/.docker'} 96 with self.m.env(env): 97 self.m.run.with_retry(self.m.step, name, attempts, cmd=cmd) 98