f20 - docker file & instructions for test_finder

BUG=b:200620192
TEST=./build-dockerimage.sh $chroot $sysroot local

Change-Id: I6a7668968ea0850d2a19dea5939005e94ccbca2f
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/dev-util/+/3219659
Reviewed-by: Jesse McGuire <jessemcguire@google.com>
Reviewed-by: Seewai Fu <seewaifu@google.com>
Commit-Queue: Derek Beckett <dbeckett@chromium.org>
Tested-by: Derek Beckett <dbeckett@chromium.org>
diff --git a/src/chromiumos/test/test_finder/docker/Dockerfile b/src/chromiumos/test/test_finder/docker/Dockerfile
new file mode 100644
index 0000000..a2ac449
--- /dev/null
+++ b/src/chromiumos/test/test_finder/docker/Dockerfile
@@ -0,0 +1,16 @@
+# Copyright 2021 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+FROM ubuntu:bionic
+COPY cros-test-finder /usr/bin/
+
+
+RUN mkdir -p /tmp/test
+# Bring in metadata to 1 location
+COPY *.pb /tmp/test/metadata/
+
+
+# Own cros-test-finder & metadata
+RUN chmod 755 /usr/bin/cros-test-finder
+RUN chmod 644 /tmp/test/metadata/*.pb
\ No newline at end of file
diff --git a/src/chromiumos/test/test_finder/docker/build-dockerimage.sh b/src/chromiumos/test/test_finder/docker/build-dockerimage.sh
new file mode 100755
index 0000000..0a847d8
--- /dev/null
+++ b/src/chromiumos/test/test_finder/docker/build-dockerimage.sh
@@ -0,0 +1,75 @@
+#!/bin/bash
+# Copyright 2021 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+set -e
+readonly script_dir="$(dirname "$(realpath -e "${BASH_SOURCE[0]}")")"
+
+source "${script_dir}/../../../../../test/docker/util.sh"
+
+usage() {
+    echo "Usage: $0 <chroot> <sysroot> [options] [key=value...]"
+    echo
+    echo "Build a docker container for the cros-test-finder service."
+    echo
+    echo "Args:"
+    echo "  chroot  - Path to the ChromeOS chroot on the host system."
+    echo "  sysroot - Path inside of the chroot to the board sysroot."
+    echo "  labels  - Zero or more key=value strings to apply as labels to container."
+    echo
+    echo "Options:"
+    echo "  --tags/-t - Comma separated list of tag names to apply to container"
+    exit 1
+}
+
+if [[ $# -lt 3 ]]; then
+    usage
+fi
+
+prep_container() {
+  # Determine the full path to this script and use it to resolve the checkout src path.
+  readonly src="${script_dir}/../../../../../../../"
+
+  src_root="$(realpath "${src}")"
+
+  # Set the src/config/bin dir and load the common.sh src
+  readonly config_bin_dir="${src_root}/config/bin"
+  # shellcheck source=/dev/null
+  source "${config_bin_dir}/common.sh"
+
+  create_venv
+
+  python3 "${script_dir}"/metadata.py -chroot="${chroot_path}" -sysroot="${sysroot_path}" -path="${output_dir}" -force_path=True -src="${src_root}"
+}
+
+readonly chroot_path="$1"; shift
+readonly sysroot_path="$1"; shift
+
+tags=""
+while [[ $# -gt 0 ]]; do
+    case $1 in
+        --tags|-t)
+            tags="$2"
+            shift
+            shift
+            ;;
+        *)
+            break
+            ;;
+    esac
+done
+
+
+readonly output_dir="tmp/docker/crostestfinder"
+readonly full_output_dir="${chroot_path}/${sysroot_path}/${output_dir}"
+
+prep_container
+
+
+build_container_image               \
+    "crostestfinder"             \
+    "${full_output_dir}/Dockerfile" \
+    "${chroot_path}"                \
+    "${tags}"                       \
+    "$@"
diff --git a/src/chromiumos/test/test_finder/docker/metadata.py b/src/chromiumos/test/test_finder/docker/metadata.py
new file mode 100644
index 0000000..526a777
--- /dev/null
+++ b/src/chromiumos/test/test_finder/docker/metadata.py
@@ -0,0 +1,164 @@
+#!/usr/bin/env python3
+
+# Copyright 2021 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Prep everything for for a Docker Build Context."""
+
+
+import argparse
+
+import os
+import pathlib
+import shutil
+import sys
+
+sys.path.insert(1, str(pathlib.Path(__file__).parent.resolve()/'../../'))
+
+def parse_local_arguments():
+  """Parse the CLI."""
+  parser = argparse.ArgumentParser(
+      description='Prep Tauto, Tast, & Services for DockerBuild.')
+  parser.add_argument('-chroot',
+                      dest='chroot',
+                      help='chroot (String): The chroot path to use.')
+  parser.add_argument('-sysroot',
+                      dest='sysroot',
+                      help=' sysroot (String): The sysroot path to use.')
+  parser.add_argument('-path',
+                      dest='path',
+                      help='path (String): Path to write artifacts to.')
+  parser.add_argument('-force_path',
+                      dest='force_path',
+                      default=False,
+                      help='Delete anything conflicting in the outpath.')
+  parser.add_argument('-src',
+                      dest='src',
+                      help='path (String): The src tree path.')
+
+  args = parser.parse_args()
+  return args
+
+
+class DockerPrep():
+  """Prep Needed files for the Test Execution Container Docker Build."""
+
+  def __init__(self, args):
+    """@param args (ArgumentParser): .chroot, .sysroot, .path."""
+    self.args = args
+    self.full_autotest = ''
+    self.full_bin = ''
+    self.full_out = ''
+    self.build_path = ''
+    self.chroot_bin = ''
+    self.chroot = self.args.chroot
+    self.src_root = self.args.src
+    self.sysroot = self.args.sysroot
+    if self.sysroot.startswith('/'):
+      self.sysroot = self.sysroot[1:]
+
+  def config_paths(self):
+    """Build up the paths needed in local mem."""
+    self.build_path = os.path.join(self.chroot, self.sysroot)
+    self.full_autotest = os.path.join(self.build_path,
+                                      'usr/local/build/autotest')
+    self.chroot_bin = os.path.join(self.chroot, 'usr/bin')
+    self.full_bin = os.path.join(self.build_path, 'usr/bin')
+    self.full_out = os.path.join(self.build_path, self.args.path)
+    self.validate_paths()
+    self.prep_artifact_dir()
+
+  def validate_paths(self):
+    """Verify the paths generated are valid/exist."""
+    if not os.path.isdir(self.full_autotest):
+      if not os.path.exists(self.full_autotest):
+        raise Exception('Autotest path %s does not exist' %
+                        self.full_autotest)
+      raise Exception('Autotest path %s is not a directory' %
+                      self.full_autotest)
+
+    if not os.path.isdir(self.build_path):
+      if not os.path.exists(self.build_path):
+        raise Exception('sysroot %s does not exist' % self.build_path)
+      raise Exception('sysroot %s is not a directory' % self.build_path)
+
+  def prep_artifact_dir(self):
+    """Prepare the artifact dir. If it does not exist, create it."""
+    if os.path.exists(self.full_out):
+      if self.args.force_path:
+        print('Deleting existing prepdir {}'.format(self.full_out))
+        shutil.rmtree(self.full_out)
+      else:
+        raise Exception('outpath %s exists and force is not set.' %
+                        self.full_out)
+    os.makedirs(self.full_out, exist_ok=True)
+
+  def copy_services(self):
+    """Copy services needed for Docker."""
+    shutil.copy(os.path.join(self.chroot_bin, 'cros-test-finder'),
+                self.full_out)
+
+
+  def copy_metadata(self):
+    """Return the absolute path of the metadata files."""
+    # Relative to build
+    _BUILD_METADATA_FILES = [
+        ('usr/local/build/autotest/autotest_metadata.pb',
+         os.path.join(self.full_out, 'autotest_metadata.pb')),
+        ('usr/share/tast/metadata/local/cros.pb',
+         os.path.join(self.full_out, 'local_cros.pb')),
+        ('build/share/tast/metadata/local/crosint.pb',
+         os.path.join(self.full_out, 'crosint.pb'))
+    ]
+
+    # relative to chroot.
+    _CHROOT_METADATA_FILES = [('usr/share/tast/metadata/remote/cros.pb',
+                               os.path.join(self.full_out,
+                                            'remote_cros.pb'))]
+
+    for f, d in _BUILD_METADATA_FILES:
+      full_md_path = FromSysrootPath(self.build_path, f)
+      if not os.path.exists(full_md_path):
+        print('Path %s does not exist, skipping' % full_md_path)
+        continue
+      shutil.copyfile(full_md_path, os.path.join(self.full_out, d))
+
+    for f, d in _CHROOT_METADATA_FILES:
+      full_md_path = FromSysrootPath(self.chroot, f)
+      if not os.path.exists(full_md_path):
+        print('Path %s does not exist, skipping' % full_md_path)
+        continue
+      shutil.copyfile(full_md_path, os.path.join(self.full_out, d))
+
+  def copy_dockerfiles(self):
+    """Copy Dockerfiles needed to build the container to the output dir."""
+    dockerfile_relative_dir = 'platform/dev/src/chromiumos/test/test_finder/docker'
+
+    src = os.path.join(self.src_root, dockerfile_relative_dir)
+    for item in os.listdir(src):
+      s = os.path.join(src, item)
+      d = os.path.join(self.full_out, item)
+      shutil.copy2(s, d)
+
+
+def FromSysrootPath(sysroot, file):
+  return os.path.join(sysroot, file)
+
+def main():
+  """Entry point."""
+
+  if not (sys.version_info.major == 3 and sys.version_info.minor >= 6):
+    print('python3.6 or greater is required.')
+    sys.exit(1)
+
+  args = parse_local_arguments()
+  builder = DockerPrep(args)
+  builder.config_paths()
+  builder.copy_services()
+  builder.copy_metadata()
+  builder.copy_dockerfiles()
+
+
+if __name__ == '__main__':
+  main()