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