diff --git a/samples/x-nb-psc-sb-psc-ilb-crun/.gitignore b/samples/x-nb-psc-sb-psc-ilb-crun/.gitignore new file mode 100644 index 0000000..6811358 --- /dev/null +++ b/samples/x-nb-psc-sb-psc-ilb-crun/.gitignore @@ -0,0 +1,8 @@ +.env +.terraform.lock.hcl +terraform.tfstate.backup +terraform.tfstate +.terraform.tfstate.lock.info +.DS_Store +AM-SetAudience.xml +.terraform diff --git a/samples/x-nb-psc-sb-psc-ilb-crun/README.md b/samples/x-nb-psc-sb-psc-ilb-crun/README.md new file mode 100644 index 0000000..7c55f3d --- /dev/null +++ b/samples/x-nb-psc-sb-psc-ilb-crun/README.md @@ -0,0 +1,119 @@ +# How to consume internal Cloud Run apps from Apigee X using a single Service Attachment + +## Overview + +This sample emphasises on the southbound connectivity based on +Private Service Connect (PSC) to communicate with internal Cloud Run +applications through an internal HTTPS (L7) load balancer. + +![Use case overview](./pictures/overview.png) + +An internal HTTPS load balancer (L7 ILB) is used to serve several Cloud Run apps using Serverless NEG and URL Mask. + +A serverless NEG backend can point to several Cloud Run services. + +A URL mask is a template of your URL schema. The serverless NEG uses this template to map the request to the appropriate service. + +The L7 ILB is accessed through a PSC Service Attachment that can be reached by an ApigeeX instance via a PSC endpoint Attachment. These two attachments (Endpoint and Service attachments) must be part of the same GCP region. + +Apigee X uses a target endpoint to reach the L7 ILB through the PSC channel. This target endpoint is configured in HTTPS and uses a dedicated hostname (the hostname of the l7 ILB). Therefore we need two DNS resolutions: + +- The first on Apigee X (target endpoint) to point to the PSC endpoint attachment: a private Cloud DNS (A record) and a DNS peering are required to manage the resolution. This private zone is configured on the consumer VPC (named apigee-network in the picture above) that is peered with the Apigee X VPC. The DNS peering is configured between the two VPCs for a particular domain (iloveapis.io in the example above) +- The second on the PSC service attachment to point to the L7 ILB: a private Cloud DNS (A record) is required to manage the resolution. This private zone is configured on the VPC (named ilb-network in the picture above). Note that the L7 ILB presents an SSL certificate used to establish a secured communication + +An identity token (ID token) is required to consume the different Cloud Run services. +This ID token is generated on Apigee X and transmitted as a bearer token to the Cloud Run apps. + +A service account (```sa_apigee_apiproxy```) is created for this purpose and used to deplpy the API proxy (```cloudrun-api-v1```) on the Apigee X instance. +The permission of this service account is ``` roles/run.invoker ``` + +## Setup Instructions + +You can implement a full or partial install of the sample + +### Full installation: Apigee X (PSC NB) + Southbound connectivity to Cloud Run apps using PSC + +For this type of installation, we consider that an Apigee X instance has already been provisionned. + +### Partial installation: Southbound connectivity to Cloud Run apps using PSC + +For this type of installation, we consider that an Apigee X instance has already been provisionned. + +We do not care about the existing northbopund connectivity, which can rely on VPC peering or PSC. + +Our focus here is the deployment of 3 (basic) internal Cloud Run services and the network components +to consume them from the Apigee X instance. Apigee is of course used to expose these services +as APIs and API products. + + +## Requirements + +| Name | Version | +|------|---------| +| [google](#requirement\_google) | 4.58.0 | + +## Providers + +| Name | Version | +|------|---------| +| [google](#provider\_google) | 4.58.0 | +| [null](#provider\_null) | 3.2.1 | +| [tls](#provider\_tls) | 4.0.4 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [cloud\_run](#module\_cloud\_run) | GoogleCloudPlatform/cloud-run/google | ~> 0.2.0 | + +## Resources + +| Name | Type | +|------|------| +| [google_apigee_endpoint_attachment.endpoint_attachment](https://registry.terraform.io/providers/hashicorp/google/4.58.0/docs/resources/apigee_endpoint_attachment) | resource | +| [google_artifact_registry_repository.docker-main](https://registry.terraform.io/providers/hashicorp/google/4.58.0/docs/resources/artifact_registry_repository) | resource | +| [google_compute_address.default](https://registry.terraform.io/providers/hashicorp/google/4.58.0/docs/resources/compute_address) | resource | +| [google_compute_forwarding_rule.default](https://registry.terraform.io/providers/hashicorp/google/4.58.0/docs/resources/compute_forwarding_rule) | resource | +| [google_compute_network.default](https://registry.terraform.io/providers/hashicorp/google/4.58.0/docs/resources/compute_network) | resource | +| [google_compute_region_backend_service.default](https://registry.terraform.io/providers/hashicorp/google/4.58.0/docs/resources/compute_region_backend_service) | resource | +| [google_compute_region_network_endpoint_group.cloudrun_neg](https://registry.terraform.io/providers/hashicorp/google/4.58.0/docs/resources/compute_region_network_endpoint_group) | resource | +| [google_compute_region_ssl_certificate.default](https://registry.terraform.io/providers/hashicorp/google/4.58.0/docs/resources/compute_region_ssl_certificate) | resource | +| [google_compute_region_target_https_proxy.default](https://registry.terraform.io/providers/hashicorp/google/4.58.0/docs/resources/compute_region_target_https_proxy) | resource | +| [google_compute_region_url_map.https_lb](https://registry.terraform.io/providers/hashicorp/google/4.58.0/docs/resources/compute_region_url_map) | resource | +| [google_compute_service_attachment.psc_ilb_service_attachment](https://registry.terraform.io/providers/hashicorp/google/4.58.0/docs/resources/compute_service_attachment) | resource | +| [google_compute_subnetwork.default](https://registry.terraform.io/providers/hashicorp/google/4.58.0/docs/resources/compute_subnetwork) | resource | +| [google_compute_subnetwork.proxy_subnet](https://registry.terraform.io/providers/hashicorp/google/4.58.0/docs/resources/compute_subnetwork) | resource | +| [google_compute_subnetwork.psc_ilb_nat](https://registry.terraform.io/providers/hashicorp/google/4.58.0/docs/resources/compute_subnetwork) | resource | +| [google_dns_managed_zone.private-zone-apigee](https://registry.terraform.io/providers/hashicorp/google/4.58.0/docs/resources/dns_managed_zone) | resource | +| [google_dns_managed_zone.private-zone-ilb](https://registry.terraform.io/providers/hashicorp/google/4.58.0/docs/resources/dns_managed_zone) | resource | +| [google_dns_record_set.a-apigee](https://registry.terraform.io/providers/hashicorp/google/4.58.0/docs/resources/dns_record_set) | resource | +| [google_dns_record_set.a-ilb](https://registry.terraform.io/providers/hashicorp/google/4.58.0/docs/resources/dns_record_set) | resource | +| [google_project_iam_member.sa_apigee_apiproxy](https://registry.terraform.io/providers/hashicorp/google/4.58.0/docs/resources/project_iam_member) | resource | +| [google_project_service.gcp_services](https://registry.terraform.io/providers/hashicorp/google/4.58.0/docs/resources/project_service) | resource | +| [google_service_account.service_account_apiproxy](https://registry.terraform.io/providers/hashicorp/google/4.58.0/docs/resources/service_account) | resource | +| [google_service_networking_peered_dns_domain.apigee](https://registry.terraform.io/providers/hashicorp/google/4.58.0/docs/resources/service_networking_peered_dns_domain) | resource | +| [null_resource.login_image_build](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource | +| [null_resource.search_image_build](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource | +| [null_resource.translate_image_build](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource | +| [tls_private_key.default](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/resources/private_key) | resource | +| [tls_self_signed_cert.default](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/resources/self_signed_cert) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [apigee\_endpoint\_attachment](#input\_apigee\_endpoint\_attachment) | Apigee endpoint attachment value. | `string` | n/a | yes | +| [consumer\_vpc](#input\_consumer\_vpc) | Consumer VPC network name. | `string` | n/a | yes | +| [gcp\_project\_id](#input\_gcp\_project\_id) | The GCP project ID to create the gcp resources in. | `string` | n/a | yes | +| [gcp\_region](#input\_gcp\_region) | The GCP region to create the gcp resources in. | `string` | n/a | yes | +| [gcp\_service\_list](#input\_gcp\_service\_list) | The list of required Google apis | `list(string)` |
[
"artifactregistry.googleapis.com",
"cloudbuild.googleapis.com",
"run.googleapis.com",
"dns.googleapis.com",
"compute.googleapis.com",
"logging.googleapis.com",
"monitoring.googleapis.com"
]
| no | +| [gcp\_zone](#input\_gcp\_zone) | The GCP zone to create the gcp resources in. | `string` | n/a | yes | +| [repository\_id](#input\_repository\_id) | Repository id of the artifact registry. | `string` | n/a | yes | +| [url\_mask](#input\_url\_mask) | URL mask of the serverless network endpoint group (neg). | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [service\_urls](#output\_service\_urls) | Cloud Run service URLs | + \ No newline at end of file diff --git a/samples/x-nb-psc-sb-psc-ilb-crun/cleanup.sh b/samples/x-nb-psc-sb-psc-ilb-crun/cleanup.sh new file mode 100755 index 0000000..c8a8b1c --- /dev/null +++ b/samples/x-nb-psc-sb-psc-ilb-crun/cleanup.sh @@ -0,0 +1,44 @@ +#!/bin/sh +# 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. + +set -e + +SCRIPTPATH="$( cd "$(dirname "$0")" || exit >/dev/null 2>&1 ; pwd -P )" + +# Ask for input parameters if they are not set + +[ -z "$GCP_PROJECT_ID" ] && printf "GCP project id: " && read -r GCP_PROJECT_ID + +### +### destroy_common_gcp_resources() +### +destroy_common_gcp_resources() { + + terraform init + terraform destroy --var-file="./input.tfvars" \ + -var "gcp_project_id=${GCP_PROJECT_ID}" + -auto-approve +} + +## +### +### destroy_common_gcp_resources() +### +main() { + cd ${SCRIPTPATH} + destroy_common_gcp_resources +} + +main "${@}" diff --git a/samples/x-nb-psc-sb-psc-ilb-crun/cloudrun-api-v1/apiproxy/cloudrun-api-v1.xml b/samples/x-nb-psc-sb-psc-ilb-crun/cloudrun-api-v1/apiproxy/cloudrun-api-v1.xml new file mode 100644 index 0000000..fe974a4 --- /dev/null +++ b/samples/x-nb-psc-sb-psc-ilb-crun/cloudrun-api-v1/apiproxy/cloudrun-api-v1.xml @@ -0,0 +1,14 @@ + + + diff --git a/samples/x-nb-psc-sb-psc-ilb-crun/cloudrun-api-v1/apiproxy/policies/RF-404NotFound.xml b/samples/x-nb-psc-sb-psc-ilb-crun/cloudrun-api-v1/apiproxy/policies/RF-404NotFound.xml new file mode 100644 index 0000000..f01f7b8 --- /dev/null +++ b/samples/x-nb-psc-sb-psc-ilb-crun/cloudrun-api-v1/apiproxy/policies/RF-404NotFound.xml @@ -0,0 +1,23 @@ + + + + + + + {"error":"not_found"} + 404 + Not Found + + + diff --git a/samples/x-nb-psc-sb-psc-ilb-crun/cloudrun-api-v1/apiproxy/proxies/default.xml b/samples/x-nb-psc-sb-psc-ilb-crun/cloudrun-api-v1/apiproxy/proxies/default.xml new file mode 100644 index 0000000..dad25ed --- /dev/null +++ b/samples/x-nb-psc-sb-psc-ilb-crun/cloudrun-api-v1/apiproxy/proxies/default.xml @@ -0,0 +1,55 @@ + + + + + + + (proxy.pathsuffix MatchesPath "/login") and (request.verb = "GET") + + + + + (proxy.pathsuffix MatchesPath "/search") and (request.verb = "GET") + + + + + (proxy.pathsuffix MatchesPath "/translate") and (request.verb = "GET") + + + + + + + RF-404NotFound + + + + + + + + + AM-SetAudience + + + + + + /v1/crun + + + default + + diff --git a/samples/x-nb-psc-sb-psc-ilb-crun/cloudrun-api-v1/apiproxy/targets/default.xml b/samples/x-nb-psc-sb-psc-ilb-crun/cloudrun-api-v1/apiproxy/targets/default.xml new file mode 100644 index 0000000..ae8107c --- /dev/null +++ b/samples/x-nb-psc-sb-psc-ilb-crun/cloudrun-api-v1/apiproxy/targets/default.xml @@ -0,0 +1,26 @@ + + + + + + + + https://internal.example.com + + + + + + + diff --git a/samples/x-nb-psc-sb-psc-ilb-crun/input.tfvars b/samples/x-nb-psc-sb-psc-ilb-crun/input.tfvars new file mode 100644 index 0000000..cd84f21 --- /dev/null +++ b/samples/x-nb-psc-sb-psc-ilb-crun/input.tfvars @@ -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. + */ + +/** + ** gcp variables +*/ +gcp_region = "europe-west1" +gcp_zone = "europe-west1-b" + +/** + ** artifact registry variables +*/ +repository_id = "docker-main" + +/** + ** serverless neg variables +*/ +url_mask = "/" + +/** + ** name of the apigee endpoint attachment +*/ +apigee_endpoint_attachment = "pscendpoint" + +/** + ** consumer vpc network name +*/ +consumer_vpc = "apigee-network" diff --git a/samples/x-nb-psc-sb-psc-ilb-crun/main.tf b/samples/x-nb-psc-sb-psc-ilb-crun/main.tf new file mode 100644 index 0000000..f12063d --- /dev/null +++ b/samples/x-nb-psc-sb-psc-ilb-crun/main.tf @@ -0,0 +1,342 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +variable "gcp_service_list" { + description = "The list of required Google apis" + type = list(string) + default = [ + "artifactregistry.googleapis.com", + "cloudbuild.googleapis.com", + "run.googleapis.com", + "dns.googleapis.com", + "compute.googleapis.com", + "logging.googleapis.com", + "monitoring.googleapis.com" + ] +} + +resource "google_project_service" "gcp_services" { + for_each = toset(var.gcp_service_list) + project = var.gcp_project_id + service = each.key + disable_dependent_services = true +} + +resource "google_artifact_registry_repository" "docker-main" { + location = var.gcp_region + repository_id = var.repository_id + description = "Main Docker Repository for Cloud Run" + format = "DOCKER" + depends_on = [ + google_project_service.gcp_services + ] +} + +resource "null_resource" "login_image_build" { + triggers = { + always_run = timestamp() + } + + provisioner "local-exec" { + working_dir = "${path.module}/services/login" + command = "gcloud builds submit --tag ${var.gcp_region}-docker.pkg.dev/${var.gcp_project_id}/docker-main/login" + } + + depends_on = [ + google_artifact_registry_repository.docker-main + ] +} + +resource "null_resource" "search_image_build" { + triggers = { + always_run = timestamp() + } + + provisioner "local-exec" { + working_dir = "${path.module}/services/search" + command = "gcloud builds submit --tag ${var.gcp_region}-docker.pkg.dev/${var.gcp_project_id}/docker-main/search" + } + + depends_on = [ + google_artifact_registry_repository.docker-main + ] +} + +resource "null_resource" "translate_image_build" { + triggers = { + always_run = timestamp() + } + + provisioner "local-exec" { + working_dir = "${path.module}/services/translate" + command = "gcloud builds submit --tag ${var.gcp_region}-docker.pkg.dev/${var.gcp_project_id}/docker-main/translate" + } + + depends_on = [ + google_artifact_registry_repository.docker-main + ] +} + +module "cloud_run" { + source = "GoogleCloudPlatform/cloud-run/google" + version = "~> 0.2.0" + + #3 Cloud Run internal services + for_each = toset([ + "login", + "search", + "translate" + ]) + + # Required variables + service_name = each.key + project_id = var.gcp_project_id + location = var.gcp_region + image = "${var.gcp_region}-docker.pkg.dev/${var.gcp_project_id}/docker-main/${each.key}" + service_annotations = { + "run.googleapis.com/ingress": "internal" + } + + depends_on = [ + google_project_service.gcp_services, + null_resource.login_image_build, + null_resource.search_image_build, + null_resource.translate_image_build + ] + +} + +# VPC network +resource "google_compute_network" "default" { + name = "l7-ilb-network" + auto_create_subnetworks = false +} + +# Proxy-only subnet +resource "google_compute_subnetwork" "proxy_subnet" { + name = "l7-ilb-proxy-subnet" + ip_cidr_range = "10.0.0.0/24" + region = var.gcp_region + purpose = "REGIONAL_MANAGED_PROXY" + role = "ACTIVE" + network = google_compute_network.default.id +} + +# Backend subnet +resource "google_compute_subnetwork" "default" { + name = "l7-ilb-subnet" + ip_cidr_range = "10.0.1.0/24" + region = var.gcp_region + network = google_compute_network.default.id +} + +# Reserved internal address +resource "google_compute_address" "default" { + name = "l7-ilb-ip" + subnetwork = google_compute_subnetwork.default.id + address_type = "INTERNAL" + address = "10.0.1.5" + region = var.gcp_region + project = var.gcp_project_id +} + +# Regional forwarding rule +resource "google_compute_forwarding_rule" "default" { + project = var.gcp_project_id + name = "psc-l7-ilb-forwarding-rule" + region = var.gcp_region + depends_on = [google_compute_subnetwork.proxy_subnet] + ip_address = google_compute_address.default.id + load_balancing_scheme = "INTERNAL_MANAGED" + port_range = "443" + target = google_compute_region_target_https_proxy.default.id + network = google_compute_network.default.id + subnetwork = google_compute_subnetwork.default.id + network_tier = "PREMIUM" +} + +# Self-signed regional SSL certificate for testing +resource "tls_private_key" "default" { + algorithm = "RSA" + rsa_bits = 2048 +} + +resource "tls_self_signed_cert" "default" { + private_key_pem = tls_private_key.default.private_key_pem + + # Certificate expires after 100 days. + validity_period_hours = 2400 + + # Generate a new certificate if Terraform is run within three + # hours of the certificate's expiration time. + early_renewal_hours = 3 + + # Reasonable set of uses for a server SSL certificate. + allowed_uses = [ + "key_encipherment", + "digital_signature", + "server_auth", + ] + dns_names = ["internal.example.com"] + subject { + common_name = "internal.example.com" + organization = "Exo, Inc" + } +} + +resource "google_compute_region_ssl_certificate" "default" { + name_prefix = "exco-sslcert-" + private_key = tls_private_key.default.private_key_pem + certificate = tls_self_signed_cert.default.cert_pem + region = var.gcp_region + lifecycle { + create_before_destroy = true + } +} + +# Regional target HTTPS proxy +resource "google_compute_region_target_https_proxy" "default" { + name = "l7-ilb-target-https-proxy" + region = var.gcp_region + url_map = google_compute_region_url_map.https_lb.id + ssl_certificates = [google_compute_region_ssl_certificate.default.self_link] +} + +# Regional URL map +resource "google_compute_region_url_map" "https_lb" { + name = "l7-ilb-regional-url-map" + region = var.gcp_region + default_service = google_compute_region_backend_service.default.id +} + +resource "google_compute_region_backend_service" "default" { + load_balancing_scheme = "INTERNAL_MANAGED" + backend { + group = google_compute_region_network_endpoint_group.cloudrun_neg.id + balancing_mode = "UTILIZATION" + } + region = var.gcp_region + name = "region-backend-service" + protocol = "HTTPS" +} + +resource "google_compute_region_network_endpoint_group" "cloudrun_neg" { + name = "cloudrun-neg" + network_endpoint_type = "SERVERLESS" + region = var.gcp_region + cloud_run { + url_mask = var.url_mask + } +} + +resource "google_compute_subnetwork" "psc_ilb_nat" { + name = "psc-ilb-nat" + region = var.gcp_region + network = google_compute_network.default.id + purpose = "PRIVATE_SERVICE_CONNECT" + ip_cidr_range = "10.75.0.0/28" +} + +resource "google_compute_service_attachment" "psc_ilb_service_attachment" { + name = "psc-attachment-ilb" + region = var.gcp_region + description = "Service attachment for ilb configured with Terraform" + project = var.gcp_project_id + enable_proxy_protocol = false + connection_preference = "ACCEPT_AUTOMATIC" + nat_subnets = [google_compute_subnetwork.psc_ilb_nat.id] + target_service = google_compute_forwarding_rule.default.id +} + +resource "google_apigee_endpoint_attachment" "endpoint_attachment" { + org_id = "organizations/${var.gcp_project_id}" + endpoint_attachment_id = var.apigee_endpoint_attachment + location = var.gcp_region + service_attachment = google_compute_service_attachment.psc_ilb_service_attachment.id +} + +# Service account used by apigee apiproxy to invoke a cloud run app using id token +resource "google_service_account" "service_account_apiproxy" { + account_id = "apigee-apiproxy" + display_name = "invoke cloud run app from apigee apiproxy" +} + +locals { + service_account_a = "serviceAccount:apigee-apiproxy@${var.gcp_project_id}.iam.gserviceaccount.com" + } + +resource "google_project_iam_member" "sa_apigee_apiproxy" { + for_each = toset([ + "roles/run.invoker" + ]) + role = each.key + project = var.gcp_project_id + member = local.service_account_a + depends_on = [google_service_account.service_account_apiproxy] +} + +resource "google_dns_managed_zone" "private-zone-apigee" { + name = "private-zone-apigee" + dns_name = "example.com." + description = "ExCo private DNS zone (Apigee)" + visibility = "private" + private_visibility_config { + networks { + network_url = "projects/${var.gcp_project_id}/global/networks/${var.consumer_vpc}" + } + } + depends_on = [ + google_project_service.gcp_services + ] +} + +resource "google_dns_managed_zone" "private-zone-ilb" { + name = "private-zone-ilb" + dns_name = "example.com." + description = "ExCo private DNS zone (L7 ILB)" + visibility = "private" + private_visibility_config { + networks { + network_url = "projects/${var.gcp_project_id}/global/networks/l7-ilb-network" + } + } + depends_on = [ + google_project_service.gcp_services + ] +} + +resource "google_dns_record_set" "a-apigee" { + name = "internal.${google_dns_managed_zone.private-zone-apigee.dns_name}" + managed_zone = google_dns_managed_zone.private-zone-apigee.name + type = "A" + ttl = 300 + rrdatas = [google_apigee_endpoint_attachment.endpoint_attachment.host] +} + +resource "google_dns_record_set" "a-ilb" { + name = "internal.${google_dns_managed_zone.private-zone-ilb.dns_name}" + managed_zone = google_dns_managed_zone.private-zone-ilb.name + type = "A" + ttl = 300 + rrdatas = ["10.0.1.5"] +} + +resource "google_service_networking_peered_dns_domain" "apigee" { + project = var.gcp_project_id + name = "apigee-dns-peering" + network = var.consumer_vpc + dns_suffix = google_dns_managed_zone.private-zone-apigee.dns_name +} diff --git a/samples/x-nb-psc-sb-psc-ilb-crun/outputs.tf b/samples/x-nb-psc-sb-psc-ilb-crun/outputs.tf new file mode 100644 index 0000000..4d384de --- /dev/null +++ b/samples/x-nb-psc-sb-psc-ilb-crun/outputs.tf @@ -0,0 +1,20 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +output "service_urls" { + description = "Cloud Run service URLs" + value = values(module.cloud_run)[*].service_url +} diff --git a/samples/x-nb-psc-sb-psc-ilb-crun/pictures/overview.png b/samples/x-nb-psc-sb-psc-ilb-crun/pictures/overview.png new file mode 100644 index 0000000..bb46da0 Binary files /dev/null and b/samples/x-nb-psc-sb-psc-ilb-crun/pictures/overview.png differ diff --git a/samples/x-nb-psc-sb-psc-ilb-crun/pipeline.sh b/samples/x-nb-psc-sb-psc-ilb-crun/pipeline.sh new file mode 100755 index 0000000..0faf64f --- /dev/null +++ b/samples/x-nb-psc-sb-psc-ilb-crun/pipeline.sh @@ -0,0 +1,69 @@ +#!/bin/sh +# 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. + +set -e + +SCRIPTPATH="$( cd "$(dirname "$0")" || exit >/dev/null 2>&1 ; pwd -P )" + +# Ask for input parameters if they are not set + +[ -z "$GCP_PROJECT_ID" ] && printf "GCP project id: " && read -r GCP_PROJECT_ID +[ -z "$APIGEE_X_ORG" ] && printf "Apigee X Organization: " && read -r APIGEE_X_ORG +[ -z "$APIGEE_X_ENV" ] && printf "Apigee X Environment: " && read -r APIGEE_X_ENV +[ -z "$APIGEE_X_HOSTNAME" ] && printf "Apigee X Hostname: " && read -r APIGEE_X_HOSTNAME + +### +### create_common_gcp_resources() +### +create_common_gcp_resources() { + + terraform init + + terraform plan -var "gcp_project_id=${GCP_PROJECT_ID}" \ + --var-file=./input.tfvars + + terraform apply -var "gcp_project_id=${GCP_PROJECT_ID}" \ + --var-file=./input.tfvars \ + -auto-approve +} + +## +### +### create_common_gcp_resources() +### +main() { + cd "$SCRIPTPATH" + create_common_gcp_resources + # Cloud Run app url + APP_URL=$(terraform output -json service_urls | jq -r '.[0]') + + # dynamic set of the audience + CLOUDRUN_APP_SUFFIX=$(echo "${APP_URL}" | sed "s/https:\/\/login//") + export CLOUDRUN_APP_SUFFIX + envsubst < "$SCRIPTPATH"/templates/AM-SetAudience.template.xml > "$SCRIPTPATH"/cloudrun-api-v1/apiproxy/policies/AM-SetAudience.xml + + # deploy the Apigee api proxy + APIGEE_TOKEN="$(gcloud config config-helper --force-auth-refresh --format json | jq -r '.credential.access_token')" + SA_EMAIL="apigee-apiproxy@$GCP_PROJECT_ID.iam.gserviceaccount.com" + sackmesser deploy --googleapi \ + -o "$APIGEE_X_ORG" \ + -e "$APIGEE_X_ENV" \ + -t "$APIGEE_TOKEN" \ + -h "$APIGEE_X_HOSTNAME" \ + -d "$SCRIPTPATH"/cloudrun-api-v1 \ + --deployment-sa "$SA_EMAIL" +} + +main "${@}" diff --git a/samples/x-nb-psc-sb-psc-ilb-crun/provider.tf b/samples/x-nb-psc-sb-psc-ilb-crun/provider.tf new file mode 100644 index 0000000..6f28e18 --- /dev/null +++ b/samples/x-nb-psc-sb-psc-ilb-crun/provider.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 + * + * 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 { + required_providers { + google = { + source = "hashicorp/google" + version = "4.58.0" + } + } +} + +provider "google" { + project = var.gcp_project_id + region = var.gcp_region + zone = var.gcp_zone +} diff --git a/samples/x-nb-psc-sb-psc-ilb-crun/services/login/.dockerignore b/samples/x-nb-psc-sb-psc-ilb-crun/services/login/.dockerignore new file mode 100644 index 0000000..29d6828 --- /dev/null +++ b/samples/x-nb-psc-sb-psc-ilb-crun/services/login/.dockerignore @@ -0,0 +1,3 @@ +node_modules +npm-debug.log + diff --git a/samples/x-nb-psc-sb-psc-ilb-crun/services/login/Dockerfile b/samples/x-nb-psc-sb-psc-ilb-crun/services/login/Dockerfile new file mode 100644 index 0000000..ebfe054 --- /dev/null +++ b/samples/x-nb-psc-sb-psc-ilb-crun/services/login/Dockerfile @@ -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. +FROM node:16 + +# Create app directory +WORKDIR /usr/src/app + +# Install app dependencies +# A wildcard is used to ensure both package.json AND package-lock.json are copied +# where available (npm@5+) +COPY package*.json ./ + +RUN npm install +# If you are building your code for production +# RUN npm ci --only=production + +# Bundle app source +COPY . . + +EXPOSE 8080 +CMD [ "node", "index.js" ] diff --git a/samples/x-nb-psc-sb-psc-ilb-crun/services/login/index.js b/samples/x-nb-psc-sb-psc-ilb-crun/services/login/index.js new file mode 100644 index 0000000..e56ce8d --- /dev/null +++ b/samples/x-nb-psc-sb-psc-ilb-crun/services/login/index.js @@ -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 + + 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. + +*/ +const express = require('express'); +const app = express(); + +app.get('/login', (req, res) => { + const name = process.env.NAME || 'Login'; + res.json({ + service: "login", + id: "123-abc" + }); +}); + +const port = parseInt(process.env.PORT) || 8080; +app.listen(port, () => { + console.log(`Login service: listening on port ${port}`); +}); diff --git a/samples/x-nb-psc-sb-psc-ilb-crun/services/login/package.json b/samples/x-nb-psc-sb-psc-ilb-crun/services/login/package.json new file mode 100644 index 0000000..640456d --- /dev/null +++ b/samples/x-nb-psc-sb-psc-ilb-crun/services/login/package.json @@ -0,0 +1,18 @@ +{ + "name": "login", + "description": "Simple Login service in Node", + "version": "1.0.0", + "private": true, + "main": "index.js", + "scripts": { + "start": "node index.js" + }, + "engines": { + "node": ">=12.0.0" + }, + "author": "Google LLC", + "license": "Apache-2.0", + "dependencies": { + "express": "^4.17.1" + } +} diff --git a/samples/x-nb-psc-sb-psc-ilb-crun/services/search/.dockerignore b/samples/x-nb-psc-sb-psc-ilb-crun/services/search/.dockerignore new file mode 100644 index 0000000..29d6828 --- /dev/null +++ b/samples/x-nb-psc-sb-psc-ilb-crun/services/search/.dockerignore @@ -0,0 +1,3 @@ +node_modules +npm-debug.log + diff --git a/samples/x-nb-psc-sb-psc-ilb-crun/services/search/Dockerfile b/samples/x-nb-psc-sb-psc-ilb-crun/services/search/Dockerfile new file mode 100644 index 0000000..ebfe054 --- /dev/null +++ b/samples/x-nb-psc-sb-psc-ilb-crun/services/search/Dockerfile @@ -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. +FROM node:16 + +# Create app directory +WORKDIR /usr/src/app + +# Install app dependencies +# A wildcard is used to ensure both package.json AND package-lock.json are copied +# where available (npm@5+) +COPY package*.json ./ + +RUN npm install +# If you are building your code for production +# RUN npm ci --only=production + +# Bundle app source +COPY . . + +EXPOSE 8080 +CMD [ "node", "index.js" ] diff --git a/samples/x-nb-psc-sb-psc-ilb-crun/services/search/index.js b/samples/x-nb-psc-sb-psc-ilb-crun/services/search/index.js new file mode 100644 index 0000000..234d0a5 --- /dev/null +++ b/samples/x-nb-psc-sb-psc-ilb-crun/services/search/index.js @@ -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 + + 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. + +*/ +const express = require('express'); +const app = express(); + +app.get('/search', (req, res) => { + const name = process.env.NAME || 'Search'; + res.json({ + service: "search", + id: "456-def" + }); +}); + +const port = parseInt(process.env.PORT) || 8080; +app.listen(port, () => { + console.log(`Search service: listening on port ${port}`); +}); diff --git a/samples/x-nb-psc-sb-psc-ilb-crun/services/search/package.json b/samples/x-nb-psc-sb-psc-ilb-crun/services/search/package.json new file mode 100644 index 0000000..41dd294 --- /dev/null +++ b/samples/x-nb-psc-sb-psc-ilb-crun/services/search/package.json @@ -0,0 +1,18 @@ +{ + "name": "search", + "description": "Simple Search service in Node", + "version": "1.0.0", + "private": true, + "main": "index.js", + "scripts": { + "start": "node index.js" + }, + "engines": { + "node": ">=12.0.0" + }, + "author": "Google LLC", + "license": "Apache-2.0", + "dependencies": { + "express": "^4.17.1" + } +} diff --git a/samples/x-nb-psc-sb-psc-ilb-crun/services/translate/.dockerignore b/samples/x-nb-psc-sb-psc-ilb-crun/services/translate/.dockerignore new file mode 100644 index 0000000..29d6828 --- /dev/null +++ b/samples/x-nb-psc-sb-psc-ilb-crun/services/translate/.dockerignore @@ -0,0 +1,3 @@ +node_modules +npm-debug.log + diff --git a/samples/x-nb-psc-sb-psc-ilb-crun/services/translate/Dockerfile b/samples/x-nb-psc-sb-psc-ilb-crun/services/translate/Dockerfile new file mode 100644 index 0000000..ba53edb --- /dev/null +++ b/samples/x-nb-psc-sb-psc-ilb-crun/services/translate/Dockerfile @@ -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. +FROM node:16 + +# Create app directory +WORKDIR /usr/src/app + +# Install app dependencies +# A wildcard is used to ensure both package.json AND package-lock.json are copied +# where available (npm@5+) +COPY package*.json ./ + +RUN npm install +# If you are building your code for production +# RUN npm ci --only=production + +# Bundle app source +COPY . . + +EXPOSE 8080 +CMD [ "node", "index.js" ] + diff --git a/samples/x-nb-psc-sb-psc-ilb-crun/services/translate/index.js b/samples/x-nb-psc-sb-psc-ilb-crun/services/translate/index.js new file mode 100644 index 0000000..ae01fd7 --- /dev/null +++ b/samples/x-nb-psc-sb-psc-ilb-crun/services/translate/index.js @@ -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 + + 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. + +*/ +const express = require('express'); +const app = express(); + +app.get('/translate', (req, res) => { + const name = process.env.NAME || 'Translate'; + res.json({ + service: "translate", + id: "789-ghi" + }); +}); + +const port = parseInt(process.env.PORT) || 8080; +app.listen(port, () => { + console.log(`Translate service: listening on port ${port}`); +}); diff --git a/samples/x-nb-psc-sb-psc-ilb-crun/services/translate/package.json b/samples/x-nb-psc-sb-psc-ilb-crun/services/translate/package.json new file mode 100644 index 0000000..f928cfb --- /dev/null +++ b/samples/x-nb-psc-sb-psc-ilb-crun/services/translate/package.json @@ -0,0 +1,18 @@ +{ + "name": "translate", + "description": "Simple Translate service in Node", + "version": "1.0.0", + "private": true, + "main": "index.js", + "scripts": { + "start": "node index.js" + }, + "engines": { + "node": ">=12.0.0" + }, + "author": "Google LLC", + "license": "Apache-2.0", + "dependencies": { + "express": "^4.17.1" + } +} diff --git a/samples/x-nb-psc-sb-psc-ilb-crun/templates/AM-SetAudience.template.xml b/samples/x-nb-psc-sb-psc-ilb-crun/templates/AM-SetAudience.template.xml new file mode 100644 index 0000000..d727843 --- /dev/null +++ b/samples/x-nb-psc-sb-psc-ilb-crun/templates/AM-SetAudience.template.xml @@ -0,0 +1,19 @@ + + + + + flow.audience + + + diff --git a/samples/x-nb-psc-sb-psc-ilb-crun/variables.tf b/samples/x-nb-psc-sb-psc-ilb-crun/variables.tf new file mode 100644 index 0000000..ba67396 --- /dev/null +++ b/samples/x-nb-psc-sb-psc-ilb-crun/variables.tf @@ -0,0 +1,50 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +variable "gcp_project_id" { + type = string + description = "The GCP project ID to create the gcp resources in." +} + +variable "gcp_region" { + type = string + description = "The GCP region to create the gcp resources in." +} + +variable "gcp_zone" { + type = string + description = "The GCP zone to create the gcp resources in." +} + +variable "repository_id" { + type = string + description = "Repository id of the artifact registry." +} + +variable "url_mask" { + type = string + description = "URL mask of the serverless network endpoint group (neg)." +} + +variable "apigee_endpoint_attachment" { + type = string + description = "Apigee endpoint attachment value." +} + +variable "consumer_vpc" { + type = string + description = "Consumer VPC network name." +} \ No newline at end of file