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