diff --git a/.gitignore b/.gitignore index 79fa83df..91778178 100644 --- a/.gitignore +++ b/.gitignore @@ -21,13 +21,13 @@ bundle.zip **/*.pkrvars.hcl fixture_* fast/configs -fast/stages/**/[0-9]*providers.tf -fast/stages/**/terraform.tfvars -fast/stages/**/terraform.tfvars.json -fast/stages/**/terraform-*.auto.tfvars.json -fast/stages/**/0*.auto.tfvars* +fast/**/[0-9]*providers.tf +fast/**/terraform.tfvars +fast/**/terraform.tfvars.json +fast/**/terraform-*.auto.tfvars.json +fast/**/[0-9]*.auto.tfvars* **/node_modules -fast/stages/**/globals.auto.tfvars.json +fast/**/globals.auto.tfvars.json cloud_sql_proxy examples/cloud-operations/binauthz/tenant-setup.yaml examples/cloud-operations/binauthz/app/app.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index 59e4d371..4d6ab829 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,33 @@ All notable changes to this project will be documented in this file. ## [Unreleased] - + + +### DOCUMENTATION + +- [[#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)) + +### FAST + +- [[#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)) + +### MODULES + +- [[#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)) + +### TOOLS + +- [[#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)) + +## [20.0.0] - 2023-02-04 + ### BLUEPRINTS +- [[#1038](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1038)] Vertex Pipelines MLOps framework blueprint ([javiergp](https://github.com/javiergp)) +- [[#1124](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1124)] Removed unused file package-lock.json ([apichick](https://github.com/apichick)) +- [[#1119](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1119)] **incompatible change:** Multi-Cluster Ingress gateway api config ([wiktorn](https://github.com/wiktorn)) +- [[#1111](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1111)] **incompatible change:** In the apigee module now both the /22 and /28 peering IP ranges are p… ([apichick](https://github.com/apichick)) - [[#1106](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1106)] Network Dashboard: PSA support for Filestore and Memorystore ([aurelienlegrand](https://github.com/aurelienlegrand)) - [[#1110](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1110)] Bump cookiejar from 2.1.3 to 2.1.4 in /blueprints/apigee/bigquery-analytics/functions/export ([dependabot[bot]](https://github.com/dependabot[bot])) - [[#1097](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1097)] Use terraform resource to activate Anthos Service Mesh ([wiktorn](https://github.com/wiktorn)) @@ -49,6 +72,11 @@ All notable changes to this project will be documented in this file. ### MODULES +- [[#1127](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1127)] Skip node config for autopilot ([ludoo](https://github.com/ludoo)) +- [[#1125](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1125)] Added mesh_certificates setting in GKE cluster ([rosmo](https://github.com/rosmo)) +- [[#1094](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1094)] Added GLB example with MIG as backend ([eliamaldini](https://github.com/eliamaldini)) +- [[#1119](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1119)] **incompatible change:** Multi-Cluster Ingress gateway api config ([wiktorn](https://github.com/wiktorn)) +- [[#1111](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1111)] **incompatible change:** In the apigee module now both the /22 and /28 peering IP ranges are p… ([apichick](https://github.com/apichick)) - [[#1116](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1116)] Include cloudbuild API in project module ([aymanfarhat](https://github.com/aymanfarhat)) - [[#1115](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1115)] add new parameters support in apigee module ([blackillzone](https://github.com/blackillzone)) - [[#1112](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1112)] Add HTTPS frontend with SNEG example ([juliodiez](https://github.com/juliodiez)) @@ -470,7 +498,7 @@ All notable changes to this project will be documented in this file. - fix `tag` output on `data-catalog-policy-tag` module - add shared-vpc support on `gcs-to-bq-with-least-privileges` - new `net-ilb-l7` module -- new [02-networking-peering](fast/stages/02-networking-peering) networking stage +- new `02-networking-peering` networking stage - **incompatible change** the variable for PSA ranges in networking stages have changed ## [14.0.0] - 2022-02-25 @@ -489,8 +517,8 @@ All notable changes to this project will be documented in this file. - **incompatible change** removed `ingress_settings` configuration option in the `cloud-functions` module. - new [m4ce VM example](blueprints/cloud-operations/vm-migration/) - Support for resource management tags in the `organization`, `folder`, `project`, `compute-vm`, and `kms` modules -- new [data platform](fast/stages/03-data-platform) stage 3 -- new [02-networking-nva](fast/stages/02-networking-nva) networking stage +- new `data platform` stage 3 +- new `02-networking-nva` networking stage - allow customizing the names of custom roles - added `environment` and `context` resource management tags - use resource management tags to restrict scope of roles/orgpolicy.policyAdmin @@ -925,7 +953,8 @@ All notable changes to this project will be documented in this file. - merge development branch with suite of new modules and end-to-end examples -[Unreleased]: https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/compare/v19.0.0...HEAD +[Unreleased]: https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/compare/v20.0.0...HEAD +[20.0.0]: https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/compare/v19.0.0...v20.0.0 [19.0.0]: https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/compare/v18.0.0...v19.0.0 [18.0.0]: https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/compare/v16.0.0...v18.0.0 [16.0.0]: https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/compare/v15.0.0...v16.0.0 diff --git a/blueprints/README.md b/blueprints/README.md index 83588bfe..60a84912 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 for Terraform Cloud/Enterprise workflow](./cloud-operations/terraform-enterprise-wif), [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) +- **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) diff --git a/blueprints/apigee/bigquery-analytics/functions/export/package-lock.json b/blueprints/apigee/bigquery-analytics/functions/export/package-lock.json deleted file mode 100644 index 9ccbef77..00000000 --- a/blueprints/apigee/bigquery-analytics/functions/export/package-lock.json +++ /dev/null @@ -1,5548 +0,0 @@ -{ - "name": "export", - "version": "1.0.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "export", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "@google-cloud/functions-framework": "^3.1.2", - "@google-cloud/logging-bunyan": "^4.2.0", - "bunyan": "^1.8.15", - "express": "^4.18.2", - "google-auth-library": "^8.6.0", - "superagent": "^8.0.3" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "dependencies": { - "@babel/highlight": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", - "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/parser": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.2.tgz", - "integrity": "sha512-afk318kh2uKbo7BEj2QtEi8HVCGrwHUffrYDy7dgVcSa2j9lY3LDjPzcyGdpX7xgm35aWqvciZJ4WKmdF/SxYg==", - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@google-cloud/common": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-4.0.3.tgz", - "integrity": "sha512-fUoMo5b8iAKbrYpneIRV3z95AlxVJPrjpevxs4SKoclngWZvTXBSGpNisF5+x5m+oNGve7jfB1e6vNBZBUs7Fw==", - "dependencies": { - "@google-cloud/projectify": "^3.0.0", - "@google-cloud/promisify": "^3.0.0", - "arrify": "^2.0.1", - "duplexify": "^4.1.1", - "ent": "^2.2.0", - "extend": "^3.0.2", - "google-auth-library": "^8.0.2", - "retry-request": "^5.0.0", - "teeny-request": "^8.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@google-cloud/functions-framework": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@google-cloud/functions-framework/-/functions-framework-3.1.2.tgz", - "integrity": "sha512-pYvEH65/Rqh1JNPdcBmorcV7Xoom2/iOSmbtYza8msro7Inl+qOYxbyMiQfySD2gwAyn38WyWPRqsDRcf/BFLg==", - "dependencies": { - "@types/express": "4.17.13", - "body-parser": "^1.18.3", - "cloudevents": "^6.0.0", - "express": "^4.16.4", - "minimist": "^1.2.5", - "on-finished": "^2.3.0", - "read-pkg-up": "^7.0.1", - "semver": "^7.3.5" - }, - "bin": { - "functions-framework": "build/src/main.js", - "functions-framework-nodejs": "build/src/main.js" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@google-cloud/logging": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@google-cloud/logging/-/logging-10.3.0.tgz", - "integrity": "sha512-3QNSMci/8mmvLs4Iyb6Z/pFT/lJCDPWGWSRx08+Yi254xpva32pOU0grzgbPYls8SFhDWUQgYr9DGZg+IH0kEQ==", - "dependencies": { - "@google-cloud/common": "^4.0.0", - "@google-cloud/paginator": "^4.0.0", - "@google-cloud/projectify": "^3.0.0", - "@google-cloud/promisify": "^3.0.0", - "arrify": "^2.0.1", - "dot-prop": "^6.0.0", - "eventid": "^2.0.0", - "extend": "^3.0.2", - "gcp-metadata": "^4.0.0", - "google-auth-library": "^8.0.2", - "google-gax": "^3.5.2", - "on-finished": "^2.3.0", - "pumpify": "^2.0.1", - "stream-events": "^1.0.5", - "uuid": "^9.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@google-cloud/logging-bunyan": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@google-cloud/logging-bunyan/-/logging-bunyan-4.2.0.tgz", - "integrity": "sha512-BbzbJguK0sIZedO/0p27N5FDRUkdH2KsiejkoXOTNItU2GI8LweM7dtxihV9m7TcYOXVIxPhirnn8Tu3miq/VA==", - "dependencies": { - "@google-cloud/logging": "^10.2.2", - "google-auth-library": "^8.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "bunyan": "*" - } - }, - "node_modules/@google-cloud/paginator": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-4.0.1.tgz", - "integrity": "sha512-6G1ui6bWhNyHjmbYwavdN7mpVPRBtyDg/bfqBTAlwr413On2TnFNfDxc9UhTJctkgoCDgQXEKiRPLPR9USlkbQ==", - "dependencies": { - "arrify": "^2.0.0", - "extend": "^3.0.2" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@google-cloud/projectify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-3.0.0.tgz", - "integrity": "sha512-HRkZsNmjScY6Li8/kb70wjGlDDyLkVk3KvoEo9uIoxSjYLJasGiCch9+PqRVDOCGUFvEIqyogl+BeqILL4OJHA==", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@google-cloud/promisify": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-3.0.1.tgz", - "integrity": "sha512-z1CjRjtQyBOYL+5Qr9DdYIfrdLBe746jRTYfaYU6MeXkqp7UfYs/jX16lFFVzZ7PGEJvqZNqYUEtb1mvDww4pA==", - "engines": { - "node": ">=12" - } - }, - "node_modules/@grpc/grpc-js": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.7.3.tgz", - "integrity": "sha512-H9l79u4kJ2PVSxUNA08HMYAnUBLj9v6KjYQ7SQ71hOZcEXhShE/y5iQCesP8+6/Ik/7i2O0a10bPquIcYfufog==", - "dependencies": { - "@grpc/proto-loader": "^0.7.0", - "@types/node": ">=12.12.47" - }, - "engines": { - "node": "^8.13.0 || >=10.10.0" - } - }, - "node_modules/@grpc/proto-loader": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.3.tgz", - "integrity": "sha512-5dAvoZwna2Py3Ef96Ux9jIkp3iZ62TUsV00p3wVBPNX5K178UbNi8Q7gQVqwXT1Yq9RejIGG9G2IPEo93T6RcA==", - "dependencies": { - "@types/long": "^4.0.1", - "lodash.camelcase": "^4.3.0", - "long": "^4.0.0", - "protobufjs": "^7.0.0", - "yargs": "^16.2.0" - }, - "bin": { - "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" - }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" - }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" - }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" - }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" - }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" - }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" - }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" - }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" - }, - "node_modules/@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "engines": { - "node": ">= 10" - } - }, - "node_modules/@types/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/connect": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", - "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/express": { - "version": "4.17.13", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", - "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.18", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "4.17.31", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.31.tgz", - "integrity": "sha512-DxMhY+NAsTwMMFHBTtJFNp5qiHKJ7TeqOo23zVEM9alT1Ml27Q3xcTH0xwxn7Q0BbMcVEJOs/7aQtUWupUQN3Q==", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*" - } - }, - "node_modules/@types/linkify-it": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", - "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==" - }, - "node_modules/@types/long": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", - "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" - }, - "node_modules/@types/markdown-it": { - "version": "12.2.3", - "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", - "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", - "dependencies": { - "@types/linkify-it": "*", - "@types/mdurl": "*" - } - }, - "node_modules/@types/mdurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", - "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==" - }, - "node_modules/@types/mime": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", - "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==" - }, - "node_modules/@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==" - }, - "node_modules/@types/normalize-package-data": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", - "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==" - }, - "node_modules/@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" - }, - "node_modules/@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" - }, - "node_modules/@types/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==", - "dependencies": { - "@types/mime": "*", - "@types/node": "*" - } - }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/agent-base/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/agent-base/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" - }, - "node_modules/arrify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", - "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", - "engines": { - "node": ">=8" - } - }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/bignumber.js": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.0.tgz", - "integrity": "sha512-4LwHK4nfDOraBCtst+wOWIHbu1vhvAPJK8g8nROd4iuc3PSEjWif/qwbkh8jwCJz6yDBvtU4KPynETgrfh7y3A==", - "engines": { - "node": "*" - } - }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" - }, - "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" - }, - "node_modules/bunyan": { - "version": "1.8.15", - "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.15.tgz", - "integrity": "sha512-0tECWShh6wUysgucJcBAoYegf3JJoZWibxdqhTm7OHPeT42qdjkZ29QCMcKwbgU1kiH+auSIasNRXMLWXafXig==", - "engines": [ - "node >=0.10.0" - ], - "bin": { - "bunyan": "bin/bunyan" - }, - "optionalDependencies": { - "dtrace-provider": "~0.8", - "moment": "^2.19.3", - "mv": "~2", - "safe-json-stringify": "~1" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/catharsis": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", - "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", - "dependencies": { - "lodash": "^4.17.15" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/cloudevents": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/cloudevents/-/cloudevents-6.0.2.tgz", - "integrity": "sha512-mn/4EZnAbhfb/TghubK2jPnxYM15JRjf8LnWJtXidiVKi5ZCkd+p9jyBZbL57w7nRm6oFAzJhjxRLsXd/DNaBQ==", - "dependencies": { - "ajv": "^8.11.0", - "ajv-formats": "^2.1.1", - "util": "^0.12.4", - "uuid": "^8.3.2" - } - }, - "node_modules/cloudevents/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" - }, - "node_modules/cookiejar": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", - "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==" - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/dezalgo": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", - "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", - "dependencies": { - "asap": "^2.0.0", - "wrappy": "1" - } - }, - "node_modules/dot-prop": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", - "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", - "dependencies": { - "is-obj": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/dtrace-provider": { - "version": "0.8.8", - "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.8.tgz", - "integrity": "sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==", - "hasInstallScript": true, - "optional": true, - "dependencies": { - "nan": "^2.14.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/duplexify": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", - "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==", - "dependencies": { - "end-of-stream": "^1.4.1", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1", - "stream-shift": "^1.0.0" - } - }, - "node_modules/ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/ent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==" - }, - "node_modules/entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, - "node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "engines": { - "node": ">=8" - } - }, - "node_modules/escodegen": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", - "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=4.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/escodegen/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/espree": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", - "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", - "dependencies": { - "acorn": "^8.8.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/eventid": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/eventid/-/eventid-2.0.1.tgz", - "integrity": "sha512-sPNTqiMokAvV048P2c9+foqVJzk49o6d4e0D/sq5jog3pw+4kBgyR0gaM1FM7Mx6Kzd9dztesh9oYz1LWWOpzw==", - "dependencies": { - "uuid": "^8.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/eventid/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.1", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.5.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" - }, - "node_modules/fast-safe-stringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", - "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" - }, - "node_modules/fast-text-encoding": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz", - "integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==" - }, - "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dependencies": { - "is-callable": "^1.1.3" - } - }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/formidable": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.1.tgz", - "integrity": "sha512-0EcS9wCFEzLvfiks7omJ+SiYJAiD+TzK4Pcw1UlUoGnhUxDcMKjt0P7x8wEb0u6OHu8Nb98WG3nxtlF5C7bvUQ==", - "dependencies": { - "dezalgo": "^1.0.4", - "hexoid": "^1.0.0", - "once": "^1.4.0", - "qs": "^6.11.0" - }, - "funding": { - "url": "https://ko-fi.com/tunnckoCore/commissions" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "node_modules/gaxios": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.3.tgz", - "integrity": "sha512-gSaYYIO1Y3wUtdfHmjDUZ8LWaxJQpiavzbF5Kq53akSzvmVg0RfyOcFDbO1KJ/KCGRFz2qG+lS81F0nkr7cRJA==", - "dependencies": { - "abort-controller": "^3.0.0", - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.7" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/gcp-metadata": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.3.1.tgz", - "integrity": "sha512-x850LS5N7V1F3UcV7PoupzGsyD6iVwTVvsh3tbXfkctZnBnjW5yu5z1/3k3SehF7TyoTIe78rJs02GMMy+LF+A==", - "dependencies": { - "gaxios": "^4.0.0", - "json-bigint": "^1.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", - "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/glob": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", - "integrity": "sha512-MKZeRNyYZAVVVG1oZeLaWie1uweH40m9AZwIwxyPbTSX4hHrVYSzLg0Ro5Z5R7XKkIX+Cc6oD1rqeDJnwsB8/A==", - "optional": true, - "dependencies": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/google-auth-library": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.6.0.tgz", - "integrity": "sha512-y6bw1yTWMVgs1vGJwBZ3uu+uIClfgxQfsEVcTNKjQeNQOVwox69+ZUgTeTAzrh+74hBqrk1gWyb9RsQVDI7seg==", - "dependencies": { - "arrify": "^2.0.0", - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "fast-text-encoding": "^1.0.0", - "gaxios": "^5.0.0", - "gcp-metadata": "^5.0.0", - "gtoken": "^6.1.0", - "jws": "^4.0.0", - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/google-auth-library/node_modules/gaxios": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.0.2.tgz", - "integrity": "sha512-TjtV2AJOZoMQqRYoy5eM8cCQogYwazWNYLQ72QB0kwa6vHHruYkGmhhyrlzbmgNHK1dNnuP2WSH81urfzyN2Og==", - "dependencies": { - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.7" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/google-auth-library/node_modules/gcp-metadata": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.0.1.tgz", - "integrity": "sha512-jiRJ+Fk7e8FH68Z6TLaqwea307OktJpDjmYnU7/li6ziwvVvU2RlrCyQo5vkdeP94chm0kcSCOOszvmuaioq3g==", - "dependencies": { - "gaxios": "^5.0.0", - "json-bigint": "^1.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/google-gax": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.5.2.tgz", - "integrity": "sha512-AyP53w0gHcWlzxm+jSgqCR3Xu4Ld7EpSjhtNBnNhzwwWaIUyphH9kBGNIEH+i4UGkTUXOY29K/Re8EiAvkBRGw==", - "dependencies": { - "@grpc/grpc-js": "~1.7.0", - "@grpc/proto-loader": "^0.7.0", - "@types/long": "^4.0.0", - "abort-controller": "^3.0.0", - "duplexify": "^4.0.0", - "fast-text-encoding": "^1.0.3", - "google-auth-library": "^8.0.2", - "is-stream-ended": "^0.1.4", - "node-fetch": "^2.6.1", - "object-hash": "^3.0.0", - "proto3-json-serializer": "^1.0.0", - "protobufjs": "7.1.2", - "protobufjs-cli": "1.0.2", - "retry-request": "^5.0.0" - }, - "bin": { - "compileProtos": "build/tools/compileProtos.js", - "minifyProtoJson": "build/tools/minify.js" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/google-p12-pem": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz", - "integrity": "sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==", - "dependencies": { - "node-forge": "^1.3.1" - }, - "bin": { - "gp12-pem": "build/src/bin/gp12-pem.js" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" - }, - "node_modules/gtoken": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz", - "integrity": "sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==", - "dependencies": { - "gaxios": "^5.0.1", - "google-p12-pem": "^4.0.0", - "jws": "^4.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/gtoken/node_modules/gaxios": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.0.2.tgz", - "integrity": "sha512-TjtV2AJOZoMQqRYoy5eM8cCQogYwazWNYLQ72QB0kwa6vHHruYkGmhhyrlzbmgNHK1dNnuP2WSH81urfzyN2Og==", - "dependencies": { - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.7" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hexoid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", - "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==", - "engines": { - "node": ">=8" - } - }, - "node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==" - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/http-proxy-agent/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/http-proxy-agent/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/https-proxy-agent/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/https-proxy-agent/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-core-module": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-function": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", - "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-stream-ended": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", - "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==" - }, - "node_modules/is-typed-array": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", - "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "node_modules/js2xmlparser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", - "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", - "dependencies": { - "xmlcreate": "^2.0.4" - } - }, - "node_modules/jsdoc": { - "version": "3.6.11", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.11.tgz", - "integrity": "sha512-8UCU0TYeIYD9KeLzEcAu2q8N/mx9O3phAGl32nmHlE0LpaJL71mMkP4d+QE5zWfNt50qheHtOZ0qoxVrsX5TUg==", - "dependencies": { - "@babel/parser": "^7.9.4", - "@types/markdown-it": "^12.2.3", - "bluebird": "^3.7.2", - "catharsis": "^0.9.0", - "escape-string-regexp": "^2.0.0", - "js2xmlparser": "^4.0.2", - "klaw": "^3.0.0", - "markdown-it": "^12.3.2", - "markdown-it-anchor": "^8.4.1", - "marked": "^4.0.10", - "mkdirp": "^1.0.4", - "requizzle": "^0.2.3", - "strip-json-comments": "^3.1.0", - "taffydb": "2.6.2", - "underscore": "~1.13.2" - }, - "bin": { - "jsdoc": "jsdoc.js" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/jsdoc/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/json-bigint": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", - "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", - "dependencies": { - "bignumber.js": "^9.0.0" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, - "node_modules/jwa": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", - "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", - "dependencies": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/jws": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", - "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", - "dependencies": { - "jwa": "^2.0.0", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/klaw": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", - "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", - "dependencies": { - "graceful-fs": "^4.1.9" - } - }, - "node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" - }, - "node_modules/linkify-it": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", - "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", - "dependencies": { - "uc.micro": "^1.0.1" - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "node_modules/lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" - }, - "node_modules/long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/markdown-it": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", - "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", - "dependencies": { - "argparse": "^2.0.1", - "entities": "~2.1.0", - "linkify-it": "^3.0.1", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" - }, - "bin": { - "markdown-it": "bin/markdown-it.js" - } - }, - "node_modules/markdown-it-anchor": { - "version": "8.6.5", - "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.5.tgz", - "integrity": "sha512-PI1qEHHkTNWT+X6Ip9w+paonfIQ+QZP9sCeMYi47oqhH+EsW8CrJ8J7CzV19QVOj6il8ATGbK2nTECj22ZHGvQ==", - "peerDependencies": { - "@types/markdown-it": "*", - "markdown-it": "*" - } - }, - "node_modules/marked": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.2.tgz", - "integrity": "sha512-JjBTFTAvuTgANXx82a5vzK9JLSMoV6V3LBVn4Uhdso6t7vXrGx7g1Cd2r6NYSsxrYbQGFCMqBDhFHyK5q2UvcQ==", - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 12" - } - }, - "node_modules/mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==" - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", - "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "optional": true, - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/moment": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", - "optional": true, - "engines": { - "node": "*" - } - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/mv": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", - "integrity": "sha512-at/ZndSy3xEGJ8i0ygALh8ru9qy7gWW1cmkaqBN29JmMlIvM//MEO9y1sk/avxuwnPcfhkejkLsuPxH81BrkSg==", - "optional": true, - "dependencies": { - "mkdirp": "~0.5.1", - "ncp": "~2.0.0", - "rimraf": "~2.4.0" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/nan": { - "version": "2.17.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz", - "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==", - "optional": true - }, - "node_modules/ncp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", - "integrity": "sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA==", - "optional": true, - "bin": { - "ncp": "bin/ncp" - } - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", - "engines": { - "node": ">= 6.13.0" - } - }, - "node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/object-inspect": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", - "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" - }, - "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" - }, - "node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/proto3-json-serializer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-1.1.0.tgz", - "integrity": "sha512-SjXwUWe/vANGs/mJJTbw5++7U67nwsymg7qsoPtw6GiXqw3kUy8ByojrlEdVE2efxAdKreX8WkDafxvYW95ZQg==", - "dependencies": { - "protobufjs": "^7.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/protobufjs": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", - "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", - "hasInstallScript": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/node": ">=13.7.0", - "long": "^5.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/protobufjs-cli": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.0.2.tgz", - "integrity": "sha512-cz9Pq9p/Zs7okc6avH20W7QuyjTclwJPgqXG11jNaulfS3nbVisID8rC+prfgq0gbZE0w9LBFd1OKFF03kgFzg==", - "dependencies": { - "chalk": "^4.0.0", - "escodegen": "^1.13.0", - "espree": "^9.0.0", - "estraverse": "^5.1.0", - "glob": "^8.0.0", - "jsdoc": "^3.6.3", - "minimist": "^1.2.0", - "semver": "^7.1.2", - "tmp": "^0.2.1", - "uglify-js": "^3.7.7" - }, - "bin": { - "pbjs": "bin/pbjs", - "pbts": "bin/pbts" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "protobufjs": "^7.0.0" - } - }, - "node_modules/protobufjs-cli/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/protobufjs-cli/node_modules/glob": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", - "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/protobufjs-cli/node_modules/minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/protobufjs/node_modules/long": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", - "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==" - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/pumpify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz", - "integrity": "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==", - "dependencies": { - "duplexify": "^4.1.1", - "inherits": "^2.0.3", - "pump": "^3.0.0" - } - }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "engines": { - "node": ">=6" - } - }, - "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dependencies": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg/node_modules/type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/requizzle": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.3.tgz", - "integrity": "sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ==", - "dependencies": { - "lodash": "^4.17.14" - } - }, - "node_modules/resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "dependencies": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/retry-request": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-5.0.2.tgz", - "integrity": "sha512-wfI3pk7EE80lCIXprqh7ym48IHYdwmAAzESdbU8Q9l7pnRCk9LEhpbOTNKjz6FARLm/Bl5m+4F0ABxOkYUujSQ==", - "dependencies": { - "debug": "^4.1.1", - "extend": "^3.0.2" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/retry-request/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/retry-request/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/rimraf": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", - "integrity": "sha512-J5xnxTyqaiw06JjMftq7L9ouA448dw/E7dKghkP9WpKNuwmARNNg+Gk8/u5ryb9N/Yo2+z3MCwuqFK/+qPOPfQ==", - "optional": true, - "dependencies": { - "glob": "^6.0.1" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safe-json-stringify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz", - "integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==", - "optional": true - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz", - "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==" - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/stream-events": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", - "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", - "dependencies": { - "stubs": "^3.0.0" - } - }, - "node_modules/stream-shift": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", - "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/stubs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", - "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==" - }, - "node_modules/superagent": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-8.0.3.tgz", - "integrity": "sha512-oBC+aNsCjzzjmO5AOPBPFS+Z7HPzlx+DQr/aHwM08kI+R24gsDmAS1LMfza1fK+P+SKlTAoNZpOvooE/pRO1HA==", - "dependencies": { - "component-emitter": "^1.3.0", - "cookiejar": "^2.1.3", - "debug": "^4.3.4", - "fast-safe-stringify": "^2.1.1", - "form-data": "^4.0.0", - "formidable": "^2.0.1", - "methods": "^1.1.2", - "mime": "2.6.0", - "qs": "^6.11.0", - "semver": "^7.3.8" - }, - "engines": { - "node": ">=6.4.0 <13 || >=14" - } - }, - "node_modules/superagent/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/superagent/node_modules/mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/superagent/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/taffydb": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", - "integrity": "sha512-y3JaeRSplks6NYQuCOj3ZFMO3j60rTwbuKCvZxsAraGYH2epusatvZ0baZYA01WsGqJBq/Dl6vOrMUJqyMj8kA==" - }, - "node_modules/teeny-request": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-8.0.2.tgz", - "integrity": "sha512-34pe0a4zASseXZCKdeTiIZqSKA8ETHb1EwItZr01PAR3CLPojeAKgSjzeNS4373gi59hNulyDrPKEbh2zO9sCg==", - "dependencies": { - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "node-fetch": "^2.6.1", - "stream-events": "^1.0.5", - "uuid": "^9.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "dependencies": { - "rimraf": "^3.0.0" - }, - "engines": { - "node": ">=8.17.0" - } - }, - "node_modules/tmp/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/tmp/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "engines": { - "node": ">=8" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/uc.micro": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" - }, - "node_modules/uglify-js": { - "version": "3.17.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", - "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/underscore": { - "version": "1.13.6", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", - "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==" - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/util": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", - "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", - "dependencies": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "which-typed-array": "^1.1.2" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", - "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.10" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, - "node_modules/xmlcreate": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", - "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==" - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "engines": { - "node": ">=10" - } - } - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "requires": { - "@babel/highlight": "^7.18.6" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" - }, - "@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", - "requires": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "@babel/parser": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.2.tgz", - "integrity": "sha512-afk318kh2uKbo7BEj2QtEi8HVCGrwHUffrYDy7dgVcSa2j9lY3LDjPzcyGdpX7xgm35aWqvciZJ4WKmdF/SxYg==" - }, - "@google-cloud/common": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-4.0.3.tgz", - "integrity": "sha512-fUoMo5b8iAKbrYpneIRV3z95AlxVJPrjpevxs4SKoclngWZvTXBSGpNisF5+x5m+oNGve7jfB1e6vNBZBUs7Fw==", - "requires": { - "@google-cloud/projectify": "^3.0.0", - "@google-cloud/promisify": "^3.0.0", - "arrify": "^2.0.1", - "duplexify": "^4.1.1", - "ent": "^2.2.0", - "extend": "^3.0.2", - "google-auth-library": "^8.0.2", - "retry-request": "^5.0.0", - "teeny-request": "^8.0.0" - } - }, - "@google-cloud/functions-framework": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@google-cloud/functions-framework/-/functions-framework-3.1.2.tgz", - "integrity": "sha512-pYvEH65/Rqh1JNPdcBmorcV7Xoom2/iOSmbtYza8msro7Inl+qOYxbyMiQfySD2gwAyn38WyWPRqsDRcf/BFLg==", - "requires": { - "@types/express": "4.17.13", - "body-parser": "^1.18.3", - "cloudevents": "^6.0.0", - "express": "^4.16.4", - "minimist": "^1.2.5", - "on-finished": "^2.3.0", - "read-pkg-up": "^7.0.1", - "semver": "^7.3.5" - } - }, - "@google-cloud/logging": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@google-cloud/logging/-/logging-10.3.0.tgz", - "integrity": "sha512-3QNSMci/8mmvLs4Iyb6Z/pFT/lJCDPWGWSRx08+Yi254xpva32pOU0grzgbPYls8SFhDWUQgYr9DGZg+IH0kEQ==", - "requires": { - "@google-cloud/common": "^4.0.0", - "@google-cloud/paginator": "^4.0.0", - "@google-cloud/projectify": "^3.0.0", - "@google-cloud/promisify": "^3.0.0", - "arrify": "^2.0.1", - "dot-prop": "^6.0.0", - "eventid": "^2.0.0", - "extend": "^3.0.2", - "gcp-metadata": "^4.0.0", - "google-auth-library": "^8.0.2", - "google-gax": "^3.5.2", - "on-finished": "^2.3.0", - "pumpify": "^2.0.1", - "stream-events": "^1.0.5", - "uuid": "^9.0.0" - } - }, - "@google-cloud/logging-bunyan": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@google-cloud/logging-bunyan/-/logging-bunyan-4.2.0.tgz", - "integrity": "sha512-BbzbJguK0sIZedO/0p27N5FDRUkdH2KsiejkoXOTNItU2GI8LweM7dtxihV9m7TcYOXVIxPhirnn8Tu3miq/VA==", - "requires": { - "@google-cloud/logging": "^10.2.2", - "google-auth-library": "^8.0.2" - } - }, - "@google-cloud/paginator": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-4.0.1.tgz", - "integrity": "sha512-6G1ui6bWhNyHjmbYwavdN7mpVPRBtyDg/bfqBTAlwr413On2TnFNfDxc9UhTJctkgoCDgQXEKiRPLPR9USlkbQ==", - "requires": { - "arrify": "^2.0.0", - "extend": "^3.0.2" - } - }, - "@google-cloud/projectify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-3.0.0.tgz", - "integrity": "sha512-HRkZsNmjScY6Li8/kb70wjGlDDyLkVk3KvoEo9uIoxSjYLJasGiCch9+PqRVDOCGUFvEIqyogl+BeqILL4OJHA==" - }, - "@google-cloud/promisify": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-3.0.1.tgz", - "integrity": "sha512-z1CjRjtQyBOYL+5Qr9DdYIfrdLBe746jRTYfaYU6MeXkqp7UfYs/jX16lFFVzZ7PGEJvqZNqYUEtb1mvDww4pA==" - }, - "@grpc/grpc-js": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.7.3.tgz", - "integrity": "sha512-H9l79u4kJ2PVSxUNA08HMYAnUBLj9v6KjYQ7SQ71hOZcEXhShE/y5iQCesP8+6/Ik/7i2O0a10bPquIcYfufog==", - "requires": { - "@grpc/proto-loader": "^0.7.0", - "@types/node": ">=12.12.47" - } - }, - "@grpc/proto-loader": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.3.tgz", - "integrity": "sha512-5dAvoZwna2Py3Ef96Ux9jIkp3iZ62TUsV00p3wVBPNX5K178UbNi8Q7gQVqwXT1Yq9RejIGG9G2IPEo93T6RcA==", - "requires": { - "@types/long": "^4.0.1", - "lodash.camelcase": "^4.3.0", - "long": "^4.0.0", - "protobufjs": "^7.0.0", - "yargs": "^16.2.0" - } - }, - "@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" - }, - "@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" - }, - "@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" - }, - "@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" - }, - "@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "requires": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" - }, - "@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" - }, - "@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" - }, - "@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" - }, - "@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" - }, - "@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==" - }, - "@types/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", - "requires": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "@types/connect": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", - "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", - "requires": { - "@types/node": "*" - } - }, - "@types/express": { - "version": "4.17.13", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", - "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", - "requires": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.18", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "@types/express-serve-static-core": { - "version": "4.17.31", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.31.tgz", - "integrity": "sha512-DxMhY+NAsTwMMFHBTtJFNp5qiHKJ7TeqOo23zVEM9alT1Ml27Q3xcTH0xwxn7Q0BbMcVEJOs/7aQtUWupUQN3Q==", - "requires": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*" - } - }, - "@types/linkify-it": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", - "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==" - }, - "@types/long": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", - "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" - }, - "@types/markdown-it": { - "version": "12.2.3", - "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", - "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", - "requires": { - "@types/linkify-it": "*", - "@types/mdurl": "*" - } - }, - "@types/mdurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", - "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==" - }, - "@types/mime": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", - "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==" - }, - "@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==" - }, - "@types/normalize-package-data": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", - "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==" - }, - "@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" - }, - "@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" - }, - "@types/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==", - "requires": { - "@types/mime": "*", - "@types/node": "*" - } - }, - "abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "requires": { - "event-target-shim": "^5.0.0" - } - }, - "accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "requires": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - } - }, - "acorn": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==" - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "requires": {} - }, - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "requires": { - "debug": "4" - }, - "dependencies": { - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - } - } - }, - "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "requires": { - "ajv": "^8.0.0" - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - }, - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" - }, - "arrify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", - "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==" - }, - "asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==" - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" - }, - "bignumber.js": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.0.tgz", - "integrity": "sha512-4LwHK4nfDOraBCtst+wOWIHbu1vhvAPJK8g8nROd4iuc3PSEjWif/qwbkh8jwCJz6yDBvtU4KPynETgrfh7y3A==" - }, - "bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" - }, - "body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", - "requires": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" - }, - "bunyan": { - "version": "1.8.15", - "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.15.tgz", - "integrity": "sha512-0tECWShh6wUysgucJcBAoYegf3JJoZWibxdqhTm7OHPeT42qdjkZ29QCMcKwbgU1kiH+auSIasNRXMLWXafXig==", - "requires": { - "dtrace-provider": "~0.8", - "moment": "^2.19.3", - "mv": "~2", - "safe-json-stringify": "~1" - } - }, - "bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "catharsis": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", - "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", - "requires": { - "lodash": "^4.17.15" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "cloudevents": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/cloudevents/-/cloudevents-6.0.2.tgz", - "integrity": "sha512-mn/4EZnAbhfb/TghubK2jPnxYM15JRjf8LnWJtXidiVKi5ZCkd+p9jyBZbL57w7nRm6oFAzJhjxRLsXd/DNaBQ==", - "requires": { - "ajv": "^8.11.0", - "ajv-formats": "^2.1.1", - "util": "^0.12.4", - "uuid": "^8.3.2" - }, - "dependencies": { - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" - } - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "requires": { - "safe-buffer": "5.2.1" - } - }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" - }, - "cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" - }, - "cookiejar": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", - "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==" - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" - }, - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" - }, - "destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" - }, - "dezalgo": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", - "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", - "requires": { - "asap": "^2.0.0", - "wrappy": "1" - } - }, - "dot-prop": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", - "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", - "requires": { - "is-obj": "^2.0.0" - } - }, - "dtrace-provider": { - "version": "0.8.8", - "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.8.tgz", - "integrity": "sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==", - "optional": true, - "requires": { - "nan": "^2.14.0" - } - }, - "duplexify": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", - "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==", - "requires": { - "end-of-stream": "^1.4.1", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1", - "stream-shift": "^1.0.0" - } - }, - "ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "requires": { - "once": "^1.4.0" - } - }, - "ent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==" - }, - "entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==" - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==" - }, - "escodegen": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", - "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", - "requires": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - }, - "dependencies": { - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" - } - } - }, - "eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==" - }, - "espree": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", - "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", - "requires": { - "acorn": "^8.8.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" - }, - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" - }, - "event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" - }, - "eventid": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/eventid/-/eventid-2.0.1.tgz", - "integrity": "sha512-sPNTqiMokAvV048P2c9+foqVJzk49o6d4e0D/sq5jog3pw+4kBgyR0gaM1FM7Mx6Kzd9dztesh9oYz1LWWOpzw==", - "requires": { - "uuid": "^8.0.0" - }, - "dependencies": { - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" - } - } - }, - "express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", - "requires": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.1", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.5.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" - }, - "fast-safe-stringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", - "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" - }, - "fast-text-encoding": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz", - "integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==" - }, - "finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "requires": { - "is-callable": "^1.1.3" - } - }, - "form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "formidable": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.1.tgz", - "integrity": "sha512-0EcS9wCFEzLvfiks7omJ+SiYJAiD+TzK4Pcw1UlUoGnhUxDcMKjt0P7x8wEb0u6OHu8Nb98WG3nxtlF5C7bvUQ==", - "requires": { - "dezalgo": "^1.0.4", - "hexoid": "^1.0.0", - "once": "^1.4.0", - "qs": "^6.11.0" - } - }, - "forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "gaxios": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.3.tgz", - "integrity": "sha512-gSaYYIO1Y3wUtdfHmjDUZ8LWaxJQpiavzbF5Kq53akSzvmVg0RfyOcFDbO1KJ/KCGRFz2qG+lS81F0nkr7cRJA==", - "requires": { - "abort-controller": "^3.0.0", - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.7" - } - }, - "gcp-metadata": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.3.1.tgz", - "integrity": "sha512-x850LS5N7V1F3UcV7PoupzGsyD6iVwTVvsh3tbXfkctZnBnjW5yu5z1/3k3SehF7TyoTIe78rJs02GMMy+LF+A==", - "requires": { - "gaxios": "^4.0.0", - "json-bigint": "^1.0.0" - } - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" - }, - "get-intrinsic": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", - "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" - } - }, - "glob": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", - "integrity": "sha512-MKZeRNyYZAVVVG1oZeLaWie1uweH40m9AZwIwxyPbTSX4hHrVYSzLg0Ro5Z5R7XKkIX+Cc6oD1rqeDJnwsB8/A==", - "optional": true, - "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "google-auth-library": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.6.0.tgz", - "integrity": "sha512-y6bw1yTWMVgs1vGJwBZ3uu+uIClfgxQfsEVcTNKjQeNQOVwox69+ZUgTeTAzrh+74hBqrk1gWyb9RsQVDI7seg==", - "requires": { - "arrify": "^2.0.0", - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "fast-text-encoding": "^1.0.0", - "gaxios": "^5.0.0", - "gcp-metadata": "^5.0.0", - "gtoken": "^6.1.0", - "jws": "^4.0.0", - "lru-cache": "^6.0.0" - }, - "dependencies": { - "gaxios": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.0.2.tgz", - "integrity": "sha512-TjtV2AJOZoMQqRYoy5eM8cCQogYwazWNYLQ72QB0kwa6vHHruYkGmhhyrlzbmgNHK1dNnuP2WSH81urfzyN2Og==", - "requires": { - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.7" - } - }, - "gcp-metadata": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.0.1.tgz", - "integrity": "sha512-jiRJ+Fk7e8FH68Z6TLaqwea307OktJpDjmYnU7/li6ziwvVvU2RlrCyQo5vkdeP94chm0kcSCOOszvmuaioq3g==", - "requires": { - "gaxios": "^5.0.0", - "json-bigint": "^1.0.0" - } - } - } - }, - "google-gax": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.5.2.tgz", - "integrity": "sha512-AyP53w0gHcWlzxm+jSgqCR3Xu4Ld7EpSjhtNBnNhzwwWaIUyphH9kBGNIEH+i4UGkTUXOY29K/Re8EiAvkBRGw==", - "requires": { - "@grpc/grpc-js": "~1.7.0", - "@grpc/proto-loader": "^0.7.0", - "@types/long": "^4.0.0", - "abort-controller": "^3.0.0", - "duplexify": "^4.0.0", - "fast-text-encoding": "^1.0.3", - "google-auth-library": "^8.0.2", - "is-stream-ended": "^0.1.4", - "node-fetch": "^2.6.1", - "object-hash": "^3.0.0", - "proto3-json-serializer": "^1.0.0", - "protobufjs": "7.1.2", - "protobufjs-cli": "1.0.2", - "retry-request": "^5.0.0" - } - }, - "google-p12-pem": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz", - "integrity": "sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==", - "requires": { - "node-forge": "^1.3.1" - } - }, - "gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "requires": { - "get-intrinsic": "^1.1.3" - } - }, - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" - }, - "gtoken": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz", - "integrity": "sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==", - "requires": { - "gaxios": "^5.0.1", - "google-p12-pem": "^4.0.0", - "jws": "^4.0.0" - }, - "dependencies": { - "gaxios": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.0.2.tgz", - "integrity": "sha512-TjtV2AJOZoMQqRYoy5eM8cCQogYwazWNYLQ72QB0kwa6vHHruYkGmhhyrlzbmgNHK1dNnuP2WSH81urfzyN2Og==", - "requires": { - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.7" - } - } - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" - }, - "has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "requires": { - "has-symbols": "^1.0.2" - } - }, - "hexoid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", - "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==" - }, - "hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==" - }, - "http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "requires": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - } - }, - "http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "requires": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "dependencies": { - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - } - } - }, - "https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "requires": { - "agent-base": "6", - "debug": "4" - }, - "dependencies": { - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - } - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" - }, - "is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" - }, - "is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==" - }, - "is-core-module": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", - "requires": { - "has": "^1.0.3" - } - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - }, - "is-generator-function": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", - "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" - }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" - }, - "is-stream-ended": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", - "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==" - }, - "is-typed-array": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", - "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", - "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "js2xmlparser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", - "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", - "requires": { - "xmlcreate": "^2.0.4" - } - }, - "jsdoc": { - "version": "3.6.11", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.11.tgz", - "integrity": "sha512-8UCU0TYeIYD9KeLzEcAu2q8N/mx9O3phAGl32nmHlE0LpaJL71mMkP4d+QE5zWfNt50qheHtOZ0qoxVrsX5TUg==", - "requires": { - "@babel/parser": "^7.9.4", - "@types/markdown-it": "^12.2.3", - "bluebird": "^3.7.2", - "catharsis": "^0.9.0", - "escape-string-regexp": "^2.0.0", - "js2xmlparser": "^4.0.2", - "klaw": "^3.0.0", - "markdown-it": "^12.3.2", - "markdown-it-anchor": "^8.4.1", - "marked": "^4.0.10", - "mkdirp": "^1.0.4", - "requizzle": "^0.2.3", - "strip-json-comments": "^3.1.0", - "taffydb": "2.6.2", - "underscore": "~1.13.2" - }, - "dependencies": { - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" - } - } - }, - "json-bigint": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", - "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", - "requires": { - "bignumber.js": "^9.0.0" - } - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, - "jwa": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", - "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", - "requires": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "jws": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", - "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", - "requires": { - "jwa": "^2.0.0", - "safe-buffer": "^5.0.1" - } - }, - "klaw": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", - "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", - "requires": { - "graceful-fs": "^4.1.9" - } - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" - }, - "linkify-it": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", - "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", - "requires": { - "uc.micro": "^1.0.1" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "requires": { - "p-locate": "^4.1.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" - }, - "long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "requires": { - "yallist": "^4.0.0" - } - }, - "markdown-it": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", - "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", - "requires": { - "argparse": "^2.0.1", - "entities": "~2.1.0", - "linkify-it": "^3.0.1", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" - } - }, - "markdown-it-anchor": { - "version": "8.6.5", - "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.5.tgz", - "integrity": "sha512-PI1qEHHkTNWT+X6Ip9w+paonfIQ+QZP9sCeMYi47oqhH+EsW8CrJ8J7CzV19QVOj6il8ATGbK2nTECj22ZHGvQ==", - "requires": {} - }, - "marked": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.2.tgz", - "integrity": "sha512-JjBTFTAvuTgANXx82a5vzK9JLSMoV6V3LBVn4Uhdso6t7vXrGx7g1Cd2r6NYSsxrYbQGFCMqBDhFHyK5q2UvcQ==" - }, - "mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==" - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" - }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" - }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "requires": { - "mime-db": "1.52.0" - } - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", - "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==" - }, - "mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "optional": true, - "requires": { - "minimist": "^1.2.6" - } - }, - "moment": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", - "optional": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "mv": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", - "integrity": "sha512-at/ZndSy3xEGJ8i0ygALh8ru9qy7gWW1cmkaqBN29JmMlIvM//MEO9y1sk/avxuwnPcfhkejkLsuPxH81BrkSg==", - "optional": true, - "requires": { - "mkdirp": "~0.5.1", - "ncp": "~2.0.0", - "rimraf": "~2.4.0" - } - }, - "nan": { - "version": "2.17.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz", - "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==", - "optional": true - }, - "ncp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", - "integrity": "sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA==", - "optional": true - }, - "negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" - }, - "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "requires": { - "whatwg-url": "^5.0.0" - } - }, - "node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==" - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } - } - }, - "object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==" - }, - "object-inspect": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", - "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==" - }, - "on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "requires": { - "ee-first": "1.1.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "requires": { - "wrappy": "1" - } - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "requires": { - "p-limit": "^2.2.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" - }, - "parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" - }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==" - }, - "proto3-json-serializer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-1.1.0.tgz", - "integrity": "sha512-SjXwUWe/vANGs/mJJTbw5++7U67nwsymg7qsoPtw6GiXqw3kUy8ByojrlEdVE2efxAdKreX8WkDafxvYW95ZQg==", - "requires": { - "protobufjs": "^7.0.0" - } - }, - "protobufjs": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", - "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", - "requires": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/node": ">=13.7.0", - "long": "^5.0.0" - }, - "dependencies": { - "long": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", - "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==" - } - } - }, - "protobufjs-cli": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.0.2.tgz", - "integrity": "sha512-cz9Pq9p/Zs7okc6avH20W7QuyjTclwJPgqXG11jNaulfS3nbVisID8rC+prfgq0gbZE0w9LBFd1OKFF03kgFzg==", - "requires": { - "chalk": "^4.0.0", - "escodegen": "^1.13.0", - "espree": "^9.0.0", - "estraverse": "^5.1.0", - "glob": "^8.0.0", - "jsdoc": "^3.6.3", - "minimist": "^1.2.0", - "semver": "^7.1.2", - "tmp": "^0.2.1", - "uglify-js": "^3.7.7" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "requires": { - "balanced-match": "^1.0.0" - } - }, - "glob": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", - "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - } - }, - "minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", - "requires": { - "brace-expansion": "^2.0.1" - } - } - } - }, - "proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "requires": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - } - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "pumpify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz", - "integrity": "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==", - "requires": { - "duplexify": "^4.1.1", - "inherits": "^2.0.3", - "pump": "^3.0.0" - } - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - }, - "qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "requires": { - "side-channel": "^1.0.4" - } - }, - "range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" - }, - "raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "requires": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } - }, - "read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "requires": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "dependencies": { - "type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==" - } - } - }, - "read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "requires": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" - }, - "require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" - }, - "requizzle": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.3.tgz", - "integrity": "sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ==", - "requires": { - "lodash": "^4.17.14" - } - }, - "resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "requires": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "retry-request": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-5.0.2.tgz", - "integrity": "sha512-wfI3pk7EE80lCIXprqh7ym48IHYdwmAAzESdbU8Q9l7pnRCk9LEhpbOTNKjz6FARLm/Bl5m+4F0ABxOkYUujSQ==", - "requires": { - "debug": "^4.1.1", - "extend": "^3.0.2" - }, - "dependencies": { - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - } - } - }, - "rimraf": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", - "integrity": "sha512-J5xnxTyqaiw06JjMftq7L9ouA448dw/E7dKghkP9WpKNuwmARNNg+Gk8/u5ryb9N/Yo2+z3MCwuqFK/+qPOPfQ==", - "optional": true, - "requires": { - "glob": "^6.0.1" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "safe-json-stringify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz", - "integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==", - "optional": true - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "requires": { - "lru-cache": "^6.0.0" - } - }, - "send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "requires": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "dependencies": { - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - } - } - }, - "serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - } - }, - "setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "optional": true - }, - "spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" - }, - "spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz", - "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==" - }, - "statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" - }, - "stream-events": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", - "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", - "requires": { - "stubs": "^3.0.0" - } - }, - "stream-shift": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", - "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "requires": { - "safe-buffer": "~5.2.0" - } - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" - }, - "stubs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", - "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==" - }, - "superagent": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-8.0.3.tgz", - "integrity": "sha512-oBC+aNsCjzzjmO5AOPBPFS+Z7HPzlx+DQr/aHwM08kI+R24gsDmAS1LMfza1fK+P+SKlTAoNZpOvooE/pRO1HA==", - "requires": { - "component-emitter": "^1.3.0", - "cookiejar": "^2.1.3", - "debug": "^4.3.4", - "fast-safe-stringify": "^2.1.1", - "form-data": "^4.0.0", - "formidable": "^2.0.1", - "methods": "^1.1.2", - "mime": "2.6.0", - "qs": "^6.11.0", - "semver": "^7.3.8" - }, - "dependencies": { - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "requires": { - "ms": "2.1.2" - } - }, - "mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==" - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - } - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" - }, - "taffydb": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", - "integrity": "sha512-y3JaeRSplks6NYQuCOj3ZFMO3j60rTwbuKCvZxsAraGYH2epusatvZ0baZYA01WsGqJBq/Dl6vOrMUJqyMj8kA==" - }, - "teeny-request": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-8.0.2.tgz", - "integrity": "sha512-34pe0a4zASseXZCKdeTiIZqSKA8ETHb1EwItZr01PAR3CLPojeAKgSjzeNS4373gi59hNulyDrPKEbh2zO9sCg==", - "requires": { - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "node-fetch": "^2.6.1", - "stream-events": "^1.0.5", - "uuid": "^9.0.0" - } - }, - "tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "requires": { - "rimraf": "^3.0.0" - }, - "dependencies": { - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "requires": { - "glob": "^7.1.3" - } - } - } - }, - "toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" - }, - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "requires": { - "prelude-ls": "~1.1.2" - } - }, - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==" - }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } - }, - "uc.micro": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" - }, - "uglify-js": { - "version": "3.17.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", - "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==" - }, - "underscore": { - "version": "1.13.6", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", - "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==" - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "requires": { - "punycode": "^2.1.0" - } - }, - "util": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", - "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", - "requires": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "which-typed-array": "^1.1.2" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" - }, - "uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==" - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "which-typed-array": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", - "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", - "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.10" - } - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, - "xmlcreate": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", - "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==" - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - } - }, - "yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==" - } - } -} diff --git a/blueprints/apigee/bigquery-analytics/functions/gcs2bq/package-lock.json b/blueprints/apigee/bigquery-analytics/functions/gcs2bq/package-lock.json deleted file mode 100644 index c5a7620b..00000000 --- a/blueprints/apigee/bigquery-analytics/functions/gcs2bq/package-lock.json +++ /dev/null @@ -1,5675 +0,0 @@ -{ - "name": "gcs2bq", - "version": "1.0.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "gcs2bq", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "@google-cloud/bigquery": "^6.0.3", - "@google-cloud/functions-framework": "^3.1.2", - "@google-cloud/logging-bunyan": "^4.2.0", - "@google-cloud/storage": "^6.7.0", - "bunyan": "^1.8.15" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "dependencies": { - "@babel/highlight": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", - "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/parser": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.2.tgz", - "integrity": "sha512-afk318kh2uKbo7BEj2QtEi8HVCGrwHUffrYDy7dgVcSa2j9lY3LDjPzcyGdpX7xgm35aWqvciZJ4WKmdF/SxYg==", - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@google-cloud/bigquery": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@google-cloud/bigquery/-/bigquery-6.0.3.tgz", - "integrity": "sha512-BP464228S9dqDCb4dR99h9D8+N498YZi/AZvoOJUaieg2H6qbiYBE1xlYuaMvyV1WEQT/2/yZTCJnCo5WiaY0Q==", - "dependencies": { - "@google-cloud/common": "^4.0.0", - "@google-cloud/paginator": "^4.0.0", - "@google-cloud/promisify": "^3.0.0", - "arrify": "^2.0.1", - "big.js": "^6.0.0", - "duplexify": "^4.0.0", - "extend": "^3.0.2", - "is": "^3.3.0", - "p-event": "^4.1.0", - "readable-stream": "^4.0.0", - "stream-events": "^1.0.5", - "uuid": "^8.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@google-cloud/bigquery/node_modules/@google-cloud/paginator": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-4.0.1.tgz", - "integrity": "sha512-6G1ui6bWhNyHjmbYwavdN7mpVPRBtyDg/bfqBTAlwr413On2TnFNfDxc9UhTJctkgoCDgQXEKiRPLPR9USlkbQ==", - "dependencies": { - "arrify": "^2.0.0", - "extend": "^3.0.2" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@google-cloud/bigquery/node_modules/readable-stream": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.2.0.tgz", - "integrity": "sha512-gJrBHsaI3lgBoGMW/jHZsQ/o/TIWiu5ENCJG1BB7fuCKzpFM8GaS2UoBVt9NO+oI+3FcrBNbUkl3ilDe09aY4A==", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@google-cloud/common": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-4.0.3.tgz", - "integrity": "sha512-fUoMo5b8iAKbrYpneIRV3z95AlxVJPrjpevxs4SKoclngWZvTXBSGpNisF5+x5m+oNGve7jfB1e6vNBZBUs7Fw==", - "dependencies": { - "@google-cloud/projectify": "^3.0.0", - "@google-cloud/promisify": "^3.0.0", - "arrify": "^2.0.1", - "duplexify": "^4.1.1", - "ent": "^2.2.0", - "extend": "^3.0.2", - "google-auth-library": "^8.0.2", - "retry-request": "^5.0.0", - "teeny-request": "^8.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@google-cloud/functions-framework": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@google-cloud/functions-framework/-/functions-framework-3.1.2.tgz", - "integrity": "sha512-pYvEH65/Rqh1JNPdcBmorcV7Xoom2/iOSmbtYza8msro7Inl+qOYxbyMiQfySD2gwAyn38WyWPRqsDRcf/BFLg==", - "dependencies": { - "@types/express": "4.17.13", - "body-parser": "^1.18.3", - "cloudevents": "^6.0.0", - "express": "^4.16.4", - "minimist": "^1.2.5", - "on-finished": "^2.3.0", - "read-pkg-up": "^7.0.1", - "semver": "^7.3.5" - }, - "bin": { - "functions-framework": "build/src/main.js", - "functions-framework-nodejs": "build/src/main.js" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@google-cloud/logging": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@google-cloud/logging/-/logging-10.3.0.tgz", - "integrity": "sha512-3QNSMci/8mmvLs4Iyb6Z/pFT/lJCDPWGWSRx08+Yi254xpva32pOU0grzgbPYls8SFhDWUQgYr9DGZg+IH0kEQ==", - "dependencies": { - "@google-cloud/common": "^4.0.0", - "@google-cloud/paginator": "^4.0.0", - "@google-cloud/projectify": "^3.0.0", - "@google-cloud/promisify": "^3.0.0", - "arrify": "^2.0.1", - "dot-prop": "^6.0.0", - "eventid": "^2.0.0", - "extend": "^3.0.2", - "gcp-metadata": "^4.0.0", - "google-auth-library": "^8.0.2", - "google-gax": "^3.5.2", - "on-finished": "^2.3.0", - "pumpify": "^2.0.1", - "stream-events": "^1.0.5", - "uuid": "^9.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@google-cloud/logging-bunyan": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@google-cloud/logging-bunyan/-/logging-bunyan-4.2.0.tgz", - "integrity": "sha512-BbzbJguK0sIZedO/0p27N5FDRUkdH2KsiejkoXOTNItU2GI8LweM7dtxihV9m7TcYOXVIxPhirnn8Tu3miq/VA==", - "dependencies": { - "@google-cloud/logging": "^10.2.2", - "google-auth-library": "^8.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "bunyan": "*" - } - }, - "node_modules/@google-cloud/logging/node_modules/@google-cloud/paginator": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-4.0.1.tgz", - "integrity": "sha512-6G1ui6bWhNyHjmbYwavdN7mpVPRBtyDg/bfqBTAlwr413On2TnFNfDxc9UhTJctkgoCDgQXEKiRPLPR9USlkbQ==", - "dependencies": { - "arrify": "^2.0.0", - "extend": "^3.0.2" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@google-cloud/logging/node_modules/gaxios": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.3.tgz", - "integrity": "sha512-gSaYYIO1Y3wUtdfHmjDUZ8LWaxJQpiavzbF5Kq53akSzvmVg0RfyOcFDbO1KJ/KCGRFz2qG+lS81F0nkr7cRJA==", - "dependencies": { - "abort-controller": "^3.0.0", - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.7" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@google-cloud/logging/node_modules/gcp-metadata": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.3.1.tgz", - "integrity": "sha512-x850LS5N7V1F3UcV7PoupzGsyD6iVwTVvsh3tbXfkctZnBnjW5yu5z1/3k3SehF7TyoTIe78rJs02GMMy+LF+A==", - "dependencies": { - "gaxios": "^4.0.0", - "json-bigint": "^1.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@google-cloud/logging/node_modules/uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@google-cloud/paginator": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-3.0.7.tgz", - "integrity": "sha512-jJNutk0arIQhmpUUQJPJErsojqo834KcyB6X7a1mxuic8i1tKXxde8E69IZxNZawRIlZdIK2QY4WALvlK5MzYQ==", - "dependencies": { - "arrify": "^2.0.0", - "extend": "^3.0.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@google-cloud/projectify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-3.0.0.tgz", - "integrity": "sha512-HRkZsNmjScY6Li8/kb70wjGlDDyLkVk3KvoEo9uIoxSjYLJasGiCch9+PqRVDOCGUFvEIqyogl+BeqILL4OJHA==", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@google-cloud/promisify": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-3.0.1.tgz", - "integrity": "sha512-z1CjRjtQyBOYL+5Qr9DdYIfrdLBe746jRTYfaYU6MeXkqp7UfYs/jX16lFFVzZ7PGEJvqZNqYUEtb1mvDww4pA==", - "engines": { - "node": ">=12" - } - }, - "node_modules/@google-cloud/storage": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-6.7.0.tgz", - "integrity": "sha512-iEit3dvUhGQV3pPC8aci/Y+F6K2QJ/UvcXhymj8gnO8IYQfZSZvFf361yX4BWNUlbHzanUQVQdF9RvgEM8fwpw==", - "dependencies": { - "@google-cloud/paginator": "^3.0.7", - "@google-cloud/projectify": "^3.0.0", - "@google-cloud/promisify": "^3.0.0", - "abort-controller": "^3.0.0", - "async-retry": "^1.3.3", - "compressible": "^2.0.12", - "duplexify": "^4.0.0", - "ent": "^2.2.0", - "extend": "^3.0.2", - "gaxios": "^5.0.0", - "google-auth-library": "^8.0.1", - "mime": "^3.0.0", - "mime-types": "^2.0.8", - "p-limit": "^3.0.1", - "retry-request": "^5.0.0", - "teeny-request": "^8.0.0", - "uuid": "^8.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@grpc/grpc-js": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.7.3.tgz", - "integrity": "sha512-H9l79u4kJ2PVSxUNA08HMYAnUBLj9v6KjYQ7SQ71hOZcEXhShE/y5iQCesP8+6/Ik/7i2O0a10bPquIcYfufog==", - "dependencies": { - "@grpc/proto-loader": "^0.7.0", - "@types/node": ">=12.12.47" - }, - "engines": { - "node": "^8.13.0 || >=10.10.0" - } - }, - "node_modules/@grpc/proto-loader": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.3.tgz", - "integrity": "sha512-5dAvoZwna2Py3Ef96Ux9jIkp3iZ62TUsV00p3wVBPNX5K178UbNi8Q7gQVqwXT1Yq9RejIGG9G2IPEo93T6RcA==", - "dependencies": { - "@types/long": "^4.0.1", - "lodash.camelcase": "^4.3.0", - "long": "^4.0.0", - "protobufjs": "^7.0.0", - "yargs": "^16.2.0" - }, - "bin": { - "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" - }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" - }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" - }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" - }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" - }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" - }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" - }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" - }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" - }, - "node_modules/@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "engines": { - "node": ">= 10" - } - }, - "node_modules/@types/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/connect": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", - "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/express": { - "version": "4.17.13", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", - "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.18", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "4.17.31", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.31.tgz", - "integrity": "sha512-DxMhY+NAsTwMMFHBTtJFNp5qiHKJ7TeqOo23zVEM9alT1Ml27Q3xcTH0xwxn7Q0BbMcVEJOs/7aQtUWupUQN3Q==", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*" - } - }, - "node_modules/@types/linkify-it": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", - "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==" - }, - "node_modules/@types/long": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", - "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" - }, - "node_modules/@types/markdown-it": { - "version": "12.2.3", - "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", - "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", - "dependencies": { - "@types/linkify-it": "*", - "@types/mdurl": "*" - } - }, - "node_modules/@types/mdurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", - "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==" - }, - "node_modules/@types/mime": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", - "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==" - }, - "node_modules/@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==" - }, - "node_modules/@types/normalize-package-data": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", - "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==" - }, - "node_modules/@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" - }, - "node_modules/@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" - }, - "node_modules/@types/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==", - "dependencies": { - "@types/mime": "*", - "@types/node": "*" - } - }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/ajv": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz", - "integrity": "sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" - }, - "node_modules/arrify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", - "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", - "engines": { - "node": ">=8" - } - }, - "node_modules/async-retry": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", - "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", - "dependencies": { - "retry": "0.13.1" - } - }, - "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/big.js": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-6.2.1.tgz", - "integrity": "sha512-bCtHMwL9LeDIozFn+oNhhFoq+yQ3BNdnsLSASUxLciOb1vgvpHsIO1dsENiGMgbb4SkP5TrzWzRiLddn8ahVOQ==", - "engines": { - "node": "*" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/bigjs" - } - }, - "node_modules/bignumber.js": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.0.tgz", - "integrity": "sha512-4LwHK4nfDOraBCtst+wOWIHbu1vhvAPJK8g8nROd4iuc3PSEjWif/qwbkh8jwCJz6yDBvtU4KPynETgrfh7y3A==", - "engines": { - "node": "*" - } - }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" - }, - "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" - }, - "node_modules/bunyan": { - "version": "1.8.15", - "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.15.tgz", - "integrity": "sha512-0tECWShh6wUysgucJcBAoYegf3JJoZWibxdqhTm7OHPeT42qdjkZ29QCMcKwbgU1kiH+auSIasNRXMLWXafXig==", - "engines": [ - "node >=0.10.0" - ], - "bin": { - "bunyan": "bin/bunyan" - }, - "optionalDependencies": { - "dtrace-provider": "~0.8", - "moment": "^2.19.3", - "mv": "~2", - "safe-json-stringify": "~1" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/catharsis": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", - "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", - "dependencies": { - "lodash": "^4.17.15" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/cloudevents": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/cloudevents/-/cloudevents-6.0.3.tgz", - "integrity": "sha512-ADEHAv2KShH/cDIy2GP+npFz3R6Fu/UCsUO/j4kYA9VqN4yhGdF+Zg6wmjeq6qlUvlaKdrVBwgZuH/w57IsyGQ==", - "dependencies": { - "ajv": "^8.11.0", - "ajv-formats": "^2.1.1", - "util": "^0.12.4", - "uuid": "^8.3.2" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "dependencies": { - "mime-db": ">= 1.43.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/dot-prop": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", - "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", - "dependencies": { - "is-obj": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/dtrace-provider": { - "version": "0.8.8", - "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.8.tgz", - "integrity": "sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==", - "hasInstallScript": true, - "optional": true, - "dependencies": { - "nan": "^2.14.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/duplexify": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", - "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==", - "dependencies": { - "end-of-stream": "^1.4.1", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1", - "stream-shift": "^1.0.0" - } - }, - "node_modules/ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/ent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==" - }, - "node_modules/entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, - "node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "engines": { - "node": ">=8" - } - }, - "node_modules/escodegen": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", - "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=4.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/escodegen/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/espree": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", - "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", - "dependencies": { - "acorn": "^8.8.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/eventid": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/eventid/-/eventid-2.0.1.tgz", - "integrity": "sha512-sPNTqiMokAvV048P2c9+foqVJzk49o6d4e0D/sq5jog3pw+4kBgyR0gaM1FM7Mx6Kzd9dztesh9oYz1LWWOpzw==", - "dependencies": { - "uuid": "^8.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.1", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.5.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" - }, - "node_modules/fast-text-encoding": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz", - "integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==" - }, - "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dependencies": { - "is-callable": "^1.1.3" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "node_modules/gaxios": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.0.2.tgz", - "integrity": "sha512-TjtV2AJOZoMQqRYoy5eM8cCQogYwazWNYLQ72QB0kwa6vHHruYkGmhhyrlzbmgNHK1dNnuP2WSH81urfzyN2Og==", - "dependencies": { - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.7" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/gcp-metadata": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.0.1.tgz", - "integrity": "sha512-jiRJ+Fk7e8FH68Z6TLaqwea307OktJpDjmYnU7/li6ziwvVvU2RlrCyQo5vkdeP94chm0kcSCOOszvmuaioq3g==", - "dependencies": { - "gaxios": "^5.0.0", - "json-bigint": "^1.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", - "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/glob": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", - "integrity": "sha512-MKZeRNyYZAVVVG1oZeLaWie1uweH40m9AZwIwxyPbTSX4hHrVYSzLg0Ro5Z5R7XKkIX+Cc6oD1rqeDJnwsB8/A==", - "optional": true, - "dependencies": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/google-auth-library": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.6.0.tgz", - "integrity": "sha512-y6bw1yTWMVgs1vGJwBZ3uu+uIClfgxQfsEVcTNKjQeNQOVwox69+ZUgTeTAzrh+74hBqrk1gWyb9RsQVDI7seg==", - "dependencies": { - "arrify": "^2.0.0", - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "fast-text-encoding": "^1.0.0", - "gaxios": "^5.0.0", - "gcp-metadata": "^5.0.0", - "gtoken": "^6.1.0", - "jws": "^4.0.0", - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/google-gax": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.5.2.tgz", - "integrity": "sha512-AyP53w0gHcWlzxm+jSgqCR3Xu4Ld7EpSjhtNBnNhzwwWaIUyphH9kBGNIEH+i4UGkTUXOY29K/Re8EiAvkBRGw==", - "dependencies": { - "@grpc/grpc-js": "~1.7.0", - "@grpc/proto-loader": "^0.7.0", - "@types/long": "^4.0.0", - "abort-controller": "^3.0.0", - "duplexify": "^4.0.0", - "fast-text-encoding": "^1.0.3", - "google-auth-library": "^8.0.2", - "is-stream-ended": "^0.1.4", - "node-fetch": "^2.6.1", - "object-hash": "^3.0.0", - "proto3-json-serializer": "^1.0.0", - "protobufjs": "7.1.2", - "protobufjs-cli": "1.0.2", - "retry-request": "^5.0.0" - }, - "bin": { - "compileProtos": "build/tools/compileProtos.js", - "minifyProtoJson": "build/tools/minify.js" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/google-p12-pem": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz", - "integrity": "sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==", - "dependencies": { - "node-forge": "^1.3.1" - }, - "bin": { - "gp12-pem": "build/src/bin/gp12-pem.js" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" - }, - "node_modules/gtoken": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz", - "integrity": "sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==", - "dependencies": { - "gaxios": "^5.0.1", - "google-p12-pem": "^4.0.0", - "jws": "^4.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==" - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/is/-/is-3.3.0.tgz", - "integrity": "sha512-nW24QBoPcFGGHJGUwnfpI7Yc5CdqWNdsyHQszVE/z2pKHXzh7FZ5GWhJqSyaQ9wMkQnsTx+kAI8bHlCX4tKdbg==", - "engines": { - "node": "*" - } - }, - "node_modules/is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-core-module": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-function": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", - "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-stream-ended": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", - "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==" - }, - "node_modules/is-typed-array": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", - "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "node_modules/js2xmlparser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", - "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", - "dependencies": { - "xmlcreate": "^2.0.4" - } - }, - "node_modules/jsdoc": { - "version": "3.6.11", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.11.tgz", - "integrity": "sha512-8UCU0TYeIYD9KeLzEcAu2q8N/mx9O3phAGl32nmHlE0LpaJL71mMkP4d+QE5zWfNt50qheHtOZ0qoxVrsX5TUg==", - "dependencies": { - "@babel/parser": "^7.9.4", - "@types/markdown-it": "^12.2.3", - "bluebird": "^3.7.2", - "catharsis": "^0.9.0", - "escape-string-regexp": "^2.0.0", - "js2xmlparser": "^4.0.2", - "klaw": "^3.0.0", - "markdown-it": "^12.3.2", - "markdown-it-anchor": "^8.4.1", - "marked": "^4.0.10", - "mkdirp": "^1.0.4", - "requizzle": "^0.2.3", - "strip-json-comments": "^3.1.0", - "taffydb": "2.6.2", - "underscore": "~1.13.2" - }, - "bin": { - "jsdoc": "jsdoc.js" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/jsdoc/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/json-bigint": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", - "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", - "dependencies": { - "bignumber.js": "^9.0.0" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, - "node_modules/jwa": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", - "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", - "dependencies": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/jws": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", - "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", - "dependencies": { - "jwa": "^2.0.0", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/klaw": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", - "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", - "dependencies": { - "graceful-fs": "^4.1.9" - } - }, - "node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" - }, - "node_modules/linkify-it": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", - "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", - "dependencies": { - "uc.micro": "^1.0.1" - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "node_modules/lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" - }, - "node_modules/long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/markdown-it": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", - "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", - "dependencies": { - "argparse": "^2.0.1", - "entities": "~2.1.0", - "linkify-it": "^3.0.1", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" - }, - "bin": { - "markdown-it": "bin/markdown-it.js" - } - }, - "node_modules/markdown-it-anchor": { - "version": "8.6.5", - "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.5.tgz", - "integrity": "sha512-PI1qEHHkTNWT+X6Ip9w+paonfIQ+QZP9sCeMYi47oqhH+EsW8CrJ8J7CzV19QVOj6il8ATGbK2nTECj22ZHGvQ==", - "peerDependencies": { - "@types/markdown-it": "*", - "markdown-it": "*" - } - }, - "node_modules/marked": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.2.tgz", - "integrity": "sha512-JjBTFTAvuTgANXx82a5vzK9JLSMoV6V3LBVn4Uhdso6t7vXrGx7g1Cd2r6NYSsxrYbQGFCMqBDhFHyK5q2UvcQ==", - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 12" - } - }, - "node_modules/mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==" - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", - "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", - "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "optional": true, - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/moment": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", - "optional": true, - "engines": { - "node": "*" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/mv": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", - "integrity": "sha512-at/ZndSy3xEGJ8i0ygALh8ru9qy7gWW1cmkaqBN29JmMlIvM//MEO9y1sk/avxuwnPcfhkejkLsuPxH81BrkSg==", - "optional": true, - "dependencies": { - "mkdirp": "~0.5.1", - "ncp": "~2.0.0", - "rimraf": "~2.4.0" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/nan": { - "version": "2.17.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz", - "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==", - "optional": true - }, - "node_modules/ncp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", - "integrity": "sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA==", - "optional": true, - "bin": { - "ncp": "bin/ncp" - } - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", - "engines": { - "node": ">= 6.13.0" - } - }, - "node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/object-inspect": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", - "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-event": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.2.0.tgz", - "integrity": "sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==", - "dependencies": { - "p-timeout": "^3.1.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", - "engines": { - "node": ">=4" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-timeout": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", - "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", - "dependencies": { - "p-finally": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" - }, - "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" - }, - "node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/proto3-json-serializer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-1.1.0.tgz", - "integrity": "sha512-SjXwUWe/vANGs/mJJTbw5++7U67nwsymg7qsoPtw6GiXqw3kUy8ByojrlEdVE2efxAdKreX8WkDafxvYW95ZQg==", - "dependencies": { - "protobufjs": "^7.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/protobufjs": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", - "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", - "hasInstallScript": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/node": ">=13.7.0", - "long": "^5.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/protobufjs-cli": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.0.2.tgz", - "integrity": "sha512-cz9Pq9p/Zs7okc6avH20W7QuyjTclwJPgqXG11jNaulfS3nbVisID8rC+prfgq0gbZE0w9LBFd1OKFF03kgFzg==", - "dependencies": { - "chalk": "^4.0.0", - "escodegen": "^1.13.0", - "espree": "^9.0.0", - "estraverse": "^5.1.0", - "glob": "^8.0.0", - "jsdoc": "^3.6.3", - "minimist": "^1.2.0", - "semver": "^7.1.2", - "tmp": "^0.2.1", - "uglify-js": "^3.7.7" - }, - "bin": { - "pbjs": "bin/pbjs", - "pbts": "bin/pbts" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "protobufjs": "^7.0.0" - } - }, - "node_modules/protobufjs-cli/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/protobufjs-cli/node_modules/glob": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", - "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/protobufjs-cli/node_modules/minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/protobufjs/node_modules/long": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", - "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==" - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/pumpify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz", - "integrity": "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==", - "dependencies": { - "duplexify": "^4.1.1", - "inherits": "^2.0.3", - "pump": "^3.0.0" - } - }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "engines": { - "node": ">=6" - } - }, - "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dependencies": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg/node_modules/type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/requizzle": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.3.tgz", - "integrity": "sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ==", - "dependencies": { - "lodash": "^4.17.14" - } - }, - "node_modules/resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "dependencies": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "engines": { - "node": ">= 4" - } - }, - "node_modules/retry-request": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-5.0.2.tgz", - "integrity": "sha512-wfI3pk7EE80lCIXprqh7ym48IHYdwmAAzESdbU8Q9l7pnRCk9LEhpbOTNKjz6FARLm/Bl5m+4F0ABxOkYUujSQ==", - "dependencies": { - "debug": "^4.1.1", - "extend": "^3.0.2" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/rimraf": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", - "integrity": "sha512-J5xnxTyqaiw06JjMftq7L9ouA448dw/E7dKghkP9WpKNuwmARNNg+Gk8/u5ryb9N/Yo2+z3MCwuqFK/+qPOPfQ==", - "optional": true, - "dependencies": { - "glob": "^6.0.1" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safe-json-stringify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz", - "integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==", - "optional": true - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/send/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz", - "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==" - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/stream-events": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", - "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", - "dependencies": { - "stubs": "^3.0.0" - } - }, - "node_modules/stream-shift": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", - "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/stubs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", - "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==" - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/taffydb": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", - "integrity": "sha512-y3JaeRSplks6NYQuCOj3ZFMO3j60rTwbuKCvZxsAraGYH2epusatvZ0baZYA01WsGqJBq/Dl6vOrMUJqyMj8kA==" - }, - "node_modules/teeny-request": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-8.0.2.tgz", - "integrity": "sha512-34pe0a4zASseXZCKdeTiIZqSKA8ETHb1EwItZr01PAR3CLPojeAKgSjzeNS4373gi59hNulyDrPKEbh2zO9sCg==", - "dependencies": { - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "node-fetch": "^2.6.1", - "stream-events": "^1.0.5", - "uuid": "^9.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/teeny-request/node_modules/uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "dependencies": { - "rimraf": "^3.0.0" - }, - "engines": { - "node": ">=8.17.0" - } - }, - "node_modules/tmp/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/tmp/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "engines": { - "node": ">=8" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/uc.micro": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" - }, - "node_modules/uglify-js": { - "version": "3.17.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", - "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/underscore": { - "version": "1.13.6", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", - "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==" - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/util": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", - "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", - "dependencies": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "which-typed-array": "^1.1.2" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", - "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.10" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, - "node_modules/xmlcreate": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", - "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==" - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "engines": { - "node": ">=10" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "requires": { - "@babel/highlight": "^7.18.6" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" - }, - "@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", - "requires": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "@babel/parser": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.2.tgz", - "integrity": "sha512-afk318kh2uKbo7BEj2QtEi8HVCGrwHUffrYDy7dgVcSa2j9lY3LDjPzcyGdpX7xgm35aWqvciZJ4WKmdF/SxYg==" - }, - "@google-cloud/bigquery": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@google-cloud/bigquery/-/bigquery-6.0.3.tgz", - "integrity": "sha512-BP464228S9dqDCb4dR99h9D8+N498YZi/AZvoOJUaieg2H6qbiYBE1xlYuaMvyV1WEQT/2/yZTCJnCo5WiaY0Q==", - "requires": { - "@google-cloud/common": "^4.0.0", - "@google-cloud/paginator": "^4.0.0", - "@google-cloud/promisify": "^3.0.0", - "arrify": "^2.0.1", - "big.js": "^6.0.0", - "duplexify": "^4.0.0", - "extend": "^3.0.2", - "is": "^3.3.0", - "p-event": "^4.1.0", - "readable-stream": "^4.0.0", - "stream-events": "^1.0.5", - "uuid": "^8.0.0" - }, - "dependencies": { - "@google-cloud/paginator": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-4.0.1.tgz", - "integrity": "sha512-6G1ui6bWhNyHjmbYwavdN7mpVPRBtyDg/bfqBTAlwr413On2TnFNfDxc9UhTJctkgoCDgQXEKiRPLPR9USlkbQ==", - "requires": { - "arrify": "^2.0.0", - "extend": "^3.0.2" - } - }, - "readable-stream": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.2.0.tgz", - "integrity": "sha512-gJrBHsaI3lgBoGMW/jHZsQ/o/TIWiu5ENCJG1BB7fuCKzpFM8GaS2UoBVt9NO+oI+3FcrBNbUkl3ilDe09aY4A==", - "requires": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10" - } - } - } - }, - "@google-cloud/common": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-4.0.3.tgz", - "integrity": "sha512-fUoMo5b8iAKbrYpneIRV3z95AlxVJPrjpevxs4SKoclngWZvTXBSGpNisF5+x5m+oNGve7jfB1e6vNBZBUs7Fw==", - "requires": { - "@google-cloud/projectify": "^3.0.0", - "@google-cloud/promisify": "^3.0.0", - "arrify": "^2.0.1", - "duplexify": "^4.1.1", - "ent": "^2.2.0", - "extend": "^3.0.2", - "google-auth-library": "^8.0.2", - "retry-request": "^5.0.0", - "teeny-request": "^8.0.0" - } - }, - "@google-cloud/functions-framework": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@google-cloud/functions-framework/-/functions-framework-3.1.2.tgz", - "integrity": "sha512-pYvEH65/Rqh1JNPdcBmorcV7Xoom2/iOSmbtYza8msro7Inl+qOYxbyMiQfySD2gwAyn38WyWPRqsDRcf/BFLg==", - "requires": { - "@types/express": "4.17.13", - "body-parser": "^1.18.3", - "cloudevents": "^6.0.0", - "express": "^4.16.4", - "minimist": "^1.2.5", - "on-finished": "^2.3.0", - "read-pkg-up": "^7.0.1", - "semver": "^7.3.5" - } - }, - "@google-cloud/logging": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@google-cloud/logging/-/logging-10.3.0.tgz", - "integrity": "sha512-3QNSMci/8mmvLs4Iyb6Z/pFT/lJCDPWGWSRx08+Yi254xpva32pOU0grzgbPYls8SFhDWUQgYr9DGZg+IH0kEQ==", - "requires": { - "@google-cloud/common": "^4.0.0", - "@google-cloud/paginator": "^4.0.0", - "@google-cloud/projectify": "^3.0.0", - "@google-cloud/promisify": "^3.0.0", - "arrify": "^2.0.1", - "dot-prop": "^6.0.0", - "eventid": "^2.0.0", - "extend": "^3.0.2", - "gcp-metadata": "^4.0.0", - "google-auth-library": "^8.0.2", - "google-gax": "^3.5.2", - "on-finished": "^2.3.0", - "pumpify": "^2.0.1", - "stream-events": "^1.0.5", - "uuid": "^9.0.0" - }, - "dependencies": { - "@google-cloud/paginator": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-4.0.1.tgz", - "integrity": "sha512-6G1ui6bWhNyHjmbYwavdN7mpVPRBtyDg/bfqBTAlwr413On2TnFNfDxc9UhTJctkgoCDgQXEKiRPLPR9USlkbQ==", - "requires": { - "arrify": "^2.0.0", - "extend": "^3.0.2" - } - }, - "gaxios": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.3.tgz", - "integrity": "sha512-gSaYYIO1Y3wUtdfHmjDUZ8LWaxJQpiavzbF5Kq53akSzvmVg0RfyOcFDbO1KJ/KCGRFz2qG+lS81F0nkr7cRJA==", - "requires": { - "abort-controller": "^3.0.0", - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.7" - } - }, - "gcp-metadata": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.3.1.tgz", - "integrity": "sha512-x850LS5N7V1F3UcV7PoupzGsyD6iVwTVvsh3tbXfkctZnBnjW5yu5z1/3k3SehF7TyoTIe78rJs02GMMy+LF+A==", - "requires": { - "gaxios": "^4.0.0", - "json-bigint": "^1.0.0" - } - }, - "uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==" - } - } - }, - "@google-cloud/logging-bunyan": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@google-cloud/logging-bunyan/-/logging-bunyan-4.2.0.tgz", - "integrity": "sha512-BbzbJguK0sIZedO/0p27N5FDRUkdH2KsiejkoXOTNItU2GI8LweM7dtxihV9m7TcYOXVIxPhirnn8Tu3miq/VA==", - "requires": { - "@google-cloud/logging": "^10.2.2", - "google-auth-library": "^8.0.2" - } - }, - "@google-cloud/paginator": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-3.0.7.tgz", - "integrity": "sha512-jJNutk0arIQhmpUUQJPJErsojqo834KcyB6X7a1mxuic8i1tKXxde8E69IZxNZawRIlZdIK2QY4WALvlK5MzYQ==", - "requires": { - "arrify": "^2.0.0", - "extend": "^3.0.2" - } - }, - "@google-cloud/projectify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-3.0.0.tgz", - "integrity": "sha512-HRkZsNmjScY6Li8/kb70wjGlDDyLkVk3KvoEo9uIoxSjYLJasGiCch9+PqRVDOCGUFvEIqyogl+BeqILL4OJHA==" - }, - "@google-cloud/promisify": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-3.0.1.tgz", - "integrity": "sha512-z1CjRjtQyBOYL+5Qr9DdYIfrdLBe746jRTYfaYU6MeXkqp7UfYs/jX16lFFVzZ7PGEJvqZNqYUEtb1mvDww4pA==" - }, - "@google-cloud/storage": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-6.7.0.tgz", - "integrity": "sha512-iEit3dvUhGQV3pPC8aci/Y+F6K2QJ/UvcXhymj8gnO8IYQfZSZvFf361yX4BWNUlbHzanUQVQdF9RvgEM8fwpw==", - "requires": { - "@google-cloud/paginator": "^3.0.7", - "@google-cloud/projectify": "^3.0.0", - "@google-cloud/promisify": "^3.0.0", - "abort-controller": "^3.0.0", - "async-retry": "^1.3.3", - "compressible": "^2.0.12", - "duplexify": "^4.0.0", - "ent": "^2.2.0", - "extend": "^3.0.2", - "gaxios": "^5.0.0", - "google-auth-library": "^8.0.1", - "mime": "^3.0.0", - "mime-types": "^2.0.8", - "p-limit": "^3.0.1", - "retry-request": "^5.0.0", - "teeny-request": "^8.0.0", - "uuid": "^8.0.0" - } - }, - "@grpc/grpc-js": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.7.3.tgz", - "integrity": "sha512-H9l79u4kJ2PVSxUNA08HMYAnUBLj9v6KjYQ7SQ71hOZcEXhShE/y5iQCesP8+6/Ik/7i2O0a10bPquIcYfufog==", - "requires": { - "@grpc/proto-loader": "^0.7.0", - "@types/node": ">=12.12.47" - } - }, - "@grpc/proto-loader": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.3.tgz", - "integrity": "sha512-5dAvoZwna2Py3Ef96Ux9jIkp3iZ62TUsV00p3wVBPNX5K178UbNi8Q7gQVqwXT1Yq9RejIGG9G2IPEo93T6RcA==", - "requires": { - "@types/long": "^4.0.1", - "lodash.camelcase": "^4.3.0", - "long": "^4.0.0", - "protobufjs": "^7.0.0", - "yargs": "^16.2.0" - } - }, - "@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" - }, - "@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" - }, - "@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" - }, - "@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" - }, - "@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "requires": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" - }, - "@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" - }, - "@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" - }, - "@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" - }, - "@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" - }, - "@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==" - }, - "@types/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", - "requires": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "@types/connect": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", - "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", - "requires": { - "@types/node": "*" - } - }, - "@types/express": { - "version": "4.17.13", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", - "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", - "requires": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.18", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "@types/express-serve-static-core": { - "version": "4.17.31", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.31.tgz", - "integrity": "sha512-DxMhY+NAsTwMMFHBTtJFNp5qiHKJ7TeqOo23zVEM9alT1Ml27Q3xcTH0xwxn7Q0BbMcVEJOs/7aQtUWupUQN3Q==", - "requires": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*" - } - }, - "@types/linkify-it": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", - "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==" - }, - "@types/long": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", - "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" - }, - "@types/markdown-it": { - "version": "12.2.3", - "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", - "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", - "requires": { - "@types/linkify-it": "*", - "@types/mdurl": "*" - } - }, - "@types/mdurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", - "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==" - }, - "@types/mime": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", - "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==" - }, - "@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==" - }, - "@types/normalize-package-data": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", - "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==" - }, - "@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" - }, - "@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" - }, - "@types/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==", - "requires": { - "@types/mime": "*", - "@types/node": "*" - } - }, - "abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "requires": { - "event-target-shim": "^5.0.0" - } - }, - "accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "requires": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - } - }, - "acorn": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==" - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "requires": {} - }, - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "requires": { - "debug": "4" - } - }, - "ajv": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz", - "integrity": "sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==", - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "requires": { - "ajv": "^8.0.0" - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - }, - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" - }, - "arrify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", - "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==" - }, - "async-retry": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", - "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", - "requires": { - "retry": "0.13.1" - } - }, - "available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==" - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" - }, - "big.js": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-6.2.1.tgz", - "integrity": "sha512-bCtHMwL9LeDIozFn+oNhhFoq+yQ3BNdnsLSASUxLciOb1vgvpHsIO1dsENiGMgbb4SkP5TrzWzRiLddn8ahVOQ==" - }, - "bignumber.js": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.0.tgz", - "integrity": "sha512-4LwHK4nfDOraBCtst+wOWIHbu1vhvAPJK8g8nROd4iuc3PSEjWif/qwbkh8jwCJz6yDBvtU4KPynETgrfh7y3A==" - }, - "bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" - }, - "body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", - "requires": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" - }, - "bunyan": { - "version": "1.8.15", - "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.15.tgz", - "integrity": "sha512-0tECWShh6wUysgucJcBAoYegf3JJoZWibxdqhTm7OHPeT42qdjkZ29QCMcKwbgU1kiH+auSIasNRXMLWXafXig==", - "requires": { - "dtrace-provider": "~0.8", - "moment": "^2.19.3", - "mv": "~2", - "safe-json-stringify": "~1" - } - }, - "bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "catharsis": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", - "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", - "requires": { - "lodash": "^4.17.15" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "cloudevents": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/cloudevents/-/cloudevents-6.0.3.tgz", - "integrity": "sha512-ADEHAv2KShH/cDIy2GP+npFz3R6Fu/UCsUO/j4kYA9VqN4yhGdF+Zg6wmjeq6qlUvlaKdrVBwgZuH/w57IsyGQ==", - "requires": { - "ajv": "^8.11.0", - "ajv-formats": "^2.1.1", - "util": "^0.12.4", - "uuid": "^8.3.2" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "requires": { - "mime-db": ">= 1.43.0 < 2" - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "requires": { - "safe-buffer": "5.2.1" - } - }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" - }, - "cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "requires": { - "ms": "2.1.2" - } - }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" - }, - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" - }, - "destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" - }, - "dot-prop": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", - "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", - "requires": { - "is-obj": "^2.0.0" - } - }, - "dtrace-provider": { - "version": "0.8.8", - "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.8.tgz", - "integrity": "sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==", - "optional": true, - "requires": { - "nan": "^2.14.0" - } - }, - "duplexify": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", - "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==", - "requires": { - "end-of-stream": "^1.4.1", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1", - "stream-shift": "^1.0.0" - } - }, - "ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "requires": { - "once": "^1.4.0" - } - }, - "ent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==" - }, - "entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==" - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==" - }, - "escodegen": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", - "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", - "requires": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - }, - "dependencies": { - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" - } - } - }, - "eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==" - }, - "espree": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", - "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", - "requires": { - "acorn": "^8.8.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" - }, - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" - }, - "event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" - }, - "eventid": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/eventid/-/eventid-2.0.1.tgz", - "integrity": "sha512-sPNTqiMokAvV048P2c9+foqVJzk49o6d4e0D/sq5jog3pw+4kBgyR0gaM1FM7Mx6Kzd9dztesh9oYz1LWWOpzw==", - "requires": { - "uuid": "^8.0.0" - } - }, - "events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" - }, - "express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", - "requires": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.1", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.5.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" - }, - "fast-text-encoding": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz", - "integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==" - }, - "finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "requires": { - "is-callable": "^1.1.3" - } - }, - "forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "gaxios": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.0.2.tgz", - "integrity": "sha512-TjtV2AJOZoMQqRYoy5eM8cCQogYwazWNYLQ72QB0kwa6vHHruYkGmhhyrlzbmgNHK1dNnuP2WSH81urfzyN2Og==", - "requires": { - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.7" - } - }, - "gcp-metadata": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.0.1.tgz", - "integrity": "sha512-jiRJ+Fk7e8FH68Z6TLaqwea307OktJpDjmYnU7/li6ziwvVvU2RlrCyQo5vkdeP94chm0kcSCOOszvmuaioq3g==", - "requires": { - "gaxios": "^5.0.0", - "json-bigint": "^1.0.0" - } - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" - }, - "get-intrinsic": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", - "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" - } - }, - "glob": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", - "integrity": "sha512-MKZeRNyYZAVVVG1oZeLaWie1uweH40m9AZwIwxyPbTSX4hHrVYSzLg0Ro5Z5R7XKkIX+Cc6oD1rqeDJnwsB8/A==", - "optional": true, - "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "google-auth-library": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.6.0.tgz", - "integrity": "sha512-y6bw1yTWMVgs1vGJwBZ3uu+uIClfgxQfsEVcTNKjQeNQOVwox69+ZUgTeTAzrh+74hBqrk1gWyb9RsQVDI7seg==", - "requires": { - "arrify": "^2.0.0", - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "fast-text-encoding": "^1.0.0", - "gaxios": "^5.0.0", - "gcp-metadata": "^5.0.0", - "gtoken": "^6.1.0", - "jws": "^4.0.0", - "lru-cache": "^6.0.0" - } - }, - "google-gax": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.5.2.tgz", - "integrity": "sha512-AyP53w0gHcWlzxm+jSgqCR3Xu4Ld7EpSjhtNBnNhzwwWaIUyphH9kBGNIEH+i4UGkTUXOY29K/Re8EiAvkBRGw==", - "requires": { - "@grpc/grpc-js": "~1.7.0", - "@grpc/proto-loader": "^0.7.0", - "@types/long": "^4.0.0", - "abort-controller": "^3.0.0", - "duplexify": "^4.0.0", - "fast-text-encoding": "^1.0.3", - "google-auth-library": "^8.0.2", - "is-stream-ended": "^0.1.4", - "node-fetch": "^2.6.1", - "object-hash": "^3.0.0", - "proto3-json-serializer": "^1.0.0", - "protobufjs": "7.1.2", - "protobufjs-cli": "1.0.2", - "retry-request": "^5.0.0" - } - }, - "google-p12-pem": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz", - "integrity": "sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==", - "requires": { - "node-forge": "^1.3.1" - } - }, - "gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "requires": { - "get-intrinsic": "^1.1.3" - } - }, - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" - }, - "gtoken": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz", - "integrity": "sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==", - "requires": { - "gaxios": "^5.0.1", - "google-p12-pem": "^4.0.0", - "jws": "^4.0.0" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" - }, - "has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "requires": { - "has-symbols": "^1.0.2" - } - }, - "hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==" - }, - "http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "requires": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - } - }, - "http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "requires": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - } - }, - "https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "requires": { - "agent-base": "6", - "debug": "4" - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" - }, - "is": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/is/-/is-3.3.0.tgz", - "integrity": "sha512-nW24QBoPcFGGHJGUwnfpI7Yc5CdqWNdsyHQszVE/z2pKHXzh7FZ5GWhJqSyaQ9wMkQnsTx+kAI8bHlCX4tKdbg==" - }, - "is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" - }, - "is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==" - }, - "is-core-module": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", - "requires": { - "has": "^1.0.3" - } - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - }, - "is-generator-function": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", - "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" - }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" - }, - "is-stream-ended": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", - "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==" - }, - "is-typed-array": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", - "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", - "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "js2xmlparser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", - "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", - "requires": { - "xmlcreate": "^2.0.4" - } - }, - "jsdoc": { - "version": "3.6.11", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.11.tgz", - "integrity": "sha512-8UCU0TYeIYD9KeLzEcAu2q8N/mx9O3phAGl32nmHlE0LpaJL71mMkP4d+QE5zWfNt50qheHtOZ0qoxVrsX5TUg==", - "requires": { - "@babel/parser": "^7.9.4", - "@types/markdown-it": "^12.2.3", - "bluebird": "^3.7.2", - "catharsis": "^0.9.0", - "escape-string-regexp": "^2.0.0", - "js2xmlparser": "^4.0.2", - "klaw": "^3.0.0", - "markdown-it": "^12.3.2", - "markdown-it-anchor": "^8.4.1", - "marked": "^4.0.10", - "mkdirp": "^1.0.4", - "requizzle": "^0.2.3", - "strip-json-comments": "^3.1.0", - "taffydb": "2.6.2", - "underscore": "~1.13.2" - }, - "dependencies": { - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" - } - } - }, - "json-bigint": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", - "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", - "requires": { - "bignumber.js": "^9.0.0" - } - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, - "jwa": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", - "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", - "requires": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "jws": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", - "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", - "requires": { - "jwa": "^2.0.0", - "safe-buffer": "^5.0.1" - } - }, - "klaw": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", - "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", - "requires": { - "graceful-fs": "^4.1.9" - } - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" - }, - "linkify-it": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", - "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", - "requires": { - "uc.micro": "^1.0.1" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "requires": { - "p-locate": "^4.1.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" - }, - "long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "requires": { - "yallist": "^4.0.0" - } - }, - "markdown-it": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", - "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", - "requires": { - "argparse": "^2.0.1", - "entities": "~2.1.0", - "linkify-it": "^3.0.1", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" - } - }, - "markdown-it-anchor": { - "version": "8.6.5", - "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.5.tgz", - "integrity": "sha512-PI1qEHHkTNWT+X6Ip9w+paonfIQ+QZP9sCeMYi47oqhH+EsW8CrJ8J7CzV19QVOj6il8ATGbK2nTECj22ZHGvQ==", - "requires": {} - }, - "marked": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.2.tgz", - "integrity": "sha512-JjBTFTAvuTgANXx82a5vzK9JLSMoV6V3LBVn4Uhdso6t7vXrGx7g1Cd2r6NYSsxrYbQGFCMqBDhFHyK5q2UvcQ==" - }, - "mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==" - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" - }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" - }, - "mime": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", - "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==" - }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "requires": { - "mime-db": "1.52.0" - } - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", - "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==" - }, - "mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "optional": true, - "requires": { - "minimist": "^1.2.6" - } - }, - "moment": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", - "optional": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "mv": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", - "integrity": "sha512-at/ZndSy3xEGJ8i0ygALh8ru9qy7gWW1cmkaqBN29JmMlIvM//MEO9y1sk/avxuwnPcfhkejkLsuPxH81BrkSg==", - "optional": true, - "requires": { - "mkdirp": "~0.5.1", - "ncp": "~2.0.0", - "rimraf": "~2.4.0" - } - }, - "nan": { - "version": "2.17.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz", - "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==", - "optional": true - }, - "ncp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", - "integrity": "sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA==", - "optional": true - }, - "negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" - }, - "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "requires": { - "whatwg-url": "^5.0.0" - } - }, - "node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==" - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } - } - }, - "object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==" - }, - "object-inspect": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", - "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==" - }, - "on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "requires": { - "ee-first": "1.1.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "requires": { - "wrappy": "1" - } - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "p-event": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.2.0.tgz", - "integrity": "sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==", - "requires": { - "p-timeout": "^3.1.0" - } - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==" - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "requires": { - "p-limit": "^2.2.0" - }, - "dependencies": { - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "requires": { - "p-try": "^2.0.0" - } - } - } - }, - "p-timeout": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", - "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", - "requires": { - "p-finally": "^1.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" - }, - "parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" - }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==" - }, - "process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==" - }, - "proto3-json-serializer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-1.1.0.tgz", - "integrity": "sha512-SjXwUWe/vANGs/mJJTbw5++7U67nwsymg7qsoPtw6GiXqw3kUy8ByojrlEdVE2efxAdKreX8WkDafxvYW95ZQg==", - "requires": { - "protobufjs": "^7.0.0" - } - }, - "protobufjs": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", - "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", - "requires": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/node": ">=13.7.0", - "long": "^5.0.0" - }, - "dependencies": { - "long": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", - "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==" - } - } - }, - "protobufjs-cli": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.0.2.tgz", - "integrity": "sha512-cz9Pq9p/Zs7okc6avH20W7QuyjTclwJPgqXG11jNaulfS3nbVisID8rC+prfgq0gbZE0w9LBFd1OKFF03kgFzg==", - "requires": { - "chalk": "^4.0.0", - "escodegen": "^1.13.0", - "espree": "^9.0.0", - "estraverse": "^5.1.0", - "glob": "^8.0.0", - "jsdoc": "^3.6.3", - "minimist": "^1.2.0", - "semver": "^7.1.2", - "tmp": "^0.2.1", - "uglify-js": "^3.7.7" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "requires": { - "balanced-match": "^1.0.0" - } - }, - "glob": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", - "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - } - }, - "minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", - "requires": { - "brace-expansion": "^2.0.1" - } - } - } - }, - "proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "requires": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - } - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "pumpify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz", - "integrity": "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==", - "requires": { - "duplexify": "^4.1.1", - "inherits": "^2.0.3", - "pump": "^3.0.0" - } - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - }, - "qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "requires": { - "side-channel": "^1.0.4" - } - }, - "range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" - }, - "raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "requires": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } - }, - "read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "requires": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "dependencies": { - "type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==" - } - } - }, - "read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "requires": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" - }, - "require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" - }, - "requizzle": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.3.tgz", - "integrity": "sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ==", - "requires": { - "lodash": "^4.17.14" - } - }, - "resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "requires": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==" - }, - "retry-request": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-5.0.2.tgz", - "integrity": "sha512-wfI3pk7EE80lCIXprqh7ym48IHYdwmAAzESdbU8Q9l7pnRCk9LEhpbOTNKjz6FARLm/Bl5m+4F0ABxOkYUujSQ==", - "requires": { - "debug": "^4.1.1", - "extend": "^3.0.2" - } - }, - "rimraf": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", - "integrity": "sha512-J5xnxTyqaiw06JjMftq7L9ouA448dw/E7dKghkP9WpKNuwmARNNg+Gk8/u5ryb9N/Yo2+z3MCwuqFK/+qPOPfQ==", - "optional": true, - "requires": { - "glob": "^6.0.1" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "safe-json-stringify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz", - "integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==", - "optional": true - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "requires": { - "lru-cache": "^6.0.0" - } - }, - "send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "requires": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - } - } - }, - "serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - } - }, - "setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "optional": true - }, - "spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" - }, - "spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz", - "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==" - }, - "statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" - }, - "stream-events": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", - "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", - "requires": { - "stubs": "^3.0.0" - } - }, - "stream-shift": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", - "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "requires": { - "safe-buffer": "~5.2.0" - } - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" - }, - "stubs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", - "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" - }, - "taffydb": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", - "integrity": "sha512-y3JaeRSplks6NYQuCOj3ZFMO3j60rTwbuKCvZxsAraGYH2epusatvZ0baZYA01WsGqJBq/Dl6vOrMUJqyMj8kA==" - }, - "teeny-request": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-8.0.2.tgz", - "integrity": "sha512-34pe0a4zASseXZCKdeTiIZqSKA8ETHb1EwItZr01PAR3CLPojeAKgSjzeNS4373gi59hNulyDrPKEbh2zO9sCg==", - "requires": { - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "node-fetch": "^2.6.1", - "stream-events": "^1.0.5", - "uuid": "^9.0.0" - }, - "dependencies": { - "uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==" - } - } - }, - "tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "requires": { - "rimraf": "^3.0.0" - }, - "dependencies": { - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "requires": { - "glob": "^7.1.3" - } - } - } - }, - "toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" - }, - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "requires": { - "prelude-ls": "~1.1.2" - } - }, - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==" - }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } - }, - "uc.micro": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" - }, - "uglify-js": { - "version": "3.17.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", - "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==" - }, - "underscore": { - "version": "1.13.6", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", - "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==" - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "requires": { - "punycode": "^2.1.0" - } - }, - "util": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", - "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", - "requires": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "which-typed-array": "^1.1.2" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" - }, - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "which-typed-array": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", - "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", - "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.10" - } - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, - "xmlcreate": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", - "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==" - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - } - }, - "yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==" - }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" - } - } -} diff --git a/blueprints/apigee/bigquery-analytics/package-lock.json b/blueprints/apigee/bigquery-analytics/package-lock.json deleted file mode 100644 index 97ea7994..00000000 --- a/blueprints/apigee/bigquery-analytics/package-lock.json +++ /dev/null @@ -1,251 +0,0 @@ -{ - "name": "apigee", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "dependencies": { - "superagent-debugger": "^1.2.9" - } - }, - "node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "node_modules/moment": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", - "engines": { - "node": "*" - } - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/query-string": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", - "integrity": "sha512-O2XLNDBIg1DnTOa+2XrIwSiXEV8h2KImXUnjhhn2+UsvZ+Es2uyd5CCRTNQlDGbzUQOW3aYCBx9rVA6dzsiY7Q==", - "dependencies": { - "object-assign": "^4.1.0", - "strict-uri-encode": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strict-uri-encode": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", - "integrity": "sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/superagent-debugger": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/superagent-debugger/-/superagent-debugger-1.2.9.tgz", - "integrity": "sha512-iH4NvJl1utorgRbrsYoOM8yoeTbS7YWLoDkAwRy2rgB6aP5Lr36XxmpE8GbgvmUY6R4QmYr+4R4IdAGMPmwR9g==", - "dependencies": { - "chalk": "^1.1.3", - "debug": "^2.6.0", - "lodash": "^4.17.4", - "moment": "^2.17.1", - "query-string": "^4.3.1" - } - }, - "node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", - "engines": { - "node": ">=0.8.0" - } - } - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==" - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==" - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "moment": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" - }, - "query-string": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", - "integrity": "sha512-O2XLNDBIg1DnTOa+2XrIwSiXEV8h2KImXUnjhhn2+UsvZ+Es2uyd5CCRTNQlDGbzUQOW3aYCBx9rVA6dzsiY7Q==", - "requires": { - "object-assign": "^4.1.0", - "strict-uri-encode": "^1.0.0" - } - }, - "strict-uri-encode": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", - "integrity": "sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==" - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "superagent-debugger": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/superagent-debugger/-/superagent-debugger-1.2.9.tgz", - "integrity": "sha512-iH4NvJl1utorgRbrsYoOM8yoeTbS7YWLoDkAwRy2rgB6aP5Lr36XxmpE8GbgvmUY6R4QmYr+4R4IdAGMPmwR9g==", - "requires": { - "chalk": "^1.1.3", - "debug": "^2.6.0", - "lodash": "^4.17.4", - "moment": "^2.17.1", - "query-string": "^4.3.1" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==" - } - } -} diff --git a/blueprints/apigee/bigquery-analytics/versions.tf b/blueprints/apigee/bigquery-analytics/versions.tf index 4900174a..08492c6f 100644 --- a/blueprints/apigee/bigquery-analytics/versions.tf +++ b/blueprints/apigee/bigquery-analytics/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/blueprints/apigee/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/versions.tf b/blueprints/apigee/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/versions.tf index 4900174a..08492c6f 100644 --- a/blueprints/apigee/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/versions.tf +++ b/blueprints/apigee/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/blueprints/cloud-operations/adfs/versions.tf b/blueprints/cloud-operations/adfs/versions.tf index 4900174a..08492c6f 100644 --- a/blueprints/cloud-operations/adfs/versions.tf +++ b/blueprints/cloud-operations/adfs/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/blueprints/cloud-operations/apigee/bundle-export.zip b/blueprints/cloud-operations/apigee/bundle-export.zip new file mode 100644 index 00000000..8b090adf Binary files /dev/null and b/blueprints/cloud-operations/apigee/bundle-export.zip differ diff --git a/blueprints/cloud-operations/apigee/bundle-gcs2bq.zip b/blueprints/cloud-operations/apigee/bundle-gcs2bq.zip new file mode 100644 index 00000000..19a03789 Binary files /dev/null and b/blueprints/cloud-operations/apigee/bundle-gcs2bq.zip differ diff --git a/blueprints/cloud-operations/asset-inventory-feed-remediation/versions.tf b/blueprints/cloud-operations/asset-inventory-feed-remediation/versions.tf index 4900174a..08492c6f 100644 --- a/blueprints/cloud-operations/asset-inventory-feed-remediation/versions.tf +++ b/blueprints/cloud-operations/asset-inventory-feed-remediation/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/blueprints/cloud-operations/dns-fine-grained-iam/versions.tf b/blueprints/cloud-operations/dns-fine-grained-iam/versions.tf index 4900174a..08492c6f 100644 --- a/blueprints/cloud-operations/dns-fine-grained-iam/versions.tf +++ b/blueprints/cloud-operations/dns-fine-grained-iam/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/blueprints/cloud-operations/dns-shared-vpc/versions.tf b/blueprints/cloud-operations/dns-shared-vpc/versions.tf index 4900174a..08492c6f 100644 --- a/blueprints/cloud-operations/dns-shared-vpc/versions.tf +++ b/blueprints/cloud-operations/dns-shared-vpc/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/blueprints/cloud-operations/iam-delegated-role-grants/versions.tf b/blueprints/cloud-operations/iam-delegated-role-grants/versions.tf index 4900174a..08492c6f 100644 --- a/blueprints/cloud-operations/iam-delegated-role-grants/versions.tf +++ b/blueprints/cloud-operations/iam-delegated-role-grants/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/blueprints/cloud-operations/onprem-sa-key-management/versions.tf b/blueprints/cloud-operations/onprem-sa-key-management/versions.tf index 4900174a..08492c6f 100644 --- a/blueprints/cloud-operations/onprem-sa-key-management/versions.tf +++ b/blueprints/cloud-operations/onprem-sa-key-management/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/blueprints/cloud-operations/packer-image-builder/versions.tf b/blueprints/cloud-operations/packer-image-builder/versions.tf index 4900174a..08492c6f 100644 --- a/blueprints/cloud-operations/packer-image-builder/versions.tf +++ b/blueprints/cloud-operations/packer-image-builder/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/blueprints/cloud-operations/quota-monitoring/versions.tf b/blueprints/cloud-operations/quota-monitoring/versions.tf index 4900174a..08492c6f 100644 --- a/blueprints/cloud-operations/quota-monitoring/versions.tf +++ b/blueprints/cloud-operations/quota-monitoring/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/blueprints/cloud-operations/scheduled-asset-inventory-export-bq/versions.tf b/blueprints/cloud-operations/scheduled-asset-inventory-export-bq/versions.tf index 4900174a..08492c6f 100644 --- a/blueprints/cloud-operations/scheduled-asset-inventory-export-bq/versions.tf +++ b/blueprints/cloud-operations/scheduled-asset-inventory-export-bq/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/blueprints/cloud-operations/terraform-enterprise-wif/tfc-workflow-using-wif/tfc-oidc/versions.tf b/blueprints/cloud-operations/terraform-enterprise-wif/tfc-workflow-using-wif/tfc-oidc/versions.tf index 4900174a..08492c6f 100644 --- a/blueprints/cloud-operations/terraform-enterprise-wif/tfc-workflow-using-wif/tfc-oidc/versions.tf +++ b/blueprints/cloud-operations/terraform-enterprise-wif/tfc-workflow-using-wif/tfc-oidc/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/blueprints/data-solutions/README.md b/blueprints/data-solutions/README.md index 4919f29a..3da4e7e9 100644 --- a/blueprints/data-solutions/README.md +++ b/blueprints/data-solutions/README.md @@ -52,6 +52,20 @@ running on a VPC with a private IP and a dedicated Service Account. A GCS bucket ### SQL Server Always On Availability Groups -This [blueprint](./data-platform-foundations/) implements SQL Server Always On Availability Groups using Fabric modules. It builds a two node cluster with a fileshare witness instance in an existing VPC and adds the necessary firewalling. The actual setup process (apart from Active Directory operations) has been scripted, so that least amount of manual works needs to performed. +This [blueprint](./sqlserver-alwayson/) implements SQL Server Always On Availability Groups using Fabric modules. It builds a two node cluster with a fileshare witness instance in an existing VPC and adds the necessary firewalling. The actual setup process (apart from Active Directory operations) has been scripted, so that least amount of manual works needs to performed. + +
+ +### MLOps with Vertex AI + + +This [blueprint](./vertex-mlops/) implements the infrastructure required to have a fully functional MLOPs environment using Vertex AI: required GCP services activation, Vertex Workbench, GCS buckets to host Vertex AI and Cloud Build artifacts, Artifact Registry docker repository to host custom images, required service accounts, networking and Workload Identity Federation Provider for Github integration (optional). + +
+ +### Shielded Folder + + +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.
diff --git a/blueprints/data-solutions/cmek-via-centralized-kms/README.md b/blueprints/data-solutions/cmek-via-centralized-kms/README.md index 88590c74..3813c90c 100644 --- a/blueprints/data-solutions/cmek-via-centralized-kms/README.md +++ b/blueprints/data-solutions/cmek-via-centralized-kms/README.md @@ -1,8 +1,8 @@ # GCE and GCS CMEK via centralized Cloud KMS -This example creates a sample centralized [Cloud KMS](https://cloud.google.com/kms?hl=it) configuration, and uses it to implement CMEK for [Cloud Storage](https://cloud.google.com/storage/docs/encryption/using-customer-managed-keys) and [Compute Engine](https://cloud.google.com/compute/docs/disks/customer-managed-encryption) in a separate project. +This example creates a sample centralized [Cloud KMS](https://cloud.google.com/kms?hl=it) configuration, and uses it to implement CMEK for [Cloud Storage](https://cloud.google.com/storage/docs/encryption/using-customer-managed-keys) and [Compute Engine](https://cloud.google.com/compute/docs/disks/customer-managed-encryption) in a service project. -The example is designed to match real-world use cases with a minimum amount of resources, and be used as a starting point for scenarios where application projects implement CMEK using keys managed by a central team. It also includes the IAM wiring needed to make such scenarios work. +The example is designed to match real-world use cases with a minimum amount of resources, and be used as a starting point for scenarios where application projects implement CMEK using keys managed by a central team. 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. This is the high level diagram: @@ -35,12 +35,10 @@ This sample creates several distinct groups of resources: | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [billing_account](variables.tf#L16) | Billing account id used as default for new projects. | string | ✓ | | -| [root_node](variables.tf#L45) | The resource name of the parent Folder or Organization. Must be of the form folders/folder_id or organizations/org_id. | string | ✓ | | -| [location](variables.tf#L21) | The location where resources will be deployed. | string | | "europe" | -| [project_kms_name](variables.tf#L27) | Name for the new KMS Project. | string | | "my-project-kms-001" | -| [project_service_name](variables.tf#L33) | Name for the new Service Project. | string | | "my-project-service-001" | -| [region](variables.tf#L39) | The region where resources will be deployed. | string | | "europe-west1" | +| [prefix](variables.tf#L21) | Optional prefix used to generate resources names. | string | ✓ | | +| [project_config](variables.tf#L27) | Provide 'billing_account_id' and 'parent' values if project creation is needed, uses existing 'projects_id' if null. Parent is in 'folders/nnn' or 'organizations/nnn' format. | object({…}) | ✓ | | +| [location](variables.tf#L15) | The location where resources will be deployed. | string | | "europe" | +| [region](variables.tf#L44) | The region where resources will be deployed. | string | | "europe-west1" | | [vpc_ip_cidr_range](variables.tf#L50) | Ip range used in the subnet deployef in the Service Project. | string | | "10.0.0.0/20" | | [vpc_name](variables.tf#L56) | Name of the VPC created in the Service Project. | string | | "local" | | [vpc_subnet_name](variables.tf#L62) | Name of the subnet created in the Service Project. | string | | "subnet" | diff --git a/blueprints/data-solutions/cmek-via-centralized-kms/main.tf b/blueprints/data-solutions/cmek-via-centralized-kms/main.tf index fb7f9fdd..73f2b701 100644 --- a/blueprints/data-solutions/cmek-via-centralized-kms/main.tf +++ b/blueprints/data-solutions/cmek-via-centralized-kms/main.tf @@ -12,33 +12,61 @@ # See the License for the specific language governing permissions and # limitations under the License. +locals { + # Needed when you create KMS keys and encrypted resources in the same terraform state but different projects. + kms_keys = { + gce = "projects/${module.project-kms.project_id}/locations/${var.region}/keyRings/${var.prefix}-${var.region}/cryptoKeys/key-gcs" + gcs = "projects/${module.project-kms.project_id}/locations/${var.region}/keyRings/${var.prefix}-${var.region}/cryptoKeys/key-gcs" + } +} + ############################################################################### # Projects # ############################################################################### module "project-service" { source = "../../../modules/project" - name = var.project_service_name - parent = var.root_node - billing_account = var.billing_account + name = var.project_config.project_ids.service + parent = var.project_config.parent + billing_account = var.project_config.billing_account_id + project_create = var.project_config.billing_account_id != null + prefix = var.project_config.billing_account_id == null ? null : var.prefix services = [ "compute.googleapis.com", "servicenetworking.googleapis.com", - "storage-component.googleapis.com" + "storage.googleapis.com", + "storage-component.googleapis.com", + ] + service_encryption_key_ids = { + compute = [ + local.kms_keys.gce + ] + storage = [ + local.kms_keys.gcs + ] + } + service_config = { + disable_on_destroy = false, disable_dependent_services = false + } + depends_on = [ + module.kms ] - oslogin = true } module "project-kms" { source = "../../../modules/project" - name = var.project_kms_name - parent = var.root_node - billing_account = var.billing_account + name = var.project_config.project_ids.encryption + parent = var.project_config.parent + billing_account = var.project_config.billing_account_id + project_create = var.project_config.billing_account_id != null + prefix = var.project_config.billing_account_id == null ? null : var.prefix services = [ "cloudkms.googleapis.com", "servicenetworking.googleapis.com" ] - oslogin = true + service_config = { + disable_on_destroy = false, disable_dependent_services = false + } } ############################################################################### @@ -48,11 +76,11 @@ module "project-kms" { module "vpc" { source = "../../../modules/net-vpc" project_id = module.project-service.project_id - name = var.vpc_name + name = "${var.prefix}-vpc" subnets = [ { - ip_cidr_range = var.vpc_ip_cidr_range - name = var.vpc_subnet_name + ip_cidr_range = "10.0.0.0/20" + name = "${var.prefix}-${var.region}" region = var.region } ] @@ -63,7 +91,7 @@ module "vpc-firewall" { project_id = module.project-service.project_id network = module.vpc.name default_rules_config = { - admin_ranges = [var.vpc_ip_cidr_range] + admin_ranges = ["10.0.0.0/20"] } } @@ -75,22 +103,10 @@ module "kms" { source = "../../../modules/kms" project_id = module.project-kms.project_id keyring = { - name = "my-keyring", - location = var.location + name = "${var.prefix}-${var.region}", + location = var.region } keys = { key-gce = null, key-gcs = null } - key_iam = { - key-gce = { - "roles/cloudkms.cryptoKeyEncrypterDecrypter" = [ - "serviceAccount:${module.project-service.service_accounts.robots.compute}", - ] - }, - key-gcs = { - "roles/cloudkms.cryptoKeyEncrypterDecrypter" = [ - "serviceAccount:${module.project-service.service_accounts.robots.storage}", - ] - } - } } ############################################################################### @@ -101,10 +117,10 @@ module "vm_example" { source = "../../../modules/compute-vm" project_id = module.project-service.project_id zone = "${var.region}-b" - name = "kms-vm" + name = "${var.prefix}-vm" network_interfaces = [{ network = module.vpc.self_link, - subnetwork = module.vpc.subnet_self_links["${var.region}/subnet"], + subnetwork = module.vpc.subnet_self_links["${var.region}/${var.prefix}-${var.region}"], nat = false, addresses = null }] @@ -127,7 +143,7 @@ module "vm_example" { encryption = { encrypt_boot = true disk_encryption_key_raw = null - kms_key_self_link = module.kms.key_ids.key-gce + kms_key_self_link = local.kms_keys.gce } } @@ -138,7 +154,9 @@ module "vm_example" { module "kms-gcs" { source = "../../../modules/gcs" project_id = module.project-service.project_id - prefix = "my-bucket-001" - name = "kms-gcs" - encryption_key = module.kms.keys.key-gcs.id + prefix = var.prefix + name = "${var.prefix}-bucket" + location = var.region + storage_class = "REGIONAL" + encryption_key = local.kms_keys.gcs } diff --git a/blueprints/data-solutions/cmek-via-centralized-kms/variables.tf b/blueprints/data-solutions/cmek-via-centralized-kms/variables.tf index 737bde3d..5d35351c 100644 --- a/blueprints/data-solutions/cmek-via-centralized-kms/variables.tf +++ b/blueprints/data-solutions/cmek-via-centralized-kms/variables.tf @@ -12,28 +12,33 @@ # See the License for the specific language governing permissions and # limitations under the License. - -variable "billing_account" { - description = "Billing account id used as default for new projects." - type = string -} - variable "location" { description = "The location where resources will be deployed." type = string default = "europe" } -variable "project_kms_name" { - description = "Name for the new KMS Project." +variable "prefix" { + description = "Optional prefix used to generate resources names." type = string - default = "my-project-kms-001" + nullable = false } -variable "project_service_name" { - description = "Name for the new Service Project." - type = string - default = "my-project-service-001" +variable "project_config" { + description = "Provide 'billing_account_id' and 'parent' values if project creation is needed, uses existing 'projects_id' if null. Parent is in 'folders/nnn' or 'organizations/nnn' format." + type = object({ + billing_account_id = optional(string, null) + parent = optional(string, null) + project_ids = optional(object({ + encryption = string + service = string + }), { + encryption = "encryption", + service = "service" + } + ) + }) + nullable = false } variable "region" { @@ -42,11 +47,6 @@ variable "region" { default = "europe-west1" } -variable "root_node" { - description = "The resource name of the parent Folder or Organization. Must be of the form folders/folder_id or organizations/org_id." - type = string -} - variable "vpc_ip_cidr_range" { description = "Ip range used in the subnet deployef in the Service Project." type = string diff --git a/blueprints/data-solutions/cmek-via-centralized-kms/versions.tf b/blueprints/data-solutions/cmek-via-centralized-kms/versions.tf index 4900174a..08492c6f 100644 --- a/blueprints/data-solutions/cmek-via-centralized-kms/versions.tf +++ b/blueprints/data-solutions/cmek-via-centralized-kms/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/blueprints/data-solutions/data-platform-foundations/03-composer.tf b/blueprints/data-solutions/data-platform-foundations/03-composer.tf index 33a21408..f806f0e5 100644 --- a/blueprints/data-solutions/data-platform-foundations/03-composer.tf +++ b/blueprints/data-solutions/data-platform-foundations/03-composer.tf @@ -40,6 +40,7 @@ locals { LOD_SA_DF = module.load-sa-df-0.email ORC_PRJ = module.orch-project.project_id ORC_GCS = module.orch-cs-0.url + ORC_GCS_TMP_DF = module.orch-cs-df-template.url TRF_PRJ = module.transf-project.project_id TRF_GCS_STAGING = module.transf-cs-df-0.url TRF_NET_VPC = local.transf_vpc diff --git a/blueprints/data-solutions/data-platform-foundations/03-orchestration.tf b/blueprints/data-solutions/data-platform-foundations/03-orchestration.tf index 8e2d0725..a202afdd 100644 --- a/blueprints/data-solutions/data-platform-foundations/03-orchestration.tf +++ b/blueprints/data-solutions/data-platform-foundations/03-orchestration.tf @@ -25,6 +25,11 @@ locals { ? var.network_config.network_self_link : module.orch-vpc.0.self_link ) + + # Note: This formatting is needed for output purposes since the fabric artifact registry + # module doesn't yet expose the docker usage path of a registry folder in the needed format. + orch_docker_path = format("%s-docker.pkg.dev/%s/%s", + var.region, module.orch-project.project_id, module.orch-artifact-reg.name) } module "orch-project" { @@ -44,6 +49,8 @@ module "orch-project" { "roles/iam.serviceAccountUser", "roles/storage.objectAdmin", "roles/storage.admin", + "roles/artifactregistry.admin", + "roles/serviceusage.serviceUsageConsumer", ] } iam = { @@ -65,7 +72,15 @@ module "orch-project" { ] "roles/storage.objectAdmin" = [ module.orch-sa-cmp-0.iam_email, + module.orch-sa-df-build.iam_email, "serviceAccount:${module.orch-project.service_accounts.robots.composer}", + "serviceAccount:${module.orch-project.service_accounts.robots.cloudbuild}", + ] + "roles/artifactregistry.reader" = [ + module.load-sa-df-0.iam_email, + ] + "roles/cloudbuild.serviceAgent" = [ + module.orch-sa-df-build.iam_email, ] "roles/storage.objectViewer" = [module.load-sa-df-0.iam_email] } @@ -81,6 +96,7 @@ module "orch-project" { "compute.googleapis.com", "container.googleapis.com", "containerregistry.googleapis.com", + "artifactregistry.googleapis.com", "dataflow.googleapis.com", "orgpolicy.googleapis.com", "pubsub.googleapis.com", @@ -148,3 +164,46 @@ module "orch-nat" { region = var.region router_network = module.orch-vpc.0.name } + +module "orch-artifact-reg" { + source = "../../../modules/artifact-registry" + project_id = module.orch-project.project_id + id = "${var.prefix}-app-images" + location = var.region + format = "DOCKER" + description = "Docker repository storing application images e.g. Dataflow, Cloud Run etc..." +} + +module "orch-cs-df-template" { + source = "../../../modules/gcs" + project_id = module.orch-project.project_id + prefix = var.prefix + name = "orc-cs-df-template" + location = var.region + storage_class = "REGIONAL" + encryption_key = try(local.service_encryption_keys.storage, null) +} + +module "orch-cs-build-staging" { + source = "../../../modules/gcs" + project_id = module.orch-project.project_id + prefix = var.prefix + name = "orc-cs-build-staging" + location = var.region + storage_class = "REGIONAL" + encryption_key = try(local.service_encryption_keys.storage, null) +} + +module "orch-sa-df-build" { + source = "../../../modules/iam-service-account" + project_id = module.orch-project.project_id + prefix = var.prefix + name = "orc-sa-df-build" + display_name = "Data platform Dataflow build service account" + # Note values below should pertain to the system / group / users who are able to + # invoke the build via this service account + iam = { + "roles/iam.serviceAccountTokenCreator" = [local.groups_iam.data-engineers] + "roles/iam.serviceAccountUser" = [local.groups_iam.data-engineers] + } +} diff --git a/blueprints/data-solutions/data-platform-foundations/IAM.md b/blueprints/data-solutions/data-platform-foundations/IAM.md index 5a1995da..dd898bd7 100644 --- a/blueprints/data-solutions/data-platform-foundations/IAM.md +++ b/blueprints/data-solutions/data-platform-foundations/IAM.md @@ -71,11 +71,13 @@ Legend: + additive, conditional. | members | roles | |---|---| -|gcp-data-engineers
group|[roles/bigquery.dataEditor](https://cloud.google.com/iam/docs/understanding-roles#bigquery.dataEditor)
[roles/bigquery.jobUser](https://cloud.google.com/iam/docs/understanding-roles#bigquery.jobUser)
[roles/cloudbuild.builds.editor](https://cloud.google.com/iam/docs/understanding-roles#cloudbuild.builds.editor)
[roles/composer.admin](https://cloud.google.com/iam/docs/understanding-roles#composer.admin)
[roles/composer.environmentAndStorageObjectAdmin](https://cloud.google.com/iam/docs/understanding-roles#composer.environmentAndStorageObjectAdmin)
[roles/iam.serviceAccountUser](https://cloud.google.com/iam/docs/understanding-roles#iam.serviceAccountUser)
[roles/iap.httpsResourceAccessor](https://cloud.google.com/iam/docs/understanding-roles#iap.httpsResourceAccessor)
[roles/storage.admin](https://cloud.google.com/iam/docs/understanding-roles#storage.admin)
[roles/storage.objectAdmin](https://cloud.google.com/iam/docs/understanding-roles#storage.objectAdmin) | +|gcp-data-engineers
group|[roles/artifactregistry.admin](https://cloud.google.com/iam/docs/understanding-roles#artifactregistry.admin)
[roles/bigquery.dataEditor](https://cloud.google.com/iam/docs/understanding-roles#bigquery.dataEditor)
[roles/bigquery.jobUser](https://cloud.google.com/iam/docs/understanding-roles#bigquery.jobUser)
[roles/cloudbuild.builds.editor](https://cloud.google.com/iam/docs/understanding-roles#cloudbuild.builds.editor)
[roles/composer.admin](https://cloud.google.com/iam/docs/understanding-roles#composer.admin)
[roles/composer.environmentAndStorageObjectAdmin](https://cloud.google.com/iam/docs/understanding-roles#composer.environmentAndStorageObjectAdmin)
[roles/iam.serviceAccountUser](https://cloud.google.com/iam/docs/understanding-roles#iam.serviceAccountUser)
[roles/iap.httpsResourceAccessor](https://cloud.google.com/iam/docs/understanding-roles#iap.httpsResourceAccessor)
[roles/serviceusage.serviceUsageConsumer](https://cloud.google.com/iam/docs/understanding-roles#serviceusage.serviceUsageConsumer)
[roles/storage.admin](https://cloud.google.com/iam/docs/understanding-roles#storage.admin)
[roles/storage.objectAdmin](https://cloud.google.com/iam/docs/understanding-roles#storage.objectAdmin) | |SERVICE_IDENTITY_cloudcomposer-accounts
serviceAccount|[roles/composer.ServiceAgentV2Ext](https://cloud.google.com/iam/docs/understanding-roles#composer.ServiceAgentV2Ext)
[roles/storage.objectAdmin](https://cloud.google.com/iam/docs/understanding-roles#storage.objectAdmin) | +|SERVICE_IDENTITY_gcp-sa-cloudbuild
serviceAccount|[roles/storage.objectAdmin](https://cloud.google.com/iam/docs/understanding-roles#storage.objectAdmin) | |SERVICE_IDENTITY_service-networking
serviceAccount|[roles/servicenetworking.serviceAgent](https://cloud.google.com/iam/docs/understanding-roles#servicenetworking.serviceAgent) +| -|load-df-0
serviceAccount|[roles/bigquery.dataEditor](https://cloud.google.com/iam/docs/understanding-roles#bigquery.dataEditor)
[roles/storage.objectViewer](https://cloud.google.com/iam/docs/understanding-roles#storage.objectViewer) | +|load-df-0
serviceAccount|[roles/artifactregistry.reader](https://cloud.google.com/iam/docs/understanding-roles#artifactregistry.reader)
[roles/bigquery.dataEditor](https://cloud.google.com/iam/docs/understanding-roles#bigquery.dataEditor)
[roles/storage.objectViewer](https://cloud.google.com/iam/docs/understanding-roles#storage.objectViewer) | |orc-cmp-0
serviceAccount|[roles/bigquery.jobUser](https://cloud.google.com/iam/docs/understanding-roles#bigquery.jobUser)
[roles/composer.worker](https://cloud.google.com/iam/docs/understanding-roles#composer.worker)
[roles/iam.serviceAccountUser](https://cloud.google.com/iam/docs/understanding-roles#iam.serviceAccountUser)
[roles/storage.objectAdmin](https://cloud.google.com/iam/docs/understanding-roles#storage.objectAdmin) | +|orc-sa-df-build
serviceAccount|[roles/cloudbuild.serviceAgent](https://cloud.google.com/iam/docs/understanding-roles#cloudbuild.serviceAgent)
[roles/storage.objectAdmin](https://cloud.google.com/iam/docs/understanding-roles#storage.objectAdmin) | |trf-df-0
serviceAccount|[roles/bigquery.dataEditor](https://cloud.google.com/iam/docs/understanding-roles#bigquery.dataEditor) | ## Project trf diff --git a/blueprints/data-solutions/data-platform-foundations/README.md b/blueprints/data-solutions/data-platform-foundations/README.md index b038cfe4..08b24b21 100644 --- a/blueprints/data-solutions/data-platform-foundations/README.md +++ b/blueprints/data-solutions/data-platform-foundations/README.md @@ -21,7 +21,7 @@ The approach adapts to different high-level requirements: - least privilege principle - rely on service account impersonation -The code in this blueprint doesn't address Organization-level configurations (Organization policy, VPC-SC, centralized logs). We expect those elements to be managed by automation stages external to this script like those in [FAST](../../../fast) and this blueprint deployed on top of them as one of the [stages](../../../fast/stages/03-data-platform/dev/README.md). +The code in this blueprint doesn't address Organization-level configurations (Organization policy, VPC-SC, centralized logs). We expect those elements to be managed by automation stages external to this script like those in [FAST](../../../fast) and this blueprint deployed on top of them as one of the [stages](../../../fast/stages/3-data-platform/dev/README.md). ### Project structure @@ -219,7 +219,7 @@ module "data-platform" { prefix = "myprefix" } -# tftest modules=39 resources=287 +# tftest modules=43 resources=297 ``` ## Customizations @@ -263,13 +263,14 @@ You can find examples in the `[demo](./demo)` folder. | name | description | sensitive | |---|---|:---:| -| [bigquery-datasets](outputs.tf#L17) | BigQuery datasets. | | -| [demo_commands](outputs.tf#L27) | Demo commands. Relevant only if Composer is deployed. | | -| [gcs-buckets](outputs.tf#L40) | GCS buckets. | | -| [kms_keys](outputs.tf#L53) | Cloud MKS keys. | | -| [projects](outputs.tf#L58) | GCP Projects informations. | | -| [vpc_network](outputs.tf#L84) | VPC network. | | -| [vpc_subnet](outputs.tf#L93) | VPC subnetworks. | | +| [bigquery-datasets](outputs.tf#L16) | BigQuery datasets. | | +| [demo_commands](outputs.tf#L26) | Demo commands. Relevant only if Composer is deployed. | | +| [df_template](outputs.tf#L49) | Dataflow template image and template details. | | +| [gcs-buckets](outputs.tf#L58) | GCS buckets. | | +| [kms_keys](outputs.tf#L71) | Cloud MKS keys. | | +| [projects](outputs.tf#L76) | GCP Projects informations. | | +| [vpc_network](outputs.tf#L102) | VPC network. | | +| [vpc_subnet](outputs.tf#L111) | VPC subnetworks. | | ## TODOs diff --git a/blueprints/data-solutions/data-platform-foundations/demo/README.md b/blueprints/data-solutions/data-platform-foundations/demo/README.md index 97add086..639549fc 100644 --- a/blueprints/data-solutions/data-platform-foundations/demo/README.md +++ b/blueprints/data-solutions/data-platform-foundations/demo/README.md @@ -23,10 +23,11 @@ Below you can find a description of each example: ## Running the demo To run demo examples, please follow the following steps: -- 01: copy sample data to the `drop off` Cloud Storage bucket impersonating the `load` service account. -- 02: copy sample data structure definition in the `orchestration` Cloud Storage bucket impersonating the `orchestration` service account. -- 03: copy the Cloud Composer DAG to the Cloud Composer Storage bucket impersonating the `orchestration` service account. -- 04: Open the Cloud Composer Airflow UI and run the imported DAG. -- 05: Run the BigQuery query to see results. +- 01: Copy sample data to the `drop off` Cloud Storage bucket impersonating the `load` service account. +- 02: Copy sample data structure definition in the `orchestration` Cloud Storage bucket impersonating the `orchestration` service account. +- 03: Copy the Cloud Composer DAG to the Cloud Composer Storage bucket impersonating the `orchestration` service account. +- 04: Build the Dataflow Flex template and image via a Cloud Build pipeline +- 05: Open the Cloud Composer Airflow UI and run the imported DAG. +- 06: Run the BigQuery query to see results. You can find pre-computed commands in the `demo_commands` output variable of the deployed terraform [data pipeline](../). diff --git a/blueprints/data-solutions/data-platform-foundations/demo/dataflow-csv2bq/.gitignore b/blueprints/data-solutions/data-platform-foundations/demo/dataflow-csv2bq/.gitignore new file mode 100644 index 00000000..68bc17f9 --- /dev/null +++ b/blueprints/data-solutions/data-platform-foundations/demo/dataflow-csv2bq/.gitignore @@ -0,0 +1,160 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ diff --git a/blueprints/data-solutions/data-platform-foundations/demo/dataflow-csv2bq/Dockerfile b/blueprints/data-solutions/data-platform-foundations/demo/dataflow-csv2bq/Dockerfile new file mode 100644 index 00000000..69c6d2ee --- /dev/null +++ b/blueprints/data-solutions/data-platform-foundations/demo/dataflow-csv2bq/Dockerfile @@ -0,0 +1,29 @@ +# 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. + +FROM gcr.io/dataflow-templates-base/python39-template-launcher-base + +ENV FLEX_TEMPLATE_PYTHON_REQUIREMENTS_FILE="/template/requirements.txt" +ENV FLEX_TEMPLATE_PYTHON_PY_FILE="/template/csv2bq.py" + +COPY ./src/ /template + +RUN apt-get update \ + && apt-get install -y libffi-dev git \ + && rm -rf /var/lib/apt/lists/* \ + && pip install --no-cache-dir --upgrade pip \ + && pip install --no-cache-dir -r $FLEX_TEMPLATE_PYTHON_REQUIREMENTS_FILE \ + && pip download --no-cache-dir --dest /tmp/dataflow-requirements-cache -r $FLEX_TEMPLATE_PYTHON_REQUIREMENTS_FILE + +ENV PIP_NO_DEPS=True diff --git a/blueprints/data-solutions/data-platform-foundations/demo/dataflow-csv2bq/README.md b/blueprints/data-solutions/data-platform-foundations/demo/dataflow-csv2bq/README.md new file mode 100644 index 00000000..44f178fa --- /dev/null +++ b/blueprints/data-solutions/data-platform-foundations/demo/dataflow-csv2bq/README.md @@ -0,0 +1,63 @@ +## Pipeline summary +This demo serves as a simple example of building and launching a Flex Template Dataflow pipeline. The code mainly focuses on reading a CSV file as input along with a JSON schema file as side input. The pipeline Parses both inputs and writes the data to the relevant BigQuery table while applying the schema passed from input. + +![Dataflow pipeline overview](../../images/df_demo_pipeline.png "Dataflow pipeline overview") + +## Example build run + +Below is an example for triggering the Dataflow flex template build pipeline defined in `cloudbuild.yaml`. The Terraform output provides an example as well filled with the parameters values based on the generated resources in the data platform. + +``` +GCP_PROJECT="[ORCHESTRATION-PROJECT]" +TEMPLATE_IMAGE="[REGION].pkg.dev/[ORCHESTRATION-PROJECT]/[REPOSITORY]/csv2bq:latest" +TEMPLATE_PATH="gs://[DATAFLOW-TEMPLATE-BUCKEt]/csv2bq.json" +STAGIN_PATH="gs://[ORCHESTRATION-STAGING-BUCKET]/build" +LOG_PATH="gs://[ORCHESTRATION-LOGS-BUCKET]/logs" +REGION="[REGION]" +BUILD_SERVICE_ACCOUNT=orc-sa-df-build@[SERVICE_PROJECT_ID].iam.gserviceaccount.com + +gcloud builds submit \ + --config=cloudbuild.yaml \ + --project=$GCP_PROJECT \ + --region=$REGION \ + --gcs-log-dir=$LOG_PATH \ + --gcs-source-staging-dir=$STAGIN_PATH \ + --substitutions=_TEMPLATE_IMAGE=$TEMPLATE_IMAGE,_TEMPLATE_PATH=$TEMPLATE_PATH,_DOCKER_DIR="." \ + --impersonate-service-account=$BUILD_SERVICE_ACCOUNT +``` + +**Note:** For the scope of the demo, the launch of this build is manual, but in production, this build would be launched via a configured cloud build trigger when new changes are merged into the code branch of the Dataflow template. + +## Example Dataflow pipeline launch in bash (from flex template) + +Below is an example of launching a dataflow pipeline manually, based on the built template. When launched manually, the Dataflow pipeline would be launched via the orchestration service account, which is what the Airflow DAG is also using in the scope of this demo. + +**Note:** In the data platform demo, the launch of this Dataflow pipeline is handled by the airflow operator (DataflowStartFlexTemplateOperator). + +``` +#!/bin/bash + +PROJECT_ID=[LOAD-PROJECT] +REGION=[REGION] +ORCH_SERVICE_ACCOUNT=orchestrator@[SERVICE_PROJECT_ID].iam.gserviceaccount.com +SUBNET=[SUBNET-NAME] + +PIPELINE_STAGIN_PATH="gs://[LOAD-STAGING-BUCKET]/build" +CSV_FILE=gs://[DROP-ZONE-BUCKET]/customers.csv +JSON_SCHEMA=gs://[ORCHESTRATION-BUCKET]/customers_schema.json +OUTPUT_TABLE=[DESTINATION-PROJ].[DESTINATION-DATASET].customers +TEMPLATE_PATH=gs://[ORCHESTRATION-DF-GCS]/csv2bq.json + + +gcloud dataflow flex-template run "csv2bq-`date +%Y%m%d-%H%M%S`" \ + --template-file-gcs-location $TEMPLATE_PATH \ + --parameters temp_location="$PIPELINE_STAGIN_PATH/tmp" \ + --parameters staging_location="$PIPELINE_STAGIN_PATH/stage" \ + --parameters csv_file=$CSV_FILE \ + --parameters json_schema=$JSON_SCHEMA\ + --parameters output_table=$OUTPUT_TABLE \ + --region $REGION \ + --project $PROJECT_ID \ + --subnetwork="regions/$REGION/subnetworks/$SUBNET" \ + --service-account-email=$ORCH_SERVICE_ACCOUNT +``` diff --git a/blueprints/data-solutions/data-platform-foundations/demo/dataflow-csv2bq/cloudbuild.yaml b/blueprints/data-solutions/data-platform-foundations/demo/dataflow-csv2bq/cloudbuild.yaml new file mode 100644 index 00000000..11354c2e --- /dev/null +++ b/blueprints/data-solutions/data-platform-foundations/demo/dataflow-csv2bq/cloudbuild.yaml @@ -0,0 +1,30 @@ +# 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. + +steps: +- name: gcr.io/cloud-builders/gcloud + id: "Build docker image" + args: ['builds', 'submit', '--tag', '$_TEMPLATE_IMAGE', '.'] + dir: '$_DOCKER_DIR' + waitFor: ['-'] +- name: gcr.io/cloud-builders/gcloud + id: "Build template" + args: ['dataflow', + 'flex-template', + 'build', + '$_TEMPLATE_PATH', + '--image=$_TEMPLATE_IMAGE', + '--sdk-language=PYTHON' + ] + waitFor: ['Build docker image'] diff --git a/blueprints/data-solutions/data-platform-foundations/demo/dataflow-csv2bq/src/csv2bq.py b/blueprints/data-solutions/data-platform-foundations/demo/dataflow-csv2bq/src/csv2bq.py new file mode 100644 index 00000000..0f8ad127 --- /dev/null +++ b/blueprints/data-solutions/data-platform-foundations/demo/dataflow-csv2bq/src/csv2bq.py @@ -0,0 +1,79 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import apache_beam as beam +from apache_beam.io import ReadFromText, Read, WriteToBigQuery, BigQueryDisposition +from apache_beam.options.pipeline_options import PipelineOptions, SetupOptions +from apache_beam.io.filesystems import FileSystems +import json +import argparse + + +class ParseRow(beam.DoFn): + """ + Splits a given csv row by a seperator, validates fields and returns a dict + structure compatible with the BigQuery transform + """ + + def process(self, element: str, table_fields: list, delimiter: str): + split_row = element.split(delimiter) + parsed_row = {} + + for i, field in enumerate(table_fields['BigQuery Schema']): + parsed_row[field['name']] = split_row[i] + + yield parsed_row + +def run(argv=None, save_main_session=True): + parser = argparse.ArgumentParser() + parser.add_argument('--csv_file', + type=str, + required=True, + help='Path to the CSV file') + parser.add_argument('--json_schema', + type=str, + required=True, + help='Path to the JSON schema') + parser.add_argument('--output_table', + type=str, + required=True, + help='BigQuery path for the output table') + + args, pipeline_args = parser.parse_known_args(argv) + pipeline_options = PipelineOptions(pipeline_args) + pipeline_options.view_as( + SetupOptions).save_main_session = save_main_session + + with beam.Pipeline(options=pipeline_options) as p: + + def get_table_schema(table_path, table_schema): + return {'fields': table_schema['BigQuery Schema']} + + csv_input = p | 'Read CSV' >> ReadFromText(args.csv_file) + schema_input = p | 'Load Schema' >> beam.Create( + json.loads(FileSystems.open(args.json_schema).read())) + + table_fields = beam.pvalue.AsDict(schema_input) + parsed = csv_input | 'Parse and validate rows' >> beam.ParDo( + ParseRow(), table_fields, ',') + + parsed | 'Write to BigQuery' >> WriteToBigQuery( + args.output_table, + schema=get_table_schema, + create_disposition=BigQueryDisposition.CREATE_IF_NEEDED, + write_disposition=BigQueryDisposition.WRITE_TRUNCATE, + schema_side_inputs=(table_fields, )) + +if __name__ == "__main__": + run() diff --git a/blueprints/data-solutions/data-platform-foundations/demo/dataflow-csv2bq/src/requirements.txt b/blueprints/data-solutions/data-platform-foundations/demo/dataflow-csv2bq/src/requirements.txt new file mode 100644 index 00000000..21c569a0 --- /dev/null +++ b/blueprints/data-solutions/data-platform-foundations/demo/dataflow-csv2bq/src/requirements.txt @@ -0,0 +1 @@ +apache-beam==2.44.0 diff --git a/blueprints/data-solutions/data-platform-foundations/demo/datapipeline_dc_tags_flex.py b/blueprints/data-solutions/data-platform-foundations/demo/datapipeline_dc_tags_flex.py new file mode 100644 index 00000000..f911e335 --- /dev/null +++ b/blueprints/data-solutions/data-platform-foundations/demo/datapipeline_dc_tags_flex.py @@ -0,0 +1,461 @@ +# 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. + +# -------------------------------------------------------------------------------- +# Load The Dependencies +# -------------------------------------------------------------------------------- + +import datetime +import json +import os +import time + +from airflow import models +from airflow.operators import dummy +from airflow.providers.google.cloud.operators.dataflow import DataflowStartFlexTemplateOperator +from airflow.providers.google.cloud.operators.bigquery import BigQueryInsertJobOperator, BigQueryUpsertTableOperator, BigQueryUpdateTableSchemaOperator +from airflow.utils.task_group import TaskGroup + +# -------------------------------------------------------------------------------- +# Set variables - Needed for the DEMO +# -------------------------------------------------------------------------------- +BQ_LOCATION = os.environ.get("BQ_LOCATION") +DATA_CAT_TAGS = json.loads(os.environ.get("DATA_CAT_TAGS")) +DWH_LAND_PRJ = os.environ.get("DWH_LAND_PRJ") +DWH_LAND_BQ_DATASET = os.environ.get("DWH_LAND_BQ_DATASET") +DWH_LAND_GCS = os.environ.get("DWH_LAND_GCS") +DWH_CURATED_PRJ = os.environ.get("DWH_CURATED_PRJ") +DWH_CURATED_BQ_DATASET = os.environ.get("DWH_CURATED_BQ_DATASET") +DWH_CURATED_GCS = os.environ.get("DWH_CURATED_GCS") +DWH_CONFIDENTIAL_PRJ = os.environ.get("DWH_CONFIDENTIAL_PRJ") +DWH_CONFIDENTIAL_BQ_DATASET = os.environ.get("DWH_CONFIDENTIAL_BQ_DATASET") +DWH_CONFIDENTIAL_GCS = os.environ.get("DWH_CONFIDENTIAL_GCS") +DWH_PLG_PRJ = os.environ.get("DWH_PLG_PRJ") +DWH_PLG_BQ_DATASET = os.environ.get("DWH_PLG_BQ_DATASET") +DWH_PLG_GCS = os.environ.get("DWH_PLG_GCS") +GCP_REGION = os.environ.get("GCP_REGION") +DRP_PRJ = os.environ.get("DRP_PRJ") +DRP_BQ = os.environ.get("DRP_BQ") +DRP_GCS = os.environ.get("DRP_GCS") +DRP_PS = os.environ.get("DRP_PS") +LOD_PRJ = os.environ.get("LOD_PRJ") +LOD_GCS_STAGING = os.environ.get("LOD_GCS_STAGING") +LOD_NET_VPC = os.environ.get("LOD_NET_VPC") +LOD_NET_SUBNET = os.environ.get("LOD_NET_SUBNET") +LOD_SA_DF = os.environ.get("LOD_SA_DF") +ORC_PRJ = os.environ.get("ORC_PRJ") +ORC_GCS = os.environ.get("ORC_GCS") +ORC_GCS_TMP_DF = os.environ.get("ORC_GCS_TMP_DF") +TRF_PRJ = os.environ.get("TRF_PRJ") +TRF_GCS_STAGING = os.environ.get("TRF_GCS_STAGING") +TRF_NET_VPC = os.environ.get("TRF_NET_VPC") +TRF_NET_SUBNET = os.environ.get("TRF_NET_SUBNET") +TRF_SA_DF = os.environ.get("TRF_SA_DF") +TRF_SA_BQ = os.environ.get("TRF_SA_BQ") +DF_KMS_KEY = os.environ.get("DF_KMS_KEY", "") +DF_REGION = os.environ.get("GCP_REGION") +DF_ZONE = os.environ.get("GCP_REGION") + "-b" + +# -------------------------------------------------------------------------------- +# Set default arguments +# -------------------------------------------------------------------------------- + +# If you are running Airflow in more than one time zone +# see https://airflow.apache.org/docs/apache-airflow/stable/timezone.html +# for best practices +yesterday = datetime.datetime.now() - datetime.timedelta(days=1) + +default_args = { + 'owner': 'airflow', + 'start_date': yesterday, + 'depends_on_past': False, + 'email': [''], + 'email_on_failure': False, + 'email_on_retry': False, + 'retries': 1, + 'retry_delay': datetime.timedelta(minutes=5), +} + +dataflow_environment = { + 'serviceAccountEmail': LOD_SA_DF, + 'workerZone': DF_ZONE, + 'stagingLocation': f'{LOD_GCS_STAGING}/staging', + 'tempLocation': f'{LOD_GCS_STAGING}/tmp', + 'subnetwork': LOD_NET_SUBNET, + 'kmsKeyName': DF_KMS_KEY, + 'ipConfiguration': 'WORKER_IP_PRIVATE' +} + +# -------------------------------------------------------------------------------- +# Main DAG +# -------------------------------------------------------------------------------- + +with models.DAG('data_pipeline_dc_tags_dag_flex', + default_args=default_args, + schedule_interval=None) as dag: + start = dummy.DummyOperator(task_id='start', trigger_rule='all_success') + + end = dummy.DummyOperator(task_id='end', trigger_rule='all_success') + + # Bigquery Tables created here for demo porpuse. + # Consider a dedicated pipeline or tool for a real life scenario. + with TaskGroup('upsert_table') as upsert_table: + upsert_table_customers = BigQueryUpsertTableOperator( + task_id="upsert_table_customers", + project_id=DWH_LAND_PRJ, + dataset_id=DWH_LAND_BQ_DATASET, + impersonation_chain=[TRF_SA_DF], + table_resource={ + "tableReference": { + "tableId": "customers" + }, + }, + ) + + upsert_table_purchases = BigQueryUpsertTableOperator( + task_id="upsert_table_purchases", + project_id=DWH_LAND_PRJ, + dataset_id=DWH_LAND_BQ_DATASET, + impersonation_chain=[TRF_SA_BQ], + table_resource={"tableReference": { + "tableId": "purchases" + }}, + ) + + upsert_table_customer_purchase_curated = BigQueryUpsertTableOperator( + task_id="upsert_table_customer_purchase_curated", + project_id=DWH_CURATED_PRJ, + dataset_id=DWH_CURATED_BQ_DATASET, + impersonation_chain=[TRF_SA_BQ], + table_resource={ + "tableReference": { + "tableId": "customer_purchase" + } + }, + ) + + upsert_table_customer_purchase_confidential = BigQueryUpsertTableOperator( + task_id="upsert_table_customer_purchase_confidential", + project_id=DWH_CONFIDENTIAL_PRJ, + dataset_id=DWH_CONFIDENTIAL_BQ_DATASET, + impersonation_chain=[TRF_SA_BQ], + table_resource={ + "tableReference": { + "tableId": "customer_purchase" + } + }, + ) + + # Bigquery Tables schema defined here for demo porpuse. + # Consider a dedicated pipeline or tool for a real life scenario. + with TaskGroup('update_schema_table') as update_schema_table: + update_table_schema_customers = BigQueryUpdateTableSchemaOperator( + task_id="update_table_schema_customers", + project_id=DWH_LAND_PRJ, + dataset_id=DWH_LAND_BQ_DATASET, + table_id="customers", + impersonation_chain=[TRF_SA_BQ], + include_policy_tags=True, + schema_fields_updates=[{ + "mode": "REQUIRED", + "name": "id", + "type": "INTEGER", + "description": "ID" + }, { + "mode": "REQUIRED", + "name": "name", + "type": "STRING", + "description": "Name", + "policyTags": { + "names": [DATA_CAT_TAGS.get('2_Private', None)] + } + }, { + "mode": "REQUIRED", + "name": "surname", + "type": "STRING", + "description": "Surname", + "policyTags": { + "names": [DATA_CAT_TAGS.get('2_Private', None)] + } + }, { + "mode": "REQUIRED", + "name": "timestamp", + "type": "TIMESTAMP", + "description": "Timestamp" + }]) + + update_table_schema_purchases = BigQueryUpdateTableSchemaOperator( + task_id="update_table_schema_purchases", + project_id=DWH_LAND_PRJ, + dataset_id=DWH_LAND_BQ_DATASET, + table_id="purchases", + impersonation_chain=[TRF_SA_BQ], + include_policy_tags=True, + schema_fields_updates=[{ + "mode": "REQUIRED", + "name": "id", + "type": "INTEGER", + "description": "ID" + }, { + "mode": "REQUIRED", + "name": "customer_id", + "type": "INTEGER", + "description": "ID" + }, { + "mode": "REQUIRED", + "name": "item", + "type": "STRING", + "description": "Item Name" + }, { + "mode": "REQUIRED", + "name": "price", + "type": "FLOAT", + "description": "Item Price" + }, { + "mode": "REQUIRED", + "name": "timestamp", + "type": "TIMESTAMP", + "description": "Timestamp" + }]) + + update_table_schema_customer_purchase_curated = BigQueryUpdateTableSchemaOperator( + task_id="update_table_schema_customer_purchase_curated", + project_id=DWH_CURATED_PRJ, + dataset_id=DWH_CURATED_BQ_DATASET, + table_id="customer_purchase", + impersonation_chain=[TRF_SA_BQ], + include_policy_tags=True, + schema_fields_updates=[{ + "mode": "REQUIRED", + "name": "customer_id", + "type": "INTEGER", + "description": "ID" + }, { + "mode": "REQUIRED", + "name": "purchase_id", + "type": "INTEGER", + "description": "ID" + }, { + "mode": "REQUIRED", + "name": "name", + "type": "STRING", + "description": "Name", + "policyTags": { + "names": [DATA_CAT_TAGS.get('2_Private', None)] + } + }, { + "mode": "REQUIRED", + "name": "surname", + "type": "STRING", + "description": "Surname", + "policyTags": { + "names": [DATA_CAT_TAGS.get('2_Private', None)] + } + }, { + "mode": "REQUIRED", + "name": "item", + "type": "STRING", + "description": "Item Name" + }, { + "mode": "REQUIRED", + "name": "price", + "type": "FLOAT", + "description": "Item Price" + }, { + "mode": "REQUIRED", + "name": "timestamp", + "type": "TIMESTAMP", + "description": "Timestamp" + }]) + + update_table_schema_customer_purchase_confidential = BigQueryUpdateTableSchemaOperator( + task_id="update_table_schema_customer_purchase_confidential", + project_id=DWH_CONFIDENTIAL_PRJ, + dataset_id=DWH_CONFIDENTIAL_BQ_DATASET, + table_id="customer_purchase", + impersonation_chain=[TRF_SA_BQ], + include_policy_tags=True, + schema_fields_updates=[{ + "mode": "REQUIRED", + "name": "customer_id", + "type": "INTEGER", + "description": "ID" + }, { + "mode": "REQUIRED", + "name": "purchase_id", + "type": "INTEGER", + "description": "ID" + }, { + "mode": "REQUIRED", + "name": "name", + "type": "STRING", + "description": "Name", + "policyTags": { + "names": [DATA_CAT_TAGS.get('2_Private', None)] + } + }, { + "mode": "REQUIRED", + "name": "surname", + "type": "STRING", + "description": "Surname", + "policyTags": { + "names": [DATA_CAT_TAGS.get('2_Private', None)] + } + }, { + "mode": "REQUIRED", + "name": "item", + "type": "STRING", + "description": "Item Name" + }, { + "mode": "REQUIRED", + "name": "price", + "type": "FLOAT", + "description": "Item Price" + }, { + "mode": "REQUIRED", + "name": "timestamp", + "type": "TIMESTAMP", + "description": "Timestamp" + }]) + + customers_import = DataflowStartFlexTemplateOperator( + task_id='dataflow_customers_import', + project_id=LOD_PRJ, + location=DF_REGION, + body={ + 'launchParameter': { + 'jobName': f'dataflow-customers-import-{round(time.time())}', + 'containerSpecGcsPath': f'{ORC_GCS_TMP_DF}/csv2bq.json', + 'environment': { + 'serviceAccountEmail': LOD_SA_DF, + 'workerZone': DF_ZONE, + 'stagingLocation': f'{LOD_GCS_STAGING}/staging', + 'tempLocation': f'{LOD_GCS_STAGING}/tmp', + 'subnetwork': LOD_NET_SUBNET, + 'kmsKeyName': DF_KMS_KEY, + 'ipConfiguration': 'WORKER_IP_PRIVATE' + }, + 'parameters': { + 'csv_file': + f'{DRP_GCS}/customers.csv', + 'json_schema': + f'{ORC_GCS}/customers_schema.json', + 'output_table': + f'{DWH_LAND_PRJ}:{DWH_LAND_BQ_DATASET}.customers', + } + } + }) + + purchases_import = DataflowStartFlexTemplateOperator( + task_id='dataflow_purchases_import', + project_id=LOD_PRJ, + location=DF_REGION, + body={ + 'launchParameter': { + 'jobName': f'dataflow-purchases-import-{round(time.time())}', + 'containerSpecGcsPath': f'{ORC_GCS_TMP_DF}/csv2bq.json', + 'environment': { + 'serviceAccountEmail': LOD_SA_DF, + 'workerZone': DF_ZONE, + 'stagingLocation': f'{LOD_GCS_STAGING}/staging', + 'tempLocation': f'{LOD_GCS_STAGING}/tmp', + 'subnetwork': LOD_NET_SUBNET, + 'kmsKeyName': DF_KMS_KEY, + 'ipConfiguration': 'WORKER_IP_PRIVATE' + }, + 'parameters': { + 'csv_file': + f'{DRP_GCS}/purchases.csv', + 'json_schema': + f'{ORC_GCS}/purchases_schema.json', + 'output_table': + f'{DWH_LAND_PRJ}:{DWH_LAND_BQ_DATASET}.purchases', + } + } + }) + + join_customer_purchase = BigQueryInsertJobOperator( + task_id='bq_join_customer_purchase', + gcp_conn_id='bigquery_default', + project_id=TRF_PRJ, + location=BQ_LOCATION, + configuration={ + 'jobType': 'QUERY', + 'query': { + 'query': + """SELECT + c.id as customer_id, + p.id as purchase_id, + c.name as name, + c.surname as surname, + p.item as item, + p.price as price, + p.timestamp as timestamp + FROM `{dwh_0_prj}.{dwh_0_dataset}.customers` c + JOIN `{dwh_0_prj}.{dwh_0_dataset}.purchases` p ON c.id = p.customer_id + """.format( + dwh_0_prj=DWH_LAND_PRJ, + dwh_0_dataset=DWH_LAND_BQ_DATASET, + ), + 'destinationTable': { + 'projectId': DWH_CURATED_PRJ, + 'datasetId': DWH_CURATED_BQ_DATASET, + 'tableId': 'customer_purchase' + }, + 'writeDisposition': + 'WRITE_TRUNCATE', + "useLegacySql": + False + } + }, + impersonation_chain=[TRF_SA_BQ]) + + confidential_customer_purchase = BigQueryInsertJobOperator( + task_id='bq_confidential_customer_purchase', + gcp_conn_id='bigquery_default', + project_id=TRF_PRJ, + location=BQ_LOCATION, + configuration={ + 'jobType': 'QUERY', + 'query': { + 'query': + """SELECT + customer_id, + purchase_id, + name, + surname, + item, + price, + timestamp + FROM `{dwh_cur_prj}.{dwh_cur_dataset}.customer_purchase` + """.format( + dwh_cur_prj=DWH_CURATED_PRJ, + dwh_cur_dataset=DWH_CURATED_BQ_DATASET, + ), + 'destinationTable': { + 'projectId': DWH_CONFIDENTIAL_PRJ, + 'datasetId': DWH_CONFIDENTIAL_BQ_DATASET, + 'tableId': 'customer_purchase' + }, + 'writeDisposition': + 'WRITE_TRUNCATE', + "useLegacySql": + False + } + }, + impersonation_chain=[TRF_SA_BQ]) + +start >> upsert_table >> update_schema_table >> [ + customers_import, purchases_import +] >> join_customer_purchase >> confidential_customer_purchase >> end diff --git a/blueprints/data-solutions/data-platform-foundations/demo/datapipeline_flex.py b/blueprints/data-solutions/data-platform-foundations/demo/datapipeline_flex.py new file mode 100644 index 00000000..34ff10cc --- /dev/null +++ b/blueprints/data-solutions/data-platform-foundations/demo/datapipeline_flex.py @@ -0,0 +1,225 @@ +# 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. + +# -------------------------------------------------------------------------------- +# Load The Dependencies +# -------------------------------------------------------------------------------- + +import datetime +import json +import os +import time + +from airflow import models +from airflow.providers.google.cloud.operators.dataflow import DataflowStartFlexTemplateOperator +from airflow.operators import dummy +from airflow.providers.google.cloud.operators.bigquery import BigQueryInsertJobOperator + +# -------------------------------------------------------------------------------- +# Set variables - Needed for the DEMO +# -------------------------------------------------------------------------------- +BQ_LOCATION = os.environ.get("BQ_LOCATION") +DATA_CAT_TAGS = json.loads(os.environ.get("DATA_CAT_TAGS")) +DWH_LAND_PRJ = os.environ.get("DWH_LAND_PRJ") +DWH_LAND_BQ_DATASET = os.environ.get("DWH_LAND_BQ_DATASET") +DWH_LAND_GCS = os.environ.get("DWH_LAND_GCS") +DWH_CURATED_PRJ = os.environ.get("DWH_CURATED_PRJ") +DWH_CURATED_BQ_DATASET = os.environ.get("DWH_CURATED_BQ_DATASET") +DWH_CURATED_GCS = os.environ.get("DWH_CURATED_GCS") +DWH_CONFIDENTIAL_PRJ = os.environ.get("DWH_CONFIDENTIAL_PRJ") +DWH_CONFIDENTIAL_BQ_DATASET = os.environ.get("DWH_CONFIDENTIAL_BQ_DATASET") +DWH_CONFIDENTIAL_GCS = os.environ.get("DWH_CONFIDENTIAL_GCS") +DWH_PLG_PRJ = os.environ.get("DWH_PLG_PRJ") +DWH_PLG_BQ_DATASET = os.environ.get("DWH_PLG_BQ_DATASET") +DWH_PLG_GCS = os.environ.get("DWH_PLG_GCS") +GCP_REGION = os.environ.get("GCP_REGION") +DRP_PRJ = os.environ.get("DRP_PRJ") +DRP_BQ = os.environ.get("DRP_BQ") +DRP_GCS = os.environ.get("DRP_GCS") +DRP_PS = os.environ.get("DRP_PS") +LOD_PRJ = os.environ.get("LOD_PRJ") +LOD_GCS_STAGING = os.environ.get("LOD_GCS_STAGING") +LOD_NET_VPC = os.environ.get("LOD_NET_VPC") +LOD_NET_SUBNET = os.environ.get("LOD_NET_SUBNET") +LOD_SA_DF = os.environ.get("LOD_SA_DF") +ORC_PRJ = os.environ.get("ORC_PRJ") +ORC_GCS = os.environ.get("ORC_GCS") +ORC_GCS_TMP_DF = os.environ.get("ORC_GCS_TMP_DF") +TRF_PRJ = os.environ.get("TRF_PRJ") +TRF_GCS_STAGING = os.environ.get("TRF_GCS_STAGING") +TRF_NET_VPC = os.environ.get("TRF_NET_VPC") +TRF_NET_SUBNET = os.environ.get("TRF_NET_SUBNET") +TRF_SA_DF = os.environ.get("TRF_SA_DF") +TRF_SA_BQ = os.environ.get("TRF_SA_BQ") +DF_KMS_KEY = os.environ.get("DF_KMS_KEY", "") +DF_REGION = os.environ.get("GCP_REGION") +DF_ZONE = os.environ.get("GCP_REGION") + "-b" + +# -------------------------------------------------------------------------------- +# Set default arguments +# -------------------------------------------------------------------------------- + +# If you are running Airflow in more than one time zone +# see https://airflow.apache.org/docs/apache-airflow/stable/timezone.html +# for best practices +yesterday = datetime.datetime.now() - datetime.timedelta(days=1) + +default_args = { + 'owner': 'airflow', + 'start_date': yesterday, + 'depends_on_past': False, + 'email': [''], + 'email_on_failure': False, + 'email_on_retry': False, + 'retries': 1, + 'retry_delay': datetime.timedelta(minutes=5), +} + +dataflow_environment = { + 'serviceAccountEmail': LOD_SA_DF, + 'workerZone': DF_ZONE, + 'stagingLocation': f'{LOD_GCS_STAGING}/staging', + 'tempLocation': f'{LOD_GCS_STAGING}/tmp', + 'subnetwork': LOD_NET_SUBNET, + 'kmsKeyName': DF_KMS_KEY, + 'ipConfiguration': 'WORKER_IP_PRIVATE' +} + +# -------------------------------------------------------------------------------- +# Main DAG +# -------------------------------------------------------------------------------- + +with models.DAG('data_pipeline_dag_flex', + default_args=default_args, + schedule_interval=None) as dag: + + start = dummy.DummyOperator(task_id='start', trigger_rule='all_success') + + end = dummy.DummyOperator(task_id='end', trigger_rule='all_success') + + # Bigquery Tables automatically created for demo purposes. + # Consider a dedicated pipeline or tool for a real life scenario. + customers_import = DataflowStartFlexTemplateOperator( + task_id='dataflow_customers_import', + project_id=LOD_PRJ, + location=DF_REGION, + body={ + 'launchParameter': { + 'jobName': f'dataflow-customers-import-{round(time.time())}', + 'containerSpecGcsPath': f'{ORC_GCS_TMP_DF}/csv2bq.json', + 'environment': dataflow_environment, + 'parameters': { + 'csv_file': + f'{DRP_GCS}/customers.csv', + 'json_schema': + f'{ORC_GCS}/customers_schema.json', + 'output_table': + f'{DWH_LAND_PRJ}:{DWH_LAND_BQ_DATASET}.customers', + } + } + }) + + purchases_import = DataflowStartFlexTemplateOperator( + task_id='dataflow_purchases_import', + project_id=LOD_PRJ, + location=DF_REGION, + body={ + 'launchParameter': { + 'jobName': f'dataflow-purchases-import-{round(time.time())}', + 'containerSpecGcsPath': f'{ORC_GCS_TMP_DF}/csv2bq.json', + 'environment': dataflow_environment, + 'parameters': { + 'csv_file': + f'{DRP_GCS}/purchases.csv', + 'json_schema': + f'{ORC_GCS}/purchases_schema.json', + 'output_table': + f'{DWH_LAND_PRJ}:{DWH_LAND_BQ_DATASET}.purchases', + } + } + }) + + join_customer_purchase = BigQueryInsertJobOperator( + task_id='bq_join_customer_purchase', + gcp_conn_id='bigquery_default', + project_id=TRF_PRJ, + location=BQ_LOCATION, + configuration={ + 'jobType': 'QUERY', + 'query': { + 'query': + """SELECT + c.id as customer_id, + p.id as purchase_id, + p.item as item, + p.price as price, + p.timestamp as timestamp + FROM `{dwh_0_prj}.{dwh_0_dataset}.customers` c + JOIN `{dwh_0_prj}.{dwh_0_dataset}.purchases` p ON c.id = p.customer_id + """.format( + dwh_0_prj=DWH_LAND_PRJ, + dwh_0_dataset=DWH_LAND_BQ_DATASET, + ), + 'destinationTable': { + 'projectId': DWH_CURATED_PRJ, + 'datasetId': DWH_CURATED_BQ_DATASET, + 'tableId': 'customer_purchase' + }, + 'writeDisposition': + 'WRITE_TRUNCATE', + "useLegacySql": + False + } + }, + impersonation_chain=[TRF_SA_BQ]) + + confidential_customer_purchase = BigQueryInsertJobOperator( + task_id='bq_confidential_customer_purchase', + gcp_conn_id='bigquery_default', + project_id=TRF_PRJ, + location=BQ_LOCATION, + configuration={ + 'jobType': 'QUERY', + 'query': { + 'query': + """SELECT + c.id as customer_id, + p.id as purchase_id, + c.name as name, + c.surname as surname, + p.item as item, + p.price as price, + p.timestamp as timestamp + FROM `{dwh_0_prj}.{dwh_0_dataset}.customers` c + JOIN `{dwh_0_prj}.{dwh_0_dataset}.purchases` p ON c.id = p.customer_id + """.format( + dwh_0_prj=DWH_LAND_PRJ, + dwh_0_dataset=DWH_LAND_BQ_DATASET, + ), + 'destinationTable': { + 'projectId': DWH_CONFIDENTIAL_PRJ, + 'datasetId': DWH_CONFIDENTIAL_BQ_DATASET, + 'tableId': 'customer_purchase' + }, + 'writeDisposition': + 'WRITE_TRUNCATE', + "useLegacySql": + False + } + }, + impersonation_chain=[TRF_SA_BQ]) + + start >> [ + customers_import, purchases_import + ] >> join_customer_purchase >> confidential_customer_purchase >> end diff --git a/blueprints/data-solutions/data-platform-foundations/images/df_demo_pipeline.png b/blueprints/data-solutions/data-platform-foundations/images/df_demo_pipeline.png new file mode 100644 index 00000000..541532b4 Binary files /dev/null and b/blueprints/data-solutions/data-platform-foundations/images/df_demo_pipeline.png differ diff --git a/blueprints/data-solutions/data-platform-foundations/outputs.tf b/blueprints/data-solutions/data-platform-foundations/outputs.tf index 2394fe09..ae853da0 100644 --- a/blueprints/data-solutions/data-platform-foundations/outputs.tf +++ b/blueprints/data-solutions/data-platform-foundations/outputs.tf @@ -13,7 +13,6 @@ # limitations under the License. # tfdoc:file:description Output variables. - output "bigquery-datasets" { description = "BigQuery datasets." value = { @@ -30,13 +29,32 @@ output "demo_commands" { 01 = "gsutil -i ${module.drop-sa-cs-0.email} cp demo/data/*.csv gs://${module.drop-cs-0.name}" 02 = try("gsutil -i ${module.orch-sa-cmp-0.email} cp demo/data/*.j* gs://${module.orch-cs-0.name}", "Composer not deployed.") 03 = try("gsutil -i ${module.orch-sa-cmp-0.email} cp demo/*.py ${google_composer_environment.orch-cmp-0[0].config[0].dag_gcs_prefix}/", "Composer not deployed") - 04 = try("Open ${google_composer_environment.orch-cmp-0[0].config.0.airflow_uri} and run uploaded DAG.", "Composer not deployed") - 05 = < + +## Variables + +| name | description | type | required | default | +|---|---|:---:|:---:|:---:| +| [access_policy_config](variables.tf#L17) | Provide 'access_policy_create' values if a folder scoped Access Policy creation is needed, uses existing 'policy_name' otherwise. Parent is in 'organizations/123456' format. Policy will be created scoped to the folder. | object({…}) | ✓ | | +| [folder_config](variables.tf#L49) | Provide 'folder_create' values if folder creation is needed, uses existing 'folder_id' otherwise. Parent is in 'folders/nnn' or 'organizations/nnn' format. | object({…}) | ✓ | | +| [organization](variables.tf#L128) | Organization details. | object({…}) | ✓ | | +| [prefix](variables.tf#L136) | Prefix used for resources that need unique names. | string | ✓ | | +| [project_config](variables.tf#L141) | Provide 'billing_account_id' value if project creation is needed, uses existing 'project_ids' if null. Parent is in 'folders/nnn' or 'organizations/nnn' format. | object({…}) | ✓ | | +| [data_dir](variables.tf#L29) | Relative path for the folder storing configuration data. | string | | "data" | +| [enable_features](variables.tf#L35) | Flag to enable features on the solution. | object({…}) | | {…} | +| [groups](variables.tf#L65) | User groups. | object({…}) | | {} | +| [kms_keys](variables.tf#L75) | KMS keys to create, keyed by name. | map(object({…})) | | {} | +| [log_locations](variables.tf#L86) | Optional locations for GCS, BigQuery, and logging buckets created here. | object({…}) | | {…} | +| [log_sinks](variables.tf#L103) | Org-level log sinks, in name => {type, filter} format. | map(object({…})) | | {…} | +| [vpc_sc_access_levels](variables.tf#L161) | VPC SC access level definitions. | map(object({…})) | | {} | +| [vpc_sc_egress_policies](variables.tf#L190) | VPC SC egress policy defnitions. | map(object({…})) | | {} | +| [vpc_sc_ingress_policies](variables.tf#L210) | VPC SC ingress policy defnitions. | map(object({…})) | | {} | + +## Outputs + +| name | description | sensitive | +|---|---|:---:| +| [folders](outputs.tf#L15) | Folders id. | | +| [folders_sink_writer_identities](outputs.tf#L23) | Folders id. | | + + diff --git a/blueprints/data-solutions/shielded-folder/data/firewall-policies/cidrs.yaml b/blueprints/data-solutions/shielded-folder/data/firewall-policies/cidrs.yaml new file mode 100644 index 00000000..90dabfb6 --- /dev/null +++ b/blueprints/data-solutions/shielded-folder/data/firewall-policies/cidrs.yaml @@ -0,0 +1,15 @@ +# skip boilerplate check + +healthchecks: + - 35.191.0.0/16 + - 130.211.0.0/22 + - 209.85.152.0/22 + - 209.85.204.0/22 + +rfc1918: + - 10.0.0.0/8 + - 172.16.0.0/12 + - 192.168.0.0/16 + +onprem_probes: + - 10.255.255.254/32 \ No newline at end of file diff --git a/blueprints/data-solutions/shielded-folder/data/firewall-policies/hierarchical-policy-rules.yaml b/blueprints/data-solutions/shielded-folder/data/firewall-policies/hierarchical-policy-rules.yaml new file mode 100644 index 00000000..6a3b3133 --- /dev/null +++ b/blueprints/data-solutions/shielded-folder/data/firewall-policies/hierarchical-policy-rules.yaml @@ -0,0 +1,50 @@ +# skip boilerplate check + +allow-admins: + description: Access from the admin subnet to all subnets + direction: INGRESS + action: allow + priority: 1000 + ranges: + - $rfc1918 + ports: + all: [] + target_resources: null + enable_logging: false + +allow-healthchecks: + description: Enable HTTP and HTTPS healthchecks + direction: INGRESS + action: allow + priority: 1001 + ranges: + - $healthchecks + ports: + tcp: ["80", "443"] + target_resources: null + enable_logging: false + +allow-ssh-from-iap: + description: Enable SSH from IAP + direction: INGRESS + action: allow + priority: 1002 + ranges: + - 35.235.240.0/20 + ports: + tcp: ["22"] + target_resources: null + enable_logging: false + +allow-icmp: + description: Enable ICMP + direction: INGRESS + action: allow + priority: 1003 + ranges: + - 0.0.0.0/0 + ports: + icmp: [] + target_resources: null + enable_logging: false + \ No newline at end of file diff --git a/fast/stages/01-resman/data/org-policies/compute.yaml b/blueprints/data-solutions/shielded-folder/data/org-policies/compute.yaml similarity index 100% rename from fast/stages/01-resman/data/org-policies/compute.yaml rename to blueprints/data-solutions/shielded-folder/data/org-policies/compute.yaml diff --git a/fast/stages/01-resman/data/org-policies/iam.yaml b/blueprints/data-solutions/shielded-folder/data/org-policies/iam.yaml similarity index 100% rename from fast/stages/01-resman/data/org-policies/iam.yaml rename to blueprints/data-solutions/shielded-folder/data/org-policies/iam.yaml diff --git a/fast/stages/01-resman/data/org-policies/serverless.yaml b/blueprints/data-solutions/shielded-folder/data/org-policies/serverless.yaml similarity index 100% rename from fast/stages/01-resman/data/org-policies/serverless.yaml rename to blueprints/data-solutions/shielded-folder/data/org-policies/serverless.yaml diff --git a/fast/stages/01-resman/data/org-policies/sql.yaml b/blueprints/data-solutions/shielded-folder/data/org-policies/sql.yaml similarity index 100% rename from fast/stages/01-resman/data/org-policies/sql.yaml rename to blueprints/data-solutions/shielded-folder/data/org-policies/sql.yaml diff --git a/fast/stages/01-resman/data/org-policies/storage.yaml b/blueprints/data-solutions/shielded-folder/data/org-policies/storage.yaml similarity index 100% rename from fast/stages/01-resman/data/org-policies/storage.yaml rename to blueprints/data-solutions/shielded-folder/data/org-policies/storage.yaml diff --git a/blueprints/data-solutions/shielded-folder/data/vpc-sc/accessible-services.yaml b/blueprints/data-solutions/shielded-folder/data/vpc-sc/accessible-services.yaml new file mode 100644 index 00000000..2107d2ff --- /dev/null +++ b/blueprints/data-solutions/shielded-folder/data/vpc-sc/accessible-services.yaml @@ -0,0 +1,119 @@ +# skip boilerplate check + +- accessapproval.googleapis.com +- adsdatahub.googleapis.com +- aiplatform.googleapis.com +- alloydb.googleapis.com +- alpha-documentai.googleapis.com +- analyticshub.googleapis.com +- apigee.googleapis.com +- apigeeconnect.googleapis.com +- artifactregistry.googleapis.com +- assuredworkloads.googleapis.com +- automl.googleapis.com +- baremetalsolution.googleapis.com +- batch.googleapis.com +- beyondcorp.googleapis.com +- bigquery.googleapis.com +- bigquerydatapolicy.googleapis.com +- bigquerydatatransfer.googleapis.com +- bigquerymigration.googleapis.com +- bigqueryreservation.googleapis.com +- bigtable.googleapis.com +- binaryauthorization.googleapis.com +- cloudasset.googleapis.com +- cloudbuild.googleapis.com +- clouddebugger.googleapis.com +- clouderrorreporting.googleapis.com +- cloudfunctions.googleapis.com +- cloudkms.googleapis.com +- cloudprofiler.googleapis.com +- cloudresourcemanager.googleapis.com +- cloudsearch.googleapis.com +- cloudtrace.googleapis.com +- composer.googleapis.com +- compute.googleapis.com +- connectgateway.googleapis.com +- contactcenterinsights.googleapis.com +- container.googleapis.com +- containeranalysis.googleapis.com +- containerfilesystem.googleapis.com +- containerregistry.googleapis.com +- containerthreatdetection.googleapis.com +- contentwarehouse.googleapis.com +- datacatalog.googleapis.com +- dataflow.googleapis.com +- datafusion.googleapis.com +- datalineage.googleapis.com +- datamigration.googleapis.com +- datapipelines.googleapis.com +- dataplex.googleapis.com +- dataproc.googleapis.com +- datastream.googleapis.com +- dialogflow.googleapis.com +- dlp.googleapis.com +- dns.googleapis.com +- documentai.googleapis.com +- domains.googleapis.com +- essentialcontacts.googleapis.com +- eventarc.googleapis.com +- file.googleapis.com +- firebaseappcheck.googleapis.com +- firebaserules.googleapis.com +- firestore.googleapis.com +- gameservices.googleapis.com +- gkebackup.googleapis.com +- gkeconnect.googleapis.com +- gkehub.googleapis.com +- gkemulticloud.googleapis.com +- healthcare.googleapis.com +- iam.googleapis.com +- iamcredentials.googleapis.com +- iaptunnel.googleapis.com +- ids.googleapis.com +- integrations.googleapis.com +- language.googleapis.com +- lifesciences.googleapis.com +- logging.googleapis.com +- managedidentities.googleapis.com +- memcache.googleapis.com +- meshca.googleapis.com +- metastore.googleapis.com +- ml.googleapis.com +- monitoring.googleapis.com +- networkconnectivity.googleapis.com +- networkmanagement.googleapis.com +- networksecurity.googleapis.com +- networkservices.googleapis.com +- notebooks.googleapis.com +- opsconfigmonitoring.googleapis.com +- osconfig.googleapis.com +- oslogin.googleapis.com +- policytroubleshooter.googleapis.com +- privateca.googleapis.com +- pubsub.googleapis.com +- pubsublite.googleapis.com +- recaptchaenterprise.googleapis.com +- recommender.googleapis.com +- redis.googleapis.com +- retail.googleapis.com +- run.googleapis.com +- secretmanager.googleapis.com +- servicecontrol.googleapis.com +- servicedirectory.googleapis.com +- spanner.googleapis.com +- speakerid.googleapis.com +- speech.googleapis.com +- sqladmin.googleapis.com +- storage.googleapis.com +- storagetransfer.googleapis.com +- texttospeech.googleapis.com +- tpu.googleapis.com +- trafficdirector.googleapis.com +- transcoder.googleapis.com +- translate.googleapis.com +- videointelligence.googleapis.com +- vision.googleapis.com +- visionai.googleapis.com +- vpcaccess.googleapis.com +- workstations.googleapis.com \ No newline at end of file diff --git a/blueprints/data-solutions/shielded-folder/data/vpc-sc/restricted-services.yaml b/blueprints/data-solutions/shielded-folder/data/vpc-sc/restricted-services.yaml new file mode 100644 index 00000000..2107d2ff --- /dev/null +++ b/blueprints/data-solutions/shielded-folder/data/vpc-sc/restricted-services.yaml @@ -0,0 +1,119 @@ +# skip boilerplate check + +- accessapproval.googleapis.com +- adsdatahub.googleapis.com +- aiplatform.googleapis.com +- alloydb.googleapis.com +- alpha-documentai.googleapis.com +- analyticshub.googleapis.com +- apigee.googleapis.com +- apigeeconnect.googleapis.com +- artifactregistry.googleapis.com +- assuredworkloads.googleapis.com +- automl.googleapis.com +- baremetalsolution.googleapis.com +- batch.googleapis.com +- beyondcorp.googleapis.com +- bigquery.googleapis.com +- bigquerydatapolicy.googleapis.com +- bigquerydatatransfer.googleapis.com +- bigquerymigration.googleapis.com +- bigqueryreservation.googleapis.com +- bigtable.googleapis.com +- binaryauthorization.googleapis.com +- cloudasset.googleapis.com +- cloudbuild.googleapis.com +- clouddebugger.googleapis.com +- clouderrorreporting.googleapis.com +- cloudfunctions.googleapis.com +- cloudkms.googleapis.com +- cloudprofiler.googleapis.com +- cloudresourcemanager.googleapis.com +- cloudsearch.googleapis.com +- cloudtrace.googleapis.com +- composer.googleapis.com +- compute.googleapis.com +- connectgateway.googleapis.com +- contactcenterinsights.googleapis.com +- container.googleapis.com +- containeranalysis.googleapis.com +- containerfilesystem.googleapis.com +- containerregistry.googleapis.com +- containerthreatdetection.googleapis.com +- contentwarehouse.googleapis.com +- datacatalog.googleapis.com +- dataflow.googleapis.com +- datafusion.googleapis.com +- datalineage.googleapis.com +- datamigration.googleapis.com +- datapipelines.googleapis.com +- dataplex.googleapis.com +- dataproc.googleapis.com +- datastream.googleapis.com +- dialogflow.googleapis.com +- dlp.googleapis.com +- dns.googleapis.com +- documentai.googleapis.com +- domains.googleapis.com +- essentialcontacts.googleapis.com +- eventarc.googleapis.com +- file.googleapis.com +- firebaseappcheck.googleapis.com +- firebaserules.googleapis.com +- firestore.googleapis.com +- gameservices.googleapis.com +- gkebackup.googleapis.com +- gkeconnect.googleapis.com +- gkehub.googleapis.com +- gkemulticloud.googleapis.com +- healthcare.googleapis.com +- iam.googleapis.com +- iamcredentials.googleapis.com +- iaptunnel.googleapis.com +- ids.googleapis.com +- integrations.googleapis.com +- language.googleapis.com +- lifesciences.googleapis.com +- logging.googleapis.com +- managedidentities.googleapis.com +- memcache.googleapis.com +- meshca.googleapis.com +- metastore.googleapis.com +- ml.googleapis.com +- monitoring.googleapis.com +- networkconnectivity.googleapis.com +- networkmanagement.googleapis.com +- networksecurity.googleapis.com +- networkservices.googleapis.com +- notebooks.googleapis.com +- opsconfigmonitoring.googleapis.com +- osconfig.googleapis.com +- oslogin.googleapis.com +- policytroubleshooter.googleapis.com +- privateca.googleapis.com +- pubsub.googleapis.com +- pubsublite.googleapis.com +- recaptchaenterprise.googleapis.com +- recommender.googleapis.com +- redis.googleapis.com +- retail.googleapis.com +- run.googleapis.com +- secretmanager.googleapis.com +- servicecontrol.googleapis.com +- servicedirectory.googleapis.com +- spanner.googleapis.com +- speakerid.googleapis.com +- speech.googleapis.com +- sqladmin.googleapis.com +- storage.googleapis.com +- storagetransfer.googleapis.com +- texttospeech.googleapis.com +- tpu.googleapis.com +- trafficdirector.googleapis.com +- transcoder.googleapis.com +- translate.googleapis.com +- videointelligence.googleapis.com +- vision.googleapis.com +- visionai.googleapis.com +- vpcaccess.googleapis.com +- workstations.googleapis.com \ No newline at end of file diff --git a/blueprints/data-solutions/shielded-folder/images/overview_diagram.png b/blueprints/data-solutions/shielded-folder/images/overview_diagram.png new file mode 100644 index 00000000..abc8d56e Binary files /dev/null and b/blueprints/data-solutions/shielded-folder/images/overview_diagram.png differ diff --git a/blueprints/data-solutions/shielded-folder/kms.tf b/blueprints/data-solutions/shielded-folder/kms.tf new file mode 100644 index 00000000..7a3b42b5 --- /dev/null +++ b/blueprints/data-solutions/shielded-folder/kms.tf @@ -0,0 +1,102 @@ +/** + * 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 Security project, Cloud KMS and Secret Manager resources. + +locals { + kms_locations = distinct(flatten([ + for k, v in var.kms_keys : v.locations + ])) + kms_locations_keys = { + for loc in local.kms_locations : loc => { + for k, v in var.kms_keys : k => v if contains(v.locations, loc) + } + } + + kms_log_locations = distinct(flatten([ + for k, v in local.kms_log_sink_keys : compact(v.locations) + ])) + + # Log sink keys + kms_log_sink_keys = { + "storage" = { + labels = {} + locations = [var.log_locations.storage] + rotation_period = "7776000s" + } + "bq" = { + labels = {} + locations = [var.log_locations.bq] + rotation_period = "7776000s" + } + "pubsub" = { + labels = {} + locations = [var.log_locations.pubsub] + rotation_period = "7776000s" + } + } + kms_log_locations_keys = { + for loc in local.kms_log_locations : loc => { + for k, v in local.kms_log_sink_keys : k => v if contains(v.locations, loc) + } + } +} + +module "sec-project" { + count = var.enable_features.encryption ? 1 : 0 + source = "../../../modules/project" + name = var.project_config.project_ids["sec-core"] + parent = module.folder.id + billing_account = var.project_config.billing_account_id + project_create = var.project_config.billing_account_id != null && var.enable_features.encryption + prefix = var.project_config.billing_account_id == null ? null : var.prefix + group_iam = { + (local.groups.workload-security) = [ + "roles/editor" + ] + } + services = [ + "cloudkms.googleapis.com", + "secretmanager.googleapis.com", + "stackdriver.googleapis.com" + ] +} + +module "sec-kms" { + for_each = var.enable_features.encryption ? toset(local.kms_locations) : toset([]) + source = "../../../modules/kms" + project_id = module.sec-project[0].project_id + keyring = { + location = each.key + name = "${each.key}" + } + # rename to `key_iam` to switch to authoritative bindings + key_iam_additive = { + for k, v in local.kms_locations_keys[each.key] : k => v.iam + } + keys = local.kms_locations_keys[each.key] +} + +module "log-kms" { + for_each = var.enable_features.encryption ? toset(local.kms_log_locations) : toset([]) + source = "../../../modules/kms" + project_id = module.sec-project[0].project_id + keyring = { + location = each.key + name = "${each.key}" + } + keys = local.kms_log_locations_keys[each.key] +} diff --git a/blueprints/data-solutions/shielded-folder/log-export.tf b/blueprints/data-solutions/shielded-folder/log-export.tf new file mode 100644 index 00000000..9eff32d2 --- /dev/null +++ b/blueprints/data-solutions/shielded-folder/log-export.tf @@ -0,0 +1,107 @@ +/** + * 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 Audit log project and sink. + +locals { + gcs_storage_class = ( + length(split("-", var.log_locations.storage)) < 2 + ? "MULTI_REGIONAL" + : "REGIONAL" + ) + log_types = toset([for k, v in var.log_sinks : v.type]) + + _log_keys = var.enable_features.encryption ? { + bq = var.enable_features.log_sink ? ["projects/${module.sec-project.0.project_id}/locations/${var.log_locations.bq}/keyRings/${var.log_locations.bq}/cryptoKeys/bq"] : null + pubsub = var.enable_features.log_sink ? ["projects/${module.sec-project.0.project_id}/locations/${var.log_locations.pubsub}/keyRings/${var.log_locations.pubsub}/cryptoKeys/pubsub"] : null + storage = var.enable_features.log_sink ? ["projects/${module.sec-project.0.project_id}/locations/${var.log_locations.storage}/keyRings/${var.log_locations.storage}/cryptoKeys/storage"] : null + } : {} + + log_keys = { + for service, key in local._log_keys : service => key if key != null + } +} + +module "log-export-project" { + count = var.enable_features.log_sink ? 1 : 0 + source = "../../../modules/project" + name = var.project_config.project_ids["audit-logs"] + parent = module.folder.id + billing_account = var.project_config.billing_account_id + project_create = var.project_config.billing_account_id != null + prefix = var.project_config.billing_account_id == null ? null : var.prefix + group_iam = { + (local.groups.workload-security) = [ + "roles/editor" + ] + } + iam = { + # "roles/owner" = [module.automation-tf-bootstrap-sa.iam_email] + } + services = [ + "bigquery.googleapis.com", + "pubsub.googleapis.com", + "storage.googleapis.com", + "stackdriver.googleapis.com" + ] + service_encryption_key_ids = var.enable_features.encryption ? local.log_keys : {} + + depends_on = [ + module.log-kms + ] +} + +# one log export per type, with conditionals to skip those not needed + +module "log-export-dataset" { + source = "../../../modules/bigquery-dataset" + count = var.enable_features.log_sink && contains(local.log_types, "bigquery") ? 1 : 0 + project_id = module.log-export-project[0].project_id + id = "${var.prefix}_audit_export" + friendly_name = "Audit logs export." + location = replace(var.log_locations.bq, "europe", "EU") + encryption_key = var.enable_features.encryption ? module.log-kms[var.log_locations.bq].keys["bq"].id : false +} + +module "log-export-gcs" { + source = "../../../modules/gcs" + count = var.enable_features.log_sink && contains(local.log_types, "storage") ? 1 : 0 + project_id = module.log-export-project[0].project_id + name = "audit-logs" + prefix = var.prefix + location = replace(var.log_locations.storage, "europe", "EU") + storage_class = local.gcs_storage_class + encryption_key = var.enable_features.encryption ? module.log-kms[var.log_locations.storage].keys["storage"].id : null +} + +module "log-export-logbucket" { + source = "../../../modules/logging-bucket" + for_each = var.enable_features.log_sink ? toset([for k, v in var.log_sinks : k if v.type == "logging"]) : [] + parent_type = "project" + parent = module.log-export-project[0].project_id + id = "audit-logs-${each.key}" + location = var.log_locations.logging + #TODO check if logging bucket support encryption. +} + +module "log-export-pubsub" { + source = "../../../modules/pubsub" + for_each = toset([for k, v in var.log_sinks : k if v.type == "pubsub" && var.enable_features.log_sink]) + project_id = module.log-export-project[0].project_id + name = "audit-logs-${each.key}" + regions = [var.log_locations.pubsub] + kms_key = var.enable_features.encryption ? module.log-kms[var.log_locations.pubsub].keys["pubsub"].id : null +} diff --git a/blueprints/data-solutions/shielded-folder/main.tf b/blueprints/data-solutions/shielded-folder/main.tf new file mode 100644 index 00000000..52fa0db2 --- /dev/null +++ b/blueprints/data-solutions/shielded-folder/main.tf @@ -0,0 +1,141 @@ +# 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 Folder resources. + +locals { + # Create Log sink ingress policies + _sink_ingress_policies = var.enable_features.log_sink ? { + log_sink = { + from = { + access_levels = ["*"] + identities = values(module.folder.sink_writer_identities) + } + to = { + resources = ["projects/${module.log-export-project.0.number}"] + operations = [{ service_name = "*" }] + } } + } : null + + _vpc_sc_vpc_accessible_services = var.data_dir != null ? yamldecode( + file("${var.data_dir}/vpc-sc/restricted-services.yaml") + ) : null + _vpc_sc_restricted_services = var.data_dir != null ? yamldecode( + file("${var.data_dir}/vpc-sc/restricted-services.yaml") + ) : null + + access_policy_create = var.access_policy_config.access_policy_create != null ? { + parent = "organizations/${var.organization.id}" + title = "shielded-folder" + scopes = [module.folder.id] + } : null + + groups = { + for k, v in var.groups : k => "${v}@${var.organization.domain}" + } + groups_iam = { + for k, v in local.groups : k => "group:${v}" + } + group_iam = { + (local.groups.workload-engineers) = [ + "roles/editor", + "roles/iam.serviceAccountTokenCreator" + ] + } + + vpc_sc_resources = [ + for k, v in data.google_projects.folder-projects.projects : format("projects/%s", v.number) + ] + + log_sink_destinations = var.enable_features.log_sink ? merge( + # use the same dataset for all sinks with `bigquery` as destination + { for k, v in var.log_sinks : k => module.log-export-dataset.0 if v.type == "bigquery" }, + # use the same gcs bucket for all sinks with `storage` as destination + { for k, v in var.log_sinks : k => module.log-export-gcs.0 if v.type == "storage" }, + # use separate pubsub topics and logging buckets for sinks with + # destination `pubsub` and `logging` + module.log-export-pubsub, + module.log-export-logbucket + ) : null +} + +module "folder" { + source = "../../../modules/folder" + folder_create = var.folder_config.folder_create != null + parent = try(var.folder_config.folder_create.parent, null) + name = try(var.folder_config.folder_create.display_name, null) + id = var.folder_config.folder_create != null ? null : var.folder_config.folder_id + group_iam = local.group_iam + org_policies_data_path = var.data_dir != null ? "${var.data_dir}/org-policies" : null + firewall_policy_factory = var.data_dir != null ? { + cidr_file = "${var.data_dir}/firewall-policies/cidrs.yaml" + policy_name = "${var.prefix}-fw-policy" + rules_file = "${var.data_dir}/firewall-policies/hierarchical-policy-rules.yaml" + } : null + logging_sinks = var.enable_features.log_sink ? { + for name, attrs in var.log_sinks : name => { + bq_partitioned_table = attrs.type == "bigquery" + destination = local.log_sink_destinations[name].id + filter = attrs.filter + type = attrs.type + } + } : null +} + +module "folder-workload" { + source = "../../../modules/folder" + parent = module.folder.id + name = "${var.prefix}-workload" +} + + +#TODO VPCSC: Access levels +data "google_projects" "folder-projects" { + filter = "parent.id:${split("/", module.folder.id)[1]}" + + depends_on = [ + module.sec-project, + module.log-export-project + ] +} + +module "vpc-sc" { + count = var.enable_features.vpc_sc ? 1 : 0 + source = "../../../modules/vpc-sc" + access_policy = try(var.access_policy_config.policy_name, null) + access_policy_create = local.access_policy_create + access_levels = var.vpc_sc_access_levels + egress_policies = var.vpc_sc_egress_policies + ingress_policies = merge(var.vpc_sc_ingress_policies, local._sink_ingress_policies) + service_perimeters_regular = { + shielded = { + # Move `spec` definition to `status` and comment `use_explicit_dry_run_spec` variable to enforce VPC-SC configuration + # Before enforing configuration check logs and create Access Level, Ingress/Egress policy as needed + + status = null + spec = { + access_levels = keys(var.vpc_sc_access_levels) + resources = local.vpc_sc_resources + restricted_services = local._vpc_sc_restricted_services + egress_policies = keys(var.vpc_sc_egress_policies) + ingress_policies = keys(merge(var.vpc_sc_ingress_policies, local._sink_ingress_policies)) + vpc_accessible_services = { + allowed_services = local._vpc_sc_vpc_accessible_services + enable_restriction = true + } + } + use_explicit_dry_run_spec = true + } + } +} diff --git a/blueprints/data-solutions/shielded-folder/outputs.tf b/blueprints/data-solutions/shielded-folder/outputs.tf new file mode 100644 index 00000000..e1107fc6 --- /dev/null +++ b/blueprints/data-solutions/shielded-folder/outputs.tf @@ -0,0 +1,30 @@ +# 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. + +output "folders" { + description = "Folders id." + value = { + shielded-folder = module.folder.id + workload-folder = module.folder-workload.id + } +} + +output "folders_sink_writer_identities" { + description = "Folders id." + value = { + shielded-folder = module.folder.sink_writer_identities + workload-folder = module.folder-workload.sink_writer_identities + } +} + diff --git a/blueprints/data-solutions/shielded-folder/variables.tf b/blueprints/data-solutions/shielded-folder/variables.tf new file mode 100644 index 00000000..a9ecbb24 --- /dev/null +++ b/blueprints/data-solutions/shielded-folder/variables.tf @@ -0,0 +1,229 @@ +# 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 Variables definition. + +variable "access_policy_config" { + description = "Provide 'access_policy_create' values if a folder scoped Access Policy creation is needed, uses existing 'policy_name' otherwise. Parent is in 'organizations/123456' format. Policy will be created scoped to the folder." + type = object({ + policy_name = optional(string, null) + access_policy_create = optional(object({ + parent = string + title = string + }), null) + }) + nullable = false +} + +variable "data_dir" { + description = "Relative path for the folder storing configuration data." + type = string + default = "data" +} + +variable "enable_features" { + description = "Flag to enable features on the solution." + type = object({ + encryption = optional(bool, false) + log_sink = optional(bool, true) + vpc_sc = optional(bool, true) + }) + default = { + encryption = false + log_sink = true + vpc_sc = true + } +} + +variable "folder_config" { + description = "Provide 'folder_create' values if folder creation is needed, uses existing 'folder_id' otherwise. Parent is in 'folders/nnn' or 'organizations/nnn' format." + type = object({ + folder_id = optional(string, null) + folder_create = optional(object({ + display_name = string + parent = string + }), null) + }) + validation { + condition = var.folder_config.folder_id != null || var.folder_config.folder_create != null + error_message = "At least one attribute should be set." + } + nullable = false +} + +variable "groups" { + description = "User groups." + type = object({ + workload-engineers = optional(string, "gcp-data-engineers") + workload-security = optional(string, "gcp-data-security") + }) + default = {} + nullable = false +} + +variable "kms_keys" { + description = "KMS keys to create, keyed by name." + type = map(object({ + iam = optional(map(list(string)), {}) + labels = optional(map(string), {}) + locations = optional(list(string), ["global", "europe", "europe-west1"]) + rotation_period = optional(string, "7776000s") + })) + default = {} +} + +variable "log_locations" { + description = "Optional locations for GCS, BigQuery, and logging buckets created here." + type = object({ + bq = optional(string, "europe") + storage = optional(string, "europe") + logging = optional(string, "global") + pubsub = optional(string, "global") + }) + default = { + bq = "europe" + storage = "europe" + logging = "global" + pubsub = null + } + nullable = false +} + +variable "log_sinks" { + description = "Org-level log sinks, in name => {type, filter} format." + type = map(object({ + filter = string + type = string + })) + default = { + audit-logs = { + filter = "logName:\"/logs/cloudaudit.googleapis.com%2Factivity\" OR logName:\"/logs/cloudaudit.googleapis.com%2Fsystem_event\"" + type = "bigquery" + } + vpc-sc = { + filter = "protoPayload.metadata.@type=\"type.googleapis.com/google.cloud.audit.VpcServiceControlAuditMetadata\"" + type = "bigquery" + } + } + validation { + condition = alltrue([ + for k, v in var.log_sinks : + contains(["bigquery", "logging", "pubsub", "storage"], v.type) + ]) + error_message = "Type must be one of 'bigquery', 'logging', 'pubsub', 'storage'." + } +} + +variable "organization" { + description = "Organization details." + type = object({ + domain = string + id = string + }) +} + +variable "prefix" { + description = "Prefix used for resources that need unique names." + type = string +} + +variable "project_config" { + description = "Provide 'billing_account_id' value if project creation is needed, uses existing 'project_ids' if null. Parent is in 'folders/nnn' or 'organizations/nnn' format." + type = object({ + billing_account_id = optional(string, null) + project_ids = optional(object({ + sec-core = string + audit-logs = string + }), { + sec-core = "sec-core" + audit-logs = "audit-logs" + } + ) + }) + nullable = false + validation { + condition = var.project_config.billing_account_id != null || var.project_config.project_ids != null + error_message = "At least one attribute should be set." + } +} + +variable "vpc_sc_access_levels" { + description = "VPC SC access level definitions." + type = map(object({ + combining_function = optional(string) + conditions = optional(list(object({ + device_policy = optional(object({ + allowed_device_management_levels = optional(list(string)) + allowed_encryption_statuses = optional(list(string)) + require_admin_approval = bool + require_corp_owned = bool + require_screen_lock = optional(bool) + os_constraints = optional(list(object({ + os_type = string + minimum_version = optional(string) + require_verified_chrome_os = optional(bool) + }))) + })) + ip_subnetworks = optional(list(string), []) + members = optional(list(string), []) + negate = optional(bool) + regions = optional(list(string), []) + required_access_levels = optional(list(string), []) + })), []) + description = optional(string) + })) + default = {} + nullable = false +} + +variable "vpc_sc_egress_policies" { + description = "VPC SC egress policy defnitions." + type = map(object({ + from = object({ + identity_type = optional(string, "ANY_IDENTITY") + identities = optional(list(string)) + }) + to = object({ + operations = optional(list(object({ + method_selectors = optional(list(string)) + service_name = string + })), []) + resources = optional(list(string)) + resource_type_external = optional(bool, false) + }) + })) + default = {} + nullable = false +} + +variable "vpc_sc_ingress_policies" { + description = "VPC SC ingress policy defnitions." + type = map(object({ + from = object({ + access_levels = optional(list(string), []) + identity_type = optional(string) + identities = optional(list(string)) + resources = optional(list(string), []) + }) + to = object({ + operations = optional(list(object({ + method_selectors = optional(list(string)) + service_name = string + })), []) + resources = optional(list(string)) + }) + })) + default = {} + nullable = false +} diff --git a/blueprints/data-solutions/vertex-mlops/README.md b/blueprints/data-solutions/vertex-mlops/README.md new file mode 100644 index 00000000..d9f85fd8 --- /dev/null +++ b/blueprints/data-solutions/vertex-mlops/README.md @@ -0,0 +1,79 @@ +# MLOps with Vertex AI + +## Introduction +This example implements the infrastructure required to deploy an end-to-end [MLOps process](https://services.google.com/fh/files/misc/practitioners_guide_to_mlops_whitepaper.pdf) using [Vertex AI](https://cloud.google.com/vertex-ai) platform. + +## GCP resources +The blueprint will deploy all the required resources to have a fully functional MLOPs environment containing: +- Vertex Workbench (for the experimentation environment) +- GCP Project (optional) to host all the resources +- Isolated VPC network and a subnet to be used by Vertex and Dataflow. Alternatively, an external Shared VPC can be configured using the `network_config`variable. +- Firewall rule to allow the internal subnet communication required by Dataflow +- Cloud NAT required to reach the internet from the different computing resources (Vertex and Dataflow) +- GCS buckets to host Vertex AI and Cloud Build Artifacts. By default the buckets will be regional and should match the Vertex AI region for the different resources (i.e. Vertex Managed Dataset) and processes (i.e. Vertex trainining) +- BigQuery Dataset where the training data will be stored. This is optional, since the training data could be already hosted in an existing BigQuery dataset. +- Artifact Registry Docker repository to host the custom images. +- Service account (`mlops-[env]@`) with the minimum permissions required by Vertex AI and Dataflow (if this service is used inside of the Vertex AI Pipeline). +- Service account (`github@`) to be used by Workload Identity Federation, to federate Github identity (Optional). +- Secret to store the Github SSH key to get access the CICD code repo. + +![MLOps project description](./images/mlops_projects.png "MLOps project description") + +## Pre-requirements + +### User groups + +Assign roles relying on User groups is a way to decouple the final set of permissions from the stage where entities and resources are created, and their IAM bindings defined. You can configure the group names through the `groups` variable. These groups should be created before launching Terraform. + +We use the following groups to control access to resources: + +- *Data Scientits* (gcp-ml-ds@). They manage notebooks and create ML pipelines. +- *ML Engineers* (gcp-ml-eng@). They manage the different Vertex resources. +- *ML Viewer* (gcp-ml-eng@). Group with wiewer permission for the different resources. + +Please note that these groups are not suitable for production grade environments. Roles can be customized in the `main.tf`file. + +## Instructions +### Deploy the experimentation environment + +- Create a `terraform.tfvars` file and specify the variables to match your desired configuration. You can use the provided `terraform.tfvars.sample` as reference. +- Run `terraform init` and `terraform apply` + +## What's next? + +This blueprint can be used as a building block for setting up an end2end ML Ops solution. As next step, you can follow this [guide](https://cloud.google.com/architecture/architecture-for-mlops-using-tfx-kubeflow-pipelines-and-cloud-build) to setup a Vertex AI pipeline and run it on the deployed infraestructure. + + +## Variables + +| name | description | type | required | default | +|---|---|:---:|:---:|:---:| +| [project_id](variables.tf#L101) | Project id, references existing project if `project_create` is null. | string | ✓ | | +| [bucket_name](variables.tf#L18) | GCS bucket name to store the Vertex AI artifacts. | string | | null | +| [dataset_name](variables.tf#L24) | BigQuery Dataset to store the training data. | string | | null | +| [groups](variables.tf#L30) | Name of the groups (name@domain.org) to apply opinionated IAM permissions. | object({…}) | | {…} | +| [identity_pool_claims](variables.tf#L45) | Claims to be used by Workload Identity Federation (i.e.: attribute.repository/ORGANIZATION/REPO). If a not null value is provided, then google_iam_workload_identity_pool resource will be created. | string | | null | +| [labels](variables.tf#L51) | Labels to be assigned at project level. | map(string) | | {} | +| [location](variables.tf#L57) | Location used for multi-regional resources. | string | | "eu" | +| [network_config](variables.tf#L63) | Shared VPC network configurations to use. If null networks will be created in projects with preconfigured values. | object({…}) | | null | +| [notebooks](variables.tf#L73) | Vertex AI workbenchs to be deployed. | map(object({…})) | | {} | +| [prefix](variables.tf#L86) | Prefix used for the project id. | string | | null | +| [project_create](variables.tf#L92) | Provide values if project creation is needed, uses existing project if null. Parent is in 'folders/nnn' or 'organizations/nnn' format. | object({…}) | | null | +| [project_services](variables.tf#L106) | List of core services enabled on all projects. | list(string) | | […] | +| [region](variables.tf#L126) | Region used for regional resources. | string | | "europe-west4" | +| [repo_name](variables.tf#L132) | Cloud Source Repository name. null to avoid to create it. | string | | null | +| [sa_mlops_name](variables.tf#L138) | Name for the MLOPs Service Account. | string | | "sa-mlops" | + +## Outputs + +| name | description | sensitive | +|---|---|:---:| +| [github](outputs.tf#L33) | Github Configuration. | | +| [notebook](outputs.tf#L39) | Vertex AI managed notebook details. | | +| [project](outputs.tf#L44) | The project resource as return by the `project` module. | | +| [project_id](outputs.tf#L49) | Project ID. | | + + +# TODO +- Add support for User Managed Notebooks, SA permission option and non default SA for Single User mode. +- Improve default naming for local VPC and Cloud NAT \ No newline at end of file diff --git a/blueprints/data-solutions/vertex-mlops/ci-cd.tf b/blueprints/data-solutions/vertex-mlops/ci-cd.tf new file mode 100644 index 00000000..d73eacc8 --- /dev/null +++ b/blueprints/data-solutions/vertex-mlops/ci-cd.tf @@ -0,0 +1,74 @@ +/** + * 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 + * + * 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. + */ + +resource "google_iam_workload_identity_pool" "github_pool" { + count = var.identity_pool_claims == null ? 0 : 1 + project = module.project.project_id + workload_identity_pool_id = "gh-pool" + display_name = "Github Actions Identity Pool" + description = "Identity pool for Github Actions" +} + +resource "google_iam_workload_identity_pool_provider" "github_provider" { + count = var.identity_pool_claims == null ? 0 : 1 + project = module.project.project_id + workload_identity_pool_id = google_iam_workload_identity_pool.github_pool[0].workload_identity_pool_id + workload_identity_pool_provider_id = "gh-provider" + display_name = "Github Actions provider" + description = "OIDC provider for Github Actions" + attribute_mapping = { + "google.subject" = "assertion.sub" + "attribute.repository" = "assertion.repository" + } + oidc { + issuer_uri = "https://token.actions.githubusercontent.com" + } +} + +module "artifact_registry" { + source = "../../../modules/artifact-registry" + id = "docker-repo" + project_id = module.project.project_id + location = var.region + format = "DOCKER" + # iam = { + # "roles/artifactregistry.admin" = ["group:cicd@example.com"] + # } +} + +module "service-account-github" { + source = "../../../modules/iam-service-account" + name = "sa-github" + project_id = module.project.project_id + iam = var.identity_pool_claims == null ? {} : { "roles/iam.workloadIdentityUser" = ["principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.github_pool[0].name}/${var.identity_pool_claims}"] } +} + +# NOTE: Secret manager module at the moment does not support CMEK +module "secret-manager" { + project_id = module.project.project_id + source = "../../../modules/secret-manager" + secrets = { + github-key = [var.region] + } + iam = { + github-key = { + "roles/secretmanager.secretAccessor" = [ + "serviceAccount:${module.project.service_accounts.robots.cloudbuild}", + module.service-account-mlops.iam_email + ] + } + } +} \ No newline at end of file diff --git a/blueprints/data-solutions/vertex-mlops/images/mlops_projects.png b/blueprints/data-solutions/vertex-mlops/images/mlops_projects.png new file mode 100644 index 00000000..24017bc9 Binary files /dev/null and b/blueprints/data-solutions/vertex-mlops/images/mlops_projects.png differ diff --git a/blueprints/data-solutions/vertex-mlops/main.tf b/blueprints/data-solutions/vertex-mlops/main.tf new file mode 100644 index 00000000..5f7fbc0c --- /dev/null +++ b/blueprints/data-solutions/vertex-mlops/main.tf @@ -0,0 +1,278 @@ +/** + * 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 + * + * 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 { + group_iam = merge( + var.groups.gcp-ml-viewer == null ? {} : { + (var.groups.gcp-ml-viewer) = [ + "roles/aiplatform.viewer", + "roles/artifactregistry.reader", + "roles/dataflow.viewer", + "roles/logging.viewer", + "roles/storage.objectViewer" + ] + }, + var.groups.gcp-ml-ds == null ? {} : { + (var.groups.gcp-ml-ds) = [ + "roles/aiplatform.admin", + "roles/artifactregistry.admin", + "roles/bigquery.dataEditor", + "roles/bigquery.jobUser", + "roles/bigquery.user", + "roles/cloudbuild.builds.editor", + "roles/cloudfunctions.developer", + "roles/dataflow.developer", + "roles/dataflow.worker", + "roles/iam.serviceAccountUser", + "roles/logging.logWriter", + "roles/logging.viewer", + "roles/notebooks.admin", + "roles/pubsub.editor", + "roles/serviceusage.serviceUsageConsumer", + "roles/storage.admin" + ] + }, + var.groups.gcp-ml-eng == null ? {} : { + (var.groups.gcp-ml-eng) = [ + "roles/aiplatform.admin", + "roles/artifactregistry.admin", + "roles/bigquery.dataEditor", + "roles/bigquery.jobUser", + "roles/bigquery.user", + "roles/dataflow.developer", + "roles/dataflow.worker", + "roles/iam.serviceAccountUser", + "roles/logging.logWriter", + "roles/logging.viewer", + "roles/serviceusage.serviceUsageConsumer", + "roles/storage.admin" + ] + } + ) + + 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-local.0.subnet_self_links)[0] + ) + vpc = ( + local.use_shared_vpc + ? var.network_config.network_self_link + : module.vpc-local.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 "gcs-bucket" { + count = var.bucket_name == null ? 0 : 1 + source = "../../../modules/gcs" + project_id = module.project.project_id + name = var.bucket_name + prefix = var.prefix + location = var.region + storage_class = "REGIONAL" + versioning = false + encryption_key = try(local.service_encryption_keys.storage, null) +} + +# Default bucket for Cloud Build to prevent error: "'us' violates constraint ‘constraints/gcp.resourceLocations’" +# https://stackoverflow.com/questions/53206667/cloud-build-fails-with-resource-location-constraint +module "gcs-bucket-cloudbuild" { + source = "../../../modules/gcs" + project_id = module.project.project_id + name = "${var.project_id}_cloudbuild" + prefix = var.prefix + location = var.region + storage_class = "REGIONAL" + versioning = false + encryption_key = try(local.service_encryption_keys.storage, null) +} + +module "bq-dataset" { + count = var.dataset_name == null ? 0 : 1 + source = "../../../modules/bigquery-dataset" + project_id = module.project.project_id + id = var.dataset_name + location = var.region + encryption_key = try(local.service_encryption_keys.bq, null) +} + +module "vpc-local" { + count = local.use_shared_vpc ? 0 : 1 + source = "../../../modules/net-vpc" + project_id = module.project.project_id + name = "default" + subnets = [ + { + "name" : "default", + "region" : "${var.region}", + "ip_cidr_range" : "10.4.0.0/24", + "secondary_ip_range" : null + } + ] + psa_config = { + ranges = { + "vertex" : "10.13.0.0/18" + } + routes = null + } +} + +module "firewall" { + count = local.use_shared_vpc ? 0 : 1 + source = "../../../modules/net-vpc-firewall" + project_id = module.project.project_id + network = module.vpc-local[0].name + default_rules_config = { + disabled = true + } + ingress_rules = { + dataflow-ingress = { + description = "Dataflow service." + direction = "INGRESS" + action = "allow" + sources = ["dataflow"] + targets = ["dataflow"] + ranges = [] + use_service_accounts = false + rules = [{ protocol = "tcp", ports = ["12345-12346"] }] + extra_attributes = {} + } + } + +} + +module "cloudnat" { + count = local.use_shared_vpc ? 0 : 1 + source = "../../../modules/net-cloudnat" + project_id = module.project.project_id + region = var.region + name = "default" + router_network = module.vpc-local[0].self_link +} + +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.prefix + group_iam = local.group_iam + iam = { + "roles/aiplatform.user" = [module.service-account-mlops.iam_email] + "roles/artifactregistry.reader" = [module.service-account-mlops.iam_email] + "roles/artifactregistry.writer" = [module.service-account-github.iam_email] + "roles/bigquery.dataEditor" = [module.service-account-mlops.iam_email] + "roles/bigquery.jobUser" = [module.service-account-mlops.iam_email] + "roles/bigquery.user" = [module.service-account-mlops.iam_email] + "roles/cloudbuild.builds.editor" = [ + module.service-account-mlops.iam_email, + module.service-account-github.iam_email + ] + + "roles/cloudfunctions.invoker" = [module.service-account-mlops.iam_email] + "roles/dataflow.developer" = [module.service-account-mlops.iam_email] + "roles/dataflow.worker" = [module.service-account-mlops.iam_email] + "roles/iam.serviceAccountUser" = [ + module.service-account-mlops.iam_email, + "serviceAccount:${module.project.service_accounts.robots.cloudbuild}" + ] + "roles/monitoring.metricWriter" = [module.service-account-mlops.iam_email] + "roles/run.invoker" = [module.service-account-mlops.iam_email] + "roles/serviceusage.serviceUsageConsumer" = [ + module.service-account-mlops.iam_email, + module.service-account-github.iam_email + ] + "roles/storage.admin" = [ + module.service-account-mlops.iam_email, + module.service-account-github.iam_email + ] + } + labels = var.labels + + org_policies = { + # Example of applying a project wide policy + # "constraints/compute.requireOsLogin" = { + # enforce = false + # } + } + + service_encryption_key_ids = { + bq = [try(local.service_encryption_keys.bq, null)] + compute = [try(local.service_encryption_keys.compute, null)] + cloudbuild = [try(local.service_encryption_keys.storage, null)] + notebooks = [try(local.service_encryption_keys.compute, null)] + storage = [try(local.service_encryption_keys.storage, null)] + } + services = var.project_services + + + shared_vpc_service_config = local.shared_vpc_project == null ? null : { + attach = true + host_project = local.shared_vpc_project + } + +} + +module "service-account-mlops" { + source = "../../../modules/iam-service-account" + name = var.sa_mlops_name + project_id = module.project.project_id + iam = { + "roles/iam.serviceAccountUser" = [module.service-account-github.iam_email] + } +} + +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}" +} + + +resource "google_sourcerepo_repository" "code-repo" { + count = var.repo_name == null ? 0 : 1 + name = var.repo_name + project = module.project.project_id +} + + diff --git a/blueprints/data-solutions/vertex-mlops/notebooks.tf b/blueprints/data-solutions/vertex-mlops/notebooks.tf new file mode 100644 index 00000000..09d3e5a8 --- /dev/null +++ b/blueprints/data-solutions/vertex-mlops/notebooks.tf @@ -0,0 +1,60 @@ +/** + * 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 + * + * 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. + */ + +resource "google_notebooks_runtime" "runtime" { + for_each = var.notebooks + name = each.key + + project = module.project.project_id + location = var.notebooks[each.key].region + access_config { + access_type = "SINGLE_USER" + runtime_owner = var.notebooks[each.key].owner + } + software_config { + enable_health_monitoring = true + idle_shutdown = var.notebooks[each.key].idle_shutdown + idle_shutdown_timeout = 1800 + } + virtual_machine { + virtual_machine_config { + machine_type = "n1-standard-4" + network = local.vpc + subnet = local.subnet + internal_ip_only = var.notebooks[each.key].internal_ip_only + dynamic "encryption_config" { + for_each = try(local.service_encryption_keys.compute, null) == null ? [] : [1] + content { + kms_key = local.service_encryption_keys.compute + } + } + metadata = { + notebook-disable-nbconvert = "false" + notebook-disable-downloads = "false" + notebook-disable-terminal = "false" + #notebook-disable-root = "true" + #notebook-upgrade-schedule = "48 4 * * MON" + } + data_disk { + initialize_params { + disk_size_gb = "100" + disk_type = "PD_STANDARD" + } + } + } + } +} + diff --git a/blueprints/data-solutions/vertex-mlops/outputs.tf b/blueprints/data-solutions/vertex-mlops/outputs.tf new file mode 100644 index 00000000..9cb390d6 --- /dev/null +++ b/blueprints/data-solutions/vertex-mlops/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 + * + * 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. + */ + +# TODO(): proper outputs + + +locals { + docker_split = try(split("/", module.artifact_registry.id), null) + docker_repo = try("${local.docker_split[3]}-docker.pkg.dev/${local.docker_split[1]}/${local.docker_split[5]}", null) + gh_config = { + WORKLOAD_ID_PROVIDER = try(google_iam_workload_identity_pool_provider.github_provider[0].name, null) + SERVICE_ACCOUNT = try(module.service-account-github.email, null) + PROJECT_ID = module.project.project_id + DOCKER_REPO = local.docker_repo + SA_MLOPS = module.service-account-mlops.email + SUBNETWORK = local.subnet + } +} + +output "github" { + + description = "Github Configuration." + value = local.gh_config +} + +output "notebook" { + description = "Vertex AI managed notebook details." + value = { for k, v in resource.google_notebooks_runtime.runtime : k => v.id } +} + +output "project" { + description = "The project resource as return by the `project` module." + value = module.project +} + +output "project_id" { + description = "Project ID." + value = module.project.project_id +} diff --git a/blueprints/data-solutions/vertex-mlops/terraform.tfvars.sample b/blueprints/data-solutions/vertex-mlops/terraform.tfvars.sample new file mode 100644 index 00000000..097bac3a --- /dev/null +++ b/blueprints/data-solutions/vertex-mlops/terraform.tfvars.sample @@ -0,0 +1,20 @@ +bucket_name = "creditcards-dev" +dataset_name = "creditcards" +identity_pool_claims = "attribute.repository/ORGANIZATION/REPO" +labels = { + "env" : "dev", + "team" : "ml" +} +notebooks = { + "myworkbench" : { + "owner" : "user@example.com", + "region" : "europe-west4", + "subnet" : "default", + } +} +prefix = "pref" +project_id = "creditcards-dev" +project_create = { + billing_account_id = "000000-123456-123456" + parent = "folders/111111111111" +} diff --git a/blueprints/data-solutions/vertex-mlops/variables.tf b/blueprints/data-solutions/vertex-mlops/variables.tf new file mode 100644 index 00000000..f3f6efad --- /dev/null +++ b/blueprints/data-solutions/vertex-mlops/variables.tf @@ -0,0 +1,152 @@ +/** + * 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 + * + * 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 "bucket_name" { + description = "GCS bucket name to store the Vertex AI artifacts." + type = string + default = null +} + +variable "dataset_name" { + description = "BigQuery Dataset to store the training data." + type = string + default = null +} + +variable "groups" { + description = "Name of the groups (name@domain.org) to apply opinionated IAM permissions." + type = object({ + gcp-ml-ds = string + gcp-ml-eng = string + gcp-ml-viewer = string + }) + default = { + gcp-ml-ds = null + gcp-ml-eng = null + gcp-ml-viewer = null + } + nullable = false +} + +variable "identity_pool_claims" { + description = "Claims to be used by Workload Identity Federation (i.e.: attribute.repository/ORGANIZATION/REPO). If a not null value is provided, then google_iam_workload_identity_pool resource will be created." + type = string + default = null +} + +variable "labels" { + description = "Labels to be assigned at project level." + type = map(string) + default = {} +} + +variable "location" { + description = "Location used for multi-regional resources." + 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 "notebooks" { + description = "Vertex AI workbenchs to be deployed." + type = map(object({ + owner = string + region = string + subnet = string + internal_ip_only = optional(bool, false) + idle_shutdown = optional(bool) + })) + default = {} + nullable = false +} + +variable "prefix" { + description = "Prefix used for the project id." + type = string + default = null +} + +variable "project_create" { + description = "Provide values if project creation is needed, uses existing project if null. Parent is in 'folders/nnn' or 'organizations/nnn' format." + 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 "project_services" { + description = "List of core services enabled on all projects." + type = list(string) + default = [ + "aiplatform.googleapis.com", + "artifactregistry.googleapis.com", + "bigquery.googleapis.com", + "cloudbuild.googleapis.com", + "compute.googleapis.com", + "datacatalog.googleapis.com", + "dataflow.googleapis.com", + "iam.googleapis.com", + "monitoring.googleapis.com", + "notebooks.googleapis.com", + "secretmanager.googleapis.com", + "servicenetworking.googleapis.com", + "serviceusage.googleapis.com" + ] +} + +variable "region" { + description = "Region used for regional resources." + type = string + default = "europe-west4" +} + +variable "repo_name" { + description = "Cloud Source Repository name. null to avoid to create it." + type = string + default = null +} + +variable "sa_mlops_name" { + description = "Name for the MLOPs Service Account." + type = string + default = "sa-mlops" +} + +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 +} \ No newline at end of file diff --git a/blueprints/factories/bigquery-factory/README.md b/blueprints/factories/bigquery-factory/README.md index 05cabffb..2cba6e01 100644 --- a/blueprints/factories/bigquery-factory/README.md +++ b/blueprints/factories/bigquery-factory/README.md @@ -1,36 +1,18 @@ # Google Cloud BQ Factory -This module allows creation and management of BigQuery datasets and views as well as tables by defining them in well formatted `yaml` files. +This module allows creation and management of BigQuery datasets tables and views by defining them in well-formatted YAML files. YAML abstraction for BQ can simplify users onboarding and also makes creation of tables easier compared to HCL. -Yaml abstraction for BQ can simplify users onboarding and also makes creation of tables easier compared to HCL. +This factory is based on the [BQ dataset module](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/tree/master/modules/bigquery-dataset) which currently only supports tables and views. As soon as external table and materialized view support is added, this factory will be enhanced accordingly. -Subfolders distinguish between views and tables and ensures easier navigation for users. - -This factory is based on the [BQ dataset module](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/tree/master/modules/bigquery-dataset) which currently only supports tables and views. As soon as external table and materialized view support is added, factory will be enhanced accordingly. - -You can create as many files as you like, the code will loop through it and create the required variables in order to execute everything accordingly. +You can create as many files as you like, the code will loop through it and create everything accordingly. ## Example ### Terraform code -```hcl -module "bq" { - source = "github.com/GoogleCloudPlatform/cloud-foundation-fabric/modules/bigquery-dataset" - - for_each = local.output - project_id = var.project_id - id = each.key - views = try(each.value.views, null) - tables = try(each.value.tables, null) -} -# tftest skip -``` - -### Configuration Structure - +In this section we show how to create tables and views from a file structure simlar to the one shown below. ```bash -base_folder +bigquery │ ├── tables │ ├── table_a.yaml @@ -40,32 +22,43 @@ base_folder │ ├── view_b.yaml ``` -## YAML structure and definition formatting - -### Tables - -Table definition to be placed in a set of yaml files in the corresponding subfolder. Structure should look as following: +First we create the table definition in `bigquery/tables/countries.yaml`. ```yaml - -dataset: # required name of the dataset the table is to be placed in -table: # required descriptive name of the table -schema: # required schema in JSON FORMAT Example: [{name: "test", type: "STRING"},{name: "test2", type: "INT64"}] -labels: # not required, defaults to {}, Example: {"a":"thisislabela","b":"thisislabelb"} -use_legacy_sql: boolean # not required, defaults to false -deletion_protection: boolean # not required, defaults to false +# tftest-file id=table path=bigquery/tables/countries.yaml +dataset: my_dataset +table: countries +deletion_protection: true +labels: + env: prod +schema: + - name: country + type: STRING + - name: population + type: INT64 ``` -### Views -View definition to be placed in a set of yaml files in the corresponding subfolder. Structure should look as following: +And a view in `bigquery/views/population.yaml`. ```yaml -dataset: # required, name of the dataset the view is to be placed in -view: # required, descriptive name of the view -query: # required, SQL Query for the view in quotes -labels: # not required, defaults to {}, Example: {"a":"thisislabela","b":"thisislabelb"} -use_legacy_sql: bool # not required, defaults to false -deletion_protection: bool # not required, defaults to false +# tftest-file id=view path=bigquery/views/population.yaml +dataset: my_dataset +view: department +query: SELECT SUM(population) from my_dataset.countries +labels: + env: prod +``` + +With this file structure, we can use the factory as follows: + +```hcl +module "bq" { + source = "./fabric/blueprints/factories/bigquery-factory" + project_id = var.project_id + tables_path = "bigquery/tables" + views_path = "bigquery/views" +} +# tftest modules=2 resources=3 files=table,view inventory=simple.yaml ``` @@ -74,8 +67,8 @@ deletion_protection: bool # not required, defaults to false | name | description | type | required | default | |---|---|:---:|:---:|:---:| | [project_id](variables.tf#L17) | Project ID. | string | ✓ | | -| [tables_dir](variables.tf#L22) | Relative path for the folder storing table data. | string | ✓ | | -| [views_dir](variables.tf#L27) | Relative path for the folder storing view data. | string | ✓ | | +| [tables_path](variables.tf#L22) | Relative path for the folder storing table data. | string | ✓ | | +| [views_path](variables.tf#L27) | Relative path for the folder storing view data. | string | ✓ | | ## TODO diff --git a/blueprints/factories/bigquery-factory/main.tf b/blueprints/factories/bigquery-factory/main.tf index 5995ea19..8c26f747 100644 --- a/blueprints/factories/bigquery-factory/main.tf +++ b/blueprints/factories/bigquery-factory/main.tf @@ -1,5 +1,5 @@ /** - * Copyright 2022 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,17 +16,22 @@ locals { views = { - for f in fileset("${var.views_dir}", "**/*.yaml") : - trimsuffix(f, ".yaml") => yamldecode(file("${var.views_dir}/${f}")) + for f in fileset(var.views_path, "**/*.yaml") : + trimsuffix(f, ".yaml") => yamldecode(file("${var.views_path}/${f}")) } tables = { - for f in fileset("${var.tables_dir}", "**/*.yaml") : - trimsuffix(f, ".yaml") => yamldecode(file("${var.tables_dir}/${f}")) + for f in fileset(var.tables_path, "**/*.yaml") : + trimsuffix(f, ".yaml") => yamldecode(file("${var.tables_path}/${f}")) } - output = { - for dataset in distinct([for v in values(merge(local.views, local.tables)) : v.dataset]) : + all_datasets = distinct(concat( + [for x in values(local.tables) : x.dataset], + [for x in values(local.views) : x.dataset] + )) + + datasets = { + for dataset in local.all_datasets : dataset => { "views" = { for k, v in local.views : @@ -57,9 +62,8 @@ locals { } module "bq" { - source = "../../../modules/bigquery-dataset" - - for_each = local.output + source = "../../../modules/bigquery-dataset" + for_each = local.datasets project_id = var.project_id id = each.key views = try(each.value.views, null) diff --git a/blueprints/factories/bigquery-factory/variables.tf b/blueprints/factories/bigquery-factory/variables.tf index 774ec86e..57025f62 100644 --- a/blueprints/factories/bigquery-factory/variables.tf +++ b/blueprints/factories/bigquery-factory/variables.tf @@ -1,5 +1,5 @@ /** - * Copyright 2022 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,12 +19,12 @@ variable "project_id" { type = string } -variable "tables_dir" { +variable "tables_path" { description = "Relative path for the folder storing table data." type = string } -variable "views_dir" { +variable "views_path" { description = "Relative path for the folder storing view data." type = string } diff --git a/blueprints/factories/net-vpc-firewall-yaml/versions.tf b/blueprints/factories/net-vpc-firewall-yaml/versions.tf index 4900174a..08492c6f 100644 --- a/blueprints/factories/net-vpc-firewall-yaml/versions.tf +++ b/blueprints/factories/net-vpc-firewall-yaml/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/blueprints/factories/project-factory/README.md b/blueprints/factories/project-factory/README.md index 32a1f8f0..2b8c3874 100644 --- a/blueprints/factories/project-factory/README.md +++ b/blueprints/factories/project-factory/README.md @@ -59,6 +59,7 @@ module "projects" { for_each = local.projects defaults = local.defaults project_id = each.key + descriptive_name = try(each.value.descriptive_name, null) billing_account_id = try(each.value.billing_account_id, null) billing_alert = try(each.value.billing_alert, null) dns_zones = try(each.value.dns_zones, []) @@ -222,28 +223,29 @@ vpc: | name | description | type | required | default | |---|---|:---:|:---:|:---:| | [billing_account_id](variables.tf#L17) | Billing account id. | string | ✓ | | -| [prefix](variables.tf#L151) | Prefix used for resource names. | string | ✓ | | -| [project_id](variables.tf#L160) | Project id. | string | ✓ | | +| [prefix](variables.tf#L157) | Prefix used for resource names. | string | ✓ | | +| [project_id](variables.tf#L166) | Project id. | string | ✓ | | | [billing_alert](variables.tf#L22) | Billing alert configuration. | object({…}) | | null | | [defaults](variables.tf#L35) | Project factory default values. | object({…}) | | null | -| [dns_zones](variables.tf#L57) | DNS private zones to create as child of var.defaults.environment_dns_zone. | list(string) | | [] | -| [essential_contacts](variables.tf#L63) | Email contacts to be used for billing and GCP notifications. | list(string) | | [] | -| [folder_id](variables.tf#L69) | Folder ID for the folder where the project will be created. | string | | null | -| [group_iam](variables.tf#L75) | Custom IAM settings in group => [role] format. | map(list(string)) | | {} | -| [group_iam_additive](variables.tf#L81) | Custom additive IAM settings in group => [role] format. | map(list(string)) | | {} | -| [iam](variables.tf#L87) | Custom IAM settings in role => [principal] format. | map(list(string)) | | {} | -| [iam_additive](variables.tf#L93) | Custom additive IAM settings in role => [principal] format. | map(list(string)) | | {} | -| [kms_service_agents](variables.tf#L99) | KMS IAM configuration in as service => [key]. | map(list(string)) | | {} | -| [labels](variables.tf#L105) | Labels to be assigned at project level. | map(string) | | {} | -| [org_policies](variables.tf#L111) | Org-policy overrides at project level. | map(object({…})) | | {} | -| [service_accounts](variables.tf#L165) | Service accounts to be created, and roles assigned them on the project. | map(list(string)) | | {} | -| [service_accounts_additive](variables.tf#L171) | Service accounts to be created, and roles assigned them on the project additively. | map(list(string)) | | {} | -| [service_accounts_iam](variables.tf#L177) | IAM bindings on service account resources. Format is KEY => {ROLE => [MEMBERS]}. | map(map(list(string))) | | {} | -| [service_accounts_iam_additive](variables.tf#L184) | IAM additive bindings on service account resources. Format is KEY => {ROLE => [MEMBERS]}. | map(map(list(string))) | | {} | -| [service_identities_iam](variables.tf#L191) | Custom IAM settings for service identities in service => [role] format. | map(list(string)) | | {} | -| [service_identities_iam_additive](variables.tf#L198) | Custom additive IAM settings for service identities in service => [role] format. | map(list(string)) | | {} | -| [services](variables.tf#L205) | Services to be enabled for the project. | list(string) | | [] | -| [vpc](variables.tf#L212) | VPC configuration for the project. | object({…}) | | null | +| [descriptive_name](variables.tf#L57) | Name of the project name. Used for project name instead of `name` variable. | string | | null | +| [dns_zones](variables.tf#L63) | DNS private zones to create as child of var.defaults.environment_dns_zone. | list(string) | | [] | +| [essential_contacts](variables.tf#L69) | Email contacts to be used for billing and GCP notifications. | list(string) | | [] | +| [folder_id](variables.tf#L75) | Folder ID for the folder where the project will be created. | string | | null | +| [group_iam](variables.tf#L81) | Custom IAM settings in group => [role] format. | map(list(string)) | | {} | +| [group_iam_additive](variables.tf#L87) | Custom additive IAM settings in group => [role] format. | map(list(string)) | | {} | +| [iam](variables.tf#L93) | Custom IAM settings in role => [principal] format. | map(list(string)) | | {} | +| [iam_additive](variables.tf#L99) | Custom additive IAM settings in role => [principal] format. | map(list(string)) | | {} | +| [kms_service_agents](variables.tf#L105) | KMS IAM configuration in as service => [key]. | map(list(string)) | | {} | +| [labels](variables.tf#L111) | Labels to be assigned at project level. | map(string) | | {} | +| [org_policies](variables.tf#L117) | Org-policy overrides at project level. | map(object({…})) | | {} | +| [service_accounts](variables.tf#L171) | Service accounts to be created, and roles assigned them on the project. | map(list(string)) | | {} | +| [service_accounts_additive](variables.tf#L177) | Service accounts to be created, and roles assigned them on the project additively. | map(list(string)) | | {} | +| [service_accounts_iam](variables.tf#L183) | IAM bindings on service account resources. Format is KEY => {ROLE => [MEMBERS]}. | map(map(list(string))) | | {} | +| [service_accounts_iam_additive](variables.tf#L190) | IAM additive bindings on service account resources. Format is KEY => {ROLE => [MEMBERS]}. | map(map(list(string))) | | {} | +| [service_identities_iam](variables.tf#L197) | Custom IAM settings for service identities in service => [role] format. | map(list(string)) | | {} | +| [service_identities_iam_additive](variables.tf#L204) | Custom additive IAM settings for service identities in service => [role] format. | map(list(string)) | | {} | +| [services](variables.tf#L211) | Services to be enabled for the project. | list(string) | | [] | +| [vpc](variables.tf#L218) | VPC configuration for the project. | object({…}) | | null | ## Outputs diff --git a/blueprints/factories/project-factory/main.tf b/blueprints/factories/project-factory/main.tf index f6b2a797..518d5a69 100644 --- a/blueprints/factories/project-factory/main.tf +++ b/blueprints/factories/project-factory/main.tf @@ -180,6 +180,7 @@ module "project" { source = "../../../modules/project" billing_account = local.billing_account_id name = var.project_id + descriptive_name = var.descriptive_name prefix = var.prefix contacts = { for c in local.essential_contacts : c => ["ALL"] } iam = local.iam diff --git a/blueprints/factories/project-factory/variables.tf b/blueprints/factories/project-factory/variables.tf index 0ece0f04..3aa3fa36 100644 --- a/blueprints/factories/project-factory/variables.tf +++ b/blueprints/factories/project-factory/variables.tf @@ -1,5 +1,5 @@ /** - * Copyright 2022 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -54,6 +54,12 @@ variable "defaults" { default = null } +variable "descriptive_name" { + description = "Name of the project name. Used for project name instead of `name` variable." + type = string + default = null +} + variable "dns_zones" { description = "DNS private zones to create as child of var.defaults.environment_dns_zone." type = list(string) diff --git a/blueprints/gke/multitenant-fleet/README.md b/blueprints/gke/multitenant-fleet/README.md index 1e09afaa..cadcf410 100644 --- a/blueprints/gke/multitenant-fleet/README.md +++ b/blueprints/gke/multitenant-fleet/README.md @@ -4,7 +4,7 @@ This blueprint presents an opinionated architecture to handle multiple homogeneo The pattern used in this design is useful, for blueprint, in cases where multiple clusters host/support the same workloads, such as in the case of a multi-regional deployment. Furthermore, combined with Anthos Config Sync and proper RBAC, this architecture can be used to host multiple tenants (e.g. teams, applications) sharing the clusters. -This blueprint is used as part of the [FAST GKE stage](../../../fast/stages/03-gke-multitenant/) but it can also be used independently if desired. +This blueprint is used as part of the [FAST GKE stage](../../../fast/stages/3-gke-multitenant/) but it can also be used independently if desired.

GKE multitenant diff --git a/blueprints/networking/__need_fixing/nginx-reverse-proxy-cluster/versions.tf b/blueprints/networking/__need_fixing/nginx-reverse-proxy-cluster/versions.tf index 4900174a..08492c6f 100644 --- a/blueprints/networking/__need_fixing/nginx-reverse-proxy-cluster/versions.tf +++ b/blueprints/networking/__need_fixing/nginx-reverse-proxy-cluster/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/blueprints/networking/__need_fixing/onprem-google-access-dns/versions.tf b/blueprints/networking/__need_fixing/onprem-google-access-dns/versions.tf index 4900174a..08492c6f 100644 --- a/blueprints/networking/__need_fixing/onprem-google-access-dns/versions.tf +++ b/blueprints/networking/__need_fixing/onprem-google-access-dns/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/blueprints/networking/decentralized-firewall/versions.tf b/blueprints/networking/decentralized-firewall/versions.tf index 4900174a..08492c6f 100644 --- a/blueprints/networking/decentralized-firewall/versions.tf +++ b/blueprints/networking/decentralized-firewall/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/blueprints/networking/filtering-proxy-psc/versions.tf b/blueprints/networking/filtering-proxy-psc/versions.tf index 4900174a..08492c6f 100644 --- a/blueprints/networking/filtering-proxy-psc/versions.tf +++ b/blueprints/networking/filtering-proxy-psc/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/blueprints/networking/filtering-proxy/versions.tf b/blueprints/networking/filtering-proxy/versions.tf index 4900174a..08492c6f 100644 --- a/blueprints/networking/filtering-proxy/versions.tf +++ b/blueprints/networking/filtering-proxy/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/blueprints/networking/hub-and-spoke-peering/versions.tf b/blueprints/networking/hub-and-spoke-peering/versions.tf index 4900174a..08492c6f 100644 --- a/blueprints/networking/hub-and-spoke-peering/versions.tf +++ b/blueprints/networking/hub-and-spoke-peering/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/blueprints/networking/hub-and-spoke-vpn/README.md b/blueprints/networking/hub-and-spoke-vpn/README.md index 5f596142..bdf877c7 100644 --- a/blueprints/networking/hub-and-spoke-vpn/README.md +++ b/blueprints/networking/hub-and-spoke-vpn/README.md @@ -7,7 +7,7 @@ A few additional features are also shown: - [custom BGP advertisements](https://cloud.google.com/router/docs/how-to/advertising-overview) to implement transitivity between spokes - [VPC Global Routing](https://cloud.google.com/network-connectivity/docs/router/how-to/configuring-routing-mode) to leverage a regional set of VPN gateways in different regions as next hops (used here for illustrative/study purpose, not usually done in real life) -The blueprint has been purposefully kept simple to show how to use and wire the VPC and VPN-HA modules together, and so that it can be used as a basis for experimentation. For a more complex scenario that better reflects real-life usage, including [Shared VPC](https://cloud.google.com/vpc/docs/shared-vpc) and [DNS cross-project binding](https://cloud.google.com/dns/docs/zones/cross-project-binding) please refer to the [FAST network stage](../../../fast/stages/02-networking-vpn/). +The blueprint has been purposefully kept simple to show how to use and wire the VPC and VPN-HA modules together, and so that it can be used as a basis for experimentation. For a more complex scenario that better reflects real-life usage, including [Shared VPC](https://cloud.google.com/vpc/docs/shared-vpc) and [DNS cross-project binding](https://cloud.google.com/dns/docs/zones/cross-project-binding) please refer to the [FAST network stage](../../../fast/stages/2-networking-b-vpn/). This is the high level diagram of this blueprint: diff --git a/blueprints/networking/hub-and-spoke-vpn/versions.tf b/blueprints/networking/hub-and-spoke-vpn/versions.tf index 4900174a..08492c6f 100644 --- a/blueprints/networking/hub-and-spoke-vpn/versions.tf +++ b/blueprints/networking/hub-and-spoke-vpn/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/blueprints/networking/ilb-next-hop/versions.tf b/blueprints/networking/ilb-next-hop/versions.tf index 4900174a..08492c6f 100644 --- a/blueprints/networking/ilb-next-hop/versions.tf +++ b/blueprints/networking/ilb-next-hop/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/blueprints/networking/private-cloud-function-from-onprem/versions.tf b/blueprints/networking/private-cloud-function-from-onprem/versions.tf index 4900174a..08492c6f 100644 --- a/blueprints/networking/private-cloud-function-from-onprem/versions.tf +++ b/blueprints/networking/private-cloud-function-from-onprem/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/blueprints/networking/shared-vpc-gke/versions.tf b/blueprints/networking/shared-vpc-gke/versions.tf index 4900174a..08492c6f 100644 --- a/blueprints/networking/shared-vpc-gke/versions.tf +++ b/blueprints/networking/shared-vpc-gke/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/blueprints/third-party-solutions/openshift/tf/versions.tf b/blueprints/third-party-solutions/openshift/tf/versions.tf index 4900174a..08492c6f 100644 --- a/blueprints/third-party-solutions/openshift/tf/versions.tf +++ b/blueprints/third-party-solutions/openshift/tf/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/default-versions.tf b/default-versions.tf index 4900174a..08492c6f 100644 --- a/default-versions.tf +++ b/default-versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/diagram.svg b/diagram.svg new file mode 100644 index 00000000..689adf24 --- /dev/null +++ b/diagram.svg @@ -0,0 +1,293 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+
+
+ + + +
+ +
+
+
+
+ + + +
+ +
+
+
+
+ + + +
+ +
+
+
+
+
+ + + + + + + +
+ +
+
+ +
+ Organization +
+
+ +
+ tag value [tenant] +
+
+ +
+ IAM bindings() +
+
+ +
+ organization policies() +
+
+
+
+ + + + + + +
+ «folder» +
+
+ +
+ Tenant0 +
+
+ +
+ IAM bindings() +
+
+ +
+ organization policies() +
+
+ +
+ tag bindings() +
+
+
+
+ + + + + + +
+ «folder» +
+
+ +
+ Tenant1 +
+
+ +
+ IAM bindings() +
+
+ +
+ organization policies() +
+
+ +
+ tag bindings() +
+
+
+
+ + + + + + +
+ «project» +
+
+ +
+ Tenant0_IaC +
+
+ +
+ service accounts [all stages] +
+
+ +
+ storage buckets [stage 0+1] +
+
+ +
+ optional CI/CD [stage 0+1] +
+
+ +
+ IAM bindings() +
+
+
+
+ + + + + + +
+ «project» +
+
+ +
+ Tenant1_IaC +
+
+ +
+ service accounts [all stages] +
+
+ +
+ storage buckets [stage 0+1] +
+
+ +
+ optional CI/CD [stage 0+1] +
+
+ +
+ IAM bindings() +
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/fast/README.md b/fast/README.md index e35a4837..7459c870 100644 --- a/fast/README.md +++ b/fast/README.md @@ -12,7 +12,7 @@ Fabric FAST was initially conceived to help enterprises quickly set up a GCP org ### Contracts and stages -FAST uses the concept of stages, which individually perform precise tasks but, taken together, build a functional, ready-to-use GCP organization. More importantly, stages are modeled around the security boundaries that typically appear in mature organizations. This arrangement allows delegating ownership of each stage to the team responsible for the types of resources it manages. For example, as its name suggests, the networking stage sets up all the networking elements and is usually the responsibility of a dedicated networking team within the organization. +FAST uses the concept of stages, which individually perform precise tasks but taken together build a functional, ready-to-use GCP organization. More importantly, stages are modeled around the security boundaries that typically appear in mature organizations. This arrangement allows delegating ownership of each stage to the team responsible for the types of resources it manages. For example, as its name suggests, the networking stage sets up all the networking elements and is usually the responsibility of a dedicated networking team within the organization. From the perspective of FAST's overall design, stages also work as contacts or interfaces, defining a set of pre-requisites and inputs required to perform their designed task and generating outputs needed by other stages lower in the chain. The diagram below shows the relationships between stages. @@ -20,7 +20,7 @@ From the perspective of FAST's overall design, stages also work as contacts or i Stages diagram

-Please refer to the [stages](./stages/) section for further details on each stage. +Please refer to the [stages](./stages/) section for further details on each stage. For details on tenant-level stages which introduce a deeper level of autonomy via nested FAST setups rooted in a top-level folder, refer to the [multitenant stages](#multitenant-organizations) section below. ### Security-first design @@ -32,11 +32,21 @@ FAST also aims to minimize the number of permissions granted to principals accor A resource factory consumes a simple representation of a resource (e.g., in YAML) and deploys it (e.g., using Terraform). Used correctly, factories can help decrease the management overhead of large-scale infrastructure deployments. See "[Resource Factories: A descriptive approach to Terraform](https://medium.com/google-cloud/resource-factories-a-descriptive-approach-to-terraform-581b3ebb59c)" for more details and the rationale behind factories. -FAST uses YAML-based factories to deploy subnets and firewall rules and, as its name suggests, in the [project factory](./stages/03-project-factory/) stage. +FAST uses YAML-based factories to deploy subnets and firewall rules and, as its name suggests, in the [project factory](./stages/3-project-factory/) stage. ### CI/CD -One of our objectives with FAST is to provide a lightweight reference design for the IaC repositories, and a built-in implementation for running our code in automated pipelines. Our CI/CD approach leverages [Workload Identity Federation](https://cloud.google.com/iam/docs/workload-identity-federation), and provides sample workflow configurations for several major providers. Refer to the [CI/CD section in the bootstrap stage](stages/00-bootstrap/README.md#cicd) for more details. We also provide separate [optional small stages](./extras/) to help you configure your CI/CD provider. +One of our objectives with FAST is to provide a lightweight reference design for the IaC repositories, and a built-in implementation for running our code in automated pipelines. Our CI/CD approach leverages [Workload Identity Federation](https://cloud.google.com/iam/docs/workload-identity-federation), and provides sample workflow configurations for several major providers. Refer to the [CI/CD section in the bootstrap stage](./stages/0-bootstrap/README.md#cicd) for more details. We also provide separate [optional small stages](./extras/) to help you configure your CI/CD provider. + +### Multitenant organizations + +FAST has built-in support for complex multitenant organizations, where each tenant has complete control over a separate hierarchy rooted in a top-level folder. This approach is particularly suited for large enterprises or governments, where country-level subsidiaries or government agencies have a wide degree of autonomy within a shared GCP organization managed by a central entity. + +FAST implements multitenancy via [dedicated stages](stages-multitenant) for tenant-level bootstrap and resource management, which configure separate hierarchies within the organization rooted in top-level folders, so that subsequent FAST stages (networking, security, data, etc.) can be used directly for each tenant. The diagram below shows the relationships between organization-level and tenant-level stages. + +

+ Stages diagram +

## Implementation @@ -57,9 +67,9 @@ Those familiar with Python will note that FAST follows many of the maxims in the ## Roadmap -Besides the features already described, FAST roadmap includes: +Besides the features already described, FAST also includes: - Stage to deploy environment-specific multitenant GKE clusters following Google's best practices - Stage to deploy a fully featured data platform -- Reference implementation to use FAST in CI/CD pipelines (in progress) -- Static policy enforcement +- Reference implementation to use FAST in CI/CD pipelines +- Static policy enforcement (planned) diff --git a/fast/extras/0-cicd-github/README.md b/fast/extras/0-cicd-github/README.md new file mode 100644 index 00000000..58407b5e --- /dev/null +++ b/fast/extras/0-cicd-github/README.md @@ -0,0 +1,139 @@ +# FAST GitHub repository management + +This small extra stage allows creating and populating GitHub repositories used to host FAST stage code, including rewriting of module sources and secrets used for private modules repository access. + +It is designed for use in a GitHub organization, and is only meant as a one-shot solution with perishable state especially when used for initial population, as you don't want Terraform to keep overwriting your changes with initial versions of files. + +Initial population is only meant to be used with actual stage, while populating the modules repository should be done by hand to avoid hitting the GitHub hourly limit for their API. + +Once initial population is done, you need to manually push to the repository + +- the `.tfvars` file with custom variable values for your stages +- the workflow configuration file generated by FAST stages + +## GitHub provider credentials + +A [GitHub token](https://github.com/settings/tokens) is needed to authenticate against their API. The token needs organization-level permissions, like shown in this screenshot: + +

+ GitHub token scopes. +

+ +Once a token is available set it in the `GITHUB_TOKEN` environment variable before running Terraform. + +## Variable configuration + +The `organization` required variable sets the GitHub organization where repositories will be created, and is used to configure the Terraform provider. + +### Modules repository and sources + +The `modules_config` variable controls creation and management of the key and secret used to access the private modules repository, and indirectly control population of initial files: if the `modules_config` variable is not specified no module repository is know to the code, so module source paths cannot be replaced, and initial population of files cannot happen. If the variable is specified, an optional `source_ref` attribute can be set to the reference used to pin modules versions. + +This is an example that configures the modules repository name and an optional reference, enabling initial population of repositories where the feature has been turned on: + +```hcl +modules_config = { + repository_name = "GoogleCloudPlatform/cloud-foundation-fabric" + source_ref = "v19.0.0" +} +# tftest skip +``` + +In the above example, no key options are set so it's assumed modules will be fetched from a public repository. If modules repository authentication is needed the `key_config` attribute also needs to be set. + +If no keypair path is specified an internally generated key will be stored as an access key in the modules repository, and as secrets in the stage repositories: + +```hcl +modules_config = { + repository_name = "GoogleCloudPlatform/cloud-foundation-fabric" + key_config = { + create_key = true + create_secrets = true + } +} +# tftest skip +``` + +To use an existing keypair pass the path to the private key, the public key name is assumed to have the same name ending with the `.pub` suffix. This is useful in cases where the access key has already been set in the modules repository, and new repositories need to be created and their corresponding secret set: + +```hcl +modules_config = { + repository_name = "GoogleCloudPlatform/cloud-foundation-fabric" + key_config = { + create_secrets = true + keypair_path = "~/modules-repository-key" + } +} +# tftest skip +``` + +### Repositories + +The `repositories` variable is where you configure which repositories to create and whether initial population of files is desired. + +This is an example that creates repositories for stages 00 and 01, and populates initial files for stages 00, 01, and 02: + +```tfvars +repositories = { + fast_00_bootstrap = { + create_options = { + description = "FAST bootstrap." + features = { + issues = true + } + } + populate_from = "../../stages/0-bootstrap" + } + fast_01_resman = { + create_options = { + description = "FAST resource management." + features = { + issues = true + } + } + populate_from = "../../stages/1-resman" + } + fast_02_networking = { + populate_from = "../../stages/2-networking-peering" + } +} +# tftest skip +``` + +The `create_options` repository attribute controls creation: if the attribute is not present, the repository is assumed to be already existing. + +Initial population depends on a modules repository being configured in the `modules_config` variable described in the preceding section and on the`populate_from` attributes in each repository where population is required, which point to the folder holding the files to be committed. + +### 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. + + + + +## Files + +| 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 | +| [outputs.tf](./outputs.tf) | Module outputs. | | +| [providers.tf](./providers.tf) | Provider configuration. | | +| [variables.tf](./variables.tf) | Module variables. | | + +## Variables + +| name | description | type | required | default | +|---|---|:---:|:---:|:---:| +| [organization](variables.tf#L50) | 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#L55) | Repositories to create. | map(object({…})) | | {} | + +## Outputs + +| name | description | sensitive | +|---|---|:---:| +| [clone](outputs.tf#L17) | Clone repository commands. | | + + diff --git a/fast/extras/00-cicd-github/cicd-versions.tf b/fast/extras/0-cicd-github/cicd-versions.tf similarity index 96% rename from fast/extras/00-cicd-github/cicd-versions.tf rename to fast/extras/0-cicd-github/cicd-versions.tf index 09f544cb..830f1e48 100644 --- a/fast/extras/00-cicd-github/cicd-versions.tf +++ b/fast/extras/0-cicd-github/cicd-versions.tf @@ -1,5 +1,5 @@ /** - * Copyright 2022 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/fast/extras/00-cicd-github/github_token.png b/fast/extras/0-cicd-github/github_token.png similarity index 100% rename from fast/extras/00-cicd-github/github_token.png rename to fast/extras/0-cicd-github/github_token.png diff --git a/fast/extras/00-cicd-github/main.tf b/fast/extras/0-cicd-github/main.tf similarity index 72% rename from fast/extras/00-cicd-github/main.tf rename to fast/extras/0-cicd-github/main.tf index ac6028c1..d91ab970 100644 --- a/fast/extras/00-cicd-github/main.tf +++ b/fast/extras/0-cicd-github/main.tf @@ -1,5 +1,5 @@ /** - * Copyright 2022 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,9 +15,6 @@ */ locals { - _modules_repository = [ - for k, v in var.repositories : local.repositories[k] if v.has_modules - ] _repository_files = flatten([ for k, v in var.repositories : [ for f in concat( @@ -30,12 +27,12 @@ locals { } ] if v.populate_from != null ]) - modules_ref = var.modules_ref == null ? "" : "?ref=${var.modules_ref}" - modules_repository = ( - length(local._modules_repository) > 0 - ? local._modules_repository.0 - : null + modules_ref = ( + try(var.modules_config.source_ref, null) == null + ? "" + : "?ref=${var.modules_config.source_ref}" ) + modules_repo = try(var.modules_config.repository_name, null) repositories = { for k, v in var.repositories : k => v.create_options == null ? k : github_repository.default[k].name @@ -56,6 +53,15 @@ locals { name = "templates/providers.tf.tpl" } if v.populate_from != null + }, + { + for k, v in var.repositories : + "${k}/templates/workflow-github.yaml" => { + repository = k + file = "../../assets/templates/workflow-github.yaml" + name = "templates/workflow-github.yaml" + } + if v.populate_from != null } ) } @@ -96,41 +102,49 @@ resource "github_repository" "default" { } resource "tls_private_key" "default" { - count = local.modules_repository != null ? 1 : 0 algorithm = "ED25519" } resource "github_repository_deploy_key" "default" { - count = local.modules_repository == null ? 0 : 1 + count = ( + try(var.modules_config.key_config.create_key, null) == true ? 1 : 0 + ) title = "Modules repository access" - repository = local.modules_repository - key = tls_private_key.default.0.public_key_openssh - read_only = true + repository = local.modules_repo + key = ( + try(var.modules_config.key_config.keypair_path, null) == null + ? tls_private_key.default.public_key_openssh + : file(pathexpand("${var.modules_config.key_config.keypair_path}.pub")) + ) + read_only = true } resource "github_actions_secret" "default" { - for_each = local.modules_repository == null ? {} : { - for k, v in local.repositories : - k => v if k != local.modules_repository - } - repository = each.key - secret_name = "CICD_MODULES_KEY" - plaintext_value = tls_private_key.default.0.private_key_openssh + for_each = ( + try(var.modules_config.key_config.create_secrets, null) == true + ? local.repositories + : {} + ) + repository = each.key + secret_name = "CICD_MODULES_KEY" + plaintext_value = ( + try(var.modules_config.key_config.keypair_path, null) == null + ? tls_private_key.default.private_key_openssh + : file(pathexpand("${var.modules_config.key_config.keypair_path}")) + ) } resource "github_repository_file" "default" { - for_each = ( - local.modules_repository == null ? {} : local.repository_files - ) + for_each = local.modules_repo == null ? {} : local.repository_files repository = local.repositories[each.value.repository] branch = "main" file = each.value.name content = ( - endswith(each.value.name, ".tf") && local.modules_repository != null + endswith(each.value.name, ".tf") && local.modules_repo != null ? replace( file(each.value.file), "/source\\s*=\\s*\"../../../modules/([^/\"]+)\"/", - "source = \"git@github.com:${var.organization}/${local.modules_repository}.git//$1${local.modules_ref}\"" # " + "source = \"git@github.com:${local.modules_repo}.git//$1${local.modules_ref}\"" # " ) : file(each.value.file) ) diff --git a/fast/extras/00-cicd-github/outputs.tf b/fast/extras/0-cicd-github/outputs.tf similarity index 96% rename from fast/extras/00-cicd-github/outputs.tf rename to fast/extras/0-cicd-github/outputs.tf index cb580e1f..61b5ffbc 100644 --- a/fast/extras/00-cicd-github/outputs.tf +++ b/fast/extras/0-cicd-github/outputs.tf @@ -1,5 +1,5 @@ /** - * Copyright 2022 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/fast/extras/00-cicd-github/providers.tf b/fast/extras/0-cicd-github/providers.tf similarity index 95% rename from fast/extras/00-cicd-github/providers.tf rename to fast/extras/0-cicd-github/providers.tf index 29be30ae..a7ccb32d 100644 --- a/fast/extras/00-cicd-github/providers.tf +++ b/fast/extras/0-cicd-github/providers.tf @@ -1,5 +1,5 @@ /** - * Copyright 2022 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/fast/extras/00-cicd-github/variables.tf b/fast/extras/0-cicd-github/variables.tf similarity index 75% rename from fast/extras/00-cicd-github/variables.tf rename to fast/extras/0-cicd-github/variables.tf index 0d9cb7fd..8e5d0832 100644 --- a/fast/extras/00-cicd-github/variables.tf +++ b/fast/extras/0-cicd-github/variables.tf @@ -1,5 +1,5 @@ /** - * Copyright 2022 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,10 +25,26 @@ variable "commmit_config" { nullable = false } -variable "modules_ref" { - description = "Optional git ref used in module sources." - type = string - default = null +variable "modules_config" { + description = "Configure access to repository module via key, and replacement for modules sources in stage repositories." + type = object({ + repository_name = string + source_ref = optional(string) + key_config = optional(object({ + create_key = optional(bool, false) + create_secrets = optional(bool, false) + keypair_path = optional(string) + }), {}) + }) + default = null + validation { + condition = ( + var.modules_config == null + || + try(var.modules_config.repository_name, null) != null + ) + error_message = "Modules configuration requires a modules repository name." + } } variable "organization" { @@ -63,7 +79,6 @@ variable "repositories" { }), {}) visibility = optional(string, "private") })) - has_modules = optional(bool, false) populate_from = optional(string) })) default = {} diff --git a/fast/extras/00-cicd-github/README.md b/fast/extras/00-cicd-github/README.md deleted file mode 100644 index acf249bc..00000000 --- a/fast/extras/00-cicd-github/README.md +++ /dev/null @@ -1,105 +0,0 @@ -# FAST GitHub repository management - -This small extra stage allows creation and management of GitHub repositories used to host FAST stage code, including initial population of files and rewriting of module sources. - -This stage is designed for quick repository creation in a GitHub organization, and is not suited for medium or long-term repository management especially if you enable initial population of files. - -## Initial population caveats - -Initial file population of repositories is controlled via the `populate_from` attribute, and needs a bit of care: - -- never run this stage with the same variables used for population once the repository starts being used, as **Terraform will manage file state and revert any changes at each apply**, which is probably not what you want. -- initial population of the modules repository is discouraged, as the number of resulting files Terraform needs to manage is very close to the GitHub hourly limit for their API, it's much easier to populate modules via regular git commands - -The scenario for which this stage has been designed is one-shot creation and/or population of stage repositories, running it multiple times with different variables and Terraform states if incremental creation is needed for subsequent FAST stages (e.g. GKE, data platform, etc.). - -Once initial population is done, you need to manually push to the repository - -- the `.tfvars` file with custom variable values for your stages -- the workflow configuration file generated by FAST stages - -## GitHub provider credentials - -A [GitHub token](https://github.com/settings/tokens) is needed to authenticate against their API. The token needs organization-level permissions, like shown in this screenshot: - -

- GitHub token scopes. -

- -## Variable configuration - -The `organization` required variable sets the GitHub organization where repositories will be created, and is used to configure the Terraform provider. - -The `repositories` variable is where you configure which repositories to create, whether initial population of files is desired, and which repository is used to host modules. - -This is an example that creates repositories for stages 00 and 01, defines an existing repositories as the source for modules, and populates initial files for stages 00, 01, and 02: - -```tfvars -organization = "ludomagno" -repositories = { - fast_00_bootstrap = { - create_options = { - description = "FAST bootstrap." - features = { - issues = true - } - } - populate_from = "../../stages/00-bootstrap" - } - fast_01_resman = { - create_options = { - description = "FAST resource management." - features = { - issues = true - } - } - populate_from = "../../stages/01-resman" - } - fast_02_networking = { - populate_from = "../../stages/02-networking-peering" - } - fast_modules = { - has_modules = true - } -} -``` - -The `create_options` repository attribute controls creation: if the attribute is not present, the repository is assumed to be already existing. - -Initial population depends on a modules repository being configured, identified by the `has_modules` attribute, and on `populate_from` attributes in each repository where population is required, pointing to the folder holding the files to be committed. - -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. - -## Modules secret - -When initial population is configured for a repository, this stage also adds a secret with the private key used to authenticate against the modules repository. This matches the configuration of the GitHub workflow files created for each FAST stage when CI/CD is enabled. - - - - -## Files - -| 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 | -| [outputs.tf](./outputs.tf) | Module outputs. | | -| [providers.tf](./providers.tf) | Provider configuration. | | -| [variables.tf](./variables.tf) | Module variables. | | - -## Variables - -| name | description | type | required | default | -|---|---|:---:|:---:|:---:| -| [organization](variables.tf#L34) | GitHub organization. | string | ✓ | | -| [commmit_config](variables.tf#L17) | Configure commit metadata. | object({…}) | | {} | -| [modules_ref](variables.tf#L28) | Optional git ref used in module sources. | string | | null | -| [repositories](variables.tf#L39) | Repositories to create. | map(object({…})) | | {} | - -## Outputs - -| name | description | sensitive | -|---|---|:---:| -| [clone](outputs.tf#L17) | Clone repository commands. | | - - diff --git a/fast/extras/README.md b/fast/extras/README.md index 121fa4b0..9213224c 100644 --- a/fast/extras/README.md +++ b/fast/extras/README.md @@ -2,4 +2,4 @@ This folder contains additional helper stages for FAST, which can be used to simplify specific operational tasks: -- [GitHub repository management](./00-cicd-github/) +- [GitHub repository management](./0-cicd-github/) diff --git a/fast/stage-links.sh b/fast/stage-links.sh new file mode 100755 index 00000000..79d1973f --- /dev/null +++ b/fast/stage-links.sh @@ -0,0 +1,114 @@ +#!/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. + +if [ $# -eq 0 ]; then + echo "Error: no folder or GCS bucket specified. Use -h or --help for usage." + exit 1 +fi + +if [[ "$1" == "-h" || "$1" == "--help" ]]; then + cat < $MESSAGE <---" +fi diff --git a/fast/stages-multitenant/0-bootstrap-tenant/IAM.md b/fast/stages-multitenant/0-bootstrap-tenant/IAM.md new file mode 100644 index 00000000..543a82ab --- /dev/null +++ b/fast/stages-multitenant/0-bootstrap-tenant/IAM.md @@ -0,0 +1,49 @@ +# IAM bindings reference + +Legend: + additive, conditional. + +## Organization [org_id #0] + +| members | roles | +|---|---| +|tn0-admins
group|[roles/orgpolicy.policyAdmin](https://cloud.google.com/iam/docs/understanding-roles#orgpolicy.policyAdmin) +
[roles/resourcemanager.organizationViewer](https://cloud.google.com/iam/docs/understanding-roles#resourcemanager.organizationViewer) +| +|tn0-gke-dev-0
serviceAccount|[roles/orgpolicy.policyAdmin](https://cloud.google.com/iam/docs/understanding-roles#orgpolicy.policyAdmin) +| +|tn0-gke-prod-0
serviceAccount|[roles/orgpolicy.policyAdmin](https://cloud.google.com/iam/docs/understanding-roles#orgpolicy.policyAdmin) +| +|tn0-networking-0
serviceAccount|[roles/orgpolicy.policyAdmin](https://cloud.google.com/iam/docs/understanding-roles#orgpolicy.policyAdmin) +| +|tn0-pf-dev-0
serviceAccount|[roles/orgpolicy.policyAdmin](https://cloud.google.com/iam/docs/understanding-roles#orgpolicy.policyAdmin) +| +|tn0-pf-prod-0
serviceAccount|[roles/orgpolicy.policyAdmin](https://cloud.google.com/iam/docs/understanding-roles#orgpolicy.policyAdmin) +| +|tn0-resman-0
serviceAccount|[roles/orgpolicy.policyAdmin](https://cloud.google.com/iam/docs/understanding-roles#orgpolicy.policyAdmin) +| +|tn0-sandbox-0
serviceAccount|[roles/orgpolicy.policyAdmin](https://cloud.google.com/iam/docs/understanding-roles#orgpolicy.policyAdmin) +| +|tn0-security-0
serviceAccount|[roles/orgpolicy.policyAdmin](https://cloud.google.com/iam/docs/understanding-roles#orgpolicy.policyAdmin) +| +|tn0-teams-0
serviceAccount|[roles/orgpolicy.policyAdmin](https://cloud.google.com/iam/docs/understanding-roles#orgpolicy.policyAdmin) +| + +## Folder test tenant 0 [#1] + +| members | roles | +|---|---| +|tn0-admins
group|[roles/compute.xpnAdmin](https://cloud.google.com/iam/docs/understanding-roles#compute.xpnAdmin)
[roles/logging.admin](https://cloud.google.com/iam/docs/understanding-roles#logging.admin)
[roles/owner](https://cloud.google.com/iam/docs/understanding-roles#owner)
[roles/resourcemanager.folderAdmin](https://cloud.google.com/iam/docs/understanding-roles#resourcemanager.folderAdmin)
[roles/resourcemanager.projectCreator](https://cloud.google.com/iam/docs/understanding-roles#resourcemanager.projectCreator) | +|tn0-networking-0
serviceAccount|[roles/compute.xpnAdmin](https://cloud.google.com/iam/docs/understanding-roles#compute.xpnAdmin) | +|tn0-resman-0
serviceAccount|[roles/compute.xpnAdmin](https://cloud.google.com/iam/docs/understanding-roles#compute.xpnAdmin)
[roles/logging.admin](https://cloud.google.com/iam/docs/understanding-roles#logging.admin)
[roles/owner](https://cloud.google.com/iam/docs/understanding-roles#owner)
[roles/resourcemanager.folderAdmin](https://cloud.google.com/iam/docs/understanding-roles#resourcemanager.folderAdmin)
[roles/resourcemanager.projectCreator](https://cloud.google.com/iam/docs/understanding-roles#resourcemanager.projectCreator) | + +## Project prod-iac-core-0 + +| members | roles | +|---|---| +|tn0-bootstrap-1
serviceAccount|[roles/logging.logWriter](https://cloud.google.com/iam/docs/understanding-roles#logging.logWriter) +| + +## Project tn0-audit-logs-0 + +| members | roles | +|---|---| +|f260055713332-284719
serviceAccount|[roles/logging.bucketWriter](https://cloud.google.com/iam/docs/understanding-roles#logging.bucketWriter) +| +|prod-resman-0
serviceAccount|[roles/owner](https://cloud.google.com/iam/docs/understanding-roles#owner) | +|tn0-resman-0
serviceAccount|[roles/owner](https://cloud.google.com/iam/docs/understanding-roles#owner) | + +## Project tn0-iac-core-0 + +| members | roles | +|---|---| +|tn0-admins
group|[roles/iam.serviceAccountTokenCreator](https://cloud.google.com/iam/docs/understanding-roles#iam.serviceAccountTokenCreator)
[roles/iam.workloadIdentityPoolAdmin](https://cloud.google.com/iam/docs/understanding-roles#iam.workloadIdentityPoolAdmin) | +|SERVICE_IDENTITY_service-networking
serviceAccount|[roles/servicenetworking.serviceAgent](https://cloud.google.com/iam/docs/understanding-roles#servicenetworking.serviceAgent) +| +|prod-resman-0
serviceAccount|[roles/owner](https://cloud.google.com/iam/docs/understanding-roles#owner) | +|tn0-resman-0
serviceAccount|[roles/cloudbuild.builds.editor](https://cloud.google.com/iam/docs/understanding-roles#cloudbuild.builds.editor)
[roles/iam.serviceAccountAdmin](https://cloud.google.com/iam/docs/understanding-roles#iam.serviceAccountAdmin)
[roles/iam.workloadIdentityPoolAdmin](https://cloud.google.com/iam/docs/understanding-roles#iam.workloadIdentityPoolAdmin)
[roles/owner](https://cloud.google.com/iam/docs/understanding-roles#owner)
[roles/source.admin](https://cloud.google.com/iam/docs/understanding-roles#source.admin)
[roles/storage.admin](https://cloud.google.com/iam/docs/understanding-roles#storage.admin) | diff --git a/fast/stages-multitenant/0-bootstrap-tenant/README.md b/fast/stages-multitenant/0-bootstrap-tenant/README.md new file mode 100644 index 00000000..bbeaf9f6 --- /dev/null +++ b/fast/stages-multitenant/0-bootstrap-tenant/README.md @@ -0,0 +1,210 @@ +# Tenant bootstrap + +The primary purpose of this stage is to decouple a single tenant from centrally managed resources in the organization, so that subsequent management of the tenant's own hierarchy and resources can be implemented with a high degree of autonomy. + +It is logically equivalent to organization-level bootstrap as it's concerned with setting up IAM bindings on a root node and creating supporting projects attached to it, but it depends on the organization-level resource management stage and uses the same service account and permissions since it operates at the hierarchy level (folders, tags, organization policies). + +The resources and policies managed here are: + +- the tag value in the `tenant` key used in IAM conditions +- the billing IAM bindings for the tenant-specific automation service accounts +- the organization-level IAM binding that allows conditional managing of org policies on the tenant folder +- the top-level tenant folder which acts as the root of the tenant's hierarchy +- any organization policy that needs to be set for the tenant on its root folder +- the tenant automation and logging projects +- service accounts for all tenant stages +- GCS buckets for bootstrap and resource management state +- optional CI/CD setup for this and the resource management tenant stages +- tenant-specific Workload Identity Federation pool and providers (planned) + +One notable difference compared to organization-level bootstrap is the creation of service accounts for all tenant stages: this is done here so that Billing and Organization Policy Admin bindings can be set, leveraging permissions of the org-level resman service account which is used to run this stage. Doing this here avoids the need to grant broad scoped permissions on the organization to tenant-level service accounts, and effectively decouples the tenant from the organization. + +The following diagram is a high level reference of what this stage manages, showing one hypothetical tenant (additional tenants require additional instances of this stage being deployed): + +```mermaid +%%{init: {'theme':'base'}}%% +classDiagram + Organization~🏢~ -- Tenant 0~📁~ + Tenant 0~📁~ -- tn0_automation + Tenant 0~📁~ -- tn0_logging + class Organization~🏢~ { + - tag value + - IAM bindings() + - org policies() + } + class Tenant 0~📁~ { + - log sinks + - IAM bindings() + - tag bindings() + } + class tn0_automation { + - GCS buckets + - service accounts + - optional CI/CD + - IAM bindings() + } + class tn0_logging { + - log sink destinations + } +``` + +As most of the features of this stage follow the same design and configurations of the [organization-level bootstrap stage](../../stages/0-bootstrap/), we will only focus on the tenant-specific configuration in this document. + +## Naming + +This stage sets the prefix used to name tenant resources, and passes it downstream to the other tenant stages together with the other globals needed by the tenant. The default is to append the tenant short name (a 3 or 4 letter acronym or abbreviation) to the organization-level prefix, if that is not desired this can be changed by editing local definitions in the `main.tf` file. Just be aware that some resources have name length constraints. + +## How to run this stage + +The tenant bootstrap stage is the effective boundary between organization and tenant-level resources: it uses the same inputs as the organization-level resource management stage, and produces outputs which provide the needed context to all other tenant stages. + +### Output files and cross-stage variables + +As mentioned above, the organization-level set of output files are used here with one exception: the provider file is different since state is specific to this stage. The `stage-links.sh` script can be used to get the commands needed for the provider and output files, just pass a single argument with your FAST output files folder path, or GCS bucket URI: + +```bash +../../stage-links.sh ~/fast-config +``` + +The script output can be copy/pasted to a terminal: + +```bash +# copy and paste the following commands for '0-bootstrap-tenant' + +cp ~/fast-config/providers/0-bootstrap-tenant-providers.tf ./ +ln -s ~/fast-config/tfvars/globals.auto.tfvars.json ./ +ln -s ~/fast-config/tfvars/0-bootstrap.auto.tfvars.json ./ +ln -s ~/fast-config/tfvars/1-resman.auto.tfvars.json ./ + +# ---> remember to set the prefix in the provider file <--- +``` + +As shown in the script output above, the provider file is a template used as a source for potentially multiple tenant installations, so it needs to be specifically configured for this tenant by setting the backend `prefix` to a unique string so that the Terraform state file will not overlap with other tenants. Open it in an editor and perform the change before proceeding. + +### Global overrides + +The globals variable file linekd above contains definition which were set for the organization, for example the locations used for log sink destinations. These might not be correct for each tenant, so this stage allows overriding them via the tenant configuration variable described in the next section. + +### Tenant-level configuration + +The tenant configuration resides in the `tenant_config` variable, this is an example configuration for a tenant with comments explaining the different choices that need to be made: + +```hcl +tenant_config = { + # used for the top-level folder name + descriptive_name = "My First Tenant" + # tenant-specific groups, only the admin group is required + # the organization domain is automatically added after the group name + groups = { + gcp-admins = "tn01-admins" + # gcp-devops = "tn01-devops" + # gcp-network-admins = "tn01-networking" + # gcp-security-admins = "tn01-security" + } + # the 3 or 4 letter acronym or abbreviation used in resource names + short_name = "tn01" + # optional CI/CD configuration, refer to the org-level stages for information + # cicd = { + # branch = null + # identity_provider = "foo-provider" + # name = "myorg/tn01-bootstrap" + # type = "github" + # } + # optional group-level IAM bindings to add to the top-level folder + # group_iam = { + # tn01-support = ["roles/viewer"] + # } + # optional IAM bindings to add to the top-level folder + # iam = { + # "roles/logging.admin" = [ + # "serviceAccount:foo@myprj.iam.gserviceaccount.com" + # ] + # } + # optional location overrides to global locations + # locations = { + # bq = null + # gcs = null + # logging = null + # pubsub = null + # } + # optional folder ids for automation and logging project folders, typically + # added in later stages and entered here once created + # project_parent_ids = { + # automation = "folders/012345678" + # logging = "folders/0123456789" + # } +} +# tftest skip +``` + +Configure the tenant variable in a tfvars file for this stage. A few minor points worth noting: + +- the administrator group is the only one required here, specifying other groups only has the effect of populating the output file with group names for reuse in later stages +- the `iam` variable is merged with the IAM bindings for service accounts in the `main.tf` file, which take precedence; if a role specified in the variable is ignored, that's probably the case +- locations can be overridden at the attribute level, there's no need to specify those that are equal to the ones in the organization globals file + +### Running the stage + +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. + +### TODO + +- [ ] tenant-level Workload Identity Federation pool and providers configuration +- [ ] tenant-level logging project and sinks + + + + +## Files + +| name | description | modules | resources | +|---|---|---|---| +| [automation-sas.tf](./automation-sas.tf) | Tenant automation stage 2 and 3 service accounts. | iam-service-account | google_organization_iam_member | +| [automation.tf](./automation.tf) | Tenant automation project and resources. | gcs · iam-service-account · project | | +| [billing.tf](./billing.tf) | Billing roles for standalone billing accounts. | | 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 | | +| [main.tf](./main.tf) | Module-level locals and resources. | folder | | +| [organization.tf](./organization.tf) | Organization tag and conditional IAM grant. | organization | google_organization_iam_member · google_tags_tag_value_iam_member | +| [outputs-files.tf](./outputs-files.tf) | Output files persistence to local filesystem. | | local_file | +| [outputs-gcs.tf](./outputs-gcs.tf) | Output files persistence to automation GCS bucket. | | google_storage_bucket_object | +| [outputs.tf](./outputs.tf) | Module outputs. | | | +| [variables.tf](./variables.tf) | Module variables. | | | + +## Variables + +| 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 | | + +## Outputs + +| name | description | sensitive | consumers | +|---|---|:---:|---| +| [cicd_workflows](outputs.tf#L102) | CI/CD workflows for tenant bootstrap and resource management stages. | ✓ | | +| [federated_identity](outputs.tf#L108) | Workload Identity Federation pool and providers. | | | +| [provider](outputs.tf#L118) | Terraform provider file for tenant resource management stage. | ✓ | stage-01 | +| [tenant_resources](outputs.tf#L125) | Tenant-level resources. | | | +| [tfvars](outputs.tf#L136) | Terraform variable files for the following tenant stages. | ✓ | | + + diff --git a/fast/stages-multitenant/0-bootstrap-tenant/automation-sas.tf b/fast/stages-multitenant/0-bootstrap-tenant/automation-sas.tf new file mode 100644 index 00000000..cae14093 --- /dev/null +++ b/fast/stages-multitenant/0-bootstrap-tenant/automation-sas.tf @@ -0,0 +1,135 @@ +/** + * 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 Tenant automation stage 2 and 3 service accounts. + +locals { + branch_sas = { + dp-dev = { + condition = join(" && ", [ + "resource.matchTag('${local.tag_keys.context}', 'data')", + "resource.matchTag('${local.tag_keys.environment}', 'development')" + ]) + description = "data platform dev" + flag = "data_platform" + } + dp-prod = { + condition = join(" && ", [ + "resource.matchTag('${local.tag_keys.context}', 'data')", + "resource.matchTag('${local.tag_keys.environment}', 'production')" + ]) + description = "data platform prod" + flag = "data_platform" + } + gke-dev = { + condition = join(" && ", [ + "resource.matchTag('${local.tag_keys.context}', 'gke')", + "resource.matchTag('${local.tag_keys.environment}', 'development')" + ]) + description = "GKE dev" + flag = "gke" + } + gke-prod = { + condition = join(" && ", [ + "resource.matchTag('${local.tag_keys.context}', 'gke')", + "resource.matchTag('${local.tag_keys.environment}', 'production')" + ]) + description = "GKE prod" + flag = "gke" + } + networking = { + condition = "resource.matchTag('${local.tag_keys.context}', 'networking')" + description = "networking" + flag = "-" + } + pf-dev = { + condition = "resource.matchTag('${local.tag_keys.environment}', 'development')" + description = "project factory dev" + flag = "project_factory" + } + pf-prod = { + condition = "resource.matchTag('${local.tag_keys.environment}', 'production')" + description = "project factory prod" + flag = "project_factory" + } + sandbox = { + condition = "resource.matchTag('${local.tag_keys.context}', 'sandbox')" + description = "sandbox" + flag = "sandbox" + } + security = { + condition = "resource.matchTag('${local.tag_keys.context}', 'security')" + description = "security" + flag = "-" + } + teams = { + condition = "resource.matchTag('${local.tag_keys.context}', 'teams')" + description = "teams" + flag = "teams" + } + } +} + +module "automation-tf-resman-sa-stage2-3" { + source = "../../../modules/iam-service-account" + for_each = { + for k, v in local.branch_sas : + k => v if lookup(local.fast_features, v.flag, true) + } + project_id = module.automation-project.project_id + name = "${each.key}-0" + display_name = "Terraform ${each.value.description} service account." + prefix = local.prefix + iam_billing_roles = !var.billing_account.is_org_level ? { + (var.billing_account.id) = [ + "roles/billing.user", "roles/billing.costsManager" + ] + } : {} + iam_organization_roles = var.billing_account.is_org_level ? { + (var.organization.id) = [ + "roles/billing.user", "roles/billing.costsManager" + ] + } : {} +} + +# assign org policy admin with a tag-based condition to stage 2 and 3 SAs + +resource "google_organization_iam_member" "org_policy_admin_stage2_3" { + for_each = { + for k, v in module.automation-tf-resman-sa-stage2-3 : k => v.iam_email + } + org_id = var.organization.id + role = "roles/orgpolicy.policyAdmin" + member = each.value + condition { + title = "org_policy_tag_${var.tenant_config.short_name}_${each.key}_scoped" + description = join("", [ + "Org policy tag scoped grant for tenant ${var.tenant_config.short_name} ", + local.branch_sas[each.key].description + ]) + expression = join(" && ", [ + local.iam_tenant_condition, local.branch_sas[each.key].condition + ]) + } +} + +# assign custom tenant network admin role to networking SA + +resource "google_organization_iam_member" "tenant_network_admin" { + org_id = var.organization.id + role = var.custom_roles.tenant_network_admin + member = module.automation-tf-resman-sa-stage2-3["networking"].iam_email +} diff --git a/fast/stages-multitenant/0-bootstrap-tenant/automation.tf b/fast/stages-multitenant/0-bootstrap-tenant/automation.tf new file mode 100644 index 00000000..9684e7ca --- /dev/null +++ b/fast/stages-multitenant/0-bootstrap-tenant/automation.tf @@ -0,0 +1,141 @@ +/** + * 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 Tenant automation project and resources. + +module "automation-project" { + source = "../../../modules/project" + billing_account = var.billing_account.id + name = "iac-core-0" + parent = coalesce( + var.project_parent_ids.automation, + module.tenant-folder.id + ) + prefix = local.prefix + # human (groups) IAM bindings + group_iam = { + (local.groups.gcp-admins) = [ + "roles/iam.serviceAccountAdmin", + "roles/iam.serviceAccountTokenCreator", + ] + (local.groups.gcp-admins) = [ + "roles/iam.serviceAccountTokenCreator", + "roles/iam.workloadIdentityPoolAdmin" + ] + } + # machine (service accounts) IAM bindings + iam = { + "roles/owner" = [ + module.automation-tf-resman-sa.iam_email, + "serviceAccount:${local.resman_sa}" + ] + "roles/cloudbuild.builds.editor" = [ + module.automation-tf-resman-sa.iam_email + ] + "roles/iam.serviceAccountAdmin" = [ + module.automation-tf-resman-sa.iam_email + ] + "roles/iam.workloadIdentityPoolAdmin" = [ + module.automation-tf-resman-sa.iam_email + ] + "roles/source.admin" = [ + module.automation-tf-resman-sa.iam_email + ] + "roles/storage.admin" = [ + module.automation-tf-resman-sa.iam_email + ] + } + services = [ + "accesscontextmanager.googleapis.com", + "bigquery.googleapis.com", + "bigqueryreservation.googleapis.com", + "bigquerystorage.googleapis.com", + "billingbudgets.googleapis.com", + "cloudbilling.googleapis.com", + "cloudbuild.googleapis.com", + "cloudkms.googleapis.com", + "cloudresourcemanager.googleapis.com", + "container.googleapis.com", + "compute.googleapis.com", + "container.googleapis.com", + "essentialcontacts.googleapis.com", + "iam.googleapis.com", + "iamcredentials.googleapis.com", + "orgpolicy.googleapis.com", + "pubsub.googleapis.com", + "servicenetworking.googleapis.com", + "serviceusage.googleapis.com", + "sourcerepo.googleapis.com", + "stackdriver.googleapis.com", + "storage-component.googleapis.com", + "storage.googleapis.com", + "sts.googleapis.com" + ] +} + +# output files bucket + +module "automation-tf-output-gcs" { + source = "../../../modules/gcs" + project_id = module.automation-project.project_id + name = "iac-core-outputs-0" + prefix = local.prefix + location = local.locations.gcs + storage_class = local.gcs_storage_class + versioning = true +} + +# resource management stage bucket and service account + +module "automation-tf-resman-gcs" { + source = "../../../modules/gcs" + project_id = module.automation-project.project_id + name = "iac-core-resman-0" + prefix = local.prefix + location = local.locations.gcs + storage_class = local.gcs_storage_class + versioning = true + iam = { + "roles/storage.objectAdmin" = [module.automation-tf-resman-sa.iam_email] + } +} + +module "automation-tf-resman-sa" { + source = "../../../modules/iam-service-account" + project_id = module.automation-project.project_id + name = "resman-0" + display_name = "Terraform stage 1 resman service account." + prefix = local.prefix + # allow SA used by CI/CD workflow to impersonate this SA + iam = { + "roles/iam.serviceAccountTokenCreator" = compact([ + try(module.automation-tf-cicd-sa-resman["0"].iam_email, null) + ]) + } + iam_billing_roles = !var.billing_account.is_org_level ? { + (var.billing_account.id) = [ + "roles/billing.admin", "roles/billing.costsManager" + ] + } : {} + iam_organization_roles = var.billing_account.is_org_level ? { + (var.organization.id) = [ + "roles/billing.admin", "roles/billing.costsManager" + ] + } : {} + iam_storage_roles = { + (module.automation-tf-output-gcs.name) = ["roles/storage.admin"] + } +} diff --git a/fast/stages-multitenant/0-bootstrap-tenant/billing.tf b/fast/stages-multitenant/0-bootstrap-tenant/billing.tf new file mode 100644 index 00000000..77c26b91 --- /dev/null +++ b/fast/stages-multitenant/0-bootstrap-tenant/billing.tf @@ -0,0 +1,39 @@ +/** + * 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 Billing roles for standalone billing accounts. + +# 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 + ]) + 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 + ]) + billing_account_id = var.billing_account.id + role = "roles/billing.costsManager" + member = each.key +} diff --git a/fast/stages-multitenant/0-bootstrap-tenant/cicd.tf b/fast/stages-multitenant/0-bootstrap-tenant/cicd.tf new file mode 100644 index 00000000..a25215af --- /dev/null +++ b/fast/stages-multitenant/0-bootstrap-tenant/cicd.tf @@ -0,0 +1,223 @@ +/** + * 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 Workload Identity Federation configurations for CI/CD. + +locals { + _file_prefix = "tenants/${var.tenant_config.short_name}" + # derive identity pool names from identity providers for easy reference + cicd_identity_pools = { + for k, v in local.cicd_identity_providers : + k => split("/providers/", v.name)[0] + } + # merge org-level and tenant-level identity providers + cicd_identity_providers = merge( + var.automation.federated_identity_providers, + { + for k, v in google_iam_workload_identity_pool_provider.default : + k => { + issuer = local.identity_providers[k].issuer + issuer_uri = local.identity_providers[k].issuer_uri + name = v.name + principal_tpl = local.identity_providers[k].principal_tpl + principalset_tpl = local.identity_providers[k].principalset_tpl + } + }) + # filter CI/CD repositories to only keep valid ones + cicd_repositories = { + for k, v in coalesce(var.cicd_repositories, {}) : k => v + if( + v != null + && + ( + try(v.type, null) == "sourcerepo" + || + contains( + keys(local.cicd_identity_providers), + coalesce(try(v.identity_provider, null), ":") + ) + ) + && + fileexists( + format("${path.module}/templates/workflow-%s.yaml", try(v.type, "")) + ) + ) + } +} + +# tenant bootstrap runs in the org scope and uses top-level automation project + +module "automation-tf-cicd-repo-bootstrap" { + source = "../../../modules/source-repository" + for_each = { + for k, v in local.cicd_repositories : 0 => v + if k == "bootstrap" && try(v.type, null) == "sourcerepo" + } + project_id = var.automation.project_id + name = each.value.name + iam = { + "roles/source.admin" = [ + local.resman_sa + ] + "roles/source.reader" = [ + module.automation-tf-cicd-sa-bootstrap["0"].iam_email + ] + } + triggers = { + "fast-${var.tenant_config.short_name}-0-bootstrap" = { + filename = ".cloudbuild/workflow.yaml" + included_files = ["**/*tf", ".cloudbuild/workflow.yaml"] + service_account = module.automation-tf-cicd-sa-bootstrap["0"].id + substitutions = {} + template = { + project_id = null + branch_name = each.value.branch + repo_name = each.value.name + tag_name = null + } + } + } +} + +module "automation-tf-cicd-sa-bootstrap" { + source = "../../../modules/iam-service-account" + for_each = { + for k, v in local.cicd_repositories : 0 => v + if k == "bootstrap" && try(v.type, null) != null + } + project_id = var.automation.project_id + name = "bootstrap-1" + display_name = "Terraform CI/CD ${var.tenant_config.short_name} bootstrap." + prefix = local.prefix + iam = ( + each.value.type == "sourcerepo" + # used directly from the cloud build trigger for source repos + ? {} + # impersonated via workload identity federation for external repos + : { + "roles/iam.workloadIdentityUser" = [ + each.value.branch == null + ? format( + local.cicd_identity_providers[each.value.identity_provider].principalset_tpl, + local.cicd_identity_pools[each.value.identity_provider], + each.value.name + ) + : format( + local.cicd_identity_providers[each.value.identity_provider].principal_tpl, + local.cicd_identity_pools[each.value.identity_provider], + each.value.name, + each.value.branch + ) + ] + } + ) + iam_project_roles = { + (var.automation.project_id) = ["roles/logging.logWriter"] + } + iam_storage_roles = { + (var.automation.outputs_bucket) = ["roles/storage.objectViewer"] + } +} + +module "automation-tf-org-resman-sa" { + source = "../../../modules/iam-service-account" + for_each = { + for k, v in local.cicd_repositories : 0 => v + if k == "bootstrap" && try(v.type, null) != null + } + project_id = var.automation.project_id + name = local.resman_sa + service_account_create = false + iam_additive = { + "roles/iam.serviceAccountTokenCreator" = compact([ + try(module.automation-tf-cicd-sa-bootstrap["0"].iam_email, null) + ]) + } +} + +# tenant resman runs in the tenant scope and uses its own automation project + +module "automation-tf-cicd-repo-resman" { + source = "../../../modules/source-repository" + for_each = { + for k, v in local.cicd_repositories : 0 => v + if k == "resman" && try(v.type, null) == "sourcerepo" + } + project_id = module.automation-project.project_id + name = each.value.name + iam = { + "roles/source.admin" = [ + module.automation-tf-resman-sa.iam_email + ] + "roles/source.reader" = [ + module.automation-tf-cicd-sa-resman["0"].iam_email + ] + } + triggers = { + fast-1-resman = { + filename = ".cloudbuild/workflow.yaml" + included_files = ["**/*tf", ".cloudbuild/workflow.yaml"] + service_account = module.automation-tf-cicd-sa-resman["0"].id + substitutions = {} + template = { + project_id = null + branch_name = each.value.branch + repo_name = each.value.name + tag_name = null + } + } + } +} + +module "automation-tf-cicd-sa-resman" { + source = "../../../modules/iam-service-account" + for_each = { + for k, v in local.cicd_repositories : 0 => v + if k == "resman" && try(v.type, null) != null + } + project_id = module.automation-project.project_id + name = "resman-1" + display_name = "Terraform CI/CD resman." + prefix = local.prefix + iam = ( + each.value.type == "sourcerepo" + # used directly from the cloud build trigger for source repos + ? {} + # impersonated via workload identity federation for external repos + : { + "roles/iam.workloadIdentityUser" = [ + each.value.branch == null + ? format( + local.cicd_identity_providers[each.value.identity_provider].principalset_tpl, + local.cicd_identity_pools[each.value.identity_provider], + each.value.name + ) + : format( + local.cicd_identity_providers[each.value.identity_provider].principal_tpl, + local.cicd_identity_pools[each.value.identity_provider], + each.value.name, + each.value.branch + ) + ] + } + ) + iam_project_roles = { + (module.automation-project.project_id) = ["roles/logging.logWriter"] + } + iam_storage_roles = { + (module.automation-tf-output-gcs.name) = ["roles/storage.objectViewer"] + } +} diff --git a/fast/stages-multitenant/0-bootstrap-tenant/diagram.svg b/fast/stages-multitenant/0-bootstrap-tenant/diagram.svg new file mode 100644 index 00000000..4090c7b0 --- /dev/null +++ b/fast/stages-multitenant/0-bootstrap-tenant/diagram.svg @@ -0,0 +1,597 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/fast/stages-multitenant/0-bootstrap-tenant/identity-providers.tf b/fast/stages-multitenant/0-bootstrap-tenant/identity-providers.tf new file mode 100644 index 00000000..3f8499b7 --- /dev/null +++ b/fast/stages-multitenant/0-bootstrap-tenant/identity-providers.tf @@ -0,0 +1,96 @@ +/** + * 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 Workload Identity Federation provider definitions. + +locals { + identity_providers = { + for k, v in var.federated_identity_providers : k => merge( + v, + lookup(local.identity_providers_defs, v.issuer, {}) + ) + } + identity_providers_defs = { + # https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect + github = { + attribute_mapping = { + "google.subject" = "assertion.sub" + "attribute.sub" = "assertion.sub" + "attribute.actor" = "assertion.actor" + "attribute.repository" = "assertion.repository" + "attribute.repository_owner" = "assertion.repository_owner" + "attribute.ref" = "assertion.ref" + } + issuer_uri = "https://token.actions.githubusercontent.com" + principal_tpl = "principal://iam.googleapis.com/%s/subject/repo:%s:ref:refs/heads/%s" + principalset_tpl = "principalSet://iam.googleapis.com/%s/attribute.repository/%s" + } + # https://docs.gitlab.com/ee/ci/cloud_services/index.html#how-it-works + gitlab = { + attribute_mapping = { + "google.subject" = "assertion.sub" + "attribute.sub" = "assertion.sub" + "attribute.environment" = "assertion.environment" + "attribute.environment_protected" = "assertion.environment_protected" + "attribute.namespace_id" = "assertion.namespace_id" + "attribute.namespace_path" = "assertion.namespace_path" + "attribute.pipeline_id" = "assertion.pipeline_id" + "attribute.pipeline_source" = "assertion.pipeline_source" + "attribute.project_id" = "assertion.project_id" + "attribute.project_path" = "assertion.project_path" + "attribute.repository" = "assertion.project_path" + "attribute.ref" = "assertion.ref" + "attribute.ref_protected" = "assertion.ref_protected" + "attribute.ref_type" = "assertion.ref_type" + } + allowed_audiences = ["https://gitlab.com"] + issuer_uri = "https://gitlab.com" + principal_tpl = "principalSet://iam.googleapis.com/%s/attribute.sub/project_path:%s:ref_type:branch:ref:%s" + principalset_tpl = "principalSet://iam.googleapis.com/%s/attribute.repository/%s" + } + } +} + +resource "google_iam_workload_identity_pool" "default" { + provider = google-beta + count = length(local.identity_providers) > 0 ? 1 : 0 + project = module.automation-project.project_id + workload_identity_pool_id = "${var.prefix}-bootstrap" +} + +resource "google_iam_workload_identity_pool_provider" "default" { + provider = google-beta + for_each = local.identity_providers + project = module.automation-project.project_id + workload_identity_pool_id = ( + google_iam_workload_identity_pool.default.0.workload_identity_pool_id + ) + workload_identity_pool_provider_id = "${var.prefix}-bootstrap-${each.key}" + attribute_condition = each.value.attribute_condition + attribute_mapping = each.value.attribute_mapping + oidc { + allowed_audiences = ( + try(each.value.custom_settings.allowed_audiences, null) != null + ? each.value.custom_settings.allowed_audiences + : try(each.value.allowed_audiences, null) + ) + issuer_uri = ( + try(each.value.custom_settings.issuer_uri, null) != null + ? each.value.custom_settings.issuer_uri + : try(each.value.issuer_uri, null) + ) + } +} diff --git a/fast/stages-multitenant/0-bootstrap-tenant/log-export.tf b/fast/stages-multitenant/0-bootstrap-tenant/log-export.tf new file mode 100644 index 00000000..b0bf115a --- /dev/null +++ b/fast/stages-multitenant/0-bootstrap-tenant/log-export.tf @@ -0,0 +1,94 @@ +/** + * 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 Audit log project and sink. + +locals { + log_sink_destinations = merge( + # use the same dataset for all sinks with `bigquery` as destination + { for k, v in var.log_sinks : k => module.log-export-dataset.0 if v.type == "bigquery" }, + # use the same gcs bucket for all sinks with `storage` as destination + { for k, v in var.log_sinks : k => module.log-export-gcs.0 if v.type == "storage" }, + # use separate pubsub topics and logging buckets for sinks with + # destination `pubsub` and `logging` + module.log-export-pubsub, + module.log-export-logbucket + ) + log_types = toset([for k, v in var.log_sinks : v.type]) +} + +module "log-export-project" { + source = "../../../modules/project" + billing_account = var.billing_account.id + name = "audit-logs-0" + parent = coalesce( + var.project_parent_ids.logging, + module.tenant-folder.id + ) + prefix = local.prefix + iam = { + "roles/owner" = [ + module.automation-tf-resman-sa.iam_email, + "serviceAccount:${local.resman_sa}" + ] + } + services = [ + # "cloudresourcemanager.googleapis.com", + # "iam.googleapis.com", + # "serviceusage.googleapis.com", + "bigquery.googleapis.com", + "storage.googleapis.com", + "stackdriver.googleapis.com" + ] +} + +# one log export per type, with conditionals to skip those not needed + +module "log-export-dataset" { + source = "../../../modules/bigquery-dataset" + count = contains(local.log_types, "bigquery") ? 1 : 0 + project_id = module.log-export-project.project_id + id = "audit_export" + friendly_name = "Audit logs export." + location = var.locations.bq +} + +module "log-export-gcs" { + source = "../../../modules/gcs" + count = contains(local.log_types, "storage") ? 1 : 0 + project_id = module.log-export-project.project_id + name = "audit-logs-0" + prefix = local.prefix + location = var.locations.gcs + storage_class = local.gcs_storage_class +} + +module "log-export-logbucket" { + source = "../../../modules/logging-bucket" + for_each = toset([for k, v in var.log_sinks : k if v.type == "logging"]) + parent_type = "project" + parent = module.log-export-project.project_id + id = "audit-logs-${each.key}" + location = var.locations.logging +} + +module "log-export-pubsub" { + source = "../../../modules/pubsub" + for_each = toset([for k, v in var.log_sinks : k if v.type == "pubsub"]) + project_id = module.log-export-project.project_id + name = "audit-logs-${each.key}" + regions = var.locations.pubsub +} diff --git a/fast/stages-multitenant/0-bootstrap-tenant/main.tf b/fast/stages-multitenant/0-bootstrap-tenant/main.tf new file mode 100644 index 00000000..3a150594 --- /dev/null +++ b/fast/stages-multitenant/0-bootstrap-tenant/main.tf @@ -0,0 +1,100 @@ +/** + * 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 { + gcs_storage_class = ( + length(split("-", local.locations.gcs)) < 2 + ? "MULTI_REGIONAL" + : "REGIONAL" + ) + groups = { + for k, v in var.tenant_config.groups : + k => v == null ? null : "${v}@${var.organization.domain}" + } + fast_features = { + for k, v in var.tenant_config.fast_features : + k => v == null ? var.fast_features[k] : v + } + locations = { + for k, v in var.tenant_config.locations : + k => v == null || v == [] ? var.locations[k] : v + } + prefix = join("-", compact([var.prefix, var.tenant_config.short_name])) + resman_sa = ( + var.test_principal == null + ? data.google_client_openid_userinfo.resman-sa.0.email + : var.test_principal + ) +} + +data "google_client_openid_userinfo" "resman-sa" { + count = var.test_principal == null ? 1 : 0 +} + +module "tenant-folder" { + source = "../../../modules/folder" + parent = "organizations/${var.organization.id}" + name = var.tenant_config.descriptive_name + logging_sinks = { + for name, attrs in var.log_sinks : name => { + bq_partitioned_table = attrs.type == "bigquery" + destination = local.log_sink_destinations[name].id + filter = attrs.filter + type = attrs.type + } + } + tag_bindings = { + tenant = try( + module.organization.tag_values["${var.tag_names.tenant}/${var.tenant_config.short_name}"].id, + null + ) + } +} + +module "tenant-folder-iam" { + source = "../../../modules/folder" + id = module.tenant-folder.id + folder_create = false + group_iam = merge(var.group_iam, { + (local.groups.gcp-admins) = [ + "roles/logging.admin", + "roles/owner", + "roles/resourcemanager.folderAdmin", + "roles/resourcemanager.projectCreator", + "roles/compute.xpnAdmin" + ] + }) + iam = merge(var.iam, { + "roles/compute.xpnAdmin" = [ + module.automation-tf-resman-sa.iam_email, + module.automation-tf-resman-sa-stage2-3["networking"].iam_email + ] + "roles/logging.admin" = [ + module.automation-tf-resman-sa.iam_email + ] + "roles/resourcemanager.folderAdmin" = [ + module.automation-tf-resman-sa.iam_email + ] + "roles/resourcemanager.projectCreator" = [ + module.automation-tf-resman-sa.iam_email + ] + "roles/owner" = [ + module.automation-tf-resman-sa.iam_email + ] + }) + iam_additive = var.iam_additive + depends_on = [module.automation-project] +} diff --git a/fast/stages-multitenant/0-bootstrap-tenant/organization.tf b/fast/stages-multitenant/0-bootstrap-tenant/organization.tf new file mode 100644 index 00000000..46f8c0d4 --- /dev/null +++ b/fast/stages-multitenant/0-bootstrap-tenant/organization.tf @@ -0,0 +1,84 @@ +/** + * 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 Organization tag and conditional IAM grant. + +locals { + iam_tenant_condition = "resource.matchTag('${local.tag_keys.tenant}', '${var.tenant_config.short_name}')" + tag_keys = { + for k, v in var.tag_names : k => "${var.organization.id}/${v}" + } +} + +module "organization" { + source = "../../../modules/organization" + organization_id = "organizations/${var.organization.id}" + iam_additive = merge( + { + "roles/resourcemanager.organizationViewer" = [ + "group:${local.groups.gcp-admins}" + ] + }, + var.billing_account.is_org_level ? { + "roles/billing.admin" = [ + "group:${local.groups.gcp-admins}", + module.automation-tf-resman-sa.iam_email + ] + "roles/billing.costsManager" = ["group:${local.groups.gcp-admins}"] + } : {} + ) + tags = { + tenant = { + id = var.tag_keys.tenant + values = { + (var.tenant_config.short_name) = {} + } + } + } +} + +resource "google_tags_tag_value_iam_member" "resman_tag_user" { + for_each = var.tag_values + tag_value = each.value + role = "roles/resourcemanager.tagUser" + member = module.automation-tf-resman-sa.iam_email +} + +resource "google_tags_tag_value_iam_member" "admins_tag_viewer" { + for_each = var.tag_values + tag_value = each.value + role = "roles/resourcemanager.tagViewer" + member = "group:${local.groups.gcp-admins}" +} + +# assign org policy admin with a tag-based condition to admin group and stage 1 SA + +resource "google_organization_iam_member" "org_policy_admin_stage0" { + for_each = toset([ + "group:${local.groups.gcp-admins}", + module.automation-tf-resman-sa.iam_email + ]) + org_id = var.organization.id + role = "roles/orgpolicy.policyAdmin" + member = each.key + condition { + title = "org_policy_tag_${var.tenant_config.short_name}_scoped" + description = "Org policy tag scoped grant for tenant ${var.tenant_config.short_name}." + expression = local.iam_tenant_condition + } +} + +# tag-based condition for service accounts is in the automation-sa file diff --git a/fast/stages-multitenant/0-bootstrap-tenant/outputs-files.tf b/fast/stages-multitenant/0-bootstrap-tenant/outputs-files.tf new file mode 100644 index 00000000..28bec327 --- /dev/null +++ b/fast/stages-multitenant/0-bootstrap-tenant/outputs-files.tf @@ -0,0 +1,46 @@ +/** + * 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 Output files persistence to local filesystem. + +locals { + outputs_root = join("/", [ + try(pathexpand(var.outputs_location), ""), + "tenants", + var.tenant_config.short_name + ]) +} + +resource "local_file" "providers" { + count = var.outputs_location == null ? 0 : 1 + file_permission = "0644" + filename = "${local.outputs_root}/providers/1-resman-tenant-providers.tf" + content = try(local.provider, null) +} + +resource "local_file" "tfvars" { + count = var.outputs_location == null ? 0 : 1 + file_permission = "0644" + filename = "${local.outputs_root}/tfvars/0-bootstrap-tenant.auto.tfvars.json" + content = jsonencode(local.tfvars) +} + +resource "local_file" "workflows" { + for_each = var.outputs_location == null ? {} : local.cicd_workflows + file_permission = "0644" + filename = "${local.outputs_root}/workflows/${each.key}-${local.cicd_repositories[each.key].type}.yaml" + content = each.value +} diff --git a/fast/stages-multitenant/0-bootstrap-tenant/outputs-gcs.tf b/fast/stages-multitenant/0-bootstrap-tenant/outputs-gcs.tf new file mode 100644 index 00000000..5196bad7 --- /dev/null +++ b/fast/stages-multitenant/0-bootstrap-tenant/outputs-gcs.tf @@ -0,0 +1,41 @@ +/** + * 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 Output files persistence to automation GCS bucket. + +resource "google_storage_bucket_object" "providers" { + bucket = module.automation-tf-output-gcs.name + # provider suffix allows excluding via .gitignore when linked from stages + name = "providers/1-resman-tenant-providers.tf" + content = local.provider +} + +resource "google_storage_bucket_object" "tfvars" { + bucket = module.automation-tf-output-gcs.name + name = "tfvars/0-bootstrap-tenant.auto.tfvars.json" + content = jsonencode(local.tfvars) +} + +resource "google_storage_bucket_object" "workflows" { + for_each = local.cicd_workflows + bucket = ( + each.key == "bootstrap" + ? var.automation.outputs_bucket + : module.automation-tf-output-gcs.name + ) + name = "workflows/${each.key}-${local.cicd_repositories[each.key].type}.yaml" + content = each.value +} diff --git a/fast/stages-multitenant/0-bootstrap-tenant/outputs.tf b/fast/stages-multitenant/0-bootstrap-tenant/outputs.tf new file mode 100644 index 00000000..4f22ff63 --- /dev/null +++ b/fast/stages-multitenant/0-bootstrap-tenant/outputs.tf @@ -0,0 +1,140 @@ +/** + * 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 { + cicd_workflows = { + for k, v in local.cicd_repositories : k => templatefile( + "${path.module}/templates/workflow-${v.type}.yaml", ( + k == "bootstrap" + ? { + identity_provider = try( + local.cicd_identity_providers[v["identity_provider"]].name, "" + ) + outputs_bucket = var.automation.outputs_bucket + service_account = try( + module.automation-tf-cicd-sa-bootstrap["0"].email, "" + ) + stage_name = k + tf_providers_file = "" + tf_var_files = [ + "0-bootstrap.auto.tfvars.json", + "1-resman.auto.tfvars.json", + "globals.auto.tfvars.json" + ] + } + : { + identity_provider = try( + local.cicd_identity_providers[v["identity_provider"]].name, "" + ) + outputs_bucket = module.automation-tf-output-gcs.name + service_account = try( + module.automation-tf-cicd-sa-resman["0"].email, "" + ) + stage_name = k + tf_providers_file = ( + "${local._file_prefix}/providers/1-resman-tenant-providers.tf" + ) + tf_var_files = [ + "${local._file_prefix}/tfvars/0-bootstrap-tenant.auto.tfvars.json" + ] + } + ) + ) + } + provider = templatefile( + "${path.module}/templates/providers.tf.tpl", { + bucket = module.automation-tf-resman-gcs.name + name = "resman" + sa = module.automation-tf-resman-sa.email + } + ) + tfvars = { + automation = { + outputs_bucket = module.automation-tf-output-gcs.name + project_id = module.automation-project.project_id + project_number = module.automation-project.number + federated_identity_pools = compact([ + try(google_iam_workload_identity_pool.default.0.name, null), + var.automation.federated_identity_pool, + ]) + federated_identity_providers = local.cicd_identity_providers + service_accounts = merge( + { resman = module.automation-tf-resman-sa.email }, + { + for k, v in local.branch_sas : k => try( + module.automation-tf-resman-sa-stage2-3[k].email, null + ) + } + ) + } + billing_account = var.billing_account + custom_roles = var.custom_roles + fast_features = local.fast_features + groups = var.tenant_config.groups + locations = local.locations + organization = var.organization + prefix = local.prefix + root_node = module.tenant-folder.id + short_name = var.tenant_config.short_name + tags = { + keys = var.tag_keys + names = var.tag_names + values = merge(var.tag_values, { + for k, v in module.organization.tag_values : k => v.id + }) + } + } +} + +output "cicd_workflows" { + description = "CI/CD workflows for tenant bootstrap and resource management stages." + sensitive = true + value = local.cicd_workflows +} + +output "federated_identity" { + description = "Workload Identity Federation pool and providers." + value = { + pool = try( + google_iam_workload_identity_pool.default.0.name, null + ) + providers = local.cicd_identity_providers + } +} + +output "provider" { + # tfdoc:output:consumers stage-01 + description = "Terraform provider file for tenant resource management stage." + sensitive = true + value = local.provider +} + +output "tenant_resources" { + description = "Tenant-level resources." + value = { + bucket = module.automation-tf-resman-gcs.name + folder = module.tenant-folder.id + project_id = module.automation-project.project_id + project_number = module.automation-project.number + service_account = module.automation-tf-resman-sa.email + } +} + +output "tfvars" { + description = "Terraform variable files for the following tenant stages." + sensitive = true + value = local.tfvars +} diff --git a/tests/blueprints/factories/bigquery_factory/fixture/main.tf b/fast/stages-multitenant/0-bootstrap-tenant/templates/providers.tf.tpl similarity index 64% rename from tests/blueprints/factories/bigquery_factory/fixture/main.tf rename to fast/stages-multitenant/0-bootstrap-tenant/templates/providers.tf.tpl index 75f4fc1c..e11a51b8 100644 --- a/tests/blueprints/factories/bigquery_factory/fixture/main.tf +++ b/fast/stages-multitenant/0-bootstrap-tenant/templates/providers.tf.tpl @@ -1,5 +1,5 @@ /** - * Copyright 2022 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,10 +14,17 @@ * limitations under the License. */ -module "bq" { - source = "../../../../../blueprints/factories/bigquery-factory/" - - project_id = "test-project" - views_dir = "./views" - tables_dir = "./tables" +terraform { + backend "gcs" { + bucket = "${bucket}" + impersonate_service_account = "${sa}" + } } +provider "google" { + impersonate_service_account = "${sa}" +} +provider "google-beta" { + impersonate_service_account = "${sa}" +} + +# end provider.tf for ${name} diff --git a/fast/stages-multitenant/0-bootstrap-tenant/templates/workflow-github.yaml b/fast/stages-multitenant/0-bootstrap-tenant/templates/workflow-github.yaml new file mode 100644 index 00000000..364ee8f4 --- /dev/null +++ b/fast/stages-multitenant/0-bootstrap-tenant/templates/workflow-github.yaml @@ -0,0 +1,202 @@ +# 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 +# +# 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: "FAST ${stage_name} stage" + +on: + pull_request: + branches: + - main + types: + - closed + - opened + - synchronize + +env: + FAST_OUTPUTS_BUCKET: ${outputs_bucket} + FAST_SERVICE_ACCOUNT: ${service_account} + FAST_WIF_PROVIDER: ${identity_provider} + SSH_AUTH_SOCK: /tmp/ssh_agent.sock + %{~ if tf_providers_file != "" ~} + TF_PROVIDERS_FILE: ${tf_providers_file} + %{~ endif ~} + TF_VAR_FILES: ${tf_var_files == [] ? "''" : join("\n ", tf_var_files)} + TF_VERSION: 1.3.2 + +jobs: + fast-pr: + permissions: + contents: read + id-token: write + issues: write + pull-requests: write + runs-on: ubuntu-latest + steps: + - id: checkout + name: Checkout repository + uses: actions/checkout@v3 + + # set up SSH key authentication to the modules repository + - id: ssh-config + name: Configure SSH authentication + run: | + ssh-agent -a "$SSH_AUTH_SOCK" > /dev/null + ssh-add - <<< "$${{ secrets.CICD_MODULES_KEY }}" + + # set up authentication via Workload identity Federation + - id: gcp-auth + name: Authenticate to Google Cloud + uses: google-github-actions/auth@v0 + with: + workload_identity_provider: $${{ env.FAST_WIF_PROVIDER }} + service_account: $${{ env.FAST_SERVICE_ACCOUNT }} + access_token_lifetime: 3600s + + - id: gcp-sdk + name: Set up Cloud SDK + uses: google-github-actions/setup-gcloud@v0 + with: + install_components: alpha + + # copy provider and tfvars files + - id: tf-config + name: Copy Terraform output files + run: | + %{~ if tf_providers_file != "" ~} + gcloud alpha storage cp -r \ + "gs://$${{env.FAST_OUTPUTS_BUCKET}}/providers/$${{env.TF_PROVIDERS_FILE}}" ./ + %{~ endif ~} + gcloud alpha storage cp -r \ + "gs://$${{env.FAST_OUTPUTS_BUCKET}}/tfvars" ./ + for f in $${{env.TF_VAR_FILES}}; do + ln -s "tfvars/$f" ./ + done + + - id: tf-setup + name: Set up Terraform + uses: hashicorp/setup-terraform@v2.0.3 + with: + terraform_version: $${{ env.TF_VERSION }} + + # run Terraform init/validate/plan + - id: tf-init + name: Terraform init + continue-on-error: true + run: | + terraform init -no-color + + - id: tf-validate + name: Terraform validate + continue-on-error: true + run: terraform validate -no-color + + - id: tf-plan + name: Terraform plan + continue-on-error: true + run: | + terraform plan -input=false -out ../plan.out -no-color + + - id: tf-apply + if: github.event.pull_request.merged == true && success() + name: Terraform apply + continue-on-error: true + run: | + terraform apply -input=false -auto-approve -no-color ../plan.out + + - id: pr-comment + name: Post comment to Pull Request + continue-on-error: true + uses: actions/github-script@v6 + if: github.event_name == 'pull_request' + env: + PLAN: $${{ steps.tf-plan.outputs.stdout }}\n$${{ steps.tf-plan.outputs.stderr }} + with: + script: | + const output = `### Terraform Initialization \`$${{ steps.tf-init.outcome }}\` + + ### Terraform Validation \`$${{ steps.tf-validate.outcome }}\` + +
Validation Output + + \`\`\`\n + $${{ steps.tf-validate.outputs.stdout }} + \`\`\` + +
+ + ### Terraform Plan \`$${{ steps.tf-plan.outcome }}\` + +
Show Plan + + \`\`\`\n + $${process.env.PLAN.split('\n').filter(l => l.match(/^([A-Z\s].*|)$$/)).join('\n')} + \`\`\` + +
+ + ### Terraform Apply \`$${{ steps.tf-apply.outcome }}\` + + *Pusher: @$${{ github.actor }}, Action: \`$${{ github.event_name }}\`, Working Directory: \`$${{ env.tf_actions_working_dir }}\`, Workflow: \`$${{ github.workflow }}\`*`; + + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: output + }) + + - id: pr-short-comment + name: Post comment to Pull Request + uses: actions/github-script@v6 + if: github.event_name == 'pull_request' && steps.pr-comment.outcome != 'success' + with: + script: | + const output = `### Terraform Initialization \`$${{ steps.tf-init.outcome }}\` + + ### Terraform Validation \`$${{ steps.tf-validate.outcome }}\` + + ### Terraform Plan \`$${{ steps.tf-plan.outcome }}\` + + Plan output is in the action log. + + ### Terraform Apply \`$${{ steps.tf-apply.outcome }}\` + + *Pusher: @$${{ github.actor }}, Action: \`$${{ github.event_name }}\`, Working Directory: \`$${{ env.tf_actions_working_dir }}\`, Workflow: \`$${{ github.workflow }}\`*`; + + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: output + }) + + - id: check-init + name: Check init failure + if: steps.tf-init.outcome != 'success' + run: exit 1 + + - id: check-validate + name: Check validate failure + if: steps.tf-validate.outcome != 'success' + run: exit 1 + + - id: check-plan + name: Check plan failure + if: steps.tf-plan.outcome != 'success' + run: exit 1 + + - id: check-apply + name: Check apply failure + if: github.event.pull_request.merged == true && steps.tf-apply.outcome != 'success' + run: exit 1 diff --git a/fast/stages-multitenant/0-bootstrap-tenant/templates/workflow-gitlab.yaml b/fast/stages-multitenant/0-bootstrap-tenant/templates/workflow-gitlab.yaml new file mode 100644 index 00000000..739e7485 --- /dev/null +++ b/fast/stages-multitenant/0-bootstrap-tenant/templates/workflow-gitlab.yaml @@ -0,0 +1,124 @@ +# 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 +# +# 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. + +default: + before_script: + - echo "$${CI_JOB_JWT_V2}" > token.txt + image: + name: hashicorp/terraform + entrypoint: + - "/usr/bin/env" + - "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + +variables: + GOOGLE_CREDENTIALS: cicd-sa-credentials.json + FAST_OUTPUTS_BUCKET: ${outputs_bucket} + FAST_SERVICE_ACCOUNT: ${service_account} + FAST_WIF_PROVIDER: ${identity_provider} + SSH_AUTH_SOCK: /tmp/ssh_agent.sock + %{~ if tf_providers_file != "" ~} + TF_PROVIDERS_FILE: ${tf_providers_file} + %{~ endif ~} + TF_VAR_FILES: ${tf_var_files == [] ? "''" : join("\n ", tf_var_files)} + +stages: + - gcp-auth + - tf-files + - tf-plan + - tf-apply + +cache: + key: gcp-auth + paths: + - cicd-sa-credentials.json + - .tf-setup + +gcp-auth: + image: + name: google/cloud-sdk:slim + stage: gcp-auth + script: + - | + gcloud iam workload-identity-pools create-cred-config \ + $${FAST_WIF_PROVIDER} \ + --service-account=$${FAST_SERVICE_ACCOUNT} \ + --service-account-token-lifetime-seconds=3600 \ + --output-file=$${GOOGLE_CREDENTIALS} \ + --credential-source-file=token.txt +tf-files: + dependencies: + - gcp-auth + image: + name: google/cloud-sdk:slim + stage: tf-files + script: + # - gcloud components install -q alpha + - gcloud config set auth/credential_file_override $${GOOGLE_CREDENTIALS} + - mkdir -p .tf-setup + %{~ if tf_providers_file != "" ~} + - | + gcloud alpha storage cp -r \ + "gs://$${FAST_OUTPUTS_BUCKET}/providers/$${TF_PROVIDERS_FILE}" .tf-setup/ + %{~ endif ~} + - | + gcloud alpha storage cp -r \ + "gs://$${FAST_OUTPUTS_BUCKET}/tfvars" .tf-setup/ + +tf-plan: + # uncomment the following lines and set the SSH key secret for private modules repo + # before_script: + # - | + # ssh-agent -a $SSH_AUTH_SOCK > /dev/null + # echo "$CICD_MODULES_KEY" | base64 -d | tr -d '\r' | ssh-add - > /dev/null + # mkdir -p ~/.ssh + # ssh-keyscan -H 'gitlab.com' >> ~/.ssh/known_hosts + # ssh-keyscan gitlab.com | sort -u - ~/.ssh/known_hosts -o ~/.ssh/known_hosts + stage: tf-plan + script: + - cp .tf-setup/$${TF_PROVIDERS_FILE} ./ + - | + for f in $${TF_VAR_FILES}; do + ln -s ".tf-setup/tfvars/$f" ./ + done + - terraform init + - terraform validate + - terraform plan + dependencies: + - tf-files + +tf-apply: + # uncomment the following lines and set the SSH key secret for private modules repo + # before_script: + # - | + # ssh-agent -a $SSH_AUTH_SOCK > /dev/null + # echo "$CICD_MODULES_KEY" | base64 -d | tr -d '\r' | ssh-add - > /dev/null + # mkdir -p ~/.ssh + # ssh-keyscan -H 'gitlab.com' >> ~/.ssh/known_hosts + # ssh-keyscan gitlab.com | sort -u - ~/.ssh/known_hosts -o ~/.ssh/known_hosts + stage: tf-apply + script: + - cp .tf-setup/$${TF_PROVIDERS_FILE} ./ + - | + for f in $${TF_VAR_FILES}; do + ln -s ".tf-setup/tfvars/$f" ./ + done + - terraform init + - terraform validate + - terraform apply -input=false -auto-approve + dependencies: + - tf-files + when: manual + only: + variables: + - $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH diff --git a/fast/stages-multitenant/0-bootstrap-tenant/templates/workflow-sourcerepo.yaml b/fast/stages-multitenant/0-bootstrap-tenant/templates/workflow-sourcerepo.yaml new file mode 100644 index 00000000..e171c45e --- /dev/null +++ b/fast/stages-multitenant/0-bootstrap-tenant/templates/workflow-sourcerepo.yaml @@ -0,0 +1,100 @@ +# 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 +# +# 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. + +steps: + - name: alpine:3 + id: tf-download + entrypoint: sh + args: + - -eEuo + - pipefail + - -c + - |- + mkdir -p /builder/home/.local/bin + wget https://releases.hashicorp.com/terraform/$${_TF_VERSION}/terraform_$${_TF_VERSION}_linux_amd64.zip + unzip terraform_$${_TF_VERSION}_linux_amd64.zip -d /builder/home/.local/bin + rm terraform_$${_TF_VERSION}_linux_amd64.zip + chmod 755 /builder/home/.local/bin/terraform + - name: alpine:3 + id: tf-check-format + entrypoint: sh + args: + - -eEuo + - pipefail + - -c + - |- + terraform fmt -recursive -check /workspace/ + - name: gcr.io/google.com/cloudsdktool/cloud-sdk:alpine + id: tf-files + entrypoint: bash + args: + - -eEuo + - pipefail + - -c + - |- + %{~ if tf_providers_file != "" ~} + /google-cloud-sdk/bin/gsutil cp \ + gs://$${_FAST_OUTPUTS_BUCKET}/providers/$${_TF_PROVIDERS_FILE} ./ + %{~ endif ~} + /google-cloud-sdk/bin/gsutil cp -r \ + gs://$${_FAST_OUTPUTS_BUCKET}/tfvars ./ + for f in $${_TF_VAR_FILES}; do + ln -s tfvars/$f ./ + done + - name: alpine:3 + id: tf-init + entrypoint: sh + args: + - -eEuo + - pipefail + - -c + - |- + terraform init -no-color + - name: alpine:3 + id: tf-check-validate + entrypoint: sh + args: + - -eEuo + - pipefail + - -c + - |- + terraform validate -no-color + - name: alpine:3 + id: tf-plan + entrypoint: sh + args: + - -eEuo + - pipefail + - -c + - |- + terraform plan -no-color -input=false -out plan.out + # store artifact and ask for approval here if needed + - name: alpine:3 + id: tf-apply + entrypoint: sh + args: + - -eEuo + - pipefail + - -c + - |- + terraform apply -no-color -input=false -auto-approve plan.out +options: + env: + - PATH=/usr/local/bin:/usr/bin:/bin:/builder/home/.local/bin + logging: CLOUD_LOGGING_ONLY +substitutions: + _FAST_OUTPUTS_BUCKET: ${outputs_bucket} + _TF_PROVIDERS_FILE: ${tf_providers_file} + _TF_VAR_FILES: ${tf_var_files == [] ? "''" : join("\n ", tf_var_files)} + _TF_VERSION: 1.3.2 diff --git a/fast/stages-multitenant/0-bootstrap-tenant/variables.tf b/fast/stages-multitenant/0-bootstrap-tenant/variables.tf new file mode 100644 index 00000000..d218986f --- /dev/null +++ b/fast/stages-multitenant/0-bootstrap-tenant/variables.tf @@ -0,0 +1,306 @@ +/** + * 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. + */ + +# defaults for variables marked with global tfdoc annotations, can be set via +# the tfvars file generated in stage 00 and stored in its outputs + +variable "automation" { + # tfdoc:variable:source 0-bootstrap + description = "Automation resources created by the organization-level bootstrap stage." + type = object({ + outputs_bucket = string + project_id = string + project_number = string + federated_identity_pool = string + federated_identity_providers = map(object({ + issuer = string + issuer_uri = string + name = string + principal_tpl = string + principalset_tpl = string + })) + }) +} + +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." + type = object({ + id = string + is_org_level = optional(bool, true) + }) + validation { + condition = var.billing_account.is_org_level != null + error_message = "Invalid `null` value for `billing_account.is_org_level`." + } +} + +variable "cicd_repositories" { + description = "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." + type = object({ + bootstrap = optional(object({ + branch = optional(string) + identity_provider = string + name = string + type = string + })) + resman = optional(object({ + branch = optional(string) + identity_provider = string + name = string + type = string + })) + }) + default = null + validation { + condition = alltrue([ + for k, v in coalesce(var.cicd_repositories, {}) : + v == null || try(v.name, null) != null + ]) + error_message = "Non-null repositories need a non-null name." + } + validation { + condition = alltrue([ + for k, v in coalesce(var.cicd_repositories, {}) : + v == null || ( + try(v.identity_provider, null) != null + || + try(v.type, null) == "sourcerepo" + ) + ]) + error_message = "Non-null repositories need a non-null provider unless type is 'sourcerepo'." + } + validation { + condition = alltrue([ + for k, v in coalesce(var.cicd_repositories, {}) : + v == null || ( + contains(["github", "gitlab", "sourcerepo"], coalesce(try(v.type, null), "null")) + ) + ]) + error_message = "Invalid repository type, supported types: 'github' 'gitlab' or 'sourcerepo'." + } +} + +variable "custom_roles" { + # tfdoc:variable:source 0-bootstrap + description = "Custom roles defined at the organization level, in key => id format." + type = object({ + service_project_network_admin = string + tenant_network_admin = string + }) + default = null +} + +variable "fast_features" { + # tfdoc:variable:source 0-bootstrap + description = "Selective control for top-level FAST features." + type = object({ + data_platform = optional(bool, true) + gke = optional(bool, true) + project_factory = optional(bool, true) + sandbox = optional(bool, true) + teams = optional(bool, true) + }) + default = {} + nullable = false +} + +variable "federated_identity_providers" { + description = "Workload Identity Federation pools. The `cicd_repositories` variable references keys here." + type = map(object({ + attribute_condition = string + issuer = string + custom_settings = object({ + issuer_uri = string + allowed_audiences = list(string) + }) + })) + default = {} + nullable = false +} + +variable "group_iam" { + description = "Tenant-level custom group IAM settings in group => [roles] format." + type = map(list(string)) + default = {} +} + +variable "iam" { + description = "Tenant-level custom IAM settings in role => [principal] format." + type = map(list(string)) + default = {} +} + +variable "iam_additive" { + description = "Tenant-level custom IAM settings in role => [principal] format for non-authoritative bindings." + type = map(list(string)) + default = {} +} + +variable "locations" { + # tfdoc:variable:source 0-bootstrap + description = "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." + type = object({ + bq = string + gcs = string + logging = string + pubsub = list(string) + }) + default = { + bq = "EU" + gcs = "EU" + logging = "global" + pubsub = [] + } + nullable = false +} + +# See https://cloud.google.com/architecture/exporting-stackdriver-logging-for-security-and-access-analytics +# for additional logging filter examples +variable "log_sinks" { + description = "Tenant-level log sinks, in name => {type, filter} format." + type = map(object({ + filter = string + type = string + })) + default = { + audit-logs = { + filter = "logName:\"/logs/cloudaudit.googleapis.com%2Factivity\" OR logName:\"/logs/cloudaudit.googleapis.com%2Fsystem_event\"" + type = "logging" + } + } + validation { + condition = alltrue([ + for k, v in var.log_sinks : + contains(["bigquery", "logging", "pubsub", "storage"], v.type) + ]) + error_message = "Type must be one of 'bigquery', 'logging', 'pubsub', 'storage'." + } +} + +variable "organization" { + # tfdoc:variable:source 0-bootstrap + description = "Organization details." + type = object({ + domain = string + id = number + customer_id = string + }) +} + +variable "outputs_location" { + description = "Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable." + type = string + default = null +} + +variable "prefix" { + # tfdoc:variable:source 0-bootstrap + description = "Prefix used for resources that need unique names. Use 9 characters or less." + type = string + validation { + condition = try(length(var.prefix), 0) < 10 + error_message = "Use a maximum of 9 characters for prefix." + } +} + +variable "project_parent_ids" { + description = "Optional parents for projects created here in folders/nnnnnnn format. Null values will use the tenant folder as parent." + type = object({ + automation = string + logging = string + }) + default = { + automation = null + logging = null + } + nullable = false +} + +variable "tag_keys" { + # tfdoc:variable:source 1-resman + description = "Organization tag keys." + type = object({ + context = string + environment = string + tenant = string + }) + nullable = false +} + +variable "tag_names" { + # tfdoc:variable:source 1-resman + description = "Customized names for resource management tags." + type = object({ + context = string + environment = string + tenant = string + }) + nullable = false +} + +variable "tag_values" { + # tfdoc:variable:source 1-resman + description = "Organization resource management tag values." + type = map(string) + nullable = false +} + +variable "tenant_config" { + description = "Tenant configuration. Short name must be 4 characters or less." + type = object({ + descriptive_name = string + groups = object({ + gcp-admins = string + gcp-devops = optional(string) + gcp-network-admins = optional(string) + gcp-security-admins = optional(string) + }) + short_name = string + fast_features = optional(object({ + data_platform = optional(bool) + gke = optional(bool) + project_factory = optional(bool) + sandbox = optional(bool) + teams = optional(bool) + }), {}) + locations = optional(object({ + bq = optional(string) + gcs = optional(string) + logging = optional(string) + pubsub = optional(list(string)) + }), {}) + }) + nullable = false + validation { + condition = alltrue([ + for a in ["descriptive_name", "groups", "short_name"] : + var.tenant_config[a] != null + ]) + error_message = "Non-optional members must not be null." + } + validation { + condition = length(var.tenant_config.short_name) < 5 + error_message = "Short name must be a string of 4 characters or less." + } +} + + +variable "test_principal" { + description = "Used when testing to bypass the data source returning the current identity." + type = string + default = null +} diff --git a/fast/stages-multitenant/1-resman-tenant/IAM.md b/fast/stages-multitenant/1-resman-tenant/IAM.md new file mode 100644 index 00000000..16db4a6c --- /dev/null +++ b/fast/stages-multitenant/1-resman-tenant/IAM.md @@ -0,0 +1,60 @@ +# IAM bindings reference + +Legend: + additive, conditional. + +## Folder development [#0] + +| members | roles | +|---|---| +|tn0-gke-dev-0
serviceAccount|[roles/compute.xpnAdmin](https://cloud.google.com/iam/docs/understanding-roles#compute.xpnAdmin)
[roles/logging.admin](https://cloud.google.com/iam/docs/understanding-roles#logging.admin)
[roles/owner](https://cloud.google.com/iam/docs/understanding-roles#owner)
[roles/resourcemanager.folderAdmin](https://cloud.google.com/iam/docs/understanding-roles#resourcemanager.folderAdmin)
[roles/resourcemanager.projectCreator](https://cloud.google.com/iam/docs/understanding-roles#resourcemanager.projectCreator) | + +## Folder development [#1] + +| members | roles | +|---|---| +|tn0-gke-dev-0
serviceAccount|organizations/[org_id #0]/roles/serviceProjectNetworkAdmin | +|tn0-pf-dev-0
serviceAccount|organizations/[org_id #0]/roles/serviceProjectNetworkAdmin | + +## Folder networking + +| members | roles | +|---|---| +|tn0-networking-0
serviceAccount|[roles/compute.xpnAdmin](https://cloud.google.com/iam/docs/understanding-roles#compute.xpnAdmin)
[roles/logging.admin](https://cloud.google.com/iam/docs/understanding-roles#logging.admin)
[roles/owner](https://cloud.google.com/iam/docs/understanding-roles#owner)
[roles/resourcemanager.folderAdmin](https://cloud.google.com/iam/docs/understanding-roles#resourcemanager.folderAdmin)
[roles/resourcemanager.projectCreator](https://cloud.google.com/iam/docs/understanding-roles#resourcemanager.projectCreator) | + +## Folder production [#0] + +| members | roles | +|---|---| +|tn0-gke-prod-0
serviceAccount|[roles/compute.xpnAdmin](https://cloud.google.com/iam/docs/understanding-roles#compute.xpnAdmin)
[roles/logging.admin](https://cloud.google.com/iam/docs/understanding-roles#logging.admin)
[roles/owner](https://cloud.google.com/iam/docs/understanding-roles#owner)
[roles/resourcemanager.folderAdmin](https://cloud.google.com/iam/docs/understanding-roles#resourcemanager.folderAdmin)
[roles/resourcemanager.projectCreator](https://cloud.google.com/iam/docs/understanding-roles#resourcemanager.projectCreator) | + +## Folder production [#1] + +| members | roles | +|---|---| +|tn0-gke-prod-0
serviceAccount|organizations/[org_id #0]/roles/serviceProjectNetworkAdmin | +|tn0-pf-prod-0
serviceAccount|organizations/[org_id #0]/roles/serviceProjectNetworkAdmin | + +## Folder sandbox + +| members | roles | +|---|---| +|tn0-sandbox-0
serviceAccount|[roles/logging.admin](https://cloud.google.com/iam/docs/understanding-roles#logging.admin)
[roles/owner](https://cloud.google.com/iam/docs/understanding-roles#owner)
[roles/resourcemanager.folderAdmin](https://cloud.google.com/iam/docs/understanding-roles#resourcemanager.folderAdmin)
[roles/resourcemanager.projectCreator](https://cloud.google.com/iam/docs/understanding-roles#resourcemanager.projectCreator) | + +## Folder security + +| members | roles | +|---|---| +|tn0-security-0
serviceAccount|[roles/logging.admin](https://cloud.google.com/iam/docs/understanding-roles#logging.admin)
[roles/owner](https://cloud.google.com/iam/docs/understanding-roles#owner)
[roles/resourcemanager.folderAdmin](https://cloud.google.com/iam/docs/understanding-roles#resourcemanager.folderAdmin)
[roles/resourcemanager.projectCreator](https://cloud.google.com/iam/docs/understanding-roles#resourcemanager.projectCreator) | + +## Folder teams + +| members | roles | +|---|---| +|tn0-teams-0
serviceAccount|[roles/compute.xpnAdmin](https://cloud.google.com/iam/docs/understanding-roles#compute.xpnAdmin)
[roles/logging.admin](https://cloud.google.com/iam/docs/understanding-roles#logging.admin)
[roles/owner](https://cloud.google.com/iam/docs/understanding-roles#owner)
[roles/resourcemanager.folderAdmin](https://cloud.google.com/iam/docs/understanding-roles#resourcemanager.folderAdmin)
[roles/resourcemanager.projectCreator](https://cloud.google.com/iam/docs/understanding-roles#resourcemanager.projectCreator) | + +## Folder test tenant 0 + +| members | roles | +|---|---| +|tn0-networking-0
serviceAccount|[roles/compute.orgFirewallPolicyAdmin](https://cloud.google.com/iam/docs/understanding-roles#compute.orgFirewallPolicyAdmin) +
[roles/compute.xpnAdmin](https://cloud.google.com/iam/docs/understanding-roles#compute.xpnAdmin) +| +|tn0-security-0
serviceAccount|[roles/accesscontextmanager.policyAdmin](https://cloud.google.com/iam/docs/understanding-roles#accesscontextmanager.policyAdmin) +| diff --git a/fast/stages-multitenant/1-resman-tenant/README.md b/fast/stages-multitenant/1-resman-tenant/README.md new file mode 100644 index 00000000..ae42fc30 --- /dev/null +++ b/fast/stages-multitenant/1-resman-tenant/README.md @@ -0,0 +1,184 @@ +# Tenant resource management + +This stage is run for a specific tenant after [tenant bootstrap](../0-bootstrap-tenant/) has successfully created initial resources for the tenant, which is then decoupled from the organization. + +It is logically equivalent and almost identical in code to the corresponding [organization resource management stage](../../stages/1-resman/), with a few notable differences: + +- the hierarchy is rooted in the tenant top-level folder instead of the organization +- there's no management of tag values and keys since they organization-level resources (it could be implemented for tenant-specific tags if the need arises) +- automation service accounts for subsequent stages are configured but not created here (tenant-level bootstrap creates them and assigns organization-level permissions) + +The stage runs with a dedicated service account for the tenant, which has no permissions at the organization level except for billing and organization policies, constrained by a condition on the tenant tag. + +The following diagram is a high level reference of what this stage manages, showing one hypothetical tenant (additional tenants require additional instances of this stage being deployed): + +```mermaid +%%{init: {'theme':'base'}}%% +classDiagram + Tenant_root~📁~ -- tn0_automation + Tenant_root~📁~ -- Networking~📁~ + Tenant_root~📁~ -- Security~📁~ + Tenant_root~📁~ -- Data_Platform~📁~ + Data_Platform~📁~ -- DP_Dev~📁~ + Data_Platform~📁~ -- DP_Prod~📁~ + Tenant_root~📁~ -- GKE~📁~ + GKE~📁~ -- GKE_Dev~📁~ + GKE~📁~ -- GKE_Prod~📁~ + Tenant_root~📁~ -- Teams~📁~ + Teams~📁~ -- Team_0~📁~ + Team_0~📁~ -- Team_0_Dev~📁~ + Team_0~📁~ -- Team_0_Prod~📁~ + Tenant_root~📁~ -- Sandbox~📁~ + class Tenant_root~📁~ { + - IAM bindings() + - org policies() + } + class tn0_automation { + - GCS buckets + - IAM bindings() + } + class Data_Platform~📁~ { + - IAM bindings() + - tag bindings() + } + class DP_Dev~📁~ { + - IAM bindings() + - tag bindings() + } + class DP_Prod~📁~ { + - IAM bindings() + - tag bindings() + } + class GKE~📁~ { + - IAM bindings() + - tag bindings() + } + class GKE_Dev~📁~ { + - IAM bindings() + - tag bindings() + } + class GKE_Prod~📁~ { + - IAM bindings() + - tag bindings() + } + class Networking~📁~ { + - IAM bindings() + - tag bindings() + } + class Security~📁~ { + - IAM bindings() + - tag bindings() + } + class Sandbox~📁~ { + - IAM bindings() + - tag bindings() + } + class Teams~📁~ { + - IAM bindings() + - tag bindings() + } + class Team_0~📁~ { + - IAM bindings() + - tag bindings() + } + class Team_0_Dev~📁~ { + - IAM bindings() + - tag bindings() + } + class Team_0_Prod~📁~ { + - IAM bindings() + - tag bindings() + } +``` + +As most of the features of this stage follow the same design and configurations of the [organization-level resource management stage](../../stages/1-resman/), we will only focus on the tenant-specific configuration in this document. + +## How to run this stage + +As mentioned above this stage is decoupled from organization-level stages: it uses a service account and state bucket from the tenant-specific automation project, and its tfvars and provider files are also tenant-specific. + +The `stage-links.sh` script can be used to get the commands needed for the provider and output files, just set the variable for the tenant shortname (the same one specified in the tenant bootstrap stage) and pass a single argument with your FAST output files folder path, or GCS bucket URI: + +```bash +TENANT=tn0 ../../stage-links.sh ~/fast-config +``` + +The script output can be copy/pasted to a terminal: + +```bash +# copy and paste the following commands for '1-resman-tenant' + +ln -s ~/fast-config/tenants/tn0/providers/1-resman-tenant-providers.tf ./ +ln -s ~/fast-config/tenants/tn0/tfvars/0-bootstrap-tenant.auto.tfvars.json ./ +``` + +Once that is done, stage-level configuration variables are the same as the corresponding organization-level stage. + +### Running the stage + +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. + + + + +## Files + +| name | description | modules | resources | +|---|---|---|---| +| [branch-data-platform.tf](./branch-data-platform.tf) | Data Platform stages resources. | folder · gcs · iam-service-account | | +| [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 | | +| [branch-project-factory.tf](./branch-project-factory.tf) | Project factory stage resources. | gcs · iam-service-account | | +| [branch-sandbox.tf](./branch-sandbox.tf) | Sandbox stage resources. | folder · gcs | | +| [branch-security.tf](./branch-security.tf) | Security stage resources. | folder · gcs · iam-service-account | | +| [branch-teams.tf](./branch-teams.tf) | Team stage resources. | folder · gcs · iam-service-account | | +| [cicd-data-platform.tf](./cicd-data-platform.tf) | CI/CD resources for the data platform branch. | iam-service-account · source-repository | | +| [cicd-gke.tf](./cicd-gke.tf) | CI/CD resources for the data platform branch. | iam-service-account · source-repository | | +| [cicd-networking.tf](./cicd-networking.tf) | CI/CD resources for the networking branch. | iam-service-account · source-repository | | +| [cicd-project-factory.tf](./cicd-project-factory.tf) | CI/CD resources for the teams branch. | iam-service-account · source-repository | | +| [cicd-security.tf](./cicd-security.tf) | CI/CD resources for the security branch. | iam-service-account · source-repository | | +| [main.tf](./main.tf) | Module-level locals and resources. | | | +| [outputs-files.tf](./outputs-files.tf) | Output files persistence to local filesystem. | | local_file | +| [outputs-gcs.tf](./outputs-gcs.tf) | Output files persistence to automation GCS bucket. | | google_storage_bucket_object | +| [outputs.tf](./outputs.tf) | Module outputs. | | | +| [root_node.tf](./root_node.tf) | Tenant root folder configuration. | folder | | +| [variables.tf](./variables.tf) | Module variables. | | | + +## Variables + +| 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 | | + +## Outputs + +| name | description | sensitive | consumers | +|---|---|:---:|---| +| [cicd_repositories](outputs.tf#L189) | WIF configuration for CI/CD repositories. | | | +| [dataplatform](outputs.tf#L203) | Data for the Data Platform stage. | | | +| [gke_multitenant](outputs.tf#L219) | Data for the GKE multitenant stage. | | 03-gke-multitenant | +| [networking](outputs.tf#L240) | Data for the networking stage. | | | +| [project_factories](outputs.tf#L249) | Data for the project factories stage. | | | +| [providers](outputs.tf#L264) | Terraform provider files for this stage and dependent stages. | ✓ | 02-networking · 02-security · 03-dataplatform · xx-sandbox · xx-teams | +| [sandbox](outputs.tf#L271) | Data for the sandbox stage. | | xx-sandbox | +| [security](outputs.tf#L285) | Data for the networking stage. | | 02-security | +| [teams](outputs.tf#L295) | Data for the teams stage. | | | +| [tfvars](outputs.tf#L307) | Terraform variable files for the following stages. | ✓ | | + + diff --git a/fast/stages-multitenant/1-resman-tenant/branch-data-platform.tf b/fast/stages-multitenant/1-resman-tenant/branch-data-platform.tf new file mode 100644 index 00000000..3916d635 --- /dev/null +++ b/fast/stages-multitenant/1-resman-tenant/branch-data-platform.tf @@ -0,0 +1,133 @@ +/** + * 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 Data Platform stages resources. + +module "branch-dp-folder" { + source = "../../../modules/folder" + count = var.fast_features.data_platform ? 1 : 0 + parent = module.root-folder.id + name = "Data Platform" + tag_bindings = { + context = var.tags.values["${var.tags.names.context}/data"] + } +} + +module "branch-dp-dev-folder" { + source = "../../../modules/folder" + count = var.fast_features.data_platform ? 1 : 0 + parent = module.branch-dp-folder.0.id + name = "Development" + group_iam = {} + iam = { + (local.custom_roles.service_project_network_admin) = [ + local.automation_sas_iam.dp-dev + ] + # remove owner here and at project level if SA does not manage project resources + "roles/owner" = [local.automation_sas_iam.dp-dev] + "roles/logging.admin" = [local.automation_sas_iam.dp-dev] + "roles/resourcemanager.folderAdmin" = [local.automation_sas_iam.dp-dev] + "roles/resourcemanager.projectCreator" = [local.automation_sas_iam.dp-dev] + } + tag_bindings = { + context = var.tags.values["${var.tags.names.environment}/development"] + } +} + +module "branch-dp-prod-folder" { + source = "../../../modules/folder" + count = var.fast_features.data_platform ? 1 : 0 + parent = module.branch-dp-folder.0.id + name = "Production" + group_iam = {} + iam = { + (local.custom_roles.service_project_network_admin) = [ + local.automation_sas_iam.dp-prod + ] + # remove owner here and at project level if SA does not manage project resources + "roles/owner" = [local.automation_sas_iam.dp-prod] + "roles/logging.admin" = [local.automation_sas_iam.dp-prod] + "roles/resourcemanager.folderAdmin" = [local.automation_sas_iam.dp-prod] + "roles/resourcemanager.projectCreator" = [local.automation_sas_iam.dp-prod] + } + tag_bindings = { + context = var.tags.values["${var.tags.names.environment}/production"] + } +} + +# automation service accounts and buckets + +module "branch-dp-dev-sa" { + source = "../../../modules/iam-service-account" + count = var.fast_features.data_platform ? 1 : 0 + project_id = var.automation.project_id + name = "dp-dev-0" + prefix = var.prefix + service_account_create = var.test_skip_data_sources + iam = { + "roles/iam.serviceAccountTokenCreator" = compact([ + try(module.branch-dp-dev-sa-cicd.0.iam_email, null) + ]) + } + iam_storage_roles = { + (var.automation.outputs_bucket) = ["roles/storage.admin"] + } +} + +module "branch-dp-prod-sa" { + source = "../../../modules/iam-service-account" + count = var.fast_features.data_platform ? 1 : 0 + project_id = var.automation.project_id + name = "dp-prod-0" + prefix = var.prefix + service_account_create = var.test_skip_data_sources + iam = { + "roles/iam.serviceAccountTokenCreator" = compact([ + try(module.branch-dp-prod-sa-cicd.0.iam_email, null) + ]) + } + iam_storage_roles = { + (var.automation.outputs_bucket) = ["roles/storage.admin"] + } +} + +module "branch-dp-dev-gcs" { + source = "../../../modules/gcs" + count = var.fast_features.data_platform ? 1 : 0 + project_id = var.automation.project_id + name = "dev-resman-dp-0" + prefix = var.prefix + location = var.locations.gcs + storage_class = local.gcs_storage_class + versioning = true + iam = { + "roles/storage.objectAdmin" = [local.automation_sas_iam.dp-dev] + } +} + +module "branch-dp-prod-gcs" { + source = "../../../modules/gcs" + count = var.fast_features.data_platform ? 1 : 0 + project_id = var.automation.project_id + name = "prod-resman-dp-0" + prefix = var.prefix + location = var.locations.gcs + storage_class = local.gcs_storage_class + versioning = true + iam = { + "roles/storage.objectAdmin" = [local.automation_sas_iam.dp-prod] + } +} diff --git a/fast/stages-multitenant/1-resman-tenant/branch-gke.tf b/fast/stages-multitenant/1-resman-tenant/branch-gke.tf new file mode 100644 index 00000000..9ece810b --- /dev/null +++ b/fast/stages-multitenant/1-resman-tenant/branch-gke.tf @@ -0,0 +1,133 @@ +/** + * 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 GKE multitenant stage resources. + +module "branch-gke-folder" { + source = "../../../modules/folder" + count = var.fast_features.gke ? 1 : 0 + parent = module.root-folder.id + name = "GKE" + tag_bindings = { + context = var.tags.values["${var.tags.names.context}/gke"] + } +} + +module "branch-gke-dev-folder" { + source = "../../../modules/folder" + count = var.fast_features.gke ? 1 : 0 + parent = module.branch-gke-folder.0.id + name = "Development" + iam = { + "roles/owner" = [local.automation_sas_iam.gke-dev] + "roles/logging.admin" = [local.automation_sas_iam.gke-dev] + "roles/resourcemanager.folderAdmin" = [local.automation_sas_iam.gke-dev] + "roles/resourcemanager.projectCreator" = [local.automation_sas_iam.gke-dev] + "roles/compute.xpnAdmin" = [local.automation_sas_iam.gke-dev] + } + tag_bindings = { + context = var.tags.values["${var.tags.names.environment}/development"] + } +} + +module "branch-gke-prod-folder" { + source = "../../../modules/folder" + count = var.fast_features.gke ? 1 : 0 + parent = module.branch-gke-folder.0.id + name = "Production" + iam = { + "roles/owner" = [local.automation_sas_iam.gke-prod] + "roles/logging.admin" = [local.automation_sas_iam.gke-prod] + "roles/resourcemanager.folderAdmin" = [local.automation_sas_iam.gke-prod] + "roles/resourcemanager.projectCreator" = [local.automation_sas_iam.gke-prod] + "roles/compute.xpnAdmin" = [local.automation_sas_iam.gke-prod] + } + tag_bindings = { + context = var.tags.values["${var.tags.names.environment}/production"] + } +} + +module "branch-gke-dev-sa" { + source = "../../../modules/iam-service-account" + count = var.fast_features.gke ? 1 : 0 + project_id = var.automation.project_id + name = "gke-dev-0" + prefix = var.prefix + service_account_create = var.test_skip_data_sources + iam = { + "roles/iam.serviceAccountTokenCreator" = concat( + ( + local.groups.gcp-devops == null + ? [] + : ["group:${local.groups.gcp-devops}"] + ), + compact([ + try(module.branch-gke-dev-sa-cicd.0.iam_email, null) + ]) + ) + } + iam_storage_roles = { + (var.automation.outputs_bucket) = ["roles/storage.admin"] + } +} + +module "branch-gke-prod-sa" { + source = "../../../modules/iam-service-account" + count = var.fast_features.gke ? 1 : 0 + project_id = var.automation.project_id + name = "gke-prod-0" + prefix = var.prefix + service_account_create = var.test_skip_data_sources + iam = { + "roles/iam.serviceAccountTokenCreator" = concat( + ( + local.groups.gcp-devops == null + ? [] + : ["group:${local.groups.gcp-devops}"] + ), + compact([ + try(module.branch-gke-prod-sa-cicd.0.iam_email, null) + ]) + ) + } + iam_storage_roles = { + (var.automation.outputs_bucket) = ["roles/storage.admin"] + } +} + +module "branch-gke-dev-gcs" { + source = "../../../modules/gcs" + count = var.fast_features.gke ? 1 : 0 + project_id = var.automation.project_id + name = "dev-resman-gke-0" + prefix = var.prefix + versioning = true + iam = { + "roles/storage.objectAdmin" = [local.automation_sas_iam.gke-dev] + } +} + +module "branch-gke-prod-gcs" { + source = "../../../modules/gcs" + count = var.fast_features.gke ? 1 : 0 + project_id = var.automation.project_id + name = "prod-resman-gke-0" + prefix = var.prefix + versioning = true + iam = { + "roles/storage.objectAdmin" = [local.automation_sas_iam.gke-prod] + } +} diff --git a/fast/stages-multitenant/1-resman-tenant/branch-networking.tf b/fast/stages-multitenant/1-resman-tenant/branch-networking.tf new file mode 100644 index 00000000..85490baf --- /dev/null +++ b/fast/stages-multitenant/1-resman-tenant/branch-networking.tf @@ -0,0 +1,107 @@ +/** + * 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 Networking stage resources. + +module "branch-network-folder" { + source = "../../../modules/folder" + parent = module.root-folder.id + name = "Networking" + group_iam = local.groups.gcp-network-admins == null ? {} : { + (local.groups.gcp-network-admins) = [ + # add any needed roles for resources/services not managed via Terraform, + # or replace editor with ~viewer if no broad resource management needed + # e.g. + # "roles/compute.networkAdmin", + # "roles/dns.admin", + # "roles/compute.securityAdmin", + "roles/editor", + ] + } + iam = { + "roles/logging.admin" = [local.automation_sas_iam.networking] + "roles/owner" = [local.automation_sas_iam.networking] + "roles/resourcemanager.folderAdmin" = [local.automation_sas_iam.networking] + "roles/resourcemanager.projectCreator" = [local.automation_sas_iam.networking] + "roles/compute.xpnAdmin" = [local.automation_sas_iam.networking] + } + tag_bindings = { + context = var.tags.values["${var.tags.names.context}/networking"] + } +} + +module "branch-network-prod-folder" { + source = "../../../modules/folder" + parent = module.branch-network-folder.id + name = "Production" + iam = { + (local.custom_roles.service_project_network_admin) = concat( + local.branch_optional_sa_lists.dp-prod, + local.branch_optional_sa_lists.gke-prod, + local.branch_optional_sa_lists.pf-prod, + ) + } + tag_bindings = { + environment = var.tags.values["${var.tags.names.environment}/production"] + } +} + +module "branch-network-dev-folder" { + source = "../../../modules/folder" + parent = module.branch-network-folder.id + name = "Development" + iam = { + (local.custom_roles.service_project_network_admin) = concat( + local.branch_optional_sa_lists.dp-dev, + local.branch_optional_sa_lists.gke-dev, + local.branch_optional_sa_lists.pf-dev, + ) + } + tag_bindings = { + environment = var.tags.values["${var.tags.names.environment}/development"] + } +} + +# automation service account and bucket + +module "branch-network-sa" { + source = "../../../modules/iam-service-account" + project_id = var.automation.project_id + name = "networking-0" + prefix = var.prefix + service_account_create = var.test_skip_data_sources + iam = { + "roles/iam.serviceAccountTokenCreator" = compact([ + try(module.branch-network-sa-cicd.0.iam_email, null) + ]) + } + iam_storage_roles = { + (var.automation.outputs_bucket) = ["roles/storage.admin"] + } +} + +module "branch-network-gcs" { + source = "../../../modules/gcs" + project_id = var.automation.project_id + name = "prod-resman-net-0" + prefix = var.prefix + location = var.locations.gcs + storage_class = local.gcs_storage_class + versioning = true + iam = { + "roles/storage.objectAdmin" = [local.automation_sas_iam.networking] + } +} diff --git a/fast/stages-multitenant/1-resman-tenant/branch-project-factory.tf b/fast/stages-multitenant/1-resman-tenant/branch-project-factory.tf new file mode 100644 index 00000000..2fa64bbc --- /dev/null +++ b/fast/stages-multitenant/1-resman-tenant/branch-project-factory.tf @@ -0,0 +1,79 @@ +/** + * 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 Project factory stage resources. + +module "branch-pf-dev-sa" { + source = "../../../modules/iam-service-account" + count = var.fast_features.project_factory ? 1 : 0 + project_id = var.automation.project_id + name = "pf-dev-0" + prefix = var.prefix + service_account_create = var.test_skip_data_sources + iam = { + "roles/iam.serviceAccountTokenCreator" = compact([ + try(module.branch-pf-dev-sa-cicd.0.iam_email, null) + ]) + } + iam_storage_roles = { + (var.automation.outputs_bucket) = ["roles/storage.admin"] + } +} + +module "branch-pf-prod-sa" { + source = "../../../modules/iam-service-account" + count = var.fast_features.project_factory ? 1 : 0 + project_id = var.automation.project_id + name = "pf-prod-0" + prefix = var.prefix + service_account_create = var.test_skip_data_sources + iam = { + "roles/iam.serviceAccountTokenCreator" = compact([ + try(module.branch-pf-prod-sa-cicd.0.iam_email, null) + ]) + } + iam_storage_roles = { + (var.automation.outputs_bucket) = ["roles/storage.admin"] + } +} + +module "branch-pf-dev-gcs" { + source = "../../../modules/gcs" + count = var.fast_features.project_factory ? 1 : 0 + project_id = var.automation.project_id + name = "dev-resman-pf-0" + prefix = var.prefix + location = var.locations.gcs + storage_class = local.gcs_storage_class + versioning = true + iam = { + "roles/storage.objectAdmin" = [local.automation_sas_iam.pf-dev] + } +} + +module "branch-pf-prod-gcs" { + source = "../../../modules/gcs" + count = var.fast_features.project_factory ? 1 : 0 + project_id = var.automation.project_id + name = "prod-resman-pf-0" + prefix = var.prefix + location = var.locations.gcs + storage_class = local.gcs_storage_class + versioning = true + iam = { + "roles/storage.objectAdmin" = [local.automation_sas_iam.pf-prod] + } +} diff --git a/fast/stages-multitenant/1-resman-tenant/branch-sandbox.tf b/fast/stages-multitenant/1-resman-tenant/branch-sandbox.tf new file mode 100644 index 00000000..6f3d526c --- /dev/null +++ b/fast/stages-multitenant/1-resman-tenant/branch-sandbox.tf @@ -0,0 +1,51 @@ +/** + * 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 Sandbox stage resources. + +module "branch-sandbox-folder" { + source = "../../../modules/folder" + count = var.fast_features.sandbox ? 1 : 0 + parent = module.root-folder.id + name = "Sandbox" + iam = { + "roles/logging.admin" = [local.automation_sas_iam.sandbox] + "roles/owner" = [local.automation_sas_iam.sandbox] + "roles/resourcemanager.folderAdmin" = [local.automation_sas_iam.sandbox] + "roles/resourcemanager.projectCreator" = [local.automation_sas_iam.sandbox] + } + org_policies = { + "constraints/sql.restrictPublicIp" = { enforce = false } + "constraints/compute.vmExternalIpAccess" = { allow = { all = true } } + } + tag_bindings = { + context = var.tags.values["${var.tags.names.context}/sandbox"] + } +} + +module "branch-sandbox-gcs" { + source = "../../../modules/gcs" + count = var.fast_features.sandbox ? 1 : 0 + project_id = var.automation.project_id + name = "dev-resman-sbox-0" + prefix = var.prefix + location = var.locations.gcs + storage_class = local.gcs_storage_class + versioning = true + iam = { + "roles/storage.objectAdmin" = [local.automation_sas_iam.sandbox] + } +} diff --git a/fast/stages-multitenant/1-resman-tenant/branch-security.tf b/fast/stages-multitenant/1-resman-tenant/branch-security.tf new file mode 100644 index 00000000..d7253cce --- /dev/null +++ b/fast/stages-multitenant/1-resman-tenant/branch-security.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. + */ + +# tfdoc:file:description Security stage resources. + +module "branch-security-folder" { + source = "../../../modules/folder" + parent = module.root-folder.id + name = "Security" + group_iam = local.groups.gcp-security-admins == null ? {} : { + (local.groups.gcp-security-admins) = [ + # add any needed roles for resources/services not managed via Terraform, + # e.g. + # "roles/bigquery.admin", + # "roles/cloudasset.owner", + # "roles/cloudkms.admin", + # "roles/logging.admin", + # "roles/secretmanager.admin", + # "roles/storage.admin", + "roles/viewer" + ] + } + iam = { + "roles/logging.admin" = [local.automation_sas_iam.security] + "roles/owner" = [local.automation_sas_iam.security] + "roles/resourcemanager.folderAdmin" = [local.automation_sas_iam.security] + "roles/resourcemanager.projectCreator" = [local.automation_sas_iam.security] + } + tag_bindings = { + context = var.tags.values["${var.tags.names.context}/security"] + } +} + +# automation service account and bucket + +module "branch-security-sa" { + source = "../../../modules/iam-service-account" + project_id = var.automation.project_id + name = "security-0" + prefix = var.prefix + service_account_create = var.test_skip_data_sources + iam = { + "roles/iam.serviceAccountTokenCreator" = compact([ + try(module.branch-security-sa-cicd.0.iam_email, null) + ]) + } + iam_storage_roles = { + (var.automation.outputs_bucket) = ["roles/storage.admin"] + } +} + +module "branch-security-gcs" { + source = "../../../modules/gcs" + project_id = var.automation.project_id + name = "prod-resman-sec-0" + prefix = var.prefix + location = var.locations.gcs + storage_class = local.gcs_storage_class + versioning = true + iam = { + "roles/storage.objectAdmin" = [local.automation_sas_iam.security] + } +} diff --git a/fast/stages-multitenant/1-resman-tenant/branch-teams.tf b/fast/stages-multitenant/1-resman-tenant/branch-teams.tf new file mode 100644 index 00000000..57f22110 --- /dev/null +++ b/fast/stages-multitenant/1-resman-tenant/branch-teams.tf @@ -0,0 +1,163 @@ +/** + * 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 Team stage resources. + +# TODO(ludo): add support for CI/CD + +############### top-level Teams branch and automation resources ############### + +module "branch-teams-folder" { + source = "../../../modules/folder" + count = var.fast_features.teams ? 1 : 0 + parent = module.root-folder.id + name = "Teams" + iam = { + "roles/logging.admin" = [local.automation_sas_iam.teams] + "roles/owner" = [local.automation_sas_iam.teams] + "roles/resourcemanager.folderAdmin" = [local.automation_sas_iam.teams] + "roles/resourcemanager.projectCreator" = [local.automation_sas_iam.teams] + "roles/compute.xpnAdmin" = [local.automation_sas_iam.teams] + } + tag_bindings = { + context = var.tags.values["${var.tags.names.context}/teams"] + } +} + +module "branch-teams-sa" { + source = "../../../modules/iam-service-account" + count = var.fast_features.teams ? 1 : 0 + project_id = var.automation.project_id + name = "teams-0" + prefix = var.prefix + service_account_create = var.test_skip_data_sources + iam_storage_roles = { + (var.automation.outputs_bucket) = ["roles/storage.admin"] + } +} + +module "branch-teams-gcs" { + source = "../../../modules/gcs" + count = var.fast_features.teams ? 1 : 0 + project_id = var.automation.project_id + name = "prod-resman-teams-0" + prefix = var.prefix + location = var.locations.gcs + storage_class = local.gcs_storage_class + versioning = true + iam = { + "roles/storage.objectAdmin" = [module.branch-teams-sa.0.iam_email] + } +} + +################## per-team folders and automation resources ################## + +module "branch-teams-team-folder" { + source = "../../../modules/folder" + for_each = var.fast_features.teams ? coalesce(var.team_folders, {}) : {} + parent = module.branch-teams-folder.0.id + name = each.value.descriptive_name + iam = { + "roles/logging.admin" = [module.branch-teams-team-sa[each.key].iam_email] + "roles/owner" = [module.branch-teams-team-sa[each.key].iam_email] + "roles/resourcemanager.folderAdmin" = [module.branch-teams-team-sa[each.key].iam_email] + "roles/resourcemanager.projectCreator" = [module.branch-teams-team-sa[each.key].iam_email] + "roles/compute.xpnAdmin" = [module.branch-teams-team-sa[each.key].iam_email] + } + group_iam = each.value.group_iam == null ? {} : each.value.group_iam +} + +module "branch-teams-team-sa" { + source = "../../../modules/iam-service-account" + for_each = var.fast_features.teams ? coalesce(var.team_folders, {}) : {} + project_id = var.automation.project_id + name = "prod-teams-${each.key}-0" + display_name = "Terraform team ${each.key} service account." + prefix = var.prefix + iam = { + "roles/iam.serviceAccountTokenCreator" = ( + each.value.impersonation_groups == null + ? [] + : [for g in each.value.impersonation_groups : "group:${g}"] + ) + } +} + +module "branch-teams-team-gcs" { + source = "../../../modules/gcs" + for_each = var.fast_features.teams ? coalesce(var.team_folders, {}) : {} + project_id = var.automation.project_id + name = "prod-teams-${each.key}-0" + prefix = var.prefix + location = var.locations.gcs + storage_class = local.gcs_storage_class + versioning = true + iam = { + "roles/storage.objectAdmin" = [module.branch-teams-team-sa[each.key].iam_email] + } +} + +# per-team environment folders where project factory SAs can create projects + +module "branch-teams-team-dev-folder" { + source = "../../../modules/folder" + for_each = var.fast_features.teams ? coalesce(var.team_folders, {}) : {} + parent = module.branch-teams-team-folder[each.key].id + # naming: environment descriptive name + name = "Development" + # environment-wide human permissions on the whole teams environment + group_iam = {} + iam = { + (local.custom_roles.service_project_network_admin) = ( + local.branch_optional_sa_lists.pf-dev + ) + # remove owner here and at project level if SA does not manage project resources + "roles/owner" = local.branch_optional_sa_lists.pf-dev + "roles/logging.admin" = local.branch_optional_sa_lists.pf-dev + "roles/resourcemanager.folderAdmin" = local.branch_optional_sa_lists.pf-dev + "roles/resourcemanager.projectCreator" = local.branch_optional_sa_lists.pf-dev + } + tag_bindings = { + environment = try( + var.tags.values["${var.tags.names.environment}/development"], null + ) + } +} + +module "branch-teams-team-prod-folder" { + source = "../../../modules/folder" + for_each = var.fast_features.teams ? coalesce(var.team_folders, {}) : {} + parent = module.branch-teams-team-folder[each.key].id + # naming: environment descriptive name + name = "Production" + # environment-wide human permissions on the whole teams environment + group_iam = {} + iam = { + (local.custom_roles.service_project_network_admin) = ( + local.branch_optional_sa_lists.pf-prod + ) + # remove owner here and at project level if SA does not manage project resources + "roles/owner" = local.branch_optional_sa_lists.pf-prod + "roles/logging.admin" = local.branch_optional_sa_lists.pf-prod + "roles/resourcemanager.folderAdmin" = local.branch_optional_sa_lists.pf-prod + "roles/resourcemanager.projectCreator" = local.branch_optional_sa_lists.pf-prod + } + tag_bindings = { + environment = try( + var.tags.values["${var.tags.names.environment}/production"], null + ) + } +} diff --git a/fast/stages-multitenant/1-resman-tenant/cicd-data-platform.tf b/fast/stages-multitenant/1-resman-tenant/cicd-data-platform.tf new file mode 100644 index 00000000..704f45d7 --- /dev/null +++ b/fast/stages-multitenant/1-resman-tenant/cicd-data-platform.tf @@ -0,0 +1,173 @@ +/** + * 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 CI/CD resources for the data platform branch. + +# source repositories + +module "branch-dp-dev-cicd-repo" { + source = "../../../modules/source-repository" + for_each = ( + try(local.cicd_repositories.data_platform_dev.type, null) == "sourcerepo" + ? { 0 = local.cicd_repositories.data_platform_dev } + : {} + ) + project_id = var.automation.project_id + name = each.value.name + iam = { + "roles/source.admin" = local.branch_optional_sa_lists.dp-dev + "roles/source.reader" = compact([ + try(module.branch-dp-dev-sa-cicd.0.iam_email, "") + ]) + } + triggers = { + fast-03-dp-dev = { + filename = ".cloudbuild/workflow.yaml" + included_files = [ + "**/*json", "**/*tf", "**/*yaml", ".cloudbuild/workflow.yaml" + ] + service_account = module.branch-dp-dev-sa-cicd.0.id + substitutions = {} + template = { + project_id = null + branch_name = each.value.branch + repo_name = each.value.name + tag_name = null + } + } + } + depends_on = [module.branch-dp-dev-sa-cicd] +} + +module "branch-dp-prod-cicd-repo" { + source = "../../../modules/source-repository" + for_each = ( + try(local.cicd_repositories.data_platform_prod.type, null) == "sourcerepo" + ? { 0 = local.cicd_repositories.data_platform_prod } + : {} + ) + project_id = var.automation.project_id + name = each.value.name + iam = { + "roles/source.admin" = local.branch_optional_sa_lists.dp-prod + "roles/source.reader" = [module.branch-dp-prod-sa-cicd.0.iam_email] + } + triggers = { + fast-03-dp-prod = { + filename = ".cloudbuild/workflow.yaml" + included_files = [ + "**/*json", "**/*tf", "**/*yaml", ".cloudbuild/workflow.yaml" + ] + service_account = module.branch-dp-prod-sa-cicd.0.id + substitutions = {} + template = { + project_id = null + branch_name = each.value.branch + repo_name = each.value.name + tag_name = null + } + } + } + depends_on = [module.branch-dp-prod-sa-cicd] +} + +# SAs used by CI/CD workflows to impersonate automation SAs + +module "branch-dp-dev-sa-cicd" { + source = "../../../modules/iam-service-account" + for_each = ( + try(local.cicd_repositories.data_platform_dev.name, null) != null + ? { 0 = local.cicd_repositories.data_platform_dev } + : {} + ) + project_id = var.automation.project_id + name = "dev-resman-dp-1" + display_name = "Terraform CI/CD data platform development service account." + prefix = var.prefix + iam = ( + each.value.type == "sourcerepo" + # used directly from the cloud build trigger for source repos + ? { + "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam + } + # impersonated via workload identity federation for external repos + : { + "roles/iam.workloadIdentityUser" = [ + each.value.branch == null + ? format( + local.cicd_identity_providers[each.value.identity_provider].principalset_tpl, + local.cicd_identity_pools[each.value.identity_provider], + each.value.name + ) + : format( + local.cicd_identity_providers[each.value.identity_provider].principal_tpl, + local.cicd_identity_pools[each.value.identity_provider], + each.value.name, + each.value.branch + ) + ] + } + ) + iam_project_roles = { + (var.automation.project_id) = ["roles/logging.logWriter"] + } + iam_storage_roles = { + (var.automation.outputs_bucket) = ["roles/storage.objectViewer"] + } +} + +module "branch-dp-prod-sa-cicd" { + source = "../../../modules/iam-service-account" + for_each = ( + try(local.cicd_repositories.data_platform_prod.name, null) != null + ? { 0 = local.cicd_repositories.data_platform_prod } + : {} + ) + project_id = var.automation.project_id + name = "prod-resman-dp-1" + display_name = "Terraform CI/CD data platform production service account." + prefix = var.prefix + iam = ( + each.value.type == "sourcerepo" + # used directly from the cloud build trigger for source repos + ? { + "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam + } + # impersonated via workload identity federation for external repos + : { + "roles/iam.workloadIdentityUser" = [ + each.value.branch == null + ? format( + local.cicd_identity_providers[each.value.identity_provider].principalset_tpl, + local.cicd_identity_pools[each.value.identity_provider], + each.value.name + ) + : format( + local.cicd_identity_providers[each.value.identity_provider].principal_tpl, + local.cicd_identity_pools[each.value.identity_provider], + each.value.name, + each.value.branch + ) + ] + } + ) + iam_project_roles = { + (var.automation.project_id) = ["roles/logging.logWriter"] + } + iam_storage_roles = { + (var.automation.outputs_bucket) = ["roles/storage.objectViewer"] + } +} diff --git a/fast/stages-multitenant/1-resman-tenant/cicd-gke.tf b/fast/stages-multitenant/1-resman-tenant/cicd-gke.tf new file mode 100644 index 00000000..dfd035a5 --- /dev/null +++ b/fast/stages-multitenant/1-resman-tenant/cicd-gke.tf @@ -0,0 +1,175 @@ +/** + * 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 CI/CD resources for the data platform branch. + +# source repositories + +module "branch-gke-dev-cicd-repo" { + source = "../../../modules/source-repository" + for_each = ( + try(local.cicd_repositories.gke_dev.type, null) == "sourcerepo" + ? { 0 = local.cicd_repositories.gke_dev } + : {} + ) + project_id = var.automation.project_id + name = each.value.name + iam = { + "roles/source.admin" = compact([ + try(module.branch-gke-dev-sa.0.iam_email, "") + ]) + "roles/source.reader" = compact([ + try(module.branch-gke-dev-sa-cicd.0.iam_email, "") + ]) + } + triggers = { + fast-03-gke-dev = { + filename = ".cloudbuild/workflow.yaml" + included_files = [ + "**/*json", "**/*tf", "**/*yaml", ".cloudbuild/workflow.yaml" + ] + service_account = module.branch-gke-dev-sa-cicd.0.id + substitutions = {} + template = { + project_id = null + branch_name = each.value.branch + repo_name = each.value.name + tag_name = null + } + } + } + depends_on = [module.branch-gke-dev-sa-cicd] +} + +module "branch-gke-prod-cicd-repo" { + source = "../../../modules/source-repository" + for_each = ( + try(local.cicd_repositories.gke_prod.type, null) == "sourcerepo" + ? { 0 = local.cicd_repositories.gke_prod } + : {} + ) + project_id = var.automation.project_id + name = each.value.name + iam = { + "roles/source.admin" = [module.branch-gke-prod-sa.0.iam_email] + "roles/source.reader" = [module.branch-gke-prod-sa-cicd.0.iam_email] + } + triggers = { + fast-03-gke-prod = { + filename = ".cloudbuild/workflow.yaml" + included_files = [ + "**/*json", "**/*tf", "**/*yaml", ".cloudbuild/workflow.yaml" + ] + service_account = module.branch-gke-prod-sa-cicd.0.id + substitutions = {} + template = { + project_id = null + branch_name = each.value.branch + repo_name = each.value.name + tag_name = null + } + } + } + depends_on = [module.branch-gke-prod-sa-cicd] +} + +# SAs used by CI/CD workflows to impersonate automation SAs + +module "branch-gke-dev-sa-cicd" { + source = "../../../modules/iam-service-account" + for_each = ( + try(local.cicd_repositories.gke_dev.name, null) != null + ? { 0 = local.cicd_repositories.gke_dev } + : {} + ) + project_id = var.automation.project_id + name = "dev-resman-gke-1" + display_name = "Terraform CI/CD GKE development service account." + prefix = var.prefix + iam = ( + each.value.type == "sourcerepo" + # used directly from the cloud build trigger for source repos + ? { + "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam + } + # impersonated via workload identity federation for external repos + : { + "roles/iam.workloadIdentityUser" = [ + each.value.branch == null + ? format( + local.cicd_identity_providers[each.value.identity_provider].principalset_tpl, + local.cicd_identity_pools[each.value.identity_provider], + each.value.name + ) + : format( + local.cicd_identity_providers[each.value.identity_provider].principal_tpl, + local.cicd_identity_pools[each.value.identity_provider], + each.value.name, + each.value.branch + ) + ] + } + ) + iam_project_roles = { + (var.automation.project_id) = ["roles/logging.logWriter"] + } + iam_storage_roles = { + (var.automation.outputs_bucket) = ["roles/storage.objectViewer"] + } +} + +module "branch-gke-prod-sa-cicd" { + source = "../../../modules/iam-service-account" + for_each = ( + try(local.cicd_repositories.gke_prod.name, null) != null + ? { 0 = local.cicd_repositories.gke_prod } + : {} + ) + project_id = var.automation.project_id + name = "prod-resman-gke-1" + display_name = "Terraform CI/CD GKE production service account." + prefix = var.prefix + iam = ( + each.value.type == "sourcerepo" + # used directly from the cloud build trigger for source repos + ? { + "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam + } + # impersonated via workload identity federation for external repos + : { + "roles/iam.workloadIdentityUser" = [ + each.value.branch == null + ? format( + local.cicd_identity_providers[each.value.identity_provider].principalset_tpl, + local.cicd_identity_pools[each.value.identity_provider], + each.value.name + ) + : format( + local.cicd_identity_providers[each.value.identity_provider].principal_tpl, + local.cicd_identity_pools[each.value.identity_provider], + each.value.name, + each.value.branch + ) + ] + } + ) + iam_project_roles = { + (var.automation.project_id) = ["roles/logging.logWriter"] + } + iam_storage_roles = { + (var.automation.outputs_bucket) = ["roles/storage.objectViewer"] + } +} diff --git a/fast/stages-multitenant/1-resman-tenant/cicd-networking.tf b/fast/stages-multitenant/1-resman-tenant/cicd-networking.tf new file mode 100644 index 00000000..dbaf587d --- /dev/null +++ b/fast/stages-multitenant/1-resman-tenant/cicd-networking.tf @@ -0,0 +1,94 @@ +/** + * 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 CI/CD resources for the networking branch. + +# source repository + +module "branch-network-cicd-repo" { + source = "../../../modules/source-repository" + for_each = ( + try(local.cicd_repositories.networking.type, null) == "sourcerepo" + ? { 0 = local.cicd_repositories.networking } + : {} + ) + project_id = var.automation.project_id + name = each.value.name + iam = { + "roles/source.admin" = [module.branch-network-sa.iam_email] + "roles/source.reader" = [module.branch-network-sa-cicd.0.iam_email] + } + triggers = { + fast-02-networking = { + filename = ".cloudbuild/workflow.yaml" + included_files = ["**/*tf", ".cloudbuild/workflow.yaml"] + service_account = module.branch-network-sa-cicd.0.id + substitutions = {} + template = { + project_id = null + branch_name = each.value.branch + repo_name = each.value.name + tag_name = null + } + } + } + depends_on = [module.branch-network-sa-cicd] +} + +# SA used by CI/CD workflows to impersonate automation SAs + +module "branch-network-sa-cicd" { + source = "../../../modules/iam-service-account" + for_each = ( + try(local.cicd_repositories.networking.name, null) != null + ? { 0 = local.cicd_repositories.networking } + : {} + ) + project_id = var.automation.project_id + name = "prod-resman-net-1" + display_name = "Terraform CI/CD stage 2 networking service account." + prefix = var.prefix + iam = ( + each.value.type == "sourcerepo" + # used directly from the cloud build trigger for source repos + ? { + "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam + } + # impersonated via workload identity federation for external repos + : { + "roles/iam.workloadIdentityUser" = [ + each.value.branch == null + ? format( + local.cicd_identity_providers[each.value.identity_provider].principalset_tpl, + local.cicd_identity_pools[each.value.identity_provider], + each.value.name + ) + : format( + local.cicd_identity_providers[each.value.identity_provider].principal_tpl, + local.cicd_identity_pools[each.value.identity_provider], + each.value.name, + each.value.branch + ) + ] + } + ) + iam_project_roles = { + (var.automation.project_id) = ["roles/logging.logWriter"] + } + iam_storage_roles = { + (var.automation.outputs_bucket) = ["roles/storage.objectViewer"] + } +} diff --git a/fast/stages-multitenant/1-resman-tenant/cicd-project-factory.tf b/fast/stages-multitenant/1-resman-tenant/cicd-project-factory.tf new file mode 100644 index 00000000..4c46d858 --- /dev/null +++ b/fast/stages-multitenant/1-resman-tenant/cicd-project-factory.tf @@ -0,0 +1,191 @@ +/** + * 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 CI/CD resources for the teams branch. + +# source repositories + +moved { + from = module.branch-teams-dev-pf-cicd-repo + to = module.branch-pf-dev-cicd-repo +} + +module "branch-pf-dev-cicd-repo" { + source = "../../../modules/source-repository" + for_each = ( + try(local.cicd_repositories.project_factory_dev.type, null) == "sourcerepo" + ? { 0 = local.cicd_repositories.project_factory_dev } + : {} + ) + project_id = var.automation.project_id + name = each.value.name + iam = { + "roles/source.admin" = local.branch_optional_sa_lists.pf-dev + "roles/source.reader" = [module.branch-pf-dev-sa-cicd.0.iam_email] + } + triggers = { + fast-03-pf-dev = { + filename = ".cloudbuild/workflow.yaml" + included_files = [ + "**/*json", "**/*tf", "**/*yaml", ".cloudbuild/workflow.yaml" + ] + service_account = module.branch-pf-dev-sa-cicd.0.id + substitutions = {} + template = { + project_id = null + branch_name = each.value.branch + repo_name = each.value.name + tag_name = null + } + } + } + depends_on = [module.branch-pf-dev-sa-cicd] +} + +moved { + from = module.branch-teams-prod-pf-cicd-repo + to = module.branch-pf-prod-cicd-repo +} + +module "branch-pf-prod-cicd-repo" { + source = "../../../modules/source-repository" + for_each = ( + try(local.cicd_repositories.project_factory_prod.type, null) == "sourcerepo" + ? { 0 = local.cicd_repositories.project_factory_prod } + : {} + ) + project_id = var.automation.project_id + name = each.value.name + iam = { + "roles/source.admin" = local.branch_optional_sa_lists.pf-prod + "roles/source.reader" = [module.branch-pf-prod-sa-cicd.0.iam_email] + } + triggers = { + fast-03-pf-prod = { + filename = ".cloudbuild/workflow.yaml" + included_files = [ + "**/*json", "**/*tf", "**/*yaml", ".cloudbuild/workflow.yaml" + ] + service_account = module.branch-pf-prod-sa-cicd.0.id + substitutions = {} + template = { + project_id = null + branch_name = each.value.branch + repo_name = each.value.name + tag_name = null + } + } + } + depends_on = [module.branch-pf-prod-sa-cicd] +} + +# SAs used by CI/CD workflows to impersonate automation SAs + +moved { + from = module.branch-teams-dev-pf-sa-cicd + to = module.branch-pf-dev-sa-cicd +} + +module "branch-pf-dev-sa-cicd" { + source = "../../../modules/iam-service-account" + for_each = ( + try(local.cicd_repositories.project_factory_dev.name, null) != null + ? { 0 = local.cicd_repositories.project_factory_dev } + : {} + ) + project_id = var.automation.project_id + name = "dev-pf-resman-pf-1" + display_name = "Terraform CI/CD project factory development service account." + prefix = var.prefix + iam = ( + each.value.type == "sourcerepo" + # used directly from the cloud build trigger for source repos + ? { + "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam + } + # impersonated via workload identity federation for external repos + : { + "roles/iam.workloadIdentityUser" = [ + each.value.branch == null + ? format( + local.cicd_identity_providers[each.value.identity_provider].principalset_tpl, + local.cicd_identity_pools[each.value.identity_provider], + each.value.name + ) + : format( + local.cicd_identity_providers[each.value.identity_provider].principal_tpl, + local.cicd_identity_pools[each.value.identity_provider], + each.value.name, + each.value.branch + ) + ] + } + ) + iam_project_roles = { + (var.automation.project_id) = ["roles/logging.logWriter"] + } + iam_storage_roles = { + (var.automation.outputs_bucket) = ["roles/storage.objectViewer"] + } +} + +moved { + from = module.branch-teams-prod-pf-sa-cicd + to = module.branch-pf-prod-sa-cicd +} + +module "branch-pf-prod-sa-cicd" { + source = "../../../modules/iam-service-account" + for_each = ( + try(local.cicd_repositories.project_factory_prod.name, null) != null + ? { 0 = local.cicd_repositories.project_factory_prod } + : {} + ) + project_id = var.automation.project_id + name = "prod-pf-resman-pf-1" + display_name = "Terraform CI/CD project factory production service account." + prefix = var.prefix + iam = ( + each.value.type == "sourcerepo" + # used directly from the cloud build trigger for source repos + ? { + "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam + } + # impersonated via workload identity federation for external repos + : { + "roles/iam.workloadIdentityUser" = [ + each.value.branch == null + ? format( + local.cicd_identity_providers[each.value.identity_provider].principalset_tpl, + var.automation.federated_identity_pool, + each.value.name + ) + : format( + local.cicd_identity_providers[each.value.identity_provider].principal_tpl, + var.automation.federated_identity_pool, + each.value.name, + each.value.branch + ) + ] + } + ) + iam_project_roles = { + (var.automation.project_id) = ["roles/logging.logWriter"] + } + iam_storage_roles = { + (var.automation.outputs_bucket) = ["roles/storage.objectViewer"] + } +} diff --git a/fast/stages-multitenant/1-resman-tenant/cicd-security.tf b/fast/stages-multitenant/1-resman-tenant/cicd-security.tf new file mode 100644 index 00000000..5cb1581c --- /dev/null +++ b/fast/stages-multitenant/1-resman-tenant/cicd-security.tf @@ -0,0 +1,94 @@ +/** + * 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 CI/CD resources for the security branch. + +# source repository + +module "branch-security-cicd-repo" { + source = "../../../modules/source-repository" + for_each = ( + try(local.cicd_repositories.security.type, null) == "sourcerepo" + ? { 0 = local.cicd_repositories.security } + : {} + ) + project_id = var.automation.project_id + name = each.value.name + iam = { + "roles/source.admin" = [module.branch-security-sa.iam_email] + "roles/source.reader" = [module.branch-security-sa-cicd.0.iam_email] + } + triggers = { + fast-02-security = { + filename = ".cloudbuild/workflow.yaml" + included_files = ["**/*tf", ".cloudbuild/workflow.yaml"] + service_account = module.branch-security-sa-cicd.0.id + substitutions = {} + template = { + project_id = null + branch_name = each.value.branch + repo_name = each.value.name + tag_name = null + } + } + } + depends_on = [module.branch-security-sa-cicd] +} + +# SA used by CI/CD workflows to impersonate automation SAs + +module "branch-security-sa-cicd" { + source = "../../../modules/iam-service-account" + for_each = ( + try(local.cicd_repositories.security.name, null) != null + ? { 0 = local.cicd_repositories.security } + : {} + ) + project_id = var.automation.project_id + name = "prod-resman-sec-1" + display_name = "Terraform CI/CD stage 2 security service account." + prefix = var.prefix + iam = ( + each.value.type == "sourcerepo" + # used directly from the cloud build trigger for source repos + ? { + "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam + } + # impersonated via workload identity federation for external repos + : { + "roles/iam.workloadIdentityUser" = [ + each.value.branch == null + ? format( + local.cicd_identity_providers[each.value.identity_provider].principalset_tpl, + local.cicd_identity_pools[each.value.identity_provider], + each.value.name + ) + : format( + local.cicd_identity_providers[each.value.identity_provider].principal_tpl, + local.cicd_identity_pools[each.value.identity_provider], + each.value.name, + each.value.branch + ) + ] + } + ) + iam_project_roles = { + (var.automation.project_id) = ["roles/logging.logWriter"] + } + iam_storage_roles = { + (var.automation.outputs_bucket) = ["roles/storage.objectViewer"] + } +} diff --git a/fast/stages-multitenant/1-resman-tenant/data/org-policies/compute.yaml b/fast/stages-multitenant/1-resman-tenant/data/org-policies/compute.yaml new file mode 100644 index 00000000..0d27ac42 --- /dev/null +++ b/fast/stages-multitenant/1-resman-tenant/data/org-policies/compute.yaml @@ -0,0 +1,73 @@ +# skip boilerplate check +# +# sample subset of useful organization policies, edit to suit requirements + +compute.disableGuestAttributesAccess: + enforce: true + +compute.requireOsLogin: + enforce: true + +compute.restrictLoadBalancerCreationForTypes: + allow: + values: + - in:INTERNAL + +compute.skipDefaultNetworkCreation: + enforce: true + +compute.vmExternalIpAccess: + deny: + all: true + + +# compute.disableInternetNetworkEndpointGroup: +# enforce: true + +# compute.disableNestedVirtualization: +# enforce: true + +# compute.disableSerialPortAccess: +# enforce: true + +# compute.restrictCloudNATUsage: +# deny: +# all: true + +# compute.restrictDedicatedInterconnectUsage: +# deny: +# all: true + +# compute.restrictPartnerInterconnectUsage: +# deny: +# all: true + +# compute.restrictProtocolForwardingCreationForTypes: +# deny: +# all: true + +# compute.restrictSharedVpcHostProjects: +# deny: +# all: true + +# compute.restrictSharedVpcSubnetworks: +# deny: +# all: true + +# compute.restrictVpcPeering: +# deny: +# all: true + +# compute.restrictVpnPeerIPs: +# deny: +# all: true + +# compute.restrictXpnProjectLienRemoval: +# enforce: true + +# compute.setNewProjectDefaultToZonalDNSOnly: +# enforce: true + +# compute.vmCanIpForward: +# deny: +# all: true diff --git a/fast/stages-multitenant/1-resman-tenant/data/org-policies/iam.yaml b/fast/stages-multitenant/1-resman-tenant/data/org-policies/iam.yaml new file mode 100644 index 00000000..4d83f827 --- /dev/null +++ b/fast/stages-multitenant/1-resman-tenant/data/org-policies/iam.yaml @@ -0,0 +1,12 @@ +# skip boilerplate check +# +# sample subset of useful organization policies, edit to suit requirements + +iam.automaticIamGrantsForDefaultServiceAccounts: + enforce: true + +iam.disableServiceAccountKeyCreation: + enforce: true + +iam.disableServiceAccountKeyUpload: + enforce: true diff --git a/fast/stages-multitenant/1-resman-tenant/data/org-policies/serverless.yaml b/fast/stages-multitenant/1-resman-tenant/data/org-policies/serverless.yaml new file mode 100644 index 00000000..de62e6c7 --- /dev/null +++ b/fast/stages-multitenant/1-resman-tenant/data/org-policies/serverless.yaml @@ -0,0 +1,26 @@ +# skip boilerplate check +# +# sample subset of useful organization policies, edit to suit requirements + +run.allowedIngress: + allow: + values: + - is:internal + +# run.allowedVPCEgress: +# allow: +# values: +# - is:private-ranges-only + +# cloudfunctions.allowedIngressSettings: +# allow: +# values: +# - is:ALLOW_INTERNAL_ONLY + +# cloudfunctions.allowedVpcConnectorEgressSettings: +# allow: +# values: +# - is:PRIVATE_RANGES_ONLY + +# cloudfunctions.requireVPCConnector: +# enforce: true diff --git a/fast/stages-multitenant/1-resman-tenant/data/org-policies/sql.yaml b/fast/stages-multitenant/1-resman-tenant/data/org-policies/sql.yaml new file mode 100644 index 00000000..88b84d9d --- /dev/null +++ b/fast/stages-multitenant/1-resman-tenant/data/org-policies/sql.yaml @@ -0,0 +1,9 @@ +# skip boilerplate check +# +# sample subset of useful organization policies, edit to suit requirements + +sql.restrictAuthorizedNetworks: + enforce: true + +sql.restrictPublicIp: + enforce: true diff --git a/fast/stages-multitenant/1-resman-tenant/data/org-policies/storage.yaml b/fast/stages-multitenant/1-resman-tenant/data/org-policies/storage.yaml new file mode 100644 index 00000000..6c0a673f --- /dev/null +++ b/fast/stages-multitenant/1-resman-tenant/data/org-policies/storage.yaml @@ -0,0 +1,6 @@ +# skip boilerplate check +# +# sample subset of useful organization policies, edit to suit requirements + +storage.uniformBucketLevelAccess: + enforce: true diff --git a/fast/stages/01-resman/diagram.png b/fast/stages-multitenant/1-resman-tenant/diagram.png similarity index 100% rename from fast/stages/01-resman/diagram.png rename to fast/stages-multitenant/1-resman-tenant/diagram.png diff --git a/fast/stages/01-resman/diagram.svg b/fast/stages-multitenant/1-resman-tenant/diagram.svg similarity index 100% rename from fast/stages/01-resman/diagram.svg rename to fast/stages-multitenant/1-resman-tenant/diagram.svg diff --git a/fast/stages-multitenant/1-resman-tenant/main.tf b/fast/stages-multitenant/1-resman-tenant/main.tf new file mode 100644 index 00000000..76c04639 --- /dev/null +++ b/fast/stages-multitenant/1-resman-tenant/main.tf @@ -0,0 +1,79 @@ +/** + * 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 { + automation_resman_sa_iam = [ + "serviceAccount:${var.automation.service_accounts.resman}" + ] + automation_sas_iam = { + for k, v in var.automation.service_accounts : + k => v == null ? null : "serviceAccount:${v}" + } + branch_optional_sa_lists = { + dp-dev = compact([local.automation_sas_iam.dp-dev]) + dp-prod = compact([local.automation_sas_iam.dp-prod]) + gke-dev = compact([local.automation_sas_iam.gke-dev]) + gke-prod = compact([local.automation_sas_iam.gke-prod]) + pf-dev = compact([local.automation_sas_iam.pf-dev]) + pf-prod = compact([local.automation_sas_iam.pf-prod]) + } + # derive identity pool names from identity providers for easy reference + cicd_identity_pools = { + for k, v in local.cicd_identity_providers : + k => split("/providers/", v.name)[0] + } + cicd_identity_providers = coalesce( + try(var.automation.federated_identity_providers, null), {} + ) + cicd_repositories = { + for k, v in coalesce(var.cicd_repositories, {}) : k => v + if( + v != null && + ( + try(v.type, null) == "sourcerepo" + || + contains( + keys(local.cicd_identity_providers), + coalesce(try(v.identity_provider, null), ":") + ) + ) && + fileexists("${path.module}/templates/workflow-${try(v.type, "")}.yaml") + ) + } + cicd_workflow_var_files = { + stage_2 = [ + "0-bootstrap-tenant.auto.tfvars.json", + ] + stage_3 = [ + "0-bootstrap-tenant.auto.tfvars.json", + "2-networking.auto.tfvars.json", + "2-security.auto.tfvars.json" + ] + } + custom_roles = coalesce(var.custom_roles, {}) + gcs_storage_class = ( + length(split("-", var.locations.gcs)) < 2 + ? "MULTI_REGIONAL" + : "REGIONAL" + ) + groups = { + for k, v in var.groups : + k => v == null ? null : "${v}@${var.organization.domain}" + } + groups_iam = { + for k, v in local.groups : k => v != null ? "group:${v}" : null + } +} diff --git a/fast/stages-multitenant/1-resman-tenant/outputs-files.tf b/fast/stages-multitenant/1-resman-tenant/outputs-files.tf new file mode 100644 index 00000000..29d5ed46 --- /dev/null +++ b/fast/stages-multitenant/1-resman-tenant/outputs-files.tf @@ -0,0 +1,46 @@ +/** + * 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 Output files persistence to local filesystem. + +locals { + outputs_root = join("/", [ + try(pathexpand(var.outputs_location), ""), + "tenants", + var.short_name + ]) +} + +resource "local_file" "providers" { + for_each = var.outputs_location == null ? {} : local.providers + file_permission = "0644" + filename = "${local.outputs_root}/providers/${each.key}-providers.tf" + content = try(each.value, null) +} + +resource "local_file" "tfvars" { + count = var.outputs_location == null ? 0 : 1 + file_permission = "0644" + filename = "${local.outputs_root}/tfvars/1-resman.auto.tfvars.json" + content = jsonencode(local.tfvars) +} + +resource "local_file" "workflows" { + for_each = var.outputs_location == null ? {} : local.cicd_workflows + file_permission = "0644" + filename = "${local.outputs_root}/workflows/${replace(each.key, "_", "-")}-workflow.yaml" + content = try(each.value, null) +} diff --git a/fast/stages-multitenant/1-resman-tenant/outputs-gcs.tf b/fast/stages-multitenant/1-resman-tenant/outputs-gcs.tf new file mode 100644 index 00000000..6b0fc89c --- /dev/null +++ b/fast/stages-multitenant/1-resman-tenant/outputs-gcs.tf @@ -0,0 +1,37 @@ +/** + * 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 Output files persistence to automation GCS bucket. + +resource "google_storage_bucket_object" "providers" { + for_each = local.providers + bucket = var.automation.outputs_bucket + name = "providers/${each.key}-providers.tf" + content = each.value +} + +resource "google_storage_bucket_object" "tfvars" { + bucket = var.automation.outputs_bucket + name = "tfvars/1-resman.auto.tfvars.json" + content = jsonencode(local.tfvars) +} + +resource "google_storage_bucket_object" "workflows" { + for_each = local.cicd_workflows + bucket = var.automation.outputs_bucket + name = "workflows/${replace(each.key, "_", "-")}-workflow.yaml" + content = each.value +} diff --git a/fast/stages-multitenant/1-resman-tenant/outputs.tf b/fast/stages-multitenant/1-resman-tenant/outputs.tf new file mode 100644 index 00000000..592f995e --- /dev/null +++ b/fast/stages-multitenant/1-resman-tenant/outputs.tf @@ -0,0 +1,311 @@ +/** + * 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 { + _tpl_providers = "${path.module}/templates/providers.tf.tpl" + cicd_workflow_attrs = { + data_platform_dev = { + service_account = try(module.branch-dp-dev-sa-cicd.0.email, null) + tf_providers_file = "3-data-platform-dev-providers.tf" + tf_var_files = local.cicd_workflow_var_files.stage_3 + } + data_platform_prod = { + service_account = try(module.branch-dp-prod-sa-cicd.0.email, null) + tf_providers_file = "3-data-platform-prod-providers.tf" + tf_var_files = local.cicd_workflow_var_files.stage_3 + } + gke_dev = { + service_account = try(module.branch-gke-dev-sa-cicd.0.email, null) + tf_providers_file = "3-gke-dev-providers.tf" + tf_var_files = local.cicd_workflow_var_files.stage_3 + } + gke_prod = { + service_account = try(module.branch-gke-prod-sa-cicd.0.email, null) + tf_providers_file = "3-gke-prod-providers.tf" + tf_var_files = local.cicd_workflow_var_files.stage_3 + } + networking = { + service_account = try(module.branch-network-sa-cicd.0.email, null) + tf_providers_file = "2-networking-providers.tf" + tf_var_files = local.cicd_workflow_var_files.stage_2 + } + project_factory_dev = { + service_account = try(module.branch-pf-dev-sa-cicd.0.email, null) + tf_providers_file = "3-project-factory-dev-providers.tf" + tf_var_files = local.cicd_workflow_var_files.stage_3 + } + project_factory_prod = { + service_account = try(module.branch-pf-prod-sa-cicd.0.email, null) + tf_providers_file = "3-project-factory-prod-providers.tf" + tf_var_files = local.cicd_workflow_var_files.stage_3 + } + security = { + service_account = try(module.branch-security-sa-cicd.0.email, null) + tf_providers_file = "2-security-providers.tf" + tf_var_files = local.cicd_workflow_var_files.stage_2 + } + } + cicd_workflows = { + for k, v in local.cicd_repositories : k => templatefile( + "${path.module}/templates/workflow-${v.type}.yaml", + merge(local.cicd_workflow_attrs[k], { + identity_provider = try( + local.cicd_identity_providers[v.identity_provider].name, null + ) + outputs_bucket = var.automation.outputs_bucket + stage_name = k + }) + ) + } + folder_ids = merge( + { + data-platform-dev = try(module.branch-dp-dev-folder.0.id, null) + data-platform-prod = try(module.branch-dp-prod-folder.0.id, null) + gke-dev = try(module.branch-gke-dev-folder.0.id, null) + gke-prod = try(module.branch-gke-prod-folder.0.id, null) + networking = module.branch-network-folder.id + networking-dev = module.branch-network-dev-folder.id + networking-prod = module.branch-network-prod-folder.id + sandbox = try(module.branch-sandbox-folder.0.id, null) + security = module.branch-security-folder.id + teams = try(module.branch-teams-folder.0.id, null) + }, + { + for k, v in module.branch-teams-team-folder : + "team-${k}" => v.id + }, + { + for k, v in module.branch-teams-team-dev-folder : + "team-${k}-dev" => v.id + }, + { + for k, v in module.branch-teams-team-prod-folder : + "team-${k}-prod" => v.id + } + ) + providers = merge( + { + "2-networking" = templatefile(local._tpl_providers, { + backend_extra = null + bucket = module.branch-network-gcs.name + name = "networking" + sa = module.branch-network-sa.email + }) + "2-security" = templatefile(local._tpl_providers, { + backend_extra = null + bucket = module.branch-security-gcs.name + name = "security" + sa = module.branch-security-sa.email + }) + }, + !var.fast_features.data_platform ? {} : { + "3-data-platform-dev" = templatefile(local._tpl_providers, { + backend_extra = null + bucket = module.branch-dp-dev-gcs.0.name + name = "dp-dev" + sa = module.branch-dp-dev-sa.0.email + }) + "3-data-platform-prod" = templatefile(local._tpl_providers, { + backend_extra = null + bucket = module.branch-dp-prod-gcs.0.name + name = "dp-prod" + sa = module.branch-dp-prod-sa.0.email + }) + }, + !var.fast_features.gke ? {} : { + "3-gke-dev" = templatefile(local._tpl_providers, { + backend_extra = null + bucket = module.branch-gke-dev-gcs.0.name + name = "gke-dev" + sa = module.branch-gke-dev-sa.0.email + }) + "3-gke-prod" = templatefile(local._tpl_providers, { + backend_extra = null + bucket = module.branch-gke-prod-gcs.0.name + name = "gke-prod" + sa = module.branch-gke-prod-sa.0.email + }) + }, + !var.fast_features.project_factory ? {} : { + "3-project-factory-dev" = templatefile(local._tpl_providers, { + backend_extra = null + bucket = module.branch-pf-dev-gcs.0.name + name = "team-dev" + sa = var.automation.service_accounts.pf-dev + }) + "3-project-factory-prod" = templatefile(local._tpl_providers, { + backend_extra = null + bucket = module.branch-pf-prod-gcs.0.name + name = "team-prod" + sa = var.automation.service_accounts.pf-prod + }) + }, + !var.fast_features.sandbox ? {} : { + "9-sandbox" = templatefile(local._tpl_providers, { + backend_extra = null + bucket = module.branch-sandbox-gcs.0.name + name = "sandbox" + sa = var.automation.service_accounts.sandbox + }) + }, + !var.fast_features.teams ? {} : merge( + { + "3-teams" = templatefile(local._tpl_providers, { + backend_extra = null + bucket = module.branch-teams-gcs.0.name + name = "teams" + sa = module.branch-teams-sa.0.email + }) + }, + { + for k, v in module.branch-teams-team-sa : + "3-teams-${k}" => templatefile(local._tpl_providers, { + backend_extra = null + bucket = module.branch-teams-team-gcs[k].name + name = "teams" + sa = v.email + }) + } + ) + ) + tfvars = { + folder_ids = local.folder_ids + } +} + +output "cicd_repositories" { + description = "WIF configuration for CI/CD repositories." + value = { + for k, v in local.cicd_repositories : k => { + branch = v.branch + name = v.name + provider = try( + local.cicd_identity_providers[v.identity_provider].name, null + ) + service_account = local.cicd_workflow_attrs[k].service_account + } if v != null + } +} + +output "dataplatform" { + description = "Data for the Data Platform stage." + value = !var.fast_features.data_platform ? {} : { + dev = { + folder = module.branch-dp-dev-folder.0.id + gcs_bucket = module.branch-dp-dev-gcs.0.name + service_account = module.branch-dp-dev-sa.0.email + } + prod = { + folder = module.branch-dp-prod-folder.0.id + gcs_bucket = module.branch-dp-prod-gcs.0.name + service_account = module.branch-dp-prod-sa.0.email + } + } +} + +output "gke_multitenant" { + # tfdoc:output:consumers 03-gke-multitenant + description = "Data for the GKE multitenant stage." + value = ( + var.fast_features.gke + ? { + "dev" = { + folder = module.branch-gke-dev-folder.0.id + gcs_bucket = module.branch-gke-dev-gcs.0.name + service_account = module.branch-gke-dev-sa.0.email + } + "prod" = { + folder = module.branch-gke-prod-folder.0.id + gcs_bucket = module.branch-gke-prod-gcs.0.name + service_account = module.branch-gke-prod-sa.0.email + } + } + : {} + ) +} + +output "networking" { + description = "Data for the networking stage." + value = { + folder = module.branch-network-folder.id + gcs_bucket = module.branch-network-gcs.name + service_account = module.branch-network-sa.iam_email + } +} + +output "project_factories" { + description = "Data for the project factories stage." + value = !var.fast_features.project_factory ? {} : { + dev = { + bucket = module.branch-pf-dev-gcs.0.name + sa = var.automation.service_accounts.pf-dev + } + prod = { + bucket = module.branch-pf-prod-gcs.0.name + sa = var.automation.service_accounts.pf-prod + } + } +} + +# ready to use provider configurations for subsequent stages +output "providers" { + # tfdoc:output:consumers 02-networking 02-security 03-dataplatform xx-sandbox xx-teams + description = "Terraform provider files for this stage and dependent stages." + sensitive = true + value = local.providers +} + +output "sandbox" { + # tfdoc:output:consumers xx-sandbox + description = "Data for the sandbox stage." + value = ( + var.fast_features.sandbox + ? { + folder = module.branch-sandbox-folder.0.id + gcs_bucket = module.branch-sandbox-gcs.0.name + service_account = var.automation.service_accounts.sandbox + } + : null + ) +} + +output "security" { + # tfdoc:output:consumers 02-security + description = "Data for the networking stage." + value = { + folder = module.branch-security-folder.id + gcs_bucket = module.branch-security-gcs.name + service_account = module.branch-security-sa.iam_email + } +} + +output "teams" { + description = "Data for the teams stage." + value = { + for k, v in module.branch-teams-team-folder : k => { + folder = v.id + gcs_bucket = module.branch-teams-team-gcs[k].name + service_account = module.branch-teams-team-sa[k].email + } + } +} + +# ready to use variable values for subsequent stages +output "tfvars" { + description = "Terraform variable files for the following stages." + sensitive = true + value = local.tfvars +} diff --git a/fast/stages-multitenant/1-resman-tenant/root_node.tf b/fast/stages-multitenant/1-resman-tenant/root_node.tf new file mode 100644 index 00000000..5b83d2dd --- /dev/null +++ b/fast/stages-multitenant/1-resman-tenant/root_node.tf @@ -0,0 +1,41 @@ +/** + * 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 Tenant root folder configuration. + +module "root-folder" { + source = "../../../modules/folder" + id = var.root_node + folder_create = var.test_skip_data_sources + # start test attributes + parent = ( + var.test_skip_data_sources ? "organizations/${var.organization.id}" : null + ) + name = var.test_skip_data_sources ? "Test" : null + # end test attributes + iam_additive = { + "roles/accesscontextmanager.policyAdmin" = [ + local.automation_sas_iam.security + ] + "roles/compute.orgFirewallPolicyAdmin" = [ + local.automation_sas_iam.networking + ] + "roles/compute.xpnAdmin" = [ + local.automation_sas_iam.networking + ] + } + org_policies_data_path = var.organization_policy_data_path +} diff --git a/fast/stages-multitenant/1-resman-tenant/templates/providers.tf.tpl b/fast/stages-multitenant/1-resman-tenant/templates/providers.tf.tpl new file mode 100644 index 00000000..993c78ca --- /dev/null +++ b/fast/stages-multitenant/1-resman-tenant/templates/providers.tf.tpl @@ -0,0 +1,33 @@ +/** + * 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. + */ + +terraform { + backend "gcs" { + bucket = "${bucket}" + impersonate_service_account = "${sa}" + %{~ if backend_extra != null ~} + ${indent(4, backend_extra)} + %{~ endif ~} + } +} +provider "google" { + impersonate_service_account = "${sa}" +} +provider "google-beta" { + impersonate_service_account = "${sa}" +} + +# end provider.tf for ${name} diff --git a/fast/stages-multitenant/1-resman-tenant/templates/workflow-github.yaml b/fast/stages-multitenant/1-resman-tenant/templates/workflow-github.yaml new file mode 100644 index 00000000..e9669909 --- /dev/null +++ b/fast/stages-multitenant/1-resman-tenant/templates/workflow-github.yaml @@ -0,0 +1,198 @@ +# 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 +# +# 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: "FAST ${stage_name} stage" + +on: + pull_request: + branches: + - main + types: + - closed + - opened + - synchronize + +env: + FAST_OUTPUTS_BUCKET: ${outputs_bucket} + FAST_SERVICE_ACCOUNT: ${service_account} + FAST_WIF_PROVIDER: ${identity_provider} + SSH_AUTH_SOCK: /tmp/ssh_agent.sock + TF_PROVIDERS_FILE: ${tf_providers_file} + TF_VAR_FILES: ${tf_var_files == [] ? "''" : join("\n ", tf_var_files)} + TF_VERSION: 1.3.2 + +jobs: + fast-pr: + permissions: + contents: read + id-token: write + issues: write + pull-requests: write + runs-on: ubuntu-latest + steps: + - id: checkout + name: Checkout repository + uses: actions/checkout@v3 + + # set up SSH key authentication to the modules repository + - id: ssh-config + name: Configure SSH authentication + run: | + ssh-agent -a "$SSH_AUTH_SOCK" > /dev/null + ssh-add - <<< "$${{ secrets.CICD_MODULES_KEY }}" + + # set up authentication via Workload identity Federation + - id: gcp-auth + name: Authenticate to Google Cloud + uses: google-github-actions/auth@v0 + with: + workload_identity_provider: $${{ env.FAST_WIF_PROVIDER }} + service_account: $${{ env.FAST_SERVICE_ACCOUNT }} + access_token_lifetime: 3600s + + - id: gcp-sdk + name: Set up Cloud SDK + uses: google-github-actions/setup-gcloud@v0 + with: + install_components: alpha + + # copy provider and tfvars files + - id: tf-config + name: Copy Terraform output files + run: | + gcloud alpha storage cp -r \ + "gs://$${{env.FAST_OUTPUTS_BUCKET}}/providers/$${{env.TF_PROVIDERS_FILE}}" ./ + gcloud alpha storage cp -r \ + "gs://$${{env.FAST_OUTPUTS_BUCKET}}/tfvars" ./ + for f in $${{env.TF_VAR_FILES}}; do + ln -s "tfvars/$f" ./ + done + + - id: tf-setup + name: Set up Terraform + uses: hashicorp/setup-terraform@v2.0.3 + with: + terraform_version: $${{ env.TF_VERSION }} + + # run Terraform init/validate/plan + - id: tf-init + name: Terraform init + continue-on-error: true + run: | + terraform init -no-color + + - id: tf-validate + name: Terraform validate + continue-on-error: true + run: terraform validate -no-color + + - id: tf-plan + name: Terraform plan + continue-on-error: true + run: | + terraform plan -input=false -out ../plan.out -no-color + + - id: tf-apply + if: github.event.pull_request.merged == true && success() + name: Terraform apply + continue-on-error: true + run: | + terraform apply -input=false -auto-approve -no-color ../plan.out + + - id: pr-comment + name: Post comment to Pull Request + continue-on-error: true + uses: actions/github-script@v6 + if: github.event_name == 'pull_request' + env: + PLAN: $${{ steps.tf-plan.outputs.stdout }}\n$${{ steps.tf-plan.outputs.stderr }} + with: + script: | + const output = `### Terraform Initialization \`$${{ steps.tf-init.outcome }}\` + + ### Terraform Validation \`$${{ steps.tf-validate.outcome }}\` + +
Validation Output + + \`\`\`\n + $${{ steps.tf-validate.outputs.stdout }} + \`\`\` + +
+ + ### Terraform Plan \`$${{ steps.tf-plan.outcome }}\` + +
Show Plan + + \`\`\`\n + $${process.env.PLAN.split('\n').filter(l => l.match(/^([A-Z\s].*|)$$/)).join('\n')} + \`\`\` + +
+ + ### Terraform Apply \`$${{ steps.tf-apply.outcome }}\` + + *Pusher: @$${{ github.actor }}, Action: \`$${{ github.event_name }}\`, Working Directory: \`$${{ env.tf_actions_working_dir }}\`, Workflow: \`$${{ github.workflow }}\`*`; + + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: output + }) + + - id: pr-short-comment + name: Post comment to Pull Request + uses: actions/github-script@v6 + if: github.event_name == 'pull_request' && steps.pr-comment.outcome != 'success' + with: + script: | + const output = `### Terraform Initialization \`$${{ steps.tf-init.outcome }}\` + + ### Terraform Validation \`$${{ steps.tf-validate.outcome }}\` + + ### Terraform Plan \`$${{ steps.tf-plan.outcome }}\` + + Plan output is in the action log. + + ### Terraform Apply \`$${{ steps.tf-apply.outcome }}\` + + *Pusher: @$${{ github.actor }}, Action: \`$${{ github.event_name }}\`, Working Directory: \`$${{ env.tf_actions_working_dir }}\`, Workflow: \`$${{ github.workflow }}\`*`; + + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: output + }) + + - id: check-init + name: Check init failure + if: steps.tf-init.outcome != 'success' + run: exit 1 + + - id: check-validate + name: Check validate failure + if: steps.tf-validate.outcome != 'success' + run: exit 1 + + - id: check-plan + name: Check plan failure + if: steps.tf-plan.outcome != 'success' + run: exit 1 + + - id: check-apply + name: Check apply failure + if: github.event.pull_request.merged == true && steps.tf-apply.outcome != 'success' + run: exit 1 diff --git a/fast/stages-multitenant/1-resman-tenant/templates/workflow-gitlab.yaml b/fast/stages-multitenant/1-resman-tenant/templates/workflow-gitlab.yaml new file mode 100644 index 00000000..8981e70b --- /dev/null +++ b/fast/stages-multitenant/1-resman-tenant/templates/workflow-gitlab.yaml @@ -0,0 +1,120 @@ +# 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 +# +# 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. + +default: + before_script: + - echo "$${CI_JOB_JWT_V2}" > token.txt + image: + name: hashicorp/terraform + entrypoint: + - "/usr/bin/env" + - "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + +variables: + GOOGLE_CREDENTIALS: cicd-sa-credentials.json + FAST_OUTPUTS_BUCKET: ${outputs_bucket} + FAST_SERVICE_ACCOUNT: ${service_account} + FAST_WIF_PROVIDER: ${identity_provider} + SSH_AUTH_SOCK: /tmp/ssh_agent.sock + TF_PROVIDERS_FILE: ${tf_providers_file} + TF_VAR_FILES: ${tf_var_files == [] ? "''" : join("\n ", tf_var_files)} + +stages: + - gcp-auth + - tf-files + - tf-plan + - tf-apply + +cache: + key: gcp-auth + paths: + - cicd-sa-credentials.json + - .tf-setup + +gcp-auth: + image: + name: google/cloud-sdk:slim + stage: gcp-auth + script: + - | + gcloud iam workload-identity-pools create-cred-config \ + $${FAST_WIF_PROVIDER} \ + --service-account=$${FAST_SERVICE_ACCOUNT} \ + --service-account-token-lifetime-seconds=3600 \ + --output-file=$${GOOGLE_CREDENTIALS} \ + --credential-source-file=token.txt +tf-files: + dependencies: + - gcp-auth + image: + name: google/cloud-sdk:slim + stage: tf-files + script: + # - gcloud components install -q alpha + - gcloud config set auth/credential_file_override $${GOOGLE_CREDENTIALS} + - mkdir -p .tf-setup + - | + gcloud alpha storage cp -r \ + "gs://$${FAST_OUTPUTS_BUCKET}/providers/$${TF_PROVIDERS_FILE}" .tf-setup/ + - | + gcloud alpha storage cp -r \ + "gs://$${FAST_OUTPUTS_BUCKET}/tfvars" .tf-setup/ + +tf-plan: + # uncomment the following lines and set the SSH key secret for private modules repo + # before_script: + # - | + # ssh-agent -a $SSH_AUTH_SOCK > /dev/null + # echo "$CICD_MODULES_KEY" | base64 -d | tr -d '\r' | ssh-add - > /dev/null + # mkdir -p ~/.ssh + # ssh-keyscan -H 'gitlab.com' >> ~/.ssh/known_hosts + # ssh-keyscan gitlab.com | sort -u - ~/.ssh/known_hosts -o ~/.ssh/known_hosts + stage: tf-plan + script: + - cp .tf-setup/$${TF_PROVIDERS_FILE} ./ + - | + for f in $${TF_VAR_FILES}; do + ln -s ".tf-setup/tfvars/$f" ./ + done + - terraform init + - terraform validate + - terraform plan + dependencies: + - tf-files + +tf-apply: + # uncomment the following lines and set the SSH key secret for private modules repo + # before_script: + # - | + # ssh-agent -a $SSH_AUTH_SOCK > /dev/null + # echo "$CICD_MODULES_KEY" | base64 -d | tr -d '\r' | ssh-add - > /dev/null + # mkdir -p ~/.ssh + # ssh-keyscan -H 'gitlab.com' >> ~/.ssh/known_hosts + # ssh-keyscan gitlab.com | sort -u - ~/.ssh/known_hosts -o ~/.ssh/known_hosts + stage: tf-apply + script: + - cp .tf-setup/$${TF_PROVIDERS_FILE} ./ + - | + for f in $${TF_VAR_FILES}; do + ln -s ".tf-setup/tfvars/$f" ./ + done + - terraform init + - terraform validate + - terraform apply -input=false -auto-approve + dependencies: + - tf-files + when: manual + only: + variables: + - $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH diff --git a/fast/stages-multitenant/1-resman-tenant/templates/workflow-sourcerepo.yaml b/fast/stages-multitenant/1-resman-tenant/templates/workflow-sourcerepo.yaml new file mode 100644 index 00000000..446c9c96 --- /dev/null +++ b/fast/stages-multitenant/1-resman-tenant/templates/workflow-sourcerepo.yaml @@ -0,0 +1,98 @@ +# 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 +# +# 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. + +steps: + - name: alpine:3 + id: tf-download + entrypoint: sh + args: + - -eEuo + - pipefail + - -c + - |- + mkdir -p /builder/home/.local/bin + wget https://releases.hashicorp.com/terraform/$${_TF_VERSION}/terraform_$${_TF_VERSION}_linux_amd64.zip + unzip terraform_$${_TF_VERSION}_linux_amd64.zip -d /builder/home/.local/bin + rm terraform_$${_TF_VERSION}_linux_amd64.zip + chmod 755 /builder/home/.local/bin/terraform + - name: alpine:3 + id: tf-check-format + entrypoint: sh + args: + - -eEuo + - pipefail + - -c + - |- + terraform fmt -recursive -check /workspace/ + - name: gcr.io/google.com/cloudsdktool/cloud-sdk:alpine + id: tf-files + entrypoint: bash + args: + - -eEuo + - pipefail + - -c + - |- + /google-cloud-sdk/bin/gsutil cp \ + gs://$${_FAST_OUTPUTS_BUCKET}/providers/$${_TF_PROVIDERS_FILE} ./ + /google-cloud-sdk/bin/gsutil cp -r \ + gs://$${_FAST_OUTPUTS_BUCKET}/tfvars ./ + for f in $${_TF_VAR_FILES}; do + ln -s tfvars/$f ./ + done + - name: alpine:3 + id: tf-init + entrypoint: sh + args: + - -eEuo + - pipefail + - -c + - |- + terraform init -no-color + - name: alpine:3 + id: tf-check-validate + entrypoint: sh + args: + - -eEuo + - pipefail + - -c + - |- + terraform validate -no-color + - name: alpine:3 + id: tf-plan + entrypoint: sh + args: + - -eEuo + - pipefail + - -c + - |- + terraform plan -no-color -input=false -out plan.out + # store artifact and ask for approval here if needed + - name: alpine:3 + id: tf-apply + entrypoint: sh + args: + - -eEuo + - pipefail + - -c + - |- + terraform apply -no-color -input=false -auto-approve plan.out +options: + env: + - PATH=/usr/local/bin:/usr/bin:/bin:/builder/home/.local/bin + logging: CLOUD_LOGGING_ONLY +substitutions: + _FAST_OUTPUTS_BUCKET: ${outputs_bucket} + _TF_PROVIDERS_FILE: ${tf_providers_file} + _TF_VAR_FILES: ${tf_var_files == [] ? "''" : join("\n ", tf_var_files)} + _TF_VERSION: 1.3.2 diff --git a/fast/stages-multitenant/1-resman-tenant/variables.tf b/fast/stages-multitenant/1-resman-tenant/variables.tf new file mode 100644 index 00000000..0229dd78 --- /dev/null +++ b/fast/stages-multitenant/1-resman-tenant/variables.tf @@ -0,0 +1,281 @@ +/** + * 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. + */ + +# defaults for variables marked with global tfdoc annotations, can be set via +# the tfvars file generated in stage 00 and stored in its outputs + +variable "automation" { + # tfdoc:variable:source 0-bootstrap + description = "Automation resources created by the bootstrap stage." + type = object({ + outputs_bucket = string + project_id = string + project_number = string + federated_identity_pools = list(string) + federated_identity_providers = map(object({ + issuer = string + issuer_uri = string + name = string + principal_tpl = string + principalset_tpl = string + })) + service_accounts = object({ + networking = string + resman = string + security = string + dp-dev = optional(string) + dp-prod = optional(string) + gke-dev = optional(string) + gke-prod = optional(string) + pf-dev = optional(string) + pf-prod = optional(string) + sandbox = optional(string) + teams = optional(string) + }) + }) +} + +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." + type = object({ + id = string + is_org_level = optional(bool, true) + }) + validation { + condition = var.billing_account.is_org_level != null + error_message = "Invalid `null` value for `billing_account.is_org_level`." + } +} + +variable "cicd_repositories" { + description = "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." + type = object({ + data_platform_dev = object({ + branch = string + identity_provider = string + name = string + type = string + }) + data_platform_prod = object({ + branch = string + identity_provider = string + name = string + type = string + }) + gke_dev = object({ + branch = string + identity_provider = string + name = string + type = string + }) + gke_prod = object({ + branch = string + identity_provider = string + name = string + type = string + }) + networking = object({ + branch = string + identity_provider = string + name = string + type = string + }) + project_factory_dev = object({ + branch = string + identity_provider = string + name = string + type = string + }) + project_factory_prod = object({ + branch = string + identity_provider = string + name = string + type = string + }) + security = object({ + branch = string + identity_provider = string + name = string + type = string + }) + }) + default = null + validation { + condition = alltrue([ + for k, v in coalesce(var.cicd_repositories, {}) : + v == null || try(v.name, null) != null + ]) + error_message = "Non-null repositories need a non-null name." + } + validation { + condition = alltrue([ + for k, v in coalesce(var.cicd_repositories, {}) : + v == null || ( + try(v.identity_provider, null) != null + || + try(v.type, null) == "sourcerepo" + ) + ]) + error_message = "Non-null repositories need a non-null provider unless type is 'sourcerepo'." + } + validation { + condition = alltrue([ + for k, v in coalesce(var.cicd_repositories, {}) : + v == null || ( + contains(["github", "gitlab", "sourcerepo"], coalesce(try(v.type, null), "null")) + ) + ]) + error_message = "Invalid repository type, supported types: 'github' 'gitlab' or 'sourcerepo'." + } +} + +variable "custom_roles" { + # tfdoc:variable:source 0-bootstrap + description = "Custom roles defined at the org level, in key => id format." + type = object({ + service_project_network_admin = string + }) + default = null +} + +variable "data_dir" { + description = "Relative path for the folder storing configuration data." + type = string + default = "data" +} + +variable "fast_features" { + # tfdoc:variable:source 0-0-bootstrap + description = "Selective control for top-level FAST features." + type = object({ + data_platform = optional(bool, false) + gke = optional(bool, false) + project_factory = optional(bool, false) + sandbox = optional(bool, false) + teams = optional(bool, false) + }) + default = {} + nullable = false +} + +variable "groups" { + # tfdoc:variable:source 0-bootstrap + # https://cloud.google.com/docs/enterprise/setup-checklist + description = "Group names to grant organization-level permissions." + type = object({ + gcp-devops = optional(string) + gcp-network-admins = optional(string) + gcp-security-admins = optional(string) + }) + default = {} + nullable = false +} + +variable "locations" { + # tfdoc:variable:source 0-bootstrap + description = "Optional locations for GCS, BigQuery, and logging buckets created here." + type = object({ + bq = string + gcs = string + logging = string + pubsub = list(string) + }) + default = { + bq = "EU" + gcs = "EU" + logging = "global" + pubsub = [] + } + nullable = false +} + +variable "organization" { + # tfdoc:variable:source 0-bootstrap + description = "Organization details." + type = object({ + domain = string + id = number + customer_id = string + }) +} + +variable "organization_policy_data_path" { + description = "Path for the data folder used by the organization policies factory." + type = string + default = null +} + +variable "outputs_location" { + description = "Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable." + type = string + default = null +} + +variable "prefix" { + # tfdoc:variable:source 0-bootstrap + description = "Prefix used for resources that need unique names. Use 9 characters or less." + type = string + + validation { + condition = try(length(var.prefix), 0) < 10 + error_message = "Use a maximum of 9 characters for prefix." + } +} + +variable "root_node" { + description = "Root folder node for the tenant, in folders/nnnnnn format." + type = string +} + +variable "short_name" { + description = "Short name used to identify the tenant." + type = string +} + +variable "tags" { + description = "Resource management tags." + type = object({ + keys = object({ + context = string + environment = string + tenant = string + }) + names = object({ + context = string + environment = string + tenant = string + }) + values = map(string) + }) + nullable = false +} + +variable "team_folders" { + description = "Team folders to be created. Format is described in a code comment." + type = map(object({ + descriptive_name = string + group_iam = map(list(string)) + impersonation_groups = list(string) + })) + default = null +} + +variable "test_skip_data_sources" { + description = "Used when testing to bypass data sources." + type = bool + default = false +} diff --git a/fast/stages-multitenant/README.md b/fast/stages-multitenant/README.md new file mode 100644 index 00000000..27ce17dd --- /dev/null +++ b/fast/stages-multitenant/README.md @@ -0,0 +1,27 @@ +# FAST multitenant stages + +The stages in this folder set up separate resource hierarchies inside the same organization that are fully FAST-compliant, and allow each tenant to run and manage their own networking, security, or application-level stages. They are designed to be used where a high degree of autonomy is needed for each tenant, for example individual subsidiaries of a large corporation all sharing the same GCP organization. + +The multitenant stages have the following characteristics: + +- they support one tenant at a time, so one copy of both stages is needed for each tenant +- they have the organization-level bootstrap and resource management stages as prerequisite +- they are logically equivalent to the respective organization-level stages but behave slightly differently, as they actively minimize access and changes to organization or shared resources + +Once both tenant-level stages are run, a hierarchy and a set of resources is available for the new tenant, including a separate automation project, service accounts for subsequent stages, etc. + +The tenant-level stages require that organization-level stage 0 (bootstrap) and 1 (resource management) have been applied. Their position and role in the FAST stage flow is shown in the following diagram: + +

+ Stages diagram +

+ +## Tenant bootstrap (0) + +This stage creates the top-level root folder and tag for the tenant, and the tenant-level automation project and automation service accounts. It also sets up billing and organization-level roles for the tenant administrators group and the automation service accounts. As in the organizational-level stages, it can optionally set up CI/CD for itself and the tenant resource management stage. + +This stage is run with the organization-level resource management service account as it leverages its permissions, and is the bridge between the organization-level stages and the tenant stages which are effectively decoupled from the rest of the organization. + +## Tenant resource management (1) + +This stage populates the resource hierarchy rooted in the top-level tenant folder, assigns roles to the tenant automation service accounts, and optionally sets up CI/CD for the following stages. It is functionally equivalent to the organization-level resource management stage, but runs with a tenant-specific service account and has no control over resources outside of the tenant context. diff --git a/fast/stages-multitenant/diagram.png b/fast/stages-multitenant/diagram.png new file mode 100644 index 00000000..940fa3c3 Binary files /dev/null and b/fast/stages-multitenant/diagram.png differ diff --git a/fast/stages-multitenant/stages.png b/fast/stages-multitenant/stages.png new file mode 100644 index 00000000..dc2acc0a Binary files /dev/null and b/fast/stages-multitenant/stages.png differ diff --git a/fast/stages-multitenant/stages.svg b/fast/stages-multitenant/stages.svg new file mode 100644 index 00000000..157453f0 --- /dev/null +++ b/fast/stages-multitenant/stages.svg @@ -0,0 +1,1278 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/fast/stages.png b/fast/stages.png index 238ad5a0..d6c3e386 100644 Binary files a/fast/stages.png and b/fast/stages.png differ diff --git a/fast/stages.svg b/fast/stages.svg index f952c884..b6855676 100644 --- a/fast/stages.svg +++ b/fast/stages.svg @@ -1,1063 +1,929 @@ + inkscape:version="1.0.2 (e86c870879, 2021-01-15)"> + + + + image/svg+xml + + + + + id="defs273" /> + inkscape:window-maximized="1" + inkscape:current-layer="svg269" /> + id="g1d4f14c2bd7_0_215.0"> + id="path2" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/fast/stages/00-bootstrap/IAM.md b/fast/stages/0-bootstrap/IAM.md similarity index 100% rename from fast/stages/00-bootstrap/IAM.md rename to fast/stages/0-bootstrap/IAM.md diff --git a/fast/stages/00-bootstrap/README.md b/fast/stages/0-bootstrap/README.md similarity index 88% rename from fast/stages/00-bootstrap/README.md rename to fast/stages/0-bootstrap/README.md index 889b0bce..2cab1151 100644 --- a/fast/stages/00-bootstrap/README.md +++ b/fast/stages/0-bootstrap/README.md @@ -18,7 +18,7 @@ Use the following diagram as a simple high level reference for the following sec As mentioned above, this stage only does the bare minimum required to bootstrap automation, and ensure that base audit and billing exports are in place from the start to provide some measure of accountability, even before the security configurations are applied in a later stage. -It also sets up organization-level IAM bindings so the Organization Administrator role is only used here, trading off some design freedom for ease of auditing and troubleshooting, and reducing the risk of costly security mistakes down the line. The only exception to this rule is for the [Resource Management stage](../01-resman) service account, described below. +It also sets up organization-level IAM bindings so the Organization Administrator role is only used here, trading off some design freedom for ease of auditing and troubleshooting, and reducing the risk of costly security mistakes down the line. The only exception to this rule is for the [Resource Management stage](../1-resman) service account, described below. ### User groups @@ -28,7 +28,7 @@ We have standardized the initial set of groups on those outlined in the [GCP Ent ### Organization-level IAM -The service account used in the [Resource Management stage](../01-resman) needs to be able to grant specific permissions at the organizational level, to enable specific functionality for subsequent stages that deal with network or security resources, or billing-related activities. +The service account used in the [Resource Management stage](../1-resman) needs to be able to grant specific permissions at the organizational level, to enable specific functionality for subsequent stages that deal with network or security resources, or billing-related activities. In order to be able to assign those roles without having the full authority of the Organization Admin role, this stage defines a custom role that only allows setting IAM policies on the organization, and grants it via a [delegated role grant](https://cloud.google.com/iam/docs/setting-limits-on-granting-roles) that only allows it to be used to grant a limited subset of roles. @@ -89,7 +89,7 @@ This stage also implements initial support for two interrelated features Workload Identity Federation support allows configuring external providers independently from CI/CD, and offers predefined attributes for a few well known ones (more can be easily added by editing the `identity-providers.tf` file). Once providers have been configured their names are passed to the following stages via interface outputs, and can be leveraged to set up access or impersonation in IAM bindings. -CI/CD support is fully implemented for GitHub, Gitlab, and Cloud Source Repositories / Cloud Build. For GitHub, we also offer a [separate supporting setup](../../extras/00-cicd-github/) to quickly create / configure repositories. +CI/CD support is fully implemented for GitHub, Gitlab, and Cloud Source Repositories / Cloud Build. For GitHub, we also offer a [separate supporting setup](../../extras/0-cicd-github/) to quickly create / configure repositories. @@ -419,7 +419,7 @@ The `type` attribute can be set to one of the supported repository types: `githu Once the stage is applied the generated output files will contain pre-configured workflow files for each repository, that will use Workload Identity Federation via a dedicated service account for each repository to impersonate the automation service account for the stage. -You can use Terraform to automate creation of the repositories using the extra stage defined in [fast/extras/00-cicd-github](../../extras/00-cicd-github/) (only for Github for now). +You can use Terraform to automate creation of the repositories using the extra stage defined in [fast/extras/0-cicd-github](../../extras/0-cicd-github/) (only for Github for now). The remaining configuration is manual, as it regards the repositories themselves: @@ -453,7 +453,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 · organization · project | google_billing_account_iam_member · google_organization_iam_binding | +| [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 | | @@ -468,35 +468,35 @@ 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 and organization id ('nnnnnnnn' or null). | object({…}) | ✓ | | | -| [organization](variables.tf#L202) | Organization details. | object({…}) | ✓ | | | -| [prefix](variables.tf#L217) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | | -| [bootstrap_user](variables.tf#L25) | Email of the nominal user running this stage for the first time. | string | | null | | -| [cicd_repositories](variables.tf#L31) | 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#L83) | 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#L114) | Workload Identity Federation pools. The `cicd_repositories` variable references keys here. | map(object({…})) | | {} | | -| [groups](variables.tf#L128) | Group names to grant organization-level permissions. | map(string) | | {…} | | -| [iam](variables.tf#L146) | Organization-level custom IAM settings in role => [principal] format. | map(list(string)) | | {} | | -| [iam_additive](variables.tf#L152) | Organization-level custom IAM settings in role => [principal] format for non-authoritative bindings. | map(list(string)) | | {} | | -| [locations](variables.tf#L158) | Optional locations for GCS, BigQuery, and logging buckets created here. | object({…}) | | {…} | | -| [log_sinks](variables.tf#L177) | Org-level log sinks, in name => {type, filter} format. | map(object({…})) | | {…} | | -| [outputs_location](variables.tf#L211) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | string | | null | | -| [project_parent_ids](variables.tf#L227) | 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. | 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({…}) | | {…} | | ## Outputs | name | description | sensitive | consumers | |---|---|:---:|---| -| [automation](outputs.tf#L89) | Automation resources. | | | -| [billing_dataset](outputs.tf#L94) | BigQuery dataset prepared for billing export. | | | -| [cicd_repositories](outputs.tf#L99) | CI/CD repository configurations. | | | -| [custom_roles](outputs.tf#L111) | Organization-level custom roles. | | | -| [federated_identity](outputs.tf#L116) | Workload Identity Federation pool and providers. | | | -| [outputs_bucket](outputs.tf#L126) | GCS bucket where generated output files are stored. | | | -| [project_ids](outputs.tf#L131) | Projects created by this stage. | | | -| [providers](outputs.tf#L141) | Terraform provider files for this stage and dependent stages. | ✓ | stage-01 | -| [service_accounts](outputs.tf#L148) | Automation service accounts created by this stage. | | | -| [tfvars](outputs.tf#L158) | Terraform variable files for the following stages. | ✓ | | +| [automation](outputs.tf#L86) | Automation resources. | | | +| [billing_dataset](outputs.tf#L91) | BigQuery dataset prepared for billing export. | | | +| [cicd_repositories](outputs.tf#L96) | CI/CD repository configurations. | | | +| [custom_roles](outputs.tf#L108) | Organization-level custom roles. | | | +| [federated_identity](outputs.tf#L113) | Workload Identity Federation pool and providers. | | | +| [outputs_bucket](outputs.tf#L123) | GCS bucket where generated output files are stored. | | | +| [project_ids](outputs.tf#L128) | Projects created by this stage. | | | +| [providers](outputs.tf#L138) | Terraform provider files for this stage and dependent stages. | ✓ | stage-01 | +| [service_accounts](outputs.tf#L145) | Automation service accounts created by this stage. | | | +| [tfvars](outputs.tf#L154) | Terraform variable files for the following stages. | ✓ | | diff --git a/fast/stages/00-bootstrap/automation.tf b/fast/stages/0-bootstrap/automation.tf similarity index 82% rename from fast/stages/00-bootstrap/automation.tf rename to fast/stages/0-bootstrap/automation.tf index 1475c811..90c14a81 100644 --- a/fast/stages/00-bootstrap/automation.tf +++ b/fast/stages/0-bootstrap/automation.tf @@ -127,39 +127,6 @@ module "automation-tf-bootstrap-sa" { } } -# cicd stage's bucket and service account - -module "automation-tf-cicd-gcs" { - source = "../../../modules/gcs" - project_id = module.automation-project.project_id - name = "iac-core-cicd-0" - prefix = local.prefix - location = var.locations.gcs - storage_class = local.gcs_storage_class - versioning = true - iam = { - "roles/storage.objectAdmin" = [module.automation-tf-cicd-provisioning-sa.iam_email] - } - depends_on = [module.organization] -} - -module "automation-tf-cicd-provisioning-sa" { - source = "../../../modules/iam-service-account" - project_id = module.automation-project.project_id - name = "cicd-0" - display_name = "Terraform stage 1 CICD service account." - prefix = local.prefix - # allow SA used by CI/CD workflow to impersonate this SA - iam = { - "roles/iam.serviceAccountTokenCreator" = compact([ - try(module.automation-tf-cicd-sa["cicd"].iam_email, null) - ]) - } - iam_storage_roles = { - (module.automation-tf-output-gcs.name) = ["roles/storage.admin"] - } -} - # resource hierarchy stage's bucket and service account module "automation-tf-resman-gcs" { @@ -183,7 +150,8 @@ module "automation-tf-resman-sa" { display_name = "Terraform stage 1 resman service account." prefix = local.prefix # allow SA used by CI/CD workflow to impersonate this SA - iam = { + # we use additive IAM to allow tenant CI/CD SAs to impersonate it + iam_additive = { "roles/iam.serviceAccountTokenCreator" = compact([ try(module.automation-tf-cicd-sa["resman"].iam_email, null) ]) diff --git a/fast/stages/00-bootstrap/billing.tf b/fast/stages/0-bootstrap/billing.tf similarity index 60% rename from fast/stages/00-bootstrap/billing.tf rename to fast/stages/0-bootstrap/billing.tf index df10e8f0..aee033bd 100644 --- a/fast/stages/00-bootstrap/billing.tf +++ b/fast/stages/0-bootstrap/billing.tf @@ -30,7 +30,7 @@ locals { module "billing-export-project" { source = "../../../modules/project" - count = local.billing_org ? 1 : 0 + count = var.billing_account.is_org_level ? 1 : 0 billing_account = var.billing_account.id name = "billing-exp-0" parent = coalesce( @@ -52,56 +52,18 @@ module "billing-export-project" { module "billing-export-dataset" { source = "../../../modules/bigquery-dataset" - count = local.billing_org ? 1 : 0 + count = var.billing_account.is_org_level ? 1 : 0 project_id = module.billing-export-project.0.project_id id = "billing_export" friendly_name = "Billing export." location = var.locations.bq } -# billing account in a different org - -module "billing-organization-ext" { - source = "../../../modules/organization" - count = local.billing_org_ext ? 1 : 0 - organization_id = "organizations/${var.billing_account.organization_id}" - iam_additive = { - "roles/billing.admin" = local.billing_ext_admins - } -} - - -resource "google_organization_iam_binding" "billing_org_ext_admin_delegated" { - # refer to organization.tf for the explanation of how this binding works - count = local.billing_org_ext ? 1 : 0 - org_id = var.billing_account.organization_id - # if the billing org does not have our custom role, user the predefined one - # role = "roles/resourcemanager.organizationAdmin" - role = join("", [ - "organizations/${var.billing_account.organization_id}/", - "roles/${var.custom_role_names.organization_iam_admin}" - ]) - members = [module.automation-tf-resman-sa.iam_email] - condition { - title = "automation_sa_delegated_grants" - description = "Automation service account delegated grants." - expression = format( - "api.getAttribute('iam.googleapis.com/modifiedGrantsByRole', []).hasOnly([%s])", - join(",", formatlist("'%s'", [ - "roles/billing.costsManager", - "roles/billing.user", - ] - )) - ) - } - depends_on = [module.billing-organization-ext] -} - # standalone billing account resource "google_billing_account_iam_member" "billing_ext_admin" { for_each = toset( - local.billing_ext ? local.billing_ext_admins : [] + !var.billing_account.is_org_level ? local.billing_ext_admins : [] ) billing_account_id = var.billing_account.id role = "roles/billing.admin" @@ -110,7 +72,7 @@ resource "google_billing_account_iam_member" "billing_ext_admin" { resource "google_billing_account_iam_member" "billing_ext_cost_manager" { for_each = toset( - local.billing_ext ? local.billing_ext_admins : [] + !var.billing_account.is_org_level ? local.billing_ext_admins : [] ) billing_account_id = var.billing_account.id role = "roles/billing.costsManager" diff --git a/fast/stages/00-bootstrap/cicd.tf b/fast/stages/0-bootstrap/cicd.tf similarity index 86% rename from fast/stages/00-bootstrap/cicd.tf rename to fast/stages/0-bootstrap/cicd.tf index 7cdae41c..2b2a3df9 100644 --- a/fast/stages/00-bootstrap/cicd.tf +++ b/fast/stages/0-bootstrap/cicd.tf @@ -17,6 +17,16 @@ # tfdoc:file:description Workload Identity Federation configurations for CI/CD. locals { + cicd_providers = { + for k, v in google_iam_workload_identity_pool_provider.default : + k => { + issuer = local.identity_providers[k].issuer + issuer_uri = local.identity_providers[k].issuer_uri + name = v.name + principal_tpl = local.identity_providers[k].principal_tpl + principalset_tpl = local.identity_providers[k].principalset_tpl + } + } cicd_repositories = { for k, v in coalesce(var.cicd_repositories, {}) : k => v if( @@ -32,18 +42,13 @@ locals { ) } cicd_workflow_providers = { - bootstrap = "00-bootstrap-providers.tf" - cicd = "00-cicd-providers.tf" - resman = "01-resman-providers.tf" + bootstrap = "0-bootstrap-providers.tf" + resman = "1-resman-providers.tf" } cicd_workflow_var_files = { bootstrap = [] - cicd = [ - "00-bootstrap.auto.tfvars.json", - "globals.auto.tfvars.json" - ] resman = [ - "00-bootstrap.auto.tfvars.json", + "0-bootstrap.auto.tfvars.json", "globals.auto.tfvars.json" ] } @@ -69,7 +74,7 @@ module "automation-tf-cicd-repo" { ] } triggers = { - "fast-00-${each.key}" = { + "fast-0-${each.key}" = { filename = ".cloudbuild/workflow.yaml" included_files = ["**/*tf", ".cloudbuild/workflow.yaml"] service_account = module.automation-tf-cicd-sa[each.key].id diff --git a/fast/stages/00-bootstrap/diagram.png b/fast/stages/0-bootstrap/diagram.png similarity index 100% rename from fast/stages/00-bootstrap/diagram.png rename to fast/stages/0-bootstrap/diagram.png diff --git a/fast/stages/00-bootstrap/diagram.svg b/fast/stages/0-bootstrap/diagram.svg similarity index 100% rename from fast/stages/00-bootstrap/diagram.svg rename to fast/stages/0-bootstrap/diagram.svg diff --git a/fast/stages/00-bootstrap/groups.gif b/fast/stages/0-bootstrap/groups.gif similarity index 100% rename from fast/stages/00-bootstrap/groups.gif rename to fast/stages/0-bootstrap/groups.gif diff --git a/fast/stages/00-bootstrap/identity-providers.tf b/fast/stages/0-bootstrap/identity-providers.tf similarity index 100% rename from fast/stages/00-bootstrap/identity-providers.tf rename to fast/stages/0-bootstrap/identity-providers.tf diff --git a/fast/stages/00-bootstrap/log-export.tf b/fast/stages/0-bootstrap/log-export.tf similarity index 82% rename from fast/stages/00-bootstrap/log-export.tf rename to fast/stages/0-bootstrap/log-export.tf index 1c9f5a87..b9b5da42 100644 --- a/fast/stages/00-bootstrap/log-export.tf +++ b/fast/stages/0-bootstrap/log-export.tf @@ -17,6 +17,16 @@ # tfdoc:file:description Audit log project and sink. locals { + log_sink_destinations = merge( + # use the same dataset for all sinks with `bigquery` as destination + { for k, v in var.log_sinks : k => module.log-export-dataset.0 if v.type == "bigquery" }, + # use the same gcs bucket for all sinks with `storage` as destination + { for k, v in var.log_sinks : k => module.log-export-gcs.0 if v.type == "storage" }, + # use separate pubsub topics and logging buckets for sinks with + # destination `pubsub` and `logging` + module.log-export-pubsub, + module.log-export-logbucket + ) log_types = toset([for k, v in var.log_sinks : v.type]) } diff --git a/fast/stages/00-bootstrap/main.tf b/fast/stages/0-bootstrap/main.tf similarity index 78% rename from fast/stages/00-bootstrap/main.tf rename to fast/stages/0-bootstrap/main.tf index 839019f3..dba2ed08 100644 --- a/fast/stages/00-bootstrap/main.tf +++ b/fast/stages/0-bootstrap/main.tf @@ -28,10 +28,6 @@ locals { for k, v in local.groups : k => "group:${v}" } - # convenience flags that express where billing account resides - billing_ext = var.billing_account.organization_id == null - billing_org = var.billing_account.organization_id == var.organization.id - billing_org_ext = !local.billing_ext && !local.billing_org # naming: environment used in most resource names prefix = join("-", compact([var.prefix, "prod"])) } diff --git a/fast/stages/00-bootstrap/organization.tf b/fast/stages/0-bootstrap/organization.tf similarity index 89% rename from fast/stages/00-bootstrap/organization.tf rename to fast/stages/0-bootstrap/organization.tf index 33b87820..154b37fe 100644 --- a/fast/stages/00-bootstrap/organization.tf +++ b/fast/stages/0-bootstrap/organization.tf @@ -24,7 +24,10 @@ locals { "domain:${var.organization.domain}" ] "roles/logging.admin" = concat( - [module.automation-tf-bootstrap-sa.iam_email], + [ + module.automation-tf-bootstrap-sa.iam_email, + module.automation-tf-resman-sa.iam_email + ], local._iam_bootstrap_user ) "roles/owner" = local._iam_bootstrap_user @@ -35,12 +38,11 @@ locals { [module.automation-tf-bootstrap-sa.iam_email], local._iam_bootstrap_user ) - # the following is useful if roles/browser is not desirable - # "roles/resourcemanager.organizationViewer" = [ - # "domain:${var.organization.domain}" - # ] "roles/resourcemanager.projectCreator" = concat( - [module.automation-tf-bootstrap-sa.iam_email], + [ + module.automation-tf-bootstrap-sa.iam_email, + module.automation-tf-resman-sa.iam_email + ], local._iam_bootstrap_user ) "roles/resourcemanager.projectMover" = [ @@ -78,8 +80,12 @@ locals { local.groups_iam.gcp-security-admins, module.automation-tf-resman-sa.iam_email ] + # the following is useful if roles/browser is not desirable + # "roles/resourcemanager.organizationViewer" = [ + # "domain:${var.organization.domain}" + # ] }, - local.billing_org ? { + var.billing_account.is_org_level ? { "roles/billing.admin" = [ local.groups_iam.gcp-billing-admins, local.groups_iam.gcp-organization-admins, @@ -115,16 +121,6 @@ locals { iam_roles_additive = distinct(concat( keys(local._iam_additive), keys(var.iam_additive) )) - log_sink_destinations = merge( - # use the same dataset for all sinks with `bigquery` as destination - { for k, v in var.log_sinks : k => module.log-export-dataset.0 if v.type == "bigquery" }, - # use the same gcs bucket for all sinks with `storage` as destination - { for k, v in var.log_sinks : k => module.log-export-gcs.0 if v.type == "storage" }, - # use separate pubsub topics and logging buckets for sinks with - # destination `pubsub` and `logging` - module.log-export-pubsub, - module.log-export-logbucket - ) } module "organization" { @@ -190,6 +186,9 @@ module "organization" { "dns.networks.bindPrivateDNSZone", "resourcemanager.projects.get", ] + (var.custom_role_names.tenant_network_admin) = [ + "compute.globalOperations.get", + ] } logging_sinks = { for name, attrs in var.log_sinks : name => { @@ -220,8 +219,10 @@ resource "google_organization_iam_binding" "org_admin_delegated" { "roles/compute.orgFirewallPolicyAdmin", "roles/compute.xpnAdmin", "roles/orgpolicy.policyAdmin", + "roles/resourcemanager.organizationViewer", + module.organization.custom_role_id[var.custom_role_names.tenant_network_admin] ], - local.billing_org ? [ + var.billing_account.is_org_level ? [ "roles/billing.admin", "roles/billing.costsManager", "roles/billing.user", diff --git a/fast/stages/00-bootstrap/outputs-files.tf b/fast/stages/0-bootstrap/outputs-files.tf similarity index 97% rename from fast/stages/00-bootstrap/outputs-files.tf rename to fast/stages/0-bootstrap/outputs-files.tf index ded88cd5..90e195b5 100644 --- a/fast/stages/00-bootstrap/outputs-files.tf +++ b/fast/stages/0-bootstrap/outputs-files.tf @@ -26,7 +26,7 @@ resource "local_file" "providers" { resource "local_file" "tfvars" { for_each = var.outputs_location == null ? {} : { 1 = 1 } file_permission = "0644" - filename = "${try(pathexpand(var.outputs_location), "")}/tfvars/00-bootstrap.auto.tfvars.json" + filename = "${try(pathexpand(var.outputs_location), "")}/tfvars/0-bootstrap.auto.tfvars.json" content = jsonencode(local.tfvars) } diff --git a/fast/stages/00-bootstrap/outputs-gcs.tf b/fast/stages/0-bootstrap/outputs-gcs.tf similarity index 96% rename from fast/stages/00-bootstrap/outputs-gcs.tf rename to fast/stages/0-bootstrap/outputs-gcs.tf index 2c281d4c..0aded986 100644 --- a/fast/stages/00-bootstrap/outputs-gcs.tf +++ b/fast/stages/0-bootstrap/outputs-gcs.tf @@ -26,7 +26,7 @@ resource "google_storage_bucket_object" "providers" { resource "google_storage_bucket_object" "tfvars" { bucket = module.automation-tf-output-gcs.name - name = "tfvars/00-bootstrap.auto.tfvars.json" + name = "tfvars/0-bootstrap.auto.tfvars.json" content = jsonencode(local.tfvars) } diff --git a/fast/stages/00-bootstrap/outputs.tf b/fast/stages/0-bootstrap/outputs.tf similarity index 77% rename from fast/stages/00-bootstrap/outputs.tf rename to fast/stages/0-bootstrap/outputs.tf index 73dd64f4..364abd68 100644 --- a/fast/stages/00-bootstrap/outputs.tf +++ b/fast/stages/0-bootstrap/outputs.tf @@ -21,7 +21,7 @@ locals { for k, v in local.cicd_repositories : k => templatefile( "${path.module}/templates/workflow-${v.type}.yaml", { identity_provider = try( - local.wif_providers[v["identity_provider"]].name, "" + local.cicd_providers[v["identity_provider"]].name, "" ) outputs_bucket = module.automation-tf-output-gcs.name service_account = try( @@ -38,19 +38,26 @@ locals { k => try(module.organization.custom_role_id[v], null) } providers = { - "00-bootstrap" = templatefile(local._tpl_providers, { - bucket = module.automation-tf-bootstrap-gcs.name - name = "bootstrap" - sa = module.automation-tf-bootstrap-sa.email + "0-bootstrap" = templatefile(local._tpl_providers, { + backend_extra = null + bucket = module.automation-tf-bootstrap-gcs.name + name = "bootstrap" + sa = module.automation-tf-bootstrap-sa.email }) - "00-cicd" = templatefile(local._tpl_providers, { - bucket = module.automation-tf-cicd-gcs.name - name = "cicd" - sa = module.automation-tf-cicd-provisioning-sa.email + "1-resman" = templatefile(local._tpl_providers, { + backend_extra = null + bucket = module.automation-tf-resman-gcs.name + name = "resman" + sa = module.automation-tf-resman-sa.email }) - "01-resman" = templatefile(local._tpl_providers, { + "0-bootstrap-tenant" = templatefile(local._tpl_providers, { + backend_extra = join("\n", [ + "# remove the newline between quotes and set the tenant name as prefix", + "prefix = \"", + "\"" + ]) bucket = module.automation-tf-resman-gcs.name - name = "resman" + name = "bootstrap-tenant" sa = module.automation-tf-resman-sa.email }) } @@ -59,7 +66,7 @@ locals { federated_identity_pool = try( google_iam_workload_identity_pool.default.0.name, null ) - federated_identity_providers = local.wif_providers + federated_identity_providers = local.cicd_providers outputs_bucket = module.automation-tf-output-gcs.name project_id = module.automation-project.project_id project_number = module.automation-project.number @@ -74,16 +81,6 @@ locals { organization = var.organization prefix = var.prefix } - wif_providers = { - for k, v in google_iam_workload_identity_pool_provider.default : - k => { - issuer = local.identity_providers[k].issuer - issuer_uri = local.identity_providers[k].issuer_uri - name = v.name - principal_tpl = local.identity_providers[k].principal_tpl - principalset_tpl = local.identity_providers[k].principalset_tpl - } - } } output "automation" { @@ -102,7 +99,7 @@ output "cicd_repositories" { for k, v in local.cicd_repositories : k => { branch = v.branch name = v.name - provider = try(local.wif_providers[v.identity_provider].name, null) + provider = try(local.cicd_providers[v.identity_provider].name, null) service_account = try(module.automation-tf-cicd-sa[k].email, null) } } @@ -119,7 +116,7 @@ output "federated_identity" { pool = try( google_iam_workload_identity_pool.default.0.name, null ) - providers = local.wif_providers + providers = local.cicd_providers } } @@ -149,7 +146,6 @@ output "service_accounts" { description = "Automation service accounts created by this stage." value = { bootstrap = module.automation-tf-bootstrap-sa.email - cicd = module.automation-tf-cicd-provisioning-sa.email resman = module.automation-tf-resman-sa.email } } diff --git a/tests/blueprints/data_solutions/cmek_via_centralized_kms/fixture/variables.tf b/fast/stages/0-bootstrap/templates/providers.tf.tpl similarity index 62% rename from tests/blueprints/data_solutions/cmek_via_centralized_kms/fixture/variables.tf rename to fast/stages/0-bootstrap/templates/providers.tf.tpl index 6a534739..d1c224c5 100644 --- a/tests/blueprints/data_solutions/cmek_via_centralized_kms/fixture/variables.tf +++ b/fast/stages/0-bootstrap/templates/providers.tf.tpl @@ -14,13 +14,20 @@ * limitations under the License. */ -variable "billing_account" { - type = string - default = "123456-123456-123456" +terraform { + backend "gcs" { + bucket = "${bucket}" + impersonate_service_account = "${sa}" + %{~ if backend_extra != null ~} + ${indent(4, backend_extra)} + %{~ endif ~} + } +} +provider "google" { + impersonate_service_account = "${sa}" +} +provider "google-beta" { + impersonate_service_account = "${sa}" } -variable "root_node" { - description = "The resource name of the parent Folder or Organization. Must be of the form folders/folder_id or organizations/org_id." - type = string - default = "folders/12345678" -} +# end provider.tf for ${name} diff --git a/fast/stages/0-bootstrap/templates/workflow-github.yaml b/fast/stages/0-bootstrap/templates/workflow-github.yaml new file mode 100644 index 00000000..87b5ae1a --- /dev/null +++ b/fast/stages/0-bootstrap/templates/workflow-github.yaml @@ -0,0 +1,202 @@ +# 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 +# +# 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: "FAST ${stage_name} stage" + +on: + pull_request: + branches: + - main + types: + - closed + - opened + - synchronize + +env: + FAST_OUTPUTS_BUCKET: ${outputs_bucket} + FAST_SERVICE_ACCOUNT: ${service_account} + FAST_WIF_PROVIDER: ${identity_provider} + SSH_AUTH_SOCK: /tmp/ssh_agent.sock + TF_PROVIDERS_FILE: ${tf_providers_file} + %{~ if tf_var_files != [] ~} + TF_VAR_FILES: ${join("\n ", tf_var_files)} + %{~ endif ~} + TF_VERSION: 1.3.2 + +jobs: + fast-pr: + permissions: + contents: read + id-token: write + issues: write + pull-requests: write + runs-on: ubuntu-latest + steps: + - id: checkout + name: Checkout repository + uses: actions/checkout@v3 + + # set up SSH key authentication to the modules repository + - id: ssh-config + name: Configure SSH authentication + run: | + ssh-agent -a "$SSH_AUTH_SOCK" > /dev/null + ssh-add - <<< "$${{ secrets.CICD_MODULES_KEY }}" + + # set up authentication via Workload identity Federation + - id: gcp-auth + name: Authenticate to Google Cloud + uses: google-github-actions/auth@v0 + with: + workload_identity_provider: $${{ env.FAST_WIF_PROVIDER }} + service_account: $${{ env.FAST_SERVICE_ACCOUNT }} + access_token_lifetime: 3600s + + - id: gcp-sdk + name: Set up Cloud SDK + uses: google-github-actions/setup-gcloud@v0 + with: + install_components: alpha + + # copy provider and tfvars files + - id: tf-config + name: Copy Terraform output files + run: | + gcloud alpha storage cp -r \ + "gs://$${{env.FAST_OUTPUTS_BUCKET}}/providers/$${{env.TF_PROVIDERS_FILE}}" ./ + %{~ if tf_var_files != [] ~} + gcloud alpha storage cp -r \ + "gs://$${{env.FAST_OUTPUTS_BUCKET}}/tfvars" ./ + for f in $${{env.TF_VAR_FILES}}; do + ln -s "tfvars/$f" ./ + done + %{~ endif ~} + + - id: tf-setup + name: Set up Terraform + uses: hashicorp/setup-terraform@v2.0.3 + with: + terraform_version: $${{ env.TF_VERSION }} + + # run Terraform init/validate/plan + - id: tf-init + name: Terraform init + continue-on-error: true + run: | + terraform init -no-color + + - id: tf-validate + continue-on-error: true + name: Terraform validate + run: terraform validate -no-color + + - id: tf-plan + name: Terraform plan + continue-on-error: true + run: | + terraform plan -input=false -out ../plan.out -no-color + + - id: tf-apply + if: github.event.pull_request.merged == true && success() + name: Terraform apply + continue-on-error: true + run: | + terraform apply -input=false -auto-approve -no-color ../plan.out + + - id: pr-comment + name: Post comment to Pull Request + continue-on-error: true + uses: actions/github-script@v6 + if: github.event_name == 'pull_request' + env: + PLAN: $${{ steps.tf-plan.outputs.stdout }}\n$${{ steps.tf-plan.outputs.stderr }} + with: + script: | + const output = `### Terraform Initialization \`$${{ steps.tf-init.outcome }}\` + + ### Terraform Validation \`$${{ steps.tf-validate.outcome }}\` + +
Validation Output + + \`\`\`\n + $${{ steps.tf-validate.outputs.stdout }} + \`\`\` + +
+ + ### Terraform Plan \`$${{ steps.tf-plan.outcome }}\` + +
Show Plan + + \`\`\`\n + $${process.env.PLAN.split('\n').filter(l => l.match(/^([A-Z\s].*|)$$/)).join('\n')} + \`\`\` + +
+ + ### Terraform Apply \`$${{ steps.tf-apply.outcome }}\` + + *Pusher: @$${{ github.actor }}, Action: \`$${{ github.event_name }}\`, Working Directory: \`$${{ env.tf_actions_working_dir }}\`, Workflow: \`$${{ github.workflow }}\`*`; + + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: output + }) + + - id: pr-short-comment + name: Post comment to Pull Request + uses: actions/github-script@v6 + if: github.event_name == 'pull_request' && steps.pr-comment.outcome != 'success' + with: + script: | + const output = `### Terraform Initialization \`$${{ steps.tf-init.outcome }}\` + + ### Terraform Validation \`$${{ steps.tf-validate.outcome }}\` + + ### Terraform Plan \`$${{ steps.tf-plan.outcome }}\` + + Plan output is in the action log. + + ### Terraform Apply \`$${{ steps.tf-apply.outcome }}\` + + *Pusher: @$${{ github.actor }}, Action: \`$${{ github.event_name }}\`, Working Directory: \`$${{ env.tf_actions_working_dir }}\`, Workflow: \`$${{ github.workflow }}\`*`; + + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: output + }) + + - id: check-init + name: Check init failure + if: steps.tf-init.outcome != 'success' + run: exit 1 + + - id: check-validate + name: Check validate failure + if: steps.tf-validate.outcome != 'success' + run: exit 1 + + - id: check-plan + name: Check plan failure + if: steps.tf-plan.outcome != 'success' + run: exit 1 + + - id: check-apply + name: Check apply failure + if: github.event.pull_request.merged == true && steps.tf-apply.outcome != 'success' + run: exit 1 diff --git a/fast/stages/0-bootstrap/templates/workflow-gitlab.yaml b/fast/stages/0-bootstrap/templates/workflow-gitlab.yaml new file mode 100644 index 00000000..8981e70b --- /dev/null +++ b/fast/stages/0-bootstrap/templates/workflow-gitlab.yaml @@ -0,0 +1,120 @@ +# 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 +# +# 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. + +default: + before_script: + - echo "$${CI_JOB_JWT_V2}" > token.txt + image: + name: hashicorp/terraform + entrypoint: + - "/usr/bin/env" + - "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + +variables: + GOOGLE_CREDENTIALS: cicd-sa-credentials.json + FAST_OUTPUTS_BUCKET: ${outputs_bucket} + FAST_SERVICE_ACCOUNT: ${service_account} + FAST_WIF_PROVIDER: ${identity_provider} + SSH_AUTH_SOCK: /tmp/ssh_agent.sock + TF_PROVIDERS_FILE: ${tf_providers_file} + TF_VAR_FILES: ${tf_var_files == [] ? "''" : join("\n ", tf_var_files)} + +stages: + - gcp-auth + - tf-files + - tf-plan + - tf-apply + +cache: + key: gcp-auth + paths: + - cicd-sa-credentials.json + - .tf-setup + +gcp-auth: + image: + name: google/cloud-sdk:slim + stage: gcp-auth + script: + - | + gcloud iam workload-identity-pools create-cred-config \ + $${FAST_WIF_PROVIDER} \ + --service-account=$${FAST_SERVICE_ACCOUNT} \ + --service-account-token-lifetime-seconds=3600 \ + --output-file=$${GOOGLE_CREDENTIALS} \ + --credential-source-file=token.txt +tf-files: + dependencies: + - gcp-auth + image: + name: google/cloud-sdk:slim + stage: tf-files + script: + # - gcloud components install -q alpha + - gcloud config set auth/credential_file_override $${GOOGLE_CREDENTIALS} + - mkdir -p .tf-setup + - | + gcloud alpha storage cp -r \ + "gs://$${FAST_OUTPUTS_BUCKET}/providers/$${TF_PROVIDERS_FILE}" .tf-setup/ + - | + gcloud alpha storage cp -r \ + "gs://$${FAST_OUTPUTS_BUCKET}/tfvars" .tf-setup/ + +tf-plan: + # uncomment the following lines and set the SSH key secret for private modules repo + # before_script: + # - | + # ssh-agent -a $SSH_AUTH_SOCK > /dev/null + # echo "$CICD_MODULES_KEY" | base64 -d | tr -d '\r' | ssh-add - > /dev/null + # mkdir -p ~/.ssh + # ssh-keyscan -H 'gitlab.com' >> ~/.ssh/known_hosts + # ssh-keyscan gitlab.com | sort -u - ~/.ssh/known_hosts -o ~/.ssh/known_hosts + stage: tf-plan + script: + - cp .tf-setup/$${TF_PROVIDERS_FILE} ./ + - | + for f in $${TF_VAR_FILES}; do + ln -s ".tf-setup/tfvars/$f" ./ + done + - terraform init + - terraform validate + - terraform plan + dependencies: + - tf-files + +tf-apply: + # uncomment the following lines and set the SSH key secret for private modules repo + # before_script: + # - | + # ssh-agent -a $SSH_AUTH_SOCK > /dev/null + # echo "$CICD_MODULES_KEY" | base64 -d | tr -d '\r' | ssh-add - > /dev/null + # mkdir -p ~/.ssh + # ssh-keyscan -H 'gitlab.com' >> ~/.ssh/known_hosts + # ssh-keyscan gitlab.com | sort -u - ~/.ssh/known_hosts -o ~/.ssh/known_hosts + stage: tf-apply + script: + - cp .tf-setup/$${TF_PROVIDERS_FILE} ./ + - | + for f in $${TF_VAR_FILES}; do + ln -s ".tf-setup/tfvars/$f" ./ + done + - terraform init + - terraform validate + - terraform apply -input=false -auto-approve + dependencies: + - tf-files + when: manual + only: + variables: + - $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH diff --git a/fast/stages/0-bootstrap/templates/workflow-sourcerepo.yaml b/fast/stages/0-bootstrap/templates/workflow-sourcerepo.yaml new file mode 100644 index 00000000..446c9c96 --- /dev/null +++ b/fast/stages/0-bootstrap/templates/workflow-sourcerepo.yaml @@ -0,0 +1,98 @@ +# 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 +# +# 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. + +steps: + - name: alpine:3 + id: tf-download + entrypoint: sh + args: + - -eEuo + - pipefail + - -c + - |- + mkdir -p /builder/home/.local/bin + wget https://releases.hashicorp.com/terraform/$${_TF_VERSION}/terraform_$${_TF_VERSION}_linux_amd64.zip + unzip terraform_$${_TF_VERSION}_linux_amd64.zip -d /builder/home/.local/bin + rm terraform_$${_TF_VERSION}_linux_amd64.zip + chmod 755 /builder/home/.local/bin/terraform + - name: alpine:3 + id: tf-check-format + entrypoint: sh + args: + - -eEuo + - pipefail + - -c + - |- + terraform fmt -recursive -check /workspace/ + - name: gcr.io/google.com/cloudsdktool/cloud-sdk:alpine + id: tf-files + entrypoint: bash + args: + - -eEuo + - pipefail + - -c + - |- + /google-cloud-sdk/bin/gsutil cp \ + gs://$${_FAST_OUTPUTS_BUCKET}/providers/$${_TF_PROVIDERS_FILE} ./ + /google-cloud-sdk/bin/gsutil cp -r \ + gs://$${_FAST_OUTPUTS_BUCKET}/tfvars ./ + for f in $${_TF_VAR_FILES}; do + ln -s tfvars/$f ./ + done + - name: alpine:3 + id: tf-init + entrypoint: sh + args: + - -eEuo + - pipefail + - -c + - |- + terraform init -no-color + - name: alpine:3 + id: tf-check-validate + entrypoint: sh + args: + - -eEuo + - pipefail + - -c + - |- + terraform validate -no-color + - name: alpine:3 + id: tf-plan + entrypoint: sh + args: + - -eEuo + - pipefail + - -c + - |- + terraform plan -no-color -input=false -out plan.out + # store artifact and ask for approval here if needed + - name: alpine:3 + id: tf-apply + entrypoint: sh + args: + - -eEuo + - pipefail + - -c + - |- + terraform apply -no-color -input=false -auto-approve plan.out +options: + env: + - PATH=/usr/local/bin:/usr/bin:/bin:/builder/home/.local/bin + logging: CLOUD_LOGGING_ONLY +substitutions: + _FAST_OUTPUTS_BUCKET: ${outputs_bucket} + _TF_PROVIDERS_FILE: ${tf_providers_file} + _TF_VAR_FILES: ${tf_var_files == [] ? "''" : join("\n ", tf_var_files)} + _TF_VERSION: 1.3.2 diff --git a/fast/stages/00-bootstrap/terraform.tfvars.sample b/fast/stages/0-bootstrap/terraform.tfvars.sample similarity index 100% rename from fast/stages/00-bootstrap/terraform.tfvars.sample rename to fast/stages/0-bootstrap/terraform.tfvars.sample diff --git a/fast/stages/00-bootstrap/variables.tf b/fast/stages/0-bootstrap/variables.tf similarity index 88% rename from fast/stages/00-bootstrap/variables.tf rename to fast/stages/0-bootstrap/variables.tf index 0b9f37c2..4ab90deb 100644 --- a/fast/stages/00-bootstrap/variables.tf +++ b/fast/stages/0-bootstrap/variables.tf @@ -15,11 +15,15 @@ */ variable "billing_account" { - description = "Billing account id and organization id ('nnnnnnnn' or null)." + description = "Billing account id. If billing account is not part of the same org set `is_org_level` to false." type = object({ - id = string - organization_id = number + id = string + is_org_level = optional(bool, true) }) + validation { + condition = var.billing_account.is_org_level != null + error_message = "Invalid `null` value for `billing_account.is_org_level`." + } } variable "bootstrap_user" { @@ -31,24 +35,18 @@ variable "bootstrap_user" { variable "cicd_repositories" { description = "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." type = object({ - bootstrap = object({ + bootstrap = optional(object({ branch = string identity_provider = string name = string type = string - }) - cicd = object({ + })) + resman = optional(object({ branch = string identity_provider = string name = string type = string - }) - resman = object({ - branch = string - identity_provider = string - name = string - type = string - }) + })) }) default = null validation { @@ -85,29 +83,25 @@ variable "custom_role_names" { type = object({ organization_iam_admin = string service_project_network_admin = string + tenant_network_admin = string }) default = { organization_iam_admin = "organizationIamAdmin" service_project_network_admin = "serviceProjectNetworkAdmin" + tenant_network_admin = "tenantNetworkAdmin" } } variable "fast_features" { description = "Selective control for top-level FAST features." type = object({ - data_platform = bool - gke = bool - project_factory = bool - sandbox = bool - teams = bool + data_platform = optional(bool, false) + gke = optional(bool, false) + project_factory = optional(bool, false) + sandbox = optional(bool, false) + teams = optional(bool, false) }) - default = { - data_platform = true - gke = true - project_factory = true - sandbox = true - teams = true - } + default = {} nullable = false } @@ -183,11 +177,11 @@ variable "log_sinks" { default = { audit-logs = { filter = "logName:\"/logs/cloudaudit.googleapis.com%2Factivity\" OR logName:\"/logs/cloudaudit.googleapis.com%2Fsystem_event\"" - type = "bigquery" + type = "logging" } vpc-sc = { filter = "protoPayload.metadata.@type=\"type.googleapis.com/google.cloud.audit.VpcServiceControlAuditMetadata\"" - type = "bigquery" + type = "logging" } } validation { diff --git a/fast/stages/00-bootstrap/templates b/fast/stages/00-bootstrap/templates deleted file mode 120000 index bcb6967b..00000000 --- a/fast/stages/00-bootstrap/templates +++ /dev/null @@ -1 +0,0 @@ -../../assets/templates \ No newline at end of file diff --git a/fast/stages/01-resman/templates b/fast/stages/01-resman/templates deleted file mode 120000 index bcb6967b..00000000 --- a/fast/stages/01-resman/templates +++ /dev/null @@ -1 +0,0 @@ -../../assets/templates \ No newline at end of file diff --git a/fast/stages/01-resman/IAM.md b/fast/stages/1-resman/IAM.md similarity index 100% rename from fast/stages/01-resman/IAM.md rename to fast/stages/1-resman/IAM.md diff --git a/fast/stages/01-resman/README.md b/fast/stages/1-resman/README.md similarity index 68% rename from fast/stages/01-resman/README.md rename to fast/stages/1-resman/README.md index d3656352..c2091eb5 100644 --- a/fast/stages/01-resman/README.md +++ b/fast/stages/1-resman/README.md @@ -34,17 +34,21 @@ Additionally, a few critical benefits are directly provided by this design: - grouping application resources and services using teams or business logic is a flexible approach, which maps well to typical operational or budget requirements - automation stages (e.g. Networking) can be segregated in a simple and effective way, by creating the required service accounts and buckets for each stage here, and applying a handful of IAM roles to the relevant folder -For a discussion on naming, please refer to the [Bootstrap stage documentation](../00-bootstrap/README.md#naming), as the same approach is shared by all stages. +For a discussion on naming, please refer to the [Bootstrap stage documentation](../0-bootstrap/README.md#naming), as the same approach is shared by all stages. + +### Multitenancy + +Fully multitenant hierarchies inside the same organization are implemented via [separate additional stages](../../stages-multitenant/) that need to be run once for each tenant, and require this stage as a prerequisite. ### Workload Identity Federation and CI/CD This stage also implements optional support for CI/CD, much in the same way as the bootstrap stage. The only difference is on Workload Identity Federation, which is only configured in bootstrap and made available here via stage interface variables (the automatically generated `.tfvars` files). -For details on how to configure CI/CD please refer to the [relevant section in the bootstrap stage documentation](../00-bootstrap/README.md#cicd-repositories). +For details on how to configure CI/CD please refer to the [relevant section in the bootstrap stage documentation](../0-bootstrap/README.md#cicd-repositories). ## How to run this stage -This stage is meant to be executed after the [bootstrap](../00-bootstrap) stage has run, as it leverages the automation service account and bucket created there. The relevant user groups must also exist, but that's one of the requirements for the previous stage too, so if you ran that successfully, you're good to go. +This stage is meant to be executed after the [bootstrap](../0-bootstrap) stage has run, as it leverages the automation service account and bucket created there. The relevant user groups must also exist, but that's one of the requirements for the previous stage too, so if you ran that successfully, you're good to go. It's of course possible to run this stage in isolation, but that's outside the scope of this document, and you would need to refer to the code for the bootstrap stage for the actual roles needed. @@ -56,7 +60,7 @@ The default way of making sure you have the right permissions, is to use the ide To simplify setup, the previous stage pre-configures a valid providers file in its output, and optionally writes it to a local file if the `outputs_location` variable is set to a valid path. -If you have set a valid value for `outputs_location` in the bootstrap stage (see the [bootstrap stage README](../00-bootstrap/#output-files-and-cross-stage-variables) for more details), simply link the relevant `providers.tf` file from this stage's folder in the path you specified: +If you have set a valid value for `outputs_location` in the bootstrap stage (see the [bootstrap stage README](../0-bootstrap/#output-files-and-cross-stage-variables) for more details), simply link the relevant `providers.tf` file from this stage's folder in the path you specified: ```bash # `outputs_location` is set to `~/fast-config` @@ -66,12 +70,12 @@ ln -s ~/fast-config/providers/01-resman-providers.tf . If you have not configured `outputs_location` in bootstrap, you can derive the providers file from that stage's outputs: ```bash -cd ../00-bootstrap +cd ../0-bootstrap terraform output -json providers | jq -r '.["01-resman"]' \ - > ../01-resman/providers.tf + > ../1-resman/providers.tf ``` -If you want to continue to rely on `outputs_location` logic, create a `terraform.tfvars` file and configure it as described [here](../00-bootstrap/#output-files-and-cross-stage-variables). +If you want to continue to rely on `outputs_location` logic, create a `terraform.tfvars` file and configure it as described [here](../0-bootstrap/#output-files-and-cross-stage-variables). ### Variable configuration @@ -82,17 +86,17 @@ There are two broad sets of variables you will need to fill in: To avoid the tedious job of filling in the first group of variable with values derived from other stages' outputs, the same mechanism used above for the provider configuration can be used to leverage pre-configured `.tfvars` files. -If you configured a valid path for `outputs_location` in the bootstrap stage, simply link the relevant `*.auto.tfvars.json` files from the outputs folder. For this stage, you need the `globals.auto.tfvars.json` file containing global values compiled manually for the bootstrap stage, and `00-bootstrap.auto.tfvars.json` containing values derived from resources managed by the bootstrap stage: +If you configured a valid path for `outputs_location` in the bootstrap stage, simply link the relevant `*.auto.tfvars.json` files from the outputs folder. For this stage, you need the `globals.auto.tfvars.json` file containing global values compiled manually for the bootstrap stage, and `0-bootstrap.auto.tfvars.json` containing values derived from resources managed by the bootstrap stage: ```bash # `outputs_location` is set to `~/fast-config` ln -s ~/fast-config/tfvars/globals.auto.tfvars.json . -ln -s ~/fast-config/tfvars/00-bootstrap.auto.tfvars.json . +ln -s ~/fast-config/tfvars/0-bootstrap.auto.tfvars.json . ``` A second set of variables is specific to this stage, they are all optional so if you need to customize them, create an extra `terraform.tfvars` file. -Refer to the [Variables](#variables) table at the bottom of this document, for a full list of variables, their origin (e.g. a stage or specific to this one), and descriptions explaining their meaning. The sections below also describe some of the possible customizations. For billing configurations, refer to the [Bootstrap documentation on billing](../00-bootstrap/README.md#billing-account) as the `billing_account` variable is identical across all stages. +Refer to the [Variables](#variables) table at the bottom of this document, for a full list of variables, their origin (e.g. a stage or specific to this one), and descriptions explaining their meaning. The sections below also describe some of the possible customizations. For billing configurations, refer to the [Bootstrap documentation on billing](../0-bootstrap/README.md#billing-account) as the `billing_account` variable is identical across all stages. Once done, you can run this stage: @@ -141,7 +145,7 @@ For policies where additional data is needed, a root-level `organization_policy_ ### IAM -IAM roles can be easily edited in the relevant `branch-xxx.tf` file, following the best practice outlined in the [bootstrap stage](../00-bootstrap#customizations) documentation of separating user-level and service-account level IAM policies in modules' `iam_groups`, `iam`, and `iam_additive` variables. +IAM roles can be easily edited in the relevant `branch-xxx.tf` file, following the best practice outlined in the [bootstrap stage](../0-bootstrap#customizations) documentation of separating user-level and service-account level IAM policies in modules' `iam_groups`, `iam`, and `iam_additive` variables. A full reference of IAM roles managed by this stage [is available here](./IAM.md). @@ -156,11 +160,11 @@ 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. | organization | google_billing_account_iam_member | -| [branch-data-platform.tf](./branch-data-platform.tf) | Data Platform stages resources. | folder · gcs · iam-service-account | | +| [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 | | -| [branch-project-factory.tf](./branch-project-factory.tf) | Project factory stage resources. | gcs · iam-service-account | | +| [branch-project-factory.tf](./branch-project-factory.tf) | Project factory stage resources. | gcs · iam-service-account | google_organization_iam_member | | [branch-sandbox.tf](./branch-sandbox.tf) | Sandbox stage resources. | folder · gcs · iam-service-account | | | [branch-security.tf](./branch-security.tf) | Security stage resources. | folder · gcs · iam-service-account | | | [branch-teams.tf](./branch-teams.tf) | Team stage resources. | folder · gcs · iam-service-account | | @@ -170,7 +174,7 @@ Due to its simplicity, this stage lends itself easily to customizations: adding | [cicd-project-factory.tf](./cicd-project-factory.tf) | CI/CD resources for the teams branch. | iam-service-account · source-repository | | | [cicd-security.tf](./cicd-security.tf) | CI/CD resources for the security branch. | iam-service-account · source-repository | | | [main.tf](./main.tf) | Module-level locals and resources. | | | -| [organization.tf](./organization.tf) | Organization policies. | organization | google_organization_iam_member | +| [organization.tf](./organization.tf) | Organization policies. | organization | | | [outputs-files.tf](./outputs-files.tf) | Output files persistence to local filesystem. | | local_file | | [outputs-gcs.tf](./outputs-gcs.tf) | Output files persistence to automation GCS bucket. | | google_storage_bucket_object | | [outputs.tf](./outputs.tf) | Module outputs. | | | @@ -180,34 +184,34 @@ 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({…}) | ✓ | | 00-bootstrap | -| [billing_account](variables.tf#L38) | Billing account id and organization id ('nnnnnnnn' or null). | object({…}) | ✓ | | 00-bootstrap | -| [organization](variables.tf#L197) | Organization details. | object({…}) | ✓ | | 00-bootstrap | -| [prefix](variables.tf#L221) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 00-bootstrap | -| [cicd_repositories](variables.tf#L47) | 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#L129) | Custom roles defined at the org level, in key => id format. | object({…}) | | null | 00-bootstrap | -| [data_dir](variables.tf#L138) | Relative path for the folder storing configuration data. | string | | "data" | | -| [fast_features](variables.tf#L144) | Selective control for top-level FAST features. | object({…}) | | {…} | 00-bootstrap | -| [groups](variables.tf#L164) | Group names to grant organization-level permissions. | map(string) | | {…} | 00-bootstrap | -| [locations](variables.tf#L179) | Optional locations for GCS, BigQuery, and logging buckets created here. | object({…}) | | {…} | 00-bootstrap | -| [organization_policy_configs](variables.tf#L207) | Organization policies customization. | object({…}) | | null | | -| [outputs_location](variables.tf#L215) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | string | | null | | -| [tag_names](variables.tf#L232) | Customized names for resource management tags. | object({…}) | | {…} | | -| [team_folders](variables.tf#L249) | Team folders to be created. Format is described in a code comment. | map(object({…})) | | null | | +| [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 | | ## Outputs | name | description | sensitive | consumers | |---|---|:---:|---| -| [cicd_repositories](outputs.tf#L197) | WIF configuration for CI/CD repositories. | | | -| [dataplatform](outputs.tf#L211) | Data for the Data Platform stage. | | | -| [gke_multitenant](outputs.tf#L227) | Data for the GKE multitenant stage. | | 03-gke-multitenant | -| [networking](outputs.tf#L248) | Data for the networking stage. | | | -| [project_factories](outputs.tf#L257) | Data for the project factories stage. | | | -| [providers](outputs.tf#L272) | Terraform provider files for this stage and dependent stages. | ✓ | 02-networking · 02-security · 03-dataplatform · xx-sandbox · xx-teams | -| [sandbox](outputs.tf#L279) | Data for the sandbox stage. | | xx-sandbox | -| [security](outputs.tf#L293) | Data for the networking stage. | | 02-security | -| [teams](outputs.tf#L303) | Data for the teams stage. | | | -| [tfvars](outputs.tf#L315) | Terraform variable files for the following stages. | ✓ | | +| [cicd_repositories](outputs.tf#L210) | WIF configuration for CI/CD repositories. | | | +| [dataplatform](outputs.tf#L224) | Data for the Data Platform stage. | | | +| [gke_multitenant](outputs.tf#L240) | Data for the GKE multitenant stage. | | 03-gke-multitenant | +| [networking](outputs.tf#L261) | Data for the networking stage. | | | +| [project_factories](outputs.tf#L270) | Data for the project factories stage. | | | +| [providers](outputs.tf#L285) | Terraform provider files for this stage and dependent stages. | ✓ | 02-networking · 02-security · 03-dataplatform · xx-sandbox · xx-teams | +| [sandbox](outputs.tf#L292) | Data for the sandbox stage. | | xx-sandbox | +| [security](outputs.tf#L306) | Data for the networking stage. | | 02-security | +| [teams](outputs.tf#L316) | Data for the teams stage. | | | +| [tfvars](outputs.tf#L328) | Terraform variable files for the following stages. | ✓ | | diff --git a/fast/stages/01-resman/billing.tf b/fast/stages/1-resman/billing.tf similarity index 77% rename from fast/stages/01-resman/billing.tf rename to fast/stages/1-resman/billing.tf index fe497c7c..ba20ab05 100644 --- a/fast/stages/01-resman/billing.tf +++ b/fast/stages/1-resman/billing.tf @@ -34,23 +34,11 @@ locals { # billing account in same org (resources is in the organization.tf file) -# billing account in a different org - -module "billing-organization-ext" { - source = "../../../modules/organization" - count = local.billing_org_ext ? 1 : 0 - organization_id = "organizations/${var.billing_account.organization_id}" - iam_additive = { - "roles/billing.user" = local.billing_ext_users - "roles/billing.costsManager" = local.billing_ext_users - } -} - # standalone billing account resource "google_billing_account_iam_member" "billing_ext_admin" { for_each = toset( - local.billing_ext ? local.billing_ext_users : [] + !var.billing_account.is_org_level ? local.billing_ext_users : [] ) billing_account_id = var.billing_account.id role = "roles/billing.user" @@ -59,7 +47,7 @@ resource "google_billing_account_iam_member" "billing_ext_admin" { resource "google_billing_account_iam_member" "billing_ext_costsmanager" { for_each = toset( - local.billing_ext ? local.billing_ext_users : [] + !var.billing_account.is_org_level ? local.billing_ext_users : [] ) billing_account_id = var.billing_account.id role = "roles/billing.costsManager" diff --git a/fast/stages/01-resman/branch-data-platform.tf b/fast/stages/1-resman/branch-data-platform.tf similarity index 86% rename from fast/stages/01-resman/branch-data-platform.tf rename to fast/stages/1-resman/branch-data-platform.tf index 66cc9fbb..7a93e7a5 100644 --- a/fast/stages/01-resman/branch-data-platform.tf +++ b/fast/stages/1-resman/branch-data-platform.tf @@ -137,3 +137,22 @@ module "branch-dp-prod-gcs" { "roles/storage.objectAdmin" = [module.branch-dp-prod-sa.0.iam_email] } } + +resource "google_organization_iam_member" "org_policy_admin_dp" { + for_each = !var.fast_features.data_platform ? {} : { + data-dev = ["data", "development", module.branch-dp-dev-sa.0.iam_email] + data-prod = ["data", "production", module.branch-dp-prod-sa.0.iam_email] + } + org_id = var.organization.id + role = "roles/orgpolicy.policyAdmin" + member = each.value.2 + condition { + title = "org_policy_tag_dp_scoped" + description = "Org policy tag scoped grant for ${each.value.0}/${each.value.1}." + expression = <<-END + resource.matchTag('${var.organization.id}/${var.tag_names.context}', '${each.value.0}') + && + resource.matchTag('${var.organization.id}/${var.tag_names.environment}', '${each.value.1}') + END + } +} diff --git a/fast/stages/01-resman/branch-gke.tf b/fast/stages/1-resman/branch-gke.tf similarity index 94% rename from fast/stages/01-resman/branch-gke.tf rename to fast/stages/1-resman/branch-gke.tf index 84ca41ed..76777d8f 100644 --- a/fast/stages/01-resman/branch-gke.tf +++ b/fast/stages/1-resman/branch-gke.tf @@ -77,7 +77,11 @@ module "branch-gke-dev-sa" { prefix = var.prefix iam = { "roles/iam.serviceAccountTokenCreator" = concat( - ["group:${local.groups.gcp-devops}"], + ( + local.groups.gcp-devops == null + ? [] + : ["group:${local.groups.gcp-devops}"] + ), compact([ try(module.branch-gke-dev-sa-cicd.0.iam_email, null) ]) @@ -97,7 +101,11 @@ module "branch-gke-prod-sa" { prefix = var.prefix iam = { "roles/iam.serviceAccountTokenCreator" = concat( - ["group:${local.groups.gcp-devops}"], + ( + local.groups.gcp-devops == null + ? [] + : ["group:${local.groups.gcp-devops}"] + ), compact([ try(module.branch-gke-prod-sa-cicd.0.iam_email, null) ]) diff --git a/fast/stages/01-resman/branch-networking.tf b/fast/stages/1-resman/branch-networking.tf similarity index 98% rename from fast/stages/01-resman/branch-networking.tf rename to fast/stages/1-resman/branch-networking.tf index 530cf6b0..1fd7a6b3 100644 --- a/fast/stages/01-resman/branch-networking.tf +++ b/fast/stages/1-resman/branch-networking.tf @@ -20,7 +20,7 @@ module "branch-network-folder" { source = "../../../modules/folder" parent = "organizations/${var.organization.id}" name = "Networking" - group_iam = { + group_iam = local.groups.gcp-network-admins == null ? {} : { (local.groups.gcp-network-admins) = [ # add any needed roles for resources/services not managed via Terraform, # or replace editor with ~viewer if no broad resource management needed diff --git a/fast/stages/01-resman/branch-project-factory.tf b/fast/stages/1-resman/branch-project-factory.tf similarity index 78% rename from fast/stages/01-resman/branch-project-factory.tf rename to fast/stages/1-resman/branch-project-factory.tf index 41651a28..d74a8acb 100644 --- a/fast/stages/01-resman/branch-project-factory.tf +++ b/fast/stages/1-resman/branch-project-factory.tf @@ -79,3 +79,22 @@ module "branch-pf-prod-gcs" { "roles/storage.objectAdmin" = [module.branch-pf-prod-sa.0.iam_email] } } + +resource "google_organization_iam_member" "org_policy_admin_pf" { + for_each = !var.fast_features.project_factory ? {} : { + pf-dev = ["teams", "development", module.branch-pf-dev-sa.0.iam_email] + pf-prod = ["teams", "production", module.branch-pf-prod-sa.0.iam_email] + } + org_id = var.organization.id + role = "roles/orgpolicy.policyAdmin" + member = each.value.2 + condition { + title = "org_policy_tag_pf_scoped" + description = "Org policy tag scoped grant for ${each.value.0}/${each.value.1}." + expression = <<-END + resource.matchTag('${var.organization.id}/${var.tag_names.context}', '${each.value.0}') + && + resource.matchTag('${var.organization.id}/${var.tag_names.environment}', '${each.value.1}') + END + } +} diff --git a/fast/stages/01-resman/branch-sandbox.tf b/fast/stages/1-resman/branch-sandbox.tf similarity index 100% rename from fast/stages/01-resman/branch-sandbox.tf rename to fast/stages/1-resman/branch-sandbox.tf diff --git a/fast/stages/01-resman/branch-security.tf b/fast/stages/1-resman/branch-security.tf similarity index 96% rename from fast/stages/01-resman/branch-security.tf rename to fast/stages/1-resman/branch-security.tf index c7b4fc97..4b0c0fb1 100644 --- a/fast/stages/01-resman/branch-security.tf +++ b/fast/stages/1-resman/branch-security.tf @@ -20,7 +20,7 @@ module "branch-security-folder" { source = "../../../modules/folder" parent = "organizations/${var.organization.id}" name = "Security" - group_iam = { + group_iam = local.groups.gcp-security-admins == null ? {} : { (local.groups.gcp-security-admins) = [ # add any needed roles for resources/services not managed via Terraform, # e.g. @@ -51,7 +51,7 @@ module "branch-security-folder" { module "branch-security-sa" { source = "../../../modules/iam-service-account" project_id = var.automation.project_id - name = "prod-resman-sec-0" + name = "security-0" display_name = "Terraform resman security service account." prefix = var.prefix iam = { diff --git a/fast/stages/01-resman/branch-teams.tf b/fast/stages/1-resman/branch-teams.tf similarity index 100% rename from fast/stages/01-resman/branch-teams.tf rename to fast/stages/1-resman/branch-teams.tf diff --git a/fast/stages/01-resman/cicd-data-platform.tf b/fast/stages/1-resman/cicd-data-platform.tf similarity index 99% rename from fast/stages/01-resman/cicd-data-platform.tf rename to fast/stages/1-resman/cicd-data-platform.tf index 5b07883c..e69fd5bc 100644 --- a/fast/stages/01-resman/cicd-data-platform.tf +++ b/fast/stages/1-resman/cicd-data-platform.tf @@ -103,7 +103,7 @@ module "branch-dp-dev-sa-cicd" { each.value.type == "sourcerepo" # used directly from the cloud build trigger for source repos ? { - "roles/iam.serviceAccountUser" = local.automation_resman_sa + "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam } # impersonated via workload identity federation for external repos : { @@ -146,7 +146,7 @@ module "branch-dp-prod-sa-cicd" { each.value.type == "sourcerepo" # used directly from the cloud build trigger for source repos ? { - "roles/iam.serviceAccountUser" = local.automation_resman_sa + "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam } # impersonated via workload identity federation for external repos : { diff --git a/fast/stages/01-resman/cicd-gke.tf b/fast/stages/1-resman/cicd-gke.tf similarity index 99% rename from fast/stages/01-resman/cicd-gke.tf rename to fast/stages/1-resman/cicd-gke.tf index fa4f8767..4388a3ac 100644 --- a/fast/stages/01-resman/cicd-gke.tf +++ b/fast/stages/1-resman/cicd-gke.tf @@ -103,7 +103,7 @@ module "branch-gke-dev-sa-cicd" { each.value.type == "sourcerepo" # used directly from the cloud build trigger for source repos ? { - "roles/iam.serviceAccountUser" = local.automation_resman_sa + "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam } # impersonated via workload identity federation for external repos : { @@ -146,7 +146,7 @@ module "branch-gke-prod-sa-cicd" { each.value.type == "sourcerepo" # used directly from the cloud build trigger for source repos ? { - "roles/iam.serviceAccountUser" = local.automation_resman_sa + "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam } # impersonated via workload identity federation for external repos : { diff --git a/fast/stages/01-resman/cicd-networking.tf b/fast/stages/1-resman/cicd-networking.tf similarity index 99% rename from fast/stages/01-resman/cicd-networking.tf rename to fast/stages/1-resman/cicd-networking.tf index 894348ff..245d5ed0 100644 --- a/fast/stages/01-resman/cicd-networking.tf +++ b/fast/stages/1-resman/cicd-networking.tf @@ -65,7 +65,7 @@ module "branch-network-sa-cicd" { each.value.type == "sourcerepo" # used directly from the cloud build trigger for source repos ? { - "roles/iam.serviceAccountUser" = local.automation_resman_sa + "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam } # impersonated via workload identity federation for external repos : { diff --git a/fast/stages/01-resman/cicd-project-factory.tf b/fast/stages/1-resman/cicd-project-factory.tf similarity index 99% rename from fast/stages/01-resman/cicd-project-factory.tf rename to fast/stages/1-resman/cicd-project-factory.tf index 8f357ce6..1e2b4565 100644 --- a/fast/stages/01-resman/cicd-project-factory.tf +++ b/fast/stages/1-resman/cicd-project-factory.tf @@ -114,7 +114,7 @@ module "branch-pf-dev-sa-cicd" { each.value.type == "sourcerepo" # used directly from the cloud build trigger for source repos ? { - "roles/iam.serviceAccountUser" = local.automation_resman_sa + "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam } # impersonated via workload identity federation for external repos : { @@ -162,7 +162,7 @@ module "branch-pf-prod-sa-cicd" { each.value.type == "sourcerepo" # used directly from the cloud build trigger for source repos ? { - "roles/iam.serviceAccountUser" = local.automation_resman_sa + "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam } # impersonated via workload identity federation for external repos : { diff --git a/fast/stages/01-resman/cicd-security.tf b/fast/stages/1-resman/cicd-security.tf similarity index 99% rename from fast/stages/01-resman/cicd-security.tf rename to fast/stages/1-resman/cicd-security.tf index dd27a473..c35bfbfb 100644 --- a/fast/stages/01-resman/cicd-security.tf +++ b/fast/stages/1-resman/cicd-security.tf @@ -65,7 +65,7 @@ module "branch-security-sa-cicd" { each.value.type == "sourcerepo" # used directly from the cloud build trigger for source repos ? { - "roles/iam.serviceAccountUser" = local.automation_resman_sa + "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam } # impersonated via workload identity federation for external repos : { diff --git a/fast/stages/1-resman/data/org-policies/compute.yaml b/fast/stages/1-resman/data/org-policies/compute.yaml new file mode 100644 index 00000000..0d27ac42 --- /dev/null +++ b/fast/stages/1-resman/data/org-policies/compute.yaml @@ -0,0 +1,73 @@ +# skip boilerplate check +# +# sample subset of useful organization policies, edit to suit requirements + +compute.disableGuestAttributesAccess: + enforce: true + +compute.requireOsLogin: + enforce: true + +compute.restrictLoadBalancerCreationForTypes: + allow: + values: + - in:INTERNAL + +compute.skipDefaultNetworkCreation: + enforce: true + +compute.vmExternalIpAccess: + deny: + all: true + + +# compute.disableInternetNetworkEndpointGroup: +# enforce: true + +# compute.disableNestedVirtualization: +# enforce: true + +# compute.disableSerialPortAccess: +# enforce: true + +# compute.restrictCloudNATUsage: +# deny: +# all: true + +# compute.restrictDedicatedInterconnectUsage: +# deny: +# all: true + +# compute.restrictPartnerInterconnectUsage: +# deny: +# all: true + +# compute.restrictProtocolForwardingCreationForTypes: +# deny: +# all: true + +# compute.restrictSharedVpcHostProjects: +# deny: +# all: true + +# compute.restrictSharedVpcSubnetworks: +# deny: +# all: true + +# compute.restrictVpcPeering: +# deny: +# all: true + +# compute.restrictVpnPeerIPs: +# deny: +# all: true + +# compute.restrictXpnProjectLienRemoval: +# enforce: true + +# compute.setNewProjectDefaultToZonalDNSOnly: +# enforce: true + +# compute.vmCanIpForward: +# deny: +# all: true diff --git a/fast/stages/1-resman/data/org-policies/iam.yaml b/fast/stages/1-resman/data/org-policies/iam.yaml new file mode 100644 index 00000000..4d83f827 --- /dev/null +++ b/fast/stages/1-resman/data/org-policies/iam.yaml @@ -0,0 +1,12 @@ +# skip boilerplate check +# +# sample subset of useful organization policies, edit to suit requirements + +iam.automaticIamGrantsForDefaultServiceAccounts: + enforce: true + +iam.disableServiceAccountKeyCreation: + enforce: true + +iam.disableServiceAccountKeyUpload: + enforce: true diff --git a/fast/stages/1-resman/data/org-policies/serverless.yaml b/fast/stages/1-resman/data/org-policies/serverless.yaml new file mode 100644 index 00000000..de62e6c7 --- /dev/null +++ b/fast/stages/1-resman/data/org-policies/serverless.yaml @@ -0,0 +1,26 @@ +# skip boilerplate check +# +# sample subset of useful organization policies, edit to suit requirements + +run.allowedIngress: + allow: + values: + - is:internal + +# run.allowedVPCEgress: +# allow: +# values: +# - is:private-ranges-only + +# cloudfunctions.allowedIngressSettings: +# allow: +# values: +# - is:ALLOW_INTERNAL_ONLY + +# cloudfunctions.allowedVpcConnectorEgressSettings: +# allow: +# values: +# - is:PRIVATE_RANGES_ONLY + +# cloudfunctions.requireVPCConnector: +# enforce: true diff --git a/fast/stages/1-resman/data/org-policies/sql.yaml b/fast/stages/1-resman/data/org-policies/sql.yaml new file mode 100644 index 00000000..88b84d9d --- /dev/null +++ b/fast/stages/1-resman/data/org-policies/sql.yaml @@ -0,0 +1,9 @@ +# skip boilerplate check +# +# sample subset of useful organization policies, edit to suit requirements + +sql.restrictAuthorizedNetworks: + enforce: true + +sql.restrictPublicIp: + enforce: true diff --git a/fast/stages/1-resman/data/org-policies/storage.yaml b/fast/stages/1-resman/data/org-policies/storage.yaml new file mode 100644 index 00000000..6c0a673f --- /dev/null +++ b/fast/stages/1-resman/data/org-policies/storage.yaml @@ -0,0 +1,6 @@ +# skip boilerplate check +# +# sample subset of useful organization policies, edit to suit requirements + +storage.uniformBucketLevelAccess: + enforce: true diff --git a/fast/stages/1-resman/diagram.png b/fast/stages/1-resman/diagram.png new file mode 100644 index 00000000..d1026318 Binary files /dev/null and b/fast/stages/1-resman/diagram.png differ diff --git a/fast/stages/1-resman/diagram.svg b/fast/stages/1-resman/diagram.svg new file mode 100644 index 00000000..541db3f4 --- /dev/null +++ b/fast/stages/1-resman/diagram.svg @@ -0,0 +1,1340 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/fast/stages/01-resman/main.tf b/fast/stages/1-resman/main.tf similarity index 78% rename from fast/stages/01-resman/main.tf rename to fast/stages/1-resman/main.tf index 0651ee3f..ff08b8c5 100644 --- a/fast/stages/01-resman/main.tf +++ b/fast/stages/1-resman/main.tf @@ -17,15 +17,13 @@ locals { # convenience flags that express where billing account resides automation_resman_sa = try( - [format( - "serviceAccount:%s", - data.google_client_openid_userinfo.provider_identity.0.email - )], - [] + data.google_client_openid_userinfo.provider_identity.0.email, null + ) + automation_resman_sa_iam = ( + local.automation_resman_sa == null + ? [] + : ["serviceAccount:${local.automation_resman_sa}"] ) - billing_ext = var.billing_account.organization_id == null - billing_org = var.billing_account.organization_id == var.organization.id - billing_org_ext = !local.billing_ext && !local.billing_org branch_optional_sa_lists = { dp-dev = compact([try(module.branch-dp-dev-sa.0.iam_email, "")]) dp-prod = compact([try(module.branch-dp-prod-sa.0.iam_email, "")]) @@ -51,16 +49,16 @@ locals { } cicd_workflow_var_files = { stage_2 = [ - "00-bootstrap.auto.tfvars.json", - "01-resman.auto.tfvars.json", + "0-bootstrap.auto.tfvars.json", + "1-resman.auto.tfvars.json", "globals.auto.tfvars.json" ] stage_3 = [ - "00-bootstrap.auto.tfvars.json", - "01-resman.auto.tfvars.json", + "0-bootstrap.auto.tfvars.json", + "1-resman.auto.tfvars.json", "globals.auto.tfvars.json", - "02-networking.auto.tfvars.json", - "02-security.auto.tfvars.json" + "2-networking.auto.tfvars.json", + "2-security.auto.tfvars.json" ] } custom_roles = coalesce(var.custom_roles, {}) @@ -74,8 +72,7 @@ locals { k => "${v}@${var.organization.domain}" } groups_iam = { - for k, v in local.groups : - k => "group:${v}" + for k, v in local.groups : k => v != null ? "group:${v}" : null } identity_providers = coalesce( try(var.automation.federated_identity_providers, null), {} diff --git a/fast/stages/01-resman/organization.tf b/fast/stages/1-resman/organization.tf similarity index 66% rename from fast/stages/01-resman/organization.tf rename to fast/stages/1-resman/organization.tf index 7ecf7952..3d7db46d 100644 --- a/fast/stages/01-resman/organization.tf +++ b/fast/stages/1-resman/organization.tf @@ -16,7 +16,6 @@ # tfdoc:file:description Organization policies. - locals { all_drs_domains = concat( [var.organization.customer_id], @@ -47,7 +46,7 @@ module "organization" { module.branch-network-sa.iam_email ] }, - local.billing_org ? { + var.billing_account.is_org_level ? { "roles/billing.costsManager" = concat( local.branch_optional_sa_lists.pf-dev, local.branch_optional_sa_lists.pf-prod @@ -85,6 +84,9 @@ module "organization" { } org_policies_data_path = "${var.data_dir}/org-policies" + # do not assign tagViewer or tagUser roles here on tag keys and values as + # they are managed authoritatively and will break multitenant stages + tags = { (var.tag_names.context) = { description = "Resource management context." @@ -106,45 +108,10 @@ module "organization" { production = null } } + (var.tag_names.tenant) = { + description = "Organization tenant." + } } } -# organization policy admin role assigned with a condition on tags - -resource "google_organization_iam_member" "org_policy_admin_dp" { - for_each = !var.fast_features.data_platform ? {} : { - data-dev = ["data", "development", module.branch-dp-dev-sa.0.iam_email] - data-prod = ["data", "production", module.branch-dp-prod-sa.0.iam_email] - } - org_id = var.organization.id - role = "roles/orgpolicy.policyAdmin" - member = each.value.2 - condition { - title = "org_policy_tag_dp_scoped" - description = "Org policy tag scoped grant for ${each.value.0}/${each.value.1}." - expression = <<-END - resource.matchTag('${var.organization.id}/${var.tag_names.context}', '${each.value.0}') - && - resource.matchTag('${var.organization.id}/${var.tag_names.environment}', '${each.value.1}') - END - } -} - -resource "google_organization_iam_member" "org_policy_admin_pf" { - for_each = !var.fast_features.project_factory ? {} : { - pf-dev = ["teams", "development", module.branch-pf-dev-sa.0.iam_email] - pf-prod = ["teams", "production", module.branch-pf-prod-sa.0.iam_email] - } - org_id = var.organization.id - role = "roles/orgpolicy.policyAdmin" - member = each.value.2 - condition { - title = "org_policy_tag_pf_scoped" - description = "Org policy tag scoped grant for ${each.value.0}/${each.value.1}." - expression = <<-END - resource.matchTag('${var.organization.id}/${var.tag_names.context}', '${each.value.0}') - && - resource.matchTag('${var.organization.id}/${var.tag_names.environment}', '${each.value.1}') - END - } -} +# organization policy conditional roles are in the relevant branch files diff --git a/fast/stages/01-resman/outputs-files.tf b/fast/stages/1-resman/outputs-files.tf similarity index 94% rename from fast/stages/01-resman/outputs-files.tf rename to fast/stages/1-resman/outputs-files.tf index bd281d45..f7f080dd 100644 --- a/fast/stages/01-resman/outputs-files.tf +++ b/fast/stages/1-resman/outputs-files.tf @@ -30,7 +30,7 @@ resource "local_file" "providers" { resource "local_file" "tfvars" { for_each = var.outputs_location == null ? {} : { 1 = 1 } file_permission = "0644" - filename = "${local.outputs_location}/tfvars/01-resman.auto.tfvars.json" + filename = "${local.outputs_location}/tfvars/1-resman.auto.tfvars.json" content = jsonencode(local.tfvars) } diff --git a/fast/stages/01-resman/outputs-gcs.tf b/fast/stages/1-resman/outputs-gcs.tf similarity index 96% rename from fast/stages/01-resman/outputs-gcs.tf rename to fast/stages/1-resman/outputs-gcs.tf index f1db11ef..5b9f5d85 100644 --- a/fast/stages/01-resman/outputs-gcs.tf +++ b/fast/stages/1-resman/outputs-gcs.tf @@ -25,7 +25,7 @@ resource "google_storage_bucket_object" "providers" { resource "google_storage_bucket_object" "tfvars" { bucket = var.automation.outputs_bucket - name = "tfvars/01-resman.auto.tfvars.json" + name = "tfvars/1-resman.auto.tfvars.json" content = jsonencode(local.tfvars) } diff --git a/fast/stages/01-resman/outputs.tf b/fast/stages/1-resman/outputs.tf similarity index 72% rename from fast/stages/01-resman/outputs.tf rename to fast/stages/1-resman/outputs.tf index e07df577..80ec2b9d 100644 --- a/fast/stages/01-resman/outputs.tf +++ b/fast/stages/1-resman/outputs.tf @@ -19,42 +19,42 @@ locals { cicd_workflow_attrs = { data_platform_dev = { service_account = try(module.branch-dp-dev-sa-cicd.0.email, null) - tf_providers_file = "03-data-platform-dev-providers.tf" + tf_providers_file = "3-data-platform-dev-providers.tf" tf_var_files = local.cicd_workflow_var_files.stage_3 } data_platform_prod = { service_account = try(module.branch-dp-prod-sa-cicd.0.email, null) - tf_providers_file = "03-data-platform-prod-providers.tf" + tf_providers_file = "3-data-platform-prod-providers.tf" tf_var_files = local.cicd_workflow_var_files.stage_3 } gke_dev = { service_account = try(module.branch-gke-dev-sa-cicd.0.email, null) - tf_providers_file = "03-gke-dev-providers.tf" + tf_providers_file = "3-gke-dev-providers.tf" tf_var_files = local.cicd_workflow_var_files.stage_3 } gke_prod = { service_account = try(module.branch-gke-prod-sa-cicd.0.email, null) - tf_providers_file = "03-gke-prod-providers.tf" + tf_providers_file = "3-gke-prod-providers.tf" tf_var_files = local.cicd_workflow_var_files.stage_3 } networking = { service_account = try(module.branch-network-sa-cicd.0.email, null) - tf_providers_file = "02-networking-providers.tf" + tf_providers_file = "2-networking-providers.tf" tf_var_files = local.cicd_workflow_var_files.stage_2 } project_factory_dev = { service_account = try(module.branch-pf-dev-sa-cicd.0.email, null) - tf_providers_file = "03-project-factory-dev-providers.tf" + tf_providers_file = "3-project-factory-dev-providers.tf" tf_var_files = local.cicd_workflow_var_files.stage_3 } project_factory_prod = { service_account = try(module.branch-pf-prod-sa-cicd.0.email, null) - tf_providers_file = "03-project-factory-prod-providers.tf" + tf_providers_file = "3-project-factory-prod-providers.tf" tf_var_files = local.cicd_workflow_var_files.stage_3 } security = { service_account = try(module.branch-security-sa-cicd.0.email, null) - tf_providers_file = "02-security-providers.tf" + tf_providers_file = "2-security-providers.tf" tf_var_files = local.cicd_workflow_var_files.stage_2 } } @@ -98,74 +98,85 @@ locals { ) providers = merge( { - "02-networking" = templatefile(local._tpl_providers, { - bucket = module.branch-network-gcs.name - name = "networking" - sa = module.branch-network-sa.email + "2-networking" = templatefile(local._tpl_providers, { + backend_extra = null + bucket = module.branch-network-gcs.name + name = "networking" + sa = module.branch-network-sa.email }) - "02-security" = templatefile(local._tpl_providers, { - bucket = module.branch-security-gcs.name - name = "security" - sa = module.branch-security-sa.email + "2-security" = templatefile(local._tpl_providers, { + backend_extra = null + bucket = module.branch-security-gcs.name + name = "security" + sa = module.branch-security-sa.email }) }, !var.fast_features.data_platform ? {} : { - "03-data-platform-dev" = templatefile(local._tpl_providers, { - bucket = module.branch-dp-dev-gcs.0.name - name = "dp-dev" - sa = module.branch-dp-dev-sa.0.email + "3-data-platform-dev" = templatefile(local._tpl_providers, { + backend_extra = null + bucket = module.branch-dp-dev-gcs.0.name + name = "dp-dev" + sa = module.branch-dp-dev-sa.0.email }) - "03-data-platform-prod" = templatefile(local._tpl_providers, { - bucket = module.branch-dp-prod-gcs.0.name - name = "dp-prod" - sa = module.branch-dp-prod-sa.0.email + "3-data-platform-prod" = templatefile(local._tpl_providers, { + backend_extra = null + bucket = module.branch-dp-prod-gcs.0.name + name = "dp-prod" + sa = module.branch-dp-prod-sa.0.email }) }, !var.fast_features.gke ? {} : { - "03-gke-dev" = templatefile(local._tpl_providers, { - bucket = module.branch-gke-dev-gcs.0.name - name = "gke-dev" - sa = module.branch-gke-dev-sa.0.email + "3-gke-dev" = templatefile(local._tpl_providers, { + backend_extra = null + bucket = module.branch-gke-dev-gcs.0.name + name = "gke-dev" + sa = module.branch-gke-dev-sa.0.email }) - "03-gke-prod" = templatefile(local._tpl_providers, { - bucket = module.branch-gke-prod-gcs.0.name - name = "gke-prod" - sa = module.branch-gke-prod-sa.0.email + "3-gke-prod" = templatefile(local._tpl_providers, { + backend_extra = null + bucket = module.branch-gke-prod-gcs.0.name + name = "gke-prod" + sa = module.branch-gke-prod-sa.0.email }) }, !var.fast_features.project_factory ? {} : { - "03-project-factory-dev" = templatefile(local._tpl_providers, { - bucket = module.branch-pf-dev-gcs.0.name - name = "team-dev" - sa = module.branch-pf-dev-sa.0.email + "3-project-factory-dev" = templatefile(local._tpl_providers, { + backend_extra = null + bucket = module.branch-pf-dev-gcs.0.name + name = "team-dev" + sa = module.branch-pf-dev-sa.0.email }) - "03-project-factory-prod" = templatefile(local._tpl_providers, { - bucket = module.branch-pf-prod-gcs.0.name - name = "team-prod" - sa = module.branch-pf-prod-sa.0.email + "3-project-factory-prod" = templatefile(local._tpl_providers, { + backend_extra = null + bucket = module.branch-pf-prod-gcs.0.name + name = "team-prod" + sa = module.branch-pf-prod-sa.0.email }) }, !var.fast_features.sandbox ? {} : { - "99-sandbox" = templatefile(local._tpl_providers, { - bucket = module.branch-sandbox-gcs.0.name - name = "sandbox" - sa = module.branch-sandbox-sa.0.email + "9-sandbox" = templatefile(local._tpl_providers, { + backend_extra = null + bucket = module.branch-sandbox-gcs.0.name + name = "sandbox" + sa = module.branch-sandbox-sa.0.email }) }, !var.fast_features.teams ? {} : merge( { - "03-teams" = templatefile(local._tpl_providers, { - bucket = module.branch-teams-gcs.0.name - name = "teams" - sa = module.branch-teams-sa.0.email + "3-teams" = templatefile(local._tpl_providers, { + backend_extra = null + bucket = module.branch-teams-gcs.0.name + name = "teams" + sa = module.branch-teams-sa.0.email }) }, { for k, v in module.branch-teams-team-sa : - "03-teams-${k}" => templatefile(local._tpl_providers, { - bucket = module.branch-teams-team-gcs[k].name - name = "teams" - sa = v.email + "3-teams-${k}" => templatefile(local._tpl_providers, { + backend_extra = null + bucket = module.branch-teams-team-gcs[k].name + name = "teams" + sa = v.email }) } ) @@ -190,7 +201,9 @@ locals { tfvars = { folder_ids = local.folder_ids service_accounts = local.service_accounts + tag_keys = { for k, v in module.organization.tag_keys : k => v.id } tag_names = var.tag_names + tag_values = { for k, v in module.organization.tag_values : k => v.id } } } diff --git a/tests/blueprints/factories/bigquery_factory/fixture/variables.tf b/fast/stages/1-resman/templates/providers.tf.tpl similarity index 61% rename from tests/blueprints/factories/bigquery_factory/fixture/variables.tf rename to fast/stages/1-resman/templates/providers.tf.tpl index 8269dbbe..d1c224c5 100644 --- a/tests/blueprints/factories/bigquery_factory/fixture/variables.tf +++ b/fast/stages/1-resman/templates/providers.tf.tpl @@ -14,21 +14,20 @@ * limitations under the License. */ -variable "views_dir" { - description = "Relative path for the folder storing view data." - type = string - default = "/views" +terraform { + backend "gcs" { + bucket = "${bucket}" + impersonate_service_account = "${sa}" + %{~ if backend_extra != null ~} + ${indent(4, backend_extra)} + %{~ endif ~} + } +} +provider "google" { + impersonate_service_account = "${sa}" +} +provider "google-beta" { + impersonate_service_account = "${sa}" } -variable "tables_dir" { - description = "Relative path for the folder storing table data." - type = string - default = "tables" -} - -variable "project_id" { - description = "Project ID" - type = string - default = "test-project" - -} +# end provider.tf for ${name} diff --git a/fast/stages/1-resman/templates/workflow-github.yaml b/fast/stages/1-resman/templates/workflow-github.yaml new file mode 100644 index 00000000..41c51411 --- /dev/null +++ b/fast/stages/1-resman/templates/workflow-github.yaml @@ -0,0 +1,198 @@ +# 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 +# +# 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: "FAST ${stage_name} stage" + +on: + pull_request: + branches: + - main + types: + - closed + - opened + - synchronize + +env: + FAST_OUTPUTS_BUCKET: ${outputs_bucket} + FAST_SERVICE_ACCOUNT: ${service_account} + FAST_WIF_PROVIDER: ${identity_provider} + SSH_AUTH_SOCK: /tmp/ssh_agent.sock + TF_PROVIDERS_FILE: ${tf_providers_file} + TF_VAR_FILES: ${tf_var_files == [] ? "''" : join("\n ", tf_var_files)} + TF_VERSION: 1.3.2 + +jobs: + fast-pr: + permissions: + contents: read + id-token: write + issues: write + pull-requests: write + runs-on: ubuntu-latest + steps: + - id: checkout + name: Checkout repository + uses: actions/checkout@v3 + + # set up SSH key authentication to the modules repository + - id: ssh-config + name: Configure SSH authentication + run: | + ssh-agent -a "$SSH_AUTH_SOCK" > /dev/null + ssh-add - <<< "$${{ secrets.CICD_MODULES_KEY }}" + + # set up authentication via Workload identity Federation + - id: gcp-auth + name: Authenticate to Google Cloud + uses: google-github-actions/auth@v0 + with: + workload_identity_provider: $${{ env.FAST_WIF_PROVIDER }} + service_account: $${{ env.FAST_SERVICE_ACCOUNT }} + access_token_lifetime: 3600s + + - id: gcp-sdk + name: Set up Cloud SDK + uses: google-github-actions/setup-gcloud@v0 + with: + install_components: alpha + + # copy provider and tfvars files + - id: tf-config + name: Copy Terraform output files + run: | + gcloud alpha storage cp -r \ + "gs://$${{env.FAST_OUTPUTS_BUCKET}}/providers/$${{env.TF_PROVIDERS_FILE}}" ./ + gcloud alpha storage cp -r \ + "gs://$${{env.FAST_OUTPUTS_BUCKET}}/tfvars" ./ + for f in $${{env.TF_VAR_FILES}}; do + ln -s "tfvars/$f" ./ + done + + - id: tf-setup + name: Set up Terraform + uses: hashicorp/setup-terraform@v2.0.3 + with: + terraform_version: $${{ env.TF_VERSION }} + + # run Terraform init/validate/plan + - id: tf-init + name: Terraform init + continue-on-error: true + run: | + terraform init -no-color + + - id: tf-validate + continue-on-error: true + name: Terraform validate + run: terraform validate -no-color + + - id: tf-plan + name: Terraform plan + continue-on-error: true + run: | + terraform plan -input=false -out ../plan.out -no-color + + - id: tf-apply + if: github.event.pull_request.merged == true && success() + name: Terraform apply + continue-on-error: true + run: | + terraform apply -input=false -auto-approve -no-color ../plan.out + + - id: pr-comment + name: Post comment to Pull Request + continue-on-error: true + uses: actions/github-script@v6 + if: github.event_name == 'pull_request' + env: + PLAN: $${{ steps.tf-plan.outputs.stdout }}\n$${{ steps.tf-plan.outputs.stderr }} + with: + script: | + const output = `### Terraform Initialization \`$${{ steps.tf-init.outcome }}\` + + ### Terraform Validation \`$${{ steps.tf-validate.outcome }}\` + +
Validation Output + + \`\`\`\n + $${{ steps.tf-validate.outputs.stdout }} + \`\`\` + +
+ + ### Terraform Plan \`$${{ steps.tf-plan.outcome }}\` + +
Show Plan + + \`\`\`\n + $${process.env.PLAN.split('\n').filter(l => l.match(/^([A-Z\s].*|)$$/)).join('\n')} + \`\`\` + +
+ + ### Terraform Apply \`$${{ steps.tf-apply.outcome }}\` + + *Pusher: @$${{ github.actor }}, Action: \`$${{ github.event_name }}\`, Working Directory: \`$${{ env.tf_actions_working_dir }}\`, Workflow: \`$${{ github.workflow }}\`*`; + + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: output + }) + + - id: pr-short-comment + name: Post comment to Pull Request + uses: actions/github-script@v6 + if: github.event_name == 'pull_request' && steps.pr-comment.outcome != 'success' + with: + script: | + const output = `### Terraform Initialization \`$${{ steps.tf-init.outcome }}\` + + ### Terraform Validation \`$${{ steps.tf-validate.outcome }}\` + + ### Terraform Plan \`$${{ steps.tf-plan.outcome }}\` + + Plan output is in the action log. + + ### Terraform Apply \`$${{ steps.tf-apply.outcome }}\` + + *Pusher: @$${{ github.actor }}, Action: \`$${{ github.event_name }}\`, Working Directory: \`$${{ env.tf_actions_working_dir }}\`, Workflow: \`$${{ github.workflow }}\`*`; + + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: output + }) + + - id: check-init + name: Check init failure + if: steps.tf-init.outcome != 'success' + run: exit 1 + + - id: check-validate + name: Check validate failure + if: steps.tf-validate.outcome != 'success' + run: exit 1 + + - id: check-plan + name: Check plan failure + if: steps.tf-plan.outcome != 'success' + run: exit 1 + + - id: check-apply + name: Check apply failure + if: github.event.pull_request.merged == true && steps.tf-apply.outcome != 'success' + run: exit 1 diff --git a/fast/stages/1-resman/templates/workflow-gitlab.yaml b/fast/stages/1-resman/templates/workflow-gitlab.yaml new file mode 100644 index 00000000..8981e70b --- /dev/null +++ b/fast/stages/1-resman/templates/workflow-gitlab.yaml @@ -0,0 +1,120 @@ +# 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 +# +# 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. + +default: + before_script: + - echo "$${CI_JOB_JWT_V2}" > token.txt + image: + name: hashicorp/terraform + entrypoint: + - "/usr/bin/env" + - "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + +variables: + GOOGLE_CREDENTIALS: cicd-sa-credentials.json + FAST_OUTPUTS_BUCKET: ${outputs_bucket} + FAST_SERVICE_ACCOUNT: ${service_account} + FAST_WIF_PROVIDER: ${identity_provider} + SSH_AUTH_SOCK: /tmp/ssh_agent.sock + TF_PROVIDERS_FILE: ${tf_providers_file} + TF_VAR_FILES: ${tf_var_files == [] ? "''" : join("\n ", tf_var_files)} + +stages: + - gcp-auth + - tf-files + - tf-plan + - tf-apply + +cache: + key: gcp-auth + paths: + - cicd-sa-credentials.json + - .tf-setup + +gcp-auth: + image: + name: google/cloud-sdk:slim + stage: gcp-auth + script: + - | + gcloud iam workload-identity-pools create-cred-config \ + $${FAST_WIF_PROVIDER} \ + --service-account=$${FAST_SERVICE_ACCOUNT} \ + --service-account-token-lifetime-seconds=3600 \ + --output-file=$${GOOGLE_CREDENTIALS} \ + --credential-source-file=token.txt +tf-files: + dependencies: + - gcp-auth + image: + name: google/cloud-sdk:slim + stage: tf-files + script: + # - gcloud components install -q alpha + - gcloud config set auth/credential_file_override $${GOOGLE_CREDENTIALS} + - mkdir -p .tf-setup + - | + gcloud alpha storage cp -r \ + "gs://$${FAST_OUTPUTS_BUCKET}/providers/$${TF_PROVIDERS_FILE}" .tf-setup/ + - | + gcloud alpha storage cp -r \ + "gs://$${FAST_OUTPUTS_BUCKET}/tfvars" .tf-setup/ + +tf-plan: + # uncomment the following lines and set the SSH key secret for private modules repo + # before_script: + # - | + # ssh-agent -a $SSH_AUTH_SOCK > /dev/null + # echo "$CICD_MODULES_KEY" | base64 -d | tr -d '\r' | ssh-add - > /dev/null + # mkdir -p ~/.ssh + # ssh-keyscan -H 'gitlab.com' >> ~/.ssh/known_hosts + # ssh-keyscan gitlab.com | sort -u - ~/.ssh/known_hosts -o ~/.ssh/known_hosts + stage: tf-plan + script: + - cp .tf-setup/$${TF_PROVIDERS_FILE} ./ + - | + for f in $${TF_VAR_FILES}; do + ln -s ".tf-setup/tfvars/$f" ./ + done + - terraform init + - terraform validate + - terraform plan + dependencies: + - tf-files + +tf-apply: + # uncomment the following lines and set the SSH key secret for private modules repo + # before_script: + # - | + # ssh-agent -a $SSH_AUTH_SOCK > /dev/null + # echo "$CICD_MODULES_KEY" | base64 -d | tr -d '\r' | ssh-add - > /dev/null + # mkdir -p ~/.ssh + # ssh-keyscan -H 'gitlab.com' >> ~/.ssh/known_hosts + # ssh-keyscan gitlab.com | sort -u - ~/.ssh/known_hosts -o ~/.ssh/known_hosts + stage: tf-apply + script: + - cp .tf-setup/$${TF_PROVIDERS_FILE} ./ + - | + for f in $${TF_VAR_FILES}; do + ln -s ".tf-setup/tfvars/$f" ./ + done + - terraform init + - terraform validate + - terraform apply -input=false -auto-approve + dependencies: + - tf-files + when: manual + only: + variables: + - $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH diff --git a/fast/stages/1-resman/templates/workflow-sourcerepo.yaml b/fast/stages/1-resman/templates/workflow-sourcerepo.yaml new file mode 100644 index 00000000..446c9c96 --- /dev/null +++ b/fast/stages/1-resman/templates/workflow-sourcerepo.yaml @@ -0,0 +1,98 @@ +# 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 +# +# 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. + +steps: + - name: alpine:3 + id: tf-download + entrypoint: sh + args: + - -eEuo + - pipefail + - -c + - |- + mkdir -p /builder/home/.local/bin + wget https://releases.hashicorp.com/terraform/$${_TF_VERSION}/terraform_$${_TF_VERSION}_linux_amd64.zip + unzip terraform_$${_TF_VERSION}_linux_amd64.zip -d /builder/home/.local/bin + rm terraform_$${_TF_VERSION}_linux_amd64.zip + chmod 755 /builder/home/.local/bin/terraform + - name: alpine:3 + id: tf-check-format + entrypoint: sh + args: + - -eEuo + - pipefail + - -c + - |- + terraform fmt -recursive -check /workspace/ + - name: gcr.io/google.com/cloudsdktool/cloud-sdk:alpine + id: tf-files + entrypoint: bash + args: + - -eEuo + - pipefail + - -c + - |- + /google-cloud-sdk/bin/gsutil cp \ + gs://$${_FAST_OUTPUTS_BUCKET}/providers/$${_TF_PROVIDERS_FILE} ./ + /google-cloud-sdk/bin/gsutil cp -r \ + gs://$${_FAST_OUTPUTS_BUCKET}/tfvars ./ + for f in $${_TF_VAR_FILES}; do + ln -s tfvars/$f ./ + done + - name: alpine:3 + id: tf-init + entrypoint: sh + args: + - -eEuo + - pipefail + - -c + - |- + terraform init -no-color + - name: alpine:3 + id: tf-check-validate + entrypoint: sh + args: + - -eEuo + - pipefail + - -c + - |- + terraform validate -no-color + - name: alpine:3 + id: tf-plan + entrypoint: sh + args: + - -eEuo + - pipefail + - -c + - |- + terraform plan -no-color -input=false -out plan.out + # store artifact and ask for approval here if needed + - name: alpine:3 + id: tf-apply + entrypoint: sh + args: + - -eEuo + - pipefail + - -c + - |- + terraform apply -no-color -input=false -auto-approve plan.out +options: + env: + - PATH=/usr/local/bin:/usr/bin:/bin:/builder/home/.local/bin + logging: CLOUD_LOGGING_ONLY +substitutions: + _FAST_OUTPUTS_BUCKET: ${outputs_bucket} + _TF_PROVIDERS_FILE: ${tf_providers_file} + _TF_VAR_FILES: ${tf_var_files == [] ? "''" : join("\n ", tf_var_files)} + _TF_VERSION: 1.3.2 diff --git a/fast/stages/01-resman/variables.tf b/fast/stages/1-resman/variables.tf similarity index 79% rename from fast/stages/01-resman/variables.tf rename to fast/stages/1-resman/variables.tf index 8b6f866b..16c65ee8 100644 --- a/fast/stages/01-resman/variables.tf +++ b/fast/stages/1-resman/variables.tf @@ -18,7 +18,7 @@ # the tfvars file generated in stage 00 and stored in its outputs variable "automation" { - # tfdoc:variable:source 00-bootstrap + # tfdoc:variable:source 0-bootstrap description = "Automation resources created by the bootstrap stage." type = object({ outputs_bucket = string @@ -36,65 +36,69 @@ variable "automation" { } variable "billing_account" { - # tfdoc:variable:source 00-bootstrap - description = "Billing account id and organization id ('nnnnnnnn' or null)." + # 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." type = object({ - id = string - organization_id = number + id = string + is_org_level = optional(bool, true) }) + validation { + condition = var.billing_account.is_org_level != null + error_message = "Invalid `null` value for `billing_account.is_org_level`." + } } variable "cicd_repositories" { description = "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." type = object({ - data_platform_dev = object({ + data_platform_dev = optional(object({ branch = string identity_provider = string name = string type = string - }) - data_platform_prod = object({ + })) + data_platform_prod = optional(object({ branch = string identity_provider = string name = string type = string - }) - gke_dev = object({ + })) + gke_dev = optional(object({ branch = string identity_provider = string name = string type = string - }) - gke_prod = object({ + })) + gke_prod = optional(object({ branch = string identity_provider = string name = string type = string - }) - networking = object({ + })) + networking = optional(object({ branch = string identity_provider = string name = string type = string - }) - project_factory_dev = object({ + })) + project_factory_dev = optional(object({ branch = string identity_provider = string name = string type = string - }) - project_factory_prod = object({ + })) + project_factory_prod = optional(object({ branch = string identity_provider = string name = string type = string - }) - security = object({ + })) + security = optional(object({ branch = string identity_provider = string name = string type = string - }) + })) }) default = null validation { @@ -127,7 +131,7 @@ variable "cicd_repositories" { } variable "custom_roles" { - # tfdoc:variable:source 00-bootstrap + # tfdoc:variable:source 0-bootstrap description = "Custom roles defined at the org level, in key => id format." type = object({ service_project_network_admin = string @@ -142,42 +146,34 @@ variable "data_dir" { } variable "fast_features" { - # tfdoc:variable:source 00-bootstrap + # tfdoc:variable:source 0-0-bootstrap description = "Selective control for top-level FAST features." type = object({ - data_platform = bool - gke = bool - project_factory = bool - sandbox = bool - teams = bool + data_platform = optional(bool, false) + gke = optional(bool, false) + project_factory = optional(bool, false) + sandbox = optional(bool, false) + teams = optional(bool, false) }) - default = { - data_platform = true - gke = true - project_factory = true - sandbox = true - teams = true - } - # nullable = false + default = {} + nullable = false } variable "groups" { - # tfdoc:variable:source 00-bootstrap - description = "Group names to grant organization-level permissions." - type = map(string) + # tfdoc:variable:source 0-bootstrap # https://cloud.google.com/docs/enterprise/setup-checklist - default = { - gcp-billing-admins = "gcp-billing-admins", - gcp-devops = "gcp-devops", - gcp-network-admins = "gcp-network-admins" - gcp-organization-admins = "gcp-organization-admins" - gcp-security-admins = "gcp-security-admins" - gcp-support = "gcp-support" - } + description = "Group names to grant organization-level permissions." + type = object({ + gcp-devops = optional(string) + gcp-network-admins = optional(string) + gcp-security-admins = optional(string) + }) + default = {} + nullable = false } variable "locations" { - # tfdoc:variable:source 00-bootstrap + # tfdoc:variable:source 0-bootstrap description = "Optional locations for GCS, BigQuery, and logging buckets created here." type = object({ bq = string @@ -195,7 +191,7 @@ variable "locations" { } variable "organization" { - # tfdoc:variable:source 00-bootstrap + # tfdoc:variable:source 0-bootstrap description = "Organization details." type = object({ domain = string @@ -219,7 +215,7 @@ variable "outputs_location" { } variable "prefix" { - # tfdoc:variable:source 00-bootstrap + # tfdoc:variable:source 0-bootstrap description = "Prefix used for resources that need unique names. Use 9 characters or less." type = string @@ -234,10 +230,12 @@ variable "tag_names" { type = object({ context = string environment = string + tenant = string }) default = { context = "context" environment = "environment" + tenant = "tenant" } nullable = false validation { diff --git a/fast/stages/02-networking-peering/.gitignore b/fast/stages/2-networking-a-peering/.gitignore similarity index 100% rename from fast/stages/02-networking-peering/.gitignore rename to fast/stages/2-networking-a-peering/.gitignore diff --git a/fast/stages/02-networking-peering/IAM.md b/fast/stages/2-networking-a-peering/IAM.md similarity index 100% rename from fast/stages/02-networking-peering/IAM.md rename to fast/stages/2-networking-a-peering/IAM.md diff --git a/fast/stages/02-networking-peering/README.md b/fast/stages/2-networking-a-peering/README.md similarity index 84% rename from fast/stages/02-networking-peering/README.md rename to fast/stages/2-networking-a-peering/README.md index c7829f0f..7966ce80 100644 --- a/fast/stages/02-networking-peering/README.md +++ b/fast/stages/2-networking-a-peering/README.md @@ -44,13 +44,13 @@ As mentioned initially, there are of course other ways to implement internal con This is a summary of the main options: -- [HA VPN](https://cloud.google.com/network-connectivity/docs/vpn/concepts/topologies) (implemented by [02-networking-vpn](../02-networking-vpn/)) +- [HA VPN](https://cloud.google.com/network-connectivity/docs/vpn/concepts/topologies) (implemented by [02-networking-vpn](../2-networking-b-vpn/)) - Pros: simple compatibility with GCP services that leverage peering internally, better control on routes, avoids peering groups shared quotas and limits - Cons: additional cost, marginal increase in latency, requires multiple tunnels for full bandwidth - [VPC Peering](https://cloud.google.com/vpc/docs/vpc-peering) (implemented here) - Pros: no additional costs, full bandwidth with no configurations, no extra latency, total environment isolation - Cons: no transitivity (e.g. to GKE masters, Cloud SQL, etc.), no selective exchange of routes, several quotas and limits shared between VPCs in a peering group -- [Multi-NIC appliances](https://cloud.google.com/architecture/best-practices-vpc-design#multi-nic) (implemented by [02-networking-nva](../02-networking-nva/)) +- [Multi-NIC appliances](https://cloud.google.com/architecture/best-practices-vpc-design#multi-nic) (implemented by [02-networking-nva](../2-networking-c-nva/)) - Pros: additional security features (e.g. IPS), potentially better integration with on-prem systems by using the same vendor - Cons: complex HA/failover setup, limited by VM bandwidth and scale, additional costs for VMs and licenses, out of band management of a critical cloud component @@ -122,7 +122,7 @@ This configuration is battle-tested, and flexible enough to lend itself to simpl ## How to run this stage -This stage is meant to be executed after the [resman](../01-resman) stage has run, as it leverages the automation service account and bucket created there, and additional resources configured in the [bootstrap](../00-bootstrap) stage. +This stage is meant to be executed after the [resman](../1-resman) stage has run, as it leverages the automation service account and bucket created there, and additional resources configured in the [bootstrap](../0-bootstrap) stage. It's of course possible to run this stage in isolation, but that's outside the scope of this document, and you would need to refer to the code for the previous stages for the environmental requirements. @@ -130,7 +130,7 @@ Before running this stage, you need to make sure you have the correct credential ### Providers configuration -The default way of making sure you have the right permissions, is to use the identity of the service account pre-created for this stage during the [resource management](../01-resman) stage, and that you are a member of the group that can impersonate it via provider-level configuration (`gcp-devops` or `organization-admins`). +The default way of making sure you have the right permissions, is to use the identity of the service account pre-created for this stage during the [resource management](../1-resman) stage, and that you are a member of the group that can impersonate it via provider-level configuration (`gcp-devops` or `organization-admins`). To simplify setup, the previous stage pre-configures a valid providers file in its output, and optionally writes it to a local file if the `outputs_location` variable is set to a valid path. @@ -144,7 +144,7 @@ ln -s ~/fast-config/providers/02-networking-providers.tf . If you have not configured `outputs_location` in bootstrap, you can derive the providers file from that stage's outputs: ```bash -cd ../01-resman +cd ../1-resman terraform output -json providers | jq -r '.["02-networking"]' \ > ../02-networking/providers.tf ``` @@ -284,6 +284,7 @@ DNS configurations are centralised in the `dns-*.tf` files. Spokes delegate DNS | [monitoring.tf](./monitoring.tf) | Network monitoring dashboards. | | google_monitoring_dashboard | | [outputs.tf](./outputs.tf) | Module outputs. | | google_storage_bucket_object · local_file | | [peerings.tf](./peerings.tf) | None | net-vpc-peering | | +| [regions.tf](./regions.tf) | Compute short names for regions. | | | | [spoke-dev.tf](./spoke-dev.tf) | Dev spoke VPC and related resources. | net-cloudnat · net-vpc · net-vpc-firewall · project | google_project_iam_binding | | [spoke-prod.tf](./spoke-prod.tf) | Production spoke VPC and related resources. | net-cloudnat · net-vpc · net-vpc-firewall · project | google_project_iam_binding | | [test-resources.tf](./test-resources.tf) | temporary instances for testing | compute-vm | | @@ -295,23 +296,23 @@ DNS configurations are centralised in the `dns-*.tf` files. Spokes delegate DNS | name | description | type | required | default | producer | |---|---|:---:|:---:|:---:|:---:| -| [automation](variables.tf#L17) | Automation resources created by the bootstrap stage. | object({…}) | ✓ | | 00-bootstrap | -| [billing_account](variables.tf#L25) | Billing account id and organization id ('nnnnnnnn' or null). | object({…}) | ✓ | | 00-bootstrap | -| [folder_ids](variables.tf#L74) | Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created. | object({…}) | ✓ | | 01-resman | -| [organization](variables.tf#L102) | Organization details. | object({…}) | ✓ | | 00-bootstrap | -| [prefix](variables.tf#L118) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 00-bootstrap | -| [custom_adv](variables.tf#L34) | Custom advertisement definitions in name => range format. | map(string) | | {…} | | -| [custom_roles](variables.tf#L51) | Custom roles defined at the org level, in key => id format. | object({…}) | | null | 00-bootstrap | -| [data_dir](variables.tf#L60) | Relative path for the folder storing configuration data for network resources. | string | | "data" | | -| [dns](variables.tf#L66) | Onprem DNS resolvers. | map(list(string)) | | {…} | | -| [l7ilb_subnets](variables.tf#L84) | Subnets used for L7 ILBs. | map(list(object({…}))) | | {…} | | -| [outputs_location](variables.tf#L112) | Path where providers and tfvars files for the following stages are written. Leave empty to disable. | string | | null | | +| [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 | +| [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 | | | [peering_configs](variables-peerings.tf#L19) | Peering configurations. | map(object({…})) | | {…} | | -| [psa_ranges](variables.tf#L129) | IP ranges used for Private Service Access (e.g. CloudSQL). | object({…}) | | null | | -| [region_trigram](variables.tf#L166) | Short names for GCP regions. | map(string) | | {…} | | -| [router_onprem_configs](variables.tf#L175) | Configurations for routers used for onprem connectivity. | map(object({…})) | | {…} | | -| [service_accounts](variables.tf#L193) | Automation service accounts in name => email format. | object({…}) | | null | 01-resman | -| [vpn_onprem_configs](variables.tf#L207) | VPN gateway configuration for onprem interconnection. | 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({…})) | | {…} | | ## Outputs diff --git a/fast/stages/02-networking-nva/data/cidrs.yaml b/fast/stages/2-networking-a-peering/data/cidrs.yaml similarity index 100% rename from fast/stages/02-networking-nva/data/cidrs.yaml rename to fast/stages/2-networking-a-peering/data/cidrs.yaml diff --git a/fast/stages/02-networking-peering/data/dashboards/firewall_insights.json b/fast/stages/2-networking-a-peering/data/dashboards/firewall_insights.json similarity index 100% rename from fast/stages/02-networking-peering/data/dashboards/firewall_insights.json rename to fast/stages/2-networking-a-peering/data/dashboards/firewall_insights.json diff --git a/fast/stages/02-networking-peering/data/dashboards/vpn.json b/fast/stages/2-networking-a-peering/data/dashboards/vpn.json similarity index 100% rename from fast/stages/02-networking-peering/data/dashboards/vpn.json rename to fast/stages/2-networking-a-peering/data/dashboards/vpn.json diff --git a/fast/stages/02-networking-nva/data/firewall-rules/dev/rules.yaml b/fast/stages/2-networking-a-peering/data/firewall-rules/dev/rules.yaml similarity index 100% rename from fast/stages/02-networking-nva/data/firewall-rules/dev/rules.yaml rename to fast/stages/2-networking-a-peering/data/firewall-rules/dev/rules.yaml diff --git a/fast/stages/02-networking-peering/data/firewall-rules/landing/rules.yaml b/fast/stages/2-networking-a-peering/data/firewall-rules/landing/rules.yaml similarity index 100% rename from fast/stages/02-networking-peering/data/firewall-rules/landing/rules.yaml rename to fast/stages/2-networking-a-peering/data/firewall-rules/landing/rules.yaml diff --git a/fast/stages/02-networking-nva/data/hierarchical-policy-rules.yaml b/fast/stages/2-networking-a-peering/data/hierarchical-policy-rules.yaml similarity index 100% rename from fast/stages/02-networking-nva/data/hierarchical-policy-rules.yaml rename to fast/stages/2-networking-a-peering/data/hierarchical-policy-rules.yaml diff --git a/fast/stages/02-networking-peering/data/subnets/dev/dev-dataplatform-ew1.yaml b/fast/stages/2-networking-a-peering/data/subnets/dev/dev-dataplatform-ew1.yaml similarity index 100% rename from fast/stages/02-networking-peering/data/subnets/dev/dev-dataplatform-ew1.yaml rename to fast/stages/2-networking-a-peering/data/subnets/dev/dev-dataplatform-ew1.yaml diff --git a/fast/stages/02-networking-peering/data/subnets/dev/dev-default-ew1.yaml b/fast/stages/2-networking-a-peering/data/subnets/dev/dev-default-ew1.yaml similarity index 100% rename from fast/stages/02-networking-peering/data/subnets/dev/dev-default-ew1.yaml rename to fast/stages/2-networking-a-peering/data/subnets/dev/dev-default-ew1.yaml diff --git a/fast/stages/02-networking-peering/data/subnets/dev/dev-gke-nodes-ew1.yaml b/fast/stages/2-networking-a-peering/data/subnets/dev/dev-gke-nodes-ew1.yaml similarity index 100% rename from fast/stages/02-networking-peering/data/subnets/dev/dev-gke-nodes-ew1.yaml rename to fast/stages/2-networking-a-peering/data/subnets/dev/dev-gke-nodes-ew1.yaml diff --git a/fast/stages/02-networking-peering/data/subnets/landing/landing-default-ew1.yaml b/fast/stages/2-networking-a-peering/data/subnets/landing/landing-default-ew1.yaml similarity index 100% rename from fast/stages/02-networking-peering/data/subnets/landing/landing-default-ew1.yaml rename to fast/stages/2-networking-a-peering/data/subnets/landing/landing-default-ew1.yaml diff --git a/fast/stages/02-networking-peering/data/subnets/prod/prod-default-ew1.yaml b/fast/stages/2-networking-a-peering/data/subnets/prod/prod-default-ew1.yaml similarity index 100% rename from fast/stages/02-networking-peering/data/subnets/prod/prod-default-ew1.yaml rename to fast/stages/2-networking-a-peering/data/subnets/prod/prod-default-ew1.yaml diff --git a/fast/stages/02-networking-peering/diagram.png b/fast/stages/2-networking-a-peering/diagram.png similarity index 100% rename from fast/stages/02-networking-peering/diagram.png rename to fast/stages/2-networking-a-peering/diagram.png diff --git a/fast/stages/02-networking-peering/diagram.svg b/fast/stages/2-networking-a-peering/diagram.svg similarity index 100% rename from fast/stages/02-networking-peering/diagram.svg rename to fast/stages/2-networking-a-peering/diagram.svg diff --git a/fast/stages/02-networking-peering/dns-dev.tf b/fast/stages/2-networking-a-peering/dns-dev.tf similarity index 100% rename from fast/stages/02-networking-peering/dns-dev.tf rename to fast/stages/2-networking-a-peering/dns-dev.tf diff --git a/fast/stages/02-networking-peering/dns-landing.tf b/fast/stages/2-networking-a-peering/dns-landing.tf similarity index 100% rename from fast/stages/02-networking-peering/dns-landing.tf rename to fast/stages/2-networking-a-peering/dns-landing.tf diff --git a/fast/stages/02-networking-peering/dns-prod.tf b/fast/stages/2-networking-a-peering/dns-prod.tf similarity index 100% rename from fast/stages/02-networking-peering/dns-prod.tf rename to fast/stages/2-networking-a-peering/dns-prod.tf diff --git a/fast/stages/02-networking-peering/landing.tf b/fast/stages/2-networking-a-peering/landing.tf similarity index 82% rename from fast/stages/02-networking-peering/landing.tf rename to fast/stages/2-networking-a-peering/landing.tf index 83a0d509..37e3adfd 100644 --- a/fast/stages/02-networking-peering/landing.tf +++ b/fast/stages/2-networking-a-peering/landing.tf @@ -63,7 +63,7 @@ module "landing-vpc" { next_hop = "default-internet-gateway" } } - data_folder = "${var.data_dir}/subnets/landing" + data_folder = "${var.factories_config.data_dir}/subnets/landing" } module "landing-firewall" { @@ -74,18 +74,23 @@ module "landing-firewall" { disabled = true } factories_config = { - cidr_tpl_file = "${var.data_dir}/cidrs.yaml" - rules_folder = "${var.data_dir}/firewall-rules/landing" + cidr_tpl_file = "${var.factories_config.data_dir}/cidrs.yaml" + rules_folder = "${var.factories_config.data_dir}/firewall-rules/landing" } } -module "landing-nat-ew1" { +moved { + from = module.landing-nat-ew1 + to = module.landing-nat-primary +} + +module "landing-nat-primary" { source = "../../../modules/net-cloudnat" project_id = module.landing-project.project_id - region = "europe-west1" - name = "ew1" + region = var.regions.primary + name = local.region_shortnames[var.regions.primary] router_create = true - router_name = "prod-nat-ew1" + router_name = "prod-nat-${local.region_shortnames[var.regions.primary]}" router_network = module.landing-vpc.name router_asn = 4200001024 } diff --git a/fast/stages/02-networking-vpn/main.tf b/fast/stages/2-networking-a-peering/main.tf similarity index 76% rename from fast/stages/02-networking-vpn/main.tf rename to fast/stages/2-networking-a-peering/main.tf index f68d39eb..5888752d 100644 --- a/fast/stages/02-networking-vpn/main.tf +++ b/fast/stages/2-networking-a-peering/main.tf @@ -17,14 +17,14 @@ # tfdoc:file:description Networking folder and hierarchical policy. locals { + # combine all regions from variables and subnets + regions = distinct(concat( + values(var.regions), + values(module.dev-spoke-vpc.subnet_regions), + values(module.landing-vpc.subnet_regions), + values(module.prod-spoke-vpc.subnet_regions), + )) 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}" - })] - } stage3_sas_delegated_grants = [ "roles/composer.sharedVpcAgent", "roles/compute.networkUser", @@ -46,9 +46,9 @@ module "folder" { folder_create = var.folder_ids.networking == null id = var.folder_ids.networking firewall_policy_factory = { - cidr_file = "${var.data_dir}/cidrs.yaml" - policy_name = null - rules_file = "${var.data_dir}/hierarchical-policy-rules.yaml" + cidr_file = "${var.factories_config.data_dir}/cidrs.yaml" + policy_name = var.factories_config.firewall_policy_name + rules_file = "${var.factories_config.data_dir}/hierarchical-policy-rules.yaml" } firewall_policy_association = { factory-policy = "factory" diff --git a/fast/stages/02-networking-vpn/monitoring.tf b/fast/stages/2-networking-a-peering/monitoring.tf similarity index 93% rename from fast/stages/02-networking-vpn/monitoring.tf rename to fast/stages/2-networking-a-peering/monitoring.tf index 7b8b70c5..be3a47fa 100644 --- a/fast/stages/02-networking-vpn/monitoring.tf +++ b/fast/stages/2-networking-a-peering/monitoring.tf @@ -17,7 +17,7 @@ # tfdoc:file:description Network monitoring dashboards. locals { - dashboard_path = "${var.data_dir}/dashboards" + dashboard_path = "${var.factories_config.data_dir}/dashboards" dashboard_files = fileset(local.dashboard_path, "*.json") dashboards = { for filename in local.dashboard_files : diff --git a/fast/stages/02-networking-peering/outputs.tf b/fast/stages/2-networking-a-peering/outputs.tf similarity index 93% rename from fast/stages/02-networking-peering/outputs.tf rename to fast/stages/2-networking-a-peering/outputs.tf index 3b97b7f2..628c706b 100644 --- a/fast/stages/02-networking-peering/outputs.tf +++ b/fast/stages/2-networking-a-peering/outputs.tf @@ -48,13 +48,13 @@ locals { resource "local_file" "tfvars" { for_each = var.outputs_location == null ? {} : { 1 = 1 } file_permission = "0644" - filename = "${try(pathexpand(var.outputs_location), "")}/tfvars/02-networking.auto.tfvars.json" + filename = "${try(pathexpand(var.outputs_location), "")}/tfvars/2-networking.auto.tfvars.json" content = jsonencode(local.tfvars) } resource "google_storage_bucket_object" "tfvars" { bucket = var.automation.outputs_bucket - name = "tfvars/02-networking.auto.tfvars.json" + name = "tfvars/2-networking.auto.tfvars.json" content = jsonencode(local.tfvars) } @@ -89,8 +89,8 @@ output "tfvars" { output "vpn_gateway_endpoints" { description = "External IP Addresses for the GCP VPN gateways." value = local.enable_onprem_vpn == false ? null : { - onprem-ew1 = { - for v in module.landing-to-onprem-ew1-vpn[0].gateway.vpn_interfaces : + onprem-primary = { + for v in module.landing-to-onprem-primary-vpn[0].gateway.vpn_interfaces : v.id => v.ip_address } } diff --git a/fast/stages/02-networking-peering/peerings.tf b/fast/stages/2-networking-a-peering/peerings.tf similarity index 100% rename from fast/stages/02-networking-peering/peerings.tf rename to fast/stages/2-networking-a-peering/peerings.tf diff --git a/fast/stages/2-networking-a-peering/regions.tf b/fast/stages/2-networking-a-peering/regions.tf new file mode 100644 index 00000000..53514afa --- /dev/null +++ b/fast/stages/2-networking-a-peering/regions.tf @@ -0,0 +1,42 @@ +/** + * 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 Compute short names for regions. + +locals { + # only map when the first character would not work + _region_cardinal = { + southeast = "se" + } + # only map when the first character would not work + _region_geo = { + australia = "o" + } + # split in [geo, cardinal, number] tokens + _region_tokens = { + for v in local.regions : v => regexall("(?:[a-z]+)|(?:[0-9]+)", v) + } + region_shortnames = { + for k, v in local._region_tokens : k => join("", [ + # first token via geo alias map or first character + lookup(local._region_geo, v.0, substr(v.0, 0, 1)), + # first token via cardinal alias map or first character + lookup(local._region_cardinal, v.1, substr(v.1, 0, 1)), + # region number as is + v.2 + ]) + } +} diff --git a/fast/stages/02-networking-peering/spoke-dev.tf b/fast/stages/2-networking-a-peering/spoke-dev.tf similarity index 84% rename from fast/stages/02-networking-peering/spoke-dev.tf rename to fast/stages/2-networking-a-peering/spoke-dev.tf index e67cfb70..ed7d13cb 100644 --- a/fast/stages/02-networking-peering/spoke-dev.tf +++ b/fast/stages/2-networking-a-peering/spoke-dev.tf @@ -16,6 +16,19 @@ # 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 @@ -48,9 +61,9 @@ module "dev-spoke-vpc" { project_id = module.dev-spoke-project.project_id name = "dev-spoke-0" mtu = 1500 - data_folder = "${var.data_dir}/subnets/dev" + data_folder = "${var.factories_config.data_dir}/subnets/dev" psa_config = try(var.psa_ranges.dev, null) - subnets_proxy_only = local.l7ilb_subnets.dev + subnets_proxy_only = local.l7ilb_subnets_dev # set explicit routes for googleapis in case the default route is deleted routes = { private-googleapis = { @@ -74,8 +87,8 @@ module "dev-spoke-firewall" { disabled = true } factories_config = { - cidr_tpl_file = "${var.data_dir}/cidrs.yaml" - rules_folder = "${var.data_dir}/firewall-rules/dev" + cidr_tpl_file = "${var.factories_config.data_dir}/cidrs.yaml" + rules_folder = "${var.factories_config.data_dir}/firewall-rules/dev" } } @@ -84,7 +97,7 @@ module "dev-spoke-cloudnat" { source = "../../../modules/net-cloudnat" project_id = module.dev-spoke-project.project_id region = each.value - name = "dev-nat-${var.region_trigram[each.value]}" + name = "dev-nat-${local.region_shortnames[each.value]}" router_create = true router_network = module.dev-spoke-vpc.name router_asn = 4200001024 diff --git a/fast/stages/02-networking-vpn/spoke-prod.tf b/fast/stages/2-networking-a-peering/spoke-prod.tf similarity index 84% rename from fast/stages/02-networking-vpn/spoke-prod.tf rename to fast/stages/2-networking-a-peering/spoke-prod.tf index cf49152f..f584b32d 100644 --- a/fast/stages/02-networking-vpn/spoke-prod.tf +++ b/fast/stages/2-networking-a-peering/spoke-prod.tf @@ -16,6 +16,19 @@ # 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 @@ -48,9 +61,9 @@ module "prod-spoke-vpc" { project_id = module.prod-spoke-project.project_id name = "prod-spoke-0" mtu = 1500 - data_folder = "${var.data_dir}/subnets/prod" + data_folder = "${var.factories_config.data_dir}/subnets/prod" psa_config = try(var.psa_ranges.prod, null) - subnets_proxy_only = local.l7ilb_subnets.prod + subnets_proxy_only = local.l7ilb_subnets_prod # set explicit routes for googleapis in case the default route is deleted routes = { private-googleapis = { @@ -74,8 +87,8 @@ module "prod-spoke-firewall" { disabled = true } factories_config = { - cidr_tpl_file = "${var.data_dir}/cidrs.yaml" - rules_folder = "${var.data_dir}/firewall-rules/prod" + cidr_tpl_file = "${var.factories_config.data_dir}/cidrs.yaml" + rules_folder = "${var.factories_config.data_dir}/firewall-rules/prod" } } @@ -84,7 +97,7 @@ module "prod-spoke-cloudnat" { source = "../../../modules/net-cloudnat" project_id = module.prod-spoke-project.project_id region = each.value - name = "prod-nat-${var.region_trigram[each.value]}" + name = "prod-nat-${local.region_shortnames[each.value]}" router_create = true router_network = module.prod-spoke-vpc.name router_asn = 4200001024 diff --git a/fast/stages/02-networking-peering/test-resources.tf b/fast/stages/2-networking-a-peering/test-resources.tf similarity index 76% rename from fast/stages/02-networking-peering/test-resources.tf rename to fast/stages/2-networking-a-peering/test-resources.tf index 204971fe..67073845 100644 --- a/fast/stages/02-networking-peering/test-resources.tf +++ b/fast/stages/2-networking-a-peering/test-resources.tf @@ -19,11 +19,11 @@ # module "test-vm-landing-0" { # source = "../../../modules/compute-vm" # project_id = module.landing-project.project_id -# zone = "europe-west1-b" +# zone = "${var.regions.primary}-b" # name = "test-vm-0" # network_interfaces = [{ # network = module.landing-vpc.self_link -# subnetwork = module.landing-vpc.subnet_self_links["europe-west1/landing-default-ew1"] +# subnetwork = module.landing-vpc.subnet_self_links["${var.regions.primary}/landing-default-${local.region_shortnames[var.regions.primary]}"] # }] # tags = ["ssh"] # service_account_create = true @@ -31,8 +31,8 @@ # image = "projects/debian-cloud/global/images/family/debian-10" # } # options = { -# spot = true -# termination_action = "STOP" +# spot = true +# termination_action = "STOP" # } # metadata = { # startup-script = < { bgp_peer = { address = cidrhost(t.session_range, 1) asn = t.peer_asn } - bgp_peer_options = local.bgp_peer_options_onprem.landing-ew1 + bgp_peer_options = local.bgp_peer_options_onprem.landing-primary bgp_session_range = "${cidrhost(t.session_range, 2)}/30" peer_external_gateway_interface = t.peer_external_gateway_interface shared_secret = t.secret diff --git a/fast/stages/02-networking-separate-envs/.gitignore b/fast/stages/2-networking-b-vpn/.gitignore similarity index 100% rename from fast/stages/02-networking-separate-envs/.gitignore rename to fast/stages/2-networking-b-vpn/.gitignore diff --git a/fast/stages/02-networking-separate-envs/IAM.md b/fast/stages/2-networking-b-vpn/IAM.md similarity index 100% rename from fast/stages/02-networking-separate-envs/IAM.md rename to fast/stages/2-networking-b-vpn/IAM.md diff --git a/fast/stages/02-networking-vpn/README.md b/fast/stages/2-networking-b-vpn/README.md similarity index 81% rename from fast/stages/02-networking-vpn/README.md rename to fast/stages/2-networking-b-vpn/README.md index 047a1189..2177f311 100644 --- a/fast/stages/02-networking-vpn/README.md +++ b/fast/stages/2-networking-b-vpn/README.md @@ -45,10 +45,10 @@ This is a summary of the main options: - [HA VPN](https://cloud.google.com/network-connectivity/docs/vpn/concepts/topologies) (implemented here) - Pros: simple compatibility with GCP services that leverage peering internally, better control on routes, avoids peering groups shared quotas and limits - Cons: additional cost, marginal increase in latency, requires multiple tunnels for full bandwidth -- [VPC Peering](https://cloud.google.com/vpc/docs/vpc-peering) (implemented by [02-networking-peering](../02-networking-peering/)) +- [VPC Peering](https://cloud.google.com/vpc/docs/vpc-peering) (implemented by [02-networking-peering](../2-networking-a-peering/)) - Pros: no additional costs, full bandwidth with no configurations, no extra latency - Cons: no transitivity (e.g. to GKE masters, Cloud SQL, etc.), no selective exchange of routes, several quotas and limits shared between VPCs in a peering group -- [Multi-NIC appliances](https://cloud.google.com/architecture/best-practices-vpc-design#multi-nic) (implemented by [02-networking-nva](../02-networking-nva/)) +- [Multi-NIC appliances](https://cloud.google.com/architecture/best-practices-vpc-design#multi-nic) (implemented by [02-networking-nva](../2-networking-c-nva/)) - Pros: additional security features (e.g. IPS), potentially better integration with on-prem systems by using the same vendor - Cons: complex HA/failover setup, limited by VM bandwidth and scale, additional costs for VMs and licenses, out of band management of a critical cloud component @@ -128,7 +128,7 @@ This configuration is battle-tested, and flexible enough to lend itself to simpl ## How to run this stage -This stage is meant to be executed after the [resman](../01-resman) stage has run, as it leverages the automation service account and bucket created there, and additional resources configured in the [bootstrap](../00-bootstrap) stage. +This stage is meant to be executed after the [resman](../1-resman) stage has run, as it leverages the automation service account and bucket created there, and additional resources configured in the [bootstrap](../0-bootstrap) stage. It's of course possible to run this stage in isolation, but that's outside the scope of this document, and you would need to refer to the code for the previous stages for the environmental requirements. @@ -136,7 +136,7 @@ Before running this stage, you need to make sure you have the correct credential ### Providers configuration -The default way of making sure you have the right permissions, is to use the identity of the service account pre-created for this stage during the [resource management](../01-resman) stage, and that you are a member of the group that can impersonate it via provider-level configuration (`gcp-devops` or `organization-admins`). +The default way of making sure you have the right permissions, is to use the identity of the service account pre-created for this stage during the [resource management](../1-resman) stage, and that you are a member of the group that can impersonate it via provider-level configuration (`gcp-devops` or `organization-admins`). To simplify setup, the previous stage pre-configures a valid providers file in its output, and optionally writes it to a local file if the `outputs_location` variable is set to a valid path. @@ -150,7 +150,7 @@ ln -s ~/fast-config/providers/02-networking-providers.tf . If you have not configured `outputs_location` in bootstrap, you can derive the providers file from that stage's outputs: ```bash -cd ../01-resman +cd ../1-resman terraform output -json providers | jq -r '.["02-networking"]' \ > ../02-networking/providers.tf ``` @@ -306,6 +306,7 @@ DNS configurations are centralised in the `dns-*.tf` files. Spokes delegate DNS | [main.tf](./main.tf) | Networking folder and hierarchical policy. | folder | | | [monitoring.tf](./monitoring.tf) | Network monitoring dashboards. | | google_monitoring_dashboard | | [outputs.tf](./outputs.tf) | Module outputs. | | google_storage_bucket_object · local_file | +| [regions.tf](./regions.tf) | Compute short names for regions. | | | | [spoke-dev.tf](./spoke-dev.tf) | Dev spoke VPC and related resources. | net-cloudnat · net-vpc · net-vpc-firewall · project | google_project_iam_binding | | [spoke-prod.tf](./spoke-prod.tf) | Production spoke VPC and related resources. | net-cloudnat · net-vpc · net-vpc-firewall · project | google_project_iam_binding | | [test-resources.tf](./test-resources.tf) | temporary instances for testing | compute-vm | | @@ -313,31 +314,31 @@ DNS configurations are centralised in the `dns-*.tf` files. Spokes delegate DNS | [variables.tf](./variables.tf) | Module variables. | | | | [vpn-onprem.tf](./vpn-onprem.tf) | VPN between landing and onprem. | net-vpn-ha | | | [vpn-spoke-dev.tf](./vpn-spoke-dev.tf) | VPN between landing and development spoke. | net-vpn-ha | | -| [vpn-spoke-prod-ew1.tf](./vpn-spoke-prod-ew1.tf) | VPN between landing and production spoke in ew1. | net-vpn-ha | | -| [vpn-spoke-prod-ew4.tf](./vpn-spoke-prod-ew4.tf) | VPN between landing and production spoke in ew4. | net-vpn-ha | | +| [vpn-spoke-prod-primary.tf](./vpn-spoke-prod-primary.tf) | VPN between landing and production spoke in ew1. | net-vpn-ha | | +| [vpn-spoke-prod-secondary.tf](./vpn-spoke-prod-secondary.tf) | VPN between landing and production spoke in ew4. | net-vpn-ha | | ## Variables | name | description | type | required | default | producer | |---|---|:---:|:---:|:---:|:---:| -| [automation](variables.tf#L17) | Automation resources created by the bootstrap stage. | object({…}) | ✓ | | 00-bootstrap | -| [billing_account](variables.tf#L25) | Billing account id and organization id ('nnnnnnnn' or null). | object({…}) | ✓ | | 00-bootstrap | -| [folder_ids](variables.tf#L74) | Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created. | object({…}) | ✓ | | 01-resman | -| [organization](variables.tf#L102) | Organization details. | object({…}) | ✓ | | 00-bootstrap | -| [prefix](variables.tf#L118) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 00-bootstrap | -| [custom_adv](variables.tf#L34) | Custom advertisement definitions in name => range format. | map(string) | | {…} | | -| [custom_roles](variables.tf#L51) | Custom roles defined at the org level, in key => id format. | object({…}) | | null | 00-bootstrap | -| [data_dir](variables.tf#L60) | Relative path for the folder storing configuration data for network resources. | string | | "data" | | -| [dns](variables.tf#L66) | Onprem DNS resolvers. | map(list(string)) | | {…} | | -| [l7ilb_subnets](variables.tf#L84) | Subnets used for L7 ILBs. | map(list(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 | | -| [region_trigram](variables.tf#L166) | Short names for GCP regions. | map(string) | | {…} | | -| [router_onprem_configs](variables.tf#L175) | 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#L193) | Automation service accounts in name => email format. | object({…}) | | null | 01-resman | -| [vpn_onprem_configs](variables.tf#L207) | VPN gateway configuration for onprem interconnection. | map(object({…})) | | {…} | | -| [vpn_spoke_configs](variables-vpn.tf#L37) | VPN gateway configuration for spokes. | map(object({…})) | | {…} | | +| [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 | +| [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({…})) | | {…} | | +| [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({…})) | | {…} | | +| [vpn_spoke_configs](variables-vpn.tf#L37) | VPN gateway configuration for spokes. | map(object({…})) | | {…} | | ## Outputs diff --git a/fast/stages/02-networking-peering/data/cidrs.yaml b/fast/stages/2-networking-b-vpn/data/cidrs.yaml similarity index 100% rename from fast/stages/02-networking-peering/data/cidrs.yaml rename to fast/stages/2-networking-b-vpn/data/cidrs.yaml diff --git a/fast/stages/02-networking-separate-envs/data/dashboards/firewall_insights.json b/fast/stages/2-networking-b-vpn/data/dashboards/firewall_insights.json similarity index 100% rename from fast/stages/02-networking-separate-envs/data/dashboards/firewall_insights.json rename to fast/stages/2-networking-b-vpn/data/dashboards/firewall_insights.json diff --git a/fast/stages/02-networking-separate-envs/data/dashboards/vpn.json b/fast/stages/2-networking-b-vpn/data/dashboards/vpn.json similarity index 100% rename from fast/stages/02-networking-separate-envs/data/dashboards/vpn.json rename to fast/stages/2-networking-b-vpn/data/dashboards/vpn.json diff --git a/fast/stages/02-networking-peering/data/firewall-rules/dev/rules.yaml b/fast/stages/2-networking-b-vpn/data/firewall-rules/dev/rules.yaml similarity index 100% rename from fast/stages/02-networking-peering/data/firewall-rules/dev/rules.yaml rename to fast/stages/2-networking-b-vpn/data/firewall-rules/dev/rules.yaml diff --git a/fast/stages/02-networking-vpn/data/firewall-rules/landing/rules.yaml b/fast/stages/2-networking-b-vpn/data/firewall-rules/landing/rules.yaml similarity index 100% rename from fast/stages/02-networking-vpn/data/firewall-rules/landing/rules.yaml rename to fast/stages/2-networking-b-vpn/data/firewall-rules/landing/rules.yaml diff --git a/fast/stages/02-networking-peering/data/hierarchical-policy-rules.yaml b/fast/stages/2-networking-b-vpn/data/hierarchical-policy-rules.yaml similarity index 100% rename from fast/stages/02-networking-peering/data/hierarchical-policy-rules.yaml rename to fast/stages/2-networking-b-vpn/data/hierarchical-policy-rules.yaml diff --git a/fast/stages/02-networking-separate-envs/data/subnets/dev/dev-dataplatform-ew1.yaml b/fast/stages/2-networking-b-vpn/data/subnets/dev/dev-dataplatform-ew1.yaml similarity index 100% rename from fast/stages/02-networking-separate-envs/data/subnets/dev/dev-dataplatform-ew1.yaml rename to fast/stages/2-networking-b-vpn/data/subnets/dev/dev-dataplatform-ew1.yaml diff --git a/fast/stages/02-networking-separate-envs/data/subnets/dev/dev-default-ew1.yaml b/fast/stages/2-networking-b-vpn/data/subnets/dev/dev-default-ew1.yaml similarity index 100% rename from fast/stages/02-networking-separate-envs/data/subnets/dev/dev-default-ew1.yaml rename to fast/stages/2-networking-b-vpn/data/subnets/dev/dev-default-ew1.yaml diff --git a/fast/stages/02-networking-vpn/data/subnets/dev/dev-gke-nodes-ew1.yaml b/fast/stages/2-networking-b-vpn/data/subnets/dev/dev-gke-nodes-ew1.yaml similarity index 100% rename from fast/stages/02-networking-vpn/data/subnets/dev/dev-gke-nodes-ew1.yaml rename to fast/stages/2-networking-b-vpn/data/subnets/dev/dev-gke-nodes-ew1.yaml diff --git a/fast/stages/02-networking-vpn/data/subnets/landing/landing-default-ew1.yaml b/fast/stages/2-networking-b-vpn/data/subnets/landing/landing-default-ew1.yaml similarity index 100% rename from fast/stages/02-networking-vpn/data/subnets/landing/landing-default-ew1.yaml rename to fast/stages/2-networking-b-vpn/data/subnets/landing/landing-default-ew1.yaml diff --git a/fast/stages/02-networking-separate-envs/data/subnets/prod/prod-default-ew1.yaml b/fast/stages/2-networking-b-vpn/data/subnets/prod/prod-default-ew1.yaml similarity index 100% rename from fast/stages/02-networking-separate-envs/data/subnets/prod/prod-default-ew1.yaml rename to fast/stages/2-networking-b-vpn/data/subnets/prod/prod-default-ew1.yaml diff --git a/fast/stages/02-networking-vpn/diagram.png b/fast/stages/2-networking-b-vpn/diagram.png similarity index 100% rename from fast/stages/02-networking-vpn/diagram.png rename to fast/stages/2-networking-b-vpn/diagram.png diff --git a/fast/stages/02-networking-vpn/diagram.svg b/fast/stages/2-networking-b-vpn/diagram.svg similarity index 100% rename from fast/stages/02-networking-vpn/diagram.svg rename to fast/stages/2-networking-b-vpn/diagram.svg diff --git a/fast/stages/02-networking-vpn/dns-dev.tf b/fast/stages/2-networking-b-vpn/dns-dev.tf similarity index 100% rename from fast/stages/02-networking-vpn/dns-dev.tf rename to fast/stages/2-networking-b-vpn/dns-dev.tf diff --git a/fast/stages/02-networking-vpn/dns-landing.tf b/fast/stages/2-networking-b-vpn/dns-landing.tf similarity index 100% rename from fast/stages/02-networking-vpn/dns-landing.tf rename to fast/stages/2-networking-b-vpn/dns-landing.tf diff --git a/fast/stages/02-networking-vpn/dns-prod.tf b/fast/stages/2-networking-b-vpn/dns-prod.tf similarity index 100% rename from fast/stages/02-networking-vpn/dns-prod.tf rename to fast/stages/2-networking-b-vpn/dns-prod.tf diff --git a/fast/stages/02-networking-vpn/landing.tf b/fast/stages/2-networking-b-vpn/landing.tf similarity index 82% rename from fast/stages/02-networking-vpn/landing.tf rename to fast/stages/2-networking-b-vpn/landing.tf index 83a0d509..37e3adfd 100644 --- a/fast/stages/02-networking-vpn/landing.tf +++ b/fast/stages/2-networking-b-vpn/landing.tf @@ -63,7 +63,7 @@ module "landing-vpc" { next_hop = "default-internet-gateway" } } - data_folder = "${var.data_dir}/subnets/landing" + data_folder = "${var.factories_config.data_dir}/subnets/landing" } module "landing-firewall" { @@ -74,18 +74,23 @@ module "landing-firewall" { disabled = true } factories_config = { - cidr_tpl_file = "${var.data_dir}/cidrs.yaml" - rules_folder = "${var.data_dir}/firewall-rules/landing" + cidr_tpl_file = "${var.factories_config.data_dir}/cidrs.yaml" + rules_folder = "${var.factories_config.data_dir}/firewall-rules/landing" } } -module "landing-nat-ew1" { +moved { + from = module.landing-nat-ew1 + to = module.landing-nat-primary +} + +module "landing-nat-primary" { source = "../../../modules/net-cloudnat" project_id = module.landing-project.project_id - region = "europe-west1" - name = "ew1" + region = var.regions.primary + name = local.region_shortnames[var.regions.primary] router_create = true - router_name = "prod-nat-ew1" + router_name = "prod-nat-${local.region_shortnames[var.regions.primary]}" router_network = module.landing-vpc.name router_asn = 4200001024 } diff --git a/fast/stages/02-networking-peering/main.tf b/fast/stages/2-networking-b-vpn/main.tf similarity index 76% rename from fast/stages/02-networking-peering/main.tf rename to fast/stages/2-networking-b-vpn/main.tf index f68d39eb..5888752d 100644 --- a/fast/stages/02-networking-peering/main.tf +++ b/fast/stages/2-networking-b-vpn/main.tf @@ -17,14 +17,14 @@ # tfdoc:file:description Networking folder and hierarchical policy. locals { + # combine all regions from variables and subnets + regions = distinct(concat( + values(var.regions), + values(module.dev-spoke-vpc.subnet_regions), + values(module.landing-vpc.subnet_regions), + values(module.prod-spoke-vpc.subnet_regions), + )) 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}" - })] - } stage3_sas_delegated_grants = [ "roles/composer.sharedVpcAgent", "roles/compute.networkUser", @@ -46,9 +46,9 @@ module "folder" { folder_create = var.folder_ids.networking == null id = var.folder_ids.networking firewall_policy_factory = { - cidr_file = "${var.data_dir}/cidrs.yaml" - policy_name = null - rules_file = "${var.data_dir}/hierarchical-policy-rules.yaml" + cidr_file = "${var.factories_config.data_dir}/cidrs.yaml" + policy_name = var.factories_config.firewall_policy_name + rules_file = "${var.factories_config.data_dir}/hierarchical-policy-rules.yaml" } firewall_policy_association = { factory-policy = "factory" diff --git a/fast/stages/02-networking-nva/monitoring.tf b/fast/stages/2-networking-b-vpn/monitoring.tf similarity index 93% rename from fast/stages/02-networking-nva/monitoring.tf rename to fast/stages/2-networking-b-vpn/monitoring.tf index 7b8b70c5..be3a47fa 100644 --- a/fast/stages/02-networking-nva/monitoring.tf +++ b/fast/stages/2-networking-b-vpn/monitoring.tf @@ -17,7 +17,7 @@ # tfdoc:file:description Network monitoring dashboards. locals { - dashboard_path = "${var.data_dir}/dashboards" + dashboard_path = "${var.factories_config.data_dir}/dashboards" dashboard_files = fileset(local.dashboard_path, "*.json") dashboards = { for filename in local.dashboard_files : diff --git a/fast/stages/02-networking-vpn/outputs.tf b/fast/stages/2-networking-b-vpn/outputs.tf similarity index 93% rename from fast/stages/02-networking-vpn/outputs.tf rename to fast/stages/2-networking-b-vpn/outputs.tf index 3b97b7f2..628c706b 100644 --- a/fast/stages/02-networking-vpn/outputs.tf +++ b/fast/stages/2-networking-b-vpn/outputs.tf @@ -48,13 +48,13 @@ locals { resource "local_file" "tfvars" { for_each = var.outputs_location == null ? {} : { 1 = 1 } file_permission = "0644" - filename = "${try(pathexpand(var.outputs_location), "")}/tfvars/02-networking.auto.tfvars.json" + filename = "${try(pathexpand(var.outputs_location), "")}/tfvars/2-networking.auto.tfvars.json" content = jsonencode(local.tfvars) } resource "google_storage_bucket_object" "tfvars" { bucket = var.automation.outputs_bucket - name = "tfvars/02-networking.auto.tfvars.json" + name = "tfvars/2-networking.auto.tfvars.json" content = jsonencode(local.tfvars) } @@ -89,8 +89,8 @@ output "tfvars" { output "vpn_gateway_endpoints" { description = "External IP Addresses for the GCP VPN gateways." value = local.enable_onprem_vpn == false ? null : { - onprem-ew1 = { - for v in module.landing-to-onprem-ew1-vpn[0].gateway.vpn_interfaces : + onprem-primary = { + for v in module.landing-to-onprem-primary-vpn[0].gateway.vpn_interfaces : v.id => v.ip_address } } diff --git a/fast/stages/2-networking-b-vpn/regions.tf b/fast/stages/2-networking-b-vpn/regions.tf new file mode 100644 index 00000000..53514afa --- /dev/null +++ b/fast/stages/2-networking-b-vpn/regions.tf @@ -0,0 +1,42 @@ +/** + * 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 Compute short names for regions. + +locals { + # only map when the first character would not work + _region_cardinal = { + southeast = "se" + } + # only map when the first character would not work + _region_geo = { + australia = "o" + } + # split in [geo, cardinal, number] tokens + _region_tokens = { + for v in local.regions : v => regexall("(?:[a-z]+)|(?:[0-9]+)", v) + } + region_shortnames = { + for k, v in local._region_tokens : k => join("", [ + # first token via geo alias map or first character + lookup(local._region_geo, v.0, substr(v.0, 0, 1)), + # first token via cardinal alias map or first character + lookup(local._region_cardinal, v.1, substr(v.1, 0, 1)), + # region number as is + v.2 + ]) + } +} diff --git a/fast/stages/02-networking-vpn/spoke-dev.tf b/fast/stages/2-networking-b-vpn/spoke-dev.tf similarity index 84% rename from fast/stages/02-networking-vpn/spoke-dev.tf rename to fast/stages/2-networking-b-vpn/spoke-dev.tf index e67cfb70..ed7d13cb 100644 --- a/fast/stages/02-networking-vpn/spoke-dev.tf +++ b/fast/stages/2-networking-b-vpn/spoke-dev.tf @@ -16,6 +16,19 @@ # 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 @@ -48,9 +61,9 @@ module "dev-spoke-vpc" { project_id = module.dev-spoke-project.project_id name = "dev-spoke-0" mtu = 1500 - data_folder = "${var.data_dir}/subnets/dev" + data_folder = "${var.factories_config.data_dir}/subnets/dev" psa_config = try(var.psa_ranges.dev, null) - subnets_proxy_only = local.l7ilb_subnets.dev + subnets_proxy_only = local.l7ilb_subnets_dev # set explicit routes for googleapis in case the default route is deleted routes = { private-googleapis = { @@ -74,8 +87,8 @@ module "dev-spoke-firewall" { disabled = true } factories_config = { - cidr_tpl_file = "${var.data_dir}/cidrs.yaml" - rules_folder = "${var.data_dir}/firewall-rules/dev" + cidr_tpl_file = "${var.factories_config.data_dir}/cidrs.yaml" + rules_folder = "${var.factories_config.data_dir}/firewall-rules/dev" } } @@ -84,7 +97,7 @@ module "dev-spoke-cloudnat" { source = "../../../modules/net-cloudnat" project_id = module.dev-spoke-project.project_id region = each.value - name = "dev-nat-${var.region_trigram[each.value]}" + name = "dev-nat-${local.region_shortnames[each.value]}" router_create = true router_network = module.dev-spoke-vpc.name router_asn = 4200001024 diff --git a/fast/stages/02-networking-peering/spoke-prod.tf b/fast/stages/2-networking-b-vpn/spoke-prod.tf similarity index 84% rename from fast/stages/02-networking-peering/spoke-prod.tf rename to fast/stages/2-networking-b-vpn/spoke-prod.tf index cf49152f..f584b32d 100644 --- a/fast/stages/02-networking-peering/spoke-prod.tf +++ b/fast/stages/2-networking-b-vpn/spoke-prod.tf @@ -16,6 +16,19 @@ # 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 @@ -48,9 +61,9 @@ module "prod-spoke-vpc" { project_id = module.prod-spoke-project.project_id name = "prod-spoke-0" mtu = 1500 - data_folder = "${var.data_dir}/subnets/prod" + data_folder = "${var.factories_config.data_dir}/subnets/prod" psa_config = try(var.psa_ranges.prod, null) - subnets_proxy_only = local.l7ilb_subnets.prod + subnets_proxy_only = local.l7ilb_subnets_prod # set explicit routes for googleapis in case the default route is deleted routes = { private-googleapis = { @@ -74,8 +87,8 @@ module "prod-spoke-firewall" { disabled = true } factories_config = { - cidr_tpl_file = "${var.data_dir}/cidrs.yaml" - rules_folder = "${var.data_dir}/firewall-rules/prod" + cidr_tpl_file = "${var.factories_config.data_dir}/cidrs.yaml" + rules_folder = "${var.factories_config.data_dir}/firewall-rules/prod" } } @@ -84,7 +97,7 @@ module "prod-spoke-cloudnat" { source = "../../../modules/net-cloudnat" project_id = module.prod-spoke-project.project_id region = each.value - name = "prod-nat-${var.region_trigram[each.value]}" + name = "prod-nat-${local.region_shortnames[each.value]}" router_create = true router_network = module.prod-spoke-vpc.name router_asn = 4200001024 diff --git a/fast/stages/02-networking-vpn/test-resources.tf b/fast/stages/2-networking-b-vpn/test-resources.tf similarity index 76% rename from fast/stages/02-networking-vpn/test-resources.tf rename to fast/stages/2-networking-b-vpn/test-resources.tf index 204971fe..67073845 100644 --- a/fast/stages/02-networking-vpn/test-resources.tf +++ b/fast/stages/2-networking-b-vpn/test-resources.tf @@ -19,11 +19,11 @@ # module "test-vm-landing-0" { # source = "../../../modules/compute-vm" # project_id = module.landing-project.project_id -# zone = "europe-west1-b" +# zone = "${var.regions.primary}-b" # name = "test-vm-0" # network_interfaces = [{ # network = module.landing-vpc.self_link -# subnetwork = module.landing-vpc.subnet_self_links["europe-west1/landing-default-ew1"] +# subnetwork = module.landing-vpc.subnet_self_links["${var.regions.primary}/landing-default-${local.region_shortnames[var.regions.primary]}"] # }] # tags = ["ssh"] # service_account_create = true @@ -31,8 +31,8 @@ # image = "projects/debian-cloud/global/images/family/debian-10" # } # options = { -# spot = true -# termination_action = "STOP" +# spot = true +# termination_action = "STOP" # } # metadata = { # startup-script = < { bgp_peer = { address = cidrhost(t.session_range, 1) asn = t.peer_asn } - bgp_peer_options = local.bgp_peer_options_onprem.landing-ew1 + bgp_peer_options = local.bgp_peer_options_onprem.landing-primary bgp_session_range = "${cidrhost(t.session_range, 2)}/30" peer_external_gateway_interface = t.peer_external_gateway_interface shared_secret = t.secret diff --git a/fast/stages/02-networking-vpn/vpn-spoke-dev.tf b/fast/stages/2-networking-b-vpn/vpn-spoke-dev.tf similarity index 62% rename from fast/stages/02-networking-vpn/vpn-spoke-dev.tf rename to fast/stages/2-networking-b-vpn/vpn-spoke-dev.tf index 317560af..09cc31e0 100644 --- a/fast/stages/02-networking-vpn/vpn-spoke-dev.tf +++ b/fast/stages/2-networking-b-vpn/vpn-spoke-dev.tf @@ -33,26 +33,31 @@ locals { # development spoke -module "landing-to-dev-ew1-vpn" { +moved { + from = module.landing-to-dev-ew1-vpn + to = module.landing-to-dev-primary-vpn +} + +module "landing-to-dev-primary-vpn" { source = "../../../modules/net-vpn-ha" project_id = module.landing-project.project_id network = module.landing-vpc.self_link - region = "europe-west1" - name = "vpn-to-dev-ew1" + region = var.regions.primary + name = "vpn-to-dev-${local.region_shortnames[var.regions.primary]}" router_config = { # The router used for this VPN is managed in vpn-prod.tf create = false - name = "landing-vpn-ew1" - asn = var.router_spoke_configs.landing-ew1.asn + name = "landing-vpn-${local.region_shortnames[var.regions.primary]}" + asn = var.router_spoke_configs.landing-primary.asn } - peer_gateway = { gcp = module.dev-to-landing-ew1-vpn.self_link } + peer_gateway = { gcp = module.dev-to-landing-primary-vpn.self_link } tunnels = { 0 = { bgp_peer = { address = cidrhost("169.254.0.0/27", 1) - asn = var.router_spoke_configs.spoke-dev-ew1.asn + asn = var.router_spoke_configs.spoke-dev-primary.asn } - bgp_peer_options = local.vpn_spoke_bgp_peer_options.landing-ew1 + bgp_peer_options = local.vpn_spoke_bgp_peer_options.landing-primary bgp_session_range = "${ cidrhost("169.254.0.0/27", 2) }/30" @@ -61,9 +66,9 @@ module "landing-to-dev-ew1-vpn" { 1 = { bgp_peer = { address = cidrhost("169.254.0.0/27", 5) - asn = var.router_spoke_configs.spoke-dev-ew1.asn + asn = var.router_spoke_configs.spoke-dev-primary.asn } - bgp_peer_options = local.vpn_spoke_bgp_peer_options.landing-ew1 + bgp_peer_options = local.vpn_spoke_bgp_peer_options.landing-primary bgp_session_range = "${ cidrhost("169.254.0.0/27", 6) }/30" @@ -71,44 +76,49 @@ module "landing-to-dev-ew1-vpn" { } } depends_on = [ - module.landing-to-prod-ew1-vpn.router + module.landing-to-prod-primary-vpn.router ] } -module "dev-to-landing-ew1-vpn" { +moved { + from = module.dev-to-landing-ew1-vpn + to = module.dev-to-landing-primary-vpn +} + +module "dev-to-landing-primary-vpn" { source = "../../../modules/net-vpn-ha" project_id = module.dev-spoke-project.project_id network = module.dev-spoke-vpc.self_link - region = "europe-west1" - name = "vpn-to-landing-ew1" + region = var.regions.primary + name = "vpn-to-landing-${local.region_shortnames[var.regions.primary]}" router_config = { - name = "dev-spoke-vpn-ew1" - asn = var.router_spoke_configs.spoke-dev-ew1.asn + name = "dev-spoke-vpn-${local.region_shortnames[var.regions.primary]}" + asn = var.router_spoke_configs.spoke-dev-primary.asn } - peer_gateway = { gcp = module.landing-to-dev-ew1-vpn.self_link } + peer_gateway = { gcp = module.landing-to-dev-primary-vpn.self_link } tunnels = { 0 = { bgp_peer = { address = cidrhost("169.254.0.0/27", 2) - asn = var.router_spoke_configs.landing-ew1.asn + asn = var.router_spoke_configs.landing-primary.asn } - bgp_peer_options = local.vpn_spoke_bgp_peer_options.dev-ew1 + bgp_peer_options = local.vpn_spoke_bgp_peer_options.dev-primary bgp_session_range = "${ cidrhost("169.254.0.0/27", 1) }/30" - shared_secret = module.landing-to-dev-ew1-vpn.random_secret + shared_secret = module.landing-to-dev-primary-vpn.random_secret vpn_gateway_interface = 0 } 1 = { bgp_peer = { address = cidrhost("169.254.0.0/27", 6) - asn = var.router_spoke_configs.landing-ew1.asn + asn = var.router_spoke_configs.landing-primary.asn } - bgp_peer_options = local.vpn_spoke_bgp_peer_options.dev-ew1 + bgp_peer_options = local.vpn_spoke_bgp_peer_options.dev-primary bgp_session_range = "${ cidrhost("169.254.0.0/27", 5) }/30" - shared_secret = module.landing-to-dev-ew1-vpn.random_secret + shared_secret = module.landing-to-dev-primary-vpn.random_secret vpn_gateway_interface = 1 } } diff --git a/fast/stages/02-networking-vpn/vpn-spoke-prod-ew1.tf b/fast/stages/2-networking-b-vpn/vpn-spoke-prod-primary.tf similarity index 58% rename from fast/stages/02-networking-vpn/vpn-spoke-prod-ew1.tf rename to fast/stages/2-networking-b-vpn/vpn-spoke-prod-primary.tf index a215ad4e..071b1d05 100644 --- a/fast/stages/02-networking-vpn/vpn-spoke-prod-ew1.tf +++ b/fast/stages/2-networking-b-vpn/vpn-spoke-prod-primary.tf @@ -18,24 +18,29 @@ # local.vpn_spoke_bgp_peer_options is defined in the dev VPN file -module "landing-to-prod-ew1-vpn" { +moved { + from = module.landing-to-prod-ew1-vpn + to = module.landing-to-prod-primary-vpn +} + +module "landing-to-prod-primary-vpn" { source = "../../../modules/net-vpn-ha" project_id = module.landing-project.project_id network = module.landing-vpc.self_link - region = "europe-west1" - name = "vpn-to-prod-ew1" + region = var.regions.primary + name = "vpn-to-prod-${local.region_shortnames[var.regions.primary]}" router_config = { - name = "landing-vpn-ew1" - asn = var.router_spoke_configs.landing-ew1.asn + name = "landing-vpn-${local.region_shortnames[var.regions.primary]}" + asn = var.router_spoke_configs.landing-primary.asn } - peer_gateway = { gcp = module.prod-to-landing-ew1-vpn.self_link } + peer_gateway = { gcp = module.prod-to-landing-primary-vpn.self_link } tunnels = { 0 = { bgp_peer = { address = cidrhost("169.254.0.64/27", 1) - asn = var.router_spoke_configs.spoke-prod-ew1.asn + asn = var.router_spoke_configs.spoke-prod-primary.asn } - bgp_peer_options = local.vpn_spoke_bgp_peer_options.landing-ew1 + bgp_peer_options = local.vpn_spoke_bgp_peer_options.landing-primary bgp_session_range = "${ cidrhost("169.254.0.64/27", 2) }/30" @@ -44,9 +49,9 @@ module "landing-to-prod-ew1-vpn" { 1 = { bgp_peer = { address = cidrhost("169.254.0.64/27", 5) - asn = var.router_spoke_configs.spoke-prod-ew1.asn + asn = var.router_spoke_configs.spoke-prod-primary.asn } - bgp_peer_options = local.vpn_spoke_bgp_peer_options.landing-ew1 + bgp_peer_options = local.vpn_spoke_bgp_peer_options.landing-primary bgp_session_range = "${ cidrhost("169.254.0.64/27", 6) }/30" @@ -55,40 +60,45 @@ module "landing-to-prod-ew1-vpn" { } } -module "prod-to-landing-ew1-vpn" { +moved { + from = module.prod-to-landing-ew1-vpn + to = module.prod-to-landing-primary-vpn +} + +module "prod-to-landing-primary-vpn" { source = "../../../modules/net-vpn-ha" project_id = module.prod-spoke-project.project_id network = module.prod-spoke-vpc.self_link - region = "europe-west1" - name = "vpn-to-landing-ew1" + region = var.regions.primary + name = "vpn-to-landing-${local.region_shortnames[var.regions.primary]}" router_config = { - name = "prod-spoke-vpn-ew1" - asn = var.router_spoke_configs.spoke-prod-ew1.asn + name = "prod-spoke-vpn-${local.region_shortnames[var.regions.primary]}" + asn = var.router_spoke_configs.spoke-prod-primary.asn } - peer_gateway = { gcp = module.landing-to-prod-ew1-vpn.self_link } + peer_gateway = { gcp = module.landing-to-prod-primary-vpn.self_link } tunnels = { 0 = { bgp_peer = { address = cidrhost("169.254.0.64/27", 2) - asn = var.router_spoke_configs.landing-ew1.asn + asn = var.router_spoke_configs.landing-primary.asn } - bgp_peer_options = local.vpn_spoke_bgp_peer_options.prod-ew1 + bgp_peer_options = local.vpn_spoke_bgp_peer_options.prod-primary bgp_session_range = "${ cidrhost("169.254.0.64/27", 1) }/30" - shared_secret = module.landing-to-prod-ew1-vpn.random_secret + shared_secret = module.landing-to-prod-primary-vpn.random_secret vpn_gateway_interface = 0 } 1 = { bgp_peer = { address = cidrhost("169.254.0.64/27", 6) - asn = var.router_spoke_configs.landing-ew1.asn + asn = var.router_spoke_configs.landing-primary.asn } - bgp_peer_options = local.vpn_spoke_bgp_peer_options.prod-ew1 + bgp_peer_options = local.vpn_spoke_bgp_peer_options.prod-primary bgp_session_range = "${ cidrhost("169.254.0.64/27", 5) }/30" - shared_secret = module.landing-to-prod-ew1-vpn.random_secret + shared_secret = module.landing-to-prod-primary-vpn.random_secret vpn_gateway_interface = 1 } } diff --git a/fast/stages/02-networking-vpn/vpn-spoke-prod-ew4.tf b/fast/stages/2-networking-b-vpn/vpn-spoke-prod-secondary.tf similarity index 57% rename from fast/stages/02-networking-vpn/vpn-spoke-prod-ew4.tf rename to fast/stages/2-networking-b-vpn/vpn-spoke-prod-secondary.tf index 994fba0b..a7c0e0fe 100644 --- a/fast/stages/02-networking-vpn/vpn-spoke-prod-ew4.tf +++ b/fast/stages/2-networking-b-vpn/vpn-spoke-prod-secondary.tf @@ -18,24 +18,29 @@ # local.vpn_spoke_bgp_peer_options is defined in the dev VPN file -module "landing-to-prod-ew4-vpn" { +moved { + from = module.landing-to-prod-ew4-vpn + to = module.landing-to-prod-secondary-vpn +} + +module "landing-to-prod-secondary-vpn" { source = "../../../modules/net-vpn-ha" project_id = module.landing-project.project_id network = module.landing-vpc.self_link - region = "europe-west4" - name = "vpn-to-prod-ew4" + region = var.regions.secondary + name = "vpn-to-prod-${local.region_shortnames[var.regions.secondary]}" router_config = { - name = "landing-vpn-ew4" - asn = var.router_spoke_configs.landing-ew4.asn + name = "landing-vpn-${local.region_shortnames[var.regions.secondary]}" + asn = var.router_spoke_configs.landing-secondary.asn } - peer_gateway = { gcp = module.prod-to-landing-ew4-vpn.self_link } + peer_gateway = { gcp = module.prod-to-landing-secondary-vpn.self_link } tunnels = { 0 = { bgp_peer = { address = cidrhost("169.254.0.96/27", 1) - asn = var.router_spoke_configs.spoke-prod-ew4.asn + asn = var.router_spoke_configs.spoke-prod-secondary.asn } - bgp_peer_options = local.vpn_spoke_bgp_peer_options.landing-ew4 + bgp_peer_options = local.vpn_spoke_bgp_peer_options.landing-secondary bgp_session_range = "${ cidrhost("169.254.0.96/27", 2) }/30" @@ -44,9 +49,9 @@ module "landing-to-prod-ew4-vpn" { 1 = { bgp_peer = { address = cidrhost("169.254.0.96/27", 5) - asn = var.router_spoke_configs.spoke-prod-ew4.asn + asn = var.router_spoke_configs.spoke-prod-secondary.asn } - bgp_peer_options = local.vpn_spoke_bgp_peer_options.landing-ew4 + bgp_peer_options = local.vpn_spoke_bgp_peer_options.landing-secondary bgp_session_range = "${ cidrhost("169.254.0.96/27", 6) }/30" @@ -55,40 +60,45 @@ module "landing-to-prod-ew4-vpn" { } } -module "prod-to-landing-ew4-vpn" { +moved { + from = module.prod-to-landing-ew4-vpn + to = module.prod-to-landing-secondary-vpn +} + +module "prod-to-landing-secondary-vpn" { source = "../../../modules/net-vpn-ha" project_id = module.prod-spoke-project.project_id network = module.prod-spoke-vpc.self_link - region = "europe-west4" - name = "vpn-to-landing-ew4" + region = var.regions.secondary + name = "vpn-to-landing-${local.region_shortnames[var.regions.secondary]}" router_config = { - name = "prod-spoke-vpn-ew4" - asn = var.router_spoke_configs.spoke-prod-ew4.asn + name = "prod-spoke-vpn-${local.region_shortnames[var.regions.secondary]}" + asn = var.router_spoke_configs.spoke-prod-secondary.asn } - peer_gateway = { gcp = module.landing-to-prod-ew4-vpn.self_link } + peer_gateway = { gcp = module.landing-to-prod-secondary-vpn.self_link } tunnels = { 0 = { bgp_peer = { address = cidrhost("169.254.0.96/27", 2) - asn = var.router_spoke_configs.landing-ew4.asn + asn = var.router_spoke_configs.landing-secondary.asn } - bgp_peer_options = local.vpn_spoke_bgp_peer_options.prod-ew4 + bgp_peer_options = local.vpn_spoke_bgp_peer_options.prod-secondary bgp_session_range = "${ cidrhost("169.254.0.96/27", 1) }/30" - shared_secret = module.landing-to-prod-ew4-vpn.random_secret + shared_secret = module.landing-to-prod-secondary-vpn.random_secret vpn_gateway_interface = 0 } 1 = { bgp_peer = { address = cidrhost("169.254.0.96/27", 6) - asn = var.router_spoke_configs.landing-ew4.asn + asn = var.router_spoke_configs.landing-secondary.asn } - bgp_peer_options = local.vpn_spoke_bgp_peer_options.prod-ew4 + bgp_peer_options = local.vpn_spoke_bgp_peer_options.prod-secondary bgp_session_range = "${ cidrhost("169.254.0.96/27", 5) }/30" - shared_secret = module.landing-to-prod-ew4-vpn.random_secret + shared_secret = module.landing-to-prod-secondary-vpn.random_secret vpn_gateway_interface = 1 } } diff --git a/fast/stages/02-networking-nva/README.md b/fast/stages/2-networking-c-nva/README.md similarity index 80% rename from fast/stages/02-networking-nva/README.md rename to fast/stages/2-networking-c-nva/README.md index 79c27e8d..425e1d19 100644 --- a/fast/stages/02-networking-nva/README.md +++ b/fast/stages/2-networking-c-nva/README.md @@ -69,7 +69,7 @@ Internal connectivity (e.g. between the trusted landing VPC and the spokes) is r This is an options summary: -- [VPC Peering](https://cloud.google.com/vpc/docs/vpc-peering) (used here to connect the trusted landing VPC with the spokes, also used by [02-networking-vpn](../02-networking-vpn/)) +- [VPC Peering](https://cloud.google.com/vpc/docs/vpc-peering) (used here to connect the trusted landing VPC with the spokes, also used by [02-networking-vpn](../2-networking-b-vpn/)) - Pros: no additional costs, full bandwidth with no configurations, no extra latency - Cons: no transitivity (e.g. to GKE masters, Cloud SQL, etc.), no selective exchange of routes, several quotas and limits shared between VPCs in a peering group - [Multi-NIC appliances](https://cloud.google.com/architecture/best-practices-vpc-design#multi-nic) (used here to connect the trusted landing and untrusted VPCs) @@ -192,7 +192,7 @@ This configuration is battle-tested, and flexible enough to lend itself to simpl ## How to run this stage -This stage is meant to be executed after the [resman](../01-resman) stage has run. It leverages the automation service account and the storage bucket created there, and additional resources configured in the [bootstrap](../00-bootstrap) stage. +This stage is meant to be executed after the [resman](../1-resman) stage has run. It leverages the automation service account and the storage bucket created there, and additional resources configured in the [bootstrap](../0-bootstrap) stage. It's possible to run this stage in isolation, but that's outside of the scope of this document. Please, refer to the previous stages for the environment requirements. @@ -200,7 +200,7 @@ Before running this stage, you need to make sure you have the correct credential ### Providers configuration -The default way of making sure you have the right permissions, is to use the identity of the service account pre-created for this stage, during the [resource management](../01-resman) stage, and that you are a member of the group that can impersonate it via provider-level configuration (`gcp-devops` or `organization-admins`). +The default way of making sure you have the right permissions, is to use the identity of the service account pre-created for this stage, during the [resource management](../1-resman) stage, and that you are a member of the group that can impersonate it via provider-level configuration (`gcp-devops` or `organization-admins`). To simplify the setup, the previous stage pre-configures a valid providers file in its output and optionally writes it to a local file if the `outputs_location` variable is set to a valid path. @@ -214,9 +214,9 @@ ln -s ~/fast-config/providers/02-networking-providers.tf . If you have not configured `outputs_location` in bootstrap, you can derive the providers file from that stage outputs: ```bash -cd ../01-resman +cd ../1-resman terraform output -json providers | jq -r '.["02-networking"]' \ - > ../02-networking-nva/providers.tf + > ../2-networking-c-nva/providers.tf ``` ### Variable configuration @@ -339,9 +339,6 @@ The new VPC requires a set of dedicated CIDRs, one per region, added to variable > Variables managing L7 Internal Load Balancers (`l7ilb_subnets`) and Private Service Access (`psa_ranges`) should also be adapted, and subnets and firewall rules for the new spoke should be added, as described above. -VPC network peering connectivity to the `trusted landing VPC` is managed by the `vpc-peering-*.tf` files. -Copy `vpc-peering-prod.tf` to `vpc-peering-staging.tf` and replace "prod" with "staging", where relevant. - Configure the NVAs deployed or update the sample [NVA config file](data/nva-startup-script.tftpl) making sure they support the new subnets. DNS configurations are centralised in the `dns-*.tf` files. Spokes delegate DNS resolution to Landing through DNS peering, and optionally define a private zone (e.g. `dev.gcp.example.com`) which the landing peers to. To configure DNS for a new environment, copy one of the other environments DNS files [e.g. (dns-dev.tf)](dns-dev.tf) into a new `dns-*.tf` file suffixed with the environment name (e.g. `dns-staging.tf`), and update its content accordingly. Don't forget to add a peering zone from the landing to the newly created environment private zone. @@ -361,6 +358,7 @@ DNS configurations are centralised in the `dns-*.tf` files. Spokes delegate DNS | [monitoring.tf](./monitoring.tf) | Network monitoring dashboards. | | google_monitoring_dashboard | | [nva.tf](./nva.tf) | None | compute-mig · compute-vm · simple-nva | | | [outputs.tf](./outputs.tf) | Module outputs. | | google_storage_bucket_object · local_file | +| [regions.tf](./regions.tf) | Compute short names for regions. | | | | [spoke-dev.tf](./spoke-dev.tf) | Dev spoke VPC and related resources. | net-vpc · net-vpc-firewall · net-vpc-peering · project | google_project_iam_binding | | [spoke-prod.tf](./spoke-prod.tf) | Production spoke VPC and related resources. | net-vpc · net-vpc-firewall · net-vpc-peering · project | google_project_iam_binding | | [test-resources.tf](./test-resources.tf) | temporary instances for testing | compute-vm | | @@ -371,23 +369,23 @@ DNS configurations are centralised in the `dns-*.tf` files. Spokes delegate DNS | name | description | type | required | default | producer | |---|---|:---:|:---:|:---:|:---:| -| [automation](variables.tf#L17) | Automation resources created by the bootstrap stage. | object({…}) | ✓ | | 00-bootstrap | -| [billing_account](variables.tf#L25) | Billing account id and organization id ('nnnnnnnn' or null). | object({…}) | ✓ | | 00-bootstrap | -| [folder_ids](variables.tf#L79) | Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created. | object({…}) | ✓ | | 01-resman | -| [organization](variables.tf#L115) | Organization details. | object({…}) | ✓ | | 00-bootstrap | -| [prefix](variables.tf#L131) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 00-bootstrap | -| [custom_adv](variables.tf#L34) | Custom advertisement definitions in name => range format. | map(string) | | {…} | | -| [custom_roles](variables.tf#L56) | Custom roles defined at the org level, in key => id format. | object({…}) | | null | 00-bootstrap | -| [data_dir](variables.tf#L65) | Relative path for the folder storing configuration data for network resources. | string | | "data" | | -| [dns](variables.tf#L71) | Onprem DNS resolvers. | map(list(string)) | | {…} | | -| [l7ilb_subnets](variables.tf#L89) | Subnets used for L7 ILBs. | map(list(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). | object({…}) | | null | | -| [region_trigram](variables.tf#L183) | Short names for GCP regions. | map(string) | | {…} | | -| [router_configs](variables.tf#L192) | Configurations for CRs and onprem routers. | map(object({…})) | | {…} | | -| [service_accounts](variables.tf#L215) | Automation service accounts in name => email format. | object({…}) | | null | 01-resman | -| [vpn_onprem_configs](variables.tf#L229) | VPN gateway configuration for onprem interconnection. | map(object({…})) | | {…} | | +| [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 | +| [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({…})) | | {…} | | ## Outputs diff --git a/fast/stages/02-networking-separate-envs/data/cidrs.yaml b/fast/stages/2-networking-c-nva/data/cidrs.yaml similarity index 100% rename from fast/stages/02-networking-separate-envs/data/cidrs.yaml rename to fast/stages/2-networking-c-nva/data/cidrs.yaml diff --git a/fast/stages/02-networking-nva/data/dashboards/firewall_insights.json b/fast/stages/2-networking-c-nva/data/dashboards/firewall_insights.json similarity index 100% rename from fast/stages/02-networking-nva/data/dashboards/firewall_insights.json rename to fast/stages/2-networking-c-nva/data/dashboards/firewall_insights.json diff --git a/fast/stages/02-networking-nva/data/dashboards/vpn.json b/fast/stages/2-networking-c-nva/data/dashboards/vpn.json similarity index 100% rename from fast/stages/02-networking-nva/data/dashboards/vpn.json rename to fast/stages/2-networking-c-nva/data/dashboards/vpn.json diff --git a/fast/stages/02-networking-vpn/data/firewall-rules/dev/rules.yaml b/fast/stages/2-networking-c-nva/data/firewall-rules/dev/rules.yaml similarity index 100% rename from fast/stages/02-networking-vpn/data/firewall-rules/dev/rules.yaml rename to fast/stages/2-networking-c-nva/data/firewall-rules/dev/rules.yaml diff --git a/fast/stages/02-networking-nva/data/firewall-rules/landing-trusted/rules.yaml b/fast/stages/2-networking-c-nva/data/firewall-rules/landing-trusted/rules.yaml similarity index 100% rename from fast/stages/02-networking-nva/data/firewall-rules/landing-trusted/rules.yaml rename to fast/stages/2-networking-c-nva/data/firewall-rules/landing-trusted/rules.yaml diff --git a/fast/stages/02-networking-nva/data/firewall-rules/landing-untrusted/rules.yaml b/fast/stages/2-networking-c-nva/data/firewall-rules/landing-untrusted/rules.yaml similarity index 100% rename from fast/stages/02-networking-nva/data/firewall-rules/landing-untrusted/rules.yaml rename to fast/stages/2-networking-c-nva/data/firewall-rules/landing-untrusted/rules.yaml diff --git a/fast/stages/02-networking-separate-envs/data/hierarchical-policy-rules.yaml b/fast/stages/2-networking-c-nva/data/hierarchical-policy-rules.yaml similarity index 100% rename from fast/stages/02-networking-separate-envs/data/hierarchical-policy-rules.yaml rename to fast/stages/2-networking-c-nva/data/hierarchical-policy-rules.yaml diff --git a/fast/stages/02-networking-nva/data/nva-startup-script.tftpl b/fast/stages/2-networking-c-nva/data/nva-startup-script.tftpl similarity index 100% rename from fast/stages/02-networking-nva/data/nva-startup-script.tftpl rename to fast/stages/2-networking-c-nva/data/nva-startup-script.tftpl diff --git a/fast/stages/02-networking-nva/data/subnets/dev/dev-dataplatform-ew1.yaml b/fast/stages/2-networking-c-nva/data/subnets/dev/dev-dataplatform-ew1.yaml similarity index 100% rename from fast/stages/02-networking-nva/data/subnets/dev/dev-dataplatform-ew1.yaml rename to fast/stages/2-networking-c-nva/data/subnets/dev/dev-dataplatform-ew1.yaml diff --git a/fast/stages/02-networking-nva/data/subnets/dev/dev-default-ew1.yaml b/fast/stages/2-networking-c-nva/data/subnets/dev/dev-default-ew1.yaml similarity index 100% rename from fast/stages/02-networking-nva/data/subnets/dev/dev-default-ew1.yaml rename to fast/stages/2-networking-c-nva/data/subnets/dev/dev-default-ew1.yaml diff --git a/fast/stages/02-networking-nva/data/subnets/dev/dev-default-ew4.yaml b/fast/stages/2-networking-c-nva/data/subnets/dev/dev-default-ew4.yaml similarity index 100% rename from fast/stages/02-networking-nva/data/subnets/dev/dev-default-ew4.yaml rename to fast/stages/2-networking-c-nva/data/subnets/dev/dev-default-ew4.yaml diff --git a/fast/stages/02-networking-nva/data/subnets/landing-trusted/landing-trusted-default-ew1.yaml b/fast/stages/2-networking-c-nva/data/subnets/landing-trusted/landing-trusted-default-ew1.yaml similarity index 100% rename from fast/stages/02-networking-nva/data/subnets/landing-trusted/landing-trusted-default-ew1.yaml rename to fast/stages/2-networking-c-nva/data/subnets/landing-trusted/landing-trusted-default-ew1.yaml diff --git a/fast/stages/02-networking-nva/data/subnets/landing-trusted/landing-trusted-default-ew4.yaml b/fast/stages/2-networking-c-nva/data/subnets/landing-trusted/landing-trusted-default-ew4.yaml similarity index 100% rename from fast/stages/02-networking-nva/data/subnets/landing-trusted/landing-trusted-default-ew4.yaml rename to fast/stages/2-networking-c-nva/data/subnets/landing-trusted/landing-trusted-default-ew4.yaml diff --git a/fast/stages/02-networking-nva/data/subnets/landing-untrusted/landing-untrusted-default-ew1.yaml b/fast/stages/2-networking-c-nva/data/subnets/landing-untrusted/landing-untrusted-default-ew1.yaml similarity index 100% rename from fast/stages/02-networking-nva/data/subnets/landing-untrusted/landing-untrusted-default-ew1.yaml rename to fast/stages/2-networking-c-nva/data/subnets/landing-untrusted/landing-untrusted-default-ew1.yaml diff --git a/fast/stages/02-networking-nva/data/subnets/landing-untrusted/landing-untrusted-default-ew4.yaml b/fast/stages/2-networking-c-nva/data/subnets/landing-untrusted/landing-untrusted-default-ew4.yaml similarity index 100% rename from fast/stages/02-networking-nva/data/subnets/landing-untrusted/landing-untrusted-default-ew4.yaml rename to fast/stages/2-networking-c-nva/data/subnets/landing-untrusted/landing-untrusted-default-ew4.yaml diff --git a/fast/stages/02-networking-nva/data/subnets/prod/prod-default-ew1.yaml b/fast/stages/2-networking-c-nva/data/subnets/prod/prod-default-ew1.yaml similarity index 100% rename from fast/stages/02-networking-nva/data/subnets/prod/prod-default-ew1.yaml rename to fast/stages/2-networking-c-nva/data/subnets/prod/prod-default-ew1.yaml diff --git a/fast/stages/02-networking-nva/data/subnets/prod/prod-default-ew4.yaml b/fast/stages/2-networking-c-nva/data/subnets/prod/prod-default-ew4.yaml similarity index 100% rename from fast/stages/02-networking-nva/data/subnets/prod/prod-default-ew4.yaml rename to fast/stages/2-networking-c-nva/data/subnets/prod/prod-default-ew4.yaml diff --git a/fast/stages/02-networking-nva/diagram.png b/fast/stages/2-networking-c-nva/diagram.png similarity index 100% rename from fast/stages/02-networking-nva/diagram.png rename to fast/stages/2-networking-c-nva/diagram.png diff --git a/fast/stages/02-networking-nva/diagram.svg b/fast/stages/2-networking-c-nva/diagram.svg similarity index 100% rename from fast/stages/02-networking-nva/diagram.svg rename to fast/stages/2-networking-c-nva/diagram.svg diff --git a/fast/stages/02-networking-nva/dns-dev.tf b/fast/stages/2-networking-c-nva/dns-dev.tf similarity index 100% rename from fast/stages/02-networking-nva/dns-dev.tf rename to fast/stages/2-networking-c-nva/dns-dev.tf diff --git a/fast/stages/02-networking-nva/dns-landing.tf b/fast/stages/2-networking-c-nva/dns-landing.tf similarity index 100% rename from fast/stages/02-networking-nva/dns-landing.tf rename to fast/stages/2-networking-c-nva/dns-landing.tf diff --git a/fast/stages/02-networking-nva/dns-prod.tf b/fast/stages/2-networking-c-nva/dns-prod.tf similarity index 100% rename from fast/stages/02-networking-nva/dns-prod.tf rename to fast/stages/2-networking-c-nva/dns-prod.tf diff --git a/fast/stages/02-networking-nva/landing.tf b/fast/stages/2-networking-c-nva/landing.tf similarity index 76% rename from fast/stages/02-networking-nva/landing.tf rename to fast/stages/2-networking-c-nva/landing.tf index 5a990030..e66b03db 100644 --- a/fast/stages/02-networking-nva/landing.tf +++ b/fast/stages/2-networking-c-nva/landing.tf @@ -53,7 +53,7 @@ module "landing-untrusted-vpc" { inbound = false logging = false } - data_folder = "${var.data_dir}/subnets/landing-untrusted" + data_folder = "${var.factories_config.data_dir}/subnets/landing-untrusted" } module "landing-untrusted-firewall" { @@ -64,31 +64,41 @@ module "landing-untrusted-firewall" { disabled = true } factories_config = { - cidr_tpl_file = "${var.data_dir}/cidrs.yaml" - rules_folder = "${var.data_dir}/firewall-rules/landing-untrusted" + cidr_tpl_file = "${var.factories_config.data_dir}/cidrs.yaml" + rules_folder = "${var.factories_config.data_dir}/firewall-rules/landing-untrusted" } } # NAT -module "landing-nat-ew1" { +moved { + from = module.landing-nat-ew1 + to = module.landing-nat-primary +} + +module "landing-nat-primary" { source = "../../../modules/net-cloudnat" project_id = module.landing-project.project_id - region = "europe-west1" - name = "ew1" + region = var.regions.primary + name = local.region_shortnames[var.regions.primary] router_create = true - router_name = "prod-nat-ew1" + router_name = "prod-nat-${local.region_shortnames[var.regions.primary]}" router_network = module.landing-untrusted-vpc.name router_asn = 4200001024 } -module "landing-nat-ew4" { +moved { + from = module.landing-nat-ew4 + to = module.landing-nat-secondary +} + +module "landing-nat-secondary" { source = "../../../modules/net-cloudnat" project_id = module.landing-project.project_id - region = "europe-west4" - name = "ew4" + region = var.regions.secondary + name = local.region_shortnames[var.regions.secondary] router_create = true - router_name = "prod-nat-ew4" + router_name = "prod-nat-${local.region_shortnames[var.regions.secondary]}" router_network = module.landing-untrusted-vpc.name router_asn = 4200001024 } @@ -101,7 +111,10 @@ module "landing-trusted-vpc" { name = "prod-trusted-landing-0" delete_default_routes_on_create = true mtu = 1500 - + data_folder = "${var.factories_config.data_dir}/subnets/landing-trusted" + dns_policy = { + inbound = true + } # Set explicit routes for googleapis in case the default route is deleted routes = { private-googleapis = { @@ -115,12 +128,6 @@ module "landing-trusted-vpc" { next_hop = "default-internet-gateway" } } - - dns_policy = { - inbound = true - } - - data_folder = "${var.data_dir}/subnets/landing-trusted" } module "landing-trusted-firewall" { @@ -131,7 +138,7 @@ module "landing-trusted-firewall" { disabled = true } factories_config = { - cidr_tpl_file = "${var.data_dir}/cidrs.yaml" - rules_folder = "${var.data_dir}/firewall-rules/landing-trusted" + cidr_tpl_file = "${var.factories_config.data_dir}/cidrs.yaml" + rules_folder = "${var.factories_config.data_dir}/firewall-rules/landing-trusted" } } diff --git a/fast/stages/02-networking-nva/main.tf b/fast/stages/2-networking-c-nva/main.tf similarity index 73% rename from fast/stages/02-networking-nva/main.tf rename to fast/stages/2-networking-c-nva/main.tf index 4db5061b..e4066ba3 100644 --- a/fast/stages/02-networking-nva/main.tf +++ b/fast/stages/2-networking-c-nva/main.tf @@ -24,6 +24,14 @@ locals { name = "${env}-l7ilb-${s.region}" })] } + # combine all regions from variables and subnets + regions = distinct(concat( + values(var.regions), + values(module.dev-spoke-vpc.subnet_regions), + values(module.landing-trusted-vpc.subnet_regions), + values(module.landing-untrusted-vpc.subnet_regions), + values(module.prod-spoke-vpc.subnet_regions), + )) service_accounts = { for k, v in coalesce(var.service_accounts, {}) : k => "serviceAccount:${v}" if v != null @@ -45,11 +53,11 @@ module "folder" { folder_create = var.folder_ids.networking == null id = var.folder_ids.networking firewall_policy_factory = { - cidr_file = "${var.data_dir}/cidrs.yaml" - policy_name = null - rules_file = "${var.data_dir}/hierarchical-policy-rules.yaml" + cidr_file = "${var.factories_config.data_dir}/cidrs.yaml" + policy_name = var.factories_config.firewall_policy_name + rules_file = "${var.factories_config.data_dir}/hierarchical-policy-rules.yaml" } firewall_policy_association = { - factory-policy = "factory" + factory-policy = var.factories_config.firewall_policy_name } } diff --git a/fast/stages/02-networking-peering/monitoring.tf b/fast/stages/2-networking-c-nva/monitoring.tf similarity index 93% rename from fast/stages/02-networking-peering/monitoring.tf rename to fast/stages/2-networking-c-nva/monitoring.tf index 7b8b70c5..be3a47fa 100644 --- a/fast/stages/02-networking-peering/monitoring.tf +++ b/fast/stages/2-networking-c-nva/monitoring.tf @@ -17,7 +17,7 @@ # tfdoc:file:description Network monitoring dashboards. locals { - dashboard_path = "${var.data_dir}/dashboards" + dashboard_path = "${var.factories_config.data_dir}/dashboards" dashboard_files = fileset(local.dashboard_path, "*.json") dashboards = { for filename in local.dashboard_files : diff --git a/fast/stages/02-networking-nva/nva.tf b/fast/stages/2-networking-c-nva/nva.tf similarity index 66% rename from fast/stages/02-networking-nva/nva.tf rename to fast/stages/2-networking-c-nva/nva.tf index f4f7b9e5..7ae9c30c 100644 --- a/fast/stages/02-networking-nva/nva.tf +++ b/fast/stages/2-networking-c-nva/nva.tf @@ -21,29 +21,32 @@ locals { { name = "untrusted" routes = [ - var.custom_adv.gcp_landing_untrusted_ew1, - var.custom_adv.gcp_landing_untrusted_ew4, + var.custom_adv.gcp_landing_untrusted_primary, + var.custom_adv.gcp_landing_untrusted_secondary, ] }, { name = "trusted" routes = [ - var.custom_adv.gcp_dev_ew1, - var.custom_adv.gcp_dev_ew4, - var.custom_adv.gcp_landing_trusted_ew1, - var.custom_adv.gcp_landing_trusted_ew4, - var.custom_adv.gcp_prod_ew1, - var.custom_adv.gcp_prod_ew4, + var.custom_adv.gcp_dev_primary, + var.custom_adv.gcp_dev_secondary, + var.custom_adv.gcp_landing_trusted_primary, + var.custom_adv.gcp_landing_trusted_secondary, + var.custom_adv.gcp_prod_primary, + var.custom_adv.gcp_prod_secondary, ] }, ] nva_locality = { - europe-west1-b = { region = "europe-west1", trigram = "ew1", zone = "b" }, - europe-west1-c = { region = "europe-west1", trigram = "ew1", zone = "c" }, - europe-west4-b = { region = "europe-west4", trigram = "ew4", zone = "b" }, - europe-west4-c = { region = "europe-west4", trigram = "ew4", zone = "c" }, + for v in setproduct(keys(var.regions), local.nva_zones) : + join("-", v) => { + name = v.0 + region = var.regions[v.0] + shortname = local.region_shortnames[var.regions[v.0]] + zone = v.1 + } } - + nva_zones = ["b", "c"] } # NVA config @@ -57,7 +60,7 @@ module "nva-template" { for_each = local.nva_locality source = "../../../modules/compute-vm" project_id = module.landing-project.project_id - name = "nva-template-${each.value.trigram}-${each.value.zone}" + name = "nva-template-${each.key}" zone = "${each.value.region}-${each.value.zone}" instance_type = "e2-standard-2" tags = ["nva"] @@ -66,13 +69,13 @@ module "nva-template" { network_interfaces = [ { network = module.landing-untrusted-vpc.self_link - subnetwork = module.landing-untrusted-vpc.subnet_self_links["${each.value.region}/landing-untrusted-default-${each.value.trigram}"] + subnetwork = module.landing-untrusted-vpc.subnet_self_links["${each.value.region}/landing-untrusted-default-${each.value.shortname}"] nat = false addresses = null }, { network = module.landing-trusted-vpc.self_link - subnetwork = module.landing-trusted-vpc.subnet_self_links["${each.value.region}/landing-trusted-default-${each.value.trigram}"] + subnetwork = module.landing-trusted-vpc.subnet_self_links["${each.value.region}/landing-trusted-default-${each.value.shortname}"] nat = false addresses = null } @@ -98,7 +101,7 @@ module "nva-mig" { source = "../../../modules/compute-mig" project_id = module.landing-project.project_id location = each.value.region - name = "nva-cos-${each.value.trigram}-${each.value.zone}" + name = "nva-cos-${each.key}" instance_template = module.nva-template[each.key].template.self_link target_size = 1 auto_healing_policies = { @@ -113,21 +116,27 @@ module "nva-mig" { } module "ilb-nva-untrusted" { - for_each = { for l in local.nva_locality : l.region => l.trigram... } + for_each = { + for k, v in var.regions : k => { + region = v + shortname = local.region_shortnames[v] + subnet = "${v}/landing-untrusted-default-${local.region_shortnames[v]}" + } + } source = "../../../modules/net-ilb" project_id = module.landing-project.project_id - region = each.key - name = "nva-untrusted-${each.value.0}" + region = each.value.region + name = "nva-untrusted-${each.key}" service_label = var.prefix global_access = true vpc_config = { network = module.landing-untrusted-vpc.self_link - subnetwork = module.landing-untrusted-vpc.subnet_self_links["${each.key}/landing-untrusted-default-${each.value.0}"] + subnetwork = module.landing-untrusted-vpc.subnet_self_links[each.value.subnet] } backends = [ - for key, _ in local.nva_locality : { - group = module.nva-mig[key].group_manager.instance_group - } if local.nva_locality[key].region == each.key + for k, v in module.nva-mig : + { group = v.group_manager.instance_group } + if startswith(k, each.key) ] health_check_config = { enable_logging = true @@ -137,23 +146,28 @@ module "ilb-nva-untrusted" { } } - module "ilb-nva-trusted" { - for_each = { for l in local.nva_locality : l.region => l.trigram... } + for_each = { + for k, v in var.regions : k => { + region = v + shortname = local.region_shortnames[v] + subnet = "${v}/landing-trusted-default-${local.region_shortnames[v]}" + } + } source = "../../../modules/net-ilb" project_id = module.landing-project.project_id - region = each.key - name = "nva-trusted-${each.value.0}" + region = each.value.region + name = "nva-trusted-${each.key}" service_label = var.prefix global_access = true vpc_config = { network = module.landing-trusted-vpc.self_link - subnetwork = module.landing-trusted-vpc.subnet_self_links["${each.key}/landing-trusted-default-${each.value.0}"] + subnetwork = module.landing-trusted-vpc.subnet_self_links[each.value.subnet] } backends = [ - for key, _ in local.nva_locality : { - group = module.nva-mig[key].group_manager.instance_group - } if local.nva_locality[key].region == each.key + for k, v in module.nva-mig : + { group = v.group_manager.instance_group } + if startswith(k, each.key) ] health_check_config = { enable_logging = true @@ -162,4 +176,3 @@ module "ilb-nva-trusted" { } } } - diff --git a/fast/stages/02-networking-nva/outputs.tf b/fast/stages/2-networking-c-nva/outputs.tf similarity index 89% rename from fast/stages/02-networking-nva/outputs.tf rename to fast/stages/2-networking-c-nva/outputs.tf index df324570..74620242 100644 --- a/fast/stages/02-networking-nva/outputs.tf +++ b/fast/stages/2-networking-c-nva/outputs.tf @@ -43,13 +43,13 @@ locals { resource "local_file" "tfvars" { for_each = var.outputs_location == null ? {} : { 1 = 1 } file_permission = "0644" - filename = "${try(pathexpand(var.outputs_location), "")}/tfvars/02-networking.auto.tfvars.json" + filename = "${try(pathexpand(var.outputs_location), "")}/tfvars/2-networking.auto.tfvars.json" content = jsonencode(local.tfvars) } resource "google_storage_bucket_object" "tfvars" { bucket = var.automation.outputs_bucket - name = "tfvars/02-networking.auto.tfvars.json" + name = "tfvars/2-networking.auto.tfvars.json" content = jsonencode(local.tfvars) } @@ -79,12 +79,12 @@ output "tfvars" { output "vpn_gateway_endpoints" { description = "External IP Addresses for the GCP VPN gateways." value = local.enable_onprem_vpn == false ? null : { - onprem-ew1 = { - for v in module.landing-to-onprem-ew1-vpn[0].gateway.vpn_interfaces : + onprem-primary = { + for v in module.landing-to-onprem-primary-vpn[0].gateway.vpn_interfaces : v.id => v.ip_address } - onprem-ew4 = { - for v in module.landing-to-onprem-ew4-vpn[0].gateway.vpn_interfaces : + onprem-secondary = { + for v in module.landing-to-onprem-secondary-vpn[0].gateway.vpn_interfaces : v.id => v.ip_address } } diff --git a/fast/stages/2-networking-c-nva/regions.tf b/fast/stages/2-networking-c-nva/regions.tf new file mode 100644 index 00000000..53514afa --- /dev/null +++ b/fast/stages/2-networking-c-nva/regions.tf @@ -0,0 +1,42 @@ +/** + * 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 Compute short names for regions. + +locals { + # only map when the first character would not work + _region_cardinal = { + southeast = "se" + } + # only map when the first character would not work + _region_geo = { + australia = "o" + } + # split in [geo, cardinal, number] tokens + _region_tokens = { + for v in local.regions : v => regexall("(?:[a-z]+)|(?:[0-9]+)", v) + } + region_shortnames = { + for k, v in local._region_tokens : k => join("", [ + # first token via geo alias map or first character + lookup(local._region_geo, v.0, substr(v.0, 0, 1)), + # first token via cardinal alias map or first character + lookup(local._region_cardinal, v.1, substr(v.1, 0, 1)), + # region number as is + v.2 + ]) + } +} diff --git a/fast/stages/02-networking-nva/spoke-dev.tf b/fast/stages/2-networking-c-nva/spoke-dev.tf similarity index 76% rename from fast/stages/02-networking-nva/spoke-dev.tf rename to fast/stages/2-networking-c-nva/spoke-dev.tf index fb88384c..b2293813 100644 --- a/fast/stages/02-networking-nva/spoke-dev.tf +++ b/fast/stages/2-networking-c-nva/spoke-dev.tf @@ -16,6 +16,19 @@ # 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 @@ -47,10 +60,10 @@ module "dev-spoke-vpc" { project_id = module.dev-spoke-project.project_id name = "dev-spoke-0" mtu = 1500 - data_folder = "${var.data_dir}/subnets/dev" + 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 + subnets_proxy_only = local.l7ilb_subnets_dev # Set explicit routes for googleapis; send everything else to NVAs routes = { private-googleapis = { @@ -65,33 +78,33 @@ module "dev-spoke-vpc" { next_hop_type = "gateway" next_hop = "default-internet-gateway" } - nva-ew1-to-ew1 = { + nva-primary-to-primary = { dest_range = "0.0.0.0/0" priority = 1000 - tags = ["ew1"] + tags = ["primary"] next_hop_type = "ilb" - next_hop = module.ilb-nva-trusted["europe-west1"].forwarding_rule_address + next_hop = module.ilb-nva-trusted["primary"].forwarding_rule_address } - nva-ew4-to-ew4 = { + nva-secondary-to-secondary = { dest_range = "0.0.0.0/0" priority = 1000 - tags = ["ew4"] + tags = ["secondary"] next_hop_type = "ilb" - next_hop = module.ilb-nva-trusted["europe-west4"].forwarding_rule_address + next_hop = module.ilb-nva-trusted["secondary"].forwarding_rule_address } - nva-ew1-to-ew4 = { + nva-primary-to-secondary = { dest_range = "0.0.0.0/0" priority = 1001 - tags = ["ew1"] + tags = ["primary"] next_hop_type = "ilb" - next_hop = module.ilb-nva-trusted["europe-west4"].forwarding_rule_address + next_hop = module.ilb-nva-trusted["primary"].forwarding_rule_address } - nva-ew4-to-ew1 = { + nva-secondary-to-primary = { dest_range = "0.0.0.0/0" priority = 1001 - tags = ["ew4"] + tags = ["secondary"] next_hop_type = "ilb" - next_hop = module.ilb-nva-trusted["europe-west1"].forwarding_rule_address + next_hop = module.ilb-nva-trusted["secondary"].forwarding_rule_address } } } @@ -104,8 +117,8 @@ module "dev-spoke-firewall" { disabled = true } factories_config = { - cidr_tpl_file = "${var.data_dir}/cidrs.yaml" - rules_folder = "${var.data_dir}/firewall-rules/dev" + cidr_tpl_file = "${var.factories_config.data_dir}/cidrs.yaml" + rules_folder = "${var.factories_config.data_dir}/firewall-rules/dev" } } diff --git a/fast/stages/02-networking-nva/spoke-prod.tf b/fast/stages/2-networking-c-nva/spoke-prod.tf similarity index 76% rename from fast/stages/02-networking-nva/spoke-prod.tf rename to fast/stages/2-networking-c-nva/spoke-prod.tf index 484550ac..51ad2974 100644 --- a/fast/stages/02-networking-nva/spoke-prod.tf +++ b/fast/stages/2-networking-c-nva/spoke-prod.tf @@ -16,6 +16,19 @@ # 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 @@ -47,10 +60,10 @@ module "prod-spoke-vpc" { project_id = module.prod-spoke-project.project_id name = "prod-spoke-0" mtu = 1500 - data_folder = "${var.data_dir}/subnets/prod" + 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 + subnets_proxy_only = local.l7ilb_subnets_prod # Set explicit routes for googleapis; send everything else to NVAs routes = { private-googleapis = { @@ -65,33 +78,33 @@ module "prod-spoke-vpc" { next_hop_type = "gateway" next_hop = "default-internet-gateway" } - nva-ew1-to-ew1 = { + nva-primary-to-primary = { dest_range = "0.0.0.0/0" priority = 1000 - tags = ["ew1"] + tags = ["primary"] next_hop_type = "ilb" - next_hop = module.ilb-nva-trusted["europe-west1"].forwarding_rule_address + next_hop = module.ilb-nva-trusted["primary"].forwarding_rule_address } - nva-ew4-to-ew4 = { + nva-secondary-to-secondary = { dest_range = "0.0.0.0/0" priority = 1000 - tags = ["ew4"] + tags = ["secondary"] next_hop_type = "ilb" - next_hop = module.ilb-nva-trusted["europe-west4"].forwarding_rule_address + next_hop = module.ilb-nva-trusted["secondary"].forwarding_rule_address } - nva-ew1-to-ew4 = { + nva-primary-to-secondary = { dest_range = "0.0.0.0/0" priority = 1001 - tags = ["ew1"] + tags = ["primary"] next_hop_type = "ilb" - next_hop = module.ilb-nva-trusted["europe-west4"].forwarding_rule_address + next_hop = module.ilb-nva-trusted["secondary"].forwarding_rule_address } - nva-ew4-to-ew1 = { + nva-secondary-to-primary = { dest_range = "0.0.0.0/0" priority = 1001 - tags = ["ew4"] + tags = ["secondary"] next_hop_type = "ilb" - next_hop = module.ilb-nva-trusted["europe-west1"].forwarding_rule_address + next_hop = module.ilb-nva-trusted["primary"].forwarding_rule_address } } } @@ -104,8 +117,8 @@ module "prod-spoke-firewall" { disabled = true } factories_config = { - cidr_tpl_file = "${var.data_dir}/cidrs.yaml" - rules_folder = "${var.data_dir}/firewall-rules/prod" + cidr_tpl_file = "${var.factories_config.data_dir}/cidrs.yaml" + rules_folder = "${var.factories_config.data_dir}/firewall-rules/prod" } } diff --git a/fast/stages/02-networking-nva/test-resources.tf b/fast/stages/2-networking-c-nva/test-resources.tf similarity index 63% rename from fast/stages/02-networking-nva/test-resources.tf rename to fast/stages/2-networking-c-nva/test-resources.tf index d3b6109e..14676e81 100644 --- a/fast/stages/02-networking-nva/test-resources.tf +++ b/fast/stages/2-networking-c-nva/test-resources.tf @@ -18,23 +18,23 @@ # # Untrusted (Landing) -# module "test-vm-landing-untrusted-ew1-0" { +# module "test-vm-landing-untrusted-primary-0" { # source = "../../../modules/compute-vm" # project_id = module.landing-project.project_id -# zone = "europe-west1-b" -# name = "test-vm-lnd-unt-ew1-0" +# zone = "${var.regions.primary}-b" +# name = "test-vm-lnd-unt-primary-0" # network_interfaces = [{ # network = module.landing-untrusted-vpc.self_link -# subnetwork = module.landing-untrusted-vpc.subnet_self_links["europe-west1/landing-untrusted-default-ew1"] +# subnetwork = module.landing-untrusted-vpc.subnet_self_links["${var.regions.primary}/landing-untrusted-default-${local.region_shortnames[var.regions.primary]}"] # }] -# tags = ["ew1", "ssh"] +# tags = ["primary", "ssh"] # service_account_create = true # boot_disk = { # image = "projects/debian-cloud/global/images/family/debian-10" # } # options = { -# spot = true -# termination_action = "STOP" +# spot = true +# termination_action = "STOP" # } # metadata = { # startup-script = < v.adv == null ? null : { - advertise_groups = [] - advertise_ip_ranges = { - for adv in(v.adv == null ? [] : v.adv.custom) : - var.custom_adv[adv] => adv + custom_advertise = try(v.adv.default, false) ? null : { + all_subnets = false + all_vpc_subnets = false + all_peer_vpc_subnets = false + ip_ranges = { + for adv in(v.adv == null ? [] : v.adv.custom) : + var.custom_adv[adv] => adv + } } - advertise_mode = try(v.adv.default, false) ? "DEFAULT" : "CUSTOM" route_priority = null } } } -module "landing-to-onprem-ew1-vpn" { +moved { + from = module.landing-to-onprem-ew1-vpn + to = module.landing-to-onprem-primary-vpn +} + +module "landing-to-onprem-primary-vpn" { count = local.enable_onprem_vpn ? 1 : 0 source = "../../../modules/net-vpn-ha" project_id = module.landing-project.project_id network = module.landing-trusted-vpc.self_link - region = "europe-west1" - name = "vpn-to-onprem-ew1" + region = var.regions.primary + name = "vpn-to-onprem-${local.region_shortnames[var.regions.primary]}" router_config = { - name = "landing-onprem-vpn-ew1" - asn = var.router_configs.landing-trusted-ew1.asn + name = "landing-onprem-vpn-${local.region_shortnames[var.regions.primary]}" + asn = var.router_configs.landing-trusted-primary.asn } peer_gateway = { - external = var.vpn_onprem_configs.landing-trusted-ew1.peer_external_gateway + external = var.vpn_onprem_configs.landing-trusted-primary.peer_external_gateway } tunnels = { - for t in var.vpn_onprem_configs.landing-trusted-ew1.tunnels : + for t in var.vpn_onprem_configs.landing-trusted-primary.tunnels : "remote-${t.vpn_gateway_interface}-${t.peer_external_gateway_interface}" => { - bgp_peer = { - address = cidrhost(t.session_range, 1) - asn = t.peer_asn - } - bgp_peer_options = local.bgp_peer_options_onprem.landing-trusted-ew1 + bgp_peer = merge( + { address = cidrhost(t.session_range, 1), asn = t.peer_asn }, + local.bgp_peer_options_onprem.landing-trusted-primary + ) bgp_session_range = "${cidrhost(t.session_range, 2)}/30" peer_external_gateway_interface = t.peer_external_gateway_interface shared_secret = t.secret @@ -62,28 +69,32 @@ module "landing-to-onprem-ew1-vpn" { } } -module "landing-to-onprem-ew4-vpn" { +moved { + from = module.landing-to-onprem-ew4-vpn + to = module.landing-to-onprem-secondary-vpn +} + +module "landing-to-onprem-secondary-vpn" { count = local.enable_onprem_vpn ? 1 : 0 source = "../../../modules/net-vpn-ha" project_id = module.landing-project.project_id network = module.landing-trusted-vpc.self_link - region = "europe-west4" - name = "vpn-to-onprem-ew4" + region = var.regions.secondary + name = "vpn-to-onprem-${local.region_shortnames[var.regions.secondary]}" router_config = { - name = "landing-onprem-vpn-ew4" - asn = var.router_configs.landing-trusted-ew4.asn + name = "landing-onprem-vpn-${local.region_shortnames[var.regions.secondary]}" + asn = var.router_configs.landing-trusted-secondary.asn } peer_gateway = { - external = var.vpn_onprem_configs.landing-trusted-ew4.peer_external_gateway + external = var.vpn_onprem_configs.landing-trusted-secondary.peer_external_gateway } tunnels = { - for t in var.vpn_onprem_configs.landing-trusted-ew4.tunnels : + for t in var.vpn_onprem_configs.landing-trusted-secondary.tunnels : "remote-${t.vpn_gateway_interface}-${t.peer_external_gateway_interface}" => { - bgp_peer = { - address = cidrhost(t.session_range, 1) - asn = t.peer_asn - } - bgp_peer_options = local.bgp_peer_options_onprem.landing-trusted-ew4 + bgp_peer = merge( + { address = cidrhost(t.session_range, 1), asn = t.peer_asn }, + local.bgp_peer_options_onprem.landing-trusted-secondary + ) bgp_session_range = "${cidrhost(t.session_range, 2)}/30" peer_external_gateway_interface = t.peer_external_gateway_interface shared_secret = t.secret diff --git a/fast/stages/02-networking-vpn/.gitignore b/fast/stages/2-networking-d-separate-envs/.gitignore similarity index 100% rename from fast/stages/02-networking-vpn/.gitignore rename to fast/stages/2-networking-d-separate-envs/.gitignore diff --git a/fast/stages/02-networking-vpn/IAM.md b/fast/stages/2-networking-d-separate-envs/IAM.md similarity index 100% rename from fast/stages/02-networking-vpn/IAM.md rename to fast/stages/2-networking-d-separate-envs/IAM.md diff --git a/fast/stages/02-networking-separate-envs/README.md b/fast/stages/2-networking-d-separate-envs/README.md similarity index 78% rename from fast/stages/02-networking-separate-envs/README.md rename to fast/stages/2-networking-d-separate-envs/README.md index 66b31646..a461dc97 100644 --- a/fast/stages/02-networking-separate-envs/README.md +++ b/fast/stages/2-networking-d-separate-envs/README.md @@ -89,7 +89,7 @@ This configuration is battle-tested, and flexible enough to lend itself to simpl ## How to run this stage -This stage is meant to be executed after the [resman](../01-resman) stage has run, as it leverages the automation service account and bucket created there, and additional resources configured in the [bootstrap](../00-bootstrap) stage. +This stage is meant to be executed after the [resman](../1-resman) stage has run, as it leverages the automation service account and bucket created there, and additional resources configured in the [bootstrap](../0-bootstrap) stage. It's of course possible to run this stage in isolation, but that's outside the scope of this document, and you would need to refer to the code for the previous stages for the environmental requirements. @@ -97,7 +97,7 @@ Before running this stage, you need to make sure you have the correct credential ### Providers configuration -The default way of making sure you have the right permissions, is to use the identity of the service account pre-created for this stage during the [resource management](../01-resman) stage, and that you are a member of the group that can impersonate it via provider-level configuration (`gcp-devops` or `organization-admins`). +The default way of making sure you have the right permissions, is to use the identity of the service account pre-created for this stage during the [resource management](../1-resman) stage, and that you are a member of the group that can impersonate it via provider-level configuration (`gcp-devops` or `organization-admins`). To simplify setup, the previous stage pre-configures a valid providers file in its output, and optionally writes it to a local file if the `outputs_location` variable is set to a valid path. @@ -111,7 +111,7 @@ ln -s ~/fast-config/providers/02-networking-providers.tf . If you have not configured `outputs_location` in bootstrap, you can derive the providers file from that stage's outputs: ```bash -cd ../01-resman +cd ../1-resman terraform output -json providers | jq -r '.["02-networking"]' \ > ../02-networking/providers.tf ``` @@ -132,7 +132,7 @@ If you have set a valid value for `outputs_location` in the bootstrap and in the ln -s ../../configs/example/02-networking/terraform-bootstrap.auto.tfvars.json ln -s ../../configs/example/02-networking/terraform-resman.auto.tfvars.json # also copy the tfvars file used for the bootstrap stage -cp ../00-bootstrap/terraform.tfvars . +cp ../0-bootstrap/terraform.tfvars . ``` A second set of variables is specific to this stage, they are all optional so if you need to customize them, add them to the file copied from bootstrap. @@ -227,6 +227,7 @@ You're now ready to run `terraform init` and `apply`. | [main.tf](./main.tf) | Networking folder and hierarchical policy. | folder | | | [monitoring.tf](./monitoring.tf) | Network monitoring dashboards. | | google_monitoring_dashboard | | [outputs.tf](./outputs.tf) | Module outputs. | | google_storage_bucket_object · local_file | +| [regions.tf](./regions.tf) | Compute short names for regions. | | | | [spoke-dev.tf](./spoke-dev.tf) | Dev spoke VPC and related resources. | net-cloudnat · net-vpc · net-vpc-firewall · project | google_project_iam_binding | | [spoke-prod.tf](./spoke-prod.tf) | Production spoke VPC and related resources. | net-cloudnat · net-vpc · net-vpc-firewall · project | google_project_iam_binding | | [test-resources.tf](./test-resources.tf) | Temporary instances for testing | compute-vm | | @@ -238,21 +239,22 @@ You're now ready to run `terraform init` and `apply`. | name | description | type | required | default | producer | |---|---|:---:|:---:|:---:|:---:| -| [automation](variables.tf#L17) | Automation resources created by the bootstrap stage. | object({…}) | ✓ | | 00-bootstrap | -| [billing_account](variables.tf#L25) | Billing account id and organization id ('nnnnnnnn' or null). | object({…}) | ✓ | | 00-bootstrap | -| [folder_ids](variables.tf#L74) | Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created. | object({…}) | ✓ | | 01-resman | -| [organization](variables.tf#L102) | Organization details. | object({…}) | ✓ | | 00-bootstrap | -| [prefix](variables.tf#L118) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 00-bootstrap | -| [custom_adv](variables.tf#L34) | Custom advertisement definitions in name => range format. | map(string) | | {…} | | -| [custom_roles](variables.tf#L50) | Custom roles defined at the org level, in key => id format. | object({…}) | | null | 00-bootstrap | -| [data_dir](variables.tf#L59) | Relative path for the folder storing configuration data for network resources. | string | | "data" | | -| [dns](variables.tf#L65) | Onprem DNS resolvers. | map(list(string)) | | {…} | | -| [l7ilb_subnets](variables.tf#L84) | Subnets used for L7 ILBs. | map(list(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 | | -| [router_onprem_configs](variables.tf#L166) | Configurations for routers used for onprem connectivity. | map(object({…})) | | {…} | | -| [service_accounts](variables.tf#L189) | Automation service accounts in name => email format. | object({…}) | | null | 01-resman | -| [vpn_onprem_configs](variables.tf#L201) | VPN gateway configuration for onprem interconnection. | map(object({…})) | | {…} | | +| [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 | +| [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 diff --git a/fast/stages/02-networking-vpn/data/cidrs.yaml b/fast/stages/2-networking-d-separate-envs/data/cidrs.yaml similarity index 100% rename from fast/stages/02-networking-vpn/data/cidrs.yaml rename to fast/stages/2-networking-d-separate-envs/data/cidrs.yaml diff --git a/fast/stages/02-networking-vpn/data/dashboards/firewall_insights.json b/fast/stages/2-networking-d-separate-envs/data/dashboards/firewall_insights.json similarity index 100% rename from fast/stages/02-networking-vpn/data/dashboards/firewall_insights.json rename to fast/stages/2-networking-d-separate-envs/data/dashboards/firewall_insights.json diff --git a/fast/stages/02-networking-vpn/data/dashboards/vpn.json b/fast/stages/2-networking-d-separate-envs/data/dashboards/vpn.json similarity index 100% rename from fast/stages/02-networking-vpn/data/dashboards/vpn.json rename to fast/stages/2-networking-d-separate-envs/data/dashboards/vpn.json diff --git a/fast/stages/02-networking-separate-envs/data/firewall-rules/dev/rules.yaml b/fast/stages/2-networking-d-separate-envs/data/firewall-rules/dev/rules.yaml similarity index 100% rename from fast/stages/02-networking-separate-envs/data/firewall-rules/dev/rules.yaml rename to fast/stages/2-networking-d-separate-envs/data/firewall-rules/dev/rules.yaml diff --git a/fast/stages/02-networking-vpn/data/hierarchical-policy-rules.yaml b/fast/stages/2-networking-d-separate-envs/data/hierarchical-policy-rules.yaml similarity index 100% rename from fast/stages/02-networking-vpn/data/hierarchical-policy-rules.yaml rename to fast/stages/2-networking-d-separate-envs/data/hierarchical-policy-rules.yaml diff --git a/fast/stages/02-networking-vpn/data/subnets/dev/dev-dataplatform-ew1.yaml b/fast/stages/2-networking-d-separate-envs/data/subnets/dev/dev-dataplatform-ew1.yaml similarity index 100% rename from fast/stages/02-networking-vpn/data/subnets/dev/dev-dataplatform-ew1.yaml rename to fast/stages/2-networking-d-separate-envs/data/subnets/dev/dev-dataplatform-ew1.yaml diff --git a/fast/stages/02-networking-vpn/data/subnets/dev/dev-default-ew1.yaml b/fast/stages/2-networking-d-separate-envs/data/subnets/dev/dev-default-ew1.yaml similarity index 100% rename from fast/stages/02-networking-vpn/data/subnets/dev/dev-default-ew1.yaml rename to fast/stages/2-networking-d-separate-envs/data/subnets/dev/dev-default-ew1.yaml diff --git a/fast/stages/02-networking-vpn/data/subnets/prod/prod-default-ew1.yaml b/fast/stages/2-networking-d-separate-envs/data/subnets/prod/prod-default-ew1.yaml similarity index 100% rename from fast/stages/02-networking-vpn/data/subnets/prod/prod-default-ew1.yaml rename to fast/stages/2-networking-d-separate-envs/data/subnets/prod/prod-default-ew1.yaml diff --git a/fast/stages/02-networking-separate-envs/diagram.png b/fast/stages/2-networking-d-separate-envs/diagram.png similarity index 100% rename from fast/stages/02-networking-separate-envs/diagram.png rename to fast/stages/2-networking-d-separate-envs/diagram.png diff --git a/fast/stages/02-networking-separate-envs/diagram.svg b/fast/stages/2-networking-d-separate-envs/diagram.svg similarity index 100% rename from fast/stages/02-networking-separate-envs/diagram.svg rename to fast/stages/2-networking-d-separate-envs/diagram.svg diff --git a/fast/stages/02-networking-separate-envs/dns-dev.tf b/fast/stages/2-networking-d-separate-envs/dns-dev.tf similarity index 100% rename from fast/stages/02-networking-separate-envs/dns-dev.tf rename to fast/stages/2-networking-d-separate-envs/dns-dev.tf diff --git a/fast/stages/02-networking-separate-envs/dns-prod.tf b/fast/stages/2-networking-d-separate-envs/dns-prod.tf similarity index 100% rename from fast/stages/02-networking-separate-envs/dns-prod.tf rename to fast/stages/2-networking-d-separate-envs/dns-prod.tf diff --git a/fast/stages/02-networking-separate-envs/main.tf b/fast/stages/2-networking-d-separate-envs/main.tf similarity index 65% rename from fast/stages/02-networking-separate-envs/main.tf rename to fast/stages/2-networking-d-separate-envs/main.tf index 0b901e33..dda1c252 100644 --- a/fast/stages/02-networking-separate-envs/main.tf +++ b/fast/stages/2-networking-d-separate-envs/main.tf @@ -18,17 +18,25 @@ locals { custom_roles = coalesce(var.custom_roles, {}) - l7ilb_subnets = { - for env, v in var.l7ilb_subnets : env => [ + _l7ilb_subnets = { + for k, v in var.l7ilb_subnets : k => [ for s in v : merge(s, { active = true - name = "${env}-l7ilb-${s.region}" + region = lookup(var.regions, s.region, s.region) })] } - region_trigram = { - europe-west1 = "ew1" - europe-west3 = "ew3" + 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), + values(module.dev-spoke-vpc.subnet_regions), + values(module.prod-spoke-vpc.subnet_regions), + )) stage3_sas_delegated_grants = [ "roles/composer.sharedVpcAgent", "roles/compute.networkUser", @@ -47,12 +55,12 @@ module "folder" { folder_create = var.folder_ids.networking == null id = var.folder_ids.networking firewall_policy_factory = { - cidr_file = "${var.data_dir}/cidrs.yaml" - policy_name = null - rules_file = "${var.data_dir}/hierarchical-policy-rules.yaml" + cidr_file = "${var.factories_config.data_dir}/cidrs.yaml" + policy_name = var.factories_config.firewall_policy_name + rules_file = "${var.factories_config.data_dir}/hierarchical-policy-rules.yaml" } firewall_policy_association = { - factory-policy = "factory" + factory-policy = var.factories_config.firewall_policy_name } } diff --git a/fast/stages/02-networking-separate-envs/monitoring.tf b/fast/stages/2-networking-d-separate-envs/monitoring.tf similarity index 94% rename from fast/stages/02-networking-separate-envs/monitoring.tf rename to fast/stages/2-networking-d-separate-envs/monitoring.tf index 463c6a08..01ed0c47 100644 --- a/fast/stages/02-networking-separate-envs/monitoring.tf +++ b/fast/stages/2-networking-d-separate-envs/monitoring.tf @@ -17,7 +17,7 @@ # tfdoc:file:description Network monitoring dashboards. locals { - dashboard_path = "${var.data_dir}/dashboards" + dashboard_path = "${var.factories_config.data_dir}/dashboards" dashboard_files = fileset(local.dashboard_path, "*.json") dashboards = { for filename in local.dashboard_files : diff --git a/fast/stages/02-networking-separate-envs/outputs.tf b/fast/stages/2-networking-d-separate-envs/outputs.tf similarity index 90% rename from fast/stages/02-networking-separate-envs/outputs.tf rename to fast/stages/2-networking-d-separate-envs/outputs.tf index d06d499d..59d70db6 100644 --- a/fast/stages/02-networking-separate-envs/outputs.tf +++ b/fast/stages/2-networking-d-separate-envs/outputs.tf @@ -44,13 +44,13 @@ locals { resource "local_file" "tfvars" { for_each = var.outputs_location == null ? {} : { 1 = 1 } file_permission = "0644" - filename = "${try(pathexpand(var.outputs_location), "")}/tfvars/02-networking.auto.tfvars.json" + filename = "${try(pathexpand(var.outputs_location), "")}/tfvars/2-networking.auto.tfvars.json" content = jsonencode(local.tfvars) } resource "google_storage_bucket_object" "tfvars" { bucket = var.automation.outputs_bucket - name = "tfvars/02-networking.auto.tfvars.json" + name = "tfvars/2-networking.auto.tfvars.json" content = jsonencode(local.tfvars) } @@ -90,12 +90,12 @@ output "tfvars" { output "vpn_gateway_endpoints" { description = "External IP Addresses for the GCP VPN gateways." value = local.enable_onprem_vpn == false ? null : { - dev-onprem-ew1 = { - for v in module.dev-to-onprem-ew1-vpn[0].gateway.vpn_interfaces : + dev-onprem-primary = { + for v in module.dev-to-onprem-primary-vpn[0].gateway.vpn_interfaces : v.id => v.ip_address } - prod-onprem-ew1 = { - for v in module.prod-to-onprem-ew1-vpn[0].gateway.vpn_interfaces : + prod-onprem-primary = { + for v in module.prod-to-onprem-primary-vpn[0].gateway.vpn_interfaces : v.id => v.ip_address } } diff --git a/fast/stages/2-networking-d-separate-envs/regions.tf b/fast/stages/2-networking-d-separate-envs/regions.tf new file mode 100644 index 00000000..53514afa --- /dev/null +++ b/fast/stages/2-networking-d-separate-envs/regions.tf @@ -0,0 +1,42 @@ +/** + * 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 Compute short names for regions. + +locals { + # only map when the first character would not work + _region_cardinal = { + southeast = "se" + } + # only map when the first character would not work + _region_geo = { + australia = "o" + } + # split in [geo, cardinal, number] tokens + _region_tokens = { + for v in local.regions : v => regexall("(?:[a-z]+)|(?:[0-9]+)", v) + } + region_shortnames = { + for k, v in local._region_tokens : k => join("", [ + # first token via geo alias map or first character + lookup(local._region_geo, v.0, substr(v.0, 0, 1)), + # first token via cardinal alias map or first character + lookup(local._region_cardinal, v.1, substr(v.1, 0, 1)), + # region number as is + v.2 + ]) + } +} diff --git a/fast/stages/02-networking-separate-envs/spoke-dev.tf b/fast/stages/2-networking-d-separate-envs/spoke-dev.tf similarity index 92% rename from fast/stages/02-networking-separate-envs/spoke-dev.tf rename to fast/stages/2-networking-d-separate-envs/spoke-dev.tf index ca7d8d46..ecd0b073 100644 --- a/fast/stages/02-networking-separate-envs/spoke-dev.tf +++ b/fast/stages/2-networking-d-separate-envs/spoke-dev.tf @@ -47,7 +47,7 @@ module "dev-spoke-vpc" { project_id = module.dev-spoke-project.project_id name = "dev-spoke-0" mtu = 1500 - data_folder = "${var.data_dir}/subnets/dev" + data_folder = "${var.factories_config.data_dir}/subnets/dev" psa_config = try(var.psa_ranges.dev, null) subnets_proxy_only = local.l7ilb_subnets.dev # set explicit routes for googleapis in case the default route is deleted @@ -73,8 +73,8 @@ module "dev-spoke-firewall" { disabled = true } factories_config = { - cidr_tpl_file = "${var.data_dir}/cidrs.yaml" - rules_folder = "${var.data_dir}/firewall-rules/dev" + cidr_tpl_file = "${var.factories_config.data_dir}/cidrs.yaml" + rules_folder = "${var.factories_config.data_dir}/firewall-rules/dev" } } @@ -83,7 +83,7 @@ module "dev-spoke-cloudnat" { source = "../../../modules/net-cloudnat" project_id = module.dev-spoke-project.project_id region = each.value - name = "dev-nat-${local.region_trigram[each.value]}" + name = "dev-nat-${local.region_shortnames[each.value]}" router_create = true router_network = module.dev-spoke-vpc.name router_asn = 4200001024 diff --git a/fast/stages/02-networking-separate-envs/spoke-prod.tf b/fast/stages/2-networking-d-separate-envs/spoke-prod.tf similarity index 92% rename from fast/stages/02-networking-separate-envs/spoke-prod.tf rename to fast/stages/2-networking-d-separate-envs/spoke-prod.tf index eba530a6..2e95a09e 100644 --- a/fast/stages/02-networking-separate-envs/spoke-prod.tf +++ b/fast/stages/2-networking-d-separate-envs/spoke-prod.tf @@ -47,7 +47,7 @@ module "prod-spoke-vpc" { project_id = module.prod-spoke-project.project_id name = "prod-spoke-0" mtu = 1500 - data_folder = "${var.data_dir}/subnets/prod" + data_folder = "${var.factories_config.data_dir}/subnets/prod" psa_config = try(var.psa_ranges.prod, null) subnets_proxy_only = local.l7ilb_subnets.prod # set explicit routes for googleapis in case the default route is deleted @@ -73,8 +73,8 @@ module "prod-spoke-firewall" { disabled = true } factories_config = { - cidr_tpl_file = "${var.data_dir}/cidrs.yaml" - rules_folder = "${var.data_dir}/firewall-rules/prod" + cidr_tpl_file = "${var.factories_config.data_dir}/cidrs.yaml" + rules_folder = "${var.factories_config.data_dir}/firewall-rules/prod" } } @@ -83,7 +83,7 @@ module "prod-spoke-cloudnat" { source = "../../../modules/net-cloudnat" project_id = module.prod-spoke-project.project_id region = each.value - name = "prod-nat-${local.region_trigram[each.value]}" + name = "prod-nat-${local.region_shortnames[each.value]}" router_create = true router_network = module.prod-spoke-vpc.name router_asn = 4200001024 diff --git a/fast/stages/02-networking-separate-envs/test-resources.tf b/fast/stages/2-networking-d-separate-envs/test-resources.tf similarity index 86% rename from fast/stages/02-networking-separate-envs/test-resources.tf rename to fast/stages/2-networking-d-separate-envs/test-resources.tf index ae3ba90a..55c42c25 100644 --- a/fast/stages/02-networking-separate-envs/test-resources.tf +++ b/fast/stages/2-networking-d-separate-envs/test-resources.tf @@ -19,12 +19,12 @@ # module "test-vm-dev-0" { # source = "../../../modules/compute-vm" # project_id = module.dev-spoke-project.project_id -# zone = "europe-west1-b" +# zone = "${var.regions.primary}-b" # name = "test-vm-0" # network_interfaces = [{ # network = module.dev-spoke-vpc.self_link # # change the subnet name to match the values you are actually using -# subnetwork = module.dev-spoke-vpc.subnet_self_links["europe-west1/dev-default-ew1"] +# subnetwork = module.dev-spoke-vpc.subnet_self_links["${var.regions.primary}/dev-default-${local.region_shortnames[var.regions.primary]}"] # alias_ips = {} # nat = false # addresses = null @@ -52,12 +52,12 @@ # module "test-vm-prod-0" { # source = "../../../modules/compute-vm" # project_id = module.prod-spoke-project.project_id -# zone = "europe-west1-b" +# zone = "${var.regions.primary}-b" # name = "test-vm-0" # network_interfaces = [{ # network = module.prod-spoke-vpc.self_link # # change the subnet name to match the values you are actually using -# subnetwork = module.prod-spoke-vpc.subnet_self_links["europe-west1/prod-default-ew1"] +# subnetwork = module.prod-spoke-vpc.subnet_self_links["${var.regions.primary}/prod-default-${local.region_shortnames[var.regions.primary]}"] # alias_ips = {} # nat = false # addresses = null diff --git a/fast/stages/02-networking-separate-envs/variables.tf b/fast/stages/2-networking-d-separate-envs/variables.tf similarity index 82% rename from fast/stages/02-networking-separate-envs/variables.tf rename to fast/stages/2-networking-d-separate-envs/variables.tf index 019d0b2e..03d56513 100644 --- a/fast/stages/02-networking-separate-envs/variables.tf +++ b/fast/stages/2-networking-d-separate-envs/variables.tf @@ -15,7 +15,7 @@ */ variable "automation" { - # tfdoc:variable:source 00-bootstrap + # tfdoc:variable:source 0-bootstrap description = "Automation resources created by the bootstrap stage." type = object({ outputs_bucket = string @@ -23,12 +23,16 @@ variable "automation" { } variable "billing_account" { - # tfdoc:variable:source 00-bootstrap - description = "Billing account id and organization id ('nnnnnnnn' or null)." + # 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." type = object({ - id = string - organization_id = number + id = string + is_org_level = optional(bool, true) }) + validation { + condition = var.billing_account.is_org_level != null + error_message = "Invalid `null` value for `billing_account.is_org_level`." + } } variable "custom_adv" { @@ -48,7 +52,7 @@ variable "custom_adv" { } variable "custom_roles" { - # tfdoc:variable:source 00-bootstrap + # tfdoc:variable:source 0-bootstrap description = "Custom roles defined at the org level, in key => id format." type = object({ service_project_network_admin = string @@ -56,12 +60,6 @@ variable "custom_roles" { default = null } -variable "data_dir" { - description = "Relative path for the folder storing configuration data for network resources." - type = string - default = "data" -} - variable "dns" { description = "Onprem DNS resolvers." type = map(list(string)) @@ -71,8 +69,28 @@ variable "dns" { } } +variable "factories_config" { + description = "Configuration for network resource factories." + type = object({ + data_dir = optional(string, "data") + firewall_policy_name = optional(string, "factory") + }) + default = { + data_dir = "data" + } + nullable = false + validation { + condition = var.factories_config.data_dir != null + error_message = "Data folder needs to be non-null." + } + validation { + condition = var.factories_config.firewall_policy_name != null + error_message = "Firewall policy name needs to be non-null." + } +} + variable "folder_ids" { - # tfdoc:variable:source 01-resman + # tfdoc:variable:source 1-resman description = "Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created." type = object({ networking = string @@ -90,17 +108,15 @@ variable "l7ilb_subnets" { default = { prod = [ { ip_cidr_range = "10.128.92.0/24", region = "europe-west1" }, - { ip_cidr_range = "10.128.93.0/24", region = "europe-west4" } ] dev = [ { ip_cidr_range = "10.128.60.0/24", region = "europe-west1" }, - { ip_cidr_range = "10.128.61.0/24", region = "europe-west4" } ] } } variable "organization" { - # tfdoc:variable:source 00-bootstrap + # tfdoc:variable:source 0-bootstrap description = "Organization details." type = object({ domain = string @@ -116,7 +132,7 @@ variable "outputs_location" { } variable "prefix" { - # tfdoc:variable:source 00-bootstrap + # tfdoc:variable:source 0-bootstrap description = "Prefix used for resources that need unique names. Use 9 characters or less." type = string @@ -163,6 +179,16 @@ variable "psa_ranges" { # } } +variable "regions" { + description = "Region definitions." + type = object({ + primary = string + }) + default = { + primary = "europe-west1" + } +} + variable "router_onprem_configs" { description = "Configurations for routers used for onprem connectivity." type = map(object({ @@ -173,12 +199,12 @@ variable "router_onprem_configs" { asn = number })) default = { - prod-ew1 = { + prod-primary = { asn = "65533" adv = null # adv = { default = false, custom = [] } } - dev-ew1 = { + dev-primary = { asn = "65534" adv = null # adv = { default = false, custom = [] } @@ -187,7 +213,7 @@ variable "router_onprem_configs" { } variable "service_accounts" { - # tfdoc:variable:source 01-resman + # tfdoc:variable:source 1-resman description = "Automation service accounts in name => email format." type = object({ data-platform-dev = string @@ -218,7 +244,7 @@ variable "vpn_onprem_configs" { })) })) default = { - dev-ew1 = { + dev-primary = { adv = { default = false custom = [ @@ -247,7 +273,7 @@ variable "vpn_onprem_configs" { } ] } - prod-ew1 = { + prod-primary = { adv = { default = false custom = [ diff --git a/fast/stages/02-networking-separate-envs/vpn-onprem-dev.tf b/fast/stages/2-networking-d-separate-envs/vpn-onprem-dev.tf similarity index 78% rename from fast/stages/02-networking-separate-envs/vpn-onprem-dev.tf rename to fast/stages/2-networking-d-separate-envs/vpn-onprem-dev.tf index 1b0ff117..e95cd14f 100644 --- a/fast/stages/02-networking-separate-envs/vpn-onprem-dev.tf +++ b/fast/stages/2-networking-d-separate-envs/vpn-onprem-dev.tf @@ -32,28 +32,33 @@ locals { } } -module "dev-to-onprem-ew1-vpn" { +moved { + from = module.dev-to-onprem-ew1-vpn + to = module.dev-to-onprem-primary-vpn +} + +module "dev-to-onprem-primary-vpn" { count = local.enable_onprem_vpn ? 1 : 0 source = "../../../modules/net-vpn-ha" project_id = module.dev-spoke-project.project_id network = module.dev-spoke-vpc.self_link - region = "europe-west1" - name = "vpn-to-onprem-ew1" + region = var.regions.primary + name = "vpn-to-onprem-${local.region_shortnames[var.regions.primary]}" router_config = { - name = "dev-onprem-vpn-ew1" - asn = var.router_onprem_configs.dev-ew1.asn + name = "dev-onprem-vpn-${local.region_shortnames[var.regions.primary]}" + asn = var.router_onprem_configs.dev-primary.asn } peer_gateway = { - external = var.vpn_onprem_configs.dev-ew1.peer_external_gateway + external = var.vpn_onprem_configs.dev-primary.peer_external_gateway } tunnels = { - for t in var.vpn_onprem_configs.dev-ew1.tunnels : + for t in var.vpn_onprem_configs.dev-primary.tunnels : "remote-${t.vpn_gateway_interface}-${t.peer_external_gateway_interface}" => { bgp_peer = { address = cidrhost(t.session_range, 1) asn = t.peer_asn } - bgp_peer_options = local.bgp_peer_options_onprem.dev-ew1 + bgp_peer_options = local.bgp_peer_options_onprem.dev-primary bgp_session_range = "${cidrhost(t.session_range, 2)}/30" peer_external_gateway_interface = t.peer_external_gateway_interface shared_secret = t.secret diff --git a/fast/stages/02-networking-separate-envs/vpn-onprem-prod.tf b/fast/stages/2-networking-d-separate-envs/vpn-onprem-prod.tf similarity index 73% rename from fast/stages/02-networking-separate-envs/vpn-onprem-prod.tf rename to fast/stages/2-networking-d-separate-envs/vpn-onprem-prod.tf index d4b2af24..0793e274 100644 --- a/fast/stages/02-networking-separate-envs/vpn-onprem-prod.tf +++ b/fast/stages/2-networking-d-separate-envs/vpn-onprem-prod.tf @@ -16,28 +16,33 @@ # tfdoc:file:description VPN between prod and onprem. -module "prod-to-onprem-ew1-vpn" { +moved { + from = module.prod-to-onprem-ew1-vpn + to = module.prod-to-onprem-primary-vpn +} + +module "prod-to-onprem-primary-vpn" { count = local.enable_onprem_vpn ? 1 : 0 source = "../../../modules/net-vpn-ha" project_id = module.prod-spoke-project.project_id network = module.prod-spoke-vpc.self_link - region = "europe-west1" - name = "vpn-to-onprem-ew1" + region = var.regions.primary + name = "vpn-to-onprem-${local.region_shortnames[var.regions.primary]}" router_config = { - name = "prod-onprem-vpn-ew1" - asn = var.router_onprem_configs.prod-ew1.asn + name = "prod-onprem-vpn-${local.region_shortnames[var.regions.primary]}" + asn = var.router_onprem_configs.prod-primary.asn } peer_gateway = { - external = var.vpn_onprem_configs.prod-ew1.peer_external_gateway + external = var.vpn_onprem_configs.prod-primary.peer_external_gateway } tunnels = { - for t in var.vpn_onprem_configs.prod-ew1.tunnels : + for t in var.vpn_onprem_configs.prod-primary.tunnels : "remote-${t.vpn_gateway_interface}-${t.peer_external_gateway_interface}" => { bgp_peer = { address = cidrhost(t.session_range, 1) asn = t.peer_asn } - bgp_peer_options = local.bgp_peer_options_onprem.prod-ew1 + bgp_peer_options = local.bgp_peer_options_onprem.prod-primary bgp_session_range = "${cidrhost(t.session_range, 2)}/30" peer_external_gateway_interface = t.peer_external_gateway_interface shared_secret = t.secret diff --git a/fast/stages/02-security/IAM.md b/fast/stages/2-security/IAM.md similarity index 100% rename from fast/stages/02-security/IAM.md rename to fast/stages/2-security/IAM.md diff --git a/fast/stages/02-security/README.md b/fast/stages/2-security/README.md similarity index 92% rename from fast/stages/02-security/README.md rename to fast/stages/2-security/README.md index 024ababb..a609cd81 100644 --- a/fast/stages/02-security/README.md +++ b/fast/stages/2-security/README.md @@ -42,7 +42,7 @@ Some care needs to be taken with project membership in perimeters, which can onl ## How to run this stage -This stage is meant to be executed after the [resource management](../01-resman) stage has run, as it leverages the folder and automation resources created there. The relevant user groups must also exist, but that's one of the requirements for the previous stages too, so if you ran those successfully, you're good to go. +This stage is meant to be executed after the [resource management](../1-resman) stage has run, as it leverages the folder and automation resources created there. The relevant user groups must also exist, but that's one of the requirements for the previous stages too, so if you ran those successfully, you're good to go. It's possible to run this stage in isolation, but that's outside the scope of this document, and you would need to refer to the code for the bootstrap stage for the required roles. @@ -64,7 +64,7 @@ ln -s ~/fast-config/providers/02-security-providers.tf . If you have not configured `outputs_location` in resource management, you can derive the providers file from that stage's outputs: ```bash -cd ../01-resman +cd ../1-resman terraform output -json providers | jq -r '.["02-security"]' \ > ../02-security/providers.tf ``` @@ -85,7 +85,7 @@ If you configured a valid path for `outputs_location` in the previous stages, si ln -s ~/fast-config/tfvars/00-bootstrap.auto.tfvars.json . ln -s ~/fast-config/tfvars/01-resman.auto.tfvars.json . # also copy the tfvars file used for the bootstrap stage -cp ../00-bootstrap/terraform.tfvars . +cp ../0-bootstrap/terraform.tfvars . ``` A second set of optional variables is specific to this stage. If you need to customize them add them to the file copied from bootstrap. @@ -262,20 +262,20 @@ Some references that might be useful in setting up this stage: | name | description | type | required | default | producer | |---|---|:---:|:---:|:---:|:---:| -| [automation](variables.tf#L17) | Automation resources created by the bootstrap stage. | object({…}) | ✓ | | 00-bootstrap | -| [billing_account](variables.tf#L25) | Billing account id and organization id ('nnnnnnnn' or null). | object({…}) | ✓ | | 00-bootstrap | -| [folder_ids](variables.tf#L34) | Folder name => id mappings, the 'security' folder name must exist. | object({…}) | ✓ | | 01-resman | -| [organization](variables.tf#L80) | Organization details. | object({…}) | ✓ | | 00-bootstrap | -| [prefix](variables.tf#L96) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 00-bootstrap | -| [service_accounts](variables.tf#L107) | Automation service accounts that can assign the encrypt/decrypt roles on keys. | object({…}) | ✓ | | 01-resman | -| [groups](variables.tf#L42) | Group names to grant organization-level permissions. | map(string) | | {…} | 00-bootstrap | -| [kms_defaults](variables.tf#L57) | Defaults used for KMS keys. | object({…}) | | {…} | | -| [kms_keys](variables.tf#L69) | KMS keys to create, keyed by name. Null attributes will be interpolated with defaults. | map(object({…})) | | {} | | -| [outputs_location](variables.tf#L90) | Path where providers, tfvars files, and lists for the following stages are written. Leave empty to disable. | string | | null | | -| [vpc_sc_access_levels](variables.tf#L118) | VPC SC access level definitions. | map(object({…})) | | {} | | -| [vpc_sc_egress_policies](variables.tf#L147) | VPC SC egress policy defnitions. | map(object({…})) | | {} | | -| [vpc_sc_ingress_policies](variables.tf#L167) | VPC SC ingress policy defnitions. | map(object({…})) | | {} | | -| [vpc_sc_perimeters](variables.tf#L188) | VPC SC regular perimeter definitions. | object({…}) | | {} | | +| [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#L38) | Folder name => id mappings, the 'security' folder name must exist. | object({…}) | ✓ | | 1-resman | +| [organization](variables.tf#L84) | Organization details. | object({…}) | ✓ | | 0-bootstrap | +| [prefix](variables.tf#L100) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 0-bootstrap | +| [service_accounts](variables.tf#L111) | Automation service accounts that can assign the encrypt/decrypt roles on keys. | object({…}) | ✓ | | 1-resman | +| [groups](variables.tf#L46) | Group names to grant organization-level permissions. | map(string) | | {…} | 0-bootstrap | +| [kms_defaults](variables.tf#L61) | Defaults used for KMS keys. | object({…}) | | {…} | | +| [kms_keys](variables.tf#L73) | KMS keys to create, keyed by name. Null attributes will be interpolated with defaults. | map(object({…})) | | {} | | +| [outputs_location](variables.tf#L94) | Path where providers, tfvars files, and lists for the following stages are written. Leave empty to disable. | string | | null | | +| [vpc_sc_access_levels](variables.tf#L122) | VPC SC access level definitions. | map(object({…})) | | {} | | +| [vpc_sc_egress_policies](variables.tf#L151) | VPC SC egress policy defnitions. | map(object({…})) | | {} | | +| [vpc_sc_ingress_policies](variables.tf#L171) | VPC SC ingress policy defnitions. | map(object({…})) | | {} | | +| [vpc_sc_perimeters](variables.tf#L192) | VPC SC regular perimeter definitions. | object({…}) | | {} | | ## Outputs diff --git a/fast/stages/02-security/core-dev.tf b/fast/stages/2-security/core-dev.tf similarity index 100% rename from fast/stages/02-security/core-dev.tf rename to fast/stages/2-security/core-dev.tf diff --git a/fast/stages/02-security/core-prod.tf b/fast/stages/2-security/core-prod.tf similarity index 100% rename from fast/stages/02-security/core-prod.tf rename to fast/stages/2-security/core-prod.tf diff --git a/fast/stages/02-security/diagram.png b/fast/stages/2-security/diagram.png similarity index 100% rename from fast/stages/02-security/diagram.png rename to fast/stages/2-security/diagram.png diff --git a/fast/stages/02-security/diagram.svg b/fast/stages/2-security/diagram.svg similarity index 100% rename from fast/stages/02-security/diagram.svg rename to fast/stages/2-security/diagram.svg diff --git a/fast/stages/02-security/main.tf b/fast/stages/2-security/main.tf similarity index 100% rename from fast/stages/02-security/main.tf rename to fast/stages/2-security/main.tf diff --git a/fast/stages/02-security/outputs.tf b/fast/stages/2-security/outputs.tf similarity index 96% rename from fast/stages/02-security/outputs.tf rename to fast/stages/2-security/outputs.tf index b7e42e49..ff0c13ed 100644 --- a/fast/stages/02-security/outputs.tf +++ b/fast/stages/2-security/outputs.tf @@ -44,13 +44,13 @@ locals { resource "local_file" "tfvars" { for_each = var.outputs_location == null ? {} : { 1 = 1 } file_permission = "0644" - filename = "${pathexpand(var.outputs_location)}/tfvars/02-security.auto.tfvars.json" + filename = "${pathexpand(var.outputs_location)}/tfvars/2-security.auto.tfvars.json" content = jsonencode(local.tfvars) } resource "google_storage_bucket_object" "tfvars" { bucket = var.automation.outputs_bucket - name = "tfvars/02-security.auto.tfvars.json" + name = "tfvars/2-security.auto.tfvars.json" content = jsonencode(local.tfvars) } diff --git a/fast/stages/02-security/variables.tf b/fast/stages/2-security/variables.tf similarity index 91% rename from fast/stages/02-security/variables.tf rename to fast/stages/2-security/variables.tf index 349589c9..e14d6376 100644 --- a/fast/stages/02-security/variables.tf +++ b/fast/stages/2-security/variables.tf @@ -15,7 +15,7 @@ */ variable "automation" { - # tfdoc:variable:source 00-bootstrap + # tfdoc:variable:source 0-bootstrap description = "Automation resources created by the bootstrap stage." type = object({ outputs_bucket = string @@ -23,16 +23,20 @@ variable "automation" { } variable "billing_account" { - # tfdoc:variable:source 00-bootstrap - description = "Billing account id and organization id ('nnnnnnnn' or null)." + # 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." type = object({ - id = string - organization_id = number + id = string + is_org_level = optional(bool, true) }) + validation { + condition = var.billing_account.is_org_level != null + error_message = "Invalid `null` value for `billing_account.is_org_level`." + } } variable "folder_ids" { - # tfdoc:variable:source 01-resman + # tfdoc:variable:source 1-resman description = "Folder name => id mappings, the 'security' folder name must exist." type = object({ security = string @@ -40,7 +44,7 @@ variable "folder_ids" { } variable "groups" { - # tfdoc:variable:source 00-bootstrap + # tfdoc:variable:source 0-bootstrap description = "Group names to grant organization-level permissions." type = map(string) # https://cloud.google.com/docs/enterprise/setup-checklist @@ -78,7 +82,7 @@ variable "kms_keys" { } variable "organization" { - # tfdoc:variable:source 00-bootstrap + # tfdoc:variable:source 0-bootstrap description = "Organization details." type = object({ domain = string @@ -94,7 +98,7 @@ variable "outputs_location" { } variable "prefix" { - # tfdoc:variable:source 00-bootstrap + # tfdoc:variable:source 0-bootstrap description = "Prefix used for resources that need unique names. Use 9 characters or less." type = string @@ -105,7 +109,7 @@ variable "prefix" { } variable "service_accounts" { - # tfdoc:variable:source 01-resman + # tfdoc:variable:source 1-resman description = "Automation service accounts that can assign the encrypt/decrypt roles on keys." type = object({ data-platform-dev = string diff --git a/fast/stages/02-security/vpc-sc-restricted-services.yaml b/fast/stages/2-security/vpc-sc-restricted-services.yaml similarity index 100% rename from fast/stages/02-security/vpc-sc-restricted-services.yaml rename to fast/stages/2-security/vpc-sc-restricted-services.yaml diff --git a/fast/stages/02-security/vpc-sc.tf b/fast/stages/2-security/vpc-sc.tf similarity index 100% rename from fast/stages/02-security/vpc-sc.tf rename to fast/stages/2-security/vpc-sc.tf diff --git a/fast/stages/03-data-platform/README.md b/fast/stages/3-data-platform/README.md similarity index 100% rename from fast/stages/03-data-platform/README.md rename to fast/stages/3-data-platform/README.md diff --git a/fast/stages/03-data-platform/dev/IAM.md b/fast/stages/3-data-platform/dev/IAM.md similarity index 100% rename from fast/stages/03-data-platform/dev/IAM.md rename to fast/stages/3-data-platform/dev/IAM.md diff --git a/fast/stages/03-data-platform/dev/README.md b/fast/stages/3-data-platform/dev/README.md similarity index 82% rename from fast/stages/03-data-platform/dev/README.md rename to fast/stages/3-data-platform/dev/README.md index 35f9bf1c..615dbde8 100644 --- a/fast/stages/03-data-platform/dev/README.md +++ b/fast/stages/3-data-platform/dev/README.md @@ -42,11 +42,11 @@ As per our GCP best practices the Data Platform relies on user groups to assign ### Network -A Shared VPC is used here, either from one of the FAST networking stages (e.g. [hub and spoke via VPN](../../02-networking-vpn)) or from an external source. +A Shared VPC is used here, either from one of the FAST networking stages (e.g. [hub and spoke via VPN](../../2-networking-b-vpn)) or from an external source. ### Encryption -Cloud KMS crypto keys can be configured wither from the [FAST security stage](../../02-security) or from an external source. This step is optional and depends on customer policies and security best practices. +Cloud KMS crypto keys can be configured wither from the [FAST security stage](../../2-security) or from an external source. This step is optional and depends on customer policies and security best practices. To configure the use of Cloud KMS on resources, you have to specify the key id on the `service_encryption_keys` variable. Key locations should match resource locations. @@ -55,19 +55,20 @@ To configure the use of Cloud KMS on resources, you have to specify the key id o [Data Catalog](https://cloud.google.com/data-catalog) helps you to document your data entry at scale. Data Catalog relies on [tags](https://cloud.google.com/data-catalog/docs/tags-and-tag-templates#tags) and [tag template](https://cloud.google.com/data-catalog/docs/tags-and-tag-templates#tag-templates) to manage metadata for all data entries in a unified and centralized service. To implement [column-level security](https://cloud.google.com/bigquery/docs/column-level-security-intro) on BigQuery, we suggest to use `Tags` and `Tag templates`. The default configuration will implement 3 tags: - - `3_Confidential`: policy tag for columns that include very sensitive information, such as credit card numbers. - - `2_Private`: policy tag for columns that include sensitive personal identifiable information (PII) information, such as a person's first name. - - `1_Sensitive`: policy tag for columns that include data that cannot be made public, such as the credit limit. + +- `3_Confidential`: policy tag for columns that include very sensitive information, such as credit card numbers. +- `2_Private`: policy tag for columns that include sensitive personal identifiable information (PII) information, such as a person's first name. +- `1_Sensitive`: policy tag for columns that include data that cannot be made public, such as the credit limit. Anything that is not tagged is available to all users who have access to the data warehouse. -You can configure your tags and roles associated by configuring the `data_catalog_tags` variable. We suggest useing the "[Best practices for using policy tags in BigQuery](https://cloud.google.com/bigquery/docs/best-practices-policy-tags)" article as a guide to designing your tags structure and access pattern. By default, no groups has access to tagged data. +You can configure your tags and roles associated by configuring the `data_catalog_tags` variable. We suggest useing the "[Best practices for using policy tags in BigQuery](https://cloud.google.com/bigquery/docs/best-practices-policy-tags)" article as a guide to designing your tags structure and access pattern. By default, no groups has access to tagged data. ### VPC-SC -As is often the case in real-world configurations, [VPC-SC](https://cloud.google.com/vpc-service-controls) is needed to mitigate data exfiltration. VPC-SC can be configured from the [FAST security stage](../../02-security). This step is optional, but highly recomended, and depends on customer policies and security best practices. +As is often the case in real-world configurations, [VPC-SC](https://cloud.google.com/vpc-service-controls) is needed to mitigate data exfiltration. VPC-SC can be configured from the [FAST security stage](../../2-security). This step is optional, but highly recomended, and depends on customer policies and security best practices. -To configure the use of VPC-SC on the data platform, you have to specify the data platform project numbers on the `vpc_sc_perimeter_projects.dev` variable on [FAST security stage](../../02-security#perimeter-resources). +To configure the use of VPC-SC on the data platform, you have to specify the data platform project numbers on the `vpc_sc_perimeter_projects.dev` variable on [FAST security stage](../../2-security#perimeter-resources). In the case your Data Warehouse need to handle confidential data and you have the requirement to separate them deeply from other data and IAM is not enough, the suggested configuration is to keep the confidential project in a separate VPC-SC perimeter with the adequate ingress/egress rules needed for the load and tranformation service account. Below you can find an high level diagram describing the configuration. @@ -77,7 +78,7 @@ In the case your Data Warehouse need to handle confidential data and you have th ## How to run this stage -This stage can be run in isolation by prviding the necessary variables, but it's really meant to be used as part of the FAST flow after the "foundational stages" ([`00-bootstrap`](../../00-bootstrap), [`01-resman`](../../01-resman), [`02-networking`](../../02-networking-vpn) and [`02-security`](../../02-security)). +This stage can be run in isolation by prviding the necessary variables, but it's really meant to be used as part of the FAST flow after the "foundational stages" ([`00-bootstrap`](../../0-bootstrap), [`01-resman`](../../1-resman), [`02-networking`](../../2-networking-b-vpn) and [`02-security`](../../2-security)). When running in isolation, the following roles are needed on the principal used to apply Terraform: @@ -111,9 +112,9 @@ ln -s ~/fast-config/providers/03-data-platform-dev-providers.tf . If you have not configured `outputs_location` in bootstrap, you can derive the providers file from that stage's outputs: ```bash -cd ../../01-resman +cd ../../1-resman terraform output -json providers | jq -r '.["03-data-platform-dev"]' \ - > ../03-data-platform/dev/providers.tf + > ../3-data-platform/dev/providers.tf ``` ### Variable configuration @@ -133,7 +134,7 @@ ln -s ~/fast-config/tfvars/00-bootstrap.auto.tfvars.json . ln -s ~/fast-config/tfvars/01-resman.auto.tfvars.json . ln -s ~/fast-config/tfvars/02-networking.auto.tfvars.json . # also copy the tfvars file used for the bootstrap stage -cp ../../00-bootstrap/terraform.tfvars . +cp ../../0-bootstrap/terraform.tfvars . ``` If you're not using FAST or its output files, refer to the [Variables](#variables) table at the bottom of this document for a full list of variables, their origin (e.g., a stage or specific to this one), and descriptions explaining their meaning. @@ -147,7 +148,7 @@ terraform apply ## Demo pipeline -The application layer is out of scope of this script. As a demo purpuse only, several Cloud Composer DAGs are provided. Demos will import data from the `landing` area to the `DataWarehouse Confidential` dataset suing different features. +The application layer is out of scope of this script. As a demo purpuse only, several Cloud Composer DAGs are provided. Demos will import data from the `landing` area to the `DataWarehouse Confidential` dataset suing different features. You can find examples in the `[demo](../../../../blueprints/data-solutions/data-platform-foundations/demo)` folder. @@ -166,24 +167,24 @@ You can find examples in the `[demo](../../../../blueprints/data-solutions/data- | name | description | type | required | default | producer | |---|---|:---:|:---:|:---:|:---:| -| [automation](variables.tf#L17) | Automation resources created by the bootstrap stage. | object({…}) | ✓ | | 00-bootstrap | -| [billing_account](variables.tf#L25) | Billing account id and organization id ('nnnnnnnn' or null). | object({…}) | ✓ | | 00-globals | -| [folder_ids](variables.tf#L98) | Folder to be used for the networking resources in folders/nnnn format. | object({…}) | ✓ | | 01-resman | -| [host_project_ids](variables.tf#L116) | Shared VPC project ids. | object({…}) | ✓ | | 02-networking | -| [organization](variables.tf#L146) | Organization details. | object({…}) | ✓ | | 00-globals | -| [prefix](variables.tf#L162) | Unique prefix used for resource names. Not used for projects if 'project_create' is null. | string | ✓ | | 00-globals | -| [composer_config](variables.tf#L34) | Cloud Composer configuration options. | object({…}) | | {…} | | -| [data_catalog_tags](variables.tf#L81) | List of Data Catalog Policy tags to be created with optional IAM binging configuration in {tag => {ROLE => [MEMBERS]}} format. | map(map(list(string))) | | {…} | | -| [data_force_destroy](variables.tf#L92) | Flag to set 'force_destroy' on data services like BigQery or Cloud Storage. | bool | | false | | -| [groups](variables.tf#L106) | Groups. | map(string) | | {…} | | -| [location](variables.tf#L124) | Location used for multi-regional resources. | string | | "eu" | | -| [network_config_composer](variables.tf#L130) | Network configurations to use for Composer. | object({…}) | | {…} | | -| [outputs_location](variables.tf#L156) | Path where providers, tfvars files, and lists for the following stages are written. Leave empty to disable. | string | | null | | -| [project_services](variables.tf#L168) | List of core services enabled on all projects. | list(string) | | […] | | -| [region](variables.tf#L179) | Region used for regional resources. | string | | "europe-west1" | | -| [service_encryption_keys](variables.tf#L185) | Cloud KMS to use to encrypt different services. Key location should match service region. | object({…}) | | null | | -| [subnet_self_links](variables.tf#L197) | Shared VPC subnet self links. | object({…}) | | null | 02-networking | -| [vpc_self_links](variables.tf#L206) | Shared VPC self links. | object({…}) | | null | 02-networking | +| [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#L102) | Folder to be used for the networking resources in folders/nnnn format. | object({…}) | ✓ | | 1-resman | +| [host_project_ids](variables.tf#L120) | Shared VPC project ids. | object({…}) | ✓ | | 2-networking | +| [organization](variables.tf#L150) | Organization details. | object({…}) | ✓ | | 00-globals | +| [prefix](variables.tf#L166) | Unique prefix used for resource names. Not used for projects if 'project_create' is null. | string | ✓ | | 00-globals | +| [composer_config](variables.tf#L38) | Cloud Composer configuration options. | object({…}) | | {…} | | +| [data_catalog_tags](variables.tf#L85) | List of Data Catalog Policy tags to be created with optional IAM binging configuration in {tag => {ROLE => [MEMBERS]}} format. | map(map(list(string))) | | {…} | | +| [data_force_destroy](variables.tf#L96) | Flag to set 'force_destroy' on data services like BigQery or Cloud Storage. | bool | | false | | +| [groups](variables.tf#L110) | Groups. | map(string) | | {…} | | +| [location](variables.tf#L128) | Location used for multi-regional resources. | string | | "eu" | | +| [network_config_composer](variables.tf#L134) | Network configurations to use for Composer. | object({…}) | | {…} | | +| [outputs_location](variables.tf#L160) | Path where providers, tfvars files, and lists for the following stages are written. Leave empty to disable. | string | | null | | +| [project_services](variables.tf#L172) | List of core services enabled on all projects. | list(string) | | […] | | +| [region](variables.tf#L183) | Region used for regional resources. | string | | "europe-west1" | | +| [service_encryption_keys](variables.tf#L189) | Cloud KMS to use to encrypt different services. Key location should match service region. | object({…}) | | null | | +| [subnet_self_links](variables.tf#L201) | Shared VPC subnet self links. | object({…}) | | null | 2-networking | +| [vpc_self_links](variables.tf#L210) | Shared VPC self links. | object({…}) | | null | 2-networking | ## Outputs diff --git a/fast/stages/03-data-platform/dev/demo b/fast/stages/3-data-platform/dev/demo similarity index 100% rename from fast/stages/03-data-platform/dev/demo rename to fast/stages/3-data-platform/dev/demo diff --git a/fast/stages/03-data-platform/dev/diagram.png b/fast/stages/3-data-platform/dev/diagram.png similarity index 100% rename from fast/stages/03-data-platform/dev/diagram.png rename to fast/stages/3-data-platform/dev/diagram.png diff --git a/fast/stages/03-data-platform/dev/diagram_vpcsc.png b/fast/stages/3-data-platform/dev/diagram_vpcsc.png similarity index 100% rename from fast/stages/03-data-platform/dev/diagram_vpcsc.png rename to fast/stages/3-data-platform/dev/diagram_vpcsc.png diff --git a/fast/stages/03-data-platform/dev/main.tf b/fast/stages/3-data-platform/dev/main.tf similarity index 100% rename from fast/stages/03-data-platform/dev/main.tf rename to fast/stages/3-data-platform/dev/main.tf diff --git a/fast/stages/03-data-platform/dev/outputs.tf b/fast/stages/3-data-platform/dev/outputs.tf similarity index 95% rename from fast/stages/03-data-platform/dev/outputs.tf rename to fast/stages/3-data-platform/dev/outputs.tf index d0f79358..2eb813b4 100644 --- a/fast/stages/03-data-platform/dev/outputs.tf +++ b/fast/stages/3-data-platform/dev/outputs.tf @@ -27,13 +27,13 @@ locals { resource "local_file" "tfvars" { for_each = var.outputs_location == null ? {} : { 1 = 1 } file_permission = "0644" - filename = "${pathexpand(var.outputs_location)}/tfvars/03-data-platform-dev.auto.tfvars.json" + filename = "${pathexpand(var.outputs_location)}/tfvars/3-data-platform-dev.auto.tfvars.json" content = jsonencode(local.tfvars) } resource "google_storage_bucket_object" "tfvars" { bucket = var.automation.outputs_bucket - name = "tfvars/03-data-platform-dev.auto.tfvars.json" + name = "tfvars/3-data-platform-dev.auto.tfvars.json" content = jsonencode(local.tfvars) } diff --git a/fast/stages/03-data-platform/dev/variables.tf b/fast/stages/3-data-platform/dev/variables.tf similarity index 90% rename from fast/stages/03-data-platform/dev/variables.tf rename to fast/stages/3-data-platform/dev/variables.tf index 29dd1e45..74a5dbe1 100644 --- a/fast/stages/03-data-platform/dev/variables.tf +++ b/fast/stages/3-data-platform/dev/variables.tf @@ -15,7 +15,7 @@ # tfdoc:file:description Terraform Variables. variable "automation" { - # tfdoc:variable:source 00-bootstrap + # tfdoc:variable:source 0-bootstrap description = "Automation resources created by the bootstrap stage." type = object({ outputs_bucket = string @@ -23,12 +23,16 @@ variable "automation" { } variable "billing_account" { - # tfdoc:variable:source 00-globals - description = "Billing account id and organization id ('nnnnnnnn' or null)." + # 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." type = object({ - id = string - organization_id = number + id = string + is_org_level = optional(bool, true) }) + validation { + condition = var.billing_account.is_org_level != null + error_message = "Invalid `null` value for `billing_account.is_org_level`." + } } variable "composer_config" { @@ -96,7 +100,7 @@ variable "data_force_destroy" { } variable "folder_ids" { - # tfdoc:variable:source 01-resman + # tfdoc:variable:source 1-resman description = "Folder to be used for the networking resources in folders/nnnn format." type = object({ data-platform-dev = string @@ -114,7 +118,7 @@ variable "groups" { } variable "host_project_ids" { - # tfdoc:variable:source 02-networking + # tfdoc:variable:source 2-networking description = "Shared VPC project ids." type = object({ dev-spoke-0 = string @@ -195,7 +199,7 @@ variable "service_encryption_keys" { } variable "subnet_self_links" { - # tfdoc:variable:source 02-networking + # tfdoc:variable:source 2-networking description = "Shared VPC subnet self links." type = object({ dev-spoke-0 = map(string) @@ -204,7 +208,7 @@ variable "subnet_self_links" { } variable "vpc_self_links" { - # tfdoc:variable:source 02-networking + # tfdoc:variable:source 2-networking description = "Shared VPC self links." type = object({ dev-spoke-0 = string diff --git a/fast/stages/03-gke-multitenant/README.md b/fast/stages/3-gke-multitenant/README.md similarity index 71% rename from fast/stages/03-gke-multitenant/README.md rename to fast/stages/3-gke-multitenant/README.md index f08910c8..9f9d9498 100644 --- a/fast/stages/03-gke-multitenant/README.md +++ b/fast/stages/3-gke-multitenant/README.md @@ -2,7 +2,7 @@ This directory contains a stage that can be used to centralize management of GKE multinenant clusters. -The Terraform code follows the same general approach used for the [project factory](../03-project-factory/) and [data platform](../03-data-platform/) stages, where a "fat module" contains the stage code and is used by thin code wrappers that localize it for each environment or specialized configuration: +The Terraform code follows the same general approach used for the [project factory](../3-project-factory/) and [data platform](../3-data-platform/) stages, where a "fat module" contains the stage code and is used by thin code wrappers that localize it for each environment or specialized configuration: The [`dev` folder](./dev/) contains an example setup for a generic development environment, and can be used as-is or cloned to implement other environments, or more specialized setups diff --git a/fast/stages/03-gke-multitenant/dev/README.md b/fast/stages/3-gke-multitenant/dev/README.md similarity index 90% rename from fast/stages/03-gke-multitenant/dev/README.md rename to fast/stages/3-gke-multitenant/dev/README.md index c446fbcb..4accf8e1 100644 --- a/fast/stages/03-gke-multitenant/dev/README.md +++ b/fast/stages/3-gke-multitenant/dev/README.md @@ -39,7 +39,7 @@ This stage creates a project containing and as many clusters and node pools as r ## How to run this stage -This stage is meant to be executed after "foundational stages" (i.e., stages [`00-bootstrap`](../../00-bootstrap), [`01-resman`](../../01-resman), 02-networking (either [VPN](../../02-networking-vpn) or [NVA](../../02-networking-nva)) and [`02-security`](../../02-security)) have been run. +This stage is meant to be executed after "foundational stages" (i.e., stages [`00-bootstrap`](../../0-bootstrap), [`01-resman`](../../1-resman), 02-networking (either [VPN](../../2-networking-b-vpn) or [NVA](../../2-networking-c-nva)) and [`02-security`](../../2-security)) have been run. It's of course possible to run this stage in isolation, by making sure the architectural prerequisites are satisfied (e.g., networking), and that the Service Account running the stage is granted the roles/permissions below: @@ -140,23 +140,23 @@ terraform apply | name | description | type | required | default | producer | |---|---|:---:|:---:|:---:|:---:| -| [automation](variables.tf#L21) | Automation resources created by the bootstrap stage. | object({…}) | ✓ | | 00-bootstrap | -| [billing_account](variables.tf#L29) | Billing account id and organization id ('nnnnnnnn' or null). | object({…}) | ✓ | | 00-bootstrap | -| [folder_ids](variables.tf#L149) | Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created. | object({…}) | ✓ | | 01-resman | -| [host_project_ids](variables.tf#L164) | Host project for the shared VPC. | object({…}) | ✓ | | 02-networking | -| [prefix](variables.tf#L213) | Prefix used for resources that need unique names. | string | ✓ | | | -| [vpc_self_links](variables.tf#L225) | Self link for the shared VPC. | object({…}) | ✓ | | 02-networking | -| [clusters](variables.tf#L38) | Clusters configuration. Refer to the gke-cluster module for type details. | map(object({…})) | | {} | | -| [fleet_configmanagement_clusters](variables.tf#L86) | Config management features enabled on specific sets of member clusters, in config name => [cluster name] format. | map(list(string)) | | {} | | -| [fleet_configmanagement_templates](variables.tf#L94) | Sets of config management configurations that can be applied to member clusters, in config name => {options} format. | map(object({…})) | | {} | | -| [fleet_features](variables.tf#L129) | Enable and configue fleet features. Set to null to disable GKE Hub if fleet workload identity is not used. | object({…}) | | null | | -| [fleet_workload_identity](variables.tf#L142) | Use Fleet Workload Identity for clusters. Enables GKE Hub if set to true. | bool | | false | | -| [group_iam](variables.tf#L157) | Project-level authoritative IAM bindings for groups in {GROUP_EMAIL => [ROLES]} format. Use group emails as keys, list of roles as values. | map(list(string)) | | {} | | -| [iam](variables.tf#L172) | Project-level authoritative IAM bindings for users and service accounts in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | -| [labels](variables.tf#L179) | Project-level labels. | map(string) | | {} | | -| [nodepools](variables.tf#L185) | Nodepools configuration. Refer to the gke-nodepool module for type details. | map(map(object({…}))) | | {} | | -| [outputs_location](variables.tf#L207) | Path where providers, tfvars files, and lists for the following stages are written. Leave empty to disable. | string | | null | | -| [project_services](variables.tf#L218) | Additional project services to enable. | list(string) | | [] | | +| [automation](variables.tf#L21) | Automation resources created by the bootstrap stage. | object({…}) | ✓ | | 0-bootstrap | +| [billing_account](variables.tf#L29) | 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#L153) | Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created. | object({…}) | ✓ | | 1-resman | +| [host_project_ids](variables.tf#L168) | Host project for the shared VPC. | object({…}) | ✓ | | 2-networking | +| [prefix](variables.tf#L217) | Prefix used for resources that need unique names. | string | ✓ | | | +| [vpc_self_links](variables.tf#L229) | Self link for the shared VPC. | object({…}) | ✓ | | 2-networking | +| [clusters](variables.tf#L42) | Clusters configuration. Refer to the gke-cluster module for type details. | map(object({…})) | | {} | | +| [fleet_configmanagement_clusters](variables.tf#L90) | Config management features enabled on specific sets of member clusters, in config name => [cluster name] format. | map(list(string)) | | {} | | +| [fleet_configmanagement_templates](variables.tf#L98) | Sets of config management configurations that can be applied to member clusters, in config name => {options} format. | map(object({…})) | | {} | | +| [fleet_features](variables.tf#L133) | Enable and configue fleet features. Set to null to disable GKE Hub if fleet workload identity is not used. | object({…}) | | null | | +| [fleet_workload_identity](variables.tf#L146) | Use Fleet Workload Identity for clusters. Enables GKE Hub if set to true. | bool | | false | | +| [group_iam](variables.tf#L161) | Project-level authoritative IAM bindings for groups in {GROUP_EMAIL => [ROLES]} format. Use group emails as keys, list of roles as values. | map(list(string)) | | {} | | +| [iam](variables.tf#L176) | Project-level authoritative IAM bindings for users and service accounts in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | +| [labels](variables.tf#L183) | Project-level labels. | map(string) | | {} | | +| [nodepools](variables.tf#L189) | Nodepools configuration. Refer to the gke-nodepool module for type details. | map(map(object({…}))) | | {} | | +| [outputs_location](variables.tf#L211) | Path where providers, tfvars files, and lists for the following stages are written. Leave empty to disable. | string | | null | | +| [project_services](variables.tf#L222) | Additional project services to enable. | list(string) | | [] | | ## Outputs diff --git a/fast/stages/03-gke-multitenant/dev/diagram.png b/fast/stages/3-gke-multitenant/dev/diagram.png similarity index 100% rename from fast/stages/03-gke-multitenant/dev/diagram.png rename to fast/stages/3-gke-multitenant/dev/diagram.png diff --git a/fast/stages/03-gke-multitenant/dev/main.tf b/fast/stages/3-gke-multitenant/dev/main.tf similarity index 100% rename from fast/stages/03-gke-multitenant/dev/main.tf rename to fast/stages/3-gke-multitenant/dev/main.tf diff --git a/fast/stages/03-gke-multitenant/dev/outputs.tf b/fast/stages/3-gke-multitenant/dev/outputs.tf similarity index 96% rename from fast/stages/03-gke-multitenant/dev/outputs.tf rename to fast/stages/3-gke-multitenant/dev/outputs.tf index 87b0ca73..3f231c68 100644 --- a/fast/stages/03-gke-multitenant/dev/outputs.tf +++ b/fast/stages/3-gke-multitenant/dev/outputs.tf @@ -42,13 +42,13 @@ locals { resource "local_file" "tfvars" { for_each = var.outputs_location == null ? {} : { 1 = 1 } file_permission = "0644" - filename = "${pathexpand(var.outputs_location)}/tfvars/03-gke-dev.auto.tfvars.json" + filename = "${pathexpand(var.outputs_location)}/tfvars/3-gke-dev.auto.tfvars.json" content = jsonencode(local.tfvars) } resource "google_storage_bucket_object" "tfvars" { bucket = var.automation.outputs_bucket - name = "tfvars/03-gke-dev.auto.tfvars.json" + name = "tfvars/3-gke-dev.auto.tfvars.json" content = jsonencode(local.tfvars) } diff --git a/fast/stages/03-gke-multitenant/dev/variables.tf b/fast/stages/3-gke-multitenant/dev/variables.tf similarity index 92% rename from fast/stages/03-gke-multitenant/dev/variables.tf rename to fast/stages/3-gke-multitenant/dev/variables.tf index 6be89126..2dbf5a6e 100644 --- a/fast/stages/03-gke-multitenant/dev/variables.tf +++ b/fast/stages/3-gke-multitenant/dev/variables.tf @@ -19,7 +19,7 @@ # cloud dns for gke? variable "automation" { - # tfdoc:variable:source 00-bootstrap + # tfdoc:variable:source 0-bootstrap description = "Automation resources created by the bootstrap stage." type = object({ outputs_bucket = string @@ -27,12 +27,16 @@ variable "automation" { } variable "billing_account" { - # tfdoc:variable:source 00-bootstrap - description = "Billing account id and organization id ('nnnnnnnn' or null)." + # 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." type = object({ - id = string - organization_id = number + id = string + is_org_level = optional(bool, true) }) + validation { + condition = var.billing_account.is_org_level != null + error_message = "Invalid `null` value for `billing_account.is_org_level`." + } } variable "clusters" { @@ -147,7 +151,7 @@ variable "fleet_workload_identity" { } variable "folder_ids" { - # tfdoc:variable:source 01-resman + # tfdoc:variable:source 1-resman description = "Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created." type = object({ gke-dev = string @@ -162,7 +166,7 @@ variable "group_iam" { } variable "host_project_ids" { - # tfdoc:variable:source 02-networking + # tfdoc:variable:source 2-networking description = "Host project for the shared VPC." type = object({ dev-spoke-0 = string @@ -223,7 +227,7 @@ variable "project_services" { } variable "vpc_self_links" { - # tfdoc:variable:source 02-networking + # tfdoc:variable:source 2-networking description = "Self link for the shared VPC." type = object({ dev-spoke-0 = string diff --git a/fast/stages/03-project-factory/README.md b/fast/stages/3-project-factory/README.md similarity index 100% rename from fast/stages/03-project-factory/README.md rename to fast/stages/3-project-factory/README.md diff --git a/fast/stages/03-project-factory/dev/README.md b/fast/stages/3-project-factory/dev/README.md similarity index 85% rename from fast/stages/03-project-factory/dev/README.md rename to fast/stages/3-project-factory/dev/README.md index 8fe213ce..2d95f918 100644 --- a/fast/stages/03-project-factory/dev/README.md +++ b/fast/stages/3-project-factory/dev/README.md @@ -28,7 +28,7 @@ The project factory takes care of the following activities: ## How to run this stage -This stage is meant to be executed after "foundational stages" (i.e., stages [`00-bootstrap`](../../00-bootstrap), [`01-resman`](../../01-resman), 02-networking (either [VPN](../../02-networking-vpn) or [NVA](../../02-networking-nva)) and [`02-security`](../../02-security)) have been run. +This stage is meant to be executed after "foundational stages" (i.e., stages [`00-bootstrap`](../../0-bootstrap), [`01-resman`](../../1-resman), 02-networking (either [VPN](../../2-networking-b-vpn) or [NVA](../../2-networking-c-nva)) and [`02-security`](../../2-security)) have been run. It's of course possible to run this stage in isolation, by making sure the architectural prerequisites are satisfied (e.g., networking), and that the Service Account running the stage is granted the roles/permissions below: @@ -108,13 +108,13 @@ terraform apply | name | description | type | required | default | producer | |---|---|:---:|:---:|:---:|:---:| -| [billing_account](variables.tf#L19) | Billing account id and organization id ('nnnnnnnn' or null). | object({…}) | ✓ | | 00-bootstrap | -| [prefix](variables.tf#L56) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 00-bootstrap | -| [data_dir](variables.tf#L28) | Relative path for the folder storing configuration data. | string | | "data/projects" | | -| [defaults_file](variables.tf#L34) | Relative path for the file storing the project factory configuration. | string | | "data/defaults.yaml" | | -| [environment_dns_zone](variables.tf#L40) | DNS zone suffix for environment. | string | | null | 02-networking | -| [host_project_ids](variables.tf#L47) | Host project for the shared VPC. | object({…}) | | null | 02-networking | -| [vpc_self_links](variables.tf#L67) | Self link for the shared VPC. | object({…}) | | null | 02-networking | +| [billing_account](variables.tf#L19) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | object({…}) | ✓ | | 0-bootstrap | +| [prefix](variables.tf#L60) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 0-bootstrap | +| [data_dir](variables.tf#L32) | Relative path for the folder storing configuration data. | string | | "data/projects" | | +| [defaults_file](variables.tf#L38) | Relative path for the file storing the project factory configuration. | string | | "data/defaults.yaml" | | +| [environment_dns_zone](variables.tf#L44) | DNS zone suffix for environment. | string | | null | 2-networking | +| [host_project_ids](variables.tf#L51) | Host project for the shared VPC. | object({…}) | | null | 2-networking | +| [vpc_self_links](variables.tf#L71) | Self link for the shared VPC. | object({…}) | | null | 2-networking | ## Outputs diff --git a/fast/stages/03-project-factory/dev/data/defaults.yaml b/fast/stages/3-project-factory/dev/data/defaults.yaml similarity index 100% rename from fast/stages/03-project-factory/dev/data/defaults.yaml rename to fast/stages/3-project-factory/dev/data/defaults.yaml diff --git a/fast/stages/03-project-factory/dev/data/projects/project.yaml.sample b/fast/stages/3-project-factory/dev/data/projects/project.yaml.sample similarity index 100% rename from fast/stages/03-project-factory/dev/data/projects/project.yaml.sample rename to fast/stages/3-project-factory/dev/data/projects/project.yaml.sample diff --git a/fast/stages/03-project-factory/dev/diagram.png b/fast/stages/3-project-factory/dev/diagram.png similarity index 100% rename from fast/stages/03-project-factory/dev/diagram.png rename to fast/stages/3-project-factory/dev/diagram.png diff --git a/fast/stages/03-project-factory/dev/diagram.svg b/fast/stages/3-project-factory/dev/diagram.svg similarity index 100% rename from fast/stages/03-project-factory/dev/diagram.svg rename to fast/stages/3-project-factory/dev/diagram.svg diff --git a/fast/stages/03-project-factory/dev/main.tf b/fast/stages/3-project-factory/dev/main.tf similarity index 100% rename from fast/stages/03-project-factory/dev/main.tf rename to fast/stages/3-project-factory/dev/main.tf diff --git a/fast/stages/03-project-factory/dev/outputs.tf b/fast/stages/3-project-factory/dev/outputs.tf similarity index 100% rename from fast/stages/03-project-factory/dev/outputs.tf rename to fast/stages/3-project-factory/dev/outputs.tf diff --git a/fast/stages/03-project-factory/dev/variables.tf b/fast/stages/3-project-factory/dev/variables.tf similarity index 76% rename from fast/stages/03-project-factory/dev/variables.tf rename to fast/stages/3-project-factory/dev/variables.tf index 2993bfba..5ad49f77 100644 --- a/fast/stages/03-project-factory/dev/variables.tf +++ b/fast/stages/3-project-factory/dev/variables.tf @@ -17,12 +17,16 @@ #TODO: tfdoc annotations variable "billing_account" { - # tfdoc:variable:source 00-bootstrap - description = "Billing account id and organization id ('nnnnnnnn' or null)." + # 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." type = object({ - id = string - organization_id = number + id = string + is_org_level = optional(bool, true) }) + validation { + condition = var.billing_account.is_org_level != null + error_message = "Invalid `null` value for `billing_account.is_org_level`." + } } variable "data_dir" { @@ -38,14 +42,14 @@ variable "defaults_file" { } variable "environment_dns_zone" { - # tfdoc:variable:source 02-networking + # tfdoc:variable:source 2-networking description = "DNS zone suffix for environment." type = string default = null } variable "host_project_ids" { - # tfdoc:variable:source 02-networking + # tfdoc:variable:source 2-networking description = "Host project for the shared VPC." type = object({ dev-spoke-0 = string @@ -54,7 +58,7 @@ variable "host_project_ids" { } variable "prefix" { - # tfdoc:variable:source 00-bootstrap + # tfdoc:variable:source 0-bootstrap description = "Prefix used for resources that need unique names. Use 9 characters or less." type = string @@ -65,7 +69,7 @@ variable "prefix" { } variable "vpc_self_links" { - # tfdoc:variable:source 02-networking + # tfdoc:variable:source 2-networking description = "Self link for the shared VPC." type = object({ dev-spoke-0 = string diff --git a/fast/stages/CLEANUP.md b/fast/stages/CLEANUP.md index e5f1418f..3bc581f9 100644 --- a/fast/stages/CLEANUP.md +++ b/fast/stages/CLEANUP.md @@ -1,4 +1,5 @@ # FAST deployment clean up + If you want to destroy a previous FAST deployment in your organization, follow these steps. Destruction must be done in reverse order, from stage 3 to stage 0 @@ -11,6 +12,7 @@ terraform destroy ``` ## Stage 3 (GKE) + Terraform refuses to delete non-empty GCS buckets and BigQuery datasets, so they need to be removed manually from the state. ```bash @@ -24,14 +26,15 @@ done terraform destroy ``` - ## Stage 2 (Security) + ```bash cd $FAST_PWD/02-security/ terraform destroy ``` ## Stage 2 (Networking) + ```bash cd $FAST_PWD/02-networking-XXX/ terraform destroy @@ -43,7 +46,6 @@ A minor glitch can surface running `terraform destroy`, where the service projec Stage 1 is a little more complicated because of the GCS buckets containing your terraform statefiles. By default, Terraform refuses to delete non-empty buckets, which is good to protect your terraform state, but it makes destruction a bit harder. Use the commands below to remove the GCS buckets from the state and then execute `terraform destroy` - ```bash cd $FAST_PWD/01-resman/ @@ -110,5 +112,6 @@ rm -i terraform.tfstate* ``` In case you want to deploy FAST stages again, the make sure to: -* Modify the [prefix](00-bootstrap/variables.tf) variable to allow the deployment of resources that need unique names (eg, projects). -* Modify the [custom_roles](00-bootstrap/variables.tf) variable to allow recently deleted custom roles to be created again. + +* Modify the [prefix](0-bootstrap/variables.tf) variable to allow the deployment of resources that need unique names (eg, projects). +* Modify the [custom_roles](0-bootstrap/variables.tf) variable to allow recently deleted custom roles to be created again. diff --git a/fast/stages/COMPANION.md b/fast/stages/COMPANION.md index e0f6ec62..d5d7752f 100644 --- a/fast/stages/COMPANION.md +++ b/fast/stages/COMPANION.md @@ -1,32 +1,42 @@ # FAST deployment companion guide -To deploy a GCP Landing Zone using FAST, your organization needs to meet a few prerequisites before starting. This guide serves as quick guide to prepare your GCP organization and also as cheat sheet with the commands and minimal configuration required to deploy FAST. +To deploy a GCP Landing Zone using FAST, your organization needs to meet a few prerequisites before starting. This guide serves as quick guide to prepare your GCP organization and also as cheat sheet with the commands and minimal configuration required to deploy FAST. The detailed explanation of each stage, their configuration, possible modifications and adaptations are included in the README of stage. This document only outlines the minimal configuration to get from an empty organization to a working FAST deployment. **Warning! Executing FAST sets organization policies and authoritative role bindings in your GCP Organization. We recommend using FAST on a clean organization, or to fork and adapt FAST to support your existing Organization needs.** ## Prerequisites + 1. FAST uses the recommended groups from the [GCP Enterprise Setup checklist](). Go to [Workspace / Cloud Identity](https://admin.google.com) and ensure all the following groups exist: - - `gcp-billing-admins@` - - `gcp-devops@` - - `gcp-network-admins@` - - `gcp-organization-admins@` - - `gcp-security-admins@` - - `gcp-support@` + +- `gcp-billing-admins@` +- `gcp-devops@` +- `gcp-network-admins@` +- `gcp-organization-admins@` +- `gcp-security-admins@` +- `gcp-support@` + 2. If you already executed FAST in your organization, make you [clean it up](CLEANUP.md) before continuing with the rest of this guide. + 3. Grant your user “Organization Administrator” role in your organization and add it to the `gcp-organization-admins@` group. + 4. Login with your user using gcloud. + ```bash gcloud auth login gcloud auth application-default login ``` + 5. Clone the Fabric repository. + ```bash git clone https://github.com/GoogleCloudPlatform/cloud-foundation-fabric.git cd cloud-foundation-fabric ``` + 6. Grant required roles to your user. + ```bash # set a variable to the fast folder export FAST_PWD="$(pwd)/fast/stages" @@ -49,9 +59,11 @@ gcloud organizations add-iam-policy-binding $FAST_ORG_ID \ --member user:$FAST_BU --role $role done ``` -7. Configure Billing Account permissions. + +7. Configure Billing Account permissions. If you are using a standalone billing account, the user applying this stage for the first time needs to be a Billing Administrator. + ```bash # find your billing account id with gcloud beta billing accounts list # replace with your billing id! @@ -60,9 +72,11 @@ export FAST_BA_ID=XXXXXX-YYYYYY-ZZZZZZ gcloud beta billing accounts add-iam-policy-binding $FAST_BA_ID \ --member user:$FAST_BU --role roles/billing.admin ``` -If you are using a billing account in a different organization, please follow [these steps](00-bootstrap#billing-account-in-a-different-organization) instead. + +If you are using a billing account in a different organization, please follow [these steps](0-bootstrap#billing-account-in-a-different-organization) instead. ## Stage 0 (Bootstrap) + This initial stage will create common projects for IaC, Logging & Billing, and bootstrap IAM policies. ```bash @@ -73,7 +87,9 @@ cd $FAST_PWD/00-bootstrap # then edit to match your environment! edit terraform.tfvars.sample ``` + Here you have a terraform.tfvars example: + ```hcl # fetch the required id by running `gcloud beta billing accounts list` billing_account={ @@ -99,7 +115,7 @@ terraform init terraform apply -var bootstrap_user=$FAST_BU # link the generated provider file -ln -s ~/fast-config/providers/00-bootstrap* . +ln -s ~/fast-config/providers/0-0-bootstrap* . # re-run init and apply to remove user-level IAM terraform init -migrate-state @@ -108,22 +124,27 @@ terraform apply ``` ## Stage 1 (Resource Management) + This stage performs two important tasks: + - Create the top-level hierarchy of folders, and the associated resources used later on to automate each part of the hierarchy (eg. Networking). - Set organization policies on the organization, and any exception required on specific folders. + ```bash # move to the 01-resman directory cd $FAST_PWD/01-resman # Link providers and variables from previous stages -ln -s ~/fast-config/providers/01-resman-providers.tf . -ln -s ~/fast-config/tfvars/00-bootstrap.auto.tfvars.json . +ln -s ~/fast-config/providers/1-0-resman-providers.tf . +ln -s ~/fast-config/tfvars/0-0-bootstrap.auto.tfvars.json . ln -s ~/fast-config/tfvars/globals.auto.tfvars.json . # Edit your terraform.tfvars to append Teams configuration (optional) edit terraform.tfvars ``` + In the following terraform.tfvars it is shown an example of configuration for teams provisioning: + ```hcl outputs_location = "~/fast-config" @@ -140,6 +161,7 @@ team_folders = { } } ``` + ```bash # run init and apply terraform init @@ -147,28 +169,34 @@ terraform apply ``` ## Stage 2 (Networking) + In this stage, we will deploy one of the 3 available Hub&Spoke networking topologies: + 1. VPC Peering 2. HA VPN 3. Multi-NIC appliances (NVA) + ```bash # move to the 02-networking-XXX directory (where XXX should be one of vpn|peering|nva) cd $FAST_PWD/02-networking-XXX # setup providers and variables from previous stages -ln -s ~/fast-config/providers/02-networking-providers.tf . -ln -s ~/fast-config/tfvars/00-bootstrap.auto.tfvars.json . -ln -s ~/fast-config/tfvars/01-resman.auto.tfvars.json . +ln -s ~/fast-config/providers/2-0-networking-providers.tf . +ln -s ~/fast-config/tfvars/0-0-bootstrap.auto.tfvars.json . +ln -s ~/fast-config/tfvars/1-0-resman.auto.tfvars.json . ln -s ~/fast-config/tfvars/globals.auto.tfvars.json . # Create terraform.tfvars. output_location variable is required to generate networking stage output file edit terraform.tfvars ``` + In the following terraform.tfvars we configure output_location variable to generate networking stage output file: + ```hcl # path for automatic generation of configs outputs_location = "~/fast-config" ``` + ```bash # run init and apply terraform init @@ -176,21 +204,25 @@ terraform apply ``` ## Stage 2 (Security) + This stage sets up security resources (KMS and VPC-SC) and configurations which impact the whole organization, or are shared across the hierarchy to other projects and teams. + ```bash # move to the 02-security directory cd $FAST_PWD/02-security # link providers and variables from previous stages -ln -s ~/fast-config/providers/02-security-providers.tf . -ln -s ~/fast-config/tfvars/00-bootstrap.auto.tfvars.json . -ln -s ~/fast-config/tfvars/01-resman.auto.tfvars.json . +ln -s ~/fast-config/providers/2-0-security-providers.tf . +ln -s ~/fast-config/tfvars/0-0-bootstrap.auto.tfvars.json . +ln -s ~/fast-config/tfvars/1-0-resman.auto.tfvars.json . ln -s ~/fast-config/tfvars/globals.auto.tfvars.json . # Edit terraform.tfvars to include KMS and/or VPC-SC configuration edit terraform.tfvars ``` -Some examples of terraform.tfvars configurations for KMS and VPC-SC can be found [here](02-security#customizations) + +Some examples of terraform.tfvars configurations for KMS and VPC-SC can be found [here](2-security#customizations) + ```bash # run init and apply terraform init @@ -198,15 +230,17 @@ terraform apply ``` ## Stage 3 (Project Factory) + The Project Factory stage builds on top of your foundations to create and set up projects (and related resources) to be used for your workloads. It is organized in folders representing environments (e.g. "dev", "prod"), each implemented by a stand-alone terraform resource factory. + ```bash # Variable `outputs_location` is set to `~/fast-config` -cd $FAST_PWD/03-project-factory/ENVIRONMENT -ln -s ~/fast-config/providers/03-project-factory-ENVIRONMENT-providers.tf . +cd $FAST_PWD/3-0-project-factory/ENVIRONMENT +ln -s ~/fast-config/providers/3-0-project-factory-ENVIRONMENT-providers.tf . -ln -s ~/fast-config/tfvars/00-bootstrap.auto.tfvars.json . -ln -s ~/fast-config/tfvars/01-resman.auto.tfvars.json . -ln -s ~/fast-config/tfvars/02-networking.auto.tfvars.json . +ln -s ~/fast-config/tfvars/0-0-bootstrap.auto.tfvars.json . +ln -s ~/fast-config/tfvars/1-0-resman.auto.tfvars.json . +ln -s ~/fast-config/tfvars/2-0-networking.auto.tfvars.json . ln -s ~/fast-config/tfvars/globals.auto.tfvars.json . # Define your environment default values (eg for billing alerts and labels) diff --git a/fast/stages/README.md b/fast/stages/README.md index 9b41bf1c..acb14be6 100644 --- a/fast/stages/README.md +++ b/fast/stages/README.md @@ -1,4 +1,4 @@ -# Fast stages +# FAST stages Each of the folders contained here is a separate "stage", or Terraform root module. @@ -9,7 +9,7 @@ When combined together, each stage is designed to leverage the previous stage's This has two important consequences - any stage can be swapped out and replaced by different code as long as it respects the contract by providing a predefined set of outputs and optionally accepting a predefined set of variables -- data flow between stages can be partially automated (see [stage 00 documentation on output files](./00-bootstrap/README.md#output-files-and-cross-stage-variables)), reducing the effort and pain required to compile variables by hand +- data flow between stages can be partially automated (see [stage 00 documentation on output files](./0-bootstrap/README.md#output-files-and-cross-stage-variables)), reducing the effort and pain required to compile variables by hand One important assumption is that the flow of data is always forward looking, so no stage needs to depend on outputs generated further down the chain. This greatly simplifies both the logic and the implementation, and allows stages to be effectively independent. @@ -19,28 +19,32 @@ Refer to each stage's documentation for a detailed description of its purpose, t To destroy a previous FAST deployment follow the instructions detailed in [cleanup](CLEANUP.md). -## Organizational level (00-01) +## Organization (0 and 1) -- [Bootstrap](00-bootstrap/README.md) +- [Bootstrap](0-bootstrap/README.md) Enables critical organization-level functionality that depends on broad permissions. It has two primary purposes. The first is to bootstrap the resources needed for automation of this and the following stages (service accounts, GCS buckets). And secondly, it applies the minimum amount of configuration needed at the organization level, to avoid the need of broad permissions later on, and to implement a minimum of security features like sinks and exports from the start.\ Exports: automation variables, organization-level custom roles -- [Resource Management](01-resman/README.md) +- [Resource Management](1-resman/README.md) Creates the base resource hierarchy (folders) and the automation resources required later to delegate deployment of each part of the hierarchy to separate stages. This stage also configures organization-level policies and any exceptions needed by different branches of the resource hierarchy.\ Exports: folder ids, automation service account emails -## Shared resources (02) +## Multitenancy -- [Security](02-security/README.md) +Implemented via separate stages that configure separate FAST-enabled hierarchies for each tenant, check the [multitenant stages folder](../stages-multitenant/). + +## Shared resources (2) + +- [Security](2-security/README.md) Manages centralized security configurations in a separate stage, and is typically owned by the security team. This stage implements VPC Security Controls via separate perimeters for environments and central services, and creates projects to host centralized KMS keys used by the whole organization. It's meant to be easily extended to include other security-related resources which are required, like Secret Manager.\ Exports: KMS key ids -- Networking ([VPN](02-networking-vpn/README.md)/[NVA](02-networking-nva/README.md)/[Peering](02-networking-separate-envs/README.md)/[Separate environments](02-networking-separate-envs/README.md)) - Manages centralized network resources in a separate stage, and is typically owned by the networking team. This stage implements a hub-and-spoke design, and includes connectivity via VPN to on-premises, and YAML-based factories for firewall rules (hierarchical and VPC-level) and subnets. It's currently available in four flavors: [spokes connected via VPN](02-networking-vpn/README.md), [and spokes connected via appliances](02-networking-nva/README.md), [spokes connected via VPC peering](02-networking-peering/README.md), and [separated network environments](02-networking-separate-envs/README.md).\ +- Networking ([Peering](2-networking-a-peering/README.md)/[VPN](2-networking-b-vpn/README.md)/[NVA](2-networking-c-nva/README.md)/[Separate environments](2-networking-d-separate-envs/README.md)) + Manages centralized network resources in a separate stage, and is typically owned by the networking team. This stage implements a hub-and-spoke design, and includes connectivity via VPN to on-premises, and YAML-based factories for firewall rules (hierarchical and VPC-level) and subnets. It's currently available in four flavors: [spokes connected via VPC peering](2-networking-a-peering/README.md), [spokes connected via VPN](2-networking-b-vpn/README.md), [and spokes connected via appliances](2-networking-c-nva/README.md), and [separated network environments](2-networking-d-separate-envs/README.md).\ Exports: host project ids and numbers, vpc self links -## Environment-level resources (03) +## Environment-level resources (3) -- [Project Factory](03-project-factory/dev/) +- [Project Factory](3-project-factory/dev/) YAML-based fatory to create and configure application or team-level projects. Configuration includes VPC-level settings for Shared VPC, service-level configuration for CMEK encryption via centralized keys, and service account creation for workloads and applications. This stage is meant to be used once per environment. -- [Data Platform](03-data-platform/dev/) -- [GKE Multitenant](03-gke-multitenant/dev/) +- [Data Platform](3-data-platform/dev/) +- [GKE Multitenant](3-gke-multitenant/dev/) - GCE Migration (in development) diff --git a/modules/__experimental/net-neg/versions.tf b/modules/__experimental/net-neg/versions.tf index 4900174a..08492c6f 100644 --- a/modules/__experimental/net-neg/versions.tf +++ b/modules/__experimental/net-neg/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/api-gateway/versions.tf b/modules/api-gateway/versions.tf index 4900174a..08492c6f 100644 --- a/modules/api-gateway/versions.tf +++ b/modules/api-gateway/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/apigee/versions.tf b/modules/apigee/versions.tf index 4900174a..08492c6f 100644 --- a/modules/apigee/versions.tf +++ b/modules/apigee/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/artifact-registry/versions.tf b/modules/artifact-registry/versions.tf index 4900174a..08492c6f 100644 --- a/modules/artifact-registry/versions.tf +++ b/modules/artifact-registry/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/bigquery-dataset/versions.tf b/modules/bigquery-dataset/versions.tf index 4900174a..08492c6f 100644 --- a/modules/bigquery-dataset/versions.tf +++ b/modules/bigquery-dataset/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/bigtable-instance/versions.tf b/modules/bigtable-instance/versions.tf index 4900174a..08492c6f 100644 --- a/modules/bigtable-instance/versions.tf +++ b/modules/bigtable-instance/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/billing-budget/versions.tf b/modules/billing-budget/versions.tf index 4900174a..08492c6f 100644 --- a/modules/billing-budget/versions.tf +++ b/modules/billing-budget/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/binauthz/versions.tf b/modules/binauthz/versions.tf index 4900174a..08492c6f 100644 --- a/modules/binauthz/versions.tf +++ b/modules/binauthz/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/cloud-config-container/__need_fixing/onprem/versions.tf b/modules/cloud-config-container/__need_fixing/onprem/versions.tf index 4900174a..08492c6f 100644 --- a/modules/cloud-config-container/__need_fixing/onprem/versions.tf +++ b/modules/cloud-config-container/__need_fixing/onprem/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/cloud-config-container/coredns/versions.tf b/modules/cloud-config-container/coredns/versions.tf index 4900174a..08492c6f 100644 --- a/modules/cloud-config-container/coredns/versions.tf +++ b/modules/cloud-config-container/coredns/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/cloud-config-container/cos-generic-metadata/versions.tf b/modules/cloud-config-container/cos-generic-metadata/versions.tf index 4900174a..08492c6f 100644 --- a/modules/cloud-config-container/cos-generic-metadata/versions.tf +++ b/modules/cloud-config-container/cos-generic-metadata/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/cloud-config-container/envoy-traffic-director/versions.tf b/modules/cloud-config-container/envoy-traffic-director/versions.tf index 4900174a..08492c6f 100644 --- a/modules/cloud-config-container/envoy-traffic-director/versions.tf +++ b/modules/cloud-config-container/envoy-traffic-director/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/cloud-config-container/mysql/versions.tf b/modules/cloud-config-container/mysql/versions.tf index 4900174a..08492c6f 100644 --- a/modules/cloud-config-container/mysql/versions.tf +++ b/modules/cloud-config-container/mysql/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/cloud-config-container/nginx-tls/versions.tf b/modules/cloud-config-container/nginx-tls/versions.tf index 4900174a..08492c6f 100644 --- a/modules/cloud-config-container/nginx-tls/versions.tf +++ b/modules/cloud-config-container/nginx-tls/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/cloud-config-container/nginx/versions.tf b/modules/cloud-config-container/nginx/versions.tf index 4900174a..08492c6f 100644 --- a/modules/cloud-config-container/nginx/versions.tf +++ b/modules/cloud-config-container/nginx/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/cloud-config-container/simple-nva/versions.tf b/modules/cloud-config-container/simple-nva/versions.tf index 4900174a..08492c6f 100644 --- a/modules/cloud-config-container/simple-nva/versions.tf +++ b/modules/cloud-config-container/simple-nva/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/cloud-config-container/squid/versions.tf b/modules/cloud-config-container/squid/versions.tf index 4900174a..08492c6f 100644 --- a/modules/cloud-config-container/squid/versions.tf +++ b/modules/cloud-config-container/squid/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/cloud-function/versions.tf b/modules/cloud-function/versions.tf index 4900174a..08492c6f 100644 --- a/modules/cloud-function/versions.tf +++ b/modules/cloud-function/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/cloud-identity-group/versions.tf b/modules/cloud-identity-group/versions.tf index 4900174a..08492c6f 100644 --- a/modules/cloud-identity-group/versions.tf +++ b/modules/cloud-identity-group/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/cloud-run/versions.tf b/modules/cloud-run/versions.tf index 4900174a..08492c6f 100644 --- a/modules/cloud-run/versions.tf +++ b/modules/cloud-run/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/cloudsql-instance/versions.tf b/modules/cloudsql-instance/versions.tf index 4900174a..08492c6f 100644 --- a/modules/cloudsql-instance/versions.tf +++ b/modules/cloudsql-instance/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/compute-mig/versions.tf b/modules/compute-mig/versions.tf index 4900174a..08492c6f 100644 --- a/modules/compute-mig/versions.tf +++ b/modules/compute-mig/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/compute-vm/README.md b/modules/compute-vm/README.md index 2f715555..7cfaf068 100644 --- a/modules/compute-vm/README.md +++ b/modules/compute-vm/README.md @@ -8,6 +8,23 @@ This module can operate in two distinct modes: In both modes, an optional service account can be created and assigned to either instances or template. If you need a managed instance group when using the module in template mode, refer to the [`compute-mig`](../compute-mig) module. ## Examples +- [Instance using defaults](#instance-using-defaults) +- [Service account management](#service-account-management) +- [Disk management](#disk-management) + - [Disk sources](#disk-sources) + - [Disk types and options](#disk-types-and-options) +- [Network interfaces](#network-interfaces) + - [Internal and external IPs](#internal-and-external-ips) + - [Using Alias IPs](#using-alias-ips) + - [Using gVNIC](#using-gvnic) +- [Metadata](#metadata) +- [IAM](#iam) +- [Spot VM](#spot-vm) +- [Confidential compute](#confidential-compute) +- [Disk encryption with Cloud KMS](#disk-encryption-with-cloud-kms) +- [Instance template](#instance-template) +- [Instance group](#instance-group) + ### Instance using defaults @@ -25,47 +42,73 @@ module "simple-vm-example" { }] service_account_create = true } -# tftest modules=1 resources=2 - +# tftest modules=1 resources=2 inventory=simple.yaml ``` -### Spot VM +### Service account management -[Spot VMs](https://cloud.google.com/compute/docs/instances/spot) are ephemeral compute instances suitable for batch jobs and fault-tolerant workloads. Spot VMs provide new features that [preemptible instances](https://cloud.google.com/compute/docs/instances/preemptible) do not support, such as the absence of a maximum runtime. +VM service accounts can be managed in three different ways: +- You can let the module create a service account for you by settting `service_account_create = true` +- You can use an existing service account by setting `service_account_create = false` (the default value) and passing the full email address of the service account to the `service_account` variable. This is useful, for example, if you want to reuse the service account from another previously created instance, or if you want to create the service account manually with the `iam-service-account` module. In this case, you probably also want to set `service_account_scopes` to `cloud-platform`. +- Lastly, you can use the default compute service account by setting `service_account_crate = false`. Please note that using the default compute service account is not recommended. ```hcl -module "spot-vm-example" { +module "vm-managed-sa-example" { source = "./fabric/modules/compute-vm" project_id = var.project_id zone = "europe-west1-b" - name = "test" - options = { - spot = true - termination_action = "STOP" - } + name = "test1" network_interfaces = [{ network = var.vpc.self_link subnetwork = var.subnet.self_link }] service_account_create = true } -# tftest modules=1 resources=2 +module "vm-managed-sa-example2" { + source = "./fabric/modules/compute-vm" + project_id = var.project_id + zone = "europe-west1-b" + name = "test2" + network_interfaces = [{ + network = var.vpc.self_link + subnetwork = var.subnet.self_link + }] + service_account = module.vm-managed-sa-example.service_account_email + service_account_scopes = ["cloud-platform"] +} + +# not recommended +module "vm-default-sa-example2" { + source = "./fabric/modules/compute-vm" + project_id = var.project_id + zone = "europe-west1-b" + name = "test3" + network_interfaces = [{ + network = var.vpc.self_link + subnetwork = var.subnet.self_link + }] + service_account_create = false +} + +# tftest modules=3 resources=4 inventory=sas.yaml ``` -### Disk sources +### Disk management + +#### Disk sources Attached disks can be created and optionally initialized from a pre-existing source, or attached to VMs when pre-existing. The `source` and `source_type` attributes of the `attached_disks` variable allows several modes of operation: -- `source_type = "image"` can be used with zonal disks in instances and templates, set `source` to the image name or link -- `source_type = "snapshot"` can be used with instances only, set `source` to the snapshot name or link -- `source_type = "attach"` can be used for both instances and templates to attach an existing disk, set source to the name (for zonal disks) or link (for regional disks) of the existing disk to attach; no disk will be created +- `source_type = "image"` can be used with zonal disks in instances and templates, set `source` to the image name or self link +- `source_type = "snapshot"` can be used with instances only, set `source` to the snapshot name or self link +- `source_type = "attach"` can be used for both instances and templates to attach an existing disk, set source to the name (for zonal disks) or self link (for regional disks) of the existing disk to attach; no disk will be created - `source_type = null` can be used where an empty disk is needed, `source` becomes irrelevant and can be left null This is an example of attaching a pre-existing regional PD to a new instance: ```hcl -module "simple-vm-example" { +module "vm-disks-example" { source = "./fabric/modules/compute-vm" project_id = var.project_id zone = "${var.region}-b" @@ -91,7 +134,7 @@ module "simple-vm-example" { And the same example for an instance template (where not using the full self link of the disk triggers recreation of the template) ```hcl -module "simple-vm-example" { +module "vm-disks-example" { source = "./fabric/modules/compute-vm" project_id = var.project_id zone = "${var.region}-b" @@ -115,39 +158,82 @@ module "simple-vm-example" { # tftest modules=1 resources=2 ``` -### Disk encryption with Cloud KMS +#### Disk types and options -This example shows how to control disk encryption via the the `encryption` variable, in this case the self link to a KMS CryptoKey that will be used to encrypt boot and attached disk. Managing the key with the `../kms` module is of course possible, but is not shown here. +The `attached_disks` variable exposes an `option` attribute that can be used to fine tune the configuration of each disk. The following example shows a VM with multiple disks ```hcl -module "kms-vm-example" { +module "vm-disk-options-example" { source = "./fabric/modules/compute-vm" project_id = var.project_id zone = "europe-west1-b" - name = "kms-test" + name = "test" network_interfaces = [{ network = var.vpc.self_link subnetwork = var.subnet.self_link }] attached_disks = [ { - name = "attached-disk" - size = 10 + name = "data1" + size = "10" + source_type = "image" + source = "image-1" + options = { + auto_delete = false + replica_zone = "europe-west1-c" + } + }, + { + name = "data2" + size = "20" + source_type = "snapshot" + source = "snapshot-2" + options = { + type = "pd-ssd" + mode = "READ_ONLY" + } } ] service_account_create = true - boot_disk = { - image = "projects/debian-cloud/global/images/family/debian-10" - } - encryption = { - encrypt_boot = true - kms_key_self_link = var.kms_key.self_link - } } -# tftest modules=1 resources=3 +# tftest modules=1 resources=4 inventory=disk-options.yaml ``` -### Using Alias IPs +### Network interfaces + +#### Internal and external IPs + +By default VNs are create with an automatically assigned IP addresses, but you can change it through the `addreses` and `nat` attributes of the `network_interfaces` variable: + +```hcl +module "vm-internal-ip" { + source = "./fabric/modules/compute-vm" + project_id = "my-project" + zone = "europe-west1-b" + name = "vm-internal-ip" + network_interfaces = [{ + network = var.vpc.self_link + subnetwork = var.subnet.self_link + addresses = { external = null, internal = "10.0.0.2" } + }] +} + +module "vm-external-ip" { + source = "./fabric/modules/compute-vm" + project_id = "my-project" + zone = "europe-west1-b" + name = "vm-external-ip" + network_interfaces = [{ + network = var.vpc.self_link + subnetwork = var.subnet.self_link + nat = true + addresses = { external = "8.8.8.8", internal = null } + }] +} +# tftest modules=2 resources=2 inventory=ips.yaml +``` + +#### Using Alias IPs This example shows how to add additional [Alias IPs](https://cloud.google.com/vpc/docs/alias-ip) to your VM. @@ -164,12 +250,11 @@ module "vm-with-alias-ips" { alias1 = "10.16.0.10/32" } }] - service_account_create = true } -# tftest modules=1 resources=2 +# tftest modules=1 resources=1 inventory=alias-ips.yaml ``` -### Using gVNIC +#### Using gVNIC This example shows how to enable [gVNIC](https://cloud.google.com/compute/docs/networking/using-gvnic) on your VM by customizing a `cos` image. Given that gVNIC needs to be enabled as an instance configuration and as a guest os configuration, you'll need to supply a bootable disk with `guest_os_features=GVNIC`. `SEV_CAPABLE`, `UEFI_COMPATIBLE` and `VIRTIO_SCSI_MULTIQUEUE` are enabled implicitly in the `cos`, `rhel`, `centos` and other images. @@ -210,9 +295,151 @@ module "vm-with-gvnic" { }] service_account_create = true } -# tftest modules=1 resources=3 +# tftest modules=1 resources=3 inventory=gvnic.yaml ``` +### Metadata + +You can define labels and custom metadata values. Metadata can be leveraged, for example, to define a custom startup script. + +```hcl +module "vm-metadata-example" { + source = "./fabric/modules/compute-vm" + project_id = var.project_id + zone = "europe-west1-b" + name = "nginx-server" + network_interfaces = [{ + network = var.vpc.self_link + subnetwork = var.subnet.self_link + }] + labels = { + env = "dev" + system = "crm" + } + metadata = { + startup-script = <<-EOF + #! /bin/bash + apt-get update + apt-get install -y nginx + EOF + } + service_account_create = true +} +# tftest modules=1 resources=2 inventory=metadata.yaml +``` + +### IAM + +Like most modules, you can assign IAM roles to the instance using the `iam` variable. + +```hcl +module "vm-iam-example" { + source = "./fabric/modules/compute-vm" + project_id = var.project_id + zone = "europe-west1-b" + name = "webserver" + network_interfaces = [{ + network = var.vpc.self_link + subnetwork = var.subnet.self_link + }] + iam = { + "roles/compute.instanceAdmin" = [ + "group:webserver@example.com", + "group:admin@example.com" + ] + } +} +# tftest modules=1 resources=2 inventory=iam.yaml + +``` + +### Spot VM + +[Spot VMs](https://cloud.google.com/compute/docs/instances/spot) are ephemeral compute instances suitable for batch jobs and fault-tolerant workloads. Spot VMs provide new features that [preemptible instances](https://cloud.google.com/compute/docs/instances/preemptible) do not support, such as the absence of a maximum runtime. + +```hcl +module "spot-vm-example" { + source = "./fabric/modules/compute-vm" + project_id = var.project_id + zone = "europe-west1-b" + name = "test" + options = { + spot = true + termination_action = "STOP" + } + network_interfaces = [{ + network = var.vpc.self_link + subnetwork = var.subnet.self_link + }] +} +# tftest modules=1 resources=1 inventory=spot.yaml +``` + +### Confidential compute + +You can enable confidential compute with the `confidential_compute` variable, which can be used for standalone instances or for instance templates. + +```hcl +module "vm-confidential-example" { + source = "./fabric/modules/compute-vm" + project_id = var.project_id + zone = "europe-west1-b" + name = "confidential-vm" + confidential_compute = true + network_interfaces = [{ + network = var.vpc.self_link + subnetwork = var.subnet.self_link + }] + +} + +module "template-confidential-example" { + source = "./fabric/modules/compute-vm" + project_id = var.project_id + zone = "europe-west1-b" + name = "confidential-template" + confidential_compute = true + create_template = true + network_interfaces = [{ + network = var.vpc.self_link + subnetwork = var.subnet.self_link + }] +} + +# tftest modules=2 resources=2 inventory=confidential.yaml +``` + +### Disk encryption with Cloud KMS + +This example shows how to control disk encryption via the the `encryption` variable, in this case the self link to a KMS CryptoKey that will be used to encrypt boot and attached disk. Managing the key with the `../kms` module is of course possible, but is not shown here. + +```hcl +module "kms-vm-example" { + source = "./fabric/modules/compute-vm" + project_id = var.project_id + zone = "europe-west1-b" + name = "kms-test" + network_interfaces = [{ + network = var.vpc.self_link + subnetwork = var.subnet.self_link + }] + attached_disks = [{ + name = "attached-disk" + size = 10 + }] + service_account_create = true + boot_disk = { + image = "projects/debian-cloud/global/images/family/debian-10" + } + encryption = { + encrypt_boot = true + kms_key_self_link = var.kms_key.self_link + } +} +# tftest modules=1 resources=3 inventory=cmek.yaml +``` + + ### Instance template This example shows how to use the module to manage an instance template that defines an additional attached disk for each instance, and overrides defaults for the boot disk image and service account. @@ -239,7 +466,7 @@ module "cos-test" { service_account = "vm-default@my-project.iam.gserviceaccount.com" create_template = true } -# tftest modules=1 resources=1 +# tftest modules=1 resources=1 inventory=template.yaml ``` ### Instance group @@ -270,7 +497,7 @@ module "instance-group" { } group = { named_ports = {} } } -# tftest modules=1 resources=2 +# tftest modules=1 resources=2 inventory=group.yaml ``` diff --git a/modules/compute-vm/versions.tf b/modules/compute-vm/versions.tf index 4900174a..08492c6f 100644 --- a/modules/compute-vm/versions.tf +++ b/modules/compute-vm/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/container-registry/versions.tf b/modules/container-registry/versions.tf index 4900174a..08492c6f 100644 --- a/modules/container-registry/versions.tf +++ b/modules/container-registry/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/data-catalog-policy-tag/versions.tf b/modules/data-catalog-policy-tag/versions.tf index 4900174a..08492c6f 100644 --- a/modules/data-catalog-policy-tag/versions.tf +++ b/modules/data-catalog-policy-tag/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/datafusion/versions.tf b/modules/datafusion/versions.tf index 4900174a..08492c6f 100644 --- a/modules/datafusion/versions.tf +++ b/modules/datafusion/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/dns/versions.tf b/modules/dns/versions.tf index 4900174a..08492c6f 100644 --- a/modules/dns/versions.tf +++ b/modules/dns/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/endpoints/versions.tf b/modules/endpoints/versions.tf index 4900174a..08492c6f 100644 --- a/modules/endpoints/versions.tf +++ b/modules/endpoints/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/folder/versions.tf b/modules/folder/versions.tf index 4900174a..08492c6f 100644 --- a/modules/folder/versions.tf +++ b/modules/folder/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/gcs/versions.tf b/modules/gcs/versions.tf index 4900174a..08492c6f 100644 --- a/modules/gcs/versions.tf +++ b/modules/gcs/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/gke-cluster/README.md b/modules/gke-cluster/README.md index dabead4f..df7f14ca 100644 --- a/modules/gke-cluster/README.md +++ b/modules/gke-cluster/README.md @@ -103,25 +103,25 @@ module "cluster-autopilot" { | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [location](variables.tf#L118) | Cluster zone or region. | string | ✓ | | -| [name](variables.tf#L175) | Cluster name. | string | ✓ | | -| [project_id](variables.tf#L201) | Cluster project id. | string | ✓ | | -| [vpc_config](variables.tf#L218) | VPC-level configuration. | object({…}) | ✓ | | +| [location](variables.tf#L119) | Cluster zone or region. | string | ✓ | | +| [name](variables.tf#L176) | Cluster name. | string | ✓ | | +| [project_id](variables.tf#L202) | Cluster project id. | string | ✓ | | +| [vpc_config](variables.tf#L219) | VPC-level configuration. | object({…}) | ✓ | | | [cluster_autoscaling](variables.tf#L17) | Enable and configure limits for Node Auto-Provisioning with Cluster Autoscaler. | object({…}) | | null | | [description](variables.tf#L38) | Cluster description. | string | | null | | [enable_addons](variables.tf#L44) | Addons enabled in the cluster (true means enabled). | object({…}) | | {…} | -| [enable_features](variables.tf#L68) | Enable cluster-level features. Certain features allow configuration. | object({…}) | | {…} | -| [issue_client_certificate](variables.tf#L106) | Enable issuing client certificate. | bool | | false | -| [labels](variables.tf#L112) | Cluster resource labels. | map(string) | | null | -| [logging_config](variables.tf#L123) | Logging configuration. | list(string) | | ["SYSTEM_COMPONENTS"] | -| [maintenance_config](variables.tf#L129) | Maintenance window configuration. | object({…}) | | {…} | -| [max_pods_per_node](variables.tf#L152) | Maximum number of pods per node in this cluster. | number | | 110 | -| [min_master_version](variables.tf#L158) | Minimum version of the master, defaults to the version of the most recent official release. | string | | null | -| [monitoring_config](variables.tf#L164) | Monitoring components. | object({…}) | | {…} | -| [node_locations](variables.tf#L180) | Zones in which the cluster's nodes are located. | list(string) | | [] | -| [private_cluster_config](variables.tf#L187) | Private cluster configuration. | object({…}) | | null | -| [release_channel](variables.tf#L206) | Release channel for GKE upgrades. | string | | null | -| [tags](variables.tf#L212) | Network tags applied to nodes. | list(string) | | null | +| [enable_features](variables.tf#L68) | Enable cluster-level features. Certain features allow configuration. | object({…}) | | {…} | +| [issue_client_certificate](variables.tf#L107) | Enable issuing client certificate. | bool | | false | +| [labels](variables.tf#L113) | Cluster resource labels. | map(string) | | null | +| [logging_config](variables.tf#L124) | Logging configuration. | list(string) | | ["SYSTEM_COMPONENTS"] | +| [maintenance_config](variables.tf#L130) | Maintenance window configuration. | object({…}) | | {…} | +| [max_pods_per_node](variables.tf#L153) | Maximum number of pods per node in this cluster. | number | | 110 | +| [min_master_version](variables.tf#L159) | Minimum version of the master, defaults to the version of the most recent official release. | string | | null | +| [monitoring_config](variables.tf#L165) | Monitoring components. | object({…}) | | {…} | +| [node_locations](variables.tf#L181) | Zones in which the cluster's nodes are located. | list(string) | | [] | +| [private_cluster_config](variables.tf#L188) | Private cluster configuration. | object({…}) | | null | +| [release_channel](variables.tf#L207) | Release channel for GKE upgrades. | string | | null | +| [tags](variables.tf#L213) | Network tags applied to nodes. | list(string) | | null | ## Outputs diff --git a/modules/gke-cluster/main.tf b/modules/gke-cluster/main.tf index f55f46e8..83604c00 100644 --- a/modules/gke-cluster/main.tf +++ b/modules/gke-cluster/main.tf @@ -55,15 +55,18 @@ resource "google_container_cluster" "cluster" { # the default nodepool is deleted here, use the gke-nodepool module instead # default nodepool configuration based on a shielded_nodes variable - node_config { - dynamic "shielded_instance_config" { - for_each = var.enable_features.shielded_nodes ? [""] : [] - content { - enable_secure_boot = true - enable_integrity_monitoring = true + dynamic "node_config" { + for_each = var.enable_features.autopilot ? [] : [""] + content { + dynamic "shielded_instance_config" { + for_each = var.enable_features.shielded_nodes ? [""] : [] + content { + enable_secure_boot = true + enable_integrity_monitoring = true + } } + tags = var.tags } - tags = var.tags } @@ -262,6 +265,13 @@ resource "google_container_cluster" "cluster" { } } + dynamic "mesh_certificates" { + for_each = var.enable_features.mesh_certificates != null ? [""] : [] + content { + enable_certificates = var.enable_features.mesh_certificates + } + } + dynamic "monitoring_config" { for_each = var.monitoring_config != null && !var.enable_features.autopilot ? [""] : [] content { diff --git a/modules/gke-cluster/variables.tf b/modules/gke-cluster/variables.tf index f02ed504..ecfa11c9 100644 --- a/modules/gke-cluster/variables.tf +++ b/modules/gke-cluster/variables.tf @@ -84,6 +84,7 @@ variable "enable_features" { groups_for_rbac = optional(string) intranode_visibility = optional(bool, false) l4_ilb_subsetting = optional(bool, false) + mesh_certificates = optional(bool) pod_security_policy = optional(bool, false) resource_usage_export = optional(object({ dataset = string @@ -232,4 +233,4 @@ variable "vpc_config" { master_authorized_ranges = optional(map(string)) }) nullable = false -} \ No newline at end of file +} diff --git a/modules/gke-cluster/versions.tf b/modules/gke-cluster/versions.tf index 4900174a..08492c6f 100644 --- a/modules/gke-cluster/versions.tf +++ b/modules/gke-cluster/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/gke-hub/README.md b/modules/gke-hub/README.md index 6afcd1c8..793a81ca 100644 --- a/modules/gke-hub/README.md +++ b/modules/gke-hub/README.md @@ -141,6 +141,13 @@ module "project" { ] } +resource "google_project_iam_member" "gkehub_fix" { + member = "serviceAccount:${module.project.service_accounts.robots.fleet}" + project = module.project.project_id + role = "roles/gkehub.serviceAgent" +} + + module "vpc" { source = "./fabric/modules/net-vpc" project_id = module.project.project_id @@ -151,7 +158,7 @@ module "vpc" { ip_cidr_range = "10.0.1.0/24" name = "subnet-cluster-1" region = "europe-west1" - secondary_ip_range = { + secondary_ip_ranges = { pods = "10.1.0.0/16" services = "10.2.0.0/24" } @@ -160,16 +167,16 @@ module "vpc" { ip_cidr_range = "10.0.2.0/24" name = "subnet-cluster-2" region = "europe-west4" - secondary_ip_range = { + secondary_ip_ranges = { pods = "10.3.0.0/16" services = "10.4.0.0/24" } }, { - ip_cidr_range = "10.0.0.0/28" - name = "subnet-mgmt" - region = "europe-west1" - secondary_ip_range = null + ip_cidr_range = "10.0.0.0/28" + name = "subnet-mgmt" + region = "europe-west1" + secondary_ip_ranges = null } ] } @@ -222,18 +229,24 @@ module "cluster_1" { enable_private_endpoint = false master_global_access = true } + release_channel = "REGULAR" labels = { mesh_id = "proj-${module.project.number}" } + enable_features = { + workload_identity = true + dataplane_v2 = true + } } module "cluster_1_nodepool" { source = "./fabric/modules/gke-nodepool" project_id = module.project.project_id cluster_name = module.cluster_1.name + cluster_id = module.cluster_1.id location = "europe-west1" - name = "nodepool" + name = "cluster-1-nodepool" node_count = { initial = 1 } service_account = { create = true } tags = ["cluster-1-node"] @@ -261,14 +274,19 @@ module "cluster_2" { labels = { mesh_id = "proj-${module.project.number}" } + enable_features = { + workload_identity = true + dataplane_v2 = true + } } module "cluster_2_nodepool" { source = "./fabric/modules/gke-nodepool" project_id = module.project.project_id cluster_name = module.cluster_2.name + cluster_id = module.cluster_2.id location = "europe-west4" - name = "nodepool" + name = "cluster-2-nodepool" node_count = { initial = 1 } service_account = { create = true } tags = ["cluster-2-node"] @@ -277,6 +295,7 @@ module "cluster_2_nodepool" { module "hub" { source = "./fabric/modules/gke-hub" project_id = module.project.project_id + depends_on = [google_project_iam_member.gkehub_fix] clusters = { cluster-1 = module.cluster_1.id cluster-2 = module.cluster_2.id @@ -295,7 +314,7 @@ module "hub" { ] } -# tftest modules=8 resources=30 +# tftest modules=8 resources=31 ``` diff --git a/modules/gke-hub/versions.tf b/modules/gke-hub/versions.tf index 4900174a..08492c6f 100644 --- a/modules/gke-hub/versions.tf +++ b/modules/gke-hub/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/gke-nodepool/versions.tf b/modules/gke-nodepool/versions.tf index 4900174a..08492c6f 100644 --- a/modules/gke-nodepool/versions.tf +++ b/modules/gke-nodepool/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/iam-service-account/versions.tf b/modules/iam-service-account/versions.tf index 4900174a..08492c6f 100644 --- a/modules/iam-service-account/versions.tf +++ b/modules/iam-service-account/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/kms/versions.tf b/modules/kms/versions.tf index 4900174a..08492c6f 100644 --- a/modules/kms/versions.tf +++ b/modules/kms/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/logging-bucket/versions.tf b/modules/logging-bucket/versions.tf index 4900174a..08492c6f 100644 --- a/modules/logging-bucket/versions.tf +++ b/modules/logging-bucket/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/net-address/versions.tf b/modules/net-address/versions.tf index 4900174a..08492c6f 100644 --- a/modules/net-address/versions.tf +++ b/modules/net-address/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/net-cloudnat/versions.tf b/modules/net-cloudnat/versions.tf index 4900174a..08492c6f 100644 --- a/modules/net-cloudnat/versions.tf +++ b/modules/net-cloudnat/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/net-glb/versions.tf b/modules/net-glb/versions.tf index 4900174a..08492c6f 100644 --- a/modules/net-glb/versions.tf +++ b/modules/net-glb/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/net-ilb-l7/versions.tf b/modules/net-ilb-l7/versions.tf index 4900174a..08492c6f 100644 --- a/modules/net-ilb-l7/versions.tf +++ b/modules/net-ilb-l7/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/net-ilb/versions.tf b/modules/net-ilb/versions.tf index 4900174a..08492c6f 100644 --- a/modules/net-ilb/versions.tf +++ b/modules/net-ilb/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/net-interconnect-attachment-direct/versions.tf b/modules/net-interconnect-attachment-direct/versions.tf index 4900174a..08492c6f 100644 --- a/modules/net-interconnect-attachment-direct/versions.tf +++ b/modules/net-interconnect-attachment-direct/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/net-vpc-firewall/versions.tf b/modules/net-vpc-firewall/versions.tf index 4900174a..08492c6f 100644 --- a/modules/net-vpc-firewall/versions.tf +++ b/modules/net-vpc-firewall/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/net-vpc-peering/versions.tf b/modules/net-vpc-peering/versions.tf index 4900174a..08492c6f 100644 --- a/modules/net-vpc-peering/versions.tf +++ b/modules/net-vpc-peering/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/net-vpc/versions.tf b/modules/net-vpc/versions.tf index 4900174a..08492c6f 100644 --- a/modules/net-vpc/versions.tf +++ b/modules/net-vpc/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/net-vpn-dynamic/versions.tf b/modules/net-vpn-dynamic/versions.tf index 4900174a..08492c6f 100644 --- a/modules/net-vpn-dynamic/versions.tf +++ b/modules/net-vpn-dynamic/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/net-vpn-ha/versions.tf b/modules/net-vpn-ha/versions.tf index 4900174a..08492c6f 100644 --- a/modules/net-vpn-ha/versions.tf +++ b/modules/net-vpn-ha/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/net-vpn-static/versions.tf b/modules/net-vpn-static/versions.tf index 4900174a..08492c6f 100644 --- a/modules/net-vpn-static/versions.tf +++ b/modules/net-vpn-static/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/organization/README.md b/modules/organization/README.md index da44046a..b6caa3cd 100644 --- a/modules/organization/README.md +++ b/modules/organization/README.md @@ -462,13 +462,13 @@ module "org" { | [iam_bindings_authoritative](variables.tf#L116) | IAM authoritative bindings, in {ROLE => [MEMBERS]} format. Roles and members not explicitly listed will be cleared. Bindings should also be authoritative when using authoritative audit config. Use with caution. | map(list(string)) | | null | | [logging_exclusions](variables.tf#L122) | Logging exclusions for this organization in the form {NAME -> FILTER}. | map(string) | | {} | | [logging_sinks](variables.tf#L129) | Logging sinks to create for the organization. | map(object({…})) | | {} | -| [network_tags](variables.tf#L159) | Network tags by key name. The `iam` attribute behaves like the similarly named one at module level. | map(object({…})) | | {} | -| [org_policies](variables.tf#L180) | Organization policies applied to this organization keyed by policy name. | map(object({…})) | | {} | +| [network_tags](variables.tf#L159) | Network tags by key name. If `id` is provided, key creation is skipped. The `iam` attribute behaves like the similarly named one at module level. | map(object({…})) | | {} | +| [org_policies](variables.tf#L181) | Organization policies applied to this organization keyed by policy name. | map(object({…})) | | {} | | [org_policies_data_path](variables.tf#L220) | Path containing org policies in YAML format. | string | | null | | [org_policy_custom_constraints](variables.tf#L226) | Organization policiy custom constraints keyed by constraint name. | map(object({…})) | | {} | | [org_policy_custom_constraints_data_path](variables.tf#L240) | Path containing org policy custom constraints in YAML format. | string | | null | | [tag_bindings](variables.tf#L255) | Tag bindings for this organization, in key => tag value id format. | map(string) | | null | -| [tags](variables.tf#L261) | Tags by key name. The `iam` attribute behaves like the similarly named one at module level. | map(object({…})) | | {} | +| [tags](variables.tf#L261) | Tags by key name. If `id` is provided, key or value creation is skipped. The `iam` attribute behaves like the similarly named one at module level. | map(object({…})) | | {} | ## Outputs @@ -480,9 +480,9 @@ module "org" { | [firewall_policy_id](outputs.tf#L40) | Map of firewall policy ids created in the organization. | | | [network_tag_keys](outputs.tf#L45) | Tag key resources. | | | [network_tag_values](outputs.tf#L54) | Tag value resources. | | -| [organization_id](outputs.tf#L65) | Organization id dependent on module resources. | | -| [sink_writer_identities](outputs.tf#L82) | Writer identities created for each sink. | | -| [tag_keys](outputs.tf#L90) | Tag key resources. | | -| [tag_values](outputs.tf#L99) | Tag value resources. | | +| [organization_id](outputs.tf#L62) | Organization id dependent on module resources. | | +| [sink_writer_identities](outputs.tf#L79) | Writer identities created for each sink. | | +| [tag_keys](outputs.tf#L87) | Tag key resources. | | +| [tag_values](outputs.tf#L96) | Tag value resources. | | diff --git a/modules/organization/outputs.tf b/modules/organization/outputs.tf index 40d84b47..2e594ee6 100644 --- a/modules/organization/outputs.tf +++ b/modules/organization/outputs.tf @@ -54,11 +54,8 @@ output "network_tag_keys" { output "network_tag_values" { description = "Tag value resources." value = { - for k, v in google_tags_tag_value.default - : k => v if( - google_tags_tag_key.default[split("/", k)[0]].purpose != null && - google_tags_tag_key.default[split("/", k)[0]].purpose != "" - ) + for k, v in google_tags_tag_value.default : + k => v if local.tag_values[k].tag_network } } @@ -99,10 +96,7 @@ output "tag_keys" { output "tag_values" { description = "Tag value resources." value = { - for k, v in google_tags_tag_value.default - : k => v if( - google_tags_tag_key.default[split("/", k)[0]].purpose == null || - google_tags_tag_key.default[split("/", k)[0]].purpose == "" - ) + for k, v in google_tags_tag_value.default : + k => v if !local.tag_values[k].tag_network } } diff --git a/modules/organization/tags.tf b/modules/organization/tags.tf index 544b8989..b579479e 100644 --- a/modules/organization/tags.tf +++ b/modules/organization/tags.tf @@ -23,17 +23,21 @@ locals { "Managed by the Terraform organization module." ) key = "${tag}/${value}" + id = try(value_attrs.id, null) name = value roles = keys(coalesce( value_attrs == null ? null : value_attrs.iam, {} )) - tag = tag + tag = tag + tag_id = attrs.id + tag_network = try(attrs.network, null) != null } ] ]) _tag_values_iam = flatten([ for key, value_attrs in local.tag_values : [ for role in value_attrs.roles : { + id = value_attrs.id key = value_attrs.key name = value_attrs.name role = role @@ -44,8 +48,9 @@ locals { _tags_iam = flatten([ for tag, attrs in local.tags : [ for role in keys(coalesce(attrs.iam, {})) : { - role = role - tag = tag + role = role + tag = tag + tag_id = attrs.id } ] ]) @@ -64,7 +69,7 @@ locals { # keys resource "google_tags_tag_key" "default" { - for_each = local.tags + for_each = { for k, v in local.tags : k => v if v.id == null } parent = var.organization_id purpose = ( lookup(each.value, "network", null) == null ? null : "GCE_FIREWALL" @@ -83,8 +88,12 @@ resource "google_tags_tag_key" "default" { resource "google_tags_tag_key_iam_binding" "default" { for_each = local.tags_iam - tag_key = google_tags_tag_key.default[each.value.tag].id - role = each.value.role + tag_key = ( + each.value.tag_id == null + ? google_tags_tag_key.default[each.value.tag].id + : each.value.tag_id + ) + role = each.value.role members = coalesce( local.tags[each.value.tag]["iam"][each.value.role], [] ) @@ -93,16 +102,24 @@ resource "google_tags_tag_key_iam_binding" "default" { # values resource "google_tags_tag_value" "default" { - for_each = local.tag_values - parent = google_tags_tag_key.default[each.value.tag].id + for_each = { for k, v in local.tag_values : k => v if v.id == null } + parent = ( + each.value.tag_id == null + ? google_tags_tag_key.default[each.value.tag].id + : each.value.tag_id + ) short_name = each.value.name description = each.value.description } resource "google_tags_tag_value_iam_binding" "default" { - for_each = local.tag_values_iam - tag_value = google_tags_tag_value.default[each.value.key].id - role = each.value.role + for_each = local.tag_values_iam + tag_value = ( + each.value.id == null + ? google_tags_tag_value.default[each.value.key].id + : each.value.id + ) + role = each.value.role members = coalesce( local.tags[each.value.tag]["values"][each.value.name]["iam"][each.value.role], [] diff --git a/modules/organization/variables.tf b/modules/organization/variables.tf index 84c81ff5..ced5cad3 100644 --- a/modules/organization/variables.tf +++ b/modules/organization/variables.tf @@ -157,10 +157,11 @@ variable "logging_sinks" { } variable "network_tags" { - description = "Network tags by key name. The `iam` attribute behaves like the similarly named one at module level." + description = "Network tags by key name. If `id` is provided, key creation is skipped. The `iam` attribute behaves like the similarly named one at module level." type = map(object({ description = optional(string, "Managed by the Terraform organization module.") iam = optional(map(list(string)), {}) + id = optional(string) network = string # project_id/vpc_name values = optional(map(object({ description = optional(string, "Managed by the Terraform organization module.") @@ -193,7 +194,6 @@ variable "org_policies" { values = optional(list(string)) })) enforce = optional(bool, true) # for boolean policies only. - # conditional values rules = optional(list(object({ allow = optional(object({ @@ -259,13 +259,15 @@ variable "tag_bindings" { } variable "tags" { - description = "Tags by key name. The `iam` attribute behaves like the similarly named one at module level." + description = "Tags by key name. If `id` is provided, key or value creation is skipped. The `iam` attribute behaves like the similarly named one at module level." type = map(object({ description = optional(string, "Managed by the Terraform organization module.") iam = optional(map(list(string)), {}) + id = optional(string) values = optional(map(object({ description = optional(string, "Managed by the Terraform organization module.") iam = optional(map(list(string)), {}) + id = optional(string) })), {}) })) nullable = false diff --git a/modules/organization/versions.tf b/modules/organization/versions.tf index 4900174a..08492c6f 100644 --- a/modules/organization/versions.tf +++ b/modules/organization/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/project/versions.tf b/modules/project/versions.tf index 4900174a..08492c6f 100644 --- a/modules/project/versions.tf +++ b/modules/project/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/projects-data-source/versions.tf b/modules/projects-data-source/versions.tf index 4900174a..08492c6f 100644 --- a/modules/projects-data-source/versions.tf +++ b/modules/projects-data-source/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/pubsub/versions.tf b/modules/pubsub/versions.tf index 4900174a..08492c6f 100644 --- a/modules/pubsub/versions.tf +++ b/modules/pubsub/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/secret-manager/versions.tf b/modules/secret-manager/versions.tf index 4900174a..08492c6f 100644 --- a/modules/secret-manager/versions.tf +++ b/modules/secret-manager/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/service-directory/versions.tf b/modules/service-directory/versions.tf index 4900174a..08492c6f 100644 --- a/modules/service-directory/versions.tf +++ b/modules/service-directory/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/source-repository/versions.tf b/modules/source-repository/versions.tf index 4900174a..08492c6f 100644 --- a/modules/source-repository/versions.tf +++ b/modules/source-repository/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/modules/vpc-sc/versions.tf b/modules/vpc-sc/versions.tf index 4900174a..08492c6f 100644 --- a/modules/vpc-sc/versions.tf +++ b/modules/vpc-sc/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.48.0" # tftest + version = ">= 4.50.0" # tftest } } } diff --git a/tests/blueprints/cloud_operations/terraform-enterprise-wif/gcp-workload-identity-provider/__init__.py b/tests/blueprints/cloud_operations/terraform_enterprise_wif/__init__.py similarity index 100% rename from tests/blueprints/cloud_operations/terraform-enterprise-wif/gcp-workload-identity-provider/__init__.py rename to tests/blueprints/cloud_operations/terraform_enterprise_wif/__init__.py diff --git a/tests/blueprints/factories/bigquery_factory/__init__.py b/tests/blueprints/cloud_operations/terraform_enterprise_wif/gcp_workload_identity_provider/__init__.py similarity index 100% rename from tests/blueprints/factories/bigquery_factory/__init__.py rename to tests/blueprints/cloud_operations/terraform_enterprise_wif/gcp_workload_identity_provider/__init__.py diff --git a/tests/blueprints/cloud_operations/terraform-enterprise-wif/gcp-workload-identity-provider/fixture/main.tf b/tests/blueprints/cloud_operations/terraform_enterprise_wif/gcp_workload_identity_provider/fixture/main.tf similarity index 100% rename from tests/blueprints/cloud_operations/terraform-enterprise-wif/gcp-workload-identity-provider/fixture/main.tf rename to tests/blueprints/cloud_operations/terraform_enterprise_wif/gcp_workload_identity_provider/fixture/main.tf diff --git a/tests/blueprints/cloud_operations/terraform-enterprise-wif/gcp-workload-identity-provider/fixture/variables.tf b/tests/blueprints/cloud_operations/terraform_enterprise_wif/gcp_workload_identity_provider/fixture/variables.tf similarity index 100% rename from tests/blueprints/cloud_operations/terraform-enterprise-wif/gcp-workload-identity-provider/fixture/variables.tf rename to tests/blueprints/cloud_operations/terraform_enterprise_wif/gcp_workload_identity_provider/fixture/variables.tf diff --git a/tests/blueprints/cloud_operations/terraform-enterprise-wif/gcp-workload-identity-provider/test_plan.py b/tests/blueprints/cloud_operations/terraform_enterprise_wif/gcp_workload_identity_provider/test_plan.py similarity index 100% rename from tests/blueprints/cloud_operations/terraform-enterprise-wif/gcp-workload-identity-provider/test_plan.py rename to tests/blueprints/cloud_operations/terraform_enterprise_wif/gcp_workload_identity_provider/test_plan.py diff --git a/tests/blueprints/data_solutions/cmek_via_centralized_kms/fixture/main.tf b/tests/blueprints/data_solutions/cmek_via_centralized_kms/fixture/main.tf index 65cc20ae..3fee8af5 100644 --- a/tests/blueprints/data_solutions/cmek_via_centralized_kms/fixture/main.tf +++ b/tests/blueprints/data_solutions/cmek_via_centralized_kms/fixture/main.tf @@ -15,7 +15,10 @@ */ module "test" { - source = "../../../../../blueprints/data-solutions/cmek-via-centralized-kms/" - billing_account = var.billing_account - root_node = var.root_node + source = "../../../../../blueprints/data-solutions/cmek-via-centralized-kms/" + project_config = { + billing_account_id = "123456-123456-123456" + parent = "folders/12345678" + } + prefix = "prefix" } diff --git a/tests/blueprints/data_solutions/data_platform_foundations/test_plan.py b/tests/blueprints/data_solutions/data_platform_foundations/test_plan.py index 93961b5e..785f4705 100644 --- a/tests/blueprints/data_solutions/data_platform_foundations/test_plan.py +++ b/tests/blueprints/data_solutions/data_platform_foundations/test_plan.py @@ -21,5 +21,6 @@ FIXTURES_DIR = os.path.join(os.path.dirname(__file__), 'fixture') def test_resources(e2e_plan_runner): "Test that plan works and the numbers of resources is as expected." modules, resources = e2e_plan_runner(FIXTURES_DIR) - assert len(modules) == 38 - assert len(resources) == 286 + + assert len(modules) == 42 + assert len(resources) == 296 diff --git a/tests/blueprints/data_solutions/shielded_folder/simple.tfvars b/tests/blueprints/data_solutions/shielded_folder/simple.tfvars new file mode 100644 index 00000000..83e8b139 --- /dev/null +++ b/tests/blueprints/data_solutions/shielded_folder/simple.tfvars @@ -0,0 +1,20 @@ +access_policy_config = { + access_policy_create = { + parent = "organizations/1234567890123" + title = "ShieldedMVP" + } +} +folder_config = { + folder_create = { + display_name = "ShieldedMVP" + parent = "organizations/1234567890123" + } +} +organization = { + domain = "example.com" + id = "1122334455" +} +prefix = "prefix" +project_config = { + billing_account_id = "123456-123456-123456" +} diff --git a/tests/blueprints/data_solutions/shielded_folder/simple.yaml b/tests/blueprints/data_solutions/shielded_folder/simple.yaml new file mode 100644 index 00000000..244dcb97 --- /dev/null +++ b/tests/blueprints/data_solutions/shielded_folder/simple.yaml @@ -0,0 +1,51 @@ +# 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. + +values: + module.folder.google_compute_firewall_policy.policy["prefix-fw-policy"]: + short_name: prefix-fw-policy + module.folder.google_folder.folder[0]: + display_name: ShieldedMVP + parent: organizations/1234567890123 + module.log-export-project[0].google_project.project[0]: + billing_account: 123456-123456-123456 + project_id: prefix-audit-logs + module.vpc-sc[0].google_access_context_manager_access_policy.default[0]: + parent: organizations/1122334455 + title: shielded-folder + module.vpc-sc[0].google_access_context_manager_service_perimeter.regular["shielded"]: + description: null + perimeter_type: PERIMETER_TYPE_REGULAR + title: shielded + +counts: + google_access_context_manager_access_policy: 1 + google_access_context_manager_service_perimeter: 1 + google_bigquery_dataset: 1 + google_bigquery_dataset_iam_member: 2 + google_bigquery_default_service_account: 1 + google_compute_firewall_policy: 1 + google_compute_firewall_policy_rule: 4 + google_folder: 2 + google_folder_iam_binding: 2 + google_logging_folder_sink: 2 + google_org_policy_policy: 12 + google_project: 1 + google_project_iam_binding: 1 + google_project_service: 4 + google_project_service_identity: 1 + google_projects: 1 + google_storage_project_service_account: 1 + modules: 5 + resources: 38 diff --git a/tests/modules/gke_nodepool/__init__.py b/tests/blueprints/data_solutions/shielded_folder/tftest.yaml similarity index 84% rename from tests/modules/gke_nodepool/__init__.py rename to tests/blueprints/data_solutions/shielded_folder/tftest.yaml index 6d6d1266..3c0bcb8c 100644 --- a/tests/modules/gke_nodepool/__init__.py +++ b/tests/blueprints/data_solutions/shielded_folder/tftest.yaml @@ -1,4 +1,4 @@ -# Copyright 2022 Google LLC +# Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -11,3 +11,8 @@ # 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. + +module: blueprints/data-solutions/shielded-folder + +tests: + simple: diff --git a/tests/fast/stages/s00_bootstrap/__init__.py b/tests/blueprints/data_solutions/vertex_mlops/__init__.py similarity index 100% rename from tests/fast/stages/s00_bootstrap/__init__.py rename to tests/blueprints/data_solutions/vertex_mlops/__init__.py diff --git a/tests/blueprints/data_solutions/vertex_mlops/fixture/main.tf b/tests/blueprints/data_solutions/vertex_mlops/fixture/main.tf new file mode 100644 index 00000000..0b671f33 --- /dev/null +++ b/tests/blueprints/data_solutions/vertex_mlops/fixture/main.tf @@ -0,0 +1,39 @@ +/** + * 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 + * + * 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. + */ + +module "projects" { + source = "../../../../../blueprints/data-solutions/vertex-mlops/" + labels = { + "env" : "dev", + "team" : "ml" + } + bucket_name = "test-dev" + dataset_name = "test" + identity_pool_claims = "attribute.repository/ORGANIZATION/REPO" + notebooks = { + "myworkbench" : { + "owner" : "user@example.com", + "region" : "europe-west4", + "subnet" : "default", + } + } + prefix = "pref" + project_id = "test-dev" + project_create = { + billing_account_id = "000000-123456-123456" + parent = "folders/111111111111" + } +} diff --git a/tests/blueprints/factories/bigquery_factory/test_plan.py b/tests/blueprints/data_solutions/vertex_mlops/test_plan.py similarity index 71% rename from tests/blueprints/factories/bigquery_factory/test_plan.py rename to tests/blueprints/data_solutions/vertex_mlops/test_plan.py index 74705e42..eac30ad5 100644 --- a/tests/blueprints/factories/bigquery_factory/test_plan.py +++ b/tests/blueprints/data_solutions/vertex_mlops/test_plan.py @@ -11,9 +11,13 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +import os +import pytest + +FIXTURES_DIR = os.path.join(os.path.dirname(__file__), 'fixture') def test_resources(e2e_plan_runner): "Test that plan works and the numbers of resources is as expected." - modules, resources = e2e_plan_runner() - assert len(modules) > 0 - assert len(resources) > 0 + modules, resources = e2e_plan_runner(FIXTURES_DIR) + # TODO: to re-enable per-module resource count check print _, then test + assert len(modules) > 0 and len(resources) > 0 \ No newline at end of file diff --git a/tests/blueprints/factories/bigquery_factory/examples/simple.yaml b/tests/blueprints/factories/bigquery_factory/examples/simple.yaml new file mode 100644 index 00000000..d32492d6 --- /dev/null +++ b/tests/blueprints/factories/bigquery_factory/examples/simple.yaml @@ -0,0 +1,40 @@ +# 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. + +values: + module.bq.module.bq["my_dataset"].google_bigquery_dataset.default: + dataset_id: my_dataset + project: project-id + module.bq.module.bq["my_dataset"].google_bigquery_table.default["countries"]: + dataset_id: my_dataset + friendly_name: countries + labels: + env: prod + project: project-id + schema: '[{"name":"country","type":"STRING"},{"name":"population","type":"INT64"}]' + table_id: countries + module.bq.module.bq["my_dataset"].google_bigquery_table.views["department"]: + dataset_id: my_dataset + friendly_name: department + labels: + env: prod + project: project-id + table_id: department + view: + - query: SELECT SUM(population) from my_dataset.countries + use_legacy_sql: false + +counts: + google_bigquery_dataset: 1 + google_bigquery_table: 2 diff --git a/tests/fast/stages/s03_data_platform/test_plan.py b/tests/fast/stages/s03_data_platform/test_plan.py deleted file mode 100644 index 0bb333e7..00000000 --- a/tests/fast/stages/s03_data_platform/test_plan.py +++ /dev/null @@ -1,21 +0,0 @@ -# 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 -# -# 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. - - -def test_counts(plan_summary): - "Test stage." - summary = plan_summary("fast/stages/03-data-platform/dev/", - tf_var_files=["common.tfvars"]) - assert summary.counts["modules"] > 0 - assert summary.counts["resources"] > 0 diff --git a/tests/fast/stages/s03_gke_multitenant/test_plan.py b/tests/fast/stages/s03_gke_multitenant/test_plan.py deleted file mode 100644 index 2d196ec4..00000000 --- a/tests/fast/stages/s03_gke_multitenant/test_plan.py +++ /dev/null @@ -1,21 +0,0 @@ -# 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 -# -# 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. - - -def test_counts(plan_summary): - "Test stage." - summary = plan_summary("fast/stages/03-gke-multitenant/dev/", - tf_var_files=["common.tfvars"]) - assert summary.counts["modules"] > 0 - assert summary.counts["resources"] > 0 diff --git a/tests/fast/stages/s03_project_factory/common.tfvars b/tests/fast/stages/s03_project_factory/common.tfvars deleted file mode 100644 index b65956b6..00000000 --- a/tests/fast/stages/s03_project_factory/common.tfvars +++ /dev/null @@ -1,11 +0,0 @@ -data_dir = "../../../../tests/fast/stages/s03_project_factory/data/projects/" -defaults_file = "../../../../tests/fast/stages/s03_project_factory/data/defaults.yaml" -prefix = "test" -environment_dns_zone = "dev" -billing_account = { - id = "000000-111111-222222" - organization_id = 123456789012 -} -vpc_self_links = { - dev-spoke-0 = "link" -} diff --git a/tests/fast/stages/s03_project_factory/test_plan.py b/tests/fast/stages/s03_project_factory/test_plan.py deleted file mode 100644 index 3b284abb..00000000 --- a/tests/fast/stages/s03_project_factory/test_plan.py +++ /dev/null @@ -1,21 +0,0 @@ -# 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 -# -# 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. - - -def test_counts(plan_summary): - "Test stage." - summary = plan_summary("fast/stages/03-project-factory/dev", - tf_var_files=["common.tfvars"]) - assert summary.counts["modules"] > 0 - assert summary.counts["resources"] > 0 diff --git a/tests/fast/stages/s01_resman/__init__.py b/tests/fast/stages/s0_bootstrap/__init__.py similarity index 100% rename from tests/fast/stages/s01_resman/__init__.py rename to tests/fast/stages/s0_bootstrap/__init__.py diff --git a/tests/fast/stages/s00_bootstrap/simple.tfvars b/tests/fast/stages/s0_bootstrap/simple.tfvars similarity index 71% rename from tests/fast/stages/s00_bootstrap/simple.tfvars rename to tests/fast/stages/s0_bootstrap/simple.tfvars index f8ef5735..5c389f53 100644 --- a/tests/fast/stages/s00_bootstrap/simple.tfvars +++ b/tests/fast/stages/s0_bootstrap/simple.tfvars @@ -4,8 +4,7 @@ organization = { customer_id = "C00000000" } billing_account = { - id = "000000-111111-222222" - organization_id = 123456789012 + id = "000000-111111-222222" } prefix = "fast" outputs_location = "/fast-config" diff --git a/tests/fast/stages/s00_bootstrap/simple.yaml b/tests/fast/stages/s0_bootstrap/simple.yaml similarity index 79% rename from tests/fast/stages/s00_bootstrap/simple.yaml rename to tests/fast/stages/s0_bootstrap/simple.yaml index ed0d7738..63a64cb9 100644 --- a/tests/fast/stages/s00_bootstrap/simple.yaml +++ b/tests/fast/stages/s0_bootstrap/simple.yaml @@ -13,23 +13,22 @@ # limitations under the License. counts: - google_bigquery_dataset: 2 - google_bigquery_dataset_iam_member: 2 + google_bigquery_dataset: 1 google_bigquery_default_service_account: 3 google_logging_organization_sink: 2 google_organization_iam_binding: 19 - google_organization_iam_custom_role: 2 + google_organization_iam_custom_role: 3 google_organization_iam_member: 16 google_project: 3 google_project_iam_binding: 9 - google_project_iam_member: 1 + google_project_iam_member: 3 google_project_service: 29 google_project_service_identity: 3 - google_service_account: 3 - google_service_account_iam_binding: 3 - google_storage_bucket: 4 - google_storage_bucket_iam_binding: 2 - google_storage_bucket_iam_member: 3 + google_service_account: 2 + google_service_account_iam_binding: 1 + google_storage_bucket: 3 + google_storage_bucket_iam_binding: 1 + google_storage_bucket_iam_member: 2 google_storage_bucket_object: 5 google_storage_project_service_account: 3 local_file: 5 @@ -38,6 +37,7 @@ outputs: custom_roles: organization_iam_admin: organizations/123456789012/roles/organizationIamAdmin service_project_network_admin: organizations/123456789012/roles/serviceProjectNetworkAdmin + tenant_network_admin: organizations/123456789012/roles/tenantNetworkAdmin outputs_bucket: fast-prod-iac-core-outputs-0 project_ids: automation: fast-prod-iac-core-0 @@ -45,5 +45,4 @@ outputs: log-export: fast-prod-audit-logs-0 service_accounts: bootstrap: fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com - cicd: fast-prod-cicd-0@fast-prod-iac-core-0.iam.gserviceaccount.com resman: fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com diff --git a/tests/fast/stages/s00_bootstrap/simple_projects.yaml b/tests/fast/stages/s0_bootstrap/simple_projects.yaml similarity index 100% rename from tests/fast/stages/s00_bootstrap/simple_projects.yaml rename to tests/fast/stages/s0_bootstrap/simple_projects.yaml diff --git a/tests/fast/stages/s00_bootstrap/simple_sas.yaml b/tests/fast/stages/s0_bootstrap/simple_sas.yaml similarity index 82% rename from tests/fast/stages/s00_bootstrap/simple_sas.yaml rename to tests/fast/stages/s0_bootstrap/simple_sas.yaml index ba84948d..0424e598 100644 --- a/tests/fast/stages/s00_bootstrap/simple_sas.yaml +++ b/tests/fast/stages/s0_bootstrap/simple_sas.yaml @@ -17,10 +17,6 @@ values: account_id: fast-prod-bootstrap-0 display_name: Terraform organization bootstrap service account. project: fast-prod-iac-core-0 - module.automation-tf-cicd-provisioning-sa.google_service_account.service_account[0]: - account_id: fast-prod-cicd-0 - display_name: Terraform stage 1 CICD service account. - project: fast-prod-iac-core-0 module.automation-tf-resman-sa.google_service_account.service_account[0]: account_id: fast-prod-resman-0 display_name: Terraform stage 1 resman service account. diff --git a/tests/fast/stages/s00_bootstrap/tftest.yaml b/tests/fast/stages/s0_bootstrap/tftest.yaml similarity index 83% rename from tests/fast/stages/s00_bootstrap/tftest.yaml rename to tests/fast/stages/s0_bootstrap/tftest.yaml index 4656859b..b53749ad 100644 --- a/tests/fast/stages/s00_bootstrap/tftest.yaml +++ b/tests/fast/stages/s0_bootstrap/tftest.yaml @@ -1,6 +1,6 @@ # skip boilerplate check -module: fast/stages/00-bootstrap +module: fast/stages/0-bootstrap tests: simple: diff --git a/tests/fast/stages/s02_networking_nva/__init__.py b/tests/fast/stages/s1_resman/__init__.py similarity index 100% rename from tests/fast/stages/s02_networking_nva/__init__.py rename to tests/fast/stages/s1_resman/__init__.py diff --git a/tests/fast/stages/s01_resman/common.tfvars b/tests/fast/stages/s1_resman/common.tfvars similarity index 91% rename from tests/fast/stages/s01_resman/common.tfvars rename to tests/fast/stages/s1_resman/common.tfvars index f6d1d5ac..34c61351 100644 --- a/tests/fast/stages/s01_resman/common.tfvars +++ b/tests/fast/stages/s1_resman/common.tfvars @@ -6,8 +6,7 @@ automation = { outputs_bucket = "test" } billing_account = { - id = "000000-111111-222222" - organization_id = 123456789012 + id = "000000-111111-222222" } custom_roles = { # organization_iam_admin = "organizations/123456789012/roles/organizationIamAdmin", diff --git a/tests/fast/stages/s01_resman/test_plan.py b/tests/fast/stages/s1_resman/test_plan.py similarity index 93% rename from tests/fast/stages/s01_resman/test_plan.py rename to tests/fast/stages/s1_resman/test_plan.py index c8dce750..39bdfc11 100644 --- a/tests/fast/stages/s01_resman/test_plan.py +++ b/tests/fast/stages/s1_resman/test_plan.py @@ -15,7 +15,7 @@ def test_counts(plan_summary): "Test stage." - summary = plan_summary("fast/stages/01-resman", + summary = plan_summary("fast/stages/1-resman", tf_var_files=["common.tfvars"]) assert summary.counts["modules"] > 0 assert summary.counts["resources"] > 0 diff --git a/tests/fast/stages/s02_networking_peering/__init__.py b/tests/fast/stages/s2_networking_a_peering/__init__.py similarity index 100% rename from tests/fast/stages/s02_networking_peering/__init__.py rename to tests/fast/stages/s2_networking_a_peering/__init__.py diff --git a/tests/fast/stages/s02_networking_nva/common.tfvars b/tests/fast/stages/s2_networking_a_peering/common.tfvars similarity index 81% rename from tests/fast/stages/s02_networking_nva/common.tfvars rename to tests/fast/stages/s2_networking_a_peering/common.tfvars index acfc641f..6c2b0c03 100644 --- a/tests/fast/stages/s02_networking_nva/common.tfvars +++ b/tests/fast/stages/s2_networking_a_peering/common.tfvars @@ -1,10 +1,9 @@ -data_dir = "../../../fast/stages/02-networking-nva/data/" +data_dir = "../../../fast/stages/2-networking-a-peering/data/" automation = { outputs_bucket = "test" } billing_account = { - id = "000000-111111-222222" - organization_id = 123456789012 + id = "000000-111111-222222" } custom_roles = { service_project_network_admin = "organizations/123456789012/roles/foo" diff --git a/tests/fast/stages/s02_networking_peering/test_plan.py b/tests/fast/stages/s2_networking_a_peering/test_plan.py similarity index 87% rename from tests/fast/stages/s02_networking_peering/test_plan.py rename to tests/fast/stages/s2_networking_a_peering/test_plan.py index dff0fd47..09590d36 100644 --- a/tests/fast/stages/s02_networking_peering/test_plan.py +++ b/tests/fast/stages/s2_networking_a_peering/test_plan.py @@ -20,16 +20,16 @@ from deepdiff import DeepDiff BASEDIR = Path(__file__).parent FIXTURE_PEERING = BASEDIR / 'fixture' -FIXTURE_VPN = BASEDIR.parent / 's02_networking_vpn/fixture' +FIXTURE_VPN = BASEDIR.parent / 's2_networking_b_vpn/fixture' STAGES = Path(__file__).parents[4] / 'fast/stages' -STAGE_PEERING = STAGES / '02-networking-peering' -STAGE_VPN = STAGES / '02-networking-vpn' +STAGE_PEERING = STAGES / '2-networking-a-peering' +STAGE_VPN = STAGES / '2-networking-b-vpn' def test_counts(plan_summary): "Test stage." - summary = plan_summary("fast/stages/02-networking-peering", + summary = plan_summary("fast/stages/2-networking-a-peering", tf_var_files=["common.tfvars"]) assert summary.counts["modules"] > 0 assert summary.counts["resources"] > 0 @@ -38,9 +38,9 @@ def test_counts(plan_summary): def test_vpn_peering_parity(plan_summary): '''Ensure VPN- and peering-based networking stages are identical except for VPN and VPC peering resources''' - summary_peering = plan_summary("fast/stages/02-networking-peering", + summary_peering = plan_summary("fast/stages/2-networking-a-peering", tf_var_files=["common.tfvars"]) - summary_vpn = plan_summary("fast/stages/02-networking-vpn", + summary_vpn = plan_summary("fast/stages/2-networking-b-vpn", tf_var_files=["common.tfvars"]) ddiff = DeepDiff(summary_vpn.values, summary_peering.values, diff --git a/tests/fast/stages/s02_networking_separate_envs/__init__.py b/tests/fast/stages/s2_networking_b_vpn/__init__.py similarity index 100% rename from tests/fast/stages/s02_networking_separate_envs/__init__.py rename to tests/fast/stages/s2_networking_b_vpn/__init__.py diff --git a/tests/fast/stages/s02_networking_peering/common.tfvars b/tests/fast/stages/s2_networking_b_vpn/common.tfvars similarity index 83% rename from tests/fast/stages/s02_networking_peering/common.tfvars rename to tests/fast/stages/s2_networking_b_vpn/common.tfvars index 11b49d7c..66a7a609 100644 --- a/tests/fast/stages/s02_networking_peering/common.tfvars +++ b/tests/fast/stages/s2_networking_b_vpn/common.tfvars @@ -1,10 +1,9 @@ -data_dir = "../../../fast/stages/02-networking-peering/data/" +data_dir = "../../../../../fast/stages/2-networking-b-vpn/data/" automation = { outputs_bucket = "test" } billing_account = { - id = "000000-111111-222222" - organization_id = 123456789012 + id = "000000-111111-222222" } custom_roles = { service_project_network_admin = "organizations/123456789012/roles/foo" diff --git a/tests/fast/stages/s02_networking_vpn/fixture/main.tf b/tests/fast/stages/s2_networking_b_vpn/fixture/main.tf similarity index 100% rename from tests/fast/stages/s02_networking_vpn/fixture/main.tf rename to tests/fast/stages/s2_networking_b_vpn/fixture/main.tf diff --git a/tests/fast/stages/s02_networking_vpn/test_plan.py b/tests/fast/stages/s2_networking_b_vpn/test_plan.py similarity index 92% rename from tests/fast/stages/s02_networking_vpn/test_plan.py rename to tests/fast/stages/s2_networking_b_vpn/test_plan.py index cc62dbf7..8ac1bade 100644 --- a/tests/fast/stages/s02_networking_vpn/test_plan.py +++ b/tests/fast/stages/s2_networking_b_vpn/test_plan.py @@ -15,7 +15,7 @@ def test_counts(plan_summary): "Test stage." - summary = plan_summary("fast/stages/02-networking-vpn", + summary = plan_summary("fast/stages/2-networking-b-vpn", tf_var_files=["common.tfvars"]) assert summary.counts["modules"] > 0 assert summary.counts["resources"] > 0 diff --git a/tests/fast/stages/s02_networking_vpn/__init__.py b/tests/fast/stages/s2_networking_c_nva/__init__.py similarity index 100% rename from tests/fast/stages/s02_networking_vpn/__init__.py rename to tests/fast/stages/s2_networking_c_nva/__init__.py diff --git a/tests/fast/stages/s02_networking_vpn/common.tfvars b/tests/fast/stages/s2_networking_c_nva/common.tfvars similarity index 72% rename from tests/fast/stages/s02_networking_vpn/common.tfvars rename to tests/fast/stages/s2_networking_c_nva/common.tfvars index 7241594d..ad12b8d3 100644 --- a/tests/fast/stages/s02_networking_vpn/common.tfvars +++ b/tests/fast/stages/s2_networking_c_nva/common.tfvars @@ -1,10 +1,9 @@ -data_dir = "../../../../../fast/stages/02-networking-vpn/data/" +data_dir = "../../../fast/stages/2-networking-c-nva/data/" automation = { outputs_bucket = "test" } billing_account = { - id = "000000-111111-222222" - organization_id = 123456789012 + id = "000000-111111-222222" } custom_roles = { service_project_network_admin = "organizations/123456789012/roles/foo" @@ -14,11 +13,6 @@ folder_ids = { networking-dev = null networking-prod = null } -region_trigram = { - europe-west1 = "ew1" - europe-west3 = "ew3" - europe-west8 = "ew8" -} service_accounts = { data-platform-dev = "string" data-platform-prod = "string" diff --git a/tests/fast/stages/s02_networking_nva/test_plan.py b/tests/fast/stages/s2_networking_c_nva/test_plan.py similarity index 92% rename from tests/fast/stages/s02_networking_nva/test_plan.py rename to tests/fast/stages/s2_networking_c_nva/test_plan.py index 24964f7a..70c37bc3 100644 --- a/tests/fast/stages/s02_networking_nva/test_plan.py +++ b/tests/fast/stages/s2_networking_c_nva/test_plan.py @@ -15,7 +15,7 @@ def test_counts(plan_summary): "Test stage." - summary = plan_summary("fast/stages/02-networking-nva", + summary = plan_summary("fast/stages/2-networking-c-nva", tf_var_files=["common.tfvars"]) assert summary.counts["modules"] > 0 assert summary.counts["resources"] > 0 diff --git a/tests/fast/stages/s02_security/__init__.py b/tests/fast/stages/s2_networking_d_separate_envs/__init__.py similarity index 100% rename from tests/fast/stages/s02_security/__init__.py rename to tests/fast/stages/s2_networking_d_separate_envs/__init__.py diff --git a/tests/fast/stages/s02_networking_separate_envs/common.tfvars b/tests/fast/stages/s2_networking_d_separate_envs/common.tfvars similarity index 78% rename from tests/fast/stages/s02_networking_separate_envs/common.tfvars rename to tests/fast/stages/s2_networking_d_separate_envs/common.tfvars index c6b793fd..3ff0020a 100644 --- a/tests/fast/stages/s02_networking_separate_envs/common.tfvars +++ b/tests/fast/stages/s2_networking_d_separate_envs/common.tfvars @@ -1,10 +1,9 @@ -data_dir = "../../../../../fast/stages/02-networking-separate-envs/data/" +data_dir = "../../../../../fast/stages/2-networking-d-separate-envs/data/" automation = { outputs_bucket = "test" } billing_account = { - id = "000000-111111-222222" - organization_id = 123456789012 + id = "000000-111111-222222" } custom_roles = { service_project_network_admin = "organizations/123456789012/roles/foo" diff --git a/tests/fast/stages/s02_networking_separate_envs/test_plan.py b/tests/fast/stages/s2_networking_d_separate_envs/test_plan.py similarity index 91% rename from tests/fast/stages/s02_networking_separate_envs/test_plan.py rename to tests/fast/stages/s2_networking_d_separate_envs/test_plan.py index 89f6e257..51a9257a 100644 --- a/tests/fast/stages/s02_networking_separate_envs/test_plan.py +++ b/tests/fast/stages/s2_networking_d_separate_envs/test_plan.py @@ -15,7 +15,7 @@ def test_counts(plan_summary): "Test stage." - summary = plan_summary("fast/stages/02-networking-separate-envs", + summary = plan_summary("fast/stages/2-networking-d-separate-envs", tf_var_files=["common.tfvars"]) assert summary.counts["modules"] > 0 assert summary.counts["resources"] > 0 diff --git a/tests/fast/stages/s03_data_platform/__init__.py b/tests/fast/stages/s2_security/__init__.py similarity index 100% rename from tests/fast/stages/s03_data_platform/__init__.py rename to tests/fast/stages/s2_security/__init__.py diff --git a/tests/fast/stages/s02_security/common.tfvars b/tests/fast/stages/s2_security/common.tfvars similarity index 91% rename from tests/fast/stages/s02_security/common.tfvars rename to tests/fast/stages/s2_security/common.tfvars index b480a673..6fbb60b6 100644 --- a/tests/fast/stages/s02_security/common.tfvars +++ b/tests/fast/stages/s2_security/common.tfvars @@ -2,16 +2,15 @@ automation = { outputs_bucket = "test" } billing_account = { - id = "000000-111111-222222" - organization_id = 123456789012 + id = "000000-111111-222222" } folder_ids = { security = null } organization = { - domain = "gcp-pso-italy.net" - id = 856933387836 - customer_id = "C01lmug8b" + domain = "fast.example.com" + id = 123456789012 + customer_id = "C00000000" } prefix = "fast" kms_keys = { diff --git a/tests/fast/stages/s02_security/test_plan.py b/tests/fast/stages/s2_security/test_plan.py similarity index 93% rename from tests/fast/stages/s02_security/test_plan.py rename to tests/fast/stages/s2_security/test_plan.py index 004f6b83..edf5622e 100644 --- a/tests/fast/stages/s02_security/test_plan.py +++ b/tests/fast/stages/s2_security/test_plan.py @@ -15,7 +15,7 @@ def test_counts(plan_summary): "Test stage." - summary = plan_summary("fast/stages/02-security", + summary = plan_summary("fast/stages/2-security", tf_var_files=["common.tfvars"]) assert summary.counts["modules"] > 0 assert summary.counts["resources"] > 0 diff --git a/tests/fast/stages/s03_gke_multitenant/__init__.py b/tests/fast/stages/s3_data_platform/__init__.py similarity index 100% rename from tests/fast/stages/s03_gke_multitenant/__init__.py rename to tests/fast/stages/s3_data_platform/__init__.py diff --git a/tests/fast/stages/s03_data_platform/common.tfvars b/tests/fast/stages/s3_data_platform/common.tfvars similarity index 85% rename from tests/fast/stages/s03_data_platform/common.tfvars rename to tests/fast/stages/s3_data_platform/common.tfvars index f5aada16..2ec41d37 100644 --- a/tests/fast/stages/s03_data_platform/common.tfvars +++ b/tests/fast/stages/s3_data_platform/common.tfvars @@ -2,8 +2,7 @@ automation = { outputs_bucket = "test" } billing_account = { - id = "012345-67890A-BCDEF0", - organization_id = 123456 + id = "012345-67890A-BCDEF0", } folder_ids = { data-platform-dev = "folders/12345678" @@ -12,9 +11,9 @@ host_project_ids = { dev-spoke-0 = "fast-dev-net-spoke-0" } organization = { - domain = "example.com" + domain = "fast.example.com" id = 123456789012 - customer_id = "A11aaaaa1" + customer_id = "C00000000" } prefix = "fast" subnet_self_links = { diff --git a/tests/fast/stages/s3_data_platform/test_plan.py b/tests/fast/stages/s3_data_platform/test_plan.py new file mode 100644 index 00000000..ad7fa3d2 --- /dev/null +++ b/tests/fast/stages/s3_data_platform/test_plan.py @@ -0,0 +1,21 @@ +# 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 +# +# 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. + + +def test_counts(plan_summary): + "Test stage." + summary = plan_summary("fast/stages/3-data-platform/dev/", + tf_var_files=["common.tfvars"]) + assert summary.counts["modules"] > 0 + assert summary.counts["resources"] > 0 diff --git a/tests/fast/stages/s03_project_factory/__init__.py b/tests/fast/stages/s3_gke_multitenant/__init__.py similarity index 100% rename from tests/fast/stages/s03_project_factory/__init__.py rename to tests/fast/stages/s3_gke_multitenant/__init__.py diff --git a/tests/fast/stages/s03_gke_multitenant/common.tfvars b/tests/fast/stages/s3_gke_multitenant/common.tfvars similarity index 92% rename from tests/fast/stages/s03_gke_multitenant/common.tfvars rename to tests/fast/stages/s3_gke_multitenant/common.tfvars index d22db1d2..1cafdd9a 100644 --- a/tests/fast/stages/s03_gke_multitenant/common.tfvars +++ b/tests/fast/stages/s3_gke_multitenant/common.tfvars @@ -2,8 +2,7 @@ automation = { outputs_bucket = "test" } billing_account = { - id = "012345-67890A-BCDEF0", - organization_id = 123456 + id = "012345-67890A-BCDEF0", } clusters = { mycluster = { diff --git a/tests/fast/stages/s3_gke_multitenant/test_plan.py b/tests/fast/stages/s3_gke_multitenant/test_plan.py new file mode 100644 index 00000000..c517cb93 --- /dev/null +++ b/tests/fast/stages/s3_gke_multitenant/test_plan.py @@ -0,0 +1,21 @@ +# 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 +# +# 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. + + +def test_counts(plan_summary): + "Test stage." + summary = plan_summary("fast/stages/3-gke-multitenant/dev/", + tf_var_files=["common.tfvars"]) + assert summary.counts["modules"] > 0 + assert summary.counts["resources"] > 0 diff --git a/tests/modules/api_gateway/__init__.py b/tests/fast/stages/s3_project_factory/__init__.py similarity index 100% rename from tests/modules/api_gateway/__init__.py rename to tests/fast/stages/s3_project_factory/__init__.py diff --git a/tests/fast/stages/s3_project_factory/common.tfvars b/tests/fast/stages/s3_project_factory/common.tfvars new file mode 100644 index 00000000..d3f8c6f9 --- /dev/null +++ b/tests/fast/stages/s3_project_factory/common.tfvars @@ -0,0 +1,10 @@ +data_dir = "../../../../tests/fast/stages/s3_project_factory/data/projects/" +defaults_file = "../../../../tests/fast/stages/s3_project_factory/data/defaults.yaml" +prefix = "test" +environment_dns_zone = "dev" +billing_account = { + id = "000000-111111-222222" +} +vpc_self_links = { + dev-spoke-0 = "link" +} diff --git a/tests/fast/stages/s03_project_factory/data/defaults.yaml b/tests/fast/stages/s3_project_factory/data/defaults.yaml similarity index 100% rename from tests/fast/stages/s03_project_factory/data/defaults.yaml rename to tests/fast/stages/s3_project_factory/data/defaults.yaml diff --git a/tests/fast/stages/s03_project_factory/data/projects/project.yaml b/tests/fast/stages/s3_project_factory/data/projects/project.yaml similarity index 100% rename from tests/fast/stages/s03_project_factory/data/projects/project.yaml rename to tests/fast/stages/s3_project_factory/data/projects/project.yaml diff --git a/tests/fast/stages/s3_project_factory/test_plan.py b/tests/fast/stages/s3_project_factory/test_plan.py new file mode 100644 index 00000000..fa293da8 --- /dev/null +++ b/tests/fast/stages/s3_project_factory/test_plan.py @@ -0,0 +1,21 @@ +# 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 +# +# 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. + + +def test_counts(plan_summary): + "Test stage." + summary = plan_summary("fast/stages/3-project-factory/dev", + tf_var_files=["common.tfvars"]) + assert summary.counts["modules"] > 0 + assert summary.counts["resources"] > 0 diff --git a/tests/modules/compute_vm/__init__.py b/tests/fast/stages_multitenant/__init__.py similarity index 100% rename from tests/modules/compute_vm/__init__.py rename to tests/fast/stages_multitenant/__init__.py diff --git a/tests/modules/dns/__init__.py b/tests/fast/stages_multitenant/s0_bootstrap_tenant/__init__.py similarity index 100% rename from tests/modules/dns/__init__.py rename to tests/fast/stages_multitenant/s0_bootstrap_tenant/__init__.py diff --git a/tests/fast/stages_multitenant/s0_bootstrap_tenant/simple.tfvars b/tests/fast/stages_multitenant/s0_bootstrap_tenant/simple.tfvars new file mode 100644 index 00000000..52ca76a3 --- /dev/null +++ b/tests/fast/stages_multitenant/s0_bootstrap_tenant/simple.tfvars @@ -0,0 +1,61 @@ +automation = { + federated_identity_pool = null + federated_identity_providers = null + project_id = "fast-prod-automation" + project_number = 123456 + outputs_bucket = "test" +} +billing_account = { + id = "000000-111111-222222" +} +custom_roles = { + # organization_iam_admin = "organizations/123456789012/roles/organizationIamAdmin", + service_project_network_admin = "organizations/123456789012/roles/xpnServiceAdmin" + tenant_network_admin = "organizations/123456789012/roles/TenantNetworkAdmin" +} +groups = { + gcp-billing-admins = "gcp-billing-admins", + gcp-devops = "gcp-devops", + gcp-network-admins = "gcp-network-admins", + gcp-organization-admins = "gcp-organization-admins", + gcp-security-admins = "gcp-security-admins", + gcp-support = "gcp-support" +} +organization = { + domain = "fast.example.com" + id = 123456789012 + customer_id = "C00000000" +} +prefix = "fast2" +tag_keys = { + context = "tagKeys/1234567890" + environment = "tagKeys/4567890123" + tenant = "tagKeys/7890123456" +} +tag_names = { + context = "context" + environment = "environment" + tenant = "tenant" +} +tag_values = { + "context/data" : "tagValues/1234567890", + "context/gke" : "tagValues/1234567890", + "context/networking" : "tagValues/1234567890", + "context/sandbox" : "tagValues/1234567890", + "context/security" : "tagValues/1234567890", + "context/teams" : "tagValues/1234567890", + "environment/development" : "tagValues/1234567890", + "environment/production" : "tagValues/1234567890" +} +tenant_config = { + groups = { + gcp-admins = "gcp-tn01-admins" + } + descriptive_name = "Tenant 01" + locations = { + gcs = "europe-west8" + logging = "europe-west8" + } + short_name = "tn01" +} +test_principal = "foo-prod-resman-0@foo-prod-iac-core-0.iam.gserviceaccount.com" diff --git a/tests/blueprints/factories/bigquery_factory/fixture/views/view_a.yaml b/tests/fast/stages_multitenant/s0_bootstrap_tenant/simple.yaml similarity index 50% rename from tests/blueprints/factories/bigquery_factory/fixture/views/view_a.yaml rename to tests/fast/stages_multitenant/s0_bootstrap_tenant/simple.yaml index 23c41b98..e5ccc4fd 100644 --- a/tests/blueprints/factories/bigquery_factory/fixture/views/view_a.yaml +++ b/tests/fast/stages_multitenant/s0_bootstrap_tenant/simple.yaml @@ -12,6 +12,22 @@ # See the License for the specific language governing permissions and # limitations under the License. -dataset: dataset_b -view: view_a -query: "SELECT CURRENT_DATE() LIMIT 1" +counts: + google_bigquery_default_service_account: 2 + google_folder: 2 + google_folder_iam_binding: 5 + google_organization_iam_member: 39 + google_project: 2 + google_project_iam_binding: 8 + google_project_service: 26 + google_project_service_identity: 3 + google_service_account: 11 + google_storage_bucket: 2 + google_storage_bucket_iam_binding: 1 + google_storage_bucket_iam_member: 1 + google_storage_bucket_object: 2 + google_storage_project_service_account: 2 + google_tags_tag_binding: 1 + google_tags_tag_value: 1 + modules: 19 + resources: 129 diff --git a/tests/fast/stages_multitenant/s0_bootstrap_tenant/tftest.yaml b/tests/fast/stages_multitenant/s0_bootstrap_tenant/tftest.yaml new file mode 100644 index 00000000..c2fa9fa8 --- /dev/null +++ b/tests/fast/stages_multitenant/s0_bootstrap_tenant/tftest.yaml @@ -0,0 +1,10 @@ +# skip boilerplate check + +module: fast/stages-multitenant/0-bootstrap-tenant + +tests: + simple: + tfvars: + - simple.tfvars + inventory: + - simple.yaml diff --git a/tests/modules/gcs/__init__.py b/tests/fast/stages_multitenant/s1_resman_tenant/__init__.py similarity index 100% rename from tests/modules/gcs/__init__.py rename to tests/fast/stages_multitenant/s1_resman_tenant/__init__.py diff --git a/tests/fast/stages_multitenant/s1_resman_tenant/simple.tfvars b/tests/fast/stages_multitenant/s1_resman_tenant/simple.tfvars new file mode 100644 index 00000000..33cf4619 --- /dev/null +++ b/tests/fast/stages_multitenant/s1_resman_tenant/simple.tfvars @@ -0,0 +1,70 @@ +automation = { + federated_identity_pools = null + federated_identity_providers = null + project_id = "tn0-prod-automation-0" + project_number = 123456 + outputs_bucket = "tn0-prod-automation-0" + service_accounts = { + networking = "foo-tn0-net-0@foo-tn0-prod-iac-core-0.iam.gserviceaccount.com" + resman = "foo-tn0-resman-0@foo-tn0-prod-iac-core-0.iam.gserviceaccount.com" + security = "foo-tn0-sec-0@foo-tn0-prod-iac-core-0.iam.gserviceaccount.com" + dp-dev = "foo-tn0-dp-dev-0@foo-tn0-prod-iac-core-0.iam.gserviceaccount.com" + dp-prod = "foo-tn0-dp-prod-0@foo-tn0-prod-iac-core-0.iam.gserviceaccount.com" + gke-dev = "foo-tn0-gke-dev-0@foo-tn0-prod-iac-core-0.iam.gserviceaccount.com" + gke-prod = "foo-tn0-gke-prod-0@foo-tn0-prod-iac-core-0.iam.gserviceaccount.com" + pf-dev = "foo-tn0-pf-dev-0@foo-tn0-prod-iac-core-0.iam.gserviceaccount.com" + pf-prod = "foo-tn0-pf-prod-0@foo-tn0-prod-iac-core-0.iam.gserviceaccount.com" + sandbox = "foo-tn0-sandbox-0@foo-tn0-prod-iac-core-0.iam.gserviceaccount.com" + teams = "foo-tn0-teams-0@foo-tn0-prod-iac-core-0.iam.gserviceaccount.com" + } +} +billing_account = { + id = "000000-111111-222222" +} +custom_roles = { + # organization_iam_admin = "organizations/123456789012/roles/organizationIamAdmin", + service_project_network_admin = "organizations/123456789012/roles/xpnServiceAdmin" +} +fast_features = { + data_platform = true + gke = true + project_factory = true + sandbox = true + teams = true +} +groups = { + gcp-devops = "gcp-devops", + gcp-network-admins = "gcp-network-admins", + gcp-security-admins = "gcp-security-admins", +} +organization = { + domain = "fast.example.com" + id = 123456789012 + customer_id = "C00000000" +} +prefix = "foo-tn0" +root_node = "folders/1234567890" +short_name = "tn0" +tags = { + keys = { + context = "tagKeys/1234567890" + environment = "tagKeys/4567890123" + tenant = "tagKeys/7890123456" + } + names = { + context = "context" + environment = "environment" + tenant = "tenant" + } + values = { + "context/data" : "tagValues/1234567890", + "context/gke" : "tagValues/1234567890", + "context/networking" : "tagValues/1234567890", + "context/sandbox" : "tagValues/1234567890", + "context/security" : "tagValues/1234567890", + "context/teams" : "tagValues/1234567890", + "environment/development" : "tagValues/1234567890", + "environment/production" : "tagValues/1234567890" + } +} +test_skip_data_sources = true diff --git a/tests/blueprints/factories/bigquery_factory/fixture/tables/table_a.yaml b/tests/fast/stages_multitenant/s1_resman_tenant/simple.yaml similarity index 59% rename from tests/blueprints/factories/bigquery_factory/fixture/tables/table_a.yaml rename to tests/fast/stages_multitenant/s1_resman_tenant/simple.yaml index 05adbcb0..44c07c62 100644 --- a/tests/blueprints/factories/bigquery_factory/fixture/tables/table_a.yaml +++ b/tests/fast/stages_multitenant/s1_resman_tenant/simple.yaml @@ -12,6 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. -dataset: dataset_a -table: table_a -schema: [{name: "test", type: "STRING"},{name: "test2", type: "INT64"}] +counts: + google_folder: 13 + google_folder_iam_binding: 42 + google_folder_iam_member: 3 + google_org_policy_policy: 2 + google_service_account: 9 + google_service_account_iam_binding: 8 + google_storage_bucket: 10 + google_storage_bucket_iam_binding: 10 + google_storage_bucket_iam_member: 9 + google_storage_bucket_object: 11 + google_tags_tag_binding: 12 + modules: 32 + resources: 129 diff --git a/tests/fast/stages_multitenant/s1_resman_tenant/tftest.yaml b/tests/fast/stages_multitenant/s1_resman_tenant/tftest.yaml new file mode 100644 index 00000000..2e107e08 --- /dev/null +++ b/tests/fast/stages_multitenant/s1_resman_tenant/tftest.yaml @@ -0,0 +1,10 @@ +# skip boilerplate check + +module: fast/stages-multitenant/1-resman-tenant + +tests: + simple: + tfvars: + - simple.tfvars + inventory: + - simple.yaml diff --git a/tests/fixtures.py b/tests/fixtures.py index 788f8178..11a397de 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -31,7 +31,7 @@ PlanSummary = collections.namedtuple('PlanSummary', 'values counts outputs') @contextlib.contextmanager def _prepare_root_module(path): """Context manager to prepare a terraform module to be tested. - + If the TFTEST_COPY environment variable is set, `path` is copied to a temporary directory and a few terraform files (e.g. terraform.tfvars) are delete to ensure a clean test environment. @@ -49,6 +49,7 @@ def _prepare_root_module(path): # deployment with links to configs) ignore_patterns = shutil.ignore_patterns('*.auto.tfvars', '*.auto.tfvars.json', + '[0-9]-*-providers.tf', 'terraform.tfstate*', 'terraform.tfvars', '.terraform') @@ -180,19 +181,19 @@ def plan_validator(module_path, inventory_paths, basedir, tf_var_files=None, expected_values = inventory['values'] for address, expected_value in expected_values.items(): assert address in summary.values, \ - f'{address} is not a valid address in the plan' + f'{address} is not a valid address in the plan' for k, v in expected_value.items(): assert k in summary.values[address], \ - f'{k} not found at {address}' + f'{k} not found at {address}' plan_value = summary.values[address][k] assert plan_value == v, \ - f'{k} at {address} failed. Got `{plan_value}`, expected `{v}`' + f'{k} at {address} failed. Got `{plan_value}`, expected `{v}`' if 'counts' in inventory: expected_counts = inventory['counts'] for type_, expected_count in expected_counts.items(): assert type_ in summary.counts, \ - f'module does not create any resources of type `{type_}`' + f'module does not create any resources of type `{type_}`' plan_count = summary.counts[type_] assert plan_count == expected_count, \ f'count of {type_} resources failed. Got {plan_count}, expected {expected_count}' @@ -201,7 +202,7 @@ def plan_validator(module_path, inventory_paths, basedir, tf_var_files=None, expected_outputs = inventory['outputs'] for output_name, expected_output in expected_outputs.items(): assert output_name in summary.outputs, \ - f'module does not output `{output_name}`' + f'module does not output `{output_name}`' output = summary.outputs[output_name] # assert 'value' in output, \ # f'output `{output_name}` does not have a value (is it sensitive or dynamic?)' diff --git a/tests/modules/compute_vm/examples/alias-ips.yaml b/tests/modules/compute_vm/examples/alias-ips.yaml new file mode 100644 index 00000000..016f9660 --- /dev/null +++ b/tests/modules/compute_vm/examples/alias-ips.yaml @@ -0,0 +1,36 @@ +# 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. + +values: + module.vm-with-alias-ips.google_compute_instance.default[0]: + name: test + network_interface: + - access_config: [] + alias_ip_range: + - ip_cidr_range: 10.16.0.10/32 + subnetwork_range_name: alias1 + ipv6_access_config: [] + network: projects/xxx/global/networks/aaa + nic_type: null + queue_count: null + subnetwork: subnet_self_link + project: my-project + zone: europe-west1-b + +counts: + google_compute_instance: 1 + modules: 1 + resources: 1 + +outputs: {} diff --git a/tests/modules/compute_vm/examples/cmek.yaml b/tests/modules/compute_vm/examples/cmek.yaml new file mode 100644 index 00000000..cf390fde --- /dev/null +++ b/tests/modules/compute_vm/examples/cmek.yaml @@ -0,0 +1,57 @@ +# 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. + +values: + module.kms-vm-example.google_compute_disk.disks["attached-disk"]: + disk_encryption_key: + - kms_key_self_link: kms_key_self_link + kms_key_service_account: null + raw_key: null + labels: + disk_name: attached-disk + disk_type: pd-balanced + name: kms-test-attached-disk + project: project-id + size: 10 + type: pd-balanced + zone: europe-west1-b + module.kms-vm-example.google_compute_instance.default[0]: + attached_disk: + - device_name: attached-disk + disk_encryption_key_raw: null + mode: READ_WRITE + source: kms-test-attached-disk + boot_disk: + - auto_delete: true + disk_encryption_key_raw: null + initialize_params: + - image: projects/debian-cloud/global/images/family/debian-10 + size: 10 + type: pd-balanced + kms_key_self_link: kms_key_self_link + mode: READ_WRITE + name: kms-test + zone: europe-west1-b + module.kms-vm-example.google_service_account.service_account[0]: + account_id: tf-vm-kms-test + description: null + disabled: false + display_name: Terraform VM kms-test. + project: project-id + timeouts: null + +counts: + google_compute_disk: 1 + google_compute_instance: 1 + google_service_account: 1 diff --git a/tests/modules/compute_vm/examples/confidential.yaml b/tests/modules/compute_vm/examples/confidential.yaml new file mode 100644 index 00000000..e842d4cb --- /dev/null +++ b/tests/modules/compute_vm/examples/confidential.yaml @@ -0,0 +1,31 @@ +# 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. + +values: + module.template-confidential-example.google_compute_instance_template.default[0]: + confidential_instance_config: + - enable_confidential_compute: true + name_prefix: confidential-template- + project: project-id + region: europe-west1 + module.vm-confidential-example.google_compute_instance.default[0]: + confidential_instance_config: + - enable_confidential_compute: true + name: confidential-vm + project: project-id + zone: europe-west1-b + +counts: + google_compute_instance: 1 + google_compute_instance_template: 1 diff --git a/tests/modules/compute_vm/examples/disk-options.yaml b/tests/modules/compute_vm/examples/disk-options.yaml new file mode 100644 index 00000000..91c11b41 --- /dev/null +++ b/tests/modules/compute_vm/examples/disk-options.yaml @@ -0,0 +1,59 @@ +# 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. + +values: + module.vm-disk-options-example.google_compute_disk.disks["data2"]: + name: test-data2 + project: project-id + size: 20 + snapshot: snapshot-2 + type: pd-ssd + zone: europe-west1-b + module.vm-disk-options-example.google_compute_instance.default[0]: + attached_disk: + - device_name: data2 + disk_encryption_key_raw: null + mode: READ_ONLY + source: test-data2 + - device_name: data1 + disk_encryption_key_raw: null + mode: READ_WRITE + source: test-data1 + boot_disk: + - auto_delete: true + disk_encryption_key_raw: null + initialize_params: + - image: projects/debian-cloud/global/images/family/debian-11 + size: 10 + type: pd-balanced + mode: READ_WRITE + description: Managed by the compute-vm Terraform module. + name: test + project: project-id + zone: europe-west1-b + module.vm-disk-options-example.google_compute_region_disk.disks["data1"]: + name: test-data1 + project: project-id + region: europe-west1 + replica_zones: + - europe-west1-b + - europe-west1-c + size: 10 + type: pd-balanced + +counts: + google_compute_disk: 1 + google_compute_instance: 1 + google_compute_region_disk: 1 + google_service_account: 1 diff --git a/tests/modules/compute_vm/examples/group.yaml b/tests/modules/compute_vm/examples/group.yaml new file mode 100644 index 00000000..c28c4764 --- /dev/null +++ b/tests/modules/compute_vm/examples/group.yaml @@ -0,0 +1,27 @@ +# 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. + +values: + module.instance-group.google_compute_instance.default[0]: {} + module.instance-group.google_compute_instance_group.unmanaged[0]: + name: ilb-test + named_port: [] + network: projects/xxx/global/networks/aaa + project: my-project + timeouts: null + zone: europe-west1-b + +counts: + google_compute_instance: 1 + google_compute_instance_group: 1 diff --git a/tests/modules/compute_vm/examples/gvnic.yaml b/tests/modules/compute_vm/examples/gvnic.yaml new file mode 100644 index 00000000..da95de9e --- /dev/null +++ b/tests/modules/compute_vm/examples/gvnic.yaml @@ -0,0 +1,43 @@ +# 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. + +values: + google_compute_image.cos-gvnic: + guest_os_features: + - type: GVNIC + - type: SEV_CAPABLE + - type: UEFI_COMPATIBLE + - type: VIRTIO_SCSI_MULTIQUEUE + name: my-image + project: my-project + source_image: https://www.googleapis.com/compute/v1/projects/cos-cloud/global/images/cos-89-16108-534-18 + module.vm-with-gvnic.google_compute_instance.default[0]: + name: test + network_interface: + - access_config: [] + alias_ip_range: [] + ipv6_access_config: [] + network: projects/xxx/global/networks/aaa + nic_type: GVNIC + queue_count: null + subnetwork: subnet_self_link + project: my-project + zone: europe-west1-b + +counts: + google_compute_image: 1 + google_compute_instance: 1 + google_service_account: 1 + modules: 1 + resources: 3 diff --git a/tests/modules/compute_vm/examples/iam.yaml b/tests/modules/compute_vm/examples/iam.yaml new file mode 100644 index 00000000..254d266d --- /dev/null +++ b/tests/modules/compute_vm/examples/iam.yaml @@ -0,0 +1,34 @@ +# 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. + +values: + module.vm-iam-example.google_compute_instance.default[0]: + name: webserver + module.vm-iam-example.google_compute_instance_iam_binding.default["roles/compute.instanceAdmin"]: + condition: [] + instance_name: webserver + members: + - group:admin@example.com + - group:webserver@example.com + project: project-id + role: roles/compute.instanceAdmin + zone: europe-west1-b + +counts: + google_compute_instance: 1 + google_compute_instance_iam_binding: 1 + modules: 1 + resources: 2 + +outputs: {} diff --git a/tests/modules/compute_vm/examples/ips.yaml b/tests/modules/compute_vm/examples/ips.yaml new file mode 100644 index 00000000..65931abb --- /dev/null +++ b/tests/modules/compute_vm/examples/ips.yaml @@ -0,0 +1,45 @@ +# 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. + +values: + module.vm-external-ip.google_compute_instance.default[0]: + name: vm-external-ip + network_interface: + - access_config: + - nat_ip: 8.8.8.8 + public_ptr_domain_name: null + alias_ip_range: [] + ipv6_access_config: [] + network: projects/xxx/global/networks/aaa + nic_type: null + queue_count: null + subnetwork: subnet_self_link + project: my-project + zone: europe-west1-b + module.vm-internal-ip.google_compute_instance.default[0]: + name: vm-internal-ip + network_interface: + - access_config: [] + alias_ip_range: [] + ipv6_access_config: [] + network: projects/xxx/global/networks/aaa + network_ip: 10.0.0.2 + nic_type: null + queue_count: null + subnetwork: subnet_self_link + project: my-project + zone: europe-west1-b + +counts: + google_compute_instance: 2 diff --git a/tests/modules/compute_vm/examples/metadata.yaml b/tests/modules/compute_vm/examples/metadata.yaml new file mode 100644 index 00000000..fbe0d06f --- /dev/null +++ b/tests/modules/compute_vm/examples/metadata.yaml @@ -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 +# +# 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. + +values: + module.vm-metadata-example.google_compute_instance.default[0]: + metadata: + startup-script: | + #! /bin/bash + apt-get update + apt-get install -y nginx + name: nginx-server + project: project-id + zone: europe-west1-b + labels: + env: dev + system: crm + module.vm-metadata-example.google_service_account.service_account[0]: {} + +counts: + google_compute_instance: 1 + google_service_account: 1 diff --git a/tests/modules/compute_vm/examples/sas.yaml b/tests/modules/compute_vm/examples/sas.yaml new file mode 100644 index 00000000..96a94831 --- /dev/null +++ b/tests/modules/compute_vm/examples/sas.yaml @@ -0,0 +1,49 @@ +# 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. + +values: + module.vm-default-sa-example2.google_compute_instance.default[0]: + name: test3 + project: project-id + service_account: + - scopes: + - https://www.googleapis.com/auth/devstorage.read_only + - https://www.googleapis.com/auth/logging.write + - https://www.googleapis.com/auth/monitoring.write + zone: europe-west1-b + module.vm-managed-sa-example.google_compute_instance.default[0]: + name: test1 + project: project-id + service_account: + - scopes: + - https://www.googleapis.com/auth/cloud-platform + - https://www.googleapis.com/auth/userinfo.email + zone: europe-west1-b + module.vm-managed-sa-example.google_service_account.service_account[0]: + account_id: tf-vm-test1 + display_name: Terraform VM test1. + project: project-id + module.vm-managed-sa-example2.google_compute_instance.default[0]: + name: test2 + project: project-id + service_account: + - scopes: + - https://www.googleapis.com/auth/cloud-platform + zone: europe-west1-b + +counts: + google_compute_instance: 3 + google_service_account: 1 + modules: 3 + resources: 4 diff --git a/tests/modules/compute_vm/examples/simple.yaml b/tests/modules/compute_vm/examples/simple.yaml new file mode 100644 index 00000000..6754efaa --- /dev/null +++ b/tests/modules/compute_vm/examples/simple.yaml @@ -0,0 +1,72 @@ +# 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. + +values: + module.simple-vm-example.google_compute_instance.default[0]: + advanced_machine_features: [] + allow_stopping_for_update: true + attached_disk: [] + boot_disk: + - auto_delete: true + disk_encryption_key_raw: null + initialize_params: + - image: projects/debian-cloud/global/images/family/debian-11 + size: 10 + type: pd-balanced + mode: READ_WRITE + can_ip_forward: false + deletion_protection: false + description: Managed by the compute-vm Terraform module. + enable_display: false + hostname: null + labels: null + machine_type: f1-micro + metadata: null + metadata_startup_script: null + name: test + network_interface: + - access_config: [] + alias_ip_range: [] + ipv6_access_config: [] + network: projects/xxx/global/networks/aaa + nic_type: null + queue_count: null + subnetwork: subnet_self_link + project: project-id + scheduling: + - automatic_restart: true + instance_termination_action: null + max_run_duration: [] + min_node_cpus: null + node_affinities: [] + on_host_maintenance: MIGRATE + preemptible: false + provisioning_model: STANDARD + scratch_disk: [] + service_account: + - scopes: + - https://www.googleapis.com/auth/cloud-platform + - https://www.googleapis.com/auth/userinfo.email + shielded_instance_config: [] + tags: null + zone: europe-west1-b + module.simple-vm-example.google_service_account.service_account[0]: + account_id: tf-vm-test + display_name: Terraform VM test. + project: project-id + + +counts: + google_compute_instance: 1 + google_service_account: 1 diff --git a/tests/modules/compute_vm/examples/spot.yaml b/tests/modules/compute_vm/examples/spot.yaml new file mode 100644 index 00000000..c15852db --- /dev/null +++ b/tests/modules/compute_vm/examples/spot.yaml @@ -0,0 +1,31 @@ +# 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. + +values: + module.spot-vm-example.google_compute_instance.default[0]: + name: test + project: project-id + scheduling: + - automatic_restart: false + instance_termination_action: STOP + max_run_duration: [] + min_node_cpus: null + node_affinities: [] + on_host_maintenance: TERMINATE + preemptible: true + provisioning_model: SPOT + zone: europe-west1-b + +counts: + google_compute_instance: 1 diff --git a/tests/modules/compute_vm/examples/template.yaml b/tests/modules/compute_vm/examples/template.yaml new file mode 100644 index 00000000..1f1888bf --- /dev/null +++ b/tests/modules/compute_vm/examples/template.yaml @@ -0,0 +1,65 @@ +# 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. + +values: + module.cos-test.google_compute_instance_template.default[0]: + disk: + - auto_delete: true + boot: true + disk_encryption_key: [] + disk_name: null + disk_size_gb: 10 + disk_type: pd-balanced + labels: null + resource_policies: null + source: null + source_image: projects/cos-cloud/global/images/family/cos-stable + source_image_encryption_key: [] + source_snapshot: null + source_snapshot_encryption_key: [] + - auto_delete: true + device_name: disk-1 + disk_encryption_key: [] + disk_name: disk-1 + disk_size_gb: 10 + disk_type: pd-balanced + labels: null + mode: READ_WRITE + resource_policies: null + source: null + source_image_encryption_key: [] + source_snapshot: null + source_snapshot_encryption_key: [] + type: PERSISTENT + name_prefix: test- + network_interface: + - access_config: [] + alias_ip_range: [] + ipv6_access_config: [] + network: projects/xxx/global/networks/aaa + network_ip: null + nic_type: null + queue_count: null + subnetwork: subnet_self_link + project: my-project + region: europe-west1 + service_account: + - email: vm-default@my-project.iam.gserviceaccount.com + scopes: + - https://www.googleapis.com/auth/devstorage.read_only + - https://www.googleapis.com/auth/logging.write + - https://www.googleapis.com/auth/monitoring.write + +counts: + google_compute_instance_template: 1 diff --git a/tests/modules/compute_vm/fixture/main.tf b/tests/modules/compute_vm/fixture/main.tf deleted file mode 100644 index 5815f25f..00000000 --- a/tests/modules/compute_vm/fixture/main.tf +++ /dev/null @@ -1,31 +0,0 @@ -/** - * 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 - * - * 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. - */ - -module "test" { - source = "../../../../modules/compute-vm" - project_id = "my-project" - zone = "europe-west1-b" - name = "test" - attached_disks = var.attached_disks - attached_disk_defaults = var.attached_disk_defaults - create_template = var.create_template - confidential_compute = var.confidential_compute - group = var.group - iam = var.iam - metadata = var.metadata - network_interfaces = var.network_interfaces - service_account_create = var.service_account_create -} diff --git a/tests/modules/compute_vm/fixture/variables.tf b/tests/modules/compute_vm/fixture/variables.tf deleted file mode 100644 index 02d839f6..00000000 --- a/tests/modules/compute_vm/fixture/variables.tf +++ /dev/null @@ -1,70 +0,0 @@ -/** - * 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 - * - * 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 "attached_disks" { - description = "Additional disks, if options is null defaults will be used in its place. Source type is one of 'image' (zonal disks in vms and template), 'snapshot' (vm), 'existing', and null." - type = any - default = [] -} - -variable "attached_disk_defaults" { - description = "Defaults for attached disks options." - type = any - default = { - auto_delete = true - mode = "READ_WRITE" - replica_zone = null - type = "pd-balanced" - } -} - -variable "confidential_compute" { - type = bool - default = false -} - -variable "create_template" { - type = bool - default = false -} - -variable "group" { - type = any - default = null -} - -variable "iam" { - type = map(set(string)) - default = {} -} - -variable "metadata" { - type = map(string) - default = {} -} - -variable "network_interfaces" { - type = any - default = [{ - network = "https://www.googleapis.com/compute/v1/projects/my-project/global/networks/default", - subnetwork = "https://www.googleapis.com/compute/v1/projects/my-project/regions/europe-west1/subnetworks/default-default", - }] -} - -variable "service_account_create" { - type = bool - default = false -} diff --git a/tests/modules/compute_vm/test_plan.py b/tests/modules/compute_vm/test_plan.py deleted file mode 100644 index 701891c5..00000000 --- a/tests/modules/compute_vm/test_plan.py +++ /dev/null @@ -1,77 +0,0 @@ -# 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 -# -# 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. - -def test_defaults(plan_runner): - _, resources = plan_runner() - assert len(resources) == 1 - assert resources[0]['type'] == 'google_compute_instance' - - -def test_service_account(plan_runner): - _, resources = plan_runner(service_account_create='true') - assert len(resources) == 2 - assert set(r['type'] for r in resources) == set([ - 'google_compute_instance', 'google_service_account' - ]) - - -def test_template(plan_runner): - _, resources = plan_runner(create_template='true') - assert len(resources) == 1 - assert resources[0]['type'] == 'google_compute_instance_template' - assert resources[0]['values']['name_prefix'] == 'test-' - - -def test_group(plan_runner): - _, resources = plan_runner(group='{named_ports={}}') - assert len(resources) == 2 - assert set(r['type'] for r in resources) == set([ - 'google_compute_instance_group', 'google_compute_instance' - ]) - - -def test_iam(plan_runner): - iam = ( - '{"roles/compute.instanceAdmin" = ["user:a@a.com", "user:b@a.com"],' - '"roles/iam.serviceAccountUser" = ["user:a@a.com"]}' - ) - _, resources = plan_runner(iam=iam) - assert len(resources) == 3 - assert set(r['type'] for r in resources) == set([ - 'google_compute_instance', 'google_compute_instance_iam_binding']) - iam_bindings = dict( - (r['index'], r['values']['members']) for r in resources if r['type'] - == 'google_compute_instance_iam_binding' - ) - assert iam_bindings == { - 'roles/compute.instanceAdmin': ['user:a@a.com', 'user:b@a.com'], - 'roles/iam.serviceAccountUser': ['user:a@a.com'], - } - - -def test_confidential_compute(plan_runner): - _, resources = plan_runner(confidential_compute='true') - assert len(resources) == 1 - assert resources[0]['values']['confidential_instance_config'] == [ - {'enable_confidential_compute': True}] - assert resources[0]['values']['scheduling'][0]['on_host_maintenance'] == 'TERMINATE' - - -def test_confidential_compute_template(plan_runner): - _, resources = plan_runner(confidential_compute='true', - create_template='true') - assert len(resources) == 1 - assert resources[0]['values']['confidential_instance_config'] == [ - {'enable_confidential_compute': True}] - assert resources[0]['values']['scheduling'][0]['on_host_maintenance'] == 'TERMINATE' diff --git a/tests/modules/compute_vm/test_plan_disks.py b/tests/modules/compute_vm/test_plan_disks.py deleted file mode 100644 index 153c072f..00000000 --- a/tests/modules/compute_vm/test_plan_disks.py +++ /dev/null @@ -1,165 +0,0 @@ -# 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 -# -# 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. - - -def test_types(plan_runner): - _disks = '''[{ - name = "data1" - size = "10" - source_type = "image" - source = "image-1" - options = null - }, { - name = "data2" - size = "20" - source_type = "snapshot" - source = "snapshot-2" - options = null - }, { - name = "data3" - size = null - source_type = "attach" - source = "disk-3" - options = null - }] - ''' - _, resources = plan_runner(attached_disks=_disks) - assert len(resources) == 3 - disks = { - r['values']['name']: r['values'] - for r in resources if r['type'] == 'google_compute_disk' - } - assert disks['test-data1']['size'] == 10 - assert disks['test-data2']['size'] == 20 - assert disks['test-data1']['image'] == 'image-1' - assert disks['test-data1']['snapshot'] is None - assert disks['test-data2']['snapshot'] == 'snapshot-2' - assert disks['test-data2']['image'] is None - instance = [ - r['values'] for r in resources - if r['type'] == 'google_compute_instance' - ][0] - instance_disks = { - d['source']: d['device_name'] - for d in instance['attached_disk'] - } - assert instance_disks == { - 'test-data1': 'data1', - 'test-data2': 'data2', - 'disk-3': 'data3' - } - - -def test_options(plan_runner): - _disks = '''[{ - name = "data1" - size = "10" - source_type = "image" - source = "image-1" - options = { - mode = null, replica_zone = null, type = "pd-standard" - } - }, { - name = "data2" - size = "20" - source_type = null - source = null - options = { - mode = null, replica_zone = "europe-west1-c", type = "pd-ssd" - } - }] - ''' - _, resources = plan_runner(attached_disks=_disks) - assert len(resources) == 3 - disks_z = [ - r['values'] for r in resources if r['type'] == 'google_compute_disk' - ] - disks_r = [ - r['values'] for r in resources - if r['type'] == 'google_compute_region_disk' - ] - assert len(disks_z) == len(disks_r) == 1 - instance = [ - r['values'] for r in resources - if r['type'] == 'google_compute_instance' - ][0] - instance_disks = [d['device_name'] for d in instance['attached_disk']] - assert instance_disks == ['data1', 'data2'] - - -def test_template(plan_runner): - _disks = '''[{ - name = "data1" - size = "10" - source_type = "image" - source = "image-1" - options = { - mode = null, replica_zone = null, type = "pd-standard" - } - }, { - name = "data2" - size = "20" - source_type = null - source = null - options = { - mode = null, replica_zone = "europe-west1-c", type = "pd-ssd" - } - }] - ''' - _, resources = plan_runner(attached_disks=_disks, create_template="true") - assert len(resources) == 1 - template = [ - r['values'] for r in resources - if r['type'] == 'google_compute_instance_template' - ][0] - assert len(template['disk']) == 3 - - -def test_auto_delete(plan_runner): - _disks = '''[{ - name = "data1" - size = "10" - options = { - auto_delete = true, mode = "READ_WRITE" - } - }, { - name = "data2" - size = "20" - options = { - auto_delete = false, mode = "READ_WRITE" - }, - }, { - name = "data3" - size = "20" - options = { - mode = "READ_ONLY" - } - }] - ''' - _, resources = plan_runner(attached_disks=_disks, create_template="true") - assert len(resources) == 1 - template = [ - r['values'] for r in resources - if r['type'] == 'google_compute_instance_template' - ][0] - additional_disks = [ - d for d in template['disk'] if 'boot' not in d or d['boot'] != True - ] - assert len(additional_disks) == 3 - disk_data1 = [d for d in additional_disks if d['disk_name'] == 'data1'] - disk_data2 = [d for d in additional_disks if d['disk_name'] == 'data2'] - disk_data3 = [d for d in additional_disks if d['disk_name'] == 'data3'] - assert len(disk_data1) == 1 and disk_data1[0]['auto_delete'] == True - assert len(disk_data2) == 1 and disk_data2[0]['auto_delete'] == False - assert len(disk_data3) == 1 and disk_data3[0]['auto_delete'] == False diff --git a/tests/modules/compute_vm/test_plan_interfaces.py b/tests/modules/compute_vm/test_plan_interfaces.py deleted file mode 100644 index e88c087b..00000000 --- a/tests/modules/compute_vm/test_plan_interfaces.py +++ /dev/null @@ -1,42 +0,0 @@ -# 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 -# -# 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. - -def test_address(plan_runner): - nics = '''[{ - network = "https://www.googleapis.com/compute/v1/projects/my-project/global/networks/default", - subnetwork = "https://www.googleapis.com/compute/v1/projects/my-project/regions/europe-west1/subnetworks/default-default", - nat = false, - addresses = {external=null, internal="10.0.0.2"} - }] - ''' - _, resources = plan_runner(network_interfaces=nics) - assert len(resources) == 1 - n = resources[0]['values']['network_interface'][0] - assert n['network_ip'] == "10.0.0.2" - assert n['access_config'] == [] - - -def test_nat_address(plan_runner): - nics = '''[{ - network = "https://www.googleapis.com/compute/v1/projects/my-project/global/networks/default", - subnetwork = "https://www.googleapis.com/compute/v1/projects/my-project/regions/europe-west1/subnetworks/default-default", - nat = true, - addresses = {external="8.8.8.8", internal=null} - }] - ''' - _, resources = plan_runner(network_interfaces=nics) - assert len(resources) == 1 - n = resources[0]['values']['network_interface'][0] - assert 'network_ip' not in n - assert n['access_config'][0]['nat_ip'] == '8.8.8.8' diff --git a/tests/modules/gke_cluster/__init__.py b/tests/modules/gke_cluster/__init__.py deleted file mode 100644 index 6d6d1266..00000000 --- a/tests/modules/gke_cluster/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# 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 -# -# 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. diff --git a/tests/modules/iam_service_account/__init__.py b/tests/modules/iam_service_account/__init__.py deleted file mode 100644 index 6d6d1266..00000000 --- a/tests/modules/iam_service_account/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# 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 -# -# 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. diff --git a/tests/modules/net_glb/__init__.py b/tests/modules/net_glb/__init__.py deleted file mode 100644 index 6d6d1266..00000000 --- a/tests/modules/net_glb/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# 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 -# -# 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. diff --git a/tests/modules/organization/tags.tfvars b/tests/modules/organization/tags.tfvars new file mode 100644 index 00000000..2a4dcb42 --- /dev/null +++ b/tests/modules/organization/tags.tfvars @@ -0,0 +1,59 @@ +network_tags = { + net_environment = { + network = "foobar" + } +} +tags = { + foo = {} + bar = { + description = null + iam = null + values = null + } + baz = { + id = "tagKeys/1234567890" + values = { + one = null + two = null + } + } + foobar = { + description = "Foobar tag." + iam = { + "roles/resourcemanager.tagAdmin" = [ + "user:user1@example.com", "user:user2@example.com" + ] + } + values = { + one = null + two = { + description = "Foobar 2." + iam = { + "roles/resourcemanager.tagViewer" = [ + "user:user3@example.com" + ] + } + } + three = { + description = "Foobar 3." + iam = { + "roles/resourcemanager.tagViewer" = [ + "user:user3@example.com" + ] + "roles/resourcemanager.tagAdmin" = [ + "user:user4@example.com" + ] + } + } + four = { + description = "Foobar 4." + id = "tagValues/1234567890" + iam = { + "roles/resourcemanager.tagViewer" = [ + "user:user4@example.com" + ] + } + } + } + } +} diff --git a/tests/modules/organization/tags.yaml b/tests/modules/organization/tags.yaml new file mode 100644 index 00000000..3e5524d4 --- /dev/null +++ b/tests/modules/organization/tags.yaml @@ -0,0 +1,76 @@ +# 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 +# +# 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. + +values: + google_tags_tag_key.default["bar"]: + description: Managed by the Terraform organization module. + parent: organizations/1234567890 + purpose: null + purpose_data: null + short_name: bar + google_tags_tag_key.default["foo"]: + description: Managed by the Terraform organization module. + parent: organizations/1234567890 + purpose: null + purpose_data: null + short_name: foo + google_tags_tag_key.default["foobar"]: + description: Foobar tag. + parent: organizations/1234567890 + purpose: null + purpose_data: null + short_name: foobar + google_tags_tag_key.default["net_environment"]: + description: Managed by the Terraform organization module. + parent: organizations/1234567890 + purpose: GCE_FIREWALL + purpose_data: + network: foobar + short_name: net_environment + ? google_tags_tag_key_iam_binding.default["foobar:roles/resourcemanager.tagAdmin"] + : condition: [] + members: + - user:user1@example.com + - user:user2@example.com + role: roles/resourcemanager.tagAdmin + google_tags_tag_value.default["foobar/one"]: + description: Managed by the Terraform organization module. + short_name: one + google_tags_tag_value.default["foobar/three"]: + description: Foobar 3. + short_name: three + google_tags_tag_value.default["foobar/two"]: + description: Foobar 2. + short_name: two + ? google_tags_tag_value_iam_binding.default["foobar/three:roles/resourcemanager.tagAdmin"] + : condition: [] + members: + - user:user4@example.com + role: roles/resourcemanager.tagAdmin + ? google_tags_tag_value_iam_binding.default["foobar/three:roles/resourcemanager.tagViewer"] + : condition: [] + members: + - user:user3@example.com + role: roles/resourcemanager.tagViewer + ? google_tags_tag_value_iam_binding.default["foobar/two:roles/resourcemanager.tagViewer"] + : condition: [] + members: + - user:user3@example.com + role: roles/resourcemanager.tagViewer + +counts: + google_tags_tag_key: 4 + google_tags_tag_key_iam_binding: 1 + google_tags_tag_value: 5 + google_tags_tag_value_iam_binding: 4 diff --git a/tests/modules/organization/tftest.yaml b/tests/modules/organization/tftest.yaml index 7466614e..7568b732 100644 --- a/tests/modules/organization/tftest.yaml +++ b/tests/modules/organization/tftest.yaml @@ -1,4 +1,4 @@ -# Copyright 2022 Google LLC +# Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -23,3 +23,4 @@ tests: org_policies_boolean: org_policies_custom_constraints: firewall_policies_factory_combined: + tags: diff --git a/tools/check_links.py b/tools/check_links.py index 77dc6173..1e2759df 100755 --- a/tools/check_links.py +++ b/tools/check_links.py @@ -86,7 +86,7 @@ def main(dirs, external): state = '✓' if all(l.valid for l in doc.links) else '✗' print(f'[{state}] {doc.relpath} ({len(doc.links)})') if state == '✗': - error = [f'{dir_name}{doc.relpath}'] + error = [f'{dir_name}/{doc.relpath}'] for l in doc.links: if not l.valid: error.append(f' - {l.dest}') diff --git a/tools/plan_summary.py b/tools/plan_summary.py index 78c5f939..ae52c86c 100755 --- a/tools/plan_summary.py +++ b/tools/plan_summary.py @@ -15,16 +15,19 @@ # limitations under the License. import click +import os import sys import tempfile import yaml from pathlib import Path -BASEDIR = Path(__file__).parents[1] -sys.path.append(str(BASEDIR / 'tests')) - -import fixtures +try: + import fixtures +except ImportError: + BASEDIR = Path(__file__).parents[1] + sys.path.append(str(BASEDIR / 'tests')) + import fixtures @click.command()