solutions_builder/cli/cli_utils.py (175 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 os
import yaml
import typer
import subprocess
import re
import shutil
import git
from copier import run_copy
from .cli_constants import DEBUG
def confirm(msg, skip=False, abort=True, default=True):
if skip:
return True
return typer.confirm(msg, abort=abort, default=default)
def validate_solution_folder(path):
"""Check if the solution folder has sb.yaml file."""
if not os.path.isfile(path + "/sb.yaml"):
confirm(
f"Path {path} is not a valid solution folder: missing sb.yaml.\n"
"Do you want to initialize with a new `sb.yaml`?")
run_module_template("init_sb_yaml", modules_dir="helper_modules")
return True
def get_copier_yaml(path):
filepath = f"{path}/copier.yaml"
if not os.path.isfile(filepath):
filepath = f"{path}/copier.yml"
if not os.path.isfile(filepath):
raise FileNotFoundError(
f"Path {path} is not a valid module folder: missing copier.yaml or copier.yml"
)
return read_yaml(filepath)
def get_immediate_subdirectories(a_dir):
return sorted([
name for name in os.listdir(a_dir)
if os.path.isdir(os.path.join(a_dir, name))
])
def patch_yaml(dest_path, patch_path):
orig_yaml = read_yaml(dest_path)
patch_yaml = read_yaml(patch_path)
return merge_dict(orig_yaml, patch_yaml)
def merge_dict(dict1, dict2):
"""Merge two dict based on first level of properties."""
for key in dict1.keys():
if isinstance(dict1[key], list):
dict1[key] += dict2.get(key, [])
elif isinstance(dict1[key], dict):
dict1[key].update(dict2.get(key, {}))
else:
dict1[key] = dict2.get(key, dict1[key])
return dict1
def dedupe(obj):
if isinstance(obj, list):
try:
return [*set(obj)]
except:
list_map = {}
for x in obj:
list_map[str(x)] = x
return [list_map[key] for key in list_map.keys()]
else:
return obj
def read_yaml(filepath):
"""Read YAML file and convert to a dict."""
try:
with open(filepath) as f:
data = yaml.safe_load(f)
return data
except FileNotFoundError as e:
return {}
def write_yaml(filepath, dict_data):
"""Write a dict as a YAML file"""
with open(filepath, "w") as f:
yaml.dump(dict_data, f)
def exec_shell(command, working_dir=".", stop_when_error=True, stdout=None):
"""Execute shell commands"""
proc = subprocess.Popen(command, cwd=working_dir, shell=True, stdout=stdout)
exit_status = proc.wait()
if exit_status != 0 and stop_when_error:
raise RuntimeError(
f"Error occurs when running command: {command} (working_dir={working_dir})")
return exit_status
def exec_output(command, working_dir=".", stop_when_error=True):
"""Execute shell commands"""
output = subprocess.check_output(command,
cwd=working_dir,
shell=True,
text=True)
print(output)
return output
def exec_gcloud_output(command, working_dir=".", hide_error=False):
output = ""
try:
output = exec_output(command)
except Exception as e:
print(f"Error: {e}")
output = ""
output = output.strip()
return output
def list_subfolders(path):
modules = get_immediate_subdirectories(path)
for module_name in modules:
print_highlight(f"- {module_name}")
print()
def list_component_templates():
current_dir = os.path.dirname(__file__)
path = current_dir + "/../modules"
list_subfolders(path)
def check_git_url(url):
regex_str = "((git|ssh|http(s)?)|(git@[\\w\\.]+))(:(//)?)([\\w\\.\\@\\:/\\-~]+)(\\.git)(/)?"
regex = re.compile(regex_str)
match = regex.match(url)
return match is not None
def clone_remote_git(source_url):
git_url, git_subfolder = source_url.split(".git")
git_url += ".git"
current_dir = os.path.dirname(__file__)
dest_dir = current_dir + "/../downloaded_repos/" + git_url
if os.path.exists(dest_dir):
if confirm(
f"🎤 Git repo '{git_url}' has been downloaded before. \n "
"Do you want to re-download it?", abort=False):
shutil.rmtree(dest_dir)
git.Repo.clone_from(git_url, dest_dir)
else:
git.Repo.clone_from(git_url, dest_dir)
print()
return dest_dir + "/" + git_subfolder
def get_package_dir():
current_dir = os.path.dirname(__file__)
return current_dir + "/../"
def get_answers_dict(data):
if data:
return dict(s.split("=") for s in data.split(","))
else:
return {}
def get_project_number(project_id):
"""
Get GCP project number based on project_id using gcloud command.
"""
print(f"(Retrieving project number for {project_id}...)")
command = f"gcloud projects describe {project_id} --format='value(projectNumber)'"
project_number = exec_gcloud_output(command, hide_error=True)
project_number = project_number.strip()
if not project_number.isnumeric():
return ""
return project_number
def set_gcloud_project(project_id):
"""
Set GCP project based on project_id using gcloud command.
"""
if project_id:
print(f"(Setting gcloud to project '{project_id}'...)")
exec_gcloud_output(f"gcloud config set project {project_id} --quiet")
def create_default_artifact_repo(project_id, repo_name, region="us"):
"""
Create default artifact repository.
"""
print(f"(Creating default artifact repository...)")
exec_gcloud_output(f"gcloud config set project {project_id}")
exec_gcloud_output(
f"gcloud artifacts repositories create {repo_name}"
f" --repository-format=docker --location={region}")
def set_debug_flag(is_debug):
DEBUG = is_debug
def verify_copier_file(path):
"Check if copier.yaml exists in folder path"
if not os.path.isfile(path + "/copier.yaml"):
confirm(f"No copier.yaml found in {path}. Do you still want to continue?")
def update_global_var(var_name, var_value, solution_path="."):
"""
Update global variable.
"""
sb_yaml = read_yaml(f"{solution_path}/sb.yaml")
global_variables = sb_yaml.get("global_variables", {})
global_variables[var_name] = var_value
sb_yaml["global_variables"] = global_variables
write_yaml(f"{solution_path}/sb.yaml", sb_yaml)
def run_module_template(module_name, modules_dir="modules",
dest_dir=".", data={}, answers_file=None):
"""
Run module template.
"""
print(f"Adding module '{module_name}'...\n")
current_dir = os.path.dirname(__file__)
template_dir = f"{current_dir}/../{modules_dir}/{module_name}"
worker = run_copy(template_dir,
dest_dir,
data=data,
answers_file=answers_file,
unsafe=True)
return worker.answers.user
def print_success(msg):
"""Print success message with styling."""
typer.echo(typer.style(msg, fg=typer.colors.GREEN, bold=True))
# Print error message with styling.
def print_error(msg):
typer.echo(typer.style(msg, fg=typer.colors.RED, bold=True))
# Print highlighted message with styling.
def print_highlight(msg):
typer.echo(typer.style(msg, fg=typer.colors.WHITE, bold=True))