feat(gcp): add check to ensure Managed Instance Groups span multiple zones (#9566)

Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
This commit is contained in:
lydiavilchez
2025-12-22 15:12:08 +01:00
committed by GitHub
parent 641dc78c3a
commit 43875b6ae7
14 changed files with 710 additions and 1 deletions

View File

@@ -93,6 +93,11 @@ The following list includes all the Azure checks with configurable variables tha
## GCP
### Configurable Checks
The following list includes all the GCP checks with configurable variables that can be changed in the configuration yaml file:
| Check Name | Value | Type |
|---------------------------------------------------------------|--------------------------------------------------|-----------------|
| `compute_instance_group_multiple_zones` | `mig_min_zones` | Integer |
## Kubernetes
@@ -548,6 +553,9 @@ gcp:
# GCP Compute Configuration
# gcp.compute_public_address_shodan
shodan_api_key: null
# gcp.compute_instance_group_multiple_zones
# Minimum number of zones a MIG should span for high availability
mig_min_zones: 2
# Kubernetes Configuration
kubernetes:

View File

@@ -6,6 +6,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
### Added
- Add Prowler ThreatScore for the Alibaba Cloud provider [(#9511)](https://github.com/prowler-cloud/prowler/pull/9511)
- `compute_instance_group_multiple_zones` check for GCP provider [(#9566)](https://github.com/prowler-cloud/prowler/pull/9566)
### Changed
- Update AWS Step Functions service metadata to new format [(#9432)](https://github.com/prowler-cloud/prowler/pull/9432)

View File

@@ -507,6 +507,9 @@ gcp:
# GCP Compute Configuration
# gcp.compute_public_address_shodan
shodan_api_key: null
# gcp.compute_instance_group_multiple_zones
# Minimum number of zones a MIG should span for high availability
mig_min_zones: 2
# GCP Service Account and user-managed keys unused configuration
# gcp.iam_service_account_unused
# gcp.iam_sa_user_managed_key_unused

View File

@@ -0,0 +1,36 @@
{
"Provider": "gcp",
"CheckID": "compute_instance_group_multiple_zones",
"CheckTitle": "Ensure Managed Instance Groups span multiple zones for high availability",
"CheckType": [],
"ServiceName": "compute",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "low",
"ResourceType": "compute.googleapis.com/InstanceGroupManager",
"Description": "Managed Instance Groups (MIGs) should be configured for multi-zone deployments to ensure high availability and fault tolerance. A multi-zone MIG distributes instances across multiple zones within a region, protecting applications from zonal failures.",
"Risk": "Running a MIG in a single zone creates a single point of failure. If that zone experiences an outage, all instances in the group become unavailable, resulting in application downtime during zonal failures, no automatic failover to healthy zones, and reduced resilience against infrastructure issues.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://cloud.google.com/compute/docs/instance-groups/regional-migs",
"https://cloud.google.com/compute/docs/instance-groups/distributing-instances-with-regional-instance-groups"
],
"Remediation": {
"Code": {
"CLI": "gcloud compute instance-groups managed create INSTANCE_GROUP_NAME --region=REGION --template=INSTANCE_TEMPLATE --size=TARGET_SIZE --zones=ZONE1,ZONE2,ZONE3",
"NativeIaC": "",
"Other": "1. Navigate to Compute Engine > Instance groups\n2. Click 'Create instance group'\n3. Select 'New managed instance group (stateless)'\n4. For 'Location', select 'Multiple zones'\n5. Choose the target region and zones\n6. Configure the instance template and target size\n7. Click 'Create'",
"Terraform": "```hcl\n# Create a regional MIG that spans multiple zones\nresource \"google_compute_region_instance_group_manager\" \"example\" {\n name = \"example-mig\"\n region = \"us-central1\"\n base_instance_name = \"example\"\n target_size = 3\n\n version {\n instance_template = google_compute_instance_template.example.id\n }\n\n # Distribute instances across multiple zones\n distribution_policy_zones = [\"us-central1-a\", \"us-central1-b\", \"us-central1-c\"]\n}\n```"
},
"Recommendation": {
"Text": "Use regional managed instance groups instead of zonal MIGs to distribute instances across multiple zones. This provides automatic failover and load distribution, ensuring high availability for production workloads.",
"Url": "https://hub.prowler.com/check/compute_instance_group_multiple_zones"
}
},
"Categories": [
"resilience"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "This check uses a configurable minimum zone count (default: 2). Configure via 'mig_min_zones' in config.yaml."
}

View File

@@ -0,0 +1,45 @@
from prowler.lib.check.models import Check, Check_Report_GCP
from prowler.providers.gcp.services.compute.compute_client import compute_client
class compute_instance_group_multiple_zones(Check):
"""
Ensure Managed Instance Groups span multiple zones for high availability.
This check verifies whether GCP Managed Instance Groups (MIGs) are distributed
across multiple zones to ensure high availability and fault tolerance.
- PASS: The MIG spans the minimum required zones (configurable via mig_min_zones).
- FAIL: The MIG does not meet the minimum zone requirement.
"""
def execute(self) -> list[Check_Report_GCP]:
findings = []
min_zones = compute_client.audit_config.get("mig_min_zones", 2)
for instance_group in compute_client.instance_groups:
report = Check_Report_GCP(
metadata=self.metadata(),
resource=instance_group,
location=instance_group.region,
)
zone_count = len(instance_group.zones)
zones_str = ", ".join(instance_group.zones)
report.status = "PASS"
if instance_group.is_regional:
report.status_extended = f"Managed Instance Group {instance_group.name} is a regional MIG spanning {zone_count} zones ({zones_str})."
else:
report.status_extended = f"Managed Instance Group {instance_group.name} spans {zone_count} zones ({zones_str})."
if zone_count < min_zones:
report.status = "FAIL"
if instance_group.is_regional:
report.status_extended = f"Managed Instance Group {instance_group.name} is a regional MIG but only spans {zone_count} zone(s) ({zones_str}), minimum required is {min_zones}."
else:
report.status_extended = f"Managed Instance Group {instance_group.name} is a zonal MIG running only in {zones_str}, consider converting to a regional MIG for high availability."
findings.append(report)
return findings

View File

@@ -1,3 +1,5 @@
from typing import Optional
from pydantic.v1 import BaseModel
from prowler.lib.logger import logger
@@ -18,6 +20,7 @@ class Compute(GCPService):
self.firewalls = []
self.compute_projects = []
self.load_balancers = []
self.instance_groups = []
self._get_regions()
self._get_projects()
self._get_url_maps()
@@ -28,6 +31,8 @@ class Compute(GCPService):
self.__threading_call__(self._get_subnetworks, self.regions)
self._get_firewalls()
self.__threading_call__(self._get_addresses, self.regions)
self.__threading_call__(self._get_regional_instance_groups, self.regions)
self.__threading_call__(self._get_zonal_instance_groups, self.zones)
def _get_regions(self):
for project_id in self.project_ids:
@@ -362,6 +367,87 @@ class Compute(GCPService):
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
def _get_regional_instance_groups(self, region: str) -> None:
"""Fetch regional managed instance groups for all projects."""
for project_id in self.project_ids:
try:
request = self.client.regionInstanceGroupManagers().list(
project=project_id, region=region
)
while request is not None:
response = request.execute(
http=self.__get_AuthorizedHttp_client__(),
num_retries=DEFAULT_RETRY_ATTEMPTS,
)
for mig in response.get("items", []):
zones = [
zone_info["zone"].split("/")[-1]
for zone_info in mig.get("distributionPolicy", {}).get(
"zones", []
)
if zone_info.get("zone")
]
self.instance_groups.append(
ManagedInstanceGroup(
name=mig.get("name", ""),
id=mig.get("id", ""),
region=region,
zone=None,
zones=zones,
is_regional=True,
target_size=mig.get("targetSize", 0),
project_id=project_id,
)
)
request = self.client.regionInstanceGroupManagers().list_next(
previous_request=request, previous_response=response
)
except Exception as error:
logger.error(
f"{region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
def _get_zonal_instance_groups(self, zone: str) -> None:
"""Fetch zonal managed instance groups for all projects."""
for project_id in self.project_ids:
try:
request = self.client.instanceGroupManagers().list(
project=project_id, zone=zone
)
while request is not None:
response = request.execute(
http=self.__get_AuthorizedHttp_client__(),
num_retries=DEFAULT_RETRY_ATTEMPTS,
)
for mig in response.get("items", []):
mig_zone = mig.get("zone", zone).split("/")[-1]
mig_region = mig_zone.rsplit("-", 1)[0]
self.instance_groups.append(
ManagedInstanceGroup(
name=mig.get("name", ""),
id=mig.get("id", ""),
region=mig_region,
zone=mig_zone,
zones=[mig_zone],
is_regional=False,
target_size=mig.get("targetSize", 0),
project_id=project_id,
)
)
request = self.client.instanceGroupManagers().list_next(
previous_request=request, previous_response=response
)
except Exception as error:
logger.error(
f"{zone} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
class Instance(BaseModel):
name: str
@@ -428,3 +514,14 @@ class LoadBalancer(BaseModel):
service: str
logging: bool = False
project_id: str
class ManagedInstanceGroup(BaseModel):
name: str
id: str
region: str
zone: Optional[str]
zones: list
is_regional: bool
target_size: int
project_id: str

View File

@@ -329,7 +329,11 @@ config_azure = {
"defender_attack_path_minimal_risk_level": "High",
}
config_gcp = {"shodan_api_key": None, "max_unused_account_days": 30}
config_gcp = {
"shodan_api_key": None,
"mig_min_zones": 2,
"max_unused_account_days": 30,
}
config_kubernetes = {
"audit_log_maxbackup": 10,

View File

@@ -410,6 +410,9 @@ gcp:
# GCP Compute Configuration
# gcp.compute_public_address_shodan
shodan_api_key: null
# gcp.compute_instance_group_multiple_zones
# Minimum number of zones a MIG should span for high availability
mig_min_zones: 2
max_unused_account_days: 30
# Kubernetes Configuration

View File

@@ -57,6 +57,7 @@ def mock_api_client(GCPService, service, api_version, _):
mock_api_sink_calls(client)
mock_api_services_calls(client)
mock_api_access_policies_calls(client)
mock_api_instance_group_managers_calls(client)
return client
@@ -1184,3 +1185,59 @@ def mock_api_access_policies_calls(client: MagicMock):
client.accessPolicies().servicePerimeters().list = mock_list_service_perimeters
client.accessPolicies().servicePerimeters().list_next.return_value = None
def mock_api_instance_group_managers_calls(client: MagicMock):
"""Mock API calls for Managed Instance Groups (both regional and zonal)."""
regional_mig1_id = str(uuid4())
regional_mig2_id = str(uuid4())
zonal_mig1_id = str(uuid4())
# Mock regional instance group managers
client.regionInstanceGroupManagers().list().execute.return_value = {
"items": [
{
"name": "regional-mig-1",
"id": regional_mig1_id,
"targetSize": 3,
"distributionPolicy": {
"zones": [
{
"zone": "https://www.googleapis.com/compute/v1/projects/test-project/zones/europe-west1-b"
},
{
"zone": "https://www.googleapis.com/compute/v1/projects/test-project/zones/europe-west1-c"
},
{
"zone": "https://www.googleapis.com/compute/v1/projects/test-project/zones/europe-west1-d"
},
]
},
},
{
"name": "regional-mig-single-zone",
"id": regional_mig2_id,
"targetSize": 1,
"distributionPolicy": {
"zones": [
{
"zone": "https://www.googleapis.com/compute/v1/projects/test-project/zones/europe-west1-b"
}
]
},
},
]
}
client.regionInstanceGroupManagers().list_next.return_value = None
# Mock zonal instance group managers
client.instanceGroupManagers().list().execute.return_value = {
"items": [
{
"name": "zonal-mig-1",
"id": zonal_mig1_id,
"targetSize": 2,
},
]
}
client.instanceGroupManagers().list_next.return_value = None

View File

@@ -91,6 +91,7 @@ class TestGCPProvider:
"shodan_api_key": None,
"max_unused_account_days": 180,
"storage_min_retention_days": 90,
"mig_min_zones": 2,
}
@freeze_time(datetime.today())

View File

@@ -0,0 +1,389 @@
from re import search
from unittest import mock
from prowler.providers.gcp.models import GCPProject
from tests.providers.gcp.gcp_fixtures import GCP_PROJECT_ID, set_mocked_gcp_provider
class Test_compute_instance_group_multiple_zones:
"""Tests for the compute_instance_group_multiple_zones check."""
def test_no_instance_groups(self):
"""Test when there are no managed instance groups."""
compute_client = mock.MagicMock()
compute_client.project_ids = [GCP_PROJECT_ID]
compute_client.instance_groups = []
compute_client.audit_config = {"mig_min_zones": 2}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_gcp_provider(),
),
mock.patch(
"prowler.providers.gcp.services.compute.compute_instance_group_multiple_zones.compute_instance_group_multiple_zones.compute_client",
new=compute_client,
),
):
from prowler.providers.gcp.services.compute.compute_instance_group_multiple_zones.compute_instance_group_multiple_zones import (
compute_instance_group_multiple_zones,
)
check = compute_instance_group_multiple_zones()
result = check.execute()
assert len(result) == 0
def test_regional_mig_multiple_zones_pass(self):
"""Test a regional MIG spanning multiple zones - should PASS."""
from prowler.providers.gcp.services.compute.compute_service import (
ManagedInstanceGroup,
)
mig = ManagedInstanceGroup(
name="regional-mig-1",
id="123456789",
region="us-central1",
zone=None,
zones=["us-central1-a", "us-central1-b", "us-central1-c"],
is_regional=True,
target_size=3,
project_id=GCP_PROJECT_ID,
)
compute_client = mock.MagicMock()
compute_client.project_ids = [GCP_PROJECT_ID]
compute_client.instance_groups = [mig]
compute_client.audit_config = {"mig_min_zones": 2}
compute_client.projects = {
GCP_PROJECT_ID: GCPProject(
id=GCP_PROJECT_ID,
number="123456789012",
name="test-project",
labels={},
lifecycle_state="ACTIVE",
)
}
compute_client.region = "us-central1"
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_gcp_provider(),
),
mock.patch(
"prowler.providers.gcp.services.compute.compute_instance_group_multiple_zones.compute_instance_group_multiple_zones.compute_client",
new=compute_client,
),
):
from prowler.providers.gcp.services.compute.compute_instance_group_multiple_zones.compute_instance_group_multiple_zones import (
compute_instance_group_multiple_zones,
)
check = compute_instance_group_multiple_zones()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert search(
f"Managed Instance Group {mig.name} is a regional MIG spanning 3 zones",
result[0].status_extended,
)
assert result[0].resource_id == mig.id
assert result[0].resource_name == mig.name
assert result[0].location == mig.region
assert result[0].project_id == GCP_PROJECT_ID
def test_zonal_mig_single_zone_fail(self):
"""Test a zonal MIG in a single zone - should FAIL."""
from prowler.providers.gcp.services.compute.compute_service import (
ManagedInstanceGroup,
)
mig = ManagedInstanceGroup(
name="zonal-mig-1",
id="987654321",
region="us-central1",
zone="us-central1-a",
zones=["us-central1-a"],
is_regional=False,
target_size=2,
project_id=GCP_PROJECT_ID,
)
compute_client = mock.MagicMock()
compute_client.project_ids = [GCP_PROJECT_ID]
compute_client.instance_groups = [mig]
compute_client.audit_config = {"mig_min_zones": 2}
compute_client.projects = {
GCP_PROJECT_ID: GCPProject(
id=GCP_PROJECT_ID,
number="123456789012",
name="test-project",
labels={},
lifecycle_state="ACTIVE",
)
}
compute_client.region = "us-central1"
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_gcp_provider(),
),
mock.patch(
"prowler.providers.gcp.services.compute.compute_instance_group_multiple_zones.compute_instance_group_multiple_zones.compute_client",
new=compute_client,
),
):
from prowler.providers.gcp.services.compute.compute_instance_group_multiple_zones.compute_instance_group_multiple_zones import (
compute_instance_group_multiple_zones,
)
check = compute_instance_group_multiple_zones()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert search(
f"Managed Instance Group {mig.name} is a zonal MIG running only in",
result[0].status_extended,
)
assert result[0].resource_id == mig.id
assert result[0].resource_name == mig.name
assert result[0].location == mig.region
assert result[0].project_id == GCP_PROJECT_ID
def test_regional_mig_single_zone_fail(self):
"""Test a regional MIG with only one zone configured - should FAIL."""
from prowler.providers.gcp.services.compute.compute_service import (
ManagedInstanceGroup,
)
mig = ManagedInstanceGroup(
name="regional-mig-single-zone",
id="111222333",
region="europe-west1",
zone=None,
zones=["europe-west1-b"],
is_regional=True,
target_size=1,
project_id=GCP_PROJECT_ID,
)
compute_client = mock.MagicMock()
compute_client.project_ids = [GCP_PROJECT_ID]
compute_client.instance_groups = [mig]
compute_client.audit_config = {"mig_min_zones": 2}
compute_client.projects = {
GCP_PROJECT_ID: GCPProject(
id=GCP_PROJECT_ID,
number="123456789012",
name="test-project",
labels={},
lifecycle_state="ACTIVE",
)
}
compute_client.region = "europe-west1"
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_gcp_provider(),
),
mock.patch(
"prowler.providers.gcp.services.compute.compute_instance_group_multiple_zones.compute_instance_group_multiple_zones.compute_client",
new=compute_client,
),
):
from prowler.providers.gcp.services.compute.compute_instance_group_multiple_zones.compute_instance_group_multiple_zones import (
compute_instance_group_multiple_zones,
)
check = compute_instance_group_multiple_zones()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert search(
f"Managed Instance Group {mig.name} is a regional MIG but only spans 1 zone",
result[0].status_extended,
)
assert result[0].resource_id == mig.id
assert result[0].resource_name == mig.name
assert result[0].location == mig.region
assert result[0].project_id == GCP_PROJECT_ID
def test_multiple_migs_mixed_results(self):
"""Test multiple MIGs with mixed compliance results."""
from prowler.providers.gcp.services.compute.compute_service import (
ManagedInstanceGroup,
)
mig_regional_pass = ManagedInstanceGroup(
name="regional-mig-good",
id="111",
region="us-central1",
zone=None,
zones=["us-central1-a", "us-central1-b"],
is_regional=True,
target_size=2,
project_id=GCP_PROJECT_ID,
)
mig_zonal_fail = ManagedInstanceGroup(
name="zonal-mig-bad",
id="222",
region="us-central1",
zone="us-central1-a",
zones=["us-central1-a"],
is_regional=False,
target_size=1,
project_id=GCP_PROJECT_ID,
)
compute_client = mock.MagicMock()
compute_client.project_ids = [GCP_PROJECT_ID]
compute_client.instance_groups = [mig_regional_pass, mig_zonal_fail]
compute_client.audit_config = {"mig_min_zones": 2}
compute_client.projects = {
GCP_PROJECT_ID: GCPProject(
id=GCP_PROJECT_ID,
number="123456789012",
name="test-project",
labels={},
lifecycle_state="ACTIVE",
)
}
compute_client.region = "us-central1"
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_gcp_provider(),
),
mock.patch(
"prowler.providers.gcp.services.compute.compute_instance_group_multiple_zones.compute_instance_group_multiple_zones.compute_client",
new=compute_client,
),
):
from prowler.providers.gcp.services.compute.compute_instance_group_multiple_zones.compute_instance_group_multiple_zones import (
compute_instance_group_multiple_zones,
)
check = compute_instance_group_multiple_zones()
result = check.execute()
assert len(result) == 2
# First MIG (regional with 2 zones) should pass
assert result[0].status == "PASS"
assert result[0].resource_id == mig_regional_pass.id
# Second MIG (zonal with 1 zone) should fail
assert result[1].status == "FAIL"
assert result[1].resource_id == mig_zonal_fail.id
def test_custom_min_zones_config(self):
"""Test that the configurable min zones parameter is respected."""
from prowler.providers.gcp.services.compute.compute_service import (
ManagedInstanceGroup,
)
# MIG with 2 zones - should fail if min_zones is 3
mig = ManagedInstanceGroup(
name="regional-mig-2zones",
id="333",
region="us-central1",
zone=None,
zones=["us-central1-a", "us-central1-b"],
is_regional=True,
target_size=2,
project_id=GCP_PROJECT_ID,
)
compute_client = mock.MagicMock()
compute_client.project_ids = [GCP_PROJECT_ID]
compute_client.instance_groups = [mig]
compute_client.audit_config = {"mig_min_zones": 3} # Require 3 zones
compute_client.projects = {
GCP_PROJECT_ID: GCPProject(
id=GCP_PROJECT_ID,
number="123456789012",
name="test-project",
labels={},
lifecycle_state="ACTIVE",
)
}
compute_client.region = "us-central1"
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_gcp_provider(),
),
mock.patch(
"prowler.providers.gcp.services.compute.compute_instance_group_multiple_zones.compute_instance_group_multiple_zones.compute_client",
new=compute_client,
),
):
from prowler.providers.gcp.services.compute.compute_instance_group_multiple_zones.compute_instance_group_multiple_zones import (
compute_instance_group_multiple_zones,
)
check = compute_instance_group_multiple_zones()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert search("minimum required is 3", result[0].status_extended)
def test_default_min_zones_when_not_configured(self):
"""Test that default min_zones (2) is used when not configured."""
from prowler.providers.gcp.services.compute.compute_service import (
ManagedInstanceGroup,
)
mig = ManagedInstanceGroup(
name="regional-mig-default",
id="444",
region="us-central1",
zone=None,
zones=["us-central1-a", "us-central1-b"],
is_regional=True,
target_size=2,
project_id=GCP_PROJECT_ID,
)
compute_client = mock.MagicMock()
compute_client.project_ids = [GCP_PROJECT_ID]
compute_client.instance_groups = [mig]
compute_client.audit_config = {} # No mig_min_zones configured
compute_client.projects = {
GCP_PROJECT_ID: GCPProject(
id=GCP_PROJECT_ID,
number="123456789012",
name="test-project",
labels={},
lifecycle_state="ACTIVE",
)
}
compute_client.region = "us-central1"
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_gcp_provider(),
),
mock.patch(
"prowler.providers.gcp.services.compute.compute_instance_group_multiple_zones.compute_instance_group_multiple_zones.compute_client",
new=compute_client,
),
):
from prowler.providers.gcp.services.compute.compute_instance_group_multiple_zones.compute_instance_group_multiple_zones import (
compute_instance_group_multiple_zones,
)
check = compute_instance_group_multiple_zones()
result = check.execute()
assert len(result) == 1
# 2 zones >= default 2, so should PASS
assert result[0].status == "PASS"

View File

@@ -186,3 +186,68 @@ class TestComputeService:
assert compute_client.load_balancers[3].service == "regional_service2"
assert compute_client.load_balancers[3].project_id == GCP_PROJECT_ID
assert not compute_client.load_balancers[3].logging
# Test Managed Instance Groups
# We expect 3 MIGs: 2 regional (from region europe-west1-b) and 1 zonal (from zone1)
assert len(compute_client.instance_groups) == 3
# First regional MIG - multiple zones
regional_mig_1 = next(
(
mig
for mig in compute_client.instance_groups
if mig.name == "regional-mig-1"
),
None,
)
assert regional_mig_1 is not None
assert regional_mig_1.id.__class__.__name__ == "str"
assert regional_mig_1.region == "europe-west1-b"
assert regional_mig_1.zone is None # Regional MIGs don't have a single zone
assert len(regional_mig_1.zones) == 3
assert "europe-west1-b" in regional_mig_1.zones
assert "europe-west1-c" in regional_mig_1.zones
assert "europe-west1-d" in regional_mig_1.zones
assert regional_mig_1.is_regional
assert regional_mig_1.target_size == 3
assert regional_mig_1.project_id == GCP_PROJECT_ID
# Second regional MIG - single zone
regional_mig_2 = next(
(
mig
for mig in compute_client.instance_groups
if mig.name == "regional-mig-single-zone"
),
None,
)
assert regional_mig_2 is not None
assert regional_mig_2.id.__class__.__name__ == "str"
assert regional_mig_2.region == "europe-west1-b"
assert regional_mig_2.zone is None
assert len(regional_mig_2.zones) == 1
assert "europe-west1-b" in regional_mig_2.zones
assert regional_mig_2.is_regional
assert regional_mig_2.target_size == 1
assert regional_mig_2.project_id == GCP_PROJECT_ID
# Zonal MIG
zonal_mig = next(
(
mig
for mig in compute_client.instance_groups
if mig.name == "zonal-mig-1"
),
None,
)
assert zonal_mig is not None
assert zonal_mig.id.__class__.__name__ == "str"
assert (
zonal_mig.region == "zone1"
) # zone1 has no hyphen so region is "zone1"
assert zonal_mig.zone == "zone1"
assert len(zonal_mig.zones) == 1
assert "zone1" in zonal_mig.zones
assert not zonal_mig.is_regional
assert zonal_mig.target_size == 2
assert zonal_mig.project_id == GCP_PROJECT_ID