diff --git a/azurerm/internal/services/kusto/client.go b/azurerm/internal/services/kusto/client.go index 531fc0275b31..6462e4e7fb99 100644 --- a/azurerm/internal/services/kusto/client.go +++ b/azurerm/internal/services/kusto/client.go @@ -6,8 +6,9 @@ import ( ) type Client struct { - ClustersClient *kusto.ClustersClient - DatabasesClient *kusto.DatabasesClient + ClustersClient *kusto.ClustersClient + DatabasesClient *kusto.DatabasesClient + DataConnectionsClient *kusto.DataConnectionsClient } func BuildClient(o *common.ClientOptions) *Client { @@ -17,8 +18,12 @@ func BuildClient(o *common.ClientOptions) *Client { DatabasesClient := kusto.NewDatabasesClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) o.ConfigureClient(&DatabasesClient.Client, o.ResourceManagerAuthorizer) + DataConnectionsClient := kusto.NewDataConnectionsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) + o.ConfigureClient(&DataConnectionsClient.Client, o.ResourceManagerAuthorizer) + return &Client{ - ClustersClient: &ClustersClient, - DatabasesClient: &DatabasesClient, + ClustersClient: &ClustersClient, + DatabasesClient: &DatabasesClient, + DataConnectionsClient: &DataConnectionsClient, } } diff --git a/azurerm/provider.go b/azurerm/provider.go index 6aafed3f3585..a6b1d37bc4f3 100644 --- a/azurerm/provider.go +++ b/azurerm/provider.go @@ -298,6 +298,7 @@ func Provider() terraform.ResourceProvider { "azurerm_kubernetes_cluster": resourceArmKubernetesCluster(), "azurerm_kusto_cluster": resourceArmKustoCluster(), "azurerm_kusto_database": resourceArmKustoDatabase(), + "azurerm_kusto_eventhub_data_connection": resourceArmKustoEventHubDataConnection(), "azurerm_lb_backend_address_pool": resourceArmLoadBalancerBackendAddressPool(), "azurerm_lb_nat_pool": resourceArmLoadBalancerNatPool(), "azurerm_lb_nat_rule": resourceArmLoadBalancerNatRule(), diff --git a/azurerm/resource_arm_kusto_eventhub_data_connection.go b/azurerm/resource_arm_kusto_eventhub_data_connection.go new file mode 100644 index 000000000000..055babed6370 --- /dev/null +++ b/azurerm/resource_arm_kusto_eventhub_data_connection.go @@ -0,0 +1,321 @@ +package azurerm + +import ( + "fmt" + "log" + "regexp" + + "github.com/Azure/azure-sdk-for-go/services/kusto/mgmt/2019-05-15/kusto" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmKustoEventHubDataConnection() *schema.Resource { + return &schema.Resource{ + Create: resourceArmKustoEventHubDataConnectionCreateUpdate, + Read: resourceArmKustoEventHubDataConnectionRead, + Update: resourceArmKustoEventHubDataConnectionCreateUpdate, + Delete: resourceArmKustoEventHubDataConnectionDelete, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateAzureRMKustoDataConnectionName, + }, + + "resource_group_name": azure.SchemaResourceGroupName(), + + "location": azure.SchemaLocation(), + + "cluster_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateAzureRMKustoClusterName, + }, + + "database_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateAzureRMKustoDatabaseName, + }, + + "eventhub_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: azure.ValidateResourceID, + }, + + "consumer_group": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: azure.ValidateEventHubConsumerName(), + }, + + "table_name": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateAzureRMKustoEntityName, + }, + + "mapping_rule_name": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateAzureRMKustoEntityName, + }, + + "data_format": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + string(kusto.AVRO), + string(kusto.CSV), + string(kusto.JSON), + string(kusto.MULTIJSON), + string(kusto.PSV), + string(kusto.RAW), + string(kusto.SCSV), + string(kusto.SINGLEJSON), + string(kusto.SOHSV), + string(kusto.TSV), + string(kusto.TXT), + }, false), + }, + }, + CustomizeDiff: func(d *schema.ResourceDiff, _ interface{}) error { + _, hasTableName := d.GetOk("table_name") + _, hasMappingRuleName := d.GetOk("mapping_rule_name") + _, hasDataFormat := d.GetOk("data_format") + + if !(AllEquals(hasTableName, hasMappingRuleName, hasDataFormat)) { + return fmt.Errorf("if one of the target table properties `table_name`, `mapping_rule_name` or `data_format` are set, the other values must also be defined") + } + + return nil + }, + } +} + +func AllEquals(v ...interface{}) bool { + if len(v) > 1 { + a := v[0] + for _, s := range v { + if a != s { + return false + } + } + } + return true +} + +func resourceArmKustoEventHubDataConnectionCreateUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).Kusto.DataConnectionsClient + ctx, cancel := timeouts.ForDelete(meta.(*ArmClient).StopContext, d) + defer cancel() + + log.Printf("[INFO] preparing arguments for Azure Kusto EventHub Data Connection creation.") + + name := d.Get("name").(string) + resourceGroup := d.Get("resource_group_name").(string) + clusterName := d.Get("cluster_name").(string) + databaseName := d.Get("database_name").(string) + + if requireResourcesToBeImported && d.IsNewResource() { + connectionModel, err := client.Get(ctx, resourceGroup, clusterName, databaseName, name) + if err != nil { + if !utils.ResponseWasNotFound(connectionModel.Response) { + return fmt.Errorf("Error checking for presence of existing Kusto EventHub Data Connection %q (Resource Group %q, Cluster %q, Database %q): %s", name, resourceGroup, clusterName, databaseName, err) + } + } + + if dataConnection, ok := connectionModel.Value.(kusto.EventHubDataConnection); ok { + if dataConnection.ID != nil && *dataConnection.ID != "" { + return tf.ImportAsExistsError("azurerm_kusto_eventhub_data_connection", *dataConnection.ID) + } + } + } + + location := azure.NormalizeLocation(d.Get("location").(string)) + + eventHubDataConnectionProperties := expandKustoEventHubDataConnectionProperties(d) + + dataConnection1 := kusto.EventHubDataConnection{ + Name: &name, + Location: &location, + EventHubConnectionProperties: eventHubDataConnectionProperties, + } + + future, err := client.CreateOrUpdate(ctx, resourceGroup, clusterName, databaseName, name, dataConnection1) + if err != nil { + return fmt.Errorf("Error creating or updating Kusto EventHub Data Connection %q (Resource Group %q, Cluster %q, Database: %q): %+v", name, resourceGroup, clusterName, databaseName, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error waiting for completion of Kusto EventHub Data Connection %q (Resource Group %q, Cluster %q, Database: %q): %+v", name, resourceGroup, clusterName, databaseName, err) + } + + connectionModel, getDetailsErr := client.Get(ctx, resourceGroup, clusterName, databaseName, name) + + if getDetailsErr != nil { + return fmt.Errorf("Error retrieving Kusto EventHub Data Connection %q (Resource Group %q, Cluster %q, Database: %q): %+v", name, resourceGroup, clusterName, databaseName, err) + } + + if dataConnection, ok := connectionModel.Value.(kusto.EventHubDataConnection); ok { + if dataConnection.ID == nil { + return fmt.Errorf("Cannot read ID for Kusto EventHub Data Connection %q (Resource Group %q, Cluster %q, Database: %q): %+v", name, resourceGroup, clusterName, databaseName, err) + } + + d.SetId(*dataConnection.ID) + } + + return resourceArmKustoEventHubDataConnectionRead(d, meta) +} + +func resourceArmKustoEventHubDataConnectionRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).Kusto.DataConnectionsClient + ctx, cancel := timeouts.ForDelete(meta.(*ArmClient).StopContext, d) + defer cancel() + + id, err := azure.ParseAzureResourceID(d.Id()) + if err != nil { + return err + } + + resourceGroup := id.ResourceGroup + clusterName := id.Path["Clusters"] + databaseName := id.Path["Databases"] + name := id.Path["DataConnections"] + + connectionModel, err := client.Get(ctx, resourceGroup, clusterName, databaseName, name) + + if err != nil { + if utils.ResponseWasNotFound(connectionModel.Response) { + d.SetId("") + return nil + } + return fmt.Errorf("Error retrieving Kusto EventHub Data Connection %q (Resource Group %q, Cluster %q, Database %q): %+v", name, resourceGroup, clusterName, databaseName, err) + } + + d.Set("name", name) + d.Set("resource_group_name", resourceGroup) + d.Set("cluster_name", clusterName) + d.Set("database_name", databaseName) + + if dataConnection, ok := connectionModel.Value.(kusto.EventHubDataConnection); ok { + if location := dataConnection.Location; location != nil { + d.Set("location", azure.NormalizeLocation(*location)) + } + + if props := dataConnection.EventHubConnectionProperties; props != nil { + d.Set("eventhub_id", props.EventHubResourceID) + d.Set("consumer_group", props.ConsumerGroup) + d.Set("table_name", props.TableName) + d.Set("mapping_rule_name", props.MappingRuleName) + d.Set("data_format", props.DataFormat) + } + } + + return nil +} + +func resourceArmKustoEventHubDataConnectionDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).Kusto.DataConnectionsClient + ctx, cancel := timeouts.ForDelete(meta.(*ArmClient).StopContext, d) + defer cancel() + + id, err := azure.ParseAzureResourceID(d.Id()) + if err != nil { + return err + } + + resourceGroup := id.ResourceGroup + clusterName := id.Path["Clusters"] + databaseName := id.Path["Databases"] + name := id.Path["DataConnections"] + + future, err := client.Delete(ctx, resourceGroup, clusterName, databaseName, name) + if err != nil { + return fmt.Errorf("Error deleting Kusto EventHub Data Connection %q (Resource Group %q, Cluster %q, Database %q): %+v", name, resourceGroup, clusterName, databaseName, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error waiting for deletion of Kusto EventHub Data Connection %q (Resource Group %q, Cluster %q, Database %q): %+v", name, resourceGroup, clusterName, databaseName, err) + } + + return nil +} + +func validateAzureRMKustoDataConnectionName(v interface{}, k string) (warnings []string, errors []error) { + name := v.(string) + + if regexp.MustCompile(`^[\s]+$`).MatchString(name) { + errors = append(errors, fmt.Errorf("%q must not consist of whitespaces only", k)) + } + + if !regexp.MustCompile(`^[a-zA-Z0-9\s.-]+$`).MatchString(name) { + errors = append(errors, fmt.Errorf("%q may only contain letters, digits, whitespaces, dashes and dots: %q", k, name)) + } + + if len(name) > 40 { + errors = append(errors, fmt.Errorf("%q must be (inclusive) between 1 and 40 characters long but is %d", k, len(name))) + } + + return warnings, errors +} + +func validateAzureRMKustoEntityName(v interface{}, k string) (warnings []string, errors []error) { + name := v.(string) + + if regexp.MustCompile(`^[\s]+$`).MatchString(name) { + errors = append(errors, fmt.Errorf("%q must not consist of whitespaces only", k)) + } + + if !regexp.MustCompile(`^[a-zA-Z0-9_\s.-]+$`).MatchString(name) { + errors = append(errors, fmt.Errorf("%q may only contain letters, digits, underscores, spaces, dashes and dots: %q", k, name)) + } + + if len(name) > 1024 { + errors = append(errors, fmt.Errorf("%q must be (inclusive) between 1 and 1024 characters long but is %d", k, len(name))) + } + + return warnings, errors +} + +func expandKustoEventHubDataConnectionProperties(d *schema.ResourceData) *kusto.EventHubConnectionProperties { + eventHubConnectionProperties := &kusto.EventHubConnectionProperties{} + + if eventhubResourceId, ok := d.GetOk("eventhub_id"); ok { + eventHubConnectionProperties.EventHubResourceID = utils.String(eventhubResourceId.(string)) + } + + if consumerGroup, ok := d.GetOk("consumer_group"); ok { + eventHubConnectionProperties.ConsumerGroup = utils.String(consumerGroup.(string)) + } + + if tableName, ok := d.GetOk("table_name"); ok { + eventHubConnectionProperties.TableName = utils.String(tableName.(string)) + } + + if mappingRuleName, ok := d.GetOk("mapping_rule_name"); ok { + eventHubConnectionProperties.MappingRuleName = utils.String(mappingRuleName.(string)) + } + + if df, ok := d.GetOk("data_format"); ok { + eventHubConnectionProperties.DataFormat = kusto.DataFormat(df.(string)) + } + + return eventHubConnectionProperties +} diff --git a/azurerm/resource_arm_kusto_eventhub_data_connection_test.go b/azurerm/resource_arm_kusto_eventhub_data_connection_test.go new file mode 100644 index 000000000000..38c72cead389 --- /dev/null +++ b/azurerm/resource_arm_kusto_eventhub_data_connection_test.go @@ -0,0 +1,165 @@ +package azurerm + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func TestAccAzureRMKustoEventHubDataConnection_basic(t *testing.T) { + resourceName := "azurerm_kusto_eventhub_data_connection.test" + ri := tf.AccRandTimeInt() + rs := acctest.RandString(6) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMKustoEventHubDataConnectionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMKustoEventHubDataConnection_basic(ri, rs, testLocation()), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMKustoEventHubDataConnectionExists(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccAzureRMKustoEventHubDataConnection_basic(rInt int, rs string, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_kusto_cluster" "test" { + name = "acctestkc%s" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + + sku { + name = "Dev(No SLA)_Standard_D11_v2" + capacity = 1 + } +} + +resource "azurerm_kusto_database" "test" { + name = "acctestkd-%d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + cluster_name = "${azurerm_kusto_cluster.test.name}" +} + +resource "azurerm_eventhub_namespace" "test" { + name = "acctesteventhubnamespace-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + sku = "Standard" +} + +resource "azurerm_eventhub" "test" { + name = "acctesteventhub-%d" + namespace_name = "${azurerm_eventhub_namespace.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + partition_count = 1 + message_retention = 1 +} + +resource "azurerm_eventhub_consumer_group" "test" { + name = "acctesteventhubcg-%d" + namespace_name = "${azurerm_eventhub_namespace.test.name}" + eventhub_name = "${azurerm_eventhub.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" +} + +resource "azurerm_kusto_eventhub_data_connection" "test" { + name = "acctestkedc-%d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + cluster_name = "${azurerm_kusto_cluster.test.name}" + database_name = "${azurerm_kusto_database.test.name}" + + eventhub_id = "${azurerm_eventhub.test.id}" + consumer_group = "${azurerm_eventhub_consumer_group.test.name}" +} +`, rInt, location, rs, rInt, rInt, rInt, rInt, rInt) +} + +func testCheckAzureRMKustoEventHubDataConnectionDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*ArmClient).Kusto.DataConnectionsClient + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_kusto_eventhub_data_connection" { + continue + } + + resourceGroup := rs.Primary.Attributes["resource_group_name"] + clusterName := rs.Primary.Attributes["cluster_name"] + databaseName := rs.Primary.Attributes["database_name"] + name := rs.Primary.Attributes["name"] + + ctx := testAccProvider.Meta().(*ArmClient).StopContext + resp, err := client.Get(ctx, resourceGroup, clusterName, databaseName, name) + + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return nil + } + return err + } + + return nil + } + + return nil +} + +func testCheckAzureRMKustoEventHubDataConnectionExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + // Ensure we have enough information in state to look up in API + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } + + name := rs.Primary.Attributes["name"] + resourceGroup, hasResourceGroup := rs.Primary.Attributes["resource_group_name"] + if !hasResourceGroup { + return fmt.Errorf("Bad: no resource group found in state for Kusto EventHub Data Connection: %s", name) + } + + clusterName, hasClusterName := rs.Primary.Attributes["cluster_name"] + if !hasClusterName { + return fmt.Errorf("Bad: no resource group found in state for Kusto EventHub Data Connection: %s", name) + } + + databaseName, hasDatabaseName := rs.Primary.Attributes["database_name"] + if !hasDatabaseName { + return fmt.Errorf("Bad: no resource group found in state for Kusto EventHub Data Connection: %s", name) + } + + client := testAccProvider.Meta().(*ArmClient).Kusto.DataConnectionsClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + resp, err := client.Get(ctx, resourceGroup, clusterName, databaseName, name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Bad: Kusto EventHub Data Connection %q (resource group: %q, cluster: %q, database: %q) does not exist", name, resourceGroup, clusterName, databaseName) + } + + return fmt.Errorf("Bad: Get on DataConnectionsClient: %+v", err) + } + + return nil + } +} diff --git a/website/azurerm.erb b/website/azurerm.erb index a15f20223823..f2af5ba183fd 100644 --- a/website/azurerm.erb +++ b/website/azurerm.erb @@ -925,6 +925,21 @@ +