diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 6d773f9b..a73d8ae2 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -69,4 +69,7 @@ jobs: - name: Check python formatting id: yapf run: | - yapf --style="{based_on_style: google, indent_width: 2, SPLIT_BEFORE_NAMED_ASSIGNS: false}" -p -d tools/*.py + yapf --style="{based_on_style: google, indent_width: 2, SPLIT_BEFORE_NAMED_ASSIGNS: false}" -p -d \ + tools/*.py \ + blueprints/cloud-operations/network-dashboard/src/*py \ + blueprints/cloud-operations/network-dashboard/src/plugins/*py diff --git a/blueprints/apigee/bigquery-analytics/functions/export/package-lock.json b/blueprints/apigee/bigquery-analytics/functions/export/package-lock.json index 737005be..9ccbef77 100644 --- a/blueprints/apigee/bigquery-analytics/functions/export/package-lock.json +++ b/blueprints/apigee/bigquery-analytics/functions/export/package-lock.json @@ -826,9 +826,9 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, "node_modules/cookiejar": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz", - "integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==" + "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", @@ -3783,9 +3783,9 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, "cookiejar": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz", - "integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==" + "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", diff --git a/blueprints/apigee/hybrid-gke/README.md b/blueprints/apigee/hybrid-gke/README.md index cee4aec1..ae5c0364 100644 --- a/blueprints/apigee/hybrid-gke/README.md +++ b/blueprints/apigee/hybrid-gke/README.md @@ -25,20 +25,24 @@ The diagram below depicts the architecture. terraform apply ``` + Create an A record in your DNS registrar to point the environment group hostname to the public IP address returned after the terraform configuration was applied. You might need to wait some time until the certificate is provisioned. + +5. Install Apigee hybrid using de ansible playbook that is in the ansible folder by running this command + + ansible-playbook playbook.yaml -vvvß + ## Testing the blueprint 2. Deploy an api proxy ``` - ./deploy-apiproxy.sh + ./deploy-apiproxy.sh apis-test ``` -3. In the console check the IP address that has been allocated to the Apigee ingress gateway and send some traffic to the deployed API proxy. +3. Send a request ``` - curl -k -v -H "Host:HOSTNAME" \ - --resolve HOSTNAME:443:IP_ADDRESS \ - https://HOSTNAME/httpbin/headers + curl -v https://HOSTNAME/httpbin/headers ``` @@ -56,4 +60,10 @@ The diagram below depicts the architecture. | [region](variables.tf#L84) | Region. | string | | "europe-west1" | | [zone](variables.tf#L90) | Zone. | string | | "europe-west1-c" | +## Outputs + +| name | description | sensitive | +|---|---|:---:| +| [ip_address](outputs.tf#L17) | GLB IP address. | | + diff --git a/blueprints/apigee/hybrid-gke/ansible.tf b/blueprints/apigee/hybrid-gke/ansible.tf index e5a491a3..b7694ab1 100644 --- a/blueprints/apigee/hybrid-gke/ansible.tf +++ b/blueprints/apigee/hybrid-gke/ansible.tf @@ -18,12 +18,13 @@ resource "local_file" "vars_file" { content = yamlencode({ - cluster = module.cluster.name - region = var.region - project_id = module.project.project_id - envgroup = local.envgroup - env = local.environment - hostname = var.hostname + cluster = module.cluster.name + region = var.region + project_id = module.project.project_id + envgroups = local.envgroups + environments = local.environments + service_accounts = local.google_sas + ingress_ip_name = local.ingress_ip_name }) filename = "${path.module}/ansible/vars/vars.yaml" file_permission = "0666" diff --git a/blueprints/apigee/hybrid-gke/ansible/roles/apigee-hybrid/tasks/k8s_service_accounts.yaml b/blueprints/apigee/hybrid-gke/ansible/roles/apigee-hybrid/tasks/k8s_service_accounts.yaml new file mode 100644 index 00000000..e74ca159 --- /dev/null +++ b/blueprints/apigee/hybrid-gke/ansible/roles/apigee-hybrid/tasks/k8s_service_accounts.yaml @@ -0,0 +1,28 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +- name: Create and annotate k8s service account + kubernetes.core.k8s: + state: present + definition: + apiVersion: v1 + kind: ServiceAccount + metadata: + name: "{{ k8s_service_account }}" + namespace: apigee + annotations: + iam.gke.io/gcp-service-account: "{{ google_service_account }}@{{ project_id }}.iam.gserviceaccount.com" + with_items: "{{ k8s_service_accounts }}" + loop_control: + loop_var: k8s_service_account \ No newline at end of file diff --git a/blueprints/apigee/hybrid-gke/ansible/roles/apigee-hybrid/tasks/main.yaml b/blueprints/apigee/hybrid-gke/ansible/roles/apigee-hybrid/tasks/main.yaml index 4b72039b..0907846f 100644 --- a/blueprints/apigee/hybrid-gke/ansible/roles/apigee-hybrid/tasks/main.yaml +++ b/blueprints/apigee/hybrid-gke/ansible/roles/apigee-hybrid/tasks/main.yaml @@ -1,11 +1,11 @@ # 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. @@ -19,18 +19,27 @@ --project {{ project_id }} \ --internal-ip -- name: Install cert-manager - shell: > - kubectl apply \ - --validate=false \ - -f https://github.com/jetstack/cert-manager/releases/download/v1.7.2/cert-manager.yaml +- name: Download cert-manager + uri: + url: https://github.com/jetstack/cert-manager/releases/download/v1.7.2/cert-manager.yaml + dest: ~/cert-manager.yaml -- name: Wait until pods are ready in cert-manager namespace - shell: > - kubectl wait --for=condition=ready pods \ - -l app.kubernetes.io/instance=cert-manager \ - -n cert-manager \ - --timeout=90s +- name: Apply metrics-server manifest to the cluster. + kubernetes.core.k8s: + state: present + src: ~/cert-manager.yaml + +- name: + kubernetes.core.k8s_info: + kind: Pod + wait: yes + label_selectors: + - "app.kubernetes.io/instance=cert-manager" + namespace: cert-manager + wait_timeout: 90 + wait_condition: + type: Ready + status: True - name: Fetch apigeectl version uri: @@ -48,7 +57,7 @@ unarchive: src: "~/apigeectl.tar.gz" dest: "~" - remote_src: yes + remote_src: yes - name: Move apigeectl folder shell: > @@ -66,25 +75,69 @@ file: src: ~/apigeectl/{{ item }} dest: "~/hybrid-files/{{ item }}" - state: link + state: link with_items: - tools - config - templates - - plugins + - plugins -- name: Create service accounts - shell: > - ~/hybrid-files/tools/create-service-account -i {{ project_id }} -e non-prod -d ~/hybrid-files/service-accounts +- name: Create apigee namespace + kubernetes.core.k8s: + state: present + definition: + apiVersion: v1 + kind: Namespace + metadata: + name: apigee -- name: Create certificates +- name: Create k8s service accounts + include_tasks: k8s_service_accounts.yaml + vars: + google_service_account: "{{ item.key }}" + k8s_service_accounts: "{{ item.value }}" + with_dict: "{{ service_accounts }}" + +- name: Set hostnames + set_fact: + hostnames: "{{ hostnames | default([]) + item.value }}" + with_dict: "{{ envgroups }}" + +- name: Create certificate and private key shell: > openssl req \ -nodes \ -new \ -x509 \ - -keyout ~/hybrid-files/certs/{{ envgroup }}.key \ - -out ~/hybrid-files/certs/{{ envgroup }}.cert -subj '/CN='{{ hostname }}'' -days 3650 + -keyout ~/hybrid-files/certs/server.key \ + -out ~/hybrid-files/certs/server.crt \ + -subj "/CN=apigee.com' \ + -addext "subjectAltName={{ hostnames | map('regex_replace', '^', 'DNS:') | join(',') }}"" + -days 3650 + +- name: Read certificate + slurp: + src: ~/hybrid-files/certs/server.crt + register: certificate_output + +- name: Read private ket + slurp: + src: ~/hybrid-files/certs/server.key + register: privatekey_output + +- name: Create secret + kubernetes.core.k8s: + state: present + definition: + apiVersion: v1 + kind: Secret + metadata: + name: tls-hybrid-ingress + namespace: apigee + type: kubernetes.io/tls + data: + tls.crt: "{{ certificate_output.content }}" + tls.key: "{{ privatekey_output.content }}" - name: Create overrides.yaml template: @@ -96,48 +149,185 @@ curl -X POST -H "Authorization: Bearer $(gcloud auth print-access-token)" \ -H "Content-Type:application/json" \ "https://apigee.googleapis.com/v1/organizations/{{ project_id }}:setSyncAuthorization" \ - -d '{"identities":["'"serviceAccount:apigee-non-prod@{{ project_id }}.iam.gserviceaccount.com"'"]}' + -d '{"identities":["'"serviceAccount:apigee-synchronizer@{{ project_id }}.iam.gserviceaccount.com"'"]}' - name: Dry-run (init) shell: > - ~/apigeectl/apigeectl init -f overrides/overrides.yaml --dry-run=client + ~/apigeectl/apigeectl init -f overrides/overrides.yaml --dry-run=client args: chdir: ~/hybrid-files - name: Install the Apigee deployment services Apigee Deployment Controller and Apigee Admission Webhook. shell: > - ~/apigeectl/apigeectl init -f overrides/overrides.yaml + ~/apigeectl/apigeectl init -f overrides/overrides.yaml args: - chdir: ~/hybrid-files + chdir: ~/hybrid-files -- name: Wait until pods are ready in apigee-system namespace - shell: > - kubectl wait --for=condition=ready pods \ - -l app=apigee-controller \ - -n apigee-system \ - --timeout=300s +- name: Wait for apigee-controller pod to be ready + kubernetes.core.k8s_info: + kind: Pod + wait: yes + label_selectors: + - "app=apigee-controller" + namespace: apigee-system + wait_timeout: 600 + wait_condition: + type: Ready + status: True -- name: Wait until pods are ready in apigee namespace - shell: > - kubectl wait --for=condition=ready pods \ - -l app=apigee-ingressgateway-manager \ - -n apigee \ - --timeout=300s +- name: Wait for apigee-selfsigned-issuer issuer to be ready + kubernetes.core.k8s_info: + kind: Issuer + wait: yes + name: apigee-selfsigned-issuer + namespace: apigee-system + wait_timeout: 600 + wait_condition: + type: Ready + status: True + +- name: Wait for apigee-serving-cert certificate to be ready + kubernetes.core.k8s_info: + kind: Certificate + wait: yes + name: apigee-serving-cert + namespace: apigee-system + wait_timeout: 600 + wait_condition: + type: Ready + status: True + +- name: Wait for apigee-resources-install job to be complete + kubernetes.core.k8s_info: + kind: Job + wait: yes + name: apigee-resources-install + namespace: apigee-system + wait_timeout: 360 + wait_condition: + type: Complete + status: True - name: Dry-run (apply) shell: > - ~/apigeectl/apigeectl apply -f overrides/overrides.yaml --dry-run=client + ~/apigeectl/apigeectl apply -f overrides/overrides.yaml --dry-run=client args: chdir: ~/hybrid-files - name: Install the Apigee runtime components shell: > - ~/apigeectl/apigeectl apply -f overrides/overrides.yaml + ~/apigeectl/apigeectl apply -f overrides/overrides.yaml args: - chdir: ~/hybrid-files + chdir: ~/hybrid-files -- name: Check status of the deployment - shell: > - while [ -n "$(kubectl get pods -n apigee | tail -n +2 | grep -v Running | grep -v Completed)" ]; do sleep 1; done - args: - chdir: ~/hybrid-files \ No newline at end of file +- name: Wait for apigee-runtime pod to be ready + kubernetes.core.k8s_info: + kind: Pod + wait: yes + label_selectors: + - "app=apigee-runtime" + namespace: apigee + wait_timeout: 360 + wait_condition: + type: Ready + status: True + +- name: + kubernetes.core.k8s: + state: present + definition: + apiVersion: apigee.cloud.google.com/v1alpha1 + kind: ApigeeRoute + metadata: + name: apigee-wildcard + namespace: apigee + spec: + hostnames: + - '*' + ports: + - number: 443 + protocol: HTTPS + tls: + credentialName: tls-hybrid-ingress + mode: SIMPLE + selector: + app: apigee-ingressgateway + enableNonSniClient: true + +- name: Create google-managed certificate + kubernetes.core.k8s: + state: present + definition: + apiVersion: networking.gke.io/v1 + kind: ManagedCertificate + metadata: + name: "apigee-cert-hybrid" + namespace: apigee + spec: + domains: "{{ hostnames }}" + +- name: Create backend config + kubernetes.core.k8s: + state: present + definition: + apiVersion: cloud.google.com/v1 + kind: BackendConfig + metadata: + name: apigee-ingress-backendconfig + namespace: apigee + spec: + healthCheck: + requestPath: /healthz/ready + port: 15021 + type: HTTP + logging: + enable: true + sampleRate: 0.5 + +- name: Create service + kubernetes.core.k8s: + state: present + definition: + apiVersion: v1 + kind: Service + metadata: + name: apigee-ingressgateway-hybrid + namespace: apigee + annotations: + cloud.google.com/backend-config: '{"default": "apigee-ingress-backendconfig"}' + cloud.google.com/neg: '{"ingress": true}' + cloud.google.com/app-protocols: '{"https":"HTTPS", "status-port": "HTTP"}' + labels: + app: apigee-ingressgateway-hybrid + spec: + ports: + - name: status-port + port: 15021 + targetPort: 15021 + - name: https + port: 443 + targetPort: 8443 + selector: + app: apigee-ingressgateway + ingress_name: ingress + type: ClusterIP + +- name: Create ingress + kubernetes.core.k8s: + state: present + definition: + apiVersion: networking.k8s.io/v1 + kind: Ingress + metadata: + annotations: + networking.gke.io/managed-certificates: "apigee-cert-hybrid" + kubernetes.io/ingress.global-static-ip-name: "{{ ingress_ip_name }}" + kubernetes.io/ingress.allow-http: "false" + name: xlb-apigee + namespace: apigee + spec: + defaultBackend: + service: + name: apigee-ingressgateway-hybrid + port: + number: 443 \ No newline at end of file diff --git a/blueprints/apigee/hybrid-gke/ansible/roles/apigee-hybrid/templates/overrides.yaml.j2 b/blueprints/apigee/hybrid-gke/ansible/roles/apigee-hybrid/templates/overrides.yaml.j2 index 1c2c09ed..691cc6d5 100644 --- a/blueprints/apigee/hybrid-gke/ansible/roles/apigee-hybrid/templates/overrides.yaml.j2 +++ b/blueprints/apigee/hybrid-gke/ansible/roles/apigee-hybrid/templates/overrides.yaml.j2 @@ -1,29 +1,26 @@ gcp: region: {{ region }} projectID: {{ project_id }} + workloadIdentityEnabled: true k8sCluster: name: {{ cluster }} - region: CLUSTER_LOCATION # Must be the closest Google Cloud region to your cluster. + region: {{ region }} # Must be the closest Google Cloud region to your cluster. org: {{ project_id }} -instanceID: "instance-1" +instanceID: "{{ cluster }}-{{ region }}" cassandra: hostNetwork: false - # Set to false for single region installations and multi-region installations - # with connectivity between pods in different clusters, for example GKE installations. - # Set to true for multi-region installations with no communication between - # pods in different clusters, for example GKE On-prem, GKE on AWS, Anthos on bare metal, - # AKS, EKS, and OpenShift installations. - # See Multi-region deployment: Prerequisites virtualhosts: - - name: {{ envgroup }} +{% for k in envgroups %} + - name: {{ k }} + sslSecret: tls-hybrid-ingress + additionalGateways: ["apigee-wildcard"] selector: app: apigee-ingressgateway - sslCertPath: ./certs/{{ envgroup }}.cert - sslKeyPath: ./certs/{{ envgroup }}.key +{% endfor %} ao: args: @@ -37,27 +34,9 @@ ingressGateways: replicaCountMax: 10 envs: - - name: {{ env }} - serviceAccountPaths: - synchronizer: ./service-accounts/{{ project_id }}-apigee-non-prod.json - udca: ./service-accounts/{{ project_id }}-apigee-non-prod.json - runtime: ./service-accounts/{{ project_id }}-apigee-non-prod.json - -mart: - serviceAccountPath: ./service-accounts/{{ project_id }}-apigee-non-prod.json - -connectAgent: - serviceAccountPath: ./service-accounts/{{ project_id }}-apigee-non-prod.json - -metrics: - serviceAccountPath: ./service-accounts/{{ project_id }}-apigee-non-prod.json - -udca: - serviceAccountPath: ./service-accounts/{{ project_id }}-apigee-non-prod.json - -watcher: - serviceAccountPath: ./service-accounts/{{ project_id }}-apigee-non-prod.json +{% for k in environments %} + - name: {{ k }} +{% endfor %} logger: - enabled: true - serviceAccountPath: ./service-accounts/{{ project_id }}-apigee-non-prod.json + enabled: false diff --git a/blueprints/apigee/hybrid-gke/apigee.tf b/blueprints/apigee/hybrid-gke/apigee.tf index e3dc6b2e..b92592aa 100644 --- a/blueprints/apigee/hybrid-gke/apigee.tf +++ b/blueprints/apigee/hybrid-gke/apigee.tf @@ -15,8 +15,51 @@ */ locals { - envgroup = "test" - environment = "apis-test" + envgroups = { + test = [var.hostname] + } + environments = { + apis-test = { + envgroups = ["test"] + } + } + org_short_name = (length(module.project.project_id) < 16 ? + module.project.project_id : + substr(module.project.project_id, 0, 15)) + org_hash = format("%s-%s", local.org_short_name, substr(sha256(module.project.project_id), 0, 7)) + org_env_hashes = { + for k, v in local.environments : + k => format("%s-%s-%s", local.org_short_name, length(k) < 16 ? k : substr(k, 0, 15), substr(sha256("${module.project.project_id}:${k}"), 0, 7)) + } + google_sas = { + apigee-metrics = [ + "apigee-metrics-sa" + ] + apigee-cassandra = [ + "apigee-cassandra-schema-setup-${local.org_hash}-sa", + "apigee-cassandra-user-setup-${local.org_hash}-sa" + ] + apigee-mart = [ + "apigee-mart-${local.org_hash}-sa", + "apigee-connect-agent-${local.org_hash}-sa" + ] + apigee-watcher = [ + "apigee-watcher-${local.org_hash}-sa" + ] + apigee-udca = concat([ + "apigee-udca-${local.org_hash}-sa" + ], + [for k, v in local.org_env_hashes : + "apigee-udca-${local.org_env_hashes[k]}-sa" + ]) + apigee-synchronizer = [ + for k, v in local.org_env_hashes : + "apigee-synchronizer-${local.org_env_hashes[k]}-sa" + ] + apigee-runtime = [for k, v in local.org_env_hashes : + "apigee-runtime-${local.org_env_hashes[k]}-sa" + ] + } } module "apigee" { @@ -26,20 +69,24 @@ module "apigee" { analytics_region = var.region runtime_type = "HYBRID" } - envgroups = { - (local.envgroup) = [var.hostname] - } - environments = { - (local.environment) = { - envgroups = [local.envgroup] - } + envgroups = local.envgroups + environments = local.environments +} + +module "sas" { + for_each = local.google_sas + source = "../../../modules/iam-service-account" + project_id = module.project.project_id + name = each.key + # authoritative roles granted *on* the service accounts to other identities + iam = { + "roles/iam.workloadIdentityUser" = [for v in each.value : "serviceAccount:${module.project.project_id}.svc.id.goog[apigee/${v}]"] } } resource "local_file" "deploy_apiproxy_file" { content = templatefile("${path.module}/templates/deploy-apiproxy.sh.tpl", { org = module.project.project_id - env = local.environment }) filename = "${path.module}/deploy-apiproxy.sh" file_permission = "0777" diff --git a/blueprints/apigee/hybrid-gke/diagram.png b/blueprints/apigee/hybrid-gke/diagram.png index 6d5c2d6b..57e07ca3 100644 Binary files a/blueprints/apigee/hybrid-gke/diagram.png and b/blueprints/apigee/hybrid-gke/diagram.png differ diff --git a/blueprints/apigee/hybrid-gke/glb.tf b/blueprints/apigee/hybrid-gke/glb.tf new file mode 100644 index 00000000..80ff2269 --- /dev/null +++ b/blueprints/apigee/hybrid-gke/glb.tf @@ -0,0 +1,25 @@ +/** + * 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 { + ingress_ip_name = "apigee" +} + +module "addresses" { + source = "../../../modules/net-address" + project_id = module.project.project_id + global_addresses = [local.ingress_ip_name] +} diff --git a/blueprints/apigee/hybrid-gke/main.tf b/blueprints/apigee/hybrid-gke/main.tf index 5f1a676b..5be174ef 100644 --- a/blueprints/apigee/hybrid-gke/main.tf +++ b/blueprints/apigee/hybrid-gke/main.tf @@ -40,5 +40,12 @@ module "project" { "roles/resourcemanager.projectIamAdmin" = [module.mgmt_server.service_account_iam_email] "roles/iam.serviceAccountAdmin" = [module.mgmt_server.service_account_iam_email] "roles/iam.serviceAccountKeyAdmin" = [module.mgmt_server.service_account_iam_email] + "roles/monitoring.metricWriter" = [module.sas["apigee-metrics"].iam_email] + "roles/storage.objectAdmin" = [module.sas["apigee-cassandra"].iam_email] + "roles/apigeeconnect.Agent" = [module.sas["apigee-mart"].iam_email] + "roles/apigee.runtimeAgent" = [module.sas["apigee-watcher"].iam_email] + "roles/apigee.analyticsAgent" = [module.sas["apigee-udca"].iam_email] + "roles/apigee.synchronizerManager" = [module.sas["apigee-synchronizer"].iam_email] + "roles/cloudtrace.agent" = [module.sas["apigee-runtime"].iam_email] } -} \ No newline at end of file +} diff --git a/blueprints/apigee/hybrid-gke/mgmt.tf b/blueprints/apigee/hybrid-gke/mgmt.tf index f51975f5..538940e7 100644 --- a/blueprints/apigee/hybrid-gke/mgmt.tf +++ b/blueprints/apigee/hybrid-gke/mgmt.tf @@ -34,4 +34,12 @@ module "mgmt_server" { type = var.mgmt_server_config.disk_type size = var.mgmt_server_config.disk_size } -} \ No newline at end of file + metadata = { + startup-script = <string | | "net-dash" | | [project_create_config](variables.tf#L81) | Optional configuration if project creation is required. | object({…}) | | null | | [region](variables.tf#L95) | Compute region where the Cloud Function will be deployed. | string | | "europe-west1" | -| [schedule_config](variables.tf#L101) | Schedule timer configuration in crontab format. | string | | "0/30 * * * *" | +| [schedule_config](variables.tf#L101) | Schedule timer configuration in crontab format. | string | | "*/30 * * * *" | ## Outputs diff --git a/blueprints/cloud-operations/network-dashboard/deploy-cloud-function/variables.tf b/blueprints/cloud-operations/network-dashboard/deploy-cloud-function/variables.tf index ab59f91f..680b689d 100644 --- a/blueprints/cloud-operations/network-dashboard/deploy-cloud-function/variables.tf +++ b/blueprints/cloud-operations/network-dashboard/deploy-cloud-function/variables.tf @@ -101,5 +101,5 @@ variable "region" { variable "schedule_config" { description = "Schedule timer configuration in crontab format." type = string - default = "0/30 * * * *" + default = "*/30 * * * *" } diff --git a/blueprints/cloud-operations/network-dashboard/src/main.py b/blueprints/cloud-operations/network-dashboard/src/main.py index 6db262a6..bd57f18e 100755 --- a/blueprints/cloud-operations/network-dashboard/src/main.py +++ b/blueprints/cloud-operations/network-dashboard/src/main.py @@ -84,8 +84,8 @@ def do_discovery(resources): {k: len(v) for k, v in resources.items() if not isinstance(v, str)})) -def do_init(resources, discovery_root, monitoring_project, folders=None, projects=None, - custom_quota=None): +def do_init(resources, discovery_root, monitoring_project, folders=None, + projects=None, custom_quota=None): '''Calls init plugins to configure keys in the shared resource map. Args: diff --git a/blueprints/cloud-operations/network-dashboard/src/plugins/discover-cai.py b/blueprints/cloud-operations/network-dashboard/src/plugins/discover-cai.py index 1d22cd8e..1ac62d06 100644 --- a/blueprints/cloud-operations/network-dashboard/src/plugins/discover-cai.py +++ b/blueprints/cloud-operations/network-dashboard/src/plugins/discover-cai.py @@ -39,7 +39,9 @@ TYPES = { 'subnetworks': 'compute.googleapis.com/Subnetwork', 'routers': 'compute.googleapis.com/Router', 'routes': 'compute.googleapis.com/Route', - 'sql_instances': 'sqladmin.googleapis.com/Instance' + 'sql_instances': 'sqladmin.googleapis.com/Instance', + 'filestore_instances': 'file.googleapis.com/Instance', + 'memorystore_instances': 'redis.googleapis.com/Instance', } NAMES = {v: k for k, v in TYPES.items()} @@ -62,8 +64,8 @@ def _handle_discovery(resources, response, data): 'Processes the asset API response and returns parsed resources or next URL.' LOGGER.info('discovery handle request') for result in parse_cai_results(data, 'cai-compute', method='list'): - resource = _handle_resource( - resources, result['assetType'], result['resource']) + resource = _handle_resource(resources, result['assetType'], + result['resource']) if not resource: continue yield resource @@ -82,10 +84,16 @@ def _handle_resource(resources, asset_type, data): # e.g. assetType = GlobalAddress but discoveryName = Address resource_name = NAMES[asset_type] resource = { - 'id': attrs.get('id'), - 'name': attrs['name'], - 'self_link': _self_link(attrs['selfLink']), - 'assetType': asset_type + 'id': + attrs.get('id'), + 'name': + attrs['name'], + # Some resources (ex: Filestore) don't have a self_link, using parent + name in that case + 'self_link': + f'{data["parent"]}/{attrs["name"]}' + if not 'selfLink' in attrs else _self_link(attrs['selfLink']), + 'assetType': + asset_type } # derive parent type and id and skip if parent is not within scope parent_data = _get_parent(data['parent'], resources) @@ -212,8 +220,41 @@ def _handle_sql_instances(resource, data): ], 'region': data['region'], 'availabilityType': data['settings']['availabilityType'], + 'network': data['settings']['ipConfiguration']['privateNetwork'] } + +def _handle_filestore_instances(resource, data): + 'Handles filestore instance type resource data.' + return { + # Getting only the instance name, removing the rest + 'name': data['name'].split('/')[-1], + # Is a list but for now, only one network is supported for Filestore + 'network': data['networks'][0]['network'], + 'reservedIpRange': data['networks'][0]['reservedIpRange'], + 'ipAddresses': data['networks'][0]['ipAddresses'] + } + + +def _handle_memorystore_instances(resource, data): + 'Handles Memorystore (Redis) instance type resource data.' + return { + # Getting only the instance name, removing the rest + 'name': + data['name'].split('/')[-1], + 'locationId': + data['locationId'], + 'replicaCount': + 0 if not 'replicaCount' in data else data['replicaCount'], + 'network': + data['authorizedNetwork'], + 'reservedIpRange': + '' if not 'reservedIpRange' in data else data['reservedIpRange'], + 'host': + '' if not 'host' in data else data['host'], + } + + def _handle_subnetworks(resource, data): 'Handles subnetwork type resource data.' secondary_ranges = [{ @@ -237,8 +278,7 @@ def _self_link(s): def _url(resources): 'Returns discovery URL' discovery_root = resources['config:discovery_root'] - asset_types = '&'.join( - f'assetTypes={t}' for t in TYPES.values()) + asset_types = '&'.join(f'assetTypes={t}' for t in TYPES.values()) return CAI_URL.format(root=discovery_root, asset_types=asset_types) diff --git a/blueprints/cloud-operations/network-dashboard/src/plugins/series-psa.py b/blueprints/cloud-operations/network-dashboard/src/plugins/series-psa.py index 0ea088f9..82e06009 100644 --- a/blueprints/cloud-operations/network-dashboard/src/plugins/series-psa.py +++ b/blueprints/cloud-operations/network-dashboard/src/plugins/series-psa.py @@ -34,7 +34,28 @@ def _sql_addresses(sql_instances): if not v['ipAddresses']: continue # 1 IP for the instance + 1 IP for the ILB + 1 IP if HA - yield v['ipAddresses'][0], 2 if v['availabilityType'] != 'REGIONAL' else 3 + yield v['ipAddresses'][ + 0], 2 if v['availabilityType'] != 'REGIONAL' else 3, v['network'] + + +def _filestore_addresses(filestore_instances): + 'Returns counts of Filestore instances per PSA range.' + for v in filestore_instances.values(): + if not v['ipAddresses'] or not v['reservedIpRange']: + continue + # Subnet size (reservedIpRange) can be /29, /26 or /24 + yield v['ipAddresses'][0], ipaddress.ip_network( + v['reservedIpRange']).num_addresses, v['network'] + + +def _memorystore_addresses(memorystore_instances): + 'Returns counts of Memorystore (Redis) instances per PSA range.' + for v in memorystore_instances.values(): + if not v['reservedIpRange'] or v['reservedIpRange'] == '': + continue + # Subnet size (reservedIpRange) can be minimum /28 or /29 + yield v['host'], ipaddress.ip_network( + v['reservedIpRange']).num_addresses, v['network'] @register_timeseries @@ -46,25 +67,34 @@ def timeseries(resources): ('project', 'network', 'subnetwork'), dtype.endswith('ratio')) psa_nets = { - k: ipaddress.ip_network('{}/{}'.format(v['address'], v['prefixLength'])) - for k, v in resources['global_addresses'].items() if v['prefixLength'] + k: { + 'network_link': + v['network'], + 'network_prefix': + ipaddress.ip_network('{}/{}'.format(v['address'], + v['prefixLength'])) + } for k, v in resources['global_addresses'].items() if v['prefixLength'] } psa_counts = {} - for address, ip_count in _sql_addresses(resources.get('sql_instances', {})): + for address, ip_count, network in itertools.chain( + _sql_addresses(resources.get('sql_instances', {})), + _filestore_addresses(resources.get('filestore_instances', {})), + _memorystore_addresses(resources.get('memorystore_instances', {}))): ip_address = ipaddress.ip_address(address) for k, v in psa_nets.items(): - if ip_address in v: + if network == v['network_link'] and ip_address in v['network_prefix']: psa_counts[k] = psa_counts.get(k, 0) + ip_count break for k, v in psa_counts.items(): - max_ips = psa_nets[k].num_addresses - 4 + max_ips = psa_nets[k]['network_prefix'].num_addresses - 4 psa_range = resources['global_addresses'][k] labels = { 'network': psa_range['network'], 'project': psa_range['project_id'], 'psa_range': psa_range['name'] } + yield TimeSeries('network/psa/addresses_available', max_ips, labels) yield TimeSeries('network/psa/addresses_used', v, labels) yield TimeSeries('network/psa/addresses_used_ratio', diff --git a/blueprints/data-solutions/data-platform-foundations/README.md b/blueprints/data-solutions/data-platform-foundations/README.md index 30cdab83..b038cfe4 100644 --- a/blueprints/data-solutions/data-platform-foundations/README.md +++ b/blueprints/data-solutions/data-platform-foundations/README.md @@ -219,7 +219,7 @@ module "data-platform" { prefix = "myprefix" } -# tftest modules=39 resources=286 +# tftest modules=39 resources=287 ``` ## Customizations diff --git a/blueprints/gke/multi-cluster-mesh-gke-fleet-api/ansible/roles/install/tasks/install.yaml b/blueprints/gke/multi-cluster-mesh-gke-fleet-api/ansible/roles/install/tasks/install.yaml index b81c4962..f59f03e3 100644 --- a/blueprints/gke/multi-cluster-mesh-gke-fleet-api/ansible/roles/install/tasks/install.yaml +++ b/blueprints/gke/multi-cluster-mesh-gke-fleet-api/ansible/roles/install/tasks/install.yaml @@ -23,25 +23,6 @@ set_fact: context: "gke_{{ project_id }}_{{ region }}_{{ cluster }}" -- name: Install ASM in cluster - shell: > - gcloud container fleet mesh update \ - --control-plane automatic \ - --memberships {{ cluster }} \ - --project {{ project_id }} - -- name: Wait until MCP is provisioned - shell: > - for i in $(seq 12); do - result=$(gcloud container fleet mesh describe --project {{ project_id }} --format json \ - | jq -r '.membershipStates | to_entries[] | select(.key | endswith("{{ cluster }}")) | .value.servicemesh.controlPlaneManagement.state') - if [ "$result" = "ACTIVE" ]; then - break - fi - echo "ASM control plane is not ready yet..." - sleep 60 - done - - name: Get endpoint IP shell: > gcloud container clusters describe "{{ cluster }}" \ diff --git a/blueprints/gke/multitenant-fleet/README.md b/blueprints/gke/multitenant-fleet/README.md index 52f26ced..ce14b5a0 100644 --- a/blueprints/gke/multitenant-fleet/README.md +++ b/blueprints/gke/multitenant-fleet/README.md @@ -224,7 +224,7 @@ module "gke" { } } -# tftest modules=8 resources=35 +# tftest modules=8 resources=37 ``` diff --git a/fast/stages/00-bootstrap/README.md b/fast/stages/00-bootstrap/README.md index 6e0e1b55..889b0bce 100644 --- a/fast/stages/00-bootstrap/README.md +++ b/fast/stages/00-bootstrap/README.md @@ -264,6 +264,7 @@ terraform init terraform apply \ -var bootstrap_user=$(gcloud config list --format 'value(core.account)') ``` + > If you see an error related to project name already exists, please make sure the project name is unique or the project was not deleted recently Once the initial `apply` completes successfully, configure a remote backend using the new GCS bucket, and impersonation on the automation service account for this stage. To do this you can use the generated `providers.tf` file if you have configured output files as described above, or extract its contents from Terraform's output, then migrate state with `terraform init`: diff --git a/fast/stages/00-bootstrap/organization.tf b/fast/stages/00-bootstrap/organization.tf index 0700d564..33b87820 100644 --- a/fast/stages/00-bootstrap/organization.tf +++ b/fast/stages/00-bootstrap/organization.tf @@ -23,9 +23,10 @@ locals { "roles/browser" = [ "domain:${var.organization.domain}" ] - "roles/logging.admin" = [ - module.automation-tf-bootstrap-sa.iam_email - ] + "roles/logging.admin" = concat( + [module.automation-tf-bootstrap-sa.iam_email], + local._iam_bootstrap_user + ) "roles/owner" = local._iam_bootstrap_user "roles/resourcemanager.folderAdmin" = [ module.automation-tf-resman-sa.iam_email diff --git a/modules/apigee/README.md b/modules/apigee/README.md index fb26c1e7..02b1d13f 100644 --- a/modules/apigee/README.md +++ b/modules/apigee/README.md @@ -25,14 +25,18 @@ module "apigee" { } environments = { apis-test = { - display_name = "APIs test" - description = "APIs Test" - envgroups = ["test"] + display_name = "APIs test" + description = "APIs Test" + deployment_type = "ARCHIVE" + api_proxy_type = "PROGRAMMABLE" + envgroups = ["test"] } apis-prod = { - display_name = "APIs prod" - description = "APIs prod" - envgroups = ["prod"] + display_name = "APIs prod" + description = "APIs prod" + deployment_type = "PROXY" + api_proxy_type = "CONFIGURABLE" + envgroups = ["prod"] iam = { "roles/viewer" = ["group:devops@myorg.com"] } @@ -169,12 +173,12 @@ module "apigee" { | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [project_id](variables.tf#L75) | Project ID. | string | ✓ | | +| [project_id](variables.tf#L77) | Project ID. | string | ✓ | | | [endpoint_attachments](variables.tf#L17) | Endpoint attachments. | map(object({…})) | | null | | [envgroups](variables.tf#L26) | Environment groups (NAME => [HOSTNAMES]). | map(list(string)) | | null | -| [environments](variables.tf#L32) | Environments. | map(object({…})) | | null | -| [instances](variables.tf#L47) | Instances. | map(object({…})) | | null | -| [organization](variables.tf#L61) | Apigee organization. If set to null the organization must already exist. | object({…}) | | null | +| [environments](variables.tf#L32) | Environments. | map(object({…})) | | null | +| [instances](variables.tf#L49) | Instances. | map(object({…})) | | null | +| [organization](variables.tf#L63) | Apigee organization. If set to null the organization must already exist. | object({…}) | | null | ## Outputs diff --git a/modules/apigee/main.tf b/modules/apigee/main.tf index fe34a738..f1c71ec1 100644 --- a/modules/apigee/main.tf +++ b/modules/apigee/main.tf @@ -40,10 +40,12 @@ resource "google_apigee_envgroup" "envgroups" { } resource "google_apigee_environment" "environments" { - for_each = local.environments - name = each.key - display_name = each.value.display_name - description = each.value.description + for_each = local.environments + name = each.key + display_name = each.value.display_name + description = each.value.description + deployment_type = each.value.deployment_type + api_proxy_type = each.value.api_proxy_type dynamic "node_config" { for_each = try(each.value.node_config, null) != null ? [""] : [] content { diff --git a/modules/apigee/variables.tf b/modules/apigee/variables.tf index 266f0d34..81cf77f6 100644 --- a/modules/apigee/variables.tf +++ b/modules/apigee/variables.tf @@ -32,8 +32,10 @@ variable "envgroups" { variable "environments" { description = "Environments." type = map(object({ - display_name = optional(string) - description = optional(string, "Terraform-managed") + display_name = optional(string) + description = optional(string, "Terraform-managed") + deployment_type = optional(string) + api_proxy_type = optional(string) node_config = optional(object({ min_node_count = optional(number) max_node_count = optional(number) diff --git a/modules/gke-hub/README.md b/modules/gke-hub/README.md index 0f4c5ae8..17d7b427 100644 --- a/modules/gke-hub/README.md +++ b/modules/gke-hub/README.md @@ -295,7 +295,7 @@ module "hub" { ] } -# tftest modules=8 resources=28 +# tftest modules=8 resources=30 ``` diff --git a/modules/gke-hub/main.tf b/modules/gke-hub/main.tf index f433d322..ddd35a46 100644 --- a/modules/gke-hub/main.tf +++ b/modules/gke-hub/main.tf @@ -70,6 +70,20 @@ resource "google_gke_hub_feature" "default" { } } +resource "google_gke_hub_feature_membership" "servicemesh" { + provider = google-beta + for_each = var.features.servicemesh ? var.clusters : {} + project = var.project_id + location = "global" + feature = google_gke_hub_feature.default["servicemesh"].name + membership = google_gke_hub_membership.default[each.key].membership_id + + mesh { + management = "MANAGEMENT_AUTOMATIC" + control_plane = "AUTOMATIC" + } +} + resource "google_gke_hub_feature_membership" "default" { provider = google-beta for_each = local.cluster_cm_config diff --git a/modules/net-glb/README.md b/modules/net-glb/README.md index 3d2269a0..84ee8798 100644 --- a/modules/net-glb/README.md +++ b/modules/net-glb/README.md @@ -509,6 +509,46 @@ module "glb-0" { # tftest modules=1 resources=5 ``` +Serverless NEGs don't use the port name but it should be set to `http`. An HTTPS frontend requires the protocol to be set to `HTTPS`, and the port name field will infer this value if omitted so you need to set it explicitly: + +```hcl +module "glb-0" { + source = "./fabric/modules/net-glb" + project_id = "myprj" + name = "glb-test-0" + backend_service_configs = { + default = { + backends = [ + { backend = "neg-0" } + ] + health_checks = [] + port_name = "http" + } + } + # with a single serverless NEG the implied default health check is not needed + health_check_configs = {} + neg_configs = { + neg-0 = { + cloudrun = { + region = "europe-west8" + target_service = { + name = "hello" + } + } + } + } + protocol = "HTTPS" + ssl_certificates = { + managed_configs = { + default = { + domains = ["glb-test-0.example.org"] + } + } + } +} +# tftest modules=1 resources=6 inventory=https-sneg.yaml +``` + ### URL Map The module exposes the full URL map resource configuration, with some minor changes to the interface to decrease verbosity, and support for aliasing backend services via keys. diff --git a/modules/project/service-accounts.tf b/modules/project/service-accounts.tf index abf34cae..b25c6126 100644 --- a/modules/project/service-accounts.tf +++ b/modules/project/service-accounts.tf @@ -75,7 +75,8 @@ locals { "gkehub.googleapis.com", "pubsub.googleapis.com", "secretmanager.googleapis.com", - "sqladmin.googleapis.com" + "sqladmin.googleapis.com", + "cloudbuild.googleapis.com", ] service_accounts_cmek_service_keys = distinct(flatten([ for s in keys(var.service_encryption_key_ids) : [ diff --git a/tests/blueprints/apigee/bigquery-analytics/basic.yaml b/tests/blueprints/apigee/bigquery-analytics/basic.yaml index 2b044dcb..d89eaef5 100644 --- a/tests/blueprints/apigee/bigquery-analytics/basic.yaml +++ b/tests/blueprints/apigee/bigquery-analytics/basic.yaml @@ -14,4 +14,4 @@ counts: modules: 9 - resources: 60 + resources: 61 diff --git a/tests/blueprints/apigee/hybrid-gke/basic.yaml b/tests/blueprints/apigee/hybrid-gke/basic.yaml index 2db435da..0bab5641 100644 --- a/tests/blueprints/apigee/hybrid-gke/basic.yaml +++ b/tests/blueprints/apigee/hybrid-gke/basic.yaml @@ -13,5 +13,5 @@ # limitations under the License. counts: - modules: 9 - resources: 37 + modules: 17 + resources: 59 diff --git a/tests/blueprints/cloud_operations/asset_inventory_feed_remediation/test_plan.py b/tests/blueprints/cloud_operations/asset_inventory_feed_remediation/test_plan.py index df03e144..497af6be 100644 --- a/tests/blueprints/cloud_operations/asset_inventory_feed_remediation/test_plan.py +++ b/tests/blueprints/cloud_operations/asset_inventory_feed_remediation/test_plan.py @@ -16,4 +16,4 @@ 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) == 6 - assert len(resources) == 18 + assert len(resources) == 19 diff --git a/tests/blueprints/cloud_operations/scheduled_asset_inventory_export_bq/test_plan.py b/tests/blueprints/cloud_operations/scheduled_asset_inventory_export_bq/test_plan.py index c5394839..3bcc6344 100644 --- a/tests/blueprints/cloud_operations/scheduled_asset_inventory_export_bq/test_plan.py +++ b/tests/blueprints/cloud_operations/scheduled_asset_inventory_export_bq/test_plan.py @@ -16,4 +16,4 @@ 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) == 7 - assert len(resources) == 29 + assert len(resources) == 30 diff --git a/tests/blueprints/cloud_operations/unmanaged_instances_healthcheck/test_plan.py b/tests/blueprints/cloud_operations/unmanaged_instances_healthcheck/test_plan.py index c79be049..b1f0fba3 100644 --- a/tests/blueprints/cloud_operations/unmanaged_instances_healthcheck/test_plan.py +++ b/tests/blueprints/cloud_operations/unmanaged_instances_healthcheck/test_plan.py @@ -16,4 +16,4 @@ 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) == 10 - assert len(resources) == 31 + assert len(resources) == 32 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 17563647..93961b5e 100644 --- a/tests/blueprints/data_solutions/data_platform_foundations/test_plan.py +++ b/tests/blueprints/data_solutions/data_platform_foundations/test_plan.py @@ -22,4 +22,4 @@ 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) == 285 + assert len(resources) == 286 diff --git a/tests/blueprints/gke/binauthz/test_plan.py b/tests/blueprints/gke/binauthz/test_plan.py index cf012c06..b4437b6f 100644 --- a/tests/blueprints/gke/binauthz/test_plan.py +++ b/tests/blueprints/gke/binauthz/test_plan.py @@ -16,4 +16,4 @@ 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) == 13 - assert len(resources) == 43 + assert len(resources) == 44 diff --git a/tests/blueprints/gke/multi_cluster_mesh_gke_fleet_api/test_plan.py b/tests/blueprints/gke/multi_cluster_mesh_gke_fleet_api/test_plan.py index 270a142d..2379849d 100644 --- a/tests/blueprints/gke/multi_cluster_mesh_gke_fleet_api/test_plan.py +++ b/tests/blueprints/gke/multi_cluster_mesh_gke_fleet_api/test_plan.py @@ -16,4 +16,4 @@ 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) == 12 - assert len(resources) == 53 + assert len(resources) == 55 diff --git a/tests/blueprints/networking/private_cloud_function_from_onprem/test_plan.py b/tests/blueprints/networking/private_cloud_function_from_onprem/test_plan.py index 2b3f8d7f..81225db3 100644 --- a/tests/blueprints/networking/private_cloud_function_from_onprem/test_plan.py +++ b/tests/blueprints/networking/private_cloud_function_from_onprem/test_plan.py @@ -16,4 +16,4 @@ 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) == 10 - assert len(resources) == 38 + assert len(resources) == 39 diff --git a/tests/blueprints/serverless/api_gateway/test_plan.py b/tests/blueprints/serverless/api_gateway/test_plan.py index 6cf48a87..9d658398 100644 --- a/tests/blueprints/serverless/api_gateway/test_plan.py +++ b/tests/blueprints/serverless/api_gateway/test_plan.py @@ -16,4 +16,4 @@ 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) == 7 - assert len(resources) == 31 + assert len(resources) == 32 diff --git a/tests/fast/stages/s00_bootstrap/simple.yaml b/tests/fast/stages/s00_bootstrap/simple.yaml index 703b84b4..ed0d7738 100644 --- a/tests/fast/stages/s00_bootstrap/simple.yaml +++ b/tests/fast/stages/s00_bootstrap/simple.yaml @@ -24,7 +24,7 @@ counts: google_project_iam_binding: 9 google_project_iam_member: 1 google_project_service: 29 - google_project_service_identity: 2 + google_project_service_identity: 3 google_service_account: 3 google_service_account_iam_binding: 3 google_storage_bucket: 4 diff --git a/tests/modules/apigee/fixture/test.env_only_with_api_proxy_type.tfvars b/tests/modules/apigee/fixture/test.env_only_with_api_proxy_type.tfvars new file mode 100644 index 00000000..2a9164bf --- /dev/null +++ b/tests/modules/apigee/fixture/test.env_only_with_api_proxy_type.tfvars @@ -0,0 +1,13 @@ +project_id = "my-project" +environments = { + apis-test = { + display_name = "APIs test" + description = "APIs Test" + api_proxy_type = "PROGRAMMABLE" + envgroups = ["test"] + node_config = { + min_node_count = 2 + max_node_count = 5 + } + } +} diff --git a/tests/modules/apigee/fixture/test.env_only_with_deployment_type.tfvars b/tests/modules/apigee/fixture/test.env_only_with_deployment_type.tfvars new file mode 100644 index 00000000..48ef24e6 --- /dev/null +++ b/tests/modules/apigee/fixture/test.env_only_with_deployment_type.tfvars @@ -0,0 +1,13 @@ +project_id = "my-project" +environments = { + apis-test = { + display_name = "APIs test" + description = "APIs Test" + deployment_type = "ARCHIVE" + envgroups = ["test"] + node_config = { + min_node_count = 2 + max_node_count = 5 + } + } +} diff --git a/tests/modules/apigee/fixture/variables.tf b/tests/modules/apigee/fixture/variables.tf index 266f0d34..81cf77f6 100644 --- a/tests/modules/apigee/fixture/variables.tf +++ b/tests/modules/apigee/fixture/variables.tf @@ -32,8 +32,10 @@ variable "envgroups" { variable "environments" { description = "Environments." type = map(object({ - display_name = optional(string) - description = optional(string, "Terraform-managed") + display_name = optional(string) + description = optional(string, "Terraform-managed") + deployment_type = optional(string) + api_proxy_type = optional(string) node_config = optional(object({ min_node_count = optional(number) max_node_count = optional(number) diff --git a/tests/modules/apigee/test_plan.py b/tests/modules/apigee/test_plan.py index e693ddbb..d16ef296 100644 --- a/tests/modules/apigee/test_plan.py +++ b/tests/modules/apigee/test_plan.py @@ -54,6 +54,18 @@ def test_env_only(plan_runner): 'google_apigee_envgroup_attachment.envgroup_attachments': 1, } +def test_env_only_with_deployment_type(plan_runner): + "Test that creates an environment in an existing environment group, with deployment_type set." + _, resources = plan_runner(tf_var_file='test.env_only_with_deployment_type.tfvars') + assert [r['values'].get('deployment_type') for r in resources + ] == [None, 'ARCHIVE'] + +def test_env_only_with_api_proxy_type(plan_runner): + "Test that creates an environment in an existing environment group, with api_proxy_type set." + _, resources = plan_runner(tf_var_file='test.env_only_with_api_proxy_type.tfvars') + assert [r['values'].get('api_proxy_type') for r in resources + ] == [None, 'PROGRAMMABLE'] + def test_instance_only(plan_runner): "Test that creates only an instance." _, resources = plan_runner(tf_var_file='test.instance_only.tfvars') diff --git a/tests/modules/gke_hub/fixture/variables.tf b/tests/modules/gke_hub/fixture/variables.tf index 5c5c106f..1d76d4f9 100644 --- a/tests/modules/gke_hub/fixture/variables.tf +++ b/tests/modules/gke_hub/fixture/variables.tf @@ -31,7 +31,7 @@ variable "features" { configmanagement = true identityservice = false multiclusteringress = null - servicemesh = false + servicemesh = true multiclusterservicediscovery = false } } diff --git a/tests/modules/gke_hub/test_plan.py b/tests/modules/gke_hub/test_plan.py index 35521813..51258c83 100644 --- a/tests/modules/gke_hub/test_plan.py +++ b/tests/modules/gke_hub/test_plan.py @@ -23,11 +23,14 @@ def resources(plan_runner): def test_resource_count(resources): "Test number of resources created." - assert len(resources) == 5 + assert len(resources) == 8 assert sorted(r['address'] for r in resources) == [ 'module.hub.google_gke_hub_feature.default["configmanagement"]', + 'module.hub.google_gke_hub_feature.default["servicemesh"]', 'module.hub.google_gke_hub_feature_membership.default["cluster-1"]', 'module.hub.google_gke_hub_feature_membership.default["cluster-2"]', + 'module.hub.google_gke_hub_feature_membership.servicemesh["cluster-1"]', + 'module.hub.google_gke_hub_feature_membership.servicemesh["cluster-2"]', 'module.hub.google_gke_hub_membership.default["cluster-1"]', 'module.hub.google_gke_hub_membership.default["cluster-2"]' ] diff --git a/tests/modules/net_glb/examples/https-sneg.yaml b/tests/modules/net_glb/examples/https-sneg.yaml new file mode 100644 index 00000000..fa0823cb --- /dev/null +++ b/tests/modules/net_glb/examples/https-sneg.yaml @@ -0,0 +1,35 @@ +# 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.glb-0.google_compute_backend_service.default["default"]: + port_name: http + protocol: HTTPS + module.glb-0.google_compute_global_forwarding_rule.default: + load_balancing_scheme: EXTERNAL + port_range: '443' + module.glb-0.google_compute_region_network_endpoint_group.serverless["neg-0"]: + cloud_run: + - service: hello + tag: null + url_mask: null + +counts: + google_compute_backend_service: 1 + google_compute_global_forwarding_rule: 1 + google_compute_managed_ssl_certificate: 1 + google_compute_region_network_endpoint_group: 1 + google_compute_target_https_proxy: 1 + google_compute_url_map: 1 +