xref: /aosp_15_r20/external/google-cloud-java/owl-bot-postprocessor/synthtool/gcp/artman.py (revision 55e87721aa1bc457b326496a7ca40f3ea1a63287)
1*55e87721SMatt Gilbride# Copyright 2018 Google LLC
2*55e87721SMatt Gilbride#
3*55e87721SMatt Gilbride# Licensed under the Apache License, Version 2.0 (the "License");
4*55e87721SMatt Gilbride# you may not use this file except in compliance with the License.
5*55e87721SMatt Gilbride# You may obtain a copy of the License at
6*55e87721SMatt Gilbride#
7*55e87721SMatt Gilbride#     https://www.apache.org/licenses/LICENSE-2.0
8*55e87721SMatt Gilbride#
9*55e87721SMatt Gilbride# Unless required by applicable law or agreed to in writing, software
10*55e87721SMatt Gilbride# distributed under the License is distributed on an "AS IS" BASIS,
11*55e87721SMatt Gilbride# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*55e87721SMatt Gilbride# See the License for the specific language governing permissions and
13*55e87721SMatt Gilbride# limitations under the License.
14*55e87721SMatt Gilbride
15*55e87721SMatt Gilbrideimport functools
16*55e87721SMatt Gilbrideimport json
17*55e87721SMatt Gilbrideimport os
18*55e87721SMatt Gilbrideimport platform
19*55e87721SMatt Gilbrideimport tempfile
20*55e87721SMatt Gilbride
21*55e87721SMatt Gilbridefrom synthtool import metadata, shell
22*55e87721SMatt Gilbridefrom synthtool.log import logger
23*55e87721SMatt Gilbride
24*55e87721SMatt GilbrideARTMAN_VERSION = os.environ.get("SYNTHTOOL_ARTMAN_VERSION", "latest")
25*55e87721SMatt Gilbride
26*55e87721SMatt Gilbride
27*55e87721SMatt Gilbrideclass Artman:
28*55e87721SMatt Gilbride    def __init__(self):
29*55e87721SMatt Gilbride        # Docker on mac by default cannot use the default temp file location
30*55e87721SMatt Gilbride        # # instead use the more standard *nix /tmp location\
31*55e87721SMatt Gilbride        if platform.system() == "Darwin":
32*55e87721SMatt Gilbride            tempfile.tempdir = "/tmp"
33*55e87721SMatt Gilbride        self._ensure_dependencies_installed()
34*55e87721SMatt Gilbride        self._install_artman()
35*55e87721SMatt Gilbride        self._report_metadata()
36*55e87721SMatt Gilbride
37*55e87721SMatt Gilbride    @functools.lru_cache()
38*55e87721SMatt Gilbride    def _docker_image_info(self):
39*55e87721SMatt Gilbride        result = shell.run(
40*55e87721SMatt Gilbride            ["docker", "inspect", f"googleapis/artman:{ARTMAN_VERSION}"],
41*55e87721SMatt Gilbride            hide_output=True,
42*55e87721SMatt Gilbride        )
43*55e87721SMatt Gilbride        return json.loads(result.stdout)[0]
44*55e87721SMatt Gilbride
45*55e87721SMatt Gilbride    @property
46*55e87721SMatt Gilbride    def version(self) -> str:
47*55e87721SMatt Gilbride        # The artman version is hidden in the container's environment variables.
48*55e87721SMatt Gilbride        # We could just docker run `artman --version`, but we already have the
49*55e87721SMatt Gilbride        # container info so why not? This is faster as it saves us an exec().
50*55e87721SMatt Gilbride        env_vars = dict(
51*55e87721SMatt Gilbride            value.split("=", 1) for value in self._docker_image_info()["Config"]["Env"]
52*55e87721SMatt Gilbride        )
53*55e87721SMatt Gilbride
54*55e87721SMatt Gilbride        return env_vars.get("ARTMAN_VERSION", "unknown")
55*55e87721SMatt Gilbride
56*55e87721SMatt Gilbride    @property
57*55e87721SMatt Gilbride    def docker_image(self) -> str:
58*55e87721SMatt Gilbride        return self._docker_image_info()["RepoDigests"][0]
59*55e87721SMatt Gilbride
60*55e87721SMatt Gilbride    def run(
61*55e87721SMatt Gilbride        self, image, root_dir, config, *args, generator_dir=None, generator_args=None
62*55e87721SMatt Gilbride    ):
63*55e87721SMatt Gilbride        """Executes artman command in the artman container.
64*55e87721SMatt Gilbride
65*55e87721SMatt Gilbride        Args:
66*55e87721SMatt Gilbride            image:
67*55e87721SMatt Gilbride                The Docker image for artman.
68*55e87721SMatt Gilbride            root_dir:
69*55e87721SMatt Gilbride                The input directory that will be mounted to artman docker
70*55e87721SMatt Gilbride                container as local googleapis directory.
71*55e87721SMatt Gilbride            config:
72*55e87721SMatt Gilbride                Path to artman configuration YAML file.
73*55e87721SMatt Gilbride            *args:
74*55e87721SMatt Gilbride                Arguments to artman that follow ``generate``. Defines which
75*55e87721SMatt Gilbride                artifacts to generate.
76*55e87721SMatt Gilbride            generator_dir (Optional[str]):
77*55e87721SMatt Gilbride                Path to local gapic-generator directory to use for generation.
78*55e87721SMatt Gilbride                By default, the latest version of gapic-generator will be used.
79*55e87721SMatt Gilbride            generator_args (Optional[List[str]]):
80*55e87721SMatt Gilbride                Additional arguments to pass to the gapic generator, such as
81*55e87721SMatt Gilbride                ``--dev_samples``.
82*55e87721SMatt Gilbride        Returns:
83*55e87721SMatt Gilbride            The output directory with artman-generated files.
84*55e87721SMatt Gilbride        """
85*55e87721SMatt Gilbride        container_name = "artman-docker"
86*55e87721SMatt Gilbride        output_dir = root_dir / "artman-genfiles"
87*55e87721SMatt Gilbride
88*55e87721SMatt Gilbride        additional_flags = []
89*55e87721SMatt Gilbride
90*55e87721SMatt Gilbride        if generator_args:
91*55e87721SMatt Gilbride            additional_flags.append(
92*55e87721SMatt Gilbride                "--generator-args='{}'".format(" ".join(generator_args))
93*55e87721SMatt Gilbride            )
94*55e87721SMatt Gilbride
95*55e87721SMatt Gilbride        docker_cmd = ["docker", "run", "--name", container_name, "--rm", "-i"]
96*55e87721SMatt Gilbride
97*55e87721SMatt Gilbride        # Environment variables
98*55e87721SMatt Gilbride        docker_cmd.extend(
99*55e87721SMatt Gilbride            [
100*55e87721SMatt Gilbride                "-e",
101*55e87721SMatt Gilbride                f"HOST_USER_ID={os.getuid()}",
102*55e87721SMatt Gilbride                "-e",
103*55e87721SMatt Gilbride                f"HOST_GROUP_ID={os.getgid()}",
104*55e87721SMatt Gilbride                "-e",
105*55e87721SMatt Gilbride                "RUNNING_IN_ARTMAN_DOCKER=True",
106*55e87721SMatt Gilbride            ]
107*55e87721SMatt Gilbride        )
108*55e87721SMatt Gilbride
109*55e87721SMatt Gilbride        # Local directories to mount as volumes (and set working directory -w)
110*55e87721SMatt Gilbride        docker_cmd.extend(
111*55e87721SMatt Gilbride            [
112*55e87721SMatt Gilbride                "-v",
113*55e87721SMatt Gilbride                f"{root_dir}:{root_dir}",
114*55e87721SMatt Gilbride                "-v",
115*55e87721SMatt Gilbride                f"{output_dir}:{output_dir}",
116*55e87721SMatt Gilbride                "-w",
117*55e87721SMatt Gilbride                root_dir,
118*55e87721SMatt Gilbride            ]
119*55e87721SMatt Gilbride        )
120*55e87721SMatt Gilbride
121*55e87721SMatt Gilbride        # Use local copy of GAPIC generator to generate, if path provided
122*55e87721SMatt Gilbride        if generator_dir:
123*55e87721SMatt Gilbride            docker_cmd.extend(["-v", f"{generator_dir}:/toolkit"])
124*55e87721SMatt Gilbride
125*55e87721SMatt Gilbride        # Run /bin/bash in the image and then provide the shell command to run
126*55e87721SMatt Gilbride        docker_cmd.extend([image, "/bin/bash", "-c"])
127*55e87721SMatt Gilbride
128*55e87721SMatt Gilbride        artman_command = " ".join(
129*55e87721SMatt Gilbride            map(
130*55e87721SMatt Gilbride                str,
131*55e87721SMatt Gilbride                ["artman", "--local", "--config", config]
132*55e87721SMatt Gilbride                + additional_flags
133*55e87721SMatt Gilbride                + ["generate"]
134*55e87721SMatt Gilbride                + list(args),
135*55e87721SMatt Gilbride            )
136*55e87721SMatt Gilbride        )
137*55e87721SMatt Gilbride
138*55e87721SMatt Gilbride        cmd = docker_cmd + [artman_command]
139*55e87721SMatt Gilbride
140*55e87721SMatt Gilbride        shell.run(cmd, cwd=root_dir)
141*55e87721SMatt Gilbride
142*55e87721SMatt Gilbride        return output_dir
143*55e87721SMatt Gilbride
144*55e87721SMatt Gilbride    def _ensure_dependencies_installed(self):
145*55e87721SMatt Gilbride        logger.debug("Ensuring dependencies.")
146*55e87721SMatt Gilbride
147*55e87721SMatt Gilbride        dependencies = ["docker", "git"]
148*55e87721SMatt Gilbride        failed_dependencies = []
149*55e87721SMatt Gilbride        for dependency in dependencies:
150*55e87721SMatt Gilbride            return_code = shell.run(["which", dependency], check=False).returncode
151*55e87721SMatt Gilbride            if return_code:
152*55e87721SMatt Gilbride                failed_dependencies.append(dependency)
153*55e87721SMatt Gilbride
154*55e87721SMatt Gilbride        if failed_dependencies:
155*55e87721SMatt Gilbride            raise EnvironmentError(
156*55e87721SMatt Gilbride                f"Dependencies missing: {', '.join(failed_dependencies)}"
157*55e87721SMatt Gilbride            )
158*55e87721SMatt Gilbride
159*55e87721SMatt Gilbride    def _install_artman(self):
160*55e87721SMatt Gilbride        logger.debug("Pulling artman image.")
161*55e87721SMatt Gilbride        shell.run(
162*55e87721SMatt Gilbride            ["docker", "pull", f"googleapis/artman:{ARTMAN_VERSION}"], hide_output=False
163*55e87721SMatt Gilbride        )
164*55e87721SMatt Gilbride
165*55e87721SMatt Gilbride    def _report_metadata(self):
166*55e87721SMatt Gilbride        metadata.add_generator_source(
167*55e87721SMatt Gilbride            name="artman", version=self.version, docker_image=self.docker_image
168*55e87721SMatt Gilbride        )
169