From 924cffa279f1f5ec994c57a13e6ae1736ca8556e Mon Sep 17 00:00:00 2001 From: Jon Chen Date: Fri, 29 Sep 2023 04:13:25 +0900 Subject: [PATCH 1/7] added vars cli to replace global vars --- pyproject.toml | 2 +- solutions_builder/cli/cli.py | 4 + solutions_builder/cli/vars.py | 137 ++++++++++++++++++ solutions_builder/cli/vars_test.py | 67 +++++++++ .../{{terraform_stage_name}}/backend.tf | 2 +- .../workflows/deployment_cloudrun_dev.yaml | 4 +- .../.github/workflows/deployment_gke_dev.yaml | 4 +- .../workflows/e2e_cloudrun_api_test.yaml | 4 +- .../.github/workflows/e2e_gke_api_test.yaml | 4 +- .../workflows/unit_test_linter_common.yaml | 4 +- solutions_builder/template_root/sb.yaml | 5 +- .../stages/0-jumphost/terraform.tfvars | 6 +- .../stages/1-bootstrap/terraform.tfvars | 2 +- .../stages/2-foundation/terraform.tfvars | 6 +- 14 files changed, 230 insertions(+), 21 deletions(-) create mode 100644 solutions_builder/cli/vars.py create mode 100644 solutions_builder/cli/vars_test.py diff --git a/pyproject.toml b/pyproject.toml index 5ac250ee..4dde156c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "solutions-builder" -version = "1.17.13" +version = "1.17.14" description = "A solution framework to generate a project with built-in structure and modules" authors = ["Jon Chen "] license = "Apache" diff --git a/solutions_builder/cli/cli.py b/solutions_builder/cli/cli.py index 9efe24fd..75a9c744 100644 --- a/solutions_builder/cli/cli.py +++ b/solutions_builder/cli/cli.py @@ -23,6 +23,7 @@ from .infra import infra_app from .template import template_app from .set import set_app +from .vars import vars_app from .cli_utils import * __version__ = importlib.metadata.version("solutions-builder") @@ -48,6 +49,9 @@ app.add_typer(set_app, name="set", help="Set properties to an existing solution folder.") +app.add_typer(vars_app, + name="vars", + help="Set global variables to an existing solution folder.") # Create a new solution diff --git a/solutions_builder/cli/vars.py b/solutions_builder/cli/vars.py new file mode 100644 index 00000000..81c904aa --- /dev/null +++ b/solutions_builder/cli/vars.py @@ -0,0 +1,137 @@ +""" +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 pathlib +import re +import jinja2 +from typing import Optional +from typing_extensions import Annotated +from copier import run_auto +from .cli_utils import * + +vars_app = typer.Typer() + +INCLUDE_PATTERNS = [ + "*.yaml", "*.yml", "*.env", "*.tfvars", "*.tf", "*.sh", "*.md" +] +EXCLUDE_PATTERNS = ["**/.terraform/**/*.*", "**/node_modules/**/*.*", "**/.venv/**/*.*"] + +# Replace a varialbe with a given text content. +def replace_var_to_template(var_name, text, custom_template=False, debug=False): + match_pattern = f"^([^\\r]*[:|=]\\s*)([\"\']?)([^\"^\']*)([\"\']?)\\s*#\\s*sb-var:{var_name}" + output_pattern = f"\\1\\2{{{{{var_name}}}}}\\4 # sb-var:{var_name}" + + if custom_template: + match_pattern = match_pattern + ":(.*)" + output_pattern = f"\\1\\2\\5\\4 # sb-var:{var_name}:\\5" + + if debug: + print(f"match_pattern = {match_pattern}") + + text = re.sub(match_pattern, output_pattern, text) + return text + +def restore_template_in_comment(var_name, var_value, text): + # Restore jinja2 variables in the custom content comment. + match_pattern = f"(#\\s*sb-var:{var_name}:)(.*){var_value}(.*)" + output_pattern = f"\\1\\2{{{{{var_name}}}}}\\3" + text = re.sub(match_pattern, output_pattern, text) + return text + + +def replace_var_to_value(var_name, var_value, text): + # Replace simple variable pattern with sb-var:var_name + text = replace_var_to_template(var_name, text) + + # Replace custom-content variable pattern with sb-var:var_name:custom_template + text = replace_var_to_template(var_name, text, custom_template=True) + + # Update variables using Jinja2 + jinja_env = jinja2.Environment() + text = text.replace("# copier:raw", "# copier:raw{% raw %}") + text = text.replace("# copier:endraw", "# copier:endraw{% endraw %}") + template = jinja_env.from_string(text) + + # Set vars data for jinja + data = {} + data[var_name] = var_value + + # Apply variable values using Jinja2 + text = template.render(**data) + + # Restore vars to template in comment. + text = restore_template_in_comment(var_name, var_value, text) + + return text + +# Apply a specific variable with a new value. +def apply_var_to_folder(solution_path, var_name, var_value): + file_set = set() + + # Adding includes. + for pattern in INCLUDE_PATTERNS: + file_list = pathlib.Path(solution_path).rglob(f"{pattern}") + file_set.update(set([str(x) for x in file_list])) + + # Removing excludes. + for pattern in EXCLUDE_PATTERNS: + file_list = pathlib.Path(solution_path).rglob(f"{pattern}") + file_set = file_set - set([str(x) for x in file_list]) + + for filename in list(file_set): + with open(filename, "r") as file: + print(f"filename = {filename}") + + # Replace variable + filedata = file.read() + filedata = replace_var_to_value(var_name, var_value, filedata) + # filedata = filedata + "\n" + + # Write back to the original file. + with open(filename, "w") as file: + file.write(filedata) + +# CLI command for `sb vars set ` +@vars_app.command(name="set") +def set_var( + var_name, + var_value, + solution_path: Annotated[Optional[str], typer.Argument()] = ".", +): + validate_solution_folder(solution_path) + + # Update to the root sb.yaml + root_st_yaml = read_yaml(f"{solution_path}/sb.yaml") + global_variables = root_st_yaml.get("global_variables", {}) + global_variables[var_name] = var_value + root_st_yaml["global_variables"] = global_variables + write_yaml(f"{solution_path}/sb.yaml", root_st_yaml) + + # Apply vars to individual files + apply_var_to_folder(solution_path, var_name, var_value) + +# # Reapply and replace all variables +# @vars_app.command() +# def apply_all( +# solution_path: Annotated[Optional[str], typer.Argument()] = ".", +# ): +# validate_solution_folder(solution_path) +# root_st_yaml = read_yaml(f"{solution_path}/sb.yaml") +# global_variables = root_st_yaml.get("global_variables", {}) + +# for var_name, value in global_variables.items(): +# apply_var_to_folder(solution_path, var_name, value) diff --git a/solutions_builder/cli/vars_test.py b/solutions_builder/cli/vars_test.py new file mode 100644 index 00000000..e2487b97 --- /dev/null +++ b/solutions_builder/cli/vars_test.py @@ -0,0 +1,67 @@ +""" +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 pytest +from .vars import * + +def test_replace_var_to_template(): + # Test with equals and quotes + text = "project_id = \"not-replaced-yet\" # sb-var:project_id" + text = replace_var_to_template("project_id", text) + assert text == "project_id = \"{{project_id}}\" # sb-var:project_id" + + # Test with equals without quotes + text = "project_id = test # sb-var:project_id" + text = replace_var_to_template("project_id", text) + assert text == "project_id = {{project_id}} # sb-var:project_id" + + # Test with comma without quotes + text = "project_id: test # sb-var:project_id" + text = replace_var_to_template("project_id", text) + assert text == "project_id: {{project_id}} # sb-var:project_id" + +def test_replace_var_to_custom_template(): + # Test with equals and quotes + text = "project_id = \"not-replaced-yet\" # sb-var:project_id:prefix-{{project_id}}-suffix" + text = replace_var_to_template("project_id", text, custom_template=True) + assert text == "project_id = \"prefix-{{project_id}}-suffix\" # sb-var:project_id:prefix-{{project_id}}-suffix" + +def test_replace_var_to_value(): + text = "project_id = \"not-replaced-yet\" # sb-var:project_id" + text = replace_var_to_value("project_id", "fake-id", text) + assert text == "project_id = \"fake-id\" # sb-var:project_id" + + text = " PROJECT_ID: not-replaced-yet # sb-var:project_id" + text = replace_var_to_value("project_id", "fake-id", text) + assert text == " PROJECT_ID: fake-id # sb-var:project_id" + +def test_replace_with_multiple_lines(): + text = """ + env: + PROJECT_ID: not-replaced-yet # sb-var:project_id + """ + text = replace_var_to_value("project_id", "fake-id", text) + + print(text) + assert text == """ + env: + PROJECT_ID: fake-id # sb-var:project_id + """ + +def test_replace_var_to_value_custom_template(): + text = "project_id = \"not-replaced-yet\" # sb-var:project_id:prefix-{{project_id}}-suffix" + text = replace_var_to_value("project_id", "fake-id", text) + assert text == "project_id = \"prefix-fake-id-suffix\" # sb-var:project_id:prefix-{{project_id}}-suffix" diff --git a/solutions_builder/modules/terraform_gke/terraform/stages/{{terraform_stage_name}}/backend.tf b/solutions_builder/modules/terraform_gke/terraform/stages/{{terraform_stage_name}}/backend.tf index 812fd786..0e3d94cf 100644 --- a/solutions_builder/modules/terraform_gke/terraform/stages/{{terraform_stage_name}}/backend.tf +++ b/solutions_builder/modules/terraform_gke/terraform/stages/{{terraform_stage_name}}/backend.tf @@ -17,7 +17,7 @@ terraform { backend "gcs" { - bucket = "{{project_id}}-tfstate" + bucket = "{{project_id}}-tfstate" # sb-var:project_id:{{project_id}}-tfstate prefix = "stage/{{terraform_stage_name}}" } } diff --git a/solutions_builder/template_root/.github/workflows/deployment_cloudrun_dev.yaml b/solutions_builder/template_root/.github/workflows/deployment_cloudrun_dev.yaml index ad4c6539..a1501317 100644 --- a/solutions_builder/template_root/.github/workflows/deployment_cloudrun_dev.yaml +++ b/solutions_builder/template_root/.github/workflows/deployment_cloudrun_dev.yaml @@ -16,7 +16,7 @@ env: REGION: {{gcp_region}} ENV: dev -# copoier:raw {% raw %} +# copier:raw {% raw %} jobs: build_common: name: Build Common image @@ -85,4 +85,4 @@ jobs: _IMAGE="${{ matrix.target-folder[1] }}",\ _SERVICE_ACCOUNT="deployment-${{ env.ENV }}@${{ env.PROJECT_ID }}.iam.gserviceaccount.com" -# copoier:endraw {% endraw %} +# copier:endraw {% endraw %} diff --git a/solutions_builder/template_root/.github/workflows/deployment_gke_dev.yaml b/solutions_builder/template_root/.github/workflows/deployment_gke_dev.yaml index 34933596..a2a07923 100644 --- a/solutions_builder/template_root/.github/workflows/deployment_gke_dev.yaml +++ b/solutions_builder/template_root/.github/workflows/deployment_gke_dev.yaml @@ -21,7 +21,7 @@ env: SKAFFOLD_VERSION: 2.0.3 SKAFFOLD_CACHE_GCS: gs://{{project_id}}.appspot.com/skaffold/cache -# copoier:raw {% raw %} +# copier:raw {% raw %} jobs: deploy_backends: name: Deploy microservices to GKE cluster @@ -82,4 +82,4 @@ jobs: run: | gsutil cp ~/.skaffold/cache $SKAFFOLD_CACHE_GCS -# copoier:endraw {% endraw %} +# copier:endraw {% endraw %} diff --git a/solutions_builder/template_root/.github/workflows/e2e_cloudrun_api_test.yaml b/solutions_builder/template_root/.github/workflows/e2e_cloudrun_api_test.yaml index 351f081c..75f76946 100644 --- a/solutions_builder/template_root/.github/workflows/e2e_cloudrun_api_test.yaml +++ b/solutions_builder/template_root/.github/workflows/e2e_cloudrun_api_test.yaml @@ -20,7 +20,7 @@ env: REGION: {{gcp_region}} ENV: dev -# copoier:raw {% raw %} +# copier:raw {% raw %} jobs: build_common: name: Build Common image @@ -168,4 +168,4 @@ jobs: SERVICE_NAME_PREFIX=e2e-pr${PR_NUMBER}- gcloud run services delete ${SERVICE_NAME_PREFIX}${{ matrix.cloudrun-services }} --region=${{ env.REGION }} --quiet -# copoier:endraw {% endraw %} +# copier:endraw {% endraw %} diff --git a/solutions_builder/template_root/.github/workflows/e2e_gke_api_test.yaml b/solutions_builder/template_root/.github/workflows/e2e_gke_api_test.yaml index 83a27728..3bc5f0c2 100644 --- a/solutions_builder/template_root/.github/workflows/e2e_gke_api_test.yaml +++ b/solutions_builder/template_root/.github/workflows/e2e_gke_api_test.yaml @@ -25,7 +25,7 @@ env: SKAFFOLD_VERSION: 2.0.3 SKAFFOLD_CACHE_GCS: gs://{{project_id}}.appspot.com/skaffold/cache -# copoier:raw {% raw %} +# copier:raw {% raw %} jobs: deploy: name: Deploy all microservices in PR namespace @@ -228,4 +228,4 @@ jobs: skaffold delete --namespace $NAMESPACE kubectl delete namespace $NAMESPACE -# copoier:endraw {% endraw %} +# copier:endraw {% endraw %} diff --git a/solutions_builder/template_root/.github/workflows/unit_test_linter_common.yaml b/solutions_builder/template_root/.github/workflows/unit_test_linter_common.yaml index b4664101..cec2cd99 100644 --- a/solutions_builder/template_root/.github/workflows/unit_test_linter_common.yaml +++ b/solutions_builder/template_root/.github/workflows/unit_test_linter_common.yaml @@ -15,7 +15,7 @@ on: env: PROJECT_ID: {{project_id}} -# copoier:raw {% raw %} +# copier:raw {% raw %} jobs: unit-test: runs-on: ubuntu-latest @@ -95,4 +95,4 @@ jobs: cd ${{ matrix.target-folder }}/src python -m pylint $(git ls-files '*.py') --rcfile=$BASE_DIR/.pylintrc -# copoier:endraw {% endraw %} +# copier:endraw {% endraw %} diff --git a/solutions_builder/template_root/sb.yaml b/solutions_builder/template_root/sb.yaml index 33697ed5..7d5bb141 100644 --- a/solutions_builder/template_root/sb.yaml +++ b/solutions_builder/template_root/sb.yaml @@ -4,5 +4,6 @@ components: {} created_at: "{% now 'utc', '%Y-%b-%d %H:%M:%S UTC' %}" modified_at: "{% now 'utc', '%Y-%b-%d %H:%M:%S UTC' %}" project_number: {{project_number}} -project_id: {{project_id}} -project_name: {{project_name}} +project_id: {{project_id}} # sb-var:project_id +project_name: {{project_name}} # sb-var:project_name +global_variables: {} diff --git a/solutions_builder/template_root/terraform/stages/0-jumphost/terraform.tfvars b/solutions_builder/template_root/terraform/stages/0-jumphost/terraform.tfvars index efde235b..093dc5d6 100644 --- a/solutions_builder/template_root/terraform/stages/0-jumphost/terraform.tfvars +++ b/solutions_builder/template_root/terraform/stages/0-jumphost/terraform.tfvars @@ -1,3 +1,3 @@ -project_id = "{{project_id}}" -region = "{{gcp_region}}" -jump_host_zone = "{{jump_host_zone}}" +project_id = "{{project_id}}" # sb-var:project_id +region = "{{gcp_region}}" # sb-var:gcp_region +jump_host_zone = "{{jump_host_zone}}" # sb-var:jump_host_zone diff --git a/solutions_builder/template_root/terraform/stages/1-bootstrap/terraform.tfvars b/solutions_builder/template_root/terraform/stages/1-bootstrap/terraform.tfvars index d71961d3..73381cb6 100644 --- a/solutions_builder/template_root/terraform/stages/1-bootstrap/terraform.tfvars +++ b/solutions_builder/template_root/terraform/stages/1-bootstrap/terraform.tfvars @@ -1,2 +1,2 @@ -project_id = "{{project_id}}" +project_id = "{{project_id}}" # sb-var:project_id storage_multiregion = "US" diff --git a/solutions_builder/template_root/terraform/stages/2-foundation/terraform.tfvars b/solutions_builder/template_root/terraform/stages/2-foundation/terraform.tfvars index 8ad92c0f..98632116 100644 --- a/solutions_builder/template_root/terraform/stages/2-foundation/terraform.tfvars +++ b/solutions_builder/template_root/terraform/stages/2-foundation/terraform.tfvars @@ -1,6 +1,6 @@ -project_id = "{{project_id}}" -project_number = "{{project_number}}" -region = "{{gcp_region}}" +project_id = "{{project_id}}" # sb-var:project_id +project_number = "{{project_number}}" # sb-var:project_number +region = "{{gcp_region}}" # sb-var:gcp_region storage_multiregion = "US" vpc_network = "{{vpc_network}}" vpc_subnetwork = "{{vpc_subnetwork}}" From 273012e4255a730b7b87213cd3d0a9002aa87961 Mon Sep 17 00:00:00 2001 From: Jon Chen Date: Fri, 29 Sep 2023 04:50:09 +0900 Subject: [PATCH 2/7] update vars CLI and CLI usage readme --- docs/CLI_USAGE.md | 30 ++++++++++++++++ solutions_builder/cli/vars.py | 57 +++++++++++++++--------------- solutions_builder/cli/vars_test.py | 2 -- 3 files changed, 59 insertions(+), 30 deletions(-) create mode 100644 docs/CLI_USAGE.md diff --git a/docs/CLI_USAGE.md b/docs/CLI_USAGE.md new file mode 100644 index 00000000..bbd6e2e0 --- /dev/null +++ b/docs/CLI_USAGE.md @@ -0,0 +1,30 @@ +# Solution Builder CLI Usage + +## Global Variables + +### Set and apply a global variable + +You can add an anchor to specify where a variable to apply in the solution folder. + +For example, in a YAML file: +```yaml +detail: + PROJECT_ID: old-project-id # sb-var:project_id + OTHER: something +``` +- It sets a variable named "project_id" as the anchor for this "PROJECT_ID" property at the same line in the YAML file. + +Then, you can run the following to replace the variable value: +``` +$ sb vars set project_id new-project-id +``` +- This will find all occurance of the `sb-var:project_id` anchors in your folder, and replace with the new value "new-project-id" + +The YAML file will become: + +```yaml +detail: + PROJECT_ID: new-project-id # sb-var:project_id + OTHER: something +``` + diff --git a/solutions_builder/cli/vars.py b/solutions_builder/cli/vars.py index 81c904aa..215812c4 100644 --- a/solutions_builder/cli/vars.py +++ b/solutions_builder/cli/vars.py @@ -42,23 +42,27 @@ def replace_var_to_template(var_name, text, custom_template=False, debug=False): if debug: print(f"match_pattern = {match_pattern}") - text = re.sub(match_pattern, output_pattern, text) - return text + text, count = re.subn(match_pattern, output_pattern, text) + return (text, count) def restore_template_in_comment(var_name, var_value, text): # Restore jinja2 variables in the custom content comment. match_pattern = f"(#\\s*sb-var:{var_name}:)(.*){var_value}(.*)" output_pattern = f"\\1\\2{{{{{var_name}}}}}\\3" - text = re.sub(match_pattern, output_pattern, text) - return text + text, count = re.subn(match_pattern, output_pattern, text) + return (text, count) def replace_var_to_value(var_name, var_value, text): + overall_count = 0 + # Replace simple variable pattern with sb-var:var_name - text = replace_var_to_template(var_name, text) + text, count = replace_var_to_template(var_name, text) + overall_count += count # Replace custom-content variable pattern with sb-var:var_name:custom_template - text = replace_var_to_template(var_name, text, custom_template=True) + text, count = replace_var_to_template(var_name, text, custom_template=True) + overall_count += count # Update variables using Jinja2 jinja_env = jinja2.Environment() @@ -74,9 +78,9 @@ def replace_var_to_value(var_name, var_value, text): text = template.render(**data) # Restore vars to template in comment. - text = restore_template_in_comment(var_name, var_value, text) + text, count = restore_template_in_comment(var_name, var_value, text) - return text + return (text, overall_count) # Apply a specific variable with a new value. def apply_var_to_folder(solution_path, var_name, var_value): @@ -92,18 +96,23 @@ def apply_var_to_folder(solution_path, var_name, var_value): file_list = pathlib.Path(solution_path).rglob(f"{pattern}") file_set = file_set - set([str(x) for x in file_list]) + modified_files_list = [] for filename in list(file_set): with open(filename, "r") as file: - print(f"filename = {filename}") - # Replace variable filedata = file.read() - filedata = replace_var_to_value(var_name, var_value, filedata) - # filedata = filedata + "\n" + filedata, count = replace_var_to_value(var_name, var_value, filedata) + filedata = filedata + "\n" + + if count > 0: + modified_files_list.append(filename) - # Write back to the original file. - with open(filename, "w") as file: - file.write(filedata) + # If there's any changes, write back to the original file. + if count > 0: + with open(filename, "w") as file: + file.write(filedata) + + return modified_files_list # CLI command for `sb vars set ` @vars_app.command(name="set") @@ -122,16 +131,8 @@ def set_var( write_yaml(f"{solution_path}/sb.yaml", root_st_yaml) # Apply vars to individual files - apply_var_to_folder(solution_path, var_name, var_value) - -# # Reapply and replace all variables -# @vars_app.command() -# def apply_all( -# solution_path: Annotated[Optional[str], typer.Argument()] = ".", -# ): -# validate_solution_folder(solution_path) -# root_st_yaml = read_yaml(f"{solution_path}/sb.yaml") -# global_variables = root_st_yaml.get("global_variables", {}) - -# for var_name, value in global_variables.items(): -# apply_var_to_folder(solution_path, var_name, value) + filenames = apply_var_to_folder(solution_path, var_name, var_value) + + print_success( + f"Complete. {len(filenames)} files updated.\n" + ) diff --git a/solutions_builder/cli/vars_test.py b/solutions_builder/cli/vars_test.py index e2487b97..bb011256 100644 --- a/solutions_builder/cli/vars_test.py +++ b/solutions_builder/cli/vars_test.py @@ -54,8 +54,6 @@ def test_replace_with_multiple_lines(): PROJECT_ID: not-replaced-yet # sb-var:project_id """ text = replace_var_to_value("project_id", "fake-id", text) - - print(text) assert text == """ env: PROJECT_ID: fake-id # sb-var:project_id From da4971eb6513fc3c93543eb5e2d6b6e32ed7aa3b Mon Sep 17 00:00:00 2001 From: Jon Chen Date: Fri, 29 Sep 2023 04:52:40 +0900 Subject: [PATCH 3/7] update root readme --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index a979d306..6d246ed5 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,10 @@ sb deploy - See other deployment options in [solutions_builder/modules](solutions_builder/modules). +## CLI Usage + +For more information on how to use the CLI, please refer to the [CLI_USAGE.md](docs/CLI_USAGE.md). + ## Additional Documentations You can find more documentations in [docs](docs) folder. In a nutshell, it covers the following: From 7f82c7ac6e303f8c9d1f51edb86751a5e9855632 Mon Sep 17 00:00:00 2001 From: Jon Chen Date: Fri, 29 Sep 2023 11:07:56 +0900 Subject: [PATCH 4/7] fixed typo and added comment for regex patterns --- solutions_builder/cli/cli.py | 2 +- solutions_builder/cli/vars.py | 38 ++++++++++++------- .../{{terraform_stage_name}}/terraform.tfvars | 4 +- 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/solutions_builder/cli/cli.py b/solutions_builder/cli/cli.py index 75a9c744..83f05981 100644 --- a/solutions_builder/cli/cli.py +++ b/solutions_builder/cli/cli.py @@ -51,7 +51,7 @@ help="Set properties to an existing solution folder.") app.add_typer(vars_app, name="vars", - help="Set global variables to an existing solution folder.") + help="Set variables in an existing solutions-builder folder.") # Create a new solution diff --git a/solutions_builder/cli/vars.py b/solutions_builder/cli/vars.py index 215812c4..2b8299e6 100644 --- a/solutions_builder/cli/vars.py +++ b/solutions_builder/cli/vars.py @@ -1,18 +1,16 @@ -""" -Copyright 2023 Google LLC +# 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 +# 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 +# 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. -""" +# 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 pathlib @@ -30,11 +28,22 @@ ] EXCLUDE_PATTERNS = ["**/.terraform/**/*.*", "**/node_modules/**/*.*", "**/.venv/**/*.*"] -# Replace a varialbe with a given text content. +# Replace a variable with a given text content. def replace_var_to_template(var_name, text, custom_template=False, debug=False): + # This pattern matches lines with sb-var anchor in the comment at the end. + # For example: + # PROJECT_ID: 12345 # sb-var:project_id + # GCP_REGION = "us-central1" # sb-var:gcp_region match_pattern = f"^([^\\r]*[:|=]\\s*)([\"\']?)([^\"^\']*)([\"\']?)\\s*#\\s*sb-var:{var_name}" + + # This output patterh print the jinja2 template for the specific variable name. + # For example: + # PROJECT_ID: {{project_id}} # sb-var:project_id output_pattern = f"\\1\\2{{{{{var_name}}}}}\\4 # sb-var:{var_name}" + # In addition, if custom_template is true, the pattern will extend to the custom + # template string at the end of the anchor. For example: + # BUCKET_NAME: my-project-bucket # sb-var:project_id:{{project_id}}-bucket if custom_template: match_pattern = match_pattern + ":(.*)" output_pattern = f"\\1\\2\\5\\4 # sb-var:{var_name}:\\5" @@ -42,6 +51,7 @@ def replace_var_to_template(var_name, text, custom_template=False, debug=False): if debug: print(f"match_pattern = {match_pattern}") + # Replace with regex pattern and returns new text and count of changes. text, count = re.subn(match_pattern, output_pattern, text) return (text, count) @@ -49,6 +59,8 @@ def restore_template_in_comment(var_name, var_value, text): # Restore jinja2 variables in the custom content comment. match_pattern = f"(#\\s*sb-var:{var_name}:)(.*){var_value}(.*)" output_pattern = f"\\1\\2{{{{{var_name}}}}}\\3" + + text, count = re.subn(match_pattern, output_pattern, text) text, count = re.subn(match_pattern, output_pattern, text) return (text, count) diff --git a/solutions_builder/modules/terraform_gke/terraform/stages/{{terraform_stage_name}}/terraform.tfvars b/solutions_builder/modules/terraform_gke/terraform/stages/{{terraform_stage_name}}/terraform.tfvars index a05cb2b2..961a23c9 100644 --- a/solutions_builder/modules/terraform_gke/terraform/stages/{{terraform_stage_name}}/terraform.tfvars +++ b/solutions_builder/modules/terraform_gke/terraform/stages/{{terraform_stage_name}}/terraform.tfvars @@ -1,5 +1,5 @@ -project_id = "{{project_id}}" -region = "{{gcp_region}}" +project_id = "{{project_id}}" # sb-var:project_id +region = "{{gcp_region}}" # sb-var:gcp_region kubernetes_version = "{{kubernetes_version}}" node_machine_type = "{{node_machine_type}}" cluster_name = "{{cluster_name}}" From d6cfb0c7f3c4c50687ccd7c2081e4b81c505e2b1 Mon Sep 17 00:00:00 2001 From: Jon Chen Date: Fri, 29 Sep 2023 11:40:36 +0900 Subject: [PATCH 5/7] adding sb-var to terraform stages --- .../terraform/stages/3-task-dispatch/terraform.tfvars | 4 ++-- .../stages/{{terraform_stage_name}}/terraform.tfvars | 4 ++-- .../terraform/stages/3-httplb-cloudrun/terraform.tfvars | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/solutions_builder/modules/task_dispatch_service/terraform/stages/3-task-dispatch/terraform.tfvars b/solutions_builder/modules/task_dispatch_service/terraform/stages/3-task-dispatch/terraform.tfvars index d9aab314..55b08a73 100644 --- a/solutions_builder/modules/task_dispatch_service/terraform/stages/3-task-dispatch/terraform.tfvars +++ b/solutions_builder/modules/task_dispatch_service/terraform/stages/3-task-dispatch/terraform.tfvars @@ -19,8 +19,8 @@ # Note for modules: Jinja variables are defined in copier.yaml. -project_id = "{{project_id}}" -region = "{{gcp_region}}" +project_id = "{{project_id}}" # sb-var:project_id +region = "{{gcp_region}}" # sb-var:gcp_region task_pubsub_topic = "{{task_pubsub_topic}}" message_retention_duration = "{{pubsub_message_retention_duration}}" eventarc_trigger_name = "{{eventarc_trigger_name}}" diff --git a/solutions_builder/modules/terraform_gke_ingress/terraform/stages/{{terraform_stage_name}}/terraform.tfvars b/solutions_builder/modules/terraform_gke_ingress/terraform/stages/{{terraform_stage_name}}/terraform.tfvars index 9862127a..91a9eac5 100644 --- a/solutions_builder/modules/terraform_gke_ingress/terraform/stages/{{terraform_stage_name}}/terraform.tfvars +++ b/solutions_builder/modules/terraform_gke_ingress/terraform/stages/{{terraform_stage_name}}/terraform.tfvars @@ -1,5 +1,5 @@ -project_id = "{{project_id}}" -region = "{{gcp_region}}" +project_id = "{{project_id}}" # sb-var:project_id +region = "{{gcp_region}}" # sb-var:gcp_region cluster_name = "{{cluster_name}}" cluster_external_endpoint = "{{cluster_external_endpoint}}" cluster_ca_certificate = "{{('masterAuth.clusterCaCertificate', cluster_name, gcp_region) | get_cluster_value}}" diff --git a/solutions_builder/modules/terraform_httplb_cloudrun/terraform/stages/3-httplb-cloudrun/terraform.tfvars b/solutions_builder/modules/terraform_httplb_cloudrun/terraform/stages/3-httplb-cloudrun/terraform.tfvars index a647ca47..e75541fa 100644 --- a/solutions_builder/modules/terraform_httplb_cloudrun/terraform/stages/3-httplb-cloudrun/terraform.tfvars +++ b/solutions_builder/modules/terraform_httplb_cloudrun/terraform/stages/3-httplb-cloudrun/terraform.tfvars @@ -1,5 +1,5 @@ -project_id = "{{project_id}}" -region = "{{gcp_region}}" +project_id = "{{project_id}}" # sb-var:project_id +region = "{{gcp_region}}" # sb-var:gcp_region domains = "{{domains}}" cloudrun_services = "{{cloudrun_services}}" loadbalancer_name = "{{loadbalancer_name}}" From 84cefcde1146d7244ab8cc5260e364b40cbf5709 Mon Sep 17 00:00:00 2001 From: Jon Chen Date: Fri, 29 Sep 2023 11:59:29 +0900 Subject: [PATCH 6/7] updated cli and vars, updated readme --- docs/CLI_USAGE.md | 26 ++++++++++++++++++++++++++ solutions_builder/cli/vars.py | 19 +++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/docs/CLI_USAGE.md b/docs/CLI_USAGE.md index bbd6e2e0..74f43085 100644 --- a/docs/CLI_USAGE.md +++ b/docs/CLI_USAGE.md @@ -28,3 +28,29 @@ detail: OTHER: something ``` +### Apply all existing global variables + +You can apply all existing global variables to files with corresponding variable anchors. + +For example, in a YAML file: +```yaml +detail: + PROJECT_ID: old-project-id # sb-var:project_id + OTHER: something +``` + +And in the `sb.yaml` file in your project root folder: +```yaml +global_variables: + project_id: MY_PROJECT_ID + project_name: core-solution-services + project_number: MY_PROJECT_NUMBER + gcp_region: us-central1 +``` + +Run the following to apply all these values to exsiting variables in all files. +``` +$ sb vars apply-all +``` + +All files with the corresponding anchors will be updated altogether. diff --git a/solutions_builder/cli/vars.py b/solutions_builder/cli/vars.py index 2b8299e6..94bdea1f 100644 --- a/solutions_builder/cli/vars.py +++ b/solutions_builder/cli/vars.py @@ -148,3 +148,22 @@ def set_var( print_success( f"Complete. {len(filenames)} files updated.\n" ) + +# CLI command for `sb vars apply-all` +@vars_app.command(name="apply-all") +def apply_all( + solution_path: Annotated[Optional[str], typer.Argument()] = ".", +): + validate_solution_folder(solution_path) + + # Update to the root sb.yaml + root_st_yaml = read_yaml(f"{solution_path}/sb.yaml") + global_variables = root_st_yaml.get("global_variables", {}) + filenames = [] + + for var_name, var_value in global_variables.items(): + filenames += apply_var_to_folder(solution_path, var_name, var_value) + + print_success( + f"Complete. {len(filenames)} files updated.\n" + ) From a1c5ced60cf75a8e6696a047158cc3cedd388477 Mon Sep 17 00:00:00 2001 From: Jon Chen Date: Mon, 2 Oct 2023 21:28:20 +0800 Subject: [PATCH 7/7] fix typo and update CLI usage doc --- docs/CLI_USAGE.md | 4 ++-- solutions_builder/cli/vars.py | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/CLI_USAGE.md b/docs/CLI_USAGE.md index 74f43085..fa600cad 100644 --- a/docs/CLI_USAGE.md +++ b/docs/CLI_USAGE.md @@ -18,7 +18,7 @@ Then, you can run the following to replace the variable value: ``` $ sb vars set project_id new-project-id ``` -- This will find all occurance of the `sb-var:project_id` anchors in your folder, and replace with the new value "new-project-id" +- This will find all occurrence of the `sb-var:project_id` anchors in your folder, and replace with the new value "new-project-id" The YAML file will become: @@ -48,7 +48,7 @@ global_variables: gcp_region: us-central1 ``` -Run the following to apply all these values to exsiting variables in all files. +Run the following to apply all these values to existing variables in all files. ``` $ sb vars apply-all ``` diff --git a/solutions_builder/cli/vars.py b/solutions_builder/cli/vars.py index 94bdea1f..75c70307 100644 --- a/solutions_builder/cli/vars.py +++ b/solutions_builder/cli/vars.py @@ -60,7 +60,6 @@ def restore_template_in_comment(var_name, var_value, text): match_pattern = f"(#\\s*sb-var:{var_name}:)(.*){var_value}(.*)" output_pattern = f"\\1\\2{{{{{var_name}}}}}\\3" - text, count = re.subn(match_pattern, output_pattern, text) text, count = re.subn(match_pattern, output_pattern, text) return (text, count)