blob: 454451b62912ccf8e2fda3d0f65ffd157fd1ef6c [file] [log] [blame] [edit]
# Copyright 2019 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
import os
from pathlib import Path
import re
import subprocess
import sys
import click
import templates
@click.group(invoke_without_command=False)
@click.pass_context
@click.version_option(message="%(version)s")
def main(ctx):
pass
@main.command()
@click.option(
"--api_shortname",
required=True,
type=str,
prompt="Service name? (e.g. automl)",
help="Name for the new directory name and (default) artifact name"
)
@click.option(
"--name-pretty",
required=True,
type=str,
prompt="Pretty name? (e.g. 'Cloud AutoML')",
help="The human-friendly name that appears in README.md"
)
@click.option(
"--product-docs",
required=True,
type=str,
prompt="Product Documentation URL",
help="Documentation URL that appears in README.md"
)
@click.option(
"--api-description",
required=True,
type=str,
prompt="Description for README. The first sentence is prefixed by the "
"pretty name",
help="Description that appears in README.md"
)
@click.option(
"--release-level",
type=click.Choice(["stable", "preview"]),
default="preview",
show_default=True,
help="A label that appears in repo-metadata.json. The first library "
"generation is always 'preview'."
)
@click.option(
"--transport",
type=click.Choice(["grpc", "http", "both"]),
default="grpc",
show_default=True,
help="A label that appears in repo-metadata.json"
)
@click.option("--language", type=str, default="java", show_default=True)
@click.option(
"--distribution-name",
type=str,
help="Maven coordinates of the generated library. By default it's "
"com.google.cloud:google-cloud-<api_shortname>"
)
@click.option(
"--api-id",
type=str,
help="The value of the apiid parameter used in README.md It has link to "
"https://console.cloud.google.com/flows/enableapi?apiid=<api_id>"
)
@click.option(
"--requires-billing",
type=bool,
default=True,
show_default=True,
help="Based on this value, README.md explains whether billing setup is "
"needed or not."
)
@click.option(
"--destination-name",
type=str,
default=None,
help="The directory name of the new library. By default it's "
"java-<api_shortname>"
)
@click.option(
"--proto-path",
required=True,
type=str,
default=None,
help="Path to proto file from the root of the googleapis repository to the"
"directory that contains the proto files (without the version)."
"For example, to generate the library for 'google/maps/routing/v2', "
"then you specify this value as 'google/maps/routing'"
)
@click.option(
"--cloud-api",
type=bool,
default=True,
show_default=True,
help="If true, the artifact ID of the library is 'google-cloud-'; "
"otherwise 'google-'"
)
@click.option(
"--group-id",
type=str,
default="com.google.cloud",
show_default=True,
help="The group ID of the artifact when distribution name is not set"
)
@click.option(
"--owlbot-image",
type=str,
default="gcr.io/cloud-devrel-public-resources/owlbot-java",
show_default=True,
help="The owlbot container image used in OwlBot.yaml"
)
@click.option(
"--library-type",
type=str,
default="GAPIC_AUTO",
show_default=True,
help="A label that appear in repo-metadata.json to tell how the library is "
"maintained or generated"
)
@click.option(
"--googleapis-gen-url",
type=str,
default="https://github.com/googleapis/googleapis-gen.git",
show_default=True,
help="The URL of the repository that has generated Java code from proto "
"service definition"
)
def generate(
api_shortname,
name_pretty,
product_docs,
api_description,
release_level,
distribution_name,
api_id,
requires_billing,
transport,
language,
destination_name,
proto_path,
cloud_api,
group_id,
owlbot_image,
library_type,
googleapis_gen_url,
):
cloud_prefix = "cloud-" if cloud_api else ""
output_name = destination_name if destination_name else api_shortname
if distribution_name is None:
distribution_name = f"{group_id}:google-{cloud_prefix}{output_name}"
distribution_name_short = re.split(r"[:\/]", distribution_name)[-1]
if api_id is None:
api_id = f"{api_shortname}.googleapis.com"
if not product_docs.startswith("https"):
sys.exit("product_docs must starts with 'https://'")
client_documentation = (
f"https://cloud.google.com/{language}/docs/reference/{distribution_name_short}/latest/overview"
)
if proto_path is None:
proto_path = f"/google/cloud/{api_shortname}"
if api_shortname == "":
sys.exit("api_shortname is empty")
repo_metadata = {
"api_shortname": api_shortname,
"name_pretty": name_pretty,
"product_documentation": product_docs,
"api_description": api_description,
"client_documentation": client_documentation,
"release_level": release_level,
"transport": transport,
"language": language,
"repo": f"googleapis/{language}-{output_name}",
"repo_short": f"{language}-{output_name}",
"distribution_name": distribution_name,
"api_id": api_id,
"library_type": library_type,
}
if requires_billing:
repo_metadata["requires_billing"] = True
# Initialize workdir
workdir = Path(f"{sys.path[0]}/../../java-{output_name}").resolve()
if os.path.isdir(workdir):
sys.exit(
"Couldn't create the module because "
f"the module {workdir} already exists. In Java client library "
"generation, a new API version of an existing module does not "
"require new-client.py invocation. "
"See go/yoshi-java-new-client#adding-a-new-service-version-by-owlbot."
)
print(f"Creating a new module {workdir}")
os.makedirs(workdir, exist_ok=False)
# write .repo-metadata.json file
with open(workdir / ".repo-metadata.json", "w") as fp:
json.dump(repo_metadata, fp, indent=2)
# create owlbot.py
templates.render(
template_name="owlbot.py.j2",
output_name=str(workdir / "owlbot.py"),
should_include_templates=True,
template_excludes=[],
)
# In monorepo, .OwlBot.yaml needs to be in the directory of the module.
owlbot_yaml_location_from_module = ".OwlBot.yaml"
# create owlbot config
templates.render(
template_name="owlbot.yaml.monorepo.j2",
output_name=str(workdir / owlbot_yaml_location_from_module),
artifact_name=distribution_name_short,
proto_path=proto_path,
module_name=f"java-{output_name}",
api_shortname=api_shortname
)
# get the sha256 digets for the owlbot image
subprocess.check_call(["docker", "pull", "-q", owlbot_image])
owlbot_image_digest = (
subprocess.check_output(
["docker", "inspect", "--format='{{index .RepoDigests 0}}", owlbot_image,],
encoding="utf-8",
)
.strip()
.split("@")[-1]
)
user = subprocess.check_output(["id", "-u"], encoding="utf8").strip()
group = subprocess.check_output(["id", "-g"], encoding="utf8").strip()
# run owlbot copy
print("Cloning googleapis-gen...")
subprocess.check_call(["git", "clone", "-q", googleapis_gen_url, "./gen/googleapis-gen"], cwd=workdir)
subprocess.check_call(["docker", "pull", "gcr.io/cloud-devrel-public-resources/owlbot-cli:latest"])
copy_code_parameters = [
"docker",
"run",
"--rm",
"--user",
f"{user}:{group}",
"-v",
f"{workdir}:/repo",
"-v",
""f"{workdir}""/gen/googleapis-gen:/googleapis-gen",
"-w",
"/repo",
"--env", "HOME=/tmp",
"gcr.io/cloud-devrel-public-resources/owlbot-cli:latest",
"copy-code",
"--source-repo=/googleapis-gen",
f"--config-file={owlbot_yaml_location_from_module}"
]
print("Running copy-code: " + str(copy_code_parameters))
print(" in directory: " + str(workdir))
subprocess.check_call(
copy_code_parameters,
cwd=workdir,
)
print("Removing googleapis-gen...")
subprocess.check_call(["rm", "-fr", "gen"], cwd=workdir)
# Bringing owl-bot-staging from the new module's directory to the root
# directory so that owlbot-java can process them.
subprocess.check_call(
[
"mv",
"owl-bot-staging",
"../"
],
cwd=workdir,
)
monorepo_root=(workdir / '..').resolve()
print("monorepo_root=",monorepo_root)
print("Running the post-processor...")
subprocess.check_call(
[
"docker",
"run",
"--rm",
"-v",
f"{monorepo_root}:/workspace",
"--user",
f"{user}:{group}",
owlbot_image,
],
cwd=monorepo_root,
)
# Remove irrelevant files from templates
subprocess.check_call(
["bash", "generation/update_owlbot_postprocessor_config.sh"],
cwd=monorepo_root
)
subprocess.check_call(
["bash", "generation/delete_non_generated_samples.sh"],
cwd=monorepo_root
)
print("Regenerating the BOM")
subprocess.check_call(
[
"bash", "generation/generate_gapic_bom.sh",
],
cwd=monorepo_root,
)
print("Regenerating root pom.xml")
# This script takes care of updating the root pom.xml
os.system(f"cd {monorepo_root} && generation/generate_root_pom.sh")
print("Consolidating configurations")
subprocess.check_call(
[
"bash", "generation/consolidate_config.sh"
],
cwd=monorepo_root,
)
print("Setting parent poms")
subprocess.check_call(
[
"bash", "generation/set_parent_pom.sh"
],
cwd=monorepo_root,
)
print("Applying the versions")
subprocess.check_call(
[
"bash", "generation/apply_current_versions.sh"
],
cwd=monorepo_root,
)
print("Adding annotations in readme")
subprocess.check_call(
[
"bash", "generation/readme_update.sh"
],
cwd=monorepo_root,
)
print(f"Prepared new library in {workdir}")
print(f"Please create a pull request:\n"
f" $ git checkout -b new_module_java-{output_name}\n"
f" $ git add .\n"
f" $ git commit -m 'feat: [{api_shortname}] new module for {api_shortname}'\n"
f" $ gh pr create --title 'feat: [{api_shortname}] new module for {api_shortname}'")
if __name__ == "__main__":
main()