solutions_builder/cli/component.py (153 lines of code) (raw):

""" Copyright 2023 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 typer import git import shutil from typing import Optional from typing_extensions import Annotated from copier import run_copy from .cli_utils import * def add_component(component_name, template_path: Annotated[str, typer.Option("--template", "-t")] = None, solution_path: Annotated[Optional[str], typer.Argument()] = ".", destination_path: Annotated[str, typer.Option("--dest", "-d")] = "components", yes: Optional[bool] = False, answers=None): # validate_solution_folder(solution_path) # Check if template_path is empty. if not template_path: print("Please set --template or -t to a local folder path, " "remote git repo, or one of modules below:") list_component_templates() return confirm( f"This will add component '{component_name}' to " f"'{destination_path}' folder. Continue?", skip=yes) answers_dict = get_answers_dict(answers) answers_dict["destination_path"] = destination_path process_component("add", component_name, template_path, solution_path, destination_path, data=answers_dict) print_success( f"Complete. Component {component_name} added to solution at {solution_path}\n" ) def update_component(component_name, solution_path: Annotated[Optional[str], typer.Argument()] = ".", destination_path: Annotated[str, typer.Option("--dest", "-d")] = "components", yes: Optional[bool] = False, answers=None): validate_solution_folder(solution_path) confirm( f"This will update '{component_name}' in " f"'{solution_path}/components'. Continue?", skip=yes) sb_yaml = read_yaml(f"{solution_path}/sb.yaml") components = sb_yaml.get("components", {}) component_dict = components.get(component_name, {}) template_path = component_dict.get("template_path") answers_dict = get_answers_dict(answers) process_component("update", component_name, template_path, solution_path, destination_path, data=answers_dict, use_existing_answers=yes) print_success( f"Complete. Component {component_name} updated.\n" ) def update_root_yaml(component_name, answers, solution_path): # Update Solution root YAML with new component name. solution_yaml_dict = read_yaml(f"{solution_path}/sb.yaml") or {} components = solution_yaml_dict.get("components", {}) components[component_name] = answers solution_yaml_dict["components"] = components # Update global variables. global_variables = solution_yaml_dict.get("global_variables", {}) if "project_id" in answers: global_variables["project_id"] = answers["project_id"] if "project_number" in answers: global_variables["project_number"] = answers["project_number"] solution_yaml_dict["global_variables"] = global_variables write_yaml(f"{solution_path}/sb.yaml", solution_yaml_dict) def process_component(method, component_name, template_path, solution_path, destination_path, data={}, use_existing_answers=False): assert template_path, "template_path is empty." current_dir = os.path.dirname(__file__) template_dir = f"{current_dir}/../modules/{template_path}" answers_file = None # Get basic info from root sb.yaml. sb_yaml = read_yaml(f"{solution_path}/sb.yaml") global_variables = sb_yaml.get("global_variables", {}) component_answers = {} # If the component name is a Git URL, use the URL as-is in copier. if check_git_url(template_path): template_dir = clone_remote_git(template_path) # Otherwise, try to locate the component in local modules/ folder. else: if method == "update": data["component_name"] = component_name if component_name not in sb_yaml["components"]: raise NameError( f"Component {component_name} is not defined in the root yaml 'sb.yaml' file." ) component_answers = sb_yaml["components"][component_name] template_path = component_answers["template_path"] answers_file = f".sb/module_answers/{component_name}.yaml" # Use existing answer values in data, skipping the prompt. if use_existing_answers: answers_yaml = read_yaml(answers_file) for key, value in answers_yaml.items(): data[key] = value else: template_dir = f"{current_dir}/../modules/{template_path}" if not os.path.exists(template_dir): # If module does not exist in the solutions-builder package, # try loading from the local file path. template_dir = template_path if not os.path.exists(template_dir): raise FileNotFoundError( f"Component '{template_path}' does not exist.") # # Get destination_path defined in copier.yaml # destination_path = solution_path + "/" + copier_dict["_metadata"].get( # "destination_path") # destination_path = destination_path.replace("//", "/") copier_dict = get_copier_yaml(template_dir) data["component_name"] = component_name if "project_id" in data: data["project_id"] = global_variables.get("project_id") if "project_number" in data: data["project_number"] = global_variables.get("project_number") data["solution_path"] = solution_path data["template_path"] = template_path # Run copier with data. worker = run_copy(template_dir, ".", data=data, answers_file=answers_file, unsafe=True) # Get answer values inputed by user. answers = worker.answers.user for key, value in worker.answers.user_defaults.items(): if key not in answers: answers[key] = component_answers.get(key) or value answers["template_path"] = template_path answers["destination_path"] = destination_path # Update component's answer back to sb.yaml. update_root_yaml(component_name, answers, solution_path) # Patch skaffold.yaml for patch_file in copier_dict.get("_patch", []): print(f"Patching {patch_file}...") new_yaml = patch_yaml(f"{solution_path}/{patch_file}", f"{solution_path}/{patch_file}.patch") new_yaml["requires"] = dedupe(new_yaml.get("requires")) write_yaml(f"{solution_path}/{patch_file}", new_yaml) os.remove(f"{solution_path}/{patch_file}.patch") print() def list_components(solution_path: Annotated[Optional[str], typer.Argument()] = "."): """List installed components.""" sb_yaml = read_yaml(f"{solution_path}/sb.yaml") components = sb_yaml.get("components", []) print("Installed components:") for component_name, properties in components.items(): typer.echo( typer.style(f"- {component_name} ", fg=typer.colors.WHITE, bold=True) + typer.style(f"(template: {properties['template_path']})", fg=typer.colors.BLACK, bold=True)) print() # List available components to add. def list_available_components(): print("Available components to add:\n") list_component_templates()