From 50856e6951763237be2133781acb4a7714bc8c72 Mon Sep 17 00:00:00 2001 From: lcaggio Date: Thu, 23 Feb 2023 18:36:03 +0100 Subject: [PATCH 01/49] First commit --- blueprints/data-solutions/bq-ml/README.md | 6 + blueprints/data-solutions/bq-ml/main.tf | 247 +++++++++++++++++++ blueprints/data-solutions/bq-ml/outputs.tf | 52 ++++ blueprints/data-solutions/bq-ml/variables.tf | 69 ++++++ blueprints/data-solutions/bq-ml/versions.tf | 29 +++ 5 files changed, 403 insertions(+) create mode 100644 blueprints/data-solutions/bq-ml/README.md create mode 100644 blueprints/data-solutions/bq-ml/main.tf create mode 100644 blueprints/data-solutions/bq-ml/outputs.tf create mode 100644 blueprints/data-solutions/bq-ml/variables.tf create mode 100644 blueprints/data-solutions/bq-ml/versions.tf diff --git a/blueprints/data-solutions/bq-ml/README.md b/blueprints/data-solutions/bq-ml/README.md new file mode 100644 index 00000000..42e4832c --- /dev/null +++ b/blueprints/data-solutions/bq-ml/README.md @@ -0,0 +1,6 @@ +# BQ ML and Vertex Pipeline + +This blueprint creates #TODO + + + diff --git a/blueprints/data-solutions/bq-ml/main.tf b/blueprints/data-solutions/bq-ml/main.tf new file mode 100644 index 00000000..2d7ab45b --- /dev/null +++ b/blueprints/data-solutions/bq-ml/main.tf @@ -0,0 +1,247 @@ +# 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. + +############################################################################### +# Project # +############################################################################### +locals { + service_encryption_keys = var.service_encryption_keys + shared_vpc_project = try(var.network_config.host_project, null) + + subnet = ( + local.use_shared_vpc + ? var.network_config.subnet_self_link + : values(module.vpc.0.subnet_self_links)[0] + ) + vpc = ( + local.use_shared_vpc + ? var.network_config.network_self_link + : module.vpc.0.self_link + ) + use_shared_vpc = var.network_config != null + + shared_vpc_bindings = { + "roles/compute.networkUser" = [ + "robot-df", "notebooks" + ] + } + + shared_vpc_role_members = { + robot-df = "serviceAccount:${module.project.service_accounts.robots.dataflow}" + notebooks = "serviceAccount:${module.project.service_accounts.robots.notebooks}" + } + + # reassemble in a format suitable for for_each + shared_vpc_bindings_map = { + for binding in flatten([ + for role, members in local.shared_vpc_bindings : [ + for member in members : { role = role, member = member } + ] + ]) : "${binding.role}-${binding.member}" => binding + } +} + +module "project" { + source = "../../../modules/project" + name = var.project_id + parent = try(var.project_create.parent, null) + billing_account = try(var.project_create.billing_account_id, null) + project_create = var.project_create != null + prefix = var.project_create == null ? null : var.prefix + services = [ + "aiplatform.googleapis.com", + "bigquery.googleapis.com", + "bigquerystorage.googleapis.com", + "bigqueryreservation.googleapis.com", + "compute.googleapis.com", + "ml.googleapis.com", + "notebooks.googleapis.com", + "servicenetworking.googleapis.com", + "stackdriver.googleapis.com", + "storage.googleapis.com", + "storage-component.googleapis.com" + ] + + shared_vpc_service_config = local.shared_vpc_project == null ? null : { + attach = true + host_project = local.shared_vpc_project + } + + service_encryption_key_ids = { + compute = [try(local.service_encryption_keys.compute, null)] + bq = [try(local.service_encryption_keys.bq, null)] + storage = [try(local.service_encryption_keys.storage, null)] + } + service_config = { + disable_on_destroy = false, disable_dependent_services = false + } +} + +############################################################################### +# Networking # +############################################################################### + +module "vpc" { + source = "../../../modules/net-vpc" + count = local.use_shared_vpc ? 0 : 1 + project_id = module.project.project_id + name = "${var.prefix}-vpc" + subnets = [ + { + ip_cidr_range = "10.0.0.0/20" + name = "${var.prefix}-subnet" + region = var.region + } + ] +} + +module "vpc-firewall" { + source = "../../../modules/net-vpc-firewall" + count = local.use_shared_vpc ? 0 : 1 + project_id = module.project.project_id + network = module.vpc.0.name + default_rules_config = { + admin_ranges = ["10.0.0.0/20"] + } + ingress_rules = { + #TODO Remove and rely on 'ssh' tag once terraform-provider-google/issues/9273 is fixed + ("${var.prefix}-iap") = { + description = "Enable SSH from IAP on Notebooks." + source_ranges = ["35.235.240.0/20"] + targets = ["notebook-instance"] + rules = [{ protocol = "tcp", ports = [22] }] + } + } +} + +module "cloudnat" { + source = "../../../modules/net-cloudnat" + count = local.use_shared_vpc ? 0 : 1 + project_id = module.project.project_id + name = "${var.prefix}-default" + region = var.region + router_network = module.vpc.0.name +} + +resource "google_project_iam_member" "shared_vpc" { + count = local.use_shared_vpc ? 1 : 0 + project = var.network_config.host_project + role = "roles/compute.networkUser" + member = "serviceAccount:${module.project.service_accounts.robots.notebooks}" +} + + +############################################################################### +# Storage # +############################################################################### + +module "bucket" { + source = "../../../modules/gcs" + project_id = module.project.project_id + prefix = var.prefix + location = var.location + name = "data" + encryption_key = try(local.service_encryption_keys.storage, null) # Example assignment of an encryption key +} + +module "dataset" { + source = "../../../modules/bigquery-dataset" + project_id = module.project.project_id + id = "${replace(var.prefix, "-", "_")}_data" + encryption_key = try(local.service_encryption_keys.bq, null) # Example assignment of an encryption key +} + +############################################################################### +# Vertex AI # +############################################################################### +resource "google_vertex_ai_metadata_store" "store" { + provider = google-beta + project = module.project.project_id + name = "${var.prefix}-metadata-store" + description = "Vertex Ai Metadata Store" + region = var.region + #TODO Check/Implement P4SA logic for IAM role + # encryption_spec { + # kms_key_name = var.service_encryption_keys.ai_metadata_store + # } +} + +module "service-account-notebook" { + source = "../../../modules/iam-service-account" + project_id = module.project.project_id + name = "notebook-sa" + iam_project_roles = { + (module.project.project_id) = [ + "roles/bigquery.admin", + "roles/bigquery.jobUser", + "roles/bigquery.dataEditor", + "roles/bigquery.user", + "roles/dialogflow.client", + "roles/storage.admin", + ] + } +} + +module "service-account-vertex" { + source = "../../../modules/iam-service-account" + project_id = module.project.project_id + name = "vertex-sa" + iam_project_roles = { + (module.project.project_id) = [ + "roles/bigquery.admin", + "roles/bigquery.jobUser", + "roles/bigquery.dataEditor", + "roles/bigquery.user", + "roles/dialogflow.client", + "roles/storage.admin", + ] + } +} + +resource "google_notebooks_instance" "playground" { + name = "${var.prefix}-notebook" + location = format("%s-%s", var.region, "b") + machine_type = "e2-medium" + project = module.project.project_id + + container_image { + repository = "gcr.io/deeplearning-platform-release/base-cpu" + tag = "latest" + } + + install_gpu_driver = true + boot_disk_type = "PD_SSD" + boot_disk_size_gb = 110 + disk_encryption = try(local.service_encryption_keys.compute != null, false) ? "CMEK" : null + kms_key = try(local.service_encryption_keys.compute, null) + + no_public_ip = true + no_proxy_access = false + + network = local.vpc + subnet = local.subnet + + service_account = module.service-account-notebook.email + + # Remove once terraform-provider-google/issues/9164 is fixed + lifecycle { + ignore_changes = [disk_encryption, kms_key] + } + + #TODO Uncomment once terraform-provider-google/issues/9273 is fixed + # tags = ["ssh"] + depends_on = [ + google_project_iam_member.shared_vpc, + ] +} diff --git a/blueprints/data-solutions/bq-ml/outputs.tf b/blueprints/data-solutions/bq-ml/outputs.tf new file mode 100644 index 00000000..2b62074b --- /dev/null +++ b/blueprints/data-solutions/bq-ml/outputs.tf @@ -0,0 +1,52 @@ +# Copyright 2022 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. + +output "bucket" { + description = "GCS Bucket URL." + value = module.bucket.url +} + +output "dataset" { + description = "GCS Bucket URL." + value = module.dataset.id +} + +output "notebook" { + description = "Vertex AI notebook details." + value = { + name = resource.google_notebooks_instance.playground.name + id = resource.google_notebooks_instance.playground.id + } +} + +output "project" { + description = "Project id." + value = module.project.project_id +} + +output "vpc" { + description = "VPC Network." + value = local.vpc +} + +output "service-account-vertex" { + description = "Service account to be used for Vertex AI pipelines" + value = module.service-account-vertex.email +} + +output "vertex-ai-metadata-store" { + description = "" + value = google_vertex_ai_metadata_store.store.id + +} diff --git a/blueprints/data-solutions/bq-ml/variables.tf b/blueprints/data-solutions/bq-ml/variables.tf new file mode 100644 index 00000000..3bd0ca65 --- /dev/null +++ b/blueprints/data-solutions/bq-ml/variables.tf @@ -0,0 +1,69 @@ +# Copyright 2022 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. + + +variable "location" { + description = "The location where resources will be deployed." + type = string + default = "EU" +} + +variable "network_config" { + description = "Shared VPC network configurations to use. If null networks will be created in projects with preconfigured values." + type = object({ + host_project = string + network_self_link = string + subnet_self_link = string + }) + default = null +} + +variable "prefix" { + description = "Prefix used for resource names." + type = string + validation { + condition = var.prefix != "" + error_message = "Prefix cannot be empty." + } +} + +variable "project_create" { + description = "Provide values if project creation is needed, uses existing project if null. Parent format: folders/folder_id or organizations/org_id." + type = object({ + billing_account_id = string + parent = string + }) + default = null +} + +variable "project_id" { + description = "Project id, references existing project if `project_create` is null." + type = string +} + +variable "region" { + description = "The region where resources will be deployed." + type = string + default = "europe-west1" +} + +variable "service_encryption_keys" { # service encription key + description = "Cloud KMS to use to encrypt different services. Key location should match service region." + type = object({ + bq = string + compute = string + storage = string + }) + default = null +} diff --git a/blueprints/data-solutions/bq-ml/versions.tf b/blueprints/data-solutions/bq-ml/versions.tf new file mode 100644 index 00000000..08492c6f --- /dev/null +++ b/blueprints/data-solutions/bq-ml/versions.tf @@ -0,0 +1,29 @@ +# Copyright 2022 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. + +terraform { + required_version = ">= 1.3.1" + required_providers { + google = { + source = "hashicorp/google" + version = ">= 4.50.0" # tftest + } + google-beta = { + source = "hashicorp/google-beta" + version = ">= 4.50.0" # tftest + } + } +} + + From a51c68200542780ded5bd37d05375369e0ce85d7 Mon Sep 17 00:00:00 2001 From: Giorgio Conte Date: Fri, 24 Feb 2023 13:27:44 +0000 Subject: [PATCH 02/49] Updated tf file to add the following features: - default location of dataset to US - changed name of vertex metastore to "default" - add ai user and service account us to notebook SA - add ai user to vertex sa --- blueprints/data-solutions/bq-ml/main.tf | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/blueprints/data-solutions/bq-ml/main.tf b/blueprints/data-solutions/bq-ml/main.tf index 2d7ab45b..77ae55ea 100644 --- a/blueprints/data-solutions/bq-ml/main.tf +++ b/blueprints/data-solutions/bq-ml/main.tf @@ -160,6 +160,7 @@ module "dataset" { project_id = module.project.project_id id = "${replace(var.prefix, "-", "_")}_data" encryption_key = try(local.service_encryption_keys.bq, null) # Example assignment of an encryption key + location = "US" } ############################################################################### @@ -168,7 +169,7 @@ module "dataset" { resource "google_vertex_ai_metadata_store" "store" { provider = google-beta project = module.project.project_id - name = "${var.prefix}-metadata-store" + name = "default" #"${var.prefix}-metadata-store" description = "Vertex Ai Metadata Store" region = var.region #TODO Check/Implement P4SA logic for IAM role @@ -189,6 +190,8 @@ module "service-account-notebook" { "roles/bigquery.user", "roles/dialogflow.client", "roles/storage.admin", + "roles/aiplatform.user", + "roles/iam.serviceAccountUser" ] } } @@ -205,6 +208,7 @@ module "service-account-vertex" { "roles/bigquery.user", "roles/dialogflow.client", "roles/storage.admin", + "roles/aiplatform.user" ] } } @@ -234,6 +238,12 @@ resource "google_notebooks_instance" "playground" { service_account = module.service-account-notebook.email + # Enable Secure Boot + + shielded_instance_config { + enable_secure_boot = true + } + # Remove once terraform-provider-google/issues/9164 is fixed lifecycle { ignore_changes = [disk_encryption, kms_key] From 3271acd2f2f6ae7907b7b4b214ac9c4b466f894c Mon Sep 17 00:00:00 2001 From: Giorgio Conte Date: Mon, 27 Feb 2023 10:56:47 +0000 Subject: [PATCH 03/49] Added sql and jupyter notebook to run the demo --- .../bq-ml/demo/bmql_pipeline.ipynb | 290 ++++++++++++++++++ .../bq-ml/demo/requirements.txt | 2 + .../bq-ml/demo/sql/explain_predict.sql | 8 + .../bq-ml/demo/sql/features.sql | 19 ++ .../data-solutions/bq-ml/demo/sql/train.sql | 11 + 5 files changed, 330 insertions(+) create mode 100644 blueprints/data-solutions/bq-ml/demo/bmql_pipeline.ipynb create mode 100644 blueprints/data-solutions/bq-ml/demo/requirements.txt create mode 100644 blueprints/data-solutions/bq-ml/demo/sql/explain_predict.sql create mode 100644 blueprints/data-solutions/bq-ml/demo/sql/features.sql create mode 100644 blueprints/data-solutions/bq-ml/demo/sql/train.sql diff --git a/blueprints/data-solutions/bq-ml/demo/bmql_pipeline.ipynb b/blueprints/data-solutions/bq-ml/demo/bmql_pipeline.ipynb new file mode 100644 index 00000000..58a1eddc --- /dev/null +++ b/blueprints/data-solutions/bq-ml/demo/bmql_pipeline.ipynb @@ -0,0 +1,290 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%pip install -r requirements.txt" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import kfp\n", + "from google.cloud import aiplatform as aip\n", + "import google_cloud_pipeline_components.v1.bigquery as bqop" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Set your env variable" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "PREFIX = 'your-prefix'\n", + "PROJECT_ID = 'your-project-id'\n", + "LOCATION = 'US'\n", + "REGION = 'us-central1'\n", + "PIPELINE_NAME = 'bqml-vertex-pipeline'\n", + "MODEL_NAME = 'bqml-model'\n", + "EXPERIMENT_NAME = 'bqml-experiment'\n", + "ENDPOINT_DISPLAY_NAME = 'bqml-endpoint'\n", + "\n", + "SERVICE_ACCOUNT = f\"vertex-sa@{PROJECT_ID}.iam.gserviceaccount.com\"\n", + "PIPELINE_ROOT = f\"gs://{PREFIX}-data\"\n", + "DATASET = \"{}_data\".format(PREFIX.replace(\"-\",\"_\")) " + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Vertex Pipeline Definition\n", + "\n", + "In the following code block we are defining our Vertex AI pipeline. It is made up of three main steps:\n", + "1. Create a BigQuery dataset which will contains the BQ ML models\n", + "2. Train the BQ ML model, in this case a logistic regression\n", + "3. Evaluate the BQ ML model with the standard evaluation metrics\n", + "\n", + "The pipeline takes as input the following variables:\n", + "- ```model_name```: the display name of the BQ ML model\n", + "- ```split_fraction```: the percentage of data that will be used as evaluation dataset\n", + "- ```evaluate_job_conf```: bq dict configuration to define where to store evalution metrics\n", + "- ```dataset```: name of dataset where the artifacts will be stored\n", + "- ```project_id```: the project id where the GCP resources will be created\n", + "- ```location```: BigQuery location" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "with open(\"sql/train.sql\") as file:\n", + " train_query = file.read()\n", + "\n", + "with open(\"sql/features.sql\") as file:\n", + " features_query = file.read()\n", + "\n", + "\n", + "@kfp.dsl.pipeline(name='bqml-pipeline', pipeline_root=PIPELINE_ROOT)\n", + "def pipeline(\n", + " model_name: str,\n", + " split_fraction: float,\n", + " evaluate_job_conf: dict, \n", + " dataset: str = DATASET,\n", + " project_id: str = PROJECT_ID,\n", + " location: str = LOCATION,\n", + " ):\n", + "\n", + " create_dataset = bqop.BigqueryQueryJobOp(\n", + " project=project_id,\n", + " location=location,\n", + " query=f'CREATE SCHEMA IF NOT EXISTS {dataset}'\n", + " )\n", + "\n", + " create_features_table = bqop.BigqueryQueryJobOp(\n", + " project=project_id,\n", + " location=location,\n", + " query=features_query.format(dataset=dataset, project_id=project_id),\n", + " #job_configuration_query = {\"writeDisposition\": \"WRITE_TRUNCATE\"} #, \"destinationTable\":{\"projectId\":project_id,\"datasetId\":dataset,\"tableId\":\"ecommerce_abt_table\"}} #{\"destinationTable\":{\"projectId\":\"project_id\",\"datasetId\":dataset,\"tableId\":\"ecommerce_abt_table\"}}, #\"writeDisposition\": \"WRITE_TRUNCATE\", \n", + "\n", + " ).after(create_dataset)\n", + "\n", + " create_bqml_model = bqop.BigqueryCreateModelJobOp(\n", + " project=project_id,\n", + " location=location,\n", + " query=train_query.format(model_type = 'LOGISTIC_REG'\n", + " , project_id = project_id\n", + " , dataset = dataset\n", + " , model_name = model_name\n", + " , split_fraction=split_fraction)\n", + " ).after(create_features_table)\n", + "\n", + " evaluate_bqml_model = bqop.BigqueryEvaluateModelJobOp(\n", + " project=project_id,\n", + " location=location,\n", + " model=create_bqml_model.outputs[\"model\"],\n", + " job_configuration_query=evaluate_job_conf\n", + " ).after(create_bqml_model)\n", + "\n", + "\n", + "# this is to compile our pipeline and generate the json description file\n", + "kfp.v2.compiler.Compiler().compile(pipeline_func=pipeline,\n", + " package_path=f'{PIPELINE_NAME}.json') " + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Create Experiment\n", + "\n", + "We will create an experiment in order to keep track of our trainings and tasks on a specific issue or problem." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "my_experiment = aip.Experiment.get_or_create(\n", + " experiment_name=EXPERIMENT_NAME,\n", + " description='This is a new experiment to keep track of bqml trainings',\n", + " project=PROJECT_ID,\n", + " location=REGION\n", + " )" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Running the same training pipeline with different parameters\n", + "\n", + "One of the main tasks during the training phase is to compare different models or to try the same model with different inputs. We can leverage the power of Vertex Pipelines in order to submit the same steps with different training parameters. Thanks to the experiments artifact it is possible to easily keep track of all the tests that have been done. This simplifies the process to select the best model to deploy.\n", + "\n", + "In this demo case, we will run the same training pipeline while changing the data split percentage between training and test data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# this configuration is needed in order to persist the evaluation metrics on big query\n", + "job_configuration_query = {\"destinationTable\": {\"projectId\": PROJECT_ID, \"datasetId\": DATASET}, \"writeDisposition\": \"WRITE_TRUNCATE\"}\n", + "\n", + "for split_fraction in [0.1, 0.2]:\n", + " job_configuration_query['destinationTable']['tableId'] = MODEL_NAME+'-fraction-{}-eval_table'.format(int(split_fraction*100))\n", + " pipeline = aip.PipelineJob(\n", + " parameter_values = {'split_fraction':split_fraction, 'model_name': MODEL_NAME+'-fraction-{}'.format(int(split_fraction*100)), 'evaluate_job_conf': job_configuration_query },\n", + " display_name=PIPELINE_NAME,\n", + " template_path=f'{PIPELINE_NAME}.json',\n", + " pipeline_root=PIPELINE_ROOT,\n", + " enable_caching=True\n", + " \n", + " )\n", + "\n", + " pipeline.submit(service_account=SERVICE_ACCOUNT, experiment=my_experiment)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Deploy the model to an endpoint\n", + "\n", + "Thanks to the integration of Vertex Endpoint, it is very straightforward to create a live endpoint to serve the model which we prefer." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# get the model from the Model Registry \n", + "model = aip.Model(model_name='levelup_model_name-fraction-10')\n", + "\n", + "# let's create a Vertex Endpoint where we will deploy the ML model\n", + "endpoint = aip.Endpoint.create(\n", + " display_name=ENDPOINT_DISPLAY_NAME,\n", + " project=PROJECT_ID,\n", + " location=REGION,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "ename": "", + "evalue": "", + "output_type": "error", + "traceback": [ + "\u001b[1;31mRunning cells with '/usr/bin/python3' requires the ipykernel package.\n", + "\u001b[1;31mRun the following command to install 'ipykernel' into the Python environment. \n", + "\u001b[1;31mCommand: '/usr/bin/python3 -m pip install ipykernel -U --user --force-reinstall'" + ] + } + ], + "source": [ + "# deploy the BQ ML model on Vertex Endpoint\n", + "# have a coffe - this step can take up 10/15 minutes to finish\n", + "model.deploy(endpoint=endpoint, deployed_model_display_name='bqml-deployed-model')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Let's get a prediction from new data\n", + "inference_test = {\n", + " 'postal_code': '97700-000',\n", + " 'number_of_successful_orders': 0,\n", + " 'city': 'Santiago',\n", + " 'sum_previous_orders': 1,\n", + " 'number_of_unsuccessful_orders': 0,\n", + " 'day_of_week': 'WEEKDAY',\n", + " 'traffic_source': 'Facebook',\n", + " 'browser': 'Firefox',\n", + " 'hour_of_day': 20}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "my_prediction = endpoint.predict([inference_test])\n", + "\n", + "my_prediction" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.10.9" + }, + "orig_nbformat": 4, + "vscode": { + "interpreter": { + "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6" + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/blueprints/data-solutions/bq-ml/demo/requirements.txt b/blueprints/data-solutions/bq-ml/demo/requirements.txt new file mode 100644 index 00000000..82953973 --- /dev/null +++ b/blueprints/data-solutions/bq-ml/demo/requirements.txt @@ -0,0 +1,2 @@ +kfp==1.8.19 +google-cloud-pipeline-components==1.0.39 \ No newline at end of file diff --git a/blueprints/data-solutions/bq-ml/demo/sql/explain_predict.sql b/blueprints/data-solutions/bq-ml/demo/sql/explain_predict.sql new file mode 100644 index 00000000..0d67bc7c --- /dev/null +++ b/blueprints/data-solutions/bq-ml/demo/sql/explain_predict.sql @@ -0,0 +1,8 @@ +select * +from ML.EXPLAIN_PREDICT(MODEL `{project-id}.{dataset}.{model-name}`, + (select * except (session_id, session_starting_ts, user_id, has_purchased) + from `{project-id}.{dataset}.ecommerce_abt` + where extract(ISOYEAR from session_starting_ts) = 2023 + ), + STRUCT(5 AS top_k_features, 0.5 as threshold) +) \ No newline at end of file diff --git a/blueprints/data-solutions/bq-ml/demo/sql/features.sql b/blueprints/data-solutions/bq-ml/demo/sql/features.sql new file mode 100644 index 00000000..02434329 --- /dev/null +++ b/blueprints/data-solutions/bq-ml/demo/sql/features.sql @@ -0,0 +1,19 @@ +with abt as ( + SELECT user_id, session_id, city, postal_code, browser,traffic_source, min(created_at) as session_starting_ts, sum(case when event_type = 'purchase' then 1 else 0 end) has_purchased + FROM `bigquery-public-data.thelook_ecommerce.events` + group by user_id, session_id, city, postal_code, browser, traffic_source +), previous_orders as ( +select user_id, array_agg (struct(created_at as order_creations_ts, o.order_id, o.status, oi.order_cost )) as user_orders + from `bigquery-public-data.thelook_ecommerce.orders` o + join (select order_id, sum(sale_price) order_cost + from `bigquery-public-data.thelook_ecommerce.order_items` group by 1) oi + on o.order_id = oi.order_id + group by 1 +) +select abt.*, case when extract(DAYOFWEEK from session_starting_ts) in (1,7) then 'WEEKEND' else 'WEEKDAY' end as day_of_week, extract(hour from session_starting_ts) hour_of_day + , (select count(distinct uo.order_id) from unnest(user_orders) uo where uo.order_creations_ts < session_starting_ts and status in ('Shipped', 'Complete', 'Processing') ) as number_of_successful_orders + , IFNULL((select sum(distinct uo.order_cost) from unnest(user_orders) uo where uo.order_creations_ts < session_starting_ts and status in ('Shipped', 'Complete', 'Processing') ), 0) as sum_previous_orders + , (select count(distinct uo.order_id) from unnest(user_orders) uo where uo.order_creations_ts < session_starting_ts and status in ('Cancelled', 'Returned') ) as number_of_unsuccessful_orders +from abt + left join previous_orders pso + on abt.user_id = pso.user_id \ No newline at end of file diff --git a/blueprints/data-solutions/bq-ml/demo/sql/train.sql b/blueprints/data-solutions/bq-ml/demo/sql/train.sql new file mode 100644 index 00000000..0f5517b8 --- /dev/null +++ b/blueprints/data-solutions/bq-ml/demo/sql/train.sql @@ -0,0 +1,11 @@ +create or replace model `{project_id}.{dataset}.{model_name}` +OPTIONS(model_type='{model_type}', + input_label_cols=['has_purchased'], + enable_global_explain=TRUE, + MODEL_REGISTRY='VERTEX_AI', + data_split_method = 'RANDOM', + data_split_eval_fraction = {split_fraction} + ) as +select * except (session_id, session_starting_ts, user_id) +from `{project_id}.{dataset}.ecommerce_abt_table` +where extract(ISOYEAR from session_starting_ts) = 2022 \ No newline at end of file From 17b8a461f08dd12dd3f2d4fa8c0a5d661fe4603e Mon Sep 17 00:00:00 2001 From: Giorgio Conte Date: Mon, 27 Feb 2023 15:28:49 +0000 Subject: [PATCH 04/49] fixed notebook with dynamic model name cleared output from cells added creation of view instead of table --- .../bq-ml/demo/bmql_pipeline.ipynb | 19 ++++--------------- .../bq-ml/demo/sql/features.sql | 2 ++ 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/blueprints/data-solutions/bq-ml/demo/bmql_pipeline.ipynb b/blueprints/data-solutions/bq-ml/demo/bmql_pipeline.ipynb index 58a1eddc..07719fa9 100644 --- a/blueprints/data-solutions/bq-ml/demo/bmql_pipeline.ipynb +++ b/blueprints/data-solutions/bq-ml/demo/bmql_pipeline.ipynb @@ -98,7 +98,7 @@ " query=f'CREATE SCHEMA IF NOT EXISTS {dataset}'\n", " )\n", "\n", - " create_features_table = bqop.BigqueryQueryJobOp(\n", + " create_features_view = bqop.BigqueryQueryJobOp(\n", " project=project_id,\n", " location=location,\n", " query=features_query.format(dataset=dataset, project_id=project_id),\n", @@ -114,7 +114,7 @@ " , dataset = dataset\n", " , model_name = model_name\n", " , split_fraction=split_fraction)\n", - " ).after(create_features_table)\n", + " ).after(create_features_view)\n", "\n", " evaluate_bqml_model = bqop.BigqueryEvaluateModelJobOp(\n", " project=project_id,\n", @@ -205,7 +205,7 @@ "outputs": [], "source": [ "# get the model from the Model Registry \n", - "model = aip.Model(model_name='levelup_model_name-fraction-10')\n", + "model = aip.Model(model_name=f'{MODEL_NAME}-fraction-10')\n", "\n", "# let's create a Vertex Endpoint where we will deploy the ML model\n", "endpoint = aip.Endpoint.create(\n", @@ -219,18 +219,7 @@ "cell_type": "code", "execution_count": null, "metadata": {}, - "outputs": [ - { - "ename": "", - "evalue": "", - "output_type": "error", - "traceback": [ - "\u001b[1;31mRunning cells with '/usr/bin/python3' requires the ipykernel package.\n", - "\u001b[1;31mRun the following command to install 'ipykernel' into the Python environment. \n", - "\u001b[1;31mCommand: '/usr/bin/python3 -m pip install ipykernel -U --user --force-reinstall'" - ] - } - ], + "outputs": [], "source": [ "# deploy the BQ ML model on Vertex Endpoint\n", "# have a coffe - this step can take up 10/15 minutes to finish\n", diff --git a/blueprints/data-solutions/bq-ml/demo/sql/features.sql b/blueprints/data-solutions/bq-ml/demo/sql/features.sql index 02434329..63b79d82 100644 --- a/blueprints/data-solutions/bq-ml/demo/sql/features.sql +++ b/blueprints/data-solutions/bq-ml/demo/sql/features.sql @@ -1,3 +1,5 @@ +CREATE view if not exists `{project_id}.{dataset}.ecommerce_abt` as + with abt as ( SELECT user_id, session_id, city, postal_code, browser,traffic_source, min(created_at) as session_starting_ts, sum(case when event_type = 'purchase' then 1 else 0 end) has_purchased FROM `bigquery-public-data.thelook_ecommerce.events` From dc3778302286108e2e46ffbaf0a6b3287b3c9870 Mon Sep 17 00:00:00 2001 From: lcaggio Date: Wed, 1 Mar 2023 07:54:10 +0100 Subject: [PATCH 05/49] Fix Variables --- modules/dataproc/README.md | 29 +++++++++++++++++++++++++++++ modules/dataproc/main.tf | 26 +++++++++++++------------- modules/dataproc/variables.tf | 6 +++--- 3 files changed, 45 insertions(+), 16 deletions(-) diff --git a/modules/dataproc/README.md b/modules/dataproc/README.md index 80835dd1..982d043c 100644 --- a/modules/dataproc/README.md +++ b/modules/dataproc/README.md @@ -46,6 +46,35 @@ module "processing-dp-cluster" { # tftest modules=1 resources=1 ``` +### Cluster with CMEK encrypotion + +To set cluster configuration use the Customer Managed Encryption key, set '' variable. The Compute Engine service agent and the Cloud Storage service agent needs to have 'CryptoKey Encrypter/Decrypter' role on they configured KMS key ([Documentation](https://cloud.google.com/dataproc/docs/concepts/configuring-clusters/customer-managed-encryption)). + +```hcl +module "processing-dp-cluster" { + source = "./fabric/modules/dataproc" + project_id = "my-project" + name = "my-cluster" + region = "europe-west1" + prefix = "prefix" + dataproc_config = { + cluster_config = { + gce_cluster_config = { + subnetwork = "https://www.googleapis.com/compute/v1/projects/PROJECT/regions/europe-west1/subnetworks/SUBNET" + zone = "europe-west1-b" + service_account = "" + service_account_scopes = ["cloud-platform"] + internal_ip_only = true + } + } + } + encryption_config = try({ + kms_key_name = "projects/project-id/locations/region/keyRings/key-ring-name/cryptoKeys/key-name" + }, null) +} +# tftest modules=1 resources=1 +``` + ## IAM Examples IAM is managed via several variables that implement different levels of control: diff --git a/modules/dataproc/main.tf b/modules/dataproc/main.tf index ab09cbea..55bef5c7 100644 --- a/modules/dataproc/main.tf +++ b/modules/dataproc/main.tf @@ -59,9 +59,9 @@ resource "google_dataproc_cluster" "cluster" { dynamic "shielded_instance_config" { for_each = var.dataproc_config.cluster_config.gce_cluster_config.shielded_instance_config == null ? [] : [""] content { - enable_secure_boot = var.dataproc_config.cluster_config.gce_cluster_config.shielded_instance_config.value.enable_secure_boot - enable_vtpm = var.dataproc_config.cluster_config.gce_cluster_config.shielded_instance_config.value.enable_vtpm - enable_integrity_monitoring = var.dataproc_config.cluster_config.gce_cluster_config.shielded_instance_config.value.enable_integrity_monitoring + enable_secure_boot = var.dataproc_config.cluster_config.gce_cluster_config.shielded_instance_config.enable_secure_boot + enable_vtpm = var.dataproc_config.cluster_config.gce_cluster_config.shielded_instance_config.enable_vtpm + enable_integrity_monitoring = var.dataproc_config.cluster_config.gce_cluster_config.shielded_instance_config.enable_integrity_monitoring } } } @@ -99,9 +99,9 @@ resource "google_dataproc_cluster" "cluster" { dynamic "disk_config" { for_each = var.dataproc_config.cluster_config.worker_config.disk_config == null ? [] : [""] content { - boot_disk_type = var.dataproc_config.cluster_config.worker_config.disk_config.value.boot_disk_type - boot_disk_size_gb = var.dataproc_config.cluster_config.worker_config.disk_config.value.boot_disk_size_gb - num_local_ssds = var.dataproc_config.cluster_config.worker_config.disk_config.value.num_local_ssds + boot_disk_type = var.dataproc_config.cluster_config.worker_config.disk_config.boot_disk_type + boot_disk_size_gb = var.dataproc_config.cluster_config.worker_config.disk_config.boot_disk_size_gb + num_local_ssds = var.dataproc_config.cluster_config.worker_config.disk_config.num_local_ssds } } image_uri = var.dataproc_config.cluster_config.worker_config.image_uri @@ -165,20 +165,20 @@ resource "google_dataproc_cluster" "cluster" { dynamic "autoscaling_config" { for_each = var.dataproc_config.cluster_config.autoscaling_config == null ? [] : [""] content { - policy_uri = var.dataproc_config.cluster_config.autoscaling_config.value.policy_uri + policy_uri = var.dataproc_config.cluster_config.autoscaling_config.policy_uri } } dynamic "initialization_action" { for_each = var.dataproc_config.cluster_config.initialization_action == null ? [] : [""] content { - script = var.dataproc_config.cluster_config.initialization_action.value.script - timeout_sec = var.dataproc_config.cluster_config.initialization_action.value.timeout_sec + script = var.dataproc_config.cluster_config.initialization_action.script + timeout_sec = var.dataproc_config.cluster_config.initialization_action.timeout_sec } } dynamic "encryption_config" { - for_each = var.dataproc_config.cluster_config.encryption_config == null ? [] : [""] + for_each = try(var.dataproc_config.cluster_config.encryption_config.kms_key_name == null ? [] : [""], []) content { - kms_key_name = var.dataproc_config.cluster_config.encryption_config.value.kms_key_name + kms_key_name = var.dataproc_config.cluster_config.encryption_config.kms_key_name } } dynamic "dataproc_metric_config" { @@ -243,8 +243,8 @@ resource "google_dataproc_cluster" "cluster" { dynamic "kubernetes_software_config" { for_each = var.dataproc_config.virtual_cluster_config.kubernetes_cluster_config.kubernetes_software_config == null ? [] : [""] content { - component_version = var.dataproc_config.virtual_cluster_config.kubernetes_cluster_config.kubernetes_software_config.value.component_version - properties = var.dataproc_config.virtual_cluster_config.kubernetes_cluster_config.kubernetes_software_config.value.properties + component_version = var.dataproc_config.virtual_cluster_config.kubernetes_cluster_config.kubernetes_software_config.component_version + properties = var.dataproc_config.virtual_cluster_config.kubernetes_cluster_config.kubernetes_software_config.properties } } diff --git a/modules/dataproc/variables.tf b/modules/dataproc/variables.tf index 3636a706..314d2431 100644 --- a/modules/dataproc/variables.tf +++ b/modules/dataproc/variables.tf @@ -84,9 +84,9 @@ variable "dataproc_config" { }), null) }), null) software_config = optional(object({ - image_version = string - override_properties = list(map(string)) - optional_components = list(string) + image_version = optional(string, null) + override_properties = map(string) + optional_components = optional(list(string), null) }), null) security_config = optional(object({ kerberos_config = object({ From dad3c4901275f62695810a1256d7d2d078674437 Mon Sep 17 00:00:00 2001 From: lcaggio Date: Wed, 1 Mar 2023 08:00:46 +0100 Subject: [PATCH 06/49] Fix linting --- modules/dataproc/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/dataproc/README.md b/modules/dataproc/README.md index 982d043c..0a0c4a4b 100644 --- a/modules/dataproc/README.md +++ b/modules/dataproc/README.md @@ -148,7 +148,7 @@ module "processing-dp-cluster" { | [name](variables.tf#L211) | Cluster name. | string | ✓ | | | [project_id](variables.tf#L226) | Project ID. | string | ✓ | | | [region](variables.tf#L231) | Dataproc region. | string | ✓ | | -| [dataproc_config](variables.tf#L17) | Dataproc cluster config. | object({…}) | | {} | +| [dataproc_config](variables.tf#L17) | Dataproc cluster config. | object({…}) | | {} | | [group_iam](variables.tf#L184) | Authoritative IAM binding for organization groups, in {GROUP_EMAIL => [ROLES]} format. Group emails need to be static. Can be used in combination with the `iam` variable. | map(list(string)) | | {} | | [iam](variables.tf#L191) | IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | [iam_additive](variables.tf#L198) | IAM additive bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | From 3a2d6e1b46b138b0dd097cd4b91c35fea100aa37 Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Wed, 1 Mar 2023 08:08:07 +0100 Subject: [PATCH 07/49] Fix secondary ranges in net-vpc readme (#1198) Fixes #1197 --- modules/net-vpc/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/net-vpc/README.md b/modules/net-vpc/README.md index bd5675d2..25dfaa58 100644 --- a/modules/net-vpc/README.md +++ b/modules/net-vpc/README.md @@ -174,7 +174,7 @@ module "vpc-host" { ip_cidr_range = "10.0.0.0/24" name = "subnet-1" region = "europe-west1" - secondary_ip_range = { + secondary_ip_ranges = { pods = "172.16.0.0/20" services = "192.168.0.0/24" } From 67bc391b66286072dcccd6c036d2bf67d6a2e551 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Wed, 1 Mar 2023 09:58:50 +0100 Subject: [PATCH 08/49] Add test for #1197 --- tests/modules/net_vpc/examples/shared-vpc.yaml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/modules/net_vpc/examples/shared-vpc.yaml b/tests/modules/net_vpc/examples/shared-vpc.yaml index b004e315..6467fd38 100644 --- a/tests/modules/net_vpc/examples/shared-vpc.yaml +++ b/tests/modules/net_vpc/examples/shared-vpc.yaml @@ -24,7 +24,12 @@ values: module.vpc-host.google_compute_shared_vpc_service_project.service_projects["project2"]: host_project: my-project service_project: project2 - module.vpc-host.google_compute_subnetwork.subnetwork["europe-west1/subnet-1"]: {} + module.vpc-host.google_compute_subnetwork.subnetwork["europe-west1/subnet-1"]: + secondary_ip_range: + - ip_cidr_range: 172.16.0.0/20 + range_name: pods + - ip_cidr_range: 192.168.0.0/24 + range_name: services module.vpc-host.google_compute_subnetwork_iam_binding.binding["europe-west1/subnet-1.roles/compute.networkUser"]: condition: [] members: From d7dae1da08b707989637547c773588b5be4a1f6a Mon Sep 17 00:00:00 2001 From: Aleksandr Averbukh Date: Wed, 1 Mar 2023 10:33:08 +0100 Subject: [PATCH 09/49] Add missing tfvars template to the tfc blueprint --- .../terraform.auto.tfvars.template | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 blueprints/cloud-operations/terraform-cloud-dynamic-credentials/gcp-workload-identity-provider/terraform.auto.tfvars.template diff --git a/blueprints/cloud-operations/terraform-cloud-dynamic-credentials/gcp-workload-identity-provider/terraform.auto.tfvars.template b/blueprints/cloud-operations/terraform-cloud-dynamic-credentials/gcp-workload-identity-provider/terraform.auto.tfvars.template new file mode 100644 index 00000000..2d2167a3 --- /dev/null +++ b/blueprints/cloud-operations/terraform-cloud-dynamic-credentials/gcp-workload-identity-provider/terraform.auto.tfvars.template @@ -0,0 +1,18 @@ +# 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. + +billing_account = "015647-1A8CBC-A002D9" +parent = "folders/0123456789" +tfc_organization_id = "org-W3bx9naazHrUz99U" +tfc_workspace_id = "ws-2GnGJtqTfvxKptHn" From 2d9dd5071c3c7ac2a6c1144dbc9f2391edf08a86 Mon Sep 17 00:00:00 2001 From: Aleksandr Averbukh Date: Wed, 1 Mar 2023 10:42:39 +0100 Subject: [PATCH 10/49] Add more explicit template --- .../terraform.auto.tfvars.template | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/blueprints/cloud-operations/terraform-cloud-dynamic-credentials/gcp-workload-identity-provider/terraform.auto.tfvars.template b/blueprints/cloud-operations/terraform-cloud-dynamic-credentials/gcp-workload-identity-provider/terraform.auto.tfvars.template index 2d2167a3..918d8375 100644 --- a/blueprints/cloud-operations/terraform-cloud-dynamic-credentials/gcp-workload-identity-provider/terraform.auto.tfvars.template +++ b/blueprints/cloud-operations/terraform-cloud-dynamic-credentials/gcp-workload-identity-provider/terraform.auto.tfvars.template @@ -16,3 +16,13 @@ billing_account = "015647-1A8CBC-A002D9" parent = "folders/0123456789" tfc_organization_id = "org-W3bx9naazHrUz99U" tfc_workspace_id = "ws-2GnGJtqTfvxKptHn" + +billing_account = "xxx" +project_create = false +project_id = "xxx" +parent = "organizations/xxx" +tfc_organization_id = "org-xxxxxxxxxxxxx" +tfc_workspace_id = "ws-xxxxxxxxxxxxx" +workload_identity_pool_id = "tfc-pool" +workload_identity_pool_provider_id = "tfc-provider" +issuer_uri = "https://app.terraform.io/" \ No newline at end of file From b7418353be9c1a54d9fb48cd4248f7c5957e6338 Mon Sep 17 00:00:00 2001 From: Aleksandr Averbukh Date: Wed, 1 Mar 2023 10:43:32 +0100 Subject: [PATCH 11/49] Missing newline --- .../terraform.auto.tfvars.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blueprints/cloud-operations/terraform-cloud-dynamic-credentials/gcp-workload-identity-provider/terraform.auto.tfvars.template b/blueprints/cloud-operations/terraform-cloud-dynamic-credentials/gcp-workload-identity-provider/terraform.auto.tfvars.template index 918d8375..327a51d6 100644 --- a/blueprints/cloud-operations/terraform-cloud-dynamic-credentials/gcp-workload-identity-provider/terraform.auto.tfvars.template +++ b/blueprints/cloud-operations/terraform-cloud-dynamic-credentials/gcp-workload-identity-provider/terraform.auto.tfvars.template @@ -25,4 +25,4 @@ tfc_organization_id = "org-xxxxxxxxxxxxx" tfc_workspace_id = "ws-xxxxxxxxxxxxx" workload_identity_pool_id = "tfc-pool" workload_identity_pool_provider_id = "tfc-provider" -issuer_uri = "https://app.terraform.io/" \ No newline at end of file +issuer_uri = "https://app.terraform.io/" From e9119f2c9d849dcb1517e6230318764596a71f4d Mon Sep 17 00:00:00 2001 From: lcaggio Date: Wed, 1 Mar 2023 10:43:33 +0100 Subject: [PATCH 12/49] Update README. --- .../demo/orchestrate_pyspark.py | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 blueprints/data-solutions/data-platform-spark/demo/orchestrate_pyspark.py diff --git a/blueprints/data-solutions/data-platform-spark/demo/orchestrate_pyspark.py b/blueprints/data-solutions/data-platform-spark/demo/orchestrate_pyspark.py new file mode 100644 index 00000000..a8e0f2d3 --- /dev/null +++ b/blueprints/data-solutions/data-platform-spark/demo/orchestrate_pyspark.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python + +# 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 +# +# http://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 datetime +import datetime +import os + +from airflow import models +from airflow.providers.google.cloud.operators.dataproc import ( + DataprocCreateBatchOperator, DataprocDeleteBatchOperator, DataprocGetBatchOperator, DataprocListBatchesOperator + +) +from airflow.utils.dates import days_ago + +# -------------------------------------------------------------------------------- +# Get variables +# -------------------------------------------------------------------------------- +BQ_LOCATION = os.environ.get("BQ_LOCATION") +CURATED_BQ_DATASET = os.environ.get("CURATED_BQ_DATASET") +CURATED_GCS = os.environ.get("CURATED_GCS") +CURATED_PRJ = os.environ.get("CURATED_PRJ") +DP_KMS_KEY = os.environ.get("DP_KMS_KEY", "") +DP_REGION = os.environ.get("DP_REGION") +GCP_REGION = os.environ.get("GCP_REGION") +LAND_PRJ = os.environ.get("LAND_PRJ") +LAND_BQ_DATASET = os.environ.get("LAND_BQ_DATASET") +LAND_GCS = os.environ.get("LAND_GCS") +PHS_NAME = os.environ.get("PHS_NAME") +PROCESSING_GCS = os.environ.get("PROCESSING_GCS") +PROCESSING_PRJ = os.environ.get("PROCESSING_PRJ") +PROCESSING_SA_DP = os.environ.get("PROCESSING_SA_DP") +PROCESSING_SA_SUBNET = os.environ.get("PROCESSING_SUBNET") +PROCESSING_SA_VPC = os.environ.get("PROCESSING_VPC") + +PYTHON_FILE_LOCATION = "gs://"+PROCESSING_GCS+"/pyspark_sort.py" +PHS_CLUSTER_PATH = "projects/"+PROCESSING_PRJ+"/regions/"+DP_REGION+"/clusters/"+PHS_NAME + +default_args = { + # Tell airflow to start one day ago, so that it runs as soon as you upload it + "start_date": days_ago(1), + "region": DP_REGION, +} +with models.DAG( + "dataproc_batch_operators", # The id you will see in the DAG airflow page + default_args=default_args, # The interval with which to schedule the DAG + schedule_interval=None, # Override to match your needs +) as dag: + + create_batch = DataprocCreateBatchOperator( + task_id="batch_create", + project_id=PROCESSING_PRJ, + batch={ + "environment_config": { + "execution_config": { + "service_account": PROCESSING_SA_DP, + "subnetwork_uri": PROCESSING_SA_SUBNET + } + }, + "pyspark_batch": { + "main_python_file_uri": PYTHON_FILE_LOCATION, + }, + "history_server_cluster": PHS_NAME, + }, + batch_id="batch-create-phs", + ) + + list_batches = DataprocListBatchesOperator( + task_id="list-all-batches", + ) + + get_batch = DataprocGetBatchOperator( + task_id="get_batch", + batch_id="batch-create-phs", + ) + + create_batch >> list_batches >> get_batch \ No newline at end of file From 0d37fe83388346f4fefd78494b561f2f1b23faf0 Mon Sep 17 00:00:00 2001 From: lcaggio Date: Wed, 1 Mar 2023 10:44:01 +0100 Subject: [PATCH 13/49] Update README --- modules/dataproc/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/dataproc/README.md b/modules/dataproc/README.md index 0a0c4a4b..9ba44947 100644 --- a/modules/dataproc/README.md +++ b/modules/dataproc/README.md @@ -46,9 +46,9 @@ module "processing-dp-cluster" { # tftest modules=1 resources=1 ``` -### Cluster with CMEK encrypotion +### Cluster with CMEK encryption PIPPO -To set cluster configuration use the Customer Managed Encryption key, set '' variable. The Compute Engine service agent and the Cloud Storage service agent needs to have 'CryptoKey Encrypter/Decrypter' role on they configured KMS key ([Documentation](https://cloud.google.com/dataproc/docs/concepts/configuring-clusters/customer-managed-encryption)). +To set cluster configuration use the Customer Managed Encryption key, set `dataproc_config.encryption_config.` variable. The Compute Engine service agent and the Cloud Storage service agent need to have `CryptoKey Encrypter/Decrypter` role on they configured KMS key ([Documentation](https://cloud.google.com/dataproc/docs/concepts/configuring-clusters/customer-managed-encryption)). ```hcl module "processing-dp-cluster" { @@ -67,10 +67,10 @@ module "processing-dp-cluster" { internal_ip_only = true } } + encryption_config = { + kms_key_name = "projects/project-id/locations/region/keyRings/key-ring-name/cryptoKeys/key-name" + } } - encryption_config = try({ - kms_key_name = "projects/project-id/locations/region/keyRings/key-ring-name/cryptoKeys/key-name" - }, null) } # tftest modules=1 resources=1 ``` From e9a73f873f331e02f947655d51a1ab4a520ccbc7 Mon Sep 17 00:00:00 2001 From: lcaggio Date: Wed, 1 Mar 2023 10:46:33 +0100 Subject: [PATCH 14/49] Remove wrongly submitted file. --- .../demo/orchestrate_pyspark.py | 89 ------------------- 1 file changed, 89 deletions(-) delete mode 100644 blueprints/data-solutions/data-platform-spark/demo/orchestrate_pyspark.py diff --git a/blueprints/data-solutions/data-platform-spark/demo/orchestrate_pyspark.py b/blueprints/data-solutions/data-platform-spark/demo/orchestrate_pyspark.py deleted file mode 100644 index a8e0f2d3..00000000 --- a/blueprints/data-solutions/data-platform-spark/demo/orchestrate_pyspark.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env python - -# 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 -# -# http://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 datetime -import datetime -import os - -from airflow import models -from airflow.providers.google.cloud.operators.dataproc import ( - DataprocCreateBatchOperator, DataprocDeleteBatchOperator, DataprocGetBatchOperator, DataprocListBatchesOperator - -) -from airflow.utils.dates import days_ago - -# -------------------------------------------------------------------------------- -# Get variables -# -------------------------------------------------------------------------------- -BQ_LOCATION = os.environ.get("BQ_LOCATION") -CURATED_BQ_DATASET = os.environ.get("CURATED_BQ_DATASET") -CURATED_GCS = os.environ.get("CURATED_GCS") -CURATED_PRJ = os.environ.get("CURATED_PRJ") -DP_KMS_KEY = os.environ.get("DP_KMS_KEY", "") -DP_REGION = os.environ.get("DP_REGION") -GCP_REGION = os.environ.get("GCP_REGION") -LAND_PRJ = os.environ.get("LAND_PRJ") -LAND_BQ_DATASET = os.environ.get("LAND_BQ_DATASET") -LAND_GCS = os.environ.get("LAND_GCS") -PHS_NAME = os.environ.get("PHS_NAME") -PROCESSING_GCS = os.environ.get("PROCESSING_GCS") -PROCESSING_PRJ = os.environ.get("PROCESSING_PRJ") -PROCESSING_SA_DP = os.environ.get("PROCESSING_SA_DP") -PROCESSING_SA_SUBNET = os.environ.get("PROCESSING_SUBNET") -PROCESSING_SA_VPC = os.environ.get("PROCESSING_VPC") - -PYTHON_FILE_LOCATION = "gs://"+PROCESSING_GCS+"/pyspark_sort.py" -PHS_CLUSTER_PATH = "projects/"+PROCESSING_PRJ+"/regions/"+DP_REGION+"/clusters/"+PHS_NAME - -default_args = { - # Tell airflow to start one day ago, so that it runs as soon as you upload it - "start_date": days_ago(1), - "region": DP_REGION, -} -with models.DAG( - "dataproc_batch_operators", # The id you will see in the DAG airflow page - default_args=default_args, # The interval with which to schedule the DAG - schedule_interval=None, # Override to match your needs -) as dag: - - create_batch = DataprocCreateBatchOperator( - task_id="batch_create", - project_id=PROCESSING_PRJ, - batch={ - "environment_config": { - "execution_config": { - "service_account": PROCESSING_SA_DP, - "subnetwork_uri": PROCESSING_SA_SUBNET - } - }, - "pyspark_batch": { - "main_python_file_uri": PYTHON_FILE_LOCATION, - }, - "history_server_cluster": PHS_NAME, - }, - batch_id="batch-create-phs", - ) - - list_batches = DataprocListBatchesOperator( - task_id="list-all-batches", - ) - - get_batch = DataprocGetBatchOperator( - task_id="get_batch", - batch_id="batch-create-phs", - ) - - create_batch >> list_batches >> get_batch \ No newline at end of file From b39b486cd433678f43d44d8d831922516e251b2b Mon Sep 17 00:00:00 2001 From: lcaggio Date: Wed, 1 Mar 2023 10:48:33 +0100 Subject: [PATCH 15/49] Fix README --- modules/dataproc/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/dataproc/README.md b/modules/dataproc/README.md index 9ba44947..d071ecda 100644 --- a/modules/dataproc/README.md +++ b/modules/dataproc/README.md @@ -46,7 +46,7 @@ module "processing-dp-cluster" { # tftest modules=1 resources=1 ``` -### Cluster with CMEK encryption PIPPO +### Cluster with CMEK encryption To set cluster configuration use the Customer Managed Encryption key, set `dataproc_config.encryption_config.` variable. The Compute Engine service agent and the Cloud Storage service agent need to have `CryptoKey Encrypter/Decrypter` role on they configured KMS key ([Documentation](https://cloud.google.com/dataproc/docs/concepts/configuring-clusters/customer-managed-encryption)). From b4a8a37805476963cab45f8ebadd6434b0482076 Mon Sep 17 00:00:00 2001 From: Aleksandr Averbukh Date: Wed, 1 Mar 2023 11:34:37 +0100 Subject: [PATCH 16/49] Fix tfvars template --- .../terraform.auto.tfvars.template | 5 ----- 1 file changed, 5 deletions(-) diff --git a/blueprints/cloud-operations/terraform-cloud-dynamic-credentials/gcp-workload-identity-provider/terraform.auto.tfvars.template b/blueprints/cloud-operations/terraform-cloud-dynamic-credentials/gcp-workload-identity-provider/terraform.auto.tfvars.template index 327a51d6..57787bf9 100644 --- a/blueprints/cloud-operations/terraform-cloud-dynamic-credentials/gcp-workload-identity-provider/terraform.auto.tfvars.template +++ b/blueprints/cloud-operations/terraform-cloud-dynamic-credentials/gcp-workload-identity-provider/terraform.auto.tfvars.template @@ -12,11 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -billing_account = "015647-1A8CBC-A002D9" -parent = "folders/0123456789" -tfc_organization_id = "org-W3bx9naazHrUz99U" -tfc_workspace_id = "ws-2GnGJtqTfvxKptHn" - billing_account = "xxx" project_create = false project_id = "xxx" From 2ebb21e4ccb3f6c04d81295a26a96fe1208696ab Mon Sep 17 00:00:00 2001 From: erabusi <72961506+erabusi@users.noreply.github.com> Date: Thu, 2 Mar 2023 12:21:39 +0530 Subject: [PATCH 17/49] Fix url_redirect issue on net-glb module (#1204) --- modules/net-glb/urlmap.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/net-glb/urlmap.tf b/modules/net-glb/urlmap.tf index a7f01d55..ec1acaf6 100644 --- a/modules/net-glb/urlmap.tf +++ b/modules/net-glb/urlmap.tf @@ -921,9 +921,9 @@ resource "google_compute_url_map" "default" { } dynamic "url_redirect" { for_each = ( - route_rules.value.default_url_redirect == null + route_rules.value.url_redirect == null ? [] - : [route_rules.value.default_url_redirect] + : [route_rules.value.url_redirect] ) content { host_redirect = url_redirect.value.host From a5fd32edcb3d7488eb201f9f7795e0bff5a0eddd Mon Sep 17 00:00:00 2001 From: Luca Prete Date: Thu, 2 Mar 2023 09:53:07 +0100 Subject: [PATCH 18/49] Blueprint: GLB hybrid NEG internal --- blueprints/README.md | 2 +- blueprints/networking/README.md | 42 ++--- .../glb-hybrid-neg-internal/README.md | 100 ++++++++++++ .../data/nva-startup-script.tftpl | 42 +++++ .../glb-hybrid-neg-internal/diagram.png | Bin 0 -> 63567 bytes .../glb-hybrid-neg-internal/diagram.svg | 1 + .../networking/glb-hybrid-neg-internal/glb.tf | 71 +++++++++ .../glb-hybrid-neg-internal/main.tf | 122 +++++++++++++++ .../networking/glb-hybrid-neg-internal/nva.tf | 87 +++++++++++ .../glb-hybrid-neg-internal/outputs.tf | 20 +++ .../glb-hybrid-neg-internal/spoke.tf | 146 ++++++++++++++++++ .../glb-hybrid-neg-internal/variables.tf | 76 +++++++++ 12 files changed, 690 insertions(+), 19 deletions(-) create mode 100644 blueprints/networking/glb-hybrid-neg-internal/README.md create mode 100644 blueprints/networking/glb-hybrid-neg-internal/data/nva-startup-script.tftpl create mode 100644 blueprints/networking/glb-hybrid-neg-internal/diagram.png create mode 100644 blueprints/networking/glb-hybrid-neg-internal/diagram.svg create mode 100644 blueprints/networking/glb-hybrid-neg-internal/glb.tf create mode 100644 blueprints/networking/glb-hybrid-neg-internal/main.tf create mode 100644 blueprints/networking/glb-hybrid-neg-internal/nva.tf create mode 100644 blueprints/networking/glb-hybrid-neg-internal/outputs.tf create mode 100644 blueprints/networking/glb-hybrid-neg-internal/spoke.tf create mode 100644 blueprints/networking/glb-hybrid-neg-internal/variables.tf diff --git a/blueprints/README.md b/blueprints/README.md index b108f861..e7136d9c 100644 --- a/blueprints/README.md +++ b/blueprints/README.md @@ -9,7 +9,7 @@ Currently available blueprints: - **data solutions** - [GCE and GCS CMEK via centralized Cloud KMS](./data-solutions/cmek-via-centralized-kms), [Cloud Composer version 2 private instance, supporting Shared VPC and external CMEK key](./data-solutions/composer-2), [Cloud SQL instance with multi-region read replicas](./data-solutions/cloudsql-multiregion), [Data Platform](./data-solutions/data-platform-foundations), [Spinning up a foundation data pipeline on Google Cloud using Cloud Storage, Dataflow and BigQuery](./data-solutions/gcs-to-bq-with-least-privileges), [#SQL Server Always On Groups blueprint](./data-solutions/sqlserver-alwayson), [Data Playground](./data-solutions/data-playground), [MLOps with Vertex AI](./data-solutions/vertex-mlops), [Shielded Folder](./data-solutions/shielded-folder) - **factories** - [The why and the how of Resource Factories](./factories), [Google Cloud Identity Group Factory](./factories/cloud-identity-group-factory), [Google Cloud BQ Factory](./factories/bigquery-factory), [Google Cloud VPC Firewall Factory](./factories/net-vpc-firewall-yaml), [Minimal Project Factory](./factories/project-factory) - **GKE** - [Binary Authorization Pipeline Blueprint](./gke/binauthz), [Storage API](./gke/binauthz/image), [Multi-cluster mesh on GKE (fleet API)](./gke/multi-cluster-mesh-gke-fleet-api), [GKE Multitenant Blueprint](./gke/multitenant-fleet), [Shared VPC with GKE support](./networking/shared-vpc-gke/) -- **networking** - [Decentralized firewall management](./networking/decentralized-firewall), [Decentralized firewall validator](./networking/decentralized-firewall/validator), [Network filtering with Squid](./networking/filtering-proxy), [Network filtering with Squid with isolated VPCs using Private Service Connect](./networking/filtering-proxy-psc), [HTTP Load Balancer with Cloud Armor](./networking/glb-and-armor), [Hub and Spoke via VPN](./networking/hub-and-spoke-vpn), [Hub and Spoke via VPC Peering](./networking/hub-and-spoke-peering), [Internal Load Balancer as Next Hop](./networking/ilb-next-hop), On-prem DNS and Google Private Access, [Calling a private Cloud Function from On-premises](./networking/private-cloud-function-from-onprem), [Hybrid connectivity to on-premise services through PSC](./networking/psc-hybrid), [PSC Producer](./networking/psc-hybrid/psc-producer), [PSC Consumer](./networking/psc-hybrid/psc-consumer), [Shared VPC with optional GKE cluster](./networking/shared-vpc-gke) +- **networking** - [Calling a private Cloud Function from On-premises](./networking/private-cloud-function-from-onprem), [Decentralized firewall management](./networking/decentralized-firewall), [Decentralized firewall validator](./networking/decentralized-firewall/validator), [Network filtering with Squid](./networking/filtering-proxy), [GLB and multi-regional daisy-chaining through hybrid NEGs](./networking/glb-hybrid-neg-internal), [Hybrid connectivity to on-premise services through PSC](./networking/psc-hybrid), [HTTP Load Balancer with Cloud Armor](./networking/glb-and-armor), [Hub and Spoke via VPN](./networking/hub-and-spoke-vpn), [Hub and Spoke via VPC Peering](./networking/hub-and-spoke-peering), [Internal Load Balancer as Next Hop](./networking/ilb-next-hop), [Network filtering with Squid with isolated VPCs using Private Service Connect](./networking/filtering-proxy-psc), On-prem DNS and Google Private Access, [PSC Producer](./networking/psc-hybrid/psc-producer), [PSC Consumer](./networking/psc-hybrid/psc-consumer), [Shared VPC with optional GKE cluster](./networking/shared-vpc-gke) - **serverless** - [Creating multi-region deployments for API Gateway](./serverless/api-gateway), [Cloud Run series](./serverless/cloud-run-explore) - **third party solutions** - [OpenShift on GCP user-provisioned infrastructure](./third-party-solutions/openshift), [Wordpress deployment on Cloud Run](./third-party-solutions/wordpress/cloudrun) diff --git a/blueprints/networking/README.md b/blueprints/networking/README.md index e7c0b1ae..05f49335 100644 --- a/blueprints/networking/README.md +++ b/blueprints/networking/README.md @@ -6,15 +6,27 @@ They are meant to be used as minimal but complete starting points to create actu ## Blueprints +### Calling a private Cloud Function from on-premises + + This [blueprint](./private-cloud-function-from-onprem/) shows how to invoke a [private Google Cloud Function](https://cloud.google.com/functions/docs/networking/network-settings) from the on-prem environment via a [Private Service Connect endpoint](https://cloud.google.com/vpc/docs/private-service-connect#benefits-apis). + +
+ +### Calling on-premise services through PSC and hybrid NEGs + + This [blueprint](./psc-hybrid/) shows how to privately connect to on-premise services (IP + port) from GCP, leveraging [Private Service Connect (PSC)](https://cloud.google.com/vpc/docs/private-service-connect) and [Hybrid Network Endpoint Groups](https://cloud.google.com/load-balancing/docs/negs/hybrid-neg-concepts). + +
+ ### Decentralized firewall management This [blueprint](./decentralized-firewall/) shows how a decentralized firewall management can be organized using the [firewall factory](../factories/net-vpc-firewall-yaml/).
-### Network filtering with Squid +### GLB and multi-regional daisy-chaining through hybrid NEGs - This [blueprint](./filtering-proxy/) how to deploy a filtering HTTP proxy to restrict Internet access, in a simplified setup using a VPC with two subnets and a Cloud DNS zone, and an optional MIG for scaling. + This [blueprint](./glb-hybrid-neg-internal/) shows the experimental use of hybrid NEGs behind external Global Load Balancers (GLBs) to connect to GCP instances living in spoke VPCs and behind Network Virtual Appliances (NVAs).
@@ -24,14 +36,6 @@ They are meant to be used as minimal but complete starting points to create actu
-### Hub and Spoke via Peering - - This [blueprint](./hub-and-spoke-peering/) implements a hub and spoke topology via VPC peering, a common design where a landing zone VPC (hub) is connected to on-premises, and then peered with satellite VPCs (spokes) to further partition the infrastructure. - -The sample highlights the lack of transitivity in peering: the absence of connectivity between spokes, and the need create workarounds for private service access to managed services. One such workaround is shown for private GKE, allowing access from hub and all spokes to GKE masters via a dedicated VPN. - -
- ### Hub and Spoke via Dynamic VPN This [blueprint](./hub-and-spoke-vpn/) implements a hub and spoke topology via dynamic VPN tunnels, a common design where peering cannot be used due to limitations on the number of spokes or connectivity to managed services. @@ -40,6 +44,14 @@ The blueprint shows how to implement spoke transitivity via BGP advertisements,
+### Hub and Spoke via Peering + + This [blueprint](./hub-and-spoke-peering/) implements a hub and spoke topology via VPC peering, a common design where a landing zone VPC (hub) is connected to on-premises, and then peered with satellite VPCs (spokes) to further partition the infrastructure. + +The sample highlights the lack of transitivity in peering: the absence of connectivity between spokes, and the need create workarounds for private service access to managed services. One such workaround is shown for private GKE, allowing access from hub and all spokes to GKE masters via a dedicated VPN. + +
+ ### ILB as next hop This [blueprint](./ilb-next-hop/) allows testing [ILB as next hop](https://cloud.google.com/load-balancing/docs/internal/ilb-next-hop-overview) using simple Linux gateway VMS between two VPCs, to emulate virtual appliances. An optional additional ILB can be enabled to test multiple load balancer configurations and hashing. @@ -63,15 +75,9 @@ The emulated on-premises environment can be used to test access to different ser --> -### Calling a private Cloud Function from on-premises +### Network filtering with Squid - This [blueprint](./private-cloud-function-from-onprem/) shows how to invoke a [private Google Cloud Function](https://cloud.google.com/functions/docs/networking/network-settings) from the on-prem environment via a [Private Service Connect endpoint](https://cloud.google.com/vpc/docs/private-service-connect#benefits-apis). - -
- -### Calling on-premise services through PSC and hybrid NEGs - - This [blueprint](./psc-hybrid/) shows how to privately connect to on-premise services (IP + port) from GCP, leveraging [Private Service Connect (PSC)](https://cloud.google.com/vpc/docs/private-service-connect) and [Hybrid Network Endpoint Groups](https://cloud.google.com/load-balancing/docs/negs/hybrid-neg-concepts). + This [blueprint](./filtering-proxy/) how to deploy a filtering HTTP proxy to restrict Internet access, in a simplified setup using a VPC with two subnets and a Cloud DNS zone, and an optional MIG for scaling.
diff --git a/blueprints/networking/glb-hybrid-neg-internal/README.md b/blueprints/networking/glb-hybrid-neg-internal/README.md new file mode 100644 index 00000000..b6bd3d78 --- /dev/null +++ b/blueprints/networking/glb-hybrid-neg-internal/README.md @@ -0,0 +1,100 @@ +# GLB and multi-regional daisy-chaining through hybrid NEGs + +The blueprint shows the experimental use of hybrid NEGs behind eXternal Global Load Balancers (GLBs) to connect to GCP instances living in spoke VPCs and behind Network Virtual Appliances (NVAs). + +

+ +This allows users to not configure per-destination-VM NAT rules in the NVAs. + +The user traffic will enter the GLB, it will go across the NVAs and it will be routed to the destination VMs (or the ILBs behind the VMs) in the spokes. + +## What the blueprint creates + +This is what the blueprint brings up, using the default module values. +The ids `primary` and `secondary` are used to identify two regions. By default, `europe-west1` and `europe-west4`. + +- Projects: landing, spoke-01 + +- VPCs and subnets + + landing-untrusted: primary - 192.168.1.0/24 and secondary - 192.168.2.0/24 + + landing-trusted: primary - 192.168.11.0/24 and secondary - 192.168.22.0/24 + + spoke-01: primary - 192.168.101.0/24 and secondary - 192.168.102.0/24 + +- Cloud NAT + + landing-untrusted (both for primary and secondary) + + in spoke-01 (both for primary and secondary) - this is just for test purposes, so you VMs can automatically install nginx, even if NVAs are still not ready + +- VMs + + NVAs in MIGs in the landing project, both in primary and secondary, with NICs in the untrusted and in the trusted VPCs + + Test VMs, in spoke-01, both in primary and secondary. Optionally, deployed in MIGs + +- Hybrid NEGs in the untrusted VPC, both in primary and secondary, either pointing to the test VMs in the spoke or -optionally- to ILBs in the spokes (if test VMs are deployed as MIGs) + +- Internal Load balancers (L4 ILBs) + + in the untrusted VPC, pointing to NVA MIGs, both in primary and secondary. Their VIPs are used by custom routes in the untrusted VPC, so that all traffic that arrives in the untrusted VPC destined for the test VMs in the spoke is sent through the NVAs + + optionally, in the spokes. They are created if the user decides to deploy the test VMs as MIGs + +- External Global Load balancer (GLB) in the untrusted VPC, using the hybrid NEGs as its backends + +## Health Checks + +Google Cloud sends [health checks](https://cloud.google.com/load-balancing/docs/health-checks) using [specific IP ranges](https://cloud.google.com/load-balancing/docs/health-checks#fw-netlb). Each VPC uses [implicit routes](https://cloud.google.com/vpc/docs/routes#special_return_paths) to send the health check replies back to Google. + +At the moment of writing, when Google Cloud sends out [health checks](https://cloud.google.com/load-balancing/docs/health-checks) against backend services, it expects replies to come back from the same VPC where they have been sent out to. + +Given the GLB lives in the untrusted VPC, its backend service health checks are sent out to that VPC, and so the replies are expected from it. Anyway, the destinations of the health checks are the test VMs in the spokes. + +The blueprint configures some custom routes in the untrusted VPC and routing/NAT rules in the NVAs, so that health checks reach the test VMs through the NVAs, and replies come back through the NVAs in the untrusted VPC. Without these configurations health checks will fail and backend services won't be reachable. + +Specifically: + +- we create two custom routes in the untrusted VPC (one per region) so that traffic for the spoke subnets is sent to the VIP of the L4 ILBs in front of the NVAs + +- we configure the NVAs so they know how to route traffic to the spokes via the trusted VPC gateway + +- we configure the NVAs to s-NAT (specifically, masquerade) health checks traffic destined to the test VMs + +## Change the ilb_create variable + +Through the `ilb_create` variable you can decide whether test VMs in the spoke will be deployed as MIGs with ILBs in front. This will also configure NEGs, so they point to the ILB VIPs, instead of the VM IPs. + +At the moment, every time a user changes the configuration of a NEG, the NEG is recreated. When this happens, the provider doesn't check if it is used by other resources, such as GLB backend services. Until this doesn't get fixed, every time you'll need to change the NEG configuration (i.e. when changing the variable `ilb_create`) you'll have to workaround it. Here is how: + +- Destroy the existing backend service: `terraform destroy -target 'module.hybrid-glb.google_compute_backend_service.default["default"]'` + +- Change the variable `ilb_create` + +- run `terraform apply` + + +## Variables + +| name | description | type | required | default | +|---|---|:---:|:---:|:---:| +| [prefix](variables.tf#L36) | Prefix used for resource names. | string | ✓ | | +| [ilb_create](variables.tf#L17) | Whether we should create an ILB L4 in front of the test VMs in the spoke. | string | | "false" | +| [ip_config](variables.tf#L23) | The subnet IP configurations. | object({…}) | | {} | +| [project_names](variables.tf#L45) | The project names. | object({…}) | | {…} | +| [projects_create](variables.tf#L57) | Parameters for the creation of the new project. | object({…}) | | null | +| [regions](variables.tf#L66) | Region definitions. | object({…}) | | {…} | + +## Outputs + +| name | description | sensitive | +|---|---|:---:| +| [glb_ip_address](outputs.tf#L17) | Load balancer IP address. | | + + +## Test +```hcl +module "test" { + source = "./fabric/blueprints/networking/glb-hybrid-neg-internal" + prefix = "prefix" + projects_create = { + billing_account_id = "123456-123456-123456" + parent = "folders/123456789" + } +} + +# tftest modules=21 resources=64 +``` diff --git a/blueprints/networking/glb-hybrid-neg-internal/data/nva-startup-script.tftpl b/blueprints/networking/glb-hybrid-neg-internal/data/nva-startup-script.tftpl new file mode 100644 index 00000000..62b9988a --- /dev/null +++ b/blueprints/networking/glb-hybrid-neg-internal/data/nva-startup-script.tftpl @@ -0,0 +1,42 @@ +#!/bin/bash + +# 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 +# +# http://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. + +echo 'Enabling IP forwarding' +sed '/net.ipv4.ip_forward=1/s/^#//g' -i /etc/sysctl.conf && +sysctl -p /etc/sysctl.conf && +/etc/init.d/procps restart + +echo 'Setting routes' +ip route add ${spoke-primary} via ${gateway-trusted} dev ens5 +ip route add ${spoke-secondary} via ${gateway-trusted} dev ens5 + +echo 'Setting NAT masquerade, so that HCs can reach the spoke through the NVA using the trusted intf source IP' +iptables \ + -t nat \ + -A POSTROUTING \ + -s 130.211.0.0/22,35.191.0.0/16 \ + -d ${spoke-primary} \ + -p tcp \ + --dport 80 \ + -j MASQUERADE +iptables \ + -t nat \ + -A POSTROUTING \ + -s 130.211.0.0/22,35.191.0.0/16 \ + -d ${spoke-secondary} \ + -p tcp \ + --dport 80 \ + -j MASQUERADE diff --git a/blueprints/networking/glb-hybrid-neg-internal/diagram.png b/blueprints/networking/glb-hybrid-neg-internal/diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..bf82cd80a407c1b4af6b46789467ff206f9ed5d3 GIT binary patch literal 63567 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 literal 0 HcmV?d00001 diff --git a/blueprints/networking/glb-hybrid-neg-internal/diagram.svg b/blueprints/networking/glb-hybrid-neg-internal/diagram.svg new file mode 100644 index 00000000..3c90fc42 --- /dev/null +++ b/blueprints/networking/glb-hybrid-neg-internal/diagram.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/blueprints/networking/glb-hybrid-neg-internal/glb.tf b/blueprints/networking/glb-hybrid-neg-internal/glb.tf new file mode 100644 index 00000000..4d67d68a --- /dev/null +++ b/blueprints/networking/glb-hybrid-neg-internal/glb.tf @@ -0,0 +1,71 @@ +/** + * 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 + * + * http://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. + */ + +# tfdoc:file:description External Global Load Balancer. + +module "hybrid-glb" { + source = "../../../modules/net-glb" + project_id = module.project_landing.project_id + name = "hybrid-glb" + backend_service_configs = { + default = { + backends = [ + { + backend = "neg-primary" + balancing_mode = "RATE" + max_rate = { per_endpoint = 100 } + }, + { + backend = "neg-secondary" + balancing_mode = "RATE" + max_rate = { per_endpoint = 100 } + } + ] + } + } + neg_configs = { + neg-primary = { + hybrid = { + network = module.vpc_landing_untrusted.name + zone = local.zones["primary"] + endpoints = { + primary = { + ip_address = (var.ilb_create + ? module.test_vm_ilbs["primary"].forwarding_rule_address + : module.test_vms["primary"].internal_ip + ) + port = 80 + } + } + } + } + neg-secondary = { + hybrid = { + network = module.vpc_landing_untrusted.name + zone = local.zones["secondary"] + endpoints = { + secondary = { + ip_address = (var.ilb_create + ? module.test_vm_ilbs["secondary"].forwarding_rule_address + : module.test_vms["secondary"].internal_ip + ) + port = 80 + } + } + } + } + } +} diff --git a/blueprints/networking/glb-hybrid-neg-internal/main.tf b/blueprints/networking/glb-hybrid-neg-internal/main.tf new file mode 100644 index 00000000..55600156 --- /dev/null +++ b/blueprints/networking/glb-hybrid-neg-internal/main.tf @@ -0,0 +1,122 @@ +/** + * 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 + * + * http://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. + */ + +locals { + zones = { + primary = "${var.regions.primary}-b" + secondary = "${var.regions.secondary}-b" + } +} + +module "project_landing" { + source = "../../../modules/project" + billing_account = (var.projects_create != null + ? var.projects_create.billing_account_id + : null + ) + name = var.project_names.landing + parent = (var.projects_create != null + ? var.projects_create.parent + : null + ) + prefix = var.prefix + project_create = var.projects_create != null + + services = [ + "compute.googleapis.com", + "networkmanagement.googleapis.com", + # Logging and Monitoring + "logging.googleapis.com", + "monitoring.googleapis.com" + ] +} + +module "vpc_landing_untrusted" { + source = "../../../modules/net-vpc" + project_id = module.project_landing.project_id + name = "landing-untrusted" + + routes = { + spoke1-primary = { + dest_range = var.ip_config.spoke_primary + next_hop_type = "ilb" + next_hop = module.nva_untrusted_ilbs["primary"].forwarding_rule_self_link + } + spoke1-secondary = { + dest_range = var.ip_config.spoke_secondary + next_hop_type = "ilb" + next_hop = module.nva_untrusted_ilbs["secondary"].forwarding_rule_self_link + } + } + + subnets = [ + { + ip_cidr_range = var.ip_config.untrusted_primary + name = "untrusted-${var.regions.primary}" + region = var.regions.primary + }, + { + ip_cidr_range = var.ip_config.untrusted_secondary + name = "untrusted-${var.regions.secondary}" + region = var.regions.secondary + } + ] +} + +module "vpc_landing_trusted" { + source = "../../../modules/net-vpc" + project_id = module.project_landing.project_id + name = "landing-trusted" + subnets = [ + { + ip_cidr_range = var.ip_config.trusted_primary + name = "trusted-${var.regions.primary}" + region = var.regions.primary + }, + { + ip_cidr_range = var.ip_config.trusted_secondary + name = "trusted-${var.regions.secondary}" + region = var.regions.secondary + } + ] +} + +module "firewall_landing_untrusted" { + source = "../../../modules/net-vpc-firewall" + project_id = module.project_landing.project_id + network = module.vpc_landing_untrusted.name + + ingress_rules = { + allow-ssh-from-hcs = { + description = "Allow health checks to NVAs coming on port 22." + targets = ["ssh"] + source_ranges = [ + "130.211.0.0/22", + "35.191.0.0/16" + ] + rules = [{ protocol = "tcp", ports = [22] }] + } + } +} + +module "nats_landing" { + for_each = var.regions + source = "../../../modules/net-cloudnat" + project_id = module.project_landing.project_id + region = each.value + name = "nat-${each.value}" + router_network = module.vpc_landing_untrusted.self_link +} diff --git a/blueprints/networking/glb-hybrid-neg-internal/nva.tf b/blueprints/networking/glb-hybrid-neg-internal/nva.tf new file mode 100644 index 00000000..1d2a508f --- /dev/null +++ b/blueprints/networking/glb-hybrid-neg-internal/nva.tf @@ -0,0 +1,87 @@ +/** + * 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 + * + * http://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. + */ + +# tfdoc:file:description Network Virtual Appliances (NVAs). + +module "nva_instance_templates" { + for_each = var.regions + source = "../../../modules/compute-vm" + project_id = module.project_landing.project_id + can_ip_forward = true + create_template = true + name = "nva-${each.value}" + service_account_create = true + zone = local.zones[each.key] + + metadata = { + startup-script = templatefile( + "${path.module}/data/nva-startup-script.tftpl", + { + gateway-trusted = cidrhost(module.vpc_landing_trusted.subnet_ips["${each.value}/trusted-${each.value}"], 1) + spoke-primary = var.ip_config.spoke_primary + spoke-secondary = var.ip_config.spoke_secondary + } + ) + } + + network_interfaces = [ + { + network = module.vpc_landing_untrusted.self_link + subnetwork = module.vpc_landing_untrusted.subnet_self_links["${each.value}/untrusted-${each.value}"] + }, + { + network = module.vpc_landing_trusted.self_link + subnetwork = module.vpc_landing_trusted.subnet_self_links["${each.value}/trusted-${each.value}"] + } + ] + + tags = [ + "http-server", + "https-server", + "ssh" + ] +} + +module "nva_migs" { + for_each = var.regions + source = "../../../modules/compute-mig" + project_id = module.project_landing.project_id + location = local.zones[each.key] + name = "nva-${each.value}" + target_size = 1 + instance_template = module.nva_instance_templates[each.key].template.self_link +} + +module "nva_untrusted_ilbs" { + for_each = var.regions + source = "../../../modules/net-ilb" + project_id = module.project_landing.project_id + region = each.value + name = "nva-ilb-${local.zones[each.key]}" + service_label = "nva-ilb-${local.zones[each.key]}" + vpc_config = { + network = module.vpc_landing_untrusted.self_link + subnetwork = module.vpc_landing_untrusted.subnet_self_links["${each.value}/untrusted-${each.value}"] + } + backends = [{ + group = module.nva_migs[each.key].group_manager.instance_group + }] + health_check_config = { + tcp = { + port = 22 + } + } +} diff --git a/blueprints/networking/glb-hybrid-neg-internal/outputs.tf b/blueprints/networking/glb-hybrid-neg-internal/outputs.tf new file mode 100644 index 00000000..7d8ce185 --- /dev/null +++ b/blueprints/networking/glb-hybrid-neg-internal/outputs.tf @@ -0,0 +1,20 @@ +/** + * 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 + * + * http://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. + */ + +output "glb_ip_address" { + description = "Load balancer IP address." + value = module.hybrid-glb.address +} diff --git a/blueprints/networking/glb-hybrid-neg-internal/spoke.tf b/blueprints/networking/glb-hybrid-neg-internal/spoke.tf new file mode 100644 index 00000000..1769d4c6 --- /dev/null +++ b/blueprints/networking/glb-hybrid-neg-internal/spoke.tf @@ -0,0 +1,146 @@ +/** + * 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 + * + * http://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. + */ + +# tfdoc:file:description VPC Spoke(s) and test VMs. + +module "project_spoke_01" { + source = "../../../modules/project" + billing_account = (var.projects_create != null + ? var.projects_create.billing_account_id + : null + ) + name = var.project_names.spoke_01 + parent = (var.projects_create != null + ? var.projects_create.parent + : null + ) + prefix = var.prefix + + services = [ + "compute.googleapis.com", + "networkmanagement.googleapis.com", + # Logging and Monitoring + "logging.googleapis.com", + "monitoring.googleapis.com" + ] +} + +module "vpc_spoke_01" { + source = "../../../modules/net-vpc" + project_id = module.project_spoke_01.project_id + name = "spoke-01" + subnets = [ + { + ip_cidr_range = var.ip_config.spoke_primary + name = "spoke-01-${var.regions.primary}" + region = var.regions.primary + }, + { + ip_cidr_range = var.ip_config.spoke_secondary + name = "spoke-01-${var.regions.secondary}" + region = var.regions.secondary + } + ] + peering_config = { + peer_vpc_self_link = module.vpc_landing_trusted.self_link + import_routes = true + } +} + +module "firewall_spoke_01" { + source = "../../../modules/net-vpc-firewall" + project_id = module.project_spoke_01.project_id + network = module.vpc_spoke_01.name + + ingress_rules = { + allow-nva-hcs = { + description = "Allow health checks coming on port 80 and 443 from NVAs." + targets = ["http-server", "https-server"] + source_ranges = [ + var.ip_config.trusted_primary, + var.ip_config.trusted_secondary + ] + rules = [{ protocol = "tcp", ports = [80, 443] }] + } + } +} + +# NAT is used to install nginx for test purposed, even if NVAs are still not ready + +module "nats_spoke_01" { + for_each = var.regions + source = "../../../modules/net-cloudnat" + name = "spoke-01-${each.value}" + project_id = module.project_spoke_01.project_id + region = each.value + router_network = module.vpc_spoke_01.name +} + +module "test_vms" { + for_each = var.regions + source = "../../../modules/compute-vm" + name = "spoke-01-${each.value}" + project_id = module.project_spoke_01.project_id + create_template = var.ilb_create + service_account_create = true + zone = local.zones[each.key] + + metadata = { + startup-script = "apt update && apt install -y nginx" + } + + network_interfaces = [{ + network = module.vpc_spoke_01.self_link + subnetwork = module.vpc_spoke_01.subnet_self_links["${each.value}/spoke-01-${each.value}"] + }] + + tags = [ + "http-server", + "https-server", + "ssh" + ] +} + +module "test_vm_migs" { + for_each = var.ilb_create ? var.regions : {} + source = "../../../modules/compute-mig" + project_id = module.project_spoke_01.project_id + location = local.zones[each.key] + name = "test-vm-${each.value}" + target_size = 1 + instance_template = module.test_vms[each.key].template.self_link +} + +module "test_vm_ilbs" { + for_each = var.ilb_create ? var.regions : {} + source = "../../../modules/net-ilb" + project_id = module.project_spoke_01.project_id + region = each.value + name = "test-vm-ilb-${each.value}" + service_label = "test-vm-ilb-${each.value}" + vpc_config = { + network = module.vpc_spoke_01.self_link + subnetwork = module.vpc_spoke_01.subnet_self_links["${each.value}/spoke-01-${each.value}"] + } + backends = [{ + group = module.test_vm_migs[each.key].group_manager.instance_group + }] + health_check_config = { + tcp = { + port = 80 + } + } +} diff --git a/blueprints/networking/glb-hybrid-neg-internal/variables.tf b/blueprints/networking/glb-hybrid-neg-internal/variables.tf new file mode 100644 index 00000000..3f96b867 --- /dev/null +++ b/blueprints/networking/glb-hybrid-neg-internal/variables.tf @@ -0,0 +1,76 @@ +/** + * 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 + * + * http://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. + */ + +variable "ilb_create" { + description = "Whether we should create an ILB L4 in front of the test VMs in the spoke." + type = string + default = false +} + +variable "ip_config" { + description = "The subnet IP configurations." + type = object({ + spoke_primary = optional(string, "192.168.101.0/24") + spoke_secondary = optional(string, "192.168.102.0/24") + trusted_primary = optional(string, "192.168.11.0/24") + trusted_secondary = optional(string, "192.168.22.0/24") + untrusted_primary = optional(string, "192.168.1.0/24") + untrusted_secondary = optional(string, "192.168.2.0/24") + }) + default = {} +} + +variable "prefix" { + description = "Prefix used for resource names." + type = string + validation { + condition = var.prefix != "" + error_message = "Prefix cannot be empty." + } +} + +variable "project_names" { + description = "The project names." + type = object({ + landing = string + spoke_01 = string + }) + default = { + landing = "landing" + spoke_01 = "spoke-01" + } +} + +variable "projects_create" { + description = "Parameters for the creation of the new project." + type = object({ + billing_account_id = string + parent = string + }) + default = null +} + +variable "regions" { + description = "Region definitions." + type = object({ + primary = string + secondary = string + }) + default = { + primary = "europe-west1" + secondary = "europe-west4" + } +} From b7793f69a2a1a9b48f12321b44fa4e660f6cf2db Mon Sep 17 00:00:00 2001 From: lcaggio Date: Thu, 2 Mar 2023 10:39:08 +0100 Subject: [PATCH 19/49] Dataproc module. Fix output. --- modules/dataproc/outputs.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/dataproc/outputs.tf b/modules/dataproc/outputs.tf index 755b6dd5..4cd952a3 100644 --- a/modules/dataproc/outputs.tf +++ b/modules/dataproc/outputs.tf @@ -37,6 +37,6 @@ output "instance_names" { output "name" { description = "The name of the cluster." - value = google_dataproc_cluster.cluster.cluster_config.0.bucket + value = google_dataproc_cluster.cluster.name } From 99d19d5ec89221d678555f95a46d8ca1e07860a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Taneli=20Lepp=C3=A4?= Date: Thu, 2 Mar 2023 10:21:52 +0100 Subject: [PATCH 20/49] Fix issue with GKE cluster notifications topic, change pubsub module output to static. --- modules/gke-cluster/main.tf | 6 ++++-- modules/pubsub/README.md | 10 +++++----- modules/pubsub/main.tf | 1 + modules/pubsub/outputs.tf | 3 ++- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/modules/gke-cluster/main.tf b/modules/gke-cluster/main.tf index 107d8341..a42c4fb3 100644 --- a/modules/gke-cluster/main.tf +++ b/modules/gke-cluster/main.tf @@ -406,9 +406,11 @@ resource "google_compute_network_peering_routes_config" "gke_master" { resource "google_pubsub_topic" "notifications" { count = ( - try(var.enable_features.upgrade_notifications.topic_id, null) == null ? 0 : 1 + try(var.enable_features.upgrade_notifications, null) != null && + try(var.enable_features.upgrade_notifications.topic_id, null) == null ? 1 : 0 ) - name = "gke-pubsub-notifications" + project = var.project_id + name = "gke-pubsub-notifications" labels = { content = "gke-notifications" } diff --git a/modules/pubsub/README.md b/modules/pubsub/README.md index 81e43365..09fa1b3a 100644 --- a/modules/pubsub/README.md +++ b/modules/pubsub/README.md @@ -169,10 +169,10 @@ module "pubsub" { | name | description | sensitive | |---|---|:---:| | [id](outputs.tf#L17) | Topic id. | | -| [schema](outputs.tf#L25) | Schema resource. | | -| [schema_id](outputs.tf#L30) | Schema resource id. | | -| [subscription_id](outputs.tf#L35) | Subscription ids. | | -| [subscriptions](outputs.tf#L45) | Subscription resources. | | -| [topic](outputs.tf#L53) | Topic resource. | | +| [schema](outputs.tf#L26) | Schema resource. | | +| [schema_id](outputs.tf#L31) | Schema resource id. | | +| [subscription_id](outputs.tf#L36) | Subscription ids. | | +| [subscriptions](outputs.tf#L46) | Subscription resources. | | +| [topic](outputs.tf#L54) | Topic resource. | | diff --git a/modules/pubsub/main.tf b/modules/pubsub/main.tf index 7e5630a9..ccb6f5d7 100644 --- a/modules/pubsub/main.tf +++ b/modules/pubsub/main.tf @@ -33,6 +33,7 @@ locals { options = try(v.options, v, null) == null ? var.defaults : v.options } } + topic_id_static = "projects/${var.project_id}/topics/${var.name}" } resource "google_pubsub_schema" "default" { diff --git a/modules/pubsub/outputs.tf b/modules/pubsub/outputs.tf index 4aea42c5..3e99889b 100644 --- a/modules/pubsub/outputs.tf +++ b/modules/pubsub/outputs.tf @@ -16,8 +16,9 @@ output "id" { description = "Topic id." - value = google_pubsub_topic.default.id + value = local.topic_id_static depends_on = [ + google_pubsub_topic.default, google_pubsub_topic_iam_binding.default ] } From 06dd38170d5794a45dcac3a3034804c803343255 Mon Sep 17 00:00:00 2001 From: Aleksandr Averbukh Date: Fri, 3 Mar 2023 07:15:08 +0100 Subject: [PATCH 21/49] Fix outdated go deps, dependabot alerts (#1208) --- .../function/restarter/go.mod | 12 +- .../function/restarter/go.sum | 828 ++++++++++++++++++ tools/tfeditor/go.mod | 8 +- tools/tfeditor/go.sum | 44 + 4 files changed, 883 insertions(+), 9 deletions(-) diff --git a/blueprints/cloud-operations/unmanaged-instances-healthcheck/function/restarter/go.mod b/blueprints/cloud-operations/unmanaged-instances-healthcheck/function/restarter/go.mod index a6b7b277..2a6604e4 100644 --- a/blueprints/cloud-operations/unmanaged-instances-healthcheck/function/restarter/go.mod +++ b/blueprints/cloud-operations/unmanaged-instances-healthcheck/function/restarter/go.mod @@ -3,12 +3,10 @@ module example.com/restarter go 1.16 require ( - cloud.google.com/go/iam v0.3.0 // indirect - cloud.google.com/go/pubsub v1.19.0 + cloud.google.com/go/pubsub v1.28.0 github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a // indirect - golang.org/x/sys v0.1.0 // indirect - google.golang.org/api v0.71.0 - google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6 // indirect - google.golang.org/grpc v1.45.0 // indirect + golang.org/x/net v0.7.0 + golang.org/x/text v0.7.0 + google.golang.org/api v0.111.0 + google.golang.org/genproto v0.0.0-20230301171018-9ab4bdc49ad5 // indirect ) diff --git a/blueprints/cloud-operations/unmanaged-instances-healthcheck/function/restarter/go.sum b/blueprints/cloud-operations/unmanaged-instances-healthcheck/function/restarter/go.sum index f6300054..bf15feb9 100644 --- a/blueprints/cloud-operations/unmanaged-instances-healthcheck/function/restarter/go.sum +++ b/blueprints/cloud-operations/unmanaged-instances-healthcheck/function/restarter/go.sum @@ -3,6 +3,7 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= @@ -15,6 +16,7 @@ cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOY cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= @@ -29,42 +31,519 @@ cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2Z cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U= cloud.google.com/go v0.100.2 h1:t9Iw5QH5v4XtlEQaCtUY7x6sCABps8sW0acw7e2WQ6Y= cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= +cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= +cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= +cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= +cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM= +cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= +cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys= +cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= +cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4= +cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw= +cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E= +cloud.google.com/go/accesscontextmanager v1.3.0/go.mod h1:TgCBehyr5gNMz7ZaH9xubp+CE8dkrszb4oK9CWyvD4o= +cloud.google.com/go/accesscontextmanager v1.4.0/go.mod h1:/Kjh7BBu/Gh83sv+K60vN9QE5NJcd80sU33vIe2IFPE= +cloud.google.com/go/accesscontextmanager v1.6.0/go.mod h1:8XCvZWfYw3K/ji0iVnp+6pu7huxoQTLmxAbVjbloTtM= +cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= +cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= +cloud.google.com/go/aiplatform v1.27.0/go.mod h1:Bvxqtl40l0WImSb04d0hXFU7gDOiq9jQmorivIiWcKg= +cloud.google.com/go/aiplatform v1.35.0/go.mod h1:7MFT/vCaOyZT/4IIFfxH4ErVg/4ku6lKv3w0+tFTgXQ= +cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= +cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4= +cloud.google.com/go/analytics v0.17.0/go.mod h1:WXFa3WSym4IZ+JiKmavYdJwGG/CvpqiqczmL59bTD9M= +cloud.google.com/go/apigateway v1.3.0/go.mod h1:89Z8Bhpmxu6AmUxuVRg/ECRGReEdiP3vQtk4Z1J9rJk= +cloud.google.com/go/apigateway v1.4.0/go.mod h1:pHVY9MKGaH9PQ3pJ4YLzoj6U5FUDeDFBllIz7WmzJoc= +cloud.google.com/go/apigateway v1.5.0/go.mod h1:GpnZR3Q4rR7LVu5951qfXPJCHquZt02jf7xQx7kpqN8= +cloud.google.com/go/apigeeconnect v1.3.0/go.mod h1:G/AwXFAKo0gIXkPTVfZDd2qA1TxBXJ3MgMRBQkIi9jc= +cloud.google.com/go/apigeeconnect v1.4.0/go.mod h1:kV4NwOKqjvt2JYR0AoIWo2QGfoRtn/pkS3QlHp0Ni04= +cloud.google.com/go/apigeeconnect v1.5.0/go.mod h1:KFaCqvBRU6idyhSNyn3vlHXc8VMDJdRmwDF6JyFRqZ8= +cloud.google.com/go/apigeeregistry v0.4.0/go.mod h1:EUG4PGcsZvxOXAdyEghIdXwAEi/4MEaoqLMLDMIwKXY= +cloud.google.com/go/apigeeregistry v0.5.0/go.mod h1:YR5+s0BVNZfVOUkMa5pAR2xGd0A473vA5M7j247o1wM= +cloud.google.com/go/apikeys v0.4.0/go.mod h1:XATS/yqZbaBK0HOssf+ALHp8jAlNHUgyfprvNcBIszU= +cloud.google.com/go/apikeys v0.5.0/go.mod h1:5aQfwY4D+ewMMWScd3hm2en3hCj+BROlyrt3ytS7KLI= +cloud.google.com/go/appengine v1.4.0/go.mod h1:CS2NhuBuDXM9f+qscZ6V86m1MIIqPj3WC/UoEuR1Sno= +cloud.google.com/go/appengine v1.5.0/go.mod h1:TfasSozdkFI0zeoxW3PTBLiNqRmzraodCWatWI9Dmak= +cloud.google.com/go/appengine v1.6.0/go.mod h1:hg6i0J/BD2cKmDJbaFSYHFyZkgBEfQrDg/X0V5fJn84= +cloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4= +cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0= +cloud.google.com/go/area120 v0.7.0/go.mod h1:a3+8EUD1SX5RUcCs3MY5YasiO1z6yLiNLRiFrykbynY= +cloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ= +cloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk= +cloud.google.com/go/artifactregistry v1.8.0/go.mod h1:w3GQXkJX8hiKN0v+at4b0qotwijQbYUqF2GWkZzAhC0= +cloud.google.com/go/artifactregistry v1.9.0/go.mod h1:2K2RqvA2CYvAeARHRkLDhMDJ3OXy26h3XW+3/Jh2uYc= +cloud.google.com/go/artifactregistry v1.11.1/go.mod h1:lLYghw+Itq9SONbCa1YWBoWs1nOucMH0pwXN1rOBZFI= +cloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o= +cloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s= +cloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjbytpUaW0= +cloud.google.com/go/asset v1.9.0/go.mod h1:83MOE6jEJBMqFKadM9NLRcs80Gdw76qGuHn8m3h8oHQ= +cloud.google.com/go/asset v1.10.0/go.mod h1:pLz7uokL80qKhzKr4xXGvBQXnzHn5evJAEAtZiIb0wY= +cloud.google.com/go/asset v1.11.1/go.mod h1:fSwLhbRvC9p9CXQHJ3BgFeQNM4c9x10lqlrdEUYXlJo= +cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY= +cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw= +cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI= +cloud.google.com/go/assuredworkloads v1.8.0/go.mod h1:AsX2cqyNCOvEQC8RMPnoc0yEarXQk6WEKkxYfL6kGIo= +cloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0= +cloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E= +cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0= +cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8= +cloud.google.com/go/automl v1.7.0/go.mod h1:RL9MYCCsJEOmt0Wf3z9uzG0a7adTT1fe+aObgSpkCt8= +cloud.google.com/go/automl v1.8.0/go.mod h1:xWx7G/aPEe/NP+qzYXktoBSDfjO+vnKMGgsApGJJquM= +cloud.google.com/go/automl v1.12.0/go.mod h1:tWDcHDp86aMIuHmyvjuKeeHEGq76lD7ZqfGLN6B0NuU= +cloud.google.com/go/baremetalsolution v0.3.0/go.mod h1:XOrocE+pvK1xFfleEnShBlNAXf+j5blPPxrhjKgnIFc= +cloud.google.com/go/baremetalsolution v0.4.0/go.mod h1:BymplhAadOO/eBa7KewQ0Ppg4A4Wplbn+PsFKRLo0uI= +cloud.google.com/go/baremetalsolution v0.5.0/go.mod h1:dXGxEkmR9BMwxhzBhV0AioD0ULBmuLZI8CdwalUxuss= +cloud.google.com/go/batch v0.3.0/go.mod h1:TR18ZoAekj1GuirsUsR1ZTKN3FC/4UDnScjT8NXImFE= +cloud.google.com/go/batch v0.4.0/go.mod h1:WZkHnP43R/QCGQsZ+0JyG4i79ranE2u8xvjq/9+STPE= +cloud.google.com/go/batch v0.7.0/go.mod h1:vLZN95s6teRUqRQ4s3RLDsH8PvboqBK+rn1oevL159g= +cloud.google.com/go/beyondcorp v0.2.0/go.mod h1:TB7Bd+EEtcw9PCPQhCJtJGjk/7TC6ckmnSFS+xwTfm4= +cloud.google.com/go/beyondcorp v0.3.0/go.mod h1:E5U5lcrcXMsCuoDNyGrpyTm/hn7ne941Jz2vmksAxW8= +cloud.google.com/go/beyondcorp v0.4.0/go.mod h1:3ApA0mbhHx6YImmuubf5pyW8srKnCEPON32/5hj+RmM= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/bigquery v1.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA= +cloud.google.com/go/bigquery v1.43.0/go.mod h1:ZMQcXHsl+xmU1z36G2jNGZmKp9zNY5BUua5wDgmNCfw= +cloud.google.com/go/bigquery v1.44.0/go.mod h1:0Y33VqXTEsbamHJvJHdFmtqHvMIY28aK1+dFsvaChGc= +cloud.google.com/go/bigquery v1.47.0/go.mod h1:sA9XOgy0A8vQK9+MWhEQTY6Tix87M/ZurWFIxmF9I/E= +cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY= +cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s= +cloud.google.com/go/billing v1.6.0/go.mod h1:WoXzguj+BeHXPbKfNWkqVtDdzORazmCjraY+vrxcyvI= +cloud.google.com/go/billing v1.7.0/go.mod h1:q457N3Hbj9lYwwRbnlD7vUpyjq6u5U1RAOArInEiD5Y= +cloud.google.com/go/billing v1.12.0/go.mod h1:yKrZio/eu+okO/2McZEbch17O5CB5NpZhhXG6Z766ss= +cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM= +cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI= +cloud.google.com/go/binaryauthorization v1.3.0/go.mod h1:lRZbKgjDIIQvzYQS1p99A7/U1JqvqeZg0wiI5tp6tg0= +cloud.google.com/go/binaryauthorization v1.4.0/go.mod h1:tsSPQrBd77VLplV70GUhBf/Zm3FsKmgSqgm4UmiDItk= +cloud.google.com/go/binaryauthorization v1.5.0/go.mod h1:OSe4OU1nN/VswXKRBmciKpo9LulY41gch5c68htf3/Q= +cloud.google.com/go/certificatemanager v1.3.0/go.mod h1:n6twGDvcUBFu9uBgt4eYvvf3sQ6My8jADcOVwHmzadg= +cloud.google.com/go/certificatemanager v1.4.0/go.mod h1:vowpercVFyqs8ABSmrdV+GiFf2H/ch3KyudYQEMM590= +cloud.google.com/go/certificatemanager v1.6.0/go.mod h1:3Hh64rCKjRAX8dXgRAyOcY5vQ/fE1sh8o+Mdd6KPgY8= +cloud.google.com/go/channel v1.8.0/go.mod h1:W5SwCXDJsq/rg3tn3oG0LOxpAo6IMxNa09ngphpSlnk= +cloud.google.com/go/channel v1.9.0/go.mod h1:jcu05W0my9Vx4mt3/rEHpfxc9eKi9XwsdDL8yBMbKUk= +cloud.google.com/go/channel v1.11.0/go.mod h1:IdtI0uWGqhEeatSB62VOoJ8FSUhJ9/+iGkJVqp74CGE= +cloud.google.com/go/cloudbuild v1.3.0/go.mod h1:WequR4ULxlqvMsjDEEEFnOG5ZSRSgWOywXYDb1vPE6U= +cloud.google.com/go/cloudbuild v1.4.0/go.mod h1:5Qwa40LHiOXmz3386FrjrYM93rM/hdRr7b53sySrTqA= +cloud.google.com/go/cloudbuild v1.6.0/go.mod h1:UIbc/w9QCbH12xX+ezUsgblrWv+Cv4Tw83GiSMHOn9M= +cloud.google.com/go/clouddms v1.3.0/go.mod h1:oK6XsCDdW4Ib3jCCBugx+gVjevp2TMXFtgxvPSee3OM= +cloud.google.com/go/clouddms v1.4.0/go.mod h1:Eh7sUGCC+aKry14O1NRljhjyrr0NFC0G2cjwX0cByRk= +cloud.google.com/go/clouddms v1.5.0/go.mod h1:QSxQnhikCLUw13iAbffF2CZxAER3xDGNHjsTAkQJcQA= +cloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY= +cloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI= +cloud.google.com/go/cloudtasks v1.7.0/go.mod h1:ImsfdYWwlWNJbdgPIIGJWC+gemEGTBK/SunNQQNCAb4= +cloud.google.com/go/cloudtasks v1.8.0/go.mod h1:gQXUIwCSOI4yPVK7DgTVFiiP0ZW/eQkydWzwVMdHxrI= +cloud.google.com/go/cloudtasks v1.9.0/go.mod h1:w+EyLsVkLWHcOaqNEyvcKAsWp9p29dL6uL9Nst1cI7Y= cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= cloud.google.com/go/compute v1.5.0 h1:b1zWmYuuHz7gO9kDcM/EpHGr06UgsYNRpNJzI2kFiLM= cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= +cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= +cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= +cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= +cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= +cloud.google.com/go/compute v1.12.0/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= +cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= +cloud.google.com/go/compute v1.13.0/go.mod h1:5aPTS0cUNMIc1CE546K+Th6weJUNQErARyZtRXDJ8GE= +cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= +cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA= +cloud.google.com/go/compute v1.18.0 h1:FEigFqoDbys2cvFkZ9Fjq4gnHBP55anJ0yQyau2f9oY= +cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= +cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= +cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY= +cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck= +cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w= +cloud.google.com/go/container v1.6.0/go.mod h1:Xazp7GjJSeUYo688S+6J5V+n/t+G5sKBTFkKNudGRxg= +cloud.google.com/go/container v1.7.0/go.mod h1:Dp5AHtmothHGX3DwwIHPgq45Y8KmNsgN3amoYfxVkLo= +cloud.google.com/go/container v1.13.1/go.mod h1:6wgbMPeQRw9rSnKBCAJXnds3Pzj03C4JHamr8asWKy4= +cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= +cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4= +cloud.google.com/go/containeranalysis v0.7.0/go.mod h1:9aUL+/vZ55P2CXfuZjS4UjQ9AgXoSw8Ts6lemfmxBxI= +cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0= +cloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs= +cloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc= +cloud.google.com/go/datacatalog v1.7.0/go.mod h1:9mEl4AuDYWw81UGc41HonIHH7/sn52H0/tc8f8ZbZIE= +cloud.google.com/go/datacatalog v1.8.0/go.mod h1:KYuoVOv9BM8EYz/4eMFxrr4DUKhGIOXxZoKYF5wdISM= +cloud.google.com/go/datacatalog v1.8.1/go.mod h1:RJ58z4rMp3gvETA465Vg+ag8BGgBdnRPEMMSTr5Uv+M= +cloud.google.com/go/datacatalog v1.12.0/go.mod h1:CWae8rFkfp6LzLumKOnmVh4+Zle4A3NXLzVJ1d1mRm0= +cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM= +cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ= +cloud.google.com/go/dataflow v0.8.0/go.mod h1:Rcf5YgTKPtQyYz8bLYhFoIV/vP39eL7fWNcSOyFfLJE= +cloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo= +cloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE= +cloud.google.com/go/dataform v0.5.0/go.mod h1:GFUYRe8IBa2hcomWplodVmUx/iTL0FrsauObOM3Ipr0= +cloud.google.com/go/dataform v0.6.0/go.mod h1:QPflImQy33e29VuapFdf19oPbE4aYTJxr31OAPV+ulA= +cloud.google.com/go/datafusion v1.4.0/go.mod h1:1Zb6VN+W6ALo85cXnM1IKiPw+yQMKMhB9TsTSRDo/38= +cloud.google.com/go/datafusion v1.5.0/go.mod h1:Kz+l1FGHB0J+4XF2fud96WMmRiq/wj8N9u007vyXZ2w= +cloud.google.com/go/datafusion v1.6.0/go.mod h1:WBsMF8F1RhSXvVM8rCV3AeyWVxcC2xY6vith3iw3S+8= +cloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I= +cloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ= +cloud.google.com/go/datalabeling v0.7.0/go.mod h1:WPQb1y08RJbmpM3ww0CSUAGweL0SxByuW2E+FU+wXcM= +cloud.google.com/go/dataplex v1.3.0/go.mod h1:hQuRtDg+fCiFgC8j0zV222HvzFQdRd+SVX8gdmFcZzA= +cloud.google.com/go/dataplex v1.4.0/go.mod h1:X51GfLXEMVJ6UN47ESVqvlsRplbLhcsAt0kZCCKsU0A= +cloud.google.com/go/dataplex v1.5.2/go.mod h1:cVMgQHsmfRoI5KFYq4JtIBEUbYwc3c7tXmIDhRmNNVQ= +cloud.google.com/go/dataproc v1.7.0/go.mod h1:CKAlMjII9H90RXaMpSxQ8EU6dQx6iAYNPcYPOkSbi8s= +cloud.google.com/go/dataproc v1.8.0/go.mod h1:5OW+zNAH0pMpw14JVrPONsxMQYMBqJuzORhIBfBn9uI= +cloud.google.com/go/dataproc v1.12.0/go.mod h1:zrF3aX0uV3ikkMz6z4uBbIKyhRITnxvr4i3IjKsKrw4= +cloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo= +cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA= +cloud.google.com/go/dataqna v0.7.0/go.mod h1:Lx9OcIIeqCrw1a6KdO3/5KMP1wAmTc0slZWwP12Qq3c= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/datastore v1.10.0/go.mod h1:PC5UzAmDEkAmkfaknstTYbNpgE49HAgW2J1gcgUfmdM= +cloud.google.com/go/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo= +cloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ= +cloud.google.com/go/datastream v1.4.0/go.mod h1:h9dpzScPhDTs5noEMQVWP8Wx8AFBRyS0s8KWPx/9r0g= +cloud.google.com/go/datastream v1.5.0/go.mod h1:6TZMMNPwjUqZHBKPQ1wwXpb0d5VDVPl2/XoS5yi88q4= +cloud.google.com/go/datastream v1.6.0/go.mod h1:6LQSuswqLa7S4rPAOZFVjHIG3wJIjZcZrw8JDEDJuIs= +cloud.google.com/go/deploy v1.4.0/go.mod h1:5Xghikd4VrmMLNaF6FiRFDlHb59VM59YoDQnOUdsH/c= +cloud.google.com/go/deploy v1.5.0/go.mod h1:ffgdD0B89tToyW/U/D2eL0jN2+IEV/3EMuXHA0l4r+s= +cloud.google.com/go/deploy v1.6.0/go.mod h1:f9PTHehG/DjCom3QH0cntOVRm93uGBDt2vKzAPwpXQI= +cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4= +cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0= +cloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8= +cloud.google.com/go/dialogflow v1.18.0/go.mod h1:trO7Zu5YdyEuR+BhSNOqJezyFQ3aUzz0njv7sMx/iek= +cloud.google.com/go/dialogflow v1.19.0/go.mod h1:JVmlG1TwykZDtxtTXujec4tQ+D8SBFMoosgy+6Gn0s0= +cloud.google.com/go/dialogflow v1.29.0/go.mod h1:b+2bzMe+k1s9V+F2jbJwpHPzrnIyHihAdRFMtn2WXuM= +cloud.google.com/go/dialogflow v1.31.0/go.mod h1:cuoUccuL1Z+HADhyIA7dci3N5zUssgpBJmCzI6fNRB4= +cloud.google.com/go/dlp v1.6.0/go.mod h1:9eyB2xIhpU0sVwUixfBubDoRwP+GjeUoxxeueZmqvmM= +cloud.google.com/go/dlp v1.7.0/go.mod h1:68ak9vCiMBjbasxeVD17hVPxDEck+ExiHavX8kiHG+Q= +cloud.google.com/go/dlp v1.9.0/go.mod h1:qdgmqgTyReTz5/YNSSuueR8pl7hO0o9bQ39ZhtgkWp4= +cloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU= +cloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU= +cloud.google.com/go/documentai v1.9.0/go.mod h1:FS5485S8R00U10GhgBC0aNGrJxBP8ZVpEeJ7PQDZd6k= +cloud.google.com/go/documentai v1.10.0/go.mod h1:vod47hKQIPeCfN2QS/jULIvQTugbmdc0ZvxxfQY1bg4= +cloud.google.com/go/documentai v1.16.0/go.mod h1:o0o0DLTEZ+YnJZ+J4wNfTxmDVyrkzFvttBXXtYRMHkM= +cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y= +cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg= +cloud.google.com/go/domains v0.8.0/go.mod h1:M9i3MMDzGFXsydri9/vW+EWz9sWb4I6WyHqdlAk0idE= +cloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk= +cloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w= +cloud.google.com/go/edgecontainer v0.3.0/go.mod h1:FLDpP4nykgwwIfcLt6zInhprzw0lEi2P1fjO6Ie0qbc= +cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU= +cloud.google.com/go/essentialcontacts v1.3.0/go.mod h1:r+OnHa5jfj90qIfZDO/VztSFqbQan7HV75p8sA+mdGI= +cloud.google.com/go/essentialcontacts v1.4.0/go.mod h1:8tRldvHYsmnBCHdFpvU+GL75oWiBKl80BiqlFh9tp+8= +cloud.google.com/go/essentialcontacts v1.5.0/go.mod h1:ay29Z4zODTuwliK7SnX8E86aUF2CTzdNtvv42niCX0M= +cloud.google.com/go/eventarc v1.7.0/go.mod h1:6ctpF3zTnaQCxUjHUdcfgcA1A2T309+omHZth7gDfmc= +cloud.google.com/go/eventarc v1.8.0/go.mod h1:imbzxkyAU4ubfsaKYdQg04WS1NvncblHEup4kvF+4gw= +cloud.google.com/go/eventarc v1.10.0/go.mod h1:u3R35tmZ9HvswGRBnF48IlYgYeBcPUCjkr4BTdem2Kw= +cloud.google.com/go/filestore v1.3.0/go.mod h1:+qbvHGvXU1HaKX2nD0WEPo92TP/8AQuCVEBXNY9z0+w= +cloud.google.com/go/filestore v1.4.0/go.mod h1:PaG5oDfo9r224f8OYXURtAsY+Fbyq/bLYoINEK8XQAI= +cloud.google.com/go/filestore v1.5.0/go.mod h1:FqBXDWBp4YLHqRnVGveOkHDf8svj9r5+mUDLupOWEDs= +cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= +cloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk= +cloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg= +cloud.google.com/go/functions v1.8.0/go.mod h1:RTZ4/HsQjIqIYP9a9YPbU+QFoQsAlYgrwOXJWHn1POY= +cloud.google.com/go/functions v1.9.0/go.mod h1:Y+Dz8yGguzO3PpIjhLTbnqV1CWmgQ5UwtlpzoyquQ08= +cloud.google.com/go/functions v1.10.0/go.mod h1:0D3hEOe3DbEvCXtYOZHQZmD+SzYsi1YbI7dGvHfldXw= +cloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM= +cloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA= +cloud.google.com/go/gaming v1.7.0/go.mod h1:LrB8U7MHdGgFG851iHAfqUdLcKBdQ55hzXy9xBJz0+w= +cloud.google.com/go/gaming v1.8.0/go.mod h1:xAqjS8b7jAVW0KFYeRUxngo9My3f33kFmua++Pi+ggM= +cloud.google.com/go/gaming v1.9.0/go.mod h1:Fc7kEmCObylSWLO334NcO+O9QMDyz+TKC4v1D7X+Bc0= +cloud.google.com/go/gkebackup v0.2.0/go.mod h1:XKvv/4LfG829/B8B7xRkk8zRrOEbKtEam6yNfuQNH60= +cloud.google.com/go/gkebackup v0.3.0/go.mod h1:n/E671i1aOQvUxT541aTkCwExO/bTer2HDlj4TsBRAo= +cloud.google.com/go/gkebackup v0.4.0/go.mod h1:byAyBGUwYGEEww7xsbnUTBHIYcOPy/PgUWUtOeRm9Vg= +cloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o= +cloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A= +cloud.google.com/go/gkeconnect v0.7.0/go.mod h1:SNfmVqPkaEi3bF/B3CNZOAYPYdg7sU+obZ+QTky2Myw= +cloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0= +cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0= +cloud.google.com/go/gkehub v0.11.0/go.mod h1:JOWHlmN+GHyIbuWQPl47/C2RFhnFKH38jH9Ascu3n0E= +cloud.google.com/go/gkemulticloud v0.3.0/go.mod h1:7orzy7O0S+5kq95e4Hpn7RysVA7dPs8W/GgfUtsPbrA= +cloud.google.com/go/gkemulticloud v0.4.0/go.mod h1:E9gxVBnseLWCk24ch+P9+B2CoDFJZTyIgLKSalC7tuI= +cloud.google.com/go/gkemulticloud v0.5.0/go.mod h1:W0JDkiyi3Tqh0TJr//y19wyb1yf8llHVto2Htf2Ja3Y= +cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= +cloud.google.com/go/gsuiteaddons v1.3.0/go.mod h1:EUNK/J1lZEZO8yPtykKxLXI6JSVN2rg9bN8SXOa0bgM= +cloud.google.com/go/gsuiteaddons v1.4.0/go.mod h1:rZK5I8hht7u7HxFQcFei0+AtfS9uSushomRlg+3ua1o= +cloud.google.com/go/gsuiteaddons v1.5.0/go.mod h1:TFCClYLd64Eaa12sFVmUyG62tk4mdIsI7pAnSXRkcFo= cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c= cloud.google.com/go/iam v0.3.0 h1:exkAomrVUuzx9kWFI1wm3KI0uoDeUFPB4kKGzx6x+Gc= cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= +cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= +cloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHDMFMc= +cloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg= +cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= +cloud.google.com/go/iam v0.11.0/go.mod h1:9PiLDanza5D+oWFZiH1uG+RnRCfEGKoyl6yo4cgWZGY= +cloud.google.com/go/iam v0.12.0 h1:DRtTY29b75ciH6Ov1PHb4/iat2CLCvrOm40Q0a6DFpE= +cloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY= +cloud.google.com/go/iap v1.4.0/go.mod h1:RGFwRJdihTINIe4wZ2iCP0zF/qu18ZwyKxrhMhygBEc= +cloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A= +cloud.google.com/go/iap v1.6.0/go.mod h1:NSuvI9C/j7UdjGjIde7t7HBz+QTwBcapPE07+sSRcLk= +cloud.google.com/go/ids v1.1.0/go.mod h1:WIuwCaYVOzHIj2OhN9HAwvW+DBdmUAdcWlFxRl+KubM= +cloud.google.com/go/ids v1.2.0/go.mod h1:5WXvp4n25S0rA/mQWAg1YEEBBq6/s+7ml1RDCW1IrcY= +cloud.google.com/go/ids v1.3.0/go.mod h1:JBdTYwANikFKaDP6LtW5JAi4gubs57SVNQjemdt6xV4= +cloud.google.com/go/iot v1.3.0/go.mod h1:r7RGh2B61+B8oz0AGE+J72AhA0G7tdXItODWsaA2oLs= +cloud.google.com/go/iot v1.4.0/go.mod h1:dIDxPOn0UvNDUMD8Ger7FIaTuvMkj+aGk94RPP0iV+g= +cloud.google.com/go/iot v1.5.0/go.mod h1:mpz5259PDl3XJthEmh9+ap0affn/MqNSP4My77Qql9o= cloud.google.com/go/kms v1.1.0 h1:1yc4rLqCkVDS9Zvc7m+3mJ47kw0Uo5Q5+sMjcmUVUeM= cloud.google.com/go/kms v1.1.0/go.mod h1:WdbppnCDMDpOvoYBMn1+gNmOeEoZYqAv+HeuKARGCXI= +cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA= +cloud.google.com/go/kms v1.5.0/go.mod h1:QJS2YY0eJGBg3mnDfuaCyLauWwBJiHRboYxJ++1xJNg= +cloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0= +cloud.google.com/go/kms v1.8.0 h1:VrJLOsMRzW7IqTTYn+OYupqF3iKSE060Nrn+PECrYjg= +cloud.google.com/go/kms v1.8.0/go.mod h1:4xFEhYFqvW+4VMELtZyxomGSYtSQKzM178ylFW4jMAg= +cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= +cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= +cloud.google.com/go/language v1.7.0/go.mod h1:DJ6dYN/W+SQOjF8e1hLQXMF21AkH2w9wiPzPCJa2MIE= +cloud.google.com/go/language v1.8.0/go.mod h1:qYPVHf7SPoNNiCL2Dr0FfEFNil1qi3pQEyygwpgVKB8= +cloud.google.com/go/language v1.9.0/go.mod h1:Ns15WooPM5Ad/5no/0n81yUetis74g3zrbeJBE+ptUY= +cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= +cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08= +cloud.google.com/go/lifesciences v0.8.0/go.mod h1:lFxiEOMqII6XggGbOnKiyZ7IBwoIqA84ClvoezaA/bo= +cloud.google.com/go/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9OfilNXBw= +cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M= +cloud.google.com/go/longrunning v0.1.1/go.mod h1:UUFxuDWkv22EuY93jjmDMFT5GPQKeFVJBIF6QlTqdsE= +cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc= +cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo= +cloud.google.com/go/managedidentities v1.3.0/go.mod h1:UzlW3cBOiPrzucO5qWkNkh0w33KFtBJU281hacNvsdE= +cloud.google.com/go/managedidentities v1.4.0/go.mod h1:NWSBYbEMgqmbZsLIyKvxrYbtqOsxY1ZrGM+9RgDqInM= +cloud.google.com/go/managedidentities v1.5.0/go.mod h1:+dWcZ0JlUmpuxpIDfyP5pP5y0bLdRwOS4Lp7gMni/LA= +cloud.google.com/go/maps v0.1.0/go.mod h1:BQM97WGyfw9FWEmQMpZ5T6cpovXXSd1cGmFma94eubI= +cloud.google.com/go/maps v0.6.0/go.mod h1:o6DAMMfb+aINHz/p/jbcY+mYeXBoZoxTfdSQ8VAJaCw= +cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4= +cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= +cloud.google.com/go/mediatranslation v0.7.0/go.mod h1:LCnB/gZr90ONOIQLgSXagp8XUW1ODs2UmUMvcgMfI2I= +cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE= +cloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM= +cloud.google.com/go/memcache v1.6.0/go.mod h1:XS5xB0eQZdHtTuTF9Hf8eJkKtR3pVRCcvJwtm68T3rA= +cloud.google.com/go/memcache v1.7.0/go.mod h1:ywMKfjWhNtkQTxrWxCkCFkoPjLHPW6A7WOTVI8xy3LY= +cloud.google.com/go/memcache v1.9.0/go.mod h1:8oEyzXCu+zo9RzlEaEjHl4KkgjlNDaXbCQeQWlzNFJM= +cloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY= +cloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s= +cloud.google.com/go/metastore v1.7.0/go.mod h1:s45D0B4IlsINu87/AsWiEVYbLaIMeUSoxlKKDqBGFS8= +cloud.google.com/go/metastore v1.8.0/go.mod h1:zHiMc4ZUpBiM7twCIFQmJ9JMEkDSyZS9U12uf7wHqSI= +cloud.google.com/go/metastore v1.10.0/go.mod h1:fPEnH3g4JJAk+gMRnrAnoqyv2lpUCqJPWOodSaf45Eo= +cloud.google.com/go/monitoring v1.7.0/go.mod h1:HpYse6kkGo//7p6sT0wsIC6IBDET0RhIsnmlA53dvEk= +cloud.google.com/go/monitoring v1.8.0/go.mod h1:E7PtoMJ1kQXWxPjB6mv2fhC5/15jInuulFdYYtlcvT4= +cloud.google.com/go/monitoring v1.12.0/go.mod h1:yx8Jj2fZNEkL/GYZyTLS4ZtZEZN8WtDEiEqG4kLK50w= +cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA= +cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o= +cloud.google.com/go/networkconnectivity v1.6.0/go.mod h1:OJOoEXW+0LAxHh89nXd64uGG+FbQoeH8DtxCHVOMlaM= +cloud.google.com/go/networkconnectivity v1.7.0/go.mod h1:RMuSbkdbPwNMQjB5HBWD5MpTBnNm39iAVpC3TmsExt8= +cloud.google.com/go/networkconnectivity v1.10.0/go.mod h1:UP4O4sWXJG13AqrTdQCD9TnLGEbtNRqjuaaA7bNjF5E= +cloud.google.com/go/networkmanagement v1.4.0/go.mod h1:Q9mdLLRn60AsOrPc8rs8iNV6OHXaGcDdsIQe1ohekq8= +cloud.google.com/go/networkmanagement v1.5.0/go.mod h1:ZnOeZ/evzUdUsnvRt792H0uYEnHQEMaz+REhhzJRcf4= +cloud.google.com/go/networkmanagement v1.6.0/go.mod h1:5pKPqyXjB/sgtvB5xqOemumoQNB7y95Q7S+4rjSOPYY= +cloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ= +cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU= +cloud.google.com/go/networksecurity v0.7.0/go.mod h1:mAnzoxx/8TBSyXEeESMy9OOYwo1v+gZ5eMRnsT5bC8k= +cloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY= +cloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34= +cloud.google.com/go/notebooks v1.4.0/go.mod h1:4QPMngcwmgb6uw7Po99B2xv5ufVoIQ7nOGDyL4P8AgA= +cloud.google.com/go/notebooks v1.5.0/go.mod h1:q8mwhnP9aR8Hpfnrc5iN5IBhrXUy8S2vuYs+kBJ/gu0= +cloud.google.com/go/notebooks v1.7.0/go.mod h1:PVlaDGfJgj1fl1S3dUwhFMXFgfYGhYQt2164xOMONmE= +cloud.google.com/go/optimization v1.1.0/go.mod h1:5po+wfvX5AQlPznyVEZjGJTMr4+CAkJf2XSTQOOl9l4= +cloud.google.com/go/optimization v1.2.0/go.mod h1:Lr7SOHdRDENsh+WXVmQhQTrzdu9ybg0NecjHidBq6xs= +cloud.google.com/go/optimization v1.3.1/go.mod h1:IvUSefKiwd1a5p0RgHDbWCIbDFgKuEdB+fPPuP0IDLI= +cloud.google.com/go/orchestration v1.3.0/go.mod h1:Sj5tq/JpWiB//X/q3Ngwdl5K7B7Y0KZ7bfv0wL6fqVA= +cloud.google.com/go/orchestration v1.4.0/go.mod h1:6W5NLFWs2TlniBphAViZEVhrXRSMgUGDfW7vrWKvsBk= +cloud.google.com/go/orchestration v1.6.0/go.mod h1:M62Bevp7pkxStDfFfTuCOaXgaaqRAga1yKyoMtEoWPQ= +cloud.google.com/go/orgpolicy v1.4.0/go.mod h1:xrSLIV4RePWmP9P3tBl8S93lTmlAxjm06NSm2UTmKvE= +cloud.google.com/go/orgpolicy v1.5.0/go.mod h1:hZEc5q3wzwXJaKrsx5+Ewg0u1LxJ51nNFlext7Tanwc= +cloud.google.com/go/orgpolicy v1.10.0/go.mod h1:w1fo8b7rRqlXlIJbVhOMPrwVljyuW5mqssvBtU18ONc= +cloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs= +cloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg= +cloud.google.com/go/osconfig v1.9.0/go.mod h1:Yx+IeIZJ3bdWmzbQU4fxNl8xsZ4amB+dygAwFPlvnNo= +cloud.google.com/go/osconfig v1.10.0/go.mod h1:uMhCzqC5I8zfD9zDEAfvgVhDS8oIjySWh+l4WK6GnWw= +cloud.google.com/go/osconfig v1.11.0/go.mod h1:aDICxrur2ogRd9zY5ytBLV89KEgT2MKB2L/n6x1ooPw= +cloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E= +cloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU= +cloud.google.com/go/oslogin v1.6.0/go.mod h1:zOJ1O3+dTU8WPlGEkFSh7qeHPPSoxrcMbbK1Nm2iX70= +cloud.google.com/go/oslogin v1.7.0/go.mod h1:e04SN0xO1UNJ1M5GP0vzVBFicIe4O53FOfcixIqTyXo= +cloud.google.com/go/oslogin v1.9.0/go.mod h1:HNavntnH8nzrn8JCTT5fj18FuJLFJc4NaZJtBnQtKFs= +cloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0= +cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA= +cloud.google.com/go/phishingprotection v0.7.0/go.mod h1:8qJI4QKHoda/sb/7/YmMQ2omRLSLYSu9bU0EKCNI+Lk= +cloud.google.com/go/policytroubleshooter v1.3.0/go.mod h1:qy0+VwANja+kKrjlQuOzmlvscn4RNsAc0e15GGqfMxg= +cloud.google.com/go/policytroubleshooter v1.4.0/go.mod h1:DZT4BcRw3QoO8ota9xw/LKtPa8lKeCByYeKTIf/vxdE= +cloud.google.com/go/policytroubleshooter v1.5.0/go.mod h1:Rz1WfV+1oIpPdN2VvvuboLVRsB1Hclg3CKQ53j9l8vw= +cloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0= +cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI= +cloud.google.com/go/privatecatalog v0.7.0/go.mod h1:2s5ssIFO69F5csTXcwBP7NPFTZvps26xGzvQ2PQaBYg= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/pubsub v1.19.0 h1:WZy66ga6/tqmZiwv1jwKVgqV8FuEuAmPR5CEJHNVCZk= cloud.google.com/go/pubsub v1.19.0/go.mod h1:/O9kmSe9bb9KRnIAWkzmqhPjHo6LtzGOBYd/kr06XSs= +cloud.google.com/go/pubsub v1.26.0/go.mod h1:QgBH3U/jdJy/ftjPhTkyXNj543Tin1pRYcdcPRnFIRI= +cloud.google.com/go/pubsub v1.27.1/go.mod h1:hQN39ymbV9geqBnfQq6Xf63yNhUAhv9CZhzp5O6qsW0= +cloud.google.com/go/pubsub v1.28.0 h1:XzabfdPx/+eNrsVVGLFgeUnQQKPGkMb8klRCeYK52is= +cloud.google.com/go/pubsub v1.28.0/go.mod h1:vuXFpwaVoIPQMGXqRyUQigu/AX1S3IWugR9xznmcXX8= +cloud.google.com/go/pubsublite v1.5.0/go.mod h1:xapqNQ1CuLfGi23Yda/9l4bBCKz/wC3KIJ5gKcxveZg= +cloud.google.com/go/pubsublite v1.6.0/go.mod h1:1eFCS0U11xlOuMFV/0iBqw3zP12kddMeCbj/F3FSj9k= +cloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4= +cloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o= +cloud.google.com/go/recaptchaenterprise/v2 v2.2.0/go.mod h1:/Zu5jisWGeERrd5HnlS3EUGb/D335f9k51B/FVil0jk= +cloud.google.com/go/recaptchaenterprise/v2 v2.3.0/go.mod h1:O9LwGCjrhGHBQET5CA7dd5NwwNQUErSgEDit1DLNTdo= +cloud.google.com/go/recaptchaenterprise/v2 v2.4.0/go.mod h1:Am3LHfOuBstrLrNCBrlI5sbwx9LBg3te2N6hGvHn2mE= +cloud.google.com/go/recaptchaenterprise/v2 v2.5.0/go.mod h1:O8LzcHXN3rz0j+LBC91jrwI3R+1ZSZEWrfL7XHgNo9U= +cloud.google.com/go/recaptchaenterprise/v2 v2.6.0/go.mod h1:RPauz9jeLtB3JVzg6nCbe12qNoaa8pXc4d/YukAmcnA= +cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg= +cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4= +cloud.google.com/go/recommendationengine v0.7.0/go.mod h1:1reUcE3GIu6MeBz/h5xZJqNLuuVjNg1lmWMPyjatzac= +cloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg= +cloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c= +cloud.google.com/go/recommender v1.7.0/go.mod h1:XLHs/W+T8olwlGOgfQenXBTbIseGclClff6lhFVe9Bs= +cloud.google.com/go/recommender v1.8.0/go.mod h1:PkjXrTT05BFKwxaUxQmtIlrtj0kph108r02ZZQ5FE70= +cloud.google.com/go/recommender v1.9.0/go.mod h1:PnSsnZY7q+VL1uax2JWkt/UegHssxjUVVCrX52CuEmQ= +cloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y= +cloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A= +cloud.google.com/go/redis v1.9.0/go.mod h1:HMYQuajvb2D0LvMgZmLDZW8V5aOC/WxstZHiy4g8OiA= +cloud.google.com/go/redis v1.10.0/go.mod h1:ThJf3mMBQtW18JzGgh41/Wld6vnDDc/F/F35UolRZPM= +cloud.google.com/go/redis v1.11.0/go.mod h1:/X6eicana+BWcUda5PpwZC48o37SiFVTFSs0fWAJ7uQ= +cloud.google.com/go/resourcemanager v1.3.0/go.mod h1:bAtrTjZQFJkiWTPDb1WBjzvc6/kifjj4QBYuKCCoqKA= +cloud.google.com/go/resourcemanager v1.4.0/go.mod h1:MwxuzkumyTX7/a3n37gmsT3py7LIXwrShilPh3P1tR0= +cloud.google.com/go/resourcemanager v1.5.0/go.mod h1:eQoXNAiAvCf5PXxWxXjhKQoTMaUSNrEfg+6qdf/wots= +cloud.google.com/go/resourcesettings v1.3.0/go.mod h1:lzew8VfESA5DQ8gdlHwMrqZs1S9V87v3oCnKCWoOuQU= +cloud.google.com/go/resourcesettings v1.4.0/go.mod h1:ldiH9IJpcrlC3VSuCGvjR5of/ezRrOxFtpJoJo5SmXg= +cloud.google.com/go/resourcesettings v1.5.0/go.mod h1:+xJF7QSG6undsQDfsCJyqWXyBwUoJLhetkRMDRnIoXA= +cloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4= +cloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY= +cloud.google.com/go/retail v1.10.0/go.mod h1:2gDk9HsL4HMS4oZwz6daui2/jmKvqShXKQuB2RZ+cCc= +cloud.google.com/go/retail v1.11.0/go.mod h1:MBLk1NaWPmh6iVFSz9MeKG/Psyd7TAgm6y/9L2B4x9Y= +cloud.google.com/go/retail v1.12.0/go.mod h1:UMkelN/0Z8XvKymXFbD4EhFJlYKRx1FGhQkVPU5kF14= +cloud.google.com/go/run v0.2.0/go.mod h1:CNtKsTA1sDcnqqIFR3Pb5Tq0usWxJJvsWOCPldRU3Do= +cloud.google.com/go/run v0.3.0/go.mod h1:TuyY1+taHxTjrD0ZFk2iAR+xyOXEA0ztb7U3UNA0zBo= +cloud.google.com/go/run v0.8.0/go.mod h1:VniEnuBwqjigv0A7ONfQUaEItaiCRVujlMqerPPiktM= +cloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s= +cloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI= +cloud.google.com/go/scheduler v1.6.0/go.mod h1:SgeKVM7MIwPn3BqtcBntpLyrIJftQISRrYB5ZtT+KOk= +cloud.google.com/go/scheduler v1.7.0/go.mod h1:jyCiBqWW956uBjjPMMuX09n3x37mtyPJegEWKxRsn44= +cloud.google.com/go/scheduler v1.8.0/go.mod h1:TCET+Y5Gp1YgHT8py4nlg2Sew8nUHMqcpousDgXJVQc= +cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA= +cloud.google.com/go/secretmanager v1.8.0/go.mod h1:hnVgi/bN5MYHd3Gt0SPuTPPp5ENina1/LxM+2W9U9J4= +cloud.google.com/go/secretmanager v1.9.0/go.mod h1:b71qH2l1yHmWQHt9LC80akm86mX8AL6X1MA01dW8ht4= +cloud.google.com/go/secretmanager v1.10.0/go.mod h1:MfnrdvKMPNra9aZtQFvBcvRU54hbPD8/HayQdlUgJpU= +cloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4= +cloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0= +cloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU= +cloud.google.com/go/security v1.9.0/go.mod h1:6Ta1bO8LXI89nZnmnsZGp9lVoVWXqsVbIq/t9dzI+2Q= +cloud.google.com/go/security v1.10.0/go.mod h1:QtOMZByJVlibUT2h9afNDWRZ1G96gVywH8T5GUSb9IA= +cloud.google.com/go/security v1.12.0/go.mod h1:rV6EhrpbNHrrxqlvW0BWAIawFWq3X90SduMJdFwtLB8= +cloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU= +cloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc= +cloud.google.com/go/securitycenter v1.15.0/go.mod h1:PeKJ0t8MoFmmXLXWm41JidyzI3PJjd8sXWaVqg43WWk= +cloud.google.com/go/securitycenter v1.16.0/go.mod h1:Q9GMaLQFUD+5ZTabrbujNWLtSLZIZF7SAR0wWECrjdk= +cloud.google.com/go/securitycenter v1.18.1/go.mod h1:0/25gAzCM/9OL9vVx4ChPeM/+DlfGQJDwBy/UC8AKK0= +cloud.google.com/go/servicecontrol v1.4.0/go.mod h1:o0hUSJ1TXJAmi/7fLJAedOovnujSEvjKCAFNXPQ1RaU= +cloud.google.com/go/servicecontrol v1.5.0/go.mod h1:qM0CnXHhyqKVuiZnGKrIurvVImCs8gmqWsDoqe9sU1s= +cloud.google.com/go/servicecontrol v1.10.0/go.mod h1:pQvyvSRh7YzUF2efw7H87V92mxU8FnFDawMClGCNuAA= +cloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs= +cloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg= +cloud.google.com/go/servicedirectory v1.6.0/go.mod h1:pUlbnWsLH9c13yGkxCmfumWEPjsRs1RlmJ4pqiNjVL4= +cloud.google.com/go/servicedirectory v1.7.0/go.mod h1:5p/U5oyvgYGYejufvxhgwjL8UVXjkuw7q5XcG10wx1U= +cloud.google.com/go/servicedirectory v1.8.0/go.mod h1:srXodfhY1GFIPvltunswqXpVxFPpZjf8nkKQT7XcXaY= +cloud.google.com/go/servicemanagement v1.4.0/go.mod h1:d8t8MDbezI7Z2R1O/wu8oTggo3BI2GKYbdG4y/SJTco= +cloud.google.com/go/servicemanagement v1.5.0/go.mod h1:XGaCRe57kfqu4+lRxaFEAuqmjzF0r+gWHjWqKqBvKFo= +cloud.google.com/go/servicemanagement v1.6.0/go.mod h1:aWns7EeeCOtGEX4OvZUWCCJONRZeFKiptqKf1D0l/Jc= +cloud.google.com/go/serviceusage v1.3.0/go.mod h1:Hya1cozXM4SeSKTAgGXgj97GlqUvF5JaoXacR1JTP/E= +cloud.google.com/go/serviceusage v1.4.0/go.mod h1:SB4yxXSaYVuUBYUml6qklyONXNLt83U0Rb+CXyhjEeU= +cloud.google.com/go/serviceusage v1.5.0/go.mod h1:w8U1JvqUqwJNPEOTQjrMHkw3IaIFLoLsPLvsE3xueec= +cloud.google.com/go/shell v1.3.0/go.mod h1:VZ9HmRjZBsjLGXusm7K5Q5lzzByZmJHf1d0IWHEN5X4= +cloud.google.com/go/shell v1.4.0/go.mod h1:HDxPzZf3GkDdhExzD/gs8Grqk+dmYcEjGShZgYa9URw= +cloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+qE2f9A= +cloud.google.com/go/spanner v1.41.0/go.mod h1:MLYDBJR/dY4Wt7ZaMIQ7rXOTLjYrmxLE/5ve9vFfWos= +cloud.google.com/go/spanner v1.44.0/go.mod h1:G8XIgYdOK+Fbcpbs7p2fiprDw4CaZX63whnSMLVBxjk= +cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM= +cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ= +cloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0= +cloud.google.com/go/speech v1.9.0/go.mod h1:xQ0jTcmnRFFM2RfX/U+rk6FQNUF6DQlydUSyoooSpco= +cloud.google.com/go/speech v1.14.1/go.mod h1:gEosVRPJ9waG7zqqnsHpYTOoAS4KouMRLDFMekpJ0J0= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= +cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= +cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= +cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= +cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w= +cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I= +cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4= +cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw= +cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g= +cloud.google.com/go/talent v1.3.0/go.mod h1:CmcxwJ/PKfRgd1pBjQgU6W3YBwiewmUzQYH5HHmSCmM= +cloud.google.com/go/talent v1.4.0/go.mod h1:ezFtAgVuRf8jRsvyE6EwmbTK5LKciD4KVnHuDEFmOOA= +cloud.google.com/go/talent v1.5.0/go.mod h1:G+ODMj9bsasAEJkQSzO2uHQWXHHXUomArjWQQYkqK6c= +cloud.google.com/go/texttospeech v1.4.0/go.mod h1:FX8HQHA6sEpJ7rCMSfXuzBcysDAuWusNNNvN9FELDd8= +cloud.google.com/go/texttospeech v1.5.0/go.mod h1:oKPLhR4n4ZdQqWKURdwxMy0uiTS1xU161C8W57Wkea4= +cloud.google.com/go/texttospeech v1.6.0/go.mod h1:YmwmFT8pj1aBblQOI3TfKmwibnsfvhIBzPXcW4EBovc= +cloud.google.com/go/tpu v1.3.0/go.mod h1:aJIManG0o20tfDQlRIej44FcwGGl/cD0oiRyMKG19IQ= +cloud.google.com/go/tpu v1.4.0/go.mod h1:mjZaX8p0VBgllCzF6wcU2ovUXN9TONFLd7iz227X2Xg= +cloud.google.com/go/tpu v1.5.0/go.mod h1:8zVo1rYDFuW2l4yZVY0R0fb/v44xLh3llq7RuV61fPM= +cloud.google.com/go/trace v1.3.0/go.mod h1:FFUE83d9Ca57C+K8rDl/Ih8LwOzWIV1krKgxg6N0G28= +cloud.google.com/go/trace v1.4.0/go.mod h1:UG0v8UBqzusp+z63o7FK74SdFE+AXpCLdFb1rshXG+Y= +cloud.google.com/go/trace v1.8.0/go.mod h1:zH7vcsbAhklH8hWFig58HvxcxyQbaIqMarMg9hn5ECA= +cloud.google.com/go/translate v1.3.0/go.mod h1:gzMUwRjvOqj5i69y/LYLd8RrNQk+hOmIXTi9+nb3Djs= +cloud.google.com/go/translate v1.4.0/go.mod h1:06Dn/ppvLD6WvA5Rhdp029IX2Mi3Mn7fpMRLPvXT5Wg= +cloud.google.com/go/translate v1.5.0/go.mod h1:29YDSYveqqpA1CQFD7NQuP49xymq17RXNaUDdc0mNu0= +cloud.google.com/go/video v1.8.0/go.mod h1:sTzKFc0bUSByE8Yoh8X0mn8bMymItVGPfTuUBUyRgxk= +cloud.google.com/go/video v1.9.0/go.mod h1:0RhNKFRF5v92f8dQt0yhaHrEuH95m068JYOvLZYnJSw= +cloud.google.com/go/video v1.12.0/go.mod h1:MLQew95eTuaNDEGriQdcYn0dTwf9oWiA4uYebxM5kdg= +cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU= +cloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4= +cloud.google.com/go/videointelligence v1.8.0/go.mod h1:dIcCn4gVDdS7yte/w+koiXn5dWVplOZkE+xwG9FgK+M= +cloud.google.com/go/videointelligence v1.9.0/go.mod h1:29lVRMPDYHikk3v8EdPSaL8Ku+eMzDljjuvRs105XoU= +cloud.google.com/go/videointelligence v1.10.0/go.mod h1:LHZngX1liVtUhZvi2uNS0VQuOzNi2TkY1OakiuoUOjU= +cloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0= +cloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo= +cloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiCRx0vSpZoUo= +cloud.google.com/go/vision/v2 v2.4.0/go.mod h1:VtI579ll9RpVTrdKdkMzckdnwMyX2JILb+MhPqRbPsY= +cloud.google.com/go/vision/v2 v2.5.0/go.mod h1:MmaezXOOE+IWa+cS7OhRRLK2cNv1ZL98zhqFFZaaH2E= +cloud.google.com/go/vision/v2 v2.6.0/go.mod h1:158Hes0MvOS9Z/bDMSFpjwsUrZ5fPrdwuyyvKSGAGMY= +cloud.google.com/go/vmmigration v1.2.0/go.mod h1:IRf0o7myyWFSmVR1ItrBSFLFD/rJkfDCUTO4vLlJvsE= +cloud.google.com/go/vmmigration v1.3.0/go.mod h1:oGJ6ZgGPQOFdjHuocGcLqX4lc98YQ7Ygq8YQwHh9A7g= +cloud.google.com/go/vmmigration v1.5.0/go.mod h1:E4YQ8q7/4W9gobHjQg4JJSgXXSgY21nA5r8swQV+Xxc= +cloud.google.com/go/vmwareengine v0.1.0/go.mod h1:RsdNEf/8UDvKllXhMz5J40XxDrNJNN4sagiox+OI208= +cloud.google.com/go/vmwareengine v0.2.2/go.mod h1:sKdctNJxb3KLZkE/6Oui94iw/xs9PRNC2wnNLXsHvH8= +cloud.google.com/go/vpcaccess v1.4.0/go.mod h1:aQHVbTWDYUR1EbTApSVvMq1EnT57ppDmQzZ3imqIk4w= +cloud.google.com/go/vpcaccess v1.5.0/go.mod h1:drmg4HLk9NkZpGfCmZ3Tz0Bwnm2+DKqViEpeEpOq0m8= +cloud.google.com/go/vpcaccess v1.6.0/go.mod h1:wX2ILaNhe7TlVa4vC5xce1bCnqE3AeH27RV31lnmZes= +cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE= +cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg= +cloud.google.com/go/webrisk v1.6.0/go.mod h1:65sW9V9rOosnc9ZY7A7jsy1zoHS5W9IAXv6dGqhMQMc= +cloud.google.com/go/webrisk v1.7.0/go.mod h1:mVMHgEYH0r337nmt1JyLthzMr6YxwN1aAIEc2fTcq7A= +cloud.google.com/go/webrisk v1.8.0/go.mod h1:oJPDuamzHXgUc+b8SiHRcVInZQuybnvEW72PqTc7sSg= +cloud.google.com/go/websecurityscanner v1.3.0/go.mod h1:uImdKm2wyeXQevQJXeh8Uun/Ym1VqworNDlBXQevGMo= +cloud.google.com/go/websecurityscanner v1.4.0/go.mod h1:ebit/Fp0a+FWu5j4JOmJEV8S8CzdTkAS77oDsiSqYWQ= +cloud.google.com/go/websecurityscanner v1.5.0/go.mod h1:Y6xdCPy81yi0SQnDY1xdNTNpfY1oAgXUlcfN3B3eSng= +cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0= +cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M= +cloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vfKf5Af+to4M= +cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA= +cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= +git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= +github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= +github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= +github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= +github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -73,11 +552,19 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -86,12 +573,30 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= +github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= +github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= +github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= +github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= +github.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= +github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= +github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= +github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= +github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= +github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -125,8 +630,10 @@ github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -141,10 +648,14 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= @@ -154,6 +665,7 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= @@ -161,37 +673,102 @@ github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= +github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= +github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= +github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k= +github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/googleapis/gax-go/v2 v2.1.1 h1:dp3bWCh+PPO1zjRRiCSczJav13sBvG4UhNyVTa1KqdU= github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= +github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= +github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= +github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= +github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= +github.com/googleapis/gax-go/v2 v2.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ= +github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= +github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= +github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= +github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= +github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= +github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= +github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= +github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= +github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= +github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -200,24 +777,47 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20220302094943-723b81ca9867/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -241,6 +841,10 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -271,14 +875,32 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -298,6 +920,16 @@ golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a h1:qfl7ob3DIEs3Ml9oLuPwY2N04gymzAW04WsUQHIClgM= golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= +golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s= +golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -310,6 +942,12 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -323,6 +961,7 @@ golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -342,11 +981,14 @@ golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -354,19 +996,41 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -377,12 +1041,22 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -395,6 +1069,7 @@ golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -423,20 +1098,37 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= +gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= +gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= +gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY= +gonum.org/v1/plot v0.10.1/go.mod h1:VZW5OlhkL1mysU9vaqNHnsy86inf6Ot+jB3r+BczCEo= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -473,6 +1165,29 @@ google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQ google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= google.golang.org/api v0.71.0 h1:SgWof18M8V2NylsX7bL4fM28j+nFdRopHZbdipaaw20= google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= +google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= +google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= +google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= +google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= +google.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g= +google.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= +google.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= +google.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI= +google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.99.0/go.mod h1:1YOf74vkVndF7pG6hIHuINsM7eWwpVTAfNMNiL91A08= +google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= +google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo= +google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0= +google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.107.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= +google.golang.org/api v0.111.0 h1:bwKi+z2BsdwYFRKrqwutM+axAlYLz83gt5pDSXCJT+0= +google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -516,10 +1231,13 @@ google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= @@ -550,6 +1268,64 @@ google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2 google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6 h1:FglFEfyj61zP3c6LgjmVHxYxZWXYul9oiS1EZqD5gLc= google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= +google.golang.org/genproto v0.0.0-20220329172620-7be39ac1afc7/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220722212130-b98a9ff5e252/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE= +google.golang.org/genproto v0.0.0-20220801145646-83ce21fca29f/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc= +google.golang.org/genproto v0.0.0-20220815135757-37a418bb8959/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220829144015-23454907ede3/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220913154956-18f8339a66a5/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220914142337-ca0e39ece12f/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220915135415-7fd63a7952de/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220919141832-68c03719ef51/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw= +google.golang.org/genproto v0.0.0-20220926165614-551eb538f295/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= +google.golang.org/genproto v0.0.0-20220926220553-6981cbe3cfce/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= +google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U= +google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= +google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= +google.golang.org/genproto v0.0.0-20221024153911-1573dae28c9c/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= +google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= +google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo= +google.golang.org/genproto v0.0.0-20221109142239-94d6d90a7d66/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221114212237-e4508ebdbee1/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221117204609-8f9c96812029/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221201204527-e3fa12d562f3/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd/go.mod h1:cTsE614GARnxrLsqKREzmNYJACSWWpAWdNMwnD7c2BE= +google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230112194545-e10362b5ecf9/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230113154510-dbe35b8444a5/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230123190316-2c411cf9d197/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230124163310-31e0e69b6fc2/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230125152338-dcaf20b6aeaa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230127162408-596548ed4efa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230216225411-c8e22ba71e44/go.mod h1:8B0gmkoRebU8ukX6HP+4wrVQUY1+6PkQ44BSyIlflHA= +google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= +google.golang.org/genproto v0.0.0-20230223222841-637eb2293923/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= +google.golang.org/genproto v0.0.0-20230301171018-9ab4bdc49ad5 h1:/cadn7taPtPlCgiWNetEPsle7jgnlad2R7gR5MXB6dM= +google.golang.org/genproto v0.0.0-20230301171018-9ab4bdc49ad5/go.mod h1:TvhZT5f700eVlTNwND1xoEZQeWTB2RY/65kplwl/bFA= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -576,9 +1352,20 @@ google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M= google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= +google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc= +google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -594,12 +1381,17 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -607,6 +1399,42 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= +lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= +modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= +modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= +modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc= +modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw= +modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= +modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= +modernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws= +modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo= +modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= +modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= +modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= +modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A= +modernc.org/libc v1.16.1/go.mod h1:JjJE0eu4yeK7tab2n4S1w8tlWd9MxXLRzheaRnAKymU= +modernc.org/libc v1.16.17/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU= +modernc.org/libc v1.16.19/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= +modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0= +modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s= +modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= +modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= +modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4= +modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= +modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= +modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw= +modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/tools/tfeditor/go.mod b/tools/tfeditor/go.mod index d1a4a41e..29b25f50 100644 --- a/tools/tfeditor/go.mod +++ b/tools/tfeditor/go.mod @@ -3,6 +3,10 @@ module github.com/GoogleCloudPlatform/cloud-foundation-fabric/tools/tfeditor go 1.16 require ( - github.com/hashicorp/hcl/v2 v2.12.0 // indirect - github.com/zclconf/go-cty v1.8.0 // indirect + github.com/agext/levenshtein v1.2.3 // indirect + github.com/google/go-cmp v0.5.9 // indirect + github.com/hashicorp/hcl/v2 v2.16.1 + github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/zclconf/go-cty v1.13.0 + golang.org/x/text v0.7.0 // indirect ) diff --git a/tools/tfeditor/go.sum b/tools/tfeditor/go.sum index 1ed29912..cc5a958d 100644 --- a/tools/tfeditor/go.sum +++ b/tools/tfeditor/go.sum @@ -1,10 +1,13 @@ github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8= github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= +github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= github.com/apparentlymart/go-textseg v1.0.0 h1:rRmlIsPEEhUTIKQb7T++Nz/A5Q6C9IuX2wFoYVvnCs0= github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -12,40 +15,81 @@ github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/hashicorp/hcl/v2 v2.12.0 h1:PsYxySWpMD4KPaoJLnsHwtK5Qptvj/4Q6s0t4sUxZf4= github.com/hashicorp/hcl/v2 v2.12.0/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg= +github.com/hashicorp/hcl/v2 v2.16.1 h1:BwuxEMD/tsYgbhIW7UuI3crjovf3MzuFWiVgiv57iHg= +github.com/hashicorp/hcl/v2 v2.16.1/go.mod h1:JRmR89jycNkrrqnMmvPDMd56n1rQJ2Q6KocSLCMCXng= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM= github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= github.com/zclconf/go-cty v1.8.0 h1:s4AvqaeQzJIu3ndv4gVIhplVD0krU+bgrcLSVUnaWuA= github.com/zclconf/go-cty v1.8.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= +github.com/zclconf/go-cty v1.12.1/go.mod h1:s9IfD1LK5ccNMSWCVFCE2rJfHiZgi7JijgeWIMfhLvA= +github.com/zclconf/go-cty v1.13.0 h1:It5dfKTTZHe9aeppbNOda3mN7Ag7sg6QkBNm6TkyFa0= +github.com/zclconf/go-cty v1.13.0/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0= github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220517005047-85d78b3ac167/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 2217abe5f07c2b43fb0899ab389ace3e0a8a24e2 Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Fri, 3 Mar 2023 09:24:41 +0100 Subject: [PATCH 22/49] Allow preventing creation of billing IAM roles in FAST, add instructions on delayed billing association (#1207) * stage 0 * resman and networking stages * tfdoc * security stage --- fast/stages/0-bootstrap/README.md | 55 +++++++++++++------ fast/stages/0-bootstrap/billing.tf | 13 +++-- fast/stages/0-bootstrap/organization.tf | 4 +- fast/stages/0-bootstrap/variables.tf | 8 +-- fast/stages/1-resman/README.md | 39 ++++++++----- fast/stages/1-resman/billing.tf | 9 ++- fast/stages/1-resman/organization.tf | 2 +- fast/stages/1-resman/variables.tf | 8 +-- fast/stages/2-networking-a-peering/README.md | 14 +++++ fast/stages/2-networking-b-vpn/README.md | 14 +++++ fast/stages/2-networking-c-nva/README.md | 14 +++++ .../2-networking-d-separate-envs/README.md | 14 +++++ fast/stages/2-security/README.md | 14 +++++ 13 files changed, 158 insertions(+), 50 deletions(-) diff --git a/fast/stages/0-bootstrap/README.md b/fast/stages/0-bootstrap/README.md index 88bdceb3..20ed998f 100644 --- a/fast/stages/0-bootstrap/README.md +++ b/fast/stages/0-bootstrap/README.md @@ -69,8 +69,8 @@ One other design choice worth mentioning here is using a single automation proje We support three use cases in regards to billing: - the billing account is part of this same organization, IAM bindings will be set at the organization level -- the billing account is part of a different organization, billing IAM bindings will be set at the organization level in the billing account owning organization - the billing account is not considered part of an organization (even though it might be), billing IAM bindings are set on the billing account itself +- billing IAM is managed separately, and no bindings should (or can) be set via Terraform, this requires a few extra steps and is definitely not recommended and mainly used for development purposes For same-organization billing, we configure a custom organization role that can set IAM bindings, via a delegated role grant to limit its scope to the relevant roles. @@ -171,6 +171,18 @@ gcloud beta billing accounts add-iam-policy-binding $FAST_BILLING_ACCOUNT_ID \ --member user:$FAST_BU --role roles/billing.admin ``` +#### Preventing creation of billing-related IAM bindings + +This configuration is possible but unsupported and only present for development purposes, use at your own risk: + +- configure `billing_account.id` as `null` and `billing.no_iam` to `true` in your `tfvars` file +- apply with `terraform apply -target 'module.automation-project.google_project.project[0]'` in addition to the initial user variable +- once Terraform raises an error run `terraform untaint 'module.automation-project.google_project.project[0]'` +- repeat the two steps above for `'module.log-export-project.google_project.project[0]'` +- go through the process to associate the billing account with the two projects +- configure `billing_account.id` with the real billing account id +- resume applying normally + #### Groups Before the first run, the following IAM groups must exist to allow IAM bindings to be created (actual names are flexible, see the [Customization](#customizations) section): @@ -469,7 +481,16 @@ The remaining configuration is manual, as it regards the repositories themselves | name | description | modules | resources | |---|---|---|---| | [automation.tf](./automation.tf) | Automation project and resources. | gcs · iam-service-account · project | | -| [billing.tf](./billing.tf) | Billing export project and dataset. | bigquery-dataset · project | google_billing_account_iam_member | +| [billing.tf](./billing.tf) | Billing export project and dataset. | bigquery-dataset · project | + ) +} + +# billing account in same org (IAM is in the organization.tf file) + +module · ? local.billing_ext_admins : [] + ) + billing_account_id = var.billing_account.id + role = · google_billing_account_iam_member | | [cicd.tf](./cicd.tf) | Workload Identity Federation configurations for CI/CD. | iam-service-account · source-repository | | | [identity-providers.tf](./identity-providers.tf) | Workload Identity Federation provider definitions. | | google_iam_workload_identity_pool · google_iam_workload_identity_pool_provider | | [log-export.tf](./log-export.tf) | Audit log project and sink. | bigquery-dataset · gcs · logging-bucket · project · pubsub | | @@ -484,21 +505,21 @@ The remaining configuration is manual, as it regards the repositories themselves | name | description | type | required | default | producer | |---|---|:---:|:---:|:---:|:---:| -| [billing_account](variables.tf#L17) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | object({…}) | ✓ | | | -| [organization](variables.tf#L196) | Organization details. | object({…}) | ✓ | | | -| [prefix](variables.tf#L211) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | | -| [bootstrap_user](variables.tf#L29) | Email of the nominal user running this stage for the first time. | string | | null | | -| [cicd_repositories](variables.tf#L35) | CI/CD repository configuration. Identity providers reference keys in the `federated_identity_providers` variable. Set to null to disable, or set individual repositories to null if not needed. | object({…}) | | null | | -| [custom_role_names](variables.tf#L81) | Names of custom roles defined at the org level. | object({…}) | | {…} | | -| [fast_features](variables.tf#L95) | Selective control for top-level FAST features. | object({…}) | | {} | | -| [federated_identity_providers](variables.tf#L108) | Workload Identity Federation pools. The `cicd_repositories` variable references keys here. | map(object({…})) | | {} | | -| [groups](variables.tf#L122) | Group names to grant organization-level permissions. | map(string) | | {…} | | -| [iam](variables.tf#L140) | Organization-level custom IAM settings in role => [principal] format. | map(list(string)) | | {} | | -| [iam_additive](variables.tf#L146) | Organization-level custom IAM settings in role => [principal] format for non-authoritative bindings. | map(list(string)) | | {} | | -| [locations](variables.tf#L152) | Optional locations for GCS, BigQuery, and logging buckets created here. | object({…}) | | {…} | | -| [log_sinks](variables.tf#L171) | Org-level log sinks, in name => {type, filter} format. | map(object({…})) | | {…} | | -| [outputs_location](variables.tf#L205) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | string | | null | | -| [project_parent_ids](variables.tf#L221) | Optional parents for projects created here in folders/nnnnnnn format. Null values will use the organization as parent. | object({…}) | | {…} | | +| [billing_account](variables.tf#L17) | Billing account id. If billing account is not part of the same org set `is_org_level` to `false`. To disable handling of billing IAM roles set `no_iam` to `true`. | object({…}) | ✓ | | | +| [organization](variables.tf#L194) | Organization details. | object({…}) | ✓ | | | +| [prefix](variables.tf#L209) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | | +| [bootstrap_user](variables.tf#L27) | Email of the nominal user running this stage for the first time. | string | | null | | +| [cicd_repositories](variables.tf#L33) | CI/CD repository configuration. Identity providers reference keys in the `federated_identity_providers` variable. Set to null to disable, or set individual repositories to null if not needed. | object({…}) | | null | | +| [custom_role_names](variables.tf#L79) | Names of custom roles defined at the org level. | object({…}) | | {…} | | +| [fast_features](variables.tf#L93) | Selective control for top-level FAST features. | object({…}) | | {} | | +| [federated_identity_providers](variables.tf#L106) | Workload Identity Federation pools. The `cicd_repositories` variable references keys here. | map(object({…})) | | {} | | +| [groups](variables.tf#L120) | Group names to grant organization-level permissions. | map(string) | | {…} | | +| [iam](variables.tf#L138) | Organization-level custom IAM settings in role => [principal] format. | map(list(string)) | | {} | | +| [iam_additive](variables.tf#L144) | Organization-level custom IAM settings in role => [principal] format for non-authoritative bindings. | map(list(string)) | | {} | | +| [locations](variables.tf#L150) | Optional locations for GCS, BigQuery, and logging buckets created here. | object({…}) | | {…} | | +| [log_sinks](variables.tf#L169) | Org-level log sinks, in name => {type, filter} format. | map(object({…})) | | {…} | | +| [outputs_location](variables.tf#L203) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | string | | null | | +| [project_parent_ids](variables.tf#L219) | Optional parents for projects created here in folders/nnnnnnn format. Null values will use the organization as parent. | object({…}) | | {…} | | ## Outputs diff --git a/fast/stages/0-bootstrap/billing.tf b/fast/stages/0-bootstrap/billing.tf index aee033bd..203ecbe8 100644 --- a/fast/stages/0-bootstrap/billing.tf +++ b/fast/stages/0-bootstrap/billing.tf @@ -24,13 +24,18 @@ locals { module.automation-tf-bootstrap-sa.iam_email, module.automation-tf-resman-sa.iam_email ] + billing_mode = ( + var.billing_account.no_iam + ? null + : var.billing_account.is_org_level ? "org" : "resource" + ) } # billing account in same org (IAM is in the organization.tf file) module "billing-export-project" { source = "../../../modules/project" - count = var.billing_account.is_org_level ? 1 : 0 + count = local.billing_mode == "org" ? 1 : 0 billing_account = var.billing_account.id name = "billing-exp-0" parent = coalesce( @@ -52,7 +57,7 @@ module "billing-export-project" { module "billing-export-dataset" { source = "../../../modules/bigquery-dataset" - count = var.billing_account.is_org_level ? 1 : 0 + count = local.billing_mode == "org" ? 1 : 0 project_id = module.billing-export-project.0.project_id id = "billing_export" friendly_name = "Billing export." @@ -63,7 +68,7 @@ module "billing-export-dataset" { resource "google_billing_account_iam_member" "billing_ext_admin" { for_each = toset( - !var.billing_account.is_org_level ? local.billing_ext_admins : [] + local.billing_mode == "resource" ? local.billing_ext_admins : [] ) billing_account_id = var.billing_account.id role = "roles/billing.admin" @@ -72,7 +77,7 @@ resource "google_billing_account_iam_member" "billing_ext_admin" { resource "google_billing_account_iam_member" "billing_ext_cost_manager" { for_each = toset( - !var.billing_account.is_org_level ? local.billing_ext_admins : [] + local.billing_mode == "resource" ? local.billing_ext_admins : [] ) billing_account_id = var.billing_account.id role = "roles/billing.costsManager" diff --git a/fast/stages/0-bootstrap/organization.tf b/fast/stages/0-bootstrap/organization.tf index 154b37fe..d75a25f2 100644 --- a/fast/stages/0-bootstrap/organization.tf +++ b/fast/stages/0-bootstrap/organization.tf @@ -85,7 +85,7 @@ locals { # "domain:${var.organization.domain}" # ] }, - var.billing_account.is_org_level ? { + local.billing_mode == "org" ? { "roles/billing.admin" = [ local.groups_iam.gcp-billing-admins, local.groups_iam.gcp-organization-admins, @@ -222,7 +222,7 @@ resource "google_organization_iam_binding" "org_admin_delegated" { "roles/resourcemanager.organizationViewer", module.organization.custom_role_id[var.custom_role_names.tenant_network_admin] ], - var.billing_account.is_org_level ? [ + local.billing_mode == "org" ? [ "roles/billing.admin", "roles/billing.costsManager", "roles/billing.user", diff --git a/fast/stages/0-bootstrap/variables.tf b/fast/stages/0-bootstrap/variables.tf index 4ab90deb..7aec437c 100644 --- a/fast/stages/0-bootstrap/variables.tf +++ b/fast/stages/0-bootstrap/variables.tf @@ -15,15 +15,13 @@ */ variable "billing_account" { - description = "Billing account id. If billing account is not part of the same org set `is_org_level` to false." + description = "Billing account id. If billing account is not part of the same org set `is_org_level` to `false`. To disable handling of billing IAM roles set `no_iam` to `true`." type = object({ id = string is_org_level = optional(bool, true) + no_iam = optional(bool, false) }) - validation { - condition = var.billing_account.is_org_level != null - error_message = "Invalid `null` value for `billing_account.is_org_level`." - } + nullable = false } variable "bootstrap_user" { diff --git a/fast/stages/1-resman/README.md b/fast/stages/1-resman/README.md index 971c6963..781dc14d 100644 --- a/fast/stages/1-resman/README.md +++ b/fast/stages/1-resman/README.md @@ -174,7 +174,18 @@ Due to its simplicity, this stage lends itself easily to customizations: adding | name | description | modules | resources | |---|---|---|---| -| [billing.tf](./billing.tf) | Billing resources for external billing use cases. | | google_billing_account_iam_member | +| [billing.tf](./billing.tf) | Billing resources for external billing use cases. | | + ) +} + +# billing account in same org (resources is in the organization.tf file) + +# standalone billing account + +resource · ? local.billing_ext_users : [] + ) + billing_account_id = var.billing_account.id + role = · google_billing_account_iam_member | | [branch-data-platform.tf](./branch-data-platform.tf) | Data Platform stages resources. | folder · gcs · iam-service-account | google_organization_iam_member | | [branch-gke.tf](./branch-gke.tf) | GKE multitenant stage resources. | folder · gcs · iam-service-account | | | [branch-networking.tf](./branch-networking.tf) | Networking stage resources. | folder · gcs · iam-service-account | | @@ -199,19 +210,19 @@ Due to its simplicity, this stage lends itself easily to customizations: adding | name | description | type | required | default | producer | |---|---|:---:|:---:|:---:|:---:| | [automation](variables.tf#L20) | Automation resources created by the bootstrap stage. | object({…}) | ✓ | | 0-bootstrap | -| [billing_account](variables.tf#L38) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | object({…}) | ✓ | | 0-bootstrap | -| [organization](variables.tf#L193) | Organization details. | object({…}) | ✓ | | 0-bootstrap | -| [prefix](variables.tf#L217) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 0-bootstrap | -| [cicd_repositories](variables.tf#L51) | CI/CD repository configuration. Identity providers reference keys in the `automation.federated_identity_providers` variable. Set to null to disable, or set individual repositories to null if not needed. | object({…}) | | null | | -| [custom_roles](variables.tf#L133) | Custom roles defined at the org level, in key => id format. | object({…}) | | null | 0-bootstrap | -| [data_dir](variables.tf#L142) | Relative path for the folder storing configuration data. | string | | "data" | | -| [fast_features](variables.tf#L148) | Selective control for top-level FAST features. | object({…}) | | {} | 0-0-bootstrap | -| [groups](variables.tf#L162) | Group names to grant organization-level permissions. | object({…}) | | {} | 0-bootstrap | -| [locations](variables.tf#L175) | Optional locations for GCS, BigQuery, and logging buckets created here. | object({…}) | | {…} | 0-bootstrap | -| [organization_policy_configs](variables.tf#L203) | Organization policies customization. | object({…}) | | null | | -| [outputs_location](variables.tf#L211) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | string | | null | | -| [tag_names](variables.tf#L228) | Customized names for resource management tags. | object({…}) | | {…} | | -| [team_folders](variables.tf#L247) | Team folders to be created. Format is described in a code comment. | map(object({…})) | | null | | +| [billing_account](variables.tf#L38) | Billing account id. If billing account is not part of the same org set `is_org_level` to `false`. To disable handling of billing IAM roles set `no_iam` to `true`. | object({…}) | ✓ | | 0-bootstrap | +| [organization](variables.tf#L191) | Organization details. | object({…}) | ✓ | | 0-bootstrap | +| [prefix](variables.tf#L215) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 0-bootstrap | +| [cicd_repositories](variables.tf#L49) | CI/CD repository configuration. Identity providers reference keys in the `automation.federated_identity_providers` variable. Set to null to disable, or set individual repositories to null if not needed. | object({…}) | | null | | +| [custom_roles](variables.tf#L131) | Custom roles defined at the org level, in key => id format. | object({…}) | | null | 0-bootstrap | +| [data_dir](variables.tf#L140) | Relative path for the folder storing configuration data. | string | | "data" | | +| [fast_features](variables.tf#L146) | Selective control for top-level FAST features. | object({…}) | | {} | 0-0-bootstrap | +| [groups](variables.tf#L160) | Group names to grant organization-level permissions. | object({…}) | | {} | 0-bootstrap | +| [locations](variables.tf#L173) | Optional locations for GCS, BigQuery, and logging buckets created here. | object({…}) | | {…} | 0-bootstrap | +| [organization_policy_configs](variables.tf#L201) | Organization policies customization. | object({…}) | | null | | +| [outputs_location](variables.tf#L209) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | string | | null | | +| [tag_names](variables.tf#L226) | Customized names for resource management tags. | object({…}) | | {…} | | +| [team_folders](variables.tf#L245) | Team folders to be created. Format is described in a code comment. | map(object({…})) | | null | | ## Outputs diff --git a/fast/stages/1-resman/billing.tf b/fast/stages/1-resman/billing.tf index ba20ab05..c1870842 100644 --- a/fast/stages/1-resman/billing.tf +++ b/fast/stages/1-resman/billing.tf @@ -30,6 +30,11 @@ locals { local.branch_optional_sa_lists.pf-dev, local.branch_optional_sa_lists.pf-prod, ) + billing_mode = ( + var.billing_account.no_iam + ? null + : var.billing_account.is_org_level ? "org" : "resource" + ) } # billing account in same org (resources is in the organization.tf file) @@ -38,7 +43,7 @@ locals { resource "google_billing_account_iam_member" "billing_ext_admin" { for_each = toset( - !var.billing_account.is_org_level ? local.billing_ext_users : [] + local.billing_mode == "resource" ? local.billing_ext_users : [] ) billing_account_id = var.billing_account.id role = "roles/billing.user" @@ -47,7 +52,7 @@ resource "google_billing_account_iam_member" "billing_ext_admin" { resource "google_billing_account_iam_member" "billing_ext_costsmanager" { for_each = toset( - !var.billing_account.is_org_level ? local.billing_ext_users : [] + local.billing_mode == "resource" ? local.billing_ext_users : [] ) billing_account_id = var.billing_account.id role = "roles/billing.costsManager" diff --git a/fast/stages/1-resman/organization.tf b/fast/stages/1-resman/organization.tf index a3bc2f0d..4d5e3fa5 100644 --- a/fast/stages/1-resman/organization.tf +++ b/fast/stages/1-resman/organization.tf @@ -46,7 +46,7 @@ module "organization" { module.branch-network-sa.iam_email ] }, - var.billing_account.is_org_level ? { + local.billing_mode == "org" ? { "roles/billing.costsManager" = concat( local.branch_optional_sa_lists.pf-dev, local.branch_optional_sa_lists.pf-prod diff --git a/fast/stages/1-resman/variables.tf b/fast/stages/1-resman/variables.tf index 16c65ee8..f2e413c9 100644 --- a/fast/stages/1-resman/variables.tf +++ b/fast/stages/1-resman/variables.tf @@ -37,15 +37,13 @@ variable "automation" { variable "billing_account" { # tfdoc:variable:source 0-bootstrap - description = "Billing account id. If billing account is not part of the same org set `is_org_level` to false." + description = "Billing account id. If billing account is not part of the same org set `is_org_level` to `false`. To disable handling of billing IAM roles set `no_iam` to `true`." type = object({ id = string is_org_level = optional(bool, true) + no_iam = optional(bool, false) }) - validation { - condition = var.billing_account.is_org_level != null - error_message = "Invalid `null` value for `billing_account.is_org_level`." - } + nullable = false } variable "cicd_repositories" { diff --git a/fast/stages/2-networking-a-peering/README.md b/fast/stages/2-networking-a-peering/README.md index c066423c..ac282c8d 100644 --- a/fast/stages/2-networking-a-peering/README.md +++ b/fast/stages/2-networking-a-peering/README.md @@ -250,6 +250,20 @@ Variables in this stage -- like most other FAST stages -- are broadly divided in The latter set is explained in the [Customization](#customizations) sections below, and the full list can be found in the [Variables](#variables) table at the bottom of this document. +### Using delayed billing association for projects + +This configuration is possible but unsupported and only exists for development purposes, use at your own risk: + +- temporarily switch `billing_account.id` to `null` in `globals.auto.tfvars.json` +- for each project resources in the project modules used in this stage (`dev-spoke-project`, `landing-project`, `prod-spoke-project`) + - apply using `-target`, for example + `terraform apply -target 'module.landing-project.google_project.project[0]'` + - untaint the project resource after applying, for example + `terraform untaint 'module.landing-project.google_project.project[0]'` +- go through the process to associate the billing account with the two projects +- switch `billing_account.id` back to the real billing account id +- resume applying normally + ### Running the stage Once provider and variable values are in place and the correct user is configured, the stage can be run: diff --git a/fast/stages/2-networking-b-vpn/README.md b/fast/stages/2-networking-b-vpn/README.md index 7a8983d8..a34b4135 100644 --- a/fast/stages/2-networking-b-vpn/README.md +++ b/fast/stages/2-networking-b-vpn/README.md @@ -264,6 +264,20 @@ Variables in this stage -- like most other FAST stages -- are broadly divided in The latter set is explained in the [Customization](#customizations) sections below, and the full list can be found in the [Variables](#variables) table at the bottom of this document. +### Using delayed billing association for projects + +This configuration is possible but unsupported and only exists for development purposes, use at your own risk: + +- temporarily switch `billing_account.id` to `null` in `globals.auto.tfvars.json` +- for each project resources in the project modules used in this stage (`dev-spoke-project`, `landing-project`, `prod-spoke-project`) + - apply using `-target`, for example + `terraform apply -target 'module.landing-project.google_project.project[0]'` + - untaint the project resource after applying, for example + `terraform untaint 'module.landing-project.google_project.project[0]'` +- go through the process to associate the billing account with the two projects +- switch `billing_account.id` back to the real billing account id +- resume applying normally + ### Running the stage Once provider and variable values are in place and the correct user is configured, the stage can be run: diff --git a/fast/stages/2-networking-c-nva/README.md b/fast/stages/2-networking-c-nva/README.md index d0e62fd6..33501984 100644 --- a/fast/stages/2-networking-c-nva/README.md +++ b/fast/stages/2-networking-c-nva/README.md @@ -323,6 +323,20 @@ Variables in this stage -- like most other FAST stages -- are broadly divided in The latter set is explained in the [Customization](#customizations) sections below, and the full list can be found in the [Variables](#variables) table at the bottom of this document. +### Using delayed billing association for projects + +This configuration is possible but unsupported and only exists for development purposes, use at your own risk: + +- temporarily switch `billing_account.id` to `null` in `globals.auto.tfvars.json` +- for each project resources in the project modules used in this stage (`dev-spoke-project`, `landing-project`, `prod-spoke-project`) + - apply using `-target`, for example + `terraform apply -target 'module.landing-project.google_project.project[0]'` + - untaint the project resource after applying, for example + `terraform untaint 'module.landing-project.google_project.project[0]'` +- go through the process to associate the billing account with the two projects +- switch `billing_account.id` back to the real billing account id +- resume applying normally + ### Running the stage Once provider and variable values are in place and the correct user is configured, the stage can be run: diff --git a/fast/stages/2-networking-d-separate-envs/README.md b/fast/stages/2-networking-d-separate-envs/README.md index dfc199cd..3a3b70e7 100644 --- a/fast/stages/2-networking-d-separate-envs/README.md +++ b/fast/stages/2-networking-d-separate-envs/README.md @@ -212,6 +212,20 @@ Variables in this stage -- like most other FAST stages -- are broadly divided in The latter set is explained in the [Customization](#customizations) sections below, and the full list can be found in the [Variables](#variables) table at the bottom of this document. +### Using delayed billing association for projects + +This configuration is possible but unsupported and only exists for development purposes, use at your own risk: + +- temporarily switch `billing_account.id` to `null` in `globals.auto.tfvars.json` +- for each project resources in the project modules used in this stage (`dev-spoke-project`, `prod-spoke-project`) + - apply using `-target`, for example + `terraform apply -target 'module.prod-spoke-project.google_project.project[0]'` + - untaint the project resource after applying, for example + `terraform untaint 'module.prod-spoke-project.google_project.project[0]'` +- go through the process to associate the billing account with the two projects +- switch `billing_account.id` back to the real billing account id +- resume applying normally + ### Running the stage Once provider and variable values are in place and the correct user is configured, the stage can be run: diff --git a/fast/stages/2-security/README.md b/fast/stages/2-security/README.md index 6486cd74..ced74136 100644 --- a/fast/stages/2-security/README.md +++ b/fast/stages/2-security/README.md @@ -108,6 +108,20 @@ Variables in this stage -- like most other FAST stages -- are broadly divided in The latter set is explained in the [Customization](#customizations) sections below, and the full list can be found in the [Variables](#variables) table at the bottom of this document. +### Using delayed billing association for projects + +This configuration is possible but unsupported and only exists for development purposes, use at your own risk: + +- temporarily switch `billing_account.id` to `null` in `globals.auto.tfvars.json` +- for each project resources in the project modules used in this stage (`dev-sec-project`, `prod-sec-project`) + - apply using `-target`, for example + `terraform apply -target 'module.prod-sec-project.google_project.project[0]'` + - untaint the project resource after applying, for example + `terraform untaint 'module.prod-sec-project.google_project.project[0]'` +- go through the process to associate the billing account with the two projects +- switch `billing_account.id` back to the real billing account id +- resume applying normally + ### Running the stage Once provider and variable values are in place and the correct user is configured, the stage can be run: From 32808f93ea14034980f2d061806050a4bdfcf0cd Mon Sep 17 00:00:00 2001 From: lcaggio Date: Fri, 3 Mar 2023 14:52:33 +0100 Subject: [PATCH 23/49] Update README. --- blueprints/data-solutions/bq-ml/README.md | 64 +++++++++++++++++++- blueprints/data-solutions/bq-ml/diagram.png | Bin 0 -> 57434 bytes 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 blueprints/data-solutions/bq-ml/diagram.png diff --git a/blueprints/data-solutions/bq-ml/README.md b/blueprints/data-solutions/bq-ml/README.md index 42e4832c..b6f2bd2b 100644 --- a/blueprints/data-solutions/bq-ml/README.md +++ b/blueprints/data-solutions/bq-ml/README.md @@ -1,6 +1,68 @@ # BQ ML and Vertex Pipeline -This blueprint creates #TODO +This blueprint creates the infrastructure needed to deploy and run a Vertex AI environment to develop and deploy a machine learning model to be used from Vertex AI an endpoint or in BigQuery. + +This is the high level diagram: + +![High-level diagram](diagram.png "High-level diagram") + +It also includes the IAM wiring needed to make such scenarios work. Regional resources are used in this example, but the same logic will apply for 'dual regional', 'multi regional' or 'global' resources. + +The example is designed to match real-world use cases with a minimum amount of resources, and be used as a starting point for your scenario. + +## Managed resources and services + +This sample creates several distinct groups of resources: + +- Networking + - VPC network + - Subnet + - Firewall rules for SSH access via IAP and open communication within the VPC + - Cloud Nat +- IAM + - Vertex AI workbench service account + - Vertex AI pipeline service account +- Storage + - GCS bucket + - Bigquery dataset + +## Customization + +### Virtual Private Cloud (VPC) design + +As is often the case in real-world configurations, this blueprint accepts as input an existing Shared-VPC via the `network_config` variable. + +### Customer Managed Encryption Key + +As is often the case in real-world configurations, this blueprint accepts as input existing Cloud KMS keys to encrypt resources via the `service_encryption_keys` variable. + +## Demo + +In the repository `demo` folder you can find an example on how to create a Vertex AI pipeline from a publically available dataset and deploy the model to be used from a Vertex AI managed endpoint or from within Bigquery. + +## Variables + +| name | description | type | required | default | +|---|---|:---:|:---:|:---:| +| [prefix](variables.tf#L32) | Prefix used for resource names. | string | ✓ | | +| [project_id](variables.tf#L50) | Project id, references existing project if `project_create` is null. | string | ✓ | | +| [location](variables.tf#L16) | The location where resources will be deployed. | string | | "EU" | +| [network_config](variables.tf#L22) | Shared VPC network configurations to use. If null networks will be created in projects with preconfigured values. | object({…}) | | null | +| [project_create](variables.tf#L41) | Provide values if project creation is needed, uses existing project if null. Parent format: folders/folder_id or organizations/org_id. | object({…}) | | null | +| [region](variables.tf#L55) | The region where resources will be deployed. | string | | "europe-west1" | + +## Outputs + +| name | description | sensitive | +|---|---|:---:| +| [bucket](outputs.tf#L15) | GCS Bucket URL. | | +| [dataset](outputs.tf#L20) | GCS Bucket URL. | | +| [notebook](outputs.tf#L25) | Vertex AI notebook details. | | +| [project](outputs.tf#L33) | Project id. | | +| [service-account-vertex](outputs.tf#L43) | Service account to be used for Vertex AI pipelines | | +| [vertex-ai-metadata-store](outputs.tf#L48) | | | +| [vpc](outputs.tf#L38) | VPC Network. | | + diff --git a/blueprints/data-solutions/bq-ml/diagram.png b/blueprints/data-solutions/bq-ml/diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..1effbebe280f678442884a58e2dd85828f8d096a GIT binary patch literal 57434 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 Date: Fri, 3 Mar 2023 15:08:57 +0100 Subject: [PATCH 24/49] Update --- blueprints/data-solutions/bq-ml/README.md | 15 +++++++++++++++ .../bq-ml/demo/sql/explain_predict.sql | 18 +++++++++++++++++- .../data-solutions/bq-ml/demo/sql/features.sql | 18 +++++++++++++++++- .../data-solutions/bq-ml/demo/sql/train.sql | 18 +++++++++++++++++- blueprints/data-solutions/bq-ml/main.tf | 6 ++++-- blueprints/data-solutions/bq-ml/outputs.tf | 2 ++ blueprints/data-solutions/bq-ml/variables.tf | 1 + blueprints/data-solutions/bq-ml/versions.tf | 2 ++ 8 files changed, 75 insertions(+), 5 deletions(-) diff --git a/blueprints/data-solutions/bq-ml/README.md b/blueprints/data-solutions/bq-ml/README.md index b6f2bd2b..407ce146 100644 --- a/blueprints/data-solutions/bq-ml/README.md +++ b/blueprints/data-solutions/bq-ml/README.md @@ -66,3 +66,18 @@ In the repository `demo` folder you can find an example on how to create a Verte | [vpc](outputs.tf#L38) | VPC Network. | | + +## Test + +```hcl +module "test" { + source = "./fabric/blueprints/data-solutions/bq-ml/" + project_create = { + billing_account_id = "123456-123456-123456" + parent = "folders/12345678" + } + project_id = "project-1" + prefix = "prefix" +} +# tftest modules=9 resources=46 +``` diff --git a/blueprints/data-solutions/bq-ml/demo/sql/explain_predict.sql b/blueprints/data-solutions/bq-ml/demo/sql/explain_predict.sql index 0d67bc7c..7d36e038 100644 --- a/blueprints/data-solutions/bq-ml/demo/sql/explain_predict.sql +++ b/blueprints/data-solutions/bq-ml/demo/sql/explain_predict.sql @@ -1,3 +1,19 @@ +/* +* 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. +*/ + select * from ML.EXPLAIN_PREDICT(MODEL `{project-id}.{dataset}.{model-name}`, (select * except (session_id, session_starting_ts, user_id, has_purchased) @@ -5,4 +21,4 @@ from ML.EXPLAIN_PREDICT(MODEL `{project-id}.{dataset}.{model-name}`, where extract(ISOYEAR from session_starting_ts) = 2023 ), STRUCT(5 AS top_k_features, 0.5 as threshold) -) \ No newline at end of file +) diff --git a/blueprints/data-solutions/bq-ml/demo/sql/features.sql b/blueprints/data-solutions/bq-ml/demo/sql/features.sql index 63b79d82..2b55185f 100644 --- a/blueprints/data-solutions/bq-ml/demo/sql/features.sql +++ b/blueprints/data-solutions/bq-ml/demo/sql/features.sql @@ -1,3 +1,19 @@ +/* +* 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. +*/ + CREATE view if not exists `{project_id}.{dataset}.ecommerce_abt` as with abt as ( @@ -18,4 +34,4 @@ select abt.*, case when extract(DAYOFWEEK from session_starting_ts) in (1,7) the , (select count(distinct uo.order_id) from unnest(user_orders) uo where uo.order_creations_ts < session_starting_ts and status in ('Cancelled', 'Returned') ) as number_of_unsuccessful_orders from abt left join previous_orders pso - on abt.user_id = pso.user_id \ No newline at end of file + on abt.user_id = pso.user_id diff --git a/blueprints/data-solutions/bq-ml/demo/sql/train.sql b/blueprints/data-solutions/bq-ml/demo/sql/train.sql index 0f5517b8..597623d0 100644 --- a/blueprints/data-solutions/bq-ml/demo/sql/train.sql +++ b/blueprints/data-solutions/bq-ml/demo/sql/train.sql @@ -1,3 +1,19 @@ +/* +* 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. +*/ + create or replace model `{project_id}.{dataset}.{model_name}` OPTIONS(model_type='{model_type}', input_label_cols=['has_purchased'], @@ -8,4 +24,4 @@ OPTIONS(model_type='{model_type}', ) as select * except (session_id, session_starting_ts, user_id) from `{project_id}.{dataset}.ecommerce_abt_table` -where extract(ISOYEAR from session_starting_ts) = 2022 \ No newline at end of file +where extract(ISOYEAR from session_starting_ts) = 2022 diff --git a/blueprints/data-solutions/bq-ml/main.tf b/blueprints/data-solutions/bq-ml/main.tf index 77ae55ea..e91c7424 100644 --- a/blueprints/data-solutions/bq-ml/main.tf +++ b/blueprints/data-solutions/bq-ml/main.tf @@ -12,9 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. +# tfdoc:file:description Core resources. + ############################################################################### # Project # ############################################################################### + locals { service_encryption_keys = var.service_encryption_keys shared_vpc_project = try(var.network_config.host_project, null) @@ -160,7 +163,7 @@ module "dataset" { project_id = module.project.project_id id = "${replace(var.prefix, "-", "_")}_data" encryption_key = try(local.service_encryption_keys.bq, null) # Example assignment of an encryption key - location = "US" + location = "US" } ############################################################################### @@ -239,7 +242,6 @@ resource "google_notebooks_instance" "playground" { service_account = module.service-account-notebook.email # Enable Secure Boot - shielded_instance_config { enable_secure_boot = true } diff --git a/blueprints/data-solutions/bq-ml/outputs.tf b/blueprints/data-solutions/bq-ml/outputs.tf index 2b62074b..1a39e19c 100644 --- a/blueprints/data-solutions/bq-ml/outputs.tf +++ b/blueprints/data-solutions/bq-ml/outputs.tf @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +# tfdoc:file:description Output variables. + output "bucket" { description = "GCS Bucket URL." value = module.bucket.url diff --git a/blueprints/data-solutions/bq-ml/variables.tf b/blueprints/data-solutions/bq-ml/variables.tf index 3bd0ca65..160b4616 100644 --- a/blueprints/data-solutions/bq-ml/variables.tf +++ b/blueprints/data-solutions/bq-ml/variables.tf @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +# tfdoc:file:description Terraform variables. variable "location" { description = "The location where resources will be deployed." diff --git a/blueprints/data-solutions/bq-ml/versions.tf b/blueprints/data-solutions/bq-ml/versions.tf index 08492c6f..a467162b 100644 --- a/blueprints/data-solutions/bq-ml/versions.tf +++ b/blueprints/data-solutions/bq-ml/versions.tf @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +# tfdoc:file:description Terraform version. + terraform { required_version = ">= 1.3.1" required_providers { From 6526dda8c721e7216d4ba446d192383dfe025b53 Mon Sep 17 00:00:00 2001 From: Giorgio Conte Date: Fri, 3 Mar 2023 14:52:35 +0000 Subject: [PATCH 25/49] sql linting --- .../bq-ml/demo/sql/explain_predict.sql | 13 ++++++------ .../data-solutions/bq-ml/demo/sql/train.sql | 20 +++++++++---------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/blueprints/data-solutions/bq-ml/demo/sql/explain_predict.sql b/blueprints/data-solutions/bq-ml/demo/sql/explain_predict.sql index 7d36e038..86309815 100644 --- a/blueprints/data-solutions/bq-ml/demo/sql/explain_predict.sql +++ b/blueprints/data-solutions/bq-ml/demo/sql/explain_predict.sql @@ -14,11 +14,12 @@ * limitations under the License. */ -select * -from ML.EXPLAIN_PREDICT(MODEL `{project-id}.{dataset}.{model-name}`, - (select * except (session_id, session_starting_ts, user_id, has_purchased) - from `{project-id}.{dataset}.ecommerce_abt` - where extract(ISOYEAR from session_starting_ts) = 2023 +SELECT * +FROM ML.EXPLAIN_PREDICT(MODEL `{project-id}.{dataset}.{model-name}`, + (SELECT * EXCEPT (session_id, session_starting_ts, user_id, has_purchased) + FROM `{project-id}.{dataset}.ecommerce_abt` + WHERE extract(ISOYEAR FROM session_starting_ts) = 2023 ), - STRUCT(5 AS top_k_features, 0.5 as threshold) + STRUCT(5 AS top_k_features, 0.5 AS threshold) ) +LIMIT 100 diff --git a/blueprints/data-solutions/bq-ml/demo/sql/train.sql b/blueprints/data-solutions/bq-ml/demo/sql/train.sql index 597623d0..72ce3bb1 100644 --- a/blueprints/data-solutions/bq-ml/demo/sql/train.sql +++ b/blueprints/data-solutions/bq-ml/demo/sql/train.sql @@ -14,14 +14,14 @@ * limitations under the License. */ -create or replace model `{project_id}.{dataset}.{model_name}` -OPTIONS(model_type='{model_type}', - input_label_cols=['has_purchased'], - enable_global_explain=TRUE, +CREATE OR REPLACE MODEL `{project_id}.{dataset}.{model_name}` +OPTIONS(MODEL_TYPE='{model_type}', + INPUT_LABEL_COLS=['has_purchased'], + ENABLE_GLOBAL_EXPLAIN=TRUE, MODEL_REGISTRY='VERTEX_AI', - data_split_method = 'RANDOM', - data_split_eval_fraction = {split_fraction} - ) as -select * except (session_id, session_starting_ts, user_id) -from `{project_id}.{dataset}.ecommerce_abt_table` -where extract(ISOYEAR from session_starting_ts) = 2022 + DATA_SPLIT_METHOD = 'RANDOM', + DATA_SPLIT_EVAL_FRACTION = {split_fraction} + ) AS +SELECT * EXCEPT (session_id, session_starting_ts, user_id) +FROM `{project_id}.{dataset}.ecommerce_abt_table` +WHERE extract(ISOYEAR FROM session_starting_ts) = 2022 \ No newline at end of file From 96e829bdf3a3c1a1683d3776d2bca4a6ece7d693 Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Fri, 3 Mar 2023 17:23:36 +0100 Subject: [PATCH 26/49] Billing exclusion support for FAST mt resman (#1209) * fix files resource parsing in tfdoc * fix tfdoc generated output * billing exclusion support in mt bootstrap --- .../0-bootstrap-tenant/README.md | 52 ++++++++++++------- .../0-bootstrap-tenant/automation-sas.tf | 4 +- .../0-bootstrap-tenant/automation.tf | 4 +- .../0-bootstrap-tenant/billing.tf | 32 +++++++++--- .../0-bootstrap-tenant/organization.tf | 2 +- .../0-bootstrap-tenant/variables.tf | 9 ++-- .../1-resman-tenant/README.md | 32 ++++++------ .../1-resman-tenant/variables.tf | 8 ++- fast/stages/0-bootstrap/README.md | 11 +--- fast/stages/1-resman/README.md | 13 +---- tools/tfdoc.py | 2 +- 11 files changed, 87 insertions(+), 82 deletions(-) diff --git a/fast/stages-multitenant/0-bootstrap-tenant/README.md b/fast/stages-multitenant/0-bootstrap-tenant/README.md index bbeaf9f6..515148c4 100644 --- a/fast/stages-multitenant/0-bootstrap-tenant/README.md +++ b/fast/stages-multitenant/0-bootstrap-tenant/README.md @@ -147,6 +147,20 @@ Configure the tenant variable in a tfvars file for this stage. A few minor point Once the configuration is done just go through the usual `init/apply` cycle. On successful apply, a tfvars file specific for this tenant and a set of provider files will be created. +#### Using delayed billing association for projects + +This configuration is possible but unsupported and only exists for development purposes, use at your own risk: + +- temporarily switch `billing_account.id` to `null` in `globals.auto.tfvars.json` +- for each project resources in the project modules used in this stage (`automation-project`, `log-export-project`) + - apply using `-target`, for example + `terraform apply -target 'module.automation-project.google_project.project[0]'` + - untaint the project resource after applying, for example + `terraform untaint 'module.automation-project.google_project.project[0]'` +- go through the process to associate the billing account with the two projects +- switch `billing_account.id` back to the real billing account id +- resume applying normally + ### TODO - [ ] tenant-level Workload Identity Federation pool and providers configuration @@ -177,25 +191,25 @@ Once the configuration is done just go through the usual `init/apply` cycle. On | name | description | type | required | default | producer | |---|---|:---:|:---:|:---:|:---:| | [automation](variables.tf#L20) | Automation resources created by the organization-level bootstrap stage. | object({…}) | ✓ | | 0-bootstrap | -| [billing_account](variables.tf#L38) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | object({…}) | ✓ | | 0-bootstrap | -| [organization](variables.tf#L194) | Organization details. | object({…}) | ✓ | | 0-bootstrap | -| [prefix](variables.tf#L210) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 0-bootstrap | -| [tag_keys](variables.tf#L233) | Organization tag keys. | object({…}) | ✓ | | 1-resman | -| [tag_names](variables.tf#L244) | Customized names for resource management tags. | object({…}) | ✓ | | 1-resman | -| [tag_values](variables.tf#L255) | Organization resource management tag values. | map(string) | ✓ | | 1-resman | -| [tenant_config](variables.tf#L262) | Tenant configuration. Short name must be 4 characters or less. | object({…}) | ✓ | | | -| [cicd_repositories](variables.tf#L51) | CI/CD repository configuration. Identity providers reference keys in the `federated_identity_providers` variable. Set to null to disable, or set individual repositories to null if not needed. | object({…}) | | null | | -| [custom_roles](variables.tf#L97) | Custom roles defined at the organization level, in key => id format. | object({…}) | | null | 0-bootstrap | -| [fast_features](variables.tf#L107) | Selective control for top-level FAST features. | object({…}) | | {} | 0-bootstrap | -| [federated_identity_providers](variables.tf#L121) | Workload Identity Federation pools. The `cicd_repositories` variable references keys here. | map(object({…})) | | {} | | -| [group_iam](variables.tf#L135) | Tenant-level custom group IAM settings in group => [roles] format. | map(list(string)) | | {} | | -| [iam](variables.tf#L141) | Tenant-level custom IAM settings in role => [principal] format. | map(list(string)) | | {} | | -| [iam_additive](variables.tf#L147) | Tenant-level custom IAM settings in role => [principal] format for non-authoritative bindings. | map(list(string)) | | {} | | -| [locations](variables.tf#L153) | Optional locations for GCS, BigQuery, and logging buckets created here. These are the defaults set at the organization level, and can be overridden via the tenant config variable. | object({…}) | | {…} | 0-bootstrap | -| [log_sinks](variables.tf#L173) | Tenant-level log sinks, in name => {type, filter} format. | map(object({…})) | | {…} | | -| [outputs_location](variables.tf#L204) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | string | | null | | -| [project_parent_ids](variables.tf#L220) | Optional parents for projects created here in folders/nnnnnnn format. Null values will use the tenant folder as parent. | object({…}) | | {…} | | -| [test_principal](variables.tf#L302) | Used when testing to bypass the data source returning the current identity. | string | | null | | +| [billing_account](variables.tf#L38) | Billing account id. If billing account is not part of the same org set `is_org_level` to `false`. To disable handling of billing IAM roles set `no_iam` to `true`. | object({…}) | ✓ | | | +| [organization](variables.tf#L191) | Organization details. | object({…}) | ✓ | | 0-bootstrap | +| [prefix](variables.tf#L207) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 0-bootstrap | +| [tag_keys](variables.tf#L230) | Organization tag keys. | object({…}) | ✓ | | 1-resman | +| [tag_names](variables.tf#L241) | Customized names for resource management tags. | object({…}) | ✓ | | 1-resman | +| [tag_values](variables.tf#L252) | Organization resource management tag values. | map(string) | ✓ | | 1-resman | +| [tenant_config](variables.tf#L259) | Tenant configuration. Short name must be 4 characters or less. | object({…}) | ✓ | | | +| [cicd_repositories](variables.tf#L48) | CI/CD repository configuration. Identity providers reference keys in the `federated_identity_providers` variable. Set to null to disable, or set individual repositories to null if not needed. | object({…}) | | null | | +| [custom_roles](variables.tf#L94) | Custom roles defined at the organization level, in key => id format. | object({…}) | | null | 0-bootstrap | +| [fast_features](variables.tf#L104) | Selective control for top-level FAST features. | object({…}) | | {} | 0-bootstrap | +| [federated_identity_providers](variables.tf#L118) | Workload Identity Federation pools. The `cicd_repositories` variable references keys here. | map(object({…})) | | {} | | +| [group_iam](variables.tf#L132) | Tenant-level custom group IAM settings in group => [roles] format. | map(list(string)) | | {} | | +| [iam](variables.tf#L138) | Tenant-level custom IAM settings in role => [principal] format. | map(list(string)) | | {} | | +| [iam_additive](variables.tf#L144) | Tenant-level custom IAM settings in role => [principal] format for non-authoritative bindings. | map(list(string)) | | {} | | +| [locations](variables.tf#L150) | Optional locations for GCS, BigQuery, and logging buckets created here. These are the defaults set at the organization level, and can be overridden via the tenant config variable. | object({…}) | | {…} | 0-bootstrap | +| [log_sinks](variables.tf#L170) | Tenant-level log sinks, in name => {type, filter} format. | map(object({…})) | | {…} | | +| [outputs_location](variables.tf#L201) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | string | | null | | +| [project_parent_ids](variables.tf#L217) | Optional parents for projects created here in folders/nnnnnnn format. Null values will use the tenant folder as parent. | object({…}) | | {…} | | +| [test_principal](variables.tf#L299) | Used when testing to bypass the data source returning the current identity. | string | | null | | ## Outputs diff --git a/fast/stages-multitenant/0-bootstrap-tenant/automation-sas.tf b/fast/stages-multitenant/0-bootstrap-tenant/automation-sas.tf index cae14093..453b6b8d 100644 --- a/fast/stages-multitenant/0-bootstrap-tenant/automation-sas.tf +++ b/fast/stages-multitenant/0-bootstrap-tenant/automation-sas.tf @@ -93,12 +93,12 @@ module "automation-tf-resman-sa-stage2-3" { name = "${each.key}-0" display_name = "Terraform ${each.value.description} service account." prefix = local.prefix - iam_billing_roles = !var.billing_account.is_org_level ? { + iam_billing_roles = local.billing_mode == "resource" ? { (var.billing_account.id) = [ "roles/billing.user", "roles/billing.costsManager" ] } : {} - iam_organization_roles = var.billing_account.is_org_level ? { + iam_organization_roles = local.billing_mode == "org" ? { (var.organization.id) = [ "roles/billing.user", "roles/billing.costsManager" ] diff --git a/fast/stages-multitenant/0-bootstrap-tenant/automation.tf b/fast/stages-multitenant/0-bootstrap-tenant/automation.tf index 9684e7ca..145210dd 100644 --- a/fast/stages-multitenant/0-bootstrap-tenant/automation.tf +++ b/fast/stages-multitenant/0-bootstrap-tenant/automation.tf @@ -125,12 +125,12 @@ module "automation-tf-resman-sa" { try(module.automation-tf-cicd-sa-resman["0"].iam_email, null) ]) } - iam_billing_roles = !var.billing_account.is_org_level ? { + iam_billing_roles = local.billing_mode == "resource" ? { (var.billing_account.id) = [ "roles/billing.admin", "roles/billing.costsManager" ] } : {} - iam_organization_roles = var.billing_account.is_org_level ? { + iam_organization_roles = local.billing_mode == "org" ? { (var.organization.id) = [ "roles/billing.admin", "roles/billing.costsManager" ] diff --git a/fast/stages-multitenant/0-bootstrap-tenant/billing.tf b/fast/stages-multitenant/0-bootstrap-tenant/billing.tf index 77c26b91..b62b79cb 100644 --- a/fast/stages-multitenant/0-bootstrap-tenant/billing.tf +++ b/fast/stages-multitenant/0-bootstrap-tenant/billing.tf @@ -16,23 +16,39 @@ # tfdoc:file:description Billing roles for standalone billing accounts. +locals { + billing_mode = ( + var.billing_account.no_iam + ? null + : var.billing_account.is_org_level ? "org" : "resource" + ) +} + # service account billing roles are in the SA module in automation.tf resource "google_billing_account_iam_member" "billing_ext_admin" { - for_each = toset(var.billing_account.is_org_level ? [] : [ - "group:${local.groups.gcp-admins}", - module.automation-tf-resman-sa.iam_email - ]) + for_each = toset( + local.billing_mode == "resource" + ? [ + "group:${local.groups.gcp-admins}", + module.automation-tf-resman-sa.iam_email + ] + : [] + ) billing_account_id = var.billing_account.id role = "roles/billing.admin" member = each.key } resource "google_billing_account_iam_member" "billing_ext_cost_manager" { - for_each = toset(var.billing_account.is_org_level ? [] : [ - "group:${local.groups.gcp-admins}", - module.automation-tf-resman-sa.iam_email - ]) + for_each = toset( + local.billing_mode == "resource" + ? [ + "group:${local.groups.gcp-admins}", + module.automation-tf-resman-sa.iam_email + ] + : [] + ) billing_account_id = var.billing_account.id role = "roles/billing.costsManager" member = each.key diff --git a/fast/stages-multitenant/0-bootstrap-tenant/organization.tf b/fast/stages-multitenant/0-bootstrap-tenant/organization.tf index 46f8c0d4..f08f0fba 100644 --- a/fast/stages-multitenant/0-bootstrap-tenant/organization.tf +++ b/fast/stages-multitenant/0-bootstrap-tenant/organization.tf @@ -32,7 +32,7 @@ module "organization" { "group:${local.groups.gcp-admins}" ] }, - var.billing_account.is_org_level ? { + local.billing_mode == "org" ? { "roles/billing.admin" = [ "group:${local.groups.gcp-admins}", module.automation-tf-resman-sa.iam_email diff --git a/fast/stages-multitenant/0-bootstrap-tenant/variables.tf b/fast/stages-multitenant/0-bootstrap-tenant/variables.tf index d218986f..aa90f400 100644 --- a/fast/stages-multitenant/0-bootstrap-tenant/variables.tf +++ b/fast/stages-multitenant/0-bootstrap-tenant/variables.tf @@ -36,16 +36,13 @@ variable "automation" { } variable "billing_account" { - # tfdoc:variable:source 0-bootstrap - description = "Billing account id. If billing account is not part of the same org set `is_org_level` to false." + description = "Billing account id. If billing account is not part of the same org set `is_org_level` to `false`. To disable handling of billing IAM roles set `no_iam` to `true`." type = object({ id = string is_org_level = optional(bool, true) + no_iam = optional(bool, false) }) - validation { - condition = var.billing_account.is_org_level != null - error_message = "Invalid `null` value for `billing_account.is_org_level`." - } + nullable = false } variable "cicd_repositories" { diff --git a/fast/stages-multitenant/1-resman-tenant/README.md b/fast/stages-multitenant/1-resman-tenant/README.md index ae42fc30..8ff59c25 100644 --- a/fast/stages-multitenant/1-resman-tenant/README.md +++ b/fast/stages-multitenant/1-resman-tenant/README.md @@ -149,22 +149,22 @@ Once the configuration is done just go through the usual `init/apply` cycle. On | name | description | type | required | default | producer | |---|---|:---:|:---:|:---:|:---:| | [automation](variables.tf#L20) | Automation resources created by the bootstrap stage. | object({…}) | ✓ | | 0-bootstrap | -| [billing_account](variables.tf#L51) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | object({…}) | ✓ | | 0-bootstrap | -| [organization](variables.tf#L206) | Organization details. | object({…}) | ✓ | | 0-bootstrap | -| [prefix](variables.tf#L228) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 0-bootstrap | -| [root_node](variables.tf#L239) | Root folder node for the tenant, in folders/nnnnnn format. | string | ✓ | | | -| [short_name](variables.tf#L244) | Short name used to identify the tenant. | string | ✓ | | | -| [tags](variables.tf#L249) | Resource management tags. | object({…}) | ✓ | | | -| [cicd_repositories](variables.tf#L64) | CI/CD repository configuration. Identity providers reference keys in the `automation.federated_identity_providers` variable. Set to null to disable, or set individual repositories to null if not needed. | object({…}) | | null | | -| [custom_roles](variables.tf#L146) | Custom roles defined at the org level, in key => id format. | object({…}) | | null | 0-bootstrap | -| [data_dir](variables.tf#L155) | Relative path for the folder storing configuration data. | string | | "data" | | -| [fast_features](variables.tf#L161) | Selective control for top-level FAST features. | object({…}) | | {} | 0-0-bootstrap | -| [groups](variables.tf#L175) | Group names to grant organization-level permissions. | object({…}) | | {} | 0-bootstrap | -| [locations](variables.tf#L188) | Optional locations for GCS, BigQuery, and logging buckets created here. | object({…}) | | {…} | 0-bootstrap | -| [organization_policy_data_path](variables.tf#L216) | Path for the data folder used by the organization policies factory. | string | | null | | -| [outputs_location](variables.tf#L222) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | string | | null | | -| [team_folders](variables.tf#L267) | Team folders to be created. Format is described in a code comment. | map(object({…})) | | null | | -| [test_skip_data_sources](variables.tf#L277) | Used when testing to bypass data sources. | bool | | false | | +| [billing_account](variables.tf#L51) | Billing account id. If billing account is not part of the same org set `is_org_level` to `false`. To disable handling of billing IAM roles set `no_iam` to `true`. | object({…}) | ✓ | | 0-bootstrap | +| [organization](variables.tf#L204) | Organization details. | object({…}) | ✓ | | 0-bootstrap | +| [prefix](variables.tf#L226) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 0-bootstrap | +| [root_node](variables.tf#L237) | Root folder node for the tenant, in folders/nnnnnn format. | string | ✓ | | | +| [short_name](variables.tf#L242) | Short name used to identify the tenant. | string | ✓ | | | +| [tags](variables.tf#L247) | Resource management tags. | object({…}) | ✓ | | | +| [cicd_repositories](variables.tf#L62) | CI/CD repository configuration. Identity providers reference keys in the `automation.federated_identity_providers` variable. Set to null to disable, or set individual repositories to null if not needed. | object({…}) | | null | | +| [custom_roles](variables.tf#L144) | Custom roles defined at the org level, in key => id format. | object({…}) | | null | 0-bootstrap | +| [data_dir](variables.tf#L153) | Relative path for the folder storing configuration data. | string | | "data" | | +| [fast_features](variables.tf#L159) | Selective control for top-level FAST features. | object({…}) | | {} | 0-0-bootstrap | +| [groups](variables.tf#L173) | Group names to grant organization-level permissions. | object({…}) | | {} | 0-bootstrap | +| [locations](variables.tf#L186) | Optional locations for GCS, BigQuery, and logging buckets created here. | object({…}) | | {…} | 0-bootstrap | +| [organization_policy_data_path](variables.tf#L214) | Path for the data folder used by the organization policies factory. | string | | null | | +| [outputs_location](variables.tf#L220) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | string | | null | | +| [team_folders](variables.tf#L265) | Team folders to be created. Format is described in a code comment. | map(object({…})) | | null | | +| [test_skip_data_sources](variables.tf#L275) | Used when testing to bypass data sources. | bool | | false | | ## Outputs diff --git a/fast/stages-multitenant/1-resman-tenant/variables.tf b/fast/stages-multitenant/1-resman-tenant/variables.tf index 0229dd78..1698b7e1 100644 --- a/fast/stages-multitenant/1-resman-tenant/variables.tf +++ b/fast/stages-multitenant/1-resman-tenant/variables.tf @@ -50,15 +50,13 @@ variable "automation" { variable "billing_account" { # tfdoc:variable:source 0-bootstrap - description = "Billing account id. If billing account is not part of the same org set `is_org_level` to false." + description = "Billing account id. If billing account is not part of the same org set `is_org_level` to `false`. To disable handling of billing IAM roles set `no_iam` to `true`." type = object({ id = string is_org_level = optional(bool, true) + no_iam = optional(bool, false) }) - validation { - condition = var.billing_account.is_org_level != null - error_message = "Invalid `null` value for `billing_account.is_org_level`." - } + nullable = false } variable "cicd_repositories" { diff --git a/fast/stages/0-bootstrap/README.md b/fast/stages/0-bootstrap/README.md index 20ed998f..af02d1b9 100644 --- a/fast/stages/0-bootstrap/README.md +++ b/fast/stages/0-bootstrap/README.md @@ -481,16 +481,7 @@ The remaining configuration is manual, as it regards the repositories themselves | name | description | modules | resources | |---|---|---|---| | [automation.tf](./automation.tf) | Automation project and resources. | gcs · iam-service-account · project | | -| [billing.tf](./billing.tf) | Billing export project and dataset. | bigquery-dataset · project | - ) -} - -# billing account in same org (IAM is in the organization.tf file) - -module · ? local.billing_ext_admins : [] - ) - billing_account_id = var.billing_account.id - role = · google_billing_account_iam_member | +| [billing.tf](./billing.tf) | Billing export project and dataset. | bigquery-dataset · project | google_billing_account_iam_member | | [cicd.tf](./cicd.tf) | Workload Identity Federation configurations for CI/CD. | iam-service-account · source-repository | | | [identity-providers.tf](./identity-providers.tf) | Workload Identity Federation provider definitions. | | google_iam_workload_identity_pool · google_iam_workload_identity_pool_provider | | [log-export.tf](./log-export.tf) | Audit log project and sink. | bigquery-dataset · gcs · logging-bucket · project · pubsub | | diff --git a/fast/stages/1-resman/README.md b/fast/stages/1-resman/README.md index 781dc14d..efa33895 100644 --- a/fast/stages/1-resman/README.md +++ b/fast/stages/1-resman/README.md @@ -174,18 +174,7 @@ Due to its simplicity, this stage lends itself easily to customizations: adding | name | description | modules | resources | |---|---|---|---| -| [billing.tf](./billing.tf) | Billing resources for external billing use cases. | | - ) -} - -# billing account in same org (resources is in the organization.tf file) - -# standalone billing account - -resource · ? local.billing_ext_users : [] - ) - billing_account_id = var.billing_account.id - role = · google_billing_account_iam_member | +| [billing.tf](./billing.tf) | Billing resources for external billing use cases. | | google_billing_account_iam_member | | [branch-data-platform.tf](./branch-data-platform.tf) | Data Platform stages resources. | folder · gcs · iam-service-account | google_organization_iam_member | | [branch-gke.tf](./branch-gke.tf) | GKE multitenant stage resources. | folder · gcs · iam-service-account | | | [branch-networking.tf](./branch-networking.tf) | Networking stage resources. | folder · gcs · iam-service-account | | diff --git a/tools/tfdoc.py b/tools/tfdoc.py index d06dedb9..a69d94ab 100755 --- a/tools/tfdoc.py +++ b/tools/tfdoc.py @@ -60,7 +60,7 @@ FILE_DESC_DEFAULTS = { } FILE_RE_MODULES = re.compile( r'(?sm)module\s*"[^"]+"\s*\{[^\}]*?source\s*=\s*"([^"]+)"') -FILE_RE_RESOURCES = re.compile(r'(?sm)resource\s*"([^"]+)"') +FILE_RE_RESOURCES = re.compile(r'(?sm)resource\s+"([^"]+)"') HEREDOC_RE = re.compile(r'(?sm)^<<\-?END(\s*.*?)\s*END$') MARK_BEGIN = '' MARK_END = '' From 21e451d4cbdb08d6b6592803def66b829e59c8bd Mon Sep 17 00:00:00 2001 From: Ludo Date: Fri, 3 Mar 2023 23:00:51 +0100 Subject: [PATCH 27/49] update changelog --- CHANGELOG.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b327ac9..c5eebc39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,11 @@ All notable changes to this project will be documented in this file. ### BLUEPRINTS +- [[#1208](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1208)] Fix outdated go deps, dependabot alerts ([averbuks](https://github.com/averbuks)) +- [[#1150](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1150)] Blueprint: GLB hybrid NEG internal ([LucaPrete](https://github.com/LucaPrete)) +- [[#1201](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1201)] Add missing tfvars template to the tfc blueprint ([averbuks](https://github.com/averbuks)) +- [[#1196](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1196)] Fix compute-vm:CloudKMS test for provider>=4.54.0 ([dan-farmer](https://github.com/dan-farmer)) +- [[#1189](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1189)] Update healthchecker deps (dependabot alerts) ([averbuks](https://github.com/averbuks)) - [[#1184](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1184)] **incompatible change:** Allow multiple peer gateways in VPN HA module ([ludoo](https://github.com/ludoo)) - [[#1143](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1143)] Test blueprints from README files ([juliocc](https://github.com/juliocc)) - [[#1181](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1181)] Bump golang.org/x/sys from 0.0.0-20220310020820-b874c991c1a5 to 0.1.0 in /blueprints/cloud-operations/unmanaged-instances-healthcheck/function/healthchecker ([dependabot[bot]](https://github.com/dependabot[bot])) @@ -27,6 +32,10 @@ All notable changes to this project will be documented in this file. ### DOCUMENTATION +- [[#1150](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1150)] Blueprint: GLB hybrid NEG internal ([LucaPrete](https://github.com/LucaPrete)) +- [[#1193](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1193)] Add reference to Cloud Run blueprints ([juliodiez](https://github.com/juliodiez)) +- [[#1188](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1188)] Add reference to Cloud Run blueprints ([juliodiez](https://github.com/juliodiez)) +- [[#1187](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1187)] Add references to the serverless chapters ([juliodiez](https://github.com/juliodiez)) - [[#1179](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1179)] Added a PSC GCLB example ([cgrotz](https://github.com/cgrotz)) - [[#1165](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1165)] DataPlatform: Support project creation ([lcaggio](https://github.com/lcaggio)) - [[#1145](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1145)] FAST stage docs cleanup ([ludoo](https://github.com/ludoo)) @@ -36,6 +45,8 @@ All notable changes to this project will be documented in this file. ### FAST +- [[#1209](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1209)] Billing exclusion support for FAST mt resman ([ludoo](https://github.com/ludoo)) +- [[#1207](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1207)] Allow preventing creation of billing IAM roles in FAST, add instructions on delayed billing association ([ludoo](https://github.com/ludoo)) - [[#1184](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1184)] **incompatible change:** Allow multiple peer gateways in VPN HA module ([ludoo](https://github.com/ludoo)) - [[#1165](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1165)] DataPlatform: Support project creation ([lcaggio](https://github.com/lcaggio)) - [[#1170](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1170)] Add documentation about referring modules stored on CSR ([wiktorn](https://github.com/wiktorn)) @@ -52,6 +63,18 @@ All notable changes to this project will be documented in this file. ### MODULES +- [[#1206](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1206)] Dataproc module. Fix output. ([lcaggio](https://github.com/lcaggio)) +- [[#1205](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1205)] Fix issue with GKE cluster notifications topic & static output for pubsub module ([rosmo](https://github.com/rosmo)) +- [[#1204](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1204)] Fix url_redirect issue on net-glb module ([erabusi](https://github.com/erabusi)) +- [[#1199](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1199)] [Dataproc module] Fix Variables ([lcaggio](https://github.com/lcaggio)) +- [[#1200](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1200)] Add test for #1197 ([juliocc](https://github.com/juliocc)) +- [[#1198](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1198)] Fix secondary ranges in net-vpc readme ([ludoo](https://github.com/ludoo)) +- [[#1196](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1196)] Fix compute-vm:CloudKMS test for provider>=4.54.0 ([dan-farmer](https://github.com/dan-farmer)) +- [[#1194](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1194)] Fix HTTPS health check mismapped to HTTP in compute-mig and net-ilb modules ([jogoldberg](https://github.com/jogoldberg)) +- [[#1192](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1192)] Dataproc module: Fix outputs ([lcaggio](https://github.com/lcaggio)) +- [[#1190](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1190)] Dataproc Module ([lcaggio](https://github.com/lcaggio)) +- [[#1191](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1191)] Fix external gateway in VPN HA module ([ludoo](https://github.com/ludoo)) +- [[#1186](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1186)] Fix Workload Identity for ASM in GKE hub module ([valeriobponza](https://github.com/valeriobponza)) - [[#1184](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1184)] **incompatible change:** Allow multiple peer gateways in VPN HA module ([ludoo](https://github.com/ludoo)) - [[#1177](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1177)] Implemented conditional dynamic blocks for `google_access_context_manager_service_perimeter` `spec` and `status` ([calexandre](https://github.com/calexandre)) - [[#1178](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1178)] adding meshconfig.googleapis.com to JIT list. ([valeriobponza](https://github.com/valeriobponza)) @@ -77,6 +100,8 @@ All notable changes to this project will be documented in this file. ### TOOLS +- [[#1209](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1209)] Billing exclusion support for FAST mt resman ([ludoo](https://github.com/ludoo)) +- [[#1208](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1208)] Fix outdated go deps, dependabot alerts ([averbuks](https://github.com/averbuks)) - [[#1182](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1182)] Bump actions versions ([juliocc](https://github.com/juliocc)) - [[#1052](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1052)] **incompatible change:** FAST multitenant bootstrap and resource management, rename org-level FAST stages ([ludoo](https://github.com/ludoo)) From 98e17bb997daf2b237b338158169c3a2c0c81788 Mon Sep 17 00:00:00 2001 From: lcaggio Date: Sat, 4 Mar 2023 08:09:29 +0100 Subject: [PATCH 28/49] Fix readme. --- blueprints/data-solutions/README.md | 7 +++ blueprints/data-solutions/bq-ml/README.md | 50 +++++++++++-------- .../bq-ml/demo/bmql_pipeline.ipynb | 34 ++++++------- blueprints/data-solutions/bq-ml/variables.tf | 14 +++--- 4 files changed, 59 insertions(+), 46 deletions(-) diff --git a/blueprints/data-solutions/README.md b/blueprints/data-solutions/README.md index 3da4e7e9..651b87a4 100644 --- a/blueprints/data-solutions/README.md +++ b/blueprints/data-solutions/README.md @@ -69,3 +69,10 @@ This [blueprint](./vertex-mlops/) implements the infrastructure required to have This [blueprint](./shielded-folder/) implements an opinionated folder configuration according to GCP best practices. Configurations implemented on the folder would be beneficial to host workloads inheriting constraints from the folder they belong to.
+ +### BigQuery ML and Vertex Pipeline + + +This [blueprint](./bq-ml/) implements the infrastructure required to have a fully functional develpement environment using BigQuery ML and Vertex AI to develop and deploy a machine learning model to be used from Vertex AI endpoint or in BigQuery ML. + +
diff --git a/blueprints/data-solutions/bq-ml/README.md b/blueprints/data-solutions/bq-ml/README.md index 407ce146..8390d795 100644 --- a/blueprints/data-solutions/bq-ml/README.md +++ b/blueprints/data-solutions/bq-ml/README.md @@ -1,14 +1,14 @@ -# BQ ML and Vertex Pipeline +# BigQuery ML and Vertex Pipeline -This blueprint creates the infrastructure needed to deploy and run a Vertex AI environment to develop and deploy a machine learning model to be used from Vertex AI an endpoint or in BigQuery. +This blueprint creates the infrastructure needed to deploy and run a Vertex AI environment to develop and deploy a machine learning model to be used from Vertex AI endpoint or in BigQuery. -This is the high level diagram: +This is the high-level diagram: ![High-level diagram](diagram.png "High-level diagram") -It also includes the IAM wiring needed to make such scenarios work. Regional resources are used in this example, but the same logic will apply for 'dual regional', 'multi regional' or 'global' resources. +It also includes the IAM wiring needed to make such scenarios work. Regional resources are used in this example, but the same logic applies to 'dual regional', 'multi regional', or 'global' resources. -The example is designed to match real-world use cases with a minimum amount of resources, and be used as a starting point for your scenario. +The example is designed to match real-world use cases with a minimum amount of resources and be used as a starting point for your scenario. ## Managed resources and services @@ -30,15 +30,21 @@ This sample creates several distinct groups of resources: ### Virtual Private Cloud (VPC) design -As is often the case in real-world configurations, this blueprint accepts as input an existing Shared-VPC via the `network_config` variable. +As is often the case in real-world configurations, this blueprint accepts an existing Shared-VPC via the `network_config` variable as input. ### Customer Managed Encryption Key -As is often the case in real-world configurations, this blueprint accepts as input existing Cloud KMS keys to encrypt resources via the `service_encryption_keys` variable. +As is often the case in real-world configurations, this blueprint accepts as input existing Cloud KMS keys to encrypt resources via the `service_encryption_keys` variable. ## Demo -In the repository `demo` folder you can find an example on how to create a Vertex AI pipeline from a publically available dataset and deploy the model to be used from a Vertex AI managed endpoint or from within Bigquery. +In the repository [`demo`](./demo/) folder, you can find an example of creating a Vertex AI pipeline from a publically available dataset and deploying the model to be used from a Vertex AI managed endpoint or from within Bigquery. + +To run the demo: + +- Connect to the Vertex AI workbench instance +- Clone this repository +- Run the and run [`demo/bmql_pipeline.ipynb`](demo/bmql_pipeline.ipynb) Jupyter Notebook. @@ -46,27 +52,27 @@ In the repository `demo` folder you can find an example on how to create a Verte | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [prefix](variables.tf#L32) | Prefix used for resource names. | string | ✓ | | -| [project_id](variables.tf#L50) | Project id, references existing project if `project_create` is null. | string | ✓ | | -| [location](variables.tf#L16) | The location where resources will be deployed. | string | | "EU" | -| [network_config](variables.tf#L22) | Shared VPC network configurations to use. If null networks will be created in projects with preconfigured values. | object({…}) | | null | -| [project_create](variables.tf#L41) | Provide values if project creation is needed, uses existing project if null. Parent format: folders/folder_id or organizations/org_id. | object({…}) | | null | -| [region](variables.tf#L55) | The region where resources will be deployed. | string | | "europe-west1" | +| [prefix](variables.tf#L33) | Prefix used for resource names. | string | ✓ | | +| [project_id](variables.tf#L51) | Project id references existing project if `project_create` is null. | string | ✓ | | +| [location](variables.tf#L17) | The location where resources will be deployed. | string | | "US" | +| [network_config](variables.tf#L23) | Shared VPC network configurations to use. If null networks will be created in projects with pre-configured values. | object({…}) | | null | +| [project_create](variables.tf#L42) | Provide values if project creation is needed, use existing project if null. Parent format: folders/folder_id or organizations/org_id. | object({…}) | | null | +| [region](variables.tf#L56) | The region where resources will be deployed. | string | | "us-central1" | +| [service_encryption_keys](variables.tf#L62) | Cloud KMS to use to encrypt different services. The key location should match the service region. | object({…}) | | null | ## Outputs | name | description | sensitive | |---|---|:---:| -| [bucket](outputs.tf#L15) | GCS Bucket URL. | | -| [dataset](outputs.tf#L20) | GCS Bucket URL. | | -| [notebook](outputs.tf#L25) | Vertex AI notebook details. | | -| [project](outputs.tf#L33) | Project id. | | -| [service-account-vertex](outputs.tf#L43) | Service account to be used for Vertex AI pipelines | | -| [vertex-ai-metadata-store](outputs.tf#L48) | | | -| [vpc](outputs.tf#L38) | VPC Network. | | +| [bucket](outputs.tf#L17) | GCS Bucket URL. | | +| [dataset](outputs.tf#L22) | GCS Bucket URL. | | +| [notebook](outputs.tf#L27) | Vertex AI notebook details. | | +| [project](outputs.tf#L35) | Project id. | | +| [service-account-vertex](outputs.tf#L45) | Service account to be used for Vertex AI pipelines | | +| [vertex-ai-metadata-store](outputs.tf#L50) | | | +| [vpc](outputs.tf#L40) | VPC Network. | | - ## Test ```hcl diff --git a/blueprints/data-solutions/bq-ml/demo/bmql_pipeline.ipynb b/blueprints/data-solutions/bq-ml/demo/bmql_pipeline.ipynb index 07719fa9..592f3d10 100644 --- a/blueprints/data-solutions/bq-ml/demo/bmql_pipeline.ipynb +++ b/blueprints/data-solutions/bq-ml/demo/bmql_pipeline.ipynb @@ -53,18 +53,18 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Vertex Pipeline Definition\n", + "# Vertex AI Pipeline Definition\n", "\n", - "In the following code block we are defining our Vertex AI pipeline. It is made up of three main steps:\n", - "1. Create a BigQuery dataset which will contains the BQ ML models\n", - "2. Train the BQ ML model, in this case a logistic regression\n", - "3. Evaluate the BQ ML model with the standard evaluation metrics\n", + "In the following code block, we are defining our Vertex AI pipeline. It is made up of three main steps:\n", + "1. Create a BigQuery dataset that will contain the BigQuery ML models\n", + "2. Train the BigQuery ML model, in this case, a logistic regression\n", + "3. Evaluate the BigQuery ML model with the standard evaluation metrics\n", "\n", "The pipeline takes as input the following variables:\n", - "- ```model_name```: the display name of the BQ ML model\n", - "- ```split_fraction```: the percentage of data that will be used as evaluation dataset\n", - "- ```evaluate_job_conf```: bq dict configuration to define where to store evalution metrics\n", - "- ```dataset```: name of dataset where the artifacts will be stored\n", + "- ```model_name```: the display name of the BigQuery ML model\n", + "- ```split_fraction```: the percentage of data that will be used as an evaluation dataset\n", + "- ```evaluate_job_conf```: bq dict configuration to define where to store evaluation metrics\n", + "- ```dataset```: name of the dataset where the artifacts will be stored\n", "- ```project_id```: the project id where the GCP resources will be created\n", "- ```location```: BigQuery location" ] @@ -136,7 +136,7 @@ "source": [ "# Create Experiment\n", "\n", - "We will create an experiment in order to keep track of our trainings and tasks on a specific issue or problem." + "We will create an experiment to keep track of our training and tasks on a specific issue or problem." ] }, { @@ -158,11 +158,11 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Running the same training pipeline with different parameters\n", + "# Running the same training Verte AI pipeline with different parameters\n", "\n", - "One of the main tasks during the training phase is to compare different models or to try the same model with different inputs. We can leverage the power of Vertex Pipelines in order to submit the same steps with different training parameters. Thanks to the experiments artifact it is possible to easily keep track of all the tests that have been done. This simplifies the process to select the best model to deploy.\n", + "One of the main tasks during the training phase is to compare different models or to try the same model with different inputs. We can leverage the power of Vertex AI Pipelines to submit the same steps with different training parameters. Thanks to the experiments artifact, it is possible to easily keep track of all the tests that have been done. This simplifies the process of selecting the best model to deploy.\n", "\n", - "In this demo case, we will run the same training pipeline while changing the data split percentage between training and test data." + "In this demo case, we will run the same training pipeline while changing the split data percentage between training and test data." ] }, { @@ -193,9 +193,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Deploy the model to an endpoint\n", + "# Deploy the model on a Vertex AI endpoint\n", "\n", - "Thanks to the integration of Vertex Endpoint, it is very straightforward to create a live endpoint to serve the model which we prefer." + "Thanks to the integration of Vertex AI Endpoint, creating a live endpoint to serve the model we prefer is very straightforward." ] }, { @@ -221,7 +221,7 @@ "metadata": {}, "outputs": [], "source": [ - "# deploy the BQ ML model on Vertex Endpoint\n", + "# deploy the BigQuery ML model on Vertex Endpoint\n", "# have a coffe - this step can take up 10/15 minutes to finish\n", "model.deploy(endpoint=endpoint, deployed_model_display_name='bqml-deployed-model')" ] @@ -265,7 +265,7 @@ }, "language_info": { "name": "python", - "version": "3.10.9" + "version": "3.8.9" }, "orig_nbformat": 4, "vscode": { diff --git a/blueprints/data-solutions/bq-ml/variables.tf b/blueprints/data-solutions/bq-ml/variables.tf index 160b4616..13552a38 100644 --- a/blueprints/data-solutions/bq-ml/variables.tf +++ b/blueprints/data-solutions/bq-ml/variables.tf @@ -17,11 +17,11 @@ variable "location" { description = "The location where resources will be deployed." type = string - default = "EU" + default = "US" } variable "network_config" { - description = "Shared VPC network configurations to use. If null networks will be created in projects with preconfigured values." + description = "Shared VPC network configurations to use. If null networks will be created in projects with pre-configured values." type = object({ host_project = string network_self_link = string @@ -40,7 +40,7 @@ variable "prefix" { } variable "project_create" { - description = "Provide values if project creation is needed, uses existing project if null. Parent format: folders/folder_id or organizations/org_id." + description = "Provide values if project creation is needed, use existing project if null. Parent format: folders/folder_id or organizations/org_id." type = object({ billing_account_id = string parent = string @@ -49,18 +49,18 @@ variable "project_create" { } variable "project_id" { - description = "Project id, references existing project if `project_create` is null." + description = "Project id references existing project if `project_create` is null." type = string } variable "region" { description = "The region where resources will be deployed." type = string - default = "europe-west1" + default = "us-central1" } -variable "service_encryption_keys" { # service encription key - description = "Cloud KMS to use to encrypt different services. Key location should match service region." +variable "service_encryption_keys" { + description = "Cloud KMS to use to encrypt different services. The key location should match the service region." type = object({ bq = string compute = string From 0d4b599e99be6334ffa899cbb66a7dde9a884962 Mon Sep 17 00:00:00 2001 From: lcaggio Date: Sat, 4 Mar 2023 08:13:53 +0100 Subject: [PATCH 29/49] Fix README --- blueprints/data-solutions/README.md | 4 ++-- blueprints/data-solutions/bq-ml/README.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/blueprints/data-solutions/README.md b/blueprints/data-solutions/README.md index 651b87a4..a2060560 100644 --- a/blueprints/data-solutions/README.md +++ b/blueprints/data-solutions/README.md @@ -70,9 +70,9 @@ This [blueprint](./shielded-folder/) implements an opinionated folder configurat
-### BigQuery ML and Vertex Pipeline +### BigQuery ML and Vertex AI Pipeline - + This [blueprint](./bq-ml/) implements the infrastructure required to have a fully functional develpement environment using BigQuery ML and Vertex AI to develop and deploy a machine learning model to be used from Vertex AI endpoint or in BigQuery ML.
diff --git a/blueprints/data-solutions/bq-ml/README.md b/blueprints/data-solutions/bq-ml/README.md index 8390d795..53bfdca6 100644 --- a/blueprints/data-solutions/bq-ml/README.md +++ b/blueprints/data-solutions/bq-ml/README.md @@ -1,4 +1,4 @@ -# BigQuery ML and Vertex Pipeline +# BigQuery ML and Vertex AI Pipeline This blueprint creates the infrastructure needed to deploy and run a Vertex AI environment to develop and deploy a machine learning model to be used from Vertex AI endpoint or in BigQuery. From ccd68b2fa6cae816dc8025c3a4c8269f0313d3b6 Mon Sep 17 00:00:00 2001 From: lcaggio Date: Sat, 4 Mar 2023 08:19:47 +0100 Subject: [PATCH 30/49] Fix linting. --- blueprints/data-solutions/bq-ml/README.md | 3 +-- blueprints/data-solutions/bq-ml/outputs.tf | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/blueprints/data-solutions/bq-ml/README.md b/blueprints/data-solutions/bq-ml/README.md index 53bfdca6..79a73832 100644 --- a/blueprints/data-solutions/bq-ml/README.md +++ b/blueprints/data-solutions/bq-ml/README.md @@ -45,7 +45,6 @@ To run the demo: - Connect to the Vertex AI workbench instance - Clone this repository - Run the and run [`demo/bmql_pipeline.ipynb`](demo/bmql_pipeline.ipynb) Jupyter Notebook. - ## Variables @@ -69,7 +68,7 @@ To run the demo: | [notebook](outputs.tf#L27) | Vertex AI notebook details. | | | [project](outputs.tf#L35) | Project id. | | | [service-account-vertex](outputs.tf#L45) | Service account to be used for Vertex AI pipelines | | -| [vertex-ai-metadata-store](outputs.tf#L50) | | | +| [vertex-ai-metadata-store](outputs.tf#L50) | Vertex AI Metadata Store ID. | | | [vpc](outputs.tf#L40) | VPC Network. | | diff --git a/blueprints/data-solutions/bq-ml/outputs.tf b/blueprints/data-solutions/bq-ml/outputs.tf index 1a39e19c..a23ba484 100644 --- a/blueprints/data-solutions/bq-ml/outputs.tf +++ b/blueprints/data-solutions/bq-ml/outputs.tf @@ -48,7 +48,6 @@ output "service-account-vertex" { } output "vertex-ai-metadata-store" { - description = "" + description = "Vertex AI Metadata Store ID." value = google_vertex_ai_metadata_store.store.id - } From f8a7aa865acc8239dfefbcc7f4bcc28567ee8ffa Mon Sep 17 00:00:00 2001 From: lcaggio Date: Sat, 4 Mar 2023 08:25:29 +0100 Subject: [PATCH 31/49] Fix test. --- blueprints/data-solutions/bq-ml/README.md | 6 +++--- blueprints/data-solutions/bq-ml/outputs.tf | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/blueprints/data-solutions/bq-ml/README.md b/blueprints/data-solutions/bq-ml/README.md index 79a73832..2ea84190 100644 --- a/blueprints/data-solutions/bq-ml/README.md +++ b/blueprints/data-solutions/bq-ml/README.md @@ -67,9 +67,9 @@ To run the demo: | [dataset](outputs.tf#L22) | GCS Bucket URL. | | | [notebook](outputs.tf#L27) | Vertex AI notebook details. | | | [project](outputs.tf#L35) | Project id. | | -| [service-account-vertex](outputs.tf#L45) | Service account to be used for Vertex AI pipelines | | -| [vertex-ai-metadata-store](outputs.tf#L50) | Vertex AI Metadata Store ID. | | -| [vpc](outputs.tf#L40) | VPC Network. | | +| [service-account-vertex](outputs.tf#L40) | Service account to be used for Vertex AI pipelines. | | +| [vertex-ai-metadata-store](outputs.tf#L45) | Vertex AI Metadata Store ID. | | +| [vpc](outputs.tf#L50) | VPC Network. | | ## Test diff --git a/blueprints/data-solutions/bq-ml/outputs.tf b/blueprints/data-solutions/bq-ml/outputs.tf index a23ba484..8299ce2f 100644 --- a/blueprints/data-solutions/bq-ml/outputs.tf +++ b/blueprints/data-solutions/bq-ml/outputs.tf @@ -37,13 +37,8 @@ output "project" { value = module.project.project_id } -output "vpc" { - description = "VPC Network." - value = local.vpc -} - output "service-account-vertex" { - description = "Service account to be used for Vertex AI pipelines" + description = "Service account to be used for Vertex AI pipelines." value = module.service-account-vertex.email } @@ -51,3 +46,8 @@ output "vertex-ai-metadata-store" { description = "Vertex AI Metadata Store ID." value = google_vertex_ai_metadata_store.store.id } + +output "vpc" { + description = "VPC Network." + value = local.vpc +} From 652495e530e7ba1dae3e9d08ad616d33d142aee1 Mon Sep 17 00:00:00 2001 From: lcaggio Date: Sat, 4 Mar 2023 14:12:50 +0100 Subject: [PATCH 32/49] Update versions. --- blueprints/data-solutions/bq-ml/README.md | 1 + blueprints/data-solutions/bq-ml/versions.tf | 8 ++------ 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/blueprints/data-solutions/bq-ml/README.md b/blueprints/data-solutions/bq-ml/README.md index 2ea84190..ca6a1676 100644 --- a/blueprints/data-solutions/bq-ml/README.md +++ b/blueprints/data-solutions/bq-ml/README.md @@ -84,5 +84,6 @@ module "test" { project_id = "project-1" prefix = "prefix" } + # tftest modules=9 resources=46 ``` diff --git a/blueprints/data-solutions/bq-ml/versions.tf b/blueprints/data-solutions/bq-ml/versions.tf index a467162b..a7c764bf 100644 --- a/blueprints/data-solutions/bq-ml/versions.tf +++ b/blueprints/data-solutions/bq-ml/versions.tf @@ -12,20 +12,16 @@ # See the License for the specific language governing permissions and # limitations under the License. -# tfdoc:file:description Terraform version. - terraform { required_version = ">= 1.3.1" required_providers { google = { source = "hashicorp/google" - version = ">= 4.50.0" # tftest + version = ">= 4.55.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.50.0" # tftest + version = ">= 4.55.0" # tftest } } } - - From 2b8ba16a9a5db926117df6d03c6337abc40d5686 Mon Sep 17 00:00:00 2001 From: lcaggio Date: Sat, 4 Mar 2023 14:32:54 +0100 Subject: [PATCH 33/49] Fix typos --- blueprints/data-solutions/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blueprints/data-solutions/README.md b/blueprints/data-solutions/README.md index a2060560..5fc832b3 100644 --- a/blueprints/data-solutions/README.md +++ b/blueprints/data-solutions/README.md @@ -73,6 +73,6 @@ This [blueprint](./shielded-folder/) implements an opinionated folder configurat ### BigQuery ML and Vertex AI Pipeline -This [blueprint](./bq-ml/) implements the infrastructure required to have a fully functional develpement environment using BigQuery ML and Vertex AI to develop and deploy a machine learning model to be used from Vertex AI endpoint or in BigQuery ML. +This [blueprint](./bq-ml/) implements the infrastructure required to have a fully functional development environment using BigQuery ML and Vertex AI to develop and deploy a machine learning model to be used from Vertex AI endpoint or in BigQuery ML.
From 8fc9549c5803163fbe3c6cada0966c08bbf2bf84 Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Sun, 5 Mar 2023 17:08:43 +0100 Subject: [PATCH 34/49] add support for proxy and psc subnets to module factory (#1211) --- fast/stages/2-networking-a-peering/README.md | 17 ++++++----- .../2-networking-a-peering/spoke-dev.tf | 26 ++++------------- .../2-networking-a-peering/spoke-prod.tf | 26 ++++------------- .../2-networking-a-peering/variables.tf | 24 ---------------- fast/stages/2-networking-b-vpn/README.md | 17 ++++++----- fast/stages/2-networking-b-vpn/spoke-dev.tf | 26 ++++------------- fast/stages/2-networking-b-vpn/spoke-prod.tf | 26 ++++------------- fast/stages/2-networking-b-vpn/variables.tf | 24 ---------------- fast/stages/2-networking-c-nva/README.md | 19 ++++++------- fast/stages/2-networking-c-nva/main.tf | 6 ---- fast/stages/2-networking-c-nva/spoke-dev.tf | 14 ---------- fast/stages/2-networking-c-nva/spoke-prod.tf | 14 ---------- fast/stages/2-networking-c-nva/variables.tf | 18 ------------ .../2-networking-d-separate-envs/README.md | 17 ++++++----- .../2-networking-d-separate-envs/main.tf | 13 --------- .../2-networking-d-separate-envs/spoke-dev.tf | 13 ++++----- .../spoke-prod.tf | 13 ++++----- .../2-networking-d-separate-envs/variables.tf | 16 ----------- modules/net-vpc/README.md | 20 +++++++++++-- modules/net-vpc/subnets.tf | 28 +++++++++---------- tests/modules/net_vpc/examples/factory.yaml | 10 ++++++- tools/plan_summary.py | 1 - 22 files changed, 109 insertions(+), 279 deletions(-) diff --git a/fast/stages/2-networking-a-peering/README.md b/fast/stages/2-networking-a-peering/README.md index ac282c8d..293a768e 100644 --- a/fast/stages/2-networking-a-peering/README.md +++ b/fast/stages/2-networking-a-peering/README.md @@ -347,20 +347,19 @@ DNS configurations are centralised in the `dns-*.tf` files. Spokes delegate DNS | [automation](variables.tf#L17) | Automation resources created by the bootstrap stage. | object({…}) | ✓ | | 0-bootstrap | | [billing_account](variables.tf#L25) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | object({…}) | ✓ | | 0-bootstrap | | [folder_ids](variables.tf#L92) | Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created. | object({…}) | ✓ | | 1-resman | -| [organization](variables.tf#L126) | Organization details. | object({…}) | ✓ | | 0-bootstrap | -| [prefix](variables.tf#L142) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 0-bootstrap | +| [organization](variables.tf#L102) | Organization details. | object({…}) | ✓ | | 0-bootstrap | +| [prefix](variables.tf#L118) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 0-bootstrap | | [custom_adv](variables.tf#L38) | Custom advertisement definitions in name => range format. | map(string) | | {…} | | | [custom_roles](variables.tf#L55) | Custom roles defined at the org level, in key => id format. | object({…}) | | null | 0-bootstrap | | [dns](variables.tf#L64) | Onprem DNS resolvers. | map(list(string)) | | {…} | | | [factories_config](variables.tf#L72) | Configuration for network resource factories. | object({…}) | | {…} | | -| [l7ilb_subnets](variables.tf#L102) | Subnets used for L7 ILBs. | object({…}) | | {…} | | -| [outputs_location](variables.tf#L136) | Path where providers and tfvars files for the following stages are written. Leave empty to disable. | string | | null | | +| [outputs_location](variables.tf#L112) | Path where providers and tfvars files for the following stages are written. Leave empty to disable. | string | | null | | | [peering_configs](variables-peerings.tf#L19) | Peering configurations. | map(object({…})) | | {…} | | -| [psa_ranges](variables.tf#L153) | IP ranges used for Private Service Access (e.g. CloudSQL). | object({…}) | | null | | -| [regions](variables.tf#L190) | Region definitions. | object({…}) | | {…} | | -| [router_onprem_configs](variables.tf#L202) | Configurations for routers used for onprem connectivity. | map(object({…})) | | {…} | | -| [service_accounts](variables.tf#L220) | Automation service accounts in name => email format. | object({…}) | | null | 1-resman | -| [vpn_onprem_configs](variables.tf#L234) | VPN gateway configuration for onprem interconnection. | map(object({…})) | | {…} | | +| [psa_ranges](variables.tf#L129) | IP ranges used for Private Service Access (e.g. CloudSQL). | object({…}) | | null | | +| [regions](variables.tf#L166) | Region definitions. | object({…}) | | {…} | | +| [router_onprem_configs](variables.tf#L178) | Configurations for routers used for onprem connectivity. | map(object({…})) | | {…} | | +| [service_accounts](variables.tf#L196) | Automation service accounts in name => email format. | object({…}) | | null | 1-resman | +| [vpn_onprem_configs](variables.tf#L210) | VPN gateway configuration for onprem interconnection. | map(object({…})) | | {…} | | ## Outputs diff --git a/fast/stages/2-networking-a-peering/spoke-dev.tf b/fast/stages/2-networking-a-peering/spoke-dev.tf index ed7d13cb..17b4a87a 100644 --- a/fast/stages/2-networking-a-peering/spoke-dev.tf +++ b/fast/stages/2-networking-a-peering/spoke-dev.tf @@ -16,19 +16,6 @@ # tfdoc:file:description Dev spoke VPC and related resources. -locals { - _l7ilb_subnets_dev = [ - for v in var.l7ilb_subnets.dev : merge(v, { - active = true - region = lookup(var.regions, v.region, v.region) - })] - l7ilb_subnets_dev = [ - for v in local._l7ilb_subnets_dev : merge(v, { - name = "dev-l7ilb-${local.region_shortnames[v.region]}" - }) - ] -} - module "dev-spoke-project" { source = "../../../modules/project" billing_account = var.billing_account.id @@ -57,13 +44,12 @@ module "dev-spoke-project" { } module "dev-spoke-vpc" { - source = "../../../modules/net-vpc" - project_id = module.dev-spoke-project.project_id - name = "dev-spoke-0" - mtu = 1500 - data_folder = "${var.factories_config.data_dir}/subnets/dev" - psa_config = try(var.psa_ranges.dev, null) - subnets_proxy_only = local.l7ilb_subnets_dev + source = "../../../modules/net-vpc" + project_id = module.dev-spoke-project.project_id + name = "dev-spoke-0" + mtu = 1500 + data_folder = "${var.factories_config.data_dir}/subnets/dev" + psa_config = try(var.psa_ranges.dev, null) # set explicit routes for googleapis in case the default route is deleted routes = { private-googleapis = { diff --git a/fast/stages/2-networking-a-peering/spoke-prod.tf b/fast/stages/2-networking-a-peering/spoke-prod.tf index f584b32d..927c74af 100644 --- a/fast/stages/2-networking-a-peering/spoke-prod.tf +++ b/fast/stages/2-networking-a-peering/spoke-prod.tf @@ -16,19 +16,6 @@ # tfdoc:file:description Production spoke VPC and related resources. -locals { - _l7ilb_subnets_prod = [ - for v in var.l7ilb_subnets.prod : merge(v, { - active = true - region = lookup(var.regions, v.region, v.region) - })] - l7ilb_subnets_prod = [ - for v in local._l7ilb_subnets_prod : merge(v, { - name = "prod-l7ilb-${local.region_shortnames[v.region]}" - }) - ] -} - module "prod-spoke-project" { source = "../../../modules/project" billing_account = var.billing_account.id @@ -57,13 +44,12 @@ module "prod-spoke-project" { } module "prod-spoke-vpc" { - source = "../../../modules/net-vpc" - project_id = module.prod-spoke-project.project_id - name = "prod-spoke-0" - mtu = 1500 - data_folder = "${var.factories_config.data_dir}/subnets/prod" - psa_config = try(var.psa_ranges.prod, null) - subnets_proxy_only = local.l7ilb_subnets_prod + source = "../../../modules/net-vpc" + project_id = module.prod-spoke-project.project_id + name = "prod-spoke-0" + mtu = 1500 + data_folder = "${var.factories_config.data_dir}/subnets/prod" + psa_config = try(var.psa_ranges.prod, null) # set explicit routes for googleapis in case the default route is deleted routes = { private-googleapis = { diff --git a/fast/stages/2-networking-a-peering/variables.tf b/fast/stages/2-networking-a-peering/variables.tf index f0443dc0..93ba2bea 100644 --- a/fast/stages/2-networking-a-peering/variables.tf +++ b/fast/stages/2-networking-a-peering/variables.tf @@ -99,30 +99,6 @@ variable "folder_ids" { }) } -variable "l7ilb_subnets" { - description = "Subnets used for L7 ILBs." - type = object({ - dev = optional(list(object({ - ip_cidr_range = string - region = string - })), []) - prod = optional(list(object({ - ip_cidr_range = string - region = string - })), []) - }) - default = { - dev = [ - { ip_cidr_range = "10.128.60.0/24", region = "primary" }, - { ip_cidr_range = "10.128.61.0/24", region = "secondary" } - ] - prod = [ - { ip_cidr_range = "10.128.92.0/24", region = "primary" }, - { ip_cidr_range = "10.128.93.0/24", region = "secondary" } - ] - } -} - variable "organization" { # tfdoc:variable:source 0-bootstrap description = "Organization details." diff --git a/fast/stages/2-networking-b-vpn/README.md b/fast/stages/2-networking-b-vpn/README.md index a34b4135..b2f34567 100644 --- a/fast/stages/2-networking-b-vpn/README.md +++ b/fast/stages/2-networking-b-vpn/README.md @@ -372,20 +372,19 @@ DNS configurations are centralised in the `dns-*.tf` files. Spokes delegate DNS | [automation](variables.tf#L17) | Automation resources created by the bootstrap stage. | object({…}) | ✓ | | 0-bootstrap | | [billing_account](variables.tf#L25) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | object({…}) | ✓ | | 0-bootstrap | | [folder_ids](variables.tf#L92) | Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created. | object({…}) | ✓ | | 1-resman | -| [organization](variables.tf#L126) | Organization details. | object({…}) | ✓ | | 0-bootstrap | -| [prefix](variables.tf#L142) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 0-bootstrap | +| [organization](variables.tf#L102) | Organization details. | object({…}) | ✓ | | 0-bootstrap | +| [prefix](variables.tf#L118) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 0-bootstrap | | [custom_adv](variables.tf#L38) | Custom advertisement definitions in name => range format. | map(string) | | {…} | | | [custom_roles](variables.tf#L55) | Custom roles defined at the org level, in key => id format. | object({…}) | | null | 0-bootstrap | | [dns](variables.tf#L64) | Onprem DNS resolvers. | map(list(string)) | | {…} | | | [factories_config](variables.tf#L72) | Configuration for network resource factories. | object({…}) | | {…} | | -| [l7ilb_subnets](variables.tf#L102) | Subnets used for L7 ILBs. | object({…}) | | {…} | | -| [outputs_location](variables.tf#L136) | Path where providers and tfvars files for the following stages are written. Leave empty to disable. | string | | null | | -| [psa_ranges](variables.tf#L153) | IP ranges used for Private Service Access (e.g. CloudSQL). | object({…}) | | null | | -| [regions](variables.tf#L190) | Region definitions. | object({…}) | | {…} | | -| [router_onprem_configs](variables.tf#L202) | Configurations for routers used for onprem connectivity. | map(object({…})) | | {…} | | +| [outputs_location](variables.tf#L112) | Path where providers and tfvars files for the following stages are written. Leave empty to disable. | string | | null | | +| [psa_ranges](variables.tf#L129) | IP ranges used for Private Service Access (e.g. CloudSQL). | object({…}) | | null | | +| [regions](variables.tf#L166) | Region definitions. | object({…}) | | {…} | | +| [router_onprem_configs](variables.tf#L178) | Configurations for routers used for onprem connectivity. | map(object({…})) | | {…} | | | [router_spoke_configs](variables-vpn.tf#L18) | Configurations for routers used for internal connectivity. | map(object({…})) | | {…} | | -| [service_accounts](variables.tf#L220) | Automation service accounts in name => email format. | object({…}) | | null | 1-resman | -| [vpn_onprem_configs](variables.tf#L234) | VPN gateway configuration for onprem interconnection. | map(object({…})) | | {…} | | +| [service_accounts](variables.tf#L196) | Automation service accounts in name => email format. | object({…}) | | null | 1-resman | +| [vpn_onprem_configs](variables.tf#L210) | VPN gateway configuration for onprem interconnection. | map(object({…})) | | {…} | | | [vpn_spoke_configs](variables-vpn.tf#L37) | VPN gateway configuration for spokes. | map(object({…})) | | {…} | | ## Outputs diff --git a/fast/stages/2-networking-b-vpn/spoke-dev.tf b/fast/stages/2-networking-b-vpn/spoke-dev.tf index ed7d13cb..17b4a87a 100644 --- a/fast/stages/2-networking-b-vpn/spoke-dev.tf +++ b/fast/stages/2-networking-b-vpn/spoke-dev.tf @@ -16,19 +16,6 @@ # tfdoc:file:description Dev spoke VPC and related resources. -locals { - _l7ilb_subnets_dev = [ - for v in var.l7ilb_subnets.dev : merge(v, { - active = true - region = lookup(var.regions, v.region, v.region) - })] - l7ilb_subnets_dev = [ - for v in local._l7ilb_subnets_dev : merge(v, { - name = "dev-l7ilb-${local.region_shortnames[v.region]}" - }) - ] -} - module "dev-spoke-project" { source = "../../../modules/project" billing_account = var.billing_account.id @@ -57,13 +44,12 @@ module "dev-spoke-project" { } module "dev-spoke-vpc" { - source = "../../../modules/net-vpc" - project_id = module.dev-spoke-project.project_id - name = "dev-spoke-0" - mtu = 1500 - data_folder = "${var.factories_config.data_dir}/subnets/dev" - psa_config = try(var.psa_ranges.dev, null) - subnets_proxy_only = local.l7ilb_subnets_dev + source = "../../../modules/net-vpc" + project_id = module.dev-spoke-project.project_id + name = "dev-spoke-0" + mtu = 1500 + data_folder = "${var.factories_config.data_dir}/subnets/dev" + psa_config = try(var.psa_ranges.dev, null) # set explicit routes for googleapis in case the default route is deleted routes = { private-googleapis = { diff --git a/fast/stages/2-networking-b-vpn/spoke-prod.tf b/fast/stages/2-networking-b-vpn/spoke-prod.tf index f584b32d..927c74af 100644 --- a/fast/stages/2-networking-b-vpn/spoke-prod.tf +++ b/fast/stages/2-networking-b-vpn/spoke-prod.tf @@ -16,19 +16,6 @@ # tfdoc:file:description Production spoke VPC and related resources. -locals { - _l7ilb_subnets_prod = [ - for v in var.l7ilb_subnets.prod : merge(v, { - active = true - region = lookup(var.regions, v.region, v.region) - })] - l7ilb_subnets_prod = [ - for v in local._l7ilb_subnets_prod : merge(v, { - name = "prod-l7ilb-${local.region_shortnames[v.region]}" - }) - ] -} - module "prod-spoke-project" { source = "../../../modules/project" billing_account = var.billing_account.id @@ -57,13 +44,12 @@ module "prod-spoke-project" { } module "prod-spoke-vpc" { - source = "../../../modules/net-vpc" - project_id = module.prod-spoke-project.project_id - name = "prod-spoke-0" - mtu = 1500 - data_folder = "${var.factories_config.data_dir}/subnets/prod" - psa_config = try(var.psa_ranges.prod, null) - subnets_proxy_only = local.l7ilb_subnets_prod + source = "../../../modules/net-vpc" + project_id = module.prod-spoke-project.project_id + name = "prod-spoke-0" + mtu = 1500 + data_folder = "${var.factories_config.data_dir}/subnets/prod" + psa_config = try(var.psa_ranges.prod, null) # set explicit routes for googleapis in case the default route is deleted routes = { private-googleapis = { diff --git a/fast/stages/2-networking-b-vpn/variables.tf b/fast/stages/2-networking-b-vpn/variables.tf index f0443dc0..93ba2bea 100644 --- a/fast/stages/2-networking-b-vpn/variables.tf +++ b/fast/stages/2-networking-b-vpn/variables.tf @@ -99,30 +99,6 @@ variable "folder_ids" { }) } -variable "l7ilb_subnets" { - description = "Subnets used for L7 ILBs." - type = object({ - dev = optional(list(object({ - ip_cidr_range = string - region = string - })), []) - prod = optional(list(object({ - ip_cidr_range = string - region = string - })), []) - }) - default = { - dev = [ - { ip_cidr_range = "10.128.60.0/24", region = "primary" }, - { ip_cidr_range = "10.128.61.0/24", region = "secondary" } - ] - prod = [ - { ip_cidr_range = "10.128.92.0/24", region = "primary" }, - { ip_cidr_range = "10.128.93.0/24", region = "secondary" } - ] - } -} - variable "organization" { # tfdoc:variable:source 0-bootstrap description = "Organization details." diff --git a/fast/stages/2-networking-c-nva/README.md b/fast/stages/2-networking-c-nva/README.md index 33501984..02cee132 100644 --- a/fast/stages/2-networking-c-nva/README.md +++ b/fast/stages/2-networking-c-nva/README.md @@ -421,20 +421,19 @@ DNS configurations are centralised in the `dns-*.tf` files. Spokes delegate DNS | [automation](variables.tf#L17) | Automation resources created by the bootstrap stage. | object({…}) | ✓ | | 0-bootstrap | | [billing_account](variables.tf#L25) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | object({…}) | ✓ | | 0-bootstrap | | [folder_ids](variables.tf#L97) | Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created. | object({…}) | ✓ | | 1-resman | -| [organization](variables.tf#L133) | Organization details. | object({…}) | ✓ | | 0-bootstrap | -| [prefix](variables.tf#L149) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 0-bootstrap | +| [organization](variables.tf#L115) | Organization details. | object({…}) | ✓ | | 0-bootstrap | +| [prefix](variables.tf#L131) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 0-bootstrap | | [custom_adv](variables.tf#L38) | Custom advertisement definitions in name => range format. | map(string) | | {…} | | | [custom_roles](variables.tf#L60) | Custom roles defined at the org level, in key => id format. | object({…}) | | null | 0-bootstrap | | [dns](variables.tf#L69) | Onprem DNS resolvers. | map(list(string)) | | {…} | | | [factories_config](variables.tf#L77) | Configuration for network resource factories. | object({…}) | | {…} | | -| [l7ilb_subnets](variables.tf#L107) | Subnets used for L7 ILBs. | map(list(object({…}))) | | {…} | | -| [onprem_cidr](variables.tf#L125) | Onprem addresses in name => range format. | map(string) | | {…} | | -| [outputs_location](variables.tf#L143) | Path where providers and tfvars files for the following stages are written. Leave empty to disable. | string | | null | | -| [psa_ranges](variables.tf#L160) | IP ranges used for Private Service Access (e.g. CloudSQL). Ranges is in name => range format. | object({…}) | | null | | -| [regions](variables.tf#L181) | Region definitions. | object({…}) | | {…} | | -| [router_configs](variables.tf#L193) | Configurations for CRs and onprem routers. | map(object({…})) | | {…} | | -| [service_accounts](variables.tf#L216) | Automation service accounts in name => email format. | object({…}) | | null | 1-resman | -| [vpn_onprem_configs](variables.tf#L230) | VPN gateway configuration for onprem interconnection. | map(object({…})) | | {…} | | +| [onprem_cidr](variables.tf#L107) | Onprem addresses in name => range format. | map(string) | | {…} | | +| [outputs_location](variables.tf#L125) | Path where providers and tfvars files for the following stages are written. Leave empty to disable. | string | | null | | +| [psa_ranges](variables.tf#L142) | IP ranges used for Private Service Access (e.g. CloudSQL). Ranges is in name => range format. | object({…}) | | null | | +| [regions](variables.tf#L163) | Region definitions. | object({…}) | | {…} | | +| [router_configs](variables.tf#L175) | Configurations for CRs and onprem routers. | map(object({…})) | | {…} | | +| [service_accounts](variables.tf#L198) | Automation service accounts in name => email format. | object({…}) | | null | 1-resman | +| [vpn_onprem_configs](variables.tf#L212) | VPN gateway configuration for onprem interconnection. | map(object({…})) | | {…} | | ## Outputs diff --git a/fast/stages/2-networking-c-nva/main.tf b/fast/stages/2-networking-c-nva/main.tf index e4066ba3..0a56396e 100644 --- a/fast/stages/2-networking-c-nva/main.tf +++ b/fast/stages/2-networking-c-nva/main.tf @@ -18,12 +18,6 @@ locals { custom_roles = coalesce(var.custom_roles, {}) - l7ilb_subnets = { for env, v in var.l7ilb_subnets : env => [ - for s in v : merge(s, { - active = true - name = "${env}-l7ilb-${s.region}" - })] - } # combine all regions from variables and subnets regions = distinct(concat( values(var.regions), diff --git a/fast/stages/2-networking-c-nva/spoke-dev.tf b/fast/stages/2-networking-c-nva/spoke-dev.tf index b2293813..f7866783 100644 --- a/fast/stages/2-networking-c-nva/spoke-dev.tf +++ b/fast/stages/2-networking-c-nva/spoke-dev.tf @@ -16,19 +16,6 @@ # tfdoc:file:description Dev spoke VPC and related resources. -locals { - _l7ilb_subnets_dev = [ - for v in var.l7ilb_subnets.dev : merge(v, { - active = true - region = lookup(var.regions, v.region, v.region) - })] - l7ilb_subnets_dev = [ - for v in local._l7ilb_subnets_dev : merge(v, { - name = "dev-l7ilb-${local.region_shortnames[v.region]}" - }) - ] -} - module "dev-spoke-project" { source = "../../../modules/project" billing_account = var.billing_account.id @@ -63,7 +50,6 @@ module "dev-spoke-vpc" { data_folder = "${var.factories_config.data_dir}/subnets/dev" delete_default_routes_on_create = true psa_config = try(var.psa_ranges.dev, null) - subnets_proxy_only = local.l7ilb_subnets_dev # Set explicit routes for googleapis; send everything else to NVAs routes = { private-googleapis = { diff --git a/fast/stages/2-networking-c-nva/spoke-prod.tf b/fast/stages/2-networking-c-nva/spoke-prod.tf index 51ad2974..f1b79a12 100644 --- a/fast/stages/2-networking-c-nva/spoke-prod.tf +++ b/fast/stages/2-networking-c-nva/spoke-prod.tf @@ -16,19 +16,6 @@ # tfdoc:file:description Production spoke VPC and related resources. -locals { - _l7ilb_subnets_prod = [ - for v in var.l7ilb_subnets.prod : merge(v, { - active = true - region = lookup(var.regions, v.region, v.region) - })] - l7ilb_subnets_prod = [ - for v in local._l7ilb_subnets_prod : merge(v, { - name = "prod-l7ilb-${local.region_shortnames[v.region]}" - }) - ] -} - module "prod-spoke-project" { source = "../../../modules/project" billing_account = var.billing_account.id @@ -63,7 +50,6 @@ module "prod-spoke-vpc" { data_folder = "${var.factories_config.data_dir}/subnets/prod" delete_default_routes_on_create = true psa_config = try(var.psa_ranges.prod, null) - subnets_proxy_only = local.l7ilb_subnets_prod # Set explicit routes for googleapis; send everything else to NVAs routes = { private-googleapis = { diff --git a/fast/stages/2-networking-c-nva/variables.tf b/fast/stages/2-networking-c-nva/variables.tf index 50493007..5104afce 100644 --- a/fast/stages/2-networking-c-nva/variables.tf +++ b/fast/stages/2-networking-c-nva/variables.tf @@ -104,24 +104,6 @@ variable "folder_ids" { }) } -variable "l7ilb_subnets" { - description = "Subnets used for L7 ILBs." - type = map(list(object({ - ip_cidr_range = string - region = string - }))) - default = { - dev = [ - { ip_cidr_range = "10.128.159.0/24", region = "primary" }, - { ip_cidr_range = "10.128.191.0/24", region = "secondary" } - ] - prod = [ - { ip_cidr_range = "10.128.223.0/24", region = "primary" }, - { ip_cidr_range = "10.128.255.0/24", region = "secondary" } - ] - } -} - variable "onprem_cidr" { description = "Onprem addresses in name => range format." type = map(string) diff --git a/fast/stages/2-networking-d-separate-envs/README.md b/fast/stages/2-networking-d-separate-envs/README.md index 3a3b70e7..4c0aa1e8 100644 --- a/fast/stages/2-networking-d-separate-envs/README.md +++ b/fast/stages/2-networking-d-separate-envs/README.md @@ -291,19 +291,18 @@ Regions are defined via the `regions` variable which sets up a mapping between t | [automation](variables.tf#L17) | Automation resources created by the bootstrap stage. | object({…}) | ✓ | | 0-bootstrap | | [billing_account](variables.tf#L25) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | object({…}) | ✓ | | 0-bootstrap | | [folder_ids](variables.tf#L92) | Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created. | object({…}) | ✓ | | 1-resman | -| [organization](variables.tf#L118) | Organization details. | object({…}) | ✓ | | 0-bootstrap | -| [prefix](variables.tf#L134) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 0-bootstrap | +| [organization](variables.tf#L102) | Organization details. | object({…}) | ✓ | | 0-bootstrap | +| [prefix](variables.tf#L118) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 0-bootstrap | | [custom_adv](variables.tf#L38) | Custom advertisement definitions in name => range format. | map(string) | | {…} | | | [custom_roles](variables.tf#L54) | Custom roles defined at the org level, in key => id format. | object({…}) | | null | 0-bootstrap | | [dns](variables.tf#L63) | Onprem DNS resolvers. | map(list(string)) | | {…} | | | [factories_config](variables.tf#L72) | Configuration for network resource factories. | object({…}) | | {…} | | -| [l7ilb_subnets](variables.tf#L102) | Subnets used for L7 ILBs. | map(list(object({…}))) | | {…} | | -| [outputs_location](variables.tf#L128) | Path where providers and tfvars files for the following stages are written. Leave empty to disable. | string | | null | | -| [psa_ranges](variables.tf#L145) | IP ranges used for Private Service Access (e.g. CloudSQL). | object({…}) | | null | | -| [regions](variables.tf#L182) | Region definitions. | object({…}) | | {…} | | -| [router_onprem_configs](variables.tf#L192) | Configurations for routers used for onprem connectivity. | map(object({…})) | | {…} | | -| [service_accounts](variables.tf#L215) | Automation service accounts in name => email format. | object({…}) | | null | 1-resman | -| [vpn_onprem_configs](variables.tf#L227) | VPN gateway configuration for onprem interconnection. | map(object({…})) | | {…} | | +| [outputs_location](variables.tf#L112) | Path where providers and tfvars files for the following stages are written. Leave empty to disable. | string | | null | | +| [psa_ranges](variables.tf#L129) | IP ranges used for Private Service Access (e.g. CloudSQL). | object({…}) | | null | | +| [regions](variables.tf#L166) | Region definitions. | object({…}) | | {…} | | +| [router_onprem_configs](variables.tf#L176) | Configurations for routers used for onprem connectivity. | map(object({…})) | | {…} | | +| [service_accounts](variables.tf#L199) | Automation service accounts in name => email format. | object({…}) | | null | 1-resman | +| [vpn_onprem_configs](variables.tf#L211) | VPN gateway configuration for onprem interconnection. | map(object({…})) | | {…} | | ## Outputs diff --git a/fast/stages/2-networking-d-separate-envs/main.tf b/fast/stages/2-networking-d-separate-envs/main.tf index dda1c252..7e9ddc26 100644 --- a/fast/stages/2-networking-d-separate-envs/main.tf +++ b/fast/stages/2-networking-d-separate-envs/main.tf @@ -18,19 +18,6 @@ locals { custom_roles = coalesce(var.custom_roles, {}) - _l7ilb_subnets = { - for k, v in var.l7ilb_subnets : k => [ - for s in v : merge(s, { - active = true - region = lookup(var.regions, s.region, s.region) - })] - } - l7ilb_subnets = { - for k, v in local._l7ilb_subnets : k => [ - for s in v : merge(s, { - name = "${k}-l7ilb-${local.region_shortnames[s.region]}" - })] - } # combine all regions from variables and subnets regions = distinct(concat( values(var.regions), diff --git a/fast/stages/2-networking-d-separate-envs/spoke-dev.tf b/fast/stages/2-networking-d-separate-envs/spoke-dev.tf index ecd0b073..de52b428 100644 --- a/fast/stages/2-networking-d-separate-envs/spoke-dev.tf +++ b/fast/stages/2-networking-d-separate-envs/spoke-dev.tf @@ -43,13 +43,12 @@ module "dev-spoke-project" { } module "dev-spoke-vpc" { - source = "../../../modules/net-vpc" - project_id = module.dev-spoke-project.project_id - name = "dev-spoke-0" - mtu = 1500 - data_folder = "${var.factories_config.data_dir}/subnets/dev" - psa_config = try(var.psa_ranges.dev, null) - subnets_proxy_only = local.l7ilb_subnets.dev + source = "../../../modules/net-vpc" + project_id = module.dev-spoke-project.project_id + name = "dev-spoke-0" + mtu = 1500 + data_folder = "${var.factories_config.data_dir}/subnets/dev" + psa_config = try(var.psa_ranges.dev, null) # set explicit routes for googleapis in case the default route is deleted routes = { private-googleapis = { diff --git a/fast/stages/2-networking-d-separate-envs/spoke-prod.tf b/fast/stages/2-networking-d-separate-envs/spoke-prod.tf index 2e95a09e..1321b2c1 100644 --- a/fast/stages/2-networking-d-separate-envs/spoke-prod.tf +++ b/fast/stages/2-networking-d-separate-envs/spoke-prod.tf @@ -43,13 +43,12 @@ module "prod-spoke-project" { } module "prod-spoke-vpc" { - source = "../../../modules/net-vpc" - project_id = module.prod-spoke-project.project_id - name = "prod-spoke-0" - mtu = 1500 - data_folder = "${var.factories_config.data_dir}/subnets/prod" - psa_config = try(var.psa_ranges.prod, null) - subnets_proxy_only = local.l7ilb_subnets.prod + source = "../../../modules/net-vpc" + project_id = module.prod-spoke-project.project_id + name = "prod-spoke-0" + mtu = 1500 + data_folder = "${var.factories_config.data_dir}/subnets/prod" + psa_config = try(var.psa_ranges.prod, null) # set explicit routes for googleapis in case the default route is deleted routes = { private-googleapis = { diff --git a/fast/stages/2-networking-d-separate-envs/variables.tf b/fast/stages/2-networking-d-separate-envs/variables.tf index 03d56513..fde3691b 100644 --- a/fast/stages/2-networking-d-separate-envs/variables.tf +++ b/fast/stages/2-networking-d-separate-envs/variables.tf @@ -99,22 +99,6 @@ variable "folder_ids" { }) } -variable "l7ilb_subnets" { - description = "Subnets used for L7 ILBs." - type = map(list(object({ - ip_cidr_range = string - region = string - }))) - default = { - prod = [ - { ip_cidr_range = "10.128.92.0/24", region = "europe-west1" }, - ] - dev = [ - { ip_cidr_range = "10.128.60.0/24", region = "europe-west1" }, - ] - } -} - variable "organization" { # tfdoc:variable:source 0-bootstrap description = "Organization details." diff --git a/modules/net-vpc/README.md b/modules/net-vpc/README.md index 25dfaa58..2ef8b1b5 100644 --- a/modules/net-vpc/README.md +++ b/modules/net-vpc/README.md @@ -34,6 +34,7 @@ module "vpc" { ``` ### Subnet Options + ```hcl module "vpc" { source = "./fabric/modules/net-vpc" @@ -305,7 +306,7 @@ module "vpc" { ### Subnet Factory -The `net-vpc` module includes a subnet factory (see [Resource Factories](../../blueprints/factories/)) for the massive creation of subnets leveraging one configuration file per subnet. +The `net-vpc` module includes a subnet factory (see [Resource Factories](../../blueprints/factories/)) for the massive creation of subnets leveraging one configuration file per subnet. The factory also supports proxy-only and PSC subnets via the `purpose` attribute. ```hcl module "vpc" { @@ -314,7 +315,7 @@ module "vpc" { name = "my-network" data_folder = "config/subnets" } -# tftest modules=1 resources=4 files=subnet-simple,subnet-detailed inventory=factory.yaml +# tftest modules=1 resources=6 files=subnet-simple,subnet-detailed,subnet-proxy,subnet-psc inventory=factory.yaml ``` ```yaml @@ -342,6 +343,20 @@ flow_logs: # enable, set to empty map to use defaults filter_expression: null ``` +```yaml +# tftest-file id=subnet-proxy path=config/subnets/subnet-proxy.yaml +region: europe-west4 +ip_cidr_range: 10.1.0.0/24 +purpose: REGIONAL_MANAGED_PROXY +``` + +```yaml +# tftest-file id=subnet-psc path=config/subnets/subnet-psc.yaml +region: europe-west4 +ip_cidr_range: 10.2.0.0/24 +purpose: PRIVATE_SERVICE_CONNECT +``` + ### Custom Routes VPC routes can be configured through the `routes` variable. @@ -380,7 +395,6 @@ module "vpc" { # tftest modules=5 resources=15 inventory=routes.yaml ``` - ## Variables | name | description | type | required | default | diff --git a/modules/net-vpc/subnets.tf b/modules/net-vpc/subnets.tf index 7c03bfca..5a6eeb54 100644 --- a/modules/net-vpc/subnets.tf +++ b/modules/net-vpc/subnets.tf @@ -34,6 +34,8 @@ locals { iam_groups = try(v.iam_groups, []) iam_users = try(v.iam_users, []) iam_service_accounts = try(v.iam_service_accounts, []) + purpose = try(v.purpose, null) + active = try(v.active, null) } } _factory_subnets_iam = [ @@ -45,7 +47,7 @@ locals { formatlist("user:%s", lookup(v, "iam_users", [])), formatlist("serviceAccount:%s", lookup(v, "iam_service_accounts", [])) ) - } + } if v.purpose == null ] _subnet_iam_members = flatten([ for subnet, roles in(var.subnet_iam == null ? {} : var.subnet_iam) : [ @@ -61,17 +63,17 @@ locals { local._subnet_iam_members ) subnets = merge( - { for subnet in var.subnets : "${subnet.region}/${subnet.name}" => subnet }, - local._factory_subnets + { for s in var.subnets : "${s.region}/${s.name}" => s }, + { for k, v in local._factory_subnets : k => v if v.purpose == null } + ) + subnets_proxy_only = merge( + { for s in var.subnets_proxy_only : "${s.region}/${s.name}" => s }, + { for k, v in local._factory_subnets : k => v if v.purpose == "REGIONAL_MANAGED_PROXY" } + ) + subnets_psc = merge( + { for s in var.subnets_psc : "${s.region}/${s.name}" => s }, + { for k, v in local._factory_subnets : k => v if v.purpose == "PRIVATE_SERVICE_CONNECT" } ) - subnets_proxy_only = { - for subnet in var.subnets_proxy_only : - "${subnet.region}/${subnet.name}" => subnet - } - subnets_psc = { - for subnet in var.subnets_psc : - "${subnet.region}/${subnet.name}" => subnet - } } resource "google_compute_subnetwork" "subnetwork" { @@ -120,9 +122,7 @@ resource "google_compute_subnetwork" "proxy_only" { : each.value.description ) purpose = "REGIONAL_MANAGED_PROXY" - role = ( - each.value.active || each.value.active == null ? "ACTIVE" : "BACKUP" - ) + role = each.value.active != false ? "ACTIVE" : "BACKUP" } resource "google_compute_subnetwork" "psc" { diff --git a/tests/modules/net_vpc/examples/factory.yaml b/tests/modules/net_vpc/examples/factory.yaml index 0724b597..5cd74812 100644 --- a/tests/modules/net_vpc/examples/factory.yaml +++ b/tests/modules/net_vpc/examples/factory.yaml @@ -54,8 +54,16 @@ values: region: europe-west1 role: roles/compute.networkUser subnetwork: subnet-detailed + module.vpc.google_compute_subnetwork.proxy_only["europe-west4/subnet-proxy"]: + region: europe-west4 + ip_cidr_range: 10.1.0.0/24 + purpose: REGIONAL_MANAGED_PROXY + module.vpc.google_compute_subnetwork.psc["europe-west4/subnet-psc"]: + region: europe-west4 + ip_cidr_range: 10.2.0.0/24 + purpose: PRIVATE_SERVICE_CONNECT counts: google_compute_network: 1 - google_compute_subnetwork: 2 + google_compute_subnetwork: 4 google_compute_subnetwork_iam_binding: 1 diff --git a/tools/plan_summary.py b/tools/plan_summary.py index ae52c86c..441c638c 100755 --- a/tools/plan_summary.py +++ b/tools/plan_summary.py @@ -15,7 +15,6 @@ # limitations under the License. import click -import os import sys import tempfile import yaml From d3a6c9d1f1c6061431383384d700136cbbb40cb7 Mon Sep 17 00:00:00 2001 From: Ludo Date: Sun, 5 Mar 2023 18:53:46 +0100 Subject: [PATCH 35/49] update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5eebc39..6222312a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,7 @@ All notable changes to this project will be documented in this file. ### FAST +- [[#1211](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1211)] **incompatible change:** Add support for proxy and psc subnets to net-vpc module factory ([ludoo](https://github.com/ludoo)) - [[#1209](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1209)] Billing exclusion support for FAST mt resman ([ludoo](https://github.com/ludoo)) - [[#1207](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1207)] Allow preventing creation of billing IAM roles in FAST, add instructions on delayed billing association ([ludoo](https://github.com/ludoo)) - [[#1184](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1184)] **incompatible change:** Allow multiple peer gateways in VPN HA module ([ludoo](https://github.com/ludoo)) @@ -63,6 +64,7 @@ All notable changes to this project will be documented in this file. ### MODULES +- [[#1211](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1211)] **incompatible change:** Add support for proxy and psc subnets to net-vpc module factory ([ludoo](https://github.com/ludoo)) - [[#1206](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1206)] Dataproc module. Fix output. ([lcaggio](https://github.com/lcaggio)) - [[#1205](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1205)] Fix issue with GKE cluster notifications topic & static output for pubsub module ([rosmo](https://github.com/rosmo)) - [[#1204](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1204)] Fix url_redirect issue on net-glb module ([erabusi](https://github.com/erabusi)) @@ -100,6 +102,7 @@ All notable changes to this project will be documented in this file. ### TOOLS +- [[#1211](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1211)] **incompatible change:** Add support for proxy and psc subnets to net-vpc module factory ([ludoo](https://github.com/ludoo)) - [[#1209](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1209)] Billing exclusion support for FAST mt resman ([ludoo](https://github.com/ludoo)) - [[#1208](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1208)] Fix outdated go deps, dependabot alerts ([averbuks](https://github.com/averbuks)) - [[#1182](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1182)] Bump actions versions ([juliocc](https://github.com/juliocc)) From e72ddb6a2a642b29216e2147d94d220aa11da59a Mon Sep 17 00:00:00 2001 From: Anton KOVACH <2207136+antonkovach@users.noreply.github.com> Date: Sun, 5 Mar 2023 19:16:48 +0100 Subject: [PATCH 36/49] feat: Add option to skip committing unchanged files in 0-cicd-github (#1212) When running 0-cicd-github multiple times, files that haven't changed are also committed. This change adds an option to skip committing unchanged files to prevent unnecessary commits. Co-authored-by: Ludovico Magnocavallo --- fast/extras/0-cicd-github/main.tf | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/fast/extras/0-cicd-github/main.tf b/fast/extras/0-cicd-github/main.tf index 3c42b5cf..81739cc6 100644 --- a/fast/extras/0-cicd-github/main.tf +++ b/fast/extras/0-cicd-github/main.tf @@ -154,4 +154,10 @@ resource "github_repository_file" "default" { commit_author = var.commmit_config.author commit_email = var.commmit_config.email overwrite_on_create = true + + lifecycle { + ignore_changes = [ + content, + ] + } } From 4eff309685a3887a664536de200f82f63917fb5c Mon Sep 17 00:00:00 2001 From: Justin M Date: Sun, 5 Mar 2023 12:37:23 -0600 Subject: [PATCH 37/49] Update subnet sample yaml files to use subnet_secondary_ranges (#1203) * Replaces 'secondary_ip_range:' with 'secondary_ip_ranges:' in samples * Replaces 'secondary_ip_range:' with 'secondary_ip_ranges:' in tests/ * reverts previous commit- files in tests/ don't need to be changed --------- Co-authored-by: Ludovico Magnocavallo --- .../data/subnets/dev/dev-dataplatform-ew1.yaml | 2 +- .../data/subnets/dev/dev-gke-nodes-ew1.yaml | 2 +- .../data/subnets/dev/dev-dataplatform-ew1.yaml | 2 +- .../2-networking-b-vpn/data/subnets/dev/dev-gke-nodes-ew1.yaml | 2 +- .../data/subnets/dev/dev-dataplatform-ew1.yaml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/fast/stages/2-networking-a-peering/data/subnets/dev/dev-dataplatform-ew1.yaml b/fast/stages/2-networking-a-peering/data/subnets/dev/dev-dataplatform-ew1.yaml index 92994826..2c682405 100644 --- a/fast/stages/2-networking-a-peering/data/subnets/dev/dev-dataplatform-ew1.yaml +++ b/fast/stages/2-networking-a-peering/data/subnets/dev/dev-dataplatform-ew1.yaml @@ -3,6 +3,6 @@ region: europe-west1 description: Default subnet for dev Data Platform ip_cidr_range: 10.128.48.0/24 -secondary_ip_range: +secondary_ip_ranges: pods: 100.128.48.0/20 services: 100.255.48.0/24 diff --git a/fast/stages/2-networking-a-peering/data/subnets/dev/dev-gke-nodes-ew1.yaml b/fast/stages/2-networking-a-peering/data/subnets/dev/dev-gke-nodes-ew1.yaml index c2b5cbe7..9b52511e 100644 --- a/fast/stages/2-networking-a-peering/data/subnets/dev/dev-gke-nodes-ew1.yaml +++ b/fast/stages/2-networking-a-peering/data/subnets/dev/dev-gke-nodes-ew1.yaml @@ -3,6 +3,6 @@ region: europe-west1 description: Default subnet for prod gke nodes ip_cidr_range: 10.64.0.0/24 -secondary_ip_range: +secondary_ip_ranges: pods: 100.64.0.0/16 services: 192.168.1.0/24 diff --git a/fast/stages/2-networking-b-vpn/data/subnets/dev/dev-dataplatform-ew1.yaml b/fast/stages/2-networking-b-vpn/data/subnets/dev/dev-dataplatform-ew1.yaml index 92994826..2c682405 100644 --- a/fast/stages/2-networking-b-vpn/data/subnets/dev/dev-dataplatform-ew1.yaml +++ b/fast/stages/2-networking-b-vpn/data/subnets/dev/dev-dataplatform-ew1.yaml @@ -3,6 +3,6 @@ region: europe-west1 description: Default subnet for dev Data Platform ip_cidr_range: 10.128.48.0/24 -secondary_ip_range: +secondary_ip_ranges: pods: 100.128.48.0/20 services: 100.255.48.0/24 diff --git a/fast/stages/2-networking-b-vpn/data/subnets/dev/dev-gke-nodes-ew1.yaml b/fast/stages/2-networking-b-vpn/data/subnets/dev/dev-gke-nodes-ew1.yaml index c2b5cbe7..9b52511e 100644 --- a/fast/stages/2-networking-b-vpn/data/subnets/dev/dev-gke-nodes-ew1.yaml +++ b/fast/stages/2-networking-b-vpn/data/subnets/dev/dev-gke-nodes-ew1.yaml @@ -3,6 +3,6 @@ region: europe-west1 description: Default subnet for prod gke nodes ip_cidr_range: 10.64.0.0/24 -secondary_ip_range: +secondary_ip_ranges: pods: 100.64.0.0/16 services: 192.168.1.0/24 diff --git a/fast/stages/2-networking-d-separate-envs/data/subnets/dev/dev-dataplatform-ew1.yaml b/fast/stages/2-networking-d-separate-envs/data/subnets/dev/dev-dataplatform-ew1.yaml index 92994826..2c682405 100644 --- a/fast/stages/2-networking-d-separate-envs/data/subnets/dev/dev-dataplatform-ew1.yaml +++ b/fast/stages/2-networking-d-separate-envs/data/subnets/dev/dev-dataplatform-ew1.yaml @@ -3,6 +3,6 @@ region: europe-west1 description: Default subnet for dev Data Platform ip_cidr_range: 10.128.48.0/24 -secondary_ip_range: +secondary_ip_ranges: pods: 100.128.48.0/20 services: 100.255.48.0/24 From 9e19f8960861fe61830801eab27111422f1d7a4e Mon Sep 17 00:00:00 2001 From: lcaggio Date: Sun, 5 Mar 2023 22:02:41 +0100 Subject: [PATCH 38/49] Implement PR comments. --- blueprints/README.md | 2 +- blueprints/data-solutions/README.md | 3 +- blueprints/data-solutions/bq-ml/README.md | 22 +- .../data-solutions/bq-ml/datastorage.tf | 32 +++ .../bq-ml/demo/sql/explain_predict.sql | 16 +- .../bq-ml/demo/sql/features.sql | 71 ++++-- .../data-solutions/bq-ml/demo/sql/train.sql | 6 +- blueprints/data-solutions/bq-ml/main.tf | 202 +----------------- blueprints/data-solutions/bq-ml/variables.tf | 2 +- blueprints/data-solutions/bq-ml/vertex.tf | 104 +++++++++ blueprints/data-solutions/bq-ml/vpc.tf | 64 ++++++ 11 files changed, 285 insertions(+), 239 deletions(-) create mode 100644 blueprints/data-solutions/bq-ml/datastorage.tf create mode 100644 blueprints/data-solutions/bq-ml/vertex.tf create mode 100644 blueprints/data-solutions/bq-ml/vpc.tf diff --git a/blueprints/README.md b/blueprints/README.md index e7136d9c..49b374ea 100644 --- a/blueprints/README.md +++ b/blueprints/README.md @@ -6,7 +6,7 @@ Currently available blueprints: - **apigee** - [Apigee Hybrid on GKE](./apigee/hybrid-gke/), [Apigee X analytics in BigQuery](./apigee/bigquery-analytics), [Apigee network patterns](./apigee/network-patterns/) - **cloud operations** - [Active Directory Federation Services](./cloud-operations/adfs), [Cloud Asset Inventory feeds for resource change tracking and remediation](./cloud-operations/asset-inventory-feed-remediation), [Fine-grained Cloud DNS IAM via Service Directory](./cloud-operations/dns-fine-grained-iam), [Cloud DNS & Shared VPC design](./cloud-operations/dns-shared-vpc), [Delegated Role Grants](./cloud-operations/iam-delegated-role-grants), [Networking Dashboard](./cloud-operations/network-dashboard), [Managing on-prem service account keys by uploading public keys](./cloud-operations/onprem-sa-key-management), [Compute Image builder with Hashicorp Packer](./cloud-operations/packer-image-builder), [Packer example](./cloud-operations/packer-image-builder/packer), [Compute Engine quota monitoring](./cloud-operations/quota-monitoring), [Scheduled Cloud Asset Inventory Export to Bigquery](./cloud-operations/scheduled-asset-inventory-export-bq), [Configuring workload identity federation with Terraform Cloud/Enterprise workflows](./cloud-operations/terraform-cloud-dynamic-credentials), [TCP healthcheck and restart for unmanaged GCE instances](./cloud-operations/unmanaged-instances-healthcheck), [Migrate for Compute Engine (v5) blueprints](./cloud-operations/vm-migration), [Configuring workload identity federation to access Google Cloud resources from apps running on Azure](./cloud-operations/workload-identity-federation) -- **data solutions** - [GCE and GCS CMEK via centralized Cloud KMS](./data-solutions/cmek-via-centralized-kms), [Cloud Composer version 2 private instance, supporting Shared VPC and external CMEK key](./data-solutions/composer-2), [Cloud SQL instance with multi-region read replicas](./data-solutions/cloudsql-multiregion), [Data Platform](./data-solutions/data-platform-foundations), [Spinning up a foundation data pipeline on Google Cloud using Cloud Storage, Dataflow and BigQuery](./data-solutions/gcs-to-bq-with-least-privileges), [#SQL Server Always On Groups blueprint](./data-solutions/sqlserver-alwayson), [Data Playground](./data-solutions/data-playground), [MLOps with Vertex AI](./data-solutions/vertex-mlops), [Shielded Folder](./data-solutions/shielded-folder) +- **data solutions** - [GCE and GCS CMEK via centralized Cloud KMS](./data-solutions/cmek-via-centralized-kms), [Cloud Composer version 2 private instance, supporting Shared VPC and external CMEK key](./data-solutions/composer-2), [Cloud SQL instance with multi-region read replicas](./data-solutions/cloudsql-multiregion), [Data Platform](./data-solutions/data-platform-foundations), [Spinning up a foundation data pipeline on Google Cloud using Cloud Storage, Dataflow and BigQuery](./data-solutions/gcs-to-bq-with-least-privileges), [#SQL Server Always On Groups blueprint](./data-solutions/sqlserver-alwayson), [Data Playground](./data-solutions/data-playground), [MLOps with Vertex AI](./data-solutions/vertex-mlops), [Shielded Folder](./data-solutions/shielded-folder), [BigQuery ML and Vertex AI Pipeline](./data-solutions/dq'ml) - **factories** - [The why and the how of Resource Factories](./factories), [Google Cloud Identity Group Factory](./factories/cloud-identity-group-factory), [Google Cloud BQ Factory](./factories/bigquery-factory), [Google Cloud VPC Firewall Factory](./factories/net-vpc-firewall-yaml), [Minimal Project Factory](./factories/project-factory) - **GKE** - [Binary Authorization Pipeline Blueprint](./gke/binauthz), [Storage API](./gke/binauthz/image), [Multi-cluster mesh on GKE (fleet API)](./gke/multi-cluster-mesh-gke-fleet-api), [GKE Multitenant Blueprint](./gke/multitenant-fleet), [Shared VPC with GKE support](./networking/shared-vpc-gke/) - **networking** - [Calling a private Cloud Function from On-premises](./networking/private-cloud-function-from-onprem), [Decentralized firewall management](./networking/decentralized-firewall), [Decentralized firewall validator](./networking/decentralized-firewall/validator), [Network filtering with Squid](./networking/filtering-proxy), [GLB and multi-regional daisy-chaining through hybrid NEGs](./networking/glb-hybrid-neg-internal), [Hybrid connectivity to on-premise services through PSC](./networking/psc-hybrid), [HTTP Load Balancer with Cloud Armor](./networking/glb-and-armor), [Hub and Spoke via VPN](./networking/hub-and-spoke-vpn), [Hub and Spoke via VPC Peering](./networking/hub-and-spoke-peering), [Internal Load Balancer as Next Hop](./networking/ilb-next-hop), [Network filtering with Squid with isolated VPCs using Private Service Connect](./networking/filtering-proxy-psc), On-prem DNS and Google Private Access, [PSC Producer](./networking/psc-hybrid/psc-producer), [PSC Consumer](./networking/psc-hybrid/psc-consumer), [Shared VPC with optional GKE cluster](./networking/shared-vpc-gke) diff --git a/blueprints/data-solutions/README.md b/blueprints/data-solutions/README.md index 5fc832b3..9cef8bc2 100644 --- a/blueprints/data-solutions/README.md +++ b/blueprints/data-solutions/README.md @@ -73,6 +73,5 @@ This [blueprint](./shielded-folder/) implements an opinionated folder configurat ### BigQuery ML and Vertex AI Pipeline -This [blueprint](./bq-ml/) implements the infrastructure required to have a fully functional development environment using BigQuery ML and Vertex AI to develop and deploy a machine learning model to be used from Vertex AI endpoint or in BigQuery ML. - +This [blueprint](./bq-ml/) provides the necessary infrastructure to create a complete development environment for building and deploying machine learning models using BigQuery ML and Vertex AI. With this blueprint, you can deploy your models to a Vertex AI endpoint or use them within BigQuery ML.
diff --git a/blueprints/data-solutions/bq-ml/README.md b/blueprints/data-solutions/bq-ml/README.md index ca6a1676..3efa457e 100644 --- a/blueprints/data-solutions/bq-ml/README.md +++ b/blueprints/data-solutions/bq-ml/README.md @@ -1,6 +1,6 @@ # BigQuery ML and Vertex AI Pipeline -This blueprint creates the infrastructure needed to deploy and run a Vertex AI environment to develop and deploy a machine learning model to be used from Vertex AI endpoint or in BigQuery. +This blueprint provides the necessary infrastructure to create a complete development environment for building and deploying machine learning models using BigQuery ML and Vertex AI. With this blueprint, you can deploy your models to a Vertex AI endpoint or use them within BigQuery ML. This is the high-level diagram: @@ -30,15 +30,15 @@ This sample creates several distinct groups of resources: ### Virtual Private Cloud (VPC) design -As is often the case in real-world configurations, this blueprint accepts an existing Shared-VPC via the `network_config` variable as input. +As is often the case in real-world configurations, this blueprint accepts an existing Shared-VPC via the `vpc_config` variable as input. -### Customer Managed Encryption Key +### Customer Managed Encryption Keys As is often the case in real-world configurations, this blueprint accepts as input existing Cloud KMS keys to encrypt resources via the `service_encryption_keys` variable. ## Demo -In the repository [`demo`](./demo/) folder, you can find an example of creating a Vertex AI pipeline from a publically available dataset and deploying the model to be used from a Vertex AI managed endpoint or from within Bigquery. +In the [`demo`](./demo/) folder, you can find an example of creating a Vertex AI pipeline from a publicly available dataset and deploying the model to be used from a Vertex AI managed endpoint or from within Bigquery. To run the demo: @@ -47,6 +47,18 @@ To run the demo: - Run the and run [`demo/bmql_pipeline.ipynb`](demo/bmql_pipeline.ipynb) Jupyter Notebook. +## Files + +| name | description | modules | resources | +|---|---|---|---| +| [datastorage.tf](./datastorage.tf) | Datastorage resources. | bigquery-dataset · gcs | | +| [main.tf](./main.tf) | Core resources. | project | | +| [outputs.tf](./outputs.tf) | Output variables. | | | +| [variables.tf](./variables.tf) | Terraform variables. | | | +| [versions.tf](./versions.tf) | Version pins. | | | +| [vertex.tf](./vertex.tf) | Vertex resources. | iam-service-account | google_notebooks_instance · google_vertex_ai_metadata_store | +| [vpc.tf](./vpc.tf) | VPC resources. | net-cloudnat · net-vpc · net-vpc-firewall | google_project_iam_member | + ## Variables | name | description | type | required | default | @@ -54,10 +66,10 @@ To run the demo: | [prefix](variables.tf#L33) | Prefix used for resource names. | string | ✓ | | | [project_id](variables.tf#L51) | Project id references existing project if `project_create` is null. | string | ✓ | | | [location](variables.tf#L17) | The location where resources will be deployed. | string | | "US" | -| [network_config](variables.tf#L23) | Shared VPC network configurations to use. If null networks will be created in projects with pre-configured values. | object({…}) | | null | | [project_create](variables.tf#L42) | Provide values if project creation is needed, use existing project if null. Parent format: folders/folder_id or organizations/org_id. | object({…}) | | null | | [region](variables.tf#L56) | The region where resources will be deployed. | string | | "us-central1" | | [service_encryption_keys](variables.tf#L62) | Cloud KMS to use to encrypt different services. The key location should match the service region. | object({…}) | | null | +| [vpc_config](variables.tf#L23) | Shared VPC network configurations to use. If null networks will be created in projects with pre-configured values. | object({…}) | | null | ## Outputs diff --git a/blueprints/data-solutions/bq-ml/datastorage.tf b/blueprints/data-solutions/bq-ml/datastorage.tf new file mode 100644 index 00000000..ad591095 --- /dev/null +++ b/blueprints/data-solutions/bq-ml/datastorage.tf @@ -0,0 +1,32 @@ +# 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. + +# tfdoc:file:description Datastorage resources. + +module "bucket" { + source = "../../../modules/gcs" + project_id = module.project.project_id + prefix = var.prefix + location = var.location + name = "data" + encryption_key = try(local.service_encryption_keys.storage, null) # Example assignment of an encryption key +} + +module "dataset" { + source = "../../../modules/bigquery-dataset" + project_id = module.project.project_id + id = "${replace(var.prefix, "-", "_")}_data" + encryption_key = try(local.service_encryption_keys.bq, null) # Example assignment of an encryption key + location = "US" +} diff --git a/blueprints/data-solutions/bq-ml/demo/sql/explain_predict.sql b/blueprints/data-solutions/bq-ml/demo/sql/explain_predict.sql index 86309815..0c8c2063 100644 --- a/blueprints/data-solutions/bq-ml/demo/sql/explain_predict.sql +++ b/blueprints/data-solutions/bq-ml/demo/sql/explain_predict.sql @@ -14,12 +14,10 @@ * limitations under the License. */ -SELECT * -FROM ML.EXPLAIN_PREDICT(MODEL `{project-id}.{dataset}.{model-name}`, - (SELECT * EXCEPT (session_id, session_starting_ts, user_id, has_purchased) - FROM `{project-id}.{dataset}.ecommerce_abt` - WHERE extract(ISOYEAR FROM session_starting_ts) = 2023 - ), - STRUCT(5 AS top_k_features, 0.5 AS threshold) -) -LIMIT 100 +SELECT * +FROM ML.EXPLAIN_PREDICT(MODEL `{project-id}.{dataset}.{model-name}`, + (SELECT * EXCEPT (session_id, session_starting_ts, user_id, has_purchased) + FROM `{project-id}.{dataset}.ecommerce_abt` + WHERE extract(ISOYEAR FROM session_starting_ts) = 2023), + STRUCT(5 AS top_k_features, 0.5 AS threshold)) +LIMIT 100 diff --git a/blueprints/data-solutions/bq-ml/demo/sql/features.sql b/blueprints/data-solutions/bq-ml/demo/sql/features.sql index 2b55185f..a28ba85b 100644 --- a/blueprints/data-solutions/bq-ml/demo/sql/features.sql +++ b/blueprints/data-solutions/bq-ml/demo/sql/features.sql @@ -14,24 +14,55 @@ * limitations under the License. */ -CREATE view if not exists `{project_id}.{dataset}.ecommerce_abt` as - -with abt as ( - SELECT user_id, session_id, city, postal_code, browser,traffic_source, min(created_at) as session_starting_ts, sum(case when event_type = 'purchase' then 1 else 0 end) has_purchased - FROM `bigquery-public-data.thelook_ecommerce.events` - group by user_id, session_id, city, postal_code, browser, traffic_source -), previous_orders as ( -select user_id, array_agg (struct(created_at as order_creations_ts, o.order_id, o.status, oi.order_cost )) as user_orders - from `bigquery-public-data.thelook_ecommerce.orders` o - join (select order_id, sum(sale_price) order_cost - from `bigquery-public-data.thelook_ecommerce.order_items` group by 1) oi - on o.order_id = oi.order_id - group by 1 +CREATE VIEW if NOT EXISTS `{project_id}.{dataset}.ecommerce_abt` AS +WITH abt AS ( + SELECT user_id, + session_id, + city, + postal_code, + browser, + traffic_source, + min(created_at) AS session_starting_ts, + sum(CASE WHEN event_type = 'purchase' THEN 1 ELSE 0 END) has_purchased + FROM `bigquery-public-data.thelook_ecommerce.events` + GROUP BY user_id, + session_id, + city, + postal_code, + browser, + traffic_source +), previous_orders AS ( + SELECT user_id, + array_agg (struct(created_at AS order_creations_ts, + o.order_id, + o.status, + oi.order_cost)) as user_orders + FROM `bigquery-public-data.thelook_ecommerce.orders` o + JOIN (SELECT order_id, + sum(sale_price) order_cost + FROM `bigquery-public-data.thelook_ecommerce.order_items` + GROUP BY 1) oi + ON o.order_id = oi.order_id + GROUP BY 1 ) -select abt.*, case when extract(DAYOFWEEK from session_starting_ts) in (1,7) then 'WEEKEND' else 'WEEKDAY' end as day_of_week, extract(hour from session_starting_ts) hour_of_day - , (select count(distinct uo.order_id) from unnest(user_orders) uo where uo.order_creations_ts < session_starting_ts and status in ('Shipped', 'Complete', 'Processing') ) as number_of_successful_orders - , IFNULL((select sum(distinct uo.order_cost) from unnest(user_orders) uo where uo.order_creations_ts < session_starting_ts and status in ('Shipped', 'Complete', 'Processing') ), 0) as sum_previous_orders - , (select count(distinct uo.order_id) from unnest(user_orders) uo where uo.order_creations_ts < session_starting_ts and status in ('Cancelled', 'Returned') ) as number_of_unsuccessful_orders -from abt - left join previous_orders pso - on abt.user_id = pso.user_id +SELECT abt.*, + CASE WHEN extract(DAYOFWEEK FROM session_starting_ts) IN (1,7) + THEN 'WEEKEND' + ELSE 'WEEKDAY' + END AS day_of_week, + extract(HOUR FROM session_starting_ts) hour_of_day, + (SELECT count(DISTINCT uo.order_id) + FROM unnest(user_orders) uo + WHERE uo.order_creations_ts < session_starting_ts + AND status IN ('Shipped', 'Complete', 'Processing')) AS number_of_successful_orders, + IFNULL((SELECT sum(DISTINCT uo.order_cost) + FROM unnest(user_orders) uo + WHERE uo.order_creations_ts < session_starting_ts + AND status IN ('Shipped', 'Complete', 'Processing')), 0) AS sum_previous_orders, + (SELECT count(DISTINCT uo.order_id) + FROM unnest(user_orders) uo + WHERE uo.order_creations_ts < session_starting_ts + AND status IN ('Cancelled', 'Returned')) AS number_of_unsuccessful_orders +FROM abt +LEFT JOIN previous_orders pso +ON abt.user_id = pso.user_id diff --git a/blueprints/data-solutions/bq-ml/demo/sql/train.sql b/blueprints/data-solutions/bq-ml/demo/sql/train.sql index 72ce3bb1..2c30f2e6 100644 --- a/blueprints/data-solutions/bq-ml/demo/sql/train.sql +++ b/blueprints/data-solutions/bq-ml/demo/sql/train.sql @@ -22,6 +22,6 @@ OPTIONS(MODEL_TYPE='{model_type}', DATA_SPLIT_METHOD = 'RANDOM', DATA_SPLIT_EVAL_FRACTION = {split_fraction} ) AS -SELECT * EXCEPT (session_id, session_starting_ts, user_id) -FROM `{project_id}.{dataset}.ecommerce_abt_table` -WHERE extract(ISOYEAR FROM session_starting_ts) = 2022 \ No newline at end of file +SELECT * EXCEPT (session_id, session_starting_ts, user_id) +FROM `{project_id}.{dataset}.ecommerce_abt_table` +WHERE extract(ISOYEAR FROM session_starting_ts) = 2022 \ No newline at end of file diff --git a/blueprints/data-solutions/bq-ml/main.tf b/blueprints/data-solutions/bq-ml/main.tf index e91c7424..6ec7766e 100644 --- a/blueprints/data-solutions/bq-ml/main.tf +++ b/blueprints/data-solutions/bq-ml/main.tf @@ -14,45 +14,20 @@ # tfdoc:file:description Core resources. -############################################################################### -# Project # -############################################################################### - locals { service_encryption_keys = var.service_encryption_keys - shared_vpc_project = try(var.network_config.host_project, null) - + shared_vpc_project = try(var.vpc_config.host_project, null) subnet = ( local.use_shared_vpc - ? var.network_config.subnet_self_link + ? var.vpc_config.subnet_self_link : values(module.vpc.0.subnet_self_links)[0] ) + use_shared_vpc = var.vpc_config != null vpc = ( local.use_shared_vpc - ? var.network_config.network_self_link + ? var.vpc_config.network_self_link : module.vpc.0.self_link ) - use_shared_vpc = var.network_config != null - - shared_vpc_bindings = { - "roles/compute.networkUser" = [ - "robot-df", "notebooks" - ] - } - - shared_vpc_role_members = { - robot-df = "serviceAccount:${module.project.service_accounts.robots.dataflow}" - notebooks = "serviceAccount:${module.project.service_accounts.robots.notebooks}" - } - - # reassemble in a format suitable for for_each - shared_vpc_bindings_map = { - for binding in flatten([ - for role, members in local.shared_vpc_bindings : [ - for member in members : { role = role, member = member } - ] - ]) : "${binding.role}-${binding.member}" => binding - } } module "project" { @@ -75,12 +50,10 @@ module "project" { "storage.googleapis.com", "storage-component.googleapis.com" ] - shared_vpc_service_config = local.shared_vpc_project == null ? null : { attach = true host_project = local.shared_vpc_project } - service_encryption_key_ids = { compute = [try(local.service_encryption_keys.compute, null)] bq = [try(local.service_encryption_keys.bq, null)] @@ -90,170 +63,3 @@ module "project" { disable_on_destroy = false, disable_dependent_services = false } } - -############################################################################### -# Networking # -############################################################################### - -module "vpc" { - source = "../../../modules/net-vpc" - count = local.use_shared_vpc ? 0 : 1 - project_id = module.project.project_id - name = "${var.prefix}-vpc" - subnets = [ - { - ip_cidr_range = "10.0.0.0/20" - name = "${var.prefix}-subnet" - region = var.region - } - ] -} - -module "vpc-firewall" { - source = "../../../modules/net-vpc-firewall" - count = local.use_shared_vpc ? 0 : 1 - project_id = module.project.project_id - network = module.vpc.0.name - default_rules_config = { - admin_ranges = ["10.0.0.0/20"] - } - ingress_rules = { - #TODO Remove and rely on 'ssh' tag once terraform-provider-google/issues/9273 is fixed - ("${var.prefix}-iap") = { - description = "Enable SSH from IAP on Notebooks." - source_ranges = ["35.235.240.0/20"] - targets = ["notebook-instance"] - rules = [{ protocol = "tcp", ports = [22] }] - } - } -} - -module "cloudnat" { - source = "../../../modules/net-cloudnat" - count = local.use_shared_vpc ? 0 : 1 - project_id = module.project.project_id - name = "${var.prefix}-default" - region = var.region - router_network = module.vpc.0.name -} - -resource "google_project_iam_member" "shared_vpc" { - count = local.use_shared_vpc ? 1 : 0 - project = var.network_config.host_project - role = "roles/compute.networkUser" - member = "serviceAccount:${module.project.service_accounts.robots.notebooks}" -} - - -############################################################################### -# Storage # -############################################################################### - -module "bucket" { - source = "../../../modules/gcs" - project_id = module.project.project_id - prefix = var.prefix - location = var.location - name = "data" - encryption_key = try(local.service_encryption_keys.storage, null) # Example assignment of an encryption key -} - -module "dataset" { - source = "../../../modules/bigquery-dataset" - project_id = module.project.project_id - id = "${replace(var.prefix, "-", "_")}_data" - encryption_key = try(local.service_encryption_keys.bq, null) # Example assignment of an encryption key - location = "US" -} - -############################################################################### -# Vertex AI # -############################################################################### -resource "google_vertex_ai_metadata_store" "store" { - provider = google-beta - project = module.project.project_id - name = "default" #"${var.prefix}-metadata-store" - description = "Vertex Ai Metadata Store" - region = var.region - #TODO Check/Implement P4SA logic for IAM role - # encryption_spec { - # kms_key_name = var.service_encryption_keys.ai_metadata_store - # } -} - -module "service-account-notebook" { - source = "../../../modules/iam-service-account" - project_id = module.project.project_id - name = "notebook-sa" - iam_project_roles = { - (module.project.project_id) = [ - "roles/bigquery.admin", - "roles/bigquery.jobUser", - "roles/bigquery.dataEditor", - "roles/bigquery.user", - "roles/dialogflow.client", - "roles/storage.admin", - "roles/aiplatform.user", - "roles/iam.serviceAccountUser" - ] - } -} - -module "service-account-vertex" { - source = "../../../modules/iam-service-account" - project_id = module.project.project_id - name = "vertex-sa" - iam_project_roles = { - (module.project.project_id) = [ - "roles/bigquery.admin", - "roles/bigquery.jobUser", - "roles/bigquery.dataEditor", - "roles/bigquery.user", - "roles/dialogflow.client", - "roles/storage.admin", - "roles/aiplatform.user" - ] - } -} - -resource "google_notebooks_instance" "playground" { - name = "${var.prefix}-notebook" - location = format("%s-%s", var.region, "b") - machine_type = "e2-medium" - project = module.project.project_id - - container_image { - repository = "gcr.io/deeplearning-platform-release/base-cpu" - tag = "latest" - } - - install_gpu_driver = true - boot_disk_type = "PD_SSD" - boot_disk_size_gb = 110 - disk_encryption = try(local.service_encryption_keys.compute != null, false) ? "CMEK" : null - kms_key = try(local.service_encryption_keys.compute, null) - - no_public_ip = true - no_proxy_access = false - - network = local.vpc - subnet = local.subnet - - service_account = module.service-account-notebook.email - - # Enable Secure Boot - shielded_instance_config { - enable_secure_boot = true - } - - # Remove once terraform-provider-google/issues/9164 is fixed - lifecycle { - ignore_changes = [disk_encryption, kms_key] - } - - #TODO Uncomment once terraform-provider-google/issues/9273 is fixed - # tags = ["ssh"] - depends_on = [ - google_project_iam_member.shared_vpc, - ] -} diff --git a/blueprints/data-solutions/bq-ml/variables.tf b/blueprints/data-solutions/bq-ml/variables.tf index 13552a38..058284ee 100644 --- a/blueprints/data-solutions/bq-ml/variables.tf +++ b/blueprints/data-solutions/bq-ml/variables.tf @@ -20,7 +20,7 @@ variable "location" { default = "US" } -variable "network_config" { +variable "vpc_config" { description = "Shared VPC network configurations to use. If null networks will be created in projects with pre-configured values." type = object({ host_project = string diff --git a/blueprints/data-solutions/bq-ml/vertex.tf b/blueprints/data-solutions/bq-ml/vertex.tf new file mode 100644 index 00000000..061bf7da --- /dev/null +++ b/blueprints/data-solutions/bq-ml/vertex.tf @@ -0,0 +1,104 @@ +# 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. + +# tfdoc:file:description Vertex resources. + +resource "google_vertex_ai_metadata_store" "store" { + provider = google-beta + project = module.project.project_id + name = "default" #"${var.prefix}-metadata-store" + description = "Vertex Ai Metadata Store" + region = var.region + #TODO Check/Implement P4SA logic for IAM role + # encryption_spec { + # kms_key_name = var.service_encryption_keys.ai_metadata_store + # } +} + +module "service-account-notebook" { + source = "../../../modules/iam-service-account" + project_id = module.project.project_id + name = "notebook-sa" + iam_project_roles = { + (module.project.project_id) = [ + "roles/bigquery.admin", + "roles/bigquery.jobUser", + "roles/bigquery.dataEditor", + "roles/bigquery.user", + "roles/dialogflow.client", + "roles/storage.admin", + "roles/aiplatform.user", + "roles/iam.serviceAccountUser" + ] + } +} + +module "service-account-vertex" { + source = "../../../modules/iam-service-account" + project_id = module.project.project_id + name = "vertex-sa" + iam_project_roles = { + (module.project.project_id) = [ + "roles/bigquery.admin", + "roles/bigquery.jobUser", + "roles/bigquery.dataEditor", + "roles/bigquery.user", + "roles/dialogflow.client", + "roles/storage.admin", + "roles/aiplatform.user" + ] + } +} + +resource "google_notebooks_instance" "playground" { + name = "${var.prefix}-notebook" + location = format("%s-%s", var.region, "b") + machine_type = "e2-medium" + project = module.project.project_id + + container_image { + repository = "gcr.io/deeplearning-platform-release/base-cpu" + tag = "latest" + } + + install_gpu_driver = true + boot_disk_type = "PD_SSD" + boot_disk_size_gb = 110 + disk_encryption = try(local.service_encryption_keys.compute != null, false) ? "CMEK" : null + kms_key = try(local.service_encryption_keys.compute, null) + + no_public_ip = true + no_proxy_access = false + + network = local.vpc + subnet = local.subnet + + service_account = module.service-account-notebook.email + + # Enable Secure Boot + shielded_instance_config { + enable_secure_boot = true + } + + # Remove once terraform-provider-google/issues/9164 is fixed + lifecycle { + ignore_changes = [disk_encryption, kms_key] + } + + #TODO Uncomment once terraform-provider-google/issues/9273 is fixed + # tags = ["ssh"] + depends_on = [ + google_project_iam_member.shared_vpc, + ] +} diff --git a/blueprints/data-solutions/bq-ml/vpc.tf b/blueprints/data-solutions/bq-ml/vpc.tf new file mode 100644 index 00000000..c581ed5c --- /dev/null +++ b/blueprints/data-solutions/bq-ml/vpc.tf @@ -0,0 +1,64 @@ +# 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. + +# tfdoc:file:description VPC resources. + +module "vpc" { + source = "../../../modules/net-vpc" + count = local.use_shared_vpc ? 0 : 1 + project_id = module.project.project_id + name = "${var.prefix}-vpc" + subnets = [ + { + ip_cidr_range = "10.0.0.0/20" + name = "${var.prefix}-subnet" + region = var.region + } + ] +} + +module "vpc-firewall" { + source = "../../../modules/net-vpc-firewall" + count = local.use_shared_vpc ? 0 : 1 + project_id = module.project.project_id + network = module.vpc.0.name + default_rules_config = { + admin_ranges = ["10.0.0.0/20"] + } + ingress_rules = { + #TODO Remove and rely on 'ssh' tag once terraform-provider-google/issues/9273 is fixed + ("${var.prefix}-iap") = { + description = "Enable SSH from IAP on Notebooks." + source_ranges = ["35.235.240.0/20"] + targets = ["notebook-instance"] + rules = [{ protocol = "tcp", ports = [22] }] + } + } +} + +module "cloudnat" { + source = "../../../modules/net-cloudnat" + count = local.use_shared_vpc ? 0 : 1 + project_id = module.project.project_id + name = "${var.prefix}-default" + region = var.region + router_network = module.vpc.0.name +} + +resource "google_project_iam_member" "shared_vpc" { + count = local.use_shared_vpc ? 1 : 0 + project = var.vpc_config.host_project + role = "roles/compute.networkUser" + member = "serviceAccount:${module.project.service_accounts.robots.notebooks}" +} From dc034d74f730c6a5cfd54d7d9cdf62580c8574a2 Mon Sep 17 00:00:00 2001 From: lcaggio Date: Sun, 5 Mar 2023 22:24:16 +0100 Subject: [PATCH 39/49] Variables. --- blueprints/data-solutions/bq-ml/README.md | 15 ++++++++------- blueprints/data-solutions/bq-ml/variables.tf | 20 ++++++++++---------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/blueprints/data-solutions/bq-ml/README.md b/blueprints/data-solutions/bq-ml/README.md index 3efa457e..39402b91 100644 --- a/blueprints/data-solutions/bq-ml/README.md +++ b/blueprints/data-solutions/bq-ml/README.md @@ -45,7 +45,6 @@ To run the demo: - Connect to the Vertex AI workbench instance - Clone this repository - Run the and run [`demo/bmql_pipeline.ipynb`](demo/bmql_pipeline.ipynb) Jupyter Notebook. - ## Files @@ -59,17 +58,19 @@ To run the demo: | [vertex.tf](./vertex.tf) | Vertex resources. | iam-service-account | google_notebooks_instance · google_vertex_ai_metadata_store | | [vpc.tf](./vpc.tf) | VPC resources. | net-cloudnat · net-vpc · net-vpc-firewall | google_project_iam_member | + + ## Variables | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [prefix](variables.tf#L33) | Prefix used for resource names. | string | ✓ | | -| [project_id](variables.tf#L51) | Project id references existing project if `project_create` is null. | string | ✓ | | +| [prefix](variables.tf#L23) | Prefix used for resource names. | string | ✓ | | +| [project_id](variables.tf#L41) | Project id references existing project if `project_create` is null. | string | ✓ | | | [location](variables.tf#L17) | The location where resources will be deployed. | string | | "US" | -| [project_create](variables.tf#L42) | Provide values if project creation is needed, use existing project if null. Parent format: folders/folder_id or organizations/org_id. | object({…}) | | null | -| [region](variables.tf#L56) | The region where resources will be deployed. | string | | "us-central1" | -| [service_encryption_keys](variables.tf#L62) | Cloud KMS to use to encrypt different services. The key location should match the service region. | object({…}) | | null | -| [vpc_config](variables.tf#L23) | Shared VPC network configurations to use. If null networks will be created in projects with pre-configured values. | object({…}) | | null | +| [project_create](variables.tf#L32) | Provide values if project creation is needed, use existing project if null. Parent format: folders/folder_id or organizations/org_id. | object({…}) | | null | +| [region](variables.tf#L46) | The region where resources will be deployed. | string | | "us-central1" | +| [service_encryption_keys](variables.tf#L52) | Cloud KMS to use to encrypt different services. The key location should match the service region. | object({…}) | | null | +| [vpc_config](variables.tf#L62) | Shared VPC network configurations to use. If null networks will be created in projects with pre-configured values. | object({…}) | | null | ## Outputs diff --git a/blueprints/data-solutions/bq-ml/variables.tf b/blueprints/data-solutions/bq-ml/variables.tf index 058284ee..3106fb10 100644 --- a/blueprints/data-solutions/bq-ml/variables.tf +++ b/blueprints/data-solutions/bq-ml/variables.tf @@ -20,16 +20,6 @@ variable "location" { default = "US" } -variable "vpc_config" { - description = "Shared VPC network configurations to use. If null networks will be created in projects with pre-configured values." - type = object({ - host_project = string - network_self_link = string - subnet_self_link = string - }) - default = null -} - variable "prefix" { description = "Prefix used for resource names." type = string @@ -68,3 +58,13 @@ variable "service_encryption_keys" { }) default = null } + +variable "vpc_config" { + description = "Shared VPC network configurations to use. If null networks will be created in projects with pre-configured values." + type = object({ + host_project = string + network_self_link = string + subnet_self_link = string + }) + default = null +} From 16f703f336d2ff1ba555a8dc839e0fcf00f0af45 Mon Sep 17 00:00:00 2001 From: lcaggio Date: Sun, 5 Mar 2023 22:30:33 +0100 Subject: [PATCH 40/49] Fix typos --- blueprints/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blueprints/README.md b/blueprints/README.md index 49b374ea..ac936708 100644 --- a/blueprints/README.md +++ b/blueprints/README.md @@ -6,7 +6,7 @@ Currently available blueprints: - **apigee** - [Apigee Hybrid on GKE](./apigee/hybrid-gke/), [Apigee X analytics in BigQuery](./apigee/bigquery-analytics), [Apigee network patterns](./apigee/network-patterns/) - **cloud operations** - [Active Directory Federation Services](./cloud-operations/adfs), [Cloud Asset Inventory feeds for resource change tracking and remediation](./cloud-operations/asset-inventory-feed-remediation), [Fine-grained Cloud DNS IAM via Service Directory](./cloud-operations/dns-fine-grained-iam), [Cloud DNS & Shared VPC design](./cloud-operations/dns-shared-vpc), [Delegated Role Grants](./cloud-operations/iam-delegated-role-grants), [Networking Dashboard](./cloud-operations/network-dashboard), [Managing on-prem service account keys by uploading public keys](./cloud-operations/onprem-sa-key-management), [Compute Image builder with Hashicorp Packer](./cloud-operations/packer-image-builder), [Packer example](./cloud-operations/packer-image-builder/packer), [Compute Engine quota monitoring](./cloud-operations/quota-monitoring), [Scheduled Cloud Asset Inventory Export to Bigquery](./cloud-operations/scheduled-asset-inventory-export-bq), [Configuring workload identity federation with Terraform Cloud/Enterprise workflows](./cloud-operations/terraform-cloud-dynamic-credentials), [TCP healthcheck and restart for unmanaged GCE instances](./cloud-operations/unmanaged-instances-healthcheck), [Migrate for Compute Engine (v5) blueprints](./cloud-operations/vm-migration), [Configuring workload identity federation to access Google Cloud resources from apps running on Azure](./cloud-operations/workload-identity-federation) -- **data solutions** - [GCE and GCS CMEK via centralized Cloud KMS](./data-solutions/cmek-via-centralized-kms), [Cloud Composer version 2 private instance, supporting Shared VPC and external CMEK key](./data-solutions/composer-2), [Cloud SQL instance with multi-region read replicas](./data-solutions/cloudsql-multiregion), [Data Platform](./data-solutions/data-platform-foundations), [Spinning up a foundation data pipeline on Google Cloud using Cloud Storage, Dataflow and BigQuery](./data-solutions/gcs-to-bq-with-least-privileges), [#SQL Server Always On Groups blueprint](./data-solutions/sqlserver-alwayson), [Data Playground](./data-solutions/data-playground), [MLOps with Vertex AI](./data-solutions/vertex-mlops), [Shielded Folder](./data-solutions/shielded-folder), [BigQuery ML and Vertex AI Pipeline](./data-solutions/dq'ml) +- **data solutions** - [GCE and GCS CMEK via centralized Cloud KMS](./data-solutions/cmek-via-centralized-kms), [Cloud Composer version 2 private instance, supporting Shared VPC and external CMEK key](./data-solutions/composer-2), [Cloud SQL instance with multi-region read replicas](./data-solutions/cloudsql-multiregion), [Data Platform](./data-solutions/data-platform-foundations), [Spinning up a foundation data pipeline on Google Cloud using Cloud Storage, Dataflow and BigQuery](./data-solutions/gcs-to-bq-with-least-privileges), [#SQL Server Always On Groups blueprint](./data-solutions/sqlserver-alwayson), [Data Playground](./data-solutions/data-playground), [MLOps with Vertex AI](./data-solutions/vertex-mlops), [Shielded Folder](./data-solutions/shielded-folder), [BigQuery ML and Vertex AI Pipeline](./data-solutions/dq-ml) - **factories** - [The why and the how of Resource Factories](./factories), [Google Cloud Identity Group Factory](./factories/cloud-identity-group-factory), [Google Cloud BQ Factory](./factories/bigquery-factory), [Google Cloud VPC Firewall Factory](./factories/net-vpc-firewall-yaml), [Minimal Project Factory](./factories/project-factory) - **GKE** - [Binary Authorization Pipeline Blueprint](./gke/binauthz), [Storage API](./gke/binauthz/image), [Multi-cluster mesh on GKE (fleet API)](./gke/multi-cluster-mesh-gke-fleet-api), [GKE Multitenant Blueprint](./gke/multitenant-fleet), [Shared VPC with GKE support](./networking/shared-vpc-gke/) - **networking** - [Calling a private Cloud Function from On-premises](./networking/private-cloud-function-from-onprem), [Decentralized firewall management](./networking/decentralized-firewall), [Decentralized firewall validator](./networking/decentralized-firewall/validator), [Network filtering with Squid](./networking/filtering-proxy), [GLB and multi-regional daisy-chaining through hybrid NEGs](./networking/glb-hybrid-neg-internal), [Hybrid connectivity to on-premise services through PSC](./networking/psc-hybrid), [HTTP Load Balancer with Cloud Armor](./networking/glb-and-armor), [Hub and Spoke via VPN](./networking/hub-and-spoke-vpn), [Hub and Spoke via VPC Peering](./networking/hub-and-spoke-peering), [Internal Load Balancer as Next Hop](./networking/ilb-next-hop), [Network filtering with Squid with isolated VPCs using Private Service Connect](./networking/filtering-proxy-psc), On-prem DNS and Google Private Access, [PSC Producer](./networking/psc-hybrid/psc-producer), [PSC Consumer](./networking/psc-hybrid/psc-consumer), [Shared VPC with optional GKE cluster](./networking/shared-vpc-gke) From f9acf61b810e6ee3faa7d2a57a0e79ac615f02a5 Mon Sep 17 00:00:00 2001 From: lcaggio Date: Sun, 5 Mar 2023 22:42:27 +0100 Subject: [PATCH 41/49] Fix README --- blueprints/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blueprints/README.md b/blueprints/README.md index ac936708..a9867ce7 100644 --- a/blueprints/README.md +++ b/blueprints/README.md @@ -6,7 +6,7 @@ Currently available blueprints: - **apigee** - [Apigee Hybrid on GKE](./apigee/hybrid-gke/), [Apigee X analytics in BigQuery](./apigee/bigquery-analytics), [Apigee network patterns](./apigee/network-patterns/) - **cloud operations** - [Active Directory Federation Services](./cloud-operations/adfs), [Cloud Asset Inventory feeds for resource change tracking and remediation](./cloud-operations/asset-inventory-feed-remediation), [Fine-grained Cloud DNS IAM via Service Directory](./cloud-operations/dns-fine-grained-iam), [Cloud DNS & Shared VPC design](./cloud-operations/dns-shared-vpc), [Delegated Role Grants](./cloud-operations/iam-delegated-role-grants), [Networking Dashboard](./cloud-operations/network-dashboard), [Managing on-prem service account keys by uploading public keys](./cloud-operations/onprem-sa-key-management), [Compute Image builder with Hashicorp Packer](./cloud-operations/packer-image-builder), [Packer example](./cloud-operations/packer-image-builder/packer), [Compute Engine quota monitoring](./cloud-operations/quota-monitoring), [Scheduled Cloud Asset Inventory Export to Bigquery](./cloud-operations/scheduled-asset-inventory-export-bq), [Configuring workload identity federation with Terraform Cloud/Enterprise workflows](./cloud-operations/terraform-cloud-dynamic-credentials), [TCP healthcheck and restart for unmanaged GCE instances](./cloud-operations/unmanaged-instances-healthcheck), [Migrate for Compute Engine (v5) blueprints](./cloud-operations/vm-migration), [Configuring workload identity federation to access Google Cloud resources from apps running on Azure](./cloud-operations/workload-identity-federation) -- **data solutions** - [GCE and GCS CMEK via centralized Cloud KMS](./data-solutions/cmek-via-centralized-kms), [Cloud Composer version 2 private instance, supporting Shared VPC and external CMEK key](./data-solutions/composer-2), [Cloud SQL instance with multi-region read replicas](./data-solutions/cloudsql-multiregion), [Data Platform](./data-solutions/data-platform-foundations), [Spinning up a foundation data pipeline on Google Cloud using Cloud Storage, Dataflow and BigQuery](./data-solutions/gcs-to-bq-with-least-privileges), [#SQL Server Always On Groups blueprint](./data-solutions/sqlserver-alwayson), [Data Playground](./data-solutions/data-playground), [MLOps with Vertex AI](./data-solutions/vertex-mlops), [Shielded Folder](./data-solutions/shielded-folder), [BigQuery ML and Vertex AI Pipeline](./data-solutions/dq-ml) +- **data solutions** - [GCE and GCS CMEK via centralized Cloud KMS](./data-solutions/cmek-via-centralized-kms), [Cloud Composer version 2 private instance, supporting Shared VPC and external CMEK key](./data-solutions/composer-2), [Cloud SQL instance with multi-region read replicas](./data-solutions/cloudsql-multiregion), [Data Platform](./data-solutions/data-platform-foundations), [Spinning up a foundation data pipeline on Google Cloud using Cloud Storage, Dataflow and BigQuery](./data-solutions/gcs-to-bq-with-least-privileges), [#SQL Server Always On Groups blueprint](./data-solutions/sqlserver-alwayson), [Data Playground](./data-solutions/data-playground), [MLOps with Vertex AI](./data-solutions/vertex-mlops), [Shielded Folder](./data-solutions/shielded-folder), [BigQuery ML and Vertex AI Pipeline](./data-solutions/bq-ml) - **factories** - [The why and the how of Resource Factories](./factories), [Google Cloud Identity Group Factory](./factories/cloud-identity-group-factory), [Google Cloud BQ Factory](./factories/bigquery-factory), [Google Cloud VPC Firewall Factory](./factories/net-vpc-firewall-yaml), [Minimal Project Factory](./factories/project-factory) - **GKE** - [Binary Authorization Pipeline Blueprint](./gke/binauthz), [Storage API](./gke/binauthz/image), [Multi-cluster mesh on GKE (fleet API)](./gke/multi-cluster-mesh-gke-fleet-api), [GKE Multitenant Blueprint](./gke/multitenant-fleet), [Shared VPC with GKE support](./networking/shared-vpc-gke/) - **networking** - [Calling a private Cloud Function from On-premises](./networking/private-cloud-function-from-onprem), [Decentralized firewall management](./networking/decentralized-firewall), [Decentralized firewall validator](./networking/decentralized-firewall/validator), [Network filtering with Squid](./networking/filtering-proxy), [GLB and multi-regional daisy-chaining through hybrid NEGs](./networking/glb-hybrid-neg-internal), [Hybrid connectivity to on-premise services through PSC](./networking/psc-hybrid), [HTTP Load Balancer with Cloud Armor](./networking/glb-and-armor), [Hub and Spoke via VPN](./networking/hub-and-spoke-vpn), [Hub and Spoke via VPC Peering](./networking/hub-and-spoke-peering), [Internal Load Balancer as Next Hop](./networking/ilb-next-hop), [Network filtering with Squid with isolated VPCs using Private Service Connect](./networking/filtering-proxy-psc), On-prem DNS and Google Private Access, [PSC Producer](./networking/psc-hybrid/psc-producer), [PSC Consumer](./networking/psc-hybrid/psc-consumer), [Shared VPC with optional GKE cluster](./networking/shared-vpc-gke) From 77db9121f91f2fa7d5befb8461b4b5613b012b06 Mon Sep 17 00:00:00 2001 From: Anton KOVACH <2207136+antonkovach@users.noreply.github.com> Date: Mon, 6 Mar 2023 09:32:36 +0100 Subject: [PATCH 42/49] feat: Add Pull Request support to 0-cicd-github (#1213) * feat: Add Pull Request support to 0-cicd-github The cloud-foundation-fabricrepository is continually evolving, and to help keep up with the changes, it would be beneficial to introduce a pull request mechanism to review and approve changes. This feature is 100% backward compatible, and by default, no pull request is created, and changes are committed directly to the main branch. However, an optional variable pull_request_config can be used to configure the title, body, head_ref, and base_ref of the pull request that will be created for the initial population or update of files. To create a pull request, in pull_request_config set the create attribute to true. base_ref defaults to main, and head_ref to the name of the head branch. If the head branch doesn't exist, it will be created from the base_ref branch. * fix README.md * fix pull_request_config title --- fast/extras/0-cicd-github/README.md | 26 +++++++++++++++++++++++--- fast/extras/0-cicd-github/main.tf | 26 +++++++++++++++++++++++++- fast/extras/0-cicd-github/variables.tf | 13 +++++++++++++ 3 files changed, 61 insertions(+), 4 deletions(-) diff --git a/fast/extras/0-cicd-github/README.md b/fast/extras/0-cicd-github/README.md index 0bd0b5be..fc4a30c6 100644 --- a/fast/extras/0-cicd-github/README.md +++ b/fast/extras/0-cicd-github/README.md @@ -116,8 +116,27 @@ Initial population depends on a modules repository being configured in the `modu ### Commit configuration -Finally, a `commit_config` variable is optional: it can be used to configure author, email and message used in commits for initial population of files, its defaults are probably fine for most use cases. +An optional variable `commit_config` can be used to configure the author, email, and message used in commits for the initial population of files. Its defaults are probably fine for most use cases. +### Pull Request configuration + +An optional variable `pull_request_config` can be used to configure the title, body, head_ref, and base_ref of the pull request created for the initial population or update of files. By default, no pull request is created. To create a pull request, set the `create` attribute to `true`. `base_ref` defaults to `main` and `head_ref` to the head branch name. If the head branch does not exist, it will be created from the base_ref branch. + +```hcl +pull_request_config = { + create = true + title = "FAST: initial loading or update" + body = "" + base_ref = "main" + head_ref = "fast-loader" +} +# tftest skip +``` + +To start using a pull request workflow, if the initial loading was created without a pull request in the past, please use the following command to delete the actual branch files from the Terraform state to keep it in the current state: +```bash +terraform state list | grep github_repository_file | awk '{print "terraform state rm '\''"$1"'\''"}' +``` @@ -126,7 +145,7 @@ Finally, a `commit_config` variable is optional: it can be used to configure aut | name | description | resources | |---|---|---| | [cicd-versions.tf](./cicd-versions.tf) | Provider version. | | -| [main.tf](./main.tf) | Module-level locals and resources. | github_actions_secret · github_repository · github_repository_deploy_key · github_repository_file · tls_private_key | +| [main.tf](./main.tf) | Module-level locals and resources. | github_actions_secret · github_branch · github_repository · github_repository_deploy_key · github_repository_file · github_repository_pull_request · tls_private_key | | [outputs.tf](./outputs.tf) | Module outputs. | | | [providers.tf](./providers.tf) | Provider configuration. | | | [variables.tf](./variables.tf) | Module variables. | | @@ -138,7 +157,8 @@ Finally, a `commit_config` variable is optional: it can be used to configure aut | [organization](variables.tf#L51) | GitHub organization. | string | ✓ | | | [commmit_config](variables.tf#L17) | Configure commit metadata. | object({…}) | | {} | | [modules_config](variables.tf#L28) | Configure access to repository module via key, and replacement for modules sources in stage repositories. | object({…}) | | null | -| [repositories](variables.tf#L56) | Repositories to create. | map(object({…})) | | {} | +| [pull_request_config](variables.tf#L56) | Configure pull request metadata. | object({…}) | | {} | +| [repositories](variables.tf#L69) | Repositories to create. | map(object({…})) | | {} | ## Outputs diff --git a/fast/extras/0-cicd-github/main.tf b/fast/extras/0-cicd-github/main.tf index 81739cc6..37607435 100644 --- a/fast/extras/0-cicd-github/main.tf +++ b/fast/extras/0-cicd-github/main.tf @@ -136,10 +136,21 @@ resource "github_actions_secret" "default" { ) } +resource "github_branch" "default" { + for_each = ( + try(var.pull_request_config.create, null) == true + ? local.repositories + : {} + ) + repository = each.key + branch = var.pull_request_config.head_ref + source_branch = var.pull_request_config.base_ref +} + resource "github_repository_file" "default" { for_each = local.modules_repo == null ? {} : local.repository_files repository = local.repositories[each.value.repository] - branch = "main" + branch = try(var.pull_request_config.head_ref, "main") file = each.value.name content = ( endswith(each.value.name, ".tf") && local.modules_repo != null @@ -161,3 +172,16 @@ resource "github_repository_file" "default" { ] } } + +resource "github_repository_pull_request" "default" { + for_each = ( + try(var.pull_request_config.create, null) == true + ? local.repositories + : {} + ) + base_repository = each.key + title = var.pull_request_config.title + body = var.pull_request_config.body + base_ref = var.pull_request_config.base_ref + head_ref = var.pull_request_config.head_ref +} diff --git a/fast/extras/0-cicd-github/variables.tf b/fast/extras/0-cicd-github/variables.tf index ea378ee7..63854ffb 100644 --- a/fast/extras/0-cicd-github/variables.tf +++ b/fast/extras/0-cicd-github/variables.tf @@ -53,6 +53,19 @@ variable "organization" { type = string } +variable "pull_request_config" { + description = "Configure pull request metadata." + type = object({ + create = optional(bool, false) + title = optional(string, "FAST: initial loading or update") + body = optional(string, "") + base_ref = optional(string, "main") + head_ref = optional(string, "fast-loader") + }) + default = {} + nullable = false +} + variable "repositories" { description = "Repositories to create." type = map(object({ From 563ef270afaaba93595625eb2bc6e4670457a139 Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Mon, 6 Mar 2023 10:38:39 +0100 Subject: [PATCH 43/49] Try plugin cache, split examples tests (#1215) * try plugin cache, split examples tests * fix mkdir * use cache --- .github/workflows/tests.yml | 81 ++++++++++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 760f8668..22f33645 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -31,7 +31,7 @@ env: TF_VERSION: 1.3.9 jobs: - examples: + examples-blueprints: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -54,6 +54,14 @@ jobs: terraform_version: ${{ env.TF_VERSION }} terraform_wrapper: false + - name: Configure provider cache + run: | + echo 'plugin_cache_dir = "$HOME/.terraform.d/plugin-cache"' \ + | tee -a $HOME/.terraformrc + echo 'disable_checkpoint = true' \ + | tee -a $HOME/.terraformrc + mkdir -p $HOME/.terraform.d/plugin-cache + # avoid conflicts with user-installed providers on local machines - name: Pin provider versions run: | @@ -66,7 +74,52 @@ jobs: run: | mkdir -p ${{ env.TF_PLUGIN_CACHE_DIR }} pip install -r tests/requirements.txt - pytest -vv tests/examples + pytest -vv -k blueprints/ tests/examples + + examples-modules: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Config auth + run: | + echo '{"type": "service_account", "project_id": "test-only"}' \ + | tee -a $GOOGLE_APPLICATION_CREDENTIALS + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: ${{ env.PYTHON_VERSION }} + cache: 'pip' + cache-dependency-path: 'tests/requirements.txt' + + - name: Set up Terraform + uses: hashicorp/setup-terraform@v2 + with: + terraform_version: ${{ env.TF_VERSION }} + terraform_wrapper: false + + - name: Configure provider cache + run: | + echo 'plugin_cache_dir = "$HOME/.terraform.d/plugin-cache"' \ + | tee -a $HOME/.terraformrc + echo 'disable_checkpoint = true' \ + | tee -a $HOME/.terraformrc + mkdir -p $HOME/.terraform.d/plugin-cache + + # avoid conflicts with user-installed providers on local machines + - name: Pin provider versions + run: | + for f in $(find . -name versions.tf); do + sed -i 's/>=\(.*# tftest\)/=\1/g' $f; + done + + - name: Run tests on documentation examples + id: pytest + run: | + mkdir -p ${{ env.TF_PLUGIN_CACHE_DIR }} + pip install -r tests/requirements.txt + pytest -vv -k modules/ tests/examples blueprints: runs-on: ubuntu-latest @@ -91,6 +144,14 @@ jobs: terraform_version: ${{ env.TF_VERSION }} terraform_wrapper: false + - name: Configure provider cache + run: | + echo 'plugin_cache_dir = "$HOME/.terraform.d/plugin-cache"' \ + | tee -a $HOME/.terraformrc + echo 'disable_checkpoint = true' \ + | tee -a $HOME/.terraformrc + mkdir -p $HOME/.terraform.d/plugin-cache + # avoid conflicts with user-installed providers on local machines - name: Pin provider versions run: | @@ -128,6 +189,14 @@ jobs: terraform_version: ${{ env.TF_VERSION }} terraform_wrapper: false + - name: Configure provider cache + run: | + echo 'plugin_cache_dir = "$HOME/.terraform.d/plugin-cache"' \ + | tee -a $HOME/.terraformrc + echo 'disable_checkpoint = true' \ + | tee -a $HOME/.terraformrc + mkdir -p $HOME/.terraform.d/plugin-cache + # avoid conflicts with user-installed providers on local machines - name: Pin provider versions run: | @@ -165,6 +234,14 @@ jobs: terraform_version: ${{ env.TF_VERSION }} terraform_wrapper: false + - name: Configure provider cache + run: | + echo 'plugin_cache_dir = "$HOME/.terraform.d/plugin-cache"' \ + | tee -a $HOME/.terraformrc + echo 'disable_checkpoint = true' \ + | tee -a $HOME/.terraformrc + mkdir -p $HOME/.terraform.d/plugin-cache + # avoid conflicts with user-installed providers on local machines - name: Pin provider versions run: | From 0852ae3778cc07d4631861cd2cf0973e35fa3322 Mon Sep 17 00:00:00 2001 From: Giorgio Conte Date: Mon, 6 Mar 2023 10:16:21 +0000 Subject: [PATCH 44/49] demo: added batch prediction example --- .../bq-ml/demo/bmql_pipeline.ipynb | 22 +++++++++++++++++-- .../bq-ml/demo/sql/explain_predict.sql | 4 ++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/blueprints/data-solutions/bq-ml/demo/bmql_pipeline.ipynb b/blueprints/data-solutions/bq-ml/demo/bmql_pipeline.ipynb index 592f3d10..46e59314 100644 --- a/blueprints/data-solutions/bq-ml/demo/bmql_pipeline.ipynb +++ b/blueprints/data-solutions/bq-ml/demo/bmql_pipeline.ipynb @@ -17,7 +17,8 @@ "source": [ "import kfp\n", "from google.cloud import aiplatform as aip\n", - "import google_cloud_pipeline_components.v1.bigquery as bqop" + "import google_cloud_pipeline_components.v1.bigquery as bqop\n", + "from google.cloud import bigquery" ] }, { @@ -255,6 +256,23 @@ "\n", "my_prediction" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# batch prediction on BigQuery\n", + "\n", + "with open(\"sql/explain_predict.sql\") as file:\n", + " train_query = file.read()\n", + "\n", + "client = bigquery_client = bigquery.Client(location=LOCATION, project=PROJECT_ID)\n", + "batch_predictions = bigquery_client.query(train_query.format(project_id=PROJECT_ID, dataset=DATASET, model_name=f'{MODEL_NAME}-fraction-10')).to_dataframe()\n", + "\n", + "batch_predictions" + ] } ], "metadata": { @@ -265,7 +283,7 @@ }, "language_info": { "name": "python", - "version": "3.8.9" + "version": "3.10.9" }, "orig_nbformat": 4, "vscode": { diff --git a/blueprints/data-solutions/bq-ml/demo/sql/explain_predict.sql b/blueprints/data-solutions/bq-ml/demo/sql/explain_predict.sql index 0c8c2063..4ef34fd9 100644 --- a/blueprints/data-solutions/bq-ml/demo/sql/explain_predict.sql +++ b/blueprints/data-solutions/bq-ml/demo/sql/explain_predict.sql @@ -15,9 +15,9 @@ */ SELECT * -FROM ML.EXPLAIN_PREDICT(MODEL `{project-id}.{dataset}.{model-name}`, +FROM ML.EXPLAIN_PREDICT(MODEL `{project_id}.{dataset}.{model-name}`, (SELECT * EXCEPT (session_id, session_starting_ts, user_id, has_purchased) - FROM `{project-id}.{dataset}.ecommerce_abt` + FROM `{project_id}.{dataset}.ecommerce_abt` WHERE extract(ISOYEAR FROM session_starting_ts) = 2023), STRUCT(5 AS top_k_features, 0.5 AS threshold)) LIMIT 100 From ef28e208d3f119954685e1d88766ec67c1fc9f5d Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Mon, 6 Mar 2023 11:44:57 +0100 Subject: [PATCH 45/49] Use composite action for test workflow prerequisite steps (#1216) * test composite action * add shell in action steps * home input * boilerplate * static home * use action in all test steps * fix step name --- .github/actions/fabric-tests/action.yml | 55 ++++++++ .github/workflows/tests.yml | 176 +++--------------------- 2 files changed, 75 insertions(+), 156 deletions(-) create mode 100644 .github/actions/fabric-tests/action.yml diff --git a/.github/actions/fabric-tests/action.yml b/.github/actions/fabric-tests/action.yml new file mode 100644 index 00000000..df5b1bd0 --- /dev/null +++ b/.github/actions/fabric-tests/action.yml @@ -0,0 +1,55 @@ +# 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 +# +# http://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. + +name: fabric-tests +description: Set up Fabric testing environment +inputs: + PYTHON_VERSION: + required: true + TERRAFORM_VERSION: + required: true +runs: + using: composite + steps: + - name: Config auth + shell: bash + run: | + echo '{"type": "service_account", "project_id": "test-only"}' \ + | tee -a $GOOGLE_APPLICATION_CREDENTIALS + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: ${{ inputs.PYTHON_VERSION }} + cache: 'pip' + cache-dependency-path: 'tests/requirements.txt' + - name: Set up Terraform + uses: hashicorp/setup-terraform@v2 + with: + terraform_version: ${{ inputs.TERRAFORM_VERSION }} + terraform_wrapper: false + - name: Configure provider cache + shell: bash + run: | + echo 'plugin_cache_dir = "/home/runner/.terraform.d/plugin-cache"' \ + | tee -a /home/runner/.terraformrc + echo 'disable_checkpoint = true' \ + | tee -a /home/runner/.terraformrc + mkdir -p /home/runner/.terraform.d/plugin-cache + # avoid conflicts with user-installed providers on local machines + - name: Pin provider versions + shell: bash + run: | + for f in $(find . -name versions.tf); do + sed -i 's/>=\(.*# tftest\)/=\1/g' $f; + done diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 22f33645..5614b048 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -36,43 +36,15 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Config auth - run: | - echo '{"type": "service_account", "project_id": "test-only"}' \ - | tee -a $GOOGLE_APPLICATION_CREDENTIALS - - - name: Set up Python - uses: actions/setup-python@v4 + - name: Call composite action fabric-tests + uses: ./.github/actions/fabric-tests with: - python-version: ${{ env.PYTHON_VERSION }} - cache: 'pip' - cache-dependency-path: 'tests/requirements.txt' - - - name: Set up Terraform - uses: hashicorp/setup-terraform@v2 - with: - terraform_version: ${{ env.TF_VERSION }} - terraform_wrapper: false - - - name: Configure provider cache - run: | - echo 'plugin_cache_dir = "$HOME/.terraform.d/plugin-cache"' \ - | tee -a $HOME/.terraformrc - echo 'disable_checkpoint = true' \ - | tee -a $HOME/.terraformrc - mkdir -p $HOME/.terraform.d/plugin-cache - - # avoid conflicts with user-installed providers on local machines - - name: Pin provider versions - run: | - for f in $(find . -name versions.tf); do - sed -i 's/>=\(.*# tftest\)/=\1/g' $f; - done + PYTHON_VERSION: ${{ env.PYTHON_VERSION }} + TERRAFORM_VERSION: ${{ env.TERRAFORM_VERSION }} - name: Run tests on documentation examples id: pytest run: | - mkdir -p ${{ env.TF_PLUGIN_CACHE_DIR }} pip install -r tests/requirements.txt pytest -vv -k blueprints/ tests/examples @@ -81,38 +53,11 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Config auth - run: | - echo '{"type": "service_account", "project_id": "test-only"}' \ - | tee -a $GOOGLE_APPLICATION_CREDENTIALS - - - name: Set up Python - uses: actions/setup-python@v4 + - name: Call composite action fabric-tests + uses: ./.github/actions/fabric-tests with: - python-version: ${{ env.PYTHON_VERSION }} - cache: 'pip' - cache-dependency-path: 'tests/requirements.txt' - - - name: Set up Terraform - uses: hashicorp/setup-terraform@v2 - with: - terraform_version: ${{ env.TF_VERSION }} - terraform_wrapper: false - - - name: Configure provider cache - run: | - echo 'plugin_cache_dir = "$HOME/.terraform.d/plugin-cache"' \ - | tee -a $HOME/.terraformrc - echo 'disable_checkpoint = true' \ - | tee -a $HOME/.terraformrc - mkdir -p $HOME/.terraform.d/plugin-cache - - # avoid conflicts with user-installed providers on local machines - - name: Pin provider versions - run: | - for f in $(find . -name versions.tf); do - sed -i 's/>=\(.*# tftest\)/=\1/g' $f; - done + PYTHON_VERSION: ${{ env.PYTHON_VERSION }} + TERRAFORM_VERSION: ${{ env.TERRAFORM_VERSION }} - name: Run tests on documentation examples id: pytest @@ -126,38 +71,11 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Config auth - run: | - echo '{"type": "service_account", "project_id": "test-only"}' \ - | tee -a $GOOGLE_APPLICATION_CREDENTIALS - - - name: Set up Python - uses: actions/setup-python@v4 + - name: Call composite action fabric-tests + uses: ./.github/actions/fabric-tests with: - python-version: ${{ env.PYTHON_VERSION }} - cache: 'pip' - cache-dependency-path: 'tests/requirements.txt' - - - name: Set up Terraform - uses: hashicorp/setup-terraform@v2 - with: - terraform_version: ${{ env.TF_VERSION }} - terraform_wrapper: false - - - name: Configure provider cache - run: | - echo 'plugin_cache_dir = "$HOME/.terraform.d/plugin-cache"' \ - | tee -a $HOME/.terraformrc - echo 'disable_checkpoint = true' \ - | tee -a $HOME/.terraformrc - mkdir -p $HOME/.terraform.d/plugin-cache - - # avoid conflicts with user-installed providers on local machines - - name: Pin provider versions - run: | - for f in $(find . -name versions.tf); do - sed -i 's/>=\(.*# tftest\)/=\1/g' $f; - done + PYTHON_VERSION: ${{ env.PYTHON_VERSION }} + TERRAFORM_VERSION: ${{ env.TERRAFORM_VERSION }} - name: Run tests environments id: pytest @@ -171,38 +89,11 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Config auth - run: | - echo '{"type": "service_account", "project_id": "test-only"}' \ - | tee -a $GOOGLE_APPLICATION_CREDENTIALS - - - name: Set up Python - uses: actions/setup-python@v4 + - name: Call composite action fabric-tests + uses: ./.github/actions/fabric-tests with: - python-version: ${{ env.PYTHON_VERSION }} - cache: 'pip' - cache-dependency-path: 'tests/requirements.txt' - - - name: Set up Terraform - uses: hashicorp/setup-terraform@v2 - with: - terraform_version: ${{ env.TF_VERSION }} - terraform_wrapper: false - - - name: Configure provider cache - run: | - echo 'plugin_cache_dir = "$HOME/.terraform.d/plugin-cache"' \ - | tee -a $HOME/.terraformrc - echo 'disable_checkpoint = true' \ - | tee -a $HOME/.terraformrc - mkdir -p $HOME/.terraform.d/plugin-cache - - # avoid conflicts with user-installed providers on local machines - - name: Pin provider versions - run: | - for f in $(find . -name versions.tf); do - sed -i 's/>=\(.*# tftest\)/=\1/g' $f; - done + PYTHON_VERSION: ${{ env.PYTHON_VERSION }} + TERRAFORM_VERSION: ${{ env.TERRAFORM_VERSION }} - name: Run tests modules id: pytest @@ -216,38 +107,11 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Config auth - run: | - echo '{"type": "service_account", "project_id": "test-only"}' \ - | tee -a $GOOGLE_APPLICATION_CREDENTIALS - - - name: Set up Python - uses: actions/setup-python@v4 + - name: Call composite action fabric-tests + uses: ./.github/actions/fabric-tests with: - python-version: ${{ env.PYTHON_VERSION }} - cache: 'pip' - cache-dependency-path: 'tests/requirements.txt' - - - name: Set up Terraform - uses: hashicorp/setup-terraform@v2 - with: - terraform_version: ${{ env.TF_VERSION }} - terraform_wrapper: false - - - name: Configure provider cache - run: | - echo 'plugin_cache_dir = "$HOME/.terraform.d/plugin-cache"' \ - | tee -a $HOME/.terraformrc - echo 'disable_checkpoint = true' \ - | tee -a $HOME/.terraformrc - mkdir -p $HOME/.terraform.d/plugin-cache - - # avoid conflicts with user-installed providers on local machines - - name: Pin provider versions - run: | - for f in $(find . -name versions.tf); do - sed -i 's/>=\(.*# tftest\)/=\1/g' $f; - done + PYTHON_VERSION: ${{ env.PYTHON_VERSION }} + TERRAFORM_VERSION: ${{ env.TERRAFORM_VERSION }} - name: Run tests on FAST stages id: pytest From c82e7cca7b3615ca3fb2d0c848d0d0f59214aa17 Mon Sep 17 00:00:00 2001 From: Giorgio Conte Date: Mon, 6 Mar 2023 11:12:36 +0000 Subject: [PATCH 46/49] added demo README file --- .../data-solutions/bq-ml/demo/README.md | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 blueprints/data-solutions/bq-ml/demo/README.md diff --git a/blueprints/data-solutions/bq-ml/demo/README.md b/blueprints/data-solutions/bq-ml/demo/README.md new file mode 100644 index 00000000..2b5af642 --- /dev/null +++ b/blueprints/data-solutions/bq-ml/demo/README.md @@ -0,0 +1,38 @@ +# BigQuery ML and Vertex AI Pipeline Demo + +This demo shows how to combine BigQuery ML (BQML) and Vertex AI to create a ML pipeline leveraging the infrastructure created in the blueprint. + +More in details, this tutorial will focus on the following three steps: + +- define a Vertex AI pipeline to create features, train and evaluate BQML models +- serve a BQ model through an API powered by Vertex AI Endpoint +- create batch prediction via BigQuery + +# Dataset + +This tutorial uses a fictitious e-commerce dataset collecting programmatically generated data from the fictitious e-commerce store called The Look. The dataset is publicy available on BigQuery at this location `bigquery-public-data.thelook_ecommerce`. + +# Goal + +The goal of this tutorial is to train a classification ML model using BigQuery ML and predict if a new web session is going to convert. + +The tutorial focuses more on how to combine Vertex AI and BigQuery ML to create a model that can be used both for near-real time and batch predictions rather than the design of the model itself. + +# Main components + +In this tutorial we will make use of the following main components: +- Big Query: + - standard: to create a view which contains the model features and the target variable + - ML: to train, evaluate and make batch predictions +- Vertex AI: + - Pipeline: to define a configurable and re-usable set of steps to train and evaluate a BQML model + - Experiment: to keep track of all the trainings done via the Pipeline + - Model Registry: to keep track of the trained versions of a specific model + - Endpoint: to serve the model via API + - Workbench: to run this demo + +# How to get started + +1. Access the Vertex AI Workbench +2. clone this repository +2. run the [`bmql_pipeline.ipynb`](bmql_pipeline.ipynb) Jupyter Notebook \ No newline at end of file From 0ac6dd65cf30666dd3044e1a396a24fff1196826 Mon Sep 17 00:00:00 2001 From: Giorgio Conte Date: Mon, 6 Mar 2023 11:21:30 +0000 Subject: [PATCH 47/49] sql fix and more comments on demo notebook --- .../data-solutions/bq-ml/demo/README.md | 2 + .../bq-ml/demo/bmql_pipeline.ipynb | 62 +++++++++++++++++-- .../bq-ml/demo/sql/explain_predict.sql | 2 +- 3 files changed, 61 insertions(+), 5 deletions(-) diff --git a/blueprints/data-solutions/bq-ml/demo/README.md b/blueprints/data-solutions/bq-ml/demo/README.md index 2b5af642..8ab748d8 100644 --- a/blueprints/data-solutions/bq-ml/demo/README.md +++ b/blueprints/data-solutions/bq-ml/demo/README.md @@ -8,6 +8,8 @@ More in details, this tutorial will focus on the following three steps: - serve a BQ model through an API powered by Vertex AI Endpoint - create batch prediction via BigQuery +In this tutorial we will also see how to make explainable predictions, in order to understand what are the most important features that most influence the algorithm outputs. + # Dataset This tutorial uses a fictitious e-commerce dataset collecting programmatically generated data from the fictitious e-commerce store called The Look. The dataset is publicy available on BigQuery at this location `bigquery-public-data.thelook_ecommerce`. diff --git a/blueprints/data-solutions/bq-ml/demo/bmql_pipeline.ipynb b/blueprints/data-solutions/bq-ml/demo/bmql_pipeline.ipynb index 46e59314..44342882 100644 --- a/blueprints/data-solutions/bq-ml/demo/bmql_pipeline.ipynb +++ b/blueprints/data-solutions/bq-ml/demo/bmql_pipeline.ipynb @@ -1,5 +1,40 @@ { "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Copyright 2023 Google LLC**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# https://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License." + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Install python requirements and import packages" + ] + }, { "cell_type": "code", "execution_count": null, @@ -26,7 +61,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Set your env variable" + "# Set your env variables" ] }, { @@ -159,7 +194,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Running the same training Verte AI pipeline with different parameters\n", + "# Running the same training Vertex AI pipeline with different parameters\n", "\n", "One of the main tasks during the training phase is to compare different models or to try the same model with different inputs. We can leverage the power of Vertex AI Pipelines to submit the same steps with different training parameters. Thanks to the experiments artifact, it is possible to easily keep track of all the tests that have been done. This simplifies the process of selecting the best model to deploy.\n", "\n", @@ -266,13 +301,32 @@ "# batch prediction on BigQuery\n", "\n", "with open(\"sql/explain_predict.sql\") as file:\n", - " train_query = file.read()\n", + " explain_predict_query = file.read()\n", "\n", "client = bigquery_client = bigquery.Client(location=LOCATION, project=PROJECT_ID)\n", - "batch_predictions = bigquery_client.query(train_query.format(project_id=PROJECT_ID, dataset=DATASET, model_name=f'{MODEL_NAME}-fraction-10')).to_dataframe()\n", + "batch_predictions = bigquery_client.query(\n", + " explain_predict_query.format(\n", + " project_id=PROJECT_ID,\n", + " dataset=DATASET,\n", + " model_name=f'{MODEL_NAME}-fraction-10')\n", + " ).to_dataframe()\n", "\n", "batch_predictions" ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Conclusions\n", + "\n", + "Thanks to this tutorial we were able to:\n", + "- Define a re-usable Vertex AI pipeline to train and evaluate BQ ML models\n", + "- Use a Vertex AI Experiment to keep track of multiple trainings for the same model with different paramenters (in this case a different split for train/test data)\n", + "- Deploy the preferred model on a Vertex AI managed Endpoint in order to serve the model for real-time use cases via API\n", + "- Make batch prediction via Big Query and see what are the top 5 features which influenced the algorithm output" + ] } ], "metadata": { diff --git a/blueprints/data-solutions/bq-ml/demo/sql/explain_predict.sql b/blueprints/data-solutions/bq-ml/demo/sql/explain_predict.sql index 4ef34fd9..e09fbd94 100644 --- a/blueprints/data-solutions/bq-ml/demo/sql/explain_predict.sql +++ b/blueprints/data-solutions/bq-ml/demo/sql/explain_predict.sql @@ -15,7 +15,7 @@ */ SELECT * -FROM ML.EXPLAIN_PREDICT(MODEL `{project_id}.{dataset}.{model-name}`, +FROM ML.EXPLAIN_PREDICT(MODEL `{project_id}.{dataset}.{model_name}`, (SELECT * EXCEPT (session_id, session_starting_ts, user_id, has_purchased) FROM `{project_id}.{dataset}.ecommerce_abt` WHERE extract(ISOYEAR FROM session_starting_ts) = 2023), From ee55127ede281a2cce164856d1af63b8c6283919 Mon Sep 17 00:00:00 2001 From: lcaggio Date: Mon, 6 Mar 2023 12:51:46 +0100 Subject: [PATCH 48/49] Fix notebook --- .../bq-ml/demo/bmql_pipeline.ipynb | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/blueprints/data-solutions/bq-ml/demo/bmql_pipeline.ipynb b/blueprints/data-solutions/bq-ml/demo/bmql_pipeline.ipynb index 44342882..ff063957 100644 --- a/blueprints/data-solutions/bq-ml/demo/bmql_pipeline.ipynb +++ b/blueprints/data-solutions/bq-ml/demo/bmql_pipeline.ipynb @@ -14,6 +14,8 @@ "metadata": {}, "outputs": [], "source": [ + "# Copyright 2023 Google LLC\n", + "#\n", "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", "# you may not use this file except in compliance with the License.\n", "# You may obtain a copy of the License at\n", @@ -51,8 +53,9 @@ "outputs": [], "source": [ "import kfp\n", - "from google.cloud import aiplatform as aip\n", "import google_cloud_pipeline_components.v1.bigquery as bqop\n", + "\n", + "from google.cloud import aiplatform as aip\n", "from google.cloud import bigquery" ] }, @@ -70,18 +73,17 @@ "metadata": {}, "outputs": [], "source": [ - "PREFIX = 'your-prefix'\n", - "PROJECT_ID = 'your-project-id'\n", - "LOCATION = 'US'\n", - "REGION = 'us-central1'\n", - "PIPELINE_NAME = 'bqml-vertex-pipeline'\n", - "MODEL_NAME = 'bqml-model'\n", "EXPERIMENT_NAME = 'bqml-experiment'\n", "ENDPOINT_DISPLAY_NAME = 'bqml-endpoint'\n", - "\n", - "SERVICE_ACCOUNT = f\"vertex-sa@{PROJECT_ID}.iam.gserviceaccount.com\"\n", + "DATASET = \"{}_data\".format(PREFIX.replace(\"-\",\"_\")) \n", + "LOCATION = 'US'\n", + "MODEL_NAME = 'bqml-model'\n", + "PIPELINE_NAME = 'bqml-vertex-pipeline'\n", "PIPELINE_ROOT = f\"gs://{PREFIX}-data\"\n", - "DATASET = \"{}_data\".format(PREFIX.replace(\"-\",\"_\")) " + "PREFIX = 'your-prefix'\n", + "PROJECT_ID = 'your-project-id'\n", + "REGION = 'us-central1'\n", + "SERVICE_ACCOUNT = f\"vertex-sa@{PROJECT_ID}.iam.gserviceaccount.com\"" ] }, { @@ -97,12 +99,12 @@ "3. Evaluate the BigQuery ML model with the standard evaluation metrics\n", "\n", "The pipeline takes as input the following variables:\n", - "- ```model_name```: the display name of the BigQuery ML model\n", - "- ```split_fraction```: the percentage of data that will be used as an evaluation dataset\n", - "- ```evaluate_job_conf```: bq dict configuration to define where to store evaluation metrics\n", "- ```dataset```: name of the dataset where the artifacts will be stored\n", + "- ```evaluate_job_conf```: bq dict configuration to define where to store evaluation metrics\n", + "- ```location```: BigQuery location\n", + "- ```model_name```: the display name of the BigQuery ML model\n", "- ```project_id```: the project id where the GCP resources will be created\n", - "- ```location```: BigQuery location" + "- ```split_fraction```: the percentage of data that will be used as an evaluation dataset" ] }, { @@ -186,7 +188,7 @@ " description='This is a new experiment to keep track of bqml trainings',\n", " project=PROJECT_ID,\n", " location=REGION\n", - " )" + ")" ] }, { @@ -218,7 +220,6 @@ " template_path=f'{PIPELINE_NAME}.json',\n", " pipeline_root=PIPELINE_ROOT,\n", " enable_caching=True\n", - " \n", " )\n", "\n", " pipeline.submit(service_account=SERVICE_ACCOUNT, experiment=my_experiment)" @@ -278,7 +279,8 @@ " 'day_of_week': 'WEEKDAY',\n", " 'traffic_source': 'Facebook',\n", " 'browser': 'Firefox',\n", - " 'hour_of_day': 20}" + " 'hour_of_day': 20\n", + "}" ] }, { @@ -337,7 +339,7 @@ }, "language_info": { "name": "python", - "version": "3.10.9" + "version": "3.8.9" }, "orig_nbformat": 4, "vscode": { From 3123d67ddfb7f0dfe02d6363ebd847cebbeceb2d Mon Sep 17 00:00:00 2001 From: Giorgio Conte Date: Mon, 6 Mar 2023 12:28:58 +0000 Subject: [PATCH 49/49] moved sql to notebook --- .../bq-ml/demo/bmql_pipeline.ipynb | 120 +++++++++++++++++- .../bq-ml/demo/sql/explain_predict.sql | 23 ---- .../bq-ml/demo/sql/features.sql | 68 ---------- .../data-solutions/bq-ml/demo/sql/train.sql | 27 ---- 4 files changed, 113 insertions(+), 125 deletions(-) delete mode 100644 blueprints/data-solutions/bq-ml/demo/sql/explain_predict.sql delete mode 100644 blueprints/data-solutions/bq-ml/demo/sql/features.sql delete mode 100644 blueprints/data-solutions/bq-ml/demo/sql/train.sql diff --git a/blueprints/data-solutions/bq-ml/demo/bmql_pipeline.ipynb b/blueprints/data-solutions/bq-ml/demo/bmql_pipeline.ipynb index ff063957..4d3f5b53 100644 --- a/blueprints/data-solutions/bq-ml/demo/bmql_pipeline.ipynb +++ b/blueprints/data-solutions/bq-ml/demo/bmql_pipeline.ipynb @@ -93,6 +93,100 @@ "source": [ "# Vertex AI Pipeline Definition\n", "\n", + "Let's first define the queries for the features and target creation and the query to train the model\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# this query creates the features for our model and the target value we would like to predict\n", + "\n", + "features_query = \"\"\"\n", + "CREATE VIEW if NOT EXISTS `{project_id}.{dataset}.ecommerce_abt` AS\n", + "WITH abt AS (\n", + " SELECT user_id,\n", + " session_id,\n", + " city,\n", + " postal_code,\n", + " browser,\n", + " traffic_source,\n", + " min(created_at) AS session_starting_ts,\n", + " sum(CASE WHEN event_type = 'purchase' THEN 1 ELSE 0 END) has_purchased\n", + " FROM `bigquery-public-data.thelook_ecommerce.events` \n", + " GROUP BY user_id,\n", + " session_id,\n", + " city,\n", + " postal_code,\n", + " browser,\n", + " traffic_source\n", + "), previous_orders AS (\n", + " SELECT user_id,\n", + " array_agg (struct(created_at AS order_creations_ts,\n", + " o.order_id,\n", + " o.status,\n", + " oi.order_cost)) as user_orders\n", + " FROM `bigquery-public-data.thelook_ecommerce.orders` o\n", + " JOIN (SELECT order_id,\n", + " sum(sale_price) order_cost \n", + " FROM `bigquery-public-data.thelook_ecommerce.order_items`\n", + " GROUP BY 1) oi\n", + " ON o.order_id = oi.order_id\n", + " GROUP BY 1\n", + ")\n", + "SELECT abt.*,\n", + " CASE WHEN extract(DAYOFWEEK FROM session_starting_ts) IN (1,7)\n", + " THEN 'WEEKEND' \n", + " ELSE 'WEEKDAY'\n", + " END AS day_of_week,\n", + " extract(HOUR FROM session_starting_ts) hour_of_day,\n", + " (SELECT count(DISTINCT uo.order_id) \n", + " FROM unnest(user_orders) uo \n", + " WHERE uo.order_creations_ts < session_starting_ts \n", + " AND status IN ('Shipped', 'Complete', 'Processing')) AS number_of_successful_orders,\n", + " IFNULL((SELECT sum(DISTINCT uo.order_cost) \n", + " FROM unnest(user_orders) uo \n", + " WHERE uo.order_creations_ts < session_starting_ts \n", + " AND status IN ('Shipped', 'Complete', 'Processing')), 0) AS sum_previous_orders,\n", + " (SELECT count(DISTINCT uo.order_id) \n", + " FROM unnest(user_orders) uo \n", + " WHERE uo.order_creations_ts < session_starting_ts \n", + " AND status IN ('Cancelled', 'Returned')) AS number_of_unsuccessful_orders\n", + "FROM abt \n", + "LEFT JOIN previous_orders pso \n", + "ON abt.user_id = pso.user_id\n", + "\"\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# this query create the train job on BQ ML\n", + "train_query = \"\"\"\n", + "CREATE OR REPLACE MODEL `{project_id}.{dataset}.{model_name}`\n", + "OPTIONS(MODEL_TYPE='{model_type}',\n", + " INPUT_LABEL_COLS=['has_purchased'],\n", + " ENABLE_GLOBAL_EXPLAIN=TRUE,\n", + " MODEL_REGISTRY='VERTEX_AI',\n", + " DATA_SPLIT_METHOD = 'RANDOM',\n", + " DATA_SPLIT_EVAL_FRACTION = {split_fraction}\n", + " ) AS \n", + "SELECT * EXCEPT (session_id, session_starting_ts, user_id) \n", + "FROM `{project_id}.{dataset}.ecommerce_abt`\n", + "WHERE extract(ISOYEAR FROM session_starting_ts) = 2022\n", + "\"\"\"" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ "In the following code block, we are defining our Vertex AI pipeline. It is made up of three main steps:\n", "1. Create a BigQuery dataset that will contain the BigQuery ML models\n", "2. Train the BigQuery ML model, in this case, a logistic regression\n", @@ -113,13 +207,6 @@ "metadata": {}, "outputs": [], "source": [ - "with open(\"sql/train.sql\") as file:\n", - " train_query = file.read()\n", - "\n", - "with open(\"sql/features.sql\") as file:\n", - " features_query = file.read()\n", - "\n", - "\n", "@kfp.dsl.pipeline(name='bqml-pipeline', pipeline_root=PIPELINE_ROOT)\n", "def pipeline(\n", " model_name: str,\n", @@ -294,6 +381,25 @@ "my_prediction" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# batch prediction on BigQuery\n", + "\n", + "explain_predict_query = \"\"\"\n", + "SELECT *\n", + "FROM ML.EXPLAIN_PREDICT(MODEL `{project_id}.{dataset}.{model_name}`,\n", + " (SELECT * EXCEPT (session_id, session_starting_ts, user_id, has_purchased) \n", + " FROM `{project_id}.{dataset}.ecommerce_abt`\n", + " WHERE extract(ISOYEAR FROM session_starting_ts) = 2023),\n", + " STRUCT(5 AS top_k_features, 0.5 AS threshold))\n", + "LIMIT 100\n", + "\"\"\"" + ] + }, { "cell_type": "code", "execution_count": null, diff --git a/blueprints/data-solutions/bq-ml/demo/sql/explain_predict.sql b/blueprints/data-solutions/bq-ml/demo/sql/explain_predict.sql deleted file mode 100644 index e09fbd94..00000000 --- a/blueprints/data-solutions/bq-ml/demo/sql/explain_predict.sql +++ /dev/null @@ -1,23 +0,0 @@ -/* -* 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. -*/ - -SELECT * -FROM ML.EXPLAIN_PREDICT(MODEL `{project_id}.{dataset}.{model_name}`, - (SELECT * EXCEPT (session_id, session_starting_ts, user_id, has_purchased) - FROM `{project_id}.{dataset}.ecommerce_abt` - WHERE extract(ISOYEAR FROM session_starting_ts) = 2023), - STRUCT(5 AS top_k_features, 0.5 AS threshold)) -LIMIT 100 diff --git a/blueprints/data-solutions/bq-ml/demo/sql/features.sql b/blueprints/data-solutions/bq-ml/demo/sql/features.sql deleted file mode 100644 index a28ba85b..00000000 --- a/blueprints/data-solutions/bq-ml/demo/sql/features.sql +++ /dev/null @@ -1,68 +0,0 @@ -/* -* 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. -*/ - -CREATE VIEW if NOT EXISTS `{project_id}.{dataset}.ecommerce_abt` AS -WITH abt AS ( - SELECT user_id, - session_id, - city, - postal_code, - browser, - traffic_source, - min(created_at) AS session_starting_ts, - sum(CASE WHEN event_type = 'purchase' THEN 1 ELSE 0 END) has_purchased - FROM `bigquery-public-data.thelook_ecommerce.events` - GROUP BY user_id, - session_id, - city, - postal_code, - browser, - traffic_source -), previous_orders AS ( - SELECT user_id, - array_agg (struct(created_at AS order_creations_ts, - o.order_id, - o.status, - oi.order_cost)) as user_orders - FROM `bigquery-public-data.thelook_ecommerce.orders` o - JOIN (SELECT order_id, - sum(sale_price) order_cost - FROM `bigquery-public-data.thelook_ecommerce.order_items` - GROUP BY 1) oi - ON o.order_id = oi.order_id - GROUP BY 1 -) -SELECT abt.*, - CASE WHEN extract(DAYOFWEEK FROM session_starting_ts) IN (1,7) - THEN 'WEEKEND' - ELSE 'WEEKDAY' - END AS day_of_week, - extract(HOUR FROM session_starting_ts) hour_of_day, - (SELECT count(DISTINCT uo.order_id) - FROM unnest(user_orders) uo - WHERE uo.order_creations_ts < session_starting_ts - AND status IN ('Shipped', 'Complete', 'Processing')) AS number_of_successful_orders, - IFNULL((SELECT sum(DISTINCT uo.order_cost) - FROM unnest(user_orders) uo - WHERE uo.order_creations_ts < session_starting_ts - AND status IN ('Shipped', 'Complete', 'Processing')), 0) AS sum_previous_orders, - (SELECT count(DISTINCT uo.order_id) - FROM unnest(user_orders) uo - WHERE uo.order_creations_ts < session_starting_ts - AND status IN ('Cancelled', 'Returned')) AS number_of_unsuccessful_orders -FROM abt -LEFT JOIN previous_orders pso -ON abt.user_id = pso.user_id diff --git a/blueprints/data-solutions/bq-ml/demo/sql/train.sql b/blueprints/data-solutions/bq-ml/demo/sql/train.sql deleted file mode 100644 index 2c30f2e6..00000000 --- a/blueprints/data-solutions/bq-ml/demo/sql/train.sql +++ /dev/null @@ -1,27 +0,0 @@ -/* -* 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. -*/ - -CREATE OR REPLACE MODEL `{project_id}.{dataset}.{model_name}` -OPTIONS(MODEL_TYPE='{model_type}', - INPUT_LABEL_COLS=['has_purchased'], - ENABLE_GLOBAL_EXPLAIN=TRUE, - MODEL_REGISTRY='VERTEX_AI', - DATA_SPLIT_METHOD = 'RANDOM', - DATA_SPLIT_EVAL_FRACTION = {split_fraction} - ) AS -SELECT * EXCEPT (session_id, session_starting_ts, user_id) -FROM `{project_id}.{dataset}.ecommerce_abt_table` -WHERE extract(ISOYEAR FROM session_starting_ts) = 2022 \ No newline at end of file