xref: /aosp_15_r20/external/skia/infra/bots/recipe_modules/docker/api.py (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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      uid_gid_script = self.resource('get_uid_gid.py')
37      step_stdout = self.m.step(
38          name='Get uid and gid',
39          cmd=['python3', uid_gid_script],
40          stdout=self.m.raw_io.output(),
41          step_test_data=(
42              lambda: self.m.raw_io.test_api.stream_output('13:17'))
43          ).stdout.decode('utf-8')
44      uid_gid_pair = step_stdout.rstrip() if step_stdout else ''
45      # Make sure out_dir exists, otherwise mounting will fail.
46      # (Note that the docker --mount option, unlike the --volume option, does
47      # not create this dir as root if it doesn't exist.)
48      self.m.file.ensure_directory('mkdirs out_dir', out_dir, mode=0o777)
49      # ensure_directory won't change the permissions if the dir already exists,
50      # so we need to do that explicitly.
51      self._chmod(out_dir, '777')
52
53      # chmod the src_dir, but not recursively; Swarming writes some files which
54      # we can't access, so "chmod -R" will fail if this is the root workdir.
55      self._chmod(src_dir, '755')
56
57      # Need to make the script executable, or Docker can't run it.
58      self._chmod(script, '0755')
59
60      # Copy any requested files.
61      if copies:
62        for copy in copies:
63          src = copy['src']
64          dest = copy['dst']
65          dirname = self.m.path.dirname(dest)
66          self.m.file.ensure_directory(
67              'mkdirs %s' % dirname, dirname, mode=0o777)
68          self.m.file.copy('cp %s %s' % (src, dest), src, dest)
69          self._chmod(dest, '644')
70
71      # Recursive chmod any requested directories.
72      if recursive_read:
73        for elem in recursive_read:
74          self._chmod(elem, 'a+r', recursive=True)
75
76    # Run.
77    cmd = [
78      'docker', 'run', '--shm-size=2gb', '--rm', '--user', uid_gid_pair,
79      '--mount', 'type=bind,source=%s,target=%s' %
80                 (src_dir, src_dir if match_directory_structure else MOUNT_SRC),
81      '--mount', 'type=bind,source=%s,target=%s' %
82                 (out_dir, out_dir if match_directory_structure else MOUNT_OUT),
83    ]
84    if docker_args:
85      cmd.extend(docker_args)
86    if not match_directory_structure:
87      # This only works when src_dir == self.m.path.start_dir but that's our
88      # only use case for now.
89      script = MOUNT_SRC + '/' + posixpath.relpath(str(script), str(self.m.path.start_dir))
90    cmd.extend([docker_image, script])
91    if args:
92      cmd.extend(args)
93
94    env = {'DOCKER_CONFIG': '/home/chrome-bot/.docker'}
95    with self.m.env(env):
96      self.m.run.with_retry(self.m.step, name, attempts, cmd=cmd)
97