From 4f4d80eb610b95d3f6c1dd508bf6f5b19b76ff8e Mon Sep 17 00:00:00 2001 From: 1101-1 <70093559+1101-1@users.noreply.github.com> Date: Fri, 19 Jul 2024 17:02:51 +0500 Subject: [PATCH] [azure][feat] Add support for sql resource collection (#2144) Co-authored-by: Matthias Veit --- plugins/azure/fix_plugin_azure/collector.py | 2 + .../azure/fix_plugin_azure/resource/base.py | 33 +- .../fix_plugin_azure/resource/network.py | 2 +- .../azure/fix_plugin_azure/resource/sql.py | 1332 +++++++++++++++++ plugins/azure/test/collector_test.py | 4 +- plugins/azure/test/files/sql/advisors.json | 169 +++ plugins/azure/test/files/sql/databases.json | 75 + .../azure/test/files/sql/elasticPools.json | 69 + .../azure/test/files/sql/failoverGroups.json | 54 + .../azure/test/files/sql/firewallRules.json | 40 + .../test/files/sql/geoBackupPolicies.json | 15 + .../files/sql/instanceFailoverGroups.json | 60 + .../azure/test/files/sql/instancePools.json | 42 + plugins/azure/test/files/sql/jobAgents.json | 30 + .../test/files/sql/managedInstances.json | 64 + .../files/sql/privateEndpointConnections.json | 36 + .../test/files/sql/serverTrustGroups.json | 42 + plugins/azure/test/files/sql/servers.json | 36 + .../azure/test/files/sql/virtualClusters.json | 34 + .../test/files/sql/virtualNetworkRules.json | 24 + .../azure/test/files/sql/workloadGroups.json | 43 + plugins/azure/test/sql_test.py | 28 + 22 files changed, 2215 insertions(+), 19 deletions(-) create mode 100644 plugins/azure/fix_plugin_azure/resource/sql.py create mode 100644 plugins/azure/test/files/sql/advisors.json create mode 100644 plugins/azure/test/files/sql/databases.json create mode 100644 plugins/azure/test/files/sql/elasticPools.json create mode 100644 plugins/azure/test/files/sql/failoverGroups.json create mode 100644 plugins/azure/test/files/sql/firewallRules.json create mode 100644 plugins/azure/test/files/sql/geoBackupPolicies.json create mode 100644 plugins/azure/test/files/sql/instanceFailoverGroups.json create mode 100644 plugins/azure/test/files/sql/instancePools.json create mode 100644 plugins/azure/test/files/sql/jobAgents.json create mode 100644 plugins/azure/test/files/sql/managedInstances.json create mode 100644 plugins/azure/test/files/sql/privateEndpointConnections.json create mode 100644 plugins/azure/test/files/sql/serverTrustGroups.json create mode 100644 plugins/azure/test/files/sql/servers.json create mode 100644 plugins/azure/test/files/sql/virtualClusters.json create mode 100644 plugins/azure/test/files/sql/virtualNetworkRules.json create mode 100644 plugins/azure/test/files/sql/workloadGroups.json create mode 100644 plugins/azure/test/sql_test.py diff --git a/plugins/azure/fix_plugin_azure/collector.py b/plugins/azure/fix_plugin_azure/collector.py index b4f740c010..23cdf04ef0 100644 --- a/plugins/azure/fix_plugin_azure/collector.py +++ b/plugins/azure/fix_plugin_azure/collector.py @@ -34,6 +34,7 @@ AzureNetworkUsage, resources as network_resources, ) +from fix_plugin_azure.resource.sql import resources as sql_resources from fix_plugin_azure.resource.storage import AzureStorageAccountUsage, AzureStorageSku, resources as storage_resources from fixlib.baseresources import Cloud, GraphRoot, BaseAccount, BaseRegion from fixlib.core.actions import CoreFeedback, ErrorAccumulator @@ -59,6 +60,7 @@ def resource_with_params(clazz: Type[MicrosoftResource], param: str) -> bool: + aks_resources + security_resources + storage_resources + + sql_resources ) all_resources = subscription_resources + graph_resources # defines all resource kinds. used in model check diff --git a/plugins/azure/fix_plugin_azure/resource/base.py b/plugins/azure/fix_plugin_azure/resource/base.py index fc5f482663..ff63213a34 100644 --- a/plugins/azure/fix_plugin_azure/resource/base.py +++ b/plugins/azure/fix_plugin_azure/resource/base.py @@ -62,36 +62,35 @@ class MicrosoftResource(BaseResource): etag: Optional[str] = field(default=None, metadata={'description': 'A unique read-only string that changes whenever the resource is updated.'}) # fmt: skip provisioning_state: Optional[str] = field(default=None, metadata={'description': 'The current provisioning state.'}) # fmt: skip + @property def resource_subscription_id(self) -> Optional[str]: - return self.extract_part("subscriptionId") + return self.extract_part("subscriptions") + + @property + def resource_group_name(self) -> Optional[str]: + return self.extract_part("resourceGroups") def extract_part(self, part: str) -> Optional[str]: """ Extracts a specific part from a resource ID. - The function takes a resource ID and a specified part to extract, such as 'subscriptionId'. + The function takes a resource ID and a specified part to extract, such as 'subscriptions'. The resource ID is expected to follow the Azure Resource Manager path format. Example: For the resource ID "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/...", - calling extract_part("subscriptionId") would return the value within the curly braces, - representing the subscription ID. + calling extract_part("subscriptions") would return the value representing the subscription ID. Parameters: - part (str): The part to extract from the resource ID. Returns: - str: The extracted part of the resource ID. + Optional[str]: The extracted part of the resource ID, or None if not found. """ id_parts = self.id.split("/") - - if part == "subscriptionId": - if "subscriptions" not in id_parts: - return None - if index := id_parts.index("subscriptions"): - return id_parts[index + 1] - return None - else: + try: + return id_parts[id_parts.index(part) + 1] + except ValueError: return None def delete(self, graph: Graph) -> bool: @@ -101,7 +100,7 @@ def delete(self, graph: Graph) -> bool: Returns: bool: True if the resource was successfully deleted; False otherwise. """ - subscription_id = self.resource_subscription_id() + subscription_id = self.resource_subscription_id if subscription_id is None: log.warning("Failed to delete resource. Subscription ID is not available.") return False @@ -113,7 +112,7 @@ def delete_tag(self, key: str) -> bool: This method removes a specific value from a tag associated with a subscription, while keeping the tag itself intact. The tag remains on the account, but the specified value will be deleted. """ - subscription_id = self.resource_subscription_id() + subscription_id = self.resource_subscription_id if subscription_id is None: log.warning("Failed to delete tag. Subscription ID is not available.") return False @@ -125,7 +124,7 @@ def update_tag(self, key: str, value: str) -> bool: This method allows for the creation or update of a tag value associated with the specified tag name. The tag name must already exist for the operation to be successful. """ - subscription_id = self.resource_subscription_id() + subscription_id = self.resource_subscription_id if subscription_id is None: log.warning("Failed to update tag. Subscription ID is not available.") return False @@ -570,11 +569,13 @@ class AzureSku: "name": S("name"), "tier": S("tier"), "family": S("family"), + "size": S("size"), } capacity: Optional[int] = field(default=None, metadata={'description': 'Specifies the number of virtual machines in the scale set.'}) # fmt: skip family: Optional[str] = field(default=None, metadata={"description": "The family of the sku."}) name: Optional[str] = field(default=None, metadata={"description": "The sku name."}) tier: Optional[str] = field(default=None, metadata={'description': 'Specifies the tier of virtual machines in a scale set. Possible values: **standard** **basic**.'}) # fmt: skip + size: Optional[str] = field(default=None, metadata={"description": "Size of the particular SKU"}) class GraphBuilder: diff --git a/plugins/azure/fix_plugin_azure/resource/network.py b/plugins/azure/fix_plugin_azure/resource/network.py index 8b8f004151..00aa25b4bc 100644 --- a/plugins/azure/fix_plugin_azure/resource/network.py +++ b/plugins/azure/fix_plugin_azure/resource/network.py @@ -6,13 +6,13 @@ from fix_plugin_azure.azure_client import AzureResourceSpec from fix_plugin_azure.resource.base import ( AzureBaseUsage, - MicrosoftResource, GraphBuilder, AzureSubResource, AzureSku, AzureExtendedLocation, AzurePrivateLinkServiceConnectionState, AzureManagedServiceIdentity, + MicrosoftResource, ) from fix_plugin_azure.resource.containerservice import AzureManagedCluster from fix_plugin_azure.utils import rgetattr diff --git a/plugins/azure/fix_plugin_azure/resource/sql.py b/plugins/azure/fix_plugin_azure/resource/sql.py new file mode 100644 index 0000000000..c0dc11bb63 --- /dev/null +++ b/plugins/azure/fix_plugin_azure/resource/sql.py @@ -0,0 +1,1332 @@ +from datetime import datetime +from typing import Any, ClassVar, Dict, Optional, List, Type + +from attr import define, field + +from fix_plugin_azure.azure_client import AzureResourceSpec +from fix_plugin_azure.resource.base import AzureSku, GraphBuilder, MicrosoftResource +from fix_plugin_azure.resource.microsoft_graph import MicrosoftGraphServicePrincipal, MicrosoftGraphUser +from fix_plugin_azure.resource.network import AzureSubnet +from fixlib.baseresources import EdgeType, ModelReference +from fixlib.graph import BySearchCriteria +from fixlib.json_bender import Bender, S, ForallBend, Bend +from fixlib.types import Json + +service_name = "azure_sql" + + +class AzureUserIdentity: + kind: ClassVar[str] = "azure_user_identity" + mapping: ClassVar[Dict[str, Bender]] = {"client_id": S("clientId"), "principal_id": S("principalId")} + client_id: Optional[str] = field(default=None, metadata={"description": "The Azure Active Directory client id."}) + principal_id: Optional[str] = field(default=None, metadata={'description': 'The Azure Active Directory principal id.'}) # fmt: skip + + +@define(eq=False, slots=False) +class AzureResourceIdentity: + kind: ClassVar[str] = "azure_resource_identity" + mapping: ClassVar[Dict[str, Bender]] = { + "principal_id": S("principalId"), + "tenant_id": S("tenantId"), + "type": S("type"), + "user_assigned_identities": S("userAssignedIdentities"), + } + principal_id: Optional[str] = field(default=None, metadata={'description': 'The Azure Active Directory principal id.'}) # fmt: skip + tenant_id: Optional[str] = field(default=None, metadata={"description": "The Azure Active Directory tenant id."}) + type: Optional[str] = field(default=None, metadata={'description': 'The identity type. Set this to SystemAssigned in order to automatically create and assign an Azure Active Directory principal for the resource.'}) # fmt: skip + user_assigned_identities: Optional[Dict[str, AzureUserIdentity]] = field(default=None, metadata={'description': 'The resource ids of the user assigned identities to use'}) # fmt: skip + + +@define(eq=False, slots=False) +class AzurePrivateLinkServiceConnectionStateProperty: + kind: ClassVar[str] = "azure_private_link_service_connection_state_property" + mapping: ClassVar[Dict[str, Bender]] = { + "actions_required": S("actionsRequired"), + "description": S("description"), + "status": S("status"), + } + actions_required: Optional[str] = field(default=None, metadata={'description': 'The actions required for private link service connection.'}) # fmt: skip + description: Optional[str] = field(default=None, metadata={'description': 'The private link service connection description.'}) # fmt: skip + status: Optional[str] = field(default=None, metadata={'description': 'The private link service connection status.'}) # fmt: skip + + +@define(eq=False, slots=False) +class AzureDatabaseUserIdentity: + kind: ClassVar[str] = "azure_database_user_identity" + mapping: ClassVar[Dict[str, Bender]] = {"client_id": S("clientId"), "principal_id": S("principalId")} + client_id: Optional[str] = field(default=None, metadata={"description": "The Azure Active Directory client id."}) + principal_id: Optional[str] = field(default=None, metadata={'description': 'The Azure Active Directory principal id.'}) # fmt: skip + + +@define(eq=False, slots=False) +class AzureDatabaseIdentity: + kind: ClassVar[str] = "azure_database_identity" + mapping: ClassVar[Dict[str, Bender]] = { + "tenant_id": S("tenantId"), + "type": S("type"), + "user_assigned_identities": S("userAssignedIdentities"), + } + tenant_id: Optional[str] = field(default=None, metadata={"description": "The Azure Active Directory tenant id."}) + type: Optional[str] = field(default=None, metadata={"description": "The identity type"}) + user_assigned_identities: Optional[Dict[str, AzureDatabaseUserIdentity]] = field(default=None, metadata={'description': 'The resource ids of the user assigned identities to use'}) # fmt: skip + + +@define(eq=False, slots=False) +class AzureSqlDatabase(MicrosoftResource): + kind: ClassVar[str] = "azure_sql_database" + # Collect via AzureSqlServer() + reference_kinds: ClassVar[ModelReference] = { + "successors": { + "default": [ + "azure_sql_workload_group", + "azure_sql_geo_backup_policy", + "azure_sql_advisor", + "microsoft_graph_user", + ] + }, + "predecessors": {"default": ["azure_sql_elastic_pool"]}, + } + mapping: ClassVar[Dict[str, Bender]] = { + "id": S("id"), + "tags": S("tags", default={}), + "name": S("name"), + "type": S("type"), + "location": S("location"), + "ctime": S("properties", "creationDate"), + "atime": S("properties", "pausedDate"), + "auto_pause_delay": S("properties", "autoPauseDelay"), + "catalog_collation": S("properties", "catalogCollation"), + "collation": S("properties", "collation"), + "create_mode": S("properties", "createMode"), + "creation_date": S("properties", "creationDate"), + "current_backup_storage_redundancy": S("properties", "currentBackupStorageRedundancy"), + "current_service_objective_name": S("properties", "currentServiceObjectiveName"), + "current_sku": S("properties", "currentSku") >> Bend(AzureSku.mapping), + "database_id": S("properties", "databaseId"), + "default_secondary_location": S("properties", "defaultSecondaryLocation"), + "earliest_restore_date": S("properties", "earliestRestoreDate"), + "elastic_pool_id": S("properties", "elasticPoolId"), + "failover_group_id": S("properties", "failoverGroupId"), + "federated_client_id": S("properties", "federatedClientId"), + "high_availability_replica_count": S("properties", "highAvailabilityReplicaCount"), + "database_identity": S("identity") >> Bend(AzureDatabaseIdentity.mapping), + "is_infra_encryption_enabled": S("properties", "isInfraEncryptionEnabled"), + "is_ledger_on": S("properties", "isLedgerOn"), + "database_kind": S("kind"), + "license_type": S("properties", "licenseType"), + "long_term_retention_backup_resource_id": S("properties", "longTermRetentionBackupResourceId"), + "maintenance_configuration_id": S("properties", "maintenanceConfigurationId"), + "managed_by": S("managedBy"), + "max_log_size_bytes": S("properties", "maxLogSizeBytes"), + "max_size_bytes": S("properties", "maxSizeBytes"), + "min_capacity": S("properties", "minCapacity"), + "paused_date": S("properties", "pausedDate"), + "read_scale": S("properties", "readScale"), + "recoverable_database_id": S("properties", "recoverableDatabaseId"), + "recovery_services_recovery_point_id": S("properties", "recoveryServicesRecoveryPointId"), + "requested_backup_storage_redundancy": S("properties", "requestedBackupStorageRedundancy"), + "requested_service_objective_name": S("properties", "requestedServiceObjectiveName"), + "restorable_dropped_database_id": S("properties", "restorableDroppedDatabaseId"), + "restore_point_in_time": S("properties", "restorePointInTime"), + "resumed_date": S("properties", "resumedDate"), + "sample_name": S("properties", "sampleName"), + "secondary_type": S("properties", "secondaryType"), + "database_sku": S("sku") >> Bend(AzureSku.mapping), + "source_database_deletion_date": S("properties", "sourceDatabaseDeletionDate"), + "source_database_id": S("properties", "sourceDatabaseId"), + "source_resource_id": S("properties", "sourceResourceId"), + "status": S("properties", "status"), + "zone_redundant": S("properties", "zoneRedundant"), + } + auto_pause_delay: Optional[int] = field(default=None, metadata={'description': 'Time in minutes after which database is automatically paused. A value of -1 means that automatic pause is disabled'}) # fmt: skip + catalog_collation: Optional[str] = field(default=None, metadata={'description': 'Collation of the metadata catalog.'}) # fmt: skip + collation: Optional[str] = field(default=None, metadata={"description": "The collation of the database."}) + create_mode: Optional[str] = field(default=None, metadata={'description': 'Specifies the mode of database creation. Default: regular database creation. Copy: creates a database as a copy of an existing database. sourceDatabaseId must be specified as the resource ID of the source database. Secondary: creates a database as a secondary replica of an existing database. sourceDatabaseId must be specified as the resource ID of the existing primary database. PointInTimeRestore: Creates a database by restoring a point in time backup of an existing database. sourceDatabaseId must be specified as the resource ID of the existing database, and restorePointInTime must be specified. Recovery: Creates a database by restoring a geo-replicated backup. sourceDatabaseId must be specified as the recoverable database resource ID to restore. Restore: Creates a database by restoring a backup of a deleted database. sourceDatabaseId must be specified. If sourceDatabaseId is the database s original resource ID, then sourceDatabaseDeletionDate must be specified. Otherwise sourceDatabaseId must be the restorable dropped database resource ID and sourceDatabaseDeletionDate is ignored. restorePointInTime may also be specified to restore from an earlier point in time. RestoreLongTermRetentionBackup: Creates a database by restoring from a long term retention vault. recoveryServicesRecoveryPointResourceId must be specified as the recovery point resource ID. Copy, Secondary, and RestoreLongTermRetentionBackup are not supported for DataWarehouse edition.'}) # fmt: skip + creation_date: Optional[datetime] = field(default=None, metadata={'description': 'The creation date of the database (ISO8601 format).'}) # fmt: skip + current_backup_storage_redundancy: Optional[str] = field(default=None, metadata={'description': 'The storage account type used to store backups for this database.'}) # fmt: skip + current_service_objective_name: Optional[str] = field(default=None, metadata={'description': 'The current service level objective name of the database.'}) # fmt: skip + current_sku: Optional[AzureSku] = field(default=None, metadata={"description": "An ARM Resource SKU."}) + database_id: Optional[str] = field(default=None, metadata={"description": "The ID of the database."}) + default_secondary_location: Optional[str] = field(default=None, metadata={'description': 'The default secondary region for this database.'}) # fmt: skip + earliest_restore_date: Optional[datetime] = field(default=None, metadata={'description': 'This records the earliest start date and time that restore is available for this database (ISO8601 format).'}) # fmt: skip + elastic_pool_id: Optional[str] = field(default=None, metadata={'description': 'The resource identifier of the elastic pool containing this database.'}) # fmt: skip + failover_group_id: Optional[str] = field(default=None, metadata={'description': 'Failover Group resource identifier that this database belongs to.'}) # fmt: skip + federated_client_id: Optional[str] = field(default=None, metadata={'description': 'The Client id used for cross tenant per database CMK scenario'}) # fmt: skip + high_availability_replica_count: Optional[int] = field(default=None, metadata={'description': 'The number of secondary replicas associated with the database that are used to provide high availability. Not applicable to a Hyperscale database within an elastic pool.'}) # fmt: skip + database_identity: Optional[AzureDatabaseIdentity] = field(default=None, metadata={'description': 'Azure Active Directory identity configuration for a resource.'}) # fmt: skip + is_infra_encryption_enabled: Optional[bool] = field(default=None, metadata={'description': 'Infra encryption is enabled for this database.'}) # fmt: skip + is_ledger_on: Optional[bool] = field(default=None, metadata={'description': 'Whether or not this database is a ledger database, which means all tables in the database are ledger tables. Note: the value of this property cannot be changed after the database has been created.'}) # fmt: skip + database_kind: Optional[str] = field(default=None, metadata={'description': 'Kind of database. This is metadata used for the Azure portal experience.'}) # fmt: skip + license_type: Optional[str] = field(default=None, metadata={'description': 'The license type to apply for this database. `LicenseIncluded` if you need a license, or `BasePrice` if you have a license and are eligible for the Azure Hybrid Benefit.'}) # fmt: skip + long_term_retention_backup_resource_id: Optional[str] = field(default=None, metadata={'description': 'The resource identifier of the long term retention backup associated with create operation of this database.'}) # fmt: skip + maintenance_configuration_id: Optional[str] = field(default=None, metadata={'description': 'Maintenance configuration id assigned to the database. This configuration defines the period when the maintenance updates will occur.'}) # fmt: skip + managed_by: Optional[str] = field(default=None, metadata={"description": "Resource that manages the database."}) + max_log_size_bytes: Optional[int] = field(default=None, metadata={'description': 'The max log size for this database.'}) # fmt: skip + max_size_bytes: Optional[int] = field(default=None, metadata={'description': 'The max size of the database expressed in bytes.'}) # fmt: skip + min_capacity: Optional[float] = field(default=None, metadata={'description': 'Minimal capacity that database will always have allocated, if not paused'}) # fmt: skip + paused_date: Optional[datetime] = field(default=None, metadata={'description': 'The date when database was paused by user configuration or action(ISO8601 format). Null if the database is ready.'}) # fmt: skip + read_scale: Optional[str] = field(default=None, metadata={'description': 'The state of read-only routing. If enabled, connections that have application intent set to readonly in their connection string may be routed to a readonly secondary replica in the same region. Not applicable to a Hyperscale database within an elastic pool.'}) # fmt: skip + recoverable_database_id: Optional[str] = field(default=None, metadata={'description': 'The resource identifier of the recoverable database associated with create operation of this database.'}) # fmt: skip + recovery_services_recovery_point_id: Optional[str] = field(default=None, metadata={'description': 'The resource identifier of the recovery point associated with create operation of this database.'}) # fmt: skip + requested_backup_storage_redundancy: Optional[str] = field(default=None, metadata={'description': 'The storage account type to be used to store backups for this database.'}) # fmt: skip + requested_service_objective_name: Optional[str] = field(default=None, metadata={'description': 'The requested service level objective name of the database.'}) # fmt: skip + restorable_dropped_database_id: Optional[str] = field(default=None, metadata={'description': 'The resource identifier of the restorable dropped database associated with create operation of this database.'}) # fmt: skip + restore_point_in_time: Optional[datetime] = field(default=None, metadata={'description': 'Specifies the point in time (ISO8601 format) of the source database that will be restored to create the new database.'}) # fmt: skip + resumed_date: Optional[datetime] = field(default=None, metadata={'description': 'The date when database was resumed by user action or database login (ISO8601 format). Null if the database is paused.'}) # fmt: skip + sample_name: Optional[str] = field(default=None, metadata={'description': 'The name of the sample schema to apply when creating this database.'}) # fmt: skip + secondary_type: Optional[str] = field(default=None, metadata={'description': 'The secondary type of the database if it is a secondary. Valid values are Geo and Named.'}) # fmt: skip + database_sku: Optional[AzureSku] = field(default=None, metadata={"description": "An ARM Resource SKU."}) + source_database_deletion_date: Optional[datetime] = field(default=None, metadata={'description': 'Specifies the time that the database was deleted.'}) # fmt: skip + source_database_id: Optional[str] = field(default=None, metadata={'description': 'The resource identifier of the source database associated with create operation of this database.'}) # fmt: skip + source_resource_id: Optional[str] = field(default=None, metadata={'description': 'The resource identifier of the source associated with the create operation of this database. This property is only supported for DataWarehouse edition and allows to restore across subscriptions. When sourceResourceId is specified, sourceDatabaseId, recoverableDatabaseId, restorableDroppedDatabaseId and sourceDatabaseDeletionDate must not be specified and CreateMode must be PointInTimeRestore, Restore or Recover. When createMode is PointInTimeRestore, sourceResourceId must be the resource ID of the existing database or existing sql pool, and restorePointInTime must be specified. When createMode is Restore, sourceResourceId must be the resource ID of restorable dropped database or restorable dropped sql pool. When createMode is Recover, sourceResourceId must be the resource ID of recoverable database or recoverable sql pool. When source subscription belongs to a different tenant than target subscription, “x-ms-authorization-auxiliary” header must contain authentication token for the source tenant. For more details about “x-ms-authorization-auxiliary” header see https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/authenticate-multi-tenant '}) # fmt: skip + status: Optional[str] = field(default=None, metadata={"description": "The status of the database."}) + zone_redundant: Optional[bool] = field(default=None, metadata={'description': 'Whether or not this database is zone redundant, which means the replicas of this database will be spread across multiple availability zones.'}) # fmt: skip + type: Optional[str] = field(default=None, metadata={"description": "Resource type."}) + location: Optional[str] = field(default=None, metadata={"description": "Resource location."}) + + def _collect_items( + self, + graph_builder: GraphBuilder, + database_id: str, + resource_type: str, + class_instance: MicrosoftResource, + expected_error_codes: Optional[List[str]] = None, + ) -> None: + path = f"{database_id}/{resource_type}" + api_spec = AzureResourceSpec( + service="sql", + version="2021-11-01", + path=path, + path_parameters=[], + query_parameters=["api-version"], + access_path="value", + expect_array=True, + expected_error_codes=expected_error_codes or [], + ) + items = graph_builder.client.list(api_spec) + if not items: + return + collected = class_instance.collect(items, graph_builder) + for clazz in collected: + graph_builder.add_edge( + self, + edge_type=EdgeType.default, + id=clazz.id, + clazz=class_instance, + ) + + def post_process(self, graph_builder: GraphBuilder, source: Json) -> None: + if database_id := self.id: + resources_to_collect = [ + ("geoBackupPolicies", AzureSqlGeoBackupPolicy, None), + ("advisors?$expand=recommendedAction", AzureSqlAdvisor, None), + ("workloadGroups", AzureSqlWorkloadGroup, ["FeatureDisabledOnSelectedEdition"]), + ] + + for resource_type, resource_class, expected_error_codes in resources_to_collect: + graph_builder.submit_work( + service_name, + self._collect_items, + graph_builder, + database_id, + resource_type, + resource_class, + expected_error_codes, + ) + + def connect_in_graph(self, builder: GraphBuilder, source: Json) -> None: + if elastic_pool_id := self.elastic_pool_id: + builder.add_edge( + self, edge_type=EdgeType.default, reverse=True, clazz=AzureSqlElasticPool, id=elastic_pool_id + ) + # principal: collected via ms graph -> create a deferred edge + if (di := self.database_identity) and (uai := di.user_assigned_identities): + for _, identity_info in uai.items(): + if identity_info and identity_info.principal_id: + builder.add_deferred_edge( + from_node=self, + to_node=BySearchCriteria( + f'is({MicrosoftGraphUser.kind}) and reported.id=="{identity_info.principal_id}"' + ), + ) + + +@define(eq=False, slots=False) +class AzureElasticPoolPerDatabaseSettings: + kind: ClassVar[str] = "azure_elastic_pool_per_database_settings" + mapping: ClassVar[Dict[str, Bender]] = {"max_capacity": S("maxCapacity"), "min_capacity": S("minCapacity")} + max_capacity: Optional[float] = field(default=None, metadata={'description': 'The maximum capacity any one database can consume.'}) # fmt: skip + min_capacity: Optional[float] = field(default=None, metadata={'description': 'The minimum capacity all databases are guaranteed.'}) # fmt: skip + + +@define(eq=False, slots=False) +class AzureSqlElasticPool(MicrosoftResource): + kind: ClassVar[str] = "azure_sql_elastic_pool" + # Collect via AzureSqlServer() + mapping: ClassVar[Dict[str, Bender]] = { + "id": S("id"), + "tags": S("tags", default={}), + "name": S("name"), + "type": S("type"), + "location": S("location"), + "ctime": S("properties", "creationDate"), + "creation_date": S("properties", "creationDate"), + "high_availability_replica_count": S("properties", "highAvailabilityReplicaCount"), + "elastic_pool_kind": S("kind"), + "license_type": S("properties", "licenseType"), + "maintenance_configuration_id": S("properties", "maintenanceConfigurationId"), + "max_size_bytes": S("properties", "maxSizeBytes"), + "min_capacity": S("properties", "minCapacity"), + "per_database_settings": S("properties", "perDatabaseSettings") + >> Bend(AzureElasticPoolPerDatabaseSettings.mapping), + "elastic_pool_sku": S("sku") >> Bend(AzureSku.mapping), + "state": S("properties", "state"), + "zone_redundant": S("properties", "zoneRedundant"), + } + creation_date: Optional[datetime] = field(default=None, metadata={'description': 'The creation date of the elastic pool (ISO8601 format).'}) # fmt: skip + high_availability_replica_count: Optional[int] = field(default=None, metadata={'description': 'The number of secondary replicas associated with the elastic pool that are used to provide high availability. Applicable only to Hyperscale elastic pools.'}) # fmt: skip + elastic_pool_kind: Optional[str] = field(default=None, metadata={'description': 'Kind of elastic pool. This is metadata used for the Azure portal experience.'}) # fmt: skip + license_type: Optional[str] = field(default=None, metadata={'description': 'The license type to apply for this elastic pool.'}) # fmt: skip + maintenance_configuration_id: Optional[str] = field(default=None, metadata={'description': 'Maintenance configuration id assigned to the elastic pool. This configuration defines the period when the maintenance updates will will occur.'}) # fmt: skip + max_size_bytes: Optional[int] = field(default=None, metadata={'description': 'The storage limit for the database elastic pool in bytes.'}) # fmt: skip + min_capacity: Optional[float] = field(default=None, metadata={'description': 'Minimal capacity that serverless pool will not shrink below, if not paused'}) # fmt: skip + per_database_settings: Optional[AzureElasticPoolPerDatabaseSettings] = field(default=None, metadata={'description': 'Per database settings of an elastic pool.'}) # fmt: skip + elastic_pool_sku: Optional[AzureSku] = field(default=None, metadata={"description": "An ARM Resource SKU."}) + state: Optional[str] = field(default=None, metadata={"description": "The state of the elastic pool."}) + zone_redundant: Optional[bool] = field(default=None, metadata={'description': 'Whether or not this elastic pool is zone redundant, which means the replicas of this elastic pool will be spread across multiple availability zones.'}) # fmt: skip + type: Optional[str] = field(default=None, metadata={"description": "Resource type."}) + location: Optional[str] = field(default=None, metadata={"description": "Resource location."}) + + +@define(eq=False, slots=False) +class AzureSqlPrivateEndpointConnection(MicrosoftResource): + kind: ClassVar[str] = "azure_sql_private_endpoint_connection" + # Collect via AzureSqlServer() + mapping: ClassVar[Dict[str, Bender]] = { + "id": S("id"), + "tags": S("tags", default={}), + "name": S("name"), + "type": S("type"), + "group_ids": S("properties", "groupIds"), + "private_endpoint_id": S("properties", "privateEndpoint", "id"), + "private_link_service_connection_state": S("properties", "privateLinkServiceConnectionState") + >> Bend(AzurePrivateLinkServiceConnectionStateProperty.mapping), + "provisioning_state": S("properties", "provisioningState"), + } + group_ids: Optional[List[str]] = field(default=None, metadata={"description": "Group IDs."}) + private_endpoint_id: Optional[str] = field(default=None, metadata={"description": "Private endpoint ID."}) + private_link_service_connection_state: Optional[AzurePrivateLinkServiceConnectionStateProperty] = field(default=None, metadata={'description': ''}) # fmt: skip + provisioning_state: Optional[str] = field(default=None, metadata={'description': 'State of the private endpoint connection.'}) # fmt: skip + type: Optional[str] = field(default=None, metadata={"description": "Resource type."}) + + +@define(eq=False, slots=False) +class AzureFailoverGroupReadWriteEndpoint: + kind: ClassVar[str] = "azure_failover_group_read_write_endpoint" + mapping: ClassVar[Dict[str, Bender]] = { + "failover_policy": S("failoverPolicy"), + "failover_with_data_loss_grace_period_minutes": S("failoverWithDataLossGracePeriodMinutes"), + } + failover_policy: Optional[str] = field(default=None, metadata={'description': 'Failover policy of the read-write endpoint for the failover group. If failoverPolicy is Automatic then failoverWithDataLossGracePeriodMinutes is required.'}) # fmt: skip + failover_with_data_loss_grace_period_minutes: Optional[int] = field(default=None, metadata={'description': 'Grace period before failover with data loss is attempted for the read-write endpoint. If failoverPolicy is Automatic then failoverWithDataLossGracePeriodMinutes is required.'}) # fmt: skip + + +@define(eq=False, slots=False) +class AzurePartnerInfo: + kind: ClassVar[str] = "azure_partner_info" + mapping: ClassVar[Dict[str, Bender]] = { + "id": S("id"), + "location": S("location"), + "replication_role": S("replicationRole"), + } + id: Optional[str] = field(default=None, metadata={"description": "Resource identifier of the partner server."}) + location: Optional[str] = field(default=None, metadata={"description": "Geo location of the partner server."}) + replication_role: Optional[str] = field(default=None, metadata={'description': 'Replication role of the partner server.'}) # fmt: skip + + +@define(eq=False, slots=False) +class AzureSqlFailoverGroup(MicrosoftResource): + kind: ClassVar[str] = "azure_sql_failover_group" + # Collect via AzureSqlServer() + reference_kinds: ClassVar[ModelReference] = { + "predecessors": {"default": ["azure_sql_database"]}, + } + mapping: ClassVar[Dict[str, Bender]] = { + "id": S("id"), + "tags": S("tags", default={}), + "name": S("name"), + "type": S("type"), + "database_ids": S("properties", "databases"), + "partner_servers": S("properties", "partnerServers") >> ForallBend(AzurePartnerInfo.mapping), + "group_read_only_endpoint": S("properties", "readOnlyEndpoint", "failoverPolicy"), + "group_read_write_endpoint": S("properties", "readWriteEndpoint") + >> Bend(AzureFailoverGroupReadWriteEndpoint.mapping), + "replication_role": S("properties", "replicationRole"), + "replication_state": S("properties", "replicationState"), + } + database_ids: Optional[List[str]] = field(default=None, metadata={'description': 'List of databases in the failover group.'}) # fmt: skip + partner_servers: Optional[List[AzurePartnerInfo]] = field(default=None, metadata={'description': 'List of partner server information for the failover group.'}) # fmt: skip + group_read_only_endpoint: Optional[str] = field(default=None, metadata={'description': 'Read-only endpoint of the failover group instance.'}) # fmt: skip + group_read_write_endpoint: Optional[AzureFailoverGroupReadWriteEndpoint] = field(default=None, metadata={'description': 'Read-write endpoint of the failover group instance.'}) # fmt: skip + replication_role: Optional[str] = field(default=None, metadata={'description': 'Local replication role of the failover group instance.'}) # fmt: skip + replication_state: Optional[str] = field(default=None, metadata={'description': 'Replication state of the failover group instance.'}) # fmt: skip + type: Optional[str] = field(default=None, metadata={"description": "Resource type."}) + + def connect_in_graph(self, builder: GraphBuilder, source: Json) -> None: + if database_ids := self.database_ids: + for database_id in database_ids: + builder.add_edge(self, edge_type=EdgeType.default, reverse=True, clazz=AzureSqlDatabase, id=database_id) + + +@define(eq=False, slots=False) +class AzureSqlFirewallRule(MicrosoftResource): + kind: ClassVar[str] = "azure_sql_firewall_rule" + # Collect via AzureSqlServer() + mapping: ClassVar[Dict[str, Bender]] = { + "id": S("id"), + "tags": S("tags", default={}), + "name": S("name"), + "type": S("type"), + "end_ip_address": S("properties", "endIpAddress"), + "start_ip_address": S("properties", "startIpAddress"), + } + end_ip_address: Optional[str] = field(default=None, metadata={'description': 'The end IP address of the firewall rule. Must be IPv4 format. Must be greater than or equal to startIpAddress. Use value 0.0.0.0 for all Azure-internal IP addresses.'}) # fmt: skip + start_ip_address: Optional[str] = field(default=None, metadata={'description': 'The start IP address of the firewall rule. Must be IPv4 format. Use value 0.0.0.0 for all Azure-internal IP addresses.'}) # fmt: skip + type: Optional[str] = field(default=None, metadata={"description": "Resource type."}) + + +@define(eq=False, slots=False) +class AzureSqlGeoBackupPolicy(MicrosoftResource): + kind: ClassVar[str] = "azure_sql_geo_backup_policy" + # Collect via AzureSqlDatabase() + mapping: ClassVar[Dict[str, Bender]] = { + "id": S("id"), + "tags": S("tags", default={}), + "name": S("name"), + "type": S("type"), + "backup_policy_kind": S("kind"), + "state": S("properties", "state"), + "storage_type": S("properties", "storageType"), + } + backup_policy_kind: Optional[str] = field(default=None, metadata={'description': 'Kind of geo backup policy. This is metadata used for the Azure portal experience.'}) # fmt: skip + state: Optional[str] = field(default=None, metadata={"description": "The state of the geo backup policy."}) + storage_type: Optional[str] = field(default=None, metadata={'description': 'The storage type of the geo backup policy.'}) # fmt: skip + type: Optional[str] = field(default=None, metadata={"description": "Resource type."}) + + +@define(eq=False, slots=False) +class AzureInstanceFailoverGroupReadWriteEndpoint: + kind: ClassVar[str] = "azure_instance_failover_group_read_write_endpoint" + mapping: ClassVar[Dict[str, Bender]] = { + "failover_policy": S("failoverPolicy"), + "failover_with_data_loss_grace_period_minutes": S("failoverWithDataLossGracePeriodMinutes"), + } + failover_policy: Optional[str] = field(default=None, metadata={'description': 'Failover policy of the read-write endpoint for the failover group. If failoverPolicy is Automatic then failoverWithDataLossGracePeriodMinutes is required.'}) # fmt: skip + failover_with_data_loss_grace_period_minutes: Optional[int] = field(default=None, metadata={'description': 'Grace period before failover with data loss is attempted for the read-write endpoint. If failoverPolicy is Automatic then failoverWithDataLossGracePeriodMinutes is required.'}) # fmt: skip + + +@define(eq=False, slots=False) +class AzurePartnerRegionInfo: + kind: ClassVar[str] = "azure_partner_region_info" + mapping: ClassVar[Dict[str, Bender]] = {"location": S("location"), "replication_role": S("replicationRole")} + location: Optional[str] = field(default=None, metadata={'description': 'Geo location of the partner managed instances.'}) # fmt: skip + replication_role: Optional[str] = field(default=None, metadata={'description': 'Replication role of the partner managed instances.'}) # fmt: skip + + +@define(eq=False, slots=False) +class AzureManagedInstancePairInfo: + kind: ClassVar[str] = "azure_managed_instance_pair_info" + mapping: ClassVar[Dict[str, Bender]] = { + "partner_managed_instance_id": S("partnerManagedInstanceId"), + "primary_managed_instance_id": S("primaryManagedInstanceId"), + } + partner_managed_instance_id: Optional[str] = field(default=None, metadata={'description': 'Id of Partner Managed Instance in pair.'}) # fmt: skip + primary_managed_instance_id: Optional[str] = field(default=None, metadata={'description': 'Id of Primary Managed Instance in pair.'}) # fmt: skip + + +@define(eq=False, slots=False) +class AzureSqlInstanceFailoverGroup(MicrosoftResource): + kind: ClassVar[str] = "azure_sql_instance_failover_group" + # Collect via AzureSqlManagedInstance() + reference_kinds: ClassVar[ModelReference] = { + "predecessors": {"default": ["azure_sql_managed_instance"]}, + } + mapping: ClassVar[Dict[str, Bender]] = { + "id": S("id"), + "tags": S("tags", default={}), + "name": S("name"), + "type": S("type"), + "managed_instance_pairs": S("properties", "managedInstancePairs") + >> ForallBend(AzureManagedInstancePairInfo.mapping), + "partner_regions": S("properties", "partnerRegions") >> ForallBend(AzurePartnerRegionInfo.mapping), + "sql_instance_read_only_endpoint": S("properties", "readOnlyEndpoint", "failoverPolicy"), + "sql_instance_read_write_endpoint": S("properties", "readWriteEndpoint") + >> Bend(AzureInstanceFailoverGroupReadWriteEndpoint.mapping), + "replication_role": S("properties", "replicationRole"), + "replication_state": S("properties", "replicationState"), + } + managed_instance_pairs: Optional[List[AzureManagedInstancePairInfo]] = field(default=None, metadata={'description': 'List of managed instance pairs in the failover group.'}) # fmt: skip + partner_regions: Optional[List[AzurePartnerRegionInfo]] = field(default=None, metadata={'description': 'Partner region information for the failover group.'}) # fmt: skip + sql_instance_read_only_endpoint: Optional[str] = field(default=None, metadata={'description': 'Read-only endpoint of the failover group instance.'}) # fmt: skip + sql_instance_read_write_endpoint: Optional[AzureInstanceFailoverGroupReadWriteEndpoint] = field(default=None, metadata={'description': 'Read-write endpoint of the failover group instance.'}) # fmt: skip + replication_role: Optional[str] = field(default=None, metadata={'description': 'Local replication role of the failover group instance.'}) # fmt: skip + replication_state: Optional[str] = field(default=None, metadata={'description': 'Replication state of the failover group instance.'}) # fmt: skip + type: Optional[str] = field(default=None, metadata={"description": "Resource type."}) + + def connect_in_graph(self, builder: GraphBuilder, source: Json) -> None: + if managed_instance_pairs := self.managed_instance_pairs: + for managed_instance_pair in managed_instance_pairs: + if (primary_managed_instance_id := managed_instance_pair.primary_managed_instance_id) and ( + secondary_managed_instance_id := managed_instance_pair.partner_managed_instance_id + ): + builder.add_edge( + self, + reverse=True, + edge_type=EdgeType.default, + id=primary_managed_instance_id, + clazz=AzureSqlManagedInstance, + ) + builder.add_edge( + self, + reverse=True, + edge_type=EdgeType.default, + id=secondary_managed_instance_id, + clazz=AzureSqlManagedInstance, + ) + + +@define(eq=False, slots=False) +class AzureSqlInstancePool(MicrosoftResource): + kind: ClassVar[str] = "azure_sql_instance_pool" + api_spec: ClassVar[AzureResourceSpec] = AzureResourceSpec( + service="sql", + version="2021-11-01", + path="/subscriptions/{subscriptionId}/providers/Microsoft.Sql/instancePools", + path_parameters=["subscriptionId"], + query_parameters=["api-version"], + access_path="value", + expect_array=True, + ) + reference_kinds: ClassVar[ModelReference] = { + "predecessors": {"default": ["azure_subnet"]}, + } + mapping: ClassVar[Dict[str, Bender]] = { + "id": S("id"), + "tags": S("tags", default={}), + "name": S("name"), + "type": S("type"), + "location": S("location"), + "license_type": S("properties", "licenseType"), + "instance_pool_sku": S("sku") >> Bend(AzureSku.mapping), + "subnet_id": S("properties", "subnetId"), + "v_cores": S("properties", "vCores"), + } + license_type: Optional[str] = field(default=None, metadata={'description': 'The license type. Possible values are LicenseIncluded (price for SQL license is included) and BasePrice (without SQL license price).'}) # fmt: skip + instance_pool_sku: Optional[AzureSku] = field(default=None, metadata={"description": "An ARM Resource SKU."}) + subnet_id: Optional[str] = field(default=None, metadata={'description': 'Resource ID of the subnet to place this instance pool in.'}) # fmt: skip + v_cores: Optional[int] = field(default=None, metadata={'description': 'Count of vCores belonging to this instance pool.'}) # fmt: skip + type: Optional[str] = field(default=None, metadata={"description": "Resource type."}) + location: Optional[str] = field(default=None, metadata={"description": "Resource location."}) + + def connect_in_graph(self, builder: GraphBuilder, source: Json) -> None: + if subnet_id := self.subnet_id: + builder.add_edge(self, edge_type=EdgeType.default, reverse=True, clazz=AzureSubnet, id=subnet_id) + + +@define(eq=False, slots=False) +class AzureSqlJobAgent(MicrosoftResource): + kind: ClassVar[str] = "azure_sql_job_agent" + # Collect via AzureSqlServer() + reference_kinds: ClassVar[ModelReference] = { + "predecessors": {"default": ["azure_sql_database"]}, + } + mapping: ClassVar[Dict[str, Bender]] = { + "id": S("id"), + "tags": S("tags", default={}), + "name": S("name"), + "type": S("type"), + "location": S("location"), + "database_id": S("properties", "databaseId"), + "job_agent_sku": S("sku") >> Bend(AzureSku.mapping), + "state": S("properties", "state"), + } + database_id: Optional[str] = field(default=None, metadata={'description': 'Resource ID of the database to store job metadata in.'}) # fmt: skip + job_agent_sku: Optional[AzureSku] = field(default=None, metadata={"description": "An ARM Resource SKU."}) + state: Optional[str] = field(default=None, metadata={"description": "The state of the job agent."}) + type: Optional[str] = field(default=None, metadata={"description": "Resource type."}) + location: Optional[str] = field(default=None, metadata={"description": "Resource location."}) + + def connect_in_graph(self, builder: GraphBuilder, source: Json) -> None: + if database_id := self.database_id: + builder.add_edge(self, edge_type=EdgeType.default, reverse=True, clazz=AzureSqlDatabase, id=database_id) + + +@define(eq=False, slots=False) +class AzureSqlManagedDatabase(MicrosoftResource): + kind: ClassVar[str] = "azure_sql_managed_database" + # Collect via AzureSqlManagedInstance() + mapping: ClassVar[Dict[str, Bender]] = { + "id": S("id"), + "tags": S("tags", default={}), + "name": S("name"), + "type": S("type"), + "location": S("location"), + "ctime": S("properties", "creationDate"), + "auto_complete_restore": S("properties", "autoCompleteRestore"), + "catalog_collation": S("properties", "catalogCollation"), + "collation": S("properties", "collation"), + "create_mode": S("properties", "createMode"), + "creation_date": S("properties", "creationDate"), + "default_secondary_location": S("properties", "defaultSecondaryLocation"), + "earliest_restore_point": S("properties", "earliestRestorePoint"), + "failover_group_id": S("properties", "failoverGroupId"), + "last_backup_name": S("properties", "lastBackupName"), + "long_term_retention_backup_resource_id": S("properties", "longTermRetentionBackupResourceId"), + "recoverable_database_id": S("properties", "recoverableDatabaseId"), + "restorable_dropped_database_id": S("properties", "restorableDroppedDatabaseId"), + "restore_point_in_time": S("properties", "restorePointInTime"), + "source_database_id": S("properties", "sourceDatabaseId"), + "status": S("properties", "status"), + "storage_container_sas_token": S("properties", "storageContainerSasToken"), + "storage_container_uri": S("properties", "storageContainerUri"), + } + auto_complete_restore: Optional[bool] = field(default=None, metadata={'description': 'Whether to auto complete restore of this managed database.'}) # fmt: skip + catalog_collation: Optional[str] = field(default=None, metadata={'description': 'Collation of the metadata catalog.'}) # fmt: skip + collation: Optional[str] = field(default=None, metadata={"description": "Collation of the managed database."}) + create_mode: Optional[str] = field(default=None, metadata={'description': 'Managed database create mode. PointInTimeRestore: Create a database by restoring a point in time backup of an existing database. SourceDatabaseName, SourceManagedInstanceName and PointInTime must be specified. RestoreExternalBackup: Create a database by restoring from external backup files. Collation, StorageContainerUri and StorageContainerSasToken must be specified. Recovery: Creates a database by restoring a geo-replicated backup. RecoverableDatabaseId must be specified as the recoverable database resource ID to restore. RestoreLongTermRetentionBackup: Create a database by restoring from a long term retention backup (longTermRetentionBackupResourceId required).'}) # fmt: skip + creation_date: Optional[datetime] = field(default=None, metadata={"description": "Creation date of the database."}) + default_secondary_location: Optional[str] = field(default=None, metadata={"description": "Geo paired region."}) + earliest_restore_point: Optional[datetime] = field(default=None, metadata={'description': 'Earliest restore point in time for point in time restore.'}) # fmt: skip + failover_group_id: Optional[str] = field(default=None, metadata={'description': 'Instance Failover Group resource identifier that this managed database belongs to.'}) # fmt: skip + last_backup_name: Optional[str] = field(default=None, metadata={'description': 'Last backup file name for restore of this managed database.'}) # fmt: skip + long_term_retention_backup_resource_id: Optional[str] = field(default=None, metadata={'description': 'The name of the Long Term Retention backup to be used for restore of this managed database.'}) # fmt: skip + recoverable_database_id: Optional[str] = field(default=None, metadata={'description': 'The resource identifier of the recoverable database associated with create operation of this database.'}) # fmt: skip + restorable_dropped_database_id: Optional[str] = field(default=None, metadata={'description': 'The restorable dropped database resource id to restore when creating this database.'}) # fmt: skip + restore_point_in_time: Optional[datetime] = field(default=None, metadata={'description': 'Conditional. If createMode is PointInTimeRestore, this value is required. Specifies the point in time (ISO8601 format) of the source database that will be restored to create the new database.'}) # fmt: skip + source_database_id: Optional[str] = field(default=None, metadata={'description': 'The resource identifier of the source database associated with create operation of this database.'}) # fmt: skip + status: Optional[str] = field(default=None, metadata={"description": "Status of the database."}) + storage_container_sas_token: Optional[str] = field(default=None, metadata={'description': 'Conditional. If createMode is RestoreExternalBackup, this value is required. Specifies the storage container sas token.'}) # fmt: skip + storage_container_uri: Optional[str] = field(default=None, metadata={'description': 'Conditional. If createMode is RestoreExternalBackup, this value is required. Specifies the uri of the storage container where backups for this restore are stored.'}) # fmt: skip + type: Optional[str] = field(default=None, metadata={"description": "Resource type."}) + location: Optional[str] = field(default=None, metadata={"description": "Resource location."}) + + +@define(eq=False, slots=False) +class AzureManagedInstancePrivateLinkServiceConnectionStateProperty: + kind: ClassVar[str] = "azure_managed_instance_private_link_service_connection_state_property" + mapping: ClassVar[Dict[str, Bender]] = { + "actions_required": S("actionsRequired"), + "description": S("description"), + "status": S("status"), + } + actions_required: Optional[str] = field(default=None, metadata={'description': 'The private link service connection description.'}) # fmt: skip + description: Optional[str] = field(default=None, metadata={'description': 'The private link service connection description.'}) # fmt: skip + status: Optional[str] = field(default=None, metadata={'description': 'The private link service connection status.'}) # fmt: skip + + +@define(eq=False, slots=False) +class AzureManagedInstancePecProperty: + kind: ClassVar[str] = "azure_managed_instance_pec_property" + mapping: ClassVar[Dict[str, Bender]] = { + "id": S("id"), + "private_endpoint_id": S("properties", "privateEndpoint", "id"), + "private_link_service_connection_state": S("properties", "privateLinkServiceConnectionState") + >> Bend(AzureManagedInstancePrivateLinkServiceConnectionStateProperty.mapping), + "provisioning_state": S("properties", "provisioningState"), + } + id: Optional[str] = field(default=None, metadata={"description": "Resource ID."}) + private_endpoint_id: Optional[str] = field(default=None, metadata={"description": ""}) + private_link_service_connection_state: Optional[AzureManagedInstancePrivateLinkServiceConnectionStateProperty] = field(default=None, metadata={'description': ''}) # fmt: skip + provisioning_state: Optional[str] = field(default=None, metadata={'description': 'State of the Private Endpoint Connection.'}) # fmt: skip + + +@define(eq=False, slots=False) +class AzureManagedInstanceExternalAdministrator: + kind: ClassVar[str] = "azure_managed_instance_external_administrator" + mapping: ClassVar[Dict[str, Bender]] = { + "administrator_type": S("administratorType"), + "azure_ad_only_authentication": S("azureADOnlyAuthentication"), + "login": S("login"), + "principal_type": S("principalType"), + "sid": S("sid"), + "tenant_id": S("tenantId"), + } + administrator_type: Optional[str] = field(default=None, metadata={'description': 'Type of the sever administrator.'}) # fmt: skip + azure_ad_only_authentication: Optional[bool] = field(default=None, metadata={'description': 'Azure Active Directory only Authentication enabled.'}) # fmt: skip + login: Optional[str] = field(default=None, metadata={"description": "Login name of the server administrator."}) + principal_type: Optional[str] = field(default=None, metadata={'description': 'Principal Type of the sever administrator.'}) # fmt: skip + sid: Optional[str] = field(default=None, metadata={"description": "SID (object ID) of the server administrator."}) + tenant_id: Optional[str] = field(default=None, metadata={"description": "Tenant ID of the administrator."}) + + +@define(eq=False, slots=False) +class AzureServicePrincipal: + kind: ClassVar[str] = "azure_service_principal" + mapping: ClassVar[Dict[str, Bender]] = { + "client_id": S("clientId"), + "principal_id": S("principalId"), + "tenant_id": S("tenantId"), + "type": S("type"), + } + client_id: Optional[str] = field(default=None, metadata={'description': 'The Azure Active Directory application client id.'}) # fmt: skip + principal_id: Optional[str] = field(default=None, metadata={'description': 'The Azure Active Directory application object id.'}) # fmt: skip + tenant_id: Optional[str] = field(default=None, metadata={"description": "The Azure Active Directory tenant id."}) + type: Optional[str] = field(default=None, metadata={"description": "Service principal type."}) + + +@define(eq=False, slots=False) +class AzureSqlManagedInstance(MicrosoftResource): + kind: ClassVar[str] = "azure_sql_managed_instance" + api_spec: ClassVar[AzureResourceSpec] = AzureResourceSpec( + service="sql", + version="2021-11-01", + path="/subscriptions/{subscriptionId}/providers/Microsoft.Sql/managedInstances", + path_parameters=["subscriptionId"], + query_parameters=["api-version"], + access_path="value", + expect_array=True, + ) + reference_kinds: ClassVar[ModelReference] = { + "successors": { + "default": [ + "azure_sql_managed_database", + "azure_sql_server_trust_group", + "azure_sql_private_endpoint_connection", + "microsoft_graph_service_principal", + "microsoft_graph_user", + ] + }, + "predecessors": {"default": ["azure_sql_instance_pool", "azure_subnet"]}, + } + mapping: ClassVar[Dict[str, Bender]] = { + "id": S("id"), + "tags": S("tags", default={}), + "name": S("name"), + "type": S("type"), + "location": S("location"), + "administrator_login": S("properties", "administratorLogin"), + "administrator_login_password": S("properties", "administratorLoginPassword"), + "managed_instance_administrators": S("properties", "administrators") + >> Bend(AzureManagedInstanceExternalAdministrator.mapping), + "collation": S("properties", "collation"), + "current_backup_storage_redundancy": S("properties", "currentBackupStorageRedundancy"), + "dns_zone": S("properties", "dnsZone"), + "dns_zone_partner": S("properties", "dnsZonePartner"), + "fully_qualified_domain_name": S("properties", "fullyQualifiedDomainName"), + "managed_instance_identity": S("identity") >> Bend(AzureResourceIdentity.mapping), + "instance_pool_id": S("properties", "instancePoolId"), + "key_id": S("properties", "keyId"), + "license_type": S("properties", "licenseType"), + "maintenance_configuration_id": S("properties", "maintenanceConfigurationId"), + "managed_instance_create_mode": S("properties", "managedInstanceCreateMode"), + "minimal_tls_version": S("properties", "minimalTlsVersion"), + "primary_user_assigned_identity_id": S("properties", "primaryUserAssignedIdentityId"), + "instance_private_endpoint_connections": S("properties", "privateEndpointConnections") + >> ForallBend(AzureManagedInstancePecProperty.mapping), + "provisioning_state": S("properties", "provisioningState"), + "proxy_override": S("properties", "proxyOverride"), + "public_data_endpoint_enabled": S("properties", "publicDataEndpointEnabled"), + "requested_backup_storage_redundancy": S("properties", "requestedBackupStorageRedundancy"), + "restore_point_in_time": S("properties", "restorePointInTime"), + "service_principal": S("properties", "servicePrincipal") >> Bend(AzureServicePrincipal.mapping), + "managed_instance_sku": S("sku") >> Bend(AzureSku.mapping), + "source_managed_instance_id": S("properties", "sourceManagedInstanceId"), + "state": S("properties", "state"), + "storage_size_in_gb": S("properties", "storageSizeInGB"), + "subnet_id": S("properties", "subnetId"), + "timezone_id": S("properties", "timezoneId"), + "v_cores": S("properties", "vCores"), + "zone_redundant": S("properties", "zoneRedundant"), + } + administrator_login: Optional[str] = field(default=None, metadata={'description': 'Administrator username for the managed instance. Can only be specified when the managed instance is being created (and is required for creation).'}) # fmt: skip + administrator_login_password: Optional[str] = field(default=None, metadata={'description': 'The administrator login password (required for managed instance creation).'}) # fmt: skip + managed_instance_administrators: Optional[AzureManagedInstanceExternalAdministrator] = field(default=None, metadata={'description': 'Properties of a active directory administrator.'}) # fmt: skip + collation: Optional[str] = field(default=None, metadata={"description": "Collation of the managed instance."}) + current_backup_storage_redundancy: Optional[str] = field(default=None, metadata={'description': 'The storage account type used to store backups for this instance. The options are Local (LocallyRedundantStorage), Zone (ZoneRedundantStorage), Geo (GeoRedundantStorage) and GeoZone(GeoZoneRedundantStorage)'}) # fmt: skip + dns_zone: Optional[str] = field(default=None, metadata={'description': 'The Dns Zone that the managed instance is in.'}) # fmt: skip + dns_zone_partner: Optional[str] = field(default=None, metadata={'description': 'The resource id of another managed instance whose DNS zone this managed instance will share after creation.'}) # fmt: skip + fully_qualified_domain_name: Optional[str] = field(default=None, metadata={'description': 'The fully qualified domain name of the managed instance.'}) # fmt: skip + managed_instance_identity: Optional[AzureResourceIdentity] = field(default=None, metadata={'description': 'Azure Active Directory identity configuration for a resource.'}) # fmt: skip + instance_pool_id: Optional[str] = field(default=None, metadata={'description': 'The Id of the instance pool this managed server belongs to.'}) # fmt: skip + key_id: Optional[str] = field(default=None, metadata={'description': 'A CMK URI of the key to use for encryption.'}) # fmt: skip + license_type: Optional[str] = field(default=None, metadata={'description': 'The license type. Possible values are LicenseIncluded (regular price inclusive of a new SQL license) and BasePrice (discounted AHB price for bringing your own SQL licenses).'}) # fmt: skip + maintenance_configuration_id: Optional[str] = field(default=None, metadata={'description': 'Specifies maintenance configuration id to apply to this managed instance.'}) # fmt: skip + managed_instance_create_mode: Optional[str] = field(default=None, metadata={'description': 'Specifies the mode of database creation. Default: Regular instance creation. Restore: Creates an instance by restoring a set of backups to specific point in time. RestorePointInTime and SourceManagedInstanceId must be specified.'}) # fmt: skip + minimal_tls_version: Optional[str] = field(default=None, metadata={'description': 'Minimal TLS version. Allowed values: None , 1.0 , 1.1 , 1.2 '}) # fmt: skip + primary_user_assigned_identity_id: Optional[str] = field(default=None, metadata={'description': 'The resource id of a user assigned identity to be used by default.'}) # fmt: skip + instance_private_endpoint_connections: Optional[List[AzureManagedInstancePecProperty]] = field(default=None, metadata={'description': 'List of private endpoint connections on a managed instance.'}) # fmt: skip + provisioning_state: Optional[str] = field(default=None, metadata={"description": ""}) + proxy_override: Optional[str] = field(default=None, metadata={'description': 'Connection type used for connecting to the instance.'}) # fmt: skip + public_data_endpoint_enabled: Optional[bool] = field(default=None, metadata={'description': 'Whether or not the public data endpoint is enabled.'}) # fmt: skip + requested_backup_storage_redundancy: Optional[str] = field(default=None, metadata={'description': 'The storage account type to be used to store backups for this instance. The options are Local (LocallyRedundantStorage), Zone (ZoneRedundantStorage), Geo (GeoRedundantStorage) and GeoZone(GeoZoneRedundantStorage)'}) # fmt: skip + restore_point_in_time: Optional[datetime] = field(default=None, metadata={'description': 'Specifies the point in time (ISO8601 format) of the source database that will be restored to create the new database.'}) # fmt: skip + service_principal: Optional[AzureServicePrincipal] = field(default=None, metadata={'description': 'The managed instance s service principal configuration for a resource.'}) # fmt: skip + managed_instance_sku: Optional[AzureSku] = field(default=None, metadata={"description": "An ARM Resource SKU."}) + source_managed_instance_id: Optional[str] = field(default=None, metadata={'description': 'The resource identifier of the source managed instance associated with create operation of this instance.'}) # fmt: skip + state: Optional[str] = field(default=None, metadata={"description": "The state of the managed instance."}) + storage_size_in_gb: Optional[int] = field(default=None, metadata={'description': 'Storage size in GB. Minimum value: 32. Maximum value: 16384. Increments of 32 GB allowed only. Maximum value depends on the selected hardware family and number of vCores.'}) # fmt: skip + subnet_id: Optional[str] = field(default=None, metadata={'description': 'Subnet resource ID for the managed instance.'}) # fmt: skip + timezone_id: Optional[str] = field(default=None, metadata={'description': 'Id of the timezone. Allowed values are timezones supported by Windows. Windows keeps details on supported timezones, including the id.'}) # fmt: skip + v_cores: Optional[int] = field(default=None, metadata={'description': 'The number of vCores. Allowed values: 8, 16, 24, 32, 40, 64, 80.'}) # fmt: skip + zone_redundant: Optional[bool] = field(default=None, metadata={'description': 'Whether or not the multi-az is enabled.'}) # fmt: skip + type: Optional[str] = field(default=None, metadata={"description": "Resource type."}) + location: Optional[str] = field(default=None, metadata={"description": "Resource location."}) + + def _collect_items( + self, + graph_builder: GraphBuilder, + managed_instance_id: str, + resource_type: str, + class_instance: MicrosoftResource, + ) -> None: + path = f"{managed_instance_id}/{resource_type}" + api_spec = AzureResourceSpec( + service="sql", + version="2021-11-01", + path=path, + path_parameters=[], + query_parameters=["api-version"], + access_path="value", + expect_array=True, + ) + items = graph_builder.client.list(api_spec) + if not items: + return + collected = class_instance.collect(items, graph_builder) + for clazz in collected: + graph_builder.add_edge( + self, + edge_type=EdgeType.default, + id=clazz.id, + clazz=class_instance, + ) + + def post_process(self, graph_builder: GraphBuilder, source: Json) -> None: + if database_id := self.id: + resources_to_collect = [ + ("databases", AzureSqlManagedDatabase), + ("serverTrustGroups", AzureSqlServerTrustGroup), + ] + + for resource_type, resource_class in resources_to_collect: + graph_builder.submit_work( + service_name, + self._collect_items, + graph_builder, + database_id, + resource_type, + resource_class, + ) + + def collect_instance_failover_group() -> None: + rg = self.resource_group_name + subscription_id = self.resource_subscription_id + location = self.location + if not rg or not subscription_id or not location: + return + api_spec = AzureResourceSpec( + service="sql", + version="2021-11-01", + path=f"/subscriptions/{subscription_id}/resourceGroups/{rg}/providers/Microsoft.Sql/locations/{location}/instanceFailoverGroups", + path_parameters=[], + query_parameters=["api-version"], + access_path="value", + expect_array=True, + ) + items = graph_builder.client.list(api_spec) + + AzureSqlInstanceFailoverGroup.collect(items, graph_builder) + + graph_builder.submit_work(service_name, collect_instance_failover_group) + + def connect_in_graph(self, builder: GraphBuilder, source: Json) -> None: + if private_endpoint_connections := self.instance_private_endpoint_connections: + for private_endpoint_connection in private_endpoint_connections: + if endpoint_id := private_endpoint_connection.private_endpoint_id: + builder.add_edge( + self, + edge_type=EdgeType.default, + clazz=AzureSqlPrivateEndpointConnection, + private_endpoint_id=endpoint_id, + ) + if instance_pool_id := self.instance_pool_id: + builder.add_edge( + self, edge_type=EdgeType.default, reverse=True, clazz=AzureSqlInstancePool, id=instance_pool_id + ) + if subnet_id := self.subnet_id: + builder.add_edge(self, edge_type=EdgeType.default, reverse=True, clazz=AzureSubnet, id=subnet_id) + + # principal: collected via ms graph -> create a deferred edge + if mii := self.managed_instance_identity: + if pid := mii.principal_id: + builder.add_deferred_edge( + from_node=self, + to_node=BySearchCriteria(f'is({MicrosoftGraphServicePrincipal.kind}) and reported.id=="{pid}"'), + ) + if uai := mii.user_assigned_identities: + for _, identity_info in uai.items(): + if identity_info and identity_info.principal_id: + builder.add_deferred_edge( + from_node=self, + to_node=BySearchCriteria( + f'is({MicrosoftGraphUser.kind}) and reported.id=="{identity_info.principal_id}"' + ), + ) + + +@define(eq=False, slots=False) +class AzureSqlVirtualCluster(MicrosoftResource): + kind: ClassVar[str] = "azure_sql_virtual_cluster" + api_spec: ClassVar[AzureResourceSpec] = AzureResourceSpec( + service="sql", + version="2022-05-01-preview", + path="/subscriptions/{subscriptionId}/providers/Microsoft.Sql/virtualClusters", + path_parameters=["subscriptionId"], + query_parameters=["api-version"], + access_path="value", + expect_array=True, + ) + reference_kinds: ClassVar[ModelReference] = { + "successors": {"default": ["azure_sql_managed_instance"]}, + "predecessors": {"default": ["azure_subnet"]}, + } + mapping: ClassVar[Dict[str, Bender]] = { + "id": S("id"), + "tags": S("tags", default={}), + "name": S("name"), + "type": S("type"), + "location": S("location"), + "child_resources": S("properties", "childResources"), + "family": S("properties", "family"), + "maintenance_configuration_id": S("properties", "maintenanceConfigurationId"), + "subnet_id": S("properties", "subnetId"), + } + child_resources: Optional[List[str]] = field(default=None, metadata={'description': 'List of resources in this virtual cluster.'}) # fmt: skip + family: Optional[str] = field(default=None, metadata={'description': 'If the service has different generations of hardware, for the same SKU, then that can be captured here.'}) # fmt: skip + maintenance_configuration_id: Optional[str] = field(default=None, metadata={'description': 'Specifies maintenance configuration id to apply to this virtual cluster.'}) # fmt: skip + subnet_id: Optional[str] = field(default=None, metadata={'description': 'Subnet resource ID for the virtual cluster.'}) # fmt: skip + type: Optional[str] = field(default=None, metadata={"description": "Resource type."}) + location: Optional[str] = field(default=None, metadata={"description": "Resource location."}) + + def connect_in_graph(self, builder: GraphBuilder, source: Json) -> None: + if managed_instance_ids := self.child_resources: + for managed_instance_id in managed_instance_ids: + builder.add_edge( + self, edge_type=EdgeType.default, clazz=AzureSqlManagedInstance, id=managed_instance_id + ) + if subnet_id := self.subnet_id: + builder.add_edge(self, edge_type=EdgeType.default, reverse=True, clazz=AzureSubnet, id=subnet_id) + + +@define(eq=False, slots=False) +class AzureSqlServerTrustGroup(MicrosoftResource): + kind: ClassVar[str] = "azure_sql_server_trust_group" + # Collect via AzureSqlManagedInstance() + mapping: ClassVar[Dict[str, Bender]] = { + "id": S("id"), + "tags": S("tags", default={}), + "name": S("name"), + "type": S("type"), + "group_members": S("properties") >> S("groupMembers", default=[]) >> ForallBend(S("serverId")), + "trust_scopes": S("properties", "trustScopes"), + } + group_members: Optional[List[str]] = field(default=None, metadata={'description': 'Group members information for the server trust group.'}) # fmt: skip + trust_scopes: Optional[List[str]] = field(default=None, metadata={'description': 'Trust scope of the server trust group.'}) # fmt: skip + type: Optional[str] = field(default=None, metadata={"description": "Resource type."}) + + +@define(eq=False, slots=False) +class AzureSqlVirtualNetworkRule(MicrosoftResource): + kind: ClassVar[str] = "azure_sql_virtual_network_rule" + # Collect via AzureSqlServer() + mapping: ClassVar[Dict[str, Bender]] = { + "id": S("id"), + "tags": S("tags", default={}), + "name": S("name"), + "type": S("type"), + "ignore_missing_vnet_service_endpoint": S("properties", "ignoreMissingVnetServiceEndpoint"), + "state": S("properties", "state"), + "virtual_network_subnet_id": S("properties", "virtualNetworkSubnetId"), + } + ignore_missing_vnet_service_endpoint: Optional[bool] = field(default=None, metadata={'description': 'Create firewall rule before the virtual network has vnet service endpoint enabled.'}) # fmt: skip + state: Optional[str] = field(default=None, metadata={"description": "Virtual Network Rule State"}) + virtual_network_subnet_id: Optional[str] = field(default=None, metadata={'description': 'The ARM resource id of the virtual network subnet.'}) # fmt: skip + type: Optional[str] = field(default=None, metadata={"description": "Resource type."}) + + +@define(eq=False, slots=False) +class AzureSqlWorkloadGroup(MicrosoftResource): + kind: ClassVar[str] = "azure_sql_workload_group" + # Collect via AzureSqlDatabase() + mapping: ClassVar[Dict[str, Bender]] = { + "id": S("id"), + "tags": S("tags", default={}), + "name": S("name"), + "type": S("type"), + "importance": S("properties", "importance"), + "max_resource_percent": S("properties", "maxResourcePercent"), + "max_resource_percent_per_request": S("properties", "maxResourcePercentPerRequest"), + "min_resource_percent": S("properties", "minResourcePercent"), + "min_resource_percent_per_request": S("properties", "minResourcePercentPerRequest"), + "query_execution_timeout": S("properties", "queryExecutionTimeout"), + } + importance: Optional[str] = field(default=None, metadata={"description": "The workload group importance level."}) + max_resource_percent: Optional[int] = field(default=None, metadata={'description': 'The workload group cap percentage resource.'}) # fmt: skip + max_resource_percent_per_request: Optional[float] = field(default=None, metadata={'description': 'The workload group request maximum grant percentage.'}) # fmt: skip + min_resource_percent: Optional[int] = field(default=None, metadata={'description': 'The workload group minimum percentage resource.'}) # fmt: skip + min_resource_percent_per_request: Optional[float] = field(default=None, metadata={'description': 'The workload group request minimum grant percentage.'}) # fmt: skip + query_execution_timeout: Optional[int] = field(default=None, metadata={'description': 'The workload group query execution timeout.'}) # fmt: skip + type: Optional[str] = field(default=None, metadata={"description": "Resource type."}) + + +@define(eq=False, slots=False) +class AzureRecommendedActionStateInfo: + kind: ClassVar[str] = "azure_recommended_action_state_info" + mapping: ClassVar[Dict[str, Bender]] = { + "action_initiated_by": S("actionInitiatedBy"), + "current_value": S("currentValue"), + "last_modified": S("lastModified"), + } + action_initiated_by: Optional[str] = field(default=None, metadata={'description': 'Gets who initiated the execution of this recommended action. Possible Value are: User -> When user explicity notified system to apply the recommended action. System -> When auto-execute status of this advisor was set to Enabled , in which case the system applied it.'}) # fmt: skip + current_value: Optional[str] = field(default=None, metadata={'description': 'Current state the recommended action is in. Some commonly used states are: Active -> recommended action is active and no action has been taken yet. Pending -> recommended action is approved for and is awaiting execution. Executing -> recommended action is being applied on the user database. Verifying -> recommended action was applied and is being verified of its usefulness by the system. Success -> recommended action was applied and improvement found during verification. Pending Revert -> verification found little or no improvement so recommended action is queued for revert or user has manually reverted. Reverting -> changes made while applying recommended action are being reverted on the user database. Reverted -> successfully reverted the changes made by recommended action on user database. Ignored -> user explicitly ignored/discarded the recommended action. '}) # fmt: skip + last_modified: Optional[datetime] = field(default=None, metadata={'description': 'Gets the time when the state was last modified'}) # fmt: skip + + +@define(eq=False, slots=False) +class AzureRecommendedActionImplementationInfo: + kind: ClassVar[str] = "azure_recommended_action_implementation_info" + mapping: ClassVar[Dict[str, Bender]] = {"method": S("method"), "script": S("script")} + method: Optional[str] = field(default=None, metadata={'description': 'Gets the method in which this recommended action can be manually implemented. e.g., TSql, AzurePowerShell.'}) # fmt: skip + script: Optional[str] = field(default=None, metadata={'description': 'Gets the manual implementation script. e.g., T-SQL script that could be executed on the database.'}) # fmt: skip + + +@define(eq=False, slots=False) +class AzureRecommendedActionErrorInfo: + kind: ClassVar[str] = "azure_recommended_action_error_info" + mapping: ClassVar[Dict[str, Bender]] = {"error_code": S("errorCode"), "is_retryable": S("isRetryable")} + error_code: Optional[str] = field(default=None, metadata={'description': 'Gets the reason why the recommended action was put to error state. e.g., DatabaseHasQdsOff, IndexAlreadyExists'}) # fmt: skip + is_retryable: Optional[str] = field(default=None, metadata={'description': 'Gets whether the error could be ignored and recommended action could be retried. Possible values are: Yes/No'}) # fmt: skip + + +@define(eq=False, slots=False) +class AzureRecommendedActionImpactRecord: + kind: ClassVar[str] = "azure_recommended_action_impact_record" + mapping: ClassVar[Dict[str, Bender]] = { + "absolute_value": S("absoluteValue"), + "change_value_absolute": S("changeValueAbsolute"), + "change_value_relative": S("changeValueRelative"), + "dimension_name": S("dimensionName"), + "unit": S("unit"), + } + absolute_value: Optional[float] = field(default=None, metadata={'description': 'Gets the absolute value of this dimension if applicable. e.g., Number of Queries affected'}) # fmt: skip + change_value_absolute: Optional[float] = field(default=None, metadata={'description': 'Gets the absolute change in the value of this dimension. e.g., Absolute Disk space change in Megabytes'}) # fmt: skip + change_value_relative: Optional[float] = field(default=None, metadata={'description': 'Gets the relative change in the value of this dimension. e.g., Relative Disk space change in Percentage'}) # fmt: skip + dimension_name: Optional[str] = field(default=None, metadata={'description': 'Gets the name of the impact dimension. e.g., CPUChange, DiskSpaceChange, NumberOfQueriesAffected.'}) # fmt: skip + unit: Optional[str] = field(default=None, metadata={'description': 'Gets the name of the impact dimension. e.g., CPUChange, DiskSpaceChange, NumberOfQueriesAffected.'}) # fmt: skip + + +@define(eq=False, slots=False) +class AzureRecommendedActionMetricInfo: + kind: ClassVar[str] = "azure_recommended_action_metric_info" + mapping: ClassVar[Dict[str, Bender]] = { + "metric_name": S("metricName"), + "start_time": S("startTime"), + "time_grain": S("timeGrain"), + "unit": S("unit"), + "value": S("value"), + } + metric_name: Optional[str] = field(default=None, metadata={'description': 'Gets the name of the metric. e.g., CPU, Number of Queries.'}) # fmt: skip + start_time: Optional[datetime] = field(default=None, metadata={'description': 'Gets the start time of time interval given by this MetricInfo.'}) # fmt: skip + time_grain: Optional[str] = field(default=None, metadata={'description': 'Gets the duration of time interval for the value given by this MetricInfo. e.g., PT1H (1 hour)'}) # fmt: skip + unit: Optional[str] = field(default=None, metadata={'description': 'Gets the unit in which metric is measured. e.g., DTU, Frequency'}) # fmt: skip + value: Optional[float] = field(default=None, metadata={'description': 'Gets the value of the metric in the time interval given by this MetricInfo.'}) # fmt: skip + + +@define(eq=False, slots=False) +class AzureRecommendedAction: + kind: ClassVar[str] = "azure_recommended_action" + mapping: ClassVar[Dict[str, Bender]] = { + "id": S("id"), + "name": S("name"), + "type": S("type"), + "recommended_action_details": S("properties", "details"), + "error_details": S("properties", "errorDetails") >> Bend(AzureRecommendedActionErrorInfo.mapping), + "estimated_impact": S("properties", "estimatedImpact") + >> ForallBend(AzureRecommendedActionImpactRecord.mapping), + "execute_action_duration": S("properties", "executeActionDuration"), + "execute_action_initiated_by": S("properties", "executeActionInitiatedBy"), + "execute_action_initiated_time": S("properties", "executeActionInitiatedTime"), + "execute_action_start_time": S("properties", "executeActionStartTime"), + "implementation_details": S("properties", "implementationDetails") + >> Bend(AzureRecommendedActionImplementationInfo.mapping), + "is_archived_action": S("properties", "isArchivedAction"), + "is_executable_action": S("properties", "isExecutableAction"), + "is_revertable_action": S("properties", "isRevertableAction"), + "recommended_action_kind": S("kind"), + "last_refresh": S("properties", "lastRefresh"), + "linked_objects": S("properties", "linkedObjects"), + "observed_impact": S("properties", "observedImpact") >> ForallBend(AzureRecommendedActionImpactRecord.mapping), + "recommendation_reason": S("properties", "recommendationReason"), + "revert_action_duration": S("properties", "revertActionDuration"), + "revert_action_initiated_by": S("properties", "revertActionInitiatedBy"), + "revert_action_initiated_time": S("properties", "revertActionInitiatedTime"), + "revert_action_start_time": S("properties", "revertActionStartTime"), + "score": S("properties", "score"), + "state": S("properties", "state") >> Bend(AzureRecommendedActionStateInfo.mapping), + "time_series": S("properties", "timeSeries") >> ForallBend(AzureRecommendedActionMetricInfo.mapping), + "valid_since": S("properties", "validSince"), + } + recommended_action_details: Optional[Dict[str, Any]] = field(default=None, metadata={'description': 'Gets additional details specific to this recommended action.'}) # fmt: skip + error_details: Optional[AzureRecommendedActionErrorInfo] = field(default=None, metadata={'description': 'Contains error information for an Azure SQL Database, Server or Elastic Pool Recommended Action.'}) # fmt: skip + estimated_impact: Optional[List[AzureRecommendedActionImpactRecord]] = field(default=None, metadata={'description': 'Gets the estimated impact info for this recommended action e.g., Estimated CPU gain, Estimated Disk Space change'}) # fmt: skip + execute_action_duration: Optional[str] = field(default=None, metadata={'description': 'Gets the time taken for applying this recommended action on user resource. e.g., time taken for index creation'}) # fmt: skip + execute_action_initiated_by: Optional[str] = field(default=None, metadata={'description': 'Gets if approval for applying this recommended action was given by user/system.'}) # fmt: skip + execute_action_initiated_time: Optional[datetime] = field(default=None, metadata={'description': 'Gets the time when this recommended action was approved for execution.'}) # fmt: skip + execute_action_start_time: Optional[datetime] = field(default=None, metadata={'description': 'Gets the time when system started applying this recommended action on the user resource. e.g., index creation start time'}) # fmt: skip + implementation_details: Optional[AzureRecommendedActionImplementationInfo] = field(default=None, metadata={'description': 'Contains information for manual implementation for an Azure SQL Database, Server or Elastic Pool Recommended Action.'}) # fmt: skip + is_archived_action: Optional[bool] = field(default=None, metadata={'description': 'Gets if this recommended action was suggested some time ago but user chose to ignore this and system added a new recommended action again.'}) # fmt: skip + is_executable_action: Optional[bool] = field(default=None, metadata={'description': 'Gets if this recommended action is actionable by user'}) # fmt: skip + is_revertable_action: Optional[bool] = field(default=None, metadata={'description': 'Gets if changes applied by this recommended action can be reverted by user'}) # fmt: skip + recommended_action_kind: Optional[str] = field(default=None, metadata={"description": "Resource kind."}) + last_refresh: Optional[datetime] = field(default=None, metadata={'description': 'Gets time when this recommended action was last refreshed.'}) # fmt: skip + linked_objects: Optional[List[str]] = field(default=None, metadata={"description": "Gets the linked objects, if any."}) # fmt: skip + observed_impact: Optional[List[AzureRecommendedActionImpactRecord]] = field(default=None, metadata={'description': 'Gets the observed/actual impact info for this recommended action e.g., Actual CPU gain, Actual Disk Space change'}) # fmt: skip + recommendation_reason: Optional[str] = field(default=None, metadata={'description': 'Gets the reason for recommending this action. e.g., DuplicateIndex'}) # fmt: skip + revert_action_duration: Optional[str] = field(default=None, metadata={'description': 'Gets the time taken for reverting changes of this recommended action on user resource. e.g., time taken for dropping the created index.'}) # fmt: skip + revert_action_initiated_by: Optional[str] = field(default=None, metadata={'description': 'Gets if approval for reverting this recommended action was given by user/system.'}) # fmt: skip + revert_action_initiated_time: Optional[datetime] = field(default=None, metadata={'description': 'Gets the time when this recommended action was approved for revert.'}) # fmt: skip + revert_action_start_time: Optional[datetime] = field(default=None, metadata={'description': 'Gets the time when system started reverting changes of this recommended action on user resource. e.g., time when index drop is executed.'}) # fmt: skip + score: Optional[int] = field(default=None, metadata={'description': 'Gets the impact of this recommended action. Possible values are 1 - Low impact, 2 - Medium Impact and 3 - High Impact'}) # fmt: skip + state: Optional[AzureRecommendedActionStateInfo] = field(default=None, metadata={'description': 'Contains information of current state for an Azure SQL Database, Server or Elastic Pool Recommended Action.'}) # fmt: skip + time_series: Optional[List[AzureRecommendedActionMetricInfo]] = field(default=None, metadata={'description': 'Gets the time series info of metrics for this recommended action e.g., CPU consumption time series'}) # fmt: skip + valid_since: Optional[datetime] = field(default=None, metadata={'description': 'Gets the time since when this recommended action is valid.'}) # fmt: skip + type: Optional[str] = field(default=None, metadata={"description": "Resource type."}) + id: Optional[str] = field(default=None, metadata={"description": "Resource ID."}) + name: Optional[str] = field(default=None, metadata={"description": "Resource name."}) + + +@define(eq=False, slots=False) +class AzureSqlAdvisor(MicrosoftResource): + kind: ClassVar[str] = "azure_sql_advisor" + # Collect via AzureSqlServer() and AzureSqlDatabase() + mapping: ClassVar[Dict[str, Bender]] = { + "id": S("id"), + "tags": S("tags", default={}), + "name": S("name"), + "type": S("type"), + "advisor_status": S("properties", "advisorStatus"), + "auto_execute_status": S("properties", "autoExecuteStatus"), + "auto_execute_status_inherited_from": S("properties", "autoExecuteStatusInheritedFrom"), + "advisor_kind": S("kind"), + "last_checked": S("properties", "lastChecked"), + "recommendations_status": S("properties", "recommendationsStatus"), + "recommended_actions": S("properties", "recommendedActions") >> ForallBend(AzureRecommendedAction.mapping), + } + advisor_status: Optional[str] = field(default=None, metadata={'description': 'Gets the status of availability of this advisor to customers. Possible values are GA , PublicPreview , LimitedPublicPreview and PrivatePreview .'}) # fmt: skip + auto_execute_status: Optional[str] = field(default=None, metadata={'description': 'Gets the auto-execute status (whether to let the system execute the recommendations) of this advisor. Possible values are Enabled and Disabled '}) # fmt: skip + auto_execute_status_inherited_from: Optional[str] = field(default=None, metadata={'description': 'Gets the resource from which current value of auto-execute status is inherited. Auto-execute status can be set on (and inherited from) different levels in the resource hierarchy. Possible values are Subscription , Server , ElasticPool , Database and Default (when status is not explicitly set on any level).'}) # fmt: skip + advisor_kind: Optional[str] = field(default=None, metadata={"description": "Resource kind."}) + last_checked: Optional[datetime] = field(default=None, metadata={'description': 'Gets the time when the current resource was analyzed for recommendations by this advisor.'}) # fmt: skip + recommendations_status: Optional[str] = field(default=None, metadata={'description': 'Gets that status of recommendations for this advisor and reason for not having any recommendations. Possible values include, but are not limited to, Ok (Recommendations available),LowActivity (not enough workload to analyze), DbSeemsTuned (Database is doing well), etc.'}) # fmt: skip + recommended_actions: Optional[List[AzureRecommendedAction]] = field(default=None, metadata={'description': 'Gets the recommended actions for this advisor.'}) # fmt: skip + type: Optional[str] = field(default=None, metadata={"description": "Resource type."}) + + +@define(eq=False, slots=False) +class AzureServerPrivateEndpointConnection: + kind: ClassVar[str] = "azure_server_private_endpoint_connection" + mapping: ClassVar[Dict[str, Bender]] = { + "group_ids": S("properties", "groupIds"), + "id": S("id"), + "private_endpoint": S("properties", "privateEndpoint", "id"), + "private_link_service_connection_state": S("properties", "privateLinkServiceConnectionState") + >> Bend(AzurePrivateLinkServiceConnectionStateProperty.mapping), + "provisioning_state": S("properties", "provisioningState"), + } + group_ids: Optional[List[str]] = field(default=None, metadata={"description": "Group IDs."}) + id: Optional[str] = field(default=None, metadata={"description": "Resource ID."}) + private_endpoint: Optional[str] = field(default=None, metadata={"description": ""}) + private_link_service_connection_state: Optional[AzurePrivateLinkServiceConnectionStateProperty] = field(default=None, metadata={'description': ''}) # fmt: skip + provisioning_state: Optional[str] = field(default=None, metadata={'description': 'State of the private endpoint connection.'}) # fmt: skip + + +@define(eq=False, slots=False) +class AzureServerExternalAdministrator: + kind: ClassVar[str] = "azure_server_external_administrator" + mapping: ClassVar[Dict[str, Bender]] = { + "administrator_type": S("administratorType"), + "azure_ad_only_authentication": S("azureADOnlyAuthentication"), + "login": S("login"), + "principal_type": S("principalType"), + "sid": S("sid"), + "tenant_id": S("tenantId"), + } + administrator_type: Optional[str] = field(default=None, metadata={'description': 'Type of the sever administrator.'}) # fmt: skip + azure_ad_only_authentication: Optional[bool] = field(default=None, metadata={'description': 'Azure Active Directory only Authentication enabled.'}) # fmt: skip + login: Optional[str] = field(default=None, metadata={"description": "Login name of the server administrator."}) + principal_type: Optional[str] = field(default=None, metadata={'description': 'Principal Type of the sever administrator.'}) # fmt: skip + sid: Optional[str] = field(default=None, metadata={"description": "SID (object ID) of the server administrator."}) + tenant_id: Optional[str] = field(default=None, metadata={"description": "Tenant ID of the administrator."}) + + +@define(eq=False, slots=False) +class AzureSqlServer(MicrosoftResource): + kind: ClassVar[str] = "azure_sql_server" + api_spec: ClassVar[AzureResourceSpec] = AzureResourceSpec( + service="sql", + version="2021-11-01", + path="/subscriptions/{subscriptionId}/providers/Microsoft.Sql/servers", + path_parameters=["subscriptionId"], + query_parameters=["api-version"], + access_path="value", + expect_array=True, + ) + reference_kinds: ClassVar[ModelReference] = { + "successors": { + "default": [ + "azure_sql_database", + "azure_sql_elastic_pool", + "azure_sql_private_endpoint_connection", + "azure_sql_failover_group", + "azure_sql_firewall_rule", + "azure_sql_job_agent", + "azure_sql_virtual_network_rule", + "azure_sql_advisor", + "microsoft_graph_service_principal", + "microsoft_graph_user", + ] + }, + } + mapping: ClassVar[Dict[str, Bender]] = { + "id": S("id"), + "tags": S("tags", default={}), + "name": S("name"), + "type": S("type"), + "location": S("location"), + "administrator_login": S("properties", "administratorLogin"), + "administrator_login_password": S("properties", "administratorLoginPassword"), + "server_administrators": S("properties", "administrators") >> Bend(AzureServerExternalAdministrator.mapping), + "federated_client_id": S("properties", "federatedClientId"), + "fully_qualified_domain_name": S("properties", "fullyQualifiedDomainName"), + "server_identity": S("identity") >> Bend(AzureResourceIdentity.mapping), + "key_id": S("properties", "keyId"), + "server_kind": S("kind"), + "minimal_tls_version": S("properties", "minimalTlsVersion"), + "primary_user_assigned_identity_id": S("properties", "primaryUserAssignedIdentityId"), + "server_private_endpoint_connections": S("properties", "privateEndpointConnections") + >> ForallBend(AzureServerPrivateEndpointConnection.mapping), + "public_network_access": S("properties", "publicNetworkAccess"), + "restrict_outbound_network_access": S("properties", "restrictOutboundNetworkAccess"), + "state": S("properties", "state"), + "version": S("properties", "version"), + "workspace_feature": S("properties", "workspaceFeature"), + } + administrator_login: Optional[str] = field(default=None, metadata={'description': 'Administrator username for the server. Once created it cannot be changed.'}) # fmt: skip + administrator_login_password: Optional[str] = field(default=None, metadata={'description': 'The administrator login password (required for server creation).'}) # fmt: skip + server_administrators: Optional[AzureServerExternalAdministrator] = field(default=None, metadata={'description': 'Properties of a active directory administrator.'}) # fmt: skip + federated_client_id: Optional[str] = field(default=None, metadata={'description': 'The Client id used for cross tenant CMK scenario'}) # fmt: skip + fully_qualified_domain_name: Optional[str] = field(default=None, metadata={'description': 'The fully qualified domain name of the server.'}) # fmt: skip + server_identity: Optional[AzureResourceIdentity] = field(default=None, metadata={'description': 'Azure Active Directory identity configuration for a resource.'}) # fmt: skip + key_id: Optional[str] = field(default=None, metadata={'description': 'A CMK URI of the key to use for encryption.'}) # fmt: skip + server_kind: Optional[str] = field(default=None, metadata={'description': 'Kind of sql server. This is metadata used for the Azure portal experience.'}) # fmt: skip + minimal_tls_version: Optional[str] = field(default=None, metadata={'description': 'Minimal TLS version. Allowed values: 1.0 , 1.1 , 1.2 '}) # fmt: skip + primary_user_assigned_identity_id: Optional[str] = field(default=None, metadata={'description': 'The resource id of a user assigned identity to be used by default.'}) # fmt: skip + server_private_endpoint_connections: Optional[List[AzureServerPrivateEndpointConnection]] = field(default=None, metadata={'description': 'List of private endpoint connections on a server'}) # fmt: skip + public_network_access: Optional[str] = field(default=None, metadata={'description': 'Whether or not public endpoint access is allowed for this server. Value is optional but if passed in, must be Enabled or Disabled '}) # fmt: skip + restrict_outbound_network_access: Optional[str] = field(default=None, metadata={'description': 'Whether or not to restrict outbound network access for this server. Value is optional but if passed in, must be Enabled or Disabled '}) # fmt: skip + state: Optional[str] = field(default=None, metadata={"description": "The state of the server."}) + version: Optional[str] = field(default=None, metadata={"description": "The version of the server."}) + workspace_feature: Optional[str] = field(default=None, metadata={'description': 'Whether or not existing server has a workspace created and if it allows connection from workspace'}) # fmt: skip + type: Optional[str] = field(default=None, metadata={"description": "Resource type."}) + location: Optional[str] = field(default=None, metadata={"description": "Resource location."}) + + def _collect_items( + self, + graph_builder: GraphBuilder, + server_id: str, + resource_type: str, + class_instance: MicrosoftResource, + ) -> None: + path = f"{server_id}/{resource_type}" + api_spec = AzureResourceSpec( + service="sql", + version="2021-11-01", + path=path, + path_parameters=[], + query_parameters=["api-version"], + access_path="value", + expect_array=True, + ) + items = graph_builder.client.list(api_spec) + if not items: + return + collected = class_instance.collect(items, graph_builder) + for clazz in collected: + graph_builder.add_edge( + self, + edge_type=EdgeType.default, + id=clazz.id, + clazz=class_instance, + ) + + def post_process(self, graph_builder: GraphBuilder, source: Json) -> None: + if server_id := self.id: + resources_to_collect = [ + ("databases", AzureSqlDatabase), + ("elasticPools", AzureSqlElasticPool), + ("privateEndpointConnections", AzureSqlPrivateEndpointConnection), + ("failoverGroups", AzureSqlFailoverGroup), + ("firewallRules", AzureSqlFirewallRule), + ("jobAgents", AzureSqlJobAgent), + ("virtualNetworkRules", AzureSqlVirtualNetworkRule), + ("advisors?$expand=recommendedActions", AzureSqlAdvisor), + ] + + for resource_type, resource_class in resources_to_collect: + graph_builder.submit_work( + service_name, + self._collect_items, + graph_builder, + server_id, + resource_type, + resource_class, + ) + + def connect_in_graph(self, builder: GraphBuilder, source: Json) -> None: + # principal: collected via ms graph -> create a deferred edge + if si := self.server_identity: + if pid := si.principal_id: + builder.add_deferred_edge( + from_node=self, + to_node=BySearchCriteria(f'is({MicrosoftGraphServicePrincipal.kind}) and reported.id=="{pid}"'), + ) + if uai := si.user_assigned_identities: + for _, identity_info in uai.items(): + if identity_info and identity_info.principal_id: + builder.add_deferred_edge( + from_node=self, + to_node=BySearchCriteria( + f'is({MicrosoftGraphUser.kind}) and reported.id=="{identity_info.principal_id}"' + ), + ) + + +resources: List[Type[MicrosoftResource]] = [ + AzureSqlDatabase, + AzureSqlElasticPool, + AzureSqlPrivateEndpointConnection, + AzureSqlFailoverGroup, + AzureSqlFirewallRule, + AzureSqlGeoBackupPolicy, + AzureSqlInstanceFailoverGroup, + AzureSqlInstancePool, + AzureSqlJobAgent, + AzureSqlManagedDatabase, + AzureSqlManagedInstance, + AzureSqlServer, + AzureSqlVirtualCluster, + AzureSqlServerTrustGroup, + AzureSqlVirtualNetworkRule, + AzureSqlWorkloadGroup, + AzureSqlAdvisor, +] diff --git a/plugins/azure/test/collector_test.py b/plugins/azure/test/collector_test.py index 8d969d83b2..9a52b40471 100644 --- a/plugins/azure/test/collector_test.py +++ b/plugins/azure/test/collector_test.py @@ -49,8 +49,8 @@ def test_collect( config, Cloud(id="azure"), azure_subscription, credentials, core_feedback ) subscription_collector.collect() - assert len(subscription_collector.graph.nodes) == 322 - assert len(subscription_collector.graph.edges) == 471 + assert len(subscription_collector.graph.nodes) == 372 + assert len(subscription_collector.graph.edges) == 558 graph_collector = MicrosoftGraphOrganizationCollector( config, Cloud(id="azure"), MicrosoftGraphOrganization(id="test", name="test"), credentials, core_feedback diff --git a/plugins/azure/test/files/sql/advisors.json b/plugins/azure/test/files/sql/advisors.json new file mode 100644 index 0000000000..6812e109ea --- /dev/null +++ b/plugins/azure/test/files/sql/advisors.json @@ -0,0 +1,169 @@ +{ + "value": [ + { + "id": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/workloadinsight-demos/providers/Microsoft.Sql/servers/misosisvr/advisors/CreateIndex", + "name": "CreateIndex", + "type": "Microsoft.Sql/servers/advisors", + "location": "East Asia", + "kind": "", + "properties": { + "advisorStatus": "GA", + "autoExecuteStatus": "Disabled", + "autoExecuteStatusInheritedFrom": "Server", + "recommendedActions": [ + { + "id": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/workloadinsight-demos/providers/Microsoft.Sql/servers/misosisvr/advisors/CreateIndex/recommendedActions/IR_[dbo]_[DataPoints]_F5D2F347AA22DB46E4CC", + "name": "IR_[dbo]_[DataPoints]_F5D2F347AA22DB46E4CC", + "type": "Microsoft.Sql/servers/advisors/recommendedActions", + "location": "East Asia", + "kind": "", + "properties": { + "recommendationReason": "", + "validSince": "2017-03-01T14:38:05Z", + "lastRefresh": "2017-03-01T14:38:05Z", + "state": { + "currentValue": "Success", + "actionInitiatedBy": "User", + "lastModified": "2017-03-01T14:38:05Z" + }, + "isExecutableAction": true, + "isRevertableAction": true, + "isArchivedAction": false, + "executeActionStartTime": "2017-03-01T14:38:05Z", + "executeActionDuration": "PT1M", + "executeActionInitiatedBy": "User", + "executeActionInitiatedTime": "2017-03-01T14:38:05Z", + "score": 3, + "implementationDetails": { + "method": "TSql", + "script": "DROP INDEX [nci_wi_DataPoints_609E4B7D6A3813990ED44B28B340C8FC] ON [dbo].[DataPoints]" + }, + "errorDetails": {}, + "estimatedImpact": [ + { + "dimensionName": "ActionDuration", + "unit": "Seconds", + "absoluteValue": 5040 + }, + { + "dimensionName": "SpaceChange", + "unit": "Megabytes", + "absoluteValue": 120 + } + ], + "observedImpact": [ + { + "dimensionName": "AffectedQueriesCpuUtilization", + "unit": "CpuCores", + "changeValueAbsolute": -12.7, + "changeValueRelative": -0.9 + }, + { + "dimensionName": "CpuUtilization", + "unit": "CpuCores", + "changeValueAbsolute": -12.7, + "changeValueRelative": -0.3175 + }, + { + "dimensionName": "QueriesWithImprovedPerformance", + "unit": "Count", + "absoluteValue": 12 + }, + { + "dimensionName": "QueriesWithRegressedPerformance", + "unit": "Count", + "absoluteValue": 1 + }, + { + "dimensionName": "SpaceChange", + "unit": "Megabytes", + "absoluteValue": 130.742187 + }, + { + "dimensionName": "VerificationProgress", + "unit": "Percent", + "absoluteValue": 0 + } + ], + "timeSeries": [], + "details": { + "indexName": "nci_wi_DataPoints_609E4B7D6A3813990ED44B28B340C8FC", + "indexType": "NONCLUSTERED", + "schema": "[dbo]", + "table": "[DataPoints]", + "indexColumns": "[Name],[Money]", + "includedColumns": "[Power],[Pineapple]", + "indexActionStartTime": "2017-03-01T14:38:05.337", + "indexActionDuration": "00:01:00", + "databaseName": "IndexAdvisor_test_3" + } + } + } + ] + } + }, + { + "id": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/workloadinsight-demos/providers/Microsoft.Sql/servers/misosisvr/databases/IndexAdvisor_test_3/advisors/CreateIndex", + "name": "CreateIndex", + "type": "Microsoft.Sql/servers/databases/advisors", + "location": "East Asia", + "kind": "", + "properties": { + "advisorStatus": "GA", + "autoExecuteStatus": "Disabled", + "autoExecuteStatusInheritedFrom": "Database", + "recommendationsStatus": "Ok", + "lastChecked": "2017-06-20T16:39:16Z", + "recommendedActions": [ + { + "id": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/workloadinsight-demos/providers/Microsoft.Sql/servers/misosisvr/databases/IndexAdvisor_test_3/advisors/CreateIndex/recommendedActions/IR_[dbo]_[Employees]_560E15A98D14CA09BDFB", + "name": "IR_[dbo]_[Employees]_560E15A98D14CA09BDFB", + "type": "Microsoft.Sql/servers/databases/advisors/recommendedActions", + "location": "East Asia", + "kind": "", + "properties": { + "recommendationReason": "", + "validSince": "2017-03-01T14:38:05Z", + "lastRefresh": "2017-03-01T14:38:05Z", + "state": { + "currentValue": "Active", + "lastModified": "2017-03-01T14:38:05Z" + }, + "isExecutableAction": true, + "isRevertableAction": true, + "isArchivedAction": false, + "score": 3, + "implementationDetails": { + "method": "TSql", + "script": "CREATE NONCLUSTERED INDEX [nci_wi_Employees_8C18C2AF4267DC77793040782641CCDE] ON [dbo].[Employees] ([City], [State]) INCLUDE ([Postal]) WITH (ONLINE = ON)" + }, + "errorDetails": {}, + "estimatedImpact": [ + { + "dimensionName": "ActionDuration", + "unit": "Seconds", + "absoluteValue": 17 + }, + { + "dimensionName": "SpaceChange", + "unit": "Megabytes", + "absoluteValue": 128 + } + ], + "observedImpact": [], + "timeSeries": [], + "details": { + "indexName": "nci_wi_Employees_8C18C2AF4267DC77793040782641CCDE", + "indexType": "NONCLUSTERED", + "schema": "[dbo]", + "table": "[Employees]", + "indexColumns": "[City], [State]", + "includedColumns": "[Postal]" + } + } + } + ] + } + } + ] +} \ No newline at end of file diff --git a/plugins/azure/test/files/sql/databases.json b/plugins/azure/test/files/sql/databases.json new file mode 100644 index 0000000000..78237eb930 --- /dev/null +++ b/plugins/azure/test/files/sql/databases.json @@ -0,0 +1,75 @@ +{ + "value": [ + { + "sku": { + "name": "BC_Gen4", + "tier": "BusinessCritical", + "capacity": 2 + }, + "kind": "v12.0,user,vcore", + "properties": { + "collation": "SQL_Latin1_General_CP1_CI_AS", + "maxSizeBytes": 268435456000, + "status": "Online", + "databaseId": "6c764297-577b-470f-9af4-96d3d41e2ba3", + "creationDate": "2017-06-07T04:41:33.937Z", + "currentServiceObjectiveName": "BC_Gen4_2", + "requestedServiceObjectiveName": "BC_Gen4_2", + "defaultSecondaryLocation": "North Europe", + "catalogCollation": "SQL_Latin1_General_CP1_CI_AS", + "licenseType": "LicenseIncluded", + "maxLogSizeBytes": 104857600, + "isInfraEncryptionEnabled": false, + "zoneRedundant": false, + "readScale": "Enabled", + "earliestRestoreDate": "2017-06-07T04:51:33.937Z", + "currentSku": { + "name": "BC_Gen4", + "tier": "BusinessCritical", + "capacity": 2 + }, + "currentBackupStorageRedundancy": "Zone", + "requestedBackupStorageRedundancy": "Zone", + "isLedgerOn": false + }, + "location": "southeastasia", + "id": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/Default-SQL-SouthEastAsia/providers/Microsoft.Sql/servers/testsvr/databases/testdb", + "name": "testdb", + "type": "Microsoft.Sql/servers/databases" + }, + { + "sku": { + "name": "System0", + "tier": "System", + "capacity": 0 + }, + "kind": "v12.0,system", + "properties": { + "collation": "SQL_Latin1_General_CP1_CI_AS", + "maxSizeBytes": 32212254720, + "status": "Online", + "databaseId": "e6be351f-2cc9-4604-9e52-b0b28b2710b0", + "creationDate": "2017-06-07T04:23:42.537Z", + "currentServiceObjectiveName": "System0", + "requestedServiceObjectiveName": "System0", + "defaultSecondaryLocation": "North Europe", + "catalogCollation": "SQL_Latin1_General_CP1_CI_AS", + "isInfraEncryptionEnabled": false, + "zoneRedundant": false, + "readScale": "Disabled", + "currentSku": { + "name": "System0", + "tier": "System", + "capacity": 0 + }, + "currentBackupStorageRedundancy": "Local", + "requestedBackupStorageRedundancy": "Local", + "isLedgerOn": false + }, + "location": "southeastasia", + "id": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/Default-SQL-SouthEastAsia/providers/Microsoft.Sql/servers/testsvr/databases/master", + "name": "master", + "type": "Microsoft.Sql/servers/databases" + } + ] +} \ No newline at end of file diff --git a/plugins/azure/test/files/sql/elasticPools.json b/plugins/azure/test/files/sql/elasticPools.json new file mode 100644 index 0000000000..787ddf30e7 --- /dev/null +++ b/plugins/azure/test/files/sql/elasticPools.json @@ -0,0 +1,69 @@ +{ + "value": [ + { + "id": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/sqlcrudtest-2369/providers/Microsoft.Sql/servers/sqlcrudtest-8069/elasticPools/sqlcrudtest-2729", + "name": "sqlcrudtest-2729", + "type": "Microsoft.Sql/servers/elasticPools", + "location": "Japan East", + "kind": null, + "sku": { + "name": "GP_Gen4_2", + "tier": "GeneralPurpose", + "capacity": 2 + }, + "properties": { + "creationDate": "2017-02-10T01:27:21.32Z", + "state": "Ready", + "maxSizeBytes": 5242880000, + "perDatabaseSettings": { + "minCapacity": 0.25, + "maxCapacity": 1 + }, + "zoneRedundant": true, + "licenseType": "LicenseIncluded" + } + }, + { + "id": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/sqlcrudtest-2369/providers/Microsoft.Sql/servers/sqlcrudtest-8069/elasticPools/sqlcrudtest-3191", + "name": "sqlcrudtest-3191", + "type": "Microsoft.Sql/servers/elasticPools", + "location": "Japan East", + "kind": null, + "sku": { + "name": "BasicPool", + "tier": "Basic", + "capacity": 50 + }, + "properties": { + "creationDate": "2017-02-10T01:26:26.45Z", + "state": "Ready", + "maxSizeBytes": 5242880000, + "perDatabaseSettings": { + "minCapacity": 0, + "maxCapacity": 5 + } + } + }, + { + "id": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/sqlcrudtest-2369/providers/Microsoft.Sql/servers/sqlcrudtest-8069/elasticPools/sqlcrudtest-8102", + "name": "sqlcrudtest-8102", + "type": "Microsoft.Sql/servers/elasticPools", + "location": "Japan East", + "kind": null, + "sku": { + "name": "BasicPool", + "tier": "Basic", + "capacity": 50 + }, + "properties": { + "creationDate": "2017-02-10T01:25:25.033Z", + "state": "Ready", + "maxSizeBytes": 5242880000, + "perDatabaseSettings": { + "minCapacity": 0, + "maxCapacity": 5 + } + } + } + ] +} \ No newline at end of file diff --git a/plugins/azure/test/files/sql/failoverGroups.json b/plugins/azure/test/files/sql/failoverGroups.json new file mode 100644 index 0000000000..dd46096b60 --- /dev/null +++ b/plugins/azure/test/files/sql/failoverGroups.json @@ -0,0 +1,54 @@ +{ + "value": [ + { + "id": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/Default/providers/Microsoft.Sql/servers/failover-group-primary-server/failoverGroups/failover-group-test", + "name": "failover-group-test", + "type": "Microsoft.Sql/servers/failoverGroups", + "location": "Japan East", + "properties": { + "readWriteEndpoint": { + "failoverPolicy": "Automatic", + "failoverWithDataLossGracePeriodMinutes": 480 + }, + "readOnlyEndpoint": { + "failoverPolicy": "Disabled" + }, + "replicationRole": "Primary", + "replicationState": "CATCH_UP", + "partnerServers": [ + { + "id": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/Default/providers/Microsoft.Sql/servers/failover-group-secondary-server", + "location": "Japan West", + "replicationRole": "Secondary" + } + ], + "databases": [] + } + }, + { + "id": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/Default/providers/Microsoft.Sql/servers/failover-group-primary-server/failoverGroups/failover-group-test-2", + "name": "failover-group-test-2", + "type": "Microsoft.Sql/servers/failoverGroups", + "location": "Japan East", + "properties": { + "readWriteEndpoint": { + "failoverPolicy": "Automatic", + "failoverWithDataLossGracePeriodMinutes": 480 + }, + "readOnlyEndpoint": { + "failoverPolicy": "Disabled" + }, + "replicationRole": "Primary", + "replicationState": "CATCH_UP", + "partnerServers": [ + { + "id": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/Default/providers/Microsoft.Sql/servers/failover-group-secondary-server", + "location": "Japan West", + "replicationRole": "Secondary" + } + ], + "databases": [] + } + } + ] +} \ No newline at end of file diff --git a/plugins/azure/test/files/sql/firewallRules.json b/plugins/azure/test/files/sql/firewallRules.json new file mode 100644 index 0000000000..e5629f5408 --- /dev/null +++ b/plugins/azure/test/files/sql/firewallRules.json @@ -0,0 +1,40 @@ +{ + "value": [ + { + "id": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/firewallrulecrudtest-12/providers/Microsoft.Sql/servers/firewallrulecrudtest-6285/firewallRules/firewallrulecrudtest-2304", + "name": "firewallrulecrudtest-2304", + "type": "Microsoft.Sql/servers/firewallRules", + "properties": { + "startIpAddress": "0.0.0.0", + "endIpAddress": "0.0.0.0" + } + }, + { + "id": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/firewallrulecrudtest-12/providers/Microsoft.Sql/servers/firewallrulecrudtest-6285/firewallRules/firewallrulecrudtest-3927", + "name": "firewallrulecrudtest-3927", + "type": "Microsoft.Sql/servers/firewallRules", + "properties": { + "startIpAddress": "0.0.0.1", + "endIpAddress": "0.0.0.1" + } + }, + { + "id": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/firewallrulecrudtest-12/providers/Microsoft.Sql/servers/firewallrulecrudtest-6285/firewallRules/firewallrulecrudtest-5370", + "name": "firewallrulecrudtest-5370", + "type": "Microsoft.Sql/servers/firewallRules", + "properties": { + "startIpAddress": "0.0.0.3", + "endIpAddress": "0.0.0.3" + } + }, + { + "id": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/firewallrulecrudtest-12/providers/Microsoft.Sql/servers/firewallrulecrudtest-6285/firewallRules/firewallrulecrudtest-5767", + "name": "firewallrulecrudtest-5767", + "type": "Microsoft.Sql/servers/firewallRules", + "properties": { + "startIpAddress": "0.0.0.2", + "endIpAddress": "0.0.0.2" + } + } + ] +} \ No newline at end of file diff --git a/plugins/azure/test/files/sql/geoBackupPolicies.json b/plugins/azure/test/files/sql/geoBackupPolicies.json new file mode 100644 index 0000000000..a55ccbdc31 --- /dev/null +++ b/plugins/azure/test/files/sql/geoBackupPolicies.json @@ -0,0 +1,15 @@ +{ + "value": [ + { + "id": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/sqlcrudtest-4799/providers/Microsoft.Sql/servers/sqlcrudtest-5961/databases/testdw/geoBackupPolicies/Default", + "name": "Default", + "type": "Microsoft.Sql/servers/databases/geoBackupPolicies", + "location": "Central US", + "kind": null, + "properties": { + "state": "Enabled", + "storageType": "Premium" + } + } + ] +} \ No newline at end of file diff --git a/plugins/azure/test/files/sql/instanceFailoverGroups.json b/plugins/azure/test/files/sql/instanceFailoverGroups.json new file mode 100644 index 0000000000..0edc4a9c45 --- /dev/null +++ b/plugins/azure/test/files/sql/instanceFailoverGroups.json @@ -0,0 +1,60 @@ +{ + "value": [ + { + "id": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/Default/providers/Microsoft.Sql/locations/JapanEast/instanceFailoverGroups/failover-group-test", + "name": "failover-group-test", + "type": "Microsoft.Sql/locations/instanceFailoverGroups", + "properties": { + "readWriteEndpoint": { + "failoverPolicy": "Automatic", + "failoverWithDataLossGracePeriodMinutes": 480 + }, + "readOnlyEndpoint": { + "failoverPolicy": "Disabled" + }, + "replicationRole": "Primary", + "replicationState": "CATCH_UP", + "partnerRegions": [ + { + "location": "Japan West", + "replicationRole": "Secondary" + } + ], + "managedInstancePairs": [ + { + "primaryManagedInstanceId": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/Default/providers/Microsoft.Sql/managedInstances/failover-group-primary-mngdInstance", + "partnerManagedInstanceId": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/Default/providers/Microsoft.Sql/managedInstances/failover-group-secondary-mngdInstance" + } + ] + } + }, + { + "id": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/Default/providers/Microsoft.Sql/locations/JapanEast/instanceFailoverGroups/failover-group-test-1", + "name": "failover-group-test-1", + "type": "Microsoft.Sql/locations/instanceFailoverGroups", + "properties": { + "readWriteEndpoint": { + "failoverPolicy": "Automatic", + "failoverWithDataLossGracePeriodMinutes": 480 + }, + "readOnlyEndpoint": { + "failoverPolicy": "Disabled" + }, + "replicationRole": "Primary", + "replicationState": "CATCH_UP", + "partnerRegions": [ + { + "location": "Japan West", + "replicationRole": "Secondary" + } + ], + "managedInstancePairs": [ + { + "primaryManagedInstanceId": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/Default/providers/Microsoft.Sql/managedInstances/failover-group-primary-mngdInstance-1", + "partnerManagedInstanceId": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/Default/providers/Microsoft.Sql/managedInstances/failover-group-secondary-mngdInstance-1" + } + ] + } + } + ] +} \ No newline at end of file diff --git a/plugins/azure/test/files/sql/instancePools.json b/plugins/azure/test/files/sql/instancePools.json new file mode 100644 index 0000000000..bd35e8ffc2 --- /dev/null +++ b/plugins/azure/test/files/sql/instancePools.json @@ -0,0 +1,42 @@ +{ + "value": [ + { + "sku": { + "name": "GP_Gen5", + "tier": "GeneralPurpose", + "family": "Gen5" + }, + "properties": { + "subnetId": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/group1/providers/Microsoft.Network/virtualNetwork/myvnet/subnets/mysubnet1", + "vCores": 8, + "licenseType": "LicenseIncluded" + }, + "location": "japaneast", + "tags": { + "a": "b" + }, + "id": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/group1/providers/Microsoft.Sql/instancePools/testIP", + "name": "testIP", + "type": "Microsoft.Sql/instancePools" + }, + { + "sku": { + "name": "GP_Gen5", + "tier": "GeneralPurpose", + "family": "Gen5" + }, + "properties": { + "subnetId": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/group2/providers/Microsoft.Network/virtualNetwork/myvnet/subnets/mysubnet1", + "vCores": 8, + "licenseType": "LicenseIncluded" + }, + "location": "japaneast", + "tags": { + "a": "b" + }, + "id": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/group2/providers/Microsoft.Sql/instancePools/testIP2", + "name": "testIP2", + "type": "Microsoft.Sql/instancePools" + } + ] +} \ No newline at end of file diff --git a/plugins/azure/test/files/sql/jobAgents.json b/plugins/azure/test/files/sql/jobAgents.json new file mode 100644 index 0000000000..18bcb96bce --- /dev/null +++ b/plugins/azure/test/files/sql/jobAgents.json @@ -0,0 +1,30 @@ +{ + "value": [ + { + "id": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/group1/providers/Microsoft.Sql/servers/server1/jobAgents/agent1", + "name": "agent1", + "type": "Microsoft.Sql/servers/jobAgents", + "location": "southeastasia", + "sku": { + "name": "Agent", + "capacity": 200 + }, + "properties": { + "databaseId": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/group1/providers/Microsoft.Sql/servers/server1/databases/db1" + } + }, + { + "id": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/group1/providers/Microsoft.Sql/servers/server1/jobAgents/jobAgent2", + "name": "jobAgent2", + "type": "Microsoft.Sql/servers/jobAgents", + "location": "southeastasia", + "sku": { + "name": "Agent", + "capacity": 400 + }, + "properties": { + "databaseId": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/group1/providers/Microsoft.Sql/servers/server1/databases/db12" + } + } + ] +} \ No newline at end of file diff --git a/plugins/azure/test/files/sql/managedInstances.json b/plugins/azure/test/files/sql/managedInstances.json new file mode 100644 index 0000000000..ea34d1f9c4 --- /dev/null +++ b/plugins/azure/test/files/sql/managedInstances.json @@ -0,0 +1,64 @@ +{ + "value": [ + { + "location": "japaneast", + "id": "/subscriptions/20d7082a-0fc7-4468-82bd-542694d5042b/resourceGroups/Test1/providers/Microsoft.Sql/managedInstances/testinstance1", + "name": "testinstance1", + "type": "Microsoft.Sql/managedInstances", + "sku": { + "name": "GP_Gen4", + "tier": "GeneralPurpose", + "capacity": 8, + "family": "Gen4" + }, + "properties": { + "fullyQualifiedDomainName": "testinstance1.1b4e2caff2530.database.windows.net", + "administratorLogin": "dummylogin", + "subnetId": "/subscriptions/20D7082A-0FC7-4468-82BD-542694D5042B/resourceGroups/testrg/providers/Microsoft.Network/virtualNetworks/vnet1/subnets/subnet1", + "state": "Ready", + "provisioningState": "Succeeded", + "vCores": 8, + "storageSizeInGB": 1024, + "licenseType": "LicenseIncluded", + "collation": "SQL_Latin1_General_CP1_CI_AS", + "publicDataEndpointEnabled": false, + "proxyOverride": "Default", + "minimalTlsVersion": "1.2", + "dnsZone": "1b4e2caff2530", + "maintenanceConfigurationId": "/subscriptions/20D7082A-0FC7-4468-82BD-542694D5042B/providers/Microsoft.Maintenance/publicMaintenanceConfigurations/SQL_JapanEast_MI_1", + "currentBackupStorageRedundancy": "Geo", + "requestedBackupStorageRedundancy": "Geo" + } + }, + { + "location": "japaneast", + "id": "/subscriptions/20d7082a-0fc7-4468-82bd-542694d5042b/resourceGroups/Test1/providers/Microsoft.Sql/managedInstances/testinstance2", + "name": "testinstance2", + "type": "Microsoft.Sql/managedInstances", + "sku": { + "name": "BC_Gen5", + "tier": "BusinessCritical", + "capacity": 16, + "family": "Gen4" + }, + "properties": { + "fullyQualifiedDomainName": "testinstance2.2c3d1bdae3412.database.windows.net", + "administratorLogin": "dummylogin", + "subnetId": "/subscriptions/20D7082A-0FC7-4468-82BD-542694D5042B/resourceGroups/testrg/providers/Microsoft.Network/virtualNetworks/vnet2/subnets/subnet2", + "state": "Ready", + "provisioningState": "Succeeded", + "vCores": 16, + "storageSizeInGB": 1024, + "licenseType": "Full", + "collation": "SQL_Latin1_General_CP1_CI_AS", + "publicDataEndpointEnabled": false, + "proxyOverride": "Default", + "minimalTlsVersion": "1.2", + "dnsZone": "2c3d1bdae3412", + "maintenanceConfigurationId": "/subscriptions/20D7082A-0FC7-4468-82BD-542694D5042B/providers/Microsoft.Maintenance/publicMaintenanceConfigurations/SQL_JapanEast_MI_1", + "currentBackupStorageRedundancy": "Geo", + "requestedBackupStorageRedundancy": "Geo" + } + } + ] +} \ No newline at end of file diff --git a/plugins/azure/test/files/sql/privateEndpointConnections.json b/plugins/azure/test/files/sql/privateEndpointConnections.json new file mode 100644 index 0000000000..d742f48532 --- /dev/null +++ b/plugins/azure/test/files/sql/privateEndpointConnections.json @@ -0,0 +1,36 @@ +{ + "value": [ + { + "id": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/Default/providers/Microsoft.Sql/servers/test-svr/privateEndpointConnections/private-endpoint-connection-name-2", + "name": "private-endpoint-connection-name", + "type": "Microsoft.Sql/servers/privateEndpointConnections", + "properties": { + "provisioningState": "Ready", + "privateEndpoint": { + "id": "/subscriptions/55555555-6666-7777-8888-999999999999/resourceGroups/Default-Network/providers/Microsoft.Network/privateEndpoints/private-endpoint-name" + }, + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Auto-approved", + "actionsRequired": "None" + } + } + }, + { + "id": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/Default/providers/Microsoft.Sql/servers/test-svr/privateEndpointConnections/private-endpoint-connection-name-2", + "name": "private-endpoint-connection-name-2", + "type": "Microsoft.Sql/servers/privateEndpointConnections", + "properties": { + "provisioningState": "Ready", + "privateEndpoint": { + "id": "/subscriptions/55555555-6666-7777-8888-999999999999/resourceGroups/Default-Network/providers/Microsoft.Network/privateEndpoints/private-endpoint-name-2" + }, + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Auto-approved", + "actionsRequired": "None" + } + } + } + ] +} \ No newline at end of file diff --git a/plugins/azure/test/files/sql/serverTrustGroups.json b/plugins/azure/test/files/sql/serverTrustGroups.json new file mode 100644 index 0000000000..006524938f --- /dev/null +++ b/plugins/azure/test/files/sql/serverTrustGroups.json @@ -0,0 +1,42 @@ +{ + "value": [ + { + "id": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/Default/providers/Microsoft.Sql/locations/Japan East/serverTrustGroups/server-trust-group-test", + "name": "server-trust-group-test", + "properties": { + "groupMembers": [ + { + "serverId": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/Default/providers/Microsoft.Sql/managedInstances/managedInstance-1" + }, + { + "serverId": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/Default/providers/Microsoft.Sql/managedInstances/managedInstance-2" + } + ], + "trustScopes": [ + "GlobalTransactions", + "ServiceBroker" + ] + }, + "type": "Microsoft.Sql/locations/serverTrustGroups" + }, + { + "id": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/Default/providers/Microsoft.Sql/locations/Japan East/serverTrustGroups/server-trust-group-test-2", + "name": "server-trust-group-test-2", + "properties": { + "groupMembers": [ + { + "serverId": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/Default/providers/Microsoft.Sql/managedInstances/managedInstance-1" + }, + { + "serverId": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/Default/providers/Microsoft.Sql/managedInstances/managedInstance-3" + } + ], + "trustScopes": [ + "GlobalTransactions", + "ServiceBroker" + ] + }, + "type": "Microsoft.Sql/locations/serverTrustGroups" + } + ] +} \ No newline at end of file diff --git a/plugins/azure/test/files/sql/servers.json b/plugins/azure/test/files/sql/servers.json new file mode 100644 index 0000000000..139571dd7f --- /dev/null +++ b/plugins/azure/test/files/sql/servers.json @@ -0,0 +1,36 @@ +{ + "value": [ + { + "id": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/sqlcrudtest-7398/providers/Microsoft.Sql/servers/sqlcrudtest-4645", + "name": "sqlcrudtest-4645", + "type": "Microsoft.Sql/servers", + "location": "japaneast", + "kind": "v12.0", + "properties": { + "fullyQualifiedDomainName": "sqlcrudtest-4645.database.windows.net", + "administratorLogin": "dummylogin", + "version": "12.0", + "state": "Ready", + "workspaceFeature": "Connected", + "publicNetworkAccess": "Enabled", + "restrictOutboundNetworkAccess": "Enabled", + "privateEndpointConnections": [ + { + "id": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/sqlcrudtest-7398/providers/Microsoft.Sql/servers/sqlcrudtest-4645/privateEndpointConnections/private-endpoint-name-00000000-1111-2222-3333-444444444444", + "properties": { + "provisioningState": "Ready", + "privateEndpoint": { + "id": "/subscriptions/55555555-6666-7777-8888-999999999999/resourceGroups/Default-Network/providers/Microsoft.Network/privateEndpoints/private-endpoint-name" + }, + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Auto-approved", + "actionsRequired": "None" + } + } + } + ] + } + } + ] +} \ No newline at end of file diff --git a/plugins/azure/test/files/sql/virtualClusters.json b/plugins/azure/test/files/sql/virtualClusters.json new file mode 100644 index 0000000000..35e6fa2a52 --- /dev/null +++ b/plugins/azure/test/files/sql/virtualClusters.json @@ -0,0 +1,34 @@ +{ + "value": [ + { + "properties": { + "subnetId": "/subscriptions/20D7082A-0FC7-4468-82BD-542694D5042B/resourceGroups/testrg/providers/Microsoft.Network/virtualNetworks/vnet1/subnets/subnet1", + "family": "Gen4", + "childResources": [ + "/subscriptions/20D7082A-0FC7-4468-82BD-542694D5042B/resourceGroups/testrg/providers/Microsoft.Sql/managedInstances/testinstance1", + "/subscriptions/20D7082A-0FC7-4468-82BD-542694D5042B/resourceGroups/testrg/providers/Microsoft.Sql/managedInstances/testinstance2" + ], + "maintenanceConfigurationId": "/subscriptions/ab0e51c0-83c0-4380-8ae9-025516df392f/resourceGroups/Federation/providers/Microsoft.Maintenance/maintenanceConfigurations/MiPolicy1" + }, + "location": "onebox", + "id": "/subscriptions/20d7082a-0fc7-4468-82bd-542694d5042b/resourceGroups/testrg/providers/Microsoft.Sql/virtualClusters/vc-subnet1-f769ed71-b3ad-491a-a9d5-26eeceaa6be2", + "name": "vc-subnet1-f769ed71-b3ad-491a-a9d5-26eeceaa6be2", + "type": "Microsoft.Sql/virtualClusters" + }, + { + "properties": { + "subnetId": "/subscriptions/20D7082A-0FC7-4468-82BD-542694D5042B/resourceGroups/testrg/providers/Microsoft.Network/virtualNetworks/vnet2/subnets/subnet2", + "family": "Gen5", + "childResources": [ + "/subscriptions/20D7082A-0FC7-4468-82BD-542694D5042B/resourceGroups/testrg/providers/Microsoft.Sql/managedInstances/testinstance3", + "/subscriptions/20D7082A-0FC7-4468-82BD-542694D5042B/resourceGroups/testrg/providers/Microsoft.Sql/managedInstances/testinstance4" + ], + "maintenanceConfigurationId": "/subscriptions/ab0e51c0-83c0-4380-8ae9-025516df392f/resourceGroups/Federation/providers/Microsoft.Maintenance/maintenanceConfigurations/MiPolicy1" + }, + "location": "onebox", + "id": "/subscriptions/20d7082a-0fc7-4468-82bd-542694d5042b/resourceGroups/testrg/providers/Microsoft.Sql/virtualClusters/vc-subnet2-14b795bd-9c8f-46ec-adb8-2b8eff56ac16", + "name": "vc-subnet1-14b795bd-9c8f-46ec-adb8-2b8eff56ac16", + "type": "Microsoft.Sql/virtualClusters" + } + ] +} \ No newline at end of file diff --git a/plugins/azure/test/files/sql/virtualNetworkRules.json b/plugins/azure/test/files/sql/virtualNetworkRules.json new file mode 100644 index 0000000000..574778ef87 --- /dev/null +++ b/plugins/azure/test/files/sql/virtualNetworkRules.json @@ -0,0 +1,24 @@ +{ + "value": [ + { + "id": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/Default/providers/Microsoft.Sql/servers/vnet-test-svr/virtualNetworkRules/vnet-firewall-rule", + "name": "vnet-firewall-rule", + "type": "Microsoft.Sql/servers/virtualNetworkRules", + "properties": { + "ignoreMissingVnetServiceEndpoint": false, + "state": "Ready", + "virtualNetworkSubnetId": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/Default/providers/Microsoft.Network/virtualNetworks/testvnet/subnets/testsubnet" + } + }, + { + "id": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/Default/providers/Microsoft.Sql/servers/vnet-test-svr/virtualNetworkRules/vnet-firewall-rule", + "name": "vnet-firewall-rule", + "type": "Microsoft.Sql/servers/virtualNetworkRules", + "properties": { + "ignoreMissingVnetServiceEndpoint": false, + "state": "Ready", + "virtualNetworkSubnetId": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/Default/providers/Microsoft.Network/virtualNetworks/testvnet/subnets/testsubnet" + } + } + ] +} \ No newline at end of file diff --git a/plugins/azure/test/files/sql/workloadGroups.json b/plugins/azure/test/files/sql/workloadGroups.json new file mode 100644 index 0000000000..b21acea160 --- /dev/null +++ b/plugins/azure/test/files/sql/workloadGroups.json @@ -0,0 +1,43 @@ +{ + "value": [ + { + "properties": { + "minResourcePercent": 0, + "maxResourcePercent": 100, + "minResourcePercentPerRequest": 5, + "maxResourcePercentPerRequest": 5, + "importance": "normal", + "queryExecutionTimeout": 0 + }, + "id": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/Default-SQL-SouthEastAsia/providers/Microsoft.Sql/servers/testsvr/databases/testdb/workloadGroups/smallrc", + "name": "smallrc", + "type": "Microsoft.Sql/servers/databases/workloadGroups" + }, + { + "properties": { + "minResourcePercent": 0, + "maxResourcePercent": 100, + "minResourcePercentPerRequest": 10, + "maxResourcePercentPerRequest": 10, + "importance": "normal", + "queryExecutionTimeout": 0 + }, + "id": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/Default-SQL-SouthEastAsia/providers/Microsoft.Sql/servers/testsvr/databases/testdb/workloadGroups/mediumrc", + "name": "mediumrc", + "type": "Microsoft.Sql/servers/databases/workloadGroups" + }, + { + "properties": { + "minResourcePercent": 0, + "maxResourcePercent": 100, + "minResourcePercentPerRequest": 20, + "maxResourcePercentPerRequest": 20, + "importance": "high", + "queryExecutionTimeout": 0 + }, + "id": "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/Default-SQL-SouthEastAsia/providers/Microsoft.Sql/servers/testsvr/databases/testdb/workloadGroups/largerc", + "name": "largerc", + "type": "Microsoft.Sql/servers/databases/workloadGroups" + } + ] +} \ No newline at end of file diff --git a/plugins/azure/test/sql_test.py b/plugins/azure/test/sql_test.py new file mode 100644 index 0000000000..4476a1804c --- /dev/null +++ b/plugins/azure/test/sql_test.py @@ -0,0 +1,28 @@ +from conftest import roundtrip_check +from fix_plugin_azure.resource.base import GraphBuilder +from fix_plugin_azure.resource.sql import ( + AzureSqlInstancePool, + AzureSqlManagedInstance, + AzureSqlServer, + AzureSqlVirtualCluster, +) + + +def test_sql_instance_pool(builder: GraphBuilder) -> None: + collected = roundtrip_check(AzureSqlInstancePool, builder) + assert len(collected) == 2 + + +def test_sql_managed_instance(builder: GraphBuilder) -> None: + collected = roundtrip_check(AzureSqlManagedInstance, builder) + assert len(collected) == 2 + + +def test_sql_server(builder: GraphBuilder) -> None: + collected = roundtrip_check(AzureSqlServer, builder) + assert len(collected) == 1 + + +def test_sql_virtual_cluster(builder: GraphBuilder) -> None: + collected = roundtrip_check(AzureSqlVirtualCluster, builder) + assert len(collected) == 2