From 818c8430ebf48bab825464db6f1d19c23254f1f0 Mon Sep 17 00:00:00 2001 From: Patrick Decat Date: Fri, 24 Mar 2023 11:05:28 +0100 Subject: [PATCH] Add azure_private_dns_zone table (#583) --- .../azure_private_dns_zone/dependencies.txt | 0 .../test-get-expected.json | 8 + .../azure_private_dns_zone/test-get-query.sql | 6 + .../test-hydrate-expected.json | 13 ++ .../test-hydrate-query.sql | 6 + .../test-list-expected.json | 6 + .../test-list-query.sql | 4 + .../azure_private_dns_zone/variables.json | 1 + .../tests/azure_private_dns_zone/variables.tf | 68 ++++++ azure/plugin.go | 1 + azure/table_azure_dns_zone.go | 2 +- azure/table_azure_private_dns_zone.go | 207 ++++++++++++++++++ docs/tables/azure_dns_zone.md | 15 +- docs/tables/azure_private_dns_zone.md | 40 ++++ 14 files changed, 368 insertions(+), 9 deletions(-) create mode 100644 azure-test/tests/azure_private_dns_zone/dependencies.txt create mode 100644 azure-test/tests/azure_private_dns_zone/test-get-expected.json create mode 100644 azure-test/tests/azure_private_dns_zone/test-get-query.sql create mode 100644 azure-test/tests/azure_private_dns_zone/test-hydrate-expected.json create mode 100644 azure-test/tests/azure_private_dns_zone/test-hydrate-query.sql create mode 100644 azure-test/tests/azure_private_dns_zone/test-list-expected.json create mode 100644 azure-test/tests/azure_private_dns_zone/test-list-query.sql create mode 100644 azure-test/tests/azure_private_dns_zone/variables.json create mode 100644 azure-test/tests/azure_private_dns_zone/variables.tf create mode 100644 azure/table_azure_private_dns_zone.go create mode 100644 docs/tables/azure_private_dns_zone.md diff --git a/azure-test/tests/azure_private_dns_zone/dependencies.txt b/azure-test/tests/azure_private_dns_zone/dependencies.txt new file mode 100644 index 00000000..e69de29b diff --git a/azure-test/tests/azure_private_dns_zone/test-get-expected.json b/azure-test/tests/azure_private_dns_zone/test-get-expected.json new file mode 100644 index 00000000..127fd6d8 --- /dev/null +++ b/azure-test/tests/azure_private_dns_zone/test-get-expected.json @@ -0,0 +1,8 @@ +[ + { + "id": "{{ output.resource_id.value }}", + "name": "{{resourceName}}.local", + "region": "global", + "type": "Microsoft.Network/privateDnsZones" + } +] diff --git a/azure-test/tests/azure_private_dns_zone/test-get-query.sql b/azure-test/tests/azure_private_dns_zone/test-get-query.sql new file mode 100644 index 00000000..ca524c71 --- /dev/null +++ b/azure-test/tests/azure_private_dns_zone/test-get-query.sql @@ -0,0 +1,6 @@ +select name, + id, + region, + type +from azure.azure_private_dns_zone +where name = '{{resourceName}}.local' diff --git a/azure-test/tests/azure_private_dns_zone/test-hydrate-expected.json b/azure-test/tests/azure_private_dns_zone/test-hydrate-expected.json new file mode 100644 index 00000000..30dcb8e0 --- /dev/null +++ b/azure-test/tests/azure_private_dns_zone/test-hydrate-expected.json @@ -0,0 +1,13 @@ +[ + { + "akas": [ + "{{ output.resource_aka.value }}", + "azure:///subscriptions/{{ output.subscription_id.value }}/resourcegroups/{{resourceName}}/providers/microsoft.network/privatednszones/{{resourceName}}.local" + ], + "name": "{{resourceName}}.local", + "tags": { + "name": "{{resourceName}}.local" + }, + "title": "{{resourceName}}.local" + } +] diff --git a/azure-test/tests/azure_private_dns_zone/test-hydrate-query.sql b/azure-test/tests/azure_private_dns_zone/test-hydrate-query.sql new file mode 100644 index 00000000..cabc7625 --- /dev/null +++ b/azure-test/tests/azure_private_dns_zone/test-hydrate-query.sql @@ -0,0 +1,6 @@ +select name, + akas, + tags, + title +from azure.azure_private_dns_zone +where name = '{{resourceName}}.local' diff --git a/azure-test/tests/azure_private_dns_zone/test-list-expected.json b/azure-test/tests/azure_private_dns_zone/test-list-expected.json new file mode 100644 index 00000000..e066f7da --- /dev/null +++ b/azure-test/tests/azure_private_dns_zone/test-list-expected.json @@ -0,0 +1,6 @@ +[ + { + "id": "{{ output.resource_id.value }}", + "name": "{{resourceName}}.local" + } +] diff --git a/azure-test/tests/azure_private_dns_zone/test-list-query.sql b/azure-test/tests/azure_private_dns_zone/test-list-query.sql new file mode 100644 index 00000000..b0ea5d95 --- /dev/null +++ b/azure-test/tests/azure_private_dns_zone/test-list-query.sql @@ -0,0 +1,4 @@ +select id, + name +from azure.azure_private_dns_zone +where id = '{{ output.resource_id.value }}' diff --git a/azure-test/tests/azure_private_dns_zone/variables.json b/azure-test/tests/azure_private_dns_zone/variables.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/azure-test/tests/azure_private_dns_zone/variables.json @@ -0,0 +1 @@ +{} diff --git a/azure-test/tests/azure_private_dns_zone/variables.tf b/azure-test/tests/azure_private_dns_zone/variables.tf new file mode 100644 index 00000000..542e30e0 --- /dev/null +++ b/azure-test/tests/azure_private_dns_zone/variables.tf @@ -0,0 +1,68 @@ + +variable "resource_name" { + type = string + default = "turbot-test-20230313-create-update" + description = "Name of the resource used throughout the test." +} + +variable "azure_environment" { + type = string + default = "public" + description = "Azure environment used for the test." +} + +variable "azure_subscription" { + type = string + default = "3510ae4d-530b-497d-8f30-53b9616fc6c1" + description = "Azure subscription used for the test." +} + +provider "azurerm" { + # Cannot be passed as a variable + environment = var.azure_environment + subscription_id = var.azure_subscription + features {} +} + +data "azurerm_client_config" "current" {} + +data "null_data_source" "resource" { + inputs = { + scope = "azure:///subscriptions/${data.azurerm_client_config.current.subscription_id}" + } +} + +resource "azurerm_resource_group" "named_test_resource" { + name = var.resource_name + location = "East US" + tags = { + name = var.resource_name + } +} + +resource "azurerm_private_dns_zone" "named_test_resource" { + name = "${var.resource_name}.local" + resource_group_name = azurerm_resource_group.named_test_resource.name + + tags = { + name = "${var.resource_name}.local" + } +} + +output "resource_aka" { + # Azure always puts privateDnsZones with lowercase d and z + value = replace("azure://${azurerm_private_dns_zone.named_test_resource.id}", "Microsoft.Network/PrivateDnsZones", "Microsoft.Network/privatednszones") +} + +output "resource_name" { + value = var.resource_name +} + +output "resource_id" { + # Azure always puts dnsZones with with lowercase d and z + value = replace(azurerm_private_dns_zone.named_test_resource.id, "Microsoft.Network/PrivateDnsZones", "Microsoft.Network/privatednszones") +} + +output "subscription_id" { + value = var.azure_subscription +} diff --git a/azure/plugin.go b/azure/plugin.go index 3c78f6d8..d7468e7a 100644 --- a/azure/plugin.go +++ b/azure/plugin.go @@ -126,6 +126,7 @@ func Plugin(ctx context.Context) *plugin.Plugin { "azure_policy_assignment": tableAzurePolicyAssignment(ctx), "azure_policy_definition": tableAzurePolicyDefinition(ctx), "azure_postgresql_server": tableAzurePostgreSqlServer(ctx), + "azure_private_dns_zone": tableAzurePrivateDNSZone(ctx), "azure_provider": tableAzureProvider(ctx), "azure_public_ip": tableAzurePublicIP(ctx), "azure_recovery_services_vault": tableAzureRecoveryServicesVault(ctx), diff --git a/azure/table_azure_dns_zone.go b/azure/table_azure_dns_zone.go index 300f047a..7ad73b5d 100644 --- a/azure/table_azure_dns_zone.go +++ b/azure/table_azure_dns_zone.go @@ -74,7 +74,7 @@ func tableAzureDNSZone(_ context.Context) *plugin.Table { }, { Name: "zone_type", - Description: "The type of this DNS zone (Public or Private).", + Description: "The type of this DNS zone (always `Public`, see `azure_private_dns_zone` table for private DNS zones).", Type: proto.ColumnType_STRING, Transform: transform.FromField("ZoneProperties.ZoneType"), }, diff --git a/azure/table_azure_private_dns_zone.go b/azure/table_azure_private_dns_zone.go new file mode 100644 index 00000000..67eadaa4 --- /dev/null +++ b/azure/table_azure_private_dns_zone.go @@ -0,0 +1,207 @@ +package azure + +import ( + "context" + + "github.com/Azure/azure-sdk-for-go/services/dns/mgmt/2018-05-01/dns" + "github.com/Azure/azure-sdk-for-go/services/privatedns/mgmt/2018-09-01/privatedns" + "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" + + "github.com/turbot/steampipe-plugin-sdk/v5/plugin" +) + +//// TABLE DEFINITION + +func tableAzurePrivateDNSZone(_ context.Context) *plugin.Table { + return &plugin.Table{ + Name: "azure_private_dns_zone", + Description: "Azure Private DNS Zone", + Get: &plugin.GetConfig{ + KeyColumns: plugin.AllColumns([]string{"name", "resource_group"}), + Hydrate: getPrivateDNSZone, + IgnoreConfig: &plugin.IgnoreConfig{ + ShouldIgnoreErrorFunc: isNotFoundError([]string{"ResourceNotFound", "ResourceGroupNotFound", "404"}), + }, + }, + List: &plugin.ListConfig{ + Hydrate: listPrivateDNSZones, + }, + Columns: azureColumns([]*plugin.Column{ + { + Name: "name", + Type: proto.ColumnType_STRING, + Description: "The friendly name that identifies the Private DNS zone.", + }, + { + Name: "id", + Description: "Contains ID to identify a Private DNS zone uniquely.", + Type: proto.ColumnType_STRING, + Transform: transform.FromGo(), + }, + { + Name: "etag", + Description: "An unique read-only string that changes whenever the resource is updated.", + Type: proto.ColumnType_STRING, + }, + { + Name: "type", + Description: "The resource type of the Private DNS zone.", + Type: proto.ColumnType_STRING, + }, + { + Name: "max_number_of_record_sets", + Description: "The maximum number of record sets that can be created in this Private DNS zone.", + Type: proto.ColumnType_INT, + Transform: transform.FromField("PrivateZoneProperties.MaxNumberOfRecordSets"), + }, + { + Name: "number_of_record_sets", + Description: "The current number of record sets in this Private DNS zone.", + Type: proto.ColumnType_INT, + Transform: transform.FromField("PrivateZoneProperties.NumberOfRecordSets").Transform(transform.ToString), + }, + { + Name: "max_number_of_virtual_network_links", + Description: "The maximum number of virtual networks that can be linked to this Private DNS zone.", + Type: proto.ColumnType_INT, + Transform: transform.FromField("PrivateZoneProperties.MaxNumberOfVirtualNetworkLinks"), + }, + { + Name: "number_of_virtual_network_links", + Description: "The current number of virtual networks that are linked to this Private DNS zone.", + Type: proto.ColumnType_INT, + Transform: transform.FromField("PrivateZoneProperties.NumberOfVirtualNetworkLinks"), + }, + { + Name: "max_number_of_virtual_network_links_with_registration", + Description: "The maximum number of virtual networks that can be linked to this Private DNS zone with registration enabled.", + Type: proto.ColumnType_INT, + Transform: transform.FromField("PrivateZoneProperties.MaxNumberOfVirtualNetworkLinksWithRegistration"), + }, + { + Name: "number_of_virtual_network_links_with_registration", + Description: "The current number of virtual networks that are linked to this Private DNS zone with registration enabled.", + Type: proto.ColumnType_INT, + Transform: transform.FromField("PrivateZoneProperties.NumberOfVirtualNetworkLinksWithRegistration"), + }, + { + Name: "provisioning_state", + Description: "The provisioning state of the resource. Possible values include: `Creating`, `Updating`, `Deleting`, `Succeeded`, `Failed`, `Canceled`.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("PrivateZoneProperties.ProvisioningState"), + }, + + // Steampipe standard columns + { + Name: "title", + Description: ColumnDescriptionTitle, + Type: proto.ColumnType_STRING, + Transform: transform.FromField("Name"), + }, + { + Name: "tags", + Description: ColumnDescriptionTags, + Type: proto.ColumnType_JSON, + }, + { + Name: "akas", + Description: ColumnDescriptionAkas, + Type: proto.ColumnType_JSON, + Transform: transform.FromField("ID").Transform(idToAkas), + }, + + // Azure standard columns + { + Name: "region", + Description: ColumnDescriptionRegion, + Type: proto.ColumnType_STRING, + Transform: transform.FromField("Location").Transform(toLower), + }, + { + Name: "resource_group", + Description: ColumnDescriptionResourceGroup, + Type: proto.ColumnType_STRING, + Transform: transform.FromField("ID").Transform(extractResourceGroupFromID), + }, + }), + } +} + +//// FETCH FUNCTIONS //// + +func listPrivateDNSZones(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + session, err := GetNewSession(ctx, d, "MANAGEMENT") + if err != nil { + plugin.Logger(ctx).Error("azure_private_dns_zone.listPrivateDNSZones", "client_error", err) + return nil, err + } + subscriptionID := session.SubscriptionID + + dnsClient := privatedns.NewPrivateZonesClientWithBaseURI(session.ResourceManagerEndpoint, subscriptionID) + dnsClient.Authorizer = session.Authorizer + + result, err := dnsClient.List(ctx, nil) + if err != nil { + plugin.Logger(ctx).Error("azure_private_dns_zone.listPrivateDNSZones", "query_error", err) + return nil, err + } + for _, dnsZone := range result.Values() { + d.StreamListItem(ctx, dnsZone) + // Check if context has been cancelled or if the limit has been hit (if specified) + // if there is a limit, it will return the number of rows required to reach this limit + if d.RowsRemaining(ctx) == 0 { + return nil, nil + } + } + + for result.NotDone() { + err = result.NextWithContext(ctx) + if err != nil { + return nil, err + } + + for _, dnsZone := range result.Values() { + d.StreamListItem(ctx, dnsZone) + // Check if context has been cancelled or if the limit has been hit (if specified) + // if there is a limit, it will return the number of rows required to reach this limit + if d.RowsRemaining(ctx) == 0 { + return nil, nil + } + } + } + return nil, err +} + +//// HYDRATE FUNCTIONS //// + +func getPrivateDNSZone(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + plugin.Logger(ctx).Trace("getPrivateDNSZone") + + name := d.EqualsQuals["name"].GetStringValue() + resourceGroup := d.EqualsQuals["resource_group"].GetStringValue() + + session, err := GetNewSession(ctx, d, "MANAGEMENT") + if err != nil { + plugin.Logger(ctx).Error("azure_private_dns_zone.getPrivateDNSZone", "client_error", err) + return nil, err + } + subscriptionID := session.SubscriptionID + + dnsClient := dns.NewZonesClientWithBaseURI(session.ResourceManagerEndpoint, subscriptionID) + dnsClient.Authorizer = session.Authorizer + + op, err := dnsClient.Get(ctx, resourceGroup, name) + if err != nil { + plugin.Logger(ctx).Error("azure_private_dns_zone.getPrivateDNSZone", "query_error", err) + return nil, err + } + + // In some cases resource does not give any notFound error + // instead of notFound error, it returns empty data + if op.ID != nil { + return op, nil + } + + return nil, nil +} diff --git a/docs/tables/azure_dns_zone.md b/docs/tables/azure_dns_zone.md index 96d2b2e0..399c31c8 100644 --- a/docs/tables/azure_dns_zone.md +++ b/docs/tables/azure_dns_zone.md @@ -1,6 +1,6 @@ # Table: azure_dns_zone -Azure DNS zone is used to host the DNS records for a particular domain. +Azure DNS zone is used to host the DNS records for a particular domain. Please note that this table only retrieves public DNS zones, use the `azure_private_dns_zone` table for private DNS zones. ## Examples @@ -9,23 +9,22 @@ Azure DNS zone is used to host the DNS records for a particular domain. ```sql select name, - id, - zone_type + resource_group, + tags from azure_dns_zone; ``` -### List private DNS zones +### List public DNS zones with record sets ```sql select name, - id, - tags + resource_group from azure_dns_zone where - zone_type = 'Private'; + number_of_record_sets > 1; ``` ### List public DNS zones with delegated name servers @@ -33,7 +32,7 @@ where ```sql select name, - id, + resource_group, ns from azure_dns_zone, jsonb_array_elements_text(name_servers) as ns diff --git a/docs/tables/azure_private_dns_zone.md b/docs/tables/azure_private_dns_zone.md new file mode 100644 index 00000000..ed45fa13 --- /dev/null +++ b/docs/tables/azure_private_dns_zone.md @@ -0,0 +1,40 @@ +# Table: azure_private_dns_zone + +Azure private DNS zone is used to host the DNS records for a particular domain. Please note that this table only retrieves private DNS zones, use the `azure_dns_zone` table for public DNS zones. + +## Examples + +### Basic info + +```sql +select + name, + resource_group, + tags +from + azure_private_dns_zone; +``` + +### List private DNS zones with record sets + +```sql +select + name, + resource_group +from + azure_private_dns_zone +where + number_of_record_sets > 1; +``` + +### List private DNS zones linked to no virtual networks + +```sql +select + name, + resource_group +from + azure_private_dns_zone +where + number_of_virtual_network_links_with_registration = 0; +```