1*55e87721SMatt Gilbride# Copyright 2019 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 json 16*55e87721SMatt Gilbrideimport os 17*55e87721SMatt Gilbridefrom pathlib import Path 18*55e87721SMatt Gilbrideimport re 19*55e87721SMatt Gilbrideimport subprocess 20*55e87721SMatt Gilbrideimport sys 21*55e87721SMatt Gilbride 22*55e87721SMatt Gilbrideimport click 23*55e87721SMatt Gilbrideimport templates 24*55e87721SMatt Gilbride 25*55e87721SMatt Gilbride 26*55e87721SMatt Gilbride@click.group(invoke_without_command=False) 27*55e87721SMatt Gilbride@click.pass_context 28*55e87721SMatt Gilbride@click.version_option(message="%(version)s") 29*55e87721SMatt Gilbridedef main(ctx): 30*55e87721SMatt Gilbride pass 31*55e87721SMatt Gilbride 32*55e87721SMatt Gilbride@main.command() 33*55e87721SMatt Gilbride@click.option( 34*55e87721SMatt Gilbride "--api_shortname", 35*55e87721SMatt Gilbride required=True, 36*55e87721SMatt Gilbride type=str, 37*55e87721SMatt Gilbride prompt="Service name? (e.g. automl)", 38*55e87721SMatt Gilbride help="Name for the new directory name and (default) artifact name" 39*55e87721SMatt Gilbride) 40*55e87721SMatt Gilbride@click.option( 41*55e87721SMatt Gilbride "--name-pretty", 42*55e87721SMatt Gilbride required=True, 43*55e87721SMatt Gilbride type=str, 44*55e87721SMatt Gilbride prompt="Pretty name? (e.g. 'Cloud AutoML')", 45*55e87721SMatt Gilbride help="The human-friendly name that appears in README.md" 46*55e87721SMatt Gilbride) 47*55e87721SMatt Gilbride@click.option( 48*55e87721SMatt Gilbride "--product-docs", 49*55e87721SMatt Gilbride required=True, 50*55e87721SMatt Gilbride type=str, 51*55e87721SMatt Gilbride prompt="Product Documentation URL", 52*55e87721SMatt Gilbride help="Documentation URL that appears in README.md" 53*55e87721SMatt Gilbride) 54*55e87721SMatt Gilbride@click.option( 55*55e87721SMatt Gilbride "--api-description", 56*55e87721SMatt Gilbride required=True, 57*55e87721SMatt Gilbride type=str, 58*55e87721SMatt Gilbride prompt="Description for README. The first sentence is prefixed by the " 59*55e87721SMatt Gilbride "pretty name", 60*55e87721SMatt Gilbride help="Description that appears in README.md" 61*55e87721SMatt Gilbride) 62*55e87721SMatt Gilbride@click.option( 63*55e87721SMatt Gilbride "--release-level", 64*55e87721SMatt Gilbride type=click.Choice(["stable", "preview"]), 65*55e87721SMatt Gilbride default="preview", 66*55e87721SMatt Gilbride show_default=True, 67*55e87721SMatt Gilbride help="A label that appears in repo-metadata.json. The first library " 68*55e87721SMatt Gilbride "generation is always 'preview'." 69*55e87721SMatt Gilbride) 70*55e87721SMatt Gilbride@click.option( 71*55e87721SMatt Gilbride "--transport", 72*55e87721SMatt Gilbride type=click.Choice(["grpc", "http", "both"]), 73*55e87721SMatt Gilbride default="grpc", 74*55e87721SMatt Gilbride show_default=True, 75*55e87721SMatt Gilbride help="A label that appears in repo-metadata.json" 76*55e87721SMatt Gilbride) 77*55e87721SMatt Gilbride@click.option("--language", type=str, default="java", show_default=True) 78*55e87721SMatt Gilbride@click.option( 79*55e87721SMatt Gilbride "--distribution-name", 80*55e87721SMatt Gilbride type=str, 81*55e87721SMatt Gilbride help="Maven coordinates of the generated library. By default it's " 82*55e87721SMatt Gilbride "com.google.cloud:google-cloud-<api_shortname>" 83*55e87721SMatt Gilbride) 84*55e87721SMatt Gilbride@click.option( 85*55e87721SMatt Gilbride "--api-id", 86*55e87721SMatt Gilbride type=str, 87*55e87721SMatt Gilbride help="The value of the apiid parameter used in README.md It has link to " 88*55e87721SMatt Gilbride "https://console.cloud.google.com/flows/enableapi?apiid=<api_id>" 89*55e87721SMatt Gilbride) 90*55e87721SMatt Gilbride@click.option( 91*55e87721SMatt Gilbride "--requires-billing", 92*55e87721SMatt Gilbride type=bool, 93*55e87721SMatt Gilbride default=True, 94*55e87721SMatt Gilbride show_default=True, 95*55e87721SMatt Gilbride help="Based on this value, README.md explains whether billing setup is " 96*55e87721SMatt Gilbride "needed or not." 97*55e87721SMatt Gilbride) 98*55e87721SMatt Gilbride@click.option( 99*55e87721SMatt Gilbride "--destination-name", 100*55e87721SMatt Gilbride type=str, 101*55e87721SMatt Gilbride default=None, 102*55e87721SMatt Gilbride help="The directory name of the new library. By default it's " 103*55e87721SMatt Gilbride "java-<api_shortname>" 104*55e87721SMatt Gilbride) 105*55e87721SMatt Gilbride@click.option( 106*55e87721SMatt Gilbride "--proto-path", 107*55e87721SMatt Gilbride required=True, 108*55e87721SMatt Gilbride type=str, 109*55e87721SMatt Gilbride default=None, 110*55e87721SMatt Gilbride help="Path to proto file from the root of the googleapis repository to the" 111*55e87721SMatt Gilbride "directory that contains the proto files (without the version)." 112*55e87721SMatt Gilbride "For example, to generate the library for 'google/maps/routing/v2', " 113*55e87721SMatt Gilbride "then you specify this value as 'google/maps/routing'" 114*55e87721SMatt Gilbride) 115*55e87721SMatt Gilbride@click.option( 116*55e87721SMatt Gilbride "--cloud-api", 117*55e87721SMatt Gilbride type=bool, 118*55e87721SMatt Gilbride default=True, 119*55e87721SMatt Gilbride show_default=True, 120*55e87721SMatt Gilbride help="If true, the artifact ID of the library is 'google-cloud-'; " 121*55e87721SMatt Gilbride "otherwise 'google-'" 122*55e87721SMatt Gilbride) 123*55e87721SMatt Gilbride@click.option( 124*55e87721SMatt Gilbride "--group-id", 125*55e87721SMatt Gilbride type=str, 126*55e87721SMatt Gilbride default="com.google.cloud", 127*55e87721SMatt Gilbride show_default=True, 128*55e87721SMatt Gilbride help="The group ID of the artifact when distribution name is not set" 129*55e87721SMatt Gilbride) 130*55e87721SMatt Gilbride@click.option( 131*55e87721SMatt Gilbride "--owlbot-image", 132*55e87721SMatt Gilbride type=str, 133*55e87721SMatt Gilbride default="gcr.io/cloud-devrel-public-resources/owlbot-java", 134*55e87721SMatt Gilbride show_default=True, 135*55e87721SMatt Gilbride help="The owlbot container image used in OwlBot.yaml" 136*55e87721SMatt Gilbride) 137*55e87721SMatt Gilbride@click.option( 138*55e87721SMatt Gilbride "--library-type", 139*55e87721SMatt Gilbride type=str, 140*55e87721SMatt Gilbride default="GAPIC_AUTO", 141*55e87721SMatt Gilbride show_default=True, 142*55e87721SMatt Gilbride help="A label that appear in repo-metadata.json to tell how the library is " 143*55e87721SMatt Gilbride "maintained or generated" 144*55e87721SMatt Gilbride) 145*55e87721SMatt Gilbride@click.option( 146*55e87721SMatt Gilbride "--googleapis-gen-url", 147*55e87721SMatt Gilbride type=str, 148*55e87721SMatt Gilbride default="https://github.com/googleapis/googleapis-gen.git", 149*55e87721SMatt Gilbride show_default=True, 150*55e87721SMatt Gilbride help="The URL of the repository that has generated Java code from proto " 151*55e87721SMatt Gilbride "service definition" 152*55e87721SMatt Gilbride) 153*55e87721SMatt Gilbridedef generate( 154*55e87721SMatt Gilbride api_shortname, 155*55e87721SMatt Gilbride name_pretty, 156*55e87721SMatt Gilbride product_docs, 157*55e87721SMatt Gilbride api_description, 158*55e87721SMatt Gilbride release_level, 159*55e87721SMatt Gilbride distribution_name, 160*55e87721SMatt Gilbride api_id, 161*55e87721SMatt Gilbride requires_billing, 162*55e87721SMatt Gilbride transport, 163*55e87721SMatt Gilbride language, 164*55e87721SMatt Gilbride destination_name, 165*55e87721SMatt Gilbride proto_path, 166*55e87721SMatt Gilbride cloud_api, 167*55e87721SMatt Gilbride group_id, 168*55e87721SMatt Gilbride owlbot_image, 169*55e87721SMatt Gilbride library_type, 170*55e87721SMatt Gilbride googleapis_gen_url, 171*55e87721SMatt Gilbride): 172*55e87721SMatt Gilbride cloud_prefix = "cloud-" if cloud_api else "" 173*55e87721SMatt Gilbride 174*55e87721SMatt Gilbride output_name = destination_name if destination_name else api_shortname 175*55e87721SMatt Gilbride if distribution_name is None: 176*55e87721SMatt Gilbride distribution_name = f"{group_id}:google-{cloud_prefix}{output_name}" 177*55e87721SMatt Gilbride 178*55e87721SMatt Gilbride distribution_name_short = re.split(r"[:\/]", distribution_name)[-1] 179*55e87721SMatt Gilbride 180*55e87721SMatt Gilbride if api_id is None: 181*55e87721SMatt Gilbride api_id = f"{api_shortname}.googleapis.com" 182*55e87721SMatt Gilbride 183*55e87721SMatt Gilbride if not product_docs.startswith("https"): 184*55e87721SMatt Gilbride sys.exit("product_docs must starts with 'https://'") 185*55e87721SMatt Gilbride 186*55e87721SMatt Gilbride client_documentation = ( 187*55e87721SMatt Gilbride f"https://cloud.google.com/{language}/docs/reference/{distribution_name_short}/latest/overview" 188*55e87721SMatt Gilbride ) 189*55e87721SMatt Gilbride 190*55e87721SMatt Gilbride if proto_path is None: 191*55e87721SMatt Gilbride proto_path = f"/google/cloud/{api_shortname}" 192*55e87721SMatt Gilbride 193*55e87721SMatt Gilbride if api_shortname == "": 194*55e87721SMatt Gilbride sys.exit("api_shortname is empty") 195*55e87721SMatt Gilbride 196*55e87721SMatt Gilbride repo_metadata = { 197*55e87721SMatt Gilbride "api_shortname": api_shortname, 198*55e87721SMatt Gilbride "name_pretty": name_pretty, 199*55e87721SMatt Gilbride "product_documentation": product_docs, 200*55e87721SMatt Gilbride "api_description": api_description, 201*55e87721SMatt Gilbride "client_documentation": client_documentation, 202*55e87721SMatt Gilbride "release_level": release_level, 203*55e87721SMatt Gilbride "transport": transport, 204*55e87721SMatt Gilbride "language": language, 205*55e87721SMatt Gilbride "repo": f"googleapis/{language}-{output_name}", 206*55e87721SMatt Gilbride "repo_short": f"{language}-{output_name}", 207*55e87721SMatt Gilbride "distribution_name": distribution_name, 208*55e87721SMatt Gilbride "api_id": api_id, 209*55e87721SMatt Gilbride "library_type": library_type, 210*55e87721SMatt Gilbride } 211*55e87721SMatt Gilbride if requires_billing: 212*55e87721SMatt Gilbride repo_metadata["requires_billing"] = True 213*55e87721SMatt Gilbride 214*55e87721SMatt Gilbride # Initialize workdir 215*55e87721SMatt Gilbride workdir = Path(f"{sys.path[0]}/../../java-{output_name}").resolve() 216*55e87721SMatt Gilbride if os.path.isdir(workdir): 217*55e87721SMatt Gilbride sys.exit( 218*55e87721SMatt Gilbride "Couldn't create the module because " 219*55e87721SMatt Gilbride f"the module {workdir} already exists. In Java client library " 220*55e87721SMatt Gilbride "generation, a new API version of an existing module does not " 221*55e87721SMatt Gilbride "require new-client.py invocation. " 222*55e87721SMatt Gilbride "See go/yoshi-java-new-client#adding-a-new-service-version-by-owlbot." 223*55e87721SMatt Gilbride ) 224*55e87721SMatt Gilbride print(f"Creating a new module {workdir}") 225*55e87721SMatt Gilbride os.makedirs(workdir, exist_ok=False) 226*55e87721SMatt Gilbride # write .repo-metadata.json file 227*55e87721SMatt Gilbride with open(workdir / ".repo-metadata.json", "w") as fp: 228*55e87721SMatt Gilbride json.dump(repo_metadata, fp, indent=2) 229*55e87721SMatt Gilbride 230*55e87721SMatt Gilbride # create owlbot.py 231*55e87721SMatt Gilbride templates.render( 232*55e87721SMatt Gilbride template_name="owlbot.py.j2", 233*55e87721SMatt Gilbride output_name=str(workdir / "owlbot.py"), 234*55e87721SMatt Gilbride should_include_templates=True, 235*55e87721SMatt Gilbride template_excludes=[], 236*55e87721SMatt Gilbride ) 237*55e87721SMatt Gilbride 238*55e87721SMatt Gilbride # In monorepo, .OwlBot.yaml needs to be in the directory of the module. 239*55e87721SMatt Gilbride owlbot_yaml_location_from_module = ".OwlBot.yaml" 240*55e87721SMatt Gilbride # create owlbot config 241*55e87721SMatt Gilbride templates.render( 242*55e87721SMatt Gilbride template_name="owlbot.yaml.monorepo.j2", 243*55e87721SMatt Gilbride output_name=str(workdir / owlbot_yaml_location_from_module), 244*55e87721SMatt Gilbride artifact_name=distribution_name_short, 245*55e87721SMatt Gilbride proto_path=proto_path, 246*55e87721SMatt Gilbride module_name=f"java-{output_name}", 247*55e87721SMatt Gilbride api_shortname=api_shortname 248*55e87721SMatt Gilbride ) 249*55e87721SMatt Gilbride 250*55e87721SMatt Gilbride # get the sha256 digets for the owlbot image 251*55e87721SMatt Gilbride subprocess.check_call(["docker", "pull", "-q", owlbot_image]) 252*55e87721SMatt Gilbride owlbot_image_digest = ( 253*55e87721SMatt Gilbride subprocess.check_output( 254*55e87721SMatt Gilbride ["docker", "inspect", "--format='{{index .RepoDigests 0}}", owlbot_image,], 255*55e87721SMatt Gilbride encoding="utf-8", 256*55e87721SMatt Gilbride ) 257*55e87721SMatt Gilbride .strip() 258*55e87721SMatt Gilbride .split("@")[-1] 259*55e87721SMatt Gilbride ) 260*55e87721SMatt Gilbride 261*55e87721SMatt Gilbride user = subprocess.check_output(["id", "-u"], encoding="utf8").strip() 262*55e87721SMatt Gilbride group = subprocess.check_output(["id", "-g"], encoding="utf8").strip() 263*55e87721SMatt Gilbride 264*55e87721SMatt Gilbride # run owlbot copy 265*55e87721SMatt Gilbride print("Cloning googleapis-gen...") 266*55e87721SMatt Gilbride subprocess.check_call(["git", "clone", "-q", googleapis_gen_url, "./gen/googleapis-gen"], cwd=workdir) 267*55e87721SMatt Gilbride subprocess.check_call(["docker", "pull", "gcr.io/cloud-devrel-public-resources/owlbot-cli:latest"]) 268*55e87721SMatt Gilbride copy_code_parameters = [ 269*55e87721SMatt Gilbride "docker", 270*55e87721SMatt Gilbride "run", 271*55e87721SMatt Gilbride "--rm", 272*55e87721SMatt Gilbride "--user", 273*55e87721SMatt Gilbride f"{user}:{group}", 274*55e87721SMatt Gilbride "-v", 275*55e87721SMatt Gilbride f"{workdir}:/repo", 276*55e87721SMatt Gilbride "-v", 277*55e87721SMatt Gilbride ""f"{workdir}""/gen/googleapis-gen:/googleapis-gen", 278*55e87721SMatt Gilbride "-w", 279*55e87721SMatt Gilbride "/repo", 280*55e87721SMatt Gilbride "--env", "HOME=/tmp", 281*55e87721SMatt Gilbride "gcr.io/cloud-devrel-public-resources/owlbot-cli:latest", 282*55e87721SMatt Gilbride "copy-code", 283*55e87721SMatt Gilbride "--source-repo=/googleapis-gen", 284*55e87721SMatt Gilbride f"--config-file={owlbot_yaml_location_from_module}" 285*55e87721SMatt Gilbride ] 286*55e87721SMatt Gilbride print("Running copy-code: " + str(copy_code_parameters)) 287*55e87721SMatt Gilbride print(" in directory: " + str(workdir)) 288*55e87721SMatt Gilbride subprocess.check_call( 289*55e87721SMatt Gilbride copy_code_parameters, 290*55e87721SMatt Gilbride cwd=workdir, 291*55e87721SMatt Gilbride ) 292*55e87721SMatt Gilbride 293*55e87721SMatt Gilbride print("Removing googleapis-gen...") 294*55e87721SMatt Gilbride subprocess.check_call(["rm", "-fr", "gen"], cwd=workdir) 295*55e87721SMatt Gilbride 296*55e87721SMatt Gilbride # Bringing owl-bot-staging from the new module's directory to the root 297*55e87721SMatt Gilbride # directory so that owlbot-java can process them. 298*55e87721SMatt Gilbride subprocess.check_call( 299*55e87721SMatt Gilbride [ 300*55e87721SMatt Gilbride "mv", 301*55e87721SMatt Gilbride "owl-bot-staging", 302*55e87721SMatt Gilbride "../" 303*55e87721SMatt Gilbride ], 304*55e87721SMatt Gilbride cwd=workdir, 305*55e87721SMatt Gilbride ) 306*55e87721SMatt Gilbride monorepo_root=(workdir / '..').resolve() 307*55e87721SMatt Gilbride print("monorepo_root=",monorepo_root) 308*55e87721SMatt Gilbride print("Running the post-processor...") 309*55e87721SMatt Gilbride subprocess.check_call( 310*55e87721SMatt Gilbride [ 311*55e87721SMatt Gilbride "docker", 312*55e87721SMatt Gilbride "run", 313*55e87721SMatt Gilbride "--rm", 314*55e87721SMatt Gilbride "-v", 315*55e87721SMatt Gilbride f"{monorepo_root}:/workspace", 316*55e87721SMatt Gilbride "--user", 317*55e87721SMatt Gilbride f"{user}:{group}", 318*55e87721SMatt Gilbride owlbot_image, 319*55e87721SMatt Gilbride ], 320*55e87721SMatt Gilbride cwd=monorepo_root, 321*55e87721SMatt Gilbride ) 322*55e87721SMatt Gilbride 323*55e87721SMatt Gilbride # Remove irrelevant files from templates 324*55e87721SMatt Gilbride subprocess.check_call( 325*55e87721SMatt Gilbride ["bash", "generation/update_owlbot_postprocessor_config.sh"], 326*55e87721SMatt Gilbride cwd=monorepo_root 327*55e87721SMatt Gilbride ) 328*55e87721SMatt Gilbride subprocess.check_call( 329*55e87721SMatt Gilbride ["bash", "generation/delete_non_generated_samples.sh"], 330*55e87721SMatt Gilbride cwd=monorepo_root 331*55e87721SMatt Gilbride ) 332*55e87721SMatt Gilbride 333*55e87721SMatt Gilbride print("Regenerating the BOM") 334*55e87721SMatt Gilbride subprocess.check_call( 335*55e87721SMatt Gilbride [ 336*55e87721SMatt Gilbride "bash", "generation/generate_gapic_bom.sh", 337*55e87721SMatt Gilbride ], 338*55e87721SMatt Gilbride cwd=monorepo_root, 339*55e87721SMatt Gilbride ) 340*55e87721SMatt Gilbride 341*55e87721SMatt Gilbride print("Regenerating root pom.xml") 342*55e87721SMatt Gilbride 343*55e87721SMatt Gilbride # This script takes care of updating the root pom.xml 344*55e87721SMatt Gilbride os.system(f"cd {monorepo_root} && generation/generate_root_pom.sh") 345*55e87721SMatt Gilbride 346*55e87721SMatt Gilbride print("Consolidating configurations") 347*55e87721SMatt Gilbride subprocess.check_call( 348*55e87721SMatt Gilbride [ 349*55e87721SMatt Gilbride "bash", "generation/consolidate_config.sh" 350*55e87721SMatt Gilbride ], 351*55e87721SMatt Gilbride cwd=monorepo_root, 352*55e87721SMatt Gilbride ) 353*55e87721SMatt Gilbride print("Setting parent poms") 354*55e87721SMatt Gilbride subprocess.check_call( 355*55e87721SMatt Gilbride [ 356*55e87721SMatt Gilbride "bash", "generation/set_parent_pom.sh" 357*55e87721SMatt Gilbride ], 358*55e87721SMatt Gilbride cwd=monorepo_root, 359*55e87721SMatt Gilbride ) 360*55e87721SMatt Gilbride 361*55e87721SMatt Gilbride print("Applying the versions") 362*55e87721SMatt Gilbride subprocess.check_call( 363*55e87721SMatt Gilbride [ 364*55e87721SMatt Gilbride "bash", "generation/apply_current_versions.sh" 365*55e87721SMatt Gilbride ], 366*55e87721SMatt Gilbride cwd=monorepo_root, 367*55e87721SMatt Gilbride ) 368*55e87721SMatt Gilbride 369*55e87721SMatt Gilbride print("Adding annotations in readme") 370*55e87721SMatt Gilbride subprocess.check_call( 371*55e87721SMatt Gilbride [ 372*55e87721SMatt Gilbride "bash", "generation/readme_update.sh" 373*55e87721SMatt Gilbride ], 374*55e87721SMatt Gilbride cwd=monorepo_root, 375*55e87721SMatt Gilbride ) 376*55e87721SMatt Gilbride 377*55e87721SMatt Gilbride print(f"Prepared new library in {workdir}") 378*55e87721SMatt Gilbride print(f"Please create a pull request:\n" 379*55e87721SMatt Gilbride f" $ git checkout -b new_module_java-{output_name}\n" 380*55e87721SMatt Gilbride f" $ git add .\n" 381*55e87721SMatt Gilbride f" $ git commit -m 'feat: [{api_shortname}] new module for {api_shortname}'\n" 382*55e87721SMatt Gilbride f" $ gh pr create --title 'feat: [{api_shortname}] new module for {api_shortname}'") 383*55e87721SMatt Gilbride 384*55e87721SMatt Gilbrideif __name__ == "__main__": 385*55e87721SMatt Gilbride main() 386