diff --git a/azure-test/tests/azure_eventhub_namespace/dependencies.txt b/azure-test/tests/azure_eventhub_namespace/dependencies.txt new file mode 100644 index 00000000..e69de29b diff --git a/azure-test/tests/azure_eventhub_namespace/test-get-expected.json b/azure-test/tests/azure_eventhub_namespace/test-get-expected.json new file mode 100644 index 00000000..a69fecd3 --- /dev/null +++ b/azure-test/tests/azure_eventhub_namespace/test-get-expected.json @@ -0,0 +1,11 @@ +[ + { + "id": "{{ output.resource_id.value }}", + "is_auto_inflate_enabled": false, + "kafka_enabled": true, + "name": "{{ resourceName }}", + "region": "{{ output.location.value }}", + "resource_group": "{{ resourceName }}", + "type": "Microsoft.EventHub/Namespaces" + } +] diff --git a/azure-test/tests/azure_eventhub_namespace/test-get-query.sql b/azure-test/tests/azure_eventhub_namespace/test-get-query.sql new file mode 100644 index 00000000..d05c110d --- /dev/null +++ b/azure-test/tests/azure_eventhub_namespace/test-get-query.sql @@ -0,0 +1,3 @@ +select name, id, region, type, is_auto_inflate_enabled, kafka_enabled, resource_group +from azure.azure_eventhub_namespace +where name = '{{ resourceName }}' and resource_group = '{{ resourceName }}'; diff --git a/azure-test/tests/azure_eventhub_namespace/test-hydrate-expected.json b/azure-test/tests/azure_eventhub_namespace/test-hydrate-expected.json new file mode 100644 index 00000000..82954714 --- /dev/null +++ b/azure-test/tests/azure_eventhub_namespace/test-hydrate-expected.json @@ -0,0 +1,13 @@ +[ + { + "akas": [ + "{{ output.resource_aka.value }}", + "{{ output.resource_aka_lower.value }}" + ], + "name": "{{resourceName}}", + "tags": { + "name": "{{resourceName}}" + }, + "title": "{{resourceName}}" + } +] diff --git a/azure-test/tests/azure_eventhub_namespace/test-hydrate-query.sql b/azure-test/tests/azure_eventhub_namespace/test-hydrate-query.sql new file mode 100644 index 00000000..b67ab645 --- /dev/null +++ b/azure-test/tests/azure_eventhub_namespace/test-hydrate-query.sql @@ -0,0 +1,3 @@ +select name, akas, tags, title +from azure.azure_eventhub_namespace +where name = '{{ resourceName }}' and resource_group = '{{ resourceName }}'; diff --git a/azure-test/tests/azure_eventhub_namespace/test-list-expected.json b/azure-test/tests/azure_eventhub_namespace/test-list-expected.json new file mode 100644 index 00000000..e854f350 --- /dev/null +++ b/azure-test/tests/azure_eventhub_namespace/test-list-expected.json @@ -0,0 +1,6 @@ +[ + { + "id": "{{ output.resource_id.value }}", + "name": "{{ resourceName }}" + } +] diff --git a/azure-test/tests/azure_eventhub_namespace/test-list-query.sql b/azure-test/tests/azure_eventhub_namespace/test-list-query.sql new file mode 100644 index 00000000..38682fe3 --- /dev/null +++ b/azure-test/tests/azure_eventhub_namespace/test-list-query.sql @@ -0,0 +1,3 @@ +select id, name +from azure.azure_eventhub_namespace +where name = '{{ resourceName }}'; diff --git a/azure-test/tests/azure_eventhub_namespace/test-not-found-expected.json b/azure-test/tests/azure_eventhub_namespace/test-not-found-expected.json new file mode 100644 index 00000000..19765bd5 --- /dev/null +++ b/azure-test/tests/azure_eventhub_namespace/test-not-found-expected.json @@ -0,0 +1 @@ +null diff --git a/azure-test/tests/azure_eventhub_namespace/test-not-found-query.sql b/azure-test/tests/azure_eventhub_namespace/test-not-found-query.sql new file mode 100644 index 00000000..cf80fa8d --- /dev/null +++ b/azure-test/tests/azure_eventhub_namespace/test-not-found-query.sql @@ -0,0 +1,3 @@ +select name, akas, tags, title +from azure.azure_eventhub_namespace +where name = 'dummy-{{ resourceName }}' and resource_group = '{{ resourceName }}'; diff --git a/azure-test/tests/azure_eventhub_namespace/test-turbot-expected.json b/azure-test/tests/azure_eventhub_namespace/test-turbot-expected.json new file mode 100644 index 00000000..43ffbc2c --- /dev/null +++ b/azure-test/tests/azure_eventhub_namespace/test-turbot-expected.json @@ -0,0 +1,13 @@ +[ + { + "akas": [ + "{{ output.resource_aka.value }}", + "{{ output.resource_aka_lower.value }}" + ], + "name": "{{ resourceName }}", + "tags": { + "name": "{{ resourceName }}" + }, + "title": "{{ resourceName }}" + } +] diff --git a/azure-test/tests/azure_eventhub_namespace/test-turbot-query.sql b/azure-test/tests/azure_eventhub_namespace/test-turbot-query.sql new file mode 100644 index 00000000..64c9e23b --- /dev/null +++ b/azure-test/tests/azure_eventhub_namespace/test-turbot-query.sql @@ -0,0 +1,3 @@ +select name, akas, title, tags +from azure.azure_eventhub_namespace +where name = '{{ resourceName }}' and resource_group = '{{ resourceName }}'; diff --git a/azure-test/tests/azure_eventhub_namespace/variables.json b/azure-test/tests/azure_eventhub_namespace/variables.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/azure-test/tests/azure_eventhub_namespace/variables.json @@ -0,0 +1 @@ +{} diff --git a/azure-test/tests/azure_eventhub_namespace/variables.tf b/azure-test/tests/azure_eventhub_namespace/variables.tf new file mode 100644 index 00000000..063618d6 --- /dev/null +++ b/azure-test/tests/azure_eventhub_namespace/variables.tf @@ -0,0 +1,73 @@ +variable "resource_name" { + type = string + default = "turbot-test-20200125-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 + version = "=1.36.0" + environment = var.azure_environment + subscription_id = var.azure_subscription +} + +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 = "West US" +} + +resource "azurerm_eventhub_namespace" "named_test_resource" { + name = var.resource_name + location = azurerm_resource_group.named_test_resource.location + resource_group_name = azurerm_resource_group.named_test_resource.name + sku = "Standard" + capacity = 1 + + tags = { + name = var.resource_name + } +} + +output "resource_aka" { + value = "azure://${azurerm_eventhub_namespace.named_test_resource.id}" +} + +output "resource_aka_lower" { + value = "azure://${lower(azurerm_eventhub_namespace.named_test_resource.id)}" +} + +output "resource_name" { + value = var.resource_name +} + +output "resource_id" { + value = azurerm_eventhub_namespace.named_test_resource.id +} + +output "subscription_id" { + value = var.azure_subscription +} + +output "location" { + value = azurerm_resource_group.named_test_resource.location +} diff --git a/azure/plugin.go b/azure/plugin.go index 5fadc53e..af3fe330 100644 --- a/azure/plugin.go +++ b/azure/plugin.go @@ -55,6 +55,7 @@ func Plugin(ctx context.Context) *plugin.Plugin { "azure_data_factory_dataset": tableAzureDataFactoryDataset(ctx), "azure_data_factory_pipeline": tableAzureDataFactoryPipeline(ctx), "azure_diagnostic_setting": tableAzureDiagnosticSetting(ctx), + "azure_eventhub_namespace": tableAzureEventHubNamespace(ctx), "azure_express_route_circuit": tableAzureExpressRouteCircuit(ctx), "azure_firewall": tableAzureFirewall(ctx), "azure_key_vault": tableAzureKeyVault(ctx), diff --git a/azure/table_azure_eventhub_namespace.go b/azure/table_azure_eventhub_namespace.go new file mode 100644 index 00000000..413d4e60 --- /dev/null +++ b/azure/table_azure_eventhub_namespace.go @@ -0,0 +1,267 @@ +package azure + +import ( + "context" + "strings" + + "github.com/Azure/azure-sdk-for-go/services/preview/eventhub/mgmt/2018-01-01-preview/eventhub" + "github.com/turbot/steampipe-plugin-sdk/grpc/proto" + "github.com/turbot/steampipe-plugin-sdk/plugin" + "github.com/turbot/steampipe-plugin-sdk/plugin/transform" +) + +//// TABLE DEFINITION + +func tableAzureEventHubNamespace(_ context.Context) *plugin.Table { + return &plugin.Table{ + Name: "azure_eventhub_namespace", + Description: "Azure Event Hub Namespace", + Get: &plugin.GetConfig{ + KeyColumns: plugin.AllColumns([]string{"name", "resource_group"}), + Hydrate: getEventHubNamespace, + ShouldIgnoreError: isNotFoundError([]string{"ResourceGroupNotFound", "ResourceNotFound", "400", "404"}), + }, + List: &plugin.ListConfig{ + Hydrate: listEventHubNamespaces, + }, + Columns: []*plugin.Column{ + { + Name: "name", + Description: "The name of the resource.", + Type: proto.ColumnType_STRING, + }, + { + Name: "id", + Description: "The ID of the resource.", + Type: proto.ColumnType_STRING, + Transform: transform.FromGo(), + }, + { + Name: "type", + Description: "The resource type.", + Type: proto.ColumnType_STRING, + }, + { + Name: "provisioning_state", + Description: "Provisioning state of the namespace.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("EHNamespaceProperties.ProvisioningState"), + }, + { + Name: "created_at", + Description: "The time the namespace was created.", + Type: proto.ColumnType_TIMESTAMP, + Transform: transform.FromField("EHNamespaceProperties.CreatedAt").Transform(convertDateToTime), + }, + { + Name: "cluster_arm_id", + Description: "Cluster ARM ID of the namespace.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("EHNamespaceProperties.ClusterArmId"), + }, + { + Name: "is_auto_inflate_enabled", + Description: "Indicates whether auto-inflate is enabled for eventhub namespace.", + Type: proto.ColumnType_BOOL, + Transform: transform.FromField("EHNamespaceProperties.IsAutoInflateEnabled"), + }, + { + Name: "kafka_enabled", + Description: "Indicates whether kafka is enabled for eventhub namespace, or not.", + Type: proto.ColumnType_BOOL, + Transform: transform.FromField("EHNamespaceProperties.KafkaEnabled"), + }, + { + Name: "maximum_throughput_units", + Description: "Upper limit of throughput units when auto-inflate is enabled, value should be within 0 to 20 throughput units.", + Type: proto.ColumnType_INT, + Transform: transform.FromField("EHNamespaceProperties.MaximumThroughputUnits"), + }, + { + Name: "metric_id", + Description: "Identifier for azure insights metrics.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("EHNamespaceProperties.Metric_id"), + }, + { + Name: "service_bus_endpoint", + Description: "Endpoint you can use to perform service bus operations.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("EHNamespaceProperties.ServiceBusEndpoint"), + }, + { + Name: "sku_capacity", + Description: "The Event Hubs throughput units, value should be 0 to 20 throughput units.", + Type: proto.ColumnType_INT, + Transform: transform.FromField("Sku.Capacity"), + }, + { + Name: "sku_name", + Description: "Name of this SKU. Possible values include: 'Basic', 'Standard'.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("Sku.Name").Transform(transform.ToString), + }, + { + Name: "sku_tier", + Description: "The billing tier of this particular SKU. Valid values are: 'Basic', 'Standard', 'Premium'.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("Sku.Tier"), + }, + { + Name: "updated_at", + Description: "The time the namespace was updated.", + Type: proto.ColumnType_TIMESTAMP, + Transform: transform.FromField("EHNamespaceProperties.UpdatedAt").Transform(convertDateToTime), + }, + { + Name: "zone_redundant", + Description: "Enabling this property creates a standard event hubs namespace in regions supported availability zones.", + Type: proto.ColumnType_BOOL, + Transform: transform.FromField("EHNamespaceProperties.ZoneRedundant"), + }, + { + Name: "encryption", + Description: "Properties of BYOK encryption description.", + Type: proto.ColumnType_JSON, + Transform: transform.FromField("EHNamespaceProperties.Encryption"), + }, + { + Name: "identity", + Description: "Describes the properties of BYOK encryption description.", + Type: proto.ColumnType_JSON, + Transform: transform.FromField("EHNamespaceProperties.Encryption"), + }, + { + Name: "network_rule_set", + Description: "Describes the network rule set for specified namespace. The EventHub Namespace must be Premium in order to attach a EventHub Namespace Network Rule Set.", + Type: proto.ColumnType_JSON, + Hydrate: getNetworkRuleSet, + Transform: transform.FromValue(), + }, + + // 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 column + { + Name: "region", + Description: ColumnDescriptionRegion, + Type: proto.ColumnType_STRING, + Transform: transform.FromField("Location").Transform(formatRegion).Transform(toLower), + }, + { + Name: "resource_group", + Description: ColumnDescriptionResourceGroup, + Type: proto.ColumnType_STRING, + Transform: transform.FromField("ID").Transform(extractResourceGroupFromID), + }, + { + Name: "subscription_id", + Description: ColumnDescriptionSubscription, + Type: proto.ColumnType_STRING, + Transform: transform.FromField("ID").Transform(idToSubscriptionID), + }, + }, + } +} + +//// LIST FUNCTION + +func listEventHubNamespaces(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + plugin.Logger(ctx).Trace("listEventHubNamespaces") + + // Create session + session, err := GetNewSession(ctx, d, "MANAGEMENT") + if err != nil { + return nil, err + } + + subscriptionID := session.SubscriptionID + client := eventhub.NewNamespacesClient(subscriptionID) + client.Authorizer = session.Authorizer + + pagesLeft := true + for pagesLeft { + result, err := client.List(context.Background()) + if err != nil { + return nil, err + } + + for _, namespace := range result.Values() { + d.StreamListItem(ctx, namespace) + } + result.NextWithContext(context.Background()) + pagesLeft = result.NotDone() + } + + return nil, nil +} + +//// HYDRATE FUNCTIONS + +func getEventHubNamespace(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + plugin.Logger(ctx).Trace("getEventHubNamespace") + + name := d.KeyColumnQuals["name"].GetStringValue() + resourceGroup := d.KeyColumnQuals["resource_group"].GetStringValue() + + // Return nil, if no input provided + if name == "" || resourceGroup == "" { + return nil, nil + } + + // Create session + session, err := GetNewSession(ctx, d, "MANAGEMENT") + if err != nil { + return nil, err + } + subscriptionID := session.SubscriptionID + client := eventhub.NewNamespacesClient(subscriptionID) + client.Authorizer = session.Authorizer + + op, err := client.Get(context.Background(), resourceGroup, name) + if err != nil { + return nil, err + } + + return op, nil +} + +func getNetworkRuleSet(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + plugin.Logger(ctx).Trace("getNetworkRuleSet") + + // Create session + session, err := GetNewSession(ctx, d, "MANAGEMENT") + if err != nil { + return nil, err + } + subscriptionID := session.SubscriptionID + networkClient := eventhub.NewNamespacesClient(subscriptionID) + networkClient.Authorizer = session.Authorizer + + namespace := h.Item.(eventhub.EHNamespace) + resourceGroupName := strings.Split(string(*namespace.ID), "/")[4] + + op, err := networkClient.GetNetworkRuleSet(context.Background(), resourceGroupName, *namespace.Name) + if err != nil { + return nil, err + } + + return op, nil +} diff --git a/docs/tables/azure_eventhub_namespace.md b/docs/tables/azure_eventhub_namespace.md new file mode 100644 index 00000000..b7a4806c --- /dev/null +++ b/docs/tables/azure_eventhub_namespace.md @@ -0,0 +1,60 @@ +# Table: azure_eventhub_namespace + +An Event Hubs namespace provides DNS integrated network endpoints and a range of access control and network integration management features such as IP filtering, virtual network service endpoint, and Private Link and is the management container for one of multiple Event Hub instances (or topics, in Kafka parlance). + +## Examples + +### Basic info + +```sql +select + name, + id, + type, + provisioning_state, + created_at +from + azure_eventhub_namespace; +``` + +### List namespaces not configured to use virtual network service endpoint + +```sql +select + name, + id, + type, + network_rule_set -> 'properties' -> 'virtualNetworkRules' as virtual_network_rules +from + azure_eventhub_namespace +where + network_rule_set -> 'properties' -> 'virtualNetworkRules' = '[]'; +``` + +### List unencrypted namespaces + +```sql +select + name, + id, + type, + encryption +from + azure_eventhub_namespace +where + encryption is null; +``` + +### List namespaces with auto-inflate disabled + +```sql +select + name, + id, + type, + is_auto_inflate_enabled +from + azure_eventhub_namespace +where + not is_auto_inflate_enabled; +```